From patchwork Sun May 13 12:44:17 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Walleij X-Patchwork-Id: 8575 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 45F8D23EAB for ; Sun, 13 May 2012 12:44:28 +0000 (UTC) Received: from mail-yw0-f52.google.com (mail-yw0-f52.google.com [209.85.213.52]) by fiordland.canonical.com (Postfix) with ESMTP id EBF7AA187DD for ; Sun, 13 May 2012 12:44:27 +0000 (UTC) Received: by mail-yw0-f52.google.com with SMTP id p61so4613135yhp.11 for ; Sun, 13 May 2012 05:44:27 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf:from:to:cc :subject:date:message-id:x-mailer:mime-version:content-type :x-gm-message-state; bh=AwiTRSvslH458uJxiBkMsTDuqrQ5jdYiNS/dn/Et+/k=; b=LmC55Ji2vehFwQiHKMVlcEQhDT60mEFE7BNWjeuUREFMF7bZrVcvYWZ3g5A/kMNMVz P1QzBA9cyK/QJMpT+6s1w9AFFZcusEAKc0I8SSFcMc2hCbD9xxvlNY4BIpxN6/M5+p2j FXnNwwLPbPXedPLJBUuCHjxq7MOsuUCwwrLXdC5vQ3mgY1cD0ltemGoXuljtEAcHdOGJ MNYirB6Y2ySIU4WIXlAVOzHAV8LxlXBLR7327tdsBheYFg2KwuObqEZDVuiuf6NrNjY9 6pv/CIbxioK75HqxA4RdhT3j3JB6Wt5hRmxNRQIGcbZMMokjP2iA01yfXy9Tap0CQqYI 3pJw== Received: by 10.42.130.7 with SMTP id t7mr2045243ics.43.1336913067560; Sun, 13 May 2012 05:44:27 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.73.147 with SMTP id q19csp286744ibj; Sun, 13 May 2012 05:44:26 -0700 (PDT) Received: by 10.14.98.198 with SMTP id v46mr798781eef.36.1336913066142; Sun, 13 May 2012 05:44:26 -0700 (PDT) Received: from eu1sys200aog115.obsmtp.com (eu1sys200aog115.obsmtp.com. [207.126.144.139]) by mx.google.com with SMTP id h55si4580445eea.131.2012.05.13.05.44.23 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 13 May 2012 05:44:26 -0700 (PDT) Received-SPF: neutral (google.com: 207.126.144.139 is neither permitted nor denied by best guess record for domain of linus.walleij@stericsson.com) client-ip=207.126.144.139; Authentication-Results: mx.google.com; spf=neutral (google.com: 207.126.144.139 is neither permitted nor denied by best guess record for domain of linus.walleij@stericsson.com) smtp.mail=linus.walleij@stericsson.com Received: from beta.dmz-us.st.com ([167.4.1.35]) (using TLSv1) by eu1sys200aob115.postini.com ([207.126.147.11]) with SMTP ID DSNKT6+sp3y+AnVUuTKgCQRpBe9mto2f9JUW@postini.com; Sun, 13 May 2012 12:44:25 UTC Received: from zeta.dmz-us.st.com (ns4.st.com [167.4.16.71]) by beta.dmz-us.st.com (STMicroelectronics) with ESMTP id CEDB048; Sun, 13 May 2012 12:44:04 +0000 (GMT) Received: from relay2.stm.gmessaging.net (unknown [10.230.100.18]) by zeta.dmz-us.st.com (STMicroelectronics) with ESMTP id 72CC248; Sun, 13 May 2012 10:19:24 +0000 (GMT) Received: from exdcvycastm003.EQ1STM.local (alteon-source-exch [10.230.100.61]) (using TLSv1 with cipher RC4-MD5 (128/128 bits)) (Client CN "exdcvycastm003", Issuer "exdcvycastm003" (not verified)) by relay2.stm.gmessaging.net (Postfix) with ESMTPS id C8917A807B; Sun, 13 May 2012 14:44:15 +0200 (CEST) Received: from steludxu4075.lud.stericsson.com (10.230.100.153) by smtp.stericsson.com (10.230.100.1) with Microsoft SMTP Server (TLS) id 8.3.83.0; Sun, 13 May 2012 14:44:20 +0200 From: Linus Walleij To: Dmitry Torokhov Cc: , Karl-Johan Perntz , Linus Walleij Subject: [PATCH 6/7] input/nomadik-ske: add sleepy GPIO mode Date: Sun, 13 May 2012 14:44:17 +0200 Message-ID: <1336913057-10189-1-git-send-email-linus.walleij@stericsson.com> X-Mailer: git-send-email 1.7.9.2 MIME-Version: 1.0 X-Gm-Message-State: ALoCoQmukCGexM2qHddqReB4iYI2XOyXITJ2Fsrxv1QIanw/kHvwF2sDkRFL0D5sXl+HyK4R5RCt From: Karl-Johan Perntz The pins used by the SKE keypad matrix may also be used as GPIO pins. By cleverly switching the block between common active mode and GPIO mode, we may save power. This patch makes it possible to supply and two arrays of GPIO pins corresponding to the rows and columns of the SKE, and will make the driver switch to just waiting for something to happen on the GPIO lines when nothing is happening on the keypad. Whenever something happens, causing a GPIO interrupt, the driver switches back to SKE mode and enables the matrix keypad hardware again. We have found that this way of doing things makes the SKE consume considerably less power. Signed-off-by: Karl-Johan Perntz Signed-off-by: Linus Walleij --- arch/arm/plat-nomadik/include/plat/ske.h | 20 +- drivers/input/keyboard/nomadik-ske-keypad.c | 261 +++++++++++++++++++++++++-- 2 files changed, 265 insertions(+), 16 deletions(-) diff --git a/arch/arm/plat-nomadik/include/plat/ske.h b/arch/arm/plat-nomadik/include/plat/ske.h index d6002dd..f5e0ff8 100644 --- a/arch/arm/plat-nomadik/include/plat/ske.h +++ b/arch/arm/plat-nomadik/include/plat/ske.h @@ -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*/ diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 86f7d41..d2cb4df 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -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;