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@xxxxxxxxxxxxxx>
---
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
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html