Files
linux-legacy/arch/arm/mach-mx35/mx35_3stack_irq.c
Zhou Jingyu 4d5dab2ecf ENGR00118898 mx35: fix error when disable pseudo irq
avoid i2c access of pseudo irq ops while suspending

Signed-off-by: Zhou Jingyu <Jingyu.Zhou@freescale.com>
2010-08-10 11:46:02 -05:00

376 lines
9.1 KiB
C

/*
* 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 <linux/types.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/mfd/mc9s08dz60/pmic.h>
#include <mach/hardware.h>
#include <asm/irq.h>
#include <asm/setup.h>
#include <asm/bitops.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/irq.h>
#include <mach/gpio.h>
#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 */