diff options
| author | Olof Johansson <olof@lixom.net> | 2011-12-19 21:04:42 -0800 | 
|---|---|---|
| committer | Olof Johansson <olof@lixom.net> | 2011-12-19 21:04:42 -0800 | 
| commit | b86c7842ef176df0fef3d3d05f83900b3599223d (patch) | |
| tree | cff90b5421967f17d11a7611a9c5f0fca0be4d67 | |
| parent | 05da34d832d404ac9094db57e9295000207c9d62 (diff) | |
| parent | fe5d3e887e3f9d0a97e5b8df968ab84a12128495 (diff) | |
Merge branch 'omap/prcm' into next/pm
* omap/prcm:
  ARM: OMAP2+: hwmod: Add a new flag to handle hwmods left enabled at init
  ARM: OMAP4: PRM: use PRCM interrupt handler
  ARM: OMAP3: pm: use prcm chain handler
  ARM: OMAP: hwmod: add support for selecting mpu_irq for each wakeup pad
  ARM: OMAP2+: mux: add support for PAD wakeup interrupts
  ARM: OMAP: PRCM: add suspend prepare / finish support
  ARM: OMAP: PRCM: add support for chain interrupt handler
  ARM: OMAP3/4: PRM: add functions to read pending IRQs, PRM barrier
  ARM: OMAP2+: hwmod: Add API to enable IO ring wakeup
  ARM: OMAP2+: mux: add wakeup-capable hwmod mux entries to dynamic list
| -rw-r--r-- | arch/arm/mach-omap2/Makefile | 3 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/mux.c | 89 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/omap_hwmod.c | 125 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/pm34xx.c | 115 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prcm-common.h | 75 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prm2xxx_3xxx.c | 97 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prm2xxx_3xxx.h | 9 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prm44xx.c | 116 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prm44xx.h | 8 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/prm_common.c | 320 | ||||
| -rw-r--r-- | arch/arm/plat-omap/include/plat/omap_hwmod.h | 6 | 
11 files changed, 884 insertions, 79 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 529142aff766..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 *   * @@ -1441,6 +1487,25 @@ static int _enable(struct omap_hwmod *oh)  	pr_debug("omap_hwmod: %s: enabling\n", oh->name); +	/* +	 * hwmods with HWMOD_INIT_NO_IDLE flag set are left +	 * in enabled state at init. +	 * Now that someone is really trying to enable them, +	 * just ensure that the hwmod mux is set. +	 */ +	if (oh->_int_flags & _HWMOD_SKIP_ENABLE) { +		/* +		 * If the caller has mux data populated, do the mux'ing +		 * which wouldn't have been done as part of the _enable() +		 * done during setup. +		 */ +		if (oh->mux) +			omap_hwmod_mux(oh->mux, _HWMOD_STATE_ENABLED); + +		oh->_int_flags &= ~_HWMOD_SKIP_ENABLE; +		return 0; +	} +  	if (oh->_state != _HWMOD_STATE_INITIALIZED &&  	    oh->_state != _HWMOD_STATE_IDLE &&  	    oh->_state != _HWMOD_STATE_DISABLED) { @@ -1744,8 +1809,10 @@ static int _setup(struct omap_hwmod *oh, void *data)  	 * it should be set by the core code as a runtime flag during startup  	 */  	if ((oh->flags & HWMOD_INIT_NO_IDLE) && -	    (postsetup_state == _HWMOD_STATE_IDLE)) +	    (postsetup_state == _HWMOD_STATE_IDLE)) { +		oh->_int_flags |= _HWMOD_SKIP_ENABLE;  		postsetup_state = _HWMOD_STATE_ENABLED; +	}  	if (postsetup_state == _HWMOD_STATE_IDLE)  		_idle(oh); @@ -2416,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; @@ -2446,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; @@ -2662,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; +} diff --git a/arch/arm/plat-omap/include/plat/omap_hwmod.h b/arch/arm/plat-omap/include/plat/omap_hwmod.h index 8b372ede17c1..647010109afa 100644 --- a/arch/arm/plat-omap/include/plat/omap_hwmod.h +++ b/arch/arm/plat-omap/include/plat/omap_hwmod.h @@ -97,6 +97,7 @@ struct omap_hwmod_mux_info {  	struct omap_device_pad		*pads;  	int				nr_pads_dynamic;  	struct omap_device_pad		**pads_dynamic; +	int				*irqs;  	bool				enabled;  }; @@ -416,10 +417,13 @@ struct omap_hwmod_omap4_prcm {   * _HWMOD_NO_MPU_PORT: no path exists for the MPU to write to this module   * _HWMOD_WAKEUP_ENABLED: set when the omap_hwmod code has enabled ENAWAKEUP   * _HWMOD_SYSCONFIG_LOADED: set when the OCP_SYSCONFIG value has been cached + * _HWMOD_SKIP_ENABLE: set if hwmod enabled during init (HWMOD_INIT_NO_IDLE) - + *     causes the first call to _enable() to only update the pinmux   */  #define _HWMOD_NO_MPU_PORT			(1 << 0)  #define _HWMOD_WAKEUP_ENABLED			(1 << 1)  #define _HWMOD_SYSCONFIG_LOADED			(1 << 2) +#define _HWMOD_SKIP_ENABLE			(1 << 3)  /*   * omap_hwmod._state definitions @@ -604,6 +608,8 @@ int omap_hwmod_get_context_loss_count(struct omap_hwmod *oh);  int omap_hwmod_no_setup_reset(struct omap_hwmod *oh); +int omap_hwmod_pad_route_irq(struct omap_hwmod *oh, int pad_idx, int irq_idx); +  /*   * Chip variant-specific hwmod init routines - XXX should be converted   * to use initcalls once the initial boot ordering is straightened out | 
