Enable support for R-Car Gen3 internal HDMI encoder.
 
 config DRM_RCAR_LVDS
-       bool "R-Car DU LVDS Encoder Support"
-       depends on DRM_RCAR_DU
+       tristate "R-Car DU LVDS Encoder Support"
+       depends on DRM && DRM_BRIDGE && OF
        select DRM_PANEL
        select OF_FLATTREE
        select OF_OVERLAY
 
                 rcar_du_encoder.o \
                 rcar_du_group.o \
                 rcar_du_kms.o \
-                rcar_du_lvdscon.o \
                 rcar_du_plane.o
 
-rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)    += rcar_du_lvdsenc.o
 rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)    += rcar_du_of.o \
                                           rcar_du_of_lvds_r8a7790.dtb.o \
                                           rcar_du_of_lvds_r8a7791.dtb.o \
 
 obj-$(CONFIG_DRM_RCAR_DU)              += rcar-du-drm.o
 obj-$(CONFIG_DRM_RCAR_DW_HDMI)         += rcar_dw_hdmi.o
+obj-$(CONFIG_DRM_RCAR_LVDS)            += rcar_lvds.o
 
 
 #include "rcar_du_drv.h"
 #include "rcar_du_kms.h"
+#include "rcar_du_of.h"
 #include "rcar_du_regs.h"
 
 /* -----------------------------------------------------------------------------
                        .port = 1,
                },
        },
-       .num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7779_info = {
                        .port = 1,
                },
        },
-       .num_lvds = 0,
 };
 
 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,
-       .quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES,
+       .quirks = RCAR_DU_QUIRK_ALIGN_128B,
        .num_crtcs = 3,
        .routes = {
                /*
                        .port = 1,
                },
        },
-       .num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7794_info = {
                        .port = 1,
                },
        },
-       .num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7795_info = {
        },
 };
 
-module_platform_driver(rcar_du_platform_driver);
+static int __init rcar_du_init(void)
+{
+       rcar_du_of_init(rcar_du_of_table);
+
+       return platform_driver_register(&rcar_du_platform_driver);
+}
+module_init(rcar_du_init);
+
+static void __exit rcar_du_exit(void)
+{
+       platform_driver_unregister(&rcar_du_platform_driver);
+}
+module_exit(rcar_du_exit);
 
 MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
 MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
 
 struct drm_device;
 struct drm_fbdev_cma;
 struct rcar_du_device;
-struct rcar_du_lvdsenc;
 
 #define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0)        /* Per-CRTC IRQ and clock */
 #define RCAR_DU_FEATURE_EXT_CTRL_REGS  (1 << 1)        /* Has extended control registers */
 #define RCAR_DU_FEATURE_VSP1_SOURCE    (1 << 2)        /* Has inputs from VSP1 */
 
 #define RCAR_DU_QUIRK_ALIGN_128B       (1 << 0)        /* Align pitches to 128 bytes */
-#define RCAR_DU_QUIRK_LVDS_LANES       (1 << 1)        /* LVDS lanes 1 and 3 inverted */
 
 /*
  * struct rcar_du_output_routing - Output routing specification
 
 #define RCAR_DU_MAX_CRTCS              4
 #define RCAR_DU_MAX_GROUPS             DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
-#define RCAR_DU_MAX_LVDS               2
 #define RCAR_DU_MAX_VSPS               4
 
 struct rcar_du_device {
 
        unsigned int dpad0_source;
        unsigned int vspd1_sink;
-
-       struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
 };
 
 static inline bool rcar_du_has(struct rcar_du_device *rcdu,
 
 #include "rcar_du_drv.h"
 #include "rcar_du_encoder.h"
 #include "rcar_du_kms.h"
-#include "rcar_du_lvdscon.h"
-#include "rcar_du_lvdsenc.h"
 
 /* -----------------------------------------------------------------------------
  * Encoder
  */
 
