FROMLIST: drm: bridge: dw-hdmi: Add support for custom PHY configuration
authorKieran Bingham <kieran.bingham+renesas@ideasonboard.com>
Fri, 3 Mar 2017 17:20:04 +0000 (19:20 +0200)
committerZheng Yang <zhengyang@rock-chips.com>
Tue, 2 May 2017 07:17:51 +0000 (15:17 +0800)
The DWC HDMI TX controller interfaces with a companion PHY. While
Synopsys provides multiple standard PHYs, SoC vendors can also integrate
a custom PHY.

Modularize PHY configuration to support vendor PHYs through platform
data. The existing PHY configuration code was originally written to
support the DWC HDMI 3D TX PHY, and seems to be compatible with the DWC
MLP PHY. The HDMI 2.0 PHY will require a separate configuration
function.

Signed-off-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Tested-by: Neil Armstrong <narmstrong@baylibre.com>
Reviewed-by: Jose Abreu <Jose.Abreu@synopsys.com>
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Link: http://patchwork.freedesktop.org/patch/msgid/20170303172007.26541-8-laurent.pinchart+renesas@ideasonboard.com
Change-Id: I7527e77fd8679434379161a6bf3daa1639d081b8
Signed-off-by: Zheng Yang <zhengyang@rock-chips.com>
(am from https://patchwork.kernel.org/patch/9603303/)

drivers/gpu/drm/bridge/dw-hdmi.c
include/drm/bridge/dw_hdmi.h

index 135cdbd8dfaf7e07894e410de3196a9a8b798a18..15155fa25325e5bdc970e9b7a4cff3041da28d3e 100644 (file)
@@ -185,6 +185,9 @@ struct dw_hdmi_phy_data {
        const char *name;
        unsigned int gen;
        bool has_svsret;
+       int (*configure)(struct dw_hdmi *hdmi,
+                        const struct dw_hdmi_plat_data *pdata,
+                        unsigned long mpixelclock);
 };
 
 struct dw_hdmi {
@@ -1124,8 +1127,8 @@ static bool hdmi_phy_wait_i2c_done(struct dw_hdmi *hdmi, int msec)
        return true;
 }
 
-static void hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
-                                unsigned char addr)
+void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
+                          unsigned char addr)
 {
        hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0);
        hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
@@ -1137,6 +1140,7 @@ static void hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
                    HDMI_PHY_I2CM_OPERATION_ADDR);
        hdmi_phy_wait_i2c_done(hdmi, 1000);
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write);
 
 static int hdmi_phy_i2c_read(struct dw_hdmi *hdmi, unsigned char addr)
 {
@@ -1274,45 +1278,84 @@ static int dw_hdmi_phy_power_on(struct dw_hdmi *hdmi)
        return 0;
 }
 
