Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Mark Brown @ 2024-03-21 17:17 UTC (permalink / raw)
  To: Karel Balej
  Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Dmitry Torokhov, Liam Girdwood, devicetree, linux-kernel,
	linux-input, Duje Mihanović, ~postmarketos/upstreaming,
	phone-devel
In-Reply-To: <CZZLDK79D5VK.2VK3X59OHIY2Z@matfyz.cz>

[-- Attachment #1: Type: text/plain, Size: 594 bytes --]

On Thu, Mar 21, 2024 at 06:08:16PM +0100, Karel Balej wrote:
> Mark Brown, 2024-03-21T16:58:44+00:00:

> > > > > > > +static const struct regmap_config pm886_i2c_regmap = {
> > > > > > > +	.reg_bits = 8,
> > > > > > > +	.val_bits = 8,
> > > > > > > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > > > > > > +};

...

> > You shouldn't be creating two regmaps for the same set of registers,
> > that just opens the potential for confusion.

> Just the regmap config is the same. Otherwise, each regmap lives at a
> different I2C address.

Do they both genuinely have the same maximum register?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Karel Balej @ 2024-03-21 17:08 UTC (permalink / raw)
  To: Mark Brown
  Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Dmitry Torokhov, Liam Girdwood, devicetree, linux-kernel,
	linux-input, Duje Mihanović, ~postmarketos/upstreaming,
	phone-devel
In-Reply-To: <879296b4-5186-4170-af3f-971787d28514@sirena.org.uk>

Mark Brown, 2024-03-21T16:58:44+00:00:
> On Thu, Mar 21, 2024 at 05:55:17PM +0100, Karel Balej wrote:
> > Lee Jones, 2024-03-21T16:20:45+00:00:
> > > On Thu, 21 Mar 2024, Karel Balej wrote:
>
> > > > > > +static const struct regmap_config pm886_i2c_regmap = {
> > > > > > +	.reg_bits = 8,
> > > > > > +	.val_bits = 8,
> > > > > > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > > > > > +};
>
> > > > > Why is this in here?
>
> > > > Because since I moved the regulators regmap initialization into the
> > > > regulators driver, I need to access it from there.
>
> > > So move it into the regulators driver?
>
> > It is used in the MFD driver too for the base regmap.
>
> You shouldn't be creating two regmaps for the same set of registers,
> that just opens the potential for confusion.

Just the regmap config is the same. Otherwise, each regmap lives at a
different I2C address.

Regards,
K. B.

^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Mark Brown @ 2024-03-21 16:58 UTC (permalink / raw)
  To: Karel Balej
  Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Dmitry Torokhov, Liam Girdwood, devicetree, linux-kernel,
	linux-input, Duje Mihanović, ~postmarketos/upstreaming,
	phone-devel
In-Reply-To: <CZZL3MNOT0QG.2WDSNX9XD2RET@matfyz.cz>

[-- Attachment #1: Type: text/plain, Size: 713 bytes --]

On Thu, Mar 21, 2024 at 05:55:17PM +0100, Karel Balej wrote:
> Lee Jones, 2024-03-21T16:20:45+00:00:
> > On Thu, 21 Mar 2024, Karel Balej wrote:

> > > > > +static const struct regmap_config pm886_i2c_regmap = {
> > > > > +	.reg_bits = 8,
> > > > > +	.val_bits = 8,
> > > > > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > > > > +};

> > > > Why is this in here?

> > > Because since I moved the regulators regmap initialization into the
> > > regulators driver, I need to access it from there.

> > So move it into the regulators driver?

> It is used in the MFD driver too for the base regmap.

You shouldn't be creating two regmaps for the same set of registers,
that just opens the potential for confusion.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Karel Balej @ 2024-03-21 16:55 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Liam Girdwood, Mark Brown, devicetree, linux-kernel, linux-input,
	Duje Mihanović, ~postmarketos/upstreaming, phone-devel
In-Reply-To: <20240321162045.GC13211@google.com>

Lee Jones, 2024-03-21T16:20:45+00:00:
> On Thu, 21 Mar 2024, Karel Balej wrote:
>
> > Lee Jones, 2024-03-21T15:42:11+00:00:
> > > On Mon, 11 Mar 2024, Karel Balej wrote:
> > > > diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h
> > > > new file mode 100644
> > > > index 000000000000..a5e6524bb19d
> > > > --- /dev/null
> > > > +++ b/include/linux/mfd/88pm886.h
> > > > @@ -0,0 +1,38 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-only */
> > > > +#ifndef __MFD_88PM886_H
> > > > +#define __MFD_88PM886_H
> > > > +
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/regmap.h>
> > > > +
> > > > +#define PM886_A1_CHIP_ID		0xa1
> > > > +
> > > > +#define PM886_REGMAP_CONF_MAX_REG	0xfe
> > > > +
> > > > +#define PM886_REG_ID			0x00
> > > > +
> > > > +#define PM886_REG_STATUS1		0x01
> > > > +#define PM886_ONKEY_STS1		BIT(0)
> > > > +
> > > > +#define PM886_REG_MISC_CONFIG1		0x14
> > > > +#define PM886_SW_PDOWN			BIT(5)
> > > > +
> > > > +#define PM886_REG_MISC_CONFIG2		0x15
> > > > +#define PM886_INT_INV			BIT(0)
> > > > +#define PM886_INT_CLEAR			BIT(1)
> > > > +#define PM886_INT_RC			0x00
> > > > +#define PM886_INT_WC			BIT(1)
> > > > +#define PM886_INT_MASK_MODE		BIT(2)
> > > > +
> > > > +struct pm886_chip {
> > > > +	struct i2c_client *client;
> > > > +	unsigned int chip_id;
> > > > +	struct regmap *regmap;
> > > > +};
> > > > +
> > > > +static const struct regmap_config pm886_i2c_regmap = {
> > > > +	.reg_bits = 8,
> > > > +	.val_bits = 8,
> > > > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > > > +};
> > >
> > > Why is this in here?
> > 
> > Because since I moved the regulators regmap initialization into the
> > regulators driver, I need to access it from there.
>
> So move it into the regulators driver?

It is used in the MFD driver too for the base regmap.

K. B.

^ permalink raw reply

* Re: [PATCH] MAINTAINERS: update Benjamin's email address
From: Jiri Kosina @ 2024-03-21 16:39 UTC (permalink / raw)
  To: Benjamin Tissoires; +Cc: linux-input, linux-kernel
In-Reply-To: <20240321-bentiss_kernel-v1-1-eeaa04b4bfbc@kernel.org>

On Thu, 21 Mar 2024, Benjamin Tissoires wrote:

> Update my email address to the kernel.org one, as it's getting
> more convenient this way.
> 
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>

Appplied to hid.git#for-6.9/upstream-fixes.

> ---
> Basically it's getting easier for me to use the kernel.org address
> instead of my company's one. So let's do it.
> 
> Note sure if I should go through the whole tree to change the different
> occurences of my email, but worse case this can happen in a separate
> patch.

That's exactly why I prefer not to put e-mail address in copyright headers 
at all in the first place :)

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Lee Jones @ 2024-03-21 16:20 UTC (permalink / raw)
  To: Karel Balej
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Liam Girdwood, Mark Brown, devicetree, linux-kernel, linux-input,
	Duje Mihanović, ~postmarketos/upstreaming, phone-devel
In-Reply-To: <CZZK759UU6G7.MFPYOI0HBB6I@matfyz.cz>

On Thu, 21 Mar 2024, Karel Balej wrote:

> Lee Jones, 2024-03-21T15:42:11+00:00:
> > On Mon, 11 Mar 2024, Karel Balej wrote:
> >
> > > From: Karel Balej <balejk@matfyz.cz>
> > > 
> > > Marvell 88PM886 is a PMIC which provides various functions such as
> > > onkey, battery, charger and regulators. It is found for instance in the
> > > samsung,coreprimevelte smartphone with which this was tested. Implement
> > > basic support to allow for the use of regulators and onkey.
> > > 
> > > Signed-off-by: Karel Balej <balejk@matfyz.cz>
> > > ---
> > > 
> > > Notes:
> > >     RFC v4:
> > >     - Use MFD_CELL_* macros.
> > >     - Address Lee's feedback:
> > >       - Do not define regmap_config.val_bits and .reg_bits.
> > >       - Drop everything regulator related except mfd_cell (regmap
> > >         initialization, IDs enum etc.). Drop pm886_initialize_subregmaps.
> > >       - Do not store regmap pointers as an array as there is now only one
> > >         regmap. Also drop the corresponding enum.
> > >       - Move regmap_config to the header as it is needed in the regulators
> > >         driver.
> > >       - pm886_chip.whoami -> chip_id
> > >       - Reword chip ID mismatch error message and print the ID as
> > >         hexadecimal.
> > >       - Fix includes in include/linux/88pm886.h.
> > >       - Drop the pm886_irq_number enum and define the (for the moment) only
> > >         IRQ explicitly.
> > >     - Have only one MFD cell for all regulators as they are now registered
> > >       all at once in the regulators driver.
> > >     - Reword commit message.
> > >     - Make device table static and remove comma after the sentinel to signal
> > >       that nothing should come after it.
> > >     RFC v3:
> > >     - Drop onkey cell .of_compatible.
> > >     - Rename LDO page offset and regmap to REGULATORS.
> > >     RFC v2:
> > >     - Remove some abstraction.
> > >     - Sort includes alphabetically and add linux/of.h.
> > >     - Depend on OF, remove of_match_ptr and add MODULE_DEVICE_TABLE.
> > >     - Use more temporaries and break long lines.
> > >     - Do not initialize ret in probe.
> > >     - Use the wakeup-source DT property.
> > >     - Rename ret to err.
> > >     - Address Lee's comments:
> > >       - Drop patched in presets for base regmap and related defines.
> > >       - Use full sentences in comments.
> > >       - Remove IRQ comment.
> > >       - Define regmap_config member values.
> > >       - Rename data to sys_off_data.
> > >       - Add _PMIC suffix to Kconfig.
> > >       - Use dev_err_probe.
> > >       - Do not store irq_data.
> > >       - s/WHOAMI/CHIP_ID
> > >       - Drop LINUX part of include guard name.
> > >       - Merge in the regulator series modifications in order to have more
> > >         devices and modify the commit message accordingly. Changes with
> > >         respect to the original regulator series patches:
> > >         - ret -> err
> > >         - Add temporary for dev in pm88x_initialize_subregmaps.
> > >         - Drop of_compatible for the regulators.
> > >         - Do not duplicate LDO regmap for bucks.
> > >     - Rewrite commit message.
> > > 
> > >  drivers/mfd/88pm886.c       | 149 ++++++++++++++++++++++++++++++++++++
> > >  drivers/mfd/Kconfig         |  12 +++
> > >  drivers/mfd/Makefile        |   1 +
> > >  include/linux/mfd/88pm886.h |  38 +++++++++
> > >  4 files changed, 200 insertions(+)
> > >  create mode 100644 drivers/mfd/88pm886.c
> > >  create mode 100644 include/linux/mfd/88pm886.h
> >
> > Looks mostly okay.
> >
> > > diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h
> > > new file mode 100644
> > > index 000000000000..a5e6524bb19d
> > > --- /dev/null
> > > +++ b/include/linux/mfd/88pm886.h
> > > @@ -0,0 +1,38 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-only */
> > > +#ifndef __MFD_88PM886_H
> > > +#define __MFD_88PM886_H
> > > +
> > > +#include <linux/i2c.h>
> > > +#include <linux/regmap.h>
> > > +
> > > +#define PM886_A1_CHIP_ID		0xa1
> > > +
> > > +#define PM886_REGMAP_CONF_MAX_REG	0xfe
> > > +
> > > +#define PM886_REG_ID			0x00
> > > +
> > > +#define PM886_REG_STATUS1		0x01
> > > +#define PM886_ONKEY_STS1		BIT(0)
> > > +
> > > +#define PM886_REG_MISC_CONFIG1		0x14
> > > +#define PM886_SW_PDOWN			BIT(5)
> > > +
> > > +#define PM886_REG_MISC_CONFIG2		0x15
> > > +#define PM886_INT_INV			BIT(0)
> > > +#define PM886_INT_CLEAR			BIT(1)
> > > +#define PM886_INT_RC			0x00
> > > +#define PM886_INT_WC			BIT(1)
> > > +#define PM886_INT_MASK_MODE		BIT(2)
> > > +
> > > +struct pm886_chip {
> > > +	struct i2c_client *client;
> > > +	unsigned int chip_id;
> > > +	struct regmap *regmap;
> > > +};
> > > +
> > > +static const struct regmap_config pm886_i2c_regmap = {
> > > +	.reg_bits = 8,
> > > +	.val_bits = 8,
> > > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > > +};
> >
> > Why is this in here?
> 
> Because since I moved the regulators regmap initialization into the
> regulators driver, I need to access it from there.

So move it into the regulators driver?

> > What would you like me to do with this RFC patch?
> 
> I was hoping that you would take this through the MFD tree (after the
> regulator subsystem maintainers approve the regulators driver).
> 
> I have added the remaining regulators to the driver just today and plan
> to send the first non-RFC version of the series after I test it, likely
> over the weekend. Hopefully the regulators patch will finally get some
> attention then.

Don't forget that the merge-window is open.

Many maintainers take a well deserved nap during this period.

-- 
Lee Jones [李琼斯]

^ permalink raw reply

* Re: [PATCH v4 00/10] backlight: Replace struct fb_info in interfaces
From: Lee Jones @ 2024-03-21 16:13 UTC (permalink / raw)
  To: andy, daniel.thompson, jingoohan1, deller, robin, javierm,
	Thomas Zimmermann
  Cc: dri-devel, linux-fbdev, linux-input, linux-pwm
In-Reply-To: <171103756721.89062.17090257592751026195.b4-ty@kernel.org>

On Thu, 21 Mar 2024, Lee Jones wrote:

> On Tue, 05 Mar 2024 17:22:33 +0100, Thomas Zimmermann wrote:
> > Backlight drivers implement struct backlight_ops.check_fb, which
> > uses struct fb_info in its interface. Replace the callback with one
> > that does not use fb_info.
> > 
> > In DRM, we have several drivers that implement backlight support. By
> > including <linux/backlight.h> these drivers depend on <linux/fb.h>.
> > At the same time, fbdev is deprecated for new drivers and likely to
> > be replaced on many systems.
> > 
> > [...]
> 
> Applied, thanks!
> 
> [01/10] backlight: Match backlight device against struct fb_info.bl_dev
>         commit: f1ecddf747f0d734682152b37c927aa958a51497
> [02/10] auxdisplay/ht16k33: Remove struct backlight_ops.check_fb
>         commit: dddfda7d5f12a7b48aeca6c3840167529c8cd34a
> [03/10] hid/hid-picolcd: Fix initialization order
>         commit: a951a15002da620871d8f3d8218c043cdc4c2471
> [04/10] hid/hid-picolcd: Remove struct backlight_ops.check_fb
>         commit: b3c52552f8d8a816bda2bda984411c73f4dd0b87
> [05/10] backlight/aat2870-backlight: Remove struct backlight.check_fb
>         commit: 0e03c96046405281fb072c05a7810d2661a2f334
> [06/10] backlight/pwm-backlight: Remove struct backlight_ops.check_fb
>         commit: 78534967e7cb3c2fbfcb2d37820b51a80c570f90
> [07/10] fbdev/sh_mobile_lcdc_fb: Remove struct backlight_ops.check_fb
>         commit: b853c08cd6598b3b3ff91cb2bba336bfef9c0ac4
> [08/10] fbdev/ssd1307fb: Init backlight before registering framebuffer
>         commit: d5ae81e965953da27cf46db6281d6a6a28eaaccb
> [09/10] fbdev/ssd1307fb: Remove struct backlight_ops.check_fb
>         commit: ec5925ef4a2dfd7ee060f4fd2a2e8036f8a94e8e
> [10/10] backlight: Add controls_device callback to struct backlight_ops
>         commit: 2e427743de015c1ac047036ef495c3f004105439

Okay, let's try this again.

Send off for more build testing based on v6.8.

Will report back once complete.

-- 
Lee Jones [李琼斯]

^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Karel Balej @ 2024-03-21 16:12 UTC (permalink / raw)
  To: Lee Jones
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
	Liam Girdwood, Mark Brown, devicetree, linux-kernel, linux-input,
	Duje Mihanović, ~postmarketos/upstreaming, phone-devel
In-Reply-To: <20240321154211.GA13211@google.com>

Lee Jones, 2024-03-21T15:42:11+00:00:
> On Mon, 11 Mar 2024, Karel Balej wrote:
>
> > From: Karel Balej <balejk@matfyz.cz>
> > 
> > Marvell 88PM886 is a PMIC which provides various functions such as
> > onkey, battery, charger and regulators. It is found for instance in the
> > samsung,coreprimevelte smartphone with which this was tested. Implement
> > basic support to allow for the use of regulators and onkey.
> > 
> > Signed-off-by: Karel Balej <balejk@matfyz.cz>
> > ---
> > 
> > Notes:
> >     RFC v4:
> >     - Use MFD_CELL_* macros.
> >     - Address Lee's feedback:
> >       - Do not define regmap_config.val_bits and .reg_bits.
> >       - Drop everything regulator related except mfd_cell (regmap
> >         initialization, IDs enum etc.). Drop pm886_initialize_subregmaps.
> >       - Do not store regmap pointers as an array as there is now only one
> >         regmap. Also drop the corresponding enum.
> >       - Move regmap_config to the header as it is needed in the regulators
> >         driver.
> >       - pm886_chip.whoami -> chip_id
> >       - Reword chip ID mismatch error message and print the ID as
> >         hexadecimal.
> >       - Fix includes in include/linux/88pm886.h.
> >       - Drop the pm886_irq_number enum and define the (for the moment) only
> >         IRQ explicitly.
> >     - Have only one MFD cell for all regulators as they are now registered
> >       all at once in the regulators driver.
> >     - Reword commit message.
> >     - Make device table static and remove comma after the sentinel to signal
> >       that nothing should come after it.
> >     RFC v3:
> >     - Drop onkey cell .of_compatible.
> >     - Rename LDO page offset and regmap to REGULATORS.
> >     RFC v2:
> >     - Remove some abstraction.
> >     - Sort includes alphabetically and add linux/of.h.
> >     - Depend on OF, remove of_match_ptr and add MODULE_DEVICE_TABLE.
> >     - Use more temporaries and break long lines.
> >     - Do not initialize ret in probe.
> >     - Use the wakeup-source DT property.
> >     - Rename ret to err.
> >     - Address Lee's comments:
> >       - Drop patched in presets for base regmap and related defines.
> >       - Use full sentences in comments.
> >       - Remove IRQ comment.
> >       - Define regmap_config member values.
> >       - Rename data to sys_off_data.
> >       - Add _PMIC suffix to Kconfig.
> >       - Use dev_err_probe.
> >       - Do not store irq_data.
> >       - s/WHOAMI/CHIP_ID
> >       - Drop LINUX part of include guard name.
> >       - Merge in the regulator series modifications in order to have more
> >         devices and modify the commit message accordingly. Changes with
> >         respect to the original regulator series patches:
> >         - ret -> err
> >         - Add temporary for dev in pm88x_initialize_subregmaps.
> >         - Drop of_compatible for the regulators.
> >         - Do not duplicate LDO regmap for bucks.
> >     - Rewrite commit message.
> > 
> >  drivers/mfd/88pm886.c       | 149 ++++++++++++++++++++++++++++++++++++
> >  drivers/mfd/Kconfig         |  12 +++
> >  drivers/mfd/Makefile        |   1 +
> >  include/linux/mfd/88pm886.h |  38 +++++++++
> >  4 files changed, 200 insertions(+)
> >  create mode 100644 drivers/mfd/88pm886.c
> >  create mode 100644 include/linux/mfd/88pm886.h
>
> Looks mostly okay.
>
> > diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h
> > new file mode 100644
> > index 000000000000..a5e6524bb19d
> > --- /dev/null
> > +++ b/include/linux/mfd/88pm886.h
> > @@ -0,0 +1,38 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +#ifndef __MFD_88PM886_H
> > +#define __MFD_88PM886_H
> > +
> > +#include <linux/i2c.h>
> > +#include <linux/regmap.h>
> > +
> > +#define PM886_A1_CHIP_ID		0xa1
> > +
> > +#define PM886_REGMAP_CONF_MAX_REG	0xfe
> > +
> > +#define PM886_REG_ID			0x00
> > +
> > +#define PM886_REG_STATUS1		0x01
> > +#define PM886_ONKEY_STS1		BIT(0)
> > +
> > +#define PM886_REG_MISC_CONFIG1		0x14
> > +#define PM886_SW_PDOWN			BIT(5)
> > +
> > +#define PM886_REG_MISC_CONFIG2		0x15
> > +#define PM886_INT_INV			BIT(0)
> > +#define PM886_INT_CLEAR			BIT(1)
> > +#define PM886_INT_RC			0x00
> > +#define PM886_INT_WC			BIT(1)
> > +#define PM886_INT_MASK_MODE		BIT(2)
> > +
> > +struct pm886_chip {
> > +	struct i2c_client *client;
> > +	unsigned int chip_id;
> > +	struct regmap *regmap;
> > +};
> > +
> > +static const struct regmap_config pm886_i2c_regmap = {
> > +	.reg_bits = 8,
> > +	.val_bits = 8,
> > +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> > +};
>
> Why is this in here?

Because since I moved the regulators regmap initialization into the
regulators driver, I need to access it from there.

> What would you like me to do with this RFC patch?

I was hoping that you would take this through the MFD tree (after the
regulator subsystem maintainers approve the regulators driver).

I have added the remaining regulators to the driver just today and plan
to send the first non-RFC version of the series after I test it, likely
over the weekend. Hopefully the regulators patch will finally get some
attention then.

Thank you, best regards,
K. B.

^ permalink raw reply

* Re: [PATCH v4 00/10] backlight: Replace struct fb_info in interfaces
From: Lee Jones @ 2024-03-21 16:12 UTC (permalink / raw)
  To: lee, andy, daniel.thompson, jingoohan1, deller, robin, javierm,
	Thomas Zimmermann
  Cc: dri-devel, linux-fbdev, linux-input, linux-pwm
In-Reply-To: <20240305162425.23845-1-tzimmermann@suse.de>

On Tue, 05 Mar 2024 17:22:33 +0100, Thomas Zimmermann wrote:
> Backlight drivers implement struct backlight_ops.check_fb, which
> uses struct fb_info in its interface. Replace the callback with one
> that does not use fb_info.
> 
> In DRM, we have several drivers that implement backlight support. By
> including <linux/backlight.h> these drivers depend on <linux/fb.h>.
> At the same time, fbdev is deprecated for new drivers and likely to
> be replaced on many systems.
> 
> [...]

Applied, thanks!

[01/10] backlight: Match backlight device against struct fb_info.bl_dev
        commit: f1ecddf747f0d734682152b37c927aa958a51497
[02/10] auxdisplay/ht16k33: Remove struct backlight_ops.check_fb
        commit: dddfda7d5f12a7b48aeca6c3840167529c8cd34a
[03/10] hid/hid-picolcd: Fix initialization order
        commit: a951a15002da620871d8f3d8218c043cdc4c2471
[04/10] hid/hid-picolcd: Remove struct backlight_ops.check_fb
        commit: b3c52552f8d8a816bda2bda984411c73f4dd0b87
[05/10] backlight/aat2870-backlight: Remove struct backlight.check_fb
        commit: 0e03c96046405281fb072c05a7810d2661a2f334
[06/10] backlight/pwm-backlight: Remove struct backlight_ops.check_fb
        commit: 78534967e7cb3c2fbfcb2d37820b51a80c570f90
[07/10] fbdev/sh_mobile_lcdc_fb: Remove struct backlight_ops.check_fb
        commit: b853c08cd6598b3b3ff91cb2bba336bfef9c0ac4
[08/10] fbdev/ssd1307fb: Init backlight before registering framebuffer
        commit: d5ae81e965953da27cf46db6281d6a6a28eaaccb
[09/10] fbdev/ssd1307fb: Remove struct backlight_ops.check_fb
        commit: ec5925ef4a2dfd7ee060f4fd2a2e8036f8a94e8e
[10/10] backlight: Add controls_device callback to struct backlight_ops
        commit: 2e427743de015c1ac047036ef495c3f004105439

--
Lee Jones [李琼斯]


^ permalink raw reply

* Re: [RFC PATCH v4 2/5] mfd: add driver for Marvell 88PM886 PMIC
From: Lee Jones @ 2024-03-21 15:42 UTC (permalink / raw)
  To: Karel Balej
  Cc: Karel Balej, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Dmitry Torokhov, Liam Girdwood, Mark Brown, devicetree,
	linux-kernel, linux-input, Duje Mihanović,
	~postmarketos/upstreaming, phone-devel
In-Reply-To: <20240311160110.32185-3-karelb@gimli.ms.mff.cuni.cz>

On Mon, 11 Mar 2024, Karel Balej wrote:

> From: Karel Balej <balejk@matfyz.cz>
> 
> Marvell 88PM886 is a PMIC which provides various functions such as
> onkey, battery, charger and regulators. It is found for instance in the
> samsung,coreprimevelte smartphone with which this was tested. Implement
> basic support to allow for the use of regulators and onkey.
> 
> Signed-off-by: Karel Balej <balejk@matfyz.cz>
> ---
> 
> Notes:
>     RFC v4:
>     - Use MFD_CELL_* macros.
>     - Address Lee's feedback:
>       - Do not define regmap_config.val_bits and .reg_bits.
>       - Drop everything regulator related except mfd_cell (regmap
>         initialization, IDs enum etc.). Drop pm886_initialize_subregmaps.
>       - Do not store regmap pointers as an array as there is now only one
>         regmap. Also drop the corresponding enum.
>       - Move regmap_config to the header as it is needed in the regulators
>         driver.
>       - pm886_chip.whoami -> chip_id
>       - Reword chip ID mismatch error message and print the ID as
>         hexadecimal.
>       - Fix includes in include/linux/88pm886.h.
>       - Drop the pm886_irq_number enum and define the (for the moment) only
>         IRQ explicitly.
>     - Have only one MFD cell for all regulators as they are now registered
>       all at once in the regulators driver.
>     - Reword commit message.
>     - Make device table static and remove comma after the sentinel to signal
>       that nothing should come after it.
>     RFC v3:
>     - Drop onkey cell .of_compatible.
>     - Rename LDO page offset and regmap to REGULATORS.
>     RFC v2:
>     - Remove some abstraction.
>     - Sort includes alphabetically and add linux/of.h.
>     - Depend on OF, remove of_match_ptr and add MODULE_DEVICE_TABLE.
>     - Use more temporaries and break long lines.
>     - Do not initialize ret in probe.
>     - Use the wakeup-source DT property.
>     - Rename ret to err.
>     - Address Lee's comments:
>       - Drop patched in presets for base regmap and related defines.
>       - Use full sentences in comments.
>       - Remove IRQ comment.
>       - Define regmap_config member values.
>       - Rename data to sys_off_data.
>       - Add _PMIC suffix to Kconfig.
>       - Use dev_err_probe.
>       - Do not store irq_data.
>       - s/WHOAMI/CHIP_ID
>       - Drop LINUX part of include guard name.
>       - Merge in the regulator series modifications in order to have more
>         devices and modify the commit message accordingly. Changes with
>         respect to the original regulator series patches:
>         - ret -> err
>         - Add temporary for dev in pm88x_initialize_subregmaps.
>         - Drop of_compatible for the regulators.
>         - Do not duplicate LDO regmap for bucks.
>     - Rewrite commit message.
> 
>  drivers/mfd/88pm886.c       | 149 ++++++++++++++++++++++++++++++++++++
>  drivers/mfd/Kconfig         |  12 +++
>  drivers/mfd/Makefile        |   1 +
>  include/linux/mfd/88pm886.h |  38 +++++++++
>  4 files changed, 200 insertions(+)
>  create mode 100644 drivers/mfd/88pm886.c
>  create mode 100644 include/linux/mfd/88pm886.h

Looks mostly okay.

> diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h
> new file mode 100644
> index 000000000000..a5e6524bb19d
> --- /dev/null
> +++ b/include/linux/mfd/88pm886.h
> @@ -0,0 +1,38 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __MFD_88PM886_H
> +#define __MFD_88PM886_H
> +
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +
> +#define PM886_A1_CHIP_ID		0xa1
> +
> +#define PM886_REGMAP_CONF_MAX_REG	0xfe
> +
> +#define PM886_REG_ID			0x00
> +
> +#define PM886_REG_STATUS1		0x01
> +#define PM886_ONKEY_STS1		BIT(0)
> +
> +#define PM886_REG_MISC_CONFIG1		0x14
> +#define PM886_SW_PDOWN			BIT(5)
> +
> +#define PM886_REG_MISC_CONFIG2		0x15
> +#define PM886_INT_INV			BIT(0)
> +#define PM886_INT_CLEAR			BIT(1)
> +#define PM886_INT_RC			0x00
> +#define PM886_INT_WC			BIT(1)
> +#define PM886_INT_MASK_MODE		BIT(2)
> +
> +struct pm886_chip {
> +	struct i2c_client *client;
> +	unsigned int chip_id;
> +	struct regmap *regmap;
> +};
> +
> +static const struct regmap_config pm886_i2c_regmap = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = PM886_REGMAP_CONF_MAX_REG,
> +};

