Blame view

drivers/mfd/jz4740-adc.c 7.88 KB
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /*
   * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
   * JZ4740 SoC ADC driver
   *
   * This program is free software; you can redistribute it and/or modify it
   * under  the terms of the GNU General  Public License as published by the
   * Free Software Foundation;  either version 2 of the License, or (at your
   * option) any later version.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program; if not, write to the Free Software Foundation, Inc.,
   * 675 Mass Ave, Cambridge, MA 02139, USA.
   *
   * This driver synchronizes access to the JZ4740 ADC core between the
   * JZ4740 battery and hwmon drivers.
   */
  
  #include <linux/err.h>
fa860403e   Axel Lin   mfd: Include linu...
19
  #include <linux/io.h>
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  #include <linux/irq.h>
  #include <linux/interrupt.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/slab.h>
  #include <linux/spinlock.h>
  
  #include <linux/clk.h>
  #include <linux/mfd/core.h>
  
  #include <linux/jz4740-adc.h>
  
  
  #define JZ_REG_ADC_ENABLE	0x00
  #define JZ_REG_ADC_CFG		0x04
  #define JZ_REG_ADC_CTRL		0x08
  #define JZ_REG_ADC_STATUS	0x0c
  
  #define JZ_REG_ADC_TOUCHSCREEN_BASE	0x10
  #define JZ_REG_ADC_BATTERY_BASE	0x1c
  #define JZ_REG_ADC_HWMON_BASE	0x20
  
  #define JZ_ADC_ENABLE_TOUCH	BIT(2)
  #define JZ_ADC_ENABLE_BATTERY	BIT(1)
  #define JZ_ADC_ENABLE_ADCIN	BIT(0)
  
  enum {
  	JZ_ADC_IRQ_ADCIN = 0,
  	JZ_ADC_IRQ_BATTERY,
  	JZ_ADC_IRQ_TOUCH,
  	JZ_ADC_IRQ_PENUP,
  	JZ_ADC_IRQ_PENDOWN,
  };
  
  struct jz4740_adc {
  	struct resource *mem;
  	void __iomem *base;
  
  	int irq;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
60
  	struct irq_chip_generic *gc;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
61
62
63
64
65
66
  
  	struct clk *clk;
  	atomic_t clk_ref;
  
  	spinlock_t lock;
  };
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
67
68
  static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
  {
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
69
  	struct irq_chip_generic *gc = irq_desc_get_handler_data(desc);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
70
71
  	uint8_t status;
  	unsigned int i;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
72
  	status = readb(gc->reg_base + JZ_REG_ADC_STATUS);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
73
74
75
  
  	for (i = 0; i < 5; ++i) {
  		if (status & BIT(i))
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
76
  			generic_handle_irq(gc->irq_base + i);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
  	}
  }
  
  
  /* Refcounting for the ADC clock is done in here instead of in the clock
   * framework, because it is the only clock which is shared between multiple
   * devices and thus is the only clock which needs refcounting */
  static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
  {
  	if (atomic_inc_return(&adc->clk_ref) == 1)
  		clk_enable(adc->clk);
  }
  
  static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
  {
  	if (atomic_dec_return(&adc->clk_ref) == 0)
  		clk_disable(adc->clk);
  }
  
  static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
  	bool enabled)
  {
  	unsigned long flags;
  	uint8_t val;
  
  	spin_lock_irqsave(&adc->lock, flags);
  
  	val = readb(adc->base + JZ_REG_ADC_ENABLE);
  	if (enabled)
  		val |= BIT(engine);
  	else
f9c28019d   Axel Lin   mfd: Fix jz4740_a...
108
  		val &= ~BIT(engine);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  	writeb(val, adc->base + JZ_REG_ADC_ENABLE);
  
  	spin_unlock_irqrestore(&adc->lock, flags);
  }
  
  static int jz4740_adc_cell_enable(struct platform_device *pdev)
  {
  	struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
  
  	jz4740_adc_clk_enable(adc);
  	jz4740_adc_set_enabled(adc, pdev->id, true);
  
  	return 0;
  }
  
  static int jz4740_adc_cell_disable(struct platform_device *pdev)
  {
  	struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
  
  	jz4740_adc_set_enabled(adc, pdev->id, false);
  	jz4740_adc_clk_disable(adc);
  
  	return 0;
  }
  
  int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
  {
  	struct jz4740_adc *adc = dev_get_drvdata(dev);
  	unsigned long flags;
  	uint32_t cfg;
  
  	if (!adc)
  		return -ENODEV;
  
  	spin_lock_irqsave(&adc->lock, flags);
  
  	cfg = readl(adc->base + JZ_REG_ADC_CFG);
  
  	cfg &= ~mask;
  	cfg |= val;
  
  	writel(cfg, adc->base + JZ_REG_ADC_CFG);
  
  	spin_unlock_irqrestore(&adc->lock, flags);
  
  	return 0;
  }
  EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
  
  static struct resource jz4740_hwmon_resources[] = {
  	{
  		.start = JZ_ADC_IRQ_ADCIN,
  		.flags = IORESOURCE_IRQ,
  	},
  	{
  		.start	= JZ_REG_ADC_HWMON_BASE,
  		.end	= JZ_REG_ADC_HWMON_BASE + 3,
  		.flags	= IORESOURCE_MEM,
  	},
  };
  
  static struct resource jz4740_battery_resources[] = {
  	{
  		.start = JZ_ADC_IRQ_BATTERY,
  		.flags = IORESOURCE_IRQ,
  	},
  	{
  		.start	= JZ_REG_ADC_BATTERY_BASE,
  		.end	= JZ_REG_ADC_BATTERY_BASE + 3,
  		.flags	= IORESOURCE_MEM,
  	},
  };
aa05c9cf5   Axel Lin   mfd: Don't declar...
181
  static struct mfd_cell jz4740_adc_cells[] = {
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
182
183
184
185
186
  	{
  		.id = 0,
  		.name = "jz4740-hwmon",
  		.num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
  		.resources = jz4740_hwmon_resources,
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
187
188
189
190
191
192
193
194
195
  
  		.enable = jz4740_adc_cell_enable,
  		.disable = jz4740_adc_cell_disable,
  	},
  	{
  		.id = 1,
  		.name = "jz4740-battery",
  		.num_resources = ARRAY_SIZE(jz4740_battery_resources),
  		.resources = jz4740_battery_resources,
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
196
197
198
199
200
201
202
203
  
  		.enable = jz4740_adc_cell_enable,
  		.disable = jz4740_adc_cell_disable,
  	},
  };
  
  static int __devinit jz4740_adc_probe(struct platform_device *pdev)
  {
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
204
205
  	struct irq_chip_generic *gc;
  	struct irq_chip_type *ct;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
206
207
  	struct jz4740_adc *adc;
  	struct resource *mem_base;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
208
209
  	int ret;
  	int irq_base;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
210
211
  
  	adc = kmalloc(sizeof(*adc), GFP_KERNEL);
789133b7b   Axel Lin   mfd: Check jz4740...
212
213
214
215
216
  	if (!adc) {
  		dev_err(&pdev->dev, "Failed to allocate driver structure
  ");
  		return -ENOMEM;
  	}
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
217
218
219
220
221
222
223
224
  
  	adc->irq = platform_get_irq(pdev, 0);
  	if (adc->irq < 0) {
  		ret = adc->irq;
  		dev_err(&pdev->dev, "Failed to get platform irq: %d
  ", ret);
  		goto err_free;
  	}
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
225
226
227
  	irq_base = platform_get_irq(pdev, 1);
  	if (irq_base < 0) {
  		ret = irq_base;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
  		dev_err(&pdev->dev, "Failed to get irq base: %d
  ", ret);
  		goto err_free;
  	}
  
  	mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  	if (!mem_base) {
  		ret = -ENOENT;
  		dev_err(&pdev->dev, "Failed to get platform mmio resource
  ");
  		goto err_free;
  	}
  
  	/* Only request the shared registers for the MFD driver */
  	adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
  					pdev->name);
  	if (!adc->mem) {
  		ret = -EBUSY;
  		dev_err(&pdev->dev, "Failed to request mmio memory region
  ");
  		goto err_free;
  	}
  
  	adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
  	if (!adc->base) {
  		ret = -EBUSY;
  		dev_err(&pdev->dev, "Failed to ioremap mmio memory
  ");
  		goto err_release_mem_region;
  	}
  
  	adc->clk = clk_get(&pdev->dev, "adc");
  	if (IS_ERR(adc->clk)) {
  		ret = PTR_ERR(adc->clk);
  		dev_err(&pdev->dev, "Failed to get clock: %d
  ", ret);
  		goto err_iounmap;
  	}
  
  	spin_lock_init(&adc->lock);
  	atomic_set(&adc->clk_ref, 0);
  
  	platform_set_drvdata(pdev, adc);
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
271
272
273
274
275
276
277
278
  	gc = irq_alloc_generic_chip("INTC", 1, irq_base, adc->base,
  		handle_level_irq);
  
  	ct = gc->chip_types;
  	ct->regs.mask = JZ_REG_ADC_CTRL;
  	ct->regs.ack = JZ_REG_ADC_STATUS;
  	ct->chip.irq_mask = irq_gc_mask_set_bit;
  	ct->chip.irq_unmask = irq_gc_mask_clr_bit;
6fcb8a3a3   Lars-Peter Clausen   mfd: Fix generic ...
279
  	ct->chip.irq_ack = irq_gc_ack_set_bit;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
280
281
282
283
  
  	irq_setup_generic_chip(gc, IRQ_MSK(5), 0, 0, IRQ_NOPROBE | IRQ_LEVEL);
  
  	adc->gc = gc;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
284

914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
285
  	irq_set_handler_data(adc->irq, gc);
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
286
  	irq_set_chained_handler(adc->irq, jz4740_adc_irq_demux);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
287
288
289
  
  	writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
  	writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
48736c80e   Axel Lin   mfd: Fix jz4740-a...
290
  	ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
291
  		ARRAY_SIZE(jz4740_adc_cells), mem_base, irq_base);
48736c80e   Axel Lin   mfd: Fix jz4740-a...
292
293
  	if (ret < 0)
  		goto err_clk_put;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
294

48736c80e   Axel Lin   mfd: Fix jz4740-a...
295
296
297
298
  	return 0;
  
  err_clk_put:
  	clk_put(adc->clk);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
  err_iounmap:
  	platform_set_drvdata(pdev, NULL);
  	iounmap(adc->base);
  err_release_mem_region:
  	release_mem_region(adc->mem->start, resource_size(adc->mem));
  err_free:
  	kfree(adc);
  
  	return ret;
  }
  
  static int __devexit jz4740_adc_remove(struct platform_device *pdev)
  {
  	struct jz4740_adc *adc = platform_get_drvdata(pdev);
  
  	mfd_remove_devices(&pdev->dev);
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
315
316
  	irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0);
  	kfree(adc->gc);
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
317
318
  	irq_set_handler_data(adc->irq, NULL);
  	irq_set_chained_handler(adc->irq, NULL);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
319
320
321
322
323
324
325
326
327
328
329
330
  
  	iounmap(adc->base);
  	release_mem_region(adc->mem->start, resource_size(adc->mem));
  
  	clk_put(adc->clk);
  
  	platform_set_drvdata(pdev, NULL);
  
  	kfree(adc);
  
  	return 0;
  }
429c9ecc7   Lars-Peter Clausen   mfd: Make jz4740_...
331
  static struct platform_driver jz4740_adc_driver = {
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
332
333
334
335
336
337
338
  	.probe	= jz4740_adc_probe,
  	.remove = __devexit_p(jz4740_adc_remove),
  	.driver = {
  		.name = "jz4740-adc",
  		.owner = THIS_MODULE,
  	},
  };
65349d60d   Mark Brown   mfd: Convert MFD ...
339
  module_platform_driver(jz4740_adc_driver);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
340
341
342
343
344
  
  MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
  MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS("platform:jz4740-adc");