Merge tag 'arc-5.3-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/vgupta/arc
[linux-2.6-microblaze.git] / drivers / mfd / jz4740-adc.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
4  * JZ4740 SoC ADC driver
5  *
6  * This driver synchronizes access to the JZ4740 ADC core between the
7  * JZ4740 battery and hwmon drivers.
8  */
9
10 #include <linux/err.h>
11 #include <linux/io.h>
12 #include <linux/irq.h>
13 #include <linux/interrupt.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/slab.h>
18 #include <linux/spinlock.h>
19
20 #include <linux/clk.h>
21 #include <linux/mfd/core.h>
22
23 #include <linux/jz4740-adc.h>
24
25
26 #define JZ_REG_ADC_ENABLE       0x00
27 #define JZ_REG_ADC_CFG          0x04
28 #define JZ_REG_ADC_CTRL         0x08
29 #define JZ_REG_ADC_STATUS       0x0c
30
31 #define JZ_REG_ADC_TOUCHSCREEN_BASE     0x10
32 #define JZ_REG_ADC_BATTERY_BASE 0x1c
33 #define JZ_REG_ADC_HWMON_BASE   0x20
34
35 #define JZ_ADC_ENABLE_TOUCH     BIT(2)
36 #define JZ_ADC_ENABLE_BATTERY   BIT(1)
37 #define JZ_ADC_ENABLE_ADCIN     BIT(0)
38
39 enum {
40         JZ_ADC_IRQ_ADCIN = 0,
41         JZ_ADC_IRQ_BATTERY,
42         JZ_ADC_IRQ_TOUCH,
43         JZ_ADC_IRQ_PENUP,
44         JZ_ADC_IRQ_PENDOWN,
45 };
46
47 struct jz4740_adc {
48         struct resource *mem;
49         void __iomem *base;
50
51         int irq;
52         struct irq_chip_generic *gc;
53
54         struct clk *clk;
55         atomic_t clk_ref;
56
57         spinlock_t lock;
58 };
59
60 static void jz4740_adc_irq_demux(struct irq_desc *desc)
61 {
62         struct irq_chip_generic *gc = irq_desc_get_handler_data(desc);
63         uint8_t status;
64         unsigned int i;
65
66         status = readb(gc->reg_base + JZ_REG_ADC_STATUS);
67
68         for (i = 0; i < 5; ++i) {
69                 if (status & BIT(i))
70                         generic_handle_irq(gc->irq_base + i);
71         }
72 }
73
74
75 /* Refcounting for the ADC clock is done in here instead of in the clock
76  * framework, because it is the only clock which is shared between multiple
77  * devices and thus is the only clock which needs refcounting */
78 static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
79 {
80         if (atomic_inc_return(&adc->clk_ref) == 1)
81                 clk_prepare_enable(adc->clk);
82 }
83
84 static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
85 {
86         if (atomic_dec_return(&adc->clk_ref) == 0)
87                 clk_disable_unprepare(adc->clk);
88 }
89
90 static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
91         bool enabled)
92 {
93         unsigned long flags;
94         uint8_t val;
95
96         spin_lock_irqsave(&adc->lock, flags);
97
98         val = readb(adc->base + JZ_REG_ADC_ENABLE);
99         if (enabled)
100                 val |= BIT(engine);
101         else
102                 val &= ~BIT(engine);
103         writeb(val, adc->base + JZ_REG_ADC_ENABLE);
104
105         spin_unlock_irqrestore(&adc->lock, flags);
106 }
107
108 static int jz4740_adc_cell_enable(struct platform_device *pdev)
109 {
110         struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
111
112         jz4740_adc_clk_enable(adc);
113         jz4740_adc_set_enabled(adc, pdev->id, true);
114
115         return 0;
116 }
117
118 static int jz4740_adc_cell_disable(struct platform_device *pdev)
119 {
120         struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
121
122         jz4740_adc_set_enabled(adc, pdev->id, false);
123         jz4740_adc_clk_disable(adc);
124
125         return 0;
126 }
127
128 int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
129 {
130         struct jz4740_adc *adc = dev_get_drvdata(dev);
131         unsigned long flags;
132         uint32_t cfg;
133
134         if (!adc)
135                 return -ENODEV;
136
137         spin_lock_irqsave(&adc->lock, flags);
138
139         cfg = readl(adc->base + JZ_REG_ADC_CFG);
140
141         cfg &= ~mask;
142         cfg |= val;
143
144         writel(cfg, adc->base + JZ_REG_ADC_CFG);
145
146         spin_unlock_irqrestore(&adc->lock, flags);
147
148         return 0;
149 }
150 EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
151
152 static struct resource jz4740_hwmon_resources[] = {
153         {
154                 .start = JZ_ADC_IRQ_ADCIN,
155                 .flags = IORESOURCE_IRQ,
156         },
157         {
158                 .start  = JZ_REG_ADC_HWMON_BASE,
159                 .end    = JZ_REG_ADC_HWMON_BASE + 3,
160                 .flags  = IORESOURCE_MEM,
161         },
162 };
163
164 static struct resource jz4740_battery_resources[] = {
165         {
166                 .start = JZ_ADC_IRQ_BATTERY,
167                 .flags = IORESOURCE_IRQ,
168         },
169         {
170                 .start  = JZ_REG_ADC_BATTERY_BASE,
171                 .end    = JZ_REG_ADC_BATTERY_BASE + 3,
172                 .flags  = IORESOURCE_MEM,
173         },
174 };
175
176 static const struct mfd_cell jz4740_adc_cells[] = {
177         {
178                 .id = 0,
179                 .name = "jz4740-hwmon",
180                 .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
181                 .resources = jz4740_hwmon_resources,
182
183                 .enable = jz4740_adc_cell_enable,
184                 .disable = jz4740_adc_cell_disable,
185         },
186         {
187                 .id = 1,
188                 .name = "jz4740-battery",
189                 .num_resources = ARRAY_SIZE(jz4740_battery_resources),
190                 .resources = jz4740_battery_resources,
191
192                 .enable = jz4740_adc_cell_enable,
193                 .disable = jz4740_adc_cell_disable,
194         },
195 };
196
197 static int jz4740_adc_probe(struct platform_device *pdev)
198 {
199         struct irq_chip_generic *gc;
200         struct irq_chip_type *ct;
201         struct jz4740_adc *adc;
202         struct resource *mem_base;
203         int ret;
204         int irq_base;
205
206         adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL);
207         if (!adc)
208                 return -ENOMEM;
209
210         adc->irq = platform_get_irq(pdev, 0);
211         if (adc->irq < 0) {
212                 ret = adc->irq;
213                 dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
214                 return ret;
215         }
216
217         irq_base = platform_get_irq(pdev, 1);
218         if (irq_base < 0) {
219                 dev_err(&pdev->dev, "Failed to get irq base: %d\n", irq_base);
220                 return irq_base;
221         }
222
223         mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
224         if (!mem_base) {
225                 dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
226                 return -ENOENT;
227         }
228
229         /* Only request the shared registers for the MFD driver */
230         adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
231                                         pdev->name);
232         if (!adc->mem) {
233                 dev_err(&pdev->dev, "Failed to request mmio memory region\n");
234                 return -EBUSY;
235         }
236
237         adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
238         if (!adc->base) {
239                 ret = -EBUSY;
240                 dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
241                 goto err_release_mem_region;
242         }
243
244         adc->clk = clk_get(&pdev->dev, "adc");
245         if (IS_ERR(adc->clk)) {
246                 ret = PTR_ERR(adc->clk);
247                 dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
248                 goto err_iounmap;
249         }
250
251         spin_lock_init(&adc->lock);
252         atomic_set(&adc->clk_ref, 0);
253
254         platform_set_drvdata(pdev, adc);
255
256         gc = irq_alloc_generic_chip("INTC", 1, irq_base, adc->base,
257                 handle_level_irq);
258
259         ct = gc->chip_types;
260         ct->regs.mask = JZ_REG_ADC_CTRL;
261         ct->regs.ack = JZ_REG_ADC_STATUS;
262         ct->chip.irq_mask = irq_gc_mask_set_bit;
263         ct->chip.irq_unmask = irq_gc_mask_clr_bit;
264         ct->chip.irq_ack = irq_gc_ack_set_bit;
265
266         irq_setup_generic_chip(gc, IRQ_MSK(5), IRQ_GC_INIT_MASK_CACHE, 0,
267                                 IRQ_NOPROBE | IRQ_LEVEL);
268
269         adc->gc = gc;
270
271         irq_set_chained_handler_and_data(adc->irq, jz4740_adc_irq_demux, gc);
272
273         writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
274         writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
275
276         ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
277                               ARRAY_SIZE(jz4740_adc_cells), mem_base,
278                               irq_base, NULL);
279         if (ret < 0)
280                 goto err_clk_put;
281
282         return 0;
283
284 err_clk_put:
285         clk_put(adc->clk);
286 err_iounmap:
287         iounmap(adc->base);
288 err_release_mem_region:
289         release_mem_region(adc->mem->start, resource_size(adc->mem));
290         return ret;
291 }
292
293 static int jz4740_adc_remove(struct platform_device *pdev)
294 {
295         struct jz4740_adc *adc = platform_get_drvdata(pdev);
296
297         mfd_remove_devices(&pdev->dev);
298
299         irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0);
300         kfree(adc->gc);
301         irq_set_chained_handler_and_data(adc->irq, NULL, NULL);
302
303         iounmap(adc->base);
304         release_mem_region(adc->mem->start, resource_size(adc->mem));
305
306         clk_put(adc->clk);
307
308         return 0;
309 }
310
311 static struct platform_driver jz4740_adc_driver = {
312         .probe  = jz4740_adc_probe,
313         .remove = jz4740_adc_remove,
314         .driver = {
315                 .name = "jz4740-adc",
316         },
317 };
318
319 module_platform_driver(jz4740_adc_driver);
320
321 MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
322 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
323 MODULE_LICENSE("GPL");
324 MODULE_ALIAS("platform:jz4740-adc");