Commit c7d7ddee authored by Gregory CLEMENT's avatar Gregory CLEMENT Committed by Tejun Heo

ata: libahci: Allow using multiple regulators

The current implementation of the libahci allows using multiple PHYs
but not multiple regulators. This patch adds the support of multiple
regulators. Until now it was mandatory to have a PHY under a subnode,
now a port subnode can contain either a regulator or a PHY (or both).

In order to be able to asociate a port with a regulator the port are
now a platform device in the device tree case.

There was only one driver which used directly the regulator field of
the ahci_host_priv structure. To preserve the bisectability the change
in the ahci_imx driver was done in the same patch.
Signed-off-by: default avatarGregory CLEMENT <gregory.clement@free-electrons.com>
Acked-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
parent 6bd15996
......@@ -333,7 +333,7 @@ struct ahci_host_priv {
u32 em_msg_type; /* EM message type */
bool got_runtime_pm; /* Did we do pm_runtime_get? */
struct clk *clks[AHCI_MAX_CLKS]; /* Optional */
struct regulator *target_pwr; /* Optional */
struct regulator **target_pwrs; /* Optional */
/*
* If platform uses PHYs. There is a 1:1 relation between the port number and
* the PHY position in this array.
......
......@@ -221,11 +221,9 @@ static int imx_sata_enable(struct ahci_host_priv *hpriv)
if (imxpriv->no_device)
return 0;
if (hpriv->target_pwr) {
ret = regulator_enable(hpriv->target_pwr);
ret = ahci_platform_enable_regulators(hpriv);
if (ret)
return ret;
}
ret = clk_prepare_enable(imxpriv->sata_ref_clk);
if (ret < 0)
......@@ -270,8 +268,7 @@ static int imx_sata_enable(struct ahci_host_priv *hpriv)
disable_clk:
clk_disable_unprepare(imxpriv->sata_ref_clk);
disable_regulator:
if (hpriv->target_pwr)
regulator_disable(hpriv->target_pwr);
ahci_platform_disable_regulators(hpriv);
return ret;
}
......@@ -291,8 +288,7 @@ static void imx_sata_disable(struct ahci_host_priv *hpriv)
clk_disable_unprepare(imxpriv->sata_ref_clk);
if (hpriv->target_pwr)
regulator_disable(hpriv->target_pwr);
ahci_platform_disable_regulators(hpriv);
}
static void ahci_imx_error_handler(struct ata_port *ap)
......
......@@ -24,6 +24,7 @@
#include <linux/ahci_platform.h>
#include <linux/phy/phy.h>
#include <linux/pm_runtime.h>
#include <linux/of_platform.h>
#include "ahci.h"
static void ahci_host_stop(struct ata_host *host);
......@@ -137,6 +138,59 @@ void ahci_platform_disable_clks(struct ahci_host_priv *hpriv)
}
EXPORT_SYMBOL_GPL(ahci_platform_disable_clks);
/**
* ahci_platform_enable_regulators - Enable regulators
* @hpriv: host private area to store config values
*
* This function enables all the regulators found in
* hpriv->target_pwrs, if any. If a regulator fails to be enabled, it
* disables all the regulators already enabled in reverse order and
* returns an error.
*
* RETURNS:
* 0 on success otherwise a negative error code
*/
int ahci_platform_enable_regulators(struct ahci_host_priv *hpriv)
{
int rc, i;
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->target_pwrs[i])
continue;
rc = regulator_enable(hpriv->target_pwrs[i]);
if (rc)
goto disable_target_pwrs;
}
return 0;
disable_target_pwrs:
while (--i >= 0)
if (hpriv->target_pwrs[i])
regulator_disable(hpriv->target_pwrs[i]);
return rc;
}
EXPORT_SYMBOL_GPL(ahci_platform_enable_regulators);
/**
* ahci_platform_disable_regulators - Disable regulators
* @hpriv: host private area to store config values
*
* This function disables all regulators found in hpriv->target_pwrs.
*/
void ahci_platform_disable_regulators(struct ahci_host_priv *hpriv)
{
int i;
for (i = 0; i < hpriv->nports; i++) {
if (!hpriv->target_pwrs[i])
continue;
regulator_disable(hpriv->target_pwrs[i]);
}
}
EXPORT_SYMBOL_GPL(ahci_platform_disable_regulators);
/**
* ahci_platform_enable_resources - Enable platform resources
* @hpriv: host private area to store config values
......@@ -157,11 +211,9 @@ int ahci_platform_enable_resources(struct ahci_host_priv *hpriv)
{
int rc;
if (hpriv->target_pwr) {
rc = regulator_enable(hpriv->target_pwr);
rc = ahci_platform_enable_regulators(hpriv);
if (rc)
return rc;
}
rc = ahci_platform_enable_clks(hpriv);
if (rc)
......@@ -177,8 +229,8 @@ int ahci_platform_enable_resources(struct ahci_host_priv *hpriv)
ahci_platform_disable_clks(hpriv);
disable_regulator:
if (hpriv->target_pwr)
regulator_disable(hpriv->target_pwr);
ahci_platform_disable_regulators(hpriv);
return rc;
}
EXPORT_SYMBOL_GPL(ahci_platform_enable_resources);
......@@ -199,8 +251,7 @@ void ahci_platform_disable_resources(struct ahci_host_priv *hpriv)
ahci_platform_disable_clks(hpriv);
if (hpriv->target_pwr)
regulator_disable(hpriv->target_pwr);
ahci_platform_disable_regulators(hpriv);
}
EXPORT_SYMBOL_GPL(ahci_platform_disable_resources);
......@@ -216,6 +267,68 @@ static void ahci_platform_put_resources(struct device *dev, void *res)
for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++)
clk_put(hpriv->clks[c]);
/*
* The regulators are tied to child node device and not to the
* SATA device itself. So we can't use devm for automatically
* releasing them. We have to do it manually here.
*/
for (c = 0; c < hpriv->nports; c++)
if (hpriv->target_pwrs && hpriv->target_pwrs[c])
regulator_put(hpriv->target_pwrs[c]);
}
static int ahci_platform_get_phy(struct ahci_host_priv *hpriv, u32 port,
struct device *dev, struct device_node *node)
{
int rc;
hpriv->phys[port] = devm_of_phy_get(dev, node, NULL);
if (!IS_ERR(hpriv->phys[port]))
return 0;
rc = PTR_ERR(hpriv->phys[port]);
switch (rc) {
case -ENOSYS:
/* No PHY support. Check if PHY is required. */
if (of_find_property(node, "phys", NULL)) {
dev_err(dev,
"couldn't get PHY in node %s: ENOSYS\n",
node->name);
break;
}
case -ENODEV:
/* continue normally */
hpriv->phys[port] = NULL;
rc = 0;
break;
default:
dev_err(dev,
"couldn't get PHY in node %s: %d\n",
node->name, rc);
break;
}
return rc;
}
static int ahci_platform_get_regulator(struct ahci_host_priv *hpriv, u32 port,
struct device *dev)
{
struct regulator *target_pwr;
int rc = 0;
target_pwr = regulator_get_optional(dev, "target");
if (!IS_ERR(target_pwr))
hpriv->target_pwrs[port] = target_pwr;
else
rc = PTR_ERR(target_pwr);
return rc;
}
/**
......@@ -240,7 +353,7 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
struct ahci_host_priv *hpriv;
struct clk *clk;
struct device_node *child;
int i, enabled_ports = 0, rc = -ENOMEM;
int i, sz, enabled_ports = 0, rc = -ENOMEM, child_nodes;
u32 mask_port_map = 0;
if (!devres_open_group(dev, NULL, GFP_KERNEL))
......@@ -261,14 +374,6 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
goto err_out;
}
hpriv->target_pwr = devm_regulator_get_optional(dev, "target");
if (IS_ERR(hpriv->target_pwr)) {
rc = PTR_ERR(hpriv->target_pwr);
if (rc == -EPROBE_DEFER)
goto err_out;
hpriv->target_pwr = NULL;
}
for (i = 0; i < AHCI_MAX_CLKS; i++) {
/*
* For now we must use clk_get(dev, NULL) for the first clock,
......@@ -290,19 +395,33 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
hpriv->clks[i] = clk;
}
hpriv->nports = of_get_child_count(dev->of_node);
hpriv->nports = child_nodes = of_get_child_count(dev->of_node);
if (hpriv->nports) {
hpriv->phys = devm_kzalloc(dev,
hpriv->nports * sizeof(*hpriv->phys),
GFP_KERNEL);
/*
* If no sub-node was found, we still need to set nports to
* one in order to be able to use the
* ahci_platform_[en|dis]able_[phys|regulators] functions.
*/
if (!child_nodes)
hpriv->nports = 1;
sz = hpriv->nports * sizeof(*hpriv->phys);
hpriv->phys = devm_kzalloc(dev, sz, GFP_KERNEL);
if (!hpriv->phys) {
rc = -ENOMEM;
goto err_out;
}
sz = hpriv->nports * sizeof(*hpriv->target_pwrs);
hpriv->target_pwrs = devm_kzalloc(dev, sz, GFP_KERNEL);
if (!hpriv->target_pwrs) {
rc = -ENOMEM;
goto err_out;
}
if (child_nodes) {
for_each_child_of_node(dev->of_node, child) {
u32 port;
struct platform_device *port_dev;
if (!of_device_is_available(child))
continue;
......@@ -316,18 +435,23 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
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);
of_platform_device_create(child, NULL, NULL);
port_dev = of_find_device_by_node(child);
if (port_dev) {
rc = ahci_platform_get_regulator(hpriv, port,
&port_dev->dev);
if (rc == -EPROBE_DEFER)
goto err_out;
}
rc = ahci_platform_get_phy(hpriv, port, dev, child);
if (rc)
goto err_out;
enabled_ports++;
}
if (!enabled_ports) {
......@@ -343,38 +467,14 @@ struct ahci_host_priv *ahci_platform_get_resources(struct platform_device *pdev)
* 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");
rc = ahci_platform_get_phy(hpriv, 0, dev, dev->of_node);
if (rc)
goto err_out;
}
case -ENODEV:
/* continue normally */
hpriv->phys = NULL;
break;
default:
rc = ahci_platform_get_regulator(hpriv, 0, dev);
if (rc == -EPROBE_DEFER)
goto err_out;
}
}
}
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
hpriv->got_runtime_pm = true;
......
......@@ -24,6 +24,8 @@ struct platform_device;
int ahci_platform_enable_clks(struct ahci_host_priv *hpriv);
void ahci_platform_disable_clks(struct ahci_host_priv *hpriv);
int ahci_platform_enable_regulators(struct ahci_host_priv *hpriv);
void ahci_platform_disable_regulators(struct ahci_host_priv *hpriv);
int ahci_platform_enable_resources(struct ahci_host_priv *hpriv);
void ahci_platform_disable_resources(struct ahci_host_priv *hpriv);
struct ahci_host_priv *ahci_platform_get_resources(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment