/* * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "board-mx35_3stack.h" #include "iomux.h" /*! * @file mach-mx35/mx35_3stack_irq.c * * @brief This file contains the board specific initialization routines. * * @ingroup MSL_MX35 */ #ifdef CONFIG_MXC_PSEUDO_IRQS /* * The interrupt status and mask variables. */ static unsigned long pseudo_irq_pending; static unsigned long pseudo_irq_enable; static unsigned long pseudo_irq_wakeup; static unsigned long pseudo_suspend; static atomic_t pseudo_irq_state = ATOMIC_INIT(0); /* * The declaration of handler of two work queue. * The one is the work queue to indentify the events from MCU. * The another is the work queue to change the events mask. */ static void mcu_event_handler(struct work_struct *work); static void mcu_state_handler(struct work_struct *work); static void mcu_event_delay(unsigned long data); /*! * The work structure for mcu events. */ static DECLARE_WORK(mcu_event_ws, mcu_event_handler); static DECLARE_WORK(mcu_state_ws, mcu_state_handler); static DEFINE_TIMER(mcu_delay_timer, mcu_event_delay, HZ, 0); static inline void mxc_pseudo_irq_ack(void) { disable_irq(MXC_PSEUDO_PARENT); atomic_set(&pseudo_irq_state, 0); } static inline void mxc_pseudo_irq_trigger(void) { if (!atomic_xchg(&pseudo_irq_state, 1)) enable_irq(MXC_PSEUDO_PARENT); } /* * mask a pseudo interrupt by setting the bit in the mask variable. * @param irq a pseudo virtual irq number */ static void pseudo_mask_irq(u32 irq) { int index = irq - MXC_PSEUDO_IO_BASE; clear_bit(index, &pseudo_irq_enable); } /* * disable a pseudo interrupt by triggerring a work queue * @param irq a pseudo virtual irq number */ static void pseudo_disable_irq(u32 irq) { struct irq_desc *desc = irq_desc + irq; desc->chip->mask(irq); desc->status |= IRQ_MASKED; schedule_work(&mcu_state_ws); } /* * Acknowledge a pseudo interrupt by clearing the bit in the isr variable. * @param irq a pseudo virtual irq number */ static void pseudo_ack_irq(u32 irq) { int index = irq - MXC_PSEUDO_IO_BASE; /* clear the interrupt status */ clear_bit(index, &pseudo_irq_pending); } /* * unmask a pseudo interrupt by clearing the bit in the imr. * @param irq a pseudo virtual irq number */ static void pseudo_unmask_irq(u32 irq) { int index = irq - MXC_PSEUDO_IO_BASE; set_bit(index, &pseudo_irq_enable); if (test_bit(index, &pseudo_irq_pending)) mxc_pseudo_irq_trigger(); } /* * Enable a pseudo interrupt by triggerring a work queue * @param irq a pseudo virtual irq number */ static void pseudo_enable_irq(u32 irq) { struct irq_desc *desc = irq_desc + irq; desc->chip->unmask(irq); desc->status &= ~IRQ_MASKED; schedule_work(&mcu_state_ws); } /* * set pseudo irq as a wake-up source. * @param irq a pseudo virtual irq number * @param enable enable as wake-up if equal to non-ero * @return This function return 0 on success */ static int pseudo_set_wake_irq(u32 irq, u32 enable) { int index = irq - MXC_PSEUDO_IO_BASE; if (index >= MXC_MAX_PSEUDO_IO_LINES) return -ENODEV; if (enable) { if (!pseudo_irq_wakeup) enable_irq_wake(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); pseudo_irq_wakeup |= (1 << index); } else { pseudo_irq_wakeup &= ~(1 << index); if (!pseudo_irq_wakeup) disable_irq_wake(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); } return 0; } static struct irq_chip pseudo_irq_chip = { .ack = pseudo_ack_irq, .mask = pseudo_mask_irq, .disable = pseudo_disable_irq, .unmask = pseudo_unmask_irq, .enable = pseudo_enable_irq, .set_wake = pseudo_set_wake_irq, }; static void mxc_pseudo_irq_handler(u32 irq, struct irq_desc *desc) { u32 pseudo_irq; u32 index, mask; desc->chip->mask(irq); mxc_pseudo_irq_ack(); mask = pseudo_irq_enable; index = pseudo_irq_pending; if (unlikely(!(index & mask))) { printk(KERN_ERR "\nPseudo IRQ: Spurious interrupt:0x%0x\n\n", index); pr_info("IEN=0x%x, PENDING=0x%x\n", mask, index); return; } index = index & mask; pseudo_irq = MXC_PSEUDO_IO_BASE; for (; index != 0; index >>= 1, pseudo_irq++) { struct irq_desc *d; if ((index & 1) == 0) continue; d = irq_desc + pseudo_irq; if (unlikely(!(d->handle_irq))) { printk(KERN_ERR "\nPseudo irq: %d unhandeled\n", pseudo_irq); BUG(); /* oops */ } d->handle_irq(pseudo_irq, d); d->chip->ack(pseudo_irq); } } static void mcu_event_delay(unsigned long data) { schedule_work(&mcu_event_ws); } /*! * This function is called when mcu interrupt occurs on the processor. * It is the interrupt handler for the mcu. * * @param irq the irq number * @param dev_id the pointer on the device * * @return The function returns IRQ_HANDLED when handled. */ static irqreturn_t mcu_irq_handler(int irq, void *dev_id) { disable_irq_nosync(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); if (pseudo_suspend) mod_timer(&mcu_delay_timer, jiffies + HZ); else schedule_work(&mcu_event_ws); return IRQ_HANDLED; } /*! * This function is the work handler of mcu interrupt. * It reads the events status and trigger the pseudo irq. */ static void mcu_event_handler(struct work_struct *work) { int i, err; unsigned int flag1, flag2; /* read int flags and ack int */ for (i = 0; i < 3; i++) { err = mcu_pmic_read_reg(REG_MCU_INT_FLAG_1, &flag1, 0xFFFFFFFF); err |= mcu_pmic_read_reg(REG_MCU_INT_FLAG_2, &flag2, 0xFFFFFFFF); err |= mcu_pmic_write_reg(REG_MCU_INT_FLAG_1, 0, 0xFFFFFFFF); err |= mcu_pmic_write_reg(REG_MCU_INT_FLAG_2, 0, 0xFFFFFFFF); if (err == 0) break; } if (i >= 3) { printk(KERN_ERR "Reads MCU event fail\n"); goto no_new_events; } for (i = 0; flag1 && (i < MCU_INT_RTC); i++, flag1 >>= 1) if (flag1 & 1) set_bit(i, &pseudo_irq_pending); for (i = MCU_INT_RTC; flag2 && (i <= MCU_INT_KEYPAD); i++, flag2 >>= 1) if (flag2 & 1) set_bit(i, &pseudo_irq_pending); no_new_events: if (pseudo_irq_pending & pseudo_irq_enable) mxc_pseudo_irq_trigger(); enable_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0)); } static void mcu_state_handler(struct work_struct *work) { int err, i; unsigned int event1, event2; event1 = pseudo_irq_enable & ((1 << MCU_INT_RTC) - 1); event2 = pseudo_irq_enable >> MCU_INT_RTC; if (is_suspend_ops_started()) return; for (i = 0; i < 3; i++) { err = mcu_pmic_write_reg(REG_MCU_INT_ENABLE_1, event1, 0xFF); err |= mcu_pmic_write_reg(REG_MCU_INT_ENABLE_2, event2, 0xFF); if (err == 0) break; } if (i >= 3) printk(KERN_ERR "Change MCU event mask fail\n"); } static int __init mxc_pseudo_init(void) { int i; /* disable the interrupt and clear the status */ pseudo_irq_pending = 0; pseudo_irq_enable = 0; pr_info("3-Stack Pseudo interrupt rev=0.1v\n"); for (i = MXC_PSEUDO_IO_BASE; i < (MXC_PSEUDO_IO_BASE + MXC_MAX_PSEUDO_IO_LINES); i++) { set_irq_chip(i, &pseudo_irq_chip); set_irq_handler(i, handle_simple_irq); set_irq_flags(i, IRQF_VALID); } set_irq_flags(MXC_PSEUDO_PARENT, IRQF_NOAUTOEN); set_irq_handler(MXC_PSEUDO_PARENT, mxc_pseudo_irq_handler); /* Set and install PMIC IRQ handler */ mxc_request_iomux(MX35_PIN_GPIO1_0, MUX_CONFIG_FUNC); mxc_iomux_set_pad(MX35_PIN_GPIO1_0, PAD_CTL_PKE_NONE); gpio_request(IOMUX_TO_GPIO(MX35_PIN_GPIO1_0), NULL); gpio_direction_input(IOMUX_TO_GPIO(MX35_PIN_GPIO1_0)); set_irq_type(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), IRQF_TRIGGER_RISING); if (request_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), mcu_irq_handler, 0, "MCU_IRQ", 0)) { printk(KERN_ERR "mcu request irq failed\n"); return -1; } return 0; } fs_initcall_sync(mxc_pseudo_init); static int mxc_pseudo_irq_suspend(struct platform_device *dev, pm_message_t mesg) { int err, i; unsigned int event1, event2; if (!pseudo_irq_wakeup) return 0; event1 = pseudo_irq_wakeup & ((1 << MCU_INT_RTC) - 1); event2 = pseudo_irq_wakeup >> MCU_INT_RTC; for (i = 0; i < 3; i++) { err = mcu_pmic_write_reg(REG_MCU_INT_ENABLE_1, event1, 0xFF); err |= mcu_pmic_write_reg(REG_MCU_INT_ENABLE_2, event2, 0xFF); if (err == 0) break; } pseudo_suspend = 1; return err; } static int mxc_pseudo_irq_resume(struct platform_device *dev) { if (!pseudo_irq_wakeup) return 0; schedule_work(&mcu_state_ws); pseudo_suspend = 0; return 0; } static struct platform_driver mxc_pseudo_irq_driver = { .driver = { .name = "mxc_pseudo_irq", }, .suspend = mxc_pseudo_irq_suspend, .resume = mxc_pseudo_irq_resume, }; static int __init mxc_pseudo_sysinit(void) { return platform_driver_register(&mxc_pseudo_irq_driver); } late_initcall(mxc_pseudo_sysinit); #endif /* CONFIG_MXC_PSEUDO_IRQS */