diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2010-07-11 21:48:08 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2010-08-12 05:27:58 -0400 |
commit | 91f4debf5e2df904e7fade530bd1a6d182efd72c (patch) | |
tree | 263ca3ab3ca3a52e011c4500f9afc051190ad023 /drivers | |
parent | b12c35e22d102172cd2a69581f939ec9a70a7942 (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/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/jz4740-adc.c | 384 |
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8f5145b4b56f..d59334f34bf6 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 | ||
517 | config 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 | |||
517 | endif # MFD_SUPPORT | 525 | endif # MFD_SUPPORT |
518 | 526 | ||
519 | menu "Multimedia Capabilities Port drivers" | 527 | menu "Multimedia Capabilities Port drivers" |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4410747f7b5b..1f7077966994 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -72,3 +72,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o | |||
72 | obj-$(CONFIG_LPC_SCH) += lpc_sch.o | 72 | obj-$(CONFIG_LPC_SCH) += lpc_sch.o |
73 | obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o | 73 | obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o |
74 | obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o | 74 | obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o |
75 | obj-$(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 000000000000..7a844aef5dcf --- /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 | |||
46 | enum { | ||
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 | |||
54 | struct 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 | |||
67 | static 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 | |||
87 | static 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 | |||
93 | static 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 | |||
99 | static 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 | |||
107 | static 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 | |||
114 | static 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 */ | ||
132 | static 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 | |||
138 | static 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 | |||
144 | static 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 | |||
162 | static 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 | |||
172 | static 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 | |||
182 | int 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 | } | ||
204 | EXPORT_SYMBOL_GPL(jz4740_adc_set_config); | ||
205 | |||
206 | static 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 | |||
218 | static 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 | |||
230 | const 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 | |||
255 | static 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 | |||
328 | err_iounmap: | ||
329 | platform_set_drvdata(pdev, NULL); | ||
330 | iounmap(adc->base); | ||
331 | err_release_mem_region: | ||
332 | release_mem_region(adc->mem->start, resource_size(adc->mem)); | ||
333 | err_free: | ||
334 | kfree(adc); | ||
335 | |||
336 | return ret; | ||
337 | } | ||
338 | |||
339 | static 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 | |||
360 | struct 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 | |||
369 | static int __init jz4740_adc_init(void) | ||
370 | { | ||
371 | return platform_driver_register(&jz4740_adc_driver); | ||
372 | } | ||
373 | module_init(jz4740_adc_init); | ||
374 | |||
375 | static void __exit jz4740_adc_exit(void) | ||
376 | { | ||
377 | platform_driver_unregister(&jz4740_adc_driver); | ||
378 | } | ||
379 | module_exit(jz4740_adc_exit); | ||
380 | |||
381 | MODULE_DESCRIPTION("JZ4740 SoC ADC driver"); | ||
382 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | ||
383 | MODULE_LICENSE("GPL"); | ||
384 | MODULE_ALIAS("platform:jz4740-adc"); | ||