]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
Merge tag 'du-next-20180925' of git://linuxtv.org/pinchartl/media into drm-next
authorDave Airlie <airlied@redhat.com>
Thu, 27 Sep 2018 00:54:54 +0000 (10:54 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 27 Sep 2018 00:56:11 +0000 (10:56 +1000)
R-Car DU support for the D3 and E3 SoCs (v4.20)

Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/3289904.RCOHkcp7u8@avalon
Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
Documentation/devicetree/bindings/display/renesas,du.txt
drivers/gpu/drm/bridge/thc63lvd1024.c
drivers/gpu/drm/rcar-du/rcar_du_crtc.c
drivers/gpu/drm/rcar-du/rcar_du_crtc.h
drivers/gpu/drm/rcar-du/rcar_du_drv.c
drivers/gpu/drm/rcar-du/rcar_du_drv.h
drivers/gpu/drm/rcar-du/rcar_du_group.c
drivers/gpu/drm/rcar-du/rcar_du_kms.c
drivers/gpu/drm/rcar-du/rcar_lvds.c
drivers/gpu/drm/rcar-du/rcar_lvds_regs.h

index 5a4e379bb4149112e3201fd677f31d269bd2c8c1..3aeb0ec06fd02421bcb412fdfbca08747476b904 100644 (file)
@@ -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:
index caae2348a2921a81637c94994a031d86b0bebbbe..9de67be632d1a7a42bbd765e5fa3cbd07b9a01f5 100644 (file)
@@ -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         -
 
 
index c8b9edd5a7f41309c26f030022e1227273fd2d4a..b083a740565ca44f73fb1029d43113129409473d 100644 (file)
@@ -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,
 };
index 6288b9ad9e24581e905f79f9a3c0ba2c9dbe4438..17741843cf519be7769047c24db32732551f7d24 100644 (file)
@@ -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;
index 4990bbe9ba26a88157a5fbec8f98d70a7ceb66a3..59ac6e7d22c932777d1ca96dcee8f537d9d24ea1 100644 (file)
@@ -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__ */
index 0954ecd2f9430dd1594e4a84d0083d3584035791..084f58df4a8cf3baa8d3ab63091c23be708aa1b1 100644 (file)
@@ -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 },
        { }
 };
 
index fef9ea5c22f3e2a0a26b97a78eb5bae40b43d1c6..143c037e2c0f715ba8e109b62582df2fe0b8991c 100644 (file)
@@ -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
index ef2c177afb6d89b0d13f518881d05cd4acd0b156..d85f0a1c158173b97bf94b251a889f31e8fff948 100644 (file)
@@ -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)
index b5d79ecd25eaa08d2e03b4e682bd66c3507e37b9..4ebd61ecbee177ab928d7837b28469d049f3d491 100644 (file)
@@ -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);
index ce0eb68c3416d158e37f7c972f09f4321014b5aa..173d7ad0b991b49257882d1f8ceacec4d6c07cc8 100644 (file)
@@ -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 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
        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 },
        { }
 };
 
index 4870f50d9bec15a8d73ba320c8a39b60192cdd11..87149f2f8056035d1d39ae4d32eaed07256a7633 100644 (file)
@@ -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
 #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)
 #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__ */