* [PATCH 2/6] HID: i2c-hid-of: Allow using i2c-hid-of on non OF platforms
From: Hans de Goede @ 2023-04-09 14:42 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Douglas Anderson
Cc: Hans de Goede, linux-input
In-Reply-To: <20230409144243.25360-1-hdegoede@redhat.com>
There are some x86 tablets / 2-in-1s which ship with Android as their
factory OS image. These have pretty broken ACPI tables, relying on
everything being hardcoded in the factory kernel image.
platform/x86/x86-android-tablets.c manually instantiates i2c-clients for
i2c devices on these tablets to make them work with the mainline kernel.
The Lenovo Yoga Book 1 (yb1-x90f/l) is such a 2-in-1. It has 2 I2C-HID
devices its main touchscreen and a Wacom digitizer. Its main touchscreen
can alternatively also be used in HiDeep's native protocol mode but
for the Wacom digitizer we really need I2C-HID.
This patch allows using i2c-hid-of on non OF platforms so that it can
bind to a non ACPI instantiated i2c_client on x86 for the Wacom digitizer.
Note the driver already has an "i2c-over-hid" i2c_device_id (rather then
an of_device_id).
Besides enabling building on non-OF platforms this also replaces
the only of_property_read_u32() call with device_property_read_u32() note
that other properties where already read using device_property_read_...().
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/hid/i2c-hid/Kconfig | 6 ++++--
drivers/hid/i2c-hid/i2c-hid-of.c | 4 +++-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index 4439be7fa74d..3be17109301a 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -23,12 +23,14 @@ config I2C_HID_ACPI
config I2C_HID_OF
tristate "HID over I2C transport layer Open Firmware driver"
- depends on OF
+ # No "depends on OF" because this can also be used for manually
+ # (board-file) instantiated "hid-over-i2c" type i2c-clients.
select I2C_HID_CORE
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
other HID based devices which is connected to your computer via I2C.
- This driver supports Open Firmware (Device Tree)-based systems.
+ This driver supports Open Firmware (Device Tree)-based systems as
+ well as binding to manually (board-file) instantiated i2c-hid-clients.
If unsure, say N.
diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c
index c82a5a54c3e6..385f7460e03c 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of.c
@@ -82,7 +82,7 @@ static int i2c_hid_of_probe(struct i2c_client *client)
ihid_of->ops.power_up = i2c_hid_of_power_up;
ihid_of->ops.power_down = i2c_hid_of_power_down;
- ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
+ ret = device_property_read_u32(dev, "hid-descr-addr", &val);
if (ret) {
dev_err(dev, "HID register address not provided\n");
return -ENODEV;
@@ -113,11 +113,13 @@ static int i2c_hid_of_probe(struct i2c_client *client)
hid_descriptor_address, quirks);
}
+#ifdef CONFIG_OF
static const struct of_device_id i2c_hid_of_match[] = {
{ .compatible = "hid-over-i2c" },
{},
};
MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
+#endif
static const struct i2c_device_id i2c_hid_of_id_table[] = {
{ "hid", 0 },
--
2.39.1
^ permalink raw reply related
* [PATCH 0/6] HID: i2c-hid-of: Allow using i2c-hid-of on non OF platforms + remove specialized drivers
From: Hans de Goede @ 2023-04-09 14:42 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Douglas Anderson
Cc: Hans de Goede, linux-input
Hi All,
This series consist of 2 parts:
1. Patches 1-3. Allow using i2c-hid-of on non OF platforms to allow I2C-HID
devices which are not enumerated by ACPI to work on ACPI platforms
(by manual i2c_client instantiation using i2c_client_id matching).
2. Patches 4-6. Remove the special i2c-hid-of-elan and i2c-hid-of-goodix
driver, folding the functionality into the generic i2c-hid-of driver.
Since 1. requires adding reset-gpio support to i2c-hid-of there was
very little difference left between the generic i2c-hid-of code and
the specialized drivers. So I decided to merge them into the generic
driver instead of having duplicate code.
Note patches 4-6 have not been actually tested with an "elan,ekth6915"
touchscreen nor with a "goodix,gt7375p" touchscreen.
Douglas, can you perhaps test this patch-set with an "elan,ekth6915"
touchscreen and with a "goodix,gt7375p" touchscreen ?
Regards,
Hans
Hans de Goede (6):
HID: i2c-hid-of: Consistenly use dev local variable in probe()
HID: i2c-hid-of: Allow using i2c-hid-of on non OF platforms
HID: i2c-hid-of: Add reset GPIO support to i2c-hid-of
HID: i2c-hid-of: Add chip_data struct
HID: i2c-hid-of: Consolidate Elan support into generic i2c-hid-of
driver
HID: i2c-hid-of: Consolidate Goodix support into generic i2c-hid-of
driver
drivers/hid/i2c-hid/Kconfig | 36 +------
drivers/hid/i2c-hid/Makefile | 2 -
drivers/hid/i2c-hid/i2c-hid-of-elan.c | 129 ------------------------
drivers/hid/i2c-hid/i2c-hid-of-goodix.c | 125 -----------------------
drivers/hid/i2c-hid/i2c-hid-of.c | 124 +++++++++++++++++++----
5 files changed, 106 insertions(+), 310 deletions(-)
delete mode 100644 drivers/hid/i2c-hid/i2c-hid-of-elan.c
delete mode 100644 drivers/hid/i2c-hid/i2c-hid-of-goodix.c
--
2.39.1
^ permalink raw reply
* [PATCH 1/6] HID: i2c-hid-of: Consistenly use dev local variable in probe()
From: Hans de Goede @ 2023-04-09 14:42 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Douglas Anderson
Cc: Hans de Goede, linux-input
In-Reply-To: <20230409144243.25360-1-hdegoede@redhat.com>
i2c_hid_of_probe() has a dev local variable pointing to &i2c_client->dev,
consistently use this everywhere in i2c_hid_of_probe().
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
drivers/hid/i2c-hid/i2c-hid-of.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c
index 10176568133a..c82a5a54c3e6 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of.c
@@ -75,7 +75,7 @@ static int i2c_hid_of_probe(struct i2c_client *client)
int ret;
u32 val;
- ihid_of = devm_kzalloc(&client->dev, sizeof(*ihid_of), GFP_KERNEL);
+ ihid_of = devm_kzalloc(dev, sizeof(*ihid_of), GFP_KERNEL);
if (!ihid_of)
return -ENOMEM;
@@ -84,24 +84,21 @@ static int i2c_hid_of_probe(struct i2c_client *client)
ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
if (ret) {
- dev_err(&client->dev, "HID register address not provided\n");
+ dev_err(dev, "HID register address not provided\n");
return -ENODEV;
}
if (val >> 16) {
- dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
- val);
+ dev_err(dev, "Bad HID register address: 0x%08x\n", val);
return -EINVAL;
}
hid_descriptor_address = val;
- if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms",
- &val))
+ if (!device_property_read_u32(dev, "post-power-on-delay-ms", &val))
ihid_of->post_power_delay_ms = val;
ihid_of->supplies[0].supply = "vdd";
ihid_of->supplies[1].supply = "vddl";
- ret = devm_regulator_bulk_get(&client->dev,
- ARRAY_SIZE(ihid_of->supplies),
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ihid_of->supplies),
ihid_of->supplies);
if (ret)
return ret;
--
2.39.1
^ permalink raw reply related
* Re: [PATCH 2/5] Input: add driver for Focaltech FTS touchscreen
From: Joel Selvaraj @ 2023-04-09 14:24 UTC (permalink / raw)
To: Markuss Broks, Caleb Connolly, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Andy Gross, Bjorn Andersson, Konrad Dybcio,
Henrik Rydberg, Arnd Bergmann, Robert Jarzmik, Jeff LaBundy,
Neil Armstrong, Jean Delvare, Max Krummenacher, Job Noorman,
Alistair Francis, Chris Morgan
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
~postmarketos/upstreaming, phone-devel
In-Reply-To: <f9c8e0d4-1d0c-cefc-866c-046d4d374576@gmail.com>
Hi Markuss,
On 09/04/23 08:32, Markuss Broks wrote:
> By that I meant doing something like the Zinitix driver[1] does. It has
> a struct data type for whatever you read from hardware, e.g.
>
> struct point_coord {
> __le16 x;
> __le16 y;
> ...
> };
>
> from that driver. That way you can cast the data read to that struct and
> have it look a bit nicer.
Understood. Thanks for the clarification. Jeff LaBundy explained it a
bit too. I have addressed all the other comments in this patch series in
my WIP v2. However, I am having quite the trouble casting the buffer to
a struct. The register layout is bit weird. It would have been easier if
they are sets of 8bits or 16bits values. Instead it seems to be split in
terms 4bits and 12bits or I don't know. I am a bit new to this and
having trouble handling endianess with these weird splits in the buffer.
Here is a hand drawn image of the buffer layout [1]. Let me know if you
or anyone have any thoughts on this. Wonder if it's worth the trouble :)
[1] https://imgur.com/a/4RYrB1G
> This is just a suggestion though, you have the final choice in what
> design you choose for your code :)
I am gonna try it a few more days and if I can't make it work in a
sensible way, I will probably go with the existing approach.
> - Markuss
Thanks,
Joel
^ permalink raw reply
* Re: [PATCH 2/5] Input: add driver for Focaltech FTS touchscreen
From: Markuss Broks @ 2023-04-09 13:32 UTC (permalink / raw)
To: Joel Selvaraj, Caleb Connolly, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Andy Gross, Bjorn Andersson, Konrad Dybcio,
Henrik Rydberg, Arnd Bergmann, Robert Jarzmik, Jeff LaBundy,
Neil Armstrong, Jean Delvare, Max Krummenacher, Job Noorman,
Alistair Francis, Chris Morgan
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
~postmarketos/upstreaming, phone-devel
In-Reply-To: <d40faca2-fe5d-5b5a-eefe-68eb3e5e8125@gmail.com>
Hi Joel,
Sorry for responding late:
On 3/13/23 00:21, Joel Selvaraj wrote:
> Hi Markuss,
>
> Thanks for the quick review! I agree with most of your comments and will
> fix them in a v2 soon. I have a few doubts as discussed below.
>
> On 12/03/23 15:40, Markuss Broks wrote:
>
>> Why is the _ratelimited variant necessary?
> I assumed in case of the interrupt working, but i2c reads fail for some
> reason, it would spam a lot of error messages if the user touches the
> screen continuously, like a swipe up gesture or something.
>
> I referred to ad7879 touchscreen's irq handling code [1] and thought
> it's probably best to do this, to be on the safe side. I will remove
> this if it's not needed in v2.
>
>> Overall, I think it's better to cast the data type to a struct, which
>> would make this seem with less random.
> Sorry, I am not sure I got this right. Do you mean I create an array of
> struct called say "fts_point" that stores the x, y, type, etc. info of
> all the points, then report it separately. Like similar to something
> done by the auo-pixcir touchscreen driver [2]?
By that I meant doing something like the Zinitix driver[1] does. It has
a struct data type for whatever you read from hardware, e.g.
struct point_coord {
__le16 x;
__le16 y;
...
};
from that driver. That way you can cast the data read to that struct and
have it look a bit nicer.
This is just a suggestion though, you have the final choice in what
design you choose for your code :)
>
> If I didn't get this correctly, can you show me some code in mainline,
> that does it? It would be very helpful.
>
> [1]
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/input/touchscreen/ad7879.c?h=v6.3-rc1#n250
> [2]
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/input/touchscreen/auo-pixcir-ts.c?h=v6.3-rc1#n162
>
>> - Markuss
> Thanks,
> Joel
[1]
https://elixir.free-electrons.com/linux/v6.3-rc5/source/drivers/input/touchscreen/zinitix.c
- Markuss
^ permalink raw reply
* Re: [PATCH v4 2/3] Input: add ABS_SND_PROFILE
From: Bjorn Andersson @ 2023-04-07 17:11 UTC (permalink / raw)
To: Gergo Koteles, Dmitry Torokhov
Cc: Jonathan Corbet, Andy Gross, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Jiri Kosina, Benjamin Tissoires, linux-input,
linux-kernel, linux-arm-msm, devicetree, Caleb Connolly
In-Reply-To: <1a4752739568afbdbaaff48436d2bb595d2bda0d.1677022414.git.soyer@irl.hu>
On Wed, Feb 22, 2023 at 01:10:33AM +0100, Gergo Koteles wrote:
> ABS_SND_PROFILE used to describe the state of a multi-value sound profile
> switch. This will be used for the alert-slider on OnePlus phones or other
> phones.
>
> Profile values added as SND_PROFLE_(SILENT|VIBRATE|RING) identifiers
> to input-event-codes.h so they can be used from DTS.
>
> Signed-off-by: Gergo Koteles <soyer@irl.hu>
> ---
> Documentation/input/event-codes.rst | 6 ++++++
> drivers/hid/hid-debug.c | 1 +
> include/uapi/linux/input-event-codes.h | 9 +++++++++
> 3 files changed, 16 insertions(+)
>
> diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst
> index b4557462edd7..d43336e64d6a 100644
> --- a/Documentation/input/event-codes.rst
> +++ b/Documentation/input/event-codes.rst
> @@ -241,6 +241,12 @@ A few EV_ABS codes have special meanings:
> emitted only when the selected profile changes, indicating the newly
> selected profile value.
>
> +* ABS_SND_PROFILE:
> +
> + - Used to describe the state of a multi-value sound profile switch.
> + An event is emitted only when the selected profile changes,
> + indicating the newly selected profile value.
> +
> * ABS_MT_<name>:
>
> - Used to describe multitouch input events. Please see
> diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
> index e213bdde543a..76fb2ecbbc51 100644
> --- a/drivers/hid/hid-debug.c
> +++ b/drivers/hid/hid-debug.c
> @@ -1018,6 +1018,7 @@ static const char *absolutes[ABS_CNT] = {
> [ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt",
> [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth",
> [ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile",
> + [ABS_SND_PROFILE] = "SoundProfile",
> [ABS_MISC] = "Misc",
> [ABS_MT_TOUCH_MAJOR] = "MTMajor",
> [ABS_MT_TOUCH_MINOR] = "MTMinor",
> diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> index 022a520e31fc..e8d5ee027b40 100644
> --- a/include/uapi/linux/input-event-codes.h
> +++ b/include/uapi/linux/input-event-codes.h
> @@ -866,6 +866,7 @@
>
> #define ABS_VOLUME 0x20
> #define ABS_PROFILE 0x21
> +#define ABS_SND_PROFILE 0x22
>
> #define ABS_MISC 0x28
>
> @@ -974,4 +975,12 @@
> #define SND_MAX 0x07
> #define SND_CNT (SND_MAX+1)
>
> +/*
> + * ABS_SND_PROFILE values
> + */
> +
> +#define SND_PROFILE_SILENT 0x00
> +#define SND_PROFILE_VIBRATE 0x01
> +#define SND_PROFILE_RING 0x02
The patch looks good to me, bu I'd need these header file additions in
order to merge the dts patch. Could I get an ack and take it through the
Qualocmm tree, or could you pick it up for 6.4, and then I can merge the
dts change after that?
Regards,
Bjorn
> +
> #endif
> --
> 2.39.2
>
^ permalink raw reply
* Re: [PATCH] Input: elan_i2c - Implement inhibit/uninhibit functions.
From: Dmitry Torokhov @ 2023-04-07 16:57 UTC (permalink / raw)
To: jingle.wu; +Cc: linux-kernel, linux-input, phoenix, josh.chen, dave.wang
In-Reply-To: <20230320011456.986321-1-jingle.wu@emc.com.tw>
Hi Jingle,
On Mon, Mar 20, 2023 at 09:14:56AM +0800, jingle.wu wrote:
> Add inhibit/uninhibit functions.
>
> Signed-off-by: Jingle.wu <jingle.wu@emc.com.tw>
> ---
> drivers/input/mouse/elan_i2c_core.c | 86 +++++++++++++++++++++++++++++
> 1 file changed, 86 insertions(+)
>
> diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c
> index 5f0d75a45c80..b7100945c9cc 100644
> --- a/drivers/input/mouse/elan_i2c_core.c
> +++ b/drivers/input/mouse/elan_i2c_core.c
> @@ -329,6 +329,89 @@ static int elan_initialize(struct elan_tp_data *data, bool skip_reset)
> return error;
> }
>
> +static int elan_reactivate(struct elan_tp_data *data)
> +{
> + struct device *dev = &data->client->dev;
> + int ret;
Please call this variable and other similar ones "error".
> +
> + ret = elan_set_power(data, true);
> + if (ret)
> + dev_err(dev, "failed to restore power: %d\n", ret);
> +
> + ret = data->ops->sleep_control(data->client, false);
> + if (ret) {
> + dev_err(dev,
> + "failed to wake device up: %d\n", ret);
> + return ret;
> + }
> +
> + return ret;
return 0;
> +}
> +
> +static void elan_inhibit(struct input_dev *input_dev)
> +{
> + struct elan_tp_data *data = input_get_drvdata(input_dev);
> + struct i2c_client *client = data->client;
> + int ret;
> +
> + if (data->in_fw_update)
> + return;
Simply and silently ignoring inhibit request is not great. Can we wait
for firmware update to complete?
> +
> + dev_dbg(&client->dev, "inhibiting\n");
> + /*
> + * We are taking the mutex to make sure sysfs operations are
> + * complete before we attempt to bring the device into low[er]
> + * power mode.
> + */
> + ret = mutex_lock_interruptible(&data->sysfs_mutex);
> + if (ret)
> + return;
> +
> + disable_irq(client->irq);
> +
> + ret = elan_set_power(data, false);
> + if (ret)
> + enable_irq(client->irq);
> +
> + mutex_unlock(&data->sysfs_mutex);
> +
> +}
> +
> +static void elan_close(struct input_dev *input_dev)
> +{
> + if ((input_dev->users) && (!input_dev->inhibited))
> + elan_inhibit(input_dev);
I am not sure why you need these checks. Input core will only call
input_dev->close() when device is powered up st (i.e. it is not inhibited
and there are users of it) and when either:
- there is inhibit request or
- the last user is letting go of the device
Similarly elan_open() will be called when first user opens device if
device is not inhibited, or when request to uninhibit comes for
inhibited device that has users.
But you need to make sure you start in a low power state.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 2/2] dt-bindings: input: cirrus,cs40l26: Add bindings
From: Rob Herring @ 2023-04-07 14:21 UTC (permalink / raw)
To: Fred Treven
Cc: ben.bright, linux-kernel, linux-input, james.ogletree, robh+dt,
patches, dmitry.torokhov, devicetree, krzystztof.kozlowski+dt
In-Reply-To: <1680819613-29256-1-git-send-email-fred.treven@cirrus.com>
On Thu, 06 Apr 2023 17:20:13 -0500, Fred Treven wrote:
> Add devicetree bindings for CS40L26 driver.
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
> .../devicetree/bindings/input/cs40l26.yaml | 92 ++++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 93 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/cs40l26.yaml
>
My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/input/cs40l26.example.dts:24.13-26: Warning (reg_format): /example-0/i2c/cs40l26@58:reg: property has invalid length (4 bytes) (#address-cells == 2, #size-cells == 1)
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (pci_device_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (pci_device_bus_num): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (simple_bus_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/input/cs40l26.example.dts:21.13-34.11: Warning (i2c_bus_bridge): /example-0/i2c: incorrect #address-cells for I2C bus
Documentation/devicetree/bindings/input/cs40l26.example.dts:21.13-34.11: Warning (i2c_bus_bridge): /example-0/i2c: incorrect #size-cells for I2C bus
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (i2c_bus_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (i2c_bus_reg): Failed prerequisite 'i2c_bus_bridge'
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (spi_bus_reg): Failed prerequisite 'reg_format'
Documentation/devicetree/bindings/input/cs40l26.example.dts:22.31-33.13: Warning (avoid_default_addr_size): /example-0/i2c/cs40l26@58: Relying on default #address-cells value
Documentation/devicetree/bindings/input/cs40l26.example.dts:22.31-33.13: Warning (avoid_default_addr_size): /example-0/i2c/cs40l26@58: Relying on default #size-cells value
Documentation/devicetree/bindings/input/cs40l26.example.dtb: Warning (unique_unit_address_if_enabled): Failed prerequisite 'avoid_default_addr_size'
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/input/cs40l26.example.dtb: cs40l26@58: 'cirrus,bst-exploratory-mode-disabled', 'reset-gpios' do not match any of the regexes: 'pinctrl-[0-9]+'
From schema: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/input/cs40l26.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/1680819613-29256-1-git-send-email-fred.treven@cirrus.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* [PATCH v2 0/2] Input: atmel_mxt_ts - support capacitive keys
From: André Apitzsch @ 2023-04-07 12:44 UTC (permalink / raw)
To: Nick Dyer, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea, Linus Walleij
Cc: linux-input, devicetree, linux-arm-kernel, linux-kernel,
André Apitzsch, Krzysztof Kozlowski
Add support for touch keys found in some Atmel touch controller
configurations.
Signed-off-by: André Apitzsch <git@apitzsch.eu>
---
Changes in v2:
* Added A-b, R-b tags
---
André Apitzsch (2):
dt-bindings: input: atmel,maxtouch: add linux,keycodes
Input: atmel_mxt_ts - support capacitive keys
.../devicetree/bindings/input/atmel,maxtouch.yaml | 7 ++
drivers/input/touchscreen/atmel_mxt_ts.c | 85 ++++++++++++++++++++++
2 files changed, 92 insertions(+)
---
base-commit: f2afccfefe7be1f7346564fe619277110d341f9b
change-id: 20230407-atmel_keys-7a49c6b677b2
Best regards,
--
André Apitzsch <git@apitzsch.eu>
^ permalink raw reply
* [PATCH v2 1/2] dt-bindings: input: atmel,maxtouch: add linux,keycodes
From: André Apitzsch @ 2023-04-07 12:44 UTC (permalink / raw)
To: Nick Dyer, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea, Linus Walleij
Cc: linux-input, devicetree, linux-arm-kernel, linux-kernel,
André Apitzsch, Krzysztof Kozlowski
In-Reply-To: <20230407-atmel_keys-v2-0-92446a4343cb@apitzsch.eu>
In some configurations the touch controller can support the touch keys.
Document the linux,keycodes property that enables those keys and
specifies the keycodes that should be used to report the key events.
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: André Apitzsch <git@apitzsch.eu>
---
Documentation/devicetree/bindings/input/atmel,maxtouch.yaml | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml b/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
index 3ec579d63570..c40799355ed7 100644
--- a/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
+++ b/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
@@ -14,6 +14,9 @@ description: |
Atmel maXTouch touchscreen or touchpads such as the mXT244
and similar devices.
+allOf:
+ - $ref: input.yaml#
+
properties:
compatible:
const: atmel,maxtouch
@@ -60,6 +63,10 @@ properties:
or experiment to determine which bit corresponds to which input. Use
KEY_RESERVED for unused padding values.
+ linux,keycodes:
+ minItems: 1
+ maxItems: 8
+
atmel,wakeup-method:
$ref: /schemas/types.yaml#/definitions/uint32
description: |
--
2.40.0
^ permalink raw reply related
* [PATCH v2 2/2] Input: atmel_mxt_ts - support capacitive keys
From: André Apitzsch @ 2023-04-07 12:44 UTC (permalink / raw)
To: Nick Dyer, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Nicolas Ferre, Alexandre Belloni, Claudiu Beznea, Linus Walleij
Cc: linux-input, devicetree, linux-arm-kernel, linux-kernel,
André Apitzsch
In-Reply-To: <20230407-atmel_keys-v2-0-92446a4343cb@apitzsch.eu>
Add support for touch keys found in some Atmel touch controller
configurations.
Reviewed-by: Claudiu Beznea <claudiu.beznea@microchip.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: André Apitzsch <git@apitzsch.eu>
---
drivers/input/touchscreen/atmel_mxt_ts.c | 85 ++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 996bf434e1cb..eb368dd1abf0 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -55,6 +55,7 @@
#define MXT_TOUCH_KEYARRAY_T15 15
#define MXT_TOUCH_PROXIMITY_T23 23
#define MXT_TOUCH_PROXKEY_T52 52
+#define MXT_TOUCH_PTC_KEYS_T97 97
#define MXT_PROCI_GRIPFACE_T20 20
#define MXT_PROCG_NOISE_T22 22
#define MXT_PROCI_ONETOUCH_T24 24
@@ -326,9 +327,13 @@ struct mxt_data {
u16 T71_address;
u8 T9_reportid_min;
u8 T9_reportid_max;
+ u8 T15_reportid_min;
+ u8 T15_reportid_max;
u16 T18_address;
u8 T19_reportid;
u16 T44_address;
+ u8 T97_reportid_min;
+ u8 T97_reportid_max;
u8 T100_reportid_min;
u8 T100_reportid_max;
@@ -344,6 +349,9 @@ struct mxt_data {
u32 *t19_keymap;
unsigned int t19_num_keys;
+ u32 *t15_keymap;
+ unsigned int t15_num_keys;
+
enum mxt_suspend_mode suspend_mode;
u32 wakeup_method;
@@ -375,6 +383,7 @@ static bool mxt_object_readable(unsigned int type)
case MXT_TOUCH_KEYARRAY_T15:
case MXT_TOUCH_PROXIMITY_T23:
case MXT_TOUCH_PROXKEY_T52:
+ case MXT_TOUCH_PTC_KEYS_T97:
case MXT_TOUCH_MULTITOUCHSCREEN_T100:
case MXT_PROCI_GRIPFACE_T20:
case MXT_PROCG_NOISE_T22:
@@ -891,6 +900,25 @@ static void mxt_proc_t9_message(struct mxt_data *data, u8 *message)
data->update_input = true;
}
+static void mxt_proc_t15_messages(struct mxt_data *data, u8 *message)
+{
+ struct input_dev *input_dev = data->input_dev;
+ unsigned long keystates = get_unaligned_le32(&message[2]);
+ int key;
+
+ for (key = 0; key < data->t15_num_keys; key++) {
+ input_report_key(input_dev, data->t15_keymap[key],
+ !!(keystates & BIT(key)));
+ }
+
+ data->update_input = true;
+}
+
+static void mxt_proc_t97_messages(struct mxt_data *data, u8 *message)
+{
+ mxt_proc_t15_messages(data, message);
+}
+
static void mxt_proc_t100_message(struct mxt_data *data, u8 *message)
{
struct device *dev = &data->client->dev;
@@ -1017,6 +1045,12 @@ static int mxt_proc_message(struct mxt_data *data, u8 *message)
} else if (report_id >= data->T9_reportid_min &&
report_id <= data->T9_reportid_max) {
mxt_proc_t9_message(data, message);
+ } else if (report_id >= data->T15_reportid_min &&
+ report_id <= data->T15_reportid_max) {
+ mxt_proc_t15_messages(data, message);
+ } else if (report_id >= data->T97_reportid_min &&
+ report_id <= data->T97_reportid_max) {
+ mxt_proc_t97_messages(data, message);
} else if (report_id >= data->T100_reportid_min &&
report_id <= data->T100_reportid_max) {
mxt_proc_t100_message(data, message);
@@ -1689,9 +1723,13 @@ static void mxt_free_object_table(struct mxt_data *data)
data->T71_address = 0;
data->T9_reportid_min = 0;
data->T9_reportid_max = 0;
+ data->T15_reportid_min = 0;
+ data->T15_reportid_max = 0;
data->T18_address = 0;
data->T19_reportid = 0;
data->T44_address = 0;
+ data->T97_reportid_min = 0;
+ data->T97_reportid_max = 0;
data->T100_reportid_min = 0;
data->T100_reportid_max = 0;
data->max_reportid = 0;
@@ -1764,6 +1802,10 @@ static int mxt_parse_object_table(struct mxt_data *data,
object->num_report_ids - 1;
data->num_touchids = object->num_report_ids;
break;
+ case MXT_TOUCH_KEYARRAY_T15:
+ data->T15_reportid_min = min_id;
+ data->T15_reportid_max = max_id;
+ break;
case MXT_SPT_COMMSCONFIG_T18:
data->T18_address = object->start_address;
break;
@@ -1773,6 +1815,10 @@ static int mxt_parse_object_table(struct mxt_data *data,
case MXT_SPT_GPIOPWM_T19:
data->T19_reportid = min_id;
break;
+ case MXT_TOUCH_PTC_KEYS_T97:
+ data->T97_reportid_min = min_id;
+ data->T97_reportid_max = max_id;
+ break;
case MXT_TOUCH_MULTITOUCHSCREEN_T100:
data->multitouch = MXT_TOUCH_MULTITOUCHSCREEN_T100;
data->T100_reportid_min = min_id;
@@ -2050,6 +2096,7 @@ static int mxt_initialize_input_device(struct mxt_data *data)
int error;
unsigned int num_mt_slots;
unsigned int mt_flags = 0;
+ int i;
switch (data->multitouch) {
case MXT_TOUCH_MULTI_T9:
@@ -2095,6 +2142,10 @@ static int mxt_initialize_input_device(struct mxt_data *data)
input_dev->open = mxt_input_open;
input_dev->close = mxt_input_close;
+ input_dev->keycode = data->t15_keymap;
+ input_dev->keycodemax = data->t15_num_keys;
+ input_dev->keycodesize = sizeof(data->t15_keymap[0]);
+
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
/* For single touch */
@@ -2162,6 +2213,12 @@ static int mxt_initialize_input_device(struct mxt_data *data)
0, 255, 0, 0);
}
+ /* For T15 and T97 Key Array */
+ if (data->T15_reportid_min || data->T97_reportid_min) {
+ for (i = 0; i < data->t15_num_keys; i++)
+ input_set_capability(input_dev, EV_KEY, data->t15_keymap[i]);
+ }
+
input_set_drvdata(input_dev, data);
error = input_register_device(input_dev);
@@ -3080,8 +3137,10 @@ static void mxt_input_close(struct input_dev *dev)
static int mxt_parse_device_properties(struct mxt_data *data)
{
static const char keymap_property[] = "linux,gpio-keymap";
+ static const char buttons_property[] = "linux,keycodes";
struct device *dev = &data->client->dev;
u32 *keymap;
+ u32 *buttonmap;
int n_keys;
int error;
@@ -3111,6 +3170,32 @@ static int mxt_parse_device_properties(struct mxt_data *data)
data->t19_num_keys = n_keys;
}
+ if (device_property_present(dev, buttons_property)) {
+ n_keys = device_property_count_u32(dev, buttons_property);
+ if (n_keys <= 0) {
+ error = n_keys < 0 ? n_keys : -EINVAL;
+ dev_err(dev, "invalid/malformed '%s' property: %d\n",
+ buttons_property, error);
+ return error;
+ }
+
+ buttonmap = devm_kmalloc_array(dev, n_keys, sizeof(*buttonmap),
+ GFP_KERNEL);
+ if (!buttonmap)
+ return -ENOMEM;
+
+ error = device_property_read_u32_array(dev, buttons_property,
+ buttonmap, n_keys);
+ if (error) {
+ dev_err(dev, "failed to parse '%s' property: %d\n",
+ buttons_property, error);
+ return error;
+ }
+
+ data->t15_keymap = buttonmap;
+ data->t15_num_keys = n_keys;
+ }
+
return 0;
}
--
2.40.0
^ permalink raw reply related
* Re: [PATCH 2/2] dt-bindings: input: cirrus,cs40l26: Add bindings
From: Krzysztof Kozlowski @ 2023-04-07 9:16 UTC (permalink / raw)
To: Fred Treven, ben.bright, james.ogletree, dmitry.torokhov, robh+dt,
krzystztof.kozlowski+dt, patches, linux-input, devicetree,
linux-kernel
In-Reply-To: <1680819613-29256-1-git-send-email-fred.treven@cirrus.com>
On 07/04/2023 00:20, Fred Treven wrote:
> Add devicetree bindings for CS40L26 driver.
I appreciate the try to write my name manually, but there is no need to
struggle. :) You will just make a mistake.
Just copy-paste or use scripts/get_maintainers.pl.
You can automate everything with something like:
https://github.com/krzk/tools/blob/master/linux/.bash_aliases_linux#L91
>
> Signed-off-by: Fred Treven <fred.treven@cirrus.com>
> ---
> .../devicetree/bindings/input/cs40l26.yaml | 92 ++++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 93 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/cs40l26.yaml
Filename matching compatible, so you need vendor-prefix.
>
> diff --git a/Documentation/devicetree/bindings/input/cs40l26.yaml b/Documentation/devicetree/bindings/input/cs40l26.yaml
> new file mode 100644
> index 000000000000..1036a374baa0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/cs40l26.yaml
> @@ -0,0 +1,92 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/cs40l26.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Cirrus Logic CS40L26 Boosted Haptic Amplifier
> +
> +maintainers:
> + - Fred Treven <fred.treven@cirrus.com>
> +
> +description:
> + CS40L26 is a Boosted Haptic Driver with Integrated DSP and Waveform Memory
> + with Advanced Closed Loop Algorithms and LRA protection
> +
> +properties:
> + compatible:
> + enum:
> + - cirrus,cs40l26a
> + - cirrus,cs40l26b
> + - cirrus,cs40l27a
> + - cirrus,cs40l27b
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> + description:
> + Property describing the interrupt line the devices /ALERT pin is connected to.
> + The device only has one interrupt source.
Drop description - it is almost useless. You could just mention ALERT pin.
> +
> + VA-supply:
> + description: Regulator for VA analog voltage
> +
> + VP-supply:
> + description: Regulator for VP peak voltage
> +
> + cirrus,bst-ipk-microamp:
> + description:
> + Maximum amount of current that can be drawn by the device's boost
> + converter in uA. Accepted values are between 1600000 uA and 4800000 uA in
> + 50000 uA increments.
> + minimum: 1600000
> + maximum: 4800000
> + default: 4500000
Isn't this property of regulator? Why do you need it here?
> +
> + cirrus,bst-ctl-microvolt:
> + description:
> + Maximum target voltage to which the class H algorithm may increase the
> + VBST supply, expressed in uV. Valid values range from 2550000 to 11000000
> + (inclusive) in steps of 50000. If this value is specified as zero or VP
> + rises above this value, VBST is bypassed to VP. If this value is omitted,
> + the maximum target voltage remains at 11 V.
Don't repeat constraints in free form text - drop last sentence.
> + minimum: 2550000
> + maximum: 11000000
> + default: 11000000
> +
> + cirrus,bst-exploratory-mode-disable:
> + description:
> + Disable boost exploratory mode if this boolean is present in the
> + devicetree.
Don't explain how DT works. Explain how hardware works instead.
> Boost exploratory mode allows the device to overshoot
> + the set peak current limit. This has potential to damage the boost
> + inductor. Disabling this mode will prevent this from happening; it will
> + also prevent the device from detecting boost inductor short errors.
> + (Default) Enabled
Why this is suitable for DT? Why would anyone need to disable it per board?
> + type: boolean
> +
> +
Just one blank line.
> +required:
> + - compatible
> + - reg
> + - interrupts
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + i2c {
> + cs40l26: cs40l26@58 {
Node names should be generic.
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
> + compatible = "cirrus,cs40l26a";
> + reg = <0x58>;
> + interrupt-parent = <&gpio0>;
> + interrupts = <57 8>;
If 8 is interrupt flag, use appropriate define.
> + reset-gpios = <&gpio0 54 0>;
Same for GPIO flag.
> + VA-supply = <&dummy_vreg>;
> + VP-supply = <&dummy_vreg>;
> + cirrus,bst-ctl-microvolt = <2600000>; // Max boost voltage = 2.6V
> + cirrus,bst-ipk-microamp = <1650000>; // Max boost current = 1.65A
> + cirrus,bst-exploratory-mode-disabled; // Disable exploratory mode
The comments are not useful - they copy the property. Instead you could
explain WHY. Or just drop the comments.
>
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH V3] dt-bindings: input: pwm-beeper: convert to dt schema
From: Peng Fan (OSS) @ 2023-04-07 7:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, s.hauer
Cc: linux-input, devicetree, linux-kernel, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
Convert the binding doc to dt schema, and also fixed the
example from fixed-regulator to regulator-fixed.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
V3:
Drop description for pwms
Simplify description for amp-supply
update beeper-hz range to 10-10000
V2:
License update
Don't need to show providers
Make example complete
Decrease beeper hz
Misc update
.../devicetree/bindings/input/pwm-beeper.txt | 24 -----------
.../devicetree/bindings/input/pwm-beeper.yaml | 41 +++++++++++++++++++
2 files changed, 41 insertions(+), 24 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/input/pwm-beeper.txt
create mode 100644 Documentation/devicetree/bindings/input/pwm-beeper.yaml
diff --git a/Documentation/devicetree/bindings/input/pwm-beeper.txt b/Documentation/devicetree/bindings/input/pwm-beeper.txt
deleted file mode 100644
index 8fc0e48c20db..000000000000
--- a/Documentation/devicetree/bindings/input/pwm-beeper.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-* PWM beeper device tree bindings
-
-Registers a PWM device as beeper.
-
-Required properties:
-- compatible: should be "pwm-beeper"
-- pwms: phandle to the physical PWM device
-
-Optional properties:
-- amp-supply: phandle to a regulator that acts as an amplifier for the beeper
-- beeper-hz: bell frequency in Hz
-
-Example:
-
-beeper_amp: amplifier {
- compatible = "fixed-regulator";
- gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
-};
-
-beeper {
- compatible = "pwm-beeper";
- pwms = <&pwm0>;
- amp-supply = <&beeper_amp>;
-};
diff --git a/Documentation/devicetree/bindings/input/pwm-beeper.yaml b/Documentation/devicetree/bindings/input/pwm-beeper.yaml
new file mode 100644
index 000000000000..a7611c206989
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/pwm-beeper.yaml
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/pwm-beeper.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: PWM beeper
+
+maintainers:
+ - Sascha Hauer <s.hauer@pengutronix.de>
+
+properties:
+ compatible:
+ const: pwm-beeper
+
+ pwms:
+ maxItems: 1
+
+ amp-supply:
+ description: an amplifier for the beeper
+
+ beeper-hz:
+ description: bell frequency in Hz
+ minimum: 10
+ maximum: 10000
+
+required:
+ - compatible
+ - pwms
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ beeper {
+ compatible = "pwm-beeper";
+ pwms = <&pwm0>;
+ amp-supply = <&beeper_amp>;
+ beeper-hz = <1000>;
+ };
--
2.37.1
^ permalink raw reply related
* [PATCH v2] Input: cma3000_d0x - Remove duplicate code
From: Jiapeng Chong @ 2023-04-07 2:13 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, linux-kernel, Jiapeng Chong, Abaci Robot
Function input_set_abs_params() has already set EV_ABS bit for us.
drivers/input/misc/cma3000_d0x.c:328 cma3000_init() warn: inconsistent indenting.
Reported-by: Abaci Robot <abaci@linux.alibaba.com>
Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=4588
Signed-off-by: Jiapeng Chong <jiapeng.chong@linux.alibaba.com>
---
Changes in v2:
-Remove __set_bit(EV_ABS, input_dev->evbit);
drivers/input/misc/cma3000_d0x.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/input/misc/cma3000_d0x.c b/drivers/input/misc/cma3000_d0x.c
index e6feb73bb52b..1772846708d2 100644
--- a/drivers/input/misc/cma3000_d0x.c
+++ b/drivers/input/misc/cma3000_d0x.c
@@ -325,8 +325,6 @@ struct cma3000_accl_data *cma3000_init(struct device *dev, int irq,
input_dev->open = cma3000_open;
input_dev->close = cma3000_close;
- __set_bit(EV_ABS, input_dev->evbit);
-
input_set_abs_params(input_dev, ABS_X,
-data->g_range, data->g_range, pdata->fuzz_x, 0);
input_set_abs_params(input_dev, ABS_Y,
--
2.20.1.7.g153144c
^ permalink raw reply related
* [PATCH 2/2] dt-bindings: input: cirrus,cs40l26: Add bindings
From: Fred Treven @ 2023-04-06 22:20 UTC (permalink / raw)
To: ben.bright, james.ogletree, dmitry.torokhov, robh+dt,
krzystztof.kozlowski+dt, patches, linux-input, devicetree,
linux-kernel
Cc: Fred Treven
In-Reply-To: <Add devicetree bindings for CS40L26 driver>
Add devicetree bindings for CS40L26 driver.
Signed-off-by: Fred Treven <fred.treven@cirrus.com>
---
.../devicetree/bindings/input/cs40l26.yaml | 92 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 93 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/cs40l26.yaml
diff --git a/Documentation/devicetree/bindings/input/cs40l26.yaml b/Documentation/devicetree/bindings/input/cs40l26.yaml
new file mode 100644
index 000000000000..1036a374baa0
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/cs40l26.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/cs40l26.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS40L26 Boosted Haptic Amplifier
+
+maintainers:
+ - Fred Treven <fred.treven@cirrus.com>
+
+description:
+ CS40L26 is a Boosted Haptic Driver with Integrated DSP and Waveform Memory
+ with Advanced Closed Loop Algorithms and LRA protection
+
+properties:
+ compatible:
+ enum:
+ - cirrus,cs40l26a
+ - cirrus,cs40l26b
+ - cirrus,cs40l27a
+ - cirrus,cs40l27b
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+ description:
+ Property describing the interrupt line the devices /ALERT pin is connected to.
+ The device only has one interrupt source.
+
+ VA-supply:
+ description: Regulator for VA analog voltage
+
+ VP-supply:
+ description: Regulator for VP peak voltage
+
+ cirrus,bst-ipk-microamp:
+ description:
+ Maximum amount of current that can be drawn by the device's boost
+ converter in uA. Accepted values are between 1600000 uA and 4800000 uA in
+ 50000 uA increments.
+ minimum: 1600000
+ maximum: 4800000
+ default: 4500000
+
+ cirrus,bst-ctl-microvolt:
+ description:
+ Maximum target voltage to which the class H algorithm may increase the
+ VBST supply, expressed in uV. Valid values range from 2550000 to 11000000
+ (inclusive) in steps of 50000. If this value is specified as zero or VP
+ rises above this value, VBST is bypassed to VP. If this value is omitted,
+ the maximum target voltage remains at 11 V.
+ minimum: 2550000
+ maximum: 11000000
+ default: 11000000
+
+ cirrus,bst-exploratory-mode-disable:
+ description:
+ Disable boost exploratory mode if this boolean is present in the
+ devicetree. Boost exploratory mode allows the device to overshoot
+ the set peak current limit. This has potential to damage the boost
+ inductor. Disabling this mode will prevent this from happening; it will
+ also prevent the device from detecting boost inductor short errors.
+ (Default) Enabled
+ type: boolean
+
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ cs40l26: cs40l26@58 {
+ compatible = "cirrus,cs40l26a";
+ reg = <0x58>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <57 8>;
+ reset-gpios = <&gpio0 54 0>;
+ VA-supply = <&dummy_vreg>;
+ VP-supply = <&dummy_vreg>;
+ cirrus,bst-ctl-microvolt = <2600000>; // Max boost voltage = 2.6V
+ cirrus,bst-ipk-microamp = <1650000>; // Max boost current = 1.65A
+ cirrus,bst-exploratory-mode-disabled; // Disable exploratory mode
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 7051386d5a13..3cd9f0c8e38d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4934,6 +4934,7 @@ L: patches@opensource.cirrus.com
S: Supported
W: https://github.com/CirrusLogic/linux-drivers/wiki
T: git https://github.com/CirrusLogic/linux-drivers.git
+F: Documentation/devicetree/bindings/input/cs40l26.yaml
F: drivers/input/misc/cs40l*
F: include/linux/input/cs40l*
--
2.7.4
^ permalink raw reply related
* [PATCH 1/2] Input: cs40l26: Support for CS40L26 Boosted Haptic Amplifier
From: Fred Treven @ 2023-04-06 22:16 UTC (permalink / raw)
To: dmitry.torokhov, ben.bright, james.ogletree, lee, jdelvare, joel,
cy_huang, rdunlap, eajames, jeff, ping.bai, msp, arnd,
bartosz.golaszewski, linux-kernel, linux-input, patches
Cc: Fred Treven
In-Reply-To: <Support for CS40L26 Boosted Haptic Amplifier>
Introduce support for Cirrus Logic device CS40L26, a boosted
haptics driver with integrated DSP and waveform memory with
advanced closed loop algorithms and LRA protection.
Signed-off-by: Fred Treven <fred.treven@cirrus.com>
---
MAINTAINERS | 11 +
drivers/input/misc/Kconfig | 30 +
drivers/input/misc/Makefile | 4 +
drivers/input/misc/cs40l26-i2c.c | 81 ++
drivers/input/misc/cs40l26-spi.c | 81 ++
drivers/input/misc/cs40l26-tables.c | 170 +++
drivers/input/misc/cs40l26.c | 2508 +++++++++++++++++++++++++++++++++++
include/linux/input/cs40l26.h | 532 ++++++++
8 files changed, 3417 insertions(+)
create mode 100644 drivers/input/misc/cs40l26-i2c.c
create mode 100644 drivers/input/misc/cs40l26-spi.c
create mode 100644 drivers/input/misc/cs40l26-tables.c
create mode 100644 drivers/input/misc/cs40l26.c
create mode 100644 include/linux/input/cs40l26.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2b073facf399..7051386d5a13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4926,6 +4926,17 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/cirrus/ep93xx_eth.c
+CIRRUS LOGIC HAPTICS DRIVER
+M: Fred Treven <fred.treven@cirrus.com>
+M: Ben Bright <ben.bright@cirrus.com>
+M: James Ogletree <james.ogletree@cirrus.com>
+L: patches@opensource.cirrus.com
+S: Supported
+W: https://github.com/CirrusLogic/linux-drivers/wiki
+T: git https://github.com/CirrusLogic/linux-drivers.git
+F: drivers/input/misc/cs40l*
+F: include/linux/input/cs40l*
+
CIRRUS LOGIC LOCHNAGAR DRIVER
M: Charles Keepax <ckeepax@opensource.cirrus.com>
M: Richard Fitzgerald <rf@opensource.cirrus.com>
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 81a54a59e13c..418e70724d26 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -140,6 +140,36 @@ config INPUT_BMA150
To compile this driver as a module, choose M here: the
module will be called bma150.
+config INPUT_CS40L26
+ tristate "Cirrus Logic CS40L26 Haptic Amplifier support"
+ select CS_DSP
+ help
+ Say Y here to enable support for CS40L26 boosted haptic
+ amplifier.
+
+ To compile the driver as a module choose M here: the module
+ will be called cs40l26_core.
+
+config INPUT_CS40L26_I2C
+ tristate "Support I2C bus connection"
+ depends on INPUT_CS40L26 && I2C
+ select REGMAP_I2C
+ help
+ Say Y if you have CS40L26 hooked to an I2C bus.
+
+ To compile the driver as a module choose M here: the
+ module will be called cs40l26_i2c.
+
+config INPUT_CS40L26_SPI
+ tristate "Support SPI bus connection"
+ depends on INPUT_CS40L26 && SPI
+ select REGMAP_SPI
+ help
+ Say Y if you have CS40L26 hooked to a SPI bus.
+
+ To compile the driver as a module choose M here: the
+ module will be called cs40l26_spi.
+
config INPUT_E3X0_BUTTON
tristate "NI Ettus Research USRP E3xx Button support."
default n
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 04296a4abe8e..631ef6ebb061 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -28,6 +28,10 @@ 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
+cs40l26-core-objs := cs40l26.o cs40l26-tables.o
+obj-$(CONFIG_INPUT_CS40L26) += cs40l26-core.o
+obj-$(CONFIG_INPUT_CS40L26_I2C) += cs40l26-i2c.o
+obj-$(CONFIG_INPUT_CS40L26_SPI) += cs40l26-spi.o
obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
diff --git a/drivers/input/misc/cs40l26-i2c.c b/drivers/input/misc/cs40l26-i2c.c
new file mode 100644
index 000000000000..7d8e7a381681
--- /dev/null
+++ b/drivers/input/misc/cs40l26-i2c.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/i2c.h>
+
+#include <linux/input/cs40l26.h>
+
+static const struct i2c_device_id cs40l26_id_i2c[] = {
+ {"cs40l26a", 0},
+ {"cs40l26b", 1},
+ {"cs40l27a", 2},
+ {"cs40l27b", 3},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs40l26_id_i2c);
+
+static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { .compatible = "cirrus,cs40l27a" },
+ { .compatible = "cirrus,cs40l27b" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
+
+static int cs40l26_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cs40l26_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct device *dev = &client->dev;
+ struct cs40l26_private *cs40l26;
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_i2c(client, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap))
+ return dev_err_probe(dev, PTR_ERR(cs40l26->regmap),
+ "Failed to allocate register map\n");
+
+ cs40l26->dev = dev;
+ cs40l26->irq = client->irq;
+
+ dev_set_drvdata(dev, cs40l26);
+
+ return cs40l26_probe(cs40l26, pdata);
+}
+
+static void cs40l26_i2c_remove(struct i2c_client *client)
+{
+ struct cs40l26_private *cs40l26 = i2c_get_clientdata(client);
+
+ cs40l26_remove(cs40l26);
+}
+
+static struct i2c_driver cs40l26_i2c_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = &cs40l26_pm_ops,
+ },
+ .id_table = cs40l26_id_i2c,
+ .probe = cs40l26_i2c_probe,
+ .remove = cs40l26_i2c_remove,
+};
+
+module_i2c_driver(cs40l26_i2c_driver);
+
+MODULE_DESCRIPTION("CS40L26 I2C Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cs40l26-spi.c b/drivers/input/misc/cs40l26-spi.c
new file mode 100644
index 000000000000..b53c62eec23f
--- /dev/null
+++ b/drivers/input/misc/cs40l26-spi.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/spi/spi.h>
+
+#include <linux/input/cs40l26.h>
+
+static const struct spi_device_id cs40l26_id_spi[] = {
+ {"cs40l26a", 0},
+ {"cs40l26b", 1},
+ {"cs40l27a", 2},
+ {"cs40l27b", 3},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cs40l26_id_spi);
+
+static const struct of_device_id cs40l26_of_match[CS40L26_NUM_DEVS + 1] = {
+ { .compatible = "cirrus,cs40l26a" },
+ { .compatible = "cirrus,cs40l26b" },
+ { .compatible = "cirrus,cs40l27a" },
+ { .compatible = "cirrus,cs40l27b" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l26_of_match);
+
+static int cs40l26_spi_probe(struct spi_device *spi)
+{
+ struct cs40l26_platform_data *pdata = dev_get_platdata(&spi->dev);
+ struct device *dev = &spi->dev;
+ struct cs40l26_private *cs40l26;
+
+ cs40l26 = devm_kzalloc(dev, sizeof(struct cs40l26_private), GFP_KERNEL);
+ if (!cs40l26)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs40l26);
+
+ cs40l26->regmap = devm_regmap_init_spi(spi, &cs40l26_regmap);
+ if (IS_ERR(cs40l26->regmap))
+ return dev_err_probe(dev, PTR_ERR(cs40l26->regmap),
+ "Failed to allocate register map\n");
+
+ cs40l26->dev = dev;
+ cs40l26->irq = spi->irq;
+
+ dev_set_drvdata(dev, cs40l26);
+
+ return cs40l26_probe(cs40l26, pdata);
+}
+
+static void cs40l26_spi_remove(struct spi_device *spi)
+{
+ struct cs40l26_private *cs40l26 = spi_get_drvdata(spi);
+
+ cs40l26_remove(cs40l26);
+}
+
+static struct spi_driver cs40l26_spi_driver = {
+ .driver = {
+ .name = "cs40l26",
+ .of_match_table = cs40l26_of_match,
+ .pm = &cs40l26_pm_ops,
+ },
+
+ .id_table = cs40l26_id_spi,
+ .probe = cs40l26_spi_probe,
+ .remove = cs40l26_spi_remove,
+};
+
+module_spi_driver(cs40l26_spi_driver);
+
+MODULE_DESCRIPTION("CS40L26 SPI Driver");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/cs40l26-tables.c b/drivers/input/misc/cs40l26-tables.c
new file mode 100644
index 000000000000..4a81eceb724c
--- /dev/null
+++ b/drivers/input/misc/cs40l26-tables.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/regulator/consumer.h>
+
+#include <linux/input/cs40l26.h>
+
+/* LUT for converting gain percentage to attenuation in dB */
+const u32 cs40l26_atten_lut_q21_2[CS40L26_NUM_ATTEN_LUT_VALUES] = {
+ 400, /* MUTE */
+ 160, /* 1% */
+ 136,
+ 122,
+ 112,
+ 104,
+ 98,
+ 92,
+ 88,
+ 84,
+ 80,
+ 77,
+ 74,
+ 71,
+ 68,
+ 66,
+ 64,
+ 62,
+ 60,
+ 58,
+ 56,
+ 54,
+ 53,
+ 51,
+ 50,
+ 48, /* 25% */
+ 47,
+ 45,
+ 44,
+ 43,
+ 42,
+ 41,
+ 40,
+ 39,
+ 37,
+ 36,
+ 35,
+ 35,
+ 34,
+ 33,
+ 32,
+ 31,
+ 30,
+ 29,
+ 29,
+ 28,
+ 27,
+ 26,
+ 26,
+ 25,
+ 24, /* 50 % */
+ 23,
+ 23,
+ 22,
+ 21,
+ 21,
+ 20,
+ 20,
+ 19,
+ 18,
+ 18,
+ 17,
+ 17,
+ 16,
+ 16,
+ 15,
+ 14,
+ 14,
+ 13,
+ 13,
+ 12,
+ 12,
+ 11,
+ 11,
+ 10,
+ 10, /* 75% */
+ 10,
+ 9,
+ 9,
+ 8,
+ 8,
+ 7,
+ 7,
+ 6,
+ 6,
+ 6,
+ 5,
+ 5,
+ 4,
+ 4,
+ 4,
+ 3,
+ 3,
+ 3,
+ 2,
+ 2,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0, /* 100% */
+};
+
+static bool cs40l26_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS40L26_DEVID ... CS40L26_REVID:
+ case CS40L26_TEST_KEY_CTRL:
+ case CS40L26_GLOBAL_ENABLES:
+ case CS40L26_ERROR_RELEASE:
+ case CS40L26_PWRMGT_CTL ... CS40L26_PWRMGT_STS:
+ case CS40L26_REFCLK_INPUT:
+ case CS40L26_PLL_REFCLK_DETECT_0:
+ case CS40L26_VBST_CTL_1 ... CS40L26_BST_IPK_CTL:
+ case CS40L26_TEST_LBST:
+ case CS40L26_NGATE1_INPUT:
+ case CS40L26_DAC_MSM_CONFIG ... CS40L26_TST_DAC_MSM_CONFIG:
+ case CS40L26_IRQ1_STATUS:
+ case CS40L26_IRQ1_EINT_1 ... CS40L26_IRQ1_EINT_5:
+ case CS40L26_IRQ1_STS_1 ... CS40L26_IRQ1_STS_5:
+ case CS40L26_IRQ1_MASK_1 ... CS40L26_IRQ1_MASK_5:
+ case CS40L26_MIXER_NGATE_CH1_CFG:
+ case CS40L26_DSP_MBOX_1 ... CS40L26_DSP_VIRTUAL2_MBOX_8:
+ case CS40L26_OTP_MEM0 ... CS40L26_OTP_MEM31:
+ case CS40L26_DSP1_XMEM_PACKED_0 ... CS40L26_DSP1_XMEM_PACKED_6143:
+ case CS40L26_DSP1_XROM_PACKED_0 ... CS40L26_DSP1_XROM_PACKED_4604:
+ case CS40L26_DSP1_XMEM_UNPACKED32_0 ... CS40L26_DSP1_XROM_UNPACKED32_3070:
+ case CS40L26_DSP1_SYS_INFO_ID:
+ case CS40L26_DSP1_XMEM_UNPACKED24_0 ... CS40L26_DSP1_XMEM_UNPACKED24_8191:
+ case CS40L26_DSP1_XROM_UNPACKED24_0 ... CS40L26_DSP1_XROM_UNPACKED24_6141:
+ case CS40L26_DSP1_CCM_CORE_CONTROL:
+ case CS40L26_DSP1_YMEM_PACKED_0 ... CS40L26_DSP1_YMEM_PACKED_1532:
+ case CS40L26_DSP1_YMEM_UNPACKED32_0 ... CS40L26_DSP1_YMEM_UNPACKED32_1022:
+ case CS40L26_DSP1_YMEM_UNPACKED24_0 ... CS40L26_DSP1_YMEM_UNPACKED24_2045:
+ case CS40L26_DSP1_PMEM_0 ... CS40L26_DSP1_PMEM_5114:
+ case CS40L26_DSP1_PROM_0 ... CS40L26_DSP1_PROM_30714:
+ return true;
+ default:
+ return false;
+ }
+}
+
+const struct regmap_config cs40l26_regmap = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .max_register = CS40L26_LASTREG,
+ .num_reg_defaults = 0,
+ .readable_reg = cs40l26_readable_reg,
+ .cache_type = REGCACHE_NONE,
+};
+EXPORT_SYMBOL_GPL(cs40l26_regmap);
diff --git a/drivers/input/misc/cs40l26.c b/drivers/input/misc/cs40l26.c
new file mode 100644
index 000000000000..cfe283840ac7
--- /dev/null
+++ b/drivers/input/misc/cs40l26.c
@@ -0,0 +1,2508 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#include <linux/firmware.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/pm_runtime.h>
+#include <linux/string.h>
+
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/input/cs40l26.h>
+
+static const struct cs_dsp_region cs40l26_dsp_regions[] = {
+ { .type = WMFW_HALO_PM_PACKED, .base = CS40L26_DSP1_PMEM_0 },
+ { .type = WMFW_HALO_XM_PACKED, .base = CS40L26_DSP1_XMEM_PACKED_0 },
+ { .type = WMFW_HALO_YM_PACKED, .base = CS40L26_DSP1_YMEM_PACKED_0 },
+ { .type = WMFW_ADSP2_XM, .base = CS40L26_DSP1_XMEM_UNPACKED24_0 },
+ { .type = WMFW_ADSP2_YM, .base = CS40L26_DSP1_YMEM_UNPACKED24_0 },
+};
+
+static int cs40l26_mailbox_write(struct cs40l26_private *cs40l26, u32 write_val)
+{
+ int i, ret;
+ u32 ack;
+
+ /*
+ * Don't use regmap_read_poll_timeout here since the initial transaction could fail
+ * with -EIO if the device is hibernating.
+ */
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1, write_val);
+ if (ret)
+ dev_dbg(cs40l26->dev, "Failed to write to mailbox: %d on attempt no. %d\n",
+ ret, i + 1);
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1, &ack);
+ if (!ack && !ret)
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX);
+ }
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ ret = -ETIMEDOUT;
+ dev_err(cs40l26->dev, "Mailbox ack. failed with value 0x%X", ack);
+ }
+
+ return ret;
+}
+
+static int cs40l26_fw_ctl_write_raw(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, unsigned int off_words, size_t len_bytes, u32 *buf)
+{
+ size_t len_words = len_bytes / sizeof(__be32);
+ struct cs_dsp_coeff_ctl *ctl;
+ __be32 *val;
+ int i, ret;
+
+ ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id);
+ if (IS_ERR_OR_NULL(ctl)) {
+ dev_err(dsp->dev, "Failed to find fw ctl %s\n", name);
+ return -ENOENT;
+ }
+
+ val = kzalloc(len_bytes, GFP_KERNEL);
+ if (!val)
+ return -ENOMEM;
+
+ for (i = 0; i < len_words; i++)
+ val[i] = cpu_to_be32(buf[i]);
+
+ ret = cs_dsp_coeff_write_ctrl(ctl, off_words, val, len_bytes);
+ if (ret)
+ dev_err(dsp->dev, "Failed to write fw ctl %s: %d\n", name, ret);
+
+ kfree(val);
+
+ return ret;
+}
+
+static inline int cs40l26_fw_ctl_write(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, u32 val)
+{
+ return cs40l26_fw_ctl_write_raw(dsp, name, algo_id, 0, sizeof(u32), &val);
+}
+
+static int cs40l26_fw_ctl_read_raw(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, unsigned int off_words, size_t len_bytes, u32 *buf)
+{
+ size_t len_words = len_bytes / sizeof(u32);
+ struct cs_dsp_coeff_ctl *ctl;
+ int i, ret;
+
+ ctl = cs_dsp_get_ctl(dsp, name, WMFW_ADSP2_XM, algo_id);
+ if (IS_ERR_OR_NULL(ctl)) {
+ dev_err(dsp->dev, "Failed to find fw ctl %s\n", name);
+ return -ENOENT;
+ }
+
+ ret = cs_dsp_coeff_read_ctrl(ctl, off_words, buf, len_bytes);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to read fw ctl %s: %d\n", name, ret);
+ return ret;
+ }
+
+ for (i = 0; i < len_words; i++)
+ buf[i] = be32_to_cpu(buf[i]);
+
+ return 0;
+}
+
+static inline int cs40l26_fw_ctl_read(struct cs_dsp *dsp, const char * const name,
+ unsigned int algo_id, u32 *buf)
+{
+ return cs40l26_fw_ctl_read_raw(dsp, name, algo_id, 0, sizeof(u32), buf);
+}
+
+static const struct cs40l26_pseq_params cs40l26_pseq_params_write_full = {
+ .num_op_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS,
+ .op_mask = CS40L26_PSEQ_WRITE_FULL_OP_MASK,
+ .low_addr_shift = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT,
+ .low_addr_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK,
+ .low_data_mask = CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK,
+ .up_addr_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT,
+ .up_addr_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK,
+ .up_data_mask = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK,
+ .up_data_shift = CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT,
+};
+
+static const struct cs40l26_pseq_params cs40l26_pseq_params_write_x16 = {
+ .num_op_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS,
+ .op_mask = CS40L26_PSEQ_WRITE_X16_OP_MASK,
+ .low_addr_shift = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT,
+ .low_addr_mask = CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK,
+ .low_data_mask = 0,
+ .up_addr_shift = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT,
+ .up_addr_mask = CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK,
+ .up_data_mask = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK,
+ .up_data_shift = CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT,
+};
+
+static int cs40l26_pseq_find_end(struct cs40l26_private *cs40l26, struct cs40l26_pseq_op **op_end)
+{
+ struct cs40l26_pseq_op *op;
+
+ list_for_each_entry(op, &cs40l26->pseq_op_head, list) {
+ if (op->operation == CS40L26_PSEQ_OP_END)
+ break;
+ }
+
+ if (op->operation != CS40L26_PSEQ_OP_END) {
+ dev_err(cs40l26->dev, "Failed to find PSEQ list terminator\n");
+ return -ENOENT;
+ }
+
+ *op_end = op;
+
+ return 0;
+}
+
+static int cs40l26_pseq_write(struct cs40l26_private *cs40l26, u32 addr, u32 data, bool update,
+ u8 op_code)
+{
+ struct device *dev = cs40l26->dev;
+ bool is_new = true;
+ struct cs40l26_pseq_op *op, *op_new, *op_end;
+ const struct cs40l26_pseq_params *params;
+ int ret;
+
+ switch (op_code) {
+ case CS40L26_PSEQ_OP_WRITE_FULL:
+ /*
+ * The DSP sign-extends bit 23 to bits[31:24].
+ * Warn if POWER_ON_SEQUENCE will not function as expected.
+ */
+ if ((data & BIT(23)) && (((data & GENMASK(31, 24)) >> 24) != 0xFF))
+ dev_warn(dev, "PSEQ will sign extend: %08X @ %08X", data, addr);
+
+ params = &cs40l26_pseq_params_write_full;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_L16:
+ case CS40L26_PSEQ_OP_WRITE_H16:
+ if (addr & CS40L26_PSEQ_INVALID_ADDR) {
+ dev_err(dev, "Invalid PSEQ address: 0x%08X\n", addr);
+ return -EINVAL;
+ }
+
+ params = &cs40l26_pseq_params_write_x16;
+ break;
+ default:
+ dev_err(dev, "Invalid PSEQ OP code: 0x%02X\n", op_code);
+ return -EINVAL;
+ }
+
+ op_new = devm_kzalloc(dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL);
+ if (!op_new)
+ return -ENOMEM;
+
+ op_new->size = params->num_op_words;
+ op_new->operation = op_code;
+ op_new->words[0] = (op_code << CS40L26_PSEQ_OP_SHIFT);
+ op_new->words[0] |= (addr & params->up_addr_mask) >> params->up_addr_shift;
+ op_new->words[1] = (addr & params->low_addr_mask) << params->low_addr_shift;
+ op_new->words[1] |= (data & params->up_data_mask) >> params->up_data_shift;
+ if (op_code == CS40L26_PSEQ_OP_WRITE_FULL)
+ op_new->words[2] = data & params->low_data_mask;
+
+ list_for_each_entry(op, &cs40l26->pseq_op_head, list) {
+ if (op->words[0] == op_new->words[0] && (op->words[1] & params->op_mask) ==
+ (op_new->words[1] & params->op_mask) && update) {
+ if (op->size != params->num_op_words) {
+ dev_err(dev, "Failed to replace PSEQ op.\n");
+ ret = -EINVAL;
+ goto op_new_free;
+ }
+ is_new = false;
+ break;
+ }
+ }
+
+ ret = cs40l26_pseq_find_end(cs40l26, &op_end);
+ if (ret)
+ goto op_new_free;
+
+ if (((CS40L26_PSEQ_MAX_WORDS * sizeof(u32)) - op_end->offset) <
+ (op_new->size * sizeof(u32))) {
+ dev_err(dev, "Not enough space in pseq to add op\n");
+ ret = -ENOMEM;
+ goto op_new_free;
+ }
+
+ if (is_new) {
+ op_new->offset = op_end->offset;
+ op_end->offset += (op_new->size * sizeof(u32));
+ } else {
+ op_new->offset = op->offset;
+ }
+
+ ret = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE", CS40L26_PM_ALGO_ID,
+ op_new->offset / sizeof(u32), op_new->size * sizeof(u32), op_new->words);
+ if (ret)
+ goto op_new_free;
+
+ if (is_new) {
+ ret = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE",
+ CS40L26_PM_ALGO_ID, op_end->offset / sizeof(u32),
+ op_end->size * sizeof(u32), op_end->words);
+ if (ret)
+ goto op_new_free;
+
+ list_add(&op_new->list, &cs40l26->pseq_op_head);
+ cs40l26->pseq_num_ops++;
+ } else {
+ list_replace(&op->list, &op_new->list);
+ }
+
+ return 0;
+
+op_new_free:
+ devm_kfree(dev, op_new);
+
+ return ret;
+}
+
+static int cs40l26_pseq_multi_write(struct cs40l26_private *cs40l26,
+ const struct reg_sequence *reg_seq, int num_regs, bool update, u8 op_code)
+{
+ int ret, i;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = cs40l26_pseq_write(cs40l26, reg_seq[i].reg, reg_seq[i].def, update, op_code);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int cs40l26_update_reg_defaults_via_pseq(struct cs40l26_private *cs40l26)
+{
+ int ret;
+
+ ret = cs40l26_pseq_write(cs40l26, CS40L26_NGATE1_INPUT, CS40L26_DATA_SRC_DSP1TX4, true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_pseq_write(cs40l26, CS40L26_MIXER_NGATE_CH1_CFG,
+ CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT, true, CS40L26_PSEQ_OP_WRITE_FULL);
+ if (ret)
+ return ret;
+
+ return cs40l26_pseq_write(cs40l26, CS40L26_TST_DAC_MSM_CONFIG,
+ CS40L26_TST_DAC_MSM_CFG_DFLT_CHG_VAL_H16, true, CS40L26_PSEQ_OP_WRITE_H16);
+}
+
+static int cs40l26_pseq_init(struct cs40l26_private *cs40l26)
+{
+ struct cs40l26_pseq_op *pseq_op;
+ int ret, i, num_words;
+ u8 operation;
+ u32 *words;
+
+ INIT_LIST_HEAD(&cs40l26->pseq_op_head);
+ cs40l26->pseq_num_ops = 0;
+
+ words = kcalloc(CS40L26_PSEQ_MAX_WORDS, sizeof(u32), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(words))
+ return -ENOMEM;
+
+ ret = cs40l26_fw_ctl_read_raw(&cs40l26->dsp, "POWER_ON_SEQUENCE", CS40L26_PM_ALGO_ID, 0,
+ CS40L26_PSEQ_MAX_WORDS * sizeof(u32), words);
+ if (ret)
+ goto err_free;
+
+ for (i = 0; i < CS40L26_PSEQ_MAX_WORDS; i += num_words) {
+ operation = (words[i] & CS40L26_PSEQ_OP_MASK) >> CS40L26_PSEQ_OP_SHIFT;
+
+ switch (operation) {
+ case CS40L26_PSEQ_OP_END:
+ num_words = CS40L26_PSEQ_OP_END_WORDS;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_ADDR8:
+ case CS40L26_PSEQ_OP_WRITE_H16:
+ case CS40L26_PSEQ_OP_WRITE_L16:
+ num_words = CS40L26_PSEQ_OP_WRITE_X16_WORDS;
+ break;
+ case CS40L26_PSEQ_OP_WRITE_FULL:
+ num_words = CS40L26_PSEQ_OP_WRITE_FULL_WORDS;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid OP code 0x%02X\n", operation);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ pseq_op = devm_kzalloc(cs40l26->dev, sizeof(struct cs40l26_pseq_op), GFP_KERNEL);
+ if (!pseq_op) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ memcpy(pseq_op->words, &words[i], num_words * sizeof(u32));
+ pseq_op->size = num_words;
+ pseq_op->offset = i * sizeof(u32);
+ pseq_op->operation = operation;
+ list_add(&pseq_op->list, &cs40l26->pseq_op_head);
+
+ cs40l26->pseq_num_ops++;
+
+ if (operation == CS40L26_PSEQ_OP_END)
+ break;
+ }
+
+ if (operation != CS40L26_PSEQ_OP_END) {
+ dev_err(cs40l26->dev, "PSEQ_END_OF_SCRIPT not found\n");
+ ret = -ENOENT;
+ goto err_free;
+ }
+
+ ret = cs40l26_update_reg_defaults_via_pseq(cs40l26);
+
+err_free:
+ kfree(words);
+
+ return ret;
+}
+
+static inline void cs40l26_pm_exit(struct device *dev)
+{
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+}
+
+static inline int cs40l26_pm_enter(struct device *dev)
+{
+ return pm_runtime_resume_and_get(dev);
+}
+
+static inline int cs40l26_pm_timeout_ms_set(struct cs40l26_private *cs40l26,
+ unsigned int dsp_state, u32 timeout_ms)
+{
+ return regmap_write(cs40l26->regmap,
+ (dsp_state == CS40L26_DSP_STATE_STANDBY) ?
+ CS40L26_A1_PM_STDBY_TICKS_STATIC_REG :
+ CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG,
+ timeout_ms * CS40L26_PM_TICKS_PER_MS);
+}
+
+static inline int cs40l26_pm_timeout_ms_get(struct cs40l26_private *cs40l26,
+ unsigned int dsp_state, u32 *timeout_ms)
+{
+ u32 timeout_ticks;
+ int ret;
+
+ ret = regmap_read(cs40l26->regmap, (dsp_state == CS40L26_DSP_STATE_STANDBY) ?
+ CS40L26_A1_PM_STDBY_TICKS_STATIC_REG :
+ CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG, &timeout_ticks);
+ if (ret)
+ return ret;
+
+ *timeout_ms = timeout_ticks / CS40L26_PM_TICKS_PER_MS;
+
+ return 0;
+}
+
+static inline void cs40l26_pm_runtime_setup(struct cs40l26_private *cs40l26)
+{
+ pm_runtime_mark_last_busy(cs40l26->dev);
+ pm_runtime_use_autosuspend(cs40l26->dev);
+ pm_runtime_set_autosuspend_delay(cs40l26->dev, CS40L26_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_enable(cs40l26->dev);
+}
+
+static inline void cs40l26_pm_runtime_teardown(struct cs40l26_private *cs40l26)
+{
+ pm_runtime_disable(cs40l26->dev);
+ pm_runtime_dont_use_autosuspend(cs40l26->dev);
+}
+
+static int cs40l26_irq_update_mask(struct cs40l26_private *cs40l26, u32 reg, u32 val, u32 bit_mask)
+{
+ u32 eint_reg, cur_mask, new_mask;
+ int ret;
+
+ if (reg == CS40L26_IRQ1_MASK_1) {
+ eint_reg = CS40L26_IRQ1_EINT_1;
+ } else if (reg == CS40L26_IRQ1_MASK_2) {
+ eint_reg = CS40L26_IRQ1_EINT_2;
+ } else {
+ dev_err(cs40l26->dev, "Invalid IRQ mask reg: 0x%08X\n", reg);
+ return -EINVAL;
+ }
+
+ ret = regmap_read(cs40l26->regmap, reg, &cur_mask);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get IRQ mask\n");
+ return ret;
+ }
+
+ new_mask = (cur_mask & ~bit_mask) | val;
+
+ ret = regmap_write(cs40l26->regmap, eint_reg, bit_mask);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to clear IRQ\n");
+ return ret;
+ }
+
+ ret = regmap_write(cs40l26->regmap, reg, new_mask);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update IRQ mask\n");
+ return ret;
+ }
+
+ if (bit_mask & GENMASK(31, 16)) {
+ ret = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(31, 16)) >> 16,
+ true, CS40L26_PSEQ_OP_WRITE_H16);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update IRQ mask H16");
+ return ret;
+ }
+ }
+
+ if (bit_mask & GENMASK(15, 0)) {
+ ret = cs40l26_pseq_write(cs40l26, reg, (new_mask & GENMASK(15, 0)), true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update IRQ mask L16");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static inline int cs40l26_gpio_timeout_ms_set(struct cs40l26_private *cs40l26, u32 timeout_ms)
+{
+ return cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_GPI_MS", CS40L26_VIBEGEN_ALGO_ID,
+ timeout_ms);
+}
+
+static int cs40l26_erase_gpio_mapping(struct cs40l26_private *cs40l26,
+ enum cs40l26_gpio_map mapping)
+{
+ u32 disable = CS40L26_EVENT_MAP_GPI_DISABLE;
+ u32 offset;
+ int ret;
+
+ switch (mapping) {
+ case CS40L26_GPIO_MAP_A_PRESS:
+ offset = 0;
+ break;
+ case CS40L26_GPIO_MAP_A_RELEASE:
+ offset = 1;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid GPIO mapping: %u\n", mapping);
+ return -EINVAL;
+ }
+
+ if (cs40l26->dsp.running) {
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, offset, sizeof(u32), &disable);
+ if (ret) {
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ return ret;
+ }
+
+ ret = cs40l26_gpio_timeout_ms_set(cs40l26, 0);
+
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ } else {
+ ret = regmap_write(cs40l26->regmap, CS40L26_A1_EVENT_MAP_1, disable);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to erase GPIO mapping: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static int cs40l26_gpio_config(struct cs40l26_private *cs40l26)
+{
+ u32 defaults[2], mask, val;
+ int ret;
+
+ defaults[0] = cs40l26->gpio_release_default;
+ defaults[1] = cs40l26->gpio_press_default;
+
+ ret = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, 0, 2 * sizeof(u32), defaults);
+ if (ret)
+ return ret;
+
+ switch (cs40l26->devid) {
+ case CS40L26_DEVID_A:
+ case CS40L26_DEVID_L27_A:
+ val = (u32) GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO2_RISE);
+ break;
+ default:
+ val = 0;
+ }
+
+ mask = (u32) GENMASK(CS40L26_IRQ1_GPIO4_FALL, CS40L26_IRQ1_GPIO1_RISE);
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask);
+}
+
+static int cs40l26_map_effect_to_gpio(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ u16 button = effect->trigger.button;
+ u8 edge, ev_handler_bank_ram, gpio;
+ unsigned int offset;
+ u32 write_val;
+ int ret;
+
+ gpio = (button & CS40L26_BTN_NUM_MASK) >> CS40L26_BTN_NUM_SHIFT;
+ edge = (button & CS40L26_BTN_EDGE_MASK) >> CS40L26_BTN_EDGE_SHIFT;
+
+ if (gpio != 1) {
+ dev_err(cs40l26->dev, "GPIO%u not supported on 0x%02X\n", gpio, cs40l26->revid);
+ return -EINVAL;
+ }
+
+ ev_handler_bank_ram = (ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_RAM ||
+ ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_BUZ) ? 1 : 0;
+
+ offset = edge ? 0 : 1;
+
+ write_val = (ueffect->trigger_index & CS40L26_BTN_INDEX_MASK) |
+ (ev_handler_bank_ram << CS40L26_BTN_BANK_SHIFT);
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_write_raw(&cs40l26->dsp, "ENT_MAP_TABLE_EVENT_DATA_PACKED",
+ CS40L26_EVENT_HANDLER_ALGO_ID, offset, sizeof(u32), &write_val);
+ if (ret)
+ goto err_pwr_lock;
+
+ ret = cs40l26_gpio_timeout_ms_set(cs40l26, effect->replay.length);
+
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (ret)
+ return ret;
+
+ ueffect->mapping = edge ? CS40L26_GPIO_MAP_A_PRESS : CS40L26_GPIO_MAP_A_RELEASE;
+
+ return 0;
+}
+
+static inline int cs40l26_num_waves_get(struct cs40l26_private *cs40l26,
+ u32 *num_waves)
+{
+ return cs40l26_fw_ctl_read(&cs40l26->dsp, "NUM_OF_WAVES", CS40L26_VIBEGEN_ALGO_ID,
+ num_waves);
+}
+
+static struct cs40l26_uploaded_effect *cs40l26_uploaded_effect_find(struct cs40l26_private *cs40l26,
+ int id)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+
+ if (list_empty(&cs40l26->effect_head)) {
+ dev_dbg(cs40l26->dev, "Effect list is empty\n");
+ return ERR_PTR(-ENODATA);
+ }
+
+ list_for_each_entry(ueffect, &cs40l26->effect_head, list) {
+ if (ueffect->id == id)
+ return ueffect;
+ }
+
+ return NULL;
+}
+
+static struct cs40l26_buzzgen_config cs40l26_buzzgen_configs[] = {
+ { .duration_name = "BUZZ_EFFECTS2_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS2_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS2_BUZZ_LEVEL",
+ .effect_id = -1 },
+
+ { .duration_name = "BUZZ_EFFECTS3_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS3_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS3_BUZZ_LEVEL",
+ .effect_id = -1 },
+
+ { .duration_name = "BUZZ_EFFECTS4_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS4_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS4_BUZZ_LEVEL",
+ .effect_id = -1 },
+
+ { .duration_name = "BUZZ_EFFECTS5_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS5_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS5_BUZZ_LEVEL",
+ .effect_id = -1 },
+
+ { .duration_name = "BUZZ_EFFECTS6_BUZZ_DURATION",
+ .freq_name = "BUZZ_EFFECTS6_BUZZ_FREQ",
+ .level_name = "BUZZ_EFFECTS6_BUZZ_LEVEL",
+ .effect_id = -1 },
+};
+
+static int cs40l26_buzzgen_find_slot(struct cs40l26_private *cs40l26, int id)
+{
+ int i, slot = -1;
+
+ for (i = CS40L26_BUZZGEN_NUM_CONFIGS - 1; i >= 0; i--) {
+ if (cs40l26_buzzgen_configs[i].effect_id == id) {
+ slot = i;
+ break;
+ } else if (cs40l26_buzzgen_configs[i].effect_id == -1) {
+ slot = i;
+ }
+ }
+
+ return slot;
+}
+
+static int cs40l26_erase_buzzgen(struct cs40l26_private *cs40l26, int id)
+{
+ int slot = cs40l26_buzzgen_find_slot(cs40l26, id);
+
+ if (slot == -1) {
+ dev_err(cs40l26->dev, "Failed to erase BUZZGEN config for id %d\n", id);
+ return -EINVAL;
+ }
+
+ cs40l26_buzzgen_configs[slot].effect_id = -1;
+
+ return 0;
+}
+
+static int cs40l26_sine_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ u32 freq, level, duration;
+ int ret, slot;
+
+ slot = cs40l26_buzzgen_find_slot(cs40l26, effect->id);
+ if (slot == -1) {
+ dev_err(cs40l26->dev, "No free BUZZGEN slot available\n");
+ return -ENOSPC;
+ }
+
+ cs40l26_buzzgen_configs[slot].effect_id = effect->id;
+
+ /* Divide by 4 to match firmware's formatting expectation */
+ duration = (u32) (effect->replay.length / 4);
+
+ if (effect->u.periodic.period < CS40L26_BUZZGEN_PERIOD_MS_MIN)
+ freq = 1000 / CS40L26_BUZZGEN_PERIOD_MS_MIN;
+ else if (effect->u.periodic.period > CS40L26_BUZZGEN_PERIOD_MS_MAX)
+ freq = 1000 / CS40L26_BUZZGEN_PERIOD_MS_MAX;
+ else
+ freq = 1000 / effect->u.periodic.period;
+
+ if (effect->u.periodic.magnitude < CS40L26_BUZZGEN_LEVEL_MIN)
+ level = CS40L26_BUZZGEN_LEVEL_MIN;
+ else if (effect->u.periodic.magnitude > CS40L26_BUZZGEN_LEVEL_MAX)
+ level = CS40L26_BUZZGEN_LEVEL_MAX;
+ else
+ level = effect->u.periodic.magnitude;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].duration_name,
+ CS40L26_BUZZGEN_ALGO_ID, duration);
+ if (ret)
+ goto err_pwr_lock;
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].freq_name,
+ CS40L26_BUZZGEN_ALGO_ID, freq);
+ if (ret)
+ goto err_pwr_lock;
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, cs40l26_buzzgen_configs[slot].level_name,
+ CS40L26_BUZZGEN_ALGO_ID, level);
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (ret)
+ return ret;
+
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = CS40L26_WVFRM_BANK_BUZ;
+ /*
+ * BUZZGEN 1 is reserved for OTP buzz; BUZZGEN 2 - BUZZGEN 6 are valid.
+ * Add an offset of 1 for this reason.
+ */
+ ueffect->trigger_index = CS40L26_BUZZGEN_INDEX_START + slot + 1;
+
+ return 0;
+}
+
+static int cs40l26_custom_upload(struct cs40l26_private *cs40l26, struct ff_effect *effect,
+ struct cs40l26_uploaded_effect *ueffect)
+{
+ struct device *dev = cs40l26->dev;
+ u32 nwaves, min_index, max_index, trigger_index;
+ u16 bank, index;
+ int ret;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ ret = cs40l26_num_waves_get(cs40l26, &nwaves);
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (ret)
+ return ret;
+
+ bank = (u16) (cs40l26->raw_custom_data[0] & 0xffffu);
+ index = (u16) (cs40l26->raw_custom_data[1] & 0xffffu);
+
+ if (bank == CS40L26_WVFRM_BANK_RAM) {
+ if (nwaves == 0) {
+ dev_err(dev, "No waveforms in RAM\n");
+ return -ENODATA;
+ }
+
+ min_index = CS40L26_RAM_INDEX_START;
+ max_index = min_index + nwaves - 1;
+ } else if (bank == CS40L26_WVFRM_BANK_ROM) {
+ min_index = CS40L26_ROM_INDEX_START;
+ max_index = CS40L26_ROM_INDEX_END;
+ } else {
+ dev_err(dev, "Invalid custom waveform bank: %u\n", bank);
+ return -EINVAL;
+ }
+
+ trigger_index = index + min_index;
+ if (trigger_index > max_index) {
+ dev_err(dev, "RAM index 0x%X out of bounds\n", trigger_index);
+ return -EINVAL;
+ }
+ dev_dbg(dev, "%s: Trigger Index = 0x%08X\n", __func__, trigger_index);
+
+ ueffect->id = effect->id;
+ ueffect->wvfrm_bank = bank;
+ ueffect->trigger_index = trigger_index;
+
+ return 0;
+}
+
+static int cs40l26_uploaded_effect_add(struct cs40l26_private *cs40l26)
+{
+ struct ff_effect *effect = &cs40l26->upload_effect;
+ struct device *dev = cs40l26->dev;
+ bool is_new = false;
+ struct cs40l26_uploaded_effect *ueffect;
+ int ret;
+
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, cs40l26->upload_effect.id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ is_new = true;
+ ueffect = kzalloc(sizeof(struct cs40l26_uploaded_effect), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(ueffect))
+ return -ENOMEM;
+ }
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ ret = cs40l26_custom_upload(cs40l26, effect, ueffect);
+ } else if (effect->u.periodic.waveform == FF_SINE) {
+ ret = cs40l26_sine_upload(cs40l26, effect, ueffect);
+ } else {
+ dev_err(dev, "Invalid periodic waveform type: 0x%X\n", effect->u.periodic.waveform);
+ ret = -EINVAL;
+ }
+ if (ret)
+ goto err_free;
+
+ if (effect->trigger.button) {
+ ret = cs40l26_map_effect_to_gpio(cs40l26, effect, ueffect);
+ if (ret)
+ goto err_free;
+ } else {
+ ueffect->mapping = CS40L26_GPIO_MAP_INVALID;
+ }
+
+ if (is_new)
+ list_add(&ueffect->list, &cs40l26->effect_head);
+
+ return 0;
+
+err_free:
+ if (is_new)
+ kfree(ueffect);
+
+ return ret;
+}
+
+static void cs40l26_upload_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, upload_work);
+ struct device *dev = cs40l26->dev;
+
+ cs40l26->upload_ret = cs40l26_pm_enter(dev);
+ if (cs40l26->upload_ret)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ cs40l26->upload_ret = cs40l26_uploaded_effect_add(cs40l26);
+
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+}
+
+static void cs40l26_vibe_start_worker(struct work_struct *work)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+ struct cs40l26_private *cs40l26;
+ struct ff_effect *effect;
+ struct device *dev;
+ u32 invert;
+ int ret;
+
+ cs40l26 = container_of(work, struct cs40l26_private, vibe_start_work);
+ dev = cs40l26->dev;
+
+ ret = cs40l26_pm_enter(dev);
+ if (ret)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ effect = cs40l26->trigger_effect;
+
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, effect->id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(dev, "No such effect (ID = %d)\n", effect->id);
+ goto err_mutex;
+ }
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_MS", CS40L26_VIBEGEN_ALGO_ID,
+ effect->replay.length);
+ if (ret)
+ goto err_pwr_lock;
+
+ switch (effect->direction) {
+ case 0x0000:
+ invert = 0;
+ break;
+ case 0x8000:
+ invert = 1;
+ break;
+ default:
+ dev_err(dev, "Invalid direction 0x%X\n", effect->direction);
+ goto err_pwr_lock;
+ }
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, "SOURCE_INVERT", CS40L26_EXT_ALGO_ID, invert);
+ if (ret)
+ goto err_pwr_lock;
+
+ ret = cs40l26_mailbox_write(cs40l26, ueffect->trigger_index);
+ if (ret)
+ goto err_pwr_lock;
+
+ reinit_completion(&cs40l26->erase);
+
+err_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+}
+
+static void cs40l26_vibe_stop_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26;
+ int ret;
+
+ cs40l26 = container_of(work, struct cs40l26_private, vibe_stop_work);
+
+ ret = cs40l26_pm_enter(cs40l26->dev);
+ if (ret)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ ret = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_STOP_PLAYBACK);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to stop playback\n");
+
+ mutex_unlock(&cs40l26->lock);
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+static void cs40l26_erase_worker(struct work_struct *work)
+{
+ struct cs40l26_uploaded_effect *ueffect;
+ struct cs40l26_private *cs40l26;
+ u16 duration, replay_len;
+ int id;
+
+ cs40l26 = container_of(work, struct cs40l26_private, erase_work);
+
+ cs40l26->erase_ret = cs40l26_pm_enter(cs40l26->dev);
+ if (cs40l26->erase_ret)
+ return;
+
+ mutex_lock(&cs40l26->lock);
+
+ id = cs40l26->erase_effect->id;
+ ueffect = cs40l26_uploaded_effect_find(cs40l26, id);
+ if (IS_ERR_OR_NULL(ueffect)) {
+ dev_err(cs40l26->dev, "Failed to erase nonexistent effect (%d)\n", id);
+ cs40l26->erase_ret = PTR_ERR(ueffect);
+ goto err_mutex;
+ }
+
+ /* Wait for any ongoing playback to stop */
+ replay_len = cs40l26->erase_effect->replay.length;
+ duration = replay_len ? replay_len + 500 : CS40L26_VIBEGEN_MAX_TIME_MS;
+
+ mutex_unlock(&cs40l26->lock);
+
+ if (!wait_for_completion_timeout(&cs40l26->erase, msecs_to_jiffies(duration))) {
+ cs40l26->erase_ret = -ETIME;
+ dev_err(cs40l26->dev, "Timed out waiting for playback stop\n");
+ goto err_pm;
+ }
+ mutex_lock(&cs40l26->lock);
+
+ if (ueffect->wvfrm_bank == CS40L26_WVFRM_BANK_BUZ) {
+ cs40l26->erase_ret = cs40l26_erase_buzzgen(cs40l26, ueffect->id);
+ if (cs40l26->erase_ret)
+ goto err_mutex;
+ }
+
+ if (ueffect->mapping != CS40L26_GPIO_MAP_INVALID) {
+ cs40l26->erase_ret = cs40l26_erase_gpio_mapping(cs40l26, ueffect->mapping);
+ if (cs40l26->erase_ret)
+ goto err_mutex;
+ }
+
+ list_del(&ueffect->list);
+ kfree(ueffect);
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+err_pm:
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+static void cs40l26_set_gain_worker(struct work_struct *work)
+{
+ struct cs40l26_private *cs40l26 = container_of(work, struct cs40l26_private, set_gain_work);
+
+ if (cs40l26_pm_enter(cs40l26->dev))
+ return;
+
+ mutex_lock(&cs40l26->lock);
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ cs40l26_fw_ctl_write(&cs40l26->dsp, "SOURCE_ATTENUATION", CS40L26_EXT_ALGO_ID,
+ cs40l26_atten_lut_q21_2[cs40l26->gain_pct]);
+
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ mutex_unlock(&cs40l26->lock);
+ cs40l26_pm_exit(cs40l26->dev);
+}
+
+static int cs40l26_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ u32 len = effect->u.periodic.custom_len;
+ int ret;
+
+ if (effect->type != FF_PERIODIC) {
+ dev_err(cs40l26->dev, "Effect type 0x%X not supported\n", effect->type);
+ return -EINVAL;
+ }
+
+ memcpy(&cs40l26->upload_effect, effect, sizeof(struct ff_effect));
+
+ if (effect->u.periodic.waveform == FF_CUSTOM) {
+ cs40l26->raw_custom_data = kcalloc(len, sizeof(s16), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(cs40l26->raw_custom_data))
+ return -ENOMEM;
+
+ if (copy_from_user(cs40l26->raw_custom_data, effect->u.periodic.custom_data,
+ sizeof(s16) * len)) {
+ dev_err(cs40l26->dev, "Failed to get user data\n");
+ ret = -EFAULT;
+ goto out_free;
+ }
+ }
+
+ /*
+ * The no-sleep nature of Input FF callbacks requires usage of
+ * worker functions in order to properly utilize the PM runtime
+ * framework.
+ */
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->upload_work);
+ flush_work(&cs40l26->upload_work);
+ ret = cs40l26->upload_ret;
+
+out_free:
+ if (effect->u.periodic.waveform == FF_CUSTOM)
+ kfree(cs40l26->raw_custom_data);
+ cs40l26->raw_custom_data = NULL;
+ memset(&cs40l26->upload_effect, 0, sizeof(struct ff_effect));
+
+ return ret;
+}
+
+static int cs40l26_playback_effect(struct input_dev *dev, int effect_id, int val)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+
+ cs40l26->trigger_effect = &dev->ff->effects[effect_id];
+ if (IS_ERR_OR_NULL(cs40l26->trigger_effect)) {
+ dev_err(cs40l26->dev, "No such effect (ID = %d)\n", effect_id);
+ return -ENOENT;
+ }
+
+ if (val > 0)
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_start_work);
+ else
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->vibe_stop_work);
+
+ return 0;
+}
+
+static int cs40l26_erase_effect(struct input_dev *dev, int effect_id)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+ struct ff_effect *effect;
+
+ effect = &dev->ff->effects[effect_id];
+ if (IS_ERR_OR_NULL(effect)) {
+ dev_err(cs40l26->dev, "Could not erase nonexistent effect\n");
+ return -ENOENT;
+ }
+
+ cs40l26->erase_effect = effect;
+
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->erase_work);
+ flush_work(&cs40l26->erase_work);
+
+ return cs40l26->erase_ret;
+}
+
+static void cs40l26_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct cs40l26_private *cs40l26 = input_get_drvdata(dev);
+
+ if (gain >= CS40L26_NUM_ATTEN_LUT_VALUES) {
+ dev_err(cs40l26->dev, "Gain %u%% out of bounds\n", gain);
+ return;
+ }
+
+ cs40l26->gain_pct = gain;
+ queue_work(cs40l26->vibe_workqueue, &cs40l26->set_gain_work);
+}
+
+static int cs40l26_input_init(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ cs40l26->input = devm_input_allocate_device(dev);
+ if (IS_ERR_OR_NULL(cs40l26->input))
+ return -ENOMEM;
+
+ cs40l26->input->name = "cs40l26_input";
+ cs40l26->input->id.product = cs40l26->devid;
+ cs40l26->input->id.version = cs40l26->revid;
+
+ input_set_drvdata(cs40l26->input, cs40l26);
+ input_set_capability(cs40l26->input, EV_FF, FF_PERIODIC);
+ input_set_capability(cs40l26->input, EV_FF, FF_CUSTOM);
+ input_set_capability(cs40l26->input, EV_FF, FF_SINE);
+ input_set_capability(cs40l26->input, EV_FF, FF_GAIN);
+
+ ret = input_ff_create(cs40l26->input, FF_MAX_EFFECTS);
+ if (ret) {
+ dev_err(dev, "Failed to create FF device: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * input_ff_create() automatically sets FF_RUMBLE capabilities;
+ * we want to restrtict this to only FF_PERIODIC
+ */
+ __clear_bit(FF_RUMBLE, cs40l26->input->ffbit);
+
+ cs40l26->input->ff->upload = cs40l26_upload_effect;
+ cs40l26->input->ff->playback = cs40l26_playback_effect;
+ cs40l26->input->ff->erase = cs40l26_erase_effect;
+ cs40l26->input->ff->set_gain = cs40l26_set_gain;
+
+ ret = input_register_device(cs40l26->input);
+ if (ret) {
+ dev_err(dev, "Cannot register input device: %d\n", ret);
+ return ret;
+ }
+
+ INIT_LIST_HEAD(&cs40l26->effect_head);
+
+ return ret;
+}
+
+static int cs40l26_bst_config(struct cs40l26_private *cs40l26)
+{
+ u32 bst_ipk_ma, bst_ipk_ctl, vbst_mv, vbst_ctl, vbst_cfg;
+ int ret;
+
+ vbst_mv = cs40l26->pdata.vbst_uv / 1000;
+ if (vbst_mv < CS40L26_VBST_MILLIVOLT_MIN || vbst_mv > CS40L26_VBST_MILLIVOLT_MAX)
+ vbst_ctl = CS40L26_BST_CTL_VP;
+ else
+ vbst_ctl = ((vbst_mv - CS40L26_VBST_MILLIVOLT_MIN) / CS40L26_VBST_MILLIVOLT_STEP);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_1, vbst_ctl);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update boost voltage: %d\n", ret);
+ return ret;
+ }
+
+ ret = cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_1, vbst_ctl, true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_VBST_CTL_2, &vbst_cfg);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get boost control config: %d\n", ret);
+ return ret;
+ }
+ vbst_cfg |= (1 << CS40L26_BST_CTL_LIM_EN_SHIFT);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_VBST_CTL_2, vbst_cfg);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update boost control config: %d\n", ret);
+ return ret;
+ }
+
+ ret = cs40l26_pseq_write(cs40l26, CS40L26_VBST_CTL_2, vbst_cfg, true,
+ CS40L26_PSEQ_OP_WRITE_FULL);
+ if (ret)
+ return ret;
+
+ bst_ipk_ma = cs40l26->pdata.bst_ipk_ua / 1000;
+ if (bst_ipk_ma < CS40L26_BST_IPK_MILLIAMP_MIN || bst_ipk_ma > CS40L26_BST_IPK_MILLIAMP_MAX)
+ bst_ipk_ctl = CS40L26_BST_IPK_MILLIAMP_DEFAULT;
+ else
+ bst_ipk_ctl = (bst_ipk_ma / CS40L26_BST_IPK_MILLIAMP_STEP) -
+ CS40L26_BST_IPK_CTL_RESERVED;
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_BST_IPK_CTL, bst_ipk_ctl);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to update boost peak current: %d\n", ret);
+ return ret;
+ }
+
+ ret = cs40l26_pseq_write(cs40l26, CS40L26_BST_IPK_CTL, bst_ipk_ctl, true,
+ CS40L26_PSEQ_OP_WRITE_L16);
+ if (ret)
+ return ret;
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0,
+ BIT(CS40L26_IRQ1_BST_IPK_FLAG));
+}
+
+static int cs40l26_wksrc_config(struct cs40l26_private *cs40l26)
+{
+ u8 mask_wksrc;
+ u32 val, mask;
+
+ if (cs40l26->devid == CS40L26_DEVID_A || cs40l26->devid == CS40L26_DEVID_L27_A)
+ mask_wksrc = 1;
+ else
+ mask_wksrc = 0;
+
+ val = BIT(CS40L26_IRQ1_WKSRC_STS_SPI) | (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO2) |
+ (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ (mask_wksrc << CS40L26_IRQ1_WKSRC_STS_GPIO4);
+
+ mask = BIT(CS40L26_IRQ1_WKSRC_STS_ANY) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO1) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_I2C) | BIT(CS40L26_IRQ1_WKSRC_STS_SPI) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO2) | BIT(CS40L26_IRQ1_WKSRC_STS_GPIO3) |
+ BIT(CS40L26_IRQ1_WKSRC_STS_GPIO4);
+
+ return cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, val, mask);
+}
+
+static int cs40l26_hw_init(struct cs40l26_private *cs40l26)
+{
+ int ret;
+
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_1, 0, BIT(CS40L26_IRQ1_AMP_ERR) |
+ BIT(CS40L26_IRQ1_TEMP_ERR) | BIT(CS40L26_IRQ1_BST_SHORT_ERR) |
+ BIT(CS40L26_IRQ1_BST_DCM_UVP_ERR) | BIT(CS40L26_IRQ1_BST_OVP_ERR) |
+ BIT(CS40L26_IRQ1_VIRTUAL2_MBOX_WR));
+ if (ret)
+ return ret;
+
+ ret = cs40l26_irq_update_mask(cs40l26, CS40L26_IRQ1_MASK_2, 0, BIT(CS40L26_IRQ2_PLL_LOCK) |
+ BIT(CS40L26_IRQ2_PLL_READY));
+ if (ret)
+ return ret;
+
+ ret = cs40l26_wksrc_config(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_gpio_config(cs40l26);
+ if (ret)
+ return ret;
+
+ return cs40l26_bst_config(cs40l26);
+}
+
+static int cs40l26_lbst_short_test(struct cs40l26_private *cs40l26)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ unsigned int err;
+ int ret;
+
+ ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK,
+ CS40L26_BST_CTL_SEL_FIXED);
+ if (ret) {
+ dev_err(dev, "Failed to set VBST_CTL_2\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK,
+ CS40L26_BST_CTL_VP);
+ if (ret) {
+ dev_err(dev, "Failed to set VBST_CTL_1\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 1);
+ if (ret) {
+ dev_err(dev, "Failed to set GLOBAL_EN\n");
+ return ret;
+ }
+
+ /* Wait until boost converter is guranteed to be powered up */
+ usleep_range(CS40L26_BST_TIME_US_MIN, CS40L26_BST_TIME_US_MAX);
+
+ ret = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err);
+ if (ret) {
+ dev_err(dev, "Failed to get ERROR_RELEASE contents\n");
+ return ret;
+ }
+
+ if (err & BIT(CS40L26_BST_SHORT_ERR_RLS)) {
+ dev_err(dev, "FATAL: Boost shorted at startup\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_GLOBAL_ENABLES, CS40L26_GLOBAL_EN_MASK, 0);
+ if (ret) {
+ dev_err(dev, "Failed to clear GLOBAL_EN\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_2, CS40L26_BST_CTL_SEL_MASK,
+ CS40L26_BST_CTL_SEL_CLASS_H);
+ if (ret) {
+ dev_err(dev, "Failed to set VBST_CTL_2\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, CS40L26_VBST_CTL_1, CS40L26_BST_CTL_MASK,
+ CS40L26_BST_CTL_VP);
+ if (ret)
+ dev_err(dev, "Failed to set VBST_CTL_1\n");
+
+ return ret;
+}
+
+static int cs40l26_part_num_resolve(struct cs40l26_private *cs40l26)
+{
+ u32 devid, revid;
+ int ret;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_DEVID, &devid);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read device ID: %d\n", ret);
+ return ret;
+ }
+ devid &= CS40L26_DEVID_MASK;
+
+ switch (devid) {
+ case CS40L26_DEVID_A:
+ case CS40L26_DEVID_B:
+ case CS40L26_DEVID_L27_A:
+ case CS40L26_DEVID_L27_B:
+ cs40l26->devid = devid;
+ break;
+ default:
+ dev_err(cs40l26->dev, "Invalid device ID: 0x%06X\n", devid);
+ return -EINVAL;
+ }
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_REVID, &revid);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read revision ID\n");
+ return ret;
+ }
+ revid &= CS40L26_REVID_MASK;
+
+ if (revid == CS40L26_REVID_A1 || revid == CS40L26_REVID_B0) {
+ cs40l26->revid = revid;
+ } else {
+ dev_err(cs40l26->dev, "Invalid device revision: 0x%02X\n", revid);
+ return -EINVAL;
+ }
+
+ dev_info(cs40l26->dev, "Cirrus Logic CS40L26 ID: 0x%06X, Revision: 0x%02X\n",
+ cs40l26->devid, cs40l26->revid);
+
+ return 0;
+}
+
+static int cs40l26_handle_platform_data(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ u32 val;
+
+ if (!device_property_read_u32(dev, "cirrus,bst-ctl-microvolt", &val))
+ cs40l26->pdata.vbst_uv = val;
+ else
+ cs40l26->pdata.vbst_uv = 0;
+
+ if (!device_property_read_u32(dev, "cirrus,bst-ipk-microamp", &val))
+ cs40l26->pdata.bst_ipk_ua = val;
+ else
+ cs40l26->pdata.bst_ipk_ua = 0;
+
+ if (!device_property_read_bool(dev, "cirrus,bst-exploratory-mode-disable"))
+ cs40l26->pdata.exploratory_mode_enabled = false;
+ else
+ cs40l26->pdata.exploratory_mode_enabled = true;
+
+ return 0;
+}
+
+static int cs40l26_dsp_state_get(struct cs40l26_private *cs40l26, u8 *state)
+{
+ bool mutex_available = !mutex_is_locked(&cs40l26->dsp.pwr_lock);
+ u32 dsp_state = CS40L26_DSP_STATE_NONE;
+ int i, ret = 0;
+
+ if (cs40l26->dsp.running) {
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ if (mutex_available)
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "PM_CUR_STATE",
+ CS40L26_PM_ALGO_ID, &dsp_state);
+
+ if (mutex_available)
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+
+ if (ret)
+ return ret;
+
+ if (dsp_state != CS40L26_DSP_STATE_NONE)
+ break;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX);
+ }
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(cs40l26->dev, "Timed out reading PM_CUR_STATE\n");
+ return -ETIMEDOUT;
+ }
+ } else {
+ ret = regmap_read_poll_timeout(cs40l26->regmap, CS40L26_A1_PM_CUR_STATE_STATIC_REG,
+ dsp_state, dsp_state != CS40L26_DSP_STATE_NONE,
+ CS40L26_DSP_TIMEOUT_US_MAX, CS40L26_DSP_TIMEOUT_US_MAX *
+ CS40L26_DSP_TIMEOUT_COUNT);
+ if (ret)
+ return ret;
+ }
+
+ switch (dsp_state) {
+ case CS40L26_DSP_STATE_HIBERNATE:
+ case CS40L26_DSP_STATE_SHUTDOWN:
+ case CS40L26_DSP_STATE_STANDBY:
+ case CS40L26_DSP_STATE_ACTIVE:
+ *state = CS40L26_DSP_STATE_MASK & dsp_state;
+ break;
+ default:
+ dev_err(cs40l26->dev, "DSP state %u is invalid\n", dsp_state);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static bool cs40l26_dsp_can_run(struct cs40l26_private *cs40l26)
+{
+ bool mutex_available = !mutex_is_locked(&cs40l26->dsp.pwr_lock);
+ u32 pm_state_locks;
+ u8 state;
+ int ret;
+
+ ret = cs40l26_dsp_state_get(cs40l26, &state);
+ if (ret)
+ return false;
+
+ if (state == CS40L26_DSP_STATE_ACTIVE)
+ return true;
+
+ if (state != CS40L26_DSP_STATE_STANDBY) {
+ dev_err(cs40l26->dev, "DSP in bad state: %u\n", state);
+ return false;
+ }
+
+ if (cs40l26->dsp.running) {
+ if (mutex_available)
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+
+ ret = cs40l26_fw_ctl_read_raw(&cs40l26->dsp, "PM_STATE_LOCKS", CS40L26_PM_ALGO_ID,
+ CS40L26_DSP_LOCK3_OFFSET / sizeof(u32), sizeof(u32),
+ &pm_state_locks);
+
+ if (mutex_available)
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+
+ if (ret)
+ return false;
+ } else {
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_PM_STATE_LOCKS3_STATIC_REG,
+ &pm_state_locks);
+ if (ret)
+ return false;
+ }
+
+ return (pm_state_locks & CS40L26_DSP_LOCK3_MASK) == CS40L26_DSP_LOCK3_MASK;
+}
+
+static int cs40l26_mbox_buffer_read(struct cs40l26_private *cs40l26, u32 *val)
+{
+ u32 q_rd, q_wt, status;
+ int ret;
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "QUEUE_WT", CS40L26_MAILBOX_ALGO_ID, &q_wt);
+ if (ret)
+ goto exit_pwr_lock;
+
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "QUEUE_RD", CS40L26_MAILBOX_ALGO_ID, &q_rd);
+ if (ret)
+ goto exit_pwr_lock;
+
+ if (q_rd - sizeof(u32) == q_wt) {
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "MAILBOX_STATUS",
+ CS40L26_MAILBOX_ALGO_ID, &status);
+ if (ret)
+ goto exit_pwr_lock;
+
+ if (status) {
+ dev_err(cs40l26->dev, "Mailbox buffer is full, info missing\n");
+ ret = -ENOSPC;
+ goto exit_pwr_lock;
+ }
+ }
+
+ if (q_rd == q_wt) {
+ dev_dbg(cs40l26->dev, "Reached end of queue\n");
+ ret = 1;
+ goto exit_pwr_lock;
+ }
+
+ ret = regmap_read(cs40l26->regmap, q_rd, val);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read from mailbox buffer\n");
+ goto exit_pwr_lock;
+ }
+
+ q_rd = (q_rd == cs40l26->mbox_q_last) ? cs40l26->mbox_q_base : q_rd + sizeof(u32);
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, "QUEUE_RD", CS40L26_MAILBOX_ALGO_ID, q_rd);
+
+exit_pwr_lock:
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+
+ return ret;
+}
+
+static int cs40l26_handle_mbox_buffer(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ u32 val;
+
+ while (!cs40l26_mbox_buffer_read(cs40l26, &val)) {
+ if ((val & CS40L26_DSP_MBOX_CMD_INDEX_MASK) == CS40L26_DSP_MBOX_PANIC) {
+ dev_err(dev, "DSP PANIC! Error condition: 0x%06X\n",
+ (u32)(val & CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK));
+ return -ENOTRECOVERABLE;
+ }
+
+ switch (val) {
+ case CS40L26_DSP_MBOX_COMPLETE_MBOX:
+ dev_dbg(dev, "Mailbox: COMPLETE_MBOX\n");
+ complete_all(&cs40l26->erase);
+ break;
+ case CS40L26_DSP_MBOX_COMPLETE_GPIO:
+ dev_dbg(dev, "Mailbox: COMPLETE_GPIO\n");
+ break;
+ case CS40L26_DSP_MBOX_TRIGGER_CP:
+ dev_dbg(dev, "Mailbox: TRIGGER_CP\n");
+ break;
+ case CS40L26_DSP_MBOX_TRIGGER_GPIO:
+ dev_dbg(dev, "Mailbox: TRIGGER_GPIO\n");
+ break;
+ case CS40L26_DSP_MBOX_PM_AWAKE:
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ dev_dbg(dev, "Mailbox: AWAKE\n");
+ break;
+ case CS40L26_DSP_MBOX_SYS_ACK:
+ dev_err(dev, "Mailbox: ACK\n");
+ return -EPERM;
+ default:
+ dev_err(dev, "MBOX buffer value (0x%X) is invalid\n", val);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int cs40l26_error_release(struct cs40l26_private *cs40l26, unsigned int err_rls)
+{
+ struct regmap *regmap = cs40l26->regmap;
+ struct device *dev = cs40l26->dev;
+ u32 err_sts, err_cfg;
+ int ret;
+
+ ret = regmap_read(regmap, CS40L26_ERROR_RELEASE, &err_sts);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get error status\n");
+ return ret;
+ }
+
+ err_cfg = err_sts & ~BIT(err_rls);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret) {
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+ return ret;
+ }
+
+ err_cfg |= BIT(err_rls);
+
+ ret = regmap_write(regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret) {
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+ return ret;
+ }
+
+ err_cfg &= ~BIT(err_rls);
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_ERROR_RELEASE, err_cfg);
+ if (ret)
+ dev_err(dev, "Actuator Safe Mode release sequence failed\n");
+
+ return ret;
+}
+
+static int cs40l26_handle_irq1(struct cs40l26_private *cs40l26, enum cs40l26_irq1 irq1)
+{
+ struct device *dev = cs40l26->dev;
+ u32 err_rls = 0;
+ int ret = 0;
+ u32 pwrmgt_sts, last_wksrc;
+
+ switch (irq1) {
+ case CS40L26_IRQ1_GPIO1_RISE:
+ case CS40L26_IRQ1_GPIO1_FALL:
+ case CS40L26_IRQ1_GPIO2_RISE:
+ case CS40L26_IRQ1_GPIO2_FALL:
+ case CS40L26_IRQ1_GPIO3_RISE:
+ case CS40L26_IRQ1_GPIO3_FALL:
+ case CS40L26_IRQ1_GPIO4_RISE:
+ case CS40L26_IRQ1_GPIO4_FALL:
+ dev_dbg(dev, "GPIO%u %s edge detected\n", (irq1 / 2) + 1,
+ (irq1 % 2) ? "falling" : "rising");
+
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_ANY:
+ dev_dbg(dev, "Wakesource detected (ANY)\n");
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_PWRMGT_STS, &pwrmgt_sts);
+ if (ret) {
+ dev_err(dev, "Failed to get Power Management Status\n");
+ goto err;
+ }
+
+ cs40l26->wksrc_sts = (u8)((pwrmgt_sts & CS40L26_WKSRC_STS_MASK) >>
+ CS40L26_WKSRC_STS_SHIFT);
+
+ mutex_lock(&cs40l26->dsp.pwr_lock);
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "LAST_WAKESRC_CTL", cs40l26->dsp.fw_id,
+ &last_wksrc);
+ mutex_unlock(&cs40l26->dsp.pwr_lock);
+ if (ret)
+ goto err;
+
+ cs40l26->last_wksrc_pol = (u8) (last_wksrc & CS40L26_WKSRC_GPIO_POL_MASK);
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_GPIO1:
+ case CS40L26_IRQ1_WKSRC_STS_GPIO2:
+ case CS40L26_IRQ1_WKSRC_STS_GPIO3:
+ case CS40L26_IRQ1_WKSRC_STS_GPIO4:
+ dev_dbg(dev, "GPIO%u event woke device from hibernate\n",
+ irq1 - CS40L26_IRQ1_WKSRC_STS_GPIO1 + 1);
+
+ if (cs40l26->wksrc_sts & cs40l26->last_wksrc_pol) {
+ dev_dbg(dev, "GPIO%u falling edge detected\n", irq1 - 8);
+ cs40l26->wksrc_sts |= CS40L26_WKSRC_STS_EN;
+ } else {
+ dev_dbg(dev, "GPIO%u rising edge detected\n", irq1 - 8);
+ }
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_SPI:
+ dev_dbg(dev, "SPI event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_WKSRC_STS_I2C:
+ dev_dbg(dev, "I2C event woke device from hibernate\n");
+ break;
+ case CS40L26_IRQ1_GLOBAL_EN_ASSERT:
+ dev_dbg(dev, "Started power up seq. (GLOBAL_EN asserted)\n");
+ break;
+ case CS40L26_IRQ1_PDN_DONE:
+ dev_dbg(dev, "Completed power down seq. (GLOBAL_EN cleared)\n");
+ break;
+ case CS40L26_IRQ1_PUP_DONE:
+ dev_dbg(dev, "Completed power up seq. (GLOBAL_EN asserted)\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_RISE:
+ dev_warn(dev, "BST overvoltage warning\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_FLAG_FALL:
+ dev_warn(dev, "BST voltage returned below warning threshold\n");
+ break;
+ case CS40L26_IRQ1_BST_OVP_ERR:
+ dev_err(dev, "BST overvolt. error\n");
+ err_rls = CS40L26_BST_OVP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_DCM_UVP_ERR:
+ dev_err(dev, "BST undervolt. error\n");
+ err_rls = CS40L26_BST_UVP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_SHORT_ERR:
+ dev_err(dev, "LBST short detected\n");
+ err_rls = CS40L26_BST_SHORT_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_BST_IPK_FLAG:
+ dev_dbg(dev, "Current is being limited by LBST inductor\n");
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_RISE:
+ dev_err(dev, "Die overtemperature warning\n");
+ err_rls = CS40L26_TEMP_WARN_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_TEMP_WARN_FALL:
+ dev_warn(dev, "Die temperature returned below threshold\n");
+ break;
+ case CS40L26_IRQ1_TEMP_ERR:
+ dev_err(dev, "Die overtemperature error\n");
+ err_rls = CS40L26_TEMP_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_AMP_ERR:
+ dev_err(dev, "AMP short detected\n");
+ err_rls = CS40L26_AMP_SHORT_ERR_RLS;
+ break;
+ case CS40L26_IRQ1_DC_WATCHDOG_RISE:
+ dev_err(dev, "DC level detected\n");
+ break;
+ case CS40L26_IRQ1_DC_WATCHDOG_FALL:
+ dev_warn(dev, "Previously detected DC level removed\n");
+ break;
+ case CS40L26_IRQ1_VIRTUAL1_MBOX_WR:
+ dev_dbg(dev, "Virtual 1 MBOX write occurred\n");
+ break;
+ case CS40L26_IRQ1_VIRTUAL2_MBOX_WR:
+ ret = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1));
+ if (ret) {
+ dev_err(dev, "Failed to clear Mailbox IRQ\n");
+ return ret;
+ }
+
+ return cs40l26_handle_mbox_buffer(cs40l26);
+ default:
+ dev_err(dev, "Unrecognized IRQ1 EINT1 status\n");
+ return -EINVAL;
+ }
+
+ if (err_rls)
+ ret = cs40l26_error_release(cs40l26, err_rls);
+
+err:
+ regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_1, BIT(irq1));
+
+ return ret;
+}
+
+static int cs40l26_handle_irq2(struct cs40l26_private *cs40l26, enum cs40l26_irq2 irq2)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ switch (irq2) {
+ case CS40L26_IRQ2_PLL_LOCK:
+ dev_dbg(dev, "PLL achieved lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_PHASE_LOCK:
+ dev_dbg(dev, "PLL achieved phase lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_FREQ_LOCK:
+ dev_dbg(dev, "PLL achieved frequency lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_UNLOCK_RISE:
+ dev_err(dev, "PLL has lost lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_UNLOCK_FALL:
+ dev_warn(dev, "PLL has regained lock\n");
+ break;
+ case CS40L26_IRQ2_PLL_READY:
+ dev_dbg(dev, "PLL ready\n");
+ break;
+ case CS40L26_IRQ2_PLL_REFCLK_PRESENT:
+ dev_warn(dev, "REFCLK present for PLL\n");
+ break;
+ case CS40L26_IRQ2_REFCLK_MISSING_RISE:
+ dev_err(dev, "REFCLK input for PLL is missing\n");
+ break;
+ case CS40L26_IRQ2_REFCLK_MISSING_FALL:
+ dev_warn(dev, "REFCLK reported missing is now present\n");
+ break;
+ case CS40L26_IRQ2_ASP_RXSLOT_CFG_ERR:
+ dev_err(dev, "Misconfig. of ASP_RX 1 2 or 3 SLOT fields\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH1_ENTRY:
+ dev_warn(dev,
+ "CH1 data of noise gate has fallen below threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH1_EXIT:
+ dev_err(dev,
+ "CH1 data of noise gate has risen above threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH2_ENTRY:
+ dev_warn(dev,
+ "CH2 data of noise gate has fallen below threshold\n");
+ break;
+ case CS40L26_IRQ2_AUX_NG_CH2_EXIT:
+ dev_err(dev,
+ "CH2 data of noise gate has risen above threshold\n");
+ break;
+ case CS40L26_IRQ2_AMP_NG_ON_RISE:
+ dev_warn(dev, "Amplifier entered noise-gated state\n");
+ break;
+ case CS40L26_IRQ2_AMP_NG_ON_FALL:
+ dev_warn(dev, "Amplifier exited noise-gated state\n");
+ break;
+ case CS40L26_IRQ2_VPBR_FLAG:
+ dev_err(dev, "VP Brownout Prevent not supported\n");
+ break;
+ case CS40L26_IRQ2_VPBR_ATT_CLR:
+ dev_warn(dev,
+ "Cleared attenuation applied by VP brownout event\n");
+ break;
+ case CS40L26_IRQ2_VBBR_FLAG:
+ dev_err(dev, "VBST Brownout Prevent not supported\n");
+ break;
+ case CS40L26_IRQ2_VBBR_ATT_CLR:
+ dev_warn(dev, "Cleared attenuation caused by VBST brownout\n");
+ break;
+ case CS40L26_IRQ2_I2C_NACK_ERR:
+ dev_err(dev, "I2C interface NACK during Broadcast Mode\n");
+ break;
+ case CS40L26_IRQ2_VPMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VPMON)\n");
+ break;
+ case CS40L26_IRQ2_VBSTMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VBSTMON)\n");
+ break;
+ case CS40L26_IRQ2_VMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (VMON)\n");
+ break;
+ case CS40L26_IRQ2_IMON_CLIPPED:
+ dev_err(dev, "Input larger than full-scale value (IMON)\n");
+ break;
+ default:
+ dev_err(dev, "Unrecognized IRQ1 EINT2 status\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_IRQ1_EINT_2, BIT(irq2));
+ if (ret)
+ dev_err(dev, "Failed to clear IRQ1 EINT2 %u\n", irq2);
+
+ return ret;
+}
+
+static irqreturn_t cs40l26_irq(int irq, void *data)
+{
+ struct cs40l26_private *cs40l26 = (struct cs40l26_private *)data;
+ struct regmap *regmap = cs40l26->regmap;
+ int irq1_count = 0, irq2_count = 0;
+ struct device *dev = cs40l26->dev;
+ unsigned int eint, mask, sts;
+ unsigned long val;
+ int i, ret;
+
+ if (cs40l26_pm_enter(dev)) {
+ dev_err(dev, "Interrupts missed\n");
+ return IRQ_NONE;
+ }
+
+
+ mutex_lock(&cs40l26->lock);
+
+ if (regmap_read(regmap, CS40L26_IRQ1_STATUS, &sts)) {
+ dev_err(dev, "Failed to read IRQ1 Status\n");
+ ret = IRQ_NONE;
+ goto err;
+ }
+
+ if (sts != CS40L26_IRQ_STATUS_ASSERT) {
+ dev_err(dev, "IRQ1 asserted with no pending interrupts\n");
+ ret = IRQ_NONE;
+ goto err;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_EINT_1, &eint);
+ if (ret) {
+ dev_err(dev, "Failed to read interrupts status 1\n");
+ goto err;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_MASK_1, &mask);
+ if (ret) {
+ dev_err(dev, "Failed to get interrupts mask 1\n");
+ goto err;
+ }
+
+ val = eint & ~mask;
+ for_each_set_bit(i, &val, CS40L26_IRQ1_NUM_IRQS) {
+ ret = cs40l26_handle_irq1(cs40l26, i);
+ if (ret)
+ goto err;
+
+ irq1_count++;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_EINT_2, &eint);
+ if (ret) {
+ dev_err(dev, "Failed to read interrupts status 2\n");
+ goto err;
+ }
+
+ ret = regmap_read(regmap, CS40L26_IRQ1_MASK_2, &mask);
+ if (ret) {
+ dev_err(dev, "Failed to get interrupts mask 2\n");
+ goto err;
+ }
+
+ val = eint & ~mask;
+ for_each_set_bit(i, &val, CS40L26_IRQ2_NUM_IRQS) {
+ ret = cs40l26_handle_irq2(cs40l26, i);
+ if (ret)
+ goto err;
+
+ irq2_count++;
+ }
+
+err:
+ mutex_unlock(&cs40l26->lock);
+
+ cs40l26_pm_exit(dev);
+
+ /*
+ * IRQ_HANDLED is returned if at least one interrupt request generated
+ * by the device was handled successfully.
+ */
+ if (ret)
+ dev_err(dev, "Failed to process IRQ (%d): %d\n", irq, ret);
+
+ return (irq1_count + irq2_count) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int cs40l26_prevent_hiber(struct cs40l26_private *cs40l26)
+{
+
+ int i, ret;
+
+ for (i = 0; i < CS40L26_DSP_TIMEOUT_COUNT; i++) {
+ ret = cs40l26_mailbox_write(cs40l26, CS40L26_DSP_MBOX_CMD_PREVENT_HIBER);
+ if (ret)
+ return ret;
+
+ usleep_range(CS40L26_DSP_TIMEOUT_US_MIN, CS40L26_DSP_TIMEOUT_US_MAX);
+
+ if (cs40l26_dsp_can_run(cs40l26))
+ break;
+ }
+
+ if (i == CS40L26_DSP_TIMEOUT_COUNT) {
+ dev_err(cs40l26->dev, "Failed to prevent hibernation\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int cs40l26_allow_hiber(struct cs40l26_private *cs40l26)
+{
+ int ret;
+
+ cs40l26->wksrc_sts = 0x00;
+
+ /* Don't perform mailbox write since reading for acknowledgment will wake the device */
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP_VIRTUAL1_MBOX_1,
+ CS40L26_DSP_MBOX_CMD_ALLOW_HIBER);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to allow hibernate: %d\n", ret);
+
+ return ret;
+}
+
+static int cs40l26_dsp_pre_config(struct cs40l26_private *cs40l26)
+{
+ u32 halo_state, timeout_ms;
+ u8 dsp_state;
+ int ret, i;
+
+ ret = cs40l26_prevent_hiber(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_DSP_HALO_STATE_REG, &halo_state);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get HALO state\n");
+ return ret;
+ }
+
+ if (halo_state != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(cs40l26->dev, "DSP not Ready: HALO_STATE: %08X\n", halo_state);
+ return -EINVAL;
+ }
+
+ ret = cs40l26_pm_timeout_ms_get(cs40l26, CS40L26_DSP_STATE_ACTIVE, &timeout_ms);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to get active->standby timeout: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < 10; i++) {
+ ret = cs40l26_dsp_state_get(cs40l26, &dsp_state);
+ if (ret)
+ return ret;
+
+ if (dsp_state != CS40L26_DSP_STATE_SHUTDOWN &&
+ dsp_state != CS40L26_DSP_STATE_STANDBY)
+ dev_warn(cs40l26->dev, "DSP core not safe to kill\n");
+ else
+ break;
+
+ usleep_range(timeout_ms * 1000, (timeout_ms * 1000) + 100);
+ }
+
+ if (i == 10) {
+ dev_err(cs40l26->dev, "DSP Core could not be shut down\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_write(cs40l26->regmap, CS40L26_DSP1_CCM_CORE_CONTROL,
+ CS40L26_DSP_CCM_CORE_KILL);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to kill DSP core\n");
+
+ return ret;
+}
+
+static const struct reg_sequence cs40l26_a1_errata[] = {
+ { CS40L26_PLL_REFCLK_DETECT_0, 0x00000000 },
+ { CS40L26_TEST_KEY_CTRL, 0x00000055 },
+ { CS40L26_TEST_KEY_CTRL, 0x000000AA },
+ { CS40L26_TEST_LBST, CS40L26_DISABLE_EXPLORATORY_MODE },
+};
+
+static int cs40l26_handle_errata(struct cs40l26_private *cs40l26)
+{
+ int num_writes = 1, ret;
+
+ if (!cs40l26->pdata.exploratory_mode_enabled) {
+ ret = cs40l26_lbst_short_test(cs40l26);
+ if (ret)
+ return ret;
+
+ num_writes = ARRAY_SIZE(cs40l26_a1_errata);
+ }
+
+ return cs40l26_pseq_multi_write(cs40l26, cs40l26_a1_errata, num_writes, false,
+ CS40L26_PSEQ_OP_WRITE_FULL);
+}
+
+static int cs40l26_cs_dsp_pre_run(struct cs_dsp *dsp)
+{
+ struct cs40l26_private *cs40l26 = container_of(dsp, struct cs40l26_private, dsp);
+ int ret;
+
+ ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_STANDBY,
+ CS40L26_PM_STDBY_TIMEOUT_MS_DEFAULT);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to set standby->hibernate timeout: %d\n", ret);
+ return ret;
+ }
+
+ ret = cs40l26_pm_timeout_ms_set(cs40l26, CS40L26_DSP_STATE_ACTIVE,
+ CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to set active->standby timeout: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_PWRMGT_CTL, CS40L26_MEM_RDY_MASK,
+ 1 << CS40L26_MEM_RDY_SHIFT);
+ if (ret) {
+ dev_err(dsp->dev, "Failed to set MEM_RDY to initialize RAM");
+ return ret;
+ }
+
+ ret = cs40l26_fw_ctl_read(dsp, "QUEUE_BASE", CS40L26_MAILBOX_ALGO_ID,
+ &cs40l26->mbox_q_base);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_fw_ctl_read(dsp, "QUEUE_LEN", CS40L26_MAILBOX_ALGO_ID, &cs40l26->mbox_q_len);
+ if (ret)
+ return ret;
+
+ cs40l26->mbox_q_last = cs40l26->mbox_q_base + ((cs40l26->mbox_q_len - 1) * sizeof(u32));
+
+ ret = cs40l26_fw_ctl_write(dsp, "CALL_RAM_INIT", dsp->fw_id, 1);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_pseq_init(cs40l26);
+ if (ret)
+ return ret;
+
+ return cs40l26_handle_errata(cs40l26);
+}
+
+static int cs40l26_cs_dsp_post_run(struct cs_dsp *dsp)
+{
+ struct cs40l26_private *cs40l26 = container_of(dsp, struct cs40l26_private, dsp);
+ u32 halo_state, nwaves;
+ int ret;
+
+ ret = cs40l26_prevent_hiber(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_fw_ctl_read(&cs40l26->dsp, "HALO_STATE", dsp->fw_id, &halo_state);
+ if (ret)
+ return ret;
+
+ if (halo_state != CS40L26_DSP_HALO_STATE_RUN) {
+ dev_err(dsp->dev, "Firmware in unexpected state: 0x%X\n", halo_state);
+ return -EINVAL;
+ }
+
+ ret = cs40l26_hw_init(cs40l26);
+ if (ret)
+ return ret;
+
+ cs40l26_pm_runtime_setup(cs40l26);
+
+ ret = cs40l26_allow_hiber(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_pm_enter(dsp->dev);
+ if (ret)
+ return ret;
+
+ ret = cs40l26_fw_ctl_write(&cs40l26->dsp, "TIMEOUT_MS", CS40L26_VIBEGEN_ALGO_ID, 0);
+ if (ret)
+ goto pm_err;
+
+ ret = cs40l26_num_waves_get(cs40l26, &nwaves);
+ if (ret)
+ goto pm_err;
+
+ dev_info(dsp->dev, "%s loaded with %u RAM waveforms\n", "CS40L26", nwaves);
+
+pm_err:
+ cs40l26_pm_exit(dsp->dev);
+
+ return ret;
+}
+
+static const struct cs_dsp_client_ops cs40l26_cs_dsp_client_ops = {
+ .pre_run = cs40l26_cs_dsp_pre_run,
+ .post_run = cs40l26_cs_dsp_post_run,
+};
+
+static int cs40l26_cs_dsp_init(struct cs40l26_private *cs40l26)
+{
+ int ret;
+
+ cs40l26->dsp.num = 1;
+ cs40l26->dsp.type = WMFW_HALO;
+ cs40l26->dsp.dev = cs40l26->dev;
+ cs40l26->dsp.regmap = cs40l26->regmap;
+ cs40l26->dsp.base = CS40L26_DSP_CTRL_BASE;
+ cs40l26->dsp.base_sysinfo = CS40L26_DSP1_SYS_INFO_ID;
+ cs40l26->dsp.mem = cs40l26_dsp_regions;
+ cs40l26->dsp.num_mems = CS40L26_DSP_NUM_REGIONS;
+ cs40l26->dsp.lock_regions = 0xFFFFFFFF;
+ cs40l26->dsp.client_ops = &cs40l26_cs_dsp_client_ops;
+
+ ret = cs_dsp_halo_init(&cs40l26->dsp);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to initialize HALO core\n");
+
+ return ret;
+}
+
+static int cs40l26_fw_upload(struct cs40l26_private *cs40l26)
+{
+ struct device *dev = cs40l26->dev;
+ const struct firmware *wmfw, *bin;
+ int ret;
+
+ ret = cs40l26_cs_dsp_init(cs40l26);
+ if (ret)
+ return ret;
+
+ ret = request_firmware(&wmfw, "cs40l26.wmfw", dev);
+ if (ret) {
+ dev_err(dev, "Failed to load firmware: %d\n", ret);
+ return ret;
+ }
+
+ ret = request_firmware(&bin, "cs40l26.bin", dev);
+ if (ret) {
+ dev_err(dev, "Failed to load tuning: %d\n", ret);
+ goto err_fw_rls;
+ }
+
+ ret = cs40l26_dsp_pre_config(cs40l26);
+ if (ret)
+ goto err_bin_rls;
+
+ mutex_lock(&cs40l26->lock);
+
+ ret = cs_dsp_power_up(&cs40l26->dsp, wmfw, "cs40l26.wmfw", bin,
+ "cs40l26.bin", "cs40l26");
+ if (ret)
+ goto err_mutex;
+
+ if (cs40l26->dsp.fw_id != CS40L26_FW_ID) {
+ dev_err(dev, "Invalid firmware ID: 0x%X\n", cs40l26->dsp.fw_id);
+ ret = -EINVAL;
+ goto err_mutex;
+ }
+
+ if (cs40l26->dsp.fw_id_version < CS40L26_FW_ID_VERSION_MIN) {
+ dev_err(dev, "Invalid firmware version 0x%X\n", cs40l26->dsp.fw_id_version);
+ ret = -EINVAL;
+ goto err_mutex;
+ }
+
+ ret = cs_dsp_run(&cs40l26->dsp);
+ if (ret)
+ dev_err(cs40l26->dev, "Failed to run DSP\n");
+
+err_mutex:
+ mutex_unlock(&cs40l26->lock);
+err_bin_rls:
+ release_firmware(bin);
+err_fw_rls:
+ release_firmware(wmfw);
+
+ return ret;
+}
+
+static inline int cs40l26_worker_init(struct cs40l26_private *cs40l26)
+{
+ cs40l26->vibe_workqueue =
+ alloc_ordered_workqueue("cs40l26_workqueue", WQ_HIGHPRI);
+ if (IS_ERR_OR_NULL(cs40l26->vibe_workqueue)) {
+ dev_err(cs40l26->dev, "Failed to allocate workqueue\n");
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&cs40l26->erase_work, cs40l26_erase_worker);
+ INIT_WORK(&cs40l26->set_gain_work, cs40l26_set_gain_worker);
+ INIT_WORK(&cs40l26->upload_work, cs40l26_upload_worker);
+ INIT_WORK(&cs40l26->vibe_start_work, cs40l26_vibe_start_worker);
+ INIT_WORK(&cs40l26->vibe_stop_work, cs40l26_vibe_stop_worker);
+
+ return 0;
+}
+
+static struct regulator_bulk_data cs40l26_supplies[] = {
+ { .supply = "VP" },
+ { .supply = "VA" },
+};
+
+int cs40l26_probe(struct cs40l26_private *cs40l26, struct cs40l26_platform_data *pdata)
+{
+ struct device *dev = cs40l26->dev;
+ int ret;
+
+ ret = devm_regulator_bulk_get(dev, CS40L26_NUM_SUPPLIES, cs40l26_supplies);
+ if (ret) {
+ dev_err(dev, "Failed to request core supplies: %d\n", ret);
+ goto err;
+ }
+
+ if (pdata) {
+ cs40l26->pdata = *pdata;
+ } else if (cs40l26->dev->of_node) {
+ ret = cs40l26_handle_platform_data(cs40l26);
+ if (ret)
+ goto err;
+ } else {
+ dev_err(dev, "No platform data found\n");
+ ret = -ENODATA;
+ goto err;
+ }
+
+ ret = regulator_bulk_enable(CS40L26_NUM_SUPPLIES, cs40l26_supplies);
+ if (ret) {
+ dev_err(dev, "Failed to enable core supplies: %d\n", ret);
+ goto err;
+ }
+
+ cs40l26->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR_OR_NULL(cs40l26->reset_gpio)) {
+ dev_err(dev, "Failed to get reset GPIO: %d\n", (int) PTR_ERR(cs40l26->reset_gpio));
+ ret = -ENOENT;
+ goto err;
+ }
+
+ usleep_range(CS40L26_MIN_RESET_PULSE_WIDTH_US, CS40L26_MIN_RESET_PULSE_WIDTH_US + 100);
+
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 1);
+
+ usleep_range(CS40L26_CP_READY_DELAY_US, CS40L26_CP_READY_DELAY_US + 100);
+
+ mutex_init(&cs40l26->lock);
+
+ ret = cs40l26_worker_init(cs40l26);
+ if (ret) {
+ dev_err(dev, "Failed to initialize worker threads\n");
+ goto err;
+ }
+
+ init_completion(&cs40l26->erase);
+ complete_all(&cs40l26->erase);
+
+ ret = cs40l26_part_num_resolve(cs40l26);
+ if (ret)
+ goto err;
+
+ /*
+ * The DSP may lock up if a haptic effect is triggered via GPI
+ * event or control port and the PLL is set to closed-loop.
+ *
+ * Set the PLL to open-loop and remove default GPI mappings
+ * to prevent this while the driver is loading and configuring
+ * RAM firmware.
+ *
+ * The firmware will set the PLL back to closed-loop when it is loaded
+ * and the DSP has been started.
+ */
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_REFCLK_INPUT,
+ CS40L26_PLL_REFCLK_LOOP_MASK, 1 << CS40L26_PLL_REFCLK_LOOP_SHIFT);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to set PLL to open-loop: %d\n", ret);
+ goto err;
+ }
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_EVENT_MAP_1, &cs40l26->gpio_press_default);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read GPIO mapping 1\n");
+ goto err;
+ }
+
+ ret = regmap_read(cs40l26->regmap, CS40L26_A1_EVENT_MAP_2, &cs40l26->gpio_release_default);
+ if (ret) {
+ dev_err(cs40l26->dev, "Failed to read GPIO mapping 2\n");
+ goto err;
+ }
+
+ ret = cs40l26_erase_gpio_mapping(cs40l26, CS40L26_GPIO_MAP_A_PRESS);
+ if (ret)
+ goto err;
+
+ ret = cs40l26_erase_gpio_mapping(cs40l26, CS40L26_GPIO_MAP_A_RELEASE);
+ if (ret)
+ goto err;
+
+ /* Set LRA to HI-Z in order to avoid fault conditions */
+ ret = regmap_update_bits(cs40l26->regmap, CS40L26_TST_DAC_MSM_CONFIG,
+ CS40L26_SPK_DEFAULT_HIZ_MASK, 1 << CS40L26_SPK_DEFAULT_HIZ_SHIFT);
+ if (ret) {
+ dev_err(dev, "Failed to set LRA to HI-Z\n");
+ goto err;
+ }
+
+ ret = cs40l26_fw_upload(cs40l26);
+ if (ret)
+ goto err;
+
+ ret = devm_request_threaded_irq(dev, cs40l26->irq, NULL, cs40l26_irq, IRQF_ONESHOT |
+ IRQF_SHARED | IRQF_TRIGGER_LOW, "cs40l26", cs40l26);
+ if (ret) {
+ dev_err(dev, "Failed to request threaded IRQ\n");
+ goto err;
+ }
+
+ ret = cs40l26_input_init(cs40l26);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ cs40l26_remove(cs40l26);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cs40l26_probe);
+
+int cs40l26_remove(struct cs40l26_private *cs40l26)
+{
+ struct regulator *vp_consumer = cs40l26_supplies[CS40L26_VP_SUPPLY].consumer;
+ struct regulator *va_consumer = cs40l26_supplies[CS40L26_VA_SUPPLY].consumer;
+
+ if (cs40l26->input)
+ input_unregister_device(cs40l26->input);
+
+ cs40l26_pm_runtime_teardown(cs40l26);
+
+ if (cs40l26->dsp.running)
+ cs_dsp_stop(&cs40l26->dsp);
+ if (cs40l26->dsp.booted)
+ cs_dsp_power_down(&cs40l26->dsp);
+ if (&cs40l26->dsp)
+ cs_dsp_remove(&cs40l26->dsp);
+
+ if (cs40l26->vibe_workqueue) {
+ cancel_work_sync(&cs40l26->erase_work);
+ cancel_work_sync(&cs40l26->set_gain_work);
+ cancel_work_sync(&cs40l26->upload_work);
+ cancel_work_sync(&cs40l26->vibe_start_work);
+ cancel_work_sync(&cs40l26->vibe_stop_work);
+ destroy_workqueue(cs40l26->vibe_workqueue);
+ }
+
+ mutex_destroy(&cs40l26->lock);
+
+ if (!IS_ERR_OR_NULL(cs40l26->reset_gpio))
+ gpiod_set_value_cansleep(cs40l26->reset_gpio, 0);
+
+ if (vp_consumer)
+ regulator_disable(vp_consumer);
+
+ if (va_consumer)
+ regulator_disable(va_consumer);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l26_remove);
+
+static int cs40l26_suspend(struct device *dev)
+{
+ struct cs40l26_private *cs40l26 = dev_get_drvdata(dev);
+ int ret;
+
+ mutex_lock(&cs40l26->lock);
+ dev_dbg(dev, "%s: Enabling hibernation\n", __func__);
+
+ ret = cs40l26_allow_hiber(cs40l26);
+
+ mutex_unlock(&cs40l26->lock);
+
+ return ret;
+}
+
+static int cs40l26_sys_suspend(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "System suspend, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int cs40l26_sys_suspend_noirq(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "Late system suspend, re-enabling IRQ\n");
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int cs40l26_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s: Disabling hibernation\n", __func__);
+
+ return cs40l26_prevent_hiber(dev_get_drvdata(dev));
+}
+
+static int cs40l26_sys_resume(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "System resume, re-enabling IRQ\n");
+
+ enable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+static int cs40l26_sys_resume_noirq(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+
+ dev_dbg(dev, "Early system resume, disabling IRQ\n");
+
+ disable_irq(i2c_client->irq);
+
+ return 0;
+}
+
+const struct dev_pm_ops cs40l26_pm_ops = {
+ SET_RUNTIME_PM_OPS(cs40l26_suspend, cs40l26_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend, cs40l26_sys_resume)
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs40l26_sys_suspend_noirq, cs40l26_sys_resume_noirq)
+};
+EXPORT_SYMBOL_GPL(cs40l26_pm_ops);
+
+MODULE_DESCRIPTION("CS40L26 Boosted Mono Class D Amplifier for Haptics");
+MODULE_AUTHOR("Fred Treven, Cirrus Logic Inc. <fred.treven@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/input/cs40l26.h b/include/linux/input/cs40l26.h
new file mode 100644
index 000000000000..5a878b9bc363
--- /dev/null
+++ b/include/linux/input/cs40l26.h
@@ -0,0 +1,532 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * CS40L26 Boosted Haptic Driver with Integrated DSP and
+ * Waveform Memory with Advanced Closed Loop Algorithms and LRA protection
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Fred Treven <fred.treven@cirrus.com>
+ */
+
+#ifndef __CS40L26_H__
+#define __CS40L26_H__
+
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/wmfw.h>
+
+#define CS40L26_FIRSTREG 0x0
+#define CS40L26_LASTREG 0x3C7DFE8
+
+#define CS40L26_DEVID 0x0
+#define CS40L26_REVID 0x4
+#define CS40L26_TEST_KEY_CTRL 0x40
+#define CS40L26_GLOBAL_ENABLES 0x2014
+#define CS40L26_ERROR_RELEASE 0x2034
+#define CS40L26_PWRMGT_CTL 0x2900
+#define CS40L26_PWRMGT_STS 0x290C
+#define CS40L26_REFCLK_INPUT 0x2C04
+#define CS40L26_PLL_REFCLK_DETECT_0 0x2C28
+#define CS40L26_VBST_CTL_1 0x3800
+#define CS40L26_VBST_CTL_2 0x3804
+#define CS40L26_BST_IPK_CTL 0x3808
+#define CS40L26_TEST_LBST 0x391C
+#define CS40L26_NGATE1_INPUT 0x4C60
+#define CS40L26_DAC_MSM_CONFIG 0x7400
+#define CS40L26_TST_DAC_MSM_CONFIG 0x7404
+#define CS40L26_IRQ1_STATUS 0x10004
+#define CS40L26_IRQ1_EINT_1 0x10010
+#define CS40L26_IRQ1_EINT_2 0x10014
+#define CS40L26_IRQ1_EINT_3 0x10018
+#define CS40L26_IRQ1_EINT_4 0x1001C
+#define CS40L26_IRQ1_EINT_5 0x10020
+#define CS40L26_IRQ1_STS_1 0x10090
+#define CS40L26_IRQ1_STS_2 0x10094
+#define CS40L26_IRQ1_STS_3 0x10098
+#define CS40L26_IRQ1_STS_4 0x1009C
+#define CS40L26_IRQ1_STS_5 0x100A0
+#define CS40L26_IRQ1_MASK_1 0x10110
+#define CS40L26_IRQ1_MASK_2 0x10114
+#define CS40L26_IRQ1_MASK_3 0x10118
+#define CS40L26_IRQ1_MASK_4 0x1011C
+#define CS40L26_IRQ1_MASK_5 0x10120
+#define CS40L26_MIXER_NGATE_CH1_CFG 0x12004
+#define CS40L26_DSP_MBOX_1 0x13000
+#define CS40L26_DSP_MBOX_2 0x13004
+#define CS40L26_DSP_MBOX_3 0x13008
+#define CS40L26_DSP_MBOX_4 0x1300C
+#define CS40L26_DSP_MBOX_5 0x13010
+#define CS40L26_DSP_MBOX_6 0x13014
+#define CS40L26_DSP_MBOX_7 0x13018
+#define CS40L26_DSP_MBOX_8 0x1301C
+#define CS40L26_DSP_VIRTUAL1_MBOX_1 0x13020
+#define CS40L26_DSP_VIRTUAL1_MBOX_2 0x13024
+#define CS40L26_DSP_VIRTUAL1_MBOX_3 0x13028
+#define CS40L26_DSP_VIRTUAL1_MBOX_4 0x1302C
+#define CS40L26_DSP_VIRTUAL1_MBOX_5 0x13030
+#define CS40L26_DSP_VIRTUAL1_MBOX_6 0x13034
+#define CS40L26_DSP_VIRTUAL1_MBOX_7 0x13038
+#define CS40L26_DSP_VIRTUAL1_MBOX_8 0x1303C
+#define CS40L26_DSP_VIRTUAL2_MBOX_1 0x13040
+#define CS40L26_DSP_VIRTUAL2_MBOX_2 0x13044
+#define CS40L26_DSP_VIRTUAL2_MBOX_3 0x13048
+#define CS40L26_DSP_VIRTUAL2_MBOX_4 0x1304C
+#define CS40L26_DSP_VIRTUAL2_MBOX_5 0x13050
+#define CS40L26_DSP_VIRTUAL2_MBOX_6 0x13054
+#define CS40L26_DSP_VIRTUAL2_MBOX_7 0x13058
+#define CS40L26_DSP_VIRTUAL2_MBOX_8 0x1305C
+#define CS40L26_OTP_MEM0 0x30000
+#define CS40L26_OTP_MEM31 0x3007C
+#define CS40L26_DSP1_XMEM_PACKED_0 0x2000000
+#define CS40L26_DSP1_XMEM_PACKED_6143 0x2005FFC
+#define CS40L26_DSP1_XROM_PACKED_0 0x2006000
+#define CS40L26_DSP1_XROM_PACKED_4604 0x200A7F0
+#define CS40L26_DSP1_XMEM_UNPACKED32_0 0x2400000
+#define CS40L26_DSP1_XMEM_UNPACKED32_4095 0x2403FFC
+#define CS40L26_DSP1_XROM_UNPACKED32_0 0x2404000
+#define CS40L26_DSP1_XROM_UNPACKED32_3070 0x2406FF8
+#define CS40L26_DSP1_SYS_INFO_ID 0x25E0000
+#define CS40L26_DSP1_XMEM_UNPACKED24_0 0x2800000
+#define CS40L26_DSP1_XMEM_UNPACKED24_8191 0x2807FFC
+#define CS40L26_DSP1_XROM_UNPACKED24_0 0x2808000
+#define CS40L26_DSP1_XROM_UNPACKED24_6141 0x280DFF4
+#define CS40L26_DSP1_CCM_CORE_CONTROL 0x2BC1000
+#define CS40L26_DSP1_YMEM_PACKED_0 0x2C00000
+#define CS40L26_DSP1_YMEM_PACKED_1532 0x2C017F0
+#define CS40L26_DSP1_YMEM_UNPACKED32_0 0x3000000
+#define CS40L26_DSP1_YMEM_UNPACKED32_1022 0x3000FF8
+#define CS40L26_DSP1_YMEM_UNPACKED24_0 0x3400000
+#define CS40L26_DSP1_YMEM_UNPACKED24_2045 0x3401FF4
+#define CS40L26_DSP1_PMEM_0 0x3800000
+#define CS40L26_DSP1_PMEM_5114 0x3804FE8
+#define CS40L26_DSP1_PROM_0 0x3C60000
+#define CS40L26_DSP1_PROM_30714 0x3C7DFE8
+
+/* Device */
+#define CS40L26_DEVID_A 0x40A260
+#define CS40L26_DEVID_B 0x40A26B
+#define CS40L26_DEVID_L27_A 0x40A270
+#define CS40L26_DEVID_L27_B 0x40A27B
+#define CS40L26_NUM_DEVS 4
+#define CS40L26_DEVID_MASK GENMASK(23, 0)
+#define CS40L26_REVID_A1 0xA1
+#define CS40L26_REVID_B0 0xB0
+#define CS40L26_REVID_MASK GENMASK(7, 0)
+
+#define CS40L26_MIN_RESET_PULSE_WIDTH_US 1500
+#define CS40L26_CP_READY_DELAY_US 6000
+
+#define CS40L26_DATA_SRC_DSP1TX4 0x35
+#define CS40L26_MIXER_NGATE_CH1_CFG_DEFAULT 0x00010003
+#define CS40L26_TST_DAC_MSM_CFG_DFLT_CHG_VAL_H16 0x1133
+
+#define CS40L26_SPK_DEFAULT_HIZ_MASK BIT(28)
+#define CS40L26_SPK_DEFAULT_HIZ_SHIFT 28
+
+#define CS40L26_DSP_CCM_CORE_KILL 0x00000080
+#define CS40L26_DSP_CCM_CORE_RESET 0x00000281
+
+#define CS40L26_MEM_RDY_MASK BIT(1)
+#define CS40L26_MEM_RDY_SHIFT 1
+
+/* Errata */
+#define CS40L26_DISABLE_EXPLORATORY_MODE 0x014DC080
+
+/* Boost Converter Control */
+#define CS40L26_GLOBAL_EN_MASK BIT(0)
+
+#define CS40L26_BST_IPK_MILLIAMP_MAX 4800
+#define CS40L26_BST_IPK_MILLIAMP_MIN 1600
+#define CS40L26_BST_IPK_MILLIAMP_DEFAULT 0x4A
+#define CS40L26_BST_IPK_MILLIAMP_STEP 50
+#define CS40L26_BST_IPK_CTL_RESERVED 16
+
+#define CS40L26_VBST_MILLIVOLT_MIN 2500
+#define CS40L26_VBST_MILLIVOLT_MAX 11000
+#define CS40L26_VBST_MILLIVOLT_STEP 50
+
+#define CS40L26_BST_CTL_VP 0x00
+#define CS40L26_BST_CTL_MASK GENMASK(7, 0)
+#define CS40L26_BST_CTL_SHIFT 0
+#define CS40L26_BST_CTL_SEL_MASK GENMASK(1, 0)
+#define CS40L26_BST_CTL_SEL_FIXED 0x0
+#define CS40L26_BST_CTL_SEL_CLASS_H 0x1
+#define CS40L26_BST_CTL_LIM_EN_MASK BIT(2)
+#define CS40L26_BST_CTL_LIM_EN_SHIFT 2
+
+#define CS40L26_BST_TIME_US_MIN 10000
+#define CS40L26_BST_TIME_US_MAX 10100
+
+/* Phase Locked Loop */
+#define CS40L26_PLL_OL 1
+#define CS40L26_PLL_CL 0
+
+#define CS40L26_PLL_REFCLK_LOOP_MASK BIT(11)
+#define CS40L26_PLL_REFCLK_LOOP_SHIFT 11
+
+/* Haptic Triggering */
+
+#define CS40L26_RAM_INDEX_START 0x01000000
+#define CS40L26_RAM_INDEX_END 0x0100007F
+
+#define CS40L26_ROM_INDEX_START 0x01800000
+#define CS40L26_ROM_INDEX_END 0x01800026
+
+#define CS40L26_BUZZGEN_INDEX_START 0x01800080
+#define CS40L26_BUZZGEN_INDEX_END 0x01800085
+
+#define CS40L26_BUZZGEN_NUM_CONFIGS 5
+
+#define CS40L26_BUZZGEN_PERIOD_MS_MAX 10
+#define CS40L26_BUZZGEN_PERIOD_MS_MIN 4
+
+#define CS40L26_BUZZGEN_DURATION_OFFSET 8
+#define CS40L26_BUZZGEN_DURATION_DIV_STEP 4
+
+#define CS40L26_BUZZGEN_LEVEL_MIN 0x00
+#define CS40L26_BUZZGEN_LEVEL_MAX 0xFF
+
+#define CS40L26_VIBEGEN_MAX_TIME_MS 10000
+
+#define CS40L26_NUM_ATTEN_LUT_VALUES 101
+
+/* GPIO */
+#define CS40L26_EVENT_MAP_GPI_DISABLE 0x1FF
+
+#define CS40L26_BTN_INDEX_MASK GENMASK(7, 0)
+#define CS40L26_BTN_BUZZ_MASK BIT(7)
+#define CS40L26_BTN_BUZZ_SHIFT 7
+#define CS40L26_BTN_BANK_MASK BIT(8)
+#define CS40L26_BTN_BANK_SHIFT 8
+#define CS40L26_BTN_NUM_MASK GENMASK(14, 12)
+#define CS40L26_BTN_NUM_SHIFT 12
+#define CS40L26_BTN_EDGE_MASK BIT(15)
+#define CS40L26_BTN_EDGE_SHIFT 15
+
+/* A1 ROM Controls */
+#define CS40L26_A1_PM_CUR_STATE_STATIC_REG 0x02800370
+#define CS40L26_A1_PM_STATE_LOCKS_STATIC_REG 0x02800378
+#define CS40L26_A1_PM_STATE_LOCKS3_STATIC_REG (CS40L26_A1_PM_STATE_LOCKS_STATIC_REG + \
+ CS40L26_DSP_LOCK3_OFFSET)
+#define CS40L26_A1_PM_TIMEOUT_TICKS_STATIC_REG 0x02800350
+#define CS40L26_A1_PM_STDBY_TICKS_STATIC_REG 0x02800360
+#define CS40L26_A1_PM_ACTIVE_TICKS_STATIC_REG 0x02800368
+#define CS40L26_A1_DSP_HALO_STATE_REG 0x02800fa8
+#define CS40L26_A1_DSP_REQ_ACTIVE_REG 0x02800c08
+#define CS40L26_A1_EVENT_MAP_1 0x02806FC4
+#define CS40L26_A1_EVENT_MAP_2 0x02806FC8
+
+/* Interrupts */
+#define CS40L26_IRQ_STATUS_ASSERT 0x1
+
+#define CS40L26_IRQ_EINT1_ALL_MASK 0xFFDC7FFF
+#define CS40L26_IRQ_EINT2_ALL_MASK 0x07DE0400
+
+/* Firmware Handling */
+#define CS40L26_FW_ID 0x1800D4
+#define CS40L26_FW_ID_VERSION_MIN 0x070237
+
+/* Algorithms */
+#define CS40L26_BUZZGEN_ALGO_ID 0x0004F202
+#define CS40L26_EVENT_HANDLER_ALGO_ID 0x0004F200
+#define CS40L26_GPIO_ALGO_ID 0x0004F201
+#define CS40L26_MAILBOX_ALGO_ID 0x0004F203
+#define CS40L26_PM_ALGO_ID 0x0004F206
+#define CS40L26_VIBEGEN_ALGO_ID 0x000400BD
+#define CS40L26_EXT_ALGO_ID 0x0004013C
+
+/* DSP */
+#define CS40L26_DSP_STATE_HIBERNATE 0
+#define CS40L26_DSP_STATE_SHUTDOWN 1
+#define CS40L26_DSP_STATE_STANDBY 2
+#define CS40L26_DSP_STATE_ACTIVE 3
+#define CS40L26_DSP_STATE_NONE 4
+#define CS40L26_DSP_STATE_MASK GENMASK(7, 0)
+
+#define CS40L26_DSP_LOCK3_OFFSET 8
+#define CS40L26_DSP_LOCK3_MASK BIT(1)
+#define CS40L26_DSP_PM_ACTIVE BIT(0)
+
+#define CS40L26_DSP_HALO_STATE_RUN 2
+
+#define CS40L26_DSP_CTRL_BASE 0x2B80000
+#define CS40L26_DSP_NUM_REGIONS 5
+
+#define CS40L26_DSP_TIMEOUT_US_MIN 1000
+#define CS40L26_DSP_TIMEOUT_US_MAX 1100
+#define CS40L26_DSP_TIMEOUT_COUNT 100
+
+#define CS40L26_PM_LOCKS_TIMEOUT_COUNT 10
+
+/* Mailbox Controls */
+#define CS40L26_DSP_MBOX_RESET 0x0
+
+#define CS40L26_DSP_MBOX_CMD_HIBER 0x02000001
+#define CS40L26_DSP_MBOX_CMD_WAKEUP 0x02000002
+#define CS40L26_DSP_MBOX_CMD_PREVENT_HIBER 0x02000003
+#define CS40L26_DSP_MBOX_CMD_ALLOW_HIBER 0x02000004
+#define CS40L26_DSP_MBOX_CMD_SHUTDOWN 0x02000005
+#define CS40L26_DSP_MBOX_PM_CMD_BASE CS40L26_DSP_MBOX_CMD_HIBER
+
+#define CS40L26_DSP_MBOX_CMD_INDEX_MASK GENMASK(28, 24)
+#define CS40L26_DSP_MBOX_CMD_INDEX_SHIFT 24
+#define CS40L26_DSP_MBOX_CMD_PAYLOAD_MASK GENMASK(23, 0)
+
+#define CS40L26_DSP_MBOX_CMD_STOP_PLAYBACK 0x05000000
+
+#define CS40L26_DSP_MBOX_COMPLETE_MBOX 0x01000000
+#define CS40L26_DSP_MBOX_COMPLETE_GPIO 0x01000001
+#define CS40L26_DSP_MBOX_TRIGGER_CP 0x01000010
+#define CS40L26_DSP_MBOX_TRIGGER_GPIO 0x01000011
+#define CS40L26_DSP_MBOX_PM_AWAKE 0x02000002
+#define CS40L26_DSP_MBOX_SYS_ACK 0x0A000000
+#define CS40L26_DSP_MBOX_PANIC 0x0C000000
+
+#define CS40L26_DSP_MBOX_QUEUE_SIZE_BYTES 16
+
+/* Power Management */
+#define CS40L26_PM_STDBY_TIMEOUT_LOWER_OFFSET 16
+#define CS40L26_PM_STDBY_TIMEOUT_UPPER_OFFSET 20
+#define CS40L26_PM_STDBY_TIMEOUT_MS_DEFAULT 100
+#define CS40L26_PM_ACTIVE_TIMEOUT_LOWER_OFFSET 24
+#define CS40L26_PM_ACTIVE_TIMEOUT_UPPER_OFFSET 28
+#define CS40L26_PM_ACTIVE_TIMEOUT_MS_DEFAULT 250
+#define CS40L26_PM_TIMEOUT_TICKS_LOWER_MASK GENMASK(23, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_MASK GENMASK(7, 0)
+#define CS40L26_PM_TIMEOUT_TICKS_UPPER_SHIFT 24
+#define CS40L26_PM_TICKS_PER_MS 32
+
+#define CS40L26_AUTOSUSPEND_DELAY_MS 2000
+
+#define CS40L26_PSEQ_MAX_WORDS 129
+#define CS40L26_PSEQ_NUM_OPS 8
+#define CS40L26_PSEQ_OP_MASK GENMASK(23, 16)
+#define CS40L26_PSEQ_OP_SHIFT 16
+#define CS40L26_PSEQ_OP_WRITE_FULL 0x00
+#define CS40L26_PSEQ_OP_WRITE_FULL_WORDS 3
+#define CS40L26_PSEQ_OP_WRITE_ADDR8 0x02
+#define CS40L26_PSEQ_OP_WRITE_L16 0x04
+#define CS40L26_PSEQ_OP_WRITE_H16 0x05
+#define CS40L26_PSEQ_OP_WRITE_X16_WORDS 2
+#define CS40L26_PSEQ_OP_END 0xFF
+#define CS40L26_PSEQ_OP_END_WORDS 1
+#define CS40L26_PSEQ_INVALID_ADDR 0xFF000000
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_SHIFT 8
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_SHIFT 16
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_ADDR_MASK GENMASK(15, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_ADDR_MASK GENMASK(31, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_SHIFT 24
+#define CS40L26_PSEQ_WRITE_FULL_LOWER_DATA_MASK GENMASK(23, 0)
+#define CS40L26_PSEQ_WRITE_FULL_UPPER_DATA_MASK GENMASK(31, 24)
+#define CS40L26_PSEQ_WRITE_FULL_OP_MASK GENMASK(31, 8)
+#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_SHIFT 16
+#define CS40L26_PSEQ_WRITE_X16_LOWER_ADDR_MASK GENMASK(7, 0)
+#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_SHIFT 8
+#define CS40L26_PSEQ_WRITE_X16_UPPER_ADDR_MASK GENMASK(23, 8)
+#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_SHIFT 0
+#define CS40L26_PSEQ_WRITE_X16_UPPER_DATA_MASK GENMASK(31, 0)
+#define CS40L26_PSEQ_WRITE_X16_OP_MASK GENMASK(23, 16)
+
+/* Wake Sources */
+#define CS40L26_WKSRC_STS_MASK GENMASK(9, 4)
+#define CS40L26_WKSRC_STS_SHIFT 4
+#define CS40L26_WKSRC_STS_EN BIT(7)
+#define CS40L26_WKSRC_GPIO_POL_MASK GENMASK(3, 0)
+
+#define CS40L26_IRQ1_WKSRC_MASK GENMASK(14, 9)
+#define CS40L26_IRQ1_WKSRC_SHIFT 9
+#define CS40L26_IRQ1_WKSRC_GPIO_MASK GENMASK(3, 0)
+
+/* enums */
+enum cs40l26_power_supply {
+ CS40L26_VP_SUPPLY,
+ CS40L26_VA_SUPPLY,
+ CS40L26_NUM_SUPPLIES,
+};
+
+enum cs40l26_wvfrm_bank {
+ CS40L26_WVFRM_BANK_RAM,
+ CS40L26_WVFRM_BANK_ROM,
+ CS40L26_WVFRM_BANK_OWT,
+ CS40L26_WVFRM_BANK_BUZ,
+ CS40L26_WVFRM_BANK_NUM,
+};
+
+enum cs40l26_gpio_map {
+ CS40L26_GPIO_MAP_A_PRESS,
+ CS40L26_GPIO_MAP_A_RELEASE,
+ CS40L26_GPIO_MAP_NUM_AVAILABLE,
+ CS40L26_GPIO_MAP_INVALID,
+};
+
+enum cs40l26_err_rls {
+ CS40L26_RSRVD_ERR_RLS, /* 0 */
+ CS40L26_AMP_SHORT_ERR_RLS, /* 1 */
+ CS40L26_BST_SHORT_ERR_RLS, /* 2 */
+ CS40L26_BST_OVP_ERR_RLS, /* 3 */
+ CS40L26_BST_UVP_ERR_RLS, /* 4 */
+ CS40L26_TEMP_WARN_ERR_RLS, /* 5 */
+ CS40L26_TEMP_ERR_RLS, /* 6 */
+};
+
+enum cs40l26_irq1 {
+ CS40L26_IRQ1_GPIO1_RISE,/* 0 */
+ CS40L26_IRQ1_GPIO1_FALL,/* 1 */
+ CS40L26_IRQ1_GPIO2_RISE,/* 2 */
+ CS40L26_IRQ1_GPIO2_FALL,/* 3 */
+ CS40L26_IRQ1_GPIO3_RISE,/* 4 */
+ CS40L26_IRQ1_GPIO3_FALL,/* 5 */
+ CS40L26_IRQ1_GPIO4_RISE,/* 6 */
+ CS40L26_IRQ1_GPIO4_FALL,/* 7 */
+ CS40L26_IRQ1_WKSRC_STS_ANY,/* 8 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO1,/* 9 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO2,/* 10 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO3,/* 11 */
+ CS40L26_IRQ1_WKSRC_STS_GPIO4,/* 12 */
+ CS40L26_IRQ1_WKSRC_STS_SPI,/* 13 */
+ CS40L26_IRQ1_WKSRC_STS_I2C,/* 14 */
+ CS40L26_IRQ1_GLOBAL_EN_ASSERT,/* 15 */
+ CS40L26_IRQ1_PDN_DONE,/* 16 */
+ CS40L26_IRQ1_PUP_DONE,/* 17 */
+ CS40L26_IRQ1_BST_OVP_FLAG_RISE,/* 18 */
+ CS40L26_IRQ1_BST_OVP_FLAG_FALL,/* 19 */
+ CS40L26_IRQ1_BST_OVP_ERR,/* 20 */
+ CS40L26_IRQ1_BST_DCM_UVP_ERR,/* 21 */
+ CS40L26_IRQ1_BST_SHORT_ERR,/* 22 */
+ CS40L26_IRQ1_BST_IPK_FLAG,/* 23 */
+ CS40L26_IRQ1_TEMP_WARN_RISE,/* 24 */
+ CS40L26_IRQ1_TEMP_WARN_FALL,/* 25 */
+ CS40L26_IRQ1_TEMP_ERR,/* 26 */
+ CS40L26_IRQ1_AMP_ERR,/* 27 */
+ CS40L26_IRQ1_DC_WATCHDOG_RISE,/* 28 */
+ CS40L26_IRQ1_DC_WATCHDOG_FALL,/* 29 */
+ CS40L26_IRQ1_VIRTUAL1_MBOX_WR,/* 30 */
+ CS40L26_IRQ1_VIRTUAL2_MBOX_WR,/* 31 */
+ CS40L26_IRQ1_NUM_IRQS,
+};
+
+enum cs40l26_irq2 {
+ CS40L26_IRQ2_PLL_LOCK,/* 0 */
+ CS40L26_IRQ2_PLL_PHASE_LOCK,/* 1 */
+ CS40L26_IRQ2_PLL_FREQ_LOCK,/* 2 */
+ CS40L26_IRQ2_PLL_UNLOCK_RISE,/* 3 */
+ CS40L26_IRQ2_PLL_UNLOCK_FALL,/* 4 */
+ CS40L26_IRQ2_PLL_READY,/* 5 */
+ CS40L26_IRQ2_PLL_REFCLK_PRESENT,/* 6 */
+ CS40L26_IRQ2_REFCLK_MISSING_RISE,/* 7 */
+ CS40L26_IRQ2_REFCLK_MISSING_FALL,/* 8 */
+ CS40L26_IRQ2_RESERVED,/* 9 */
+ CS40L26_IRQ2_ASP_RXSLOT_CFG_ERR,/* 10 */
+ CS40L26_IRQ2_AUX_NG_CH1_ENTRY,/* 11 */
+ CS40L26_IRQ2_AUX_NG_CH1_EXIT,/* 12 */
+ CS40L26_IRQ2_AUX_NG_CH2_ENTRY,/* 13 */
+ CS40L26_IRQ2_AUX_NG_CH2_EXIT,/* 14 */
+ CS40L26_IRQ2_AMP_NG_ON_RISE,/* 15 */
+ CS40L26_IRQ2_AMP_NG_ON_FALL,/* 16 */
+ CS40L26_IRQ2_VPBR_FLAG,/* 17 */
+ CS40L26_IRQ2_VPBR_ATT_CLR,/* 18 */
+ CS40L26_IRQ2_VBBR_FLAG,/* 19 */
+ CS40L26_IRQ2_VBBR_ATT_CLR,/* 20 */
+ CS40L26_IRQ2_RESERVED2,/* 21 */
+ CS40L26_IRQ2_I2C_NACK_ERR,/* 22 */
+ CS40L26_IRQ2_VPMON_CLIPPED,/* 23 */
+ CS40L26_IRQ2_VBSTMON_CLIPPED,/* 24 */
+ CS40L26_IRQ2_VMON_CLIPPED,/* 25 */
+ CS40L26_IRQ2_IMON_CLIPPED,/* 26 */
+ CS40L26_IRQ2_NUM_IRQS,
+};
+
+/* structs */
+struct cs40l26_platform_data {
+ u32 bst_ipk_ua;
+ u32 vbst_uv;
+ bool exploratory_mode_enabled;
+};
+
+struct cs40l26_pseq_op {
+ u8 size;
+ u16 offset; /* offset in bytes from pseq_base */
+ u8 operation;
+ u32 words[3];
+ struct list_head list;
+};
+
+struct cs40l26_pseq_params {
+ int num_op_words;
+ unsigned int op_mask;
+ unsigned int low_addr_shift;
+ unsigned int low_addr_mask;
+ unsigned int low_data_mask;
+ unsigned int up_addr_shift;
+ unsigned int up_addr_mask;
+ unsigned int up_data_mask;
+ unsigned int up_data_shift;
+};
+
+struct cs40l26_buzzgen_config {
+ char *duration_name;
+ char *freq_name;
+ char *level_name;
+ int effect_id;
+};
+
+struct cs40l26_uploaded_effect {
+ int id;
+ u32 trigger_index;
+ u16 wvfrm_bank;
+ enum cs40l26_gpio_map mapping;
+ struct list_head list;
+};
+
+struct cs40l26_private {
+ struct device *dev;
+ struct regmap *regmap;
+ struct cs_dsp dsp;
+ int irq;
+ struct mutex lock;
+ struct gpio_desc *reset_gpio;
+ struct cs40l26_platform_data pdata;
+ u32 devid;
+ u8 revid;
+ struct input_dev *input;
+ struct ff_effect *erase_effect;
+ struct ff_effect *trigger_effect;
+ struct ff_effect upload_effect;
+ s16 *raw_custom_data;
+ struct list_head effect_head;
+ struct work_struct erase_work;
+ struct work_struct set_gain_work;
+ struct work_struct upload_work;
+ struct work_struct vibe_start_work;
+ struct work_struct vibe_stop_work;
+ struct workqueue_struct *vibe_workqueue;
+ struct completion erase;
+ int erase_ret;
+ int upload_ret;
+ u16 gain_pct;
+ u8 wksrc_sts;
+ u8 last_wksrc_pol;
+ int pseq_num_ops;
+ struct list_head pseq_op_head;
+ u32 gpio_press_default;
+ u32 gpio_release_default;
+ u32 mbox_q_base;
+ u32 mbox_q_len;
+ u32 mbox_q_last;
+};
+
+/* Exports */
+int cs40l26_probe(struct cs40l26_private *cs40l26, struct cs40l26_platform_data *pdata);
+int cs40l26_remove(struct cs40l26_private *cs40l26);
+
+extern const struct regmap_config cs40l26_regmap;
+extern const struct dev_pm_ops cs40l26_pm_ops;
+extern const u32 cs40l26_atten_lut_q21_2[CS40L26_NUM_ATTEN_LUT_VALUES];
+
+#endif /* __CS40L26_H__ */
--
2.7.4
^ permalink raw reply related
* Re: [PATCH v2] USB: core: Fix docs warning caused by wireless_status feature
From: Benjamin Tissoires @ 2023-04-06 15:17 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Bastien Nocera, linux-usb, linux-input, Alan Stern,
Filipe Laíns, Nestor Lopez Casado, Stephen Rothwell
In-Reply-To: <2023040541-decipher-chowtime-42a3@gregkh>
On Wed, Apr 5, 2023 at 8:06 PM Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Wed, Apr 05, 2023 at 08:03:16PM +0200, Bastien Nocera wrote:
> > On Wed, 2023-04-05 at 19:13 +0200, Greg Kroah-Hartman wrote:
> > > On Wed, Apr 05, 2023 at 11:27:54AM +0200, Bastien Nocera wrote:
> > > > Fix wrongly named 'dev' parameter in doc block, should have been
> > > > iface:
> > > > drivers/usb/core/message.c:1939: warning: Function parameter or
> > > > member 'iface' not described in 'usb_set_wireless_status'
> > > > drivers/usb/core/message.c:1939: warning: Excess function parameter
> > > > 'dev' description in 'usb_set_wireless_status'
> > > >
> > > > And fix missing struct member doc in kernel API, and reorder to
> > > > match struct:
> > > > include/linux/usb.h:270: warning: Function parameter or member
> > > > 'wireless_status_work' not described in 'usb_interface'
> > > >
> > > > Reported-by: Stephen Rothwell <sfr@canb.auug.org.au>
> > > > Link:
> > > > https://lore.kernel.org/linux-next/20230405114807.5a57bf46@canb.auug.org.au/T/#t
> > > > Fixes: 0a4db185f078 ("USB: core: Add API to change the
> > > > wireless_status")
> > >
> > > I do not see that git commit id anywhere, where is it from? What
> > > tree?
> > >
> > > Ah, input tree, not much I can do there...
> >
> > Yes, it's from the hid tree.
> >
> > Benjamin is waiting on either Alan or yourself ack'ing the changes
> > before pushing it through the hid tree, to avoid stepping on somebody
> > else's toes.
> >
> > The patch seems to fix the warnings in my local tests, let me know if
> > you have any comments about the wording.
>
> Nope:
>
> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
>
Thanks!
I have fixed a small checkpatch warning about using space before tab
and applied as the b4 thank you notice should have already mentioned.
Cheers,
Benjamin
^ permalink raw reply
* Re: [PATCH v2] USB: core: Fix docs warning caused by wireless_status feature
From: Benjamin Tissoires @ 2023-04-06 15:15 UTC (permalink / raw)
To: linux-usb, linux-input, Bastien Nocera
Cc: Greg Kroah-Hartman, Alan Stern, Filipe Laíns,
Nestor Lopez Casado, Stephen Rothwell
In-Reply-To: <20230405092754.36579-1-hadess@hadess.net>
On Wed, 05 Apr 2023 11:27:54 +0200, Bastien Nocera wrote:
> Fix wrongly named 'dev' parameter in doc block, should have been iface:
> drivers/usb/core/message.c:1939: warning: Function parameter or member 'iface' not described in 'usb_set_wireless_status'
> drivers/usb/core/message.c:1939: warning: Excess function parameter 'dev' description in 'usb_set_wireless_status'
>
> And fix missing struct member doc in kernel API, and reorder to
> match struct:
> include/linux/usb.h:270: warning: Function parameter or member 'wireless_status_work' not described in 'usb_interface'
>
> [...]
Applied to https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git (for-6.4/logitech-hidpp), thanks!
[1/1] USB: core: Fix docs warning caused by wireless_status feature
https://git.kernel.org/hid/hid/c/539adfedbd2d
Cheers,
--
Benjamin Tissoires <benjamin.tissoires@redhat.com>
^ permalink raw reply
* Re: [PATCH v3 2/2] platform/x86: Add driver for Yoga Tablet Mode switch
From: Andrew Kallmeyer @ 2023-04-06 15:03 UTC (permalink / raw)
To: Hans de Goede
Cc: platform-driver-x86, Gergo Koteles, Ike Panhc, linux-input,
Armin Wolf, Barnabás Pőcze
In-Reply-To: <a24153ed-fafc-0e73-1db1-35168464b973@redhat.com>
On Thu, Apr 6, 2023 at 3:34 AM Hans de Goede <hdegoede@redhat.com> wrote:
>
> Hi Andrew,
>
> Thank you for your patch. I have merged this now,
> with tiny code cleanups squashed in.
Thanks for all the help Hans, and everyone else too! I thought I would
need to send another patch version tonight, but I'm glad to hear this
is getting merged!
Thanks,
Andrew
^ permalink raw reply
* [PATCH AUTOSEL 5.4 2/9] Input: i8042 - add quirk for Fujitsu Lifebook A574/H
From: Sasha Levin @ 2023-04-06 11:33 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Jonathan Denose, Jonathan Denose, Hans de Goede, Dmitry Torokhov,
Sasha Levin, wse, mkorpershoek, chenhuacai, wsa+renesas,
linux-input
In-Reply-To: <20230406113337.648916-1-sashal@kernel.org>
From: Jonathan Denose <jdenose@chromium.org>
[ Upstream commit f5bad62f9107b701a6def7cac1f5f65862219b83 ]
Fujitsu Lifebook A574/H requires the nomux option to properly
probe the touchpad, especially when waking from sleep.
Signed-off-by: Jonathan Denose <jdenose@google.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20230303152623.45859-1-jdenose@google.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/input/serio/i8042-x86ia64io.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 6b2e88da30766..92fb2f72511e8 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -601,6 +601,14 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = {
},
.driver_data = (void *)(SERIO_QUIRK_NOMUX)
},
+ {
+ /* Fujitsu Lifebook A574/H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVA0501PZ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
{
/* Gigabyte M912 */
.matches = {
--
2.39.2
^ permalink raw reply related
* [PATCH AUTOSEL 4.19 2/8] Input: i8042 - add quirk for Fujitsu Lifebook A574/H
From: Sasha Levin @ 2023-04-06 11:33 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Jonathan Denose, Jonathan Denose, Hans de Goede, Dmitry Torokhov,
Sasha Levin, wse, wsa+renesas, mkorpershoek, chenhuacai,
linux-input
In-Reply-To: <20230406113400.649038-1-sashal@kernel.org>
From: Jonathan Denose <jdenose@chromium.org>
[ Upstream commit f5bad62f9107b701a6def7cac1f5f65862219b83 ]
Fujitsu Lifebook A574/H requires the nomux option to properly
probe the touchpad, especially when waking from sleep.
Signed-off-by: Jonathan Denose <jdenose@google.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20230303152623.45859-1-jdenose@google.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/input/serio/i8042-x86ia64io.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index b2ab20c16cc77..da2bf8259330e 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -605,6 +605,14 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = {
},
.driver_data = (void *)(SERIO_QUIRK_NOMUX)
},
+ {
+ /* Fujitsu Lifebook A574/H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVA0501PZ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
{
/* Gigabyte M912 */
.matches = {
--
2.39.2
^ permalink raw reply related
* [PATCH AUTOSEL 5.15 02/11] Input: i8042 - add quirk for Fujitsu Lifebook A574/H
From: Sasha Levin @ 2023-04-06 11:32 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Jonathan Denose, Jonathan Denose, Hans de Goede, Dmitry Torokhov,
Sasha Levin, wse, chenhuacai, wsa+renesas, linux-input
In-Reply-To: <20230406113250.648634-1-sashal@kernel.org>
From: Jonathan Denose <jdenose@chromium.org>
[ Upstream commit f5bad62f9107b701a6def7cac1f5f65862219b83 ]
Fujitsu Lifebook A574/H requires the nomux option to properly
probe the touchpad, especially when waking from sleep.
Signed-off-by: Jonathan Denose <jdenose@google.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20230303152623.45859-1-jdenose@google.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/input/serio/i8042-x86ia64io.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 239c777f8271c..339e765bcf5ae 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -601,6 +601,14 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = {
},
.driver_data = (void *)(SERIO_QUIRK_NOMUX)
},
+ {
+ /* Fujitsu Lifebook A574/H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVA0501PZ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
{
/* Gigabyte M912 */
.matches = {
--
2.39.2
^ permalink raw reply related
* [PATCH AUTOSEL 5.10 2/9] Input: i8042 - add quirk for Fujitsu Lifebook A574/H
From: Sasha Levin @ 2023-04-06 11:33 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Jonathan Denose, Jonathan Denose, Hans de Goede, Dmitry Torokhov,
Sasha Levin, wse, mkorpershoek, wsa+renesas, chenhuacai,
linux-input
In-Reply-To: <20230406113315.648777-1-sashal@kernel.org>
From: Jonathan Denose <jdenose@chromium.org>
[ Upstream commit f5bad62f9107b701a6def7cac1f5f65862219b83 ]
Fujitsu Lifebook A574/H requires the nomux option to properly
probe the touchpad, especially when waking from sleep.
Signed-off-by: Jonathan Denose <jdenose@google.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20230303152623.45859-1-jdenose@google.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/input/serio/i8042-x86ia64io.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 65c0081838e3d..9dcdf21c50bdc 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -601,6 +601,14 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = {
},
.driver_data = (void *)(SERIO_QUIRK_NOMUX)
},
+ {
+ /* Fujitsu Lifebook A574/H */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "FMVA0501PZ"),
+ },
+ .driver_data = (void *)(SERIO_QUIRK_NOMUX)
+ },
{
/* Gigabyte M912 */
.matches = {
--
2.39.2
^ permalink raw reply related
* Re: [PATCH v3 2/2] platform/x86: Add driver for Yoga Tablet Mode switch
From: Hans de Goede @ 2023-04-06 10:34 UTC (permalink / raw)
To: Andrew Kallmeyer, platform-driver-x86
Cc: Gergo Koteles, Ike Panhc, linux-input, Armin Wolf,
Barnabás Pőcze
In-Reply-To: <20230329014559.44494-3-kallmeyeras@gmail.com>
Hi Andrew,
Thank you for your patch. I have merged this now,
with tiny code cleanups squashed in.
I've described the code-cleanups below (inline) :
On 3/29/23 03:45, Andrew Kallmeyer wrote:
> From: Gergo Koteles <soyer@irl.hu>
>
> This WMI driver for the tablet mode control switch for Lenovo Yoga
> notebooks was originally written by Gergo Koteles. The mode is mapped to
> a SW_TABLET_MODE switch capable input device.
>
> Andrew followed the suggestions that were posted in reply to Gergo's RFC
> patch, and on the v1 & v2 versions of this patch to follow-up and get it
> merged.
>
> Changes from Gergo's RFC:
>
> - Refactored obtaining a reference to the EC ACPI device needed for the
> quirk implementation as suggested by Hans de Goede
> - Applied small fixes and switched to always registering handles with
> the driver for automatic cleanup as suggested by Barnabás Pőcze.
> - Merged the lenovo_ymc_trigger_ec function with the
> ideapad_trigger_ymc_next_read function since it was no longer
> external.
> - Added the word "Tablet" to the driver description to hopefully make
> it more clear.
> - Fixed the LENOVO_YMC_QUERY_METHOD ID and the name string for the EC
> APCI device trigged for the quirk
> - Triggered the input event on probe so that the initial tablet mode
> state when the driver is loaded is reported to userspace as suggested
> by Armin Wolf.
> - Restricted the permissions of the ec_trigger parameter as suggested
> by Armin Wolf. Also updated the description.
>
> We have tested this on the Yoga 7 14AIL7 for the non-quirk path and on
> the Yoga 7 14ARB7 which has the firmware bug that requires triggering
> the embedded controller to send the mode change events. This workaround
> is also used by the Windows drivers.
>
> According to reports at https://github.com/lukas-w/yoga-usage-mode,
> which uses the same WMI devices, the following models should also work:
> Yoga C940, Ideapad flex 14API, Yoga 9 14IAP7, Yoga 7 14ARB7, etc.
>
> Signed-off-by: Gergo Koteles <soyer@irl.hu>
> Co-developed-by: Andrew Kallmeyer <kallmeyeras@gmail.com>
> Signed-off-by: Andrew Kallmeyer <kallmeyeras@gmail.com>
> Link: https://lore.kernel.org/r/20221004214332.35934-1-soyer@irl.hu/
> Link: https://lore.kernel.org/r/20230310041726.217447-1-kallmeyeras@gmail.com/
> Link: https://lore.kernel.org/r/20230323025200.5462-1-kallmeyeras@gmail.com/
> ---
> drivers/platform/x86/Kconfig | 10 ++
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/ideapad-laptop.h | 1 +
> drivers/platform/x86/lenovo-ymc.c | 186 ++++++++++++++++++++++++++
> 4 files changed, 198 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-ymc.c
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 5692385e2..858be0c65 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -470,6 +470,16 @@ config IDEAPAD_LAPTOP
> This is a driver for Lenovo IdeaPad netbooks contains drivers for
> rfkill switch, hotkey, fan control and backlight control.
>
> +config LENOVO_YMC
> + tristate "Lenovo Yoga Tablet Mode Control"
> + depends on ACPI_WMI
> + depends on INPUT
> + depends on IDEAPAD_LAPTOP
I don't believe that we need any symbols from IDEAPAD_LAPTOP with
the new static inline helpers, so I've dropped this depends on.
> + select INPUT_SPARSEKMAP
> + help
> + This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
> + events for Lenovo Yoga notebooks.
> +
> config SENSORS_HDAPS
> tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
> depends on INPUT
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 1d3d1b025..10054cdea 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -63,6 +63,7 @@ obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
> # IBM Thinkpad and Lenovo
> obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
> obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
> +obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
> obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
> obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> diff --git a/drivers/platform/x86/ideapad-laptop.h b/drivers/platform/x86/ideapad-laptop.h
> index 7dd8ce027..2564cb1cd 100644
> --- a/drivers/platform/x86/ideapad-laptop.h
> +++ b/drivers/platform/x86/ideapad-laptop.h
> @@ -35,6 +35,7 @@ enum {
> VPCCMD_W_FAN,
> VPCCMD_R_RF,
> VPCCMD_W_RF,
> + VPCCMD_W_YMC = 0x2A,
> VPCCMD_R_FAN = 0x2B,
> VPCCMD_R_SPECIAL_BUTTONS = 0x31,
> VPCCMD_W_BL_POWER = 0x33,
> diff --git a/drivers/platform/x86/lenovo-ymc.c b/drivers/platform/x86/lenovo-ymc.c
> new file mode 100644
> index 000000000..5e520a764
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-ymc.c
> @@ -0,0 +1,186 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * lenovo-ymc.c - Lenovo Yoga Mode Control driver
> + *
> + * Copyright © 2022 Gergo Koteles <soyer@irl.hu>
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.h>
> +#include <linux/wmi.h>
> +#include "ideapad-laptop.h"
> +
> +#define LENOVO_YMC_EVENT_GUID "06129D99-6083-4164-81AD-F092F9D773A6"
> +#define LENOVO_YMC_QUERY_GUID "09B0EE6E-C3FD-4243-8DA1-7911FF80BB8C"
> +
> +#define LENOVO_YMC_QUERY_INSTANCE 0
> +#define LENOVO_YMC_QUERY_METHOD 0x01
> +
> +static bool ec_trigger __read_mostly;
> +module_param(ec_trigger, bool, 0444);
> +MODULE_PARM_DESC(ec_trigger, "Enable EC triggering work-around to force emitting tablet mode events");
> +
> +static const struct dmi_system_id ec_trigger_quirk_dmi_table[] = {
> + {
> + // Lenovo Yoga 7 14ARB7
Generally speaking we prefer C-style /* */ comments in pdx86,
so I've converted all comments to this style.
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "82QF"),
> + },
> + },
> + { },
Since this is a terminator, it should not have a trailing comma (removed).
> +};
> +
> +struct lenovo_ymc_private {
> + struct input_dev *input_dev;
> + struct acpi_device *ec_acpi_dev;
> +};
> +
> +static void lenovo_ymc_trigger_ec(struct wmi_device *wdev, struct lenovo_ymc_private *priv)
> +{
> + int err;
There must be an empty line between declarations and
the first statement (added).
> + if (!priv->ec_acpi_dev)
> + return;
> + err = write_ec_cmd(priv->ec_acpi_dev->handle, VPCCMD_W_YMC, 1);
> + if (err)
> + dev_warn(&wdev->dev, "Could not write YMC: %d\n", err);
> +}
> +
> +static const struct key_entry lenovo_ymc_keymap[] = {
> + // Laptop
> + { KE_SW, 0x01, { .sw = { SW_TABLET_MODE, 0 } } },
> + // Tablet
> + { KE_SW, 0x02, { .sw = { SW_TABLET_MODE, 1 } } },
> + // Drawing Board
> + { KE_SW, 0x03, { .sw = { SW_TABLET_MODE, 1 } } },
> + // Tent
> + { KE_SW, 0x04, { .sw = { SW_TABLET_MODE, 1 } } },
> + { KE_END },
> +};
> +
> +static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + struct lenovo_ymc_private *priv = dev_get_drvdata(&wdev->dev);
> +
And this empty line here looks weird. I have removed this and also
moved the status declaration down a bit since normally we order
variable declarations from long declaration to short declarations
(known as reverse christmas tree order).
> + u32 input_val = 0;
> + struct acpi_buffer input = {sizeof(input_val), &input_val};
> + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
> + acpi_status status;
> + union acpi_object *obj;
> + int code;
> +
> + status = wmi_evaluate_method(LENOVO_YMC_QUERY_GUID,
> + LENOVO_YMC_QUERY_INSTANCE,
> + LENOVO_YMC_QUERY_METHOD,
> + &input, &output);
> +
> + if (ACPI_FAILURE(status)) {
> + dev_warn(&wdev->dev,
> + "Failed to evaluate query method: %s\n",
> + acpi_format_exception(status));
> + return;
> + }
> +
> + obj = output.pointer;
> +
> + if (obj->type != ACPI_TYPE_INTEGER) {
> + dev_warn(&wdev->dev,
> + "WMI event data is not an integer\n");
> + goto free_obj;
> + }
> + code = obj->integer.value;
> +
> + if (!sparse_keymap_report_event(priv->input_dev, code, 1, true))
> + dev_warn(&wdev->dev, "Unknown key %d pressed\n", code);
> +
> +free_obj:
> + kfree(obj);
> + lenovo_ymc_trigger_ec(wdev, priv);
> +}
> +
> +static void acpi_dev_put_helper(void *p) { acpi_dev_put(p); }
> +
> +static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct input_dev *input_dev;
> + struct lenovo_ymc_private *priv;
> + int err;
> +
> + ec_trigger |= dmi_check_system(ec_trigger_quirk_dmi_table);
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + if (ec_trigger) {
> + pr_debug("Lenovo YMC enable EC triggering.\n");
> + priv->ec_acpi_dev = acpi_dev_get_first_match_dev("VPC2004", NULL, -1);
> +
> + if (!priv->ec_acpi_dev) {
> + dev_err(&wdev->dev, "Could not find EC ACPI device.\n");
> + return -ENODEV;
> + }
> + err = devm_add_action_or_reset(&wdev->dev,
> + acpi_dev_put_helper, priv->ec_acpi_dev);
> + if (err) {
> + dev_err(&wdev->dev,
> + "Could not clean up EC ACPI device: %d\n", err);
> + return err;
> + }
> + }
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Tablet Mode Control switch";
> + input_dev->phys = LENOVO_YMC_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_dev->dev.parent = &wdev->dev;
> + err = sparse_keymap_setup(input_dev, lenovo_ymc_keymap, NULL);
> + if (err) {
> + dev_err(&wdev->dev,
> + "Could not set up input device keymap: %d\n", err);
> + return err;
> + }
> +
> + err = input_register_device(input_dev);
> + if (err) {
> + dev_err(&wdev->dev,
> + "Could not register input device: %d\n", err);
> + return err;
> + }
> +
> + priv->input_dev = input_dev;
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + // Report the state for the first time on probe
> + lenovo_ymc_trigger_ec(wdev, priv);
> + lenovo_ymc_notify(wdev, NULL);
> + return 0;
> +}
> +
> +static const struct wmi_device_id lenovo_ymc_wmi_id_table[] = {
> + { .guid_string = LENOVO_YMC_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, lenovo_ymc_wmi_id_table);
> +
> +static struct wmi_driver lenovo_ymc_driver = {
> + .driver = {
> + .name = "lenovo-ymc",
> + },
> + .id_table = lenovo_ymc_wmi_id_table,
> + .probe = lenovo_ymc_probe,
> + .notify = lenovo_ymc_notify,
> +};
> +
> +module_wmi_driver(lenovo_ymc_driver);
> +
> +MODULE_AUTHOR("Gergo Koteles <soyer@irl.hu>");
> +MODULE_DESCRIPTION("Lenovo Yoga Mode Control driver");
> +MODULE_LICENSE("GPL");
Thank you for your patch, I've applied this patch to my review-hans
branch:
https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans
Once I've run some tests on this branch the patches there will be
added to the platform-drivers-x86/for-next branch and eventually
will be included in the pdx86 pull-request to Linus for the next
merge-window.
Regards,
Hans
^ permalink raw reply
* Re: [GIT PULL] intel-gpio for 6.4-2
From: Bartosz Golaszewski @ 2023-04-06 8:40 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Linux GPIO, linux-arm-kernel, linux-input, Linus Walleij,
Arnd Bergmann, Dmitry Torokhov
In-Reply-To: <ZC1vWkY52ggGxem8@black.fi.intel.com>
On Wed, Apr 5, 2023 at 2:53 PM Andy Shevchenko
<andriy.shevchenko@linux.intel.com> wrote:
>
> Hi Linux GPIO maintainers et al.,
>
> Here is the second PR for GPIO subsystem that contains some treewide fixes
> against previous PR along with Intel GPIO related stuff. It has been in the
> Linux Next for a few weeks without no additional problems being reported.
> Please, pull this for v6.4 cycle.
>
> This can be treated as immutable tag, in case anyone wants to go with it
> separately.
>
> Thanks,
>
> With Best Regards,
> Andy Shevchenko
>
> The following changes since commit 380c7ba3923c6e471aff0f951a6cf42e8dec2c79:
>
> gpiolib: Clean up headers (2023-03-06 12:33:02 +0200)
>
> are available in the Git repository at:
>
> git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git tags/intel-gpio-v6.4-2
>
> for you to fetch changes up to 782eea0c89f7d071d6b56ecfa1b8b0c81164b9be:
>
> gpiolib: acpi: Add a ignore wakeup quirk for Clevo NL5xNU (2023-03-23 16:22:18 +0200)
>
> ----------------------------------------------------------------
> intel-gpio for v6.4-2
>
> * Fixed suspend issue on Clevo NL5xNU
> * Split a new Intel Tangier (library) driver for current and new platforms
> * Introduced a new driver for Intel Elkhart Lake PSE GPIO (see also above)
> * Contained a few fixes for the previous of_gpio.h cleanup
> * Miscellaneous cleanups here and there
>
> The following is an automated git shortlog grouped by driver:
>
> elkhartlake:
> - Introduce Intel Elkhart Lake PSE GPIO
>
> gpiolib:
> - acpi: Add a ignore wakeup quirk for Clevo NL5xNU
> - acpi: Move ACPI device NULL check to acpi_get_driver_gpio_data()
> - acpi: use the fwnode in acpi_gpiochip_find()
>
> ich:
> - Use devm_gpiochip_add_data() to simplify remove path
>
> merrifield:
> - Utilise temporary variable for struct device
> - Use dev_err_probe()
> - Adapt to Intel Tangier GPIO driver
>
> mips:
> - ar7: include linux/gpio/driver.h
>
> mm-lantiq:
> - Fix typo in the newly added header filename
>
> powerpc/40x:
> - Add missing select OF_GPIO_MM_GPIOCHIP
>
> sh:
> - mach-x3proto: Add missing #include <linux/gpio/driver.h>
>
> tangier:
> - Introduce Intel Tangier GPIO driver
>
> ----------------------------------------------------------------
> Andrew Davis (1):
> gpio: ich: Use devm_gpiochip_add_data() to simplify remove path
>
> Andy Shevchenko (5):
> gpio: merrifield: Use dev_err_probe()
> gpio: merrifield: Utilise temporary variable for struct device
> powerpc/40x: Add missing select OF_GPIO_MM_GPIOCHIP
> gpio: mm-lantiq: Fix typo in the newly added header filename
> gpiolib: acpi: Move ACPI device NULL check to acpi_get_driver_gpio_data()
>
> Arnd Bergmann (1):
> mips: ar7: include linux/gpio/driver.h
>
> Benjamin Tissoires (1):
> gpiolib: acpi: use the fwnode in acpi_gpiochip_find()
>
> Geert Uytterhoeven (1):
> sh: mach-x3proto: Add missing #include <linux/gpio/driver.h>
>
> Pandith N (3):
> gpio: tangier: Introduce Intel Tangier GPIO driver
> gpio: merrifield: Adapt to Intel Tangier GPIO driver
> gpio: elkhartlake: Introduce Intel Elkhart Lake PSE GPIO
>
> Werner Sembach (1):
> gpiolib: acpi: Add a ignore wakeup quirk for Clevo NL5xNU
>
> MAINTAINERS | 2 +
> arch/mips/ar7/gpio.c | 2 +-
> arch/powerpc/platforms/40x/Kconfig | 1 +
> arch/sh/boards/mach-x3proto/setup.c | 2 +-
> drivers/gpio/Kconfig | 24 +-
> drivers/gpio/Makefile | 2 +
> drivers/gpio/gpio-elkhartlake.c | 90 ++++++
> drivers/gpio/gpio-ich.c | 10 +-
> drivers/gpio/gpio-merrifield.c | 453 +++---------------------------
> drivers/gpio/gpio-mm-lantiq.c | 2 +-
> drivers/gpio/gpio-tangier.c | 536 ++++++++++++++++++++++++++++++++++++
> drivers/gpio/gpio-tangier.h | 117 ++++++++
> drivers/gpio/gpiolib-acpi.c | 26 +-
> 13 files changed, 825 insertions(+), 442 deletions(-)
> create mode 100644 drivers/gpio/gpio-elkhartlake.c
> create mode 100644 drivers/gpio/gpio-tangier.c
> create mode 100644 drivers/gpio/gpio-tangier.h
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
Pulled, thanks!
Bart
^ permalink raw reply
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