Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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