@@ -32,6 +32,7 @@
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/i2c/twl4030.h>
+#include <linux/mutex.h>
/*
@@ -59,9 +60,12 @@ struct twl4030_keypad {
unsigned n_rows;
unsigned n_cols;
unsigned irq;
+ unsigned user_disabled:1;
+ unsigned disable_depth;
struct device *dbg_dev;
struct input_dev *input;
+ struct mutex mutex;
};
/*----------------------------------------------------------------------*/
@@ -155,6 +159,24 @@ static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
return ret;
}
+static int twl4030_kp_enable_interrupts(struct twl4030_keypad *kp)
+{
+ u8 reg;
+ int ret;
+ /* Enable KP and TO interrupts now. */
+ reg = (u8)~(KEYP_IMR1_KP | KEYP_IMR1_TO);
+ ret = twl4030_kpwrite_u8(kp, reg, KEYP_IMR1);
+ return ret;
+}
+
+static void twl4030_kp_disable_interrupts(struct twl4030_keypad *kp)
+{
+ u8 reg;
+ /* mask all events - we don't care about the result */
+ reg = KEYP_IMR1_MIS | KEYP_IMR1_TO | KEYP_IMR1_LK | KEYP_IMR1_KP;
+ (void)twl4030_kpwrite_u8(kp, reg, KEYP_IMR1);
+}
+
static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
{
/* If all bits in a row are active for all coloumns then
@@ -198,25 +220,11 @@ static int twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
return 0;
}
-static void twl4030_kp_scan(struct twl4030_keypad *kp, bool release_all)
+static void twl4030_kp_report_changes(struct twl4030_keypad *kp, u16 *new_state)
{
struct input_dev *input = kp->input;
- u16 new_state[TWL4030_MAX_ROWS];
int col, row;
- if (release_all)
- memset(new_state, 0, sizeof(new_state));
- else {
- /* check for any changes */
- int ret = twl4030_read_kp_matrix_state(kp, new_state);
-
- if (ret < 0) /* panic ... */
- return;
-
- if (twl4030_is_in_ghost_state(kp, new_state))
- return;
- }
-
/* check for changes and print those */
for (row = 0; row < kp->n_rows; row++) {
int changed = new_state[row] ^ kp->kp_state[row];
@@ -244,6 +252,79 @@ static void twl4030_kp_scan(struct twl4030_keypad *kp, bool release_all)
input_sync(input);
}
+static inline int twl4030_kp_disabled(struct twl4030_keypad *kp)
+{
+ return kp->disable_depth != 0;
+}
+
+static void twl4030_kp_enable(struct twl4030_keypad *kp)
+{
+ BUG_ON(!twl4030_kp_disabled(kp));
+ if (--kp->disable_depth == 0) {
+ enable_irq(kp->irq);
+ twl4030_kp_enable_interrupts(kp);
+ }
+}
+
+static int twl4030_kp_scan(struct twl4030_keypad *kp, u16 *new_state)
+{
+ /* check for any changes */
+ int ret = twl4030_read_kp_matrix_state(kp, new_state);
+ if (ret < 0) /* panic ... */
+ return ret;
+
+ return twl4030_is_in_ghost_state(kp, new_state);
+}
+
+static void twl4030_kp_disable(struct twl4030_keypad *kp)
+{
+ u16 new_state[TWL4030_MAX_ROWS];
+
+ if (kp->disable_depth++ == 0) {
+ memset(new_state, 0, sizeof(new_state));
+ twl4030_kp_report_changes(kp, new_state);
+ twl4030_kp_disable_interrupts(kp);
+ disable_irq(kp->irq);
+ }
+}
+
+static ssize_t twl4030_kp_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct twl4030_keypad *kp = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", twl4030_kp_disabled(kp));
+}
+
+static ssize_t twl4030_kp_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct twl4030_keypad *kp = dev_get_drvdata(dev);
+ long i = 0;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &i);
+ if (ret)
+ return -EINVAL;
+ i = !!i;
+
+ mutex_lock(&kp->mutex);
+ if (i == kp->user_disabled) {
+ mutex_unlock(&kp->mutex);
+ return count;
+ }
+ kp->user_disabled = i;
+
+ if (i)
+ twl4030_kp_disable(kp);
+ else
+ twl4030_kp_enable(kp);
+
+ mutex_unlock(&kp->mutex);
+ return count;
+}
+
/*
* Keypad interrupt handler
*/
@@ -252,6 +333,7 @@ static irqreturn_t do_kp_irq(int irq, void *_kp)
struct twl4030_keypad *kp = _kp;
u8 reg;
int ret;
+ u16 new_state[TWL4030_MAX_ROWS];
#ifdef CONFIG_LOCKDEP
/* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
@@ -264,12 +346,22 @@ static irqreturn_t do_kp_irq(int irq, void *_kp)
/* Read & Clear TWL4030 pending interrupt */
ret = twl4030_kpread(kp, ®, KEYP_ISR1, 1);
+ mutex_lock(&kp->mutex);
+ if (twl4030_kp_disabled(kp)) {
+ mutex_unlock(&kp->mutex);
+ return IRQ_HANDLED;
+ }
+
/* Release all keys if I2C has gone bad or
* the KEYP has gone to idle state */
if (ret >= 0 && (reg & KEYP_IMR1_KP))
- twl4030_kp_scan(kp, false);
+ twl4030_kp_scan(kp, new_state);
else
- twl4030_kp_scan(kp, true);
+ memset(new_state, 0, sizeof(new_state));
+
+ twl4030_kp_report_changes(kp, new_state);
+
+ mutex_unlock(&kp->mutex);
return IRQ_HANDLED;
}
@@ -327,6 +419,9 @@ static int __devinit twl4030_kp_program(struct twl4030_keypad *kp)
return 0;
}
+static DEVICE_ATTR(disable_kp, 0664, twl4030_kp_disable_show,
+ twl4030_kp_disable_store);
+
/*
* Registers keypad device with input subsystem
* and configures TWL4030 keypad registers
@@ -337,7 +432,6 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
const struct matrix_keymap_data *keymap_data = pdata->keymap_data;
struct twl4030_keypad *kp;
struct input_dev *input;
- u8 reg;
int error;
if (!pdata || !pdata->rows || !pdata->cols ||
@@ -353,6 +447,8 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
goto err1;
}
+ mutex_init(&kp->mutex);
+
/* Get the debug Device */
kp->dbg_dev = &pdev->dev;
kp->input = input;
@@ -411,18 +507,20 @@ static int __devinit twl4030_kp_probe(struct platform_device *pdev)
}
/* Enable KP and TO interrupts now. */
- reg = (u8) ~(KEYP_IMR1_KP | KEYP_IMR1_TO);
- if (twl4030_kpwrite_u8(kp, reg, KEYP_IMR1)) {
- error = -EIO;
+ error = twl4030_kp_enable_interrupts(kp);
+ if (error < 0)
+ goto err4;
+
+ error = device_create_file(&pdev->dev, &dev_attr_disable_kp);
+ if (error < 0)
goto err4;
- }
platform_set_drvdata(pdev, kp);
return 0;
err4:
/* mask all events - we don't care about the result */
- (void) twl4030_kpwrite_u8(kp, 0xff, KEYP_IMR1);
+ twl4030_kp_disable_interrupts(kp);
err3:
free_irq(kp->irq, NULL);
err2:
@@ -441,11 +539,45 @@ static int __devexit twl4030_kp_remove(struct platform_device *pdev)
free_irq(kp->irq, kp);
input_unregister_device(kp->input);
platform_set_drvdata(pdev, NULL);
+ device_remove_file(&pdev->dev, &dev_attr_disable_kp);
kfree(kp);
return 0;
}
+#ifdef CONFIG_PM
+static int twl4030_kp_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ struct twl4030_keypad *kp = dev_get_drvdata(&pdev->dev);
+ mutex_lock(&kp->mutex);
+ twl4030_kp_disable(kp);
+ mutex_unlock(&kp->mutex);
+ return 0;
+}
+
+static int twl4030_kp_resume(struct platform_device *pdev)
+{
+ struct twl4030_keypad *kp = dev_get_drvdata(&pdev->dev);
+ mutex_lock(&kp->mutex);
+ twl4030_kp_enable(kp);
+ mutex_unlock(&kp->mutex);
+ return 0;
+}
+
+static void twl4030_kp_shutdown(struct platform_device *pdev)
+{
+ struct twl4030_keypad *kp = dev_get_drvdata(&pdev->dev);
+ /* Disable controller */
+ twl4030_kpwrite_u8(kp, 0, KEYP_CTRL);
+}
+#else
+
+#define twl4030_kp_suspend NULL
+#define twl4030_kp_resume NULL
+#define twl4030_kp_shutdown NULL
+
+#endif /* CONFIG_PM */
+
/*
* NOTE: twl4030 are multi-function devices connected via I2C.
* So this device is a child of an I2C parent, thus it needs to
@@ -455,6 +587,9 @@ static int __devexit twl4030_kp_remove(struct platform_device *pdev)
static struct platform_driver twl4030_kp_driver = {
.probe = twl4030_kp_probe,
.remove = __devexit_p(twl4030_kp_remove),
+ .suspend = twl4030_kp_suspend,
+ .resume = twl4030_kp_resume,
+ .shutdown = twl4030_kp_shutdown,
.driver = {
.name = "twl4030_keypad",
.owner = THIS_MODULE,