From: Trilok Soni <ts...@codeaurora.org>

Add support for PMIC8058 power key driven over dedicated
KYPD_PWR_N pin. It allows the user to specify the amount
of time by which the power key reporting can be delayed.

Cc: Dmitry Torokhov <dmitry.torok...@gmail.com>
Signed-off-by: Trilok Soni <ts...@codeaurora.org>
---
Changes from v1:
Addressed review comments from Dmitry.
Added KEY_SCREENLOCK instead of KEY_END.
Moved to timer list instead of hires timers.

 drivers/input/misc/Kconfig            |   11 ++
 drivers/input/misc/Makefile           |    1 +
 drivers/input/misc/pmic8058-pwrkey.c  |  318 +++++++++++++++++++++++++++++++++
 include/linux/input/pmic8058-pwrkey.h |   37 ++++
 4 files changed, 367 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/pmic8058-pwrkey.c
 create mode 100644 include/linux/input/pmic8058-pwrkey.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index b0c6772..d70a7be 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -330,6 +330,17 @@ config INPUT_PWM_BEEPER
          To compile this driver as a module, choose M here: the module will be
          called pwm-beeper.
 
+config INPUT_PMIC8058_PWRKEY
+       tristate "PMIC8058 power key support"
+       depends on PMIC8058
+       help
+         Say Y here if you want support for the PMIC8058 power key.
+
+         If unsure, say N.
+
+         To compile this driver as a module, choose M here: the
+         module will be called pmic8058-pwrkey.
+
 config INPUT_GPIO_ROTARY_ENCODER
        tristate "Rotary encoders connected to GPIO pins"
        depends on GPIOLIB && GENERIC_GPIO
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9b47971..0348704 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_INPUT_PCF8574)           += pcf8574_keypad.o
 obj-$(CONFIG_INPUT_PCSPKR)             += pcspkr.o
 obj-$(CONFIG_INPUT_POWERMATE)          += powermate.o
 obj-$(CONFIG_INPUT_PWM_BEEPER)         += pwm-beeper.o
+obj-$(CONFIG_INPUT_PMIC8058_PWRKEY)    += pmic8058-pwrkey.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)       += rb532_button.o
 obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER)        += rotary_encoder.o
 obj-$(CONFIG_INPUT_SGI_BTNS)           += sgi_btns.o
diff --git a/drivers/input/misc/pmic8058-pwrkey.c 
b/drivers/input/misc/pmic8058-pwrkey.c
new file mode 100644
index 0000000..029483e
--- /dev/null
+++ b/drivers/input/misc/pmic8058-pwrkey.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/log2.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#include <linux/input/pmic8058-pwrkey.h>
+
+#define PON_CNTL_1     0x1C
+#define PON_CNTL_PULL_UP BIT(7)
+#define PON_CNTL_TRIG_DELAY_MASK (0x7)
+
+/**
+ * struct pmic8058_pwrkey - pmic8058 pwrkey information
+ * @key_press_irq: key press irq number
+ * @pm_chip: pmic8058 parent
+ * @timer: timer for end key simulation
+ * @key_pressed: flag to keep track for power key reporting
+ * @pdata: platform data
+ * @lock:  protect key press update and end key simulation
+ */
+struct pmic8058_pwrkey {
+       struct input_dev *pwr;
+       int key_press_irq;
+       struct pm8058_chip      *pm_chip;
+       struct timer_list timer;
+       bool key_pressed;
+       const struct pmic8058_pwrkey_pdata *pdata;
+       spinlock_t lock;
+};
+
+static void pmic8058_pwrkey_timer(unsigned long handle)
+{
+       unsigned long flags;
+       struct pmic8058_pwrkey *pwrkey = (struct pmic8058_pwrkey *)handle;
+
+       spin_lock_irqsave(&pwrkey->lock, flags);
+       pwrkey->key_pressed = true;
+
+       input_report_key(pwrkey->pwr, KEY_POWER, 1);
+       input_sync(pwrkey->pwr);
+       spin_unlock_irqrestore(&pwrkey->lock, flags);
+}
+
+static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
+{
+       struct pmic8058_pwrkey *pwrkey = _pwrkey;
+       const struct pmic8058_pwrkey_pdata *pdata = pwrkey->pdata;
+       unsigned long flags;
+
+       /* no pwrkey time duration, means no end key simulation */
+       if (!pwrkey->pdata->pwrkey_time_ms) {
+               input_report_key(pwrkey->pwr, KEY_POWER, 1);
+               input_sync(pwrkey->pwr);
+               return IRQ_HANDLED;
+       }
+
+       spin_lock_irqsave(&pwrkey->lock, flags);
+
+       input_report_key(pwrkey->pwr, KEY_SCROLLLOCK, 1);
+       input_sync(pwrkey->pwr);
+
+       mod_timer(&pwrkey->timer, jiffies +
+                        msecs_to_jiffies(pdata->pwrkey_time_ms));
+       spin_unlock_irqrestore(&pwrkey->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t pwrkey_release_irq(int irq, void *_pwrkey)
+{
+       struct pmic8058_pwrkey *pwrkey = _pwrkey;
+       unsigned long flags;
+
+       /* no pwrkey time, means no delay in pwr key reporting */
+       if (!pwrkey->pdata->pwrkey_time_ms) {
+               input_report_key(pwrkey->pwr, KEY_POWER, 0);
+               input_sync(pwrkey->pwr);
+               return IRQ_HANDLED;
+       }
+
+       spin_lock_irqsave(&pwrkey->lock, flags);
+       del_timer_sync(&pwrkey->timer);
+
+       if (pwrkey->key_pressed) {
+               pwrkey->key_pressed = false;
+               input_report_key(pwrkey->pwr, KEY_POWER, 0);
+               input_sync(pwrkey->pwr);
+       }
+
+       input_report_key(pwrkey->pwr, KEY_SCREENLOCK, 0);
+       input_sync(pwrkey->pwr);
+
+       spin_unlock_irqrestore(&pwrkey->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int pmic8058_pwrkey_suspend(struct device *dev)
+{
+       struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+       if (device_may_wakeup(dev))
+               enable_irq_wake(pwrkey->key_press_irq);
+
+       return 0;
+}
+
+static int pmic8058_pwrkey_resume(struct device *dev)
+{
+       struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+       if (device_may_wakeup(dev))
+               disable_irq_wake(pwrkey->key_press_irq);
+
+       return 0;
+}
+
+static const struct dev_pm_ops pm8058_pwr_key_pm_ops = {
+       .suspend        = pmic8058_pwrkey_suspend,
+       .resume         = pmic8058_pwrkey_resume,
+};
+#endif
+
+static int __devinit pmic8058_pwrkey_probe(struct platform_device *pdev)
+{
+       struct input_dev *pwr;
+       int key_release_irq = platform_get_irq(pdev, 0);
+       int key_press_irq = platform_get_irq(pdev, 1);
+       int err;
+       unsigned int delay;
+       u8 pon_cntl;
+       struct pmic8058_pwrkey *pwrkey;
+       struct pmic8058_pwrkey_pdata *pdata = pdev->dev.platform_data;
+       struct pm8058_chip      *pm_chip;
+
+       pm_chip = platform_get_drvdata(pdev);
+       if (pm_chip == NULL) {
+               dev_err(&pdev->dev, "no parent data passed in\n");
+               return -EINVAL;
+       }
+
+       if (!pdata) {
+               dev_err(&pdev->dev, "power key platform data not supplied\n");
+               return -EINVAL;
+       }
+
+       if (pdata->kpd_trigger_delay_us > 62500) {
+               dev_err(&pdev->dev, "invalid pwr key trigger delay\n");
+               return -EINVAL;
+       }
+
+       if (pdata->pwrkey_time_ms &&
+            (pdata->pwrkey_time_ms < 500 || pdata->pwrkey_time_ms > 1000)) {
+               dev_err(&pdev->dev, "invalid pwr key time supplied\n");
+               return -EINVAL;
+       }
+
+       pwrkey = kzalloc(sizeof(*pwrkey), GFP_KERNEL);
+       if (!pwrkey)
+               return -ENOMEM;
+
+       pwrkey->pm_chip = pm_chip;
+       pwrkey->pdata   = pdata;
+
+       pwr = input_allocate_device();
+       if (!pwr) {
+               dev_dbg(&pdev->dev, "Can't allocate power button\n");
+               err = -ENOMEM;
+               goto free_pwrkey;
+       }
+
+       input_set_capability(pwr, EV_KEY, KEY_POWER);
+       input_set_capability(pwr, EV_KEY, KEY_SCROLLLOCK);
+
+       pwr->name = "pmic8058_pwrkey";
+       pwr->phys = "pmic8058_pwrkey/input0";
+       pwr->dev.parent = &pdev->dev;
+
+       delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC;
+       delay = 1 + ilog2(delay);
+
+       err = pm8058_read(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err);
+               goto free_input_dev;
+       }
+
+
+       pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
+       pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
+       pon_cntl |= (pdata->pull_up ? PON_CNTL_PULL_UP : ~PON_CNTL_PULL_UP);
+       err = pm8058_write(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err);
+               goto free_input_dev;
+       }
+
+       setup_timer(&pwrkey->timer, pmic8058_pwrkey_timer,
+                                (unsigned long) pwrkey);
+
+       spin_lock_init(&pwrkey->lock);
+
+       err = input_register_device(pwr);
+       if (err) {
+               dev_dbg(&pdev->dev, "Can't register power key: %d\n", err);
+               goto free_input_dev;
+       }
+
+       pwrkey->key_press_irq = key_press_irq;
+       pwrkey->pwr = pwr;
+
+       platform_set_drvdata(pdev, pwrkey);
+
+       err = request_any_context_irq(key_press_irq, pwrkey_press_irq,
+                        IRQF_TRIGGER_RISING, "pmic8058_pwrkey_press", pwrkey);
+       if (err < 0) {
+               dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+                                key_press_irq, err);
+               goto unreg_input_dev;
+       }
+
+       err = request_any_context_irq(key_release_irq, pwrkey_release_irq,
+                        IRQF_TRIGGER_RISING, "pmic8058_pwrkey_release",
+                                pwrkey);
+       if (err < 0) {
+               dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+                                key_release_irq, err);
+
+               goto free_press_irq;
+       }
+
+       device_init_wakeup(&pdev->dev, pdata->wakeup);
+
+       return 0;
+
+free_press_irq:
+       free_irq(key_press_irq, NULL);
+unreg_input_dev:
+       input_unregister_device(pwr);
+       pwr = NULL;
+free_input_dev:
+       input_free_device(pwr);
+free_pwrkey:
+       kfree(pwrkey);
+       return err;
+}
+
+static int __devexit pmic8058_pwrkey_remove(struct platform_device *pdev)
+{
+       struct pmic8058_pwrkey *pwrkey = platform_get_drvdata(pdev);
+       int key_release_irq = platform_get_irq(pdev, 0);
+       int key_press_irq = platform_get_irq(pdev, 1);
+
+       device_init_wakeup(&pdev->dev, 0);
+
+       free_irq(key_press_irq, pwrkey);
+       free_irq(key_release_irq, pwrkey);
+       del_timer_sync(&pwrkey->timer);
+       input_unregister_device(pwrkey->pwr);
+       kfree(pwrkey);
+
+       return 0;
+}
+
+static struct platform_driver pmic8058_pwrkey_driver = {
+       .probe          = pmic8058_pwrkey_probe,
+       .remove         = __devexit_p(pmic8058_pwrkey_remove),
+       .driver         = {
+               .name   = "pm8058-pwrkey",
+               .owner  = THIS_MODULE,
+#ifdef CONFIG_PM
+               .pm     = &pm8058_pwr_key_pm_ops,
+#endif
+       },
+};
+
+static int __init pmic8058_pwrkey_init(void)
+{
+       return platform_driver_register(&pmic8058_pwrkey_driver);
+}
+module_init(pmic8058_pwrkey_init);
+
+static void __exit pmic8058_pwrkey_exit(void)
+{
+       platform_driver_unregister(&pmic8058_pwrkey_driver);
+}
+module_exit(pmic8058_pwrkey_exit);
+
+MODULE_ALIAS("platform:pmic8058_pwrkey");
+MODULE_DESCRIPTION("PMIC8058 Power Key driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Trilok Soni <ts...@codeaurora.org>");
diff --git a/include/linux/input/pmic8058-pwrkey.h 
b/include/linux/input/pmic8058-pwrkey.h
new file mode 100644
index 0000000..e41c616
--- /dev/null
+++ b/include/linux/input/pmic8058-pwrkey.h
@@ -0,0 +1,37 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __PMIC8058_PWRKEY_H__
+#define __PMIC8058_PWRKEY_H__
+/**
+ * struct pmic8058_pwrkey_pdata - platform data for pwrkey driver
+ * @pull up:  power on register control for pull up/down configuration
+ * @pwrkey_time_ms: time after which power key event should be generated, if
+ *                  key is released before then end key is reported.
+ *                  Supply zero for only power key reporting.
+ * @kpd_trigger_delay_us: time delay for power key state change interrupt
+ *                  trigger.
+ * @wakeup: configure power key as wakeup source
+ */
+struct pmic8058_pwrkey_pdata {
+       bool pull_up;
+       u16  pwrkey_time_ms;
+       u32  kpd_trigger_delay_us;
+       u32  wakeup;
+};
+
+#endif /* __PMIC8058_PWRKEY_H__ */
-- 
1.7.3.5

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to