Re: [PATCH v2 3/4] phy: rockchip-samsung-dcphy: model TX and RX as separate PHYs

Michael Riesch <[email protected]> Mon, 3 Aug 2026 14:05:29 +0200
Newsgroups org.infradead.lists.linux-phy,org.infradead.lists.linux-arm-kernel,org.infradead.lists.linux-rockchip,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Hi Jason,

Thanks for the patch!

On 7/26/26 16:55, Jason Yang via B4 Relay wrote:
> From: Jason Yang <[email protected]>
> 
> The DC-PHY drives a MIPI DSI transmitter and a MIPI CSI receiver, and on
> RK3588 both can be wired to the same PHY as independent consumers. The
> PHY core reference-counts power_on() per struct phy, so a single struct
> phy cannot bring the two directions up independently.
> 
> Register one struct phy per direction and move the per-direction state
> (direction, PHY type, lane count and powered flag) into its own driver
> data. of_xlate() accepts the legacy single cell as the transmitter and
> an optional second cell that selects the direction, so existing
> single-cell DSI device trees keep resolving to the transmitter.
> configure() rejects the not yet supported receiver before touching any
> state, and each direction keeps its own lane count.
> 
> The PHY's common block contains a single BIAS block shared by both
> directions (RK3588 TRM section 22.2), so it must be programmed only
> once. Whichever direction powers on first enables it; each direction
> records its powered state under the per-provider mutex so that powering
> one direction on does not disturb an already running one. The APB reset
> is issued on the transmitter bring-up path.
> The receiver bring-up itself is added in a later change.
> 
> Signed-off-by: Jason Yang <[email protected]>
> Assisted-by: Claude:claude-opus-4-8
> ---
>  drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c | 143 +++++++++++++++-------
>  1 file changed, 102 insertions(+), 41 deletions(-)
> 
> diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
> index d0d77421bd4b..95eb1200cab4 100644
> --- a/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
> +++ b/drivers/phy/rockchip/phy-rockchip-samsung-dcphy.c
> @@ -5,6 +5,7 @@
>   *      Guochun Huang <[email protected]>
>   */
>  
> +#include <dt-bindings/phy/rockchip,rk3588-mipi-dcphy.h>
>  #include <dt-bindings/phy/phy.h>
>  #include <linux/bitfield.h>
>  #include <linux/clk.h>
> @@ -13,6 +14,7 @@
>  #include <linux/kernel.h>
>  #include <linux/mfd/syscon.h>
>  #include <linux/module.h>
> +#include <linux/mutex.h>
>  #include <linux/of.h>
>  #include <linux/phy/phy.h>
>  #include <linux/platform_device.h>
> @@ -280,6 +282,19 @@ struct samsung_mipi_dcphy_plat_data {
>  	u32 dphy_tx_max_lane_kbps;
>  };
>  
> +struct samsung_mipi_dcphy;
> +
> +/* One PHY per direction: transmitter (DSI) and receiver (CSI). */
> +struct samsung_mipi_dcphy_dir {

This may be bike-shedding, but I am not a big fan of the notion of
"direction". This is a combo PHY that in essence contains different
PHYs, so why not call this "samsung_mipi_phy" or something?

> +	struct phy *phy;
> +	struct samsung_mipi_dcphy *parent;
> +	u8 dir;
> +	u8 type;
> +	unsigned int lanes;
> +	/* Written under the parent's lock. */
> +	bool powered;
> +};
> +
>  struct samsung_mipi_dcphy {
>  	struct device *dev;
>  	struct clk *ref_clk;
> @@ -290,9 +305,9 @@ struct samsung_mipi_dcphy {
>  	struct reset_control *s_phy_rst;
>  	struct reset_control *apb_rst;
>  	struct reset_control *grf_apb_rst;
> -	unsigned int lanes;
> -	struct phy *phy;
> -	u8 type;
> +	struct samsung_mipi_dcphy_dir phys[2];

This would look like this then:

	struct samsung_mipi_phy phys[2];

which seems clean and simple to me.

> +	/* Serialises the two directions' access to the shared PHY state. */
> +	struct mutex lock;
>  
>  	const struct samsung_mipi_dcphy_plat_data *pdata;
>  	struct {
> @@ -995,7 +1010,7 @@ static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
>  	regmap_update_bits(samsung->regmap, DPHY_MC_GNR_CON0,
>  			   PHY_ENABLE, PHY_ENABLE);
>  
> -	switch (samsung->lanes) {
> +	switch (samsung->phys[RK_DCPHY_DIR_TX].lanes) {
>  	case 4:
>  		regmap_write(samsung->regmap, DPHY_MD3_GNR_CON1,
>  			     T_PHY_READY(0x2000));
> @@ -1026,7 +1041,7 @@ static void samsung_mipi_dphy_lane_enable(struct samsung_mipi_dcphy *samsung)
>  
>  static void samsung_mipi_dphy_lane_disable(struct samsung_mipi_dcphy *samsung)
>  {
> -	switch (samsung->lanes) {
> +	switch (samsung->phys[RK_DCPHY_DIR_TX].lanes) {
>  	case 4:
>  		regmap_update_bits(samsung->regmap, DPHY_MD3_GNR_CON0,
>  				   PHY_ENABLE, 0);
> @@ -1334,11 +1349,23 @@ samsung_mipi_dphy_data_lane_timing_init(struct samsung_mipi_dcphy *samsung)
>  
>  static int samsung_mipi_dphy_tx_power_on(struct samsung_mipi_dcphy *samsung)
>  {
> +	bool first = !samsung->phys[RK_DCPHY_DIR_RX].powered;

Note to myself: the lock is acquired in the calling method, all is well.

>  	int ret;
>  
> +	/*
> +	 * The shared BIAS block is brought up by whichever direction powers
> +	 * on first, leaving an already active peer undisturbed.
> +	 */
> +	if (first) {
> +		reset_control_assert(samsung->apb_rst);
> +		udelay(1);
> +		reset_control_deassert(samsung->apb_rst);
> +	}
> +
>  	reset_control_assert(samsung->m_phy_rst);
>  
> -	samsung_mipi_dcphy_bias_block_enable(samsung);
> +	if (first)
> +		samsung_mipi_dcphy_bias_block_enable(samsung);

This approach with the "first" variable seems unintuitive. If you need
reference counting on the BIAS block, then use a state variable
"bias_powered", use a mutex that protects it, and call
samsung_mipi_dcphy_bias_block_{en,dis}able (from
samsung_mipi_dcphy_power{on, off}) unconditionally. Those methods shall
acquire the mutex, check the state variable, {en,dis}able the BIAS
block, set the state variable accordingly, release the mutex.


>  	samsung_mipi_dcphy_pll_configure(samsung);
>  	samsung_mipi_dphy_clk_lane_timing_init(samsung);
>  	samsung_mipi_dphy_data_lane_timing_init(samsung);
> @@ -1368,34 +1395,44 @@ static int samsung_mipi_dphy_tx_power_off(struct samsung_mipi_dcphy *samsung)
>  
>  static int samsung_mipi_dcphy_power_on(struct phy *phy)
>  {
> -	struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
> -
> -	reset_control_assert(samsung->apb_rst);
> -	udelay(1);
> -	reset_control_deassert(samsung->apb_rst);
> +	struct samsung_mipi_dcphy_dir *pd = phy_get_drvdata(phy);

Again, this may be bike-shedding, but "pd" seems misleading to me.
Please reconsider the naming. Maybe "samsung_phy" for the individual PHY
and "samsung" for the complete combo PHY? Or "phy_data" and
"dcphy_data"? ...?

> +	struct samsung_mipi_dcphy *samsung = pd->parent;
> +	int ret;
>  
> -	switch (samsung->type) {
> -	case PHY_TYPE_DPHY:
> -		return samsung_mipi_dphy_tx_power_on(samsung);
> -	default:
> -		/* CPHY part to be implemented later */
> +	if (pd->type != PHY_TYPE_DPHY)
>  		return -EOPNOTSUPP;
> -	}
>  
> -	return 0;
> +	mutex_lock(&samsung->lock);
> +	if (pd->dir == RK_DCPHY_DIR_RX)
> +		ret = -EOPNOTSUPP;
> +	else
> +		ret = samsung_mipi_dphy_tx_power_on(samsung);
> +	if (!ret)
> +		pd->powered = true;

Not sure whether you actually need to track the power status of the
individual PHYs (maybe you actually need to track the BIAS block status
alone) but anyway I would appreciate if
"samsung_mipi_dphy_tx_power_{on,off}" would set the "pd->powered" variable.

Best regards,
Michael


> +	mutex_unlock(&samsung->lock);
> +
> +	return ret;
>  }
>  
>  static int samsung_mipi_dcphy_power_off(struct phy *phy)
>  {
> -	struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy_dir *pd = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy *samsung = pd->parent;
> +	int ret;
>  
> -	switch (samsung->type) {
> -	case PHY_TYPE_DPHY:
> -		return samsung_mipi_dphy_tx_power_off(samsung);
> -	default:
> -		/* CPHY part to be implemented later */
> +	if (pd->type != PHY_TYPE_DPHY)
>  		return -EOPNOTSUPP;
> -	}
> +
> +	if (pd->dir == RK_DCPHY_DIR_RX)
> +		return -EOPNOTSUPP;
> +
> +	mutex_lock(&samsung->lock);
> +	ret = samsung_mipi_dphy_tx_power_off(samsung);
> +	if (!ret)
> +		pd->powered = false;
> +	mutex_unlock(&samsung->lock);
> +
> +	return ret;
>  }
>  
>  static int
> @@ -1488,10 +1525,14 @@ samsung_mipi_dcphy_pll_calc_rate(struct samsung_mipi_dcphy *samsung,
>  static int samsung_mipi_dcphy_configure(struct phy *phy,
>  					union phy_configure_opts *opts)
>  {
> -	struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy_dir *pd = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy *samsung = pd->parent;
>  	unsigned long long target_rate = opts->mipi_dphy.hs_clk_rate;
>  
> -	samsung->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
> +	if (pd->dir == RK_DCPHY_DIR_RX)
> +		return -EOPNOTSUPP;
> +
> +	pd->lanes = opts->mipi_dphy.lanes > 4 ? 4 : opts->mipi_dphy.lanes;
>  
>  	samsung_mipi_dcphy_pll_calc_rate(samsung, target_rate);
>  	opts->mipi_dphy.hs_clk_rate = samsung->pll.rate;
> @@ -1501,16 +1542,16 @@ static int samsung_mipi_dcphy_configure(struct phy *phy,
>  
>  static int samsung_mipi_dcphy_init(struct phy *phy)
>  {
> -	struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy_dir *pd = phy_get_drvdata(phy);
>  
> -	return pm_runtime_resume_and_get(samsung->dev);
> +	return pm_runtime_resume_and_get(pd->parent->dev);
>  }
>  
>  static int samsung_mipi_dcphy_exit(struct phy *phy)
>  {
> -	struct samsung_mipi_dcphy *samsung = phy_get_drvdata(phy);
> +	struct samsung_mipi_dcphy_dir *pd = phy_get_drvdata(phy);
>  
> -	pm_runtime_put(samsung->dev);
> +	pm_runtime_put(pd->parent->dev);
>  
>  	return 0;
>  }
> @@ -1536,19 +1577,29 @@ static struct phy *samsung_mipi_dcphy_xlate(struct device *dev,
>  					    const struct of_phandle_args *args)
>  {
>  	struct samsung_mipi_dcphy *samsung = dev_get_drvdata(dev);
> +	struct samsung_mipi_dcphy_dir *pd;
> +	u8 dir = RK_DCPHY_DIR_TX;
>  
> -	if (args->args_count != 1) {
> +	if (args->args_count < 1 || args->args_count > 2) {
>  		dev_err(dev, "invalid number of arguments\n");
>  		return ERR_PTR(-EINVAL);
>  	}
>  
> -	if (samsung->type != PHY_NONE && samsung->type != args->args[0])
> -		dev_warn(dev, "phy type select %d overwriting type %d\n",
> -			 args->args[0], samsung->type);
> +	if (args->args_count == 2) {
> +		if (args->args[1] > RK_DCPHY_DIR_RX) {
> +			dev_err(dev, "invalid direction %u\n", args->args[1]);
> +			return ERR_PTR(-EINVAL);
> +		}
> +		dir = args->args[1];
> +	}
>  
> -	samsung->type = args->args[0];
> +	pd = &samsung->phys[dir];
> +	if (pd->type != PHY_NONE && pd->type != args->args[0])
> +		dev_warn(dev, "phy type select %u overwriting type %u\n",
> +			 args->args[0], pd->type);
> +	pd->type = args->args[0];
>  
> -	return samsung->phy;
> +	return pd->phy;
>  }
>  
>  static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
> @@ -1559,6 +1610,7 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
>  	struct phy_provider *phy_provider;
>  	struct resource *res;
>  	void __iomem *regs;
> +	unsigned int i;
>  	int ret;
>  
>  	samsung = devm_kzalloc(dev, sizeof(*samsung), GFP_KERNEL);
> @@ -1568,6 +1620,7 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
>  	samsung->dev = dev;
>  	samsung->pdata = device_get_match_data(dev);
>  	platform_set_drvdata(pdev, samsung);
> +	mutex_init(&samsung->lock);
>  
>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	regs = devm_ioremap_resource(dev, res);
> @@ -1613,11 +1666,19 @@ static int samsung_mipi_dcphy_probe(struct platform_device *pdev)
>  		return dev_err_probe(dev, PTR_ERR(samsung->grf_apb_rst),
>  				     "Failed to get system grf_apb_rst control\n");
>  
> -	samsung->phy = devm_phy_create(dev, NULL, &samsung_mipi_dcphy_ops);
> -	if (IS_ERR(samsung->phy))
> -		return dev_err_probe(dev, PTR_ERR(samsung->phy), "Failed to create MIPI DC-PHY\n");
> +	for (i = 0; i < ARRAY_SIZE(samsung->phys); i++) {
> +		struct phy *phy = devm_phy_create(dev, NULL,
> +						  &samsung_mipi_dcphy_ops);
>  
> -	phy_set_drvdata(samsung->phy, samsung);
> +		if (IS_ERR(phy))
> +			return dev_err_probe(dev, PTR_ERR(phy),
> +					     "Failed to create MIPI DC-PHY\n");
> +
> +		samsung->phys[i].phy = phy;
> +		samsung->phys[i].parent = samsung;
> +		samsung->phys[i].dir = i;
> +		phy_set_drvdata(phy, &samsung->phys[i]);
> +	}
>  
>  	ret = devm_pm_runtime_enable(dev);
>  	if (ret)
> 


-- 
linux-phy mailing list
[email protected]
https://lists.infradead.org/mailman/listinfo/linux-phy