[PATCH 2/7] PCI: rockchip: introduce per-lanes PHYs support

Shawn Lin shawn.lin at rock-chips.com
Mon Jul 17 19:36:40 PDT 2017


Hi Brian,

On 2017/7/18 4:14, Brian Norris wrote:
> On Mon, Jul 17, 2017 at 03:36:17PM +0800, Shawn Lin wrote:
>> We distinguish the legacy PHY with the newer per-lane
>> PHYs by adding legacy_phy flag and consolidate all
>> the phy operations into a single function to simply the
>> code. Note that the legacy phy is still the first option
>> to be searched in order not to break the backward compatibility
>> of DT blob, althoug we use devm_phy_optional_get instead which
>> seams to violate the original statement of pcie-rockchip's DT
>> document.
>>
>> Signed-off-by: Shawn Lin <shawn.lin at rock-chips.com>
>> ---
>>
>>   drivers/pci/host/pcie-rockchip.c | 144 ++++++++++++++++++++++++++++++++-------
>>   1 file changed, 118 insertions(+), 26 deletions(-)
>>
>> diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c
>> index 6632a51..f755df5 100644
>> --- a/drivers/pci/host/pcie-rockchip.c
>> +++ b/drivers/pci/host/pcie-rockchip.c
>> @@ -47,6 +47,7 @@
>>   #define HIWORD_UPDATE_BIT(val)		HIWORD_UPDATE(val, val)
>>   
>>   #define ENCODE_LANES(x)			((((x) >> 1) & 3) << 4)
>> +#define MAX_LANE_NUM			4
>>   
>>   #define PCIE_CLIENT_BASE		0x0
>>   #define PCIE_CLIENT_CONFIG		(PCIE_CLIENT_BASE + 0x00)
>> @@ -210,7 +211,9 @@
>>   struct rockchip_pcie {
>>   	void	__iomem *reg_base;		/* DT axi-base */
>>   	void	__iomem *apb_base;		/* DT apb-base */
>> +	bool	legacy_phy;
>>   	struct	phy *phy;
>> +	struct  phy **phys;
> 
> Would this be simpler as just an array?
> 
> 	struct phy *phys[MAX_LANE_NUM};
> 
> You can probably also combine it with the 'phy' field, and just treat it
> differently based on the 'legacy_phy' switch.

Make sense.

> 
>>   	struct	reset_control *core_rst;
>>   	struct	reset_control *mgmt_rst;
>>   	struct	reset_control *mgmt_sticky_rst;
>> @@ -242,6 +245,15 @@ struct rockchip_pcie {
>>   	phys_addr_t mem_bus_addr;
>>   };
>>   
>> +enum phy_ops_type {
>> +	PHY_INIT,
>> +	PHY_PWR_ON,
>> +	PHY_PWR_OFF,
>> +	PHY_EXIT,
>> +};
>> +
>> +const char *phy_ops_name[] = {"init", "power on", "power off", "exit"};
> 
> This could be 'static'. But if it's only for printing error
> messages...do you really even need this? Somebody could easily refer
> back to the driver if they need to convert an int/enum to something
> meaningful.

ok, I will kill all these above including the following ugly
rockchip_pcie_manipulate_phys.

> 
>> +
>>   static u32 rockchip_pcie_read(struct rockchip_pcie *rockchip, u32 reg)
>>   {
>>   	return readl(rockchip->apb_base + reg);
>> @@ -507,6 +519,104 @@ static void rockchip_pcie_set_power_limit(struct rockchip_pcie *rockchip)
>>   	rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_DCR);
>>   }
>>   
>> +static int rockchip_pcie_get_phys(struct rockchip_pcie *rockchip)
>> +{
>> +	struct device *dev = rockchip->dev;
>> +	struct phy *phy;
>> +	char *name;
>> +	u32 i;
>> +
>> +	rockchip->phy = devm_phy_get(dev, "pcie-phy");
>> +	if (IS_ERR(rockchip->phy)) {
>> +		if (PTR_ERR(rockchip->phy) == -EPROBE_DEFER)
>> +			return PTR_ERR(rockchip->phy);
>> +		dev_dbg(dev, "missing legacy phy, and search for per-lane PHY\n");
>> +	} else {
>> +		rockchip->legacy_phy = true;
>> +		dev_warn(dev, "legacy phy model is deprecated!\n");
>> +		return 0;
>> +	}
>> +
>> +	/* per-lane PHYs */
>> +	rockchip->phys = devm_kcalloc(dev, sizeof(phy), MAX_LANE_NUM,
>> +				      GFP_KERNEL);
>> +	if (!rockchip->phys)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < MAX_LANE_NUM; i++) {
>> +		name = kasprintf(GFP_KERNEL, "%s-%u", "pcie-phy", i);
> 
> Since the string is constant, this could just be:
> 
> 		kasprintf(..., "pcie-phy-%u", i);
> 

Looks good, and will improve it.

>> +		if (!name)
>> +			return -ENOMEM;
>> +
>> +		phy = devm_of_phy_get(rockchip->dev,
>> +				      rockchip->dev->of_node, name);
>> +		kfree(name);
>> +
>> +		if (IS_ERR(phy)) {
>> +			if (PTR_ERR(phy) != -EPROBE_DEFER)
>> +				dev_err(dev, "missing phy for lane %d: %ld\n",
>> +					i, PTR_ERR(phy));
>> +			return PTR_ERR(phy);
>> +		}
>> +
>> +		rockchip->phys[i] = phy;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int rockchip_pcie_manipulate_phys(struct rockchip_pcie *rockchip,
>> +					 enum phy_ops_type type)
>> +{
>> +	int i, phy_num, err;
>> +	struct device *dev = rockchip->dev;
>> +	struct phy *phy;
>> +
>> +	phy_num = rockchip->legacy_phy ? 1 : MAX_LANE_NUM;
>> +
>> +	for (i = 0; i < phy_num; i++) {
>> +		phy = rockchip->legacy_phy ? rockchip->phy :
>> +					     rockchip->phys[i];
>> +		switch (type) {
>> +		case PHY_INIT:
>> +			if (phy->init_count > phy_num)
> 
> This looks illegal and badly designed. Illegal, because this looks to be
> a count that should only be touched by the PHY core (and protected by
> its mutex). Badly designed, because this is *not* the right way to
> handle overflow/underflow. You should make sure that this driver does
> init/exit and power on/off in a properly-balanced fashion. And that
> doesn't mean just "skip" it when the count is too high; it means this
> driver should know on its own when is the right time to power on/off the
> phy/lane.

right,  init_cout/power_count should be kept inside the phy core but
we didn't want to duplicate it for consumer. I will try to kill all
of these.

> 
> And this logic doesn't even make sense. Why should I be allowed to init
> lane 0 only once, but I can init lane 3 up to 4 time >
>> +				continue;
>> +			err = phy_init(phy);
>> +			break;
>> +		case PHY_PWR_ON:
>> +			if (phy->power_count > phy_num)
> 
> Same goes here.
> 
>> +				continue;
>> +			err = phy_power_on(phy);
>> +			break;
>> +		case PHY_PWR_OFF:
>> +			if (!phy->power_count)
> 
> And here.
> 
>> +				continue;
>> +			err = phy_power_off(phy);
>> +			break;
>> +		case PHY_EXIT:
>> +			if (!phy->init_count)
> 
> And here.
> 
>> +				continue;
>> +			err = phy_exit(phy);
>> +			break;
>> +		default:
>> +			dev_err(dev, "unsupported phy_ops_type\n");
>> +			return -EOPNOTSUPP;
>> +		}
>> +
>> +		if (err < 0) {
>> +			if (rockchip->legacy_phy)
>> +				dev_err(dev, "fail to %s legacy PHY, err %d\n",
>> +					phy_ops_name[type], err);
>> +			else
>> +				dev_err(dev, "fail to %s per-lane PHY#%u, err %d\n",
>> +					phy_ops_name[type], i, err);
>> +			return err;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +}
> 
> Really, I think you should kill this whole function, and code the loop
> elsewhere.
> 
>> +
>>   /**
>>    * rockchip_pcie_init_port - Initialize hardware
>>    * @rockchip: PCIe port information
>> @@ -537,11 +647,9 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip)
>>   		return err;
>>   	}
>>   
>> -	err = phy_init(rockchip->phy);
>> -	if (err < 0) {
>> -		dev_err(dev, "fail to init phy, err %d\n", err);
>> +	err = rockchip_pcie_manipulate_phys(rockchip, PHY_INIT);
>> +	if (err < 0)
>>   		return err;
>> -	}
>>   
>>   	err = reset_control_assert(rockchip->core_rst);
>>   	if (err) {
>> @@ -602,11 +710,9 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip)
>>   			    PCIE_CLIENT_MODE_RC,
>>   			    PCIE_CLIENT_CONFIG);
>>   
>> -	err = phy_power_on(rockchip->phy);
>> -	if (err) {
>> -		dev_err(dev, "fail to power on phy, err %d\n", err);
>> +	err = rockchip_pcie_manipulate_phys(rockchip, PHY_PWR_ON);
>> +	if (err)
>>   		return err;
>> -	}
>>   
>>   	/*
>>   	 * Please don't reorder the deassert sequence of the following
>> @@ -853,20 +959,6 @@ static void rockchip_pcie_legacy_int_handler(struct irq_desc *desc)
>>   	chained_irq_exit(chip, desc);
>>   }
>>   
>> -static int rockchip_pcie_get_phys(struct rockchip_pcie *rockchip)
>> -{
>> -	struct device *dev = rockchip->dev;
>> -
>> -	rockchip->phy = devm_phy_get(dev, "pcie-phy");
>> -	if (IS_ERR(rockchip->phy)) {
>> -		if (PTR_ERR(rockchip->phy) != -EPROBE_DEFER)
>> -			dev_err(dev, "missing phy\n");
>> -		return PTR_ERR(rockchip->phy);
>> -	}
>> -
>> -	return 0;
>> -}
>> -
>>   /**
>>    * rockchip_pcie_parse_dt - Parse Device Tree
>>    * @rockchip: PCIe port information
>> @@ -1295,8 +1387,8 @@ static int __maybe_unused rockchip_pcie_suspend_noirq(struct device *dev)
>>   		return ret;
>>   	}
>>   
>> -	phy_power_off(rockchip->phy);
>> -	phy_exit(rockchip->phy);
>> +	rockchip_pcie_manipulate_phys(rockchip, PHY_PWR_OFF);
>> +	rockchip_pcie_manipulate_phys(rockchip, PHY_EXIT);
> 
> What's wrong with this (once you unify the 'phys' array)?
> 
> 	for (i = 0; i < MAX_LANE_NUM; i++) {
> 		if (rockchip->phys[i]) { // Not necessarily required;
> 					 // phy APIs handle NULL
> 			phy_power_off(rockchip->phys[i]);
> 			phy_exit(rockchip->phys[i]);
> 		}
> 	}
> 
> Similar for all other uses, AFAICT.
> 
> Once you actually need to prevent multiple power-offs (when you power
> down some lanes, but not others), you can avoid the illegal access to
> PHY-internal counters by just keeping your own mask of on/off PHYs.

I would like to see if I could reconstruct the phy consumer/provider
to avoid all these counters and fix all corner cases . :)


> 
>>   
>>   	clk_disable_unprepare(rockchip->clk_pcie_pm);
>>   	clk_disable_unprepare(rockchip->hclk_pcie);
>> @@ -1538,8 +1630,8 @@ static int rockchip_pcie_remove(struct platform_device *pdev)
>>   	pci_unmap_iospace(rockchip->io);
>>   	irq_domain_remove(rockchip->irq_domain);
>>   
>> -	phy_power_off(rockchip->phy);
>> -	phy_exit(rockchip->phy);
>> +	rockchip_pcie_manipulate_phys(rockchip, PHY_PWR_OFF);
>> +	rockchip_pcie_manipulate_phys(rockchip, PHY_EXIT);
> 
> Same here.
> 
> Brian
> 
>>   
>>   	clk_disable_unprepare(rockchip->clk_pcie_pm);
>>   	clk_disable_unprepare(rockchip->hclk_pcie);
>> -- 
>> 1.9.1
>>
>>
> 
> 
> 




More information about the Linux-rockchip mailing list