-static void rcar_du_encoder_disable(struct drm_encoder *encoder)
-{
-       struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-
-       if (renc->connector && renc->connector->panel) {
-               drm_panel_disable(renc->connector->panel);
-               drm_panel_unprepare(renc->connector->panel);
-       }
-
-       if (renc->lvds)
-               rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
-}
-
-static void rcar_du_encoder_enable(struct drm_encoder *encoder)
-{
-       struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-
-       if (renc->lvds)
-               rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
-
-       if (renc->connector && renc->connector->panel) {
-               drm_panel_prepare(renc->connector->panel);
-               drm_panel_enable(renc->connector->panel);
-       }
-}
-
-static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
-                                       struct drm_crtc_state *crtc_state,
-                                       struct drm_connector_state *conn_state)
-{
-       struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-       struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
-       const struct drm_display_mode *mode = &crtc_state->mode;
-       struct drm_connector *connector = conn_state->connector;
-       struct drm_device *dev = encoder->dev;
-
-       /*
-        * Only panel-related encoder types require validation here, everything
-        * else is handled by the bridge drivers.
-        */
-       if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
-               const struct drm_display_mode *panel_mode;
-
-               if (list_empty(&connector->modes)) {
-                       dev_dbg(dev->dev, "encoder: empty modes list\n");
-                       return -EINVAL;
-               }
-
-               panel_mode = list_first_entry(&connector->modes,
-                                             struct drm_display_mode, head);
-
-               /* We're not allowed to modify the resolution. */
-               if (mode->hdisplay != panel_mode->hdisplay ||
-                   mode->vdisplay != panel_mode->vdisplay)
-                       return -EINVAL;
-
-               /*
-                * The flat panel mode is fixed, just copy it to the adjusted
-                * mode.
-                */
-               drm_mode_copy(adjusted_mode, panel_mode);
-       }
-
-       if (renc->lvds)
-               rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
-
-       return 0;
-}
-
 static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
                                     struct drm_crtc_state *crtc_state,
                                     struct drm_connector_state *conn_state)
 {
        struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-       struct drm_display_info *info = &conn_state->connector->display_info;
-       enum rcar_lvds_mode mode;
 
        rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
-
-       if (!renc->lvds) {
-               /*
-                * The DU driver creates connectors only for the outputs of the
-                * internal LVDS encoders.
-                */
-               renc->connector = NULL;
-               return;
-       }
-
-       renc->connector = to_rcar_connector(conn_state->connector);
-
-       if (!info->num_bus_formats || !info->bus_formats) {
-               dev_err(encoder->dev->dev, "no LVDS bus format reported\n");
-               return;
-       }
-
-       switch (info->bus_formats[0]) {
-       case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
-       case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
-               mode = RCAR_LVDS_MODE_JEIDA;
-               break;
-       case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
-               mode = RCAR_LVDS_MODE_VESA;
-               break;
-       default:
-               dev_err(encoder->dev->dev,
-                       "unsupported LVDS bus format 0x%04x\n",
-                       info->bus_formats[0]);
-               return;
-       }
-
-       if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
-               mode |= RCAR_LVDS_MODE_MIRROR;
-
-       rcar_du_lvdsenc_set_mode(renc->lvds, mode);
 }
 
 static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
        .atomic_mode_set = rcar_du_encoder_mode_set,
