From 02e725ca989f392158b2a15cde6dd0d925bc8e50 Mon Sep 17 00:00:00 2001
From: Cory Maccarrone <darkstar6262@gmail.com>
Date: Wed, 29 Jul 2009 10:15:23 -0700
Subject: [PATCH 2/3] htcpld: Various fixes to the I2C HTC PLD

* Replace tasklets with work queues to avoid schedule while atomic bugs
* Change keypad event handling to only report events once if the same button
  is pushed
* Add a call to htcpld_chip_set() to enable a bit that activates direction-pad
  interrupts on each call (the bit gets disabled each time a button is pushed)

Signed-off-by: Cory Maccarrone <darkstar6262@gmail.com>
Signed-off-by: Angelo Arrifano <miknix@gmail.com>
---
 drivers/i2c/chips/i2c-htcpld.c |  101 ++++++++++++++++++++++------------------
 1 files changed, 56 insertions(+), 45 deletions(-)

diff --git a/drivers/i2c/chips/i2c-htcpld.c b/drivers/i2c/chips/i2c-htcpld.c
index 377ad4f..8fef7f1 100644
--- a/drivers/i2c/chips/i2c-htcpld.c
+++ b/drivers/i2c/chips/i2c-htcpld.c
@@ -5,6 +5,7 @@
  *  vibrator and other power devices. The cpld also returns buttons status
  *  of the directional pads found on this HTC devices.
  *
+ *  Copyright (C) 2009 Cory Maccarrone <darkstar6262@gmail.com>
  *  Copyright (C) 2008 Angelo Arrifano <miknix@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -54,14 +55,20 @@ static const unsigned char htcpld_btns_map[] = {
 
 struct htcpld_btns_dev {
 	unsigned char keycode[ARRAY_SIZE(htcpld_btns_map)];
-	char dpad[ARRAY_SIZE(htcpld_btns_map)];
-	char dkbd[ARRAY_SIZE(htcpld_btns_map)];	
+	char keymap[ARRAY_SIZE(htcpld_btns_map)];
+	char previous_keymap[ARRAY_SIZE(htcpld_btns_map)];
 	int irq;
 	int poll_interval;
 	struct input_dev *input;
 	struct timer_list timer;
 };
 
+struct platform_device *htcpld_pdev = NULL;
+void htcpld_btns_poll(unsigned long data);
+void htcpld_btns_dowork(struct work_struct *data);
+
+DECLARE_DELAYED_WORK(htcpld_work, &htcpld_btns_dowork);
+
 void htcpld_chip_set(u8 chip_addr, u8 val)
 {
 	struct htcpld_chip_data *chip_data;
@@ -185,73 +192,72 @@ static int htcpld_remove(struct i2c_client *client)
 
 static irqreturn_t htcpld_btns_irq(int irq, void *dev_id)
 {
-	struct platform_device *pdev = dev_id;
-	struct device *dev = &pdev->dev;
+	struct device *dev = &htcpld_pdev->dev;
 	struct htcpld_btns_dev *bdev = dev_get_drvdata(dev);
 	struct htcpld_btns_platform_data *pdata = dev->platform_data;
-	u8 dpad_status, dkbd_status;
-	int i;
-	
-	dkbd_status = _htcpld_chip_cache_read(htcpld_dkbd_chip);
-	dpad_status = _htcpld_chip_cache_read(htcpld_dpad_chip);
 
-	/* Send key press events */
-	for (i=0; i<ARRAY_SIZE(htcpld_btns_map); i++) {
-		if (!((dpad_status >> (7 - i)) & 0x01)) {
-			bdev->dpad[i] = 1;
-			input_report_key(bdev->input, bdev->keycode[i], 1);
-		}
-		if (!((dkbd_status >> (7 - i)) & 0x01)) {
-			bdev->dkbd[i] = 1;
-			input_report_key(bdev->input, bdev->keycode[i], 1);	
-		}
-	}
-	input_sync(bdev->input);
+	/* Disable further IRQs until we process the one we just got */
+	disable_irq(irq);
 
-	/* Poll for key release */
-	/* FIXME: We really should disable this irq before polling */
-	mod_timer(&bdev->timer, jiffies + pdata->poll_interval);
+	/* Set that an interrupt is pending so we can go process it */
+	schedule_delayed_work(&htcpld_work, 0);
 
 	return IRQ_HANDLED;
 }
 
-static void htcpld_btns_poll(unsigned long data)
+void htcpld_btns_dowork(struct work_struct *data) {
+	htcpld_btns_poll(0);
+}
+
+void htcpld_btns_poll(unsigned long data)
 {
-	struct platform_device *pdev = (struct platform_device *)data;
-	struct device *dev = &pdev->dev;
+	struct device *dev = &htcpld_pdev->dev;
 	struct htcpld_btns_dev *bdev = dev_get_drvdata(dev);
 	struct htcpld_btns_platform_data *pdata = dev->platform_data;
 	u8 dpad_status, dkbd_status;
 	int i, we_continue = 0;
-	
+
 	dkbd_status = _htcpld_chip_cache_read(htcpld_dkbd_chip);
 	dpad_status = _htcpld_chip_cache_read(htcpld_dpad_chip);
 
-	/* Send key release events */
+	/* Check for changes in the maps */
 	for (i=0; i<ARRAY_SIZE(htcpld_btns_map); i++) {
-		if (bdev->dpad[i]) {
-			if ((dpad_status >> (7 - i)) & 0x01) {
-				bdev->dpad[i] = 0;
-				input_report_key(bdev->input, bdev->keycode[i], 0);
-			} else {
-				we_continue = 1;
-			}
+		/* Key up by default*/
+		int val = 0;
+
+		/* If any key is down, send the keycode for down. */
+		if (!((dpad_status >> (7 - i)) & 0x01) || !((dkbd_status >> (7 - i)) & 0x01)) {
+			val = 1;
+			we_continue = 1;
 		}
-		if (bdev->dkbd[i]) {
-			if ((dkbd_status >> (7 - i)) & 0x01) {
-				bdev->dkbd[i] = 0;
-				input_report_key(bdev->input, bdev->keycode[i], 0);	
-			} else
-				we_continue = 1;
+
+		bdev->keymap[i] = val;
+	}
+
+	/* Send events */
+	for (i = 0; i < ARRAY_SIZE(htcpld_btns_map); i++) {
+		if (bdev->keymap[i] != bdev->previous_keymap[i]) {
+			input_report_key(bdev->input, bdev->keycode[i], bdev->keymap[i]);
+			bdev->previous_keymap[i] = bdev->keymap[i];
 		}
 	}
+
 	input_sync(bdev->input);
 
+	/* Copy the new state into the previous state */
+	memcpy(bdev->previous_keymap, bdev->keymap, sizeof(bdev->keymap));
+
 	if (we_continue) {
 		/* Poll again */
-		mod_timer(&bdev->timer, jiffies + pdata->poll_interval);
+		schedule_delayed_work(&htcpld_work, pdata->poll_interval);
 		return;
 	}
+
+	/* Enable front DPAD interrupts */
+	htcpld_chip_set(0x05, (htcpld_chip_get(0x05) | 0x80) & 0xfe);
+
+	/* Reenable the IRQ */
+	enable_irq(bdev->irq);
 }
 
 static int __devinit htcpld_btns_probe(struct platform_device *pdev)
@@ -306,18 +312,23 @@ static int __devinit htcpld_btns_probe(struct platform_device *pdev)
 	if (error)
 		goto err_free_mem;
 
-	setup_timer(&bdev->timer, htcpld_btns_poll, (unsigned long)pdev);
-
 	error = request_irq(bdev->irq, htcpld_btns_irq,
 	         IRQF_TRIGGER_FALLING | IRQF_SAMPLE_RANDOM, pdev->name, pdev);
 	if (error)
 		goto err_unreg;
 
+	htcpld_pdev = pdev;
+
+	memset(bdev->previous_keymap, 0, sizeof(bdev->keymap));
+
 	printk("i2c-htcpld: HTC I2C CPLD Buttons\n");
 	printk("i2c-htcpld: Using IRQ: %d\n", bdev->irq);
 	printk("i2c-htcpld: DKbd chip at 0x%x\n", htcpld_dkbd_chip->addr);
 	printk("i2c-htcpld: DPad chip at 0x%x\n", htcpld_dpad_chip->addr);
 
+	/* Enable front DPAD interrupts */
+	htcpld_chip_set(0x05, (htcpld_chip_get(0x05) | 0x80) & 0xfe);
+
 	return 0;
 
  err_unreg:
-- 
1.6.3.3

