[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