summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt13
-rw-r--r--Documentation/devicetree/bindings/display/renesas,du.txt2
-rw-r--r--drivers/gpu/drm/bridge/thc63lvd1024.c18
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.c136
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.h5
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.c63
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.h3
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_group.c88
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.c12
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_lvds.c359
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_lvds_regs.h43
11 files changed, 595 insertions, 147 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
index 5a4e379bb414..3aeb0ec06fd0 100644
--- a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
+++ b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
@@ -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:
diff --git a/Documentation/devicetree/bindings/display/renesas,du.txt b/Documentation/devicetree/bindings/display/renesas,du.txt
index caae2348a292..9de67be632d1 100644
--- a/Documentation/devicetree/bindings/display/renesas,du.txt
+++ b/Documentation/devicetree/bindings/display/renesas,du.txt
@@ -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 -
diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c
index c8b9edd5a7f4..b083a740565c 100644
--- a/drivers/gpu/drm/bridge/thc63lvd1024.c
+++ b/drivers/gpu/drm/bridge/thc63lvd1024.c
@@ -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,
};
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
index 6288b9ad9e24..17741843cf51 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c
@@ -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;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
index 4990bbe9ba26..59ac6e7d22c9 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h
@@ -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__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 0954ecd2f943..084f58df4a8c 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -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 },
{ }
};
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index fef9ea5c22f3..143c037e2c0f 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -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
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c
index ef2c177afb6d..d85f0a1c1581 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_group.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c
@@ -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)
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
index b5d79ecd25ea..4ebd61ecbee1 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -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);
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
index ce0eb68c3416..173d7ad0b991 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
@@ -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 @@ done:
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 },
{ }
};
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h b/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
index 4870f50d9bec..87149f2f8056 100644
--- a/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds_regs.h
@@ -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__ */