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)
- 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;
}
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);
}
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
{
const struct drm_display_mode *mode = &rcrtc->crtc.mode;
+ unsigned long mode_clock = mode->clock * 1000;
unsigned long clk;
u32 value;
+ u32 escr;
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);
- div = DIV_ROUND_CLOSEST(clk, mode->clock * 1000);
+ div = DIV_ROUND_CLOSEST(clk, mode_clock);
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,
- ESCR_DCLKSEL_CLKS | div);
+ escr);
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? OTAR2 : OTAR, 0);
/* Signal polarities */
struct rcar_du_crtc *rcrtc = &rcdu->crtcs[index];
struct drm_crtc *crtc = &rcrtc->crtc;
unsigned int irqflags;
- char clk_name[5];
+ struct clk *clk;
+ char clk_name[9];
char *name;
int irq;
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)) {
sprintf(clk_name, "du.%u", index);
name = clk_name;
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->mmio_offset = mmio_offsets[index];
rcrtc->index = index;
rcar_du_group_write(rgrp, DEFR4, DEFR4_CODE);
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);
+ /* 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
* superposition 0 to DU0 pins. DU1 pins will be configured dynamically.
*/