mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/2] Input: Add Awinic AW8697 haptics driver
@ 2026-09-30 17:18 Armandas Kvietkus via B4 Relay
  2026-09-30 17:18 ` [PATCH v2 1/2] dt-bindings: input: awinic,aw86927: Add Awinic AW8697 Armandas Kvietkus via B4 Relay
  2026-09-30 17:18 ` [PATCH v2 2/2] Input: aw8697 - Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay
  0 siblings, 2 replies; 3+ messages in thread
From: Armandas Kvietkus via B4 Relay @ 2026-09-30 17:18 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Griffin Kroah-Hartman
  Cc: linux-input, devicetree, linux-kernel, Armandas Kvietkus

This series adds a driver for the Awinic AW8697 LRA haptic driver IC.
It is the vibrator in the Xiaomi POCO F3.

The AW8697 is an older generation than the AW86927. The register map
is different, so it gets its own driver. The binding reuses
awinic,aw86927.yaml.

Tested on the phone: rumble at several strengths, 72 effects in a row,
stop on close, driver rebind and module reload.

Signed-off-by: Armandas Kvietkus <armandas.kvietkus@proton.me>
---
Changes in v2:
- Do not queue I2C work from the ff-memless timer during system
  suspend. A suspended flag is set and cleared under the input event
  lock, as in da7280. Tested with delayed effects across suspend.
- Use explicit error checks instead of returning the last regmap call.
- The NULL IRQ name finding is not changed. devm_request_threaded_irq()
  uses dev_name() when the name is NULL. /proc/interrupts shows 11-005a.
- Link to v1: https://patch.msgid.link/20260930-mainlining-v1-0-c303cbdb00c8@proton.me

v2 only fixes findings from the automated AI review. I will not send
another revision for automated review findings alone, because that
can go on forever. I will wait for human review.

---
Armandas Kvietkus (2):
      dt-bindings: input: awinic,aw86927: Add Awinic AW8697
      Input: aw8697 - Add Awinic AW8697 haptics driver

 .../devicetree/bindings/input/awinic,aw86927.yaml  |   4 +-
 MAINTAINERS                                        |   6 +
 drivers/input/misc/Kconfig                         |  14 +
 drivers/input/misc/Makefile                        |   1 +
 drivers/input/misc/aw8697.c                        | 761 +++++++++++++++++++++
 5 files changed, 785 insertions(+), 1 deletion(-)
---
base-commit: 165768bb70265b5c38cf0b73fafd75be235f8b14
change-id: 20260930-mainlining-0bd8b7b5ca6c

Best regards,
--  
Armandas Kvietkus <armandas.kvietkus@proton.me>



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 1/2] dt-bindings: input: awinic,aw86927: Add Awinic AW8697
  2026-09-30 17:18 [PATCH v2 0/2] Input: Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay
@ 2026-09-30 17:18 ` Armandas Kvietkus via B4 Relay
  2026-09-30 17:18 ` [PATCH v2 2/2] Input: aw8697 - Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Armandas Kvietkus via B4 Relay @ 2026-09-30 17:18 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Griffin Kroah-Hartman
  Cc: linux-input, devicetree, linux-kernel, Armandas Kvietkus

From: Armandas Kvietkus <armandas.kvietkus@proton.me>

The AW8697 uses the same bus, interrupt and reset lines as the AW86927.
The register map is different, so there is no fallback compatible.

Signed-off-by: Armandas Kvietkus <armandas.kvietkus@proton.me>
---
 Documentation/devicetree/bindings/input/awinic,aw86927.yaml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/input/awinic,aw86927.yaml b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
index bd74b81488f6..f0e26664e3e4 100644
--- a/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
+++ b/Documentation/devicetree/bindings/input/awinic,aw86927.yaml
@@ -12,7 +12,9 @@ maintainers:
 properties:
   compatible:
     oneOf:
-      - const: awinic,aw86927
+      - enum:
+          - awinic,aw8697
+          - awinic,aw86927
       - items:
           - enum:
               - awinic,aw86938

-- 
2.55.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v2 2/2] Input: aw8697 - Add Awinic AW8697 haptics driver
  2026-09-30 17:18 [PATCH v2 0/2] Input: Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay
  2026-09-30 17:18 ` [PATCH v2 1/2] dt-bindings: input: awinic,aw86927: Add Awinic AW8697 Armandas Kvietkus via B4 Relay
@ 2026-09-30 17:18 ` Armandas Kvietkus via B4 Relay
  1 sibling, 0 replies; 3+ messages in thread
From: Armandas Kvietkus via B4 Relay @ 2026-09-30 17:18 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Griffin Kroah-Hartman
  Cc: linux-input, devicetree, linux-kernel, Armandas Kvietkus

From: Armandas Kvietkus <armandas.kvietkus@proton.me>

Add a driver for the Awinic AW8697 LRA haptic driver IC. It supports
FF_RUMBLE and calibrates the LRA resonance at probe.

Based on the aw86927 driver.

Tested on a Xiaomi POCO F3.

Signed-off-by: Armandas Kvietkus <armandas.kvietkus@proton.me>
---
 MAINTAINERS                 |   6 +
 drivers/input/misc/Kconfig  |  14 +
 drivers/input/misc/Makefile |   1 +
 drivers/input/misc/aw8697.c | 761 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 782 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c52ec2d7d3c1..b00217bdca20 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4419,6 +4419,12 @@ L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	drivers/media/pci/hws/
 
+AWINIC AW8697 HAPTICS DRIVER
+M:	Armandas Kvietkus <armandas.kvietkus@proton.me>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/input/misc/aw8697.c
+
 AWINIC AW99706 WLED BACKLIGHT DRIVER
 M:	Junjie Cao <junjie.cao@linux.dev>
 S:	Maintained
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 169768e3d5bb..d2b71a0f9aee 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -126,6 +126,20 @@ config INPUT_ATMEL_CAPTOUCH
 	  To compile this driver as a module, choose M here: the
 	  module will be called atmel_captouch.
 
+config INPUT_AW8697
+	tristate "Awinic AW8697 Haptic Driver Support"
+	depends on I2C && INPUT
+	select INPUT_FF_MEMLESS
+	select REGMAP_I2C
+	help
+	  Say Y here if you have an Awinic AW8697 haptic chip. The
+	  AW8697 drives a linear resonant actuator and is found in
+	  phones such as the Xiaomi POCO F3. The driver exposes a
+	  force feedback input device with rumble support.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called aw8697.
+
 config INPUT_AW86927
 	tristate "Awinic AW86927 Haptic Driver Support"
 	depends on I2C && INPUT
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 1339619f0769..171d3ff1f167 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -23,6 +23,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_AW8697)		+= aw8697.o
 obj-$(CONFIG_INPUT_AW86927)		+= aw86927.o
 obj-$(CONFIG_INPUT_BBNSM_PWRKEY)	+= nxp-bbnsm-pwrkey.o
 obj-$(CONFIG_INPUT_BMA150)		+= bma150.o