-       .disable = rcar_du_encoder_disable,
-       .enable = rcar_du_encoder_enable,
-       .atomic_check = rcar_du_encoder_atomic_check,
 };
 
 static const struct drm_encoder_funcs encoder_funcs = {
        renc->output = output;
        encoder = rcar_encoder_to_drm_encoder(renc);
 
-       switch (output) {
-       case RCAR_DU_OUTPUT_LVDS0:
-               renc->lvds = rcdu->lvds[0];
-               break;
+       dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
+               enc_node, output);
 
-       case RCAR_DU_OUTPUT_LVDS1:
-               renc->lvds = rcdu->lvds[1];
-               break;
-
-       default:
-               break;
-       }
-
-       if (enc_node) {
-               dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
-                       enc_node, output);
-
-               /* Locate the DRM bridge from the encoder DT node. */
-               bridge = of_drm_find_bridge(enc_node);
-               if (!bridge) {
-                       ret = -EPROBE_DEFER;
-                       goto done;
-               }
-       } else {
-               dev_dbg(rcdu->dev,
-                       "initializing internal encoder for output %u\n",
-                       output);
+       /* Locate the DRM bridge from the encoder DT node. */
+       bridge = of_drm_find_bridge(enc_node);
+       if (!bridge) {
+               ret = -EPROBE_DEFER;
+               goto done;
        }
 
        ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
 
        drm_encoder_helper_add(encoder, &encoder_helper_funcs);
 
-       if (bridge) {
-               /*
-                * Attach the bridge to the encoder. The bridge will create the
-                * connector.
-                */
-               ret = drm_bridge_attach(encoder, bridge, NULL);
-               if (ret) {
-                       drm_encoder_cleanup(encoder);
-                       return ret;
-               }
-       } else {
-               /* There's no bridge, create the connector manually. */
-               switch (output) {
-               case RCAR_DU_OUTPUT_LVDS0:
-               case RCAR_DU_OUTPUT_LVDS1:
-                       ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
-                       break;
-
-               default:
-                       ret = -EINVAL;
-                       break;
-               }
+       /*
+        * Attach the bridge to the encoder. The bridge will create the
+        * connector.
+        */
+       ret = drm_bridge_attach(encoder, bridge, NULL);
+       if (ret) {
+               drm_encoder_cleanup(encoder);
+               return ret;
        }
 
 done:
 
 
 struct drm_panel;
 struct rcar_du_device;
-struct rcar_du_lvdsenc;
 
 struct rcar_du_encoder {
        struct drm_encoder base;
        enum rcar_du_output output;
-       struct rcar_du_connector *connector;
-       struct rcar_du_lvdsenc *lvds;
 };
 
 #define to_rcar_encoder(e) \
 
 #define rcar_encoder_to_drm_encoder(e) (&(e)->base)
 
-struct rcar_du_connector {
-       struct drm_connector connector;
-       struct rcar_du_encoder *encoder;
-       struct drm_panel *panel;
-};
-
-#define to_rcar_connector(c) \
-       container_of(c, struct rcar_du_connector, connector)
-
 int rcar_du_encoder_init(struct rcar_du_device *rcdu,
                         enum rcar_du_output output,
                         struct device_node *enc_node,
 
 #include "rcar_du_drv.h"
 #include "rcar_du_encoder.h"
 #include "rcar_du_kms.h"
-#include "rcar_du_lvdsenc.h"
 #include "rcar_du_regs.h"
 #include "rcar_du_vsp.h"
 
        of_node_put(entity_ep_node);
 
        if (!encoder) {
-               /*
-                * If no encoder has been found the entity must be the
-                * connector.
-                */
-               connector = entity;
+               dev_warn(rcdu->dev,
+                        "no encoder found for endpoint %pOF, skipping\n",
+                        ep->local_node);
+               return -ENODEV;
        }
 
        ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
        }
 
        /* Initialize the encoders. */
-       ret = rcar_du_lvdsenc_init(rcdu);
-       if (ret < 0)
-               return ret;
-
        ret = rcar_du_encoders_init(rcdu);
        if (ret < 0)
                return ret;
 
+++ /dev/null
-/*
- * rcar_du_lvdscon.c  --  R-Car Display Unit LVDS Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#include <drm/drmP.h>
-#include <drm/drm_atomic_helper.h>
-#include <drm/drm_crtc.h>
-#include <drm/drm_crtc_helper.h>
-#include <drm/drm_panel.h>
-
-#include <video/display_timing.h>
-#include <video/of_display_timing.h>
-#include <video/videomode.h>
-
-#include "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_kms.h"
-#include "rcar_du_lvdscon.h"
-
-static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
-{
-       struct rcar_du_connector *rcon = to_rcar_connector(connector);
-
-       return drm_panel_get_modes(rcon->panel);
-}
-
-static const struct drm_connector_helper_funcs connector_helper_funcs = {
-       .get_modes = rcar_du_lvds_connector_get_modes,
-};
-
-static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
-{
-       struct rcar_du_connector *rcon = to_rcar_connector(connector);
-
-       drm_panel_detach(rcon->panel);
-       drm_connector_cleanup(connector);
-}
-
-static const struct drm_connector_funcs connector_funcs = {
-       .reset = drm_atomic_helper_connector_reset,
-       .fill_modes = drm_helper_probe_single_connector_modes,
-       .destroy = rcar_du_lvds_connector_destroy,
-       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
-                               struct rcar_du_encoder *renc,
-                               const struct device_node *np)
-{
-       struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
-       struct rcar_du_connector *rcon;
-       struct drm_connector *connector;
-       int ret;
-
-       rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
-       if (rcon == NULL)
-               return -ENOMEM;
-
-       connector = &rcon->connector;
-
-       rcon->panel = of_drm_find_panel(np);
-       if (!rcon->panel)
-               return -EPROBE_DEFER;
-
-       ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
-                                DRM_MODE_CONNECTOR_LVDS);
-       if (ret < 0)
-               return ret;
-
-       drm_connector_helper_add(connector, &connector_helper_funcs);
-
-       ret = drm_mode_connector_attach_encoder(connector, encoder);
-       if (ret < 0)
-               return ret;
-
-       ret = drm_panel_attach(rcon->panel, connector);
-       if (ret < 0)
-               return ret;
-
-       rcon->encoder = renc;
-
-       return 0;
-}
 
+++ /dev/null
-/*
- * rcar_du_lvdscon.h  --  R-Car Display Unit LVDS Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#ifndef __RCAR_DU_LVDSCON_H__
-#define __RCAR_DU_LVDSCON_H__
-
-struct rcar_du_device;
-struct rcar_du_encoder;
-
-int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
-                               struct rcar_du_encoder *renc,
-                               const struct device_node *np);
-
-#endif /* __RCAR_DU_LVDSCON_H__ */
 
+++ /dev/null
-/*
- * rcar_du_lvdsenc.c  --  R-Car Display Unit LVDS Encoder
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-
-#include "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_lvdsenc.h"
-#include "rcar_lvds_regs.h"
-
-struct rcar_du_lvdsenc {
-       struct rcar_du_device *dev;
-
-       unsigned int index;
-       void __iomem *mmio;
-       struct clk *clock;
-       bool enabled;
-
-       enum rcar_lvds_input input;
-       enum rcar_lvds_mode mode;
-};
-
-static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
-{
-       iowrite32(data, lvds->mmio + reg);
-}
-
-static u32 rcar_lvds_lvdpllcr_gen2(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;
-       else
-               return LVDPLLCR_PLLDLYCNT_150M;
-}
-
-static u32 rcar_lvds_lvdpllcr_gen3(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;
-       else
-               return LVDPLLCR_PLLDIVCNT_148M;
-}
-
-static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
-                                struct rcar_du_crtc *rcrtc)
-{
-       const struct drm_display_mode *mode = &rcrtc->crtc.mode;
-       u32 lvdpllcr;
-       u32 lvdhcr;
-       u32 lvdcr0;
-       int ret;
-
-       if (lvds->enabled)
-               return 0;
-
-       ret = clk_prepare_enable(lvds->clock);
-       if (ret < 0)
-               return ret;
-
-       /*
-        * Hardcode the channels and control signals routing for now.
-        *
-        * HSYNC -> CTRL0
-        * VSYNC -> CTRL1
-        * DISP  -> CTRL2
-        * 0     -> CTRL3
-        */
-       rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
-                       LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
-                       LVDCTRCR_CTR0SEL_HSYNC);
-
-       if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
-               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
-                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
-       else
-               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
-                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
-
-       rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
-
-       /* PLL clock configuration. */
-       if (lvds->dev->info->gen < 3)
-               lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
-       else
-               lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
-       rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
-
-       /* Set the LVDS mode and select the input. */
-       lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
-       if (rcrtc->index == 2)
-               lvdcr0 |= LVDCR0_DUSEL;
-       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-       /* Turn all the channels on. */
-       rcar_lvds_write(lvds, LVDCR1,
-                       LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
-                       LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
-
-       if (lvds->dev->info->gen < 3) {
-               /* Enable LVDS operation and turn the bias circuitry on. */
-               lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
-               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-       }
-
-       /* Turn the PLL on. */
-       lvdcr0 |= LVDCR0_PLLON;
-       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-       if (lvds->dev->info->gen > 2) {
-               /* Set LVDS normal mode. */
-               lvdcr0 |= LVDCR0_PWD;
-               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-       }
-
-       /* Wait for the startup delay. */
-       usleep_range(100, 150);
-
-       /* Turn the output on. */
-       lvdcr0 |= LVDCR0_LVRES;
-       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-       lvds->enabled = true;
-
-       return 0;
-}
-
-static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
-{
-       if (!lvds->enabled)
-               return;
-
-       rcar_lvds_write(lvds, LVDCR0, 0);
-       rcar_lvds_write(lvds, LVDCR1, 0);
-
-       clk_disable_unprepare(lvds->clock);
-
-       lvds->enabled = false;
-}
-
-int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
-                          bool enable)
-{
-       if (!enable) {
-               rcar_du_lvdsenc_stop(lvds);
-               return 0;
-       } else if (crtc) {
-               struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
-               return rcar_du_lvdsenc_start(lvds, rcrtc);
-       } else
-               return -EINVAL;
-}
-
-void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-                                 struct drm_display_mode *mode)
-{
-       /*
-        * The internal LVDS encoder has a restricted clock frequency operating
-        * range (31MHz to 148.5MHz). Clamp the clock accordingly.
-        */
-       mode->clock = clamp(mode->clock, 31000, 148500);
-}
-
-void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-                             enum rcar_lvds_mode mode)
-{
-       lvds->mode = mode;
-}
-
-static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
-                                        struct platform_device *pdev)
-{
-       struct resource *mem;
-       char name[7];
-
-       sprintf(name, "lvds.%u", lvds->index);
-
-       mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
-       lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
-       if (IS_ERR(lvds->mmio))
-               return PTR_ERR(lvds->mmio);
-
-       lvds->clock = devm_clk_get(&pdev->dev, name);
-       if (IS_ERR(lvds->clock)) {
-               dev_err(&pdev->dev, "failed to get clock for %s\n", name);
-               return PTR_ERR(lvds->clock);
-       }
-
-       return 0;
-}
-
-int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
-{
-       struct platform_device *pdev = to_platform_device(rcdu->dev);
-       struct rcar_du_lvdsenc *lvds;
-       unsigned int i;
-       int ret;
-
-       for (i = 0; i < rcdu->info->num_lvds; ++i) {
-               lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
-               if (lvds == NULL)
-                       return -ENOMEM;
-
-               lvds->dev = rcdu;
-               lvds->index = i;
-               lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
-               lvds->enabled = false;
-
-               ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
-               if (ret < 0)
-                       return ret;
-
-               rcdu->lvds[i] = lvds;
-       }
-
-       return 0;
-}
 
