diff mbox

[V1,4/6] input: misc: onkey: da9062: DA9062 OnKey driver

Message ID c05802897197e71839a33b1f5ac224031b14e65b.1429280614.git.stwiss.opensource@diasemi.com
State Superseded
Headers show

Commit Message

Steve Twiss April 17, 2015, 2:23 p.m. UTC
From: S Twiss <stwiss.opensource@diasemi.com>

Add OnKey driver support for DA9062

Signed-off-by: Steve Twiss <stwiss.opensource@diasemi.com>

---

This patch applies against linux-next and v4.0 



 drivers/input/misc/Kconfig        |  10 ++
 drivers/input/misc/Makefile       |   1 +
 drivers/input/misc/da9062-onkey.c | 223 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 234 insertions(+)
 create mode 100644 drivers/input/misc/da9062-onkey.c

Comments

Dmitry Torokhov April 17, 2015, 4:12 p.m. UTC | #1
Hi,

On Fri, Apr 17, 2015 at 03:23:33PM +0100, S Twiss wrote:
> From: S Twiss <stwiss.opensource@diasemi.com>
> 
> Add OnKey driver support for DA9062
> 
> Signed-off-by: Steve Twiss <stwiss.opensource@diasemi.com>
> 
> ---
> 
> This patch applies against linux-next and v4.0 
> 
> 
> 
>  drivers/input/misc/Kconfig        |  10 ++
>  drivers/input/misc/Makefile       |   1 +
>  drivers/input/misc/da9062-onkey.c | 223 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 234 insertions(+)
>  create mode 100644 drivers/input/misc/da9062-onkey.c
> 
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index 6deb8da..472ca38 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -586,6 +586,16 @@ config INPUT_DA9055_ONKEY
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called da9055_onkey.
>  
> +config INPUT_DA9062_ONKEY
> +	tristate "Dialog DA9062 OnKey"
> +	depends on MFD_DA9062
> +	help
> +	  Support the ONKEY of Dialog DA9062 Power Management IC as an
> +	  input device reporting power button status.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called da9062-onkey.
> +
>  config INPUT_DM355EVM
>  	tristate "TI DaVinci DM355 EVM Keypad and IR Remote"
>  	depends on MFD_DM355EVM_MSP
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 403a1a5..a631283 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
>  obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
>  obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
>  obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
> +obj-$(CONFIG_INPUT_DA9062_ONKEY)	+= da9062-onkey.o

Can we maybe keep the same naming convention for all of these? Also, any
chance all of them or some of them can be combined?

