mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 19:29:37 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
653 lines
15 KiB
C
653 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs
|
|
*
|
|
* Copyright 2009 Wolfson Microelectronics PLC.
|
|
*
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*/
|
|
|
|
#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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
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_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:
|
|
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);
|
|
irq_set_noprobe(virq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const 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->pdata;
|
|
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->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->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);
|
|
}
|