diff mbox series

[3/6] drm/rockchip: dsi: add ability to work as a phy instead of full dsi

Message ID 20210210111020.2476369-4-heiko@sntech.de (mailing list archive)
State New, archived
Headers show
Series Support second Image Signal Processor on rk3399 | expand

Commit Message

Heiko Stübner Feb. 10, 2021, 11:10 a.m. UTC
From: Heiko Stuebner <heiko.stuebner@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@theobroma-systems.com>
Tested-by: Sebastian Fricke <sebastian.fricke@posteo.net>
---
 drivers/gpu/drm/rockchip/Kconfig              |   2 +
 .../gpu/drm/rockchip/dw-mipi-dsi-rockchip.c   | 341 ++++++++++++++++++
 2 files changed, 343 insertions(+)

Comments

Helen Mae Koike Fornazier Feb. 15, 2021, 2:33 p.m. UTC | #1
On 2/10/21 8:10 AM, Heiko Stuebner wrote:
> From: Heiko Stuebner <heiko.stuebner@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@theobroma-systems.com>
> Tested-by: Sebastian Fricke <sebastian.fricke@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@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 */ }
>   };
>
Heiko Stübner March 24, 2021, 2:52 p.m. UTC | #2
Am Montag, 15. Februar 2021, 15:33:19 CET schrieb Helen Koike:
> > From: Heiko Stuebner <heiko.stuebner@theobroma-systems.com>
> > 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?

ok

> > +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.

I don't think so

I.e. the driver does

	i = max_mbps_to_parameter(...)
	...
	ret = power-on-clocks-and-stuff
	...
	dw_mipi_dsi_phy_write(.... dppa_map[i].hsfreqrange)

So will need to keep the param index separate.


> In general, the patch doesn't look wrong to me.
> 
> For the whole serie:
> Acked-by: Helen Koike <helen.koike@collabora.com>

Thanks a lot :-)


Heiko
diff mbox series

Patch

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
 	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;
+
+	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 */ }
 };