Subject: [RFC PATCH v1 1/2] drm: rockchip: hdmi: add RK3229 HDMI support

From: [email-masked] (Yakir Yang)
Date: Thu, 7 Jan 2016 14:22:02 +0800

Thanks for "Kbuild test robot" reminding, I forget to update 
"mode_valid" function define in imx-hdmi side, would send new version out,

--

>> drivers/gpu/drm/imx/dw_hdmi-imx.c:181:2: warning: initialization from incompatible pointer type

      .mode_valid = imx6q_hdmi_mode_valid,
      ^
    drivers/gpu/drm/imx/dw_hdmi-imx.c:181:2: warning: (near initialization for 'imx6q_hdmi_drv_data.mode_valid')
    drivers/gpu/drm/imx/dw_hdmi-imx.c:189:2: warning: initialization from incompatible pointer type
      .mode_valid = imx6dl_hdmi_mode_valid,
      ^
    drivers/gpu/drm/imx/dw_hdmi-imx.c:189:2: warning: (near initialization for 'imx6dl_hdmi_drv_data.mode_valid')

Sorry,
- Yakir

On 01/07/2016 12:37 PM, Yakir Yang wrote:
> RK3229 integrate an DesignedWare HDMI2.0 controller and an INNO HDMI2.0 phy,
> the max output resolution is 4K.
>
> Signed-off-by: Yakir Yang <[email-masked]>
> ---
>   drivers/gpu/drm/bridge/dw-hdmi.c            |  33 ++-
>   drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 380 +++++++++++++++++++++++++---
>   drivers/gpu/drm/rockchip/dw_hdmi-rockchip.h | 137 ++++++++++
>   include/drm/bridge/dw_hdmi.h                |   5 +-
>   4 files changed, 516 insertions(+), 39 deletions(-)
>   create mode 100644 drivers/gpu/drm/rockchip/dw_hdmi-rockchip.h
>
> diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c
> index 6fbec99..60b1dcf 100644
> --- a/drivers/gpu/drm/bridge/dw-hdmi.c
> +++ b/drivers/gpu/drm/bridge/dw-hdmi.c
> @@ -735,10 +735,12 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
>   {
>   	unsigned res_idx;
>   	u8 val, msec;
> +	int ret;
>   	const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
>   	const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg;
>   	const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr;
>   	const struct dw_hdmi_phy_config *phy_config = pdata->phy_config;
> +	int mpixelclock = hdmi->hdmi_data.video_mode.mpixelclock;
>   
>   	if (prep)
>   		return -EINVAL;
> @@ -758,27 +760,38 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
>   		return -EINVAL;
>   	}
>   
> +	if (hdmi->plat_data->extphy_config) {
> +		/* gen2 tx power off */
> +		dw_hdmi_phy_gen2_txpwron(hdmi, 0);
> +		dw_hdmi_phy_gen2_pddq(hdmi, 1);
> +
> +		ret = hdmi->plat_data->extphy_config(hdmi->plat_data, res_idx,
> +						     mpixelclock);
> +		/* gen2 tx power on */
> +		dw_hdmi_phy_gen2_txpwron(hdmi, 1);
> +		dw_hdmi_phy_gen2_pddq(hdmi, 0);
> +
> +		return ret;
> +	}
> +
>   	/* PLL/MPLL Cfg - always match on final entry */
>   	for (; mpll_config->mpixelclock != ~0UL; mpll_config++)
> -		if (hdmi->hdmi_data.video_mode.mpixelclock <=
> -		    mpll_config->mpixelclock)
> +		if (mpixelclock <= mpll_config->mpixelclock)
>   			break;
>   
>   	for (; curr_ctrl->mpixelclock != ~0UL; curr_ctrl++)
> -		if (hdmi->hdmi_data.video_mode.mpixelclock <=
> -		    curr_ctrl->mpixelclock)
> +		if (mpixelclock <= curr_ctrl->mpixelclock)
>   			break;
>   
>   	for (; phy_config->mpixelclock != ~0UL; phy_config++)
> -		if (hdmi->hdmi_data.video_mode.mpixelclock <=
> -		    phy_config->mpixelclock)
> +		if (mpixelclock <= phy_config->mpixelclock)
>   			break;
>   
>   	if (mpll_config->mpixelclock == ~0UL ||
>   	    curr_ctrl->mpixelclock == ~0UL ||
>   	    phy_config->mpixelclock == ~0UL) {
>   		dev_err(hdmi->dev, "Pixel clock %d - unsupported by HDMI\n",
> -			hdmi->hdmi_data.video_mode.mpixelclock);
> +			mpixelclock);
>   		return -EINVAL;
>   	}
>   
> @@ -1476,14 +1489,16 @@ dw_hdmi_connector_mode_valid(struct drm_connector *connector,
>   {
>   	struct dw_hdmi *hdmi = container_of(connector,
>   					   struct dw_hdmi, connector);
> +	struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
>   	enum drm_mode_status mode_status = MODE_OK;
>   
>   	/* We don't support double-clocked modes */
>   	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
>   		return MODE_BAD;
>   
> -	if (hdmi->plat_data->mode_valid)
> -		mode_status = hdmi->plat_data->mode_valid(connector, mode);
> +	if (plat_data->mode_valid)
> +		mode_status = plat_data->mode_valid(plat_data, mode);
> +
>   
>   	return mode_status;
>   }
> diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> index c65ce8c..424d548 100644
> --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> @@ -7,6 +7,7 @@
>    * (at your option) any later version.
>    */
>   
> +#include <linux/clk.h>
>   #include <linux/module.h>
>   #include <linux/platform_device.h>
>   #include <linux/mfd/syscon.h>
> @@ -21,18 +22,135 @@
>   #include "rockchip_drm_drv.h"
>   #include "rockchip_drm_vop.h"
>   
> -#define GRF_SOC_CON6                    0x025c
> -#define HDMI_SEL_VOP_LIT                (1 << 4)
> +#include "dw_hdmi-rockchip.h"
>   
>   struct rockchip_hdmi {
>   	struct device *dev;
>   	struct regmap *regmap;
>   	struct drm_encoder encoder;
> +	struct dw_hdmi_plat_data plat_data;
> +
> +	void __iomem *extphy_regbase;
> +	struct clk *extphy_pclk;
>   };
>   
>   #define to_rockchip_hdmi(x)	container_of(x, struct rockchip_hdmi, x)
>   
> -static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
> +static const struct extphy_config_tab rk3229_extphy_cfg[] = {
> +	{ .mpixelclock = 165000000,
> +	  .pre_emphasis = 0, .slopeboost = 0, .clk_level = 4,
> +	  .data0_level = 4, 4, 4,
> +	},
> +
> +	{ .mpixelclock = 225000000,
> +	  .pre_emphasis = 0, .slopeboost = 0, .clk_level = 6,
> +	  .data0_level = 6, 6, 6,
> +	},
> +
> +	{ .mpixelclock = 340000000,
> +	  .pre_emphasis = 1, .slopeboost = 0, .clk_level = 6,
> +	  .data0_level = 10, 10, 10,
> +	},
> +
> +	{ .mpixelclock = 594000000,
> +	  .pre_emphasis = 1, .slopeboost = 0, .clk_level = 7,
> +	  .data0_level = 10, 10, 10,
> +	},
> +
> +	{ .mpixelclock = ~0UL},
> +};
> +
> +static const struct extphy_pll_config_tab rk3229_extphy_pll_cfg[] = {
> +	{
> +		.mpixelclock = 27000000, .param = {
> +			{ .pll_nd = 1, .pll_nf = 45,
> +			  .tmsd_divider_a = 3, 1, 1,
> +			  .pclk_divider_a = 1, 3, 3, 4,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 1, .ppll_nf = 40, .ppll_no = 8,
> +			},
> +			{ .pll_nd = 1, .pll_nf = 45,
> +			  .tmsd_divider_a = 0, 3, 3,
> +			  .pclk_divider_a = 1, 3, 3, 4,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 1, .ppll_nf = 40, .ppll_no = 8,
> +			},
> +		},
> +	}, {
> +		.mpixelclock = 59400000, .param = {
> +			{ .pll_nd = 2, .pll_nf = 99,
> +			  .tmsd_divider_a = 3, 1, 1,
> +			  .pclk_divider_a = 1, 3, 2, 2,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 1, .ppll_nf = 40, .ppll_no = 8,
> +			},
> +			{ .pll_nd = 2, .pll_nf = 99,
> +			  .tmsd_divider_a = 1, 1, 1,
> +			  .pclk_divider_a = 1, 3, 2, 2,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 1, .ppll_nf = 40, .ppll_no = 8,
> +			},
> +		},
> +	}, {
> +		.mpixelclock = 74250000, .param = {
> +			{ .pll_nd = 2, .pll_nf = 99,
> +			  .tmsd_divider_a = 1, 1, 1,
> +			  .pclk_divider_a = 1, 2, 2, 2,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 1, .ppll_nf = 40, .ppll_no = 8,
> +			},
> +			{ .pll_nd = 4, .pll_nf = 495,
> +			  .tmsd_divider_a = 1, 2, 2,
> +			  .pclk_divider_a = 1, 3, 3, 4,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 2, .ppll_nf = 40, .ppll_no = 4,
> +			},
> +		},
> +	}, {
> +		.mpixelclock = 148500000, .param = {
> +			{ .pll_nd = 2, .pll_nf = 99,
> +			  .tmsd_divider_a = 1, 0, 0,
> +			  .pclk_divider_a = 1, 2, 1, 1,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 2, .ppll_nf = 40, .ppll_no = 4,
> +			},
> +			{ .pll_nd = 4, .pll_nf = 495,
> +			  .tmsd_divider_a = 0, 2, 2,
> +			  .pclk_divider_a = 1, 3, 2, 2,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 4, .ppll_nf = 40, .ppll_no = 2,
> +			},
> +		},
> +	}, {
> +		.mpixelclock = 297000000, .param = {
> +			{ .pll_nd = 2, .pll_nf = 99,
> +			  .tmsd_divider_a = 0, 0, 0,
> +			  .pclk_divider_a = 1, 0, 1, 1,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 4, .ppll_nf = 40, .ppll_no = 2,
> +			},
> +			{ .pll_nd = 4, .pll_nf = 495,
> +			  .tmsd_divider_a = 1, 2, 0,
> +			  .pclk_divider_a = 1, 3, 1, 1,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 8, .ppll_nf = 40, .ppll_no = 1,
> +			},
> +		},
> +	}, {
> +		.mpixelclock = 594000000, .param = {
> +			{ .pll_nd = 1, .pll_nf = 99,
> +			  .tmsd_divider_a = 0, 2, 0,
> +			  .pclk_divider_a = 1, 0, 1, 1,
> +			  .vco_div_5 = 0,
> +			  .ppll_nd = 8, .ppll_nf = 40, .ppll_no = 1,
> +			},
> +		}
> +	}, {
> +		.mpixelclock = ~0UL,
> +	}
> +};
> +
> +static const struct dw_hdmi_mpll_config rk3288_mpll_cfg[] = {
>   	{
>   		27000000, {
>   			{ 0x00b3, 0x0000},
> @@ -112,7 +230,7 @@ static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
>   	}
>   };
>   
> -static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
> +static const struct dw_hdmi_curr_ctrl rk3288_cur_ctr[] = {
>   	/*      pixelclk    bpp8    bpp10   bpp12 */
>   	{
>   		40000000,  { 0x0018, 0x0018, 0x0018 },
> @@ -133,7 +251,7 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
>   	}
>   };
>   
> -static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
> +static const struct dw_hdmi_phy_config rk3288_phy_config[] = {
>   	/*pixelclk   symbol   term   vlev*/
>   	{ 74250000,  0x8009, 0x0004, 0x0272},
>   	{ 148500000, 0x802b, 0x0004, 0x028d},
> @@ -141,9 +259,158 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
>   	{ ~0UL,	     0x0000, 0x0000, 0x0000}
>   };
>   
> +static int hdmi_ext_phy_write(struct rockchip_hdmi *hdmi, unsigned short data,
> +			      unsigned char addr)
> +{
> +	writel_relaxed(data, hdmi->extphy_regbase + (addr) * 0x04);
> +	return 0;
> +}
> +
> +static unsigned int hdmi_phy_i2c_read(struct rockchip_hdmi *hdmi,
> +				      unsigned char addr)
> +{
> +	return readl_relaxed(hdmi->extphy_regbase + (addr) * 0x04);
> +}
> +
> +static int rk3229_extphy_config(struct dw_hdmi_plat_data *plat_data,
> +				  int res, int pixelclock)
> +{
> +	struct rockchip_hdmi *hdmi = to_rockchip_hdmi(plat_data);
> +	const struct extphy_pll_config_tab *mpll = rk3229_extphy_pll_cfg;
> +	const struct extphy_config_tab *ctrl = rk3229_extphy_cfg;
> +	unsigned long timeout;
> +	int i, stat;
> +
> +	/* Find out the extphy MPLL configure parameters */
> +	for (i = 0; mpll[i].mpixelclock != ~0UL; i++)
> +		if (pixelclock == mpll[i].mpixelclock)
> +			break;
> +	if (mpll[i].mpixelclock == ~0UL) {
> +		dev_err(hdmi->dev, "Extphy couldn't support %dHz\n", pixelclock);
> +		return -EINVAL;
> +	}
> +
> +	regmap_write(hdmi->regmap, RK3229_GRF_SOC_CON2,
> +		     RK3229_PLL_POWER_DOWN | RK3229_PLL_PDATA_DEN);
> +
> +	/*
> +	 * Configure external phy PLL registers.
> +	 */
> +	stat = ((mpll[i].param[res].pll_nf >> 1) & EXT_PHY_PLL_FB_BIT8_MASK) |
> +	       ((mpll[i].param[res].vco_div_5 & 1) << 5) |
> +	       (mpll[i].param[res].pll_nd & EXT_PHY_PLL_PRE_DIVIDER_MASK);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PLL_PRE_DIVIDER);
> +
> +	hdmi_ext_phy_write(hdmi, mpll[i].param[res].pll_nf, EXT_PHY_PLL_FB_DIVIDER);
> +
> +	stat = (mpll[i].param[res].pclk_divider_a & EXT_PHY_PCLK_DIVIDERA_MASK) |
> +	       ((mpll[i].param[res].pclk_divider_b & 3) << 5);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PCLK_DIVIDER1);
> +
> +	stat = (mpll[i].param[res].pclk_divider_d & EXT_PHY_PCLK_DIVIDERD_MASK) |
> +	       ((mpll[i].param[res].pclk_divider_c & 3) << 5);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PCLK_DIVIDER2);
> +
> +	stat = ((mpll[i].param[res].tmsd_divider_c & 3) << 4) |
> +	       ((mpll[i].param[res].tmsd_divider_a & 3) << 2) |
> +	       (mpll[i].param[res].tmsd_divider_b & 3);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_TMDSCLK_DIVIDER);
> +
> +	hdmi_ext_phy_write(hdmi, mpll[i].param[res].ppll_nf, EXT_PHY_PPLL_FB_DIVIDER);
> +
> +	if (mpll[i].param[res].ppll_no == 1) {
> +		hdmi_ext_phy_write(hdmi, 0, EXT_PHY_PPLL_POST_DIVIDER);
> +
> +		stat = 0x20 | mpll[i].param[res].ppll_nd;
> +		hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PPLL_PRE_DIVIDER);
> +	} else {
> +		stat = ((mpll[i].param[res].ppll_no / 2) - 1) << 4;
> +		hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PPLL_POST_DIVIDER);
> +
> +		stat = 0xe0 | mpll[i].param[res].ppll_nd;
> +		hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PPLL_PRE_DIVIDER);
> +	}
> +
> +
> +	/* Find out the extphy driver configure parameters */
> +	for (i = 0; ctrl[i].mpixelclock != ~0UL; i++)
> +		if (pixelclock <= ctrl[i].mpixelclock)
> +			break;
> +	if (ctrl[i].mpixelclock == ~0UL) {
> +		dev_err(hdmi->dev, "Extphy couldn't support %dHz\n", pixelclock);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * Configure the extphy driver registers.
> +	 */
> +	if (ctrl[i].slopeboost) {
> +		hdmi_ext_phy_write(hdmi, 0xff, EXT_PHY_SIGNAL_CTRL);
> +
> +		stat = (ctrl[i].slopeboost - 1) & 3;
> +		stat = (stat << 6) | (stat << 4) | (stat << 2) | stat;
> +		hdmi_ext_phy_write(hdmi, stat, EXT_PHY_SLOPEBOOST);
> +	} else
> +		hdmi_ext_phy_write(hdmi, 0x0f, EXT_PHY_SIGNAL_CTRL);
> +
> +	stat = ctrl[i].pre_emphasis & 3;
> +	stat = (stat << 4) | (stat << 2) | stat;
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PREEMPHASIS);
> +
> +	stat = ((ctrl[i].clk_level & 0xf) << 4) | (ctrl[i].data2_level & 0xf);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_LEVEL1);
> +
> +	stat = ((ctrl[i].data1_level & 0xf) << 4) | (ctrl[i].data0_level & 0xf);
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_LEVEL2);
> +
> +	hdmi_ext_phy_write(hdmi, 0x22, 0xf3);
> +
> +	stat = clk_get_rate(hdmi->extphy_pclk) / 100000;
> +	hdmi_ext_phy_write(hdmi, ((stat >> 8) & 0xff) | 0x80, EXT_PHY_TERM_CAL);
> +	hdmi_ext_phy_write(hdmi,  stat & 0xff, EXT_PHY_TERM_CAL_DIV_L);
> +
> +	if (pixelclock > 340000000)
> +		stat = EXT_PHY_AUTO_R100_OHMS;
> +	else if (pixelclock > 200000000)
> +		stat = EXT_PHY_AUTO_R50_OHMS;
> +	else
> +		stat = EXT_PHY_AUTO_ROPEN_CIRCUIT;
> +	hdmi_ext_phy_write(hdmi, stat | 0x20, EXT_PHY_TERM_RESIS_AUTO);
> +	hdmi_ext_phy_write(hdmi, (stat >> 8) & 0xff, EXT_PHY_TERM_CAL);
> +
> +	stat = (pixelclock > 200000000) ? 0 : 0x11;
> +	hdmi_ext_phy_write(hdmi, stat, EXT_PHY_PLL_BW);
> +	hdmi_ext_phy_write(hdmi, 0x27, EXT_PHY_PPLL_BW);
> +
> +	regmap_write(hdmi->regmap, RK3229_GRF_SOC_CON2, RK3229_PLL_POWER_UP);
> +
> +	/* Detect whether PLL is lock or not */
> +	timeout = jiffies + msecs_to_jiffies(100);
> +	while (!time_after(jiffies, timeout)) {
> +		usleep_range(1000, 2000);
> +		stat = hdmi_phy_i2c_read(hdmi, EXT_PHY_PPLL_POST_DIVIDER);
> +		if (stat & EXT_PHY_PPLL_LOCK_STATUS_MASK)
> +			break;
> +	}
> +
> +	regmap_write(hdmi->regmap, RK3229_GRF_SOC_CON2, RK3229_PLL_PDATA_EN);
> +
> +	if ((stat & EXT_PHY_PPLL_LOCK_STATUS_MASK) == 0) {
> +		dev_err(hdmi->dev, "EXT PHY PLL not locked\n");
> +		return -EBUSY;
> +	}
> +
> +	return 0;
> +}
> +
>   static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
>   {
>   	struct device_node *np = hdmi->dev->of_node;
> +	struct platform_device *pdev;
> +	struct resource *iores;
> +	int ret;
> +
> +	pdev = container_of(hdmi->dev, struct platform_device, dev);
>   
>   	hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
>   	if (IS_ERR(hdmi->regmap)) {
> @@ -151,24 +418,62 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
>   		return PTR_ERR(hdmi->regmap);
>   	}
>   
> +	if (hdmi->plat_data.dev_type == RK3229_HDMI) {
> +		iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +		if (!iores)
> +			return -ENXIO;
> +
> +		hdmi->extphy_regbase = devm_ioremap_resource(hdmi->dev, iores);
> +		if (IS_ERR(hdmi->extphy_regbase)) {
> +			dev_err(hdmi->dev, "failed to map extphy regbase\n");
> +			return PTR_ERR(hdmi->extphy_regbase);
> +		}
> +
> +		hdmi->extphy_pclk = devm_clk_get(hdmi->dev, "extphy");
> +		if (IS_ERR(hdmi->extphy_pclk)) {
> +			dev_err(hdmi->dev, "failed to get extphy clock\n");
> +			return PTR_ERR(hdmi->extphy_pclk);
> +		}
> +
> +		ret = clk_prepare_enable(hdmi->extphy_pclk);
> +		if (ret) {
> +			dev_err(hdmi->dev, "failed to enable extphy clk: %d\n",
> +				ret);
> +			return ret;
> +		}
> +
> +		regmap_write(hdmi->regmap, RK3229_GRF_SOC_CON6,
> +			     RK3229_IO_3V_DOMAIN);
> +
> +		regmap_write(hdmi->regmap, RK3229_GRF_SOC_CON2,
> +			     RK3229_DDC_MASK_EN);
> +	}
> +
>   	return 0;
>   }
>   
>   static enum drm_mode_status
> -dw_hdmi_rockchip_mode_valid(struct drm_connector *connector,
> +dw_hdmi_rockchip_mode_valid(const struct dw_hdmi_plat_data *plat_data,
>   			    struct drm_display_mode *mode)
>   {
> -	const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
>   	int pclk = mode->clock * 1000;
>   	bool valid = false;
>   	int i;
>   
> -	for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
> -		if (pclk == mpll_cfg[i].mpixelclock) {
> -			valid = true;
> -			break;
> +	if (plat_data->dev_type == RK3288_HDMI)
> +		for (i = 0; rk3288_mpll_cfg[i].mpixelclock != (~0UL); i++)
> +			if (pclk == rk3288_mpll_cfg[i].mpixelclock) {
> +				valid = true;
> +				break;
> +			}
> +
> +	if (plat_data->dev_type == RK3229_HDMI)
> +		for (i = 0; rk3229_extphy_pll_cfg[i].mpixelclock != ~0UL; i++) {
> +			if (pclk == rk3229_extphy_pll_cfg[i].mpixelclock) {
> +				valid = true;
> +				break;
> +			}
>   		}
> -	}
>   
>   	return (valid) ? MODE_OK : MODE_BAD;
>   }
> @@ -198,21 +503,29 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
>   static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
>   {
>   	struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
> +	int out_mode = ROCKCHIP_OUT_MODE_AAAA;
>   	u32 val;
>   	int mux;
>   
> +	if (hdmi->plat_data.dev_type == RK3229_HDMI)
> +		out_mode = ROCKCHIP_OUT_MODE_P888;
> +
>   	rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA,
> -				      ROCKCHIP_OUT_MODE_AAAA);
> +				      out_mode);
>   
> -	mux = rockchip_drm_encoder_get_mux_id(hdmi->dev->of_node, encoder);
> -	if (mux)
> -		val = HDMI_SEL_VOP_LIT | (HDMI_SEL_VOP_LIT << 16);
> -	else
> -		val = HDMI_SEL_VOP_LIT << 16;
> +	if (hdmi->plat_data.dev_type == RK3288_HDMI) {
> +		mux = rockchip_drm_encoder_get_mux_id(hdmi->dev->of_node,
> +						      encoder);
> +		if (mux)
> +			val = RK3288_HDMI_SEL_VOP_LIT |
> +			      (RK3288_HDMI_SEL_VOP_LIT << 16);
> +		else
> +			val = RK3288_HDMI_SEL_VOP_LIT << 16;
>   
> -	regmap_write(hdmi->regmap, GRF_SOC_CON6, val);
> -	dev_dbg(hdmi->dev, "vop %s output to hdmi\n",
> -		(mux) ? "LIT" : "BIG");
> +		regmap_write(hdmi->regmap, RK3288_GRF_SOC_CON6, val);
> +
> +		dev_dbg(hdmi->dev, "vop %s output to hdmi\n", (mux) ? "LIT" : "BIG");
> +	}
>   }
>   
>   static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
> @@ -222,17 +535,26 @@ static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_fun
>   	.disable    = dw_hdmi_rockchip_encoder_disable,
>   };
>   
> -static const struct dw_hdmi_plat_data rockchip_hdmi_drv_data = {
> +static const struct dw_hdmi_plat_data rk3288_hdmi_drv_data = {
>   	.mode_valid = dw_hdmi_rockchip_mode_valid,
> -	.mpll_cfg   = rockchip_mpll_cfg,
> -	.cur_ctr    = rockchip_cur_ctr,
> -	.phy_config = rockchip_phy_config,
> +	.mpll_cfg   = rk3288_mpll_cfg,
> +	.cur_ctr    = rk3288_cur_ctr,
> +	.phy_config = rk3288_phy_config,
>   	.dev_type   = RK3288_HDMI,
>   };
>   
> +static const struct dw_hdmi_plat_data rk3229_hdmi_drv_data = {
> +	.mode_valid = dw_hdmi_rockchip_mode_valid,
> +	.extphy_config = rk3229_extphy_config,
> +	.dev_type   = RK3229_HDMI,
> +};
> +
>   static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
>   	{ .compatible = "rockchip,rk3288-dw-hdmi",
> -	  .data = &rockchip_hdmi_drv_data
> +	  .data = &rk3288_hdmi_drv_data
> +	},
> +	{ .compatible = "rockchip,rk3229-dw-hdmi",
> +	  .data = &rk3229_hdmi_drv_data
>   	},
>   	{},
>   };
> @@ -242,7 +564,6 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
>   				 void *data)
>   {
>   	struct platform_device *pdev = to_platform_device(dev);
> -	const struct dw_hdmi_plat_data *plat_data;
>   	const struct of_device_id *match;
>   	struct drm_device *drm = data;
>   	struct drm_encoder *encoder;
> @@ -259,7 +580,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
>   		return -ENOMEM;
>   
>   	match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
> -	plat_data = match->data;
> +	hdmi->plat_data = *(struct dw_hdmi_plat_data *)(match->data);
>   	hdmi->dev = &pdev->dev;
>   	encoder = &hdmi->encoder;
>   
> @@ -293,7 +614,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
>   	drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs,
>   			 DRM_MODE_ENCODER_TMDS, NULL);
>   
> -	return dw_hdmi_bind(dev, master, data, encoder, iores, irq, plat_data);
> +	return dw_hdmi_bind(dev, master, data, encoder, iores, irq,
> +			    &hdmi->plat_data);
>   }
>   
>   static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
> diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.h b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.h
> new file mode 100644
> index 0000000..aca2543
> --- /dev/null
> +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.h
> @@ -0,0 +1,137 @@
> +#ifndef __DW_HDMI_ROCKCHIP__
> +#define __DW_HDMI_ROCKCHIP__
> +
> +struct extphy_config_tab {
> +	u32 mpixelclock;
> +	int pre_emphasis;
> +	int slopeboost;
> +	int clk_level;
> +	int data0_level;
> +	int data1_level;
> +	int data2_level;
> +};
> +
> +struct extphy_pll_config_tab {
> +	unsigned long mpixelclock;
> +	struct {
> +		u8	pll_nd;
> +		u16	pll_nf;
> +		u8	tmsd_divider_a;
> +		u8	tmsd_divider_b;
> +		u8	tmsd_divider_c;
> +		u8	pclk_divider_a;
> +		u8	pclk_divider_b;
> +		u8	pclk_divider_c;
> +		u8	pclk_divider_d;
> +		u8	vco_div_5;
> +		u8	ppll_nd;
> +		u8	ppll_nf;
> +		u8	ppll_no;
> +	} param[DW_HDMI_RES_MAX];
> +};
> +
> +#define RK3288_GRF_SOC_CON6			0x025c
> +#define RK3288_HDMI_SEL_VOP_LIT			(1 << 4)
> +
> +#define RK3229_GRF_SOC_CON6			0x0418
> +#define RK3229_IO_3V_DOMAIN			((7 << 4) | (7 << (4 + 16)))
> +
> +#define RK3229_GRF_SOC_CON2			0x0408
> +#define RK3229_DDC_MASK_EN			((3 << 13) | (3 << (13 + 16)))
> +
> +#define RK3229_PLL_POWER_DOWN			(BIT(12) | BIT(12 + 16))
> +#define RK3229_PLL_POWER_UP			BIT(12 + 16)
> +#define RK3229_PLL_PDATA_DEN			BIT(11 + 16)
> +#define RK3229_PLL_PDATA_EN			(BIT(11) | BIT(11 + 16))
> +
> +/* PHY Defined for RK322X */
> +#define EXT_PHY_CONTROL				0
> +#define EXT_PHY_ANALOG_RESET_MASK		0x80
> +#define EXT_PHY_DIGITAL_RESET_MASK		0x40
> +#define EXT_PHY_PCLK_INVERT_MASK		0x08
> +#define EXT_PHY_PREPCLK_INVERT_MASK		0x04
> +#define EXT_PHY_TMDSCLK_INVERT_MASK		0x02
> +#define EXT_PHY_SRC_SELECT_MASK			0x01
> +
> +#define EXT_PHY_TERM_CAL			0x03
> +#define EXT_PHY_TERM_CAL_EN_MASK		0x80
> +#define EXT_PHY_TERM_CAL_DIV_H_MASK		0x7f
> +
> +#define EXT_PHY_TERM_CAL_DIV_L			0x04
> +
> +#define EXT_PHY_PLL_PRE_DIVIDER			0xe2
> +#define EXT_PHY_PLL_FB_BIT8_MASK		0x80
> +#define EXT_PHY_PLL_PCLK_DIV5_EN_MASK		0x20
> +#define EXT_PHY_PLL_PRE_DIVIDER_MASK		0x1f
> +
> +#define EXT_PHY_PLL_FB_DIVIDER			0xe3
> +
> +#define EXT_PHY_PCLK_DIVIDER1			0xe4
> +#define EXT_PHY_PCLK_DIVIDERB_MASK		0x60
> +#define EXT_PHY_PCLK_DIVIDERA_MASK		0x1f
> +
> +#define EXT_PHY_PCLK_DIVIDER2			0xe5
> +#define EXT_PHY_PCLK_DIVIDERC_MASK		0x60
> +#define EXT_PHY_PCLK_DIVIDERD_MASK		0x1f
> +
> +#define EXT_PHY_TMDSCLK_DIVIDER			0xe6
> +#define EXT_PHY_TMDSCLK_DIVIDERC_MASK		0x30
> +#define EXT_PHY_TMDSCLK_DIVIDERA_MASK		0x0c
> +#define EXT_PHY_TMDSCLK_DIVIDERB_MASK		0x03
> +
> +#define EXT_PHY_PLL_BW				0xe7
> +
> +#define EXT_PHY_PPLL_PRE_DIVIDER		0xe9
> +#define EXT_PHY_PPLL_ENABLE_MASK		0xc0
> +#define EXT_PHY_PPLL_PRE_DIVIDER_MASK		0x1f
> +
> +#define EXT_PHY_PPLL_FB_DIVIDER			0xea
> +
> +#define EXT_PHY_PPLL_POST_DIVIDER		0xeb
> +#define EXT_PHY_PPLL_FB_DIVIDER_BIT8_MASK	0x80
> +#define EXT_PHY_PPLL_POST_DIVIDER_MASK		0x30
> +#define EXT_PHY_PPLL_LOCK_STATUS_MASK		0x01
> +
> +#define EXT_PHY_PPLL_BW				0xec
> +
> +#define EXT_PHY_SIGNAL_CTRL			0xee
> +#define EXT_PHY_TRANSITION_CLK_EN_MASK		0x80
> +#define EXT_PHY_TRANSITION_D0_EN_MASK		0x40
> +#define EXT_PHY_TRANSITION_D1_EN_MASK		0x20
> +#define EXT_PHY_TRANSITION_D2_EN_MASK		0x10
> +#define EXT_PHY_LEVEL_CLK_EN_MASK		0x08
> +#define EXT_PHY_LEVEL_D0_EN_MASK		0x04
> +#define EXT_PHY_LEVEL_D1_EN_MASK		0x02
> +#define EXT_PHY_LEVEL_D2_EN_MASK		0x01
> +
> +#define EXT_PHY_SLOPEBOOST			0xef
> +#define EXT_PHY_SLOPEBOOST_CLK_MASK		0x03
> +#define EXT_PHY_SLOPEBOOST_D0_MASK		0x0c
> +#define EXT_PHY_SLOPEBOOST_D1_MASK		0x30
> +#define EXT_PHY_SLOPEBOOST_D2_MASK		0xc0
> +
> +#define EXT_PHY_PREEMPHASIS			0xf0
> +#define EXT_PHY_PREEMPHASIS_D0_MASK		0x03
> +#define EXT_PHY_PREEMPHASIS_D1_MASK		0x0c
> +#define EXT_PHY_PREEMPHASIS_D2_MASK		0x30
> +
> +#define EXT_PHY_LEVEL1				0xf1
> +#define EXT_PHY_LEVEL_CLK_MASK			0xf0
> +#define EXT_PHY_LEVEL_D2_MASK			0x0f
> +
> +#define EXT_PHY_LEVEL2				0xf2
> +#define EXT_PHY_LEVEL_D1_MASK			0xf0
> +#define EXT_PHY_LEVEL_D0_MASK			0x0f
> +
> +#define EXT_PHY_TERM_RESIS_AUTO			0xf4
> +#define EXT_PHY_AUTO_R50_OHMS			0
> +#define EXT_PHY_AUTO_R75_OHMS			(1 << 2)
> +#define EXT_PHY_AUTO_R100_OHMS			(2 << 2)
> +#define EXT_PHY_AUTO_ROPEN_CIRCUIT		(3 << 2)
> +
> +#define EXT_PHY_TERM_RESIS_MANUAL_CLK		0xfb
> +#define EXT_PHY_TERM_RESIS_MANUAL_D2		0xfc
> +#define EXT_PHY_TERM_RESIS_MANUAL_D1		0xfd
> +#define EXT_PHY_TERM_RESIS_MANUAL_D0		0xfe
> +
> +#endif /* __DW_HDMI_ROCKCHIP__ */
> diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
> index bae79f3..44084e8 100644
> --- a/include/drm/bridge/dw_hdmi.h
> +++ b/include/drm/bridge/dw_hdmi.h
> @@ -24,6 +24,7 @@ enum {
>   enum dw_hdmi_devtype {
>   	IMX6Q_HDMI,
>   	IMX6DL_HDMI,
> +	RK3229_HDMI,
>   	RK3288_HDMI,
>   };
>   
> @@ -52,8 +53,10 @@ struct dw_hdmi_plat_data {
>   	const struct dw_hdmi_mpll_config *mpll_cfg;
>   	const struct dw_hdmi_curr_ctrl *cur_ctr;
>   	const struct dw_hdmi_phy_config *phy_config;
> -	enum drm_mode_status (*mode_valid)(struct drm_connector *connector,
> +	enum drm_mode_status (*mode_valid)(const struct dw_hdmi_plat_data *plat_data,
>   					   struct drm_display_mode *mode);
> +	int (*extphy_config)(struct dw_hdmi_plat_data *plat_data,
> +			     int bpp, int pixelclock);
>   };
>   
>   void dw_hdmi_unbind(struct device *dev, struct device *master, void *data);