summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/ata/ahci.h7
-rw-r--r--drivers/ata/libahci_platform.c189
2 files changed, 157 insertions, 39 deletions
diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index 15396f0330ae..59ae0ee00149 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -334,7 +334,12 @@ struct ahci_host_priv {
bool got_runtime_pm; /* Did we do pm_runtime_get? */
struct clk *clks[AHCI_MAX_CLKS]; /* Optional */
struct regulator *target_pwr; /* Optional */
- struct phy *phy; /* If platform uses phy */
+ /*
+ * If platform uses PHYs. There is a 1:1 relation between the port number and
+ * the PHY position in this array.
+ */
+ struct phy **phys;
+ unsigned nports; /* Number of ports */
void *plat_data; /* Other platform data */
/*
* Optional ahci_start_engine override, if not set this gets set to the
diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c
index 00582d3a46a4..a60b8cd40198 100644
--- a/drivers/ata/libahci_platform.c
+++ b/drivers/ata/libahci_platform.c
@@ -39,6 +39,67 @@ static struct scsi_host_template ahci_platform_sht = {
};
/**
+ * ahci_platform_enable_phys - Enable PHYs
+ * @hpriv: host private area to store config values
+ *
+ * This function enables all the PHYs found in hpriv->phys, if any.
+ * If a PHY fails to be enabled, it disables all the PHYs already
+ * enabled in reverse order and returns an error.
+ *
+ * RETURNS:
+ * 0 on success otherwise a negative error code
+ */
+int ahci_platform_enable_phys(struct ahci_host_priv *hpriv)
+{
+ int rc, i;
+
+ for (i = 0; i < hpriv->nports; i++) {
+ if (!hpriv->phys[i])
+ continue;
+
+ rc = phy_init(hpriv->phys[i]);
+ if (rc)
+ goto disable_phys;
+
+ rc = phy_power_on(hpriv->phys[i]);
+ if (rc) {
+ phy_exit(hpriv->phys[i]);
+ goto disable_phys;
+ }
+ }
+
+ return 0;
+
+disable_phys:
+ while (--i >= 0) {
+ phy_power_off(hpriv->phys[i]);
+ phy_exit(hpriv->phys[i]);
+ }
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ahci_platform_enable_phys);
+
+/**
+ * ahci_platform_disable_phys - Disable PHYs
+ * @hpriv: host private area to store config values
+ *
+ * This function disables all PHYs found in hpriv->phys.
+ */
+void ahci_platform_disable_phys(struct ahci_host_priv *hpriv)
+{
+ int i;
+
+ for (i = 0; i < hpriv->nports; i++) {
+ if (!hpriv->phys[i])
+ continue;
+
+ phy_power_off(hpriv->phys[i]);
+ phy_exit(hpriv->phys[i]);
+ }
+}
+EXPORT_SYMBOL_GPL(ahci_platform_disable_phys);
+
+/**
* ahci_platform_enable_clks - Enable platform clocks
* @hpriv: host private area to store config values
*
@@ -92,7 +153,7 @@ EXPORT_SYMBOL_GPL(ahci_platform_disable_clks);
* following order:
* 1) Regulator
* 2) Clocks (through ahci_platform_enable_clks)
- * 3) Phy
+ * 3) Phys
*
* If resource enabling fails at any point the previous enabled resources
* are disabled in reverse order.
@@ -114,17 +175,9 @@ int ahci_platform_enable_resources(struct ahci_host_priv *hpriv)
if (rc)
goto disable_regulator;
- if (hpriv->phy) {
- rc = phy_init(hpriv->phy);
- if (rc)
- goto disable_clks;
-
- rc = phy_power_on(hpriv->phy);
- if (rc) {
- phy_exit(hpriv->phy);
- goto disable_clks;
- }
- }
+ rc = ahci_platform_enable_phys(hpriv);
+ if (rc)
+ goto disable_clks;
return 0;
@@ -144,16 +197,13 @@ EXPORT_SYMBOL_GPL(ahci_platform_enable_resources);
*
* This function disables all ahci_platform managed resources in the
* following order:
- * 1) Phy
+ * 1) Phys
* 2) Clocks (through ahci_platform_disable_clks)
* 3) Regulator
*/
void ahci_platform_disable_resources(struct ahci_host_priv *hpriv)
{
- if (hpriv->phy) {
- phy_power_off(hpriv->phy);
- phy_exit(hpriv->phy);
- }
+ ahci_platform_disable_phys(hpriv);
ahci_platform_disable_clks(hpriv);
@@ -187,7 +237,7 @@ static void ahci_platform_put_resources(struct device *dev, void *res)
* 2) regulator for controlling the targets power (optional)
* 3) 0 - AHCI_MAX_CLKS clocks, as specified in the devs devicetree node,
* or for non devicetree enabled platforms a single clock
- * 4) phy (optional)
+ * 4) phys (optional)
*
* RETURNS:
* The allocated ahci_host_priv on success, otherwise an ERR_PTR value
@@ -197,7 +247,9 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct ahci_host_priv *hpriv;
struct clk *clk;
- int i, rc = -ENOMEM;
+ struct device_node *child;
+ int i, enabled_ports = 0, rc = -ENOMEM;
+ u32 mask_port_map = 0;
if (!devres_open_group(dev, NULL, GFP_KERNEL))
return ERR_PTR(-ENOMEM);
@@ -246,28 +298,89 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
hpriv->clks[i] = clk;
}
- hpriv->phy = devm_phy_get(dev, "sata-phy");
- if (IS_ERR(hpriv->phy)) {
- rc = PTR_ERR(hpriv->phy);
- switch (rc) {
- case -ENOSYS:
- /* No PHY support. Check if PHY is required. */
- if (of_find_property(dev->of_node, "phys", NULL)) {
- dev_err(dev, "couldn't get sata-phy: ENOSYS\n");
+ hpriv->nports = of_get_child_count(dev->of_node);
+
+ if (hpriv->nports) {
+ hpriv->phys = devm_kzalloc(dev,
+ hpriv->nports * sizeof(*hpriv->phys),
+ GFP_KERNEL);
+ if (!hpriv->phys) {
+ rc = -ENOMEM;
+ goto err_out;
+ }
+
+ for_each_child_of_node(dev->of_node, child) {
+ u32 port;
+
+ if (!of_device_is_available(child))
+ continue;
+
+ if (of_property_read_u32(child, "reg", &port)) {
+ rc = -EINVAL;
goto err_out;
}
- case -ENODEV:
- /* continue normally */
- hpriv->phy = NULL;
- break;
- case -EPROBE_DEFER:
- goto err_out;
+ if (port >= hpriv->nports) {
+ dev_warn(dev, "invalid port number %d\n", port);
+ continue;
+ }
+
+ mask_port_map |= BIT(port);
+
+ hpriv->phys[port] = devm_of_phy_get(dev, child, NULL);
+ if (IS_ERR(hpriv->phys[port])) {
+ rc = PTR_ERR(hpriv->phys[port]);
+ dev_err(dev,
+ "couldn't get PHY in node %s: %d\n",
+ child->name, rc);
+ goto err_out;
+ }
- default:
- dev_err(dev, "couldn't get sata-phy\n");
+ enabled_ports++;
+ }
+ if (!enabled_ports) {
+ dev_warn(dev, "No port enabled\n");
+ rc = -ENODEV;
goto err_out;
}
+
+ if (!hpriv->mask_port_map)
+ hpriv->mask_port_map = mask_port_map;
+ } else {
+ /*
+ * If no sub-node was found, keep this for device tree
+ * compatibility
+ */
+ struct phy *phy = devm_phy_get(dev, "sata-phy");
+ if (!IS_ERR(phy)) {
+ hpriv->phys = devm_kzalloc(dev, sizeof(*hpriv->phys),
+ GFP_KERNEL);
+ if (!hpriv->phys) {
+ rc = -ENOMEM;
+ goto err_out;
+ }
+
+ hpriv->phys[0] = phy;
+ hpriv->nports = 1;
+ } else {
+ rc = PTR_ERR(phy);
+ switch (rc) {
+ case -ENOSYS:
+ /* No PHY support. Check if PHY is required. */
+ if (of_find_property(dev->of_node, "phys", NULL)) {
+ dev_err(dev, "couldn't get sata-phy: ENOSYS\n");
+ goto err_out;
+ }
+ case -ENODEV:
+ /* continue normally */
+ hpriv->phys = NULL;
+ break;
+
+ default:
+ goto err_out;
+
+ }
+ }
}
pm_runtime_enable(dev);
@@ -290,7 +403,7 @@ EXPORT_SYMBOL_GPL(ahci_platform_get_resources);
* @pi_template: template for the ata_port_info to use
*
* This function does all the usual steps needed to bring up an
- * ahci-platform host, note any necessary resources (ie clks, phy, etc.)
+ * ahci-platform host, note any necessary resources (ie clks, phys, etc.)
* must be initialized / enabled before calling this.
*
* RETURNS:
@@ -405,7 +518,7 @@ static void ahci_host_stop(struct ata_host *host)
* @dev: device pointer for the host
*
* This function does all the usual steps needed to suspend an
- * ahci-platform host, note any necessary resources (ie clks, phy, etc.)
+ * ahci-platform host, note any necessary resources (ie clks, phys, etc.)
* must be disabled after calling this.
*
* RETURNS:
@@ -442,7 +555,7 @@ EXPORT_SYMBOL_GPL(ahci_platform_suspend_host);
* @dev: device pointer for the host
*
* This function does all the usual steps needed to resume an ahci-platform
- * host, note any necessary resources (ie clks, phy, etc.) must be
+ * host, note any necessary resources (ie clks, phys, etc.) must be
* initialized / enabled before calling this.
*
* RETURNS: