diff options
Diffstat (limited to 'drivers/gpio')
| -rw-r--r-- | drivers/gpio/mcp23s08.c | 133 |
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 | */ | ||
| 57 | struct mcp23s08_driver_data { | ||
| 58 | unsigned ngpio; | ||
| 59 | struct mcp23s08 *mcp[4]; | ||
| 60 | struct mcp23s08 chip[]; | ||
| 61 | }; | ||
| 62 | |||
| 52 | static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) | 63 | static 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 | ||
| 211 | static int mcp23s08_probe(struct spi_device *spi) | 222 | static 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); |
| 296 | fail: | ||
| 297 | if (status < 0) | ||
| 298 | dev_dbg(&spi->dev, "can't setup chip %d, --> %d\n", | ||
| 299 | addr, status); | ||
| 300 | return status; | ||
| 301 | } | ||
| 302 | |||
| 303 | static 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 | ||
| 306 | fail: | 359 | fail: |
| 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 | ||
| 311 | static int mcp23s08_remove(struct spi_device *spi) | 373 | static 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) | |||
| 356 | module_exit(mcp23s08_exit); | 428 | module_exit(mcp23s08_exit); |
| 357 | 429 | ||
| 358 | MODULE_LICENSE("GPL"); | 430 | MODULE_LICENSE("GPL"); |
| 359 | |||
