This is a port of linux/drivers/input/keyboard/snvs_pwrkey.c, adding SNVS power key support for imx chips after i.mx6sx
ON/OFF key is used to power on/off whole system. Signed-off-by: Siddharth Menon <[email protected]> Cc: Tom Rini <[email protected]> --- drivers/button/Kconfig | 8 ++ drivers/button/Makefile | 3 +- drivers/button/button-snvs-pwrkey.c | 154 ++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 drivers/button/button-snvs-pwrkey.c diff --git a/drivers/button/Kconfig b/drivers/button/Kconfig index 6cae16fcc8b..f9a1c3e2437 100644 --- a/drivers/button/Kconfig +++ b/drivers/button/Kconfig @@ -48,4 +48,12 @@ config BUTTON_QCOM_PMIC on Qualcomm SoCs. These will be configured as the Enter and Down keys respectively, allowing navigation of bootmenu with buttons on device. +config BUTTON_SNVS_PWRKEY + bool "IMX SNVS Power Key Driver" + depends on BUTTON + depends on MACH_IMX + help + This is the SNVS powerkey driver for the Freescale i.MX application + processors. + endmenu diff --git a/drivers/button/Makefile b/drivers/button/Makefile index 68555081a47..f617574a128 100644 --- a/drivers/button/Makefile +++ b/drivers/button/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_BUTTON) += button-uclass.o obj-$(CONFIG_BUTTON_ADC) += button-adc.o obj-$(CONFIG_BUTTON_GPIO) += button-gpio.o -obj-$(CONFIG_BUTTON_QCOM_PMIC) += button-qcom-pmic.o \ No newline at end of file +obj-$(CONFIG_BUTTON_QCOM_PMIC) += button-qcom-pmic.o +obj-$(CONFIG_BUTTON_SNVS_PWRKEY) += snvs_pwrkey.o \ No newline at end of file diff --git a/drivers/button/button-snvs-pwrkey.c b/drivers/button/button-snvs-pwrkey.c new file mode 100644 index 00000000000..ba8120abe18 --- /dev/null +++ b/drivers/button/button-snvs-pwrkey.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the IMX SNVS ON/OFF Power Key + * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2026 Siddharth Menon <[email protected]> + */ + +#include <button.h> +#include <dm.h> +#include <dt-bindings/input/linux-event-codes.h> +#include <log.h> +#include <regmap.h> +#include <syscon.h> +#include <time.h> +#include <linux/bitops.h> +#include <linux/err.h> + +#define SNVS_HPVIDR1_REG 0xBF8 +#define SNVS_LPSR_REG 0x4C // LP Status Register +#define SNVS_LPCR_REG 0x38 // LP Control Register +#define SNVS_HPSR_REG 0x14 +#define SNVS_HPSR_BTN BIT(6) +#define SNVS_LPSR_SPO BIT(18) +#define SNVS_LPCR_DEP_EN BIT(5) +#define SNVS_LPCR_BPT_SHIFT 16 +#define SNVS_LPCR_BPT_MASK (3 << SNVS_LPCR_BPT_SHIFT) + +#define DEBOUNCE_TIME 30000 // microseconds + +struct pwrkey_drv_data { + struct regmap *snvs; + int keycode; + ulong last_update_time; + bool old_state; + bool keystate; + u8 minor_rev; + bool click_pending; +}; + +/* + * Only report a new event once it has been held for + * DEBOUNCE_TIME, on both the press and release edge. + */ +static enum button_state_t imx_snvs_pwrkey_poll_state(struct pwrkey_drv_data *pdata) +{ + bool pressed; + uint state; + + if (regmap_read(pdata->snvs, SNVS_HPSR_REG, &state)) + return pdata->keystate ? BUTTON_ON : BUTTON_OFF; + + pressed = !!(state & SNVS_HPSR_BTN); + + if (pressed != pdata->old_state) { + pdata->old_state = pressed; + pdata->last_update_time = get_timer_us(0); + } else if (get_timer_us(0) - pdata->last_update_time >= DEBOUNCE_TIME) { + pdata->keystate = pressed; + } + + return pdata->keystate ? BUTTON_ON : BUTTON_OFF; +} + +static enum button_state_t imx_snvs_pwrkey_get_state(struct udevice *dev) +{ + struct pwrkey_drv_data *pdata = dev_get_priv(dev); + + return imx_snvs_pwrkey_poll_state(pdata); +} + +static int imx_snvs_pwrkey_get_code(struct udevice *dev) +{ + struct pwrkey_drv_data *pdata = dev_get_priv(dev); + + return pdata->keycode; +} + +static int imx_snvs_pwrkey_bind(struct udevice *dev) +{ + struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev); + + uc_plat->label = "Power Button"; + + return 0; +} + +static int imx_snvs_pwrkey_probe(struct udevice *dev) +{ + struct pwrkey_drv_data *pdata = dev_get_priv(dev); + unsigned int val; + unsigned int bpt; + unsigned int vid; + int error; + + pdata->snvs = syscon_regmap_lookup_by_phandle(dev, "regmap"); + if (IS_ERR(pdata->snvs)) { + log_err("%s: can't get snvs syscon\n", dev->name); + return PTR_ERR(pdata->snvs); + } + + pdata->keycode = dev_read_u32_default(dev, "linux,keycode", KEY_POWER); + + error = dev_read_u32(dev, "power-off-time-sec", &val); + if (!error) { + switch (val) { + case 0: + bpt = 0x3; + break; + case 5: + case 10: + case 15: + bpt = (val / 5) - 1; + break; + default: + log_err("%s: power-off-time-sec %u out of range\n", + dev->name, val); + return -EINVAL; + } + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, + SNVS_LPCR_BPT_MASK, + bpt << SNVS_LPCR_BPT_SHIFT); + } + + regmap_read(pdata->snvs, SNVS_HPVIDR1_REG, &vid); + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_DEP_EN, + SNVS_LPCR_DEP_EN); + + /* clear the unexpected interrupt before driver ready */ + regmap_write(pdata->snvs, SNVS_LPSR_REG, SNVS_LPSR_SPO); + + return 0; +} + +static const struct button_ops imx_snvs_pwrkey_ops = { + .get_state = imx_snvs_pwrkey_get_state, + .get_code = imx_snvs_pwrkey_get_code, +}; + +static const struct udevice_id imx_snvs_pwrkey_ids[] = { + { .compatible = "fsl,sec-v4.0-pwrkey" }, + { } +}; + +U_BOOT_DRIVER(imx_snvs_pwrkey) = { + .name = "snvs_pwrkey", + .id = UCLASS_BUTTON, + .of_match = imx_snvs_pwrkey_ids, + .bind = imx_snvs_pwrkey_bind, + .probe = imx_snvs_pwrkey_probe, + .ops = &imx_snvs_pwrkey_ops, + .priv_auto = sizeof(struct pwrkey_drv_data), +}; -- 2.47.3
