gpio: hlwd: Implement edge trigger emulation

Like the Spreadtrum EIC driver[1], this driver needs to emulate edge
triggered interrupts to support the generic gpio-keys driver.

[1]: https://www.spinics.net/lists/kernel/msg2764576.html

Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
Jonathan Neuschäfer 2019-01-14 20:14:50 +01:00 committed by Linus Walleij
parent 588de43ca1
commit a7241c1b18
1 changed files with 56 additions and 0 deletions

View File

@ -51,6 +51,8 @@ struct hlwd_gpio {
struct irq_chip irqc;
void __iomem *regs;
int irq;
u32 edge_emulation;
u32 rising_edge, falling_edge;
};
static void hlwd_gpio_irqhandler(struct irq_desc *desc)
@ -61,10 +63,36 @@ static void hlwd_gpio_irqhandler(struct irq_desc *desc)
unsigned long flags;
unsigned long pending;
int hwirq;
u32 emulated_pending;
spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
pending = ioread32be(hlwd->regs + HW_GPIOB_INTFLAG);
pending &= ioread32be(hlwd->regs + HW_GPIOB_INTMASK);
/* Treat interrupts due to edge trigger emulation separately */
emulated_pending = hlwd->edge_emulation & pending;
pending &= ~emulated_pending;
if (emulated_pending) {
u32 level, rising, falling;
level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
rising = level & emulated_pending;
falling = ~level & emulated_pending;
/* Invert the levels */
iowrite32be(level ^ emulated_pending,
hlwd->regs + HW_GPIOB_INTLVL);
/* Ack all emulated-edge interrupts */
iowrite32be(emulated_pending, hlwd->regs + HW_GPIOB_INTFLAG);
/* Signal interrupts only on the correct edge */
rising &= hlwd->rising_edge;
falling &= hlwd->falling_edge;
/* Mark emulated interrupts as pending */
pending |= rising | falling;
}
spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
chained_irq_enter(chip, desc);
@ -120,6 +148,27 @@ static void hlwd_gpio_irq_enable(struct irq_data *data)
hlwd_gpio_irq_unmask(data);
}
static void hlwd_gpio_irq_setup_emulation(struct hlwd_gpio *hlwd, int hwirq,
unsigned int flow_type)
{
u32 level, state;
/* Set the trigger level to the inactive level */
level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
state = ioread32be(hlwd->regs + HW_GPIOB_IN) & BIT(hwirq);
level &= ~BIT(hwirq);
level |= state ^ BIT(hwirq);
iowrite32be(level, hlwd->regs + HW_GPIOB_INTLVL);
hlwd->edge_emulation |= BIT(hwirq);
hlwd->rising_edge &= ~BIT(hwirq);
hlwd->falling_edge &= ~BIT(hwirq);
if (flow_type & IRQ_TYPE_EDGE_RISING)
hlwd->rising_edge |= BIT(hwirq);
if (flow_type & IRQ_TYPE_EDGE_FALLING)
hlwd->falling_edge |= BIT(hwirq);
}
static int hlwd_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
{
struct hlwd_gpio *hlwd =
@ -129,6 +178,8 @@ static int hlwd_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
spin_lock_irqsave(&hlwd->gpioc.bgpio_lock, flags);
hlwd->edge_emulation &= ~BIT(data->hwirq);
switch (flow_type) {
case IRQ_TYPE_LEVEL_HIGH:
level = ioread32be(hlwd->regs + HW_GPIOB_INTLVL);
@ -140,6 +191,11 @@ static int hlwd_gpio_irq_set_type(struct irq_data *data, unsigned int flow_type)
level &= ~BIT(data->hwirq);
iowrite32be(level, hlwd->regs + HW_GPIOB_INTLVL);
break;
case IRQ_TYPE_EDGE_RISING:
case IRQ_TYPE_EDGE_FALLING:
case IRQ_TYPE_EDGE_BOTH:
hlwd_gpio_irq_setup_emulation(hlwd, data->hwirq, flow_type);
break;
default:
spin_unlock_irqrestore(&hlwd->gpioc.bgpio_lock, flags);
return -EINVAL;