Commit 2e240bee authored by Dave Airlie's avatar Dave Airlie

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: default avatarDave Airlie <airlied@redhat.com>

From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/3289904.RCOHkcp7u8@avalon
parents 36c9c3c9 12270207
...@@ -15,10 +15,21 @@ Required properties: ...@@ -15,10 +15,21 @@ Required properties:
- "renesas,r8a7796-lvds" for R8A7796 (R-Car M3-W) compatible LVDS encoders - "renesas,r8a7796-lvds" for R8A7796 (R-Car M3-W) compatible LVDS encoders
- "renesas,r8a77970-lvds" for R8A77970 (R-Car V3M) 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,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 - "renesas,r8a77995-lvds" for R8A77995 (R-Car D3) compatible LVDS encoders
- reg: Base address and length for the memory-mapped registers - 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 - resets: A phandle + reset specifier for the module reset
Required nodes: Required nodes:
......
...@@ -16,6 +16,7 @@ Required Properties: ...@@ -16,6 +16,7 @@ Required Properties:
- "renesas,du-r8a77965" for R8A77965 (R-Car M3-N) compatible DU - "renesas,du-r8a77965" for R8A77965 (R-Car M3-N) compatible DU
- "renesas,du-r8a77970" for R8A77970 (R-Car V3M) compatible DU - "renesas,du-r8a77970" for R8A77970 (R-Car V3M) compatible DU
- "renesas,du-r8a77980" for R8A77980 (R-Car V3H) 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 - "renesas,du-r8a77995" for R8A77995 (R-Car D3) compatible DU
- reg: the memory-mapped I/O registers base address and length - reg: the memory-mapped I/O registers base address and length
...@@ -63,6 +64,7 @@ corresponding to each DU output. ...@@ -63,6 +64,7 @@ corresponding to each DU output.
R8A77965 (R-Car M3-N) DPAD 0 HDMI 0 LVDS 0 - R8A77965 (R-Car M3-N) DPAD 0 HDMI 0 LVDS 0 -
R8A77970 (R-Car V3M) DPAD 0 LVDS 0 - - R8A77970 (R-Car V3M) DPAD 0 LVDS 0 - -
R8A77980 (R-Car V3H) 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 - R8A77995 (R-Car D3) DPAD 0 LVDS 0 LVDS 1 -
......
...@@ -45,6 +45,23 @@ static int thc63_attach(struct drm_bridge *bridge) ...@@ -45,6 +45,23 @@ static int thc63_attach(struct drm_bridge *bridge)
return drm_bridge_attach(bridge->encoder, thc63->next, 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) static void thc63_enable(struct drm_bridge *bridge)
{ {
struct thc63_dev *thc63 = to_thc63(bridge); struct thc63_dev *thc63 = to_thc63(bridge);
...@@ -77,6 +94,7 @@ static void thc63_disable(struct drm_bridge *bridge) ...@@ -77,6 +94,7 @@ static void thc63_disable(struct drm_bridge *bridge)
static const struct drm_bridge_funcs thc63_bridge_func = { static const struct drm_bridge_funcs thc63_bridge_func = {
.attach = thc63_attach, .attach = thc63_attach,
.mode_valid = thc63_mode_valid,
.enable = thc63_enable, .enable = thc63_enable,
.disable = thc63_disable, .disable = thc63_disable,
}; };
......
...@@ -57,46 +57,12 @@ static void rcar_du_crtc_set(struct rcar_du_crtc *rcrtc, u32 reg, u32 set) ...@@ -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); rcar_du_read(rcdu, rcrtc->mmio_offset + reg) | set);
} }
static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 reg, void rcar_du_crtc_dsysr_clr_set(struct rcar_du_crtc *rcrtc, u32 clr, u32 set)
u32 clr, u32 set)
{ {
struct rcar_du_device *rcdu = rcrtc->group->dev; 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); rcrtc->dsysr = (rcrtc->dsysr & ~clr) | set;
} rcar_du_write(rcdu, rcrtc->mmio_offset + DSYSR, rcrtc->dsysr);
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);
} }
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
...@@ -294,6 +260,14 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) ...@@ -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); rcar_du_group_write(rcrtc->group, DPLLCR, dpllcr);
escr = ESCR_DCLKSEL_DCLKIN | div; 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 { } else {
struct du_clk_params params = { .diff = (unsigned long)-1 }; struct du_clk_params params = { .diff = (unsigned long)-1 };
...@@ -546,6 +520,51 @@ static void rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc) ...@@ -546,6 +520,51 @@ static void rcar_du_crtc_setup(struct rcar_du_crtc *rcrtc)
drm_crtc_vblank_on(&rcrtc->crtc); 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) static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
{ {
bool interlaced; bool interlaced;
...@@ -556,7 +575,7 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc) ...@@ -556,7 +575,7 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
* actively driven). * actively driven).
*/ */
interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE; interlaced = rcrtc->crtc.mode.flags & DRM_MODE_FLAG_INTERLACE;
rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK | DSYSR_SCM_MASK, rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK | DSYSR_SCM_MASK,
(interlaced ? DSYSR_SCM_INT_VIDEO : 0) | (interlaced ? DSYSR_SCM_INT_VIDEO : 0) |
DSYSR_TVM_MASTER); DSYSR_TVM_MASTER);
...@@ -624,8 +643,13 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) ...@@ -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 * Select switch sync mode. This stops display operation and configures
* the HSYNC and VSYNC signals as inputs. * 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); rcar_du_group_start_stop(rcrtc->group, false);
} }
...@@ -639,16 +663,7 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc, ...@@ -639,16 +663,7 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
{ {
struct rcar_du_crtc *rcrtc = to_rcar_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_get(rcrtc);
rcar_du_crtc_setup(rcrtc);
rcrtc->initialized = true;
}
rcar_du_crtc_start(rcrtc); rcar_du_crtc_start(rcrtc);
} }
...@@ -667,7 +682,6 @@ static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc, ...@@ -667,7 +682,6 @@ static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
} }
spin_unlock_irq(&crtc->dev->event_lock); spin_unlock_irq(&crtc->dev->event_lock);
rcrtc->initialized = false;
rcrtc->outputs = 0; rcrtc->outputs = 0;
} }
...@@ -680,14 +694,17 @@ static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc, ...@@ -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. * 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. * We thus need to first get and setup the CRTC in order to configure
* The .atomic_enable() handler will notice and skip the CRTC setup. * 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_get(rcrtc);
rcar_du_crtc_setup(rcrtc);
rcrtc->initialized = true;
}
if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE)) if (rcar_du_has(rcrtc->group->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_atomic_begin(rcrtc); rcar_du_vsp_atomic_begin(rcrtc);
...@@ -1108,6 +1125,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex, ...@@ -1108,6 +1125,7 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
rcrtc->group = rgrp; rcrtc->group = rgrp;
rcrtc->mmio_offset = mmio_offsets[hwindex]; rcrtc->mmio_offset = mmio_offsets[hwindex];
rcrtc->index = 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)) if (rcar_du_has(rcdu, RCAR_DU_FEATURE_VSP1_SOURCE))
primary = &rcrtc->vsp->planes[rcrtc->vsp_pipe].plane; primary = &rcrtc->vsp->planes[rcrtc->vsp_pipe].plane;
......
...@@ -30,6 +30,7 @@ struct rcar_du_vsp; ...@@ -30,6 +30,7 @@ struct rcar_du_vsp;
* @mmio_offset: offset of the CRTC registers in the DU MMIO block * @mmio_offset: offset of the CRTC registers in the DU MMIO block
* @index: CRTC software and hardware index * @index: CRTC software and hardware index
* @initialized: whether the CRTC has been initialized and clocks enabled * @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 * @vblank_enable: whether vblank events are enabled on this CRTC
* @event: event to post when the pending page flip completes * @event: event to post when the pending page flip completes
* @flip_wait: wait queue used to signal page flip completion * @flip_wait: wait queue used to signal page flip completion
...@@ -50,6 +51,8 @@ struct rcar_du_crtc { ...@@ -50,6 +51,8 @@ struct rcar_du_crtc {
unsigned int index; unsigned int index;
bool initialized; bool initialized;
u32 dsysr;
bool vblank_enable; bool vblank_enable;
struct drm_pending_vblank_event *event; struct drm_pending_vblank_event *event;
wait_queue_head_t flip_wait; wait_queue_head_t flip_wait;
...@@ -103,4 +106,6 @@ void rcar_du_crtc_route_output(struct drm_crtc *crtc, ...@@ -103,4 +106,6 @@ void rcar_du_crtc_route_output(struct drm_crtc *crtc,
enum rcar_du_output output); enum rcar_du_output output);
void rcar_du_crtc_finish_page_flip(struct rcar_du_crtc *rcrtc); 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__ */ #endif /* __RCAR_DU_CRTC_H__ */
...@@ -36,7 +36,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7743_info = { ...@@ -36,7 +36,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7743_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -58,7 +59,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = { ...@@ -58,7 +59,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -77,7 +79,8 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = { ...@@ -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 = { static const struct rcar_du_device_info rcar_du_r8a7779_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_INTERLACED, .features = RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(1) | BIT(0), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -99,7 +102,8 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = { ...@@ -99,7 +102,8 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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, .quirks = RCAR_DU_QUIRK_ALIGN_128B,
.channels_mask = BIT(2) | BIT(1) | BIT(0), .channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = { .routes = {
...@@ -128,7 +132,8 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = { ...@@ -128,7 +132,8 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -151,7 +156,8 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = { ...@@ -151,7 +156,8 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* R8A7792 has two RGB outputs. */ /* R8A7792 has two RGB outputs. */
...@@ -170,7 +176,8 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = { ...@@ -170,7 +176,8 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | 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), .channels_mask = BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -193,7 +200,8 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = { ...@@ -193,7 +200,8 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | RCAR_DU_FEATURE_EXT_CTRL_REGS
| RCAR_DU_FEATURE_VSP1_SOURCE | 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), .channels_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -226,7 +234,8 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = { ...@@ -226,7 +234,8 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | RCAR_DU_FEATURE_EXT_CTRL_REGS
| RCAR_DU_FEATURE_VSP1_SOURCE | 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), .channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -255,7 +264,8 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = { ...@@ -255,7 +264,8 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | RCAR_DU_FEATURE_EXT_CTRL_REGS
| RCAR_DU_FEATURE_VSP1_SOURCE | 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), .channels_mask = BIT(3) | BIT(1) | BIT(0),
.routes = { .routes = {
/* /*
...@@ -284,7 +294,8 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = { ...@@ -284,7 +294,8 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS | RCAR_DU_FEATURE_EXT_CTRL_REGS
| RCAR_DU_FEATURE_VSP1_SOURCE | RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED, | RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(0), .channels_mask = BIT(0),
.routes = { .routes = {
/* R8A77970 has one RGB output and one LVDS output. */ /* R8A77970 has one RGB output and one LVDS output. */
...@@ -300,6 +311,34 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = { ...@@ -300,6 +311,34 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
.num_lvds = 1, .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[] = { static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info }, { .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info }, { .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
...@@ -313,6 +352,8 @@ static const struct of_device_id rcar_du_of_table[] = { ...@@ -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-r8a7796", .data = &rcar_du_r8a7796_info },
{ .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info }, { .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_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; ...@@ -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_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_VSP1_SOURCE BIT(2) /* Has inputs from VSP1 */
#define RCAR_DU_FEATURE_INTERLACED BIT(3) /* HW supports interlaced */ #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 */ #define RCAR_DU_QUIRK_ALIGN_128B BIT(0) /* Align pitches to 128 bytes */
...@@ -53,6 +54,7 @@ struct rcar_du_output_routing { ...@@ -53,6 +54,7 @@ struct rcar_du_output_routing {
* @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*) * @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*)
* @num_lvds: number of internal LVDS encoders * @num_lvds: number of internal LVDS encoders
* @dpll_mask: bit mask of DU channels equipped with a DPLL * @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 { struct rcar_du_device_info {
unsigned int gen; unsigned int gen;
...@@ -62,6 +64,7 @@ struct rcar_du_device_info { ...@@ -62,6 +64,7 @@ struct rcar_du_device_info {
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds; unsigned int num_lvds;
unsigned int dpll_mask; unsigned int dpll_mask;
unsigned int lvds_clk_mask;
}; };
#define RCAR_DU_MAX_CRTCS 4 #define RCAR_DU_MAX_CRTCS 4
......
...@@ -56,8 +56,6 @@ static void rcar_du_group_setup_pins(struct rcar_du_group *rgrp) ...@@ -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) static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp)
{ {
struct rcar_du_device *rcdu = rgrp->dev; struct rcar_du_device *rcdu = rgrp->dev;
unsigned int possible_crtcs =
rcdu->info->routes[RCAR_DU_OUTPUT_DPAD0].possible_crtcs;
u32 defr8 = DEFR8_CODE; u32 defr8 = DEFR8_CODE;
if (rcdu->info->gen < 3) { if (rcdu->info->gen < 3) {
...@@ -69,26 +67,71 @@ static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp) ...@@ -69,26 +67,71 @@ static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp)
* DU instances that support it. * DU instances that support it.
*/ */
if (rgrp->index == 0) { 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) if (rgrp->dev->vspd1_sink == 2)
defr8 |= DEFR8_VSCS; defr8 |= DEFR8_VSCS;
} }
} else { } else {
/* /*
* On Gen3 VSPD routing can't be configured, but DPAD routing * On Gen3 VSPD routing can't be configured, and DPAD routing
* needs to be set despite having a single option available. * 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; if (rgrp->index == rcdu->dpad0_source / 2)
struct rcar_du_crtc *crtc = &rcdu->crtcs[rgb_crtc]; defr8 |= DEFR8_DRGBS_DU(rcdu->dpad0_source);
if (crtc->index / 2 == rgrp->index)
defr8 |= DEFR8_DRGBS_DU(crtc->index);
} }
rcar_du_group_write(rgrp, DEFR8, defr8); 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) static void rcar_du_group_setup(struct rcar_du_group *rgrp)
{ {
struct rcar_du_device *rcdu = rgrp->dev; struct rcar_du_device *rcdu = rgrp->dev;
...@@ -106,21 +149,7 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp) ...@@ -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)) { if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_EXT_CTRL_REGS)) {
rcar_du_group_setup_defr8(rgrp); rcar_du_group_setup_defr8(rgrp);
rcar_du_group_setup_didsr(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);
} }
if (rcdu->info->gen >= 3) if (rcdu->info->gen >= 3)
...@@ -173,9 +202,10 @@ void rcar_du_group_put(struct rcar_du_group *rgrp) ...@@ -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) static void __rcar_du_group_start_stop(struct rcar_du_group *rgrp, bool start)
{ {
rcar_du_group_write(rgrp, DSYSR, struct rcar_du_crtc *rcrtc = &rgrp->dev->crtcs[rgrp->index * 2];
(rcar_du_group_read(rgrp, DSYSR) & ~(DSYSR_DRES | DSYSR_DEN)) |
(start ? DSYSR_DEN : DSYSR_DRES)); 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) 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) ...@@ -544,6 +544,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
struct drm_device *dev = rcdu->ddev; struct drm_device *dev = rcdu->ddev;
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct drm_fbdev_cma *fbdev; struct drm_fbdev_cma *fbdev;
unsigned int dpad0_sources;
unsigned int num_encoders; unsigned int num_encoders;
unsigned int num_groups; unsigned int num_groups;
unsigned int swindex; unsigned int swindex;
...@@ -666,6 +667,17 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) ...@@ -666,6 +667,17 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
encoder->possible_clones = (1 << num_encoders) - 1; 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_mode_config_reset(dev);
drm_kms_helper_poll_init(dev); drm_kms_helper_poll_init(dev);
......
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
#include "rcar_lvds_regs.h" #include "rcar_lvds_regs.h"
struct rcar_lvds;
/* Keep in sync with the LVDCR0.LVMD hardware register values. */ /* Keep in sync with the LVDCR0.LVMD hardware register values. */
enum rcar_lvds_mode { enum rcar_lvds_mode {
RCAR_LVDS_MODE_JEIDA = 0, RCAR_LVDS_MODE_JEIDA = 0,
...@@ -31,14 +33,16 @@ enum rcar_lvds_mode { ...@@ -31,14 +33,16 @@ enum rcar_lvds_mode {
RCAR_LVDS_MODE_VESA = 4, RCAR_LVDS_MODE_VESA = 4,
}; };
#define RCAR_LVDS_QUIRK_LANES (1 << 0) /* LVDS lanes 1 and 3 inverted */ #define RCAR_LVDS_QUIRK_LANES BIT(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 BIT(1) /* LVEN bit needs to be set on R8A77970/R8A7799x */
#define RCAR_LVDS_QUIRK_GEN3_LVEN (1 << 2) /* LVEN bit needs to be set */ #define RCAR_LVDS_QUIRK_PWD BIT(2) /* PWD bit available (all of Gen3 but E3) */
/* on R8A77970/R8A7799x */ #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 { struct rcar_lvds_device_info {
unsigned int gen; unsigned int gen;
unsigned int quirks; unsigned int quirks;
void (*pll_setup)(struct rcar_lvds *lvds, unsigned int freq);
}; };
struct rcar_lvds { struct rcar_lvds {
...@@ -52,7 +56,11 @@ struct rcar_lvds { ...@@ -52,7 +56,11 @@ struct rcar_lvds {
struct drm_panel *panel; struct drm_panel *panel;
void __iomem *mmio; 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; bool enabled;
struct drm_display_mode display_mode; struct drm_display_mode display_mode;
...@@ -128,33 +136,216 @@ static const struct drm_connector_funcs rcar_lvds_conn_funcs = { ...@@ -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) u32 val;
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
else if (freq < 61000) if (freq < 39000000)
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M; val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
else if (freq < 121000) else if (freq < 61000000)
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M; val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
else if (freq < 121000000)
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
else 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) u32 val;
return LVDPLLCR_PLLDIVCNT_42M;
else if (freq < 85000) if (freq < 42000000)
return LVDPLLCR_PLLDIVCNT_85M; val = LVDPLLCR_PLLDIVCNT_42M;
else if (freq < 128000) else if (freq < 85000000)
return LVDPLLCR_PLLDIVCNT_128M; val = LVDPLLCR_PLLDIVCNT_85M;
else if (freq < 128000000)
val = LVDPLLCR_PLLDIVCNT_128M;
else
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 else
return LVDPLLCR_PLLDIVCNT_148M; rcar_lvds_write(lvds, LVDDIV, 0);
} }
/* -----------------------------------------------------------------------------
* Bridge
*/
static void rcar_lvds_enable(struct drm_bridge *bridge) static void rcar_lvds_enable(struct drm_bridge *bridge)
{ {
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
...@@ -164,14 +355,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -164,14 +355,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
* do we get a state pointer? * do we get a state pointer?
*/ */
struct drm_crtc *crtc = lvds->bridge.encoder->crtc; struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
u32 lvdpllcr;
u32 lvdhcr; u32 lvdhcr;
u32 lvdcr0; u32 lvdcr0;
int ret; int ret;
WARN_ON(lvds->enabled); WARN_ON(lvds->enabled);
ret = clk_prepare_enable(lvds->clock); ret = clk_prepare_enable(lvds->clocks.mod);
if (ret < 0) if (ret < 0)
return; return;
...@@ -196,12 +386,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -196,12 +386,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCHCR, lvdhcr); 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. */ /* PLL clock configuration. */
if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN2_PLLCR) lvds->info->pll_setup(lvds, mode->clock * 1000);
lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
else
lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
/* Set the LVDS mode and select the input. */ /* Set the LVDS mode and select the input. */
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT; lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
...@@ -220,11 +411,16 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -220,11 +411,16 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
} }
/* Turn the PLL on. */ 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; lvdcr0 |= LVDCR0_PLLON;
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
}
if (lvds->info->gen > 2) { if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
/* Set LVDS normal mode. */ /* Set LVDS normal mode. */
lvdcr0 |= LVDCR0_PWD; lvdcr0 |= LVDCR0_PWD;
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
...@@ -236,8 +432,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -236,8 +432,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
} }
/* Wait for the startup delay. */ if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
/* Wait for the PLL startup delay (simple PLL only). */
usleep_range(100, 150); usleep_range(100, 150);
}
/* Turn the output on. */ /* Turn the output on. */
lvdcr0 |= LVDCR0_LVRES; lvdcr0 |= LVDCR0_LVRES;
...@@ -264,8 +462,9 @@ static void rcar_lvds_disable(struct drm_bridge *bridge) ...@@ -264,8 +462,9 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCR0, 0); rcar_lvds_write(lvds, LVDCR0, 0);
rcar_lvds_write(lvds, LVDCR1, 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; lvds->enabled = false;
} }
...@@ -446,6 +645,60 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) ...@@ -446,6 +645,60 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
return ret; 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) static int rcar_lvds_probe(struct platform_device *pdev)
{ {
struct rcar_lvds *lvds; struct rcar_lvds *lvds;
...@@ -475,11 +728,9 @@ static int rcar_lvds_probe(struct platform_device *pdev) ...@@ -475,11 +728,9 @@ static int rcar_lvds_probe(struct platform_device *pdev)
if (IS_ERR(lvds->mmio)) if (IS_ERR(lvds->mmio))
return PTR_ERR(lvds->mmio); return PTR_ERR(lvds->mmio);
lvds->clock = devm_clk_get(&pdev->dev, NULL); ret = rcar_lvds_get_clocks(lvds);
if (IS_ERR(lvds->clock)) { if (ret < 0)
dev_err(&pdev->dev, "failed to get clock\n"); return ret;
return PTR_ERR(lvds->clock);
}
drm_bridge_add(&lvds->bridge); drm_bridge_add(&lvds->bridge);
...@@ -497,21 +748,39 @@ static int rcar_lvds_remove(struct platform_device *pdev) ...@@ -497,21 +748,39 @@ static int rcar_lvds_remove(struct platform_device *pdev)
static const struct rcar_lvds_device_info rcar_lvds_gen2_info = { static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
.gen = 2, .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 = { static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
.gen = 2, .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 = { static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
.gen = 3, .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 = { static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
.gen = 3, .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[] = { static const struct of_device_id rcar_lvds_of_table[] = {
...@@ -523,6 +792,8 @@ 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,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info }, { .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
{ .compatible = "renesas,r8a77980-lvds", .data = &rcar_lvds_gen3_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 @@ ...@@ -18,7 +18,7 @@
#define LVDCR0_PLLON (1 << 4) #define LVDCR0_PLLON (1 << 4)
#define LVDCR0_PWD (1 << 2) /* Gen3 only */ #define LVDCR0_PWD (1 << 2) /* Gen3 only */
#define LVDCR0_BEN (1 << 2) /* Gen2 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 LVDCR0_LVRES (1 << 0)
#define LVDCR1 0x0004 #define LVDCR1 0x0004
...@@ -27,21 +27,36 @@ ...@@ -27,21 +27,36 @@
#define LVDCR1_CLKSTBY (3 << 0) #define LVDCR1_CLKSTBY (3 << 0)
#define LVDPLLCR 0x0008 #define LVDPLLCR 0x0008
/* Gen2 & V3M */
#define LVDPLLCR_CEEN (1 << 14) #define LVDPLLCR_CEEN (1 << 14)
#define LVDPLLCR_FBEN (1 << 13) #define LVDPLLCR_FBEN (1 << 13)
#define LVDPLLCR_COSEL (1 << 12) #define LVDPLLCR_COSEL (1 << 12)
/* Gen2 */
#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0) #define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0)
#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0) #define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0)
#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0) #define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0)
#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0) #define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0)
#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0) #define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0)
/* Gen3 */ /* Gen3 but V3M,D3 and E3 */
#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0) #define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0)
#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0) #define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0)
#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0) #define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0)
#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0) #define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0)
#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 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 0x000c
#define LVDCTRCR_CTR3SEL_ZERO (0 << 12) #define LVDCTRCR_CTR3SEL_ZERO (0 << 12)
...@@ -71,4 +86,26 @@ ...@@ -71,4 +86,26 @@
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4)) #define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
#define LVDCHCR_CHSEL_MASK(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__ */ #endif /* __RCAR_LVDS_REGS_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment