diff options
author | Chethan Krishna N <chethan.krishna@stericsson.com> | 2011-08-30 15:28:29 +0530 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-05-22 11:03:34 +0200 |
commit | dc0c779a8f09e343841063d5f19d9a71b14be402 (patch) | |
tree | 2a138c313618269b5487ecd0e051e8ebefeb50ec | |
parent | adbf88539fcd5fab848b60c7053cec6c0052267c (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.h | 12 | ||||
-rw-r--r-- | drivers/input/keyboard/db5500_keypad.c | 308 |
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); } |