aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTero Kristo <t-kristo@ti.com>2011-12-16 16:36:58 -0500
committerPaul Walmsley <paul@pwsan.com>2011-12-16 16:36:58 -0500
commit0a84a91c37ada296ffe7147e73af99b5654628ec (patch)
tree1f7980f622af5fff2335e8282321fdb909c48753
parent26c98c561c02f3c08fd6182d16de0c2857d0644c (diff)
ARM: OMAP: PRCM: add support for chain interrupt handler
Introduce a chained interrupt handler mechanism for the PRCM interrupt, so that individual PRCM event can cleanly be handled by handlers in separate drivers. We do this by introducing PRCM event names, which are then matched to the particular PRCM interrupt bit depending on the specific OMAP SoC being used. PRCM interrupts have two priority levels, high or normal. High priority is needed for IO event handling, so that we can be sure that IO events are processed before other events. This reduces latency for IO event customers and also prevents incorrect ack sequence on OMAP3. Signed-off-by: Tero Kristo <t-kristo@ti.com> Cc: Paul Walmsley <paul@pwsan.com> Cc: Kevin Hilman <khilman@ti.com> Cc: Avinash.H.M <avinashhm@ti.com> Cc: Benoit Cousson <b-cousson@ti.com> Cc: Tony Lindgren <tony@atomide.com> Cc: Govindraj.R <govindraj.raja@ti.com> Tested-by: Kevin Hilman <khilman@ti.com> Reviewed-by: Kevin Hilman <khilman@ti.com> [paul@pwsan.com: drop some dead code; use SoC-specific pending IRQ detection; move code to prm_common.c; add lots of documentation; remove saved_mask; add OCP barrier on ISR exit; improved error handling; split out per-SoC initialization to a separate patch] Signed-off-by: Paul Walmsley <paul@pwsan.com>
-rw-r--r--arch/arm/mach-omap2/Makefile5
-rw-r--r--arch/arm/mach-omap2/prcm-common.h63
-rw-r--r--arch/arm/mach-omap2/prm_common.c277
3 files changed, 342 insertions, 3 deletions
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index b009f17dee56..2e1ab2252e15 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -14,7 +14,7 @@ clock-common = clock.o clock_common_data.o \
14 14
15obj-$(CONFIG_ARCH_OMAP2) += $(omap-2-3-common) $(hwmod-common) 15obj-$(CONFIG_ARCH_OMAP2) += $(omap-2-3-common) $(hwmod-common)
16obj-$(CONFIG_ARCH_OMAP3) += $(omap-2-3-common) $(hwmod-common) 16obj-$(CONFIG_ARCH_OMAP3) += $(omap-2-3-common) $(hwmod-common)
17obj-$(CONFIG_ARCH_OMAP4) += prm44xx.o $(hwmod-common) 17obj-$(CONFIG_ARCH_OMAP4) += $(hwmod-common)
18 18
19obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o 19obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o
20 20
@@ -77,6 +77,7 @@ endif
77endif 77endif
78 78
79# PRCM 79# PRCM
80obj-y += prm_common.o
80obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o 81obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o
81obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \ 82obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \
82 vc3xxx_data.o vp3xxx_data.o 83 vc3xxx_data.o vp3xxx_data.o
@@ -86,7 +87,7 @@ obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \
86obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \ 87obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \
87 cm44xx.o prcm_mpu44xx.o \ 88 cm44xx.o prcm_mpu44xx.o \
88 prminst44xx.o vc44xx_data.o \ 89 prminst44xx.o vc44xx_data.o \
89 vp44xx_data.o 90 vp44xx_data.o prm44xx.o
90 91
91# OMAP voltage domains 92# OMAP voltage domains
92voltagedomain-common := voltage.o vc.o vp.o 93voltagedomain-common := voltage.o vc.o vp.o
diff --git a/arch/arm/mach-omap2/prcm-common.h b/arch/arm/mach-omap2/prcm-common.h
index 0363dcb0ef93..76db3795e68f 100644
--- a/arch/arm/mach-omap2/prcm-common.h
+++ b/arch/arm/mach-omap2/prcm-common.h
@@ -4,7 +4,7 @@
4/* 4/*
5 * OMAP2/3 PRCM base and module definitions 5 * OMAP2/3 PRCM base and module definitions
6 * 6 *
7 * Copyright (C) 2007-2009 Texas Instruments, Inc. 7 * Copyright (C) 2007-2009, 2011 Texas Instruments, Inc.
8 * Copyright (C) 2007-2009 Nokia Corporation 8 * Copyright (C) 2007-2009 Nokia Corporation
9 * 9 *
10 * Written by Paul Walmsley 10 * Written by Paul Walmsley
@@ -408,6 +408,67 @@
408extern void __iomem *prm_base; 408extern void __iomem *prm_base;
409extern void __iomem *cm_base; 409extern void __iomem *cm_base;
410extern void __iomem *cm2_base; 410extern void __iomem *cm2_base;
411
412/**
413 * struct omap_prcm_irq - describes a PRCM interrupt bit
414 * @name: a short name describing the interrupt type, e.g. "wkup" or "io"
415 * @offset: the bit shift of the interrupt inside the IRQ{ENABLE,STATUS} regs
416 * @priority: should this interrupt be handled before @priority=false IRQs?
417 *
418 * Describes interrupt bits inside the PRM_IRQ{ENABLE,STATUS}_MPU* registers.
419 * On systems with multiple PRM MPU IRQ registers, the bitfields read from
420 * the registers are concatenated, so @offset could be > 31 on these systems -
421 * see omap_prm_irq_handler() for more details. I/O ring interrupts should
422 * have @priority set to true.
423 */
424struct omap_prcm_irq {
425 const char *name;
426 unsigned int offset;
427 bool priority;
428};
429
430/**
431 * struct omap_prcm_irq_setup - PRCM interrupt controller details
432 * @ack: PRM register offset for the first PRM_IRQSTATUS_MPU register
433 * @mask: PRM register offset for the first PRM_IRQENABLE_MPU register
434 * @nr_regs: number of PRM_IRQ{STATUS,ENABLE}_MPU* registers
435 * @nr_irqs: number of entries in the @irqs array
436 * @irqs: ptr to an array of PRCM interrupt bits (see @nr_irqs)
437 * @irq: MPU IRQ asserted when a PRCM interrupt arrives
438 * @read_pending_irqs: fn ptr to determine if any PRCM IRQs are pending
439 * @ocp_barrier: fn ptr to force buffered PRM writes to complete
440 * @priority_mask: 1 bit per IRQ, set to 1 if omap_prcm_irq.priority = true
441 * @base_irq: base dynamic IRQ number, returned from irq_alloc_descs() in init
442 *
443 * @priority_mask and @base_irq are populated dynamically during
444 * omap_prcm_register_chain_handler() - these fields are not to be
445 * specified in static initializers.
446 */
447struct omap_prcm_irq_setup {
448 u16 ack;
449 u16 mask;
450 u8 nr_regs;
451 u8 nr_irqs;
452 const struct omap_prcm_irq *irqs;
453 int irq;
454 void (*read_pending_irqs)(unsigned long *events);
455 void (*ocp_barrier)(void);
456 u32 *priority_mask;
457 int base_irq;
458};
459
460/* OMAP_PRCM_IRQ: convenience macro for creating struct omap_prcm_irq records */
461#define OMAP_PRCM_IRQ(_name, _offset, _priority) { \
462 .name = _name, \
463 .offset = _offset, \
464 .priority = _priority \
465 }
466
467extern void omap_prcm_irq_cleanup(void);
468extern int omap_prcm_register_chain_handler(
469 struct omap_prcm_irq_setup *irq_setup);
470extern int omap_prcm_event_to_irq(const char *event);
471
411# endif 472# endif
412 473
413#endif 474#endif
diff --git a/arch/arm/mach-omap2/prm_common.c b/arch/arm/mach-omap2/prm_common.c
new file mode 100644
index 000000000000..5694be56a947
--- /dev/null
+++ b/arch/arm/mach-omap2/prm_common.c
@@ -0,0 +1,277 @@
1/*
2 * OMAP2+ common Power & Reset Management (PRM) IP block functions
3 *
4 * Copyright (C) 2011 Texas Instruments, Inc.
5 * Tero Kristo <t-kristo@ti.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 *
12 * For historical purposes, the API used to configure the PRM
13 * interrupt handler refers to it as the "PRCM interrupt." The
14 * underlying registers are located in the PRM on OMAP3/4.
15 *
16 * XXX This code should eventually be moved to a PRM driver.
17 */
18
19#include <linux/kernel.h>
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/io.h>
23#include <linux/irq.h>
24#include <linux/interrupt.h>
25#include <linux/slab.h>
26
27#include <mach/system.h>
28#include <plat/common.h>
29#include <plat/prcm.h>
30#include <plat/irqs.h>
31
32#include "prm2xxx_3xxx.h"
33#include "prm44xx.h"
34
35/*
36 * OMAP_PRCM_MAX_NR_PENDING_REG: maximum number of PRM_IRQ*_MPU regs
37 * XXX this is technically not needed, since
38 * omap_prcm_register_chain_handler() could allocate this based on the
39 * actual amount of memory needed for the SoC
40 */
41#define OMAP_PRCM_MAX_NR_PENDING_REG 2
42
43/*
44 * prcm_irq_chips: an array of all of the "generic IRQ chips" in use
45 * by the PRCM interrupt handler code. There will be one 'chip' per
46 * PRM_{IRQSTATUS,IRQENABLE}_MPU register pair. (So OMAP3 will have
47 * one "chip" and OMAP4 will have two.)
48 */
49static struct irq_chip_generic **prcm_irq_chips;
50
51/*
52 * prcm_irq_setup: the PRCM IRQ parameters for the hardware the code
53 * is currently running on. Defined and passed by initialization code
54 * that calls omap_prcm_register_chain_handler().
55 */
56static struct omap_prcm_irq_setup *prcm_irq_setup;
57
58/* Private functions */
59
60/*
61 * Move priority events from events to priority_events array
62 */
63static void omap_prcm_events_filter_priority(unsigned long *events,
64 unsigned long *priority_events)
65{
66 int i;
67
68 for (i = 0; i < prcm_irq_setup->nr_regs; i++) {
69 priority_events[i] =
70 events[i] & prcm_irq_setup->priority_mask[i];
71 events[i] ^= priority_events[i];
72 }
73}
74
75/*
76 * PRCM Interrupt Handler
77 *
78 * This is a common handler for the OMAP PRCM interrupts. Pending
79 * interrupts are detected by a call to prcm_pending_events and
80 * dispatched accordingly. Clearing of the wakeup events should be
81 * done by the SoC specific individual handlers.
82 */
83static void omap_prcm_irq_handler(unsigned int irq, struct irq_desc *desc)
84{
85 unsigned long pending[OMAP_PRCM_MAX_NR_PENDING_REG];
86 unsigned long priority_pending[OMAP_PRCM_MAX_NR_PENDING_REG];
87 struct irq_chip *chip = irq_desc_get_chip(desc);
88 unsigned int virtirq;
89 int nr_irqs = prcm_irq_setup->nr_regs * 32;
90
91 /*
92 * Loop until all pending irqs are handled, since
93 * generic_handle_irq() can cause new irqs to come
94 */
95 while (1) {
96 prcm_irq_setup->read_pending_irqs(pending);
97
98 /* No bit set, then all IRQs are handled */
99 if (find_first_bit(pending, nr_irqs) >= nr_irqs)
100 break;
101
102 omap_prcm_events_filter_priority(pending, priority_pending);
103
104 /*
105 * Loop on all currently pending irqs so that new irqs
106 * cannot starve previously pending irqs
107 */
108
109 /* Serve priority events first */
110 for_each_set_bit(virtirq, priority_pending, nr_irqs)
111 generic_handle_irq(prcm_irq_setup->base_irq + virtirq);
112
113 /* Serve normal events next */
114 for_each_set_bit(virtirq, pending, nr_irqs)
115 generic_handle_irq(prcm_irq_setup->base_irq + virtirq);
116 }
117 if (chip->irq_ack)
118 chip->irq_ack(&desc->irq_data);
119 if (chip->irq_eoi)
120 chip->irq_eoi(&desc->irq_data);
121 chip->irq_unmask(&desc->irq_data);
122
123 prcm_irq_setup->ocp_barrier(); /* avoid spurious IRQs */
124}
125
126/* Public functions */
127
128/**
129 * omap_prcm_event_to_irq - given a PRCM event name, returns the
130 * corresponding IRQ on which the handler should be registered
131 * @name: name of the PRCM interrupt bit to look up - see struct omap_prcm_irq
132 *
133 * Returns the Linux internal IRQ ID corresponding to @name upon success,
134 * or -ENOENT upon failure.
135 */
136int omap_prcm_event_to_irq(const char *name)
137{
138 int i;
139
140 if (!prcm_irq_setup || !name)
141 return -ENOENT;
142
143 for (i = 0; i < prcm_irq_setup->nr_irqs; i++)
144 if (!strcmp(prcm_irq_setup->irqs[i].name, name))
145 return prcm_irq_setup->base_irq +
146 prcm_irq_setup->irqs[i].offset;
147
148 return -ENOENT;
149}
150
151/**
152 * omap_prcm_irq_cleanup - reverses memory allocated and other steps
153 * done by omap_prcm_register_chain_handler()
154 *
155 * No return value.
156 */
157void omap_prcm_irq_cleanup(void)
158{
159 int i;
160
161 if (!prcm_irq_setup) {
162 pr_err("PRCM: IRQ handler not initialized; cannot cleanup\n");
163 return;
164 }
165
166 if (prcm_irq_chips) {
167 for (i = 0; i < prcm_irq_setup->nr_regs; i++) {
168 if (prcm_irq_chips[i])
169 irq_remove_generic_chip(prcm_irq_chips[i],
170 0xffffffff, 0, 0);
171 prcm_irq_chips[i] = NULL;
172 }
173 kfree(prcm_irq_chips);
174 prcm_irq_chips = NULL;
175 }
176
177 kfree(prcm_irq_setup->priority_mask);
178 prcm_irq_setup->priority_mask = NULL;
179
180 irq_set_chained_handler(prcm_irq_setup->irq, NULL);
181
182 if (prcm_irq_setup->base_irq > 0)
183 irq_free_descs(prcm_irq_setup->base_irq,
184 prcm_irq_setup->nr_regs * 32);
185 prcm_irq_setup->base_irq = 0;
186}
187
188/**
189 * omap_prcm_register_chain_handler - initializes the prcm chained interrupt
190 * handler based on provided parameters
191 * @irq_setup: hardware data about the underlying PRM/PRCM
192 *
193 * Set up the PRCM chained interrupt handler on the PRCM IRQ. Sets up
194 * one generic IRQ chip per PRM interrupt status/enable register pair.
195 * Returns 0 upon success, -EINVAL if called twice or if invalid
196 * arguments are passed, or -ENOMEM on any other error.
197 */
198int omap_prcm_register_chain_handler(struct omap_prcm_irq_setup *irq_setup)
199{
200 int nr_regs = irq_setup->nr_regs;
201 u32 mask[OMAP_PRCM_MAX_NR_PENDING_REG];
202 int offset, i;
203 struct irq_chip_generic *gc;
204 struct irq_chip_type *ct;
205
206 if (!irq_setup)
207 return -EINVAL;
208
209 if (prcm_irq_setup) {
210 pr_err("PRCM: already initialized; won't reinitialize\n");
211 return -EINVAL;
212 }
213
214 if (nr_regs > OMAP_PRCM_MAX_NR_PENDING_REG) {
215 pr_err("PRCM: nr_regs too large\n");
216 return -EINVAL;
217 }
218
219 prcm_irq_setup = irq_setup;
220
221 prcm_irq_chips = kzalloc(sizeof(void *) * nr_regs, GFP_KERNEL);
222 prcm_irq_setup->priority_mask = kzalloc(sizeof(u32) * nr_regs,
223 GFP_KERNEL);
224
225 if (!prcm_irq_chips || !prcm_irq_setup->priority_mask) {
226 pr_err("PRCM: kzalloc failed\n");
227 goto err;
228 }
229
230 memset(mask, 0, sizeof(mask));
231
232 for (i = 0; i < irq_setup->nr_irqs; i++) {
233 offset = irq_setup->irqs[i].offset;
234 mask[offset >> 5] |= 1 << (offset & 0x1f);
235 if (irq_setup->irqs[i].priority)
236 irq_setup->priority_mask[offset >> 5] |=
237 1 << (offset & 0x1f);
238 }
239
240 irq_set_chained_handler(irq_setup->irq, omap_prcm_irq_handler);
241
242 irq_setup->base_irq = irq_alloc_descs(-1, 0, irq_setup->nr_regs * 32,
243 0);
244
245 if (irq_setup->base_irq < 0) {
246 pr_err("PRCM: failed to allocate irq descs: %d\n",
247 irq_setup->base_irq);
248 goto err;
249 }
250
251 for (i = 0; i <= irq_setup->nr_regs; i++) {
252 gc = irq_alloc_generic_chip("PRCM", 1,
253 irq_setup->base_irq + i * 32, prm_base,
254 handle_level_irq);
255
256 if (!gc) {
257 pr_err("PRCM: failed to allocate generic chip\n");
258 goto err;
259 }
260 ct = gc->chip_types;
261 ct->chip.irq_ack = irq_gc_ack_set_bit;
262 ct->chip.irq_mask = irq_gc_mask_clr_bit;
263 ct->chip.irq_unmask = irq_gc_mask_set_bit;
264
265 ct->regs.ack = irq_setup->ack + i * 4;
266 ct->regs.mask = irq_setup->mask + i * 4;
267
268 irq_setup_generic_chip(gc, mask[i], 0, IRQ_NOREQUEST, 0);
269 prcm_irq_chips[i] = gc;
270 }
271
272 return 0;
273
274err:
275 omap_prcm_irq_cleanup();
276 return -ENOMEM;
277}