aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpio/mcp23s08.c
diff options
context:
space:
mode:
authorDavid Brownell <david-b@pacbell.net>2008-07-25 04:46:09 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2008-07-25 13:53:30 -0400
commit8f1cc3b10e6ee0c5c7c8ed27f8771c4f252b4862 (patch)
treea64611af44cf46aea2e2b145fad85042bb647a3d /drivers/gpio/mcp23s08.c
parentd8f388d8dc8d4f36539dd37c1fff62cc404ea0fc (diff)
gpio: mcp23s08 handles multiple chips per chipselect
Teach the mcp23s08 driver about a curious feature of these chips: up to four of them can share the same chipselect, with the SPI signals wired in parallel, by matching two bits in the first protocol byte against two address lines on the chip. This is handled by three software changes: * Platform data now holds an array of per-chip structs, not just one chip's address and pullup configuration. * Probe() and remove() now use another level of structure, wrapping an instance of the original structure for each mcp23s08 chip sharing that chipselect. * The HAEN bit is set, so that the hardware address bits can no longer be ignored (boot firmware may not have enabled them). The "one struct per chip" preserves the guts of the current code, but platform_data will need minor changes. OLD: /* incorrect "slave" ID may not have mattered */ .slave = 3, .pullups = BIT(3) | BIT(1) | BIT(0), NEW: /* slave address _must_ match chip's wiring */ .chip[3] = { .is_present = true, .pullups = BIT(3) | BIT(1) | BIT(0), }, There's no change in how things _behave_ for spi_device nodes with a single mcp23s08 chip. New multi-chip configurations assign GPIOs in sequence, without holes. The spi_device just resembles a bigger controller, but internally it has multiple gpio_chip instances. Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/gpio/mcp23s08.c')
-rw-r--r--drivers/gpio/mcp23s08.c133
1 files changed, 102 insertions, 31 deletions
diff --git a/drivers/gpio/mcp23s08.c b/drivers/gpio/mcp23s08.c
index 7efd7d3a81f9..8a1b405fefda 100644
--- a/drivers/gpio/mcp23s08.c
+++ b/drivers/gpio/mcp23s08.c
@@ -40,15 +40,26 @@ struct mcp23s08 {
40 struct spi_device *spi; 40 struct spi_device *spi;
41 u8 addr; 41 u8 addr;
42 42
43 u8 cache[11];
43 /* lock protects the cached values */ 44 /* lock protects the cached values */
44 struct mutex lock; 45 struct mutex lock;
45 u8 cache[11];
46 46
47 struct gpio_chip chip; 47 struct gpio_chip chip;
48 48
49 struct work_struct work; 49 struct work_struct work;
50}; 50};
51 51
52/* A given spi_device can represent up to four mcp23s08 chips
53 * sharing the same chipselect but using different addresses
54 * (e.g. chips #0 and #3 might be populated, but not #1 or $2).
55 * Driver data holds all the per-chip data.
56 */
57struct mcp23s08_driver_data {
58 unsigned ngpio;
59 struct mcp23s08 *mcp[4];
60 struct mcp23s08 chip[];
61};
62
52static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) 63static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg)
53{ 64{
54 u8 tx[2], rx[1]; 65 u8 tx[2], rx[1];
@@ -208,25 +219,18 @@ done:
208 219
209/*----------------------------------------------------------------------*/ 220/*----------------------------------------------------------------------*/
210 221
211static int mcp23s08_probe(struct spi_device *spi) 222static int mcp23s08_probe_one(struct spi_device *spi, unsigned addr,
223 unsigned base, unsigned pullups)
212{ 224{
213 struct mcp23s08 *mcp; 225 struct mcp23s08_driver_data *data = spi_get_drvdata(spi);
214 struct mcp23s08_platform_data *pdata; 226 struct mcp23s08 *mcp = data->mcp[addr];
215 int status; 227 int status;
216 int do_update = 0; 228 int do_update = 0;
217 229
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); 230 mutex_init(&mcp->lock);
227 231
228 mcp->spi = spi; 232 mcp->spi = spi;
229 mcp->addr = 0x40 | (pdata->slave << 1); 233 mcp->addr = 0x40 | (addr << 1);
230 234
231 mcp->chip.label = "mcp23s08", 235 mcp->chip.label = "mcp23s08",
232 236
@@ -236,27 +240,28 @@ static int mcp23s08_probe(struct spi_device *spi)
236 mcp->chip.set = mcp23s08_set; 240 mcp->chip.set = mcp23s08_set;
237 mcp->chip.dbg_show = mcp23s08_dbg_show; 241 mcp->chip.dbg_show = mcp23s08_dbg_show;
238 242
239 mcp->chip.base = pdata->base; 243 mcp->chip.base = base;
240 mcp->chip.ngpio = 8; 244 mcp->chip.ngpio = 8;
241 mcp->chip.can_sleep = 1; 245 mcp->chip.can_sleep = 1;
242 mcp->chip.dev = &spi->dev; 246 mcp->chip.dev = &spi->dev;
243 mcp->chip.owner = THIS_MODULE; 247 mcp->chip.owner = THIS_MODULE;
244 248
245 spi_set_drvdata(spi, mcp); 249 /* verify MCP_IOCON.SEQOP = 0, so sequential reads work,
246 250 * and MCP_IOCON.HAEN = 1, so we work with all chips.
247 /* verify MCP_IOCON.SEQOP = 0, so sequential reads work */ 251 */
248 status = mcp23s08_read(mcp, MCP_IOCON); 252 status = mcp23s08_read(mcp, MCP_IOCON);
249 if (status < 0) 253 if (status < 0)
250 goto fail; 254 goto fail;
251 if (status & IOCON_SEQOP) { 255 if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) {
252 status &= ~IOCON_SEQOP; 256 status &= ~IOCON_SEQOP;
257 status |= IOCON_HAEN;
253 status = mcp23s08_write(mcp, MCP_IOCON, (u8) status); 258 status = mcp23s08_write(mcp, MCP_IOCON, (u8) status);
254 if (status < 0) 259 if (status < 0)
255 goto fail; 260 goto fail;
256 } 261 }
257 262
258 /* configure ~100K pullups */ 263 /* configure ~100K pullups */
259 status = mcp23s08_write(mcp, MCP_GPPU, pdata->pullups); 264 status = mcp23s08_write(mcp, MCP_GPPU, pullups);
260 if (status < 0) 265 if (status < 0)
261 goto fail; 266 goto fail;
262 267
@@ -283,11 +288,58 @@ static int mcp23s08_probe(struct spi_device *spi)
283 tx[1] = MCP_IPOL; 288 tx[1] = MCP_IPOL;
284 memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2); 289 memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2);
285 status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); 290 status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0);
286 291 if (status < 0)
287 /* FIXME check status... */ 292 goto fail;
288 } 293 }
289 294
290 status = gpiochip_add(&mcp->chip); 295 status = gpiochip_add(&mcp->chip);
296fail:
297 if (status < 0)
298 dev_dbg(&spi->dev, "can't setup chip %d, --> %d\n",
299 addr, status);
300 return status;
301}
302
303static int mcp23s08_probe(struct spi_device *spi)
304{
305 struct mcp23s08_platform_data *pdata;
306 unsigned addr;
307 unsigned chips = 0;
308 struct mcp23s08_driver_data *data;
309 int status;
310 unsigned base;
311
312 pdata = spi->dev.platform_data;
313 if (!pdata || !gpio_is_valid(pdata->base))
314 return -ENODEV;
315
316 for (addr = 0; addr < 4; addr++) {
317 if (!pdata->chip[addr].is_present)
318 continue;
319 chips++;
320 }
321 if (!chips)
322 return -ENODEV;
323
324 data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08),
325 GFP_KERNEL);
326 if (!data)
327 return -ENOMEM;
328 spi_set_drvdata(spi, data);
329
330 base = pdata->base;
331 for (addr = 0; addr < 4; addr++) {
332 if (!pdata->chip[addr].is_present)
333 continue;
334 chips--;
335 data->mcp[addr] = &data->chip[chips];
336 status = mcp23s08_probe_one(spi, addr, base,
337 pdata->chip[addr].pullups);
338 if (status < 0)
339 goto fail;
340 base += 8;
341 }
342 data->ngpio = base - pdata->base;
291 343
292 /* NOTE: these chips have a relatively sane IRQ framework, with 344 /* NOTE: these chips have a relatively sane IRQ framework, with
293 * per-signal masking and level/edge triggering. It's not yet 345 * per-signal masking and level/edge triggering. It's not yet
@@ -295,8 +347,9 @@ static int mcp23s08_probe(struct spi_device *spi)
295 */ 347 */
296 348
297 if (pdata->setup) { 349 if (pdata->setup) {
298 status = pdata->setup(spi, mcp->chip.base, 350 status = pdata->setup(spi,
299 mcp->chip.ngpio, pdata->context); 351 pdata->base, data->ngpio,
352 pdata->context);
300 if (status < 0) 353 if (status < 0)
301 dev_dbg(&spi->dev, "setup --> %d\n", status); 354 dev_dbg(&spi->dev, "setup --> %d\n", status);
302 } 355 }
@@ -304,19 +357,29 @@ static int mcp23s08_probe(struct spi_device *spi)
304 return 0; 357 return 0;
305 358
306fail: 359fail:
307 kfree(mcp); 360 for (addr = 0; addr < 4; addr++) {
361 int tmp;
362
363 if (!data->mcp[addr])
364 continue;
365 tmp = gpiochip_remove(&data->mcp[addr]->chip);
366 if (tmp < 0)
367 dev_err(&spi->dev, "%s --> %d\n", "remove", tmp);
368 }
369 kfree(data);
308 return status; 370 return status;
309} 371}
310 372
311static int mcp23s08_remove(struct spi_device *spi) 373static int mcp23s08_remove(struct spi_device *spi)
312{ 374{
313 struct mcp23s08 *mcp = spi_get_drvdata(spi); 375 struct mcp23s08_driver_data *data = spi_get_drvdata(spi);
314 struct mcp23s08_platform_data *pdata = spi->dev.platform_data; 376 struct mcp23s08_platform_data *pdata = spi->dev.platform_data;
377 unsigned addr;
315 int status = 0; 378 int status = 0;
316 379
317 if (pdata->teardown) { 380 if (pdata->teardown) {
318 status = pdata->teardown(spi, 381 status = pdata->teardown(spi,
319 mcp->chip.base, mcp->chip.ngpio, 382 pdata->base, data->ngpio,
320 pdata->context); 383 pdata->context);
321 if (status < 0) { 384 if (status < 0) {
322 dev_err(&spi->dev, "%s --> %d\n", "teardown", status); 385 dev_err(&spi->dev, "%s --> %d\n", "teardown", status);
@@ -324,11 +387,20 @@ static int mcp23s08_remove(struct spi_device *spi)
324 } 387 }
325 } 388 }
326 389
327 status = gpiochip_remove(&mcp->chip); 390 for (addr = 0; addr < 4; addr++) {
391 int tmp;
392
393 if (!data->mcp[addr])
394 continue;
395
396 tmp = gpiochip_remove(&data->mcp[addr]->chip);
397 if (tmp < 0) {
398 dev_err(&spi->dev, "%s --> %d\n", "remove", tmp);
399 status = tmp;
400 }
401 }
328 if (status == 0) 402 if (status == 0)
329 kfree(mcp); 403 kfree(data);
330 else
331 dev_err(&spi->dev, "%s --> %d\n", "remove", status);
332 return status; 404 return status;
333} 405}
334 406
@@ -356,4 +428,3 @@ static void __exit mcp23s08_exit(void)
356module_exit(mcp23s08_exit); 428module_exit(mcp23s08_exit);
357 429
358MODULE_LICENSE("GPL"); 430MODULE_LICENSE("GPL");
359