diff options
author | Lars Poeschel <poeschel@lemonage.de> | 2014-01-16 05:44:15 -0500 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2014-01-17 09:30:14 -0500 |
commit | 4e47f91bf741e011a90ceb6241b8d78141709733 (patch) | |
tree | 1febf3cbbf59a1202dc5d944d392ce28b6b851fe | |
parent | 785acec3eecf4c21bab9e24afb5d354b57a72e03 (diff) |
gpio: mcp23s08: Add irq functionality for i2c chips
This adds interrupt functionality for i2c chips to the driver.
They can act as a interrupt-controller and generate interrupts, if
the inputs change.
This is tested with a mcp23017 chip on an arm based platform.
v3:
- be a bit more clear that the irq functionality is also available
on spi versions of the chips, but the linux driver does not support
this yet
v2:
- some more word about irq-mirror property in binding doc
- use of_read_bool instead of of_find_property for
"interrupt-contrller" and "irq-mirror"
- cache the "interrupt-controller" for remove function
- do set the irq-mirror bit only if device is marked as
interrupt-controller
- do create the irq mapping and setup of irq_desc of all possible
interrupts in probe path instead of in gpio_to_irq
- mark gpios as in use as interrupts in irq in irq_startup and
unlock it in irq_shutdown
- rename virq to child_irq
- remove dev argument from mcp23s08_irq_setup function
- move gpiochip_add before mcp23s08_irq_setup in probe path
Signed-off-by: Lars Poeschel <poeschel@lemonage.de>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-rw-r--r-- | Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt | 28 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-mcp23s08.c | 248 |
3 files changed, 271 insertions, 6 deletions
diff --git a/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt b/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt index daa30174bcc1..3ddc7ccfe5f3 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt +++ b/Documentation/devicetree/bindings/gpio/gpio-mcp23s08.txt | |||
@@ -38,12 +38,38 @@ Required device specific properties (only for SPI chips): | |||
38 | removed. | 38 | removed. |
39 | - spi-max-frequency = The maximum frequency this chip is able to handle | 39 | - spi-max-frequency = The maximum frequency this chip is able to handle |
40 | 40 | ||
41 | Example I2C: | 41 | Optional properties: |
42 | - #interrupt-cells : Should be two. | ||
43 | - first cell is the pin number | ||
44 | - second cell is used to specify flags. | ||
45 | - interrupt-controller: Marks the device node as a interrupt controller. | ||
46 | NOTE: The interrupt functionality is only supported for i2c versions of the | ||
47 | chips. The spi chips can also do the interrupts, but this is not supported by | ||
48 | the linux driver yet. | ||
49 | |||
50 | Optional device specific properties: | ||
51 | - microchip,irq-mirror: Sets the mirror flag in the IOCON register. Devices | ||
52 | with two interrupt outputs (these are the devices ending with 17 and | ||
53 | those that have 16 IOs) have two IO banks: IO 0-7 form bank 1 and | ||
54 | IO 8-15 are bank 2. These chips have two different interrupt outputs: | ||
55 | One for bank 1 and another for bank 2. If irq-mirror is set, both | ||
56 | interrupts are generated regardless of the bank that an input change | ||
57 | occured on. If it is not set, the interrupt are only generated for the | ||
58 | bank they belong to. | ||
59 | On devices with only one interrupt output this property is useless. | ||
60 | |||
61 | Example I2C (with interrupt): | ||
42 | gpiom1: gpio@20 { | 62 | gpiom1: gpio@20 { |
43 | compatible = "microchip,mcp23017"; | 63 | compatible = "microchip,mcp23017"; |
44 | gpio-controller; | 64 | gpio-controller; |
45 | #gpio-cells = <2>; | 65 | #gpio-cells = <2>; |
46 | reg = <0x20>; | 66 | reg = <0x20>; |
67 | |||
68 | interrupt-parent = <&gpio1>; | ||
69 | interrupts = <17 IRQ_TYPE_LEVEL_LOW>; | ||
70 | interrupt-controller; | ||
71 | #interrupt-cells=<2>; | ||
72 | microchip,irq-mirror; | ||
47 | }; | 73 | }; |
48 | 74 | ||
49 | Example SPI: | 75 | Example SPI: |
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 858d489f6bae..2d49784109b5 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -722,6 +722,7 @@ config GPIO_MCP23S08 | |||
722 | SPI/I2C driver for Microchip MCP23S08/MCP23S17/MCP23008/MCP23017 | 722 | SPI/I2C driver for Microchip MCP23S08/MCP23S17/MCP23008/MCP23017 |
723 | I/O expanders. | 723 | I/O expanders. |
724 | This provides a GPIO interface supporting inputs and outputs. | 724 | This provides a GPIO interface supporting inputs and outputs. |
725 | The I2C versions of the chips can be used as interrupt-controller. | ||
725 | 726 | ||
726 | config GPIO_MC33880 | 727 | config GPIO_MC33880 |
727 | tristate "Freescale MC33880 high-side/low-side switch" | 728 | tristate "Freescale MC33880 high-side/low-side switch" |
diff --git a/drivers/gpio/gpio-mcp23s08.c b/drivers/gpio/gpio-mcp23s08.c index b16401ee4766..ef3fd48aca3b 100644 --- a/drivers/gpio/gpio-mcp23s08.c +++ b/drivers/gpio/gpio-mcp23s08.c | |||
@@ -1,5 +1,13 @@ | |||
1 | /* | 1 | /* |
2 | * MCP23S08 SPI/GPIO gpio expander driver | 2 | * MCP23S08 SPI/I2C GPIO gpio expander driver |
3 | * | ||
4 | * The inputs and outputs of the mcp23s08, mcp23s17, mcp23008 and mcp23017 are | ||
5 | * supported. | ||
6 | * For the I2C versions of the chips (mcp23008 and mcp23017) generation of | ||
7 | * interrupts is also supported. | ||
8 | * The hardware of the SPI versions of the chips (mcp23s08 and mcp23s17) is | ||
9 | * also capable of generating interrupts, but the linux driver does not | ||
10 | * support that yet. | ||
3 | */ | 11 | */ |
4 | 12 | ||
5 | #include <linux/kernel.h> | 13 | #include <linux/kernel.h> |
@@ -12,7 +20,8 @@ | |||
12 | #include <linux/spi/mcp23s08.h> | 20 | #include <linux/spi/mcp23s08.h> |
13 | #include <linux/slab.h> | 21 | #include <linux/slab.h> |
14 | #include <asm/byteorder.h> | 22 | #include <asm/byteorder.h> |
15 | #include <linux/of.h> | 23 | #include <linux/interrupt.h> |
24 | #include <linux/of_irq.h> | ||
16 | #include <linux/of_device.h> | 25 | #include <linux/of_device.h> |
17 | 26 | ||
18 | /** | 27 | /** |
@@ -34,6 +43,7 @@ | |||
34 | #define MCP_DEFVAL 0x03 | 43 | #define MCP_DEFVAL 0x03 |
35 | #define MCP_INTCON 0x04 | 44 | #define MCP_INTCON 0x04 |
36 | #define MCP_IOCON 0x05 | 45 | #define MCP_IOCON 0x05 |
46 | # define IOCON_MIRROR (1 << 6) | ||
37 | # define IOCON_SEQOP (1 << 5) | 47 | # define IOCON_SEQOP (1 << 5) |
38 | # define IOCON_HAEN (1 << 3) | 48 | # define IOCON_HAEN (1 << 3) |
39 | # define IOCON_ODR (1 << 2) | 49 | # define IOCON_ODR (1 << 2) |
@@ -57,8 +67,14 @@ struct mcp23s08 { | |||
57 | u8 addr; | 67 | u8 addr; |
58 | 68 | ||
59 | u16 cache[11]; | 69 | u16 cache[11]; |
70 | u16 irq_rise; | ||
71 | u16 irq_fall; | ||
72 | int irq; | ||
73 | bool irq_controller; | ||
60 | /* lock protects the cached values */ | 74 | /* lock protects the cached values */ |
61 | struct mutex lock; | 75 | struct mutex lock; |
76 | struct mutex irq_lock; | ||
77 | struct irq_domain *irq_domain; | ||
62 | 78 | ||
63 | struct gpio_chip chip; | 79 | struct gpio_chip chip; |
64 | 80 | ||
@@ -77,6 +93,11 @@ struct mcp23s08_driver_data { | |||
77 | struct mcp23s08 chip[]; | 93 | struct mcp23s08 chip[]; |
78 | }; | 94 | }; |
79 | 95 | ||
96 | /* This lock class tells lockdep that GPIO irqs are in a different | ||
97 | * category than their parents, so it won't report false recursion. | ||
98 | */ | ||
99 | static struct lock_class_key gpio_lock_class; | ||
100 | |||
80 | /*----------------------------------------------------------------------*/ | 101 | /*----------------------------------------------------------------------*/ |
81 | 102 | ||
82 | #if IS_ENABLED(CONFIG_I2C) | 103 | #if IS_ENABLED(CONFIG_I2C) |
@@ -316,6 +337,195 @@ mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) | |||
316 | } | 337 | } |
317 | 338 | ||
318 | /*----------------------------------------------------------------------*/ | 339 | /*----------------------------------------------------------------------*/ |
340 | static irqreturn_t mcp23s08_irq(int irq, void *data) | ||
341 | { | ||
342 | struct mcp23s08 *mcp = data; | ||
343 | int intcap, intf, i; | ||
344 | unsigned int child_irq; | ||
345 | |||
346 | mutex_lock(&mcp->lock); | ||
347 | intf = mcp->ops->read(mcp, MCP_INTF); | ||
348 | if (intf < 0) { | ||
349 | mutex_unlock(&mcp->lock); | ||
350 | return IRQ_HANDLED; | ||
351 | } | ||
352 | |||
353 | mcp->cache[MCP_INTF] = intf; | ||
354 | |||
355 | intcap = mcp->ops->read(mcp, MCP_INTCAP); | ||
356 | if (intcap < 0) { | ||
357 | mutex_unlock(&mcp->lock); | ||
358 | return IRQ_HANDLED; | ||
359 | } | ||
360 | |||
361 | mcp->cache[MCP_INTCAP] = intcap; | ||
362 | mutex_unlock(&mcp->lock); | ||
363 | |||
364 | |||
365 | for (i = 0; i < mcp->chip.ngpio; i++) { | ||
366 | if ((BIT(i) & mcp->cache[MCP_INTF]) && | ||
367 | ((BIT(i) & intcap & mcp->irq_rise) || | ||
368 | (mcp->irq_fall & ~intcap & BIT(i)))) { | ||
369 | child_irq = irq_find_mapping(mcp->irq_domain, i); | ||
370 | handle_nested_irq(child_irq); | ||
371 | } | ||
372 | } | ||
373 | |||
374 | return IRQ_HANDLED; | ||
375 | } | ||
376 | |||
377 | static int mcp23s08_gpio_to_irq(struct gpio_chip *chip, unsigned offset) | ||
378 | { | ||
379 | struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); | ||
380 | |||
381 | return irq_find_mapping(mcp->irq_domain, offset); | ||
382 | } | ||
383 | |||
384 | static void mcp23s08_irq_mask(struct irq_data *data) | ||
385 | { | ||
386 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
387 | unsigned int pos = data->hwirq; | ||
388 | |||
389 | mcp->cache[MCP_GPINTEN] &= ~BIT(pos); | ||
390 | } | ||
391 | |||
392 | static void mcp23s08_irq_unmask(struct irq_data *data) | ||
393 | { | ||
394 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
395 | unsigned int pos = data->hwirq; | ||
396 | |||
397 | mcp->cache[MCP_GPINTEN] |= BIT(pos); | ||
398 | } | ||
399 | |||
400 | static int mcp23s08_irq_set_type(struct irq_data *data, unsigned int type) | ||
401 | { | ||
402 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
403 | unsigned int pos = data->hwirq; | ||
404 | int status = 0; | ||
405 | |||
406 | if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { | ||
407 | mcp->cache[MCP_INTCON] &= ~BIT(pos); | ||
408 | mcp->irq_rise |= BIT(pos); | ||
409 | mcp->irq_fall |= BIT(pos); | ||
410 | } else if (type & IRQ_TYPE_EDGE_RISING) { | ||
411 | mcp->cache[MCP_INTCON] &= ~BIT(pos); | ||
412 | mcp->irq_rise |= BIT(pos); | ||
413 | mcp->irq_fall &= ~BIT(pos); | ||
414 | } else if (type & IRQ_TYPE_EDGE_FALLING) { | ||
415 | mcp->cache[MCP_INTCON] &= ~BIT(pos); | ||
416 | mcp->irq_rise &= ~BIT(pos); | ||
417 | mcp->irq_fall |= BIT(pos); | ||
418 | } else | ||
419 | return -EINVAL; | ||
420 | |||
421 | return status; | ||
422 | } | ||
423 | |||
424 | static void mcp23s08_irq_bus_lock(struct irq_data *data) | ||
425 | { | ||
426 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
427 | |||
428 | mutex_lock(&mcp->irq_lock); | ||
429 | } | ||
430 | |||
431 | static void mcp23s08_irq_bus_unlock(struct irq_data *data) | ||
432 | { | ||
433 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
434 | |||
435 | mutex_lock(&mcp->lock); | ||
436 | mcp->ops->write(mcp, MCP_GPINTEN, mcp->cache[MCP_GPINTEN]); | ||
437 | mcp->ops->write(mcp, MCP_DEFVAL, mcp->cache[MCP_DEFVAL]); | ||
438 | mcp->ops->write(mcp, MCP_INTCON, mcp->cache[MCP_INTCON]); | ||
439 | mutex_unlock(&mcp->lock); | ||
440 | mutex_unlock(&mcp->irq_lock); | ||
441 | } | ||
442 | |||
443 | static unsigned int mcp23s08_irq_startup(struct irq_data *data) | ||
444 | { | ||
445 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
446 | |||
447 | if (gpio_lock_as_irq(&mcp->chip, data->hwirq)) | ||
448 | dev_err(mcp->chip.dev, | ||
449 | "unable to lock HW IRQ %lu for IRQ usage\n", | ||
450 | data->hwirq); | ||
451 | |||
452 | mcp23s08_irq_unmask(data); | ||
453 | return 0; | ||
454 | } | ||
455 | |||
456 | static void mcp23s08_irq_shutdown(struct irq_data *data) | ||
457 | { | ||
458 | struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); | ||
459 | |||
460 | mcp23s08_irq_mask(data); | ||
461 | gpio_unlock_as_irq(&mcp->chip, data->hwirq); | ||
462 | } | ||
463 | |||
464 | static struct irq_chip mcp23s08_irq_chip = { | ||
465 | .name = "gpio-mcp23xxx", | ||
466 | .irq_mask = mcp23s08_irq_mask, | ||
467 | .irq_unmask = mcp23s08_irq_unmask, | ||
468 | .irq_set_type = mcp23s08_irq_set_type, | ||
469 | .irq_bus_lock = mcp23s08_irq_bus_lock, | ||
470 | .irq_bus_sync_unlock = mcp23s08_irq_bus_unlock, | ||
471 | .irq_startup = mcp23s08_irq_startup, | ||
472 | .irq_shutdown = mcp23s08_irq_shutdown, | ||
473 | }; | ||
474 | |||
475 | static int mcp23s08_irq_setup(struct mcp23s08 *mcp) | ||
476 | { | ||
477 | struct gpio_chip *chip = &mcp->chip; | ||
478 | int err, irq, j; | ||
479 | |||
480 | mutex_init(&mcp->irq_lock); | ||
481 | |||
482 | mcp->irq_domain = irq_domain_add_linear(chip->of_node, chip->ngpio, | ||
483 | &irq_domain_simple_ops, mcp); | ||
484 | if (!mcp->irq_domain) | ||
485 | return -ENODEV; | ||
486 | |||
487 | err = devm_request_threaded_irq(chip->dev, mcp->irq, NULL, mcp23s08_irq, | ||
488 | IRQF_TRIGGER_LOW | IRQF_ONESHOT, | ||
489 | dev_name(chip->dev), mcp); | ||
490 | if (err != 0) { | ||
491 | dev_err(chip->dev, "unable to request IRQ#%d: %d\n", | ||
492 | mcp->irq, err); | ||
493 | return err; | ||
494 | } | ||
495 | |||
496 | chip->to_irq = mcp23s08_gpio_to_irq; | ||
497 | |||
498 | for (j = 0; j < mcp->chip.ngpio; j++) { | ||
499 | irq = irq_create_mapping(mcp->irq_domain, j); | ||
500 | irq_set_lockdep_class(irq, &gpio_lock_class); | ||
501 | irq_set_chip_data(irq, mcp); | ||
502 | irq_set_chip(irq, &mcp23s08_irq_chip); | ||
503 | irq_set_nested_thread(irq, true); | ||
504 | #ifdef CONFIG_ARM | ||
505 | set_irq_flags(irq, IRQF_VALID); | ||
506 | #else | ||
507 | irq_set_noprobe(irq); | ||
508 | #endif | ||
509 | } | ||
510 | return 0; | ||
511 | } | ||
512 | |||
513 | static void mcp23s08_irq_teardown(struct mcp23s08 *mcp) | ||
514 | { | ||
515 | unsigned int irq, i; | ||
516 | |||
517 | free_irq(mcp->irq, mcp); | ||
518 | |||
519 | for (i = 0; i < mcp->chip.ngpio; i++) { | ||
520 | irq = irq_find_mapping(mcp->irq_domain, i); | ||
521 | if (irq > 0) | ||
522 | irq_dispose_mapping(irq); | ||
523 | } | ||
524 | |||
525 | irq_domain_remove(mcp->irq_domain); | ||
526 | } | ||
527 | |||
528 | /*----------------------------------------------------------------------*/ | ||
319 | 529 | ||
320 | #ifdef CONFIG_DEBUG_FS | 530 | #ifdef CONFIG_DEBUG_FS |
321 | 531 | ||
@@ -370,10 +580,11 @@ done: | |||
370 | /*----------------------------------------------------------------------*/ | 580 | /*----------------------------------------------------------------------*/ |
371 | 581 | ||
372 | static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, | 582 | static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, |
373 | void *data, unsigned addr, | 583 | void *data, unsigned addr, unsigned type, |
374 | unsigned type, unsigned base, unsigned pullups) | 584 | unsigned base, unsigned pullups) |
375 | { | 585 | { |
376 | int status; | 586 | int status; |
587 | bool mirror = false; | ||
377 | 588 | ||
378 | mutex_init(&mcp->lock); | 589 | mutex_init(&mcp->lock); |
379 | 590 | ||
@@ -432,13 +643,25 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, | |||
432 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, | 643 | /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, |
433 | * and MCP_IOCON.HAEN = 1, so we work with all chips. | 644 | * and MCP_IOCON.HAEN = 1, so we work with all chips. |
434 | */ | 645 | */ |
646 | |||
435 | status = mcp->ops->read(mcp, MCP_IOCON); | 647 | status = mcp->ops->read(mcp, MCP_IOCON); |
436 | if (status < 0) | 648 | if (status < 0) |
437 | goto fail; | 649 | goto fail; |
438 | if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { | 650 | |
651 | mcp->irq_controller = of_property_read_bool(mcp->chip.of_node, | ||
652 | "interrupt-controller"); | ||
653 | if (mcp->irq && mcp->irq_controller && (type == MCP_TYPE_017)) | ||
654 | mirror = of_property_read_bool(mcp->chip.of_node, | ||
655 | "microchip,irq-mirror"); | ||
656 | |||
657 | if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN) || mirror) { | ||
439 | /* mcp23s17 has IOCON twice, make sure they are in sync */ | 658 | /* mcp23s17 has IOCON twice, make sure they are in sync */ |
440 | status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); | 659 | status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); |
441 | status |= IOCON_HAEN | (IOCON_HAEN << 8); | 660 | status |= IOCON_HAEN | (IOCON_HAEN << 8); |
661 | status &= ~(IOCON_INTPOL | (IOCON_INTPOL << 8)); | ||
662 | if (mirror) | ||
663 | status |= IOCON_MIRROR | (IOCON_MIRROR << 8); | ||
664 | |||
442 | status = mcp->ops->write(mcp, MCP_IOCON, status); | 665 | status = mcp->ops->write(mcp, MCP_IOCON, status); |
443 | if (status < 0) | 666 | if (status < 0) |
444 | goto fail; | 667 | goto fail; |
@@ -470,6 +693,16 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, | |||
470 | } | 693 | } |
471 | 694 | ||
472 | status = gpiochip_add(&mcp->chip); | 695 | status = gpiochip_add(&mcp->chip); |
696 | if (status < 0) | ||
697 | goto fail; | ||
698 | |||
699 | if (mcp->irq && mcp->irq_controller) { | ||
700 | status = mcp23s08_irq_setup(mcp); | ||
701 | if (status) { | ||
702 | mcp23s08_irq_teardown(mcp); | ||
703 | goto fail; | ||
704 | } | ||
705 | } | ||
473 | fail: | 706 | fail: |
474 | if (status < 0) | 707 | if (status < 0) |
475 | dev_dbg(dev, "can't setup chip %d, --> %d\n", | 708 | dev_dbg(dev, "can't setup chip %d, --> %d\n", |
@@ -546,6 +779,7 @@ static int mcp230xx_probe(struct i2c_client *client, | |||
546 | if (match || !pdata) { | 779 | if (match || !pdata) { |
547 | base = -1; | 780 | base = -1; |
548 | pullups = 0; | 781 | pullups = 0; |
782 | client->irq = irq_of_parse_and_map(client->dev.of_node, 0); | ||
549 | } else { | 783 | } else { |
550 | if (!gpio_is_valid(pdata->base)) { | 784 | if (!gpio_is_valid(pdata->base)) { |
551 | dev_dbg(&client->dev, "invalid platform data\n"); | 785 | dev_dbg(&client->dev, "invalid platform data\n"); |
@@ -559,6 +793,7 @@ static int mcp230xx_probe(struct i2c_client *client, | |||
559 | if (!mcp) | 793 | if (!mcp) |
560 | return -ENOMEM; | 794 | return -ENOMEM; |
561 | 795 | ||
796 | mcp->irq = client->irq; | ||
562 | status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, | 797 | status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr, |
563 | id->driver_data, base, pullups); | 798 | id->driver_data, base, pullups); |
564 | if (status) | 799 | if (status) |
@@ -579,6 +814,9 @@ static int mcp230xx_remove(struct i2c_client *client) | |||
579 | struct mcp23s08 *mcp = i2c_get_clientdata(client); | 814 | struct mcp23s08 *mcp = i2c_get_clientdata(client); |
580 | int status; | 815 | int status; |
581 | 816 | ||
817 | if (client->irq && mcp->irq_controller) | ||
818 | mcp23s08_irq_teardown(mcp); | ||
819 | |||
582 | status = gpiochip_remove(&mcp->chip); | 820 | status = gpiochip_remove(&mcp->chip); |
583 | if (status == 0) | 821 | if (status == 0) |
584 | kfree(mcp); | 822 | kfree(mcp); |