1e8deb28cSBalaji T K /* 2e8deb28cSBalaji T K * twl6030-irq.c - TWL6030 irq support 3e8deb28cSBalaji T K * 4e8deb28cSBalaji T K * Copyright (C) 2005-2009 Texas Instruments, Inc. 5e8deb28cSBalaji T K * 6e8deb28cSBalaji T K * Modifications to defer interrupt handling to a kernel thread: 7e8deb28cSBalaji T K * Copyright (C) 2006 MontaVista Software, Inc. 8e8deb28cSBalaji T K * 9e8deb28cSBalaji T K * Based on tlv320aic23.c: 10e8deb28cSBalaji T K * Copyright (c) by Kai Svahn <kai.svahn@nokia.com> 11e8deb28cSBalaji T K * 12e8deb28cSBalaji T K * Code cleanup and modifications to IRQ handler. 13e8deb28cSBalaji T K * by syed khasim <x0khasim@ti.com> 14e8deb28cSBalaji T K * 15e8deb28cSBalaji T K * TWL6030 specific code and IRQ handling changes by 16e8deb28cSBalaji T K * Jagadeesh Bhaskar Pakaravoor <j-pakaravoor@ti.com> 17e8deb28cSBalaji T K * Balaji T K <balajitk@ti.com> 18e8deb28cSBalaji T K * 19e8deb28cSBalaji T K * This program is free software; you can redistribute it and/or modify 20e8deb28cSBalaji T K * it under the terms of the GNU General Public License as published by 21e8deb28cSBalaji T K * the Free Software Foundation; either version 2 of the License, or 22e8deb28cSBalaji T K * (at your option) any later version. 23e8deb28cSBalaji T K * 24e8deb28cSBalaji T K * This program is distributed in the hope that it will be useful, 25e8deb28cSBalaji T K * but WITHOUT ANY WARRANTY; without even the implied warranty of 26e8deb28cSBalaji T K * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27e8deb28cSBalaji T K * GNU General Public License for more details. 28e8deb28cSBalaji T K * 29e8deb28cSBalaji T K * You should have received a copy of the GNU General Public License 30e8deb28cSBalaji T K * along with this program; if not, write to the Free Software 31e8deb28cSBalaji T K * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 32e8deb28cSBalaji T K */ 33e8deb28cSBalaji T K 34e8deb28cSBalaji T K #include <linux/init.h> 35e8deb28cSBalaji T K #include <linux/interrupt.h> 36e8deb28cSBalaji T K #include <linux/irq.h> 37e8deb28cSBalaji T K #include <linux/kthread.h> 38e8deb28cSBalaji T K #include <linux/i2c/twl.h> 39e8deb28cSBalaji T K 40e8deb28cSBalaji T K /* 41e8deb28cSBalaji T K * TWL6030 (unlike its predecessors, which had two level interrupt handling) 42e8deb28cSBalaji T K * three interrupt registers INT_STS_A, INT_STS_B and INT_STS_C. 43e8deb28cSBalaji T K * It exposes status bits saying who has raised an interrupt. There are 44e8deb28cSBalaji T K * three mask registers that corresponds to these status registers, that 45e8deb28cSBalaji T K * enables/disables these interrupts. 46e8deb28cSBalaji T K * 47e8deb28cSBalaji T K * We set up IRQs starting at a platform-specified base. An interrupt map table, 48e8deb28cSBalaji T K * specifies mapping between interrupt number and the associated module. 49e8deb28cSBalaji T K * 50e8deb28cSBalaji T K */ 51e8deb28cSBalaji T K 52e8deb28cSBalaji T K static int twl6030_interrupt_mapping[24] = { 53e8deb28cSBalaji T K PWR_INTR_OFFSET, /* Bit 0 PWRON */ 54e8deb28cSBalaji T K PWR_INTR_OFFSET, /* Bit 1 RPWRON */ 55e8deb28cSBalaji T K PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */ 56e8deb28cSBalaji T K RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ 57e8deb28cSBalaji T K RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ 58e8deb28cSBalaji T K HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ 59e8deb28cSBalaji T K SMPSLDO_INTR_OFFSET, /* Bit 6 VXXX_SHORT */ 60e8deb28cSBalaji T K SMPSLDO_INTR_OFFSET, /* Bit 7 VMMC_SHORT */ 61e8deb28cSBalaji T K 62e8deb28cSBalaji T K SMPSLDO_INTR_OFFSET, /* Bit 8 VUSIM_SHORT */ 63e8deb28cSBalaji T K BATDETECT_INTR_OFFSET, /* Bit 9 BAT */ 64e8deb28cSBalaji T K SIMDETECT_INTR_OFFSET, /* Bit 10 SIM */ 65e8deb28cSBalaji T K MMCDETECT_INTR_OFFSET, /* Bit 11 MMC */ 66e8deb28cSBalaji T K RSV_INTR_OFFSET, /* Bit 12 Reserved */ 67e8deb28cSBalaji T K MADC_INTR_OFFSET, /* Bit 13 GPADC_RT_EOC */ 68e8deb28cSBalaji T K MADC_INTR_OFFSET, /* Bit 14 GPADC_SW_EOC */ 69e8deb28cSBalaji T K GASGAUGE_INTR_OFFSET, /* Bit 15 CC_AUTOCAL */ 70e8deb28cSBalaji T K 71e8deb28cSBalaji T K USBOTG_INTR_OFFSET, /* Bit 16 ID_WKUP */ 72e8deb28cSBalaji T K USBOTG_INTR_OFFSET, /* Bit 17 VBUS_WKUP */ 73e8deb28cSBalaji T K USBOTG_INTR_OFFSET, /* Bit 18 ID */ 74e8deb28cSBalaji T K USBOTG_INTR_OFFSET, /* Bit 19 VBUS */ 75e8deb28cSBalaji T K CHARGER_INTR_OFFSET, /* Bit 20 CHRG_CTRL */ 76e8deb28cSBalaji T K CHARGER_INTR_OFFSET, /* Bit 21 EXT_CHRG */ 77e8deb28cSBalaji T K CHARGER_INTR_OFFSET, /* Bit 22 INT_CHRG */ 78e8deb28cSBalaji T K RSV_INTR_OFFSET, /* Bit 23 Reserved */ 79e8deb28cSBalaji T K }; 80e8deb28cSBalaji T K /*----------------------------------------------------------------------*/ 81e8deb28cSBalaji T K 82e8deb28cSBalaji T K static unsigned twl6030_irq_base; 83e8deb28cSBalaji T K 84e8deb28cSBalaji T K static struct completion irq_event; 85e8deb28cSBalaji T K 86e8deb28cSBalaji T K /* 87e8deb28cSBalaji T K * This thread processes interrupts reported by the Primary Interrupt Handler. 88e8deb28cSBalaji T K */ 89e8deb28cSBalaji T K static int twl6030_irq_thread(void *data) 90e8deb28cSBalaji T K { 91e8deb28cSBalaji T K long irq = (long)data; 92e8deb28cSBalaji T K static unsigned i2c_errors; 93e8deb28cSBalaji T K static const unsigned max_i2c_errors = 100; 94e8deb28cSBalaji T K int ret; 95e8deb28cSBalaji T K 96e8deb28cSBalaji T K current->flags |= PF_NOFREEZE; 97e8deb28cSBalaji T K 98e8deb28cSBalaji T K while (!kthread_should_stop()) { 99e8deb28cSBalaji T K int i; 100e8deb28cSBalaji T K union { 101e8deb28cSBalaji T K u8 bytes[4]; 102e8deb28cSBalaji T K u32 int_sts; 103e8deb28cSBalaji T K } sts; 104e8deb28cSBalaji T K 105e8deb28cSBalaji T K /* Wait for IRQ, then read PIH irq status (also blocking) */ 106e8deb28cSBalaji T K wait_for_completion_interruptible(&irq_event); 107e8deb28cSBalaji T K 108e8deb28cSBalaji T K /* read INT_STS_A, B and C in one shot using a burst read */ 109e8deb28cSBalaji T K ret = twl_i2c_read(TWL_MODULE_PIH, sts.bytes, 110e8deb28cSBalaji T K REG_INT_STS_A, 3); 111e8deb28cSBalaji T K if (ret) { 112e8deb28cSBalaji T K pr_warning("twl6030: I2C error %d reading PIH ISR\n", 113e8deb28cSBalaji T K ret); 114e8deb28cSBalaji T K if (++i2c_errors >= max_i2c_errors) { 115e8deb28cSBalaji T K printk(KERN_ERR "Maximum I2C error count" 116e8deb28cSBalaji T K " exceeded. Terminating %s.\n", 117e8deb28cSBalaji T K __func__); 118e8deb28cSBalaji T K break; 119e8deb28cSBalaji T K } 120e8deb28cSBalaji T K complete(&irq_event); 121e8deb28cSBalaji T K continue; 122e8deb28cSBalaji T K } 123e8deb28cSBalaji T K 124e8deb28cSBalaji T K 125e8deb28cSBalaji T K 126e8deb28cSBalaji T K sts.bytes[3] = 0; /* Only 24 bits are valid*/ 127e8deb28cSBalaji T K 128e8deb28cSBalaji T K for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) { 129e8deb28cSBalaji T K local_irq_disable(); 130e8deb28cSBalaji T K if (sts.int_sts & 0x1) { 131e8deb28cSBalaji T K int module_irq = twl6030_irq_base + 132e8deb28cSBalaji T K twl6030_interrupt_mapping[i]; 133e8deb28cSBalaji T K struct irq_desc *d = irq_to_desc(module_irq); 134e8deb28cSBalaji T K 135e8deb28cSBalaji T K if (!d) { 136e8deb28cSBalaji T K pr_err("twl6030: Invalid SIH IRQ: %d\n", 137e8deb28cSBalaji T K module_irq); 138e8deb28cSBalaji T K return -EINVAL; 139e8deb28cSBalaji T K } 140e8deb28cSBalaji T K 141e8deb28cSBalaji T K /* These can't be masked ... always warn 142e8deb28cSBalaji T K * if we get any surprises. 143e8deb28cSBalaji T K */ 144e8deb28cSBalaji T K if (d->status & IRQ_DISABLED) 145e8deb28cSBalaji T K note_interrupt(module_irq, d, 146e8deb28cSBalaji T K IRQ_NONE); 147e8deb28cSBalaji T K else 148e8deb28cSBalaji T K d->handle_irq(module_irq, d); 149e8deb28cSBalaji T K 150e8deb28cSBalaji T K } 151e8deb28cSBalaji T K local_irq_enable(); 152e8deb28cSBalaji T K } 153e8deb28cSBalaji T K ret = twl_i2c_write(TWL_MODULE_PIH, sts.bytes, 154e8deb28cSBalaji T K REG_INT_STS_A, 3); /* clear INT_STS_A */ 155e8deb28cSBalaji T K if (ret) 156e8deb28cSBalaji T K pr_warning("twl6030: I2C error in clearing PIH ISR\n"); 157e8deb28cSBalaji T K 158e8deb28cSBalaji T K enable_irq(irq); 159e8deb28cSBalaji T K } 160e8deb28cSBalaji T K 161e8deb28cSBalaji T K return 0; 162e8deb28cSBalaji T K } 163e8deb28cSBalaji T K 164e8deb28cSBalaji T K /* 165e8deb28cSBalaji T K * handle_twl6030_int() is the desc->handle method for the twl6030 interrupt. 166e8deb28cSBalaji T K * This is a chained interrupt, so there is no desc->action method for it. 167e8deb28cSBalaji T K * Now we need to query the interrupt controller in the twl6030 to determine 168e8deb28cSBalaji T K * which module is generating the interrupt request. However, we can't do i2c 169e8deb28cSBalaji T K * transactions in interrupt context, so we must defer that work to a kernel 170e8deb28cSBalaji T K * thread. All we do here is acknowledge and mask the interrupt and wakeup 171e8deb28cSBalaji T K * the kernel thread. 172e8deb28cSBalaji T K */ 173e8deb28cSBalaji T K static irqreturn_t handle_twl6030_pih(int irq, void *devid) 174e8deb28cSBalaji T K { 175e8deb28cSBalaji T K disable_irq_nosync(irq); 176e8deb28cSBalaji T K complete(devid); 177e8deb28cSBalaji T K return IRQ_HANDLED; 178e8deb28cSBalaji T K } 179e8deb28cSBalaji T K 180e8deb28cSBalaji T K /*----------------------------------------------------------------------*/ 181e8deb28cSBalaji T K 182e8deb28cSBalaji T K static inline void activate_irq(int irq) 183e8deb28cSBalaji T K { 184e8deb28cSBalaji T K #ifdef CONFIG_ARM 185e8deb28cSBalaji T K /* ARM requires an extra step to clear IRQ_NOREQUEST, which it 186e8deb28cSBalaji T K * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. 187e8deb28cSBalaji T K */ 188e8deb28cSBalaji T K set_irq_flags(irq, IRQF_VALID); 189e8deb28cSBalaji T K #else 190e8deb28cSBalaji T K /* same effect on other architectures */ 191e8deb28cSBalaji T K set_irq_noprobe(irq); 192e8deb28cSBalaji T K #endif 193e8deb28cSBalaji T K } 194e8deb28cSBalaji T K 195e8deb28cSBalaji T K /*----------------------------------------------------------------------*/ 196e8deb28cSBalaji T K 197e8deb28cSBalaji T K static unsigned twl6030_irq_next; 198e8deb28cSBalaji T K 199e8deb28cSBalaji T K /*----------------------------------------------------------------------*/ 200e8deb28cSBalaji T K int twl6030_interrupt_unmask(u8 bit_mask, u8 offset) 201e8deb28cSBalaji T K { 202e8deb28cSBalaji T K int ret; 203e8deb28cSBalaji T K u8 unmask_value; 204e8deb28cSBalaji T K ret = twl_i2c_read_u8(TWL_MODULE_PIH, &unmask_value, 205e8deb28cSBalaji T K REG_INT_STS_A + offset); 206e8deb28cSBalaji T K unmask_value &= (~(bit_mask)); 207e8deb28cSBalaji T K ret |= twl_i2c_write_u8(TWL_MODULE_PIH, unmask_value, 208e8deb28cSBalaji T K REG_INT_STS_A + offset); /* unmask INT_MSK_A/B/C */ 209e8deb28cSBalaji T K return ret; 210e8deb28cSBalaji T K } 211e8deb28cSBalaji T K EXPORT_SYMBOL(twl6030_interrupt_unmask); 212e8deb28cSBalaji T K 213e8deb28cSBalaji T K int twl6030_interrupt_mask(u8 bit_mask, u8 offset) 214e8deb28cSBalaji T K { 215e8deb28cSBalaji T K int ret; 216e8deb28cSBalaji T K u8 mask_value; 217e8deb28cSBalaji T K ret = twl_i2c_read_u8(TWL_MODULE_PIH, &mask_value, 218e8deb28cSBalaji T K REG_INT_STS_A + offset); 219e8deb28cSBalaji T K mask_value |= (bit_mask); 220e8deb28cSBalaji T K ret |= twl_i2c_write_u8(TWL_MODULE_PIH, mask_value, 221e8deb28cSBalaji T K REG_INT_STS_A + offset); /* mask INT_MSK_A/B/C */ 222e8deb28cSBalaji T K return ret; 223e8deb28cSBalaji T K } 224e8deb28cSBalaji T K EXPORT_SYMBOL(twl6030_interrupt_mask); 225e8deb28cSBalaji T K 226e8deb28cSBalaji T K int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) 227e8deb28cSBalaji T K { 228e8deb28cSBalaji T K 229e8deb28cSBalaji T K int status = 0; 230e8deb28cSBalaji T K int i; 231e8deb28cSBalaji T K struct task_struct *task; 232e8deb28cSBalaji T K int ret; 233e8deb28cSBalaji T K u8 mask[4]; 234e8deb28cSBalaji T K 235e8deb28cSBalaji T K static struct irq_chip twl6030_irq_chip; 236e8deb28cSBalaji T K mask[1] = 0xFF; 237e8deb28cSBalaji T K mask[2] = 0xFF; 238e8deb28cSBalaji T K mask[3] = 0xFF; 239e8deb28cSBalaji T K ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], 240e8deb28cSBalaji T K REG_INT_MSK_LINE_A, 3); /* MASK ALL INT LINES */ 241e8deb28cSBalaji T K ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], 242e8deb28cSBalaji T K REG_INT_MSK_STS_A, 3); /* MASK ALL INT STS */ 243e8deb28cSBalaji T K ret = twl_i2c_write(TWL_MODULE_PIH, &mask[0], 244e8deb28cSBalaji T K REG_INT_STS_A, 3); /* clear INT_STS_A,B,C */ 245e8deb28cSBalaji T K 246e8deb28cSBalaji T K twl6030_irq_base = irq_base; 247e8deb28cSBalaji T K 248e8deb28cSBalaji T K /* install an irq handler for each of the modules; 249e8deb28cSBalaji T K * clone dummy irq_chip since PIH can't *do* anything 250e8deb28cSBalaji T K */ 251e8deb28cSBalaji T K twl6030_irq_chip = dummy_irq_chip; 252e8deb28cSBalaji T K twl6030_irq_chip.name = "twl6030"; 253e8deb28cSBalaji T K twl6030_irq_chip.set_type = NULL; 254e8deb28cSBalaji T K 255e8deb28cSBalaji T K for (i = irq_base; i < irq_end; i++) { 256e8deb28cSBalaji T K set_irq_chip_and_handler(i, &twl6030_irq_chip, 257e8deb28cSBalaji T K handle_simple_irq); 258e8deb28cSBalaji T K activate_irq(i); 259e8deb28cSBalaji T K } 260e8deb28cSBalaji T K 261e8deb28cSBalaji T K twl6030_irq_next = i; 262e8deb28cSBalaji T K pr_info("twl6030: %s (irq %d) chaining IRQs %d..%d\n", "PIH", 263e8deb28cSBalaji T K irq_num, irq_base, twl6030_irq_next - 1); 264e8deb28cSBalaji T K 265e8deb28cSBalaji T K /* install an irq handler to demultiplex the TWL6030 interrupt */ 266e8deb28cSBalaji T K init_completion(&irq_event); 267e8deb28cSBalaji T K task = kthread_run(twl6030_irq_thread, (void *)irq_num, "twl6030-irq"); 268e8deb28cSBalaji T K if (IS_ERR(task)) { 269e8deb28cSBalaji T K pr_err("twl6030: could not create irq %d thread!\n", irq_num); 270e8deb28cSBalaji T K status = PTR_ERR(task); 271e8deb28cSBalaji T K goto fail_kthread; 272e8deb28cSBalaji T K } 273e8deb28cSBalaji T K 274e8deb28cSBalaji T K status = request_irq(irq_num, handle_twl6030_pih, IRQF_DISABLED, 275e8deb28cSBalaji T K "TWL6030-PIH", &irq_event); 276e8deb28cSBalaji T K if (status < 0) { 277e8deb28cSBalaji T K pr_err("twl6030: could not claim irq%d: %d\n", irq_num, status); 278e8deb28cSBalaji T K goto fail_irq; 279e8deb28cSBalaji T K } 280e8deb28cSBalaji T K return status; 281e8deb28cSBalaji T K fail_irq: 282e8deb28cSBalaji T K free_irq(irq_num, &irq_event); 283e8deb28cSBalaji T K 284e8deb28cSBalaji T K fail_kthread: 285e8deb28cSBalaji T K for (i = irq_base; i < irq_end; i++) 286e8deb28cSBalaji T K set_irq_chip_and_handler(i, NULL, NULL); 287e8deb28cSBalaji T K return status; 288e8deb28cSBalaji T K } 289e8deb28cSBalaji T K 290e8deb28cSBalaji T K int twl6030_exit_irq(void) 291e8deb28cSBalaji T K { 292e8deb28cSBalaji T K 293e8deb28cSBalaji T K if (twl6030_irq_base) { 294e8deb28cSBalaji T K pr_err("twl6030: can't yet clean up IRQs?\n"); 295e8deb28cSBalaji T K return -ENOSYS; 296e8deb28cSBalaji T K } 297e8deb28cSBalaji T K return 0; 298e8deb28cSBalaji T K } 299e8deb28cSBalaji T K 300