summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChethan Krishna N <chethan.krishna@stericsson.com>2011-08-30 15:28:29 +0530
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-05-22 11:03:34 +0200
commitdc0c779a8f09e343841063d5f19d9a71b14be402 (patch)
tree2a138c313618269b5487ecd0e051e8ebefeb50ec
parentadbf88539fcd5fab848b60c7053cec6c0052267c (diff)
db5500_keypad: handle regulator in apsleep
db5500_keypad doesn't block APSleep during CPU Idle proper implementation of regluator framework. Key presses are also detected using manual scan. ST-Ericsson ID: 342613 ST-Ericsson FOSS-OUT ID: NA ST-Ericsson Linux next : Not tested, NA Change-Id: I9e42c56f8f2f270ce0197b035222aa9d7fecdb70 Signed-off-by: Chethan Krishna N <chethan.krishna@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26050 Reviewed-by: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29724 Reviewed-by: Naga RADHESH Y <naga.radheshy@stericsson.com> Tested-by: Naga RADHESH Y <naga.radheshy@stericsson.com>
-rw-r--r--arch/arm/mach-ux500/include/mach/db5500-keypad.h12
-rw-r--r--drivers/input/keyboard/db5500_keypad.c308
2 files changed, 288 insertions, 32 deletions
diff --git a/arch/arm/mach-ux500/include/mach/db5500-keypad.h b/arch/arm/mach-ux500/include/mach/db5500-keypad.h
index 66b4c07f838..8db5a05017b 100644
--- a/arch/arm/mach-ux500/include/mach/db5500-keypad.h
+++ b/arch/arm/mach-ux500/include/mach/db5500-keypad.h
@@ -10,16 +10,28 @@
#include <linux/input/matrix_keypad.h>
+#define KEYPAD_MAX_ROWS 9
+#define KEYPAD_MAX_COLS 8
/**
* struct db5500_keypad_platform_data - structure for platform specific data
* @keymap_data: matrix scan code table for keycodes
* @debounce_ms: platform specific debounce time
* @no_autorepeat: flag for auto repetition
+ * @init : pointer to keypad init function
+ * @exit : pointer to keypad exit function
+ * @gpio_input_pins: pointer to gpio input pins
+ * @gpio_output_pins: pointer to gpio output pins
+ * @switch_delay : gpio switch_delay
*/
struct db5500_keypad_platform_data {
const struct matrix_keymap_data *keymap_data;
u8 debounce_ms;
bool no_autorepeat;
+ int (*init)(void);
+ int (*exit)(void);
+ int *gpio_input_pins;
+ int *gpio_output_pins;
+ int switch_delay;
};
#endif
diff --git a/drivers/input/keyboard/db5500_keypad.c b/drivers/input/keyboard/db5500_keypad.c
index 8ef1bd43b7f..61cda09cacf 100644
--- a/drivers/input/keyboard/db5500_keypad.c
+++ b/drivers/input/keyboard/db5500_keypad.c
@@ -15,6 +15,7 @@
#include <linux/delay.h>
#include <linux/clk.h>
#include <mach/db5500-keypad.h>
+#include <linux/regulator/consumer.h>
#define KEYPAD_CTR 0x0
#define KEYPAD_IRQ_CLEAR 0x4
@@ -35,12 +36,11 @@
#define KEYPAD_GND_ROW 8
-#define KEYPAD_MAX_ROWS 9
-#define KEYPAD_MAX_COLS 8
#define KEYPAD_ROW_SHIFT 3
#define KEYPAD_KEYMAP_SIZE \
(KEYPAD_MAX_ROWS * KEYPAD_MAX_COLS)
+#define KEY_PRESSED_DELAY 10
/**
* struct db5500_keypad - data structure used by keypad driver
* @irq: irq number
@@ -49,7 +49,17 @@
* @board: keypad platform data
* @keymap: matrix scan code table for keycodes
* @clk: clock structure pointer
+ * @regulator : regulator used by keypad
+ * @switch_work : delayed work variable for switching to gpio
+ * @gpio_work : delayed work variable for reporting key event in gpio mode
* @previous_set: previous set of registers
+ * @enable : flag to enable the driver event
+ * @valid_key : hold the state of valid key press
+ * @db5500_rows : rows gpio array for db5500 keypad
+ * @db5500_cols : cols gpio array for db5500 keypad
+ * @gpio_input_irq : array for gpio irqs
+ * @gpio_row : gpio row
+ * @gpio_col : gpio_col
*/
struct db5500_keypad {
int irq;
@@ -58,7 +68,17 @@ struct db5500_keypad {
const struct db5500_keypad_platform_data *board;
unsigned short keymap[KEYPAD_KEYMAP_SIZE];
struct clk *clk;
+ struct regulator *regulator;
+ struct delayed_work switch_work;
+ struct delayed_work gpio_work;
u8 previous_set[KEYPAD_MAX_ROWS];
+ bool enable;
+ bool valid_key;
+ int db5500_rows[KEYPAD_MAX_ROWS - 1];
+ int db5500_cols[KEYPAD_MAX_COLS];
+ int gpio_input_irq[KEYPAD_MAX_ROWS - 1];
+ int gpio_row;
+ int gpio_col;
};
/**
@@ -97,17 +117,8 @@ static void db5500_keypad_report(struct db5500_keypad *keypad, int row,
}
}
-/**
- * db5500_keypad_irq() - irq handler for keypad
- * @irq: irq value for keypad
- * @dev_id: pointer for device id
- *
- * This function uses to handle the interrupt of the keypad
- * and returns irqreturn.
- */
-static irqreturn_t db5500_keypad_irq(int irq, void *dev_id)
+static void db5500_keypad_scan(struct db5500_keypad *keypad)
{
- struct db5500_keypad *keypad = dev_id;
u8 current_set[ARRAY_SIZE(keypad->previous_set)];
int tries = 100;
bool changebit;
@@ -121,7 +132,7 @@ static irqreturn_t db5500_keypad_irq(int irq, void *dev_id)
again:
if (!tries--) {
dev_warn(&keypad->input->dev, "values failed to stabilize\n");
- return IRQ_HANDLED;
+ return;
}
changebit = readl(keypad->base + KEYPAD_ARRAY_01)
@@ -154,7 +165,7 @@ again:
common &= current_set[i];
if ((allrows & common) != common)
- return IRQ_HANDLED;
+ return;
for (i = 0; i < ARRAY_SIZE(current_set); i++) {
/*
@@ -174,7 +185,7 @@ again:
/* update the reference set of array registers */
memcpy(keypad->previous_set, current_set, sizeof(keypad->previous_set));
- return IRQ_HANDLED;
+ return;
}
/**
@@ -254,19 +265,172 @@ static int db5500_keypad_chip_init(struct db5500_keypad *keypad)
return 0;
}
-/**
- * db5500_keypad_close() - stops the keypad driver
- * @keypad: pointer to device structure
- *
- * This function uses to stop the keypad
- * driver and returns integer.
- */
-static void db5500_keypad_close(struct db5500_keypad *keypad)
+static void db5500_mode_enable(struct db5500_keypad *keypad, bool enable)
{
- db5500_keypad_writel(keypad, 0, KEYPAD_CTR);
- db5500_keypad_writel(keypad, 0, KEYPAD_INT_ENABLE);
+ int i;
- clk_disable(keypad->clk);
+ if (!enable) {
+ db5500_keypad_writel(keypad, 0, KEYPAD_CTR);
+ db5500_keypad_writel(keypad, 0, KEYPAD_INT_ENABLE);
+ if (keypad->board->exit)
+ keypad->board->exit();
+ for (i = 0; i < KEYPAD_MAX_ROWS - 1; i++) {
+ enable_irq(keypad->gpio_input_irq[i]);
+ enable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+ clk_disable(keypad->clk);
+ regulator_disable(keypad->regulator);
+ } else {
+ regulator_enable(keypad->regulator);
+ clk_enable(keypad->clk);
+ for (i = 0; i < KEYPAD_MAX_ROWS - 1; i++) {
+ disable_irq_nosync(keypad->gpio_input_irq[i]);
+ disable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+ if (keypad->board->init)
+ keypad->board->init();
+ db5500_keypad_chip_init(keypad);
+ }
+}
+
+static void db5500_gpio_switch_work(struct work_struct *work)
+{
+ struct db5500_keypad *keypad = container_of(work,
+ struct db5500_keypad, switch_work.work);
+
+ db5500_mode_enable(keypad, false);
+ keypad->enable = false;
+}
+
+static void db5500_gpio_release_work(struct work_struct *work)
+{
+ int code;
+ struct db5500_keypad *keypad = container_of(work,
+ struct db5500_keypad, gpio_work.work);
+ struct input_dev *input = keypad->input;
+
+ code = MATRIX_SCAN_CODE(keypad->gpio_col, keypad->gpio_row,
+ KEYPAD_ROW_SHIFT);
+ input_event(input, EV_MSC, MSC_SCAN, code);
+ input_report_key(input, keypad->keymap[code], 1);
+ input_sync(input);
+ input_report_key(input, keypad->keymap[code], 0);
+ input_sync(input);
+}
+
+static int db5500_read_get_gpio_row(struct db5500_keypad *keypad)
+{
+ int row;
+ int value = 0;
+ int ret;
+
+ /* read all rows GPIO data register values */
+ for (row = 0; row < KEYPAD_MAX_ROWS - 1; row++) {
+ ret = gpio_get_value(keypad->db5500_rows[row]);
+ value += (1 << row) * ret;
+ }
+
+ /* get the exact row */
+ for (row = 0; row < KEYPAD_MAX_ROWS - 1; row++) {
+ if (((1 << row) & value) == 0)
+ return row;
+ }
+
+ return -1;
+}
+
+static void db5500_set_cols(struct db5500_keypad *keypad, int col)
+{
+ int i ;
+ int value;
+
+ /*
+ * Set all columns except the requested column
+ * output pin as high
+ */
+ for (i = 0; i < KEYPAD_MAX_COLS; i++) {
+ if (i == col)
+ value = 0;
+ else
+ value = 1;
+ gpio_request(keypad->db5500_cols[i], "db5500-kpd");
+ gpio_direction_output(keypad->db5500_cols[i], value);
+ gpio_free(keypad->db5500_cols[i]);
+ }
+}
+
+static void db5500_free_cols(struct db5500_keypad *keypad)
+{
+ int i ;
+
+ for (i = 0; i < KEYPAD_MAX_COLS; i++) {
+ gpio_request(keypad->db5500_cols[i], "db5500-kpd");
+ gpio_direction_output(keypad->db5500_cols[i], 0);
+ gpio_free(keypad->db5500_cols[i]);
+ }
+}
+
+static void db5500_manual_scan(struct db5500_keypad *keypad)
+{
+ int row;
+ int col;
+
+ keypad->valid_key = false;
+
+ for (col = 0; col < KEYPAD_MAX_COLS; col++) {
+ db5500_set_cols(keypad, col);
+ row = db5500_read_get_gpio_row(keypad);
+ if (row >= 0) {
+ keypad->valid_key = true;
+ keypad->gpio_row = row;
+ keypad->gpio_col = col;
+ break;
+ }
+ }
+ db5500_free_cols(keypad);
+}
+
+static irqreturn_t db5500_keypad_gpio_irq(int irq, void *dev_id)
+{
+ struct db5500_keypad *keypad = dev_id;
+
+ if (!gpio_get_value(IRQ_TO_GPIO(irq))) {
+ db5500_manual_scan(keypad);
+ if (!keypad->enable) {
+ keypad->enable = true;
+ db5500_mode_enable(keypad, true);
+ }
+
+ /*
+ * Schedule the work queue to change it to
+ * report the key pressed, if it is not detected in keypad mode.
+ */
+ if (keypad->valid_key) {
+ schedule_delayed_work(&keypad->gpio_work,
+ KEY_PRESSED_DELAY);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t db5500_keypad_irq(int irq, void *dev_id)
+{
+ struct db5500_keypad *keypad = dev_id;
+
+ cancel_delayed_work_sync(&keypad->gpio_work);
+ cancel_delayed_work_sync(&keypad->switch_work);
+ db5500_keypad_scan(keypad);
+
+ /*
+ * Schedule the work queue to change it to
+ * GPIO mode, if there is no activity in keypad mode
+ */
+ if (keypad->enable)
+ schedule_delayed_work(&keypad->switch_work,
+ keypad->board->switch_delay);
+
+ return IRQ_HANDLED;
}
/**
@@ -278,7 +442,7 @@ static void db5500_keypad_close(struct db5500_keypad *keypad)
*/
static int __devinit db5500_keypad_probe(struct platform_device *pdev)
{
- const struct db5500_keypad_platform_data *plat;
+ struct db5500_keypad_platform_data *plat;
struct db5500_keypad *keypad;
struct resource *res;
struct input_dev *input;
@@ -286,6 +450,7 @@ static int __devinit db5500_keypad_probe(struct platform_device *pdev)
struct clk *clk;
int ret;
int irq;
+ int i;
plat = pdev->dev.platform_data;
if (!plat) {
@@ -343,6 +508,20 @@ static int __devinit db5500_keypad_probe(struct platform_device *pdev)
goto out_freekeypad;
}
+ keypad->regulator = regulator_get(&pdev->dev, "v-ape");
+ if (IS_ERR(keypad->regulator)) {
+ dev_err(&pdev->dev, "regulator_get failed\n");
+ keypad->regulator = NULL;
+ ret = -EINVAL;
+ goto out_regulator_get;
+ } else {
+ ret = regulator_enable(keypad->regulator);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "regulator_enable failed\n");
+ goto out_regulator_enable;
+ }
+ }
+
input->id.bustype = BUS_HOST;
input->name = "db5500-keypad";
input->dev.parent = &pdev->dev;
@@ -373,12 +552,56 @@ static int __devinit db5500_keypad_probe(struct platform_device *pdev)
keypad->base = base;
keypad->clk = clk;
- ret = db5500_keypad_chip_init(keypad);
- if (ret < 0) {
- dev_err(&pdev->dev, "unable to init keypad hardware\n");
+ INIT_DELAYED_WORK(&keypad->switch_work, db5500_gpio_switch_work);
+ INIT_DELAYED_WORK(&keypad->gpio_work, db5500_gpio_release_work);
+
+ clk_enable(keypad->clk);
+if (!keypad->board->init) {
+ dev_err(&pdev->dev, "init funtion not defined\n");
+ ret = -EINVAL;
goto out_unregisterinput;
}
+ if (keypad->board->init() < 0) {
+ dev_err(&pdev->dev, "keyboard init config failed\n");
+ ret = -EINVAL;
+ goto out_unregisterinput;
+ }
+
+ if (!keypad->board->exit) {
+ dev_err(&pdev->dev, "exit funtion not defined\n");
+ ret = -EINVAL;
+ goto out_unregisterinput;
+ }
+
+ if (keypad->board->exit() < 0) {
+ dev_err(&pdev->dev, "keyboard exit config failed\n");
+ ret = -EINVAL;
+ goto out_unregisterinput;
+ }
+
+ for (i = 0; i < KEYPAD_MAX_ROWS - 1; i++) {
+ keypad->db5500_rows[i] = *plat->gpio_input_pins;
+ keypad->db5500_cols[i] = *plat->gpio_output_pins;
+ keypad->gpio_input_irq[i] =
+ GPIO_TO_IRQ(keypad->db5500_rows[i]);
+ plat->gpio_input_pins++;
+ plat->gpio_output_pins++;
+ }
+
+ for (i = 0; i < KEYPAD_MAX_ROWS - 1; i++) {
+ ret = request_threaded_irq(keypad->gpio_input_irq[i],
+ NULL, db5500_keypad_gpio_irq,
+ IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND,
+ "db5500-keypad-gpio", keypad);
+ if (ret) {
+ dev_err(&pdev->dev, "allocate gpio irq %d failed\n",
+ keypad->gpio_input_irq[i]);
+ goto out_unregisterinput;
+ }
+ enable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+
ret = request_threaded_irq(keypad->irq, NULL, db5500_keypad_irq,
IRQF_ONESHOT, "db5500-keypad", keypad);
if (ret) {
@@ -388,6 +611,8 @@ static int __devinit db5500_keypad_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, keypad);
+ clk_disable(keypad->clk);
+ regulator_disable(keypad->regulator);
return 0;
out_unregisterinput:
@@ -396,6 +621,10 @@ out_unregisterinput:
clk_disable(keypad->clk);
out_freeinput:
input_free_device(input);
+out_regulator_enable:
+ regulator_put(keypad->regulator);
+out_regulator_get:
+ input_free_device(input);
out_freekeypad:
kfree(keypad);
out_freeclk:
@@ -420,12 +649,19 @@ static int __devexit db5500_keypad_remove(struct platform_device *pdev)
struct db5500_keypad *keypad = platform_get_drvdata(pdev);
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ cancel_delayed_work_sync(&keypad->gpio_work);
+ cancel_delayed_work_sync(&keypad->switch_work);
free_irq(keypad->irq, keypad);
input_unregister_device(keypad->input);
clk_disable(keypad->clk);
clk_put(keypad->clk);
+ if (keypad->board->exit)
+ keypad->board->exit();
+
+ regulator_put(keypad->regulator);
+
iounmap(keypad->base);
if (res)
@@ -453,8 +689,13 @@ static int db5500_keypad_suspend(struct device *dev)
if (device_may_wakeup(dev))
enable_irq_wake(irq);
else {
+ cancel_delayed_work_sync(&keypad->gpio_work);
+ cancel_delayed_work_sync(&keypad->switch_work);
disable_irq(irq);
- db5500_keypad_close(keypad);
+ if (keypad->enable) {
+ db5500_mode_enable(keypad, false);
+ keypad->enable = false;
+ }
}
return 0;
@@ -476,7 +717,10 @@ static int db5500_keypad_resume(struct device *dev)
if (device_may_wakeup(dev))
disable_irq_wake(irq);
else {
- db5500_keypad_chip_init(keypad);
+ if (!keypad->enable) {
+ keypad->enable = true;
+ db5500_mode_enable(keypad, true);
+ }
enable_irq(irq);
}