From f48097d294d6f76a38bf1a1cb579aa99ede44297 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 20 Aug 2018 17:07:25 +0300 Subject: [PATCH 01/11] dt-bindings: display: renesas: du: Document r8a77990 bindings Document the E3 (r8a77990) SoC in the R-Car DU bindings. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Rob Herring Reviewed-by: Ulrich Hecht --- Documentation/devicetree/bindings/display/renesas,du.txt | 2 ++ 1 file changed, 2 insertions(+) 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 - From f9c32db12e2439ec9553a1a4c43d0ee308387790 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 20 Aug 2018 17:12:49 +0300 Subject: [PATCH 02/11] dt-bindings: display: renesas: lvds: Document r8a77990 bindings The E3 (r8a77990) supports two LVDS channels. Extend the binding to support them. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Rob Herring Reviewed-by: Ulrich Hecht Reviewed-by: Kieran Bingham --- .../devicetree/bindings/display/bridge/renesas,lvds.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt index 5a4e379bb414..13af7e2ac7e8 100644 --- a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt +++ b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt @@ -15,6 +15,7 @@ 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 From 9734a7009de62c05e9b08fa65efb7c5214ab3f30 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 22 Aug 2018 15:27:16 +0300 Subject: [PATCH 03/11] dt-bindings: display: renesas: lvds: Add EXTAL and DU_DOTCLKIN clocks On the D3 and E3 SoCs, the LVDS encoder can derive its internal pixel clock from an externally supplied clock, either through the EXTAL pin or through one of the DU_DOTCLKINx pins. Add corresponding clocks to the DT bindings. To retain backward compatibility with DT that don't specify the clock-names property, the functional clock must always be specified first, and the clock-names property is optional when only the functional clock is specified. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Ulrich Hecht Reviewed-by: Kieran Bingham --- .../bindings/display/bridge/renesas,lvds.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt index 13af7e2ac7e8..3aeb0ec06fd0 100644 --- a/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt +++ b/Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt @@ -19,7 +19,17 @@ Required properties: - "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: From 399d9f2f197a06b8866192a019a97d2af29cc81e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 22 Aug 2018 17:04:06 +0300 Subject: [PATCH 04/11] drm: bridge: thc63: Restrict modes based on hardware operating frequency The THC63LVD1024 is restricted to a pixel clock frequency in the range of 8 to 135 MHz. Implement the bridge .mode_valid() operation accordingly. Signed-off-by: Laurent Pinchart Reviewed-by: Andrzej Hajda Tested-by: Jacopo Mondi --- drivers/gpu/drm/bridge/thc63lvd1024.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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, }; From c25c0136119990c62c160d95592714833bc214a5 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 21 Aug 2018 18:06:50 +0300 Subject: [PATCH 05/11] drm: rcar-du: lvds: D3/E3 support The LVDS encoders in the D3 and E3 SoCs differ significantly from those in the other R-Car Gen3 family members: - The LVDS PLL architecture is more complex and requires computing PLL parameters manually. - The PLL uses external clocks as inputs, which need to be retrieved from DT. - In addition to the different PLL setup, the startup sequence has changed *again* (seems someone had trouble making his/her mind). Supporting all this requires DT bindings extensions for external clocks, brand new PLL setup code, and a few quirks to handle the differences in the startup sequence. The implementation doesn't support all hardware features yet, namely - Using the LV[01] clocks generated by the CPG as PLL input. - Providing the LVDS PLL clock to the DU for use with the RGB output. Those features can be added later when the need will arise. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Ulrich Hecht Reviewed-by: Jacopo Mondi --- drivers/gpu/drm/rcar-du/rcar_lvds.c | 359 ++++++++++++++++++++--- drivers/gpu/drm/rcar-du/rcar_lvds_regs.h | 43 ++- 2 files changed, 355 insertions(+), 47 deletions(-) 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__ */ From 0bb63534fdf3bc9a82bcfe9f5c6a9653b8b2a3f1 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 14 Jul 2017 03:26:17 +0300 Subject: [PATCH 06/11] drm: rcar-du: Perform the initial CRTC setup from rcar_du_crtc_get() The rcar_du_crtc_get() function is always immediately followed by a call to rcar_du_crtc_setup(). Call the later from the former to simplify the code, and add a comment to explain how the get and put calls are balanced. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Jacopo Mondi --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 107 +++++++++++++------------ 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 6288b9ad9e24..c89751c26f9c 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -66,39 +66,6 @@ static void rcar_du_crtc_clr_set(struct rcar_du_crtc *rcrtc, u32 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); -} - /* ----------------------------------------------------------------------------- * Hardware Setup */ @@ -546,6 +513,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; @@ -639,16 +651,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 +670,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 +682,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); From b4734f43f3cadfaa423ce6aceb1e9faea07b8eb8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 21 Aug 2018 21:31:04 +0300 Subject: [PATCH 07/11] drm: rcar-du: Use LVDS PLL clock as dot clock when possible On selected SoCs, the DU can use the clock output by the LVDS encoder PLL as its input dot clock. This feature is optional, but on the D3 and E3 SoC it is often the only way to obtain a precise dot clock frequency, as the other available clocks (CPG-generated clock and external clock) usually have fixed rates. Add a DU model information field to describe which DU channels can use the LVDS PLL output clock as their input clock, and configure clock routing accordingly. This feature is available on H2, M2-W, M2-N, D3 and E3 SoCs, with D3 and E3 being the primary targets. It is left disabled in this commit, and will be enabled per-SoC after careful testing. At the hardware level, clock routing is configured at runtime in two steps, first selecting an internal dot clock between the LVDS PLL clock and the external DOTCLKIN clock, and then selecting between the internal dot clock and the CPG-generated clock. The first part requires stopping the whole DU group in order for the change to take effect, thus causing flickering on the screen. For this reason we currently hardcode the clock source to the LVDS PLL clock if available, and allow flicker-free selection of the external DOTCLKIN clock or CPG-generated clock otherwise. A more dynamic clock selection process can be implemented later if the need arises. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Jacopo Mondi --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 8 ++++ drivers/gpu/drm/rcar-du/rcar_du_drv.h | 2 + drivers/gpu/drm/rcar-du/rcar_du_group.c | 64 +++++++++++++++++++------ 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index c89751c26f9c..2f8776c1ec8f 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -261,6 +261,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 }; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index fef9ea5c22f3..ebba9aefba6a 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -53,6 +53,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 +63,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..4c62841eff2f 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_group.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c @@ -89,6 +89,54 @@ static void rcar_du_group_setup_defr8(struct rcar_du_group *rgrp) 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 +154,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) From 1f98b2a4fd4632db3b585a624032b7ec785a5255 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 22 Aug 2018 00:01:07 +0300 Subject: [PATCH 08/11] drm: rcar-du: Enable configurable DPAD0 routing on Gen3 All Gen3 SoCs supported so far have a fixed association between DPAD0 and DU channels, which led to hardcoding that association when writing the corresponding hardware register. The D3 and E3 will break that mechanism as DPAD0 can be dynamically connected to either DU0 or DU1. Make DPAD0 routing dynamic on Gen3. To ensure a valid hardware configuration when the DU starts without the RGB output enabled, DPAD0 is associated at initialization time to the first DU channel that it can be connected to. This makes no change on Gen2 as all Gen2 SoCs can connected DPAD0 to DU0, which is the current implicit default value. As the DPAD0 source is always 0 when a single source is possible on Gen2, we can also simplify the Gen2 code in the same function to remove a conditional check. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Jacopo Mondi --- drivers/gpu/drm/rcar-du/rcar_du_group.c | 17 ++++++----------- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c index 4c62841eff2f..f38703e7a10d 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,21 +67,18 @@ 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); 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); From 9144adc5e5a99577bce0d4ee2ca3615f53b9d296 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 22 Aug 2018 16:05:02 +0300 Subject: [PATCH 09/11] drm: rcar-du: Cache DSYSR value to ensure known initial value DSYSR is a DU channel register that also contains group fields. It is thus written to by both the group and CRTC code, using read-update-write sequences. As the register isn't initialized explicitly at startup time, this can lead to invalid or otherwise unexpected values being written to some of the fields if they have been modified by the firmware or just not reset properly. To fix this we can write a fully known value to the DSYSR register when turning a channel's functional clock on. However, the mix of group and channel fields complicate this. A simpler solution is to cache the register and initialize the cached value to the desired hardware defaults. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 16 ++++++++-------- drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 5 +++++ drivers/gpu/drm/rcar-du/rcar_du_group.c | 7 ++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index 2f8776c1ec8f..f827fccf6416 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -57,13 +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); + rcrtc->dsysr = (rcrtc->dsysr & ~clr) | set; + rcar_du_write(rcdu, rcrtc->mmio_offset + DSYSR, rcrtc->dsysr); } /* ----------------------------------------------------------------------------- @@ -576,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); } @@ -645,7 +644,7 @@ 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. */ - rcar_du_crtc_clr_set(rcrtc, DSYSR, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH); + rcar_du_crtc_dsysr_clr_set(rcrtc, DSYSR_TVM_MASK, DSYSR_TVM_SWITCH); rcar_du_group_start_stop(rcrtc->group, false); } @@ -1121,6 +1120,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_group.c b/drivers/gpu/drm/rcar-du/rcar_du_group.c index f38703e7a10d..d85f0a1c1581 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_group.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_group.c @@ -202,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) From ffd15c3e7898cfb6d2a986b2aa8014ad7dc9e333 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 22 Aug 2018 16:21:33 +0300 Subject: [PATCH 10/11] drm: rcar-du: Don't use TV sync mode when not supported by the hardware The official way to stop the display is to clear the display enable (DEN) bit in the DSYSR register, but that operates at a group level and affects the two channels in the group. To disable channels selectively, the driver uses TV sync mode that stops display operation on the channel and turns output signals into inputs. While TV sync mode is available in all DU models currently supported, the D3 and E3 DUs don't support it. We will thus need to find an alternative way to turn channels off. In the meantime, condition the switch to TV sync mode to the availability of the feature, to avoid writing an invalid value to the DSYSR register. When the feature is unavailable the display output will turn blank as all planes are disabled when stopping the CRTC. Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 7 +++++- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 33 +++++++++++++++++--------- drivers/gpu/drm/rcar-du/rcar_du_drv.h | 1 + 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index f827fccf6416..17741843cf51 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -643,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_dsysr_clr_set(rcrtc, 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); } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index 0954ecd2f943..fa0d381c2d0f 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. */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index ebba9aefba6a..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 */ From 122702077e4492e02de8a6257e6cb2227c617cf0 Mon Sep 17 00:00:00 2001 From: Ulrich Hecht Date: Tue, 14 Aug 2018 15:49:56 +0200 Subject: [PATCH 11/11] drm: rcar-du: Add r8a77990 and r8a77995 device support Add support for the R-Car D3 (R8A77995) and E3 (R8A77990) SoCs to the R-Car DU driver. The two SoCs instantiate compatible DUs, so a single information structure is enough. Signed-off-by: Ulrich Hecht [Add support for R8A77990] Signed-off-by: Laurent Pinchart Tested-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index fa0d381c2d0f..084f58df4a8c 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -311,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 }, @@ -324,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 }, { } };