summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/display/bridge/lontium,lt8912.txt41
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.txt1
-rw-r--r--drivers/gpu/drm/bridge/Kconfig8
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/lt8912.c499
5 files changed, 550 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt8912.txt b/Documentation/devicetree/bindings/display/bridge/lontium,lt8912.txt
new file mode 100644
index 000000000000..0d0378febadb
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt8912.txt
@@ -0,0 +1,41 @@
+Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge bindings
+
+Required properties:
+ - compatible: "lontium,lt8912"
+ - reg: virtual channel id
+ - reset-gpios: a GPIO spec for the reset pin
+ - i2c-bus: phandle of an I2C controller used for register access
+ - display-timings : Refer to binding doc display-timing.txt for details
+
+Example:
+
+&dsi {
+ status = "okay";
+
+ lt8912@0 {
+ compatible = "lontium,lt8912";
+ reg = <0>;
+ reset-gpios = <&gpio0 RK_PA2 GPIO_ACTIVE_LOW>;
+ i2c-bus = <&i2c1>;
+
+ display-timings {
+ native-mode = <&timing0>;
+
+ timing0: timing0 {
+ clock-frequency = <74250000>;
+ hactive = <1280>;
+ vactive = <720>;
+ hfront-porch = <110>;
+ hsync-len = <40>;
+ hback-porch = <220>;
+ vfront-porch = <5>;
+ vsync-len = <5>;
+ vback-porch = <20>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ de-active = <0>;
+ pixelclk-active = <0>;
+ };
+ };
+ };
+};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index d8eac2dcfaa7..5af2daa96e14 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -190,6 +190,7 @@ linaro Linaro Limited
linksys Belkin International, Inc. (Linksys)
linux Linux-specific binding
lltc Linear Technology Corporation
+lontium Lontium Semiconductor Corporation
lsi LSI Corp. (LSI Logic)
lwn Liebherr-Werk Nenzing GmbH
macnica Macnica Americas
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 593e9a4e9ed6..46a03b8c3f4e 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -51,6 +51,14 @@ config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW
to DP++. This is used with the i.MX6 imx-ldb
driver. You are likely to say N here.
+config DRM_LONTIUM_LT8912
+ tristate "Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge chip driver.
+
config DRM_NWL_DSI
tristate
select DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d684dce13256..8119857c7ed4 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
+obj-$(CONFIG_DRM_LONTIUM_LT8912) += lt8912.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
diff --git a/drivers/gpu/drm/bridge/lt8912.c b/drivers/gpu/drm/bridge/lt8912.c
new file mode 100644
index 000000000000..1790a71f2767
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lt8912.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Rockchip Electronics Co. Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <video/of_display_timing.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_of.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_mipi_dsi.h>
+
+struct lt8912 {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct drm_display_mode mode;
+ struct device *dev;
+ struct mipi_dsi_device *dsi;
+ struct regmap *regmap[3];
+ struct gpio_desc *reset_n;
+};
+
+static inline struct lt8912 *bridge_to_lt8912(struct drm_bridge *b)
+{
+ return container_of(b, struct lt8912, bridge);
+}
+
+static inline struct lt8912 *connector_to_lt8912(struct drm_connector *c)
+{
+ return container_of(c, struct lt8912, connector);
+}
+
+/* LT8912 MIPI to HDMI & LVDS REG setting - 20180115.txt */
+static void lt8912_init(struct lt8912 *lt8912)
+{
+ u8 lanes = lt8912->dsi->lanes;
+ const struct drm_display_mode *mode = &lt8912->mode;
+ u32 hactive, hfp, hsync, hbp, vfp, vsync, vbp, htotal, vtotal;
+ unsigned int version[2];
+
+ /* TODO: lvds output init */
+
+ hactive = mode->hdisplay;
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsync = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vsync = mode->vsync_end - mode->vsync_start;
+ vbp = mode->vtotal - mode->vsync_end;
+ htotal = mode->htotal;
+ vtotal = mode->vtotal;
+
+ regmap_read(lt8912->regmap[0], 0x00, &version[0]);
+ regmap_read(lt8912->regmap[0], 0x01, &version[1]);
+
+ dev_info(lt8912->dev, "LT8912 ID: %02x, %02x\n",
+ version[0], version[1]);
+
+ /* DigitalClockEn */
+ regmap_write(lt8912->regmap[0], 0x08, 0xff);
+ regmap_write(lt8912->regmap[0], 0x09, 0x81);
+ regmap_write(lt8912->regmap[0], 0x0a, 0xff);
+ regmap_write(lt8912->regmap[0], 0x0b, 0x64);
+ regmap_write(lt8912->regmap[0], 0x0c, 0xff);
+
+ regmap_write(lt8912->regmap[0], 0x44, 0x31);
+ regmap_write(lt8912->regmap[0], 0x51, 0x1f);
+
+ /* TxAnalog */
+ regmap_write(lt8912->regmap[0], 0x31, 0xa1);
+ regmap_write(lt8912->regmap[0], 0x32, 0xa1);
+ regmap_write(lt8912->regmap[0], 0x33, 0x03);
+ regmap_write(lt8912->regmap[0], 0x37, 0x00);
+ regmap_write(lt8912->regmap[0], 0x38, 0x22);
+ regmap_write(lt8912->regmap[0], 0x60, 0x82);
+
+ /* CbusAnalog */
+ regmap_write(lt8912->regmap[0], 0x39, 0x45);
+ regmap_write(lt8912->regmap[0], 0x3b, 0x00);
+
+ /* HDMIPllAnalog */
+ regmap_write(lt8912->regmap[0], 0x44, 0x31);
+ regmap_write(lt8912->regmap[0], 0x55, 0x44);
+ regmap_write(lt8912->regmap[0], 0x57, 0x01);
+ regmap_write(lt8912->regmap[0], 0x5a, 0x02);
+
+ /* MipiBasicSet */
+ regmap_write(lt8912->regmap[1], 0x10, 0x01);
+ regmap_write(lt8912->regmap[1], 0x11, 0x08);
+ regmap_write(lt8912->regmap[1], 0x12, 0x04);
+ regmap_write(lt8912->regmap[1], 0x13, lanes % 4);
+ regmap_write(lt8912->regmap[1], 0x14, 0x00);
+
+ regmap_write(lt8912->regmap[1], 0x15, 0x00);
+ regmap_write(lt8912->regmap[1], 0x1a, 0x03);
+ regmap_write(lt8912->regmap[1], 0x1b, 0x03);
+
+ /* MIPIDig */
+ regmap_write(lt8912->regmap[1], 0x18, hsync);
+ regmap_write(lt8912->regmap[1], 0x19, vsync);
+ regmap_write(lt8912->regmap[1], 0x1c, hactive);
+ regmap_write(lt8912->regmap[1], 0x1d, hactive >> 8);
+
+ regmap_write(lt8912->regmap[1], 0x1e, 0x67);
+ regmap_write(lt8912->regmap[1], 0x2f, 0x0c);
+
+ regmap_write(lt8912->regmap[1], 0x34, htotal);
+ regmap_write(lt8912->regmap[1], 0x35, htotal >> 8);
+ regmap_write(lt8912->regmap[1], 0x36, vtotal);
+ regmap_write(lt8912->regmap[1], 0x37, vtotal >> 8);
+ regmap_write(lt8912->regmap[1], 0x38, vbp);
+ regmap_write(lt8912->regmap[1], 0x39, vbp >> 8);
+ regmap_write(lt8912->regmap[1], 0x3a, vfp);
+ regmap_write(lt8912->regmap[1], 0x3b, vfp >> 8);
+ regmap_write(lt8912->regmap[1], 0x3c, hbp);
+ regmap_write(lt8912->regmap[1], 0x3d, hbp >> 8);
+ regmap_write(lt8912->regmap[1], 0x3e, hfp);
+ regmap_write(lt8912->regmap[1], 0x3f, hfp >> 8);
+
+ /* DDSConfig */
+ regmap_write(lt8912->regmap[1], 0x4e, 0x52);
+ regmap_write(lt8912->regmap[1], 0x4f, 0xde);
+ regmap_write(lt8912->regmap[1], 0x50, 0xc0);
+ regmap_write(lt8912->regmap[1], 0x51, 0x80);
+ regmap_write(lt8912->regmap[1], 0x51, 0x00);
+
+ regmap_write(lt8912->regmap[1], 0x1f, 0x5e);
+ regmap_write(lt8912->regmap[1], 0x20, 0x01);
+ regmap_write(lt8912->regmap[1], 0x21, 0x2c);
+ regmap_write(lt8912->regmap[1], 0x22, 0x01);
+ regmap_write(lt8912->regmap[1], 0x23, 0xfa);
+ regmap_write(lt8912->regmap[1], 0x24, 0x00);
+ regmap_write(lt8912->regmap[1], 0x25, 0xc8);
+ regmap_write(lt8912->regmap[1], 0x26, 0x00);
+ regmap_write(lt8912->regmap[1], 0x27, 0x5e);
+ regmap_write(lt8912->regmap[1], 0x28, 0x01);
+ regmap_write(lt8912->regmap[1], 0x29, 0x2c);
+ regmap_write(lt8912->regmap[1], 0x2a, 0x01);
+ regmap_write(lt8912->regmap[1], 0x2b, 0xfa);
+ regmap_write(lt8912->regmap[1], 0x2c, 0x00);
+ regmap_write(lt8912->regmap[1], 0x2d, 0xc8);
+ regmap_write(lt8912->regmap[1], 0x2e, 0x00);
+
+ regmap_write(lt8912->regmap[0], 0x03, 0x7f);
+ usleep_range(10000, 20000);
+ regmap_write(lt8912->regmap[0], 0x03, 0xff);
+
+ regmap_write(lt8912->regmap[1], 0x42, 0x64);
+ regmap_write(lt8912->regmap[1], 0x43, 0x00);
+ regmap_write(lt8912->regmap[1], 0x44, 0x04);
+ regmap_write(lt8912->regmap[1], 0x45, 0x00);
+ regmap_write(lt8912->regmap[1], 0x46, 0x59);
+ regmap_write(lt8912->regmap[1], 0x47, 0x00);
+ regmap_write(lt8912->regmap[1], 0x48, 0xf2);
+ regmap_write(lt8912->regmap[1], 0x49, 0x06);
+ regmap_write(lt8912->regmap[1], 0x4a, 0x00);
+ regmap_write(lt8912->regmap[1], 0x4b, 0x72);
+ regmap_write(lt8912->regmap[1], 0x4c, 0x45);
+ regmap_write(lt8912->regmap[1], 0x4d, 0x00);
+ regmap_write(lt8912->regmap[1], 0x52, 0x08);
+ regmap_write(lt8912->regmap[1], 0x53, 0x00);
+ regmap_write(lt8912->regmap[1], 0x54, 0xb2);
+ regmap_write(lt8912->regmap[1], 0x55, 0x00);
+ regmap_write(lt8912->regmap[1], 0x56, 0xe4);
+ regmap_write(lt8912->regmap[1], 0x57, 0x0d);
+ regmap_write(lt8912->regmap[1], 0x58, 0x00);
+ regmap_write(lt8912->regmap[1], 0x59, 0xe4);
+ regmap_write(lt8912->regmap[1], 0x5a, 0x8a);
+ regmap_write(lt8912->regmap[1], 0x5b, 0x00);
+ regmap_write(lt8912->regmap[1], 0x5c, 0x34);
+ regmap_write(lt8912->regmap[1], 0x1e, 0x4f);
+ regmap_write(lt8912->regmap[1], 0x51, 0x00);
+
+ regmap_write(lt8912->regmap[0], 0xb2, 0x01);
+
+ /* AudioIIsEn */
+ regmap_write(lt8912->regmap[2], 0x06, 0x08);
+ regmap_write(lt8912->regmap[2], 0x07, 0xf0);
+
+ regmap_write(lt8912->regmap[2], 0x34, 0xd2);
+
+ regmap_write(lt8912->regmap[2], 0x3c, 0x41);
+
+ /* MIPIRxLogicRes */
+ regmap_write(lt8912->regmap[0], 0x03, 0x7f);
+ usleep_range(10000, 20000);
+ regmap_write(lt8912->regmap[0], 0x03, 0xff);
+
+ regmap_write(lt8912->regmap[1], 0x51, 0x80);
+ usleep_range(10000, 20000);
+ regmap_write(lt8912->regmap[1], 0x51, 0x00);
+}
+
+static void lt8912_exit(struct lt8912 *lt8912)
+{
+ regmap_write(lt8912->regmap[0], 0x08, 0x00);
+ regmap_write(lt8912->regmap[0], 0x09, 0x81);
+ regmap_write(lt8912->regmap[0], 0x0a, 0x00);
+ regmap_write(lt8912->regmap[0], 0x0b, 0x20);
+ regmap_write(lt8912->regmap[0], 0x0c, 0x00);
+
+ regmap_write(lt8912->regmap[0], 0x54, 0x1d);
+ regmap_write(lt8912->regmap[0], 0x51, 0x15);
+
+ regmap_write(lt8912->regmap[0], 0x44, 0x31);
+ regmap_write(lt8912->regmap[0], 0x41, 0xbd);
+ regmap_write(lt8912->regmap[0], 0x5c, 0x11);
+
+ regmap_write(lt8912->regmap[0], 0x30, 0x08);
+ regmap_write(lt8912->regmap[0], 0x31, 0x00);
+ regmap_write(lt8912->regmap[0], 0x32, 0x00);
+ regmap_write(lt8912->regmap[0], 0x33, 0x00);
+ regmap_write(lt8912->regmap[0], 0x34, 0x00);
+ regmap_write(lt8912->regmap[0], 0x35, 0x00);
+ regmap_write(lt8912->regmap[0], 0x36, 0x00);
+ regmap_write(lt8912->regmap[0], 0x37, 0x00);
+ regmap_write(lt8912->regmap[0], 0x38, 0x00);
+}
+
+static void lt8912_power_on(struct lt8912 *lt8912)
+{
+ gpiod_direction_output(lt8912->reset_n, 1);
+ msleep(120);
+ gpiod_direction_output(lt8912->reset_n, 0);
+}
+
+static void lt8912_power_off(struct lt8912 *lt8912)
+{
+ gpiod_direction_output(lt8912->reset_n, 1);
+}
+
+static enum drm_connector_status
+lt8912_connector_detect(struct drm_connector *connector, bool force)
+{
+ /* TODO: HPD handing (reg[0xc1] - bit[7]) */
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs lt8912_connector_funcs = {
+ .detect = lt8912_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .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 struct drm_encoder *
+lt8912_connector_best_encoder(struct drm_connector *connector)
+{
+ struct lt8912 *lt8912 = connector_to_lt8912(connector);
+
+ return lt8912->bridge.encoder;
+}
+
+static int lt8912_connector_get_modes(struct drm_connector *connector)
+{
+ struct lt8912 *lt8912 = connector_to_lt8912(connector);
+ struct drm_display_mode *mode;
+ int ret;
+
+ /* TODO: EDID handing */
+
+ mode = drm_mode_create(connector->dev);
+ if (!mode)
+ return -EINVAL;
+
+ ret = of_get_drm_display_mode(lt8912->dev->of_node, mode,
+ OF_USE_NATIVE_MODE);
+ if (ret) {
+ dev_err(lt8912->dev, "failed to get display timings\n");
+ drm_mode_destroy(connector->dev, mode);
+ return 0;
+ }
+
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+
+ return 1;
+}
+
+static const struct drm_connector_helper_funcs lt8912_connector_helper_funcs = {
+ .get_modes = lt8912_connector_get_modes,
+ .best_encoder = lt8912_connector_best_encoder,
+};
+
+static void lt8912_bridge_post_disable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+
+ lt8912_power_off(lt8912);
+}
+
+static void lt8912_bridge_disable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+
+ lt8912_exit(lt8912);
+}
+
+static void lt8912_bridge_enable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+
+ lt8912_init(lt8912);
+}
+
+static void lt8912_bridge_pre_enable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+
+ lt8912_power_on(lt8912);
+}
+
+static void lt8912_bridge_mode_set(struct drm_bridge *bridge,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+
+ drm_mode_copy(&lt8912->mode, adj);
+}
+
+static int lt8912_bridge_attach(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt8912 = bridge_to_lt8912(bridge);
+ struct drm_connector *connector = &lt8912->connector;
+ int ret;
+
+ ret = drm_connector_init(bridge->dev, connector,
+ &lt8912_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret) {
+ dev_err(lt8912->dev, "failed to initialize connector\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &lt8912_connector_helper_funcs);
+ drm_mode_connector_attach_encoder(connector, bridge->encoder);
+
+ return 0;
+}
+
+static const struct drm_bridge_funcs lt8912_bridge_funcs = {
+ .attach = lt8912_bridge_attach,
+ .mode_set = lt8912_bridge_mode_set,
+ .pre_enable = lt8912_bridge_pre_enable,
+ .enable = lt8912_bridge_enable,
+ .disable = lt8912_bridge_disable,
+ .post_disable = lt8912_bridge_post_disable,
+};
+
+static const struct regmap_config lt8912_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int lt8912_i2c_init(struct lt8912 *lt8912,
+ struct i2c_adapter *adapter)
+{
+ struct i2c_board_info info[] = {
+ { I2C_BOARD_INFO("lt8912p0", 0x48), },
+ { I2C_BOARD_INFO("lt8912p1", 0x49), },
+ { I2C_BOARD_INFO("lt8912p2", 0x4a), }
+ };
+ struct regmap *regmap;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(info); i++) {
+ struct i2c_client *client;
+
+ client = i2c_new_device(adapter, &info[i]);
+ if (!client)
+ return -ENODEV;
+
+ regmap = devm_regmap_init_i2c(client, &lt8912_regmap_config);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(lt8912->dev,
+ "Failed to initialize regmap: %d\n", ret);
+ return ret;
+ }
+
+ lt8912->regmap[i] = regmap;
+ }
+
+ return 0;
+}
+
+static int lt8912_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct lt8912 *lt8912;
+ struct device_node *node;
+ struct i2c_adapter *adapter;
+ int ret;
+
+ lt8912 = devm_kzalloc(dev, sizeof(*lt8912), GFP_KERNEL);
+ if (!lt8912)
+ return -ENOMEM;
+
+ lt8912->dev = dev;
+ lt8912->dsi = dsi;
+ mipi_dsi_set_drvdata(dsi, lt8912);
+
+ lt8912->reset_n = devm_gpiod_get(dev, "reset", GPIOD_ASIS);
+ if (IS_ERR(lt8912->reset_n)) {
+ ret = PTR_ERR(lt8912->reset_n);
+ dev_err(dev, "failed to request reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ node = of_parse_phandle(dev->of_node, "i2c-bus", 0);
+ if (!node) {
+ dev_err(dev, "No i2c-bus found\n");
+ return -ENODEV;
+ }
+
+ adapter = of_find_i2c_adapter_by_node(node);
+ of_node_put(node);
+ if (!adapter) {
+ dev_err(dev, "No i2c adapter found\n");
+ return -EPROBE_DEFER;
+ }
+
+ ret = lt8912_i2c_init(lt8912, adapter);
+ if (ret)
+ return ret;
+
+ /* TODO: interrupt handing */
+
+ lt8912->bridge.funcs = &lt8912_bridge_funcs;
+ lt8912->bridge.of_node = dev->of_node;
+ ret = drm_bridge_add(&lt8912->bridge);
+ if (ret) {
+ dev_err(dev, "failed to add bridge: %d\n", ret);
+ return ret;
+ }
+
+ dsi->lanes = 4;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret) {
+ drm_bridge_remove(&lt8912->bridge);
+ dev_err(dev, "failed to attach dsi to host: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt8912_remove(struct mipi_dsi_device *dsi)
+{
+ struct lt8912 *lt8912 = mipi_dsi_get_drvdata(dsi);
+
+ mipi_dsi_detach(dsi);
+ drm_bridge_remove(&lt8912->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id lt8912_of_match[] = {
+ { .compatible = "lontium,lt8912" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lt8912_of_match);
+
+static struct mipi_dsi_driver lt8912_driver = {
+ .driver = {
+ .name = "lt8912",
+ .of_match_table = lt8912_of_match,
+ },
+ .probe = lt8912_probe,
+ .remove = lt8912_remove,
+};
+module_mipi_dsi_driver(lt8912_driver);
+
+MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
+MODULE_DESCRIPTION("Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge");
+MODULE_LICENSE("GPL v2");