Blame view

drivers/mfd/jz4740-adc.c 7.69 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;
  };
bd0b9ac40   Thomas Gleixner   genirq: Remove ir...
67
  static void jz4740_adc_irq_demux(struct irq_desc *desc)
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
68
  {
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
  	}
  }
  
  
  /* 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)
404d5df42   Lars-Peter Clausen   mfd: jz4740-adc: ...
87
  		clk_prepare_enable(adc->clk);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
88
89
90
91
92
  }
  
  static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
  {
  	if (atomic_dec_return(&adc->clk_ref) == 0)
404d5df42   Lars-Peter Clausen   mfd: jz4740-adc: ...
93
  		clk_disable_unprepare(adc->clk);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
94
95
96
97
98
99
100
101
102
103
104
105
106
107
  }
  
  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,
  	},
  };
5ac98553a   Geert Uytterhoeven   mfd: Constify str...
181
  static const 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
  
  		.enable = jz4740_adc_cell_enable,
  		.disable = jz4740_adc_cell_disable,
  	},
  };
f791be492   Bill Pemberton   mfd: remove use o...
201
  static int jz4740_adc_probe(struct platform_device *pdev)
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
202
  {
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
203
204
  	struct irq_chip_generic *gc;
  	struct irq_chip_type *ct;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
205
206
  	struct jz4740_adc *adc;
  	struct resource *mem_base;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
207
208
  	int ret;
  	int irq_base;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
209

931cbaf35   Devendra Naga   mfd: jz4740-adc: ...
210
  	adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL);
789133b7b   Axel Lin   mfd: Check jz4740...
211
212
213
214
215
  	if (!adc) {
  		dev_err(&pdev->dev, "Failed to allocate driver structure
  ");
  		return -ENOMEM;
  	}
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
216
217
218
219
220
221
  
  	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);
931cbaf35   Devendra Naga   mfd: jz4740-adc: ...
222
  		return ret;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
223
  	}
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
224
225
  	irq_base = platform_get_irq(pdev, 1);
  	if (irq_base < 0) {
931cbaf35   Devendra Naga   mfd: jz4740-adc: ...
226
227
228
  		dev_err(&pdev->dev, "Failed to get irq base: %d
  ", irq_base);
  		return irq_base;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
229
230
231
232
  	}
  
  	mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  	if (!mem_base) {
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
233
234
  		dev_err(&pdev->dev, "Failed to get platform mmio resource
  ");
931cbaf35   Devendra Naga   mfd: jz4740-adc: ...
235
  		return -ENOENT;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
236
237
238
239
240
241
  	}
  
  	/* 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) {
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
242
243
  		dev_err(&pdev->dev, "Failed to request mmio memory region
  ");
931cbaf35   Devendra Naga   mfd: jz4740-adc: ...
244
  		return -EBUSY;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
  	}
  
  	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 ...
267
268
269
270
271
272
273
274
  	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 ...
275
  	ct->chip.irq_ack = irq_gc_ack_set_bit;
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
276

5a688c455   Matt Redfearn   mfd: jz4740-adc: ...
277
278
  	irq_setup_generic_chip(gc, IRQ_MSK(5), IRQ_GC_INIT_MASK_CACHE, 0,
  				IRQ_NOPROBE | IRQ_LEVEL);
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
279
280
  
  	adc->gc = gc;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
281

59fa909e7   Thomas Gleixner   mfd: jz4740: Cons...
282
  	irq_set_chained_handler_and_data(adc->irq, jz4740_adc_irq_demux, gc);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
283
284
285
  
  	writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
  	writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
48736c80e   Axel Lin   mfd: Fix jz4740-a...
286
  	ret = mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
0848c94fb   Mark Brown   mfd: core: Push i...
287
288
  			      ARRAY_SIZE(jz4740_adc_cells), mem_base,
  			      irq_base, NULL);
48736c80e   Axel Lin   mfd: Fix jz4740-a...
289
290
  	if (ret < 0)
  		goto err_clk_put;
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
291

48736c80e   Axel Lin   mfd: Fix jz4740-a...
292
293
294
295
  	return 0;
  
  err_clk_put:
  	clk_put(adc->clk);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
296
  err_iounmap:
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
297
298
299
  	iounmap(adc->base);
  err_release_mem_region:
  	release_mem_region(adc->mem->start, resource_size(adc->mem));
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
300
301
  	return ret;
  }
4740f73fe   Bill Pemberton   mfd: remove use o...
302
  static int jz4740_adc_remove(struct platform_device *pdev)
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
303
304
305
306
  {
  	struct jz4740_adc *adc = platform_get_drvdata(pdev);
  
  	mfd_remove_devices(&pdev->dev);
914e6d4e3   Lars-Peter Clausen   mfd: Use generic ...
307
308
  	irq_remove_generic_chip(adc->gc, IRQ_MSK(5), IRQ_NOPROBE | IRQ_LEVEL, 0);
  	kfree(adc->gc);
59fa909e7   Thomas Gleixner   mfd: jz4740: Cons...
309
  	irq_set_chained_handler_and_data(adc->irq, NULL, NULL);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
310
311
312
313
314
  
  	iounmap(adc->base);
  	release_mem_region(adc->mem->start, resource_size(adc->mem));
  
  	clk_put(adc->clk);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
315
316
  	return 0;
  }
429c9ecc7   Lars-Peter Clausen   mfd: Make jz4740_...
317
  static struct platform_driver jz4740_adc_driver = {
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
318
  	.probe	= jz4740_adc_probe,
84449216b   Bill Pemberton   mfd: remove use o...
319
  	.remove = jz4740_adc_remove,
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
320
321
  	.driver = {
  		.name = "jz4740-adc",
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
322
323
  	},
  };
65349d60d   Mark Brown   mfd: Convert MFD ...
324
  module_platform_driver(jz4740_adc_driver);
91f4debf5   Lars-Peter Clausen   mfd: Add JZ4740 A...
325
326
327
328
329
  
  MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
  MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS("platform:jz4740-adc");