]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/gpu/drm/i2c/sn65dsi83.c
drm: i2c: add support for SN65DSI83
[karo-tx-linux.git] / drivers / gpu / drm / i2c / sn65dsi83.c
diff --git a/drivers/gpu/drm/i2c/sn65dsi83.c b/drivers/gpu/drm/i2c/sn65dsi83.c
new file mode 100644 (file)
index 0000000..655d3bc
--- /dev/null
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (c) 2017 Lothar Waßmann <LW@KARO-electronics.de>
+ *
+ * Texas Instruments SN65DSI83 MIPI DSI to LVDS converter driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#include <video/of_display_timing.h>
+
+#include "sn65dsi83.h"
+
+#define SYNC_DELAY     22
+
+struct sn65dsi83 {
+       struct i2c_client *client;
+       struct i2c_client *i2c_edid;
+
+       struct regmap *regmap;
+       struct drm_panel *panel;
+
+       enum drm_connector_status status;
+       bool powered;
+
+       struct drm_display_mode curr_mode;
+       bool mode_valid;
+       int bus_format;
+
+       unsigned int current_edid_segment;
+       uint8_t edid_buf[256];
+       bool edid_read;
+       struct edid *edid;
+
+       wait_queue_head_t wq;
+       struct drm_bridge bridge;
+       struct drm_connector connector;
+       struct drm_encoder *encoder;
+
+       struct regulator *vcc_reg;
+       struct clk *refclk;
+       struct gpio_desc *enable_gpio;
+
+       struct device_node *host_node;
+       struct mipi_dsi_device *dsi;
+       u8 num_dsi_lanes;
+
+       u8 errstat;
+};
+
+static struct sn65dsi83 *encoder_to_sn65dsi83(struct drm_encoder *encoder)
+{
+       return to_encoder_slave(encoder)->slave_priv;
+}
+
+static const struct reg_sequence sn65dsi83_fixed_registers[] = {
+       { 0x00, 0x35, },
+       { 0x01, 0x38, },
+       { 0x02, 0x49, },
+       { 0x03, 0x53, },
+       { 0x04, 0x44, },
+       { 0x05, 0x20, },
+       { 0x06, 0x20, },
+       { 0x07, 0x20, },
+       { 0x08, 0x01, },
+};
+
+static const uint8_t sn65dsi83_register_defaults[] = {
+       /* 0     1     2     3     4     5     6     7
+        * 8     9     a     b     c     d     e     f */
+       0x01, 0x20, 0x20, 0x20, 0x44, 0x53, 0x49, 0x38, /* 00 */
+       0x35, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */
+       0x70, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 20 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 40 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 50 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 90 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* a0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* d0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* e0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* f0 */
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static bool sn65dsi83_register_volatile(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case SN65DSI83_REG_CHIPREV(0):
+       case SN65DSI83_REG_CHIPREV(1):
+       case SN65DSI83_REG_CHIPREV(2):
+       case SN65DSI83_REG_CHIPREV(3):
+       case SN65DSI83_REG_CHIPREV(4):
+       case SN65DSI83_REG_CHIPREV(5):
+       case SN65DSI83_REG_CHIPREV(6):
+       case SN65DSI83_REG_CHIPREV(7):
+       case SN65DSI83_REG_CHIPREV(8):
+       case SN65DSI83_REG_RSTCTRL:
+       case SN65DSI83_REG_LVDSCTRL:
+       case SN65DSI83_REG_DSICTRL:
+       case SN65DSI83_REG_PLLCTRL:
+       case SN65DSI83_REG_LVDSCFG0:
+       case SN65DSI83_REG_LVDSCFG1:
+       case SN65DSI83_REG_LVDSCFG2:
+       case SN65DSI83_REG_LVDSCFG3:
+       case SN65DSI83_REG_LVDSCFG4:
+       case SN65DSI83_REG_LVDSCFG5:
+       case SN65DSI83_REG_LVDSCFG6:
+       case SN65DSI83_REG_LVDSCFG7:
+
+       case SN65DSI83_REG_LINE_LENGTH_LSB:
+       case SN65DSI83_REG_LINE_LENGTH_MSB:
+       case SN65DSI83_REG_VERT_SIZE_LSB:
+       case SN65DSI83_REG_VERT_SIZE_MSB:
+       case SN65DSI83_REG_SYNC_DELAY_LSB:
+       case SN65DSI83_REG_SYNC_DELAY_MSB:
+       case SN65DSI83_REG_HSYNC_WIDTH_LSB:
+       case SN65DSI83_REG_HSYNC_WIDTH_MSB:
+       case SN65DSI83_REG_VSYNC_WIDTH_LSB:
+       case SN65DSI83_REG_VSYNC_WIDTH_MSB:
+       case SN65DSI83_REG_H_BACK_PORCH:
+       case SN65DSI83_REG_V_BACK_PORCH:
+       case SN65DSI83_REG_H_FRONT_PORCH:
+       case SN65DSI83_REG_V_FRONT_PORCH:
+
+       case SN65DSI83_REG_IRQCTRL:
+       case SN65DSI83_REG_IRQEN:
+       case SN65DSI83_REG_ERRSTAT:
+               return true;
+       }
+
+       return false;
+}
+
+static const struct regmap_config sn65dsi83_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+
+       .max_register = 0xff,
+       .cache_type = REGCACHE_RBTREE,
+       .reg_defaults_raw = sn65dsi83_register_defaults,
+       .num_reg_defaults_raw = ARRAY_SIZE(sn65dsi83_register_defaults),
+
+       .volatile_reg = sn65dsi83_register_volatile,
+};
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration
+ */
+
+static int sn65dsi83_power_on(struct sn65dsi83 *dsi83)
+{
+       if (dsi83->powered)
+               return 0;
+
+       gpiod_set_value(dsi83->enable_gpio, 1);
+       regcache_sync(dsi83->regmap);
+
+       if (dsi83->vcc_reg) {
+               int ret = regulator_enable(dsi83->vcc_reg);
+
+               if (ret) {
+                       dev_err(&dsi83->client->dev, "Failed to enable VCC regulator\n");
+                       return ret;
+               }
+       }
+       dsi83->powered = true;
+       return 0;
+}
+
+static void sn65dsi83_power_off(struct sn65dsi83 *dsi83)
+{
+       if (!dsi83->powered)
+               return;
+
+       regcache_mark_dirty(dsi83->regmap);
+       if (dsi83->vcc_reg)
+               regulator_disable(dsi83->vcc_reg);
+       gpiod_set_value(dsi83->enable_gpio, 0);
+       dsi83->powered = false;
+}
+
+static irqreturn_t sn65dsi83_irq_handler(int irq, void *devid)
+{
+       struct sn65dsi83 *dsi83 = devid;
+       int ret;
+       unsigned int irqctrl;
+       unsigned int irqen;
+       unsigned int errstat;
+
+       ret = regmap_read(dsi83->regmap, SN65DSI83_REG_IRQCTRL, &irqctrl);
+       if (ret)
+               goto regmap_fail;
+
+       if (!irqctrl)
+               return IRQ_NONE;
+
+       ret = regmap_read(dsi83->regmap, SN65DSI83_REG_IRQEN, &irqen);
+       if (ret)
+               goto regmap_fail;
+
+       ret = regmap_read(dsi83->regmap, SN65DSI83_REG_ERRSTAT, &errstat);
+       if (ret)
+               goto regmap_fail;
+
+       if (errstat != dsi83->errstat) {
+               wake_up(&dsi83->wq);
+               dsi83->errstat = errstat;
+       }
+
+       if (!(irqen & errstat))
+               return IRQ_NONE;
+
+       ret = regmap_write(dsi83->regmap, SN65DSI83_REG_ERRSTAT,
+                       errstat & irqen);
+       if (ret)
+               goto regmap_fail;
+
+       return IRQ_HANDLED;
+
+regmap_fail:
+       dev_err(&dsi83->client->dev, "regmap_read() failed: %d\n", ret);
+       return IRQ_NONE;
+}
+
+static inline struct sn65dsi83 *connector_to_sn65dsi83(struct drm_connector *connector)
+{
+       return container_of(connector, struct sn65dsi83, connector);
+}
+
+static enum drm_connector_status
+sn65dsi83_detect(struct sn65dsi83 *dsi83,
+               struct drm_connector *connector)
+{
+       return connector_status_connected;
+}
+
+/*
+ * index into this table is value to be written to REG_LVDSCTRL
+ * for the frequency (in kHz) listed in the table
+ */
+static unsigned long lvds_clk_lookup[] = {
+       37500,
+       62500,
+       87500,
+       112500,
+       137500,
+       154000,
+};
+
+#define DSI83_WRITE(r, v) do {                                         \
+       int ret = regmap_write(dsi83->regmap, SN65DSI83_REG_##r, v);    \
+       if (ret)                                                        \
+               return ret;                                             \
+       } while (0)
+
+#define DSI83_READ(r, v) do {                                          \
+       int ret = regmap_read(dsi83->regmap, SN65DSI83_REG_##r, v);     \
+       if (ret)                                                        \
+               return ret;                                             \
+       } while (0)
+
+#define DSI83_UPDATE_BITS(r, v, m) do {                                        \
+       int ret = regmap_update_bits(dsi83->regmap, SN65DSI83_REG_##r,  \
+                                       v, m);                          \
+       if (ret)                                                        \
+               return ret;                                             \
+       } while (0)
+
+static int sn65dsi83_pll_init(struct sn65dsi83 *dsi83)
+{
+       int ret;
+       size_t i;
+       unsigned long lvds_clk = dsi83->curr_mode.clock;
+       unsigned long mipi_dsi_clk = lvds_clk * 24 / dsi83->num_dsi_lanes / 2;
+       int ref_clk_mult;
+       int dsi_clk_div;
+       int lvds_clk_src;
+
+       if (dsi83->refclk) {
+               unsigned long ref_clk = clk_get_rate(dsi83->refclk);
+
+               ref_clk_mult = lvds_clk / ref_clk;
+               dsi_clk_div = 0;
+               lvds_clk_src = 0;
+       } else {
+               ref_clk_mult = 0;
+               dsi_clk_div = DIV_ROUND_UP(mipi_dsi_clk, lvds_clk) - 1;
+               lvds_clk_src = 1;
+       }
+       DRM_DEBUG_DRIVER("lvds_clk=%lu mipi_dsi_clk=%lu lvds_clk_src=%u ref_clk_mult=%u dsi_clk_div=%u\n",
+                       lvds_clk, mipi_dsi_clk, lvds_clk_src, ref_clk_mult,
+                       dsi_clk_div);
+
+       if (mipi_dsi_clk < 40000 || mipi_dsi_clk > 500000)
+               return -EINVAL;
+       if (dsi_clk_div > 25)
+               return -EINVAL;
+       if (lvds_clk < 25000)
+               return -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(lvds_clk_lookup); i++) {
+               if (lvds_clk <= lvds_clk_lookup[i])
+                       break;
+       }
+
+       if (i < ARRAY_SIZE(lvds_clk_lookup)) {
+               u32 lvdsctrl, pll;
+               int lvds_clk_sel = i << 1;
+
+               DSI83_READ(PLLCTRL, &pll);
+               DSI83_READ(LVDSCTRL, &lvdsctrl);
+
+               if (WARN_ON((pll & PLLCTRL_PLL_EN) && ((lvdsctrl & 0xe) == lvds_clk)))
+                       return 0;
+
+               /* PLL must be disabled before changing LVDSCRTL & DSICTRL */
+               if (pll & PLLCTRL_PLL_EN)
+                       DSI83_WRITE(PLLCTRL, pll & ~PLLCTRL_PLL_EN);
+
+               DSI83_WRITE(LVDSCTRL, (lvdsctrl & ~0xe) | lvds_clk_sel | lvds_clk_src);
+               DSI83_WRITE(DSICTRL, (dsi_clk_div << 3) | (ref_clk_mult << 0));
+               DSI83_WRITE(LVDSCFG2, mipi_dsi_clk / 5000);
+               DSI83_UPDATE_BITS(PLLCTRL, 0x1, 0x1);
+
+               /* clear error status bits */
+               DSI83_WRITE(ERRSTAT, ~0);
+               ret = 0;
+       } else {
+               ret = -EINVAL;
+               dev_err(&dsi83->client->dev,
+                       "LVDS clk %lu out of range 25000000 .. 154000000\n",
+                       lvds_clk);
+       }
+       return ret;
+}
+
+static void sn65dsi83_mode_set(struct sn65dsi83 *dsi83,
+                            struct drm_display_mode *mode,
+                            struct drm_display_mode *adj_mode)
+{
+       unsigned int h_back_porch, h_front_porch, hsync_len;
+       unsigned int v_back_porch, v_front_porch, vsync_len;
+       unsigned int hdisplay, vdisplay;
+       unsigned long pclk;
+
+
+       h_back_porch = adj_mode->crtc_hsync_start - adj_mode->crtc_hdisplay;
+       v_back_porch = adj_mode->crtc_vsync_start - adj_mode->crtc_vdisplay;
+
+       h_front_porch = adj_mode->crtc_hsync_start - adj_mode->crtc_hdisplay;
+       v_front_porch = adj_mode->crtc_vsync_start - adj_mode->crtc_vdisplay;
+
+       hsync_len = adj_mode->crtc_hsync_end - adj_mode->crtc_hsync_start;
+       vsync_len = adj_mode->crtc_vsync_end - adj_mode->crtc_vsync_start;
+
+       hdisplay = adj_mode->crtc_hdisplay;
+       vdisplay = adj_mode->crtc_vdisplay;
+
+       pclk = mode->crtc_htotal * mode->crtc_vtotal * drm_mode_vrefresh(mode);
+
+       drm_mode_copy(&dsi83->curr_mode, adj_mode);
+}
+
+static int sn65dsi83_get_modes(struct drm_connector *connector)
+{
+       struct sn65dsi83 *dsi83 = connector_to_sn65dsi83(connector);
+       struct drm_display_mode *mode;
+       int num_modes = 0;
+
+       if (dsi83->panel && dsi83->panel->funcs &&
+           dsi83->panel->funcs->get_modes) {
+               struct drm_display_info *di = &connector->display_info;
+
+               num_modes = dsi83->panel->funcs->get_modes(dsi83->panel);
+               if (!dsi83->bus_format && di->num_bus_formats)
+                       dsi83->bus_format = di->bus_formats[0];
+               if (num_modes > 0)
+                       return num_modes;
+       }
+
+       if (dsi83->edid) {
+               drm_mode_connector_update_edid_property(connector,
+                                                       dsi83->edid);
+               num_modes = drm_add_edid_modes(connector, dsi83->edid);
+       }
+
+       if (dsi83->mode_valid) {
+               mode = drm_mode_create(connector->dev);
+               if (!mode)
+                       return 0;
+               drm_mode_copy(mode, &dsi83->curr_mode);
+               mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+               drm_mode_probed_add(connector, mode);
+       }
+       return num_modes;
+}
+
+static int sn65dsi83_chip_init(struct sn65dsi83 *dsi83)
+{
+
+       DSI83_UPDATE_BITS(LVDSCFG0, 0x3 << 3,
+                       (4 - dsi83->num_dsi_lanes) << 3);
+
+       if (dsi83->mode_valid) {
+               unsigned int h_back_porch, h_front_porch, hsync_len;
+               unsigned int v_back_porch, v_front_porch, vsync_len;
+               unsigned int hdisplay, vdisplay;
+               unsigned long pclk;
+               struct drm_display_mode *mode = &dsi83->curr_mode;
+               int lvds_format;
+
+               hdisplay = mode->crtc_hdisplay;
+               vdisplay = mode->crtc_vdisplay;
+
+               hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+               vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+
+               h_front_porch = mode->crtc_hsync_start - hdisplay;
+               v_front_porch = mode->crtc_vsync_start - vdisplay;
+
+               h_back_porch = mode->htotal - hsync_len - h_front_porch - hdisplay;
+               v_back_porch = mode->vtotal - vsync_len - v_front_porch - vdisplay;
+
+               pclk = mode->crtc_htotal * mode->crtc_vtotal * drm_mode_vrefresh(mode);
+
+               DSI83_WRITE(HSYNC_WIDTH_MSB, hsync_len >> 8);
+               DSI83_WRITE(HSYNC_WIDTH_LSB, hsync_len & 0xff);
+
+               DSI83_WRITE(VSYNC_WIDTH_MSB, vsync_len >> 8);
+               DSI83_WRITE(VSYNC_WIDTH_LSB, vsync_len & 0xff);
+
+               DSI83_WRITE(LINE_LENGTH_MSB, hdisplay >> 8);
+               DSI83_WRITE(LINE_LENGTH_LSB, hdisplay & 0xff);
+
+               DSI83_WRITE(VERT_SIZE_MSB, vdisplay >> 8);
+               DSI83_WRITE(VERT_SIZE_LSB, vdisplay & 0xff);
+
+               DSI83_WRITE(H_BACK_PORCH, h_back_porch);
+               DSI83_WRITE(H_FRONT_PORCH, h_front_porch);
+
+               DSI83_WRITE(V_BACK_PORCH, v_back_porch);
+               DSI83_WRITE(V_FRONT_PORCH, v_front_porch);
+
+               DSI83_WRITE(SYNC_DELAY_LSB, SYNC_DELAY % 255);
+               DSI83_WRITE(SYNC_DELAY_MSB, SYNC_DELAY / 255);
+
+               switch (dsi83->bus_format) {
+               case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+                       lvds_format = BIT(1);
+                       break;
+               case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+                       lvds_format = BIT(3) | BIT(1);
+                       break;
+               case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+                       lvds_format = BIT(3);
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               DSI83_UPDATE_BITS(LVDSCFG4, BIT(3) | BIT(1), lvds_format);
+               if (dsi83->client->irq)
+                       DSI83_WRITE(IRQCTRL, IRQCTRL_IRQ_EN);
+       }
+       return 0;
+}
+
+/* Connector funcs */
+static int sn65dsi83_connector_get_modes(struct drm_connector *connector)
+{
+       return sn65dsi83_get_modes(connector);
+}
+
+static enum drm_mode_status
+sn65dsi83_mode_valid(struct sn65dsi83 *dsi83, const struct drm_display_mode *mode)
+{
+       if (mode->clock > 154000)
+               return MODE_CLOCK_HIGH;
+       if (mode->clock < 25000)
+               return MODE_CLOCK_LOW;
+       if (mode->hdisplay < 0 || mode->hdisplay >= (1 << 12))
+               return MODE_H_ILLEGAL;
+       if (mode->vdisplay < 0 || mode->vdisplay >= (1 << 12))
+               return MODE_V_ILLEGAL;
+
+       return MODE_OK;
+}
+
+static struct drm_encoder *
+sn65dsi83_connector_best_encoder(struct drm_connector *connector)
+{
+       struct sn65dsi83 *dsi83 = connector_to_sn65dsi83(connector);
+       return dsi83->bridge.encoder;
+}
+
+static enum drm_mode_status
+sn65dsi83_connector_mode_valid(struct drm_connector *connector,
+                            struct drm_display_mode *mode)
+{
+       struct sn65dsi83 *dsi83 = connector_to_sn65dsi83(connector);
+
+       DRM_DEBUG_DRIVER("Validating mode '%s'\n",
+               mode->name);
+       return sn65dsi83_mode_valid(dsi83, mode);
+}
+
+static struct drm_connector_helper_funcs sn65dsi83_connector_helper_funcs = {
+       .get_modes = sn65dsi83_connector_get_modes,
+       .best_encoder = sn65dsi83_connector_best_encoder,
+       .mode_valid = sn65dsi83_connector_mode_valid,
+};
+
+static enum drm_connector_status
+sn65dsi83_connector_detect(struct drm_connector *connector, bool force)
+{
+       struct sn65dsi83 *dsi83 = connector_to_sn65dsi83(connector);
+
+       return sn65dsi83_detect(dsi83, connector);
+}
+
+static struct drm_connector_funcs sn65dsi83_connector_funcs = {
+       .dpms = drm_atomic_helper_connector_dpms,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .detect = sn65dsi83_connector_detect,
+       .destroy = drm_connector_cleanup,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int sn65dsi83_attach_dsi(struct sn65dsi83 *dsi83)
+{
+       struct device *dev = &dsi83->client->dev;
+       struct mipi_dsi_host *host;
+       struct mipi_dsi_device *dsi;
+       int ret;
+
+       host = of_find_mipi_dsi_host_by_node(dsi83->host_node);
+       if (!host) {
+               dev_err(dev, "failed to find dsi host\n");
+               return -EPROBE_DEFER;
+       }
+
+       dsi = mipi_dsi_new_dummy(host, 0);
+       if (IS_ERR(dsi)) {
+               dev_err(dev, "failed to create dummy dsi device\n");
+               ret = PTR_ERR(dsi);
+               goto err_dsi_device;
+       }
+
+       dsi83->dsi = dsi;
+
+       dsi->lanes = dsi83->num_dsi_lanes;
+       dsi->format = MIPI_DSI_FMT_RGB888;
+       dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+                         MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
+
+       ret = mipi_dsi_attach(dsi);
+       if (ret < 0) {
+               dev_err(dev, "failed to attach dsi to host\n");
+               goto err_dsi_attach;
+       }
+
+       return 0;
+
+err_dsi_attach:
+       mipi_dsi_unregister_device(dsi);
+err_dsi_device:
+       return ret;
+}
+
+static void sn65dsi83_detach_dsi(struct sn65dsi83 *dsi83)
+{
+       mipi_dsi_detach(dsi83->dsi);
+       mipi_dsi_unregister_device(dsi83->dsi);
+       of_node_put(dsi83->host_node);
+}
+
+enum {
+       LVDS_BIT_MAP_SPWG,
+       LVDS_BIT_MAP_JEIDA
+};
+
+static const struct imx_ldb_bit_mapping {
+       u32 bus_format;
+       u32 datawidth;
+       const char *const mapping;
+} imx_ldb_bit_mappings[] = {
+       { MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,  18, "spwg" },
+       { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,  24, "spwg" },
+       { MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
+};
+
+static int sn65dsi83_of_get_bus_format(struct device *dev, struct device_node *np)
+{
+       const char *bm = NULL;
+       u32 datawidth = 0;
+       int ret, i;
+       struct device_node *disp_timings;
+
+       disp_timings = of_get_child_by_name(np, "display-timings");
+       if (disp_timings) {
+               struct device_node *native_mode;
+
+               native_mode = of_parse_phandle(disp_timings, "native-mode", 0);
+               if (!native_mode)
+                       native_mode = of_get_next_child(disp_timings, NULL);
+               if (of_property_read_string(native_mode, "lvds-data-mapping",
+                                               &bm) == 0)
+                       DRM_DEBUG_DRIVER("Using data-mapping '%s' from node: '%s'\n",
+                               bm, of_node_full_name(native_mode));
+               if (of_property_read_u32(native_mode, "lvds-data-width",
+                                               &datawidth) == 0)
+                       DRM_DEBUG_DRIVER("Using data-width %u from node: '%s'\n",
+                               datawidth, of_node_full_name(native_mode));
+
+       }
+       if (bm == NULL) {
+               ret = of_property_read_string(np, "lvds-data-mapping", &bm);
+               if (ret < 0)
+                       dev_err(dev, "lvds-data-mapping missing in DT\n");
+               else
+                       DRM_DEBUG_DRIVER("Using data-mapping '%s' from node: '%s'\n",
+                               bm, of_node_full_name(np));
+       }
+
+       if (datawidth == 0) {
+               ret = of_property_read_u32(np, "lvds-data-width", &datawidth);
+               if (ret < 0)
+                       dev_err(dev, "lvds-data-width missing in DT\n");
+               else
+                       DRM_DEBUG_DRIVER("Using data-width %u from node: '%s'\n",
+                               datawidth, of_node_full_name(np));
+       }
+       if (datawidth == 0 || bm == NULL)
+               return -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) {
+               if (strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) == 0 &&
+                   datawidth == imx_ldb_bit_mappings[i].datawidth)
+                       return imx_ldb_bit_mappings[i].bus_format;
+       }
+
+       dev_err(dev, "invalid data mapping: %u-bit \"%s\"\n", datawidth, bm);
+
+       return -ENOENT;
+}
+
+/*
+  mdss                                 mdp
+  port[0]               dsi0_in   <->  port[0] mdp5_intf1_out
+  port[1] [data-lanes]  dsi0_out  <-+
+                        |
+  dsi83                 |
+  port[0] * dsi83_in  <-+
+  port[1]   dsi83_out <->  lvds-out
+*/
+
+int sn65dsi83_parse_dt(struct device_node *np, struct sn65dsi83 *dsi83)
+{
+       int ret;
+       u32 num_lanes;
+       struct device *dev = &dsi83->client->dev;
+       struct device_node *endpoint;
+       struct device_node *dsi0;
+
+       endpoint = of_graph_get_next_endpoint(np, NULL);
+       if (!endpoint)
+               return -ENODEV;
+
+       dsi83->host_node = of_graph_get_remote_port_parent(endpoint);
+       if (!dsi83->host_node) {
+               of_node_put(endpoint);
+               return -ENODEV;
+       }
+
+       dsi0 = of_graph_get_remote_port(endpoint);
+       if (!dsi0) {
+               dev_err(dev, "dsi0-out endpoint not found in %s\n",
+                       of_node_full_name(endpoint));
+               return -EINVAL;
+       }
+
+       endpoint = of_find_node_by_name(dsi0, "endpoint");
+       if (!endpoint) {
+               dev_err(dev, "No 'endpoint' node found in '%s'\n",
+                       of_node_full_name(dsi0));
+               return -EINVAL;
+       }
+
+       if (!of_find_property(endpoint, "data-lanes", &num_lanes)) {
+               dev_err(dev, "No 'data-lanes' property found in %s\n",
+                       of_node_full_name(endpoint));
+               return -EINVAL;
+       }
+       num_lanes /= sizeof(u32);
+       if (num_lanes < 1 || num_lanes > 4) {
+               dev_err(dev, "Invalid number of data lanes %u from property '%s/data-lanes'\n",
+                       num_lanes, of_node_full_name(endpoint));
+               return -EINVAL;
+       }
+       ret = of_get_drm_display_mode(np, &dsi83->curr_mode, OF_USE_NATIVE_MODE);
+       dsi83->mode_valid = ret == 0;
+
+       dsi83->bus_format = sn65dsi83_of_get_bus_format(dev, np);
+       if (dsi83->bus_format == -EINVAL) {
+               /*
+                * If no bus format was specified in the device tree,
+                * we can still get it from the connected panel later.
+                */
+               if (dsi83->panel && dsi83->panel->funcs &&
+                       dsi83->panel->funcs->get_modes)
+                       dsi83->bus_format = 0;
+       }
+       if (dsi83->bus_format < 0) {
+               dev_err(dev, "could not determine data mapping: %d\n",
+                       dsi83->bus_format);
+               return dsi83->bus_format;
+       }
+       dsi83->num_dsi_lanes = num_lanes;
+
+       of_node_put(endpoint);
+       return 0;
+}
+
+static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge)
+{
+       return container_of(bridge, struct sn65dsi83, bridge);
+}
+
+static void sn65dsi83_bridge_pre_enable(struct drm_bridge *bridge)
+{
+       struct sn65dsi83 *dsi83 = bridge_to_sn65dsi83(bridge);
+
+       sn65dsi83_power_on(dsi83);
+}
+
+static void sn65dsi83_bridge_enable(struct drm_bridge *bridge)
+{
+       struct sn65dsi83 *dsi83 = bridge_to_sn65dsi83(bridge);
+       int ret;
+
+       gpiod_set_value(dsi83->enable_gpio, 1);
+
+       ret = sn65dsi83_chip_init(dsi83);
+       if (ret)
+               dev_err(&dsi83->client->dev,
+                       "Failed to initialize chip: %d\n",
+                       ret);
+       ret = sn65dsi83_pll_init(dsi83);
+       if (ret)
+               dev_err(&dsi83->client->dev, "Failed to initialize PLL: %d\n",
+                       ret);
+}
+
+static void sn65dsi83_bridge_post_disable(struct drm_bridge *bridge)
+{
+       struct sn65dsi83 *dsi83 = bridge_to_sn65dsi83(bridge);
+
+       gpiod_set_value(dsi83->enable_gpio, 0);
+       sn65dsi83_power_off(dsi83);
+}
+
+static void sn65dsi83_bridge_disable(struct drm_bridge *bridge)
+{
+}
+
+static void sn65dsi83_bridge_mode_set(struct drm_bridge *bridge,
+                                   struct drm_display_mode *mode,
+                                   struct drm_display_mode *adj_mode)
+{
+       struct sn65dsi83 *dsi83 = bridge_to_sn65dsi83(bridge);
+
+       sn65dsi83_mode_set(dsi83, mode, adj_mode);
+}
+
+static int sn65dsi83_bridge_attach(struct drm_bridge *bridge)
+{
+       struct sn65dsi83 *dsi83 = bridge_to_sn65dsi83(bridge);
+       int ret;
+
+       if (!bridge->encoder) {
+               DRM_ERROR("Parent encoder object not found");
+               return -ENODEV;
+       }
+
+       ret = drm_connector_init(bridge->dev, &dsi83->connector,
+                                &sn65dsi83_connector_funcs,
+                                DRM_MODE_CONNECTOR_LVDS);
+       if (ret) {
+               DRM_ERROR("Failed to initialize connector with drm\n");
+               return ret;
+       }
+       drm_connector_helper_add(&dsi83->connector,
+                                &sn65dsi83_connector_helper_funcs);
+       drm_mode_connector_attach_encoder(&dsi83->connector, bridge->encoder);
+       drm_connector_register(&dsi83->connector);
+
+       if (dsi83->panel)
+               drm_panel_attach(dsi83->panel, &dsi83->connector);
+
+       ret = sn65dsi83_attach_dsi(dsi83);
+       if (ret)
+               return ret;
+
+       drm_helper_hpd_irq_event(dsi83->connector.dev);
+       return 0;
+}
+
+static struct drm_bridge_funcs sn65dsi83_bridge_funcs = {
+       .pre_enable = sn65dsi83_bridge_pre_enable,
+       .enable = sn65dsi83_bridge_enable,
+       .disable = sn65dsi83_bridge_disable,
+       .post_disable = sn65dsi83_bridge_post_disable,
+       .mode_set = sn65dsi83_bridge_mode_set,
+       .attach = sn65dsi83_bridge_attach,
+};
+
+/* -----------------------------------------------------------------------------
+ * Encoder operations
+ */
+static int sn65dsi83_encoder_get_modes(struct drm_encoder *encoder,
+                            struct drm_connector *connector)
+{
+       return sn65dsi83_get_modes(connector);
+}
+
+static void sn65dsi83_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+       struct sn65dsi83 *dsi83 = encoder_to_sn65dsi83(encoder);
+
+       if (mode == DRM_MODE_DPMS_ON)
+               sn65dsi83_power_on(dsi83);
+       else
+               sn65dsi83_power_off(dsi83);
+}
+
+static enum drm_connector_status
+sn65dsi83_encoder_detect(struct drm_encoder *encoder,
+                      struct drm_connector *connector)
+{
+       struct sn65dsi83 *dsi83 = encoder_to_sn65dsi83(encoder);
+
+       return sn65dsi83_detect(dsi83, connector);
+}
+
+static int sn65dsi83_encoder_mode_valid(struct drm_encoder *encoder,
+                                     struct drm_display_mode *mode)
+{
+       struct sn65dsi83 *dsi83 = encoder_to_sn65dsi83(encoder);
+
+       return sn65dsi83_mode_valid(dsi83, mode);
+}
+
+static void sn65dsi83_encoder_mode_set(struct drm_encoder *encoder,
+                                    struct drm_display_mode *mode,
+                                    struct drm_display_mode *adj_mode)
+{
+       struct sn65dsi83 *dsi83 = encoder_to_sn65dsi83(encoder);
+
+       sn65dsi83_mode_set(dsi83, mode, adj_mode);
+}
+
+static struct drm_encoder_slave_funcs sn65dsi83_encoder_funcs = {
+       .dpms = sn65dsi83_encoder_dpms,
+       .mode_valid = sn65dsi83_encoder_mode_valid,
+       .mode_set = sn65dsi83_encoder_mode_set,
+       .detect = sn65dsi83_encoder_detect,
+       .get_modes = sn65dsi83_encoder_get_modes,
+};
+
+static int sn65dsi83_encoder_init(struct i2c_client *i2c, struct drm_device *dev,
+                               struct drm_encoder_slave *encoder)
+{
+
+       struct sn65dsi83 *dsi83 = i2c_get_clientdata(i2c);
+
+       encoder->slave_priv = dsi83;
+       encoder->slave_funcs = &sn65dsi83_encoder_funcs;
+
+       dsi83->encoder = &encoder->base;
+
+       return 0;
+}
+
+static int sn65dsi83_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct sn65dsi83 *dsi83;
+       struct device *dev = &client->dev;
+       int ret;
+       u8 chiprev[9];
+
+       if (!dev->of_node)
+               return -EINVAL;
+
+       dsi83 = devm_kzalloc(dev, sizeof(*dsi83), GFP_KERNEL);
+       if (!dsi83)
+               return -ENOMEM;
+
+       dsi83->status = connector_status_disconnected;
+       dsi83->client = client;
+
+       ret = sn65dsi83_parse_dt(dev->of_node, dsi83);
+       if (ret)
+               return ret;
+
+       dsi83->refclk = devm_clk_get(dev, NULL);
+       if (IS_ERR(dsi83->refclk)) {
+               if (PTR_ERR(dsi83->refclk) == -EPROBE_DEFER)
+                       return PTR_ERR(dsi83->refclk);
+               dsi83->refclk = NULL;
+       }
+
+       dsi83->vcc_reg = devm_regulator_get_optional(dev, "vcc");
+       if (IS_ERR(dsi83->vcc_reg)) {
+               ret = PTR_ERR(dsi83->vcc_reg);
+               dev_err(dev, "Failed to get VCC regulator: %d\n", ret);
+               return ret;
+       }
+       if (dsi83->vcc_reg) {
+               ret = regulator_enable(dsi83->vcc_reg);
+               if (ret) {
+                       dev_err(dev, "Failed to enable VCC regulator: %d\n", ret);
+                       return ret;
+               }
+       }
+
+       dsi83->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+                                               GPIOD_OUT_LOW);
+       if (IS_ERR(dsi83->enable_gpio))
+               return PTR_ERR(dsi83->enable_gpio);
+
+       dsi83->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config);
+       if (IS_ERR(dsi83->regmap))
+               return PTR_ERR(dsi83->regmap);
+
+       sn65dsi83_power_on(dsi83);
+       ret = regmap_bulk_read(dsi83->regmap, SN65DSI83_REG_CHIPREV(0),
+                       &chiprev, sizeof(chiprev));
+       if (ret) {
+               dev_err(dev, "Failed to read CHIPREV register\n");
+               return ret;
+       }
+       print_hex_dump_bytes("CHIPREV: ", DUMP_PREFIX_NONE, chiprev,
+                       sizeof(chiprev));
+       dev_info(dev, "Rev. %u\n", chiprev[8]);
+
+       if (client->irq) {
+               init_waitqueue_head(&dsi83->wq);
+
+               ret = devm_request_threaded_irq(dev, client->irq, NULL,
+                                               sn65dsi83_irq_handler,
+                                               IRQF_ONESHOT, dev_name(dev),
+                                               dsi83);
+               if (ret)
+                       return ret;
+       }
+
+       sn65dsi83_power_off(dsi83);
+
+       i2c_set_clientdata(client, dsi83);
+
+       dsi83->bridge.funcs = &sn65dsi83_bridge_funcs;
+       dsi83->bridge.of_node = dev->of_node;
+
+       ret = drm_bridge_add(&dsi83->bridge);
+       if (ret)
+               dev_err(dev, "failed to add sn65dsi83 bridge\n");
+
+       return ret;
+}
+
+static int sn65dsi83_remove(struct i2c_client *client)
+{
+       struct sn65dsi83 *dsi83 = i2c_get_clientdata(client);
+
+       if (dsi83->dsi)
+               sn65dsi83_detach_dsi(dsi83);
+
+       drm_bridge_remove(&dsi83->bridge);
+       return 0;
+}
+
+static const struct i2c_device_id sn65dsi83_i2c_ids[] = {
+       { "sn65dsi83", },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, sn65dsi83_i2c_ids);
+
+static const struct of_device_id sn65dsi83_of_ids[] = {
+       { .compatible = "ti,sn65dsi83", },
+       { }
+};
+MODULE_DEVICE_TABLE(of, sn65dsi83_of_ids);
+
+static struct drm_i2c_encoder_driver sn65dsi83_driver = {
+       .i2c_driver = {
+               .driver = {
+                       .name = "sn65dsi83",
+                       .of_match_table = sn65dsi83_of_ids,
+               },
+               .id_table = sn65dsi83_i2c_ids,
+               .probe = sn65dsi83_probe,
+               .remove = sn65dsi83_remove,
+       },
+       .encoder_init = sn65dsi83_encoder_init,
+};
+
+static int __init sn65dsi83_init(void)
+{
+       return drm_i2c_encoder_register(THIS_MODULE, &sn65dsi83_driver);
+}
+module_init(sn65dsi83_init);
+
+static void __exit sn65dsi83_exit(void)
+{
+       drm_i2c_encoder_unregister(&sn65dsi83_driver);
+}
+module_exit(sn65dsi83_exit);
+
+MODULE_AUTHOR("Lothar Waßmann <LW@KARO-electronics.de>");
+MODULE_DESCRIPTION("SN65DSI83 DSI to LVDS converter driver");
+MODULE_LICENSE("GPL");