#include <linux/of_device.h>
 #include <linux/phy/phy.h>
 #include <linux/pm_runtime.h>
+#include <linux/phy/phy.h>
 #include <linux/regmap.h>
 
 #include <video/mipi_display.h>
 #define BANDGAP_AND_BIAS_CONTROL                       0x20
 #define TERMINATION_RESISTER_CONTROL                   0x21
 #define AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY                0x22
+#define HS_RX_CONTROL_OF_LANE_CLK                      0x34
 #define HS_RX_CONTROL_OF_LANE_0                                0x44
+#define HS_RX_CONTROL_OF_LANE_1                                0x54
 #define HS_TX_CLOCK_LANE_REQUEST_STATE_TIME_CONTROL    0x60
 #define HS_TX_CLOCK_LANE_PREPARE_STATE_TIME_CONTROL    0x61
 #define HS_TX_CLOCK_LANE_HS_ZERO_STATE_TIME_CONTROL    0x62
 #define HS_TX_DATA_LANE_HS_ZERO_STATE_TIME_CONTROL     0x72
 #define HS_TX_DATA_LANE_TRAIL_STATE_TIME_CONTROL       0x73
 #define HS_TX_DATA_LANE_EXIT_STATE_TIME_CONTROL                0x74
+#define HS_RX_DATA_LANE_THS_SETTLE_CONTROL             0x75
+#define HS_RX_CONTROL_OF_LANE_2                                0x84
+#define HS_RX_CONTROL_OF_LANE_3                                0x94
 
 #define DW_MIPI_NEEDS_PHY_CFG_CLK      BIT(0)
 #define DW_MIPI_NEEDS_GRF_CLK          BIT(1)
 #define RK3399_TXRX_MASTERSLAVEZ       BIT(7)
 #define RK3399_TXRX_ENABLECLK          BIT(6)
 #define RK3399_TXRX_BASEDIR            BIT(5)
+#define RK3399_TXRX_SRC_SEL_ISP0       BIT(4)
+#define RK3399_TXRX_TURNREQUEST                GENMASK(3, 0)
 
 #define HIWORD_UPDATE(val, mask)       (val | (mask) << 16)
 
 #define to_dsi(nm)     container_of(nm, struct dw_mipi_dsi_rockchip, nm)
 
+enum {
+       DW_DSI_USAGE_IDLE,
+       DW_DSI_USAGE_DSI,
+       DW_DSI_USAGE_PHY,
+};
+
 enum {
        BANDGAP_97_07,
        BANDGAP_98_05,
        u32 lanecfg2_grf_reg;
        u32 lanecfg2;
 
+       int (*dphy_rx_init)(struct phy *phy);
+       int (*dphy_rx_power_on)(struct phy *phy);
+       int (*dphy_rx_power_off)(struct phy *phy);
+
        unsigned int flags;
        unsigned int max_data_lanes;
 };
        struct phy *phy;
        union phy_configure_opts phy_opts;
 
+       /* being a phy for other mipi hosts */
+       unsigned int usage_mode;
+       struct mutex usage_mutex;
+       struct phy *dphy;
+       struct phy_configure_opts_mipi_dphy dphy_config;
+
        unsigned int lane_mbps; /* per lane */
        u16 input_div;
        u16 feedback_div;
        struct device *second;
        int ret;
 
+       mutex_lock(&dsi->usage_mutex);
+
+       if (dsi->usage_mode != DW_DSI_USAGE_IDLE) {
+               DRM_DEV_ERROR(dsi->dev, "dsi controller already in use\n");
+               mutex_unlock(&dsi->usage_mutex);
+               return -EBUSY;
+       }
+
+       dsi->usage_mode = DW_DSI_USAGE_DSI;
+       mutex_unlock(&dsi->usage_mutex);
+
        ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_ops);
        if (ret) {
                DRM_DEV_ERROR(dsi->dev, "Failed to register component: %d\n",
 
        component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops);
 
+       mutex_lock(&dsi->usage_mutex);
+       dsi->usage_mode = DW_DSI_USAGE_IDLE;
+       mutex_unlock(&dsi->usage_mutex);
+
        return 0;
 }
 
        .detach = dw_mipi_dsi_rockchip_host_detach,
 };
 
