diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-02-05 01:28:25 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-05 12:44:13 -0500 |
commit | e58b9e2762a6ef99e20dba47aba21b911658541d (patch) | |
tree | 0097fbe262bb4d1944bfb3c9a922ec36b90851c5 /drivers | |
parent | 15fae37d9f5f21571a9618d8353164b6ddfea6f6 (diff) |
mcp23s08 spi gpio expander support
Basic driver for 8-bit SPI based MCP23S08 GPIO expander, without support for
IRQs or the shared chipselect mechanism.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Eric Miao <eric.miao@marvell.com>
Cc: Sam Ravnborg <sam@ravnborg.org>
Cc: Haavard Skinnemoen <hskinnemoen@atmel.com>
Cc: Philipp Zabel <philipp.zabel@gmail.com>
Cc: Russell King <rmk@arm.linux.org.uk>
Cc: Ben Gardner <bgardner@wabtec.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpio/Kconfig | 7 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/mcp23s08.c | 357 |
3 files changed, 365 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 36d5d6aefc0e..d924a32c089a 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -52,4 +52,11 @@ config GPIO_PCF857X | |||
52 | 52 | ||
53 | comment "SPI GPIO expanders:" | 53 | comment "SPI GPIO expanders:" |
54 | 54 | ||
55 | config GPIO_MCP23S08 | ||
56 | tristate "Microchip MCP23S08 I/O expander" | ||
57 | depends on SPI_MASTER | ||
58 | help | ||
59 | SPI driver for Microchip MCP23S08 I/O expander. This provides | ||
60 | a GPIO interface supporting inputs and outputs. | ||
61 | |||
55 | endmenu | 62 | endmenu |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b469ab24fb11..a1fd877ac527 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -4,4 +4,5 @@ ccflags-$(CONFIG_DEBUG_GPIO) += -DDEBUG | |||
4 | 4 | ||
5 | obj-$(CONFIG_HAVE_GPIO_LIB) += gpiolib.o | 5 | obj-$(CONFIG_HAVE_GPIO_LIB) += gpiolib.o |
6 | 6 | ||
7 | obj-$(CONFIG_GPIO_MCP23S08) += mcp23s08.o | ||
7 | obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o | 8 | obj-$(CONFIG_GPIO_PCF857X) += pcf857x.o |
diff --git a/drivers/gpio/mcp23s08.c b/drivers/gpio/mcp23s08.c new file mode 100644 index 000000000000..bb60e8c1a1f0 --- /dev/null +++ b/drivers/gpio/mcp23s08.c | |||
@@ -0,0 +1,357 @@ | |||
1 | /* | ||
2 | * mcp23s08.c - 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 | |||
10 | #include <linux/spi/spi.h> | ||
11 | #include <linux/spi/mcp23s08.h> | ||
12 | |||
13 | #include <asm/gpio.h> | ||
14 | |||
15 | |||
16 | /* Registers are all 8 bits wide. | ||
17 | * | ||
18 | * The mcp23s17 has twice as many bits, and can be configured to work | ||
19 | * with either 16 bit registers or with two adjacent 8 bit banks. | ||
20 | * | ||
21 | * Also, there are I2C versions of both chips. | ||
22 | */ | ||
23 | #define MCP_IODIR 0x00 /* init/reset: all ones */ | ||
24 | #define MCP_IPOL 0x01 | ||
25 | #define MCP_GPINTEN 0x02 | ||
26 | #define MCP_DEFVAL 0x03 | ||
27 | #define MCP_INTCON 0x04 | ||
28 | #define MCP_IOCON 0x05 | ||
29 | # define IOCON_SEQOP (1 << 5) | ||
30 | # define IOCON_HAEN (1 << 3) | ||
31 | # define IOCON_ODR (1 << 2) | ||
32 | # define IOCON_INTPOL (1 << 1) | ||
33 | #define MCP_GPPU 0x06 | ||
34 | #define MCP_INTF 0x07 | ||
35 | #define MCP_INTCAP 0x08 | ||
36 | #define MCP_GPIO 0x09 | ||
37 | #define MCP_OLAT 0x0a | ||
38 | |||
39 | struct mcp23s08 { | ||
40 | struct spi_device *spi; | ||
41 | u8 addr; | ||
42 | |||
43 | /* lock protects the cached values */ | ||
44 | struct mutex lock; | ||
45 | u8 cache[11]; | ||
46 | |||
47 | struct gpio_chip chip; | ||
48 | |||
49 | struct work_struct work; | ||
50 | }; | ||
51 | |||
52 | static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) | ||
53 | { | ||
54 | u8 tx[2], rx[1]; | ||
55 | int status; | ||
56 | |||
57 | tx[0] = mcp->addr | 0x01; | ||
58 | tx[1] = reg; | ||
59 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, rx, sizeof rx); | ||
60 | return (status < 0) ? status : rx[0]; | ||
61 | } | ||
62 | |||
63 | static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, u8 val) | ||
64 | { | ||
65 | u8 tx[3]; | ||
66 | |||
67 | tx[0] = mcp->addr; | ||
68 | tx[1] = reg; | ||
69 | tx[2] = val; | ||
70 | return spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); | ||
71 | } | ||
72 | |||
73 | static int | ||
74 | mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u8 *vals, unsigned n) | ||
75 | { | ||
76 | u8 tx[2]; | ||
77 | |||
78 | if ((n + reg) > sizeof mcp->cache) | ||
79 | return -EINVAL; | ||
80 | tx[0] = mcp->addr | 0x01; | ||
81 | tx[1] = reg; | ||
82 | return spi_write_then_read(mcp->spi, tx, sizeof tx, vals, n); | ||
83 | } | ||
84 | |||
85 | /*----------------------------------------------------------------------*/ | ||
86 | |||
87 | static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) | ||
88 | { | ||
89 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
90 | int status; | ||
91 | |||
92 | mutex_lock(&mcp->lock); | ||
93 | mcp->cache[MCP_IODIR] |= (1 << offset); | ||
94 | status = mcp23s08_write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); | ||
95 | mutex_unlock(&mcp->lock); | ||
96 | return status; | ||
97 | } | ||
98 | |||
99 | static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) | ||
100 | { | ||
101 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
102 | int status; | ||
103 | |||
104 | mutex_lock(&mcp->lock); | ||
105 | |||
106 | /* REVISIT reading this clears any IRQ ... */ | ||
107 | status = mcp23s08_read(mcp, MCP_GPIO); | ||
108 | if (status < 0) | ||
109 | status = 0; | ||
110 | else { | ||
111 | mcp->cache[MCP_GPIO] = status; | ||
112 | status = !!(status & (1 << offset)); | ||
113 | } | ||
114 | mutex_unlock(&mcp->lock); | ||
115 | return status; | ||
116 | } | ||
117 | |||
118 | static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) | ||
119 | { | ||
120 | u8 olat = mcp->cache[MCP_OLAT]; | ||
121 | |||
122 | if (value) | ||
123 | olat |= mask; | ||
124 | else | ||
125 | olat &= ~mask; | ||
126 | mcp->cache[MCP_OLAT] = olat; | ||
127 | return mcp23s08_write(mcp, MCP_OLAT, olat); | ||
128 | } | ||
129 | |||
130 | static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) | ||
131 | { | ||
132 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
133 | u8 mask = 1 << offset; | ||
134 | |||
135 | mutex_lock(&mcp->lock); | ||
136 | __mcp23s08_set(mcp, mask, value); | ||
137 | mutex_unlock(&mcp->lock); | ||
138 | } | ||
139 | |||
140 | static int | ||
141 | mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) | ||
142 | { | ||
143 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
144 | u8 mask = 1 << offset; | ||
145 | int status; | ||
146 | |||
147 | mutex_lock(&mcp->lock); | ||
148 | status = __mcp23s08_set(mcp, mask, value); | ||
149 | if (status == 0) { | ||
150 | mcp->cache[MCP_IODIR] &= ~mask; | ||
151 | status = mcp23s08_write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); | ||
152 | } | ||
153 | mutex_unlock(&mcp->lock); | ||
154 | return status; | ||
155 | } | ||
156 | |||
157 | /*----------------------------------------------------------------------*/ | ||
158 | |||
159 | #ifdef CONFIG_DEBUG_FS | ||
160 | |||
161 | #include <linux/seq_file.h> | ||
162 | |||
163 | /* | ||
164 | * This shows more info than the generic gpio dump code: | ||
165 | * pullups, deglitching, open drain drive. | ||
166 | */ | ||
167 | static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) | ||
168 | { | ||
169 | struct mcp23s08 *mcp; | ||
170 | char bank; | ||
171 | unsigned t; | ||
172 | unsigned mask; | ||
173 | |||
174 | mcp = container_of(chip, struct mcp23s08, chip); | ||
175 | |||
176 | /* NOTE: we only handle one bank for now ... */ | ||
177 | bank = '0' + ((mcp->addr >> 1) & 0x3); | ||
178 | |||
179 | mutex_lock(&mcp->lock); | ||
180 | t = mcp23s08_read_regs(mcp, 0, mcp->cache, sizeof mcp->cache); | ||
181 | if (t < 0) { | ||
182 | seq_printf(s, " I/O ERROR %d\n", t); | ||
183 | goto done; | ||
184 | } | ||
185 | |||
186 | for (t = 0, mask = 1; t < 8; t++, mask <<= 1) { | ||
187 | const char *label; | ||
188 | |||
189 | label = gpiochip_is_requested(chip, t); | ||
190 | if (!label) | ||
191 | continue; | ||
192 | |||
193 | seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s", | ||
194 | chip->base + t, bank, t, label, | ||
195 | (mcp->cache[MCP_IODIR] & mask) ? "in " : "out", | ||
196 | (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", | ||
197 | (mcp->cache[MCP_GPPU] & mask) ? " " : "up"); | ||
198 | /* NOTE: ignoring the irq-related registers */ | ||
199 | seq_printf(s, "\n"); | ||
200 | } | ||
201 | done: | ||
202 | mutex_unlock(&mcp->lock); | ||
203 | } | ||
204 | |||
205 | #else | ||
206 | #define mcp23s08_dbg_show NULL | ||
207 | #endif | ||
208 | |||
209 | /*----------------------------------------------------------------------*/ | ||
210 | |||
211 | static int mcp23s08_probe(struct spi_device *spi) | ||
212 | { | ||
213 | struct mcp23s08 *mcp; | ||
214 | struct mcp23s08_platform_data *pdata; | ||
215 | int status; | ||
216 | int do_update = 0; | ||
217 | |||
218 | pdata = spi->dev.platform_data; | ||
219 | if (!pdata || pdata->slave > 3 || !pdata->base) | ||
220 | return -ENODEV; | ||
221 | |||
222 | mcp = kzalloc(sizeof *mcp, GFP_KERNEL); | ||
223 | if (!mcp) | ||
224 | return -ENOMEM; | ||
225 | |||
226 | mutex_init(&mcp->lock); | ||
227 | |||
228 | mcp->spi = spi; | ||
229 | mcp->addr = 0x40 | (pdata->slave << 1); | ||
230 | |||
231 | mcp->chip.label = "mcp23s08", | ||
232 | |||
233 | mcp->chip.direction_input = mcp23s08_direction_input; | ||
234 | mcp->chip.get = mcp23s08_get; | ||
235 | mcp->chip.direction_output = mcp23s08_direction_output; | ||
236 | mcp->chip.set = mcp23s08_set; | ||
237 | mcp->chip.dbg_show = mcp23s08_dbg_show; | ||
238 | |||
239 | mcp->chip.base = pdata->base; | ||
240 | mcp->chip.ngpio = 8; | ||
241 | mcp->chip.can_sleep = 1; | ||
242 | |||
243 | spi_set_drvdata(spi, mcp); | ||
244 | |||
245 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work */ | ||
246 | status = mcp23s08_read(mcp, MCP_IOCON); | ||
247 | if (status < 0) | ||
248 | goto fail; | ||
249 | if (status & IOCON_SEQOP) { | ||
250 | status &= ~IOCON_SEQOP; | ||
251 | status = mcp23s08_write(mcp, MCP_IOCON, (u8) status); | ||
252 | if (status < 0) | ||
253 | goto fail; | ||
254 | } | ||
255 | |||
256 | /* configure ~100K pullups */ | ||
257 | status = mcp23s08_write(mcp, MCP_GPPU, pdata->pullups); | ||
258 | if (status < 0) | ||
259 | goto fail; | ||
260 | |||
261 | status = mcp23s08_read_regs(mcp, 0, mcp->cache, sizeof mcp->cache); | ||
262 | if (status < 0) | ||
263 | goto fail; | ||
264 | |||
265 | /* disable inverter on input */ | ||
266 | if (mcp->cache[MCP_IPOL] != 0) { | ||
267 | mcp->cache[MCP_IPOL] = 0; | ||
268 | do_update = 1; | ||
269 | } | ||
270 | |||
271 | /* disable irqs */ | ||
272 | if (mcp->cache[MCP_GPINTEN] != 0) { | ||
273 | mcp->cache[MCP_GPINTEN] = 0; | ||
274 | do_update = 1; | ||
275 | } | ||
276 | |||
277 | if (do_update) { | ||
278 | u8 tx[4]; | ||
279 | |||
280 | tx[0] = mcp->addr; | ||
281 | tx[1] = MCP_IPOL; | ||
282 | memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2); | ||
283 | status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); | ||
284 | |||
285 | /* FIXME check status... */ | ||
286 | } | ||
287 | |||
288 | status = gpiochip_add(&mcp->chip); | ||
289 | |||
290 | /* NOTE: these chips have a relatively sane IRQ framework, with | ||
291 | * per-signal masking and level/edge triggering. It's not yet | ||
292 | * handled here... | ||
293 | */ | ||
294 | |||
295 | if (pdata->setup) { | ||
296 | status = pdata->setup(spi, mcp->chip.base, | ||
297 | mcp->chip.ngpio, pdata->context); | ||
298 | if (status < 0) | ||
299 | dev_dbg(&spi->dev, "setup --> %d\n", status); | ||
300 | } | ||
301 | |||
302 | return 0; | ||
303 | |||
304 | fail: | ||
305 | kfree(mcp); | ||
306 | return status; | ||
307 | } | ||
308 | |||
309 | static int mcp23s08_remove(struct spi_device *spi) | ||
310 | { | ||
311 | struct mcp23s08 *mcp = spi_get_drvdata(spi); | ||
312 | struct mcp23s08_platform_data *pdata = spi->dev.platform_data; | ||
313 | int status = 0; | ||
314 | |||
315 | if (pdata->teardown) { | ||
316 | status = pdata->teardown(spi, | ||
317 | mcp->chip.base, mcp->chip.ngpio, | ||
318 | pdata->context); | ||
319 | if (status < 0) { | ||
320 | dev_err(&spi->dev, "%s --> %d\n", "teardown", status); | ||
321 | return status; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | status = gpiochip_remove(&mcp->chip); | ||
326 | if (status == 0) | ||
327 | kfree(mcp); | ||
328 | else | ||
329 | dev_err(&spi->dev, "%s --> %d\n", "remove", status); | ||
330 | return status; | ||
331 | } | ||
332 | |||
333 | static struct spi_driver mcp23s08_driver = { | ||
334 | .probe = mcp23s08_probe, | ||
335 | .remove = mcp23s08_remove, | ||
336 | .driver = { | ||
337 | .name = "mcp23s08", | ||
338 | .owner = THIS_MODULE, | ||
339 | }, | ||
340 | }; | ||
341 | |||
342 | /*----------------------------------------------------------------------*/ | ||
343 | |||
344 | static int __init mcp23s08_init(void) | ||
345 | { | ||
346 | return spi_register_driver(&mcp23s08_driver); | ||
347 | } | ||
348 | module_init(mcp23s08_init); | ||
349 | |||
350 | static void __exit mcp23s08_exit(void) | ||
351 | { | ||
352 | spi_unregister_driver(&mcp23s08_driver); | ||
353 | } | ||
354 | module_exit(mcp23s08_exit); | ||
355 | |||
356 | MODULE_LICENSE("GPL"); | ||
357 | |||