diff options
Diffstat (limited to 'drivers/mfd/wm831x-irq.c')
| -rw-r--r-- | drivers/mfd/wm831x-irq.c | 385 |
1 files changed, 245 insertions, 140 deletions
diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index ac056ea6b66..64e512eadf1 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -15,30 +15,22 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> +#include <linux/irq.h> #include <linux/mfd/core.h> #include <linux/interrupt.h> +#include <linux/irqdomain.h> #include <linux/mfd/wm831x/core.h> #include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/gpio.h> #include <linux/mfd/wm831x/irq.h> #include <linux/delay.h> -/* - * Since generic IRQs don't currently support interrupt controllers on - * interrupt driven buses we don't use genirq but instead provide an - * interface that looks very much like the standard ones. This leads - * to some bodges, including storing interrupt handler information in - * the static irq_data table we use to look up the data for individual - * interrupts, but hopefully won't last too long. - */ - struct wm831x_irq_data { int primary; int reg; int mask; - irq_handler_t handler; - void *handler_data; }; static struct wm831x_irq_data wm831x_irqs[] = { @@ -334,115 +326,138 @@ static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data) return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg; } -static inline int irq_data_to_mask_reg(struct wm831x_irq_data *irq_data) +static inline struct wm831x_irq_data *irq_to_wm831x_irq(struct wm831x *wm831x, + int irq) { - return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; + return &wm831x_irqs[irq]; } -static void __wm831x_enable_irq(struct wm831x *wm831x, int irq) +static void wm831x_irq_lock(struct irq_data *data) { - struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); - wm831x->irq_masks[irq_data->reg - 1] &= ~irq_data->mask; - wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), - wm831x->irq_masks[irq_data->reg - 1]); -} - -void wm831x_enable_irq(struct wm831x *wm831x, int irq) -{ mutex_lock(&wm831x->irq_lock); - __wm831x_enable_irq(wm831x, irq); - mutex_unlock(&wm831x->irq_lock); } -EXPORT_SYMBOL_GPL(wm831x_enable_irq); -static void __wm831x_disable_irq(struct wm831x *wm831x, int irq) +static void wm831x_irq_sync_unlock(struct irq_data *data) { - struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(wm831x->gpio_update); i++) { + if (wm831x->gpio_update[i]) { + wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + i, + WM831X_GPN_INT_MODE | WM831X_GPN_POL, + wm831x->gpio_update[i]); + wm831x->gpio_update[i] = 0; + } + } - wm831x->irq_masks[irq_data->reg - 1] |= irq_data->mask; - wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data), - wm831x->irq_masks[irq_data->reg - 1]); -} + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + if (wm831x->irq_masks_cur[i] != wm831x->irq_masks_cache[i]) { + dev_dbg(wm831x->dev, "IRQ mask sync: %x = %x\n", + WM831X_INTERRUPT_STATUS_1_MASK + i, + wm831x->irq_masks_cur[i]); + + wm831x->irq_masks_cache[i] = wm831x->irq_masks_cur[i]; + wm831x_reg_write(wm831x, + WM831X_INTERRUPT_STATUS_1_MASK + i, + wm831x->irq_masks_cur[i]); + } + } -void wm831x_disable_irq(struct wm831x *wm831x, int irq) -{ - mutex_lock(&wm831x->irq_lock); - __wm831x_disable_irq(wm831x, irq); mutex_unlock(&wm831x->irq_lock); } -EXPORT_SYMBOL_GPL(wm831x_disable_irq); -int wm831x_request_irq(struct wm831x *wm831x, - unsigned int irq, irq_handler_t handler, - unsigned long flags, const char *name, - void *dev) +static void wm831x_irq_enable(struct irq_data *data) { - int ret = 0; - - if (irq < 0 || irq >= WM831X_NUM_IRQS) - return -EINVAL; - - mutex_lock(&wm831x->irq_lock); - - if (wm831x_irqs[irq].handler) { - dev_err(wm831x->dev, "Already have handler for IRQ %d\n", irq); - ret = -EINVAL; - goto out; - } - - wm831x_irqs[irq].handler = handler; - wm831x_irqs[irq].handler_data = dev; + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, + data->hwirq); - __wm831x_enable_irq(wm831x, irq); - -out: - mutex_unlock(&wm831x->irq_lock); - - return ret; + wm831x->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; } -EXPORT_SYMBOL_GPL(wm831x_request_irq); -void wm831x_free_irq(struct wm831x *wm831x, unsigned int irq, void *data) +static void wm831x_irq_disable(struct irq_data *data) { - if (irq < 0 || irq >= WM831X_NUM_IRQS) - return; - - mutex_lock(&wm831x->irq_lock); + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x, + data->hwirq); - wm831x_irqs[irq].handler = NULL; - wm831x_irqs[irq].handler_data = NULL; + wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} - __wm831x_disable_irq(wm831x, irq); +static int wm831x_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct wm831x *wm831x = irq_data_get_irq_chip_data(data); + int irq; - mutex_unlock(&wm831x->irq_lock); -} -EXPORT_SYMBOL_GPL(wm831x_free_irq); + irq = data->hwirq; + if (irq < WM831X_IRQ_GPIO_1 || irq > WM831X_IRQ_GPIO_11) { + /* Ignore internal-only IRQs */ + if (irq >= 0 && irq < WM831X_NUM_IRQS) + return 0; + else + return -EINVAL; + } -static void wm831x_handle_irq(struct wm831x *wm831x, int irq, int status) -{ - struct wm831x_irq_data *irq_data = &wm831x_irqs[irq]; + /* Rebase the IRQ into the GPIO range so we've got a sensible array + * index. + */ + irq -= WM831X_IRQ_GPIO_1; - if (irq_data->handler) { - irq_data->handler(irq, irq_data->handler_data); - wm831x_reg_write(wm831x, irq_data_to_status_reg(irq_data), - irq_data->mask); - } else { - dev_err(wm831x->dev, "Unhandled IRQ %d, masking\n", irq); - __wm831x_disable_irq(wm831x, irq); + /* We set the high bit to flag that we need an update; don't + * do the update here as we can be called with the bus lock + * held. + */ + wm831x->gpio_level_low[irq] = false; + wm831x->gpio_level_high[irq] = false; + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_INT_MODE; + break; + case IRQ_TYPE_EDGE_RISING: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL; + break; + case IRQ_TYPE_EDGE_FALLING: + wm831x->gpio_update[irq] = 0x10000; + break; + case IRQ_TYPE_LEVEL_HIGH: + wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL; + wm831x->gpio_level_high[irq] = true; + break; + case IRQ_TYPE_LEVEL_LOW: + wm831x->gpio_update[irq] = 0x10000; + wm831x->gpio_level_low[irq] = true; + break; + default: + return -EINVAL; } + + return 0; } -/* Main interrupt handling occurs in a workqueue since we need - * interrupts enabled to interact with the chip. */ -static void wm831x_irq_worker(struct work_struct *work) +static struct irq_chip wm831x_irq_chip = { + .name = "wm831x", + .irq_bus_lock = wm831x_irq_lock, + .irq_bus_sync_unlock = wm831x_irq_sync_unlock, + .irq_disable = wm831x_irq_disable, + .irq_enable = wm831x_irq_enable, + .irq_set_type = wm831x_irq_set_type, +}; + +/* The processing of the primary interrupt occurs in a thread so that + * we can interact with the device over I2C or SPI. */ +static irqreturn_t wm831x_irq_thread(int irq, void *data) { - struct wm831x *wm831x = container_of(work, struct wm831x, irq_work); + struct wm831x *wm831x = data; unsigned int i; - int primary; - int status_regs[5]; - int read[5] = { 0 }; + int primary, status_addr, ret; + int status_regs[WM831X_NUM_IRQ_REGS] = { 0 }; + int read[WM831X_NUM_IRQ_REGS] = { 0 }; int *status; primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS); @@ -452,7 +467,18 @@ static void wm831x_irq_worker(struct work_struct *work) goto out; } - mutex_lock(&wm831x->irq_lock); + /* The touch interrupts are visible in the primary register as + * an optimisation; open code this to avoid complicating the + * main handling loop and so we can also skip iterating the + * descriptors. + */ + if (primary & WM831X_TCHPD_INT) + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + WM831X_IRQ_TCHPD)); + if (primary & WM831X_TCHDATA_INT) + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + WM831X_IRQ_TCHDATA)); + primary &= ~(WM831X_TCHDATA_EINT | WM831X_TCHPD_EINT); for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) { int offset = wm831x_irqs[i].reg - 1; @@ -465,91 +491,170 @@ static void wm831x_irq_worker(struct work_struct *work) /* Hopefully there should only be one register to read * each time otherwise we ought to do a block read. */ if (!read[offset]) { - *status = wm831x_reg_read(wm831x, - irq_data_to_status_reg(&wm831x_irqs[i])); + status_addr = irq_data_to_status_reg(&wm831x_irqs[i]); + + *status = wm831x_reg_read(wm831x, status_addr); if (*status < 0) { dev_err(wm831x->dev, "Failed to read IRQ status: %d\n", *status); - goto out_lock; + goto out; } - /* Mask out the disabled IRQs */ - *status &= ~wm831x->irq_masks[offset]; read[offset] = 1; + + /* Ignore any bits that we don't think are masked */ + *status &= ~wm831x->irq_masks_cur[offset]; + + /* Acknowledge now so we don't miss + * notifications while we handle. + */ + wm831x_reg_write(wm831x, status_addr, *status); } if (*status & wm831x_irqs[i].mask) - wm831x_handle_irq(wm831x, i, *status); + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + + /* Simulate an edge triggered IRQ by polling the input + * status. This is sucky but improves interoperability. + */ + if (primary == WM831X_GP_INT && + wm831x->gpio_level_high[i - WM831X_IRQ_GPIO_1]) { + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + while (ret & 1 << (i - WM831X_IRQ_GPIO_1)) { + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + ret = wm831x_reg_read(wm831x, + WM831X_GPIO_LEVEL); + } + } + + if (primary == WM831X_GP_INT && + wm831x->gpio_level_low[i - WM831X_IRQ_GPIO_1]) { + ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); + while (!(ret & 1 << (i - WM831X_IRQ_GPIO_1))) { + handle_nested_irq(irq_find_mapping(wm831x->irq_domain, + i)); + ret = wm831x_reg_read(wm831x, + WM831X_GPIO_LEVEL); + } + } } -out_lock: - mutex_unlock(&wm831x->irq_lock); out: - enable_irq(wm831x->irq); + return IRQ_HANDLED; } - -static irqreturn_t wm831x_cpu_irq(int irq, void *data) +static int wm831x_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) { - struct wm831x *wm831x = data; - - /* Shut the interrupt to the CPU up and schedule the actual - * handler; we can't check that the IRQ is asserted. */ - disable_irq_nosync(irq); - - queue_work(wm831x->irq_wq, &wm831x->irq_work); + irq_set_chip_data(virq, h->host_data); + irq_set_chip_and_handler(virq, &wm831x_irq_chip, handle_edge_irq); + irq_set_nested_thread(virq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(virq, IRQF_VALID); +#else + irq_set_noprobe(virq); +#endif - return IRQ_HANDLED; + return 0; } +static struct irq_domain_ops wm831x_irq_domain_ops = { + .map = wm831x_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + int wm831x_irq_init(struct wm831x *wm831x, int irq) { - int i, ret; + struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev); + struct irq_domain *domain; + int i, ret, irq_base; mutex_init(&wm831x->irq_lock); - if (!irq) { - dev_warn(wm831x->dev, - "No interrupt specified - functionality limited\n"); - return 0; + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) { + wm831x->irq_masks_cur[i] = 0xffff; + wm831x->irq_masks_cache[i] = 0xffff; + wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, + 0xffff); } + /* Try to dynamically allocate IRQs if no base is specified */ + if (pdata && pdata->irq_base) { + irq_base = irq_alloc_descs(pdata->irq_base, 0, + WM831X_NUM_IRQS, 0); + if (irq_base < 0) { + dev_warn(wm831x->dev, "Failed to allocate IRQs: %d\n", + irq_base); + irq_base = 0; + } + } else { + irq_base = 0; + } - wm831x->irq_wq = create_singlethread_workqueue("wm831x-irq"); - if (!wm831x->irq_wq) { - dev_err(wm831x->dev, "Failed to allocate IRQ worker\n"); - return -ESRCH; + if (irq_base) + domain = irq_domain_add_legacy(wm831x->dev->of_node, + ARRAY_SIZE(wm831x_irqs), + irq_base, 0, + &wm831x_irq_domain_ops, + wm831x); + else + domain = irq_domain_add_linear(wm831x->dev->of_node, + ARRAY_SIZE(wm831x_irqs), + &wm831x_irq_domain_ops, + wm831x); + + if (!domain) { + dev_warn(wm831x->dev, "Failed to allocate IRQ domain\n"); + return -EINVAL; } + if (pdata && pdata->irq_cmos) + i = 0; + else + i = WM831X_IRQ_OD; + + wm831x_set_bits(wm831x, WM831X_IRQ_CONFIG, + WM831X_IRQ_OD, i); + wm831x->irq = irq; - INIT_WORK(&wm831x->irq_work, wm831x_irq_worker); + wm831x->irq_domain = domain; + + if (irq) { + /* Try to flag /IRQ as a wake source; there are a number of + * unconditional wake sources in the PMIC so this isn't + * conditional but we don't actually care *too* much if it + * fails. + */ + ret = enable_irq_wake(irq); + if (ret != 0) { + dev_warn(wm831x->dev, + "Can't enable IRQ as wake source: %d\n", + ret); + } - /* Mask the individual interrupt sources */ - for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks); i++) { - wm831x->irq_masks[i] = 0xffff; - wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i, - 0xffff); + ret = request_threaded_irq(irq, NULL, wm831x_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "wm831x", wm831x); + if (ret != 0) { + dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", + irq, ret); + return ret; + } + } else { + dev_warn(wm831x->dev, + "No interrupt specified - functionality limited\n"); } /* Enable top level interrupts, we mask at secondary level */ wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0); - /* We're good to go. We set IRQF_SHARED since there's a - * chance the driver will interoperate with another driver but - * the need to disable the IRQ while handing via I2C/SPI means - * that this may break and performance will be impacted. If - * this does happen it's a hardware design issue and the only - * other alternative would be polling. - */ - ret = request_irq(irq, wm831x_cpu_irq, IRQF_TRIGGER_LOW | IRQF_SHARED, - "wm831x", wm831x); - if (ret != 0) { - dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n", - irq, ret); - return ret; - } - return 0; } |