diff --git a/drivers/input/misc/aw8697.c b/drivers/input/misc/aw8697.c
new file mode 100644
index 000000000000..96c0205089fd
--- /dev/null
+++ b/drivers/input/misc/aw8697.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Armandas Kvietkus <armandas.kvietkus@proton.me>
+ * Copyright (C) 2025 Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
+ * Copyright (c) 2021 AWINIC Technology CO., LTD
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#define AW8697_ID_REG				0x00
+#define AW8697_ID_SOFTRST			0xaa
+#define AW8697_CHIPID				0x97
+
+#define AW8697_SYSINT_REG			0x02
+#define AW8697_SYSINT_BSTERRI			BIT(7)
+#define AW8697_SYSINT_OVI			BIT(6)
+#define AW8697_SYSINT_UVLI			BIT(5)
+#define AW8697_SYSINT_FF_AEI			BIT(4)
+#define AW8697_SYSINT_FF_AFI			BIT(3)
+#define AW8697_SYSINT_OCDI			BIT(2)
+#define AW8697_SYSINT_OTI			BIT(1)
+#define AW8697_SYSINT_DONEI			BIT(0)
+
+#define AW8697_SYSINTM_REG			0x03
+#define AW8697_SYSINTM_BSTERRM			BIT(7)
+#define AW8697_SYSINTM_UVLM			BIT(5)
+#define AW8697_SYSINTM_FF_AEM			BIT(4)
+#define AW8697_SYSINTM_FF_AFM			BIT(3)
+#define AW8697_SYSINTM_DONEM			BIT(0)
+
+#define AW8697_SYSCTRL_REG			0x04
+#define AW8697_SYSCTRL_RAMINIT			BIT(5)
+#define AW8697_SYSCTRL_PLAY_MODE_MASK		GENMASK(3, 2)
+#define AW8697_SYSCTRL_PLAY_MODE_RAM		0
+#define AW8697_SYSCTRL_PLAY_MODE_CONT		2
+#define AW8697_SYSCTRL_BST_MODE_BOOST		BIT(1)
+#define AW8697_SYSCTRL_WORK_MODE_STANDBY	BIT(0)
+
+#define AW8697_GO_REG				0x05
+#define AW8697_GO				BIT(0)
+
+#define AW8697_WAVSEQ1_REG			0x07
+#define AW8697_WAVSEQ2_REG			0x08
+#define AW8697_WAVSEQ_WAV_MASK			GENMASK(6, 0)
+
+#define AW8697_WAVLOOP1_REG			0x0f
+#define AW8697_WAVLOOP1_SEQ1_MASK		GENMASK(7, 4)
+#define AW8697_WAVLOOP_INFINITE			0x0f
+
+#define AW8697_DBGCTRL_REG			0x20
+#define AW8697_DBGCTRL_INT_MODE_EDGE		BIT(2)
+
+#define AW8697_BASEADDRH_REG			0x21
+#define AW8697_BASEADDRL_REG			0x22
+
+#define AW8697_DATCTRL_REG			0x2b
+#define AW8697_DATCTRL_FC_1000HZ		GENMASK(7, 6)
+#define AW8697_DATCTRL_LPF_EN			BIT(5)
+
+#define AW8697_PWMPRC_REG			0x2d
+#define AW8697_PWMPRC_PRC_EN			BIT(7)
+
+#define AW8697_PWMDBG_REG			0x2e
+#define AW8697_PWMDBG_PWM_MODE_MASK		GENMASK(6, 5)
+#define AW8697_PWMDBG_PWM_24K			2
+
+#define AW8697_BSTDBG1_REG			0x31
+#define AW8697_BSTDBG2_REG			0x32
+#define AW8697_BSTDBG3_REG			0x33
+
+#define AW8697_ANADBG_REG			0x35
+#define AW8697_ANADBG_IOC_MASK			GENMASK(3, 2)
+#define AW8697_ANADBG_IOC_4P65A			3
+
+#define AW8697_ANACTRL_REG			0x36
+#define AW8697_ANACTRL_LRA_SRC_REG		BIT(5)
+
+#define AW8697_DATDBG_REG			0x39
+
+#define AW8697_PRLVL_REG			0x3e
+#define AW8697_PRLVL_PR_EN			BIT(7)
+
+#define AW8697_RAMADDRH_REG			0x40
+#define AW8697_RAMADDRL_REG			0x41
+#define AW8697_RAMDATA_REG			0x42
+
+#define AW8697_GLBSTATE_REG			0x46
+#define AW8697_GLBSTATE_STATE_MASK		GENMASK(3, 0)
+#define AW8697_GLBSTATE_STANDBY			0
+
+#define AW8697_BSTAUTO_REG			0x47
+#define AW8697_BSTAUTO_AUTOSW			BIT(2)
+
+#define AW8697_CONTCTRL_REG			0x48
+#define AW8697_CONTCTRL_CLOSE_PLAYBACK		BIT(3)
+#define AW8697_CONTCTRL_F0_DETECT		BIT(2)
+
+#define AW8697_FPREH_REG			0x49
+#define AW8697_FPREL_REG			0x4a
+
+#define AW8697_TRIMLRA_REG			0x5b
+#define AW8697_TRIMLRA_MASK			GENMASK(5, 0)
+
+#define AW8697_RSPARE_REG			0x5d
+
+#define AW8697_ADCTEST_REG			0x66
+#define AW8697_ADCTEST_VBAT_HW			BIT(6)
+
+#define AW8697_FLRAF0H_REG			0x68
+#define AW8697_FLRAF0L_REG			0x69
+
+#define AW8697_DRVLVL_REG			0x7b
+#define AW8697_DRVLVL_F0_DETECT			0x49
+
+#define AW8697_NUMF01_REG			0x7d
+#define AW8697_NUMF01_PRE_MASK			GENMASK(7, 4)
+#define AW8697_NUMF01_WAIT_MASK			GENMASK(3, 0)
+#define AW8697_NUMF02_REG			0x7e
+#define AW8697_NUMF03_REG			0x7f
+
+#define AW8697_F0_PRE_NUM			9
+#define AW8697_F0_WAIT_NUM			3
+#define AW8697_F0_REPEAT_NUM			1
+#define AW8697_F0_TRACE_NUM			31
+#define AW8697_F0_CLK_DHZ			3846154
+#define AW8697_F0_REF_DHZ			1700
+#define AW8697_F0_TOLERANCE_PCT			7
+#define AW8697_TRIM_STEPS_PER_UNIT		400
+
+#define AW8697_RAM_BASE_ADDR			0x800
+#define AW8697_GAIN_MAX				0x80
+
+struct aw8697_data {
+	struct work_struct play_work;
+	struct device *dev;
+	struct input_dev *input_dev;
+	struct regmap *regmap;
+	struct gpio_desc *reset_gpio;
+	u16 level;
+	bool suspended;
+};
+
+static const struct regmap_config aw8697_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_NONE,
+	.max_register = 0x7f,
+};
+
+static const u8 aw8697_waveform[] = {
+	0x00, 0x02, 0x03, 0x05, 0x07, 0x08, 0x0a, 0x0c, 0x0d, 0x0f, 0x10, 0x12,
+	0x13, 0x15, 0x16, 0x17, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+	0x21, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x26,
+	0x26, 0x26, 0x26, 0x26, 0x25, 0x25, 0x24, 0x24, 0x23, 0x23, 0x22, 0x21,
+	0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x17, 0x16, 0x15, 0x13,
+	0x12, 0x10, 0x0f, 0x0d, 0x0c, 0x0a, 0x08, 0x07, 0x05, 0x03, 0x02, 0x00,
+	0xfe, 0xfd, 0xfb, 0xf9, 0xf8, 0xf6, 0xf4, 0xf3, 0xf1, 0xf0, 0xee, 0xed,
+	0xeb, 0xea, 0xe9, 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf,
+	0xde, 0xdd, 0xdd, 0xdc, 0xdc, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xda,
+	0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdc, 0xdc, 0xdd, 0xdd, 0xde, 0xdf, 0xe0,
+	0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xed, 0xee,
+	0xf0, 0xf1, 0xf3, 0xf4, 0xf6, 0xf8, 0xf9, 0xfb, 0xfd, 0xfe,
+};
+
+struct aw8697_sram_header {
+	u8 version;
+	__be16 start_address;
+	__be16 end_address;
+} __packed;
+
+static const struct aw8697_sram_header aw8697_sram_header = {
+	.version = 0x01,
+	.start_address = cpu_to_be16(AW8697_RAM_BASE_ADDR +
+				     sizeof(struct aw8697_sram_header)),
+	.end_address = cpu_to_be16(AW8697_RAM_BASE_ADDR +
+				   sizeof(struct aw8697_sram_header) +
+				   ARRAY_SIZE(aw8697_waveform) - 1),
+};
+
+static int aw8697_stop(struct aw8697_data *haptics)
+{
+	unsigned int val;
+	int err;
+
+	err = regmap_clear_bits(haptics->regmap, AW8697_GO_REG, AW8697_GO);
+	if (err)
+		return err;
+
+	err = regmap_read_poll_timeout(haptics->regmap, AW8697_GLBSTATE_REG, val,
+				       FIELD_GET(AW8697_GLBSTATE_STATE_MASK, val) ==
+						AW8697_GLBSTATE_STANDBY,
+				       2500, 2500 * 100);
+	if (err)
+		dev_warn(haptics->dev, "Failed to enter standby, forcing it\n");
+
+	err = regmap_set_bits(haptics->regmap, AW8697_SYSCTRL_REG,
+			      AW8697_SYSCTRL_WORK_MODE_STANDBY);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int aw8697_play(struct aw8697_data *haptics)
+{
+	int err;
+
+	err = aw8697_stop(haptics);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_WAVSEQ1_REG,
+				 AW8697_WAVSEQ_WAV_MASK,
+				 FIELD_PREP(AW8697_WAVSEQ_WAV_MASK, 1));
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_WAVSEQ2_REG,
+				 AW8697_WAVSEQ_WAV_MASK, 0);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_WAVLOOP1_REG,
+				 AW8697_WAVLOOP1_SEQ1_MASK,
+				 FIELD_PREP(AW8697_WAVLOOP1_SEQ1_MASK,
+					    AW8697_WAVLOOP_INFINITE));
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_DATDBG_REG,
+			   haptics->level * AW8697_GAIN_MAX / U16_MAX);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_SYSCTRL_REG,
+				 AW8697_SYSCTRL_PLAY_MODE_MASK |
+					AW8697_SYSCTRL_BST_MODE_BOOST |
+					AW8697_SYSCTRL_WORK_MODE_STANDBY,
+				 FIELD_PREP(AW8697_SYSCTRL_PLAY_MODE_MASK,
+					    AW8697_SYSCTRL_PLAY_MODE_RAM) |
+					AW8697_SYSCTRL_BST_MODE_BOOST);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_GO_REG, AW8697_GO);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static void aw8697_play_work(struct work_struct *work)
+{
+	struct aw8697_data *haptics = container_of(work, struct aw8697_data, play_work);
+	int err;
+
+	if (haptics->level)
+		err = aw8697_play(haptics);
+	else
+		err = aw8697_stop(haptics);
+
+	if (err)
+		dev_err(haptics->dev, "Failed to set playback state: %d\n", err);
+}
+
+static int aw8697_play_effect(struct input_dev *dev, void *data,
+			      struct ff_effect *effect)
+{
+	struct aw8697_data *haptics = input_get_drvdata(dev);
+	u16 level;
+
+	level = effect->u.rumble.strong_magnitude;
+	if (!level)
+		level = effect->u.rumble.weak_magnitude;
+
+	if (haptics->suspended || haptics->level == level)
+		return 0;
+
+	haptics->level = level;
+	schedule_work(&haptics->play_work);
+
+	return 0;
+}
+
+static void aw8697_halt(struct aw8697_data *haptics)
+{
+	int err;
+
+	cancel_work_sync(&haptics->play_work);
+	haptics->level = 0;
+
+	err = aw8697_stop(haptics);
+	if (err)
+		dev_err(haptics->dev, "Failed to stop playback: %d\n", err);
+}
+
+static void aw8697_close(struct input_dev *input)
+{
+	struct aw8697_data *haptics = input_get_drvdata(input);
+
+	aw8697_halt(haptics);
+}
+
+static int aw8697_hw_init(struct aw8697_data *haptics)
+{
+	int err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_PWMDBG_REG,
+				 AW8697_PWMDBG_PWM_MODE_MASK,
+				 FIELD_PREP(AW8697_PWMDBG_PWM_MODE_MASK,
+					    AW8697_PWMDBG_PWM_24K));
+	if (err)
+		return err;
+
+	err = regmap_clear_bits(haptics->regmap, AW8697_PWMPRC_REG,
+				AW8697_PWMPRC_PRC_EN);
+	if (err)
+		return err;
+
+	err = regmap_clear_bits(haptics->regmap, AW8697_PRLVL_REG,
+				AW8697_PRLVL_PR_EN);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_ADCTEST_REG,
+			      AW8697_ADCTEST_VBAT_HW);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_BSTDBG1_REG, 0x30);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_BSTDBG2_REG, 0xeb);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_BSTDBG3_REG, 0xd4);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_ANADBG_REG,
+				 AW8697_ANADBG_IOC_MASK,
+				 FIELD_PREP(AW8697_ANADBG_IOC_MASK,
+					    AW8697_ANADBG_IOC_4P65A));
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_RSPARE_REG, 0x68);
+	if (err)
+		return err;
+
+	err = regmap_clear_bits(haptics->regmap, AW8697_BSTAUTO_REG,
+				AW8697_BSTAUTO_AUTOSW);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int aw8697_ram_init(struct aw8697_data *haptics)
+{
+	int err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_SYSCTRL_REG,
+			      AW8697_SYSCTRL_RAMINIT);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_BASEADDRH_REG,
+			   AW8697_RAM_BASE_ADDR >> 8);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_BASEADDRL_REG,
+			   AW8697_RAM_BASE_ADDR & 0xff);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_RAMADDRH_REG,
+			   AW8697_RAM_BASE_ADDR >> 8);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_RAMADDRL_REG,
+			   AW8697_RAM_BASE_ADDR & 0xff);
+	if (err)
+		return err;
+
+	err = regmap_noinc_write(haptics->regmap, AW8697_RAMDATA_REG,
+				 &aw8697_sram_header, sizeof(aw8697_sram_header));
+	if (err)
+		return err;
+
+	err = regmap_noinc_write(haptics->regmap, AW8697_RAMDATA_REG,
+				 aw8697_waveform, ARRAY_SIZE(aw8697_waveform));
+	if (err)
+		return err;
+
+	err = regmap_clear_bits(haptics->regmap, AW8697_SYSCTRL_REG,
+				AW8697_SYSCTRL_RAMINIT);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int aw8697_f0_measure(struct aw8697_data *haptics, unsigned int *period)
+{
+	unsigned int fpre = AW8697_F0_CLK_DHZ / AW8697_F0_REF_DHZ;
+	unsigned int val;
+	u8 buf[2];
+	int err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_SYSCTRL_REG,
+				 AW8697_SYSCTRL_PLAY_MODE_MASK |
+					AW8697_SYSCTRL_BST_MODE_BOOST |
+					AW8697_SYSCTRL_WORK_MODE_STANDBY,
+				 FIELD_PREP(AW8697_SYSCTRL_PLAY_MODE_MASK,
+					    AW8697_SYSCTRL_PLAY_MODE_CONT) |
+					AW8697_SYSCTRL_BST_MODE_BOOST);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_CONTCTRL_REG,
+				 AW8697_CONTCTRL_CLOSE_PLAYBACK |
+					AW8697_CONTCTRL_F0_DETECT,
+				 AW8697_CONTCTRL_F0_DETECT);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_DATCTRL_REG,
+			      AW8697_DATCTRL_FC_1000HZ | AW8697_DATCTRL_LPF_EN);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_ANACTRL_REG,
+			      AW8697_ANACTRL_LRA_SRC_REG);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_FPREH_REG, fpre >> 8);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_FPREL_REG, fpre & 0xff);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_DRVLVL_REG,
+			   AW8697_DRVLVL_F0_DETECT);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_NUMF01_REG,
+			   FIELD_PREP(AW8697_NUMF01_PRE_MASK, AW8697_F0_PRE_NUM) |
+			   FIELD_PREP(AW8697_NUMF01_WAIT_MASK, AW8697_F0_WAIT_NUM));
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_NUMF02_REG,
+			   AW8697_F0_REPEAT_NUM);
+	if (err)
+		return err;
+
+	err = regmap_write(haptics->regmap, AW8697_NUMF03_REG,
+			   AW8697_F0_TRACE_NUM);
+	if (err)
+		return err;
+
+	err = regmap_read(haptics->regmap, AW8697_SYSINT_REG, &val);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_GO_REG, AW8697_GO);
+	if (err)
+		return err;
+
+	err = regmap_read_poll_timeout(haptics->regmap, AW8697_SYSINT_REG, val,
+				       val & AW8697_SYSINT_DONEI,
+				       10000, 500000);
+	if (err)
+		return err;
+
+	err = regmap_bulk_read(haptics->regmap, AW8697_FLRAF0H_REG, buf,
+			       sizeof(buf));
+	if (err)
+		return err;
+
+	*period = (buf[0] << 8) | buf[1];
+
+	return 0;
+}
+
+static int aw8697_f0_calibrate(struct aw8697_data *haptics)
+{
+	unsigned int period = 0;
+	int f0, step, err, ret;
+
+	err = regmap_write(haptics->regmap, AW8697_TRIMLRA_REG, 0);
+	if (err)
+		return err;
+
+	ret = aw8697_f0_measure(haptics, &period);
+
+	err = aw8697_stop(haptics);
+	if (err)
+		return err;
+
+	err = regmap_update_bits(haptics->regmap, AW8697_CONTCTRL_REG,
+				 AW8697_CONTCTRL_CLOSE_PLAYBACK |
+					AW8697_CONTCTRL_F0_DETECT,
+				 AW8697_CONTCTRL_CLOSE_PLAYBACK);
+	if (err)
+		return err;
+
+	if (ret)
+		return ret;
+
+	if (!period)
+		return -EINVAL;
+
+	f0 = AW8697_F0_CLK_DHZ / period;
+	if (abs(f0 - AW8697_F0_REF_DHZ) * 100 >
+	    AW8697_F0_REF_DHZ * AW8697_F0_TOLERANCE_PCT)
+		return -ERANGE;
+
+	step = DIV_ROUND_CLOSEST((f0 - AW8697_F0_REF_DHZ) *
+				 AW8697_TRIM_STEPS_PER_UNIT, f0);
+
+	dev_dbg(haptics->dev, "LRA f0 %d.%d Hz, trim %d\n",
+		f0 / 10, f0 % 10, step);
+
+	err = regmap_write(haptics->regmap, AW8697_TRIMLRA_REG,
+			   step & AW8697_TRIMLRA_MASK);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static irqreturn_t aw8697_irq(int irq, void *data)
+{
+	struct aw8697_data *haptics = data;
+	struct device *dev = haptics->dev;
+	unsigned int val;
+	int err;
+
+	err = regmap_read(haptics->regmap, AW8697_SYSINT_REG, &val);
+	if (err) {
+		dev_err(dev, "Failed to read SYSINT register: %d\n", err);
+		return IRQ_NONE;
+	}
+
+	if (val & AW8697_SYSINT_BSTERRI)
+		dev_err(dev, "Boost error interrupt\n");
+	if (val & AW8697_SYSINT_OVI)
+		dev_err(dev, "Over voltage interrupt\n");
+	if (val & AW8697_SYSINT_UVLI)
+		dev_err(dev, "Under voltage lockout interrupt\n");
+	if (val & AW8697_SYSINT_OCDI)
+		dev_err(dev, "Over current interrupt\n");
+	if (val & AW8697_SYSINT_OTI)
+		dev_err(dev, "Over temperature interrupt\n");
+	if (val & AW8697_SYSINT_DONEI)
+		dev_dbg(dev, "Playback done\n");
+	if (val & AW8697_SYSINT_FF_AFI)
+		dev_dbg(dev, "FIFO almost full\n");
+	if (val & AW8697_SYSINT_FF_AEI)
+		dev_dbg(dev, "FIFO almost empty\n");
+
+	return IRQ_HANDLED;
+}
+
+static void aw8697_hw_reset(struct aw8697_data *haptics)
+{
+	gpiod_set_value_cansleep(haptics->reset_gpio, 1);
+	usleep_range(1000, 2000);
+	gpiod_set_value_cansleep(haptics->reset_gpio, 0);
+	usleep_range(8000, 8500);
+}
+
+static int aw8697_detect(struct aw8697_data *haptics)
+{
+	unsigned int chip_id;
+	int err;
+
+	err = regmap_read(haptics->regmap, AW8697_ID_REG, &chip_id);
+	if (err)
+		return dev_err_probe(haptics->dev, err, "Failed to read chip ID\n");
+
+	if (chip_id != AW8697_CHIPID)
+		return dev_err_probe(haptics->dev, -ENODEV,
+				     "Unexpected chip ID 0x%02x\n", chip_id);
+
+	return 0;
+}
+
+static int aw8697_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct aw8697_data *haptics;
+	unsigned int val;
+	int err;
+
+	haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+	if (!haptics)
+		return -ENOMEM;
+
+	haptics->dev = dev;
+	i2c_set_clientdata(client, haptics);
+
+	haptics->regmap = devm_regmap_init_i2c(client, &aw8697_regmap_config);
+	if (IS_ERR(haptics->regmap))
+		return dev_err_probe(dev, PTR_ERR(haptics->regmap),
+				     "Failed to allocate register map\n");
+
+	haptics->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(haptics->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(haptics->reset_gpio),
+				     "Failed to get reset gpio\n");
+
+	aw8697_hw_reset(haptics);
+
+	err = regmap_write(haptics->regmap, AW8697_ID_REG, AW8697_ID_SOFTRST);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to reset chip\n");
+
+	usleep_range(3000, 3500);
+
+	err = aw8697_detect(haptics);
+	if (err)
+		return err;
+
+	err = regmap_set_bits(haptics->regmap, AW8697_DBGCTRL_REG,
+			      AW8697_DBGCTRL_INT_MODE_EDGE);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to set interrupt mode\n");
+
+	err = regmap_write(haptics->regmap, AW8697_SYSINTM_REG,
+			   AW8697_SYSINTM_BSTERRM | AW8697_SYSINTM_UVLM |
+			   AW8697_SYSINTM_FF_AEM | AW8697_SYSINTM_FF_AFM |
+			   AW8697_SYSINTM_DONEM);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to set interrupt mask\n");
+
+	err = aw8697_stop(haptics);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to enter standby\n");
+
+	err = aw8697_hw_init(haptics);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to configure chip\n");
+
+	err = aw8697_ram_init(haptics);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to load waveform\n");
+
+	err = aw8697_f0_calibrate(haptics);
+	if (err)
+		dev_warn(dev, "LRA f0 calibration failed: %d\n", err);
+
+	err = regmap_read(haptics->regmap, AW8697_SYSINT_REG, &val);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to clear interrupts\n");
+
+	err = devm_request_threaded_irq(dev, client->irq, NULL, aw8697_irq,
+					IRQF_ONESHOT, NULL, haptics);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to request irq\n");
+
+	INIT_WORK(&haptics->play_work, aw8697_play_work);
+
+	haptics->input_dev = devm_input_allocate_device(dev);
+	if (!haptics->input_dev)
+		return -ENOMEM;
+
+	haptics->input_dev->name = "aw8697-haptics";
+	haptics->input_dev->close = aw8697_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, aw8697_play_effect);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to create FF device\n");
+
+	err = input_register_device(haptics->input_dev);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to register input device\n");
+
+	return 0;
+}
+
+static int aw8697_suspend(struct device *dev)
+{
+	struct aw8697_data *haptics = dev_get_drvdata(dev);
+
+	guard(mutex)(&haptics->input_dev->mutex);
+
+	scoped_guard(spinlock_irq, &haptics->input_dev->event_lock)
+		haptics->suspended = true;
+
+	if (input_device_enabled(haptics->input_dev))
+		aw8697_halt(haptics);
+
+	return 0;
+}
+
+static int aw8697_resume(struct device *dev)
+{
+	struct aw8697_data *haptics = dev_get_drvdata(dev);
+
+	guard(mutex)(&haptics->input_dev->mutex);
+
+	scoped_guard(spinlock_irq, &haptics->input_dev->event_lock)
+		haptics->suspended = false;
+
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aw8697_pm_ops, aw8697_suspend, aw8697_resume);
+
+static const struct i2c_device_id aw8697_id[] = {
+	{ "aw8697" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, aw8697_id);
+
+static const struct of_device_id aw8697_of_match[] = {
+	{ .compatible = "awinic,aw8697" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aw8697_of_match);
+
+static struct i2c_driver aw8697_driver = {
+	.driver = {
+		.name = "aw8697-haptics",
+		.of_match_table = aw8697_of_match,
+		.pm = pm_sleep_ptr(&aw8697_pm_ops),
+	},
+	.probe = aw8697_probe,
+	.id_table = aw8697_id,
+};
+module_i2c_driver(aw8697_driver);
+
+MODULE_AUTHOR("Armandas Kvietkus <armandas.kvietkus@proton.me>");
+MODULE_DESCRIPTION("Awinic AW8697 LRA haptics driver");
+MODULE_LICENSE("GPL");

-- 
2.55.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-30 17:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 17:18 [PATCH v2 0/2] Input: Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay
2026-09-30 17:18 ` [PATCH v2 1/2] dt-bindings: input: awinic,aw86927: Add Awinic AW8697 Armandas Kvietkus via B4 Relay
2026-09-30 17:18 ` [PATCH v2 2/2] Input: aw8697 - Add Awinic AW8697 haptics driver Armandas Kvietkus via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®