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