diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
new file mode 100644
index 0000000..a2a95f5
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define SN_BRIDGE_REVISION_ID 0x2
+
+/* Link Training specific registers */
+#define SN_DEVICE_REV_REG			0x08
+#define SN_REFCLK_FREQ_REG			0x0A
+#define SN_DSI_LANES_REG			0x10
+#define SN_DSIA_CLK_FREQ_REG			0x12
+#define SN_ENH_FRAME_REG			0x5A
+#define SN_SSC_CONFIG_REG			0x93
+#define SN_DATARATE_CONFIG_REG			0x94
+#define SN_PLL_ENABLE_REG			0x0D
+#define SN_SCRAMBLE_CONFIG_REG			0x95
+#define SN_AUX_WDATA0_REG			0x64
+#define SN_AUX_ADDR_19_16_REG			0x74
+#define SN_AUX_ADDR_15_8_REG			0x75
+#define SN_AUX_ADDR_7_0_REG			0x76
+#define SN_AUX_LENGTH_REG			0x77
+#define SN_AUX_CMD_REG				0x78
+#define SN_ML_TX_MODE_REG			0x96
+/* video config specific registers */
+#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
+#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
+#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
+#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
+#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
+#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
+#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
+#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
+#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
+#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
+#define SN_DATA_FORMAT_REG			0x5B
+
+#define MIN_DSI_CLK_FREQ_MHZ	40
+
+#define DPPLL_CLK_SRC_REFCLK	0
+#define DPPLL_CLK_SRC_DSICLK	1
+
+enum ti_sn_bridge_ref_clks {
+	SN_REF_CLK_12_MHZ = 0,
+	SN_REF_CLK_19_2_MHZ,
+	SN_REF_CLK_26_MHZ,
+	SN_REF_CLK_27_MHZ,
+	SN_REF_CLK_38_4_MHZ,
+	SN_REF_CLK_MAX,
+};
+
+struct ti_sn_bridge {
+	struct device *dev;
+	struct regmap *regmap;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct device_node *host_node;
+	struct mipi_dsi_device *dsi;
+	/* handle to the connected eDP panel */
+	struct drm_panel *panel;
+	/* list of gpios needed to enable the bridge functionality */
+	struct gpio_descs *enable_gpios;
+	int *value_array;
+	int intp_irq;
+	unsigned int num_supplies;
+	struct regulator_bulk_data *supplies;
+	struct i2c_client *i2c_client;
+	struct i2c_adapter *ddc;
+	u32 num_modes;
+	struct drm_display_mode curr_mode;
+};
+
+static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
+	{ .range_min = 0, .range_max = 0xff },
+};
+
+static const struct regmap_access_table ti_sn_bridge_volatile_table = {
+	.yes_ranges = ti_sn_bridge_volatile_ranges,
+	.n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges),
+};
+
+static const struct regmap_config ti_sn_bridge_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_table = &ti_sn_bridge_volatile_table,
+	.cache_type = REGCACHE_NONE,
+};
+
+static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
+{
+	int value, i = 0;
+
+	value = on ? 1 : 0;
+
+	if (pdata->enable_gpios->ndescs && pdata->value_array) {
+		for (i = 0; i < pdata->enable_gpios->ndescs; i++)
+			pdata->value_array[i] = value;
+		gpiod_set_array_value(pdata->enable_gpios->ndescs,
+			pdata->enable_gpios->desc, pdata->value_array);
+	}
+
+	DRM_DEBUG("config to: %d\n", value);
+}
+
+static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
+{
+	static int ref_count;
+
+	if (enable)
+		ref_count++;
+	else
+		ref_count--;
+
+	if (enable && (ref_count == 1)) {
+		ti_sn_bridge_gpio_configure(pdata, true);
+
+		if (regulator_bulk_enable(pdata->num_supplies,
+						pdata->supplies)) {
+			DRM_ERROR("bridge regulator enable failed\n");
+			return;
+		}
+	} else if (!enable && !ref_count) {
+		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
+
+		ti_sn_bridge_gpio_configure(pdata, false);
+	} else {
+		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
+							enable, ref_count);
+	}
+}
+
+/* Connector funcs */
+static struct ti_sn_bridge *
+connector_to_ti_sn_bridge(struct drm_connector *connector)
+{
+	return container_of(connector, struct ti_sn_bridge, connector);
+}
+
+static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+	struct drm_panel *panel = pdata->panel;
+	struct edid *edid;
+
+	if (panel) {
+		DRM_DEBUG("get mode from connected drm_panel\n");
+		pdata->num_modes = drm_panel_get_modes(panel);
+	} else {
+		/* get from EDID */
+		if (!pdata->ddc)
+			return 0;
+		ti_sn_bridge_power_ctrl(pdata, true);
+		edid = drm_get_edid(connector, pdata->ddc);
+		if (edid) {
+			drm_mode_connector_update_edid_property(connector,
+								edid);
+			pdata->num_modes = drm_add_edid_modes(connector, edid);
+			drm_edid_to_eld(connector, edid);
+			kfree(edid);
+		} else {
+			DRM_DEBUG("failed to get edid\n");
+		}
+		ti_sn_bridge_power_ctrl(pdata, false);
+	}
+
+	return pdata->num_modes;
+}
+
+static enum drm_mode_status
+ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	/* maximum supported resolution is 4K at 60 fps */
+	if (mode->clock > 594000)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
+	.get_modes = ti_sn_bridge_connector_get_modes,
+	.mode_valid = ti_sn_bridge_connector_mode_valid,
+};
+
+static enum drm_connector_status
+ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+
+	/**
+	 * TODO: Currently if drm_panel is present, then always
+	 * return the status as connected. Need to add support to detect
+	 * device state for no panel(hot pluggable) scenarios.
+	 */
+	if (pdata->panel)
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ti_sn_bridge_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 struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ti_sn_bridge, bridge);
+}
+
+static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
+{
+	unsigned int rev = 0;
+	int ret = 0;
+
+	ret = regmap_read(pdata->regmap, SN_DEVICE_REV_REG, &rev);
+	if (ret)
+		goto exit;
+
+	if (rev == SN_BRIDGE_REVISION_ID) {
+		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
+	} else {
+		DRM_ERROR("ti_sn_bridge revision id: 0x%x mismatch\n", rev);
+		ret = -EINVAL;
+	}
+
+exit:
+	return ret;
+}
+
+static const char * const ti_sn_bridge_supply_names[] = {
+	"vccio",
+	"vcca",
+};
+
+static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
+{
+	unsigned int i;
+
+	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
+
+	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
+				     sizeof(*pdata->supplies), GFP_KERNEL);
+	if (!pdata->supplies)
+		return -ENOMEM;
+
+	for (i = 0; i < pdata->num_supplies; i++)
+		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
+
+	return devm_regulator_bulk_get(pdata->dev,
+				pdata->num_supplies, pdata->supplies);
+}
+
+static int ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
+{
+	struct device_node *panel_node, *port, *endpoint;
+
+	pdata->panel = NULL;
+	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
+	if (port) {
+		endpoint = of_get_child_by_name(port, "endpoint");
+		of_node_put(port);
+		if (!endpoint) {
+			dev_err(pdata->dev, "no output endpoint found\n");
+			return -EINVAL;
+		}
+
+		panel_node = of_graph_get_remote_port_parent(endpoint);
+		of_node_put(endpoint);
+		if (!panel_node) {
+			dev_err(pdata->dev, "no output node found\n");
+			return -EINVAL;
+		}
+
+		pdata->panel = of_drm_find_panel(panel_node);
+		of_node_put(panel_node);
+		if (!pdata->panel) {
+			dev_err(pdata->dev, "no panel node found\n");
+			return -EINVAL;
+		}
+	}
+
+	drm_panel_attach(pdata->panel, &pdata->connector);
+
+	return 0;
+}
+
+static int ti_sn_bridge_attach(struct drm_bridge *bridge)
+{
+	struct mipi_dsi_host *host;
+	struct mipi_dsi_device *dsi;
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	int ret;
+	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
+						   .channel = 0,
+						   .node = NULL,
+						 };
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found\n");
+		ret = -ENODEV;
+		goto error;
+	}
+
+	/* HPD not supported */
+	pdata->connector.polled = 0;
+
+	ret = drm_connector_init(bridge->dev, &pdata->connector,
+				 &ti_sn_bridge_connector_funcs,
+				 DRM_MODE_CONNECTOR_eDP);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		goto error;
+	}
+
+	drm_connector_helper_add(&pdata->connector,
+				 &ti_sn_bridge_connector_helper_funcs);
+	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
+
+	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
+	if (!host) {
+		DRM_ERROR("failed to find dsi host\n");
+		ret = -ENODEV;
+		goto error;
+	}
+
+	dsi = mipi_dsi_device_register_full(host, &info);
+	if (IS_ERR(dsi)) {
+		DRM_ERROR("failed to create dsi device\n");
+		ret = PTR_ERR(dsi);
+		goto error;
+	}
+
+	/* TODO: setting to 4 lanes always for now */
+	dsi->lanes = 4;
+	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) {
+		DRM_ERROR("failed to attach dsi to host\n");
+		goto err_dsi_attach;
+	}
+
+	pdata->dsi = dsi;
+
+	DRM_DEBUG("bridge attached\n");
+	/* attach panel to bridge */
+	ti_sn_bridge_attach_panel(pdata);
+
+	return 0;
+
+err_dsi_attach:
+	mipi_dsi_device_unregister(dsi);
+error:
+	return ret;
+}
+
+static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adj_mode)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+
+	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
+		adj_mode->hdisplay, adj_mode->vdisplay,
+		adj_mode->vrefresh, adj_mode->clock);
+
+	drm_mode_copy(&pdata->curr_mode, adj_mode);
+}
+
+static void ti_sn_bridge_disable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+
+	if (panel) {
+		drm_panel_disable(panel);
+		drm_panel_unprepare(panel);
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, false);
+}
+
+static void ti_sn_bridge_enable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+	struct drm_display_mode *mode = &pdata->curr_mode;
+	unsigned int bit_rate_mhz, clk_freq_mhz;
+	unsigned int val = 0;
+
+	if (!pdata->num_modes)
+		return;
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+
+	if (panel)
+		drm_panel_prepare(panel);
+
+	/* CLK_SRC config */
+	regmap_write(pdata->regmap, SN_REFCLK_FREQ_REG,
+			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
+
+	/* DSI_A lane config */
+	regmap_read(pdata->regmap, SN_DSI_LANES_REG, &val);
+	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
+	regmap_write(pdata->regmap, SN_DSI_LANES_REG, val);
+
+	/* DP lane config */
+	regmap_read(pdata->regmap, SN_SSC_CONFIG_REG, &val);
+	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
+	regmap_write(pdata->regmap, SN_SSC_CONFIG_REG, val);
+
+	/* set dsi clk frequency value */
+	bit_rate_mhz = (mode->clock / 1000) *
+			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
+	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
+	/* for each increment in val, frequency increases by 5MHz */
+	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
+		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
+	regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
+
+	regmap_write(pdata->regmap, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */
+
+	regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
+	usleep_range(10000, 10050);
+
+	/* DPCD Register 0x0010A in Sink */
+	regmap_write(pdata->regmap, SN_AUX_WDATA0_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, 0x00);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, 0x0A);
+	regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_CMD_REG, 0x81);
+	usleep_range(10000, 10050);
+
+	/* Semi auto link training mode */
+	regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
+	usleep_range(20000, 20050);
+
+	/* config video parameters */
+	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
+			mode->hdisplay & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
+			(mode->hdisplay >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
+			mode->vdisplay & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
+			(mode->vdisplay >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->hsync_end - mode->hsync_start) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->hsync_end - mode->hsync_start) >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->vsync_end - mode->vsync_start) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->vsync_end - mode->vsync_start) >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
+			(mode->htotal - mode->hsync_end) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
+			(mode->vtotal - mode->vsync_end) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
+			(mode->hsync_start - mode->hdisplay) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
+			(mode->vsync_start - mode->vdisplay) & 0xFF);
+	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
+		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x00);
+	else
+		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x01);
+	usleep_range(10000, 10050);
+
+	/* enable video stream */
+	regmap_read(pdata->regmap, SN_ENH_FRAME_REG, &val);
+	val |= BIT(3);
+	regmap_write(pdata->regmap, SN_ENH_FRAME_REG, val);
+
+	if (panel)
+		drm_panel_enable(panel);
+}
+
+static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
+	.attach = ti_sn_bridge_attach,
+	.enable = ti_sn_bridge_enable,
+	.disable = ti_sn_bridge_disable,
+	.mode_set = ti_sn_bridge_mode_set,
+};
+
+static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
+{
+	int ret = 0;
+
+	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
+						"enable", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->enable_gpios)) {
+		DRM_ERROR("failed to get enable gpio from DT\n");
+		ret = PTR_ERR(pdata->enable_gpios);
+		goto exit;
+	}
+	/* initialize the value array */
+	pdata->value_array = kmalloc(pdata->enable_gpios->ndescs * sizeof(int),
+								GFP_KERNEL);
+	if (!pdata->value_array) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	memset(pdata->value_array, 0,
+			pdata->enable_gpios->ndescs * sizeof(int));
+
+exit:
+	return ret;
+}
+
+static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
+{
+	struct device_node *np = pdata->dev->of_node;
+	struct device_node *end_node;
+
+	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
+	if (!end_node) {
+		DRM_ERROR("remote endpoint not found\n");
+		return -ENODEV;
+	}
+
+	pdata->host_node = of_graph_get_remote_port_parent(end_node);
+	of_node_put(end_node);
+	if (!pdata->host_node) {
+		DRM_ERROR("remote node not found\n");
+		return -ENODEV;
+	}
+	of_node_put(pdata->host_node);
+
+	return 0;
+}
+
+static int ti_sn_bridge_probe(struct i2c_client *client,
+	 const struct i2c_device_id *id)
+{
+	struct ti_sn_bridge *pdata;
+	struct device_node *ddc_node;
+	int ret = 0;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		DRM_ERROR("device doesn't support I2C\n");
+		return -ENODEV;
+	}
+
+	pdata = devm_kzalloc(&client->dev,
+		sizeof(struct ti_sn_bridge), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->dev = &client->dev;
+	pdata->i2c_client = client;
+	pdata->intp_irq = client->irq;
+	DRM_DEBUG("I2C address is %x\n", client->addr);
+
+	pdata->regmap = devm_regmap_init_i2c(client,
+				&ti_sn_bridge_regmap_config);
+	if (IS_ERR(pdata->regmap))
+		return PTR_ERR(pdata->regmap);
+
+	ret = ti_sn_bridge_parse_dsi_host(pdata);
+	if (ret)
+		goto err_dt_parse;
+
+	ret = ti_sn_bridge_parse_gpios(pdata);
+	if (ret)
+		goto err_dt_parse;
+
+	ret = ti_sn_bridge_parse_regulators(pdata);
+	if (ret) {
+		DRM_ERROR("failed to parse regulators\n");
+		goto err_reg_parse;
+	}
+
+	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!pdata->ddc) {
+			dev_dbg(pdata->dev, "failed to read ddc node\n");
+			return -EPROBE_DEFER;
+		}
+	} else {
+		dev_dbg(pdata->dev, "no ddc property found\n");
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+	ret = ti_sn_bridge_read_device_rev(pdata);
+	if (ret)
+		goto err_config;
+	ti_sn_bridge_power_ctrl(pdata, false);
+
+	i2c_set_clientdata(client, pdata);
+	dev_set_drvdata(&client->dev, pdata);
+
+	pdata->bridge.funcs = &ti_sn_bridge_funcs;
+	pdata->bridge.of_node = client->dev.of_node;
+
+	drm_bridge_add(&pdata->bridge);
+
+	return ret;
+
+err_config:
+	ti_sn_bridge_power_ctrl(pdata, false);
+err_reg_parse:
+	kfree(pdata->value_array);
+err_dt_parse:
+	devm_kfree(&client->dev, pdata);
+	return ret;
+}
+
+static int ti_sn_bridge_remove(struct i2c_client *client)
+{
+	int ret = -EINVAL;
+	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
+
+	if (!pdata)
+		goto end;
+
+	mipi_dsi_detach(pdata->dsi);
+	mipi_dsi_device_unregister(pdata->dsi);
+
+	ti_sn_bridge_power_ctrl(pdata, false);
+	kfree(pdata->value_array);
+
+	drm_bridge_remove(&pdata->bridge);
+	i2c_put_adapter(pdata->ddc);
+
+	devm_kfree(&client->dev, pdata);
+
+end:
+	return ret;
+}
+
+static struct i2c_device_id ti_sn_bridge_id[] = {
+	{ "ti,sn65dsi86", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
+
+static const struct of_device_id ti_sn_bridge_match_table[] = {
+	{.compatible = "ti,sn65dsi86"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
+
+static struct i2c_driver ti_sn_bridge_driver = {
+	.driver = {
+		.name = "ti_sn65dsi86",
+		.owner = THIS_MODULE,
+		.of_match_table = ti_sn_bridge_match_table,
+	},
+	.probe = ti_sn_bridge_probe,
+	.remove = ti_sn_bridge_remove,
+	.id_table = ti_sn_bridge_id,
+};
+
+module_i2c_driver(ti_sn_bridge_driver);
+MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
+MODULE_LICENSE("GPL");