-static int hdmi_phy_configure(struct dw_hdmi *hdmi)
+/*
+ * PHY configuration function for the DWC HDMI 3D TX PHY. Based on the available
+ * information the DWC MHL PHY has the same register layout and is thus also
+ * supported by this function.
+ */
+static int hdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi,
+               const struct dw_hdmi_plat_data *pdata,
+               unsigned long mpixelclock)
 {
-       const struct dw_hdmi_phy_data *phy = hdmi->phy.data;
-       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;
-       u8 tmds_cfg;
 
        /* 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 <= mpll_config->mpixelclock)
                        break;
 
        for (; phy_config->mpixelclock != ~0UL; phy_config++)
-               if (hdmi->hdmi_data.video_mode.mpixelclock <=
-                   phy_config->mpixelclock)
+               if (mpixelclock <= mpll_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);
+           phy_config->mpixelclock == ~0UL)
                return -EINVAL;
-       }
+
+       /*
+        * RK3399 mpll clock source is vpll, also is vop clock source.
+        * vpll rate is twice of mpixelclock in YCBCR420 mode, we need
+        * to enable mpll pre-divider.
+        */
+       if (hdmi->hdmi_data.enc_in_format == YCBCR420 &&
+           (hdmi->dev_type == RK3399_HDMI || hdmi->dev_type == RK3368_HDMI))
+               dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[0].cpce | 4,
+                                     HDMI_3D_TX_PHY_CPCE_CTRL);
+       else
+               dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[0].cpce,
+                                     HDMI_3D_TX_PHY_CPCE_CTRL);
+       dw_hdmi_phy_i2c_write(hdmi, mpll_config->res[0].gmp,
+                             HDMI_3D_TX_PHY_GMPCTRL);
+       dw_hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[0],
+                             HDMI_3D_TX_PHY_CURRCTRL);
+
+       dw_hdmi_phy_i2c_write(hdmi, 0, HDMI_3D_TX_PHY_PLLPHBYCTRL);
+       dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK,
+                             HDMI_3D_TX_PHY_MSM_CTRL);
+
+       dw_hdmi_phy_i2c_write(hdmi, phy_config->term, HDMI_3D_TX_PHY_TXTERM);
+       dw_hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr,
+                             HDMI_3D_TX_PHY_CKSYMTXCTRL);
+       dw_hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr,
+                             HDMI_3D_TX_PHY_VLEVCTRL);
+
+       /* Override and disable clock termination. */
+       dw_hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE,
+                             HDMI_3D_TX_PHY_CKCALCTRL);
+       return 0;
+}
+
+static int hdmi_phy_configure(struct dw_hdmi *hdmi)
+{
+       const struct dw_hdmi_phy_data *phy = hdmi->phy.data;
+       const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+       unsigned long mpixelclock = hdmi->hdmi_data.video_mode.mpixelclock;
+       u8 tmds_cfg;
+       int ret;
 
        dw_hdmi_phy_power_off(hdmi);
 
        /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
        if (hdmi->connector.scdc_present) {
                drm_scdc_readb(hdmi->ddc, SCDC_TMDS_CONFIG, &tmds_cfg);
-               if (mpll_config->mpixelclock > 340000000)
+               if (mpixelclock > 340000000)
                        tmds_cfg |= 2;
                else
                        tmds_cfg &= 0x1;
@@ -1333,39 +1376,20 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
        hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
                    HDMI_PHY_I2CM_SLAVE_ADDR);
        hdmi_phy_test_clear(hdmi, 0);
-       /*
-        * RK3399 mpll clock source is vpll, also is vop clock source.
-        * vpll rate is twice of mpixelclock in YCBCR420 mode, we need
-        * to enable mpll pre-divider.
-        */
-       if (hdmi->hdmi_data.enc_in_format == YCBCR420 &&
-           (hdmi->dev_type == RK3399_HDMI || hdmi->dev_type == RK3368_HDMI))
-               hdmi_phy_i2c_write(hdmi, mpll_config->res[0].cpce | 4,
-                                  HDMI_3D_TX_PHY_CPCE_CTRL);
-       else
-               hdmi_phy_i2c_write(hdmi, mpll_config->res[0].cpce,
-                                  HDMI_3D_TX_PHY_CPCE_CTRL);
-       hdmi_phy_i2c_write(hdmi, mpll_config->res[0].gmp,
-                          HDMI_3D_TX_PHY_GMPCTRL);
-       hdmi_phy_i2c_write(hdmi, curr_ctrl->curr[0],
-                          HDMI_3D_TX_PHY_CURRCTRL);
-
-       hdmi_phy_i2c_write(hdmi, 0, HDMI_3D_TX_PHY_PLLPHBYCTRL);
-       hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_MSM_CTRL_CKO_SEL_FB_CLK,
-                          HDMI_3D_TX_PHY_MSM_CTRL);
-
-       hdmi_phy_i2c_write(hdmi, phy_config->term, HDMI_3D_TX_PHY_TXTERM);
-       hdmi_phy_i2c_write(hdmi, phy_config->sym_ctr,
-                          HDMI_3D_TX_PHY_CKSYMTXCTRL);
-       hdmi_phy_i2c_write(hdmi, phy_config->vlev_ctr,
-                          HDMI_3D_TX_PHY_VLEVCTRL);
 
