* Re: [PATCH 09/21] input: gpio-keys: make legacy gpiolib optional
From: Dmitry Torokhov @ 2025-08-11 19:21 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Matti Vaittinen, Arnd Bergmann, Bartosz Golaszewski,
Linus Walleij, linux-gpio, Lee Jones, Arnd Bergmann,
Gatien Chevallier, Fabrice Gasnier, Bartosz Golaszewski,
Thomas Gleixner, Charles Keepax, Krzysztof Kozlowski,
Christophe JAILLET, linux-input, linux-kernel
In-Reply-To: <aJnng9z9pUTFI49x@smile.fi.intel.com>
On Mon, Aug 11, 2025 at 03:52:19PM +0300, Andy Shevchenko wrote:
> On Mon, Aug 11, 2025 at 01:34:43PM +0300, Matti Vaittinen wrote:
> > On 08/08/2025 18:17, Arnd Bergmann wrote:
>
> ...
>
> > As such, this patch seems Ok to me, you can treat this as an ack :) This,
> > however made me ponder following - is this the tight way to handle the
> > power-button IRQ? I don't see any other MFD devices doing this in same way,
> > although I am pretty sure there are other PMICs with similar power-button
> > IRQ...
> >
> > I see for example the "drivers/mfd/rt5120.c" to invoke
> > "drivers/input/misc/rt5120-pwrkey.c" instead of using the gpio-keys. This,
> > however, feels like code duplication to me. I'd rather kept using the
> > gpio-keys, but seeing:
> >
> > git grep KEY_POWER drivers/mfd/
> > drivers/mfd/rohm-bd71828.c: .code = KEY_POWER,
> > drivers/mfd/rohm-bd718x7.c: .code = KEY_POWER,
> >
> > makes me wonder if there is more widely used (better) way?
>
> FWIW, on Intel platforms that use power button by PMIC we add a special driver
> for each of such cases.
If we can make gpio-keys work for various power buttons that would be
great IMO. The MFD drivers in question already are using device tree,
but they do not define/expect nodes for the power buttons. If the nodes
were there then I think gpio-keys would work out of the box?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] HID: lg-g15 - Add support for Logitech G13.
From: Markus Elfring @ 2025-08-11 17:10 UTC (permalink / raw)
To: Leo L. Schwab, linux-input
Cc: LKML, Benjamin Tissoires, Hans de Goede, Jiri Kosina, Kate Hsuan
In-Reply-To: <20250810225617.1006272-2-ewhac@ewhac.org>
…
> +++ b/drivers/hid/hid-lg-g15.c
…
> +static int lg_g13_kbd_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
> +{
…
> + mutex_lock(&g15->mutex);
> + ret = lg_g13_kbd_led_write(g15, g15_led, brightness);
> + mutex_unlock(&g15->mutex);
> +
> + return ret;
> +}
…
Under which circumstances would you become interested to apply a statement
like “guard(mutex)(&g15->mutex);”?
https://elixir.bootlin.com/linux/v6.16/source/include/linux/mutex.h#L225
Regards,
Markus
^ permalink raw reply
* Re: [PATCH] HID: lg-g15 - Add support for Logitech G13.
From: Markus Elfring @ 2025-08-11 17:00 UTC (permalink / raw)
To: Leo L. Schwab, linux-input
Cc: LKML, Benjamin Tissoires, Hans de Goede, Jiri Kosina, Kate Hsuan
In-Reply-To: <20250810225617.1006272-2-ewhac@ewhac.org>
…
> This patch supports input event generation for …
See also once more:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v6.16#n94
Regards,
Markus
^ permalink raw reply
* [PATCH v3] gpiolib: acpi: Program debounce when finding GPIO
From: Mario Limonciello (AMD) @ 2025-08-11 16:43 UTC (permalink / raw)
To: Hans de Goede, Mika Westerberg, Andy Shevchenko, Linus Walleij,
Bartosz Golaszewski
Cc: open list:GPIO ACPI SUPPORT, open list:GPIO ACPI SUPPORT,
open list:INPUT (KEYBOARD, MOUSE, JOYSTICK, TOUCHSCREEN)...,
Mario Limonciello
When soc-button-array looks up the GPIO to use it calls acpi_find_gpio()
which will parse _CRS.
acpi_find_gpio.cold (drivers/gpio/gpiolib-acpi-core.c:953)
gpiod_find_and_request (drivers/gpio/gpiolib.c:4598 drivers/gpio/gpiolib.c:4625)
gpiod_get_index (drivers/gpio/gpiolib.c:4877)
The GPIO is setup basically, but the debounce information is discarded.
The platform will assert what debounce should be in _CRS, so program it
at the time it's available.
As this is considered non fatal if it fails, introduce a helper for
programming debounce and show a warning when failing to program.
Reviewed-by: Hans de Goede <hansg@kernel.org>
Signed-off-by: Mario Limonciello (AMD) <superm1@kernel.org>
---
v3:
* squash patches 1 and 2 together and drop the extra fatal hunk
v2:
* https://lore.kernel.org/all/20250625181342.3175969-1-superm1@kernel.org/
---
drivers/gpio/gpiolib-acpi-core.c | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/drivers/gpio/gpiolib-acpi-core.c b/drivers/gpio/gpiolib-acpi-core.c
index 12b24a717e43..15222bfc25bb 100644
--- a/drivers/gpio/gpiolib-acpi-core.c
+++ b/drivers/gpio/gpiolib-acpi-core.c
@@ -291,6 +291,17 @@ acpi_gpio_to_gpiod_flags(const struct acpi_resource_gpio *agpio, int polarity)
return GPIOD_ASIS;
}
+static void acpi_set_debounce_timeout(struct gpio_desc *desc, unsigned int timeout)
+{
+ int ret;
+
+ /* ACPI uses hundredths of milliseconds units */
+ ret = gpio_set_debounce_timeout(desc, timeout * 10);
+ if (ret)
+ dev_warn(&desc->gdev->dev,
+ "Failed to set debounce-timeout: %d\n", ret);
+}
+
static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip,
struct acpi_resource_gpio *agpio,
unsigned int index,
@@ -300,18 +311,12 @@ static struct gpio_desc *acpi_request_own_gpiod(struct gpio_chip *chip,
enum gpiod_flags flags = acpi_gpio_to_gpiod_flags(agpio, polarity);
unsigned int pin = agpio->pin_table[index];
struct gpio_desc *desc;
- int ret;
desc = gpiochip_request_own_desc(chip, pin, label, polarity, flags);
if (IS_ERR(desc))
return desc;
- /* ACPI uses hundredths of milliseconds units */
- ret = gpio_set_debounce_timeout(desc, agpio->debounce_timeout * 10);
- if (ret)
- dev_warn(chip->parent,
- "Failed to set debounce-timeout for pin 0x%04X, err %d\n",
- pin, ret);
+ acpi_set_debounce_timeout(desc, agpio->debounce_timeout);
return desc;
}
@@ -957,6 +962,7 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode,
acpi_gpio_update_gpiod_flags(dflags, &info);
acpi_gpio_update_gpiod_lookup_flags(lookupflags, &info);
+ acpi_set_debounce_timeout(desc, info.debounce);
return desc;
}
@@ -1025,10 +1031,7 @@ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *con_id,
if (ret < 0)
return ret;
- /* ACPI uses hundredths of milliseconds units */
- ret = gpio_set_debounce_timeout(desc, info.debounce * 10);
- if (ret)
- return ret;
+ acpi_set_debounce_timeout(desc, info.debounce);
irq_flags = acpi_dev_get_irq_type(info.triggering,
info.polarity);
base-commit: 8f5ae30d69d7543eee0d70083daf4de8fe15d585
--
2.50.1
^ permalink raw reply related
* Re: [PATCH v2 2/3] Input: aw86927 - add driver for Awinic AW86927
From: Dmitry Torokhov @ 2025-08-11 16:35 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Luca Weiss, linux-input, devicetree, linux-kernel,
linux-arm-msm
In-Reply-To: <20250811-aw86927-v2-2-64be8f3da560@fairphone.com>
Hi Griffin,
On Mon, Aug 11, 2025 at 01:12:02PM +0200, Griffin Kroah-Hartman wrote:
> Add support for the I2C-connected Awinic AW86927 LRA haptic driver.
>
> This driver includes a hardcoded sine waveform to be uploaded to the
> AW86927's SRAM for haptic playback.
> This driver does not currently support all the capabilities of the
> AW86927, such as F0 calibration, RTP mode, and CONT mode.
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
Mostly just a few nits.
> ---
> drivers/input/misc/Kconfig | 11 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/aw86927.c | 841 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 853 insertions(+)
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index f5496ca0c0d2..20a5f552d9f4 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -126,6 +126,17 @@ config INPUT_ATMEL_CAPTOUCH
> To compile this driver as a module, choose M here: the
> module will be called atmel_captouch.
>
> +config INPUT_AW86927
> + tristate "Awinic AW86927 Haptic Driver Support"
> + depends on I2C && INPUT
> + select INPUT_FF_MEMLESS
> + select REGMAP_I2C
> + help
> + Say Y here if you have an Awinic AW86927 haptic chip.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called aw86927.
> +
> config INPUT_BBNSM_PWRKEY
> tristate "NXP BBNSM Power Key Driver"
> depends on ARCH_MXC || COMPILE_TEST
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 6d91804d0a6f..a311a84d1b70 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_INPUT_ATC260X_ONKEY) += atc260x-onkey.o
> obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
> obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o
> obj-$(CONFIG_INPUT_ATMEL_CAPTOUCH) += atmel_captouch.o
> +obj-$(CONFIG_INPUT_AW86927) += aw86927.o
> obj-$(CONFIG_INPUT_BBNSM_PWRKEY) += nxp-bbnsm-pwrkey.o
> obj-$(CONFIG_INPUT_BMA150) += bma150.o
> obj-$(CONFIG_INPUT_CM109) += cm109.o
> diff --git a/drivers/input/misc/aw86927.c b/drivers/input/misc/aw86927.c
> new file mode 100644
> index 000000000000..7f4946baeb48
> --- /dev/null
> +++ b/drivers/input/misc/aw86927.c
> @@ -0,0 +1,841 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> + *
> + * Partially based on vendor driver:
> + * Copyright (c) 2021 AWINIC Technology CO., LTD
> + *
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#define AW86927_RSTCFG (0x00)
No need to encase simple constants into parenthesis.
> +#define AW86927_RSTCFG_SOFTRST (0xaa)
> +
> +#define AW86927_SYSINT (0x02)
> +#define AW86927_SYSINT_BST_SCPI BIT(7)
You need to add include for BIT(). Double check if you include all the
headers for APIs used directly in the driver.
> +#define AW86927_SYSINT_BST_OVPI BIT(6)
> +#define AW86927_SYSINT_UVLI BIT(5)
> +#define AW86927_SYSINT_FF_AEI BIT(4)
> +#define AW86927_SYSINT_FF_AFI BIT(3)
> +#define AW86927_SYSINT_OCDI BIT(2)
> +#define AW86927_SYSINT_OTI BIT(1)
> +#define AW86927_SYSINT_DONEI BIT(0)
> +
> +#define AW86927_SYSINTM (0x03)
Maybe add _REG suffix so readers are not tempted to read this as "mask"?
> +#define AW86927_SYSINTM_BST_OVPM BIT(6)
> +#define AW86927_SYSINTM_FF_AEM BIT(4)
> +#define AW86927_SYSINTM_FF_AFM BIT(3)
> +#define AW86927_SYSINTM_DONEM BIT(0)
> +
> +#define AW86927_PLAYCFG1 (0x06)
> +#define AW86927_PLAYCFG1_BST_MODE_MASK GENMASK(7, 7)
> +#define AW86927_PLAYCFG1_BST_MODE_BYPASS (0)
> +#define AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK GENMASK(6, 0)
> +#define AW86927_PLAYCFG1_BST_8500MV (0x50)
> +
> +#define AW86927_PLAYCFG2 (0x07)
> +
> +#define AW86927_PLAYCFG3 (0x08)
> +#define AW86927_PLAYCFG3_AUTO_BST_MASK GENMASK(4, 4)
> +#define AW86927_PLAYCFG3_AUTO_BST_ENABLE (1)
> +#define AW86927_PLAYCFG3_AUTO_BST_DISABLE (0)
> +#define AW86927_PLAYCFG3_PLAY_MODE_MASK GENMASK(1, 0)
> +#define AW86927_PLAYCFG3_PLAY_MODE_RAM (0)
> +
> +#define AW86927_PLAYCFG4 (0x09)
> +#define AW86927_PLAYCFG4_STOP BIT(1)
> +#define AW86927_PLAYCFG4_GO BIT(0)
> +
> +#define AW86927_WAVCFG1 (0x0a)
> +#define AW86927_WAVCFG1_WAVSEQ1_MASK GENMASK(6, 0)
> +
> +#define AW86927_WAVCFG2 (0x0b)
> +#define AW86927_WAVCFG2_WAVSEQ2_MASK GENMASK(6, 0)
> +
> +#define AW86927_WAVCFG9 (0x12)
> +#define AW86927_WAVCFG9_SEQ1LOOP_MASK GENMASK(7, 4)
> +#define AW86927_WAVCFG9_SEQ1LOOP_INFINITELY (0x0f)
> +
> +#define AW86927_CONTCFG1 (0x18)
> +#define AW86927_CONTCFG1_BRK_BST_MD_MASK GENMASK(6, 6)
> +
> +#define AW86927_CONTCFG5 (0x1c)
> +#define AW86927_CONTCFG5_BST_BRK_GAIN_MASK GENMASK(7, 4)
> +#define AW86927_CONTCFG5_BRK_GAIN_MASK GENMASK(3, 0)
> +
> +#define AW86927_CONTCFG10 (0x21)
> +#define AW86927_CONTCFG10_BRK_TIME_MASK GENMASK(7, 0)
> +#define AW86927_CONTCFG10_BRK_TIME_DEFAULT (8)
> +
> +#define AW86927_CONTCFG13 (0x24)
> +#define AW86927_CONTCFG13_TSET_MASK GENMASK(7, 4)
> +#define AW86927_CONTCFG13_BEME_SET_MASK GENMASK(3, 0)
> +
> +#define AW86927_BASEADDRH (0x2d)
> +#define AW86927_BASEADDRL (0x2e)
> +
> +#define AW86927_GLBRD5 (0x3f)
> +#define AW86927_GLBRD5_STATE_MASK GENMASK(3, 0)
> +#define AW86927_GLBRD5_STATE_STANDBY (0)
> +
> +#define AW86927_RAMADDRH (0x40)
> +#define AW86927_RAMADDRL (0x41)
> +#define AW86927_RAMDATA (0x42)
> +
> +#define AW86927_SYSCTRL3 (0x45)
> +#define AW86927_SYSCTRL3_STANDBY_MASK GENMASK(5, 5)
> +#define AW86927_SYSCTRL3_STANDBY_ON (1)
> +#define AW86927_SYSCTRL3_STANDBY_OFF (0)
> +#define AW86927_SYSCTRL3_EN_RAMINIT_MASK GENMASK(2, 2)
> +#define AW86927_SYSCTRL3_EN_RAMINIT_ON (1)
> +#define AW86927_SYSCTRL3_EN_RAMINIT_OFF (0)
> +
> +#define AW86927_SYSCTRL4 (0x46)
> +#define AW86927_SYSCTRL4_WAVDAT_MODE_MASK GENMASK(6, 5)
> +#define AW86927_SYSCTRL4_WAVDAT_24K (0)
> +#define AW86927_SYSCTRL4_INT_EDGE_MODE_MASK GENMASK(4, 4)
> +#define AW86927_SYSCTRL4_INT_EDGE_MODE_POS (0)
> +#define AW86927_SYSCTRL4_INT_MODE_MASK GENMASK(3, 3)
> +#define AW86927_SYSCTRL4_INT_MODE_EDGE (1)
> +#define AW86927_SYSCTRL4_GAIN_BYPASS_MASK GENMASK(0, 0)
> +
> +#define AW86927_PWMCFG1 (0x48)
> +#define AW86927_PWMCFG1_PRC_EN_MASK GENMASK(7, 7)
> +#define AW86927_PWMCFG1_PRC_DISABLE (0)
> +
> +#define AW86927_PWMCFG3 (0x4a)
> +#define AW86927_PWMCFG3_PR_EN_MASK GENMASK(7, 7)
> +#define AW86927_PWMCFG3_PRCTIME_MASK GENMASK(6, 0)
> +
> +#define AW86927_PWMCFG4 (0x4b)
> +#define AW86927_PWMCFG4_PRTIME_MASK GENMASK(7, 0)
> +
> +#define AW86927_VBATCTRL (0x4c)
> +#define AW86927_VBATCTRL_VBAT_MODE_MASK GENMASK(6, 6)
> +#define AW86927_VBATCTRL_VBAT_MODE_SW (0)
> +
> +#define AW86927_DETCFG1 (0x4d)
> +#define AW86927_DETCFG1_DET_GO_MASK GENMASK(1, 0)
> +#define AW86927_DETCFG1_DET_GO_DET_SEQ0 (1)
> +#define AW86927_DETCFG1_DET_GO_NA (0)
> +
> +#define AW86927_DETCFG2 (0x4e)
> +#define AW86927_DETCFG2_DET_SEQ0_MASK GENMASK(6, 3)
> +#define AW86927_DETCFG2_DET_SEQ0_VBAT (0)
> +#define AW86927_DETCFG2_D2S_GAIN_MASK GENMASK(2, 0)
> +#define AW86927_DETCFG2_D2S_GAIN_10 (4)
> +
> +#define AW86927_CHIPIDH (0x57)
> +#define AW86927_CHIPIDL (0x58)
> +#define AW86927_CHIPID (0x9270)
> +
> +#define AW86927_TMCFG (0x5b)
> +#define AW86927_TMCFG_UNLOCK (0x7d)
> +#define AW86927_TMCFG_LOCK (0x00)
> +
> +#define AW86927_ANACFG11 (0x70)
> +#define AW86927_ANACFG12 (0x71)
> +#define AW86927_ANACFG12_BST_SKIP_MASK GENMASK(7, 7)
> +#define AW86927_ANACFG12_BST_SKIP_SHUTDOWN (1)
> +
> +#define AW86927_ANACFG13 (0x72)
> +#define AW86927_ANACFG13_BST_PC_MASK GENMASK(7, 4)
> +#define AW86927_ANACFG13_BST_PEAKCUR_3P45A (6)
> +
> +#define AW86927_ANACFG15 (0x74)
> +#define AW86927_ANACFG15_BST_PEAK_MODE_MASK GENMASK(7, 7)
> +#define AW86927_ANACFG15_BST_PEAK_BACK (1)
> +
> +#define AW86927_ANACFG16 (0x75)
> +#define AW86927_ANACFG16_BST_SRC_MASK GENMASK(4, 4)
> +#define AW86927_ANACFG16_BST_SRC_3NS (0)
> +
> +/* default value of base addr */
> +#define AW86927_RAM_BASE_ADDR (0x800)
> +#define AW86927_BASEADDRH_VAL (0x08)
> +#define AW86927_BASEADDRL_VAL (0x00)
> +
> +enum aw86927_work_mode {
> + AW86927_STANDBY_MODE,
> + AW86927_RAM_MODE,
> +};
> +
> +struct aw86927_data {
> + struct work_struct play_work;
> + struct device *dev;
> + struct input_dev *input_dev;
> + struct i2c_client *client;
> + struct regmap *regmap;
> + struct gpio_desc *reset_gpio;
> + bool running;
> +};
> +
> +static const struct regmap_config aw86927_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .cache_type = REGCACHE_NONE,
> + .max_register = 0x80,
> +};
> +
> +/*
> + * Sine wave representing the magnitude of the drive to be used.
> + * Data is encoded in two's complement.
> + * round(84 * sin(x / 16.25))
> + */
> +static const uint8_t aw86927_waveform[] = {
> + 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x1a, 0x1f, 0x23, 0x28, 0x2d, 0x31, 0x35,
> + 0x39, 0x3d, 0x41, 0x44, 0x47, 0x4a, 0x4c, 0x4f, 0x51, 0x52, 0x53, 0x54,
> + 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x52, 0x51, 0x4f, 0x4d, 0x4a, 0x47,
> + 0x44, 0x41, 0x3d, 0x3a, 0x36, 0x31, 0x2d, 0x28, 0x24, 0x1f, 0x1a, 0x15,
> + 0x10, 0x0a, 0x05, 0x00, 0xfc, 0xf6, 0xf1, 0xec, 0xe7, 0xe2, 0xdd, 0xd8,
> + 0xd4, 0xcf, 0xcb, 0xc7, 0xc3, 0xbf, 0xbc, 0xb9, 0xb6, 0xb4, 0xb1, 0xb0,
> + 0xae, 0xad, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xac, 0xae, 0xaf, 0xb1,
> + 0xb3, 0xb6, 0xb8, 0xbc, 0xbf, 0xc2, 0xc6, 0xca, 0xce, 0xd3, 0xd7, 0xdc,
> + 0xe1, 0xe6, 0xeb, 0xf0, 0xf5, 0xfb
> +};
> +
> +struct aw86927_sram_waveform_header {
> + uint8_t version;
> + struct {
> + __be16 start_address;
> + __be16 end_address;
> + } __packed waveform_address[1];
Why does this need to be an array?
> +} __packed;
> +
> +static const struct aw86927_sram_waveform_header sram_waveform_header = {
> + .version = 0x01,
> + .waveform_address = {
> + /* Simple sine wave defined above */
> + {
> + .start_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
> + sizeof(struct aw86927_sram_waveform_header)),
> + .end_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
> + sizeof(struct aw86927_sram_waveform_header) +
> + ARRAY_SIZE(aw86927_waveform) - 1),
> + }
> + }
> +};
> +
> +static int aw86927_wait_enter_standby(struct aw86927_data *haptics)
> +{
> + unsigned int reg_val;
> + int err;
> +
> + err = regmap_read_poll_timeout(haptics->regmap,
> + AW86927_GLBRD5, reg_val,
> + (FIELD_GET(AW86927_GLBRD5_STATE_MASK, reg_val) == AW86927_GLBRD5_STATE_STANDBY),
> + 2500, 2500 * 100);
> +
> + if (err)
> + dev_err(haptics->dev, "did not enter standby: %d\n", err);
My preference is explicit returns in error paths. So:
if (err) {
dev_err(...);
return err;
}
return 0;
> + return err;
> +}
> +
> +static int aw86927_play_mode(struct aw86927_data *haptics, uint8_t play_mode)
> +{
> + int err;
> +
> + switch (play_mode) {
> + case AW86927_STANDBY_MODE:
> + /* Briefly toggle standby, then toggle back to standby off */
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL3,
> + AW86927_SYSCTRL3_STANDBY_MASK,
> + FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
> + AW86927_SYSCTRL3_STANDBY_ON));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL3,
> + AW86927_SYSCTRL3_STANDBY_MASK,
> + FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
> + AW86927_SYSCTRL3_STANDBY_OFF));
> + if (err)
> + return err;
> + break;
> + case AW86927_RAM_MODE:
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_PLAYCFG3,
> + AW86927_PLAYCFG3_PLAY_MODE_MASK,
> + FIELD_PREP(AW86927_PLAYCFG3_PLAY_MODE_MASK,
> + AW86927_PLAYCFG3_PLAY_MODE_RAM));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_PLAYCFG1,
> + AW86927_PLAYCFG1_BST_MODE_MASK,
> + FIELD_PREP(AW86927_PLAYCFG1_BST_MODE_MASK,
> + AW86927_PLAYCFG1_BST_MODE_BYPASS));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_VBATCTRL,
> + AW86927_VBATCTRL_VBAT_MODE_MASK,
> + FIELD_PREP(AW86927_VBATCTRL_VBAT_MODE_MASK,
> + AW86927_VBATCTRL_VBAT_MODE_SW));
> + if (err)
> + return err;
> + break;
> + }
> + return 0;
> +}
> +
> +static int aw86927_stop(struct aw86927_data *haptics)
> +{
> + int err;
> +
> + err = regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_STOP);
> + if (err) {
> + dev_err(haptics->dev, "Failed to stop playback: %d\n", err);
> + return err;
> + }
> +
> + err = aw86927_wait_enter_standby(haptics);
> + if (err) {
> + dev_err(haptics->dev, "Failed to enter standby, trying to force it\n");
> + err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
> + if (err)
> + return err;
> + }
> + return 0;
> +}
> +
> +static int aw86927_haptics_play(struct input_dev *dev, void *data, struct ff_effect *effect)
> +{
> + struct aw86927_data *haptics = input_get_drvdata(dev);
> + int level;
> +
> + level = effect->u.rumble.strong_magnitude;
> + if (!level)
> + level = effect->u.rumble.weak_magnitude;
> +
> + /* If already running, don't restart playback */
Why not if effect parameters are changing? Also what if someone is
issuing stop for already stopped effect?
> + if (haptics->running && level)
> + return 0;
> +
> + haptics->running = level;
> + schedule_work(&haptics->play_work);
> +
> + return 0;
> +}
> +
> +static int aw86927_play_sine(struct aw86927_data *haptics)
> +{
> + int err;
> +
> + err = aw86927_stop(haptics);
> + if (err)
> + return err;
> +
> + err = aw86927_play_mode(haptics, AW86927_RAM_MODE);
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap, AW86927_PLAYCFG3,
> + AW86927_PLAYCFG3_AUTO_BST_MASK,
> + FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
> + AW86927_PLAYCFG3_AUTO_BST_ENABLE));
> + if (err)
> + return err;
> +
> + /* Set waveseq 1 to the first wave */
> + err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG1,
> + AW86927_WAVCFG1_WAVSEQ1_MASK,
> + FIELD_PREP(AW86927_WAVCFG1_WAVSEQ1_MASK,
> + 1));
> + if (err)
> + return err;
> +
> + /* set wavseq 2 to zero */
> + err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG2,
> + AW86927_WAVCFG2_WAVSEQ2_MASK,
> + FIELD_PREP(AW86927_WAVCFG2_WAVSEQ2_MASK,
> + 0));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_WAVCFG9,
> + AW86927_WAVCFG9_SEQ1LOOP_MASK,
> + FIELD_PREP(AW86927_WAVCFG9_SEQ1LOOP_MASK,
> + AW86927_WAVCFG9_SEQ1LOOP_INFINITELY));
> + if (err)
> + return err;
> +
> + /* set gain to value lower than 0x80 to avoid distorted playback */
> + err = regmap_write(haptics->regmap, AW86927_PLAYCFG2, 0x7c);
> + if (err)
> + return err;
> +
> + /* Start playback */
> + return regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_GO);
Explicit error return in functions with multiple failure points please.
Also applies to the rest of the code.
> +}
> +
> +static void aw86927_close(struct input_dev *input)
> +{
> + struct aw86927_data *haptics = input_get_drvdata(input);
> + struct device *dev = &haptics->client->dev;
> + int err;
> +
> + cancel_work_sync(&haptics->play_work);
> +
> + err = aw86927_stop(haptics);
> + if (err)
> + dev_err(dev, "Failed to close the Driver: %d\n", err);
> +}
> +
> +static void aw86927_haptics_play_work(struct work_struct *work)
> +{
> + struct aw86927_data *haptics =
> + container_of(work, struct aw86927_data, play_work);
> + struct device *dev = &haptics->client->dev;
> + int err;
> +
> + if (haptics->running)
> + err = aw86927_play_sine(haptics);
> + else
> + err = aw86927_stop(haptics);
> +
> + if (err)
> + dev_err(dev, "Failed to execute work command: %d\n", err);
> +}
> +
> +static void aw86927_hw_reset(struct aw86927_data *haptics)
> +{
> + /* Assert reset */
> + gpiod_set_value_cansleep(haptics->reset_gpio, 1);
> + /* Wait ~1ms */
> + usleep_range(1000, 2000);
> + /* Deassert reset */
> + gpiod_set_value_cansleep(haptics->reset_gpio, 0);
> + /* Wait ~8ms until I2C is accessible */
> + usleep_range(8000, 8500);
> +}
> +
> +static int aw86927_haptic_init(struct aw86927_data *haptics)
> +{
> + int err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL4,
> + AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
> + FIELD_PREP(AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
> + AW86927_SYSCTRL4_WAVDAT_24K));
> + if (err)
> + return err;
> +
> + /* enable gain bypass */
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL4,
> + AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
> + FIELD_PREP(AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
> + 0x01));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_TMCFG,
> + AW86927_TMCFG_UNLOCK);
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_ANACFG11,
> + 0x0f);
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_ANACFG12,
> + AW86927_ANACFG12_BST_SKIP_MASK,
> + FIELD_PREP(AW86927_ANACFG12_BST_SKIP_MASK,
> + AW86927_ANACFG12_BST_SKIP_SHUTDOWN));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_ANACFG15,
> + AW86927_ANACFG15_BST_PEAK_MODE_MASK,
> + FIELD_PREP(AW86927_ANACFG15_BST_PEAK_MODE_MASK,
> + AW86927_ANACFG15_BST_PEAK_BACK));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_ANACFG16,
> + AW86927_ANACFG16_BST_SRC_MASK,
> + FIELD_PREP(AW86927_ANACFG16_BST_SRC_MASK,
> + AW86927_ANACFG16_BST_SRC_3NS));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_TMCFG,
> + AW86927_TMCFG_LOCK);
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_CONTCFG1,
> + AW86927_CONTCFG1_BRK_BST_MD_MASK,
> + FIELD_PREP(AW86927_CONTCFG1_BRK_BST_MD_MASK,
> + 0x00));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_CONTCFG5,
> + FIELD_PREP(AW86927_CONTCFG5_BST_BRK_GAIN_MASK, 0x05) |
> + FIELD_PREP(AW86927_CONTCFG5_BRK_GAIN_MASK, 0x08));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap, AW86927_CONTCFG10,
> + AW86927_CONTCFG10_BRK_TIME_MASK,
> + FIELD_PREP(AW86927_CONTCFG10_BRK_TIME_MASK,
> + AW86927_CONTCFG10_BRK_TIME_DEFAULT));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_CONTCFG13,
> + FIELD_PREP(AW86927_CONTCFG13_TSET_MASK, 0x06) |
> + FIELD_PREP(AW86927_CONTCFG13_BEME_SET_MASK, 0x02));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_DETCFG2,
> + AW86927_DETCFG2_D2S_GAIN_MASK,
> + FIELD_PREP(AW86927_DETCFG2_D2S_GAIN_MASK,
> + AW86927_DETCFG2_D2S_GAIN_10));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_PWMCFG1,
> + AW86927_PWMCFG1_PRC_EN_MASK,
> + FIELD_PREP(AW86927_PWMCFG1_PRC_EN_MASK,
> + AW86927_PWMCFG1_PRC_DISABLE));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_PWMCFG3,
> + FIELD_PREP(AW86927_PWMCFG3_PR_EN_MASK, 0x01) |
> + FIELD_PREP(AW86927_PWMCFG3_PRCTIME_MASK, 0x3f));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_PWMCFG4,
> + AW86927_PWMCFG4_PRTIME_MASK,
> + FIELD_PREP(AW86927_PWMCFG4_PRTIME_MASK,
> + 0x32));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_TMCFG,
> + AW86927_TMCFG_UNLOCK);
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_ANACFG13,
> + AW86927_ANACFG13_BST_PC_MASK,
> + FIELD_PREP(AW86927_ANACFG13_BST_PC_MASK,
> + AW86927_ANACFG13_BST_PEAKCUR_3P45A));
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_TMCFG,
> + AW86927_TMCFG_LOCK);
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_PLAYCFG1,
> + AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
> + FIELD_PREP(AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
> + AW86927_PLAYCFG1_BST_8500MV));
> + if (err)
> + return err;
> +
> + return regmap_update_bits(haptics->regmap,
> + AW86927_PLAYCFG3,
> + AW86927_PLAYCFG3_AUTO_BST_MASK,
> + FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
> + AW86927_PLAYCFG3_AUTO_BST_DISABLE));
> +}
> +
> +static int aw86927_ram_init(struct aw86927_data *haptics)
> +{
> + int err;
> +
> + err = aw86927_wait_enter_standby(haptics);
> + if (err)
> + return err;
> +
> + /* Enable SRAM init */
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL3,
> + AW86927_SYSCTRL3_EN_RAMINIT_MASK,
> + FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
> + AW86927_SYSCTRL3_EN_RAMINIT_ON));
> +
> + /* Set base address for the start of the SRAM waveforms */
> + err = regmap_write(haptics->regmap,
> + AW86927_BASEADDRH,
> + AW86927_BASEADDRH_VAL);
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_BASEADDRL,
> + AW86927_BASEADDRL_VAL);
> + if (err)
> + return err;
> +
> + /* Set start of SRAM, before the data is written it will be the same as the base */
> + err = regmap_write(haptics->regmap,
> + AW86927_RAMADDRH,
> + AW86927_BASEADDRH_VAL);
> + if (err)
> + return err;
> +
> + err = regmap_write(haptics->regmap,
> + AW86927_RAMADDRL,
> + AW86927_BASEADDRL_VAL);
> + if (err)
> + return err;
> +
> + /* Write waveform header to SRAM */
> + err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
> + &sram_waveform_header, sizeof(sram_waveform_header));
> +
> + /* Write waveform to SRAM */
> + err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
> + aw86927_waveform, ARRAY_SIZE(aw86927_waveform));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_DETCFG2,
> + AW86927_DETCFG2_DET_SEQ0_MASK,
> + FIELD_PREP(AW86927_DETCFG2_DET_SEQ0_MASK,
> + AW86927_DETCFG2_DET_SEQ0_VBAT));
> + if (err)
> + return err;
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_DETCFG1,
> + AW86927_DETCFG1_DET_GO_MASK,
> + FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
> + AW86927_DETCFG1_DET_GO_DET_SEQ0));
> + if (err)
> + return err;
> +
> + usleep_range(3000, 3500);
> +
> + err = regmap_update_bits(haptics->regmap,
> + AW86927_DETCFG1,
> + AW86927_DETCFG1_DET_GO_MASK,
> + FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
> + AW86927_DETCFG1_DET_GO_NA));
> + if (err)
> + return err;
> +
> + /* Disable SRAM init */
> + return regmap_update_bits(haptics->regmap,
> + AW86927_SYSCTRL3,
> + AW86927_SYSCTRL3_EN_RAMINIT_MASK,
> + FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
> + AW86927_SYSCTRL3_EN_RAMINIT_OFF));
> +}
> +
> +static irqreturn_t aw86927_irq(int irq, void *data)
> +{
> + struct aw86927_data *haptics = data;
> + struct device *dev = &haptics->client->dev;
> + unsigned int reg_val;
> + int err;
> +
> + err = regmap_read(haptics->regmap, AW86927_SYSINT, ®_val);
> + if (err) {
> + dev_err(dev, "Failed to read SYSINT register: %d\n", err);
> + return IRQ_NONE;
> + }
> +
> + if (reg_val & AW86927_SYSINT_BST_SCPI)
> + dev_err(dev, "Received a Short Circuit Protection interrupt\n");
> + if (reg_val & AW86927_SYSINT_BST_OVPI)
> + dev_err(dev, "Received an Over Voltage Protection interrupt\n");
> + if (reg_val & AW86927_SYSINT_UVLI)
> + dev_err(dev, "Received an Under Voltage Lock Out interrupt\n");
> + if (reg_val & AW86927_SYSINT_OCDI)
> + dev_err(dev, "Received an Over Current interrupt\n");
> + if (reg_val & AW86927_SYSINT_OTI)
> + dev_err(dev, "Received an Over Temperature interrupt\n");
> +
> + if (reg_val & AW86927_SYSINT_DONEI)
> + dev_dbg(dev, "Chip playback done!\n");
> + if (reg_val & AW86927_SYSINT_FF_AFI)
> + dev_dbg(dev, "The RTP mode FIFO is almost full!\n");
> + if (reg_val & AW86927_SYSINT_FF_AEI)
> + dev_dbg(dev, "The RTP mode FIFO is almost empty!\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int aw86927_detect(struct aw86927_data *haptics)
> +{
> + __be16 read_buf;
> + u16 chip_id;
> + int err;
> +
> + err = regmap_bulk_read(haptics->regmap, AW86927_CHIPIDH, &read_buf, 2);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Failed to read CHIPID registers\n");
> +
> + chip_id = be16_to_cpu(read_buf);
> +
> + if (chip_id != AW86927_CHIPID) {
> + dev_err(haptics->dev, "Unexpected CHIPID value 0x%x\n", chip_id);
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> +static int aw86927_probe(struct i2c_client *client)
> +{
> + struct aw86927_data *haptics;
> + unsigned int read_buf;
> + int err;
> +
> + haptics = devm_kzalloc(&client->dev, sizeof(struct aw86927_data), GFP_KERNEL);
> + if (!haptics)
> + return -ENOMEM;
> +
> + haptics->dev = &client->dev;
> + haptics->client = client;
> +
> + i2c_set_clientdata(client, haptics);
> + dev_set_drvdata(&client->dev, haptics);
No need to do this twice, first call should be enough.
> +
> + haptics->regmap = devm_regmap_init_i2c(client, &aw86927_regmap_config);
> + if (IS_ERR(haptics->regmap))
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
> + "Failed to allocate register map\n");
> +
> + haptics->input_dev = devm_input_allocate_device(haptics->dev);
> + if (!haptics->input_dev)
> + return -ENOMEM;
> +
> + haptics->reset_gpio = devm_gpiod_get(haptics->dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(haptics->reset_gpio))
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->reset_gpio),
> + "Failed to get reset gpio\n");
Is it mandatory to wire the reset pin? I see the chip supports software
reset so maybe this can be optional?
> +
> + /* Hardware reset */
> + aw86927_hw_reset(haptics);
> +
> + /* Software reset */
> + err = regmap_write(haptics->regmap, AW86927_RSTCFG, AW86927_RSTCFG_SOFTRST);
> + if (err)
> + return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
> + "Failed Software reset\n");
Do you need to issue software reset together with hardware reset? Is
one or the other not enough?
> +
> + /* Wait ~3ms until I2C is accessible */
> + usleep_range(3000, 3500);
> +
> + err = aw86927_detect(haptics);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Failed to find chip\n");
> +
> + /* IRQ config */
> + err = regmap_write(haptics->regmap, AW86927_SYSCTRL4,
> + FIELD_PREP(AW86927_SYSCTRL4_INT_MODE_MASK,
> + AW86927_SYSCTRL4_INT_MODE_EDGE) |
> + FIELD_PREP(AW86927_SYSCTRL4_INT_EDGE_MODE_MASK,
> + AW86927_SYSCTRL4_INT_EDGE_MODE_POS));
> + if (err)
> + return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
> +
> + err = regmap_write(haptics->regmap, AW86927_SYSINTM,
> + AW86927_SYSINTM_BST_OVPM |
> + AW86927_SYSINTM_FF_AEM |
> + AW86927_SYSINTM_FF_AFM |
> + AW86927_SYSINTM_DONEM);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
> +
> + err = devm_request_threaded_irq(haptics->dev, client->irq, NULL,
> + aw86927_irq, IRQF_ONESHOT, NULL, haptics);
Error handling? Also it looks like here it is safe to register the
interrupt handler early since it does not actually do anything, but
better to move it after the bulk of initialization in case it will get
expanded.
> +
> + INIT_WORK(&haptics->play_work, aw86927_haptics_play_work);
> +
> + haptics->input_dev->name = "aw86927-haptics";
> + haptics->input_dev->close = aw86927_close;
> +
> + input_set_drvdata(haptics->input_dev, haptics);
> + input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
> +
> + err = input_ff_create_memless(haptics->input_dev, NULL,
> + aw86927_haptics_play);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Failed to create FF dev\n");
> +
> + /* Set up registers */
> + err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
> +
> + err = aw86927_haptic_init(haptics);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Haptic init failed\n");
> +
> + /* RAM init, upload the waveform for playback */
> + err = aw86927_ram_init(haptics);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Failed to init aw86927 sram\n");
> +
> + err = input_register_device(haptics->input_dev);
> + if (err)
> + return dev_err_probe(haptics->dev, err, "Failed to register input device\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id aw86927_of_id[] = {
> + { .compatible = "awinic,aw86927" },
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, aw86927_of_id);
> +
> +static struct i2c_driver aw86927_driver = {
> + .driver = {
> + .name = "aw86927-haptics",
> + .of_match_table = aw86927_of_id,
> + },
> + .probe = aw86927_probe,
> +};
> +
> +module_i2c_driver(aw86927_driver);
> +
> +MODULE_AUTHOR("Griffin Kroah-Hartman <griffin.kroah@fairphone.com>");
> +MODULE_DESCRIPTION("AWINIC AW86927 LRA Haptic Driver");
> +MODULE_LICENSE("GPL");
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2 02/11] Input: add FF_HID effect type
From: Jonathan Denose @ 2025-08-11 15:26 UTC (permalink / raw)
To: tomasz.pakula.oficjalny
Cc: Jiri Kosina, Benjamin Tissoires, Dmitry Torokhov, Jonathan Corbet,
Henrik Rydberg, linux-input, linux-kernel, linux-doc,
Angela Czubak, Sean O'Brien
In-Reply-To: <c7e398e92ecc75f05b575581e79d4cebfa8efb47.camel@gmail.com>
On Mon, Aug 4, 2025 at 3:34 PM <tomasz.pakula.oficjalny@gmail.com> wrote:
>
> On Mon, 2025-08-04 at 14:11 +0000, Jonathan Denose wrote:
> > From: Angela Czubak <aczubak@google.com>
> >
> > FF_HID effect type can be used to trigger haptic feedback with HID simple
> > haptic usages.
> >
> > Signed-off-by: Angela Czubak <aczubak@google.com>
> > Co-developed-by: Jonathan Denose <jdenose@google.com>
> > Signed-off-by: Jonathan Denose <jdenose@google.com>
> > ---
> > include/uapi/linux/input.h | 22 +++++++++++++++++++++-
> > 1 file changed, 21 insertions(+), 1 deletion(-)
> >
> > diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
> > index 2557eb7b056178b2b8be98d9cea855eba1bd5aaf..3ea7c826c6fb2034e46f95cb95b84ef6f5b866df 100644
> > --- a/include/uapi/linux/input.h
> > +++ b/include/uapi/linux/input.h
> > @@ -428,6 +428,24 @@ struct ff_rumble_effect {
> > __u16 weak_magnitude;
> > };
> >
> > +/**
> > + * struct ff_hid_effect
> > + * @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
> > + * @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
> > + * @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
> > + * @intensity: strength of the effect as percentage
> > + * @repeat_count: number of times to retrigger effect
> > + * @retrigger_period: time before effect is retriggered (in ms)
> > + */
> > +struct ff_hid_effect {
> > + __u16 hid_usage;
> > + __u16 vendor_id;
> > + __u8 vendor_waveform_page;
> > + __u16 intensity;
> > + __u16 repeat_count;
> > + __u16 retrigger_period;
> > +};
>
> Wouldn't it make more sense to call this new effect ff_haptic_effect?
> hid_effect sound generic, too generic. One could say, all ff effect are
> hid effects because most ff apis (linux' included) are based on USB PID
> spec.
>
> > +
> > /**
> > * struct ff_effect - defines force feedback effect
> > * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
> > @@ -464,6 +482,7 @@ struct ff_effect {
> > struct ff_periodic_effect periodic;
> > struct ff_condition_effect condition[2]; /* One for each axis */
> > struct ff_rumble_effect rumble;
> > + struct ff_hid_effect hid;
> > } u;
> > };
> >
> > @@ -471,6 +490,7 @@ struct ff_effect {
> > * Force feedback effect types
> > */
> >
> > +#define FF_HID 0x4f
>
> Again here, FF_HID sounds confusing without having the broader context.
> Constant, Sine, Inertia, Spring are way more descriptive. FF_HAPTIC
> would be a great name to distinguish such an effect. Or maybe FF_TACTILE
> with ff_tactile_effect?
>
> > #define FF_RUMBLE 0x50
> > #define FF_PERIODIC 0x51
> > #define FF_CONSTANT 0x52
> > @@ -480,7 +500,7 @@ struct ff_effect {
> > #define FF_INERTIA 0x56
> > #define FF_RAMP 0x57
> >
> > -#define FF_EFFECT_MIN FF_RUMBLE
> > +#define FF_EFFECT_MIN FF_HID
> > #define FF_EFFECT_MAX FF_RAMP
> >
> > /*
>
> Overall, I'll keep an eye on this as I'm slowly working towards a
> proposal for a revamped and extended ff api on Linux that would make it
> fully featured (we're lacking things like device control and querying
> effects and their status, arbitrary number of axes and arbitrary axes
> themselves).
Thanks for your review, your comments make sense to me!
I'll change ff_hid_effect to ff_haptic_effect and FF_HID to FF_HAPTIC
and upload a new version of this series.
^ permalink raw reply
* Re: [PATCH 00/21] gpiolib: fence off legacy interfaces
From: Bartosz Golaszewski @ 2025-08-11 13:10 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Arnd Bergmann, Andrew Lunn, Sebastian Hesselbarth,
Gregory Clement, Russell King, Daniel Mack, Haojian Zhuang,
Robert Jarzmik, Krzysztof Kozlowski, Alim Akhtar,
Geert Uytterhoeven, Thomas Bogendoerfer, Yoshinori Sato,
Rich Felker, John Paul Adrian Glaubitz, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Dmitry Torokhov, Lee Jones, Pavel Machek, Mauro Carvalho Chehab,
Matti Vaittinen, Florian Fainelli, Jeff Johnson, Hans de Goede,
Ilpo Järvinen, Greg Kroah-Hartman, Jaroslav Kysela,
Takashi Iwai, Liam Girdwood, Mark Brown, Andy Shevchenko,
Dr. David Alan Gilbert, linux-arm-kernel, linux-kernel,
linux-samsung-soc, linux-m68k, linux-mips, linux-sh, linux-input,
linux-leds, linux-media, patches, netdev, linux-wireless, ath10k,
platform-driver-x86, linux-usb, linux-sound, Bartosz Golaszewski,
Linus Walleij, linux-gpio
In-Reply-To: <20250808151822.536879-1-arnd@kernel.org>
On Fri, 8 Aug 2025 17:17:44 +0200, Arnd Bergmann <arnd@kernel.org> said:
> From: Arnd Bergmann <arnd@arndb.de>
>
> Commit 678bae2eaa81 ("gpiolib: make legacy interfaces optional") was
> merged for linux-6.17, so now it is possible to use the legacy interfaces
> conditionally and eventually have the support left out of the kernel
> whenever it is not needed.
>
> I created six patches to force-enable CONFIG_GPIOLIB_LEGACY on the
> few (mostly ancient) platforms that still require this, plus a set of
> patches to either add the corresponding Kconfig dependencies that make
> the device drivers conditional on that symbol, or change them to no
> longer require it.
>
> The final patch ends up turning the Kconfig symbol off by default,
> which of course depends on everything else getting merged first to avoid
> build errors.
>
> I would suggest that patches 1-20 can just get merged through the
> respective maintainer trees independently when they are deemed ready,
> and the final patch can wait another merge window.
>
Oh, not at all, I'm fine sending a second PR late into the merge window to
get that done in a single cycle.
Thanks for doing this, awesome work!
Bartosz
^ permalink raw reply
* Re: [PATCH 09/21] input: gpio-keys: make legacy gpiolib optional
From: Andy Shevchenko @ 2025-08-11 12:52 UTC (permalink / raw)
To: Matti Vaittinen
Cc: Arnd Bergmann, Bartosz Golaszewski, Linus Walleij, linux-gpio,
Dmitry Torokhov, Lee Jones, Arnd Bergmann, Gatien Chevallier,
Fabrice Gasnier, Bartosz Golaszewski, Thomas Gleixner,
Charles Keepax, Krzysztof Kozlowski, Christophe JAILLET,
linux-input, linux-kernel
In-Reply-To: <b7e97aa3-8f2d-4a59-8a38-577717404865@gmail.com>
On Mon, Aug 11, 2025 at 01:34:43PM +0300, Matti Vaittinen wrote:
> On 08/08/2025 18:17, Arnd Bergmann wrote:
...
> As such, this patch seems Ok to me, you can treat this as an ack :) This,
> however made me ponder following - is this the tight way to handle the
> power-button IRQ? I don't see any other MFD devices doing this in same way,
> although I am pretty sure there are other PMICs with similar power-button
> IRQ...
>
> I see for example the "drivers/mfd/rt5120.c" to invoke
> "drivers/input/misc/rt5120-pwrkey.c" instead of using the gpio-keys. This,
> however, feels like code duplication to me. I'd rather kept using the
> gpio-keys, but seeing:
>
> git grep KEY_POWER drivers/mfd/
> drivers/mfd/rohm-bd71828.c: .code = KEY_POWER,
> drivers/mfd/rohm-bd718x7.c: .code = KEY_POWER,
>
> makes me wonder if there is more widely used (better) way?
FWIW, on Intel platforms that use power button by PMIC we add a special driver
for each of such cases.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 4/6] HID: Map more automobile simulation inputs
From: Jiri Kosina @ 2025-08-11 12:00 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, Benjamin Tissoires, linux-input
In-Reply-To: <20250808043017.1953101-5-vi@endrift.com>
On Thu, 7 Aug 2025, Vicki Pfau wrote:
> The HID usage tables section 5.3 specify clutch and shifter values that had
> previously been ignored. As the ABS_CLUTCH and ABS_SHIFTER bits now exist,
> we should use them appropriately.
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
FWIW
Acked-by: Jiri Kosina <jkosina@suse.com>
so that it could be merged together with the rest of the series.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 2/2] HID: input: report battery status changes immediately
From: Jiri Kosina @ 2025-08-11 11:48 UTC (permalink / raw)
To: José Expósito; +Cc: bentiss, luguohong, linux-input, linux-kernel
In-Reply-To: <20250806073944.5310-2-jose.exposito89@gmail.com>
On Wed, 6 Aug 2025, José Expósito wrote:
> When the battery status changes, report the change immediately to user
> space.
Could you please make the changelog a little bit more elaborative, i.e.
why is it needed, what user-visible behavior change/improvement it brings,
etc.
I know it's in the e-mail thread, but at least some summary should go also
to the commit itself.
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
From: Konrad Dybcio @ 2025-08-11 11:40 UTC (permalink / raw)
To: Griffin Kroah-Hartman, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20250811-aw86927-v2-3-64be8f3da560@fairphone.com>
On 8/11/25 1:12 PM, Griffin Kroah-Hartman wrote:
> Add the required node for haptic playback (Awinic AW86927).
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> ---
I'll hit enter harder this time! ;)
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* Re: [PATCH 1/2] HID: intel-thc-hid: intel-quicki2c: Fix ACPI dsd ICRS/ISUB length
From: Jiri Kosina @ 2025-08-11 11:39 UTC (permalink / raw)
To: Aaron Ma; +Cc: even.xu, xinpeng.sun, bentiss, linux-input, linux-kernel
In-Reply-To: <20250803065726.2895470-1-aaron.ma@canonical.com>
Both patches now applied to hid.git#for-6.17/upstream-fixes, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* [PATCH v2 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
From: Griffin Kroah-Hartman @ 2025-08-11 11:12 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
In-Reply-To: <20250811-aw86927-v2-0-64be8f3da560@fairphone.com>
Add the required node for haptic playback (Awinic AW86927).
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
index 4c6cb4a644e2..9576efdf1e8d 100644
--- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
+++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts
@@ -866,7 +866,16 @@ ocp96011_sbu_mux: endpoint {
};
};
- /* AW86927FCR haptics @ 5a */
+ vibrator@5a {
+ compatible = "awinic,aw86927";
+ reg = <0x5a>;
+
+ interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&tlmm 100 GPIO_ACTIVE_LOW>;
+
+ pinctrl-0 = <&aw86927_int_default>;
+ pinctrl-names = "default";
+ };
};
&i2c2 {
@@ -1415,6 +1424,13 @@ usb_redrive_1v8_en_default: usb-redrive-1v8-en-default-state {
bias-disable;
output-high;
};
+
+ aw86927_int_default: aw86927-int-default-state {
+ pins = "gpio101";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
};
&uart5 {
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/3] dt-bindings: input: Add bindings for Awinic AW86927
From: Griffin Kroah-Hartman @ 2025-08-11 11:12 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
In-Reply-To: <20250811-aw86927-v2-0-64be8f3da560@fairphone.com>
Add bindings for the Awinic AW86927 haptic chip which can be found in
smartphones.
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
.../devicetree/bindings/input/awinic,aw86927.yaml | 48 ++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/awinic,aw86927.yaml b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
new file mode 100644
index 000000000000..b7252916bd72
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/awinic,aw86927.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Awinic AW86927 LRA Haptic IC
+
+maintainers:
+ - Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
+
+properties:
+ compatible:
+ const: awinic,aw86927
+
+ reg:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reset-gpios
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vibrator@5a {
+ compatible = "awinic,aw86927";
+ reg = <0x5a>;
+ interrupts-extended = <&tlmm 101 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&tlmm 100 GPIO_ACTIVE_LOW>;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/3] Input: aw86927 - add driver for Awinic AW86927
From: Griffin Kroah-Hartman @ 2025-08-11 11:12 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
In-Reply-To: <20250811-aw86927-v2-0-64be8f3da560@fairphone.com>
Add support for the I2C-connected Awinic AW86927 LRA haptic driver.
This driver includes a hardcoded sine waveform to be uploaded to the
AW86927's SRAM for haptic playback.
This driver does not currently support all the capabilities of the
AW86927, such as F0 calibration, RTP mode, and CONT mode.
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/aw86927.c | 841 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 853 insertions(+)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index f5496ca0c0d2..20a5f552d9f4 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -126,6 +126,17 @@ config INPUT_ATMEL_CAPTOUCH
To compile this driver as a module, choose M here: the
module will be called atmel_captouch.
+config INPUT_AW86927
+ tristate "Awinic AW86927 Haptic Driver Support"
+ depends on I2C && INPUT
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y here if you have an Awinic AW86927 haptic chip.
+
+ To compile this driver as a module, choose M here: the
+ module will be called aw86927.
+
config INPUT_BBNSM_PWRKEY
tristate "NXP BBNSM Power Key Driver"
depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 6d91804d0a6f..a311a84d1b70 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_INPUT_ATC260X_ONKEY) += atc260x-onkey.o
obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o
obj-$(CONFIG_INPUT_ATMEL_CAPTOUCH) += atmel_captouch.o
+obj-$(CONFIG_INPUT_AW86927) += aw86927.o
obj-$(CONFIG_INPUT_BBNSM_PWRKEY) += nxp-bbnsm-pwrkey.o
obj-$(CONFIG_INPUT_BMA150) += bma150.o
obj-$(CONFIG_INPUT_CM109) += cm109.o
diff --git a/drivers/input/misc/aw86927.c b/drivers/input/misc/aw86927.c
new file mode 100644
index 000000000000..7f4946baeb48
--- /dev/null
+++ b/drivers/input/misc/aw86927.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
+ *
+ * Partially based on vendor driver:
+ * Copyright (c) 2021 AWINIC Technology CO., LTD
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define AW86927_RSTCFG (0x00)
+#define AW86927_RSTCFG_SOFTRST (0xaa)
+
+#define AW86927_SYSINT (0x02)
+#define AW86927_SYSINT_BST_SCPI BIT(7)
+#define AW86927_SYSINT_BST_OVPI BIT(6)
+#define AW86927_SYSINT_UVLI BIT(5)
+#define AW86927_SYSINT_FF_AEI BIT(4)
+#define AW86927_SYSINT_FF_AFI BIT(3)
+#define AW86927_SYSINT_OCDI BIT(2)
+#define AW86927_SYSINT_OTI BIT(1)
+#define AW86927_SYSINT_DONEI BIT(0)
+
+#define AW86927_SYSINTM (0x03)
+#define AW86927_SYSINTM_BST_OVPM BIT(6)
+#define AW86927_SYSINTM_FF_AEM BIT(4)
+#define AW86927_SYSINTM_FF_AFM BIT(3)
+#define AW86927_SYSINTM_DONEM BIT(0)
+
+#define AW86927_PLAYCFG1 (0x06)
+#define AW86927_PLAYCFG1_BST_MODE_MASK GENMASK(7, 7)
+#define AW86927_PLAYCFG1_BST_MODE_BYPASS (0)
+#define AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK GENMASK(6, 0)
+#define AW86927_PLAYCFG1_BST_8500MV (0x50)
+
+#define AW86927_PLAYCFG2 (0x07)
+
+#define AW86927_PLAYCFG3 (0x08)
+#define AW86927_PLAYCFG3_AUTO_BST_MASK GENMASK(4, 4)
+#define AW86927_PLAYCFG3_AUTO_BST_ENABLE (1)
+#define AW86927_PLAYCFG3_AUTO_BST_DISABLE (0)
+#define AW86927_PLAYCFG3_PLAY_MODE_MASK GENMASK(1, 0)
+#define AW86927_PLAYCFG3_PLAY_MODE_RAM (0)
+
+#define AW86927_PLAYCFG4 (0x09)
+#define AW86927_PLAYCFG4_STOP BIT(1)
+#define AW86927_PLAYCFG4_GO BIT(0)
+
+#define AW86927_WAVCFG1 (0x0a)
+#define AW86927_WAVCFG1_WAVSEQ1_MASK GENMASK(6, 0)
+
+#define AW86927_WAVCFG2 (0x0b)
+#define AW86927_WAVCFG2_WAVSEQ2_MASK GENMASK(6, 0)
+
+#define AW86927_WAVCFG9 (0x12)
+#define AW86927_WAVCFG9_SEQ1LOOP_MASK GENMASK(7, 4)
+#define AW86927_WAVCFG9_SEQ1LOOP_INFINITELY (0x0f)
+
+#define AW86927_CONTCFG1 (0x18)
+#define AW86927_CONTCFG1_BRK_BST_MD_MASK GENMASK(6, 6)
+
+#define AW86927_CONTCFG5 (0x1c)
+#define AW86927_CONTCFG5_BST_BRK_GAIN_MASK GENMASK(7, 4)
+#define AW86927_CONTCFG5_BRK_GAIN_MASK GENMASK(3, 0)
+
+#define AW86927_CONTCFG10 (0x21)
+#define AW86927_CONTCFG10_BRK_TIME_MASK GENMASK(7, 0)
+#define AW86927_CONTCFG10_BRK_TIME_DEFAULT (8)
+
+#define AW86927_CONTCFG13 (0x24)
+#define AW86927_CONTCFG13_TSET_MASK GENMASK(7, 4)
+#define AW86927_CONTCFG13_BEME_SET_MASK GENMASK(3, 0)
+
+#define AW86927_BASEADDRH (0x2d)
+#define AW86927_BASEADDRL (0x2e)
+
+#define AW86927_GLBRD5 (0x3f)
+#define AW86927_GLBRD5_STATE_MASK GENMASK(3, 0)
+#define AW86927_GLBRD5_STATE_STANDBY (0)
+
+#define AW86927_RAMADDRH (0x40)
+#define AW86927_RAMADDRL (0x41)
+#define AW86927_RAMDATA (0x42)
+
+#define AW86927_SYSCTRL3 (0x45)
+#define AW86927_SYSCTRL3_STANDBY_MASK GENMASK(5, 5)
+#define AW86927_SYSCTRL3_STANDBY_ON (1)
+#define AW86927_SYSCTRL3_STANDBY_OFF (0)
+#define AW86927_SYSCTRL3_EN_RAMINIT_MASK GENMASK(2, 2)
+#define AW86927_SYSCTRL3_EN_RAMINIT_ON (1)
+#define AW86927_SYSCTRL3_EN_RAMINIT_OFF (0)
+
+#define AW86927_SYSCTRL4 (0x46)
+#define AW86927_SYSCTRL4_WAVDAT_MODE_MASK GENMASK(6, 5)
+#define AW86927_SYSCTRL4_WAVDAT_24K (0)
+#define AW86927_SYSCTRL4_INT_EDGE_MODE_MASK GENMASK(4, 4)
+#define AW86927_SYSCTRL4_INT_EDGE_MODE_POS (0)
+#define AW86927_SYSCTRL4_INT_MODE_MASK GENMASK(3, 3)
+#define AW86927_SYSCTRL4_INT_MODE_EDGE (1)
+#define AW86927_SYSCTRL4_GAIN_BYPASS_MASK GENMASK(0, 0)
+
+#define AW86927_PWMCFG1 (0x48)
+#define AW86927_PWMCFG1_PRC_EN_MASK GENMASK(7, 7)
+#define AW86927_PWMCFG1_PRC_DISABLE (0)
+
+#define AW86927_PWMCFG3 (0x4a)
+#define AW86927_PWMCFG3_PR_EN_MASK GENMASK(7, 7)
+#define AW86927_PWMCFG3_PRCTIME_MASK GENMASK(6, 0)
+
+#define AW86927_PWMCFG4 (0x4b)
+#define AW86927_PWMCFG4_PRTIME_MASK GENMASK(7, 0)
+
+#define AW86927_VBATCTRL (0x4c)
+#define AW86927_VBATCTRL_VBAT_MODE_MASK GENMASK(6, 6)
+#define AW86927_VBATCTRL_VBAT_MODE_SW (0)
+
+#define AW86927_DETCFG1 (0x4d)
+#define AW86927_DETCFG1_DET_GO_MASK GENMASK(1, 0)
+#define AW86927_DETCFG1_DET_GO_DET_SEQ0 (1)
+#define AW86927_DETCFG1_DET_GO_NA (0)
+
+#define AW86927_DETCFG2 (0x4e)
+#define AW86927_DETCFG2_DET_SEQ0_MASK GENMASK(6, 3)
+#define AW86927_DETCFG2_DET_SEQ0_VBAT (0)
+#define AW86927_DETCFG2_D2S_GAIN_MASK GENMASK(2, 0)
+#define AW86927_DETCFG2_D2S_GAIN_10 (4)
+
+#define AW86927_CHIPIDH (0x57)
+#define AW86927_CHIPIDL (0x58)
+#define AW86927_CHIPID (0x9270)
+
+#define AW86927_TMCFG (0x5b)
+#define AW86927_TMCFG_UNLOCK (0x7d)
+#define AW86927_TMCFG_LOCK (0x00)
+
+#define AW86927_ANACFG11 (0x70)
+#define AW86927_ANACFG12 (0x71)
+#define AW86927_ANACFG12_BST_SKIP_MASK GENMASK(7, 7)
+#define AW86927_ANACFG12_BST_SKIP_SHUTDOWN (1)
+
+#define AW86927_ANACFG13 (0x72)
+#define AW86927_ANACFG13_BST_PC_MASK GENMASK(7, 4)
+#define AW86927_ANACFG13_BST_PEAKCUR_3P45A (6)
+
+#define AW86927_ANACFG15 (0x74)
+#define AW86927_ANACFG15_BST_PEAK_MODE_MASK GENMASK(7, 7)
+#define AW86927_ANACFG15_BST_PEAK_BACK (1)
+
+#define AW86927_ANACFG16 (0x75)
+#define AW86927_ANACFG16_BST_SRC_MASK GENMASK(4, 4)
+#define AW86927_ANACFG16_BST_SRC_3NS (0)
+
+/* default value of base addr */
+#define AW86927_RAM_BASE_ADDR (0x800)
+#define AW86927_BASEADDRH_VAL (0x08)
+#define AW86927_BASEADDRL_VAL (0x00)
+
+enum aw86927_work_mode {
+ AW86927_STANDBY_MODE,
+ AW86927_RAM_MODE,
+};
+
+struct aw86927_data {
+ struct work_struct play_work;
+ struct device *dev;
+ struct input_dev *input_dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ bool running;
+};
+
+static const struct regmap_config aw86927_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_NONE,
+ .max_register = 0x80,
+};
+
+/*
+ * Sine wave representing the magnitude of the drive to be used.
+ * Data is encoded in two's complement.
+ * round(84 * sin(x / 16.25))
+ */
+static const uint8_t aw86927_waveform[] = {
+ 0x00, 0x05, 0x0a, 0x0f, 0x14, 0x1a, 0x1f, 0x23, 0x28, 0x2d, 0x31, 0x35,
+ 0x39, 0x3d, 0x41, 0x44, 0x47, 0x4a, 0x4c, 0x4f, 0x51, 0x52, 0x53, 0x54,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x52, 0x51, 0x4f, 0x4d, 0x4a, 0x47,
+ 0x44, 0x41, 0x3d, 0x3a, 0x36, 0x31, 0x2d, 0x28, 0x24, 0x1f, 0x1a, 0x15,
+ 0x10, 0x0a, 0x05, 0x00, 0xfc, 0xf6, 0xf1, 0xec, 0xe7, 0xe2, 0xdd, 0xd8,
+ 0xd4, 0xcf, 0xcb, 0xc7, 0xc3, 0xbf, 0xbc, 0xb9, 0xb6, 0xb4, 0xb1, 0xb0,
+ 0xae, 0xad, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xac, 0xae, 0xaf, 0xb1,
+ 0xb3, 0xb6, 0xb8, 0xbc, 0xbf, 0xc2, 0xc6, 0xca, 0xce, 0xd3, 0xd7, 0xdc,
+ 0xe1, 0xe6, 0xeb, 0xf0, 0xf5, 0xfb
+};
+
+struct aw86927_sram_waveform_header {
+ uint8_t version;
+ struct {
+ __be16 start_address;
+ __be16 end_address;
+ } __packed waveform_address[1];
+} __packed;
+
+static const struct aw86927_sram_waveform_header sram_waveform_header = {
+ .version = 0x01,
+ .waveform_address = {
+ /* Simple sine wave defined above */
+ {
+ .start_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
+ sizeof(struct aw86927_sram_waveform_header)),
+ .end_address = cpu_to_be16(AW86927_RAM_BASE_ADDR +
+ sizeof(struct aw86927_sram_waveform_header) +
+ ARRAY_SIZE(aw86927_waveform) - 1),
+ }
+ }
+};
+
+static int aw86927_wait_enter_standby(struct aw86927_data *haptics)
+{
+ unsigned int reg_val;
+ int err;
+
+ err = regmap_read_poll_timeout(haptics->regmap,
+ AW86927_GLBRD5, reg_val,
+ (FIELD_GET(AW86927_GLBRD5_STATE_MASK, reg_val) == AW86927_GLBRD5_STATE_STANDBY),
+ 2500, 2500 * 100);
+
+ if (err)
+ dev_err(haptics->dev, "did not enter standby: %d\n", err);
+ return err;
+}
+
+static int aw86927_play_mode(struct aw86927_data *haptics, uint8_t play_mode)
+{
+ int err;
+
+ switch (play_mode) {
+ case AW86927_STANDBY_MODE:
+ /* Briefly toggle standby, then toggle back to standby off */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_STANDBY_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
+ AW86927_SYSCTRL3_STANDBY_ON));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_STANDBY_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_STANDBY_MASK,
+ AW86927_SYSCTRL3_STANDBY_OFF));
+ if (err)
+ return err;
+ break;
+ case AW86927_RAM_MODE:
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_PLAY_MODE_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_PLAY_MODE_MASK,
+ AW86927_PLAYCFG3_PLAY_MODE_RAM));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG1,
+ AW86927_PLAYCFG1_BST_MODE_MASK,
+ FIELD_PREP(AW86927_PLAYCFG1_BST_MODE_MASK,
+ AW86927_PLAYCFG1_BST_MODE_BYPASS));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_VBATCTRL,
+ AW86927_VBATCTRL_VBAT_MODE_MASK,
+ FIELD_PREP(AW86927_VBATCTRL_VBAT_MODE_MASK,
+ AW86927_VBATCTRL_VBAT_MODE_SW));
+ if (err)
+ return err;
+ break;
+ }
+ return 0;
+}
+
+static int aw86927_stop(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_STOP);
+ if (err) {
+ dev_err(haptics->dev, "Failed to stop playback: %d\n", err);
+ return err;
+ }
+
+ err = aw86927_wait_enter_standby(haptics);
+ if (err) {
+ dev_err(haptics->dev, "Failed to enter standby, trying to force it\n");
+ err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+static int aw86927_haptics_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct aw86927_data *haptics = input_get_drvdata(dev);
+ int level;
+
+ level = effect->u.rumble.strong_magnitude;
+ if (!level)
+ level = effect->u.rumble.weak_magnitude;
+
+ /* If already running, don't restart playback */
+ if (haptics->running && level)
+ return 0;
+
+ haptics->running = level;
+ schedule_work(&haptics->play_work);
+
+ return 0;
+}
+
+static int aw86927_play_sine(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = aw86927_stop(haptics);
+ if (err)
+ return err;
+
+ err = aw86927_play_mode(haptics, AW86927_RAM_MODE);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap, AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_AUTO_BST_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
+ AW86927_PLAYCFG3_AUTO_BST_ENABLE));
+ if (err)
+ return err;
+
+ /* Set waveseq 1 to the first wave */
+ err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG1,
+ AW86927_WAVCFG1_WAVSEQ1_MASK,
+ FIELD_PREP(AW86927_WAVCFG1_WAVSEQ1_MASK,
+ 1));
+ if (err)
+ return err;
+
+ /* set wavseq 2 to zero */
+ err = regmap_update_bits(haptics->regmap, AW86927_WAVCFG2,
+ AW86927_WAVCFG2_WAVSEQ2_MASK,
+ FIELD_PREP(AW86927_WAVCFG2_WAVSEQ2_MASK,
+ 0));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_WAVCFG9,
+ AW86927_WAVCFG9_SEQ1LOOP_MASK,
+ FIELD_PREP(AW86927_WAVCFG9_SEQ1LOOP_MASK,
+ AW86927_WAVCFG9_SEQ1LOOP_INFINITELY));
+ if (err)
+ return err;
+
+ /* set gain to value lower than 0x80 to avoid distorted playback */
+ err = regmap_write(haptics->regmap, AW86927_PLAYCFG2, 0x7c);
+ if (err)
+ return err;
+
+ /* Start playback */
+ return regmap_write(haptics->regmap, AW86927_PLAYCFG4, AW86927_PLAYCFG4_GO);
+}
+
+static void aw86927_close(struct input_dev *input)
+{
+ struct aw86927_data *haptics = input_get_drvdata(input);
+ struct device *dev = &haptics->client->dev;
+ int err;
+
+ cancel_work_sync(&haptics->play_work);
+
+ err = aw86927_stop(haptics);
+ if (err)
+ dev_err(dev, "Failed to close the Driver: %d\n", err);
+}
+
+static void aw86927_haptics_play_work(struct work_struct *work)
+{
+ struct aw86927_data *haptics =
+ container_of(work, struct aw86927_data, play_work);
+ struct device *dev = &haptics->client->dev;
+ int err;
+
+ if (haptics->running)
+ err = aw86927_play_sine(haptics);
+ else
+ err = aw86927_stop(haptics);
+
+ if (err)
+ dev_err(dev, "Failed to execute work command: %d\n", err);
+}
+
+static void aw86927_hw_reset(struct aw86927_data *haptics)
+{
+ /* Assert reset */
+ gpiod_set_value_cansleep(haptics->reset_gpio, 1);
+ /* Wait ~1ms */
+ usleep_range(1000, 2000);
+ /* Deassert reset */
+ gpiod_set_value_cansleep(haptics->reset_gpio, 0);
+ /* Wait ~8ms until I2C is accessible */
+ usleep_range(8000, 8500);
+}
+
+static int aw86927_haptic_init(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL4,
+ AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
+ FIELD_PREP(AW86927_SYSCTRL4_WAVDAT_MODE_MASK,
+ AW86927_SYSCTRL4_WAVDAT_24K));
+ if (err)
+ return err;
+
+ /* enable gain bypass */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL4,
+ AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
+ FIELD_PREP(AW86927_SYSCTRL4_GAIN_BYPASS_MASK,
+ 0x01));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_UNLOCK);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_ANACFG11,
+ 0x0f);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG12,
+ AW86927_ANACFG12_BST_SKIP_MASK,
+ FIELD_PREP(AW86927_ANACFG12_BST_SKIP_MASK,
+ AW86927_ANACFG12_BST_SKIP_SHUTDOWN));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG15,
+ AW86927_ANACFG15_BST_PEAK_MODE_MASK,
+ FIELD_PREP(AW86927_ANACFG15_BST_PEAK_MODE_MASK,
+ AW86927_ANACFG15_BST_PEAK_BACK));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG16,
+ AW86927_ANACFG16_BST_SRC_MASK,
+ FIELD_PREP(AW86927_ANACFG16_BST_SRC_MASK,
+ AW86927_ANACFG16_BST_SRC_3NS));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_LOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_CONTCFG1,
+ AW86927_CONTCFG1_BRK_BST_MD_MASK,
+ FIELD_PREP(AW86927_CONTCFG1_BRK_BST_MD_MASK,
+ 0x00));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_CONTCFG5,
+ FIELD_PREP(AW86927_CONTCFG5_BST_BRK_GAIN_MASK, 0x05) |
+ FIELD_PREP(AW86927_CONTCFG5_BRK_GAIN_MASK, 0x08));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap, AW86927_CONTCFG10,
+ AW86927_CONTCFG10_BRK_TIME_MASK,
+ FIELD_PREP(AW86927_CONTCFG10_BRK_TIME_MASK,
+ AW86927_CONTCFG10_BRK_TIME_DEFAULT));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_CONTCFG13,
+ FIELD_PREP(AW86927_CONTCFG13_TSET_MASK, 0x06) |
+ FIELD_PREP(AW86927_CONTCFG13_BEME_SET_MASK, 0x02));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG2,
+ AW86927_DETCFG2_D2S_GAIN_MASK,
+ FIELD_PREP(AW86927_DETCFG2_D2S_GAIN_MASK,
+ AW86927_DETCFG2_D2S_GAIN_10));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PWMCFG1,
+ AW86927_PWMCFG1_PRC_EN_MASK,
+ FIELD_PREP(AW86927_PWMCFG1_PRC_EN_MASK,
+ AW86927_PWMCFG1_PRC_DISABLE));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_PWMCFG3,
+ FIELD_PREP(AW86927_PWMCFG3_PR_EN_MASK, 0x01) |
+ FIELD_PREP(AW86927_PWMCFG3_PRCTIME_MASK, 0x3f));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PWMCFG4,
+ AW86927_PWMCFG4_PRTIME_MASK,
+ FIELD_PREP(AW86927_PWMCFG4_PRTIME_MASK,
+ 0x32));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_UNLOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_ANACFG13,
+ AW86927_ANACFG13_BST_PC_MASK,
+ FIELD_PREP(AW86927_ANACFG13_BST_PC_MASK,
+ AW86927_ANACFG13_BST_PEAKCUR_3P45A));
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_TMCFG,
+ AW86927_TMCFG_LOCK);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG1,
+ AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
+ FIELD_PREP(AW86927_PLAYCFG1_BST_VOUT_VREFSET_MASK,
+ AW86927_PLAYCFG1_BST_8500MV));
+ if (err)
+ return err;
+
+ return regmap_update_bits(haptics->regmap,
+ AW86927_PLAYCFG3,
+ AW86927_PLAYCFG3_AUTO_BST_MASK,
+ FIELD_PREP(AW86927_PLAYCFG3_AUTO_BST_MASK,
+ AW86927_PLAYCFG3_AUTO_BST_DISABLE));
+}
+
+static int aw86927_ram_init(struct aw86927_data *haptics)
+{
+ int err;
+
+ err = aw86927_wait_enter_standby(haptics);
+ if (err)
+ return err;
+
+ /* Enable SRAM init */
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ AW86927_SYSCTRL3_EN_RAMINIT_ON));
+
+ /* Set base address for the start of the SRAM waveforms */
+ err = regmap_write(haptics->regmap,
+ AW86927_BASEADDRH,
+ AW86927_BASEADDRH_VAL);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_BASEADDRL,
+ AW86927_BASEADDRL_VAL);
+ if (err)
+ return err;
+
+ /* Set start of SRAM, before the data is written it will be the same as the base */
+ err = regmap_write(haptics->regmap,
+ AW86927_RAMADDRH,
+ AW86927_BASEADDRH_VAL);
+ if (err)
+ return err;
+
+ err = regmap_write(haptics->regmap,
+ AW86927_RAMADDRL,
+ AW86927_BASEADDRL_VAL);
+ if (err)
+ return err;
+
+ /* Write waveform header to SRAM */
+ err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
+ &sram_waveform_header, sizeof(sram_waveform_header));
+
+ /* Write waveform to SRAM */
+ err = regmap_noinc_write(haptics->regmap, AW86927_RAMDATA,
+ aw86927_waveform, ARRAY_SIZE(aw86927_waveform));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG2,
+ AW86927_DETCFG2_DET_SEQ0_MASK,
+ FIELD_PREP(AW86927_DETCFG2_DET_SEQ0_MASK,
+ AW86927_DETCFG2_DET_SEQ0_VBAT));
+ if (err)
+ return err;
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG1,
+ AW86927_DETCFG1_DET_GO_MASK,
+ FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
+ AW86927_DETCFG1_DET_GO_DET_SEQ0));
+ if (err)
+ return err;
+
+ usleep_range(3000, 3500);
+
+ err = regmap_update_bits(haptics->regmap,
+ AW86927_DETCFG1,
+ AW86927_DETCFG1_DET_GO_MASK,
+ FIELD_PREP(AW86927_DETCFG1_DET_GO_MASK,
+ AW86927_DETCFG1_DET_GO_NA));
+ if (err)
+ return err;
+
+ /* Disable SRAM init */
+ return regmap_update_bits(haptics->regmap,
+ AW86927_SYSCTRL3,
+ AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ FIELD_PREP(AW86927_SYSCTRL3_EN_RAMINIT_MASK,
+ AW86927_SYSCTRL3_EN_RAMINIT_OFF));
+}
+
+static irqreturn_t aw86927_irq(int irq, void *data)
+{
+ struct aw86927_data *haptics = data;
+ struct device *dev = &haptics->client->dev;
+ unsigned int reg_val;
+ int err;
+
+ err = regmap_read(haptics->regmap, AW86927_SYSINT, ®_val);
+ if (err) {
+ dev_err(dev, "Failed to read SYSINT register: %d\n", err);
+ return IRQ_NONE;
+ }
+
+ if (reg_val & AW86927_SYSINT_BST_SCPI)
+ dev_err(dev, "Received a Short Circuit Protection interrupt\n");
+ if (reg_val & AW86927_SYSINT_BST_OVPI)
+ dev_err(dev, "Received an Over Voltage Protection interrupt\n");
+ if (reg_val & AW86927_SYSINT_UVLI)
+ dev_err(dev, "Received an Under Voltage Lock Out interrupt\n");
+ if (reg_val & AW86927_SYSINT_OCDI)
+ dev_err(dev, "Received an Over Current interrupt\n");
+ if (reg_val & AW86927_SYSINT_OTI)
+ dev_err(dev, "Received an Over Temperature interrupt\n");
+
+ if (reg_val & AW86927_SYSINT_DONEI)
+ dev_dbg(dev, "Chip playback done!\n");
+ if (reg_val & AW86927_SYSINT_FF_AFI)
+ dev_dbg(dev, "The RTP mode FIFO is almost full!\n");
+ if (reg_val & AW86927_SYSINT_FF_AEI)
+ dev_dbg(dev, "The RTP mode FIFO is almost empty!\n");
+
+ return IRQ_HANDLED;
+}
+
+static int aw86927_detect(struct aw86927_data *haptics)
+{
+ __be16 read_buf;
+ u16 chip_id;
+ int err;
+
+ err = regmap_bulk_read(haptics->regmap, AW86927_CHIPIDH, &read_buf, 2);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to read CHIPID registers\n");
+
+ chip_id = be16_to_cpu(read_buf);
+
+ if (chip_id != AW86927_CHIPID) {
+ dev_err(haptics->dev, "Unexpected CHIPID value 0x%x\n", chip_id);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int aw86927_probe(struct i2c_client *client)
+{
+ struct aw86927_data *haptics;
+ unsigned int read_buf;
+ int err;
+
+ haptics = devm_kzalloc(&client->dev, sizeof(struct aw86927_data), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+
+ haptics->dev = &client->dev;
+ haptics->client = client;
+
+ i2c_set_clientdata(client, haptics);
+ dev_set_drvdata(&client->dev, haptics);
+
+ haptics->regmap = devm_regmap_init_i2c(client, &aw86927_regmap_config);
+ if (IS_ERR(haptics->regmap))
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
+ "Failed to allocate register map\n");
+
+ haptics->input_dev = devm_input_allocate_device(haptics->dev);
+ if (!haptics->input_dev)
+ return -ENOMEM;
+
+ haptics->reset_gpio = devm_gpiod_get(haptics->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(haptics->reset_gpio))
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ /* Hardware reset */
+ aw86927_hw_reset(haptics);
+
+ /* Software reset */
+ err = regmap_write(haptics->regmap, AW86927_RSTCFG, AW86927_RSTCFG_SOFTRST);
+ if (err)
+ return dev_err_probe(haptics->dev, PTR_ERR(haptics->regmap),
+ "Failed Software reset\n");
+
+ /* Wait ~3ms until I2C is accessible */
+ usleep_range(3000, 3500);
+
+ err = aw86927_detect(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to find chip\n");
+
+ /* IRQ config */
+ err = regmap_write(haptics->regmap, AW86927_SYSCTRL4,
+ FIELD_PREP(AW86927_SYSCTRL4_INT_MODE_MASK,
+ AW86927_SYSCTRL4_INT_MODE_EDGE) |
+ FIELD_PREP(AW86927_SYSCTRL4_INT_EDGE_MODE_MASK,
+ AW86927_SYSCTRL4_INT_EDGE_MODE_POS));
+ if (err)
+ return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
+
+ err = regmap_write(haptics->regmap, AW86927_SYSINTM,
+ AW86927_SYSINTM_BST_OVPM |
+ AW86927_SYSINTM_FF_AEM |
+ AW86927_SYSINTM_FF_AFM |
+ AW86927_SYSINTM_DONEM);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
+
+ err = devm_request_threaded_irq(haptics->dev, client->irq, NULL,
+ aw86927_irq, IRQF_ONESHOT, NULL, haptics);
+
+ INIT_WORK(&haptics->play_work, aw86927_haptics_play_work);
+
+ haptics->input_dev->name = "aw86927-haptics";
+ haptics->input_dev->close = aw86927_close;
+
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ err = input_ff_create_memless(haptics->input_dev, NULL,
+ aw86927_haptics_play);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to create FF dev\n");
+
+ /* Set up registers */
+ err = aw86927_play_mode(haptics, AW86927_STANDBY_MODE);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "\n");//FIXME error msg
+
+ err = aw86927_haptic_init(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Haptic init failed\n");
+
+ /* RAM init, upload the waveform for playback */
+ err = aw86927_ram_init(haptics);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to init aw86927 sram\n");
+
+ err = input_register_device(haptics->input_dev);
+ if (err)
+ return dev_err_probe(haptics->dev, err, "Failed to register input device\n");
+
+ return 0;
+}
+
+static const struct of_device_id aw86927_of_id[] = {
+ { .compatible = "awinic,aw86927" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, aw86927_of_id);
+
+static struct i2c_driver aw86927_driver = {
+ .driver = {
+ .name = "aw86927-haptics",
+ .of_match_table = aw86927_of_id,
+ },
+ .probe = aw86927_probe,
+};
+
+module_i2c_driver(aw86927_driver);
+
+MODULE_AUTHOR("Griffin Kroah-Hartman <griffin.kroah@fairphone.com>");
+MODULE_DESCRIPTION("AWINIC AW86927 LRA Haptic Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/3] Add support for Awinic AW86927 haptic driver
From: Griffin Kroah-Hartman @ 2025-08-11 11:12 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Luca Weiss
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Griffin Kroah-Hartman
Add devicetree bindings and a driver for the AW86927 haptic driver, and
add it to the devicetree for the Fairphone 5 smartphone.
This driver does not enable all capabilities of the AW86927, features
such as f0 detection, rtp mode, and cont mode are not included.
Note: This is my first driver I have ever worked on so if there is
anything I can do to improve it please let me know!
Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
---
Changes in v2:
- Changed title and fixed license of devicetree binding
- Fixed typo where the 'm' in 'ms' was excluded
- Changed error handling return values in driver probe function
- Link to v1: https://lore.kernel.org/r/20250806-aw86927-v1-0-23d8a6d0f2b2@fairphone.com
---
Griffin Kroah-Hartman (3):
dt-bindings: input: Add bindings for Awinic AW86927
Input: aw86927 - add driver for Awinic AW86927
arm64: dts: qcom: qcm6490-fairphone-fp5: Add vibrator support
.../devicetree/bindings/input/awinic,aw86927.yaml | 48 ++
arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts | 18 +-
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/aw86927.c | 841 +++++++++++++++++++++
5 files changed, 918 insertions(+), 1 deletion(-)
---
base-commit: 3624e9a34b36d64a7037946eda28ae9599363a3b
change-id: 20250804-aw86927-9dddc32fcaec
Best regards,
--
Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
^ permalink raw reply
* [PATCH v13 10/10] MAINTAINERS: Add entry on MAX7360 driver
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Add myself as maintainer of Maxim MAX7360 driver and device-tree bindings.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index fe168477caa4..2026f2e44dd0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15013,6 +15013,19 @@ L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/iio/temperature/max30208.c
+MAXIM MAX7360 KEYPAD LED MFD DRIVER
+M: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+S: Maintained
+F: Documentation/devicetree/bindings/gpio/maxim,max7360-gpio.yaml
+F: Documentation/devicetree/bindings/mfd/maxim,max7360.yaml
+F: drivers/gpio/gpio-max7360.c
+F: drivers/input/keyboard/max7360-keypad.c
+F: drivers/input/misc/max7360-rotary.c
+F: drivers/mfd/max7360.c
+F: drivers/pinctrl/pinctrl-max7360.c
+F: drivers/pwm/pwm-max7360.c
+F: include/linux/mfd/max7360.h
+
MAXIM MAX77650 PMIC MFD DRIVER
M: Bartosz Golaszewski <brgl@bgdev.pl>
L: linux-kernel@vger.kernel.org
--
2.39.5
^ permalink raw reply related
* [PATCH v13 06/10] gpio: regmap: Allow to provide init_valid_mask callback
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko, Bartosz Golaszewski
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Allows to populate the gpio_regmap_config structure with
init_valid_mask() callback to set on the final gpio_chip structure.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/gpio-regmap.c | 1 +
include/linux/gpio/regmap.h | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index e1944931ee7c..d9d23853e032 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -261,6 +261,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
chip->names = config->names;
chip->label = config->label ?: dev_name(config->parent);
chip->can_sleep = regmap_might_sleep(config->regmap);
+ chip->init_valid_mask = config->init_valid_mask;
chip->request = gpiochip_generic_request;
chip->free = gpiochip_generic_free;
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 19b52ac03a5d..622a2939ebe0 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -6,6 +6,7 @@
struct device;
struct fwnode_handle;
struct gpio_regmap;
+struct gpio_chip;
struct irq_domain;
struct regmap;
@@ -40,6 +41,8 @@ struct regmap;
* @drvdata: (Optional) Pointer to driver specific data which is
* not used by gpio-remap but is provided "as is" to the
* driver callback(s).
+ * @init_valid_mask: (Optional) Routine to initialize @valid_mask, to be used
+ * if not all GPIOs are valid.
* @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If
* set, a regmap-irq device will be created and the IRQ
* domain will be set accordingly.
@@ -93,6 +96,10 @@ struct gpio_regmap_config {
unsigned int offset, unsigned int *reg,
unsigned int *mask);
+ int (*init_valid_mask)(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios);
+
void *drvdata;
};
--
2.39.5
^ permalink raw reply related
* [PATCH v13 05/10] gpio: regmap: Allow to allocate regmap-irq device
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko, Bartosz Golaszewski
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
GPIO controller often have support for IRQ: allow to easily allocate
both gpio-regmap and regmap-irq in one operation.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/gpio-regmap.c | 29 +++++++++++++++++++++++++++--
include/linux/gpio/regmap.h | 11 +++++++++++
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index e8a32dfebdcb..e1944931ee7c 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -32,6 +32,11 @@ struct gpio_regmap {
unsigned int reg_dir_in_base;
unsigned int reg_dir_out_base;
+#ifdef CONFIG_REGMAP_IRQ
+ int regmap_irq_line;
+ struct regmap_irq_chip_data *irq_chip_data;
+#endif
+
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
unsigned int offset, unsigned int *reg,
unsigned int *mask);
@@ -215,6 +220,7 @@ EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);
*/
struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
{
+ struct irq_domain *irq_domain;
struct gpio_regmap *gpio;
struct gpio_chip *chip;
int ret;
@@ -295,8 +301,22 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
if (ret < 0)
goto err_free_gpio;
- if (config->irq_domain) {
- ret = gpiochip_irqchip_add_domain(chip, config->irq_domain);
+#ifdef CONFIG_REGMAP_IRQ
+ if (config->regmap_irq_chip) {
+ gpio->regmap_irq_line = config->regmap_irq_line;
+ ret = regmap_add_irq_chip_fwnode(dev_fwnode(config->parent), config->regmap,
+ config->regmap_irq_line, config->regmap_irq_flags,
+ 0, config->regmap_irq_chip, &gpio->irq_chip_data);
+ if (ret)
+ goto err_free_gpio;
+
+ irq_domain = regmap_irq_get_domain(gpio->irq_chip_data);
+ } else
+#endif
+ irq_domain = config->irq_domain;
+
+ if (irq_domain) {
+ ret = gpiochip_irqchip_add_domain(chip, irq_domain);
if (ret)
goto err_remove_gpiochip;
}
@@ -317,6 +337,11 @@ EXPORT_SYMBOL_GPL(gpio_regmap_register);
*/
void gpio_regmap_unregister(struct gpio_regmap *gpio)
{
+#ifdef CONFIG_REGMAP_IRQ
+ if (gpio->irq_chip_data)
+ regmap_del_irq_chip(gpio->regmap_irq_line, gpio->irq_chip_data);
+#endif
+
gpiochip_remove(&gpio->gpio_chip);
kfree(gpio);
}
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index c722c67668c6..19b52ac03a5d 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -40,6 +40,11 @@ struct regmap;
* @drvdata: (Optional) Pointer to driver specific data which is
* not used by gpio-remap but is provided "as is" to the
* driver callback(s).
+ * @regmap_irq_chip: (Optional) Pointer on an regmap_irq_chip structure. If
+ * set, a regmap-irq device will be created and the IRQ
+ * domain will be set accordingly.
+ * @regmap_irq_line (Optional) The IRQ the device uses to signal interrupts.
+ * @regmap_irq_flags (Optional) The IRQF_ flags to use for the interrupt.
*
* The ->reg_mask_xlate translates a given base address and GPIO offset to
* register and mask pair. The base address is one of the given register
@@ -78,6 +83,12 @@ struct gpio_regmap_config {
int ngpio_per_reg;
struct irq_domain *irq_domain;
+#ifdef CONFIG_REGMAP_IRQ
+ struct regmap_irq_chip *regmap_irq_chip;
+ int regmap_irq_line;
+ unsigned long regmap_irq_flags;
+#endif
+
int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
unsigned int offset, unsigned int *reg,
unsigned int *mask);
--
2.39.5
^ permalink raw reply related
* [PATCH v13 04/10] pwm: max7360: Add MAX7360 PWM support
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
From: Kamel Bouhara <kamel.bouhara@bootlin.com>
Add driver for Maxim Integrated MAX7360 PWM controller, supporting up to
8 independent PWM outputs.
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/pwm/Kconfig | 10 +++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-max7360.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 220 insertions(+)
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index f00ce973dddf..f2b1ce47de7f 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -432,6 +432,16 @@ config PWM_LPSS_PLATFORM
To compile this driver as a module, choose M here: the module
will be called pwm-lpss-platform.
+config PWM_MAX7360
+ tristate "MAX7360 PWMs"
+ depends on MFD_MAX7360
+ help
+ PWM driver for Maxim Integrated MAX7360 multifunction device, with
+ support for up to 8 PWM outputs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-max7360.
+
config PWM_MC33XS2410
tristate "MC33XS2410 PWM support"
depends on OF
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index ff4f47e5fb7a..dfa8b4966ee1 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
obj-$(CONFIG_PWM_LPSS) += pwm-lpss.o
obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o
obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o
+obj-$(CONFIG_PWM_MAX7360) += pwm-max7360.o
obj-$(CONFIG_PWM_MC33XS2410) += pwm-mc33xs2410.o
obj-$(CONFIG_PWM_MEDIATEK) += pwm-mediatek.o
obj-$(CONFIG_PWM_MESON) += pwm-meson.o
diff --git a/drivers/pwm/pwm-max7360.c b/drivers/pwm/pwm-max7360.c
new file mode 100644
index 000000000000..5a0c10d2320e
--- /dev/null
+++ b/drivers/pwm/pwm-max7360.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ *
+ * PWM functionality of the MAX7360 multi-function device.
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf
+ *
+ * Limitations:
+ * - Only supports normal polarity.
+ * - The period is fixed to 2 ms.
+ * - Only the duty cycle can be changed, new values are applied at the beginning
+ * of the next cycle.
+ * - When disabled, the output is put in Hi-Z immediately.
+ */
+#include <linux/bits.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mfd/max7360.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#define MAX7360_NUM_PWMS 8
+#define MAX7360_PWM_MAX 255
+#define MAX7360_PWM_STEPS 256
+#define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC)
+
+struct max7360_pwm_waveform {
+ u8 duty_steps;
+ bool enabled;
+};
+
+static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+
+ /*
+ * Make sure we use the individual PWM configuration register and not
+ * the global one.
+ * We never need to use the global one, so there is no need to revert
+ * that in the .free() callback.
+ */
+ return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm),
+ MAX7360_PORT_CFG_COMMON_PWM, 0);
+}
+
+static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ u64 duty_steps;
+
+ /*
+ * Ignore user provided values for period_length_ns and duty_offset_ns:
+ * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0.
+ * Values from 0 to 254 as duty_steps will provide duty cycles of 0/256
+ * to 254/256, while value 255 will provide a duty cycle of 100%.
+ */
+ if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) {
+ duty_steps = MAX7360_PWM_MAX;
+ } else {
+ duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS;
+ if (duty_steps == MAX7360_PWM_MAX)
+ duty_steps = MAX7360_PWM_MAX - 1;
+ }
+
+ wfhw->duty_steps = min(MAX7360_PWM_MAX, duty_steps);
+ wfhw->enabled = !!wf->period_length_ns;
+
+ if (wf->period_length_ns < MAX7360_PWM_PERIOD_NS)
+ return 1;
+ else
+ return 0;
+}
+
+static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct max7360_pwm_waveform *wfhw = _wfhw;
+
+ wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0;
+ wf->duty_offset_ns = 0;
+
+ if (wfhw->enabled) {
+ if (wfhw->duty_steps == MAX7360_PWM_MAX)
+ wf->duty_length_ns = MAX7360_PWM_PERIOD_NS;
+ else
+ wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS,
+ MAX7360_PWM_STEPS);
+ } else {
+ wf->duty_length_ns = 0;
+ }
+
+ return 0;
+}
+
+static int max7360_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ const struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ if (wfhw->enabled) {
+ ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps);
+ if (ret)
+ return ret;
+ }
+
+ val = wfhw->enabled ? BIT(pwm->hwpwm) : 0;
+ return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val);
+}
+
+static int max7360_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct regmap *regmap = pwmchip_get_drvdata(chip);
+ struct max7360_pwm_waveform *wfhw = _wfhw;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val);
+ if (ret)
+ return ret;
+
+ if (val & BIT(pwm->hwpwm)) {
+ wfhw->enabled = true;
+ ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val);
+ if (ret)
+ return ret;
+
+ wfhw->duty_steps = val;
+ } else {
+ wfhw->enabled = false;
+ wfhw->duty_steps = 0;
+ }
+
+ return 0;
+}
+
+static const struct pwm_ops max7360_pwm_ops = {
+ .request = max7360_pwm_request,
+ .round_waveform_tohw = max7360_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = max7360_pwm_round_waveform_fromhw,
+ .read_waveform = max7360_pwm_read_waveform,
+ .write_waveform = max7360_pwm_write_waveform,
+};
+
+static int max7360_pwm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ /*
+ * This MFD sub-device does not have any associated device tree node:
+ * properties are stored in the device node of the parent (MFD) device
+ * and this same node is used in phandles of client devices.
+ * Reuse this device tree node here, as otherwise the PWM subsystem
+ * would be confused by this topology.
+ */
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ chip->ops = &max7360_pwm_ops;
+
+ pwmchip_set_drvdata(chip, regmap);
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ return 0;
+}
+
+static struct platform_driver max7360_pwm_driver = {
+ .driver = {
+ .name = "max7360-pwm",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = max7360_pwm_probe,
+};
+module_platform_driver(max7360_pwm_driver);
+
+MODULE_DESCRIPTION("MAX7360 PWM driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related
* [PATCH v13 09/10] input: misc: Add support for MAX7360 rotary
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Add driver for Maxim Integrated MAX7360 rotary encoder controller,
supporting a single rotary switch.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/input/misc/Kconfig | 10 ++
drivers/input/misc/Makefile | 1 +
drivers/input/misc/max7360-rotary.c | 192 ++++++++++++++++++++++++++++++++++++
3 files changed, 203 insertions(+)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 0fb21c99a5e3..d604aad90a89 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -230,6 +230,16 @@ config INPUT_M68K_BEEP
tristate "M68k Beeper support"
depends on M68K
+config INPUT_MAX7360_ROTARY
+ tristate "Maxim MAX7360 Rotary Encoder"
+ depends on MFD_MAX7360
+ help
+ If you say yes here you get support for the rotary encoder on the
+ Maxim MAX7360 I/O Expander.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max7360_rotary.
+
config INPUT_MAX77650_ONKEY
tristate "Maxim MAX77650 ONKEY support"
depends on MFD_MAX77650
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index d468c8140b93..ac45cb9b5c99 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
+obj-$(CONFIG_INPUT_MAX7360_ROTARY) += max7360-rotary.o
obj-$(CONFIG_INPUT_MAX77650_ONKEY) += max77650-onkey.o
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
diff --git a/drivers/input/misc/max7360-rotary.c b/drivers/input/misc/max7360-rotary.c
new file mode 100644
index 000000000000..385831ef34b6
--- /dev/null
+++ b/drivers/input/misc/max7360-rotary.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define MAX7360_ROTARY_DEFAULT_STEPS 24
+
+struct max7360_rotary {
+ struct input_dev *input;
+ struct regmap *regmap;
+ unsigned int debounce_ms;
+
+ unsigned int pos;
+
+ u32 steps;
+ u32 axis;
+ bool relative_axis;
+ bool rollover;
+};
+
+static void max7360_rotary_report_event(struct max7360_rotary *max7360_rotary, int steps)
+{
+ if (max7360_rotary->relative_axis) {
+ input_report_rel(max7360_rotary->input, max7360_rotary->axis, steps);
+ } else {
+ int pos = max7360_rotary->pos;
+ int maxval = max7360_rotary->steps;
+
+ /*
+ * Add steps to the position.
+ * Make sure added steps are always in ]-maxval; maxval[
+ * interval, so (pos + maxval) is always >= 0.
+ * Then set back pos to the [0; maxval[ interval.
+ */
+ pos += steps % maxval;
+ if (max7360_rotary->rollover)
+ pos = (pos + maxval) % maxval;
+ else
+ pos = clamp(pos, 0, maxval - 1);
+
+ max7360_rotary->pos = pos;
+ input_report_abs(max7360_rotary->input, max7360_rotary->axis, max7360_rotary->pos);
+ }
+
+ input_sync(max7360_rotary->input);
+}
+
+static irqreturn_t max7360_rotary_irq(int irq, void *data)
+{
+ struct max7360_rotary *max7360_rotary = data;
+ struct device *dev = max7360_rotary->input->dev.parent;
+ unsigned int val;
+ int error;
+
+ error = regmap_read(max7360_rotary->regmap, MAX7360_REG_RTR_CNT, &val);
+ if (error < 0) {
+ dev_err(dev, "Failed to read rotary counter\n");
+ return IRQ_NONE;
+ }
+
+ if (val == 0)
+ return IRQ_NONE;
+
+ max7360_rotary_report_event(max7360_rotary, sign_extend32(val, 7));
+
+ return IRQ_HANDLED;
+}
+
+static int max7360_rotary_hw_init(struct max7360_rotary *max7360_rotary)
+{
+ struct device *dev = max7360_rotary->input->dev.parent;
+ int val;
+ int error;
+
+ val = FIELD_PREP(MAX7360_ROT_DEBOUNCE, max7360_rotary->debounce_ms) |
+ FIELD_PREP(MAX7360_ROT_INTCNT, 1) | MAX7360_ROT_INTCNT_DLY;
+ error = regmap_write(max7360_rotary->regmap, MAX7360_REG_RTRCFG, val);
+ if (error)
+ dev_err(dev, "Failed to set max7360 rotary encoder configuration\n");
+
+ return error;
+}
+
+static int max7360_rotary_probe(struct platform_device *pdev)
+{
+ struct max7360_rotary *max7360_rotary;
+ struct device *dev = &pdev->dev;
+ struct input_dev *input;
+ struct regmap *regmap;
+ int irq;
+ int error;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get IRQ\n");
+
+ max7360_rotary = devm_kzalloc(dev, sizeof(*max7360_rotary), GFP_KERNEL);
+ if (!max7360_rotary)
+ return -ENOMEM;
+
+ max7360_rotary->regmap = regmap;
+
+ device_property_read_u32(dev->parent, "linux,axis", &max7360_rotary->axis);
+ max7360_rotary->rollover = device_property_read_bool(dev->parent,
+ "rotary-encoder,rollover");
+ max7360_rotary->relative_axis =
+ device_property_read_bool(dev->parent, "rotary-encoder,relative-axis");
+
+ error = device_property_read_u32(dev->parent, "rotary-encoder,steps",
+ &max7360_rotary->steps);
+ if (error)
+ max7360_rotary->steps = MAX7360_ROTARY_DEFAULT_STEPS;
+
+ device_property_read_u32(dev->parent, "rotary-debounce-delay-ms",
+ &max7360_rotary->debounce_ms);
+ if (max7360_rotary->debounce_ms > MAX7360_ROT_DEBOUNCE_MAX)
+ return dev_err_probe(dev, -EINVAL, "Invalid debounce timing: %u\n",
+ max7360_rotary->debounce_ms);
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ max7360_rotary->input = input;
+
+ input->id.bustype = BUS_I2C;
+ input->name = pdev->name;
+
+ if (max7360_rotary->relative_axis)
+ input_set_capability(input, EV_REL, max7360_rotary->axis);
+ else
+ input_set_abs_params(input, max7360_rotary->axis, 0, max7360_rotary->steps, 0, 1);
+
+ error = devm_request_threaded_irq(dev, irq, NULL, max7360_rotary_irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "max7360-rotary", max7360_rotary);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register interrupt\n");
+
+ error = input_register_device(input);
+ if (error)
+ return dev_err_probe(dev, error, "Could not register input device\n");
+
+ error = max7360_rotary_hw_init(max7360_rotary);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to initialize max7360 rotary\n");
+
+ device_init_wakeup(dev, true);
+ error = dev_pm_set_wake_irq(dev, irq);
+ if (error)
+ dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
+
+ return 0;
+}
+
+static void max7360_rotary_remove(struct platform_device *pdev)
+{
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+}
+
+static struct platform_driver max7360_rotary_driver = {
+ .driver = {
+ .name = "max7360-rotary",
+ },
+ .probe = max7360_rotary_probe,
+ .remove = max7360_rotary_remove,
+};
+module_platform_driver(max7360_rotary_driver);
+
+MODULE_DESCRIPTION("MAX7360 Rotary driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related
* [PATCH v13 03/10] pinctrl: Add MAX7360 pinctrl driver
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Add driver for Maxim Integrated MAX7360 pinctrl on the PORT pins. Pins
can be used either for GPIO, PWM or rotary encoder functionalities.
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/pinctrl/Kconfig | 11 ++
drivers/pinctrl/Makefile | 1 +
drivers/pinctrl/pinctrl-max7360.c | 215 ++++++++++++++++++++++++++++++++++++++
3 files changed, 227 insertions(+)
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index ddd11668457c..57e4bdc8011d 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -358,6 +358,17 @@ config PINCTRL_LPC18XX
help
Pinctrl driver for NXP LPC18xx/43xx System Control Unit (SCU).
+config PINCTRL_MAX7360
+ tristate "MAX7360 Pincontrol support"
+ depends on MFD_MAX7360
+ select PINMUX
+ select GENERIC_PINCONF
+ help
+ Say Y here to enable pin control support for Maxim MAX7360 keypad
+ controller.
+ This keypad controller has 8 GPIO pins that may work as GPIO, or PWM,
+ or rotary encoder alternate modes.
+
config PINCTRL_MAX77620
tristate "MAX77620/MAX20024 Pincontrol support"
depends on MFD_MAX77620 && OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 909ab89a56d2..65aa432fc97e 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o
obj-$(CONFIG_PINCTRL_LOONGSON2) += pinctrl-loongson2.o
obj-$(CONFIG_PINCTRL_XWAY) += pinctrl-xway.o
obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o
+obj-$(CONFIG_PINCTRL_MAX7360) += pinctrl-max7360.o
obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o
obj-$(CONFIG_PINCTRL_MCP23S08_I2C) += pinctrl-mcp23s08_i2c.o
obj-$(CONFIG_PINCTRL_MCP23S08_SPI) += pinctrl-mcp23s08_spi.o
diff --git a/drivers/pinctrl/pinctrl-max7360.c b/drivers/pinctrl/pinctrl-max7360.c
new file mode 100644
index 000000000000..abfaff468bad
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-max7360.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/mfd/max7360.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/stddef.h>
+
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinmux.h>
+
+#include "core.h"
+#include "pinmux.h"
+
+struct max7360_pinctrl {
+ struct pinctrl_dev *pctldev;
+ struct pinctrl_desc pinctrl_desc;
+};
+
+static const struct pinctrl_pin_desc max7360_pins[] = {
+ PINCTRL_PIN(0, "PORT0"),
+ PINCTRL_PIN(1, "PORT1"),
+ PINCTRL_PIN(2, "PORT2"),
+ PINCTRL_PIN(3, "PORT3"),
+ PINCTRL_PIN(4, "PORT4"),
+ PINCTRL_PIN(5, "PORT5"),
+ PINCTRL_PIN(6, "PORT6"),
+ PINCTRL_PIN(7, "PORT7"),
+};
+
+static const unsigned int port0_pins[] = {0};
+static const unsigned int port1_pins[] = {1};
+static const unsigned int port2_pins[] = {2};
+static const unsigned int port3_pins[] = {3};
+static const unsigned int port4_pins[] = {4};
+static const unsigned int port5_pins[] = {5};
+static const unsigned int port6_pins[] = {6};
+static const unsigned int port7_pins[] = {7};
+static const unsigned int rotary_pins[] = {6, 7};
+
+static const struct pingroup max7360_groups[] = {
+ PINCTRL_PINGROUP("PORT0", port0_pins, ARRAY_SIZE(port0_pins)),
+ PINCTRL_PINGROUP("PORT1", port1_pins, ARRAY_SIZE(port1_pins)),
+ PINCTRL_PINGROUP("PORT2", port2_pins, ARRAY_SIZE(port2_pins)),
+ PINCTRL_PINGROUP("PORT3", port3_pins, ARRAY_SIZE(port3_pins)),
+ PINCTRL_PINGROUP("PORT4", port4_pins, ARRAY_SIZE(port4_pins)),
+ PINCTRL_PINGROUP("PORT5", port5_pins, ARRAY_SIZE(port5_pins)),
+ PINCTRL_PINGROUP("PORT6", port6_pins, ARRAY_SIZE(port6_pins)),
+ PINCTRL_PINGROUP("PORT7", port7_pins, ARRAY_SIZE(port7_pins)),
+ PINCTRL_PINGROUP("ROTARY", rotary_pins, ARRAY_SIZE(rotary_pins)),
+};
+
+static int max7360_pinctrl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(max7360_groups);
+}
+
+static const char *max7360_pinctrl_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned int group)
+{
+ return max7360_groups[group].name;
+}
+
+static int max7360_pinctrl_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned int group,
+ const unsigned int **pins,
+ unsigned int *num_pins)
+{
+ *pins = max7360_groups[group].pins;
+ *num_pins = max7360_groups[group].npins;
+ return 0;
+}
+
+static const struct pinctrl_ops max7360_pinctrl_ops = {
+ .get_groups_count = max7360_pinctrl_get_groups_count,
+ .get_group_name = max7360_pinctrl_get_group_name,
+ .get_group_pins = max7360_pinctrl_get_group_pins,
+#ifdef CONFIG_OF
+ .dt_node_to_map = pinconf_generic_dt_node_to_map_pin,
+ .dt_free_map = pinconf_generic_dt_free_map,
+#endif
+};
+
+static const char * const simple_groups[] = {
+ "PORT0", "PORT1", "PORT2", "PORT3",
+ "PORT4", "PORT5", "PORT6", "PORT7",
+};
+
+static const char * const rotary_groups[] = { "ROTARY" };
+
+#define MAX7360_PINCTRL_FN_GPIO 0
+#define MAX7360_PINCTRL_FN_PWM 1
+#define MAX7360_PINCTRL_FN_ROTARY 2
+static const struct pinfunction max7360_functions[] = {
+ [MAX7360_PINCTRL_FN_GPIO] = PINCTRL_PINFUNCTION("gpio", simple_groups,
+ ARRAY_SIZE(simple_groups)),
+ [MAX7360_PINCTRL_FN_PWM] = PINCTRL_PINFUNCTION("pwm", simple_groups,
+ ARRAY_SIZE(simple_groups)),
+ [MAX7360_PINCTRL_FN_ROTARY] = PINCTRL_PINFUNCTION("rotary", rotary_groups,
+ ARRAY_SIZE(rotary_groups)),
+};
+
+static int max7360_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(max7360_functions);
+}
+
+static const char *max7360_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+ return max7360_functions[selector].name;
+}
+
+static int max7360_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
+ const char * const **groups,
+ unsigned int * const num_groups)
+{
+ *groups = max7360_functions[selector].groups;
+ *num_groups = max7360_functions[selector].ngroups;
+
+ return 0;
+}
+
+static int max7360_set_mux(struct pinctrl_dev *pctldev, unsigned int selector,
+ unsigned int group)
+{
+ struct regmap *regmap = dev_get_regmap(pctldev->dev->parent, NULL);
+ int val;
+
+ /*
+ * GPIO and PWM functions are the same: we only need to handle the
+ * rotary encoder function, on pins 6 and 7.
+ */
+ if (max7360_groups[group].pins[0] >= 6) {
+ if (selector == MAX7360_PINCTRL_FN_ROTARY)
+ val = MAX7360_GPIO_CFG_RTR_EN;
+ else
+ val = 0;
+
+ return regmap_write_bits(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_RTR_EN, val);
+ }
+
+ return 0;
+}
+
+static const struct pinmux_ops max7360_pmxops = {
+ .get_functions_count = max7360_get_functions_count,
+ .get_function_name = max7360_get_function_name,
+ .get_function_groups = max7360_get_function_groups,
+ .set_mux = max7360_set_mux,
+ .strict = true,
+};
+
+static int max7360_pinctrl_probe(struct platform_device *pdev)
+{
+ struct regmap *regmap;
+ struct pinctrl_desc *pd;
+ struct max7360_pinctrl *chip;
+ struct device *dev = &pdev->dev;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ pd = &chip->pinctrl_desc;
+
+ pd->pctlops = &max7360_pinctrl_ops;
+ pd->pmxops = &max7360_pmxops;
+ pd->name = dev_name(dev);
+ pd->pins = max7360_pins;
+ pd->npins = MAX7360_MAX_GPIO;
+ pd->owner = THIS_MODULE;
+
+ /*
+ * This MFD sub-device does not have any associated device tree node:
+ * properties are stored in the device node of the parent (MFD) device
+ * and this same node is used in phandles of client devices.
+ * Reuse this device tree node here, as otherwise the pinctrl subsystem
+ * would be confused by this topology.
+ */
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ chip->pctldev = devm_pinctrl_register(dev, pd, chip);
+ if (IS_ERR(chip->pctldev))
+ return dev_err_probe(dev, PTR_ERR(chip->pctldev), "can't register controller\n");
+
+ return 0;
+}
+
+static struct platform_driver max7360_pinctrl_driver = {
+ .driver = {
+ .name = "max7360-pinctrl",
+ },
+ .probe = max7360_pinctrl_probe,
+};
+module_platform_driver(max7360_pinctrl_driver);
+
+MODULE_DESCRIPTION("MAX7360 pinctrl driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related
* [PATCH v13 08/10] input: keyboard: Add support for MAX7360 keypad
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Add driver for Maxim Integrated MAX7360 keypad controller, providing
support for up to 64 keys, with a matrix of 8 columns and 8 rows.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/input/keyboard/Kconfig | 12 ++
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/max7360-keypad.c | 308 ++++++++++++++++++++++++++++++++
3 files changed, 321 insertions(+)
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 7c4f309a4cb6..1b10528b7ca3 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -422,6 +422,18 @@ config KEYBOARD_MAX7359
To compile this driver as a module, choose M here: the
module will be called max7359_keypad.
+config KEYBOARD_MAX7360
+ tristate "Maxim MAX7360 Key Switch Controller"
+ select INPUT_MATRIXKMAP
+ depends on I2C
+ depends on MFD_MAX7360
+ help
+ If you say yes here you get support for the keypad controller on the
+ Maxim MAX7360 I/O Expander.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max7360_keypad.
+
config KEYBOARD_MPR121
tristate "Freescale MPR121 Touchkey"
depends on I2C
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 8bc20ab2b103..636367cd1042 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_KEYBOARD_LPC32XX) += lpc32xx-keys.o
obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o
obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o
obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o
+obj-$(CONFIG_KEYBOARD_MAX7360) += max7360-keypad.o
obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o
obj-$(CONFIG_KEYBOARD_MT6779) += mt6779-keypad.o
obj-$(CONFIG_KEYBOARD_MTK_PMIC) += mtk-pmic-keys.o
diff --git a/drivers/input/keyboard/max7360-keypad.c b/drivers/input/keyboard/max7360-keypad.c
new file mode 100644
index 000000000000..503be952b0a6
--- /dev/null
+++ b/drivers/input/keyboard/max7360-keypad.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/mod_devicetable.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+
+struct max7360_keypad {
+ struct input_dev *input;
+ unsigned int rows;
+ unsigned int cols;
+ unsigned int debounce_ms;
+ int irq;
+ struct regmap *regmap;
+ unsigned short keycodes[MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS];
+};
+
+static irqreturn_t max7360_keypad_irq(int irq, void *data)
+{
+ struct max7360_keypad *max7360_keypad = data;
+ struct device *dev = max7360_keypad->input->dev.parent;
+ unsigned int val;
+ unsigned int row, col;
+ unsigned int release;
+ unsigned int code;
+ int error;
+
+ error = regmap_read(max7360_keypad->regmap, MAX7360_REG_KEYFIFO, &val);
+ if (error) {
+ dev_err(dev, "Failed to read MAX7360 FIFO");
+ return IRQ_NONE;
+ }
+
+ /* FIFO overflow: ignore it and get next event. */
+ if (val == MAX7360_FIFO_OVERFLOW) {
+ dev_warn(dev, "max7360 FIFO overflow");
+ error = regmap_read_poll_timeout(max7360_keypad->regmap, MAX7360_REG_KEYFIFO,
+ val, val != MAX7360_FIFO_OVERFLOW, 0, 1000);
+ if (error) {
+ dev_err(dev, "Failed to empty MAX7360 FIFO");
+ return IRQ_NONE;
+ }
+ }
+
+ if (val == MAX7360_FIFO_EMPTY) {
+ dev_dbg(dev, "Got a spurious interrupt");
+
+ return IRQ_NONE;
+ }
+
+ row = FIELD_GET(MAX7360_FIFO_ROW, val);
+ col = FIELD_GET(MAX7360_FIFO_COL, val);
+ release = val & MAX7360_FIFO_RELEASE;
+
+ code = MATRIX_SCAN_CODE(row, col, get_count_order(max7360_keypad->cols));
+
+ dev_dbg(dev, "key[%d:%d] %s\n", row, col, release ? "release" : "press");
+
+ input_event(max7360_keypad->input, EV_MSC, MSC_SCAN, code);
+ input_report_key(max7360_keypad->input, max7360_keypad->keycodes[code], !release);
+ input_sync(max7360_keypad->input);
+
+ return IRQ_HANDLED;
+}
+
+static int max7360_keypad_open(struct input_dev *pdev)
+{
+ struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
+ struct device *dev = max7360_keypad->input->dev.parent;
+ int error;
+
+ /* Somebody is using the device: get out of sleep. */
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG,
+ MAX7360_CFG_SLEEP, MAX7360_CFG_SLEEP);
+ if (error)
+ dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
+
+ return error;
+}
+
+static void max7360_keypad_close(struct input_dev *pdev)
+{
+ struct max7360_keypad *max7360_keypad = input_get_drvdata(pdev);
+ struct device *dev = max7360_keypad->input->dev.parent;
+ int error;
+
+ /* Nobody is using the device anymore: go to sleep. */
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_CONFIG, MAX7360_CFG_SLEEP, 0);
+ if (error)
+ dev_err(dev, "Failed to write max7360 configuration: %d\n", error);
+}
+
+static int max7360_keypad_hw_init(struct max7360_keypad *max7360_keypad)
+{
+ struct device *dev = max7360_keypad->input->dev.parent;
+ unsigned int val;
+ int error;
+
+ val = max7360_keypad->debounce_ms - MAX7360_DEBOUNCE_MIN;
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_DEBOUNCE,
+ MAX7360_DEBOUNCE,
+ FIELD_PREP(MAX7360_DEBOUNCE, val));
+ if (error)
+ return dev_err_probe(dev, error,
+ "Failed to write max7360 debounce configuration\n");
+
+ error = regmap_write_bits(max7360_keypad->regmap, MAX7360_REG_INTERRUPT,
+ MAX7360_INTERRUPT_TIME_MASK,
+ FIELD_PREP(MAX7360_INTERRUPT_TIME_MASK, 1));
+ if (error)
+ return dev_err_probe(dev, error,
+ "Failed to write max7360 keypad interrupt configuration\n");
+
+ return 0;
+}
+
+static int max7360_keypad_build_keymap(struct max7360_keypad *max7360_keypad)
+{
+ struct input_dev *input_dev = max7360_keypad->input;
+ struct device *dev = input_dev->dev.parent->parent;
+ struct matrix_keymap_data keymap_data;
+ const char *propname = "linux,keymap";
+ unsigned int max_keys;
+ int error;
+ int size;
+
+ size = device_property_count_u32(dev, propname);
+ if (size <= 0) {
+ dev_err(dev, "missing or malformed property %s: %d\n", propname, size);
+ return size < 0 ? size : -EINVAL;
+ }
+
+ max_keys = max7360_keypad->cols * max7360_keypad->rows;
+ if (size > max_keys) {
+ dev_err(dev, "%s size overflow (%d vs max %u)\n", propname, size, max_keys);
+ return -EINVAL;
+ }
+
+ u32 *keys __free(kfree) = kmalloc_array(size, sizeof(*keys), GFP_KERNEL);
+ if (!keys)
+ return -ENOMEM;
+
+ error = device_property_read_u32_array(dev, propname, keys, size);
+ if (error) {
+ dev_err(dev, "failed to read %s property: %d\n", propname, error);
+ return error;
+ }
+
+ keymap_data.keymap = keys;
+ keymap_data.keymap_size = size;
+ error = matrix_keypad_build_keymap(&keymap_data, NULL,
+ max7360_keypad->rows, max7360_keypad->cols,
+ max7360_keypad->keycodes, max7360_keypad->input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int max7360_keypad_parse_fw(struct device *dev,
+ struct max7360_keypad *max7360_keypad,
+ bool *autorepeat)
+{
+ int error;
+
+ error = matrix_keypad_parse_properties(dev->parent, &max7360_keypad->rows,
+ &max7360_keypad->cols);
+ if (error)
+ return error;
+
+ if (!max7360_keypad->rows || !max7360_keypad->cols ||
+ max7360_keypad->rows > MAX7360_MAX_KEY_ROWS ||
+ max7360_keypad->cols > MAX7360_MAX_KEY_COLS) {
+ dev_err(dev, "Invalid number of columns or rows (%ux%u)\n",
+ max7360_keypad->cols, max7360_keypad->rows);
+ return -EINVAL;
+ }
+
+ *autorepeat = device_property_read_bool(dev->parent, "autorepeat");
+
+ max7360_keypad->debounce_ms = MAX7360_DEBOUNCE_MIN;
+ error = device_property_read_u32(dev->parent, "keypad-debounce-delay-ms",
+ &max7360_keypad->debounce_ms);
+ if (error == -EINVAL) {
+ dev_info(dev, "Using default keypad-debounce-delay-ms: %u\n",
+ max7360_keypad->debounce_ms);
+ } else if (error < 0) {
+ dev_err(dev, "Failed to read keypad-debounce-delay-ms property\n");
+ return error;
+ }
+
+ if (!in_range(max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN,
+ MAX7360_DEBOUNCE_MAX - MAX7360_DEBOUNCE_MIN + 1)) {
+ dev_err(dev, "Invalid keypad-debounce-delay-ms: %u, should be between %u and %u.\n",
+ max7360_keypad->debounce_ms, MAX7360_DEBOUNCE_MIN, MAX7360_DEBOUNCE_MAX);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max7360_keypad_probe(struct platform_device *pdev)
+{
+ struct max7360_keypad *max7360_keypad;
+ struct device *dev = &pdev->dev;
+ struct input_dev *input;
+ struct regmap *regmap;
+ bool autorepeat;
+ int error;
+ int irq;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n");
+
+ irq = fwnode_irq_get_byname(dev_fwnode(dev->parent), "intk");
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get IRQ\n");
+
+ max7360_keypad = devm_kzalloc(dev, sizeof(*max7360_keypad), GFP_KERNEL);
+ if (!max7360_keypad)
+ return -ENOMEM;
+
+ max7360_keypad->regmap = regmap;
+
+ error = max7360_keypad_parse_fw(dev, max7360_keypad, &autorepeat);
+ if (error)
+ return error;
+
+ input = devm_input_allocate_device(dev);
+ if (!input)
+ return -ENOMEM;
+
+ max7360_keypad->input = input;
+
+ input->id.bustype = BUS_I2C;
+ input->name = pdev->name;
+ input->open = max7360_keypad_open;
+ input->close = max7360_keypad_close;
+
+ error = max7360_keypad_build_keymap(max7360_keypad);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to build keymap\n");
+
+ input_set_capability(input, EV_MSC, MSC_SCAN);
+ if (autorepeat)
+ __set_bit(EV_REP, input->evbit);
+
+ input_set_drvdata(input, max7360_keypad);
+
+ error = devm_request_threaded_irq(dev, irq, NULL, max7360_keypad_irq,
+ IRQF_ONESHOT,
+ "max7360-keypad", max7360_keypad);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register interrupt\n");
+
+ error = input_register_device(input);
+ if (error)
+ return dev_err_probe(dev, error, "Could not register input device\n");
+
+ error = max7360_keypad_hw_init(max7360_keypad);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to initialize max7360 keypad\n");
+
+ device_init_wakeup(dev, true);
+ error = dev_pm_set_wake_irq(dev, irq);
+ if (error)
+ dev_warn(dev, "Failed to set up wakeup irq: %d\n", error);
+
+ return 0;
+}
+
+static void max7360_keypad_remove(struct platform_device *pdev)
+{
+ dev_pm_clear_wake_irq(&pdev->dev);
+ device_init_wakeup(&pdev->dev, false);
+}
+
+static struct platform_driver max7360_keypad_driver = {
+ .driver = {
+ .name = "max7360-keypad",
+ },
+ .probe = max7360_keypad_probe,
+ .remove = max7360_keypad_remove,
+};
+module_platform_driver(max7360_keypad_driver);
+
+MODULE_DESCRIPTION("MAX7360 Keypad driver");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related
* [PATCH v13 07/10] gpio: max7360: Add MAX7360 gpio support
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Bartosz Golaszewski, Andy Shevchenko
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
Add driver for Maxim Integrated MAX7360 GPIO/GPO controller.
Two sets of GPIOs are provided by the device:
- Up to 8 GPIOs, shared with the PWM and rotary encoder functionalities.
These GPIOs also provide interrupts on input changes.
- Up to 6 GPOs, on unused keypad columns pins.
Co-developed-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
drivers/gpio/Kconfig | 12 +++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-max7360.c | 257 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 270 insertions(+)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index e43abb322fa6..6cf57a18dbe7 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1492,6 +1492,18 @@ config GPIO_MADERA
help
Support for GPIOs on Cirrus Logic Madera class codecs.
+config GPIO_MAX7360
+ tristate "MAX7360 GPIO support"
+ depends on MFD_MAX7360
+ select GPIO_REGMAP
+ select REGMAP_IRQ
+ help
+ Allows to use MAX7360 I/O Expander PWM lines as GPIO and keypad COL
+ lines as GPO.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-max7360.
+
config GPIO_MAX77620
tristate "GPIO support for PMIC MAX77620 and MAX20024"
depends on MFD_MAX77620
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 379f55e9ed1e..52abba3ef81c 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -106,6 +106,7 @@ obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o
obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o
obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o
obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o
+obj-$(CONFIG_GPIO_MAX7360) += gpio-max7360.o
obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o
obj-$(CONFIG_GPIO_MAX77650) += gpio-max77650.o
obj-$(CONFIG_GPIO_MAX77759) += gpio-max77759.o
diff --git a/drivers/gpio/gpio-max7360.c b/drivers/gpio/gpio-max7360.c
new file mode 100644
index 000000000000..db92a43776a9
--- /dev/null
+++ b/drivers/gpio/gpio-max7360.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Bootlin
+ *
+ * Author: Kamel BOUHARA <kamel.bouhara@bootlin.com>
+ * Author: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max7360.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define MAX7360_GPIO_PORT 1
+#define MAX7360_GPIO_COL 2
+
+struct max7360_gpio_plat_data {
+ unsigned int function;
+};
+
+static struct max7360_gpio_plat_data max7360_gpio_port_plat = { .function = MAX7360_GPIO_PORT };
+static struct max7360_gpio_plat_data max7360_gpio_col_plat = { .function = MAX7360_GPIO_COL };
+
+static int max7360_get_available_gpos(struct device *dev, unsigned int *available_gpios)
+{
+ u32 columns;
+ int ret;
+
+ ret = device_property_read_u32(dev->parent, "keypad,num-columns", &columns);
+ if (ret) {
+ dev_err(dev, "Failed to read columns count\n");
+ return ret;
+ }
+
+ *available_gpios = min(MAX7360_MAX_GPO, MAX7360_MAX_KEY_COLS - columns);
+
+ return 0;
+}
+
+static int max7360_gpo_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ unsigned int available_gpios;
+ int ret;
+
+ ret = max7360_get_available_gpos(gc->parent, &available_gpios);
+ if (ret)
+ return ret;
+
+ bitmap_clear(valid_mask, 0, MAX7360_MAX_KEY_COLS - available_gpios);
+
+ return 0;
+}
+
+static int max7360_set_gpos_count(struct device *dev, struct regmap *regmap)
+{
+ /*
+ * MAX7360 COL0 to COL7 pins can be used either as keypad columns,
+ * general purpose output or a mix of both.
+ * By default, all pins are used as keypad, here we update this
+ * configuration to allow to use some of them as GPIOs.
+ */
+ unsigned int available_gpios;
+ unsigned int val;
+ int ret;
+
+ ret = max7360_get_available_gpos(dev, &available_gpios);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure which GPIOs will be used for keypad.
+ * MAX7360_REG_DEBOUNCE contains configuration both for keypad debounce
+ * timings and gpos/keypad columns repartition. Only the later is
+ * modified here.
+ */
+ val = FIELD_PREP(MAX7360_PORTS, available_gpios);
+ ret = regmap_write_bits(regmap, MAX7360_REG_DEBOUNCE, MAX7360_PORTS, val);
+ if (ret)
+ dev_err(dev, "Failed to write max7360 columns/gpos configuration");
+
+ return ret;
+}
+
+static int max7360_gpio_reg_mask_xlate(struct gpio_regmap *gpio,
+ unsigned int base, unsigned int offset,
+ unsigned int *reg, unsigned int *mask)
+{
+ if (base == MAX7360_REG_PWMBASE) {
+ /*
+ * GPIO output is using PWM duty cycle registers: one register
+ * per line, with value being either 0 or 255.
+ */
+ *reg = base + offset;
+ *mask = GENMASK(7, 0);
+ } else {
+ *reg = base;
+ *mask = BIT(offset);
+ }
+
+ return 0;
+}
+
+static const struct regmap_irq max7360_regmap_irqs[MAX7360_MAX_GPIO] = {
+ REGMAP_IRQ_REG(0, 0, BIT(0)),
+ REGMAP_IRQ_REG(1, 0, BIT(1)),
+ REGMAP_IRQ_REG(2, 0, BIT(2)),
+ REGMAP_IRQ_REG(3, 0, BIT(3)),
+ REGMAP_IRQ_REG(4, 0, BIT(4)),
+ REGMAP_IRQ_REG(5, 0, BIT(5)),
+ REGMAP_IRQ_REG(6, 0, BIT(6)),
+ REGMAP_IRQ_REG(7, 0, BIT(7)),
+};
+
+static int max7360_handle_mask_sync(const int index,
+ const unsigned int mask_buf_def,
+ const unsigned int mask_buf,
+ void *const irq_drv_data)
+{
+ struct regmap *regmap = irq_drv_data;
+ int ret;
+
+ for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
+ ret = regmap_assign_bits(regmap, MAX7360_REG_PWMCFG(i),
+ MAX7360_PORT_CFG_INTERRUPT_MASK, mask_buf & BIT(i));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max7360_gpio_probe(struct platform_device *pdev)
+{
+ const struct max7360_gpio_plat_data *plat_data;
+ struct gpio_regmap_config gpio_config = { };
+ struct regmap_irq_chip *irq_chip;
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ unsigned int outconf;
+ int ret;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "could not get parent regmap\n");
+
+ plat_data = device_get_match_data(dev);
+ if (plat_data->function == MAX7360_GPIO_PORT) {
+ if (device_property_read_bool(dev, "interrupt-controller")) {
+ /*
+ * Port GPIOs with interrupt-controller property: add IRQ
+ * controller.
+ */
+ gpio_config.regmap_irq_flags = IRQF_ONESHOT | IRQF_SHARED;
+ gpio_config.regmap_irq_line =
+ fwnode_irq_get_byname(dev_fwnode(dev->parent), "inti");
+ if (gpio_config.regmap_irq_line < 0)
+ return dev_err_probe(dev, gpio_config.regmap_irq_line,
+ "Failed to get IRQ\n");
+
+ /* Create custom IRQ configuration. */
+ irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
+ gpio_config.regmap_irq_chip = irq_chip;
+ if (!irq_chip)
+ return -ENOMEM;
+
+ irq_chip->name = dev_name(dev);
+ irq_chip->status_base = MAX7360_REG_GPIOIN;
+ irq_chip->status_is_level = true;
+ irq_chip->num_regs = 1;
+ irq_chip->num_irqs = MAX7360_MAX_GPIO;
+ irq_chip->irqs = max7360_regmap_irqs;
+ irq_chip->handle_mask_sync = max7360_handle_mask_sync;
+ irq_chip->irq_drv_data = regmap;
+
+ for (unsigned int i = 0; i < MAX7360_MAX_GPIO; i++) {
+ ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
+ MAX7360_PORT_CFG_INTERRUPT_EDGES,
+ MAX7360_PORT_CFG_INTERRUPT_EDGES);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to enable interrupts\n");
+ }
+ }
+
+ /*
+ * Port GPIOs: set output mode configuration (constant-current or not).
+ * This property is optional.
+ */
+ ret = device_property_read_u32(dev, "maxim,constant-current-disable", &outconf);
+ if (!ret) {
+ ret = regmap_write(regmap, MAX7360_REG_GPIOOUTM, outconf);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set constant-current configuration\n");
+ }
+ }
+
+ /* Add gpio device. */
+ gpio_config.parent = dev;
+ gpio_config.regmap = regmap;
+ if (plat_data->function == MAX7360_GPIO_PORT) {
+ gpio_config.ngpio = MAX7360_MAX_GPIO;
+ gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOIN);
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PWMBASE);
+ gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(MAX7360_REG_GPIOCTRL);
+ gpio_config.ngpio_per_reg = MAX7360_MAX_GPIO;
+ gpio_config.reg_mask_xlate = max7360_gpio_reg_mask_xlate;
+ } else {
+ ret = max7360_set_gpos_count(dev, regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set GPOS pin count\n");
+
+ gpio_config.reg_set_base = GPIO_REGMAP_ADDR(MAX7360_REG_PORTS);
+ gpio_config.ngpio = MAX7360_MAX_KEY_COLS;
+ gpio_config.init_valid_mask = max7360_gpo_init_valid_mask;
+ }
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
+}
+
+static const struct of_device_id max7360_gpio_of_match[] = {
+ {
+ .compatible = "maxim,max7360-gpo",
+ .data = &max7360_gpio_col_plat
+ }, {
+ .compatible = "maxim,max7360-gpio",
+ .data = &max7360_gpio_port_plat
+ }, {
+ }
+};
+MODULE_DEVICE_TABLE(of, max7360_gpio_of_match);
+
+static struct platform_driver max7360_gpio_driver = {
+ .driver = {
+ .name = "max7360-gpio",
+ .of_match_table = max7360_gpio_of_match,
+ },
+ .probe = max7360_gpio_probe,
+};
+module_platform_driver(max7360_gpio_driver);
+
+MODULE_DESCRIPTION("MAX7360 GPIO driver");
+MODULE_AUTHOR("Kamel BOUHARA <kamel.bouhara@bootlin.com>");
+MODULE_AUTHOR("Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>");
+MODULE_LICENSE("GPL");
--
2.39.5
^ permalink raw reply related
* [PATCH v13 02/10] mfd: Add max7360 support
From: Mathieu Dubois-Briand @ 2025-08-11 10:46 UTC (permalink / raw)
To: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Kamel Bouhara, Linus Walleij, Bartosz Golaszewski,
Dmitry Torokhov, Uwe Kleine-König, Michael Walle, Mark Brown,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
Cc: devicetree, linux-kernel, linux-gpio, linux-input, linux-pwm,
andriy.shevchenko, Grégory Clement, Thomas Petazzoni,
Mathieu Dubois-Briand, Andy Shevchenko
In-Reply-To: <20250811-mdb-max7360-support-v13-0-e79fcabff386@bootlin.com>
From: Kamel Bouhara <kamel.bouhara@bootlin.com>
Add core driver to support MAX7360 i2c chip, multi function device
with keypad, GPIO, PWM, GPO and rotary encoder submodules.
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Co-developed-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/mfd/Kconfig | 14 ++++
drivers/mfd/Makefile | 1 +
drivers/mfd/max7360.c | 171 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/max7360.h | 109 ++++++++++++++++++++++++++++
4 files changed, 295 insertions(+)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 425c5fba6cb1..58b1c2900d59 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2481,5 +2481,19 @@ config MFD_UPBOARD_FPGA
To compile this driver as a module, choose M here: the module will be
called upboard-fpga.
+config MFD_MAX7360
+ tristate "Maxim MAX7360 I2C IO Expander"
+ depends on I2C
+ select MFD_CORE
+ select REGMAP_I2C
+ select REGMAP_IRQ
+ help
+ Say yes here to add support for Maxim MAX7360 device, embedding
+ keypad, rotary encoder, PWM and GPIO features.
+
+ This driver provides common support for accessing the device;
+ additional drivers must be enabled in order to use the functionality
+ of the device.
+
endmenu
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index f7bdedd5a66d..c81c6a8473e1 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -163,6 +163,7 @@ obj-$(CONFIG_MFD_DA9063) += da9063.o
obj-$(CONFIG_MFD_DA9150) += da9150-core.o
obj-$(CONFIG_MFD_MAX14577) += max14577.o
+obj-$(CONFIG_MFD_MAX7360) += max7360.o
obj-$(CONFIG_MFD_MAX77541) += max77541.o
obj-$(CONFIG_MFD_MAX77620) += max77620.o
obj-$(CONFIG_MFD_MAX77650) += max77650.o
diff --git a/drivers/mfd/max7360.c b/drivers/mfd/max7360.c
new file mode 100644
index 000000000000..5ee459c490ec
--- /dev/null
+++ b/drivers/mfd/max7360.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Maxim MAX7360 Core Driver
+ *
+ * Copyright 2025 Bootlin
+ *
+ * Authors:
+ * Kamel Bouhara <kamel.bouhara@bootlin.com>
+ * Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max7360.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+static const struct mfd_cell max7360_cells[] = {
+ { .name = "max7360-pinctrl" },
+ { .name = "max7360-pwm" },
+ { .name = "max7360-keypad" },
+ { .name = "max7360-rotary" },
+ {
+ .name = "max7360-gpo",
+ .of_compatible = "maxim,max7360-gpo",
+ },
+ {
+ .name = "max7360-gpio",
+ .of_compatible = "maxim,max7360-gpio",
+ },
+};
+
+static const struct regmap_range max7360_volatile_ranges[] = {
+ regmap_reg_range(MAX7360_REG_KEYFIFO, MAX7360_REG_KEYFIFO),
+ regmap_reg_range(MAX7360_REG_I2C_TIMEOUT, MAX7360_REG_RTR_CNT),
+};
+
+static const struct regmap_access_table max7360_volatile_table = {
+ .yes_ranges = max7360_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(max7360_volatile_ranges),
+};
+
+static const struct regmap_config max7360_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX7360_REG_PWMCFG(MAX7360_PORT_PWM_COUNT - 1),
+ .volatile_table = &max7360_volatile_table,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int max7360_mask_irqs(struct regmap *regmap)
+{
+ struct device *dev = regmap_get_device(regmap);
+ unsigned int val;
+ int ret;
+
+ /*
+ * GPIO/PWM interrupts are not masked on reset: as the MAX7360 "INTI"
+ * interrupt line is shared between GPIOs and rotary encoder, this could
+ * result in repeated spurious interrupts on the rotary encoder driver
+ * if the GPIO driver is not loaded. Mask them now to avoid this
+ * situation.
+ */
+ for (unsigned int i = 0; i < MAX7360_PORT_PWM_COUNT; i++) {
+ ret = regmap_write_bits(regmap, MAX7360_REG_PWMCFG(i),
+ MAX7360_PORT_CFG_INTERRUPT_MASK,
+ MAX7360_PORT_CFG_INTERRUPT_MASK);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to write MAX7360 port configuration\n");
+ }
+
+ /* Read GPIO in register, to ACK any pending IRQ. */
+ ret = regmap_read(regmap, MAX7360_REG_GPIOIN, &val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read GPIO values\n");
+
+ return 0;
+}
+
+static int max7360_reset(struct regmap *regmap)
+{
+ struct device *dev = regmap_get_device(regmap);
+ int ret;
+
+ ret = regmap_write(regmap, MAX7360_REG_GPIOCFG, MAX7360_GPIO_CFG_GPIO_RST);
+ if (ret) {
+ dev_err(dev, "Failed to reset GPIO configuration: %x\n", ret);
+ return ret;
+ }
+
+ ret = regcache_drop_region(regmap, MAX7360_REG_GPIOCFG, MAX7360_REG_GPIO_LAST);
+ if (ret) {
+ dev_err(dev, "Failed to drop regmap cache: %x\n", ret);
+ return ret;
+ }
+
+ ret = regmap_write(regmap, MAX7360_REG_SLEEP, 0);
+ if (ret) {
+ dev_err(dev, "Failed to reset autosleep configuration: %x\n", ret);
+ return ret;
+ }
+
+ ret = regmap_write(regmap, MAX7360_REG_DEBOUNCE, 0);
+ if (ret)
+ dev_err(dev, "Failed to reset GPO port count: %x\n", ret);
+
+ return ret;
+}
+
+static int max7360_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(client, &max7360_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to initialise regmap\n");
+
+ ret = max7360_reset(regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to reset device\n");
+
+ /* Get the device out of shutdown mode. */
+ ret = regmap_write_bits(regmap, MAX7360_REG_GPIOCFG,
+ MAX7360_GPIO_CFG_GPIO_EN,
+ MAX7360_GPIO_CFG_GPIO_EN);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable GPIO and PWM module\n");
+
+ ret = max7360_mask_irqs(regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Could not mask interrupts\n");
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+ max7360_cells, ARRAY_SIZE(max7360_cells),
+ NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register child devices\n");
+
+ return 0;
+}
+
+static const struct of_device_id max7360_dt_match[] = {
+ { .compatible = "maxim,max7360" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, max7360_dt_match);
+
+static struct i2c_driver max7360_driver = {
+ .driver = {
+ .name = "max7360",
+ .of_match_table = max7360_dt_match,
+ },
+ .probe = max7360_probe,
+};
+module_i2c_driver(max7360_driver);
+
+MODULE_DESCRIPTION("Maxim MAX7360 I2C IO Expander core driver");
+MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max7360.h b/include/linux/mfd/max7360.h
new file mode 100644
index 000000000000..44cf2bf651a2
--- /dev/null
+++ b/include/linux/mfd/max7360.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __LINUX_MFD_MAX7360_H
+#define __LINUX_MFD_MAX7360_H
+
+#include <linux/bits.h>
+
+#define MAX7360_MAX_KEY_ROWS 8
+#define MAX7360_MAX_KEY_COLS 8
+#define MAX7360_MAX_KEY_NUM (MAX7360_MAX_KEY_ROWS * MAX7360_MAX_KEY_COLS)
+#define MAX7360_ROW_SHIFT 3
+
+#define MAX7360_MAX_GPIO 8
+#define MAX7360_MAX_GPO 6
+#define MAX7360_PORT_PWM_COUNT 8
+#define MAX7360_PORT_RTR_PIN (MAX7360_PORT_PWM_COUNT - 1)
+
+/*
+ * MAX7360 registers
+ */
+#define MAX7360_REG_KEYFIFO 0x00
+#define MAX7360_REG_CONFIG 0x01
+#define MAX7360_REG_DEBOUNCE 0x02
+#define MAX7360_REG_INTERRUPT 0x03
+#define MAX7360_REG_PORTS 0x04
+#define MAX7360_REG_KEYREP 0x05
+#define MAX7360_REG_SLEEP 0x06
+
+/*
+ * MAX7360 GPIO registers
+ *
+ * All these registers are reset together when writing bit 3 of
+ * MAX7360_REG_GPIOCFG.
+ */
+#define MAX7360_REG_GPIOCFG 0x40
+#define MAX7360_REG_GPIOCTRL 0x41
+#define MAX7360_REG_GPIODEB 0x42
+#define MAX7360_REG_GPIOCURR 0x43
+#define MAX7360_REG_GPIOOUTM 0x44
+#define MAX7360_REG_PWMCOM 0x45
+#define MAX7360_REG_RTRCFG 0x46
+#define MAX7360_REG_I2C_TIMEOUT 0x48
+#define MAX7360_REG_GPIOIN 0x49
+#define MAX7360_REG_RTR_CNT 0x4A
+#define MAX7360_REG_PWMBASE 0x50
+#define MAX7360_REG_PWMCFGBASE 0x58
+
+#define MAX7360_REG_GPIO_LAST 0x5F
+
+#define MAX7360_REG_PWM(x) (MAX7360_REG_PWMBASE + (x))
+#define MAX7360_REG_PWMCFG(x) (MAX7360_REG_PWMCFGBASE + (x))
+
+/*
+ * Configuration register bits
+ */
+#define MAX7360_FIFO_EMPTY 0x3F
+#define MAX7360_FIFO_OVERFLOW 0x7F
+#define MAX7360_FIFO_RELEASE BIT(6)
+#define MAX7360_FIFO_COL GENMASK(5, 3)
+#define MAX7360_FIFO_ROW GENMASK(2, 0)
+
+#define MAX7360_CFG_SLEEP BIT(7)
+#define MAX7360_CFG_INTERRUPT BIT(5)
+#define MAX7360_CFG_KEY_RELEASE BIT(3)
+#define MAX7360_CFG_WAKEUP BIT(1)
+#define MAX7360_CFG_TIMEOUT BIT(0)
+
+#define MAX7360_DEBOUNCE GENMASK(4, 0)
+#define MAX7360_DEBOUNCE_MIN 9
+#define MAX7360_DEBOUNCE_MAX 40
+#define MAX7360_PORTS GENMASK(8, 5)
+
+#define MAX7360_INTERRUPT_TIME_MASK GENMASK(4, 0)
+#define MAX7360_INTERRUPT_FIFO_MASK GENMASK(7, 5)
+
+#define MAX7360_PORT_CFG_INTERRUPT_MASK BIT(7)
+#define MAX7360_PORT_CFG_INTERRUPT_EDGES BIT(6)
+#define MAX7360_PORT_CFG_COMMON_PWM BIT(5)
+
+/*
+ * Autosleep register values
+ */
+#define MAX7360_AUTOSLEEP_8192MS 0x01
+#define MAX7360_AUTOSLEEP_4096MS 0x02
+#define MAX7360_AUTOSLEEP_2048MS 0x03
+#define MAX7360_AUTOSLEEP_1024MS 0x04
+#define MAX7360_AUTOSLEEP_512MS 0x05
+#define MAX7360_AUTOSLEEP_256MS 0x06
+
+#define MAX7360_GPIO_CFG_RTR_EN BIT(7)
+#define MAX7360_GPIO_CFG_GPIO_EN BIT(4)
+#define MAX7360_GPIO_CFG_GPIO_RST BIT(3)
+
+#define MAX7360_ROT_DEBOUNCE GENMASK(3, 0)
+#define MAX7360_ROT_DEBOUNCE_MIN 0
+#define MAX7360_ROT_DEBOUNCE_MAX 15
+#define MAX7360_ROT_INTCNT GENMASK(6, 4)
+#define MAX7360_ROT_INTCNT_DLY BIT(7)
+
+#define MAX7360_INT_INTI 0
+#define MAX7360_INT_INTK 1
+
+#define MAX7360_INT_GPIO 0
+#define MAX7360_INT_KEYPAD 1
+#define MAX7360_INT_ROTARY 2
+
+#define MAX7360_NR_INTERNAL_IRQS 3
+
+#endif
--
2.39.5
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox