Commit 1b30dbde authored by Laurent Pinchart's avatar Laurent Pinchart

drm: rcar-du: Add support for external pixel clock

The DU uses the module functional clock as the default pixel clock, but
supports using an externally supplied pixel clock instead. Support this
by adding the external pixel clock to the DT bindings, and selecting the
clock automatically at runtime based on the requested mode pixel
frequency.

The input clock pins to DU channels routing is configurable, but
currently hardcoded to connect input clock i to channel i.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
parent 0c1c8776
...@@ -26,6 +26,10 @@ Required Properties: ...@@ -26,6 +26,10 @@ Required Properties:
per LVDS encoder. The functional clocks must be named "du.x" with "x" per LVDS encoder. The functional clocks must be named "du.x" with "x"
being the channel numerical index. The LVDS clocks must be named being the channel numerical index. The LVDS clocks must be named
"lvds.x" with "x" being the LVDS encoder numerical index. "lvds.x" with "x" being the LVDS encoder numerical index.
- In addition to the functional and encoder clocks, all DU versions also
support externally supplied pixel clocks. Those clocks are optional.
When supplied they must be named "dclkin.x" with "x" being the input
clock numerical index.
Required nodes: Required nodes:
......
...@@ -74,33 +74,71 @@ static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc) ...@@ -74,33 +74,71 @@ static int rcar_du_crtc_get(struct rcar_du_crtc *rcrtc)
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = clk_prepare_enable(rcrtc->extclock);
if (ret < 0)
goto error_clock;
ret = rcar_du_group_get(rcrtc->group); ret = rcar_du_group_get(rcrtc->group);
if (ret < 0) if (ret < 0)
clk_disable_unprepare(rcrtc->clock); goto error_group;
return 0;
error_group:
clk_disable_unprepare(rcrtc->extclock);
error_clock:
clk_disable_unprepare(rcrtc->clock);
return ret; return ret;
} }
static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc) static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
{ {
rcar_du_group_put(rcrtc->group); rcar_du_group_put(rcrtc->group);
clk_disable_unprepare(rcrtc->extclock);
clk_disable_unprepare(rcrtc->clock); clk_disable_unprepare(rcrtc->clock);
} }
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
{ {
const struct drm_display_mode *mode = &rcrtc->crtc.mode; const struct drm_display_mode *mode = &rcrtc->crtc.mode;
unsigned long mode_clock = mode->clock * 1000;
unsigned long clk; unsigned long clk;
u32 value; u32 value;
u32 escr;
u32 div; u32 div;
/* Dot clock */ /* Compute the clock divisor and select the internal or external dot
* clock based on the requested frequency.
*/
clk = clk_get_rate(rcrtc->clock); clk = clk_get_rate(rcrtc->clock);
div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000); div = DIV_ROUND_CLOSEST(clk, mode_clock);
div = clamp(div, 1U, 64U) - 1; div = clamp(div, 1U, 64U) - 1;
escr = div | ESCR_DCLKSEL_CLKS;
if (rcrtc->extclock) {
unsigned long extclk;
unsigned long extrate;
unsigned long rate;
u32 extdiv;
extclk = clk_get_rate(rcrtc->extclock);
extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock);
extdiv = clamp(extdiv, 1U, 64U) - 1;
rate = clk / (div + 1);
extrate = extclk / (extdiv + 1);
if (abs((long)extrate - (long)mode_clock) <
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;
}
}
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR, rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? ESCR2 : ESCR,
ESCR_DCLKSEL_CLKS | div); escr);
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0); rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0);
/* Signal polarities */ /* Signal polarities */
...@@ -543,12 +581,13 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index) ...@@ -543,12 +581,13 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index]; struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index];
struct drm_crtc *crtc = &rcrtc->crtc; struct drm_crtc *crtc = &rcrtc->crtc;
unsigned int irqflags; unsigned int irqflags;
char clk_name[5]; struct clk *clk;
char clk_name[9];
char *name; char *name;
int irq; int irq;
int ret; int ret;
/* Get the CRTC clock. */ /* Get the CRTC clock and the optional external clock. */
if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) { if (rcar_du_has(rcdu, RCAR_DU_FEATURE_CRTC_IRQ_CLOCK)) {
sprintf(clk_name, "du.%u", index); sprintf(clk_name, "du.%u", index);
name = clk_name; name = clk_name;
...@@ -562,6 +601,15 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index) ...@@ -562,6 +601,15 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
return PTR_ERR(rcrtc->clock); return PTR_ERR(rcrtc->clock);
} }
sprintf(clk_name, "dclkin.%u", index);
clk = devm_clk_get(rcdu->dev, clk_name);
if (!IS_ERR(clk)) {
rcrtc->extclock = clk;
} else if (PTR_ERR(rcrtc->clock) == -EPROBE_DEFER) {
dev_info(rcdu->dev, "can't get external clock %u\n", index);
return -EPROBE_DEFER;
}
rcrtc->group = rgrp; rcrtc->group = rgrp;
rcrtc->mmio_offset = mmio_offsets[index]; rcrtc->mmio_offset = mmio_offsets[index];
rcrtc->index = index; rcrtc->index = index;
......
...@@ -26,6 +26,7 @@ struct rcar_du_crtc { ...@@ -26,6 +26,7 @@ struct rcar_du_crtc {
struct drm_crtc crtc; struct drm_crtc crtc;
struct clk *clock; struct clk *clock;
struct clk *extclock;
unsigned int mmio_offset; unsigned int mmio_offset;
unsigned int index; unsigned int index;
bool started; bool started;
......
...@@ -66,9 +66,21 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp) ...@@ -66,9 +66,21 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE); rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE);
rcar_du_group_write(rgrp, DEFR5, DEFR5_CODE | DEFR5_DEFE5); rcar_du_group_write(rgrp, DEFR5, DEFR5_CODE | DEFR5_DEFE5);
if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_EXT_CTRL_REGS)) if (rcar_du_has(rgrp->dev, RCAR_DU_FEATURE_EXT_CTRL_REGS)) {
rcar_du_group_setup_defr8(rgrp); rcar_du_group_setup_defr8(rgrp);
/* Configure input dot clock routing. We currently hardcode the
* configuration to routing DOTCLKINn to DUn.
*/
rcar_du_group_write(rgrp, DIDSR, DIDSR_CODE |
DIDSR_LCDS_DCLKIN(2) |
DIDSR_LCDS_DCLKIN(1) |
DIDSR_LCDS_DCLKIN(0) |
DIDSR_PDCS_CLK(2, 0) |
DIDSR_PDCS_CLK(1, 0) |
DIDSR_PDCS_CLK(0, 0));
}
/* Use DS1PR and DS2PR to configure planes priorities and connects the /* Use DS1PR and DS2PR to configure planes priorities and connects the
* superposition 0 to DU0 pins. DU1 pins will be configured dynamically. * superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
*/ */
......
...@@ -256,8 +256,8 @@ ...@@ -256,8 +256,8 @@
#define DIDSR_LCDS_LVDS0(n) (2 << (8 + (n) * 2)) #define DIDSR_LCDS_LVDS0(n) (2 << (8 + (n) * 2))
#define DIDSR_LCDS_LVDS1(n) (3 << (8 + (n) * 2)) #define DIDSR_LCDS_LVDS1(n) (3 << (8 + (n) * 2))
#define DIDSR_LCDS_MASK(n) (3 << (8 + (n) * 2)) #define DIDSR_LCDS_MASK(n) (3 << (8 + (n) * 2))
#define DIDSR_PCDS_CLK(n, clk) (clk << ((n) * 2)) #define DIDSR_PDCS_CLK(n, clk) (clk << ((n) * 2))
#define DIDSR_PCDS_MASK(n) (3 << ((n) * 2)) #define DIDSR_PDCS_MASK(n) (3 << ((n) * 2))
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Display Timing Generation Registers * Display Timing Generation Registers
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment