diff options
Diffstat (limited to 'drivers/gpio/gpio-mcp23s08.c')
-rw-r--r-- | drivers/gpio/gpio-mcp23s08.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-mcp23s08.c b/drivers/gpio/gpio-mcp23s08.c new file mode 100644 index 000000000000..0083ec051de5 --- /dev/null +++ b/drivers/gpio/gpio-mcp23s08.c | |||
@@ -0,0 +1,530 @@ | |||
1 | /* | ||
2 | * MCP23S08 SPI gpio expander driver | ||
3 | */ | ||
4 | |||
5 | #include <linux/kernel.h> | ||
6 | #include <linux/device.h> | ||
7 | #include <linux/workqueue.h> | ||
8 | #include <linux/mutex.h> | ||
9 | #include <linux/gpio.h> | ||
10 | #include <linux/spi/spi.h> | ||
11 | #include <linux/spi/mcp23s08.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <asm/byteorder.h> | ||
14 | |||
15 | /** | ||
16 | * MCP types supported by driver | ||
17 | */ | ||
18 | #define MCP_TYPE_S08 0 | ||
19 | #define MCP_TYPE_S17 1 | ||
20 | |||
21 | /* Registers are all 8 bits wide. | ||
22 | * | ||
23 | * The mcp23s17 has twice as many bits, and can be configured to work | ||
24 | * with either 16 bit registers or with two adjacent 8 bit banks. | ||
25 | * | ||
26 | * Also, there are I2C versions of both chips. | ||
27 | */ | ||
28 | #define MCP_IODIR 0x00 /* init/reset: all ones */ | ||
29 | #define MCP_IPOL 0x01 | ||
30 | #define MCP_GPINTEN 0x02 | ||
31 | #define MCP_DEFVAL 0x03 | ||
32 | #define MCP_INTCON 0x04 | ||
33 | #define MCP_IOCON 0x05 | ||
34 | # define IOCON_SEQOP (1 << 5) | ||
35 | # define IOCON_HAEN (1 << 3) | ||
36 | # define IOCON_ODR (1 << 2) | ||
37 | # define IOCON_INTPOL (1 << 1) | ||
38 | #define MCP_GPPU 0x06 | ||
39 | #define MCP_INTF 0x07 | ||
40 | #define MCP_INTCAP 0x08 | ||
41 | #define MCP_GPIO 0x09 | ||
42 | #define MCP_OLAT 0x0a | ||
43 | |||
44 | struct mcp23s08; | ||
45 | |||
46 | struct mcp23s08_ops { | ||
47 | int (*read)(struct mcp23s08 *mcp, unsigned reg); | ||
48 | int (*write)(struct mcp23s08 *mcp, unsigned reg, unsigned val); | ||
49 | int (*read_regs)(struct mcp23s08 *mcp, unsigned reg, | ||
50 | u16 *vals, unsigned n); | ||
51 | }; | ||
52 | |||
53 | struct mcp23s08 { | ||
54 | struct spi_device *spi; | ||
55 | u8 addr; | ||
56 | |||
57 | u16 cache[11]; | ||
58 | /* lock protects the cached values */ | ||
59 | struct mutex lock; | ||
60 | |||
61 | struct gpio_chip chip; | ||
62 | |||
63 | struct work_struct work; | ||
64 | |||
65 | const struct mcp23s08_ops *ops; | ||
66 | }; | ||
67 | |||
68 | /* A given spi_device can represent up to eight mcp23sxx chips | ||
69 | * sharing the same chipselect but using different addresses | ||
70 | * (e.g. chips #0 and #3 might be populated, but not #1 or $2). | ||
71 | * Driver data holds all the per-chip data. | ||
72 | */ | ||
73 | struct mcp23s08_driver_data { | ||
74 | unsigned ngpio; | ||
75 | struct mcp23s08 *mcp[8]; | ||
76 | struct mcp23s08 chip[]; | ||
77 | }; | ||
78 | |||
79 | static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) | ||
80 | { | ||
81 | u8 tx[2], rx[1]; | ||
82 | int status; | ||
83 | |||
84 | tx[0] = mcp->addr | 0x01; | ||
85 | tx[1] = reg; | ||
86 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, rx, sizeof rx); | ||
87 | return (status < 0) ? status : rx[0]; | ||
88 | } | ||
89 | |||
90 | static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) | ||
91 | { | ||
92 | u8 tx[3]; | ||
93 | |||
94 | tx[0] = mcp->addr; | ||
95 | tx[1] = reg; | ||
96 | tx[2] = val; | ||
97 | return spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); | ||
98 | } | ||
99 | |||
100 | static int | ||
101 | mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) | ||
102 | { | ||
103 | u8 tx[2], *tmp; | ||
104 | int status; | ||
105 | |||
106 | if ((n + reg) > sizeof mcp->cache) | ||
107 | return -EINVAL; | ||
108 | tx[0] = mcp->addr | 0x01; | ||
109 | tx[1] = reg; | ||
110 | |||
111 | tmp = (u8 *)vals; | ||
112 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, tmp, n); | ||
113 | if (status >= 0) { | ||
114 | while (n--) | ||
115 | vals[n] = tmp[n]; /* expand to 16bit */ | ||
116 | } | ||
117 | return status; | ||
118 | } | ||
119 | |||
120 | static int mcp23s17_read(struct mcp23s08 *mcp, unsigned reg) | ||
121 | { | ||
122 | u8 tx[2], rx[2]; | ||
123 | int status; | ||
124 | |||
125 | tx[0] = mcp->addr | 0x01; | ||
126 | tx[1] = reg << 1; | ||
127 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, rx, sizeof rx); | ||
128 | return (status < 0) ? status : (rx[0] | (rx[1] << 8)); | ||
129 | } | ||
130 | |||
131 | static int mcp23s17_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) | ||
132 | { | ||
133 | u8 tx[4]; | ||
134 | |||
135 | tx[0] = mcp->addr; | ||
136 | tx[1] = reg << 1; | ||
137 | tx[2] = val; | ||
138 | tx[3] = val >> 8; | ||
139 | return spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); | ||
140 | } | ||
141 | |||
142 | static int | ||
143 | mcp23s17_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) | ||
144 | { | ||
145 | u8 tx[2]; | ||
146 | int status; | ||
147 | |||
148 | if ((n + reg) > sizeof mcp->cache) | ||
149 | return -EINVAL; | ||
150 | tx[0] = mcp->addr | 0x01; | ||
151 | tx[1] = reg << 1; | ||
152 | |||
153 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, | ||
154 | (u8 *)vals, n * 2); | ||
155 | if (status >= 0) { | ||
156 | while (n--) | ||
157 | vals[n] = __le16_to_cpu((__le16)vals[n]); | ||
158 | } | ||
159 | |||
160 | return status; | ||
161 | } | ||
162 | |||
163 | static const struct mcp23s08_ops mcp23s08_ops = { | ||
164 | .read = mcp23s08_read, | ||
165 | .write = mcp23s08_write, | ||
166 | .read_regs = mcp23s08_read_regs, | ||
167 | }; | ||
168 | |||
169 | static const struct mcp23s08_ops mcp23s17_ops = { | ||
170 | .read = mcp23s17_read, | ||
171 | .write = mcp23s17_write, | ||
172 | .read_regs = mcp23s17_read_regs, | ||
173 | }; | ||
174 | |||
175 | |||
176 | /*----------------------------------------------------------------------*/ | ||
177 | |||
178 | static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) | ||
179 | { | ||
180 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
181 | int status; | ||
182 | |||
183 | mutex_lock(&mcp->lock); | ||
184 | mcp->cache[MCP_IODIR] |= (1 << offset); | ||
185 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); | ||
186 | mutex_unlock(&mcp->lock); | ||
187 | return status; | ||
188 | } | ||
189 | |||
190 | static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) | ||
191 | { | ||
192 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
193 | int status; | ||
194 | |||
195 | mutex_lock(&mcp->lock); | ||
196 | |||
197 | /* REVISIT reading this clears any IRQ ... */ | ||
198 | status = mcp->ops->read(mcp, MCP_GPIO); | ||
199 | if (status < 0) | ||
200 | status = 0; | ||
201 | else { | ||
202 | mcp->cache[MCP_GPIO] = status; | ||
203 | status = !!(status & (1 << offset)); | ||
204 | } | ||
205 | mutex_unlock(&mcp->lock); | ||
206 | return status; | ||
207 | } | ||
208 | |||
209 | static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) | ||
210 | { | ||
211 | unsigned olat = mcp->cache[MCP_OLAT]; | ||
212 | |||
213 | if (value) | ||
214 | olat |= mask; | ||
215 | else | ||
216 | olat &= ~mask; | ||
217 | mcp->cache[MCP_OLAT] = olat; | ||
218 | return mcp->ops->write(mcp, MCP_OLAT, olat); | ||
219 | } | ||
220 | |||
221 | static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) | ||
222 | { | ||
223 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
224 | unsigned mask = 1 << offset; | ||
225 | |||
226 | mutex_lock(&mcp->lock); | ||
227 | __mcp23s08_set(mcp, mask, value); | ||
228 | mutex_unlock(&mcp->lock); | ||
229 | } | ||
230 | |||
231 | static int | ||
232 | mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) | ||
233 | { | ||
234 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
235 | unsigned mask = 1 << offset; | ||
236 | int status; | ||
237 | |||
238 | mutex_lock(&mcp->lock); | ||
239 | status = __mcp23s08_set(mcp, mask, value); | ||
240 | if (status == 0) { | ||
241 | mcp->cache[MCP_IODIR] &= ~mask; | ||
242 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); | ||
243 | } | ||
244 | mutex_unlock(&mcp->lock); | ||
245 | return status; | ||
246 | } | ||
247 | |||
248 | /*----------------------------------------------------------------------*/ | ||
249 | |||
250 | #ifdef CONFIG_DEBUG_FS | ||
251 | |||
252 | #include <linux/seq_file.h> | ||
253 | |||
254 | /* | ||
255 | * This shows more info than the generic gpio dump code: | ||
256 | * pullups, deglitching, open drain drive. | ||
257 | */ | ||
258 | static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) | ||
259 | { | ||
260 | struct mcp23s08 *mcp; | ||
261 | char bank; | ||
262 | int t; | ||
263 | unsigned mask; | ||
264 | |||
265 | mcp = container_of(chip, struct mcp23s08, chip); | ||
266 | |||
267 | /* NOTE: we only handle one bank for now ... */ | ||
268 | bank = '0' + ((mcp->addr >> 1) & 0x7); | ||
269 | |||
270 | mutex_lock(&mcp->lock); | ||
271 | t = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); | ||
272 | if (t < 0) { | ||
273 | seq_printf(s, " I/O ERROR %d\n", t); | ||
274 | goto done; | ||
275 | } | ||
276 | |||
277 | for (t = 0, mask = 1; t < chip->ngpio; t++, mask <<= 1) { | ||
278 | const char *label; | ||
279 | |||
280 | label = gpiochip_is_requested(chip, t); | ||
281 | if (!label) | ||
282 | continue; | ||
283 | |||
284 | seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s", | ||
285 | chip->base + t, bank, t, label, | ||
286 | (mcp->cache[MCP_IODIR] & mask) ? "in " : "out", | ||
287 | (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", | ||
288 | (mcp->cache[MCP_GPPU] & mask) ? " " : "up"); | ||
289 | /* NOTE: ignoring the irq-related registers */ | ||
290 | seq_printf(s, "\n"); | ||
291 | } | ||
292 | done: | ||
293 | mutex_unlock(&mcp->lock); | ||
294 | } | ||
295 | |||
296 | #else | ||
297 | #define mcp23s08_dbg_show NULL | ||
298 | #endif | ||
299 | |||
300 | /*----------------------------------------------------------------------*/ | ||
301 | |||
302 | static int mcp23s08_probe_one(struct spi_device *spi, unsigned addr, | ||
303 | unsigned type, unsigned base, unsigned pullups) | ||
304 | { | ||
305 | struct mcp23s08_driver_data *data = spi_get_drvdata(spi); | ||
306 | struct mcp23s08 *mcp = data->mcp[addr]; | ||
307 | int status; | ||
308 | |||
309 | mutex_init(&mcp->lock); | ||
310 | |||
311 | mcp->spi = spi; | ||
312 | mcp->addr = 0x40 | (addr << 1); | ||
313 | |||
314 | mcp->chip.direction_input = mcp23s08_direction_input; | ||
315 | mcp->chip.get = mcp23s08_get; | ||
316 | mcp->chip.direction_output = mcp23s08_direction_output; | ||
317 | mcp->chip.set = mcp23s08_set; | ||
318 | mcp->chip.dbg_show = mcp23s08_dbg_show; | ||
319 | |||
320 | if (type == MCP_TYPE_S17) { | ||
321 | mcp->ops = &mcp23s17_ops; | ||
322 | mcp->chip.ngpio = 16; | ||
323 | mcp->chip.label = "mcp23s17"; | ||
324 | } else { | ||
325 | mcp->ops = &mcp23s08_ops; | ||
326 | mcp->chip.ngpio = 8; | ||
327 | mcp->chip.label = "mcp23s08"; | ||
328 | } | ||
329 | mcp->chip.base = base; | ||
330 | mcp->chip.can_sleep = 1; | ||
331 | mcp->chip.dev = &spi->dev; | ||
332 | mcp->chip.owner = THIS_MODULE; | ||
333 | |||
334 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, | ||
335 | * and MCP_IOCON.HAEN = 1, so we work with all chips. | ||
336 | */ | ||
337 | status = mcp->ops->read(mcp, MCP_IOCON); | ||
338 | if (status < 0) | ||
339 | goto fail; | ||
340 | if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { | ||
341 | /* mcp23s17 has IOCON twice, make sure they are in sync */ | ||
342 | status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); | ||
343 | status |= IOCON_HAEN | (IOCON_HAEN << 8); | ||
344 | status = mcp->ops->write(mcp, MCP_IOCON, status); | ||
345 | if (status < 0) | ||
346 | goto fail; | ||
347 | } | ||
348 | |||
349 | /* configure ~100K pullups */ | ||
350 | status = mcp->ops->write(mcp, MCP_GPPU, pullups); | ||
351 | if (status < 0) | ||
352 | goto fail; | ||
353 | |||
354 | status = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); | ||
355 | if (status < 0) | ||
356 | goto fail; | ||
357 | |||
358 | /* disable inverter on input */ | ||
359 | if (mcp->cache[MCP_IPOL] != 0) { | ||
360 | mcp->cache[MCP_IPOL] = 0; | ||
361 | status = mcp->ops->write(mcp, MCP_IPOL, 0); | ||
362 | if (status < 0) | ||
363 | goto fail; | ||
364 | } | ||
365 | |||
366 | /* disable irqs */ | ||
367 | if (mcp->cache[MCP_GPINTEN] != 0) { | ||
368 | mcp->cache[MCP_GPINTEN] = 0; | ||
369 | status = mcp->ops->write(mcp, MCP_GPINTEN, 0); | ||
370 | if (status < 0) | ||
371 | goto fail; | ||
372 | } | ||
373 | |||
374 | status = gpiochip_add(&mcp->chip); | ||
375 | fail: | ||
376 | if (status < 0) | ||
377 | dev_dbg(&spi->dev, "can't setup chip %d, --> %d\n", | ||
378 | addr, status); | ||
379 | return status; | ||
380 | } | ||
381 | |||
382 | static int mcp23s08_probe(struct spi_device *spi) | ||
383 | { | ||
384 | struct mcp23s08_platform_data *pdata; | ||
385 | unsigned addr; | ||
386 | unsigned chips = 0; | ||
387 | struct mcp23s08_driver_data *data; | ||
388 | int status, type; | ||
389 | unsigned base; | ||
390 | |||
391 | type = spi_get_device_id(spi)->driver_data; | ||
392 | |||
393 | pdata = spi->dev.platform_data; | ||
394 | if (!pdata || !gpio_is_valid(pdata->base)) { | ||
395 | dev_dbg(&spi->dev, "invalid or missing platform data\n"); | ||
396 | return -EINVAL; | ||
397 | } | ||
398 | |||
399 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { | ||
400 | if (!pdata->chip[addr].is_present) | ||
401 | continue; | ||
402 | chips++; | ||
403 | if ((type == MCP_TYPE_S08) && (addr > 3)) { | ||
404 | dev_err(&spi->dev, | ||
405 | "mcp23s08 only supports address 0..3\n"); | ||
406 | return -EINVAL; | ||
407 | } | ||
408 | } | ||
409 | if (!chips) | ||
410 | return -ENODEV; | ||
411 | |||
412 | data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08), | ||
413 | GFP_KERNEL); | ||
414 | if (!data) | ||
415 | return -ENOMEM; | ||
416 | spi_set_drvdata(spi, data); | ||
417 | |||
418 | base = pdata->base; | ||
419 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { | ||
420 | if (!pdata->chip[addr].is_present) | ||
421 | continue; | ||
422 | chips--; | ||
423 | data->mcp[addr] = &data->chip[chips]; | ||
424 | status = mcp23s08_probe_one(spi, addr, type, base, | ||
425 | pdata->chip[addr].pullups); | ||
426 | if (status < 0) | ||
427 | goto fail; | ||
428 | |||
429 | base += (type == MCP_TYPE_S17) ? 16 : 8; | ||
430 | } | ||
431 | data->ngpio = base - pdata->base; | ||
432 | |||
433 | /* NOTE: these chips have a relatively sane IRQ framework, with | ||
434 | * per-signal masking and level/edge triggering. It's not yet | ||
435 | * handled here... | ||
436 | */ | ||
437 | |||
438 | if (pdata->setup) { | ||
439 | status = pdata->setup(spi, | ||
440 | pdata->base, data->ngpio, | ||
441 | pdata->context); | ||
442 | if (status < 0) | ||
443 | dev_dbg(&spi->dev, "setup --> %d\n", status); | ||
444 | } | ||
445 | |||
446 | return 0; | ||
447 | |||
448 | fail: | ||
449 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { | ||
450 | int tmp; | ||
451 | |||
452 | if (!data->mcp[addr]) | ||
453 | continue; | ||
454 | tmp = gpiochip_remove(&data->mcp[addr]->chip); | ||
455 | if (tmp < 0) | ||
456 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); | ||
457 | } | ||
458 | kfree(data); | ||
459 | return status; | ||
460 | } | ||
461 | |||
462 | static int mcp23s08_remove(struct spi_device *spi) | ||
463 | { | ||
464 | struct mcp23s08_driver_data *data = spi_get_drvdata(spi); | ||
465 | struct mcp23s08_platform_data *pdata = spi->dev.platform_data; | ||
466 | unsigned addr; | ||
467 | int status = 0; | ||
468 | |||
469 | if (pdata->teardown) { | ||
470 | status = pdata->teardown(spi, | ||
471 | pdata->base, data->ngpio, | ||
472 | pdata->context); | ||
473 | if (status < 0) { | ||
474 | dev_err(&spi->dev, "%s --> %d\n", "teardown", status); | ||
475 | return status; | ||
476 | } | ||
477 | } | ||
478 | |||
479 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { | ||
480 | int tmp; | ||
481 | |||
482 | if (!data->mcp[addr]) | ||
483 | continue; | ||
484 | |||
485 | tmp = gpiochip_remove(&data->mcp[addr]->chip); | ||
486 | if (tmp < 0) { | ||
487 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); | ||
488 | status = tmp; | ||
489 | } | ||
490 | } | ||
491 | if (status == 0) | ||
492 | kfree(data); | ||
493 | return status; | ||
494 | } | ||
495 | |||
496 | static const struct spi_device_id mcp23s08_ids[] = { | ||
497 | { "mcp23s08", MCP_TYPE_S08 }, | ||
498 | { "mcp23s17", MCP_TYPE_S17 }, | ||
499 | { }, | ||
500 | }; | ||
501 | MODULE_DEVICE_TABLE(spi, mcp23s08_ids); | ||
502 | |||
503 | static struct spi_driver mcp23s08_driver = { | ||
504 | .probe = mcp23s08_probe, | ||
505 | .remove = mcp23s08_remove, | ||
506 | .id_table = mcp23s08_ids, | ||
507 | .driver = { | ||
508 | .name = "mcp23s08", | ||
509 | .owner = THIS_MODULE, | ||
510 | }, | ||
511 | }; | ||
512 | |||
513 | /*----------------------------------------------------------------------*/ | ||
514 | |||
515 | static int __init mcp23s08_init(void) | ||
516 | { | ||
517 | return spi_register_driver(&mcp23s08_driver); | ||
518 | } | ||
519 | /* register after spi postcore initcall and before | ||
520 | * subsys initcalls that may rely on these GPIOs | ||
521 | */ | ||
522 | subsys_initcall(mcp23s08_init); | ||
523 | |||
524 | static void __exit mcp23s08_exit(void) | ||
525 | { | ||
526 | spi_unregister_driver(&mcp23s08_driver); | ||
527 | } | ||
528 | module_exit(mcp23s08_exit); | ||
529 | |||
530 | MODULE_LICENSE("GPL"); | ||