Blame view

drivers/mfd/twl4030-irq.c 20.1 KB
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  /*
   * 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>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
   */
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
29
  #include <linux/export.h>
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
30
31
  #include <linux/interrupt.h>
  #include <linux/irq.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
32
  #include <linux/slab.h>
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
33
34
  #include <linux/of.h>
  #include <linux/irqdomain.h>
a20542565   Wolfram Sang   mfd: twl: Move he...
35
  #include <linux/mfd/twl.h>
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
36

b0b4a7c28   G, Manjunath Kondaiah   mfd: Fix twl-irq ...
37
  #include "twl-core.h"
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  
  /*
   * 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
   */
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
56
57
  #define TWL4030_CORE_NR_IRQS	8
  #define TWL4030_PWR_NR_IRQS	8
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
58
59
60
61
62
  
  /* PIH register offsets */
  #define REG_PIH_ISR_P1			0x01
  #define REG_PIH_ISR_P2			0x02
  #define REG_PIH_SIR			0x03	/* for testing */
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  /* Linux could (eventually) use either IRQ line */
  static int irq_line;
  
  struct sih {
  	char	name[8];
  	u8	module;			/* module id */
  	u8	control_offset;		/* for SIH_CTRL */
  	bool	set_cor;
  
  	u8	bits;			/* valid in isr/imr */
  	u8	bytes_ixr;		/* bytelen of ISR/IMR/SIR */
  
  	u8	edr_offset;
  	u8	bytes_edr;		/* bytelen of EDR */
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
77
  	u8	irq_lines;		/* number of supported irq lines */
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
78
  	/* SIR ignored -- set interrupt, for testing only */
35a27e8e6   Thomas Gleixner   mfd: Rename struc...
79
  	struct sih_irq_data {
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
80
81
82
83
84
  		u8	isr_offset;
  		u8	imr_offset;
  	} mask[2];
  	/* + 2 bytes padding */
  };
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
85
86
  static const struct sih *sih_modules;
  static int nr_sih_modules;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
87
88
89
90
91
92
93
  #define SIH_INITIALIZER(modname, nbits) \
  	.module		= TWL4030_MODULE_ ## modname, \
  	.control_offset = TWL4030_ ## modname ## _SIH_CTRL, \
  	.bits		= nbits, \
  	.bytes_ixr	= DIV_ROUND_UP(nbits, 8), \
  	.edr_offset	= TWL4030_ ## modname ## _EDR, \
  	.bytes_edr	= DIV_ROUND_UP((2*(nbits)), 8), \
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
94
  	.irq_lines	= 2, \
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
95
96
97
98
99
100
101
102
103
104
105
106
107
  	.mask = { { \
  		.isr_offset	= TWL4030_ ## modname ## _ISR1, \
  		.imr_offset	= TWL4030_ ## modname ## _IMR1, \
  	}, \
  	{ \
  		.isr_offset	= TWL4030_ ## modname ## _ISR2, \
  		.imr_offset	= TWL4030_ ## modname ## _IMR2, \
  	}, },
  
  /* register naming policies are inconsistent ... */
  #define TWL4030_INT_PWR_EDR		TWL4030_INT_PWR_EDR1
  #define TWL4030_MODULE_KEYPAD_KEYP	TWL4030_MODULE_KEYPAD
  #define TWL4030_MODULE_INT_PWR		TWL4030_MODULE_INT
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
108
109
  /*
   * Order in this table matches order in PIH_ISR.  That is,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
110
111
   * BIT(n) in PIH_ISR is sih_modules[n].
   */
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
112
113
  /* sih_modules_twl4030 is used both in twl4030 and twl5030 */
  static const struct sih sih_modules_twl4030[6] = {
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
114
115
116
117
118
119
120
121
122
123
  	[0] = {
  		.name		= "gpio",
  		.module		= TWL4030_MODULE_GPIO,
  		.control_offset	= REG_GPIO_SIH_CTRL,
  		.set_cor	= true,
  		.bits		= TWL4030_GPIO_MAX,
  		.bytes_ixr	= 3,
  		/* Note: *all* of these IRQs default to no-trigger */
  		.edr_offset	= REG_GPIO_EDR1,
  		.bytes_edr	= 5,
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
124
  		.irq_lines	= 2,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  		.mask = { {
  			.isr_offset	= REG_GPIO_ISR1A,
  			.imr_offset	= REG_GPIO_IMR1A,
  		}, {
  			.isr_offset	= REG_GPIO_ISR1B,
  			.imr_offset	= REG_GPIO_IMR1B,
  		}, },
  	},
  	[1] = {
  		.name		= "keypad",
  		.set_cor	= true,
  		SIH_INITIALIZER(KEYPAD_KEYP, 4)
  	},
  	[2] = {
  		.name		= "bci",
  		.module		= TWL4030_MODULE_INTERRUPTS,
  		.control_offset	= TWL4030_INTERRUPTS_BCISIHCTRL,
8e52e279b   Grazvydas Ignotas   mfd: Fix TWL4030 ...
142
  		.set_cor	= true,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
143
144
145
146
147
  		.bits		= 12,
  		.bytes_ixr	= 2,
  		.edr_offset	= TWL4030_INTERRUPTS_BCIEDR1,
  		/* Note: most of these IRQs default to no-trigger */
  		.bytes_edr	= 3,
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
148
  		.irq_lines	= 2,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
  		.mask = { {
  			.isr_offset	= TWL4030_INTERRUPTS_BCIISR1A,
  			.imr_offset	= TWL4030_INTERRUPTS_BCIIMR1A,
  		}, {
  			.isr_offset	= TWL4030_INTERRUPTS_BCIISR1B,
  			.imr_offset	= TWL4030_INTERRUPTS_BCIIMR1B,
  		}, },
  	},
  	[3] = {
  		.name		= "madc",
  		SIH_INITIALIZER(MADC, 4)
  	},
  	[4] = {
  		/* USB doesn't use the same SIH organization */
  		.name		= "usb",
  	},
  	[5] = {
  		.name		= "power",
  		.set_cor	= true,
  		SIH_INITIALIZER(INT_PWR, 8)
  	},
  		/* there are no SIH modules #6 or #7 ... */
  };
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
  static const struct sih sih_modules_twl5031[8] = {
  	[0] = {
  		.name		= "gpio",
  		.module		= TWL4030_MODULE_GPIO,
  		.control_offset	= REG_GPIO_SIH_CTRL,
  		.set_cor	= true,
  		.bits		= TWL4030_GPIO_MAX,
  		.bytes_ixr	= 3,
  		/* Note: *all* of these IRQs default to no-trigger */
  		.edr_offset	= REG_GPIO_EDR1,
  		.bytes_edr	= 5,
  		.irq_lines	= 2,
  		.mask = { {
  			.isr_offset	= REG_GPIO_ISR1A,
  			.imr_offset	= REG_GPIO_IMR1A,
  		}, {
  			.isr_offset	= REG_GPIO_ISR1B,
  			.imr_offset	= REG_GPIO_IMR1B,
  		}, },
  	},
  	[1] = {
  		.name		= "keypad",
  		.set_cor	= true,
  		SIH_INITIALIZER(KEYPAD_KEYP, 4)
  	},
  	[2] = {
  		.name		= "bci",
  		.module		= TWL5031_MODULE_INTERRUPTS,
  		.control_offset	= TWL5031_INTERRUPTS_BCISIHCTRL,
  		.bits		= 7,
  		.bytes_ixr	= 1,
  		.edr_offset	= TWL5031_INTERRUPTS_BCIEDR1,
  		/* Note: most of these IRQs default to no-trigger */
  		.bytes_edr	= 2,
  		.irq_lines	= 2,
  		.mask = { {
  			.isr_offset	= TWL5031_INTERRUPTS_BCIISR1,
  			.imr_offset	= TWL5031_INTERRUPTS_BCIIMR1,
  		}, {
  			.isr_offset	= TWL5031_INTERRUPTS_BCIISR2,
  			.imr_offset	= TWL5031_INTERRUPTS_BCIIMR2,
  		}, },
  	},
  	[3] = {
  		.name		= "madc",
  		SIH_INITIALIZER(MADC, 4)
  	},
  	[4] = {
  		/* USB doesn't use the same SIH organization */
  		.name		= "usb",
  	},
  	[5] = {
  		.name		= "power",
  		.set_cor	= true,
  		SIH_INITIALIZER(INT_PWR, 8)
  	},
  	[6] = {
  		/*
191211f50   Ilkka Koskinen   mfd: Rename twl50...
230
231
232
  		 * ECI/DBI doesn't use the same SIH organization.
  		 * For example, it supports only one interrupt output line.
  		 * That is, the interrupts are seen on both INT1 and INT2 lines.
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
233
  		 */
191211f50   Ilkka Koskinen   mfd: Rename twl50...
234
  		.name		= "eci_dbi",
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
235
236
237
238
239
240
241
242
243
244
245
  		.module		= TWL5031_MODULE_ACCESSORY,
  		.bits		= 9,
  		.bytes_ixr	= 2,
  		.irq_lines	= 1,
  		.mask = { {
  			.isr_offset	= TWL5031_ACIIDR_LSB,
  			.imr_offset	= TWL5031_ACIIMR_LSB,
  		}, },
  
  	},
  	[7] = {
191211f50   Ilkka Koskinen   mfd: Rename twl50...
246
247
  		/* Audio accessory */
  		.name		= "audio",
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
  		.module		= TWL5031_MODULE_ACCESSORY,
  		.control_offset	= TWL5031_ACCSIHCTRL,
  		.bits		= 2,
  		.bytes_ixr	= 1,
  		.edr_offset	= TWL5031_ACCEDR1,
  		/* Note: most of these IRQs default to no-trigger */
  		.bytes_edr	= 1,
  		.irq_lines	= 2,
  		.mask = { {
  			.isr_offset	= TWL5031_ACCISR1,
  			.imr_offset	= TWL5031_ACCIMR1,
  		}, {
  			.isr_offset	= TWL5031_ACCISR2,
  			.imr_offset	= TWL5031_ACCIMR2,
  		}, },
  	},
  };
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
265
266
267
268
269
270
271
  #undef TWL4030_MODULE_KEYPAD_KEYP
  #undef TWL4030_MODULE_INT_PWR
  #undef TWL4030_INT_PWR_EDR
  
  /*----------------------------------------------------------------------*/
  
  static unsigned twl4030_irq_base;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
272
273
274
275
276
277
278
279
280
  /*
   * 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.
   */
1cef8e410   Russell King   mfd: twl4030 irq ...
281
  static irqreturn_t handle_twl4030_pih(int irq, void *devid)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
282
  {
7750c9b0d   Felipe Balbi   mfd: Drop the twl...
283
284
  	irqreturn_t	ret;
  	u8		pih_isr;
6fbc6420b   Peter Ujfalusi   mfd: twl4030-irq:...
285
286
  	ret = twl_i2c_read_u8(TWL_MODULE_PIH, &pih_isr,
  			      REG_PIH_ISR_P1);
7750c9b0d   Felipe Balbi   mfd: Drop the twl...
287
  	if (ret) {
04aa4438c   Lee Jones   mfd: twl4030-irq:...
288
289
  		pr_warn("twl4030: I2C error %d reading PIH ISR
  ", ret);
7750c9b0d   Felipe Balbi   mfd: Drop the twl...
290
291
  		return IRQ_NONE;
  	}
5a903090e   Felipe Balbi   mfd: Micro-optimi...
292
293
294
295
296
297
298
  	while (pih_isr) {
  		unsigned long	pending = __ffs(pih_isr);
  		unsigned int	irq;
  
  		pih_isr &= ~BIT(pending);
  		irq = pending + twl4030_irq_base;
  		handle_nested_irq(irq);
7750c9b0d   Felipe Balbi   mfd: Drop the twl...
299
  	}
1cef8e410   Russell King   mfd: twl4030 irq ...
300
  	return IRQ_HANDLED;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
301
  }
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
302

a30d46c04   David Brownell   mfd: twl4030 IRQ ...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  /*----------------------------------------------------------------------*/
  
  /*
   * 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...
   */
  static int twl4030_init_sih_modules(unsigned line)
  {
  	const struct 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 */
04aa4438c   Lee Jones   mfd: twl4030-irq:...
328
  	memset(buf, 0xff, sizeof(buf));
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
329
  	sih = sih_modules;
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
330
  	for (i = 0; i < nr_sih_modules; i++, sih++) {
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
331
332
333
  		/* skip USB -- it's funky */
  		if (!sih->bytes_ixr)
  			continue;
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
334
335
336
  		/* Not all the SIH modules support multiple interrupt lines */
  		if (sih->irq_lines <= line)
  			continue;
fc7b92fca   Balaji T K   mfd: Rename all t...
337
  		status = twl_i2c_write(sih->module, buf,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
338
339
340
341
342
  				sih->mask[line].imr_offset, sih->bytes_ixr);
  		if (status < 0)
  			pr_err("twl4030: err %d initializing %s %s
  ",
  					status, sih->name, "IMR");
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
343
344
  		/*
  		 * Maybe disable "exclusive" mode; buffer second pending irq;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
345
346
347
  		 * set Clear-On-Read (COR) bit.
  		 *
  		 * NOTE that sometimes COR polarity is documented as being
8e52e279b   Grazvydas Ignotas   mfd: Fix TWL4030 ...
348
  		 * inverted:  for MADC, COR=1 means "clear on write".
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
349
350
351
  		 * And for PWR_INT it's not documented...
  		 */
  		if (sih->set_cor) {
fc7b92fca   Balaji T K   mfd: Rename all t...
352
  			status = twl_i2c_write_u8(sih->module,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
353
354
355
356
357
358
359
360
361
362
  					TWL4030_SIH_CTRL_COR_MASK,
  					sih->control_offset);
  			if (status < 0)
  				pr_err("twl4030: err %d initializing %s %s
  ",
  						status, sih->name, "SIH_CTRL");
  		}
  	}
  
  	sih = sih_modules;
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
363
  	for (i = 0; i < nr_sih_modules; i++, sih++) {
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
364
365
366
367
368
369
  		u8 rxbuf[4];
  		int j;
  
  		/* skip USB */
  		if (!sih->bytes_ixr)
  			continue;
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
370
371
372
  		/* Not all the SIH modules support multiple interrupt lines */
  		if (sih->irq_lines <= line)
  			continue;
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
373
374
  		/*
  		 * Clear pending interrupt status.  Either the read was
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
375
376
377
378
379
  		 * 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++) {
fc7b92fca   Balaji T K   mfd: Rename all t...
380
  			status = twl_i2c_read(sih->module, rxbuf,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
381
382
  				sih->mask[line].isr_offset, sih->bytes_ixr);
  			if (status < 0)
8a012ff9d   Lee Jones   mfd: twl4030-irq:...
383
384
  				pr_warn("twl4030: err %d initializing %s %s
  ",
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
385
  					status, sih->name, "ISR");
8a012ff9d   Lee Jones   mfd: twl4030-irq:...
386
  			if (!sih->set_cor) {
fc7b92fca   Balaji T K   mfd: Rename all t...
387
  				status = twl_i2c_write(sih->module, buf,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
388
389
  					sih->mask[line].isr_offset,
  					sih->bytes_ixr);
8a012ff9d   Lee Jones   mfd: twl4030-irq:...
390
391
392
393
394
  				if (status < 0)
  					pr_warn("twl4030: write failed: %d
  ",
  						status);
  			}
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
395
396
  			/*
  			 * else COR=1 means read sufficed.
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
397
398
399
400
401
402
403
404
405
406
  			 * (for most SIH modules...)
  			 */
  		}
  	}
  
  	return 0;
  }
  
  static inline void activate_irq(int irq)
  {
9bd09f345   Rob Herring   mfd: Kill off set...
407
  	irq_clear_status_flags(irq, IRQ_NOREQUEST | IRQ_NOPROBE);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
408
409
410
  }
  
  /*----------------------------------------------------------------------*/
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
411
412
413
414
415
416
  struct sih_agent {
  	int			irq_base;
  	const struct sih	*sih;
  
  	u32			imr;
  	bool			imr_change_pending;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
417
418
  
  	u32			edge_change;
91e3569ff   Felipe Balbi   mfd: Implement bu...
419
420
  
  	struct mutex		irq_lock;
c1e61bcf8   NeilBrown   mfd: Use request_...
421
  	char			*irq_name;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
422
  };
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
423
424
425
426
427
428
429
430
  /*----------------------------------------------------------------------*/
  
  /*
   * 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.
   */
845aeab5f   Mark Brown   mfd: Convert TWL4...
431
  static void twl4030_sih_mask(struct irq_data *data)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
432
  {
848684246   Felipe Balbi   mfd: Drop twl4030...
433
  	struct sih_agent *agent = irq_data_get_irq_chip_data(data);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
434

848684246   Felipe Balbi   mfd: Drop twl4030...
435
436
  	agent->imr |= BIT(data->irq - agent->irq_base);
  	agent->imr_change_pending = true;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
437
  }
845aeab5f   Mark Brown   mfd: Convert TWL4...
438
  static void twl4030_sih_unmask(struct irq_data *data)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
439
  {
848684246   Felipe Balbi   mfd: Drop twl4030...
440
  	struct sih_agent *agent = irq_data_get_irq_chip_data(data);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
441

848684246   Felipe Balbi   mfd: Drop twl4030...
442
443
  	agent->imr &= ~BIT(data->irq - agent->irq_base);
  	agent->imr_change_pending = true;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
444
  }
845aeab5f   Mark Brown   mfd: Convert TWL4...
445
  static int twl4030_sih_set_type(struct irq_data *data, unsigned trigger)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
446
  {
848684246   Felipe Balbi   mfd: Drop twl4030...
447
  	struct sih_agent *agent = irq_data_get_irq_chip_data(data);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
448
449
450
  
  	if (trigger & ~(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
  		return -EINVAL;
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
451
  	if (irqd_get_trigger_type(data) != trigger)
848684246   Felipe Balbi   mfd: Drop twl4030...
452
  		agent->edge_change |= BIT(data->irq - agent->irq_base);
91e3569ff   Felipe Balbi   mfd: Implement bu...
453

a30d46c04   David Brownell   mfd: twl4030 IRQ ...
454
455
  	return 0;
  }
91e3569ff   Felipe Balbi   mfd: Implement bu...
456
457
  static void twl4030_sih_bus_lock(struct irq_data *data)
  {
848684246   Felipe Balbi   mfd: Drop twl4030...
458
  	struct sih_agent	*agent = irq_data_get_irq_chip_data(data);
91e3569ff   Felipe Balbi   mfd: Implement bu...
459

848684246   Felipe Balbi   mfd: Drop twl4030...
460
  	mutex_lock(&agent->irq_lock);
91e3569ff   Felipe Balbi   mfd: Implement bu...
461
462
463
464
  }
  
  static void twl4030_sih_bus_sync_unlock(struct irq_data *data)
  {
848684246   Felipe Balbi   mfd: Drop twl4030...
465
466
467
468
469
470
471
472
473
  	struct sih_agent	*agent = irq_data_get_irq_chip_data(data);
  	const struct sih	*sih = agent->sih;
  	int			status;
  
  	if (agent->imr_change_pending) {
  		union {
  			u32	word;
  			u8	bytes[4];
  		} imr;
c9531227b   NeilBrown   mfd: Fix twl4030-...
474
  		/* byte[0] gets overwritten as we write ... */
14591d888   Peter Ujfalusi   mfd/rtc/gpio: twl...
475
  		imr.word = cpu_to_le32(agent->imr);
848684246   Felipe Balbi   mfd: Drop twl4030...
476
477
478
479
480
481
482
483
484
485
486
  		agent->imr_change_pending = false;
  
  		/* write the whole mask ... simpler than subsetting it */
  		status = twl_i2c_write(sih->module, imr.bytes,
  				sih->mask[irq_line].imr_offset,
  				sih->bytes_ixr);
  		if (status)
  			pr_err("twl4030: %s, %s --> %d
  ", __func__,
  					"write", status);
  	}
91e3569ff   Felipe Balbi   mfd: Implement bu...
487

2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
488
489
490
491
492
493
494
495
496
497
498
499
500
  	if (agent->edge_change) {
  		u32		edge_change;
  		u8		bytes[6];
  
  		edge_change = agent->edge_change;
  		agent->edge_change = 0;
  
  		/*
  		 * 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.
  		 */
14591d888   Peter Ujfalusi   mfd/rtc/gpio: twl...
501
  		status = twl_i2c_read(sih->module, bytes,
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
502
503
504
505
506
507
508
509
510
511
512
  				sih->edr_offset, sih->bytes_edr);
  		if (status) {
  			pr_err("twl4030: %s, %s --> %d
  ", __func__,
  					"read", status);
  			return;
  		}
  
  		/* Modify only the bits we know must change */
  		while (edge_change) {
  			int		i = fls(edge_change) - 1;
14591d888   Peter Ujfalusi   mfd/rtc/gpio: twl...
513
  			int		byte = i >> 2;
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
514
515
  			int		off = (i & 0x3) * 2;
  			unsigned int	type;
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
516
  			bytes[byte] &= ~(0x03 << off);
5dbf79d49   Javier Martinez Canillas   mfd: twl4030-irq:...
517
  			type = irq_get_trigger_type(i + agent->irq_base);
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
  			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
  ", __func__,
  					"write", status);
  	}
848684246   Felipe Balbi   mfd: Drop twl4030...
534
  	mutex_unlock(&agent->irq_lock);
91e3569ff   Felipe Balbi   mfd: Implement bu...
535
  }
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
536
537
  static struct irq_chip twl4030_sih_irq_chip = {
  	.name		= "twl4030",
8cd6af293   Felipe Balbi   mfd: Remove twl40...
538
  	.irq_mask	= twl4030_sih_mask,
845aeab5f   Mark Brown   mfd: Convert TWL4...
539
540
  	.irq_unmask	= twl4030_sih_unmask,
  	.irq_set_type	= twl4030_sih_set_type,
91e3569ff   Felipe Balbi   mfd: Implement bu...
541
542
  	.irq_bus_lock	= twl4030_sih_bus_lock,
  	.irq_bus_sync_unlock = twl4030_sih_bus_sync_unlock,
55098ff79   Kevin Hilman   mfd: twl4030: All...
543
  	.flags		= IRQCHIP_SKIP_SET_WAKE,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  };
  
  /*----------------------------------------------------------------------*/
  
  static inline int sih_read_isr(const struct sih *sih)
  {
  	int status;
  	union {
  		u8 bytes[4];
  		u32 word;
  	} isr;
  
  	/* FIXME need retry-on-error ... */
  
  	isr.word = 0;
fc7b92fca   Balaji T K   mfd: Rename all t...
559
  	status = twl_i2c_read(sih->module, isr.bytes,
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
560
561
562
563
564
565
566
567
568
  			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.
   */
c1e61bcf8   NeilBrown   mfd: Use request_...
569
  static irqreturn_t handle_twl4030_sih(int irq, void *data)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
570
  {
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
571
  	struct sih_agent *agent = irq_get_handler_data(irq);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
572
573
574
575
  	const struct sih *sih = agent->sih;
  	int isr;
  
  	/* reading ISR acks the IRQs, using clear-on-read mode */
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
576
  	isr = sih_read_isr(sih);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
577
578
579
580
581
582
  
  	if (isr < 0) {
  		pr_err("twl4030: %s SIH, read ISR error %d
  ",
  			sih->name, isr);
  		/* REVISIT:  recover; eventually mask it all, etc */
c1e61bcf8   NeilBrown   mfd: Use request_...
583
  		return IRQ_HANDLED;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
584
585
586
587
588
589
590
591
  	}
  
  	while (isr) {
  		irq = fls(isr);
  		irq--;
  		isr &= ~BIT(irq);
  
  		if (irq < sih->bits)
925e853c2   Felipe Balbi   mfd: Set twl4030-...
592
  			handle_nested_irq(agent->irq_base + irq);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
593
594
595
596
597
  		else
  			pr_err("twl4030: %s SIH, invalid ISR bit %d
  ",
  				sih->name, irq);
  	}
c1e61bcf8   NeilBrown   mfd: Use request_...
598
  	return IRQ_HANDLED;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
599
  }
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
600
  /* returns the first IRQ used by this SIH bank, or negative errno */
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
601
  int twl4030_sih_setup(struct device *dev, int module, int irq_base)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
602
603
604
605
606
607
  {
  	int			sih_mod;
  	const struct sih	*sih = NULL;
  	struct sih_agent	*agent;
  	int			i, irq;
  	int			status = -EINVAL;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
608
609
  
  	/* only support modules with standard clear-on-read for now */
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
610
  	for (sih_mod = 0, sih = sih_modules; sih_mod < nr_sih_modules;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
611
612
  			sih_mod++, sih++) {
  		if (sih->module == module && sih->set_cor) {
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
613
  			status = 0;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
614
615
616
  			break;
  		}
  	}
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
617

485857390   Uwe Kleine-König   mfd: twl4030-irq:...
618
619
620
  	if (status < 0) {
  		dev_err(dev, "module to setup SIH for not found
  ");
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
621
  		return status;
485857390   Uwe Kleine-König   mfd: twl4030-irq:...
622
  	}
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
623

04aa4438c   Lee Jones   mfd: twl4030-irq:...
624
  	agent = kzalloc(sizeof(*agent), GFP_KERNEL);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
625
626
  	if (!agent)
  		return -ENOMEM;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
627
628
629
  	agent->irq_base = irq_base;
  	agent->sih = sih;
  	agent->imr = ~0;
91e3569ff   Felipe Balbi   mfd: Implement bu...
630
  	mutex_init(&agent->irq_lock);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
631
632
633
  
  	for (i = 0; i < sih->bits; i++) {
  		irq = irq_base + i;
91e3569ff   Felipe Balbi   mfd: Implement bu...
634
  		irq_set_chip_data(irq, agent);
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
635
636
  		irq_set_chip_and_handler(irq, &twl4030_sih_irq_chip,
  					 handle_edge_irq);
b18d1f0f4   NeilBrown   mfd: Set twl4030-...
637
  		irq_set_nested_thread(irq, 1);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
638
639
  		activate_irq(irq);
  	}
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
640
641
  	/* replace generic PIH handler (handle_simple_irq) */
  	irq = sih_mod + twl4030_irq_base;
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
642
  	irq_set_handler_data(irq, agent);
c1e61bcf8   NeilBrown   mfd: Use request_...
643
  	agent->irq_name = kasprintf(GFP_KERNEL, "twl4030_%s", sih->name);
8b41669ce   Kalle Jokiniemi   mfd: twl4030: Fix...
644
  	status = request_threaded_irq(irq, NULL, handle_twl4030_sih,
7d5b1ed89   Fabio Estevam   mfd: twl4030-irq:...
645
  				      IRQF_EARLY_RESUME | IRQF_ONESHOT,
c1e61bcf8   NeilBrown   mfd: Use request_...
646
  				      agent->irq_name ?: sih->name, NULL);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
647

ec1a07b34   Benoit Cousson   mfd: Replace twl-...
648
649
  	dev_info(dev, "%s (irq %d) chaining IRQs %d..%d
  ", sih->name,
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
650
  			irq, irq_base, irq_base + i - 1);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
651

c1e61bcf8   NeilBrown   mfd: Use request_...
652
  	return status < 0 ? status : irq_base;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
653
654
655
  }
  
  /* FIXME need a call to reverse twl4030_sih_setup() ... */
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
656
657
658
659
  /*----------------------------------------------------------------------*/
  
  /* FIXME pass in which interrupt line we'll use ... */
  #define twl_irq_line	0
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
660
  int twl4030_init_irq(struct device *dev, int irq_num)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
661
662
  {
  	static struct irq_chip	twl4030_irq_chip;
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
663
  	int			status, i;
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
664
665
  	int			irq_base, irq_end, nr_irqs;
  	struct			device_node *node = dev->of_node;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
666

a30d46c04   David Brownell   mfd: twl4030 IRQ ...
667
  	/*
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
668
669
670
671
672
673
674
  	 * 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);
287980e49   Arnd Bergmann   remove lots of IS...
675
  	if (irq_base < 0) {
78518ffa0   Benoit Cousson   mfd: Move twl-cor...
676
677
678
679
680
681
682
683
684
685
686
  		dev_err(dev, "Fail to allocate IRQ descs
  ");
  		return irq_base;
  	}
  
  	irq_domain_add_legacy(node, nr_irqs, irq_base, 0,
  			      &irq_domain_simple_ops, NULL);
  
  	irq_end = irq_base + TWL4030_CORE_NR_IRQS;
  
  	/*
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
687
688
689
690
691
692
  	 * 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;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
693
  	twl4030_irq_base = irq_base;
cbcde05ec   Felipe Contreras   mfd: Trivial twl4...
694
  	/*
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
695
  	 * Install an irq handler for each of the SIH modules;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
696
697
698
699
  	 * clone dummy irq_chip since PIH can't *do* anything
  	 */
  	twl4030_irq_chip = dummy_irq_chip;
  	twl4030_irq_chip.name = "twl4030";
fe2122138   Thomas Gleixner   mfd: twl4030: Fix...
700
  	twl4030_sih_irq_chip.irq_ack = dummy_irq_chip.irq_ack;
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
701
702
  
  	for (i = irq_base; i < irq_end; i++) {
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
703
704
  		irq_set_chip_and_handler(i, &twl4030_irq_chip,
  					 handle_simple_irq);
925e853c2   Felipe Balbi   mfd: Set twl4030-...
705
  		irq_set_nested_thread(i, 1);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
706
707
  		activate_irq(i);
  	}
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
708

ec1a07b34   Benoit Cousson   mfd: Replace twl-...
709
710
  	dev_info(dev, "%s (irq %d) chaining IRQs %d..%d
  ", "PIH",
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
711
  			irq_num, irq_base, irq_end);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
712
713
  
  	/* ... and the PWR_INT module ... */
f01b1f90b   Benoit Cousson   mfd: Make twl4030...
714
  	status = twl4030_sih_setup(dev, TWL4030_MODULE_INT, irq_end);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
715
  	if (status < 0) {
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
716
717
  		dev_err(dev, "sih_setup PWR INT --> %d
  ", status);
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
718
719
720
721
  		goto fail;
  	}
  
  	/* install an irq handler to demultiplex the TWL4030 interrupt */
286f8f3cd   NeilBrown   mfd: Base interru...
722
723
724
  	status = request_threaded_irq(irq_num, NULL, handle_twl4030_pih,
  				      IRQF_ONESHOT,
  				      "TWL4030-PIH", NULL);
1cef8e410   Russell King   mfd: twl4030 irq ...
725
  	if (status < 0) {
ec1a07b34   Benoit Cousson   mfd: Replace twl-...
726
727
  		dev_err(dev, "could not claim irq%d: %d
  ", irq_num, status);
1cef8e410   Russell King   mfd: twl4030 irq ...
728
729
  		goto fail_rqirq;
  	}
5a2f1b5fa   NeilBrown   mfd: enable wakeu...
730
  	enable_irq_wake(irq_num);
1cef8e410   Russell King   mfd: twl4030 irq ...
731

78518ffa0   Benoit Cousson   mfd: Move twl-cor...
732
  	return irq_base;
1cef8e410   Russell King   mfd: twl4030 irq ...
733
734
  fail_rqirq:
  	/* clean up twl4030_sih_setup */
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
735
  fail:
925e853c2   Felipe Balbi   mfd: Set twl4030-...
736
737
  	for (i = irq_base; i < irq_end; i++) {
  		irq_set_nested_thread(i, 0);
d5bb12216   Thomas Gleixner   mfd: Cleanup irq ...
738
  		irq_set_chip_and_handler(i, NULL, NULL);
925e853c2   Felipe Balbi   mfd: Set twl4030-...
739
  	}
2f2a7d5ef   Felipe Balbi   mfd: Drop twl4030...
740

a30d46c04   David Brownell   mfd: twl4030 IRQ ...
741
742
  	return status;
  }
e8deb28ca   Balaji T K   mfd: Add support ...
743
  int twl4030_exit_irq(void)
a30d46c04   David Brownell   mfd: twl4030 IRQ ...
744
745
746
747
748
749
750
751
752
  {
  	/* FIXME undo twl_init_irq() */
  	if (twl4030_irq_base) {
  		pr_err("twl4030: can't yet clean up IRQs?
  ");
  		return -ENOSYS;
  	}
  	return 0;
  }
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
753

e8deb28ca   Balaji T K   mfd: Add support ...
754
  int twl4030_init_chip_irq(const char *chip)
1920a61e2   Ilkka Koskinen   mfd: Initial supp...
755
756
757
758
759
760
761
762
763
764
765
  {
  	if (!strcmp(chip, "twl5031")) {
  		sih_modules = sih_modules_twl5031;
  		nr_sih_modules = ARRAY_SIZE(sih_modules_twl5031);
  	} else {
  		sih_modules = sih_modules_twl4030;
  		nr_sih_modules = ARRAY_SIZE(sih_modules_twl4030);
  	}
  
  	return 0;
  }