diff mbox

[RFC,v1,4/6] input: pmic8058_pwrkey: Add support for power key

Message ID 1289393281-4459-5-git-send-email-tsoni@codeaurora.org
State Superseded
Headers show

Commit Message

Trilok Soni Nov. 10, 2010, 12:47 p.m. UTC
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.torokhov@gmail.com>
Signed-off-by: Trilok Soni <tsoni@codeaurora.org>
---
 drivers/input/misc/Kconfig            |   11 +
 drivers/input/misc/Makefile           |    1 +
 drivers/input/misc/pmic8058-pwrkey.c  |  322 +++++++++++++++++++++++++++++++++
 include/linux/input/pmic8058-pwrkey.h |   37 ++++
 4 files changed, 371 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/pmic8058-pwrkey.c
 create mode 100644 include/linux/input/pmic8058-pwrkey.h

Comments

Dmitry Torokhov Nov. 11, 2010, 7:21 a.m. UTC | #1
Hi Trilkok,

On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> 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.
> 

Why do we need to delay KEY_POWER reporting? Do we need to use high
resolution timers or regular timers would do as well? KEY_END appears to
be abused (you don't want to move your cursor to the end of line, do
you?). Also I wonder if header file should reside in linux/mfd with the
rest of pmic8058 components.

Thanks. 

> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Signed-off-by: Trilok Soni <tsoni@codeaurora.org>
> ---
>  drivers/input/misc/Kconfig            |   11 +
>  drivers/input/misc/Makefile           |    1 +
>  drivers/input/misc/pmic8058-pwrkey.c  |  322 +++++++++++++++++++++++++++++++++
>  include/linux/input/pmic8058-pwrkey.h |   37 ++++
>  4 files changed, 371 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 b99b8cb..aeb9165 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -348,6 +348,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 1fe1f6c..c4357a0 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -31,6 +31,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..3714b24
> --- /dev/null
> +++ b/drivers/input/misc/pmic8058-pwrkey.c
> @@ -0,0 +1,322 @@
> +/* Copyright (c) 2010, 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/hrtimer.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 hrtimer timer;
> +	bool key_pressed;
> +	struct pmic8058_pwrkey_pdata *pdata;
> +	spinlock_t lock;
> +};
> +
> +static enum hrtimer_restart pmic8058_pwrkey_timer(struct hrtimer *timer)
> +{
> +	unsigned long flags;
> +	struct pmic8058_pwrkey *pwrkey = container_of(timer,
> +						struct pmic8058_pwrkey,	timer);
> +
> +	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);
> +
> +	return HRTIMER_NORESTART;
> +}
> +
> +static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
> +{
> +	struct pmic8058_pwrkey *pwrkey = _pwrkey;
> +	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_END, 1);
> +	input_sync(pwrkey->pwr);
> +
> +	hrtimer_start(&pwrkey->timer,
> +			ktime_set(pdata->pwrkey_time_ms / 1000,
> +				(pdata->pwrkey_time_ms % 1000) * 1000000),
> +			HRTIMER_MODE_REL);
> +	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);
> +	hrtimer_cancel(&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_END, 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 -EFAULT;
> +	}
> +
> +	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_END);
> +
> +	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;
> +	}
> +
> +	hrtimer_init(&pwrkey->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> +	pwrkey->timer.function = pmic8058_pwrkey_timer;
> +
> +	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);
> +	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 <tsoni@codeaurora.org>");
> diff --git a/include/linux/input/pmic8058-pwrkey.h b/include/linux/input/pmic8058-pwrkey.h
> new file mode 100644
> index 0000000..dd849fe
> --- /dev/null
> +++ b/include/linux/input/pmic8058-pwrkey.h
> @@ -0,0 +1,37 @@
> +/* Copyright (c) 2010, 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.0.2
>
Trilok Soni Nov. 11, 2010, noon UTC | #2
Hi Dmitry,

On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> Hi Trilkok,
> 
> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
>> 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.
>>
> 
> Why do we need to delay KEY_POWER reporting? Do we need to use high
> resolution timers or regular timers would do as well? KEY_END appears to
> be abused (you don't want to move your cursor to the end of line, do
> you?). Also I wonder if header file should reside in linux/mfd with the
> rest of pmic8058 components.

Most of the time Mobile devices come with single physical key for POWER,
which if pressed for less than 500ms (configurable) then it will only
report KEY_END (which say locks the screen on mobile) and if it pressed
more than 500ms then it will also report KEY_POWER event too, which will
say display menu on your mobile for asking you to suspend/switch off/etc, operations.

For the timers I can move from hrtimers to regular timers.

For the header file, I can move them to include/linux/mfd too. No problem on that.

---Trilok Soni
Dmitry Torokhov Nov. 12, 2010, 12:57 a.m. UTC | #3
On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
> Hi Dmitry,
> 
> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> > Hi Trilkok,
> > 
> > On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> >> 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.
> >>
> > 
> > Why do we need to delay KEY_POWER reporting? Do we need to use high
> > resolution timers or regular timers would do as well? KEY_END
> > appears to be abused (you don't want to move your cursor to the end
> > of line, do you?). Also I wonder if header file should reside in
> > linux/mfd with the rest of pmic8058 components.
> 
> Most of the time Mobile devices come with single physical key for
> POWER, which if pressed for less than 500ms (configurable) then it
> will only report KEY_END (which say locks the screen on mobile) and if
> it pressed more than 500ms then it will also report KEY_POWER event
> too, which will say display menu on your mobile for asking you to
> suspend/switch off/etc, operations.
> 

I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
would likely not ask this question ;)

> For the timers I can move from hrtimers to regular timers.
> 
> For the header file, I can move them to include/linux/mfd too. No
> problem on that.
> 

I am not even sure we need to keep them in separate header files, but it
is up to you.

Thanks.
Trilok Soni Nov. 12, 2010, 8:56 a.m. UTC | #4
Hi Dmitry,

On 11/12/2010 6:27 AM, Dmitry Torokhov wrote:
> On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
>> Hi Dmitry,
>>
>> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
>>> Hi Trilkok,
>>>
>>> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
>>>> 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.
>>>>
>>>
>>> Why do we need to delay KEY_POWER reporting? Do we need to use high
>>> resolution timers or regular timers would do as well? KEY_END
>>> appears to be abused (you don't want to move your cursor to the end
>>> of line, do you?). Also I wonder if header file should reside in
>>> linux/mfd with the rest of pmic8058 components.
>>
>> Most of the time Mobile devices come with single physical key for
>> POWER, which if pressed for less than 500ms (configurable) then it
>> will only report KEY_END (which say locks the screen on mobile) and if
>> it pressed more than 500ms then it will also report KEY_POWER event
>> too, which will say display menu on your mobile for asking you to
>> suspend/switch off/etc, operations.
>>
> 
> I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
> would likely not ask this question ;)
> 

KEY_SCRENNLOCK looks good, let me analyze the impact on userspace framework
which I have. I will come back on this in a day.

>> For the timers I can move from hrtimers to regular timers.
>>
>> For the header file, I can move them to include/linux/mfd too. No
>> problem on that.
>>
> 
> I am not even sure we need to keep them in separate header files, but it
> is up to you.

Do you suggest that all the MFD sub-devices's platform data structures should come from single
header file?

---Trilok Soni
Dmitry Torokhov Nov. 12, 2010, 7:58 p.m. UTC | #5
On Fri, Nov 12, 2010 at 02:26:28PM +0530, Trilok Soni wrote:
> Hi Dmitry,
> 
> On 11/12/2010 6:27 AM, Dmitry Torokhov wrote:
> > On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
> >> Hi Dmitry,
> >>
> >> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> >>> Hi Trilkok,
> >>>
> >>> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> >>>> 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.
> >>>>
> >>>
> >>> Why do we need to delay KEY_POWER reporting? Do we need to use high
> >>> resolution timers or regular timers would do as well? KEY_END
> >>> appears to be abused (you don't want to move your cursor to the end
> >>> of line, do you?). Also I wonder if header file should reside in
> >>> linux/mfd with the rest of pmic8058 components.
> >>
> >> Most of the time Mobile devices come with single physical key for
> >> POWER, which if pressed for less than 500ms (configurable) then it
> >> will only report KEY_END (which say locks the screen on mobile) and if
> >> it pressed more than 500ms then it will also report KEY_POWER event
> >> too, which will say display menu on your mobile for asking you to
> >> suspend/switch off/etc, operations.
> >>
> > 
> > I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
> > would likely not ask this question ;)
> > 
> 
> KEY_SCRENNLOCK looks good, let me analyze the impact on userspace framework
> which I have. I will come back on this in a day.
> 
> >> For the timers I can move from hrtimers to regular timers.
> >>
> >> For the header file, I can move them to include/linux/mfd too. No
> >> problem on that.
> >>
> > 
> > I am not even sure we need to keep them in separate header files, but it
> > is up to you.
> 
> Do you suggest that all the MFD sub-devices's platform data structures should come from single
> header file?
> 

It is an option, depends on how many external headers are needed, etc.

When I looked at this particular file I got the feeling that it could be
folded together with the rest. I expect that the board code that will
specify platform resources will include every one of this sub-files
anyway. But maybe if I was presented with the combined header I'd say
"wow, thats too big"...

Like I said, it is up to you.
diff mbox

Patch

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index b99b8cb..aeb9165 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -348,6 +348,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 1fe1f6c..c4357a0 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -31,6 +31,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..3714b24
--- /dev/null
+++ b/drivers/input/misc/pmic8058-pwrkey.c
@@ -0,0 +1,322 @@ 
+/* Copyright (c) 2010, 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/hrtimer.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 hrtimer timer;
+	bool key_pressed;
+	struct pmic8058_pwrkey_pdata *pdata;
+	spinlock_t lock;
+};
+
+static enum hrtimer_restart pmic8058_pwrkey_timer(struct hrtimer *timer)
+{
+	unsigned long flags;
+	struct pmic8058_pwrkey *pwrkey = container_of(timer,
+						struct pmic8058_pwrkey,	timer);
+
+	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);
+
+	return HRTIMER_NORESTART;
+}
+
+static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
+{
+	struct pmic8058_pwrkey *pwrkey = _pwrkey;
+	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_END, 1);
+	input_sync(pwrkey->pwr);
+
+	hrtimer_start(&pwrkey->timer,
+			ktime_set(pdata->pwrkey_time_ms / 1000,
+				(pdata->pwrkey_time_ms % 1000) * 1000000),
+			HRTIMER_MODE_REL);
+	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);
+	hrtimer_cancel(&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_END, 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 -EFAULT;
+	}
+
+	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_END);
+
+	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;
+	}
+
+	hrtimer_init(&pwrkey->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	pwrkey->timer.function = pmic8058_pwrkey_timer;
+
+	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);
+	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 <tsoni@codeaurora.org>");
diff --git a/include/linux/input/pmic8058-pwrkey.h b/include/linux/input/pmic8058-pwrkey.h
new file mode 100644
index 0000000..dd849fe
--- /dev/null
+++ b/include/linux/input/pmic8058-pwrkey.h
@@ -0,0 +1,37 @@ 
+/* Copyright (c) 2010, 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__ */