| From ffa694b7ce4a92c980e8325357901963fd3026e3 Mon Sep 17 00:00:00 2001 |
| From: Koji Matsuoka <koji.matsuoka.xm@renesas.com> |
| Date: Fri, 11 Nov 2016 18:07:41 +0100 |
| Subject: [PATCH 263/286] drm: rcar-du: Add DPLL support |
| |
| The implementation hardcodes a workaround for the H3 ES1.x SoC |
| regardless of the SoC revision, as the workaround can be safely applied |
| on all devices in the Gen3 family without any side effect. |
| |
| Signed-off-by: Koji Matsuoka <koji.matsuoka.xm@renesas.com> |
| Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com> |
| Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> |
| (cherry picked from commit dc4aedbf7c152c092c19e980a9fa1e89d6bc215f) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/gpu/drm/rcar-du/rcar_du_crtc.c | 81 ++++++++++++++++++++++++++++++++- |
| drivers/gpu/drm/rcar-du/rcar_du_drv.c | 1 |
| drivers/gpu/drm/rcar-du/rcar_du_drv.h | 1 |
| drivers/gpu/drm/rcar-du/rcar_du_regs.h | 23 +++++++++ |
| 4 files changed, 105 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c |
| +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c |
| @@ -106,9 +106,62 @@ static void rcar_du_crtc_put(struct rcar |
| * Hardware Setup |
| */ |
| |
| +struct dpll_info { |
| + unsigned int output; |
| + unsigned int fdpll; |
| + unsigned int n; |
| + unsigned int m; |
| +}; |
| + |
| +static void rcar_du_dpll_divider(struct rcar_du_crtc *rcrtc, |
| + struct dpll_info *dpll, |
| + unsigned long input, |
| + unsigned long target) |
| +{ |
| + unsigned long best_diff = (unsigned long)-1; |
| + unsigned long diff; |
| + unsigned int fdpll; |
| + unsigned int m; |
| + unsigned int n; |
| + |
| + for (n = 39; n < 120; n++) { |
| + for (m = 0; m < 4; m++) { |
| + for (fdpll = 1; fdpll < 32; fdpll++) { |
| + unsigned long output; |
| + |
| + /* 1/2 (FRQSEL=1) for duty rate 50% */ |
| + output = input * (n + 1) / (m + 1) |
| + / (fdpll + 1) / 2; |
| + |
| + if (output >= 400000000) |
| + continue; |
| + |
| + diff = abs((long)output - (long)target); |
| + if (best_diff > diff) { |
| + best_diff = diff; |
| + dpll->n = n; |
| + dpll->m = m; |
| + dpll->fdpll = fdpll; |
| + dpll->output = output; |
| + } |
| + |
| + if (diff == 0) |
| + goto done; |
| + } |
| + } |
| + } |
| + |
| +done: |
| + dev_dbg(rcrtc->group->dev->dev, |
| + "output:%u, fdpll:%u, n:%u, m:%u, diff:%lu\n", |
| + dpll->output, dpll->fdpll, dpll->n, dpll->m, |
| + best_diff); |
| +} |
| + |
| static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) |
| { |
| const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; |
| + struct rcar_du_device *rcdu = rcrtc->group->dev; |
| unsigned long mode_clock = mode->clock * 1000; |
| unsigned long clk; |
| u32 value; |
| @@ -124,12 +177,18 @@ static void rcar_du_crtc_set_display_tim |
| escr = div | ESCR_DCLKSEL_CLKS; |
| |
| if (rcrtc->extclock) { |
| + struct dpll_info dpll = { 0 }; |
| unsigned long extclk; |
| unsigned long extrate; |
| unsigned long rate; |
| u32 extdiv; |
| |
| extclk = clk_get_rate(rcrtc->extclock); |
| + if (rcdu->info->dpll_ch & (1 << rcrtc->index)) { |
| + rcar_du_dpll_divider(rcrtc, &dpll, extclk, mode_clock); |
| + extclk = dpll.output; |
| + } |
| + |
| extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock); |
| extdiv = clamp(extdiv, 1U, 64U) - 1; |
| |
| @@ -140,7 +199,27 @@ static void rcar_du_crtc_set_display_tim |
| abs((long)rate - (long)mode_clock)) { |
| dev_dbg(rcrtc->group->dev->dev, |
| "crtc%u: using external clock\n", rcrtc->index); |
| - escr = extdiv | ESCR_DCLKSEL_DCLKIN; |
| + |
| + if (rcdu->info->dpll_ch & (1 << rcrtc->index)) { |
| + u32 dpllcr = DPLLCR_CODE | DPLLCR_CLKE |
| + | DPLLCR_FDPLL(dpll.fdpll) |
| + | DPLLCR_N(dpll.n) | DPLLCR_M(dpll.m) |
| + | DPLLCR_STBY; |
| + |
| + if (rcrtc->index == 1) |
| + dpllcr |= DPLLCR_PLCS1 |
| + | DPLLCR_INCS_DOTCLKIN1; |
| + else |
| + dpllcr |= DPLLCR_PLCS0 |
| + | DPLLCR_INCS_DOTCLKIN0; |
| + |
| + rcar_du_group_write(rcrtc->group, DPLLCR, |
| + dpllcr); |
| + |
| + escr = ESCR_DCLKSEL_DCLKIN | 1; |
| + } else { |
| + escr = ESCR_DCLKSEL_DCLKIN | extdiv; |
| + } |
| } |
| } |
| |
| --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c |
| +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c |
| @@ -163,6 +163,7 @@ static const struct rcar_du_device_info |
| }, |
| }, |
| .num_lvds = 1, |
| + .dpll_ch = BIT(1) | BIT(2), |
| }; |
| |
| static const struct rcar_du_device_info rcar_du_r8a7796_info = { |
| --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h |
| +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h |
| @@ -65,6 +65,7 @@ struct rcar_du_device_info { |
| unsigned int num_crtcs; |
| struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; |
| unsigned int num_lvds; |
| + unsigned int dpll_ch; |
| }; |
| |
| #define RCAR_DU_MAX_CRTCS 4 |
| --- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h |
| +++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h |
| @@ -277,6 +277,29 @@ |
| #define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */ |
| #define DEFR10_DEFE10 (1 << 0) |
| |
| +#define DPLLCR 0x20044 |
| +#define DPLLCR_CODE (0x95 << 24) |
| +#define DPLLCR_PLCS1 (1 << 23) |
| +/* |
| + * PLCS0 is bit 21, but H3 ES1.x requires bit 20 to be set as well. As bit 20 |
| + * isn't implemented by other SoC in the Gen3 family it can safely be set |
| + * unconditionally. |
| + */ |
| +#define DPLLCR_PLCS0 (3 << 20) |
| +#define DPLLCR_CLKE (1 << 18) |
| +#define DPLLCR_FDPLL(n) ((n) << 12) |
| +#define DPLLCR_N(n) ((n) << 5) |
| +#define DPLLCR_M(n) ((n) << 3) |
| +#define DPLLCR_STBY (1 << 2) |
| +#define DPLLCR_INCS_DOTCLKIN0 (0 << 0) |
| +#define DPLLCR_INCS_DOTCLKIN1 (1 << 1) |
| + |
| +#define DPLLC2R 0x20048 |
| +#define DPLLC2R_CODE (0x95 << 24) |
| +#define DPLLC2R_SELC (1 << 12) |
| +#define DPLLC2R_M(n) ((n) << 8) |
| +#define DPLLC2R_FDPLL(n) ((n) << 0) |
| + |
| /* ----------------------------------------------------------------------------- |
| * Display Timing Generation Registers |
| */ |