* [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC
@ 2026-06-16 10:08 Fenglin Wu
2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu
` (3 more replies)
0 siblings, 4 replies; 12+ messages in thread
From: Fenglin Wu @ 2026-06-16 10:08 UTC (permalink / raw)
To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel, Fenglin Wu
Qualcomm PMIH010x PMIC has a haptics module inside and it could drive
a LRA actuator with several play modes, including: DIRECT_PLAY, FIFO,
PAT_MEM, SWR, etc. Add an initial driver to support two of the play
modes using the input force-feedback framework:
-- FF_CONSTANT effect for DIRECT_PLAY mode which drives sinusoidual
waveforms with fixed period and amplitude, which would generate
a constant vibration effect on the LRA actuator.
-- FF_PERIODIC effect with FF_CUSTOM for FIFO streaming mode, which
can play an arbitrary waveform composed of a sequence of 8-bit
samples at a configurable play rate.
Also, add the device node in the existing pmih0108 dtsi files, and enble
the haptics device for several boards by updating the vmax and
lra-period sttings according to the LRA components that mounted on each
of them.
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
Fenglin Wu (4):
dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics
dt-bindings: mfd: qcom,spmi-pmic: Document haptics device
input: misc: Add Qualcomm SPMI PMIC haptics driver
arm64: dts: qcom: Add PMIH0108 haptics device node
.../bindings/input/qcom,spmi-haptics.yaml | 119 +++
.../devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 +
arch/arm64/boot/dts/qcom/kaanapali-mtp.dts | 7 +
arch/arm64/boot/dts/qcom/kaanapali-qrd.dts | 7 +
arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi | 9 +
arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +
arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 7 +
arch/arm64/boot/dts/qcom/sm8750-qrd.dts | 7 +
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/qcom-spmi-haptics.c | 831 +++++++++++++++++++++
11 files changed, 1012 insertions(+)
---
base-commit: 66725039f7090afe14c31bd259e2059a68f04023
change-id: 20260616-qcom-spmi-haptics-3cc97e7b232e
Best regards,
--
Fenglin Wu <fenglin.wu@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics 2026-06-16 10:08 [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC Fenglin Wu @ 2026-06-16 10:08 ` Fenglin Wu 2026-06-16 10:11 ` Konrad Dybcio 2026-06-16 10:18 ` sashiko-bot 2026-06-16 10:08 ` [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device Fenglin Wu ` (2 subsequent siblings) 3 siblings, 2 replies; 12+ messages in thread From: Fenglin Wu @ 2026-06-16 10:08 UTC (permalink / raw) To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel, Fenglin Wu Add binding document for the haptics module inside Qualcomm PMIH010X. Assisted-by: Claude:claude-4-6-sonnet Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> --- .../bindings/input/qcom,spmi-haptics.yaml | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml new file mode 100644 index 000000000000..0e26d68563dc --- /dev/null +++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/qcom,spmi-haptics.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Haptics device inside Qualcomm Technologies, Inc. PMIH010X + +maintainers: + - Fenglin Wu <fenglin.wu@oss.qualcomm.com> + +description: | + The Qualcomm PMIH010X PMIC integrates a haptics module (HAP530_HV) that + drives an LRA (Linear Resonant Actuator) with output voltage up to 10 V. + Two play modes are supported: + + DIRECT_PLAY: The hardware outputs sinusoidal waveforms whose period is + defined by lra-period-us and whose peak voltage is defined by vmax-mv. + The driving amplitude can be scaled in the range [0, 255] via a single + register byte. Hardware-based LRA auto-resonance tracking is enabled by + default in this mode, allowing the haptics engine to follow the actual + resonant frequency of the LRA and update the driving period accordingly + to achieve stronger vibration magnitude. + + FIFO streaming: The hardware can play an arbitrary waveform composed of a + sequence of 8-bit samples at a configurable play rate. Samples are + pre-filled into the internal FIFO memory of the haptics module and + continuously replenished via the FIFO-empty IRQ until all samples have + been played. The following play rate values are accepted: + -- 0(T_LRA): each FIFO byte drives one full sinusoidal cycle with the + period defined in lra-period-us. + -- 1/2/3(T_LRA_DIV_2/4/8): each FIFO byte drives a half/quarter/eighth + sinusoidal cycle with the period defined in lra-period-us. + -- 8/9/10/11/12/13(8KHz/16KHz/24KHz/32KHz/44.1KHz/48KHz): the FIFO + data is treated as PCM samples and drives the output with an + arbitrarily shaped waveform. This mode is typically used to define + custom driving waveforms for specific vibration effects such as fast + attack, crisp brake, etc. + + In FIFO streaming mode, hardware-based LRA auto-resonance tracking is + disabled by default. Because this mode is intended to drive arbitrary + waveforms that may not follow the resonant frequency, autonomous hardware + resonance correction would interfere with the intended output. + + In the driver, FIFO streaming is implemented using an FF_PERIODIC effect + with an FF_CUSTOM waveform. The expected custom data layout is: + custom_data[0] = play rate code (see qcom,wf-play-rate values below) + custom_data[1] = vmax in mV; 0 = use device default (qcom,vmax-mv) + custom_data[2..] = signed 8-bit PCM samples (at least one required) + +properties: + compatible: + const: qcom,pmih010x-haptics + + reg: + items: + - description: HAP_CFG module base address + - description: HAP_PTN module base address + + reg-names: + items: + - const: hap-cfg + - const: hap-ptn + + interrupts: + maxItems: 1 + + interrupt-names: + items: + - const: fifo-empty + + qcom,vmax-mv: + description: + Maximum allowed output driving voltage in millivolts, rounded to the + nearest 50 mV step. This is the peak driving voltage in DIRECT_PLAY mode + which outputs sinusoidal waveforms. The value should be equal to the square + root of 2 times the Vrms voltage of the LRA. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 50 + maximum: 10000 + + qcom,lra-period-us: + description: + LRA actuator initial resonance period in microseconds + (1,000,000 / resonant_freq_hz). Used to configure T_LRA-based play + rates and the auto-resonance zero-crossing window. + minimum: 5 + maximum: 20475 + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - qcom,vmax-mv + - qcom,lra-period-us + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + haptics@f000 { + compatible = "qcom,pmih010x-haptics"; + reg = <0xf000>, <0xf100>; + reg-names = "hap-cfg", "hap-ptn"; + interrupts = <0x7 0xf0 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "fifo-empty"; + + qcom,vmax-mv = <1300>; + qcom,lra-period-us = <5880>; + }; + }; -- 2.43.0 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics 2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu @ 2026-06-16 10:11 ` Konrad Dybcio 2026-06-16 10:18 ` sashiko-bot 1 sibling, 0 replies; 12+ messages in thread From: Konrad Dybcio @ 2026-06-16 10:11 UTC (permalink / raw) To: Fenglin Wu, linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel On 6/16/26 12:08 PM, Fenglin Wu wrote: > Add binding document for the haptics module inside Qualcomm PMIH010X. > > Assisted-by: Claude:claude-4-6-sonnet > Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> > --- [...] > + qcom,vmax-mv: > + description: > + Maximum allowed output driving voltage in millivolts, rounded to the > + nearest 50 mV step. This is the peak driving voltage in DIRECT_PLAY mode > + which outputs sinusoidal waveforms. The value should be equal to the square > + root of 2 times the Vrms voltage of the LRA. > + $ref: /schemas/types.yaml#/definitions/uint32 > + minimum: 50 > + maximum: 10000 multipleOf: 50? Konrad ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics 2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu 2026-06-16 10:11 ` Konrad Dybcio @ 2026-06-16 10:18 ` sashiko-bot 1 sibling, 0 replies; 12+ messages in thread From: sashiko-bot @ 2026-06-16 10:18 UTC (permalink / raw) To: Fenglin Wu; +Cc: conor+dt, robh, dmitry.torokhov, linux-input, devicetree Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] The device tree binding description contains Linux-specific driver implementation details, violating the OS-agnostic principle. - [Low] Stale reference to an undefined property 'qcom,wf-play-rate' in the binding description. -- commit 24bf9f2257f4052d27a53163621c8f5173fe1da8 Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com> dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Add binding document for the haptics module inside Qualcomm PMIH010X. > diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml > new file mode 100644 > index 0000000000000..0e26d68563dcd > --- /dev/null > +++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml [ ... ] > + In FIFO streaming mode, hardware-based LRA auto-resonance tracking is > + disabled by default. Because this mode is intended to drive arbitrary > + waveforms that may not follow the resonant frequency, autonomous hardware > + resonance correction would interfere with the intended output. > + > + In the driver, FIFO streaming is implemented using an FF_PERIODIC effect > + with an FF_CUSTOM waveform. The expected custom data layout is: [Severity: Low] Are these Linux-specific driver implementation details, such as FF_PERIODIC and FF_CUSTOM, appropriate for this binding document? Device tree bindings are typically expected to be strictly OS-agnostic hardware descriptions. Should the documentation for the driver's ioctl payload and custom_data memory layout be moved to the Linux driver documentation instead? > + custom_data[0] = play rate code (see qcom,wf-play-rate values below) [Severity: Low] Is there a missing property definition for qcom,wf-play-rate? This line references qcom,wf-play-rate values, but the property doesn't appear to be defined anywhere in the schema's properties section. Does this need to be added, or is this a stale reference that should be removed? > + custom_data[1] = vmax in mV; 0 = use device default (qcom,vmax-mv) > + custom_data[2..] = signed 8-bit PCM samples (at least one required) > + > +properties: -- Sashiko AI review · https://sashiko.dev/#/patchset/20260616-qcom-spmi-haptics-v1-0-d24e422de6b4@oss.qualcomm.com?part=1 ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device 2026-06-16 10:08 [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC Fenglin Wu 2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu @ 2026-06-16 10:08 ` Fenglin Wu 2026-06-16 10:17 ` sashiko-bot 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu 2026-06-16 10:08 ` [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node Fenglin Wu 3 siblings, 1 reply; 12+ messages in thread From: Fenglin Wu @ 2026-06-16 10:08 UTC (permalink / raw) To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel, Fenglin Wu Some of the Qualcomm SPMI PMIC has haptics device in it, add it in the device list. Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> --- Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e5..773f4cba5935 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -165,6 +165,10 @@ patternProperties: type: object $ref: /schemas/pinctrl/qcom,pmic-gpio.yaml# + "^haptics@[0-9a-f]+$": + type: object + $ref: /schemas/input/qcom,spmi-haptics.yaml# + "^led-controller@[0-9a-f]+$": type: object $ref: /schemas/leds/qcom,spmi-flash-led.yaml# -- 2.43.0 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device 2026-06-16 10:08 ` [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device Fenglin Wu @ 2026-06-16 10:17 ` sashiko-bot 0 siblings, 0 replies; 12+ messages in thread From: sashiko-bot @ 2026-06-16 10:17 UTC (permalink / raw) To: Fenglin Wu; +Cc: conor+dt, robh, linux-input, dmitry.torokhov, devicetree Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Use of a wildcard ('x') in the compatible string. -- commit 24bf9f2257f4052d27a53163621c8f5173fe1da8 Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com> dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Add binding document for the haptics module inside Qualcomm PMIH010X. > diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml > new file mode 100644 > index 0000000000000..0e26d68563dcd > --- /dev/null > +++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml > @@ -0,0 +1,119 @@ > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause > +%YAML 1.2 > +--- [ ... ] > +properties: > + compatible: > + const: qcom,pmih010x-haptics [Severity: Low] Does this introduce a regression by using a wildcard character ('x') in the compatible string? Device tree specifications generally forbid wildcards in compatible strings. Should the specific model name of the first chip in the family (e.g., "qcom,pmih0108-haptics") be used instead of a wildcard? [ ... ] > +examples: > + - | > + #include <dt-bindings/interrupt-controller/irq.h> > + > + pmic { > + #address-cells = <1>; > + #size-cells = <0>; > + > + haptics@f000 { > + compatible = "qcom,pmih010x-haptics"; [Severity: Low] Does this example share the same regression with the wildcard compatible string? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260616-qcom-spmi-haptics-v1-0-d24e422de6b4@oss.qualcomm.com?part=2 ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver 2026-06-16 10:08 [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC Fenglin Wu 2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu 2026-06-16 10:08 ` [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device Fenglin Wu @ 2026-06-16 10:08 ` Fenglin Wu 2026-06-16 10:23 ` sashiko-bot ` (2 more replies) 2026-06-16 10:08 ` [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node Fenglin Wu 3 siblings, 3 replies; 12+ messages in thread From: Fenglin Wu @ 2026-06-16 10:08 UTC (permalink / raw) To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel, Fenglin Wu Add an initial driver for the Qualcomm PMIH010x PMIC haptics module, named as HAP530_HV. This module supports several play modes, including DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing and hardware data handling logic. Currently, the driver provides support for two play modes using the input force-feedback framework: FF_CONSTANT effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM waveform for FIFO mode. Assisted-by: Claude:claude-4-6-sonnet Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> --- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 + drivers/input/misc/qcom-spmi-haptics.c | 831 +++++++++++++++++++++++++++++++++ 3 files changed, 843 insertions(+) diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 1f6c57dba030..eac939978ce4 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -236,6 +236,17 @@ config INPUT_PMIC8XXX_PWRKEY To compile this driver as a module, choose M here: the module will be called pmic8xxx-pwrkey. +config INPUT_QCOM_SPMI_HAPTICS + tristate "Qualcomm SPMI PMIC haptics support" + depends on INPUT && MFD_SPMI_PMIC + help + Say Y to enable support for the Qualcomm PMIH010X SPMI PMIC haptics + module. Supports DIRECT_PLAY, FIFO streaming play modes via the + Linux input force-feedback framework. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-haptics. + config INPUT_SPARCSPKR tristate "SPARC Speaker support" depends on PCI && SPARC64 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 2281d6803fce..c5c9aa139a11 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o +obj-$(CONFIG_INPUT_QCOM_SPMI_HAPTICS) += qcom-spmi-haptics.o obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c new file mode 100644 index 000000000000..75b3e338b54e --- /dev/null +++ b/drivers/input/misc/qcom-spmi-haptics.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/workqueue.h> + +/* HAP_CFG register offsets, bit fields, value constants */ +#define HAP_CFG_INT_RT_STS_REG 0x10 +#define FIFO_EMPTY_BIT BIT(1) +#define HAP_CFG_EN_CTL_REG 0x46 +#define HAPTICS_EN_BIT BIT(7) +#define HAP_CFG_VMAX_REG 0x48 +#define VMAX_STEP_MV 50 +#define VMAX_MV_MAX 10000 +#define HAP_CFG_SPMI_PLAY_REG 0x4C +#define PLAY_EN_BIT BIT(7) +#define PAT_SRC_MASK GENMASK(2, 0) +#define PAT_SRC_FIFO 0 +#define PAT_SRC_DIRECT_PLAY 1 +#define HAP_CFG_TLRA_OL_HIGH_REG 0x5C +#define TLRA_OL_MSB_MASK GENMASK(3, 0) +#define TLRA_STEP_US 5 +#define HAP_CFG_TLRA_OL_LOW_REG 0x5D +#define HAP_CFG_DRV_DUTY_CFG_REG 0x60 +#define ADT_DRV_DUTY_EN_BIT BIT(7) +#define ADT_BRK_DUTY_EN_BIT BIT(6) +#define DRV_DUTY_MASK GENMASK(5, 3) +#define AUTORES_DRV_DUTY_62P5 2 +#define BRK_DUTY_MASK GENMASK(2, 0) +#define AUTORES_BRK_DUTY_62P5 5 +#define HAP_CFG_ZX_WIND_CFG_REG 0x62 +#define ZX_DEBOUNCE_MASK GENMASK(6, 4) +#define AUTORES_ZX_DEBOUNCE 3 +#define ZX_WIN_HEIGHT_MASK GENMASK(2, 0) +#define AUTORES_ZX_WIN_HEIGHT 2 +#define HAP_CFG_AUTORES_CFG_REG 0x63 +#define AUTORES_EN_BIT BIT(7) +#define AUTORES_EN_DLY_MASK GENMASK(6, 2) +#define AUTORES_EN_DLY_CYCLES 10 +#define AUTORES_ERR_WIN_MASK GENMASK(1, 0) +#define AUTORES_ERR_WIN_25PCT 1 +#define HAP_CFG_FAULT_CLR_REG 0x66 +#define ZX_TO_FAULT_CLR_BIT BIT(4) +#define SC_CLR_BIT BIT(2) +#define AUTO_RES_ERR_CLR_BIT BIT(1) +#define HPWR_RDY_FAULT_CLR_BIT BIT(0) +#define FAULT_CLR_ALL (ZX_TO_FAULT_CLR_BIT | SC_CLR_BIT | \ + AUTO_RES_ERR_CLR_BIT | HPWR_RDY_FAULT_CLR_BIT) +#define HAP_CFG_RAMP_DN_CFG2_REG 0x86 +#define AUTORES_PRE_HIZ_DLY_10US 1 + +/* HAP_PTN register offsets, bit fields, value constants */ +#define HAP_PTN_REVISION2_REG 0x01 +#define HAP_PTN_FIFO_DIN_0_REG 0x20 +#define HAP_PTN_FIFO_PLAY_RATE_REG 0x24 +#define FIFO_PLAY_RATE_MASK GENMASK(3, 0) +#define HAP_PTN_DIRECT_PLAY_REG 0x26 +#define HAP_PTN_FIFO_EMPTY_CFG_REG 0x2A +#define FIFO_THRESH_LSB 64 +#define HAP_PTN_FIFO_DIN_1B_REG 0x2C +#define HAP_PTN_MEM_OP_ACCESS_REG 0x2D +#define MEM_FLUSH_RELOAD_BIT BIT(0) +#define HAP_PTN_MMAP_FIFO_REG 0xA0 +#define MMAP_FIFO_EXIST_BIT BIT(7) +#define MMAP_FIFO_LEN_MASK GENMASK(6, 0) +#define HAP_PTN_PATX_PLAY_CFG_REG 0xA2 + +#define HAP530_MEM_TOTAL_BYTES 8192 +#define FIFO_EMPTY_THRESH 280 +#define FIFO_INIT_FILL 320 + +#define HAPTICS_AUTOSUSPEND_MS 1000 + +/* + * FF_CUSTOM data layout (custom_data[] of type s16): + * [0] = play rate (PLAY_RATE_*) + * [1] = vmax in mV (0 = use device default from qcom,vmax-mv) + * [2..N-1] = signed 8-bit PCM samples packed one per s16 (lower byte used) + */ +#define CUSTOM_DATA_RATE_IDX 0 +#define CUSTOM_DATA_VMAX_IDX 1 +#define CUSTOM_DATA_SAMPLE_START 2 + +#define HAPTICS_MAX_EFFECTS 8 + +enum qcom_haptics_mode { + HAPTICS_DIRECT_PLAY, + HAPTICS_FIFO, +}; + +enum qcom_haptics_play_rate { + PLAY_RATE_T_LRA = 0, + PLAY_RATE_T_LRA_DIV_2 = 1, + PLAY_RATE_T_LRA_DIV_4 = 2, + PLAY_RATE_T_LRA_DIV_8 = 3, + /* 4–7 are reserved */ + PLAY_RATE_F_8KHZ = 8, + PLAY_RATE_F_16KHZ = 9, + PLAY_RATE_F_24KHZ = 10, + PLAY_RATE_F_32KHZ = 11, + PLAY_RATE_F_44P1KHZ = 12, + PLAY_RATE_F_48KHZ = 13, + PLAY_RATE_MAX = PLAY_RATE_F_48KHZ, +}; + +struct qcom_haptics_effect { + enum qcom_haptics_mode mode; + enum qcom_haptics_play_rate play_rate; + u32 vmax_mv; + s8 *fifo_data; + u32 data_len; +}; + +/** + * struct qcom_haptics + * @dev: underlying SPMI device + * @regmap: regmap for SPMI register access + * @input: input device exposing the FF interface + * @cfg_base: base address of the CFG peripheral + * @ptn_base: base address of the PTN peripheral + * @t_lra_us: LRA resonance period in microseconds + * @vmax_mv: maximum actuator drive voltage in millivolts + * @fifo_len: programmed HW FIFO depth in bytes + * @gain: playback gain scaler + * @play_work: deferred work item that starts or stops playback + * @play_lock: mutex lock to serialize playbacks + * @cur_effect_id: index into @effects[] identifying the active effect + * @fifo_empty_irq: IRQ number for the FIFO-empty interrupt + * @play_request: true when a playback is requested + * @pm_ref_held: true while a pm_runtime_get is held + * @irq_enabled: true if fifo_empty_irq is enabled + * @fifo_lock: spinlock protecting the FIFO streaming data + * @fifo_data: pointer of the data buffer for FIFO streaming + * @data_len: length of the data buffer for current effect + * @data_written: number of samples written to the hardware FIFO + * @data_done: flag to indicate that all samples have been written + * @effects: table of the effects + */ +struct qcom_haptics { + struct device *dev; + struct regmap *regmap; + struct input_dev *input; + + u32 cfg_base; + u32 ptn_base; + u32 t_lra_us; + u32 vmax_mv; + u32 fifo_len; + u16 gain; + + struct work_struct play_work; + struct mutex play_lock; /* mutex used to serialize playbacks */ + int cur_effect_id; + int fifo_empty_irq; + bool play_request; + bool pm_ref_held; + bool irq_enabled; + + spinlock_t fifo_lock; /* protect the FIFO data during play */ + const s8 *fifo_data; + u32 data_len; + u32 data_written; + bool data_done; + + struct qcom_haptics_effect effects[HAPTICS_MAX_EFFECTS]; +}; + +static int cfg_write(struct qcom_haptics *h, u32 off, u32 val) +{ + return regmap_write(h->regmap, h->cfg_base + off, val); +} + +static int cfg_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val) +{ + return regmap_update_bits(h->regmap, h->cfg_base + off, mask, val); +} + +static int ptn_write(struct qcom_haptics *h, u32 off, u32 val) +{ + return regmap_write(h->regmap, h->ptn_base + off, val); +} + +static int ptn_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val) +{ + return regmap_update_bits(h->regmap, h->ptn_base + off, mask, val); +} + +static int ptn_bulk_write(struct qcom_haptics *h, u32 off, + const void *buf, size_t count) +{ + return regmap_bulk_write(h->regmap, h->ptn_base + off, buf, count); +} + +static int haptics_clear_faults(struct qcom_haptics *h) +{ + return cfg_write(h, HAP_CFG_FAULT_CLR_REG, FAULT_CLR_ALL); +} + +static int haptics_set_vmax(struct qcom_haptics *h, u32 vmax_mv) +{ + return cfg_write(h, HAP_CFG_VMAX_REG, vmax_mv / VMAX_STEP_MV); +} + +static int haptics_config_lra_period(struct qcom_haptics *h) +{ + u32 tmp = h->t_lra_us / TLRA_STEP_US; + int ret; + + ret = cfg_write(h, HAP_CFG_TLRA_OL_HIGH_REG, (tmp >> 8) & TLRA_OL_MSB_MASK); + if (ret) + return ret; + + return cfg_write(h, HAP_CFG_TLRA_OL_LOW_REG, tmp & 0xFF); +} + +static int haptics_enable_module(struct qcom_haptics *h, bool enable) +{ + return cfg_update_bits(h, HAP_CFG_EN_CTL_REG, HAPTICS_EN_BIT, + enable ? HAPTICS_EN_BIT : 0); +} + +static int haptics_configure_autores(struct qcom_haptics *h) +{ + int ret; + + /* AUTORES_CFG: enable, 10-cycle delay, 25% error window */ + ret = cfg_write(h, HAP_CFG_AUTORES_CFG_REG, + AUTORES_EN_BIT | + FIELD_PREP(AUTORES_EN_DLY_MASK, AUTORES_EN_DLY_CYCLES) | + FIELD_PREP(AUTORES_ERR_WIN_MASK, AUTORES_ERR_WIN_25PCT)); + if (ret) + return ret; + + /* DRV_DUTY: adaptive drive/brake duty cycles at 62.5% */ + ret = cfg_write(h, HAP_CFG_DRV_DUTY_CFG_REG, + ADT_DRV_DUTY_EN_BIT | ADT_BRK_DUTY_EN_BIT | + FIELD_PREP(DRV_DUTY_MASK, AUTORES_DRV_DUTY_62P5) | + FIELD_PREP(BRK_DUTY_MASK, AUTORES_BRK_DUTY_62P5)); + if (ret) + return ret; + + /* Pre-HIZ delay: 10 µs */ + ret = cfg_write(h, HAP_CFG_RAMP_DN_CFG2_REG, AUTORES_PRE_HIZ_DLY_10US); + if (ret) + return ret; + + /* Zero-cross window: debounce 3, no hysteresis, height 2 */ + return cfg_write(h, HAP_CFG_ZX_WIND_CFG_REG, + FIELD_PREP(ZX_DEBOUNCE_MASK, AUTORES_ZX_DEBOUNCE) | + FIELD_PREP(ZX_WIN_HEIGHT_MASK, AUTORES_ZX_WIN_HEIGHT)); +} + +static int haptics_write_fifo_chunk(struct qcom_haptics *h, + const s8 *data, u32 len) +{ + u32 i, bulk_len = ALIGN_DOWN(len, 4); + int ret; + + for (i = 0; i < bulk_len; i += 4) { + ret = ptn_bulk_write(h, HAP_PTN_FIFO_DIN_0_REG, &data[i], 4); + if (ret) + return ret; + } + + for (; i < len; i++) { + ret = ptn_write(h, HAP_PTN_FIFO_DIN_1B_REG, (u8)data[i]); + if (ret) + return ret; + } + + return 0; +} + +/* + * Configure the hardware FIFO memory boundary. + * FIFO occupies addresses [0, fifo_len). + */ +static int haptics_configure_fifo_mmap(struct qcom_haptics *h) +{ + u32 fifo_len, fifo_units; + + /* Config all memory space for FIFO usage for now */ + fifo_len = HAP530_MEM_TOTAL_BYTES; + fifo_len = ALIGN_DOWN(fifo_len, 64); + fifo_units = fifo_len / 64; + h->fifo_len = fifo_len; + + return ptn_write(h, HAP_PTN_MMAP_FIFO_REG, + MMAP_FIFO_EXIST_BIT | + FIELD_PREP(MMAP_FIFO_LEN_MASK, fifo_units - 1)); +} + +static u32 haptics_gain_scaled_vmax(struct qcom_haptics *h, u32 vmax_mv) +{ + u32 v = (u32)((u64)vmax_mv * h->gain / 0xFFFF); + + return max_t(u32, v, VMAX_STEP_MV); +} + +static void haptics_fifo_irq_enable(struct qcom_haptics *h, bool enable) +{ + if (h->irq_enabled == enable) + return; + + if (enable) + enable_irq(h->fifo_empty_irq); + else + disable_irq_nosync(h->fifo_empty_irq); + + h->irq_enabled = enable; +} + +/* + * Must be called with play_lock held. + * Clears PLAY_EN and resets any FIFO-specific state. + */ +static void haptics_stop_locked(struct qcom_haptics *h) +{ + unsigned long flags; + + cfg_write(h, HAP_CFG_SPMI_PLAY_REG, 0); + + if (h->effects[h->cur_effect_id].mode == HAPTICS_FIFO) { + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0); + haptics_fifo_irq_enable(h, false); + spin_lock_irqsave(&h->fifo_lock, flags); + h->fifo_data = NULL; + spin_unlock_irqrestore(&h->fifo_lock, flags); + } +} + +static int haptics_start_direct_play(struct qcom_haptics *h, int effect_id) +{ + struct ff_effect *ffe = &h->input->ff->effects[effect_id]; + u8 amplitude = (u8)((u32)ffe->u.constant.level * 255 / 0x7FFF); + int ret; + + ret = haptics_clear_faults(h); + if (ret) + return ret; + + /* Enable auto-resonance for DIRECT_PLAY mode */ + ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG, + AUTORES_EN_BIT, AUTORES_EN_BIT); + if (ret) + return ret; + + ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, h->vmax_mv)); + if (ret) + return ret; + + ret = ptn_write(h, HAP_PTN_DIRECT_PLAY_REG, amplitude); + if (ret) + return ret; + + return cfg_write(h, HAP_CFG_SPMI_PLAY_REG, + PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_DIRECT_PLAY)); +} + +static int haptics_start_fifo(struct qcom_haptics *h, int effect_id) +{ + struct qcom_haptics_effect *eff = &h->effects[effect_id]; + u32 vmax = eff->vmax_mv ? eff->vmax_mv : h->vmax_mv; + unsigned long flags; + u32 init_len; + int ret; + + ret = haptics_clear_faults(h); + if (ret) + return ret; + + /* Disable auto-resonance for FIFO mode */ + ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG, AUTORES_EN_BIT, 0); + if (ret) + return ret; + + ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, vmax)); + if (ret) + return ret; + + ret = ptn_update_bits(h, HAP_PTN_FIFO_PLAY_RATE_REG, + FIFO_PLAY_RATE_MASK, + FIELD_PREP(FIFO_PLAY_RATE_MASK, eff->play_rate)); + if (ret) + return ret; + + /* Flush FIFO before loading new data */ + ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, MEM_FLUSH_RELOAD_BIT); + if (ret) + return ret; + ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, 0); + if (ret) + return ret; + + /* Write the initial chunk and initialise streaming state */ + init_len = min_t(u32, eff->data_len, FIFO_INIT_FILL); + ret = haptics_write_fifo_chunk(h, eff->fifo_data, init_len); + if (ret) + return ret; + + spin_lock_irqsave(&h->fifo_lock, flags); + h->fifo_data = eff->fifo_data; + h->data_len = eff->data_len; + h->data_written = init_len; + h->data_done = (init_len >= eff->data_len); + spin_unlock_irqrestore(&h->fifo_lock, flags); + + /* + * Set empty threshold. When threshold > 0 the hardware fires the + * FIFO-empty interrupt when occupancy drops below the threshold, + * allowing the driver to refill. A threshold of 0 disables the IRQ. + */ + ret = ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, h->data_done ? 0 : + FIFO_EMPTY_THRESH / FIFO_THRESH_LSB); + if (ret) + return ret; + if (!h->data_done) + haptics_fifo_irq_enable(h, true); + + return cfg_write(h, HAP_CFG_SPMI_PLAY_REG, + PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_FIFO)); +} + +/* + * Threaded IRQ handler for the FIFO-empty interrupt. + * + * While a FIFO play is in progress the hardware fires this interrupt when + * the number of samples in the FIFO drops below the programmed threshold. + * The handler refills the FIFO from the effect's data buffer. When all + * samples have been written the threshold is set to zero, which suppresses + * further interrupts; the hardware drains the remaining samples naturally + * and the play work handler stops the engine on the next invocation. + */ +static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id) +{ + struct qcom_haptics *h = dev_id; + unsigned long flags; + u32 sts, to_write; + int ret; + + ret = regmap_read(h->regmap, + h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts); + if (ret || !(sts & FIFO_EMPTY_BIT)) + return IRQ_HANDLED; + + spin_lock_irqsave(&h->fifo_lock, flags); + + if (!h->fifo_data) { + spin_unlock_irqrestore(&h->fifo_lock, flags); + return IRQ_HANDLED; + } + + if (h->data_done) { + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0); + h->fifo_data = NULL; + h->play_request = false; + schedule_work(&h->play_work); + spin_unlock_irqrestore(&h->fifo_lock, flags); + return IRQ_HANDLED; + } + + /* Refill: write the next chunk, conservatively sized to the threshold */ + to_write = min_t(u32, h->data_len - h->data_written, + h->fifo_len - FIFO_EMPTY_THRESH); + haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write); + h->data_written += to_write; + + if (h->data_written >= h->data_len) { + /* Last chunk enqueued; disable threshold to stop further IRQs */ + h->data_done = true; + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0); + } + + spin_unlock_irqrestore(&h->fifo_lock, flags); + return IRQ_HANDLED; +} + +static void haptics_play_work(struct work_struct *work) +{ + struct qcom_haptics *h = container_of(work, struct qcom_haptics, play_work); + int id, ret; + + mutex_lock(&h->play_lock); + + if (!h->play_request) { + haptics_stop_locked(h); + if (h->pm_ref_held) { + pm_runtime_mark_last_busy(h->dev); + pm_runtime_put_autosuspend(h->dev); + h->pm_ref_held = false; + } + goto unlock; + } + + ret = pm_runtime_resume_and_get(h->dev); + if (ret < 0) { + dev_err(h->dev, "failed to resume device: %d\n", ret); + goto unlock; + } + h->pm_ref_held = true; + + id = h->cur_effect_id; + + switch (h->effects[id].mode) { + case HAPTICS_DIRECT_PLAY: + ret = haptics_start_direct_play(h, id); + break; + case HAPTICS_FIFO: + ret = haptics_start_fifo(h, id); + break; + default: + ret = -EINVAL; + } + + if (ret) { + dev_err(h->dev, "failed to start effect %d: %d\n", id, ret); + pm_runtime_put_autosuspend(h->dev); + h->pm_ref_held = false; + } + +unlock: + mutex_unlock(&h->play_lock); +} + +static int haptics_upload_effect(struct input_dev *dev, + struct ff_effect *effect, + struct ff_effect *old) +{ + struct qcom_haptics *h = input_get_drvdata(dev); + struct qcom_haptics_effect *priv; + int id = effect->id; + s16 *buf; + u32 i; + + if (id < 0 || id >= HAPTICS_MAX_EFFECTS) + return -EINVAL; + + priv = &h->effects[id]; + + switch (effect->type) { + case FF_CONSTANT: + kfree(priv->fifo_data); + priv->fifo_data = NULL; + priv->data_len = 0; + priv->mode = HAPTICS_DIRECT_PLAY; + return 0; + + case FF_PERIODIC: + if (effect->u.periodic.waveform != FF_CUSTOM) + return -EINVAL; + /* + * Minimum 3 elements: play-rate code + vmax + at least one sample. + * No upper bound: the FIFO is refilled continuously from the IRQ + * handler, so any length of PCM data is supported. + */ + if (effect->u.periodic.custom_len < 3) + return -EINVAL; + + buf = memdup_array_user(effect->u.periodic.custom_data, + effect->u.periodic.custom_len, + sizeof(s16)); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (buf[CUSTOM_DATA_RATE_IDX] > PLAY_RATE_MAX) { + kfree(buf); + return -EINVAL; + } + + priv->play_rate = (u8)buf[CUSTOM_DATA_RATE_IDX]; + priv->vmax_mv = (u32)clamp_val(buf[CUSTOM_DATA_VMAX_IDX], 0, VMAX_MV_MAX); + priv->data_len = effect->u.periodic.custom_len - CUSTOM_DATA_SAMPLE_START; + + kfree(priv->fifo_data); + priv->fifo_data = kmalloc(priv->data_len, GFP_KERNEL); + if (!priv->fifo_data) { + kfree(buf); + return -ENOMEM; + } + + /* Pack: one s8 sample per s16 slot (lower byte) */ + for (i = 0; i < priv->data_len; i++) + priv->fifo_data[i] = (s8)buf[CUSTOM_DATA_SAMPLE_START + i]; + + kfree(buf); + priv->mode = HAPTICS_FIFO; + return 0; + + default: + return -EINVAL; + } +} + +static int haptics_playback(struct input_dev *dev, int effect_id, int val) +{ + struct qcom_haptics *h = input_get_drvdata(dev); + + h->cur_effect_id = effect_id; + h->play_request = (val > 0); + schedule_work(&h->play_work); + return 0; +} + +static int haptics_erase(struct input_dev *dev, int effect_id) +{ + struct qcom_haptics *h = input_get_drvdata(dev); + struct qcom_haptics_effect *priv = &h->effects[effect_id]; + + kfree(priv->fifo_data); + priv->fifo_data = NULL; + priv->data_len = 0; + return 0; +} + +static void haptics_set_gain(struct input_dev *dev, u16 gain) +{ + struct qcom_haptics *h = input_get_drvdata(dev); + + h->gain = gain; +} + +static int qcom_haptics_probe(struct platform_device *pdev) +{ + struct qcom_haptics *h; + struct input_dev *input; + struct ff_device *ff; + u32 regs[2]; + int ret, irq; + + h = devm_kzalloc(&pdev->dev, sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->dev = &pdev->dev; + + h->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!h->regmap) + return dev_err_probe(&pdev->dev, -ENODEV, + "no regmap from parent\n"); + + ret = device_property_read_u32_array(&pdev->dev, "reg", regs, + ARRAY_SIZE(regs)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to read 'reg' property\n"); + + h->cfg_base = regs[0]; + h->ptn_base = regs[1]; + + ret = of_property_read_u32(h->dev->of_node, "qcom,lra-period-us", + &h->t_lra_us); + if (ret) + return dev_err_probe(h->dev, ret, "missing qcom,lra-period-us\n"); + + ret = of_property_read_u32(h->dev->of_node, "qcom,vmax-mv", &h->vmax_mv); + if (ret) + return dev_err_probe(h->dev, ret, "missing qcom,vmax-mv\n"); + + h->vmax_mv = clamp(h->vmax_mv, (u32)VMAX_STEP_MV, (u32)VMAX_MV_MAX); + + ret = haptics_config_lra_period(h); + if (ret) + return ret; + + ret = haptics_configure_autores(h); + if (ret) + return ret; + + ret = haptics_set_vmax(h, h->vmax_mv); + if (ret) + return ret; + + ret = haptics_configure_fifo_mmap(h); + if (ret) + return ret; + + mutex_init(&h->play_lock); + spin_lock_init(&h->fifo_lock); + INIT_WORK(&h->play_work, haptics_play_work); + h->gain = 0xFFFF; + + irq = platform_get_irq_byname(pdev, "fifo-empty"); + if (irq < 0) + return dev_err_probe(&pdev->dev, irq, + "failed to get fifo-empty IRQ\n"); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + haptics_fifo_empty_irq, + IRQF_ONESHOT, + "qcom-haptics-fifo-empty", h); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to request fifo-empty IRQ\n"); + + h->fifo_empty_irq = irq; + disable_irq_nosync(irq); + + input = devm_input_allocate_device(&pdev->dev); + if (!input) + return -ENOMEM; + + input->name = "qcom-spmi-haptics"; + input_set_drvdata(input, h); + h->input = input; + + input_set_capability(input, EV_FF, FF_CONSTANT); + input_set_capability(input, EV_FF, FF_PERIODIC); + input_set_capability(input, EV_FF, FF_CUSTOM); + input_set_capability(input, EV_FF, FF_GAIN); + + ret = input_ff_create(input, HAPTICS_MAX_EFFECTS); + if (ret) + return ret; + + ff = input->ff; + ff->upload = haptics_upload_effect; + ff->playback = haptics_playback; + ff->erase = haptics_erase; + ff->set_gain = haptics_set_gain; + + ret = input_register_device(input); + if (ret) { + input_ff_destroy(input); + return dev_err_probe(&pdev->dev, ret, + "failed to register input device\n"); + } + + platform_set_drvdata(pdev, h); + + /* + * Grab a reference on behalf of probe (usage_count → 1), mark the + * device active, then enable runtime PM. + */ + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, HAPTICS_AUTOSUSPEND_MS); + devm_pm_runtime_set_active_enabled(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); + + return 0; +} + +static void qcom_haptics_remove(struct platform_device *pdev) +{ + struct qcom_haptics *h = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + cancel_work_sync(&h->play_work); + mutex_lock(&h->play_lock); + haptics_stop_locked(h); + haptics_enable_module(h, false); + mutex_unlock(&h->play_lock); + + input_unregister_device(h->input); +} + +static int qcom_haptics_runtime_suspend(struct device *dev) +{ + struct qcom_haptics *h = dev_get_drvdata(dev); + + return haptics_enable_module(h, false); +} + +static int qcom_haptics_runtime_resume(struct device *dev) +{ + struct qcom_haptics *h = dev_get_drvdata(dev); + + return haptics_enable_module(h, true); +} + +static int qcom_haptics_suspend(struct device *dev) +{ + struct qcom_haptics *h = dev_get_drvdata(dev); + + cancel_work_sync(&h->play_work); + mutex_lock(&h->play_lock); + haptics_stop_locked(h); + if (h->pm_ref_held) { + pm_runtime_put_noidle(dev); + h->pm_ref_held = false; + } + mutex_unlock(&h->play_lock); + return pm_runtime_force_suspend(dev); +} + +static int qcom_haptics_resume(struct device *dev) +{ + return pm_runtime_force_resume(dev); +} + +static const struct dev_pm_ops qcom_haptics_pm_ops = { + SYSTEM_SLEEP_PM_OPS(qcom_haptics_suspend, qcom_haptics_resume) + RUNTIME_PM_OPS(qcom_haptics_runtime_suspend, qcom_haptics_runtime_resume, + NULL) +}; + +static const struct of_device_id qcom_haptics_of_match[] = { + { .compatible = "qcom,pmih010x-haptics" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_haptics_of_match); + +static struct platform_driver qcom_haptics_driver = { + .probe = qcom_haptics_probe, + .remove = qcom_haptics_remove, + .driver = { + .name = "qcom-spmi-haptics", + .of_match_table = qcom_haptics_of_match, + .pm = pm_ptr(&qcom_haptics_pm_ops), + }, +}; +module_platform_driver(qcom_haptics_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI PMIC Haptics driver"); +MODULE_LICENSE("GPL"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu @ 2026-06-16 10:23 ` sashiko-bot 2026-06-16 10:25 ` Konrad Dybcio 2026-06-16 14:37 ` Julian Braha 2 siblings, 0 replies; 12+ messages in thread From: sashiko-bot @ 2026-06-16 10:23 UTC (permalink / raw) To: Fenglin Wu; +Cc: robh, linux-input, conor+dt, devicetree, dmitry.torokhov Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [High] Repeated playback requests for an actively playing effect leak runtime PM usage counters, permanently preventing the device from suspending. - [High] The threaded IRQ handler sleeps in atomic context by executing sleepable regmap operations while holding a spinlock. - [High] Incorrect teardown ordering in `remove()` and `suspend()` allows a work item to be re-queued and executed on freed memory (Use-After-Free). - [High] Erasing or updating a force feedback effect frees the effect's data buffer synchronously, causing a Use-After-Free in the IRQ handler. - [High] A failed memory allocation during effect upload leaves internal state corrupted, guaranteeing a NULL pointer dereference upon playback. - [High] A lockless race condition between the input playback callback and the FIFO empty IRQ handler can cause new playback requests to be silently dropped. - [High] A data race on `h->cur_effect_id` allows userspace to misdirect the stop sequence, leaving hardware IRQs enabled while dropping PM references. - [Medium] Dynamically allocated custom waveform buffers are permanently leaked when the device is unbound. - [Medium] Missing `abs()` on `s16` magnitude level causes massive arithmetic overflow for negative effects. - [Medium] Registering the input device before PM runtime is fully configured creates a race condition for early playback events. -- commit 84c087855458c41a5a109b1ffb5594db116b5170 Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com> input: misc: Add Qualcomm SPMI PMIC haptics driver Add an initial driver for the Qualcomm PMIH010x PMIC haptics module, named as HAP530_HV. This module supports several play modes, including DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing and hardware data handling logic. Currently, the driver provides support for two play modes using the input force-feedback framework: FF_CONSTANT effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM waveform for FIFO mode. > diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c > new file mode 100644 > index 0000000000000..75b3e338b54eb > --- /dev/null > +++ b/drivers/input/misc/qcom-spmi-haptics.c > @@ -0,0 +1,831 @@ [ ... ] > +static int haptics_start_direct_play(struct qcom_haptics *h, int effect_id) > +{ > + struct ff_effect *ffe = &h->input->ff->effects[effect_id]; > + u8 amplitude = (u8)((u32)ffe->u.constant.level * 255 / 0x7FFF); [Severity: Medium] Does this cast cause an arithmetic overflow for negative effect levels? The ffe->u.constant.level is an s16 which can be negative to represent opposite-direction effects. Casting a negative s16 directly to u32 sign-extends it, resulting in a large unsigned value. Multiplying this by 255 overflows the 32-bit boundary, producing a garbage amplitude for the hardware register. Consider taking the absolute value of the level before casting and scaling. [ ... ] > +static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id) > +{ > + struct qcom_haptics *h = dev_id; > + unsigned long flags; > + u32 sts, to_write; > + int ret; > + > + ret = regmap_read(h->regmap, > + h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts); > + if (ret || !(sts & FIFO_EMPTY_BIT)) > + return IRQ_HANDLED; > + > + spin_lock_irqsave(&h->fifo_lock, flags); [Severity: High] Is it safe to hold a spinlock while calling sleepable regmap operations? The haptics_write_fifo_chunk() function maps to regmap_bulk_write(). For an SPMI PMIC, the regmap operates over a slow bus and takes a mutex to sleep during transactions. Calling these sleepable functions while holding fifo_lock results in a scheduling while atomic bug. [ ... ] > + if (h->data_done) { > + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0); > + h->fifo_data = NULL; > + h->play_request = false; > + schedule_work(&h->play_work); [Severity: High] Can a lockless race condition here cause new playback requests to be silently dropped? When an effect completes, this sets play_request to false. Concurrently, if a new playback request arrives, haptics_playback() sets it to true. If the IRQ handler runs immediately after haptics_playback(), it overwrites the new true value with false. When haptics_play_work() executes, it would observe play_request as false, stop the hardware, and drop the newly queued playback request. > + spin_unlock_irqrestore(&h->fifo_lock, flags); > + return IRQ_HANDLED; > + } > + > + /* Refill: write the next chunk, conservatively sized to the threshold */ > + to_write = min_t(u32, h->data_len - h->data_written, > + h->fifo_len - FIFO_EMPTY_THRESH); > + haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write); [Severity: High] Is there a risk of a use-after-free here if an effect is erased while playing? The input framework allows effect upload or erase to occur concurrently with playback. In haptics_erase() and haptics_upload_effect(), the driver calls kfree(priv->fifo_data) synchronously. However, the stop request only schedules an asynchronous work item to stop playback. Because the hardware stop is deferred, the hardware may still be actively playing and asserting the FIFO-empty interrupt. The IRQ handler will then attempt to read from the freed fifo_data. [ ... ] > +static void haptics_play_work(struct work_struct *work) > +{ > + struct qcom_haptics *h = container_of(work, struct qcom_haptics, play_work); > + int id, ret; > + > + mutex_lock(&h->play_lock); > + > + if (!h->play_request) { > + haptics_stop_locked(h); > + if (h->pm_ref_held) { > + pm_runtime_mark_last_busy(h->dev); > + pm_runtime_put_autosuspend(h->dev); > + h->pm_ref_held = false; > + } > + goto unlock; > + } > + > + ret = pm_runtime_resume_and_get(h->dev); > + if (ret < 0) { > + dev_err(h->dev, "failed to resume device: %d\n", ret); > + goto unlock; > + } > + h->pm_ref_held = true; [Severity: High] Can this leak runtime PM usage counters? The pm_ref_held flag is set to true without checking if it is already true. If userspace issues multiple start events without interleaved stops (for example, to update parameters), the PM usage counter is incremented multiple times. When playback is eventually stopped, the single check drops only one reference via pm_runtime_put_autosuspend(), permanently leaking all other references and preventing the device from suspending. [ ... ] > +static int haptics_upload_effect(struct input_dev *dev, > + struct ff_effect *effect, > + struct ff_effect *old) > +{ [ ... ] > + priv->play_rate = (u8)buf[CUSTOM_DATA_RATE_IDX]; > + priv->vmax_mv = (u32)clamp_val(buf[CUSTOM_DATA_VMAX_IDX], 0, VMAX_MV_MAX); > + priv->data_len = effect->u.periodic.custom_len - CUSTOM_DATA_SAMPLE_START; > + > + kfree(priv->fifo_data); > + priv->fifo_data = kmalloc(priv->data_len, GFP_KERNEL); > + if (!priv->fifo_data) { > + kfree(buf); > + return -ENOMEM; > + } [Severity: High] Could a failed memory allocation leave the internal state corrupted? When updating an existing FF_PERIODIC effect, data_len is updated to a new size before kmalloc() is called. If kmalloc() fails, the function returns -ENOMEM, leaving data_len non-zero while fifo_data is NULL. The input core keeps the existing effect ID valid. If userspace subsequently requests to play this effect, haptics_start_fifo() computes a non-zero init_len and passes the NULL pointer to haptics_write_fifo_chunk(), causing a crash. [ ... ] > +static int haptics_playback(struct input_dev *dev, int effect_id, int val) > +{ > + struct qcom_haptics *h = input_get_drvdata(dev); > + > + h->cur_effect_id = effect_id; > + h->play_request = (val > 0); [Severity: High] Could this lockless update of cur_effect_id misdirect the stop sequence? If a FIFO effect is actively playing and userspace issues a playback request for a different DIRECT_PLAY effect, cur_effect_id is updated asynchronously. When play_work eventually processes the stop/start sequence, haptics_stop_locked() checks the newly updated mode: if (h->effects[h->cur_effect_id].mode == HAPTICS_FIFO) { ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0); Seeing DIRECT_PLAY, it would skip disabling the FIFO IRQ and clearing fifo_data, while still dropping the PM reference. The device auto-suspends, but the IRQ remains enabled and fires spuriously. [ ... ] > +static int qcom_haptics_probe(struct platform_device *pdev) > +{ [ ... ] > + ff = input->ff; > + ff->upload = haptics_upload_effect; > + ff->playback = haptics_playback; > + ff->erase = haptics_erase; > + ff->set_gain = haptics_set_gain; [Severity: Medium] Should the ff->destroy callback be implemented to prevent memory leaks? Custom waveform buffers are allocated using kmalloc() and stored in fifo_data. This memory is normally freed in haptics_erase(). However, when the driver is unbound and the input device is unregistered, the input core destroys the ff device but does not automatically invoke erase() for surviving effects. Without a destroy callback to clean up the effects array, the allocated buffers appear to be permanently leaked. > + > + ret = input_register_device(input); > + if (ret) { > + input_ff_destroy(input); > + return dev_err_probe(&pdev->dev, ret, > + "failed to register input device\n"); > + } > + > + platform_set_drvdata(pdev, h); > + > + /* > + * Grab a reference on behalf of probe (usage_count → 1), mark the > + * device active, then enable runtime PM. > + */ > + pm_runtime_get_noresume(&pdev->dev); > + pm_runtime_use_autosuspend(&pdev->dev); > + pm_runtime_set_autosuspend_delay(&pdev->dev, HAPTICS_AUTOSUSPEND_MS); > + devm_pm_runtime_set_active_enabled(&pdev->dev); [Severity: Medium] Is there a race condition by registering the input device before PM runtime is fully configured? This call makes the force-feedback device immediately visible to userspace. If an application sends a play request before the probe function finishes, haptics_play_work() will run and call pm_runtime_resume_and_get(). Since PM runtime is not yet fully enabled, the PM runtime API will return an error and fail the playback. [ ... ] > +static void qcom_haptics_remove(struct platform_device *pdev) > +{ > + struct qcom_haptics *h = platform_get_drvdata(pdev); > + > + pm_runtime_disable(&pdev->dev); > + pm_runtime_set_suspended(&pdev->dev); > + > + cancel_work_sync(&h->play_work); > + mutex_lock(&h->play_lock); > + haptics_stop_locked(h); > + haptics_enable_module(h, false); > + mutex_unlock(&h->play_lock); > + > + input_unregister_device(h->input); [Severity: High] Does this teardown ordering allow a work item to be re-queued and executed on freed memory? During driver removal, cancel_work_sync() is called before the input device is unregistered. This creates a race window where the input device is still visible to userspace, allowing applications to issue playback events that call schedule_work(). Once remove() completes, the devres framework frees the qcom_haptics memory. The mistakenly re-queued background work will then execute, dereferencing the freed driver context. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260616-qcom-spmi-haptics-v1-0-d24e422de6b4@oss.qualcomm.com?part=3 ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu 2026-06-16 10:23 ` sashiko-bot @ 2026-06-16 10:25 ` Konrad Dybcio 2026-06-16 14:37 ` Julian Braha 2 siblings, 0 replies; 12+ messages in thread From: Konrad Dybcio @ 2026-06-16 10:25 UTC (permalink / raw) To: Fenglin Wu, linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel On 6/16/26 12:08 PM, Fenglin Wu wrote: > Add an initial driver for the Qualcomm PMIH010x PMIC haptics module, > named as HAP530_HV. This module supports several play modes, including > DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing > and hardware data handling logic. Currently, the driver provides support > for two play modes using the input force-feedback framework: FF_CONSTANT > effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM > waveform for FIFO mode. > > Assisted-by: Claude:claude-4-6-sonnet > Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> > --- [...] > +static int cfg_write(struct qcom_haptics *h, u32 off, u32 val) static inline although I have mixed feelings about having so many accessors [...] > +static int haptics_write_fifo_chunk(struct qcom_haptics *h, > + const s8 *data, u32 len) > +{ > + u32 i, bulk_len = ALIGN_DOWN(len, 4); Please avoid mixing multiple declarations and assignments > + int ret; > + > + for (i = 0; i < bulk_len; i += 4) { You can do 'int i' in loops nowadays > + ret = ptn_bulk_write(h, HAP_PTN_FIFO_DIN_0_REG, &data[i], 4); > + if (ret) > + return ret; > + } > + > + for (; i < len; i++) { > + ret = ptn_write(h, HAP_PTN_FIFO_DIN_1B_REG, (u8)data[i]); > + if (ret) > + return ret; > + } So if i'm reading this right, the first loop will always write 4*(len//4) bytes and the second one will be entered at most once, to write len rem 4 bytes.. should this be an if instead? > + > + return 0; > +} > + > +/* > + * Configure the hardware FIFO memory boundary. > + * FIFO occupies addresses [0, fifo_len). > + */ > +static int haptics_configure_fifo_mmap(struct qcom_haptics *h) > +{ > + u32 fifo_len, fifo_units; > + > + /* Config all memory space for FIFO usage for now */ What's the not-"for now" endgame for this? > + fifo_len = HAP530_MEM_TOTAL_BYTES; > + fifo_len = ALIGN_DOWN(fifo_len, 64); > + fifo_units = fifo_len / 64; > + h->fifo_len = fifo_len; > + > + return ptn_write(h, HAP_PTN_MMAP_FIFO_REG, > + MMAP_FIFO_EXIST_BIT | > + FIELD_PREP(MMAP_FIFO_LEN_MASK, fifo_units - 1)); > +} > + > +static u32 haptics_gain_scaled_vmax(struct qcom_haptics *h, u32 vmax_mv) > +{ > + u32 v = (u32)((u64)vmax_mv * h->gain / 0xFFFF); mult_frac() > + > + return max_t(u32, v, VMAX_STEP_MV); > +} > + > +static void haptics_fifo_irq_enable(struct qcom_haptics *h, bool enable) > +{ > + if (h->irq_enabled == enable) > + return; > + > + if (enable) > + enable_irq(h->fifo_empty_irq); > + else > + disable_irq_nosync(h->fifo_empty_irq); This is called in the .remove() path, I think you may need the sync variant as the underlying device may be destroyed before the ISR completes if there's a late interrupt [...] > +static int haptics_playback(struct input_dev *dev, int effect_id, int val) > +{ > + struct qcom_haptics *h = input_get_drvdata(dev); > + > + h->cur_effect_id = effect_id; > + h->play_request = (val > 0); > + schedule_work(&h->play_work); > + return 0; nit: \n before return is 'nice' [...] > + ret = device_property_read_u32_array(&pdev->dev, "reg", regs, > + ARRAY_SIZE(regs)); Here you use device_property_ > + if (ret) > + return dev_err_probe(&pdev->dev, ret, > + "failed to read 'reg' property\n"); > + > + h->cfg_base = regs[0]; > + h->ptn_base = regs[1]; > + > + ret = of_property_read_u32(h->dev->of_node, "qcom,lra-period-us", > + &h->t_lra_us); And here you use of_property_ (please use device_) [...] > + input->name = "qcom-spmi-haptics"; Odd whitespace Konrad ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu 2026-06-16 10:23 ` sashiko-bot 2026-06-16 10:25 ` Konrad Dybcio @ 2026-06-16 14:37 ` Julian Braha 2 siblings, 0 replies; 12+ messages in thread From: Julian Braha @ 2026-06-16 14:37 UTC (permalink / raw) To: Fenglin Wu, linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel Hi Fenglin, On 6/16/26 11:08, Fenglin Wu wrote: > +config INPUT_QCOM_SPMI_HAPTICS > + tristate "Qualcomm SPMI PMIC haptics support" > + depends on INPUT && MFD_SPMI_PMIC The dependency on INPUT is unnecessary, all config options in this Kconfig file already depend on INPUT due to an 'if INPUT..endif' in drivers/input/Kconfig (Yes, a few of the other config options in this file also have this duplicate dependency on INPUT due to an explicit 'depends on' attribute, but this file is in need of a cleanup.) - Julian Braha ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node 2026-06-16 10:08 [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC Fenglin Wu ` (2 preceding siblings ...) 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu @ 2026-06-16 10:08 ` Fenglin Wu 2026-06-16 10:27 ` Konrad Dybcio 3 siblings, 1 reply; 12+ messages in thread From: Fenglin Wu @ 2026-06-16 10:08 UTC (permalink / raw) To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel, Fenglin Wu Add haptics device node in the PMIH0108 PMIC base dtsi files, and enable it on several boards according to the LRA (Linear Resonant Actuator) component mounted on each of them. Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> --- arch/arm64/boot/dts/qcom/kaanapali-mtp.dts | 7 +++++++ arch/arm64/boot/dts/qcom/kaanapali-qrd.dts | 7 +++++++ arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi | 9 +++++++++ arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +++++++++ arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 7 +++++++ arch/arm64/boot/dts/qcom/sm8750-qrd.dts | 7 +++++++ 6 files changed, 46 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts b/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts index 07247dc98b70..7e3f59fc008e 100644 --- a/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts +++ b/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts @@ -952,6 +952,13 @@ wifi@0 { }; }; +&pmih0108_e1_haptics { + status = "okay"; + + qcom,lra-period-us = <6667>; + qcom,vmax-mv = <3600>; +}; + &pmh0101_flash { status = "okay"; diff --git a/arch/arm64/boot/dts/qcom/kaanapali-qrd.dts b/arch/arm64/boot/dts/qcom/kaanapali-qrd.dts index da0e8f9091c3..0865afec47ac 100644 --- a/arch/arm64/boot/dts/qcom/kaanapali-qrd.dts +++ b/arch/arm64/boot/dts/qcom/kaanapali-qrd.dts @@ -744,6 +744,13 @@ led@3 { }; }; +&pmih0108_e1_haptics { + status = "okay"; + + qcom,lra-period-us = <7692>; + qcom,vmax-mv = <1850>; +}; + &pon_resin { linux,code = <KEY_VOLUMEDOWN>; diff --git a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi index b73b0e82c3d3..22c83c549ce9 100644 --- a/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi +++ b/arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi @@ -59,6 +59,15 @@ pmih0108_e1_gpios: gpio@8800 { #interrupt-cells = <2>; }; + pmih0108_e1_haptics: haptics@f000 { + compatible = "qcom,pmih010x-haptics"; + reg = <0xf000>, <0xf100>; + reg-names = "hap-cfg", "hap-ptn"; + interrupts = <0x7 0xf0 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "fifo-empty"; + status = "disabled"; + }; + pmih0108_e1_eusb2_repeater: phy@fd00 { compatible = "qcom,pm8550b-eusb2-repeater"; reg = <0xfd00>; diff --git a/arch/arm64/boot/dts/qcom/pmih0108.dtsi b/arch/arm64/boot/dts/qcom/pmih0108.dtsi index 1c875995d881..d942d6c2fd03 100644 --- a/arch/arm64/boot/dts/qcom/pmih0108.dtsi +++ b/arch/arm64/boot/dts/qcom/pmih0108.dtsi @@ -59,6 +59,15 @@ pmih0108_gpios: gpio@8800 { #interrupt-cells = <2>; }; + pmih0108_haptics: haptics@f000 { + compatible = "qcom,pmih010x-haptics"; + reg = <0xf000>, <0xf100>; + reg-names = "hap-cfg", "hap-ptn"; + interrupts = <0x7 0xf0 0x1 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "fifo-empty"; + status = "disabled"; + }; + pmih0108_eusb2_repeater: phy@fd00 { compatible = "qcom,pm8550b-eusb2-repeater"; reg = <0xfd00>; diff --git a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts index 3837f6785320..7a3b8c440d00 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-mtp.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-mtp.dts @@ -1138,6 +1138,13 @@ wifi@0 { }; }; +&pmih0108_haptics { + status = "okay"; + + qcom,lra-period-us = <6667>; + qcom,vmax-mv = <3600>; +}; + &pmih0108_eusb2_repeater { qcom,tune-usb2-preem = /bits/ 8 <0x3>; qcom,tune-usb2-amplitude = /bits/ 8 <0xa>; diff --git a/arch/arm64/boot/dts/qcom/sm8750-qrd.dts b/arch/arm64/boot/dts/qcom/sm8750-qrd.dts index 801c46d55602..4a1079984307 100644 --- a/arch/arm64/boot/dts/qcom/sm8750-qrd.dts +++ b/arch/arm64/boot/dts/qcom/sm8750-qrd.dts @@ -933,6 +933,13 @@ &pon_resin { status = "okay"; }; +&pmih0108_haptics { + status = "okay"; + + qcom,lra-period-us = <5880>; + qcom,vmax-mv = <1700>; +}; + &pmih0108_eusb2_repeater { status = "okay"; -- 2.43.0 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node 2026-06-16 10:08 ` [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node Fenglin Wu @ 2026-06-16 10:27 ` Konrad Dybcio 0 siblings, 0 replies; 12+ messages in thread From: Konrad Dybcio @ 2026-06-16 10:27 UTC (permalink / raw) To: Fenglin Wu, linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson, Konrad Dybcio Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel On 6/16/26 12:08 PM, Fenglin Wu wrote: > Add haptics device node in the PMIH0108 PMIC base dtsi files, and enable > it on several boards according to the LRA (Linear Resonant Actuator) > component mounted on each of them. > > Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com> > --- > arch/arm64/boot/dts/qcom/kaanapali-mtp.dts | 7 +++++++ > arch/arm64/boot/dts/qcom/kaanapali-qrd.dts | 7 +++++++ > arch/arm64/boot/dts/qcom/pmih0108-kaanapali.dtsi | 9 +++++++++ > arch/arm64/boot/dts/qcom/pmih0108.dtsi | 9 +++++++++ > arch/arm64/boot/dts/qcom/sm8750-mtp.dts | 7 +++++++ > arch/arm64/boot/dts/qcom/sm8750-qrd.dts | 7 +++++++ One commit per board, please > 6 files changed, 46 insertions(+) > > diff --git a/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts b/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts > index 07247dc98b70..7e3f59fc008e 100644 > --- a/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts > +++ b/arch/arm64/boot/dts/qcom/kaanapali-mtp.dts > @@ -952,6 +952,13 @@ wifi@0 { > }; > }; > > +&pmih0108_e1_haptics { > + status = "okay"; 'status' should go last > + > + qcom,lra-period-us = <6667>; > + qcom,vmax-mv = <3600>; Do these properties depend on the physical characteristics on what's connected to the other end of the haptics driver? Konrad ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-06-16 14:37 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-16 10:08 [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC Fenglin Wu 2026-06-16 10:08 ` [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics Fenglin Wu 2026-06-16 10:11 ` Konrad Dybcio 2026-06-16 10:18 ` sashiko-bot 2026-06-16 10:08 ` [PATCH 2/4] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device Fenglin Wu 2026-06-16 10:17 ` sashiko-bot 2026-06-16 10:08 ` [PATCH 3/4] input: misc: Add Qualcomm SPMI PMIC haptics driver Fenglin Wu 2026-06-16 10:23 ` sashiko-bot 2026-06-16 10:25 ` Konrad Dybcio 2026-06-16 14:37 ` Julian Braha 2026-06-16 10:08 ` [PATCH 4/4] arm64: dts: qcom: Add PMIH0108 haptics device node Fenglin Wu 2026-06-16 10:27 ` Konrad Dybcio
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox