Blame view

drivers/gpio/gpio-htc-egpio.c 10.8 KB
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  /*
   * Support for the GPIO/IRQ expander chips present on several HTC phones.
   * These are implemented in CPLD chips present on the board.
   *
   * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net>
   * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>
   *
   * This file may be distributed under the terms of the GNU GPL license.
   */
  
  #include <linux/kernel.h>
  #include <linux/errno.h>
  #include <linux/interrupt.h>
  #include <linux/irq.h>
  #include <linux/io.h>
  #include <linux/spinlock.h>
3c6e8d05d   Linus Walleij   mfd/gpio: Move HT...
17
  #include <linux/platform_data/gpio-htc-egpio.h>
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
18
  #include <linux/platform_device.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
19
  #include <linux/slab.h>
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
20
  #include <linux/module.h>
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
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
60
61
62
63
64
65
66
67
68
69
70
71
72
  
  struct egpio_chip {
  	int              reg_start;
  	int              cached_values;
  	unsigned long    is_out;
  	struct device    *dev;
  	struct gpio_chip chip;
  };
  
  struct egpio_info {
  	spinlock_t        lock;
  
  	/* iomem info */
  	void __iomem      *base_addr;
  	int               bus_shift;	/* byte shift */
  	int               reg_shift;	/* bit shift */
  	int               reg_mask;
  
  	/* irq info */
  	int               ack_register;
  	int               ack_write;
  	u16               irqs_enabled;
  	uint              irq_start;
  	int               nirqs;
  	uint              chained_irq;
  
  	/* egpio info */
  	struct egpio_chip *chip;
  	int               nchips;
  };
  
  static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg)
  {
  	writew(value, ei->base_addr + (reg << ei->bus_shift));
  }
  
  static inline u16 egpio_readw(struct egpio_info *ei, int reg)
  {
  	return readw(ei->base_addr + (reg << ei->bus_shift));
  }
  
  /*
   * IRQs
   */
  
  static inline void ack_irqs(struct egpio_info *ei)
  {
  	egpio_writew(ei->ack_write, ei, ei->ack_register);
  	pr_debug("EGPIO ack - write %x to base+%x
  ",
  			ei->ack_write, ei->ack_register << ei->bus_shift);
  }
949b9deca   Mark Brown   mfd: Convert HTC ...
73
  static void egpio_ack(struct irq_data *data)
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
74
75
76
77
78
79
  {
  }
  
  /* There does not appear to be a way to proactively mask interrupts
   * on the egpio chip itself.  So, we simply ignore interrupts that
   * aren't desired. */
949b9deca   Mark Brown   mfd: Convert HTC ...
80
  static void egpio_mask(struct irq_data *data)
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
81
  {
949b9deca   Mark Brown   mfd: Convert HTC ...
82
83
84
85
  	struct egpio_info *ei = irq_data_get_irq_chip_data(data);
  	ei->irqs_enabled &= ~(1 << (data->irq - ei->irq_start));
  	pr_debug("EGPIO mask %d %04x
  ", data->irq, ei->irqs_enabled);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
86
  }
949b9deca   Mark Brown   mfd: Convert HTC ...
87
88
  
  static void egpio_unmask(struct irq_data *data)
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
89
  {
949b9deca   Mark Brown   mfd: Convert HTC ...
90
91
92
93
  	struct egpio_info *ei = irq_data_get_irq_chip_data(data);
  	ei->irqs_enabled |= 1 << (data->irq - ei->irq_start);
  	pr_debug("EGPIO unmask %d %04x
  ", data->irq, ei->irqs_enabled);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
94
95
96
  }
  
  static struct irq_chip egpio_muxed_chip = {
949b9deca   Mark Brown   mfd: Convert HTC ...
97
98
99
100
  	.name		= "htc-egpio",
  	.irq_ack	= egpio_ack,
  	.irq_mask	= egpio_mask,
  	.irq_unmask	= egpio_unmask,
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
101
  };
bd0b9ac40   Thomas Gleixner   genirq: Remove ir...
102
  static void egpio_handler(struct irq_desc *desc)
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
103
  {
77eda9669   Thomas Gleixner   mfd: htc-egpio: C...
104
  	struct egpio_info *ei = irq_desc_get_handler_data(desc);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
105
106
107
108
109
110
111
112
113
114
  	int irqpin;
  
  	/* Read current pins. */
  	unsigned long readval = egpio_readw(ei, ei->ack_register);
  	pr_debug("IRQ reg: %x
  ", (unsigned int)readval);
  	/* Ack/unmask interrupts. */
  	ack_irqs(ei);
  	/* Process all set pins. */
  	readval &= ei->irqs_enabled;
984b3f574   Akinobu Mita   bitops: rename fo...
115
  	for_each_set_bit(irqpin, &readval, ei->nirqs) {
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
116
117
118
  		/* Run irq handler */
  		pr_debug("got IRQ %d
  ", irqpin);
77eda9669   Thomas Gleixner   mfd: htc-egpio: C...
119
  		generic_handle_irq(ei->irq_start + irqpin);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
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
  	}
  }
  
  int htc_egpio_get_wakeup_irq(struct device *dev)
  {
  	struct egpio_info *ei = dev_get_drvdata(dev);
  
  	/* Read current pins. */
  	u16 readval = egpio_readw(ei, ei->ack_register);
  	/* Ack/unmask interrupts. */
  	ack_irqs(ei);
  	/* Return first set pin. */
  	readval &= ei->irqs_enabled;
  	return ei->irq_start + ffs(readval) - 1;
  }
  EXPORT_SYMBOL(htc_egpio_get_wakeup_irq);
  
  static inline int egpio_pos(struct egpio_info *ei, int bit)
  {
  	return bit >> ei->reg_shift;
  }
  
  static inline int egpio_bit(struct egpio_info *ei, int bit)
  {
  	return 1 << (bit & ((1 << ei->reg_shift)-1));
  }
  
  /*
   * Input pins
   */
  
  static int egpio_get(struct gpio_chip *chip, unsigned offset)
  {
  	struct egpio_chip *egpio;
  	struct egpio_info *ei;
  	unsigned           bit;
  	int                reg;
  	int                value;
  
  	pr_debug("egpio_get_value(%d)
  ", chip->base + offset);
8d5f095fc   Linus Walleij   mfd: htc-egpio: U...
161
  	egpio = gpiochip_get_data(chip);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
162
163
164
165
166
167
168
169
  	ei    = dev_get_drvdata(egpio->dev);
  	bit   = egpio_bit(ei, offset);
  	reg   = egpio->reg_start + egpio_pos(ei, offset);
  
  	value = egpio_readw(ei, reg);
  	pr_debug("readw(%p + %x) = %x
  ",
  			ei->base_addr, reg << ei->bus_shift, value);
f7d623669   Linus Walleij   mfd: htc-egpio: B...
170
  	return !!(value & bit);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
171
172
173
174
175
  }
  
  static int egpio_direction_input(struct gpio_chip *chip, unsigned offset)
  {
  	struct egpio_chip *egpio;
8d5f095fc   Linus Walleij   mfd: htc-egpio: U...
176
  	egpio = gpiochip_get_data(chip);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
  	return test_bit(offset, &egpio->is_out) ? -EINVAL : 0;
  }
  
  
  /*
   * Output pins
   */
  
  static void egpio_set(struct gpio_chip *chip, unsigned offset, int value)
  {
  	unsigned long     flag;
  	struct egpio_chip *egpio;
  	struct egpio_info *ei;
  	unsigned          bit;
  	int               pos;
  	int               reg;
  	int               shift;
  
  	pr_debug("egpio_set(%s, %d(%d), %d)
  ",
  			chip->label, offset, offset+chip->base, value);
8d5f095fc   Linus Walleij   mfd: htc-egpio: U...
198
  	egpio = gpiochip_get_data(chip);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
  	ei    = dev_get_drvdata(egpio->dev);
  	bit   = egpio_bit(ei, offset);
  	pos   = egpio_pos(ei, offset);
  	reg   = egpio->reg_start + pos;
  	shift = pos << ei->reg_shift;
  
  	pr_debug("egpio %s: reg %d = 0x%04x
  ", value ? "set" : "clear",
  			reg, (egpio->cached_values >> shift) & ei->reg_mask);
  
  	spin_lock_irqsave(&ei->lock, flag);
  	if (value)
  		egpio->cached_values |= (1 << offset);
  	else
  		egpio->cached_values &= ~(1 << offset);
  	egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg);
  	spin_unlock_irqrestore(&ei->lock, flag);
  }
  
  static int egpio_direction_output(struct gpio_chip *chip,
  					unsigned offset, int value)
  {
  	struct egpio_chip *egpio;
8d5f095fc   Linus Walleij   mfd: htc-egpio: U...
222
  	egpio = gpiochip_get_data(chip);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
223
224
225
226
227
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
  	if (test_bit(offset, &egpio->is_out)) {
  		egpio_set(chip, offset, value);
  		return 0;
  	} else {
  		return -EINVAL;
  	}
  }
  
  static void egpio_write_cache(struct egpio_info *ei)
  {
  	int               i;
  	struct egpio_chip *egpio;
  	int               shift;
  
  	for (i = 0; i < ei->nchips; i++) {
  		egpio = &(ei->chip[i]);
  		if (!egpio->is_out)
  			continue;
  
  		for (shift = 0; shift < egpio->chip.ngpio;
  				shift += (1<<ei->reg_shift)) {
  
  			int reg = egpio->reg_start + egpio_pos(ei, shift);
  
  			if (!((egpio->is_out >> shift) & ei->reg_mask))
  				continue;
  
  			pr_debug("EGPIO: setting %x to %x, was %x
  ", reg,
  				(egpio->cached_values >> shift) & ei->reg_mask,
  				egpio_readw(ei, reg));
  
  			egpio_writew((egpio->cached_values >> shift)
  					& ei->reg_mask, ei, reg);
  		}
  	}
  }
  
  
  /*
   * Setup
   */
  
  static int __init egpio_probe(struct platform_device *pdev)
  {
334a41ce9   Jingoo Han   mfd: Use dev_get_...
268
  	struct htc_egpio_platform_data *pdata = dev_get_platdata(&pdev->dev);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
269
270
271
272
273
274
275
276
  	struct resource   *res;
  	struct egpio_info *ei;
  	struct gpio_chip  *chip;
  	unsigned int      irq, irq_end;
  	int               i;
  	int               ret;
  
  	/* Initialize ei data structure. */
58645b36b   Lee Jones   mfd: htc-egpio: C...
277
  	ei = devm_kzalloc(&pdev->dev, sizeof(*ei), GFP_KERNEL);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
  	if (!ei)
  		return -ENOMEM;
  
  	spin_lock_init(&ei->lock);
  
  	/* Find chained irq */
  	ret = -EINVAL;
  	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
  	if (res)
  		ei->chained_irq = res->start;
  
  	/* Map egpio chip into virtual address space. */
  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  	if (!res)
  		goto fail;
3f9850f26   Wei Yongjun   mfd: htc-egpio: U...
293
294
  	ei->base_addr = devm_ioremap_nocache(&pdev->dev, res->start,
  					     resource_size(res));
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
295
296
  	if (!ei->base_addr)
  		goto fail;
504f97f88   Samuel Ortiz   mfd: Fix htc-egpi...
297
298
  	pr_debug("EGPIO phys=%08x virt=%p
  ", (u32)res->start, ei->base_addr);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
  
  	if ((pdata->bus_width != 16) && (pdata->bus_width != 32))
  		goto fail;
  	ei->bus_shift = fls(pdata->bus_width - 1) - 3;
  	pr_debug("bus_shift = %d
  ", ei->bus_shift);
  
  	if ((pdata->reg_width != 8) && (pdata->reg_width != 16))
  		goto fail;
  	ei->reg_shift = fls(pdata->reg_width - 1);
  	pr_debug("reg_shift = %d
  ", ei->reg_shift);
  
  	ei->reg_mask = (1 << pdata->reg_width) - 1;
  
  	platform_set_drvdata(pdev, ei);
  
  	ei->nchips = pdata->num_chips;
58645b36b   Lee Jones   mfd: htc-egpio: C...
317
318
319
  	ei->chip = devm_kzalloc(&pdev->dev,
  				sizeof(struct egpio_chip) * ei->nchips,
  				GFP_KERNEL);
720fd66df   Julia Lawall   mfd: Fix egpio kz...
320
  	if (!ei->chip) {
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
321
322
323
324
325
326
327
328
329
330
  		ret = -ENOMEM;
  		goto fail;
  	}
  	for (i = 0; i < ei->nchips; i++) {
  		ei->chip[i].reg_start = pdata->chip[i].reg_start;
  		ei->chip[i].cached_values = pdata->chip[i].initial_values;
  		ei->chip[i].is_out = pdata->chip[i].direction;
  		ei->chip[i].dev = &(pdev->dev);
  		chip = &(ei->chip[i].chip);
  		chip->label           = "htc-egpio";
58383c784   Linus Walleij   gpio: change memb...
331
  		chip->parent          = &pdev->dev;
d8f388d8d   David Brownell   gpio: sysfs inter...
332
  		chip->owner           = THIS_MODULE;
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
333
334
335
336
337
338
  		chip->get             = egpio_get;
  		chip->set             = egpio_set;
  		chip->direction_input = egpio_direction_input;
  		chip->direction_output = egpio_direction_output;
  		chip->base            = pdata->chip[i].gpio_base;
  		chip->ngpio           = pdata->chip[i].num_gpios;
8d5f095fc   Linus Walleij   mfd: htc-egpio: U...
339
  		gpiochip_add_data(chip, &ei->chip[i]);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
  	}
  
  	/* Set initial pin values */
  	egpio_write_cache(ei);
  
  	ei->irq_start = pdata->irq_base;
  	ei->nirqs = pdata->num_irqs;
  	ei->ack_register = pdata->ack_register;
  
  	if (ei->chained_irq) {
  		/* Setup irq handlers */
  		ei->ack_write = 0xFFFF;
  		if (pdata->invert_acks)
  			ei->ack_write = 0;
  		irq_end = ei->irq_start + ei->nirqs;
  		for (irq = ei->irq_start; irq < irq_end; irq++) {
d6f7ce9f7   Thomas Gleixner   mfd: Fold irq_set...
356
357
  			irq_set_chip_and_handler(irq, &egpio_muxed_chip,
  						 handle_simple_irq);
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
358
  			irq_set_chip_data(irq, ei);
9bd09f345   Rob Herring   mfd: Kill off set...
359
  			irq_clear_status_flags(irq, IRQ_NOREQUEST | IRQ_NOPROBE);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
360
  		}
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
361
  		irq_set_irq_type(ei->chained_irq, IRQ_TYPE_EDGE_RISING);
073f7f99a   Thomas Gleixner   mfd: htc-egpio: C...
362
363
  		irq_set_chained_handler_and_data(ei->chained_irq,
  						 egpio_handler, ei);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
364
365
366
367
368
369
370
371
372
373
  		ack_irqs(ei);
  
  		device_init_wakeup(&pdev->dev, 1);
  	}
  
  	return 0;
  
  fail:
  	printk(KERN_ERR "EGPIO failed to setup
  ");
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
374
375
376
377
378
379
380
381
382
383
384
  	return ret;
  }
  
  static int __exit egpio_remove(struct platform_device *pdev)
  {
  	struct egpio_info *ei = platform_get_drvdata(pdev);
  	unsigned int      irq, irq_end;
  
  	if (ei->chained_irq) {
  		irq_end = ei->irq_start + ei->nirqs;
  		for (irq = ei->irq_start; irq < irq_end; irq++) {
d6f7ce9f7   Thomas Gleixner   mfd: Fold irq_set...
385
  			irq_set_chip_and_handler(irq, NULL, NULL);
9bd09f345   Rob Herring   mfd: Kill off set...
386
  			irq_set_status_flags(irq, IRQ_NOREQUEST | IRQ_NOPROBE);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
387
  		}
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
388
  		irq_set_chained_handler(ei->chained_irq, NULL);
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
389
390
  		device_init_wakeup(&pdev->dev, 0);
  	}
a1635b8fe   Philipp Zabel   [ARM] 4947/1: htc...
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
  
  	return 0;
  }
  
  #ifdef CONFIG_PM
  static int egpio_suspend(struct platform_device *pdev, pm_message_t state)
  {
  	struct egpio_info *ei = platform_get_drvdata(pdev);
  
  	if (ei->chained_irq && device_may_wakeup(&pdev->dev))
  		enable_irq_wake(ei->chained_irq);
  	return 0;
  }
  
  static int egpio_resume(struct platform_device *pdev)
  {
  	struct egpio_info *ei = platform_get_drvdata(pdev);
  
  	if (ei->chained_irq && device_may_wakeup(&pdev->dev))
  		disable_irq_wake(ei->chained_irq);
  
  	/* Update registers from the cache, in case
  	   the CPLD was powered off during suspend */
  	egpio_write_cache(ei);
  	return 0;
  }
  #else
  #define egpio_suspend NULL
  #define egpio_resume NULL
  #endif
  
  
  static struct platform_driver egpio_driver = {
  	.driver = {
  		.name = "htc-egpio",
  	},
  	.remove       = __exit_p(egpio_remove),
  	.suspend      = egpio_suspend,
  	.resume       = egpio_resume,
  };
  
  static int __init egpio_init(void)
  {
  	return platform_driver_probe(&egpio_driver, egpio_probe);
  }
  
  static void __exit egpio_exit(void)
  {
  	platform_driver_unregister(&egpio_driver);
  }
  
  /* start early for dependencies */
  subsys_initcall(egpio_init);
  module_exit(egpio_exit)
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Kevin O'Connor <kevin@koconnor.net>");