You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
kernel_samsung_sm7125/drivers/irqchip/irq-tacna.c

808 lines
22 KiB

/*
* Interrupt support for Cirrus Logic Tacna codecs
*
* Copyright 2017-2018 Cirrus Logic, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irqchip/irq-tacna.h>
#include <linux/irqchip/irq-tacna-pdata.h>
#include <linux/mfd/tacna/core.h>
#include <linux/mfd/tacna/pdata.h>
#include <linux/mfd/tacna/registers.h>
#define TACNA_AOD_VIRQ_INDEX 0
#define TACNA_MAIN_VIRQ_INDEX 1
struct tacna_irq_priv {
struct device *dev;
int irq;
struct irq_domain *virq;
struct regmap_irq_chip_data *aod_irq_chip;
struct regmap_irq_chip_data *irq_chip;
struct tacna *tacna;
};
static const struct regmap_irq tacna_main_irqs[TACNA_NUM_MAIN_IRQ] = {
[TACNA_IRQ_OUT1R_SC] = {
.reg_offset = 0x00, .mask = TACNA_OUT1R_SC_EINT1_MASK,
},
[TACNA_IRQ_OUT1L_SC] = {
.reg_offset = 0x00, .mask = TACNA_OUT1L_SC_EINT1_MASK,
},
[TACNA_IRQ_OUT2R_SC] = {
.reg_offset = 0x00, .mask = TACNA_OUT2R_SC_EINT1_MASK,
},
[TACNA_IRQ_OUT2L_SC] = {
.reg_offset = 0x00, .mask = TACNA_OUT2L_SC_EINT1_MASK,
},
[TACNA_IRQ_DSP1_IRQ0] = {
.reg_offset = 0x20, .mask = TACNA_DSP1_IRQ0_EINT1_MASK,
},
[TACNA_IRQ_DSP2_IRQ0] = {
.reg_offset = 0x20, .mask = TACNA_DSP2_IRQ0_EINT1_MASK,
},
[TACNA_IRQ_DSP1_IRQ1] = {
.reg_offset = 0x20, .mask = TACNA_DSP1_IRQ1_EINT1_MASK,
},
[TACNA_IRQ_DSP2_IRQ1] = {
.reg_offset = 0x20, .mask = TACNA_DSP2_IRQ1_EINT1_MASK,
},
[TACNA_IRQ_DSP1_IRQ2] = {
.reg_offset = 0x20, .mask = TACNA_DSP1_IRQ2_EINT1_MASK,
},
[TACNA_IRQ_DSP2_IRQ2] = {
.reg_offset = 0x20, .mask = TACNA_DSP2_IRQ2_EINT1_MASK,
},
[TACNA_IRQ_DSP1_IRQ3] = {
.reg_offset = 0x20, .mask = TACNA_DSP1_IRQ3_EINT1_MASK,
},
[TACNA_IRQ_DSP2_IRQ3] = {
.reg_offset = 0x20, .mask = TACNA_DSP2_IRQ3_EINT1_MASK,
},
[TACNA_IRQ_US1_ACT_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_US1_ACT_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_US1_ACT_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_US1_ACT_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_US2_ACT_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_US2_ACT_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_US2_ACT_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_US2_ACT_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_US3_ACT_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_US3_ACT_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_US3_ACT_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_US3_ACT_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_INPUTS_SIG_DET_AO_RISE] = {
.reg_offset = 0x10, .mask = TACNA_INPUTS_SIG_DET_AO_RISE_EINT1_MASK,
},
[TACNA_IRQ_INPUTS_SIG_DET_AO_FALL] = {
.reg_offset = 0x10, .mask = TACNA_INPUTS_SIG_DET_AO_FALL_EINT1_MASK,
},
[TACNA_IRQ_INPUTS_SIG_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_INPUTS_SIG_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_INPUTS_SIG_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_INPUTS_SIG_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_HPDET] = {
.reg_offset = 0x10, .mask = TACNA_HPDET_EINT1_MASK,
},
[TACNA_IRQ_MICDET1] = {
.reg_offset = 0x10, .mask = TACNA_MICDET1_EINT1_MASK,
},
[TACNA_IRQ_MICDET2] = {
.reg_offset = 0x10, .mask = TACNA_MICDET2_EINT1_MASK,
},
[TACNA_IRQ_GPIO1_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO1_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO1_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO1_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO2_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO2_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO2_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO2_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO3_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO3_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO3_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO3_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO4_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO4_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO4_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO4_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO5_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO5_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO5_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO5_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO6_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO6_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO6_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO6_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO7_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO7_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO7_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO7_FALL_EINT1_MASK,
},
[TACNA_IRQ_GPIO8_RISE] = {
.reg_offset = 0x28, .mask = TACNA_GPIO8_RISE_EINT1_MASK,
},
[TACNA_IRQ_GPIO8_FALL] = {
.reg_offset = 0x28, .mask = TACNA_GPIO8_FALL_EINT1_MASK,
},
[TACNA_IRQ_OUT1R_ENABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT1R_ENABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT1L_ENABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT1L_ENABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT2R_ENABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT2R_ENABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT2L_ENABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT2L_ENABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT1R_DISABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT1R_DISABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT1L_DISABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT1L_DISABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT2R_DISABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT2R_DISABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUT2L_DISABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUT2L_DISABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUTH_ENABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUTH_ENABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_OUTH_DISABLE_DONE] = {
.reg_offset = 0x08, .mask = TACNA_OUTH_DISABLE_DONE_EINT1_MASK,
},
[TACNA_IRQ_DRC1_SIG_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_DRC1_SIG_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_DRC1_SIG_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_DRC1_SIG_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_DRC2_SIG_DET_RISE] = {
.reg_offset = 0x10, .mask = TACNA_DRC2_SIG_DET_RISE_EINT1_MASK,
},
[TACNA_IRQ_DRC2_SIG_DET_FALL] = {
.reg_offset = 0x10, .mask = TACNA_DRC2_SIG_DET_FALL_EINT1_MASK,
},
[TACNA_IRQ_JD2_HDRV_RISE] = {
.reg_offset = 0x0c, .mask = TACNA_JD2_HDRV_RISE_EINT1_MASK,
},
[TACNA_IRQ_JD2_HDRV_FALL] = {
.reg_offset = 0x0c, .mask = TACNA_JD2_HDRV_FALL_EINT1_MASK,
},
[TACNA_IRQ_FLL1_LOCK_RISE] = {
.reg_offset = 0x14, .mask = TACNA_FLL1_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_FLL1_LOCK_FALL] = {
.reg_offset = 0x14, .mask = TACNA_FLL1_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_FLL2_LOCK_RISE] = {
.reg_offset = 0x14, .mask = TACNA_FLL2_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_FLL2_LOCK_FALL] = {
.reg_offset = 0x14, .mask = TACNA_FLL2_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_FLL3_LOCK_RISE] = {
.reg_offset = 0x14, .mask = TACNA_FLL3_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_FLL3_LOCK_FALL] = {
.reg_offset = 0x14, .mask = TACNA_FLL3_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_FLL1_REF_LOST] = {
.reg_offset = 0x14, .mask = TACNA_FLL1_REF_LOST_EINT1_MASK,
},
[TACNA_IRQ_FLL2_REF_LOST] = {
.reg_offset = 0x14, .mask = TACNA_FLL2_REF_LOST_EINT1_MASK,
},
[TACNA_IRQ_FLL3_REF_LOST] = {
.reg_offset = 0x14, .mask = TACNA_FLL3_REF_LOST_EINT1_MASK,
},
[TACNA_IRQ_FLL1AO_RELOCK_FAIL] = {
.reg_offset = 0x0, .mask = TACNA_FLL1AO_RELOCK_FAIL_EINT1_MASK,
},
[TACNA_IRQ_SYSCLK_FAIL] = {
.reg_offset = 0x00, .mask = TACNA_SYSCLK_FAIL_EINT1_MASK,
},
[TACNA_IRQ_CTRLIF_ERR] = {
.reg_offset = 0x00, .mask = TACNA_CTRLIF_ERR_EINT1_MASK,
},
[TACNA_IRQ_SYSCLK_ERR] = {
.reg_offset = 0x00, .mask = TACNA_SYSCLK_ERR_EINT1_MASK,
},
[TACNA_IRQ_ASYNCCLK_ERR] = {
.reg_offset = 0x00, .mask = TACNA_ASYNCCLK_ERR_EINT1_MASK,
},
[TACNA_IRQ_DSPCLK_ERR] = {
.reg_offset = 0x00, .mask = TACNA_DSPCLK_ERR_EINT1_MASK,
},
[TACNA_IRQ_AOCLK_ERR] = {
.reg_offset = 0x00, .mask = TACNA_AOCLK_ERR_EINT1,
},
[TACNA_IRQ_DSP1_NMI] = {
.reg_offset = 0x18, .mask = TACNA_DSP1_NMI_ERR_EINT1_MASK,
},
[TACNA_IRQ_DSP1_WDT_EXPIRE] = {
.reg_offset = 0x18, .mask = TACNA_DSP1_WDT_EXPIRE_EINT1,
},
[TACNA_IRQ_DSP1_MPU_ERR] = {
.reg_offset = 0x18, .mask = TACNA_DSP1_MPU_ERR_EINT1,
},
[TACNA_IRQ_DSP2_NMI] = {
.reg_offset = 0x18, .mask = TACNA_DSP2_NMI_ERR_EINT1_MASK,
},
[TACNA_IRQ_DSP2_WDT_EXPIRE] = {
.reg_offset = 0x18, .mask = TACNA_DSP2_WDT_EXPIRE_EINT1,
},
[TACNA_IRQ_DSP2_MPU_ERR] = {
.reg_offset = 0x18, .mask = TACNA_DSP2_MPU_ERR_EINT1,
},
[TACNA_IRQ_MCU_HWERR] = {
.reg_offset = 0x20, .mask = TACNA_MCU_HWERR_IRQ_OUT_EINT1_MASK,
},
[TACNA_IRQ_BOOT_DONE] = {
.reg_offset = 0x04, .mask = TACNA_BOOT_DONE_EINT1_MASK,
},
[TACNA_IRQ_ASRC1_IN1_LOCK_RISE] = {
.reg_offset = 0x24, .mask = TACNA_ASRC1_IN1_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_ASRC1_IN1_LOCK_FALL] = {
.reg_offset = 0x24, .mask = TACNA_ASRC1_IN1_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_ASRC1_IN2_LOCK_RISE] = {
.reg_offset = 0x24, .mask = TACNA_ASRC1_IN2_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_ASRC1_IN2_LOCK_FALL] = {
.reg_offset = 0x24, .mask = TACNA_ASRC1_IN2_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_ASRC2_IN1_LOCK_RISE] = {
.reg_offset = 0x24, .mask = TACNA_ASRC2_IN1_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_ASRC2_IN1_LOCK_FALL] = {
.reg_offset = 0x24, .mask = TACNA_ASRC2_IN1_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_ASRC2_IN2_LOCK_RISE] = {
.reg_offset = 0x24, .mask = TACNA_ASRC2_IN2_LOCK_RISE_EINT1_MASK,
},
[TACNA_IRQ_ASRC2_IN2_LOCK_FALL] = {
.reg_offset = 0x24, .mask = TACNA_ASRC2_IN2_LOCK_FALL_EINT1_MASK,
},
[TACNA_IRQ_SECURE_MODE_RISE] = {
.reg_offset = 0x04, .mask = TACNA_SECURE_MODE_RISE_EINT1_MASK,
},
[TACNA_IRQ_SECURE_MODE_FALL] = {
.reg_offset = 0x04, .mask = TACNA_SECURE_MODE_FALL_EINT1_MASK,
},
};
/*
* AOD IRQ numbers are offset by TACNA_NUM_MAIN_IRQ. So we don't waste memory
* on unused table entries, they are indexed back to zero in this table
*/
#define TACNA_AOD_IRQ_OFFSET(__i) ((__i) - TACNA_NUM_MAIN_IRQ)
static const struct regmap_irq tacna_aod_irqs[TACNA_NUM_AOD_IRQ] = {
/* AOD IRQs */
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD1_RISE)] = {
.reg_offset = 0, .mask = TACNA_JD1_RISE_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD1_FALL)] = {
.reg_offset = 0, .mask = TACNA_JD1_FALL_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD2_RISE)] = {
.reg_offset = 0, .mask = TACNA_JD2_RISE_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD2_FALL)] = {
.reg_offset = 0, .mask = TACNA_JD2_FALL_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_MICD_CLAMP1_RISE)] = {
.reg_offset = 0, .mask = TACNA_MICD_CLAMP1_RISE_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_MICD_CLAMP1_FALL)] = {
.reg_offset = 0, .mask = TACNA_MICD_CLAMP1_FALL_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD3_RISE)] = {
.reg_offset = 0, .mask = TACNA_JD3_RISE_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_JD3_FALL)] = {
.reg_offset = 0, .mask = TACNA_JD3_FALL_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_MICD_CLAMP2_RISE)] = {
.reg_offset = 0, .mask = TACNA_MICD_CLAMP2_RISE_EINT1_MASK,
},
[TACNA_AOD_IRQ_OFFSET(TACNA_IRQ_AOD_MICD_CLAMP2_FALL)] = {
.reg_offset = 0, .mask = TACNA_MICD_CLAMP2_FALL_EINT1_MASK,
},
};
static const struct regmap_irq_chip tacna_main_irqchip = {
.name = "tacna IRQ",
.status_base = TACNA_IRQ1_EINT_1,
.mask_base = TACNA_IRQ1_MASK_1,
.ack_base = TACNA_IRQ1_EINT_1,
.num_regs = 11,
.irqs = tacna_main_irqs,
.num_irqs = ARRAY_SIZE(tacna_main_irqs),
};
static const struct regmap_irq_chip tacna_aod_irqchip = {
.name = "tacna AOD IRQ",
.status_base = TACNA_IRQ1_EINT_AOD,
.mask_base = TACNA_IRQ1_MASK_AOD,
.ack_base = TACNA_IRQ1_EINT_AOD,
.num_regs = 1,
.irqs = tacna_aod_irqs,
.num_irqs = ARRAY_SIZE(tacna_aod_irqs),
};
static int tacna_map_irq(struct tacna *tacna, int irq)
{
struct tacna_irq_priv *priv = dev_get_drvdata(tacna->irq_dev);
if (irq < 0)
return irq;
if (!tacna->irq_dev)
return -ENOENT;
if (irq < TACNA_NUM_MAIN_IRQ) {
dev_dbg(priv->dev, "Mapping IRQ%d to main virq\n", irq);
return regmap_irq_get_virq(priv->irq_chip, irq);
} else if (priv->aod_irq_chip) {
dev_dbg(priv->dev, "Mapping IRQ%d to AOD virq\n", irq);
irq -= TACNA_NUM_MAIN_IRQ;
return regmap_irq_get_virq(priv->aod_irq_chip, irq);
}
dev_err(priv->dev, "IRQ%d can't be mapped\n", irq);
return -ENOENT;
}
int tacna_request_irq(struct tacna *tacna, int irq, const char *name,
irq_handler_t handler, void *data)
{
irq = tacna_map_irq(tacna, irq);
if (irq < 0)
return irq;
return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name,
data);
}
EXPORT_SYMBOL_GPL(tacna_request_irq);
void tacna_free_irq(struct tacna *tacna, int irq, void *data)
{
irq = tacna_map_irq(tacna, irq);
if (irq < 0)
return;
free_irq(irq, data);
}
EXPORT_SYMBOL_GPL(tacna_free_irq);
int tacna_set_irq_wake(struct tacna *tacna, int irq, int on)
{
irq = tacna_map_irq(tacna, irq);
if (irq < 0)
return irq;
return irq_set_irq_wake(irq, on);
}
EXPORT_SYMBOL_GPL(tacna_set_irq_wake);
static irqreturn_t tacna_irq_thread(int irq, void *data)
{
struct tacna_irq_priv *priv = data;
struct tacna *tacna = priv->tacna;
unsigned int val, mask;
irqreturn_t result = IRQ_NONE;
int ret;
/* Must power up parent MFD driver to access registers */
ret = pm_runtime_get_sync(tacna->dev);
if (ret < 0) {
dev_err(priv->dev, "Failed to resume device: %d\n", ret);
return IRQ_NONE;
}
if (priv->aod_irq_chip) {
/* Check whether AOD interrupts are pending */
ret = regmap_read(tacna->regmap, TACNA_IRQ1_EINT_AOD, &val);
if (ret) {
dev_warn(priv->dev, "Failed to read AOD IRQ1 %d\n",
ret);
} else {
ret = regmap_read(tacna->regmap, TACNA_IRQ1_MASK_AOD,
&mask);
if (ret)
dev_warn(priv->dev,
"Failed to read AOD IRQ1 MASK %d\n",
ret);
}
if ((ret == 0) && (val & ~mask)) {
result = IRQ_HANDLED;
handle_nested_irq(irq_find_mapping(priv->virq,
TACNA_AOD_VIRQ_INDEX));
}
}
/* Check whether main interrupts are pending */
ret = regmap_read(tacna->regmap, TACNA_IRQ1_STATUS, &val);
if (ret) {
dev_warn(priv->dev, "Failed to read IRQ1_STATUS %d\n", ret);
} else if (val & TACNA_IRQ1_STS_MASK) {
result = IRQ_HANDLED;
handle_nested_irq(irq_find_mapping(priv->virq,
TACNA_MAIN_VIRQ_INDEX));
}
pm_runtime_mark_last_busy(tacna->dev);
pm_runtime_put_autosuspend(tacna->dev);
return result;
}
static void tacna_root_irq_disable(struct irq_data *data)
{
}
static void tacna_root_irq_enable(struct irq_data *data)
{
}
static int tacna_root_irq_set_wake(struct irq_data *data, unsigned int on)
{
struct tacna_irq_priv *priv = irq_data_get_irq_chip_data(data);
return irq_set_irq_wake(priv->irq, on);
}
static struct irq_chip tacna_root_irqchip = {
.name = "tacna",
.irq_disable = &tacna_root_irq_disable,
.irq_enable = &tacna_root_irq_enable,
.irq_set_wake = &tacna_root_irq_set_wake,
};
static struct lock_class_key tacna_irq_lock_class;
static int tacna_irq_map(struct irq_domain *d, unsigned int virq,
irq_hw_number_t hw)
{
struct tacna_irq_priv *priv = d->host_data;
dev_dbg(priv->dev, "Mapping IRQ%lu", hw);
irq_set_chip_data(virq, priv);
irq_set_lockdep_class(virq, &tacna_irq_lock_class);
irq_set_chip_and_handler(virq, &tacna_root_irqchip, handle_simple_irq);
irq_set_nested_thread(virq, TACNA_MAIN_VIRQ_INDEX);
return 0;
}
static struct irq_domain_ops tacna_irq_domain_ops = {
.map = &tacna_irq_map,
.xlate = &irq_domain_xlate_twocell,
};
#ifdef CONFIG_PM_SLEEP
static int tacna_suspend(struct device *dev)
{
struct tacna_irq_priv *priv = dev_get_drvdata(dev);
dev_info(priv->dev, "Suspend, disabling IRQ\n");
/* Prevent IRQs while things are suspending */
disable_irq(priv->irq);
return 0;
}
static int tacna_suspend_noirq(struct device *dev)
{
struct tacna_irq_priv *priv = dev_get_drvdata(dev);
dev_info(priv->dev, "No IRQ suspend, reenabling IRQ\n");
enable_irq(priv->irq);
return 0;
}
static int tacna_resume_noirq(struct device *dev)
{
struct tacna_irq_priv *priv = dev_get_drvdata(dev);
dev_info(priv->dev, "No IRQ resume, disabling IRQ\n");
disable_irq(priv->irq);
return 0;
}
static int tacna_resume(struct device *dev)
{
struct tacna_irq_priv *priv = dev_get_drvdata(dev);
dev_info(priv->dev, "Resume, reenabling IRQ\n");
enable_irq(priv->irq);
return 0;
}
#endif
static const struct dev_pm_ops tacna_irq_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tacna_suspend, tacna_resume)
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tacna_suspend_noirq, tacna_resume_noirq)
};
static irqreturn_t tacna_sysclk_fail(int irq, void *data)
{
struct tacna *tacna = data;
dev_err(tacna->dev, "SYSCLK fail\n");
return IRQ_HANDLED;
}
static irqreturn_t tacna_sysclk_error(int irq, void *data)
{
struct tacna *tacna = data;
dev_err(tacna->dev, "SYSCLK error\n");
return IRQ_HANDLED;
}
static irqreturn_t tacna_ctrlif_error(int irq, void *data)
{
struct tacna *tacna = data;
dev_err(tacna->dev, "CTRLIF error\n");
return IRQ_HANDLED;
}
static irqreturn_t tacna_boot_done(int irq, void *data)
{
struct tacna *tacna = data;
dev_info(tacna->dev, "BOOT_DONE\n");
return IRQ_HANDLED;
}
static int tacna_irq_probe(struct platform_device *pdev)
{
struct tacna *tacna = dev_get_drvdata(pdev->dev.parent);
struct tacna_irq_priv *priv;
struct irq_data *irq_data;
unsigned int irq_flags = tacna->pdata.irqchip.irq_flags;
unsigned int virq;
int ret;
dev_dbg(&pdev->dev, "probe\n");
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
priv->tacna = tacna;
priv->irq = tacna->irq;
dev_set_drvdata(priv->dev, priv);
/* Read the flags from the interrupt controller if not specified */
if (!irq_flags) {
irq_data = irq_get_irq_data(priv->irq);
if (!irq_data) {
dev_err(priv->dev, "Invalid IRQ: %d\n", priv->irq);
return -EINVAL;
}
irq_flags = irqd_get_trigger_type(irq_data);
if (irq_flags == IRQ_TYPE_NONE)
irq_flags = IRQF_TRIGGER_LOW; /* Device default */
}
if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
dev_err(priv->dev, "Host interrupt not level-triggered\n");
return -EINVAL;
}
if (irq_flags & IRQF_TRIGGER_HIGH)
ret = regmap_update_bits(tacna->regmap, TACNA_IRQ1_CTRL_AOD,
TACNA_IRQ_POL_MASK, 0);
else
ret = regmap_update_bits(tacna->regmap, TACNA_IRQ1_CTRL_AOD,
TACNA_IRQ_POL_MASK, TACNA_IRQ_POL);
if (ret) {
dev_err(priv->dev, "Failed to set IRQ polarity: %d\n", ret);
return ret;
}
/*
* Allocate a virtual IRQ domain to distribute to the regmap domains
* (without a OF node so that it's invisible to DT IRQ mappings)
*/
priv->virq = irq_domain_add_linear(NULL, 2,
&tacna_irq_domain_ops, priv);
if (!priv->virq) {
dev_err(priv->dev, "Failed to add core IRQ domain\n");
ret = -EINVAL;
goto err;
}
/*
* Create regmap interrupt handlers. Regmap registers its domains
* against the OF node of the owner of the regmap, which is the mfd
*/
switch (priv->tacna->type) {
case CS48L31:
case CS48L32:
case CS48L33:
break;
default:
virq = irq_create_mapping(priv->virq, TACNA_AOD_VIRQ_INDEX);
if (!virq) {
dev_err(priv->dev, "Failed to map AOD IRQs\n");
ret = -EINVAL;
goto err_domain;
}
ret = regmap_add_irq_chip(tacna->regmap, virq, IRQF_ONESHOT, 0,
&tacna_aod_irqchip,
&priv->aod_irq_chip);
if (ret) {
dev_err(priv->dev, "Failed to add AOD IRQs: %d\n", ret);
goto err_map_aod;
}
}
virq = irq_create_mapping(priv->virq, TACNA_MAIN_VIRQ_INDEX);
if (!virq) {
dev_err(priv->dev, "Failed to map main IRQs\n");
ret = -EINVAL;
goto err_aod;
}
ret = regmap_add_irq_chip(tacna->regmap, virq, IRQF_ONESHOT, 0,
&tacna_main_irqchip, &priv->irq_chip);
if (ret) {
dev_err(priv->dev, "Failed to add main IRQs: %d\n", ret);
goto err_map_main_irq;
}
ret = request_threaded_irq(priv->irq, NULL, tacna_irq_thread,
irq_flags | IRQF_ONESHOT, "tacna", priv);
if (ret) {
dev_err(priv->dev, "Failed to request IRQ(%d) thread: %d\n",
priv->irq, ret);
goto err_main_irq;
}
tacna->irq_dev = priv->dev;
tacna_request_irq(tacna, TACNA_IRQ_SYSCLK_FAIL, "SYSCLK fail",
tacna_sysclk_fail, tacna);
tacna_request_irq(tacna, TACNA_IRQ_SYSCLK_ERR, "SYSCLK error",
tacna_sysclk_error, tacna);
tacna_request_irq(tacna, TACNA_IRQ_CTRLIF_ERR, "CTRLIF error",
tacna_ctrlif_error, tacna);
tacna_request_irq(tacna, TACNA_IRQ_BOOT_DONE, "BOOT_DONE",
tacna_boot_done, tacna);
return 0;
err_main_irq:
regmap_del_irq_chip(irq_find_mapping(priv->virq, TACNA_MAIN_VIRQ_INDEX),
priv->irq_chip);
err_map_main_irq:
irq_dispose_mapping(irq_find_mapping(priv->virq,
TACNA_MAIN_VIRQ_INDEX));
err_aod:
if (priv->aod_irq_chip)
regmap_del_irq_chip(irq_find_mapping(priv->virq,
TACNA_AOD_VIRQ_INDEX),
priv->aod_irq_chip);
err_map_aod:
irq_dispose_mapping(irq_find_mapping(priv->virq, TACNA_AOD_VIRQ_INDEX));
err_domain:
irq_domain_remove(priv->virq);
err:
return ret;
}
static int tacna_irq_remove(struct platform_device *pdev)
{
struct tacna_irq_priv *priv = platform_get_drvdata(pdev);
struct tacna *tacna = priv->tacna;
unsigned int virq;
disable_irq(priv->irq);
tacna_free_irq(tacna, TACNA_IRQ_BOOT_DONE, tacna);
tacna_free_irq(tacna, TACNA_IRQ_CTRLIF_ERR, tacna);
tacna_free_irq(tacna, TACNA_IRQ_SYSCLK_ERR, tacna);
tacna_free_irq(tacna, TACNA_IRQ_SYSCLK_FAIL, tacna);
priv->tacna->irq_dev = NULL;
virq = irq_find_mapping(priv->virq, TACNA_MAIN_VIRQ_INDEX);
regmap_del_irq_chip(virq, priv->irq_chip);
irq_dispose_mapping(virq);
virq = irq_find_mapping(priv->virq, TACNA_AOD_VIRQ_INDEX);
if (priv->aod_irq_chip)
regmap_del_irq_chip(virq, priv->aod_irq_chip);
irq_dispose_mapping(virq);
irq_domain_remove(priv->virq);
free_irq(priv->irq, priv);
return 0;
}
static struct platform_driver tacna_irq_driver = {
.probe = &tacna_irq_probe,
.remove = &tacna_irq_remove,
.driver = {
.name = "tacna-irq",
.pm = &tacna_irq_pm_ops,
.suppress_bind_attrs = true,
}
};
module_platform_driver(tacna_irq_driver);
MODULE_DESCRIPTION("Tacna IRQ driver");
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL v2");