diff options
Diffstat (limited to 'arch/arm/plat-mxc/irq.c')
-rw-r--r-- | arch/arm/plat-mxc/irq.c | 316 |
1 files changed, 315 insertions, 1 deletions
diff --git a/arch/arm/plat-mxc/irq.c b/arch/arm/plat-mxc/irq.c index 87d253bc3d3c..13a5cc1fd95e 100644 --- a/arch/arm/plat-mxc/irq.c +++ b/arch/arm/plat-mxc/irq.c @@ -12,6 +12,7 @@ #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/device.h> +#include <linux/sysdev.h> #include <linux/errno.h> #include <asm/hardware.h> #include <asm/io.h> @@ -20,6 +21,111 @@ #include <asm/arch/common.h> /*! + * @file plat-mxc/irq.c + * + * @brief This file contains the AVIC implementation details. + * + * @ingroup Interrupt + */ + +#define IRQ_BIT(irq) (1 << (irq)) + +static uint32_t saved_wakeup_low, saved_wakeup_high; +static uint32_t suspend_wakeup_low, suspend_wakeup_high; + +/* + ***************************************** + * EDIO Registers * + ***************************************** + */ +#ifdef EDIO_BASE_ADDR + +static const int mxc_edio_irq_map[] = { + INT_EXT_INT0, + INT_EXT_INT1, + INT_EXT_INT2, + INT_EXT_INT3, + INT_EXT_INT4, + INT_EXT_INT5, + INT_EXT_INT6, + INT_EXT_INT7, +}; + +static u32 edio_irq_type[MXC_MAX_EXT_LINES] = { + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, + IRQT_LOW, +}; + +static int irq_to_edio(int irq) +{ + int i; + int edio = -1; + + for (i = 0; i < MXC_MAX_EXT_LINES; i++) { + if (mxc_edio_irq_map[i] == irq) { + edio = i; + break; + } + } + return edio; +} + +static void mxc_irq_set_edio_level(int irq, int trigger) +{ + int edio; + unsigned short rval; + + edio = irq_to_edio(irq); + rval = __raw_readw(EDIO_EPPAR); + rval = (rval & (~(0x3 << (edio * 2)))) | (trigger << (edio * 2)); + __raw_writew(rval, EDIO_EPPAR); +} + +static void mxc_irq_set_edio_dir(int irq, int dir) +{ + int edio; + unsigned short rval; + + edio = irq_to_edio(irq); + + rval = __raw_readw(EDIO_EPDR); + rval &= ~(1 << edio); + rval |= (0 << edio); + __raw_writew(rval, (EDIO_EPDR)); + + /* set direction */ + rval = __raw_readw(EDIO_EPDDR); + + if (dir) + /* out */ + rval |= (1 << edio); + else + /* in */ + rval &= ~(1 << edio); + + __raw_writew(rval, EDIO_EPDDR); + +} + +/* + * Allows tuning the IRQ type , trigger and priority + */ +static void mxc_irq_set_edio(int irq, int fiq, int priority, int trigger) +{ + + mxc_irq_set_edio_dir(irq, 0); + /* set level */ + mxc_irq_set_edio_level(irq, trigger); +} +#endif + +/*! * Disable interrupt number "irq" in the AVIC * * @param irq interrupt source number @@ -39,12 +145,190 @@ static void mxc_unmask_irq(unsigned int irq) __raw_writel(irq, AVIC_INTENNUM); } +/*! + * Set interrupt number "irq" in the AVIC as a wake-up source. + * + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * disble as wake-up if equal to zero + * + * @return This function returns 0 on success. + */ +static int mxc_set_wake_irq(unsigned int irq, unsigned int enable) +{ + uint32_t *wakeup_intr; + uint32_t irq_bit; + + if (irq < 32) { + wakeup_intr = &suspend_wakeup_low; + irq_bit = IRQ_BIT(irq); + } else { + wakeup_intr = &suspend_wakeup_high; + irq_bit = IRQ_BIT(irq - 32); + } + + if (enable) { + *wakeup_intr |= irq_bit; + } else { + *wakeup_intr &= ~irq_bit; + } + + return 0; +} + static struct irq_chip mxc_avic_chip = { .mask_ack = mxc_mask_irq, .mask = mxc_mask_irq, .unmask = mxc_unmask_irq, + .set_wake = mxc_set_wake_irq, }; +#ifdef EDIO_BASE_ADDR +static void mxc_ack_edio(u32 irq) +{ + u16 edio = (u16) irq_to_edio(irq); + if (edio_irq_type[edio] == IRQT_LOW) { + /* Mask interrupt for level sensitive */ + mxc_mask_irq(irq); + } else if (edio_irq_type[edio] == IRQT_HIGH) { + /* clear edio interrupt */ + __raw_writew((1 << edio), EDIO_EPFR); + /* dummy read for edio workaround */ + __raw_readw(EDIO_EPFR); + /* Mask interrupt for level sensitive */ + mxc_mask_irq(irq); + } else { + /* clear edio interrupt */ + __raw_writew((1 << edio), EDIO_EPFR); + /* dummy read for edio workaround */ + __raw_readw(EDIO_EPFR); + } +} + +static int mxc_edio_set_type(u32 irq, u32 type) +{ + edio_irq_type[irq_to_edio(irq)] = type; + + switch (type) { + case IRQT_RISING: + mxc_irq_set_edio_level(irq, 1); + set_irq_handler(irq, handle_edge_irq); + break; + case IRQT_FALLING: + mxc_irq_set_edio_level(irq, 2); + set_irq_handler(irq, handle_edge_irq); + break; + case IRQT_BOTHEDGE: + mxc_irq_set_edio_level(irq, 3); + set_irq_handler(irq, handle_edge_irq); + break; + case IRQT_LOW: + mxc_irq_set_edio_level(irq, 0); + set_irq_handler(irq, handle_level_irq); + break; + case IRQT_HIGH: + /* EDIO doesn't really support high-level interrupts, + * so we're faking it + */ + mxc_irq_set_edio_level(irq, 1); + set_irq_handler(irq, handle_level_irq); + break; + default: + return -EINVAL; + break; + } + return 0; +} + +static struct irq_chip mxc_edio_chip = { + .name = "MXC_EDIO", + .ack = mxc_ack_edio, + .mask = mxc_mask_irq, + .unmask = mxc_unmask_irq, + .set_type = mxc_edio_set_type, + .set_wake = mxc_set_wake_irq, +}; + +#endif + +#ifdef CONFIG_PM +/*! + * This function puts the AVIC in low-power mode/state. + * All the interrupts that are enabled are first saved. + * Only those interrupts which registers as a wake source by calling + * enable_irq_wake are enabled. All other interrupts are disabled. + * + * @param dev the system device structure used to give information + * on AVIC to suspend + * @param mesg the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_avic_suspend(struct sys_device *dev, pm_message_t mesg) +{ + saved_wakeup_high = __raw_readl(AVIC_INTENABLEH); + saved_wakeup_low = __raw_readl(AVIC_INTENABLEL); + + __raw_writel(suspend_wakeup_high, AVIC_INTENABLEH); + __raw_writel(suspend_wakeup_low, AVIC_INTENABLEL); + + return 0; +} + +/*! + * This function brings the AVIC back from low-power state. + * All the interrupts enabled before suspension are re-enabled from + * the saved information. + * + * @param dev the system device structure used to give information + * on AVIC to resume + * + * @return The function always returns 0. + */ +static int mxc_avic_resume(struct sys_device *dev) +{ + __raw_writel(saved_wakeup_high, AVIC_INTENABLEH); + __raw_writel(saved_wakeup_low, AVIC_INTENABLEL); + + return 0; +} + +#else +#define mxc_avic_suspend NULL +#define mxc_avic_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct sysdev_class mxc_avic_sysclass = { + set_kset_name("mxc_irq"), + .suspend = mxc_avic_suspend, + .resume = mxc_avic_resume, +}; + +/*! + * This structure represents AVIC as a system device. + * System devices follow a slightly different driver model. + * They don't need to do dynammic driver binding, can't be probed, + * and don't reside on any type of peripheral bus. + * So, it is represented and treated a little differently. + */ +static struct sys_device mxc_avic_device = { + .id = 0, + .cls = &mxc_avic_sysclass, +}; + +/* + * This function is used to get the AVIC Lo and Hi interrupts + * that are enabled as wake up sources to wake up the core from suspend + */ +void mxc_get_wake_irq(u32 * wake_src[]) +{ + *wake_src[0] = __raw_readl(AVIC_INTENABLEL); + *wake_src[1] = __raw_readl(AVIC_INTENABLEH); +} + /*! * This function initializes the AVIC hardware and disables all the * interrupts. It registers the interrupt enable and disable functions @@ -69,7 +353,15 @@ void __init mxc_init_irq(void) __raw_writel(0, AVIC_INTTYPEH); __raw_writel(0, AVIC_INTTYPEL); for (i = 0; i < MXC_MAX_INT_LINES; i++) { - set_irq_chip(i, &mxc_avic_chip); +#ifdef EDIO_BASE_ADDR + if (irq_to_edio(i) != -1) { + mxc_irq_set_edio(i, 0, 0, 0); + set_irq_chip(i, &mxc_edio_chip); + } else +#endif + { + set_irq_chip(i, &mxc_avic_chip); + } set_irq_handler(i, handle_level_irq); set_irq_flags(i, IRQF_VALID); } @@ -81,3 +373,25 @@ void __init mxc_init_irq(void) printk(KERN_INFO "MXC IRQ initialized\n"); } + +/*! + * This function registers AVIC hardware as a system device. + * System devices will only be suspended with interrupts disabled, and + * after all other devices have been suspended. On resume, they will be + * resumed before any other devices, and also with interrupts disabled. + * + * @return This function returns 0 on success. + */ +static int __init mxc_avic_sysinit(void) +{ + int ret = 0; + + ret = sysdev_class_register(&mxc_avic_sysclass); + if (ret == 0) { + ret = sysdev_register(&mxc_avic_device); + } + + return ret; +} + +arch_initcall(mxc_avic_sysinit); |