[PATCH 1/3] input: misc: pm8941-pwrkey: add software key press debouncing support

From: Anjelique Melendez
Date: Thu Jan 20 2022 - 15:43:44 EST


From: David Collins <collinsd@xxxxxxxxxxxxxx>

On certain PMICs, an unexpected assertion of KPDPWR_DEB (the
positive logic hardware debounced power key signal) may be seen
during the falling edge of KPDPWR_N (i.e. a power key press) when
it occurs close to the rising edge of SLEEP_CLK. This then
triggers a spurious KPDPWR interrupt.

Handle this issue by adding software debouncing support to ignore
key events that occur within the hardware debounce delay after the
most recent key release event.

Change-Id: Ifa3809935c01aab9078ba2302397bc9ebf390021
Signed-off-by: David Collins <collinsd@xxxxxxxxxxxxxx>
Signed-off-by: Anjelique Melendez <quic_amelende@xxxxxxxxxxx>
---
drivers/input/misc/pm8941-pwrkey.c | 115 ++++++++++++++++++++++++++---
1 file changed, 105 insertions(+), 10 deletions(-)

diff --git a/drivers/input/misc/pm8941-pwrkey.c b/drivers/input/misc/pm8941-pwrkey.c
index 33609603245d..b912ce00ce1c 100644
--- a/drivers/input/misc/pm8941-pwrkey.c
+++ b/drivers/input/misc/pm8941-pwrkey.c
@@ -9,9 +9,11 @@
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
+#include <linux/ktime.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
@@ -19,6 +21,16 @@

#define PON_REV2 0x01

+#define PON_SUBTYPE 0x05
+
+#define PON_SUBTYPE_PRIMARY 0x01
+#define PON_SUBTYPE_SECONDARY 0x02
+#define PON_SUBTYPE_1REG 0x03
+#define PON_SUBTYPE_GEN2_PRIMARY 0x04
+#define PON_SUBTYPE_GEN2_SECONDARY 0x05
+#define PON_SUBTYPE_GEN3_PBS 0x08
+#define PON_SUBTYPE_GEN3_HLOS 0x09
+
#define PON_RT_STS 0x10
#define PON_KPDPWR_N_SET BIT(0)
#define PON_RESIN_N_SET BIT(1)
@@ -44,6 +56,8 @@ struct pm8941_data {
unsigned int status_bit;
bool supports_ps_hold_poff_config;
bool supports_debounce_config;
+ bool needs_sw_debounce;
+ bool has_pon_pbs;
const char *name;
const char *phys;
};
@@ -52,13 +66,17 @@ struct pm8941_pwrkey {
struct device *dev;
int irq;
u32 baseaddr;
+ u32 pon_pbs_baseaddr;
struct regmap *regmap;
struct input_dev *input;

unsigned int revision;
+ unsigned int subtype;
struct notifier_block reboot_notifier;

u32 code;
+ u32 sw_debounce_time_us;
+ ktime_t last_release_time;
const struct pm8941_data *data;
};

@@ -126,19 +144,65 @@ static irqreturn_t pm8941_pwrkey_irq(int irq, void *_data)
struct pm8941_pwrkey *pwrkey = _data;
unsigned int sts;
int error;
+ u64 elapsed_us;
+
+ if (pwrkey->sw_debounce_time_us) {
+ elapsed_us = ktime_us_delta(ktime_get(),
+ pwrkey->last_release_time);
+ if (elapsed_us < pwrkey->sw_debounce_time_us) {
+ dev_dbg(pwrkey->dev, "ignoring key event received after %llu us, debounce time=%u us\n",
+ elapsed_us, pwrkey->sw_debounce_time_us);
+ return IRQ_HANDLED;
+ }
+ }

error = regmap_read(pwrkey->regmap,
pwrkey->baseaddr + PON_RT_STS, &sts);
if (error)
return IRQ_HANDLED;

- input_report_key(pwrkey->input, pwrkey->code,
- sts & pwrkey->data->status_bit);
+ sts &= pwrkey->data->status_bit;
+
+ if (pwrkey->sw_debounce_time_us && !sts)
+ pwrkey->last_release_time = ktime_get();
+
+ input_report_key(pwrkey->input, pwrkey->code, sts);
input_sync(pwrkey->input);

return IRQ_HANDLED;
}

