[PATCH 3/6] drm/rockchip: dsi: add ability to work as a phy instead of full dsi
Helen Koike
helen.koike at collabora.com
Mon Feb 15 09:33:19 EST 2021
On 2/10/21 8:10 AM, Heiko Stuebner wrote:
> From: Heiko Stuebner <heiko.stuebner at theobroma-systems.com>
>
> SoCs like the rk3288 and rk3399 have 3 mipi dphys on them. One is TX-
> only, one is RX-only and one can be configured to do either TX or RX.
>
> The RX phy is statically connected to the first Image Signal Processor,
> the TX phy is statically connected to the first DSI controller and
> the TXRX phy is connected to both the second DSI controller as well
> as the second ISP.
>
> The RX dphy is controlled externally through registers in the "General
> Register Files", while the other two are controlled through the
> "Configuration and Test Interface" inside their DSI controller's
> io-memory area.
>
> The Rockchip dw-dsi controller already controls these dphys for the
> TX case in the driver, but when we want to also allow configuration
> for RX to the ISP from the media subsystem we need to expose phy-
> functionality instead.
>
> So add a bit of infrastructure to allow the dsi driver to work as a
> phy and make sure it can be only one or the other at a time.
>
> Similarly as the dsi-controller will be part of the drm-graph when
> active, add an empty component to the drm-graph when in phy-mode
> to make the rest of the drm-graph not wait for it.
>
> Signed-off-by: Heiko Stuebner <heiko.stuebner at theobroma-systems.com>
> Tested-by: Sebastian Fricke <sebastian.fricke at posteo.net>
> ---
> drivers/gpu/drm/rockchip/Kconfig | 2 +
> .../gpu/drm/rockchip/dw-mipi-dsi-rockchip.c | 341 ++++++++++++++++++
> 2 files changed, 343 insertions(+)
>
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> index cb25c0e8fc9b..3094d4533ad6 100644
> --- a/drivers/gpu/drm/rockchip/Kconfig
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -9,6 +9,8 @@ config DRM_ROCKCHIP
> select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
> select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
> select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
> + select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI
> + select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI
maybe alphabetical order?
> select DRM_RGB if ROCKCHIP_RGB
> select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
> help
> diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> index 18e112e30f6e..e322749a5279 100644
> --- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
> @@ -14,6 +14,7 @@
> #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>
> @@ -125,7 +126,9 @@
> #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
> @@ -137,6 +140,9 @@
> #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)
> @@ -171,11 +177,19 @@
> #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,
> @@ -213,6 +227,10 @@ struct rockchip_dw_dsi_chip_data {
> 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;
> };
> @@ -236,6 +254,12 @@ struct dw_mipi_dsi_rockchip {
> 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;
> @@ -965,6 +989,17 @@ static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
> 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",
> @@ -1000,6 +1035,10 @@ static int dw_mipi_dsi_rockchip_host_detach(void *priv_data,
>
> 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;
> }
>
> @@ -1008,11 +1047,227 @@ static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_rockchip_host_ops = {
> .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;
It seems "i" could be removed, use ret instead.
In general, the patch doesn't look wrong to me.
For the whole serie:
Acked-by: Helen Koike <helen.koike at collabora.com>
Thanks
Helen
> +
> + 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);
> @@ -1109,6 +1364,19 @@ static int dw_mipi_dsi_rockchip_probe(struct platform_device *pdev)
> 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);
> @@ -1175,6 +1443,75 @@ static const struct rockchip_dw_dsi_chip_data rk3288_chip_data[] = {
> { /* 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,
> @@ -1217,6 +1554,10 @@ static const struct rockchip_dw_dsi_chip_data rk3399_chip_data[] = {
>
> .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 */ }
> };
>
More information about the Linux-rockchip
mailing list