From: Hans Verkuil <hans.verkuil@xxxxxxxxx> GPIO drivers call gpiochip_(un)lock_as_irq whenever they want to use a gpio as an interrupt. This is done when the irq is requested and it marks the gpio as in use by an interrupt. This is problematic for cases where a gpio pin is used as an interrupt pin, then, after the irq is disabled, is used as a regular gpio pin. Currently it is not possible to do this other than by first freeing the interrupt so gpiochip_unlock_as_irq is called, since an attempt to switch the gpio direction for output will fail since gpiolib believes that the gpio is in use for an interrupt and it does not know that it the irq is actually disabled. There are currently two drivers that would like to be able to do this: the tda998x_drv.c driver where a regular gpio pin needs to be temporarily reconfigured as an interrupt pin during CEC calibration, and the cec-gpio driver where you want to configure the gpio pin as an interrupt while waiting for traffic over the CEC bus, or as a regular pin when receiving or transmitting a CEC message. The solution is to add a new flag that is set when the irq is enabled, and have gpiod_direction_output check for that flag. We also add functions that drivers that do not use GPIOLIB_IRQCHIP can call when they enable/disable the irq. Signed-off-by: Hans Verkuil <hans.verkuil@xxxxxxxxx> --- drivers/gpio/gpiolib.c | 29 +++++++++++++++++++++++++++-- drivers/gpio/gpiolib.h | 1 + include/linux/gpio/driver.h | 2 ++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index b17f1142c70b..e52fa72f13d7 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -2588,8 +2588,9 @@ int gpiod_direction_output(struct gpio_desc *desc, int value) else value = !!value; - /* GPIOs used for IRQs shall not be set as output */ - if (test_bit(FLAG_USED_AS_IRQ, &desc->flags)) { + /* GPIOs used for enabled IRQs shall not be set as output */ + if (test_bit(FLAG_USED_AS_IRQ, &desc->flags) && + test_bit(FLAG_IRQ_IS_ENABLED, &desc->flags)) { gpiod_err(desc, "%s: tried to set a GPIO tied to an IRQ as output\n", __func__); @@ -3276,6 +3277,7 @@ int gpiochip_lock_as_irq(struct gpio_chip *chip, unsigned int offset) } set_bit(FLAG_USED_AS_IRQ, &desc->flags); + set_bit(FLAG_IRQ_IS_ENABLED, &desc->flags); /* * If the consumer has not set up a label (such as when the @@ -3306,6 +3308,7 @@ void gpiochip_unlock_as_irq(struct gpio_chip *chip, unsigned int offset) return; clear_bit(FLAG_USED_AS_IRQ, &desc->flags); + clear_bit(FLAG_IRQ_IS_ENABLED, &desc->flags); /* If we only had this marking, erase it */ if (desc->label && !strcmp(desc->label, "interrupt")) @@ -3313,6 +3316,28 @@ void gpiochip_unlock_as_irq(struct gpio_chip *chip, unsigned int offset) } EXPORT_SYMBOL_GPL(gpiochip_unlock_as_irq); +void gpiochip_disable_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct gpio_desc *desc = gpiochip_get_desc(chip, offset); + + if (!IS_ERR(desc) && + !WARN_ON(!test_bit(FLAG_USED_AS_IRQ, &desc->flags))) + clear_bit(FLAG_IRQ_IS_ENABLED, &desc->flags); +} +EXPORT_SYMBOL_GPL(gpiochip_disable_irq); + +void gpiochip_enable_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct gpio_desc *desc = gpiochip_get_desc(chip, offset); + + if (!IS_ERR(desc) && + !WARN_ON(!test_bit(FLAG_USED_AS_IRQ, &desc->flags))) { + WARN_ON(test_bit(FLAG_IS_OUT, &desc->flags)); + set_bit(FLAG_IRQ_IS_ENABLED, &desc->flags); + } +} +EXPORT_SYMBOL_GPL(gpiochip_enable_irq); + bool gpiochip_line_is_irq(struct gpio_chip *chip, unsigned int offset) { if (offset >= chip->ngpio) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index a7e49fef73d4..e9a86f4b00e3 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -214,6 +214,7 @@ struct gpio_desc { #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ +#define FLAG_IRQ_IS_ENABLED 10 /* GPIO is connected to an enabled IRQ */ #define FLAG_IS_HOGGED 11 /* GPIO is hogged */ #define FLAG_TRANSITORY 12 /* GPIO may lose value in sleep or reset */ diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 380251955ad1..b3dcb7018714 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -397,6 +397,8 @@ void gpiochip_unlock_as_irq(struct gpio_chip *chip, unsigned int offset); bool gpiochip_line_is_irq(struct gpio_chip *chip, unsigned int offset); int gpiochip_reqres_irq(struct gpio_chip *chip, unsigned int offset); void gpiochip_relres_irq(struct gpio_chip *chip, unsigned int offset); +void gpiochip_disable_irq(struct gpio_chip *chip, unsigned int offset); +void gpiochip_enable_irq(struct gpio_chip *chip, unsigned int offset); /* Line status inquiry for drivers */ bool gpiochip_line_is_open_drain(struct gpio_chip *chip, unsigned int offset); -- 2.18.0