Why is this in here?

> +#endif /* __MFD_88PM886_H */

What would you like me to do with this RFC patch?

-- 
Lee Jones [李琼斯]

^ permalink raw reply

* [PATCH] MAINTAINERS: update Benjamin's email address
From: Benjamin Tissoires @ 2024-03-21 15:19 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel

Update my email address to the kernel.org one, as it's getting
more convenient this way.

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
Basically it's getting easier for me to use the kernel.org address
instead of my company's one. So let's do it.

Note sure if I should go through the whole tree to change the different
occurences of my email, but worse case this can happen in a separate
patch.
---
 .mailmap    | 2 ++
 MAINTAINERS | 4 ++--
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.mailmap b/.mailmap
index e90797de3256..25019ddf467c 100644
--- a/.mailmap
+++ b/.mailmap
@@ -96,6 +96,8 @@ Ben Widawsky <bwidawsk@kernel.org> <ben@bwidawsk.net>
 Ben Widawsky <bwidawsk@kernel.org> <ben.widawsky@intel.com>
 Ben Widawsky <bwidawsk@kernel.org> <benjamin.widawsky@intel.com>
 Benjamin Poirier <benjamin.poirier@gmail.com> <bpoirier@suse.de>
+Benjamin Tissoires <bentiss@kernel.org> <benjamin.tissoires@gmail.com>
+Benjamin Tissoires <bentiss@kernel.org> <benjamin.tissoires@redhat.com>
 Bjorn Andersson <andersson@kernel.org> <bjorn@kryo.se>
 Bjorn Andersson <andersson@kernel.org> <bjorn.andersson@linaro.org>
 Bjorn Andersson <andersson@kernel.org> <bjorn.andersson@sonymobile.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index c50e72258ba9..52bb6f2c3e7b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9546,7 +9546,7 @@ F:	kernel/power/
 
 HID CORE LAYER
 M:	Jiri Kosina <jikos@kernel.org>
-M:	Benjamin Tissoires <benjamin.tissoires@redhat.com>
+M:	Benjamin Tissoires <bentiss@kernel.org>
 L:	linux-input@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
@@ -22685,7 +22685,7 @@ F:	drivers/usb/host/ehci*
 
 USB HID/HIDBP DRIVERS (USB KEYBOARDS, MICE, REMOTE CONTROLS, ...)
 M:	Jiri Kosina <jikos@kernel.org>
-M:	Benjamin Tissoires <benjamin.tissoires@redhat.com>
+M:	Benjamin Tissoires <bentiss@kernel.org>
 L:	linux-usb@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git

---
base-commit: 3e78a6c0d3e02e4cf881dc84c5127e9990f939d6
change-id: 20240321-bentiss_kernel-93118ff173aa

Best regards,
-- 
Benjamin Tissoires <bentiss@kernel.org>


^ permalink raw reply related

* Re: [PATCH 1/2] HID: uclogic: Store firmware name in params
From: Benjamin Tissoires @ 2024-03-21 15:01 UTC (permalink / raw)
  To: José Expósito
  Cc: jikos, benjamin.tissoires, linux-input, linux-kernel
In-Reply-To: <20240321144553.262409-2-jose.exposito89@gmail.com>


Hi José,

On Mar 21 2024, José Expósito wrote:
> A future patch will need to access the firmware name to expose it to
> userspace via sysfs.
> 
> Store it in `uclogic_params->fw_name`.
> 
> Signed-off-by: José Expósito <jose.exposito89@gmail.com>
> ---
>  drivers/hid/hid-uclogic-params.c | 14 +++++++-------
>  drivers/hid/hid-uclogic-params.h |  5 +++++
>  2 files changed, 12 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
> index 9859dad36495..79ec3eb80f84 100644
> --- a/drivers/hid/hid-uclogic-params.c
> +++ b/drivers/hid/hid-uclogic-params.c
> @@ -121,6 +121,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
>  		params->invalid ? "true" : "false");
>  	hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
>  	hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
> +	hid_dbg(hdev, ".fw_name = %s\n", params->fw_name);
>  	hid_dbg(hdev, ".pen = {\n");
>  	uclogic_params_pen_hid_dbg(hdev, &params->pen);
>  	hid_dbg(hdev, "\t}\n");
> @@ -652,6 +653,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
>  	if (!params->invalid) {
>  		size_t i;
>  		kfree(params->desc_ptr);
> +		kfree(params->fw_name);
>  		uclogic_params_pen_cleanup(&params->pen);
>  		for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
>  			uclogic_params_frame_cleanup(&params->frame_list[i]);
> @@ -837,7 +839,6 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
>  	/* The resulting parameters (noop) */
>  	struct uclogic_params p = {0, };
>  	static const char transition_ver[] = "HUION_T153_160607";
> -	char *ver_ptr = NULL;
>  	const size_t ver_len = sizeof(transition_ver) + 1;
>  	__u8 *params_ptr = NULL;
>  	size_t params_len = 0;
> @@ -870,14 +871,14 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
>  	}
>  
>  	/* Try to get firmware version */
> -	ver_ptr = kzalloc(ver_len, GFP_KERNEL);
> -	if (ver_ptr == NULL) {
> +	p.fw_name = kzalloc(ver_len, GFP_KERNEL);
> +	if (!p.fw_name) {
>  		rc = -ENOMEM;
>  		goto cleanup;
>  	}
> -	rc = usb_string(udev, 201, ver_ptr, ver_len);
> +	rc = usb_string(udev, 201, p.fw_name, ver_len);
>  	if (rc == -EPIPE) {
> -		*ver_ptr = '\0';
> +		*p.fw_name = '\0';
>  	} else if (rc < 0) {
>  		hid_err(hdev,
>  			"failed retrieving Huion firmware version: %d\n", rc);
> @@ -885,7 +886,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
>  	}
>  
>  	/* If this is a transition firmware */
> -	if (strcmp(ver_ptr, transition_ver) == 0) {
> +	if (strcmp(p.fw_name, transition_ver) == 0) {
>  		hid_dbg(hdev,
>  			"transition firmware detected, not probing pen v2 parameters\n");
>  	} else {
> @@ -1028,7 +1029,6 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
>  	rc = 0;
>  cleanup:
>  	kfree(params_ptr);
> -	kfree(ver_ptr);
>  	uclogic_params_cleanup(&p);
>  	return rc;
>  }
> diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h
> index d6ffadb2f601..412c916770f5 100644
> --- a/drivers/hid/hid-uclogic-params.h
> +++ b/drivers/hid/hid-uclogic-params.h
> @@ -232,6 +232,11 @@ struct uclogic_params {
>  	 * List of event hooks.
>  	 */
>  	struct uclogic_raw_event_hook *event_hooks;
> +	/*
> +	 * Firmware name, exposed to userspace via sysfs as it is used to
> +	 * identify the tablet.
> +	 */
> +	char *fw_name;

I can't remember if this was on the table or not, but any reasons to not
use hid->uniq[64] field instead of a custom sysfs?
If there is already a value, we could just append the firmware version
with a colon (:) separator.

The main reason would be to not export a new sysfs specifically for this
driver, but it would also allow HID-BPF to have access to the value and
thus allow to detect if the device works with the given bpf program.

Cheers,
Benjamin

>  };
>  
>  /* Driver data */
> -- 
> 2.44.0
> 

^ permalink raw reply

* [PATCH 2/2] HID: uclogic: Expose firmware name via sysfs
From: José Expósito @ 2024-03-21 14:38 UTC (permalink / raw)
  To: jikos
  Cc: benjamin.tissoires, linux-input, linux-kernel,
	José Expósito
In-Reply-To: <20240321144553.262409-1-jose.exposito89@gmail.com>

Some vendors reuse the same product ID for different tablets, making it
difficult for userspace to figure out which table is connected.
While matching the device name has been used in the past by userspace to
workaround this limitation, some devices have shown that this is not
always a valid approach [1].

However, if userspace could access the firmware version name, it would
be possible to know which tablet is actually connected by matching it
against a list of known firmware names [2].

This patch exposes the firmware version name via sysfs attribute.

Link: https://github.com/linuxwacom/libwacom/issues/609  [1]
Link: https://github.com/linuxwacom/libwacom/issues/610  [2]
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
---
 drivers/hid/hid-uclogic-core.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index ad74cbc9a0aa..1862ca5805a7 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -50,6 +50,17 @@ static void uclogic_inrange_timeout(struct timer_list *t)
 	input_sync(input);
 }
 
+static ssize_t fw_name_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	return sysfs_emit(buf, "%s\n", drvdata->params.fw_name);
+}
+
+static DEVICE_ATTR_RO(fw_name);
+
 static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 					unsigned int *rsize)
 {
@@ -217,6 +228,10 @@ static int uclogic_probe(struct hid_device *hdev,
 		goto failure;
 	}
 
+	rc = device_create_file(&hdev->dev, &dev_attr_fw_name);
+	if (rc)
+		hid_warn(hdev, "Unable to create sysfs attribute \"fw_name\", errno %d\n", rc);
+
 	return 0;
 failure:
 	/* Assume "remove" might not be called if "probe" failed */
@@ -477,6 +492,7 @@ static void uclogic_remove(struct hid_device *hdev)
 	del_timer_sync(&drvdata->inrange_timer);
 	hid_hw_stop(hdev);
 	kfree(drvdata->desc_ptr);
+	device_remove_file(&hdev->dev, &dev_attr_fw_name);
 	uclogic_params_cleanup(&drvdata->params);
 }
 
-- 
2.44.0


^ permalink raw reply related

* [PATCH 1/2] HID: uclogic: Store firmware name in params
From: José Expósito @ 2024-03-21 14:38 UTC (permalink / raw)
  To: jikos
  Cc: benjamin.tissoires, linux-input, linux-kernel,
	José Expósito
In-Reply-To: <20240321144553.262409-1-jose.exposito89@gmail.com>

A future patch will need to access the firmware name to expose it to
userspace via sysfs.

Store it in `uclogic_params->fw_name`.

Signed-off-by: José Expósito <jose.exposito89@gmail.com>
---
 drivers/hid/hid-uclogic-params.c | 14 +++++++-------
 drivers/hid/hid-uclogic-params.h |  5 +++++
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 9859dad36495..79ec3eb80f84 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -121,6 +121,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
 		params->invalid ? "true" : "false");
 	hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
 	hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
+	hid_dbg(hdev, ".fw_name = %s\n", params->fw_name);
 	hid_dbg(hdev, ".pen = {\n");
 	uclogic_params_pen_hid_dbg(hdev, &params->pen);
 	hid_dbg(hdev, "\t}\n");
@@ -652,6 +653,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
 	if (!params->invalid) {
 		size_t i;
 		kfree(params->desc_ptr);
+		kfree(params->fw_name);
 		uclogic_params_pen_cleanup(&params->pen);
 		for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
 			uclogic_params_frame_cleanup(&params->frame_list[i]);
@@ -837,7 +839,6 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
 	/* The resulting parameters (noop) */
 	struct uclogic_params p = {0, };
 	static const char transition_ver[] = "HUION_T153_160607";
-	char *ver_ptr = NULL;
 	const size_t ver_len = sizeof(transition_ver) + 1;
 	__u8 *params_ptr = NULL;
 	size_t params_len = 0;
@@ -870,14 +871,14 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
 	}
 
 	/* Try to get firmware version */
