diff options
Diffstat (limited to 'drivers/mfd/twl6030-irq.c')
-rw-r--r-- | drivers/mfd/twl6030-irq.c | 172 |
1 files changed, 165 insertions, 7 deletions
diff --git a/drivers/mfd/twl6030-irq.c b/drivers/mfd/twl6030-irq.c index b0563b6..fa18b02 100644 --- a/drivers/mfd/twl6030-irq.c +++ b/drivers/mfd/twl6030-irq.c @@ -37,6 +37,8 @@ #include <linux/kthread.h> #include <linux/i2c/twl.h> #include <linux/platform_device.h> +#include <linux/suspend.h> +#include <linux/reboot.h> #include "twl-core.h" @@ -55,7 +57,7 @@ static int twl6030_interrupt_mapping[24] = { PWR_INTR_OFFSET, /* Bit 0 PWRON */ PWR_INTR_OFFSET, /* Bit 1 RPWRON */ - PWR_INTR_OFFSET, /* Bit 2 BAT_VLOW */ + TWL_VLOW_INTR_OFFSET, /* Bit 2 BAT_VLOW */ RTC_INTR_OFFSET, /* Bit 3 RTC_ALARM */ RTC_INTR_OFFSET, /* Bit 4 RTC_PERIOD */ HOTDIE_INTR_OFFSET, /* Bit 5 HOT_DIE */ @@ -82,9 +84,50 @@ static int twl6030_interrupt_mapping[24] = { }; /*----------------------------------------------------------------------*/ -static unsigned twl6030_irq_base; +static unsigned twl6030_irq_base, twl6030_irq_end; +static int twl_irq; +static bool twl_irq_wake_enabled; +static struct task_struct *task; static struct completion irq_event; +static atomic_t twl6030_wakeirqs = ATOMIC_INIT(0); + +static int twl6030_irq_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, void *unused) +{ + int chained_wakeups; + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + chained_wakeups = atomic_read(&twl6030_wakeirqs); + + if (chained_wakeups && !twl_irq_wake_enabled) { + if (enable_irq_wake(twl_irq)) + pr_err("twl6030 IRQ wake enable failed\n"); + else + twl_irq_wake_enabled = true; + } else if (!chained_wakeups && twl_irq_wake_enabled) { + disable_irq_wake(twl_irq); + twl_irq_wake_enabled = false; + } + + disable_irq(twl_irq); + break; + + case PM_POST_SUSPEND: + enable_irq(twl_irq); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block twl6030_irq_pm_notifier_block = { + .notifier_call = twl6030_irq_pm_notifier, +}; /* * This thread processes interrupts reported by the Primary Interrupt Handler. @@ -104,6 +147,7 @@ static int twl6030_irq_thread(void *data) u8 bytes[4]; u32 int_sts; } sts; + u32 int_sts; /* sts.int_sts converted to CPU endianness */ /* Wait for IRQ, then read PIH irq status (also blocking) */ wait_for_completion_interruptible(&irq_event); @@ -135,9 +179,10 @@ static int twl6030_irq_thread(void *data) if (sts.bytes[2] & 0x10) sts.bytes[2] |= 0x08; - for (i = 0; sts.int_sts; sts.int_sts >>= 1, i++) { + int_sts = le32_to_cpu(sts.int_sts); + for (i = 0; int_sts; int_sts >>= 1, i++) { local_irq_disable(); - if (sts.int_sts & 0x1) { + if (int_sts & 0x1) { int module_irq = twl6030_irq_base + twl6030_interrupt_mapping[i]; generic_handle_irq(module_irq); @@ -181,6 +226,17 @@ static irqreturn_t handle_twl6030_pih(int irq, void *devid) return IRQ_HANDLED; } +/* + * handle_twl6030_vlow() is a threaded BAT_VLOW interrupt handler. BAT_VLOW + * is a secondary interrupt generated in twl6030_irq_thread(). + */ +static irqreturn_t handle_twl6030_vlow(int irq, void *unused) +{ + pr_info("handle_twl6030_vlow: kernel_power_off()\n"); + kernel_power_off(); + return IRQ_HANDLED; +} + /*----------------------------------------------------------------------*/ static inline void activate_irq(int irq) @@ -196,6 +252,16 @@ static inline void activate_irq(int irq) #endif } +int twl6030_irq_set_wake(struct irq_data *d, unsigned int on) +{ + if (on) + atomic_inc(&twl6030_wakeirqs); + else + atomic_dec(&twl6030_wakeirqs); + + return 0; +} + /*----------------------------------------------------------------------*/ static unsigned twl6030_irq_next; @@ -299,12 +365,75 @@ int twl6030_mmc_card_detect(struct device *dev, int slot) } EXPORT_SYMBOL(twl6030_mmc_card_detect); +int twl6030_vlow_init(int vlow_irq) +{ + int status; + u8 val; + + status = twl_i2c_read_u8(TWL_MODULE_PM_SLAVE_RES, &val, + REG_VBATMIN_HI_CFG_STATE); + if (status < 0) { + pr_err("twl6030: I2C err reading REG_VBATMIN_HI_CFG_STATE: %d\n", + status); + return status; + } + + status = twl_i2c_write_u8(TWL_MODULE_PM_SLAVE_RES, + val | VBATMIN_VLOW_EN, REG_VBATMIN_HI_CFG_STATE); + if (status < 0) { + pr_err("twl6030: I2C err writing REG_VBATMIN_HI_CFG_STATE: %d\n", + status); + return status; + } + + status = twl_i2c_read_u8(TWL_MODULE_PIH, &val, REG_INT_MSK_LINE_A); + if (status < 0) { + pr_err("twl6030: I2C err reading REG_INT_MSK_LINE_A: %d\n", + status); + return status; + } + + status = twl_i2c_write_u8(TWL_MODULE_PIH, val & ~VLOW_INT_MASK, + REG_INT_MSK_LINE_A); + if (status < 0) { + pr_err("twl6030: I2C err writing REG_INT_MSK_LINE_A: %d\n", + status); + return status; + } + + status = twl_i2c_read_u8(TWL_MODULE_PIH, &val, REG_INT_MSK_STS_A); + if (status < 0) { + pr_err("twl6030: I2C err reading REG_INT_MSK_STS_A: %d\n", + status); + return status; + } + + status = twl_i2c_write_u8(TWL_MODULE_PIH, val & ~VLOW_INT_MASK, + REG_INT_MSK_STS_A); + if (status < 0) { + pr_err("twl6030: I2C err writing REG_INT_MSK_STS_A: %d\n", + status); + return status; + } + + /* install an irq handler for vlow */ + status = request_threaded_irq(vlow_irq, NULL, handle_twl6030_vlow, + IRQF_ONESHOT, + "TWL6030-VLOW", handle_twl6030_vlow); + if (status < 0) { + pr_err("twl6030: could not claim vlow irq %d: %d\n", vlow_irq, + status); + return status; + } + + return 0; +} + int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) { int status = 0; int i; - struct task_struct *task; int ret; u8 mask[4]; @@ -320,6 +449,7 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) REG_INT_STS_A, 3); /* clear INT_STS_A,B,C */ twl6030_irq_base = irq_base; + twl6030_irq_end = irq_end; /* install an irq handler for each of the modules; * clone dummy irq_chip since PIH can't *do* anything @@ -327,10 +457,12 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) twl6030_irq_chip = dummy_irq_chip; twl6030_irq_chip.name = "twl6030"; twl6030_irq_chip.irq_set_type = NULL; + twl6030_irq_chip.irq_set_wake = twl6030_irq_set_wake; for (i = irq_base; i < irq_end; i++) { irq_set_chip_and_handler(i, &twl6030_irq_chip, handle_simple_irq); + irq_set_chip_data(i, (void *)irq_num); activate_irq(i); } @@ -353,10 +485,22 @@ int twl6030_init_irq(int irq_num, unsigned irq_base, unsigned irq_end) pr_err("twl6030: could not claim irq%d: %d\n", irq_num, status); goto fail_irq; } + + twl_irq = irq_num; + register_pm_notifier(&twl6030_irq_pm_notifier_block); + + status = twl6030_vlow_init(twl6030_irq_base + TWL_VLOW_INTR_OFFSET); + if (status < 0) + goto fail_vlow; + return status; -fail_irq: + +fail_vlow: free_irq(irq_num, &irq_event); +fail_irq: + kthread_stop(task); + fail_kthread: for (i = irq_base; i < irq_end; i++) irq_set_chip_and_handler(i, NULL, NULL); @@ -365,11 +509,25 @@ fail_kthread: int twl6030_exit_irq(void) { + int i; + unregister_pm_notifier(&twl6030_irq_pm_notifier_block); + + if (task) + kthread_stop(task); - if (twl6030_irq_base) { + if (!twl6030_irq_base || !twl6030_irq_end) { pr_err("twl6030: can't yet clean up IRQs?\n"); return -ENOSYS; } + + free_irq(twl6030_irq_base + TWL_VLOW_INTR_OFFSET, + handle_twl6030_vlow); + + free_irq(twl_irq, &irq_event); + + for (i = twl6030_irq_base; i < twl6030_irq_end; i++) + irq_set_chip_and_handler(i, NULL, NULL); + return 0; } |