summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-omap2
diff options
context:
space:
mode:
authorTony Lindgren <tony@atomide.com>2011-12-16 14:00:23 -0800
committerTony Lindgren <tony@atomide.com>2011-12-16 14:00:23 -0800
commit9d297f5ee1b92a84e2cd6c547c3ac1f893128359 (patch)
treecff90b5421967f17d11a7611a9c5f0fca0be4d67 /arch/arm/mach-omap2
parentaacf094128759cfb29a3ce88f92d08b79b74a4e8 (diff)
parent2f31b51659c2d8315ea2888ba5b93076febe672b (diff)
downloadlinux-9d297f5ee1b92a84e2cd6c547c3ac1f893128359.tar.gz
linux-9d297f5ee1b92a84e2cd6c547c3ac1f893128359.tar.bz2
linux-9d297f5ee1b92a84e2cd6c547c3ac1f893128359.zip
Merge branch 'tk_prm_chain_handler_devel_3.3' of git://git.pwsan.com/linux-2.6 into prcm
Conflicts: arch/arm/mach-omap2/Makefile
Diffstat (limited to 'arch/arm/mach-omap2')
-rw-r--r--arch/arm/mach-omap2/Makefile3
-rw-r--r--arch/arm/mach-omap2/mux.c89
-rw-r--r--arch/arm/mach-omap2/omap_hwmod.c102
-rw-r--r--arch/arm/mach-omap2/pm34xx.c115
-rw-r--r--arch/arm/mach-omap2/prcm-common.h75
-rw-r--r--arch/arm/mach-omap2/prm2xxx_3xxx.c97
-rw-r--r--arch/arm/mach-omap2/prm2xxx_3xxx.h9
-rw-r--r--arch/arm/mach-omap2/prm44xx.c116
-rw-r--r--arch/arm/mach-omap2/prm44xx.h8
-rw-r--r--arch/arm/mach-omap2/prm_common.c320
10 files changed, 856 insertions, 78 deletions
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index 9a6da52661ce..63a5efad70e8 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -81,6 +81,7 @@ endif
endif
# PRCM
+obj-y += prm_common.o
obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o
obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \
vc3xxx_data.o vp3xxx_data.o
@@ -90,7 +91,7 @@ obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \
obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \
cm44xx.o prcm_mpu44xx.o \
prminst44xx.o vc44xx_data.o \
- vp44xx_data.o
+ vp44xx_data.o prm44xx.o
# OMAP voltage domains
voltagedomain-common := voltage.o vc.o vp.o
diff --git a/arch/arm/mach-omap2/mux.c b/arch/arm/mach-omap2/mux.c
index 655e9480eb98..e1cc75d1a57a 100644
--- a/arch/arm/mach-omap2/mux.c
+++ b/arch/arm/mach-omap2/mux.c
@@ -32,6 +32,8 @@
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
#include <asm/system.h>
@@ -39,6 +41,7 @@
#include "control.h"
#include "mux.h"
+#include "prm.h"
#define OMAP_MUX_BASE_OFFSET 0x30 /* Offset from CTRL_BASE */
#define OMAP_MUX_BASE_SZ 0x5ca
@@ -306,7 +309,8 @@ omap_hwmod_mux_init(struct omap_device_pad *bpads, int nr_pads)
pad->idle = bpad->idle;
pad->off = bpad->off;
- if (pad->flags & OMAP_DEVICE_PAD_REMUX)
+ if (pad->flags &
+ (OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP))
nr_pads_dynamic++;
pr_debug("%s: Initialized %s\n", __func__, pad->name);
@@ -331,7 +335,8 @@ omap_hwmod_mux_init(struct omap_device_pad *bpads, int nr_pads)
for (i = 0; i < hmux->nr_pads; i++) {
struct omap_device_pad *pad = &hmux->pads[i];
- if (pad->flags & OMAP_DEVICE_PAD_REMUX) {
+ if (pad->flags &
+ (OMAP_DEVICE_PAD_REMUX | OMAP_DEVICE_PAD_WAKEUP)) {
pr_debug("%s: pad %s tagged dynamic\n",
__func__, pad->name);
hmux->pads_dynamic[nr_pads_dynamic] = pad;
@@ -351,6 +356,78 @@ err1:
return NULL;
}
+/**
+ * omap_hwmod_mux_scan_wakeups - omap hwmod scan wakeup pads
+ * @hmux: Pads for a hwmod
+ * @mpu_irqs: MPU irq array for a hwmod
+ *
+ * Scans the wakeup status of pads for a single hwmod. If an irq
+ * array is defined for this mux, the parser will call the registered
+ * ISRs for corresponding pads, otherwise the parser will stop at the
+ * first wakeup active pad and return. Returns true if there is a
+ * pending and non-served wakeup event for the mux, otherwise false.
+ */
+static bool omap_hwmod_mux_scan_wakeups(struct omap_hwmod_mux_info *hmux,
+ struct omap_hwmod_irq_info *mpu_irqs)
+{
+ int i, irq;
+ unsigned int val;
+ u32 handled_irqs = 0;
+
+ for (i = 0; i < hmux->nr_pads_dynamic; i++) {
+ struct omap_device_pad *pad = hmux->pads_dynamic[i];
+
+ if (!(pad->flags & OMAP_DEVICE_PAD_WAKEUP) ||
+ !(pad->idle & OMAP_WAKEUP_EN))
+ continue;
+
+ val = omap_mux_read(pad->partition, pad->mux->reg_offset);
+ if (!(val & OMAP_WAKEUP_EVENT))
+ continue;
+
+ if (!hmux->irqs)
+ return true;
+
+ irq = hmux->irqs[i];
+ /* make sure we only handle each irq once */
+ if (handled_irqs & 1 << irq)
+ continue;
+
+ handled_irqs |= 1 << irq;
+
+ generic_handle_irq(mpu_irqs[irq].irq);
+ }
+
+ return false;
+}
+
+/**
+ * _omap_hwmod_mux_handle_irq - Process wakeup events for a single hwmod
+ *
+ * Checks a single hwmod for every wakeup capable pad to see if there is an
+ * active wakeup event. If this is the case, call the corresponding ISR.
+ */
+static int _omap_hwmod_mux_handle_irq(struct omap_hwmod *oh, void *data)
+{
+ if (!oh->mux || !oh->mux->enabled)
+ return 0;
+ if (omap_hwmod_mux_scan_wakeups(oh->mux, oh->mpu_irqs))
+ generic_handle_irq(oh->mpu_irqs[0].irq);
+ return 0;
+}
+
+/**
+ * omap_hwmod_mux_handle_irq - Process pad wakeup irqs.
+ *
+ * Calls a function for each registered omap_hwmod to check
+ * pad wakeup statuses.
+ */
+static irqreturn_t omap_hwmod_mux_handle_irq(int irq, void *unused)
+{
+ omap_hwmod_for_each(_omap_hwmod_mux_handle_irq, NULL);
+ return IRQ_HANDLED;
+}
+
/* Assumes the calling function takes care of locking */
void omap_hwmod_mux(struct omap_hwmod_mux_info *hmux, u8 state)
{
@@ -715,6 +792,7 @@ static void __init omap_mux_free_names(struct omap_mux *m)
static int __init omap_mux_late_init(void)
{
struct omap_mux_partition *partition;
+ int ret;
list_for_each_entry(partition, &mux_partitions, node) {
struct omap_mux_entry *e, *tmp;
@@ -735,6 +813,13 @@ static int __init omap_mux_late_init(void)
}
}
+ ret = request_irq(omap_prcm_event_to_irq("io"),
+ omap_hwmod_mux_handle_irq, IRQF_SHARED | IRQF_NO_SUSPEND,
+ "hwmod_io", omap_mux_late_init);
+
+ if (ret)
+ pr_warning("mux: Failed to setup hwmod io irq %d\n", ret);
+
omap_mux_dbg_init();
return 0;
diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c
index f673f808725f..ee9416bcc3e6 100644
--- a/arch/arm/mach-omap2/omap_hwmod.c
+++ b/arch/arm/mach-omap2/omap_hwmod.c
@@ -136,6 +136,7 @@
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
+#include <linux/slab.h>
#include "common.h"
#include <plat/cpu.h>
@@ -381,6 +382,51 @@ static int _set_module_autoidle(struct omap_hwmod *oh, u8 autoidle,
}
/**
+ * _set_idle_ioring_wakeup - enable/disable IO pad wakeup on hwmod idle for mux
+ * @oh: struct omap_hwmod *
+ * @set_wake: bool value indicating to set (true) or clear (false) wakeup enable
+ *
+ * Set or clear the I/O pad wakeup flag in the mux entries for the
+ * hwmod @oh. This function changes the @oh->mux->pads_dynamic array
+ * in memory. If the hwmod is currently idled, and the new idle
+ * values don't match the previous ones, this function will also
+ * update the SCM PADCTRL registers. Otherwise, if the hwmod is not
+ * currently idled, this function won't touch the hardware: the new
+ * mux settings are written to the SCM PADCTRL registers when the
+ * hwmod is idled. No return value.
+ */
+static void _set_idle_ioring_wakeup(struct omap_hwmod *oh, bool set_wake)
+{
+ struct omap_device_pad *pad;
+ bool change = false;
+ u16 prev_idle;
+ int j;
+
+ if (!oh->mux || !oh->mux->enabled)
+ return;
+
+ for (j = 0; j < oh->mux->nr_pads_dynamic; j++) {
+ pad = oh->mux->pads_dynamic[j];
+
+ if (!(pad->flags & OMAP_DEVICE_PAD_WAKEUP))
+ continue;
+
+ prev_idle = pad->idle;
+
+ if (set_wake)
+ pad->idle |= OMAP_WAKEUP_EN;
+ else
+ pad->idle &= ~OMAP_WAKEUP_EN;
+
+ if (prev_idle != pad->idle)
+ change = true;
+ }
+
+ if (change && oh->_state == _HWMOD_STATE_IDLE)
+ omap_hwmod_mux(oh->mux, _HWMOD_STATE_IDLE);
+}
+
+/**
* _enable_wakeup: set OCP_SYSCONFIG.ENAWAKEUP bit in the hardware
* @oh: struct omap_hwmod *
*
@@ -2437,6 +2483,7 @@ int omap_hwmod_enable_wakeup(struct omap_hwmod *oh)
v = oh->_sysc_cache;
_enable_wakeup(oh, &v);
_write_sysconfig(v, oh);
+ _set_idle_ioring_wakeup(oh, true);
spin_unlock_irqrestore(&oh->_lock, flags);
return 0;
@@ -2467,6 +2514,7 @@ int omap_hwmod_disable_wakeup(struct omap_hwmod *oh)
v = oh->_sysc_cache;
_disable_wakeup(oh, &v);
_write_sysconfig(v, oh);
+ _set_idle_ioring_wakeup(oh, false);
spin_unlock_irqrestore(&oh->_lock, flags);
return 0;
@@ -2683,3 +2731,57 @@ int omap_hwmod_no_setup_reset(struct omap_hwmod *oh)
return 0;
}
+
+/**
+ * omap_hwmod_pad_route_irq - route an I/O pad wakeup to a particular MPU IRQ
+ * @oh: struct omap_hwmod * containing hwmod mux entries
+ * @pad_idx: array index in oh->mux of the hwmod mux entry to route wakeup
+ * @irq_idx: the hwmod mpu_irqs array index of the IRQ to trigger on wakeup
+ *
+ * When an I/O pad wakeup arrives for the dynamic or wakeup hwmod mux
+ * entry number @pad_idx for the hwmod @oh, trigger the interrupt
+ * service routine for the hwmod's mpu_irqs array index @irq_idx. If
+ * this function is not called for a given pad_idx, then the ISR
+ * associated with @oh's first MPU IRQ will be triggered when an I/O
+ * pad wakeup occurs on that pad. Note that @pad_idx is the index of
+ * the _dynamic or wakeup_ entry: if there are other entries not
+ * marked with OMAP_DEVICE_PAD_WAKEUP or OMAP_DEVICE_PAD_REMUX, these
+ * entries are NOT COUNTED in the dynamic pad index. This function
+ * must be called separately for each pad that requires its interrupt
+ * to be re-routed this way. Returns -EINVAL if there is an argument
+ * problem or if @oh does not have hwmod mux entries or MPU IRQs;
+ * returns -ENOMEM if memory cannot be allocated; or 0 upon success.
+ *
+ * XXX This function interface is fragile. Rather than using array
+ * indexes, which are subject to unpredictable change, it should be
+ * using hwmod IRQ names, and some other stable key for the hwmod mux
+ * pad records.
+ */
+int omap_hwmod_pad_route_irq(struct omap_hwmod *oh, int pad_idx, int irq_idx)
+{
+ int nr_irqs;
+
+ might_sleep();
+
+ if (!oh || !oh->mux || !oh->mpu_irqs || pad_idx < 0 ||
+ pad_idx >= oh->mux->nr_pads_dynamic)
+ return -EINVAL;
+
+ /* Check the number of available mpu_irqs */
+ for (nr_irqs = 0; oh->mpu_irqs[nr_irqs].irq >= 0; nr_irqs++)
+ ;
+
+ if (irq_idx >= nr_irqs)
+ return -EINVAL;
+
+ if (!oh->mux->irqs) {
+ /* XXX What frees this? */
+ oh->mux->irqs = kzalloc(sizeof(int) * oh->mux->nr_pads_dynamic,
+ GFP_KERNEL);
+ if (!oh->mux->irqs)
+ return -ENOMEM;
+ }
+ oh->mux->irqs[pad_idx] = irq_idx;
+
+ return 0;
+}
diff --git a/arch/arm/mach-omap2/pm34xx.c b/arch/arm/mach-omap2/pm34xx.c
index fa637dfdda53..53b5b1a71e0e 100644
--- a/arch/arm/mach-omap2/pm34xx.c
+++ b/arch/arm/mach-omap2/pm34xx.c
@@ -195,7 +195,7 @@ static void omap3_save_secure_ram_context(void)
* that any peripheral wake-up events occurring while attempting to
* clear the PM_WKST_x are detected and cleared.
*/
-static int prcm_clear_mod_irqs(s16 module, u8 regs)
+static int prcm_clear_mod_irqs(s16 module, u8 regs, u32 ignore_bits)
{
u32 wkst, fclk, iclk, clken;
u16 wkst_off = (regs == 3) ? OMAP3430ES2_PM_WKST3 : PM_WKST1;
@@ -207,6 +207,7 @@ static int prcm_clear_mod_irqs(s16 module, u8 regs)
wkst = omap2_prm_read_mod_reg(module, wkst_off);
wkst &= omap2_prm_read_mod_reg(module, grpsel_off);
+ wkst &= ~ignore_bits;
if (wkst) {
iclk = omap2_cm_read_mod_reg(module, iclk_off);
fclk = omap2_cm_read_mod_reg(module, fclk_off);
@@ -222,6 +223,7 @@ static int prcm_clear_mod_irqs(s16 module, u8 regs)
omap2_cm_set_mod_reg_bits(clken, module, fclk_off);
omap2_prm_write_mod_reg(wkst, module, wkst_off);
wkst = omap2_prm_read_mod_reg(module, wkst_off);
+ wkst &= ~ignore_bits;
c++;
}
omap2_cm_write_mod_reg(iclk, module, iclk_off);
@@ -231,76 +233,35 @@ static int prcm_clear_mod_irqs(s16 module, u8 regs)
return c;
}
-static int _prcm_int_handle_wakeup(void)
+static irqreturn_t _prcm_int_handle_io(int irq, void *unused)
{
int c;
- c = prcm_clear_mod_irqs(WKUP_MOD, 1);
- c += prcm_clear_mod_irqs(CORE_MOD, 1);
- c += prcm_clear_mod_irqs(OMAP3430_PER_MOD, 1);
- if (omap_rev() > OMAP3430_REV_ES1_0) {
- c += prcm_clear_mod_irqs(CORE_MOD, 3);
- c += prcm_clear_mod_irqs(OMAP3430ES2_USBHOST_MOD, 1);
- }
+ c = prcm_clear_mod_irqs(WKUP_MOD, 1,
+ ~(OMAP3430_ST_IO_MASK | OMAP3430_ST_IO_CHAIN_MASK));
- return c;
+ return c ? IRQ_HANDLED : IRQ_NONE;
}
-/*
- * PRCM Interrupt Handler
- *
- * The PRM_IRQSTATUS_MPU register indicates if there are any pending
- * interrupts from the PRCM for the MPU. These bits must be cleared in
- * order to clear the PRCM interrupt. The PRCM interrupt handler is
- * implemented to simply clear the PRM_IRQSTATUS_MPU in order to clear
- * the PRCM interrupt. Please note that bit 0 of the PRM_IRQSTATUS_MPU
- * register indicates that a wake-up event is pending for the MPU and
- * this bit can only be cleared if the all the wake-up events latched
- * in the various PM_WKST_x registers have been cleared. The interrupt
- * handler is implemented using a do-while loop so that if a wake-up
- * event occurred during the processing of the prcm interrupt handler
- * (setting a bit in the corresponding PM_WKST_x register and thus
- * preventing us from clearing bit 0 of the PRM_IRQSTATUS_MPU register)
- * this would be handled.
- */
-static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id)
+static irqreturn_t _prcm_int_handle_wakeup(int irq, void *unused)
{
- u32 irqenable_mpu, irqstatus_mpu;
- int c = 0;
-
- irqenable_mpu = omap2_prm_read_mod_reg(OCP_MOD,
- OMAP3_PRM_IRQENABLE_MPU_OFFSET);
- irqstatus_mpu = omap2_prm_read_mod_reg(OCP_MOD,
- OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
- irqstatus_mpu &= irqenable_mpu;
-
- do {
- if (irqstatus_mpu & (OMAP3430_WKUP_ST_MASK |
- OMAP3430_IO_ST_MASK)) {
- c = _prcm_int_handle_wakeup();
-
- /*
- * Is the MPU PRCM interrupt handler racing with the
- * IVA2 PRCM interrupt handler ?
- */
- WARN(c == 0, "prcm: WARNING: PRCM indicated MPU wakeup "
- "but no wakeup sources are marked\n");
- } else {
- /* XXX we need to expand our PRCM interrupt handler */
- WARN(1, "prcm: WARNING: PRCM interrupt received, but "
- "no code to handle it (%08x)\n", irqstatus_mpu);
- }
-
- omap2_prm_write_mod_reg(irqstatus_mpu, OCP_MOD,
- OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
-
- irqstatus_mpu = omap2_prm_read_mod_reg(OCP_MOD,
- OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
- irqstatus_mpu &= irqenable_mpu;
+ int c;
- } while (irqstatus_mpu);
+ /*
+ * Clear all except ST_IO and ST_IO_CHAIN for wkup module,
+ * these are handled in a separate handler to avoid acking
+ * IO events before parsing in mux code
+ */
+ c = prcm_clear_mod_irqs(WKUP_MOD, 1,
+ OMAP3430_ST_IO_MASK | OMAP3430_ST_IO_CHAIN_MASK);
+ c += prcm_clear_mod_irqs(CORE_MOD, 1, 0);
+ c += prcm_clear_mod_irqs(OMAP3430_PER_MOD, 1, 0);
+ if (omap_rev() > OMAP3430_REV_ES1_0) {
+ c += prcm_clear_mod_irqs(CORE_MOD, 3, 0);
+ c += prcm_clear_mod_irqs(OMAP3430ES2_USBHOST_MOD, 1, 0);
+ }
- return IRQ_HANDLED;
+ return c ? IRQ_HANDLED : IRQ_NONE;
}
static void omap34xx_save_context(u32 *save)
@@ -581,6 +542,7 @@ static int omap3_pm_begin(suspend_state_t state)
disable_hlt();
suspend_state = state;
omap_uart_enable_irqs(0);
+ omap_prcm_irq_prepare();
return 0;
}
@@ -592,10 +554,16 @@ static void omap3_pm_end(void)
return;
}
+static void omap3_pm_finish(void)
+{
+ omap_prcm_irq_complete();
+}
+
static const struct platform_suspend_ops omap_pm_ops = {
.begin = omap3_pm_begin,
.end = omap3_pm_end,
.enter = omap3_pm_enter,
+ .finish = omap3_pm_finish,
.valid = suspend_valid_only_mem,
};
#endif /* CONFIG_SUSPEND */
@@ -701,10 +669,6 @@ static void __init prcm_setup_regs(void)
OMAP3430_GRPSEL_GPT1_MASK |
OMAP3430_GRPSEL_GPT12_MASK,
WKUP_MOD, OMAP3430_PM_MPUGRPSEL);
- /* For some reason IO doesn't generate wakeup event even if
- * it is selected to mpu wakeup goup */
- omap2_prm_write_mod_reg(OMAP3430_IO_EN_MASK | OMAP3430_WKUP_EN_MASK,
- OCP_MOD, OMAP3_PRM_IRQENABLE_MPU_OFFSET);
/* Enable PM_WKEN to support DSS LPR */
omap2_prm_write_mod_reg(OMAP3430_PM_WKEN_DSS_EN_DSS_MASK,
@@ -881,12 +845,21 @@ static int __init omap3_pm_init(void)
* supervised mode for powerdomains */
prcm_setup_regs();
- ret = request_irq(INT_34XX_PRCM_MPU_IRQ,
- (irq_handler_t)prcm_interrupt_handler,
- IRQF_DISABLED, "prcm", NULL);
+ ret = request_irq(omap_prcm_event_to_irq("wkup"),
+ _prcm_int_handle_wakeup, IRQF_NO_SUSPEND, "pm_wkup", NULL);
+
+ if (ret) {
+ pr_err("pm: Failed to request pm_wkup irq\n");
+ goto err1;
+ }
+
+ /* IO interrupt is shared with mux code */
+ ret = request_irq(omap_prcm_event_to_irq("io"),
+ _prcm_int_handle_io, IRQF_SHARED | IRQF_NO_SUSPEND, "pm_io",
+ omap3_pm_init);
+
if (ret) {
- printk(KERN_ERR "request_irq failed to register for 0x%x\n",
- INT_34XX_PRCM_MPU_IRQ);
+ pr_err("pm: Failed to request pm_io irq\n");
goto err1;
}
diff --git a/arch/arm/mach-omap2/prcm-common.h b/arch/arm/mach-omap2/prcm-common.h
index 0363dcb0ef93..0f69d8fc5628 100644
--- a/arch/arm/mach-omap2/prcm-common.h
+++ b/arch/arm/mach-omap2/prcm-common.h
@@ -4,7 +4,7 @@
/*
* OMAP2/3 PRCM base and module definitions
*
- * Copyright (C) 2007-2009 Texas Instruments, Inc.
+ * Copyright (C) 2007-2009, 2011 Texas Instruments, Inc.
* Copyright (C) 2007-2009 Nokia Corporation
*
* Written by Paul Walmsley
@@ -408,6 +408,79 @@
extern void __iomem *prm_base;
extern void __iomem *cm_base;
extern void __iomem *cm2_base;
+
+/**
+ * struct omap_prcm_irq - describes a PRCM interrupt bit
+ * @name: a short name describing the interrupt type, e.g. "wkup" or "io"
+ * @offset: the bit shift of the interrupt inside the IRQ{ENABLE,STATUS} regs
+ * @priority: should this interrupt be handled before @priority=false IRQs?
+ *
+ * Describes interrupt bits inside the PRM_IRQ{ENABLE,STATUS}_MPU* registers.
+ * On systems with multiple PRM MPU IRQ registers, the bitfields read from
+ * the registers are concatenated, so @offset could be > 31 on these systems -
+ * see omap_prm_irq_handler() for more details. I/O ring interrupts should
+ * have @priority set to true.
+ */
+struct omap_prcm_irq {
+ const char *name;
+ unsigned int offset;
+ bool priority;
+};
+
+/**
+ * struct omap_prcm_irq_setup - PRCM interrupt controller details
+ * @ack: PRM register offset for the first PRM_IRQSTATUS_MPU register
+ * @mask: PRM register offset for the first PRM_IRQENABLE_MPU register
+ * @nr_regs: number of PRM_IRQ{STATUS,ENABLE}_MPU* registers
+ * @nr_irqs: number of entries in the @irqs array
+ * @irqs: ptr to an array of PRCM interrupt bits (see @nr_irqs)
+ * @irq: MPU IRQ asserted when a PRCM interrupt arrives
+ * @read_pending_irqs: fn ptr to determine if any PRCM IRQs are pending
+ * @ocp_barrier: fn ptr to force buffered PRM writes to complete
+ * @save_and_clear_irqen: fn ptr to save and clear IRQENABLE regs
+ * @restore_irqen: fn ptr to save and clear IRQENABLE regs
+ * @saved_mask: IRQENABLE regs are saved here during suspend
+ * @priority_mask: 1 bit per IRQ, set to 1 if omap_prcm_irq.priority = true
+ * @base_irq: base dynamic IRQ number, returned from irq_alloc_descs() in init
+ * @suspended: set to true after Linux suspend code has called our ->prepare()
+ * @suspend_save_flag: set to true after IRQ masks have been saved and disabled
+ *
+ * @saved_mask, @priority_mask, @base_irq, @suspended, and
+ * @suspend_save_flag are populated dynamically, and are not to be
+ * specified in static initializers.
+ */
+struct omap_prcm_irq_setup {
+ u16 ack;
+ u16 mask;
+ u8 nr_regs;
+ u8 nr_irqs;
+ const struct omap_prcm_irq *irqs;
+ int irq;
+ void (*read_pending_irqs)(unsigned long *events);
+ void (*ocp_barrier)(void);
+ void (*save_and_clear_irqen)(u32 *saved_mask);
+ void (*restore_irqen)(u32 *saved_mask);
+ u32 *saved_mask;
+ u32 *priority_mask;
+ int base_irq;
+ bool suspended;
+ bool suspend_save_flag;
+};
+
+/* OMAP_PRCM_IRQ: convenience macro for creating struct omap_prcm_irq records */
+#define OMAP_PRCM_IRQ(_name, _offset, _priority) { \
+ .name = _name, \
+ .offset = _offset, \
+ .priority = _priority \
+ }
+
+extern void omap_prcm_irq_cleanup(void);
+extern int omap_prcm_register_chain_handler(
+ struct omap_prcm_irq_setup *irq_setup);
+extern int omap_prcm_event_to_irq(const char *event);
+extern void omap_prcm_irq_prepare(void);
+extern void omap_prcm_irq_complete(void);
+
# endif
#endif
diff --git a/arch/arm/mach-omap2/prm2xxx_3xxx.c b/arch/arm/mach-omap2/prm2xxx_3xxx.c
index 9a08ba397327..c1c4d86a79a8 100644
--- a/arch/arm/mach-omap2/prm2xxx_3xxx.c
+++ b/arch/arm/mach-omap2/prm2xxx_3xxx.c
@@ -1,7 +1,7 @@
/*
* OMAP2/3 PRM module functions
*
- * Copyright (C) 2010 Texas Instruments, Inc.
+ * Copyright (C) 2010-2011 Texas Instruments, Inc.
* Copyright (C) 2010 Nokia Corporation
* BenoƮt Cousson
* Paul Walmsley
@@ -27,6 +27,24 @@
#include "prm-regbits-24xx.h"
#include "prm-regbits-34xx.h"
+static const struct omap_prcm_irq omap3_prcm_irqs[] = {
+ OMAP_PRCM_IRQ("wkup", 0, 0),
+ OMAP_PRCM_IRQ("io", 9, 1),
+};
+
+static struct omap_prcm_irq_setup omap3_prcm_irq_setup = {
+ .ack = OMAP3_PRM_IRQSTATUS_MPU_OFFSET,
+ .mask = OMAP3_PRM_IRQENABLE_MPU_OFFSET,
+ .nr_regs = 1,
+ .irqs = omap3_prcm_irqs,
+ .nr_irqs = ARRAY_SIZE(omap3_prcm_irqs),
+ .irq = INT_34XX_PRCM_MPU_IRQ,
+ .read_pending_irqs = &omap3xxx_prm_read_pending_irqs,
+ .ocp_barrier = &omap3xxx_prm_ocp_barrier,
+ .save_and_clear_irqen = &omap3xxx_prm_save_and_clear_irqen,
+ .restore_irqen = &omap3xxx_prm_restore_irqen,
+};
+
u32 omap2_prm_read_mod_reg(s16 module, u16 idx)
{
return __raw_readl(prm_base + module + idx);
@@ -212,3 +230,80 @@ u32 omap3_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset)
{
return omap2_prm_rmw_mod_reg_bits(mask, bits, OMAP3430_GR_MOD, offset);
}
+
+/**
+ * omap3xxx_prm_read_pending_irqs - read pending PRM MPU IRQs into @events
+ * @events: ptr to a u32, preallocated by caller
+ *
+ * Read PRM_IRQSTATUS_MPU bits, AND'ed with the currently-enabled PRM
+ * MPU IRQs, and store the result into the u32 pointed to by @events.
+ * No return value.
+ */
+void omap3xxx_prm_read_pending_irqs(unsigned long *events)
+{
+ u32 mask, st;
+
+ /* XXX Can the mask read be avoided (e.g., can it come from RAM?) */
+ mask = omap2_prm_read_mod_reg(OCP_MOD, OMAP3_PRM_IRQENABLE_MPU_OFFSET);
+ st = omap2_prm_read_mod_reg(OCP_MOD, OMAP3_PRM_IRQSTATUS_MPU_OFFSET);
+
+ events[0] = mask & st;
+}
+
+/**
+ * omap3xxx_prm_ocp_barrier - force buffered MPU writes to the PRM to complete
+ *
+ * Force any buffered writes to the PRM IP block to complete. Needed
+ * by the PRM IRQ handler, which reads and writes directly to the IP
+ * block, to avoid race conditions after acknowledging or clearing IRQ
+ * bits. No return value.
+ */
+void omap3xxx_prm_ocp_barrier(void)
+{
+ omap2_prm_read_mod_reg(OCP_MOD, OMAP3_PRM_REVISION_OFFSET);
+}
+
+/**
+ * omap3xxx_prm_save_and_clear_irqen - save/clear PRM_IRQENABLE_MPU reg
+ * @saved_mask: ptr to a u32 array to save IRQENABLE bits
+ *
+ * Save the PRM_IRQENABLE_MPU register to @saved_mask. @saved_mask
+ * must be allocated by the caller. Intended to be used in the PRM
+ * interrupt handler suspend callback. The OCP barrier is needed to
+ * ensure the write to disable PRM interrupts reaches the PRM before
+ * returning; otherwise, spurious interrupts might occur. No return
+ * value.
+ */
+void omap3xxx_prm_save_and_clear_irqen(u32 *saved_mask)
+{
+ saved_mask[0] = omap2_prm_read_mod_reg(OCP_MOD,
+ OMAP3_PRM_IRQENABLE_MPU_OFFSET);
+ omap2_prm_write_mod_reg(0, OCP_MOD, OMAP3_PRM_IRQENABLE_MPU_OFFSET);
+
+ /* OCP barrier */
+ omap2_prm_read_mod_reg(OCP_MOD, OMAP3_PRM_REVISION_OFFSET);
+}
+
+/**
+ * omap3xxx_prm_restore_irqen - set PRM_IRQENABLE_MPU register from args
+ * @saved_mask: ptr to a u32 array of IRQENABLE bits saved previously
+ *
+ * Restore the PRM_IRQENABLE_MPU register from @saved_mask. Intended
+ * to be used in the PRM interrupt handler resume callback to restore
+ * values saved by omap3xxx_prm_save_and_clear_irqen(). No OCP
+ * barrier should be needed here; any pending PRM interrupts will fire
+ * once the writes reach the PRM. No return value.
+ */
+void omap3xxx_prm_restore_irqen(u32 *saved_mask)
+{
+ omap2_prm_write_mod_reg(saved_mask[0], OCP_MOD,
+ OMAP3_PRM_IRQENABLE_MPU_OFFSET);
+}
+
+static int __init omap3xxx_prcm_init(void)
+{
+ if (cpu_is_omap34xx())
+ return omap_prcm_register_chain_handler(&omap3_prcm_irq_setup);
+ return 0;
+}
+subsys_initcall(omap3xxx_prcm_init);
diff --git a/arch/arm/mach-omap2/prm2xxx_3xxx.h b/arch/arm/mach-omap2/prm2xxx_3xxx.h
index cef533df0861..70ac2a19dc5f 100644
--- a/arch/arm/mach-omap2/prm2xxx_3xxx.h
+++ b/arch/arm/mach-omap2/prm2xxx_3xxx.h
@@ -1,7 +1,7 @@
/*
* OMAP2/3 Power/Reset Management (PRM) register definitions
*
- * Copyright (C) 2007-2009 Texas Instruments, Inc.
+ * Copyright (C) 2007-2009, 2011 Texas Instruments, Inc.
* Copyright (C) 2008-2010 Nokia Corporation
* Paul Walmsley
*
@@ -314,6 +314,13 @@ void omap3_prm_vp_clear_txdone(u8 vp_id);
extern u32 omap3_prm_vcvp_read(u8 offset);
extern void omap3_prm_vcvp_write(u32 val, u8 offset);
extern u32 omap3_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset);
+
+/* PRM interrupt-related functions */
+extern void omap3xxx_prm_read_pending_irqs(unsigned long *events);
+extern void omap3xxx_prm_ocp_barrier(void);
+extern void omap3xxx_prm_save_and_clear_irqen(u32 *saved_mask);
+extern void omap3xxx_prm_restore_irqen(u32 *saved_mask);
+
#endif /* CONFIG_ARCH_OMAP4 */
#endif
diff --git a/arch/arm/mach-omap2/prm44xx.c b/arch/arm/mach-omap2/prm44xx.c
index dd885eecf22a..33dd655e6aab 100644
--- a/arch/arm/mach-omap2/prm44xx.c
+++ b/arch/arm/mach-omap2/prm44xx.c
@@ -27,6 +27,24 @@
#include "prcm44xx.h"
#include "prminst44xx.h"
+static const struct omap_prcm_irq omap4_prcm_irqs[] = {
+ OMAP_PRCM_IRQ("wkup", 0, 0),
+ OMAP_PRCM_IRQ("io", 9, 1),
+};
+
+static struct omap_prcm_irq_setup omap4_prcm_irq_setup = {
+ .ack = OMAP4_PRM_IRQSTATUS_MPU_OFFSET,
+ .mask = OMAP4_PRM_IRQENABLE_MPU_OFFSET,
+ .nr_regs = 2,
+ .irqs = omap4_prcm_irqs,
+ .nr_irqs = ARRAY_SIZE(omap4_prcm_irqs),
+ .irq = OMAP44XX_IRQ_PRCM,
+ .read_pending_irqs = &omap44xx_prm_read_pending_irqs,
+ .ocp_barrier = &omap44xx_prm_ocp_barrier,
+ .save_and_clear_irqen = &omap44xx_prm_save_and_clear_irqen,
+ .restore_irqen = &omap44xx_prm_restore_irqen,
+};
+
/* PRM low-level functions */
/* Read a register in a CM/PRM instance in the PRM module */
@@ -121,3 +139,101 @@ u32 omap4_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset)
OMAP4430_PRM_DEVICE_INST,
offset);
}
+
+static inline u32 _read_pending_irq_reg(u16 irqen_offs, u16 irqst_offs)
+{
+ u32 mask, st;
+
+ /* XXX read mask from RAM? */
+ mask = omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST, irqen_offs);
+ st = omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST, irqst_offs);
+
+ return mask & st;
+}
+
+/**
+ * omap44xx_prm_read_pending_irqs - read pending PRM MPU IRQs into @events
+ * @events: ptr to two consecutive u32s, preallocated by caller
+ *
+ * Read PRM_IRQSTATUS_MPU* bits, AND'ed with the currently-enabled PRM
+ * MPU IRQs, and store the result into the two u32s pointed to by @events.
+ * No return value.
+ */
+void omap44xx_prm_read_pending_irqs(unsigned long *events)
+{
+ events[0] = _read_pending_irq_reg(OMAP4_PRM_IRQENABLE_MPU_OFFSET,
+ OMAP4_PRM_IRQSTATUS_MPU_OFFSET);
+
+ events[1] = _read_pending_irq_reg(OMAP4_PRM_IRQENABLE_MPU_2_OFFSET,
+ OMAP4_PRM_IRQSTATUS_MPU_2_OFFSET);
+}
+
+/**
+ * omap44xx_prm_ocp_barrier - force buffered MPU writes to the PRM to complete
+ *
+ * Force any buffered writes to the PRM IP block to complete. Needed
+ * by the PRM IRQ handler, which reads and writes directly to the IP
+ * block, to avoid race conditions after acknowledging or clearing IRQ
+ * bits. No return value.
+ */
+void omap44xx_prm_ocp_barrier(void)
+{
+ omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST,
+ OMAP4_REVISION_PRM_OFFSET);
+}
+
+/**
+ * omap44xx_prm_save_and_clear_irqen - save/clear PRM_IRQENABLE_MPU* regs
+ * @saved_mask: ptr to a u32 array to save IRQENABLE bits
+ *
+ * Save the PRM_IRQENABLE_MPU and PRM_IRQENABLE_MPU_2 registers to
+ * @saved_mask. @saved_mask must be allocated by the caller.
+ * Intended to be used in the PRM interrupt handler suspend callback.
+ * The OCP barrier is needed to ensure the write to disable PRM
+ * interrupts reaches the PRM before returning; otherwise, spurious
+ * interrupts might occur. No return value.
+ */
+void omap44xx_prm_save_and_clear_irqen(u32 *saved_mask)
+{
+ saved_mask[0] =
+ omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQSTATUS_MPU_OFFSET);
+ saved_mask[1] =
+ omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQSTATUS_MPU_2_OFFSET);
+
+ omap4_prm_write_inst_reg(0, OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQENABLE_MPU_OFFSET);
+ omap4_prm_write_inst_reg(0, OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQENABLE_MPU_2_OFFSET);
+
+ /* OCP barrier */
+ omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST,
+ OMAP4_REVISION_PRM_OFFSET);
+}
+
+/**
+ * omap44xx_prm_restore_irqen - set PRM_IRQENABLE_MPU* registers from args
+ * @saved_mask: ptr to a u32 array of IRQENABLE bits saved previously
+ *
+ * Restore the PRM_IRQENABLE_MPU and PRM_IRQENABLE_MPU_2 registers from
+ * @saved_mask. Intended to be used in the PRM interrupt handler resume
+ * callback to restore values saved by omap44xx_prm_save_and_clear_irqen().
+ * No OCP barrier should be needed here; any pending PRM interrupts will fire
+ * once the writes reach the PRM. No return value.
+ */
+void omap44xx_prm_restore_irqen(u32 *saved_mask)
+{
+ omap4_prm_write_inst_reg(saved_mask[0], OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQENABLE_MPU_OFFSET);
+ omap4_prm_write_inst_reg(saved_mask[1], OMAP4430_PRM_DEVICE_INST,
+ OMAP4_PRM_IRQENABLE_MPU_2_OFFSET);
+}
+
+static int __init omap4xxx_prcm_init(void)
+{
+ if (cpu_is_omap44xx())
+ return omap_prcm_register_chain_handler(&omap4_prcm_irq_setup);
+ return 0;
+}
+subsys_initcall(omap4xxx_prcm_init);
diff --git a/arch/arm/mach-omap2/prm44xx.h b/arch/arm/mach-omap2/prm44xx.h
index 3d66ccd849d2..7978092946db 100644
--- a/arch/arm/mach-omap2/prm44xx.h
+++ b/arch/arm/mach-omap2/prm44xx.h
@@ -1,7 +1,7 @@
/*
* OMAP44xx PRM instance offset macros
*
- * Copyright (C) 2009-2010 Texas Instruments, Inc.
+ * Copyright (C) 2009-2011 Texas Instruments, Inc.
* Copyright (C) 2009-2010 Nokia Corporation
*
* Paul Walmsley (paul@pwsan.com)
@@ -763,6 +763,12 @@ extern u32 omap4_prm_vcvp_read(u8 offset);
extern void omap4_prm_vcvp_write(u32 val, u8 offset);
extern u32 omap4_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset);
+/* PRM interrupt-related functions */
+extern void omap44xx_prm_read_pending_irqs(unsigned long *events);
+extern void omap44xx_prm_ocp_barrier(void);
+extern void omap44xx_prm_save_and_clear_irqen(u32 *saved_mask);
+extern void omap44xx_prm_restore_irqen(u32 *saved_mask);
+
# endif
#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..860118ab43e2
--- /dev/null
+++ b/arch/arm/mach-omap2/prm_common.c
@@ -0,0 +1,320 @@
+/*
+ * OMAP2+ common Power & Reset Management (PRM) IP block functions
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *
+ * For historical purposes, the API used to configure the PRM
+ * interrupt handler refers to it as the "PRCM interrupt." The
+ * underlying registers are located in the PRM on OMAP3/4.
+ *
+ * XXX This code should eventually be moved to a PRM driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include <mach/system.h>
+#include <plat/common.h>
+#include <plat/prcm.h>
+#include <plat/irqs.h>
+
+#include "prm2xxx_3xxx.h"
+#include "prm44xx.h"
+
+/*
+ * OMAP_PRCM_MAX_NR_PENDING_REG: maximum number of PRM_IRQ*_MPU regs
+ * XXX this is technically not needed, since
+ * omap_prcm_register_chain_handler() could allocate this based on the
+ * actual amount of memory needed for the SoC
+ */
+#define OMAP_PRCM_MAX_NR_PENDING_REG 2
+
+/*
+ * prcm_irq_chips: an array of all of the "generic IRQ chips" in use
+ * by the PRCM interrupt handler code. There will be one 'chip' per
+ * PRM_{IRQSTATUS,IRQENABLE}_MPU register pair. (So OMAP3 will have
+ * one "chip" and OMAP4 will have two.)
+ */
+static struct irq_chip_generic **prcm_irq_chips;
+
+/*
+ * prcm_irq_setup: the PRCM IRQ parameters for the hardware the code
+ * is currently running on. Defined and passed by initialization code
+ * that calls omap_prcm_register_chain_handler().
+ */
+static struct omap_prcm_irq_setup *prcm_irq_setup;
+
+/* Private functions */
+
+/*
+ * Move priority events from events to priority_events array
+ */
+static void omap_prcm_events_filter_priority(unsigned long *events,
+ unsigned long *priority_events)
+{
+ int i;
+
+ for (i = 0; i < prcm_irq_setup->nr_regs; i++) {
+ priority_events[i] =
+ events[i] & prcm_irq_setup->priority_mask[i];
+ events[i] ^= priority_events[i];
+ }
+}
+
+/*
+ * PRCM Interrupt Handler
+ *
+ * This is a common handler for the OMAP PRCM interrupts. Pending
+ * interrupts are detected by a call to prcm_pending_events and
+ * dispatched accordingly. Clearing of the wakeup events should be
+ * done by the SoC specific individual handlers.
+ */
+static void omap_prcm_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+ unsigned long pending[OMAP_PRCM_MAX_NR_PENDING_REG];
+ unsigned long priority_pending[OMAP_PRCM_MAX_NR_PENDING_REG];
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int virtirq;
+ int nr_irqs = prcm_irq_setup->nr_regs * 32;
+
+ /*
+ * If we are suspended, mask all interrupts from PRCM level,
+ * this does not ack them, and they will be pending until we
+ * re-enable the interrupts, at which point the
+ * omap_prcm_irq_handler will be executed again. The
+ * _save_and_clear_irqen() function must ensure that the PRM
+ * write to disable all IRQs has reached the PRM before
+ * returning, or spurious PRCM interrupts may occur during
+ * suspend.
+ */
+ if (prcm_irq_setup->suspended) {
+ prcm_irq_setup->save_and_clear_irqen(prcm_irq_setup->saved_mask);
+ prcm_irq_setup->suspend_save_flag = true;
+ }
+
+ /*
+ * Loop until all pending irqs are handled, since
+ * generic_handle_irq() can cause new irqs to come
+ */
+ while (!prcm_irq_setup->suspended) {
+ prcm_irq_setup->read_pending_irqs(pending);
+
+ /* No bit set, then all IRQs are handled */
+ if (find_first_bit(pending, nr_irqs) >= nr_irqs)
+ break;
+
+ omap_prcm_events_filter_priority(pending, priority_pending);
+
+ /*
+ * Loop on all currently pending irqs so that new irqs
+ * cannot starve previously pending irqs
+ */
+
+ /* Serve priority events first */
+ for_each_set_bit(virtirq, priority_pending, nr_irqs)
+ generic_handle_irq(prcm_irq_setup->base_irq + virtirq);
+
+ /* Serve normal events next */
+ for_each_set_bit(virtirq, pending, nr_irqs)
+ generic_handle_irq(prcm_irq_setup->base_irq + virtirq);
+ }
+ if (chip->irq_ack)
+ chip->irq_ack(&desc->irq_data);
+ if (chip->irq_eoi)
+ chip->irq_eoi(&desc->irq_data);
+ chip->irq_unmask(&desc->irq_data);
+
+ prcm_irq_setup->ocp_barrier(); /* avoid spurious IRQs */
+}
+
+/* Public functions */
+
+/**
+ * omap_prcm_event_to_irq - given a PRCM event name, returns the
+ * corresponding IRQ on which the handler should be registered
+ * @name: name of the PRCM interrupt bit to look up - see struct omap_prcm_irq
+ *
+ * Returns the Linux internal IRQ ID corresponding to @name upon success,
+ * or -ENOENT upon failure.
+ */
+int omap_prcm_event_to_irq(const char *name)
+{
+ int i;
+
+ if (!prcm_irq_setup || !name)
+ return -ENOENT;
+
+ for (i = 0; i < prcm_irq_setup->nr_irqs; i++)
+ if (!strcmp(prcm_irq_setup->irqs[i].name, name))
+ return prcm_irq_setup->base_irq +
+ prcm_irq_setup->irqs[i].offset;
+
+ return -ENOENT;
+}
+
+/**
+ * omap_prcm_irq_cleanup - reverses memory allocated and other steps
+ * done by omap_prcm_register_chain_handler()
+ *
+ * No return value.
+ */
+void omap_prcm_irq_cleanup(void)
+{
+ int i;
+
+ if (!prcm_irq_setup) {
+ pr_err("PRCM: IRQ handler not initialized; cannot cleanup\n");
+ return;
+ }
+
+ if (prcm_irq_chips) {
+ for (i = 0; i < prcm_irq_setup->nr_regs; i++) {
+ if (prcm_irq_chips[i])
+ irq_remove_generic_chip(prcm_irq_chips[i],
+ 0xffffffff, 0, 0);
+ prcm_irq_chips[i] = NULL;
+ }
+ kfree(prcm_irq_chips);
+ prcm_irq_chips = NULL;
+ }
+
+ kfree(prcm_irq_setup->saved_mask);
+ prcm_irq_setup->saved_mask = NULL;
+
+ kfree(prcm_irq_setup->priority_mask);
+ prcm_irq_setup->priority_mask = NULL;
+
+ irq_set_chained_handler(prcm_irq_setup->irq, NULL);
+
+ if (prcm_irq_setup->base_irq > 0)
+ irq_free_descs(prcm_irq_setup->base_irq,
+ prcm_irq_setup->nr_regs * 32);
+ prcm_irq_setup->base_irq = 0;
+}
+
+void omap_prcm_irq_prepare(void)
+{
+ prcm_irq_setup->suspended = true;
+}
+
+void omap_prcm_irq_complete(void)
+{
+ prcm_irq_setup->suspended = false;
+
+ /* If we have not saved the masks, do not attempt to restore */
+ if (!prcm_irq_setup->suspend_save_flag)
+ return;
+
+ prcm_irq_setup->suspend_save_flag = false;
+
+ /*
+ * Re-enable all masked PRCM irq sources, this causes the PRCM
+ * interrupt to fire immediately if the events were masked
+ * previously in the chain handler
+ */
+ prcm_irq_setup->restore_irqen(prcm_irq_setup->saved_mask);
+}
+
+/**
+ * omap_prcm_register_chain_handler - initializes the prcm chained interrupt
+ * handler based on provided parameters
+ * @irq_setup: hardware data about the underlying PRM/PRCM
+ *
+ * Set up the PRCM chained interrupt handler on the PRCM IRQ. Sets up
+ * one generic IRQ chip per PRM interrupt status/enable register pair.
+ * Returns 0 upon success, -EINVAL if called twice or if invalid
+ * arguments are passed, or -ENOMEM on any other error.
+ */
+int omap_prcm_register_chain_handler(struct omap_prcm_irq_setup *irq_setup)
+{
+ int nr_regs = irq_setup->nr_regs;
+ u32 mask[OMAP_PRCM_MAX_NR_PENDING_REG];
+ int offset, i;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+
+ if (!irq_setup)
+ return -EINVAL;
+
+ if (prcm_irq_setup) {
+ pr_err("PRCM: already initialized; won't reinitialize\n");
+ return -EINVAL;
+ }
+
+ if (nr_regs > OMAP_PRCM_MAX_NR_PENDING_REG) {
+ pr_err("PRCM: nr_regs too large\n");
+ return -EINVAL;
+ }
+
+ prcm_irq_setup = irq_setup;
+
+ prcm_irq_chips = kzalloc(sizeof(void *) * nr_regs, GFP_KERNEL);
+ prcm_irq_setup->saved_mask = kzalloc(sizeof(u32) * nr_regs, GFP_KERNEL);
+ prcm_irq_setup->priority_mask = kzalloc(sizeof(u32) * nr_regs,
+ GFP_KERNEL);
+
+ if (!prcm_irq_chips || !prcm_irq_setup->saved_mask ||
+ !prcm_irq_setup->priority_mask) {
+ pr_err("PRCM: kzalloc failed\n");
+ goto err;
+ }
+
+ memset(mask, 0, sizeof(mask));
+
+ for (i = 0; i < irq_setup->nr_irqs; i++) {
+ offset = irq_setup->irqs[i].offset;
+ mask[offset >> 5] |= 1 << (offset & 0x1f);
+ if (irq_setup->irqs[i].priority)
+ irq_setup->priority_mask[offset >> 5] |=
+ 1 << (offset & 0x1f);
+ }
+
+ irq_set_chained_handler(irq_setup->irq, omap_prcm_irq_handler);
+
+ irq_setup->base_irq = irq_alloc_descs(-1, 0, irq_setup->nr_regs * 32,
+ 0);
+
+ if (irq_setup->base_irq < 0) {
+ pr_err("PRCM: failed to allocate irq descs: %d\n",
+ irq_setup->base_irq);
+ goto err;
+ }
+
+ for (i = 0; i <= irq_setup->nr_regs; i++) {
+ gc = irq_alloc_generic_chip("PRCM", 1,
+ irq_setup->base_irq + i * 32, prm_base,
+ handle_level_irq);
+
+ if (!gc) {
+ pr_err("PRCM: failed to allocate generic chip\n");
+ goto err;
+ }
+ ct = gc->chip_types;
+ ct->chip.irq_ack = irq_gc_ack_set_bit;
+ ct->chip.irq_mask = irq_gc_mask_clr_bit;
+ ct->chip.irq_unmask = irq_gc_mask_set_bit;
+
+ ct->regs.ack = irq_setup->ack + i * 4;
+ ct->regs.mask = irq_setup->mask + i * 4;
+
+ irq_setup_generic_chip(gc, mask[i], 0, IRQ_NOREQUEST, 0);
+ prcm_irq_chips[i] = gc;
+ }
+
+ return 0;
+
+err:
+ omap_prcm_irq_cleanup();
+ return -ENOMEM;
+}