+static int pm8941_pwrkey_sw_debounce_init(struct pm8941_pwrkey *pwrkey)
+{
+ unsigned int val, addr;
+ int error;
+
+ if (pwrkey->data->has_pon_pbs && !pwrkey->pon_pbs_baseaddr) {
+ dev_err(pwrkey->dev, "PON_PBS address missing, can't read HW debounce time\n");
+ return 0;
+ }
+
+ if (pwrkey->pon_pbs_baseaddr)
+ addr = pwrkey->pon_pbs_baseaddr + PON_DBC_CTL;
+ else
+ addr = pwrkey->baseaddr + PON_DBC_CTL;
+ error = regmap_read(pwrkey->regmap, addr, &val);
+ if (error)
+ return error;
+
+ if (pwrkey->subtype >= PON_SUBTYPE_GEN2_PRIMARY)
+ pwrkey->sw_debounce_time_us = 2 * USEC_PER_SEC /
+ (1 << (0xf - (val & 0xf)));
+ else
+ pwrkey->sw_debounce_time_us = 2 * USEC_PER_SEC /
+ (1 << (0x7 - (val & 0x7)));
+
+ dev_dbg(pwrkey->dev, "SW debounce time = %u us\n",
+ pwrkey->sw_debounce_time_us);
+
+ return 0;
+}
+
static int __maybe_unused pm8941_pwrkey_suspend(struct device *dev)
{
struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev);
@@ -167,6 +231,8 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
struct pm8941_pwrkey *pwrkey;
bool pull_up;
struct device *parent;
+ struct device_node *regmap_node;
+ const __be32 *addr;
u32 req_delay;
int error;

@@ -188,8 +254,10 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
pwrkey->data = of_device_get_match_data(&pdev->dev);

parent = pdev->dev.parent;
+ regmap_node = pdev->dev.of_node;
pwrkey->regmap = dev_get_regmap(parent, NULL);
if (!pwrkey->regmap) {
+ regmap_node = parent->of_node;
/*
* We failed to get regmap for parent. Let's see if we are
* a child of pon node and read regmap and reg from its
@@ -200,15 +268,21 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to locate regmap\n");
return -ENODEV;
}
+ }

- error = of_property_read_u32(parent->of_node,
- "reg", &pwrkey->baseaddr);
- } else {
- error = of_property_read_u32(pdev->dev.of_node, "reg",
- &pwrkey->baseaddr);
+ addr = of_get_address(regmap_node, 0, NULL, NULL);
+ if (!addr) {
+ dev_err(&pdev->dev, "reg property missing\n");
+ return -EINVAL;
+ }
+ pwrkey->baseaddr = be32_to_cpu(*addr);
+
+ if (pwrkey->data->has_pon_pbs) {
+ /* PON_PBS base address is optional */
+ addr = of_get_address(regmap_node, 1, NULL, NULL);
+ if (addr)
+ pwrkey->pon_pbs_baseaddr = be32_to_cpu(*addr);
}
- if (error)
- return error;

pwrkey->irq = platform_get_irq(pdev, 0);
if (pwrkey->irq < 0)
@@ -217,7 +291,14 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_REV2,
&pwrkey->revision);
if (error) {
- dev_err(&pdev->dev, "failed to set debounce: %d\n", error);
+ dev_err(&pdev->dev, "failed to read revision: %d\n", error);
+ return error;
+ }
+
+ error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_SUBTYPE,
+ &pwrkey->subtype);
+ if (error) {
+ dev_err(&pdev->dev, "failed to read subtype: %d\n", error);
return error;
}

@@ -255,6 +336,12 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
}
}

+ if (pwrkey->data->needs_sw_debounce) {
+ error = pm8941_pwrkey_sw_debounce_init(pwrkey);
+ if (error)
+ return error;
+ }
+
if (pwrkey->data->pull_up_bit) {
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + PON_PULL_CTL,
@@ -316,6 +403,8 @@ static const struct pm8941_data pwrkey_data = {
.phys = "pm8941_pwrkey/input0",
.supports_ps_hold_poff_config = true,
.supports_debounce_config = true,
+ .needs_sw_debounce = true,
+ .has_pon_pbs = false,
};

static const struct pm8941_data resin_data = {
@@ -325,6 +414,8 @@ static const struct pm8941_data resin_data = {
.phys = "pm8941_resin/input0",
.supports_ps_hold_poff_config = true,
.supports_debounce_config = true,
+ .needs_sw_debounce = true,
+ .has_pon_pbs = false,
};

static const struct pm8941_data pon_gen3_pwrkey_data = {
@@ -333,6 +424,8 @@ static const struct pm8941_data pon_gen3_pwrkey_data = {
.phys = "pmic_pwrkey/input0",
.supports_ps_hold_poff_config = false,
.supports_debounce_config = false,
+ .needs_sw_debounce = true,
+ .has_pon_pbs = true,
};

static const struct pm8941_data pon_gen3_resin_data = {
@@ -341,6 +434,8 @@ static const struct pm8941_data pon_gen3_resin_data = {
.phys = "pmic_resin/input0",
.supports_ps_hold_poff_config = false,
.supports_debounce_config = false,
+ .needs_sw_debounce = true,
+ .has_pon_pbs = true,
};

static const struct of_device_id pm8941_pwr_key_id_table[] = {
--
2.34.1