ahci-platform: Add support for devices with more then 1 clock

The allwinner-sun4i AHCI controller needs 2 clocks to be enabled and the
imx AHCI controller needs 3 clocks to be enabled.

tj: Minor comment formatting updates.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
Hans de Goede 2014-02-22 16:53:31 +01:00 committed by Tejun Heo
parent 039ece38da
commit 156c588794
4 changed files with 93 additions and 28 deletions

View file

@ -10,6 +10,7 @@ Required properties:
Optional properties:
- dma-coherent : Present if dma operations are coherent
- clocks : a list of phandle + clock specifier pairs
Example:
sata@ffe08000 {

View file

@ -51,6 +51,7 @@
enum {
AHCI_MAX_PORTS = 32,
AHCI_MAX_CLKS = 3,
AHCI_MAX_SG = 168, /* hardware max is 64K */
AHCI_DMA_BOUNDARY = 0xffffffff,
AHCI_MAX_CMDS = 32,
@ -321,7 +322,7 @@ struct ahci_host_priv {
u32 em_loc; /* enclosure management location */
u32 em_buf_sz; /* EM buffer size in byte */
u32 em_msg_type; /* EM message type */
struct clk *clk; /* Only for platforms supporting clk */
struct clk *clks[AHCI_MAX_CLKS]; /* Optional */
void *plat_data; /* Other platform data */
/*
* Optional ahci_start_engine override, if not set this gets set to the

View file

@ -86,6 +86,60 @@ static struct scsi_host_template ahci_platform_sht = {
AHCI_SHT("ahci_platform"),
};
/**
* ahci_platform_enable_clks - Enable platform clocks
* @hpriv: host private area to store config values
*
* This function enables all the clks found in hpriv->clks, starting at
* index 0. If any clk fails to enable it disables all the clks already
* enabled in reverse order, and then returns an error.
*
* RETURNS:
* 0 on success otherwise a negative error code
*/
int ahci_platform_enable_clks(struct ahci_host_priv *hpriv)
{
int c, rc;
for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++) {
rc = clk_prepare_enable(hpriv->clks[c]);
if (rc)
goto disable_unprepare_clk;
}
return 0;
disable_unprepare_clk:
while (--c >= 0)
clk_disable_unprepare(hpriv->clks[c]);
return rc;
}
EXPORT_SYMBOL_GPL(ahci_platform_enable_clks);
/**
* ahci_platform_disable_clks - Disable platform clocks
* @hpriv: host private area to store config values
*
* This function disables all the clks found in hpriv->clks, in reverse
* order of ahci_platform_enable_clks (starting at the end of the array).
*/
void ahci_platform_disable_clks(struct ahci_host_priv *hpriv)
{
int c;
for (c = AHCI_MAX_CLKS - 1; c >= 0; c--)
if (hpriv->clks[c])
clk_disable_unprepare(hpriv->clks[c]);
}
EXPORT_SYMBOL_GPL(ahci_platform_disable_clks);
static void ahci_put_clks(struct ahci_host_priv *hpriv)
{
int c;
for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++)
clk_put(hpriv->clks[c]);
}
static int ahci_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@ -96,6 +150,7 @@ static int ahci_probe(struct platform_device *pdev)
struct ahci_host_priv *hpriv;
struct ata_host *host;
struct resource *mem;
struct clk *clk;
int irq;
int n_ports;
int i;
@ -130,17 +185,31 @@ static int ahci_probe(struct platform_device *pdev)
return -ENOMEM;
}
hpriv->clk = clk_get(dev, NULL);
if (IS_ERR(hpriv->clk)) {
dev_err(dev, "can't get clock\n");
} else {
rc = clk_prepare_enable(hpriv->clk);
if (rc) {
dev_err(dev, "clock prepare enable failed");
goto free_clk;
for (i = 0; i < AHCI_MAX_CLKS; i++) {
/*
* For now we must use clk_get(dev, NULL) for the first clock,
* because some platforms (da850, spear13xx) are not yet
* converted to use devicetree for clocks. For new platforms
* this is equivalent to of_clk_get(dev->of_node, 0).
*/
if (i == 0)
clk = clk_get(dev, NULL);
else
clk = of_clk_get(dev->of_node, i);
if (IS_ERR(clk)) {
rc = PTR_ERR(clk);
if (rc == -EPROBE_DEFER)
goto free_clk;
break;
}
hpriv->clks[i] = clk;
}
rc = ahci_enable_clks(dev, hpriv);
if (rc)
goto free_clk;
/*
* Some platforms might need to prepare for mmio region access,
* which could be done in the following init call. So, the mmio
@ -221,11 +290,9 @@ static int ahci_probe(struct platform_device *pdev)
if (pdata && pdata->exit)
pdata->exit(dev);
disable_unprepare_clk:
if (!IS_ERR(hpriv->clk))
clk_disable_unprepare(hpriv->clk);
ahci_disable_clks(hpriv);
free_clk:
if (!IS_ERR(hpriv->clk))
clk_put(hpriv->clk);
ahci_put_clks(hpriv);
return rc;
}
@ -238,10 +305,8 @@ static void ahci_host_stop(struct ata_host *host)
if (pdata && pdata->exit)
pdata->exit(dev);
if (!IS_ERR(hpriv->clk)) {
clk_disable_unprepare(hpriv->clk);
clk_put(hpriv->clk);
}
ahci_disable_clks(hpriv);
ahci_put_clks(hpriv);
}
#ifdef CONFIG_PM_SLEEP
@ -276,8 +341,7 @@ static int ahci_suspend(struct device *dev)
if (pdata && pdata->suspend)
return pdata->suspend(dev);
if (!IS_ERR(hpriv->clk))
clk_disable_unprepare(hpriv->clk);
ahci_disable_clks(hpriv);
return 0;
}
@ -289,13 +353,9 @@ static int ahci_resume(struct device *dev)
struct ahci_host_priv *hpriv = host->private_data;
int rc;
if (!IS_ERR(hpriv->clk)) {
rc = clk_prepare_enable(hpriv->clk);
if (rc) {
dev_err(dev, "clock prepare enable failed");
return rc;
}
}
rc = ahci_enable_clks(dev, hpriv);
if (rc)
return rc;
if (pdata && pdata->resume) {
rc = pdata->resume(dev);
@ -316,8 +376,7 @@ static int ahci_resume(struct device *dev)
return 0;
disable_unprepare_clk:
if (!IS_ERR(hpriv->clk))
clk_disable_unprepare(hpriv->clk);
ahci_disable_clks(hpriv);
return rc;
}

View file

@ -19,6 +19,7 @@
struct device;
struct ata_port_info;
struct ahci_host_priv;
struct ahci_platform_data {
int (*init)(struct device *dev, void __iomem *addr);
@ -30,4 +31,7 @@ struct ahci_platform_data {
unsigned int mask_port_map;
};
int ahci_platform_enable_clks(struct ahci_host_priv *hpriv);
void ahci_platform_disable_clks(struct ahci_host_priv *hpriv);
#endif /* _AHCI_PLATFORM_H */