* Re: [PATCH 1/1] arm64: dts: rockchip: enable onboard spi flash for rock-3a
From: Jonas Karlman @ 2024-04-08 14:56 UTC (permalink / raw)
To: Chukun Pan, Heiko Stuebner
Cc: Conor Dooley, Krzysztof Kozlowski, Rob Herring, linux-arm-kernel,
linux-rockchip, linux-kernel, devicetree
In-Reply-To: <421ab43b-ff92-41c6-9904-3c7681c926be@kwiboo.se>
Hi again,
On 2024-04-08 16:44, Jonas Karlman wrote:
> Hi Chukun,
>
> On 2024-04-08 14:40, Chukun Pan wrote:
>> There is a mx25u12835f spi flash on this board, enable it.
>>
>> [ 2.525805] spi-nor spi4.0: mx25u12835f (16384 Kbytes)
>>
>> Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
>> ---
>> arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts | 12 ++++++++++++
>> 1 file changed, 12 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>> index a5e974ea659e..d8738cc47c73 100644
>> --- a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>> +++ b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>> @@ -757,6 +757,18 @@ &sdmmc2 {
>> status = "okay";
>> };
>>
>> +&sfc {
>
> This is missing:
>
> #address-cells = <1>;
> #size-cells = <0>;
>
>> + status = "okay";
>> +
>> + flash@0 {
>> + compatible = "jedec,spi-nor";
>> + reg = <0x0>;
>> + spi-max-frequency = <50000000>;
>
> At least in U-Boot the spi clock only support 24, 100 or 200 mhz and I
> am pretty sure the spi flash support 100mhz, so I would suggest you test
> with 100mhz, same as used on other rk356x boards.
Sorry, looked at spi clock instead of sfc clock.
sfc clock support 24, 50, 75, 100, 125 and 150 mhz.
Regards,
Jonas
>
> For U-Boot I have used 24 mhz for rk356x boards not defining a flash@0
> node in linux device tree, such as the rock-3a board.
>
> Regards,
> Jonas
>
>> + spi-rx-bus-width = <4>;
>> + spi-tx-bus-width = <1>;
>> + };
>> +};
>> +
>> &tsadc {
>> rockchip,hw-tshut-mode = <1>;
>> rockchip,hw-tshut-polarity = <0>;
>
^ permalink raw reply
* Re: [PATCH 1/1] arm64: dts: rockchip: enable onboard spi flash for rock-3a
From: Heiko Stübner @ 2024-04-08 15:04 UTC (permalink / raw)
To: Chukun Pan, Jonas Karlman
Cc: Conor Dooley, Krzysztof Kozlowski, Rob Herring, linux-arm-kernel,
linux-rockchip, linux-kernel, devicetree
In-Reply-To: <b32c284e-f3ec-44b0-abbd-7fa403bc9870@kwiboo.se>
Am Montag, 8. April 2024, 16:56:59 CEST schrieb Jonas Karlman:
> On 2024-04-08 16:44, Jonas Karlman wrote:
> > On 2024-04-08 14:40, Chukun Pan wrote:
> >> There is a mx25u12835f spi flash on this board, enable it.
> >>
> >> [ 2.525805] spi-nor spi4.0: mx25u12835f (16384 Kbytes)
> >>
> >> Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
> >> ---
> >> arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts | 12 ++++++++++++
> >> 1 file changed, 12 insertions(+)
> >>
> >> diff --git a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
> >> index a5e974ea659e..d8738cc47c73 100644
> >> --- a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
> >> +++ b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
> >> @@ -757,6 +757,18 @@ &sdmmc2 {
> >> status = "okay";
> >> };
> >>
> >> +&sfc {
> >
> > This is missing:
> >
> > #address-cells = <1>;
> > #size-cells = <0>;
> >
> >> + status = "okay";
> >> +
> >> + flash@0 {
> >> + compatible = "jedec,spi-nor";
> >> + reg = <0x0>;
> >> + spi-max-frequency = <50000000>;
> >
> > At least in U-Boot the spi clock only support 24, 100 or 200 mhz and I
> > am pretty sure the spi flash support 100mhz, so I would suggest you test
> > with 100mhz, same as used on other rk356x boards.
>
> Sorry, looked at spi clock instead of sfc clock.
> sfc clock support 24, 50, 75, 100, 125 and 150 mhz.
I think in some previous discussion the agreement was that the node
should specify the max frequency the flash supports and have the
controller worry about its own clock ranges.
Though in this case the 50MHz for the flash even matches the clock
frequency supported by the sfc
^ permalink raw reply
* Re: [PATCH] dt-bindings: net: rockchip-dwmac: use rgmii-id in example
From: Heiko Stübner @ 2024-04-08 15:05 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Wu,
Sascha Hauer
Cc: netdev, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Andrew Lunn, Sascha Hauer
In-Reply-To: <20240408-rockchip-dwmac-rgmii-id-binding-v1-1-3886d1a8bd54@pengutronix.de>
Am Montag, 8. April 2024, 08:44:10 CEST schrieb Sascha Hauer:
> The dwmac supports specifying the RGMII clock delays, but it is
> recommended to use rgmii-id and to specify the delays in the phy node
> instead [1].
>
> Change the example accordingly to no longer promote this undesired
> setting.
>
> [1] https://lore.kernel.org/all/1a0de7b4-f0f7-4080-ae48-f5ffa9e76be3@lunn.ch/
>
> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
> ---
> Documentation/devicetree/bindings/net/rockchip-dwmac.yaml | 4 +---
> 1 file changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
> index 70bbc4220e2ac..6bbe96e352509 100644
> --- a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
> +++ b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
> @@ -137,8 +137,6 @@ examples:
> assigned-clock-parents = <&ext_gmac>;
>
> rockchip,grf = <&grf>;
> - phy-mode = "rgmii";
> + phy-mode = "rgmii-id";
> clock_in_out = "input";
> - tx_delay = <0x30>;
> - rx_delay = <0x10>;
> };
>
> ---
> base-commit: 39cd87c4eb2b893354f3b850f916353f2658ae6f
> change-id: 20240408-rockchip-dwmac-rgmii-id-binding-6166af659845
>
> Best regards,
>
^ permalink raw reply
* Re: [PATCH v2] ASoC: dt-bindings: imx-audio-spdif: convert to YAML
From: Krzysztof Kozlowski @ 2024-04-08 15:06 UTC (permalink / raw)
To: Shengjiu Wang
Cc: Shengjiu Wang, lgirdwood, broonie, robh+dt,
krzysztof.kozlowski+dt, conor+dt, linux-sound, devicetree,
linux-kernel, shawnguo, s.hauer, kernel, festevam, imx,
linux-arm-kernel
In-Reply-To: <CAA+D8ANiKH7Oc+KAjD_BMp9P2EVeM8Q6dLGFCC8a83bJXUpPEA@mail.gmail.com>
On 08/04/2024 10:01, Shengjiu Wang wrote:
>>> +
>>> +anyOf:
>>> + - required:
>>> + - spdif-in
>>> + - required:
>>> + - spdif-out
>>> + - required:
>>> + - spdif-out
>>> + - spdif-in
>>
>> Do you need the last required block?
>
> Yes, one of them or both are required.
And? It's already there: that's the meaning of any. It is not oneOf...
Before answering please test your changes and ideas. I pointed issue
here and you responded just to close my comment. That does not make me
happy, just wastes my time.
>
>>
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> + - |
>>> + sound {
>>
>> That's a random change...
>>
>> Instead of sending two patches per day, please carefully address the
>> feedback.
>
> In v1 you suggest to change it to spdif? but spdif may conflict
> with the fsl,spdif.yaml. so which name I should use?
I don't understand where is the conflict. That's a different binding.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v5 5/5] PCI: dwc: Add common send PME_Turn_Off message method
From: Frank Li @ 2024-04-08 15:11 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: Bjorn Helgaas, Jingoo Han, Gustavo Pimentel, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, imx, linux-pci, linux-kernel, devicetree
In-Reply-To: <20240406040131.GC2678@thinkpad>
On Sat, Apr 06, 2024 at 09:31:31AM +0530, Manivannan Sadhasivam wrote:
> On Fri, Apr 05, 2024 at 10:31:16AM -0400, Frank Li wrote:
> > On Fri, Apr 05, 2024 at 11:54:26AM +0530, Manivannan Sadhasivam wrote:
> > > On Tue, Mar 19, 2024 at 12:07:15PM -0400, Frank Li wrote:
> > >
> > > PCI: dwc: Add generic MSG TLP support for sending PME_Turn_Off during system suspend
> > >
> > > > Reserve space at end of first IORESOURCE_MEM window as message TLP MMIO
> > > > window. This space's size is 'region_align'.
> > > >
> > > > Set outbound ATU map memory write to send PCI message. So one MMIO write
> > > > can trigger a PCI message, such as PME_Turn_Off.
> > > >
> > > > Add common dwc_pme_turn_off() function.
> > > >
> > > > Call dwc_pme_turn_off() to send out PME_Turn_Off message in general
> > > > dw_pcie_suspend_noirq() if there are not platform callback pme_turn_off()
> > > > exist.
> > > >
> > >
> > > How about:
> > >
> > > "Instead of relying on the vendor specific implementations to send the
> > > PME_Turn_Off message, let's introduce a generic way of sending the message using
> > > the MSG TLP.
> > >
> > > This is achieved by reserving a region for MSG TLP of size 'pci->region_align',
> > > at the end of the first IORESOURCE_MEM window of the host bridge. And then
> > > sending the PME_Turn_Off message during system suspend with the help of iATU.
> > >
> > > It should be noted that this generic implementation is optional for the glue
> > > drivers and can be overridden by a custom 'pme_turn_off' callback."
> > >
> > > > Signed-off-by: Frank Li <Frank.Li@nxp.com>
> > > > ---
> > > > drivers/pci/controller/dwc/pcie-designware-host.c | 94 ++++++++++++++++++++++-
> > > > drivers/pci/controller/dwc/pcie-designware.h | 3 +
> > > > 2 files changed, 93 insertions(+), 4 deletions(-)
> > > >
> > > > diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
> > > > index 267687ab33cbc..d5723fce7a894 100644
> > > > --- a/drivers/pci/controller/dwc/pcie-designware-host.c
> > > > +++ b/drivers/pci/controller/dwc/pcie-designware-host.c
> > > > @@ -393,6 +393,31 @@ static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
> > > > return 0;
> > > > }
> > > >
> > > > +static void dw_pcie_host_request_msg_tlp_res(struct dw_pcie_rp *pp)
> > > > +{
> > > > + struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> > > > + struct resource_entry *win;
> > > > + struct resource *res;
> > > > +
> > > > + win = resource_list_first_type(&pp->bridge->windows, IORESOURCE_MEM);
> > > > + if (win) {
> > > > + res = devm_kzalloc(pci->dev, sizeof(*res), GFP_KERNEL);
> > > > + if (!res)
> > > > + return;
> > > > +
> > > > + /* Reserve last region_align block as message TLP space */
> > > > + res->start = win->res->end - pci->region_align + 1;
> > > > + res->end = win->res->end;
> > >
> > > Don't you need to adjust the host bridge window size and end address?
> >
> > Needn't. request_resource will reserve it from bridge window. Like malloc,
> > if you malloc to get a region of memory, which will never get by malloc
> > again utill you call free.
> >
>
> Hmm, will that modify the window->res->end address and size?
No. This windows already reported to pci system before this function. It is
not good to modify window-res-end. It just add child resource like below.
windows is root resource, which will create may child when call
request_resource.
bridge -> windows
child1 -> msg
child2 -> pci ep1
child3 -> pci_ep2.
...
Although you see whole bridge window, 'msg' already used and put under root
resource, new pci devices will never use 'msg' resource.
If change windows->res->end here, I worry about it may broken resource
tree.
>
> > >
> > > > + res->name = "msg";
> > > > + res->flags = win->res->flags | IORESOURCE_BUSY;
> > > > +
> > >
> > > Shouldn't this resource be added back to the host bridge?
> >
> > No, this resource will reserver for msg only for whole bridge life cycle.
> > Genenally alloc resource only happen at PCI devices probe. All pci space
> > will be fixed after system probe.
> >
>
> I don't think so. This resource still belongs to the host bridge, so we should
> add it back.
When add back? It was reserved at bridge probe. When bridge remove, all
resource will released.
>
> - Mani
>
> --
> மணிவண்ணன் சதாசிவம்
^ permalink raw reply
* Re: [PATCH 1/1] arm64: dts: rockchip: enable onboard spi flash for rock-3a
From: Jonas Karlman @ 2024-04-08 15:14 UTC (permalink / raw)
To: Heiko Stübner, Chukun Pan
Cc: Conor Dooley, Krzysztof Kozlowski, Rob Herring, linux-arm-kernel,
linux-rockchip, linux-kernel, devicetree
In-Reply-To: <3066222.xgJ6IN8ObU@diego>
On 2024-04-08 17:04, Heiko Stübner wrote:
> Am Montag, 8. April 2024, 16:56:59 CEST schrieb Jonas Karlman:
>> On 2024-04-08 16:44, Jonas Karlman wrote:
>>> On 2024-04-08 14:40, Chukun Pan wrote:
>>>> There is a mx25u12835f spi flash on this board, enable it.
>>>>
>>>> [ 2.525805] spi-nor spi4.0: mx25u12835f (16384 Kbytes)
>>>>
>>>> Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
>>>> ---
>>>> arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts | 12 ++++++++++++
>>>> 1 file changed, 12 insertions(+)
>>>>
>>>> diff --git a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>>>> index a5e974ea659e..d8738cc47c73 100644
>>>> --- a/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>>>> +++ b/arch/arm64/boot/dts/rockchip/rk3568-rock-3a.dts
>>>> @@ -757,6 +757,18 @@ &sdmmc2 {
>>>> status = "okay";
>>>> };
>>>>
>>>> +&sfc {
>>>
>>> This is missing:
>>>
>>> #address-cells = <1>;
>>> #size-cells = <0>;
>>>
>>>> + status = "okay";
>>>> +
>>>> + flash@0 {
>>>> + compatible = "jedec,spi-nor";
>>>> + reg = <0x0>;
>>>> + spi-max-frequency = <50000000>;
>>>
>>> At least in U-Boot the spi clock only support 24, 100 or 200 mhz and I
>>> am pretty sure the spi flash support 100mhz, so I would suggest you test
>>> with 100mhz, same as used on other rk356x boards.
>>
>> Sorry, looked at spi clock instead of sfc clock.
>> sfc clock support 24, 50, 75, 100, 125 and 150 mhz.
>
> I think in some previous discussion the agreement was that the node
> should specify the max frequency the flash supports and have the
> controller worry about its own clock ranges.
Interesting, sounds like there is a need to do some modification to spi
and clk drivers in U-Boot to support that.
>
> Though in this case the 50MHz for the flash even matches the clock
> frequency supported by the sfc
I think the on-board spi nor flash support 104/108/133 mhz so 50 mhz is
wrong in any case.
Regards,
Jonas
>
>
^ permalink raw reply
* Re: [PATCHv3 1/2] dt-bindings: usb: typec: anx7688: start a binding document
From: Ondřej Jirman @ 2024-04-08 15:17 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Pavel Machek, phone-devel, kernel list, fiona.klute, martijn,
samuel, heikki.krogerus, gregkh, linux-usb, robh+dt,
krzysztof.kozlowski+dt, devicetree
In-Reply-To: <1502383c-9caf-4362-8bd6-ed719a304f08@linaro.org>
On Mon, Apr 08, 2024 at 03:27:00PM GMT, Krzysztof Kozlowski wrote:
> On 08/04/2024 14:48, Ondřej Jirman wrote:
> > Yeah, I understand where the confusion is. The driver is not for anx7688 chip
> > really. The driver is named anx7688, but that's mostly a historical accident at
> > this point.
> >
> > I guess there can be a driver for anx7688 chip that can directly use the chip's
> > resources from the host by directly manipulating its registers and implementing
> > type-c functionality via eg. Linux's TCPM or TCPCI stack, etc. (eg. like
> > fusb302 driver, or various tcpci subdrivers).
> >
> > But in this case the chip is driven by an optional on-chip microcontroller's
> > firmware and *this driver* is specifically for *the Type-C port on Pinephone*
>
> We do not talk here about the driver, but bindings, so hardware.
Got it. Bindings should be the same regardless of what driver would be used,
whether this OCM based one, or some future one based on the above mentioned
TCPCI in-kernel implementation. Hardware is the same in both cases.
Just trying to imagine how to actually solve the issues...
Basic thing with the I2C regulator thing is that needs to be enabled as long
as anx7688 needs to communicate over I2C. Other user of this power rail is
touchscreen controller for its normal power supply, and it needs to be able
to disable it during system suspend.
Now for things to not fail during suspend/resume based on PM callbacks
invocation order, anx7688 driver needs to enable this regulator too, as long
as it needs it.
I can put bus-supply to I2C controller node, and read it from the ANX7688 driver
I guess, by going up a DT node. Whether that's going to be acceptable, I don't
know.
VCONN regulator I don't know where else to put either. It doesn't seem to belong
anywhere. It's not something directly connected to Type-C connector, so
not part of connector bindings, and there's nothing else I can see, other
than anx7688 device which needs it for core functionality.
ANX7688 chip desing doesn't have integrated VCONN mosfet switches so it always
needs external supply + switches that are controlled by the chip itself. There's
no sensible design where someone would not want this and the driver needs
to get this regulator reference from somewhere. The switches are sort of an
extension of the chip.
kind regards,
o.
> > and serves as an integration driver for quite a bunch of things that need to
> > work together on Pinephone for all of the Type-C port's features to operate
> > reasonably well (and one of those is some communication with anx7688 firmware
> > that we use, and enabling power to this chip and other things as appropriate,
> > based on the communication from the firmware).
>
> That's still looking like putting board design into particular device
> binding.
>
> >
> > It handles the specific needs of the Pinephone's Type-C implementation, all of
> > its quirks (of which there are many over several HW revisions) that can't be
> > handled by the particular implementation of on-chip microcontroller firmware
> > directly and need host side interaction.
> >
> > In an ideal world, many of the things this driver handles would be handled by
> > embedded microcontroller on the board (like it is with some RK3399 based Google
> > devices), but Pinephone has no such thing and this glue needs to be implemented
> > somewhere in the kernel.
>
> You might need multiple schemas, because this is for anx7688, not for
> Pinephone type-c implementation.
>
> However I still do not see yet a limitation of DTS requiring stuffing
> some other properties into anx7688 or creating some other, virtual entity.
>
>
> Best regards,
> Krzysztof
>
^ permalink raw reply
* [PATCH RESEND v10 0/5] Add support for CS40L50
From: James Ogletree @ 2024-04-08 15:32 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
* [PATCH RESEND v10 1/5] firmware: cs_dsp: Add write sequence interface
From: James Ogletree @ 2024-04-08 15:32 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: <20240408153214.42368-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 RESEND v10 3/5] mfd: cs40l50: Add support for CS40L50 core driver
From: James Ogletree @ 2024-04-08 15:32 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: <20240408153214.42368-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 RESEND v10 2/5] dt-bindings: input: cirrus,cs40l50: Add initial DT binding
From: James Ogletree @ 2024-04-08 15:32 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: <20240408153214.42368-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 RESEND v10 4/5] Input: cs40l50 - Add support for the CS40L50 haptic driver
From: James Ogletree @ 2024-04-08 15:32 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: <20240408153214.42368-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 RESEND v10 5/5] ASoC: cs40l50: Support I2S streaming to CS40L50
From: James Ogletree @ 2024-04-08 15:32 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,
David Rhodes
In-Reply-To: <20240408153214.42368-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.
Reviewed-by: David Rhodes <drhodes@opensource.cirrus.com>
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
* Re: [PATCH 1/1] arm64: dts: imx8qxp-mek: add cm40_i2c, wm8960/wm8962 and sai[0,1,4,5]
From: Frank Li @ 2024-04-08 15:36 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
open list
In-Reply-To: <938489b1-eb94-4f8a-8881-230951299f6a@linaro.org>
On Fri, Apr 05, 2024 at 08:21:18PM +0200, Krzysztof Kozlowski wrote:
> On 05/04/2024 16:46, Frank Li wrote:
> > On Fri, Apr 05, 2024 at 08:41:59AM +0200, Krzysztof Kozlowski wrote:
> >> On 04/04/2024 18:19, Frank Li wrote:
> >>> imx8qxp-mek use two kind audio codec, wm8960 and wm8962. Using dummy gpio
> >>> i2c bus mux to connect both i2c devices. One will probe failure and other
> >>> will probe success when devices driver check whoami. So one dtb can cover
> >>> both board configuration.
> >>
> >> I don't understand it. Either you add real device or not. If one board
> >> has two devices, then why do you need to check for failures?
> >>
> >> Anyway, don't add fake stuff to DTS.
> >
> > NAK can't resolve the problem. It should be common problem for long time
> > cycle boards. Some chipes will be out life cycle. such as some sensor. So
> > chips on boards have been replace by some pin to pin compatible sensor. For
> > example:
> > old boards: use sensor A with address 0x1a
> > new bench: use sensor B with address 0x1b.
> >
> > You can treat it as two kind boards, RevA or RevB. But most user want to
> > use one dtb to handle such small differences. For this case, it should be
> > simple. Just add a super set.
> > i2c
> > {
> > sensorA@1a
> > {
> > }
> > sensorB@1b
> > {
> > }
> > }
> >
> > It also depend on whoami check by i2c devices. Only A or B will probe.
> >
> > wm8960 and wm8962 are more complex example. wm8960 is out of life. But
> > wm8962 and wm8960 have the same i2c address. The current i2c frame can't
> > allow the same i2c address in one i2c bus.
> >
> > You are feel to NAK my method, but I hope you also provide constructive
> > solution to help resolve the problem.
>
> Yes, we resolved it long time ago. Your bootloader can (usually easily)
> detect revision of the board and load appropriate DTS or DTS+DTSO.
I knewn it. But the problem is one development boards A have many options,
so create many child dts for files, A1, A2, ... An which base on A
If there are difference happen at A, create new B. then create all child
dtb, B1, B2, ... Bn. DTB number will increase exponent.
If change is quite bit, we have to do that. But if change is quite small,
One dtb can cover it by driver auto detect, which will work like some
adaptor card have not plug into boards, or some sensor or NOR-flash have
not installed because reduce cost.
Although boot loader can update dts or choose difference dts, It also cause
many confusition, such as layerscape, uboot update many kernel dtb's
information, which actually increase dependence between uboot and kernel.
Also it confuse people, for example, when try to debug kernel dtb, why
change have not token affect when change dts because not realized uboot
over write it.
What's I dide is that trying to reduce unnecessary dts.
Frank
>
> Best regards,
> Krzysztof
>
^ permalink raw reply
* Re: [PATCH 2/3] platform/x86: ideapad-laptop: add fn_lock_get/set functions
From: Hans de Goede @ 2024-04-08 15:49 UTC (permalink / raw)
To: Gergo Koteles, Ike Panhc, Ilpo Järvinen, Pavel Machek,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: platform-driver-x86, linux-kernel, linux-leds, devicetree
In-Reply-To: <dfd3a62a2b71339bbddf01e8a2ccd5ca92ce7202.1712063200.git.soyer@irl.hu>
Hi,
On 4/2/24 3:21 PM, Gergo Koteles wrote:
> The FnLock is retrieved and set in several places in the code.
>
> Move details into ideapad_fn_lock_get and ideapad_fn_lock_set functions.
>
> Signed-off-by: Gergo Koteles <soyer@irl.hu>
> ---
> drivers/platform/x86/ideapad-laptop.c | 38 +++++++++++++++++++--------
> 1 file changed, 27 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
> index 901849810ce2..529df08af548 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -513,11 +513,8 @@ static ssize_t fan_mode_store(struct device *dev,
>
> static DEVICE_ATTR_RW(fan_mode);
>
> -static ssize_t fn_lock_show(struct device *dev,
> - struct device_attribute *attr,
> - char *buf)
> +static int ideapad_fn_lock_get(struct ideapad_private *priv)
> {
> - struct ideapad_private *priv = dev_get_drvdata(dev);
> unsigned long hals;
> int err;
>
> @@ -525,7 +522,27 @@ static ssize_t fn_lock_show(struct device *dev,
> if (err)
> return err;
>
> - return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals));
> + return !!test_bit(HALS_FNLOCK_STATE_BIT, &hals);
> +}
> +
> +static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state)
> +{
> + return exec_sals(priv->adev->handle,
> + state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> +}
> +
> +static ssize_t fn_lock_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ideapad_private *priv = dev_get_drvdata(dev);
> + int brightness;
> +
> + brightness = ideapad_fn_lock_get(priv);
> + if (brightness < 0)
> + return brightness;
> +
> + return sysfs_emit(buf, "%d\n", brightness);
> }
>
> static ssize_t fn_lock_store(struct device *dev,
> @@ -540,7 +557,7 @@ static ssize_t fn_lock_store(struct device *dev,
> if (err)
> return err;
>
> - err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> + err = ideapad_fn_lock_set(priv, state);
> if (err)
> return err;
>
> @@ -1709,7 +1726,6 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> {
> struct ideapad_wmi_private *wpriv = dev_get_drvdata(&wdev->dev);
> struct ideapad_private *priv;
> - unsigned long result;
>
> mutex_lock(&ideapad_shared_mutex);
>
> @@ -1722,11 +1738,11 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> ideapad_input_report(priv, 128);
> break;
> case IDEAPAD_WMI_EVENT_FN_KEYS:
> - if (priv->features.set_fn_lock_led &&
> - !eval_hals(priv->adev->handle, &result)) {
> - bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result);
> + if (priv->features.set_fn_lock_led) {
> + int brightness = ideapad_fn_lock_get(priv);
>
> - exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> + if (brightness >= 0)
> + ideapad_fn_lock_set(priv, brightness);
> }
>
> if (data->type != ACPI_TYPE_INTEGER) {
^ permalink raw reply
* Re: [PATCH 2/3] platform/x86: ideapad-laptop: add fn_lock_get/set functions
From: Hans de Goede @ 2024-04-08 15:49 UTC (permalink / raw)
To: Gergo Koteles, Ike Panhc, Ilpo Järvinen, Pavel Machek,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: platform-driver-x86, linux-kernel, linux-leds, devicetree
In-Reply-To: <dfd3a62a2b71339bbddf01e8a2ccd5ca92ce7202.1712063200.git.soyer@irl.hu>
<sorry about the previous empty email, I hit send too soon>
Hi,
On 4/2/24 3:21 PM, Gergo Koteles wrote:
> The FnLock is retrieved and set in several places in the code.
>
> Move details into ideapad_fn_lock_get and ideapad_fn_lock_set functions.
>
> Signed-off-by: Gergo Koteles <soyer@irl.hu>
Thanks, patch looks good to me:
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Regards,
Hans
> ---
> drivers/platform/x86/ideapad-laptop.c | 38 +++++++++++++++++++--------
> 1 file changed, 27 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
> index 901849810ce2..529df08af548 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -513,11 +513,8 @@ static ssize_t fan_mode_store(struct device *dev,
>
> static DEVICE_ATTR_RW(fan_mode);
>
> -static ssize_t fn_lock_show(struct device *dev,
> - struct device_attribute *attr,
> - char *buf)
> +static int ideapad_fn_lock_get(struct ideapad_private *priv)
> {
> - struct ideapad_private *priv = dev_get_drvdata(dev);
> unsigned long hals;
> int err;
>
> @@ -525,7 +522,27 @@ static ssize_t fn_lock_show(struct device *dev,
> if (err)
> return err;
>
> - return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals));
> + return !!test_bit(HALS_FNLOCK_STATE_BIT, &hals);
> +}
> +
> +static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state)
> +{
> + return exec_sals(priv->adev->handle,
> + state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> +}
> +
> +static ssize_t fn_lock_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ideapad_private *priv = dev_get_drvdata(dev);
> + int brightness;
> +
> + brightness = ideapad_fn_lock_get(priv);
> + if (brightness < 0)
> + return brightness;
> +
> + return sysfs_emit(buf, "%d\n", brightness);
> }
>
> static ssize_t fn_lock_store(struct device *dev,
> @@ -540,7 +557,7 @@ static ssize_t fn_lock_store(struct device *dev,
> if (err)
> return err;
>
> - err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> + err = ideapad_fn_lock_set(priv, state);
> if (err)
> return err;
>
> @@ -1709,7 +1726,6 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> {
> struct ideapad_wmi_private *wpriv = dev_get_drvdata(&wdev->dev);
> struct ideapad_private *priv;
> - unsigned long result;
>
> mutex_lock(&ideapad_shared_mutex);
>
> @@ -1722,11 +1738,11 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> ideapad_input_report(priv, 128);
> break;
> case IDEAPAD_WMI_EVENT_FN_KEYS:
> - if (priv->features.set_fn_lock_led &&
> - !eval_hals(priv->adev->handle, &result)) {
> - bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result);
> + if (priv->features.set_fn_lock_led) {
> + int brightness = ideapad_fn_lock_get(priv);
>
> - exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> + if (brightness >= 0)
> + ideapad_fn_lock_set(priv, brightness);
> }
>
> if (data->type != ACPI_TYPE_INTEGER) {
^ permalink raw reply
* Re: [PATCH 3/3] platform/x86: ideapad-laptop: add FnLock LED class device
From: Hans de Goede @ 2024-04-08 15:50 UTC (permalink / raw)
To: Gergo Koteles, Ike Panhc, Ilpo Järvinen, Pavel Machek,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: platform-driver-x86, linux-kernel, linux-leds, devicetree
In-Reply-To: <2db08c948568a8d5352780864956c3271b4e42ce.1712063200.git.soyer@irl.hu>
Hi,
On 4/2/24 3:21 PM, Gergo Koteles wrote:
> Some Ideapad/Yoga Laptops have an FnLock LED in the Esc key.
>
> Expose Fnlock as an LED class device for easier OSD support.
>
> Signed-off-by: Gergo Koteles <soyer@irl.hu>
Thanks, patch looks good to me:
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Regards,
Hans
> ---
> drivers/platform/x86/ideapad-laptop.c | 97 ++++++++++++++++++++++++++-
> 1 file changed, 96 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
> index 529df08af548..8a5bef4eedfe 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -152,6 +152,11 @@ struct ideapad_private {
> struct led_classdev led;
> unsigned int last_brightness;
> } kbd_bl;
> + struct {
> + bool initialized;
> + struct led_classdev led;
> + unsigned int last_brightness;
> + } fn_lock;
> };
>
> static bool no_bt_rfkill;
> @@ -531,6 +536,19 @@ static int ideapad_fn_lock_set(struct ideapad_private *priv, bool state)
> state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF);
> }
>
> +static void ideapad_fn_lock_led_notify(struct ideapad_private *priv, int brightness)
> +{
> + if (!priv->fn_lock.initialized)
> + return;
> +
> + if (brightness == priv->fn_lock.last_brightness)
> + return;
> +
> + priv->fn_lock.last_brightness = brightness;
> +
> + led_classdev_notify_brightness_hw_changed(&priv->fn_lock.led, brightness);
> +}
> +
> static ssize_t fn_lock_show(struct device *dev,
> struct device_attribute *attr,
> char *buf)
> @@ -561,6 +579,8 @@ static ssize_t fn_lock_store(struct device *dev,
> if (err)
> return err;
>
> + ideapad_fn_lock_led_notify(priv, state);
> +
> return count;
> }
>
> @@ -1479,6 +1499,65 @@ static void ideapad_kbd_bl_exit(struct ideapad_private *priv)
> led_classdev_unregister(&priv->kbd_bl.led);
> }
>
> +/*
> + * FnLock LED
> + */
> +static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led_cdev)
> +{
> + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
> +
> + return ideapad_fn_lock_get(priv);
> +}
> +
> +static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
> + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
> +
> + return ideapad_fn_lock_set(priv, brightness);
> +}
> +
> +static int ideapad_fn_lock_led_init(struct ideapad_private *priv)
> +{
> + int brightness, err;
> +
> + if (!priv->features.fn_lock)
> + return -ENODEV;
> +
> + if (WARN_ON(priv->fn_lock.initialized))
> + return -EEXIST;
> +
> + priv->fn_lock.led.max_brightness = 1;
> +
> + brightness = ideapad_fn_lock_get(priv);
> + if (brightness < 0)
> + return brightness;
> +
> + priv->fn_lock.last_brightness = brightness;
> + priv->fn_lock.led.name = "platform::" LED_FUNCTION_FNLOCK;
> + priv->fn_lock.led.brightness_get = ideapad_fn_lock_led_cdev_get;
> + priv->fn_lock.led.brightness_set_blocking = ideapad_fn_lock_led_cdev_set;
> + priv->fn_lock.led.flags = LED_BRIGHT_HW_CHANGED;
> +
> + err = led_classdev_register(&priv->platform_device->dev, &priv->fn_lock.led);
> + if (err)
> + return err;
> +
> + priv->fn_lock.initialized = true;
> +
> + return 0;
> +}
> +
> +static void ideapad_fn_lock_led_exit(struct ideapad_private *priv)
> +{
> + if (!priv->fn_lock.initialized)
> + return;
> +
> + priv->fn_lock.initialized = false;
> +
> + led_classdev_unregister(&priv->fn_lock.led);
> +}
> +
> /*
> * module init/exit
> */
> @@ -1741,8 +1820,10 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> if (priv->features.set_fn_lock_led) {
> int brightness = ideapad_fn_lock_get(priv);
>
> - if (brightness >= 0)
> + if (brightness >= 0) {
> ideapad_fn_lock_set(priv, brightness);
> + ideapad_fn_lock_led_notify(priv, brightness);
> + }
> }
>
> if (data->type != ACPI_TYPE_INTEGER) {
> @@ -1754,6 +1835,10 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
> dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n",
> data->integer.value);
>
> + /* 0x02 FnLock, 0x03 Esc */
> + if (data->integer.value == 0x02 || data->integer.value == 0x03)
> + ideapad_fn_lock_led_notify(priv, data->integer.value == 0x02);
> +
> ideapad_input_report(priv,
> data->integer.value | IDEAPAD_WMI_KEY);
>
> @@ -1847,6 +1932,14 @@ static int ideapad_acpi_add(struct platform_device *pdev)
> dev_info(&pdev->dev, "Keyboard backlight control not available\n");
> }
>
> + err = ideapad_fn_lock_led_init(priv);
> + if (err) {
> + if (err != -ENODEV)
> + dev_warn(&pdev->dev, "Could not set up FnLock LED: %d\n", err);
> + else
> + dev_info(&pdev->dev, "FnLock control not available\n");
> + }
> +
> /*
> * On some models without a hw-switch (the yoga 2 13 at least)
> * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
> @@ -1903,6 +1996,7 @@ static int ideapad_acpi_add(struct platform_device *pdev)
> for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
> ideapad_unregister_rfkill(priv, i);
>
> + ideapad_fn_lock_led_exit(priv);
> ideapad_kbd_bl_exit(priv);
> ideapad_input_exit(priv);
>
> @@ -1930,6 +2024,7 @@ static void ideapad_acpi_remove(struct platform_device *pdev)
> for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
> ideapad_unregister_rfkill(priv, i);
>
> + ideapad_fn_lock_led_exit(priv);
> ideapad_kbd_bl_exit(priv);
> ideapad_input_exit(priv);
> ideapad_debugfs_exit(priv);
^ permalink raw reply
* Re: [PATCH v2 3/3] arm64: dts: qcom: msm8998: set qcom,no-msa-ready-indicator for wifi
From: Marc Gonzalez @ 2024-04-08 15:53 UTC (permalink / raw)
To: Kalle Valo
Cc: Dmitry Baryshkov, Konrad Dybcio, Krzysztof Kozlowski,
Jeff Johnson, ath10k, wireless, DT, MSM, Rob Herring,
Conor Dooley, Pierre-Hugues Husson, Arnaud Vrac, Bjorn Andersson,
Jami Kettunen, Marijn Suijten, Jeffrey Hugo
In-Reply-To: <87h6gh406w.fsf@kernel.org>
On 04/04/2024 17:28, Kalle Valo wrote:
> Marc Gonzalez wrote:
>
>> On 04/04/2024 13:57, Kalle Valo wrote:
>>
>>> Dmitry Baryshkov wrote:
>>>
>>>> I'd say, we should take a step back and actually verify how this was
>>>> handled in the vendor kernel.
>>>
>>> One comment related to this: usually vendor driver and firmware branches
>>> go "hand in hand", meaning that a version of driver supports only one
>>> specific firmware branch. And there can be a lot of branches. So even if
>>> one branch might have a check for something specific, there are no
>>> guarantees what the other N+1 branches do :/
>>
>> The consequences and ramifications of the above comment are not clear to me.
>>
>> Does this mean:
>> "It is pointless to analyze a given version (or even several versions)
>> of the vendor driver downstream, because there are exist a large number
>> of variations of the code." ?
>
> I was trying to say that because the design philosophy between vendor
> drivers and upstream drivers is very different, we can't 100% trust
> vendor drivers. It's a very good idea to check what vendor drivers do
> but we just need to be careful before making any conclusions. Testing
> real hardware (and corresponding firmware) is the most reliable way to
> know how different products/firmware work, unfortunately.
>
>> And thus, "it is nonsensical to try to "align" the mainline driver to
>> "the" vendor driver, as there is no single "vendor driver"" ?
>
> No no, I'm not saying that. I have suffered this "N+1 different firmware
> branches behaving slighly differently" problem since ath6kl days so for
> me this is business as usual, sadly. I'm sure we can find a solution for
> ath10k.
Hello Kalle,
I can spin a v3, no problem.
Do you prefer:
Option A = never waiting for the MSA_READY indicator for ANYONE
Option B = not waiting for the MSA_READY indicator when qcom,no-msa-ready-indicator is defined
Option C = not waiting for the MSA_READY indicator for certain platforms (based on root compatible)
Option D = some other solution not yet discussed
Dmitry has tested Option A on 5 platforms, where it does not induce regressions.
I worked on msm8998, where Option A (or any equivalent) unbreaks WiFi.
Please provide guidance :)
Regards
^ permalink raw reply
* [PATCH 0/9] rtc: convert multiple bindings into dtschema
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
This series converts the following bindings into dtschema, moving them
to trivial-rtc whenever possible:
- orion-rtc: trival-rtc, referenced in arm arch.
- google,goldfish-rtc: trivial-rtc, referenced in mips arch.
- lpc32xx-rtc: trival-rtc, referenced in arm arch.
- maxim,ds1742: trivial-rtc, not referenced in arch, cheap conversion.
- rtc-aspeed: 3 devices to trivial-rtc, all referenced in arm arch.
- pxa-rtc: add missing properties and convert. Referenced in arm arch.
- st,spear600-rtc: trivial-rtc, referenced in arm arch.
- stmp3xxx-rtc: convert, referenced in arm arch.
- via,vt8500-rtc: trivial-rtc, referenced in arm arch.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
Javier Carrasco (9):
dt-bindings: rtc: orion-rtc: move to trivial-rtc
dt-bindings: rtc: google,goldfish-rtc: move to trivial-rtc
dt-bindings: rtc: lpc32xx-rtc: move to trivial-rtc
dt-bindings: rtc: maxim,ds1742: move to trivial-rtc
dt-bindings: rtc: rtc-aspeed: move to trivial-rtc
dt-bindings: rtc: pxa-rtc: convert to dtschema
dt-bindings: rtc: spear-rtc: move to trivial-rtc
dt-bindings: rtc: stmp3xxx-rtc: convert to dtschema
dt-bindings: rtc: via,vt8500-rtc: move to trivial-rtc
.../devicetree/bindings/rtc/fsl,stmp3xxx-rtc.yaml | 45 ++++++++++++++++++++++
.../bindings/rtc/google,goldfish-rtc.txt | 17 --------
.../devicetree/bindings/rtc/lpc32xx-rtc.txt | 15 --------
.../devicetree/bindings/rtc/marvell,pxa-rtc.yaml | 40 +++++++++++++++++++
.../devicetree/bindings/rtc/maxim,ds1742.txt | 12 ------
.../devicetree/bindings/rtc/orion-rtc.txt | 18 ---------
Documentation/devicetree/bindings/rtc/pxa-rtc.txt | 14 -------
.../devicetree/bindings/rtc/rtc-aspeed.txt | 22 -----------
.../devicetree/bindings/rtc/spear-rtc.txt | 15 --------
.../devicetree/bindings/rtc/stmp3xxx-rtc.txt | 21 ----------
.../devicetree/bindings/rtc/trivial-rtc.yaml | 18 +++++++++
.../devicetree/bindings/rtc/via,vt8500-rtc.txt | 15 --------
12 files changed, 103 insertions(+), 149 deletions(-)
---
base-commit: fec50db7033ea478773b159e0e2efb135270e3b7
change-id: 20240406-rtc_dtschema-302824d1ec20
Best regards,
--
Javier Carrasco <javier.carrasco.cruz@gmail.com>
^ permalink raw reply
* [PATCH 1/9] dt-bindings: rtc: orion-rtc: move to trivial-rtc
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
This RTC requires a compatible, a reg and a single interrupt,
which makes it suitable for a direct conversion into trivial-rtc.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
Documentation/devicetree/bindings/rtc/orion-rtc.txt | 18 ------------------
Documentation/devicetree/bindings/rtc/trivial-rtc.yaml | 2 ++
2 files changed, 2 insertions(+), 18 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/orion-rtc.txt b/Documentation/devicetree/bindings/rtc/orion-rtc.txt
deleted file mode 100644
index 3bf63ffa5160..000000000000
--- a/Documentation/devicetree/bindings/rtc/orion-rtc.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-* Mvebu Real Time Clock
-
-RTC controller for the Kirkwood, the Dove, the Armada 370 and the
-Armada XP SoCs
-
-Required properties:
-- compatible : Should be "marvell,orion-rtc"
-- reg: physical base address of the controller and length of memory mapped
- region.
-- interrupts: IRQ line for the RTC.
-
-Example:
-
-rtc@10300 {
- compatible = "marvell,orion-rtc";
- reg = <0xd0010300 0x20>;
- interrupts = <50>;
-};
diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
index c9e3c5262c21..5e63c8f7f618 100644
--- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
@@ -45,6 +45,8 @@ properties:
- isil,isl1208
# Intersil ISL1218 Low Power RTC with Battery Backed SRAM
- isil,isl1218
+ # Mvebu Real-time Clock
+ - marvell,orion-rtc
# SPI-BUS INTERFACE REAL TIME CLOCK MODULE
- maxim,mcp795
# Real Time Clock Module with I2C-Bus
--
2.40.1
^ permalink raw reply related
* [PATCH 2/9] dt-bindings: rtc: google,goldfish-rtc: move to trivial-rtc
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
This RTC requires a compatible, a reg and a single interrupt,
which makes it suitable for a direct conversion into trivial-rtc.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
.../devicetree/bindings/rtc/google,goldfish-rtc.txt | 17 -----------------
Documentation/devicetree/bindings/rtc/trivial-rtc.yaml | 2 ++
2 files changed, 2 insertions(+), 17 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/google,goldfish-rtc.txt b/Documentation/devicetree/bindings/rtc/google,goldfish-rtc.txt
deleted file mode 100644
index 634312dd95ca..000000000000
--- a/Documentation/devicetree/bindings/rtc/google,goldfish-rtc.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Android Goldfish RTC
-
-Android Goldfish RTC device used by Android emulator.
-
-Required properties:
-
-- compatible : should contain "google,goldfish-rtc"
-- reg : <registers mapping>
-- interrupts : <interrupt mapping>
-
-Example:
-
- goldfish_timer@9020000 {
- compatible = "google,goldfish-rtc";
- reg = <0x9020000 0x1000>;
- interrupts = <0x3>;
- };
diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
index 5e63c8f7f618..b889b9317a41 100644
--- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
@@ -41,6 +41,8 @@ properties:
- epson,rx8571
# I2C-BUS INTERFACE REAL TIME CLOCK MODULE
- epson,rx8581
+ # Android Goldfish Real-time Clock
+ - google,goldfish-rtc
# Intersil ISL1208 Low Power RTC with Battery Backed SRAM
- isil,isl1208
# Intersil ISL1218 Low Power RTC with Battery Backed SRAM
--
2.40.1
^ permalink raw reply related
* [PATCH 3/9] dt-bindings: rtc: lpc32xx-rtc: move to trivial-rtc
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
This RTC requires a compatible, a reg and a single interrupt,
which makes it suitable for a direct conversion into trivial-rtc.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
Documentation/devicetree/bindings/rtc/lpc32xx-rtc.txt | 15 ---------------
Documentation/devicetree/bindings/rtc/trivial-rtc.yaml | 2 ++
2 files changed, 2 insertions(+), 15 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/lpc32xx-rtc.txt b/Documentation/devicetree/bindings/rtc/lpc32xx-rtc.txt
deleted file mode 100644
index a87a1e9bc060..000000000000
--- a/Documentation/devicetree/bindings/rtc/lpc32xx-rtc.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-* NXP LPC32xx SoC Real Time Clock controller
-
-Required properties:
-- compatible: must be "nxp,lpc3220-rtc"
-- reg: physical base address of the controller and length of memory mapped
- region.
-- interrupts: The RTC interrupt
-
-Example:
-
- rtc@40024000 {
- compatible = "nxp,lpc3220-rtc";
- reg = <0x40024000 0x1000>;
- interrupts = <52 0>;
- };
diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
index b889b9317a41..d6ce7ac29033 100644
--- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
@@ -55,6 +55,8 @@ properties:
- microcrystal,rv3029
# Real Time Clock
- microcrystal,rv8523
+ # NXP LPC32xx SoC Real-time Clock
+ - nxp,lpc3220-rtc
# Real-time Clock Module
- pericom,pt7c4338
# I2C bus SERIAL INTERFACE REAL-TIME CLOCK IC
--
2.40.1
^ permalink raw reply related
* [PATCH 4/9] dt-bindings: rtc: maxim,ds1742: move to trivial-rtc
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
This RTC only requires the compatible a reg and properties,
which makes it suitable for a direct conversion into trivial-rtc.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
Documentation/devicetree/bindings/rtc/maxim,ds1742.txt | 12 ------------
Documentation/devicetree/bindings/rtc/trivial-rtc.yaml | 2 ++
2 files changed, 2 insertions(+), 12 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/maxim,ds1742.txt b/Documentation/devicetree/bindings/rtc/maxim,ds1742.txt
deleted file mode 100644
index d0f937c355b5..000000000000
--- a/Documentation/devicetree/bindings/rtc/maxim,ds1742.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-* Maxim (Dallas) DS1742/DS1743 Real Time Clock
-
-Required properties:
-- compatible: Should contain "maxim,ds1742".
-- reg: Physical base address of the RTC and length of memory
- mapped region.
-
-Example:
- rtc: rtc@10000000 {
- compatible = "maxim,ds1742";
- reg = <0x10000000 0x800>;
- };
diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
index d6ce7ac29033..710b096fe6f7 100644
--- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
@@ -49,6 +49,8 @@ properties:
- isil,isl1218
# Mvebu Real-time Clock
- marvell,orion-rtc
+ # Maxim DS1742/DS1743 Real-time Clock
+ - maxim,ds1742
# SPI-BUS INTERFACE REAL TIME CLOCK MODULE
- maxim,mcp795
# Real Time Clock Module with I2C-Bus
--
2.40.1
^ permalink raw reply related
* [PATCH 5/9] dt-bindings: rtc: rtc-aspeed: move to trivial-rtc
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
The RTCs documented in this binding require a compatible, a reg
and a single interrupt, which make them suitable for a direct
conversion into trivial-rtc.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
.../devicetree/bindings/rtc/rtc-aspeed.txt | 22 ----------------------
.../devicetree/bindings/rtc/trivial-rtc.yaml | 6 ++++++
2 files changed, 6 insertions(+), 22 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/rtc-aspeed.txt b/Documentation/devicetree/bindings/rtc/rtc-aspeed.txt
deleted file mode 100644
index 2e956b3dc276..000000000000
--- a/Documentation/devicetree/bindings/rtc/rtc-aspeed.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-ASPEED BMC RTC
-==============
-
-Required properties:
- - compatible: should be one of the following
- * aspeed,ast2400-rtc for the ast2400
- * aspeed,ast2500-rtc for the ast2500
- * aspeed,ast2600-rtc for the ast2600
-
- - reg: physical base address of the controller and length of memory mapped
- region
-
- - interrupts: The interrupt number
-
-Example:
-
- rtc@1e781000 {
- compatible = "aspeed,ast2400-rtc";
- reg = <0x1e781000 0x18>;
- interrupts = <22>;
- status = "disabled";
- };
diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
index 710b096fe6f7..0f773167d32a 100644
--- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
+++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml
@@ -24,6 +24,12 @@ properties:
- abracon,abb5zes3
# AB-RTCMC-32.768kHz-EOZ9: Real Time Clock/Calendar Module with I2C Interface
- abracon,abeoz9
+ # ASPEED BMC ast2400 Real-time Clock
+ - aspeed,ast2400-rtc
+ # ASPEED BMC ast2500 Real-time Clock
+ - aspeed,ast2500-rtc
+ # ASPEED BMC ast2600 Real-time Clock
+ - aspeed,ast2600-rtc
# I2C, 32-Bit Binary Counter Watchdog RTC with Trickle Charger and Reset Input/Output
- dallas,ds1374
# Dallas DS1672 Real-time Clock
--
2.40.1
^ permalink raw reply related
* [PATCH 6/9] dt-bindings: rtc: pxa-rtc: convert to dtschema
From: Javier Carrasco @ 2024-04-08 15:53 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jiaxun Yang, Vladimir Zapolskiy, Joel Stanley, Andrew Jeffery,
Maxime Coquelin, Alexandre Torgue
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel,
linux-aspeed, linux-stm32, Javier Carrasco
In-Reply-To: <20240408-rtc_dtschema-v1-0-c447542fc362@gmail.com>
Convert existing binding to dtschema to support validation.
The missing 'reg' and 'interrupts' properties have been added, taking
the 2 supported interrupts into account to fix the example.
Signed-off-by: Javier Carrasco <javier.carrasco.cruz@gmail.com>
---
.../devicetree/bindings/rtc/marvell,pxa-rtc.yaml | 40 ++++++++++++++++++++++
Documentation/devicetree/bindings/rtc/pxa-rtc.txt | 14 --------
2 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/Documentation/devicetree/bindings/rtc/marvell,pxa-rtc.yaml b/Documentation/devicetree/bindings/rtc/marvell,pxa-rtc.yaml
new file mode 100644
index 000000000000..43d68681a1bf
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/marvell,pxa-rtc.yaml
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/rtc/marvell,pxa-rtc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: PXA Real Time Clock
+
+maintainers:
+ - Javier Carrasco <javier.carrasco.cruz@gmail.com>
+
+allOf:
+ - $ref: rtc.yaml#
+
+properties:
+ compatible:
+ const: marvell,pxa-rtc
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ items:
+ - description: 1 Hz
+ - description: Alarm
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ rtc@40900000 {
+ compatible = "marvell,pxa-rtc";
+ reg = <0x40900000 0x3c>;
+ interrupts = <30>, <31>;
+ };
diff --git a/Documentation/devicetree/bindings/rtc/pxa-rtc.txt b/Documentation/devicetree/bindings/rtc/pxa-rtc.txt
deleted file mode 100644
index 8c6672a1b7d7..000000000000
--- a/Documentation/devicetree/bindings/rtc/pxa-rtc.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-* PXA RTC
-
-PXA specific RTC driver.
-
-Required properties:
-- compatible : Should be "marvell,pxa-rtc"
-
-Examples:
-
-rtc@40900000 {
- compatible = "marvell,pxa-rtc";
- reg = <0x40900000 0x3c>;
- interrupts = <30 31>;
-};
--
2.40.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).