>  obj-$(CONFIG_INPUT_DM355EVM)		+= dm355evm_keys.o
>  obj-$(CONFIG_INPUT_E3X0_BUTTON)		+= e3x0-button.o
>  obj-$(CONFIG_INPUT_DRV260X_HAPTICS)	+= drv260x.o
> diff --git a/drivers/input/misc/da9062-onkey.c b/drivers/input/misc/da9062-onkey.c
> new file mode 100644
> index 0000000..c4c0295
> --- /dev/null
> +++ b/drivers/input/misc/da9062-onkey.c
> @@ -0,0 +1,223 @@
> +/*
> + * da9062-onkey.c - ONKEY device driver for DA9062
> + * Copyright (C) 2015  Dialog Semiconductor Ltd.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/errno.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/workqueue.h>
> +#include <linux/regmap.h>
> +#include <linux/of.h>
> +#include <linux/mfd/da9062/core.h>
> +#include <linux/mfd/da9062/registers.h>
> +
> +struct da9062_onkey {
> +	struct da9062 *hw;
> +	struct delayed_work work;
> +	struct	input_dev *input;
> +	int irq;
> +	bool key_power;
> +};
> +
> +static void da9062_poll_on(struct work_struct *work)
> +{
> +	struct da9062_onkey *onkey = container_of(work, struct da9062_onkey,
> +						  work.work);
> +	unsigned int val;
> +	int fault_log = 0;
> +	bool poll = true;
> +	int ret;
> +
> +	/* poll to see when the pin is released */
> +	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A, &val);
> +	if (ret < 0) {
> +		dev_err(&onkey->input->dev,
> +			"Failed to read ON status: %d\n", ret);
> +		goto err_poll;
> +	}
> +
> +	if (!(val & DA9062AA_NONKEY_MASK)) {
> +		ret = regmap_update_bits(onkey->hw->regmap,
> +					 DA9062AA_CONTROL_B,
> +					 DA9062AA_NONKEY_LOCK_MASK, 0);
> +		if (ret < 0) {
> +			dev_err(&onkey->input->dev,
> +				"Failed to reset the Key Delay %d\n", ret);
> +			goto err_poll;
> +		}
> +
> +		input_report_key(onkey->input, KEY_POWER, 0);
> +		input_sync(onkey->input);
> +
> +		poll = false;
> +	}
> +
> +	/* if the fault log KEY_RESET is detected,
> +	 * then clear it and shutdown DA9062 via I2C
> +	 */
> +	ret = regmap_read(onkey->hw->regmap, DA9062AA_FAULT_LOG, &fault_log);
> +	if (ret < 0)
> +		dev_warn(&onkey->input->dev, "Cannot read FAULT_LOG\n");
> +
> +	if (fault_log & DA9062AA_KEY_RESET_MASK) {
> +		ret = regmap_write(onkey->hw->regmap,
> +				   DA9062AA_FAULT_LOG,
> +				   DA9062AA_KEY_RESET_MASK);
> +		if (ret < 0)
> +			dev_warn(&onkey->input->dev,
> +				 "Cannot reset KEY_RESET fault log\n");
> +		else {
> +			/* at this point we do any S/W housekeeping
> +			 * and then send shutdown command
> +			 */
> +			dev_info(&onkey->input->dev,
> +				 "Sending SHUTDOWN to DA9062 ...\n");
> +			ret = regmap_write(onkey->hw->regmap,
> +					   DA9062AA_CONTROL_F,
> +					   DA9062AA_SHUTDOWN_MASK);
> +			if (ret < 0)
> +				dev_err(&onkey->input->dev,
> +					"Cannot SHUTDOWN DA9062\n");
> +		}
> +	}

This entire block seems to not belong to the input portion of MFD. Why
do we do it here?

> +
> +err_poll:
> +	if (poll)
> +		schedule_delayed_work(&onkey->work, 50);
> +}
> +
> +static irqreturn_t da9062_onkey_irq_handler(int irq, void *data)
> +{
> +	struct da9062_onkey *onkey = data;
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A, &val);
> +	if (onkey->key_power && (ret >= 0) && (val & DA9062AA_NONKEY_MASK)) {
> +		input_report_key(onkey->input, KEY_POWER, 1);
> +		input_sync(onkey->input);
> +		schedule_delayed_work(&onkey->work, 0);
> +		dev_notice(&onkey->input->dev, "KEY_POWER pressed.\n");
> +	} else {
> +		input_report_key(onkey->input, KEY_SLEEP, 1);
> +		input_sync(onkey->input);
> +		input_report_key(onkey->input, KEY_SLEEP, 0);
> +		input_sync(onkey->input);
> +		dev_notice(&onkey->input->dev, "KEY_SLEEP pressed.\n");
> +	}

Why do we handle KEY_POWER and KEY_SLEEP completely differently?

> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int da9062_onkey_probe(struct platform_device *pdev)
> +{
> +	struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
> +	struct da9062_onkey *onkey;
> +	bool kp_tmp = true;
> +	int ret = 0;
> +
> +		kp_tmp = of_property_read_bool((&pdev->dev)->of_node,
> +					       "dlg,disable-key-power");
> +		kp_tmp = !kp_tmp;

Should we just allow specifying the keycode instead of hardcoding
KEY_POWER/KEY_SLEEP?

> +
> +
> +	onkey = devm_kzalloc(&pdev->dev, sizeof(struct da9062_onkey),
> +			     GFP_KERNEL);
> +	if (!onkey) {
> +		dev_err(&pdev->dev, "Failed to allocate memory.\n");
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	INIT_DELAYED_WORK(&onkey->work, da9062_poll_on);
> +
> +	onkey->input = devm_input_allocate_device(&pdev->dev);
> +	if (!onkey->input) {
> +		dev_err(&pdev->dev, "Failed to allocated input device.\n");
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	ret = platform_get_irq_byname(pdev, "ONKEY");
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
> +		goto err;
> +	}
> +	onkey->irq = ret;
> +
> +	ret = request_threaded_irq(onkey->irq, NULL,
> +				   da9062_onkey_irq_handler,
> +				   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +				   "ONKEY", onkey);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"Failed to request input device IRQ.\n");
> +		goto err;
> +	}
> +
> +	onkey->hw = chip;
> +	onkey->key_power = kp_tmp;
> +	onkey->input->evbit[0] = BIT_MASK(EV_KEY);
> +	onkey->input->name = "da9062-onkey";
> +	onkey->input->phys = "da9062-onkey/input0";
> +	onkey->input->dev.parent = &pdev->dev;
> +
> +	if (onkey->key_power)
> +		input_set_capability(onkey->input, EV_KEY, KEY_POWER);
> +	input_set_capability(onkey->input, EV_KEY, KEY_SLEEP);
> +
> +	ret = input_register_device(onkey->input);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"Failed to register input device.\n");
> +		goto err_irq;
> +	}
> +
> +	platform_set_drvdata(pdev, onkey);
> +	return 0;
> +
> +err_irq:
> +	free_irq(onkey->irq, onkey);
> +	cancel_delayed_work_sync(&onkey->work);
> +err:
> +	return ret;
> +}
> +
> +static int da9062_onkey_remove(struct platform_device *pdev)
> +{
> +	struct	da9062_onkey *onkey = platform_get_drvdata(pdev);
> +
> +	free_irq(onkey->irq, onkey);
> +	cancel_delayed_work_sync(&onkey->work);
> +	input_unregister_device(onkey->input);

No need to unregister explicitly if you allocated input device with
devm.

> +	return 0;
> +}
> +
> +static struct platform_driver da9062_onkey_driver = {
> +	.probe	= da9062_onkey_probe,
> +	.remove	= da9062_onkey_remove,
> +	.driver	= {
> +		.name	= "da9062-onkey",
> +		.owner	= THIS_MODULE,
> +	},
> +};
> +
> +module_platform_driver(da9062_onkey_driver);
> +
> +MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
> +MODULE_DESCRIPTION("ONKEY device driver for Dialog DA9062");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform: da9062-onkey");
> -- 
> end-of-patch for PATCH V1
> 

Thanks.
Steve Twiss April 29, 2015, 3:18 p.m. UTC | #2
Hi Dmitry,

Thanks for your patience on this one.
The Dialog OnKey for DA9062 is a fairly complicated set of interrupts
and register read and writes. I've tried to explain the best I can below.

On  17 April 2015 17:12, Dmitry Torokhov [mailto:dmitry.torokhov@gmail.com] wrote: 

[...]

> > diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> > index 403a1a5..a631283 100644
> > --- a/drivers/input/misc/Makefile
> > +++ b/drivers/input/misc/Makefile
> > @@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C)		+=
> cma3000_d0x_i2c.o
> >  obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
> >  obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
> >  obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
> > +obj-$(CONFIG_INPUT_DA9062_ONKEY)	+= da9062-onkey.o
> 
> Can we maybe keep the same naming convention for all of these? Also, any
> chance all of them or some of them can be combined?

Yes. I will rename so it uses an underscore.

Unfortunately, the 55 and 52 drivers are not functionally similar. However the
DA9063 and DA9062 OnKey drivers *are* functionally similar and so I will make
an effort to combine those two drivers in future. I would like to submit DA9063
first and drop this DA9062 until I can combine it. However, since the DA9063 driver
is identical -- your comments will be useful for my DA9063 submission attempt.
I have added my replies here below which will also relate to DA9063.

[...]

