aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2010-07-11 21:48:08 -0400
committerSamuel Ortiz <sameo@linux.intel.com>2010-08-12 05:27:58 -0400
commit91f4debf5e2df904e7fade530bd1a6d182efd72c (patch)
tree263ca3ab3ca3a52e011c4500f9afc051190ad023 /drivers
parentb12c35e22d102172cd2a69581f939ec9a70a7942 (diff)
mfd: Add JZ4740 ADC driver
This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to demultiplex IRQs and synchronize access to shared registers between the battery, hwmon and (future) touchscreen driver. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mfd/Kconfig8
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/jz4740-adc.c384
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 8f5145b4b56..d59334f34bf 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -514,6 +514,14 @@ config MFD_JANZ_CMODIO
514 host many different types of MODULbus daughterboards, including 514 host many different types of MODULbus daughterboards, including
515 CAN and GPIO controllers. 515 CAN and GPIO controllers.
516 516
517config MFD_JZ4740_ADC
518 tristate "Support for the JZ4740 SoC ADC core"
519 select MFD_CORE
520 depends on MACH_JZ4740
521 help
522 Say yes here if you want support for the ADC unit in the JZ4740 SoC.
523 This driver is necessary for jz4740-battery and jz4740-hwmon driver.
524
517endif # MFD_SUPPORT 525endif # MFD_SUPPORT
518 526
519menu "Multimedia Capabilities Port drivers" 527menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4410747f7b5..1f707796699 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -72,3 +72,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
72obj-$(CONFIG_LPC_SCH) += lpc_sch.o 72obj-$(CONFIG_LPC_SCH) += lpc_sch.o
73obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o 73obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
74obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o 74obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
75obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c
new file mode 100644
index 00000000000..7a844aef5dc
--- /dev/null
+++ b/drivers/mfd/jz4740-adc.c
@@ -0,0 +1,384 @@
1/*
2 * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
3 * JZ4740 SoC ADC driver
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * You should have received a copy of the GNU General Public License along
11 * with this program; if not, write to the Free Software Foundation, Inc.,
12 * 675 Mass Ave, Cambridge, MA 02139, USA.
13 *
14 * This driver synchronizes access to the JZ4740 ADC core between the
15 * JZ4740 battery and hwmon drivers.
16 */
17
18#include <linux/err.h>
19#include <linux/irq.h>
20#include <linux/interrupt.h>
21#include <linux/kernel.h>
22#include <linux/module.h>
23#include <linux/platform_device.h>
24#include <linux/slab.h>
25#include <linux/spinlock.h>
26
27#include <linux/clk.h>
28#include <linux/mfd/core.h>
29
30#include <linux/jz4740-adc.h>
31
32
33#define JZ_REG_ADC_ENABLE 0x00
34#define JZ_REG_ADC_CFG 0x04
35#define JZ_REG_ADC_CTRL 0x08
36#define JZ_REG_ADC_STATUS 0x0c
37
38#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10
39#define JZ_REG_ADC_BATTERY_BASE 0x1c
40#define JZ_REG_ADC_HWMON_BASE 0x20
41
42#define JZ_ADC_ENABLE_TOUCH BIT(2)
43#define JZ_ADC_ENABLE_BATTERY BIT(1)
44#define JZ_ADC_ENABLE_ADCIN BIT(0)
45
46enum {
47 JZ_ADC_IRQ_ADCIN = 0,
48 JZ_ADC_IRQ_BATTERY,
49 JZ_ADC_IRQ_TOUCH,
50 JZ_ADC_IRQ_PENUP,
51 JZ_ADC_IRQ_PENDOWN,
52};
53
54struct jz4740_adc {
55 struct resource *mem;
56 void __iomem *base;
57
58 int irq;
59 int irq_base;
60
61 struct clk *clk;
62 atomic_t clk_ref;
63
64 spinlock_t lock;
65};
66
67static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq,
68 bool masked)
69{
70 unsigned long flags;
71 uint8_t val;
72
73 irq -= adc->irq_base;
74
75 spin_lock_irqsave(&adc->lock, flags);
76
77 val = readb(adc->base + JZ_REG_ADC_CTRL);
78 if (masked)
79 val |= BIT(irq);
80 else
81 val &= ~BIT(irq);
82 writeb(val, adc->base + JZ_REG_ADC_CTRL);
83
84 spin_unlock_irqrestore(&adc->lock, flags);
85}
86
87static void jz4740_adc_irq_mask(unsigned int irq)
88{
89 struct jz4740_adc *adc = get_irq_chip_data(irq);
90 jz4740_adc_irq_set_masked(adc, irq, true);
91}
92
93static void jz4740_adc_irq_unmask(unsigned int irq)
94{
95 struct jz4740_adc *adc = get_irq_chip_data(irq);
96 jz4740_adc_irq_set_masked(adc, irq, false);
97}
98
99static void jz4740_adc_irq_ack(unsigned int irq)
100{
101 struct jz4740_adc *adc = get_irq_chip_data(irq);
102
103 irq -= adc->irq_base;
104 writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS);
105}
106
107static struct irq_chip jz4740_adc_irq_chip = {
108 .name = "jz4740-adc",
109 .mask = jz4740_adc_irq_mask,
110 .unmask = jz4740_adc_irq_unmask,
111 .ack = jz4740_adc_irq_ack,
112};
113
114static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
115{
116 struct jz4740_adc *adc = get_irq_desc_data(desc);
117 uint8_t status;
118 unsigned int i;
119
120 status = readb(adc->base + JZ_REG_ADC_STATUS);
121
122 for (i = 0; i < 5; ++i) {
123 if (status & BIT(i))
124 generic_handle_irq(adc->irq_base + i);
125 }
126}
127
128
129/* Refcounting for the ADC clock is done in here instead of in the clock
130 * framework, because it is the only clock which is shared between multiple
131 * devices and thus is the only clock which needs refcounting */
132static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
133{
134 if (atomic_inc_return(&adc->clk_ref) == 1)
135 clk_enable(adc->clk);
136}
137
138static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
139{
140 if (atomic_dec_return(&adc->clk_ref) == 0)
141 clk_disable(adc->clk);
142}
143
144static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
145 bool enabled)
146{
147 unsigned long flags;
148 uint8_t val;
149
150 spin_lock_irqsave(&adc->lock, flags);
151
152 val = readb(adc->base + JZ_REG_ADC_ENABLE);
153 if (enabled)
154 val |= BIT(engine);
155 else
156 val &= BIT(engine);
157 writeb(val, adc->base + JZ_REG_ADC_ENABLE);
158
159 spin_unlock_irqrestore(&adc->lock, flags);
160}
161
162static int jz4740_adc_cell_enable(struct platform_device *pdev)
163{
164 struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
165
166 jz4740_adc_clk_enable(adc);
167 jz4740_adc_set_enabled(adc, pdev->id, true);
168
169 return 0;
170}
171
172static int jz4740_adc_cell_disable(struct platform_device *pdev)
173{
174 struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
175
176 jz4740_adc_set_enabled(adc, pdev->id, false);
177 jz4740_adc_clk_disable(adc);
178
179 return 0;
180}
181
182int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
183{
184 struct jz4740_adc *adc = dev_get_drvdata(dev);
185 unsigned long flags;
186 uint32_t cfg;
187
188 if (!adc)
189 return -ENODEV;
190
191 spin_lock_irqsave(&adc->lock, flags);
192
193 cfg = readl(adc->base + JZ_REG_ADC_CFG);
194
195 cfg &= ~mask;
196 cfg |= val;
197
198 writel(cfg, adc->base + JZ_REG_ADC_CFG);
199
200 spin_unlock_irqrestore(&adc->lock, flags);
201
202 return 0;
203}
204EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
205
206static struct resource jz4740_hwmon_resources[] = {
207 {
208 .start = JZ_ADC_IRQ_ADCIN,
209 .flags = IORESOURCE_IRQ,
210 },
211 {
212 .start = JZ_REG_ADC_HWMON_BASE,
213 .end = JZ_REG_ADC_HWMON_BASE + 3,
214 .flags = IORESOURCE_MEM,
215 },
216};
217
218static struct resource jz4740_battery_resources[] = {
219 {
220 .start = JZ_ADC_IRQ_BATTERY,
221 .flags = IORESOURCE_IRQ,
222 },
223 {
224 .start = JZ_REG_ADC_BATTERY_BASE,
225 .end = JZ_REG_ADC_BATTERY_BASE + 3,
226 .flags = IORESOURCE_MEM,
227 },
228};
229
230const struct mfd_cell jz4740_adc_cells[] = {
231 {
232 .id = 0,
233 .name = "jz4740-hwmon",
234 .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
235 .resources = jz4740_hwmon_resources,
236 .platform_data = (void *)&jz4740_adc_cells[0],
237 .data_size = sizeof(struct mfd_cell),
238
239 .enable = jz4740_adc_cell_enable,
240 .disable = jz4740_adc_cell_disable,
241 },
242 {
243 .id = 1,
244 .name = "jz4740-battery",
245 .num_resources = ARRAY_SIZE(jz4740_battery_resources),
246 .resources = jz4740_battery_resources,
247 .platform_data = (void *)&jz4740_adc_cells[1],
248 .data_size = sizeof(struct mfd_cell),
249
250 .enable = jz4740_adc_cell_enable,
251 .disable = jz4740_adc_cell_disable,
252 },
253};
254
255static int __devinit jz4740_adc_probe(struct platform_device *pdev)
256{
257 int ret;
258 struct jz4740_adc *adc;
259 struct resource *mem_base;
260 int irq;
261
262 adc = kmalloc(sizeof(*adc), GFP_KERNEL);
263
264 adc->irq = platform_get_irq(pdev, 0);
265 if (adc->irq < 0) {
266 ret = adc->irq;
267 dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
268 goto err_free;
269 }
270
271 adc->irq_base = platform_get_irq(pdev, 1);
272 if (adc->irq_base < 0) {
273 ret = adc->irq_base;
274 dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
275 goto err_free;
276 }
277
278 mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
279 if (!mem_base) {
280 ret = -ENOENT;
281 dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
282 goto err_free;
283 }
284
285 /* Only request the shared registers for the MFD driver */
286 adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
287 pdev->name);
288 if (!adc->mem) {
289 ret = -EBUSY;
290 dev_err(&pdev->dev, "Failed to request mmio memory region\n");
291 goto err_free;
292 }
293
294 adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
295 if (!adc->base) {
296 ret = -EBUSY;
297 dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
298 goto err_release_mem_region;
299 }
300
301 adc->clk = clk_get(&pdev->dev, "adc");
302 if (IS_ERR(adc->clk)) {
303 ret = PTR_ERR(adc->clk);
304 dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
305 goto err_iounmap;
306 }
307
308 spin_lock_init(&adc->lock);
309 atomic_set(&adc->clk_ref, 0);
310
311 platform_set_drvdata(pdev, adc);
312
313 for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
314 set_irq_chip_data(irq, adc);
315 set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
316 handle_level_irq);
317 }
318
319 set_irq_data(adc->irq, adc);
320 set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
321
322 writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
323 writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
324
325 return mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
326 ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
327
328err_iounmap:
329 platform_set_drvdata(pdev, NULL);
330 iounmap(adc->base);
331err_release_mem_region:
332 release_mem_region(adc->mem->start, resource_size(adc->mem));
333err_free:
334 kfree(adc);
335
336 return ret;
337}
338
339static int __devexit jz4740_adc_remove(struct platform_device *pdev)
340{
341 struct jz4740_adc *adc = platform_get_drvdata(pdev);
342
343 mfd_remove_devices(&pdev->dev);
344
345 set_irq_data(adc->irq, NULL);
346 set_irq_chained_handler(adc->irq, NULL);
347
348 iounmap(adc->base);
349 release_mem_region(adc->mem->start, resource_size(adc->mem));
350
351 clk_put(adc->clk);
352
353 platform_set_drvdata(pdev, NULL);
354
355 kfree(adc);
356
357 return 0;
358}
359
360struct platform_driver jz4740_adc_driver = {
361 .probe = jz4740_adc_probe,
362 .remove = __devexit_p(jz4740_adc_remove),
363 .driver = {
364 .name = "jz4740-adc",
365 .owner = THIS_MODULE,
366 },
367};
368
369static int __init jz4740_adc_init(void)
370{
371 return platform_driver_register(&jz4740_adc_driver);
372}
373module_init(jz4740_adc_init);
374
375static void __exit jz4740_adc_exit(void)
376{
377 platform_driver_unregister(&jz4740_adc_driver);
378}
379module_exit(jz4740_adc_exit);
380
381MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
382MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
383MODULE_LICENSE("GPL");
384MODULE_ALIAS("platform:jz4740-adc");