aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2010-07-15 05:47:14 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2010-07-31 08:07:27 -0400
commit7cfe249475fdd82ad3c2767a9b906cc775dab868 (patch)
treea4344e847f32fd7e7f111eaecb09415e6286af1c
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>
-rw-r--r--drivers/amba/bus.c88
-rw-r--r--include/linux/amba/bus.h11
2 files changed, 80 insertions, 19 deletions
diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c
index f60b2b6a0931..d31590e7011b 100644
--- a/drivers/amba/bus.c
+++ b/drivers/amba/bus.c
@@ -122,6 +122,31 @@ static int __init amba_init(void)
122 122
123postcore_initcall(amba_init); 123postcore_initcall(amba_init);
124 124
125static int amba_get_enable_pclk(struct amba_device *pcdev)
126{
127 struct clk *pclk = clk_get(&pcdev->dev, "apb_pclk");
128 int ret;
129
130 pcdev->pclk = pclk;
131
132 if (IS_ERR(pclk))
133 return PTR_ERR(pclk);
134
135 ret = clk_enable(pclk);
136 if (ret)
137 clk_put(pclk);
138
139 return ret;
140}
141
142static void amba_put_disable_pclk(struct amba_device *pcdev)
143{
144 struct clk *pclk = pcdev->pclk;
145
146 clk_disable(pclk);
147 clk_put(pclk);
148}
149
125/* 150/*
126 * These are the device model conversion veneers; they convert the 151 * These are the device model conversion veneers; they convert the
127 * device model structures to our more specific structures. 152 * device model structures to our more specific structures.
@@ -130,17 +155,33 @@ static int amba_probe(struct device *dev)
130{ 155{
131 struct amba_device *pcdev = to_amba_device(dev); 156 struct amba_device *pcdev = to_amba_device(dev);
132 struct amba_driver *pcdrv = to_amba_driver(dev->driver); 157 struct amba_driver *pcdrv = to_amba_driver(dev->driver);
133 struct amba_id *id; 158 struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
159 int ret;
134 160
135 id = amba_lookup(pcdrv->id_table, pcdev); 161 do {
162 ret = amba_get_enable_pclk(pcdev);
163 if (ret)
164 break;
165
166 ret = pcdrv->probe(pcdev, id);
167 if (ret == 0)
168 break;
136 169
137 return pcdrv->probe(pcdev, id); 170 amba_put_disable_pclk(pcdev);
171 } while (0);
172
173 return ret;
138} 174}
139 175
140static int amba_remove(struct device *dev) 176static int amba_remove(struct device *dev)
141{ 177{
178 struct amba_device *pcdev = to_amba_device(dev);
142 struct amba_driver *drv = to_amba_driver(dev->driver); 179 struct amba_driver *drv = to_amba_driver(dev->driver);
143 return drv->remove(to_amba_device(dev)); 180 int ret = drv->remove(pcdev);
181
182 amba_put_disable_pclk(pcdev);
183
184 return ret;
144} 185}
145 186
146static void amba_shutdown(struct device *dev) 187static void amba_shutdown(struct device *dev)
@@ -203,7 +244,6 @@ static void amba_device_release(struct device *dev)
203 */ 244 */
204int amba_device_register(struct amba_device *dev, struct resource *parent) 245int amba_device_register(struct amba_device *dev, struct resource *parent)
205{ 246{
206 u32 pid, cid;
207 u32 size; 247 u32 size;
208 void __iomem *tmp; 248 void __iomem *tmp;
209 int i, ret; 249 int i, ret;
@@ -241,25 +281,35 @@ int amba_device_register(struct amba_device *dev, struct resource *parent)
241 goto err_release; 281 goto err_release;
242 } 282 }
243 283
244 /* 284 ret = amba_get_enable_pclk(dev);
245 * Read pid and cid based on size of resource 285 if (ret == 0) {
246 * they are located at end of region 286 u32 pid, cid;
247 */
248 for (pid = 0, i = 0; i < 4; i++)
249 pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) << (i * 8);
250 for (cid = 0, i = 0; i < 4; i++)
251 cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) << (i * 8);
252 287
253 iounmap(tmp); 288 /*
289 * Read pid and cid based on size of resource
290 * they are located at end of region
291 */
292 for (pid = 0, i = 0; i < 4; i++)
293 pid |= (readl(tmp + size - 0x20 + 4 * i) & 255) <<
294 (i * 8);
295 for (cid = 0, i = 0; i < 4; i++)
296 cid |= (readl(tmp + size - 0x10 + 4 * i) & 255) <<
297 (i * 8);
254 298
255 if (cid == 0xb105f00d) 299 amba_put_disable_pclk(dev);
256 dev->periphid = pid;
257 300
258 if (!dev->periphid) { 301 if (cid == 0xb105f00d)
259 ret = -ENODEV; 302 dev->periphid = pid;
260 goto err_release; 303
304 if (!dev->periphid)
305 ret = -ENODEV;
261 } 306 }
262 307
308 iounmap(tmp);
309
310 if (ret)
311 goto err_release;
312
263 ret = device_add(&dev->dev); 313 ret = device_add(&dev->dev);
264 if (ret) 314 if (ret)
265 goto err_release; 315 goto err_release;
diff --git a/include/linux/amba/bus.h b/include/linux/amba/bus.h
index 8b1038607831..b0c174012436 100644
--- a/include/linux/amba/bus.h
+++ b/include/linux/amba/bus.h
@@ -14,14 +14,19 @@
14#ifndef ASMARM_AMBA_H 14#ifndef ASMARM_AMBA_H
15#define ASMARM_AMBA_H 15#define ASMARM_AMBA_H
16 16
17#include <linux/clk.h>
17#include <linux/device.h> 18#include <linux/device.h>
19#include <linux/err.h>
18#include <linux/resource.h> 20#include <linux/resource.h>
19 21
20#define AMBA_NR_IRQS 2 22#define AMBA_NR_IRQS 2
21 23
24struct clk;
25
22struct amba_device { 26struct amba_device {
23 struct device dev; 27 struct device dev;
24 struct resource res; 28 struct resource res;
29 struct clk *pclk;
25 u64 dma_mask; 30 u64 dma_mask;
26 unsigned int periphid; 31 unsigned int periphid;
27 unsigned int irq[AMBA_NR_IRQS]; 32 unsigned int irq[AMBA_NR_IRQS];
@@ -59,6 +64,12 @@ struct amba_device *amba_find_device(const char *, struct device *, unsigned int
59int amba_request_regions(struct amba_device *, const char *); 64int amba_request_regions(struct amba_device *, const char *);
60void amba_release_regions(struct amba_device *); 65void amba_release_regions(struct amba_device *);
61 66
67#define amba_pclk_enable(d) \
68 (IS_ERR((d)->pclk) ? 0 : clk_enable((d)->pclk))
69
70#define amba_pclk_disable(d) \
71 do { if (!IS_ERR((d)->pclk)) clk_disable((d)->pclk); } while (0)
72
62#define amba_config(d) (((d)->periphid >> 24) & 0xff) 73#define amba_config(d) (((d)->periphid >> 24) & 0xff)
63#define amba_rev(d) (((d)->periphid >> 20) & 0x0f) 74#define amba_rev(d) (((d)->periphid >> 20) & 0x0f)
64#define amba_manf(d) (((d)->periphid >> 12) & 0xff) 75#define amba_manf(d) (((d)->periphid >> 12) & 0xff)