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