mirror of https://gitee.com/openkylin/linux.git
R-Car DU support for the D3 and E3 SoCs (v4.20)
-----BEGIN PGP SIGNATURE----- iQJWBAABCgBAFiEEvZRkio5H7O2/GZsYYiVdKZ4oCyQFAlupXvAiHGxhdXJlbnQu cGluY2hhcnRAaWRlYXNvbmJvYXJkLmNvbQAKCRBiJV0pnigLJECLD/9aPnSJ0l12 7PbIac4zMfkeM4+PMZi747+CtF4a9N/uZ1LMXUgV5DM2o2iQwVy/UAmRvPxIC1im 5ioSa2RBMvsdVn2ICthx7lt+8Q09Ypo+6ybOSIRzMVAtNkodUAjZKKXsZ1nuPiIZ Tz2z+4Mu6+8sNkUyFb5JqalqAJpBzOQ8n2AdT6bFUA6g5yJ+JrFWVTnwZck5ZG4e KSIe7ncs2uil9VIKGALHYuiYqRZYc3FZzmdO7EN+DISd4EZVp1sOwn9jxWR+bLOw XBjvoRSNXRTgjSw9UxBlCFjypjzth3BaD6hrJyMr1M5XC5W0L73KUyabzl3Ljd2N 8vliWyeXHvMKf3EJOmAYX2/ag7CsHrPe5AdT+jnEY+R2/IdqU+DhFykMbzcL+fsT Y4Bqsu9BXgq4UvnUz1qkcBP/hNmf+QlC5vrT8p5Do0fRZhyb+5ESEKrnNYAtNV7G QHRyqSMsR8Wb0nz6A39K8uCRGV46Q17WB1PXlLo9emBn4hmxqJjkNoPihbXMiptc a00B0JoJuwv6wQlRiexGlmLoMGHscj2NXR7oEq1XNXcCYtGZdOBuqIb7Vjvx3VD5 Id84l/2VIJ+tQGmtK+hhlJLC9AIe972K34M+ZwTBYaUv9ZN9/paVnRG7ucaGnyiF n0WvK4B98PztxCiVdROYwnoIVNyS1nnmyQ== =Mt3D -----END PGP SIGNATURE----- Merge tag 'du-next-20180925' of git://linuxtv.org/pinchartl/media into drm-next R-Car DU support for the D3 and E3 SoCs (v4.20) Signed-off-by: Dave Airlie <airlied@redhat.com> From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Link: https://patchwork.freedesktop.org/patch/msgid/3289904.RCOHkcp7u8@avalon
This commit is contained in:
commit
2e240beefe
|
@ -15,10 +15,21 @@ Required properties:
|
|||
- "renesas,r8a7796-lvds" for R8A7796 (R-Car M3-W) compatible LVDS encoders
|
||||
- "renesas,r8a77970-lvds" for R8A77970 (R-Car V3M) compatible LVDS encoders
|
||||
- "renesas,r8a77980-lvds" for R8A77980 (R-Car V3H) compatible LVDS encoders
|
||||
- "renesas,r8a77990-lvds" for R8A77990 (R-Car E3) compatible LVDS encoders
|
||||
- "renesas,r8a77995-lvds" for R8A77995 (R-Car D3) compatible LVDS encoders
|
||||
|
||||
- reg: Base address and length for the memory-mapped registers
|
||||
- clocks: A phandle + clock-specifier pair for the functional clock
|
||||
- clocks: A list of phandles + clock-specifier pairs, one for each entry in
|
||||
the clock-names property.
|
||||
- clock-names: Name of the clocks. This property is model-dependent.
|
||||
- The functional clock, which mandatory for all models, shall be listed
|
||||
first, and shall be named "fck".
|
||||
- On R8A77990 and R8A77995, the LVDS encoder can use the EXTAL or
|
||||
DU_DOTCLKINx clocks. Those clocks are optional. When supplied they must be
|
||||
named "extal" and "dclkin.x" respectively, with "x" being the DU_DOTCLKIN
|
||||
numerical index.
|
||||
- When the clocks property only contains the functional clock, the
|
||||
clock-names property may be omitted.
|
||||
- resets: A phandle + reset specifier for the module reset
|
||||
|
||||
Required nodes:
|
||||
|
|
|
@ -16,6 +16,7 @@ Required Properties:
|
|||
- "renesas,du-r8a77965" for R8A77965 (R-Car M3-N) compatible DU
|
||||
- "renesas,du-r8a77970" for R8A77970 (R-Car V3M) compatible DU
|
||||
- "renesas,du-r8a77980" for R8A77980 (R-Car V3H) compatible DU
|
||||
- "renesas,du-r8a77990" for R8A77990 (R-Car E3) compatible DU
|
||||
- "renesas,du-r8a77995" for R8A77995 (R-Car D3) compatible DU
|
||||
|
||||
- reg: the memory-mapped I/O registers base address and length
|
||||
|
@ -63,6 +64,7 @@ corresponding to each DU output.
|
|||
R8A77965 (R-Car M3-N) DPAD 0 HDMI 0 LVDS 0 -
|
||||
R8A77970 (R-Car V3M) DPAD 0 LVDS 0 - -
|
||||
R8A77980 (R-Car V3H) DPAD 0 LVDS 0 - -
|
||||
R8A77990 (R-Car E3) DPAD 0 LVDS 0 LVDS 1 -
|
||||
R8A77995 (R-Car D3) DPAD 0 LVDS 0 LVDS 1 -
|
||||
|
||||
|
||||
|
|
|
@ -45,6 +45,23 @@ static int thc63_attach(struct drm_bridge *bridge)
|
|||
return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
|
||||
}
|
||||
|
||||
static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
/*
|
||||
* The THC63LVD1024 clock frequency range is 8 to 135 MHz in single-in
|
||||
* mode. Note that the limits are different in dual-in, single-out mode,
|
||||
* and will need to be adjusted accordingly.
|
||||
*/
|
||||
if (mode->clock < 8000)
|
||||
return MODE_CLOCK_LOW;
|
||||
|
||||
if (mode->clock > 135000)
|
||||
return MODE_CLOCK_HIGH;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static void thc63_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct thc63_dev *thc63 = to_thc63(bridge);
|
||||
|
@ -77,6 +94,7 @@ static void thc63_disable(struct drm_bridge *bridge)
|
|||
|
||||
static const struct drm_bridge_funcs thc63_bridge_func = {
|
||||
.attach = thc63_attach,
|
||||
.mode_valid = thc63_mode_valid,
|
||||
.enable = thc63_enable,
|
||||
.disable = thc63_disable,
|
||||
};
|
||||
|
|
|
@ -57,46 +57,12 @@ static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set)
|
|||
rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set);
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg,
|
||||
u32 clr, u32 set)
|
||||
void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set)
|
||||
{
|
||||
struct rcar_du_device *rcdu = rcrtc->group->dev;
|
||||
u32 value = rcar_du_read(rcdu, rcrtc->mmio_offset + reg);
|
||||
|
||||
rcar_du_write(rcdu, rcrtc->mmio_offset + reg, (value & ~clr) | set);
|
||||
}
|
||||
|
||||
static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(rcrtc->clock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(rcrtc->extclock);
|
||||
if (ret < 0)
|
||||
goto error_clock;
|
||||
|
||||
ret = rcar_du_group_get(rcrtc->group);
|
||||
if (ret < 0)
|
||||
goto error_group;
|
||||
|
||||
return 0;
|
||||
|
||||
error_group:
|
||||
clk_disable_unprepare(rcrtc->extclock);
|
||||
error_clock:
|
||||
clk_disable_unprepare(rcrtc->clock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
rcar_du_group_put(rcrtc->group);
|
||||
|
||||
clk_disable_unprepare(rcrtc->extclock);
|
||||
clk_disable_unprepare(rcrtc->clock);
|
||||
rcrtc->dsysr = (rcrtc->dsysr & ~clr) | set;
|
||||
rcar_du_write(rcdu, rcrtc->mmio_offset + DSYSR, rcrtc->dsysr);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
|
@ -294,6 +260,14 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
|
|||
rcar_du_group_write(rcrtc->group, DPLLCR, dpllcr);
|
||||
|
||||
escr = ESCR_DCLKSEL_DCLKIN | div;
|
||||
} else if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index)) {
|
||||
/*
|
||||
* Use the LVDS PLL output as the dot clock when outputting to
|
||||
* the LVDS encoder on an SoC that supports this clock routing
|
||||
* option. We use the clock directly in that case, without any
|
||||
* additional divider.
|
||||
*/
|
||||
escr = ESCR_DCLKSEL_DCLKIN;
|
||||
} else {
|
||||
struct du_clk_params params = { .diff = (unsigned long)-1 };
|
||||
|
||||
|
@ -546,6 +520,51 @@ static void rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc)
|
|||
drm_crtc_vblank_on(&rcrtc->crtc);
|
||||
}
|
||||
|
||||
static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Guard against double-get, as the function is called from both the
|
||||
* .atomic_enable() and .atomic_begin() handlers.
|
||||
*/
|
||||
if (rcrtc->initialized)
|
||||
return 0;
|
||||
|
||||
ret = clk_prepare_enable(rcrtc->clock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(rcrtc->extclock);
|
||||
if (ret < 0)
|
||||
goto error_clock;
|
||||
|
||||
ret = rcar_du_group_get(rcrtc->group);
|
||||
if (ret < 0)
|
||||
goto error_group;
|
||||
|
||||
rcar_du_crtc_setup(rcrtc);
|
||||
rcrtc->initialized = true;
|
||||
|
||||
return 0;
|
||||
|
||||
error_group:
|
||||
clk_disable_unprepare(rcrtc->extclock);
|
||||
error_clock:
|
||||
clk_disable_unprepare(rcrtc->clock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
rcar_du_group_put(rcrtc->group);
|
||||
|
||||
clk_disable_unprepare(rcrtc->extclock);
|
||||
clk_disable_unprepare(rcrtc->clock);
|
||||
|
||||
rcrtc->initialized = false;
|
||||
}
|
||||
|
||||
static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
bool interlaced;
|
||||
|
@ -556,9 +575,9 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
|
|||
* actively driven).
|
||||
*/
|
||||
interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE;
|
||||
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK | DSYSR_SCM_MASK,
|
||||
(interlaced ? DSYSR_SCM_INT_VIDEO : 0) |
|
||||
DSYSR_TVM_MASTER);
|
||||
rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK | DSYSR_SCM_MASK,
|
||||
(interlaced ? DSYSR_SCM_INT_VIDEO : 0) |
|
||||
DSYSR_TVM_MASTER);
|
||||
|
||||
rcar_du_group_start_stop(rcrtc->group, true);
|
||||
}
|
||||
|
@ -624,8 +643,13 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
|
|||
/*
|
||||
* Select switch sync mode. This stops display operation and configures
|
||||
* the HSYNC and VSYNC signals as inputs.
|
||||
*
|
||||
* TODO: Find another way to stop the display for DUs that don't support
|
||||
* TVM sync.
|
||||
*/
|
||||
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH);
|
||||
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_TVM_SYNC))
|
||||
rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK,
|
||||
DSYSR_TVM_SWITCH);
|
||||
|
||||
rcar_du_group_start_stop(rcrtc->group, false);
|
||||
}
|
||||
|
@ -639,16 +663,7 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
|
|||
{
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
|
||||
/*
|
||||
* If the CRTC has already been setup by the .atomic_begin() handler we
|
||||
* can skip the setup stage.
|
||||
*/
|
||||
if (!rcrtc->initialized) {
|
||||
rcar_du_crtc_get(rcrtc);
|
||||
rcar_du_crtc_setup(rcrtc);
|
||||
rcrtc->initialized = true;
|
||||
}
|
||||
|
||||
rcar_du_crtc_get(rcrtc);
|
||||
rcar_du_crtc_start(rcrtc);
|
||||
}
|
||||
|
||||
|
@ -667,7 +682,6 @@ static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
|
|||
}
|
||||
spin_unlock_irq(&crtc->dev->event_lock);
|
||||
|
||||
rcrtc->initialized = false;
|
||||
rcrtc->outputs = 0;
|
||||
}
|
||||
|
||||
|
@ -680,14 +694,17 @@ static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
|
|||
|
||||
/*
|
||||
* If a mode set is in progress we can be called with the CRTC disabled.
|
||||
* We then need to first setup the CRTC in order to configure planes.
|
||||
* The .atomic_enable() handler will notice and skip the CRTC setup.
|
||||
* We thus need to first get and setup the CRTC in order to configure
|
||||
* planes. We must *not* put the CRTC in .atomic_flush(), as it must be
|
||||
* kept awake until the .atomic_enable() call that will follow. The get
|
||||
* operation in .atomic_enable() will in that case be a no-op, and the
|
||||
* CRTC will be put later in .atomic_disable().
|
||||
*
|
||||
* If a mode set is not in progress the CRTC is enabled, and the
|
||||
* following get call will be a no-op. There is thus no need to belance
|
||||
* it in .atomic_flush() either.
|
||||
*/
|
||||
if (!rcrtc->initialized) {
|
||||
rcar_du_crtc_get(rcrtc);
|
||||
rcar_du_crtc_setup(rcrtc);
|
||||
rcrtc->initialized = true;
|
||||
}
|
||||
rcar_du_crtc_get(rcrtc);
|
||||
|
||||
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
|
||||
rcar_du_vsp_atomic_begin(rcrtc);
|
||||
|
@ -1108,6 +1125,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
|
|||
rcrtc->group = rgrp;
|
||||
rcrtc->mmio_offset = mmio_offsets[hwindex];
|
||||
rcrtc->index = hwindex;
|
||||
rcrtc->dsysr = (rcrtc->index % 2 ? 0 : DSYSR_DRES) | DSYSR_TVM_TVSYNC;
|
||||
|
||||
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE))
|
||||
primary = &rcrtc->vsp->planes[rcrtc->vsp_pipe].plane;
|
||||
|
|
|
@ -30,6 +30,7 @@ struct rcar_du_vsp;
|
|||
* @mmio_offset: offset of the CRTC registers in the DU MMIO block
|
||||
* @index: CRTC software and hardware index
|
||||
* @initialized: whether the CRTC has been initialized and clocks enabled
|
||||
* @dsysr: cached value of the DSYSR register
|
||||
* @vblank_enable: whether vblank events are enabled on this CRTC
|
||||
* @event: event to post when the pending page flip completes
|
||||
* @flip_wait: wait queue used to signal page flip completion
|
||||
|
@ -50,6 +51,8 @@ struct rcar_du_crtc {
|
|||
unsigned int index;
|
||||
bool initialized;
|
||||
|
||||
u32 dsysr;
|
||||
|
||||
bool vblank_enable;
|
||||
struct drm_pending_vblank_event *event;
|
||||
wait_queue_head_t flip_wait;
|
||||
|
@ -103,4 +106,6 @@ void rcar_du_crtc_route_output(struct drm_crtc *crtc,
|
|||
enum rcar_du_output output);
|
||||
void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc);
|
||||
|
||||
void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set);
|
||||
|
||||
#endif /* __RCAR_DU_CRTC_H__ */
|
||||
|
|
|
@ -36,7 +36,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7743_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -58,7 +59,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -77,7 +79,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
|
|||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7779_info = {
|
||||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_INTERLACED,
|
||||
.features = RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -99,7 +102,8 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.quirks = RCAR_DU_QUIRK_ALIGN_128B,
|
||||
.channels_mask = BIT(2) | BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
|
@ -128,7 +132,8 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -151,7 +156,8 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/* R8A7792 has two RGB outputs. */
|
||||
|
@ -170,7 +176,8 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
|
|||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -193,7 +200,8 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
|
|||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -226,7 +234,8 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
|
|||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(2) | BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -255,7 +264,8 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
|
|||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(3) | BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -284,7 +294,8 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
|
|||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE
|
||||
| RCAR_DU_FEATURE_INTERLACED,
|
||||
| RCAR_DU_FEATURE_INTERLACED
|
||||
| RCAR_DU_FEATURE_TVM_SYNC,
|
||||
.channels_mask = BIT(0),
|
||||
.routes = {
|
||||
/* R8A77970 has one RGB output and one LVDS output. */
|
||||
|
@ -300,6 +311,34 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
|
|||
.num_lvds = 1,
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7799x_info = {
|
||||
.gen = 3,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE,
|
||||
.channels_mask = BIT(1) | BIT(0),
|
||||
.routes = {
|
||||
/*
|
||||
* R8A77990 and R8A77995 have one RGB output and two LVDS
|
||||
* outputs.
|
||||
*/
|
||||
[RCAR_DU_OUTPUT_DPAD0] = {
|
||||
.possible_crtcs = BIT(0) | BIT(1),
|
||||
.port = 0,
|
||||
},
|
||||
[RCAR_DU_OUTPUT_LVDS0] = {
|
||||
.possible_crtcs = BIT(0),
|
||||
.port = 1,
|
||||
},
|
||||
[RCAR_DU_OUTPUT_LVDS1] = {
|
||||
.possible_crtcs = BIT(1),
|
||||
.port = 2,
|
||||
},
|
||||
},
|
||||
.num_lvds = 2,
|
||||
.lvds_clk_mask = BIT(1) | BIT(0),
|
||||
};
|
||||
|
||||
static const struct of_device_id rcar_du_of_table[] = {
|
||||
{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
|
||||
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
|
||||
|
@ -313,6 +352,8 @@ static const struct of_device_id rcar_du_of_table[] = {
|
|||
{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
|
||||
{ .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
|
||||
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
|
||||
{ .compatible = "renesas,du-r8a77990", .data = &rcar_du_r8a7799x_info },
|
||||
{ .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ struct rcar_du_device;
|
|||
#define RCAR_DU_FEATURE_EXT_CTRL_REGS BIT(1) /* Has extended control registers */
|
||||
#define RCAR_DU_FEATURE_VSP1_SOURCE BIT(2) /* Has inputs from VSP1 */
|
||||
#define RCAR_DU_FEATURE_INTERLACED BIT(3) /* HW supports interlaced */
|
||||
#define RCAR_DU_FEATURE_TVM_SYNC BIT(4) /* Has TV switch/sync modes */
|
||||
|
||||
#define RCAR_DU_QUIRK_ALIGN_128B BIT(0) /* Align pitches to 128 bytes */
|
||||
|
||||
|
@ -53,6 +54,7 @@ struct rcar_du_output_routing {
|
|||
* @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*)
|
||||
* @num_lvds: number of internal LVDS encoders
|
||||
* @dpll_mask: bit mask of DU channels equipped with a DPLL
|
||||
* @lvds_clk_mask: bitmask of channels that can use the LVDS clock as dot clock
|
||||
*/
|
||||
struct rcar_du_device_info {
|
||||
unsigned int gen;
|
||||
|
@ -62,6 +64,7 @@ struct rcar_du_device_info {
|
|||
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
|
||||
unsigned int num_lvds;
|
||||
unsigned int dpll_mask;
|
||||
unsigned int lvds_clk_mask;
|
||||
};
|
||||
|
||||
#define RCAR_DU_MAX_CRTCS 4
|
||||
|
|
|
@ -56,8 +56,6 @@ static void rcar_du_group_setup_pins(struct rcar_du_group *rgrp)
|
|||
static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp)
|
||||
{
|
||||
struct rcar_du_device *rcdu = rgrp->dev;
|
||||
unsigned int possible_crtcs =
|
||||
rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs;
|
||||
u32 defr8 = DEFR8_CODE;
|
||||
|
||||
if (rcdu->info->gen < 3) {
|
||||
|
@ -69,26 +67,71 @@ static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp)
|
|||
* DU instances that support it.
|
||||
*/
|
||||
if (rgrp->index == 0) {
|
||||
if (possible_crtcs > 1)
|
||||
defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
|
||||
defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
|
||||
if (rgrp->dev->vspd1_sink == 2)
|
||||
defr8 |= DEFR8_VSCS;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* On Gen3 VSPD routing can't be configured, but DPAD routing
|
||||
* needs to be set despite having a single option available.
|
||||
* On Gen3 VSPD routing can't be configured, and DPAD routing
|
||||
* is set in the group corresponding to the DPAD output (no Gen3
|
||||
* SoC has multiple DPAD sources belonging to separate groups).
|
||||
*/
|
||||
unsigned int rgb_crtc = ffs(possible_crtcs) - 1;
|
||||
struct rcar_du_crtc *crtc = &rcdu->crtcs[rgb_crtc];
|
||||
|
||||
if (crtc->index / 2 == rgrp->index)
|
||||
defr8 |= DEFR8_DRGBS_DU(crtc->index);
|
||||
if (rgrp->index == rcdu->dpad0_source / 2)
|
||||
defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
|
||||
}
|
||||
|
||||
rcar_du_group_write(rgrp, DEFR8, defr8);
|
||||
}
|
||||
|
||||
static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp)
|
||||
{
|
||||
struct rcar_du_device *rcdu = rgrp->dev;
|
||||
struct rcar_du_crtc *rcrtc;
|
||||
unsigned int num_crtcs = 0;
|
||||
unsigned int i;
|
||||
u32 didsr;
|
||||
|
||||
/*
|
||||
* Configure input dot clock routing with a hardcoded configuration. If
|
||||
* the DU channel can use the LVDS encoder output clock as the dot
|
||||
* clock, do so. Otherwise route DU_DOTCLKINn signal to DUn.
|
||||
*
|
||||
* Each channel can then select between the dot clock configured here
|
||||
* and the clock provided by the CPG through the ESCR register.
|
||||
*/
|
||||
if (rcdu->info->gen < 3 && rgrp->index == 0) {
|
||||
/*
|
||||
* On Gen2 a single register in the first group controls dot
|
||||
* clock selection for all channels.
|
||||
*/
|
||||
rcrtc = rcdu->crtcs;
|
||||
num_crtcs = rcdu->num_crtcs;
|
||||
} else if (rcdu->info->gen == 3 && rgrp->num_crtcs > 1) {
|
||||
/*
|
||||
* On Gen3 dot clocks are setup through per-group registers,
|
||||
* only available when the group has two channels.
|
||||
*/
|
||||
rcrtc = &rcdu->crtcs[rgrp->index * 2];
|
||||
num_crtcs = rgrp->num_crtcs;
|
||||
}
|
||||
|
||||
if (!num_crtcs)
|
||||
return;
|
||||
|
||||
didsr = DIDSR_CODE;
|
||||
for (i = 0; i < num_crtcs; ++i, ++rcrtc) {
|
||||
if (rcdu->info->lvds_clk_mask & BIT(rcrtc->index))
|
||||
didsr |= DIDSR_LCDS_LVDS0(i)
|
||||
| DIDSR_PDCS_CLK(i, 0);
|
||||
else
|
||||
didsr |= DIDSR_LCDS_DCLKIN(i)
|
||||
| DIDSR_PDCS_CLK(i, 0);
|
||||
}
|
||||
|
||||
rcar_du_group_write(rgrp, DIDSR, didsr);
|
||||
}
|
||||
|
||||
static void rcar_du_group_setup(struct rcar_du_group *rgrp)
|
||||
{
|
||||
struct rcar_du_device *rcdu = rgrp->dev;
|
||||
|
@ -106,21 +149,7 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
|
|||
|
||||
if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_EXT_CTRL_REGS)) {
|
||||
rcar_du_group_setup_defr8(rgrp);
|
||||
|
||||
/*
|
||||
* Configure input dot clock routing. We currently hardcode the
|
||||
* configuration to routing DOTCLKINn to DUn. Register fields
|
||||
* depend on the DU generation, but the resulting value is 0 in
|
||||
* all cases.
|
||||
*
|
||||
* On Gen2 a single register in the first group controls dot
|
||||
* clock selection for all channels, while on Gen3 dot clocks
|
||||
* are setup through per-group registers, only available when
|
||||
* the group has two channels.
|
||||
*/
|
||||
if ((rcdu->info->gen < 3 && rgrp->index == 0) ||
|
||||
(rcdu->info->gen == 3 && rgrp->num_crtcs > 1))
|
||||
rcar_du_group_write(rgrp, DIDSR, DIDSR_CODE);
|
||||
rcar_du_group_setup_didsr(rgrp);
|
||||
}
|
||||
|
||||
if (rcdu->info->gen >= 3)
|
||||
|
@ -173,9 +202,10 @@ void rcar_du_group_put(struct rcar_du_group *rgrp)
|
|||
|
||||
static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
|
||||
{
|
||||
rcar_du_group_write(rgrp, DSYSR,
|
||||
(rcar_du_group_read(rgrp, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) |
|
||||
(start ? DSYSR_DEN : DSYSR_DRES));
|
||||
struct rcar_du_crtc *rcrtc = &rgrp->dev->crtcs[rgrp->index * 2];
|
||||
|
||||
rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_DRES | DSYSR_DEN,
|
||||
start ? DSYSR_DEN : DSYSR_DRES);
|
||||
}
|
||||
|
||||
void rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
|
||||
|
|
|
@ -544,6 +544,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
|
|||
struct drm_device *dev = rcdu->ddev;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_fbdev_cma *fbdev;
|
||||
unsigned int dpad0_sources;
|
||||
unsigned int num_encoders;
|
||||
unsigned int num_groups;
|
||||
unsigned int swindex;
|
||||
|
@ -666,6 +667,17 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
|
|||
encoder->possible_clones = (1 << num_encoders) - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the default DPAD0 source to the index of the first DU
|
||||
* channel that can be connected to DPAD0. The exact value doesn't
|
||||
* matter as it should be overwritten by mode setting for the RGB
|
||||
* output, but it is nonetheless required to ensure a valid initial
|
||||
* hardware configuration on Gen3 where DU0 can't always be connected to
|
||||
* DPAD0.
|
||||
*/
|
||||
dpad0_sources = rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs;
|
||||
rcdu->dpad0_source = ffs(dpad0_sources) - 1;
|
||||
|
||||
drm_mode_config_reset(dev);
|
||||
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
#include "rcar_lvds_regs.h"
|
||||
|
||||
struct rcar_lvds;
|
||||
|
||||
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
|
||||
enum rcar_lvds_mode {
|
||||
RCAR_LVDS_MODE_JEIDA = 0,
|
||||
|
@ -31,14 +33,16 @@ enum rcar_lvds_mode {
|
|||
RCAR_LVDS_MODE_VESA = 4,
|
||||
};
|
||||
|
||||
#define RCAR_LVDS_QUIRK_LANES (1 << 0) /* LVDS lanes 1 and 3 inverted */
|
||||
#define RCAR_LVDS_QUIRK_GEN2_PLLCR (1 << 1) /* LVDPLLCR has gen2 layout */
|
||||
#define RCAR_LVDS_QUIRK_GEN3_LVEN (1 << 2) /* LVEN bit needs to be set */
|
||||
/* on R8A77970/R8A7799x */
|
||||
#define RCAR_LVDS_QUIRK_LANES BIT(0) /* LVDS lanes 1 and 3 inverted */
|
||||
#define RCAR_LVDS_QUIRK_GEN3_LVEN BIT(1) /* LVEN bit needs to be set on R8A77970/R8A7799x */
|
||||
#define RCAR_LVDS_QUIRK_PWD BIT(2) /* PWD bit available (all of Gen3 but E3) */
|
||||
#define RCAR_LVDS_QUIRK_EXT_PLL BIT(3) /* Has extended PLL */
|
||||
#define RCAR_LVDS_QUIRK_DUAL_LINK BIT(4) /* Supports dual-link operation */
|
||||
|
||||
struct rcar_lvds_device_info {
|
||||
unsigned int gen;
|
||||
unsigned int quirks;
|
||||
void (*pll_setup)(struct rcar_lvds *lvds, unsigned int freq);
|
||||
};
|
||||
|
||||
struct rcar_lvds {
|
||||
|
@ -52,7 +56,11 @@ struct rcar_lvds {
|
|||
struct drm_panel *panel;
|
||||
|
||||
void __iomem *mmio;
|
||||
struct clk *clock;
|
||||
struct {
|
||||
struct clk *mod; /* CPG module clock */
|
||||
struct clk *extal; /* External clock */
|
||||
struct clk *dotclkin[2]; /* External DU clocks */
|
||||
} clocks;
|
||||
bool enabled;
|
||||
|
||||
struct drm_display_mode display_mode;
|
||||
|
@ -128,33 +136,216 @@ static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
|
|||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bridge
|
||||
* PLL Setup
|
||||
*/
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
|
||||
static void rcar_lvds_pll_setup_gen2(struct rcar_lvds *lvds, unsigned int freq)
|
||||
{
|
||||
if (freq < 39000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
||||
else if (freq < 61000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
||||
else if (freq < 121000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
||||
u32 val;
|
||||
|
||||
if (freq < 39000000)
|
||||
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
||||
else if (freq < 61000000)
|
||||
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
||||
else if (freq < 121000000)
|
||||
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
||||
else
|
||||
return LVDPLLCR_PLLDLYCNT_150M;
|
||||
val = LVDPLLCR_PLLDLYCNT_150M;
|
||||
|
||||
rcar_lvds_write(lvds, LVDPLLCR, val);
|
||||
}
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
|
||||
static void rcar_lvds_pll_setup_gen3(struct rcar_lvds *lvds, unsigned int freq)
|
||||
{
|
||||
if (freq < 42000)
|
||||
return LVDPLLCR_PLLDIVCNT_42M;
|
||||
else if (freq < 85000)
|
||||
return LVDPLLCR_PLLDIVCNT_85M;
|
||||
else if (freq < 128000)
|
||||
return LVDPLLCR_PLLDIVCNT_128M;
|
||||
u32 val;
|
||||
|
||||
if (freq < 42000000)
|
||||
val = LVDPLLCR_PLLDIVCNT_42M;
|
||||
else if (freq < 85000000)
|
||||
val = LVDPLLCR_PLLDIVCNT_85M;
|
||||
else if (freq < 128000000)
|
||||
val = LVDPLLCR_PLLDIVCNT_128M;
|
||||
else
|
||||
return LVDPLLCR_PLLDIVCNT_148M;
|
||||
val = LVDPLLCR_PLLDIVCNT_148M;
|
||||
|
||||
rcar_lvds_write(lvds, LVDPLLCR, val);
|
||||
}
|
||||
|
||||
struct pll_info {
|
||||
unsigned long diff;
|
||||
unsigned int pll_m;
|
||||
unsigned int pll_n;
|
||||
unsigned int pll_e;
|
||||
unsigned int div;
|
||||
u32 clksel;
|
||||
};
|
||||
|
||||
static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
|
||||
unsigned long target, struct pll_info *pll,
|
||||
u32 clksel)
|
||||
{
|
||||
unsigned long output;
|
||||
unsigned long fin;
|
||||
unsigned int m_min;
|
||||
unsigned int m_max;
|
||||
unsigned int m;
|
||||
int error;
|
||||
|
||||
if (!clk)
|
||||
return;
|
||||
|
||||
/*
|
||||
* The LVDS PLL is made of a pre-divider and a multiplier (strangely
|
||||
* enough called M and N respectively), followed by a post-divider E.
|
||||
*
|
||||
* ,-----. ,-----. ,-----. ,-----.
|
||||
* Fin --> | 1/M | -Fpdf-> | PFD | --> | VCO | -Fvco-> | 1/E | --> Fout
|
||||
* `-----' ,-> | | `-----' | `-----'
|
||||
* | `-----' |
|
||||
* | ,-----. |
|
||||
* `-------- | 1/N | <-------'
|
||||
* `-----'
|
||||
*
|
||||
* The clock output by the PLL is then further divided by a programmable
|
||||
* divider DIV to achieve the desired target frequency. Finally, an
|
||||
* optional fixed /7 divider is used to convert the bit clock to a pixel
|
||||
* clock (as LVDS transmits 7 bits per lane per clock sample).
|
||||
*
|
||||
* ,-------. ,-----. |\
|
||||
* Fout --> | 1/DIV | --> | 1/7 | --> | |
|
||||
* `-------' | `-----' | | --> dot clock
|
||||
* `------------> | |
|
||||
* |/
|
||||
*
|
||||
* The /7 divider is optional when the LVDS PLL is used to generate a
|
||||
* dot clock for the DU RGB output, without using the LVDS encoder. We
|
||||
* don't support this configuration yet.
|
||||
*
|
||||
* The PLL allowed input frequency range is 12 MHz to 192 MHz.
|
||||
*/
|
||||
|
||||
fin = clk_get_rate(clk);
|
||||
if (fin < 12000000 || fin > 192000000)
|
||||
return;
|
||||
|
||||
/*
|
||||
* The comparison frequency range is 12 MHz to 24 MHz, which limits the
|
||||
* allowed values for the pre-divider M (normal range 1-8).
|
||||
*
|
||||
* Fpfd = Fin / M
|
||||
*/
|
||||
m_min = max_t(unsigned int, 1, DIV_ROUND_UP(fin, 24000000));
|
||||
m_max = min_t(unsigned int, 8, fin / 12000000);
|
||||
|
||||
for (m = m_min; m <= m_max; ++m) {
|
||||
unsigned long fpfd;
|
||||
unsigned int n_min;
|
||||
unsigned int n_max;
|
||||
unsigned int n;
|
||||
|
||||
/*
|
||||
* The VCO operating range is 900 Mhz to 1800 MHz, which limits
|
||||
* the allowed values for the multiplier N (normal range
|
||||
* 60-120).
|
||||
*
|
||||
* Fvco = Fin * N / M
|
||||
*/
|
||||
fpfd = fin / m;
|
||||
n_min = max_t(unsigned int, 60, DIV_ROUND_UP(900000000, fpfd));
|
||||
n_max = min_t(unsigned int, 120, 1800000000 / fpfd);
|
||||
|
||||
for (n = n_min; n < n_max; ++n) {
|
||||
unsigned long fvco;
|
||||
unsigned int e_min;
|
||||
unsigned int e;
|
||||
|
||||
/*
|
||||
* The output frequency is limited to 1039.5 MHz,
|
||||
* limiting again the allowed values for the
|
||||
* post-divider E (normal value 1, 2 or 4).
|
||||
*
|
||||
* Fout = Fvco / E
|
||||
*/
|
||||
fvco = fpfd * n;
|
||||
e_min = fvco > 1039500000 ? 1 : 0;
|
||||
|
||||
for (e = e_min; e < 3; ++e) {
|
||||
unsigned long fout;
|
||||
unsigned long diff;
|
||||
unsigned int div;
|
||||
|
||||
/*
|
||||
* Finally we have a programable divider after
|
||||
* the PLL, followed by a an optional fixed /7
|
||||
* divider.
|
||||
*/
|
||||
fout = fvco / (1 << e) / 7;
|
||||
div = DIV_ROUND_CLOSEST(fout, target);
|
||||
diff = abs(fout / div - target);
|
||||
|
||||
if (diff < pll->diff) {
|
||||
pll->diff = diff;
|
||||
pll->pll_m = m;
|
||||
pll->pll_n = n;
|
||||
pll->pll_e = e;
|
||||
pll->div = div;
|
||||
pll->clksel = clksel;
|
||||
|
||||
if (diff == 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e)
|
||||
/ 7 / pll->div;
|
||||
error = (long)(output - target) * 10000 / (long)target;
|
||||
|
||||
dev_dbg(lvds->dev,
|
||||
"%pC %lu Hz -> Fout %lu Hz (target %lu Hz, error %d.%02u%%), PLL M/N/E/DIV %u/%u/%u/%u\n",
|
||||
clk, fin, output, target, error / 100,
|
||||
error < 0 ? -error % 100 : error % 100,
|
||||
pll->pll_m, pll->pll_n, pll->pll_e, pll->div);
|
||||
}
|
||||
|
||||
static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
|
||||
{
|
||||
struct pll_info pll = { .diff = (unsigned long)-1 };
|
||||
u32 lvdpllcr;
|
||||
|
||||
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll,
|
||||
LVDPLLCR_CKSEL_DU_DOTCLKIN(0));
|
||||
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll,
|
||||
LVDPLLCR_CKSEL_DU_DOTCLKIN(1));
|
||||
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll,
|
||||
LVDPLLCR_CKSEL_EXTAL);
|
||||
|
||||
lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT
|
||||
| LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1);
|
||||
|
||||
if (pll.pll_e > 0)
|
||||
lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL
|
||||
| LVDPLLCR_PLLE(pll.pll_e - 1);
|
||||
|
||||
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
||||
|
||||
if (pll.div > 1)
|
||||
/*
|
||||
* The DIVRESET bit is a misnomer, setting it to 1 deasserts the
|
||||
* divisor reset.
|
||||
*/
|
||||
rcar_lvds_write(lvds, LVDDIV, LVDDIV_DIVSEL |
|
||||
LVDDIV_DIVRESET | LVDDIV_DIV(pll.div - 1));
|
||||
else
|
||||
rcar_lvds_write(lvds, LVDDIV, 0);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bridge
|
||||
*/
|
||||
|
||||
static void rcar_lvds_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
|
@ -164,14 +355,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||
* do we get a state pointer?
|
||||
*/
|
||||
struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
|
||||
u32 lvdpllcr;
|
||||
u32 lvdhcr;
|
||||
u32 lvdcr0;
|
||||
int ret;
|
||||
|
||||
WARN_ON(lvds->enabled);
|
||||
|
||||
ret = clk_prepare_enable(lvds->clock);
|
||||
ret = clk_prepare_enable(lvds->clocks.mod);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
|
@ -196,12 +386,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||
|
||||
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
||||
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
|
||||
/* Disable dual-link mode. */
|
||||
rcar_lvds_write(lvds, LVDSTRIPE, 0);
|
||||
}
|
||||
|
||||
/* PLL clock configuration. */
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN2_PLLCR)
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
|
||||
else
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
|
||||
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
||||
lvds->info->pll_setup(lvds, mode->clock * 1000);
|
||||
|
||||
/* Set the LVDS mode and select the input. */
|
||||
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
||||
|
@ -220,11 +411,16 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Turn the PLL on. */
|
||||
lvdcr0 |= LVDCR0_PLLON;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
|
||||
/*
|
||||
* Turn the PLL on (simple PLL only, extended PLL is fully
|
||||
* controlled through LVDPLLCR).
|
||||
*/
|
||||
lvdcr0 |= LVDCR0_PLLON;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
if (lvds->info->gen > 2) {
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
|
||||
/* Set LVDS normal mode. */
|
||||
lvdcr0 |= LVDCR0_PWD;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
@ -236,8 +432,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Wait for the startup delay. */
|
||||
usleep_range(100, 150);
|
||||
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
|
||||
/* Wait for the PLL startup delay (simple PLL only). */
|
||||
usleep_range(100, 150);
|
||||
}
|
||||
|
||||
/* Turn the output on. */
|
||||
lvdcr0 |= LVDCR0_LVRES;
|
||||
|
@ -264,8 +462,9 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
|
|||
|
||||
rcar_lvds_write(lvds, LVDCR0, 0);
|
||||
rcar_lvds_write(lvds, LVDCR1, 0);
|
||||
rcar_lvds_write(lvds, LVDPLLCR, 0);
|
||||
|
||||
clk_disable_unprepare(lvds->clock);
|
||||
clk_disable_unprepare(lvds->clocks.mod);
|
||||
|
||||
lvds->enabled = false;
|
||||
}
|
||||
|
@ -446,6 +645,60 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct clk *rcar_lvds_get_clock(struct rcar_lvds *lvds, const char *name,
|
||||
bool optional)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
clk = devm_clk_get(lvds->dev, name);
|
||||
if (!IS_ERR(clk))
|
||||
return clk;
|
||||
|
||||
if (PTR_ERR(clk) == -ENOENT && optional)
|
||||
return NULL;
|
||||
|
||||
if (PTR_ERR(clk) != -EPROBE_DEFER)
|
||||
dev_err(lvds->dev, "failed to get %s clock\n",
|
||||
name ? name : "module");
|
||||
|
||||
return clk;
|
||||
}
|
||||
|
||||
static int rcar_lvds_get_clocks(struct rcar_lvds *lvds)
|
||||
{
|
||||
lvds->clocks.mod = rcar_lvds_get_clock(lvds, NULL, false);
|
||||
if (IS_ERR(lvds->clocks.mod))
|
||||
return PTR_ERR(lvds->clocks.mod);
|
||||
|
||||
/*
|
||||
* LVDS encoders without an extended PLL have no external clock inputs.
|
||||
*/
|
||||
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))
|
||||
return 0;
|
||||
|
||||
lvds->clocks.extal = rcar_lvds_get_clock(lvds, "extal", true);
|
||||
if (IS_ERR(lvds->clocks.extal))
|
||||
return PTR_ERR(lvds->clocks.extal);
|
||||
|
||||
lvds->clocks.dotclkin[0] = rcar_lvds_get_clock(lvds, "dclkin.0", true);
|
||||
if (IS_ERR(lvds->clocks.dotclkin[0]))
|
||||
return PTR_ERR(lvds->clocks.dotclkin[0]);
|
||||
|
||||
lvds->clocks.dotclkin[1] = rcar_lvds_get_clock(lvds, "dclkin.1", true);
|
||||
if (IS_ERR(lvds->clocks.dotclkin[1]))
|
||||
return PTR_ERR(lvds->clocks.dotclkin[1]);
|
||||
|
||||
/* At least one input to the PLL must be available. */
|
||||
if (!lvds->clocks.extal && !lvds->clocks.dotclkin[0] &&
|
||||
!lvds->clocks.dotclkin[1]) {
|
||||
dev_err(lvds->dev,
|
||||
"no input clock (extal, dclkin.0 or dclkin.1)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_lvds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_lvds *lvds;
|
||||
|
@ -475,11 +728,9 @@ static int rcar_lvds_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(lvds->mmio))
|
||||
return PTR_ERR(lvds->mmio);
|
||||
|
||||
lvds->clock = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(lvds->clock)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
return PTR_ERR(lvds->clock);
|
||||
}
|
||||
ret = rcar_lvds_get_clocks(lvds);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
drm_bridge_add(&lvds->bridge);
|
||||
|
||||
|
@ -497,21 +748,39 @@ static int rcar_lvds_remove(struct platform_device *pdev)
|
|||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
|
||||
.gen = 2,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR,
|
||||
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
|
||||
.gen = 2,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_LANES,
|
||||
.quirks = RCAR_LVDS_QUIRK_LANES,
|
||||
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
|
||||
.gen = 3,
|
||||
.quirks = RCAR_LVDS_QUIRK_PWD,
|
||||
.pll_setup = rcar_lvds_pll_setup_gen3,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
|
||||
.gen = 3,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_GEN3_LVEN,
|
||||
.quirks = RCAR_LVDS_QUIRK_PWD | RCAR_LVDS_QUIRK_GEN3_LVEN,
|
||||
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a77990_info = {
|
||||
.gen = 3,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_EXT_PLL
|
||||
| RCAR_LVDS_QUIRK_DUAL_LINK,
|
||||
.pll_setup = rcar_lvds_pll_setup_d3_e3,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = {
|
||||
.gen = 3,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_PWD
|
||||
| RCAR_LVDS_QUIRK_EXT_PLL | RCAR_LVDS_QUIRK_DUAL_LINK,
|
||||
.pll_setup = rcar_lvds_pll_setup_d3_e3,
|
||||
};
|
||||
|
||||
static const struct of_device_id rcar_lvds_of_table[] = {
|
||||
|
@ -523,6 +792,8 @@ static const struct of_device_id rcar_lvds_of_table[] = {
|
|||
{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
|
||||
{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
|
||||
{ .compatible = "renesas,r8a77980-lvds", .data = &rcar_lvds_gen3_info },
|
||||
{ .compatible = "renesas,r8a77990-lvds", .data = &rcar_lvds_r8a77990_info },
|
||||
{ .compatible = "renesas,r8a77995-lvds", .data = &rcar_lvds_r8a77995_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#define LVDCR0_PLLON (1 << 4)
|
||||
#define LVDCR0_PWD (1 << 2) /* Gen3 only */
|
||||
#define LVDCR0_BEN (1 << 2) /* Gen2 only */
|
||||
#define LVDCR0_LVEN (1 << 1) /* Gen2 only */
|
||||
#define LVDCR0_LVEN (1 << 1)
|
||||
#define LVDCR0_LVRES (1 << 0)
|
||||
|
||||
#define LVDCR1 0x0004
|
||||
|
@ -27,21 +27,36 @@
|
|||
#define LVDCR1_CLKSTBY (3 << 0)
|
||||
|
||||
#define LVDPLLCR 0x0008
|
||||
/* Gen2 & V3M */
|
||||
#define LVDPLLCR_CEEN (1 << 14)
|
||||
#define LVDPLLCR_FBEN (1 << 13)
|
||||
#define LVDPLLCR_COSEL (1 << 12)
|
||||
/* Gen2 */
|
||||
#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0)
|
||||
#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0)
|
||||
#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0)
|
||||
#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0)
|
||||
#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0)
|
||||
/* Gen3 */
|
||||
/* Gen3 but V3M,D3 and E3 */
|
||||
#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0)
|
||||
#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0)
|
||||
#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0)
|
||||
#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0)
|
||||
#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 0)
|
||||
/* D3 and E3 */
|
||||
#define LVDPLLCR_PLLON (1 << 22)
|
||||
#define LVDPLLCR_PLLSEL_PLL0 (0 << 20)
|
||||
#define LVDPLLCR_PLLSEL_LVX (1 << 20)
|
||||
#define LVDPLLCR_PLLSEL_PLL1 (2 << 20)
|
||||
#define LVDPLLCR_CKSEL_LVX (1 << 17)
|
||||
#define LVDPLLCR_CKSEL_EXTAL (3 << 17)
|
||||
#define LVDPLLCR_CKSEL_DU_DOTCLKIN(n) ((5 + (n) * 2) << 17)
|
||||
#define LVDPLLCR_OCKSEL (1 << 16)
|
||||
#define LVDPLLCR_STP_CLKOUTE (1 << 14)
|
||||
#define LVDPLLCR_OUTCLKSEL (1 << 12)
|
||||
#define LVDPLLCR_CLKOUT (1 << 11)
|
||||
#define LVDPLLCR_PLLE(n) ((n) << 10)
|
||||
#define LVDPLLCR_PLLN(n) ((n) << 3)
|
||||
#define LVDPLLCR_PLLM(n) ((n) << 0)
|
||||
|
||||
#define LVDCTRCR 0x000c
|
||||
#define LVDCTRCR_CTR3SEL_ZERO (0 << 12)
|
||||
|
@ -71,4 +86,26 @@
|
|||
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
|
||||
#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4))
|
||||
|
||||
/* All registers below are specific to D3 and E3 */
|
||||
#define LVDSTRIPE 0x0014
|
||||
#define LVDSTRIPE_ST_TRGSEL_DISP (0 << 2)
|
||||
#define LVDSTRIPE_ST_TRGSEL_HSYNC_R (1 << 2)
|
||||
#define LVDSTRIPE_ST_TRGSEL_HSYNC_F (2 << 2)
|
||||
#define LVDSTRIPE_ST_SWAP (1 << 1)
|
||||
#define LVDSTRIPE_ST_ON (1 << 0)
|
||||
|
||||
#define LVDSCR 0x0018
|
||||
#define LVDSCR_DEPTH(n) (((n) - 1) << 29)
|
||||
#define LVDSCR_BANDSET (1 << 28)
|
||||
#define LVDSCR_TWGCNT(n) ((((n) - 256) / 16) << 24)
|
||||
#define LVDSCR_SDIV(n) ((n) << 22)
|
||||
#define LVDSCR_MODE (1 << 21)
|
||||
#define LVDSCR_RSTN (1 << 20)
|
||||
|
||||
#define LVDDIV 0x001c
|
||||
#define LVDDIV_DIVSEL (1 << 8)
|
||||
#define LVDDIV_DIVRESET (1 << 7)
|
||||
#define LVDDIV_DIVSTP (1 << 6)
|
||||
#define LVDDIV_DIV(n) ((n) << 0)
|
||||
|
||||
#endif /* __RCAR_LVDS_REGS_H__ */
|
||||
|
|
Loading…
Reference in New Issue