-	ver_ptr = kzalloc(ver_len, GFP_KERNEL);
-	if (ver_ptr == NULL) {
+	p.fw_name = kzalloc(ver_len, GFP_KERNEL);
+	if (!p.fw_name) {
 		rc = -ENOMEM;
 		goto cleanup;
 	}
-	rc = usb_string(udev, 201, ver_ptr, ver_len);
+	rc = usb_string(udev, 201, p.fw_name, ver_len);
 	if (rc == -EPIPE) {
-		*ver_ptr = '\0';
+		*p.fw_name = '\0';
 	} else if (rc < 0) {
 		hid_err(hdev,
 			"failed retrieving Huion firmware version: %d\n", rc);
@@ -885,7 +886,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
 	}
 
 	/* If this is a transition firmware */
-	if (strcmp(ver_ptr, transition_ver) == 0) {
+	if (strcmp(p.fw_name, transition_ver) == 0) {
 		hid_dbg(hdev,
 			"transition firmware detected, not probing pen v2 parameters\n");
 	} else {
@@ -1028,7 +1029,6 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
 	rc = 0;
 cleanup:
 	kfree(params_ptr);
-	kfree(ver_ptr);
 	uclogic_params_cleanup(&p);
 	return rc;
 }
diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h
index d6ffadb2f601..412c916770f5 100644
--- a/drivers/hid/hid-uclogic-params.h
+++ b/drivers/hid/hid-uclogic-params.h
@@ -232,6 +232,11 @@ struct uclogic_params {
 	 * List of event hooks.
 	 */
 	struct uclogic_raw_event_hook *event_hooks;
+	/*
+	 * Firmware name, exposed to userspace via sysfs as it is used to
+	 * identify the tablet.
+	 */
+	char *fw_name;
 };
 
 /* Driver data */
-- 
2.44.0


^ permalink raw reply related

* [PATCH 0/2] Expose firmware name to identify tablets in libwacom
From: José Expósito @ 2024-03-21 14:38 UTC (permalink / raw)
  To: jikos
  Cc: benjamin.tissoires, linux-input, linux-kernel,
	José Expósito

Hi everyone,

This series exposes the UCLogic tablets firmware name to user userspace
via sysfs.

libwacom can use it to identify this family of tablets, which has been
very difficult until now because vendor keep reusing the same VIP/PID.

This is the PR consuming the new interface:
https://github.com/linuxwacom/libwacom/pull/620

Finally, I created a tool to generate a list of firmware name <-> table
model for UCLogic-like drawing tablets:
https://github.com/JoseExposito/uclogic-firmware-names

Best wishes,
José Expósito

José Expósito (2):
  HID: uclogic: Store firmware name in params
  HID: uclogic: Expose firmware name via sysfs

 drivers/hid/hid-uclogic-core.c   | 16 ++++++++++++++++
 drivers/hid/hid-uclogic-params.c | 14 +++++++-------
 drivers/hid/hid-uclogic-params.h |  5 +++++
 3 files changed, 28 insertions(+), 7 deletions(-)

-- 
2.44.0


^ permalink raw reply

* Re: [PATCH] HID: intel-ish-hid: ipc: Fix dev_err usage with uninitialized dev->devc
From: Jiri Kosina @ 2024-03-21 12:44 UTC (permalink / raw)
  To: srinivas pandruvada; +Cc: Zhang, Lixu, linux-input, benjamin.tissoires
In-Reply-To: <f5ca7644241a989779d85086ac55e8c100d926a2.camel@linux.intel.com>

On Tue, 5 Mar 2024, srinivas pandruvada wrote:

> > The variable dev->devc in ish_dev_init was utilized by dev_err before 
> > it was properly assigned. To rectify this, the assignment of dev->devc 
> > has been moved to immediately follow memory allocation.
> > 
> > Without this change "(NULL device *)" is printed for device
> > information.
> > 
> > Fixes: 8ae2f2b0a284 ("HID: intel-ish-hid: ipc: Fix potential use-
> > after-free in work function")
> > Fixes: ae02e5d40d5f ("HID: intel-ish-hid: ipc layer")
> > Signed-off-by: Zhang Lixu <lixu.zhang@intel.com>
> > Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> > ---
> This is not an important change to submit to rc cycle.
> This is just a logging issue.

Agreed about the severity. However it fixes a user-visible issue and 
doesn't have a potential to cause any regression, so I have just queued it 
in hid.git#for-6.9/upstream-fixes nevertheless.

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* Re: [PATCH] HID: i2c-hid: remove I2C_HID_READ_PENDING flag to prevent lock-up
From: Jiri Kosina @ 2024-03-21 12:31 UTC (permalink / raw)
  To: Nam Cao
  Cc: Benjamin Tissoires, Douglas Anderson, Hans de Goede,
	Maxime Ripard, Kai-Heng Feng, Johan Hovold, linux-input,
	linux-kernel, Eva Kurchatova, stable
In-Reply-To: <20240318105902.34789-1-namcao@linutronix.de>

On Mon, 18 Mar 2024, Nam Cao wrote:

> The flag I2C_HID_READ_PENDING is used to serialize I2C operations.
> However, this is not necessary, because I2C core already has its own
> locking for that.
> 
> More importantly, this flag can cause a lock-up: if the flag is set in
> i2c_hid_xfer() and an interrupt happens, the interrupt handler
> (i2c_hid_irq) will check this flag and return immediately without doing
> anything, then the interrupt handler will be invoked again in an
> infinite loop.
> 
> Since interrupt handler is an RT task, it takes over the CPU and the
> flag-clearing task never gets scheduled, thus we have a lock-up.
> 
> Delete this unnecessary flag.

Hmm, right, good catch, I can't figure out what extra semantic 
I2C_HID_READ_PENDING would be adding (rather than deadlock :) ).
Why RT throttling didn't happen and let the system in a completely locked 
up state is something I don't understand, but that's separate.

I have now queued this in hid.git#for-6.9/upstream-fixes

Thanks,

-- 
Jiri Kosina
SUSE Labs


^ permalink raw reply

* [PATCH v10 3/5] mfd: cs40l50: Add support for CS40L50 core driver
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
In-Reply-To: <20240320192956.2395731-1-jogletre@opensource.cirrus.com>

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The MFD component registers and initializes the device.

Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
 MAINTAINERS                 |   2 +
 drivers/mfd/Kconfig         |  30 ++
 drivers/mfd/Makefile        |   4 +
 drivers/mfd/cs40l50-core.c  | 570 ++++++++++++++++++++++++++++++++++++
 drivers/mfd/cs40l50-i2c.c   |  68 +++++
 drivers/mfd/cs40l50-spi.c   |  68 +++++
 include/linux/mfd/cs40l50.h | 137 +++++++++
 7 files changed, 879 insertions(+)
 create mode 100644 drivers/mfd/cs40l50-core.c
 create mode 100644 drivers/mfd/cs40l50-i2c.c
 create mode 100644 drivers/mfd/cs40l50-spi.c
 create mode 100644 include/linux/mfd/cs40l50.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 862a18e3fc7b..e804f3766cba 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4940,6 +4940,8 @@ M:	Ben Bright <ben.bright@cirrus.com>
 L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F:	drivers/mfd/cs40l*
+F:	include/linux/mfd/cs40l*
 
 CIRRUS LOGIC DSP FIRMWARE DRIVER
 M:	Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 90ce58fd629e..6273c255f107 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2241,6 +2241,36 @@ config MCP_UCB1200_TS
 
 endmenu
 
+config MFD_CS40L50_CORE
+	tristate
+	select MFD_CORE
+	select FW_CS_DSP
+	select REGMAP_IRQ
+
+config MFD_CS40L50_I2C
+	tristate "Cirrus Logic CS40L50 (I2C)"
+	select REGMAP_I2C
+	select MFD_CS40L50_CORE
+	depends on I2C
+	help
+	  Select this to support the Cirrus Logic CS40L50 Haptic
+	  Driver over I2C.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "cs40l50-i2c".
+
+config MFD_CS40L50_SPI
+	tristate "Cirrus Logic CS40L50 (SPI)"
+	select REGMAP_SPI
+	select MFD_CS40L50_CORE
+	depends on SPI
+	help
+	  Select this to support the Cirrus Logic CS40L50 Haptic
+	  Driver over SPI.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "cs40l50-spi".
+
 config MFD_VEXPRESS_SYSREG
 	tristate "Versatile Express System Registers"
 	depends on VEXPRESS_CONFIG && GPIOLIB
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..a8d18ba155d0 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -88,6 +88,10 @@ obj-$(CONFIG_MFD_MADERA)	+= madera.o
 obj-$(CONFIG_MFD_MADERA_I2C)	+= madera-i2c.o
 obj-$(CONFIG_MFD_MADERA_SPI)	+= madera-spi.o
 
+obj-$(CONFIG_MFD_CS40L50_CORE)	+= cs40l50-core.o
+obj-$(CONFIG_MFD_CS40L50_I2C)	+= cs40l50-i2c.o
+obj-$(CONFIG_MFD_CS40L50_SPI)	+= cs40l50-spi.o
+
 obj-$(CONFIG_TPS6105X)		+= tps6105x.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
 obj-$(CONFIG_TPS6507X)		+= tps6507x.o
diff --git a/drivers/mfd/cs40l50-core.c b/drivers/mfd/cs40l50-core.c
new file mode 100644
index 000000000000..34d0b1cfc486
--- /dev/null
+++ b/drivers/mfd/cs40l50-core.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+static const struct mfd_cell cs40l50_devs[] = {
+	{ .name = "cs40l50-codec", },
+	{ .name = "cs40l50-vibra", },
+};
+
+const struct regmap_config cs40l50_regmap = {
+	.reg_bits =		32,
+	.reg_stride =		4,
+	.val_bits =		32,
+	.reg_format_endian =	REGMAP_ENDIAN_BIG,
+	.val_format_endian =	REGMAP_ENDIAN_BIG,
+};
+EXPORT_SYMBOL_GPL(cs40l50_regmap);
+
+static const char * const cs40l50_supplies[] = {
+	"vdd-io",
+};
+
+static const struct regmap_irq cs40l50_reg_irqs[] = {
+	REGMAP_IRQ_REG(CS40L50_DSP_QUEUE_IRQ, CS40L50_IRQ1_INT_2_OFFSET,
+		       CS40L50_DSP_QUEUE_MASK),
+	REGMAP_IRQ_REG(CS40L50_AMP_SHORT_IRQ, CS40L50_IRQ1_INT_1_OFFSET,
+		       CS40L50_AMP_SHORT_MASK),
+	REGMAP_IRQ_REG(CS40L50_TEMP_ERR_IRQ, CS40L50_IRQ1_INT_8_OFFSET,
+		       CS40L50_TEMP_ERR_MASK),
+	REGMAP_IRQ_REG(CS40L50_BST_UVP_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+		       CS40L50_BST_UVP_MASK),
+	REGMAP_IRQ_REG(CS40L50_BST_SHORT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+		       CS40L50_BST_SHORT_MASK),
+	REGMAP_IRQ_REG(CS40L50_BST_ILIMIT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+		       CS40L50_BST_ILIMIT_MASK),
+	REGMAP_IRQ_REG(CS40L50_UVLO_VDDBATT_IRQ, CS40L50_IRQ1_INT_10_OFFSET,
+		       CS40L50_UVLO_VDDBATT_MASK),
+	REGMAP_IRQ_REG(CS40L50_GLOBAL_ERROR_IRQ, CS40L50_IRQ1_INT_18_OFFSET,
+		       CS40L50_GLOBAL_ERROR_MASK),
+};
+
+static struct regmap_irq_chip cs40l50_irq_chip = {
+	.name =		"cs40l50",
+	.status_base =	CS40L50_IRQ1_INT_1,
+	.mask_base =	CS40L50_IRQ1_MASK_1,
+	.ack_base =	CS40L50_IRQ1_INT_1,
+	.num_regs =	22,
+	.irqs =		cs40l50_reg_irqs,
+	.num_irqs =	ARRAY_SIZE(cs40l50_reg_irqs),
+	.runtime_pm =	true,
+};
+
+int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val)
+{
+	int i, ret;
+	u32 ack;
+
+	/* Device NAKs if hibernating, so optionally retry */
+	for (i = 0; i < CS40L50_DSP_TIMEOUT_COUNT; i++) {
+		ret = regmap_write(regmap, CS40L50_DSP_QUEUE, val);
+		if (!ret)
+			break;
+
+		usleep_range(CS40L50_DSP_POLL_US, CS40L50_DSP_POLL_US + 100);
+	}
+
+	/* If write never occurred, don't bother polling for ACK */
+	if (i == CS40L50_DSP_TIMEOUT_COUNT) {
+		dev_err(dev, "Timed out writing %#X to DSP: %d\n", val, ret);
+		return ret;
+	}
+
+	ret = regmap_read_poll_timeout(regmap, CS40L50_DSP_QUEUE, ack, !ack,
+				       CS40L50_DSP_POLL_US,
+				       CS40L50_DSP_POLL_US * CS40L50_DSP_TIMEOUT_COUNT);
+	if (ret)
+		dev_err(dev, "DSP failed to ACK %#X: %d\n", val, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(cs40l50_dsp_write);
+
+static const struct cs_dsp_region cs40l50_dsp_regions[] = {
+	{ .type = WMFW_HALO_PM_PACKED, .base = CS40L50_PMEM_0 },
+	{ .type = WMFW_HALO_XM_PACKED, .base = CS40L50_XMEM_PACKED_0 },
+	{ .type = WMFW_HALO_YM_PACKED, .base = CS40L50_YMEM_PACKED_0 },
+	{ .type = WMFW_ADSP2_XM, .base = CS40L50_XMEM_UNPACKED24_0 },
+	{ .type = WMFW_ADSP2_YM, .base = CS40L50_YMEM_UNPACKED24_0 },
+};
+
+static const struct reg_sequence cs40l50_internal_vamp_config[] = {
+	{ CS40L50_BST_LPMODE_SEL, CS40L50_DCM_LOW_POWER },
+	{ CS40L50_BLOCK_ENABLES2, CS40L50_OVERTEMP_WARN },
+};
+
+static const struct reg_sequence cs40l50_irq_mask_override[] = {
+	{ CS40L50_IRQ1_MASK_2, CS40L50_IRQ_MASK_2_OVERRIDE },
+	{ CS40L50_IRQ1_MASK_20, CS40L50_IRQ_MASK_20_OVERRIDE },
+};
+
+static int cs40l50_wseq_init(struct cs40l50 *cs40l50)
+{
+	struct cs_dsp *dsp = &cs40l50->dsp;
+
+	cs40l50->wseqs[CS40L50_STANDBY].ctl = cs_dsp_get_ctl(dsp, "STANDBY_SEQUENCE",
+							     WMFW_ADSP2_XM,
+							     CS40L50_PM_ALGO);
+	if (!cs40l50->wseqs[CS40L50_STANDBY].ctl) {
+		dev_err(cs40l50->dev, "Control not found for standby sequence\n");
+		return -ENOENT;
+	}
+
+	cs40l50->wseqs[CS40L50_ACTIVE].ctl = cs_dsp_get_ctl(dsp, "ACTIVE_SEQUENCE",
+							    WMFW_ADSP2_XM,
+							    CS40L50_PM_ALGO);
+	if (!cs40l50->wseqs[CS40L50_ACTIVE].ctl) {
+		dev_err(cs40l50->dev, "Control not found for active sequence\n");
+		return -ENOENT;
+	}
+
+	cs40l50->wseqs[CS40L50_PWR_ON].ctl = cs_dsp_get_ctl(dsp, "PM_PWR_ON_SEQ",
+							    WMFW_ADSP2_XM,
+							    CS40L50_PM_ALGO);
+	if (!cs40l50->wseqs[CS40L50_PWR_ON].ctl) {
+		dev_err(cs40l50->dev, "Control not found for power-on sequence\n");
+		return -ENOENT;
+	}
+
+	return cs_dsp_wseq_init(&cs40l50->dsp, cs40l50->wseqs, ARRAY_SIZE(cs40l50->wseqs));
+}
+
+static int cs40l50_dsp_config(struct cs40l50 *cs40l50)
+{
+	int ret;
+
+	/* Configure internal V_AMP supply */
+	ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_internal_vamp_config,
+				     ARRAY_SIZE(cs40l50_internal_vamp_config));
+	if (ret)
+		return ret;
+
+	ret = cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
+				      cs40l50_internal_vamp_config, CS_DSP_WSEQ_FULL,
+				      ARRAY_SIZE(cs40l50_internal_vamp_config), false);
+	if (ret)
+		return ret;
+
+	/* Override firmware defaults for IRQ masks */
+	ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_override,
+				     ARRAY_SIZE(cs40l50_irq_mask_override));
+	if (ret)
+		return ret;
+
+	return cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
+				       cs40l50_irq_mask_override, CS_DSP_WSEQ_FULL,
+				       ARRAY_SIZE(cs40l50_irq_mask_override), false);
+}
+
+static int cs40l50_dsp_post_run(struct cs_dsp *dsp)
+{
+	struct cs40l50 *cs40l50 = container_of(dsp, struct cs40l50, dsp);
+	int ret;
+
+	ret = cs40l50_wseq_init(cs40l50);
+	if (ret)
+		return ret;
+
+	ret = cs40l50_dsp_config(cs40l50);
+	if (ret) {
+		dev_err(cs40l50->dev, "Failed to configure DSP: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_mfd_add_devices(cs40l50->dev, PLATFORM_DEVID_NONE, cs40l50_devs,
+				   ARRAY_SIZE(cs40l50_devs), NULL, 0, NULL);
+	if (ret)
+		dev_err(cs40l50->dev, "Failed to add child devices: %d\n", ret);
+
+	return ret;
+}
+
+static const struct cs_dsp_client_ops client_ops = {
+	.post_run = cs40l50_dsp_post_run,
+};
+
+static void cs40l50_dsp_remove(void *data)
+{
+	cs_dsp_remove(data);
+}
+
+static int cs40l50_dsp_init(struct cs40l50 *cs40l50)
+{
+	int ret;
+
+	cs40l50->dsp.num = 1;
+	cs40l50->dsp.type = WMFW_HALO;
+	cs40l50->dsp.dev = cs40l50->dev;
+	cs40l50->dsp.regmap = cs40l50->regmap;
+	cs40l50->dsp.base = CS40L50_CORE_BASE;
+	cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID;
+	cs40l50->dsp.mem = cs40l50_dsp_regions;
+	cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions);
+	cs40l50->dsp.no_core_startstop = true;
+	cs40l50->dsp.client_ops = &client_ops;
+
+	ret = cs_dsp_halo_init(&cs40l50->dsp);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_remove,
+					&cs40l50->dsp);
+}
+
+static int cs40l50_reset_dsp(struct cs40l50 *cs40l50)
+{
+	int ret;
+
+	mutex_lock(&cs40l50->lock);
+
+	if (cs40l50->dsp.running)
+		cs_dsp_stop(&cs40l50->dsp);
+
+	if (cs40l50->dsp.booted)
+		cs_dsp_power_down(&cs40l50->dsp);
+
+	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SHUTDOWN);
+	if (ret)
+		goto err_mutex;
+
+	ret = cs_dsp_power_up(&cs40l50->dsp, cs40l50->fw, "cs40l50.wmfw",
+			      cs40l50->bin, "cs40l50.bin", "cs40l50");
+	if (ret)
+		goto err_mutex;
+
+	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SYSTEM_RESET);
+	if (ret)
+		goto err_mutex;
+
+	ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
+	if (ret)
+		goto err_mutex;
+
+	ret = cs_dsp_run(&cs40l50->dsp);
+err_mutex:
+	mutex_unlock(&cs40l50->lock);
+
+	return ret;
+}
+
+static void cs40l50_dsp_power_down(void *data)
+{
+	cs_dsp_power_down(data);
+}
+
+static void cs40l50_dsp_stop(void *data)
+{
+	cs_dsp_stop(data);
+}
+
+static void cs40l50_dsp_bringup(const struct firmware *bin, void *context)
+{
+	struct cs40l50 *cs40l50 = context;
+	u32 nwaves;
+	int ret;
+
+	/* Wavetable is optional; bringup DSP regardless */
+	cs40l50->bin = bin;
+
+	ret = cs40l50_reset_dsp(cs40l50);
+	if (ret) {
+		dev_err(cs40l50->dev, "Failed to reset DSP: %d\n", ret);
+		goto err_fw;
+	}
+
+	ret = regmap_read(cs40l50->regmap, CS40L50_NUM_WAVES, &nwaves);
+	if (ret)
+		goto err_fw;
+
+	dev_info(cs40l50->dev, "%u RAM effects loaded\n", nwaves);
+
+	/* Add teardown actions for first-time bringup */
+	ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_power_down,
+				       &cs40l50->dsp);
+	if (ret) {
+		dev_err(cs40l50->dev, "Failed to add power down action: %d\n", ret);
+		goto err_fw;
+	}
+
+	ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_stop, &cs40l50->dsp);
+	if (ret)
+		dev_err(cs40l50->dev, "Failed to add stop action: %d\n", ret);
+err_fw:
+	release_firmware(cs40l50->bin);
+	release_firmware(cs40l50->fw);
+}
+
+static void cs40l50_request_firmware(const struct firmware *fw, void *context)
+{
+	struct cs40l50 *cs40l50 = context;
+	int ret;
+
+	if (!fw) {
+		dev_err(cs40l50->dev, "No firmware file found\n");
+		return;
+	}
+
+	cs40l50->fw = fw;
+
+	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT,
+				      cs40l50->dev, GFP_KERNEL, cs40l50,
+				      cs40l50_dsp_bringup);
+	if (ret) {
+		dev_err(cs40l50->dev, "Failed to request %s: %d\n", CS40L50_WT, ret);
+		release_firmware(cs40l50->fw);
+	}
+}
+
+struct cs40l50_irq {
+	const char *name;
+	int virq;
+};
+
+static struct cs40l50_irq cs40l50_irqs[] = {
+	{ "DSP", },
+	{ "Global", },
+	{ "Boost UVLO", },
+	{ "Boost current limit", },
+	{ "Boost short", },
+	{ "Boost undervolt", },
+	{ "Overtemp", },
+	{ "Amp short", },
+};
+
+static const struct reg_sequence cs40l50_err_rls[] = {
+	{ CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_SET },
+	{ CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_CLEAR },
+};
+
+static irqreturn_t cs40l50_hw_err(int irq, void *data)
+{
+	struct cs40l50 *cs40l50 = data;
+	int ret = 0, i;
+
+	mutex_lock(&cs40l50->lock);
+
+	/* Log hardware interrupt and execute error release sequence */
+	for (i = 1; i < ARRAY_SIZE(cs40l50_irqs); i++) {
+		if (cs40l50_irqs[i].virq == irq) {
+			dev_err(cs40l50->dev, "%s error\n", cs40l50_irqs[i].name);
+			ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_err_rls,
+						     ARRAY_SIZE(cs40l50_err_rls));
+			break;
+		}
+	}
+
+	mutex_unlock(&cs40l50->lock);
+	return IRQ_RETVAL(!ret);
+}
+
+static irqreturn_t cs40l50_dsp_queue(int irq, void *data)
+{
+	struct cs40l50 *cs40l50 = data;
+	u32 rd_ptr, val, wt_ptr;
+	int ret = 0;
+
+	mutex_lock(&cs40l50->lock);
+
+	/* Read from DSP queue, log, and update read pointer */
+	while (!ret) {
+		ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_WT, &wt_ptr);
+		if (ret)
+			break;
+
+		ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, &rd_ptr);
+		if (ret)
+			break;
+
+		/* Check if queue is empty */
+		if (wt_ptr == rd_ptr)
+			break;
+
+		ret = regmap_read(cs40l50->regmap, rd_ptr, &val);
+		if (ret)
+			break;
+
+		dev_dbg(cs40l50->dev, "DSP payload: %#X", val);
+
+		rd_ptr += sizeof(u32);
+
+		if (rd_ptr > CS40L50_DSP_QUEUE_END)
+			rd_ptr = CS40L50_DSP_QUEUE_BASE;
+
+		ret = regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, rd_ptr);
+	}
+
+	mutex_unlock(&cs40l50->lock);
+
+	return IRQ_RETVAL(!ret);
+}
+
+static int cs40l50_irq_init(struct cs40l50 *cs40l50)
+{
+	int ret, i, virq;
+
+	ret = devm_regmap_add_irq_chip(cs40l50->dev, cs40l50->regmap, cs40l50->irq,
+				       IRQF_ONESHOT | IRQF_SHARED, 0,
+				       &cs40l50_irq_chip, &cs40l50->irq_data);
+	if (ret) {
+		dev_err(cs40l50->dev, "Failed adding IRQ chip\n");
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cs40l50_irqs); i++) {
+		virq = regmap_irq_get_virq(cs40l50->irq_data, i);
+		if (virq < 0) {
+			dev_err(cs40l50->dev, "Failed getting virq for %s\n",
+				cs40l50_irqs[i].name);
+			return virq;
+		}
+
+		cs40l50_irqs[i].virq = virq;
+
+		/* Handle DSP and hardware interrupts separately */
+		ret = devm_request_threaded_irq(cs40l50->dev, virq, NULL,
+						i ? cs40l50_hw_err : cs40l50_dsp_queue,
+						IRQF_ONESHOT | IRQF_SHARED,
+						cs40l50_irqs[i].name, cs40l50);
+		if (ret) {
+			return dev_err_probe(cs40l50->dev, ret,
+					     "Failed requesting %s IRQ\n",
+					     cs40l50_irqs[i].name);
+		}
+	}
+
+	return 0;
+}
+
+static int cs40l50_get_model(struct cs40l50 *cs40l50)
+{
+	int ret;
+
+	ret = regmap_read(cs40l50->regmap, CS40L50_DEVID, &cs40l50->devid);
+	if (ret)
+		return ret;
+
+	if (cs40l50->devid != CS40L50_DEVID_A)
+		return -EINVAL;
+
+	ret = regmap_read(cs40l50->regmap, CS40L50_REVID, &cs40l50->revid);
+	if (ret)
+		return ret;
+
+	if (cs40l50->revid < CS40L50_REVID_B0)
+		return -EINVAL;
+
+	dev_dbg(cs40l50->dev, "Cirrus Logic CS40L50 rev. %02X\n", cs40l50->revid);
+
+	return 0;
+}
+
+static int cs40l50_pm_runtime_setup(struct device *dev)
+{
+	int ret;
+
+	pm_runtime_set_autosuspend_delay(dev, CS40L50_AUTOSUSPEND_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_get_noresume(dev);
+	ret = pm_runtime_set_active(dev);
+	if (ret)
+		return ret;
+
+	return devm_pm_runtime_enable(dev);
+}
+
+int cs40l50_probe(struct cs40l50 *cs40l50)
+{
+	struct device *dev = cs40l50->dev;
+	int ret;
+
+	mutex_init(&cs40l50->lock);
+
+	cs40l50->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(cs40l50->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(cs40l50->reset_gpio),
+				     "Failed getting reset GPIO\n");
+
+	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(cs40l50_supplies),
+					     cs40l50_supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed getting supplies\n");
+
+	/* Ensure minimum reset pulse width */
+	usleep_range(CS40L50_RESET_PULSE_US, CS40L50_RESET_PULSE_US + 100);
+
+	gpiod_set_value_cansleep(cs40l50->reset_gpio, 0);
+
+	/* Wait for control port to be ready */
+	usleep_range(CS40L50_CP_READY_US, CS40L50_CP_READY_US + 100);
+
+	ret = cs40l50_get_model(cs40l50);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get part number\n");
+
+	ret = cs40l50_dsp_init(cs40l50);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize DSP\n");
+
+	ret = cs40l50_pm_runtime_setup(dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize runtime PM\n");
+
+	ret = cs40l50_irq_init(cs40l50);
+	if (ret)
+		return ret;
+
+	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_FW,
+				      dev, GFP_KERNEL, cs40l50, cs40l50_request_firmware);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request %s\n", CS40L50_FW);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l50_probe);
+
+int cs40l50_remove(struct cs40l50 *cs40l50)
+{
+	gpiod_set_value_cansleep(cs40l50->reset_gpio, 1);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l50_remove);
+
+static int cs40l50_runtime_suspend(struct device *dev)
+{
+	struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
+
+	return regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE, CS40L50_ALLOW_HIBER);
+}
+
+static int cs40l50_runtime_resume(struct device *dev)
+{
+	struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
+
+	return cs40l50_dsp_write(dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
+}
+
+EXPORT_GPL_DEV_PM_OPS(cs40l50_pm_ops) = {
+	RUNTIME_PM_OPS(cs40l50_runtime_suspend, cs40l50_runtime_resume, NULL)
+};
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(FW_CS_DSP);
diff --git a/drivers/mfd/cs40l50-i2c.c b/drivers/mfd/cs40l50-i2c.c
new file mode 100644
index 000000000000..639be743d956
--- /dev/null
+++ b/drivers/mfd/cs40l50-i2c.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/mfd/cs40l50.h>
+
+static int cs40l50_i2c_probe(struct i2c_client *i2c)
+{
+	struct cs40l50 *cs40l50;
+
+	cs40l50 = devm_kzalloc(&i2c->dev, sizeof(*cs40l50), GFP_KERNEL);
+	if (!cs40l50)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, cs40l50);
+
+	cs40l50->dev = &i2c->dev;
+	cs40l50->irq = i2c->irq;
+
+	cs40l50->regmap = devm_regmap_init_i2c(i2c, &cs40l50_regmap);
+	if (IS_ERR(cs40l50->regmap))
+		return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap),
+				     "Failed to initialize register map\n");
+
+	return cs40l50_probe(cs40l50);
+}
+
+static void cs40l50_i2c_remove(struct i2c_client *i2c)
+{
+	struct cs40l50 *cs40l50 = i2c_get_clientdata(i2c);
+
+	cs40l50_remove(cs40l50);
+}
+
+static const struct i2c_device_id cs40l50_id_i2c[] = {
+	{ "cs40l50" },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs40l50_id_i2c);
+
+static const struct of_device_id cs40l50_of_match[] = {
+	{ .compatible = "cirrus,cs40l50" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, cs40l50_of_match);
+
+static struct i2c_driver cs40l50_i2c_driver = {
+	.driver = {
+		.name = "cs40l50",
+		.of_match_table = cs40l50_of_match,
+		.pm = pm_ptr(&cs40l50_pm_ops),
+	},
+	.id_table = cs40l50_id_i2c,
+	.probe = cs40l50_i2c_probe,
+	.remove = cs40l50_i2c_remove,
+};
+module_i2c_driver(cs40l50_i2c_driver);
+
+MODULE_DESCRIPTION("CS40L50 I2C Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/cs40l50-spi.c b/drivers/mfd/cs40l50-spi.c
new file mode 100644
index 000000000000..53526b595a0d
--- /dev/null
+++ b/drivers/mfd/cs40l50-spi.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/mfd/cs40l50.h>
+#include <linux/spi/spi.h>
+
+static int cs40l50_spi_probe(struct spi_device *spi)
+{
+	struct cs40l50 *cs40l50;
+
+	cs40l50 = devm_kzalloc(&spi->dev, sizeof(*cs40l50), GFP_KERNEL);
+	if (!cs40l50)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, cs40l50);
+
+	cs40l50->dev = &spi->dev;
+	cs40l50->irq = spi->irq;
+
+	cs40l50->regmap = devm_regmap_init_spi(spi, &cs40l50_regmap);
+	if (IS_ERR(cs40l50->regmap))
+		return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap),
+				     "Failed to initialize register map\n");
+
+	return cs40l50_probe(cs40l50);
+}
+
+static void cs40l50_spi_remove(struct spi_device *spi)
+{
+	struct cs40l50 *cs40l50 = spi_get_drvdata(spi);
+
+	cs40l50_remove(cs40l50);
+}
+
+static const struct spi_device_id cs40l50_id_spi[] = {
+	{ "cs40l50" },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, cs40l50_id_spi);
+
+static const struct of_device_id cs40l50_of_match[] = {
+	{ .compatible = "cirrus,cs40l50" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, cs40l50_of_match);
+
+static struct spi_driver cs40l50_spi_driver = {
+	.driver = {
+		.name = "cs40l50",
+		.of_match_table = cs40l50_of_match,
+		.pm = pm_ptr(&cs40l50_pm_ops),
+	},
+	.id_table = cs40l50_id_spi,
+	.probe = cs40l50_spi_probe,
+	.remove = cs40l50_spi_remove,
+};
+module_spi_driver(cs40l50_spi_driver);
+
+MODULE_DESCRIPTION("CS40L50 SPI Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/cs40l50.h b/include/linux/mfd/cs40l50.h
new file mode 100644
index 000000000000..e5dc49860944
--- /dev/null
+++ b/include/linux/mfd/cs40l50.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#ifndef __MFD_CS40L50_H__
+#define __MFD_CS40L50_H__
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/gpio/consumer.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+/* Power Supply Configuration */
+#define CS40L50_BLOCK_ENABLES2		0x201C
+#define CS40L50_ERR_RLS			0x2034
+#define CS40L50_BST_LPMODE_SEL		0x3810
+#define CS40L50_DCM_LOW_POWER		0x1
+#define CS40L50_OVERTEMP_WARN		0x4000010
+
+/* Interrupts */
+#define CS40L50_IRQ1_INT_1		0xE010
+#define CS40L50_IRQ1_BASE		CS40L50_IRQ1_INT_1
+#define CS40L50_IRQ1_INT_2		0xE014
+#define CS40L50_IRQ1_INT_8		0xE02C
+#define CS40L50_IRQ1_INT_9		0xE030
+#define CS40L50_IRQ1_INT_10		0xE034
+#define CS40L50_IRQ1_INT_18		0xE054
+#define CS40L50_IRQ1_MASK_1		0xE090
+#define CS40L50_IRQ1_MASK_2		0xE094
+#define CS40L50_IRQ1_MASK_20		0xE0DC
+#define CS40L50_IRQ1_INT_1_OFFSET	(CS40L50_IRQ1_INT_1 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_2_OFFSET	(CS40L50_IRQ1_INT_2 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_8_OFFSET	(CS40L50_IRQ1_INT_8 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_9_OFFSET	(CS40L50_IRQ1_INT_9 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_10_OFFSET	(CS40L50_IRQ1_INT_10 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_18_OFFSET	(CS40L50_IRQ1_INT_18 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ_MASK_2_OVERRIDE	0xFFDF7FFF
+#define CS40L50_IRQ_MASK_20_OVERRIDE	0x15C01000
+#define CS40L50_AMP_SHORT_MASK		BIT(31)
+#define CS40L50_DSP_QUEUE_MASK		BIT(21)
+#define CS40L50_TEMP_ERR_MASK		BIT(31)
+#define CS40L50_BST_UVP_MASK		BIT(6)
+#define CS40L50_BST_SHORT_MASK		BIT(7)
+#define CS40L50_BST_ILIMIT_MASK		BIT(18)
+#define CS40L50_UVLO_VDDBATT_MASK	BIT(16)
+#define CS40L50_GLOBAL_ERROR_MASK	BIT(15)
+
+enum cs40l50_irq_list {
+	CS40L50_DSP_QUEUE_IRQ,
+	CS40L50_GLOBAL_ERROR_IRQ,
+	CS40L50_UVLO_VDDBATT_IRQ,
+	CS40L50_BST_ILIMIT_IRQ,
+	CS40L50_BST_SHORT_IRQ,
+	CS40L50_BST_UVP_IRQ,
+	CS40L50_TEMP_ERR_IRQ,
+	CS40L50_AMP_SHORT_IRQ,
+};
+
+/* DSP */
+#define CS40L50_XMEM_PACKED_0		0x2000000
+#define CS40L50_XMEM_UNPACKED24_0	0x2800000
+#define CS40L50_SYS_INFO_ID		0x25E0000
+#define CS40L50_DSP_QUEUE_WT		0x28042C8
+#define CS40L50_DSP_QUEUE_RD		0x28042CC
+#define CS40L50_NUM_WAVES		0x2805C18
+#define CS40L50_CORE_BASE		0x2B80000
+#define CS40L50_YMEM_PACKED_0		0x2C00000
+#define CS40L50_YMEM_UNPACKED24_0	0x3400000
+#define CS40L50_PMEM_0			0x3800000
+#define CS40L50_DSP_POLL_US		1000
+#define CS40L50_DSP_TIMEOUT_COUNT	100
+#define CS40L50_RESET_PULSE_US		2200
+#define CS40L50_CP_READY_US		3100
+#define CS40L50_AUTOSUSPEND_MS		2000
+#define CS40L50_PM_ALGO			0x9F206
+#define CS40L50_GLOBAL_ERR_RLS_SET	BIT(11)
+#define CS40L50_GLOBAL_ERR_RLS_CLEAR	0
+
+enum cs40l50_wseqs {
+	CS40L50_PWR_ON,
+	CS40L50_STANDBY,
+	CS40L50_ACTIVE,
+	CS40L50_NUM_WSEQS,
+};
+
+/* DSP Queue */
+#define CS40L50_DSP_QUEUE_BASE		0x11004
+#define CS40L50_DSP_QUEUE_END		0x1101C
+#define CS40L50_DSP_QUEUE		0x11020
+#define CS40L50_PREVENT_HIBER		0x2000003
+#define CS40L50_ALLOW_HIBER		0x2000004
+#define CS40L50_SHUTDOWN		0x2000005
+#define CS40L50_SYSTEM_RESET		0x2000007
+#define CS40L50_START_I2S		0x3000002
+#define CS40L50_OWT_PUSH		0x3000008
+#define CS40L50_STOP_PLAYBACK		0x5000000
+#define CS40L50_OWT_DELETE		0xD000000
+
+/* Firmware files */
+#define CS40L50_FW			"cs40l50.wmfw"
+#define CS40L50_WT			"cs40l50.bin"
+
+/* Device */
+#define CS40L50_DEVID			0x0
+#define CS40L50_REVID			0x4
+#define CS40L50_DEVID_A			0x40A50
+#define CS40L50_REVID_B0		0xB0
+
+struct cs40l50 {
+	struct device *dev;
+	struct regmap *regmap;
+	struct mutex lock;
+	struct cs_dsp dsp;
+	struct gpio_desc *reset_gpio;
+	struct regmap_irq_chip_data *irq_data;
+	const struct firmware *fw;
+	const struct firmware *bin;
+	struct cs_dsp_wseq wseqs[CS40L50_NUM_WSEQS];
+	int irq;
+	u32 devid;
+	u32 revid;
+};
+
+int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val);
+int cs40l50_probe(struct cs40l50 *cs40l50);
+int cs40l50_remove(struct cs40l50 *cs40l50);
+
+extern const struct regmap_config cs40l50_regmap;
+extern const struct dev_pm_ops cs40l50_pm_ops;
+
+#endif /* __MFD_CS40L50_H__ */
-- 
2.25.1


^ permalink raw reply related

* [PATCH v10 2/5] dt-bindings: input: cirrus,cs40l50: Add initial DT binding
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree,
	Krzysztof Kozlowski
In-Reply-To: <20240320192956.2395731-1-jogletre@opensource.cirrus.com>

CS40L50 is a haptic driver with waveform memory,
integrated DSP, and closed-loop algorithms.

Add a YAML DT binding document for this device.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
 .../bindings/input/cirrus,cs40l50.yaml        | 68 +++++++++++++++++++
 MAINTAINERS                                   |  8 +++
 2 files changed, 76 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml

diff --git a/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
new file mode 100644
index 000000000000..89bd06864bd4
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS40L50 Advanced Haptic Driver
+
+maintainers:
+  - James Ogletree <jogletre@opensource.cirrus.com>
+
+description:
+  CS40L50 is a haptic driver with waveform memory,
+  integrated DSP, and closed-loop algorithms.
+
+properties:
+  compatible:
+    enum:
+      - cirrus,cs40l50
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vdd-a-supply:
+    description: Power supply for internal analog circuits.
+
+  vdd-p-supply:
+    description: Power supply for always-on circuits.
+
+  vdd-io-supply:
+    description: Power supply for digital input/output.
+
+  vdd-b-supply:
+    description: Power supply for the boost converter.
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - reset-gpios
+  - vdd-io-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      haptic-driver@34 {
+        compatible = "cirrus,cs40l50";
+        reg = <0x34>;
+        interrupt-parent = <&gpio>;
+        interrupts = <113 IRQ_TYPE_LEVEL_LOW>;
+        reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>;
+        vdd-io-supply = <&vreg>;
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index dd5de540ec0b..862a18e3fc7b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4933,6 +4933,14 @@ F:	sound/pci/hda/cs*
 F:	sound/pci/hda/hda_cs_dsp_ctl.*
 F:	sound/soc/codecs/cs*
 
+CIRRUS LOGIC HAPTIC DRIVERS
+M:	James Ogletree <jogletre@opensource.cirrus.com>
+M:	Fred Treven <fred.treven@cirrus.com>
+M:	Ben Bright <ben.bright@cirrus.com>
+L:	patches@opensource.cirrus.com
+S:	Supported
+F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+
 CIRRUS LOGIC DSP FIRMWARE DRIVER
 M:	Simon Trimmer <simont@opensource.cirrus.com>
 M:	Charles Keepax <ckeepax@opensource.cirrus.com>
-- 
2.25.1


^ permalink raw reply related

* [PATCH v10 4/5] Input: cs40l50 - Add support for the CS40L50 haptic driver
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
In-Reply-To: <20240320192956.2395731-1-jogletre@opensource.cirrus.com>

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The input driver provides the interface for control of
haptic effects through the device.

Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
v10:
Minor concern that playback stop is still misused with respect to not
specifying an effect ID. The device can only play one effect at a
time, but setting max effects for the EVIOCGEFFECTS ioctl to 1 would
restrict the number of uploads to 1 as well.

 MAINTAINERS                        |   1 +
 drivers/input/misc/Kconfig         |  10 +
 drivers/input/misc/Makefile        |   1 +
 drivers/input/misc/cs40l50-vibra.c | 577 +++++++++++++++++++++++++++++
 4 files changed, 589 insertions(+)
 create mode 100644 drivers/input/misc/cs40l50-vibra.c

diff --git a/MAINTAINERS b/MAINTAINERS
index e804f3766cba..49c2e6e57b09 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4940,6 +4940,7 @@ M:	Ben Bright <ben.bright@cirrus.com>
 L:	patches@opensource.cirrus.com
 S:	Supported
 F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F:	drivers/input/misc/cs40l*
 F:	drivers/mfd/cs40l*
 F:	include/linux/mfd/cs40l*
 
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6ba984d7f0b1..ee45dbb0636e 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -140,6 +140,16 @@ config INPUT_BMA150
 	  To compile this driver as a module, choose M here: the
 	  module will be called bma150.
 
+config INPUT_CS40L50_VIBRA
+	tristate "CS40L50 Haptic Driver support"
+	depends on MFD_CS40L50_CORE
+	help
+	  Say Y here to enable support for Cirrus Logic's CS40L50
+	  haptic driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cs40l50-vibra.
+
 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..88279de6d3d5 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -28,6 +28,7 @@ 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_CS40L50_VIBRA)	+= cs40l50-vibra.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/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c
new file mode 100644
index 000000000000..5440cf224f59
--- /dev/null
+++ b/drivers/input/misc/cs40l50-vibra.c
@@ -0,0 +1,577 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/input.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+/* Wavetables */
+#define CS40L50_RAM_INDEX_START		0x1000000
+#define CS40L50_RAM_INDEX_END		0x100007F
+#define CS40L50_RTH_INDEX_START		0x1400000
+#define CS40L50_RTH_INDEX_END		0x1400001
+#define CS40L50_ROM_INDEX_START		0x1800000
+#define CS40L50_ROM_INDEX_END		0x180001A
+#define CS40L50_TYPE_PCM		8
+#define CS40L50_TYPE_PWLE		12
+#define CS40L50_PCM_ID			0x0
+#define CS40L50_OWT_CUSTOM_DATA_SIZE	2
+#define CS40L50_CUSTOM_DATA_MASK	0xFFFFU
+
+/* DSP */
+#define CS40L50_GPIO_BASE		0x2804140
+#define CS40L50_OWT_BASE		0x2805C34
+#define CS40L50_OWT_SIZE		0x2805C38
+#define CS40L50_OWT_NEXT		0x2805C3C
+
+/* GPIO */
+#define CS40L50_GPIO_NUM_MASK		GENMASK(14, 12)
+#define CS40L50_GPIO_EDGE_MASK		BIT(15)
+#define CS40L50_GPIO_MAPPING_NONE	0
+#define CS40L50_GPIO_DISABLE		0x1FF
+
+enum cs40l50_bank_type {
+	CS40L50_WVFRM_BANK_RAM,
+	CS40L50_WVFRM_BANK_ROM,
+	CS40L50_WVFRM_BANK_OWT,
+	CS40L50_WVFRM_BANK_NUM,
+};
+
+/* Describes an area in DSP memory populated by effects */
+struct cs40l50_bank {
+	enum cs40l50_bank_type type;
+	u32 base_index;
+	u32 max_index;
+};
+
+struct cs40l50_effect {
+	enum cs40l50_bank_type type;
+	struct list_head list;
+	u32 gpio_reg;
+	u32 index;
+	int id;
+};
+
+/* Describes haptic interface of loaded DSP firmware */
+struct cs40l50_vibra_dsp {
+	struct cs40l50_bank *banks;
+	u32 gpio_base_reg;
+	u32 owt_offset_reg;
+	u32 owt_size_reg;
+	u32 owt_base_reg;
+	u32 push_owt_cmd;
+	u32 delete_owt_cmd;
+	u32 stop_cmd;
+	int (*write)(struct device *dev, struct regmap *regmap, u32 val);
+};
+
+/* Describes configuration and state of haptic operations */
+struct cs40l50_vibra {
+	struct device *dev;
+	struct regmap *regmap;
+	struct input_dev *input;
+	struct mutex lock;
+	struct workqueue_struct *vibe_wq;
+	struct list_head effect_head;
+	struct cs40l50_vibra_dsp dsp;
+};
+
+struct cs40l50_work {
+	struct cs40l50_vibra *vibra;
+	struct ff_effect *effect;
+	struct work_struct work;
+	s16 *custom_data;
+	int custom_len;
+	int count;
+	int error;
+};
+
+static struct cs40l50_bank cs40l50_banks[] = {
+	{
+		.type =		CS40L50_WVFRM_BANK_RAM,
+		.base_index =	CS40L50_RAM_INDEX_START,
+		.max_index =	CS40L50_RAM_INDEX_END,
+	},
+	{
+		.type =		CS40L50_WVFRM_BANK_ROM,
+		.base_index =	CS40L50_ROM_INDEX_START,
+		.max_index =	CS40L50_ROM_INDEX_END,
+	},
+	{
+		.type =		CS40L50_WVFRM_BANK_OWT,
+		.base_index =	CS40L50_RTH_INDEX_START,
+		.max_index =	CS40L50_RTH_INDEX_END,
+	},
+};
+
+static struct cs40l50_vibra_dsp cs40l50_dsp = {
+	.banks =		cs40l50_banks,
+	.gpio_base_reg =	CS40L50_GPIO_BASE,
+	.owt_base_reg =		CS40L50_OWT_BASE,
+	.owt_offset_reg =	CS40L50_OWT_NEXT,
+	.owt_size_reg =		CS40L50_OWT_SIZE,
+	.push_owt_cmd =		CS40L50_OWT_PUSH,
+	.delete_owt_cmd =	CS40L50_OWT_DELETE,
+	.stop_cmd =		CS40L50_STOP_PLAYBACK,
+	.write =		cs40l50_dsp_write,
+};
+
+static struct cs40l50_effect *cs40l50_find_effect(int id, struct list_head *effect_head)
+{
+	struct cs40l50_effect *effect;
+
+	list_for_each_entry(effect, effect_head, list)
+		if (effect->id == id)
+			return effect;
+
+	return NULL;
+}
+
+static int cs40l50_effect_bank_set(struct cs40l50_work *work_data,
+				   struct cs40l50_effect *effect)
+{
+	s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK;
+
+	if (bank_type >= CS40L50_WVFRM_BANK_NUM) {
+		dev_err(work_data->vibra->dev, "Invalid bank (%d)\n", bank_type);
+		return -EINVAL;
+	}
+
+	if (work_data->custom_len > CS40L50_OWT_CUSTOM_DATA_SIZE)
+		effect->type = CS40L50_WVFRM_BANK_OWT;
+	else
+		effect->type = bank_type;
+
+	return 0;
+}
+
+static int cs40l50_effect_gpio_mapping_set(struct cs40l50_work *work_data,
+					   struct cs40l50_effect *effect)
+{
+	u16 gpio_num, gpio_edge, button = work_data->effect->trigger.button;
+	struct cs40l50_vibra *vibra = work_data->vibra;
+
+	if (button) {
+		gpio_num = FIELD_GET(CS40L50_GPIO_NUM_MASK, button);
+		gpio_edge = FIELD_GET(CS40L50_GPIO_EDGE_MASK, button);
+		effect->gpio_reg = vibra->dsp.gpio_base_reg + (gpio_num * 8) - gpio_edge;
+
+		return regmap_write(vibra->regmap, effect->gpio_reg, button);
+	}
+
+	effect->gpio_reg = CS40L50_GPIO_MAPPING_NONE;
+
+	return 0;
+}
+
+static int cs40l50_effect_index_set(struct cs40l50_work *work_data,
+				   struct cs40l50_effect *effect)
+{
+	struct cs40l50_vibra *vibra = work_data->vibra;
+	struct cs40l50_effect *owt_effect;
+	u32 base_index, max_index;
+
+	base_index = vibra->dsp.banks[effect->type].base_index;
+	max_index = vibra->dsp.banks[effect->type].max_index;
+
+	effect->index = base_index;
+
+	switch (effect->type) {
+	case CS40L50_WVFRM_BANK_OWT:
+		list_for_each_entry(owt_effect, &vibra->effect_head, list)
+			if (owt_effect->type == CS40L50_WVFRM_BANK_OWT)
+				effect->index++;
+		break;
+	case CS40L50_WVFRM_BANK_ROM:
+	case CS40L50_WVFRM_BANK_RAM:
+		effect->index += work_data->custom_data[1] & CS40L50_CUSTOM_DATA_MASK;
+		break;
+	default:
+		dev_err(vibra->dev, "Bank type %d not supported\n", effect->type);
+		return -EINVAL;
+	}
+
+	if (effect->index > max_index || effect->index < base_index) {
+		dev_err(vibra->dev, "Index out of bounds: %u\n", effect->index);
+		return -ENOSPC;
+	}
+
+	return 0;
+}
+
+/* Describes a header for an OWT effect */
+struct cs40l50_owt_header {
+	u32 type;
+	u32 data_words;
+	u32 offset;
+} __packed;
+
+static int cs40l50_upload_owt(struct cs40l50_work *work_data)
+{
+	u32 len = 2 * work_data->custom_len, wt_offset, wt_size;
+	struct cs40l50_vibra *vibra = work_data->vibra;
+	struct cs40l50_owt_header header;
+	u8 *out_data;
+	int error;
+
+	error = regmap_read(vibra->regmap, vibra->dsp.owt_size_reg, &wt_size);
+	if (error)
+		return error;
+
+	if ((wt_size * sizeof(u32)) < sizeof(header) + len) {
+		dev_err(vibra->dev, "No space in OWT bank for effect\n");
+		return -ENOSPC;
+	}
+
+	out_data = kzalloc(sizeof(header) + len, GFP_KERNEL);
+	if (!out_data)
+		return -ENOMEM;
+
+	header.type = work_data->custom_data[0] == CS40L50_PCM_ID ? CS40L50_TYPE_PCM :
+								    CS40L50_TYPE_PWLE;
+	header.offset = sizeof(header) / sizeof(u32);
+	header.data_words = len / sizeof(u32);
+
+	memcpy(out_data, &header, sizeof(header));
+	memcpy(out_data + sizeof(header), work_data->custom_data, len);
+
+	error = regmap_read(vibra->regmap, vibra->dsp.owt_offset_reg, &wt_offset);
+	if (error)
+		goto err_free;
+
+	error = regmap_bulk_write(vibra->regmap, vibra->dsp.owt_base_reg +
+				  (wt_offset * sizeof(u32)), out_data,
+				  sizeof(header) + len);
+	if (error)
+		goto err_free;
+
+	error = vibra->dsp.write(vibra->dev, vibra->regmap, vibra->dsp.push_owt_cmd);
+err_free:
+	kfree(out_data);
+
+	return error;
+}
+
+static void cs40l50_add_worker(struct work_struct *work)
+{
+	struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+	struct cs40l50_vibra *vibra = work_data->vibra;
+	struct cs40l50_effect *effect;
+	bool is_new = false;
+	int error;
+
+	error = pm_runtime_resume_and_get(vibra->dev);
+	if (error)
+		goto err_exit;
+
+	mutex_lock(&vibra->lock);
+
+	/* Update effect if already present, otherwise create new effect */
+	effect = cs40l50_find_effect(work_data->effect->id, &vibra->effect_head);
+	if (!effect) {
+		effect = kzalloc(sizeof(*effect), GFP_KERNEL);
+		if (!effect) {
+			error = -ENOMEM;
+			goto err_mutex;
+		}
+
+		effect->id = work_data->effect->id;
+		is_new = true;
+	}
+
+	error = cs40l50_effect_bank_set(work_data, effect);
+	if (error)
+		goto err_free;
+
+	error = cs40l50_effect_index_set(work_data, effect);
+	if (error)
+		goto err_free;
+
+	error = cs40l50_effect_gpio_mapping_set(work_data, effect);
+	if (error)
+		goto err_free;
+
+	if (effect->type == CS40L50_WVFRM_BANK_OWT)
+		error = cs40l50_upload_owt(work_data);
+err_free:
+	if (is_new) {
+		if (error)
+			kfree(effect);
+		else
+			list_add(&effect->list, &vibra->effect_head);
+	}
+err_mutex:
+	mutex_unlock(&vibra->lock);
+
+	pm_runtime_mark_last_busy(vibra->dev);
+	pm_runtime_put_autosuspend(vibra->dev);
+err_exit:
+	work_data->error = error;
+}
+
+static int cs40l50_add(struct input_dev *dev, struct ff_effect *effect,
+		       struct ff_effect *old)
+{
+	struct ff_periodic_effect *periodic = &effect->u.periodic;
+	struct cs40l50_vibra *vibra = input_get_drvdata(dev);
+	u32 len = effect->u.periodic.custom_len;
+	struct cs40l50_work work_data;
+
+	if (effect->type != FF_PERIODIC || periodic->waveform != FF_CUSTOM) {
+		dev_err(vibra->dev, "Type (%#X) or waveform (%#X) unsupported\n",
+			effect->type, periodic->waveform);
+		return -EINVAL;
+	}
+
+	work_data.custom_data = kcalloc(len, sizeof(s16), GFP_KERNEL);
+	if (!work_data.custom_data)
+		return -ENOMEM;
+
+	if (copy_from_user(work_data.custom_data, effect->u.periodic.custom_data,
+			   sizeof(s16) * len)) {
+		work_data.error = -EFAULT;
+		goto err_free;
+	}
+
+	work_data.custom_len = len;
+	work_data.vibra = vibra;
+	work_data.effect = effect;
+	INIT_WORK(&work_data.work, cs40l50_add_worker);
+
+	/* Push to the workqueue to serialize with playbacks */
+	queue_work(vibra->vibe_wq, &work_data.work);
+	flush_work(&work_data.work);
+err_free:
+	kfree(work_data.custom_data);
+
+	return work_data.error;
+}
+
+static void cs40l50_start_worker(struct work_struct *work)
+{
+	struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+	struct cs40l50_vibra *vibra = work_data->vibra;
+	struct cs40l50_effect *start_effect;
+
+	if (pm_runtime_resume_and_get(vibra->dev) < 0)
+		goto err_free;
+
+	mutex_lock(&vibra->lock);
+
+	start_effect = cs40l50_find_effect(work_data->effect->id, &vibra->effect_head);
+	if (start_effect) {
+		while (--work_data->count >= 0) {
+			vibra->dsp.write(vibra->dev, vibra->regmap, start_effect->index);
+			usleep_range(work_data->effect->replay.length,
+				     work_data->effect->replay.length + 100);
+		}
+	}
+
+	mutex_unlock(&vibra->lock);
+
+	if (!start_effect)
+		dev_err(vibra->dev, "Effect to play not found\n");
+
+	pm_runtime_mark_last_busy(vibra->dev);
+	pm_runtime_put_autosuspend(vibra->dev);
+err_free:
+	kfree(work_data);
+}
+
+static void cs40l50_stop_worker(struct work_struct *work)
+{
+	struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+	struct cs40l50_vibra *vibra = work_data->vibra;
+
+	if (pm_runtime_resume_and_get(vibra->dev) < 0)
+		return;
+
+	mutex_lock(&vibra->lock);
+
+	vibra->dsp.write(vibra->dev, vibra->regmap, vibra->dsp.stop_cmd);
+
+	mutex_unlock(&vibra->lock);
+
+	pm_runtime_mark_last_busy(vibra->dev);
+	pm_runtime_put_autosuspend(vibra->dev);
+
+	kfree(work_data);
+}
+
+static int cs40l50_playback(struct input_dev *dev, int effect_id, int val)
+{
+	struct cs40l50_vibra *vibra = input_get_drvdata(dev);
+	struct cs40l50_work *work_data;
+
+	work_data = kzalloc(sizeof(*work_data), GFP_ATOMIC);
+	if (!work_data)
+		return -ENOMEM;
+
+	work_data->vibra = vibra;
+
+	if (val > 0) {
+		work_data->effect = &dev->ff->effects[effect_id];
+		work_data->count = val;
+		INIT_WORK(&work_data->work, cs40l50_start_worker);
+	} else {
+		/* Just stop the amplifier as device drives only one effect */
+		INIT_WORK(&work_data->work, cs40l50_stop_worker);
+	}
+
+	queue_work(vibra->vibe_wq, &work_data->work);
+
+	return 0;
+}
+
+static void cs40l50_erase_worker(struct work_struct *work)
+{
+	struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+	struct cs40l50_effect *owt_effect, *erase_effect;
+	struct cs40l50_vibra *vibra = work_data->vibra;
+	int error;
+
+	error = pm_runtime_resume_and_get(vibra->dev);
+	if (error)
+		goto err_exit;
+
+	mutex_lock(&vibra->lock);
+
+	erase_effect = cs40l50_find_effect(work_data->effect->id, &vibra->effect_head);
+	if (!erase_effect) {
+		dev_err(vibra->dev, "Effect to erase not found\n");
+		error = -EINVAL;
+		goto err_mutex;
+	}
+
+	if (erase_effect->gpio_reg != CS40L50_GPIO_MAPPING_NONE) {
+		error = regmap_write(vibra->regmap, erase_effect->gpio_reg,
+				     CS40L50_GPIO_DISABLE);
+		if (error)
+			goto err_mutex;
+	}
+
+	if (erase_effect->type == CS40L50_WVFRM_BANK_OWT) {
+		error = vibra->dsp.write(vibra->dev, vibra->regmap,
+					 vibra->dsp.delete_owt_cmd |
+					 erase_effect->index);
+		if (error)
+			goto err_mutex;
+
+		list_for_each_entry(owt_effect, &vibra->effect_head, list)
+			if (owt_effect->type == CS40L50_WVFRM_BANK_OWT &&
+			    owt_effect->index > erase_effect->index)
+				owt_effect->index--;
+	}
+
+	list_del(&erase_effect->list);
+	kfree(erase_effect);
+err_mutex:
+	mutex_unlock(&vibra->lock);
+
+	pm_runtime_mark_last_busy(vibra->dev);
+	pm_runtime_put_autosuspend(vibra->dev);
+err_exit:
+	work_data->error = error;
+}
+
+static int cs40l50_erase(struct input_dev *dev, int effect_id)
+{
+	struct cs40l50_vibra *vibra = input_get_drvdata(dev);
+	struct cs40l50_work work_data;
+
+	work_data.vibra = vibra;
+	work_data.effect = &dev->ff->effects[effect_id];
+
+	INIT_WORK(&work_data.work, cs40l50_erase_worker);
+
+	/* Push to workqueue to serialize with playbacks */
+	queue_work(vibra->vibe_wq, &work_data.work);
+	flush_work(&work_data.work);
+
+	return work_data.error;
+}
+
+static void cs40l50_remove_wq(void *data)
+{
+	flush_workqueue(data);
+	destroy_workqueue(data);
+}
+
+static int cs40l50_vibra_probe(struct platform_device *pdev)
+{
+	struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+	struct cs40l50_vibra *vibra;
+	int error;
+
+	vibra = devm_kzalloc(pdev->dev.parent, sizeof(*vibra), GFP_KERNEL);
+	if (!vibra)
+		return -ENOMEM;
+
+	mutex_init(&vibra->lock);
+
+	vibra->dev = cs40l50->dev;
+	vibra->regmap = cs40l50->regmap;
+	vibra->dsp = cs40l50_dsp;
+
+	vibra->input = devm_input_allocate_device(vibra->dev);
+	if (!vibra->input)
+		return -ENOMEM;
+
+	vibra->input->id.product = cs40l50->devid;
+	vibra->input->id.version = cs40l50->revid;
+	vibra->input->name = "cs40l50_vibra";
+
+	input_set_drvdata(vibra->input, vibra);
+	input_set_capability(vibra->input, EV_FF, FF_PERIODIC);
+	input_set_capability(vibra->input, EV_FF, FF_CUSTOM);
+
+	error = input_ff_create(vibra->input, FF_MAX_EFFECTS);
+	if (error) {
+		dev_err(vibra->dev, "Failed to create input device\n");
+		return error;
+	}
+
+	vibra->input->ff->upload = cs40l50_add;
+	vibra->input->ff->playback = cs40l50_playback;
+	vibra->input->ff->erase = cs40l50_erase;
+
+	INIT_LIST_HEAD(&vibra->effect_head);
+
+	vibra->vibe_wq = alloc_ordered_workqueue("vibe_wq", WQ_HIGHPRI);
+	if (!vibra->vibe_wq)
+		return -ENOMEM;
+
+	error = devm_add_action_or_reset(vibra->dev, cs40l50_remove_wq, vibra->vibe_wq);
+	if (error)
+		return error;
+
+	return input_register_device(vibra->input);
+}
+
+static const struct platform_device_id cs40l50_vibra_id_match[] = {
+	{ "cs40l50-vibra", },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_vibra_id_match);
+
+static struct platform_driver cs40l50_vibra_driver = {
+	.probe		= cs40l50_vibra_probe,
+	.id_table	= cs40l50_vibra_id_match,
+	.driver		= {
+		.name	= "cs40l50-vibra",
+	},
+};
+module_platform_driver(cs40l50_vibra_driver);
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
-- 
2.25.1


^ permalink raw reply related

* [PATCH v10 5/5] ASoC: cs40l50: Support I2S streaming to CS40L50
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
In-Reply-To: <20240320192956.2395731-1-jogletre@opensource.cirrus.com>

Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.

The ASoC driver enables I2S streaming to the device.

Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
 MAINTAINERS                      |   1 +
 sound/soc/codecs/Kconfig         |  11 ++
 sound/soc/codecs/Makefile        |   2 +
 sound/soc/codecs/cs40l50-codec.c | 308 +++++++++++++++++++++++++++++++
 4 files changed, 322 insertions(+)
 create mode 100644 sound/soc/codecs/cs40l50-codec.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 49c2e6e57b09..62701b13f741 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4943,6 +4943,7 @@ F:	Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
 F:	drivers/input/misc/cs40l*
 F:	drivers/mfd/cs40l*
 F:	include/linux/mfd/cs40l*
+F:	sound/soc/codecs/cs40l*
 
 CIRRUS LOGIC DSP FIRMWARE DRIVER
 M:	Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f1e1dbc509f6..1a81bedfdbe3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -73,6 +73,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_CS35L56_I2C
 	imply SND_SOC_CS35L56_SPI
 	imply SND_SOC_CS35L56_SDW
+	imply SND_SOC_CS40L50
 	imply SND_SOC_CS42L42
 	imply SND_SOC_CS42L42_SDW
 	imply SND_SOC_CS42L43
@@ -800,6 +801,16 @@ config SND_SOC_CS35L56_SDW
 	help
 	  Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control
 
+config SND_SOC_CS40L50
+	tristate "Cirrus Logic CS40L50 CODEC"
+	depends on MFD_CS40L50_CORE
+	help
+	  This option enables support for I2S streaming to Cirrus Logic CS40L50.
+
+	  CS40L50 is a haptic driver with waveform memory, an integrated
+	  DSP, and closed-loop algorithms. If built as a module, it will be
+	  called snd-soc-cs40l50.
+
 config SND_SOC_CS42L42_CORE
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a87e56938ce5..7e31f000774a 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -74,6 +74,7 @@ snd-soc-cs35l56-shared-objs := cs35l56-shared.o
 snd-soc-cs35l56-i2c-objs := cs35l56-i2c.o
 snd-soc-cs35l56-spi-objs := cs35l56-spi.o
 snd-soc-cs35l56-sdw-objs := cs35l56-sdw.o
+snd-soc-cs40l50-objs := cs40l50-codec.o
 snd-soc-cs42l42-objs := cs42l42.o
 snd-soc-cs42l42-i2c-objs := cs42l42-i2c.o
 snd-soc-cs42l42-sdw-objs := cs42l42-sdw.o
@@ -460,6 +461,7 @@ obj-$(CONFIG_SND_SOC_CS35L56_SHARED)	+= snd-soc-cs35l56-shared.o
 obj-$(CONFIG_SND_SOC_CS35L56_I2C)	+= snd-soc-cs35l56-i2c.o
 obj-$(CONFIG_SND_SOC_CS35L56_SPI)	+= snd-soc-cs35l56-spi.o
 obj-$(CONFIG_SND_SOC_CS35L56_SDW)	+= snd-soc-cs35l56-sdw.o
+obj-$(CONFIG_SND_SOC_CS40L50)		+= snd-soc-cs40l50.o
 obj-$(CONFIG_SND_SOC_CS42L42_CORE)	+= snd-soc-cs42l42.o
 obj-$(CONFIG_SND_SOC_CS42L42)	+= snd-soc-cs42l42-i2c.o
 obj-$(CONFIG_SND_SOC_CS42L42_SDW)	+= snd-soc-cs42l42-sdw.o
diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c
new file mode 100644
index 000000000000..6d4a0970b219
--- /dev/null
+++ b/sound/soc/codecs/cs40l50-codec.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// CS40L50 Advanced Haptic Driver with waveform memory,
+// integrated DSP, and closed-loop algorithms
+//
+// Copyright 2024 Cirrus Logic, Inc.
+//
+// Author: James Ogletree <james.ogletree@cirrus.com>
+
+#include <linux/bitfield.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/pm_runtime.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define CS40L50_REFCLK_INPUT		0x2C04
+#define CS40L50_ASP_CONTROL2		0x4808
+#define CS40L50_ASP_DATA_CONTROL5	0x4840
+
+/* PLL Config */
+#define CS40L50_PLL_REFCLK_BCLK		0x0
+#define CS40L50_PLL_REFCLK_MCLK		0x5
+#define CS40L50_PLL_REEFCLK_MCLK_CFG	0x00
+#define CS40L50_PLL_REFCLK_LOOP_MASK	BIT(11)
+#define CS40L50_PLL_REFCLK_OPEN_LOOP	1
+#define CS40L50_PLL_REFCLK_CLOSED_LOOP	0
+#define CS40L50_PLL_REFCLK_LOOP_SHIFT	11
+#define CS40L50_PLL_REFCLK_FREQ_MASK	GENMASK(10, 5)
+#define CS40L50_PLL_REFCLK_FREQ_SHIFT	5
+#define CS40L50_PLL_REFCLK_SEL_MASK	GENMASK(2, 0)
+#define CS40L50_BCLK_RATIO_DEFAULT	32
+
+/* ASP Config */
+#define CS40L50_ASP_RX_WIDTH_SHIFT	24
+#define CS40L50_ASP_RX_WIDTH_MASK	GENMASK(31, 24)
+#define CS40L50_ASP_RX_WL_MASK		GENMASK(5, 0)
+#define CS40L50_ASP_FSYNC_INV_MASK	BIT(2)
+#define CS40L50_ASP_BCLK_INV_MASK	BIT(6)
+#define CS40L50_ASP_FMT_MASK		GENMASK(10, 8)
+#define CS40L50_ASP_FMT_I2S		0x2
+
+struct cs40l50_pll_config {
+	unsigned int freq;
+	unsigned int cfg;
+};
+
+struct cs40l50_codec {
+	struct device *dev;
+	struct regmap *regmap;
+	unsigned int daifmt;
+	unsigned int bclk_ratio;
+	unsigned int rate;
+};
+
+static const struct cs40l50_pll_config cs40l50_pll_cfg[] = {
+	{ 32768, 0x00 },
+	{ 1536000, 0x1B },
+	{ 3072000, 0x21 },
+	{ 6144000, 0x28 },
+	{ 9600000, 0x30 },
+	{ 12288000, 0x33 },
+};
+
+static int cs40l50_get_clk_config(unsigned int freq, unsigned int *cfg)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs40l50_pll_cfg); i++) {
+		if (cs40l50_pll_cfg[i].freq == freq) {
+			*cfg = cs40l50_pll_cfg[i].cfg;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int cs40l50_swap_ext_clk(struct cs40l50_codec *codec, unsigned int clk_src)
+{
+	unsigned int cfg;
+	int ret;
+
+	switch (clk_src) {
+	case CS40L50_PLL_REFCLK_BCLK:
+		ret = cs40l50_get_clk_config(codec->bclk_ratio * codec->rate, &cfg);
+		if (ret)
+			return ret;
+		break;
+	case CS40L50_PLL_REFCLK_MCLK:
+		cfg = CS40L50_PLL_REEFCLK_MCLK_CFG;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+				 CS40L50_PLL_REFCLK_LOOP_MASK,
+				 CS40L50_PLL_REFCLK_OPEN_LOOP <<
+				 CS40L50_PLL_REFCLK_LOOP_SHIFT);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+				 CS40L50_PLL_REFCLK_FREQ_MASK |
+				 CS40L50_PLL_REFCLK_SEL_MASK,
+				 (cfg << CS40L50_PLL_REFCLK_FREQ_SHIFT) | clk_src);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+				  CS40L50_PLL_REFCLK_LOOP_MASK,
+				  CS40L50_PLL_REFCLK_CLOSED_LOOP <<
+				  CS40L50_PLL_REFCLK_LOOP_SHIFT);
+}
+
+static int cs40l50_clk_en(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *kcontrol,
+			  int event)
+{
+	struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm);
+	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(comp);
+	int ret;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_STOP_PLAYBACK);
+		if (ret)
+			return ret;
+
+		ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_START_I2S);
+		if (ret)
+			return ret;
+
+		ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_BCLK);
+		if (ret)
+			return ret;
+		break;
+	case SND_SOC_DAPM_PRE_PMD:
+		ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_MCLK);
+		if (ret)
+			return ret;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget cs40l50_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l50_clk_en,
+			      SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route cs40l50_dapm_routes[] = {
+	{ "ASP Playback", NULL, "ASP PLL" },
+	{ "ASPRX1", NULL, "ASP Playback" },
+	{ "ASPRX2", NULL, "ASP Playback" },
+
+	{ "OUT", NULL, "ASPRX1" },
+	{ "OUT", NULL, "ASPRX2" },
+};
+
+static int cs40l50_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(codec_dai->component);
+
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC)
+		return -EINVAL;
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		codec->daifmt = 0;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		codec->daifmt = CS40L50_ASP_BCLK_INV_MASK;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK | CS40L50_ASP_BCLK_INV_MASK;
+		break;
+	default:
+		dev_err(codec->dev, "Invalid clock invert\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		codec->daifmt |= FIELD_PREP(CS40L50_ASP_FMT_MASK, CS40L50_ASP_FMT_I2S);
+		break;
+	default:
+		dev_err(codec->dev, "Unsupported DAI format\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cs40l50_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);
+	unsigned int asp_rx_wl = params_width(params);
+	int ret;
+
+	codec->rate = params_rate(params);
+
+	ret = regmap_update_bits(codec->regmap, CS40L50_ASP_DATA_CONTROL5,
+				 CS40L50_ASP_RX_WL_MASK, asp_rx_wl);
+	if (ret)
+		return ret;
+
+	codec->daifmt |= (asp_rx_wl << CS40L50_ASP_RX_WIDTH_SHIFT);
+
+	return regmap_update_bits(codec->regmap, CS40L50_ASP_CONTROL2,
+				  CS40L50_ASP_FSYNC_INV_MASK |
+				  CS40L50_ASP_BCLK_INV_MASK |
+				  CS40L50_ASP_FMT_MASK |
+				  CS40L50_ASP_RX_WIDTH_MASK, codec->daifmt);
+}
+
+static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);
+
+	codec->bclk_ratio = ratio;
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs40l50_dai_ops = {
+	.set_fmt = cs40l50_set_dai_fmt,
+	.set_bclk_ratio = cs40l50_set_dai_bclk_ratio,
+	.hw_params = cs40l50_hw_params,
+};
+
+static struct snd_soc_dai_driver cs40l50_dai[] = {
+	{
+		.name = "cs40l50-pcm",
+		.id = 0,
+		.playback = {
+			.stream_name = "ASP Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+		},
+		.ops = &cs40l50_dai_ops,
+	},
+};
+
+static int cs40l50_codec_probe(struct snd_soc_component *component)
+{
+	struct cs40l50_codec *codec = snd_soc_component_get_drvdata(component);
+
+	codec->bclk_ratio = CS40L50_BCLK_RATIO_DEFAULT;
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_cs40l50 = {
+	.probe = cs40l50_codec_probe,
+	.dapm_widgets = cs40l50_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(cs40l50_dapm_widgets),
+	.dapm_routes = cs40l50_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(cs40l50_dapm_routes),
+};
+
+static int cs40l50_codec_driver_probe(struct platform_device *pdev)
+{
+	struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+	struct cs40l50_codec *codec;
+
+	codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL);
+	if (!codec)
+		return -ENOMEM;
+
+	codec->regmap = cs40l50->regmap;
+	codec->dev = &pdev->dev;
+
+	return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l50,
+					       cs40l50_dai, ARRAY_SIZE(cs40l50_dai));
+}
+
+static const struct platform_device_id cs40l50_id[] = {
+	{ "cs40l50-codec", },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_id);
+
+static struct platform_driver cs40l50_codec_driver = {
+	.probe = cs40l50_codec_driver_probe,
+	.id_table = cs40l50_id,
+	.driver = {
+		.name = "cs40l50-codec",
+	},
+};
+module_platform_driver(cs40l50_codec_driver);
+
+MODULE_DESCRIPTION("ASoC CS40L50 driver");
+MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
-- 
2.25.1


