* [PATCH 2/2] Input: pm8941-powerkey - fix debounce on gen2+ PMICs
From: Caleb Connolly @ 2023-05-29 19:55 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Gross, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, linux-arm-msm, phone-devel, Caleb Connolly
In-Reply-To: <20230529-pm8941-pwrkey-debounce-v1-0-c043a6d5c814@linaro.org>
Since PM8998/PM660, the power key debounce register was redefined to
support shorter debounce times. On PM8941 the shortest debounce time
(represented by register value 0) was 15625us, on PM8998 the shortest
debounce time is 62us, with the default being 2ms.
Adjust the bit shift to correctly program debounce on PM8998 and newer.
Fixes: 68c581d5e7d8 ("Input: add Qualcomm PM8941 power key driver")
Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
---
This patch shouldn't be backported earlier then 5.4, as that is the
first kernel with support for PM8998.
---
drivers/input/misc/pm8941-pwrkey.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/input/misc/pm8941-pwrkey.c b/drivers/input/misc/pm8941-pwrkey.c
index b6a27ebae977..74d77d8aaeff 100644
--- a/drivers/input/misc/pm8941-pwrkey.c
+++ b/drivers/input/misc/pm8941-pwrkey.c
@@ -50,7 +50,10 @@
#define PON_RESIN_PULL_UP BIT(0)
#define PON_DBC_CTL 0x71
-#define PON_DBC_DELAY_MASK 0x7
+#define PON_DBC_DELAY_MASK_GEN1 0x7
+#define PON_DBC_DELAY_MASK_GEN2 0xf
+#define PON_DBC_SHIFT_GEN1 6
+#define PON_DBC_SHIFT_GEN2 14
struct pm8941_data {
unsigned int pull_up_bit;
@@ -247,7 +250,7 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
struct device *parent;
struct device_node *regmap_node;
const __be32 *addr;
- u32 req_delay;
+ u32 req_delay, mask, delay_shift;
int error;
if (of_property_read_u32(pdev->dev.of_node, "debounce", &req_delay))
@@ -336,12 +339,20 @@ static int pm8941_pwrkey_probe(struct platform_device *pdev)
pwrkey->input->phys = pwrkey->data->phys;
if (pwrkey->data->supports_debounce_config) {
- req_delay = (req_delay << 6) / USEC_PER_SEC;
+ if (pwrkey->subtype >= PON_SUBTYPE_GEN2_PRIMARY) {
+ mask = PON_DBC_DELAY_MASK_GEN2;
+ delay_shift = PON_DBC_SHIFT_GEN2;
+ } else {
+ mask = PON_DBC_DELAY_MASK_GEN1;
+ delay_shift = PON_DBC_SHIFT_GEN1;
+ }
+
+ req_delay = (req_delay << delay_shift) / USEC_PER_SEC;
req_delay = ilog2(req_delay);
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + PON_DBC_CTL,
- PON_DBC_DELAY_MASK,
+ mask,
req_delay);
if (error) {
dev_err(&pdev->dev, "failed to set debounce: %d\n",
--
2.40.1
^ permalink raw reply related
* [PATCH 1/2] MAINTAINERS: Adjust Qualcomm driver globbing
From: Caleb Connolly @ 2023-05-29 19:55 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Gross, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, linux-arm-msm, phone-devel, Caleb Connolly
In-Reply-To: <20230529-pm8941-pwrkey-debounce-v1-0-c043a6d5c814@linaro.org>
The only drivers matching pm8???-* are two levels deep, adjust the glob
to match them.
Signed-off-by: Caleb Connolly <caleb.connolly@linaro.org>
---
MAINTAINERS | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 27ef11624748..86b7842b44ab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2577,9 +2577,9 @@ F: arch/arm/boot/dts/qcom-*.dtsi
F: arch/arm/configs/qcom_defconfig
F: arch/arm/mach-qcom/
F: arch/arm64/boot/dts/qcom/
+F: drivers/*/*/pm8???-*
F: drivers/*/*/qcom*
F: drivers/*/*/qcom/
-F: drivers/*/pm8???-*
F: drivers/*/qcom*
F: drivers/*/qcom/
F: drivers/bluetooth/btqcomsmd.c
--
2.40.1
^ permalink raw reply related
* [PATCH 0/2] Fix pm8941-pwrkey debounce programming
From: Caleb Connolly @ 2023-05-29 19:55 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Gross, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, linux-arm-msm, phone-devel, Caleb Connolly
Since PM8998 the pon debounce register was adjusted to support much
lower debounce times however the driver was never changed to reflect
this.
This resulted in the debounce time being set to the minimum ~62us on
PMICs from PM8998/PM660 up until PMk8350.
Set the shift and mask correctly, and adjust MAINTAINERS to include the
driver under Qualcomm support.
---
Caleb Connolly (2):
MAINTAINERS: Adjust Qualcomm driver globbing
Input: pm8941-powerkey - fix debounce on gen2+ PMICs
MAINTAINERS | 2 +-
drivers/input/misc/pm8941-pwrkey.c | 19 +++++++++++++++----
2 files changed, 16 insertions(+), 5 deletions(-)
---
base-commit: 44c026a73be8038f03dbdeef028b642880cf1511
// Caleb (they/them)
^ permalink raw reply
* Re: [PATCH v2 6/7] docs: update some straggling Documentation/arm references
From: Greg Kroah-Hartman @ 2023-05-29 14:57 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-doc, linux-arch, linux-arm-kernel, Dmitry Torokhov,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Thierry Reding,
Uwe Kleine-König, linux-input, linux-sunxi, linux-pwm,
linux-serial
In-Reply-To: <20230529144856.102755-7-corbet@lwn.net>
On Mon, May 29, 2023 at 08:48:55AM -0600, Jonathan Corbet wrote:
> The Arm documentation has moved to Documentation/arch/arm; update the
> last remaining references to match.
>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Chen-Yu Tsai <wens@csie.org>
> Cc: Jernej Skrabec <jernej.skrabec@gmail.com>
> Cc: Samuel Holland <samuel@sholland.org>
> Cc: Thierry Reding <thierry.reding@gmail.com>
> Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: linux-input@vger.kernel.org
> Cc: linux-sunxi@lists.linux.dev
> Cc: linux-pwm@vger.kernel.org
> Cc: linux-serial@vger.kernel.org
> Signed-off-by: Jonathan Corbet <corbet@lwn.net>
> ---
> MAINTAINERS | 4 ++--
> drivers/input/touchscreen/sun4i-ts.c | 2 +-
> drivers/pwm/pwm-atmel.c | 2 +-
> drivers/pwm/pwm-pxa.c | 2 +-
> drivers/tty/serial/Kconfig | 4 ++--
> 5 files changed, 7 insertions(+), 7 deletions(-)
>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
^ permalink raw reply
* [PATCH v2 6/7] docs: update some straggling Documentation/arm references
From: Jonathan Corbet @ 2023-05-29 14:48 UTC (permalink / raw)
To: linux-doc
Cc: linux-arch, linux-arm-kernel, Jonathan Corbet, Dmitry Torokhov,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, Thierry Reding,
Uwe Kleine-König, Greg Kroah-Hartman, linux-input,
linux-sunxi, linux-pwm, linux-serial
In-Reply-To: <20230529144856.102755-1-corbet@lwn.net>
The Arm documentation has moved to Documentation/arch/arm; update the
last remaining references to match.
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Jernej Skrabec <jernej.skrabec@gmail.com>
Cc: Samuel Holland <samuel@sholland.org>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-input@vger.kernel.org
Cc: linux-sunxi@lists.linux.dev
Cc: linux-pwm@vger.kernel.org
Cc: linux-serial@vger.kernel.org
Signed-off-by: Jonathan Corbet <corbet@lwn.net>
---
MAINTAINERS | 4 ++--
drivers/input/touchscreen/sun4i-ts.c | 2 +-
drivers/pwm/pwm-atmel.c | 2 +-
drivers/pwm/pwm-pxa.c | 2 +-
drivers/tty/serial/Kconfig | 4 ++--
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index e0ad886d3163..d9525ccf2bf7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2706,7 +2706,7 @@ Q: https://patchwork.kernel.org/project/linux-samsung-soc/list/
B: mailto:linux-samsung-soc@vger.kernel.org
C: irc://irc.libera.chat/linux-exynos
T: git git://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux.git
-F: Documentation/arm/samsung/
+F: Documentation/arch/arm/samsung/
F: Documentation/devicetree/bindings/arm/samsung/
F: Documentation/devicetree/bindings/hwinfo/samsung,*
F: Documentation/devicetree/bindings/power/pd-samsung.yaml
@@ -15278,7 +15278,7 @@ OMAP DISPLAY SUBSYSTEM and FRAMEBUFFER SUPPORT (DSS2)
L: linux-omap@vger.kernel.org
L: linux-fbdev@vger.kernel.org
S: Orphan
-F: Documentation/arm/omap/dss.rst
+F: Documentation/arch/arm/omap/dss.rst
F: drivers/video/fbdev/omap2/
OMAP FRAMEBUFFER SUPPORT
diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c
index 577c75c83e25..bb3c6072fc82 100644
--- a/drivers/input/touchscreen/sun4i-ts.c
+++ b/drivers/input/touchscreen/sun4i-ts.c
@@ -22,7 +22,7 @@
* in the kernel). So this driver offers straight forward, reliable single
* touch functionality only.
*
- * s.a. A20 User Manual "1.15 TP" (Documentation/arm/sunxi.rst)
+ * s.a. A20 User Manual "1.15 TP" (Documentation/arch/arm/sunxi.rst)
* (looks like the description in the A20 User Manual v1.3 is better
* than the one in the A10 User Manual v.1.5)
*/
diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c
index 0c567d9623cd..5f7d286871cf 100644
--- a/drivers/pwm/pwm-atmel.c
+++ b/drivers/pwm/pwm-atmel.c
@@ -6,7 +6,7 @@
* Bo Shen <voice.shen@atmel.com>
*
* Links to reference manuals for the supported PWM chips can be found in
- * Documentation/arm/microchip.rst.
+ * Documentation/arch/arm/microchip.rst.
*
* Limitations:
* - Periods start with the inactive level.
diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c
index 46ed668bd141..762429d5647f 100644
--- a/drivers/pwm/pwm-pxa.c
+++ b/drivers/pwm/pwm-pxa.c
@@ -8,7 +8,7 @@
* eric miao <eric.miao@marvell.com>
*
* Links to reference manuals for some of the supported PWM chips can be found
- * in Documentation/arm/marvell.rst.
+ * in Documentation/arch/arm/marvell.rst.
*
* Limitations:
* - When PWM is stopped, the current PWM period stops abruptly at the next
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 398e5aac2e77..30bb9714f3c6 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -450,8 +450,8 @@ config SERIAL_SA1100
help
If you have a machine based on a SA1100/SA1110 StrongARM(R) CPU you
can enable its onboard serial port by enabling this option.
- Please read <file:Documentation/arm/sa1100/serial_uart.rst> for further
- info.
+ Please read <file:Documentation/arch/arm/sa1100/serial_uart.rst> for
+ further info.
config SERIAL_SA1100_CONSOLE
bool "Console on SA1100 serial port"
--
2.40.1
^ permalink raw reply related
* Re: Fwd: 6.1.30: thunderbolt: Clear registers properly when auto clear isn't in use cause call trace after resume
From: Bagas Sanjaya @ 2023-05-29 2:21 UTC (permalink / raw)
To: beld zhang, stable; +Cc: Linux Regressions, Linux USB, Linux Input Devices
In-Reply-To: <CAG7aomXP0JHmHytsv5cMsyHzee61BQnG3fc-Y+NLzum7H3DyHA@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 2137 bytes --]
On Sun, May 28, 2023 at 02:35:18PM -0400, beld zhang wrote:
> ---------- Forwarded message ---------
> From: beld zhang
> Date: Sun, May 28, 2023 at 2:07 PM
> Subject: Re: 6.1.30: thunderbolt: Clear registers properly when auto
> clear isn't in use cause call trace after resume
> To: Mario Limonciello
>
> On Sun, May 28, 2023 at 8:55 AM Mario Limonciello
> <mario.limonciello@amd.com> wrote:
> >
> > This is specific resuming from s2idle, doesn't happen at boot?
> >
> > Does it happen with hot-plugging or hot-unplugging a TBT3 or USB4 dock too?
> >
> > In addition to checking mainline, can you please attach a full dmesg to
> > somewhere ephemeral like a kernel bugzilla with thunderbolt.dyndbg='+p'
> > on the kernel command line set?
> >
>
> 6.4-rc4:
> *) test 1~4 was done with usb hub with ethernet plugged-in
> model: UE330, usb 3.0 3-port hub & GIgabit Ether adapter
> a rapoo wireless mouse in one of the ports
> 1) no crash at boot
> until [169.099024]
> 2) no crash after plug an extra usb dock
> from [297.004691]
> 3) no crash after remove it
> from [373.273511]
> 4) crash after suspend/resume: 2 call-stacks
> from [438.356253]
> 5) removed that hub(only ac-power left): NO crash after resume
> from [551.820333]
> 6) plug in the hub(no mouse): NO crash after resume
> from [1250.256607]
> 7) put on mouse: CRASH after resume
> from [1311.400963]
> mouse model: Rapoo Wireless Optical Mouse 1620
Before suspend, is attaching your mouse not crashing your system?
>
> sorry I have no idea how to fill a proper bug report at kernel
> bugzilla, hope these shared links work.
> btw I have no TB devices to test.
>
> dmesg:
> https://drive.google.com/file/d/1bUWnV7q2ziM4tdTzmuGiVuvEzaLcdfKm/view?usp=sharing
>
> config:
> https://drive.google.com/file/d/1It75_AV5tOzfkXXBAX5zAiZMoeJAe0Au/view?usp=sharing
There is a functionality on Bugzilla to attach above files. Use it.
Thanks.
--
An old man doll... just what I always wanted! - Clara
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 1/2] iio/adc: ingenic: Fix channel offsets in buffer
From: Jonathan Cameron @ 2023-05-28 17:49 UTC (permalink / raw)
To: Artur Rojek
Cc: Paul Cercueil, Dmitry Torokhov, Chris Morgan, Andy Shevchenko,
linux-mips, linux-iio, linux-kernel, linux-input
In-Reply-To: <20230521225901.388455-2-contact@artur-rojek.eu>
On Mon, 22 May 2023 00:59:00 +0200
Artur Rojek <contact@artur-rojek.eu> wrote:
> Consumers expect the buffer to only contain enabled channels. While
> preparing the buffer, the driver makes two mistakes:
> 1) It inserts empty data for disabled channels.
> 2) Each ADC readout contains samples for two 16-bit channels. If either
> of them is active, the whole 32-bit sample is pushed into the buffer
> as-is.
>
> Both of those issues cause the active channels to appear at the wrong
> offsets in the buffer. Fix the above by demuxing samples for active
> channels only.
>
> This has remained unnoticed, as all the consumers so far were only using
> channels 0 and 1, leaving them unaffected by changes introduced in this
> commit.
>
> Signed-off-by: Artur Rojek <contact@artur-rojek.eu>
> Tested-by: Paul Cercueil <paul@crapouillou.net>
Lazy me suggestions that, as we didn't notice this before, clearly the
vast majority of times the channels are both enabled.
As such you 'could' just set available_scan_masks and burn the overhead
of reading channels you don't want, instead letting the IIO core demux
deal with the data movement if needed.
> ---
>
> v2: - demux active channels from ADC readouts
> - clarify in the commit description that this patch doesn't impact
> existing consumers of this driver
>
> drivers/iio/adc/ingenic-adc.c | 20 +++++++++++++-------
> 1 file changed, 13 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/iio/adc/ingenic-adc.c b/drivers/iio/adc/ingenic-adc.c
> index a7325dbbb99a..093710a7ad4c 100644
> --- a/drivers/iio/adc/ingenic-adc.c
> +++ b/drivers/iio/adc/ingenic-adc.c
> @@ -802,13 +802,19 @@ static irqreturn_t ingenic_adc_irq(int irq, void *data)
> struct ingenic_adc *adc = iio_priv(iio_dev);
> unsigned long mask = iio_dev->active_scan_mask[0];
> unsigned int i;
> - u32 tdat[3];
> -
> - for (i = 0; i < ARRAY_SIZE(tdat); mask >>= 2, i++) {
> - if (mask & 0x3)
> - tdat[i] = readl(adc->base + JZ_ADC_REG_ADTCH);
> - else
> - tdat[i] = 0;
> + u16 tdat[6];
> + u32 val;
> +
> + memset(tdat, 0, ARRAY_SIZE(tdat));
> + for (i = 0; mask && i < ARRAY_SIZE(tdat); mask >>= 2) {
> + if (mask & 0x3) {
> + val = readl(adc->base + JZ_ADC_REG_ADTCH);
> + /* Two channels per sample. Demux active. */
> + if (mask & BIT(0))
> + tdat[i++] = val & 0xffff;
> + if (mask & BIT(1))
> + tdat[i++] = val >> 16;
> + }
> }
>
> iio_push_to_buffers(iio_dev, tdat);
^ permalink raw reply
* Re: [regression] Since kernel 6.3.1 logitech unify receiver not working properly
From: Thorsten Leemhuis @ 2023-05-28 12:21 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Linus Torvalds, Linux regressions mailing list, Bagas Sanjaya,
Filipe Laíns, Bastien Nocera, open list:HID CORE LAYER, LKML,
guy.b
In-Reply-To: <nycvar.YFH.7.76.2305262040330.29760@cbobk.fhfr.pm>
On 26.05.23 20:41, Jiri Kosina wrote:
> On Thu, 25 May 2023, Jiri Kosina wrote:
>
>>>>> That bug is pre-existing (ie the problem was not introduced by that
>>>>> commit), but who knows if the retry makes things worse (ie if it then
>>>>> triggers on a retry, the response data will be the *previous* response).
>>>>>
>>>>> The whole "goto exit" games should be removed too, because we're in a
>>>>> for-loop, and instead of "goto exit" it should just do "break".
>>>>>
>>>>> IOW, something like this might be worth testing.
>>>>>
>>>>> That said, while I think the code is buggy, I doubt this is the actual
>>>>> cause of the problem people are reporting. But it would be lovely to
>>>>> hear if the attached patch makes any difference, and I think this is
>>>>> fixing a real - but unlikely - problem anyway.
>>>
>>> FWIW, Linus, your patch is
>>> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
>>>
>>> Feel free to submit it to us or to apply it directly if you prefer as
>>> this is clearly a fix for a code path issue.
>>
>> It would be nice to hear from the people who were able to reproduce the
>> issue whether this makes any observable difference in behavior though. I
>> don't currently think it would, as it fixes a potential NULL pointer
>> dereference, which is not what has been reported.
>>
>> Has anyone of the affected people tried to bisect the issue?
>
> Could anyone
Reminder: a lot of the affected users can only be reached through the
bugzilla ticket (https://bugzilla.kernel.org/show_bug.cgi?id=217412 )
that made me start this thread. Sorry for this mess, but I can't simply
CC them because on account creation users are told that the "email
address will never be displayed to logged out users". Bugbot will
hopefully soon make this sort of problems history.
> who is able to reproduce the issue please check whether
> reverting
>
> 586e8fede7953b16 ("HID: logitech-hidpp: Retry commands when device is busy")
>
> has any observable effect?
See https://bugzilla.kernel.org/show_bug.cgi?id=217412#c26 and later –
it at least solved the problem for one user.
But it's all a mess (at least afaics). Earlier in that ticket some other
user said things work with 6.4-rc kernel, while for another confirmed
things are still broken. So maybe we deal with more than one problem. Or
testing went sideways for some of the users.
Ciao, Thorsten
^ permalink raw reply
* [PATCH] HID: sensor-hub: Allow multi-function sensor devices
From: Daniel Thompson @ 2023-05-28 9:24 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada,
Benjamin Tissoires
Cc: Daniel Thompson, Bjorn Andersson, linux-input, linux-iio,
linux-kernel
The Lenovo Yoga C630 has a combined keyboard and accelerometer that
interfaces via i2c-hid. Currently this laptop either has a working
keyboard (if CONFIG_HID_SENSOR_HUB is disabled) or a working accelerometer.
only works on kernels. Put another way, most distro kernels enable
CONFIG_HID_SENSOR_HUB and therefore cannot work on this device since the
keyboard doesn't work!
Fix this by providing a richer connect mask during the probe. With this
change both keyboard and screen orientation sensors work correctly.
Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
---
Notes:
If I am honest, this patch owes a good deal more to trial and error
than diligent understanding of the HID specs.
It works well on C630 but has an obvious risk of regressions: the
connect flags have been zero since the hub sensor code was first
introduced. It is possible this could be better as an opt-in or
opt-out quirk (or a even custom driver) but I figure trying a
no-quirk approach might be a good starting point!
For the record, hid-decode tells us this about the device:
# device 0:0
# 0x05, 0x01, // Usage Page (Generic Desktop) 0
# 0x09, 0x06, // Usage (Keyboard) 2
# 0xa1, 0x01, // Collection (Application) 4
# 0x85, 0x01, // Report ID (1) 6
# 0x05, 0x07, // Usage Page (Keyboard) 8
# 0x19, 0xe0, // Usage Minimum (224) 10
# 0x29, 0xe7, // Usage Maximum (231) 12
# 0x15, 0x00, // Logical Minimum (0) 14
# 0x25, 0x01, // Logical Maximum (1) 16
# 0x75, 0x01, // Report Size (1) 18
# 0x95, 0x08, // Report Count (8) 20
# 0x81, 0x02, // Input (Data,Var,Abs) 22
# 0x95, 0x01, // Report Count (1) 24
# 0x75, 0x08, // Report Size (8) 26
# 0x81, 0x03, // Input (Cnst,Var,Abs) 28
# 0x95, 0x05, // Report Count (5) 30
# 0x75, 0x01, // Report Size (1) 32
# 0x05, 0x08, // Usage Page (LEDs) 34
# 0x19, 0x01, // Usage Minimum (1) 36
# 0x29, 0x05, // Usage Maximum (5) 38
# 0x91, 0x02, // Output (Data,Var,Abs) 40
# 0x95, 0x01, // Report Count (1) 42
# 0x75, 0x03, // Report Size (3) 44
# 0x91, 0x03, // Output (Cnst,Var,Abs) 46
# 0x95, 0x06, // Report Count (6) 48
# 0x75, 0x08, // Report Size (8) 50
# 0x15, 0x00, // Logical Minimum (0) 52
# 0x25, 0xff, // Logical Maximum (255) 54
# 0x05, 0x07, // Usage Page (Keyboard) 56
# 0x19, 0x00, // Usage Minimum (0) 58
# 0x29, 0xff, // Usage Maximum (255) 60
# 0x81, 0x00, // Input (Data,Arr,Abs) 62
# 0xc0, // End Collection 64
# 0x05, 0x0c, // Usage Page (Consumer Devices) 65
# 0x09, 0x01, // Usage (Consumer Control) 67
# 0xa1, 0x01, // Collection (Application) 69
# 0x85, 0x03, // Report ID (3) 71
# 0x95, 0x01, // Report Count (1) 73
# 0x75, 0x10, // Report Size (16) 75
# 0x15, 0x00, // Logical Minimum (0) 77
# 0x26, 0xff, 0x03, // Logical Maximum (1023) 79
# 0x19, 0x00, // Usage Minimum (0) 82
# 0x2a, 0xff, 0x03, // Usage Maximum (1023) 84
# 0x81, 0x00, // Input (Data,Arr,Abs) 87
# 0xc0, // End Collection 89
# 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 90
# 0x09, 0x01, // Usage (Vendor Usage 1) 93
# 0xa1, 0x01, // Collection (Application) 95
# 0x85, 0x05, // Report ID (5) 97
# 0x19, 0x00, // Usage Minimum (0) 99
# 0x29, 0xff, // Usage Maximum (255) 101
# 0x15, 0x00, // Logical Minimum (0) 103
# 0x25, 0xff, // Logical Maximum (255) 105
# 0x75, 0x08, // Report Size (8) 107
# 0x95, 0x08, // Report Count (8) 109
# 0x81, 0x00, // Input (Data,Arr,Abs) 111
# 0xc0, // End Collection 113
# 0x05, 0x01, // Usage Page (Generic Desktop) 114
# 0x09, 0x0c, // Usage (Wireless Radio Controls) 116
# 0xa1, 0x01, // Collection (Application) 118
# 0x85, 0x06, // Report ID (6) 120
# 0x15, 0x00, // Logical Minimum (0) 122
# 0x25, 0x01, // Logical Maximum (1) 124
# 0x09, 0xc6, // Usage (Wireless Radio Button) 126
# 0x95, 0x01, // Report Count (1) 128
# 0x75, 0x01, // Report Size (1) 130
# 0x81, 0x06, // Input (Data,Var,Rel) 132
# 0x75, 0x07, // Report Size (7) 134
# 0x81, 0x03, // Input (Cnst,Var,Abs) 136
# 0xc0, // End Collection 138
# 0x05, 0x20, // Usage Page (Sensor) 139
# 0x09, 0x01, // Usage (Sensor) 141
# 0xa1, 0x00, // Collection (Physical) 143
# 0x05, 0x20, // Usage Page (Sensor) 145
# 0x09, 0x73, // Usage (Motion: Accelerometer 3D) 147
# 0xa1, 0x00, // Collection (Physical) 149
# 0x85, 0x07, // Report ID (7) 151
# 0x05, 0x20, // Usage Page (Sensor) 153
# 0x0a, 0x09, 0x03, // Usage (Property: Sensor Connection Type) 155
# 0x15, 0x00, // Logical Minimum (0) 158
# 0x25, 0x02, // Logical Maximum (2) 160
# 0x75, 0x08, // Report Size (8) 162
# 0x95, 0x01, // Report Count (1) 164
# 0xa1, 0x02, // Collection (Logical) 166
# 0x0a, 0x30, 0x08, // Usage (Connection Type: PC Integrated) 168
# 0x0a, 0x31, 0x08, // Usage (Connection Type: PC Attached) 171
# 0x0a, 0x32, 0x08, // Usage (Connection Type: PC External) 174
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 177
# 0xc0, // End Collection 179
# 0x0a, 0x16, 0x03, // Usage (Property: Reporting State) 180
# 0x15, 0x00, // Logical Minimum (0) 183
# 0x25, 0x05, // Logical Maximum (5) 185
# 0x75, 0x08, // Report Size (8) 187
# 0x95, 0x01, // Report Count (1) 189
# 0xa1, 0x02, // Collection (Logical) 191
# 0x0a, 0x40, 0x08, // Usage (Reporting State: Report No Events) 193
# 0x0a, 0x41, 0x08, // Usage (Reporting State: Report All Events) 196
# 0x0a, 0x42, 0x08, // Usage (Reporting State: Report Threshold Events) 199
# 0x0a, 0x43, 0x08, // Usage (Reporting State: Wake On No Events) 202
# 0x0a, 0x44, 0x08, // Usage (Reporting State: Wake On All Events) 205
# 0x0a, 0x45, 0x08, // Usage (Reporting State: Wake On Threshold Events) 208
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 211
# 0xc0, // End Collection 213
# 0x0a, 0x19, 0x03, // Usage (Property: Power State) 214
# 0x15, 0x00, // Logical Minimum (0) 217
# 0x25, 0x05, // Logical Maximum (5) 219
# 0x75, 0x08, // Report Size (8) 221
# 0x95, 0x01, // Report Count (1) 223
# 0xa1, 0x02, // Collection (Logical) 225
# 0x0a, 0x50, 0x08, // Usage (Power State: Undefined) 227
# 0x0a, 0x51, 0x08, // Usage (Power State: D0 Full Power) 230
# 0x0a, 0x52, 0x08, // Usage (Power State: D1 Low Power) 233
# 0x0a, 0x53, 0x08, // Usage (Power State: D2 Standby Power with Wakeup) 236
# 0x0a, 0x54, 0x08, // Usage (Power State: D3 Sleep with Wakeup) 239
# 0x0a, 0x55, 0x08, // Usage (Power State: D4 Power Off) 242
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 245
# 0xc0, // End Collection 247
# 0x0a, 0x01, 0x02, // Usage (Event: Sensor State) 248
# 0x15, 0x00, // Logical Minimum (0) 251
# 0x25, 0x06, // Logical Maximum (6) 253
# 0x75, 0x08, // Report Size (8) 255
# 0x95, 0x01, // Report Count (1) 257
# 0xa1, 0x02, // Collection (Logical) 259
# 0x0a, 0x00, 0x08, // Usage (Sensor State: Undefined) 261
# 0x0a, 0x01, 0x08, // Usage (Sensor State: Ready) 264
# 0x0a, 0x02, 0x08, // Usage (Sensor State: Not Available) 267
# 0x0a, 0x03, 0x08, // Usage (Sensor State: No Data Sel) 270
# 0x0a, 0x04, 0x08, // Usage (Sensor State: Initializing) 273
# 0x0a, 0x05, 0x08, // Usage (Sensor State: Access Denied) 276
# 0x0a, 0x06, 0x08, // Usage (Sensor State: Error) 279
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 282
# 0xc0, // End Collection 284
# 0x0a, 0x0e, 0x03, // Usage (Property: Report Interval) 285
# 0x15, 0x00, // Logical Minimum (0) 288
# 0x27, 0xff, 0xff, 0xff, 0xff, // Logical Maximum (4294967295) 290
# 0x75, 0x20, // Report Size (32) 295
# 0x95, 0x01, // Report Count (1) 297
# 0x55, 0x00, // Unit Exponent (0) 299
# 0xb1, 0x02, // Feature (Data,Var,Abs) 301
# 0x0a, 0x52, 0x14, // Usage (Unknown Usage 0x1452) 303
# 0x15, 0x00, // Logical Minimum (0) 306
# 0x26, 0xff, 0xff, // Logical Maximum (65535) 308
# 0x75, 0x10, // Report Size (16) 311
# 0x95, 0x01, // Report Count (1) 313
# 0x55, 0x0d, // Unit Exponent (-3) 315
# 0xb1, 0x02, // Feature (Data,Var,Abs) 317
# 0x0a, 0x52, 0x24, // Usage (Unknown Usage 0x2452) 319
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 322
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 325
# 0x75, 0x10, // Report Size (16) 328
# 0x95, 0x01, // Report Count (1) 330
# 0x55, 0x0d, // Unit Exponent (-3) 332
# 0xb1, 0x02, // Feature (Data,Var,Abs) 334
# 0x0a, 0x52, 0x34, // Usage (Unknown Usage 0x3452) 336
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 339
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 342
# 0x75, 0x10, // Report Size (16) 345
# 0x95, 0x01, // Report Count (1) 347
# 0x55, 0x0d, // Unit Exponent (-3) 349
# 0xb1, 0x02, // Feature (Data,Var,Abs) 351
# 0x05, 0x20, // Usage Page (Sensor) 353
# 0x0a, 0x01, 0x02, // Usage (Event: Sensor State) 355
# 0x15, 0x00, // Logical Minimum (0) 358
# 0x25, 0x06, // Logical Maximum (6) 360
# 0x75, 0x08, // Report Size (8) 362
# 0x95, 0x01, // Report Count (1) 364
# 0xa1, 0x02, // Collection (Logical) 366
# 0x0a, 0x00, 0x08, // Usage (Sensor State: Undefined) 368
# 0x0a, 0x01, 0x08, // Usage (Sensor State: Ready) 371
# 0x0a, 0x02, 0x08, // Usage (Sensor State: Not Available) 374
# 0x0a, 0x03, 0x08, // Usage (Sensor State: No Data Sel) 377
# 0x0a, 0x04, 0x08, // Usage (Sensor State: Initializing) 380
# 0x0a, 0x05, 0x08, // Usage (Sensor State: Access Denied) 383
# 0x0a, 0x06, 0x08, // Usage (Sensor State: Error) 386
# 0x81, 0x00, // Input (Data,Arr,Abs) 389
# 0xc0, // End Collection 391
# 0x0a, 0x02, 0x02, // Usage (Event: Sensor Event) 392
# 0x15, 0x00, // Logical Minimum (0) 395
# 0x25, 0x05, // Logical Maximum (5) 397
# 0x75, 0x08, // Report Size (8) 399
# 0x95, 0x01, // Report Count (1) 401
# 0xa1, 0x02, // Collection (Logical) 403
# 0x0a, 0x10, 0x08, // Usage (Sensor Event: Unknown) 405
# 0x0a, 0x11, 0x08, // Usage (Sensor Event: State Changed) 408
# 0x0a, 0x12, 0x08, // Usage (Sensor Event: Property Changed) 411
# 0x0a, 0x13, 0x08, // Usage (Sensor Event: Data Updated) 414
# 0x0a, 0x14, 0x08, // Usage (Sensor Event: Poll Response) 417
# 0x0a, 0x15, 0x08, // Usage (Sensor Event: Change Sensitivity) 420
# 0x81, 0x00, // Input (Data,Arr,Abs) 423
# 0xc0, // End Collection 425
# 0x0a, 0x53, 0x04, // Usage (Data Field: Acceleration Axis X) 426
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 429
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 432
# 0x75, 0x10, // Report Size (16) 435
# 0x95, 0x01, // Report Count (1) 437
# 0x55, 0x0d, // Unit Exponent (-3) 439
# 0x81, 0x02, // Input (Data,Var,Abs) 441
# 0x0a, 0x54, 0x04, // Usage (Data Field: Acceleration Axis Y) 443
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 446
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 449
# 0x75, 0x10, // Report Size (16) 452
# 0x95, 0x01, // Report Count (1) 454
# 0x55, 0x0d, // Unit Exponent (-3) 456
# 0x81, 0x02, // Input (Data,Var,Abs) 458
# 0x0a, 0x55, 0x04, // Usage (Data Field: Acceleration Axis Z) 460
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 463
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 466
# 0x75, 0x10, // Report Size (16) 469
# 0x95, 0x01, // Report Count (1) 471
# 0x55, 0x0d, // Unit Exponent (-3) 473
# 0x81, 0x02, // Input (Data,Var,Abs) 475
# 0x0a, 0x51, 0x04, // Usage (Data Field: Motion State) 477
# 0x15, 0x00, // Logical Minimum (0) 480
# 0x25, 0x01, // Logical Maximum (1) 482
# 0x75, 0x08, // Report Size (8) 484
# 0x95, 0x01, // Report Count (1) 486
# 0x81, 0x02, // Input (Data,Var,Abs) 488
# 0xc0, // End Collection 490
# 0x05, 0x20, // Usage Page (Sensor) 491
# 0x09, 0xe1, // Usage (Other: Custom) 493
# 0xa1, 0x00, // Collection (Physical) 495
# 0x85, 0x0a, // Report ID (10) 497
# 0x05, 0x20, // Usage Page (Sensor) 499
# 0x0a, 0x09, 0x03, // Usage (Property: Sensor Connection Type) 501
# 0x15, 0x00, // Logical Minimum (0) 504
# 0x25, 0x02, // Logical Maximum (2) 506
# 0x75, 0x08, // Report Size (8) 508
# 0x95, 0x01, // Report Count (1) 510
# 0xa1, 0x02, // Collection (Logical) 512
# 0x0a, 0x30, 0x08, // Usage (Connection Type: PC Integrated) 514
# 0x0a, 0x31, 0x08, // Usage (Connection Type: PC Attached) 517
# 0x0a, 0x32, 0x08, // Usage (Connection Type: PC External) 520
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 523
# 0xc0, // End Collection 525
# 0x0a, 0x16, 0x03, // Usage (Property: Reporting State) 526
# 0x15, 0x00, // Logical Minimum (0) 529
# 0x25, 0x05, // Logical Maximum (5) 531
# 0x75, 0x08, // Report Size (8) 533
# 0x95, 0x01, // Report Count (1) 535
# 0xa1, 0x02, // Collection (Logical) 537
# 0x0a, 0x40, 0x08, // Usage (Reporting State: Report No Events) 539
# 0x0a, 0x41, 0x08, // Usage (Reporting State: Report All Events) 542
# 0x0a, 0x42, 0x08, // Usage (Reporting State: Report Threshold Events) 545
# 0x0a, 0x43, 0x08, // Usage (Reporting State: Wake On No Events) 548
# 0x0a, 0x44, 0x08, // Usage (Reporting State: Wake On All Events) 551
# 0x0a, 0x45, 0x08, // Usage (Reporting State: Wake On Threshold Events) 554
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 557
# 0xc0, // End Collection 559
# 0x0a, 0x19, 0x03, // Usage (Property: Power State) 560
# 0x15, 0x00, // Logical Minimum (0) 563
# 0x25, 0x05, // Logical Maximum (5) 565
# 0x75, 0x08, // Report Size (8) 567
# 0x95, 0x01, // Report Count (1) 569
# 0xa1, 0x02, // Collection (Logical) 571
# 0x0a, 0x50, 0x08, // Usage (Power State: Undefined) 573
# 0x0a, 0x51, 0x08, // Usage (Power State: D0 Full Power) 576
# 0x0a, 0x52, 0x08, // Usage (Power State: D1 Low Power) 579
# 0x0a, 0x53, 0x08, // Usage (Power State: D2 Standby Power with Wakeup) 582
# 0x0a, 0x54, 0x08, // Usage (Power State: D3 Sleep with Wakeup) 585
# 0x0a, 0x55, 0x08, // Usage (Power State: D4 Power Off) 588
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 591
# 0xc0, // End Collection 593
# 0x0a, 0x01, 0x02, // Usage (Event: Sensor State) 594
# 0x15, 0x00, // Logical Minimum (0) 597
# 0x25, 0x06, // Logical Maximum (6) 599
# 0x75, 0x08, // Report Size (8) 601
# 0x95, 0x01, // Report Count (1) 603
# 0xa1, 0x02, // Collection (Logical) 605
# 0x0a, 0x00, 0x08, // Usage (Sensor State: Undefined) 607
# 0x0a, 0x01, 0x08, // Usage (Sensor State: Ready) 610
# 0x0a, 0x02, 0x08, // Usage (Sensor State: Not Available) 613
# 0x0a, 0x03, 0x08, // Usage (Sensor State: No Data Sel) 616
# 0x0a, 0x04, 0x08, // Usage (Sensor State: Initializing) 619
# 0x0a, 0x05, 0x08, // Usage (Sensor State: Access Denied) 622
# 0x0a, 0x06, 0x08, // Usage (Sensor State: Error) 625
# 0xb1, 0x00, // Feature (Data,Arr,Abs) 628
# 0xc0, // End Collection 630
# 0x0a, 0x0e, 0x03, // Usage (Property: Report Interval) 631
# 0x15, 0x00, // Logical Minimum (0) 634
# 0x27, 0xff, 0xff, 0xff, 0xff, // Logical Maximum (4294967295) 636
# 0x75, 0x20, // Report Size (32) 641
# 0x95, 0x01, // Report Count (1) 643
# 0x55, 0x00, // Unit Exponent (0) 645
# 0xb1, 0x02, // Feature (Data,Var,Abs) 647
# 0x0a, 0x06, 0x03, // Usage (Property: Sensor Model) 649
# 0x75, 0x08, // Report Size (8) 652
# 0x95, 0x18, // Report Count (24) 654
# 0xb1, 0x03, // Feature (Cnst,Var,Abs) 656
# 0x0a, 0x40, 0x15, // Usage (Unknown Usage 0x1540) 658
# 0x15, 0x00, // Logical Minimum (0) 661
# 0x26, 0xff, 0xff, // Logical Maximum (65535) 663
# 0x75, 0x10, // Report Size (16) 666
# 0x95, 0x01, // Report Count (1) 668
# 0x55, 0x0d, // Unit Exponent (-3) 670
# 0xb1, 0x02, // Feature (Data,Var,Abs) 672
# 0x0a, 0x40, 0x25, // Usage (Unknown Usage 0x2540) 674
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 677
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 680
# 0x75, 0x10, // Report Size (16) 683
# 0x95, 0x01, // Report Count (1) 685
# 0x55, 0x0d, // Unit Exponent (-3) 687
# 0xb1, 0x02, // Feature (Data,Var,Abs) 689
# 0x0a, 0x40, 0x35, // Usage (Unknown Usage 0x3540) 691
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 694
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 697
# 0x75, 0x10, // Report Size (16) 700
# 0x95, 0x01, // Report Count (1) 702
# 0x55, 0x0d, // Unit Exponent (-3) 704
# 0xb1, 0x02, // Feature (Data,Var,Abs) 706
# 0x05, 0x20, // Usage Page (Sensor) 708
# 0x0a, 0x01, 0x02, // Usage (Event: Sensor State) 710
# 0x15, 0x00, // Logical Minimum (0) 713
# 0x25, 0x06, // Logical Maximum (6) 715
# 0x75, 0x08, // Report Size (8) 717
# 0x95, 0x01, // Report Count (1) 719
# 0xa1, 0x02, // Collection (Logical) 721
# 0x0a, 0x00, 0x08, // Usage (Sensor State: Undefined) 723
# 0x0a, 0x01, 0x08, // Usage (Sensor State: Ready) 726
# 0x0a, 0x02, 0x08, // Usage (Sensor State: Not Available) 729
# 0x0a, 0x03, 0x08, // Usage (Sensor State: No Data Sel) 732
# 0x0a, 0x04, 0x08, // Usage (Sensor State: Initializing) 735
# 0x0a, 0x05, 0x08, // Usage (Sensor State: Access Denied) 738
# 0x0a, 0x06, 0x08, // Usage (Sensor State: Error) 741
# 0x81, 0x00, // Input (Data,Arr,Abs) 744
# 0xc0, // End Collection 746
# 0x0a, 0x02, 0x02, // Usage (Event: Sensor Event) 747
# 0x15, 0x00, // Logical Minimum (0) 750
# 0x25, 0x05, // Logical Maximum (5) 752
# 0x75, 0x08, // Report Size (8) 754
# 0x95, 0x01, // Report Count (1) 756
# 0xa1, 0x02, // Collection (Logical) 758
# 0x0a, 0x10, 0x08, // Usage (Sensor Event: Unknown) 760
# 0x0a, 0x11, 0x08, // Usage (Sensor Event: State Changed) 763
# 0x0a, 0x12, 0x08, // Usage (Sensor Event: Property Changed) 766
# 0x0a, 0x13, 0x08, // Usage (Sensor Event: Data Updated) 769
# 0x0a, 0x14, 0x08, // Usage (Sensor Event: Poll Response) 772
# 0x0a, 0x15, 0x08, // Usage (Sensor Event: Change Sensitivity) 775
# 0x81, 0x00, // Input (Data,Arr,Abs) 778
# 0xc0, // End Collection 780
# 0x0a, 0x44, 0x05, // Usage (Data Field: Custom Value 1) 781
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 784
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 787
# 0x75, 0x10, // Report Size (16) 790
# 0x95, 0x01, // Report Count (1) 792
# 0x55, 0x0d, // Unit Exponent (-3) 794
# 0x81, 0x02, // Input (Data,Var,Abs) 796
# 0x0a, 0x45, 0x05, // Usage (Data Field: Custom Value 2) 798
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 801
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 804
# 0x75, 0x10, // Report Size (16) 807
# 0x95, 0x01, // Report Count (1) 809
# 0x55, 0x0d, // Unit Exponent (-3) 811
# 0x81, 0x02, // Input (Data,Var,Abs) 813
# 0x0a, 0x46, 0x05, // Usage (Data Field: Custom Value 3) 815
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 818
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 821
# 0x75, 0x10, // Report Size (16) 824
# 0x95, 0x01, // Report Count (1) 826
# 0x55, 0x0d, // Unit Exponent (-3) 828
# 0x81, 0x02, // Input (Data,Var,Abs) 830
# 0x0a, 0x47, 0x05, // Usage (Data Field: Custom Value 4) 832
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 835
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 838
# 0x75, 0x10, // Report Size (16) 841
# 0x95, 0x01, // Report Count (1) 843
# 0x55, 0x00, // Unit Exponent (0) 845
# 0x81, 0x02, // Input (Data,Var,Abs) 847
# 0x0a, 0x48, 0x05, // Usage (Data Field: Custom Value 5) 849
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 852
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 855
# 0x75, 0x10, // Report Size (16) 858
# 0x95, 0x01, // Report Count (1) 860
# 0x55, 0x00, // Unit Exponent (0) 862
# 0x81, 0x02, // Input (Data,Var,Abs) 864
# 0x0a, 0x49, 0x05, // Usage (Data Field: Custom Value 6) 866
# 0x16, 0x01, 0x80, // Logical Minimum (-32767) 869
# 0x26, 0xff, 0x7f, // Logical Maximum (32767) 872
# 0x75, 0x10, // Report Size (16) 875
# 0x95, 0x01, // Report Count (1) 877
# 0x55, 0x00, // Unit Exponent (0) 879
# 0x81, 0x02, // Input (Data,Var,Abs) 881
# 0xc0, // End Collection 883
# 0xc0, // End Collection 884
#
R: 885 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 03 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 06 75 08 15 00 25 ff 05 07 19 00 29 ff 81 00 c0 05 0c 09 01 a1 01 85 03 95 01 75 10 15 00 26 ff 03 19 00 2a ff 03 81 00 c0 06 00 ff 09 01 a1 01 85 05 19 00 29 ff 15 00 25 ff 75 08 95 08 81 00 c0 05 01 09 0c a1 01 85 06 15 00 25 01 09 c6 95 01 75 01 81 06 75 07 81 03 c0 05 20 09 01 a1 00 05 20 09 73 a1 00 85 07 05 20 0a 09 03 15 00 25 02 75 08 95 01 a1 02 0a 30 08 0a 31 08 0a 32 08 b1 00 c0 0a 16 03 15 00 25 05 75 08 95 01 a1 02 0a 40 08 0a 41 08 0a 42 08 0a 43 08 0a 44 08 0a 45 08 b1 00 c0 0a 19 03 15 00 25 05 75 08 95 01 a1 02 0a 50 08 0a 51 08 0a 52 08 0a 53 08 0a 54 08 0a 55 08 b1 00 c0 0a 01 02 15 00 25 06 75 08 95 01 a1 02 0a 00 08 0a 01 08 0a 02 08 0a 03 08 0a 04 08 0a 05 08 0a 06 08 b1 00 c0 0a 0e 03 15 00 27 ff ff ff ff 75 20 95 01 55 00 b1 02 0a 52 14 15 00 26 ff ff 75 10 95 01 55 0d b1 02 0a 52 24 16 01 80 26 ff 7f 75 10 95 01 55 0d b1 02 0a 52 34 16 01 80 26 ff 7f 75 10 95 01 55 0d b1 02 05 20 0a 01 02 15 00 25 06 75 08 95 01 a1 02 0a 00 08 0a 01 08 0a 02 08 0a 03 08 0a 04 08 0a 05 08 0a 06 08 81 00 c0 0a 02 02 15 00 25 05 75 08 95 01 a1 02 0a 10 08 0a 11 08 0a 12 08 0a 13 08 0a 14 08 0a 15 08 81 00 c0 0a 53 04 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 54 04 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 55 04 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 51 04 15 00 25 01 75 08 95 01 81 02 c0 05 20 09 e1 a1 00 85 0a 05 20 0a 09 03 15 00 25 02 75 08 95 01 a1 02 0a 30 08 0a 31 08 0a 32 08 b1 00 c0 0a 16 03 15 00 25 05 75 08 95 01 a1 02 0a 40 08 0a 41 08 0a 42 08 0a 43 08 0a 44 08 0a 45 08 b1 00 c0 0a 19 03 15 00 25 05 75 08 95 01 a1 02 0a 50 08 0a 51 08 0a 52 08 0a 53 08 0a 54 08 0a 55 08 b1 00 c0 0a 01 02 15 00 25 06 75 08 95 01 a1 02 0a 00 08 0a 01 08 0a 02 08 0a 03 08 0a 04 08 0a 05 08 0a 06 08 b1 00 c0 0a 0e 03 15 00 27 ff ff ff ff 75 20 95 01 55 00 b1 02 0a 06 03 75 08 95 18 b1 03 0a 40 15 15 00 26 ff ff 75 10 95 01 55 0d b1 02 0a 40 25 16 01 80 26 ff 7f 75 10 95 01 55 0d b1 02 0a 40 35 16 01 80 26 ff 7f 75 10 95 01 55 0d b1 02 05 20 0a 01 02 15 00 25 06 75 08 95 01 a1 02 0a 00 08 0a 01 08 0a 02 08 0a 03 08 0a 04 08 0a 05 08 0a 06 08 81 00 c0 0a 02 02 15 00 25 05 75 08 95 01 a1 02 0a 10 08 0a 11 08 0a 12 08 0a 13 08 0a 14 08 0a 15 08 81 00 c0 0a 44 05 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 45 05 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 46 05 16 01 80 26 ff 7f 75 10 95 01 55 0d 81 02 0a 47 05 16 01 80 26 ff 7f 75 10 95 01 55 00 81 02 0a 48 05 16 01 80 26 ff 7f 75 10 95 01 55 00 81 02 0a 49 05 16 01 80 26 ff 7f 75 10 95 01 55 00 81 02 c0 c0
N: device 0:0
I: 3 0001 0001
drivers/hid/hid-sensor-hub.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index 83237b86c8ff4..2eba152e8b905 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev,
}
INIT_LIST_HEAD(&hdev->inputs);
- ret = hid_hw_start(hdev, 0);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
base-commit: 44c026a73be8038f03dbdeef028b642880cf1511
--
2.39.2
^ permalink raw reply related
* Re: (subset) [PATCH 0/5] Improvements for drv260x driver and add to Huawei Watch
From: Bjorn Andersson @ 2023-05-27 1:07 UTC (permalink / raw)
To: Rob Herring, Dan Murphy, Yassine Oudjana, Luca Weiss, phone-devel,
Konrad Dybcio, Raffaele Tranquillini, Andy Gross, Dmitry Torokhov,
~postmarketos/upstreaming, Krzysztof Kozlowski
Cc: linux-arm-msm, devicetree, linux-kernel, linux-input
In-Reply-To: <20230430-drv260x-improvements-v1-0-1fb28b4cc698@z3ntu.xyz>
On Sun, 30 Apr 2023 20:20:52 +0200, Luca Weiss wrote:
> Clean up some issues with the driver I've noticed while working on it,
> then fix the magnitude handling which previously resulted the driver
> discarding the upper 8 bits of the magnitude value, and finally we can
> add the drv260x to the APQ8026 Huawei Watch.
>
> While the "fix magnitude handling" commit technically changes behavior
> and could change behavior of existing user space applications that deal
> with this quirky behavior, it's definitely not correct and results in
> very unexpected behavior if the user space doesn't look out for it
> (meaning only use 0x00-0xFF magnitude, and also know that the magnitude
> was interpreted as signed value, see more details in the commit message
> there).
>
> [...]
Applied, thanks!
[5/5] ARM: dts: qcom: apq8026-huawei-sturgeon: Add vibrator
commit: ad318f9ce5a2e815816bfcf7f187a3ac32905523
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
* Re: [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
From: Conor Dooley @ 2023-05-26 22:03 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches@opensource.cirrus.com,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, lee@kernel.org
In-Reply-To: <BBCD72CC-8312-4D57-9814-0E3A7F260F00@cirrus.com>
[-- Attachment #1: Type: text/plain, Size: 3595 bytes --]
On Fri, May 26, 2023 at 09:32:36PM +0000, Fred Treven wrote:
> > On May 26, 2023, at 2:27 PM, Conor Dooley <conor@kernel.org> wrote:
> > Tooling does not like your series very much, prob the missing v2's on
> > some patches:
> > Grabbing thread from lore.kernel.org/all/1685059471-9598-1-git-send-email-fred.treven%40cirrus.com/t.mbox.gz
> > Checking for newer revisions
> > Grabbing search results from lore.kernel.org
> > Analyzing 6 messages in the thread
> > Will use the latest revision: v2
> > You can pick other revisions using the -vN flag
> > Checking attestation on all messages, may take a moment...
> > ---
> > ✓ [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
> > ✓ Signed: DKIM/cirrus.com
> > + Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> > ✓ [PATCH v2 2/5] Input: cs40l26 - Support for CS40L26 Haptic Device
> > ✓ Signed: DKIM/cirrus.com
> > + Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> > ERROR: missing [3/5]!
> > ERROR: missing [4/5]!
> > ERROR: missing [5/5]!
>
> Understood. I was uncertain if this was just needed for patches that had
> been edited or for all new patches. I will resubmit with some other code
> changes to address other comments I’ve received and will mark the patches
> with the same version number.
I just whack an N into git format-patch's --reroll-count/-v option, and
use the same across the whole series. Makes people's and tool's lives
easier :)
> >> +properties:
> >> + compatible:
> >> + enum:
> >> + - cirrus,cs40l26a
> >> + - cirrus,cs40l26b
> >> + - cirrus,cs40l27a
> >> + - cirrus,cs40l27b
> >
> > I had a _brief_ look at the driver - you don't seem to have any match
> > data, so are all of these devices actually compatible with eachother?
> >
> > IOW, should this be:
> > properties:
> > compatible:
> > oneOf:
> > - items:
> > - enum:
> > - cirrus,cs40l26b
> > - cirrus,cs40l27a
> > - cirrus,cs40l27b
> > - const: cirrus,cs40l26a
> >
> > - const: cirrus,cs40l26a
> >
> > And then drop all but the cs40l26a in the driver? Apologies if I missed
> > some difference in there.
>
> They are all compatible, yes. There is match data in cs40l26-i2c.c and
> cs40l26-spi.c if you are referring to the of_device_id struct.
> Please let me know if I’m misunderstanding your meaning here.
What I saw looking in the driver was:
+static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { .compatible = "cirrus,cs40l27a" },
+ { .compatible = "cirrus,cs40l27b" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
So you have a bunch of compatibles, but didn't immediately appear to be
doing anything different depending on which one of them you get.
What I meant was populating the data field of of_device_id with something
different depending on the compatible.
If the programming model is the same, you can document that they are all
compatible with "cirrus,cs40l26a", rather than having to add new entries
to the match table. Also has the advantage that if you bring out a
cirrus,cs40l27c that is compatible with the existing devices, then no
changes are needed to software to support it ;)
Or perhaps you are doing something different based on the compatible
that I just did not notice.
Cheers,
Conor.
(The [CS40L26_NUM_DEVS + 1] is also usually just [] btw)
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH RFC 5/5] mfd: cs40l26: Add CODEC driver component
From: Jeff LaBundy @ 2023-05-26 21:55 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches@opensource.cirrus.com,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, lee@kernel.org
In-Reply-To: <F6FA6CFA-BFC5-47DA-83D1-6330E66195F5@cirrus.com>
Hi Fred,
On Fri, May 26, 2023 at 09:23:53PM +0000, Fred Treven wrote:
>
> Hi Jeff,
>
> > On May 26, 2023, at 2:43 PM, Jeff LaBundy <jeff@labundy.com> wrote:
> >
> > Hi Fred,
> >
> > On Thu, May 25, 2023 at 07:04:31PM -0500, Fred Treven wrote:
> >> Use MFD interface to load the CODEC driver along
> >> with the Input FF driver.
> >>
> >> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> >> ---
> >
> > Did you mean to include a thin codec driver as part of this series to
> > support the audio-to-haptics use-case? I don't see one.
>
> That is the end-goal, but I wanted to submit a request for comment with just this patch to see if it was at all acceptable to add another device this way. I see now that it is not.
>
> >
> > As Lee correctly points out, this isn't an MFD despite the title of the
> > commit message, and is sort of an abuse of the API.
>
> Understood. Do you think it’s best to migrate the appropriate code to the MFD subsystem before submitting this initial patchset (which will include the codec driver) or would it be acceptable to make that change after this has gone in? My hope was to avoid having code being reviewed more than once if a significant portion is moved to MFD.
>
> > What you seem to actually want is a true MFD driver responsible for
> > initializing common resources such as regmap, an IRQ chip, etc. That
> > driver then adds input and codec drivers as children.
> >
> > At the moment, you're more or less treating the input driver as the
> > MFD with one child, but that is not the correct pattern.
>
> Yeah that makes sense. Please advise on what the best way to continue would be: a. Drop this patch and migrate to MFD after the Input driver has been accepted or b. Move necessary code to MFD and add both Input and codec drivers from there along with the codec driver.
If your goal is to support audio-to-haptics in mainline and to do so using an
ASoC driver, then I recommend option B. You should start with a scalable and
maintainable solution that reflects your long-term goals.
Option A is sunk cost. I also expect that you will identify additional changes
required to the input driver, particularly with respect to interrupt handling,
beyond moving initialization-related code to the MFD. It's a major re-write in
my opinion.
It would be interesting to see if an existing Cirrus ASoC driver can be gently
reworked to act as the child codec, vs. having to write an entirely new one for
L26. I'm sure the devil is in the details though.
>
> Thank you,
> Fred
>
>
Kind regards,
Jeff LaBundy
^ permalink raw reply
* Re: [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
From: Fred Treven @ 2023-05-26 21:32 UTC (permalink / raw)
To: Conor Dooley
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches@opensource.cirrus.com,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, lee@kernel.org
In-Reply-To: <20230526-swapping-clay-d114144380a4@spud>
Hi Conor,
> On May 26, 2023, at 2:27 PM, Conor Dooley <conor@kernel.org> wrote:
>
> Yo Fred,
>
> Tooling does not like your series very much, prob the missing v2's on
> some patches:
> Grabbing thread from lore.kernel.org/all/1685059471-9598-1-git-send-email-fred.treven%40cirrus.com/t.mbox.gz
> Checking for newer revisions
> Grabbing search results from lore.kernel.org
> Analyzing 6 messages in the thread
> Will use the latest revision: v2
> You can pick other revisions using the -vN flag
> Checking attestation on all messages, may take a moment...
> ---
> ✓ [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
> ✓ Signed: DKIM/cirrus.com
> + Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> ✓ [PATCH v2 2/5] Input: cs40l26 - Support for CS40L26 Haptic Device
> ✓ Signed: DKIM/cirrus.com
> + Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
> ERROR: missing [3/5]!
> ERROR: missing [4/5]!
> ERROR: missing [5/5]!
Understood. I was uncertain if this was just needed for patches that had been edited or for all new patches. I will resubmit with some other code changes to address other comments I’ve received and will mark the patches with the same version number.
>
> On Thu, May 25, 2023 at 07:04:27PM -0500, Fred Treven wrote:
>> Introduce required basic devicetree parameters for the
>> initial commit of CS40L26.
>>
>> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
>> ---
>> .../devicetree/bindings/input/cirrus,cs40l26.yaml | 102 +++++++++++++++++++++
>> MAINTAINERS | 10 ++
>> 2 files changed, 112 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml b/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
>> new file mode 100644
>> index 000000000000..9cbc964ebded
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
>> @@ -0,0 +1,102 @@
>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/input/cirrus,cs40l26.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Cirrus Logic CS40L26 Boosted Haptic Amplifier
>> +
>> +maintainers:
>> + - Fred Treven <fred.treven@cirrus.com>
>> +
>> +description:
>> + CS40L26 is a Boosted Haptic Driver with Integrated DSP and Waveform Memory
>> + with Advanced Closed Loop Algorithms and LRA protection
>> +
>> +properties:
>> + compatible:
>> + enum:
>> + - cirrus,cs40l26a
>> + - cirrus,cs40l26b
>> + - cirrus,cs40l27a
>> + - cirrus,cs40l27b
>
> I had a _brief_ look at the driver - you don't seem to have any match
> data, so are all of these devices actually compatible with eachother?
>
> IOW, should this be:
> properties:
> compatible:
> oneOf:
> - items:
> - enum:
> - cirrus,cs40l26b
> - cirrus,cs40l27a
> - cirrus,cs40l27b
> - const: cirrus,cs40l26a
>
> - const: cirrus,cs40l26a
>
> And then drop all but the cs40l26a in the driver? Apologies if I missed
> some difference in there.
They are all compatible, yes. There is match data in cs40l26-i2c.c and cs40l26-spi.c if you are referring to the of_device_id struct. Please let me know if I’m misunderstanding your meaning here.
>
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + reset-gpios:
>> + maxItems: 1
>> +
>> + VA-supply:
>> + description: Regulator for VA analog voltage
>> +
>> + VP-supply:
>> + description: Regulator for VP peak voltage
>> +
>> + cirrus,bst-ipk-microamp:
>
> Are these namings ripped from a datasheet? "bst-ipk" doesn't immediately
> mean anything to me, but I am not familiar with these devices.
Yes, they are taken from the data sheet with “bst-ipk” referring to boost inductor peak current consumption. I do think it makes sense to have clearer naming here so I can go ahead and make these changes.
>
>> + description:
>> + Maximum amount of current that can be drawn by the device's boost converter.
>> + multipleOf: 50000
>> + minimum: 1600000
>> + maximum: 4800000
>> + default: 4500000
>> +
>> + cirrus,bst-ctl-microvolt:
>
> Ditto here. If there aren't rips, then maybe it'd be a good idea to use
> full words.
Same as above. Is ripped from DS, but doesn’t need to be called this if it’s not appropriate
>
>> + description:
>> + Disable boost exploratory mode.
>> +
>> + In exploratory mode the analog maximum peak current limit of 4.5 A
>> + (tolerance of + 160 mA) will be applied. This is required for the
>> + device to successfully detect a boost inductor short.
>> +
>> + Boost exploratory mode allows the device to overshoot the set boost peak
>> + current limit (i.e. if current peak limit is set to 2.5 A to protect the
>> + battery inductor, the current limit will be opened up to 4.5 A for
>> + several milliseconds at boost startup).
>> + This has potential to damage the boost inductor.
>> +
>> + Disabling this mode will prevent this from happening; it will also
>> + prevent the device from detecting boost inductor short errors.
>> + type: boolean
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - interrupts
>> + - reset-gpios
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/gpio/gpio.h>
>> + #include <dt-bindings/input/input.h>
>> + #include <dt-bindings/interrupt-controller/irq.h>
>> +
>> + i2c {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + cs40l26@58 {
>
> Generally using generic node names what we want, so something matching
> the class of device. Section 2.2.2 "Generic Names Recommendation" of the
> dt spec contains a bunch of ones to pick from, but I don't really know
> where "haptic amplifier" fits in!
>
> Cheers,
> Conor.
Understood. I’ll try to find a better way to name the node.
Best regards,
Fred
^ permalink raw reply
* Re: [PATCH RFC 5/5] mfd: cs40l26: Add CODEC driver component
From: Fred Treven @ 2023-05-26 21:23 UTC (permalink / raw)
To: Jeff LaBundy
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches@opensource.cirrus.com,
linux-input@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, lee@kernel.org
In-Reply-To: <ZHEL30a/YEia7kjr@nixie71>
Hi Jeff,
> On May 26, 2023, at 2:43 PM, Jeff LaBundy <jeff@labundy.com> wrote:
>
> Hi Fred,
>
> On Thu, May 25, 2023 at 07:04:31PM -0500, Fred Treven wrote:
>> Use MFD interface to load the CODEC driver along
>> with the Input FF driver.
>>
>> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
>> ---
>
> Did you mean to include a thin codec driver as part of this series to
> support the audio-to-haptics use-case? I don't see one.
That is the end-goal, but I wanted to submit a request for comment with just this patch to see if it was at all acceptable to add another device this way. I see now that it is not.
>
> As Lee correctly points out, this isn't an MFD despite the title of the
> commit message, and is sort of an abuse of the API.
Understood. Do you think it’s best to migrate the appropriate code to the MFD subsystem before submitting this initial patchset (which will include the codec driver) or would it be acceptable to make that change after this has gone in? My hope was to avoid having code being reviewed more than once if a significant portion is moved to MFD.
> What you seem to actually want is a true MFD driver responsible for
> initializing common resources such as regmap, an IRQ chip, etc. That
> driver then adds input and codec drivers as children.
>
> At the moment, you're more or less treating the input driver as the
> MFD with one child, but that is not the correct pattern.
Yeah that makes sense. Please advise on what the best way to continue would be: a. Drop this patch and migrate to MFD after the Input driver has been accepted or b. Move necessary code to MFD and add both Input and codec drivers from there along with the codec driver.
Thank you,
Fred
^ permalink raw reply
* Re: [PATCH RFC 5/5] mfd: cs40l26: Add CODEC driver component
From: Jeff LaBundy @ 2023-05-26 19:43 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel, lee
In-Reply-To: <1685059471-9598-5-git-send-email-fred.treven@cirrus.com>
Hi Fred,
On Thu, May 25, 2023 at 07:04:31PM -0500, Fred Treven wrote:
> Use MFD interface to load the CODEC driver along
> with the Input FF driver.
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
Did you mean to include a thin codec driver as part of this series to
support the audio-to-haptics use-case? I don't see one.
As Lee correctly points out, this isn't an MFD despite the title of the
commit message, and is sort of an abuse of the API.
What you seem to actually want is a true MFD driver responsible for
initializing common resources such as regmap, an IRQ chip, etc. That
driver then adds input and codec drivers as children.
At the moment, you're more or less treating the input driver as the
MFD with one child, but that is not the correct pattern.
> drivers/input/misc/cs40l26.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
> index 12c29cbd4ff0..35d15a6c2230 100644
> --- a/drivers/input/misc/cs40l26.c
> +++ b/drivers/input/misc/cs40l26.c
> @@ -13,6 +13,7 @@
> #include <linux/i2c.h>
> #include <linux/interrupt.h>
> #include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> #include <linux/pm_runtime.h>
> #include <linux/string.h>
> #include <linux/firmware/cirrus/wmfw.h>
> @@ -2136,6 +2137,10 @@ static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
> return 0;
> }
>
> +static const struct mfd_cell cs40l26_devs[] = {
> + { .name = "cs40l26-codec" },
> +};
> +
> static struct regulator_bulk_data cs40l26_supplies[] = {
> { .supply = "VP" },
> { .supply = "VA" },
> @@ -2275,6 +2280,12 @@ int cs40l26_probe(struct cs40l26_private *cs40l26)
> if (error)
> goto err;
>
> + error = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs, 1, NULL, 0, NULL);
> + if (error) {
> + dev_err(dev, "Failed to MFD add device %s: %d\n", cs40l26_devs[0].name, error);
> + goto err;
> + }
> +
> return 0;
> err:
> cs40l26_remove(cs40l26);
> --
> 2.7.4
>
Kind regards,
Jeff LaBundy
^ permalink raw reply
* Re: [PATCH 4/5] Input: cs40l26 - Load multiple coefficient files
From: Jeff LaBundy @ 2023-05-26 19:37 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel, lee
In-Reply-To: <1685059471-9598-4-git-send-email-fred.treven@cirrus.com>
Hi Fred,
On Thu, May 25, 2023 at 07:04:30PM -0500, Fred Treven wrote:
> Enable the driver to load all necessary coefficient tuning
> files: cs40l26.bin (wavetable)
> cs40l26-svc.bin (Sensorless Velocity Control)
> cs40l26-dvl.bin (Dynamic Voltage Limiter)
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
Thanks for sending; glad to see this series continue to move along. Just a
few notes about best practice for reviews:
1. For the purpose of mainline submission, patches 2 and 4 must be squashed,
and then come after their dependency (patch 3 in this case). Otherwise, we
end up reviewing code that subsequently disappears or changes in a subsequent
patch.
2. All patches in the same thread must have the same version number (e.g. v2).
If a patch was not present in a previous iteration, the change log can simply
say so. Speaking of which...
3. Please include a change log below the '---' so that it is clear what has
changed since the last review.
4. This is a matter of personal taste, but for a new driver that touches a
few different areas such as this, it's nice to have a cover letter to explain
the general layout and motivation. This is also a good place to put a change
log.
> drivers/input/misc/cs40l26.c | 31 +++++++++++++++++++++++--------
> include/linux/input/cs40l26.h | 3 ++-
> 2 files changed, 25 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
> index 1959438dfe31..12c29cbd4ff0 100644
> --- a/drivers/input/misc/cs40l26.c
> +++ b/drivers/input/misc/cs40l26.c
> @@ -2006,6 +2006,12 @@ static const struct cs_dsp_client_ops cs40l26_cs_dsp_client_ops = {
> .post_run = cs40l26_cs_dsp_post_run,
> };
>
> +static struct cs_dsp_coeff_desc cs40l26_coeffs[] = {
> + { .coeff_firmware = NULL, .coeff_filename = "cs40l26.bin", .optional = false },
> + { .coeff_firmware = NULL, .coeff_filename = "cs40l26-svc.bin", .optional = true },
> + { .coeff_firmware = NULL, .coeff_filename = "cs40l26-dvl.bin", .optional = true },
> +};
> +
> static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
> {
> int error;
> @@ -2035,14 +2041,16 @@ static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
> static void cs40l26_dsp_start(struct cs40l26_private *cs40l26)
> {
> struct device *dev = cs40l26->dev;
> + int i;
>
> if (cs40l26_dsp_pre_config(cs40l26))
> goto err_rls_fw;
>
> mutex_lock(&cs40l26->lock);
>
> - if (cs_dsp_power_up(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw",
> - cs40l26->bin, "cs40l26.bin", "cs40l26"))
> + if (cs_dsp_power_up_multiple(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw",
> + cs40l26_coeffs, CS40L26_NUM_COEFF_FILES,
> + "cs40l26"))
> goto err_mutex;
>
> if (cs40l26->dsp.fw_id != CS40L26_FW_ID) {
> @@ -2062,7 +2070,9 @@ static void cs40l26_dsp_start(struct cs40l26_private *cs40l26)
> mutex_unlock(&cs40l26->lock);
>
> err_rls_fw:
> - release_firmware(cs40l26->bin);
> + for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++)
> + release_firmware(cs40l26_coeffs[i].coeff_firmware);
> +
> release_firmware(cs40l26->wmfw);
>
> cs40l26_pm_runtime_setup(cs40l26);
> @@ -2074,17 +2084,20 @@ static void cs40l26_coeff_upload(const struct firmware *bin, void *context)
>
> if (!bin) {
> dev_err(cs40l26->dev, "Failed to request coefficient file\n");
> + cs40l26->ncoeffs++;
> return;
> }
>
> - cs40l26->bin = bin;
> + cs40l26_coeffs[cs40l26->ncoeffs++].coeff_firmware = bin;
>
> - cs40l26_dsp_start(cs40l26);
> + if (cs40l26->ncoeffs == CS40L26_NUM_COEFF_FILES)
> + cs40l26_dsp_start(cs40l26);
> }
>
> static void cs40l26_fw_upload(const struct firmware *wmfw, void *context)
> {
> struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context;
> + int i;
>
> if (!wmfw) {
> dev_err(cs40l26->dev, "Failed to request firmware file\n");
> @@ -2098,9 +2111,11 @@ static void cs40l26_fw_upload(const struct firmware *wmfw, void *context)
>
> cs40l26->wmfw = wmfw;
>
> - request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
> - "cs40l26.bin", cs40l26->dev, GFP_KERNEL,
> - cs40l26, cs40l26_coeff_upload);
> + for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++)
> + request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
> + "cs40l26.bin", cs40l26->dev,
> + GFP_KERNEL, cs40l26,
> + cs40l26_coeff_upload);
> }
>
> static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
> diff --git a/include/linux/input/cs40l26.h b/include/linux/input/cs40l26.h
> index bc0acec9cf23..2f74ef61b952 100644
> --- a/include/linux/input/cs40l26.h
> +++ b/include/linux/input/cs40l26.h
> @@ -231,6 +231,7 @@
> /* Firmware Handling */
> #define CS40L26_FW_ID 0x1800D4
> #define CS40L26_FW_ID_VERSION_MIN 0x070237
> +#define CS40L26_NUM_COEFF_FILES 3
>
> /* Algorithms */
> #define CS40L26_BUZZGEN_ALGO_ID 0x0004F202
> @@ -473,8 +474,8 @@ struct cs40l26_private {
> unsigned int vbst_uv;
> bool exploratory_mode_enabled;
> const struct firmware *wmfw;
> - const struct firmware *bin;
> bool dsp_initialized;
> + int ncoeffs;
> };
>
> int cs40l26_probe(struct cs40l26_private *cs40l26);
> --
> 2.7.4
>
Kind regards,
Jeff LaBundy
^ permalink raw reply
* Re: [PATCH 0/9] drm/panel and i2c-hid: Allow panels and touchscreens to power sequence together
From: Konrad Dybcio @ 2023-05-26 19:29 UTC (permalink / raw)
To: Douglas Anderson, Jiri Kosina, Benjamin Tissoires,
Bjorn Andersson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Sam Ravnborg, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: dri-devel, linux-input, Dmitry Torokhov, hsinyi, devicetree,
yangcong5, linux-kernel, Daniel Vetter, linux-arm-msm,
cros-qcom-dts-watchers
In-Reply-To: <20230523193017.4109557-1-dianders@chromium.org>
On 23.05.2023 21:27, Douglas Anderson wrote:
>
> The big motivation for this patch series is mostly described in the patch
> ("drm/panel: Add a way for other devices to follow panel state"), but to
> quickly summarize here: for touchscreens that are connected to a panel we
> need the ability to power sequence the two device together. This is not a
> new need, but so far we've managed to get by through a combination of
> inefficiency, added costs, or perhaps just a little bit of brokenness.
> It's time to do better. This patch series allows us to do better.
Panels with integrated touchscreens have been shipping in mainstream devices
since at least 2016. Thanks a lot for looking into this!
Konrad
>
> Assuming that people think this patch series looks OK, we'll have to
> figure out the right way to land it. The panel patches and i2c-hid
> patches will go through very different trees and so either we'll need
> an Ack from one side or the other or someone to create a tag for the
> other tree to pull in. This will _probably_ require the true drm-misc
> maintainers to get involved, not a lowly committer. ;-)
>
>
> Douglas Anderson (9):
> dt-bindings: HID: i2c-hid: Add "panel" property to i2c-hid backed
> panels
> drm/panel: Check for already prepared/enabled in drm_panel
> drm/panel: Add a way for other devices to follow panel state
> HID: i2c-hid: Switch to SYSTEM_SLEEP_PM_OPS()
> HID: i2c-hid: Rearrange probe() to power things up later
> HID: i2c-hid: Make suspend and resume into helper functions
> HID: i2c-hid: Support being a panel follower
> HID: i2c-hid: Do panel follower work on the system_wq
> arm64: dts: qcom: sc7180: Link trogdor touchscreens to the panels
>
> .../bindings/input/elan,ekth6915.yaml | 6 +
> .../bindings/input/goodix,gt7375p.yaml | 6 +
> .../bindings/input/hid-over-i2c.yaml | 6 +
> .../boot/dts/qcom/sc7180-trogdor-coachz.dtsi | 1 +
> .../dts/qcom/sc7180-trogdor-homestar.dtsi | 1 +
> .../boot/dts/qcom/sc7180-trogdor-lazor.dtsi | 1 +
> .../boot/dts/qcom/sc7180-trogdor-pompom.dtsi | 1 +
> .../qcom/sc7180-trogdor-quackingstick.dtsi | 1 +
> .../dts/qcom/sc7180-trogdor-wormdingler.dtsi | 1 +
> drivers/gpu/drm/drm_panel.c | 194 +++++++++-
> drivers/hid/i2c-hid/i2c-hid-core.c | 330 +++++++++++++-----
> include/drm/drm_panel.h | 89 +++++
> 12 files changed, 542 insertions(+), 95 deletions(-)
>
^ permalink raw reply
* Re: [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
From: Conor Dooley @ 2023-05-26 19:27 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel, lee
In-Reply-To: <1685059471-9598-1-git-send-email-fred.treven@cirrus.com>
[-- Attachment #1: Type: text/plain, Size: 6730 bytes --]
Yo Fred,
Tooling does not like your series very much, prob the missing v2's on
some patches:
Grabbing thread from lore.kernel.org/all/1685059471-9598-1-git-send-email-fred.treven%40cirrus.com/t.mbox.gz
Checking for newer revisions
Grabbing search results from lore.kernel.org
Analyzing 6 messages in the thread
Will use the latest revision: v2
You can pick other revisions using the -vN flag
Checking attestation on all messages, may take a moment...
---
✓ [PATCH v2 1/5] dt-bindings: input: cirrus,cs40l26: Support for CS40L26
✓ Signed: DKIM/cirrus.com
+ Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
✓ [PATCH v2 2/5] Input: cs40l26 - Support for CS40L26 Haptic Device
✓ Signed: DKIM/cirrus.com
+ Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
ERROR: missing [3/5]!
ERROR: missing [4/5]!
ERROR: missing [5/5]!
On Thu, May 25, 2023 at 07:04:27PM -0500, Fred Treven wrote:
> Introduce required basic devicetree parameters for the
> initial commit of CS40L26.
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
> .../devicetree/bindings/input/cirrus,cs40l26.yaml | 102 +++++++++++++++++++++
> MAINTAINERS | 10 ++
> 2 files changed, 112 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
>
> diff --git a/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml b/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
> new file mode 100644
> index 000000000000..9cbc964ebded
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
> @@ -0,0 +1,102 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/cirrus,cs40l26.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Cirrus Logic CS40L26 Boosted Haptic Amplifier
> +
> +maintainers:
> + - Fred Treven <fred.treven@cirrus.com>
> +
> +description:
> + CS40L26 is a Boosted Haptic Driver with Integrated DSP and Waveform Memory
> + with Advanced Closed Loop Algorithms and LRA protection
> +
> +properties:
> + compatible:
> + enum:
> + - cirrus,cs40l26a
> + - cirrus,cs40l26b
> + - cirrus,cs40l27a
> + - cirrus,cs40l27b
I had a _brief_ look at the driver - you don't seem to have any match
data, so are all of these devices actually compatible with eachother?
IOW, should this be:
properties:
compatible:
oneOf:
- items:
- enum:
- cirrus,cs40l26b
- cirrus,cs40l27a
- cirrus,cs40l27b
- const: cirrus,cs40l26a
- const: cirrus,cs40l26a
And then drop all but the cs40l26a in the driver? Apologies if I missed
some difference in there.
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> +
> + VA-supply:
> + description: Regulator for VA analog voltage
> +
> + VP-supply:
> + description: Regulator for VP peak voltage
> +
> + cirrus,bst-ipk-microamp:
Are these namings ripped from a datasheet? "bst-ipk" doesn't immediately
mean anything to me, but I am not familiar with these devices.
> + description:
> + Maximum amount of current that can be drawn by the device's boost converter.
> + multipleOf: 50000
> + minimum: 1600000
> + maximum: 4800000
> + default: 4500000
> +
> + cirrus,bst-ctl-microvolt:
Ditto here. If there aren't rips, then maybe it'd be a good idea to use
full words.
> + description: Maximum target voltage to which DSP may increase the VBST supply.
> + multipleOf: 50000
> + minimum: 2550000
> + maximum: 11000000
> + default: 11000000
> +
> + cirrus,bst-exploratory-mode-disable:
This one is a lot better ;)
> + description:
> + Disable boost exploratory mode.
> +
> + In exploratory mode the analog maximum peak current limit of 4.5 A
> + (tolerance of + 160 mA) will be applied. This is required for the
> + device to successfully detect a boost inductor short.
> +
> + Boost exploratory mode allows the device to overshoot the set boost peak
> + current limit (i.e. if current peak limit is set to 2.5 A to protect the
> + battery inductor, the current limit will be opened up to 4.5 A for
> + several milliseconds at boost startup).
> + This has potential to damage the boost inductor.
> +
> + Disabling this mode will prevent this from happening; it will also
> + prevent the device from detecting boost inductor short errors.
> + type: boolean
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - reset-gpios
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/input/input.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + cs40l26@58 {
Generally using generic node names what we want, so something matching
the class of device. Section 2.2.2 "Generic Names Recommendation" of the
dt spec contains a bunch of ones to pick from, but I don't really know
where "haptic amplifier" fits in!
Cheers,
Conor.
> + compatible = "cirrus,cs40l26a";
> + reg = <0x58>;
> + interrupt-parent = <&gpio>;
> + interrupts = <57 IRQ_TYPE_LEVEL_LOW>;
> + reset-gpios = <&gpio 54 GPIO_ACTIVE_LOW>;
> + VA-supply = <&dummy_vreg>;
> + VP-supply = <&dummy_vreg>;
> + cirrus,bst-ctl-microvolt = <2600000>;
> + cirrus,bst-ipk-microamp = <1650000>;
> + cirrus,bst-exploratory-mode-disable;
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2b073facf399..d72ed4957b0b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4926,6 +4926,16 @@ L: netdev@vger.kernel.org
> S: Maintained
> F: drivers/net/ethernet/cirrus/ep93xx_eth.c
>
> +CIRRUS LOGIC HAPTICS DRIVER
> +M: Fred Treven <fred.treven@cirrus.com>
> +M: Ben Bright <ben.bright@cirrus.com>
> +M: James Ogletree <james.ogletree@cirrus.com>
> +L: patches@opensource.cirrus.com
> +S: Supported
> +W: https://github.com/CirrusLogic/linux-drivers/wiki
> +T: git https://github.com/CirrusLogic/linux-drivers.git
> +F: Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
> +
> CIRRUS LOGIC LOCHNAGAR DRIVER
> M: Charles Keepax <ckeepax@opensource.cirrus.com>
> M: Richard Fitzgerald <rf@opensource.cirrus.com>
> --
> 2.7.4
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 0/2] HID: ensure timely release of driver resources
From: Jiri Kosina @ 2023-05-26 18:46 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Benjamin Tissoires, linux-input, linux-kernel, Stephen Boyd
In-Reply-To: <20230523-hid-devres-v2-0-5220211a428c@gmail.com>
On Tue, 23 May 2023, Dmitry Torokhov wrote:
> These 2 patches address an issue in HID subsystem where cleanup code in
> the bus code runs before releasing devres-managed resources by the
> driver core.
>
> This is a V2 of the patch, which was split in 2 parts - generic cleanup
> and the actualy change addressing the issue.
>
> Link: https://lore.kernel.org/r/20230505232417.1377393-1-swboyd@chromium.org
> Link: https://lore.kernel.org/r/ZFWarGkRAfPOmI6E@google.com
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
> Dmitry Torokhov (2):
> HID: split apart hid_device_probe to make logic more apparent
> HID: ensure timely release of driver-allocated resources
>
> drivers/hid/hid-core.c | 118 +++++++++++++++++++++++++++++--------------------
> include/linux/hid.h | 1 +
> 2 files changed, 72 insertions(+), 47 deletions(-)
Now queued in hid.git#for-6.5/core.
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [regression] Since kernel 6.3.1 logitech unify receiver not working properly
From: Jiri Kosina @ 2023-05-26 18:41 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Linus Torvalds, Linux regressions mailing list, Bagas Sanjaya,
Filipe Laíns, Bastien Nocera, open list:HID CORE LAYER, LKML,
guy.b
In-Reply-To: <nycvar.YFH.7.76.2305251308471.29760@cbobk.fhfr.pm>
On Thu, 25 May 2023, Jiri Kosina wrote:
> > > > That bug is pre-existing (ie the problem was not introduced by that
> > > > commit), but who knows if the retry makes things worse (ie if it then
> > > > triggers on a retry, the response data will be the *previous* response).
> > > >
> > > > The whole "goto exit" games should be removed too, because we're in a
> > > > for-loop, and instead of "goto exit" it should just do "break".
> > > >
> > > > IOW, something like this might be worth testing.
> > > >
> > > > That said, while I think the code is buggy, I doubt this is the actual
> > > > cause of the problem people are reporting. But it would be lovely to
> > > > hear if the attached patch makes any difference, and I think this is
> > > > fixing a real - but unlikely - problem anyway.
> >
> > FWIW, Linus, your patch is
> > Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> >
> > Feel free to submit it to us or to apply it directly if you prefer as
> > this is clearly a fix for a code path issue.
>
> It would be nice to hear from the people who were able to reproduce the
> issue whether this makes any observable difference in behavior though. I
> don't currently think it would, as it fixes a potential NULL pointer
> dereference, which is not what has been reported.
>
> Has anyone of the affected people tried to bisect the issue?
Could anyone who is able to reproduce the issue please check whether
reverting
586e8fede7953b16 ("HID: logitech-hidpp: Retry commands when device is busy")
has any observable effect?
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] Input: Switch i2c drivers back to use .probe()
From: Uwe Kleine-König @ 2023-05-26 15:56 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: kernel, linux-input
In-Reply-To: <ZGUImQNUL9uNNHmO@google.com>
[-- Attachment #1: Type: text/plain, Size: 1501 bytes --]
Hello Dmitry,
On Wed, May 17, 2023 at 10:02:17AM -0700, Dmitry Torokhov wrote:
> On Wed, May 17, 2023 at 06:46:45PM +0200, Uwe Kleine-König wrote:
> > After commit b8a1a4cd5a98 ("i2c: Provide a temporary .probe_new()
> > call-back type"), all drivers being converted to .probe_new() and then
> > 03c835f498b5 ("i2c: Switch .probe() to not take an id parameter") convert
> > back to (the new) .probe() to be able to eventually drop .probe_new() from
> > struct i2c_driver.
> >
> > Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> > ---
> > Hello,
> >
> > this patch was generated using coccinelle, but I aligned the result to
> > the per-file indention. Other than that it's just s/probe_new/probe/.
> >
> > I used v6.4-rc1 as base for it, there are a few patches in next that
> > conflict slightly with it, but for me git can sort it all out.
> > Feel free to just drop all conflicting hunks if it doesn't go so smooth
> > for you, I'll care about the fallout later.
>
> I think I saw only 1 clash in cyttsp5 driver, and I resolved it.
> Hopefully I did not miss anything else.
>
> This is now applied for the next merge window.
FTR: Looking at commit d8bde56dfd86 ("Input: Switch i2c drivers back to
use .probe()") which is in next-20230525, everything seems to be right.
Thanks
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* [PATCH] selftests/input: Introduce basic tests for evdev ioctls
From: eballetbo @ 2023-05-26 8:22 UTC (permalink / raw)
To: linux-kselftest, linux-kernel
Cc: Shuah Khan, Dmitry Torokhov, Javier Martinez Canillas,
Dana Elfassy, linux-input, phuttere, Benjamin Tissoires,
Enric Balletbo i Serra
From: Enric Balletbo i Serra <eballetbo@kernel.org>
This provides a basic infrastructure for the creation of tests for the evdev
interface. Most of this is code is adapted from the libevdev wrapper
library. While most of evdev ioctls are covered and tested using
libevdev tests there are some evdev ioctls that aren't because are not
supported (and will not be supported) by libevdev. So, adding, at least
those tests, would make sense.
The test creates an uinput device (and an evdev device) so you can
call the wanted ioctl from userspace. So, to run those tests you need
to have support for uinput and evdev as well.
Signed-off-by: Enric Balletbo i Serra <eballetbo@kernel.org>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/input/.gitignore | 2 +
tools/testing/selftests/input/Makefile | 5 +
tools/testing/selftests/input/config | 3 +
tools/testing/selftests/input/evioc-test.c | 282 +++++++++++++++++++++
5 files changed, 293 insertions(+)
create mode 100644 tools/testing/selftests/input/.gitignore
create mode 100644 tools/testing/selftests/input/Makefile
create mode 100644 tools/testing/selftests/input/config
create mode 100644 tools/testing/selftests/input/evioc-test.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 90a62cf75008..29fc77168aa7 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -28,6 +28,7 @@ TARGETS += futex
TARGETS += gpio
TARGETS += hid
TARGETS += intel_pstate
+TARGETS += input
TARGETS += iommu
TARGETS += ipc
TARGETS += ir
diff --git a/tools/testing/selftests/input/.gitignore b/tools/testing/selftests/input/.gitignore
new file mode 100644
index 000000000000..37f5dff3255b
--- /dev/null
+++ b/tools/testing/selftests/input/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+evioc-test
diff --git a/tools/testing/selftests/input/Makefile b/tools/testing/selftests/input/Makefile
new file mode 100644
index 000000000000..031729be0628
--- /dev/null
+++ b/tools/testing/selftests/input/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -D_GNU_SOURCE -std=gnu99 -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
+
+TEST_GEN_PROGS := evioc-test
+include ../lib.mk
diff --git a/tools/testing/selftests/input/config b/tools/testing/selftests/input/config
new file mode 100644
index 000000000000..b7512f3e6d8d
--- /dev/null
+++ b/tools/testing/selftests/input/config
@@ -0,0 +1,3 @@
+CONFIG_INPUT=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_UINPUT=m
diff --git a/tools/testing/selftests/input/evioc-test.c b/tools/testing/selftests/input/evioc-test.c
new file mode 100644
index 000000000000..4c0c8ebed378
--- /dev/null
+++ b/tools/testing/selftests/input/evioc-test.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2023 Red Hat, Inc.
+ *
+ * Part of the code in this file is inspired and copied from the libevdev wrapper library
+ * for evdev devices written by Peter Hutterer.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uinput.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define TEST_DEVICE_NAME "selftest input device"
+
+struct selftest_uinput {
+ int uinput_fd; /** file descriptor to uinput */
+ int evdev_fd; /** file descriptor to evdev */
+ char *name; /** device name */
+ char *syspath; /** /sys path */
+ char *devnode; /** device node */
+};
+
+static int is_event_device(const struct dirent *dent)
+{
+ return strncmp("event", dent->d_name, 5) == 0;
+}
+
+static char *fetch_device_node(const char *path)
+{
+ struct dirent **namelist;
+ char *devnode = NULL;
+ int ndev, i;
+
+ ndev = scandir(path, &namelist, is_event_device, alphasort);
+ if (ndev <= 0)
+ return NULL;
+
+ /* ndev should only ever be 1 */
+
+ for (i = 0; i < ndev; i++) {
+ if (!devnode && asprintf(&devnode, "/dev/input/%s",
+ namelist[i]->d_name) == -1)
+ devnode = NULL;
+ free(namelist[i]);
+ }
+
+ free(namelist);
+
+ return devnode;
+}
+
+static int is_input_device(const struct dirent *dent)
+{
+ return strncmp("input", dent->d_name, 5) == 0;
+}
+
+/*
+ * Device node is guessed based on the sysfs path, the sysfs path contains a
+ * eventN file, that corresponds to our /dev/input/eventN number.
+ */
+static int fetch_syspath_and_devnode(struct selftest_uinput *uidev)
+{
+#define SYS_INPUT_DIR "/sys/devices/virtual/input/"
+ struct dirent **namelist;
+ int ndev, i;
+ int rc;
+ char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR;
+
+ rc = ioctl(uidev->uinput_fd,
+ UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)),
+ &buf[strlen(SYS_INPUT_DIR)]);
+ if (rc != -1) {
+ uidev->syspath = strdup(buf);
+ uidev->devnode = fetch_device_node(buf);
+ return 0;
+ }
+
+ ndev = scandir(SYS_INPUT_DIR, &namelist, is_input_device, alphasort);
+ if (ndev <= 0)
+ return -1;
+
+ for (i = 0; i < ndev; i++) {
+ int fd, len;
+
+ rc = snprintf(buf, sizeof(buf), "%s%s/name", SYS_INPUT_DIR,
+ namelist[i]->d_name);
+ if (rc < 0 || (size_t)rc >= sizeof(buf))
+ continue;
+
+ /* created within time frame */
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ continue;
+
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (len <= 0)
+ continue;
+
+ buf[len - 1] = '\0'; /* file contains \n */
+ if (strcmp(buf, uidev->name) == 0) {
+ if (uidev->syspath) {
+ fprintf(stderr,
+ "multiple identical devices found. syspath is unreliable\n");
+ break;
+ }
+
+ rc = snprintf(buf, sizeof(buf), "%s%s", SYS_INPUT_DIR,
+ namelist[i]->d_name);
+
+ if (rc < 0 || (size_t)rc >= sizeof(buf)) {
+ fprintf(stderr,
+ "Invalid syspath, syspath is unreliable\n");
+ break;
+ }
+
+ uidev->syspath = strdup(buf);
+ uidev->devnode = fetch_device_node(buf);
+ }
+ }
+
+ for (i = 0; i < ndev; i++)
+ free(namelist[i]);
+ free(namelist);
+
+ return uidev->devnode ? 0 : -1;
+#undef SYS_INPUT_DIR
+}
+
+void selftest_uinput_destroy(struct selftest_uinput *uidev)
+{
+ if (!uidev)
+ return;
+
+ if (uidev->uinput_fd >= 0)
+ ioctl(uidev->uinput_fd, UI_DEV_DESTROY, NULL);
+
+ close(uidev->evdev_fd);
+ close(uidev->uinput_fd);
+
+ free(uidev->syspath);
+ free(uidev->devnode);
+ free(uidev->name);
+ free(uidev);
+}
+
+int selftest_uinput_create_device(struct selftest_uinput **uidev, ...)
+{
+ struct selftest_uinput *new_device;
+ struct uinput_setup setup;
+ va_list args;
+ int rc, fd;
+ int type;
+
+ new_device = calloc(1, sizeof(struct selftest_uinput));
+ if (!new_device)
+ return -ENOMEM;
+
+ memset(&setup, 0, sizeof(setup));
+ strncpy(setup.name, TEST_DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
+ setup.id.vendor = 0x1234; /* sample vendor */
+ setup.id.product = 0x5678; /* sample product */
+ setup.id.bustype = BUS_USB;
+
+ fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
+ goto error;
+ }
+
+ va_start(args, uidev);
+ rc = 0;
+ do {
+ type = va_arg(args, int);
+ if (type == -1)
+ break;
+ rc = ioctl(fd, UI_SET_EVBIT, type);
+ } while (rc == 0);
+ va_end(args);
+
+ rc = ioctl(fd, UI_DEV_SETUP, &setup);
+ if (rc == -1)
+ goto error;
+
+ rc = ioctl(fd, UI_DEV_CREATE, NULL);
+ if (rc == -1)
+ goto error;
+
+ new_device->name = strdup(TEST_DEVICE_NAME);
+ new_device->uinput_fd = fd;
+
+ if (fetch_syspath_and_devnode(new_device) == -1) {
+ fprintf(stderr, "unable to fetch syspath or device node.\n");
+ errno = ENODEV;
+ goto error;
+ }
+
+ fd = open(new_device->devnode, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
+ goto error;
+ }
+ new_device->evdev_fd = fd;
+
+ *uidev = new_device;
+
+ return 0;
+
+error:
+ rc = -errno;
+ selftest_uinput_destroy(new_device);
+ return rc;
+}
+
+const char *selftest_uinput_get_devnode(struct selftest_uinput *uidev)
+{
+ return uidev->devnode;
+}
+
+TEST(eviocgname_get_device_name)
+{
+ struct selftest_uinput *uidev;
+ char buf[256];
+ int rc;
+
+ rc = selftest_uinput_create_device(&uidev);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ memset(buf, 0, sizeof(buf));
+ /* ioctl to get the name */
+ rc = ioctl(uidev->evdev_fd, EVIOCGNAME(sizeof(buf) - 1), buf);
+ ASSERT_GE(rc, 0);
+ ASSERT_STREQ(TEST_DEVICE_NAME, buf);
+
+ selftest_uinput_destroy(uidev);
+}
+
+TEST(eviocgrep_get_repeat_settings)
+{
+ struct selftest_uinput *uidev;
+ int rep_values[2];
+ int rc;
+
+ memset(rep_values, 0, sizeof(rep_values));
+
+ rc = selftest_uinput_create_device(&uidev);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to get the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
+ /* should fail because EV_REP is not set */
+ ASSERT_EQ(-1, rc);
+
+ selftest_uinput_destroy(uidev);
+
+ rc = selftest_uinput_create_device(&uidev, EV_REP);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to get the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+ ASSERT_EQ(0, rc);
+ /* should get the default delay an period values set by the kernel */
+ ASSERT_EQ(rep_values[0], 250);
+ ASSERT_EQ(rep_values[1], 33);
+
+ selftest_uinput_destroy(uidev);
+}
+
+TEST_HARNESS_MAIN
--
2.40.1
^ permalink raw reply related
* Re: [PATCH RFC 5/5] mfd: cs40l26: Add CODEC driver component
From: Lee Jones @ 2023-05-26 7:54 UTC (permalink / raw)
To: Fred Treven
Cc: Ben Bright, James Ogletree, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel
In-Reply-To: <1685059471-9598-5-git-send-email-fred.treven@cirrus.com>
On Thu, 25 May 2023, Fred Treven wrote:
> Use MFD interface to load the CODEC driver along
> with the Input FF driver.
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
> drivers/input/misc/cs40l26.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
> index 12c29cbd4ff0..35d15a6c2230 100644
> --- a/drivers/input/misc/cs40l26.c
> +++ b/drivers/input/misc/cs40l26.c
> @@ -13,6 +13,7 @@
> #include <linux/i2c.h>
> #include <linux/interrupt.h>
> #include <linux/kernel.h>
> +#include <linux/mfd/core.h>
NACK. Please do not use the MFD API outside of drivers/mfd.
> #include <linux/pm_runtime.h>
> #include <linux/string.h>
> #include <linux/firmware/cirrus/wmfw.h>
> @@ -2136,6 +2137,10 @@ static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
> return 0;
> }
>
> +static const struct mfd_cell cs40l26_devs[] = {
> + { .name = "cs40l26-codec" },
This is one device. Thus, not an MFD anyway.
> +};
> +
> static struct regulator_bulk_data cs40l26_supplies[] = {
> { .supply = "VP" },
> { .supply = "VA" },
> @@ -2275,6 +2280,12 @@ int cs40l26_probe(struct cs40l26_private *cs40l26)
> if (error)
> goto err;
>
> + error = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, cs40l26_devs, 1, NULL, 0, NULL);
> + if (error) {
> + dev_err(dev, "Failed to MFD add device %s: %d\n", cs40l26_devs[0].name, error);
> + goto err;
> + }
> +
> return 0;
> err:
> cs40l26_remove(cs40l26);
> --
> 2.7.4
>
--
Lee Jones [李琼斯]
^ permalink raw reply
* [PATCH v2 2/5] Input: cs40l26 - Support for CS40L26 Haptic Device
From: Fred Treven @ 2023-05-26 0:04 UTC (permalink / raw)
To: Fred Treven, Ben Bright, James Ogletree, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel
Cc: lee
In-Reply-To: <1685059471-9598-1-git-send-email-fred.treven@cirrus.com>
Introduce support for Cirrus Logic device CS40L26:
a boosted haptics driver with integrated DSP and
waveform memory with advanced closed loop algorithms
and LRA protection.
Signed-off-by: Fred Treven <fred.treven@cirrus.com>
---
MAINTAINERS | 2 +
drivers/input/misc/Kconfig | 30 +
drivers/input/misc/Makefile | 3 +
drivers/input/misc/cs40l26-i2c.c | 76 ++
drivers/input/misc/cs40l26-spi.c | 77 ++
drivers/input/misc/cs40l26.c | 2385 ++++++++++++++++++++++++++++++++++++++
include/linux/input/cs40l26.h | 486 ++++++++
7 files changed, 3059 insertions(+)
create mode 100644 drivers/input/misc/cs40l26-i2c.c
create mode 100644 drivers/input/misc/cs40l26-spi.c
create mode 100644 drivers/input/misc/cs40l26.c
create mode 100644 include/linux/input/cs40l26.h
diff --git a/MAINTAINERS b/MAINTAINERS
index d72ed4957b0b..d4e51e6634c9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4935,6 +4935,8 @@ S: Supported
W: https://github.com/CirrusLogic/linux-drivers/wiki
T: git https://github.com/CirrusLogic/linux-drivers.git
F: Documentation/devicetree/bindings/input/cirrus,cs40l26.yaml
+F: drivers/input/misc/cs40l*
+F: include/linux/input/cs40l*
CIRRUS LOGIC LOCHNAGAR DRIVER
M: Charles Keepax <ckeepax@opensource.cirrus.com>
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 81a54a59e13c..88e585897ad7 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -140,6 +140,36 @@ config INPUT_BMA150
To compile this driver as a module, choose M here: the
module will be called bma150.
+config INPUT_CS40L26
+ tristate "Cirrus Logic CS40L26 Haptic Amplifier support"
+ select CS_DSP
+ help
+ Say Y here to enable support for CS40L26 boosted haptic
+ amplifier.
+
+ To compile the driver as a module choose M here: the module
+ will be called cs40l26_core.
+
+config INPUT_CS40L26_I2C
+ tristate "Support I2C bus connection"
+ depends on INPUT_CS40L26 && I2C
+ select REGMAP_I2C
+ help
+ Say Y if you have CS40L26 connected to an I2C bus.
+
+ To compile the driver as a module choose M here: the
+ module will be called cs40l26_i2c.
+
+config INPUT_CS40L26_SPI
+ tristate "Support SPI bus connection"
+ depends on INPUT_CS40L26 && SPI
+ select REGMAP_SPI
+ help
+ Say Y if you have CS40L26 connected to a SPI bus.
+
+ To compile the driver as a module choose M here: the
+ module will be called cs40l26_spi.
+
config INPUT_E3X0_BUTTON
tristate "NI Ettus Research USRP E3xx Button support."
default n
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 04296a4abe8e..97d2245fdd6a 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -28,6 +28,9 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_CS40L26) += cs40l26.o
+obj-$(CONFIG_INPUT_CS40L26_I2C) += cs40l26-i2c.o
+obj-$(CONFIG_INPUT_CS40L26_SPI) += cs40l26-spi.o
obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
diff --git a/drivers/input/misc/cs40l26-i2c.c b/drivers/input/misc/cs40l26-i2c.c
new file mode 100644
index 000000000000..7be2d7731895
--- /dev/null
+++ b/drivers/input/misc/cs40l26-i2c.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/input/cs40l26.h>
+
+static const struct i2c_device_id cs40l26_id_i2c[] = {
+ { "cs40l26a", 0 },
+ { "cs40l26b", 1 },
+ { "cs40l27a", 2 },
+ { "cs40l27b", 3 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs40l26_id_i2c);
+
+static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { .compatible = "cirrus,cs40l27a" },
+ { .compatible = "cirrus,cs40l27b" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
+
+static int cs40l26_i2c_probe_new(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct cs40l26_private *cs40l26;
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_i2c(client, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap))
+ return dev_err_probe(dev, PTR_ERR(cs40l26->regmap),
+ "Failed to allocate register map\n");
+
+ cs40l26->dev = dev;
+ cs40l26->irq = client->irq;
+
+ return cs40l26_probe(cs40l26);
+}
+
+static void cs40l26_i2c_remove(struct i2c_client *client)
+{
+ struct cs40l26_private *cs40l26 = i2c_get_clientdata(client);
+
+ cs40l26_remove(cs40l26);
+}
+
+static struct i2c_driver cs40l26_i2c_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = pm_sleep_ptr(&cs40l26_pm_ops),
+ },
+ .id_table = cs40l26_id_i2c,
+ .probe_new = cs40l26_i2c_probe_new,
+ .remove = cs40l26_i2c_remove,
+};
+
+module_i2c_driver(cs40l26_i2c_driver);
+
+MODULE_DESCRIPTION("CS40L26 I2C Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cs40l26-spi.c b/drivers/input/misc/cs40l26-spi.c
new file mode 100644
index 000000000000..c43f4b3a5ea2
--- /dev/null
+++ b/drivers/input/misc/cs40l26-spi.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/spi/spi.h>
+#include <linux/input/cs40l26.h>
+
+static const struct spi_device_id cs40l26_id_spi[] = {
+ { "cs40l26a", 0 },
+ { "cs40l26b", 1 },
+ { "cs40l27a", 2 },
+ { "cs40l27b", 3 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cs40l26_id_spi);
+
+static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { .compatible = "cirrus,cs40l27a" },
+ { .compatible = "cirrus,cs40l27b" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
+
+static int cs40l26_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct cs40l26_private *cs40l26;
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_spi(spi, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap))
+ return dev_err_probe(dev, PTR_ERR(cs40l26->regmap),
+ "Failed to allocate register map\n");
+
+ cs40l26->dev = dev;
+ cs40l26->irq = spi->irq;
+
+ return cs40l26_probe(cs40l26);
+}
+
+static void cs40l26_spi_remove(struct spi_device *spi)
+{
+ struct cs40l26_private *cs40l26 = spi_get_drvdata(spi);
+
+ cs40l26_remove(cs40l26);
+}
+
+static struct spi_driver cs40l26_spi_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = pm_sleep_ptr(&cs40l26_pm_ops),
+ },
+
+ .id_table = cs40l26_id_spi,
+ .probe = cs40l26_spi_probe,
+ .remove = cs40l26_spi_remove,
+};
+
+module_spi_driver(cs40l26_spi_driver);
+
+MODULE_DESCRIPTION("CS40L26 SPI Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
new file mode 100644
index 000000000000..1959438dfe31
--- /dev/null
+++ b/drivers/input/misc/cs40l26.c
@@ -0,0 +1,2385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/firmware.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/string.h>
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/input/cs40l26.h>
+
+static bool cs40l26_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS40L26_DEVID ... CS40L26_REVID:
+ case CS40L26_TEST_KEY_CTRL:
+ case CS40L26_GLOBAL_ENABLES:
+ case CS40L26_ERROR_RELEASE:
+ case CS40L26_PWRMGT_CTL ... CS40L26_PWRMGT_STS:
+ case CS40L26_REFCLK_INPUT:
+ case CS40L26_PLL_REFCLK_DETECT_0:
+ case CS40L26_VBST_CTL_1 ... CS40L26_BST_IPK_CTL:
+ case CS40L26_TEST_LBST:
+ case CS40L26_NGATE1_INPUT:
+ case CS40L26_DAC_MSM_CONFIG ... CS40L26_TST_DAC_MSM_CONFIG:
+ case CS40L26_IRQ1_STATUS:
+ case CS40L26_IRQ1_EINT_1 ... CS40L26_IRQ1_EINT_5:
+ case CS40L26_IRQ1_STS_1 ... CS40L26_IRQ1_STS_5:
+ case CS40L26_IRQ1_MASK_1 ... CS40L26_IRQ1_MASK_5:
+ case CS40L26_MIXER_NGATE_CH1_CFG:
+ case CS40L26_DSP_MBOX_1 ... CS40L26_DSP_VIRTUAL2_MBOX_8:
+ case CS40L26_OTP_MEM0 ... CS40L26_OTP_MEM31:
+ case CS40L26_DSP1_XMEM_PACKED_0 ... CS40L26_DSP1_XMEM_PACKED_6143:
+ case CS40L26_DSP1_XROM_PACKED_0 ... CS40L26_DSP1_XROM_PACKED_4604:
+ case CS40L26_DSP1_XMEM_UNPACKED32_0 ... CS40L26_DSP1_XROM_UNPACKED32_3070:
+ case CS40L26_DSP1_SYS_INFO_ID:
+ case CS40L26_DSP1_XMEM_UNPACKED24_0 ... CS40L26_DSP1_XMEM_UNPACKED24_8191:
+ case CS40L26_DSP1_XROM_UNPACKED24_0 ... CS40L26_DSP1_XROM_UNPACKED24_6141:
+ case CS40L26_DSP1_SCRATCH1:
+ case CS40L26_DSP1_SCRATCH2:
+ case CS40L26_DSP1_SCRATCH3:
+ case CS40L26_DSP1_SCRATCH4:
+ case CS40L26_DSP1_CCM_CORE_CONTROL:
+ case CS40L26_DSP1_YMEM_PACKED_0 ... CS40L26_DSP1_YMEM_PACKED_1532:
+ case CS40L26_DSP1_YMEM_UNPACKED32_0 ... CS40L26_DSP1_YMEM_UNPACKED32_1022:
+ case CS40L26_DSP1_YMEM_UNPACKED24_0 ... CS40L26_DSP1_YMEM_UNPACKED24_2045:
+ case CS40L26_DSP1_PMEM_0 ... CS40L26_DSP1_PMEM_5114:
+ case CS40L26_DSP1_PROM_0 ... CS40L26_DSP1_PROM_30714:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const struct regmap_config cs40l26_regmap = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS40L26_LASTREG,
+ .num_reg_defaults = 0,
+ .readable_reg = cs40l26_readable_reg,
+ .cache_type = REGCACHE_NONE,
+};
+EXPORT_SYMBOL_GPL(cs40l26_regmap);
+
+static const struct cs_dsp_region cs40l26_dsp_regions[] = {
+ {
+ .type = WMFW_HALO_PM_PACKED,
+ .base = CS40L26_DSP1_PMEM_0,
+ },
+ {
+ .type = WMFW_HALO_XM_PACKED,
+ .base = CS40L26_DSP1_XMEM_PACKED_0,
+ },
+ {
+ .type = WMFW_HALO_YM_PACKED,
+ .base = CS40L26_DSP1_YMEM_PACKED_0,
+ },
+ {
+ .type = WMFW_ADSP2_XM,
+ .base = CS40L26_DSP1_XMEM_UNPACKED24_0,
+ },
+ {
+ .type = WMFW_ADSP2_YM,
+ .base = CS40L26_DSP1_YMEM_UNPACKED24_0,
+ },
+};
+
+static int cs40l26_mailbox_write(struct cs40l26_private *cs40l26, u32 write_val)
+{
+ unsigned int ack;
+ int i, error;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ regmap_write(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1, write_val);
+
+ error = regmap_read(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1, &ack);
+ if (!error && !ack)
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US, CS40L26_DSP_TIMEOUT_US + 100);
+ }
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ if (!error)
+ error = -ETIMEDOUT;
+
+ dev_err(cs40l26->dev, "Mailbox ack. failed with value 0x%X: %d", ack, error);
+ }
+
+ return error;
+}
+
+static int cs40l26_fw_ctl_write_raw(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, unsigned int off_words,
+ size_t len_bytes, u32 *buf)
+{
+ size_t len_words = len_bytes / sizeof(__be32);
+ struct cs_dsp_coeff_ctl *ctl;
+ int i, error;
+ __be32 *val;
+
+ ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id);
+ if (IS_ERR_OR_NULL(ctl)) {
+ dev_err(dsp->dev, "Failed to find fw ctl %s\n", name);
+ return -ENOENT;
+ }
+
+ val = kzalloc(len_bytes, GFP_KERNEL);
+ if (!val)
+ return -ENOMEM;
+
+ for (i = 0; i < len_words; i++)
+ val[i] = cpu_to_be32(buf[i]);
+
+ error = cs_dsp_coeff_write_ctrl(ctl, off_words, val, len_bytes);
+ if (error)
+ dev_err(dsp->dev, "Failed to write fw ctl %s: %d\n", name, error);
+
+ kfree(val);
+
+ return error;
+}
+
+static inline int cs40l26_fw_ctl_write(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, u32 val)
+{
+ return cs40l26_fw_ctl_write_raw(dsp, name, algo_id, 0, sizeof(u32), &val);
+}
+
+static int cs40l26_fw_ctl_read_raw(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, unsigned int off_words,
+ size_t len_bytes, u32 *buf)
+{
+ size_t len_words = len_bytes / sizeof(u32);
+ struct cs_dsp_coeff_ctl *ctl;
+ int i, error;
+
+ ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id);
+ if (IS_ERR_OR_NULL(ctl)) {
+ dev_err(dsp->dev, "Failed to find fw ctl %s\n", name);
+ return -ENOENT;
+ }
+
+ error = cs_dsp_coeff_read_ctrl(ctl, off_words, buf, len_bytes);
+ if (error) {
+ dev_err(dsp->dev, "Failed to read fw ctl %s: %d\n", name, error);
+ return error;
+ }
+
+ for (i = 0; i < len_words; i++)
+ buf[i] = be32_to_cpu(buf[i]);
+
+ return 0;
+}
+
+static inline int cs40l26_fw_ctl_read(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, u32 *buf)
+{
+ return cs40l26_fw_ctl_read_raw(dsp, name, algo_id, 0, sizeof(u32), buf);
+}
+
+static const struct cs40l26_pseq_params cs40l26_pseq_params_write_full = {
+ .num_op_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS,
+ .op_mask = CS40L26_PSEQ_WRITE_FULL_OP_MASK,
+ .low_addr_shift = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT,
+ .low_addr_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK,
+ .low_data_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK,
+ .up_addr_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT,
+ .up_addr_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK,
+ .up_data_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK,
+ .up_data_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT,
+};
+
+static const struct cs40l26_pseq_params cs40l26_pseq_params_write_x16 = {
+ .num_op_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS,
+ .op_mask = CS40L26_PSEQ_WRITE_X16_OP_MASK,
+ .low_addr_shift = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT,
+ .low_addr_mask = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK,
+ .low_data_mask = 0,
+ .up_addr_shift = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT,
+ .up_addr_mask = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK,
+ .up_data_mask = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK,
+ .up_data_shift = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT,
+};
+
+static int cs40l26_pseq_find_end(struct cs40l26_private *cs40l26, struct cs40l26_pseq_op **op_end)
+{
+ struct cs40l26_pseq_op *op;
+
+ list_for_each_entry(op, &cs40l26->pseq_op_head, list) {
+ if (op->operation == CS40L26_PSEQ_OP_END)
+ break;
+ }
+
+ if (op->operation != CS40L26_PSEQ_OP_END) {
+ dev_err(cs40l26->dev, "Failed to find PSEQ list terminator\n");
+ return -ENOENT;
+ }
+
+ *op_end = op;
+
+ return 0;
+}
+
+static int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr,
+ u32 data, bool update, u8 op_code)
+{
+ struct device *dev = cs40l26->dev;
+ bool is_new = true;
+ struct cs40l26_pseq_op *op, *op_new, *op_end;
+ const struct cs40l26_pseq_params *params;
+ int error;
+
+ switch (op_code) {
+ case CS40L26_PSEQ_OP_WRITE_FULL:
+ params = &cs40l26_pseq_params_write_full;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_L16:
+ case CS40L26_PSEQ_OP_WRITE_H16:
+ if (addr & CS40L26_PSEQ_INVALID_ADDR) {
+ dev_err(dev, "Invalid PSEQ address: 0x%08X\n", addr);
+ return -EINVAL;
+ }
+
+ params = &cs40l26_pseq_params_write_x16;
+ break;
+ default:
+ dev_err(dev, "Invalid PSEQ OP code: 0x%02X\n", op_code);
+ return -EINVAL;
+ }
+
+ op_new = devm_kzalloc(dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL);
+ if (!op_new)
+ return -ENOMEM;
+
+ op_new->size = params->num_op_words;
+ op_new->operation = op_code;
+ op_new->words[0] = (op_code << CS40L26_PSEQ_OP_SHIFT);
+ op_new->words[0] |= (addr & params->up_addr_mask) >> params->up_addr_shift;
+ op_new->words[1] = (addr & params->low_addr_mask) << params->low_addr_shift;
+ op_new->words[1] |= (data & params->up_data_mask) >> params->up_data_shift;
+ if (op_code == CS40L26_PSEQ_OP_WRITE_FULL)
+ op_new->words[2] = data & params->low_data_mask;
+
+ list_for_each_entry(op, &cs40l26->pseq_op_head, list) {
+ if (op->words[0] == op_new->words[0] &&
+ (op->words[1] & params->op_mask) ==
+ (op_new->words[1] & params->op_mask) && update) {
+ if (op->size != params->num_op_words) {
+ dev_err(dev, "Failed to replace PSEQ op.\n");
+ error = -EINVAL;
+ goto op_new_free;
+ }
+ is_new = false;
+ break;
+ }
+ }
+
+ error = cs40l26_pseq_find_end(cs40l26, &op_end);
+ if (error)
+ goto op_new_free;
+
+ if (((CS40L26_PSEQ_MAX_WORDS * sizeof(u32)) - op_end->offset) <
+ (op_new->size * sizeof(u32))) {
+ dev_err(dev, "Not enough space in pseq to add op\n");
+ error = -ENOMEM;
+ goto op_new_free;
+ }
+
+ if (is_new) {
+ op_new->offset = op_end->offset;
+ op_end->offset += (op_new->size * sizeof(u32));
+ } else {
+ op_new->offset = op->offset;
+ }
+
+ error = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE", CS40L26_PM_ALGO_ID,
+ op_new->offset / sizeof(u32), op_new->size * sizeof(u32),
+ op_new->words);
+ if (error)
+ goto op_new_free;
+
+ if (is_new) {
+ error = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE",
+ CS40L26_PM_ALGO_ID, op_end->offset / sizeof(u32),
+ op_end->size * sizeof(u32), op_end->words);
+ if (error)
+ goto op_new_free;
+
+ list_add(&op_new->list, &cs40l26->pseq_op_head);
+ } else {
+ list_replace(&op->list, &op_new->list);
+ }
+
+ return 0;
+
+op_new_free:
+ devm_kfree(dev, op_new);
+
+ return error;
+}
+
+static int cs40l26_pseq_multi_write(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs, bool update, u8 op_code)
+{
+ int error, i;
+
+ for (i = 0; i < num_regs; i++) {
+ error = cs40l26_pseq_write(cs40l26, reg_seq[i].reg,
+ reg_seq[i].def, update, op_code);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26)
+{
+ int error;
+
+ error = cs40l26_pseq_write(cs40l26, CS40L26_NGATE1_INPUT,
+ CS40L26_DATA_SRC_DSP1TX4, true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (error)
+ return error;
+
+ error = cs40l26_pseq_write(cs40l26, CS40L26_MIXER_NGATE_CH1_CFG,
+ CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT,
+ true, CS40L26_PSEQ_OP_WRITE_FULL);
+ if (error)
+ return error;
+
+ return cs40l26_pseq_write(cs40l26, CS40L26_TST_DAC_MSM_CONFIG,
+ CS40L26_TST_DAC_MSM_CFG_DFLT_CHG_VAL_H16,
+ true, CS40L26_PSEQ_OP_WRITE_H16);
+}
+
+static int cs40l26_pseq_init(struct cs40l26_private *cs40l26)
+{
+ struct cs40l26_pseq_op *pseq_op;
+ int error, i, num_words;
+ u8 operation;
+ u32 *words;
+
+ INIT_LIST_HEAD(&cs40l26->pseq_op_head);
+
+ words = kcalloc(CS40L26_PSEQ_MAX_WORDS, sizeof(u32), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(words))
+ return -ENOMEM;
+
+ error = cs40l26_fw_ctl_read_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE",
+ CS40L26_PM_ALGO_ID, 0,
+ CS40L26_PSEQ_MAX_WORDS * sizeof(u32), words);
+ if (error)
+ goto err_free;
+
+ for (i = 0; i < CS40L26_PSEQ_MAX_WORDS; i += num_words) {
+ operation = (words[i] & CS40L26_PSEQ_OP_MASK) >> CS40L26_PSEQ_OP_SHIFT;
+
+ switch (operation) {
+ case CS40L26_PSEQ_OP_END:
+ num_words = CS40L26_PSEQ_OP_END_WORDS;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_ADDR8:
+ case CS40L26_PSEQ_OP_WRITE_H16:
+ case CS40L26_PSEQ_OP_WRITE_L16:
+ num_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_FULL:
+ num_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid OP code 0x%02X\n", operation);
+ error = -EINVAL;
+ goto err_free;
+ }
+
+ pseq_op = devm_kzalloc(cs40l26->dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL);
+ if (!pseq_op) {
+ error = -ENOMEM;
+ goto err_free;
+ }
+
+ memcpy(pseq_op->words, &words[i], num_words * sizeof(u32));
+ pseq_op->size = num_words;
+ pseq_op->offset = i * sizeof(u32);
+ pseq_op->operation = operation;
+ list_add(&pseq_op->list, &cs40l26->pseq_op_head);
+
+ if (operation == CS40L26_PSEQ_OP_END)
+ break;
+ }
+
+ if (operation != CS40L26_PSEQ_OP_END) {
+ dev_err(cs40l26->dev, "PSEQ_END_OF_SCRIPT not found\n");
+ error = -ENOENT;
+ goto err_free;
+ }
+
+ error = cs40l26_update_reg_defaults_via_pseq(cs40l26);
+
+err_free:
+ kfree(words);
+
+ return error;
+}
+
+static inline void cs40l26_pm_exit(struct device *dev)
+{
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static inline int cs40l26_pm_enter(struct device *dev)
+{
+ return pm_runtime_resume_and_get(dev);
+}
+
+static inline int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
+ unsigned int dsp_state, u32 timeout_ms)
+{
+ return regmap_write(cs40l26->regmap,
+ (dsp_state == CS40L26_DSP_STATE_STANDBY) ?
+ CS40L26_A1_PM_STDBY_TICKS_STATIC_REG :
+ CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG,
+ timeout_ms * CS40L26_PM_TICKS_PER_MS);
+}
+
+static inline int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26,
+ unsigned int dsp_state, unsigned int *timeout_ms)
+{
+ unsigned int timeout_ticks;
+ int error;
+
+ error = regmap_read(cs40l26->regmap, (dsp_state == CS40L26_DSP_STATE_STANDBY) ?
+ CS40L26_A1_PM_STDBY_TICKS_STATIC_REG :
+ CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG, &timeout_ticks);
+ if (error)
+ return error;
+
+ *timeout_ms = timeout_ticks / CS40L26_PM_TICKS_PER_MS;
+
+ return 0;
+}
+
+static inline void cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26)
+{
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_use_autosuspend(cs40l26->dev);
+ pm_runtime_set_autosuspend_delay(cs40l26->dev, CS40L26_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_enable(cs40l26->dev);
+}
+
+static inline void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26)
+{
+ pm_runtime_disable(cs40l26->dev);
+ pm_runtime_dont_use_autosuspend(cs40l26->dev);
+}
+
+static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 reg, u32 val, u32 bit_mask)
+{
+ unsigned int eint_reg, cur_mask, new_mask;
+ int error;
+
+ if (reg == CS40L26_IRQ1_MASK_1) {
+ eint_reg = CS40L26_IRQ1_EINT_1;
+ } else if (reg == CS40L26_IRQ1_MASK_2) {
+ eint_reg = CS40L26_IRQ1_EINT_2;
+ } else {
+ dev_err(cs40l26->dev, "Invalid IRQ mask reg: 0x%08X\n", reg);
+ return -EINVAL;
+ }
+
+ error = regmap_read(cs40l26->regmap, reg, &cur_mask);
+ if (error)
+ return error;
+
+ new_mask = (cur_mask & ~bit_mask) | val;
+
+ error = regmap_write(cs40l26->regmap, eint_reg, bit_mask);
+ if (error)
+ return error;
+
+ error = regmap_write(cs40l26->regmap, reg, new_mask);
+ if (error)
+ return error;
+
+ if (bit_mask & GENMASK(31, 16)) {
+ error = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(31, 16)) >> 16,
+ true, CS40L26_PSEQ_OP_WRITE_H16);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to update IRQ mask H16");
+ return error;
+ }
+ }
+
+ if (bit_mask & GENMASK(15, 0)) {
+ error = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(15, 0)), true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to update IRQ mask L16");
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static inline int cs40l26_gpio_timeout_ms_set(struct cs40l26_private *cs40l26,
+ unsigned int timeout_ms)
+{
+ return cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_GPI_MS",
+ CS40L26_VIBEGEN_ALGO_ID, timeout_ms);
+}
+
+static int cs40l26_erase_gpio_mapping(struct cs40l26_private *cs40l26,
+ enum cs40l26_gpio_map mapping)
+{
+ unsigned int disable = CS40L26_EVENT_MAP_GPI_DISABLE;
+ int error;
+
+ if (cs40l26->dsp.running) {
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ error = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, mapping,
+ sizeof(u32), &disable);
+ if (error) {
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ return error;
+ }
+
+ error = cs40l26_gpio_timeout_ms_set(cs40l26, 0);
+
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ } else {
+ error = regmap_write(cs40l26->regmap, CS40L26_A1_EVENT_MAP_1, disable);
+ }
+
+ return error;
+}
+
+static int cs40l26_gpio_config(struct cs40l26_private *cs40l26)
+{
+ unsigned int defaults[2], mask, val;
+ int error;
+
+ defaults[0] = cs40l26->gpio_release_default;
+ defaults[1] = cs40l26->gpio_press_default;
+
+ error = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, 0,
+ 2 * sizeof(u32), defaults);
+ if (error)
+ return error;
+
+ switch (cs40l26->devid) {
+ case CS40L26_DEVID_A:
+ case CS40L26_DEVID_L27_A:
+ val = (unsigned int)GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO2_RISE);
+ break;
+ default:
+ val = 0;
+ }
+
+ mask = (unsigned int)GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO1_RISE);
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask);
+}
+
+static int cs40l26_map_effect_to_gpio(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ u16 button = effect->trigger.button;
+ u8 edge, ev_handler_bank_ram, gpio;
+ unsigned int offset, write_val;
+ int error;
+
+ gpio = (button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT;
+ edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT;
+
+ if (gpio != 1) {
+ dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio, cs40l26->revid);
+ return -EINVAL;
+ }
+
+ ev_handler_bank_ram = (ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_RAM ||
+ ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_BUZ) ? 1 : 0;
+
+ offset = edge ? 0 : 1;
+
+ write_val = (ueffect->trigger_index & CS40L26_BTN_INDEX_MASK) |
+ (ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT);
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ error = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, offset, sizeof(u32),
+ &write_val);
+ if (error)
+ goto err_pwr_lock;
+
+ error = cs40l26_gpio_timeout_ms_set(cs40l26, effect->replay.length);
+
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (error)
+ return error;
+
+ ueffect->mapping = edge ? CS40L26_GPIO_MAP_A_PRESS : CS40L26_GPIO_MAP_A_RELEASE;
+
+ return 0;
+}
+
+static inline int cs40l26_num_waves_get(struct cs40l26_private *cs40l26,
+ u32 *num_waves)
+{
+ return cs40l26_fw_ctl_read(&cs40l26->dsp, "NUM_OF_WAVES",
+ CS40L26_VIBEGEN_ALGO_ID, num_waves);
+}
+
+static struct cs40l26_uploaded_effect *cs40l26_uploaded_effect_find(struct cs40l26_private *cs40l26,
+ int id)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+
+ if (list_empty(&cs40l26->effect_head)) {
+ dev_dbg(cs40l26->dev, "Effect list is empty\n");
+ return ERR_PTR(-ENODATA);
+ }
+
+ list_for_each_entry(ueffect, &cs40l26->effect_head, list) {
+ if (ueffect->id == id)
+ return ueffect;
+ }
+
+ return NULL;
+}
+
+static struct cs40l26_buzzgen_config cs40l26_buzzgen_configs[] = {
+ {
+ .duration_name = "BUZZ_EFFECTS2_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS2_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS2_BUZZ_LEVEL",
+ .effect_id = -1
+ },
+ {
+ .duration_name = "BUZZ_EFFECTS3_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS3_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS3_BUZZ_LEVEL",
+ .effect_id = -1
+ },
+ {
+ .duration_name = "BUZZ_EFFECTS4_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS4_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS4_BUZZ_LEVEL",
+ .effect_id = -1
+ },
+ {
+ .duration_name = "BUZZ_EFFECTS5_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS5_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS5_BUZZ_LEVEL",
+ .effect_id = -1
+ },
+ {
+ .duration_name = "BUZZ_EFFECTS6_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS6_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS6_BUZZ_LEVEL",
+ .effect_id = -1
+ },
+};
+
+static int cs40l26_buzzgen_find_slot(struct cs40l26_private *cs40l26, int id)
+{
+ int i, slot = -1;
+
+ for (i = CS40L26_BUZZGEN_NUM_CONFIGS - 1; i >= 0; i--) {
+ if (cs40l26_buzzgen_configs[i].effect_id == id) {
+ slot = i;
+ break;
+ } else if (cs40l26_buzzgen_configs[i].effect_id == -1) {
+ slot = i;
+ }
+ }
+
+ return slot;
+}
+
+static int cs40l26_erase_buzzgen(struct cs40l26_private *cs40l26, int id)
+{
+ int slot = cs40l26_buzzgen_find_slot(cs40l26, id);
+
+ if (slot == -1) {
+ dev_err(cs40l26->dev, "Failed to erase BUZZGEN config for id %d\n", id);
+ return -EINVAL;
+ }
+
+ cs40l26_buzzgen_configs[slot].effect_id = -1;
+
+ return 0;
+}
+
+static int cs40l26_sine_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ unsigned int freq, level, duration;
+ int error, slot;
+
+ slot = cs40l26_buzzgen_find_slot(cs40l26, effect->id);
+ if (slot == -1) {
+ dev_err(cs40l26->dev, "No free BUZZGEN slot available\n");
+ return -ENOSPC;
+ }
+
+ cs40l26_buzzgen_configs[slot].effect_id = effect->id;
+
+ /* Divide by 4 to match firmware's formatting expectation */
+ duration = (u32)(effect->replay.length / 4);
+
+ if (effect->u.periodic.period < CS40L26_BUZZGEN_PERIOD_MS_MIN)
+ freq = 1000 / CS40L26_BUZZGEN_PERIOD_MS_MIN;
+ else if (effect->u.periodic.period > CS40L26_BUZZGEN_PERIOD_MS_MAX)
+ freq = 1000 / CS40L26_BUZZGEN_PERIOD_MS_MAX;
+ else
+ freq = 1000 / effect->u.periodic.period;
+
+ if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN)
+ level = CS40L26_BUZZGEN_LEVEL_MIN;
+ else if (effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX)
+ level = CS40L26_BUZZGEN_LEVEL_MAX;
+ else
+ level = effect->u.periodic.magnitude;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].duration_name,
+ CS40L26_BUZZGEN_ALGO_ID, duration);
+ if (error)
+ goto err_pwr_lock;
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].freq_name,
+ CS40L26_BUZZGEN_ALGO_ID, freq);
+ if (error)
+ goto err_pwr_lock;
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].level_name,
+ CS40L26_BUZZGEN_ALGO_ID, level);
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (error)
+ return error;
+
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = CS40L26_WVFRM_BANK_BUZ;
+ /*
+ * BUZZGEN 1 is reserved for OTP buzz; BUZZGEN 2 - BUZZGEN 6 are valid.
+ * Add an offset of 1 for this reason.
+ */
+ ueffect->trigger_index = CS40L26_BUZZGEN_INDEX_START + slot + 1;
+
+ return 0;
+}
+
+static int cs40l26_custom_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ struct device *dev = cs40l26->dev;
+ unsigned int nwaves, min_index, max_index, trigger_index;
+ u16 bank, index;
+ int error;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ error = cs40l26_num_waves_get(cs40l26, &nwaves);
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (error)
+ return error;
+
+ bank = (u16)(cs40l26->raw_custom_data[0] & 0xffffu);
+ index = (u16)(cs40l26->raw_custom_data[1] & 0xffffu);
+
+ if (bank == CS40L26_WVFRM_BANK_RAM) {
+ if (nwaves == 0) {
+ dev_err(dev, "No waveforms in RAM\n");
+ return -ENODATA;
+ }
+
+ min_index = CS40L26_RAM_INDEX_START;
+ max_index = min_index + nwaves - 1;
+ } else if (bank == CS40L26_WVFRM_BANK_ROM) {
+ min_index = CS40L26_ROM_INDEX_START;
+ max_index = CS40L26_ROM_INDEX_END;
+ } else {
+ dev_err(dev, "Invalid custom waveform bank: %u\n", bank);
+ return -EINVAL;
+ }
+
+ trigger_index = index + min_index;
+ if (trigger_index > max_index) {
+ dev_err(dev, "RAM index 0x%X out of bounds\n", trigger_index);
+ return -EINVAL;
+ }
+ dev_dbg(dev, "%s: Trigger Index = 0x%08X\n", __func__, trigger_index);
+
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = bank;
+ ueffect->trigger_index = trigger_index;
+
+ return 0;
+}
+
+static int cs40l26_uploaded_effect_add(struct cs40l26_private *cs40l26)
+{
+ struct ff_effect *effect = &cs40l26->upload_effect;
+ bool is_new = false;
+ struct cs40l26_uploaded_effect *ueffect;
+ int error;
+
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, cs40l26->upload_effect.id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ is_new = true;
+ ueffect = devm_kzalloc(cs40l26->dev, sizeof(struct cs40l26_uploaded_effect),
+ GFP_KERNEL);
+ if (IS_ERR_OR_NULL(ueffect))
+ return -ENOMEM;
+ }
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ error = cs40l26_custom_upload(cs40l26, effect, ueffect);
+ } else if (effect->u.periodic.waveform == FF_SINE) {
+ error = cs40l26_sine_upload(cs40l26, effect, ueffect);
+ } else {
+ dev_err(cs40l26->dev, "Invalid periodic waveform type: 0x%X\n",
+ effect->u.periodic.waveform);
+ error = -EINVAL;
+ }
+ if (error)
+ goto err_free;
+
+ if (effect->trigger.button) {
+ error = cs40l26_map_effect_to_gpio(cs40l26, effect, ueffect);
+ if (error)
+ goto err_free;
+ } else {
+ ueffect->mapping = CS40L26_GPIO_MAP_INVALID;
+ }
+
+ if (is_new)
+ list_add(&ueffect->list, &cs40l26->effect_head);
+
+ return 0;
+
+err_free:
+ if (is_new)
+ kfree(ueffect);
+
+ return error;
+}
+
+static void cs40l26_upload_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, upload_work);
+ struct device *dev = cs40l26->dev;
+
+ cs40l26->upload_error = cs40l26_pm_enter(dev);
+ if (cs40l26->upload_error)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ cs40l26->upload_error = cs40l26_uploaded_effect_add(cs40l26);
+
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+}
+
+static void cs40l26_vibe_start_worker(struct work_struct *work)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+ struct cs40l26_private *cs40l26;
+ struct ff_effect *effect;
+ unsigned int invert;
+ struct device *dev;
+ int error;
+
+ cs40l26 = container_of(work, struct cs40l26_private, vibe_start_work);
+ dev = cs40l26->dev;
+
+ error = cs40l26_pm_enter(dev);
+ if (error)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ effect = cs40l26->trigger_effect;
+
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(dev, "No such effect (ID = %d)\n", effect->id);
+ goto err_mutex;
+ }
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_MS", CS40L26_VIBEGEN_ALGO_ID,
+ effect->replay.length);
+ if (error)
+ goto err_pwr_lock;
+
+ switch (effect->direction) {
+ case 0x0000:
+ invert = 0;
+ break;
+ case 0x8000:
+ invert = 1;
+ break;
+ default:
+ dev_err(dev, "Invalid direction 0x%X\n", effect->direction);
+ goto err_pwr_lock;
+ }
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, "SOURCE_INVERT", CS40L26_EXT_ALGO_ID, invert);
+ if (error)
+ goto err_pwr_lock;
+
+ error = cs40l26_mailbox_write(cs40l26, ueffect->trigger_index);
+ if (error)
+ goto err_pwr_lock;
+
+ reinit_completion(&cs40l26->erase);
+
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+}
+
+static void cs40l26_vibe_stop_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26;
+ int error;
+
+ cs40l26 = container_of(work, struct cs40l26_private, vibe_stop_work);
+
+ error = cs40l26_pm_enter(cs40l26->dev);
+ if (error)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_STOP_PLAYBACK);
+ if (error)
+ dev_err(cs40l26->dev, "Failed to stop playback\n");
+
+ mutex_unlock(&cs40l26->lock);
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+static void cs40l26_erase_worker(struct work_struct *work)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+ struct cs40l26_private *cs40l26;
+ u16 duration, replay_len;
+ int id;
+
+ cs40l26 = container_of(work, struct cs40l26_private, erase_work);
+
+ cs40l26->erase_error = cs40l26_pm_enter(cs40l26->dev);
+ if (cs40l26->erase_error)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ id = cs40l26->erase_effect->id;
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(cs40l26->dev, "Failed to erase nonexistent effect (%d)\n", id);
+ cs40l26->erase_error = PTR_ERR(ueffect);
+ goto err_mutex;
+ }
+
+ /* Wait for any ongoing playback to stop */
+ replay_len = cs40l26->erase_effect->replay.length;
+ duration = replay_len ? replay_len + 500 : CS40L26_VIBEGEN_MAX_TIME_MS;
+
+ mutex_unlock(&cs40l26->lock);
+
+ if (!wait_for_completion_timeout(&cs40l26->erase, msecs_to_jiffies(duration))) {
+ cs40l26->erase_error = -ETIME;
+ dev_err(cs40l26->dev, "Timed out waiting for playback stop\n");
+ goto err_pm;
+ }
+ mutex_lock(&cs40l26->lock);
+
+ if (ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_BUZ) {
+ cs40l26->erase_error = cs40l26_erase_buzzgen(cs40l26, ueffect->id);
+ if (cs40l26->erase_error)
+ goto err_mutex;
+ }
+
+ if (ueffect->mapping != CS40L26_GPIO_MAP_INVALID) {
+ cs40l26->erase_error = cs40l26_erase_gpio_mapping(cs40l26, ueffect->mapping);
+ if (cs40l26->erase_error)
+ goto err_mutex;
+ }
+
+ list_del(&ueffect->list);
+ kfree(ueffect);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+err_pm:
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+/* LUT for converting gain percentage to attenuation in dB */
+const u32 cs40l26_atten_lut_q21_2[CS40L26_NUM_ATTEN_LUT_VALUES] = {
+ 400, /* MUTE */ 160, /* 1% */ 136, 122, 112, 104, 98, 92,
+ 88, 84, 80, 77, 74, 71, 68, 66, 64, 62, 60, 58, 56, 54, 53,
+ 51, 50, 48, /* 25% */ 47, 45, 44, 43, 42, 41, 40, 39, 37, 36,
+ 35, 35, 34, 33, 32, 31, 30, 29, 29, 28, 27, 26, 26, 25, 24, /* 50 % */
+ 23, 23, 22, 21, 21, 20, 20, 19, 18, 18, 17, 17, 16, 16, 15, 14,
+ 14, 13, 13, 12, 12, 11, 11, 10, 10, /* 75% */ 10, 9, 9, 8, 8, 7,
+ 7, 6, 6, 6, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 1, 1, 1, 0, 0, /* 100% */
+};
+
+static void cs40l26_set_gain_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, set_gain_work);
+
+ if (cs40l26_pm_enter(cs40l26->dev))
+ return;
+
+ mutex_lock(&cs40l26->lock);
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ cs40l26_fw_ctl_write(&cs40l26->dsp, "SOURCE_ATTENUATION", CS40L26_EXT_ALGO_ID,
+ cs40l26_atten_lut_q21_2[cs40l26->gain_pct]);
+
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ mutex_unlock(&cs40l26->lock);
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+static int cs40l26_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ u32 len = effect->u.periodic.custom_len;
+ int error;
+
+ if (effect->type != FF_PERIODIC) {
+ dev_err(cs40l26->dev, "Effect type 0x%X not supported\n", effect->type);
+ return -EINVAL;
+ }
+
+ memcpy(&cs40l26->upload_effect, effect, sizeof(struct ff_effect));
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ cs40l26->raw_custom_data = kcalloc(len, sizeof(s16), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(cs40l26->raw_custom_data))
+ return -ENOMEM;
+
+ if (copy_from_user(cs40l26->raw_custom_data, effect->u.periodic.custom_data,
+ sizeof(s16) * len)) {
+ dev_err(cs40l26->dev, "Failed to get user data\n");
+ error = -EFAULT;
+ goto out_free;
+ }
+ }
+
+ /*
+ * The no-sleep nature of Input FF callbacks requires usage of
+ * worker functions in order to properly utilize the PM runtime
+ * framework.
+ */
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->upload_work);
+ flush_work(&cs40l26->upload_work);
+ error = cs40l26->upload_error;
+
+out_free:
+ if (effect->u.periodic.waveform == FF_CUSTOM)
+ kfree(cs40l26->raw_custom_data);
+ cs40l26->raw_custom_data = NULL;
+ memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect));
+
+ return error;
+}
+
+static int cs40l26_playback_effect(struct input_dev *dev, int effect_id, int val)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+
+ cs40l26->trigger_effect = &dev->ff->effects[effect_id];
+ if (IS_ERR_OR_NULL(cs40l26->trigger_effect)) {
+ dev_err(cs40l26->dev, "No such effect (ID = %d)\n", effect_id);
+ return -ENOENT;
+ }
+
+ if (val > 0)
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work);
+ else
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
+
+ return 0;
+}
+
+static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct ff_effect *effect;
+
+ effect = &dev->ff->effects[effect_id];
+ if (IS_ERR_OR_NULL(effect)) {
+ dev_err(cs40l26->dev, "Could not erase nonexistent effect\n");
+ return -ENOENT;
+ }
+
+ cs40l26->erase_effect = effect;
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->erase_work);
+ flush_work(&cs40l26->erase_work);
+
+ return cs40l26->erase_error;
+}
+
+static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+
+ if (gain >= CS40L26_NUM_ATTEN_LUT_VALUES) {
+ dev_err(cs40l26->dev, "Gain %u%% out of bounds\n", gain);
+ return;
+ }
+
+ cs40l26->gain_pct = gain;
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work);
+}
+
+static int cs40l26_input_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int error;
+
+ cs40l26->input = devm_input_allocate_device(dev);
+ if (!cs40l26->input)
+ return -ENOMEM;
+
+ cs40l26->input->name = "cs40l26_input";
+ cs40l26->input->id.product = cs40l26->devid;
+ cs40l26->input->id.version = cs40l26->revid;
+
+ input_set_drvdata(cs40l26->input, cs40l26);
+ input_set_capability(cs40l26->input, EV_FF, FF_PERIODIC);
+ input_set_capability(cs40l26->input, EV_FF, FF_CUSTOM);
+ input_set_capability(cs40l26->input, EV_FF, FF_SINE);
+ input_set_capability(cs40l26->input, EV_FF, FF_GAIN);
+
+ error = input_ff_create(cs40l26->input, FF_MAX_EFFECTS);
+ if (error) {
+ dev_err(dev, "Failed to create FF device: %d\n", error);
+ return error;
+ }
+
+ /*
+ * input_ff_create() automatically sets FF_RUMBLE capabilities;
+ * we want to restrtict this to only FF_PERIODIC
+ */
+ __clear_bit(FF_RUMBLE, cs40l26->input->ffbit);
+
+ cs40l26->input->ff->upload = cs40l26_upload_effect;
+ cs40l26->input->ff->playback = cs40l26_playback_effect;
+ cs40l26->input->ff->erase = cs40l26_erase_effect;
+ cs40l26->input->ff->set_gain = cs40l26_set_gain;
+
+ error = input_register_device(cs40l26->input);
+ if (error) {
+ dev_err(dev, "Cannot register input device: %d\n", error);
+ return error;
+ }
+
+ INIT_LIST_HEAD(&cs40l26->effect_head);
+
+ return error;
+}
+
+static int cs40l26_bst_ipk_config(struct cs40l26_private *cs40l26)
+{
+ unsigned int bst_ipk;
+ int error;
+
+ if (cs40l26->bst_ipk_ua < CS40L26_BST_IPK_UA_MIN ||
+ cs40l26->bst_ipk_ua > CS40L26_BST_IPK_UA_MAX)
+ bst_ipk = CS40L26_BST_IPK_DEFAULT;
+ else
+ bst_ipk = (cs40l26->bst_ipk_ua / CS40L26_BST_IPK_UA_STEP) - 16;
+
+ error = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, bst_ipk);
+ if (error)
+ return error;
+
+ error = cs40l26_pseq_write(cs40l26, CS40L26_BST_IPK_CTL, bst_ipk,
+ true, CS40L26_PSEQ_OP_WRITE_L16);
+ if (error)
+ return error;
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0,
+ BIT(CS40L26_IRQ1_BST_IPK_FLAG));
+}
+
+static int cs40l26_bst_ctl_config(struct cs40l26_private *cs40l26)
+{
+ unsigned int bst_ctl;
+ int error;
+
+ if (cs40l26->vbst_uv < CS40L26_BST_UV_MIN ||
+ cs40l26->vbst_uv > CS40L26_BST_UV_MAX)
+ bst_ctl = CS40L26_BST_DEFAULT;
+ else
+ bst_ctl = (cs40l26->vbst_uv - CS40L26_BST_UV_MIN) / CS40L26_BST_UV_STEP;
+
+ error = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, bst_ctl);
+ if (error)
+ return error;
+
+ return cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_1, bst_ctl,
+ true, CS40L26_PSEQ_OP_WRITE_L16);
+}
+
+static int cs40l26_wksrc_config(struct cs40l26_private *cs40l26)
+{
+ unsigned int mask, val;
+
+ val = BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4);
+
+ mask = BIT(CS40L26_IRQ1_WKSRC_STS_ANY) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO1) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_I2C) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_SPI) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4);
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask);
+}
+
+static int cs40l26_hw_init(struct cs40l26_private *cs40l26)
+{
+ int error;
+
+ error = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0,
+ GENMASK(CS40L26_IRQ1_AMP_ERR,
+ CS40L26_IRQ1_BST_OVP_FLAG_RISE) |
+ BIT(CS40L26_IRQ1_VIRTUAL2_MBOX_WR));
+ if (error)
+ return error;
+
+ error = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, 0,
+ BIT(CS40L26_IRQ2_PLL_LOCK) |
+ BIT(CS40L26_IRQ2_PLL_READY));
+ if (error)
+ return error;
+
+ error = cs40l26_wksrc_config(cs40l26);
+ if (error)
+ return error;
+
+ error = cs40l26_gpio_config(cs40l26);
+ if (error)
+ return error;
+
+ error = cs40l26_bst_ipk_config(cs40l26);
+ if (error)
+ return error;
+
+ return cs40l26_bst_ctl_config(cs40l26);
+}
+
+static int cs40l26_lbst_short_test(struct cs40l26_private *cs40l26)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ unsigned int err, vbst_ctl_1, vbst_ctl_2;
+ int error;
+
+ error = regmap_read(regmap, CS40L26_VBST_CTL_2, &vbst_ctl_2);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK,
+ CS40L26_BST_CTL_SEL_FIXED);
+ if (error)
+ return error;
+
+ error = regmap_read(regmap, CS40L26_VBST_CTL_1, &vbst_ctl_1);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK,
+ CS40L26_BST_CTL_VP);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 1);
+ if (error)
+ return error;
+
+ /* Wait until boost converter is guranteed to be powered up */
+ usleep_range(CS40L26_BST_TIME_US_MIN, CS40L26_BST_TIME_US_MAX);
+
+ error = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err);
+ if (error)
+ return error;
+
+ if (err & BIT(CS40L26_BST_SHORT_ERR_RLS)) {
+ dev_err(dev, "FATAL: Boost shorted at startup\n");
+ return error;
+ }
+
+ error = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 0);
+ if (error)
+ return error;
+
+ error = regmap_write(regmap, CS40L26_VBST_CTL_2, vbst_ctl_2);
+ if (error)
+ return error;
+
+ return regmap_write(regmap, CS40L26_VBST_CTL_1, vbst_ctl_1);
+}
+
+static int cs40l26_part_num_resolve(struct cs40l26_private *cs40l26)
+{
+ unsigned int devid, revid;
+ int error;
+
+ error = regmap_read(cs40l26->regmap, CS40L26_DEVID, &devid);
+ if (error)
+ return error;
+ devid &= CS40L26_DEVID_MASK;
+
+ switch (devid) {
+ case CS40L26_DEVID_A:
+ case CS40L26_DEVID_B:
+ case CS40L26_DEVID_L27_A:
+ case CS40L26_DEVID_L27_B:
+ cs40l26->devid = devid;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid device ID: 0x%06X\n", devid);
+ return -EINVAL;
+ }
+
+ error = regmap_read(cs40l26->regmap, CS40L26_REVID, &revid);
+ if (error)
+ return error;
+ revid &= CS40L26_REVID_MASK;
+
+ if (revid == CS40L26_REVID_A1 || revid == CS40L26_REVID_B0) {
+ cs40l26->revid = revid;
+ } else {
+ dev_err(cs40l26->dev, "Invalid device revision: 0x%02X\n", revid);
+ return -EINVAL;
+ }
+
+ dev_info(cs40l26->dev, "Cirrus Logic CS40L26 ID: 0x%06X, Revision: 0x%02X\n",
+ cs40l26->devid, cs40l26->revid);
+
+ return 0;
+}
+
+static int cs40l26_parse_properties(struct cs40l26_private *cs40l26)
+{
+ int error;
+
+ error = device_property_read_u32(cs40l26->dev, "cirrus,bst-ctl-microvolt",
+ &cs40l26->vbst_uv);
+ if (error && error != -EINVAL)
+ return error;
+
+ error = device_property_read_u32(cs40l26->dev, "cirrus,bst-ipk-microamp",
+ &cs40l26->bst_ipk_ua);
+ if (error && error != -EINVAL)
+ return error;
+
+ cs40l26->exploratory_mode_enabled = !device_property_present(cs40l26->dev,
+ "cirrus,bst-exploratory-mode-disable");
+
+ return 0;
+}
+
+static int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, unsigned int *state)
+{
+ unsigned int dsp_state = CS40L26_DSP_STATE_NONE;
+ int i, error = 0;
+
+ if (cs40l26->dsp.running) {
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "PM_CUR_STATE",
+ CS40L26_PM_ALGO_ID, &dsp_state);
+
+ if (error)
+ return error;
+
+ if (dsp_state != CS40L26_DSP_STATE_NONE)
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US, CS40L26_DSP_TIMEOUT_US + 100);
+ }
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(cs40l26->dev, "Timed out reading PM_CUR_STATE\n");
+ return -ETIMEDOUT;
+ }
+ } else {
+ error = regmap_read_poll_timeout(cs40l26->regmap,
+ CS40L26_A1_PM_CUR_STATE_STATIC_REG,
+ dsp_state, dsp_state != CS40L26_DSP_STATE_NONE,
+ CS40L26_DSP_TIMEOUT_US,
+ CS40L26_DSP_TIMEOUT_US * CS40L26_DSP_TIMEOUT_COUNT);
+ if (error)
+ return error;
+ }
+
+ switch (dsp_state) {
+ case CS40L26_DSP_STATE_HIBERNATE:
+ case CS40L26_DSP_STATE_SHUTDOWN:
+ case CS40L26_DSP_STATE_STANDBY:
+ case CS40L26_DSP_STATE_ACTIVE:
+ *state = CS40L26_DSP_STATE_MASK & dsp_state;
+ break;
+ default:
+ dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state);
+ error = -EINVAL;
+ }
+
+ return error;
+}
+
+static bool cs40l26_dsp_can_run(struct cs40l26_private *cs40l26)
+{
+ unsigned int pm_state_locks, state;
+ int error;
+
+ error = cs40l26_dsp_state_get(cs40l26, &state);
+ if (error)
+ return false;
+
+ if (state == CS40L26_DSP_STATE_ACTIVE)
+ return true;
+
+ if (state != CS40L26_DSP_STATE_STANDBY) {
+ dev_err(cs40l26->dev, "DSP in bad state: %u\n", state);
+ return false;
+ }
+
+ if (cs40l26->dsp.running) {
+ error = cs40l26_fw_ctl_read_raw(&cs40l26->dsp, "PM_STATE_LOCKS",
+ CS40L26_PM_ALGO_ID, CS40L26_DSP_LOCK3_OFFSET /
+ sizeof(u32), sizeof(u32), &pm_state_locks);
+ if (error)
+ return false;
+ } else {
+ error = regmap_read(cs40l26->regmap, CS40L26_A1_PM_STATE_LOCKS3_STATIC_REG,
+ &pm_state_locks);
+ if (error)
+ return false;
+ }
+
+ return (pm_state_locks & CS40L26_DSP_LOCK3_MASK) == CS40L26_DSP_LOCK3_MASK;
+}
+
+static int cs40l26_mbox_buffer_read(struct cs40l26_private *cs40l26, u32 *val)
+{
+ unsigned int q_rd, q_wt, status;
+ int error;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "QUEUE_WT", CS40L26_MAILBOX_ALGO_ID, &q_wt);
+ if (error)
+ goto exit_pwr_lock;
+
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "QUEUE_RD", CS40L26_MAILBOX_ALGO_ID, &q_rd);
+ if (error)
+ goto exit_pwr_lock;
+
+ if (q_rd - sizeof(u32) == q_wt) {
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "MAILBOX_STATUS",
+ CS40L26_MAILBOX_ALGO_ID, &status);
+ if (error)
+ goto exit_pwr_lock;
+
+ if (status) {
+ dev_err(cs40l26->dev, "Mailbox buffer is full, info missing\n");
+ error = -ENOSPC;
+ goto exit_pwr_lock;
+ }
+ }
+
+ if (q_rd == q_wt) {
+ dev_dbg(cs40l26->dev, "Reached end of queue\n");
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ return 1;
+ }
+
+ error = regmap_read(cs40l26->regmap, q_rd, val);
+ if (error)
+ goto exit_pwr_lock;
+
+ q_rd = (q_rd == cs40l26->mbox_q_last) ? cs40l26->mbox_q_base : q_rd + sizeof(u32);
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, "QUEUE_RD", CS40L26_MAILBOX_ALGO_ID, q_rd);
+
+exit_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+
+ return error;
+}
+
+static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ unsigned int val;
+
+ while (!cs40l26_mbox_buffer_read(cs40l26, &val)) {
+ if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_PANIC) {
+ dev_err(dev, "DSP PANIC! Error condition: 0x%06X\n",
+ (u32)(val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK));
+ return -ENOTRECOVERABLE;
+ }
+
+ switch (val) {
+ case CS40L26_DSP_MBOX_COMPLETE_MBOX:
+ dev_dbg(dev, "Mailbox: COMPLETE_MBOX\n");
+ complete_all(&cs40l26->erase);
+ break;
+ case CS40L26_DSP_MBOX_COMPLETE_GPIO:
+ dev_dbg(dev, "Mailbox: COMPLETE_GPIO\n");
+ break;
+ case CS40L26_DSP_MBOX_TRIGGER_CP:
+ dev_dbg(dev, "Mailbox: TRIGGER_CP\n");
+ break;
+ case CS40L26_DSP_MBOX_TRIGGER_GPIO:
+ dev_dbg(dev, "Mailbox: TRIGGER_GPIO\n");
+ break;
+ case CS40L26_DSP_MBOX_PM_AWAKE:
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ dev_dbg(dev, "Mailbox: AWAKE\n");
+ break;
+ case CS40L26_DSP_MBOX_SYS_ACK:
+ dev_err(dev, "Mailbox: ACK\n");
+ return -EPERM;
+ default:
+ dev_err(dev, "MBOX buffer value (0x%X) is invalid\n", val);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int cs40l26_error_release(struct cs40l26_private *cs40l26, unsigned int err_rls)
+{
+ unsigned int err_sts, err_cfg;
+ int error;
+
+ error = regmap_read(cs40l26->regmap, CS40L26_ERROR_RELEASE, &err_sts);
+ if (error)
+ return error;
+
+ err_cfg = err_sts & ~BIT(err_rls);
+
+ error = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (error)
+ return error;
+
+ err_cfg |= BIT(err_rls);
+
+ error = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (error)
+ return error;
+
+ err_cfg &= ~BIT(err_rls);
+
+ return regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+}
+
+static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26, unsigned int irq1)
+{
+ unsigned int err_rls = 0, last_wksrc, pwrmgt_sts;
+ struct device *dev = cs40l26->dev;
+ int error = 0;
+
+ switch (irq1) {
+ case CS40L26_IRQ1_GPIO1_RISE:
+ case CS40L26_IRQ1_GPIO1_FALL:
+ if (cs40l26->wksrc_sts & CS40L26_WKSRC_STS_EN)
+ dev_dbg(dev, "GPIO1 %s edge detected\n",
+ (irq1 != 0) ? "falling" : "rising");
+
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_ANY:
+ dev_dbg(dev, "Wakesource detected (ANY)\n");
+
+ error = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &pwrmgt_sts);
+ if (error)
+ goto err;
+
+ cs40l26->wksrc_sts = (u8)((pwrmgt_sts & CS40L26_WKSRC_STS_MASK) >>
+ CS40L26_WKSRC_STS_SHIFT);
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "LAST_WAKESRC_CTL",
+ cs40l26->dsp.fw_id, &last_wksrc);
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (error)
+ goto err;
+
+ cs40l26->last_wksrc_pol = (u8)(last_wksrc & CS40L26_WKSRC_GPIO_POL_MASK);
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_GPIO1:
+ dev_dbg(dev, "GPIO1 event woke device from hibernate\n");
+
+ if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) {
+ dev_dbg(dev, "GPIO1 falling edge detected\n");
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ } else {
+ dev_dbg(dev, "GPIO1 rising edge detected\n");
+ }
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_SPI:
+ dev_dbg(dev, "SPI event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_I2C:
+ dev_dbg(dev, "I2C event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_RISE:
+ dev_warn(dev, "BST overvoltage warning\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_FALL:
+ dev_warn(dev, "BST voltage returned below warning threshold\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_ERR:
+ dev_err(dev, "BST overvolt. error\n");
+ err_rls = CS40L26_BST_OVP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_DCM_UVP_ERR:
+ dev_err(dev, "BST undervolt. error\n");
+ err_rls = CS40L26_BST_UVP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_SHORT_ERR:
+ dev_err(dev, "LBST short detected\n");
+ err_rls = CS40L26_BST_SHORT_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_IPK_FLAG:
+ dev_dbg(dev, "Current is being limited by LBST inductor\n");
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_RISE:
+ dev_err(dev, "Die overtemperature warning\n");
+ err_rls = CS40L26_TEMP_WARN_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_FALL:
+ dev_warn(dev, "Die temperature returned below threshold\n");
+ break;
+ case CS40L26_IRQ1_TEMP_ERR:
+ dev_err(dev, "Die overtemperature error\n");
+ err_rls = CS40L26_TEMP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_AMP_ERR:
+ dev_err(dev, "AMP short detected\n");
+ err_rls = CS40L26_AMP_SHORT_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_VIRTUAL2_MBOX_WR:
+ error = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1));
+ if (error)
+ return error;
+
+ return cs40l26_handle_mbox_buffer(cs40l26);
+ default:
+ return 0;
+ }
+
+ if (err_rls)
+ error = cs40l26_error_release(cs40l26, err_rls);
+
+err:
+ regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1));
+
+ return error;
+}
+
+static int cs40l26_handle_irq2(struct cs40l26_private *cs40l26, unsigned int irq2)
+{
+ struct device *dev = cs40l26->dev;
+
+ switch (irq2) {
+ case CS40L26_IRQ2_PLL_LOCK:
+ dev_dbg(dev, "PLL achieved lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_READY:
+ dev_dbg(dev, "PLL ready\n");
+ break;
+ default:
+ return 0;
+ }
+
+ return regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_2, BIT(irq2));
+}
+
+static irqreturn_t cs40l26_irq(int irq, void *data)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)data;
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ unsigned int eint, sts;
+ unsigned long eint_l;
+ int i, error;
+
+ if (cs40l26_pm_enter(dev)) {
+ dev_err(dev, "Interrupts missed\n");
+ return IRQ_NONE;
+ }
+
+ mutex_lock(&cs40l26->lock);
+
+ if (regmap_read(regmap, CS40L26_IRQ1_STATUS, &sts)) {
+ dev_err(dev, "Failed to read IRQ1 Status\n");
+ error = IRQ_NONE;
+ goto err;
+ }
+
+ if (sts != CS40L26_IRQ_STATUS_ASSERT) {
+ dev_err(dev, "IRQ1 asserted with no pending interrupts\n");
+ error = IRQ_NONE;
+ goto err;
+ }
+
+ error = regmap_read(regmap, CS40L26_IRQ1_EINT_1, &eint);
+ if (error)
+ goto err;
+ eint_l = (unsigned long)eint;
+
+ for_each_set_bit(i, &eint_l, CS40L26_IRQ1_NUM_IRQS) {
+ error = cs40l26_handle_irq1(cs40l26, i);
+ if (error)
+ goto err;
+ }
+
+ error = regmap_read(regmap, CS40L26_IRQ1_EINT_2, &eint);
+ if (error)
+ goto err;
+ eint_l = (unsigned long)eint;
+
+ for_each_set_bit(i, &eint_l, CS40L26_IRQ2_NUM_IRQS) {
+ error = cs40l26_handle_irq2(cs40l26, i);
+ if (error)
+ goto err;
+ }
+
+err:
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+
+ if (error)
+ dev_err(dev, "Failed to process IRQ (%d): %d\n", irq, error);
+
+ return IRQ_HANDLED;
+}
+
+static int __cs40l26_prevent_hiber(struct cs40l26_private *cs40l26)
+{
+
+ int i, error;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ error = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_PREVENT_HIBER);
+ if (error)
+ return error;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US, CS40L26_DSP_TIMEOUT_US + 100);
+
+ if (cs40l26_dsp_can_run(cs40l26))
+ break;
+ }
+
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(cs40l26->dev, "Failed to prevent hibernation\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int cs40l26_prevent_hiber(struct cs40l26_private *cs40l26)
+{
+ int error;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ error = __cs40l26_prevent_hiber(cs40l26);
+
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+
+ return error;
+}
+
+static int cs40l26_allow_hiber(struct cs40l26_private *cs40l26)
+{
+ cs40l26->wksrc_sts = 0x00;
+
+ /* Don't perform mailbox write since reading for acknowledgment will wake the device */
+ return regmap_write(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_ALLOW_HIBER);
+}
+
+static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26)
+{
+ unsigned int dsp_state, halo_state, timeout_ms;
+ int error, i;
+
+ error = regmap_read(cs40l26->regmap, CS40L26_A1_DSP_HALO_STATE_REG, &halo_state);
+ if (error)
+ return error;
+
+ if (halo_state != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(cs40l26->dev, "DSP not Ready: HALO_STATE: %u\n", halo_state);
+ return -EINVAL;
+ }
+
+ error = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_ACTIVE, &timeout_ms);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to get active->standby timeout: %d\n", error);
+ return error;
+ }
+
+ for (i = 0; i < CS40L26_DSP_STATE_TIMEOUT_COUNT; i++) {
+ error = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (error)
+ return error;
+
+ if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN &&
+ dsp_state != CS40L26_DSP_STATE_STANDBY)
+ dev_warn(cs40l26->dev, "DSP core not safe to kill\n");
+ else
+ break;
+
+ usleep_range(timeout_ms * 1000, (timeout_ms * 1000) + 100);
+ }
+ if (i == CS40L26_DSP_STATE_TIMEOUT_COUNT) {
+ dev_err(cs40l26->dev, "DSP Core could not be shut down\n");
+ return -ETIMEDOUT;
+ }
+
+ return regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL,
+ CS40L26_DSP_CCM_CORE_KILL);
+}
+
+static const struct reg_sequence cs40l26_a1_errata[] = {
+ { CS40L26_PLL_REFCLK_DETECT_0, 0x00000000 },
+ { CS40L26_TEST_KEY_CTRL, 0x00000055 },
+ { CS40L26_TEST_KEY_CTRL, 0x000000AA },
+ { CS40L26_TEST_LBST, CS40L26_DISABLE_EXPLORATORY_MODE },
+};
+
+static int cs40l26_handle_errata(struct cs40l26_private *cs40l26)
+{
+ int num_writes = 1, error;
+
+ if (!cs40l26->exploratory_mode_enabled) {
+ error = cs40l26_lbst_short_test(cs40l26);
+ if (error)
+ return error;
+
+ num_writes = ARRAY_SIZE(cs40l26_a1_errata);
+ }
+
+ return cs40l26_pseq_multi_write(cs40l26, cs40l26_a1_errata, num_writes,
+ false, CS40L26_PSEQ_OP_WRITE_FULL);
+}
+
+static int cs40l26_cs_dsp_pre_run(struct cs_dsp *dsp)
+{
+ struct cs40l26_private *cs40l26 = container_of(dsp, struct cs40l26_private, dsp);
+ int error;
+
+ error = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_STANDBY,
+ CS40L26_PM_STDBY_TIMEOUT_MS_DEFAULT);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to set standby->hibernate timeout: %d\n", error);
+ return error;
+ }
+
+ error = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_ACTIVE,
+ CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to set active->standby timeout: %d\n", error);
+ return error;
+ }
+
+ error = regmap_update_bits(cs40l26->regmap, CS40L26_PWRMGT_CTL, CS40L26_MEM_RDY_MASK,
+ 1 << CS40L26_MEM_RDY_SHIFT);
+ if (error)
+ return error;
+
+ error = cs40l26_fw_ctl_read(dsp, "QUEUE_BASE", CS40L26_MAILBOX_ALGO_ID,
+ &cs40l26->mbox_q_base);
+ if (error)
+ return error;
+
+ error = cs40l26_fw_ctl_read(dsp, "QUEUE_LEN", CS40L26_MAILBOX_ALGO_ID,
+ &cs40l26->mbox_q_len);
+ if (error)
+ return error;
+
+ cs40l26->mbox_q_last = cs40l26->mbox_q_base + ((cs40l26->mbox_q_len - 1) * sizeof(u32));
+
+ error = cs40l26_fw_ctl_write(dsp, "CALL_RAM_INIT", dsp->fw_id, 1);
+ if (error)
+ return error;
+
+ error = cs40l26_pseq_init(cs40l26);
+ if (error)
+ return error;
+
+ return cs40l26_handle_errata(cs40l26);
+}
+
+static int cs40l26_cs_dsp_post_run(struct cs_dsp *dsp)
+{
+ struct cs40l26_private *cs40l26 = container_of(dsp, struct cs40l26_private, dsp);
+ unsigned int halo_state, nwaves;
+ int error;
+
+ /*
+ * cs_dsp_halo_start_core() resets the DSP core.
+ * Therefore, hibernation must be prevented again
+ */
+ error = __cs40l26_prevent_hiber(cs40l26);
+ if (error)
+ return error;
+
+ error = cs40l26_fw_ctl_read(&cs40l26->dsp, "HALO_STATE", dsp->fw_id, &halo_state);
+ if (error)
+ return error;
+
+ if (halo_state != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(dsp->dev, "Firmware in unexpected state: %u\n", halo_state);
+ return -EINVAL;
+ }
+
+ error = cs40l26_hw_init(cs40l26);
+ if (error)
+ return error;
+
+ error = cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_MS", CS40L26_VIBEGEN_ALGO_ID, 0);
+ if (error)
+ return error;
+
+ error = cs40l26_num_waves_get(cs40l26, &nwaves);
+ if (error)
+ return error;
+
+ dev_info(dsp->dev, "%s loaded with %u RAM waveforms\n", "CS40L26", nwaves);
+
+ return 0;
+}
+
+static const struct cs_dsp_client_ops cs40l26_cs_dsp_client_ops = {
+ .pre_run = cs40l26_cs_dsp_pre_run,
+ .post_run = cs40l26_cs_dsp_post_run,
+};
+
+static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
+{
+ int error;
+
+ cs40l26->dsp.num = 1;
+ cs40l26->dsp.type = WMFW_HALO;
+ cs40l26->dsp.dev = cs40l26->dev;
+ cs40l26->dsp.regmap = cs40l26->regmap;
+ cs40l26->dsp.base = CS40L26_DSP_CTRL_BASE;
+ cs40l26->dsp.base_sysinfo = CS40L26_DSP1_SYS_INFO_ID;
+ cs40l26->dsp.mem = cs40l26_dsp_regions;
+ cs40l26->dsp.num_mems = CS40L26_DSP_NUM_REGIONS;
+ cs40l26->dsp.lock_regions = 0xFFFFFFFF;
+ cs40l26->dsp.client_ops = &cs40l26_cs_dsp_client_ops;
+
+ error = cs_dsp_halo_init(&cs40l26->dsp);
+ if (error) {
+ dev_err(cs40l26->dev, "Failed to initialize HALO core\n");
+ return error;
+ }
+
+ cs40l26->dsp_initialized = true;
+
+ return 0;
+}
+
+static void cs40l26_dsp_start(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+
+ if (cs40l26_dsp_pre_config(cs40l26))
+ goto err_rls_fw;
+
+ mutex_lock(&cs40l26->lock);
+
+ if (cs_dsp_power_up(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw",
+ cs40l26->bin, "cs40l26.bin", "cs40l26"))
+ goto err_mutex;
+
+ if (cs40l26->dsp.fw_id != CS40L26_FW_ID) {
+ dev_err(dev, "Invalid firmware ID: 0x%X\n", cs40l26->dsp.fw_id);
+ goto err_mutex;
+ }
+
+ if (cs40l26->dsp.fw_id_version < CS40L26_FW_ID_VERSION_MIN) {
+ dev_err(dev, "Invalid firmware version: 0x%X\n", cs40l26->dsp.fw_id_version);
+ goto err_mutex;
+ }
+
+ if (cs_dsp_run(&cs40l26->dsp))
+ dev_err(dev, "Failed to run DSP\n");
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+err_rls_fw:
+ release_firmware(cs40l26->bin);
+ release_firmware(cs40l26->wmfw);
+
+ cs40l26_pm_runtime_setup(cs40l26);
+}
+
+static void cs40l26_coeff_upload(const struct firmware *bin, void *context)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context;
+
+ if (!bin) {
+ dev_err(cs40l26->dev, "Failed to request coefficient file\n");
+ return;
+ }
+
+ cs40l26->bin = bin;
+
+ cs40l26_dsp_start(cs40l26);
+}
+
+static void cs40l26_fw_upload(const struct firmware *wmfw, void *context)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context;
+
+ if (!wmfw) {
+ dev_err(cs40l26->dev, "Failed to request firmware file\n");
+ return;
+ }
+
+ if (cs40l26_cs_dsp_init(cs40l26)) {
+ release_firmware(wmfw);
+ return;
+ }
+
+ cs40l26->wmfw = wmfw;
+
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ "cs40l26.bin", cs40l26->dev, GFP_KERNEL,
+ cs40l26, cs40l26_coeff_upload);
+}
+
+static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
+{
+ cs40l26->vibe_workqueue =
+ alloc_ordered_workqueue("cs40l26_workqueue", WQ_HIGHPRI);
+ if (IS_ERR_OR_NULL(cs40l26->vibe_workqueue)) {
+ dev_err(cs40l26->dev, "Failed to allocate workqueue\n");
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&cs40l26->erase_work, cs40l26_erase_worker);
+ INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker);
+ INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker);
+ INIT_WORK(&cs40l26->vibe_start_work, cs40l26_vibe_start_worker);
+ INIT_WORK(&cs40l26->vibe_stop_work, cs40l26_vibe_stop_worker);
+
+ return 0;
+}
+
+static struct regulator_bulk_data cs40l26_supplies[] = {
+ { .supply = "VP" },
+ { .supply = "VA" },
+};
+
+static int cs40l26_device_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int error;
+
+ error = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES,
+ cs40l26_supplies);
+ if (error) {
+ dev_err(dev, "Failed to request core supplies: %d\n", error);
+ return error;
+ }
+
+ error = cs40l26_parse_properties(cs40l26);
+ if (error)
+ return error;
+
+ error = regulator_bulk_enable(CS40L26_NUM_SUPPLIES, cs40l26_supplies);
+ if (error) {
+ dev_err(dev, "Failed to enable core supplies: %d\n", error);
+ return error;
+ }
+
+ cs40l26->reset_gpio =
+ devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR_OR_NULL(cs40l26->reset_gpio)) {
+ dev_err(dev, "Failed to get reset GPIO: %d\n",
+ (int)PTR_ERR(cs40l26->reset_gpio));
+ error = -ENOENT;
+ return error;
+ }
+
+ usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH_US,
+ CS40L26_MIN_RESET_PULSE_WIDTH_US + 100);
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 0);
+
+ usleep_range(CS40L26_CP_READY_DELAY_US,
+ CS40L26_CP_READY_DELAY_US + 100);
+
+ mutex_init(&cs40l26->lock);
+
+ error = cs40l26_worker_init(cs40l26);
+ if (error) {
+ dev_err(dev, "Failed to initialize worker threads\n");
+ return error;
+ }
+
+ /*
+ * Initialize completion but immediately complete since no effects are
+ * active at this point. This avoids having to determine if
+ * init_completion() or reinit_completion() is needed at the time of
+ * an incoming trigger.
+ */
+ init_completion(&cs40l26->erase);
+ complete_all(&cs40l26->erase);
+
+ error = cs40l26_part_num_resolve(cs40l26);
+ if (error)
+ return error;
+
+ /*
+ * The DSP may lock up if a haptic effect is triggered via GPI
+ * event or control port and the PLL is set to closed-loop.
+ *
+ * Set the PLL to open-loop and remove default GPI mappings
+ * to prevent this while the driver is loading and configuring
+ * RAM firmware.
+ *
+ * The firmware will set the PLL back to closed-loop when it is loaded
+ * and the DSP has been started.
+ */
+ error = regmap_update_bits(cs40l26->regmap, CS40L26_REFCLK_INPUT,
+ CS40L26_PLL_REFCLK_LOOP_MASK,
+ 1 << CS40L26_PLL_REFCLK_LOOP_SHIFT);
+ if (error)
+ return error;
+
+ error = regmap_read(cs40l26->regmap, CS40L26_A1_EVENT_MAP_1,
+ &cs40l26->gpio_press_default);
+ if (error)
+ return error;
+
+ error = regmap_read(cs40l26->regmap, CS40L26_A1_EVENT_MAP_2,
+ &cs40l26->gpio_release_default);
+ if (error)
+ return error;
+
+ error = cs40l26_erase_gpio_mapping(cs40l26, CS40L26_GPIO_MAP_A_PRESS);
+ if (error)
+ return error;
+
+ error = cs40l26_erase_gpio_mapping(cs40l26, CS40L26_GPIO_MAP_A_RELEASE);
+ if (error)
+ return error;
+
+ /* Set LRA to HI-Z in order to avoid fault conditions */
+ return regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG,
+ CS40L26_SPK_DEFAULT_HIZ_MASK,
+ 1 << CS40L26_SPK_DEFAULT_HIZ_SHIFT);
+}
+
+int cs40l26_probe(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int error;
+
+ error = cs40l26_device_init(cs40l26);
+ if (error)
+ goto err;
+
+ error = __cs40l26_prevent_hiber(cs40l26);
+ if (error)
+ goto err;
+
+ error = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ "cs40l26.wmfw", dev, GFP_KERNEL,
+ cs40l26, cs40l26_fw_upload);
+ if (error) {
+ dev_err(dev, "Failed to load firmware: %d\n", error);
+ goto err;
+ }
+
+ error = devm_request_threaded_irq(dev, cs40l26->irq, NULL, cs40l26_irq,
+ IRQF_ONESHOT | IRQF_SHARED, "cs40l26",
+ cs40l26);
+ if (error) {
+ dev_err(dev, "Failed to request threaded IRQ\n");
+ goto err;
+ }
+
+ error = cs40l26_input_init(cs40l26);
+ if (error)
+ goto err;
+
+ return 0;
+err:
+ cs40l26_remove(cs40l26);
+
+ return error;
+}
+EXPORT_SYMBOL_GPL(cs40l26_probe);
+
+int cs40l26_remove(struct cs40l26_private *cs40l26)
+{
+ struct regulator *vp_consumer = cs40l26_supplies[CS40L26_VP_SUPPLY].consumer;
+ struct regulator *va_consumer = cs40l26_supplies[CS40L26_VA_SUPPLY].consumer;
+
+ disable_irq(cs40l26->irq);
+
+ cs40l26_pm_runtime_teardown(cs40l26);
+
+ if (cs40l26->dsp_initialized) {
+ if (cs40l26->dsp.running)
+ cs_dsp_stop(&cs40l26->dsp);
+ if (cs40l26->dsp.booted)
+ cs_dsp_power_down(&cs40l26->dsp);
+ if (&cs40l26->dsp)
+ cs_dsp_remove(&cs40l26->dsp);
+ }
+
+ if (cs40l26->vibe_workqueue) {
+ cancel_work_sync(&cs40l26->erase_work);
+ cancel_work_sync(&cs40l26->set_gain_work);
+ cancel_work_sync(&cs40l26->upload_work);
+ cancel_work_sync(&cs40l26->vibe_start_work);
+ cancel_work_sync(&cs40l26->vibe_stop_work);
+ destroy_workqueue(cs40l26->vibe_workqueue);
+ }
+
+ if (!IS_ERR_OR_NULL(cs40l26->reset_gpio))
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 1);
+
+ if (va_consumer)
+ regulator_disable(va_consumer);
+
+ if (vp_consumer)
+ regulator_disable(vp_consumer);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l26_remove);
+
+static int __maybe_unused cs40l26_suspend(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ int error;
+
+ mutex_lock(&cs40l26->lock);
+ dev_dbg(dev, "%s: Enabling hibernation\n", __func__);
+
+ error = cs40l26_allow_hiber(cs40l26);
+
+ mutex_unlock(&cs40l26->lock);
+
+ return error;
+}
+
+static int __maybe_unused cs40l26_sys_suspend(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "System suspend, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused cs40l26_sys_suspend_noirq(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "Late system suspend, re-enabling IRQ\n");
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused cs40l26_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s: Disabling hibernation\n", __func__);
+
+ return cs40l26_prevent_hiber(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused cs40l26_sys_resume(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "System resume, re-enabling IRQ\n");
+
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int __maybe_unused cs40l26_sys_resume_noirq(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "Early system resume, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+const struct dev_pm_ops cs40l26_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs40l26_suspend, cs40l26_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend, cs40l26_sys_resume)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend_noirq, cs40l26_sys_resume_noirq)
+};
+EXPORT_SYMBOL_GPL(cs40l26_pm_ops);
+
+MODULE_DESCRIPTION("CS40L26 Boosted Class D Amplifier for Haptics");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/cs40l26.h b/include/linux/input/cs40l26.h
new file mode 100644
index 000000000000..bc0acec9cf23
--- /dev/null
+++ b/include/linux/input/cs40l26.h
@@ -0,0 +1,486 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#ifndef __CS40L26_H__
+#define __CS40L26_H__
+
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/wmfw.h>
+
+#define CS40L26_FIRSTREG 0x0
+#define CS40L26_LASTREG 0x3C7DFE8
+
+#define CS40L26_DEVID 0x0
+#define CS40L26_REVID 0x4
+#define CS40L26_TEST_KEY_CTRL 0x40
+#define CS40L26_GLOBAL_ENABLES 0x2014
+#define CS40L26_ERROR_RELEASE 0x2034
+#define CS40L26_PWRMGT_CTL 0x2900
+#define CS40L26_PWRMGT_STS 0x290C
+#define CS40L26_REFCLK_INPUT 0x2C04
+#define CS40L26_PLL_REFCLK_DETECT_0 0x2C28
+#define CS40L26_VBST_CTL_1 0x3800
+#define CS40L26_VBST_CTL_2 0x3804
+#define CS40L26_BST_IPK_CTL 0x3808
+#define CS40L26_TEST_LBST 0x391C
+#define CS40L26_NGATE1_INPUT 0x4C60
+#define CS40L26_DAC_MSM_CONFIG 0x7400
+#define CS40L26_TST_DAC_MSM_CONFIG 0x7404
+#define CS40L26_IRQ1_STATUS 0x10004
+#define CS40L26_IRQ1_EINT_1 0x10010
+#define CS40L26_IRQ1_EINT_2 0x10014
+#define CS40L26_IRQ1_EINT_3 0x10018
+#define CS40L26_IRQ1_EINT_4 0x1001C
+#define CS40L26_IRQ1_EINT_5 0x10020
+#define CS40L26_IRQ1_STS_1 0x10090
+#define CS40L26_IRQ1_STS_2 0x10094
+#define CS40L26_IRQ1_STS_3 0x10098
+#define CS40L26_IRQ1_STS_4 0x1009C
+#define CS40L26_IRQ1_STS_5 0x100A0
+#define CS40L26_IRQ1_MASK_1 0x10110
+#define CS40L26_IRQ1_MASK_2 0x10114
+#define CS40L26_IRQ1_MASK_3 0x10118
+#define CS40L26_IRQ1_MASK_4 0x1011C
+#define CS40L26_IRQ1_MASK_5 0x10120
+#define CS40L26_MIXER_NGATE_CH1_CFG 0x12004
+#define CS40L26_DSP_MBOX_1 0x13000
+#define CS40L26_DSP_MBOX_2 0x13004
+#define CS40L26_DSP_MBOX_3 0x13008
+#define CS40L26_DSP_MBOX_4 0x1300C
+#define CS40L26_DSP_MBOX_5 0x13010
+#define CS40L26_DSP_MBOX_6 0x13014
+#define CS40L26_DSP_MBOX_7 0x13018
+#define CS40L26_DSP_MBOX_8 0x1301C
+#define CS40L26_DSP_VIRTUAL1_MBOX_1 0x13020
+#define CS40L26_DSP_VIRTUAL1_MBOX_2 0x13024
+#define CS40L26_DSP_VIRTUAL1_MBOX_3 0x13028
+#define CS40L26_DSP_VIRTUAL1_MBOX_4 0x1302C
+#define CS40L26_DSP_VIRTUAL1_MBOX_5 0x13030
+#define CS40L26_DSP_VIRTUAL1_MBOX_6 0x13034
+#define CS40L26_DSP_VIRTUAL1_MBOX_7 0x13038
+#define CS40L26_DSP_VIRTUAL1_MBOX_8 0x1303C
+#define CS40L26_DSP_VIRTUAL2_MBOX_1 0x13040
+#define CS40L26_DSP_VIRTUAL2_MBOX_2 0x13044
+#define CS40L26_DSP_VIRTUAL2_MBOX_3 0x13048
+#define CS40L26_DSP_VIRTUAL2_MBOX_4 0x1304C
+#define CS40L26_DSP_VIRTUAL2_MBOX_5 0x13050
+#define CS40L26_DSP_VIRTUAL2_MBOX_6 0x13054
+#define CS40L26_DSP_VIRTUAL2_MBOX_7 0x13058
+#define CS40L26_DSP_VIRTUAL2_MBOX_8 0x1305C
+#define CS40L26_OTP_MEM0 0x30000
+#define CS40L26_OTP_MEM31 0x3007C
+#define CS40L26_DSP1_XMEM_PACKED_0 0x2000000
+#define CS40L26_DSP1_XMEM_PACKED_6143 0x2005FFC
+#define CS40L26_DSP1_XROM_PACKED_0 0x2006000
+#define CS40L26_DSP1_XROM_PACKED_4604 0x200A7F0
+#define CS40L26_DSP1_XMEM_UNPACKED32_0 0x2400000
+#define CS40L26_DSP1_XMEM_UNPACKED32_4095 0x2403FFC
+#define CS40L26_DSP1_XROM_UNPACKED32_0 0x2404000
+#define CS40L26_DSP1_XROM_UNPACKED32_3070 0x2406FF8
+#define CS40L26_DSP1_SYS_INFO_ID 0x25E0000
+#define CS40L26_DSP1_XMEM_UNPACKED24_0 0x2800000
+#define CS40L26_DSP1_XMEM_UNPACKED24_8191 0x2807FFC
+#define CS40L26_DSP1_XROM_UNPACKED24_0 0x2808000
+#define CS40L26_DSP1_XROM_UNPACKED24_6141 0x280DFF4
+#define CS40L26_DSP1_SCRATCH1 0x2B805C0
+#define CS40L26_DSP1_SCRATCH2 0x2B805C8
+#define CS40L26_DSP1_SCRATCH3 0x2B805D0
+#define CS40L26_DSP1_SCRATCH4 0x2B805D8
+#define CS40L26_DSP1_CCM_CORE_CONTROL 0x2BC1000
+#define CS40L26_DSP1_YMEM_PACKED_0 0x2C00000
+#define CS40L26_DSP1_YMEM_PACKED_1532 0x2C017F0
+#define CS40L26_DSP1_YMEM_UNPACKED32_0 0x3000000
+#define CS40L26_DSP1_YMEM_UNPACKED32_1022 0x3000FF8
+#define CS40L26_DSP1_YMEM_UNPACKED24_0 0x3400000
+#define CS40L26_DSP1_YMEM_UNPACKED24_2045 0x3401FF4
+#define CS40L26_DSP1_PMEM_0 0x3800000
+#define CS40L26_DSP1_PMEM_5114 0x3804FE8
+#define CS40L26_DSP1_PROM_0 0x3C60000
+#define CS40L26_DSP1_PROM_30714 0x3C7DFE8
+
+/* Device */
+#define CS40L26_DEVID_A 0x40A260
+#define CS40L26_DEVID_B 0x40A26B
+#define CS40L26_DEVID_L27_A 0x40A270
+#define CS40L26_DEVID_L27_B 0x40A27B
+#define CS40L26_NUM_DEVS 4
+#define CS40L26_DEVID_MASK GENMASK(23, 0)
+#define CS40L26_REVID_A1 0xA1
+#define CS40L26_REVID_B0 0xB0
+#define CS40L26_REVID_MASK GENMASK(7, 0)
+
+#define CS40L26_MIN_RESET_PULSE_WIDTH_US 1500
+#define CS40L26_CP_READY_DELAY_US 6000
+
+#define CS40L26_DATA_SRC_DSP1TX4 0x35
+#define CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT 0x00010003
+#define CS40L26_TST_DAC_MSM_CFG_DFLT_CHG_VAL_H16 0x1133
+
+#define CS40L26_SPK_DEFAULT_HIZ_MASK BIT(28)
+#define CS40L26_SPK_DEFAULT_HIZ_SHIFT 28
+
+#define CS40L26_DSP_CCM_CORE_KILL 0x00000080
+#define CS40L26_DSP_CCM_CORE_RESET 0x00000281
+
+#define CS40L26_MEM_RDY_MASK BIT(1)
+#define CS40L26_MEM_RDY_SHIFT 1
+
+/* Errata */
+#define CS40L26_DISABLE_EXPLORATORY_MODE 0x0100C080
+
+/* Boost Converter Control */
+#define CS40L26_GLOBAL_EN_MASK BIT(0)
+
+#define CS40L26_BST_IPK_UA_MAX 4800000
+#define CS40L26_BST_IPK_UA_MIN 1600000
+#define CS40L26_BST_IPK_UA_STEP 50000
+#define CS40L26_BST_IPK_DEFAULT 0x4A
+
+#define CS40L26_BST_UV_MIN 2500000
+#define CS40L26_BST_UV_MAX 11000000
+#define CS40L26_BST_UV_STEP 50000
+#define CS40L26_BST_DEFAULT 0xAA
+
+#define CS40L26_BST_CTL_VP 0x00
+#define CS40L26_BST_CTL_MASK GENMASK(7, 0)
+#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0)
+#define CS40L26_BST_CTL_SEL_FIXED 0x0
+#define CS40L26_BST_CTL_LIM_EN_MASK BIT(2)
+#define CS40L26_BST_CTL_LIM_EN_SHIFT 2
+
+#define CS40L26_BST_TIME_US_MIN 10000
+#define CS40L26_BST_TIME_US_MAX 10100
+
+/* Phase Locked Loop */
+#define CS40L26_PLL_OL 1
+#define CS40L26_PLL_CL 0
+
+#define CS40L26_PLL_REFCLK_LOOP_MASK BIT(11)
+#define CS40L26_PLL_REFCLK_LOOP_SHIFT 11
+
+/* Haptic Triggering */
+#define CS40L26_RAM_INDEX_START 0x01000000
+#define CS40L26_RAM_INDEX_END 0x0100007F
+
+#define CS40L26_ROM_INDEX_START 0x01800000
+#define CS40L26_ROM_INDEX_END 0x01800026
+
+#define CS40L26_BUZZGEN_INDEX_START 0x01800080
+#define CS40L26_BUZZGEN_INDEX_END 0x01800085
+
+#define CS40L26_BUZZGEN_NUM_CONFIGS 5
+
+#define CS40L26_BUZZGEN_PERIOD_MS_MAX 10
+#define CS40L26_BUZZGEN_PERIOD_MS_MIN 4
+
+#define CS40L26_BUZZGEN_DURATION_OFFSET 8
+#define CS40L26_BUZZGEN_DURATION_DIV_STEP 4
+
+#define CS40L26_BUZZGEN_LEVEL_MIN 0x00
+#define CS40L26_BUZZGEN_LEVEL_MAX 0xFF
+
+#define CS40L26_VIBEGEN_MAX_TIME_MS 10000
+
+#define CS40L26_NUM_ATTEN_LUT_VALUES 101
+
+/* GPIO */
+#define CS40L26_EVENT_MAP_GPI_DISABLE 0x1FF
+
+#define CS40L26_BTN_INDEX_MASK GENMASK(7, 0)
+#define CS40L26_BTN_BUZZ_MASK BIT(7)
+#define CS40L26_BTN_BUZZ_SHIFT 7
+#define CS40L26_BTN_BANK_MASK BIT(8)
+#define CS40L26_BTN_BANK_SHIFT 8
+#define CS40L26_BTN_NUM_MASK GENMASK(14, 12)
+#define CS40L26_BTN_NUM_SHIFT 12
+#define CS40L26_BTN_EDGE_MASK BIT(15)
+#define CS40L26_BTN_EDGE_SHIFT 15
+
+/* A1 ROM Controls */
+#define CS40L26_A1_PM_CUR_STATE_STATIC_REG 0x02800370
+#define CS40L26_A1_PM_STATE_LOCKS_STATIC_REG 0x02800378
+#define CS40L26_A1_PM_STATE_LOCKS3_STATIC_REG (CS40L26_A1_PM_STATE_LOCKS_STATIC_REG + \
+ CS40L26_DSP_LOCK3_OFFSET)
+#define CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG 0x02800350
+#define CS40L26_A1_PM_STDBY_TICKS_STATIC_REG 0x02800360
+#define CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG 0x02800368
+#define CS40L26_A1_DSP_HALO_STATE_REG 0x02800fa8
+#define CS40L26_A1_DSP_REQ_ACTIVE_REG 0x02800c08
+#define CS40L26_A1_EVENT_MAP_1 0x02806FC4
+#define CS40L26_A1_EVENT_MAP_2 0x02806FC8
+
+/* Interrupts */
+#define CS40L26_IRQ_STATUS_ASSERT 0x1
+
+#define CS40L26_IRQ_EINT1_ALL_MASK 0xFFDC7FFF
+#define CS40L26_IRQ_EINT2_ALL_MASK 0x07DE0400
+
+/* Firmware Handling */
+#define CS40L26_FW_ID 0x1800D4
+#define CS40L26_FW_ID_VERSION_MIN 0x070237
+
+/* Algorithms */
+#define CS40L26_BUZZGEN_ALGO_ID 0x0004F202
+#define CS40L26_EVENT_HANDLER_ALGO_ID 0x0004F200
+#define CS40L26_GPIO_ALGO_ID 0x0004F201
+#define CS40L26_MAILBOX_ALGO_ID 0x0004F203
+#define CS40L26_PM_ALGO_ID 0x0004F206
+#define CS40L26_VIBEGEN_ALGO_ID 0x000400BD
+#define CS40L26_EXT_ALGO_ID 0x0004013C
+
+/* DSP */
+#define CS40L26_DSP_STATE_HIBERNATE 0
+#define CS40L26_DSP_STATE_SHUTDOWN 1
+#define CS40L26_DSP_STATE_STANDBY 2
+#define CS40L26_DSP_STATE_ACTIVE 3
+#define CS40L26_DSP_STATE_NONE 4
+#define CS40L26_DSP_STATE_MASK GENMASK(7, 0)
+
+#define CS40L26_DSP_LOCK3_OFFSET 8
+#define CS40L26_DSP_LOCK3_MASK BIT(1)
+#define CS40L26_DSP_PM_ACTIVE BIT(0)
+
+#define CS40L26_DSP_HALO_STATE_RUN 2
+
+#define CS40L26_DSP_CTRL_BASE 0x2B80000
+#define CS40L26_DSP_NUM_REGIONS 5
+
+#define CS40L26_DSP_TIMEOUT_US 1000
+#define CS40L26_DSP_TIMEOUT_COUNT 100
+#define CS40L26_PM_LOCKS_TIMEOUT_COUNT 10
+#define CS40L26_DSP_STATE_TIMEOUT_COUNT 10
+
+/* Mailbox Controls */
+#define CS40L26_DSP_MBOX_RESET 0x0
+
+#define CS40L26_DSP_MBOX_CMD_HIBER 0x02000001
+#define CS40L26_DSP_MBOX_CMD_WAKEUP 0x02000002
+#define CS40L26_DSP_MBOX_CMD_PREVENT_HIBER 0x02000003
+#define CS40L26_DSP_MBOX_CMD_ALLOW_HIBER 0x02000004
+#define CS40L26_DSP_MBOX_CMD_SHUTDOWN 0x02000005
+#define CS40L26_DSP_MBOX_PM_CMD_BASE CS40L26_DSP_MBOX_CMD_HIBER
+
+#define CS40L26_DSP_MBOX_CMD_INDEX_MASK GENMASK(28, 24)
+#define CS40L26_DSP_MBOX_CMD_INDEX_SHIFT 24
+#define CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK GENMASK(23, 0)
+
+#define CS40L26_DSP_MBOX_CMD_STOP_PLAYBACK 0x05000000
+
+#define CS40L26_DSP_MBOX_COMPLETE_MBOX 0x01000000
+#define CS40L26_DSP_MBOX_COMPLETE_GPIO 0x01000001
+#define CS40L26_DSP_MBOX_TRIGGER_CP 0x01000010
+#define CS40L26_DSP_MBOX_TRIGGER_GPIO 0x01000011
+#define CS40L26_DSP_MBOX_PM_AWAKE 0x02000002
+#define CS40L26_DSP_MBOX_SYS_ACK 0x0A000000
+#define CS40L26_DSP_MBOX_PANIC 0x0C000000
+
+#define CS40L26_DSP_MBOX_QUEUE_SIZE_BYTES 16
+
+/* Power Management */
+#define CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET 16
+#define CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET 20
+#define CS40L26_PM_STDBY_TIMEOUT_MS_DEFAULT 100
+#define CS40L26_PM_ACTIVE_TIMEOUT_LOWER_OFFSET 24
+#define CS40L26_PM_ACTIVE_TIMEOUT_UPPER_OFFSET 28
+#define CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT 250
+#define CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK GENMASK(23, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK GENMASK(7, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT 24
+#define CS40L26_PM_TICKS_PER_MS 32
+
+#define CS40L26_AUTOSUSPEND_DELAY_MS 2000
+
+#define CS40L26_PSEQ_MAX_WORDS 129
+#define CS40L26_PSEQ_OP_MASK GENMASK(23, 16)
+#define CS40L26_PSEQ_OP_SHIFT 16
+#define CS40L26_PSEQ_OP_WRITE_FULL 0x00
+#define CS40L26_PSEQ_OP_WRITE_FULL_WORDS 3
+#define CS40L26_PSEQ_OP_WRITE_ADDR8 0x02
+#define CS40L26_PSEQ_OP_WRITE_L16 0x04
+#define CS40L26_PSEQ_OP_WRITE_H16 0x05
+#define CS40L26_PSEQ_OP_WRITE_X16_WORDS 2
+#define CS40L26_PSEQ_OP_END 0xFF
+#define CS40L26_PSEQ_OP_END_WORDS 1
+#define CS40L26_PSEQ_INVALID_ADDR 0xFF000000
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT 8
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT 16
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK GENMASK(15, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK GENMASK(31, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT 24
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK GENMASK(23, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK GENMASK(31, 24)
+#define CS40L26_PSEQ_WRITE_FULL_OP_MASK GENMASK(31, 8)
+#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT 16
+#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK GENMASK(7, 0)
+#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT 8
+#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK GENMASK(23, 8)
+#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT 0
+#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK GENMASK(31, 0)
+#define CS40L26_PSEQ_WRITE_X16_OP_MASK GENMASK(23, 16)
+
+/* Wake Sources */
+#define CS40L26_WKSRC_STS_MASK GENMASK(9, 4)
+#define CS40L26_WKSRC_STS_SHIFT 4
+#define CS40L26_WKSRC_STS_EN BIT(7)
+#define CS40L26_WKSRC_GPIO_POL_MASK GENMASK(3, 0)
+
+#define CS40L26_IRQ1_WKSRC_MASK GENMASK(14, 9)
+#define CS40L26_IRQ1_WKSRC_SHIFT 9
+#define CS40L26_IRQ1_WKSRC_GPIO_MASK GENMASK(3, 0)
+
+/* Interrupts */
+#define CS40L26_IRQ1_GPIO1_RISE 0
+#define CS40L26_IRQ1_GPIO1_FALL 1
+#define CS40L26_IRQ1_GPIO2_RISE 2
+#define CS40L26_IRQ1_GPIO4_FALL 7
+#define CS40L26_IRQ1_WKSRC_STS_ANY 8
+#define CS40L26_IRQ1_WKSRC_STS_GPIO1 9
+#define CS40L26_IRQ1_WKSRC_STS_GPIO2 10
+#define CS40L26_IRQ1_WKSRC_STS_GPIO3 11
+#define CS40L26_IRQ1_WKSRC_STS_GPIO4 12
+#define CS40L26_IRQ1_WKSRC_STS_SPI 13
+#define CS40L26_IRQ1_WKSRC_STS_I2C 14
+#define CS40L26_IRQ1_BST_OVP_FLAG_RISE 18
+#define CS40L26_IRQ1_BST_OVP_FLAG_FALL 19
+#define CS40L26_IRQ1_BST_OVP_ERR 20
+#define CS40L26_IRQ1_BST_DCM_UVP_ERR 21
+#define CS40L26_IRQ1_BST_SHORT_ERR 22
+#define CS40L26_IRQ1_BST_IPK_FLAG 23
+#define CS40L26_IRQ1_TEMP_WARN_RISE 24
+#define CS40L26_IRQ1_TEMP_WARN_FALL 25
+#define CS40L26_IRQ1_TEMP_ERR 26
+#define CS40L26_IRQ1_AMP_ERR 27
+#define CS40L26_IRQ1_VIRTUAL2_MBOX_WR 31
+#define CS40L26_IRQ1_NUM_IRQS 32
+
+#define CS40L26_IRQ2_PLL_LOCK 0
+#define CS40L26_IRQ2_PLL_READY 5
+#define CS40L26_IRQ2_NUM_IRQS 27
+
+enum cs40l26_power_supply {
+ CS40L26_VP_SUPPLY,
+ CS40L26_VA_SUPPLY,
+ CS40L26_NUM_SUPPLIES,
+};
+
+enum cs40l26_wvfrm_bank {
+ CS40L26_WVFRM_BANK_RAM,
+ CS40L26_WVFRM_BANK_ROM,
+ CS40L26_WVFRM_BANK_OWT,
+ CS40L26_WVFRM_BANK_BUZ,
+ CS40L26_WVFRM_BANK_NUM,
+};
+
+enum cs40l26_gpio_map {
+ CS40L26_GPIO_MAP_A_PRESS,
+ CS40L26_GPIO_MAP_A_RELEASE,
+ CS40L26_GPIO_MAP_NUM_AVAILABLE,
+ CS40L26_GPIO_MAP_INVALID,
+};
+
+enum cs40l26_err_rls {
+ CS40L26_RSRVD_ERR_RLS, /* 0 */
+ CS40L26_AMP_SHORT_ERR_RLS, /* 1 */
+ CS40L26_BST_SHORT_ERR_RLS, /* 2 */
+ CS40L26_BST_OVP_ERR_RLS, /* 3 */
+ CS40L26_BST_UVP_ERR_RLS, /* 4 */
+ CS40L26_TEMP_WARN_ERR_RLS, /* 5 */
+ CS40L26_TEMP_ERR_RLS, /* 6 */
+};
+
+struct cs40l26_pseq_op {
+ u8 size;
+ u16 offset; /* offset in bytes from pseq_base */
+ u8 operation;
+ u32 words[3];
+ struct list_head list;
+};
+
+struct cs40l26_pseq_params {
+ int num_op_words;
+ unsigned int op_mask;
+ unsigned int low_addr_shift;
+ unsigned int low_addr_mask;
+ unsigned int low_data_mask;
+ unsigned int up_addr_shift;
+ unsigned int up_addr_mask;
+ unsigned int up_data_mask;
+ unsigned int up_data_shift;
+};
+
+struct cs40l26_buzzgen_config {
+ char *duration_name;
+ char *freq_name;
+ char *level_name;
+ int effect_id;
+};
+
+struct cs40l26_uploaded_effect {
+ int id;
+ u32 trigger_index;
+ u16 wvfrm_bank;
+ enum cs40l26_gpio_map mapping;
+ struct list_head list;
+};
+
+struct cs40l26_private {
+ struct device *dev;
+ struct regmap *regmap;
+ struct cs_dsp dsp;
+ int irq;
+ struct mutex lock;
+ struct gpio_desc *reset_gpio;
+ unsigned int devid;
+ u8 revid;
+ struct input_dev *input;
+ struct ff_effect *erase_effect;
+ struct ff_effect *trigger_effect;
+ struct ff_effect upload_effect;
+ s16 *raw_custom_data;
+ struct list_head effect_head;
+ struct work_struct erase_work;
+ struct work_struct set_gain_work;
+ struct work_struct upload_work;
+ struct work_struct vibe_start_work;
+ struct work_struct vibe_stop_work;
+ struct workqueue_struct *vibe_workqueue;
+ struct completion erase;
+ int erase_error;
+ int upload_error;
+ u16 gain_pct;
+ u8 wksrc_sts;
+ u8 last_wksrc_pol;
+ struct list_head pseq_op_head;
+ unsigned int gpio_press_default;
+ unsigned int gpio_release_default;
+ u32 mbox_q_base;
+ u32 mbox_q_len;
+ u32 mbox_q_last;
+ unsigned int bst_ipk_ua;
+ unsigned int vbst_uv;
+ bool exploratory_mode_enabled;
+ const struct firmware *wmfw;
+ const struct firmware *bin;
+ bool dsp_initialized;
+};
+
+int cs40l26_probe(struct cs40l26_private *cs40l26);
+int cs40l26_remove(struct cs40l26_private *cs40l26);
+
+extern const struct regmap_config cs40l26_regmap;
+extern const struct dev_pm_ops cs40l26_pm_ops;
+
+#endif /* __CS40L26_H__ */
--
2.7.4
^ permalink raw reply related
* [PATCH 4/5] Input: cs40l26 - Load multiple coefficient files
From: Fred Treven @ 2023-05-26 0:04 UTC (permalink / raw)
To: Fred Treven, Ben Bright, James Ogletree, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Simon Trimmer, Charles Keepax,
Richard Fitzgerald, patches, linux-input, devicetree,
linux-kernel
Cc: lee
In-Reply-To: <1685059471-9598-1-git-send-email-fred.treven@cirrus.com>
Enable the driver to load all necessary coefficient tuning
files: cs40l26.bin (wavetable)
cs40l26-svc.bin (Sensorless Velocity Control)
cs40l26-dvl.bin (Dynamic Voltage Limiter)
Signed-off-by: Fred Treven <fred.treven@cirrus.com>
---
drivers/input/misc/cs40l26.c | 31 +++++++++++++++++++++++--------
include/linux/input/cs40l26.h | 3 ++-
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
index 1959438dfe31..12c29cbd4ff0 100644
--- a/drivers/input/misc/cs40l26.c
+++ b/drivers/input/misc/cs40l26.c
@@ -2006,6 +2006,12 @@ static const struct cs_dsp_client_ops cs40l26_cs_dsp_client_ops = {
.post_run = cs40l26_cs_dsp_post_run,
};
+static struct cs_dsp_coeff_desc cs40l26_coeffs[] = {
+ { .coeff_firmware = NULL, .coeff_filename = "cs40l26.bin", .optional = false },
+ { .coeff_firmware = NULL, .coeff_filename = "cs40l26-svc.bin", .optional = true },
+ { .coeff_firmware = NULL, .coeff_filename = "cs40l26-dvl.bin", .optional = true },
+};
+
static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
{
int error;
@@ -2035,14 +2041,16 @@ static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
static void cs40l26_dsp_start(struct cs40l26_private *cs40l26)
{
struct device *dev = cs40l26->dev;
+ int i;
if (cs40l26_dsp_pre_config(cs40l26))
goto err_rls_fw;
mutex_lock(&cs40l26->lock);
- if (cs_dsp_power_up(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw",
- cs40l26->bin, "cs40l26.bin", "cs40l26"))
+ if (cs_dsp_power_up_multiple(&cs40l26->dsp, cs40l26->wmfw, "cs40l26.wmfw",
+ cs40l26_coeffs, CS40L26_NUM_COEFF_FILES,
+ "cs40l26"))
goto err_mutex;
if (cs40l26->dsp.fw_id != CS40L26_FW_ID) {
@@ -2062,7 +2070,9 @@ static void cs40l26_dsp_start(struct cs40l26_private *cs40l26)
mutex_unlock(&cs40l26->lock);
err_rls_fw:
- release_firmware(cs40l26->bin);
+ for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++)
+ release_firmware(cs40l26_coeffs[i].coeff_firmware);
+
release_firmware(cs40l26->wmfw);
cs40l26_pm_runtime_setup(cs40l26);
@@ -2074,17 +2084,20 @@ static void cs40l26_coeff_upload(const struct firmware *bin, void *context)
if (!bin) {
dev_err(cs40l26->dev, "Failed to request coefficient file\n");
+ cs40l26->ncoeffs++;
return;
}
- cs40l26->bin = bin;
+ cs40l26_coeffs[cs40l26->ncoeffs++].coeff_firmware = bin;
- cs40l26_dsp_start(cs40l26);
+ if (cs40l26->ncoeffs == CS40L26_NUM_COEFF_FILES)
+ cs40l26_dsp_start(cs40l26);
}
static void cs40l26_fw_upload(const struct firmware *wmfw, void *context)
{
struct cs40l26_private *cs40l26 = (struct cs40l26_private *)context;
+ int i;
if (!wmfw) {
dev_err(cs40l26->dev, "Failed to request firmware file\n");
@@ -2098,9 +2111,11 @@ static void cs40l26_fw_upload(const struct firmware *wmfw, void *context)
cs40l26->wmfw = wmfw;
- request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
- "cs40l26.bin", cs40l26->dev, GFP_KERNEL,
- cs40l26, cs40l26_coeff_upload);
+ for (i = 0; i < CS40L26_NUM_COEFF_FILES; i++)
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ "cs40l26.bin", cs40l26->dev,
+ GFP_KERNEL, cs40l26,
+ cs40l26_coeff_upload);
}
static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
diff --git a/include/linux/input/cs40l26.h b/include/linux/input/cs40l26.h
index bc0acec9cf23..2f74ef61b952 100644
--- a/include/linux/input/cs40l26.h
+++ b/include/linux/input/cs40l26.h
@@ -231,6 +231,7 @@
/* Firmware Handling */
#define CS40L26_FW_ID 0x1800D4
#define CS40L26_FW_ID_VERSION_MIN 0x070237
+#define CS40L26_NUM_COEFF_FILES 3
/* Algorithms */
#define CS40L26_BUZZGEN_ALGO_ID 0x0004F202
@@ -473,8 +474,8 @@ struct cs40l26_private {
unsigned int vbst_uv;
bool exploratory_mode_enabled;
const struct firmware *wmfw;
- const struct firmware *bin;
bool dsp_initialized;
+ int ncoeffs;
};
int cs40l26_probe(struct cs40l26_private *cs40l26);
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox