Commit | Line | Data |
---|---|---|
e58b9e27 | 1 | /* |
752ad5e8 | 2 | * MCP23S08 SPI/GPIO gpio expander driver |
e58b9e27 DB |
3 | */ |
4 | ||
5 | #include <linux/kernel.h> | |
6 | #include <linux/device.h> | |
e58b9e27 | 7 | #include <linux/mutex.h> |
d120c17f | 8 | #include <linux/gpio.h> |
752ad5e8 | 9 | #include <linux/i2c.h> |
e58b9e27 DB |
10 | #include <linux/spi/spi.h> |
11 | #include <linux/spi/mcp23s08.h> | |
5a0e3ad6 | 12 | #include <linux/slab.h> |
0b7bb77f | 13 | #include <asm/byteorder.h> |
e58b9e27 | 14 | |
0b7bb77f PK |
15 | /** |
16 | * MCP types supported by driver | |
17 | */ | |
18 | #define MCP_TYPE_S08 0 | |
19 | #define MCP_TYPE_S17 1 | |
752ad5e8 PK |
20 | #define MCP_TYPE_008 2 |
21 | #define MCP_TYPE_017 3 | |
e58b9e27 DB |
22 | |
23 | /* Registers are all 8 bits wide. | |
24 | * | |
25 | * The mcp23s17 has twice as many bits, and can be configured to work | |
26 | * with either 16 bit registers or with two adjacent 8 bit banks. | |
e58b9e27 DB |
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 | ||
0b7bb77f PK |
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 | ||
e58b9e27 | 53 | struct mcp23s08 { |
e58b9e27 DB |
54 | u8 addr; |
55 | ||
0b7bb77f | 56 | u16 cache[11]; |
e58b9e27 DB |
57 | /* lock protects the cached values */ |
58 | struct mutex lock; | |
e58b9e27 DB |
59 | |
60 | struct gpio_chip chip; | |
61 | ||
0b7bb77f | 62 | const struct mcp23s08_ops *ops; |
d62b98f3 | 63 | void *data; /* ops specific data */ |
e58b9e27 DB |
64 | }; |
65 | ||
0b7bb77f | 66 | /* A given spi_device can represent up to eight mcp23sxx chips |
8f1cc3b1 DB |
67 | * sharing the same chipselect but using different addresses |
68 | * (e.g. chips #0 and #3 might be populated, but not #1 or $2). | |
69 | * Driver data holds all the per-chip data. | |
70 | */ | |
71 | struct mcp23s08_driver_data { | |
72 | unsigned ngpio; | |
0b7bb77f | 73 | struct mcp23s08 *mcp[8]; |
8f1cc3b1 DB |
74 | struct mcp23s08 chip[]; |
75 | }; | |
76 | ||
752ad5e8 PK |
77 | /*----------------------------------------------------------------------*/ |
78 | ||
79 | #ifdef CONFIG_I2C | |
80 | ||
81 | static int mcp23008_read(struct mcp23s08 *mcp, unsigned reg) | |
82 | { | |
83 | return i2c_smbus_read_byte_data(mcp->data, reg); | |
84 | } | |
85 | ||
86 | static int mcp23008_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) | |
87 | { | |
88 | return i2c_smbus_write_byte_data(mcp->data, reg, val); | |
89 | } | |
90 | ||
91 | static int | |
92 | mcp23008_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) | |
93 | { | |
94 | while (n--) { | |
95 | int ret = mcp23008_read(mcp, reg++); | |
96 | if (ret < 0) | |
97 | return ret; | |
98 | *vals++ = ret; | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static int mcp23017_read(struct mcp23s08 *mcp, unsigned reg) | |
105 | { | |
106 | return i2c_smbus_read_word_data(mcp->data, reg << 1); | |
107 | } | |
108 | ||
109 | static int mcp23017_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) | |
110 | { | |
111 | return i2c_smbus_write_word_data(mcp->data, reg << 1, val); | |
112 | } | |
113 | ||
114 | static int | |
115 | mcp23017_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) | |
116 | { | |
117 | while (n--) { | |
118 | int ret = mcp23017_read(mcp, reg++); | |
119 | if (ret < 0) | |
120 | return ret; | |
121 | *vals++ = ret; | |
122 | } | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | static const struct mcp23s08_ops mcp23008_ops = { | |
128 | .read = mcp23008_read, | |
129 | .write = mcp23008_write, | |
130 | .read_regs = mcp23008_read_regs, | |
131 | }; | |
132 | ||
133 | static const struct mcp23s08_ops mcp23017_ops = { | |
134 | .read = mcp23017_read, | |
135 | .write = mcp23017_write, | |
136 | .read_regs = mcp23017_read_regs, | |
137 | }; | |
138 | ||
139 | #endif /* CONFIG_I2C */ | |
140 | ||
141 | /*----------------------------------------------------------------------*/ | |
142 | ||
d62b98f3 PK |
143 | #ifdef CONFIG_SPI_MASTER |
144 | ||
e58b9e27 DB |
145 | static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) |
146 | { | |
147 | u8 tx[2], rx[1]; | |
148 | int status; | |
149 | ||
150 | tx[0] = mcp->addr | 0x01; | |
151 | tx[1] = reg; | |
d62b98f3 | 152 | status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); |
e58b9e27 DB |
153 | return (status < 0) ? status : rx[0]; |
154 | } | |
155 | ||
0b7bb77f | 156 | static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) |
e58b9e27 DB |
157 | { |
158 | u8 tx[3]; | |
159 | ||
160 | tx[0] = mcp->addr; | |
161 | tx[1] = reg; | |
162 | tx[2] = val; | |
d62b98f3 | 163 | return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); |
e58b9e27 DB |
164 | } |
165 | ||
166 | static int | |
0b7bb77f | 167 | mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) |
e58b9e27 | 168 | { |
0b7bb77f PK |
169 | u8 tx[2], *tmp; |
170 | int status; | |
e58b9e27 DB |
171 | |
172 | if ((n + reg) > sizeof mcp->cache) | |
173 | return -EINVAL; | |
174 | tx[0] = mcp->addr | 0x01; | |
175 | tx[1] = reg; | |
0b7bb77f PK |
176 | |
177 | tmp = (u8 *)vals; | |
d62b98f3 | 178 | status = spi_write_then_read(mcp->data, tx, sizeof tx, tmp, n); |
0b7bb77f PK |
179 | if (status >= 0) { |
180 | while (n--) | |
181 | vals[n] = tmp[n]; /* expand to 16bit */ | |
182 | } | |
183 | return status; | |
184 | } | |
185 | ||
186 | static int mcp23s17_read(struct mcp23s08 *mcp, unsigned reg) | |
187 | { | |
188 | u8 tx[2], rx[2]; | |
189 | int status; | |
190 | ||
191 | tx[0] = mcp->addr | 0x01; | |
192 | tx[1] = reg << 1; | |
d62b98f3 | 193 | status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); |
0b7bb77f PK |
194 | return (status < 0) ? status : (rx[0] | (rx[1] << 8)); |
195 | } | |
196 | ||
197 | static int mcp23s17_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) | |
198 | { | |
199 | u8 tx[4]; | |
200 | ||
201 | tx[0] = mcp->addr; | |
202 | tx[1] = reg << 1; | |
203 | tx[2] = val; | |
204 | tx[3] = val >> 8; | |
d62b98f3 | 205 | return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); |
0b7bb77f PK |
206 | } |
207 | ||
208 | static int | |
209 | mcp23s17_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) | |
210 | { | |
211 | u8 tx[2]; | |
212 | int status; | |
213 | ||
214 | if ((n + reg) > sizeof mcp->cache) | |
215 | return -EINVAL; | |
216 | tx[0] = mcp->addr | 0x01; | |
217 | tx[1] = reg << 1; | |
218 | ||
d62b98f3 | 219 | status = spi_write_then_read(mcp->data, tx, sizeof tx, |
0b7bb77f PK |
220 | (u8 *)vals, n * 2); |
221 | if (status >= 0) { | |
222 | while (n--) | |
223 | vals[n] = __le16_to_cpu((__le16)vals[n]); | |
224 | } | |
225 | ||
226 | return status; | |
e58b9e27 DB |
227 | } |
228 | ||
0b7bb77f PK |
229 | static const struct mcp23s08_ops mcp23s08_ops = { |
230 | .read = mcp23s08_read, | |
231 | .write = mcp23s08_write, | |
232 | .read_regs = mcp23s08_read_regs, | |
233 | }; | |
234 | ||
235 | static const struct mcp23s08_ops mcp23s17_ops = { | |
236 | .read = mcp23s17_read, | |
237 | .write = mcp23s17_write, | |
238 | .read_regs = mcp23s17_read_regs, | |
239 | }; | |
240 | ||
d62b98f3 | 241 | #endif /* CONFIG_SPI_MASTER */ |
0b7bb77f | 242 | |
e58b9e27 DB |
243 | /*----------------------------------------------------------------------*/ |
244 | ||
245 | static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) | |
246 | { | |
247 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | |
248 | int status; | |
249 | ||
250 | mutex_lock(&mcp->lock); | |
251 | mcp->cache[MCP_IODIR] |= (1 << offset); | |
0b7bb77f | 252 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); |
e58b9e27 DB |
253 | mutex_unlock(&mcp->lock); |
254 | return status; | |
255 | } | |
256 | ||
257 | static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) | |
258 | { | |
259 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | |
260 | int status; | |
261 | ||
262 | mutex_lock(&mcp->lock); | |
263 | ||
264 | /* REVISIT reading this clears any IRQ ... */ | |
0b7bb77f | 265 | status = mcp->ops->read(mcp, MCP_GPIO); |
e58b9e27 DB |
266 | if (status < 0) |
267 | status = 0; | |
268 | else { | |
269 | mcp->cache[MCP_GPIO] = status; | |
270 | status = !!(status & (1 << offset)); | |
271 | } | |
272 | mutex_unlock(&mcp->lock); | |
273 | return status; | |
274 | } | |
275 | ||
276 | static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) | |
277 | { | |
0b7bb77f | 278 | unsigned olat = mcp->cache[MCP_OLAT]; |
e58b9e27 DB |
279 | |
280 | if (value) | |
281 | olat |= mask; | |
282 | else | |
283 | olat &= ~mask; | |
284 | mcp->cache[MCP_OLAT] = olat; | |
0b7bb77f | 285 | return mcp->ops->write(mcp, MCP_OLAT, olat); |
e58b9e27 DB |
286 | } |
287 | ||
288 | static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) | |
289 | { | |
290 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | |
0b7bb77f | 291 | unsigned mask = 1 << offset; |
e58b9e27 DB |
292 | |
293 | mutex_lock(&mcp->lock); | |
294 | __mcp23s08_set(mcp, mask, value); | |
295 | mutex_unlock(&mcp->lock); | |
296 | } | |
297 | ||
298 | static int | |
299 | mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) | |
300 | { | |
301 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | |
0b7bb77f | 302 | unsigned mask = 1 << offset; |
e58b9e27 DB |
303 | int status; |
304 | ||
305 | mutex_lock(&mcp->lock); | |
306 | status = __mcp23s08_set(mcp, mask, value); | |
307 | if (status == 0) { | |
308 | mcp->cache[MCP_IODIR] &= ~mask; | |
0b7bb77f | 309 | status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); |
e58b9e27 DB |
310 | } |
311 | mutex_unlock(&mcp->lock); | |
312 | return status; | |
313 | } | |
314 | ||
315 | /*----------------------------------------------------------------------*/ | |
316 | ||
317 | #ifdef CONFIG_DEBUG_FS | |
318 | ||
319 | #include <linux/seq_file.h> | |
320 | ||
321 | /* | |
322 | * This shows more info than the generic gpio dump code: | |
323 | * pullups, deglitching, open drain drive. | |
324 | */ | |
325 | static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) | |
326 | { | |
327 | struct mcp23s08 *mcp; | |
328 | char bank; | |
1d1c1d9b | 329 | int t; |
e58b9e27 DB |
330 | unsigned mask; |
331 | ||
332 | mcp = container_of(chip, struct mcp23s08, chip); | |
333 | ||
334 | /* NOTE: we only handle one bank for now ... */ | |
0b7bb77f | 335 | bank = '0' + ((mcp->addr >> 1) & 0x7); |
e58b9e27 DB |
336 | |
337 | mutex_lock(&mcp->lock); | |
0b7bb77f | 338 | t = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); |
e58b9e27 DB |
339 | if (t < 0) { |
340 | seq_printf(s, " I/O ERROR %d\n", t); | |
341 | goto done; | |
342 | } | |
343 | ||
0b7bb77f | 344 | for (t = 0, mask = 1; t < chip->ngpio; t++, mask <<= 1) { |
e58b9e27 DB |
345 | const char *label; |
346 | ||
347 | label = gpiochip_is_requested(chip, t); | |
348 | if (!label) | |
349 | continue; | |
350 | ||
351 | seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s", | |
352 | chip->base + t, bank, t, label, | |
353 | (mcp->cache[MCP_IODIR] & mask) ? "in " : "out", | |
354 | (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", | |
355 | (mcp->cache[MCP_GPPU] & mask) ? " " : "up"); | |
356 | /* NOTE: ignoring the irq-related registers */ | |
357 | seq_printf(s, "\n"); | |
358 | } | |
359 | done: | |
360 | mutex_unlock(&mcp->lock); | |
361 | } | |
362 | ||
363 | #else | |
364 | #define mcp23s08_dbg_show NULL | |
365 | #endif | |
366 | ||
367 | /*----------------------------------------------------------------------*/ | |
368 | ||
d62b98f3 PK |
369 | static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, |
370 | void *data, unsigned addr, | |
0b7bb77f | 371 | unsigned type, unsigned base, unsigned pullups) |
e58b9e27 | 372 | { |
d62b98f3 | 373 | int status; |
e58b9e27 | 374 | |
e58b9e27 DB |
375 | mutex_init(&mcp->lock); |
376 | ||
d62b98f3 PK |
377 | mcp->data = data; |
378 | mcp->addr = addr; | |
e58b9e27 | 379 | |
e58b9e27 DB |
380 | mcp->chip.direction_input = mcp23s08_direction_input; |
381 | mcp->chip.get = mcp23s08_get; | |
382 | mcp->chip.direction_output = mcp23s08_direction_output; | |
383 | mcp->chip.set = mcp23s08_set; | |
384 | mcp->chip.dbg_show = mcp23s08_dbg_show; | |
385 | ||
d62b98f3 PK |
386 | switch (type) { |
387 | #ifdef CONFIG_SPI_MASTER | |
388 | case MCP_TYPE_S08: | |
0b7bb77f PK |
389 | mcp->ops = &mcp23s08_ops; |
390 | mcp->chip.ngpio = 8; | |
391 | mcp->chip.label = "mcp23s08"; | |
d62b98f3 PK |
392 | break; |
393 | ||
394 | case MCP_TYPE_S17: | |
395 | mcp->ops = &mcp23s17_ops; | |
396 | mcp->chip.ngpio = 16; | |
397 | mcp->chip.label = "mcp23s17"; | |
398 | break; | |
399 | #endif /* CONFIG_SPI_MASTER */ | |
400 | ||
752ad5e8 PK |
401 | #ifdef CONFIG_I2C |
402 | case MCP_TYPE_008: | |
403 | mcp->ops = &mcp23008_ops; | |
404 | mcp->chip.ngpio = 8; | |
405 | mcp->chip.label = "mcp23008"; | |
406 | break; | |
407 | ||
408 | case MCP_TYPE_017: | |
409 | mcp->ops = &mcp23017_ops; | |
410 | mcp->chip.ngpio = 16; | |
411 | mcp->chip.label = "mcp23017"; | |
412 | break; | |
413 | #endif /* CONFIG_I2C */ | |
414 | ||
d62b98f3 PK |
415 | default: |
416 | dev_err(dev, "invalid device type (%d)\n", type); | |
417 | return -EINVAL; | |
0b7bb77f | 418 | } |
d62b98f3 | 419 | |
8f1cc3b1 | 420 | mcp->chip.base = base; |
e58b9e27 | 421 | mcp->chip.can_sleep = 1; |
d62b98f3 | 422 | mcp->chip.dev = dev; |
d72cbed0 | 423 | mcp->chip.owner = THIS_MODULE; |
e58b9e27 | 424 | |
8f1cc3b1 DB |
425 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, |
426 | * and MCP_IOCON.HAEN = 1, so we work with all chips. | |
427 | */ | |
0b7bb77f | 428 | status = mcp->ops->read(mcp, MCP_IOCON); |
e58b9e27 DB |
429 | if (status < 0) |
430 | goto fail; | |
8f1cc3b1 | 431 | if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { |
0b7bb77f PK |
432 | /* mcp23s17 has IOCON twice, make sure they are in sync */ |
433 | status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); | |
434 | status |= IOCON_HAEN | (IOCON_HAEN << 8); | |
435 | status = mcp->ops->write(mcp, MCP_IOCON, status); | |
e58b9e27 DB |
436 | if (status < 0) |
437 | goto fail; | |
438 | } | |
439 | ||
440 | /* configure ~100K pullups */ | |
0b7bb77f | 441 | status = mcp->ops->write(mcp, MCP_GPPU, pullups); |
e58b9e27 DB |
442 | if (status < 0) |
443 | goto fail; | |
444 | ||
0b7bb77f | 445 | status = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); |
e58b9e27 DB |
446 | if (status < 0) |
447 | goto fail; | |
448 | ||
449 | /* disable inverter on input */ | |
450 | if (mcp->cache[MCP_IPOL] != 0) { | |
451 | mcp->cache[MCP_IPOL] = 0; | |
0b7bb77f PK |
452 | status = mcp->ops->write(mcp, MCP_IPOL, 0); |
453 | if (status < 0) | |
454 | goto fail; | |
e58b9e27 DB |
455 | } |
456 | ||
457 | /* disable irqs */ | |
458 | if (mcp->cache[MCP_GPINTEN] != 0) { | |
459 | mcp->cache[MCP_GPINTEN] = 0; | |
0b7bb77f | 460 | status = mcp->ops->write(mcp, MCP_GPINTEN, 0); |
8f1cc3b1 DB |
461 | if (status < 0) |
462 | goto fail; | |
e58b9e27 DB |
463 | } |
464 | ||
465 | status = gpiochip_add(&mcp->chip); | |
8f1cc3b1 DB |
466 | fail: |
467 | if (status < 0) | |
d62b98f3 PK |
468 | dev_dbg(dev, "can't setup chip %d, --> %d\n", |
469 | addr, status); | |
8f1cc3b1 DB |
470 | return status; |
471 | } | |
472 | ||
752ad5e8 PK |
473 | /*----------------------------------------------------------------------*/ |
474 | ||
475 | #ifdef CONFIG_I2C | |
476 | ||
477 | static int __devinit mcp230xx_probe(struct i2c_client *client, | |
478 | const struct i2c_device_id *id) | |
479 | { | |
480 | struct mcp23s08_platform_data *pdata; | |
481 | struct mcp23s08 *mcp; | |
482 | int status; | |
483 | ||
484 | pdata = client->dev.platform_data; | |
485 | if (!pdata || !gpio_is_valid(pdata->base)) { | |
486 | dev_dbg(&client->dev, "invalid or missing platform data\n"); | |
487 | return -EINVAL; | |
488 | } | |
489 | ||
490 | mcp = kzalloc(sizeof *mcp, GFP_KERNEL); | |
491 | if (!mcp) | |
492 | return -ENOMEM; | |
493 | ||
494 | status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, | |
495 | id->driver_data, pdata->base, | |
496 | pdata->chip[0].pullups); | |
497 | if (status) | |
498 | goto fail; | |
499 | ||
500 | i2c_set_clientdata(client, mcp); | |
501 | ||
502 | return 0; | |
503 | ||
504 | fail: | |
505 | kfree(mcp); | |
506 | ||
507 | return status; | |
508 | } | |
509 | ||
510 | static int __devexit mcp230xx_remove(struct i2c_client *client) | |
511 | { | |
512 | struct mcp23s08 *mcp = i2c_get_clientdata(client); | |
513 | int status; | |
514 | ||
515 | status = gpiochip_remove(&mcp->chip); | |
516 | if (status == 0) | |
517 | kfree(mcp); | |
518 | ||
519 | return status; | |
520 | } | |
521 | ||
522 | static const struct i2c_device_id mcp230xx_id[] = { | |
523 | { "mcp23008", MCP_TYPE_008 }, | |
524 | { "mcp23017", MCP_TYPE_017 }, | |
525 | { }, | |
526 | }; | |
527 | MODULE_DEVICE_TABLE(i2c, mcp230xx_id); | |
528 | ||
529 | static struct i2c_driver mcp230xx_driver = { | |
530 | .driver = { | |
531 | .name = "mcp230xx", | |
532 | .owner = THIS_MODULE, | |
533 | }, | |
534 | .probe = mcp230xx_probe, | |
535 | .remove = __devexit_p(mcp230xx_remove), | |
536 | .id_table = mcp230xx_id, | |
537 | }; | |
538 | ||
539 | static int __init mcp23s08_i2c_init(void) | |
540 | { | |
541 | return i2c_add_driver(&mcp230xx_driver); | |
542 | } | |
543 | ||
544 | static void mcp23s08_i2c_exit(void) | |
545 | { | |
546 | i2c_del_driver(&mcp230xx_driver); | |
547 | } | |
548 | ||
549 | #else | |
550 | ||
551 | static int __init mcp23s08_i2c_init(void) { return 0; } | |
552 | static void mcp23s08_i2c_exit(void) { } | |
553 | ||
554 | #endif /* CONFIG_I2C */ | |
555 | ||
556 | /*----------------------------------------------------------------------*/ | |
557 | ||
d62b98f3 PK |
558 | #ifdef CONFIG_SPI_MASTER |
559 | ||
8f1cc3b1 DB |
560 | static int mcp23s08_probe(struct spi_device *spi) |
561 | { | |
562 | struct mcp23s08_platform_data *pdata; | |
563 | unsigned addr; | |
564 | unsigned chips = 0; | |
565 | struct mcp23s08_driver_data *data; | |
0b7bb77f | 566 | int status, type; |
8f1cc3b1 DB |
567 | unsigned base; |
568 | ||
0b7bb77f PK |
569 | type = spi_get_device_id(spi)->driver_data; |
570 | ||
8f1cc3b1 | 571 | pdata = spi->dev.platform_data; |
a342d215 BD |
572 | if (!pdata || !gpio_is_valid(pdata->base)) { |
573 | dev_dbg(&spi->dev, "invalid or missing platform data\n"); | |
574 | return -EINVAL; | |
575 | } | |
8f1cc3b1 | 576 | |
0b7bb77f | 577 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { |
8f1cc3b1 DB |
578 | if (!pdata->chip[addr].is_present) |
579 | continue; | |
580 | chips++; | |
0b7bb77f PK |
581 | if ((type == MCP_TYPE_S08) && (addr > 3)) { |
582 | dev_err(&spi->dev, | |
583 | "mcp23s08 only supports address 0..3\n"); | |
584 | return -EINVAL; | |
585 | } | |
8f1cc3b1 DB |
586 | } |
587 | if (!chips) | |
588 | return -ENODEV; | |
589 | ||
590 | data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08), | |
591 | GFP_KERNEL); | |
592 | if (!data) | |
593 | return -ENOMEM; | |
594 | spi_set_drvdata(spi, data); | |
595 | ||
596 | base = pdata->base; | |
0b7bb77f | 597 | for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { |
8f1cc3b1 DB |
598 | if (!pdata->chip[addr].is_present) |
599 | continue; | |
600 | chips--; | |
601 | data->mcp[addr] = &data->chip[chips]; | |
d62b98f3 PK |
602 | status = mcp23s08_probe_one(data->mcp[addr], &spi->dev, spi, |
603 | 0x40 | (addr << 1), type, base, | |
0b7bb77f | 604 | pdata->chip[addr].pullups); |
8f1cc3b1 DB |
605 | if (status < 0) |
606 | goto fail; | |
0b7bb77f PK |
607 | |
608 | base += (type == MCP_TYPE_S17) ? 16 : 8; | |
8f1cc3b1 DB |
609 | } |
610 | data->ngpio = base - pdata->base; | |
e58b9e27 DB |
611 | |
612 | /* NOTE: these chips have a relatively sane IRQ framework, with | |
613 | * per-signal masking and level/edge triggering. It's not yet | |
614 | * handled here... | |
615 | */ | |
616 | ||
e58b9e27 DB |
617 | return 0; |
618 | ||
619 | fail: | |
0b7bb77f | 620 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { |
8f1cc3b1 DB |
621 | int tmp; |
622 | ||
623 | if (!data->mcp[addr]) | |
624 | continue; | |
625 | tmp = gpiochip_remove(&data->mcp[addr]->chip); | |
626 | if (tmp < 0) | |
627 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); | |
628 | } | |
629 | kfree(data); | |
e58b9e27 DB |
630 | return status; |
631 | } | |
632 | ||
633 | static int mcp23s08_remove(struct spi_device *spi) | |
634 | { | |
8f1cc3b1 | 635 | struct mcp23s08_driver_data *data = spi_get_drvdata(spi); |
8f1cc3b1 | 636 | unsigned addr; |
e58b9e27 DB |
637 | int status = 0; |
638 | ||
0b7bb77f | 639 | for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { |
8f1cc3b1 DB |
640 | int tmp; |
641 | ||
642 | if (!data->mcp[addr]) | |
643 | continue; | |
644 | ||
645 | tmp = gpiochip_remove(&data->mcp[addr]->chip); | |
646 | if (tmp < 0) { | |
647 | dev_err(&spi->dev, "%s --> %d\n", "remove", tmp); | |
648 | status = tmp; | |
649 | } | |
650 | } | |
e58b9e27 | 651 | if (status == 0) |
8f1cc3b1 | 652 | kfree(data); |
e58b9e27 DB |
653 | return status; |
654 | } | |
655 | ||
0b7bb77f PK |
656 | static const struct spi_device_id mcp23s08_ids[] = { |
657 | { "mcp23s08", MCP_TYPE_S08 }, | |
658 | { "mcp23s17", MCP_TYPE_S17 }, | |
659 | { }, | |
660 | }; | |
661 | MODULE_DEVICE_TABLE(spi, mcp23s08_ids); | |
662 | ||
e58b9e27 DB |
663 | static struct spi_driver mcp23s08_driver = { |
664 | .probe = mcp23s08_probe, | |
665 | .remove = mcp23s08_remove, | |
0b7bb77f | 666 | .id_table = mcp23s08_ids, |
e58b9e27 DB |
667 | .driver = { |
668 | .name = "mcp23s08", | |
669 | .owner = THIS_MODULE, | |
670 | }, | |
671 | }; | |
672 | ||
d62b98f3 PK |
673 | static int __init mcp23s08_spi_init(void) |
674 | { | |
675 | return spi_register_driver(&mcp23s08_driver); | |
676 | } | |
677 | ||
678 | static void mcp23s08_spi_exit(void) | |
679 | { | |
680 | spi_unregister_driver(&mcp23s08_driver); | |
681 | } | |
682 | ||
683 | #else | |
684 | ||
685 | static int __init mcp23s08_spi_init(void) { return 0; } | |
686 | static void mcp23s08_spi_exit(void) { } | |
687 | ||
688 | #endif /* CONFIG_SPI_MASTER */ | |
689 | ||
e58b9e27 DB |
690 | /*----------------------------------------------------------------------*/ |
691 | ||
692 | static int __init mcp23s08_init(void) | |
693 | { | |
752ad5e8 PK |
694 | int ret; |
695 | ||
696 | ret = mcp23s08_spi_init(); | |
697 | if (ret) | |
698 | goto spi_fail; | |
699 | ||
700 | ret = mcp23s08_i2c_init(); | |
701 | if (ret) | |
702 | goto i2c_fail; | |
703 | ||
704 | return 0; | |
705 | ||
706 | i2c_fail: | |
707 | mcp23s08_spi_exit(); | |
708 | spi_fail: | |
709 | return ret; | |
e58b9e27 | 710 | } |
752ad5e8 | 711 | /* register after spi/i2c postcore initcall and before |
673c0c00 DB |
712 | * subsys initcalls that may rely on these GPIOs |
713 | */ | |
714 | subsys_initcall(mcp23s08_init); | |
e58b9e27 DB |
715 | |
716 | static void __exit mcp23s08_exit(void) | |
717 | { | |
d62b98f3 | 718 | mcp23s08_spi_exit(); |
752ad5e8 | 719 | mcp23s08_i2c_exit(); |
e58b9e27 DB |
720 | } |
721 | module_exit(mcp23s08_exit); | |
722 | ||
723 | MODULE_LICENSE("GPL"); |