// SPDX-License-Identifier: GPL-2.0-or-later /* * twl4030-irq.c - TWL4030/TPS659x0 irq support * * Copyright (C) 2005-2006 Texas Instruments, Inc. * * Modifications to defer interrupt handling to a kernel thread: * Copyright (C) 2006 MontaVista Software, Inc. * * Based on tlv320aic23.c: * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> * * Code cleanup and modifications to IRQ handler. * by syed khasim <x0khasim@ti.com>
*/
/* * TWL4030 IRQ handling has two stages in hardware, and thus in software. * The Primary Interrupt Handler (PIH) stage exposes status bits saying * which Secondary Interrupt Handler (SIH) stage is raising an interrupt. * SIH modules are more traditional IRQ components, which support per-IRQ * enable/disable and trigger controls; they do most of the work. * * These chips are designed to support IRQ handling from two different * I2C masters. Each has a dedicated IRQ line, and dedicated IRQ status * and mask registers in the PIH and SIH modules. * * We set up IRQs starting at a platform-specified base, always starting * with PIH and the SIH for PWR_INT and then usually adding GPIO: * base + 0 .. base + 7 PIH * base + 8 .. base + 15 SIH for PWR_INT * base + 16 .. base + 33 SIH for GPIO
*/ #define TWL4030_CORE_NR_IRQS 8 #define TWL4030_PWR_NR_IRQS 8
/* * handle_twl4030_pih() is the desc->handle method for the twl4030 interrupt. * This is a chained interrupt, so there is no desc->action method for it. * Now we need to query the interrupt controller in the twl4030 to determine * which module is generating the interrupt request. However, we can't do i2c * transactions in interrupt context, so we must defer that work to a kernel * thread. All we do here is acknowledge and mask the interrupt and wakeup * the kernel thread.
*/ static irqreturn_t handle_twl4030_pih(int irq, void *devid)
{
irqreturn_t ret;
u8 pih_isr;
ret = twl_i2c_read_u8(TWL_MODULE_PIH, &pih_isr,
REG_PIH_ISR_P1); if (ret) {
pr_warn("twl4030: I2C error %d reading PIH ISR\n", ret); return IRQ_NONE;
}
while (pih_isr) { unsignedlong pending = __ffs(pih_isr); unsignedint irq;
/* * twl4030_init_sih_modules() ... start from a known state where no * IRQs will be coming in, and where we can quickly enable them then * handle them as they arrive. Mask all IRQs: maybe init SIH_CTRL. * * NOTE: we don't touch EDR registers here; they stay with hardware * defaults or whatever the last value was. Note that when both EDR * bits for an IRQ are clear, that's as if its IMR bit is set...
*/ staticint twl4030_init_sih_modules(unsigned line)
{ conststruct sih *sih;
u8 buf[4]; int i; int status;
/* line 0 == int1_n signal; line 1 == int2_n signal */ if (line > 1) return -EINVAL;
irq_line = line;
/* disable all interrupts on our line */
memset(buf, 0xff, sizeof(buf));
sih = sih_modules; for (i = 0; i < nr_sih_modules; i++, sih++) { /* skip USB -- it's funky */ if (!sih->bytes_ixr) continue;
/* Not all the SIH modules support multiple interrupt lines */ if (sih->irq_lines <= line) continue;
status = twl_i2c_write(sih->module, buf,
sih->mask[line].imr_offset, sih->bytes_ixr); if (status < 0)
pr_err("twl4030: err %d initializing %s %s\n",
status, sih->name, "IMR");
/* * Maybe disable "exclusive" mode; buffer second pending irq; * set Clear-On-Read (COR) bit. * * NOTE that sometimes COR polarity is documented as being * inverted: for MADC, COR=1 means "clear on write". * And for PWR_INT it's not documented...
*/ if (sih->set_cor) {
status = twl_i2c_write_u8(sih->module,
TWL4030_SIH_CTRL_COR_MASK,
sih->control_offset); if (status < 0)
pr_err("twl4030: err %d initializing %s %s\n",
status, sih->name, "SIH_CTRL");
}
}
sih = sih_modules; for (i = 0; i < nr_sih_modules; i++, sih++) {
u8 rxbuf[4]; int j;
/* skip USB */ if (!sih->bytes_ixr) continue;
/* Not all the SIH modules support multiple interrupt lines */ if (sih->irq_lines <= line) continue;
/* * Clear pending interrupt status. Either the read was * enough, or we need to write those bits. Repeat, in * case an IRQ is pending (PENDDIS=0) ... that's not * uncommon with PWR_INT.PWRON.
*/ for (j = 0; j < 2; j++) {
status = twl_i2c_read(sih->module, rxbuf,
sih->mask[line].isr_offset, sih->bytes_ixr); if (status < 0)
pr_warn("twl4030: err %d initializing %s %s\n",
status, sih->name, "ISR");
if (!sih->set_cor) {
status = twl_i2c_write(sih->module, buf,
sih->mask[line].isr_offset,
sih->bytes_ixr); if (status < 0)
pr_warn("twl4030: write failed: %d\n",
status);
} /* * else COR=1 means read sufficed. * (for most SIH modules...)
*/
}
}
/* * All irq_chip methods get issued from code holding irq_desc[irq].lock, * which can't perform the underlying I2C operations (because they sleep). * So we must hand them off to a thread (workqueue) and cope with asynch * completion, potentially including some re-ordering, of these requests.
*/
/* * Read, reserving first byte for write scratch. Yes, this * could be cached for some speedup ... but be careful about * any processor on the other IRQ line, EDR registers are * shared.
*/
status = twl_i2c_read(sih->module, bytes,
sih->edr_offset, sih->bytes_edr); if (status) {
pr_err("twl4030: %s, %s --> %d\n", __func__, "read", status); return;
}
/* Modify only the bits we know must change */ while (edge_change) { int i = fls(edge_change) - 1; int byte = i >> 2; int off = (i & 0x3) * 2; unsignedint type;
bytes[byte] &= ~(0x03 << off);
type = irq_get_trigger_type(i + agent->irq_base); if (type & IRQ_TYPE_EDGE_RISING)
bytes[byte] |= BIT(off + 1); if (type & IRQ_TYPE_EDGE_FALLING)
bytes[byte] |= BIT(off + 0);
edge_change &= ~BIT(i);
}
/* Write */
status = twl_i2c_write(sih->module, bytes,
sih->edr_offset, sih->bytes_edr); if (status)
pr_err("twl4030: %s, %s --> %d\n", __func__, "write", status);
}
staticinlineint sih_read_isr(conststruct sih *sih)
{ int status; union {
u8 bytes[4];
__le32 word;
} isr;
/* FIXME need retry-on-error ... */
isr.word = 0;
status = twl_i2c_read(sih->module, isr.bytes,
sih->mask[irq_line].isr_offset, sih->bytes_ixr);
return (status < 0) ? status : le32_to_cpu(isr.word);
}
/* * Generic handler for SIH interrupts ... we "know" this is called * in task context, with IRQs enabled.
*/ static irqreturn_t handle_twl4030_sih(int irq, void *data)
{ struct sih_agent *agent = irq_get_handler_data(irq); conststruct sih *sih = agent->sih; int isr;
/* reading ISR acks the IRQs, using clear-on-read mode */
isr = sih_read_isr(sih);
if (isr < 0) {
pr_err("twl4030: %s SIH, read ISR error %d\n",
sih->name, isr); /* REVISIT: recover; eventually mask it all, etc */ return IRQ_HANDLED;
}
while (isr) {
irq = fls(isr);
irq--;
isr &= ~BIT(irq);
/* returns the first IRQ used by this SIH bank, or negative errno */ int twl4030_sih_setup(struct device *dev, int module, int irq_base)
{ int sih_mod; conststruct sih *sih = NULL; struct sih_agent *agent; int i, irq; int status = -EINVAL;
/* only support modules with standard clear-on-read for now */ for (sih_mod = 0, sih = sih_modules; sih_mod < nr_sih_modules;
sih_mod++, sih++) { if (sih->module == module && sih->set_cor) {
status = 0; break;
}
}
if (status < 0) {
dev_err(dev, "module to setup SIH for not found\n"); return status;
}
agent = kzalloc(sizeof(*agent), GFP_KERNEL); if (!agent) return -ENOMEM;
/* FIXME pass in which interrupt line we'll use ... */ #define twl_irq_line 0
int twl4030_init_irq(struct device *dev, int irq_num)
{ staticstruct irq_chip twl4030_irq_chip; int status, i; int irq_base, irq_end, nr_irqs;
/* * TWL core and pwr interrupts must be contiguous because * the hwirqs numbers are defined contiguously from 1 to 15. * Create only one domain for both.
*/
nr_irqs = TWL4030_PWR_NR_IRQS + TWL4030_CORE_NR_IRQS;
irq_base = irq_alloc_descs(-1, 0, nr_irqs, 0); if (irq_base < 0) {
dev_err(dev, "Fail to allocate IRQ descs\n"); return irq_base;
}
/* * Mask and clear all TWL4030 interrupts since initially we do * not have any TWL4030 module interrupt handlers present
*/
status = twl4030_init_sih_modules(twl_irq_line); if (status < 0) return status;
twl4030_irq_base = irq_base;
/* * Install an irq handler for each of the SIH modules; * clone dummy irq_chip since PIH can't *do* anything
*/
twl4030_irq_chip = dummy_irq_chip;
twl4030_irq_chip.name = "twl4030";
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.