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 | |||