+static int dw_mipi_dsi_rockchip_dphy_bind(struct device *dev,
+                                         struct device *master,
+                                         void *data)
+{
+       /*
+        * Nothing to do when used as a dphy.
+        * Just make the rest of Rockchip-DRM happy
+        * by being here.
+        */
+
+       return 0;
+}
+
+static void dw_mipi_dsi_rockchip_dphy_unbind(struct device *dev,
+                                            struct device *master,
+                                            void *data)
+{
+       /* Nothing to do when used as a dphy. */
+}
+
+static const struct component_ops dw_mipi_dsi_rockchip_dphy_ops = {
+       .bind   = dw_mipi_dsi_rockchip_dphy_bind,
+       .unbind = dw_mipi_dsi_rockchip_dphy_unbind,
+};
+
+static int dw_mipi_dsi_dphy_init(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+       int ret;
+
+       mutex_lock(&dsi->usage_mutex);
+
+       if (dsi->usage_mode != DW_DSI_USAGE_IDLE) {
+               DRM_DEV_ERROR(dsi->dev, "dsi controller already in use\n");
+               mutex_unlock(&dsi->usage_mutex);
+               return -EBUSY;
+       }
+
+       dsi->usage_mode = DW_DSI_USAGE_PHY;
+       mutex_unlock(&dsi->usage_mutex);
+
+       ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_dphy_ops);
+       if (ret < 0)
+               goto err_graph;
+
+       if (dsi->cdata->dphy_rx_init) {
+               ret = clk_prepare_enable(dsi->pclk);
+               if (ret < 0)
+                       goto err_init;
+
+               ret = clk_prepare_enable(dsi->grf_clk);
+               if (ret) {
+                       clk_disable_unprepare(dsi->pclk);
+                       goto err_init;
+               }
+
+               ret = dsi->cdata->dphy_rx_init(phy);
+               clk_disable_unprepare(dsi->grf_clk);
+               clk_disable_unprepare(dsi->pclk);
+               if (ret < 0)
+                       goto err_init;
+       }
+
+       return 0;
+
+err_init:
+       component_del(dsi->dev, &dw_mipi_dsi_rockchip_dphy_ops);
+err_graph:
+       mutex_lock(&dsi->usage_mutex);
+       dsi->usage_mode = DW_DSI_USAGE_IDLE;
+       mutex_unlock(&dsi->usage_mutex);
+
+       return ret;
+}
+
+static int dw_mipi_dsi_dphy_exit(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+
+       component_del(dsi->dev, &dw_mipi_dsi_rockchip_dphy_ops);
+
+       mutex_lock(&dsi->usage_mutex);
+       dsi->usage_mode = DW_DSI_USAGE_IDLE;
+       mutex_unlock(&dsi->usage_mutex);
+
+       return 0;
+}
+
+static int dw_mipi_dsi_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+       struct phy_configure_opts_mipi_dphy *config = &opts->mipi_dphy;
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+       int ret;
+
+       ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
+       if (ret)
+               return ret;
+
+       dsi->dphy_config = *config;
+       dsi->lane_mbps = div_u64(config->hs_clk_rate, 1000 * 1000 * 1);
+
+       return 0;
+}
+
+static int dw_mipi_dsi_dphy_power_on(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+       int i, ret;
+
+       DRM_DEV_DEBUG(dsi->dev, "lanes %d - data_rate_mbps %u\n",
+                     dsi->dphy_config.lanes, dsi->lane_mbps);
+
+       i = max_mbps_to_parameter(dsi->lane_mbps);
+       if (i < 0) {
+               DRM_DEV_ERROR(dsi->dev, "failed to get parameter for %dmbps clock\n",
+                             dsi->lane_mbps);
+               return i;
+       }
+
+       ret = pm_runtime_get_sync(dsi->dev);
+       if (ret < 0) {
+               DRM_DEV_ERROR(dsi->dev, "failed to enable device: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_prepare_enable(dsi->pclk);
+       if (ret) {
+               DRM_DEV_ERROR(dsi->dev, "Failed to enable pclk: %d\n", ret);
+               goto err_pclk;
+       }
+
+       ret = clk_prepare_enable(dsi->grf_clk);
+       if (ret) {
+               DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
+               goto err_grf_clk;
+       }
+
+       ret = clk_prepare_enable(dsi->phy_cfg_clk);
+       if (ret) {
+               DRM_DEV_ERROR(dsi->dev, "Failed to enable phy_cfg_clk: %d\n", ret);
+               goto err_phy_cfg_clk;
+       }
+
+       /* do soc-variant specific init */
+       if (dsi->cdata->dphy_rx_power_on) {
+               ret = dsi->cdata->dphy_rx_power_on(phy);
+               if (ret < 0) {
+                       DRM_DEV_ERROR(dsi->dev, "hardware-specific phy bringup failed: %d\n", ret);
+                       goto err_pwr_on;
+               }
+       }
+
+       /*
+        * Configure hsfreqrange according to frequency values
+        * Set clock lane and hsfreqrange by lane0(test code 0x44)
+        */
+       dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_CLK, 0);
+       dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_0,
+                             HSFREQRANGE_SEL(dppa_map[i].hsfreqrange));
+       dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_1, 0);
+       dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_2, 0);
+       dw_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_3, 0);
+
+       /* Normal operation */
+       dw_mipi_dsi_phy_write(dsi, 0x0, 0);
+
+       clk_disable_unprepare(dsi->phy_cfg_clk);
+       clk_disable_unprepare(dsi->grf_clk);
+
+       return ret;
+
+err_pwr_on:
+       clk_disable_unprepare(dsi->phy_cfg_clk);
+err_phy_cfg_clk:
+       clk_disable_unprepare(dsi->grf_clk);
+err_grf_clk:
+       clk_disable_unprepare(dsi->pclk);
+err_pclk:
+       pm_runtime_put(dsi->dev);
+       return ret;
+}
+
+static int dw_mipi_dsi_dphy_power_off(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+       int ret;
+
+       ret = clk_prepare_enable(dsi->grf_clk);
+       if (ret) {
+               DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
+               return ret;
+       }
+
+       if (dsi->cdata->dphy_rx_power_off) {
+               ret = dsi->cdata->dphy_rx_power_off(phy);
+               if (ret < 0)
+                       DRM_DEV_ERROR(dsi->dev, "hardware-specific phy shutdown failed: %d\n", ret);
+       }
+
+       clk_disable_unprepare(dsi->grf_clk);
+       clk_disable_unprepare(dsi->pclk);
+
+       pm_runtime_put(dsi->dev);
+
+       return ret;
+}
+
+static const struct phy_ops dw_mipi_dsi_dphy_ops = {
+       .configure      = dw_mipi_dsi_dphy_configure,
+       .power_on       = dw_mipi_dsi_dphy_power_on,
+       .power_off      = dw_mipi_dsi_dphy_power_off,
+       .init           = dw_mipi_dsi_dphy_init,
+       .exit           = dw_mipi_dsi_dphy_exit,
+};
+
 static int dw_mipi_dsi_rockchip_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
        struct device_node *np = dev->of_node;
        struct dw_mipi_dsi_rockchip *dsi;
