From 592cbf46d46718c2e0aa2eee99e3b84de6e5d66f Mon Sep 17 00:00:00 2001 From: Ola Jeppsson Date: Mon, 9 May 2016 09:15:03 +0200 Subject: [PATCH] GPIO: Linux: Support IRQ_TYPE_EDGE_BOTH With this all Linux kernel interrupt API types are now supported by the driver. Branch with full history here: https://github.com/parallella/parallella-linux/tree/oh-gpio ChangeLog: commit 084e854e045df6161228348f8b442ed90e57bb24 Author: Ola Jeppsson Date: Sat May 7 19:45:43 2016 +0200 gpio: oh: Refactor oh_gpio_irq_handler Add oh_gpio_irq_handler_for_each helper function to avoid code duplication. commit 4559f5a3768dd436b921f6ee901ccbdf7646bb51 Author: Ola Jeppsson Date: Sat May 7 18:33:59 2016 +0200 gpio: oh: Add support for IRQ_TYPE_EDGE_BOTH Toggle IPOL based on the interrupt pin value for interrupt lines that are configured to be triggered on both the rising and falling edge. Signed-off-by: Ola Jeppsson --- src/gpio/driver/linux/gpio-oh.c | 99 +++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 11 deletions(-) diff --git a/src/gpio/driver/linux/gpio-oh.c b/src/gpio/driver/linux/gpio-oh.c index 31b576d0..854e0eb3 100644 --- a/src/gpio/driver/linux/gpio-oh.c +++ b/src/gpio/driver/linux/gpio-oh.c @@ -89,6 +89,25 @@ static inline u64 oh_gpio_reg_read(struct oh_gpio *gpio, unsigned long offset) return (u64) readl((u8 __iomem *) gpio->base_addr + offset); } +/** + * oh_gpio_get_value_locked - Get value of the specified pin + * @chip: gpio chip device + * @pin: gpio pin number + * + * Note: Caller must hold gpio->lock. + * + * Return: 0 if the pin is low, 1 if pin is high. + */ +static int oh_gpio_get_value_locked(struct gpio_chip *chip, unsigned pin) +{ + u64 data; + struct oh_gpio *gpio = to_oh_gpio(chip); + + data = oh_gpio_reg_read(gpio, OH_GPIO_IN); + + return (data >> pin) & 1; +} + /** * oh_gpio_get_value - Get value of the specified pin * @chip: gpio chip device @@ -98,15 +117,15 @@ static inline u64 oh_gpio_reg_read(struct oh_gpio *gpio, unsigned long offset) */ static int oh_gpio_get_value(struct gpio_chip *chip, unsigned pin) { - u64 data; + int ret; unsigned long flags; struct oh_gpio *gpio = to_oh_gpio(chip); spin_lock_irqsave(&gpio->lock, flags); - data = oh_gpio_reg_read(gpio, OH_GPIO_IN); + ret = oh_gpio_get_value_locked(chip, pin); spin_unlock_irqrestore(&gpio->lock, flags); - return (data >> pin) & 1; + return ret; } /** @@ -266,6 +285,29 @@ static void oh_gpio_irq_ack(struct irq_data *irq_data) spin_unlock_irqrestore(&gpio->lock, flags); } +/** + * oh_gpio_irq_next_edge_locked - Configure IPOL for next edge + * @gpio: oh gpio structure + * @pin: gpio pin number + * + * Note: Caller must hold gpio->lock. + */ +static void oh_gpio_irq_next_edge_locked(struct oh_gpio *gpio, unsigned pin) +{ + u64 ipol, mask; + + mask = BIT_ULL(pin); + + ipol = oh_gpio_reg_read(gpio, OH_GPIO_IPOL); + + if (oh_gpio_get_value_locked(&gpio->chip, pin)) + ipol &= ~mask; + else + ipol |= mask; + + oh_gpio_reg_write(ipol, gpio, OH_GPIO_IPOL); +} + /** * oh_gpio_irq_set_type - Set the irq type for a gpio pin * @irq_data: irq data containing irq number of gpio pin @@ -290,6 +332,8 @@ static int oh_gpio_irq_set_type(struct irq_data *irq_data, unsigned type) ipol = oh_gpio_reg_read(gpio, OH_GPIO_IPOL); switch (type) { + case IRQ_TYPE_EDGE_BOTH: + /* fall through, handle below */ case IRQ_TYPE_EDGE_RISING: itype &= ~mask; ipol |= mask; @@ -312,7 +356,11 @@ static int oh_gpio_irq_set_type(struct irq_data *irq_data, unsigned type) } oh_gpio_reg_write(itype, gpio, OH_GPIO_ITYPE); - oh_gpio_reg_write(ipol, gpio, OH_GPIO_IPOL); + + if (type == IRQ_TYPE_EDGE_BOTH) + oh_gpio_irq_next_edge_locked(gpio, pin); + else + oh_gpio_reg_write(ipol, gpio, OH_GPIO_IPOL); spin_unlock_irqrestore(&gpio->lock, flags); @@ -331,6 +379,40 @@ static struct irq_chip oh_gpio_irqchip = { .irq_set_type = oh_gpio_irq_set_type, }; +/** + * oh_gpio_irq_handler_for_each - IRQ handler helper + * @gpio: oh gpio structure + * @ilat: portion of ilat register + * @base: base pin number + * + * Calls the generic irq handlers for gpio pins with pending interrupts. + */ +static void oh_gpio_irq_handler_for_each(struct oh_gpio *gpio, + unsigned long *ilat, int base) +{ + int offset; + unsigned long flags; + unsigned child_irq_no; + struct irq_desc *child_desc; + u32 child_type; + struct irq_domain *irqdomain = gpio->chip.irqdomain; + + for_each_set_bit(offset, ilat, 32) { + child_irq_no = irq_find_mapping(irqdomain, base + offset); + child_desc = irq_to_desc(child_irq_no); + child_type = irqd_get_trigger_type(&child_desc->irq_data); + + /* Toggle edge for pin with both edges triggering enabled */ + if (child_type == IRQ_TYPE_EDGE_BOTH) { + spin_lock_irqsave(&gpio->lock, flags); + oh_gpio_irq_next_edge_locked(gpio, base + offset); + spin_unlock_irqrestore(&gpio->lock, flags); + } + + generic_handle_irq_desc(child_desc); + } +} + /** * oh_gpio_irq_handler - IRQ handler * @irq: oh_gpio irq number @@ -346,9 +428,7 @@ static irqreturn_t oh_gpio_irq_handler(int irq, void *dev_id) { u64 ilat; unsigned long flags, ilat_lo, ilat_hi; - int offset; struct oh_gpio *gpio = dev_id; - struct irq_domain *irqdomain = gpio->chip.irqdomain; spin_lock_irqsave(&gpio->lock, flags); ilat = oh_gpio_reg_read(gpio, OH_GPIO_ILAT); @@ -361,11 +441,8 @@ static irqreturn_t oh_gpio_irq_handler(int irq, void *dev_id) ilat_lo = (unsigned long) ((ilat >> 0) & 0xffffffff); ilat_hi = (unsigned long) ((ilat >> 32) & 0xffffffff); - for_each_set_bit(offset, &ilat_lo, 32) - generic_handle_irq(irq_find_mapping(irqdomain, offset)); - - for_each_set_bit(offset, &ilat_hi, 32) - generic_handle_irq(irq_find_mapping(irqdomain, 32 + offset)); + oh_gpio_irq_handler_for_each(gpio, &ilat_lo, 0); + oh_gpio_irq_handler_for_each(gpio, &ilat_hi, 32); return IRQ_HANDLED; }