> > +static void da9062_poll_on(struct work_struct *work)
> > +{
> > +	struct da9062_onkey *onkey = container_of(work, struct
> da9062_onkey,
> > +						  work.work);
> > +	unsigned int val;
> > +	int fault_log = 0;
> > +	bool poll = true;
> > +	int ret;
> > +
> > +	/* poll to see when the pin is released */
> > +	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A,
> &val);
> > +	if (ret < 0) {
> > +		dev_err(&onkey->input->dev,
> > +			"Failed to read ON status: %d\n", ret);
> > +		goto err_poll;
> > +	}
> > +
> > +	if (!(val & DA9062AA_NONKEY_MASK)) {
> > +		ret = regmap_update_bits(onkey->hw->regmap,
> > +					 DA9062AA_CONTROL_B,
> > +					 DA9062AA_NONKEY_LOCK_MASK,
> 0);
> > +		if (ret < 0) {
> > +			dev_err(&onkey->input->dev,
> > +				"Failed to reset the Key Delay %d\n", ret);
> > +			goto err_poll;
> > +		}
> > +
> > +		input_report_key(onkey->input, KEY_POWER, 0);
> > +		input_sync(onkey->input);
> > +
> > +		poll = false;
> > +	}
> > +
> > +	/* if the fault log KEY_RESET is detected,
> > +	 * then clear it and shutdown DA9062 via I2C
> > +	 */
> > +	ret = regmap_read(onkey->hw->regmap, DA9062AA_FAULT_LOG,
> &fault_log);
> > +	if (ret < 0)
> > +		dev_warn(&onkey->input->dev, "Cannot read
> FAULT_LOG\n");
> > +
> > +	if (fault_log & DA9062AA_KEY_RESET_MASK) {
> > +		ret = regmap_write(onkey->hw->regmap,
> > +				   DA9062AA_FAULT_LOG,
> > +				   DA9062AA_KEY_RESET_MASK);
> > +		if (ret < 0)
> > +			dev_warn(&onkey->input->dev,
> > +				 "Cannot reset KEY_RESET fault log\n");
> > +		else {
> > +			/* at this point we do any S/W housekeeping
> > +			 * and then send shutdown command
> > +			 */
> > +			dev_info(&onkey->input->dev,
> > +				 "Sending SHUTDOWN to DA9062 ...\n");
> > +			ret = regmap_write(onkey->hw->regmap,
> > +					   DA9062AA_CONTROL_F,
> > +					   DA9062AA_SHUTDOWN_MASK);
> > +			if (ret < 0)
> > +				dev_err(&onkey->input->dev,
> > +					"Cannot SHUTDOWN DA9062\n");
> > +		}
> > +	}
> 
> This entire block seems to not belong to the input portion of MFD. Why
> do we do it here?
> 

There are four modes of OnKey operation here, but only the first three are handled
in software. This block relates to case (3).

(1) short press & release -- SLEEP
(2) long press & release -- POWER
(3) long-long press (no release) -- software power cut
(4) long-long press (no release) -- hardware power cut

For cases (1) and (2) the OnKey driver will handle the events using the Linux
framework. And for case (4) this is completed totally in hardware and is the
emergency power off for holding down the OnKey until the power is cut by
the PMIC -- this is not handled here and in any case it is designed to be used
when the software is not responding: H/W shutdown

There is one more case and this is covered by point (3). This is when the user
holds down the OnKey as though they were attempting an emergency power
off. This is the same operation as point (4) except that software *is* still
able to respond. At this point it sends the power off signal to the PMIC in a similar
way as the hardware power cut operation would do -- but it does this 1 second
before the hardware has chance to step-in and force the PMIC to go down.

There are several reasons for case (3).

 - It is to cover the usecase of a user pressing the OnKey until the device goes
   off as their main way of turning the power off to their device.
   This software power cut allows any critical house-keeping to be completed
   before the power switch is flicked and so it mitigates the possibility for loss 
   of any data.
 - It also ensures the FAULT_LOG is cleared. The FAULT_LOG is a persistent register
   in the PMIC and holds its information between resets -- in this case the reason the
   device was powered off.

In case (4)

If the software was not responsive, then the hardware shutdown would happen
1 second later and the FAULT_LOG would persist enough information to be able
to tell the difference between a software power cut and a hardware power cut
when the device was restarted. If the software had not responded, then due to
the FAULT_LOG persistence, it would be possible to take action the next time the
device was turned on again (maybe in the bootloader -- e.g. say to complete
memory checks or put the device into a safe mode).

> > +
> > +err_poll:
> > +	if (poll)
> > +		schedule_delayed_work(&onkey->work, 50);
> > +}
> > +
> > +static irqreturn_t da9062_onkey_irq_handler(int irq, void *data)
> > +{
> > +	struct da9062_onkey *onkey = data;
> > +	unsigned int val;
> > +	int ret;
> > +
> > +	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A,
> &val);
> > +	if (onkey->key_power && (ret >= 0) && (val &
> DA9062AA_NONKEY_MASK)) {
> > +		input_report_key(onkey->input, KEY_POWER, 1);
> > +		input_sync(onkey->input);
> > +		schedule_delayed_work(&onkey->work, 0);
> > +		dev_notice(&onkey->input->dev, "KEY_POWER
> pressed.\n");
> > +	} else {
> > +		input_report_key(onkey->input, KEY_SLEEP, 1);
> > +		input_sync(onkey->input);
> > +		input_report_key(onkey->input, KEY_SLEEP, 0);
> > +		input_sync(onkey->input);
> > +		dev_notice(&onkey->input->dev, "KEY_SLEEP pressed.\n");
> > +	}
> 
> Why do we handle KEY_POWER and KEY_SLEEP completely differently?
> 

The reason for the keys being handled differently is due to the way with the
interrupt is being triggered differently in the OnKey. This depends on whether
the key-press is a short (SLEEP) or a long (POWER) key-press. 

For case (1)
(1) short-press & release -- SLEEP
The *release* of the OnKey is the trigger of the interrupt. So we know the device
has a press-release operation. This was the intention of the two key reports, one
for press and the other for release. There is no way to detect the key press here,
only the key release, but both must have happened.
input_report_key(onkey->input, KEY_SLEEP, 1);
input_report_key(onkey->input, KEY_SLEEP, 0);

For the next case (2)
(2) long-press & release -- POWER
The key is pressed but not released for a period of time (say longer than 2 seconds),
and once this time-limit has been passed without the key being released an interrupt
is triggered. This is different to case (1) in that the key release was not the trigger
of the interrupt, it was a time-out trip-line -- hence the single key report, because at
this point we know that the key was pressed, just not released yet.
input_report_key(onkey->input, KEY_POWER, 1);
The second key report is triggered when the key is released, and this can only
be detected during a polling operation and examination of the register
DA9063_REG_STATUS_A. Once the key release is detected, then the second key report
is sent out
input_report_key(onkey->input, KEY_SLEEP, 0);
This last operation happens inside the da9063_poll_on() function.

It is this polling function that also detects for the key never being released and so is
also able to handle case (3)
(3) long-long press (no release) -- software power cut

[...]

> > +static int da9062_onkey_probe(struct platform_device *pdev)
> > +{
> > +	struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
> > +	struct da9062_onkey *onkey;
> > +	bool kp_tmp = true;
> > +	int ret = 0;
> > +
> > +		kp_tmp = of_property_read_bool((&pdev->dev)->of_node,
> > +					       "dlg,disable-key-power");
> > +		kp_tmp = !kp_tmp;
> 
> Should we just allow specifying the keycode instead of hardcoding
> KEY_POWER/KEY_SLEEP?
> 

The reason I have hard-coded the key-power and key-sleep is because this
is the configuration written into the default PMIC one-time-programming
registers. Perhaps I am not quite understanding your question properly here.

[...]

> > +static int da9062_onkey_remove(struct platform_device *pdev)
> > +{
> > +	struct	da9062_onkey *onkey = platform_get_drvdata(pdev);
> > +
> > +	free_irq(onkey->irq, onkey);
> > +	cancel_delayed_work_sync(&onkey->work);
> > +	input_unregister_device(onkey->input);
> 	
> No need to unregister explicitly if you allocated input device with
> devm.
> 

Yep. I can alter this by removing the function input_unregister_device()
as described.

So, just to finish this off. I would like to reiterate that I am going to drop this patch
from my next submission attempt of the DA9062. However because the DA9063 
OnKey component is functionally identical, the comments from above will be
used to modify my DA9063 driver submission.

http://www.spinics.net/lists/linux-input/msg38034.html
http://www.spinics.net/lists/linux-input/msg38137.html

Regards,
Steve
diff mbox

Patch

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6deb8da..472ca38 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -586,6 +586,16 @@  config INPUT_DA9055_ONKEY
 	  To compile this driver as a module, choose M here: the module
 	  will be called da9055_onkey.
 
+config INPUT_DA9062_ONKEY
+	tristate "Dialog DA9062 OnKey"
+	depends on MFD_DA9062
+	help
+	  Support the ONKEY of Dialog DA9062 Power Management IC as an
+	  input device reporting power button status.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called da9062-onkey.
+
 config INPUT_DM355EVM
 	tristate "TI DaVinci DM355 EVM Keypad and IR Remote"
 	depends on MFD_DM355EVM_MSP
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 403a1a5..a631283 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -25,6 +25,7 @@  obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
 obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
 obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
 obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
+obj-$(CONFIG_INPUT_DA9062_ONKEY)	+= da9062-onkey.o
 obj-$(CONFIG_INPUT_DM355EVM)		+= dm355evm_keys.o
 obj-$(CONFIG_INPUT_E3X0_BUTTON)		+= e3x0-button.o
 obj-$(CONFIG_INPUT_DRV260X_HAPTICS)	+= drv260x.o
diff --git a/drivers/input/misc/da9062-onkey.c b/drivers/input/misc/da9062-onkey.c
new file mode 100644
index 0000000..c4c0295
--- /dev/null
+++ b/drivers/input/misc/da9062-onkey.c
@@ -0,0 +1,223 @@ 
+/*
+ * da9062-onkey.c - ONKEY device driver for DA9062
+ * Copyright (C) 2015  Dialog Semiconductor Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/mfd/da9062/core.h>
+#include <linux/mfd/da9062/registers.h>
+
+struct da9062_onkey {
+	struct da9062 *hw;
+	struct delayed_work work;
+	struct	input_dev *input;
+	int irq;
+	bool key_power;
+};
+
+static void da9062_poll_on(struct work_struct *work)
+{
+	struct da9062_onkey *onkey = container_of(work, struct da9062_onkey,
+						  work.work);
+	unsigned int val;
+	int fault_log = 0;
+	bool poll = true;
+	int ret;
+
+	/* poll to see when the pin is released */
+	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A, &val);
+	if (ret < 0) {
+		dev_err(&onkey->input->dev,
+			"Failed to read ON status: %d\n", ret);
+		goto err_poll;
+	}
+
+	if (!(val & DA9062AA_NONKEY_MASK)) {
+		ret = regmap_update_bits(onkey->hw->regmap,
+					 DA9062AA_CONTROL_B,
+					 DA9062AA_NONKEY_LOCK_MASK, 0);
+		if (ret < 0) {
+			dev_err(&onkey->input->dev,
+				"Failed to reset the Key Delay %d\n", ret);
+			goto err_poll;
+		}
+
+		input_report_key(onkey->input, KEY_POWER, 0);
+		input_sync(onkey->input);
+
+		poll = false;
+	}
+
+	/* if the fault log KEY_RESET is detected,
+	 * then clear it and shutdown DA9062 via I2C
+	 */
+	ret = regmap_read(onkey->hw->regmap, DA9062AA_FAULT_LOG, &fault_log);
+	if (ret < 0)
+		dev_warn(&onkey->input->dev, "Cannot read FAULT_LOG\n");
+
+	if (fault_log & DA9062AA_KEY_RESET_MASK) {
+		ret = regmap_write(onkey->hw->regmap,
+				   DA9062AA_FAULT_LOG,
+				   DA9062AA_KEY_RESET_MASK);
+		if (ret < 0)
+			dev_warn(&onkey->input->dev,
+				 "Cannot reset KEY_RESET fault log\n");
+		else {
+			/* at this point we do any S/W housekeeping
+			 * and then send shutdown command
+			 */
+			dev_info(&onkey->input->dev,
+				 "Sending SHUTDOWN to DA9062 ...\n");
+			ret = regmap_write(onkey->hw->regmap,
+					   DA9062AA_CONTROL_F,
+					   DA9062AA_SHUTDOWN_MASK);
+			if (ret < 0)
+				dev_err(&onkey->input->dev,
+					"Cannot SHUTDOWN DA9062\n");
+		}
+	}
+
+err_poll:
+	if (poll)
+		schedule_delayed_work(&onkey->work, 50);
+}
+
+static irqreturn_t da9062_onkey_irq_handler(int irq, void *data)
+{
+	struct da9062_onkey *onkey = data;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(onkey->hw->regmap, DA9062AA_STATUS_A, &val);
+	if (onkey->key_power && (ret >= 0) && (val & DA9062AA_NONKEY_MASK)) {
+		input_report_key(onkey->input, KEY_POWER, 1);
+		input_sync(onkey->input);
+		schedule_delayed_work(&onkey->work, 0);
+		dev_notice(&onkey->input->dev, "KEY_POWER pressed.\n");
+	} else {
+		input_report_key(onkey->input, KEY_SLEEP, 1);
+		input_sync(onkey->input);
+		input_report_key(onkey->input, KEY_SLEEP, 0);
+		input_sync(onkey->input);
+		dev_notice(&onkey->input->dev, "KEY_SLEEP pressed.\n");
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int da9062_onkey_probe(struct platform_device *pdev)
+{
+	struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
+	struct da9062_onkey *onkey;
+	bool kp_tmp = true;
+	int ret = 0;
+
+		kp_tmp = of_property_read_bool((&pdev->dev)->of_node,
+					       "dlg,disable-key-power");
+		kp_tmp = !kp_tmp;
+
+
+	onkey = devm_kzalloc(&pdev->dev, sizeof(struct da9062_onkey),
+			     GFP_KERNEL);
+	if (!onkey) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	INIT_DELAYED_WORK(&onkey->work, da9062_poll_on);
+
+	onkey->input = devm_input_allocate_device(&pdev->dev);
+	if (!onkey->input) {
+		dev_err(&pdev->dev, "Failed to allocated input device.\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ret = platform_get_irq_byname(pdev, "ONKEY");
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
+		goto err;
+	}
+	onkey->irq = ret;
+
+	ret = request_threaded_irq(onkey->irq, NULL,
+				   da9062_onkey_irq_handler,
+				   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				   "ONKEY", onkey);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Failed to request input device IRQ.\n");
+		goto err;
+	}
+
+	onkey->hw = chip;
+	onkey->key_power = kp_tmp;
+	onkey->input->evbit[0] = BIT_MASK(EV_KEY);
+	onkey->input->name = "da9062-onkey";
+	onkey->input->phys = "da9062-onkey/input0";
+	onkey->input->dev.parent = &pdev->dev;
+
+	if (onkey->key_power)
+		input_set_capability(onkey->input, EV_KEY, KEY_POWER);
+	input_set_capability(onkey->input, EV_KEY, KEY_SLEEP);
+
+	ret = input_register_device(onkey->input);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"Failed to register input device.\n");
+		goto err_irq;
+	}
+
+	platform_set_drvdata(pdev, onkey);
+	return 0;
+
+err_irq:
+	free_irq(onkey->irq, onkey);
+	cancel_delayed_work_sync(&onkey->work);
+err:
+	return ret;
+}
+
+static int da9062_onkey_remove(struct platform_device *pdev)
+{
+	struct	da9062_onkey *onkey = platform_get_drvdata(pdev);
+
+	free_irq(onkey->irq, onkey);
+	cancel_delayed_work_sync(&onkey->work);
+	input_unregister_device(onkey->input);
+	return 0;
+}
+
+static struct platform_driver da9062_onkey_driver = {
+	.probe	= da9062_onkey_probe,
+	.remove	= da9062_onkey_remove,
+	.driver	= {
+		.name	= "da9062-onkey",
+		.owner	= THIS_MODULE,
+	},
+};
+
+module_platform_driver(da9062_onkey_driver);
+
+MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
+MODULE_DESCRIPTION("ONKEY device driver for Dialog DA9062");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform: da9062-onkey");