aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd/twl6030-irq.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/twl6030-irq.c')
-rw-r--r--drivers/mfd/twl6030-irq.c172
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;
}