+++ /dev/null
-/*
- * rcar_du_lvdsenc.h  --  R-Car Display Unit LVDS Encoder
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#ifndef __RCAR_DU_LVDSENC_H__
-#define __RCAR_DU_LVDSENC_H__
-
-#include <linux/io.h>
-#include <linux/module.h>
-
-struct rcar_drm_crtc;
-struct rcar_du_lvdsenc;
-
-enum rcar_lvds_input {
-       RCAR_LVDS_INPUT_DU0,
-       RCAR_LVDS_INPUT_DU1,
-       RCAR_LVDS_INPUT_DU2,
-};
-
-/* Keep in sync with the LVDCR0.LVMD hardware register values. */
-enum rcar_lvds_mode {
-       RCAR_LVDS_MODE_JEIDA = 0,
-       RCAR_LVDS_MODE_MIRROR = 1,
-       RCAR_LVDS_MODE_VESA = 4,
-};
-
-#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
-int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
-void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-                             enum rcar_lvds_mode mode);
-int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
-                          struct drm_crtc *crtc, bool enable);
-void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-                                 struct drm_display_mode *mode);
-#else
-static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
-{
-       return 0;
-}
-static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-                                           enum rcar_lvds_mode mode)
-{
-}
-static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
-                                        struct drm_crtc *crtc, bool enable)
-{
-       return 0;
-}
-static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-                                               struct drm_display_mode *mode)
-{
-}
-#endif
-
-#endif /* __RCAR_DU_LVDSENC_H__ */
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_lvds.c  --  R-Car LVDS Encoder
+ *
+ * Copyright (C) 2013-2018 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+
+#include "rcar_lvds_regs.h"
+
+/* Keep in sync with the LVDCR0.LVMD hardware register values. */
+enum rcar_lvds_mode {
+       RCAR_LVDS_MODE_JEIDA = 0,
+       RCAR_LVDS_MODE_MIRROR = 1,
+       RCAR_LVDS_MODE_VESA = 4,
+};
+
+#define RCAR_LVDS_QUIRK_LANES  (1 << 0)        /* LVDS lanes 1 and 3 inverted */
+
+struct rcar_lvds_device_info {
+       unsigned int gen;
+       unsigned int quirks;
+};
+
+struct rcar_lvds {
+       struct device *dev;
+       const struct rcar_lvds_device_info *info;
+
+       struct drm_bridge bridge;
+
+       struct drm_bridge *next_bridge;
+       struct drm_connector connector;
+       struct drm_panel *panel;
+
+       void __iomem *mmio;
+       struct clk *clock;
+       bool enabled;
+
+       struct drm_display_mode display_mode;
+       enum rcar_lvds_mode mode;
+};
+
+#define bridge_to_rcar_lvds(bridge) \
+       container_of(bridge, struct rcar_lvds, bridge)
+
+#define connector_to_rcar_lvds(connector) \
+       container_of(connector, struct rcar_lvds, connector)
+
+static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
+{
+       iowrite32(data, lvds->mmio + reg);
+}
+
+/* -----------------------------------------------------------------------------
+ * Connector & Panel
+ */
+
+static int rcar_lvds_connector_get_modes(struct drm_connector *connector)
+{
+       struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
+
+       return drm_panel_get_modes(lvds->panel);
+}
+
+static int rcar_lvds_connector_atomic_check(struct drm_connector *connector,
+                                           struct drm_connector_state *state)
+{
+       struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
+       const struct drm_display_mode *panel_mode;
+       struct drm_crtc_state *crtc_state;
+
+       if (list_empty(&connector->modes)) {
+               dev_dbg(lvds->dev, "connector: empty modes list\n");
+               return -EINVAL;
+       }
+
+       panel_mode = list_first_entry(&connector->modes,
+                                     struct drm_display_mode, head);
+
+       /* We're not allowed to modify the resolution. */
+       crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+       if (IS_ERR(crtc_state))
+               return PTR_ERR(crtc_state);
+
+       if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
+           crtc_state->mode.vdisplay != panel_mode->vdisplay)
+               return -EINVAL;
+
+       /* The flat panel mode is fixed, just copy it to the adjusted mode. */
+       drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
+
+       return 0;
+}
+
+static const struct drm_connector_helper_funcs rcar_lvds_conn_helper_funcs = {
+       .get_modes = rcar_lvds_connector_get_modes,
+       .atomic_check = rcar_lvds_connector_atomic_check,
+};
+
+static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
+       .reset = drm_atomic_helper_connector_reset,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = drm_connector_cleanup,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Bridge
+ */
+
+static u32 rcar_lvds_lvdpllcr_gen2(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;
+       else
+               return LVDPLLCR_PLLDLYCNT_150M;
+}
+
+static u32 rcar_lvds_lvdpllcr_gen3(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;
+       else
+               return LVDPLLCR_PLLDIVCNT_148M;
+}
+
+static void rcar_lvds_enable(struct drm_bridge *bridge)
+{
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       const struct drm_display_mode *mode = &lvds->display_mode;
+       /*
+        * FIXME: We should really retrieve the CRTC through the state, but how
+        * 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);
+       if (ret < 0)
+               return;
+
+       /*
+        * Hardcode the channels and control signals routing for now.
+        *
+        * HSYNC -> CTRL0
+        * VSYNC -> CTRL1
+        * DISP  -> CTRL2
+        * 0     -> CTRL3
+        */
+       rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
+                       LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
+                       LVDCTRCR_CTR0SEL_HSYNC);
+
+       if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
+               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
+                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
+       else
+               lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
+                      | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
+
+       rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
+
+       /* PLL clock configuration. */
+       if (lvds->info->gen < 3)
+               lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
+       else
+               lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
+       rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
+
+       /* Set the LVDS mode and select the input. */
+       lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
+       if (drm_crtc_index(crtc) == 2)
+               lvdcr0 |= LVDCR0_DUSEL;
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+       /* Turn all the channels on. */
+       rcar_lvds_write(lvds, LVDCR1,
+                       LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
+                       LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
+
+       if (lvds->info->gen < 3) {
+               /* Enable LVDS operation and turn the bias circuitry on. */
+               lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+
+       /* Turn the PLL on. */
+       lvdcr0 |= LVDCR0_PLLON;
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+       if (lvds->info->gen > 2) {
+               /* Set LVDS normal mode. */
+               lvdcr0 |= LVDCR0_PWD;
+               rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+       }
+
+       /* Wait for the startup delay. */
+       usleep_range(100, 150);
+
+       /* Turn the output on. */
+       lvdcr0 |= LVDCR0_LVRES;
+       rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+       if (lvds->panel) {
+               drm_panel_prepare(lvds->panel);
+               drm_panel_enable(lvds->panel);
+       }
+
+       lvds->enabled = true;
+}
+
+static void rcar_lvds_disable(struct drm_bridge *bridge)
+{
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+       WARN_ON(!lvds->enabled);
+
+       if (lvds->panel) {
+               drm_panel_disable(lvds->panel);
+               drm_panel_unprepare(lvds->panel);
+       }
+
+       rcar_lvds_write(lvds, LVDCR0, 0);
+       rcar_lvds_write(lvds, LVDCR1, 0);
+
+       clk_disable_unprepare(lvds->clock);
+
+       lvds->enabled = false;
+}
+
+static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
+                                const struct drm_display_mode *mode,
+                                struct drm_display_mode *adjusted_mode)
+{
+       /*
+        * The internal LVDS encoder has a restricted clock frequency operating
+        * range (31MHz to 148.5MHz). Clamp the clock accordingly.
+        */
+       adjusted_mode->clock = clamp(adjusted_mode->clock, 31000, 148500);
+
+       return true;
+}
+
+static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
+{
+       struct drm_display_info *info = &lvds->connector.display_info;
+       enum rcar_lvds_mode mode;
+
+       /*
+        * There is no API yet to retrieve LVDS mode from a bridge, only panels
+        * are supported.
+        */
+       if (!lvds->panel)
+               return;
+
+       if (!info->num_bus_formats || !info->bus_formats) {
+               dev_err(lvds->dev, "no LVDS bus format reported\n");
+               return;
+       }
+
+       switch (info->bus_formats[0]) {
+       case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+       case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+               mode = RCAR_LVDS_MODE_JEIDA;
+               break;
+       case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+               mode = RCAR_LVDS_MODE_VESA;
+               break;
+       default:
+               dev_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
+                       info->bus_formats[0]);
+               return;
+       }
+
+       if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
+               mode |= RCAR_LVDS_MODE_MIRROR;
+
+       lvds->mode = mode;
+}
+
+static void rcar_lvds_mode_set(struct drm_bridge *bridge,
+                              struct drm_display_mode *mode,
+                              struct drm_display_mode *adjusted_mode)
+{
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+       WARN_ON(lvds->enabled);
+
+       lvds->display_mode = *adjusted_mode;
+
+       rcar_lvds_get_lvds_mode(lvds);
+}
+
+static int rcar_lvds_attach(struct drm_bridge *bridge)
+{
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+       struct drm_connector *connector = &lvds->connector;
+       struct drm_encoder *encoder = bridge->encoder;
+       int ret;
+
+       /* If we have a next bridge just attach it. */
+       if (lvds->next_bridge)
+               return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
+                                        bridge);
+
+       /* Otherwise we have a panel, create a connector. */
+       ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
+                                DRM_MODE_CONNECTOR_LVDS);
+       if (ret < 0)
+               return ret;
+
+       drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);
+
+       ret = drm_mode_connector_attach_encoder(connector, encoder);
+       if (ret < 0)
+               return ret;
+
+       return drm_panel_attach(lvds->panel, connector);
+}
+
+static void rcar_lvds_detach(struct drm_bridge *bridge)
+{
+       struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+       if (lvds->panel)
+               drm_panel_detach(lvds->panel);
+}
+
+static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
+       .attach = rcar_lvds_attach,
+       .detach = rcar_lvds_detach,
+       .enable = rcar_lvds_enable,
+       .disable = rcar_lvds_disable,
+       .mode_fixup = rcar_lvds_mode_fixup,
+       .mode_set = rcar_lvds_mode_set,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
+
+static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
+{
+       struct device_node *local_output = NULL;
+       struct device_node *remote_input = NULL;
+       struct device_node *remote = NULL;
+       struct device_node *node;
+       bool is_bridge = false;
+       int ret = 0;
+
+       local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
+       if (!local_output) {
+               dev_dbg(lvds->dev, "unconnected port@1\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Locate the connected entity and infer its type from the number of
+        * endpoints.
+        */
+       remote = of_graph_get_remote_port_parent(local_output);
+       if (!remote) {
+               dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
+               ret = -ENODEV;
+               goto done;
+       }
+
+       if (!of_device_is_available(remote)) {
+               dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
+                       remote);
+               ret = -ENODEV;
+               goto done;
+       }
+
+       remote_input = of_graph_get_remote_endpoint(local_output);
+
+       for_each_endpoint_of_node(remote, node) {
+               if (node != remote_input) {
+                       /*
+                        * We've found one endpoint other than the input, this
+                        * must be a bridge.
+                        */
+                       is_bridge = true;
+                       of_node_put(node);
+                       break;
+               }
+       }
+
+       if (is_bridge) {
+               lvds->next_bridge = of_drm_find_bridge(remote);
+               if (!lvds->next_bridge)
+                       ret = -EPROBE_DEFER;
+       } else {
+               lvds->panel = of_drm_find_panel(remote);
+               if (!lvds->panel)
+                       ret = -EPROBE_DEFER;
+       }
+
+done:
+       of_node_put(local_output);
+       of_node_put(remote_input);
+       of_node_put(remote);
+
+       return ret;
+}
+
+static int rcar_lvds_probe(struct platform_device *pdev)
+{
+       struct rcar_lvds *lvds;
+       struct resource *mem;
+       int ret;
+
+       lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+       if (lvds == NULL)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, lvds);
+
+       lvds->dev = &pdev->dev;
+       lvds->info = of_device_get_match_data(&pdev->dev);
+       lvds->enabled = false;
+
+       ret = rcar_lvds_parse_dt(lvds);
+       if (ret < 0)
+               return ret;
+
+       lvds->bridge.driver_private = lvds;
+       lvds->bridge.funcs = &rcar_lvds_bridge_ops;
+       lvds->bridge.of_node = pdev->dev.of_node;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
+       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);
+       }
+
+       drm_bridge_add(&lvds->bridge);
+
+       return 0;
+}
+
+static int rcar_lvds_remove(struct platform_device *pdev)
+{
+       struct rcar_lvds *lvds = platform_get_drvdata(pdev);
+
+       drm_bridge_remove(&lvds->bridge);
+
+       return 0;
+}
+
+static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
+       .gen = 2,
+};
+
+static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
+       .gen = 2,
+       .quirks = RCAR_LVDS_QUIRK_LANES,
+};
+
+static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
+       .gen = 3,
+};
+
+static const struct of_device_id rcar_lvds_of_table[] = {
+       { .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
+       { .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
+       { .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
+       { .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
+
+static struct platform_driver rcar_lvds_platform_driver = {
+       .probe          = rcar_lvds_probe,
+       .remove         = rcar_lvds_remove,
+       .driver         = {
+               .name   = "rcar-lvds",
+               .of_match_table = rcar_lvds_of_table,
+       },
+};
+
+module_platform_driver(rcar_lvds_platform_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
+MODULE_LICENSE("GPL");