* [PATCH v11 2/4] HID: usbhid: Share USB device firmware node with child HID device
From: Danny Kaehn @ 2024-06-05 23:12 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Benjamin Tissoires,
Andy Shevchenko, Danny Kaehn
Cc: Jiri Kosina, devicetree, linux-input, Dmitry Torokhov,
Bartosz Golaszewski, Ethan Twardy
In-Reply-To: <20240605-cp2112-dt-v11-0-d55f0f945a62@plexus.com>
USB HID core now shares its fwnode with its child HID device.
Since there can only be one HID device on a USB interface, it is redundant
to specify a hid node under the USB device. This allows usb HID device
drivers to be described in firmware and make use of device properties.
Signed-off-by: Danny Kaehn <danny.kaehn@plexus.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/hid/usbhid/hid-core.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index a90ed2ceae84..cb687ea7325c 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -19,6 +19,7 @@
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/mutex.h>
+#include <linux/property.h>
#include <linux/spinlock.h>
#include <asm/unaligned.h>
#include <asm/byteorder.h>
@@ -1374,6 +1375,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
hid->hiddev_report_event = hiddev_report_event;
#endif
hid->dev.parent = &intf->dev;
+ device_set_node(&hid->dev, dev_fwnode(&intf->dev));
hid->bus = BUS_USB;
hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
hid->product = le16_to_cpu(dev->descriptor.idProduct);
--
2.25.1
^ permalink raw reply related
* [PATCH v11 1/4] dt-bindings: i2c: Add CP2112 HID USB to SMBus Bridge
From: Danny Kaehn @ 2024-06-05 23:12 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Benjamin Tissoires,
Andy Shevchenko, Danny Kaehn
Cc: Jiri Kosina, devicetree, linux-input, Dmitry Torokhov,
Bartosz Golaszewski, Ethan Twardy
In-Reply-To: <20240605-cp2112-dt-v11-0-d55f0f945a62@plexus.com>
This is a USB HID device which includes an I2C controller and 8 GPIO pins.
The binding allows describing the chip's gpio and i2c controller in DT
using the subnodes named "gpio" and "i2c", respectively. This is
intended to be used in configurations where the CP2112 is permanently
connected in hardware.
Signed-off-by: Danny Kaehn <danny.kaehn@plexus.com>
---
.../devicetree/bindings/i2c/silabs,cp2112.yaml | 105 +++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml b/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml
new file mode 100644
index 000000000000..0108f2e43c8c
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/silabs,cp2112.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: CP2112 HID USB to SMBus/I2C Bridge
+
+maintainers:
+ - Danny Kaehn <danny.kaehn@plexus.com>
+
+description:
+ The CP2112 is a USB HID device which includes an integrated I2C controller
+ and 8 GPIO pins. Its GPIO pins can each be configured as inputs, open-drain
+ outputs, or push-pull outputs.
+
+properties:
+ compatible:
+ const: usb10c4,ea90
+
+ reg:
+ maxItems: 1
+ description: The USB port number on the host controller
+
+ i2c:
+ description: The SMBus/I2C controller node for the CP2112
+ $ref: /schemas/i2c/i2c-controller.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ sda-gpios:
+ maxItems: 1
+
+ scl-gpios:
+ maxItems: 1
+
+ clock-frequency:
+ minimum: 10000
+ default: 100000
+ maximum: 400000
+
+ interrupt-controller: true
+ "#interrupt-cells":
+ const: 2
+
+ gpio-controller: true
+ "#gpio-cells":
+ const: 2
+
+ gpio-line-names:
+ minItems: 1
+ maxItems: 8
+
+patternProperties:
+ "-hog(-[0-9]+)?$":
+ type: object
+
+ required:
+ - gpio-hog
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+
+ usb {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cp2112: device@1 {
+ compatible = "usb10c4,ea90";
+ reg = <1>;
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ sda-gpios = <&cp2112 0 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&cp2112 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
+
+ temp@48 {
+ compatible = "national,lm75";
+ reg = <0x48>;
+ };
+ };
+
+ gpio-controller;
+ interrupt-controller;
+ #gpio-cells = <2>;
+ gpio-line-names = "CP2112_SDA", "CP2112_SCL", "TEST2",
+ "TEST3","TEST4", "TEST5", "TEST6";
+
+ fan-rst-hog {
+ gpio-hog;
+ gpios = <7 GPIO_ACTIVE_HIGH>;
+ output-high;
+ line-name = "FAN_RST";
+ };
+ };
+ };
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v11 2/4] HID: usbhid: Share USB device firmware node with child HID device
From: Andy Shevchenko @ 2024-06-05 23:43 UTC (permalink / raw)
To: Danny Kaehn
Cc: Rob Herring, Krzysztof Kozlowski, Benjamin Tissoires, Jiri Kosina,
devicetree, linux-input, Dmitry Torokhov, Bartosz Golaszewski,
Ethan Twardy
In-Reply-To: <20240605-cp2112-dt-v11-2-d55f0f945a62@plexus.com>
On Wed, Jun 05, 2024 at 06:12:45PM -0500, Danny Kaehn wrote:
> USB HID core now shares its fwnode with its child HID device.
> Since there can only be one HID device on a USB interface, it is redundant
> to specify a hid node under the USB device. This allows usb HID device
> drivers to be described in firmware and make use of device properties.
Can this patch be applied already, so we don't drag it again and again?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v11 0/4] Firmware Support for USB-HID Devices and CP2112
From: Andy Shevchenko @ 2024-06-05 23:42 UTC (permalink / raw)
To: Danny Kaehn
Cc: Rob Herring, Krzysztof Kozlowski, Benjamin Tissoires, Jiri Kosina,
devicetree, linux-input, Dmitry Torokhov, Bartosz Golaszewski,
Ethan Twardy
In-Reply-To: <20240605-cp2112-dt-v11-0-d55f0f945a62@plexus.com>
On Wed, Jun 05, 2024 at 06:12:43PM -0500, Danny Kaehn wrote:
> This patchset allows USB-HID devices to have Firmware bindings through sharing
> the USB fwnode with the HID driver, and adds such a binding and driver
> implementation for the CP2112 USB to SMBus Bridge (which necessitated the
> USB-HID change). This change allows a CP2112 permanently attached in hardware to
> be described in DT and ACPI and interoperate with other drivers.
...
> Changes in v11:
> - Eliminate 'gpio' subnode for DT and ACPI for the CP2112 per comment
> from Rob H.
Hmm... I don't know much about DT, but how is this supposed to work in ACPI?
I mean if we want to refer to the GPIO in GpioIo() or GpioInt() resources,
what should we put there as ACPI path?
> - Edit hid-cp2112.c to match for ACPI index and fall back to matching by
> name (instead of the other way around)
> - Separate CP2112 I2C bus speed configuration into a separate patch
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v11 3/4] HID: cp2112: Fwnode Support
From: Andy Shevchenko @ 2024-06-05 23:39 UTC (permalink / raw)
To: Danny Kaehn
Cc: Rob Herring, Krzysztof Kozlowski, Benjamin Tissoires, Jiri Kosina,
devicetree, linux-input, Dmitry Torokhov, Bartosz Golaszewski,
Ethan Twardy
In-Reply-To: <20240605-cp2112-dt-v11-3-d55f0f945a62@plexus.com>
On Wed, Jun 05, 2024 at 06:12:46PM -0500, Danny Kaehn wrote:
> Support describing the CP2112's I2C and GPIO interfaces in firmware.
>
> The GPIO chip shares a firmware node with the CP2112. The I2C child
> node can either be a child with the name "i2c" or, in ACPI, a device
> node with _ADR Zero.
...
> + device_for_each_child_node(&hdev->dev, child) {
> + ret = acpi_get_local_address(ACPI_HANDLE_FWNODE(child), &addr);
> + if (ret) {
> + name = fwnode_get_name(child);
> + if (!name)
> + continue;
> + ret = match_string(cp2112_cell_names,
> + ARRAY_SIZE(cp2112_cell_names), name);
> + if (ret < 0)
> + continue;
> + addr = ret;
> + }
> +
> + switch (addr) {
> + case CP2112_I2C_ADR:
> + device_set_node(&dev->adap.dev, child);
> + break;
default:
break;
?
> + }
> + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [linux-next:master] BUILD REGRESSION 234cb065ad82915ff8d06ce01e01c3e640b674d2
From: kernel test robot @ 2024-06-05 17:06 UTC (permalink / raw)
To: Andrew Morton
Cc: Linux Memory Management List, amd-gfx, bpf, dmaengine, dri-devel,
imx, intel-gfx, intel-xe, linux-hwmon, linux-input, linux-pm,
linux-renesas-soc, linux-trace-kernel
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
branch HEAD: 234cb065ad82915ff8d06ce01e01c3e640b674d2 Add linux-next specific files for 20240605
Error/Warning reports:
https://lore.kernel.org/oe-kbuild-all/202406051521.mroqvR5l-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202406051524.a12JqLqx-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202406051711.dS1sQZ9n-lkp@intel.com
https://lore.kernel.org/oe-kbuild-all/202406051855.9VIYXbTB-lkp@intel.com
Error/Warning: (recently discovered and may have been fixed)
include/linux/container_of.h:20:54: error: 'struct ftrace_ops' has no member named 'list'
include/linux/list.h:769:26: error: 'struct ftrace_ops' has no member named 'list'
include/linux/stddef.h:16:33: error: 'struct ftrace_ops' has no member named 'list'
kernel/trace/fgraph.c:883:43: error: 'struct ftrace_ops' has no member named 'subop_list'
kernel/trace/fgraph.c:934:15: error: implicit declaration of function 'ftrace_startup_subops'; did you mean 'ftrace_startup'? [-Werror=implicit-function-declaration]
kernel/trace/fgraph.c:973:9: error: implicit declaration of function 'ftrace_shutdown_subops'; did you mean 'ftrace_shutdown'? [-Werror=implicit-function-declaration]
Error/Warning ids grouped by kconfigs:
gcc_recent_errors
|-- arc-randconfig-r061-20240605
| |-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:implicit-declaration-of-function-seq_puts
| |-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:invalid-use-of-undefined-type-struct-seq_file
| `-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:type-defaults-to-int-in-declaration-of-DEFINE_SHOW_ATTRIBUTE
|-- csky-randconfig-r053-20240605
| |-- include-linux-container_of.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-list.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-stddef.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_shutdown_subops
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_startup_subops
| `-- kernel-trace-fgraph.c:error:struct-ftrace_ops-has-no-member-named-subop_list
|-- i386-randconfig-061-20240605
| `-- drivers-hwmon-cros_ec_hwmon.c:sparse:sparse:cast-to-restricted-__le16
|-- i386-randconfig-063-20240605
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-B-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-B-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash1-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash2-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash2-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-orig_hash-got-struct-ftrace_hash-noderef-__rcu
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-src-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-src-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash-assigned-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash-save_filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash-assigned-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash-save_notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-save_filter_hash-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| `-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-save_notrace_hash-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
|-- i386-randconfig-141-20240605
| |-- drivers-dma-fsl-edma-common.c-fsl_edma_fill_tcd()-warn:statement-has-no-effect
| |-- drivers-dma-fsl-edma-common.c-fsl_edma_set_tcd_regs()-warn:statement-has-no-effect
| |-- drivers-dma-fsl-edma-main.c-fsl_edma_probe()-warn:statement-has-no-effect
| |-- drivers-dma-fsl-edma-main.c-fsl_edma_resume_early()-warn:statement-has-no-effect
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-core-dc_stream.c-dc_stream_get_max_flickerless_instant_vtotal_delta()-warn:always-true-condition-((safe_refresh_v_total-stream-timing.v_total)-)-(-u32max-)
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-core-dc_stream.c-dc_stream_get_max_flickerless_instant_vtotal_delta()-warn:always-true-condition-((stream-timing.v_total-safe_refresh_v_total)-)-(-u32max-)
| |-- drivers-gpu-drm-amd-amdgpu-amdgpu_vm.c-amdgpu_vm_bo_update()-error:we-previously-assumed-bo-could-be-null-(see-line-)
| |-- drivers-gpu-drm-i915-display-intel_dpt.c-intel_dpt_pin_to_ggtt()-error:uninitialized-symbol-vma-.
| |-- drivers-gpu-drm-i915-display-intel_fb_pin.c-intel_fb_pin_to_dpt()-error:uninitialized-symbol-vma-.
| |-- drivers-gpu-drm-i915-display-intel_fb_pin.c-intel_fb_pin_to_dpt()-error:vma-dereferencing-possible-ERR_PTR()
| |-- drivers-gpu-drm-xe-xe_drm_client.c-show_run_ticks()-error:uninitialized-symbol-gpu_timestamp-.
| |-- drivers-gpu-drm-xe-xe_drm_client.c-show_run_ticks()-error:uninitialized-symbol-hwe-.
| `-- drivers-gpu-drm-xe-xe_sched_job.c-xe_sched_job_arm()-error:uninitialized-symbol-fence-.
|-- loongarch-defconfig
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-hubbub-dcn401-dcn401_hubbub.o:warning:objtool:unexpected-relocation-symbol-type-in-.rela.discard.reachable
| `-- drivers-thermal-thermal_trip.o:warning:objtool:unexpected-relocation-symbol-type-in-.rela.discard.reachable
|-- loongarch-loongson3_defconfig
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-hubbub-dcn401-dcn401_hubbub.o:warning:objtool:unexpected-relocation-symbol-type-in-.rela.discard.reachable
| `-- drivers-thermal-thermal_trip.o:warning:objtool:unexpected-relocation-symbol-type-in-.rela.discard.reachable
|-- loongarch-randconfig-r064-20240605
| |-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:implicit-declaration-of-function-seq_puts
| |-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:invalid-use-of-undefined-type-struct-seq_file
| |-- drivers-gpu-drm-arm-display-komeda-komeda_dev.c:error:type-defaults-to-int-in-declaration-of-DEFINE_SHOW_ATTRIBUTE
| |-- include-linux-container_of.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-list.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-stddef.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_shutdown_subops
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_startup_subops
| `-- kernel-trace-fgraph.c:error:struct-ftrace_ops-has-no-member-named-subop_list
|-- riscv-randconfig-001-20240605
| |-- include-linux-container_of.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-list.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- include-linux-stddef.h:error:struct-ftrace_ops-has-no-member-named-list
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_shutdown_subops
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_startup_subops
| `-- kernel-trace-fgraph.c:error:struct-ftrace_ops-has-no-member-named-subop_list
|-- sparc64-randconfig-r121-20240605
| |-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_shutdown_subops
| `-- kernel-trace-fgraph.c:error:implicit-declaration-of-function-ftrace_startup_subops
|-- um-allyesconfig
| `-- kernel-bpf-verifier.c:error:pcpu_hot-undeclared-(first-use-in-this-function)
`-- x86_64-randconfig-003-20240605
|-- drivers-input-touchscreen-wacom_w8001.c:warning:Finger-directive-output-may-be-truncated-writing-bytes-into-a-region-of-size-between-and
`-- drivers-input-touchscreen-wacom_w8001.c:warning:Pen-directive-output-may-be-truncated-writing-bytes-into-a-region-of-size-between-and
clang_recent_errors
|-- arm64-allmodconfig
| |-- drivers-gpu-drm-amd-amdgpu-..-display-amdgpu_dm-amdgpu_dm.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-irq-dce110-irq_service_dce110.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_cursor.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_ddi.c:error:arithmetic-between-different-enumeration-types-(-enum-hpd_pin-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_ddi.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-phy-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-tc_port-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_dpll_mgr.c:error:arithmetic-between-different-enumeration-types-(-enum-tc_port-and-enum-intel_dpll_id-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_hotplug.c:error:arithmetic-between-different-enumeration-types-(-enum-hpd_pin-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_pipe_crc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_tc.c:error:arithmetic-between-different-enumeration-types-(-enum-intel_display_power_domain-and-enum-tc_port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_vdsc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-skl_universal_plane.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-skl_watermark.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| `-- drivers-gpu-drm-renesas-rcar-du-rcar_cmm.c:error:unused-function-rcar_cmm_read-Werror-Wunused-function
|-- arm64-randconfig-003-20240605
| |-- drivers-gpu-drm-amd-amdgpu-amdgpu_gfx.c:error:format-specifies-type-unsigned-char-but-the-argument-has-type-int-Werror-Wformat
| `-- drivers-gpu-drm-amd-amdgpu-amdgpu_gfx.c:error:format-specifies-type-unsigned-char-but-the-argument-has-type-u32-(aka-unsigned-int-)-Werror-Wformat
|-- hexagon-randconfig-001-20240605
| `-- drivers-gpu-drm-drm_mm.c:error:function-drm_mm_node_scanned_block-is-not-needed-and-will-not-be-emitted-Werror-Wunneeded-internal-declaration
|-- i386-randconfig-r131-20240605
| |-- kernel-trace-fgraph.c:sparse:sparse:symbol-fgraph_do_direct-was-not-declared.-Should-it-be-static
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-B-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-B-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash1-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash2-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-new_hash2-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-orig_hash-got-struct-ftrace_hash-noderef-__rcu
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-src-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-argument-(different-address-spaces)-expected-struct-ftrace_hash-src-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash-assigned-filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-filter_hash-got-struct-ftrace_hash-save_filter_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash-assigned-notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-noderef-__rcu-notrace_hash-got-struct-ftrace_hash-save_notrace_hash
| |-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-save_filter_hash-got-struct-ftrace_hash-noderef-__rcu-filter_hash
| `-- kernel-trace-ftrace.c:sparse:sparse:incorrect-type-in-assignment-(different-address-spaces)-expected-struct-ftrace_hash-save_notrace_hash-got-struct-ftrace_hash-noderef-__rcu-notrace_hash
|-- riscv-allmodconfig
| |-- drivers-gpu-drm-amd-amdgpu-..-display-amdgpu_dm-amdgpu_dm.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-amd-amdgpu-..-display-dc-irq-dce110-irq_service_dce110.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_cursor.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_ddi.c:error:arithmetic-between-different-enumeration-types-(-enum-hpd_pin-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_ddi.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-phy-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-tc_port-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_dpll_mgr.c:error:arithmetic-between-different-enumeration-types-(-enum-tc_port-and-enum-intel_dpll_id-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_hotplug.c:error:arithmetic-between-different-enumeration-types-(-enum-hpd_pin-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_pipe_crc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_tc.c:error:arithmetic-between-different-enumeration-types-(-enum-intel_display_power_domain-and-enum-tc_port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_vdsc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-skl_universal_plane.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| `-- drivers-gpu-drm-i915-display-skl_watermark.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
|-- riscv-allyesconfig
| |-- drivers-gpu-drm-amd-amdgpu-..-display-amdgpu_dm-amdgpu_dm.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
| `-- drivers-gpu-drm-amd-amdgpu-..-display-dc-irq-dce110-irq_service_dce110.c:error:arithmetic-between-different-enumeration-types-(-enum-dc_irq_source-and-enum-irq_type-)-Werror-Wenum-enum-conversion
|-- s390-allmodconfig
| |-- drivers-gpu-drm-i915-display-intel_cursor.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_ddi.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-phy-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_display_irq.c:error:arithmetic-between-different-enumeration-types-(-enum-transcoder-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_dpll_mgr.c:error:arithmetic-between-different-enumeration-types-(-enum-tc_port-and-enum-intel_dpll_id-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_hotplug.c:error:arithmetic-between-different-enumeration-types-(-enum-hpd_pin-and-enum-port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_pipe_crc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_tc.c:error:arithmetic-between-different-enumeration-types-(-enum-intel_display_power_domain-and-enum-tc_port-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-intel_vdsc.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| |-- drivers-gpu-drm-i915-display-skl_universal_plane.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
| `-- drivers-gpu-drm-i915-display-skl_watermark.c:error:arithmetic-between-different-enumeration-types-(-enum-pipe-and-enum-intel_display_power_domain-)-Werror-Wenum-enum-conversion
`-- x86_64-randconfig-002-20240605
`-- drivers-gpu-drm-drm_mm.c:error:function-drm_mm_node_scanned_block-is-not-needed-and-will-not-be-emitted-Werror-Wunneeded-internal-declaration
elapsed time: 754m
configs tested: 180
configs skipped: 3
tested configs:
alpha allnoconfig gcc
alpha allyesconfig gcc
alpha defconfig gcc
arc allmodconfig gcc
arc allnoconfig gcc
arc allyesconfig gcc
arc defconfig gcc
arc randconfig-001-20240605 gcc
arc randconfig-002-20240605 gcc
arm allmodconfig gcc
arm allnoconfig clang
arm allyesconfig gcc
arm defconfig clang
arm mps2_defconfig clang
arm mv78xx0_defconfig clang
arm mvebu_v7_defconfig clang
arm orion5x_defconfig clang
arm randconfig-001-20240605 clang
arm randconfig-002-20240605 clang
arm randconfig-003-20240605 clang
arm randconfig-004-20240605 gcc
arm64 allmodconfig clang
arm64 allnoconfig gcc
arm64 defconfig gcc
arm64 randconfig-001-20240605 gcc
arm64 randconfig-002-20240605 clang
arm64 randconfig-003-20240605 clang
arm64 randconfig-004-20240605 clang
csky allmodconfig gcc
csky allnoconfig gcc
csky allyesconfig gcc
csky defconfig gcc
csky randconfig-001-20240605 gcc
csky randconfig-002-20240605 gcc
hexagon allmodconfig clang
hexagon allnoconfig clang
hexagon allyesconfig clang
hexagon defconfig clang
hexagon randconfig-001-20240605 clang
hexagon randconfig-002-20240605 clang
i386 allmodconfig gcc
i386 allnoconfig gcc
i386 allyesconfig gcc
i386 buildonly-randconfig-001-20240605 gcc
i386 buildonly-randconfig-002-20240605 gcc
i386 buildonly-randconfig-003-20240605 gcc
i386 buildonly-randconfig-004-20240605 gcc
i386 buildonly-randconfig-005-20240605 gcc
i386 buildonly-randconfig-006-20240605 gcc
i386 defconfig clang
i386 randconfig-001-20240605 gcc
i386 randconfig-002-20240605 clang
i386 randconfig-003-20240605 gcc
i386 randconfig-004-20240605 gcc
i386 randconfig-005-20240605 gcc
i386 randconfig-006-20240605 gcc
i386 randconfig-011-20240605 gcc
i386 randconfig-012-20240605 gcc
i386 randconfig-013-20240605 clang
i386 randconfig-014-20240605 clang
i386 randconfig-015-20240605 gcc
i386 randconfig-016-20240605 clang
loongarch allmodconfig gcc
loongarch allnoconfig gcc
loongarch defconfig gcc
loongarch loongson3_defconfig gcc
loongarch randconfig-001-20240605 gcc
loongarch randconfig-002-20240605 gcc
m68k allmodconfig gcc
m68k allnoconfig gcc
m68k allyesconfig gcc
m68k atari_defconfig gcc
m68k defconfig gcc
microblaze allmodconfig gcc
microblaze allnoconfig gcc
microblaze allyesconfig gcc
microblaze defconfig gcc
mips allnoconfig gcc
mips allyesconfig gcc
mips maltaaprp_defconfig clang
mips rb532_defconfig clang
nios2 alldefconfig gcc
nios2 allmodconfig gcc
nios2 allnoconfig gcc
nios2 allyesconfig gcc
nios2 defconfig gcc
nios2 randconfig-001-20240605 gcc
nios2 randconfig-002-20240605 gcc
openrisc allnoconfig gcc
openrisc allyesconfig gcc
openrisc defconfig gcc
parisc allmodconfig gcc
parisc allnoconfig gcc
parisc allyesconfig gcc
parisc defconfig gcc
parisc randconfig-001-20240605 gcc
parisc randconfig-002-20240605 gcc
parisc64 defconfig gcc
powerpc akebono_defconfig clang
powerpc allmodconfig gcc
powerpc allnoconfig gcc
powerpc allyesconfig clang
powerpc cell_defconfig gcc
powerpc icon_defconfig gcc
powerpc ppa8548_defconfig gcc
powerpc ppc64e_defconfig gcc
powerpc randconfig-001-20240605 gcc
powerpc randconfig-002-20240605 gcc
powerpc randconfig-003-20240605 gcc
powerpc64 randconfig-001-20240605 gcc
powerpc64 randconfig-002-20240605 clang
powerpc64 randconfig-003-20240605 gcc
riscv allmodconfig clang
riscv allnoconfig gcc
riscv allyesconfig clang
riscv defconfig clang
riscv randconfig-001-20240605 gcc
riscv randconfig-002-20240605 gcc
s390 allmodconfig clang
s390 allnoconfig clang
s390 allyesconfig gcc
s390 defconfig clang
s390 randconfig-001-20240605 gcc
s390 randconfig-002-20240605 clang
sh allmodconfig gcc
sh allnoconfig gcc
sh allyesconfig gcc
sh defconfig gcc
sh randconfig-001-20240605 gcc
sh randconfig-002-20240605 gcc
sh rts7751r2dplus_defconfig gcc
sh sh7770_generic_defconfig gcc
sh shx3_defconfig gcc
sparc allmodconfig gcc
sparc allnoconfig gcc
sparc defconfig gcc
sparc64 allmodconfig gcc
sparc64 allyesconfig gcc
sparc64 defconfig gcc
sparc64 randconfig-001-20240605 gcc
sparc64 randconfig-002-20240605 gcc
um allmodconfig clang
um allnoconfig clang
um allyesconfig gcc
um defconfig clang
um i386_defconfig gcc
um randconfig-001-20240605 gcc
um randconfig-002-20240605 clang
um x86_64_defconfig clang
x86_64 allnoconfig clang
x86_64 allyesconfig clang
x86_64 buildonly-randconfig-001-20240605 gcc
x86_64 buildonly-randconfig-002-20240605 gcc
x86_64 buildonly-randconfig-003-20240605 gcc
x86_64 buildonly-randconfig-004-20240605 clang
x86_64 buildonly-randconfig-005-20240605 clang
x86_64 buildonly-randconfig-006-20240605 clang
x86_64 defconfig gcc
x86_64 randconfig-001-20240605 clang
x86_64 randconfig-002-20240605 clang
x86_64 randconfig-003-20240605 gcc
x86_64 randconfig-004-20240605 clang
x86_64 randconfig-005-20240605 clang
x86_64 randconfig-006-20240605 gcc
x86_64 randconfig-011-20240605 clang
x86_64 randconfig-012-20240605 gcc
x86_64 randconfig-013-20240605 clang
x86_64 randconfig-014-20240605 gcc
x86_64 randconfig-015-20240605 clang
x86_64 randconfig-016-20240605 gcc
x86_64 randconfig-071-20240605 gcc
x86_64 randconfig-072-20240605 clang
x86_64 randconfig-073-20240605 gcc
x86_64 randconfig-074-20240605 gcc
x86_64 randconfig-075-20240605 gcc
x86_64 randconfig-076-20240605 gcc
x86_64 rhel-8.3-rust clang
xtensa allnoconfig gcc
xtensa randconfig-001-20240605 gcc
xtensa randconfig-002-20240605 gcc
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] Input: wacom_w8001: Correct device name generation
From: Gerecke, Jason @ 2024-06-05 16:46 UTC (permalink / raw)
To: linux-input, Dmitry Torokhov
Cc: Ping Cheng, Aaron Armstrong Skomra, Joshua Dickens, Jason Gerecke
In-Reply-To: <ZlkQZsbDKTkzUfE2@google.com>
From: Jason Gerecke <jason.gerecke@wacom.com>
Fixes: 6c7cc1a29d1e ("Input: wacom_w8001 - simplify device name generation")
Signed-off-by: Jason Gerecke <jason.gerecke@wacom.com>
---
drivers/input/touchscreen/wacom_w8001.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c
index e3767db8a664b..ed9cfdd616cac 100644
--- a/drivers/input/touchscreen/wacom_w8001.c
+++ b/drivers/input/touchscreen/wacom_w8001.c
@@ -648,7 +648,7 @@ static int w8001_connect(struct serio *serio, struct serio_driver *drv)
}
if (!err_touch) {
- snprintf(w8001->pen_name, sizeof(w8001->pen_name), "%s Finger", basename);
+ snprintf(w8001->touch_name, sizeof(w8001->touch_name), "%s Finger", basename);
input_dev_touch->name = w8001->touch_name;
w8001_set_devdata(input_dev_touch, w8001, serio);
--
2.45.2
^ permalink raw reply related
* [PATCH v11 4/5] Input: cs40l50 - Add support for the CS40L50 haptic driver
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
In-Reply-To: <20240605135249.361082-1-jogletre@opensource.cirrus.com>
Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.
The input driver provides the interface for control of
haptic effects through the device.
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
These comments may need attention:
https://lore.kernel.org/linux-input/BC84DC9F-65FB-4553-A0B9-52151DD549DB@cirrus.com/
MAINTAINERS | 1 +
drivers/input/misc/Kconfig | 10 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/cs40l50-vibra.c | 555 +++++++++++++++++++++++++++++
4 files changed, 567 insertions(+)
create mode 100644 drivers/input/misc/cs40l50-vibra.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e804f3766cba..49c2e6e57b09 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4940,6 +4940,7 @@ M: Ben Bright <ben.bright@cirrus.com>
L: patches@opensource.cirrus.com
S: Supported
F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F: drivers/input/misc/cs40l*
F: drivers/mfd/cs40l*
F: include/linux/mfd/cs40l*
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6ba984d7f0b1..ee45dbb0636e 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -140,6 +140,16 @@ config INPUT_BMA150
To compile this driver as a module, choose M here: the
module will be called bma150.
+config INPUT_CS40L50_VIBRA
+ tristate "CS40L50 Haptic Driver support"
+ depends on MFD_CS40L50_CORE
+ help
+ Say Y here to enable support for Cirrus Logic's CS40L50
+ haptic driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cs40l50-vibra.
+
config INPUT_E3X0_BUTTON
tristate "NI Ettus Research USRP E3xx Button support."
default n
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 04296a4abe8e..88279de6d3d5 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_CS40L50_VIBRA) += cs40l50-vibra.o
obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
diff --git a/drivers/input/misc/cs40l50-vibra.c b/drivers/input/misc/cs40l50-vibra.c
new file mode 100644
index 000000000000..03bdb7c26ec0
--- /dev/null
+++ b/drivers/input/misc/cs40l50-vibra.c
@@ -0,0 +1,555 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/input.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+/* Wavetables */
+#define CS40L50_RAM_INDEX_START 0x1000000
+#define CS40L50_RAM_INDEX_END 0x100007F
+#define CS40L50_RTH_INDEX_START 0x1400000
+#define CS40L50_RTH_INDEX_END 0x1400001
+#define CS40L50_ROM_INDEX_START 0x1800000
+#define CS40L50_ROM_INDEX_END 0x180001A
+#define CS40L50_TYPE_PCM 8
+#define CS40L50_TYPE_PWLE 12
+#define CS40L50_PCM_ID 0x0
+#define CS40L50_OWT_CUSTOM_DATA_SIZE 2
+#define CS40L50_CUSTOM_DATA_MASK 0xFFFFU
+
+/* DSP */
+#define CS40L50_GPIO_BASE 0x2804140
+#define CS40L50_OWT_BASE 0x2805C34
+#define CS40L50_OWT_SIZE 0x2805C38
+#define CS40L50_OWT_NEXT 0x2805C3C
+#define CS40L50_EFFECTS_MAX 1
+
+/* GPIO */
+#define CS40L50_GPIO_NUM_MASK GENMASK(14, 12)
+#define CS40L50_GPIO_EDGE_MASK BIT(15)
+#define CS40L50_GPIO_MAPPING_NONE 0
+#define CS40L50_GPIO_DISABLE 0x1FF
+
+enum cs40l50_bank_type {
+ CS40L50_WVFRM_BANK_RAM,
+ CS40L50_WVFRM_BANK_ROM,
+ CS40L50_WVFRM_BANK_OWT,
+ CS40L50_WVFRM_BANK_NUM,
+};
+
+/* Describes an area in DSP memory populated by effects */
+struct cs40l50_bank {
+ enum cs40l50_bank_type type;
+ u32 base_index;
+ u32 max_index;
+};
+
+struct cs40l50_effect {
+ enum cs40l50_bank_type type;
+ struct list_head list;
+ u32 gpio_reg;
+ u32 index;
+ int id;
+};
+
+/* Describes haptic interface of loaded DSP firmware */
+struct cs40l50_vibra_dsp {
+ struct cs40l50_bank *banks;
+ u32 gpio_base_reg;
+ u32 owt_offset_reg;
+ u32 owt_size_reg;
+ u32 owt_base_reg;
+ u32 push_owt_cmd;
+ u32 delete_owt_cmd;
+ u32 stop_cmd;
+ int (*write)(struct device *dev, struct regmap *regmap, u32 val);
+};
+
+/* Describes configuration and state of haptic operations */
+struct cs40l50_vibra {
+ struct device *dev;
+ struct regmap *regmap;
+ struct input_dev *input;
+ struct workqueue_struct *vib_wq;
+ struct list_head effect_head;
+ struct cs40l50_vibra_dsp dsp;
+};
+
+struct cs40l50_work {
+ struct cs40l50_vibra *vib;
+ struct ff_effect *effect;
+ struct work_struct work;
+ s16 *custom_data;
+ int custom_len;
+ int count;
+ int error;
+};
+
+static struct cs40l50_bank cs40l50_banks[] = {
+ {
+ .type = CS40L50_WVFRM_BANK_RAM,
+ .base_index = CS40L50_RAM_INDEX_START,
+ .max_index = CS40L50_RAM_INDEX_END,
+ },
+ {
+ .type = CS40L50_WVFRM_BANK_ROM,
+ .base_index = CS40L50_ROM_INDEX_START,
+ .max_index = CS40L50_ROM_INDEX_END,
+ },
+ {
+ .type = CS40L50_WVFRM_BANK_OWT,
+ .base_index = CS40L50_RTH_INDEX_START,
+ .max_index = CS40L50_RTH_INDEX_END,
+ },
+};
+
+static struct cs40l50_vibra_dsp cs40l50_dsp = {
+ .banks = cs40l50_banks,
+ .gpio_base_reg = CS40L50_GPIO_BASE,
+ .owt_base_reg = CS40L50_OWT_BASE,
+ .owt_offset_reg = CS40L50_OWT_NEXT,
+ .owt_size_reg = CS40L50_OWT_SIZE,
+ .push_owt_cmd = CS40L50_OWT_PUSH,
+ .delete_owt_cmd = CS40L50_OWT_DELETE,
+ .stop_cmd = CS40L50_STOP_PLAYBACK,
+ .write = cs40l50_dsp_write,
+};
+
+static struct cs40l50_effect *cs40l50_find_effect(int id, struct list_head *effect_head)
+{
+ struct cs40l50_effect *effect;
+
+ list_for_each_entry(effect, effect_head, list)
+ if (effect->id == id)
+ return effect;
+
+ return NULL;
+}
+
+static int cs40l50_effect_bank_set(struct cs40l50_work *work_data,
+ struct cs40l50_effect *effect)
+{
+ s16 bank_type = work_data->custom_data[0] & CS40L50_CUSTOM_DATA_MASK;
+
+ if (bank_type >= CS40L50_WVFRM_BANK_NUM) {
+ dev_err(work_data->vib->dev, "Invalid bank (%d)\n", bank_type);
+ return -EINVAL;
+ }
+
+ if (work_data->custom_len > CS40L50_OWT_CUSTOM_DATA_SIZE)
+ effect->type = CS40L50_WVFRM_BANK_OWT;
+ else
+ effect->type = bank_type;
+
+ return 0;
+}
+
+static int cs40l50_effect_index_set(struct cs40l50_work *work_data,
+ struct cs40l50_effect *effect)
+{
+ struct cs40l50_vibra *vib = work_data->vib;
+ struct cs40l50_effect *owt_effect;
+ u32 base_index, max_index;
+
+ base_index = vib->dsp.banks[effect->type].base_index;
+ max_index = vib->dsp.banks[effect->type].max_index;
+
+ effect->index = base_index;
+
+ switch (effect->type) {
+ case CS40L50_WVFRM_BANK_OWT:
+ list_for_each_entry(owt_effect, &vib->effect_head, list)
+ if (owt_effect->type == CS40L50_WVFRM_BANK_OWT)
+ effect->index++;
+ break;
+ case CS40L50_WVFRM_BANK_ROM:
+ case CS40L50_WVFRM_BANK_RAM:
+ effect->index += work_data->custom_data[1] & CS40L50_CUSTOM_DATA_MASK;
+ break;
+ default:
+ dev_err(vib->dev, "Bank type %d not supported\n", effect->type);
+ return -EINVAL;
+ }
+
+ if (effect->index > max_index || effect->index < base_index) {
+ dev_err(vib->dev, "Index out of bounds: %u\n", effect->index);
+ return -ENOSPC;
+ }
+
+ return 0;
+}
+
+static int cs40l50_effect_gpio_mapping_set(struct cs40l50_work *work_data,
+ struct cs40l50_effect *effect)
+{
+ u16 gpio_edge, gpio_num, button = work_data->effect->trigger.button;
+ struct cs40l50_vibra *vib = work_data->vib;
+
+ if (button) {
+ gpio_num = FIELD_GET(CS40L50_GPIO_NUM_MASK, button);
+ gpio_edge = FIELD_GET(CS40L50_GPIO_EDGE_MASK, button);
+ effect->gpio_reg = vib->dsp.gpio_base_reg + (gpio_num * 8) - gpio_edge;
+
+ return regmap_write(vib->regmap, effect->gpio_reg, button);
+ }
+
+ effect->gpio_reg = CS40L50_GPIO_MAPPING_NONE;
+
+ return 0;
+}
+
+struct cs40l50_owt_header {
+ u32 type;
+ u32 data_words;
+ u32 offset;
+} __packed;
+
+static int cs40l50_upload_owt(struct cs40l50_work *work_data)
+{
+ u8 *new_owt_effect_data __free(kfree) = NULL;
+ struct cs40l50_vibra *vib = work_data->vib;
+ size_t len = work_data->custom_len * 2;
+ struct cs40l50_owt_header header;
+ u32 offset, size;
+ int error;
+
+ error = regmap_read(vib->regmap, vib->dsp.owt_size_reg, &size);
+ if (error)
+ return error;
+
+ if ((size * sizeof(u32)) < sizeof(header) + len) {
+ dev_err(vib->dev, "No space in open wavetable for effect\n");
+ return -ENOSPC;
+ }
+
+ header.type = work_data->custom_data[0] == CS40L50_PCM_ID ? CS40L50_TYPE_PCM :
+ CS40L50_TYPE_PWLE;
+ header.offset = sizeof(header) / sizeof(u32);
+ header.data_words = len / sizeof(u32);
+
+ new_owt_effect_data = kmalloc(sizeof(header) + len, GFP_KERNEL);
+
+ memcpy(new_owt_effect_data, &header, sizeof(header));
+ memcpy(new_owt_effect_data + sizeof(header), work_data->custom_data, len);
+
+ error = regmap_read(vib->regmap, vib->dsp.owt_offset_reg, &offset);
+ if (error)
+ return error;
+
+ error = regmap_bulk_write(vib->regmap, vib->dsp.owt_base_reg +
+ (offset * sizeof(u32)), new_owt_effect_data,
+ sizeof(header) + len);
+ if (error)
+ return error;
+
+ error = vib->dsp.write(vib->dev, vib->regmap, vib->dsp.push_owt_cmd);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static void cs40l50_add_worker(struct work_struct *work)
+{
+ struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+ struct cs40l50_vibra *vib = work_data->vib;
+ struct cs40l50_effect *effect;
+ bool is_new = false;
+ int error;
+
+ error = pm_runtime_resume_and_get(vib->dev);
+ if (error)
+ goto err_exit;
+
+ /* Update effect if already uploaded, otherwise create new effect */
+ effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
+ if (!effect) {
+ effect = kzalloc(sizeof(*effect), GFP_KERNEL);
+ if (!effect) {
+ error = -ENOMEM;
+ goto err_pm;
+ }
+
+ effect->id = work_data->effect->id;
+ is_new = true;
+ }
+
+ error = cs40l50_effect_bank_set(work_data, effect);
+ if (error)
+ goto err_free;
+
+ error = cs40l50_effect_index_set(work_data, effect);
+ if (error)
+ goto err_free;
+
+ error = cs40l50_effect_gpio_mapping_set(work_data, effect);
+ if (error)
+ goto err_free;
+
+ if (effect->type == CS40L50_WVFRM_BANK_OWT)
+ error = cs40l50_upload_owt(work_data);
+err_free:
+ if (is_new) {
+ if (error)
+ kfree(effect);
+ else
+ list_add(&effect->list, &vib->effect_head);
+ }
+err_pm:
+ pm_runtime_mark_last_busy(vib->dev);
+ pm_runtime_put_autosuspend(vib->dev);
+err_exit:
+ work_data->error = error;
+}
+
+static int cs40l50_add(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct ff_periodic_effect *periodic = &effect->u.periodic;
+ struct cs40l50_vibra *vib = input_get_drvdata(dev);
+ struct cs40l50_work work_data;
+
+ if (effect->type != FF_PERIODIC || periodic->waveform != FF_CUSTOM) {
+ dev_err(vib->dev, "Type (%#X) or waveform (%#X) unsupported\n",
+ effect->type, periodic->waveform);
+ return -EINVAL;
+ }
+
+ work_data.custom_data = memdup_array_user(effect->u.periodic.custom_data,
+ effect->u.periodic.custom_len,
+ sizeof(s16));
+ if (IS_ERR(work_data.custom_data))
+ return PTR_ERR(work_data.custom_data);
+
+ work_data.custom_len = effect->u.periodic.custom_len;
+ work_data.vib = vib;
+ work_data.effect = effect;
+ INIT_WORK(&work_data.work, cs40l50_add_worker);
+
+ /* Push to the workqueue to serialize with playbacks */
+ queue_work(vib->vib_wq, &work_data.work);
+ flush_work(&work_data.work);
+
+ kfree(work_data.custom_data);
+
+ return work_data.error;
+}
+
+static void cs40l50_start_worker(struct work_struct *work)
+{
+ struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+ struct cs40l50_vibra *vib = work_data->vib;
+ struct cs40l50_effect *start_effect;
+
+ if (pm_runtime_resume_and_get(vib->dev) < 0)
+ goto err_free;
+
+ start_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
+ if (start_effect) {
+ while (--work_data->count >= 0) {
+ vib->dsp.write(vib->dev, vib->regmap, start_effect->index);
+ usleep_range(work_data->effect->replay.length,
+ work_data->effect->replay.length + 100);
+ }
+ } else {
+ dev_err(vib->dev, "Effect to play not found\n");
+ }
+
+ pm_runtime_mark_last_busy(vib->dev);
+ pm_runtime_put_autosuspend(vib->dev);
+err_free:
+ kfree(work_data);
+}
+
+static void cs40l50_stop_worker(struct work_struct *work)
+{
+ struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+ struct cs40l50_vibra *vib = work_data->vib;
+
+ if (pm_runtime_resume_and_get(vib->dev) < 0)
+ return;
+
+ vib->dsp.write(vib->dev, vib->regmap, vib->dsp.stop_cmd);
+
+ pm_runtime_mark_last_busy(vib->dev);
+ pm_runtime_put_autosuspend(vib->dev);
+
+ kfree(work_data);
+}
+
+static int cs40l50_playback(struct input_dev *dev, int effect_id, int val)
+{
+ struct cs40l50_vibra *vib = input_get_drvdata(dev);
+ struct cs40l50_work *work_data;
+
+ work_data = kzalloc(sizeof(*work_data), GFP_ATOMIC);
+ if (!work_data)
+ return -ENOMEM;
+
+ work_data->vib = vib;
+
+ if (val > 0) {
+ work_data->effect = &dev->ff->effects[effect_id];
+ work_data->count = val;
+ INIT_WORK(&work_data->work, cs40l50_start_worker);
+ } else {
+ /* Stop the amplifier as device drives only one effect */
+ INIT_WORK(&work_data->work, cs40l50_stop_worker);
+ }
+
+ queue_work(vib->vib_wq, &work_data->work);
+
+ return 0;
+}
+
+static void cs40l50_erase_worker(struct work_struct *work)
+{
+ struct cs40l50_work *work_data = container_of(work, struct cs40l50_work, work);
+ struct cs40l50_effect *erase_effect, *owt_effect;
+ struct cs40l50_vibra *vib = work_data->vib;
+ int error;
+
+ error = pm_runtime_resume_and_get(vib->dev);
+ if (error)
+ goto err_exit;
+
+ erase_effect = cs40l50_find_effect(work_data->effect->id, &vib->effect_head);
+ if (!erase_effect) {
+ dev_err(vib->dev, "Effect to erase not found\n");
+ error = -EINVAL;
+ goto err_pm;
+ }
+
+ if (erase_effect->gpio_reg != CS40L50_GPIO_MAPPING_NONE) {
+ error = regmap_write(vib->regmap, erase_effect->gpio_reg,
+ CS40L50_GPIO_DISABLE);
+ if (error)
+ goto err_pm;
+ }
+
+ if (erase_effect->type == CS40L50_WVFRM_BANK_OWT) {
+ error = vib->dsp.write(vib->dev, vib->regmap,
+ vib->dsp.delete_owt_cmd |
+ (erase_effect->index & 0xFF));
+ if (error)
+ goto err_pm;
+
+ list_for_each_entry(owt_effect, &vib->effect_head, list)
+ if (owt_effect->type == CS40L50_WVFRM_BANK_OWT &&
+ owt_effect->index > erase_effect->index)
+ owt_effect->index--;
+ }
+
+ list_del(&erase_effect->list);
+ kfree(erase_effect);
+err_pm:
+ pm_runtime_mark_last_busy(vib->dev);
+ pm_runtime_put_autosuspend(vib->dev);
+err_exit:
+ work_data->error = error;
+}
+
+static int cs40l50_erase(struct input_dev *dev, int effect_id)
+{
+ struct cs40l50_vibra *vib = input_get_drvdata(dev);
+ struct cs40l50_work work_data;
+
+ work_data.vib = vib;
+ work_data.effect = &dev->ff->effects[effect_id];
+
+ INIT_WORK(&work_data.work, cs40l50_erase_worker);
+
+ /* Push to workqueue to serialize with playbacks */
+ queue_work(vib->vib_wq, &work_data.work);
+ flush_work(&work_data.work);
+
+ return work_data.error;
+}
+
+static void cs40l50_remove_wq(void *data)
+{
+ flush_workqueue(data);
+ destroy_workqueue(data);
+}
+
+static int cs40l50_vibra_probe(struct platform_device *pdev)
+{
+ struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+ struct cs40l50_vibra *vib;
+ int error;
+
+ vib = devm_kzalloc(pdev->dev.parent, sizeof(*vib), GFP_KERNEL);
+ if (!vib)
+ return -ENOMEM;
+
+ vib->dev = cs40l50->dev;
+ vib->regmap = cs40l50->regmap;
+ vib->dsp = cs40l50_dsp;
+
+ vib->input = devm_input_allocate_device(vib->dev);
+ if (!vib->input)
+ return -ENOMEM;
+
+ vib->input->id.product = cs40l50->devid;
+ vib->input->id.version = cs40l50->revid;
+ vib->input->name = "cs40l50_vibra";
+
+ input_set_drvdata(vib->input, vib);
+ input_set_capability(vib->input, EV_FF, FF_PERIODIC);
+ input_set_capability(vib->input, EV_FF, FF_CUSTOM);
+
+ error = input_ff_create(vib->input, CS40L50_EFFECTS_MAX);
+ if (error) {
+ dev_err(vib->dev, "Failed to create input device\n");
+ return error;
+ }
+
+ vib->input->ff->upload = cs40l50_add;
+ vib->input->ff->playback = cs40l50_playback;
+ vib->input->ff->erase = cs40l50_erase;
+
+ INIT_LIST_HEAD(&vib->effect_head);
+
+ vib->vib_wq = alloc_ordered_workqueue("vib_wq", WQ_HIGHPRI);
+ if (!vib->vib_wq)
+ return -ENOMEM;
+
+ error = devm_add_action_or_reset(vib->dev, cs40l50_remove_wq, vib->vib_wq);
+ if (error)
+ return error;
+
+ error = input_register_device(vib->input);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static const struct platform_device_id cs40l50_vibra_id_match[] = {
+ { "cs40l50-vibra", },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_vibra_id_match);
+
+static struct platform_driver cs40l50_vibra_driver = {
+ .probe = cs40l50_vibra_probe,
+ .id_table = cs40l50_vibra_id_match,
+ .driver = {
+ .name = "cs40l50-vibra",
+ },
+};
+module_platform_driver(cs40l50_vibra_driver);
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH v11 3/5] mfd: cs40l50: Add support for CS40L50 core driver
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
In-Reply-To: <20240605135249.361082-1-jogletre@opensource.cirrus.com>
Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.
The MFD component registers and initializes the device.
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
MAINTAINERS | 2 +
drivers/mfd/Kconfig | 30 ++
drivers/mfd/Makefile | 4 +
drivers/mfd/cs40l50-core.c | 570 ++++++++++++++++++++++++++++++++++++
drivers/mfd/cs40l50-i2c.c | 68 +++++
drivers/mfd/cs40l50-spi.c | 68 +++++
include/linux/mfd/cs40l50.h | 137 +++++++++
7 files changed, 879 insertions(+)
create mode 100644 drivers/mfd/cs40l50-core.c
create mode 100644 drivers/mfd/cs40l50-i2c.c
create mode 100644 drivers/mfd/cs40l50-spi.c
create mode 100644 include/linux/mfd/cs40l50.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 862a18e3fc7b..e804f3766cba 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4940,6 +4940,8 @@ M: Ben Bright <ben.bright@cirrus.com>
L: patches@opensource.cirrus.com
S: Supported
F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+F: drivers/mfd/cs40l*
+F: include/linux/mfd/cs40l*
CIRRUS LOGIC DSP FIRMWARE DRIVER
M: Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 90ce58fd629e..6273c255f107 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2241,6 +2241,36 @@ config MCP_UCB1200_TS
endmenu
+config MFD_CS40L50_CORE
+ tristate
+ select MFD_CORE
+ select FW_CS_DSP
+ select REGMAP_IRQ
+
+config MFD_CS40L50_I2C
+ tristate "Cirrus Logic CS40L50 (I2C)"
+ select REGMAP_I2C
+ select MFD_CS40L50_CORE
+ depends on I2C
+ help
+ Select this to support the Cirrus Logic CS40L50 Haptic
+ Driver over I2C.
+
+ This driver can be built as a module. If built as a module it will be
+ called "cs40l50-i2c".
+
+config MFD_CS40L50_SPI
+ tristate "Cirrus Logic CS40L50 (SPI)"
+ select REGMAP_SPI
+ select MFD_CS40L50_CORE
+ depends on SPI
+ help
+ Select this to support the Cirrus Logic CS40L50 Haptic
+ Driver over SPI.
+
+ This driver can be built as a module. If built as a module it will be
+ called "cs40l50-spi".
+
config MFD_VEXPRESS_SYSREG
tristate "Versatile Express System Registers"
depends on VEXPRESS_CONFIG && GPIOLIB
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..a8d18ba155d0 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -88,6 +88,10 @@ obj-$(CONFIG_MFD_MADERA) += madera.o
obj-$(CONFIG_MFD_MADERA_I2C) += madera-i2c.o
obj-$(CONFIG_MFD_MADERA_SPI) += madera-spi.o
+obj-$(CONFIG_MFD_CS40L50_CORE) += cs40l50-core.o
+obj-$(CONFIG_MFD_CS40L50_I2C) += cs40l50-i2c.o
+obj-$(CONFIG_MFD_CS40L50_SPI) += cs40l50-spi.o
+
obj-$(CONFIG_TPS6105X) += tps6105x.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_TPS6507X) += tps6507x.o
diff --git a/drivers/mfd/cs40l50-core.c b/drivers/mfd/cs40l50-core.c
new file mode 100644
index 000000000000..26e7a769eb14
--- /dev/null
+++ b/drivers/mfd/cs40l50-core.c
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/firmware/cirrus/wmfw.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/cs40l50.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+static const struct mfd_cell cs40l50_devs[] = {
+ { .name = "cs40l50-codec", },
+ { .name = "cs40l50-vibra", },
+};
+
+const struct regmap_config cs40l50_regmap = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+};
+EXPORT_SYMBOL_GPL(cs40l50_regmap);
+
+static const char * const cs40l50_supplies[] = {
+ "vdd-io",
+};
+
+static const struct regmap_irq cs40l50_reg_irqs[] = {
+ REGMAP_IRQ_REG(CS40L50_DSP_QUEUE_IRQ, CS40L50_IRQ1_INT_2_OFFSET,
+ CS40L50_DSP_QUEUE_MASK),
+ REGMAP_IRQ_REG(CS40L50_AMP_SHORT_IRQ, CS40L50_IRQ1_INT_1_OFFSET,
+ CS40L50_AMP_SHORT_MASK),
+ REGMAP_IRQ_REG(CS40L50_TEMP_ERR_IRQ, CS40L50_IRQ1_INT_8_OFFSET,
+ CS40L50_TEMP_ERR_MASK),
+ REGMAP_IRQ_REG(CS40L50_BST_UVP_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+ CS40L50_BST_UVP_MASK),
+ REGMAP_IRQ_REG(CS40L50_BST_SHORT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+ CS40L50_BST_SHORT_MASK),
+ REGMAP_IRQ_REG(CS40L50_BST_ILIMIT_IRQ, CS40L50_IRQ1_INT_9_OFFSET,
+ CS40L50_BST_ILIMIT_MASK),
+ REGMAP_IRQ_REG(CS40L50_UVLO_VDDBATT_IRQ, CS40L50_IRQ1_INT_10_OFFSET,
+ CS40L50_UVLO_VDDBATT_MASK),
+ REGMAP_IRQ_REG(CS40L50_GLOBAL_ERROR_IRQ, CS40L50_IRQ1_INT_18_OFFSET,
+ CS40L50_GLOBAL_ERROR_MASK),
+};
+
+static struct regmap_irq_chip cs40l50_irq_chip = {
+ .name = "cs40l50",
+ .status_base = CS40L50_IRQ1_INT_1,
+ .mask_base = CS40L50_IRQ1_MASK_1,
+ .ack_base = CS40L50_IRQ1_INT_1,
+ .num_regs = 22,
+ .irqs = cs40l50_reg_irqs,
+ .num_irqs = ARRAY_SIZE(cs40l50_reg_irqs),
+ .runtime_pm = true,
+};
+
+int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val)
+{
+ int i, ret;
+ u32 ack;
+
+ /* Device NAKs if hibernating, so optionally retry */
+ for (i = 0; i < CS40L50_DSP_TIMEOUT_COUNT; i++) {
+ ret = regmap_write(regmap, CS40L50_DSP_QUEUE, val);
+ if (!ret)
+ break;
+
+ usleep_range(CS40L50_DSP_POLL_US, CS40L50_DSP_POLL_US + 100);
+ }
+
+ /* If the write never took place, no need to check for the ACK */
+ if (i == CS40L50_DSP_TIMEOUT_COUNT) {
+ dev_err(dev, "Timed out writing %#X to DSP: %d\n", val, ret);
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(regmap, CS40L50_DSP_QUEUE, ack, !ack,
+ CS40L50_DSP_POLL_US,
+ CS40L50_DSP_POLL_US * CS40L50_DSP_TIMEOUT_COUNT);
+ if (ret)
+ dev_err(dev, "DSP failed to ACK %#X: %d\n", val, ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cs40l50_dsp_write);
+
+static const struct cs_dsp_region cs40l50_dsp_regions[] = {
+ { .type = WMFW_HALO_PM_PACKED, .base = CS40L50_PMEM_0 },
+ { .type = WMFW_HALO_XM_PACKED, .base = CS40L50_XMEM_PACKED_0 },
+ { .type = WMFW_HALO_YM_PACKED, .base = CS40L50_YMEM_PACKED_0 },
+ { .type = WMFW_ADSP2_XM, .base = CS40L50_XMEM_UNPACKED24_0 },
+ { .type = WMFW_ADSP2_YM, .base = CS40L50_YMEM_UNPACKED24_0 },
+};
+
+static const struct reg_sequence cs40l50_internal_vamp_config[] = {
+ { CS40L50_BST_LPMODE_SEL, CS40L50_DCM_LOW_POWER },
+ { CS40L50_BLOCK_ENABLES2, CS40L50_OVERTEMP_WARN },
+};
+
+static const struct reg_sequence cs40l50_irq_mask_override[] = {
+ { CS40L50_IRQ1_MASK_2, CS40L50_IRQ_MASK_2_OVERRIDE },
+ { CS40L50_IRQ1_MASK_20, CS40L50_IRQ_MASK_20_OVERRIDE },
+};
+
+static int cs40l50_wseq_init(struct cs40l50 *cs40l50)
+{
+ struct cs_dsp *dsp = &cs40l50->dsp;
+
+ cs40l50->wseqs[CS40L50_STANDBY].ctl = cs_dsp_get_ctl(dsp, "STANDBY_SEQUENCE",
+ WMFW_ADSP2_XM,
+ CS40L50_PM_ALGO);
+ if (!cs40l50->wseqs[CS40L50_STANDBY].ctl) {
+ dev_err(cs40l50->dev, "Control not found for standby sequence\n");
+ return -ENOENT;
+ }
+
+ cs40l50->wseqs[CS40L50_ACTIVE].ctl = cs_dsp_get_ctl(dsp, "ACTIVE_SEQUENCE",
+ WMFW_ADSP2_XM,
+ CS40L50_PM_ALGO);
+ if (!cs40l50->wseqs[CS40L50_ACTIVE].ctl) {
+ dev_err(cs40l50->dev, "Control not found for active sequence\n");
+ return -ENOENT;
+ }
+
+ cs40l50->wseqs[CS40L50_PWR_ON].ctl = cs_dsp_get_ctl(dsp, "PM_PWR_ON_SEQ",
+ WMFW_ADSP2_XM,
+ CS40L50_PM_ALGO);
+ if (!cs40l50->wseqs[CS40L50_PWR_ON].ctl) {
+ dev_err(cs40l50->dev, "Control not found for power-on sequence\n");
+ return -ENOENT;
+ }
+
+ return cs_dsp_wseq_init(&cs40l50->dsp, cs40l50->wseqs, ARRAY_SIZE(cs40l50->wseqs));
+}
+
+static int cs40l50_dsp_config(struct cs40l50 *cs40l50)
+{
+ int ret;
+
+ /* Configure internal V_AMP supply */
+ ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_internal_vamp_config,
+ ARRAY_SIZE(cs40l50_internal_vamp_config));
+ if (ret)
+ return ret;
+
+ ret = cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
+ cs40l50_internal_vamp_config, CS_DSP_WSEQ_FULL,
+ ARRAY_SIZE(cs40l50_internal_vamp_config), false);
+ if (ret)
+ return ret;
+
+ /* Override firmware defaults for IRQ masks */
+ ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_irq_mask_override,
+ ARRAY_SIZE(cs40l50_irq_mask_override));
+ if (ret)
+ return ret;
+
+ return cs_dsp_wseq_multi_write(&cs40l50->dsp, &cs40l50->wseqs[CS40L50_PWR_ON],
+ cs40l50_irq_mask_override, CS_DSP_WSEQ_FULL,
+ ARRAY_SIZE(cs40l50_irq_mask_override), false);
+}
+
+static int cs40l50_dsp_post_run(struct cs_dsp *dsp)
+{
+ struct cs40l50 *cs40l50 = container_of(dsp, struct cs40l50, dsp);
+ int ret;
+
+ ret = cs40l50_wseq_init(cs40l50);
+ if (ret)
+ return ret;
+
+ ret = cs40l50_dsp_config(cs40l50);
+ if (ret) {
+ dev_err(cs40l50->dev, "Failed to configure DSP: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_mfd_add_devices(cs40l50->dev, PLATFORM_DEVID_NONE, cs40l50_devs,
+ ARRAY_SIZE(cs40l50_devs), NULL, 0, NULL);
+ if (ret)
+ dev_err(cs40l50->dev, "Failed to add child devices: %d\n", ret);
+
+ return ret;
+}
+
+static const struct cs_dsp_client_ops client_ops = {
+ .post_run = cs40l50_dsp_post_run,
+};
+
+static void cs40l50_dsp_remove(void *data)
+{
+ cs_dsp_remove(data);
+}
+
+static int cs40l50_dsp_init(struct cs40l50 *cs40l50)
+{
+ int ret;
+
+ cs40l50->dsp.num = 1;
+ cs40l50->dsp.type = WMFW_HALO;
+ cs40l50->dsp.dev = cs40l50->dev;
+ cs40l50->dsp.regmap = cs40l50->regmap;
+ cs40l50->dsp.base = CS40L50_CORE_BASE;
+ cs40l50->dsp.base_sysinfo = CS40L50_SYS_INFO_ID;
+ cs40l50->dsp.mem = cs40l50_dsp_regions;
+ cs40l50->dsp.num_mems = ARRAY_SIZE(cs40l50_dsp_regions);
+ cs40l50->dsp.no_core_startstop = true;
+ cs40l50->dsp.client_ops = &client_ops;
+
+ ret = cs_dsp_halo_init(&cs40l50->dsp);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_remove,
+ &cs40l50->dsp);
+}
+
+static int cs40l50_reset_dsp(struct cs40l50 *cs40l50)
+{
+ int ret;
+
+ mutex_lock(&cs40l50->lock);
+
+ if (cs40l50->dsp.running)
+ cs_dsp_stop(&cs40l50->dsp);
+
+ if (cs40l50->dsp.booted)
+ cs_dsp_power_down(&cs40l50->dsp);
+
+ ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SHUTDOWN);
+ if (ret)
+ goto err_mutex;
+
+ ret = cs_dsp_power_up(&cs40l50->dsp, cs40l50->fw, "cs40l50.wmfw",
+ cs40l50->bin, "cs40l50.bin", "cs40l50");
+ if (ret)
+ goto err_mutex;
+
+ ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_SYSTEM_RESET);
+ if (ret)
+ goto err_mutex;
+
+ ret = cs40l50_dsp_write(cs40l50->dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
+ if (ret)
+ goto err_mutex;
+
+ ret = cs_dsp_run(&cs40l50->dsp);
+err_mutex:
+ mutex_unlock(&cs40l50->lock);
+
+ return ret;
+}
+
+static void cs40l50_dsp_power_down(void *data)
+{
+ cs_dsp_power_down(data);
+}
+
+static void cs40l50_dsp_stop(void *data)
+{
+ cs_dsp_stop(data);
+}
+
+static void cs40l50_dsp_bringup(const struct firmware *bin, void *context)
+{
+ struct cs40l50 *cs40l50 = context;
+ u32 nwaves;
+ int ret;
+
+ /* Wavetable is optional; bringup DSP regardless */
+ cs40l50->bin = bin;
+
+ ret = cs40l50_reset_dsp(cs40l50);
+ if (ret) {
+ dev_err(cs40l50->dev, "Failed to reset DSP: %d\n", ret);
+ goto err_fw;
+ }
+
+ ret = regmap_read(cs40l50->regmap, CS40L50_NUM_WAVES, &nwaves);
+ if (ret)
+ goto err_fw;
+
+ dev_info(cs40l50->dev, "%u RAM effects loaded\n", nwaves);
+
+ /* Add teardown actions for first-time bringup */
+ ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_power_down,
+ &cs40l50->dsp);
+ if (ret) {
+ dev_err(cs40l50->dev, "Failed to add power down action: %d\n", ret);
+ goto err_fw;
+ }
+
+ ret = devm_add_action_or_reset(cs40l50->dev, cs40l50_dsp_stop, &cs40l50->dsp);
+ if (ret)
+ dev_err(cs40l50->dev, "Failed to add stop action: %d\n", ret);
+err_fw:
+ release_firmware(cs40l50->bin);
+ release_firmware(cs40l50->fw);
+}
+
+static void cs40l50_request_firmware(const struct firmware *fw, void *context)
+{
+ struct cs40l50 *cs40l50 = context;
+ int ret;
+
+ if (!fw) {
+ dev_err(cs40l50->dev, "No firmware file found\n");
+ return;
+ }
+
+ cs40l50->fw = fw;
+
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_WT,
+ cs40l50->dev, GFP_KERNEL, cs40l50,
+ cs40l50_dsp_bringup);
+ if (ret) {
+ dev_err(cs40l50->dev, "Failed to request %s: %d\n", CS40L50_WT, ret);
+ release_firmware(cs40l50->fw);
+ }
+}
+
+struct cs40l50_irq {
+ const char *name;
+ int virq;
+};
+
+static struct cs40l50_irq cs40l50_irqs[] = {
+ { "DSP", },
+ { "Global", },
+ { "Boost UVLO", },
+ { "Boost current limit", },
+ { "Boost short", },
+ { "Boost undervolt", },
+ { "Overtemp", },
+ { "Amp short", },
+};
+
+static const struct reg_sequence cs40l50_err_rls[] = {
+ { CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_SET },
+ { CS40L50_ERR_RLS, CS40L50_GLOBAL_ERR_RLS_CLEAR },
+};
+
+static irqreturn_t cs40l50_hw_err(int irq, void *data)
+{
+ struct cs40l50 *cs40l50 = data;
+ int ret = 0, i;
+
+ mutex_lock(&cs40l50->lock);
+
+ /* Log hardware interrupt and execute error release sequence */
+ for (i = 1; i < ARRAY_SIZE(cs40l50_irqs); i++) {
+ if (cs40l50_irqs[i].virq == irq) {
+ dev_err(cs40l50->dev, "%s error\n", cs40l50_irqs[i].name);
+ ret = regmap_multi_reg_write(cs40l50->regmap, cs40l50_err_rls,
+ ARRAY_SIZE(cs40l50_err_rls));
+ break;
+ }
+ }
+
+ mutex_unlock(&cs40l50->lock);
+ return IRQ_RETVAL(!ret);
+}
+
+static irqreturn_t cs40l50_dsp_queue(int irq, void *data)
+{
+ struct cs40l50 *cs40l50 = data;
+ u32 rd_ptr, val, wt_ptr;
+ int ret = 0;
+
+ mutex_lock(&cs40l50->lock);
+
+ /* Read from DSP queue, log, and update read pointer */
+ while (!ret) {
+ ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_WT, &wt_ptr);
+ if (ret)
+ break;
+
+ ret = regmap_read(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, &rd_ptr);
+ if (ret)
+ break;
+
+ /* Check if queue is empty */
+ if (wt_ptr == rd_ptr)
+ break;
+
+ ret = regmap_read(cs40l50->regmap, rd_ptr, &val);
+ if (ret)
+ break;
+
+ dev_dbg(cs40l50->dev, "DSP payload: %#X", val);
+
+ rd_ptr += sizeof(u32);
+
+ if (rd_ptr > CS40L50_DSP_QUEUE_END)
+ rd_ptr = CS40L50_DSP_QUEUE_BASE;
+
+ ret = regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE_RD, rd_ptr);
+ }
+
+ mutex_unlock(&cs40l50->lock);
+
+ return IRQ_RETVAL(!ret);
+}
+
+static int cs40l50_irq_init(struct cs40l50 *cs40l50)
+{
+ int ret, i, virq;
+
+ ret = devm_regmap_add_irq_chip(cs40l50->dev, cs40l50->regmap, cs40l50->irq,
+ IRQF_ONESHOT | IRQF_SHARED, 0,
+ &cs40l50_irq_chip, &cs40l50->irq_data);
+ if (ret) {
+ dev_err(cs40l50->dev, "Failed adding IRQ chip\n");
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs40l50_irqs); i++) {
+ virq = regmap_irq_get_virq(cs40l50->irq_data, i);
+ if (virq < 0) {
+ dev_err(cs40l50->dev, "Failed getting virq for %s\n",
+ cs40l50_irqs[i].name);
+ return virq;
+ }
+
+ cs40l50_irqs[i].virq = virq;
+
+ /* Handle DSP and hardware interrupts separately */
+ ret = devm_request_threaded_irq(cs40l50->dev, virq, NULL,
+ i ? cs40l50_hw_err : cs40l50_dsp_queue,
+ IRQF_ONESHOT | IRQF_SHARED,
+ cs40l50_irqs[i].name, cs40l50);
+ if (ret) {
+ return dev_err_probe(cs40l50->dev, ret,
+ "Failed requesting %s IRQ\n",
+ cs40l50_irqs[i].name);
+ }
+ }
+
+ return 0;
+}
+
+static int cs40l50_get_model(struct cs40l50 *cs40l50)
+{
+ int ret;
+
+ ret = regmap_read(cs40l50->regmap, CS40L50_DEVID, &cs40l50->devid);
+ if (ret)
+ return ret;
+
+ if (cs40l50->devid != CS40L50_DEVID_A)
+ return -EINVAL;
+
+ ret = regmap_read(cs40l50->regmap, CS40L50_REVID, &cs40l50->revid);
+ if (ret)
+ return ret;
+
+ if (cs40l50->revid < CS40L50_REVID_B0)
+ return -EINVAL;
+
+ dev_dbg(cs40l50->dev, "Cirrus Logic CS40L50 rev. %02X\n", cs40l50->revid);
+
+ return 0;
+}
+
+static int cs40l50_pm_runtime_setup(struct device *dev)
+{
+ int ret;
+
+ pm_runtime_set_autosuspend_delay(dev, CS40L50_AUTOSUSPEND_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_get_noresume(dev);
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ return ret;
+
+ return devm_pm_runtime_enable(dev);
+}
+
+int cs40l50_probe(struct cs40l50 *cs40l50)
+{
+ struct device *dev = cs40l50->dev;
+ int ret;
+
+ mutex_init(&cs40l50->lock);
+
+ cs40l50->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(cs40l50->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(cs40l50->reset_gpio),
+ "Failed getting reset GPIO\n");
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(cs40l50_supplies),
+ cs40l50_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed getting supplies\n");
+
+ /* Ensure minimum reset pulse width */
+ usleep_range(CS40L50_RESET_PULSE_US, CS40L50_RESET_PULSE_US + 100);
+
+ gpiod_set_value_cansleep(cs40l50->reset_gpio, 0);
+
+ /* Wait for control port to be ready */
+ usleep_range(CS40L50_CP_READY_US, CS40L50_CP_READY_US + 100);
+
+ ret = cs40l50_get_model(cs40l50);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get part number\n");
+
+ ret = cs40l50_dsp_init(cs40l50);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize DSP\n");
+
+ ret = cs40l50_pm_runtime_setup(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize runtime PM\n");
+
+ ret = cs40l50_irq_init(cs40l50);
+ if (ret)
+ return ret;
+
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, CS40L50_FW,
+ dev, GFP_KERNEL, cs40l50, cs40l50_request_firmware);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request %s\n", CS40L50_FW);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l50_probe);
+
+int cs40l50_remove(struct cs40l50 *cs40l50)
+{
+ gpiod_set_value_cansleep(cs40l50->reset_gpio, 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cs40l50_remove);
+
+static int cs40l50_runtime_suspend(struct device *dev)
+{
+ struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
+
+ return regmap_write(cs40l50->regmap, CS40L50_DSP_QUEUE, CS40L50_ALLOW_HIBER);
+}
+
+static int cs40l50_runtime_resume(struct device *dev)
+{
+ struct cs40l50 *cs40l50 = dev_get_drvdata(dev);
+
+ return cs40l50_dsp_write(dev, cs40l50->regmap, CS40L50_PREVENT_HIBER);
+}
+
+EXPORT_GPL_DEV_PM_OPS(cs40l50_pm_ops) = {
+ RUNTIME_PM_OPS(cs40l50_runtime_suspend, cs40l50_runtime_resume, NULL)
+};
+
+MODULE_DESCRIPTION("CS40L50 Advanced Haptic Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(FW_CS_DSP);
diff --git a/drivers/mfd/cs40l50-i2c.c b/drivers/mfd/cs40l50-i2c.c
new file mode 100644
index 000000000000..639be743d956
--- /dev/null
+++ b/drivers/mfd/cs40l50-i2c.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/mfd/cs40l50.h>
+
+static int cs40l50_i2c_probe(struct i2c_client *i2c)
+{
+ struct cs40l50 *cs40l50;
+
+ cs40l50 = devm_kzalloc(&i2c->dev, sizeof(*cs40l50), GFP_KERNEL);
+ if (!cs40l50)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, cs40l50);
+
+ cs40l50->dev = &i2c->dev;
+ cs40l50->irq = i2c->irq;
+
+ cs40l50->regmap = devm_regmap_init_i2c(i2c, &cs40l50_regmap);
+ if (IS_ERR(cs40l50->regmap))
+ return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap),
+ "Failed to initialize register map\n");
+
+ return cs40l50_probe(cs40l50);
+}
+
+static void cs40l50_i2c_remove(struct i2c_client *i2c)
+{
+ struct cs40l50 *cs40l50 = i2c_get_clientdata(i2c);
+
+ cs40l50_remove(cs40l50);
+}
+
+static const struct i2c_device_id cs40l50_id_i2c[] = {
+ { "cs40l50" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs40l50_id_i2c);
+
+static const struct of_device_id cs40l50_of_match[] = {
+ { .compatible = "cirrus,cs40l50" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l50_of_match);
+
+static struct i2c_driver cs40l50_i2c_driver = {
+ .driver = {
+ .name = "cs40l50",
+ .of_match_table = cs40l50_of_match,
+ .pm = pm_ptr(&cs40l50_pm_ops),
+ },
+ .id_table = cs40l50_id_i2c,
+ .probe = cs40l50_i2c_probe,
+ .remove = cs40l50_i2c_remove,
+};
+module_i2c_driver(cs40l50_i2c_driver);
+
+MODULE_DESCRIPTION("CS40L50 I2C Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/cs40l50-spi.c b/drivers/mfd/cs40l50-spi.c
new file mode 100644
index 000000000000..53526b595a0d
--- /dev/null
+++ b/drivers/mfd/cs40l50-spi.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#include <linux/mfd/cs40l50.h>
+#include <linux/spi/spi.h>
+
+static int cs40l50_spi_probe(struct spi_device *spi)
+{
+ struct cs40l50 *cs40l50;
+
+ cs40l50 = devm_kzalloc(&spi->dev, sizeof(*cs40l50), GFP_KERNEL);
+ if (!cs40l50)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs40l50);
+
+ cs40l50->dev = &spi->dev;
+ cs40l50->irq = spi->irq;
+
+ cs40l50->regmap = devm_regmap_init_spi(spi, &cs40l50_regmap);
+ if (IS_ERR(cs40l50->regmap))
+ return dev_err_probe(cs40l50->dev, PTR_ERR(cs40l50->regmap),
+ "Failed to initialize register map\n");
+
+ return cs40l50_probe(cs40l50);
+}
+
+static void cs40l50_spi_remove(struct spi_device *spi)
+{
+ struct cs40l50 *cs40l50 = spi_get_drvdata(spi);
+
+ cs40l50_remove(cs40l50);
+}
+
+static const struct spi_device_id cs40l50_id_spi[] = {
+ { "cs40l50" },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cs40l50_id_spi);
+
+static const struct of_device_id cs40l50_of_match[] = {
+ { .compatible = "cirrus,cs40l50" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs40l50_of_match);
+
+static struct spi_driver cs40l50_spi_driver = {
+ .driver = {
+ .name = "cs40l50",
+ .of_match_table = cs40l50_of_match,
+ .pm = pm_ptr(&cs40l50_pm_ops),
+ },
+ .id_table = cs40l50_id_spi,
+ .probe = cs40l50_spi_probe,
+ .remove = cs40l50_spi_remove,
+};
+module_spi_driver(cs40l50_spi_driver);
+
+MODULE_DESCRIPTION("CS40L50 SPI Driver");
+MODULE_AUTHOR("James Ogletree, Cirrus Logic Inc. <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/cs40l50.h b/include/linux/mfd/cs40l50.h
new file mode 100644
index 000000000000..e5dc49860944
--- /dev/null
+++ b/include/linux/mfd/cs40l50.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * CS40L50 Advanced Haptic Driver with waveform memory,
+ * integrated DSP, and closed-loop algorithms
+ *
+ * Copyright 2024 Cirrus Logic, Inc.
+ *
+ * Author: James Ogletree <james.ogletree@cirrus.com>
+ */
+
+#ifndef __MFD_CS40L50_H__
+#define __MFD_CS40L50_H__
+
+#include <linux/firmware/cirrus/cs_dsp.h>
+#include <linux/gpio/consumer.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+/* Power Supply Configuration */
+#define CS40L50_BLOCK_ENABLES2 0x201C
+#define CS40L50_ERR_RLS 0x2034
+#define CS40L50_BST_LPMODE_SEL 0x3810
+#define CS40L50_DCM_LOW_POWER 0x1
+#define CS40L50_OVERTEMP_WARN 0x4000010
+
+/* Interrupts */
+#define CS40L50_IRQ1_INT_1 0xE010
+#define CS40L50_IRQ1_BASE CS40L50_IRQ1_INT_1
+#define CS40L50_IRQ1_INT_2 0xE014
+#define CS40L50_IRQ1_INT_8 0xE02C
+#define CS40L50_IRQ1_INT_9 0xE030
+#define CS40L50_IRQ1_INT_10 0xE034
+#define CS40L50_IRQ1_INT_18 0xE054
+#define CS40L50_IRQ1_MASK_1 0xE090
+#define CS40L50_IRQ1_MASK_2 0xE094
+#define CS40L50_IRQ1_MASK_20 0xE0DC
+#define CS40L50_IRQ1_INT_1_OFFSET (CS40L50_IRQ1_INT_1 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_2_OFFSET (CS40L50_IRQ1_INT_2 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_8_OFFSET (CS40L50_IRQ1_INT_8 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_9_OFFSET (CS40L50_IRQ1_INT_9 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_10_OFFSET (CS40L50_IRQ1_INT_10 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ1_INT_18_OFFSET (CS40L50_IRQ1_INT_18 - CS40L50_IRQ1_BASE)
+#define CS40L50_IRQ_MASK_2_OVERRIDE 0xFFDF7FFF
+#define CS40L50_IRQ_MASK_20_OVERRIDE 0x15C01000
+#define CS40L50_AMP_SHORT_MASK BIT(31)
+#define CS40L50_DSP_QUEUE_MASK BIT(21)
+#define CS40L50_TEMP_ERR_MASK BIT(31)
+#define CS40L50_BST_UVP_MASK BIT(6)
+#define CS40L50_BST_SHORT_MASK BIT(7)
+#define CS40L50_BST_ILIMIT_MASK BIT(18)
+#define CS40L50_UVLO_VDDBATT_MASK BIT(16)
+#define CS40L50_GLOBAL_ERROR_MASK BIT(15)
+
+enum cs40l50_irq_list {
+ CS40L50_DSP_QUEUE_IRQ,
+ CS40L50_GLOBAL_ERROR_IRQ,
+ CS40L50_UVLO_VDDBATT_IRQ,
+ CS40L50_BST_ILIMIT_IRQ,
+ CS40L50_BST_SHORT_IRQ,
+ CS40L50_BST_UVP_IRQ,
+ CS40L50_TEMP_ERR_IRQ,
+ CS40L50_AMP_SHORT_IRQ,
+};
+
+/* DSP */
+#define CS40L50_XMEM_PACKED_0 0x2000000
+#define CS40L50_XMEM_UNPACKED24_0 0x2800000
+#define CS40L50_SYS_INFO_ID 0x25E0000
+#define CS40L50_DSP_QUEUE_WT 0x28042C8
+#define CS40L50_DSP_QUEUE_RD 0x28042CC
+#define CS40L50_NUM_WAVES 0x2805C18
+#define CS40L50_CORE_BASE 0x2B80000
+#define CS40L50_YMEM_PACKED_0 0x2C00000
+#define CS40L50_YMEM_UNPACKED24_0 0x3400000
+#define CS40L50_PMEM_0 0x3800000
+#define CS40L50_DSP_POLL_US 1000
+#define CS40L50_DSP_TIMEOUT_COUNT 100
+#define CS40L50_RESET_PULSE_US 2200
+#define CS40L50_CP_READY_US 3100
+#define CS40L50_AUTOSUSPEND_MS 2000
+#define CS40L50_PM_ALGO 0x9F206
+#define CS40L50_GLOBAL_ERR_RLS_SET BIT(11)
+#define CS40L50_GLOBAL_ERR_RLS_CLEAR 0
+
+enum cs40l50_wseqs {
+ CS40L50_PWR_ON,
+ CS40L50_STANDBY,
+ CS40L50_ACTIVE,
+ CS40L50_NUM_WSEQS,
+};
+
+/* DSP Queue */
+#define CS40L50_DSP_QUEUE_BASE 0x11004
+#define CS40L50_DSP_QUEUE_END 0x1101C
+#define CS40L50_DSP_QUEUE 0x11020
+#define CS40L50_PREVENT_HIBER 0x2000003
+#define CS40L50_ALLOW_HIBER 0x2000004
+#define CS40L50_SHUTDOWN 0x2000005
+#define CS40L50_SYSTEM_RESET 0x2000007
+#define CS40L50_START_I2S 0x3000002
+#define CS40L50_OWT_PUSH 0x3000008
+#define CS40L50_STOP_PLAYBACK 0x5000000
+#define CS40L50_OWT_DELETE 0xD000000
+
+/* Firmware files */
+#define CS40L50_FW "cs40l50.wmfw"
+#define CS40L50_WT "cs40l50.bin"
+
+/* Device */
+#define CS40L50_DEVID 0x0
+#define CS40L50_REVID 0x4
+#define CS40L50_DEVID_A 0x40A50
+#define CS40L50_REVID_B0 0xB0
+
+struct cs40l50 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct cs_dsp dsp;
+ struct gpio_desc *reset_gpio;
+ struct regmap_irq_chip_data *irq_data;
+ const struct firmware *fw;
+ const struct firmware *bin;
+ struct cs_dsp_wseq wseqs[CS40L50_NUM_WSEQS];
+ int irq;
+ u32 devid;
+ u32 revid;
+};
+
+int cs40l50_dsp_write(struct device *dev, struct regmap *regmap, u32 val);
+int cs40l50_probe(struct cs40l50 *cs40l50);
+int cs40l50_remove(struct cs40l50 *cs40l50);
+
+extern const struct regmap_config cs40l50_regmap;
+extern const struct dev_pm_ops cs40l50_pm_ops;
+
+#endif /* __MFD_CS40L50_H__ */
--
2.34.1
^ permalink raw reply related
* [PATCH v11 5/5] ASoC: cs40l50: Support I2S streaming to CS40L50
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree,
David Rhodes
In-Reply-To: <20240605135249.361082-1-jogletre@opensource.cirrus.com>
Introduce support for Cirrus Logic Device CS40L50: a
haptic driver with waveform memory, integrated DSP,
and closed-loop algorithms.
The ASoC driver enables I2S streaming to the device.
Reviewed-by: David Rhodes <drhodes@opensource.cirrus.com>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
MAINTAINERS | 1 +
sound/soc/codecs/Kconfig | 11 ++
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/cs40l50-codec.c | 307 +++++++++++++++++++++++++++++++
4 files changed, 321 insertions(+)
create mode 100644 sound/soc/codecs/cs40l50-codec.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 49c2e6e57b09..62701b13f741 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4943,6 +4943,7 @@ F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
F: drivers/input/misc/cs40l*
F: drivers/mfd/cs40l*
F: include/linux/mfd/cs40l*
+F: sound/soc/codecs/cs40l*
CIRRUS LOGIC DSP FIRMWARE DRIVER
M: Simon Trimmer <simont@opensource.cirrus.com>
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f1e1dbc509f6..1a81bedfdbe3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -73,6 +73,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_CS35L56_I2C
imply SND_SOC_CS35L56_SPI
imply SND_SOC_CS35L56_SDW
+ imply SND_SOC_CS40L50
imply SND_SOC_CS42L42
imply SND_SOC_CS42L42_SDW
imply SND_SOC_CS42L43
@@ -800,6 +801,16 @@ config SND_SOC_CS35L56_SDW
help
Enable support for Cirrus Logic CS35L56 boosted amplifier with SoundWire control
+config SND_SOC_CS40L50
+ tristate "Cirrus Logic CS40L50 CODEC"
+ depends on MFD_CS40L50_CORE
+ help
+ This option enables support for I2S streaming to Cirrus Logic CS40L50.
+
+ CS40L50 is a haptic driver with waveform memory, an integrated
+ DSP, and closed-loop algorithms. If built as a module, it will be
+ called snd-soc-cs40l50.
+
config SND_SOC_CS42L42_CORE
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index b4df22186e25..3afd7c16c959 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -78,6 +78,7 @@ snd-soc-cs35l56-shared-y := cs35l56-shared.o
snd-soc-cs35l56-i2c-y := cs35l56-i2c.o
snd-soc-cs35l56-spi-y := cs35l56-spi.o
snd-soc-cs35l56-sdw-y := cs35l56-sdw.o
+snd-soc-cs40l50-objs := cs40l50-codec.o
snd-soc-cs42l42-y := cs42l42.o
snd-soc-cs42l42-i2c-y := cs42l42-i2c.o
snd-soc-cs42l42-sdw-y := cs42l42-sdw.o
@@ -475,6 +476,7 @@ obj-$(CONFIG_SND_SOC_CS35L56_SHARED) += snd-soc-cs35l56-shared.o
obj-$(CONFIG_SND_SOC_CS35L56_I2C) += snd-soc-cs35l56-i2c.o
obj-$(CONFIG_SND_SOC_CS35L56_SPI) += snd-soc-cs35l56-spi.o
obj-$(CONFIG_SND_SOC_CS35L56_SDW) += snd-soc-cs35l56-sdw.o
+obj-$(CONFIG_SND_SOC_CS40L50) += snd-soc-cs40l50.o
obj-$(CONFIG_SND_SOC_CS42L42_CORE) += snd-soc-cs42l42.o
obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42-i2c.o
obj-$(CONFIG_SND_SOC_CS42L42_SDW) += snd-soc-cs42l42-sdw.o
diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c
new file mode 100644
index 000000000000..aa629ef53db4
--- /dev/null
+++ b/sound/soc/codecs/cs40l50-codec.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// CS40L50 Advanced Haptic Driver with waveform memory,
+// integrated DSP, and closed-loop algorithms
+//
+// Copyright 2024 Cirrus Logic, Inc.
+//
+// Author: James Ogletree <james.ogletree@cirrus.com>
+
+#include <linux/bitfield.h>
+#include <linux/mfd/cs40l50.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define CS40L50_REFCLK_INPUT 0x2C04
+#define CS40L50_ASP_CONTROL2 0x4808
+#define CS40L50_ASP_DATA_CONTROL5 0x4840
+
+/* PLL Config */
+#define CS40L50_PLL_REFCLK_BCLK 0x0
+#define CS40L50_PLL_REFCLK_MCLK 0x5
+#define CS40L50_PLL_REEFCLK_MCLK_CFG 0x00
+#define CS40L50_PLL_REFCLK_LOOP_MASK BIT(11)
+#define CS40L50_PLL_REFCLK_OPEN_LOOP 1
+#define CS40L50_PLL_REFCLK_CLOSED_LOOP 0
+#define CS40L50_PLL_REFCLK_LOOP_SHIFT 11
+#define CS40L50_PLL_REFCLK_FREQ_MASK GENMASK(10, 5)
+#define CS40L50_PLL_REFCLK_FREQ_SHIFT 5
+#define CS40L50_PLL_REFCLK_SEL_MASK GENMASK(2, 0)
+#define CS40L50_BCLK_RATIO_DEFAULT 32
+
+/* ASP Config */
+#define CS40L50_ASP_RX_WIDTH_SHIFT 24
+#define CS40L50_ASP_RX_WIDTH_MASK GENMASK(31, 24)
+#define CS40L50_ASP_RX_WL_MASK GENMASK(5, 0)
+#define CS40L50_ASP_FSYNC_INV_MASK BIT(2)
+#define CS40L50_ASP_BCLK_INV_MASK BIT(6)
+#define CS40L50_ASP_FMT_MASK GENMASK(10, 8)
+#define CS40L50_ASP_FMT_I2S 0x2
+
+struct cs40l50_pll_config {
+ unsigned int freq;
+ unsigned int cfg;
+};
+
+struct cs40l50_codec {
+ struct device *dev;
+ struct regmap *regmap;
+ unsigned int daifmt;
+ unsigned int bclk_ratio;
+ unsigned int rate;
+};
+
+static const struct cs40l50_pll_config cs40l50_pll_cfg[] = {
+ { 32768, 0x00 },
+ { 1536000, 0x1B },
+ { 3072000, 0x21 },
+ { 6144000, 0x28 },
+ { 9600000, 0x30 },
+ { 12288000, 0x33 },
+};
+
+static int cs40l50_get_clk_config(const unsigned int freq, unsigned int *cfg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs40l50_pll_cfg); i++) {
+ if (cs40l50_pll_cfg[i].freq == freq) {
+ *cfg = cs40l50_pll_cfg[i].cfg;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int cs40l50_swap_ext_clk(struct cs40l50_codec *codec, const unsigned int clk_src)
+{
+ unsigned int cfg;
+ int ret;
+
+ switch (clk_src) {
+ case CS40L50_PLL_REFCLK_BCLK:
+ ret = cs40l50_get_clk_config(codec->bclk_ratio * codec->rate, &cfg);
+ if (ret)
+ return ret;
+ break;
+ case CS40L50_PLL_REFCLK_MCLK:
+ cfg = CS40L50_PLL_REEFCLK_MCLK_CFG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+ CS40L50_PLL_REFCLK_LOOP_MASK,
+ CS40L50_PLL_REFCLK_OPEN_LOOP <<
+ CS40L50_PLL_REFCLK_LOOP_SHIFT);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+ CS40L50_PLL_REFCLK_FREQ_MASK |
+ CS40L50_PLL_REFCLK_SEL_MASK,
+ (cfg << CS40L50_PLL_REFCLK_FREQ_SHIFT) | clk_src);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(codec->regmap, CS40L50_REFCLK_INPUT,
+ CS40L50_PLL_REFCLK_LOOP_MASK,
+ CS40L50_PLL_REFCLK_CLOSED_LOOP <<
+ CS40L50_PLL_REFCLK_LOOP_SHIFT);
+}
+
+static int cs40l50_clk_en(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *comp = snd_soc_dapm_to_component(w->dapm);
+ struct cs40l50_codec *codec = snd_soc_component_get_drvdata(comp);
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_STOP_PLAYBACK);
+ if (ret)
+ return ret;
+
+ ret = cs40l50_dsp_write(codec->dev, codec->regmap, CS40L50_START_I2S);
+ if (ret)
+ return ret;
+
+ ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_BCLK);
+ if (ret)
+ return ret;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ret = cs40l50_swap_ext_clk(codec, CS40L50_PLL_REFCLK_MCLK);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cs40l50_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY_S("ASP PLL", 0, SND_SOC_NOPM, 0, 0, cs40l50_clk_en,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route cs40l50_dapm_routes[] = {
+ { "ASP Playback", NULL, "ASP PLL" },
+ { "ASPRX1", NULL, "ASP Playback" },
+ { "ASPRX2", NULL, "ASP Playback" },
+
+ { "OUT", NULL, "ASPRX1" },
+ { "OUT", NULL, "ASPRX2" },
+};
+
+static int cs40l50_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct cs40l50_codec *codec = snd_soc_component_get_drvdata(codec_dai->component);
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBC_CFC)
+ return -EINVAL;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ codec->daifmt = 0;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ codec->daifmt = CS40L50_ASP_BCLK_INV_MASK;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ codec->daifmt = CS40L50_ASP_FSYNC_INV_MASK | CS40L50_ASP_BCLK_INV_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid clock invert\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ codec->daifmt |= FIELD_PREP(CS40L50_ASP_FMT_MASK, CS40L50_ASP_FMT_I2S);
+ break;
+ default:
+ dev_err(codec->dev, "Unsupported DAI format\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs40l50_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);
+ unsigned int asp_rx_wl = params_width(params);
+ int ret;
+
+ codec->rate = params_rate(params);
+
+ ret = regmap_update_bits(codec->regmap, CS40L50_ASP_DATA_CONTROL5,
+ CS40L50_ASP_RX_WL_MASK, asp_rx_wl);
+ if (ret)
+ return ret;
+
+ codec->daifmt |= (asp_rx_wl << CS40L50_ASP_RX_WIDTH_SHIFT);
+
+ return regmap_update_bits(codec->regmap, CS40L50_ASP_CONTROL2,
+ CS40L50_ASP_FSYNC_INV_MASK |
+ CS40L50_ASP_BCLK_INV_MASK |
+ CS40L50_ASP_FMT_MASK |
+ CS40L50_ASP_RX_WIDTH_MASK, codec->daifmt);
+}
+
+static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+ struct cs40l50_codec *codec = snd_soc_component_get_drvdata(dai->component);
+
+ codec->bclk_ratio = ratio;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs40l50_dai_ops = {
+ .set_fmt = cs40l50_set_dai_fmt,
+ .set_bclk_ratio = cs40l50_set_dai_bclk_ratio,
+ .hw_params = cs40l50_hw_params,
+};
+
+static struct snd_soc_dai_driver cs40l50_dai[] = {
+ {
+ .name = "cs40l50-pcm",
+ .id = 0,
+ .playback = {
+ .stream_name = "ASP Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &cs40l50_dai_ops,
+ },
+};
+
+static int cs40l50_codec_probe(struct snd_soc_component *component)
+{
+ struct cs40l50_codec *codec = snd_soc_component_get_drvdata(component);
+
+ codec->bclk_ratio = CS40L50_BCLK_RATIO_DEFAULT;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_cs40l50 = {
+ .probe = cs40l50_codec_probe,
+ .dapm_widgets = cs40l50_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs40l50_dapm_widgets),
+ .dapm_routes = cs40l50_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs40l50_dapm_routes),
+};
+
+static int cs40l50_codec_driver_probe(struct platform_device *pdev)
+{
+ struct cs40l50 *cs40l50 = dev_get_drvdata(pdev->dev.parent);
+ struct cs40l50_codec *codec;
+
+ codec = devm_kzalloc(&pdev->dev, sizeof(*codec), GFP_KERNEL);
+ if (!codec)
+ return -ENOMEM;
+
+ codec->regmap = cs40l50->regmap;
+ codec->dev = &pdev->dev;
+
+ return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cs40l50,
+ cs40l50_dai, ARRAY_SIZE(cs40l50_dai));
+}
+
+static const struct platform_device_id cs40l50_id[] = {
+ { "cs40l50-codec", },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, cs40l50_id);
+
+static struct platform_driver cs40l50_codec_driver = {
+ .probe = cs40l50_codec_driver_probe,
+ .id_table = cs40l50_id,
+ .driver = {
+ .name = "cs40l50-codec",
+ },
+};
+module_platform_driver(cs40l50_codec_driver);
+
+MODULE_DESCRIPTION("ASoC CS40L50 driver");
+MODULE_AUTHOR("James Ogletree <james.ogletree@cirrus.com>");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH v11 1/5] firmware: cs_dsp: Add write sequence interface
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree,
Charles Keepax
In-Reply-To: <20240605135249.361082-1-jogletre@opensource.cirrus.com>
A write sequence is a sequence of register addresses
and values executed by some Cirrus DSPs following
certain power state transitions.
Add support for Cirrus drivers to update or add to a
write sequence present in firmware.
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
drivers/firmware/cirrus/cs_dsp.c | 278 +++++++++++++++++++++++++
include/linux/firmware/cirrus/cs_dsp.h | 27 +++
2 files changed, 305 insertions(+)
diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c
index 79d4254d1f9b..6d886ffea10f 100644
--- a/drivers/firmware/cirrus/cs_dsp.c
+++ b/drivers/firmware/cirrus/cs_dsp.c
@@ -275,6 +275,12 @@
#define HALO_MPU_VIO_ERR_SRC_MASK 0x00007fff
#define HALO_MPU_VIO_ERR_SRC_SHIFT 0
+/*
+ * Write Sequence
+ */
+#define WSEQ_OP_MAX_WORDS 3
+#define WSEQ_END_OF_SCRIPT 0xFFFFFF
+
struct cs_dsp_ops {
bool (*validate_version)(struct cs_dsp *dsp, unsigned int version);
unsigned int (*parse_sizes)(struct cs_dsp *dsp,
@@ -3339,6 +3345,278 @@ int cs_dsp_chunk_read(struct cs_dsp_chunk *ch, int nbits)
}
EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_read, FW_CS_DSP);
+
+struct cs_dsp_wseq_op {
+ struct list_head list;
+ u32 address;
+ u32 data;
+ u16 offset;
+ u8 operation;
+};
+
+static void cs_dsp_wseq_clear(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+ struct cs_dsp_wseq_op *op, *op_tmp;
+
+ list_for_each_entry_safe(op, op_tmp, &wseq->ops, list) {
+ list_del(&op->list);
+ devm_kfree(dsp->dev, op);
+ }
+}
+
+static int cs_dsp_populate_wseq(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq)
+{
+ struct cs_dsp_wseq_op *op = NULL;
+ struct cs_dsp_chunk chunk;
+ u8 *words;
+ int ret;
+
+ if (!wseq->ctl) {
+ cs_dsp_err(dsp, "No control for write sequence\n");
+ return -EINVAL;
+ }
+
+ words = kzalloc(wseq->ctl->len, GFP_KERNEL);
+ if (!words)
+ return -ENOMEM;
+
+ ret = cs_dsp_coeff_read_ctrl(wseq->ctl, 0, words, wseq->ctl->len);
+ if (ret) {
+ cs_dsp_err(dsp, "Failed to read %s: %d\n", wseq->ctl->subname, ret);
+ goto err_free;
+ }
+
+ INIT_LIST_HEAD(&wseq->ops);
+
+ chunk = cs_dsp_chunk(words, wseq->ctl->len);
+
+ while (!cs_dsp_chunk_end(&chunk)) {
+ op = devm_kzalloc(dsp->dev, sizeof(*op), GFP_KERNEL);
+ if (!op) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ op->offset = cs_dsp_chunk_bytes(&chunk);
+ op->operation = cs_dsp_chunk_read(&chunk, 8);
+
+ switch (op->operation) {
+ case CS_DSP_WSEQ_END:
+ op->data = WSEQ_END_OF_SCRIPT;
+ break;
+ case CS_DSP_WSEQ_UNLOCK:
+ op->data = cs_dsp_chunk_read(&chunk, 16);
+ break;
+ case CS_DSP_WSEQ_ADDR8:
+ op->address = cs_dsp_chunk_read(&chunk, 8);
+ op->data = cs_dsp_chunk_read(&chunk, 32);
+ break;
+ case CS_DSP_WSEQ_H16:
+ case CS_DSP_WSEQ_L16:
+ op->address = cs_dsp_chunk_read(&chunk, 24);
+ op->data = cs_dsp_chunk_read(&chunk, 16);
+ break;
+ case CS_DSP_WSEQ_FULL:
+ op->address = cs_dsp_chunk_read(&chunk, 32);
+ op->data = cs_dsp_chunk_read(&chunk, 32);
+ break;
+ default:
+ ret = -EINVAL;
+ cs_dsp_err(dsp, "Unsupported op: %X\n", op->operation);
+ devm_kfree(dsp->dev, op);
+ goto err_free;
+ }
+
+ list_add_tail(&op->list, &wseq->ops);
+
+ if (op->operation == CS_DSP_WSEQ_END)
+ break;
+ }
+
+ if (op && op->operation != CS_DSP_WSEQ_END) {
+ cs_dsp_err(dsp, "%s missing end terminator\n", wseq->ctl->subname);
+ ret = -ENOENT;
+ }
+
+err_free:
+ kfree(words);
+
+ return ret;
+}
+
+/**
+ * cs_dsp_wseq_init() - Initialize write sequences contained within the loaded DSP firmware
+ * @dsp: Pointer to DSP structure
+ * @wseqs: List of write sequences to initialize
+ * @num_wseqs: Number of write sequences to initialize
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs)
+{
+ int i, ret;
+
+ lockdep_assert_held(&dsp->pwr_lock);
+
+ for (i = 0; i < num_wseqs; i++) {
+ ret = cs_dsp_populate_wseq(dsp, &wseqs[i]);
+ if (ret) {
+ cs_dsp_wseq_clear(dsp, &wseqs[i]);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_init, FW_CS_DSP);
+
+static struct cs_dsp_wseq_op *cs_dsp_wseq_find_op(u32 addr, u8 op_code,
+ struct list_head *wseq_ops)
+{
+ struct cs_dsp_wseq_op *op;
+
+ list_for_each_entry(op, wseq_ops, list) {
+ if (op->operation == op_code && op->address == addr)
+ return op;
+ }
+
+ return NULL;
+}
+
+/**
+ * cs_dsp_wseq_write() - Add or update an entry in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @addr: Address of the register to be written to
+ * @data: Data to be written
+ * @op_code: The type of operation of the new entry
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function formats register address and value pairs into the format
+ * required for write sequence entries, and either updates or adds the
+ * new entry into the write sequence.
+ *
+ * If update is set to true and no matching entry is found, it will add a new entry.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+ u32 addr, u32 data, u8 op_code, bool update)
+{
+ struct cs_dsp_wseq_op *op_end, *op_new = NULL;
+ u32 words[WSEQ_OP_MAX_WORDS];
+ struct cs_dsp_chunk chunk;
+ int new_op_size, ret;
+
+ if (update)
+ op_new = cs_dsp_wseq_find_op(addr, op_code, &wseq->ops);
+
+ /* If entry to update is not found, treat it as a new operation */
+ if (!op_new) {
+ op_end = cs_dsp_wseq_find_op(0, CS_DSP_WSEQ_END, &wseq->ops);
+ if (!op_end) {
+ cs_dsp_err(dsp, "Missing terminator for %s\n", wseq->ctl->subname);
+ return -EINVAL;
+ }
+
+ op_new = devm_kzalloc(dsp->dev, sizeof(*op_new), GFP_KERNEL);
+ if (!op_new)
+ return -ENOMEM;
+
+ op_new->operation = op_code;
+ op_new->address = addr;
+ op_new->offset = op_end->offset;
+ update = false;
+ }
+
+ op_new->data = data;
+
+ chunk = cs_dsp_chunk(words, sizeof(words));
+ cs_dsp_chunk_write(&chunk, 8, op_new->operation);
+
+ switch (op_code) {
+ case CS_DSP_WSEQ_FULL:
+ cs_dsp_chunk_write(&chunk, 32, op_new->address);
+ cs_dsp_chunk_write(&chunk, 32, op_new->data);
+ break;
+ case CS_DSP_WSEQ_L16:
+ case CS_DSP_WSEQ_H16:
+ cs_dsp_chunk_write(&chunk, 24, op_new->address);
+ cs_dsp_chunk_write(&chunk, 16, op_new->data);
+ break;
+ default:
+ ret = -EINVAL;
+ cs_dsp_err(dsp, "Operation %X not supported\n", op_code);
+ goto op_new_free;
+ }
+
+ new_op_size = cs_dsp_chunk_bytes(&chunk);
+
+ if (!update) {
+ if (wseq->ctl->len - op_end->offset < new_op_size) {
+ cs_dsp_err(dsp, "Not enough memory in %s for entry\n", wseq->ctl->subname);
+ ret = -E2BIG;
+ goto op_new_free;
+ }
+
+ op_end->offset += new_op_size;
+
+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_end->offset / sizeof(u32),
+ &op_end->data, sizeof(u32));
+ if (ret)
+ goto op_new_free;
+
+ list_add_tail(&op_new->list, &op_end->list);
+ }
+
+ ret = cs_dsp_coeff_write_ctrl(wseq->ctl, op_new->offset / sizeof(u32),
+ words, new_op_size);
+ if (ret)
+ goto op_new_free;
+
+ return 0;
+
+op_new_free:
+ devm_kfree(dsp->dev, op_new);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_write, FW_CS_DSP);
+
+/**
+ * cs_dsp_wseq_multi_write() - Add or update multiple entries in a write sequence
+ * @dsp: Pointer to a DSP structure
+ * @wseq: Write sequence to write to
+ * @reg_seq: List of address-data pairs
+ * @num_regs: Number of address-data pairs
+ * @op_code: The types of operations of the new entries
+ * @update: If true, searches for the first entry in the write sequence with
+ * the same address and op_code, and replaces it. If false, creates a new entry
+ * at the tail
+ *
+ * This function calls cs_dsp_wseq_write() for multiple address-data pairs.
+ *
+ * Return: Zero for success, a negative number on error.
+ */
+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+ const struct reg_sequence *reg_seq, int num_regs,
+ u8 op_code, bool update)
+{
+ int i, ret;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = cs_dsp_wseq_write(dsp, wseq, reg_seq[i].reg,
+ reg_seq[i].def, op_code, update);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cs_dsp_wseq_multi_write, FW_CS_DSP);
+
MODULE_DESCRIPTION("Cirrus Logic DSP Support");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL v2");
diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h
index 29cd11d5a3cf..4cef6fafa1d8 100644
--- a/include/linux/firmware/cirrus/cs_dsp.h
+++ b/include/linux/firmware/cirrus/cs_dsp.h
@@ -42,6 +42,16 @@
#define CS_DSP_ACKED_CTL_MIN_VALUE 0
#define CS_DSP_ACKED_CTL_MAX_VALUE 0xFFFFFF
+/*
+ * Write sequence operation codes
+ */
+#define CS_DSP_WSEQ_FULL 0x00
+#define CS_DSP_WSEQ_ADDR8 0x02
+#define CS_DSP_WSEQ_L16 0x04
+#define CS_DSP_WSEQ_H16 0x05
+#define CS_DSP_WSEQ_UNLOCK 0xFD
+#define CS_DSP_WSEQ_END 0xFF
+
/**
* struct cs_dsp_region - Describes a logical memory region in DSP address space
* @type: Memory region type
@@ -255,6 +265,23 @@ struct cs_dsp_alg_region *cs_dsp_find_alg_region(struct cs_dsp *dsp,
const char *cs_dsp_mem_region_name(unsigned int type);
+/**
+ * struct cs_dsp_wseq - Describes a write sequence
+ * @ctl: Write sequence cs_dsp control
+ * @ops: Operations contained within
+ */
+struct cs_dsp_wseq {
+ struct cs_dsp_coeff_ctl *ctl;
+ struct list_head ops;
+};
+
+int cs_dsp_wseq_init(struct cs_dsp *dsp, struct cs_dsp_wseq *wseqs, unsigned int num_wseqs);
+int cs_dsp_wseq_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq, u32 addr, u32 data,
+ u8 op_code, bool update);
+int cs_dsp_wseq_multi_write(struct cs_dsp *dsp, struct cs_dsp_wseq *wseq,
+ const struct reg_sequence *reg_seq, int num_regs,
+ u8 op_code, bool update);
+
/**
* struct cs_dsp_chunk - Describes a buffer holding data formatted for the DSP
* @data: Pointer to underlying buffer memory
--
2.34.1
^ permalink raw reply related
* [PATCH v11 2/5] dt-bindings: input: cirrus,cs40l50: Add initial DT binding
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree,
Krzysztof Kozlowski
In-Reply-To: <20240605135249.361082-1-jogletre@opensource.cirrus.com>
CS40L50 is a haptic driver with waveform memory,
integrated DSP, and closed-loop algorithms.
Add a YAML DT binding document for this device.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: James Ogletree <jogletre@opensource.cirrus.com>
---
.../bindings/input/cirrus,cs40l50.yaml | 68 +++++++++++++++++++
MAINTAINERS | 8 +++
2 files changed, 76 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
diff --git a/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
new file mode 100644
index 000000000000..89bd06864bd4
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/cirrus,cs40l50.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS40L50 Advanced Haptic Driver
+
+maintainers:
+ - James Ogletree <jogletre@opensource.cirrus.com>
+
+description:
+ CS40L50 is a haptic driver with waveform memory,
+ integrated DSP, and closed-loop algorithms.
+
+properties:
+ compatible:
+ enum:
+ - cirrus,cs40l50
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ vdd-a-supply:
+ description: Power supply for internal analog circuits.
+
+ vdd-p-supply:
+ description: Power supply for always-on circuits.
+
+ vdd-io-supply:
+ description: Power supply for digital input/output.
+
+ vdd-b-supply:
+ description: Power supply for the boost converter.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - reset-gpios
+ - vdd-io-supply
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ haptic-driver@34 {
+ compatible = "cirrus,cs40l50";
+ reg = <0x34>;
+ interrupt-parent = <&gpio>;
+ interrupts = <113 IRQ_TYPE_LEVEL_LOW>;
+ reset-gpios = <&gpio 112 GPIO_ACTIVE_LOW>;
+ vdd-io-supply = <&vreg>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index dd5de540ec0b..862a18e3fc7b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4933,6 +4933,14 @@ F: sound/pci/hda/cs*
F: sound/pci/hda/hda_cs_dsp_ctl.*
F: sound/soc/codecs/cs*
+CIRRUS LOGIC HAPTIC DRIVERS
+M: James Ogletree <jogletre@opensource.cirrus.com>
+M: Fred Treven <fred.treven@cirrus.com>
+M: Ben Bright <ben.bright@cirrus.com>
+L: patches@opensource.cirrus.com
+S: Supported
+F: Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
+
CIRRUS LOGIC DSP FIRMWARE DRIVER
M: Simon Trimmer <simont@opensource.cirrus.com>
M: Charles Keepax <ckeepax@opensource.cirrus.com>
--
2.34.1
^ permalink raw reply related
* [PATCH v11 0/5] Add support for CS40L50
From: James Ogletree @ 2024-06-05 13:52 UTC (permalink / raw)
To: dmitry.torokhov, robh+dt, krzysztof.kozlowski+dt, conor+dt, lee,
broonie, jeff
Cc: patches, linux-sound, linux-input, devicetree, James Ogletree
Changes in v11:
- Constified function parameters in ASOC driver
- Removed unneeded pm_runtime.h include
- Initializing with number of max FF effects = 1
- Minor refactoring in Input driver
- Reworded comment in MFD driver
Changes in v10:
- Minor refactoring and logical improvements all around
- Renamed and added supplies
Changes in v9:
- Fixed empty struct by utilizing cs_dsp's post_run callback
- Style fixes in MFD driver
Changes in v8:
- set_sysclk() -> set_bclk_ratio()
- Added ID table to codec driver
- Style improvements
- Fixed ordering of new write sequence operations
Changes in v7:
- Fixed sparse warning
- Moved write sequences to private data structure
- Logical and style improvements in write sequence interface
Changes in v6:
- Updated write sequencer interface to be control-name based
- Fixed a race condition and non-handling of repeats in playback callback
- Stylistic and logical improvements all around
Changes in v5:
- Added a codec sub-device to support I2S streaming
- Moved write sequencer code from cirrus_haptics to cs_dsp
- Reverted cirrus_haptics library; future Cirrus input
drivers will export and utilize cs40l50_vibra functions
- Added more comments
- Many small stylistic and logical improvements
Changes in v4:
- Moved from Input to MFD
- Moved common Cirrus haptic functions to a library
- Incorporated runtime PM framework
- Many style improvements
Changes in v3:
- YAML formatting corrections
- Fixed typo in MAINTAINERS
- Used generic node name "haptic-driver"
- Fixed probe error code paths
- Switched to "sizeof(*)"
- Removed tree reference in MAINTAINERS
Changes in v2:
- Fixed checkpatch warnings
James Ogletree (5):
firmware: cs_dsp: Add write sequence interface
dt-bindings: input: cirrus,cs40l50: Add initial DT binding
mfd: cs40l50: Add support for CS40L50 core driver
Input: cs40l50 - Add support for the CS40L50 haptic driver
ASoC: cs40l50: Support I2S streaming to CS40L50
.../bindings/input/cirrus,cs40l50.yaml | 68 +++
MAINTAINERS | 12 +
drivers/firmware/cirrus/cs_dsp.c | 278 +++++++++
drivers/input/misc/Kconfig | 10 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/cs40l50-vibra.c | 555 +++++++++++++++++
drivers/mfd/Kconfig | 30 +
drivers/mfd/Makefile | 4 +
drivers/mfd/cs40l50-core.c | 570 ++++++++++++++++++
drivers/mfd/cs40l50-i2c.c | 68 +++
drivers/mfd/cs40l50-spi.c | 68 +++
include/linux/firmware/cirrus/cs_dsp.h | 27 +
include/linux/mfd/cs40l50.h | 137 +++++
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/cs40l50-codec.c | 307 ++++++++++
16 files changed, 2148 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/cirrus,cs40l50.yaml
create mode 100644 drivers/input/misc/cs40l50-vibra.c
create mode 100644 drivers/mfd/cs40l50-core.c
create mode 100644 drivers/mfd/cs40l50-i2c.c
create mode 100644 drivers/mfd/cs40l50-spi.c
create mode 100644 include/linux/mfd/cs40l50.h
create mode 100644 sound/soc/codecs/cs40l50-codec.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH v13 3/3] Input: Add TouchNetix axiom i2c touchscreen driver
From: Kamel BOUHARA @ 2024-06-05 13:48 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
linux-input, linux-kernel, devicetree, Marco Felsch, Jeff LaBundy,
catalin.popescu, mark.satterthwaite, Thomas Petazzoni,
Gregory Clement, bsp-development.geo
In-Reply-To: <20240605124746.GA57733@tpx1.home>
[...]
>> > +
>> > + error = devm_request_threaded_irq(dev, client->irq, NULL,
>> > + axiom_irq, IRQF_ONESHOT, dev_name(dev), ts);
>> > + if (error) {
>> > + dev_info(dev, "Request irq failed, falling back to polling mode");
>>
>> I do not think you should fall back to polling mode if you fail to get
>> interrupt. If it was not specified (client->irq) then I can see that
>> we
>> might want to fall back, but if the system configured for using
>> interrupt and you can not get it you should bail out.
>>
>
> Yes, clear, the polling mode can be decorrelated to the irq not
> provided
> case.
Just to make sure I understood, is this what you propose ?
if (client->irq) {
error = devm_request_threaded_irq(...)
if (error) {
dev_warn(dev, "failed to request IRQ\n");
client->irq = 0;
}
}
if(!client->irq) {
// setup polling stuff
...
}
--
Kamel Bouhara, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v13 3/3] Input: Add TouchNetix axiom i2c touchscreen driver
From: Kamel Bouhara @ 2024-06-05 12:47 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
linux-input, linux-kernel, devicetree, Marco Felsch, Jeff LaBundy,
catalin.popescu, mark.satterthwaite, Thomas Petazzoni,
Gregory Clement, bsp-development.geo
In-Reply-To: <Zl5ZmYyntq7OJOvZ@google.com>
Le Mon, Jun 03, 2024 at 05:02:33PM -0700, Dmitry Torokhov a écrit :
> Hi Kamel,
>
Hi Dmitry,
> On Mon, Jun 03, 2024 at 05:39:25PM +0200, Kamel Bouhara wrote:
> > Add a new driver for the TouchNetix's axiom family of
> > touchscreen controllers. This driver only supports i2c
> > and can be later adapted for SPI and USB support.
> >
> > Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
> > ---
> > MAINTAINERS | 2 +
> > drivers/input/touchscreen/Kconfig | 14 +
> > drivers/input/touchscreen/Makefile | 1 +
> > drivers/input/touchscreen/touchnetix_axiom.c | 657 +++++++++++++++++++
> > 4 files changed, 674 insertions(+)
> > create mode 100644 drivers/input/touchscreen/touchnetix_axiom.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 225309db4110..a3ece12e32d7 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -22744,9 +22744,11 @@ F: drivers/platform/x86/toshiba-wmi.c
> >
> > TOUCHNETIX AXIOM I2C TOUCHSCREEN DRIVER
> > M: Kamel Bouhara <kamel.bouhara@bootlin.com>
> > +M: bsp-development.geo@leica-geosystems.com
> > L: linux-input@vger.kernel.org
> > S: Maintained
> > F: Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml
> > +F: drivers/input/touchscreen/touchnetix_axiom.c
> >
> > TPM DEVICE DRIVER
> > M: Peter Huewe <peterhuewe@gmx.de>
> > diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> > index c821fe3ee794..1ce8f1c25625 100644
> > --- a/drivers/input/touchscreen/Kconfig
> > +++ b/drivers/input/touchscreen/Kconfig
> > @@ -834,6 +834,20 @@ config TOUCHSCREEN_MIGOR
> > To compile this driver as a module, choose M here: the
> > module will be called migor_ts.
> >
> > +config TOUCHSCREEN_TOUCHNETIX_AXIOM
> > + tristate "TouchNetix AXIOM based touchscreen controllers"
> > + depends on I2C
> > + select CRC16
> > + select REGMAP_I2C
> > + help
> > + Say Y here if you have a axiom touchscreen connected to
> > + your system.
> > +
> > + If unsure, say N.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called axiom.
> > +
> > config TOUCHSCREEN_TOUCHRIGHT
> > tristate "Touchright serial touchscreen"
> > select SERIO
> > diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> > index a81cb5aa21a5..6ce7b804adc7 100644
> > --- a/drivers/input/touchscreen/Makefile
> > +++ b/drivers/input/touchscreen/Makefile
> > @@ -91,6 +91,7 @@ obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
> > obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) += surface3_spi.o
> > obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
> > obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
> > +obj-$(CONFIG_TOUCHSCREEN_TOUCHNETIX_AXIOM) += touchnetix_axiom.o
> > obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
> > obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
> > obj-$(CONFIG_TOUCHSCREEN_TS4800) += ts4800-ts.o
> > diff --git a/drivers/input/touchscreen/touchnetix_axiom.c b/drivers/input/touchscreen/touchnetix_axiom.c
> > new file mode 100644
> > index 000000000000..09550847392e
> > --- /dev/null
> > +++ b/drivers/input/touchscreen/touchnetix_axiom.c
> > @@ -0,0 +1,657 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * TouchNetix axiom Touchscreen Driver
> > + *
> > + * Copyright (C) 2020-2023 TouchNetix Ltd.
> > + *
> > + * Author(s): Bart Prescott <bartp@baasheep.co.uk>
> > + * Pedro Torruella <pedro.torruella@touchnetix.com>
> > + * Mark Satterthwaite <mark.satterthwaite@touchnetix.com>
> > + * Hannah Rossiter <hannah.rossiter@touchnetix.com>
> > + * Kamel Bouhara <kamel.bouhara@bootlin.com>
> > + *
> > + */
> > +#include <linux/bitfield.h>
> > +#include <linux/crc16.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/input.h>
> > +#include <linux/input/mt.h>
> > +#include <linux/input/touchscreen.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/regmap.h>
> > +
> > +#include <asm/unaligned.h>
> > +#define AXIOM_PROX_LEVEL -128
> > +#define AXIOM_DMA_OPS_DELAY_USEC 250
> > +#define AXIOM_STARTUP_TIME_MS 110
> > +/*
> > + * Register group u31 has 2 pages for usage table entries.
> > + */
> > +#define AXIOM_U31_MAX_USAGES 0xff
> > +#define AXIOM_U31_BYTES_PER_USAGE 6
> > +#define AXIOM_U31_PAGE0_LENGTH 0x0C
> > +#define AXIOM_U31_BOOTMODE_MASK BIT(7)
> > +#define AXIOM_U31_DEVID_MASK GENMASK(14, 0)
> > +
> > +#define AXIOM_MAX_REPORT_LEN 0x7f
> > +
> > +#define AXIOM_CMD_HEADER_READ_MASK BIT(15)
> > +#define AXIOM_U41_MAX_TARGETS 10
> > +
> > +#define AXIOM_U46_AUX_CHANNELS 4
> > +#define AXIOM_U46_AUX_MASK GENMASK(11, 0)
> > +
> > +#define AXIOM_COMMS_MAX_USAGE_PAGES 3
> > +#define AXIOM_COMMS_PAGE_SIZE 256
> > +#define AXIOM_COMMS_REPORT_LEN_MASK GENMASK(6, 0)
> > +
> > +#define AXIOM_REPORT_USAGE_ID 0x34
> > +#define AXIOM_DEVINFO_USAGE_ID 0x31
> > +#define AXIOM_USAGE_2HB_REPORT_ID 0x01
> > +#define AXIOM_USAGE_2AUX_REPORT_ID 0x46
> > +#define AXIOM_USAGE_2DCTS_REPORT_ID 0x41
> > +
> > +#define AXIOM_PAGE_OFFSET_MASK GENMASK(6, 0)
> > +
> > +struct axiom_devinfo {
> > + __le16 device_id;
> > + u8 fw_minor;
> > + u8 fw_major;
> > + u8 fw_info_extra;
> > + u8 tcp_revision;
> > + u8 bootloader_fw_minor;
> > + u8 bootloader_fw_major;
> > + __le16 jedec_id;
> > + u8 num_usages;
> > +} __packed;
> > +
> > +/*
> > + * Describes parameters of a specific usage, essentially a single element of
> > + * the "Usage Table"
> > + */
> > +struct axiom_usage_entry {
> > + u8 id;
> > + u8 is_report;
>
> This is probably a 'bool'.
Fixed thanks.
>
> > + u8 start_page;
> > + u8 num_pages;
> > +};
> > +
> > +/*
> > + * Represents state of a touch or target when detected prior to a touch (eg.
> > + * hover or proximity events).
> > + */
> > +enum axiom_target_state {
> > + AXIOM_TARGET_STATE_NOT_PRESENT = 0,
> > + AXIOM_TARGET_STATE_PROX = 1,
> > + AXIOM_TARGET_STATE_HOVER = 2,
> > + AXIOM_TARGET_STATE_TOUCHING = 3,
> > +};
> > +
> > +struct axiom_u41_target {
> > + enum axiom_target_state state;
> > + u16 x;
> > + u16 y;
> > + s8 z;
> > + bool insert;
>
> What does "insert" mean here?
I think this means target data are reported.
>
> > + bool touch;
> > +};
> > +
> > +struct axiom_target_report {
> > + u8 index;
> > + u8 present;
>
> bool?
>
> > + u16 x;
> > + u16 y;
> > + s8 z;
> > +};
> > +
> > +struct axiom_cmd_header {
> > + __le16 target_address;
> > + __le16 length;
> > +} __packed;
>
> You do not need to declare this as packed, it is naturally aligned. You
> can also make it a union with __le32 to ensure overall alignment.
Ack, fixed for v14.
>
> > +
> > +struct axiom_data {
> > + struct axiom_devinfo devinfo;
> > + struct device *dev;
> > + struct gpio_desc *reset_gpio;
> > + struct i2c_client *client;
> > + struct input_dev *input_dev;
> > + u32 max_report_len;
> > + u8 rx_buf[AXIOM_COMMS_MAX_USAGE_PAGES * AXIOM_COMMS_PAGE_SIZE];
> > + struct axiom_u41_target targets[AXIOM_U41_MAX_TARGETS];
> > + struct axiom_usage_entry usage_table[AXIOM_U31_MAX_USAGES];
> > + bool usage_table_populated;
> > + struct regmap *regmap;
> > + struct touchscreen_properties prop;
> > +};
> > +
> > +static const struct regmap_config axiom_i2c_regmap_config = {
> > + .reg_bits = 32,
> > + .reg_format_endian = REGMAP_ENDIAN_LITTLE,
> > + .val_bits = 8,
> > + .val_format_endian = REGMAP_ENDIAN_LITTLE,
> > +};
> > +
> > +/*
> > + * axiom devices are typically configured to report touches at a rate
> > + * of 100Hz (10ms) for systems that require polling for reports.
> > + * When reports are polled, it will be expected to occasionally
> > + * observe the overflow bit being set in the reports.
> > + * This indicates that reports are not being read fast enough.
> > + */
> > +#define POLL_INTERVAL_DEFAULT_MS 10
> > +
> > +/* Translate usage/page/offset triplet into physical address. */
> > +static u16 axiom_usage_to_target_address(struct axiom_data *ts, u8 usage, u8 page,
> > + char offset)
> > +{
> > + /* At the moment the convention is that u31 is always at physical address 0x0 */
> > + if (!ts->usage_table_populated) {
> > + if (usage == AXIOM_DEVINFO_USAGE_ID)
> > + return ((page << 8) + offset);
> > + else
> > + return 0xffff;
> > + }
> > +
> > + if (page >= ts->usage_table[usage].num_pages) {
> > + dev_err(ts->dev, "Invalid usage table! usage: u%02x, page: %02x, offset: %02x\n",
> > + usage, page, offset);
> > + return 0xffff;
> > + }
> > +
> > + return ((ts->usage_table[usage].start_page + page) << 8) + offset;
> > +}
> > +
> > +static int axiom_read(struct axiom_data *ts, u8 usage, u8 page, void *buf, u16 len)
> > +{
> > + struct axiom_cmd_header cmd_header;
> > + u32 preamble;
> > + int ret;
> > +
> > + cmd_header.target_address = cpu_to_le16(axiom_usage_to_target_address(ts, usage, page, 0));
> > + cmd_header.length = cpu_to_le16(len | AXIOM_CMD_HEADER_READ_MASK);
> > +
> > + preamble = get_unaligned_le32(&cmd_header);
> > +
> > + ret = regmap_write(ts->regmap, preamble, 0);
> > + if (ret) {
> > + dev_err(ts->dev, "failed to write preamble, error %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = regmap_raw_read(ts->regmap, 0, buf, len);
> > + if (ret) {
> > + dev_err(ts->dev, "failed to read target address %04x, error %d\n",
> > + cmd_header.target_address, ret);
> > + return ret;
> > + }
> > +
> > + /* Wait device's DMA operations */
> > + usleep_range(AXIOM_DMA_OPS_DELAY_USEC, AXIOM_DMA_OPS_DELAY_USEC + 50);
>
> What exactly are we waiting for after getting the data?
That is the minimum delay required the device need to flush it's
internal dma buffers.
>
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * One of the main purposes for reading the usage table is to identify
> > + * which usages reside at which target address.
> > + * When performing subsequent reads or writes to AXIOM, the target address
> > + * is used to specify which usage is being accessed.
> > + * Consider the following discovery code which will build up the usage table.
> > + */
> > +static u32 axiom_populate_usage_table(struct axiom_data *ts)
> > +{
> > + struct axiom_usage_entry *usage_table;
> > + u8 *rx_data = ts->rx_buf;
> > + u32 max_report_len = 0;
> > + u32 usage_id;
> > + int error;
> > +
> > + usage_table = ts->usage_table;
> > +
> > + /* Read the second page of usage u31 to get the usage table */
> > + error = axiom_read(ts, AXIOM_DEVINFO_USAGE_ID, 1, rx_data,
> > + (AXIOM_U31_BYTES_PER_USAGE * ts->devinfo.num_usages));
> > +
> > + if (error)
> > + return error;
> > +
> > + for (usage_id = 0; usage_id < ts->devinfo.num_usages; usage_id++) {
> > + u16 offset = (usage_id * AXIOM_U31_BYTES_PER_USAGE);
> > + u8 id = rx_data[offset + 0];
> > + u8 start_page = rx_data[offset + 1];
> > + u8 num_pages = rx_data[offset + 2];
> > + u32 max_offset = ((rx_data[offset + 3] & AXIOM_PAGE_OFFSET_MASK) + 1) * 2;
> > +
> > + usage_table[id].is_report = !num_pages;
> > +
> > + /* Store the entry into the usage table */
> > + usage_table[id].id = id;
> > + usage_table[id].start_page = start_page;
> > + usage_table[id].num_pages = num_pages;
> > +
> > + dev_dbg(ts->dev, "Usage u%02x Info: %*ph\n", id, AXIOM_U31_BYTES_PER_USAGE,
> > + &rx_data[offset]);
> > +
> > + /* Identify the max report length the module will receive */
> > + if (usage_table[id].is_report && max_offset > max_report_len)
> > + max_report_len = max_offset;
> > + }
> > +
> > + ts->usage_table_populated = true;
> > +
> > + return max_report_len;
> > +}
> > +
> > +static int axiom_discover(struct axiom_data *ts)
> > +{
> > + int error;
> > +
> > + /*
> > + * Fetch the first page of usage u31 to get the
> > + * device information and the number of usages
> > + */
> > + error = axiom_read(ts, AXIOM_DEVINFO_USAGE_ID, 0, &ts->devinfo, AXIOM_U31_PAGE0_LENGTH);
> > + if (error)
> > + return error;
> > +
> > + dev_dbg(ts->dev, " Boot Mode : %s\n",
> > + FIELD_GET(AXIOM_U31_BOOTMODE_MASK,
> > + le16_to_cpu(ts->devinfo.device_id)) ? "BLP" : "TCP");
> > + dev_dbg(ts->dev, " Device ID : %04lx\n",
> > + FIELD_GET(AXIOM_U31_DEVID_MASK, le16_to_cpu(ts->devinfo.device_id)));
> > + dev_dbg(ts->dev, " Firmware Rev : %02x.%02x\n", ts->devinfo.fw_major,
> > + ts->devinfo.fw_minor);
> > + dev_dbg(ts->dev, " Bootloader Rev : %02x.%02x\n", ts->devinfo.bootloader_fw_major,
> > + ts->devinfo.bootloader_fw_minor);
> > + dev_dbg(ts->dev, " FW Extra Info : %04x\n", ts->devinfo.fw_info_extra);
> > + dev_dbg(ts->dev, " Silicon : %04x\n", le16_to_cpu(ts->devinfo.jedec_id));
> > + dev_dbg(ts->dev, " Number usages : %04x\n", ts->devinfo.num_usages);
> > +
> > + ts->max_report_len = axiom_populate_usage_table(ts);
> > + if (!ts->max_report_len || !ts->devinfo.num_usages ||
> > + ts->max_report_len > AXIOM_MAX_REPORT_LEN) {
> > + dev_err(ts->dev, "Invalid report length or usages number");
> > + return -EINVAL;
> > + }
> > +
> > + dev_dbg(ts->dev, "Max Report Length: %u\n", ts->max_report_len);
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Support function to axiom_process_u41_report.
> > + * Generates input-subsystem events for every target.
> > + * After calling this function the caller shall issue
> > + * a Sync to the input sub-system.
> > + */
> > +static bool axiom_process_u41_report_target(struct axiom_data *ts,
> > + struct axiom_target_report *target)
> > +{
> > + struct input_dev *input_dev = ts->input_dev;
> > + struct axiom_u41_target *target_prev_state;
> > + enum axiom_target_state current_state;
> > + int id;
> > +
> > + /* Verify the target index */
> > + if (target->index >= AXIOM_U41_MAX_TARGETS) {
> > + dev_err(ts->dev, "Invalid target index! %u\n", target->index);
> > + return false;
> > + }
> > +
> > + target_prev_state = &ts->targets[target->index];
> > +
> > + current_state = AXIOM_TARGET_STATE_NOT_PRESENT;
> > +
> > + if (target->present) {
> > + if (target->z >= 0)
> > + current_state = AXIOM_TARGET_STATE_TOUCHING;
> > + else if (target->z > AXIOM_PROX_LEVEL && target->z < 0)
> > + current_state = AXIOM_TARGET_STATE_HOVER;
> > + else if (target->z == AXIOM_PROX_LEVEL)
> > + current_state = AXIOM_TARGET_STATE_PROX;
> > + }
> > +
> > + if (target_prev_state->state == current_state &&
> > + target_prev_state->x == target->x &&
> > + target_prev_state->y == target->y &&
> > + target_prev_state->z == target->z)
> > + return false;
>
> Why is this needed? Input/MT core already tries to suppress duplicate
> events, I do not think we need to do it here.
>
This indeed make sure we don't have twice the same report processed.
> > +
> > + id = target->index;
> > +
> > + dev_dbg(ts->dev, "U41 Target T%u, present:%u, x:%u, y:%u, z:%d\n",
> > + target->index, target->present,
> > + target->x, target->y, target->z);
> > +
> > + switch (current_state) {
> > + case AXIOM_TARGET_STATE_NOT_PRESENT:
> > + case AXIOM_TARGET_STATE_PROX:
> > + if (!target_prev_state->insert)
> > + break;
> > + target_prev_state->insert = false;
> > +
> > + if (!id)
> > + input_report_key(input_dev, BTN_TOUCH, 0);
> > +
> > + input_mt_report_slot_inactive(input_dev);
> > + /*
> > + * make sure the previous coordinates are
> > + * all off screen when the finger comes back
> > + */
> > + target->x = 65535;
> > + target->y = 65535;
> > + target->z = AXIOM_PROX_LEVEL;
> > + break;
> > + case AXIOM_TARGET_STATE_HOVER:
> > + case AXIOM_TARGET_STATE_TOUCHING:
> > + target_prev_state->insert = true;
> > + input_report_abs(input_dev, ABS_MT_TRACKING_ID, id);
> > + input_report_abs(input_dev, ABS_MT_POSITION_X, target->x);
> > + input_report_abs(input_dev, ABS_MT_POSITION_Y, target->y);
> > +
> > + if (current_state == AXIOM_TARGET_STATE_TOUCHING) {
> > + input_report_abs(input_dev, ABS_MT_DISTANCE, 0);
> > + input_report_abs(input_dev, ABS_DISTANCE, 0);
> > + input_report_abs(input_dev, ABS_MT_PRESSURE, target->z);
> > + input_report_abs(input_dev, ABS_PRESSURE, target->z);
>
> You only need to emit ABS_MT_DISTANCE and ABS_MT_PRESSURE. The
> single-touch variants should be generated by the input core as part of
> sending out the frame.
>
Ack, thanks.
> > + } else {
> > + input_report_abs(input_dev, ABS_MT_DISTANCE, -target->z);
> > + input_report_abs(input_dev, ABS_DISTANCE, -target->z);
> > + input_report_abs(input_dev, ABS_MT_PRESSURE, 0);
> > + input_report_abs(input_dev, ABS_PRESSURE, 0);
> > + }
> > +
> > + if (!id)
> > + input_report_key(input_dev, BTN_TOUCH, (current_state ==
> > + AXIOM_TARGET_STATE_TOUCHING));
>
> Why do you emit BTN_TOUCH manually instead of replying on
> input_mt_sync_frame() calling input_mt_pointer_emulation().
Sorry this really requires a comment line as it is to indicate when the
target is the first reported contact and Im actually not sure this
should be kept as it.
>
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + target_prev_state->state = current_state;
> > + target_prev_state->x = target->x;
> > + target_prev_state->y = target->y;
> > + target_prev_state->z = target->z;
> > +
> > + return true;
> > +}
> > +
> > +/*
> > + * U41 is the output report of the 2D CTS and contains the status of targets
> > + * (including contacts and pre-contacts) along with their X,Y,Z values.
> > + * When a target has been removed (no longer detected),
> > + * the corresponding X,Y,Z values will be zeroed.
> > + */
> > +static bool axiom_process_u41_report(struct axiom_data *ts, u8 *rx_buf)
> > +{
> > + struct axiom_target_report target;
> > + bool update_done = false;
> > + u16 target_status;
> > + int i;
> > +
> > + target_status = get_unaligned_le16(rx_buf + 1);
> > +
> > + for (i = 0; i < AXIOM_U41_MAX_TARGETS; i++) {
> > + u8 *target_step = &rx_buf[i * 4];
> > +
> > + target.index = i;
> > + input_mt_slot(ts->input_dev, i);
> > + input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
> > + target.present = ((target_status & (1 << i)) != 0) ? 1 : 0;
> > + target.x = get_unaligned_le16(target_step + 3);
> > + target.y = get_unaligned_le16(target_step + 5);
> > + target.z = (s8)(rx_buf[i + 43]);
> > + touchscreen_report_pos(ts->input_dev, &ts->prop, target.x, target.y, true);
>
> I find it confusing that you send out coordinates here, and the rest of
> events in axiom_process_u41_report_target()... Can it all be done in one
> place?
Sure this will be moved in axiom_process_u41_report_target() for v14.
>
> > + update_done |= axiom_process_u41_report_target(ts, &target);
> > + }
> > +
> > + return update_done;
> > +}
> > +
> > +/*
> > + * U46 report contains a low level measurement data generated by the capacitive
> > + * displacement sensor (CDS) algorithms from the auxiliary channels.
> > + * This information is useful when tuning multi-press to assess mechanical
> > + * consistency in the unit's construction.
> > + */
> > +static void axiom_process_u46_report(struct axiom_data *ts, u8 *rx_buf)
> > +{
> > + struct input_dev *input_dev = ts->input_dev;
> > + u32 event_value;
> > + u16 aux_value;
> > + int i;
> > +
> > + for (i = 0; i < AXIOM_U46_AUX_CHANNELS; i++) {
> > + u8 *target_step = &rx_buf[i * 2];
> > +
> > + input_mt_slot(input_dev, i);
> > + input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
> > + aux_value = get_unaligned_le16(target_step + 1) & AXIOM_U46_AUX_MASK;
> > + event_value = (i << 16) | (aux_value);
> > + input_event(input_dev, EV_MSC, MSC_RAW, event_value);
>
> This does not really belong to the input subsystem. I recommend using
> debugfs to provide this data. Some drivers also use v4l to provide a
> "picture" of the heat map.
Ack, I will just remove it here, this is used when force sensing data
(u46) are available.
>
> > + }
> > +}
> > +
> > +/*
> > + * Validates the crc and demultiplexes the axiom reports to the appropriate
> > + * report handler
> > + */
> > +static int axiom_handle_events(struct axiom_data *ts)
> > +{
> > + struct input_dev *input_dev = ts->input_dev;
> > + u8 *report_data = ts->rx_buf;
> > + struct device *dev = ts->dev;
> > + u16 crc_report;
> > + u8 *crc_bytes;
> > + u16 crc_calc;
> > + int error;
> > + u8 len;
> > +
> > + error = axiom_read(ts, AXIOM_REPORT_USAGE_ID, 0, report_data, ts->max_report_len);
> > + if (error)
> > + return error;
> > +
> > + len = (report_data[0] & AXIOM_COMMS_REPORT_LEN_MASK) << 1;
> > + if (len <= 2) {
> > + dev_err(dev, "Zero length report discarded.\n");
> > + return -ENODATA;
> > + }
> > +
> > + /* Validate the report CRC */
> > + crc_bytes = &report_data[len];
> > +
> > + crc_report = get_unaligned_le16(crc_bytes - 2);
> > + /* Length is in 16 bit words and remove the size of the CRC16 itself */
> > + crc_calc = crc16(0, report_data, (len - 2));
> > +
> > + if (crc_calc != crc_report) {
> > + dev_err(dev,
> > + "CRC mismatch! Expected: %#x, Calculated CRC: %#x.\n",
> > + crc_report, crc_calc);
> > + return -EINVAL;
> > + }
> > +
> > + switch (report_data[1]) {
> > + case AXIOM_USAGE_2DCTS_REPORT_ID:
> > + if (axiom_process_u41_report(ts, &report_data[1])) {
> > + input_mt_sync_frame(input_dev);
> > + input_sync(input_dev);
> > + }
> > + break;
> > +
> > + case AXIOM_USAGE_2AUX_REPORT_ID:
> > + /* This is an aux report (force) */
> > + axiom_process_u46_report(ts, &report_data[1]);
> > + input_mt_sync(input_dev);
> > + input_sync(input_dev);
> > + break;
> > +
> > + case AXIOM_USAGE_2HB_REPORT_ID:
> > + /* This is a heartbeat report */
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void axiom_i2c_poll(struct input_dev *input_dev)
> > +{
> > + struct axiom_data *ts = input_get_drvdata(input_dev);
> > +
> > + axiom_handle_events(ts);
> > +}
> > +
> > +static irqreturn_t axiom_irq(int irq, void *dev_id)
> > +{
> > + struct axiom_data *ts = dev_id;
> > +
> > + axiom_handle_events(ts);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static void axiom_reset(struct gpio_desc *reset_gpio)
> > +{
> > + gpiod_set_value_cansleep(reset_gpio, 1);
> > + usleep_range(1000, 2000);
> > + gpiod_set_value_cansleep(reset_gpio, 0);
> > + msleep(AXIOM_STARTUP_TIME_MS);
> > +}
> > +
> > +static int axiom_i2c_probe(struct i2c_client *client)
> > +{
> > + struct device *dev = &client->dev;
> > + struct input_dev *input_dev;
> > + struct axiom_data *ts;
> > + u32 poll_interval;
> > + int target;
> > + int error;
> > +
> > + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
> > + if (!ts)
> > + return -ENOMEM;
> > +
> > + i2c_set_clientdata(client, ts);
> > + ts->client = client;
> > + ts->dev = dev;
> > +
> > + ts->regmap = devm_regmap_init_i2c(client, &axiom_i2c_regmap_config);
> > + error = PTR_ERR_OR_ZERO(ts->regmap);
> > + if (error) {
> > + dev_err(dev, "Failed to initialize regmap: %d\n", error);
> > + return error;
> > + }
> > +
> > + error = devm_regulator_get_enable(dev, "vddi");
> > + if (error)
> > + return dev_err_probe(&client->dev, error,
> > + "Failed to enable VDDI regulator\n");
> > +
> > + error = devm_regulator_get_enable(dev, "vdda");
> > + if (error)
> > + return dev_err_probe(&client->dev, error,
> > + "Failed to enable VDDA regulator\n");
> > +
> > + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> > + if (IS_ERR(ts->reset_gpio))
> > + return dev_err_probe(dev, PTR_ERR(ts->reset_gpio), "failed to get reset GPIO\n");
> > +
> > + if (ts->reset_gpio)
> > + axiom_reset(ts->reset_gpio);
> > + else
> > + msleep(AXIOM_STARTUP_TIME_MS);
>
> Should this be called unconditionally (and matching msleep() be removed
> from axiom_i2c_probe)?
>
The reset gpio being optional we need to make sure the time after a
power ON sequence is meet. I could still move the power sequence in a
axiom_power_on() if this make things clearer.
> > +
> > + error = axiom_discover(ts);
> > + if (error)
> > + return dev_err_probe(dev, error, "Failed touchscreen discover\n");
> > +
> > + input_dev = devm_input_allocate_device(ts->dev);
> > + if (!input_dev)
> > + return -ENOMEM;
> > +
> > + input_dev->name = "TouchNetix axiom Touchscreen";
> > + input_dev->phys = "input/axiom_ts";
> > +
> > + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 65535, 0, 0);
> > + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 65535, 0, 0);
> > + input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE, 0, MT_TOOL_MAX, 0, 0);
>
> The only tool you report is finger, who do you declare more?
This is a mistake only finger is required.
>
> > + input_set_abs_params(input_dev, ABS_MT_DISTANCE, 0, 127, 0, 0);
> > + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 127, 0, 0);
> > +
> > + touchscreen_parse_properties(input_dev, true, &ts->prop);
> > +
> > + /* Registers the axiom device as a touchscreen instead of a mouse pointer */
> > + error = input_mt_init_slots(input_dev, AXIOM_U41_MAX_TARGETS, INPUT_MT_DIRECT);
> > + if (error)
> > + return error;
> > +
> > + /* Enables the raw data for up to 4 force channels to be sent to the input subsystem */
> > + set_bit(EV_REL, input_dev->evbit);
>
> The driver does not emit any relative events...
Fixed, thanks.
>
> > + set_bit(EV_MSC, input_dev->evbit);
> > + /* Declare that we support "RAW" Miscellaneous events */
> > + set_bit(MSC_RAW, input_dev->mscbit);
> > +
> > + ts->input_dev = input_dev;
> > + input_set_drvdata(ts->input_dev, ts);
> > +
> > + /* Ensure that all reports are initialised to not be present. */
> > + for (target = 0; target < AXIOM_U41_MAX_TARGETS; target++)
> > + ts->targets[target].state = AXIOM_TARGET_STATE_NOT_PRESENT;
> > +
> > + error = devm_request_threaded_irq(dev, client->irq, NULL,
> > + axiom_irq, IRQF_ONESHOT, dev_name(dev), ts);
> > + if (error) {
> > + dev_info(dev, "Request irq failed, falling back to polling mode");
>
> I do not think you should fall back to polling mode if you fail to get
> interrupt. If it was not specified (client->irq) then I can see that we
> might want to fall back, but if the system configured for using
> interrupt and you can not get it you should bail out.
>
Yes, clear, the polling mode can be decorrelated to the irq not provided
case.
> > +
> > + error = input_setup_polling(input_dev, axiom_i2c_poll);
> > + if (error)
> > + return dev_err_probe(ts->dev, error, "Unable to set up polling mode\n");
> > +
> > + if (!device_property_read_u32(ts->dev, "poll-interval", &poll_interval))
> > + input_set_poll_interval(input_dev, poll_interval);
> > + else
> > + input_set_poll_interval(input_dev, POLL_INTERVAL_DEFAULT_MS);
> > + }
> > +
> > + return input_register_device(input_dev);
>
> Please use
>
> error = input_register_device(..);
> if (error)
> return dev_err_probe(...);
>
> return 0;
>
Fixed, thanks.
> Thanks.
>
> --
> Dmitry
--
Kamel Bouhara, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com
^ permalink raw reply
* Re: [PATCH v2 0/7] HID/arm64: dts: qcom: sc8280xp-x13s: fix touchscreen power on
From: Johan Hovold @ 2024-06-05 12:25 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Bjorn Andersson, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio, Linus Walleij,
Douglas Anderson, linux-input, devicetree, linux-arm-msm,
linux-kernel
In-Reply-To: <20240507144821.12275-1-johan+linaro@kernel.org>
Hi Jiri and Benjamin,
On Tue, May 07, 2024 at 04:48:14PM +0200, Johan Hovold wrote:
> The Elan eKTH5015M touch controller on the X13s requires a 300 ms delay
> before sending commands after having deasserted reset during power on.
>
> This series switches the X13s devicetree to use the Elan specific
> binding so that the OS can determine the required power-on sequence and
> make sure that the controller is always detected during boot. [1]
> The devicetree changes are expected to go in through the Qualcomm tree
> once the binding and driver updates have been merged.
> [1] The reset signal is currently deasserted using the pin configuration
> and the controller would be detected if probe is deferred or if user
> space triggers a reprobe through sysfs.
> Johan Hovold (7):
> dt-bindings: HID: i2c-hid: add dedicated Ilitek ILI2901 schema
> dt-bindings: HID: i2c-hid: elan: add Elan eKTH5015M
> dt-bindings: HID: i2c-hid: elan: add 'no-reset-on-power-off' property
> HID: i2c-hid: elan: fix reset suspend current leakage
Could you consider picking the first four patches up for 6.10-rc3 so
that Bjorn can take the devicetree changes?
> arm64: dts: qcom: sc8280xp-x13s: fix touchscreen power on
> arm64: dts: qcom: sc8280xp-crd: use external pull up for touch reset
> arm64: defconfig: enable Elan i2c-hid driver
Johan
^ permalink raw reply
* Re: [PATCH V2 5/5] Add support for Awinic sar sensor.
From: kernel test robot @ 2024-06-05 12:09 UTC (permalink / raw)
To: wangshuaijie, dmitry.torokhov, robh, krzk+dt, conor+dt, jeff,
linux-input, devicetree, linux-kernel
Cc: oe-kbuild-all, wangshuaijie, liweilei, kangjiajun
In-Reply-To: <20240605091143.163789-6-wangshuaijie@awinic.com>
Hi,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 32f88d65f01bf6f45476d7edbe675e44fb9e1d58]
url: https://github.com/intel-lab-lkp/linux/commits/wangshuaijie-awinic-com/dt-bindings-input-Add-YAML-to-Awinic-sar-sensor/20240605-172023
base: 32f88d65f01bf6f45476d7edbe675e44fb9e1d58
patch link: https://lore.kernel.org/r/20240605091143.163789-6-wangshuaijie%40awinic.com
patch subject: [PATCH V2 5/5] Add support for Awinic sar sensor.
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20240605/202406051942.zY0QbCfv-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240605/202406051942.zY0QbCfv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202406051942.zY0QbCfv-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:39: warning: Cannot understand * @aw_sar_i2c_read() - Read register interface
on line 39 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:80: warning: Cannot understand * @aw_sar_i2c_write - write register interface
on line 80 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:125: warning: Cannot understand * @aw_sar_i2c_write_bits() - Write the corresponding bit of the register
on line 125 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:147: warning: Cannot understand * @aw_sar_i2c_write_seq() - Continuously write data to the chip
on line 147 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:177: warning: Cannot understand * @aw_sar_i2c_read_seq() - Continuously Read data from chip
on line 177 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:527: warning: Cannot understand * @aw_sar_parsing_bin_file() - Parse bin file
on line 527 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:582: warning: Cannot understand * @aw_sar_pow2() - Calculate the second power
on line 582 - I thought it was a doc line
>> drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c:603: warning: Cannot understand * @aw_sar_load_reg() - Calculate the second power
on line 603 - I thought it was a doc line
--
>> drivers/input/misc/aw_sar/./aw963xx/aw963xx.c:66: warning: Cannot understand * @aw963xx_sram_fill_not_wrote_area()
on line 66 - I thought it was a doc line
--
>> drivers/input/misc/aw_sar/aw_sar.c:1645: warning: Cannot understand * @aw_sar_chip_init() - sar sensor initialization logic.
on line 1645 - I thought it was a doc line
>> drivers/input/misc/aw_sar/aw_sar.c:1748: warning: Cannot understand * @aw_sar_get_chip_info() - Distinguish different chips by chip name and
on line 1748 - I thought it was a doc line
vim +39 drivers/input/misc/aw_sar/./comm/aw_sar_comm_interface.c
21b7e7a6d412d6 shuaijie wang 2024-06-05 37
21b7e7a6d412d6 shuaijie wang 2024-06-05 38 /**
21b7e7a6d412d6 shuaijie wang 2024-06-05 @39 * @aw_sar_i2c_read() - Read register interface
21b7e7a6d412d6 shuaijie wang 2024-06-05 40 *
21b7e7a6d412d6 shuaijie wang 2024-06-05 41 * @i2c: i2c client.
21b7e7a6d412d6 shuaijie wang 2024-06-05 42 * @reg_addr16: 16 bit register address.
21b7e7a6d412d6 shuaijie wang 2024-06-05 43 * @reg_data32: 32 bit register data.
21b7e7a6d412d6 shuaijie wang 2024-06-05 44 * @return 0 if init succeeded.
21b7e7a6d412d6 shuaijie wang 2024-06-05 45 */
21b7e7a6d412d6 shuaijie wang 2024-06-05 46 int32_t aw_sar_i2c_read(struct i2c_client *i2c, uint16_t reg_addr16, uint32_t *reg_data32)
21b7e7a6d412d6 shuaijie wang 2024-06-05 47 {
21b7e7a6d412d6 shuaijie wang 2024-06-05 48 uint8_t r_buf[6] = { 0 };
21b7e7a6d412d6 shuaijie wang 2024-06-05 49 int8_t cnt = 5;
21b7e7a6d412d6 shuaijie wang 2024-06-05 50 int32_t ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 51
21b7e7a6d412d6 shuaijie wang 2024-06-05 52 if (!i2c)
21b7e7a6d412d6 shuaijie wang 2024-06-05 53 return -EINVAL;
21b7e7a6d412d6 shuaijie wang 2024-06-05 54
21b7e7a6d412d6 shuaijie wang 2024-06-05 55 r_buf[0] = (unsigned char)(reg_addr16 >> OFFSET_BIT_8);
21b7e7a6d412d6 shuaijie wang 2024-06-05 56 r_buf[1] = (unsigned char)(reg_addr16);
21b7e7a6d412d6 shuaijie wang 2024-06-05 57
21b7e7a6d412d6 shuaijie wang 2024-06-05 58 do {
21b7e7a6d412d6 shuaijie wang 2024-06-05 59 ret = awinic_i2c_read(i2c, r_buf, 2, &r_buf[2], 4);
21b7e7a6d412d6 shuaijie wang 2024-06-05 60 if (ret < 0)
21b7e7a6d412d6 shuaijie wang 2024-06-05 61 dev_err(&i2c->dev, "i2c read error reg: 0x%04x, ret= %d cnt= %d",
21b7e7a6d412d6 shuaijie wang 2024-06-05 62 reg_addr16, ret, cnt);
21b7e7a6d412d6 shuaijie wang 2024-06-05 63 else
21b7e7a6d412d6 shuaijie wang 2024-06-05 64 break;
21b7e7a6d412d6 shuaijie wang 2024-06-05 65 usleep_range(2000, 3000);
21b7e7a6d412d6 shuaijie wang 2024-06-05 66 } while (cnt--);
21b7e7a6d412d6 shuaijie wang 2024-06-05 67
21b7e7a6d412d6 shuaijie wang 2024-06-05 68 if (cnt < 0) {
21b7e7a6d412d6 shuaijie wang 2024-06-05 69 dev_err(&i2c->dev, "i2c read error!");
21b7e7a6d412d6 shuaijie wang 2024-06-05 70 return ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 71 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 72
21b7e7a6d412d6 shuaijie wang 2024-06-05 73 *reg_data32 = ((uint32_t)r_buf[5] << OFFSET_BIT_0) | ((uint32_t)r_buf[4] << OFFSET_BIT_8) |
21b7e7a6d412d6 shuaijie wang 2024-06-05 74 ((uint32_t)r_buf[3] << OFFSET_BIT_16) | ((uint32_t)r_buf[2] << OFFSET_BIT_24);
21b7e7a6d412d6 shuaijie wang 2024-06-05 75
21b7e7a6d412d6 shuaijie wang 2024-06-05 76 return 0;
21b7e7a6d412d6 shuaijie wang 2024-06-05 77 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 78
21b7e7a6d412d6 shuaijie wang 2024-06-05 79 /**
21b7e7a6d412d6 shuaijie wang 2024-06-05 @80 * @aw_sar_i2c_write - write register interface
21b7e7a6d412d6 shuaijie wang 2024-06-05 81 *
21b7e7a6d412d6 shuaijie wang 2024-06-05 82 * @i2c: i2c client.
21b7e7a6d412d6 shuaijie wang 2024-06-05 83 * @reg_addr16: 16 bit register address.
21b7e7a6d412d6 shuaijie wang 2024-06-05 84 * @reg_data32: 32 bit register data.
21b7e7a6d412d6 shuaijie wang 2024-06-05 85 * @return 0 if init succeeded.
21b7e7a6d412d6 shuaijie wang 2024-06-05 86 */
21b7e7a6d412d6 shuaijie wang 2024-06-05 87 int32_t aw_sar_i2c_write(struct i2c_client *i2c, uint16_t reg_addr16, uint32_t reg_data32)
21b7e7a6d412d6 shuaijie wang 2024-06-05 88 {
21b7e7a6d412d6 shuaijie wang 2024-06-05 89 uint8_t w_buf[6] = { 0 };
21b7e7a6d412d6 shuaijie wang 2024-06-05 90 int8_t cnt = 5;
21b7e7a6d412d6 shuaijie wang 2024-06-05 91 int32_t ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 92
21b7e7a6d412d6 shuaijie wang 2024-06-05 93 if (!i2c)
21b7e7a6d412d6 shuaijie wang 2024-06-05 94 return -EINVAL;
21b7e7a6d412d6 shuaijie wang 2024-06-05 95
21b7e7a6d412d6 shuaijie wang 2024-06-05 96 /*reg_addr*/
21b7e7a6d412d6 shuaijie wang 2024-06-05 97 w_buf[0] = (uint8_t)(reg_addr16 >> OFFSET_BIT_8);
21b7e7a6d412d6 shuaijie wang 2024-06-05 98 w_buf[1] = (uint8_t)(reg_addr16);
21b7e7a6d412d6 shuaijie wang 2024-06-05 99 /*data*/
21b7e7a6d412d6 shuaijie wang 2024-06-05 100 w_buf[2] = (uint8_t)(reg_data32 >> OFFSET_BIT_24);
21b7e7a6d412d6 shuaijie wang 2024-06-05 101 w_buf[3] = (uint8_t)(reg_data32 >> OFFSET_BIT_16);
21b7e7a6d412d6 shuaijie wang 2024-06-05 102 w_buf[4] = (uint8_t)(reg_data32 >> OFFSET_BIT_8);
21b7e7a6d412d6 shuaijie wang 2024-06-05 103 w_buf[5] = (uint8_t)(reg_data32);
21b7e7a6d412d6 shuaijie wang 2024-06-05 104
21b7e7a6d412d6 shuaijie wang 2024-06-05 105 do {
21b7e7a6d412d6 shuaijie wang 2024-06-05 106 ret = awinic_i2c_write(i2c, w_buf, ARRAY_SIZE(w_buf));
21b7e7a6d412d6 shuaijie wang 2024-06-05 107 if (ret < 0) {
21b7e7a6d412d6 shuaijie wang 2024-06-05 108 dev_err(&i2c->dev,
21b7e7a6d412d6 shuaijie wang 2024-06-05 109 "i2c write error reg: 0x%04x data: 0x%08x, ret= %d cnt= %d",
21b7e7a6d412d6 shuaijie wang 2024-06-05 110 reg_addr16, reg_data32, ret, cnt);
21b7e7a6d412d6 shuaijie wang 2024-06-05 111 } else {
21b7e7a6d412d6 shuaijie wang 2024-06-05 112 break;
21b7e7a6d412d6 shuaijie wang 2024-06-05 113 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 114 } while (cnt--);
21b7e7a6d412d6 shuaijie wang 2024-06-05 115
21b7e7a6d412d6 shuaijie wang 2024-06-05 116 if (cnt < 0) {
21b7e7a6d412d6 shuaijie wang 2024-06-05 117 dev_err(&i2c->dev, "i2c write error!");
21b7e7a6d412d6 shuaijie wang 2024-06-05 118 return ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 119 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 120
21b7e7a6d412d6 shuaijie wang 2024-06-05 121 return 0;
21b7e7a6d412d6 shuaijie wang 2024-06-05 122 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 123
21b7e7a6d412d6 shuaijie wang 2024-06-05 124 /**
21b7e7a6d412d6 shuaijie wang 2024-06-05 @125 * @aw_sar_i2c_write_bits() - Write the corresponding bit of the register
21b7e7a6d412d6 shuaijie wang 2024-06-05 126 *
21b7e7a6d412d6 shuaijie wang 2024-06-05 127 * @i2c:i2c client.
21b7e7a6d412d6 shuaijie wang 2024-06-05 128 * @reg_addr16: 16 bit register address.
21b7e7a6d412d6 shuaijie wang 2024-06-05 129 * @mask: Write the corresponding bit as 0
21b7e7a6d412d6 shuaijie wang 2024-06-05 130 * @val: Write corresponding data to the register
21b7e7a6d412d6 shuaijie wang 2024-06-05 131 * @return 0 if init succeeded.
21b7e7a6d412d6 shuaijie wang 2024-06-05 132 */
21b7e7a6d412d6 shuaijie wang 2024-06-05 133 int32_t
21b7e7a6d412d6 shuaijie wang 2024-06-05 134 aw_sar_i2c_write_bits(struct i2c_client *i2c, uint16_t reg_addr16, uint32_t mask, uint32_t val)
21b7e7a6d412d6 shuaijie wang 2024-06-05 135 {
21b7e7a6d412d6 shuaijie wang 2024-06-05 136 uint32_t reg_val;
21b7e7a6d412d6 shuaijie wang 2024-06-05 137
21b7e7a6d412d6 shuaijie wang 2024-06-05 138 aw_sar_i2c_read(i2c, reg_addr16, ®_val);
21b7e7a6d412d6 shuaijie wang 2024-06-05 139 reg_val &= mask;
21b7e7a6d412d6 shuaijie wang 2024-06-05 140 reg_val |= (val & (~mask));
21b7e7a6d412d6 shuaijie wang 2024-06-05 141 aw_sar_i2c_write(i2c, reg_addr16, reg_val);
21b7e7a6d412d6 shuaijie wang 2024-06-05 142
21b7e7a6d412d6 shuaijie wang 2024-06-05 143 return 0;
21b7e7a6d412d6 shuaijie wang 2024-06-05 144 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 145
21b7e7a6d412d6 shuaijie wang 2024-06-05 146 /**
21b7e7a6d412d6 shuaijie wang 2024-06-05 @147 * @aw_sar_i2c_write_seq() - Continuously write data to the chip
21b7e7a6d412d6 shuaijie wang 2024-06-05 148 *
21b7e7a6d412d6 shuaijie wang 2024-06-05 149 * @i2c:i2c client.
21b7e7a6d412d6 shuaijie wang 2024-06-05 150 * @tr_data: Data written
21b7e7a6d412d6 shuaijie wang 2024-06-05 151 * @len: Length of data written
21b7e7a6d412d6 shuaijie wang 2024-06-05 152 * @return 0 if init succeeded.
21b7e7a6d412d6 shuaijie wang 2024-06-05 153 */
21b7e7a6d412d6 shuaijie wang 2024-06-05 154 int32_t aw_sar_i2c_write_seq(struct i2c_client *i2c, uint8_t *tr_data, uint16_t len)
21b7e7a6d412d6 shuaijie wang 2024-06-05 155 {
21b7e7a6d412d6 shuaijie wang 2024-06-05 156 int8_t cnt = AW_RETRIES;
21b7e7a6d412d6 shuaijie wang 2024-06-05 157 int32_t ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 158
21b7e7a6d412d6 shuaijie wang 2024-06-05 159 do {
21b7e7a6d412d6 shuaijie wang 2024-06-05 160 ret = awinic_i2c_write(i2c, tr_data, len);
21b7e7a6d412d6 shuaijie wang 2024-06-05 161 if (ret < 0)
21b7e7a6d412d6 shuaijie wang 2024-06-05 162 dev_err(&i2c->dev, "awinic i2c write seq error %d", ret);
21b7e7a6d412d6 shuaijie wang 2024-06-05 163 else
21b7e7a6d412d6 shuaijie wang 2024-06-05 164 break;
21b7e7a6d412d6 shuaijie wang 2024-06-05 165 usleep_range(AW_I2C_RW_RETRY_TIME_MIN, AW_I2C_RW_RETRY_TIME_MAX);
21b7e7a6d412d6 shuaijie wang 2024-06-05 166 } while (cnt--);
21b7e7a6d412d6 shuaijie wang 2024-06-05 167
21b7e7a6d412d6 shuaijie wang 2024-06-05 168 if (cnt < 0) {
21b7e7a6d412d6 shuaijie wang 2024-06-05 169 dev_err(&i2c->dev, "awinic i2c write error!");
21b7e7a6d412d6 shuaijie wang 2024-06-05 170 return ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 171 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 172
21b7e7a6d412d6 shuaijie wang 2024-06-05 173 return 0;
21b7e7a6d412d6 shuaijie wang 2024-06-05 174 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 175
21b7e7a6d412d6 shuaijie wang 2024-06-05 176 /**
21b7e7a6d412d6 shuaijie wang 2024-06-05 @177 * @aw_sar_i2c_read_seq() - Continuously Read data from chip
21b7e7a6d412d6 shuaijie wang 2024-06-05 178 *
21b7e7a6d412d6 shuaijie wang 2024-06-05 179 * @i2c:i2c client.
21b7e7a6d412d6 shuaijie wang 2024-06-05 180 * @addr: Read address
21b7e7a6d412d6 shuaijie wang 2024-06-05 181 * @addr_len: Length of read address (byte)
21b7e7a6d412d6 shuaijie wang 2024-06-05 182 * @data: Data written
21b7e7a6d412d6 shuaijie wang 2024-06-05 183 * @data_len: Length of data written
21b7e7a6d412d6 shuaijie wang 2024-06-05 184 * @return 0 if init succeeded.
21b7e7a6d412d6 shuaijie wang 2024-06-05 185 */
21b7e7a6d412d6 shuaijie wang 2024-06-05 186 int32_t aw_sar_i2c_read_seq(struct i2c_client *i2c, uint8_t *addr,
21b7e7a6d412d6 shuaijie wang 2024-06-05 187 uint8_t addr_len, uint8_t *data, uint16_t data_len)
21b7e7a6d412d6 shuaijie wang 2024-06-05 188 {
21b7e7a6d412d6 shuaijie wang 2024-06-05 189 int8_t cnt = AW_RETRIES;
21b7e7a6d412d6 shuaijie wang 2024-06-05 190 int32_t ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 191
21b7e7a6d412d6 shuaijie wang 2024-06-05 192 do {
21b7e7a6d412d6 shuaijie wang 2024-06-05 193 ret = awinic_i2c_read(i2c, addr, addr_len, data, data_len);
21b7e7a6d412d6 shuaijie wang 2024-06-05 194 if (ret < 0)
21b7e7a6d412d6 shuaijie wang 2024-06-05 195 dev_err(&i2c->dev, "awinic sar i2c write error %d", ret);
21b7e7a6d412d6 shuaijie wang 2024-06-05 196 else
21b7e7a6d412d6 shuaijie wang 2024-06-05 197 break;
21b7e7a6d412d6 shuaijie wang 2024-06-05 198 usleep_range(AW_I2C_RW_RETRY_TIME_MIN, AW_I2C_RW_RETRY_TIME_MAX);
21b7e7a6d412d6 shuaijie wang 2024-06-05 199 } while (cnt--);
21b7e7a6d412d6 shuaijie wang 2024-06-05 200
21b7e7a6d412d6 shuaijie wang 2024-06-05 201 if (cnt < 0) {
21b7e7a6d412d6 shuaijie wang 2024-06-05 202 dev_err(&i2c->dev, "awinic sar i2c read error!");
21b7e7a6d412d6 shuaijie wang 2024-06-05 203 return ret;
21b7e7a6d412d6 shuaijie wang 2024-06-05 204 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 205
21b7e7a6d412d6 shuaijie wang 2024-06-05 206 return 0;
21b7e7a6d412d6 shuaijie wang 2024-06-05 207 }
21b7e7a6d412d6 shuaijie wang 2024-06-05 208
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH AUTOSEL 5.10 6/8] platform/x86: touchscreen_dmi: Add info for the EZpad 6s Pro
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120554.2968012-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 3050052613790e75b5e4a8536930426b0a8b0774 ]
The "EZpad 6s Pro" uses the same touchscreen as the "EZpad 6 Pro B",
unlike the "Ezpad 6 Pro" which has its own touchscreen.
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-3-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index 30ad533b92914..7fb2603acc06a 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -1280,6 +1280,17 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_BIOS_DATE, "04/24/2018"),
},
},
+ {
+ /* Jumper EZpad 6s Pro */
+ .driver_data = (void *)&jumper_ezpad_6_pro_b_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Jumper"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ezpad"),
+ /* Above matches are too generic, add bios match */
+ DMI_MATCH(DMI_BIOS_VERSION, "E.WSA116_8.E1.042.bin"),
+ DMI_MATCH(DMI_BIOS_DATE, "01/08/2020"),
+ },
+ },
{
/* Jumper EZpad 6 m4 */
.driver_data = (void *)&jumper_ezpad_6_m4_data,
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 5.10 5/8] platform/x86: touchscreen_dmi: Add info for GlobalSpace SolT IVW 11.6" tablet
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120554.2968012-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 7c8639aa41343fd7b3dbe09baf6b0791fcc407a1 ]
This is a tablet created by GlobalSpace Technologies Limited
which uses an Intel Atom x5-Z8300, 4GB of RAM & 64GB of storage.
Link: https://web.archive.org/web/20171102141952/http://globalspace.in/11.6-device.html
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-2-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index ce03f6e9d7c67..30ad533b92914 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -860,6 +860,22 @@ static const struct ts_dmi_data schneider_sct101ctm_data = {
.properties = schneider_sct101ctm_props,
};
+static const struct property_entry globalspace_solt_ivw116_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-min-x", 7),
+ PROPERTY_ENTRY_U32("touchscreen-min-y", 22),
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 1723),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1077),
+ PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-globalspace-solt-ivw116.fw"),
+ PROPERTY_ENTRY_U32("silead,max-fingers", 10),
+ PROPERTY_ENTRY_BOOL("silead,home-button"),
+ { }
+};
+
+static const struct ts_dmi_data globalspace_solt_ivw116_data = {
+ .acpi_name = "MSSL1680:00",
+ .properties = globalspace_solt_ivw116_props,
+};
+
static const struct property_entry techbite_arc_11_6_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 5),
PROPERTY_ENTRY_U32("touchscreen-min-y", 7),
@@ -1493,6 +1509,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "SCT101CTM"),
},
},
+ {
+ /* GlobalSpace SoLT IVW 11.6" */
+ .driver_data = (void *)&globalspace_solt_ivw116_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Globalspace Tech Pvt Ltd"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SolTIVW"),
+ DMI_MATCH(DMI_PRODUCT_SKU, "PN20170413488"),
+ },
+ },
{
/* Techbite Arc 11.6 */
.driver_data = (void *)&techbite_arc_11_6_data,
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 5.10 4/8] platform/x86: touchscreen_dmi: Add support for setting touchscreen properties from cmdline
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Hans de Goede, Gregor Riepl, Sasha Levin, corbet, ilpo.jarvinen,
paulmck, jpoimboe, tglx, bp, xiongwei.song, linux-doc,
linux-input, platform-driver-x86
In-Reply-To: <20240605120554.2968012-1-sashal@kernel.org>
From: Hans de Goede <hdegoede@redhat.com>
[ Upstream commit 0b178b02673998f5acca5a0365a8858ca45beedb ]
On x86/ACPI platforms touchscreens mostly just work without needing any
device/model specific configuration. But in some cases (mostly with Silead
and Goodix touchscreens) it is still necessary to manually specify various
touchscreen-properties on a per model basis.
touchscreen_dmi is a special place for DMI quirks for this, but it can be
challenging for users to figure out the right property values, especially
for Silead touchscreens where non of these can be read back from
the touchscreen-controller.
ATM users can only test touchscreen properties by editing touchscreen_dmi.c
and then building a completely new kernel which makes it unnecessary
difficult for users to test and submit properties when necessary for their
laptop / tablet model.
Add support for specifying properties on the kernel commandline to allow
users to easily figure out the right settings. See the added documentation
in kernel-parameters.txt for the commandline syntax.
Cc: Gregor Riepl <onitake@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240523143601.47555-1-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
.../admin-guide/kernel-parameters.txt | 22 ++++++
drivers/platform/x86/touchscreen_dmi.c | 79 ++++++++++++++++++-
2 files changed, 98 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 8e4882bb8cf85..0d8cd0c622238 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1625,6 +1625,28 @@
Format:
<bus_id>,<clkrate>
+ i2c_touchscreen_props= [HW,ACPI,X86]
+ Set device-properties for ACPI-enumerated I2C-attached
+ touchscreen, to e.g. fix coordinates of upside-down
+ mounted touchscreens. If you need this option please
+ submit a drivers/platform/x86/touchscreen_dmi.c patch
+ adding a DMI quirk for this.
+
+ Format:
+ <ACPI_HW_ID>:<prop_name>=<val>[:prop_name=val][:...]
+ Where <val> is one of:
+ Omit "=<val>" entirely Set a boolean device-property
+ Unsigned number Set a u32 device-property
+ Anything else Set a string device-property
+
+ Examples (split over multiple lines):
+ i2c_touchscreen_props=GDIX1001:touchscreen-inverted-x:
+ touchscreen-inverted-y
+
+ i2c_touchscreen_props=MSSL1680:touchscreen-size-x=1920:
+ touchscreen-size-y=1080:touchscreen-inverted-y:
+ firmware-name=gsl1680-vendor-model.fw:silead,home-button
+
i8042.debug [HW] Toggle i8042 debug mode
i8042.unmask_kbd_data
[HW] Enable printing of interrupt data from the KBD port
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index fbaa618594628..ce03f6e9d7c67 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -9,10 +9,13 @@
*/
#include <linux/acpi.h>
+#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/efi_embedded_fw.h>
#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kstrtox.h>
#include <linux/notifier.h>
#include <linux/property.h>
#include <linux/string.h>
@@ -1649,7 +1652,7 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
{ }
};
-static const struct ts_dmi_data *ts_data;
+static struct ts_dmi_data *ts_data;
static void ts_dmi_add_props(struct i2c_client *client)
{
@@ -1684,6 +1687,64 @@ static int ts_dmi_notifier_call(struct notifier_block *nb,
return 0;
}
+#define MAX_CMDLINE_PROPS 16
+
+static struct property_entry ts_cmdline_props[MAX_CMDLINE_PROPS + 1];
+
+static struct ts_dmi_data ts_cmdline_data = {
+ .properties = ts_cmdline_props,
+};
+
+static int __init ts_parse_props(char *str)
+{
+ /* Save the original str to show it on syntax errors */
+ char orig_str[256];
+ char *name, *value;
+ u32 u32val;
+ int i, ret;
+
+ strscpy(orig_str, str, sizeof(orig_str));
+
+ /*
+ * str is part of the static_command_line from init/main.c and poking
+ * holes in that by writing 0 to it is allowed, as is taking long
+ * lasting references to it.
+ */
+ ts_cmdline_data.acpi_name = strsep(&str, ":");
+
+ for (i = 0; i < MAX_CMDLINE_PROPS; i++) {
+ name = strsep(&str, ":");
+ if (!name || !name[0])
+ break;
+
+ /* Replace '=' with 0 and make value point past '=' or NULL */
+ value = name;
+ strsep(&value, "=");
+ if (!value) {
+ ts_cmdline_props[i] = PROPERTY_ENTRY_BOOL(name);
+ } else if (isdigit(value[0])) {
+ ret = kstrtou32(value, 0, &u32val);
+ if (ret)
+ goto syntax_error;
+
+ ts_cmdline_props[i] = PROPERTY_ENTRY_U32(name, u32val);
+ } else {
+ ts_cmdline_props[i] = PROPERTY_ENTRY_STRING(name, value);
+ }
+ }
+
+ if (!i || str)
+ goto syntax_error;
+
+ ts_data = &ts_cmdline_data;
+ return 1;
+
+syntax_error:
+ pr_err("Invalid '%s' value for 'i2c_touchscreen_props='\n", orig_str);
+ return 1; /* "i2c_touchscreen_props=" is still a known parameter */
+}
+__setup("i2c_touchscreen_props=", ts_parse_props);
+
static struct notifier_block ts_dmi_notifier = {
.notifier_call = ts_dmi_notifier_call,
};
@@ -1691,13 +1752,25 @@ static struct notifier_block ts_dmi_notifier = {
static int __init ts_dmi_init(void)
{
const struct dmi_system_id *dmi_id;
+ struct ts_dmi_data *ts_data_dmi;
int error;
dmi_id = dmi_first_match(touchscreen_dmi_table);
- if (!dmi_id)
+ ts_data_dmi = dmi_id ? dmi_id->driver_data : NULL;
+
+ if (ts_data) {
+ /*
+ * Kernel cmdline provided data takes precedence, copy over
+ * DMI efi_embedded_fw info if available.
+ */
+ if (ts_data_dmi)
+ ts_data->embedded_fw = ts_data_dmi->embedded_fw;
+ } else if (ts_data_dmi) {
+ ts_data = ts_data_dmi;
+ } else {
return 0; /* Not an error */
+ }
- ts_data = dmi_id->driver_data;
/* Some dmi table entries only provide an efi_embedded_fw_desc */
if (!ts_data->properties)
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 5.15 08/12] platform/x86: touchscreen_dmi: Add info for the EZpad 6s Pro
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120528.2967750-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 3050052613790e75b5e4a8536930426b0a8b0774 ]
The "EZpad 6s Pro" uses the same touchscreen as the "EZpad 6 Pro B",
unlike the "Ezpad 6 Pro" which has its own touchscreen.
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-3-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index c59f829d397f2..4d4eae5a4153b 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -1349,6 +1349,17 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_BIOS_DATE, "04/24/2018"),
},
},
+ {
+ /* Jumper EZpad 6s Pro */
+ .driver_data = (void *)&jumper_ezpad_6_pro_b_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Jumper"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ezpad"),
+ /* Above matches are too generic, add bios match */
+ DMI_MATCH(DMI_BIOS_VERSION, "E.WSA116_8.E1.042.bin"),
+ DMI_MATCH(DMI_BIOS_DATE, "01/08/2020"),
+ },
+ },
{
/* Jumper EZpad 6 m4 */
.driver_data = (void *)&jumper_ezpad_6_m4_data,
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 5.15 07/12] platform/x86: touchscreen_dmi: Add info for GlobalSpace SolT IVW 11.6" tablet
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120528.2967750-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 7c8639aa41343fd7b3dbe09baf6b0791fcc407a1 ]
This is a tablet created by GlobalSpace Technologies Limited
which uses an Intel Atom x5-Z8300, 4GB of RAM & 64GB of storage.
Link: https://web.archive.org/web/20171102141952/http://globalspace.in/11.6-device.html
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-2-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index 0308c90db8e30..c59f829d397f2 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -877,6 +877,22 @@ static const struct ts_dmi_data schneider_sct101ctm_data = {
.properties = schneider_sct101ctm_props,
};
+static const struct property_entry globalspace_solt_ivw116_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-min-x", 7),
+ PROPERTY_ENTRY_U32("touchscreen-min-y", 22),
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 1723),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1077),
+ PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-globalspace-solt-ivw116.fw"),
+ PROPERTY_ENTRY_U32("silead,max-fingers", 10),
+ PROPERTY_ENTRY_BOOL("silead,home-button"),
+ { }
+};
+
+static const struct ts_dmi_data globalspace_solt_ivw116_data = {
+ .acpi_name = "MSSL1680:00",
+ .properties = globalspace_solt_ivw116_props,
+};
+
static const struct property_entry techbite_arc_11_6_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 5),
PROPERTY_ENTRY_U32("touchscreen-min-y", 7),
@@ -1572,6 +1588,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "SCT101CTM"),
},
},
+ {
+ /* GlobalSpace SoLT IVW 11.6" */
+ .driver_data = (void *)&globalspace_solt_ivw116_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Globalspace Tech Pvt Ltd"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SolTIVW"),
+ DMI_MATCH(DMI_PRODUCT_SKU, "PN20170413488"),
+ },
+ },
{
/* Techbite Arc 11.6 */
.driver_data = (void *)&techbite_arc_11_6_data,
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 5.15 06/12] platform/x86: touchscreen_dmi: Add support for setting touchscreen properties from cmdline
From: Sasha Levin @ 2024-06-05 12:05 UTC (permalink / raw)
To: linux-kernel, stable
Cc: Hans de Goede, Gregor Riepl, Sasha Levin, corbet, ilpo.jarvinen,
paulmck, tglx, jpoimboe, rdunlap, xiongwei.song, linux-doc,
linux-input, platform-driver-x86
In-Reply-To: <20240605120528.2967750-1-sashal@kernel.org>
From: Hans de Goede <hdegoede@redhat.com>
[ Upstream commit 0b178b02673998f5acca5a0365a8858ca45beedb ]
On x86/ACPI platforms touchscreens mostly just work without needing any
device/model specific configuration. But in some cases (mostly with Silead
and Goodix touchscreens) it is still necessary to manually specify various
touchscreen-properties on a per model basis.
touchscreen_dmi is a special place for DMI quirks for this, but it can be
challenging for users to figure out the right property values, especially
for Silead touchscreens where non of these can be read back from
the touchscreen-controller.
ATM users can only test touchscreen properties by editing touchscreen_dmi.c
and then building a completely new kernel which makes it unnecessary
difficult for users to test and submit properties when necessary for their
laptop / tablet model.
Add support for specifying properties on the kernel commandline to allow
users to easily figure out the right settings. See the added documentation
in kernel-parameters.txt for the commandline syntax.
Cc: Gregor Riepl <onitake@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240523143601.47555-1-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
.../admin-guide/kernel-parameters.txt | 22 ++++++
drivers/platform/x86/touchscreen_dmi.c | 79 ++++++++++++++++++-
2 files changed, 98 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e61f0d038c6d7..569b3d9f2634f 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1702,6 +1702,28 @@
Format:
<bus_id>,<clkrate>
+ i2c_touchscreen_props= [HW,ACPI,X86]
+ Set device-properties for ACPI-enumerated I2C-attached
+ touchscreen, to e.g. fix coordinates of upside-down
+ mounted touchscreens. If you need this option please
+ submit a drivers/platform/x86/touchscreen_dmi.c patch
+ adding a DMI quirk for this.
+
+ Format:
+ <ACPI_HW_ID>:<prop_name>=<val>[:prop_name=val][:...]
+ Where <val> is one of:
+ Omit "=<val>" entirely Set a boolean device-property
+ Unsigned number Set a u32 device-property
+ Anything else Set a string device-property
+
+ Examples (split over multiple lines):
+ i2c_touchscreen_props=GDIX1001:touchscreen-inverted-x:
+ touchscreen-inverted-y
+
+ i2c_touchscreen_props=MSSL1680:touchscreen-size-x=1920:
+ touchscreen-size-y=1080:touchscreen-inverted-y:
+ firmware-name=gsl1680-vendor-model.fw:silead,home-button
+
i8042.debug [HW] Toggle i8042 debug mode
i8042.unmask_kbd_data
[HW] Enable printing of interrupt data from the KBD port
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index 161bd19441042..0308c90db8e30 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -9,10 +9,13 @@
*/
#include <linux/acpi.h>
+#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/efi_embedded_fw.h>
#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kstrtox.h>
#include <linux/notifier.h>
#include <linux/property.h>
#include <linux/string.h>
@@ -1746,7 +1749,7 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
{ }
};
-static const struct ts_dmi_data *ts_data;
+static struct ts_dmi_data *ts_data;
static void ts_dmi_add_props(struct i2c_client *client)
{
@@ -1781,6 +1784,64 @@ static int ts_dmi_notifier_call(struct notifier_block *nb,
return 0;
}
+#define MAX_CMDLINE_PROPS 16
+
+static struct property_entry ts_cmdline_props[MAX_CMDLINE_PROPS + 1];
+
+static struct ts_dmi_data ts_cmdline_data = {
+ .properties = ts_cmdline_props,
+};
+
+static int __init ts_parse_props(char *str)
+{
+ /* Save the original str to show it on syntax errors */
+ char orig_str[256];
+ char *name, *value;
+ u32 u32val;
+ int i, ret;
+
+ strscpy(orig_str, str, sizeof(orig_str));
+
+ /*
+ * str is part of the static_command_line from init/main.c and poking
+ * holes in that by writing 0 to it is allowed, as is taking long
+ * lasting references to it.
+ */
+ ts_cmdline_data.acpi_name = strsep(&str, ":");
+
+ for (i = 0; i < MAX_CMDLINE_PROPS; i++) {
+ name = strsep(&str, ":");
+ if (!name || !name[0])
+ break;
+
+ /* Replace '=' with 0 and make value point past '=' or NULL */
+ value = name;
+ strsep(&value, "=");
+ if (!value) {
+ ts_cmdline_props[i] = PROPERTY_ENTRY_BOOL(name);
+ } else if (isdigit(value[0])) {
+ ret = kstrtou32(value, 0, &u32val);
+ if (ret)
+ goto syntax_error;
+
+ ts_cmdline_props[i] = PROPERTY_ENTRY_U32(name, u32val);
+ } else {
+ ts_cmdline_props[i] = PROPERTY_ENTRY_STRING(name, value);
+ }
+ }
+
+ if (!i || str)
+ goto syntax_error;
+
+ ts_data = &ts_cmdline_data;
+ return 1;
+
+syntax_error:
+ pr_err("Invalid '%s' value for 'i2c_touchscreen_props='\n", orig_str);
+ return 1; /* "i2c_touchscreen_props=" is still a known parameter */
+}
+__setup("i2c_touchscreen_props=", ts_parse_props);
+
static struct notifier_block ts_dmi_notifier = {
.notifier_call = ts_dmi_notifier_call,
};
@@ -1788,13 +1849,25 @@ static struct notifier_block ts_dmi_notifier = {
static int __init ts_dmi_init(void)
{
const struct dmi_system_id *dmi_id;
+ struct ts_dmi_data *ts_data_dmi;
int error;
dmi_id = dmi_first_match(touchscreen_dmi_table);
- if (!dmi_id)
+ ts_data_dmi = dmi_id ? dmi_id->driver_data : NULL;
+
+ if (ts_data) {
+ /*
+ * Kernel cmdline provided data takes precedence, copy over
+ * DMI efi_embedded_fw info if available.
+ */
+ if (ts_data_dmi)
+ ts_data->embedded_fw = ts_data_dmi->embedded_fw;
+ } else if (ts_data_dmi) {
+ ts_data = ts_data_dmi;
+ } else {
return 0; /* Not an error */
+ }
- ts_data = dmi_id->driver_data;
/* Some dmi table entries only provide an efi_embedded_fw_desc */
if (!ts_data->properties)
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.1 08/14] platform/x86: touchscreen_dmi: Add info for the EZpad 6s Pro
From: Sasha Levin @ 2024-06-05 12:04 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120455.2967445-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 3050052613790e75b5e4a8536930426b0a8b0774 ]
The "EZpad 6s Pro" uses the same touchscreen as the "EZpad 6 Pro B",
unlike the "Ezpad 6 Pro" which has its own touchscreen.
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-3-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index 0b9746db296f3..33fd360803e7a 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -1364,6 +1364,17 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_BIOS_DATE, "04/24/2018"),
},
},
+ {
+ /* Jumper EZpad 6s Pro */
+ .driver_data = (void *)&jumper_ezpad_6_pro_b_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Jumper"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Ezpad"),
+ /* Above matches are too generic, add bios match */
+ DMI_MATCH(DMI_BIOS_VERSION, "E.WSA116_8.E1.042.bin"),
+ DMI_MATCH(DMI_BIOS_DATE, "01/08/2020"),
+ },
+ },
{
/* Jumper EZpad 6 m4 */
.driver_data = (void *)&jumper_ezpad_6_m4_data,
--
2.43.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.1 07/14] platform/x86: touchscreen_dmi: Add info for GlobalSpace SolT IVW 11.6" tablet
From: Sasha Levin @ 2024-06-05 12:04 UTC (permalink / raw)
To: linux-kernel, stable
Cc: hmtheboy154, Hans de Goede, Sasha Levin, ilpo.jarvinen,
linux-input, platform-driver-x86
In-Reply-To: <20240605120455.2967445-1-sashal@kernel.org>
From: hmtheboy154 <buingoc67@gmail.com>
[ Upstream commit 7c8639aa41343fd7b3dbe09baf6b0791fcc407a1 ]
This is a tablet created by GlobalSpace Technologies Limited
which uses an Intel Atom x5-Z8300, 4GB of RAM & 64GB of storage.
Link: https://web.archive.org/web/20171102141952/http://globalspace.in/11.6-device.html
Signed-off-by: hmtheboy154 <buingoc67@gmail.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://lore.kernel.org/r/20240527091447.248849-2-hdegoede@redhat.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/platform/x86/touchscreen_dmi.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index 881e251bf22e8..0b9746db296f3 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -874,6 +874,22 @@ static const struct ts_dmi_data schneider_sct101ctm_data = {
.properties = schneider_sct101ctm_props,
};
+static const struct property_entry globalspace_solt_ivw116_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-min-x", 7),
+ PROPERTY_ENTRY_U32("touchscreen-min-y", 22),
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 1723),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1077),
+ PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-globalspace-solt-ivw116.fw"),
+ PROPERTY_ENTRY_U32("silead,max-fingers", 10),
+ PROPERTY_ENTRY_BOOL("silead,home-button"),
+ { }
+};
+
+static const struct ts_dmi_data globalspace_solt_ivw116_data = {
+ .acpi_name = "MSSL1680:00",
+ .properties = globalspace_solt_ivw116_props,
+};
+
static const struct property_entry techbite_arc_11_6_props[] = {
PROPERTY_ENTRY_U32("touchscreen-min-x", 5),
PROPERTY_ENTRY_U32("touchscreen-min-y", 7),
@@ -1587,6 +1603,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "SCT101CTM"),
},
},
+ {
+ /* GlobalSpace SoLT IVW 11.6" */
+ .driver_data = (void *)&globalspace_solt_ivw116_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Globalspace Tech Pvt Ltd"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SolTIVW"),
+ DMI_MATCH(DMI_PRODUCT_SKU, "PN20170413488"),
+ },
+ },
{
/* Techbite Arc 11.6 */
.driver_data = (void *)&techbite_arc_11_6_data,
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox