summaryrefslogtreecommitdiffstats
path: root/drivers/amba/bus.c
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2010-07-15 10:47:14 +0100
committerRussell King <rmk+kernel@arm.linux.org.uk>2010-07-31 13:07:27 +0100
commit7cfe249475fdd82ad3c2767a9b906cc775dab868 (patch)
treea4344e847f32fd7e7f111eaecb09415e6286af1c /drivers/amba/bus.c
parent06385e490996d885c93fa03ce6e5374e4674a5cb (diff)
ARM: AMBA: Add pclk support to AMBA bus infrastructure
Some platforms gate the pclk (APB - the bus - clock) to the peripherals for power saving, along with the functional clock. When devices are accessed without pclk enabled, the kernel will oops. This gives them two options: 1. Leave all clocks on all the time. 2. Attempt to gate pclk along with the functional clock. (With some hardware, pclk and the functional clock are gated by a single bit in a register.) (1) has the disadvantage that it causes increased power usage, which is bad news for battery operated devices. (2) can lead to kernel oops if registers are accessed without the functional clock being enabled. So, introduce the apb_pclk signal in such a way existing drivers don't need to be updated. Essentially, this means we guarantee that: 1. pclk will be enabled whenever the driver is bound to a device - from probe() to remove() time. 2. pclk will also be enabled when reading the primecell IDs from the device. In order to allow drivers to be incrementally updated to achieve greater power savings, we provide two additional calls to allow drivers to manage the pclk - amba_pclk_enable()/amba_pclk_disable(). Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/amba/bus.c')
-rw-r--r--drivers/amba/bus.c88
1 files changed, 69 insertions, 19 deletions
diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c
index f60b2b6a093..d31590e7011 100644
--- a/drivers/amba/bus.c
+++ b/drivers/amba/bus.c
@@ -122,6 +122,31 @@ static int __init amba_init(void)
postcore_initcall(amba_init);
+static int amba_get_enable_pclk(struct amba_device *pcdev)
+{
+ struct clk *pclk = clk_get(&pcdev->dev, "apb_pclk");
+ int ret;
+
+ pcdev->pclk = pclk;
+
+ if (IS_ERR(pclk))
+ return PTR_ERR(pclk);
+
+ ret = clk_enable(pclk);
+ if (ret)
+ clk_put(pclk);
+
+ return ret;
+}
+
+static void amba_put_disable_pclk(struct amba_device *pcdev)
+{
+ struct clk *pclk = pcdev->pclk;
+
+ clk_disable(pclk);
+ clk_put(pclk);
+}
+
/*
* These are the device model conversion veneers; they convert the
* device model structures to our more specific structures.
@@ -130,17 +155,33 @@ static int amba_probe(struct device *dev)
{
struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *pcdrv = to_amba_driver(dev->driver);
- struct amba_id *id;
+ struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
+ int ret;
- id = amba_lookup(pcdrv->id_table, pcdev);
+ do {
+ ret = amba_get_enable_pclk(pcdev);
+ if (ret)
+ break;
+
+ ret = pcdrv->probe(pcdev, id);
+ if (ret == 0)
+ break;
- return pcdrv->probe(pcdev, id);
+ amba_put_disable_pclk(pcdev);
+ } while (0);
+
+ return ret;
}
static int amba_remove(struct device *dev)
{
+ struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *drv = to_amba_driver(dev->driver);
- return drv->remove(to_amba_device(dev));
+ int ret = drv->remove(pcdev);
+
+ amba_put_disable_pclk(pcdev);
+
+ return ret;
}
static void amba_shutdown(struct device *dev)
@@ -203,7 +244,6 @@ static void amba_device_release(struct device *dev)
*/
int amba_device_register(struct amba_device *dev, struct resource *parent)
{
- u32 pid, cid;
u32 size;
void __iomem *tmp;
int i, ret;
@@ -241,25 +281,35 @@ int amba_device_register(struct amba_device *dev, struct resource *parent)
goto err_release;
}
- /*
- * Read pid and cid based on size of resource
- * they are located at end of region
- */
- for (pid = 0, i = 0; i < 4; i++)
- pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) << (i * 8);
- for (cid = 0, i = 0; i < 4; i++)
- cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) << (i * 8);
+ ret = amba_get_enable_pclk(dev);
+ if (ret == 0) {
+ u32 pid, cid;
- iounmap(tmp);
+ /*
+ * Read pid and cid based on size of resource
+ * they are located at end of region
+ */
+ for (pid = 0, i = 0; i < 4; i++)
+ pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) <<
+ (i * 8);
+ for (cid = 0, i = 0; i < 4; i++)
+ cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) <<
+ (i * 8);
- if (cid == 0xb105f00d)
- dev->periphid = pid;
+ amba_put_disable_pclk(dev);
- if (!dev->periphid) {
- ret = -ENODEV;
- goto err_release;
+ if (cid == 0xb105f00d)
+ dev->periphid = pid;
+
+ if (!dev->periphid)
+ ret = -ENODEV;
}
+ iounmap(tmp);
+
+ if (ret)
+ goto err_release;
+
ret = device_add(&dev->dev);
if (ret)
goto err_release;