xref: /openbmc/linux/drivers/mfd/twl6030-irq.c (revision e8deb28c)
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