^ permalink raw reply related

* [PATCH v10 1/5] firmware: cs_dsp: Add write sequence interface
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree,
	Charles Keepax
In-Reply-To: <20240320192956.2395731-1-jogletre@opensource.cirrus.com>

A write sequence is a sequence of register addresses
and values executed by some Cirrus DSPs following
certain power state transitions.

Add support for Cirrus drivers to update or add to a
write sequence present in firmware.

Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
 drivers/firmware/cirrus/cs_dsp.c       | 278 +++++++++++++++++++++++++
 include/linux/firmware/cirrus/cs_dsp.h |  27 +++
 2 files changed, 305 insertions(+)

diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c
index 79d4254d1f9b..6d886ffea10f 100644
--- a/drivers/firmware/cirrus/cs_dsp.c
+++ b/drivers/firmware/cirrus/cs_dsp.c
@@ -275,6 +275,12 @@
 #define HALO_MPU_VIO_ERR_SRC_MASK           0x00007fff
 #define HALO_MPU_VIO_ERR_SRC_SHIFT                   0
 
+/*
+ * Write Sequence
+ */
+#define WSEQ_OP_MAX_WORDS	3
+#define WSEQ_END_OF_SCRIPT	0xFFFFFF
+
 struct cs_dsp_ops {
 	bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);
 	unsigned int (*parse_sizes)(struct cs_dsp *dsp,
@@ -3339,6 +3345,278 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
 }
 EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);
 