+       struct phy_provider *phy_provider;
        struct resource *res;
        const struct rockchip_dw_dsi_chip_data *cdata =
                                of_device_get_match_data(dev);
        dsi->pdata.priv_data = dsi;
        platform_set_drvdata(pdev, dsi);
 
+       mutex_init(&dsi->usage_mutex);
+
+       dsi->dphy = devm_phy_create(dev, NULL, &dw_mipi_dsi_dphy_ops);
+       if (IS_ERR(dsi->dphy)) {
+               DRM_DEV_ERROR(&pdev->dev, "failed to create PHY\n");
+               return PTR_ERR(dsi->dphy);
+       }
+
+       phy_set_drvdata(dsi->dphy, dsi);
+       phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+       if (IS_ERR(phy_provider))
+               return PTR_ERR(phy_provider);
+
        dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
        if (IS_ERR(dsi->dmd)) {
                ret = PTR_ERR(dsi->dmd);
        { /* sentinel */ }
 };
 
+static int rk3399_dphy_tx1rx1_init(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+
+       /*
+        * Set TX1RX1 source to isp1.
+        * Assume ISP0 is supplied by the RX0 dphy.
+        */
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(0, RK3399_TXRX_SRC_SEL_ISP0));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(0, RK3399_TXRX_MASTERSLAVEZ));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(0, RK3399_TXRX_BASEDIR));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(0, RK3399_DSI1_ENABLE));
+
+       return 0;
+}
+
+static int rk3399_dphy_tx1rx1_power_on(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+
+       /* tester reset pulse */
+       dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_TESTCLR);
+       usleep_range(100, 150);
+
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(0, RK3399_TXRX_MASTERSLAVEZ));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(RK3399_TXRX_BASEDIR, RK3399_TXRX_BASEDIR));
+
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(0, RK3399_DSI1_FORCERXMODE));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(0, RK3399_DSI1_FORCETXSTOPMODE));
+
+       /* Disable lane turn around, which is ignored in receive mode */
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON24,
+                    HIWORD_UPDATE(0, RK3399_TXRX_TURNREQUEST));
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(RK3399_DSI1_TURNDISABLE,
+                                  RK3399_DSI1_TURNDISABLE));
+       usleep_range(100, 150);
+
+       dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR);
+       usleep_range(100, 150);
+
+       /* Enable dphy lanes */
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(GENMASK(dsi->dphy_config.lanes - 1, 0),
+                                  RK3399_DSI1_ENABLE));
+
+       usleep_range(100, 150);
+
+       return 0;
+}
+
+static int rk3399_dphy_tx1rx1_power_off(struct phy *phy)
+{
+       struct dw_mipi_dsi_rockchip *dsi = phy_get_drvdata(phy);
+
+       regmap_write(dsi->grf_regmap, RK3399_GRF_SOC_CON23,
+                    HIWORD_UPDATE(0, RK3399_DSI1_ENABLE));
+
+       return 0;
+}
+
 static const struct rockchip_dw_dsi_chip_data rk3399_chip_data[] = {
        {
                .reg = 0xff960000,
 
                .flags = DW_MIPI_NEEDS_PHY_CFG_CLK | DW_MIPI_NEEDS_GRF_CLK,
                .max_data_lanes = 4,
+
+               .dphy_rx_init = rk3399_dphy_tx1rx1_init,
+               .dphy_rx_power_on = rk3399_dphy_tx1rx1_power_on,
+               .dphy_rx_power_off = rk3399_dphy_tx1rx1_power_off,
        },
        { /* sentinel */ }
 };