diff mbox

[v4,06/13] drm/bridge: Add Synopsys DesignWare MIPI DSI host controller driver

Message ID 1448007339-10966-7-git-send-email-zyw@rock-chips.com (mailing list archive)
State New, archived
Headers show

Commit Message

Chris Zhong Nov. 20, 2015, 8:15 a.m. UTC
add Synopsys DesignWare MIPI DSI host controller driver support.

Signed-off-by: Chris Zhong <zyw@rock-chips.com>
---

Changes in v4:
eliminate some warnning

Changes in v3: None
Changes in v2: None

 drivers/gpu/drm/bridge/Kconfig       |   11 +
 drivers/gpu/drm/bridge/Makefile      |    1 +
 drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1056 ++++++++++++++++++++++++++++++++++
 include/drm/bridge/dw_mipi_dsi.h     |   27 +
 4 files changed, 1095 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c
 create mode 100644 include/drm/bridge/dw_mipi_dsi.h

Comments

Thierry Reding Nov. 20, 2015, 4:07 p.m. UTC | #1
On Fri, Nov 20, 2015 at 04:15:32PM +0800, Chris Zhong wrote:
> add Synopsys DesignWare MIPI DSI host controller driver support.
> 
> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
> ---
> 
> Changes in v4:
> eliminate some warnning
> 
> Changes in v3: None
> Changes in v2: None
> 
>  drivers/gpu/drm/bridge/Kconfig       |   11 +
>  drivers/gpu/drm/bridge/Makefile      |    1 +
>  drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1056 ++++++++++++++++++++++++++++++++++
>  include/drm/bridge/dw_mipi_dsi.h     |   27 +
>  4 files changed, 1095 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c
>  create mode 100644 include/drm/bridge/dw_mipi_dsi.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 6dddd39..c0900e0 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -22,6 +22,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>  
> +config DRM_DW_MIPI_DSI
> +	tristate "Synopsys DesignWare MIPI DSI host controller bridge"
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	select DRM_MIPI_DSI
> +	select DRM_PANEL
> +	help
> +	  Choose this if you want to use the Synopsys DesignWare MIPI DSI host
> +	  controller bridge. If M is selected, the module will be
> +	  called dw_mipi_dsi. DRM_MIPI_DSI support is required for this driver
> +	  to work.

Just drop the last sentence, it doesn't add value since that's already
expressed in the "select DRM_MIPI_DSI" statement.

Also, please use hyphens instead of underscore to separate parts in
module names.

> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index d4e28be..d908c4b 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -2,5 +2,6 @@ ccflags-y := -Iinclude/drm
>  
>  obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw_hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw_mipi_dsi.o

Like I said above, this should be dw-mipi-dsi.o. I know dw_hdmi uses the
underscores, but that's something I plan on fixing.

>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
> diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
> new file mode 100644
> index 0000000..23b612d
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
> @@ -0,0 +1,1056 @@
> +/*
> + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd

Does this need to be updated? We're pretty far into 2015 by now.

> +struct dw_mipi_dsi {
> +	struct mipi_dsi_host dsi_host;
> +	struct drm_connector connector;
> +	struct drm_encoder *encoder;

struct drm_bridge already has a pointer to an encoder, can't you reuse
that instead?

> +	struct drm_bridge *bridge;

Typically you'd embed the bridge into the driver structure.

> +	struct drm_panel *panel;
> +	struct device *dev;
> +
> +	void __iomem *base;
> +
> +	struct clk *pllref_clk;
> +	struct clk *cfg_clk;
> +	struct clk *pclk;
> +
> +	unsigned int lane_mbps; /* per lane */
> +	u32 channel;
> +	u32 lanes;
> +	u32 format;
> +	u16 input_div;
> +	u16 feedback_div;
> +	struct drm_display_mode *mode;
> +
> +	const struct dw_mipi_dsi_plat_data *pdata;
> +
> +	bool enabled;
> +};
> +
> +enum {
> +	STATUS_TO_CLEAR,
> +	STATUS_TO_SET,
> +};

This seems to be used only as replacement for false/true, so you should
just use false/true instead and remove these.

> +/* The table is based on 27MHz DPHY pll reference clock. */
> +static const struct dphy_pll_testdin_map dptdin_map[] = {
> +	{90, 0x00}, {100, 0x10}, {110, 0x20}, {130, 0x01},
> +	{140, 0x11}, {150, 0x21}, {170, 0x02}, {180, 0x12},
> +	{200, 0x22}, {220, 0x03}, {240, 0x13}, {250, 0x23},
> +	{270, 0x04}, {300, 0x14}, {330, 0x05}, {360, 0x15},
> +	{400, 0x25}, {450, 0x06}, {500, 0x16}, {550, 0x07},
> +	{600, 0x17}, {650, 0x08}, {700, 0x18}, {750, 0x09},
> +	{800, 0x19}, {850, 0x29}, {900, 0x39}, {950, 0x0a},
> +	{1000, 0x1a}, {1050, 0x2a}, {1100, 0x3a}, {1150, 0x0b},
> +	{1200, 0x1b}, {1250, 0x2b}, {1300, 0x3b}, {1350, 0x0c},
> +	{1400, 0x1c}, {1450, 0x2c}, {1500, 0x3c}
> +};

Might be worth reformatting this to be more table-like.

> +static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
> +{
> +	writel(val, dsi->base + reg);
> +}
> +
> +static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
> +{
> +	return readl(dsi->base + reg);
> +}
> +
> +static inline void dsi_modify(struct dw_mipi_dsi *dsi, u32 reg,
> +			      u32 mask, u32 val)
> +{
> +	u32 v;
> +
> +	v = readl(dsi->base + reg);
> +	v &= ~mask;
> +	v |= val;
> +	writel(v, dsi->base + reg);
> +}

Perhaps reuse dsi_read() and dsi_write() here?

> +static int check_status(struct dw_mipi_dsi *dsi, u32 reg, u32 status,
> +			unsigned int timeout, bool to_set)
> +{
> +	unsigned long expire;
> +	bool out;
> +	u32 val;
> +
> +	expire = jiffies + msecs_to_jiffies(timeout);
> +	for (;;) {
> +		val = dsi_read(dsi, reg);
> +		out = to_set ? ((val & status) == status) : !(val & status);
> +		if (out)
> +			break;
> +
> +		if (time_after(jiffies, expire))
> +			return -ETIMEDOUT;
> +
> +		cpu_relax();
> +	}
> +
> +	return 0;
> +}

Perhaps use the helpers in linux/iopoll.h?

> +/*
> + * The controller should generate 2 frames before
> + * preparing the peripheral.
> + */
> +static void dw_mipi_dsi_wait_for_two_frames(struct dw_mipi_dsi *dsi)
> +{
> +	unsigned long expire;
> +	int refresh, two_frames;
> +
> +	refresh = drm_mode_vrefresh(dsi->mode);
> +	two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
> +
> +	expire = jiffies + msecs_to_jiffies(two_frames);
> +	while (time_before(jiffies, expire))
> +		cpu_relax();
> +}

That's kind of rude. You know already how long it will take for two
frames to be sent, why not just sleep for that time? usleep_range() or
in this case most likely msleep() would be more civil options.

> +static void dw_mipi_dsi_phy_test(struct dw_mipi_dsi *dsi, u8 test_code,
> +				 u8 test_data)
> +{
> +	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
> +
> +	dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDOUT(0) |
> +					  PHY_TESTDIN(test_code));
> +
> +	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
> +	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
> +
> +	dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN | PHY_TESTDOUT(0) |
> +					  PHY_TESTDIN(test_data));
> +
> +	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
> +	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
> +}

This looks like it's actually programming something, rather than
testing. Can you perhaps add a comment to this explaining what exactly
it's doing?

> +static int dw_mipi_dsi_phy_init(struct dw_mipi_dsi *dsi)
> +{
> +	int ret, testdin, vco;
> +
> +	vco = (dsi->lane_mbps < 200) ? 0 : (dsi->lane_mbps + 100) / 200;
> +
> +	testdin = max_mbps_to_testdin(dsi->lane_mbps);
> +	if (testdin < 0) {
> +		dev_err(dsi->dev,
> +			"failed to get testdin for %dmbps lane clock\n",
> +			dsi->lane_mbps);
> +		return testdin;
> +	}
> +
> +	dsi_write(dsi, DSI_PWR_UP, POWERUP);
> +
> +	dw_mipi_dsi_phy_test(dsi, 0x10, 0x80 | (vco & 0x7) << 3 | 0x3);
> +	dw_mipi_dsi_phy_test(dsi, 0x11, 0x8);
> +	dw_mipi_dsi_phy_test(dsi, 0x12, 0xc0);
> +
> +	dw_mipi_dsi_phy_test(dsi, 0x44, testdin << 1);
> +
> +	dw_mipi_dsi_phy_test(dsi, 0x17, dsi->input_div - 1);
> +	dw_mipi_dsi_phy_test(dsi, 0x18, (dsi->feedback_div - 1) & 0x1f);
> +	dw_mipi_dsi_phy_test(dsi, 0x18, (dsi->feedback_div - 1) >> 5 | 0x80);
> +	dw_mipi_dsi_phy_test(dsi, 0x19, 0x30);
> +
> +	dw_mipi_dsi_phy_test(dsi, 0x20, 0x4d);
> +	dw_mipi_dsi_phy_test(dsi, 0x21, 0x3d);
> +	dw_mipi_dsi_phy_test(dsi, 0x21, 0xdf);
> +	dw_mipi_dsi_phy_test(dsi, 0x22, 0x7);
> +	dw_mipi_dsi_phy_test(dsi, 0x22, 0x87);
> +	dw_mipi_dsi_phy_test(dsi, 0x70, 0x80 | 0xf);
> +	dw_mipi_dsi_phy_test(dsi, 0x71, 0x80 | 0x55);
> +	dw_mipi_dsi_phy_test(dsi, 0x72, 0x40 | 0xa);

Can we have symbolic names for these values?

> +	dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK
> +		  | PHY_UNRSTZ | PHY_UNSHUTDOWNZ);

I think it's more conventional to put the | on the first line. I also
think it'd be more readable to align PHY_UNRSTZ | PHY_UNSHUTDOWNZ with
the other values that form this parameter, like so:

	dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
				     PHY_UNRSTZ | PHY_UNSHUTDOWNZ);

> +static int dw_mipi_dsi_get_lane_bps(struct dw_mipi_dsi *dsi)
> +{
> +	int bpp, i;

i should be unsigned int.

> +	unsigned int max_mbps = 0, target_mbps = 1000;
> +	unsigned long mpclk, pllref, tmp;
> +	int m = 1, n = 1, pre;

These can be unsigned int as well.

> +
> +	for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) {
> +		if (max_mbps < dptdin_map[i].max_mbps)
> +			max_mbps = dptdin_map[i].max_mbps;
> +	}

Looks to me like this will always be 1500. Why go through the trouble of
looking up the value if you know that the table is sorted by increasing
max_mbps? Hard-coding to 1500 isn't very nice either, but you could do
something like:

	max_mbps = dptdin_map[ARRAY_SIZE(dptdin_map) - 1].max_mbps;

> +static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
> +				   struct mipi_dsi_device *device)
> +{
> +	struct dw_mipi_dsi *dsi = host_to_dsi(host);
> +
> +	if (device->lanes > dsi->pdata->max_data_lanes) {
> +		dev_err(dsi->dev, "the number of data lanes(%d) is too many\n",

Use %u for unsigned integers.

> +				device->lanes);
> +		return -EINVAL;
> +	}
> +
> +	if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
> +	    !(device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) {
> +		dev_err(dsi->dev, "device mode is unsupported\n");
> +		return -EINVAL;
> +	}
> +
> +	dsi->lanes = device->lanes;
> +	dsi->channel = device->channel;
> +	dsi->format = device->format;
> +	dsi->panel = of_drm_find_panel(device->dev.of_node);

You might want to check this for validity?

> +	drm_panel_attach(dsi->panel, &dsi->connector);

You should check for errors here.

> +static int dw_mipi_dsi_dcs_short_write(struct dw_mipi_dsi *dsi,
> +				       const struct mipi_dsi_msg *msg)
> +{
> +	const u16 *tx_buf = msg->tx_buf;
> +	u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type);
> +
> +	if (msg->tx_len > 2) {
> +		dev_err(dsi->dev, "too long tx buf length %d for short write\n",
> +			(int)msg->tx_len);

No need to cast here. Simply use %zu as the format specifier.

> +		return -EINVAL;
> +	}
> +
> +	return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val);
> +}
> +
> +static int dw_mipi_dsi_dcs_long_write(struct dw_mipi_dsi *dsi,
> +				      const struct mipi_dsi_msg *msg)
> +{
> +	const u32 *tx_buf = msg->tx_buf;
> +	int len = msg->tx_len, pld_data_bytes = sizeof(*tx_buf), ret;
> +	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
> +	u32 remainder = 0;
> +
> +	if (msg->tx_len < 3) {
> +		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
> +			(int)msg->tx_len);

Same here.

> +static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge)
> +{
> +	struct dw_mipi_dsi *dsi = bridge->driver_private;
> +
> +	if (dsi->enabled)
> +		return;
> +
> +	if (!IS_ERR(dsi->cfg_clk))
> +		clk_prepare_enable(dsi->cfg_clk);
> +
> +	clk_prepare_enable(dsi->pclk);

Please always error-check clk_prepare() and clk_enable() (or the
combination clk_prepare_enable()). They can fail.

> +	dw_mipi_dsi_phy_init(dsi);
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
> +	dw_mipi_dsi_wait_for_two_frames(dsi);
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
> +	drm_panel_prepare(dsi->panel);
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
> +	clk_disable_unprepare(dsi->pclk);
> +
> +	if (!IS_ERR(dsi->cfg_clk))
> +		clk_disable_unprepare(dsi->cfg_clk);
> +
> +	drm_panel_enable(dsi->panel);
> +
> +	dsi->enabled = true;
> +}
> +
> +static void dw_mipi_dsi_bridge_disable(struct drm_bridge *bridge)
> +{
> +	struct dw_mipi_dsi *dsi = bridge->driver_private;
> +	unsigned long expire;
> +
> +	if (!dsi->enabled)
> +		return;
> +
> +	drm_panel_disable(dsi->panel);
> +
> +	if (!IS_ERR(dsi->cfg_clk))
> +		clk_prepare_enable(dsi->cfg_clk);
> +
> +	clk_prepare_enable(dsi->pclk);
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
> +	drm_panel_unprepare(dsi->panel);
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
> +
> +	/*
> +	 * This is necessary to make sure the peripheral
> +	 * will be driven normally when the display is
> +	 * enabled again later.
> +	 */

You can use longer lines for comments. No need to split it across three
lines if you can make it fit on two.

> +	expire = jiffies + msecs_to_jiffies(120);
> +	while (time_before(jiffies, expire))
> +		cpu_relax();

Again, cpu_relax() isn't really relaxing the CPU to the extent that it
might lead you to think. If you know you want to sleep for 120 ms, just
do msleep(120).

> +
> +	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
> +	dw_mipi_dsi_disable(dsi);
> +	clk_disable_unprepare(dsi->pclk);
> +
> +	if (!IS_ERR(dsi->cfg_clk))
> +		clk_disable_unprepare(dsi->cfg_clk);
> +
> +	dsi->enabled = false;
> +}
> +
> +static void dw_mipi_dsi_bridge_nope(struct drm_bridge *bridge)

Hehe, perhaps dw_mipi_dsi_bridge_nop()? Or simply leave the function
pointers NULL, but I guess you had to add this because the DRM bridge
infrastructure doesn't allow these to be optional. We might want to
change that.

> +{
> +	/* do nothing */
> +}
> +
> +static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
> +{
> +	dsi_write(dsi, DSI_PWR_UP, RESET);
> +	dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
> +		  | PHY_RSTZ | PHY_SHUTDOWNZ);
> +	dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVIDSION(10) |
> +		  TX_ESC_CLK_DIVIDSION(7));
> +	dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS);
> +}
> +
> +static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
> +				   struct drm_display_mode *mode)
> +{
> +	u32 val = 0, calor = 0;

"color"?

> +static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge,
> +					struct drm_display_mode *mode,
> +					struct drm_display_mode *adjusted_mode)
> +{
> +	struct dw_mipi_dsi *dsi = bridge->driver_private;
> +	int ret;
> +
> +	dsi->mode = adjusted_mode;
> +
> +	ret = dw_mipi_dsi_get_lane_bps(dsi);
> +	if (ret < 0)
> +		return;
> +
> +	if (!IS_ERR(dsi->cfg_clk))
> +		clk_prepare_enable(dsi->cfg_clk);
> +
> +	clk_prepare_enable(dsi->pclk);

Again, check errors from clk_prepare_enable(), ...

> +	dw_mipi_dsi_init(dsi);
> +	dw_mipi_dsi_dpi_config(dsi, mode);
> +	dw_mipi_dsi_packet_handler_config(dsi);
> +	dw_mipi_dsi_video_mode_config(dsi);
> +	dw_mipi_dsi_video_packet_config(dsi, mode);
> +	dw_mipi_dsi_command_mode_config(dsi);
> +	dw_mipi_dsi_line_timer_config(dsi);
> +	dw_mipi_dsi_vertical_timing_config(dsi);
> +	dw_mipi_dsi_dphy_timing_config(dsi);
> +	dw_mipi_dsi_dphy_interface_config(dsi);
> +	dw_mipi_dsi_clear_err(dsi);
> +	dw_mipi_dsi_phy_init(dsi);
> +	dw_mipi_dsi_wait_for_two_frames(dsi);
> +	drm_panel_prepare(dsi->panel);

... and drm_panel_prepare().

> +static struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {

static const, please.

> +int dw_mipi_dsi_bind(struct device *dev, struct device *master, void *data,
> +		     struct drm_encoder *encoder,
> +		     const struct dw_mipi_dsi_plat_data *pdata)
> +{
[...]
> +	dsi->cfg_clk = devm_clk_get(dev, "cfg");
> +	if (IS_ERR(dsi->cfg_clk))
> +		dev_warn(dev, "Have no configuration clock\n");

Is this truly optional?

> +	dsi->pclk = devm_clk_get(dev, "pclk");
> +	if (IS_ERR(dsi->pclk)) {
> +		ret = PTR_ERR(dsi->pclk);
> +		dev_err(dev, "Unable to get configuration clock: %d\n", ret);

pclk doesn't seem to be a "configuration clock".

> +	dev_info(dev, "version number is 0x%08x\n", val);

Do you really need this information? What purpose does it serve to have
this in the kernel log?

> diff --git a/include/drm/bridge/dw_mipi_dsi.h b/include/drm/bridge/dw_mipi_dsi.h
> new file mode 100644
> index 0000000..2e351e4
> --- /dev/null
> +++ b/include/drm/bridge/dw_mipi_dsi.h
> @@ -0,0 +1,27 @@
> +/*
> + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
> + *
> + * 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 __DW_MIPI_DSI__
> +#define __DW_MIPI_DSI__
> +
> +#include <drm/drmP.h>
> +
> +struct dw_mipi_dsi_plat_data {
> +	unsigned int max_data_lanes;
> +	enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
> +					   struct drm_display_mode *mode);
> +};
> +
> +int dw_mipi_dsi_get_encoder_pixel_format(struct drm_encoder *encoder);
> +
> +int dw_mipi_dsi_bind(struct device *dev, struct device *master,
> +		     void *data, struct drm_encoder *encoder,
> +		     const struct dw_mipi_dsi_plat_data *pdata);
> +void dw_mipi_dsi_unbind(struct device *dev, struct device *master, void *data);
> +#endif	/* __DW_MIPI_DSI__ */

Should have a blank line between the two above.

Thierry
Chris Zhong Nov. 26, 2015, 7:03 a.m. UTC | #2
Hi Thierry

Thanks for your feedback.


On 11/21/2015 12:07 AM, Thierry Reding wrote:
> On Fri, Nov 20, 2015 at 04:15:32PM +0800, Chris Zhong wrote:
>> add Synopsys DesignWare MIPI DSI host controller driver support.
>>
>> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
>> ---
>>
>> Changes in v4:
>> eliminate some warnning
>>
>> Changes in v3: None
>> Changes in v2: None
>>
>>   drivers/gpu/drm/bridge/Kconfig       |   11 +
>>   drivers/gpu/drm/bridge/Makefile      |    1 +
>>   drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1056 ++++++++++++++++++++++++++++++++++
>>   include/drm/bridge/dw_mipi_dsi.h     |   27 +
>>   4 files changed, 1095 insertions(+)
>>   create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c
>>   create mode 100644 include/drm/bridge/dw_mipi_dsi.h
>>
>>
>> +struct dw_mipi_dsi {
>> +	struct mipi_dsi_host dsi_host;
>> +	struct drm_connector connector;
>> +	struct drm_encoder *encoder;
> struct drm_bridge already has a pointer to an encoder, can't you reuse
> that instead?
>
>> +	struct drm_bridge *bridge;
> Typically you'd embed the bridge into the driver structure.
I'm almost done with all the modifications according to your comments.
Remaining this point I can not understand, the dsi is a pointer of 
drm_bridge,
hence I can not get the bridge by container_of.
Although, the "bridge->driver_private = dsi" have done in 
dw_mipi_dsi_register.

>
>> +	struct drm_panel *panel;
>> +	struct device *dev;
>> +
>> +	void __iomem *base;
>> +
>> +	struct clk *pllref_clk;
>> +	struct clk *cfg_clk;
>> +	struct clk *pclk;
>> +
>> +	unsigned int lane_mbps; /* per lane */
>> +	u32 channel;
>> +	u32 lanes;
>> +	u32 format;
>> +	u16 input_div;
>> +	u16 feedback_div;
>> +	struct drm_display_mode *mode;
>> +
>> +	const struct dw_mipi_dsi_plat_data *pdata;
>> +
>> +	bool enabled;
>> +};
>> +
>>
>>
>> Thierry
Thierry Reding Nov. 26, 2015, 8:04 a.m. UTC | #3
On Thu, Nov 26, 2015 at 03:03:54PM +0800, Chris Zhong wrote:
> Hi Thierry
> 
> Thanks for your feedback.
> 
> 
> On 11/21/2015 12:07 AM, Thierry Reding wrote:
> >On Fri, Nov 20, 2015 at 04:15:32PM +0800, Chris Zhong wrote:
> >>add Synopsys DesignWare MIPI DSI host controller driver support.
> >>
> >>Signed-off-by: Chris Zhong <zyw@rock-chips.com>
> >>---
> >>
> >>Changes in v4:
> >>eliminate some warnning
> >>
> >>Changes in v3: None
> >>Changes in v2: None
> >>
> >>  drivers/gpu/drm/bridge/Kconfig       |   11 +
> >>  drivers/gpu/drm/bridge/Makefile      |    1 +
> >>  drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1056 ++++++++++++++++++++++++++++++++++
> >>  include/drm/bridge/dw_mipi_dsi.h     |   27 +
> >>  4 files changed, 1095 insertions(+)
> >>  create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c
> >>  create mode 100644 include/drm/bridge/dw_mipi_dsi.h
> >>
> >>
> >>+struct dw_mipi_dsi {
> >>+	struct mipi_dsi_host dsi_host;
> >>+	struct drm_connector connector;
> >>+	struct drm_encoder *encoder;
> >struct drm_bridge already has a pointer to an encoder, can't you reuse
> >that instead?
> >
> >>+	struct drm_bridge *bridge;
> >Typically you'd embed the bridge into the driver structure.
> I'm almost done with all the modifications according to your comments.
> Remaining this point I can not understand, the dsi is a pointer of
> drm_bridge,
> hence I can not get the bridge by container_of.
> Although, the "bridge->driver_private = dsi" have done in
> dw_mipi_dsi_register.

I don't understand. If you don't make bridge a pointer, but rather embed
it within the structure, then you can use container_of(), can't you? You
get it allocated automatically as part of allocating the dw_mipi_dsi
structure. See for example the ps8622 and ptn3460 bridge drivers.

On a slightly unrelated note, I'm not sure if we discussed this already
or if it was in another thread, but why is this even a bridge driver? A
bridge is typically some IC outside of the SoC, whereas this clearly is
IP designed into the SoC. So it's really more of an encoder rather than
a bridge.

Thierry
Chris Zhong Nov. 26, 2015, 10:05 a.m. UTC | #4
On 11/26/2015 04:04 PM, Thierry Reding wrote:
> On Thu, Nov 26, 2015 at 03:03:54PM +0800, Chris Zhong wrote:
>> Hi Thierry
>>
>> Thanks for your feedback.
>>
>>
>> On 11/21/2015 12:07 AM, Thierry Reding wrote:
>>> On Fri, Nov 20, 2015 at 04:15:32PM +0800, Chris Zhong wrote:
>>>> add Synopsys DesignWare MIPI DSI host controller driver support.
>>>>
>>>> Signed-off-by: Chris Zhong <zyw@rock-chips.com>
>>>> ---
>>>>
>>>> Changes in v4:
>>>> eliminate some warnning
>>>>
>>>> Changes in v3: None
>>>> Changes in v2: None
>>>>
>>>>   drivers/gpu/drm/bridge/Kconfig       |   11 +
>>>>   drivers/gpu/drm/bridge/Makefile      |    1 +
>>>>   drivers/gpu/drm/bridge/dw_mipi_dsi.c | 1056 ++++++++++++++++++++++++++++++++++
>>>>   include/drm/bridge/dw_mipi_dsi.h     |   27 +
>>>>   4 files changed, 1095 insertions(+)
>>>>   create mode 100644 drivers/gpu/drm/bridge/dw_mipi_dsi.c
>>>>   create mode 100644 include/drm/bridge/dw_mipi_dsi.h
>>>>
>>>>
>>>> +struct dw_mipi_dsi {
>>>> +	struct mipi_dsi_host dsi_host;
>>>> +	struct drm_connector connector;
>>>> +	struct drm_encoder *encoder;
>>> struct drm_bridge already has a pointer to an encoder, can't you reuse
>>> that instead?
>>>
>>>> +	struct drm_bridge *bridge;
>>> Typically you'd embed the bridge into the driver structure.
>> I'm almost done with all the modifications according to your comments.
>> Remaining this point I can not understand, the dsi is a pointer of
>> drm_bridge,
>> hence I can not get the bridge by container_of.
>> Although, the "bridge->driver_private = dsi" have done in
>> dw_mipi_dsi_register.
> I don't understand. If you don't make bridge a pointer, but rather embed
> it within the structure, then you can use container_of(), can't you? You
> get it allocated automatically as part of allocating the dw_mipi_dsi
> structure. See for example the ps8622 and ptn3460 bridge drivers.
Yeah, got it, thanks. :)
I am going to modify it in next version patch.
>
> On a slightly unrelated note, I'm not sure if we discussed this already
> or if it was in another thread, but why is this even a bridge driver? A
> bridge is typically some IC outside of the SoC, whereas this clearly is
> IP designed into the SoC. So it's really more of an encoder rather than
> a bridge.
I guess you mean this thread <https://patchwork.kernel.org/patch/5816991/>
I think the bridge is beneficial to share the dw-mipi driver for 
different soc
with same version dw-mipi IP.

>
> Thierry
diff mbox

Patch

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 6dddd39..c0900e0 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -22,6 +22,17 @@  config DRM_DW_HDMI_AHB_AUDIO
 	  Designware HDMI block.  This is used in conjunction with
 	  the i.MX6 HDMI driver.
 
+config DRM_DW_MIPI_DSI
+	tristate "Synopsys DesignWare MIPI DSI host controller bridge"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_MIPI_DSI
+	select DRM_PANEL
+	help
+	  Choose this if you want to use the Synopsys DesignWare MIPI DSI host
+	  controller bridge. If M is selected, the module will be
+	  called dw_mipi_dsi. DRM_MIPI_DSI support is required for this driver
+	  to work.
 
 config DRM_NXP_PTN3460
 	tristate "NXP PTN3460 DP/LVDS bridge"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d4e28be..d908c4b 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -2,5 +2,6 @@  ccflags-y := -Iinclude/drm
 
 obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw_hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw_mipi_dsi.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
diff --git a/drivers/gpu/drm/bridge/dw_mipi_dsi.c b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
new file mode 100644
index 0000000..23b612d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/dw_mipi_dsi.c
@@ -0,0 +1,1056 @@ 
+/*
+ * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * 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/math64.h>
+#include <linux/module.h>
+#include <drm/bridge/dw_mipi_dsi.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <video/mipi_display.h>
+
+#define DSI_VERSION			0x00
+
+#define DSI_PWR_UP			0x04
+#define RESET				0
+#define POWERUP				BIT(0)
+
+#define DSI_CLKMGR_CFG			0x08
+#define TO_CLK_DIVIDSION(div)		(((div) & 0xff) << 8)
+#define TX_ESC_CLK_DIVIDSION(div)	(((div) & 0xff) << 0)
+
+#define DSI_DPI_VCID			0x0c
+#define DPI_VID(vid)			(((vid) & 0x3) << 0)
+
+#define DSI_DPI_COLOR_CODING		0x10
+#define EN18_LOOSELY			BIT(8)
+#define DPI_COLOR_CODING_16BIT_1	0x0
+#define DPI_COLOR_CODING_16BIT_2	0x1
+#define DPI_COLOR_CODING_16BIT_3	0x2
+#define DPI_COLOR_CODING_18BIT_1	0x3
+#define DPI_COLOR_CODING_18BIT_2	0x4
+#define DPI_COLOR_CODING_24BIT		0x5
+
+#define DSI_DPI_CFG_POL			0x14
+#define COLORM_ACTIVE_LOW		BIT(4)
+#define SHUTD_ACTIVE_LOW		BIT(3)
+#define HSYNC_ACTIVE_LOW		BIT(2)
+#define VSYNC_ACTIVE_LOW		BIT(1)
+#define DATAEN_ACTIVE_LOW		BIT(0)
+
+#define DSI_DPI_LP_CMD_TIM		0x18
+#define OUTVACT_LPCMD_TIME(p)		(((p) & 0xff) << 16)
+#define INVACT_LPCMD_TIME(p)		((p) & 0xff)
+
+#define DSI_DBI_CFG			0x20
+#define DSI_DBI_CMDSIZE			0x28
+
+#define DSI_PCKHDL_CFG			0x2c
+#define EN_CRC_RX			BIT(4)
+#define EN_ECC_RX			BIT(3)
+#define EN_BTA				BIT(2)
+#define EN_EOTP_RX			BIT(1)
+#define EN_EOTP_TX			BIT(0)
+
+#define DSI_MODE_CFG			0x34
+#define ENABLE_VIDEO_MODE		0
+#define ENABLE_CMD_MODE			BIT(0)
+
+#define DSI_VID_MODE_CFG		0x38
+#define FRAME_BTA_ACK			BIT(14)
+#define ENABLE_LOW_POWER		(0x3f << 8)
+#define ENABLE_LOW_POWER_MASK		(0x3f << 8)
+#define VID_MODE_TYPE_BURST_SYNC_PULSES		0x2
+#define VID_MODE_TYPE_MASK			0x3
+
+#define DSI_VID_PKT_SIZE		0x3c
+#define VID_PKT_SIZE(p)			(((p) & 0x3fff) << 0)
+#define VID_PKT_MAX_SIZE		0x3fff
+
+#define DSI_VID_HSA_TIME		0x48
+#define DSI_VID_HBP_TIME		0x4c
+#define DSI_VID_HLINE_TIME		0x50
+#define DSI_VID_VSA_LINES		0x54
+#define DSI_VID_VBP_LINES		0x58
+#define DSI_VID_VFP_LINES		0x5c
+#define DSI_VID_VACTIVE_LINES		0x60
+#define DSI_CMD_MODE_CFG		0x68
+#define MAX_RD_PKT_SIZE_LP		BIT(24)
+#define DCS_LW_TX_LP			BIT(19)
+#define DCS_SR_0P_TX_LP			BIT(18)
+#define DCS_SW_1P_TX_LP			BIT(17)
+#define DCS_SW_0P_TX_LP			BIT(16)
+#define GEN_LW_TX_LP			BIT(14)
+#define GEN_SR_2P_TX_LP			BIT(13)
+#define GEN_SR_1P_TX_LP			BIT(12)
+#define GEN_SR_0P_TX_LP			BIT(11)
+#define GEN_SW_2P_TX_LP			BIT(10)
+#define GEN_SW_1P_TX_LP			BIT(9)
+#define GEN_SW_0P_TX_LP			BIT(8)
+#define EN_ACK_RQST			BIT(1)
+#define EN_TEAR_FX			BIT(0)
+
+#define CMD_MODE_ALL_LP			(MAX_RD_PKT_SIZE_LP | \
+					 DCS_LW_TX_LP | \
+					 DCS_SR_0P_TX_LP | \
+					 DCS_SW_1P_TX_LP | \
+					 DCS_SW_0P_TX_LP | \
+					 GEN_LW_TX_LP | \
+					 GEN_SR_2P_TX_LP | \
+					 GEN_SR_1P_TX_LP | \
+					 GEN_SR_0P_TX_LP | \
+					 GEN_SW_2P_TX_LP | \
+					 GEN_SW_1P_TX_LP | \
+					 GEN_SW_0P_TX_LP)
+
+#define DSI_GEN_HDR			0x6c
+#define GEN_HDATA(data)			(((data) & 0xffff) << 8)
+#define GEN_HDATA_MASK			(0xffff << 8)
+#define GEN_HTYPE(type)			(((type) & 0xff) << 0)
+#define GEN_HTYPE_MASK			0xff
+
+#define DSI_GEN_PLD_DATA		0x70
+
+#define DSI_CMD_PKT_STATUS		0x74
+#define GEN_CMD_EMPTY			BIT(0)
+#define GEN_CMD_FULL			BIT(1)
+#define GEN_PLD_W_EMPTY			BIT(2)
+#define GEN_PLD_W_FULL			BIT(3)
+#define GEN_PLD_R_EMPTY			BIT(4)
+#define GEN_PLD_R_FULL			BIT(5)
+#define GEN_RD_CMD_BUSY			BIT(6)
+
+#define DSI_TO_CNT_CFG			0x78
+#define HSTX_TO_CNT(p)			(((p) & 0xffff) << 16)
+#define LPRX_TO_CNT(p)			((p) & 0xffff)
+
+#define DSI_BTA_TO_CNT			0x8c
+
+#define DSI_LPCLK_CTRL			0x94
+#define AUTO_CLKLANE_CTRL		BIT(1)
+#define PHY_TXREQUESTCLKHS		BIT(0)
+
+#define DSI_PHY_TMR_LPCLK_CFG		0x98
+#define PHY_CLKHS2LP_TIME(lbcc)		(((lbcc) & 0x3ff) << 16)
+#define PHY_CLKLP2HS_TIME(lbcc)		((lbcc) & 0x3ff)
+
+#define DSI_PHY_TMR_CFG			0x9c
+#define PHY_HS2LP_TIME(lbcc)		(((lbcc) & 0xff) << 24)
+#define PHY_LP2HS_TIME(lbcc)		(((lbcc) & 0xff) << 16)
+#define MAX_RD_TIME(lbcc)		((lbcc) & 0x7fff)
+
+#define DSI_PHY_RSTZ			0xa0
+#define PHY_DISFORCEPLL			0
+#define PHY_ENFORCEPLL			BIT(3)
+#define PHY_DISABLECLK			0
+#define PHY_ENABLECLK			BIT(2)
+#define PHY_RSTZ			0
+#define PHY_UNRSTZ			BIT(1)
+#define PHY_SHUTDOWNZ			0
+#define PHY_UNSHUTDOWNZ			BIT(0)
+
+#define DSI_PHY_IF_CFG			0xa4
+#define N_LANES(n)			((((n) - 1) & 0x3) << 0)
+#define PHY_STOP_WAIT_TIME(cycle)	(((cycle) & 0xff) << 8)
+
+#define DSI_PHY_STATUS			0xb0
+#define LOCK				BIT(0)
+#define STOP_STATE_CLK_LANE		BIT(2)
+
+#define DSI_PHY_TST_CTRL0		0xb4
+#define PHY_TESTCLK			BIT(1)
+#define PHY_UNTESTCLK			0
+#define PHY_TESTCLR			BIT(0)
+#define PHY_UNTESTCLR			0
+
+#define DSI_PHY_TST_CTRL1		0xb8
+#define PHY_TESTEN			BIT(16)
+#define PHY_UNTESTEN			0
+#define PHY_TESTDOUT(n)			(((n) & 0xff) << 8)
+#define PHY_TESTDIN(n)			(((n) & 0xff) << 0)
+
+#define DSI_INT_ST0			0xbc
+#define DSI_INT_ST1			0xc0
+#define DSI_INT_MSK0			0xc4
+#define DSI_INT_MSK1			0xc8
+
+#define PHY_STATUS_TIMEOUT		10
+#define CMD_PKT_STATUS_TIMEOUT		20
+
+struct dw_mipi_dsi {
+	struct mipi_dsi_host dsi_host;
+	struct drm_connector connector;
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	struct drm_panel *panel;
+	struct device *dev;
+
+	void __iomem *base;
+
+	struct clk *pllref_clk;
+	struct clk *cfg_clk;
+	struct clk *pclk;
+
+	unsigned int lane_mbps; /* per lane */
+	u32 channel;
+	u32 lanes;
+	u32 format;
+	u16 input_div;
+	u16 feedback_div;
+	struct drm_display_mode *mode;
+
+	const struct dw_mipi_dsi_plat_data *pdata;
+
+	bool enabled;
+};
+
+enum {
+	STATUS_TO_CLEAR,
+	STATUS_TO_SET,
+};
+
+enum dw_mipi_dsi_mode {
+	DW_MIPI_DSI_CMD_MODE,
+	DW_MIPI_DSI_VID_MODE,
+};
+
+struct dphy_pll_testdin_map {
+	unsigned int max_mbps;
+	u8 testdin;
+};
+
+/* The table is based on 27MHz DPHY pll reference clock. */
+static const struct dphy_pll_testdin_map dptdin_map[] = {
+	{90, 0x00}, {100, 0x10}, {110, 0x20}, {130, 0x01},
+	{140, 0x11}, {150, 0x21}, {170, 0x02}, {180, 0x12},
+	{200, 0x22}, {220, 0x03}, {240, 0x13}, {250, 0x23},
+	{270, 0x04}, {300, 0x14}, {330, 0x05}, {360, 0x15},
+	{400, 0x25}, {450, 0x06}, {500, 0x16}, {550, 0x07},
+	{600, 0x17}, {650, 0x08}, {700, 0x18}, {750, 0x09},
+	{800, 0x19}, {850, 0x29}, {900, 0x39}, {950, 0x0a},
+	{1000, 0x1a}, {1050, 0x2a}, {1100, 0x3a}, {1150, 0x0b},
+	{1200, 0x1b}, {1250, 0x2b}, {1300, 0x3b}, {1350, 0x0c},
+	{1400, 0x1c}, {1450, 0x2c}, {1500, 0x3c}
+};
+
+static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host)
+{
+	return container_of(host, struct dw_mipi_dsi, dsi_host);
+}
+
+static inline struct dw_mipi_dsi *con_to_dsi(struct drm_connector *con)
+{
+	return container_of(con, struct dw_mipi_dsi, connector);
+}
+
+int dw_mipi_dsi_get_encoder_pixel_format(struct drm_encoder *encoder)
+{
+	struct dw_mipi_dsi *dsi = encoder->bridge->driver_private;
+
+	return dsi->format;
+}
+EXPORT_SYMBOL_GPL(dw_mipi_dsi_get_encoder_pixel_format);
+
+static int max_mbps_to_testdin(unsigned int max_mbps)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(dptdin_map); i++)
+		if (dptdin_map[i].max_mbps > max_mbps)
+			return dptdin_map[i].testdin;
+
+	return -EINVAL;
+}
+
+static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
+{
+	writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
+{
+	return readl(dsi->base + reg);
+}
+
+static inline void dsi_modify(struct dw_mipi_dsi *dsi, u32 reg,
+			      u32 mask, u32 val)
+{
+	u32 v;
+
+	v = readl(dsi->base + reg);
+	v &= ~mask;
+	v |= val;
+	writel(v, dsi->base + reg);
+}
+
+static int check_status(struct dw_mipi_dsi *dsi, u32 reg, u32 status,
+			unsigned int timeout, bool to_set)
+{
+	unsigned long expire;
+	bool out;
+	u32 val;
+
+	expire = jiffies + msecs_to_jiffies(timeout);
+	for (;;) {
+		val = dsi_read(dsi, reg);
+		out = to_set ? ((val & status) == status) : !(val & status);
+		if (out)
+			break;
+
+		if (time_after(jiffies, expire))
+			return -ETIMEDOUT;
+
+		cpu_relax();
+	}
+
+	return 0;
+}
+
+/*
+ * The controller should generate 2 frames before
+ * preparing the peripheral.
+ */
+static void dw_mipi_dsi_wait_for_two_frames(struct dw_mipi_dsi *dsi)
+{
+	unsigned long expire;
+	int refresh, two_frames;
+
+	refresh = drm_mode_vrefresh(dsi->mode);
+	two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
+
+	expire = jiffies + msecs_to_jiffies(two_frames);
+	while (time_before(jiffies, expire))
+		cpu_relax();
+}
+
+static void dw_mipi_dsi_phy_test(struct dw_mipi_dsi *dsi, u8 test_code,
+				 u8 test_data)
+{
+	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
+
+	dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDOUT(0) |
+					  PHY_TESTDIN(test_code));
+
+	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
+	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
+
+	dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN | PHY_TESTDOUT(0) |
+					  PHY_TESTDIN(test_data));
+
+	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
+	dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR);
+}
+
+static int dw_mipi_dsi_phy_init(struct dw_mipi_dsi *dsi)
+{
+	int ret, testdin, vco;
+
+	vco = (dsi->lane_mbps < 200) ? 0 : (dsi->lane_mbps + 100) / 200;
+
+	testdin = max_mbps_to_testdin(dsi->lane_mbps);
+	if (testdin < 0) {
+		dev_err(dsi->dev,
+			"failed to get testdin for %dmbps lane clock\n",
+			dsi->lane_mbps);
+		return testdin;
+	}
+
+	dsi_write(dsi, DSI_PWR_UP, POWERUP);
+
+	dw_mipi_dsi_phy_test(dsi, 0x10, 0x80 | (vco & 0x7) << 3 | 0x3);
+	dw_mipi_dsi_phy_test(dsi, 0x11, 0x8);
+	dw_mipi_dsi_phy_test(dsi, 0x12, 0xc0);
+
+	dw_mipi_dsi_phy_test(dsi, 0x44, testdin << 1);
+
+	dw_mipi_dsi_phy_test(dsi, 0x17, dsi->input_div - 1);
+	dw_mipi_dsi_phy_test(dsi, 0x18, (dsi->feedback_div - 1) & 0x1f);
+	dw_mipi_dsi_phy_test(dsi, 0x18, (dsi->feedback_div - 1) >> 5 | 0x80);
+	dw_mipi_dsi_phy_test(dsi, 0x19, 0x30);
+
+	dw_mipi_dsi_phy_test(dsi, 0x20, 0x4d);
+	dw_mipi_dsi_phy_test(dsi, 0x21, 0x3d);
+	dw_mipi_dsi_phy_test(dsi, 0x21, 0xdf);
+	dw_mipi_dsi_phy_test(dsi, 0x22, 0x7);
+	dw_mipi_dsi_phy_test(dsi, 0x22, 0x87);
+	dw_mipi_dsi_phy_test(dsi, 0x70, 0x80 | 0xf);
+	dw_mipi_dsi_phy_test(dsi, 0x71, 0x80 | 0x55);
+	dw_mipi_dsi_phy_test(dsi, 0x72, 0x40 | 0xa);
+
+	dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK
+		  | PHY_UNRSTZ | PHY_UNSHUTDOWNZ);
+
+	ret = check_status(dsi, DSI_PHY_STATUS, LOCK,
+			   PHY_STATUS_TIMEOUT, STATUS_TO_SET);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to wait for phy lock state\n");
+		return ret;
+	}
+	ret = check_status(dsi, DSI_PHY_STATUS, STOP_STATE_CLK_LANE,
+			   PHY_STATUS_TIMEOUT, STATUS_TO_SET);
+	if (ret < 0) {
+		dev_err(dsi->dev,
+			"failed to wait for phy clk lane stop state\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int dw_mipi_dsi_get_lane_bps(struct dw_mipi_dsi *dsi)
+{
+	int bpp, i;
+	unsigned int max_mbps = 0, target_mbps = 1000;
+	unsigned long mpclk, pllref, tmp;
+	int m = 1, n = 1, pre;
+
+	for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) {
+		if (max_mbps < dptdin_map[i].max_mbps)
+			max_mbps = dptdin_map[i].max_mbps;
+	}
+
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	if (bpp < 0) {
+		dev_err(dsi->dev, "failed to get bpp for pixel format %d\n",
+			dsi->format);
+		return bpp;
+	}
+
+	mpclk = DIV_ROUND_UP(dsi->mode->clock, MSEC_PER_SEC);
+	if (mpclk) {
+		/* take 1 / 0.9, since mbps must big than bandwidth of RGB */
+		tmp = mpclk * (bpp / dsi->lanes) * 10 / 9;
+		if (tmp < max_mbps)
+			target_mbps = tmp;
+		else
+			dev_err(dsi->dev, "DPHY clock frequency is out of range\n");
+	}
+
+	pllref = DIV_ROUND_UP(clk_get_rate(dsi->pllref_clk), USEC_PER_SEC);
+	tmp = pllref;
+
+	for (i = 1; i < 6; i++) {
+		pre = pllref / i;
+		if ((tmp > (target_mbps % pre)) && (target_mbps / pre < 512)) {
+			tmp = target_mbps % pre;
+			n = i;
+			m = target_mbps / pre;
+		}
+		if (tmp == 0)
+			break;
+	}
+
+	dsi->lane_mbps = pllref / n * m;
+	dsi->input_div = n;
+	dsi->feedback_div = m;
+
+	return 0;
+}
+
+static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
+				   struct mipi_dsi_device *device)
+{
+	struct dw_mipi_dsi *dsi = host_to_dsi(host);
+
+	if (device->lanes > dsi->pdata->max_data_lanes) {
+		dev_err(dsi->dev, "the number of data lanes(%d) is too many\n",
+				device->lanes);
+		return -EINVAL;
+	}
+
+	if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
+	    !(device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) {
+		dev_err(dsi->dev, "device mode is unsupported\n");
+		return -EINVAL;
+	}
+
+	dsi->lanes = device->lanes;
+	dsi->channel = device->channel;
+	dsi->format = device->format;
+	dsi->panel = of_drm_find_panel(device->dev.of_node);
+	drm_panel_attach(dsi->panel, &dsi->connector);
+
+	return 0;
+}
+
+static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host,
+				   struct mipi_dsi_device *device)
+{
+	struct dw_mipi_dsi *dsi = host_to_dsi(host);
+
+	drm_panel_detach(dsi->panel);
+
+	return 0;
+}
+
+static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 val)
+{
+	int ret;
+
+	ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_CMD_FULL,
+			   CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to get available command FIFO\n");
+		return ret;
+	}
+
+	dsi_write(dsi, DSI_GEN_HDR, val);
+
+	ret = check_status(dsi, DSI_CMD_PKT_STATUS,
+			   GEN_CMD_EMPTY | GEN_PLD_W_EMPTY,
+			   CMD_PKT_STATUS_TIMEOUT, STATUS_TO_SET);
+	if (ret < 0) {
+		dev_err(dsi->dev, "failed to write command FIFO\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dw_mipi_dsi_dcs_short_write(struct dw_mipi_dsi *dsi,
+				       const struct mipi_dsi_msg *msg)
+{
+	const u16 *tx_buf = msg->tx_buf;
+	u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type);
+
+	if (msg->tx_len > 2) {
+		dev_err(dsi->dev, "too long tx buf length %d for short write\n",
+			(int)msg->tx_len);
+		return -EINVAL;
+	}
+
+	return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static int dw_mipi_dsi_dcs_long_write(struct dw_mipi_dsi *dsi,
+				      const struct mipi_dsi_msg *msg)
+{
+	const u32 *tx_buf = msg->tx_buf;
+	int len = msg->tx_len, pld_data_bytes = sizeof(*tx_buf), ret;
+	u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type);
+	u32 remainder = 0;
+
+	if (msg->tx_len < 3) {
+		dev_err(dsi->dev, "wrong tx buf length %d for long write\n",
+			(int)msg->tx_len);
+		return -EINVAL;
+	}
+
+	while (DIV_ROUND_UP(len, pld_data_bytes)) {
+		if (len < pld_data_bytes) {
+			memcpy(&remainder, tx_buf, len);
+			dsi_write(dsi, DSI_GEN_PLD_DATA, remainder);
+			len = 0;
+		} else {
+			dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf);
+			tx_buf++;
+			len -= pld_data_bytes;
+		}
+		ret = check_status(dsi, DSI_CMD_PKT_STATUS, GEN_PLD_W_FULL,
+				   CMD_PKT_STATUS_TIMEOUT, STATUS_TO_CLEAR);
+		if (ret < 0) {
+			dev_err(dsi->dev,
+				"failed to get available write payload FIFO\n");
+			return ret;
+		}
+	}
+
+	return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val);
+}
+
+static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
+					 const struct mipi_dsi_msg *msg)
+{
+	struct dw_mipi_dsi *dsi = host_to_dsi(host);
+	int ret;
+
+	switch (msg->type) {
+	case MIPI_DSI_DCS_SHORT_WRITE:
+	case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
+	case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
+		ret = dw_mipi_dsi_dcs_short_write(dsi, msg);
+		break;
+	case MIPI_DSI_DCS_LONG_WRITE:
+		ret = dw_mipi_dsi_dcs_long_write(dsi, msg);
+		break;
+	default:
+		dev_err(dsi->dev, "unsupported message type\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = {
+	.attach = dw_mipi_dsi_host_attach,
+	.detach = dw_mipi_dsi_host_detach,
+	.transfer = dw_mipi_dsi_host_transfer,
+};
+
+static enum drm_connector_status
+dw_mipi_dsi_detect(struct drm_connector *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static void dw_mipi_dsi_drm_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static struct drm_connector_funcs dw_mipi_dsi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = dw_mipi_dsi_detect,
+	.destroy = dw_mipi_dsi_drm_connector_destroy,
+};
+
+static int dw_mipi_dsi_connector_get_modes(struct drm_connector *connector)
+{
+	struct dw_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return drm_panel_get_modes(dsi->panel);
+}
+
+static enum drm_mode_status dw_mipi_dsi_mode_valid(
+					struct drm_connector *connector,
+					struct drm_display_mode *mode)
+{
+	struct dw_mipi_dsi *dsi = con_to_dsi(connector);
+
+	enum drm_mode_status mode_status = MODE_OK;
+
+	if (dsi->pdata->mode_valid)
+		mode_status = dsi->pdata->mode_valid(connector, mode);
+
+	return mode_status;
+}
+
+static struct drm_encoder *dw_mipi_dsi_connector_best_encoder(
+					struct drm_connector *connector)
+{
+	struct dw_mipi_dsi *dsi = con_to_dsi(connector);
+
+	return dsi->encoder;
+}
+
+static struct drm_connector_helper_funcs dw_mipi_dsi_connector_helper_funcs = {
+	.get_modes = dw_mipi_dsi_connector_get_modes,
+	.mode_valid = dw_mipi_dsi_mode_valid,
+	.best_encoder = dw_mipi_dsi_connector_best_encoder,
+};
+
+static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi)
+{
+	u32 val;
+
+	val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER;
+
+	dsi_write(dsi, DSI_VID_MODE_CFG, val);
+}
+
+static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi,
+				 enum dw_mipi_dsi_mode mode)
+{
+	if (mode == DW_MIPI_DSI_CMD_MODE) {
+		dsi_write(dsi, DSI_PWR_UP, RESET);
+		dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+		dsi_write(dsi, DSI_PWR_UP, POWERUP);
+	} else {
+		dsi_write(dsi, DSI_PWR_UP, RESET);
+		dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE);
+		dw_mipi_dsi_video_mode_config(dsi);
+		dsi_write(dsi, DSI_PWR_UP, POWERUP);
+	}
+}
+
+static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ);
+}
+
+static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge)
+{
+	struct dw_mipi_dsi *dsi = bridge->driver_private;
+
+	if (dsi->enabled)
+		return;
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_prepare_enable(dsi->cfg_clk);
+
+	clk_prepare_enable(dsi->pclk);
+	dw_mipi_dsi_phy_init(dsi);
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
+	dw_mipi_dsi_wait_for_two_frames(dsi);
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
+	drm_panel_prepare(dsi->panel);
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
+	clk_disable_unprepare(dsi->pclk);
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_disable_unprepare(dsi->cfg_clk);
+
+	drm_panel_enable(dsi->panel);
+
+	dsi->enabled = true;
+}
+
+static void dw_mipi_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct dw_mipi_dsi *dsi = bridge->driver_private;
+	unsigned long expire;
+
+	if (!dsi->enabled)
+		return;
+
+	drm_panel_disable(dsi->panel);
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_prepare_enable(dsi->cfg_clk);
+
+	clk_prepare_enable(dsi->pclk);
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
+	drm_panel_unprepare(dsi->panel);
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE);
+
+	/*
+	 * This is necessary to make sure the peripheral
+	 * will be driven normally when the display is
+	 * enabled again later.
+	 */
+	expire = jiffies + msecs_to_jiffies(120);
+	while (time_before(jiffies, expire))
+		cpu_relax();
+
+	dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE);
+	dw_mipi_dsi_disable(dsi);
+	clk_disable_unprepare(dsi->pclk);
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_disable_unprepare(dsi->cfg_clk);
+
+	dsi->enabled = false;
+}
+
+static void dw_mipi_dsi_bridge_nope(struct drm_bridge *bridge)
+{
+	/* do nothing */
+}
+
+static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PWR_UP, RESET);
+	dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
+		  | PHY_RSTZ | PHY_SHUTDOWNZ);
+	dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVIDSION(10) |
+		  TX_ESC_CLK_DIVIDSION(7));
+	dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS);
+}
+
+static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
+				   struct drm_display_mode *mode)
+{
+	u32 val = 0, calor = 0;
+
+	switch (dsi->format) {
+	case MIPI_DSI_FMT_RGB888:
+		calor = DPI_COLOR_CODING_24BIT;
+		break;
+	case MIPI_DSI_FMT_RGB666:
+		calor = DPI_COLOR_CODING_18BIT_2 | EN18_LOOSELY;
+		break;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		calor = DPI_COLOR_CODING_18BIT_1;
+		break;
+	case MIPI_DSI_FMT_RGB565:
+		calor = DPI_COLOR_CODING_16BIT_1;
+		break;
+	}
+
+	if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+		val |= VSYNC_ACTIVE_LOW;
+	if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+		val |= HSYNC_ACTIVE_LOW;
+
+	dsi_write(dsi, DSI_DPI_VCID, DPI_VID(dsi->channel));
+	dsi_write(dsi, DSI_DPI_COLOR_CODING, calor);
+	dsi_write(dsi, DSI_DPI_CFG_POL, val);
+	dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(4)
+		  | INVACT_LPCMD_TIME(4));
+}
+
+static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA);
+}
+
+static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi,
+					    struct drm_display_mode *mode)
+{
+	dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(mode->hdisplay));
+}
+
+static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000));
+	dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00);
+	dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP);
+	dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+}
+
+/* Get lane byte clock cycles. */
+static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
+					   u32 hcomponent)
+{
+	u32 frac, lbcc;
+
+	lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8;
+
+	frac = lbcc % dsi->mode->clock;
+	lbcc = lbcc / dsi->mode->clock;
+	if (frac)
+		lbcc++;
+
+	return lbcc;
+}
+
+static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi)
+{
+	u32 htotal, hsa, hbp, lbcc;
+	struct drm_display_mode *mode = dsi->mode;
+
+	htotal = mode->htotal;
+	hsa = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, htotal);
+	dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc);
+
+	lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hsa);
+	dsi_write(dsi, DSI_VID_HSA_TIME, lbcc);
+
+	lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hbp);
+	dsi_write(dsi, DSI_VID_HBP_TIME, lbcc);
+}
+
+static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi)
+{
+	u32 vactive, vsa, vfp, vbp;
+	struct drm_display_mode *mode = dsi->mode;
+
+	vactive = mode->vdisplay;
+	vsa = mode->vsync_end - mode->vsync_start;
+	vfp = mode->vsync_start - mode->vdisplay;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive);
+	dsi_write(dsi, DSI_VID_VSA_LINES, vsa);
+	dsi_write(dsi, DSI_VID_VFP_LINES, vfp);
+	dsi_write(dsi, DSI_VID_VBP_LINES, vbp);
+}
+
+static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME(0x40)
+		  | PHY_LP2HS_TIME(0x40) | MAX_RD_TIME(10000));
+
+	dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, PHY_CLKHS2LP_TIME(0x40)
+		  | PHY_CLKLP2HS_TIME(0x40));
+}
+
+static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
+{
+	dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+		  N_LANES(dsi->lanes));
+}
+
+static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
+{
+	dsi_read(dsi, DSI_INT_ST0);
+	dsi_read(dsi, DSI_INT_ST1);
+	dsi_write(dsi, DSI_INT_MSK0, 0);
+	dsi_write(dsi, DSI_INT_MSK1, 0);
+}
+
+static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge,
+					struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	struct dw_mipi_dsi *dsi = bridge->driver_private;
+	int ret;
+
+	dsi->mode = adjusted_mode;
+
+	ret = dw_mipi_dsi_get_lane_bps(dsi);
+	if (ret < 0)
+		return;
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_prepare_enable(dsi->cfg_clk);
+
+	clk_prepare_enable(dsi->pclk);
+	dw_mipi_dsi_init(dsi);
+	dw_mipi_dsi_dpi_config(dsi, mode);
+	dw_mipi_dsi_packet_handler_config(dsi);
+	dw_mipi_dsi_video_mode_config(dsi);
+	dw_mipi_dsi_video_packet_config(dsi, mode);
+	dw_mipi_dsi_command_mode_config(dsi);
+	dw_mipi_dsi_line_timer_config(dsi);
+	dw_mipi_dsi_vertical_timing_config(dsi);
+	dw_mipi_dsi_dphy_timing_config(dsi);
+	dw_mipi_dsi_dphy_interface_config(dsi);
+	dw_mipi_dsi_clear_err(dsi);
+	dw_mipi_dsi_phy_init(dsi);
+	dw_mipi_dsi_wait_for_two_frames(dsi);
+	drm_panel_prepare(dsi->panel);
+	clk_disable_unprepare(dsi->pclk);
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_disable_unprepare(dsi->cfg_clk);
+}
+
+static bool dw_mipi_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
+					const struct drm_display_mode *mode,
+					struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
+	.enable = dw_mipi_dsi_bridge_enable,
+	.disable = dw_mipi_dsi_bridge_disable,
+	.pre_enable = dw_mipi_dsi_bridge_nope,
+	.post_disable = dw_mipi_dsi_bridge_nope,
+	.mode_set = dw_mipi_dsi_bridge_mode_set,
+	.mode_fixup = dw_mipi_dsi_bridge_mode_fixup,
+};
+
+static int dw_mipi_dsi_register(struct drm_device *drm, struct dw_mipi_dsi *dsi)
+{
+	struct drm_encoder *encoder = dsi->encoder;
+	struct drm_bridge *bridge;
+	int ret;
+
+	bridge = devm_kzalloc(drm->dev, sizeof(*bridge), GFP_KERNEL);
+	if (!bridge)
+		return -ENOMEM;
+
+	dsi->bridge = bridge;
+	bridge->driver_private = dsi;
+
+	bridge->funcs = &dw_mipi_dsi_bridge_funcs;
+	ret = drm_bridge_attach(drm, bridge);
+	if (ret) {
+		DRM_ERROR("failed to initialize bridge with drm\n");
+		return ret;
+	}
+
+	encoder->bridge = bridge;
+
+	drm_connector_helper_add(&dsi->connector,
+			&dw_mipi_dsi_connector_helper_funcs);
+	drm_connector_init(drm, &dsi->connector, &dw_mipi_dsi_connector_funcs,
+			   DRM_MODE_CONNECTOR_DSI);
+
+	drm_mode_connector_attach_encoder(&dsi->connector, dsi->encoder);
+
+	return 0;
+}
+
+int dw_mipi_dsi_bind(struct device *dev, struct device *master, void *data,
+		     struct drm_encoder *encoder,
+		     const struct dw_mipi_dsi_plat_data *pdata)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	struct drm_device *drm = data;
+	struct dw_mipi_dsi *dsi;
+	u32 val;
+	int ret;
+
+	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi)
+		return -ENOMEM;
+
+	dsi->pdata = pdata;
+	dsi->dev = dev;
+	dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
+	dsi->dsi_host.dev = dev;
+	dsi->encoder = encoder;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	dsi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dsi->base))
+		return PTR_ERR(dsi->base);
+
+	dsi->pllref_clk = devm_clk_get(dev, "ref");
+	if (IS_ERR(dsi->pllref_clk)) {
+		ret = PTR_ERR(dsi->pllref_clk);
+		dev_err(dev, "Unable to get pll reference clock: %d\n", ret);
+		return ret;
+	}
+	clk_prepare_enable(dsi->pllref_clk);
+
+	dsi->cfg_clk = devm_clk_get(dev, "cfg");
+	if (IS_ERR(dsi->cfg_clk))
+		dev_warn(dev, "Have no configuration clock\n");
+
+	dsi->pclk = devm_clk_get(dev, "pclk");
+	if (IS_ERR(dsi->pclk)) {
+		ret = PTR_ERR(dsi->pclk);
+		dev_err(dev, "Unable to get configuration clock: %d\n", ret);
+		goto err_pllref;
+	}
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_prepare_enable(dsi->cfg_clk);
+
+	clk_prepare_enable(dsi->pclk);
+	val = dsi_read(dsi, DSI_VERSION);
+	clk_disable_unprepare(dsi->pclk);
+
+	if (!IS_ERR(dsi->cfg_clk))
+		clk_disable_unprepare(dsi->cfg_clk);
+
+	dev_info(dev, "version number is 0x%08x\n", val);
+
+	ret = dw_mipi_dsi_register(drm, dsi);
+	if (ret) {
+		dev_err(dev, "Unable to register dsi: %d\n", ret);
+		goto err_pllref;
+	}
+	dev_set_drvdata(dev, dsi);
+
+	return mipi_dsi_host_register(&dsi->dsi_host);
+
+err_pllref:
+	clk_disable_unprepare(dsi->pllref_clk);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind);
+
+void dw_mipi_dsi_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct dw_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+	mipi_dsi_host_unregister(&dsi->dsi_host);
+	clk_disable_unprepare(dsi->pllref_clk);
+}
+EXPORT_SYMBOL_GPL(dw_mipi_dsi_unbind);
+
+MODULE_DESCRIPTION("Synopsys DesignWare MIPI DSI host controller driver");
+MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/drm/bridge/dw_mipi_dsi.h b/include/drm/bridge/dw_mipi_dsi.h
new file mode 100644
index 0000000..2e351e4
--- /dev/null
+++ b/include/drm/bridge/dw_mipi_dsi.h
@@ -0,0 +1,27 @@ 
+/*
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+ *
+ * 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 __DW_MIPI_DSI__
+#define __DW_MIPI_DSI__
+
+#include <drm/drmP.h>
+
+struct dw_mipi_dsi_plat_data {
+	unsigned int max_data_lanes;
+	enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
+					   struct drm_display_mode *mode);
+};
+
+int dw_mipi_dsi_get_encoder_pixel_format(struct drm_encoder *encoder);
+
+int dw_mipi_dsi_bind(struct device *dev, struct device *master,
+		     void *data, struct drm_encoder *encoder,
+		     const struct dw_mipi_dsi_plat_data *pdata);
+void dw_mipi_dsi_unbind(struct device *dev, struct device *master, void *data);
+#endif	/* __DW_MIPI_DSI__ */