+
+struct cs_dsp_wseq_op {
+	struct list_head list;
+	u32 address;
+	u32 data;
+	u16 offset;
+	u8 operation;
+};
+
+static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+	struct cs_dsp_wseq_op *op, *op_tmp;
+
+	list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) {
+		list_del(&op->list);
+		devm_kfree(dsp->dev, op);
+	}
+}
+
+static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+	struct cs_dsp_wseq_op *op = NULL;
+	struct cs_dsp_chunk chunk;
+	u8 *words;
+	int ret;
+
+	if (!wseq->ctl) {
+		cs_dsp_err(dsp, "No control for write sequence\n");
+		return -EINVAL;
+	}
+
+	words = kzalloc(wseq->ctl->len, GFP_KERNEL);
+	if (!words)
+		return -ENOMEM;
+
+	ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len);
+	if (ret) {
+		cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret);
+		goto err_free;
+	}
+
+	INIT_LIST_HEAD(&wseq->ops);
+
+	chunk = cs_dsp_chunk(words, wseq->ctl->len);
+
+	while (!cs_dsp_chunk_end(&chunk)) {
+		op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL);
+		if (!op) {
+			ret = -ENOMEM;
+			goto err_free;
+		}
+
+		op->offset = cs_dsp_chunk_bytes(&chunk);
+		op->operation = cs_dsp_chunk_read(&chunk, 8);
+
+		switch (op->operation) {
+		case CS_DSP_WSEQ_END:
+			op->data = WSEQ_END_OF_SCRIPT;
+			break;
+		case CS_DSP_WSEQ_UNLOCK:
+			op->data = cs_dsp_chunk_read(&chunk, 16);
+			break;
+		case CS_DSP_WSEQ_ADDR8:
+			op->address = cs_dsp_chunk_read(&chunk, 8);
+			op->data = cs_dsp_chunk_read(&chunk, 32);
+			break;
+		case CS_DSP_WSEQ_H16:
+		case CS_DSP_WSEQ_L16:
+			op->address = cs_dsp_chunk_read(&chunk, 24);
+			op->data = cs_dsp_chunk_read(&chunk, 16);
+			break;
+		case CS_DSP_WSEQ_FULL:
+			op->address = cs_dsp_chunk_read(&chunk, 32);
+			op->data = cs_dsp_chunk_read(&chunk, 32);
+			break;
+		default:
+			ret = -EINVAL;
+			cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);
+			devm_kfree(dsp->dev, op);
+			goto err_free;
+		}
+
+		list_add_tail(&op->list, &wseq->ops);
+
+		if (op->operation == CS_DSP_WSEQ_END)
+			break;
+	}
+
+	if (op && op->operation != CS_DSP_WSEQ_END) {
+		cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);
+		ret = -ENOENT;
+	}
+
+err_free:
+	kfree(words);
+
+	return ret;
+}
+
+/**
+ * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware
+ * @dsp: Pointer to DSP structure
+ * @wseqs: List of write sequences to initialize
+ * @num_wseqs: Number of write sequences to initialize
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs)
+{
+	int i, ret;
+
+	lockdep_assert_held(&dsp->pwr_lock);
+
+	for (i = 0; i < num_wseqs; i++) {
+		ret = cs_dsp_populate_wseq(dsp, &wseqs[i]);
+		if (ret) {
+			cs_dsp_wseq_clear(dsp, &wseqs[i]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP);
+
+static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code,
+						  struct list_head *wseq_ops)
+{
+	struct cs_dsp_wseq_op *op;
+
+	list_for_each_entry(op, wseq_ops, list) {
+		if (op->operation == op_code && op->address == addr)
+			return op;
+	}
+
+	return NULL;
+}
+
+/**
+ * cs_dsp_wseq_write() - Add or update an entry in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @addr: Address of the register to be written to
+ * @data: Data to be written
+ * @op_code: The type of operation of the new entry
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function formats register address and value pairs into the format
+ * required for write sequence entries, and either updates or adds the
+ * new entry into the write sequence.
+ *
+ * If update is set to true and no matching entry is found, it will add a new entry.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+		      u32 addr, u32 data, u8 op_code, bool update)
+{
+	struct cs_dsp_wseq_op *op_end, *op_new = NULL;
+	u32 words[WSEQ_OP_MAX_WORDS];
+	struct cs_dsp_chunk chunk;
+	int new_op_size, ret;
+
+	if (update)
+		op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);
+
+	/* If entry to update is not found, treat it as a new operation */
+	if (!op_new) {
+		op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops);
+		if (!op_end) {
+			cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname);
+			return -EINVAL;
+		}
+
+		op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL);
+		if (!op_new)
+			return -ENOMEM;
+
+		op_new->operation = op_code;
+		op_new->address = addr;
+		op_new->offset = op_end->offset;
+		update = false;
+	}
+
+	op_new->data = data;
+
+	chunk = cs_dsp_chunk(words, sizeof(words));
+	cs_dsp_chunk_write(&chunk, 8, op_new->operation);
+
+	switch (op_code) {
+	case CS_DSP_WSEQ_FULL:
+		cs_dsp_chunk_write(&chunk, 32, op_new->address);
+		cs_dsp_chunk_write(&chunk, 32, op_new->data);
+		break;
+	case CS_DSP_WSEQ_L16:
+	case CS_DSP_WSEQ_H16:
+		cs_dsp_chunk_write(&chunk, 24, op_new->address);
+		cs_dsp_chunk_write(&chunk, 16, op_new->data);
+		break;
+	default:
+		ret = -EINVAL;
+		cs_dsp_err(dsp, "Operation %X not supported\n", op_code);
+		goto op_new_free;
+	}
+
+	new_op_size = cs_dsp_chunk_bytes(&chunk);
+
+	if (!update) {
+		if (wseq->ctl->len - op_end->offset < new_op_size) {
+			cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);
+			ret = -E2BIG;
+			goto op_new_free;
+		}
+
+		op_end->offset += new_op_size;
+
+		ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),
+					      &op_end->data, sizeof(u32));
+		if (ret)
+			goto op_new_free;
+
+		list_add_tail(&op_new->list, &op_end->list);
+	}
+
+	ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),
+				      words, new_op_size);
+	if (ret)
+		goto op_new_free;
+
+	return 0;
+
+op_new_free:
+	devm_kfree(dsp->dev, op_new);
+
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP);
+
+/**
+ * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @reg_seq: List of address-data pairs
+ * @num_regs: Number of address-data pairs
+ * @op_code: The types of operations of the new entries
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function calls cs_dsp_wseq_write() for multiple address-data pairs.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+			    const struct reg_sequence *reg_seq, int num_regs,
+			    u8 op_code, bool update)
+{
+	int i, ret;
+
+	for (i = 0; i < num_regs; i++) {
+		ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,
+					reg_seq[i].def, op_code, update);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP);
+
 MODULE_DESCRIPTION("Cirrus Logic DSP Support");
 MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
 MODULE_LICENSE("GPL v2");
diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h
index 29cd11d5a3cf..4cef6fafa1d8 100644
--- a/include/linux/firmware/cirrus/cs_dsp.h
+++ b/include/linux/firmware/cirrus/cs_dsp.h
@@ -42,6 +42,16 @@
 #define CS_DSP_ACKED_CTL_MIN_VALUE           0
 #define CS_DSP_ACKED_CTL_MAX_VALUE           0xFFFFFF
 
+/*
+ * Write sequence operation codes
+ */
+#define CS_DSP_WSEQ_FULL	0x00
+#define CS_DSP_WSEQ_ADDR8	0x02
+#define CS_DSP_WSEQ_L16		0x04
+#define CS_DSP_WSEQ_H16		0x05
+#define CS_DSP_WSEQ_UNLOCK	0xFD
+#define CS_DSP_WSEQ_END		0xFF
+
 /**
  * struct cs_dsp_region - Describes a logical memory region in DSP address space
  * @type:	Memory region type
@@ -255,6 +265,23 @@ struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp,
 
 const char *cs_dsp_mem_region_name(unsigned int type);
 
+/**
+ * struct cs_dsp_wseq - Describes a write sequence
+ * @ctl:	Write sequence cs_dsp control
+ * @ops:	Operations contained within
+ */
+struct cs_dsp_wseq {
+	struct cs_dsp_coeff_ctl *ctl;
+	struct list_head ops;
+};
+
+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs);
+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data,
+		      u8 op_code, bool update);
+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+			    const struct reg_sequence *reg_seq, int num_regs,
+			    u8 op_code, bool update);
+
 /**
  * struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP
  * @data:	Pointer to underlying buffer memory
-- 
2.25.1


^ permalink raw reply related

* [PATCH v10 0/5] Add support for CS40L50
From: James Ogletree @ 2024-03-20 19:29 UTC (permalink / raw)
  To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
	broonie, jeff
  Cc: patches, linux-sound, linux-input, devicetree, James Ogletree

Changes in v10:
- Minor refactoring and logical improvements all around
- Renamed and added supplies

Changes in v9:
- Fixed empty struct by utilizing cs_dsp's post_run callback
- Style fixes in MFD driver

Changes in v8:
- set_sysclk() -> set_bclk_ratio()
- Added ID table to codec driver
- Style improvements
- Fixed ordering of new write sequence operations

Changes in v7:
- Fixed sparse warning
- Moved write sequences to private data structure
- Logical and style improvements in write sequence interface

Changes in v6:
- Updated write sequencer interface to be control-name based
- Fixed a race condition and non-handling of repeats in playback callback
- Stylistic and logical improvements all around

Changes in v5:
- Added a codec sub-device to support I2S streaming
- Moved write sequencer code from cirrus_haptics to cs_dsp
- Reverted cirrus_haptics library; future Cirrus input
  drivers will export and utilize cs40l50_vibra functions
- Added more comments
- Many small stylistic and logical improvements

Changes in v4:
- Moved from Input to MFD
- Moved common Cirrus haptic functions to a library
- Incorporated runtime PM framework
- Many style improvements

Changes in v3:
- YAML formatting corrections
- Fixed typo in MAINTAINERS
- Used generic node name "haptic-driver"
- Fixed probe error code paths
- Switched to "sizeof(*)"
- Removed tree reference in MAINTAINERS

Changes in v2:
- Fixed checkpatch warnings

James Ogletree (5):
  firmware: cs_dsp: Add write sequence interface
  dt-bindings: input: cirrus,cs40l50: Add initial DT binding
  mfd: cs40l50: Add support for CS40L50 core driver
  Input: cs40l50 - Add support for the CS40L50 haptic driver
  ASoC: cs40l50: Support I2S streaming to CS40L50

 .../bindings/input/cirrus,cs40l50.yaml        |  68 +++
 MAINTAINERS                                   |  12 +
 drivers/firmware/cirrus/cs_dsp.c              | 278 +++++++++
 drivers/input/misc/Kconfig                    |  10 +
 drivers/input/misc/Makefile                   |   1 +
 drivers/input/misc/cs40l50-vibra.c            | 577 ++++++++++++++++++
 drivers/mfd/Kconfig                           |  30 +
 drivers/mfd/Makefile                          |   4 +
 drivers/mfd/cs40l50-core.c                    | 570 +++++++++++++++++
 drivers/mfd/cs40l50-i2c.c                     |  68 +++
 drivers/mfd/cs40l50-spi.c                     |  68 +++
 include/linux/firmware/cirrus/cs_dsp.h        |  27 +
 include/linux/mfd/cs40l50.h                   | 137 +++++
 sound/soc/codecs/Kconfig                      |  11 +
 sound/soc/codecs/Makefile                     |   2 +
 sound/soc/codecs/cs40l50-codec.c              | 308 ++++++++++
 16 files changed, 2171 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
 create mode 100644 drivers/input/misc/cs40l50-vibra.c
 create mode 100644 drivers/mfd/cs40l50-core.c
 create mode 100644 drivers/mfd/cs40l50-i2c.c
 create mode 100644 drivers/mfd/cs40l50-spi.c
 create mode 100644 include/linux/mfd/cs40l50.h
 create mode 100644 sound/soc/codecs/cs40l50-codec.c

-- 
2.25.1


^ permalink raw reply

* Re: Future handling of complex RGB devices on Linux v3
From: Werner Sembach @ 2024-03-20 18:45 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Lee Jones, jikos, linux-kernel, Jelle van der Waa, Miguel Ojeda,
	dri-devel@lists.freedesktop.org, linux-input, ojeda, linux-leds,
	Pavel Machek, Gregor Riepl
In-Reply-To: <31bbac5f-a1fc-41dd-b614-f9039763084d@tuxedocomputers.com>


Am 20.03.24 um 12:33 schrieb Werner Sembach:
>
> Am 20.03.24 um 12:16 schrieb Werner Sembach:
>> Hi Hans and the others,
>>
>> Am 22.02.24 um 14:14 schrieb Werner Sembach:
>>> Hi,
>>>
>>> Thanks everyone for the exhaustive feedback. And at least this thread is a 
>>> good comprehesive reference for the future ^^.
>>>
>>> To recap the hopefully final UAPI for complex RGB lighting devices:
>>>
>>> - By default there is a singular /sys/class/leds/* entry that treats the 
>>> device as if it was a single zone RGB keyboard backlight with no special 
>>> effects.
>>>
>>> - There is an accompanying misc device with the sysfs attributes "name", 
>>> "device_type",  "firmware_version_string", "serial_number" for device 
>>> identification and "use_leds_uapi" that defaults to 1.
>>>
>>>     - If set to 0 the /sys/class/leds/* entry disappears. The driver should 
>>> keep the last state the backlight was in active if possible.
>>>
>>>     - If set 1 it appears again. The driver should bring it back to a static 
>>> 1 zone setting while avoiding flicker if possible.
>>>
>>> - If the device is not controllable by for example hidraw, the misc device 
>>> might also implement additional ioctls or sysfs attributes to allow a more 
>>> complex low level control for the keyboard backlight. This is will be a 
>>> highly vendor specific UAPI.
>> So in the OpenRGB issue thread 
>> https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices 
>> aka HID LampArray was mentioned. I did dismiss it because I thought that is 
>> only relevant for firmware, but I now stumbled upon the Virtual HID Framework 
>> (VHF) 
>> https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/virtual-hid-framework--vhf- 
>> and now I wonder if an equivalent exists for Linux? A quick search did not 
>> yield any results for me.
> Is this what I have been searching for? 
> https://docs.kernel.org/usb/gadget_hid.html
Nope is something different: http://www.linux-usb.org/gadget/
>>
>> If a virtual HID device is possible and the WMI interface can reasonably be 
>> mapped to the LampArray API this might be the best starting point:
>>
>> - Implement a Virtual HID device with LampArray
>>
>> - Implement LampArray in OpenRGB
>>
>> - (Optional) Implement a generic LampArray leds subsystem driver that maps to 
>> the single zone control and ads the use_leds_uapi sysfs switch to the virtual 
>> HID device
>>
>> - (Optional) Implement vendor specific controls for 
>> AutonomousMode/built-in-firmware-effects via custom HID commands
>>
>> - (Optional) Implement Virtual HID devices for actual HID devices that don't 
>> support LampArray in firmware (Open question: How to prevent 
>> userspace/OpenRGB from interacting with original HID when the virtual HID 
>> device is not in AutonomousMode? How to associate the original and virtual 
>> HID device to each other that userspace can easily recognize this relation? 
>> Or is it possible to add virtual HID commands on top of a real HID device, 
>> making it look exactly like the pure virtual devices for userspace?)
>>
>> The LampArray API hereby is made with the intention to be used for multi leds 
>> devices, like per-key-backlight keyboards, unlike the leds UAPI. And it is 
>> coming anyway with new RGB devices soon. So it would not conflict with a 
>> "don't introduce unnecessary UAPI interfaces" principle. Are there any plans 
>> already of Wrapping LampArray in some kind ioctl/sysfs API? Or just have it 
>> used via hidraw? Or was there no discussion about it till now?
>>
>> Regards,
>>
>> Werner
>>
>>>
>>>     - The actual logic interacting with this low level UAPI is implemented 
>>> by a userspace driver
>>>
>>> Implementation wise: For the creation of the misc device with the 
>>> use_leds_uapi switch a helper function/macro might be useful? Wonder if it 
>>> should go into leds.h, led-class-multicolor.h, or a new header file?
>>>
>>> - Out of my head it would look something like this:
>>>
>>> led_classdev_add_optional_misc_control(
>>>     struct led_classdev *led_cdev,
>>>     char* name,
>>>     char* device_type,
>>>     char* firmware_version_string,
>>>     char* serial_number,
>>>     void (*deregister_led)(struct led_classdev *led_cdev),
>>>     void (*reregister_led)(struct led_classdev *led_cdev))
>>>
>>> Let me know your thoughts and hopefully I can start implementing it soon for 
>>> one of our devices.
>>>
>>> Kind regards,
>>>
>>> Werner Sembach
>>>

^ permalink raw reply

* Re: [PATCH v3] Input: xen-kbdfront - drop keys to shrink modalias
From: Jason Andryuk @ 2024-03-20 17:42 UTC (permalink / raw)
  To: linux-kernel, Dmitry Torokhov
  Cc: Phillip Susi, stable, Mattijs Korpershoek, linux-input, xen-devel,
	Juergen Gross
In-Reply-To: <CAKf6xpuJe6Cza6bow3QxDGf1viu0kish7Y8YRN8haXL1oEF3HA@mail.gmail.com>

Hi Dmitry,

Do you have any feedback, or can you pick up this patch?  It solves a
real issue affecting udev, which crashes the Debian installer and
breaks the mouse for Gnome.

Or would you be okay if this patch went in via the Xen tree?

Thanks,
Jason

On Wed, Nov 1, 2023 at 10:11 AM Jason Andryuk <jandryuk@gmail.com> wrote:
>
> Hi Dmitry,
>
> Do you have any feedback or can you pick this up?
>
> Thanks,
> Jason
>
> On Wed, Oct 11, 2023 at 3:34 PM Jason Andryuk <jandryuk@gmail.com> wrote:
> >
> > xen kbdfront registers itself as being able to deliver *any* key since
> > it doesn't know what keys the backend may produce.
> >
> > Unfortunately, the generated modalias gets too large and uevent creation
> > fails with -ENOMEM.
> >
> > This can lead to gdm not using the keyboard since there is no seat
> > associated [1] and the debian installer crashing [2].
> >
> > Trim the ranges of key capabilities by removing some BTN_* ranges.
> > While doing this, some neighboring undefined ranges are removed to trim
> > it further.
> >
> > An upper limit of KEY_KBD_LCD_MENU5 is still too large.  Use an upper
> > limit of KEY_BRIGHTNESS_MENU.
> >
> > This removes:
> > BTN_DPAD_UP(0x220)..BTN_DPAD_RIGHT(0x223)
> > Empty space 0x224..0x229
> >
> > Empty space 0x28a..0x28f
> > KEY_MACRO1(0x290)..KEY_MACRO30(0x2ad)
> > KEY_MACRO_RECORD_START          0x2b0
> > KEY_MACRO_RECORD_STOP           0x2b1
> > KEY_MACRO_PRESET_CYCLE          0x2b2
> > KEY_MACRO_PRESET1(0x2b3)..KEY_MACRO_PRESET3(0xb5)
> > Empty space 0x2b6..0x2b7
> > KEY_KBD_LCD_MENU1(0x2b8)..KEY_KBD_LCD_MENU5(0x2bc)
> > Empty space 0x2bd..0x2bf
> > BTN_TRIGGER_HAPPY(0x2c0)..BTN_TRIGGER_HAPPY40(0x2e7)
> > Empty space 0x2e8..0x2ff
> >
> > The modalias shrinks from 2082 to 1550 bytes.
> >
> > A chunk of keys need to be removed to allow the keyboard to be used.
> > This may break some functionality, but the hope is these macro keys are
> > uncommon and don't affect any users.
> >
> > [1] https://github.com/systemd/systemd/issues/22944
> > [2] https://lore.kernel.org/xen-devel/87o8dw52jc.fsf@vps.thesusis.net/T/
> >
> > Cc: Phillip Susi <phill@thesusis.net>
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Jason Andryuk <jandryuk@gmail.com>
> > Reviewed-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
> > ---
> > v3:
> > Add Mattijs R-b
> > Put /* and */ on separate lines
> > ---
> >  drivers/input/misc/xen-kbdfront.c | 11 ++++++++++-
> >  1 file changed, 10 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c
> > index 67f1c7364c95..d59ba8f9852e 100644
> > --- a/drivers/input/misc/xen-kbdfront.c
> > +++ b/drivers/input/misc/xen-kbdfront.c
> > @@ -256,7 +256,16 @@ static int xenkbd_probe(struct xenbus_device *dev,
> >                 __set_bit(EV_KEY, kbd->evbit);
> >                 for (i = KEY_ESC; i < KEY_UNKNOWN; i++)
> >                         __set_bit(i, kbd->keybit);
> > -               for (i = KEY_OK; i < KEY_MAX; i++)
> > +               /*
> > +                * In theory we want to go KEY_OK..KEY_MAX, but that grows the
> > +                * modalias line too long.  There is a gap of buttons from
> > +                * BTN_DPAD_UP..BTN_DPAD_RIGHT and KEY_ALS_TOGGLE is the next
> > +                * defined. Then continue up to KEY_BRIGHTNESS_MENU as an upper
> > +                * limit.
> > +                */
> > +               for (i = KEY_OK; i < BTN_DPAD_UP; i++)
> > +                       __set_bit(i, kbd->keybit);
> > +               for (i = KEY_ALS_TOGGLE; i <= KEY_BRIGHTNESS_MENU; i++)
> >                         __set_bit(i, kbd->keybit);
> >
> >                 ret = input_register_device(kbd);
> > --
> > 2.41.0
> >

^ permalink raw reply


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