@@ -34,20 +34,30 @@
* @init: pointer to keypad init function
* @exit: pointer to keypad deinitialisation function
* @keymap_data: matrix scan code table for keycodes
- * @krow: maximum number of rows
- * @kcol: maximum number of columns
* @debounce_ms: platform specific debounce time
* @no_autorepeat: flag for auto repetition
- * @wakeup_enable: allow waking up the system
+ * @wakeup_enable: allow waking up the system from the SKE block IRQ
+ * @gpio_wakeup_enable: allow waking up the system using GPIO interrups on the
+ * SKE pins - mutually exclusive with wakeup_enable, which will be
+ * disabled
+ * @gpio_switch_delay: gpio switch_delay
+ * @gpio_rows: pointer to gpio input pins (rows)
+ * @gpio_cols: pointer to gpio output pins (columns)
+ * @krow: maximum number of rows
+ * @kcol: maximum number of columns
*/
struct ske_keypad_platform_data {
int (*init)(void);
int (*exit)(void);
const struct matrix_keymap_data *keymap_data;
- u8 krow;
- u8 kcol;
u8 debounce_ms;
bool no_autorepeat;
bool wakeup_enable;
+ bool gpio_wakeup_enable;
+ int gpio_switch_delay;
+ int *gpio_rows;
+ int *gpio_cols;
+ u8 krow;
+ u8 kcol;
};
#endif /*__SKE_KPD_H*/
@@ -20,6 +20,7 @@
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/module.h>
+#include <linux/gpio.h>
#include <plat/ske.h>
@@ -65,7 +66,15 @@
* @keymap: matrix scan code table for keycodes
* @clk: clock structure pointer
* @ske_keypad_lock: lock used while writting into registers
+ * @enable: flag to enable the SKE hardware
+ * @enable_on_resume: set if keypad should be enabled on resume
* @key_pressed: hold the key state
+ * @gpio_wakeup: if we should deactivate hardware and wake on IRQ
+ * @gpio_input_irq: array for gpio irqs
+ * @gpio_switch_work: delayed work variable for gpio switch
+ * @gpio_release_work: delayed work variable for release gpio key
+ * @gpio_row: gpio row
+ * @gpio_col: gpio column
* @keys: matrix holding key status
* @scan_work: delayed work for scaning new key actions
*/
@@ -78,7 +87,15 @@ struct ske_keypad {
unsigned short keymap[SKE_KPD_KEYMAP_SIZE];
struct clk *clk;
spinlock_t ske_keypad_lock;
+ bool enable;
+ bool enable_on_resume;
int key_pressed;
+ bool gpio_wakeup;
+ struct delayed_work gpio_switch_work;
+ struct delayed_work gpio_release_work;
+ int gpio_input_irq[SKE_KPD_MAX_ROWS];
+ int gpio_row;
+ int gpio_col;
u8 keys[SKE_KPD_MAX_ROWS][SKE_KPD_MAX_COLS];
struct delayed_work scan_work;
};
@@ -149,6 +166,33 @@ static int __init ske_keypad_chip_init(struct ske_keypad *keypad)
return 0;
}
+static void ske_mode_enable(struct ske_keypad *keypad, bool enable)
+{
+ int i;
+
+ if (!enable) {
+ dev_dbg(keypad->dev, "%s disable keypad\n", __func__);
+ writel(0, keypad->reg_base + SKE_CR);
+ if (keypad->board->exit)
+ keypad->board->exit();
+ for (i = 0; i < keypad->board->krow; i++) {
+ enable_irq(keypad->gpio_input_irq[i]);
+ enable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+ clk_disable(keypad->clk);
+ } else {
+ dev_dbg(keypad->dev, "%s enable keypad\n", __func__);
+ clk_enable(keypad->clk);
+ for (i = 0; i < keypad->board->krow; i++) {
+ disable_irq_nosync(keypad->gpio_input_irq[i]);
+ disable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+ if (keypad->board->init)
+ keypad->board->init();
+ ske_keypad_chip_init(keypad);
+ }
+}
+
static void ske_keypad_report(struct ske_keypad *keypad, u8 status, int col)
{
int row = 0, code, pos;
@@ -292,12 +336,154 @@ static void ske_keypad_scan_work(struct work_struct *work)
/* Enable auto scan interrupts */
ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA);
+
+ /**
+ * Schedule the work queue to change it to GPIO mode
+ * if there is no activity in SKE mode
+ */
+ if (keypad->gpio_wakeup &&
+ !keypad->key_pressed &&
+ keypad->enable)
+ schedule_delayed_work(&keypad->gpio_switch_work,
+ keypad->board->gpio_switch_delay);
+ }
+}
+
+static void ske_gpio_switch_work(struct work_struct *work)
+{
+ struct ske_keypad *keypad = container_of(work,
+ struct ske_keypad,
+ gpio_switch_work.work);
+
+ ske_mode_enable(keypad, false);
+ keypad->enable = false;
+}
+
+static void ske_gpio_release_work(struct work_struct *work)
+{
+ int code;
+ struct ske_keypad *keypad = container_of(work,
+ struct ske_keypad,
+ gpio_release_work.work);
+ struct input_dev *input = keypad->input;
+
+ code = MATRIX_SCAN_CODE(keypad->gpio_row, keypad->gpio_col,
+ SKE_KEYPAD_ROW_SHIFT);
+
+ dev_dbg(keypad->dev, "%s Key press reported, code:%d\n",
+ __func__, code);
+
+ 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 ske_read_get_gpio_row(struct ske_keypad *keypad)
+{
+ int row;
+ int value = 0;
+ int ret;
+
+ /* read all rows GPIO data register values */
+ for (row = 0; row < keypad->board->krow ; row++) {
+ ret = gpio_get_value(keypad->board->gpio_rows[row]);
+ value += (1 << row) * ret;
+ }
+
+ /* get the exact row */
+ for (row = 0; row < keypad->board->krow; row++) {
+ if (((1 << row) & value) == 0)
+ return row;
+ }
+
+ return -1;
+}
+
+static void ske_set_cols(struct ske_keypad *keypad, int col)
+{
+ int i ;
+ int value;
+
+ /**
+ * Set all columns except the requested column
+ * output pin as high
+ */
+ for (i = 0; i < keypad->board->krow; i++) {
+ if (i == col)
+ value = 0;
+ else
+ value = 1;
+ gpio_request(keypad->board->gpio_cols[i], "ske-kp");
+ gpio_direction_output(keypad->board->gpio_cols[i],
+ value);
+ gpio_free(keypad->board->gpio_cols[i]);
}
}
+static void ske_free_cols(struct ske_keypad *keypad)
+{
+ int i ;
+
+ for (i = 0; i < keypad->board->kcol; i++) {
+ gpio_request(keypad->board->gpio_cols[i], "ske-kp");
+ gpio_direction_output(keypad->board->gpio_cols[i], 0);
+ gpio_free(keypad->board->gpio_cols[i]);
+ }
+}
+
+static void ske_gpio_scan(struct ske_keypad *keypad)
+{
+ int row;
+ int col;
+
+ for (col = 0; col < keypad->board->kcol; col++) {
+ ske_set_cols(keypad, col);
+ row = ske_read_get_gpio_row(keypad);
+ if (row >= 0) {
+ keypad->key_pressed = 1;
+ keypad->gpio_row = row;
+ keypad->gpio_col = col;
+ break;
+ }
+ }
+ ske_free_cols(keypad);
+}
+
+static irqreturn_t ske_keypad_gpio_irq(int irq, void *dev_id)
+{
+ struct ske_keypad *keypad = dev_id;
+
+ if (!gpio_get_value(NOMADIK_IRQ_TO_GPIO(irq))) {
+ ske_gpio_scan(keypad);
+ if (!keypad->enable) {
+ keypad->enable = true;
+ ske_mode_enable(keypad, true);
+ /*
+ * Schedule the work queue to change it back to GPIO
+ * mode if there is no activity in SKE mode
+ */
+ schedule_delayed_work(&keypad->gpio_switch_work,
+ keypad->board->gpio_switch_delay);
+ }
+ /*
+ * Schedule delayed work to report key press if it is not
+ * detected in SKE mode.
+ */
+ if (keypad->key_pressed)
+ schedule_delayed_work(&keypad->gpio_release_work,
+ KEY_PRESSED_DELAY);
+ }
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t ske_keypad_irq(int irq, void *dev_id)
{
struct ske_keypad *keypad = dev_id;
+ cancel_delayed_work_sync(&keypad->gpio_release_work);
+ cancel_delayed_work_sync(&keypad->gpio_switch_work);
/* disable auto scan interrupt; mask the interrupt generated */
ske_keypad_set_bits(keypad, SKE_IMSC, SKE_KPIMA, 0x0);
@@ -318,6 +504,7 @@ static int __init ske_keypad_probe(struct platform_device *pdev)
void __iomem *reg_base;
int ret = 0;
int irq;
+ int i;
if (!plat) {
dev_err(&pdev->dev, "invalid keypad platform data\n");
@@ -401,6 +588,8 @@ static int __init ske_keypad_probe(struct platform_device *pdev)
keypad->input = input;
keypad->reg_base = reg_base;
keypad->clk = clk;
+ INIT_DELAYED_WORK(&keypad->gpio_switch_work, ske_gpio_switch_work);
+ INIT_DELAYED_WORK(&keypad->gpio_release_work, ske_gpio_release_work);
INIT_DELAYED_WORK(&keypad->scan_work, ske_keypad_scan_work);
/* allocations are sane, we begin HW initialization */
@@ -412,6 +601,32 @@ static int __init ske_keypad_probe(struct platform_device *pdev)
goto out_unregisterinput;
}
+ if (plat->wakeup_enable) {
+ device_init_wakeup(&pdev->dev, true);
+ if (plat->gpio_wakeup_enable)
+ dev_warn(&pdev->dev, "both SKE block wakeup and "
+ "GPIO wakeup enabled - GPIO wakeup will "
+ "be ignored!\n");
+ } else if (plat->gpio_wakeup_enable) {
+ for (i = 0; i < keypad->board->krow; i++) {
+ keypad->gpio_input_irq[i] =
+ gpio_to_irq(keypad->board->gpio_rows[i]);
+ ret = request_threaded_irq(keypad->gpio_input_irq[i],
+ NULL, ske_keypad_gpio_irq,
+ IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND,
+ "ske-keypad-gpio", keypad);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "allocate gpio irq %d failed\n",
+ keypad->gpio_input_irq[i]);
+ goto no_wakeup;
+ }
+ enable_irq_wake(keypad->gpio_input_irq[i]);
+ }
+ keypad->gpio_wakeup = true;
+ }
+
+no_wakeup:
ret = request_irq(keypad->irq, ske_keypad_irq, 0,
"ske-keypad", keypad);
if (ret) {
@@ -419,9 +634,6 @@ static int __init ske_keypad_probe(struct platform_device *pdev)
goto out_unregisterinput;
}
- if (plat->wakeup_enable)
- device_init_wakeup(&pdev->dev, true);
-
platform_set_drvdata(pdev, keypad);
return 0;
@@ -447,15 +659,26 @@ static int __devexit ske_keypad_remove(struct platform_device *pdev)
{
struct ske_keypad *keypad = platform_get_drvdata(pdev);
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int i;
+ cancel_delayed_work_sync(&keypad->gpio_release_work);
+ cancel_delayed_work_sync(&keypad->gpio_switch_work);
cancel_delayed_work_sync(&keypad->scan_work);
input_unregister_device(keypad->input);
- clk_disable(keypad->clk);
+ if (keypad->enable)
+ clk_disable(keypad->clk);
clk_put(keypad->clk);
- if (keypad->board->exit)
+ if (keypad->enable && keypad->board->exit)
keypad->board->exit();
+
+ for (i = 0; i < keypad->board->krow; i++) {
+ disable_irq_nosync(keypad->gpio_input_irq[i]);
+ disable_irq_wake(keypad->gpio_input_irq[i]);
+ free_irq(keypad->gpio_input_irq[i], keypad);
+ }
+
free_irq(keypad->irq, keypad);
iounmap(keypad->reg_base);
release_mem_region(res->start, resource_size(res));
@@ -473,11 +696,18 @@ static int ske_keypad_suspend(struct device *dev)
if (device_may_wakeup(dev))
enable_irq_wake(irq);
- else {
+ else if (keypad->gpio_wakeup) {
+ cancel_delayed_work_sync(&keypad->gpio_release_work);
+ cancel_delayed_work_sync(&keypad->gpio_switch_work);
cancel_delayed_work_sync(&keypad->scan_work);
disable_irq(irq);
- ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0);
- clk_disable(keypad->clk);
+
+ keypad->enable_on_resume = keypad->enable;
+
+ if (keypad->enable) {
+ ske_mode_enable(keypad, false);
+ keypad->enable = false;
+ }
}
return 0;
@@ -491,10 +721,19 @@ static int ske_keypad_resume(struct device *dev)
if (device_may_wakeup(dev))
disable_irq_wake(irq);
- else {
- clk_enable(keypad->clk);
+ else if (keypad->gpio_wakeup) {
+ if (keypad->enable_on_resume && !keypad->enable) {
+ keypad->enable = true;
+ ske_mode_enable(keypad, true);
+ /*
+ * Schedule the work queue to change it to GPIO mode
+ * if there is no activity in SKE mode
+ */
+ if (!keypad->key_pressed)
+ schedule_delayed_work(&keypad->gpio_switch_work,
+ keypad->board->gpio_switch_delay);
+ }
enable_irq(irq);
- ske_keypad_chip_init(keypad);
}
return 0;