-       /* Override and disable clock termination. */
-       hdmi_phy_i2c_write(hdmi, HDMI_3D_TX_PHY_CKCALCTRL_OVERRIDE,
-                          HDMI_3D_TX_PHY_CKCALCTRL);
+       /* Write to the PHY as configured by the platform */
+       if (pdata->configure_phy)
+               ret = pdata->configure_phy(hdmi, pdata, mpixelclock);
+       else
+               ret = phy->configure(hdmi, pdata, mpixelclock);
+       if (ret) {
+               dev_err(hdmi->dev, "PHY configuration failed (clock %lu)\n",
+                       mpixelclock);
+               return ret;
+       }
 
        /* Wait for resuming transmission of TMDS clock and data */
-       if (mpll_config->mpixelclock > 340000000)
+       if (mpixelclock > 340000000)
                msleep(100);
 
        return dw_hdmi_phy_power_on(hdmi);
@@ -2306,24 +2330,31 @@ static const struct dw_hdmi_phy_data dw_hdmi_phys[] = {
                .name = "DWC MHL PHY + HEAC PHY",
                .gen = 2,
                .has_svsret = true,
+               .configure = hdmi_phy_configure_dwc_hdmi_3d_tx,
        }, {
                .type = DW_HDMI_PHY_DWC_MHL_PHY,
                .name = "DWC MHL PHY",
                .gen = 2,
                .has_svsret = true,
+               .configure = hdmi_phy_configure_dwc_hdmi_3d_tx,
        }, {
                .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY_HEAC,
                .name = "DWC HDMI 3D TX PHY + HEAC PHY",
                .gen = 2,
+               .configure = hdmi_phy_configure_dwc_hdmi_3d_tx,
        }, {
                .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY,
                .name = "DWC HDMI 3D TX PHY",
                .gen = 2,
+               .configure = hdmi_phy_configure_dwc_hdmi_3d_tx,
        }, {
                .type = DW_HDMI_PHY_DWC_HDMI20_TX_PHY,
                .name = "DWC HDMI 2.0 TX PHY",
                .gen = 2,
                .has_svsret = true,
+       }, {
+               .type = DW_HDMI_PHY_VENDOR_PHY,
+               .name = "Vendor PHY",
        }
 };
 
@@ -2354,6 +2385,14 @@ static int dw_hdmi_detect_phy(struct dw_hdmi *hdmi)
                        hdmi->phy.ops = &dw_hdmi_synopsys_phy_ops;
                        hdmi->phy.name = dw_hdmi_phys[i].name;
                        hdmi->phy.data = (void *)&dw_hdmi_phys[i];
+
+                       if (!dw_hdmi_phys[i].configure &&
+                           !hdmi->plat_data->configure_phy) {
+                               dev_err(hdmi->dev, "%s requires platform support\n",
+                                       hdmi->phy.name);
+                               return -ENODEV;
+                       }
+
                        return 0;
                }
        }
@@ -2521,7 +2560,7 @@ dw_hdmi_phy_write(struct file *file, const char __user *buf,
        }
        dev_info(hdmi->dev, "/*******hdmi phy register config******/");
        dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
-       hdmi_phy_i2c_write(hdmi, val, reg);
+       dw_hdmi_phy_i2c_write(hdmi, val, reg);
        return count;
 }
 
index 564f82c3ae95dfa7f051932229e459b4506cb0bf..dda259da124acc31b3d5ca1a6e4798e28a3d33c1 100644 (file)
@@ -87,6 +87,9 @@ 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;
+       int (*configure_phy)(struct dw_hdmi *hdmi,
+                            const struct dw_hdmi_plat_data *pdata,
+                            unsigned long mpixelclock);
 };
 
 static inline bool is_rockchip(enum dw_hdmi_devtype dev_type)
@@ -113,4 +116,8 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate);
 void dw_hdmi_audio_enable(struct dw_hdmi *hdmi);
 void dw_hdmi_audio_disable(struct dw_hdmi *hdmi);
 
+/* PHY configuration */
+void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
+                          unsigned char addr);
+
 #endif /* __IMX_HDMI_H__ */