mirror of
https://github.com/torvalds/linux.git
synced 2024-12-03 17:41:22 +00:00
cd99758ba3
The modern idiom is to use irq_domain to allocate interrupts. This is useful partly to allow further infrastructure to be based on the domains and partly because it makes it much easier to allocate virtual interrupts to devices as we don't need to allocate a contiguous range of interrupt numbers. Convert the wm831x driver over to this infrastructure, using a legacy IRQ mapping if an irq_base is specified in platform data and otherwise using a linear mapping, always registering the interrupts even if they won't ever be used. Only boards which need to use the GPIOs as interrupts should need to use an irq_base. This means that we can't use the MFD irq_base management since the unless we're using an explicit irq_base from platform data we can't rely on a linear mapping of interrupts. Instead we need to map things via the irq_domain - provide a conveniencem function wm831x_irq() to save a small amount of typing when doing so. Looking at this I couldn't clearly see anything the MFD core could do to make this nicer. Since we're not supporting device tree yet there's no meaningful advantage if we don't do this conversion in one, the fact that the interrupt resources are used for repeated IP blocks makes accessor functions for the irq_domain more trouble to do than they're worth. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
652 lines
15 KiB
C
652 lines
15 KiB
C
/*
|
|
* wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs
|
|
*
|
|
* Copyright 2009 Wolfson Microelectronics PLC.
|
|
*
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
#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>
|
|
|
|
struct wm831x_irq_data {
|
|
int primary;
|
|
int reg;
|
|
int mask;
|
|
};
|
|
|
|
static struct wm831x_irq_data wm831x_irqs[] = {
|
|
[WM831X_IRQ_TEMP_THW] = {
|
|
.primary = WM831X_TEMP_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_TEMP_THW_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_1] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP1_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_2] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP2_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_3] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP3_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_4] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP4_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_5] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP5_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_6] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP6_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_7] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP7_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_8] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP8_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_9] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP9_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_10] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP10_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_11] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP11_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_12] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP12_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_13] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP13_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_14] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP14_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_15] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP15_EINT,
|
|
},
|
|
[WM831X_IRQ_GPIO_16] = {
|
|
.primary = WM831X_GP_INT,
|
|
.reg = 5,
|
|
.mask = WM831X_GP16_EINT,
|
|
},
|
|
[WM831X_IRQ_ON] = {
|
|
.primary = WM831X_ON_PIN_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_ON_PIN_EINT,
|
|
},
|
|
[WM831X_IRQ_PPM_SYSLO] = {
|
|
.primary = WM831X_PPM_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_PPM_SYSLO_EINT,
|
|
},
|
|
[WM831X_IRQ_PPM_PWR_SRC] = {
|
|
.primary = WM831X_PPM_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_PPM_PWR_SRC_EINT,
|
|
},
|
|
[WM831X_IRQ_PPM_USB_CURR] = {
|
|
.primary = WM831X_PPM_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_PPM_USB_CURR_EINT,
|
|
},
|
|
[WM831X_IRQ_WDOG_TO] = {
|
|
.primary = WM831X_WDOG_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_WDOG_TO_EINT,
|
|
},
|
|
[WM831X_IRQ_RTC_PER] = {
|
|
.primary = WM831X_RTC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_RTC_PER_EINT,
|
|
},
|
|
[WM831X_IRQ_RTC_ALM] = {
|
|
.primary = WM831X_RTC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_RTC_ALM_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_BATT_HOT] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_BATT_HOT_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_BATT_COLD] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_BATT_COLD_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_BATT_FAIL] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_BATT_FAIL_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_OV] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_OV_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_END] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_END_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_TO] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_TO_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_MODE] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_MODE_EINT,
|
|
},
|
|
[WM831X_IRQ_CHG_START] = {
|
|
.primary = WM831X_CHG_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CHG_START_EINT,
|
|
},
|
|
[WM831X_IRQ_TCHDATA] = {
|
|
.primary = WM831X_TCHDATA_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_TCHDATA_EINT,
|
|
},
|
|
[WM831X_IRQ_TCHPD] = {
|
|
.primary = WM831X_TCHPD_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_TCHPD_EINT,
|
|
},
|
|
[WM831X_IRQ_AUXADC_DATA] = {
|
|
.primary = WM831X_AUXADC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_AUXADC_DATA_EINT,
|
|
},
|
|
[WM831X_IRQ_AUXADC_DCOMP1] = {
|
|
.primary = WM831X_AUXADC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_AUXADC_DCOMP1_EINT,
|
|
},
|
|
[WM831X_IRQ_AUXADC_DCOMP2] = {
|
|
.primary = WM831X_AUXADC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_AUXADC_DCOMP2_EINT,
|
|
},
|
|
[WM831X_IRQ_AUXADC_DCOMP3] = {
|
|
.primary = WM831X_AUXADC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_AUXADC_DCOMP3_EINT,
|
|
},
|
|
[WM831X_IRQ_AUXADC_DCOMP4] = {
|
|
.primary = WM831X_AUXADC_INT,
|
|
.reg = 1,
|
|
.mask = WM831X_AUXADC_DCOMP4_EINT,
|
|
},
|
|
[WM831X_IRQ_CS1] = {
|
|
.primary = WM831X_CS_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CS1_EINT,
|
|
},
|
|
[WM831X_IRQ_CS2] = {
|
|
.primary = WM831X_CS_INT,
|
|
.reg = 2,
|
|
.mask = WM831X_CS2_EINT,
|
|
},
|
|
[WM831X_IRQ_HC_DC1] = {
|
|
.primary = WM831X_HC_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_HC_DC1_EINT,
|
|
},
|
|
[WM831X_IRQ_HC_DC2] = {
|
|
.primary = WM831X_HC_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_HC_DC2_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO1] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO1_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO2] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO2_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO3] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO3_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO4] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO4_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO5] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO5_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO6] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO6_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO7] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO7_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO8] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO8_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO9] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO9_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_LDO10] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 3,
|
|
.mask = WM831X_UV_LDO10_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_DC1] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_UV_DC1_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_DC2] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_UV_DC2_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_DC3] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_UV_DC3_EINT,
|
|
},
|
|
[WM831X_IRQ_UV_DC4] = {
|
|
.primary = WM831X_UV_INT,
|
|
.reg = 4,
|
|
.mask = WM831X_UV_DC4_EINT,
|
|
},
|
|
};
|
|
|
|
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 struct wm831x_irq_data *irq_to_wm831x_irq(struct wm831x *wm831x,
|
|
int irq)
|
|
{
|
|
return &wm831x_irqs[irq];
|
|
}
|
|
|
|
static void wm831x_irq_lock(struct irq_data *data)
|
|
{
|
|
struct wm831x *wm831x = irq_data_get_irq_chip_data(data);
|
|
|
|
mutex_lock(&wm831x->irq_lock);
|
|
}
|
|
|
|
static void wm831x_irq_sync_unlock(struct irq_data *data)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&wm831x->irq_lock);
|
|
}
|
|
|
|
static void wm831x_irq_enable(struct irq_data *data)
|
|
{
|
|
struct wm831x *wm831x = irq_data_get_irq_chip_data(data);
|
|
struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x,
|
|
data->hwirq);
|
|
|
|
wm831x->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask;
|
|
}
|
|
|
|
static void wm831x_irq_disable(struct irq_data *data)
|
|
{
|
|
struct wm831x *wm831x = irq_data_get_irq_chip_data(data);
|
|
struct wm831x_irq_data *irq_data = irq_to_wm831x_irq(wm831x,
|
|
data->hwirq);
|
|
|
|
wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask;
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Rebase the IRQ into the GPIO range so we've got a sensible array
|
|
* index.
|
|
*/
|
|
irq -= WM831X_IRQ_GPIO_1;
|
|
|
|
/* 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.
|
|
*/
|
|
switch (type) {
|
|
case IRQ_TYPE_EDGE_BOTH:
|
|
wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_INT_MODE;
|
|
wm831x->gpio_level[irq] = false;
|
|
break;
|
|
case IRQ_TYPE_EDGE_RISING:
|
|
wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL;
|
|
wm831x->gpio_level[irq] = false;
|
|
break;
|
|
case IRQ_TYPE_EDGE_FALLING:
|
|
wm831x->gpio_update[irq] = 0x10000;
|
|
wm831x->gpio_level[irq] = false;
|
|
break;
|
|
case IRQ_TYPE_LEVEL_HIGH:
|
|
wm831x->gpio_update[irq] = 0x10000 | WM831X_GPN_POL;
|
|
wm831x->gpio_level[irq] = true;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 = data;
|
|
unsigned int i;
|
|
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);
|
|
if (primary < 0) {
|
|
dev_err(wm831x->dev, "Failed to read system interrupt: %d\n",
|
|
primary);
|
|
goto out;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
if (!(primary & wm831x_irqs[i].primary))
|
|
continue;
|
|
|
|
status = &status_regs[offset];
|
|
|
|
/* Hopefully there should only be one register to read
|
|
* each time otherwise we ought to do a block read. */
|
|
if (!read[offset]) {
|
|
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;
|
|
}
|
|
|
|
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)
|
|
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[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:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int wm831x_irq_map(struct irq_domain *h, unsigned int virq,
|
|
irq_hw_number_t hw)
|
|
{
|
|
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 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)
|
|
{
|
|
struct wm831x_pdata *pdata = wm831x->dev->platform_data;
|
|
struct irq_domain *domain;
|
|
int i, ret, irq_base;
|
|
|
|
mutex_init(&wm831x->irq_lock);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void wm831x_irq_exit(struct wm831x *wm831x)
|
|
{
|
|
if (wm831x->irq)
|
|
free_irq(wm831x->irq, wm831x);
|
|
}
|