summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig61
-rw-r--r--drivers/hid/Makefile6
-rw-r--r--drivers/hid/amd-sfh-hid/Kconfig1
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_common.h2
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_pcie.c62
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c78
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h3
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c40
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h24
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.c9
-rw-r--r--drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c75
-rw-r--r--drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c531
-rw-r--r--drivers/hid/bpf/progs/TUXEDO__Sirius-16-Gen1-and-Gen2.bpf.c47
-rw-r--r--drivers/hid/bpf/progs/XPPen__ACK05.bpf.c331
-rw-r--r--drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c44
-rw-r--r--drivers/hid/bpf/progs/hid_bpf_async.h219
-rw-r--r--drivers/hid/bpf/progs/hid_bpf_helpers.h19
-rw-r--r--drivers/hid/hid-apple.c17
-rw-r--r--drivers/hid/hid-appleir.c2
-rw-r--r--drivers/hid/hid-appletb-bl.c204
-rw-r--r--drivers/hid/hid-appletb-kbd.c516
-rw-r--r--drivers/hid/hid-asus.c107
-rw-r--r--drivers/hid/hid-core.c52
-rw-r--r--drivers/hid/hid-debug.c2
-rw-r--r--drivers/hid/hid-google-hammer.c1
-rw-r--r--drivers/hid/hid-hyperv.c4
-rw-r--r--drivers/hid/hid-ids.h52
-rw-r--r--drivers/hid/hid-lenovo.c121
-rw-r--r--drivers/hid/hid-lg-g15.c146
-rw-r--r--drivers/hid/hid-magicmouse.c12
-rw-r--r--drivers/hid/hid-multitouch.c12
-rw-r--r--drivers/hid/hid-nintendo.c56
-rw-r--r--drivers/hid/hid-nvidia-shield.c2
-rw-r--r--drivers/hid/hid-plantronics.c144
-rw-r--r--drivers/hid/hid-prodikeys.c2
-rw-r--r--drivers/hid/hid-quirks.c30
-rw-r--r--drivers/hid/hid-roccat-arvo.c20
-rw-r--r--drivers/hid/hid-roccat-common.h22
-rw-r--r--drivers/hid/hid-roccat-isku.c22
-rw-r--r--drivers/hid/hid-roccat-kone.c22
-rw-r--r--drivers/hid/hid-roccat-koneplus.c42
-rw-r--r--drivers/hid/hid-roccat-konepure.c4
-rw-r--r--drivers/hid/hid-roccat-kovaplus.c38
-rw-r--r--drivers/hid/hid-roccat-lua.c10
-rw-r--r--drivers/hid/hid-roccat-pyra.c50
-rw-r--r--drivers/hid/hid-roccat-ryos.c4
-rw-r--r--drivers/hid/hid-roccat-savu.c4
-rw-r--r--drivers/hid/hid-sony.c2
-rw-r--r--drivers/hid/hid-steam.c15
-rw-r--r--drivers/hid/hid-steelseries.c120
-rw-r--r--drivers/hid/hid-thrustmaster.c1
-rw-r--r--drivers/hid/hid-topre.c7
-rw-r--r--drivers/hid/hid-uclogic-core.c9
-rw-r--r--drivers/hid/hid-uclogic-params.c2
-rw-r--r--drivers/hid/hid-universal-pidff.c202
-rw-r--r--drivers/hid/hid-wiimote-core.c2
-rw-r--r--drivers/hid/i2c-hid/Kconfig2
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c14
-rw-r--r--drivers/hid/intel-ish-hid/Kconfig1
-rw-r--r--drivers/hid/intel-ish-hid/ipc/hw-ish.h2
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c15
-rw-r--r--drivers/hid/intel-ish-hid/ipc/pci-ish.c7
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client-buffers.c21
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.c19
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.h2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/init.c30
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h3
-rw-r--r--drivers/hid/intel-thc-hid/Kconfig42
-rw-r--r--drivers/hid/intel-thc-hid/Makefile22
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c960
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h186
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c166
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h14
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c248
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h20
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c985
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h172
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c165
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h14
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c414
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h25
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c1578
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h116
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c969
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h146
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h881
-rw-r--r--drivers/hid/surface-hid/Kconfig2
-rw-r--r--drivers/hid/usbhid/Kconfig3
-rw-r--r--drivers/hid/usbhid/hid-core.c30
-rw-r--r--drivers/hid/usbhid/hid-pidff.c569
-rw-r--r--drivers/hid/usbhid/hid-pidff.h33
-rw-r--r--drivers/hid/usbhid/usbkbd.c2
-rw-r--r--drivers/hid/wacom.h8
-rw-r--r--drivers/hid/wacom_sys.c72
-rw-r--r--drivers/hid/wacom_wac.c8
-rw-r--r--drivers/hid/wacom_wac.h7
96 files changed, 10883 insertions, 722 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4d2a89d65b65..43859fc75747 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -148,6 +148,33 @@ config HID_APPLEIR
Say Y here if you want support for Apple infrared remote control.
+config HID_APPLETB_BL
+ tristate "Apple Touch Bar Backlight"
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on X86 || COMPILE_TEST
+ help
+ Say Y here if you want support for the backlight of Touch Bars on x86
+ MacBook Pros.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hid-appletb-bl.
+
+config HID_APPLETB_KBD
+ tristate "Apple Touch Bar Keyboard Mode"
+ depends on USB_HID
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on INPUT
+ depends on X86 || COMPILE_TEST
+ select INPUT_SPARSEKMAP
+ select HID_APPLETB_BL
+ help
+ Say Y here if you want support for the keyboard mode (escape,
+ function, media and brightness keys) of Touch Bars on x86 MacBook
+ Pros.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hid-appletb-kbd.
+
config HID_ASUS
tristate "Asus"
depends on USB_HID
@@ -570,6 +597,8 @@ config HID_LED
config HID_LENOVO
tristate "Lenovo / Thinkpad devices"
+ depends on ACPI
+ select ACPI_PLATFORM_PROFILE
select NEW_LEDS
select LEDS_CLASS
help
@@ -601,6 +630,7 @@ config HID_LOGITECH
tristate "Logitech devices"
depends on USB_HID
depends on LEDS_CLASS
+ depends on LEDS_CLASS_MULTICOLOR
default !EXPERT
help
Support for Logitech devices that are not fully compliant with HID standard.
@@ -787,7 +817,7 @@ config HID_NINTENDO
Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
All controllers support bluetooth, and the Pro Controller also supports
its USB mode. This also includes support for the Nintendo Switch Online
- Controllers which include the Genesis, SNES, and N64 controllers.
+ Controllers which include the NES, Genesis, SNES, and N64 controllers.
To compile this driver as a module, choose M here: the
module will be called hid-nintendo.
@@ -1167,7 +1197,8 @@ config HID_TOPRE
tristate "Topre REALFORCE keyboards"
depends on HID
help
- Say Y for N-key rollover support on Topre REALFORCE R2 108/87 key keyboards.
+ Say Y for N-key rollover support on Topre REALFORCE R2 108/87 key and
+ Topre REALFORCE R3S 87 key keyboards.
config HID_THINGM
tristate "ThingM blink(1) USB RGB LED"
@@ -1217,6 +1248,20 @@ config HID_U2FZERO
allow setting the brightness to anything but 1, which will
trigger a single blink and immediately reset back to 0.
+config HID_UNIVERSAL_PIDFF
+ tristate "universal-pidff: extended USB PID driver compatibility and usage"
+ depends on USB_HID
+ depends on HID_PID
+ help
+ Extended PID support for selected devices.
+
+ Contains report fixups, extended usable button range and
+ pidff quirk management to extend compatibility with slightly
+ non-compliant USB PID devices and better fuzz/flat values for
+ high precision direct drive devices.
+
+ Supports Moza Racing, Cammus, VRS, FFBeast and more.
+
config HID_WACOM
tristate "Wacom Intuos/Graphire tablet support (USB)"
depends on USB_HID
@@ -1374,10 +1419,6 @@ endmenu
source "drivers/hid/bpf/Kconfig"
-endif # HID
-
-source "drivers/hid/usbhid/Kconfig"
-
source "drivers/hid/i2c-hid/Kconfig"
source "drivers/hid/intel-ish-hid/Kconfig"
@@ -1386,4 +1427,12 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig"
+source "drivers/hid/intel-thc-hid/Kconfig"
+
+endif # HID
+
+# USB support may be used with HID disabled
+
+source "drivers/hid/usbhid/Kconfig"
+
endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 24de45f3677d..10ae5dedbd84 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -29,6 +29,8 @@ obj-$(CONFIG_HID_ALPS) += hid-alps.o
obj-$(CONFIG_HID_ACRUX) += hid-axff.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o
obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o
+obj-$(CONFIG_HID_APPLETB_BL) += hid-appletb-bl.o
+obj-$(CONFIG_HID_APPLETB_KBD) += hid-appletb-kbd.o
obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o
obj-$(CONFIG_HID_ASUS) += hid-asus.o
obj-$(CONFIG_HID_AUREAL) += hid-aureal.o
@@ -140,6 +142,7 @@ hid-uclogic-objs := hid-uclogic-core.o \
hid-uclogic-params.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_UDRAW_PS3) += hid-udraw-ps3.o
+obj-$(CONFIG_HID_UNIVERSAL_PIDFF) += hid-universal-pidff.o
obj-$(CONFIG_HID_LED) += hid-led.o
obj-$(CONFIG_HID_XIAOMI) += hid-xiaomi.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
@@ -166,8 +169,9 @@ obj-$(CONFIG_USB_KBD) += usbhid/
obj-$(CONFIG_I2C_HID_CORE) += i2c-hid/
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
-obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
+
+obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/
diff --git a/drivers/hid/amd-sfh-hid/Kconfig b/drivers/hid/amd-sfh-hid/Kconfig
index 329de5e12c1a..3291786a5ee6 100644
--- a/drivers/hid/amd-sfh-hid/Kconfig
+++ b/drivers/hid/amd-sfh-hid/Kconfig
@@ -5,7 +5,6 @@ menu "AMD SFH HID Support"
config AMD_SFH_HID
tristate "AMD Sensor Fusion Hub"
- depends on HID
depends on X86
help
If you say yes to this option, support will be included for the
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h
index e5620d7db569..f44a3bb2fbd4 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h
@@ -42,7 +42,9 @@ struct amd_mp2_sensor_info {
struct sfh_dev_status {
bool is_hpd_present;
+ bool is_hpd_enabled;
bool is_als_present;
+ bool is_sra_present;
};
struct amd_mp2_dev {
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
index 0c28ca349bcd..1c1fd63330c9 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
@@ -18,6 +18,7 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/slab.h>
+#include <linux/string_choices.h>
#include "amd_sfh_pcie.h"
#include "sfh1_1/amd_sfh_init.h"
@@ -122,7 +123,7 @@ int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata)
{
int rc;
- pci_intx(privdata->pdev, true);
+ pcim_intx(privdata->pdev, true);
rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
@@ -248,7 +249,7 @@ static void amd_mp2_pci_remove(void *privdata)
struct amd_mp2_dev *mp2 = privdata;
amd_sfh_hid_client_deinit(privdata);
mp2->mp2_ops->stop_all(mp2);
- pci_intx(mp2->pdev, false);
+ pcim_intx(mp2->pdev, false);
amd_sfh_clear_intr(mp2);
}
@@ -330,6 +331,57 @@ static const struct dmi_system_id dmi_nodevs[] = {
{ }
};
+static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", str_enabled_disabled(mp2->dev_en.is_hpd_enabled));
+}
+
+static ssize_t hpd_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+ bool enabled;
+ int ret;
+
+ ret = kstrtobool(buf, &enabled);
+ if (ret)
+ return ret;
+
+ mp2->sfh1_1_ops->toggle_hpd(mp2, enabled);
+
+ return count;
+}
+static DEVICE_ATTR_RW(hpd);
+
+static umode_t sfh_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+
+ if (!mp2->sfh1_1_ops || !mp2->dev_en.is_hpd_present)
+ return 0;
+
+ return attr->mode;
+}
+
+static struct attribute *sfh_attrs[] = {
+ &dev_attr_hpd.attr,
+ NULL,
+};
+
+static struct attribute_group sfh_attr_group = {
+ .attrs = sfh_attrs,
+ .is_visible = sfh_attr_is_visible,
+};
+
+static const struct attribute_group *amd_sfh_groups[] = {
+ &sfh_attr_group,
+ NULL,
+};
+
static void sfh1_1_init_work(struct work_struct *work)
{
struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
@@ -341,6 +393,11 @@ static void sfh1_1_init_work(struct work_struct *work)
amd_sfh_clear_intr(mp2);
mp2->init_done = 1;
+
+ rc = sysfs_update_group(&mp2->pdev->dev.kobj, &sfh_attr_group);
+ if (rc)
+ dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n");
+
}
static void sfh_init_work(struct work_struct *work)
@@ -487,6 +544,7 @@ static struct pci_driver amd_mp2_pci_driver = {
.driver.pm = &amd_mp2_pm_ops,
.shutdown = amd_sfh_shutdown,
.remove = amd_sfh_remove,
+ .dev_groups = amd_sfh_groups,
};
module_pci_driver(amd_mp2_pci_driver);
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
index db36d87d5634..0a9b44ce4904 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
@@ -30,6 +30,7 @@ static int amd_sfh_get_sensor_num(struct amd_mp2_dev *mp2, u8 *sensor_id)
case ACCEL_IDX:
case GYRO_IDX:
case MAG_IDX:
+ case SRA_IDX:
case ALS_IDX:
case HPD_IDX:
if (BIT(i) & slist->sl.sensors)
@@ -58,6 +59,8 @@ static const char *get_sensor_name(int idx)
return "gyroscope";
case MAG_IDX:
return "magnetometer";
+ case SRA_IDX:
+ return "SRA";
case ALS_IDX:
return "ALS";
case HPD_IDX:
@@ -80,6 +83,9 @@ static int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
case ALS_IDX:
privdata->dev_en.is_als_present = false;
break;
+ case SRA_IDX:
+ privdata->dev_en.is_sra_present = false;
+ break;
}
if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
@@ -130,6 +136,22 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
for (i = 0; i < cl_data->num_hid_devices; i++) {
cl_data->sensor_sts[i] = SENSOR_DISABLED;
+
+ if (cl_data->sensor_idx[i] == SRA_IDX) {
+ info.sensor_idx = cl_data->sensor_idx[i];
+ writel(0, privdata->mmio + amd_get_p2c_val(privdata, 0));
+ mp2_ops->start(privdata, info);
+ status = amd_sfh_wait_for_response
+ (privdata, cl_data->sensor_idx[i], ENABLE_SENSOR);
+
+ cl_data->sensor_sts[i] = (status == 0) ? SENSOR_ENABLED : SENSOR_DISABLED;
+ if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
+ cl_data->is_any_sensor_enabled = true;
+ privdata->dev_en.is_sra_present = true;
+ }
+ continue;
+ }
+
cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i;
cl_idx = cl_data->sensor_idx[i];
@@ -181,6 +203,8 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
+ if (cl_data->sensor_idx[i] == SRA_IDX)
+ continue;
cl_data->cur_hid_dev = i;
if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
cl_data->is_any_sensor_enabled = true;
@@ -190,6 +214,8 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
switch (cl_data->sensor_idx[i]) {
case HPD_IDX:
privdata->dev_en.is_hpd_present = true;
+ privdata->dev_en.is_hpd_enabled = true;
+ amd_sfh_toggle_hpd(privdata, false);
break;
case ALS_IDX:
privdata->dev_en.is_als_present = true;
@@ -214,6 +240,8 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
cleanup:
amd_sfh_hid_client_deinit(privdata);
for (i = 0; i < cl_data->num_hid_devices; i++) {
+ if (cl_data->sensor_idx[i] == SRA_IDX)
+ continue;
devm_kfree(dev, cl_data->feature_report[i]);
devm_kfree(dev, in_data->input_report[i]);
devm_kfree(dev, cl_data->report_descr[i]);
@@ -233,6 +261,10 @@ static void amd_sfh_resume(struct amd_mp2_dev *mp2)
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
+ /* leave HPD alone; policy is controlled by sysfs */
+ if (cl_data->sensor_idx[i] == HPD_IDX)
+ continue;
+
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
info.sensor_idx = cl_data->sensor_idx[i];
mp2->mp2_ops->start(mp2, info);
@@ -263,8 +295,10 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
- if (cl_data->sensor_idx[i] != HPD_IDX &&
- cl_data->sensor_sts[i] == SENSOR_ENABLED) {
+ /* leave HPD alone; policy is controlled by sysfs */
+ if (cl_data->sensor_idx[i] == HPD_IDX)
+ continue;
+ if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
(mp2, cl_data->sensor_idx[i], DISABLE_SENSOR);
@@ -282,6 +316,44 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
amd_sfh_clear_intr(mp2);
}
+void amd_sfh_toggle_hpd(struct amd_mp2_dev *mp2, bool enabled)
+{
+ struct amdtp_cl_data *cl_data = mp2->cl_data;
+ struct amd_mp2_sensor_info info;
+ int i, status;
+
+ if (mp2->dev_en.is_hpd_enabled == enabled)
+ return;
+
+ for (i = 0; i < cl_data->num_hid_devices; i++) {
+ if (cl_data->sensor_idx[i] != HPD_IDX)
+ continue;
+ info.sensor_idx = cl_data->sensor_idx[i];
+ if (enabled) {
+ mp2->mp2_ops->start(mp2, info);
+ status = amd_sfh_wait_for_response
+ (mp2, cl_data->sensor_idx[i], ENABLE_SENSOR);
+ if (status == 0)
+ status = SENSOR_ENABLED;
+ if (status == SENSOR_ENABLED)
+ cl_data->sensor_sts[i] = SENSOR_ENABLED;
+ } else {
+ mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
+ status = amd_sfh_wait_for_response
+ (mp2, cl_data->sensor_idx[i], DISABLE_SENSOR);
+ if (status == 0)
+ status = SENSOR_DISABLED;
+ if (status != SENSOR_ENABLED)
+ cl_data->sensor_sts[i] = SENSOR_DISABLED;
+ }
+ dev_dbg(&mp2->pdev->dev, "toggle sid 0x%x (%s) status 0x%x\n",
+ cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
+ cl_data->sensor_sts[i]);
+ break;
+ }
+ mp2->dev_en.is_hpd_enabled = enabled;
+}
+
static void amd_mp2_pci_remove(void *privdata)
{
struct amd_mp2_dev *mp2 = privdata;
@@ -289,7 +361,7 @@ static void amd_mp2_pci_remove(void *privdata)
sfh_deinit_emp2();
amd_sfh_hid_client_deinit(privdata);
mp2->mp2_ops->stop_all(mp2);
- pci_intx(mp2->pdev, false);
+ pcim_intx(mp2->pdev, false);
amd_sfh_clear_intr(mp2);
}
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h
index 21c44990bbeb..797d206641c6 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h
@@ -15,12 +15,15 @@
struct amd_sfh1_1_ops {
int (*init)(struct amd_mp2_dev *mp2);
+ void (*toggle_hpd)(struct amd_mp2_dev *mp2, bool enable);
};
int amd_sfh1_1_init(struct amd_mp2_dev *mp2);
+void amd_sfh_toggle_hpd(struct amd_mp2_dev *mp2, bool enabled);
static const struct amd_sfh1_1_ops __maybe_unused sfh1_1_ops = {
.init = amd_sfh1_1_init,
+ .toggle_hpd = amd_sfh_toggle_hpd,
};
#endif
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
index 4676f060da26..837d59e7a661 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c
@@ -87,6 +87,41 @@ void sfh_interface_init(struct amd_mp2_dev *mp2)
emp2 = mp2;
}
+static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
+{
+ struct sfh_op_mode mode;
+
+ if (!platform_type || !laptop_placement)
+ return -EINVAL;
+
+ if (!emp2 || !emp2->dev_en.is_sra_present)
+ return -ENODEV;
+
+ mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3));
+
+ *platform_type = mode.op_mode.devicemode;
+
+ if (mode.op_mode.ontablestate == 1) {
+ *laptop_placement = ON_TABLE;
+ } else if (mode.op_mode.ontablestate == 2) {
+ *laptop_placement = ON_LAP_MOTION;
+ } else if (mode.op_mode.inbagstate == 1) {
+ *laptop_placement = IN_BAG;
+ } else if (mode.op_mode.outbagstate == 1) {
+ *laptop_placement = OUT_OF_BAG;
+ } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 ||
+ mode.op_mode.outbagstate == 0) {
+ *laptop_placement = LP_UNKNOWN;
+ pr_warn_once("Unknown laptop placement\n");
+ } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 ||
+ mode.op_mode.outbagstate == 3) {
+ *laptop_placement = LP_UNDEFINED;
+ pr_warn_once("Undefined laptop placement\n");
+ }
+
+ return 0;
+}
+
static int amd_sfh_hpd_info(u8 *user_present)
{
struct hpd_status hpdstatus;
@@ -94,7 +129,7 @@ static int amd_sfh_hpd_info(u8 *user_present)
if (!user_present)
return -EINVAL;
- if (!emp2 || !emp2->dev_en.is_hpd_present)
+ if (!emp2 || !emp2->dev_en.is_hpd_present || !emp2->dev_en.is_hpd_enabled)
return -ENODEV;
hpdstatus.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 4));
@@ -131,6 +166,9 @@ int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
return amd_sfh_hpd_info(&sfh_info->user_present);
case MT_ALS:
return amd_sfh_als_info(&sfh_info->ambient_light);
+ case MT_SRA:
+ return amd_sfh_mode_info(&sfh_info->platform_type,
+ &sfh_info->laptop_placement);
}
}
return -EINVAL;
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h
index 2c211d28764d..665c99ad779f 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h
@@ -22,8 +22,9 @@ enum sensor_index {
ACCEL_IDX,
GYRO_IDX,
MAG_IDX,
- ALS_IDX = 4,
- HPD_IDX = 5,
+ SRA_IDX,
+ ALS_IDX,
+ HPD_IDX,
MAX_IDX = 15,
};
@@ -164,6 +165,25 @@ struct hpd_status {
};
};
+struct sfh_op_mode {
+ union {
+ u32 val;
+ struct {
+ u32 mode : 3;
+ u32 lidstatus : 1;
+ u32 angle : 10;
+ u32 inbagstatedbg : 2;
+ u32 ontablestate : 2;
+ u32 inbagstate : 2;
+ u32 outbagstate : 2;
+ u32 inbagmlcstate : 1;
+ u32 powerstate : 2;
+ u32 data : 3;
+ u32 devicemode : 4;
+ } op_mode;
+ };
+};
+
void sfh_interface_init(struct amd_mp2_dev *mp2);
void sfh_deinit_emp2(void);
void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops);
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index 2e96ec6a3073..9a06f9b0e4ef 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -38,6 +38,9 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
struct hid_bpf_ops *e;
int ret;
+ if (unlikely(hdev->bpf.destroyed))
+ return ERR_PTR(-ENODEV);
+
if (type >= HID_REPORT_TYPES)
return ERR_PTR(-EINVAL);
@@ -93,6 +96,9 @@ int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
struct hid_bpf_ops *e;
int ret, idx;
+ if (unlikely(hdev->bpf.destroyed))
+ return -ENODEV;
+
if (rtype >= HID_REPORT_TYPES)
return -EINVAL;
@@ -130,6 +136,9 @@ int dispatch_hid_bpf_output_report(struct hid_device *hdev,
struct hid_bpf_ops *e;
int ret, idx;
+ if (unlikely(hdev->bpf.destroyed))
+ return -ENODEV;
+
idx = srcu_read_lock(&hdev->bpf.srcu);
list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
srcu_read_lock_held(&hdev->bpf.srcu)) {
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
index a4a4f324aedd..489cb4fcc2cd 100644
--- a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
+++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
@@ -41,7 +41,7 @@ static const __u8 fixed_rdesc[] = {
0x15, 0x00, // Logical Minimum (0) 22
0x25, 0x01, // Logical Maximum (1) 24
0x75, 0x01, // Report Size (1) 26
- 0x95, 0x05, // Report Count (5) 28 /* changed (was 5) */
+ 0x95, 0x05, // Report Count (5) 28 /* changed (was 6) */
0x81, 0x02, // Input (Data,Var,Abs) 30
0x05, 0x09, // Usage Page (Button) /* inserted */
0x09, 0x4a, // Usage (0x4a) /* inserted to be translated as input usage 0x149: BTN_STYLUS3 */
@@ -189,8 +189,68 @@ static const __u8 fixed_rdesc[] = {
0x96, 0x00, 0x01, // Report Count (256) 322
0xb1, 0x02, // Feature (Data,Var,Abs) 325
0xc0, // End Collection 327
+ /* New in Firmware Version: HUION_M220_240524 */
+ 0x05, 0x01, // Usage Page (Generic Desktop) 328
+ 0x09, 0x01, // Usage (Pointer) 330
+ 0xa1, 0x01, // Collection (Application) 332
+ 0x09, 0x01, // Usage (Pointer) 334
+ 0xa1, 0x00, // Collection (Physical) 336
+ 0x05, 0x09, // Usage Page (Button) 338
+ 0x19, 0x01, // UsageMinimum (1) 340
+ 0x29, 0x03, // UsageMaximum (3) 342
+ 0x15, 0x00, // Logical Minimum (0) 344
+ 0x25, 0x01, // Logical Maximum (1) 346
+ 0x85, 0x02, // Report ID (2) 348
+ 0x95, 0x03, // Report Count (3) 350
+ 0x75, 0x01, // Report Size (1) 352
+ 0x81, 0x02, // Input (Data,Var,Abs) 354
+ 0x95, 0x01, // Report Count (1) 356
+ 0x75, 0x05, // Report Size (5) 358
+ 0x81, 0x01, // Input (Cnst,Arr,Abs) 360
+ 0x05, 0x01, // Usage Page (Generic Desktop) 362
+ 0x09, 0x30, // Usage (X) 364
+ 0x09, 0x31, // Usage (Y) 366
+ 0x15, 0x81, // Logical Minimum (-127) 368
+ 0x25, 0x7f, // Logical Maximum (127) 370
+ 0x75, 0x08, // Report Size (8) 372
+ 0x95, 0x02, // Report Count (2) 374
+ 0x81, 0x06, // Input (Data,Var,Rel) 376
+ 0x95, 0x04, // Report Count (4) 378
+ 0x75, 0x08, // Report Size (8) 380
+ 0x81, 0x01, // Input (Cnst,Arr,Abs) 382
+ 0xc0, // End Collection 384
+ 0xc0, // End Collection 385
+ 0x05, 0x0d, // Usage Page (Digitizers) 386
+ 0x09, 0x05, // Usage (Touch Pad) 388
+ 0xa1, 0x01, // Collection (Application) 390
+ 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 392
+ 0x09, 0x0c, // Usage (Vendor Usage 0x0c) 395
+ 0x15, 0x00, // Logical Minimum (0) 397
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 399
+ 0x75, 0x08, // Report Size (8) 402
+ 0x95, 0x10, // Report Count (16) 404
+ 0x85, 0x3f, // Report ID (63) 406
+ 0x81, 0x22, // Input (Data,Var,Abs,NoPref) 408
+ 0xc0, // End Collection 410
+ 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 411
+ 0x09, 0x0c, // Usage (Vendor Usage 0x0c) 414
+ 0xa1, 0x01, // Collection (Application) 416
+ 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 418
+ 0x09, 0x0c, // Usage (Vendor Usage 0x0c) 421
+ 0x15, 0x00, // Logical Minimum (0) 423
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 425
+ 0x85, 0x44, // Report ID (68) 428
+ 0x75, 0x08, // Report Size (8) 430
+ 0x96, 0x6b, 0x05, // Report Count (1387) 432
+ 0x81, 0x00, // Input (Data,Arr,Abs) 435
+ 0xc0, // End Collection 437
};
+#define PRE_240524_RDESC_SIZE 328
+#define PRE_240524_RDESC_FIXED_SIZE 338 /* The original bits of the descriptor */
+#define FW_240524_RDESC_SIZE 438
+#define FW_240524_RDESC_FIXED_SIZE sizeof(fixed_rdesc)
+
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
{
@@ -199,9 +259,14 @@ int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
if (!data)
return 0; /* EPERM check */
- __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+ if (hctx->size == FW_240524_RDESC_SIZE) {
+ __builtin_memcpy(data, fixed_rdesc, FW_240524_RDESC_FIXED_SIZE);
+ return sizeof(fixed_rdesc);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc, PRE_240524_RDESC_FIXED_SIZE);
- return sizeof(fixed_rdesc);
+ return PRE_240524_RDESC_FIXED_SIZE;
}
/*
@@ -263,7 +328,9 @@ HID_BPF_OPS(huion_Kamvas_pro_19) = {
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
- ctx->retval = ctx->rdesc_size != 328;
+
+ ctx->retval = !((ctx->rdesc_size == PRE_240524_RDESC_SIZE) ||
+ (ctx->rdesc_size == FW_240524_RDESC_SIZE));
if (ctx->retval)
ctx->retval = -EINVAL;
diff --git a/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c
new file mode 100644
index 000000000000..ec360d71130f
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Red Hat, Inc
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HUION 0x256C
+#define PID_KEYDIAL_K20 0x0069
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_KEYDIAL_K20),
+);
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+/* The prefix of the firmware ID we expect for this device. The full firmware
+ * string has a date suffix, e.g. HUION_T21h_230511
+ */
+char EXPECTED_FIRMWARE_ID[] = "HUION_T21h_";
+
+/* How this BPF program works: the tablet has two modes, firmware mode and
+ * tablet mode. In firmware mode (out of the box) the tablet sends button events
+ * as keyboard shortcuts and the dial as wheel but it's not forwarded by the kernel.
+ * In tablet mode it uses a vendor specific hid report to report everything instead.
+ * Depending on the mode some hid reports are never sent and the corresponding
+ * devices are mute.
+ *
+ * To switch the tablet use e.g. https://github.com/whot/huion-switcher
+ * or one of the tools from the digimend project
+ *
+ * This BPF currently works for both modes only. The huion-switcher tool sets the
+ * HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
+ * pad and pen reports (by making them vendor collections that are ignored).
+ * If that property is not set we fix all hidraw nodes so the tablet works in
+ * either mode though the drawback is that the device will show up twice if
+ * you bind it to all event nodes
+ *
+ * Default report descriptor for the first exposed hidraw node:
+ *
+ * # HUION Huion Keydial_K20
+ * # Report descriptor length: 18 bytes
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x58, // Report Size (88) 9
+ * # 0x95, 0x01, // Report Count (1) 11
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 15
+ * # 0xc0, // End Collection 17
+ * R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
+ *
+ * This report descriptor appears to be identical for all Huion devices.
+ *
+ * Second hidraw node is the Pad. This one sends the button events until the tablet is
+ * switched to raw mode, then it's mute.
+ *
+ * # HUION Huion Keydial_K20
+ * # Report descriptor length: 135 bytes
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * # 0x09, 0x06, // Usage (Keyboard) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x03, // Report ID (3) 6
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
+ * # 0x19, 0xe0, // UsageMinimum (224) 10
+ * # 0x29, 0xe7, // UsageMaximum (231) 12
+ * # 0x15, 0x00, // Logical Minimum (0) 14
+ * # 0x25, 0x01, // Logical Maximum (1) 16
+ * # 0x75, 0x01, // Report Size (1) 18
+ * # 0x95, 0x08, // Report Count (8) 20
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 22
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24
+ * # 0x19, 0x00, // UsageMinimum (0) 26
+ * # 0x29, 0xff, // UsageMaximum (255) 28
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 30
+ * # 0x75, 0x08, // Report Size (8) 33
+ * # 0x95, 0x06, // Report Count (6) 35
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 37
+ * # 0xc0, // End Collection 39
+ * # 0x05, 0x0c, // Usage Page (Consumer) 40
+ * # 0x09, 0x01, // Usage (Consumer Control) 42
+ * # 0xa1, 0x01, // Collection (Application) 44
+ * # 0x85, 0x04, // Report ID (4) 46
+ * # 0x05, 0x0c, // Usage Page (Consumer) 48
+ * # 0x19, 0x00, // UsageMinimum (0) 50
+ * # 0x2a, 0x80, 0x03, // UsageMaximum (896) 52
+ * # 0x15, 0x00, // Logical Minimum (0) 55
+ * # 0x26, 0x80, 0x03, // Logical Maximum (896) 57
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x95, 0x01, // Report Count (1) 62
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 64
+ * # 0xc0, // End Collection 66
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 67
+ * # 0x09, 0x02, // Usage (Mouse) 69
+ * # 0xa1, 0x01, // Collection (Application) 71
+ * # 0x09, 0x01, // Usage (Pointer) 73
+ * # 0x85, 0x05, // Report ID (5) 75
+ * # 0xa1, 0x00, // Collection (Physical) 77
+ * # 0x05, 0x09, // Usage Page (Button) 79
+ * # 0x19, 0x01, // UsageMinimum (1) 81
+ * # 0x29, 0x05, // UsageMaximum (5) 83
+ * # 0x15, 0x00, // Logical Minimum (0) 85
+ * # 0x25, 0x01, // Logical Maximum (1) 87
+ * # 0x95, 0x05, // Report Count (5) 89
+ * # 0x75, 0x01, // Report Size (1) 91
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 93
+ * # 0x95, 0x01, // Report Count (1) 95
+ * # 0x75, 0x03, // Report Size (3) 97
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 99
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 101
+ * # 0x09, 0x30, // Usage (X) 103
+ * # 0x09, 0x31, // Usage (Y) 105
+ * # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 107
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 110
+ * # 0x75, 0x10, // Report Size (16) 113
+ * # 0x95, 0x02, // Report Count (2) 115
+ * # 0x81, 0x06, // Input (Data,Var,Rel) 117
+ * # 0x95, 0x01, // Report Count (1) 119
+ * # 0x75, 0x08, // Report Size (8) 121
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 123
+ * # 0x09, 0x38, // Usage (Wheel) 125
+ * # 0x15, 0x81, // Logical Minimum (-127) 127
+ * # 0x25, 0x7f, // Logical Maximum (127) 129
+ * # 0x81, 0x06, // Input (Data,Var,Rel) 131
+ * # 0xc0, // End Collection 133
+ * # 0xc0, // End Collection 134
+ * R: 135 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 05 0c 19 00 2a 80 03 15 00 26 80 03 75 10 95 01 81 00 c0 05 01 09 02 a1 01 09 01 85 05 a1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 16 00 80 26 ff 7f 7510 95 02 81 06 95 01 75 08 05 01 09 38 15 81 25 7f 81 06 c0 c0
+ *
+ * Third hidraw node is a multi-axis controller which sends the dial events
+ * and the button inside the dial. If the tablet is switched to raw mode it is mute.
+ *
+ * # HUION Huion Keydial_K20
+ * # Report descriptor length: 108 bytes
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * # 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x11, // Report ID (17) 6
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 8
+ * # 0x09, 0x21, // Usage (Puck) 10
+ * # 0xa1, 0x02, // Collection (Logical) 12
+ * # 0x15, 0x00, // Logical Minimum (0) 14
+ * # 0x25, 0x01, // Logical Maximum (1) 16
+ * # 0x75, 0x01, // Report Size (1) 18
+ * # 0x95, 0x01, // Report Count (1) 20
+ * # 0xa1, 0x00, // Collection (Physical) 22
+ * # 0x05, 0x09, // Usage Page (Button) 24
+ * # 0x09, 0x01, // Usage (Button 1) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 30
+ * # 0x09, 0x33, // Usage (Touch) 32
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 34
+ * # 0x95, 0x06, // Report Count (6) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0xa1, 0x02, // Collection (Logical) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x37, // Usage (Dial) 44
+ * # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 46
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 49
+ * # 0x75, 0x10, // Report Size (16) 52
+ * # 0x95, 0x01, // Report Count (1) 54
+ * # 0x81, 0x06, // Input (Data,Var,Rel) 56
+ * # 0x35, 0x00, // Physical Minimum (0) 58
+ * # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 60
+ * # 0x15, 0x00, // Logical Minimum (0) 63
+ * # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 65
+ * # 0x09, 0x48, // Usage (Resolution Multiplier) 68
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 70
+ * # 0x45, 0x00, // Physical Maximum (0) 72
+ * # 0xc0, // End Collection 74
+ * # 0x75, 0x08, // Report Size (8) 75
+ * # 0x95, 0x01, // Report Count (1) 77
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 79
+ * # 0x75, 0x08, // Report Size (8) 81
+ * # 0x95, 0x01, // Report Count (1) 83
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 85
+ * # 0x75, 0x08, // Report Size (8) 87
+ * # 0x95, 0x01, // Report Count (1) 89
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 91
+ * # 0x75, 0x08, // Report Size (8) 93
+ * # 0x95, 0x01, // Report Count (1) 95
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 97
+ * # 0x75, 0x08, // Report Size (8) 99
+ * # 0x95, 0x01, // Report Count (1) 101
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 103
+ * # 0xc0, // End Collection 105
+ * # 0xc0, // End Collection 106
+ * # 0xc0, // End Collection 107
+ * R: 108 05 01 09 0e a1 01 85 11 05 0d 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0
+ *
+ */
+
+#define PAD_REPORT_DESCRIPTOR_LENGTH 135
+#define PUCK_REPORT_DESCRIPTOR_LENGTH 108
+#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
+#define PAD_KBD_REPORT_ID 3
+#define PAD_CC_REPORT_ID 3 // never sends events
+#define PAD_MOUSE_REPORT_ID 4 // never sends events
+#define PUCK_REPORT_ID 17
+#define VENDOR_REPORT_ID 8
+#define PAD_KBD_REPORT_LENGTH 8
+#define PAD_CC_REPORT_LENGTH 3
+#define PAD_MOUSE_REPORT_LENGTH 7
+#define PUCK_REPORT_LENGTH 9
+#define VENDOR_REPORT_LENGTH 12
+
+__u32 last_button_state;
+
+static const __u8 disabled_rdesc_puck[] = {
+ FixedSizeVendorReport(PUCK_REPORT_LENGTH)
+};
+
+static const __u8 disabled_rdesc_pad[] = {
+ FixedSizeVendorReport(PAD_KBD_REPORT_LENGTH)
+ FixedSizeVendorReport(PAD_CC_REPORT_LENGTH)
+ FixedSizeVendorReport(PAD_MOUSE_REPORT_LENGTH)
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ // We send our pad events on the vendor report id because why not
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 is a button so we look like a tablet
+ Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // Padding
+ Input(Const)
+ // Bytes 2/3 - x/y just exist so we get to be a tablet pad
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Bytes 4-7 are the button state for 19 buttons + pad out to u32
+ // We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(10)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(10)
+ ReportSize(1)
+ Input(Var|Abs)
+ // We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2
+ UsageMinimum_i8(0x31)
+ UsageMaximum_i8(0x3a)
+ ReportCount(9)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(13)
+ ReportSize(1)
+ Input(Const) // padding
+ // Byte 6 is the wheel
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+/* Identical to fixed_rdesc_pad but with different FixedSizeVendorReport */
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ // We send our pad events on the vendor report id because why not
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 is a button so we look like a tablet
+ Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // Padding
+ Input(Const)
+ // Bytes 2/3 - x/y just exist so we get to be a tablet pad
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Bytes 4-7 are the button state for 19 buttons + pad out to u32
+ // We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(10)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(10)
+ ReportSize(1)
+ Input(Var|Abs)
+ // We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2
+ UsageMinimum_i8(0x31)
+ UsageMaximum_i8(0x3a)
+ ReportCount(9)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(13)
+ ReportSize(1)
+ Input(Const) // padding
+ // Byte 6 is the wheel
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ )
+ // Make sure we match our original report lengths
+ FixedSizeVendorReport(PAD_KBD_REPORT_LENGTH)
+ FixedSizeVendorReport(PAD_CC_REPORT_LENGTH)
+ FixedSizeVendorReport(PAD_MOUSE_REPORT_LENGTH)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(k20_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* If we have a firmware ID and it matches our expected prefix, we
+ * disable the default pad/puck nodes. They won't send events
+ * but cause duplicate devices.
+ */
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+ if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
+ return sizeof(disabled_rdesc_pad);
+ } else {
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+
+ }
+ }
+ if (rdesc_size == PUCK_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_puck, sizeof(disabled_rdesc_puck));
+ return sizeof(disabled_rdesc_puck);
+ }
+ }
+ /* Always fix the vendor mode so the tablet will work even if nothing sets
+ * the udev property (e.g. huion-switcher run manually)
+ */
+ if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
+ return sizeof(fixed_rdesc_vendor);
+
+ }
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Only sent if tablet is in raw mode */
+ if (data[0] == VENDOR_REPORT_ID) {
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 pad:7;
+ __u8 x;
+ __u8 y;
+ __u32 buttons;
+ __u8 wheel;
+ } __attribute__((packed)) *pad_report;
+
+ __u8 wheel = 0;
+
+ /* Wheel report */
+ if (data[1] == 0xf1) {
+ if (data[5] == 2)
+ wheel = 0xff;
+ else
+ wheel = data[5];
+ } else {
+ /* data[4..6] are the buttons, mapped correctly */
+ last_button_state = data[4] | (data[5] << 8) | (data[6] << 16);
+ wheel = 0; // wheel
+ }
+
+ pad_report = (struct pad_report *)data;
+ pad_report->report_id = VENDOR_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->wheel = wheel;
+
+ return sizeof(struct pad_report);
+ }
+
+ if (data[0] == PAD_KBD_REPORT_ID) {
+ const __u8 button_mapping[] = {
+ 0x0e, /* Button 1: K */
+ 0x0a, /* Button 2: G */
+ 0x0f, /* Button 3: L */
+ 0x4c, /* Button 4: Delete */
+ 0x0c, /* Button 5: I */
+ 0x07, /* Button 6: D */
+ 0x05, /* Button 7: B */
+ 0x08, /* Button 8: E */
+ 0x16, /* Button 9: S */
+ 0x1d, /* Button 10: Z */
+ 0x06, /* Button 11: C */
+ 0x19, /* Button 12: V */
+ 0xff, /* Button 13: LeftControl */
+ 0xff, /* Button 14: LeftAlt */
+ 0xff, /* Button 15: LeftShift */
+ 0x28, /* Button 16: Return Enter */
+ 0x2c, /* Button 17: Spacebar */
+ 0x11, /* Button 18: N */
+ };
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 pad:7;
+ __u8 x;
+ __u8 y;
+ __u32 buttons;
+ __u8 wheel;
+ } __attribute__((packed)) *pad_report;
+ int i, b;
+ __u8 modifiers = data[1];
+ __u32 buttons = 0;
+
+ if (modifiers & 0x01) { /* Control */
+ buttons |= BIT(12);
+ }
+ if (modifiers & 0x02) { /* Shift */
+ buttons |= BIT(14);
+ }
+ if (modifiers & 0x04) { /* Alt */
+ buttons |= BIT(13);
+ }
+
+ for (i = 2; i < PAD_KBD_REPORT_LENGTH; i++) {
+ if (!data[i])
+ break;
+
+ for (b = 0; b < ARRAY_SIZE(button_mapping); b++) {
+ if (data[i] == button_mapping[b]) {
+ buttons |= BIT(b);
+ break;
+ }
+ }
+ data[i] = 0;
+ }
+
+ pad_report = (struct pad_report *)data;
+ pad_report->report_id = VENDOR_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = buttons;
+ // The wheel happens on a different hidraw node but its
+ // values are unreliable (as is the button inside the wheel).
+ // So the wheel is simply always zero, if you want the wheel
+ // to work reliably, use the tablet mode.
+ pad_report->wheel = 0;
+
+ return sizeof(struct pad_report);
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(keydial_k20) = {
+ .hid_device_event = (void *)k20_fix_events,
+ .hid_rdesc_fixup = (void *)k20_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PUCK_REPORT_DESCRIPTOR_LENGTH:
+ case VENDOR_REPORT_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/TUXEDO__Sirius-16-Gen1-and-Gen2.bpf.c b/drivers/hid/bpf/progs/TUXEDO__Sirius-16-Gen1-and-Gen2.bpf.c
new file mode 100644
index 000000000000..a123003fb5fd
--- /dev/null
+++ b/drivers/hid/bpf/progs/TUXEDO__Sirius-16-Gen1-and-Gen2.bpf.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2025 TUXEDO Computers GmbH
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x048D, 0x8910)
+);
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(ignore_key_fix_event, struct hid_bpf_ctx *hid_ctx)
+{
+ const int expected_length = 37;
+ const int expected_report_id = 1;
+ __u8 *data;
+ int i;
+
+ if (hid_ctx->size < expected_length)
+ return 0;
+
+ data = hid_bpf_get_data(hid_ctx, 0, expected_length);
+ if (!data || data[0] != expected_report_id)
+ return 0;
+
+ // Zero out F13 (HID usage ID: 0x68) key press.
+ // The first 6 parallel key presses (excluding modifier keys) are
+ // encoded in an array containing usage IDs.
+ for (i = 3; i < 9; ++i)
+ if (data[i] == 0x68)
+ data[i] = 0x00;
+ // Additional parallel key presses starting with the 7th (excluding
+ // modifier keys) are encoded as a bit flag with the offset being
+ // the usage ID.
+ data[22] &= 0xfe;
+
+ return 0;
+}
+
+HID_BPF_OPS(ignore_button) = {
+ .hid_device_event = (void *)ignore_key_fix_event,
+};
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__ACK05.bpf.c b/drivers/hid/bpf/progs/XPPen__ACK05.bpf.c
new file mode 100644
index 000000000000..a754710fc90b
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__ACK05.bpf.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Red Hat, Inc
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define HID_BPF_ASYNC_MAX_CTX 1
+#include "hid_bpf_async.h"
+
+#define VID_UGEE 0x28BD
+/* same PID whether connected directly or through the provided dongle: */
+#define PID_ACK05_REMOTE 0x0202
+
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ACK05_REMOTE),
+);
+
+/*
+ * By default, the pad reports the buttons through a set of key sequences.
+ *
+ * The pad reports a classic keyboard report descriptor:
+ * # HANVON UGEE Shortcut Remote
+ * Report descriptor length: 102 bytes
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x02, // Usage (Mouse) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x09, // Report ID (9) 6
+ * 0x09, 0x01, // Usage (Pointer) 8
+ * 0xa1, 0x00, // Collection (Physical) 10
+ * 0x05, 0x09, // Usage Page (Button) 12
+ * 0x19, 0x01, // UsageMinimum (1) 14
+ * 0x29, 0x03, // UsageMaximum (3) 16
+ * 0x15, 0x00, // Logical Minimum (0) 18
+ * 0x25, 0x01, // Logical Maximum (1) 20
+ * 0x95, 0x03, // Report Count (3) 22
+ * 0x75, 0x01, // Report Size (1) 24
+ * 0x81, 0x02, // Input (Data,Var,Abs) 26
+ * 0x95, 0x05, // Report Count (5) 28
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 30
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 32
+ * 0x09, 0x30, // Usage (X) 34
+ * 0x09, 0x31, // Usage (Y) 36
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 38
+ * 0x95, 0x02, // Report Count (2) 41
+ * 0x75, 0x10, // Report Size (16) 43
+ * 0x81, 0x02, // Input (Data,Var,Abs) 45
+ * 0x05, 0x0d, // Usage Page (Digitizers) 47
+ * 0x09, 0x30, // Usage (Tip Pressure) 49
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 51
+ * 0x95, 0x01, // Report Count (1) 54
+ * 0x75, 0x10, // Report Size (16) 56
+ * 0x81, 0x02, // Input (Data,Var,Abs) 58
+ * 0xc0, // End Collection 60
+ * 0xc0, // End Collection 61
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 62
+ * 0x09, 0x06, // Usage (Keyboard) 64
+ * 0xa1, 0x01, // Collection (Application) 66
+ * 0x85, 0x06, // Report ID (6) 68
+ * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 70
+ * 0x19, 0xe0, // UsageMinimum (224) 72
+ * 0x29, 0xe7, // UsageMaximum (231) 74
+ * 0x15, 0x00, // Logical Minimum (0) 76
+ * 0x25, 0x01, // Logical Maximum (1) 78
+ * 0x75, 0x01, // Report Size (1) 80
+ * 0x95, 0x08, // Report Count (8) 82
+ * 0x81, 0x02, // Input (Data,Var,Abs) 84
+ * 0x05, 0x07, // Usage Page (Keyboard/Keypad) 86
+ * 0x19, 0x00, // UsageMinimum (0) 88
+ * 0x29, 0xff, // UsageMaximum (255) 90
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 92
+ * 0x75, 0x08, // Report Size (8) 95
+ * 0x95, 0x06, // Report Count (6) 97
+ * 0x81, 0x00, // Input (Data,Arr,Abs) 99
+ * 0xc0, // End Collection 101
+ *
+ * Each button gets assigned the following events:
+ *
+ * Buttons released: 06 00 00 00 00 00 00 00
+ * Button 1: 06 01 12 00 00 00 00 00 -> LControl + o
+ * Button 2: 06 01 11 00 00 00 00 00 -> LControl + n
+ * Button 3: 06 00 3e 00 00 00 00 00 -> F5
+ * Button 4: 06 02 00 00 00 00 00 00 -> LShift
+ * Button 5: 06 01 00 00 00 00 00 00 -> LControl
+ * Button 6: 06 04 00 00 00 00 00 00 -> LAlt
+ * Button 7: 06 01 16 00 00 00 00 00 -> LControl + s
+ * Button 8: 06 01 1d 00 00 00 00 00 -> LControl + z
+ * Button 9: 06 00 2c 00 00 00 00 00 -> Space
+ * Button 10: 06 03 1d 00 00 00 00 00 -> LControl + LShift + z
+ * Wheel: 06 01 57 00 00 00 00 00 -> clockwise rotation (LControl + Keypad Plus)
+ * Wheel: 06 01 56 00 00 00 00 00 -> counter-clockwise rotation
+ * (LControl + Keypad Minus)
+ *
+ * However, multiple buttons can be pressed at the same time, and when this happens,
+ * each button gets assigned a new slot in the Input (Data,Arr,Abs):
+ *
+ * Button 1 + 3: 06 01 12 3e 00 00 00 00 -> LControl + o + F5
+ *
+ * When a modifier is pressed (Button 4, 5, or 6), the assigned key is set to 00:
+ *
+ * Button 5 + 7: 06 01 00 16 00 00 00 00 -> LControl + s
+ *
+ * This is mostly fine, but with Button 8 and Button 10 sharing the same
+ * key value ("z"), there are cases where we can not know which is which.
+ *
+ */
+
+#define PAD_WIRED_DESCRIPTOR_LENGTH 102
+#define PAD_DONGLE_DESCRIPTOR_LENGTH 177
+#define STYLUS_DESCRIPTOR_LENGTH 109
+#define VENDOR_DESCRIPTOR_LENGTH 36
+#define PAD_REPORT_ID 6
+#define RAW_PAD_REPORT_ID 0xf0
+#define RAW_BATTERY_REPORT_ID 0xf2
+#define VENDOR_REPORT_ID 2
+#define PAD_REPORT_LENGTH 8
+#define VENDOR_REPORT_LENGTH 12
+
+__u16 last_button_state;
+
+static const __u8 disabled_rdesc[] = {
+ // Make sure we match our original report length
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(RAW_PAD_REPORT_ID)
+ // Byte 1 in report - same than report ID
+ ReportCount(1)
+ ReportSize(8)
+ Input(Const) // padding (internal report ID)
+ LogicalMaximum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 2-3 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(0x0a)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(10)
+ ReportSize(1)
+ Input(Var|Abs)
+ Usage_i8(0x31) // will be mapped as BTN_A / BTN_SOUTH
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportCount(5) // padding
+ Input(Const)
+ // Byte 4 in report - just exists so we get to be a tablet pad
+ UsagePage_Digitizers
+ Usage_Dig_BarrelSwitch // BTN_STYLUS
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // padding
+ Input(Const)
+ // Bytes 5/6 in report - just exists so we get to be a tablet pad
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Byte 7 in report is the dial
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ )
+ // -- Byte 0 in report
+ ReportId(RAW_BATTERY_REPORT_ID)
+ // Byte 1 in report - same than report ID
+ ReportCount(1)
+ ReportSize(8)
+ Input(Const) // padding (internal report ID)
+ // Byte 2 in report - always 0x01
+ Input(Const) // padding (internal report ID)
+ UsagePage_Digitizers
+ /*
+ * We represent the device as a stylus to force the kernel to not
+ * directly query its battery state. Instead the kernel will rely
+ * only on the provided events.
+ */
+ Usage_Dig_Stylus
+ CollectionPhysical(
+ // Byte 3 in report - battery value
+ UsagePage_BatterySystem
+ Usage_BS_AbsoluteStateOfCharge
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(100)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Byte 4 in report - charging state
+ Usage_BS_Charging
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Abs)
+ )
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(ack05_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hctx->size;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
+ /*
+ * The vendor fixed rdesc is appended after the current one,
+ * to keep the output reports working.
+ */
+ __builtin_memcpy(data + rdesc_size, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
+ return sizeof(fixed_rdesc_vendor) + rdesc_size;
+ }
+
+ hid_set_name(hctx->hid, "Disabled by HID-BPF Hanvon Ugee Shortcut Remote");
+
+ __builtin_memcpy(data, disabled_rdesc, sizeof(disabled_rdesc));
+ return sizeof(disabled_rdesc);
+}
+
+static int HID_BPF_ASYNC_FUN(switch_to_raw_mode)(struct hid_bpf_ctx *hid)
+{
+ static __u8 magic_0[32] = {0x02, 0xb0, 0x04, 0x00, 0x00};
+ int err;
+
+ /*
+ * The proprietary driver sends the 3 following packets after the
+ * above one.
+ * These don't seem to have any effect, so we don't send them to save
+ * some processing time.
+ *
+ * static __u8 magic_1[32] = {0x02, 0xb4, 0x01, 0x00, 0x01};
+ * static __u8 magic_2[32] = {0x02, 0xb4, 0x01, 0x00, 0xff};
+ * static __u8 magic_3[32] = {0x02, 0xb8, 0x04, 0x00, 0x00};
+ */
+
+ err = hid_bpf_hw_output_report(hid, magic_0, sizeof(magic_0));
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(ack05_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH);
+ int ret = 0;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != VENDOR_REPORT_ID)
+ return 0;
+
+ /* reconnect event */
+ if (data[1] == 0xf8 && data[2] == 02 && data[3] == 0x01)
+ HID_BPF_ASYNC_DELAYED_CALL(switch_to_raw_mode, hctx, 10);
+
+ /* button event */
+ if (data[1] == RAW_PAD_REPORT_ID) {
+ data[0] = data[1];
+ if (data[7] == 0x02)
+ data[7] = 0xff;
+ ret = 8;
+ } else if (data[1] == RAW_BATTERY_REPORT_ID) {
+ data[0] = data[1];
+ ret = 5;
+ }
+
+ return ret;
+}
+
+HID_BPF_OPS(xppen_ack05_remote) = {
+ .hid_device_event = (void *)ack05_fix_events,
+ .hid_rdesc_fixup = (void *)ack05_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_WIRED_DESCRIPTOR_LENGTH:
+ case PAD_DONGLE_DESCRIPTOR_LENGTH:
+ case STYLUS_DESCRIPTOR_LENGTH:
+ case VENDOR_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ break;
+ }
+
+ if (ctx->rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
+ struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
+
+ if (!hctx) {
+ ctx->retval = -EINVAL;
+ return 0;
+ }
+
+ ctx->retval = HID_BPF_ASYNC_INIT(switch_to_raw_mode) ||
+ switch_to_raw_mode(hctx);
+
+ hid_bpf_release_context(hctx);
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
index a669525691aa..0c7e5cc5dc7e 100644
--- a/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
+++ b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
@@ -10,10 +10,12 @@
#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
#define PID_ARTIST_PRO14_GEN2 0x095A
#define PID_ARTIST_PRO16_GEN2 0x095B
+#define PID_ARTIST_PRO19_GEN2 0x096A
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO14_GEN2),
- HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2)
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2),
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO19_GEN2)
);
/*
@@ -22,7 +24,7 @@ HID_BPF_CONFIG(
* - when the eraser button is pressed and the stylus is touching the tablet,
* the device sends Tip Switch instead of sending Eraser
*
- * This descriptor uses physical dimensions of the 16" device.
+ * This descriptor uses the physical dimensions of the 16" device.
*/
static const __u8 fixed_rdesc[] = {
0x05, 0x0d, // Usage Page (Digitizers) 0
@@ -100,6 +102,12 @@ int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
data[62] = 0x62;
data[73] = 0x1c;
data[72] = 0xfd;
+ } else if (hctx->hid->product == PID_ARTIST_PRO19_GEN2) {
+ /* 19" screen reports 16.101" x 9.057" */
+ data[63] = 0x3e;
+ data[62] = 0xe5;
+ data[73] = 0x23;
+ data[72] = 0x61;
}
return sizeof(fixed_rdesc);
@@ -177,6 +185,27 @@ static const __u16 angle_offsets_vertical_16[128] = {
188, 186, 184, 182, 180, 178, 176, 174, 172
};
+/* 19" inch screen 16.101" x 9.057" */
+static const __u16 angle_offsets_horizontal_19[128] = {
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 25, 27, 29, 31, 33, 35, 37, 39, 41,
+ 42, 44, 46, 48, 50, 51, 53, 55, 57, 58, 60, 62, 63, 65, 67, 68, 70, 71, 73, 74, 76,
+ 77, 79, 80, 82, 83, 84, 86, 87, 88, 89, 90, 92, 93, 94, 95, 96, 97, 98, 99, 100,
+ 101, 102, 103, 104, 104, 105, 106, 106, 107, 108, 108, 109, 109, 110, 110, 111,
+ 111, 112, 112, 112, 112, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
+ 113, 113, 112, 112, 112, 112, 111, 111, 110, 110, 109, 109, 108, 108, 107, 106,
+ 106, 105, 104, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 90
+};
+static const __u16 angle_offsets_vertical_19[128] = {
+ 0, 4, 7, 11, 14, 18, 21, 25, 28, 32, 35, 38, 42, 45, 49, 52, 56, 59, 62, 66, 69, 72,
+ 75, 79, 82, 85, 88, 91, 95, 98, 101, 104, 107, 110, 113, 116, 118, 121, 124, 127,
+ 129, 132, 135, 137, 140, 142, 145, 147, 150, 152, 154, 157, 159, 161, 163, 165, 167,
+ 169, 171, 173, 174, 176, 178, 179, 181, 183, 184, 185, 187, 188, 189, 190, 192, 193,
+ 194, 195, 195, 196, 197, 198, 198, 199, 199, 200, 200, 201, 201, 201, 201, 201, 201,
+ 201, 201, 201, 201, 201, 200, 200, 199, 199, 198, 198, 197, 196, 195, 195, 194, 193,
+ 192, 190, 189, 188, 187, 185, 184, 183, 181, 179, 178, 176, 174, 173, 171, 169, 167,
+ 165, 163, 161
+};
+
static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
const __s8 tilt, const __u16 (*compensation_table)[128])
{
@@ -241,12 +270,19 @@ static int xppen_16_fix_angle_offset(struct hid_bpf_ctx *hctx)
__s8 tilt_x = (__s8) data[8];
__s8 tilt_y = (__s8) data[9];
- if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
+ switch (hctx->hid->product) {
+ case PID_ARTIST_PRO14_GEN2:
compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
- } else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
+ break;
+ case PID_ARTIST_PRO16_GEN2:
compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
+ break;
+ case PID_ARTIST_PRO19_GEN2:
+ compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_19);
+ compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_19);
+ break;
}
return 0;
diff --git a/drivers/hid/bpf/progs/hid_bpf_async.h b/drivers/hid/bpf/progs/hid_bpf_async.h
new file mode 100644
index 000000000000..9ab585434239
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf_async.h
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ * Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_ASYNC_H__
+#define __HID_BPF_ASYNC_H__
+
+#ifndef HID_BPF_ASYNC_MAX_CTX
+#error "HID_BPF_ASYNC_MAX_CTX should be set to the maximum number of concurrent async functions"
+#endif /* HID_BPF_ASYNC_MAX_CTX */
+
+#define CLOCK_MONOTONIC 1
+
+typedef int (*hid_bpf_async_callback_t)(void *map, int *key, void *value);
+
+enum hid_bpf_async_state {
+ HID_BPF_ASYNC_STATE_UNSET = 0,
+ HID_BPF_ASYNC_STATE_INITIALIZING,
+ HID_BPF_ASYNC_STATE_INITIALIZED,
+ HID_BPF_ASYNC_STATE_STARTING,
+ HID_BPF_ASYNC_STATE_RUNNING,
+};
+
+struct hid_bpf_async_map_elem {
+ struct bpf_spin_lock lock;
+ enum hid_bpf_async_state state;
+ struct bpf_timer t;
+ struct bpf_wq wq;
+ u32 hid;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, HID_BPF_ASYNC_MAX_CTX);
+ __type(key, u32);
+ __type(value, struct hid_bpf_async_map_elem);
+} hid_bpf_async_ctx_map SEC(".maps");
+
+/**
+ * HID_BPF_ASYNC_CB: macro to define an async callback used in a bpf_wq
+ *
+ * The caller is responsible for allocating a key in the async map
+ * with hid_bpf_async_get_ctx().
+ */
+#define HID_BPF_ASYNC_CB(cb) \
+cb(void *map, int *key, void *value); \
+static __always_inline int \
+____##cb(struct hid_bpf_ctx *ctx); \
+typeof(cb(0, 0, 0)) cb(void *map, int *key, void *value) \
+{ \
+ struct hid_bpf_async_map_elem *e; \
+ struct hid_bpf_ctx *ctx; \
+ \
+ e = (struct hid_bpf_async_map_elem *)value; \
+ ctx = hid_bpf_allocate_context(e->hid); \
+ if (!ctx) \
+ return 0; /* EPERM check */ \
+ \
+ e->state = HID_BPF_ASYNC_STATE_RUNNING; \
+ \
+ ____##cb(ctx); \
+ \
+ e->state = HID_BPF_ASYNC_STATE_INITIALIZED; \
+ hid_bpf_release_context(ctx); \
+ return 0; \
+} \
+static __always_inline int \
+____##cb
+
+/**
+ * ASYNC: macro to automatically handle async callbacks contexts
+ *
+ * Needs to be used in conjunction with HID_BPF_ASYNC_INIT and HID_BPF_ASYNC_DELAYED_CALL
+ */
+#define HID_BPF_ASYNC_FUN(fun) \
+fun(struct hid_bpf_ctx *ctx); \
+int ____key__##fun; \
+static int ____async_init_##fun(void) \
+{ \
+ ____key__##fun = hid_bpf_async_get_ctx(); \
+ if (____key__##fun < 0) \
+ return ____key__##fun; \
+ return 0; \
+} \
+static int HID_BPF_ASYNC_CB(____##fun##_cb)(struct hid_bpf_ctx *hctx) \
+{ \
+ return fun(hctx); \
+} \
+typeof(fun(0)) fun
+
+#define HID_BPF_ASYNC_INIT(fun) ____async_init_##fun()
+#define HID_BPF_ASYNC_DELAYED_CALL(fun, ctx, delay) \
+ hid_bpf_async_delayed_call(ctx, delay, ____key__##fun, ____##fun##_cb)
+
+/*
+ * internal cb for starting the delayed work callback in a workqueue.
+ */
+static int __start_wq_timer_cb(void *map, int *key, void *value)
+{
+ struct hid_bpf_async_map_elem *e = (struct hid_bpf_async_map_elem *)value;
+
+ bpf_wq_start(&e->wq, 0);
+
+ return 0;
+}
+
+static int hid_bpf_async_find_empty_key(void)
+{
+ int i;
+
+ bpf_for(i, 0, HID_BPF_ASYNC_MAX_CTX) {
+ struct hid_bpf_async_map_elem *elem;
+ int key = i;
+
+ elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+ if (!elem)
+ return -ENOMEM; /* should never happen */
+
+ bpf_spin_lock(&elem->lock);
+
+ if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
+ elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
+ bpf_spin_unlock(&elem->lock);
+ return i;
+ }
+
+ bpf_spin_unlock(&elem->lock);
+ }
+
+ return -EINVAL;
+}
+
+static int hid_bpf_async_get_ctx(void)
+{
+ int key = hid_bpf_async_find_empty_key();
+ struct hid_bpf_async_map_elem *elem;
+ int err;
+
+ if (key < 0)
+ return key;
+
+ elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+ if (!elem)
+ return -EINVAL;
+
+ err = bpf_timer_init(&elem->t, &hid_bpf_async_ctx_map, CLOCK_MONOTONIC);
+ if (err)
+ return err;
+
+ err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
+ if (err)
+ return err;
+
+ err = bpf_wq_init(&elem->wq, &hid_bpf_async_ctx_map, 0);
+ if (err)
+ return err;
+
+ elem->state = HID_BPF_ASYNC_STATE_INITIALIZED;
+
+ return key;
+}
+
+static inline u64 ms_to_ns(u64 milliseconds)
+{
+ return (u64)milliseconds * 1000UL * 1000UL;
+}
+
+static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds, int key,
+ hid_bpf_async_callback_t wq_cb)
+{
+ struct hid_bpf_async_map_elem *elem;
+ int err;
+
+ elem = bpf_map_lookup_elem(&hid_bpf_async_ctx_map, &key);
+ if (!elem)
+ return -EINVAL;
+
+ bpf_spin_lock(&elem->lock);
+ /* The wq must be:
+ * - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
+ * - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
+ */
+ if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
+ elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
+ bpf_spin_unlock(&elem->lock);
+ return -EINVAL;
+ }
+ elem->state = HID_BPF_ASYNC_STATE_STARTING;
+ bpf_spin_unlock(&elem->lock);
+
+ elem->hid = hctx->hid->id;
+
+ err = bpf_wq_set_callback(&elem->wq, wq_cb, 0);
+ if (err)
+ return err;
+
+ if (milliseconds) {
+ /* needed for every call because a cancel might unset this */
+ err = bpf_timer_set_callback(&elem->t, __start_wq_timer_cb);
+ if (err)
+ return err;
+
+ err = bpf_timer_start(&elem->t, ms_to_ns(milliseconds), 0);
+ if (err)
+ return err;
+
+ return 0;
+ }
+
+ return bpf_wq_start(&elem->wq, 0);
+}
+
+static inline int hid_bpf_async_call(struct hid_bpf_ctx *ctx, int key,
+ hid_bpf_async_callback_t wq_cb)
+{
+ return hid_bpf_async_delayed_call(ctx, 0, key, wq_cb);
+}
+
+#endif /* __HID_BPF_ASYNC_H__ */
diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
index 3ba24d125a08..bf19785a6b06 100644
--- a/drivers/hid/bpf/progs/hid_bpf_helpers.h
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -19,6 +19,25 @@ extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
size_t buf__sz,
enum hid_report_type type,
enum hid_class_request reqtype) __ksym;
+extern int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx,
+ __u8 *buf, size_t buf__sz) __weak __ksym;
+extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx,
+ enum hid_report_type type,
+ __u8 *data,
+ size_t buf__sz) __weak __ksym;
+extern int hid_bpf_try_input_report(struct hid_bpf_ctx *ctx,
+ enum hid_report_type type,
+ __u8 *data,
+ size_t buf__sz) __weak __ksym;
+
+/* bpf_wq implementation */
+extern int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags) __weak __ksym;
+extern int bpf_wq_start(struct bpf_wq *wq, unsigned int flags) __weak __ksym;
+extern int bpf_wq_set_callback_impl(struct bpf_wq *wq,
+ int (callback_fn)(void *map, int *key, void *value),
+ unsigned int flags__k, void *aux__ign) __ksym;
+#define bpf_wq_set_callback(wq, cb, flags) \
+ bpf_wq_set_callback_impl(wq, cb, flags, NULL)
#define HID_MAX_DESCRIPTOR_SIZE 4096
#define HID_IGNORE_EVENT -1
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 7e1ae2a2bcc2..ed34f5cd5a91 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -378,6 +378,12 @@ static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
return false;
}
+static bool apple_is_omoton_kb066(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI &&
+ strcmp(hdev->name, "Bluetooth Keyboard") == 0;
+}
+
static inline void apple_setup_key_translation(struct input_dev *input,
const struct apple_key_translation *table)
{
@@ -474,6 +480,7 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015)
table = magic_keyboard_2015_fn_keys;
else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 ||
+ hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 ||
hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021)
table = apple2021_fn_keys;
@@ -724,7 +731,7 @@ static int apple_input_configured(struct hid_device *hdev,
{
struct apple_sc *asc = hid_get_drvdata(hdev);
- if ((asc->quirks & APPLE_HAS_FN) && !asc->fn_found) {
+ if (((asc->quirks & APPLE_HAS_FN) && !asc->fn_found) || apple_is_omoton_kb066(hdev)) {
hid_info(hdev, "Fn key not found (Apple Wireless Keyboard clone?), disabling Fn key handling\n");
asc->quirks &= ~APPLE_HAS_FN;
}
@@ -943,7 +950,7 @@ static int apple_probe(struct hid_device *hdev,
return 0;
out_err:
- del_timer_sync(&asc->battery_timer);
+ timer_delete_sync(&asc->battery_timer);
hid_hw_stop(hdev);
return ret;
}
@@ -952,7 +959,7 @@ static void apple_remove(struct hid_device *hdev)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
- del_timer_sync(&asc->battery_timer);
+ timer_delete_sync(&asc->battery_timer);
hid_hw_stop(hdev);
}
@@ -1150,6 +1157,10 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021),
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
index c45e5aa569d2..bb7db9ae41c2 100644
--- a/drivers/hid/hid-appleir.c
+++ b/drivers/hid/hid-appleir.c
@@ -319,7 +319,7 @@ static void appleir_remove(struct hid_device *hid)
{
struct appleir *appleir = hid_get_drvdata(hid);
hid_hw_stop(hid);
- del_timer_sync(&appleir->key_up_timer);
+ timer_delete_sync(&appleir->key_up_timer);
}
static const struct hid_device_id appleir_devices[] = {
diff --git a/drivers/hid/hid-appletb-bl.c b/drivers/hid/hid-appletb-bl.c
new file mode 100644
index 000000000000..bad2aead8780
--- /dev/null
+++ b/drivers/hid/hid-appletb-bl.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Touch Bar Backlight Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ * Copyright (c) 2022-2023 Kerem Karabay <kekrby@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hid.h>
+#include <linux/backlight.h>
+#include <linux/device.h>
+
+#include "hid-ids.h"
+
+#define APPLETB_BL_ON 1
+#define APPLETB_BL_DIM 3
+#define APPLETB_BL_OFF 4
+
+#define HID_UP_APPLEVENDOR_TB_BL 0xff120000
+
+#define HID_VD_APPLE_TB_BRIGHTNESS 0xff120001
+#define HID_USAGE_AUX1 0xff120020
+#define HID_USAGE_BRIGHTNESS 0xff120021
+
+static int appletb_bl_def_brightness = 2;
+module_param_named(brightness, appletb_bl_def_brightness, int, 0444);
+MODULE_PARM_DESC(brightness, "Default brightness:\n"
+ " 0 - Touchbar is off\n"
+ " 1 - Dim brightness\n"
+ " [2] - Full brightness");
+
+struct appletb_bl {
+ struct hid_field *aux1_field, *brightness_field;
+ struct backlight_device *bdev;
+
+ bool full_on;
+};
+
+static const u8 appletb_bl_brightness_map[] = {
+ APPLETB_BL_OFF,
+ APPLETB_BL_DIM,
+ APPLETB_BL_ON,
+};
+
+static int appletb_bl_set_brightness(struct appletb_bl *bl, u8 brightness)
+{
+ struct hid_report *report = bl->brightness_field->report;
+ struct hid_device *hdev = report->device;
+ int ret;
+
+ ret = hid_set_field(bl->aux1_field, 0, 1);
+ if (ret) {
+ hid_err(hdev, "Failed to set auxiliary field (%pe)\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = hid_set_field(bl->brightness_field, 0, brightness);
+ if (ret) {
+ hid_err(hdev, "Failed to set brightness field (%pe)\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ if (!bl->full_on) {
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0) {
+ hid_err(hdev, "Device didn't power on (%pe)\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ bl->full_on = true;
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+ if (brightness == APPLETB_BL_OFF) {
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+ bl->full_on = false;
+ }
+
+ return 0;
+}
+
+static int appletb_bl_update_status(struct backlight_device *bdev)
+{
+ struct appletb_bl *bl = bl_get_data(bdev);
+ u8 brightness;
+
+ if (backlight_is_blank(bdev))
+ brightness = APPLETB_BL_OFF;
+ else
+ brightness = appletb_bl_brightness_map[backlight_get_brightness(bdev)];
+
+ return appletb_bl_set_brightness(bl, brightness);
+}
+
+static const struct backlight_ops appletb_bl_backlight_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = appletb_bl_update_status,
+};
+
+static int appletb_bl_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct hid_field *aux1_field, *brightness_field;
+ struct backlight_properties bl_props = { 0 };
+ struct device *dev = &hdev->dev;
+ struct appletb_bl *bl;
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "HID parse failed\n");
+
+ aux1_field = hid_find_field(hdev, HID_FEATURE_REPORT,
+ HID_VD_APPLE_TB_BRIGHTNESS, HID_USAGE_AUX1);
+
+ brightness_field = hid_find_field(hdev, HID_FEATURE_REPORT,
+ HID_VD_APPLE_TB_BRIGHTNESS, HID_USAGE_BRIGHTNESS);
+
+ if (!aux1_field || !brightness_field)
+ return -ENODEV;
+
+ if (aux1_field->report != brightness_field->report)
+ return dev_err_probe(dev, -ENODEV, "Encountered unexpected report structure\n");
+
+ bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL);
+ if (!bl)
+ return -ENOMEM;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DRIVER);
+ if (ret)
+ return dev_err_probe(dev, ret, "HID hardware start failed\n");
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ dev_err_probe(dev, ret, "HID hardware open failed\n");
+ goto stop_hw;
+ }
+
+ bl->aux1_field = aux1_field;
+ bl->brightness_field = brightness_field;
+
+ ret = appletb_bl_set_brightness(bl,
+ appletb_bl_brightness_map[(appletb_bl_def_brightness > 2) ? 2 : appletb_bl_def_brightness]);
+
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to set default touch bar brightness to %d\n",
+ appletb_bl_def_brightness);
+ goto close_hw;
+ }
+
+ bl_props.type = BACKLIGHT_RAW;
+ bl_props.max_brightness = ARRAY_SIZE(appletb_bl_brightness_map) - 1;
+
+ bl->bdev = devm_backlight_device_register(dev, "appletb_backlight", dev, bl,
+ &appletb_bl_backlight_ops, &bl_props);
+ if (IS_ERR(bl->bdev)) {
+ ret = PTR_ERR(bl->bdev);
+ dev_err_probe(dev, ret, "Failed to register backlight device\n");
+ goto close_hw;
+ }
+
+ hid_set_drvdata(hdev, bl);
+
+ return 0;
+
+close_hw:
+ hid_hw_close(hdev);
+stop_hw:
+ hid_hw_stop(hdev);
+
+ return ret;
+}
+
+static void appletb_bl_remove(struct hid_device *hdev)
+{
+ struct appletb_bl *bl = hid_get_drvdata(hdev);
+
+ appletb_bl_set_brightness(bl, APPLETB_BL_OFF);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id appletb_bl_hid_ids[] = {
+ /* MacBook Pro's 2018, 2019, with T2 chip: iBridge DFR Brightness */
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, appletb_bl_hid_ids);
+
+static struct hid_driver appletb_bl_hid_driver = {
+ .name = "hid-appletb-bl",
+ .id_table = appletb_bl_hid_ids,
+ .probe = appletb_bl_probe,
+ .remove = appletb_bl_remove,
+};
+module_hid_driver(appletb_bl_hid_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_AUTHOR("Kerem Karabay <kekrby@gmail.com>");
+MODULE_DESCRIPTION("MacBook Pro Touch Bar Backlight driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c
new file mode 100644
index 000000000000..0d43938f44fb
--- /dev/null
+++ b/drivers/hid/hid-appletb-kbd.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Touch Bar Keyboard Mode Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ * Copyright (c) 2022-2023 Kerem Karabay <kekrby@gmail.com>
+ * Copyright (c) 2024-2025 Aditya Garg <gargaditya08@live.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hid.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+#include <linux/sysfs.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/backlight.h>
+#include <linux/timer.h>
+#include <linux/input/sparse-keymap.h>
+
+#include "hid-ids.h"
+
+#define APPLETB_KBD_MODE_ESC 0
+#define APPLETB_KBD_MODE_FN 1
+#define APPLETB_KBD_MODE_SPCL 2
+#define APPLETB_KBD_MODE_OFF 3
+#define APPLETB_KBD_MODE_MAX APPLETB_KBD_MODE_OFF
+
+#define APPLETB_DEVID_KEYBOARD 1
+#define APPLETB_DEVID_TRACKPAD 2
+
+#define HID_USAGE_MODE 0x00ff0004
+
+static int appletb_tb_def_mode = APPLETB_KBD_MODE_SPCL;
+module_param_named(mode, appletb_tb_def_mode, int, 0444);
+MODULE_PARM_DESC(mode, "Default touchbar mode:\n"
+ " 0 - escape key only\n"
+ " 1 - function-keys\n"
+ " [2] - special keys");
+
+static bool appletb_tb_fn_toggle = true;
+module_param_named(fntoggle, appletb_tb_fn_toggle, bool, 0644);
+MODULE_PARM_DESC(fntoggle, "Switch between Fn and media controls on pressing Fn key");
+
+static bool appletb_tb_autodim = true;
+module_param_named(autodim, appletb_tb_autodim, bool, 0644);
+MODULE_PARM_DESC(autodim, "Automatically dim and turn off the Touch Bar after some time");
+
+static int appletb_tb_dim_timeout = 60;
+module_param_named(dim_timeout, appletb_tb_dim_timeout, int, 0644);
+MODULE_PARM_DESC(dim_timeout, "Dim timeout in sec");
+
+static int appletb_tb_idle_timeout = 15;
+module_param_named(idle_timeout, appletb_tb_idle_timeout, int, 0644);
+MODULE_PARM_DESC(idle_timeout, "Idle timeout in sec");
+
+struct appletb_kbd {
+ struct hid_field *mode_field;
+ struct input_handler inp_handler;
+ struct input_handle kbd_handle;
+ struct input_handle tpd_handle;
+ struct backlight_device *backlight_dev;
+ struct timer_list inactivity_timer;
+ bool has_dimmed;
+ bool has_turned_off;
+ u8 saved_mode;
+ u8 current_mode;
+};
+
+static const struct key_entry appletb_kbd_keymap[] = {
+ { KE_KEY, KEY_ESC, { KEY_ESC } },
+ { KE_KEY, KEY_F1, { KEY_BRIGHTNESSDOWN } },
+ { KE_KEY, KEY_F2, { KEY_BRIGHTNESSUP } },
+ { KE_KEY, KEY_F3, { KEY_RESERVED } },
+ { KE_KEY, KEY_F4, { KEY_RESERVED } },
+ { KE_KEY, KEY_F5, { KEY_KBDILLUMDOWN } },
+ { KE_KEY, KEY_F6, { KEY_KBDILLUMUP } },
+ { KE_KEY, KEY_F7, { KEY_PREVIOUSSONG } },
+ { KE_KEY, KEY_F8, { KEY_PLAYPAUSE } },
+ { KE_KEY, KEY_F9, { KEY_NEXTSONG } },
+ { KE_KEY, KEY_F10, { KEY_MUTE } },
+ { KE_KEY, KEY_F11, { KEY_VOLUMEDOWN } },
+ { KE_KEY, KEY_F12, { KEY_VOLUMEUP } },
+ { KE_END, 0 }
+};
+
+static int appletb_kbd_set_mode(struct appletb_kbd *kbd, u8 mode)
+{
+ struct hid_report *report = kbd->mode_field->report;
+ struct hid_device *hdev = report->device;
+ int ret;
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret) {
+ hid_err(hdev, "Device didn't resume (%pe)\n", ERR_PTR(ret));
+ return ret;
+ }
+
+ ret = hid_set_field(kbd->mode_field, 0, mode);
+ if (ret) {
+ hid_err(hdev, "Failed to set mode field to %u (%pe)\n", mode, ERR_PTR(ret));
+ goto power_normal;
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+ kbd->current_mode = mode;
+
+power_normal:
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+ return ret;
+}
+
+static ssize_t mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct appletb_kbd *kbd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", kbd->current_mode);
+}
+
+static ssize_t mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct appletb_kbd *kbd = dev_get_drvdata(dev);
+ u8 mode;
+ int ret;
+
+ ret = kstrtou8(buf, 0, &mode);
+ if (ret)
+ return ret;
+
+ if (mode > APPLETB_KBD_MODE_MAX)
+ return -EINVAL;
+
+ ret = appletb_kbd_set_mode(kbd, mode);
+
+ return ret < 0 ? ret : size;
+}
+static DEVICE_ATTR_RW(mode);
+
+static struct attribute *appletb_kbd_attrs[] = {
+ &dev_attr_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(appletb_kbd);
+
+static int appletb_tb_key_to_slot(unsigned int code)
+{
+ switch (code) {
+ case KEY_ESC:
+ return 0;
+ case KEY_F1 ... KEY_F10:
+ return code - KEY_F1 + 1;
+ case KEY_F11 ... KEY_F12:
+ return code - KEY_F11 + 11;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static void appletb_inactivity_timer(struct timer_list *t)
+{
+ struct appletb_kbd *kbd = from_timer(kbd, t, inactivity_timer);
+
+ if (kbd->backlight_dev && appletb_tb_autodim) {
+ if (!kbd->has_dimmed) {
+ backlight_device_set_brightness(kbd->backlight_dev, 1);
+ kbd->has_dimmed = true;
+ mod_timer(&kbd->inactivity_timer, jiffies + msecs_to_jiffies(appletb_tb_idle_timeout * 1000));
+ } else if (!kbd->has_turned_off) {
+ backlight_device_set_brightness(kbd->backlight_dev, 0);
+ kbd->has_turned_off = true;
+ }
+ }
+}
+
+static void reset_inactivity_timer(struct appletb_kbd *kbd)
+{
+ if (kbd->backlight_dev && appletb_tb_autodim) {
+ if (kbd->has_dimmed || kbd->has_turned_off) {
+ backlight_device_set_brightness(kbd->backlight_dev, 2);
+ kbd->has_dimmed = false;
+ kbd->has_turned_off = false;
+ }
+ mod_timer(&kbd->inactivity_timer, jiffies + msecs_to_jiffies(appletb_tb_dim_timeout * 1000));
+ }
+}
+
+static int appletb_kbd_hid_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct appletb_kbd *kbd = hid_get_drvdata(hdev);
+ struct key_entry *translation;
+ struct input_dev *input;
+ int slot;
+
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD || usage->type != EV_KEY)
+ return 0;
+
+ input = field->hidinput->input;
+
+ /*
+ * Skip non-touch-bar keys.
+ *
+ * Either the touch bar itself or usbhid generate a slew of key-down
+ * events for all the meta keys. None of which we're at all interested
+ * in.
+ */
+ slot = appletb_tb_key_to_slot(usage->code);
+ if (slot < 0)
+ return 0;
+
+ reset_inactivity_timer(kbd);
+
+ translation = sparse_keymap_entry_from_scancode(input, usage->code);
+
+ if (translation && kbd->current_mode == APPLETB_KBD_MODE_SPCL) {
+ input_event(input, usage->type, translation->keycode, value);
+
+ return 1;
+ }
+
+ return kbd->current_mode == APPLETB_KBD_MODE_OFF;
+}
+
+static void appletb_kbd_inp_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+ struct appletb_kbd *kbd = handle->private;
+
+ reset_inactivity_timer(kbd);
+
+ if (type == EV_KEY && code == KEY_FN && appletb_tb_fn_toggle &&
+ (kbd->current_mode == APPLETB_KBD_MODE_SPCL ||
+ kbd->current_mode == APPLETB_KBD_MODE_FN)) {
+ if (value == 1) {
+ kbd->saved_mode = kbd->current_mode;
+ appletb_kbd_set_mode(kbd, kbd->current_mode == APPLETB_KBD_MODE_SPCL
+ ? APPLETB_KBD_MODE_FN : APPLETB_KBD_MODE_SPCL);
+ } else if (value == 0) {
+ if (kbd->saved_mode != kbd->current_mode)
+ appletb_kbd_set_mode(kbd, kbd->saved_mode);
+ }
+ }
+}
+
+static int appletb_kbd_inp_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct appletb_kbd *kbd = handler->private;
+ struct input_handle *handle;
+ int rc;
+
+ if (id->driver_info == APPLETB_DEVID_KEYBOARD) {
+ handle = &kbd->kbd_handle;
+ handle->name = "tbkbd";
+ } else if (id->driver_info == APPLETB_DEVID_TRACKPAD) {
+ handle = &kbd->tpd_handle;
+ handle->name = "tbtpd";
+ } else {
+ return -ENOENT;
+ }
+
+ if (handle->dev)
+ return -EEXIST;
+
+ handle->open = 0;
+ handle->dev = input_get_device(dev);
+ handle->handler = handler;
+ handle->private = kbd;
+
+ rc = input_register_handle(handle);
+ if (rc)
+ goto err_free_dev;
+
+ rc = input_open_device(handle);
+ if (rc)
+ goto err_unregister_handle;
+
+ return 0;
+
+ err_unregister_handle:
+ input_unregister_handle(handle);
+ err_free_dev:
+ input_put_device(handle->dev);
+ handle->dev = NULL;
+ return rc;
+}
+
+static void appletb_kbd_inp_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+
+ input_put_device(handle->dev);
+ handle->dev = NULL;
+}
+
+static int appletb_kbd_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
+{
+ int idx;
+ struct input_dev *input = hidinput->input;
+
+ /*
+ * Clear various input capabilities that are blindly set by the hid
+ * driver (usbkbd.c)
+ */
+ memset(input->evbit, 0, sizeof(input->evbit));
+ memset(input->keybit, 0, sizeof(input->keybit));
+ memset(input->ledbit, 0, sizeof(input->ledbit));
+
+ __set_bit(EV_REP, input->evbit);
+
+ sparse_keymap_setup(input, appletb_kbd_keymap, NULL);
+
+ for (idx = 0; appletb_kbd_keymap[idx].type != KE_END; idx++)
+ input_set_capability(input, EV_KEY, appletb_kbd_keymap[idx].code);
+
+ return 0;
+}
+
+static const struct input_device_id appletb_kbd_input_devices[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_BUS |
+ INPUT_DEVICE_ID_MATCH_VENDOR |
+ INPUT_DEVICE_ID_MATCH_KEYBIT,
+ .bustype = BUS_USB,
+ .vendor = USB_VENDOR_ID_APPLE,
+ .keybit = { [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN) },
+ .driver_info = APPLETB_DEVID_KEYBOARD,
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_BUS |
+ INPUT_DEVICE_ID_MATCH_VENDOR |
+ INPUT_DEVICE_ID_MATCH_KEYBIT,
+ .bustype = BUS_USB,
+ .vendor = USB_VENDOR_ID_APPLE,
+ .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
+ .driver_info = APPLETB_DEVID_TRACKPAD,
+ },
+ { }
+};
+
+static bool appletb_kbd_match_internal_device(struct input_handler *handler,
+ struct input_dev *inp_dev)
+{
+ struct device *dev = &inp_dev->dev;
+
+ /* in kernel: dev && !is_usb_device(dev) */
+ while (dev && !(dev->type && dev->type->name &&
+ !strcmp(dev->type->name, "usb_device")))
+ dev = dev->parent;
+
+ /*
+ * Apple labels all their internal keyboards and trackpads as such,
+ * instead of maintaining an ever expanding list of product-id's we
+ * just look at the device's product name.
+ */
+ if (dev)
+ return !!strstr(to_usb_device(dev)->product, "Internal Keyboard");
+
+ return false;
+}
+
+static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct appletb_kbd *kbd;
+ struct device *dev = &hdev->dev;
+ struct hid_field *mode_field;
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return dev_err_probe(dev, ret, "HID parse failed\n");
+
+ mode_field = hid_find_field(hdev, HID_OUTPUT_REPORT,
+ HID_GD_KEYBOARD, HID_USAGE_MODE);
+ if (!mode_field)
+ return -ENODEV;
+
+ kbd = devm_kzalloc(dev, sizeof(*kbd), GFP_KERNEL);
+ if (!kbd)
+ return -ENOMEM;
+
+ kbd->mode_field = mode_field;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT);
+ if (ret)
+ return dev_err_probe(dev, ret, "HID hw start failed\n");
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ dev_err_probe(dev, ret, "HID hw open failed\n");
+ goto stop_hw;
+ }
+
+ kbd->backlight_dev = backlight_device_get_by_name("appletb_backlight");
+ if (!kbd->backlight_dev) {
+ dev_err_probe(dev, -ENODEV, "Failed to get backlight device\n");
+ } else {
+ backlight_device_set_brightness(kbd->backlight_dev, 2);
+ timer_setup(&kbd->inactivity_timer, appletb_inactivity_timer, 0);
+ mod_timer(&kbd->inactivity_timer, jiffies + msecs_to_jiffies(appletb_tb_dim_timeout * 1000));
+ }
+
+ kbd->inp_handler.event = appletb_kbd_inp_event;
+ kbd->inp_handler.connect = appletb_kbd_inp_connect;
+ kbd->inp_handler.disconnect = appletb_kbd_inp_disconnect;
+ kbd->inp_handler.name = "appletb";
+ kbd->inp_handler.id_table = appletb_kbd_input_devices;
+ kbd->inp_handler.match = appletb_kbd_match_internal_device;
+ kbd->inp_handler.private = kbd;
+
+ ret = input_register_handler(&kbd->inp_handler);
+ if (ret) {
+ dev_err_probe(dev, ret, "Unable to register keyboard handler\n");
+ goto close_hw;
+ }
+
+ ret = appletb_kbd_set_mode(kbd, appletb_tb_def_mode);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to set touchbar mode\n");
+ goto unregister_handler;
+ }
+
+ hid_set_drvdata(hdev, kbd);
+
+ return 0;
+
+unregister_handler:
+ input_unregister_handler(&kbd->inp_handler);
+close_hw:
+ if (kbd->backlight_dev) {
+ put_device(&kbd->backlight_dev->dev);
+ timer_delete_sync(&kbd->inactivity_timer);
+ }
+ hid_hw_close(hdev);
+stop_hw:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void appletb_kbd_remove(struct hid_device *hdev)
+{
+ struct appletb_kbd *kbd = hid_get_drvdata(hdev);
+
+ appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
+
+ input_unregister_handler(&kbd->inp_handler);
+ if (kbd->backlight_dev) {
+ put_device(&kbd->backlight_dev->dev);
+ timer_delete_sync(&kbd->inactivity_timer);
+ }
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+#ifdef CONFIG_PM
+static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ struct appletb_kbd *kbd = hid_get_drvdata(hdev);
+
+ kbd->saved_mode = kbd->current_mode;
+ appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
+
+ return 0;
+}
+
+static int appletb_kbd_reset_resume(struct hid_device *hdev)
+{
+ struct appletb_kbd *kbd = hid_get_drvdata(hdev);
+
+ appletb_kbd_set_mode(kbd, kbd->saved_mode);
+
+ return 0;
+}
+#endif
+
+static const struct hid_device_id appletb_kbd_hid_ids[] = {
+ /* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, appletb_kbd_hid_ids);
+
+static struct hid_driver appletb_kbd_hid_driver = {
+ .name = "hid-appletb-kbd",
+ .id_table = appletb_kbd_hid_ids,
+ .probe = appletb_kbd_probe,
+ .remove = appletb_kbd_remove,
+ .event = appletb_kbd_hid_event,
+ .input_configured = appletb_kbd_input_configured,
+#ifdef CONFIG_PM
+ .suspend = appletb_kbd_suspend,
+ .reset_resume = appletb_kbd_reset_resume,
+#endif
+ .driver.dev_groups = appletb_kbd_groups,
+};
+module_hid_driver(appletb_kbd_hid_driver);
+
+/* The backlight driver should be loaded before the keyboard driver is initialised */
+MODULE_SOFTDEP("pre: hid_appletb_bl");
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_AUTHOR("Kerem Karabay <kekrby@gmail.com>");
+MODULE_AUTHOR("Aditya Garg <gargaditya08@live.com>");
+MODULE_DESCRIPTION("MacBook Pro Touch Bar Keyboard Mode driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 46e3e42f9eb5..599c836507ff 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
+#define ROG_ALLY_REPORT_SIZE 64
+#define ROG_ALLY_X_MIN_MCU 313
+#define ROG_ALLY_MIN_MCU 319
+
#define SUPPORT_KBD_BACKLIGHT BIT(0)
#define MAX_TOUCH_MAJOR 8
@@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_MEDION_E1239T BIT(10)
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -534,9 +539,99 @@ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
}
+/*
+ * We don't care about any other part of the string except the version section.
+ * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
+ * The bytes "5a 05 03 31 00 1a 13" and possibly more come before the version
+ * string, and there may be additional bytes after the version string such as
+ * "75 00 74 00 65 00" or a postfix such as "_T01"
+ */
+static int mcu_parse_version_string(const u8 *response, size_t response_size)
+{
+ const u8 *end = response + response_size;
+ const u8 *p = response;
+ int dots, err, version;
+ char buf[4];
+
+ dots = 0;
+ while (p < end && dots < 2) {
+ if (*p++ == '.')
+ dots++;
+ }
+
+ if (dots != 2 || p >= end || (p + 3) >= end)
+ return -EINVAL;
+
+ memcpy(buf, p, 3);
+ buf[3] = '\0';
+
+ err = kstrtoint(buf, 10, &version);
+ if (err || version < 0)
+ return -EINVAL;
+
+ return version;
+}
+
+static int mcu_request_version(struct hid_device *hdev)
+{
+ u8 *response __free(kfree) = kzalloc(ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+ const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 };
+ int ret;
+
+ if (!response)
+ return -ENOMEM;
+
+ ret = asus_kbd_set_report(hdev, request, sizeof(request));
+ if (ret < 0)
+ return ret;
+
+ ret = hid_hw_raw_request(hdev, FEATURE_REPORT_ID, response,
+ ROG_ALLY_REPORT_SIZE, HID_FEATURE_REPORT,
+ HID_REQ_GET_REPORT);
+ if (ret < 0)
+ return ret;
+
+ ret = mcu_parse_version_string(response, ROG_ALLY_REPORT_SIZE);
+ if (ret < 0) {
+ pr_err("Failed to parse MCU version: %d\n", ret);
+ print_hex_dump(KERN_ERR, "MCU: ", DUMP_PREFIX_NONE,
+ 16, 1, response, ROG_ALLY_REPORT_SIZE, false);
+ }
+
+ return ret;
+}
+
+static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct)
+{
+ int min_version, version;
+
+ version = mcu_request_version(hdev);
+ if (version < 0)
+ return;
+
+ switch (idProduct) {
+ case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY:
+ min_version = ROG_ALLY_MIN_MCU;
+ break;
+ case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X:
+ min_version = ROG_ALLY_X_MIN_MCU;
+ break;
+ default:
+ min_version = 0;
+ }
+
+ if (version < min_version) {
+ hid_warn(hdev,
+ "The MCU firmware version must be %d or greater to avoid issues with suspend.\n",
+ min_version);
+ }
+}
+
static int asus_kbd_register_leds(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct usb_interface *intf;
+ struct usb_device *udev;
unsigned char kbd_func;
int ret;
@@ -560,6 +655,14 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
if (ret < 0)
return ret;
}
+
+ if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
+ intf = to_usb_interface(hdev->dev.parent);
+ udev = interface_to_usbdev(intf);
+ validate_mcu_fw_version(hdev,
+ le16_to_cpu(udev->descriptor.idProduct));
+ }
+
} else {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
@@ -1280,10 +1383,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD},
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
QUIRK_ROG_CLAYMORE_II_KEYBOARD },
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 5ab8e10bbd76..cd4215007d1a 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -657,7 +657,11 @@ static int hid_parser_main(struct hid_parser *parser, struct hid_item *item)
ret = hid_add_field(parser, HID_FEATURE_REPORT, data);
break;
default:
- hid_warn(parser->device, "unknown main item tag 0x%x\n", item->tag);
+ if (item->tag >= HID_MAIN_ITEM_TAG_RESERVED_MIN &&
+ item->tag <= HID_MAIN_ITEM_TAG_RESERVED_MAX)
+ hid_warn(parser->device, "reserved main item tag 0x%x\n", item->tag);
+ else
+ hid_warn(parser->device, "unknown main item tag 0x%x\n", item->tag);
ret = 0;
}
@@ -1879,9 +1883,12 @@ u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags)
/*
* 7 extra bytes are necessary to achieve proper functionality
* of implement() working on 8 byte chunks
+ * 1 extra byte for the report ID if it is null (not used) so
+ * we can reserve that extra byte in the first position of the buffer
+ * when sending it to .raw_request()
*/
- u32 len = hid_report_len(report) + 7;
+ u32 len = hid_report_len(report) + 7 + (report->id == 0);
return kzalloc(len, flags);
}
@@ -1969,7 +1976,7 @@ static struct hid_report *hid_get_report(struct hid_report_enum *report_enum,
int __hid_request(struct hid_device *hid, struct hid_report *report,
enum hid_class_request reqtype)
{
- char *buf;
+ char *buf, *data_buf;
int ret;
u32 len;
@@ -1977,13 +1984,19 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
if (!buf)
return -ENOMEM;
+ data_buf = buf;
len = hid_report_len(report);
+ if (report->id == 0) {
+ /* reserve the first byte for the report ID */
+ data_buf++;
+ len++;
+ }
+
if (reqtype == HID_REQ_SET_REPORT)
- hid_output_report(report, buf);
+ hid_output_report(report, data_buf);
- ret = hid->ll_driver->raw_request(hid, report->id, buf, len,
- report->type, reqtype);
+ ret = hid_hw_raw_request(hid, report->id, buf, len, report->type, reqtype);
if (ret < 0) {
dbg_hid("unable to complete request: %d\n", ret);
goto out;
@@ -2176,9 +2189,9 @@ static bool hid_hiddev(struct hid_device *hdev)
static ssize_t
-read_report_descriptor(struct file *filp, struct kobject *kobj,
- struct bin_attribute *attr,
- char *buf, loff_t off, size_t count)
+report_descriptor_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct hid_device *hdev = to_hid_device(dev);
@@ -2195,24 +2208,17 @@ read_report_descriptor(struct file *filp, struct kobject *kobj,
}
static ssize_t
-show_country(struct device *dev, struct device_attribute *attr,
- char *buf)
+country_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
return sprintf(buf, "%02x\n", hdev->country & 0xff);
}
-static struct bin_attribute dev_bin_attr_report_desc = {
- .attr = { .name = "report_descriptor", .mode = 0444 },
- .read = read_report_descriptor,
- .size = HID_MAX_DESCRIPTOR_SIZE,
-};
+static const BIN_ATTR_RO(report_descriptor, HID_MAX_DESCRIPTOR_SIZE);
-static const struct device_attribute dev_attr_country = {
- .attr = { .name = "country", .mode = 0444 },
- .show = show_country,
-};
+static const DEVICE_ATTR_RO(country);
int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
{
@@ -2802,13 +2808,13 @@ static struct attribute *hid_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
-static struct bin_attribute *hid_dev_bin_attrs[] = {
- &dev_bin_attr_report_desc,
+static const struct bin_attribute *hid_dev_bin_attrs[] = {
+ &bin_attr_report_descriptor,
NULL
};
static const struct attribute_group hid_dev_group = {
.attrs = hid_dev_attrs,
- .bin_attrs = hid_dev_bin_attrs,
+ .bin_attrs_new = hid_dev_bin_attrs,
};
__ATTRIBUTE_GROUPS(hid_dev);
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 541d682af15a..8433306148d5 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -3450,7 +3450,7 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_MACRO_RECORD_START] = "MacroRecordStart",
[KEY_MACRO_RECORD_STOP] = "MacroRecordStop",
[KEY_MARK_WAYPOINT] = "MarkWayPoint", [KEY_MEDIA_REPEAT] = "MediaRepeat",
- [KEY_MEDIA_TOP_MENU] = "MediaTopMenu", [KEY_MESSENGER] = "Messanger",
+ [KEY_MEDIA_TOP_MENU] = "MediaTopMenu", [KEY_MESSENGER] = "Messenger",
[KEY_NAV_CHART] = "NavChar", [KEY_NAV_INFO] = "NavInfo",
[KEY_NEWS] = "News", [KEY_NEXT_ELEMENT] = "NextElement",
[KEY_NEXT_FAVORITE] = "NextFavorite", [KEY_NOTIFICATION_CENTER] = "NotificationCenter",
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index eb6fd2dc75d0..4c1ccf7a267a 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -22,7 +22,6 @@
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
-#include <linux/pm_wakeup.h>
#include <linux/unaligned.h>
#include "hid-ids.h"
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
index 0fb210e40a41..9eafff0b6ea4 100644
--- a/drivers/hid/hid-hyperv.c
+++ b/drivers/hid/hid-hyperv.c
@@ -192,7 +192,7 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
goto cleanup;
input_device->report_desc_size = le16_to_cpu(
- desc->desc[0].wDescriptorLength);
+ desc->rpt_desc.wDescriptorLength);
if (input_device->report_desc_size == 0) {
input_device->dev_info_status = -EINVAL;
goto cleanup;
@@ -210,7 +210,7 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
memcpy(input_device->report_desc,
((unsigned char *)desc) + desc->bLength,
- le16_to_cpu(desc->desc[0].wDescriptorLength));
+ le16_to_cpu(desc->rpt_desc.wDescriptorLength));
/* Send the ack */
memset(&ack, 0, sizeof(struct mousevsc_prt_msg));
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d1d479ca50a2..b937af010e35 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -41,6 +41,10 @@
#define USB_VENDOR_ID_ACTIONSTAR 0x2101
#define USB_DEVICE_ID_ACTIONSTAR_1011 0x1011
+#define USB_VENDOR_ID_ADATA_XPG 0x125f
+#define USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE 0x7505
+#define USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE_DONGLE 0x7506
+
#define USB_VENDOR_ID_ADS_TECH 0x06e1
#define USB_DEVICE_ID_ADS_TECH_RADIO_SI470X 0xa155
@@ -184,11 +188,18 @@
#define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242
#define USB_DEVICE_ID_APPLE_IRCONTROL5 0x8243
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 0x029c
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024 0x0320
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 0x029a
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021 0x029f
#define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
#define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302
+#define USB_VENDOR_ID_ASETEK 0x2433
+#define USB_DEVICE_ID_ASETEK_INVICTA 0xf300
+#define USB_DEVICE_ID_ASETEK_FORTE 0xf301
+#define USB_DEVICE_ID_ASETEK_LA_PRIMA 0xf303
+#define USB_DEVICE_ID_ASETEK_TONY_KANAAN 0xf306
+
#define USB_VENDOR_ID_ASUS 0x0486
#define USB_DEVICE_ID_ASUS_T91MT 0x0185
#define USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO 0x0186
@@ -261,6 +272,10 @@
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577
+#define USB_VENDOR_ID_CAMMUS 0x3416
+#define USB_DEVICE_ID_CAMMUS_C5 0x0301
+#define USB_DEVICE_ID_CAMMUS_C12 0x0302
+
#define USB_VENDOR_ID_CANDO 0x2087
#define USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH 0x0703
#define USB_DEVICE_ID_CANDO_MULTI_TOUCH 0x0a01
@@ -296,6 +311,8 @@
#define USB_DEVICE_ID_ASUS_AK1D 0x1125
#define USB_DEVICE_ID_CHICONY_TOSHIBA_WT10A 0x1408
#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421
+#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA 0xb824
+#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2 0xb82c
#define USB_VENDOR_ID_CHUNGHWAT 0x2247
#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001
@@ -452,6 +469,11 @@
#define USB_VENDOR_ID_EVISION 0x320f
#define USB_DEVICE_ID_EVISION_ICL01 0x5041
+#define USB_VENDOR_ID_FFBEAST 0x045b
+#define USB_DEVICE_ID_FFBEAST_JOYSTICK 0x58f9
+#define USB_DEVICE_ID_FFBEAST_RUDDER 0x5968
+#define USB_DEVICE_ID_FFBEAST_WHEEL 0x59d7
+
#define USB_VENDOR_ID_FLATFROG 0x25b5
#define USB_DEVICE_ID_MULTITOUCH_3200 0x0002
@@ -798,6 +820,7 @@
#define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067
#define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085
#define USB_DEVICE_ID_LENOVO_X1_TAB 0x60a3
+#define USB_DEVICE_ID_LENOVO_X1_TAB2 0x60a4
#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5
#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe
#define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae
@@ -816,6 +839,13 @@
#define I2C_DEVICE_ID_LG_8001 0x8001
#define I2C_DEVICE_ID_LG_7010 0x7010
+#define USB_VENDOR_ID_LITE_STAR 0x11ff
+#define USB_DEVICE_ID_PXN_V10 0x3245
+#define USB_DEVICE_ID_PXN_V12 0x1212
+#define USB_DEVICE_ID_PXN_V12_LITE 0x1112
+#define USB_DEVICE_ID_PXN_V12_LITE_2 0x1211
+#define USB_DEVICE_LITE_STAR_GT987_FF 0x2141
+
#define USB_VENDOR_ID_LOGITECH 0x046d
#define USB_DEVICE_ID_LOGITECH_Z_10_SPK 0x0a07
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
@@ -963,6 +993,18 @@
#define USB_VENDOR_ID_MONTEREY 0x0566
#define USB_DEVICE_ID_GENIUS_KB29E 0x3004
+#define USB_VENDOR_ID_MOZA 0x346e
+#define USB_DEVICE_ID_MOZA_R3 0x0005
+#define USB_DEVICE_ID_MOZA_R3_2 0x0015
+#define USB_DEVICE_ID_MOZA_R5 0x0004
+#define USB_DEVICE_ID_MOZA_R5_2 0x0014
+#define USB_DEVICE_ID_MOZA_R9 0x0002
+#define USB_DEVICE_ID_MOZA_R9_2 0x0012
+#define USB_DEVICE_ID_MOZA_R12 0x0006
+#define USB_DEVICE_ID_MOZA_R12_2 0x0016
+#define USB_DEVICE_ID_MOZA_R16_R21 0x0000
+#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
+
#define USB_VENDOR_ID_MSI 0x1770
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
@@ -1088,11 +1130,14 @@
#define USB_VENDOR_ID_PRODIGE 0x05af
#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062
+#define I2C_VENDOR_ID_QTEC 0x6243
+
#define USB_VENDOR_ID_QUANTA 0x0408
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH 0x3000
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001 0x3001
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003 0x3003
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008 0x3008
+#define USB_DEVICE_ID_QUANTA_HP_5MP_CAMERA_5473 0x5473
#define I2C_VENDOR_ID_RAYDIUM 0x2386
#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33
@@ -1299,6 +1344,7 @@
#define USB_VENDOR_ID_TOPRE 0x0853
#define USB_DEVICE_ID_TOPRE_REALFORCE_R2_108 0x0148
#define USB_DEVICE_ID_TOPRE_REALFORCE_R2_87 0x0146
+#define USB_DEVICE_ID_TOPRE_REALFORCE_R3S_87 0x0313
#define USB_VENDOR_ID_TOPSEED 0x0766
#define USB_DEVICE_ID_TOPSEED_CYBERLINK 0x0204
@@ -1372,6 +1418,9 @@
#define USB_DEVICE_ID_VELLEMAN_K8061_FIRST 0x8061
#define USB_DEVICE_ID_VELLEMAN_K8061_LAST 0x8068
+#define USB_VENDOR_ID_VRS 0x0483
+#define USB_DEVICE_ID_VRS_DFP 0xa355
+
#define USB_VENDOR_ID_VTL 0x0306
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
@@ -1478,4 +1527,7 @@
#define USB_VENDOR_ID_SIGNOTEC 0x2133
#define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018
+#define USB_VENDOR_ID_SMARTLINKTECHNOLOGY 0x4c4a
+#define USB_DEVICE_ID_SMARTLINKTECHNOLOGY_4155 0x4155
+
#endif
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index f66194fde891..b3121fa7a72d 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -32,11 +32,20 @@
#include <linux/leds.h>
#include <linux/workqueue.h>
+#include <linux/platform_profile.h>
+
#include "hid-ids.h"
/* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */
#define LENOVO_KEY_MICMUTE KEY_F20
+/* HID raw events for ThinkPad X12 Tabs*/
+#define TP_X12_RAW_HOTKEY_FN_F4 0x00020003
+#define TP_X12_RAW_HOTKEY_FN_F8 0x38001003
+#define TP_X12_RAW_HOTKEY_FN_F10 0x00000803
+#define TP_X12_RAW_HOTKEY_FN_F12 0x00000403
+#define TP_X12_RAW_HOTKEY_FN_SPACE 0x18001003
+
struct lenovo_drvdata {
u8 led_report[3]; /* Must be first for proper alignment */
int led_state;
@@ -71,6 +80,14 @@ struct lenovo_drvdata {
#define TP10UBKBD_LED_OFF 1
#define TP10UBKBD_LED_ON 2
+/* Function to report raw_events as key events*/
+static inline void report_key_event(struct input_dev *input, int keycode)
+{
+ input_report_key(input, keycode, 1);
+ input_report_key(input, keycode, 0);
+ input_sync(input);
+}
+
static int lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
enum led_brightness value)
{
@@ -472,7 +489,10 @@ static int lenovo_input_mapping(struct hid_device *hdev,
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field,
usage, bit, max);
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
return lenovo_input_mapping_x1_tab_kbd(hdev, hi, field, usage, bit, max);
default:
@@ -529,11 +549,14 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev)
/*
* Tell the keyboard a driver understands it, and turn F7, F9, F11 into
- * regular keys
+ * regular keys (Compact only)
*/
- ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
- if (ret)
- hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
+ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD ||
+ hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD) {
+ ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
+ if (ret)
+ hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
+ }
/* Switch middle button to native mode */
ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01);
@@ -582,8 +605,11 @@ static ssize_t attr_fn_lock_store(struct device *dev,
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
lenovo_features_set_cptkbd(hdev);
break;
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
if (ret)
@@ -680,6 +706,57 @@ static const struct attribute_group lenovo_attr_group_cptkbd = {
.attrs = lenovo_attributes_cptkbd,
};
+/* Function to handle Lenovo Thinkpad TAB X12's HID raw inputs for fn keys*/
+static int lenovo_raw_event_TP_X12_tab(struct hid_device *hdev, u32 raw_data)
+{
+ struct hid_input *hidinput;
+ struct input_dev *input = NULL;
+
+ /* Iterate through all associated input devices */
+ list_for_each_entry(hidinput, &hdev->inputs, list) {
+ input = hidinput->input;
+ if (!input)
+ continue;
+
+ switch (raw_data) {
+ /* fn-F20 being used here for MIC mute*/
+ case TP_X12_RAW_HOTKEY_FN_F4:
+ report_key_event(input, LENOVO_KEY_MICMUTE);
+ return 1;
+ /* Power-mode or Airplane mode will be called based on the device*/
+ case TP_X12_RAW_HOTKEY_FN_F8:
+ /*
+ * TP X12 TAB uses Fn-F8 calls Airplanemode
+ * Whereas TP X12 TAB2 uses Fn-F8 for toggling
+ * Power modes
+ */
+ if (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) {
+ report_key_event(input, KEY_RFKILL);
+ return 1;
+ }
+ platform_profile_cycle();
+ return 1;
+ case TP_X12_RAW_HOTKEY_FN_F10:
+ /* TAB1 has PICKUP Phone and TAB2 use Snipping tool*/
+ (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) ?
+ report_key_event(input, KEY_PICKUP_PHONE) :
+ report_key_event(input, KEY_SELECTIVE_SCREENSHOT);
+ return 1;
+ case TP_X12_RAW_HOTKEY_FN_F12:
+ /* BookMarks/STAR key*/
+ report_key_event(input, KEY_BOOKMARKS);
+ return 1;
+ case TP_X12_RAW_HOTKEY_FN_SPACE:
+ /* Keyboard LED backlight toggle*/
+ report_key_event(input, KEY_KBDILLUMTOGGLE);
+ return 1;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
static int lenovo_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size)
{
@@ -697,6 +774,15 @@ static int lenovo_raw_event(struct hid_device *hdev,
data[2] = 0x01;
}
+ /*
+ * Lenovo TP X12 Tab KBD's Fn+XX is HID raw data defined. Report ID is 0x03
+ * e.g.: Raw data received for MIC mute is 0x00020003.
+ */
+ if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB
+ || hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2)
+ && size >= 3 && report->id == 0x03))
+ return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data));
+
return 0;
}
@@ -776,8 +862,11 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
return lenovo_event_cptkbd(hdev, field, usage, value);
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
return lenovo_event_tp10ubkbd(hdev, field, usage, value);
default:
@@ -1057,8 +1146,11 @@ static int lenovo_led_brightness_set(struct led_classdev *led_cdev,
case USB_DEVICE_ID_LENOVO_TPKBD:
lenovo_led_set_tpkbd(hdev);
break;
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
break;
@@ -1243,8 +1335,15 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
* We cannot read the state, only set it, so we force it to on here
* (which should be a no-op) to make sure that our state matches the
* keyboard's FN-lock state. This is the same as what Windows does.
+ *
+ * For X12 TAB and TAB2, the default windows behaviour Fn-lock Off.
+ * Adding additional check to ensure the behaviour in case of
+ * Thinkpad X12 Tabs.
*/
- data->fn_lock = true;
+
+ data->fn_lock = !(hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB ||
+ hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2);
+
lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock);
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
@@ -1288,8 +1387,11 @@ static int lenovo_probe(struct hid_device *hdev,
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
ret = lenovo_probe_cptkbd(hdev);
break;
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_probe_tp10ubkbd(hdev);
break;
@@ -1375,8 +1477,11 @@ static void lenovo_remove(struct hid_device *hdev)
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
lenovo_remove_cptkbd(hdev);
break;
+ case USB_DEVICE_ID_LENOVO_X12_TAB:
+ case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
+ case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
lenovo_remove_tp10ubkbd(hdev);
break;
@@ -1428,7 +1533,13 @@ static const struct hid_device_id lenovo_devices[] = {
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB2) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB3) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) },
{ }
};
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
index 53e7b90f9cc3..f8605656257b 100644
--- a/drivers/hid/hid-lg-g15.c
+++ b/drivers/hid/hid-lg-g15.c
@@ -8,11 +8,13 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/usb.h>
#include <linux/wait.h>
+#include <dt-bindings/leds/common.h>
#include "hid-ids.h"
@@ -44,9 +46,13 @@ enum lg_g15_led_type {
};
struct lg_g15_led {
- struct led_classdev cdev;
+ union {
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+ };
enum led_brightness brightness;
enum lg_g15_led_type led;
+ /* Used to store initial color intensities before subled_info is allocated */
u8 red, green, blue;
};
@@ -229,15 +235,15 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15,
struct lg_g15_led *g15_led,
enum led_brightness brightness)
{
+ struct mc_subled *subleds = g15_led->mcdev.subled_info;
int ret;
+ led_mc_calc_color_components(&g15_led->mcdev, brightness);
+
g15->transfer_buf[0] = 5 + g15_led->led;
- g15->transfer_buf[1] =
- DIV_ROUND_CLOSEST(g15_led->red * brightness, 255);
- g15->transfer_buf[2] =
- DIV_ROUND_CLOSEST(g15_led->green * brightness, 255);
- g15->transfer_buf[3] =
- DIV_ROUND_CLOSEST(g15_led->blue * brightness, 255);
+ g15->transfer_buf[1] = subleds[0].brightness;
+ g15->transfer_buf[2] = subleds[1].brightness;
+ g15->transfer_buf[3] = subleds[2].brightness;
ret = hid_hw_raw_request(g15->hdev,
LG_G510_FEATURE_BACKLIGHT_RGB + g15_led->led,
@@ -258,8 +264,9 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15,
static int lg_g510_kbd_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev);
struct lg_g15_led *g15_led =
- container_of(led_cdev, struct lg_g15_led, cdev);
+ container_of(mc, struct lg_g15_led, mcdev);
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
int ret;
@@ -276,82 +283,20 @@ static int lg_g510_kbd_led_set(struct led_classdev *led_cdev,
static enum led_brightness lg_g510_kbd_led_get(struct led_classdev *led_cdev)
{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev);
struct lg_g15_led *g15_led =
- container_of(led_cdev, struct lg_g15_led, cdev);
+ container_of(mc, struct lg_g15_led, mcdev);
return g15_led->brightness;
}
-static ssize_t color_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lg_g15_led *g15_led =
- container_of(led_cdev, struct lg_g15_led, cdev);
- struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
- unsigned long value;
- int ret;
-
- if (count < 7 || (count == 8 && buf[7] != '\n') || count > 8)
- return -EINVAL;
-
- if (buf[0] != '#')
- return -EINVAL;
-
- ret = kstrtoul(buf + 1, 16, &value);
- if (ret)
- return ret;
-
- mutex_lock(&g15->mutex);
- g15_led->red = (value & 0xff0000) >> 16;
- g15_led->green = (value & 0x00ff00) >> 8;
- g15_led->blue = (value & 0x0000ff);
- ret = lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness);
- mutex_unlock(&g15->mutex);
-
- return (ret < 0) ? ret : count;
-}
-
-static ssize_t color_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lg_g15_led *g15_led =
- container_of(led_cdev, struct lg_g15_led, cdev);
- struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
- ssize_t ret;
-
- mutex_lock(&g15->mutex);
- ret = sprintf(buf, "#%02x%02x%02x\n",
- g15_led->red, g15_led->green, g15_led->blue);
- mutex_unlock(&g15->mutex);
-
- return ret;
-}
-
-static DEVICE_ATTR_RW(color);
-
-static struct attribute *lg_g510_kbd_led_attrs[] = {
- &dev_attr_color.attr,
- NULL,
-};
-
-static const struct attribute_group lg_g510_kbd_led_group = {
- .attrs = lg_g510_kbd_led_attrs,
-};
-
-static const struct attribute_group *lg_g510_kbd_led_groups[] = {
- &lg_g510_kbd_led_group,
- NULL,
-};
-
static void lg_g510_leds_sync_work(struct work_struct *work)
{
struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
+ struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS];
mutex_lock(&g15->mutex);
- lg_g510_kbd_led_write(g15, &g15->leds[LG_G15_KBD_BRIGHTNESS],
- g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
+ lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness);
mutex_unlock(&g15->mutex);
}
@@ -667,8 +612,46 @@ static void lg_g15_input_close(struct input_dev *dev)
hid_hw_close(hdev);
}
+static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index)
+{
+ int i;
+ struct mc_subled *subled_info;
+
+ g15->leds[index].mcdev.led_cdev.brightness_set_blocking =
+ lg_g510_kbd_led_set;
+ g15->leds[index].mcdev.led_cdev.brightness_get =
+ lg_g510_kbd_led_get;
+ g15->leds[index].mcdev.led_cdev.max_brightness = 255;
+ g15->leds[index].mcdev.num_colors = 3;
+
+ subled_info = devm_kcalloc(&g15->hdev->dev, 3, sizeof(*subled_info), GFP_KERNEL);
+ if (!subled_info)
+ return;
+
+ for (i = 0; i < 3; i++) {
+ switch (i + 1) {
+ case LED_COLOR_ID_RED:
+ subled_info[i].color_index = LED_COLOR_ID_RED;
+ subled_info[i].intensity = g15->leds[index].red;
+ break;
+ case LED_COLOR_ID_GREEN:
+ subled_info[i].color_index = LED_COLOR_ID_GREEN;
+ subled_info[i].intensity = g15->leds[index].green;
+ break;
+ case LED_COLOR_ID_BLUE:
+ subled_info[i].color_index = LED_COLOR_ID_BLUE;
+ subled_info[i].intensity = g15->leds[index].blue;
+ break;
+ }
+ subled_info[i].channel = i;
+ }
+ g15->leds[index].mcdev.subled_info = subled_info;
+}
+
static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
{
+ int ret;
+
g15->leds[i].led = i;
g15->leds[i].cdev.name = name;
@@ -685,6 +668,7 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
} else {
g15->leds[i].cdev.max_brightness = 1;
}
+ ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
break;
case LG_G510:
case LG_G510_USB_AUDIO:
@@ -697,12 +681,11 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
g15->leds[i].cdev.name = "g15::power_on_backlight_val";
fallthrough;
case LG_G15_KBD_BRIGHTNESS:
- g15->leds[i].cdev.brightness_set_blocking =
- lg_g510_kbd_led_set;
- g15->leds[i].cdev.brightness_get =
- lg_g510_kbd_led_get;
- g15->leds[i].cdev.max_brightness = 255;
- g15->leds[i].cdev.groups = lg_g510_kbd_led_groups;
+ /* register multicolor LED */
+ lg_g15_setup_led_rgb(g15, i);
+ ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev,
+ &g15->leds[i].mcdev,
+ NULL);
break;
default:
g15->leds[i].cdev.brightness_set_blocking =
@@ -710,11 +693,12 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
g15->leds[i].cdev.brightness_get =
lg_g510_mkey_led_get;
g15->leds[i].cdev.max_brightness = 1;
+ ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
}
break;
}
- return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
+ return ret;
}
/* Common input device init code shared between keyboards and Z-10 speaker handling */
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index ec110dea8772..adfa329e917b 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -52,6 +52,7 @@ module_param(report_undeciphered, bool, 0644);
MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
#define TRACKPAD2_2021_BT_VERSION 0x110
+#define TRACKPAD_2024_BT_VERSION 0x314
#define TRACKPAD_REPORT_ID 0x28
#define TRACKPAD2_USB_REPORT_ID 0x02
@@ -567,9 +568,12 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
*/
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
if (input->id.version == TRACKPAD2_2021_BT_VERSION)
+ input->name = "Apple Inc. Magic Trackpad 2021";
+ else if (input->id.version == TRACKPAD_2024_BT_VERSION) {
+ input->name = "Apple Inc. Magic Trackpad USB-C";
+ } else {
input->name = "Apple Inc. Magic Trackpad";
- else
- input->name = "Apple Inc. Magic Trackpad 2";
+ }
} else { /* USB_VENDOR_ID_APPLE */
input->name = hdev->name;
}
@@ -911,7 +915,7 @@ static int magicmouse_probe(struct hid_device *hdev,
return 0;
err_stop_hw:
- del_timer_sync(&msc->battery_timer);
+ timer_delete_sync(&msc->battery_timer);
hid_hw_stop(hdev);
return ret;
}
@@ -922,7 +926,7 @@ static void magicmouse_remove(struct hid_device *hdev)
if (msc) {
cancel_delayed_work_sync(&msc->work);
- del_timer_sync(&msc->battery_timer);
+ timer_delete_sync(&msc->battery_timer);
}
hid_hw_stop(hdev);
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e50887a6d22c..536a0a47518f 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -1299,7 +1299,7 @@ static void mt_touch_report(struct hid_device *hid,
mod_timer(&td->release_timer,
jiffies + msecs_to_jiffies(100));
else
- del_timer(&td->release_timer);
+ timer_delete(&td->release_timer);
}
clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
@@ -1881,7 +1881,7 @@ static void mt_remove(struct hid_device *hdev)
{
struct mt_device *td = hid_get_drvdata(hdev);
- del_timer_sync(&td->release_timer);
+ timer_delete_sync(&td->release_timer);
sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group);
hid_hw_stop(hdev);
@@ -2122,12 +2122,18 @@ static const struct hid_device_id mt_devices[] = {
HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC,
USB_VENDOR_ID_LG, I2C_DEVICE_ID_LG_7010) },
- /* Lenovo X1 TAB Gen 2 */
+ /* Lenovo X1 TAB Gen 1 */
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X1_TAB) },
+ /* Lenovo X1 TAB Gen 2 */
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LENOVO,
+ USB_DEVICE_ID_LENOVO_X1_TAB2) },
+
/* Lenovo X1 TAB Gen 3 */
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 55153a2f7988..fb4985988615 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -308,6 +308,7 @@ enum joycon_ctlr_state {
JOYCON_CTLR_STATE_INIT,
JOYCON_CTLR_STATE_READ,
JOYCON_CTLR_STATE_REMOVED,
+ JOYCON_CTLR_STATE_SUSPENDED,
};
/* Controller type received as part of device info */
@@ -456,24 +457,20 @@ static const struct joycon_ctlr_button_mapping snescon_button_mappings[] = {
{ /* sentinel */ },
};
-/*
- * "A", "B", and "C" are mapped positionally, rather than by label (e.g., "A"
- * gets assigned to BTN_EAST instead of BTN_A).
- */
static const struct joycon_ctlr_button_mapping gencon_button_mappings[] = {
- { BTN_SOUTH, JC_BTN_A, },
- { BTN_EAST, JC_BTN_B, },
- { BTN_WEST, JC_BTN_R, },
- { BTN_SELECT, JC_BTN_ZR, },
+ { BTN_WEST, JC_BTN_A, }, /* A */
+ { BTN_SOUTH, JC_BTN_B, }, /* B */
+ { BTN_EAST, JC_BTN_R, }, /* C */
+ { BTN_TL, JC_BTN_X, }, /* X MD/GEN 6B Only */
+ { BTN_NORTH, JC_BTN_Y, }, /* Y MD/GEN 6B Only */
+ { BTN_TR, JC_BTN_L, }, /* Z MD/GEN 6B Only */
+ { BTN_SELECT, JC_BTN_ZR, }, /* Mode */
{ BTN_START, JC_BTN_PLUS, },
{ BTN_MODE, JC_BTN_HOME, },
{ BTN_Z, JC_BTN_CAP, },
{ /* sentinel */ },
};
-/*
- * N64's C buttons get assigned to d-pad directions and registered as buttons.
- */
static const struct joycon_ctlr_button_mapping n64con_button_mappings[] = {
{ BTN_A, JC_BTN_A, },
{ BTN_B, JC_BTN_B, },
@@ -2754,14 +2751,46 @@ static void nintendo_hid_remove(struct hid_device *hdev)
static int nintendo_hid_resume(struct hid_device *hdev)
{
- int ret = joycon_init(hdev);
+ struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+ int ret;
+
+ hid_dbg(hdev, "resume\n");
+ if (!joycon_using_usb(ctlr)) {
+ hid_dbg(hdev, "no-op resume for bt ctlr\n");
+ ctlr->ctlr_state = JOYCON_CTLR_STATE_READ;
+ return 0;
+ }
+ ret = joycon_init(hdev);
if (ret)
- hid_err(hdev, "Failed to restore controller after resume");
+ hid_err(hdev,
+ "Failed to restore controller after resume: %d\n",
+ ret);
+ else
+ ctlr->ctlr_state = JOYCON_CTLR_STATE_READ;
return ret;
}
+static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+
+ hid_dbg(hdev, "suspend: %d\n", message.event);
+ /*
+ * Avoid any blocking loops in suspend/resume transitions.
+ *
+ * joycon_enforce_subcmd_rate() can result in repeated retries if for
+ * whatever reason the controller stops providing input reports.
+ *
+ * This has been observed with bluetooth controllers which lose
+ * connectivity prior to suspend (but not long enough to result in
+ * complete disconnection).
+ */
+ ctlr->ctlr_state = JOYCON_CTLR_STATE_SUSPENDED;
+ return 0;
+}
+
#endif
static const struct hid_device_id nintendo_hid_devices[] = {
@@ -2800,6 +2829,7 @@ static struct hid_driver nintendo_hid_driver = {
#ifdef CONFIG_PM
.resume = nintendo_hid_resume,
+ .suspend = nintendo_hid_suspend,
#endif
};
static int __init nintendo_init(void)
diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c
index ff9078ad1961..b0c40a245bf8 100644
--- a/drivers/hid/hid-nvidia-shield.c
+++ b/drivers/hid/hid-nvidia-shield.c
@@ -1102,7 +1102,7 @@ static void shield_remove(struct hid_device *hdev)
hid_hw_close(hdev);
thunderstrike_destroy(ts);
- del_timer_sync(&ts->psy_stats_timer);
+ timer_delete_sync(&ts->psy_stats_timer);
cancel_work_sync(&ts->hostcmd_req_work);
hid_hw_stop(hdev);
}
diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c
index 25cfd964dc25..acb9eb18f7cc 100644
--- a/drivers/hid/hid-plantronics.c
+++ b/drivers/hid/hid-plantronics.c
@@ -6,9 +6,6 @@
* Copyright (c) 2015-2018 Terry Junge <terry.junge@plantronics.com>
*/
-/*
- */
-
#include "hid-ids.h"
#include <linux/hid.h>
@@ -23,30 +20,28 @@
#define PLT_VOL_UP 0x00b1
#define PLT_VOL_DOWN 0x00b2
+#define PLT_MIC_MUTE 0x00b5
#define PLT1_VOL_UP (PLT_HID_1_0_PAGE | PLT_VOL_UP)
#define PLT1_VOL_DOWN (PLT_HID_1_0_PAGE | PLT_VOL_DOWN)
+#define PLT1_MIC_MUTE (PLT_HID_1_0_PAGE | PLT_MIC_MUTE)
#define PLT2_VOL_UP (PLT_HID_2_0_PAGE | PLT_VOL_UP)
#define PLT2_VOL_DOWN (PLT_HID_2_0_PAGE | PLT_VOL_DOWN)
+#define PLT2_MIC_MUTE (PLT_HID_2_0_PAGE | PLT_MIC_MUTE)
+#define HID_TELEPHONY_MUTE (HID_UP_TELEPHONY | 0x2f)
+#define HID_CONSUMER_MUTE (HID_UP_CONSUMER | 0xe2)
#define PLT_DA60 0xda60
#define PLT_BT300_MIN 0x0413
#define PLT_BT300_MAX 0x0418
-
-#define PLT_ALLOW_CONSUMER (field->application == HID_CP_CONSUMERCONTROL && \
- (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER)
-
-#define PLT_QUIRK_DOUBLE_VOLUME_KEYS BIT(0)
-#define PLT_QUIRK_FOLLOWED_OPPOSITE_VOLUME_KEYS BIT(1)
-
#define PLT_DOUBLE_KEY_TIMEOUT 5 /* ms */
-#define PLT_FOLLOWED_OPPOSITE_KEY_TIMEOUT 220 /* ms */
struct plt_drv_data {
unsigned long device_type;
- unsigned long last_volume_key_ts;
- u32 quirks;
+ unsigned long last_key_ts;
+ unsigned long double_key_to;
+ __u16 last_key;
};
static int plantronics_input_mapping(struct hid_device *hdev,
@@ -58,34 +53,43 @@ static int plantronics_input_mapping(struct hid_device *hdev,
unsigned short mapped_key;
struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
unsigned long plt_type = drv_data->device_type;
+ int allow_mute = usage->hid == HID_TELEPHONY_MUTE;
+ int allow_consumer = field->application == HID_CP_CONSUMERCONTROL &&
+ (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
+ usage->hid != HID_CONSUMER_MUTE;
/* special case for PTT products */
if (field->application == HID_GD_JOYSTICK)
goto defaulted;
- /* handle volume up/down mapping */
/* non-standard types or multi-HID interfaces - plt_type is PID */
if (!(plt_type & HID_USAGE_PAGE)) {
switch (plt_type) {
case PLT_DA60:
- if (PLT_ALLOW_CONSUMER)
+ if (allow_consumer)
goto defaulted;
- goto ignored;
+ if (usage->hid == HID_CONSUMER_MUTE) {
+ mapped_key = KEY_MICMUTE;
+ goto mapped;
+ }
+ break;
default:
- if (PLT_ALLOW_CONSUMER)
+ if (allow_consumer || allow_mute)
goto defaulted;
}
+ goto ignored;
}
- /* handle standard types - plt_type is 0xffa0uuuu or 0xffa2uuuu */
- /* 'basic telephony compliant' - allow default consumer page map */
- else if ((plt_type & HID_USAGE) >= PLT_BASIC_TELEPHONY &&
- (plt_type & HID_USAGE) != PLT_BASIC_EXCEPTION) {
- if (PLT_ALLOW_CONSUMER)
- goto defaulted;
- }
- /* not 'basic telephony' - apply legacy mapping */
- /* only map if the field is in the device's primary vendor page */
- else if (!((field->application ^ plt_type) & HID_USAGE_PAGE)) {
+
+ /* handle standard consumer control mapping */
+ /* and standard telephony mic mute mapping */
+ if (allow_consumer || allow_mute)
+ goto defaulted;
+
+ /* handle vendor unique types - plt_type is 0xffa0uuuu or 0xffa2uuuu */
+ /* if not 'basic telephony compliant' - map vendor unique controls */
+ if (!((plt_type & HID_USAGE) >= PLT_BASIC_TELEPHONY &&
+ (plt_type & HID_USAGE) != PLT_BASIC_EXCEPTION) &&
+ !((field->application ^ plt_type) & HID_USAGE_PAGE))
switch (usage->hid) {
case PLT1_VOL_UP:
case PLT2_VOL_UP:
@@ -95,8 +99,11 @@ static int plantronics_input_mapping(struct hid_device *hdev,
case PLT2_VOL_DOWN:
mapped_key = KEY_VOLUMEDOWN;
goto mapped;
+ case PLT1_MIC_MUTE:
+ case PLT2_MIC_MUTE:
+ mapped_key = KEY_MICMUTE;
+ goto mapped;
}
- }
/*
* Future mapping of call control or other usages,
@@ -105,6 +112,8 @@ static int plantronics_input_mapping(struct hid_device *hdev,
*/
ignored:
+ hid_dbg(hdev, "usage: %08x (appl: %08x) - ignored\n",
+ usage->hid, field->application);
return -1;
defaulted:
@@ -123,38 +132,26 @@ static int plantronics_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
+ unsigned long prev_tsto, cur_ts;
+ __u16 prev_key, cur_key;
- if (drv_data->quirks & PLT_QUIRK_DOUBLE_VOLUME_KEYS) {
- unsigned long prev_ts, cur_ts;
+ /* Usages are filtered in plantronics_usages. */
- /* Usages are filtered in plantronics_usages. */
+ /* HZ too low for ms resolution - double key detection disabled */
+ /* or it is a key release - handle key presses only. */
+ if (!drv_data->double_key_to || !value)
+ return 0;
- if (!value) /* Handle key presses only. */
- return 0;
+ prev_tsto = drv_data->last_key_ts + drv_data->double_key_to;
+ cur_ts = drv_data->last_key_ts = jiffies;
+ prev_key = drv_data->last_key;
+ cur_key = drv_data->last_key = usage->code;
- prev_ts = drv_data->last_volume_key_ts;
- cur_ts = jiffies;
- if (jiffies_to_msecs(cur_ts - prev_ts) <= PLT_DOUBLE_KEY_TIMEOUT)
- return 1; /* Ignore the repeated key. */
-
- drv_data->last_volume_key_ts = cur_ts;
+ /* If the same key occurs in <= double_key_to -- ignore it */
+ if (prev_key == cur_key && time_before_eq(cur_ts, prev_tsto)) {
+ hid_dbg(hdev, "double key %d ignored\n", cur_key);
+ return 1; /* Ignore the repeated key. */
}
- if (drv_data->quirks & PLT_QUIRK_FOLLOWED_OPPOSITE_VOLUME_KEYS) {
- unsigned long prev_ts, cur_ts;
-
- /* Usages are filtered in plantronics_usages. */
-
- if (!value) /* Handle key presses only. */
- return 0;
-
- prev_ts = drv_data->last_volume_key_ts;
- cur_ts = jiffies;
- if (jiffies_to_msecs(cur_ts - prev_ts) <= PLT_FOLLOWED_OPPOSITE_KEY_TIMEOUT)
- return 1; /* Ignore the followed opposite volume key. */
-
- drv_data->last_volume_key_ts = cur_ts;
- }
-
return 0;
}
@@ -196,12 +193,16 @@ static int plantronics_probe(struct hid_device *hdev,
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err;
+ return ret;
}
drv_data->device_type = plantronics_device_type(hdev);
- drv_data->quirks = id->driver_data;
- drv_data->last_volume_key_ts = jiffies - msecs_to_jiffies(PLT_DOUBLE_KEY_TIMEOUT);
+ drv_data->double_key_to = msecs_to_jiffies(PLT_DOUBLE_KEY_TIMEOUT);
+ drv_data->last_key_ts = jiffies - drv_data->double_key_to;
+
+ /* if HZ does not allow ms resolution - disable double key detection */
+ if (drv_data->double_key_to < PLT_DOUBLE_KEY_TIMEOUT)
+ drv_data->double_key_to = 0;
hid_set_drvdata(hdev, drv_data);
@@ -210,29 +211,10 @@ static int plantronics_probe(struct hid_device *hdev,
if (ret)
hid_err(hdev, "hw start failed\n");
-err:
return ret;
}
static const struct hid_device_id plantronics_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES),
- .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES),
- .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES),
- .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES),
- .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3325_SERIES),
- .driver_data = PLT_QUIRK_FOLLOWED_OPPOSITE_VOLUME_KEYS },
- { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
- USB_DEVICE_ID_PLANTRONICS_ENCOREPRO_500_SERIES),
- .driver_data = PLT_QUIRK_FOLLOWED_OPPOSITE_VOLUME_KEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
{ }
};
@@ -241,6 +223,14 @@ MODULE_DEVICE_TABLE(hid, plantronics_devices);
static const struct hid_usage_id plantronics_usages[] = {
{ HID_CP_VOLUMEUP, EV_KEY, HID_ANY_ID },
{ HID_CP_VOLUMEDOWN, EV_KEY, HID_ANY_ID },
+ { HID_TELEPHONY_MUTE, EV_KEY, HID_ANY_ID },
+ { HID_CONSUMER_MUTE, EV_KEY, HID_ANY_ID },
+ { PLT2_VOL_UP, EV_KEY, HID_ANY_ID },
+ { PLT2_VOL_DOWN, EV_KEY, HID_ANY_ID },
+ { PLT2_MIC_MUTE, EV_KEY, HID_ANY_ID },
+ { PLT1_VOL_UP, EV_KEY, HID_ANY_ID },
+ { PLT1_VOL_DOWN, EV_KEY, HID_ANY_ID },
+ { PLT1_MIC_MUTE, EV_KEY, HID_ANY_ID },
{ HID_TERMINATOR, HID_TERMINATOR, HID_TERMINATOR }
};
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
index 3d08c190a935..c6b922c2adba 100644
--- a/drivers/hid/hid-prodikeys.c
+++ b/drivers/hid/hid-prodikeys.c
@@ -254,7 +254,7 @@ static void stop_sustain_timers(struct pcmidi_snd *pm)
for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
pms = &pm->sustained_notes[i];
pms->in_use = 1;
- del_timer_sync(&pms->timer);
+ timer_delete_sync(&pms->timer);
}
}
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index e0bbf0c6345d..06c27308e497 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -27,6 +27,8 @@
static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE_DONGLE), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016), HID_QUIRK_FULLSPEED_INTERVAL },
{ HID_USB_DEVICE(USB_VENDOR_ID_AIREN, USB_DEVICE_ID_AIREN_SLIMPLUS), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI_09E8, USB_DEVICE_ID_AKAI_09E8_MIDIMIX), HID_QUIRK_NO_INIT_REPORTS },
@@ -328,8 +330,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
#endif
#if IS_ENABLED(CONFIG_HID_APPLEIR)
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) },
@@ -338,6 +338,12 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL5) },
#endif
+#if IS_ENABLED(CONFIG_HID_APPLETB_BL)
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
+#endif
+#if IS_ENABLED(CONFIG_HID_APPLETB_KBD)
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
+#endif
#if IS_ENABLED(CONFIG_HID_ASUS)
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD) },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD) },
@@ -595,6 +601,17 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_PLANTRONICS)
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
#endif
+#if IS_ENABLED(CONFIG_HID_PLAYSTATION)
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
+#endif
#if IS_ENABLED(CONFIG_HID_PRIMAX)
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
#endif
@@ -664,11 +681,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) },
@@ -745,6 +757,8 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AVERMEDIA, USB_DEVICE_ID_AVER_FM_MR800) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AXENTIA, USB_DEVICE_ID_AXENTIA_FM_RADIO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) },
{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) },
@@ -891,6 +905,8 @@ static const struct hid_device_id hid_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DPAD) },
#endif
{ HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_HP_5MP_CAMERA_5473) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SMARTLINKTECHNOLOGY, USB_DEVICE_ID_SMARTLINKTECHNOLOGY_4155) },
{ }
};
diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c
index d55aaabab1ed..3048297569c5 100644
--- a/drivers/hid/hid-roccat-arvo.c
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -224,24 +224,24 @@ static ssize_t arvo_sysfs_read(struct file *fp,
}
static ssize_t arvo_sysfs_write_button(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
return arvo_sysfs_write(fp, kobj, buf, off, count,
sizeof(struct arvo_button), ARVO_COMMAND_BUTTON);
}
-static BIN_ATTR(button, 0220, NULL, arvo_sysfs_write_button,
- sizeof(struct arvo_button));
+static const BIN_ATTR(button, 0220, NULL, arvo_sysfs_write_button,
+ sizeof(struct arvo_button));
static ssize_t arvo_sysfs_read_info(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
return arvo_sysfs_read(fp, kobj, buf, off, count,
sizeof(struct arvo_info), ARVO_COMMAND_INFO);
}
-static BIN_ATTR(info, 0440, arvo_sysfs_read_info, NULL,
- sizeof(struct arvo_info));
+static const BIN_ATTR(info, 0440, arvo_sysfs_read_info, NULL,
+ sizeof(struct arvo_info));
static struct attribute *arvo_attrs[] = {
&dev_attr_mode_key.attr,
@@ -250,7 +250,7 @@ static struct attribute *arvo_attrs[] = {
NULL,
};
-static struct bin_attribute *arvo_bin_attributes[] = {
+static const struct bin_attribute *const arvo_bin_attributes[] = {
&bin_attr_button,
&bin_attr_info,
NULL,
@@ -258,7 +258,7 @@ static struct bin_attribute *arvo_bin_attributes[] = {
static const struct attribute_group arvo_group = {
.attrs = arvo_attrs,
- .bin_attrs = arvo_bin_attributes,
+ .bin_attrs_new = arvo_bin_attributes,
};
static const struct attribute_group *arvo_groups[] = {
diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h
index 839ddfd931f0..0f9a2db04df9 100644
--- a/drivers/hid/hid-roccat-common.h
+++ b/drivers/hid/hid-roccat-common.h
@@ -46,8 +46,8 @@ ssize_t roccat_common2_sysfs_write(struct file *fp, struct kobject *kobj,
#define ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE) \
static ssize_t roccat_common2_sysfs_write_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return roccat_common2_sysfs_write(fp, kobj, buf, off, count, \
SIZE, COMMAND); \
@@ -55,8 +55,8 @@ static ssize_t roccat_common2_sysfs_write_ ## thingy(struct file *fp, \
#define ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE) \
static ssize_t roccat_common2_sysfs_read_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return roccat_common2_sysfs_read(fp, kobj, buf, off, count, \
SIZE, COMMAND); \
@@ -68,27 +68,27 @@ ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE)
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_RW(thingy, COMMAND, SIZE); \
-static struct bin_attribute bin_attr_ ## thingy = { \
+static const struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = SIZE, \
- .read = roccat_common2_sysfs_read_ ## thingy, \
- .write = roccat_common2_sysfs_write_ ## thingy \
+ .read_new = roccat_common2_sysfs_read_ ## thingy, \
+ .write_new = roccat_common2_sysfs_write_ ## thingy \
}
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_R(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE); \
-static struct bin_attribute bin_attr_ ## thingy = { \
+static const struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = SIZE, \
- .read = roccat_common2_sysfs_read_ ## thingy, \
+ .read_new = roccat_common2_sysfs_read_ ## thingy, \
}
#define ROCCAT_COMMON2_BIN_ATTRIBUTE_W(thingy, COMMAND, SIZE) \
ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE); \
-static struct bin_attribute bin_attr_ ## thingy = { \
+static const struct bin_attribute bin_attr_ ## thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = SIZE, \
- .write = roccat_common2_sysfs_write_ ## thingy \
+ .write_new = roccat_common2_sysfs_write_ ## thingy \
}
#endif
diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c
index 0cd6208fb371..65a84bfcc2f8 100644
--- a/drivers/hid/hid-roccat-isku.c
+++ b/drivers/hid/hid-roccat-isku.c
@@ -156,7 +156,7 @@ static ssize_t isku_sysfs_write(struct file *fp, struct kobject *kobj,
#define ISKU_SYSFS_W(thingy, THINGY) \
static ssize_t isku_sysfs_write_ ## thingy(struct file *fp, struct kobject *kobj, \
- struct bin_attribute *attr, char *buf, \
+ const struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return isku_sysfs_write(fp, kobj, buf, off, count, \
@@ -165,7 +165,7 @@ static ssize_t isku_sysfs_write_ ## thingy(struct file *fp, struct kobject *kobj
#define ISKU_SYSFS_R(thingy, THINGY) \
static ssize_t isku_sysfs_read_ ## thingy(struct file *fp, struct kobject *kobj, \
- struct bin_attribute *attr, char *buf, \
+ const struct bin_attribute *attr, char *buf, \
loff_t off, size_t count) \
{ \
return isku_sysfs_read(fp, kobj, buf, off, count, \
@@ -178,27 +178,27 @@ ISKU_SYSFS_W(thingy, THINGY)
#define ISKU_BIN_ATTR_RW(thingy, THINGY) \
ISKU_SYSFS_RW(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = ISKU_SIZE_ ## THINGY, \
- .read = isku_sysfs_read_ ## thingy, \
- .write = isku_sysfs_write_ ## thingy \
+ .read_new = isku_sysfs_read_ ## thingy, \
+ .write_new = isku_sysfs_write_ ## thingy \
}
#define ISKU_BIN_ATTR_R(thingy, THINGY) \
ISKU_SYSFS_R(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = ISKU_SIZE_ ## THINGY, \
- .read = isku_sysfs_read_ ## thingy, \
+ .read_new = isku_sysfs_read_ ## thingy, \
}
#define ISKU_BIN_ATTR_W(thingy, THINGY) \
ISKU_SYSFS_W(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = ISKU_SIZE_ ## THINGY, \
- .write = isku_sysfs_write_ ## thingy \
+ .write_new = isku_sysfs_write_ ## thingy \
}
ISKU_BIN_ATTR_RW(macro, MACRO);
@@ -217,7 +217,7 @@ ISKU_BIN_ATTR_W(control, CONTROL);
ISKU_BIN_ATTR_W(reset, RESET);
ISKU_BIN_ATTR_R(info, INFO);
-static struct bin_attribute *isku_bin_attributes[] = {
+static const struct bin_attribute *const isku_bin_attributes[] = {
&bin_attr_macro,
&bin_attr_keys_function,
&bin_attr_keys_easyzone,
@@ -238,7 +238,7 @@ static struct bin_attribute *isku_bin_attributes[] = {
static const struct attribute_group isku_group = {
.attrs = isku_attrs,
- .bin_attrs = isku_bin_attributes,
+ .bin_attrs_new = isku_bin_attributes,
};
static const struct attribute_group *isku_groups[] = {
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
index 3f8f459edcf3..b3c0242e5a37 100644
--- a/drivers/hid/hid-roccat-kone.c
+++ b/drivers/hid/hid-roccat-kone.c
@@ -261,7 +261,7 @@ static int kone_get_firmware_version(struct usb_device *usb_dev, int *result)
}
static ssize_t kone_sysfs_read_settings(struct file *fp, struct kobject *kobj,
- struct bin_attribute *attr, char *buf,
+ const struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
@@ -285,7 +285,7 @@ static ssize_t kone_sysfs_read_settings(struct file *fp, struct kobject *kobj,
* case of error the old data is still valid
*/
static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj,
- struct bin_attribute *attr, char *buf,
+ const struct bin_attribute *attr, char *buf,
loff_t off, size_t count) {
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
@@ -327,11 +327,11 @@ unlock:
return sizeof(struct kone_settings);
}
-static BIN_ATTR(settings, 0660, kone_sysfs_read_settings,
- kone_sysfs_write_settings, sizeof(struct kone_settings));
+static const BIN_ATTR(settings, 0660, kone_sysfs_read_settings,
+ kone_sysfs_write_settings, sizeof(struct kone_settings));
static ssize_t kone_sysfs_read_profilex(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr,
+ struct kobject *kobj, const struct bin_attribute *attr,
char *buf, loff_t off, size_t count) {
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
@@ -351,7 +351,7 @@ static ssize_t kone_sysfs_read_profilex(struct file *fp,
/* Writes data only if different to stored data */
static ssize_t kone_sysfs_write_profilex(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr,
+ struct kobject *kobj, const struct bin_attribute *attr,
char *buf, loff_t off, size_t count) {
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
@@ -382,11 +382,11 @@ static ssize_t kone_sysfs_write_profilex(struct file *fp,
return sizeof(struct kone_profile);
}
#define PROFILE_ATTR(number) \
-static struct bin_attribute bin_attr_profile##number = { \
+static const struct bin_attribute bin_attr_profile##number = { \
.attr = { .name = "profile" #number, .mode = 0660 }, \
.size = sizeof(struct kone_profile), \
- .read = kone_sysfs_read_profilex, \
- .write = kone_sysfs_write_profilex, \
+ .read_new = kone_sysfs_read_profilex, \
+ .write_new = kone_sysfs_write_profilex, \
.private = &profile_numbers[number-1], \
}
PROFILE_ATTR(1);
@@ -634,7 +634,7 @@ static struct attribute *kone_attrs[] = {
NULL,
};
-static struct bin_attribute *kone_bin_attributes[] = {
+static const struct bin_attribute *const kone_bin_attributes[] = {
&bin_attr_settings,
&bin_attr_profile1,
&bin_attr_profile2,
@@ -646,7 +646,7 @@ static struct bin_attribute *kone_bin_attributes[] = {
static const struct attribute_group kone_group = {
.attrs = kone_attrs,
- .bin_attrs = kone_bin_attributes,
+ .bin_attrs_new = kone_bin_attributes,
};
static const struct attribute_group *kone_groups[] = {
diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c
index 8ccb3b14a1a9..5d8a5ce88b4c 100644
--- a/drivers/hid/hid-roccat-koneplus.c
+++ b/drivers/hid/hid-roccat-koneplus.c
@@ -128,8 +128,8 @@ static ssize_t koneplus_sysfs_write(struct file *fp, struct kobject *kobj,
#define KONEPLUS_SYSFS_W(thingy, THINGY) \
static ssize_t koneplus_sysfs_write_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return koneplus_sysfs_write(fp, kobj, buf, off, count, \
KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
@@ -137,8 +137,8 @@ static ssize_t koneplus_sysfs_write_ ## thingy(struct file *fp, \
#define KONEPLUS_SYSFS_R(thingy, THINGY) \
static ssize_t koneplus_sysfs_read_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return koneplus_sysfs_read(fp, kobj, buf, off, count, \
KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
@@ -150,27 +150,27 @@ KONEPLUS_SYSFS_R(thingy, THINGY)
#define KONEPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
KONEPLUS_SYSFS_RW(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
- .read = koneplus_sysfs_read_ ## thingy, \
- .write = koneplus_sysfs_write_ ## thingy \
+ .read_new = koneplus_sysfs_read_ ## thingy, \
+ .write_new = koneplus_sysfs_write_ ## thingy \
}
#define KONEPLUS_BIN_ATTRIBUTE_R(thingy, THINGY) \
KONEPLUS_SYSFS_R(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
- .read = koneplus_sysfs_read_ ## thingy, \
+ .read_new = koneplus_sysfs_read_ ## thingy, \
}
#define KONEPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
KONEPLUS_SYSFS_W(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = KONEPLUS_SIZE_ ## THINGY, \
- .write = koneplus_sysfs_write_ ## thingy \
+ .write_new = koneplus_sysfs_write_ ## thingy \
}
KONEPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
KONEPLUS_BIN_ATTRIBUTE_W(talk, TALK);
@@ -183,8 +183,8 @@ KONEPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
KONEPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t koneplus_sysfs_read_profilex_settings(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -201,8 +201,8 @@ static ssize_t koneplus_sysfs_read_profilex_settings(struct file *fp,
}
static ssize_t koneplus_sysfs_read_profilex_buttons(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -219,16 +219,16 @@ static ssize_t koneplus_sysfs_read_profilex_buttons(struct file *fp,
}
#define PROFILE_ATTR(number) \
-static struct bin_attribute bin_attr_profile##number##_settings = { \
+static const struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = KONEPLUS_SIZE_PROFILE_SETTINGS, \
- .read = koneplus_sysfs_read_profilex_settings, \
+ .read_new = koneplus_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
-static struct bin_attribute bin_attr_profile##number##_buttons = { \
+static const struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = KONEPLUS_SIZE_PROFILE_BUTTONS, \
- .read = koneplus_sysfs_read_profilex_buttons, \
+ .read_new = koneplus_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
@@ -321,7 +321,7 @@ static struct attribute *koneplus_attrs[] = {
NULL,
};
-static struct bin_attribute *koneplus_bin_attributes[] = {
+static const struct bin_attribute *const koneplus_bin_attributes[] = {
&bin_attr_control,
&bin_attr_talk,
&bin_attr_macro,
@@ -346,7 +346,7 @@ static struct bin_attribute *koneplus_bin_attributes[] = {
static const struct attribute_group koneplus_group = {
.attrs = koneplus_attrs,
- .bin_attrs = koneplus_bin_attributes,
+ .bin_attrs_new = koneplus_bin_attributes,
};
static const struct attribute_group *koneplus_groups[] = {
diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c
index beca8aef8bbb..7fb705789d4e 100644
--- a/drivers/hid/hid-roccat-konepure.c
+++ b/drivers/hid/hid-roccat-konepure.c
@@ -47,7 +47,7 @@ ROCCAT_COMMON2_BIN_ATTRIBUTE_R(tcu_image, 0x0c, 0x0404);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0x0f, 0x06);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(talk, 0x10, 0x10);
-static struct bin_attribute *konepure_bin_attrs[] = {
+static const struct bin_attribute *const konepure_bin_attrs[] = {
&bin_attr_actual_profile,
&bin_attr_control,
&bin_attr_info,
@@ -62,7 +62,7 @@ static struct bin_attribute *konepure_bin_attrs[] = {
};
static const struct attribute_group konepure_group = {
- .bin_attrs = konepure_bin_attrs,
+ .bin_attrs_new = konepure_bin_attrs,
};
static const struct attribute_group *konepure_groups[] = {
diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c
index 748d4d7cb2fc..e31e4a2e62d5 100644
--- a/drivers/hid/hid-roccat-kovaplus.c
+++ b/drivers/hid/hid-roccat-kovaplus.c
@@ -171,8 +171,8 @@ static ssize_t kovaplus_sysfs_write(struct file *fp, struct kobject *kobj,
#define KOVAPLUS_SYSFS_W(thingy, THINGY) \
static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return kovaplus_sysfs_write(fp, kobj, buf, off, count, \
KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
@@ -180,8 +180,8 @@ static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \
#define KOVAPLUS_SYSFS_R(thingy, THINGY) \
static ssize_t kovaplus_sysfs_read_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return kovaplus_sysfs_read(fp, kobj, buf, off, count, \
KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
@@ -193,19 +193,19 @@ KOVAPLUS_SYSFS_R(thingy, THINGY)
#define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
KOVAPLUS_SYSFS_RW(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = KOVAPLUS_SIZE_ ## THINGY, \
- .read = kovaplus_sysfs_read_ ## thingy, \
- .write = kovaplus_sysfs_write_ ## thingy \
+ .read_new = kovaplus_sysfs_read_ ## thingy, \
+ .write_new = kovaplus_sysfs_write_ ## thingy \
}
#define KOVAPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
KOVAPLUS_SYSFS_W(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = KOVAPLUS_SIZE_ ## THINGY, \
- .write = kovaplus_sysfs_write_ ## thingy \
+ .write_new = kovaplus_sysfs_write_ ## thingy \
}
KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO);
@@ -213,8 +213,8 @@ KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -231,8 +231,8 @@ static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp,
}
static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -249,16 +249,16 @@ static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp,
}
#define PROFILE_ATTR(number) \
-static struct bin_attribute bin_attr_profile##number##_settings = { \
+static const struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = KOVAPLUS_SIZE_PROFILE_SETTINGS, \
- .read = kovaplus_sysfs_read_profilex_settings, \
+ .read_new = kovaplus_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
-static struct bin_attribute bin_attr_profile##number##_buttons = { \
+static const struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = KOVAPLUS_SIZE_PROFILE_BUTTONS, \
- .read = kovaplus_sysfs_read_profilex_buttons, \
+ .read_new = kovaplus_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
@@ -379,7 +379,7 @@ static struct attribute *kovaplus_attrs[] = {
NULL,
};
-static struct bin_attribute *kovaplus_bin_attributes[] = {
+static const struct bin_attribute *const kovaplus_bin_attributes[] = {
&bin_attr_control,
&bin_attr_info,
&bin_attr_profile_settings,
@@ -399,7 +399,7 @@ static struct bin_attribute *kovaplus_bin_attributes[] = {
static const struct attribute_group kovaplus_group = {
.attrs = kovaplus_attrs,
- .bin_attrs = kovaplus_bin_attributes,
+ .bin_attrs_new = kovaplus_bin_attributes,
};
static const struct attribute_group *kovaplus_groups[] = {
diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c
index d5ddf0d68346..023ec64b4b0e 100644
--- a/drivers/hid/hid-roccat-lua.c
+++ b/drivers/hid/hid-roccat-lua.c
@@ -66,7 +66,7 @@ static ssize_t lua_sysfs_write(struct file *fp, struct kobject *kobj,
#define LUA_SYSFS_W(thingy, THINGY) \
static ssize_t lua_sysfs_write_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, \
+ struct kobject *kobj, const struct bin_attribute *attr, \
char *buf, loff_t off, size_t count) \
{ \
return lua_sysfs_write(fp, kobj, buf, off, count, \
@@ -75,7 +75,7 @@ static ssize_t lua_sysfs_write_ ## thingy(struct file *fp, \
#define LUA_SYSFS_R(thingy, THINGY) \
static ssize_t lua_sysfs_read_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, \
+ struct kobject *kobj, const struct bin_attribute *attr, \
char *buf, loff_t off, size_t count) \
{ \
return lua_sysfs_read(fp, kobj, buf, off, count, \
@@ -85,11 +85,11 @@ static ssize_t lua_sysfs_read_ ## thingy(struct file *fp, \
#define LUA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
LUA_SYSFS_W(thingy, THINGY) \
LUA_SYSFS_R(thingy, THINGY) \
-static struct bin_attribute lua_ ## thingy ## _attr = { \
+static const struct bin_attribute lua_ ## thingy ## _attr = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = LUA_SIZE_ ## THINGY, \
- .read = lua_sysfs_read_ ## thingy, \
- .write = lua_sysfs_write_ ## thingy \
+ .read_new = lua_sysfs_read_ ## thingy, \
+ .write_new = lua_sysfs_write_ ## thingy \
};
LUA_BIN_ATTRIBUTE_RW(control, CONTROL)
diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c
index eeb3d38cd805..2b53fbfbb897 100644
--- a/drivers/hid/hid-roccat-pyra.c
+++ b/drivers/hid/hid-roccat-pyra.c
@@ -129,8 +129,8 @@ static ssize_t pyra_sysfs_write(struct file *fp, struct kobject *kobj,
#define PYRA_SYSFS_W(thingy, THINGY) \
static ssize_t pyra_sysfs_write_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return pyra_sysfs_write(fp, kobj, buf, off, count, \
PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
@@ -138,8 +138,8 @@ static ssize_t pyra_sysfs_write_ ## thingy(struct file *fp, \
#define PYRA_SYSFS_R(thingy, THINGY) \
static ssize_t pyra_sysfs_read_ ## thingy(struct file *fp, \
- struct kobject *kobj, struct bin_attribute *attr, char *buf, \
- loff_t off, size_t count) \
+ struct kobject *kobj, const struct bin_attribute *attr, \
+ char *buf, loff_t off, size_t count) \
{ \
return pyra_sysfs_read(fp, kobj, buf, off, count, \
PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
@@ -151,27 +151,27 @@ PYRA_SYSFS_R(thingy, THINGY)
#define PYRA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
PYRA_SYSFS_RW(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0660 }, \
.size = PYRA_SIZE_ ## THINGY, \
- .read = pyra_sysfs_read_ ## thingy, \
- .write = pyra_sysfs_write_ ## thingy \
+ .read_new = pyra_sysfs_read_ ## thingy, \
+ .write_new = pyra_sysfs_write_ ## thingy \
}
#define PYRA_BIN_ATTRIBUTE_R(thingy, THINGY) \
PYRA_SYSFS_R(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0440 }, \
- .size = PYRA_SIZE_ ## THINGY, \
- .read = pyra_sysfs_read_ ## thingy, \
+ .size_new = PYRA_SIZE_ ## THINGY, \
+ .read_new = pyra_sysfs_read_ ## thingy, \
}
#define PYRA_BIN_ATTRIBUTE_W(thingy, THINGY) \
PYRA_SYSFS_W(thingy, THINGY); \
-static struct bin_attribute bin_attr_##thingy = { \
+static const struct bin_attribute bin_attr_##thingy = { \
.attr = { .name = #thingy, .mode = 0220 }, \
.size = PYRA_SIZE_ ## THINGY, \
- .write = pyra_sysfs_write_ ## thingy \
+ .write_new = pyra_sysfs_write_ ## thingy \
}
PYRA_BIN_ATTRIBUTE_W(control, CONTROL);
@@ -180,8 +180,8 @@ PYRA_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
PYRA_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -198,8 +198,8 @@ static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp,
}
static ssize_t pyra_sysfs_read_profilex_buttons(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
@@ -216,16 +216,16 @@ static ssize_t pyra_sysfs_read_profilex_buttons(struct file *fp,
}
#define PROFILE_ATTR(number) \
-static struct bin_attribute bin_attr_profile##number##_settings = { \
+static const struct bin_attribute bin_attr_profile##number##_settings = { \
.attr = { .name = "profile" #number "_settings", .mode = 0440 }, \
.size = PYRA_SIZE_PROFILE_SETTINGS, \
- .read = pyra_sysfs_read_profilex_settings, \
+ .read_new = pyra_sysfs_read_profilex_settings, \
.private = &profile_numbers[number-1], \
}; \
-static struct bin_attribute bin_attr_profile##number##_buttons = { \
+static const struct bin_attribute bin_attr_profile##number##_buttons = { \
.attr = { .name = "profile" #number "_buttons", .mode = 0440 }, \
.size = PYRA_SIZE_PROFILE_BUTTONS, \
- .read = pyra_sysfs_read_profilex_buttons, \
+ .read_new = pyra_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
@@ -235,8 +235,8 @@ PROFILE_ATTR(4);
PROFILE_ATTR(5);
static ssize_t pyra_sysfs_write_settings(struct file *fp,
- struct kobject *kobj, struct bin_attribute *attr, char *buf,
- loff_t off, size_t count)
+ struct kobject *kobj, const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj)->parent->parent;
struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev));
@@ -273,7 +273,7 @@ static ssize_t pyra_sysfs_write_settings(struct file *fp,
}
PYRA_SYSFS_R(settings, SETTINGS);
-static struct bin_attribute bin_attr_settings =
+static const struct bin_attribute bin_attr_settings =
__BIN_ATTR(settings, (S_IWUSR | S_IRUGO),
pyra_sysfs_read_settings, pyra_sysfs_write_settings,
PYRA_SIZE_SETTINGS);
@@ -334,7 +334,7 @@ static struct attribute *pyra_attrs[] = {
NULL,
};
-static struct bin_attribute *pyra_bin_attributes[] = {
+static const struct bin_attribute *const pyra_bin_attributes[] = {
&bin_attr_control,
&bin_attr_info,
&bin_attr_profile_settings,
@@ -355,7 +355,7 @@ static struct bin_attribute *pyra_bin_attributes[] = {
static const struct attribute_group pyra_group = {
.attrs = pyra_attrs,
- .bin_attrs = pyra_bin_attributes,
+ .bin_attrs_new = pyra_bin_attributes,
};
static const struct attribute_group *pyra_groups[] = {
diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c
index 57714a4525e2..902dac1e714e 100644
--- a/drivers/hid/hid-roccat-ryos.c
+++ b/drivers/hid/hid-roccat-ryos.c
@@ -47,7 +47,7 @@ ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(stored_lights, 0x17, 0x0566);
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(custom_lights, 0x18, 0x14);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(light_macro, 0x19, 0x07d2);
-static struct bin_attribute *ryos_bin_attrs[] = {
+static const struct bin_attribute *const ryos_bin_attrs[] = {
&bin_attr_control,
&bin_attr_profile,
&bin_attr_keys_primary,
@@ -70,7 +70,7 @@ static struct bin_attribute *ryos_bin_attrs[] = {
};
static const struct attribute_group ryos_group = {
- .bin_attrs = ryos_bin_attrs,
+ .bin_attrs_new = ryos_bin_attrs,
};
static const struct attribute_group *ryos_groups[] = {
diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c
index 2baa47a0efc5..7399b8ffb5c7 100644
--- a/drivers/hid/hid-roccat-savu.c
+++ b/drivers/hid/hid-roccat-savu.c
@@ -30,7 +30,7 @@ ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x8, 0x0823);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x9, 0x08);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0xc, 0x04);
-static struct bin_attribute *savu_bin_attrs[] = {
+static const struct bin_attribute *const savu_bin_attrs[] = {
&bin_attr_control,
&bin_attr_profile,
&bin_attr_general,
@@ -42,7 +42,7 @@ static struct bin_attribute *savu_bin_attrs[] = {
};
static const struct attribute_group savu_group = {
- .bin_attrs = savu_bin_attrs,
+ .bin_attrs_new = savu_bin_attrs,
};
static const struct attribute_group *savu_groups[] = {
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 5258b45684e8..a2be652b7bbd 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -2164,7 +2164,7 @@ static void sony_remove(struct hid_device *hdev)
struct sony_sc *sc = hid_get_drvdata(hdev);
if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) {
- del_timer_sync(&sc->ghl_poke_timer);
+ timer_delete_sync(&sc->ghl_poke_timer);
usb_free_urb(sc->ghl_urb);
}
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 03e57d8acdad..949d307c66a8 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -559,15 +559,13 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
if (steam->gamepad_mode)
enable = false;
+ mutex_lock(&steam->report_mutex);
if (enable) {
- mutex_lock(&steam->report_mutex);
/* enable esc, enter, cursors */
steam_send_report_byte(steam, ID_SET_DEFAULT_DIGITAL_MAPPINGS);
/* reset settings */
steam_send_report_byte(steam, ID_LOAD_DEFAULT_SETTINGS);
- mutex_unlock(&steam->report_mutex);
} else {
- mutex_lock(&steam->report_mutex);
/* disable esc, enter, cursor */
steam_send_report_byte(steam, ID_CLEAR_DIGITAL_MAPPINGS);
@@ -579,15 +577,14 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */
SETTING_STEAM_WATCHDOG_ENABLE, 0, /* disable watchdog that tests if Steam is active */
0);
- mutex_unlock(&steam->report_mutex);
} else {
steam_write_settings(steam,
SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
0);
- mutex_unlock(&steam->report_mutex);
}
}
+ mutex_unlock(&steam->report_mutex);
}
static int steam_input_open(struct input_dev *dev)
@@ -1051,10 +1048,10 @@ static void steam_mode_switch_cb(struct work_struct *work)
struct steam_device, mode_switch);
unsigned long flags;
bool client_opened;
- steam->gamepad_mode = !steam->gamepad_mode;
if (!lizard_mode)
return;
+ steam->gamepad_mode = !steam->gamepad_mode;
if (steam->gamepad_mode)
steam_set_lizard_mode(steam, false);
else {
@@ -1153,11 +1150,9 @@ static void steam_client_ll_close(struct hid_device *hdev)
struct steam_device *steam = hdev->driver_data;
unsigned long flags;
- bool connected;
spin_lock_irqsave(&steam->lock, flags);
steam->client_opened--;
- connected = steam->connected && !steam->client_opened;
spin_unlock_irqrestore(&steam->lock, flags);
schedule_work(&steam->unregister_work);
@@ -1623,7 +1618,7 @@ static void steam_do_deck_input_event(struct steam_device *steam,
schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
}
- if (!steam->gamepad_mode)
+ if (!steam->gamepad_mode && lizard_mode)
return;
lpad_touched = b10 & BIT(3);
@@ -1693,7 +1688,7 @@ static void steam_do_deck_sensors_event(struct steam_device *steam,
*/
steam->sensor_timestamp_us += 4000;
- if (!steam->gamepad_mode)
+ if (!steam->gamepad_mode && lizard_mode)
return;
input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us);
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f9ff5be94309..d4bd7848b8c6 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -19,6 +19,7 @@
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
+#define STEELSERIES_ARCTIS_9 BIT(2)
struct steelseries_device {
struct hid_device *hdev;
@@ -32,6 +33,7 @@ struct steelseries_device {
struct power_supply *battery;
uint8_t battery_capacity;
bool headset_connected;
+ bool battery_charging;
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -368,32 +370,35 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
kfree(drv_data);
- return;
}
#endif
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
+#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
static const char arctis_1_battery_request[] = { 0x06, 0x12 };
+static const char arctis_9_battery_request[] = { 0x00, 0x20 };
-static int steelseries_headset_arctis_1_fetch_battery(struct hid_device *hdev)
+static int steelseries_headset_request_battery(struct hid_device *hdev,
+ const char *request, size_t len)
{
u8 *write_buf;
int ret;
/* Request battery information */
- write_buf = kmemdup(arctis_1_battery_request, sizeof(arctis_1_battery_request), GFP_KERNEL);
+ write_buf = kmemdup(request, len, GFP_KERNEL);
if (!write_buf)
return -ENOMEM;
- ret = hid_hw_raw_request(hdev, arctis_1_battery_request[0],
- write_buf, sizeof(arctis_1_battery_request),
+ hid_dbg(hdev, "Sending battery request report");
+ ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
- if (ret < (int)sizeof(arctis_1_battery_request)) {
+ if (ret < (int)len) {
hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
ret = -ENODATA;
}
+
kfree(write_buf);
return ret;
}
@@ -404,7 +409,11 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
int ret = 0;
if (sd->quirks & STEELSERIES_ARCTIS_1)
- ret = steelseries_headset_arctis_1_fetch_battery(hdev);
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_1_battery_request, sizeof(arctis_1_battery_request));
+ else if (sd->quirks & STEELSERIES_ARCTIS_9)
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_9_battery_request, sizeof(arctis_9_battery_request));
if (ret < 0)
hid_dbg(hdev,
@@ -429,6 +438,9 @@ static void steelseries_headset_battery_timer_tick(struct work_struct *work)
steelseries_headset_fetch_battery(hdev);
}
+#define STEELSERIES_PREFIX "SteelSeries "
+#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
+
static int steelseries_headset_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -437,13 +449,24 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
int ret = 0;
switch (psp) {
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = sd->hdev->name;
+ while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
+ val->strval += STEELSERIES_PREFIX_LEN;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "SteelSeries";
+ break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_STATUS:
- val->intval = sd->headset_connected ?
- POWER_SUPPLY_STATUS_DISCHARGING :
- POWER_SUPPLY_STATUS_UNKNOWN;
+ if (sd->headset_connected) {
+ val->intval = sd->battery_charging ?
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ } else
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
@@ -477,6 +500,8 @@ steelseries_headset_set_wireless_status(struct hid_device *hdev,
}
static enum power_supply_property steelseries_headset_battery_props[] = {
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_SCOPE,
@@ -505,6 +530,7 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
/* avoid the warning of 0% battery while waiting for the first info */
steelseries_headset_set_wireless_status(sd->hdev, false);
sd->battery_capacity = 100;
+ sd->battery_charging = false;
sd->battery = devm_power_supply_register(&sd->hdev->dev,
&sd->battery_desc, &battery_cfg);
@@ -520,9 +546,22 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
steelseries_headset_fetch_battery(sd->hdev);
+ if (sd->quirks & STEELSERIES_ARCTIS_9) {
+ /* The first fetch_battery request can remain unanswered in some cases */
+ schedule_delayed_work(&sd->battery_work,
+ msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ }
+
return 0;
}
+static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
+{
+ return hdev->rdesc[0] == 0x06 &&
+ hdev->rdesc[1] == usage_page &&
+ hdev->rdesc[2] == 0xff;
+}
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -548,12 +587,20 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
+ if (sd->quirks & STEELSERIES_ARCTIS_9 &&
+ !steelseries_is_vendor_usage_page(hdev, 0xc0))
+ return -ENODEV;
+
spin_lock_init(&sd->lock);
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
return ret;
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return ret;
+
if (steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -580,6 +627,7 @@ static void steelseries_remove(struct hid_device *hdev)
cancel_delayed_work_sync(&sd->battery_work);
+ hid_hw_close(hdev);
hid_hw_stop(hdev);
}
@@ -599,6 +647,15 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
return rdesc;
}
+static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
+{
+ if (capacity >= max_in)
+ return 100;
+ if (capacity <= min_in)
+ return 0;
+ return (capacity - min_in) * 100 / (max_in - min_in);
+}
+
static int steelseries_headset_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *read_buf,
int size)
@@ -606,6 +663,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
struct steelseries_device *sd = hid_get_drvdata(hdev);
int capacity = sd->battery_capacity;
bool connected = sd->headset_connected;
+ bool charging = sd->battery_charging;
unsigned long flags;
/* Not a headset */
@@ -630,6 +688,34 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
}
}
+ if (sd->quirks & STEELSERIES_ARCTIS_9) {
+ hid_dbg(sd->hdev,
+ "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
+ if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
+ if (!delayed_work_pending(&sd->battery_work))
+ goto request_battery;
+ return 0;
+ }
+
+ if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
+ connected = true;
+ charging = read_buf[4] == 0x01;
+
+ /*
+ * Found no official documentation about min and max.
+ * Values defined by testing.
+ */
+ capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
+ } else {
+ /*
+ * Device is off and sends the last known status read_buf[1] == 0x03 or
+ * there is no known status of the device read_buf[0] == 0x55
+ */
+ connected = false;
+ charging = false;
+ }
+ }
+
if (connected != sd->headset_connected) {
hid_dbg(sd->hdev,
"Connected status changed from %sconnected to %sconnected\n",
@@ -647,6 +733,15 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
power_supply_changed(sd->battery);
}
+ if (charging != sd->battery_charging) {
+ hid_dbg(sd->hdev,
+ "Battery charging status changed from %scharging to %scharging\n",
+ sd->battery_charging ? "" : "not ",
+ charging ? "" : "not ");
+ sd->battery_charging = charging;
+ power_supply_changed(sd->battery);
+ }
+
request_battery:
spin_lock_irqsave(&sd->lock, flags);
if (!sd->removed)
@@ -665,6 +760,10 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6),
.driver_data = STEELSERIES_ARCTIS_1 },
+ { /* SteelSeries Arctis 9 Wireless for XBox */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12c2),
+ .driver_data = STEELSERIES_ARCTIS_9 },
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
@@ -683,3 +782,4 @@ MODULE_DESCRIPTION("HID driver for Steelseries devices");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
+MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c
index 3b81468a1df2..0bf70664c35e 100644
--- a/drivers/hid/hid-thrustmaster.c
+++ b/drivers/hid/hid-thrustmaster.c
@@ -174,6 +174,7 @@ static void thrustmaster_interrupts(struct hid_device *hdev)
u8 ep_addr[2] = {b_ep, 0};
if (!usb_check_int_endpoints(usbif, ep_addr)) {
+ kfree(send_buf);
hid_err(hdev, "Unexpected non-int endpoint\n");
return;
}
diff --git a/drivers/hid/hid-topre.c b/drivers/hid/hid-topre.c
index 848361f6225d..ccedf8721722 100644
--- a/drivers/hid/hid-topre.c
+++ b/drivers/hid/hid-topre.c
@@ -29,6 +29,11 @@ static const __u8 *topre_report_fixup(struct hid_device *hdev, __u8 *rdesc,
hid_info(hdev,
"fixing up Topre REALFORCE keyboard report descriptor\n");
rdesc[72] = 0x02;
+ } else if (*rsize >= 106 && rdesc[28] == 0x29 && rdesc[29] == 0xe7 &&
+ rdesc[30] == 0x81 && rdesc[31] == 0x00) {
+ hid_info(hdev,
+ "fixing up Topre REALFORCE keyboard report descriptor\n");
+ rdesc[31] = 0x02;
}
return rdesc;
}
@@ -38,6 +43,8 @@ static const struct hid_device_id topre_id_table[] = {
USB_DEVICE_ID_TOPRE_REALFORCE_R2_108) },
{ HID_USB_DEVICE(USB_VENDOR_ID_TOPRE,
USB_DEVICE_ID_TOPRE_REALFORCE_R2_87) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_TOPRE,
+ USB_DEVICE_ID_TOPRE_REALFORCE_R3S_87) },
{ }
};
MODULE_DEVICE_TABLE(hid, topre_id_table);
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index d8008933c052..61a4019ddc74 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -142,11 +142,12 @@ static int uclogic_input_configured(struct hid_device *hdev,
suffix = "System Control";
break;
}
- }
-
- if (suffix)
+ } else {
hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
"%s %s", hdev->name, suffix);
+ if (!hi->input->name)
+ return -ENOMEM;
+ }
return 0;
}
@@ -474,7 +475,7 @@ static void uclogic_remove(struct hid_device *hdev)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- del_timer_sync(&drvdata->inrange_timer);
+ timer_delete_sync(&drvdata->inrange_timer);
hid_hw_stop(hdev);
kfree(drvdata->desc_ptr);
uclogic_params_cleanup(&drvdata->params);
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index ef26c7defcf6..a6044996abf2 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -842,7 +842,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
__u8 *params_ptr = NULL;
size_t params_len = 0;
/* Parameters string descriptor of a model with touch ring (HS610) */
- const __u8 touch_ring_model_params_buf[] = {
+ static const __u8 touch_ring_model_params_buf[] = {
0x13, 0x03, 0x70, 0xC6, 0x00, 0x06, 0x7C, 0x00,
0xFF, 0x1F, 0xD8, 0x13, 0x03, 0x0D, 0x10, 0x01,
0x04, 0x3C, 0x3E
diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c
new file mode 100644
index 000000000000..001a0f5efb9d
--- /dev/null
+++ b/drivers/hid/hid-universal-pidff.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID UNIVERSAL PIDFF
+ * hid-pidff wrapper for PID-enabled devices
+ * Handles device reports, quirks and extends usable button range
+ *
+ * Copyright (c) 2024, 2025 Oleg Makarenko
+ * Copyright (c) 2024, 2025 Tomasz Pakuła
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/input-event-codes.h>
+#include "hid-ids.h"
+#include "usbhid/hid-pidff.h"
+
+#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)
+
+/*
+ * Map buttons manually to extend the default joystick button limit
+ */
+static int universal_pidff_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+ return 0;
+
+ if (field->application != HID_GD_JOYSTICK)
+ return 0;
+
+ int button = ((usage->hid - 1) & HID_USAGE);
+ int code = button + BTN_JOYSTICK;
+
+ /* Detect the end of JOYSTICK buttons range */
+ if (code > BTN_DEAD)
+ code = button + KEY_NEXT_FAVORITE - JOY_RANGE;
+
+ /*
+ * Map overflowing buttons to KEY_RESERVED to not ignore
+ * them and let them still trigger MSC_SCAN
+ */
+ if (code > KEY_MAX)
+ code = KEY_RESERVED;
+
+ hid_map_usage(hi, usage, bit, max, EV_KEY, code);
+ hid_dbg(hdev, "Button %d: usage %d", button, code);
+ return 1;
+}
+
+/*
+ * Check if the device is PID and initialize it
+ * Add quirks after initialisation
+ */
+static int universal_pidff_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int i, error;
+ error = hid_parse(hdev);
+ if (error) {
+ hid_err(hdev, "HID parse failed\n");
+ goto err;
+ }
+
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (error) {
+ hid_err(hdev, "HID hw start failed\n");
+ goto err;
+ }
+
+ /* Check if device contains PID usage page */
+ error = 1;
+ for (i = 0; i < hdev->collection_size; i++)
+ if ((hdev->collection[i].usage & HID_USAGE_PAGE) == HID_UP_PID) {
+ error = 0;
+ hid_dbg(hdev, "PID usage page found\n");
+ break;
+ }
+
+ /*
+ * Do not fail as this might be the second "device"
+ * just for additional buttons/axes. Exit cleanly if force
+ * feedback usage page wasn't found (included devices were
+ * tested and confirmed to be USB PID after all).
+ */
+ if (error) {
+ hid_dbg(hdev, "PID usage page not found in the descriptor\n");
+ return 0;
+ }
+
+ /* Check if HID_PID support is enabled */
+ int (*init_function)(struct hid_device *, u32);
+ init_function = hid_pidff_init_with_quirks;
+
+ if (!init_function) {
+ hid_warn(hdev, "HID_PID support not enabled!\n");
+ return 0;
+ }
+
+ error = init_function(hdev, id->driver_data);
+ if (error) {
+ hid_warn(hdev, "Error initialising force feedback\n");
+ goto err;
+ }
+
+ hid_info(hdev, "Universal pidff driver loaded successfully!");
+
+ return 0;
+err:
+ return error;
+}
+
+static int universal_pidff_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ int axis;
+ struct input_dev *input = hidinput->input;
+
+ if (!input->absinfo)
+ return 0;
+
+ /* Decrease fuzz and deadzone on available axes */
+ for (axis = ABS_X; axis <= ABS_BRAKE; axis++) {
+ if (!test_bit(axis, input->absbit))
+ continue;
+
+ input_set_abs_params(input, axis,
+ input->absinfo[axis].minimum,
+ input->absinfo[axis].maximum,
+ axis == ABS_X ? 0 : 8, 0);
+ }
+
+ /* Remove fuzz and deadzone from the second joystick axis */
+ if (hdev->vendor == USB_VENDOR_ID_FFBEAST &&
+ hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK)
+ input_set_abs_params(input, ABS_Y,
+ input->absinfo[ABS_Y].minimum,
+ input->absinfo[ABS_Y].maximum, 0, 0);
+
+ return 0;
+}
+
+static const struct hid_device_id universal_pidff_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
+ .driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), },
+ { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), },
+ { HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10),
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12),
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE),
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2),
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_LITE_STAR_GT987_FF),
+ .driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, universal_pidff_devices);
+
+static struct hid_driver universal_pidff = {
+ .name = "hid-universal-pidff",
+ .id_table = universal_pidff_devices,
+ .input_mapping = universal_pidff_input_mapping,
+ .probe = universal_pidff_probe,
+ .input_configured = universal_pidff_input_configured
+};
+module_hid_driver(universal_pidff);
+
+MODULE_DESCRIPTION("Universal driver for USB PID Force Feedback devices");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Oleg Makarenko <oleg@makarenk.ooo>");
+MODULE_AUTHOR("Tomasz Pakuła <tomasz.pakula.oficjalny@gmail.com>");
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
index 26167cfb696f..8080083121d3 100644
--- a/drivers/hid/hid-wiimote-core.c
+++ b/drivers/hid/hid-wiimote-core.c
@@ -1171,7 +1171,7 @@ static void wiimote_init_hotplug(struct wiimote_data *wdata)
wiimote_cmd_release(wdata);
/* delete MP hotplug timer */
- del_timer_sync(&wdata->timer);
+ timer_delete_sync(&wdata->timer);
} else {
/* reschedule MP hotplug timer */
if (!(flags & WIIPROTO_FLAG_BUILTIN_MP) &&
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index ef7c595c9403..e8d51f410cc1 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -2,7 +2,7 @@
menuconfig I2C_HID
tristate "I2C HID support"
default y
- depends on I2C && INPUT && HID
+ depends on I2C
if I2C_HID
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 4e87380d3edd..d3912e3f2f13 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -51,6 +51,7 @@
#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(4)
#define I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND BIT(5)
#define I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME BIT(6)
+#define I2C_HID_QUIRK_RE_POWER_ON BIT(7)
/* Command opcodes */
#define I2C_HID_OPCODE_RESET 0x01
@@ -136,6 +137,11 @@ static const struct i2c_hid_quirks {
{ I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_1063,
I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND },
/*
+ * Without additional power on command, at least some QTEC devices send garbage
+ */
+ { I2C_VENDOR_ID_QTEC, HID_ANY_ID,
+ I2C_HID_QUIRK_RE_POWER_ON },
+ /*
* Sending the wakeup after reset actually break ELAN touchscreen controller
*/
{ USB_VENDOR_ID_ELAN, HID_ANY_ID,
@@ -284,7 +290,7 @@ static int i2c_hid_get_report(struct i2c_hid *ihid,
ihid->rawbuf, recv_len + sizeof(__le16));
if (error) {
dev_err(&ihid->client->dev,
- "failed to set a report to device: %d\n", error);
+ "failed to get a report from device: %d\n", error);
return error;
}
@@ -1073,7 +1079,11 @@ static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
return ret;
}
- return 0;
+ /* At least some QTEC devices need this after initialization */
+ if (ihid->quirks & I2C_HID_QUIRK_RE_POWER_ON)
+ ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+
+ return ret;
}
static int i2c_hid_core_probe_panel_follower(struct i2c_hid *ihid)
diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-ish-hid/Kconfig
index 253dc10d35ef..568c8688784e 100644
--- a/drivers/hid/intel-ish-hid/Kconfig
+++ b/drivers/hid/intel-ish-hid/Kconfig
@@ -6,7 +6,6 @@ config INTEL_ISH_HID
tristate "Intel Integrated Sensor Hub"
default n
depends on X86
- depends on HID
help
The Integrated Sensor Hub (ISH) enables the ability to offload
sensor polling and algorithm processing to a dedicated low power
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
index cdd80c653918..07e90d51f073 100644
--- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
@@ -36,6 +36,8 @@
#define PCI_DEVICE_ID_INTEL_ISH_ARL_H 0x7745
#define PCI_DEVICE_ID_INTEL_ISH_ARL_S 0x7F78
#define PCI_DEVICE_ID_INTEL_ISH_LNL_M 0xA845
+#define PCI_DEVICE_ID_INTEL_ISH_PTL_H 0xE345
+#define PCI_DEVICE_ID_INTEL_ISH_PTL_P 0xE445
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
index 3cd53fc80634..4c861119e97a 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -517,6 +517,10 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/* ISH FW is dead */
if (!ish_is_input_ready(dev))
return -EPIPE;
+
+ /* Send clock sync at once after reset */
+ ishtp_dev->prev_sync = 0;
+
/*
* Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
* RESET_NOTIFY_ACK - FW will be checking for it
@@ -577,15 +581,14 @@ static void fw_reset_work_fn(struct work_struct *work)
*/
static void _ish_sync_fw_clock(struct ishtp_device *dev)
{
- static unsigned long prev_sync;
- uint64_t usec;
+ struct ipc_time_update_msg time = {};
- if (prev_sync && time_before(jiffies, prev_sync + 20 * HZ))
+ if (dev->prev_sync && time_before(jiffies, dev->prev_sync + 20 * HZ))
return;
- prev_sync = jiffies;
- usec = ktime_to_us(ktime_get_boottime());
- ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t));
+ dev->prev_sync = jiffies;
+ /* The fields of time would be updated while sending message */
+ ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &time, sizeof(time));
}
/**
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index 9e2401291a2f..ff0fc8010072 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -26,9 +26,11 @@
enum ishtp_driver_data_index {
ISHTP_DRIVER_DATA_NONE,
ISHTP_DRIVER_DATA_LNL_M,
+ ISHTP_DRIVER_DATA_PTL,
};
#define ISH_FW_GEN_LNL_M "lnlm"
+#define ISH_FW_GEN_PTL "ptl"
#define ISH_FIRMWARE_PATH(gen) "intel/ish/ish_" gen ".bin"
#define ISH_FIRMWARE_PATH_ALL "intel/ish/ish_*.bin"
@@ -37,6 +39,9 @@ static struct ishtp_driver_data ishtp_driver_data[] = {
[ISHTP_DRIVER_DATA_LNL_M] = {
.fw_generation = ISH_FW_GEN_LNL_M,
},
+ [ISHTP_DRIVER_DATA_PTL] = {
+ .fw_generation = ISH_FW_GEN_PTL,
+ },
};
static const struct pci_device_id ish_pci_tbl[] = {
@@ -63,6 +68,8 @@ static const struct pci_device_id ish_pci_tbl[] = {
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_LNL_M), .driver_data = ISHTP_DRIVER_DATA_LNL_M},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_PTL_H), .driver_data = ISHTP_DRIVER_DATA_PTL},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_PTL_P), .driver_data = ISHTP_DRIVER_DATA_PTL},
{}
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
index 513d7a4a1b8a..97f4026b1627 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
+++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
@@ -252,27 +252,6 @@ int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb)
EXPORT_SYMBOL(ishtp_cl_io_rb_recycle);
/**
- * ishtp_cl_tx_empty() -test whether client device tx buffer is empty
- * @cl: Pointer to client device instance
- *
- * Look client device tx buffer list, and check whether this list is empty
- *
- * Return: true if client tx buffer list is empty else false
- */
-bool ishtp_cl_tx_empty(struct ishtp_cl *cl)
-{
- int tx_list_empty;
- unsigned long tx_flags;
-
- spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags);
- tx_list_empty = list_empty(&cl->tx_list.list);
- spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
-
- return !!tx_list_empty;
-}
-EXPORT_SYMBOL(ishtp_cl_tx_empty);
-
-/**
* ishtp_cl_rx_get_rb() -Get a rb from client device rx buffer list
* @cl: Pointer to client device instance
*
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c
index e61b01e9902e..21a2c0773cc2 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.c
+++ b/drivers/hid/intel-ish-hid/ishtp/client.c
@@ -14,25 +14,6 @@
#include "hbm.h"
#include "client.h"
-int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl)
-{
- unsigned long tx_free_flags;
- int size;
-
- spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags);
- size = cl->tx_ring_free_size * cl->device->fw_client->props.max_msg_length;
- spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags);
-
- return size;
-}
-EXPORT_SYMBOL(ishtp_cl_get_tx_free_buffer_size);
-
-int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl)
-{
- return cl->tx_ring_free_size;
-}
-EXPORT_SYMBOL(ishtp_cl_get_tx_free_rings);
-
/**
* ishtp_read_list_flush() - Flush read queue
* @cl: ishtp client instance
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h
index d9d398fadcf7..0efd49dd2530 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.h
+++ b/drivers/hid/intel-ish-hid/ishtp/client.h
@@ -120,8 +120,6 @@ int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl);
int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl);
void ishtp_cl_free_rx_ring(struct ishtp_cl *cl);
void ishtp_cl_free_tx_ring(struct ishtp_cl *cl);
-int ishtp_cl_get_tx_free_buffer_size(struct ishtp_cl *cl);
-int ishtp_cl_get_tx_free_rings(struct ishtp_cl *cl);
/* DMA I/F functions */
void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg,
diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c
index 07fdd52e4c5e..26bf9045a8de 100644
--- a/drivers/hid/intel-ish-hid/ishtp/init.c
+++ b/drivers/hid/intel-ish-hid/ishtp/init.c
@@ -15,36 +15,6 @@
#include "loader.h"
/**
- * ishtp_dev_state_str() -Convert to string format
- * @state: state to convert
- *
- * Convert state to string for prints
- *
- * Return: character pointer to converted string
- */
-const char *ishtp_dev_state_str(int state)
-{
- switch (state) {
- case ISHTP_DEV_INITIALIZING:
- return "INITIALIZING";
- case ISHTP_DEV_INIT_CLIENTS:
- return "INIT_CLIENTS";
- case ISHTP_DEV_ENABLED:
- return "ENABLED";
- case ISHTP_DEV_RESETTING:
- return "RESETTING";
- case ISHTP_DEV_DISABLED:
- return "DISABLED";
- case ISHTP_DEV_POWER_DOWN:
- return "POWER_DOWN";
- case ISHTP_DEV_POWER_UP:
- return "POWER_UP";
- default:
- return "unknown";
- }
-}
-
-/**
* ishtp_device_init() - ishtp device init
* @dev: ISHTP device instance
*
diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
index effbb442c727..ec9f6e87aaf2 100644
--- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
+++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
@@ -57,7 +57,6 @@ enum ishtp_dev_state {
ISHTP_DEV_POWER_DOWN,
ISHTP_DEV_POWER_UP
};
-const char *ishtp_dev_state_str(int state);
struct ishtp_cl;
@@ -254,6 +253,8 @@ struct ishtp_device {
unsigned int ipc_tx_cnt;
unsigned long long ipc_tx_bytes_cnt;
+ /* Time of the last clock sync */
+ unsigned long prev_sync;
const struct ishtp_hw_ops *ops;
size_t mtu;
uint32_t ishtp_msg_hdr;
diff --git a/drivers/hid/intel-thc-hid/Kconfig b/drivers/hid/intel-thc-hid/Kconfig
new file mode 100644
index 000000000000..0351d1137607
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/Kconfig
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2024, Intel Corporation.
+
+menu "Intel THC HID Support"
+ depends on X86_64 && PCI
+
+config INTEL_THC_HID
+ tristate "Intel Touch Host Controller"
+ depends on ACPI
+ help
+ THC (Touch Host Controller) is the name of the IP block in PCH that
+ interfaces with Touch Devices (ex: touchscreen, touchpad etc.). It
+ is comprised of 3 key functional blocks: A natively half-duplex
+ Quad I/O capable SPI master; a low latency I2C interface to support
+ HIDI2C compliant devices; a hardware sequencer with Read/Write DMA
+ capability to system memory.
+
+ Say Y/M here if you want to support Intel THC. If unsure, say N.
+
+config INTEL_QUICKSPI
+ tristate "Intel QuickSPI driver based on Intel Touch Host Controller"
+ depends on INTEL_THC_HID
+ help
+ Intel QuickSPI, based on Touch Host Controller (THC), implements
+ HIDSPI (HID over SPI) protocol. It configures THC to work at SPI
+ mode, and controls THC hardware sequencer to accelerate HIDSPI
+ transaction flow.
+
+ Say Y/M here if you want to support Intel QuickSPI. If unsure, say N.
+
+config INTEL_QUICKI2C
+ tristate "Intel QuickI2C driver based on Intel Touch Host Controller"
+ depends on INTEL_THC_HID
+ help
+ Intel QuickI2C, uses Touch Host Controller (THC) hardware, implements
+ HIDI2C (HID over I2C) protocol. It configures THC to work in I2C
+ mode, and controls THC hardware sequencer to accelerate HIDI2C
+ transaction flow.
+
+ Say Y/M here if you want to support Intel QuickI2C. If unsure, say N.
+
+endmenu
diff --git a/drivers/hid/intel-thc-hid/Makefile b/drivers/hid/intel-thc-hid/Makefile
new file mode 100644
index 000000000000..6f762d87af07
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/Makefile
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile - Intel Touch Host Controller (THC) drivers
+# Copyright (c) 2024, Intel Corporation.
+#
+#
+
+obj-$(CONFIG_INTEL_THC_HID) += intel-thc.o
+intel-thc-objs += intel-thc/intel-thc-dev.o
+intel-thc-objs += intel-thc/intel-thc-dma.o
+
+obj-$(CONFIG_INTEL_QUICKSPI) += intel-quickspi.o
+intel-quickspi-objs += intel-quickspi/pci-quickspi.o
+intel-quickspi-objs += intel-quickspi/quickspi-hid.o
+intel-quickspi-objs += intel-quickspi/quickspi-protocol.o
+
+obj-$(CONFIG_INTEL_QUICKI2C) += intel-quicki2c.o
+intel-quicki2c-objs += intel-quicki2c/pci-quicki2c.o
+intel-quicki2c-objs += intel-quicki2c/quicki2c-hid.o
+intel-quicki2c-objs += intel-quicki2c/quicki2c-protocol.o
+
+ccflags-y += -I $(src)/intel-thc
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
new file mode 100644
index 000000000000..8a8c4a46f927
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
@@ -0,0 +1,960 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/pm_runtime.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-hw.h"
+
+#include "quicki2c-dev.h"
+#include "quicki2c-hid.h"
+#include "quicki2c-protocol.h"
+
+/* THC QuickI2C ACPI method to get device properties */
+/* HIDI2C device method */
+static guid_t i2c_hid_guid =
+ GUID_INIT(0x3cdff6f7, 0x4267, 0x4555, 0xad, 0x05, 0xb3, 0x0a, 0x3d, 0x89, 0x38, 0xde);
+
+/* platform method */
+static guid_t thc_platform_guid =
+ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30, 0xf7, 0x87, 0xa1, 0x38);
+
+/**
+ * quicki2c_acpi_get_dsm_property - Query device ACPI DSM parameter
+ *
+ * @adev: point to ACPI device
+ * @guid: ACPI method's guid
+ * @rev: ACPI method's revision
+ * @func: ACPI method's function number
+ * @type: ACPI parameter's data type
+ * @prop_buf: point to return buffer
+ *
+ * This is a helper function for device to query its ACPI DSM parameters.
+ *
+ * Return: 0 if success or ENODEV on failed.
+ */
+static int quicki2c_acpi_get_dsm_property(struct acpi_device *adev, const guid_t *guid,
+ u64 rev, u64 func, acpi_object_type type, void *prop_buf)
+{
+ acpi_handle handle = acpi_device_handle(adev);
+ union acpi_object *obj;
+
+ obj = acpi_evaluate_dsm_typed(handle, guid, rev, func, NULL, type);
+ if (!obj) {
+ acpi_handle_err(handle,
+ "Error _DSM call failed, rev: %d, func: %d, type: %d\n",
+ (int)rev, (int)func, (int)type);
+ return -ENODEV;
+ }
+
+ if (type == ACPI_TYPE_INTEGER)
+ *(u32 *)prop_buf = (u32)obj->integer.value;
+ else if (type == ACPI_TYPE_BUFFER)
+ memcpy(prop_buf, obj->buffer.pointer, obj->buffer.length);
+
+ ACPI_FREE(obj);
+
+ return 0;
+}
+
+/**
+ * quicki2c_acpi_get_dsd_property - Query device ACPI DSD parameter
+ *
+ * @adev: point to ACPI device
+ * @dsd_method_name: ACPI method's property name
+ * @type: ACPI parameter's data type
+ * @prop_buf: point to return buffer
+ *
+ * This is a helper function for device to query its ACPI DSD parameters.
+ *
+ * Return: 0 if success or ENODEV on failed.
+ */
+static int quicki2c_acpi_get_dsd_property(struct acpi_device *adev, acpi_string dsd_method_name,
+ acpi_object_type type, void *prop_buf)
+{
+ acpi_handle handle = acpi_device_handle(adev);
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *ret_obj;
+ acpi_status status;
+
+ status = acpi_evaluate_object(handle, dsd_method_name, NULL, &buffer);
+ if (ACPI_FAILURE(status)) {
+ acpi_handle_err(handle,
+ "Can't evaluate %s method: %d\n", dsd_method_name, status);
+ return -ENODEV;
+ }
+
+ ret_obj = buffer.pointer;
+
+ memcpy(prop_buf, ret_obj->buffer.pointer, ret_obj->buffer.length);
+
+ return 0;
+}
+
+/**
+ * quicki2c_get_acpi_resources - Query all quicki2c devices' ACPI parameters
+ *
+ * @qcdev: point to quicki2c device
+ *
+ * This function gets all quicki2c devices' ACPI resource.
+ *
+ * Return: 0 if success or error code on failed.
+ */
+static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(qcdev->dev);
+ struct quicki2c_subip_acpi_parameter i2c_param;
+ struct quicki2c_subip_acpi_config i2c_config;
+ u32 hid_desc_addr;
+ int ret = -EINVAL;
+
+ if (!adev) {
+ dev_err(qcdev->dev, "Invalid acpi device pointer\n");
+ return ret;
+ }
+
+ qcdev->acpi_dev = adev;
+
+ ret = quicki2c_acpi_get_dsm_property(adev, &i2c_hid_guid,
+ QUICKI2C_ACPI_REVISION_NUM,
+ QUICKI2C_ACPI_FUNC_NUM_HID_DESC_ADDR,
+ ACPI_TYPE_INTEGER,
+ &hid_desc_addr);
+ if (ret)
+ return ret;
+
+ qcdev->hid_desc_addr = (u16)hid_desc_addr;
+
+ ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid,
+ QUICKI2C_ACPI_REVISION_NUM,
+ QUICKI2C_ACPI_FUNC_NUM_ACTIVE_LTR_VAL,
+ ACPI_TYPE_INTEGER,
+ &qcdev->active_ltr_val);
+ if (ret)
+ return ret;
+
+ ret = quicki2c_acpi_get_dsm_property(adev, &thc_platform_guid,
+ QUICKI2C_ACPI_REVISION_NUM,
+ QUICKI2C_ACPI_FUNC_NUM_LP_LTR_VAL,
+ ACPI_TYPE_INTEGER,
+ &qcdev->low_power_ltr_val);
+ if (ret)
+ return ret;
+
+ ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ICRS,
+ ACPI_TYPE_BUFFER, &i2c_param);
+ if (ret)
+ return ret;
+
+ if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT)
+ return -EOPNOTSUPP;
+
+ qcdev->i2c_slave_addr = i2c_param.device_address;
+
+ ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB,
+ ACPI_TYPE_BUFFER, &i2c_config);
+ if (ret)
+ return ret;
+
+ if (i2c_param.connection_speed > 0 &&
+ i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) {
+ qcdev->i2c_speed_mode = THC_I2C_STANDARD;
+ qcdev->i2c_clock_hcnt = i2c_config.SMHX;
+ qcdev->i2c_clock_lcnt = i2c_config.SMLX;
+ } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED &&
+ i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) {
+ qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
+ qcdev->i2c_clock_hcnt = i2c_config.FMHX;
+ qcdev->i2c_clock_lcnt = i2c_config.FMLX;
+ } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED &&
+ i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) {
+ qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
+ qcdev->i2c_clock_hcnt = i2c_config.FPHX;
+ qcdev->i2c_clock_lcnt = i2c_config.FPLX;
+ } else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED &&
+ i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) {
+ qcdev->i2c_speed_mode = THC_I2C_HIGH_SPEED;
+ qcdev->i2c_clock_hcnt = i2c_config.HMHX;
+ qcdev->i2c_clock_lcnt = i2c_config.HMLX;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+/**
+ * quicki2c_irq_quick_handler - The ISR of the quicki2c driver
+ *
+ * @irq: The irq number
+ * @dev_id: pointer to the device structure
+ *
+ * Return: IRQ_WAKE_THREAD if further process needed.
+ */
+static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id)
+{
+ struct quicki2c_device *qcdev = dev_id;
+
+ if (qcdev->state == QUICKI2C_DISABLED)
+ return IRQ_HANDLED;
+
+ /* Disable THC interrupt before current interrupt be handled */
+ thc_interrupt_enable(qcdev->thc_hw, false);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * try_recover - Try to recovery THC and Device
+ * @qcdev: pointer to quicki2c device
+ *
+ * This function is a error handler, called when fatal error happens.
+ * It try to reset Touch Device and re-configure THC to recovery
+ * transferring between Device and THC.
+ *
+ * Return: 0 if successful or error code on failed
+ */
+static int try_recover(struct quicki2c_device *qcdev)
+{
+ int ret;
+
+ thc_dma_unconfigure(qcdev->thc_hw);
+
+ ret = thc_dma_configure(qcdev->thc_hw);
+ if (ret) {
+ dev_err(qcdev->dev, "Reconfig DMA failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int handle_input_report(struct quicki2c_device *qcdev)
+{
+ struct hidi2c_report_packet *pkt = (struct hidi2c_report_packet *)qcdev->input_buf;
+ int rx_dma_finished = 0;
+ size_t report_len;
+ int ret;
+
+ while (!rx_dma_finished) {
+ ret = thc_rxdma_read(qcdev->thc_hw, THC_RXDMA2,
+ (u8 *)pkt, &report_len,
+ &rx_dma_finished);
+ if (ret)
+ return ret;
+
+ if (!pkt->len) {
+ if (qcdev->state == QUICKI2C_RESETING) {
+ qcdev->reset_ack = true;
+ wake_up(&qcdev->reset_ack_wq);
+
+ qcdev->state = QUICKI2C_RESETED;
+ } else {
+ dev_warn(qcdev->dev, "unexpected DIR happen\n");
+ }
+
+ continue;
+ }
+
+ /* discard samples before driver probe complete */
+ if (qcdev->state != QUICKI2C_ENABLED)
+ continue;
+
+ quicki2c_hid_send_report(qcdev, pkt->data,
+ HIDI2C_DATA_LEN(le16_to_cpu(pkt->len)));
+ }
+
+ return 0;
+}
+
+/**
+ * quicki2c_irq_thread_handler - IRQ thread handler of quicki2c driver
+ *
+ * @irq: The IRQ number
+ * @dev_id: pointer to the quicki2c device structure
+ *
+ * Return: IRQ_HANDLED to finish this handler.
+ */
+static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id)
+{
+ struct quicki2c_device *qcdev = dev_id;
+ int err_recover = 0;
+ int int_mask;
+ int ret;
+
+ if (qcdev->state == QUICKI2C_DISABLED)
+ return IRQ_HANDLED;
+
+ ret = pm_runtime_resume_and_get(qcdev->dev);
+ if (ret)
+ return IRQ_HANDLED;
+
+ int_mask = thc_interrupt_handler(qcdev->thc_hw);
+
+ if (int_mask & BIT(THC_FATAL_ERR_INT) || int_mask & BIT(THC_TXN_ERR_INT) ||
+ int_mask & BIT(THC_UNKNOWN_INT)) {
+ err_recover = 1;
+ goto exit;
+ }
+
+ if (int_mask & BIT(THC_RXDMA2_INT)) {
+ err_recover = handle_input_report(qcdev);
+ if (err_recover)
+ goto exit;
+ }
+
+exit:
+ thc_interrupt_enable(qcdev->thc_hw, true);
+
+ if (err_recover)
+ if (try_recover(qcdev))
+ qcdev->state = QUICKI2C_DISABLED;
+
+ pm_runtime_mark_last_busy(qcdev->dev);
+ pm_runtime_put_autosuspend(qcdev->dev);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * quicki2c_dev_init - Initialize quicki2c device
+ *
+ * @pdev: pointer to the thc pci device
+ * @mem_addr: The pointer of MMIO memory address
+ *
+ * Alloc quicki2c device structure and initialized THC device,
+ * then configure THC to HIDI2C mode.
+ *
+ * If success, enable THC hardware interrupt.
+ *
+ * Return: pointer to the quicki2c device structure if success
+ * or NULL on failed.
+ */
+static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr)
+{
+ struct device *dev = &pdev->dev;
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = devm_kzalloc(dev, sizeof(struct quicki2c_device), GFP_KERNEL);
+ if (!qcdev)
+ return ERR_PTR(-ENOMEM);
+
+ qcdev->pdev = pdev;
+ qcdev->dev = dev;
+ qcdev->mem_addr = mem_addr;
+ qcdev->state = QUICKI2C_DISABLED;
+
+ init_waitqueue_head(&qcdev->reset_ack_wq);
+
+ /* thc hw init */
+ qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr);
+ if (IS_ERR(qcdev->thc_hw)) {
+ ret = PTR_ERR(qcdev->thc_hw);
+ dev_err_once(dev, "Failed to initialize THC device context, ret = %d.\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ ret = quicki2c_get_acpi_resources(qcdev);
+ if (ret) {
+ dev_err_once(dev, "Get ACPI resources failed, ret = %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
+ if (ret) {
+ dev_err_once(dev, "Failed to select THC port, ret = %d.\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
+ qcdev->i2c_speed_mode,
+ qcdev->i2c_clock_hcnt,
+ qcdev->i2c_clock_lcnt);
+ if (ret)
+ return ERR_PTR(ret);
+
+ thc_int_trigger_type_select(qcdev->thc_hw, false);
+
+ thc_interrupt_config(qcdev->thc_hw);
+
+ thc_interrupt_enable(qcdev->thc_hw, true);
+
+ qcdev->state = QUICKI2C_INITED;
+
+ return qcdev;
+}
+
+/**
+ * quicki2c_dev_deinit - De-initialize quicki2c device
+ *
+ * @qcdev: pointer to the quicki2c device structure
+ *
+ * Disable THC interrupt and deinitilize THC.
+ */
+static void quicki2c_dev_deinit(struct quicki2c_device *qcdev)
+{
+ thc_interrupt_enable(qcdev->thc_hw, false);
+ thc_ltr_unconfig(qcdev->thc_hw);
+
+ qcdev->state = QUICKI2C_DISABLED;
+}
+
+/**
+ * quicki2c_dma_init - Configure THC DMA for quicki2c device
+ * @qcdev: pointer to the quicki2c device structure
+ *
+ * This function uses TIC's parameters(such as max input length, max output
+ * length) to allocate THC DMA buffers and configure THC DMA engines.
+ *
+ * Return: 0 if success or error code on failed.
+ */
+static int quicki2c_dma_init(struct quicki2c_device *qcdev)
+{
+ size_t swdma_max_len;
+ int ret;
+
+ swdma_max_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len),
+ le16_to_cpu(qcdev->dev_desc.report_desc_len));
+
+ ret = thc_dma_set_max_packet_sizes(qcdev->thc_hw, 0,
+ le16_to_cpu(qcdev->dev_desc.max_input_len),
+ le16_to_cpu(qcdev->dev_desc.max_output_len),
+ swdma_max_len);
+ if (ret)
+ return ret;
+
+ ret = thc_dma_allocate(qcdev->thc_hw);
+ if (ret) {
+ dev_err(qcdev->dev, "Allocate THC DMA buffer failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Enable RxDMA */
+ ret = thc_dma_configure(qcdev->thc_hw);
+ if (ret) {
+ dev_err(qcdev->dev, "Configure THC DMA failed, ret = %d\n", ret);
+ thc_dma_unconfigure(qcdev->thc_hw);
+ thc_dma_release(qcdev->thc_hw);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * quicki2c_dma_deinit - Release THC DMA for quicki2c device
+ * @qcdev: pointer to the quicki2c device structure
+ *
+ * Stop THC DMA engines and release all DMA buffers.
+ *
+ */
+static void quicki2c_dma_deinit(struct quicki2c_device *qcdev)
+{
+ thc_dma_unconfigure(qcdev->thc_hw);
+ thc_dma_release(qcdev->thc_hw);
+}
+
+/**
+ * quicki2c_alloc_report_buf - Alloc report buffers
+ * @qcdev: pointer to the quicki2c device structure
+ *
+ * Allocate report descriptor buffer, it will be used for restore TIC HID
+ * report descriptor.
+ *
+ * Allocate input report buffer, it will be used for receive HID input report
+ * data from TIC.
+ *
+ * Allocate output report buffer, it will be used for store HID output report,
+ * such as set feature.
+ *
+ * Return: 0 if success or error code on failed.
+ */
+static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev)
+{
+ size_t max_report_len;
+
+ qcdev->report_descriptor = devm_kzalloc(qcdev->dev,
+ le16_to_cpu(qcdev->dev_desc.report_desc_len),
+ GFP_KERNEL);
+ if (!qcdev->report_descriptor)
+ return -ENOMEM;
+
+ /*
+ * Some HIDI2C devices don't declare input/output max length correctly,
+ * give default 4K buffer to avoid DMA buffer overrun.
+ */
+ max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), SZ_4K);
+
+ qcdev->input_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL);
+ if (!qcdev->input_buf)
+ return -ENOMEM;
+
+ if (!le16_to_cpu(qcdev->dev_desc.max_output_len))
+ qcdev->dev_desc.max_output_len = cpu_to_le16(SZ_4K);
+
+ max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_output_len),
+ max_report_len);
+
+ qcdev->report_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL);
+ if (!qcdev->report_buf)
+ return -ENOMEM;
+
+ qcdev->report_len = max_report_len;
+
+ return 0;
+}
+
+/*
+ * quicki2c_probe: Quicki2c driver probe function
+ *
+ * @pdev: point to pci device
+ * @id: point to pci_device_id structure
+ *
+ * This function initializes THC and HIDI2C device, the flow is:
+ * - do THC pci device initialization
+ * - query HIDI2C ACPI parameters
+ * - configure THC to HIDI2C mode
+ * - go through HIDI2C enumeration flow
+ * |- read device descriptor
+ * |- reset HIDI2C device
+ * - enable THC interrupt and DMA
+ * - read report descriptor
+ * - register HID device
+ * - enable runtime power management
+ *
+ * Return 0 if success or error code on failed.
+ */
+static int quicki2c_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct quicki2c_device *qcdev;
+ void __iomem *mem_addr;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err_once(&pdev->dev, "Failed to enable PCI device, ret = %d.\n", ret);
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ mem_addr = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
+ ret = PTR_ERR_OR_ZERO(mem_addr);
+ if (ret) {
+ dev_err_once(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret);
+ goto disable_pci_device;
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (ret) {
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err_once(&pdev->dev, "No usable DMA configuration %d\n", ret);
+ goto disable_pci_device;
+ }
+ }
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_err_once(&pdev->dev,
+ "Failed to allocate IRQ vectors. ret = %d\n", ret);
+ goto disable_pci_device;
+ }
+
+ pdev->irq = pci_irq_vector(pdev, 0);
+
+ qcdev = quicki2c_dev_init(pdev, mem_addr);
+ if (IS_ERR(qcdev)) {
+ dev_err_once(&pdev->dev, "QuickI2C device init failed\n");
+ ret = PTR_ERR(qcdev);
+ goto disable_pci_device;
+ }
+
+ pci_set_drvdata(pdev, qcdev);
+
+ ret = devm_request_threaded_irq(&pdev->dev, pdev->irq,
+ quicki2c_irq_quick_handler,
+ quicki2c_irq_thread_handler,
+ IRQF_ONESHOT, KBUILD_MODNAME,
+ qcdev);
+ if (ret) {
+ dev_err_once(&pdev->dev,
+ "Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
+ goto dev_deinit;
+ }
+
+ ret = quicki2c_get_device_descriptor(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Get device descriptor failed, ret = %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quicki2c_alloc_report_buf(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Alloc report buffers failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quicki2c_dma_init(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Setup THC DMA failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
+ if (ret)
+ goto dev_deinit;
+
+ ret = quicki2c_set_power(qcdev, HIDI2C_ON);
+ if (ret) {
+ dev_err(&pdev->dev, "Set Power On command failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quicki2c_reset(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Reset HIDI2C device failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quicki2c_get_report_descriptor(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Get report descriptor failed, ret = %d\n", ret);
+ goto dma_deinit;
+ }
+
+ ret = quicki2c_hid_probe(qcdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register HID device, ret = %d\n", ret);
+ goto dma_deinit;
+ }
+
+ qcdev->state = QUICKI2C_ENABLED;
+
+ /* Enable runtime power management */
+ pm_runtime_use_autosuspend(qcdev->dev);
+ pm_runtime_set_autosuspend_delay(qcdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_mark_last_busy(qcdev->dev);
+ pm_runtime_put_noidle(qcdev->dev);
+ pm_runtime_put_autosuspend(qcdev->dev);
+
+ dev_dbg(&pdev->dev, "QuickI2C probe success\n");
+
+ return 0;
+
+dma_deinit:
+ quicki2c_dma_deinit(qcdev);
+dev_deinit:
+ quicki2c_dev_deinit(qcdev);
+disable_pci_device:
+ pci_clear_master(pdev);
+
+ return ret;
+}
+
+/**
+ * quicki2c_remove - Device Removal Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * This is called by the PCI subsystem to alert the driver
+ * that it should release a PCI device.
+ */
+static void quicki2c_remove(struct pci_dev *pdev)
+{
+ struct quicki2c_device *qcdev;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return;
+
+ quicki2c_hid_remove(qcdev);
+ quicki2c_dma_deinit(qcdev);
+
+ pm_runtime_get_noresume(qcdev->dev);
+
+ quicki2c_dev_deinit(qcdev);
+
+ pci_clear_master(pdev);
+}
+
+/**
+ * quicki2c_shutdown - Device Shutdown Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * This is called from the reboot notifier
+ * it's a simplified version of remove so we go down
+ * faster.
+ */
+static void quicki2c_shutdown(struct pci_dev *pdev)
+{
+ struct quicki2c_device *qcdev;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return;
+
+ /* Must stop DMA before reboot to avoid DMA entering into unknown state */
+ quicki2c_dma_deinit(qcdev);
+
+ quicki2c_dev_deinit(qcdev);
+}
+
+static int quicki2c_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ /*
+ * As I2C is THC subsystem, no register auto save/restore support,
+ * need driver to do that explicitly for every D3 case.
+ */
+ ret = thc_i2c_subip_regs_save(qcdev->thc_hw);
+ if (ret)
+ return ret;
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qcdev->thc_hw, false);
+
+ thc_dma_unconfigure(qcdev->thc_hw);
+
+ return 0;
+}
+
+static int quicki2c_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
+ if (ret)
+ return ret;
+
+ ret = thc_i2c_subip_regs_restore(qcdev->thc_hw);
+ if (ret)
+ return ret;
+
+ thc_interrupt_config(qcdev->thc_hw);
+
+ thc_interrupt_enable(qcdev->thc_hw, true);
+
+ ret = thc_dma_configure(qcdev->thc_hw);
+ if (ret)
+ return ret;
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int quicki2c_freeze(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qcdev->thc_hw, false);
+
+ thc_dma_unconfigure(qcdev->thc_hw);
+
+ return 0;
+}
+
+static int quicki2c_thaw(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ ret = thc_dma_configure(qcdev->thc_hw);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qcdev->thc_hw, true);
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int quicki2c_poweroff(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qcdev->thc_hw, false);
+
+ thc_ltr_unconfig(qcdev->thc_hw);
+
+ quicki2c_dma_deinit(qcdev);
+
+ return 0;
+}
+
+static int quicki2c_restore(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+ int ret;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ /* Reconfig THC HW when back from hibernate */
+ ret = thc_port_select(qcdev->thc_hw, THC_PORT_TYPE_I2C);
+ if (ret)
+ return ret;
+
+ ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
+ qcdev->i2c_speed_mode,
+ qcdev->i2c_clock_hcnt,
+ qcdev->i2c_clock_lcnt);
+ if (ret)
+ return ret;
+
+ thc_interrupt_config(qcdev->thc_hw);
+
+ thc_interrupt_enable(qcdev->thc_hw, true);
+
+ ret = thc_interrupt_quiesce(qcdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ ret = thc_dma_configure(qcdev->thc_hw);
+ if (ret)
+ return ret;
+
+ thc_ltr_config(qcdev->thc_hw,
+ qcdev->active_ltr_val,
+ qcdev->low_power_ltr_val);
+
+ thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE);
+
+ return 0;
+}
+
+static int quicki2c_runtime_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_LP);
+
+ pci_save_state(pdev);
+
+ return 0;
+}
+
+static int quicki2c_runtime_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quicki2c_device *qcdev;
+
+ qcdev = pci_get_drvdata(pdev);
+ if (!qcdev)
+ return -ENODEV;
+
+ thc_change_ltr_mode(qcdev->thc_hw, THC_LTR_MODE_ACTIVE);
+
+ return 0;
+}
+
+static const struct dev_pm_ops quicki2c_pm_ops = {
+ .suspend = quicki2c_suspend,
+ .resume = quicki2c_resume,
+ .freeze = quicki2c_freeze,
+ .thaw = quicki2c_thaw,
+ .poweroff = quicki2c_poweroff,
+ .restore = quicki2c_restore,
+ .runtime_suspend = quicki2c_runtime_suspend,
+ .runtime_resume = quicki2c_runtime_resume,
+ .runtime_idle = NULL,
+};
+
+static const struct pci_device_id quicki2c_pci_tbl[] = {
+ {PCI_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1), },
+ {PCI_VDEVICE(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2), },
+ {PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1), },
+ {PCI_VDEVICE(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2), },
+ {PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1), },
+ {PCI_VDEVICE(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2), },
+ {}
+};
+MODULE_DEVICE_TABLE(pci, quicki2c_pci_tbl);
+
+static struct pci_driver quicki2c_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = quicki2c_pci_tbl,
+ .probe = quicki2c_probe,
+ .remove = quicki2c_remove,
+ .shutdown = quicki2c_shutdown,
+ .driver.pm = &quicki2c_pm_ops,
+ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+};
+
+module_pci_driver(quicki2c_driver);
+
+MODULE_AUTHOR("Xinpeng Sun <xinpeng.sun@intel.com>");
+MODULE_AUTHOR("Even Xu <even.xu@intel.com>");
+
+MODULE_DESCRIPTION("Intel(R) QuickI2C Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("INTEL_THC");
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
new file mode 100644
index 000000000000..6ddb584bd611
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKI2C_DEV_H_
+#define _QUICKI2C_DEV_H_
+
+#include <linux/hid-over-i2c.h>
+#include <linux/workqueue.h>
+
+#define THC_LNL_DEVICE_ID_I2C_PORT1 0xA848
+#define THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A
+#define THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348
+#define THC_PTL_H_DEVICE_ID_I2C_PORT2 0xE34A
+#define THC_PTL_U_DEVICE_ID_I2C_PORT1 0xE448
+#define THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A
+
+/* Packet size value, the unit is 16 bytes */
+#define MAX_PACKET_SIZE_VALUE_LNL 256
+
+/* HIDI2C special ACPI parameters DSD name */
+#define QUICKI2C_ACPI_METHOD_NAME_ICRS "ICRS"
+#define QUICKI2C_ACPI_METHOD_NAME_ISUB "ISUB"
+
+/* HIDI2C special ACPI parameters DSM methods */
+#define QUICKI2C_ACPI_REVISION_NUM 1
+#define QUICKI2C_ACPI_FUNC_NUM_HID_DESC_ADDR 1
+#define QUICKI2C_ACPI_FUNC_NUM_ACTIVE_LTR_VAL 1
+#define QUICKI2C_ACPI_FUNC_NUM_LP_LTR_VAL 2
+
+#define QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED 100000
+#define QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED 400000
+#define QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED 1000000
+#define QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED 3400000
+
+#define QUICKI2C_DEFAULT_ACTIVE_LTR_VALUE 5
+#define QUICKI2C_DEFAULT_LP_LTR_VALUE 500
+#define QUICKI2C_RPM_TIMEOUT_MS 500
+
+/*
+ * THC uses runtime auto suspend to dynamically switch between THC active LTR
+ * and low power LTR to save CPU power.
+ * Default value is 5000ms, that means if no touch event in this time, THC will
+ * change to low power LTR mode.
+ */
+#define DEFAULT_AUTO_SUSPEND_DELAY_MS 5000
+
+enum quicki2c_dev_state {
+ QUICKI2C_NONE,
+ QUICKI2C_RESETING,
+ QUICKI2C_RESETED,
+ QUICKI2C_INITED,
+ QUICKI2C_ENABLED,
+ QUICKI2C_DISABLED,
+};
+
+enum {
+ HIDI2C_ADDRESSING_MODE_7BIT,
+ HIDI2C_ADDRESSING_MODE_10BIT,
+};
+
+/**
+ * struct quicki2c_subip_acpi_parameter - QuickI2C ACPI DSD parameters
+ * @device_address: I2C device slave address
+ * @connection_speed: I2C device expected connection speed
+ * @addressing_mode: I2C device slave address mode, 7bit or 10bit
+ *
+ * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ICRS method, used for
+ * Bus parameter.
+ */
+struct quicki2c_subip_acpi_parameter {
+ u16 device_address;
+ u64 connection_speed;
+ u8 addressing_mode;
+} __packed;
+
+/**
+ * struct quicki2c_subip_acpi_config - QuickI2C ACPI DSD parameters
+ * @SMHX: Standard Mode (100 kbit/s) Serial Clock Line HIGH Period
+ * @SMLX: Standard Mode (100 kbit/s) Serial Clock Line LOW Period
+ * @SMTD: Standard Mode (100 kbit/s) Serial Data Line Transmit Hold Period
+ * @SMRD: Standard Mode (100 kbit/s) Serial Data Receive Hold Period
+ * @FMHX: Fast Mode (400 kbit/s) Serial Clock Line HIGH Period
+ * @FMLX: Fast Mode (400 kbit/s) Serial Clock Line LOW Period
+ * @FMTD: Fast Mode (400 kbit/s) Serial Data Line Transmit Hold Period
+ * @FMRD: Fast Mode (400 kbit/s) Serial Data Line Receive Hold Period
+ * @FMSL: Maximum length (in ic_clk_cycles) of suppressed spikes
+ * in Standard Mode, Fast Mode and Fast Mode Plus
+ * @FPHX: Fast Mode Plus (1Mbit/sec) Serial Clock Line HIGH Period
+ * @FPLX: Fast Mode Plus (1Mbit/sec) Serial Clock Line LOW Period
+ * @FPTD: Fast Mode Plus (1Mbit/sec) Serial Data Line Transmit HOLD Period
+ * @FPRD: Fast Mode Plus (1Mbit/sec) Serial Data Line Receive HOLD Period
+ * @HMHX: High Speed Mode Plus (3.4Mbits/sec) Serial Clock Line HIGH Period
+ * @HMLX: High Speed Mode Plus (3.4Mbits/sec) Serial Clock Line LOW Period
+ * @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period
+ * @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period
+ * @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode
+ *
+ * Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for
+ * I2C timing configure.
+ */
+struct quicki2c_subip_acpi_config {
+ u64 SMHX;
+ u64 SMLX;
+ u64 SMTD;
+ u64 SMRD;
+
+ u64 FMHX;
+ u64 FMLX;
+ u64 FMTD;
+ u64 FMRD;
+ u64 FMSL;
+
+ u64 FPHX;
+ u64 FPLX;
+ u64 FPTD;
+ u64 FPRD;
+
+ u64 HMHX;
+ u64 HMLX;
+ u64 HMTD;
+ u64 HMRD;
+ u64 HMSL;
+};
+
+struct device;
+struct pci_dev;
+struct thc_device;
+struct hid_device;
+struct acpi_device;
+
+/**
+ * struct quicki2c_device - THC QuickI2C device struct
+ * @dev: point to kernel device
+ * @pdev: point to PCI device
+ * @thc_hw: point to THC device
+ * @hid_dev: point to hid device
+ * @acpi_dev: point to ACPI device
+ * @driver_data: point to quicki2c specific driver data
+ * @state: THC I2C device state
+ * @mem_addr: MMIO memory address
+ * @dev_desc: device descriptor for HIDI2C protocol
+ * @i2c_slave_addr: HIDI2C device slave address
+ * @hid_desc_addr: Register address for retrieve HID device descriptor
+ * @active_ltr_val: THC active LTR value
+ * @low_power_ltr_val: THC low power LTR value
+ * @i2c_speed_mode: 0 - standard mode, 1 - fast mode, 2 - fast mode plus
+ * @i2c_clock_hcnt: I2C CLK high period time (unit in cycle count)
+ * @i2c_clock_lcnt: I2C CLK low period time (unit in cycle count)
+ * @report_descriptor: store a copy of device report descriptor
+ * @input_buf: store a copy of latest input report data
+ * @report_buf: store a copy of latest input/output report packet from set/get feature
+ * @report_len: the length of input/output report packet
+ * @reset_ack_wq: workqueue for waiting reset response from device
+ * @reset_ack: indicate reset response received or not
+ */
+struct quicki2c_device {
+ struct device *dev;
+ struct pci_dev *pdev;
+ struct thc_device *thc_hw;
+ struct hid_device *hid_dev;
+ struct acpi_device *acpi_dev;
+ enum quicki2c_dev_state state;
+
+ void __iomem *mem_addr;
+
+ struct hidi2c_dev_descriptor dev_desc;
+ u8 i2c_slave_addr;
+ u16 hid_desc_addr;
+
+ u32 active_ltr_val;
+ u32 low_power_ltr_val;
+
+ u32 i2c_speed_mode;
+ u32 i2c_clock_hcnt;
+ u32 i2c_clock_lcnt;
+
+ u8 *report_descriptor;
+ u8 *input_buf;
+ u8 *report_buf;
+ u32 report_len;
+
+ wait_queue_head_t reset_ack_wq;
+ bool reset_ack;
+};
+
+#endif /* _QUICKI2C_DEV_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c
new file mode 100644
index 000000000000..5c3ec95bb3fd
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/pm_runtime.h>
+
+#include "quicki2c-dev.h"
+#include "quicki2c-hid.h"
+#include "quicki2c-protocol.h"
+
+/**
+ * quicki2c_hid_parse() - HID core parse() callback
+ *
+ * @hid: HID device instance
+ *
+ * This function gets called during call to hid_add_device
+ *
+ * Return: 0 on success and non zero on error.
+ */
+static int quicki2c_hid_parse(struct hid_device *hid)
+{
+ struct quicki2c_device *qcdev = hid->driver_data;
+
+ if (qcdev->report_descriptor)
+ return hid_parse_report(hid, qcdev->report_descriptor,
+ le16_to_cpu(qcdev->dev_desc.report_desc_len));
+
+ dev_err_once(qcdev->dev, "invalid report descriptor\n");
+ return -EINVAL;
+}
+
+static int quicki2c_hid_start(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void quicki2c_hid_stop(struct hid_device *hid)
+{
+}
+
+static int quicki2c_hid_open(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void quicki2c_hid_close(struct hid_device *hid)
+{
+}
+
+static int quicki2c_hid_raw_request(struct hid_device *hid,
+ unsigned char reportnum,
+ __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct quicki2c_device *qcdev = hid->driver_data;
+ int ret = 0;
+
+ ret = pm_runtime_resume_and_get(qcdev->dev);
+ if (ret)
+ return ret;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ ret = quicki2c_get_report(qcdev, rtype, reportnum, buf, len);
+ break;
+ case HID_REQ_SET_REPORT:
+ ret = quicki2c_set_report(qcdev, rtype, reportnum, buf, len);
+ break;
+ default:
+ dev_err(qcdev->dev, "Not supported request type %d\n", reqtype);
+ break;
+ }
+
+ pm_runtime_mark_last_busy(qcdev->dev);
+ pm_runtime_put_autosuspend(qcdev->dev);
+
+ return ret;
+}
+
+static int quicki2c_hid_power(struct hid_device *hid, int lvl)
+{
+ return 0;
+}
+
+static struct hid_ll_driver quicki2c_hid_ll_driver = {
+ .parse = quicki2c_hid_parse,
+ .start = quicki2c_hid_start,
+ .stop = quicki2c_hid_stop,
+ .open = quicki2c_hid_open,
+ .close = quicki2c_hid_close,
+ .power = quicki2c_hid_power,
+ .raw_request = quicki2c_hid_raw_request,
+};
+
+/**
+ * quicki2c_hid_probe() - Register HID low level driver
+ *
+ * @qcdev: point to quicki2c device
+ *
+ * This function is used to allocate and add HID device.
+ *
+ * Return: 0 on success, non zero on error.
+ */
+int quicki2c_hid_probe(struct quicki2c_device *qcdev)
+{
+ struct hid_device *hid;
+ int ret;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ hid->ll_driver = &quicki2c_hid_ll_driver;
+ hid->bus = BUS_PCI;
+ hid->dev.parent = qcdev->dev;
+ hid->driver_data = qcdev;
+ hid->version = le16_to_cpu(qcdev->dev_desc.version_id);
+ hid->vendor = le16_to_cpu(qcdev->dev_desc.vendor_id);
+ hid->product = le16_to_cpu(qcdev->dev_desc.product_id);
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quicki2c-hid",
+ hid->vendor, hid->product);
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ hid_destroy_device(hid);
+ return ret;
+ }
+
+ qcdev->hid_dev = hid;
+
+ return 0;
+}
+
+/**
+ * quicki2c_hid_remove() - Destroy HID device
+ *
+ * @qcdev: point to quicki2c device
+ *
+ * Return: 0 on success, non zero on error.
+ */
+void quicki2c_hid_remove(struct quicki2c_device *qcdev)
+{
+ hid_destroy_device(qcdev->hid_dev);
+}
+
+/**
+ * quicki2c_hid_send_report() - Send HID input report data to HID core
+ *
+ * @qcdev: point to quicki2c device
+ * @data: point to input report data buffer
+ * @data_len: the length of input report data
+ *
+ * Return: 0 on success, non zero on error.
+ */
+int quicki2c_hid_send_report(struct quicki2c_device *qcdev,
+ void *data, size_t data_len)
+{
+ int ret;
+
+ ret = hid_input_report(qcdev->hid_dev, HID_INPUT_REPORT, data, data_len, 1);
+ if (ret)
+ dev_err(qcdev->dev, "Failed to send HID input report, ret = %d.\n", ret);
+
+ return ret;
+}
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h
new file mode 100644
index 000000000000..e80df5f339fe
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKI2C_HID_H_
+#define _QUICKI2C_HID_H_
+
+struct quicki2c_device;
+
+int quicki2c_hid_send_report(struct quicki2c_device *qcdev,
+ void *data, size_t data_size);
+int quicki2c_hid_probe(struct quicki2c_device *qcdev);
+void quicki2c_hid_remove(struct quicki2c_device *qcdev);
+
+#endif /* _QUICKI2C_HID_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c
new file mode 100644
index 000000000000..a63f8c833252
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.c
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/bitfield.h>
+#include <linux/hid.h>
+#include <linux/hid-over-i2c.h>
+#include <linux/unaligned.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-dma.h"
+
+#include "quicki2c-dev.h"
+#include "quicki2c-hid.h"
+#include "quicki2c-protocol.h"
+
+static int quicki2c_init_write_buf(struct quicki2c_device *qcdev, u32 cmd, int cmd_len,
+ bool append_data_reg, u8 *data, int data_len,
+ u8 *write_buf, int write_buf_len)
+{
+ int buf_len, offset = 0;
+
+ buf_len = HIDI2C_REG_LEN + cmd_len;
+
+ if (append_data_reg)
+ buf_len += HIDI2C_REG_LEN;
+
+ if (data && data_len)
+ buf_len += data_len + HIDI2C_LENGTH_LEN;
+
+ if (buf_len > write_buf_len)
+ return -EINVAL;
+
+ memcpy(write_buf, &qcdev->dev_desc.cmd_reg, HIDI2C_REG_LEN);
+ offset += HIDI2C_REG_LEN;
+ memcpy(write_buf + offset, &cmd, cmd_len);
+ offset += cmd_len;
+
+ if (append_data_reg) {
+ memcpy(write_buf + offset, &qcdev->dev_desc.data_reg, HIDI2C_REG_LEN);
+ offset += HIDI2C_REG_LEN;
+ }
+
+ if (data && data_len) {
+ __le16 len = cpu_to_le16(data_len + HIDI2C_LENGTH_LEN);
+
+ memcpy(write_buf + offset, &len, HIDI2C_LENGTH_LEN);
+ offset += HIDI2C_LENGTH_LEN;
+ memcpy(write_buf + offset, data, data_len);
+ }
+
+ return buf_len;
+}
+
+static int quicki2c_encode_cmd(struct quicki2c_device *qcdev, u32 *cmd_buf,
+ u8 opcode, u8 report_type, u8 report_id)
+{
+ int cmd_len;
+
+ *cmd_buf = FIELD_PREP(HIDI2C_CMD_OPCODE, opcode) |
+ FIELD_PREP(HIDI2C_CMD_REPORT_TYPE, report_type);
+
+ if (report_id < HIDI2C_CMD_MAX_RI) {
+ *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, report_id);
+ cmd_len = HIDI2C_CMD_LEN;
+ } else {
+ *cmd_buf |= FIELD_PREP(HIDI2C_CMD_REPORT_ID, HIDI2C_CMD_MAX_RI) |
+ FIELD_PREP(HIDI2C_CMD_3RD_BYTE, report_id);
+ cmd_len = HIDI2C_CMD_LEN_OPT;
+ }
+
+ return cmd_len;
+}
+
+static int write_cmd_to_txdma(struct quicki2c_device *qcdev, int opcode,
+ int report_type, int report_id, u8 *buf, int buf_len)
+{
+ size_t write_buf_len;
+ int cmd_len, ret;
+ u32 cmd;
+
+ cmd_len = quicki2c_encode_cmd(qcdev, &cmd, opcode, report_type, report_id);
+
+ ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, buf ? true : false, buf,
+ buf_len, qcdev->report_buf, qcdev->report_len);
+ if (ret < 0)
+ return ret;
+
+ write_buf_len = ret;
+
+ return thc_dma_write(qcdev->thc_hw, qcdev->report_buf, write_buf_len);
+}
+
+int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state)
+{
+ return write_cmd_to_txdma(qcdev, HIDI2C_SET_POWER, HIDI2C_RESERVED, power_state, NULL, 0);
+}
+
+int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev)
+{
+ u32 read_len = 0;
+ int ret;
+
+ ret = thc_tic_pio_write_and_read(qcdev->thc_hw, qcdev->hid_desc_addr,
+ HIDI2C_REG_LEN, NULL, HIDI2C_DEV_DESC_LEN,
+ &read_len, (u32 *)&qcdev->dev_desc);
+ if (ret || HIDI2C_DEV_DESC_LEN != read_len) {
+ dev_err_once(qcdev->dev, "Get device descriptor failed, ret %d, read len %u\n",
+ ret, read_len);
+ return -EIO;
+ }
+
+ if (le16_to_cpu(qcdev->dev_desc.bcd_ver) != HIDI2C_HID_DESC_BCDVERSION)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev)
+{
+ u16 desc_reg = le16_to_cpu(qcdev->dev_desc.report_desc_reg);
+ size_t read_len = le16_to_cpu(qcdev->dev_desc.report_desc_len);
+ u32 prd_len = read_len;
+
+ return thc_swdma_read(qcdev->thc_hw, (u8 *)&desc_reg, HIDI2C_REG_LEN,
+ &prd_len, qcdev->report_descriptor, &read_len);
+}
+
+int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type,
+ unsigned int reportnum, void *buf, u32 buf_len)
+{
+ struct hidi2c_report_packet *rpt;
+ size_t write_buf_len, read_len = 0;
+ int cmd_len, rep_type;
+ u32 cmd;
+ int ret;
+
+ if (report_type == HID_INPUT_REPORT) {
+ rep_type = HIDI2C_INPUT;
+ } else if (report_type == HID_FEATURE_REPORT) {
+ rep_type = HIDI2C_FEATURE;
+ } else {
+ dev_err(qcdev->dev, "Unsupported report type for GET REPORT: %d\n", report_type);
+ return -EINVAL;
+ }
+
+ cmd_len = quicki2c_encode_cmd(qcdev, &cmd, HIDI2C_GET_REPORT, rep_type, reportnum);
+
+ ret = quicki2c_init_write_buf(qcdev, cmd, cmd_len, true, NULL, 0,
+ qcdev->report_buf, qcdev->report_len);
+ if (ret < 0)
+ return ret;
+
+ write_buf_len = ret;
+
+ rpt = (struct hidi2c_report_packet *)qcdev->input_buf;
+
+ ret = thc_swdma_read(qcdev->thc_hw, qcdev->report_buf, write_buf_len,
+ NULL, rpt, &read_len);
+ if (ret) {
+ dev_err_once(qcdev->dev, "Get report failed, ret %d, read len (%zu vs %d)\n",
+ ret, read_len, buf_len);
+ return ret;
+ }
+
+ if (HIDI2C_DATA_LEN(le16_to_cpu(rpt->len)) != buf_len || rpt->data[0] != reportnum) {
+ dev_err_once(qcdev->dev, "Invalid packet, len (%d vs %d) report id (%d vs %d)\n",
+ le16_to_cpu(rpt->len), buf_len, rpt->data[0], reportnum);
+ return -EINVAL;
+ }
+
+ memcpy(buf, rpt->data, buf_len);
+
+ return buf_len;
+}
+
+int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type,
+ unsigned int reportnum, void *buf, u32 buf_len)
+{
+ int rep_type;
+ int ret;
+
+ if (report_type == HID_OUTPUT_REPORT) {
+ rep_type = HIDI2C_OUTPUT;
+ } else if (report_type == HID_FEATURE_REPORT) {
+ rep_type = HIDI2C_FEATURE;
+ } else {
+ dev_err(qcdev->dev, "Unsupported report type for SET REPORT: %d\n", report_type);
+ return -EINVAL;
+ }
+
+ ret = write_cmd_to_txdma(qcdev, HIDI2C_SET_REPORT, rep_type, reportnum, buf, buf_len);
+ if (ret) {
+ dev_err_once(qcdev->dev, "Set Report failed, ret %d\n", ret);
+ return ret;
+ }
+
+ return buf_len;
+}
+
+#define HIDI2C_RESET_TIMEOUT 5
+
+int quicki2c_reset(struct quicki2c_device *qcdev)
+{
+ u16 input_reg = le16_to_cpu(qcdev->dev_desc.input_reg);
+ size_t read_len = HIDI2C_LENGTH_LEN;
+ u32 prd_len = read_len;
+ int ret;
+
+ qcdev->reset_ack = false;
+ qcdev->state = QUICKI2C_RESETING;
+
+ ret = write_cmd_to_txdma(qcdev, HIDI2C_RESET, HIDI2C_RESERVED, 0, NULL, 0);
+ if (ret) {
+ dev_err_once(qcdev->dev, "Send reset command failed, ret %d\n", ret);
+ return ret;
+ }
+
+ ret = wait_event_interruptible_timeout(qcdev->reset_ack_wq, qcdev->reset_ack,
+ HIDI2C_RESET_TIMEOUT * HZ);
+ if (qcdev->reset_ack)
+ return 0;
+
+ /*
+ * Manually read reset response if it wasn't received, in case reset interrupt
+ * was missed by touch device or THC hardware.
+ */
+ ret = thc_tic_pio_read(qcdev->thc_hw, input_reg, read_len, &prd_len,
+ (u32 *)qcdev->input_buf);
+ if (ret) {
+ dev_err_once(qcdev->dev, "Read Reset Response failed, ret %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Check response packet length, it's first 16 bits of packet.
+ * If response packet length is zero, it's reset response, otherwise not.
+ */
+ if (get_unaligned_le16(qcdev->input_buf)) {
+ dev_err_once(qcdev->dev,
+ "Wait reset response timed out ret:%d timeout:%ds\n",
+ ret, HIDI2C_RESET_TIMEOUT);
+ return -ETIMEDOUT;
+ }
+
+ qcdev->reset_ack = true;
+
+ return 0;
+}
diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h
new file mode 100644
index 000000000000..bf4908cce59c
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-protocol.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKI2C_PROTOCOL_H_
+#define _QUICKI2C_PROTOCOL_H_
+
+#include <linux/hid-over-i2c.h>
+
+struct quicki2c_device;
+
+int quicki2c_set_power(struct quicki2c_device *qcdev, enum hidi2c_power_state power_state);
+int quicki2c_get_report(struct quicki2c_device *qcdev, u8 report_type,
+ unsigned int reportnum, void *buf, u32 buf_len);
+int quicki2c_set_report(struct quicki2c_device *qcdev, u8 report_type,
+ unsigned int reportnum, void *buf, u32 buf_len);
+int quicki2c_get_device_descriptor(struct quicki2c_device *qcdev);
+int quicki2c_get_report_descriptor(struct quicki2c_device *qcdev);
+int quicki2c_reset(struct quicki2c_device *qcdev);
+
+#endif /* _QUICKI2C_PROTOCOL_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
new file mode 100644
index 000000000000..d4f89f44c3b4
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
@@ -0,0 +1,985 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-hw.h"
+
+#include "quickspi-dev.h"
+#include "quickspi-hid.h"
+#include "quickspi-protocol.h"
+
+struct quickspi_driver_data mtl = {
+ .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL,
+};
+
+struct quickspi_driver_data lnl = {
+ .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL,
+};
+
+struct quickspi_driver_data ptl = {
+ .max_packet_size_value = MAX_PACKET_SIZE_VALUE_LNL,
+};
+
+/* THC QuickSPI ACPI method to get device properties */
+/* HIDSPI Method: {6e2ac436-0fcf-41af-a265-b32a220dcfab} */
+static guid_t hidspi_guid =
+ GUID_INIT(0x6e2ac436, 0x0fcf, 0x41af, 0xa2, 0x65, 0xb3, 0x2a,
+ 0x22, 0x0d, 0xcf, 0xab);
+
+/* QuickSpi Method: {300D35b7-ac20-413e-8e9c-92e4dafd0afe} */
+static guid_t thc_quickspi_guid =
+ GUID_INIT(0x300d35b7, 0xac20, 0x413e, 0x8e, 0x9c, 0x92, 0xe4,
+ 0xda, 0xfd, 0x0a, 0xfe);
+
+/* Platform Method: {84005682-5b71-41a4-0x8d668130f787a138} */
+static guid_t thc_platform_guid =
+ GUID_INIT(0x84005682, 0x5b71, 0x41a4, 0x8d, 0x66, 0x81, 0x30,
+ 0xf7, 0x87, 0xa1, 0x38);
+
+/**
+ * thc_acpi_get_property - Query device ACPI parameter
+ *
+ * @adev: point to ACPI device
+ * @guid: ACPI method's guid
+ * @rev: ACPI method's revision
+ * @func: ACPI method's function number
+ * @type: ACPI parameter's data type
+ * @prop_buf: point to return buffer
+ *
+ * This is a helper function for device to query its ACPI parameters.
+ *
+ * Return: 0 if successful or ENODEV on failed.
+ */
+static int thc_acpi_get_property(struct acpi_device *adev, const guid_t *guid,
+ u64 rev, u64 func, acpi_object_type type, void *prop_buf)
+{
+ acpi_handle handle = acpi_device_handle(adev);
+ union acpi_object *obj;
+
+ obj = acpi_evaluate_dsm_typed(handle, guid, rev, func, NULL, type);
+ if (!obj) {
+ acpi_handle_err(handle,
+ "Error _DSM call failed, rev: %llu, func: %llu, type: %u\n",
+ rev, func, type);
+ return -ENODEV;
+ }
+
+ if (type == ACPI_TYPE_INTEGER)
+ *(u32 *)prop_buf = (u32)obj->integer.value;
+ else if (type == ACPI_TYPE_BUFFER)
+ memcpy(prop_buf, obj->buffer.pointer, obj->buffer.length);
+
+ ACPI_FREE(obj);
+
+ return 0;
+}
+
+/**
+ * quickspi_get_acpi_resources - Query all quickspi devices' ACPI parameters
+ *
+ * @qsdev: point to quickspi device
+ *
+ * This function gets all quickspi devices' ACPI resource.
+ *
+ * Return: 0 if successful or error code on failed.
+ */
+static int quickspi_get_acpi_resources(struct quickspi_device *qsdev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(qsdev->dev);
+ int ret = -EINVAL;
+
+ if (!adev) {
+ dev_err(qsdev->dev, "no valid ACPI companion\n");
+ return ret;
+ }
+
+ qsdev->acpi_dev = adev;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_HDR_ADDR,
+ ACPI_TYPE_INTEGER,
+ &qsdev->input_report_hdr_addr);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_BDY_ADDR,
+ ACPI_TYPE_INTEGER,
+ &qsdev->input_report_bdy_addr);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_OUTPUT_REP_ADDR,
+ ACPI_TYPE_INTEGER,
+ &qsdev->output_report_addr);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_READ_OPCODE,
+ ACPI_TYPE_BUFFER,
+ &qsdev->spi_read_opcode);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_WRITE_OPCODE,
+ ACPI_TYPE_BUFFER,
+ &qsdev->spi_write_opcode);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &hidspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_IO_MODE,
+ ACPI_TYPE_INTEGER,
+ &qsdev->spi_read_io_mode);
+ if (ret)
+ return ret;
+
+ if (qsdev->spi_read_io_mode & SPI_WRITE_IO_MODE)
+ qsdev->spi_write_io_mode = FIELD_GET(SPI_IO_MODE_OPCODE, qsdev->spi_read_io_mode);
+ else
+ qsdev->spi_write_io_mode = THC_SINGLE_IO;
+
+ qsdev->spi_read_io_mode = FIELD_GET(SPI_IO_MODE_OPCODE, qsdev->spi_read_io_mode);
+
+ ret = thc_acpi_get_property(adev, &thc_quickspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_CONNECTION_SPEED,
+ ACPI_TYPE_INTEGER,
+ &qsdev->spi_freq_val);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &thc_quickspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_LIMIT_PACKET_SIZE,
+ ACPI_TYPE_INTEGER,
+ &qsdev->limit_packet_size);
+ if (ret)
+ return ret;
+
+ if (qsdev->limit_packet_size || !qsdev->driver_data)
+ qsdev->spi_packet_size = DEFAULT_MIN_PACKET_SIZE_VALUE;
+ else
+ qsdev->spi_packet_size = qsdev->driver_data->max_packet_size_value;
+
+ ret = thc_acpi_get_property(adev, &thc_quickspi_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_PERFORMANCE_LIMIT,
+ ACPI_TYPE_INTEGER,
+ &qsdev->performance_limit);
+ if (ret)
+ return ret;
+
+ qsdev->performance_limit = FIELD_GET(PERFORMANCE_LIMITATION, qsdev->performance_limit);
+
+ ret = thc_acpi_get_property(adev, &thc_platform_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_ACTIVE_LTR,
+ ACPI_TYPE_INTEGER,
+ &qsdev->active_ltr_val);
+ if (ret)
+ return ret;
+
+ ret = thc_acpi_get_property(adev, &thc_platform_guid,
+ ACPI_QUICKSPI_REVISION_NUM,
+ ACPI_QUICKSPI_FUNC_NUM_LP_LTR,
+ ACPI_TYPE_INTEGER,
+ &qsdev->low_power_ltr_val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * quickspi_irq_quick_handler - The ISR of the quickspi driver
+ *
+ * @irq: The irq number
+ * @dev_id: pointer to the device structure
+ *
+ * Return: IRQ_WAKE_THREAD if further process needed.
+ */
+static irqreturn_t quickspi_irq_quick_handler(int irq, void *dev_id)
+{
+ struct quickspi_device *qsdev = dev_id;
+
+ if (qsdev->state == QUICKSPI_DISABLED)
+ return IRQ_HANDLED;
+
+ /* Disable THC interrupt before current interrupt be handled */
+ thc_interrupt_enable(qsdev->thc_hw, false);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/**
+ * try_recover - Try to recovery THC and Device
+ * @qsdev: pointer to quickspi device
+ *
+ * This function is a error handler, called when fatal error happens.
+ * It try to reset Touch Device and re-configure THC to recovery
+ * transferring between Device and THC.
+ *
+ * Return: 0 if successful or error code on failed.
+ */
+static int try_recover(struct quickspi_device *qsdev)
+{
+ int ret;
+
+ ret = reset_tic(qsdev);
+ if (ret) {
+ dev_err(qsdev->dev, "Reset touch device failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ thc_dma_unconfigure(qsdev->thc_hw);
+
+ ret = thc_dma_configure(qsdev->thc_hw);
+ if (ret) {
+ dev_err(qsdev->dev, "Re-configure THC DMA failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * quickspi_irq_thread_handler - IRQ thread handler of quickspi driver
+ *
+ * @irq: The IRQ number
+ * @dev_id: pointer to the quickspi device structure
+ *
+ * Return: IRQ_HANDLED to finish this handler.
+ */
+static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id)
+{
+ struct quickspi_device *qsdev = dev_id;
+ size_t input_len;
+ int read_finished = 0;
+ int err_recover = 0;
+ int int_mask;
+ int ret;
+
+ if (qsdev->state == QUICKSPI_DISABLED)
+ return IRQ_HANDLED;
+
+ ret = pm_runtime_resume_and_get(qsdev->dev);
+ if (ret)
+ return IRQ_HANDLED;
+
+ int_mask = thc_interrupt_handler(qsdev->thc_hw);
+
+ if (int_mask & BIT(THC_FATAL_ERR_INT) || int_mask & BIT(THC_TXN_ERR_INT)) {
+ err_recover = 1;
+ goto end;
+ }
+
+ if (int_mask & BIT(THC_NONDMA_INT)) {
+ if (qsdev->state == QUICKSPI_RESETING) {
+ qsdev->reset_ack = true;
+ wake_up_interruptible(&qsdev->reset_ack_wq);
+ } else {
+ qsdev->nondma_int_received = true;
+ wake_up_interruptible(&qsdev->nondma_int_received_wq);
+ }
+ }
+
+ if (int_mask & BIT(THC_RXDMA2_INT)) {
+ while (!read_finished) {
+ ret = thc_rxdma_read(qsdev->thc_hw, THC_RXDMA2, qsdev->input_buf,
+ &input_len, &read_finished);
+ if (ret) {
+ err_recover = 1;
+ goto end;
+ }
+
+ quickspi_handle_input_data(qsdev, input_len);
+ }
+ }
+
+end:
+ thc_interrupt_enable(qsdev->thc_hw, true);
+
+ if (err_recover)
+ if (try_recover(qsdev))
+ qsdev->state = QUICKSPI_DISABLED;
+
+ pm_runtime_mark_last_busy(qsdev->dev);
+ pm_runtime_put_autosuspend(qsdev->dev);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * quickspi_dev_init - Initialize quickspi device
+ *
+ * @pdev: pointer to the thc pci device
+ * @mem_addr: The pointer of MMIO memory address
+ * @id: point to pci_device_id structure
+ *
+ * Alloc quickspi device structure and initialized THC device,
+ * then configure THC to HIDSPI mode.
+ *
+ * If success, enable THC hardware interrupt.
+ *
+ * Return: pointer to the quickspi device structure if success
+ * or NULL on failed.
+ */
+static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __iomem *mem_addr,
+ const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = devm_kzalloc(dev, sizeof(struct quickspi_device), GFP_KERNEL);
+ if (!qsdev)
+ return ERR_PTR(-ENOMEM);
+
+ qsdev->pdev = pdev;
+ qsdev->dev = dev;
+ qsdev->mem_addr = mem_addr;
+ qsdev->state = QUICKSPI_DISABLED;
+ qsdev->driver_data = (struct quickspi_driver_data *)id->driver_data;
+
+ init_waitqueue_head(&qsdev->reset_ack_wq);
+ init_waitqueue_head(&qsdev->nondma_int_received_wq);
+ init_waitqueue_head(&qsdev->report_desc_got_wq);
+ init_waitqueue_head(&qsdev->get_report_cmpl_wq);
+ init_waitqueue_head(&qsdev->set_report_cmpl_wq);
+
+ /* thc hw init */
+ qsdev->thc_hw = thc_dev_init(qsdev->dev, qsdev->mem_addr);
+ if (IS_ERR(qsdev->thc_hw)) {
+ ret = PTR_ERR(qsdev->thc_hw);
+ dev_err(dev, "Failed to initialize THC device context, ret = %d.\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI);
+ if (ret) {
+ dev_err(dev, "Failed to select THC port, ret = %d.\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ ret = quickspi_get_acpi_resources(qsdev);
+ if (ret) {
+ dev_err(dev, "Get ACPI resources failed, ret = %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ /* THC config for input/output address */
+ thc_spi_input_output_address_config(qsdev->thc_hw,
+ qsdev->input_report_hdr_addr,
+ qsdev->input_report_bdy_addr,
+ qsdev->output_report_addr);
+
+ /* THC config for spi read operation */
+ ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_read_io_mode,
+ qsdev->spi_read_opcode,
+ qsdev->spi_packet_size);
+ if (ret) {
+ dev_err(dev, "thc_spi_read_config failed, ret = %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ /* THC config for spi write operation */
+ ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_write_io_mode,
+ qsdev->spi_write_opcode,
+ qsdev->spi_packet_size,
+ qsdev->performance_limit);
+ if (ret) {
+ dev_err(dev, "thc_spi_write_config failed, ret = %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ thc_ltr_config(qsdev->thc_hw,
+ qsdev->active_ltr_val,
+ qsdev->low_power_ltr_val);
+
+ thc_interrupt_config(qsdev->thc_hw);
+
+ thc_interrupt_enable(qsdev->thc_hw, true);
+
+ qsdev->state = QUICKSPI_INITIATED;
+
+ return qsdev;
+}
+
+/**
+ * quickspi_dev_deinit - De-initialize quickspi device
+ *
+ * @qsdev: pointer to the quickspi device structure
+ *
+ * Disable THC interrupt and deinitilize THC.
+ */
+static void quickspi_dev_deinit(struct quickspi_device *qsdev)
+{
+ thc_interrupt_enable(qsdev->thc_hw, false);
+ thc_ltr_unconfig(qsdev->thc_hw);
+
+ qsdev->state = QUICKSPI_DISABLED;
+}
+
+/**
+ * quickspi_dma_init - Configure THC DMA for quickspi device
+ * @qsdev: pointer to the quickspi device structure
+ *
+ * This function uses TIC's parameters(such as max input length, max output
+ * length) to allocate THC DMA buffers and configure THC DMA engines.
+ *
+ * Return: 0 if successful or error code on failed.
+ */
+static int quickspi_dma_init(struct quickspi_device *qsdev)
+{
+ int ret;
+
+ ret = thc_dma_set_max_packet_sizes(qsdev->thc_hw, 0,
+ le16_to_cpu(qsdev->dev_desc.max_input_len),
+ le16_to_cpu(qsdev->dev_desc.max_output_len),
+ 0);
+ if (ret)
+ return ret;
+
+ ret = thc_dma_allocate(qsdev->thc_hw);
+ if (ret) {
+ dev_err(qsdev->dev, "Allocate THC DMA buffer failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Enable RxDMA */
+ ret = thc_dma_configure(qsdev->thc_hw);
+ if (ret) {
+ dev_err(qsdev->dev, "Configure THC DMA failed, ret = %d\n", ret);
+ thc_dma_unconfigure(qsdev->thc_hw);
+ thc_dma_release(qsdev->thc_hw);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * quickspi_dma_deinit - Release THC DMA for quickspi device
+ * @qsdev: pointer to the quickspi device structure
+ *
+ * Stop THC DMA engines and release all DMA buffers.
+ *
+ */
+static void quickspi_dma_deinit(struct quickspi_device *qsdev)
+{
+ thc_dma_unconfigure(qsdev->thc_hw);
+ thc_dma_release(qsdev->thc_hw);
+}
+
+/**
+ * quickspi_alloc_report_buf - Alloc report buffers
+ * @qsdev: pointer to the quickspi device structure
+ *
+ * Allocate report descriptor buffer, it will be used for restore TIC HID
+ * report descriptor.
+ *
+ * Allocate input report buffer, it will be used for receive HID input report
+ * data from TIC.
+ *
+ * Allocate output report buffer, it will be used for store HID output report,
+ * such as set feature.
+ *
+ * Return: 0 if successful or error code on failed.
+ */
+static int quickspi_alloc_report_buf(struct quickspi_device *qsdev)
+{
+ size_t max_report_len;
+ size_t max_input_len;
+
+ qsdev->report_descriptor = devm_kzalloc(qsdev->dev,
+ le16_to_cpu(qsdev->dev_desc.rep_desc_len),
+ GFP_KERNEL);
+ if (!qsdev->report_descriptor)
+ return -ENOMEM;
+
+ max_input_len = max(le16_to_cpu(qsdev->dev_desc.rep_desc_len),
+ le16_to_cpu(qsdev->dev_desc.max_input_len));
+
+ qsdev->input_buf = devm_kzalloc(qsdev->dev, max_input_len, GFP_KERNEL);
+ if (!qsdev->input_buf)
+ return -ENOMEM;
+
+ max_report_len = max(le16_to_cpu(qsdev->dev_desc.max_output_len),
+ le16_to_cpu(qsdev->dev_desc.max_input_len));
+
+ qsdev->report_buf = devm_kzalloc(qsdev->dev, max_report_len, GFP_KERNEL);
+ if (!qsdev->report_buf)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/*
+ * quickspi_probe: Quickspi driver probe function
+ *
+ * @pdev: point to pci device
+ * @id: point to pci_device_id structure
+ *
+ * This function initializes THC and HIDSPI device, the flow is:
+ * - do THC pci device initialization
+ * - query HIDSPI ACPI parameters
+ * - configure THC to HIDSPI mode
+ * - go through HIDSPI enumeration flow
+ * |- reset HIDSPI device
+ * |- read device descriptor
+ * - enable THC interrupt and DMA
+ * - read report descriptor
+ * - register HID device
+ * - enable runtime power management
+ *
+ * Return 0 if success or error code on failure.
+ */
+static int quickspi_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct quickspi_device *qsdev;
+ void __iomem *mem_addr;
+ int ret;
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to enable PCI device, ret = %d.\n", ret);
+ return ret;
+ }
+
+ pci_set_master(pdev);
+
+ mem_addr = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
+ ret = PTR_ERR_OR_ZERO(mem_addr);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to get PCI regions, ret = %d.\n", ret);
+ goto disable_pci_device;
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (ret) {
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(&pdev->dev, "No usable DMA configuration %d\n", ret);
+ goto disable_pci_device;
+ }
+ }
+
+ ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Failed to allocate IRQ vectors. ret = %d\n", ret);
+ goto disable_pci_device;
+ }
+
+ pdev->irq = pci_irq_vector(pdev, 0);
+
+ qsdev = quickspi_dev_init(pdev, mem_addr, id);
+ if (IS_ERR(qsdev)) {
+ dev_err(&pdev->dev, "QuickSPI device init failed\n");
+ ret = PTR_ERR(qsdev);
+ goto disable_pci_device;
+ }
+
+ pci_set_drvdata(pdev, qsdev);
+
+ ret = devm_request_threaded_irq(&pdev->dev, pdev->irq,
+ quickspi_irq_quick_handler,
+ quickspi_irq_thread_handler,
+ IRQF_ONESHOT, KBUILD_MODNAME,
+ qsdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
+ goto dev_deinit;
+ }
+
+ ret = reset_tic(qsdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Reset Touch Device failed, ret = %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quickspi_alloc_report_buf(qsdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Alloc report buffers failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quickspi_dma_init(qsdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Setup THC DMA failed, ret= %d\n", ret);
+ goto dev_deinit;
+ }
+
+ ret = quickspi_get_report_descriptor(qsdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Get report descriptor failed, ret = %d\n", ret);
+ goto dma_deinit;
+ }
+
+ ret = quickspi_hid_probe(qsdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register HID device, ret = %d\n", ret);
+ goto dma_deinit;
+ }
+
+ qsdev->state = QUICKSPI_ENABLED;
+
+ /* Enable runtime power management */
+ pm_runtime_use_autosuspend(qsdev->dev);
+ pm_runtime_set_autosuspend_delay(qsdev->dev, DEFAULT_AUTO_SUSPEND_DELAY_MS);
+ pm_runtime_mark_last_busy(qsdev->dev);
+ pm_runtime_put_noidle(qsdev->dev);
+ pm_runtime_put_autosuspend(qsdev->dev);
+
+ dev_dbg(&pdev->dev, "QuickSPI probe success\n");
+
+ return 0;
+
+dma_deinit:
+ quickspi_dma_deinit(qsdev);
+dev_deinit:
+ quickspi_dev_deinit(qsdev);
+disable_pci_device:
+ pci_clear_master(pdev);
+
+ return ret;
+}
+
+/**
+ * quickspi_remove - Device Removal Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * This is called by the PCI subsystem to alert the driver
+ * that it should release a PCI device.
+ */
+static void quickspi_remove(struct pci_dev *pdev)
+{
+ struct quickspi_device *qsdev;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return;
+
+ quickspi_hid_remove(qsdev);
+ quickspi_dma_deinit(qsdev);
+
+ pm_runtime_get_noresume(qsdev->dev);
+
+ quickspi_dev_deinit(qsdev);
+
+ pci_clear_master(pdev);
+}
+
+/**
+ * quickspi_shutdown - Device Shutdown Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * This is called from the reboot notifier
+ * it's a simplified version of remove so we go down
+ * faster.
+ */
+static void quickspi_shutdown(struct pci_dev *pdev)
+{
+ struct quickspi_device *qsdev;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return;
+
+ /* Must stop DMA before reboot to avoid DMA entering into unknown state */
+ quickspi_dma_deinit(qsdev);
+
+ quickspi_dev_deinit(qsdev);
+}
+
+static int quickspi_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = quickspi_set_power(qsdev, HIDSPI_SLEEP);
+ if (ret)
+ return ret;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qsdev->thc_hw, false);
+
+ thc_dma_unconfigure(qsdev->thc_hw);
+
+ return 0;
+}
+
+static int quickspi_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI);
+ if (ret)
+ return ret;
+
+ thc_interrupt_config(qsdev->thc_hw);
+
+ thc_interrupt_enable(qsdev->thc_hw, true);
+
+ ret = thc_dma_configure(qsdev->thc_hw);
+ if (ret)
+ return ret;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ ret = quickspi_set_power(qsdev, HIDSPI_ON);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int quickspi_freeze(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qsdev->thc_hw, false);
+
+ thc_dma_unconfigure(qsdev->thc_hw);
+
+ return 0;
+}
+
+static int quickspi_thaw(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = thc_dma_configure(qsdev->thc_hw);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qsdev->thc_hw, true);
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int quickspi_poweroff(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ thc_interrupt_enable(qsdev->thc_hw, false);
+
+ thc_ltr_unconfig(qsdev->thc_hw);
+
+ quickspi_dma_deinit(qsdev);
+
+ return 0;
+}
+
+static int quickspi_restore(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+ int ret;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
+ if (ret)
+ return ret;
+
+ /* Reconfig THC HW when back from hibernate */
+ ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI);
+ if (ret)
+ return ret;
+
+ thc_spi_input_output_address_config(qsdev->thc_hw,
+ qsdev->input_report_hdr_addr,
+ qsdev->input_report_bdy_addr,
+ qsdev->output_report_addr);
+
+ ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_read_io_mode,
+ qsdev->spi_read_opcode,
+ qsdev->spi_packet_size);
+ if (ret)
+ return ret;
+
+ ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_write_io_mode,
+ qsdev->spi_write_opcode,
+ qsdev->spi_packet_size,
+ qsdev->performance_limit);
+ if (ret)
+ return ret;
+
+ thc_interrupt_config(qsdev->thc_hw);
+
+ thc_interrupt_enable(qsdev->thc_hw, true);
+
+ /* TIC may lose power, needs go through reset flow */
+ ret = reset_tic(qsdev);
+ if (ret)
+ return ret;
+
+ ret = thc_dma_configure(qsdev->thc_hw);
+ if (ret)
+ return ret;
+
+ thc_ltr_config(qsdev->thc_hw,
+ qsdev->active_ltr_val,
+ qsdev->low_power_ltr_val);
+
+ thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE);
+
+ qsdev->state = QUICKSPI_ENABLED;
+
+ return 0;
+}
+
+static int quickspi_runtime_suspend(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_LP);
+
+ pci_save_state(pdev);
+
+ return 0;
+}
+
+static int quickspi_runtime_resume(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+ struct quickspi_device *qsdev;
+
+ qsdev = pci_get_drvdata(pdev);
+ if (!qsdev)
+ return -ENODEV;
+
+ thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE);
+
+ return 0;
+}
+
+static const struct dev_pm_ops quickspi_pm_ops = {
+ .suspend = quickspi_suspend,
+ .resume = quickspi_resume,
+ .freeze = quickspi_freeze,
+ .thaw = quickspi_thaw,
+ .poweroff = quickspi_poweroff,
+ .restore = quickspi_restore,
+ .runtime_suspend = quickspi_runtime_suspend,
+ .runtime_resume = quickspi_runtime_resume,
+ .runtime_idle = NULL,
+};
+
+static const struct pci_device_id quickspi_pci_tbl[] = {
+ {PCI_DEVICE_DATA(INTEL, THC_MTL_DEVICE_ID_SPI_PORT1, &mtl), },
+ {PCI_DEVICE_DATA(INTEL, THC_MTL_DEVICE_ID_SPI_PORT2, &mtl), },
+ {PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_SPI_PORT1, &lnl), },
+ {PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_SPI_PORT2, &lnl), },
+ {PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_SPI_PORT1, &ptl), },
+ {PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_SPI_PORT2, &ptl), },
+ {PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_SPI_PORT1, &ptl), },
+ {PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_SPI_PORT2, &ptl), },
+ {}
+};
+MODULE_DEVICE_TABLE(pci, quickspi_pci_tbl);
+
+static struct pci_driver quickspi_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = quickspi_pci_tbl,
+ .probe = quickspi_probe,
+ .remove = quickspi_remove,
+ .shutdown = quickspi_shutdown,
+ .driver.pm = &quickspi_pm_ops,
+ .driver.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+};
+
+module_pci_driver(quickspi_driver);
+
+MODULE_AUTHOR("Xinpeng Sun <xinpeng.sun@intel.com>");
+MODULE_AUTHOR("Even Xu <even.xu@intel.com>");
+
+MODULE_DESCRIPTION("Intel(R) QuickSPI Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("INTEL_THC");
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
new file mode 100644
index 000000000000..6fdf674b21c5
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKSPI_DEV_H_
+#define _QUICKSPI_DEV_H_
+
+#include <linux/bits.h>
+#include <linux/hid-over-spi.h>
+#include <linux/sizes.h>
+#include <linux/wait.h>
+
+#include "quickspi-protocol.h"
+
+#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT1 0x7E49
+#define PCI_DEVICE_ID_INTEL_THC_MTL_DEVICE_ID_SPI_PORT2 0x7E4B
+#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_SPI_PORT1 0xA849
+#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_SPI_PORT2 0xA84B
+#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_SPI_PORT1 0xE349
+#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_SPI_PORT2 0xE34B
+#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_SPI_PORT1 0xE449
+#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_SPI_PORT2 0xE44B
+
+/* HIDSPI special ACPI parameters DSM methods */
+#define ACPI_QUICKSPI_REVISION_NUM 2
+#define ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_HDR_ADDR 1
+#define ACPI_QUICKSPI_FUNC_NUM_INPUT_REP_BDY_ADDR 2
+#define ACPI_QUICKSPI_FUNC_NUM_OUTPUT_REP_ADDR 3
+#define ACPI_QUICKSPI_FUNC_NUM_READ_OPCODE 4
+#define ACPI_QUICKSPI_FUNC_NUM_WRITE_OPCODE 5
+#define ACPI_QUICKSPI_FUNC_NUM_IO_MODE 6
+
+/* QickSPI device special ACPI parameters DSM methods */
+#define ACPI_QUICKSPI_FUNC_NUM_CONNECTION_SPEED 1
+#define ACPI_QUICKSPI_FUNC_NUM_LIMIT_PACKET_SIZE 2
+#define ACPI_QUICKSPI_FUNC_NUM_PERFORMANCE_LIMIT 3
+
+/* Platform special ACPI parameters DSM methods */
+#define ACPI_QUICKSPI_FUNC_NUM_ACTIVE_LTR 1
+#define ACPI_QUICKSPI_FUNC_NUM_LP_LTR 2
+
+#define SPI_WRITE_IO_MODE BIT(13)
+#define SPI_IO_MODE_OPCODE GENMASK(15, 14)
+#define PERFORMANCE_LIMITATION GENMASK(15, 0)
+
+/* Packet size value, the unit is 16 bytes */
+#define DEFAULT_MIN_PACKET_SIZE_VALUE 4
+#define MAX_PACKET_SIZE_VALUE_MTL 128
+#define MAX_PACKET_SIZE_VALUE_LNL 256
+
+/*
+ * THC uses runtime auto suspend to dynamically switch between THC active LTR
+ * and low power LTR to save CPU power.
+ * Default value is 5000ms, that means if no touch event in this time, THC will
+ * change to low power LTR mode.
+ */
+#define DEFAULT_AUTO_SUSPEND_DELAY_MS 5000
+
+enum quickspi_dev_state {
+ QUICKSPI_NONE,
+ QUICKSPI_INITIATED,
+ QUICKSPI_RESETING,
+ QUICKSPI_RESET,
+ QUICKSPI_ENABLED,
+ QUICKSPI_DISABLED,
+};
+
+/**
+ * struct quickspi_driver_data - Driver specific data for quickspi device
+ * @max_packet_size_value: identify max packet size, unit is 16 bytes
+ */
+struct quickspi_driver_data {
+ u32 max_packet_size_value;
+};
+
+struct device;
+struct pci_dev;
+struct thc_device;
+struct hid_device;
+struct acpi_device;
+
+/**
+ * struct quickspi_device - THC QuickSpi device struct
+ * @dev: point to kernel device
+ * @pdev: point to PCI device
+ * @thc_hw: point to THC device
+ * @hid_dev: point to hid device
+ * @acpi_dev: point to ACPI device
+ * @driver_data: point to quickspi specific driver data
+ * @state: THC SPI device state
+ * @mem_addr: MMIO memory address
+ * @dev_desc: device descriptor for HIDSPI protocol
+ * @input_report_hdr_addr: device input report header address
+ * @input_report_bdy_addr: device input report body address
+ * @output_report_bdy_addr: device output report address
+ * @spi_freq_val: device supported max SPI frequnecy, in Hz
+ * @spi_read_io_mode: device supported SPI read io mode
+ * @spi_write_io_mode: device supported SPI write io mode
+ * @spi_read_opcode: device read opcode
+ * @spi_write_opcode: device write opcode
+ * @limit_packet_size: 1 - limit read/write packet to 64Bytes
+ * 0 - device no packet size limiation for read/write
+ * @performance_limit: delay time, in ms.
+ * if device has performance limitation, must give a delay
+ * before write operation after a read operation.
+ * @active_ltr_val: THC active LTR value
+ * @low_power_ltr_val: THC low power LTR value
+ * @report_descriptor: store a copy of device report descriptor
+ * @input_buf: store a copy of latest input report data
+ * @report_buf: store a copy of latest input/output report packet from set/get feature
+ * @report_len: the length of input/output report packet
+ * @reset_ack_wq: workqueue for waiting reset response from device
+ * @reset_ack: indicate reset response received or not
+ * @nondma_int_received_wq: workqueue for waiting THC non-DMA interrupt
+ * @nondma_int_received: indicate THC non-DMA interrupt received or not
+ * @report_desc_got_wq: workqueue for waiting device report descriptor
+ * @report_desc_got: indicate device report descritor received or not
+ * @set_power_on_wq: workqueue for waiting set power on response from device
+ * @set_power_on: indicate set power on response received or not
+ * @get_feature_cmpl_wq: workqueue for waiting get feature response from device
+ * @get_feature_cmpl: indicate get feature received or not
+ * @set_feature_cmpl_wq: workqueue for waiting set feature to device
+ * @set_feature_cmpl: indicate set feature send complete or not
+ */
+struct quickspi_device {
+ struct device *dev;
+ struct pci_dev *pdev;
+ struct thc_device *thc_hw;
+ struct hid_device *hid_dev;
+ struct acpi_device *acpi_dev;
+ struct quickspi_driver_data *driver_data;
+ enum quickspi_dev_state state;
+
+ void __iomem *mem_addr;
+
+ struct hidspi_dev_descriptor dev_desc;
+ u32 input_report_hdr_addr;
+ u32 input_report_bdy_addr;
+ u32 output_report_addr;
+ u32 spi_freq_val;
+ u32 spi_read_io_mode;
+ u32 spi_write_io_mode;
+ u32 spi_read_opcode;
+ u32 spi_write_opcode;
+ u32 limit_packet_size;
+ u32 spi_packet_size;
+ u32 performance_limit;
+
+ u32 active_ltr_val;
+ u32 low_power_ltr_val;
+
+ u8 *report_descriptor;
+ u8 *input_buf;
+ u8 *report_buf;
+ u32 report_len;
+
+ wait_queue_head_t reset_ack_wq;
+ bool reset_ack;
+
+ wait_queue_head_t nondma_int_received_wq;
+ bool nondma_int_received;
+
+ wait_queue_head_t report_desc_got_wq;
+ bool report_desc_got;
+
+ wait_queue_head_t get_report_cmpl_wq;
+ bool get_report_cmpl;
+
+ wait_queue_head_t set_report_cmpl_wq;
+ bool set_report_cmpl;
+};
+
+#endif /* _QUICKSPI_DEV_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c
new file mode 100644
index 000000000000..ad52e402c28a
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/pm_runtime.h>
+
+#include "quickspi-dev.h"
+#include "quickspi-hid.h"
+
+/**
+ * quickspi_hid_parse() - HID core parse() callback
+ *
+ * @hid: HID device instance
+ *
+ * This function gets called during call to hid_add_device
+ *
+ * Return: 0 on success and non zero on error.
+ */
+static int quickspi_hid_parse(struct hid_device *hid)
+{
+ struct quickspi_device *qsdev = hid->driver_data;
+
+ if (qsdev->report_descriptor)
+ return hid_parse_report(hid, qsdev->report_descriptor,
+ le16_to_cpu(qsdev->dev_desc.rep_desc_len));
+
+ dev_err(qsdev->dev, "invalid report descriptor\n");
+ return -EINVAL;
+}
+
+static int quickspi_hid_start(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void quickspi_hid_stop(struct hid_device *hid)
+{
+}
+
+static int quickspi_hid_open(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void quickspi_hid_close(struct hid_device *hid)
+{
+}
+
+static int quickspi_hid_raw_request(struct hid_device *hid,
+ unsigned char reportnum,
+ __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct quickspi_device *qsdev = hid->driver_data;
+ int ret = 0;
+
+ ret = pm_runtime_resume_and_get(qsdev->dev);
+ if (ret)
+ return ret;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ ret = quickspi_get_report(qsdev, rtype, reportnum, buf);
+ break;
+ case HID_REQ_SET_REPORT:
+ ret = quickspi_set_report(qsdev, rtype, reportnum, buf, len);
+ break;
+ default:
+ dev_err_once(qsdev->dev, "Not supported request type %d\n", reqtype);
+ break;
+ }
+
+ pm_runtime_mark_last_busy(qsdev->dev);
+ pm_runtime_put_autosuspend(qsdev->dev);
+
+ return ret;
+}
+
+static int quickspi_hid_power(struct hid_device *hid, int lvl)
+{
+ return 0;
+}
+
+static struct hid_ll_driver quickspi_hid_ll_driver = {
+ .parse = quickspi_hid_parse,
+ .start = quickspi_hid_start,
+ .stop = quickspi_hid_stop,
+ .open = quickspi_hid_open,
+ .close = quickspi_hid_close,
+ .power = quickspi_hid_power,
+ .raw_request = quickspi_hid_raw_request,
+};
+
+/**
+ * quickspi_hid_probe() - Register HID low level driver
+ *
+ * @qsdev: point to quickspi device
+ *
+ * This function is used to allocate and add HID device.
+ *
+ * Return: 0 on success, non zero on error.
+ */
+int quickspi_hid_probe(struct quickspi_device *qsdev)
+{
+ struct hid_device *hid;
+ int ret;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ hid->ll_driver = &quickspi_hid_ll_driver;
+ hid->bus = BUS_PCI;
+ hid->dev.parent = qsdev->dev;
+ hid->driver_data = qsdev;
+ hid->version = le16_to_cpu(qsdev->dev_desc.version_id);
+ hid->vendor = le16_to_cpu(qsdev->dev_desc.vendor_id);
+ hid->product = le16_to_cpu(qsdev->dev_desc.product_id);
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "quickspi-hid",
+ hid->vendor, hid->product);
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ hid_destroy_device(hid);
+ return ret;
+ }
+
+ qsdev->hid_dev = hid;
+
+ return 0;
+}
+
+/**
+ * quickspi_hid_remove() - Destroy HID device
+ *
+ * @qsdev: point to quickspi device
+ *
+ * Return: 0 on success, non zero on error.
+ */
+void quickspi_hid_remove(struct quickspi_device *qsdev)
+{
+ hid_destroy_device(qsdev->hid_dev);
+}
+
+/**
+ * quickspi_hid_send_report() - Send HID input report data to HID core
+ *
+ * @qsdev: point to quickspi device
+ * @data: point to input report data buffer
+ * @data_len: the length of input report data
+ *
+ * Return: 0 on success, non zero on error.
+ */
+int quickspi_hid_send_report(struct quickspi_device *qsdev,
+ void *data, size_t data_len)
+{
+ int ret;
+
+ ret = hid_input_report(qsdev->hid_dev, HID_INPUT_REPORT, data, data_len, 1);
+ if (ret)
+ dev_err(qsdev->dev, "Failed to send HID input report, ret = %d.\n", ret);
+
+ return ret;
+}
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h
new file mode 100644
index 000000000000..f640fa876a40
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKSPI_HID_H_
+#define _QUICKSPI_HID_H_
+
+struct quickspi_device;
+
+int quickspi_hid_send_report(struct quickspi_device *qsdev,
+ void *data, size_t data_size);
+int quickspi_hid_probe(struct quickspi_device *qsdev);
+void quickspi_hid_remove(struct quickspi_device *qsdev);
+
+#endif /* _QUICKSPI_HID_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c
new file mode 100644
index 000000000000..e6ba2ddcc9cb
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c
@@ -0,0 +1,414 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright © 2024 Intel Corporation */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/hid.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-dma.h"
+
+#include "quickspi-dev.h"
+#include "quickspi-hid.h"
+#include "quickspi-protocol.h"
+
+/* THC uses HW to accelerate HID over SPI protocol, THC_M_PRT_DEV_INT_CAUSE
+ * register is used to store message header and body header, below definition
+ * let driver retrieve needed data filed easier from THC_M_PRT_DEV_INT_CAUSE
+ * register.
+ */
+#define HIDSPI_IN_REP_BDY_HDR_REP_TYPE GENMASK(7, 0)
+
+static int write_cmd_to_txdma(struct quickspi_device *qsdev,
+ int report_type, int report_id,
+ u8 *report_buf, const int report_buf_len)
+{
+ struct output_report *write_buf;
+ int write_buf_len;
+ int ret;
+
+ write_buf = (struct output_report *)qsdev->report_buf;
+
+ write_buf->output_hdr.report_type = report_type;
+ write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len);
+ write_buf->output_hdr.content_id = report_id;
+
+ if (report_buf && report_buf_len > 0)
+ memcpy(write_buf->content, report_buf, report_buf_len);
+
+ write_buf_len = HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len);
+
+ ret = thc_dma_write(qsdev->thc_hw, write_buf, write_buf_len);
+ if (ret)
+ dev_err_once(qsdev->dev, "DMA write failed, ret = %d\n", ret);
+
+ return ret;
+}
+
+static int quickspi_get_device_descriptor(struct quickspi_device *qsdev)
+{
+ u8 read_buf[HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE];
+ struct output_report output_rep;
+ u32 input_len, read_len = 0;
+ u32 int_cause_val;
+ u8 input_rep_type;
+ int ret;
+
+ output_rep.output_hdr.report_type = DEVICE_DESCRIPTOR;
+ output_rep.output_hdr.content_len = 0;
+ output_rep.output_hdr.content_id = 0;
+
+ qsdev->nondma_int_received = false;
+
+ ret = thc_tic_pio_write(qsdev->thc_hw, qsdev->output_report_addr,
+ HIDSPI_OUTPUT_REPORT_SIZE(0), (u32 *)&output_rep);
+ if (ret) {
+ dev_err_once(qsdev->dev,
+ "Write DEVICE_DESCRIPTOR command failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = wait_event_interruptible_timeout(qsdev->nondma_int_received_wq,
+ qsdev->nondma_int_received,
+ QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
+ if (ret <= 0 || !qsdev->nondma_int_received) {
+ dev_err_once(qsdev->dev, "Wait DEVICE_DESCRIPTOR timeout, ret:%d\n", ret);
+ return -ETIMEDOUT;
+ }
+ qsdev->nondma_int_received = false;
+
+ int_cause_val = thc_int_cause_read(qsdev->thc_hw);
+ input_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val);
+
+ input_len = input_len * sizeof(u32);
+ if (input_len != HIDSPI_INPUT_DEVICE_DESCRIPTOR_SIZE) {
+ dev_err_once(qsdev->dev, "Receive wrong DEVICE_DESCRIPTOR length, len = %u\n",
+ input_len);
+ return -EINVAL;
+ }
+
+ ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr,
+ input_len, &read_len, (u32 *)read_buf);
+ if (ret || read_len != input_len) {
+ dev_err_once(qsdev->dev, "Read DEVICE_DESCRIPTOR failed, ret = %d\n", ret);
+ dev_err_once(qsdev->dev, "DEVICE_DESCRIPTOR expected len = %u, actual read = %u\n",
+ input_len, read_len);
+ return ret;
+ }
+
+ input_rep_type = ((struct input_report_body_header *)read_buf)->input_report_type;
+
+ if (input_rep_type == DEVICE_DESCRIPTOR_RESPONSE) {
+ memcpy(&qsdev->dev_desc,
+ read_buf + HIDSPI_INPUT_BODY_HEADER_SIZE,
+ HIDSPI_DEVICE_DESCRIPTOR_SIZE);
+
+ return 0;
+ }
+
+ dev_err_once(qsdev->dev, "Unexpected input report type: %d\n", input_rep_type);
+ return -EINVAL;
+}
+
+int quickspi_get_report_descriptor(struct quickspi_device *qsdev)
+{
+ int ret;
+
+ ret = write_cmd_to_txdma(qsdev, REPORT_DESCRIPTOR, 0, NULL, 0);
+ if (ret) {
+ dev_err_once(qsdev->dev,
+ "Write REPORT_DESCRIPTOR command failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = wait_event_interruptible_timeout(qsdev->report_desc_got_wq,
+ qsdev->report_desc_got,
+ QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
+ if (ret <= 0 || !qsdev->report_desc_got) {
+ dev_err_once(qsdev->dev, "Wait Report Descriptor timeout, ret:%d\n", ret);
+ return -ETIMEDOUT;
+ }
+ qsdev->report_desc_got = false;
+
+ return 0;
+}
+
+int quickspi_set_power(struct quickspi_device *qsdev,
+ enum hidspi_power_state power_state)
+{
+ u8 cmd_content = power_state;
+ int ret;
+
+ ret = write_cmd_to_txdma(qsdev, COMMAND_CONTENT,
+ HIDSPI_SET_POWER_CMD_ID,
+ &cmd_content,
+ sizeof(cmd_content));
+ if (ret) {
+ dev_err_once(qsdev->dev, "Write SET_POWER command failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len)
+{
+ struct input_report_body_header *body_hdr;
+ struct input_report_body *input_body;
+ u8 *input_report;
+ u32 input_len;
+ int ret = 0;
+
+ input_body = (struct input_report_body *)qsdev->input_buf;
+ body_hdr = &input_body->body_hdr;
+ input_len = le16_to_cpu(body_hdr->content_len);
+
+ if (HIDSPI_INPUT_BODY_SIZE(input_len) > buf_len) {
+ dev_err_once(qsdev->dev, "Wrong input report length: %u",
+ input_len);
+ return;
+ }
+
+ switch (body_hdr->input_report_type) {
+ case REPORT_DESCRIPTOR_RESPONSE:
+ if (input_len != le16_to_cpu(qsdev->dev_desc.rep_desc_len)) {
+ dev_err_once(qsdev->dev, "Unexpected report descriptor length: %u\n",
+ input_len);
+ return;
+ }
+
+ memcpy(qsdev->report_descriptor, input_body->content, input_len);
+
+ qsdev->report_desc_got = true;
+ wake_up_interruptible(&qsdev->report_desc_got_wq);
+
+ break;
+
+ case COMMAND_RESPONSE:
+ if (body_hdr->content_id == HIDSPI_SET_POWER_CMD_ID) {
+ dev_dbg(qsdev->dev, "Receive set power on response\n");
+ } else {
+ dev_err_once(qsdev->dev, "Unknown command response type: %u\n",
+ body_hdr->content_id);
+ }
+
+ break;
+
+ case RESET_RESPONSE:
+ if (qsdev->state == QUICKSPI_RESETING) {
+ qsdev->reset_ack = true;
+ wake_up_interruptible(&qsdev->reset_ack_wq);
+ dev_dbg(qsdev->dev, "Receive HIR reset response\n");
+ } else {
+ dev_info(qsdev->dev, "Receive DIR\n");
+ }
+ break;
+
+ case GET_FEATURE_RESPONSE:
+ case GET_INPUT_REPORT_RESPONSE:
+ qsdev->report_len = sizeof(body_hdr->content_id) + input_len;
+ input_report = input_body->content - sizeof(body_hdr->content_id);
+
+ memcpy(qsdev->report_buf, input_report, qsdev->report_len);
+
+ qsdev->get_report_cmpl = true;
+ wake_up_interruptible(&qsdev->get_report_cmpl_wq);
+
+ break;
+
+ case SET_FEATURE_RESPONSE:
+ case OUTPUT_REPORT_RESPONSE:
+ qsdev->set_report_cmpl = true;
+ wake_up_interruptible(&qsdev->set_report_cmpl_wq);
+
+ break;
+
+ case DATA:
+ if (qsdev->state != QUICKSPI_ENABLED)
+ return;
+
+ if (input_len > le16_to_cpu(qsdev->dev_desc.max_input_len)) {
+ dev_err_once(qsdev->dev, "Unexpected too large input report length: %u\n",
+ input_len);
+ return;
+ }
+
+ input_len = sizeof(body_hdr->content_id) + input_len;
+ input_report = input_body->content - sizeof(body_hdr->content_id);
+
+ ret = quickspi_hid_send_report(qsdev, input_report, input_len);
+ if (ret)
+ dev_err_once(qsdev->dev, "Failed to send HID input report: %d\n", ret);
+
+ break;
+
+ default:
+ dev_err_once(qsdev->dev, "Unsupported input report type: %u\n",
+ body_hdr->input_report_type);
+ break;
+ }
+}
+
+static int acpi_tic_reset(struct quickspi_device *qsdev)
+{
+ acpi_status status = 0;
+ acpi_handle handle;
+
+ if (!qsdev->acpi_dev)
+ return -ENODEV;
+
+ handle = acpi_device_handle(qsdev->acpi_dev);
+ status = acpi_execute_simple_method(handle, "_RST", 0);
+ if (ACPI_FAILURE(status)) {
+ dev_err_once(qsdev->dev,
+ "Failed to reset device through ACPI method, ret = %d\n", status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int reset_tic(struct quickspi_device *qsdev)
+{
+ u32 actual_read_len, read_len = 0;
+ u32 input_report_len, reset_response, int_cause_val;
+ u8 input_rep_type;
+ int ret;
+
+ qsdev->state = QUICKSPI_RESETING;
+
+ qsdev->reset_ack = false;
+
+ /* First interrupt uses level trigger to avoid missing interrupt */
+ thc_int_trigger_type_select(qsdev->thc_hw, false);
+
+ ret = acpi_tic_reset(qsdev);
+ if (ret)
+ return ret;
+
+ ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
+ if (ret)
+ return ret;
+
+ ret = wait_event_interruptible_timeout(qsdev->reset_ack_wq,
+ qsdev->reset_ack,
+ QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
+ if (ret <= 0 || !qsdev->reset_ack) {
+ dev_err_once(qsdev->dev, "Wait RESET_RESPONSE timeout, ret:%d\n", ret);
+ return -ETIMEDOUT;
+ }
+
+ int_cause_val = thc_int_cause_read(qsdev->thc_hw);
+ input_report_len = FIELD_GET(HIDSPI_INPUT_HEADER_REPORT_LEN, int_cause_val);
+
+ read_len = input_report_len * sizeof(u32);
+ if (read_len != HIDSPI_INPUT_BODY_SIZE(0)) {
+ dev_err_once(qsdev->dev, "Receive wrong RESET_RESPONSE, len = %u\n",
+ read_len);
+ return -EINVAL;
+ }
+
+ /* Switch to edge trigger matching with HIDSPI protocol definition */
+ thc_int_trigger_type_select(qsdev->thc_hw, true);
+
+ ret = thc_tic_pio_read(qsdev->thc_hw, qsdev->input_report_bdy_addr,
+ read_len, &actual_read_len,
+ (u32 *)&reset_response);
+ if (ret || actual_read_len != read_len) {
+ dev_err_once(qsdev->dev, "Read RESET_RESPONSE body failed, ret = %d\n", ret);
+ dev_err_once(qsdev->dev, "RESET_RESPONSE body expected len = %u, actual = %u\n",
+ read_len, actual_read_len);
+ return ret;
+ }
+
+ input_rep_type = FIELD_GET(HIDSPI_IN_REP_BDY_HDR_REP_TYPE, reset_response);
+
+ if (input_rep_type == RESET_RESPONSE) {
+ dev_dbg(qsdev->dev, "RESET_RESPONSE received\n");
+ } else {
+ dev_err_once(qsdev->dev,
+ "Unexpected input report type: %d, expect RESET_RESPONSE\n",
+ input_rep_type);
+ return -EINVAL;
+ }
+
+ qsdev->state = QUICKSPI_RESET;
+
+ ret = quickspi_get_device_descriptor(qsdev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int quickspi_get_report(struct quickspi_device *qsdev,
+ u8 report_type, unsigned int report_id, void *buf)
+{
+ int rep_type;
+ int ret;
+
+ if (report_type == HID_INPUT_REPORT) {
+ rep_type = GET_INPUT_REPORT;
+ } else if (report_type == HID_FEATURE_REPORT) {
+ rep_type = GET_FEATURE;
+ } else {
+ dev_err_once(qsdev->dev, "Unsupported report type for GET REPORT: %d\n",
+ report_type);
+ return -EINVAL;
+ }
+
+ ret = write_cmd_to_txdma(qsdev, rep_type, report_id, NULL, 0);
+ if (ret) {
+ dev_err_once(qsdev->dev, "Write GET_REPORT command failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = wait_event_interruptible_timeout(qsdev->get_report_cmpl_wq,
+ qsdev->get_report_cmpl,
+ QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
+ if (ret <= 0 || !qsdev->get_report_cmpl) {
+ dev_err_once(qsdev->dev, "Wait Get Report Response timeout, ret:%d\n", ret);
+ return -ETIMEDOUT;
+ }
+ qsdev->get_report_cmpl = false;
+
+ memcpy(buf, qsdev->report_buf, qsdev->report_len);
+
+ return qsdev->report_len;
+}
+
+int quickspi_set_report(struct quickspi_device *qsdev,
+ u8 report_type, unsigned int report_id,
+ void *buf, u32 buf_len)
+{
+ int rep_type;
+ int ret;
+
+ if (report_type == HID_OUTPUT_REPORT) {
+ rep_type = OUTPUT_REPORT;
+ } else if (report_type == HID_FEATURE_REPORT) {
+ rep_type = SET_FEATURE;
+ } else {
+ dev_err_once(qsdev->dev, "Unsupported report type for SET REPORT: %d\n",
+ report_type);
+ return -EINVAL;
+ }
+
+ ret = write_cmd_to_txdma(qsdev, rep_type, report_id, buf + 1, buf_len - 1);
+ if (ret) {
+ dev_err_once(qsdev->dev, "Write SET_REPORT command failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ ret = wait_event_interruptible_timeout(qsdev->set_report_cmpl_wq,
+ qsdev->set_report_cmpl,
+ QUICKSPI_ACK_WAIT_TIMEOUT * HZ);
+ if (ret <= 0 || !qsdev->set_report_cmpl) {
+ dev_err_once(qsdev->dev, "Wait Set Report Response timeout, ret:%d\n", ret);
+ return -ETIMEDOUT;
+ }
+ qsdev->set_report_cmpl = false;
+
+ return buf_len;
+}
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h
new file mode 100644
index 000000000000..775e29c1ed13
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _QUICKSPI_PROTOCOL_H_
+#define _QUICKSPI_PROTOCOL_H_
+
+#include <linux/hid-over-spi.h>
+
+#define QUICKSPI_ACK_WAIT_TIMEOUT 5
+
+struct quickspi_device;
+
+void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len);
+int quickspi_get_report(struct quickspi_device *qsdev, u8 report_type,
+ unsigned int report_id, void *buf);
+int quickspi_set_report(struct quickspi_device *qsdev, u8 report_type,
+ unsigned int report_id, void *buf, u32 buf_len);
+int quickspi_get_report_descriptor(struct quickspi_device *qsdev);
+
+int quickspi_set_power(struct quickspi_device *qsdev,
+ enum hidspi_power_state power_state);
+
+int reset_tic(struct quickspi_device *qsdev);
+
+#endif /* _QUICKSPI_PROTOCOL_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
new file mode 100644
index 000000000000..4fc78b5a04b5
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
@@ -0,0 +1,1578 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/bitfield.h>
+#include <linux/regmap.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-hw.h"
+
+static int thc_regmap_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct thc_device *thc_ctx = context;
+ void __iomem *base = thc_ctx->mmio_addr;
+
+ *val = ioread32(base + reg);
+ return 0;
+}
+
+static int thc_regmap_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct thc_device *thc_ctx = context;
+ void __iomem *base = thc_ctx->mmio_addr;
+
+ iowrite32(val, base + reg);
+ return 0;
+}
+
+static const struct regmap_range thc_rw_ranges[] = {
+ regmap_reg_range(0x10, 0x14),
+ regmap_reg_range(0x1000, 0x1320),
+};
+
+static const struct regmap_access_table thc_rw_table = {
+ .yes_ranges = thc_rw_ranges,
+ .n_yes_ranges = ARRAY_SIZE(thc_rw_ranges),
+};
+
+static const struct regmap_config thc_regmap_cfg = {
+ .name = "thc_regmap_common",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x1320,
+ .reg_read = thc_regmap_read,
+ .reg_write = thc_regmap_write,
+ .cache_type = REGCACHE_NONE,
+ .fast_io = true,
+ .rd_table = &thc_rw_table,
+ .wr_table = &thc_rw_table,
+ .volatile_table = &thc_rw_table,
+};
+
+/**
+ * thc_clear_state - Clear THC hardware state
+ *
+ * @dev: The pointer of THC device structure
+ */
+static void thc_clear_state(const struct thc_device *dev)
+{
+ u32 val;
+
+ /* Clear interrupt cause register */
+ val = THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY |
+ THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR |
+ THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR |
+ THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, val, val);
+
+ /* Clear interrupt error state */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_IE_STALL,
+ THC_M_PRT_READ_DMA_CNTRL_IE_STALL);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_IE_STALL,
+ THC_M_PRT_READ_DMA_CNTRL_IE_STALL);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS,
+ THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS,
+ THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS);
+
+ val = THC_M_PRT_INT_EN_TXN_ERR_INT_EN |
+ THC_M_PRT_INT_EN_FATAL_ERR_INT_EN |
+ THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET, val, val);
+
+ val = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
+ THC_M_PRT_SW_SEQ_STS_TSSDONE;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, val, val);
+
+ /* Clear RxDMA state */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_IE_EOF, 0);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS,
+ THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS);
+
+ /* Clear TxDMA state */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
+ THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL,
+ THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL);
+
+ val = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS |
+ THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS |
+ THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, val, val);
+
+ /* Reset all DMAs count */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_DB_CNT_1_OFFSET,
+ THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST,
+ THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CNT_OFFSET,
+ THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST,
+ THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR);
+
+ /* Reset THC hardware sequence state */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_1_OFFSET,
+ THC_M_PRT_FRAME_DROP_CNT_1_RFDC,
+ THC_M_PRT_FRAME_DROP_CNT_1_RFDC);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRAME_DROP_CNT_2_OFFSET,
+ THC_M_PRT_FRAME_DROP_CNT_2_RFDC,
+ THC_M_PRT_FRAME_DROP_CNT_2_RFDC);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_1_OFFSET,
+ THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST,
+ THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_FRM_CNT_2_OFFSET,
+ THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST,
+ THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_1_OFFSET,
+ THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST,
+ THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_RXDMA_PKT_CNT_2_OFFSET,
+ THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST,
+ THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET,
+ THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST,
+ THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SWINT_CNT_1_OFFSET,
+ THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST,
+ THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_TX_FRM_CNT_OFFSET,
+ THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST,
+ THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_TXDMA_PKT_CNT_OFFSET,
+ THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST,
+ THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_1_OFFSET,
+ THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST,
+ THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_UFRM_CNT_2_OFFSET,
+ THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST,
+ THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_1_OFFSET,
+ THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC,
+ THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_PRD_EMPTY_CNT_2_OFFSET,
+ THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC,
+ THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC);
+}
+
+/**
+ * thc_dev_init - Allocate and initialize the THC device structure
+ *
+ * @device: The pointer of device structure
+ * @mem_addr: The pointer of MMIO memory address
+ *
+ * Return: The thc_device pointer on success, NULL on failed.
+ */
+struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr)
+{
+ struct thc_device *thc_dev;
+ int ret;
+
+ thc_dev = devm_kzalloc(device, sizeof(*thc_dev), GFP_KERNEL);
+ if (!thc_dev)
+ return ERR_PTR(-ENOMEM);
+
+ thc_dev->dev = device;
+ thc_dev->mmio_addr = mem_addr;
+ thc_dev->thc_regmap = devm_regmap_init(device, NULL, thc_dev, &thc_regmap_cfg);
+ if (IS_ERR(thc_dev->thc_regmap)) {
+ ret = PTR_ERR(thc_dev->thc_regmap);
+ dev_err_once(device, "Failed to init thc_regmap: %d\n", ret);
+ return ERR_PTR(ret);
+ }
+
+ thc_clear_state(thc_dev);
+
+ mutex_init(&thc_dev->thc_bus_lock);
+ init_waitqueue_head(&thc_dev->write_complete_wait);
+ init_waitqueue_head(&thc_dev->swdma_complete_wait);
+
+ thc_dev->dma_ctx = thc_dma_init(thc_dev);
+ if (!thc_dev->dma_ctx) {
+ dev_err_once(device, "DMA context init failed\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return thc_dev;
+}
+EXPORT_SYMBOL_NS_GPL(thc_dev_init, "INTEL_THC");
+
+static int prepare_pio(const struct thc_device *dev, const u8 pio_op,
+ const u32 address, const u32 size)
+{
+ u32 sts, ctrl, addr, mask;
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts);
+
+ /* Check if THC previous PIO still in progress */
+ if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP) {
+ dev_err_once(dev->dev, "THC PIO is still busy!\n");
+ return -EBUSY;
+ }
+
+ /* Clear error bit and complete bit in state register */
+ sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
+ THC_M_PRT_SW_SEQ_STS_TSSDONE;
+ regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts);
+
+ /* Set PIO data size, opcode and interrupt capability */
+ ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, size) |
+ FIELD_PREP(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD, pio_op);
+ if (dev->pio_int_supported)
+ ctrl |= THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE;
+
+ mask = THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC |
+ THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD |
+ THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_CNTRL_OFFSET, mask, ctrl);
+
+ /* Set PIO target address */
+ addr = FIELD_PREP(THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR, address);
+ mask = THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, mask, addr);
+ return 0;
+}
+
+static void pio_start(const struct thc_device *dev,
+ u32 size_in_bytes, const u32 *buffer)
+{
+ if (size_in_bytes && buffer)
+ regmap_bulk_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET,
+ buffer, size_in_bytes / sizeof(u32));
+
+ /* Enable Start bit */
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_CNTRL_OFFSET,
+ THC_M_PRT_SW_SEQ_CNTRL_TSSGO,
+ THC_M_PRT_SW_SEQ_CNTRL_TSSGO);
+}
+
+static int pio_complete(const struct thc_device *dev,
+ u32 *buffer, u32 *size)
+{
+ u32 sts, ctrl;
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &sts);
+ if (sts & THC_M_PRT_SW_SEQ_STS_THC_SS_ERR) {
+ dev_err_once(dev->dev, "PIO operation error\n");
+ return -EBUSY;
+ }
+
+ if (buffer && size) {
+ regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &ctrl);
+ *size = FIELD_GET(THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC, ctrl);
+
+ regmap_bulk_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET,
+ buffer, *size / sizeof(u32));
+ }
+
+ sts |= THC_M_PRT_SW_SEQ_STS_THC_SS_ERR | THC_M_PRT_SW_SEQ_STS_TSSDONE;
+ regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts);
+ return 0;
+}
+
+static int pio_wait(const struct thc_device *dev)
+{
+ u32 sts = 0;
+ int ret;
+
+ ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, sts,
+ !(sts & THC_M_PRT_SW_SEQ_STS_THC_SS_CIP ||
+ !(sts & THC_M_PRT_SW_SEQ_STS_TSSDONE)),
+ THC_REGMAP_POLLING_INTERVAL_US, THC_PIO_DONE_TIMEOUT_US);
+ if (ret)
+ dev_err_once(dev->dev, "Timeout while polling PIO operation done\n");
+
+ return ret;
+}
+
+/**
+ * thc_tic_pio_read - Read data from touch device by PIO
+ *
+ * @dev: The pointer of THC private device context
+ * @address: Slave address for the PIO operation
+ * @size: Expected read data size
+ * @actual_size: The pointer of the actual data size read from touch device
+ * @buffer: The pointer of data buffer to store the data read from touch device
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_tic_pio_read(struct thc_device *dev, const u32 address,
+ const u32 size, u32 *actual_size, u32 *buffer)
+{
+ u8 opcode;
+ int ret;
+
+ if (size <= 0 || !actual_size || !buffer) {
+ dev_err(dev->dev, "Invalid input parameters, size %u, actual_size %p, buffer %p\n",
+ size, actual_size, buffer);
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ opcode = (dev->port_type == THC_PORT_TYPE_SPI) ?
+ THC_PIO_OP_SPI_TIC_READ : THC_PIO_OP_I2C_TIC_READ;
+
+ ret = prepare_pio(dev, opcode, address, size);
+ if (ret < 0)
+ goto end;
+
+ pio_start(dev, 0, NULL);
+
+ ret = pio_wait(dev);
+ if (ret < 0)
+ goto end;
+
+ ret = pio_complete(dev, buffer, actual_size);
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_tic_pio_read, "INTEL_THC");
+
+/**
+ * thc_tic_pio_write - Write data to touch device by PIO
+ *
+ * @dev: The pointer of THC private device context
+ * @address: Slave address for the PIO operation
+ * @size: PIO write data size
+ * @buffer: The pointer of the write data buffer
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_tic_pio_write(struct thc_device *dev, const u32 address,
+ const u32 size, const u32 *buffer)
+{
+ u8 opcode;
+ int ret;
+
+ if (size <= 0 || !buffer) {
+ dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n",
+ size, buffer);
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ opcode = (dev->port_type == THC_PORT_TYPE_SPI) ?
+ THC_PIO_OP_SPI_TIC_WRITE : THC_PIO_OP_I2C_TIC_WRITE;
+
+ ret = prepare_pio(dev, opcode, address, size);
+ if (ret < 0)
+ goto end;
+
+ pio_start(dev, size, buffer);
+
+ ret = pio_wait(dev);
+ if (ret < 0)
+ goto end;
+
+ ret = pio_complete(dev, NULL, NULL);
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write, "INTEL_THC");
+
+/**
+ * thc_tic_pio_write_and_read - Write data followed by read data by PIO
+ *
+ * @dev: The pointer of THC private device context
+ * @address: Slave address for the PIO operation
+ * @write_size: PIO write data size
+ * @write_buffer: The pointer of the write data buffer
+ * @read_size: Expected PIO read data size
+ * @actual_size: The pointer of the actual read data size
+ * @read_buffer: The pointer of PIO read data buffer
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_tic_pio_write_and_read(struct thc_device *dev, const u32 address,
+ const u32 write_size, const u32 *write_buffer,
+ const u32 read_size, u32 *actual_size, u32 *read_buffer)
+{
+ u32 i2c_ctrl, mask;
+ int ret;
+
+ if (dev->port_type == THC_PORT_TYPE_SPI) {
+ dev_err(dev->dev, "SPI port type doesn't support pio write and read!");
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ /* Config i2c PIO write and read sequence */
+ i2c_ctrl = FIELD_PREP(THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC, write_size);
+ mask = THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC;
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET,
+ mask, i2c_ctrl);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET,
+ THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN,
+ THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN);
+
+ ret = prepare_pio(dev, THC_PIO_OP_I2C_TIC_WRITE_AND_READ, address, read_size);
+ if (ret < 0)
+ goto end;
+
+ pio_start(dev, write_size, write_buffer);
+
+ ret = pio_wait(dev);
+ if (ret < 0)
+ goto end;
+
+ ret = pio_complete(dev, read_buffer, actual_size);
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_tic_pio_write_and_read, "INTEL_THC");
+
+/**
+ * thc_interrupt_config - Configure THC interrupts
+ *
+ * @dev: The pointer of THC private device context
+ */
+void thc_interrupt_config(struct thc_device *dev)
+{
+ u32 mbits, mask, r_dma_ctrl_1;
+
+ /* Clear Error reporting interrupt status bits */
+ mbits = THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS |
+ THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_INT_STATUS_OFFSET,
+ mbits, mbits);
+
+ /* Enable Error Reporting Interrupts */
+ mbits = THC_M_PRT_INT_EN_TXN_ERR_INT_EN |
+ THC_M_PRT_INT_EN_FATAL_ERR_INT_EN |
+ THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_INT_EN_OFFSET,
+ mbits, mbits);
+
+ /* Clear PIO Interrupt status bits */
+ mbits = THC_M_PRT_SW_SEQ_STS_THC_SS_ERR |
+ THC_M_PRT_SW_SEQ_STS_TSSDONE;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_STS_OFFSET,
+ mbits, mbits);
+
+ /* Read Interrupts */
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ &r_dma_ctrl_1);
+ /* Disable RxDMA1 */
+ r_dma_ctrl_1 &= ~THC_M_PRT_READ_DMA_CNTRL_IE_EOF;
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ r_dma_ctrl_1);
+
+ /* Ack EOF Interrupt RxDMA1 */
+ mbits = THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS;
+ /* Ack NonDMA Interrupt */
+ mbits |= THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
+ mbits, mbits);
+
+ /* Ack EOF Interrupt RxDMA2 */
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS,
+ THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS);
+
+ /* Write Interrupts */
+ /* Disable TxDMA */
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
+ THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL,
+ 0);
+
+ /* Clear TxDMA interrupt status bits */
+ mbits = THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS;
+ mbits |= THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_WRITE_INT_STS_OFFSET,
+ mbits, mbits);
+
+ /* Enable Non-DMA device inband interrupt */
+ r_dma_ctrl_1 |= THC_M_PRT_READ_DMA_CNTRL_IE_NDDI;
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ r_dma_ctrl_1);
+
+ if (dev->port_type == THC_PORT_TYPE_SPI) {
+ /* Edge triggered interrupt */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
+ THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN,
+ THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN);
+ } else {
+ /* Level triggered interrupt */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
+ THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN, 0);
+
+ mbits = THC_M_PRT_INT_EN_THC_I2C_IC_MST_ON_HOLD_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_SCL_STUCK_AT_LOW_DET_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_TX_ABRT_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_TX_OVER_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_RX_FULL_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_RX_OVER_INT_EN |
+ THC_M_PRT_INT_EN_THC_I2C_IC_RX_UNDER_INT_EN;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET,
+ mbits, mbits);
+ }
+
+ thc_set_pio_interrupt_support(dev, false);
+
+ /* HIDSPI specific settings */
+ if (dev->port_type == THC_PORT_TYPE_SPI) {
+ mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET,
+ THC_BIT_OFFSET_INTERRUPT_TYPE) |
+ FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN,
+ THC_BIT_LENGTH_INTERRUPT_TYPE) |
+ FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET,
+ THC_BIT_OFFSET_LAST_FRAGMENT_FLAG) |
+ FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL,
+ THC_BITMASK_INVALID_TYPE_DATA);
+ mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET |
+ THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN |
+ THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET |
+ THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CFG_1_OFFSET,
+ mask, mbits);
+
+ mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET,
+ THC_BIT_OFFSET_MICROFRAME_SIZE) |
+ FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN,
+ THC_BIT_LENGTH_MICROFRAME_SIZE) |
+ FIELD_PREP(THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT,
+ THC_UNIT_MICROFRAME_SIZE) |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL;
+ mask = THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE |
+ THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_DEVINT_CFG_2_OFFSET,
+ mask, mbits);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(thc_interrupt_config, "INTEL_THC");
+
+/**
+ * thc_int_trigger_type_select - Select THC interrupt trigger type
+ *
+ * @dev: the pointer of THC private device context
+ * @edge_trigger: determine the interrupt is edge triggered or level triggered
+ */
+void thc_int_trigger_type_select(struct thc_device *dev, bool edge_trigger)
+{
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_TSEQ_CNTRL_1_OFFSET,
+ THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN,
+ edge_trigger ? THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN : 0);
+}
+EXPORT_SYMBOL_NS_GPL(thc_int_trigger_type_select, "INTEL_THC");
+
+/**
+ * thc_interrupt_enable - Enable or disable THC interrupt
+ *
+ * @dev: the pointer of THC private device context
+ * @int_enable: the flag to control THC interrupt enable or disable
+ */
+void thc_interrupt_enable(struct thc_device *dev, bool int_enable)
+{
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_INT_EN_OFFSET,
+ THC_M_PRT_INT_EN_GBL_INT_EN,
+ int_enable ? THC_M_PRT_INT_EN_GBL_INT_EN : 0);
+}
+EXPORT_SYMBOL_NS_GPL(thc_interrupt_enable, "INTEL_THC");
+
+/**
+ * thc_interrupt_quiesce - Quiesce or unquiesce external touch device interrupt
+ *
+ * @dev: the pointer of THC private device context
+ * @int_quiesce: the flag to determine quiesce or unquiesce device interrupt
+ *
+ * Return: 0 on success, other error codes on failed
+ */
+int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce)
+{
+ u32 ctrl;
+ int ret;
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl);
+ if (!(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && !int_quiesce) {
+ dev_warn(dev->dev, "THC interrupt already unquiesce\n");
+ return 0;
+ }
+
+ if ((ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN) && int_quiesce) {
+ dev_warn(dev->dev, "THC interrupt already quiesce\n");
+ return 0;
+ }
+
+ /* Quiesce device interrupt - Set quiesce bit and waiting for THC HW to ACK */
+ if (int_quiesce)
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET,
+ THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN,
+ THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN);
+
+ ret = regmap_read_poll_timeout(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, ctrl,
+ ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS,
+ THC_REGMAP_POLLING_INTERVAL_US, THC_QUIESCE_EN_TIMEOUT_US);
+ if (ret) {
+ dev_err_once(dev->dev,
+ "Timeout while waiting THC idle, target quiesce state = %s\n",
+ int_quiesce ? "true" : "false");
+ return ret;
+ }
+
+ /* Unquiesce device interrupt - Clear the quiesce bit */
+ if (!int_quiesce)
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET,
+ THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_interrupt_quiesce, "INTEL_THC");
+
+/**
+ * thc_set_pio_interrupt_support - Determine PIO interrupt is supported or not
+ *
+ * @dev: The pointer of THC private device context
+ * @supported: The flag to determine enabling PIO interrupt or not
+ */
+void thc_set_pio_interrupt_support(struct thc_device *dev, bool supported)
+{
+ dev->pio_int_supported = supported;
+}
+EXPORT_SYMBOL_NS_GPL(thc_set_pio_interrupt_support, "INTEL_THC");
+
+/**
+ * thc_ltr_config - Configure THC Latency Tolerance Reporting(LTR) settings
+ *
+ * @dev: The pointer of THC private device context
+ * @active_ltr_us: active LTR value, unit is us
+ * @lp_ltr_us: low power LTR value, unit is us
+ */
+void thc_ltr_config(struct thc_device *dev, u32 active_ltr_us, u32 lp_ltr_us)
+{
+ u32 active_ltr_scale, lp_ltr_scale, ltr_ctrl, ltr_mask, orig, tmp;
+
+ if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 &&
+ active_ltr_us < THC_LTR_MAX_VAL_SCALE_3) {
+ active_ltr_scale = THC_LTR_SCALE_3;
+ active_ltr_us = active_ltr_us >> 5;
+ } else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 &&
+ active_ltr_us < THC_LTR_MAX_VAL_SCALE_4) {
+ active_ltr_scale = THC_LTR_SCALE_4;
+ active_ltr_us = active_ltr_us >> 10;
+ } else if (active_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 &&
+ active_ltr_us < THC_LTR_MAX_VAL_SCALE_5) {
+ active_ltr_scale = THC_LTR_SCALE_5;
+ active_ltr_us = active_ltr_us >> 15;
+ } else {
+ active_ltr_scale = THC_LTR_SCALE_2;
+ }
+
+ if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_3 &&
+ lp_ltr_us < THC_LTR_MAX_VAL_SCALE_3) {
+ lp_ltr_scale = THC_LTR_SCALE_3;
+ lp_ltr_us = lp_ltr_us >> 5;
+ } else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_4 &&
+ lp_ltr_us < THC_LTR_MAX_VAL_SCALE_4) {
+ lp_ltr_scale = THC_LTR_SCALE_4;
+ lp_ltr_us = lp_ltr_us >> 10;
+ } else if (lp_ltr_us >= THC_LTR_MIN_VAL_SCALE_5 &&
+ lp_ltr_us < THC_LTR_MAX_VAL_SCALE_5) {
+ lp_ltr_scale = THC_LTR_SCALE_5;
+ lp_ltr_us = lp_ltr_us >> 15;
+ } else {
+ lp_ltr_scale = THC_LTR_SCALE_2;
+ }
+
+ regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, &orig);
+ ltr_ctrl = FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_VAL, active_ltr_us) |
+ FIELD_PREP(THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE, active_ltr_scale) |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
+ FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_VAL, lp_ltr_us) |
+ FIELD_PREP(THC_M_CMN_LTR_CTRL_LP_LTR_SCALE, lp_ltr_scale) |
+ THC_M_CMN_LTR_CTRL_LP_LTR_REQ;
+
+ ltr_mask = THC_M_CMN_LTR_CTRL_ACT_LTR_VAL |
+ THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
+ THC_M_CMN_LTR_CTRL_LP_LTR_VAL |
+ THC_M_CMN_LTR_CTRL_LP_LTR_SCALE |
+ THC_M_CMN_LTR_CTRL_LP_LTR_REQ |
+ THC_M_CMN_LTR_CTRL_LP_LTR_EN;
+
+ tmp = orig & ~ltr_mask;
+ tmp |= ltr_ctrl & ltr_mask;
+
+ regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, tmp);
+}
+EXPORT_SYMBOL_NS_GPL(thc_ltr_config, "INTEL_THC");
+
+/**
+ * thc_change_ltr_mode - Change THC LTR mode
+ *
+ * @dev: The pointer of THC private device context
+ * @ltr_mode: LTR mode(active or low power)
+ */
+void thc_change_ltr_mode(struct thc_device *dev, u32 ltr_mode)
+{
+ if (ltr_mode == THC_LTR_MODE_ACTIVE) {
+ regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
+ THC_M_CMN_LTR_CTRL_LP_LTR_EN, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN,
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN);
+ return;
+ }
+
+ regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET,
+ THC_M_CMN_LTR_CTRL_LP_LTR_EN,
+ THC_M_CMN_LTR_CTRL_LP_LTR_EN);
+}
+EXPORT_SYMBOL_NS_GPL(thc_change_ltr_mode, "INTEL_THC");
+
+/**
+ * thc_ltr_unconfig - Unconfigure THC Latency Tolerance Reporting(LTR) settings
+ *
+ * @dev: The pointer of THC private device context
+ */
+void thc_ltr_unconfig(struct thc_device *dev)
+{
+ u32 ltr_ctrl, bits_clear;
+
+ regmap_read(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, &ltr_ctrl);
+ bits_clear = THC_M_CMN_LTR_CTRL_LP_LTR_EN |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN |
+ THC_M_CMN_LTR_CTRL_LP_LTR_REQ |
+ THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ;
+
+ ltr_ctrl &= ~bits_clear;
+
+ regmap_write(dev->thc_regmap, THC_M_CMN_LTR_CTRL_OFFSET, ltr_ctrl);
+}
+EXPORT_SYMBOL_NS_GPL(thc_ltr_unconfig, "INTEL_THC");
+
+/**
+ * thc_int_cause_read - Read interrupt cause register value
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: The interrupt cause register value
+ */
+u32 thc_int_cause_read(struct thc_device *dev)
+{
+ u32 int_cause;
+
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_DEV_INT_CAUSE_REG_VAL_OFFSET, &int_cause);
+
+ return int_cause;
+}
+EXPORT_SYMBOL_NS_GPL(thc_int_cause_read, "INTEL_THC");
+
+static void thc_print_txn_error_cause(const struct thc_device *dev)
+{
+ bool known_error = false;
+ u32 cause = 0;
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &cause);
+
+ if (cause & THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR) {
+ dev_err(dev->dev, "TXN Error: Invalid PRD Entry\n");
+ known_error = true;
+ }
+ if (cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR) {
+ dev_err(dev->dev, "TXN Error: THC Buffer Overrun\n");
+ known_error = true;
+ }
+ if (cause & THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR) {
+ dev_err(dev->dev, "TXN Error: Frame Babble\n");
+ known_error = true;
+ }
+ if (cause & THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY) {
+ dev_err(dev->dev, "TXN Error: Invalid Device Register Setting\n");
+ known_error = true;
+ }
+
+ /* Clear interrupt status bits */
+ regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, cause);
+
+ if (!known_error)
+ dev_err(dev->dev, "TXN Error does not match any known value: 0x%X\n",
+ cause);
+}
+
+/**
+ * thc_interrupt_handler - Handle THC interrupts
+ *
+ * THC interrupts include several types: external touch device (TIC) non-DMA
+ * interrupts, PIO completion interrupts, DMA interrtups, I2C subIP raw
+ * interrupts and error interrupts.
+ *
+ * This is a help function for interrupt processing, it detects interrupt
+ * type, clear the interrupt status bit and return the interrupt type to caller
+ * for future processing.
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: The combined flag for interrupt type
+ */
+int thc_interrupt_handler(struct thc_device *dev)
+{
+ u32 read_sts_1, read_sts_2, read_sts_sw, write_sts;
+ u32 int_sts, err_cause, seq_cntrl, seq_sts;
+ int interrupt_type = 0;
+
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, &read_sts_1);
+
+ if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS) {
+ dev_dbg(dev->dev, "THC non-DMA device interrupt\n");
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
+ NONDMA_INT_STS_BIT);
+
+ interrupt_type |= BIT(THC_NONDMA_INT);
+
+ return interrupt_type;
+ }
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET, &int_sts);
+
+ if (int_sts & THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS) {
+ dev_err(dev->dev, "THC transaction error, int_sts: 0x%08X\n", int_sts);
+ thc_print_txn_error_cause(dev);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ TXN_ERR_INT_STS_BIT);
+
+ interrupt_type |= BIT(THC_TXN_ERR_INT);
+
+ return interrupt_type;
+ }
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET, &err_cause);
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, &read_sts_2);
+
+ if (err_cause & THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR ||
+ read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS ||
+ read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_STALL_STS) {
+ dev_err(dev->dev, "Buffer overrun or RxDMA engine stalled!\n");
+ thc_print_txn_error_cause(dev);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_2_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_STALL_STS);
+ regmap_write(dev->thc_regmap, THC_M_PRT_READ_DMA_INT_STS_1_OFFSET,
+ THC_M_PRT_READ_DMA_INT_STS_STALL_STS);
+ regmap_write(dev->thc_regmap, THC_M_PRT_ERR_CAUSE_OFFSET,
+ THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR);
+
+ interrupt_type |= BIT(THC_TXN_ERR_INT);
+
+ return interrupt_type;
+ }
+
+ if (int_sts & THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS) {
+ dev_err_once(dev->dev, "THC FATAL error, int_sts: 0x%08X\n", int_sts);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ TXN_FATAL_INT_STS_BIT);
+
+ interrupt_type |= BIT(THC_FATAL_ERR_INT);
+
+ return interrupt_type;
+ }
+
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_CNTRL_OFFSET, &seq_cntrl);
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_SW_SEQ_STS_OFFSET, &seq_sts);
+
+ if (seq_cntrl & THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE &&
+ seq_sts & THC_M_PRT_SW_SEQ_STS_TSSDONE) {
+ dev_dbg(dev->dev, "THC_SS_CD_IE and TSSDONE are set\n");
+ interrupt_type |= BIT(THC_PIO_DONE_INT);
+ }
+
+ if (read_sts_1 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) {
+ dev_dbg(dev->dev, "Got RxDMA1 Read Interrupt\n");
+
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_1_OFFSET, read_sts_1);
+
+ interrupt_type |= BIT(THC_RXDMA1_INT);
+ }
+
+ if (read_sts_2 & THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS) {
+ dev_dbg(dev->dev, "Got RxDMA2 Read Interrupt\n");
+
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_2_OFFSET, read_sts_2);
+
+ interrupt_type |= BIT(THC_RXDMA2_INT);
+ }
+
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, &read_sts_sw);
+
+ if (read_sts_sw & THC_M_PRT_READ_DMA_INT_STS_DMACPL_STS) {
+ dev_dbg(dev->dev, "Got SwDMA Read Interrupt\n");
+
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET, read_sts_sw);
+
+ dev->swdma_done = true;
+ wake_up_interruptible(&dev->swdma_complete_wait);
+
+ interrupt_type |= BIT(THC_SWDMA_INT);
+ }
+
+ regmap_read(dev->thc_regmap,
+ THC_M_PRT_WRITE_INT_STS_OFFSET, &write_sts);
+
+ if (write_sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS) {
+ dev_dbg(dev->dev, "Got TxDMA Write complete Interrupt\n");
+
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_WRITE_INT_STS_OFFSET, write_sts);
+
+ dev->write_done = true;
+ wake_up_interruptible(&dev->write_complete_wait);
+
+ interrupt_type |= BIT(THC_TXDMA_INT);
+ }
+
+ if (int_sts & THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+ if (int_sts & THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS) {
+ regmap_write(dev->thc_regmap, THC_M_PRT_INT_STATUS_OFFSET,
+ THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS);
+ interrupt_type |= BIT(THC_I2CSUBIP_INT);
+ }
+
+ if (!interrupt_type)
+ interrupt_type |= BIT(THC_UNKNOWN_INT);
+
+ return interrupt_type;
+}
+EXPORT_SYMBOL_NS_GPL(thc_interrupt_handler, "INTEL_THC");
+
+/**
+ * thc_port_select - Set THC port type
+ *
+ * @dev: The pointer of THC private device context
+ * @port_type: THC port type to use for current device
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_port_select(struct thc_device *dev, enum thc_port_type port_type)
+{
+ u32 ctrl, mask;
+
+ if (port_type == THC_PORT_TYPE_SPI) {
+ dev_dbg(dev->dev, "Set THC port type to SPI\n");
+ dev->port_type = THC_PORT_TYPE_SPI;
+
+ /* Enable delay of CS assertion and set to default value */
+ ctrl = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN |
+ FIELD_PREP(THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL,
+ THC_CSA_CK_DELAY_VAL_DEFAULT);
+ mask = THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN |
+ THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_DUTYC_CFG_OFFSET,
+ mask, ctrl);
+ } else if (port_type == THC_PORT_TYPE_I2C) {
+ dev_dbg(dev->dev, "Set THC port type to I2C\n");
+ dev->port_type = THC_PORT_TYPE_I2C;
+
+ /* Set THC transition arbitration policy to frame boundary for I2C */
+ ctrl = FIELD_PREP(THC_M_PRT_CONTROL_THC_ARB_POLICY,
+ THC_ARB_POLICY_FRAME_BOUNDARY);
+ mask = THC_M_PRT_CONTROL_THC_ARB_POLICY;
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl);
+ } else {
+ dev_err(dev->dev, "unsupported THC port type: %d\n", port_type);
+ return -EINVAL;
+ }
+
+ ctrl = FIELD_PREP(THC_M_PRT_CONTROL_PORT_TYPE, port_type);
+ mask = THC_M_PRT_CONTROL_PORT_TYPE;
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, mask, ctrl);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_port_select, "INTEL_THC");
+
+#define THC_SPI_FREQUENCY_7M 7812500
+#define THC_SPI_FREQUENCY_15M 15625000
+#define THC_SPI_FREQUENCY_17M 17857100
+#define THC_SPI_FREQUENCY_20M 20833000
+#define THC_SPI_FREQUENCY_25M 25000000
+#define THC_SPI_FREQUENCY_31M 31250000
+#define THC_SPI_FREQUENCY_41M 41666700
+
+#define THC_SPI_LOW_FREQUENCY THC_SPI_FREQUENCY_17M
+
+static u8 thc_get_spi_freq_div_val(struct thc_device *dev, u32 spi_freq_val)
+{
+ int frequency[] = {
+ THC_SPI_FREQUENCY_7M,
+ THC_SPI_FREQUENCY_15M,
+ THC_SPI_FREQUENCY_17M,
+ THC_SPI_FREQUENCY_20M,
+ THC_SPI_FREQUENCY_25M,
+ THC_SPI_FREQUENCY_31M,
+ THC_SPI_FREQUENCY_41M,
+ };
+ u8 frequency_div[] = {
+ THC_SPI_FRQ_DIV_2,
+ THC_SPI_FRQ_DIV_1,
+ THC_SPI_FRQ_DIV_7,
+ THC_SPI_FRQ_DIV_6,
+ THC_SPI_FRQ_DIV_5,
+ THC_SPI_FRQ_DIV_4,
+ THC_SPI_FRQ_DIV_3,
+ };
+ int size = ARRAY_SIZE(frequency);
+ u32 closest_freq;
+ u8 freq_div;
+ int i;
+
+ for (i = size - 1; i >= 0; i--)
+ if ((int)spi_freq_val - frequency[i] >= 0)
+ break;
+
+ if (i < 0) {
+ dev_err_once(dev->dev, "Not supported SPI frequency %d\n", spi_freq_val);
+ return THC_SPI_FRQ_RESERVED;
+ }
+
+ closest_freq = frequency[i];
+ freq_div = frequency_div[i];
+
+ dev_dbg(dev->dev,
+ "Setting SPI frequency: spi_freq_val = %u, Closest freq = %u\n",
+ spi_freq_val, closest_freq);
+
+ return freq_div;
+}
+
+/**
+ * thc_spi_read_config - Configure SPI bus read attributes
+ *
+ * @dev: The pointer of THC private device context
+ * @spi_freq_val: SPI read frequecy value
+ * @io_mode: SPI read IO mode
+ * @opcode: Read opcode
+ * @spi_rd_mps: SPI read max packet size
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val,
+ u32 io_mode, u32 opcode, u32 spi_rd_mps)
+{
+ bool is_low_freq = false;
+ u32 cfg, mask;
+ u8 freq_div;
+
+ freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val);
+ if (freq_div == THC_SPI_FRQ_RESERVED)
+ return -EINVAL;
+
+ if (spi_freq_val < THC_SPI_LOW_FREQUENCY)
+ is_low_freq = true;
+
+ cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCRF, freq_div) |
+ FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TRMODE, io_mode) |
+ (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) |
+ FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_RD_MPS, spi_rd_mps);
+ mask = THC_M_PRT_SPI_CFG_SPI_TCRF |
+ THC_M_PRT_SPI_CFG_SPI_TRMODE |
+ THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN |
+ THC_M_PRT_SPI_CFG_SPI_RD_MPS;
+
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SPI_CFG_OFFSET, mask, cfg);
+
+ if (io_mode == THC_QUAD_IO)
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode);
+ else if (io_mode == THC_DUAL_IO)
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode);
+ else
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, opcode);
+ regmap_write(dev->thc_regmap, THC_M_PRT_SPI_DMARD_OPCODE_OFFSET, opcode);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_spi_read_config, "INTEL_THC");
+
+/**
+ * thc_spi_write_config - Configure SPI bus write attributes
+ *
+ * @dev: The pointer of THC private device context
+ * @spi_freq_val: SPI write frequecy value
+ * @io_mode: SPI write IO mode
+ * @opcode: Write opcode
+ * @spi_wr_mps: SPI write max packet size
+ * @perf_limit: Performance limitation in unit of 10us
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val,
+ u32 io_mode, u32 opcode, u32 spi_wr_mps,
+ u32 perf_limit)
+{
+ bool is_low_freq = false;
+ u32 cfg, mask;
+ u8 freq_div;
+
+ freq_div = thc_get_spi_freq_div_val(dev, spi_freq_val);
+ if (freq_div == THC_SPI_FRQ_RESERVED)
+ return -EINVAL;
+
+ if (spi_freq_val < THC_SPI_LOW_FREQUENCY)
+ is_low_freq = true;
+
+ cfg = FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TCWF, freq_div) |
+ FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_TWMODE, io_mode) |
+ (is_low_freq ? THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN : 0) |
+ FIELD_PREP(THC_M_PRT_SPI_CFG_SPI_WR_MPS, spi_wr_mps);
+ mask = THC_M_PRT_SPI_CFG_SPI_TCWF |
+ THC_M_PRT_SPI_CFG_SPI_TWMODE |
+ THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN |
+ THC_M_PRT_SPI_CFG_SPI_WR_MPS;
+
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_SPI_CFG_OFFSET, mask, cfg);
+
+ if (io_mode == THC_QUAD_IO)
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO, opcode);
+ else if (io_mode == THC_DUAL_IO)
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO, opcode);
+ else
+ opcode = FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO, opcode);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_SPI_WR_OPCODE_OFFSET, opcode);
+
+ dev->perf_limit = perf_limit;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_spi_write_config, "INTEL_THC");
+
+/**
+ * thc_spi_input_output_address_config - Configure SPI input and output addresses
+ *
+ * @dev: the pointer of THC private device context
+ * @input_hdr_addr: input report header address
+ * @input_bdy_addr: input report body address
+ * @output_addr: output report address
+ */
+void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr,
+ u32 input_bdy_addr, u32 output_addr)
+{
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_DEV_INT_CAUSE_ADDR_OFFSET, input_hdr_addr);
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_RD_BULK_ADDR_1_OFFSET, input_bdy_addr);
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_RD_BULK_ADDR_2_OFFSET, input_bdy_addr);
+ regmap_write(dev->thc_regmap,
+ THC_M_PRT_WR_BULK_ADDR_OFFSET, output_addr);
+}
+EXPORT_SYMBOL_NS_GPL(thc_spi_input_output_address_config, "INTEL_THC");
+
+static int thc_i2c_subip_pio_read(struct thc_device *dev, const u32 address,
+ u32 *size, u32 *buffer)
+{
+ int ret;
+
+ if (!size || *size == 0 || !buffer) {
+ dev_err(dev->dev, "Invalid input parameters, size %p, buffer %p\n",
+ size, buffer);
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_READ, address, *size);
+ if (ret < 0)
+ goto end;
+
+ pio_start(dev, 0, NULL);
+
+ ret = pio_wait(dev);
+ if (ret < 0)
+ goto end;
+
+ ret = pio_complete(dev, buffer, size);
+ if (ret < 0)
+ goto end;
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+
+ if (ret)
+ dev_err_once(dev->dev, "Read THC I2C SubIP register failed %d, offset %u\n",
+ ret, address);
+
+ return ret;
+}
+
+static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address,
+ const u32 size, const u32 *buffer)
+{
+ int ret;
+
+ if (size == 0 || !buffer) {
+ dev_err(dev->dev, "Invalid input parameters, size %u, buffer %p\n",
+ size, buffer);
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ ret = prepare_pio(dev, THC_PIO_OP_I2C_SUBSYSTEM_WRITE, address, size);
+ if (ret < 0)
+ goto end;
+
+ pio_start(dev, size, buffer);
+
+ ret = pio_wait(dev);
+ if (ret < 0)
+ goto end;
+
+ ret = pio_complete(dev, NULL, NULL);
+ if (ret < 0)
+ goto end;
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+
+ if (ret)
+ dev_err_once(dev->dev, "Write THC I2C SubIP register failed %d, offset %u\n",
+ ret, address);
+
+ return ret;
+}
+
+#define I2C_SUBIP_CON_DEFAULT 0x663
+#define I2C_SUBIP_INT_MASK_DEFAULT 0x7FFF
+#define I2C_SUBIP_RX_TL_DEFAULT 62
+#define I2C_SUBIP_TX_TL_DEFAULT 0
+#define I2C_SUBIP_DMA_TDLR_DEFAULT 7
+#define I2C_SUBIP_DMA_RDLR_DEFAULT 7
+
+static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
+ const u32 hcnt, const u32 lcnt)
+{
+ u32 hcnt_offset, lcnt_offset;
+ u32 val;
+ int ret;
+
+ switch (speed) {
+ case THC_I2C_STANDARD:
+ hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET;
+ lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET;
+ break;
+
+ case THC_I2C_FAST_AND_PLUS:
+ hcnt_offset = THC_I2C_IC_FS_SCL_HCNT_OFFSET;
+ lcnt_offset = THC_I2C_IC_FS_SCL_LCNT_OFFSET;
+ break;
+
+ case THC_I2C_HIGH_SPEED:
+ hcnt_offset = THC_I2C_IC_HS_SCL_HCNT_OFFSET;
+ lcnt_offset = THC_I2C_IC_HS_SCL_LCNT_OFFSET;
+ break;
+
+ default:
+ dev_err_once(dev->dev, "Unsupported i2c speed %d\n", speed);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt);
+ if (ret < 0)
+ return ret;
+
+ ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED;
+ val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed);
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static u32 i2c_subip_regs[] = {
+ THC_I2C_IC_CON_OFFSET,
+ THC_I2C_IC_TAR_OFFSET,
+ THC_I2C_IC_INTR_MASK_OFFSET,
+ THC_I2C_IC_RX_TL_OFFSET,
+ THC_I2C_IC_TX_TL_OFFSET,
+ THC_I2C_IC_DMA_CR_OFFSET,
+ THC_I2C_IC_DMA_TDLR_OFFSET,
+ THC_I2C_IC_DMA_RDLR_OFFSET,
+ THC_I2C_IC_SS_SCL_HCNT_OFFSET,
+ THC_I2C_IC_SS_SCL_LCNT_OFFSET,
+ THC_I2C_IC_FS_SCL_HCNT_OFFSET,
+ THC_I2C_IC_FS_SCL_LCNT_OFFSET,
+ THC_I2C_IC_HS_SCL_HCNT_OFFSET,
+ THC_I2C_IC_HS_SCL_LCNT_OFFSET,
+ THC_I2C_IC_ENABLE_OFFSET,
+};
+
+/**
+ * thc_i2c_subip_init - Initialize and configure THC I2C subsystem
+ *
+ * @dev: The pointer of THC private device context
+ * @target_address: Slave address of touch device (TIC)
+ * @speed: I2C bus frequency speed mode
+ * @hcnt: I2C clock SCL high count
+ * @lcnt: I2C clock SCL low count
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
+ const u32 speed, const u32 hcnt, const u32 lcnt)
+{
+ u32 read_size = sizeof(u32);
+ u32 val;
+ int ret;
+
+ ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
+ if (ret < 0)
+ return ret;
+
+ val &= ~THC_I2C_IC_ENABLE_ENABLE;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
+ if (ret < 0)
+ return ret;
+
+ val &= ~THC_I2C_IC_TAR_IC_TAR;
+ val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address);
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_INT_MASK_DEFAULT;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_INTR_MASK_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_RX_TL_DEFAULT;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_RX_TL_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_TX_TL_DEFAULT;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TX_TL_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ val = THC_I2C_IC_DMA_CR_RDMAE | THC_I2C_IC_DMA_CR_TDMAE;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_CR_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_DMA_TDLR_DEFAULT;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_TDLR_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ val = I2C_SUBIP_DMA_RDLR_DEFAULT;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_DMA_RDLR_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
+ if (ret < 0)
+ return ret;
+
+ val |= THC_I2C_IC_ENABLE_ENABLE;
+ ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_ENABLE_OFFSET, sizeof(u32), &val);
+ if (ret < 0)
+ return ret;
+
+ dev->i2c_subip_regs = devm_kzalloc(dev->dev, sizeof(i2c_subip_regs), GFP_KERNEL);
+ if (!dev->i2c_subip_regs)
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_init, "INTEL_THC");
+
+/**
+ * thc_i2c_subip_regs_save - Save THC I2C sub-subsystem register values to THC device context
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_i2c_subip_regs_save(struct thc_device *dev)
+{
+ int ret;
+ u32 read_size = sizeof(u32);
+
+ for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) {
+ ret = thc_i2c_subip_pio_read(dev, i2c_subip_regs[i],
+ &read_size, (u32 *)&dev->i2c_subip_regs + i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_save, "INTEL_THC");
+
+/**
+ * thc_i2c_subip_regs_restore - Restore THC I2C subsystem registers from THC device context
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_i2c_subip_regs_restore(struct thc_device *dev)
+{
+ int ret;
+ u32 write_size = sizeof(u32);
+
+ for (int i = 0; i < ARRAY_SIZE(i2c_subip_regs); i++) {
+ ret = thc_i2c_subip_pio_write(dev, i2c_subip_regs[i],
+ write_size, (u32 *)&dev->i2c_subip_regs + i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, "INTEL_THC");
+
+MODULE_AUTHOR("Xinpeng Sun <xinpeng.sun@intel.com>");
+MODULE_AUTHOR("Even Xu <even.xu@intel.com>");
+
+MODULE_DESCRIPTION("Intel(R) Intel THC Hardware Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h
new file mode 100644
index 000000000000..0517fee2c668
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _INTEL_THC_DEV_H_
+#define _INTEL_THC_DEV_H_
+
+#include <linux/cdev.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#include "intel-thc-dma.h"
+
+#define THC_REGMAP_COMMON_OFFSET 0x10
+#define THC_REGMAP_MMIO_OFFSET 0x1000
+
+/*
+ * THC Port type
+ * @THC_PORT_TYPE_SPI: This port is used for HIDSPI
+ * @THC_PORT_TYPE_I2C: This port is used for HIDI2C
+ */
+enum thc_port_type {
+ THC_PORT_TYPE_SPI = 0,
+ THC_PORT_TYPE_I2C = 1,
+};
+
+/**
+ * THC interrupt flag
+ * @THC_NONDMA_INT: THC non-DMA interrupt
+ * @THC_RXDMA1_INT: THC RxDMA1 interrupt
+ * @THC_RXDMA2_INT: THC RxDMA2 interrupt
+ * @THC_SWDMA_INT: THC SWDMA interrupt
+ * @THC_TXDMA_INT: THC TXDMA interrupt
+ * @THC_PIO_DONE_INT: THC PIO complete interrupt
+ * @THC_I2CSUBIP_INT: THC I2C subsystem interrupt
+ * @THC_TXN_ERR_INT: THC transfer error interrupt
+ * @THC_FATAL_ERR_INT: THC fatal error interrupt
+ */
+enum thc_int_type {
+ THC_NONDMA_INT = 0,
+ THC_RXDMA1_INT = 1,
+ THC_RXDMA2_INT = 2,
+ THC_SWDMA_INT = 3,
+ THC_TXDMA_INT = 4,
+ THC_PIO_DONE_INT = 5,
+ THC_I2CSUBIP_INT = 6,
+ THC_TXN_ERR_INT = 7,
+ THC_FATAL_ERR_INT = 8,
+ THC_UNKNOWN_INT
+};
+
+/**
+ * struct thc_device - THC private device struct
+ * @thc_regmap: MMIO regmap structure for accessing THC registers
+ * @mmio_addr: MMIO registers address
+ * @thc_bus_lock: mutex locker for THC config
+ * @port_type: port type of THC port instance
+ * @pio_int_supported: PIO interrupt supported flag
+ * @dma_ctx: DMA specific data
+ * @write_complete_wait: signal event for DMA write complete
+ * @swdma_complete_wait: signal event for SWDMA sequence complete
+ * @write_done: bool value that indicates if DMA write is done
+ * @swdma_done: bool value that indicates if SWDMA swquence is done
+ * @perf_limit: the delay between read operation and write operation
+ * @i2c_subip_regs: the copy of THC I2C sub-system registers for resuming restore
+ */
+struct thc_device {
+ struct device *dev;
+ struct regmap *thc_regmap;
+ void __iomem *mmio_addr;
+ struct mutex thc_bus_lock;
+ enum thc_port_type port_type;
+ bool pio_int_supported;
+
+ struct thc_dma_context *dma_ctx;
+
+ wait_queue_head_t write_complete_wait;
+ wait_queue_head_t swdma_complete_wait;
+ bool write_done;
+ bool swdma_done;
+
+ u32 perf_limit;
+
+ u32 *i2c_subip_regs;
+};
+
+struct thc_device *thc_dev_init(struct device *device, void __iomem *mem_addr);
+int thc_tic_pio_read(struct thc_device *dev, const u32 address,
+ const u32 size, u32 *actual_size, u32 *buffer);
+int thc_tic_pio_write(struct thc_device *dev, const u32 address,
+ const u32 size, const u32 *buffer);
+int thc_tic_pio_write_and_read(struct thc_device *dev, const u32 address,
+ const u32 write_size, const u32 *write_buffer,
+ const u32 read_size, u32 *actual_size, u32 *read_buffer);
+void thc_interrupt_config(struct thc_device *dev);
+void thc_int_trigger_type_select(struct thc_device *dev, bool edge_trigger);
+void thc_interrupt_enable(struct thc_device *dev, bool int_enable);
+void thc_set_pio_interrupt_support(struct thc_device *dev, bool supported);
+int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce);
+void thc_ltr_config(struct thc_device *dev, u32 active_ltr_us, u32 lp_ltr_us);
+void thc_change_ltr_mode(struct thc_device *dev, u32 ltr_mode);
+void thc_ltr_unconfig(struct thc_device *dev);
+u32 thc_int_cause_read(struct thc_device *dev);
+int thc_interrupt_handler(struct thc_device *dev);
+int thc_port_select(struct thc_device *dev, enum thc_port_type port_type);
+int thc_spi_read_config(struct thc_device *dev, u32 spi_freq_val,
+ u32 io_mode, u32 opcode, u32 spi_rd_mps);
+int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val,
+ u32 io_mode, u32 opcode, u32 spi_wr_mps, u32 perf_limit);
+void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr,
+ u32 input_bdy_addr, u32 output_addr);
+int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
+ const u32 speed, const u32 hcnt, const u32 lcnt);
+int thc_i2c_subip_regs_save(struct thc_device *dev);
+int thc_i2c_subip_regs_restore(struct thc_device *dev);
+
+#endif /* _INTEL_THC_DEV_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
new file mode 100644
index 000000000000..8f97e71df7f4
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
@@ -0,0 +1,969 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/overflow.h>
+#include <linux/regmap.h>
+#include <linux/scatterlist.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-dma.h"
+#include "intel-thc-hw.h"
+
+static void dma_set_prd_base_addr(struct thc_device *dev, u64 physical_addr,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 addr_high, addr_low;
+
+ if (!dma_config->is_enabled)
+ return;
+
+ addr_high = upper_32_bits(physical_addr);
+ addr_low = lower_32_bits(physical_addr);
+
+ regmap_write(dev->thc_regmap, dma_config->prd_base_addr_high, addr_high);
+ regmap_write(dev->thc_regmap, dma_config->prd_base_addr_low, addr_low);
+}
+
+static void dma_set_start_bit(struct thc_device *dev,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 ctrl, mask, mbits, data, offset;
+
+ if (!dma_config->is_enabled)
+ return;
+
+ switch (dma_config->dma_channel) {
+ case THC_RXDMA1:
+ case THC_RXDMA2:
+ if (dma_config->dma_channel == THC_RXDMA2) {
+ mbits = FIELD_PREP(THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL,
+ THC_BITMASK_INTERRUPT_TYPE_DATA);
+ mask = THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL;
+ regmap_write_bits(dev->thc_regmap,
+ THC_M_PRT_DEVINT_CFG_1_OFFSET, mask, mbits);
+ }
+
+ mbits = THC_M_PRT_READ_DMA_CNTRL_IE_EOF |
+ THC_M_PRT_READ_DMA_CNTRL_SOO |
+ THC_M_PRT_READ_DMA_CNTRL_IE_STALL |
+ THC_M_PRT_READ_DMA_CNTRL_IE_ERROR |
+ THC_M_PRT_READ_DMA_CNTRL_START;
+
+ mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
+ mask |= THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN;
+ ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
+ offset = dma_config->dma_channel == THC_RXDMA1 ?
+ THC_M_PRT_READ_DMA_CNTRL_1_OFFSET : THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
+ regmap_write_bits(dev->thc_regmap, offset, mask, ctrl);
+ break;
+
+ case THC_SWDMA:
+ mbits = THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL |
+ THC_M_PRT_READ_DMA_CNTRL_IE_IOC |
+ THC_M_PRT_READ_DMA_CNTRL_SOO |
+ THC_M_PRT_READ_DMA_CNTRL_START;
+
+ mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP | mbits;
+ ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, THC_POINTER_WRAPAROUND) | mbits;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
+ mask, ctrl);
+ break;
+
+ case THC_TXDMA:
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET,
+ THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS,
+ THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS);
+
+ /* Select interrupt or polling method upon Write completion */
+ if (dev->dma_ctx->use_write_interrupts)
+ data = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL;
+ else
+ data = 0;
+
+ data |= THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
+ mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL |
+ THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START;
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
+ mask, data);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void dma_set_prd_control(struct thc_device *dev, u8 entry_count, u8 cb_depth,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 ctrl, mask;
+
+ if (!dma_config->is_enabled)
+ return;
+
+ if (dma_config->dma_channel == THC_TXDMA) {
+ mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
+ ctrl = FIELD_PREP(THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC, entry_count);
+ } else {
+ mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;
+ ctrl = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PTEC, entry_count) |
+ FIELD_PREP(THC_M_PRT_RPRD_CNTRL_PCD, cb_depth);
+ }
+
+ regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, ctrl);
+}
+
+static void dma_clear_prd_control(struct thc_device *dev,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 mask;
+
+ if (!dma_config->is_enabled)
+ return;
+
+ if (dma_config->dma_channel == THC_TXDMA)
+ mask = THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC;
+ else
+ mask = THC_M_PRT_RPRD_CNTRL_PTEC | THC_M_PRT_RPRD_CNTRL_PCD;
+
+ regmap_write_bits(dev->thc_regmap, dma_config->prd_cntrl, mask, 0);
+}
+
+static u8 dma_get_read_pointer(struct thc_device *dev,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 ctrl, read_pointer;
+
+ regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
+ read_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCRP, ctrl);
+
+ dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCRP 0x%x\n",
+ ctrl, dma_config->dma_cntrl, read_pointer);
+
+ return read_pointer;
+}
+
+static u8 dma_get_write_pointer(struct thc_device *dev,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 ctrl, write_pointer;
+
+ regmap_read(dev->thc_regmap, dma_config->dma_cntrl, &ctrl);
+ write_pointer = FIELD_GET(THC_M_PRT_READ_DMA_CNTRL_TPCWP, ctrl);
+
+ dev_dbg(dev->dev, "THC_M_PRT_READ_DMA_CNTRL 0x%x offset 0x%x TPCWP 0x%x\n",
+ ctrl, dma_config->dma_cntrl, write_pointer);
+
+ return write_pointer;
+}
+
+static void dma_set_write_pointer(struct thc_device *dev, u8 value,
+ struct thc_dma_configuration *dma_config)
+{
+ u32 ctrl, mask;
+
+ mask = THC_M_PRT_READ_DMA_CNTRL_TPCWP;
+ ctrl = FIELD_PREP(THC_M_PRT_READ_DMA_CNTRL_TPCWP, value);
+ regmap_write_bits(dev->thc_regmap, dma_config->dma_cntrl, mask, ctrl);
+}
+
+static size_t dma_get_max_packet_size(struct thc_device *dev,
+ struct thc_dma_configuration *dma_config)
+{
+ return dma_config->max_packet_size;
+}
+
+static void dma_set_max_packet_size(struct thc_device *dev, size_t size,
+ struct thc_dma_configuration *dma_config)
+{
+ if (size) {
+ dma_config->max_packet_size = ALIGN(size, SZ_4K);
+ dma_config->is_enabled = true;
+ }
+}
+
+static void thc_copy_one_sgl_to_prd(struct thc_device *dev,
+ struct thc_dma_configuration *config,
+ unsigned int ind)
+{
+ struct thc_prd_table *prd_tbl;
+ struct scatterlist *sg;
+ int j;
+
+ prd_tbl = &config->prd_tbls[ind];
+
+ for_each_sg(config->sgls[ind], sg, config->sgls_nent[ind], j) {
+ prd_tbl->entries[j].dest_addr =
+ sg_dma_address(sg) >> THC_ADDRESS_SHIFT;
+ prd_tbl->entries[j].len = sg_dma_len(sg);
+ prd_tbl->entries[j].hw_status = 0;
+ prd_tbl->entries[j].end_of_prd = 0;
+ }
+
+ /* Set the end_of_prd flag in the last filled entry */
+ if (j > 0)
+ prd_tbl->entries[j - 1].end_of_prd = 1;
+}
+
+static void thc_copy_sgls_to_prd(struct thc_device *dev,
+ struct thc_dma_configuration *config)
+{
+ unsigned int i;
+
+ memset(config->prd_tbls, 0, array_size(PRD_TABLE_SIZE, config->prd_tbl_num));
+
+ for (i = 0; i < config->prd_tbl_num; i++)
+ thc_copy_one_sgl_to_prd(dev, config, i);
+}
+
+static int setup_dma_buffers(struct thc_device *dev,
+ struct thc_dma_configuration *config,
+ enum dma_data_direction dir)
+{
+ size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
+ unsigned int i, nent = PRD_ENTRIES_NUM;
+ dma_addr_t dma_handle;
+ void *cpu_addr;
+ size_t buf_sz;
+ int count;
+
+ if (!config->is_enabled)
+ return 0;
+
+ memset(config->sgls, 0, sizeof(config->sgls));
+ memset(config->sgls_nent, 0, sizeof(config->sgls_nent));
+
+ cpu_addr = dma_alloc_coherent(dev->dev, prd_tbls_size,
+ &dma_handle, GFP_KERNEL);
+ if (!cpu_addr)
+ return -ENOMEM;
+
+ config->prd_tbls = cpu_addr;
+ config->prd_tbls_dma_handle = dma_handle;
+
+ buf_sz = dma_get_max_packet_size(dev, config);
+
+ /* Allocate and map the scatter-gather lists, one for each PRD table */
+ for (i = 0; i < config->prd_tbl_num; i++) {
+ config->sgls[i] = sgl_alloc(buf_sz, GFP_KERNEL, &nent);
+ if (!config->sgls[i] || nent > PRD_ENTRIES_NUM) {
+ dev_err_once(dev->dev, "sgl_alloc (%uth) failed, nent %u\n",
+ i, nent);
+ return -ENOMEM;
+ }
+ count = dma_map_sg(dev->dev, config->sgls[i], nent, dir);
+
+ config->sgls_nent[i] = count;
+ }
+
+ thc_copy_sgls_to_prd(dev, config);
+
+ return 0;
+}
+
+static void thc_reset_dma_settings(struct thc_device *dev)
+{
+ /* Stop all DMA channels and reset DMA read pointers */
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_START, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_START, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_START, 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_WRITE_DMA_CNTRL_OFFSET,
+ THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START, 0);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR,
+ THC_M_PRT_READ_DMA_CNTRL_TPCPR);
+}
+
+static void release_dma_buffers(struct thc_device *dev,
+ struct thc_dma_configuration *config)
+{
+ size_t prd_tbls_size = array_size(PRD_TABLE_SIZE, config->prd_tbl_num);
+ unsigned int i;
+
+ if (!config->is_enabled)
+ return;
+
+ for (i = 0; i < config->prd_tbl_num; i++) {
+ if (!config->sgls[i] || !config->sgls_nent[i])
+ continue;
+
+ dma_unmap_sg(dev->dev, config->sgls[i],
+ config->sgls_nent[i],
+ config->dir);
+
+ sgl_free(config->sgls[i]);
+ config->sgls[i] = NULL;
+ }
+
+ memset(config->prd_tbls, 0, prd_tbls_size);
+
+ if (config->prd_tbls) {
+ dma_free_coherent(dev->dev, prd_tbls_size, config->prd_tbls,
+ config->prd_tbls_dma_handle);
+ config->prd_tbls = NULL;
+ config->prd_tbls_dma_handle = 0;
+ }
+}
+
+struct thc_dma_context *thc_dma_init(struct thc_device *dev)
+{
+ struct thc_dma_context *dma_ctx;
+
+ dma_ctx = devm_kzalloc(dev->dev, sizeof(*dma_ctx), GFP_KERNEL);
+ if (!dma_ctx)
+ return NULL;
+
+ dev->dma_ctx = dma_ctx;
+
+ dma_ctx->dma_config[THC_RXDMA1].dma_channel = THC_RXDMA1;
+ dma_ctx->dma_config[THC_RXDMA2].dma_channel = THC_RXDMA2;
+ dma_ctx->dma_config[THC_TXDMA].dma_channel = THC_TXDMA;
+ dma_ctx->dma_config[THC_SWDMA].dma_channel = THC_SWDMA;
+
+ dma_ctx->dma_config[THC_RXDMA1].dir = DMA_FROM_DEVICE;
+ dma_ctx->dma_config[THC_RXDMA2].dir = DMA_FROM_DEVICE;
+ dma_ctx->dma_config[THC_TXDMA].dir = DMA_TO_DEVICE;
+ dma_ctx->dma_config[THC_SWDMA].dir = DMA_FROM_DEVICE;
+
+ dma_ctx->dma_config[THC_RXDMA1].prd_tbl_num = PRD_TABLES_NUM;
+ dma_ctx->dma_config[THC_RXDMA2].prd_tbl_num = PRD_TABLES_NUM;
+ dma_ctx->dma_config[THC_TXDMA].prd_tbl_num = 1;
+ dma_ctx->dma_config[THC_SWDMA].prd_tbl_num = 1;
+
+ dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_1_OFFSET;
+ dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_2_OFFSET;
+ dma_ctx->dma_config[THC_TXDMA].prd_base_addr_high = THC_M_PRT_WPRD_BA_HI_OFFSET;
+ dma_ctx->dma_config[THC_SWDMA].prd_base_addr_high = THC_M_PRT_RPRD_BA_HI_SW_OFFSET;
+
+ dma_ctx->dma_config[THC_RXDMA1].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_1_OFFSET;
+ dma_ctx->dma_config[THC_RXDMA2].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_2_OFFSET;
+ dma_ctx->dma_config[THC_TXDMA].prd_base_addr_low = THC_M_PRT_WPRD_BA_LOW_OFFSET;
+ dma_ctx->dma_config[THC_SWDMA].prd_base_addr_low = THC_M_PRT_RPRD_BA_LOW_SW_OFFSET;
+
+ dma_ctx->dma_config[THC_RXDMA1].prd_cntrl = THC_M_PRT_RPRD_CNTRL_1_OFFSET;
+ dma_ctx->dma_config[THC_RXDMA2].prd_cntrl = THC_M_PRT_RPRD_CNTRL_2_OFFSET;
+ dma_ctx->dma_config[THC_TXDMA].prd_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
+ dma_ctx->dma_config[THC_SWDMA].prd_cntrl = THC_M_PRT_RPRD_CNTRL_SW_OFFSET;
+
+ dma_ctx->dma_config[THC_RXDMA1].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_1_OFFSET;
+ dma_ctx->dma_config[THC_RXDMA2].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_2_OFFSET;
+ dma_ctx->dma_config[THC_TXDMA].dma_cntrl = THC_M_PRT_WRITE_DMA_CNTRL_OFFSET;
+ dma_ctx->dma_config[THC_SWDMA].dma_cntrl = THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET;
+
+ /* Enable write DMA completion interrupt by default */
+ dma_ctx->use_write_interrupts = 1;
+
+ return dma_ctx;
+}
+
+/**
+ * thc_dma_set_max_packet_sizes - Set max packet sizes for all DMA engines
+ *
+ * @dev: The pointer of THC private device context
+ * @mps_read1: RxDMA1 max packet size
+ * @mps_read2: RxDMA2 max packet size
+ * @mps_write: TxDMA max packet size
+ * @mps_swdma: Software DMA max packet size
+ *
+ * If mps is not 0, it means the corresponding DMA channel is used, then set
+ * the flag to turn on this channel.
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_dma_set_max_packet_sizes(struct thc_device *dev, size_t mps_read1,
+ size_t mps_read2, size_t mps_write,
+ size_t mps_swdma)
+{
+ if (!dev->dma_ctx) {
+ dev_err_once(dev->dev,
+ "Cannot set max packet sizes because DMA context is NULL!\n");
+ return -EINVAL;
+ }
+
+ dma_set_max_packet_size(dev, mps_read1, &dev->dma_ctx->dma_config[THC_RXDMA1]);
+ dma_set_max_packet_size(dev, mps_read2, &dev->dma_ctx->dma_config[THC_RXDMA2]);
+ dma_set_max_packet_size(dev, mps_write, &dev->dma_ctx->dma_config[THC_TXDMA]);
+ dma_set_max_packet_size(dev, mps_swdma, &dev->dma_ctx->dma_config[THC_SWDMA]);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_set_max_packet_sizes, "INTEL_THC");
+
+/**
+ * thc_dma_allocate - Allocate DMA buffers for all DMA engines
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_dma_allocate(struct thc_device *dev)
+{
+ int ret, chan;
+
+ for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
+ ret = setup_dma_buffers(dev, &dev->dma_ctx->dma_config[chan],
+ dev->dma_ctx->dma_config[chan].dir);
+ if (ret < 0) {
+ dev_err_once(dev->dev, "DMA setup failed for DMA channel %d\n", chan);
+ goto release_bufs;
+ }
+ }
+
+ return 0;
+
+release_bufs:
+ while (chan--)
+ release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_allocate, "INTEL_THC");
+
+/**
+ * thc_dma_release - Release DMA buffers for all DMA engines
+ *
+ * @dev: The pointer of THC private device context
+ */
+void thc_dma_release(struct thc_device *dev)
+{
+ int chan;
+
+ for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++)
+ release_dma_buffers(dev, &dev->dma_ctx->dma_config[chan]);
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_release, "INTEL_THC");
+
+static int calc_prd_entries_num(struct thc_prd_table *prd_tbl,
+ size_t mes_len, u8 *nent)
+{
+ *nent = DIV_ROUND_UP(mes_len, THC_MIN_BYTES_PER_SG_LIST_ENTRY);
+ if (*nent > PRD_ENTRIES_NUM)
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static size_t calc_message_len(struct thc_prd_table *prd_tbl, u8 *nent)
+{
+ size_t mes_len = 0;
+ unsigned int j;
+
+ for (j = 0; j < PRD_ENTRIES_NUM; j++) {
+ mes_len += prd_tbl->entries[j].len;
+ if (prd_tbl->entries[j].end_of_prd)
+ break;
+ }
+
+ *nent = j + 1;
+
+ return mes_len;
+}
+
+/**
+ * thc_dma_configure - Configure DMA settings for all DMA engines
+ *
+ * @dev: The pointer of THC private device context
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_dma_configure(struct thc_device *dev)
+{
+ struct thc_dma_context *dma_ctx = dev->dma_ctx;
+ int chan;
+
+ thc_reset_dma_settings(dev);
+
+ if (!dma_ctx) {
+ dev_err_once(dev->dev, "Cannot do DMA configure because DMA context is NULL\n");
+ return -EINVAL;
+ }
+
+ for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
+ dma_set_prd_base_addr(dev,
+ dma_ctx->dma_config[chan].prd_tbls_dma_handle,
+ &dma_ctx->dma_config[chan]);
+
+ dma_set_prd_control(dev, PRD_ENTRIES_NUM - 1,
+ dma_ctx->dma_config[chan].prd_tbl_num - 1,
+ &dma_ctx->dma_config[chan]);
+ }
+
+ /* Start read2 DMA engine */
+ dma_set_start_bit(dev, &dma_ctx->dma_config[THC_RXDMA2]);
+
+ dev_dbg(dev->dev, "DMA configured successfully!\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_configure, "INTEL_THC");
+
+/**
+ * thc_dma_unconfigure - Unconfigure DMA settings for all DMA engines
+ *
+ * @dev: The pointer of THC private device context
+ */
+void thc_dma_unconfigure(struct thc_device *dev)
+{
+ int chan;
+
+ for (chan = 0; chan < MAX_THC_DMA_CHANNEL; chan++) {
+ dma_set_prd_base_addr(dev, 0, &dev->dma_ctx->dma_config[chan]);
+ dma_clear_prd_control(dev, &dev->dma_ctx->dma_config[chan]);
+ }
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_1_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_START, 0);
+
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_READ_DMA_CNTRL_2_OFFSET,
+ THC_M_PRT_READ_DMA_CNTRL_START, 0);
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_unconfigure, "INTEL_THC");
+
+static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel channel)
+{
+ u32 ctrl_reg, sts_reg, sts;
+ int ret;
+
+ ctrl_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_CNTRL_1_OFFSET :
+ ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_CNTRL_2_OFFSET :
+ THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET);
+
+ regmap_write_bits(dev->thc_regmap, ctrl_reg, THC_M_PRT_READ_DMA_CNTRL_START, 0);
+
+ sts_reg = (channel == THC_RXDMA1) ? THC_M_PRT_READ_DMA_INT_STS_1_OFFSET :
+ ((channel == THC_RXDMA2) ? THC_M_PRT_READ_DMA_INT_STS_2_OFFSET :
+ THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET);
+
+ ret = regmap_read_poll_timeout(dev->thc_regmap, sts_reg, sts,
+ !(sts & THC_M_PRT_READ_DMA_INT_STS_ACTIVE),
+ THC_DEFAULT_RXDMA_POLLING_US_INTERVAL,
+ THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT);
+
+ if (ret) {
+ dev_err_once(dev->dev,
+ "Timeout while waiting for DMA %d stop\n", channel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int read_dma_buffer(struct thc_device *dev,
+ struct thc_dma_configuration *read_config,
+ u8 prd_table_index, void *read_buff)
+{
+ struct thc_prd_table *prd_tbl;
+ struct scatterlist *sg;
+ size_t mes_len, ret;
+ u8 nent;
+
+ if (prd_table_index >= read_config->prd_tbl_num) {
+ dev_err_once(dev->dev, "PRD table index %d too big\n", prd_table_index);
+ return -EINVAL;
+ }
+
+ prd_tbl = &read_config->prd_tbls[prd_table_index];
+ mes_len = calc_message_len(prd_tbl, &nent);
+ if (mes_len > read_config->max_packet_size) {
+ dev_err(dev->dev,
+ "Message length %zu is bigger than buffer length %lu\n",
+ mes_len, read_config->max_packet_size);
+ return -EMSGSIZE;
+ }
+
+ sg = read_config->sgls[prd_table_index];
+ ret = sg_copy_to_buffer(sg, nent, read_buff, mes_len);
+ if (ret != mes_len) {
+ dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
+ ret, mes_len);
+ return -EIO;
+ }
+
+ return mes_len;
+}
+
+static void update_write_pointer(struct thc_device *dev,
+ struct thc_dma_configuration *read_config)
+{
+ u8 write_ptr = dma_get_write_pointer(dev, read_config);
+
+ if (write_ptr + 1 == THC_WRAPAROUND_VALUE_ODD)
+ dma_set_write_pointer(dev, THC_POINTER_WRAPAROUND, read_config);
+ else if (write_ptr + 1 == THC_WRAPAROUND_VALUE_EVEN)
+ dma_set_write_pointer(dev, 0, read_config);
+ else
+ dma_set_write_pointer(dev, write_ptr + 1, read_config);
+}
+
+static int is_dma_buf_empty(struct thc_device *dev,
+ struct thc_dma_configuration *read_config,
+ u8 *read_ptr, u8 *write_ptr)
+{
+ *read_ptr = dma_get_read_pointer(dev, read_config);
+ *write_ptr = dma_get_write_pointer(dev, read_config);
+
+ if ((*read_ptr & THC_POINTER_MASK) == (*write_ptr & THC_POINTER_MASK))
+ if (*read_ptr != *write_ptr)
+ return true;
+
+ return false;
+}
+
+static int thc_dma_read(struct thc_device *dev,
+ struct thc_dma_configuration *read_config,
+ void *read_buff, size_t *read_len, int *read_finished)
+{
+ u8 read_ptr, write_ptr, prd_table_index;
+ int status;
+
+ if (!is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr)) {
+ prd_table_index = write_ptr & THC_POINTER_MASK;
+
+ status = read_dma_buffer(dev, read_config, prd_table_index, read_buff);
+ if (status <= 0) {
+ dev_err_once(dev->dev, "read DMA buffer failed %d\n", status);
+ return -EIO;
+ }
+
+ *read_len = status;
+
+ /* Clear the relevant PRD table */
+ thc_copy_one_sgl_to_prd(dev, read_config, prd_table_index);
+
+ /* Increment the write pointer to let the HW know we have processed this PRD */
+ update_write_pointer(dev, read_config);
+ }
+
+ /*
+ * This function only reads one frame from PRD table for each call, so we need to
+ * check if all DMAed data is read out and return the flag to the caller. Caller
+ * should repeatedly call thc_dma_read() until all DMAed data is handled.
+ */
+ if (read_finished)
+ *read_finished = is_dma_buf_empty(dev, read_config, &read_ptr, &write_ptr) ? 1 : 0;
+
+ return 0;
+}
+
+/**
+ * thc_rxdma_read - Read data from RXDMA buffer
+ *
+ * @dev: The pointer of THC private device context
+ * @dma_channel: The RXDMA engine of read data source
+ * @read_buff: The pointer of the read data buffer
+ * @read_len: The pointer of the read data length
+ * @read_finished: The pointer of the flag indicating if all pending data has been read out
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel,
+ void *read_buff, size_t *read_len, int *read_finished)
+{
+ struct thc_dma_configuration *dma_config;
+ int ret;
+
+ dma_config = &dev->dma_ctx->dma_config[dma_channel];
+
+ if (!dma_config->is_enabled) {
+ dev_err_once(dev->dev, "The DMA channel %d is not enabled", dma_channel);
+ return -EINVAL;
+ }
+
+ if (!read_buff || !read_len) {
+ dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
+ read_buff, read_len);
+ return -EINVAL;
+ }
+
+ if (dma_channel >= THC_TXDMA) {
+ dev_err(dev->dev, "Unsupported DMA channel for RxDMA read, %d\n", dma_channel);
+ return -EINVAL;
+ }
+
+ ret = thc_dma_read(dev, dma_config, read_buff, read_len, read_finished);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_rxdma_read, "INTEL_THC");
+
+static int thc_swdma_read_start(struct thc_device *dev, void *write_buff,
+ size_t write_len, u32 *prd_tbl_len)
+{
+ u32 mask, val, data0 = 0, data1 = 0;
+ int ret;
+
+ ret = thc_interrupt_quiesce(dev, true);
+ if (ret)
+ return ret;
+
+ if (thc_wait_for_dma_pause(dev, THC_RXDMA1) || thc_wait_for_dma_pause(dev, THC_RXDMA2))
+ return -EIO;
+
+ thc_reset_dma_settings(dev);
+
+ mask = THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC |
+ THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN;
+ val = FIELD_PREP(THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC, write_len) |
+ ((!prd_tbl_len) ? THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN : 0);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_RPRD_CNTRL_SW_OFFSET,
+ mask, val);
+
+ if (prd_tbl_len) {
+ mask = THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN;
+ val = FIELD_PREP(THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN,
+ *prd_tbl_len);
+ regmap_write_bits(dev->thc_regmap, THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET,
+ mask, val);
+ }
+
+ if (write_len <= sizeof(u32)) {
+ for (int i = 0; i < write_len; i++)
+ data0 |= *(((u8 *)write_buff) + i) << (i * 8);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);
+ } else if (write_len <= 2 * sizeof(u32)) {
+ data0 = *(u32 *)write_buff;
+ regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET, data0);
+
+ for (int i = 0; i < write_len - sizeof(u32); i++)
+ data1 |= *(((u8 *)write_buff) + sizeof(u32) + i) << (i * 8);
+
+ regmap_write(dev->thc_regmap, THC_M_PRT_SW_SEQ_DATA1_OFFSET, data1);
+ }
+ dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_SWDMA]);
+
+ return 0;
+}
+
+static int thc_swdma_read_completion(struct thc_device *dev)
+{
+ int ret;
+
+ ret = thc_wait_for_dma_pause(dev, THC_SWDMA);
+ if (ret)
+ return ret;
+
+ thc_reset_dma_settings(dev);
+
+ dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_RXDMA2]);
+
+ ret = thc_interrupt_quiesce(dev, false);
+
+ return ret;
+}
+
+/**
+ * thc_swdma_read - Use software DMA to read data from touch device
+ *
+ * @dev: The pointer of THC private device context
+ * @write_buff: The pointer of write buffer for SWDMA sequence
+ * @write_len: The write data length for SWDMA sequence
+ * @prd_tbl_len: The prd table length of SWDMA engine, can be set to NULL
+ * @read_buff: The pointer of the read data buffer
+ * @read_len: The pointer of the read data length
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len,
+ u32 *prd_tbl_len, void *read_buff, size_t *read_len)
+{
+ int ret;
+
+ if (!(&dev->dma_ctx->dma_config[THC_SWDMA])->is_enabled) {
+ dev_err_once(dev->dev, "The SWDMA channel is not enabled");
+ return -EINVAL;
+ }
+
+ if (!read_buff || !read_len) {
+ dev_err(dev->dev, "Invalid input parameters, read_buff %p, read_len %p\n",
+ read_buff, read_len);
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ dev->swdma_done = false;
+
+ ret = thc_swdma_read_start(dev, write_buff, write_len, prd_tbl_len);
+ if (ret)
+ goto end;
+
+ ret = wait_event_interruptible_timeout(dev->swdma_complete_wait, dev->swdma_done, 1 * HZ);
+ if (ret <= 0 || !dev->swdma_done) {
+ dev_err_once(dev->dev, "timeout for waiting SWDMA completion\n");
+ ret = -ETIMEDOUT;
+ goto end;
+ }
+
+ ret = thc_dma_read(dev, &dev->dma_ctx->dma_config[THC_SWDMA], read_buff, read_len, NULL);
+ if (ret)
+ goto end;
+
+ ret = thc_swdma_read_completion(dev);
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_swdma_read, "INTEL_THC");
+
+static int write_dma_buffer(struct thc_device *dev,
+ void *buffer, size_t buf_len)
+{
+ struct thc_dma_configuration *write_config = &dev->dma_ctx->dma_config[THC_TXDMA];
+ struct thc_prd_table *prd_tbl;
+ struct scatterlist *sg;
+ unsigned long len_left;
+ size_t ret;
+ u8 nent;
+ int i;
+
+ /* There is only one PRD table for write */
+ prd_tbl = &write_config->prd_tbls[0];
+
+ if (calc_prd_entries_num(prd_tbl, buf_len, &nent) < 0) {
+ dev_err(dev->dev, "Tx message length too big (%zu)\n", buf_len);
+ return -EOVERFLOW;
+ }
+
+ sg = write_config->sgls[0];
+ ret = sg_copy_from_buffer(sg, nent, buffer, buf_len);
+ if (ret != buf_len) {
+ dev_err_once(dev->dev, "Copied %zu bytes instead of requested %zu\n",
+ ret, buf_len);
+ return -EIO;
+ }
+
+ prd_tbl = &write_config->prd_tbls[0];
+ len_left = buf_len;
+
+ for_each_sg(write_config->sgls[0], sg, write_config->sgls_nent[0], i) {
+ if (sg_dma_address(sg) == 0 || sg_dma_len(sg) == 0) {
+ dev_err_once(dev->dev, "SGList: zero address or length\n");
+ return -EINVAL;
+ }
+
+ prd_tbl->entries[i].dest_addr =
+ sg_dma_address(sg) >> THC_ADDRESS_SHIFT;
+
+ if (len_left < sg_dma_len(sg)) {
+ prd_tbl->entries[i].len = len_left;
+ prd_tbl->entries[i].end_of_prd = 1;
+ break;
+ }
+
+ prd_tbl->entries[i].len = sg_dma_len(sg);
+ prd_tbl->entries[i].end_of_prd = 0;
+
+ len_left -= sg_dma_len(sg);
+ }
+
+ dma_set_prd_control(dev, i, 0, write_config);
+
+ return 0;
+}
+
+static void thc_ensure_performance_limitations(struct thc_device *dev)
+{
+ unsigned long delay_usec = 0;
+ /*
+ * Minimum amount of delay the THC / QUICKSPI driver must wait
+ * between end of write operation and begin of read operation.
+ * This value shall be in 10us multiples.
+ */
+ if (dev->perf_limit > 0) {
+ delay_usec = dev->perf_limit * 10;
+ udelay(delay_usec);
+ }
+}
+
+static void thc_dma_write_completion(struct thc_device *dev)
+{
+ thc_ensure_performance_limitations(dev);
+}
+
+/**
+ * thc_dma_write - Use TXDMA to write data to touch device
+ *
+ * @dev: The pointer of THC private device context
+ * @buffer: The pointer of write data buffer
+ * @buf_len: The write data length
+ *
+ * Return: 0 on success, other error codes on failed.
+ */
+int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len)
+{
+ bool restore_interrupts = false;
+ u32 sts, ctrl;
+ int ret;
+
+ if (!(&dev->dma_ctx->dma_config[THC_TXDMA])->is_enabled) {
+ dev_err_once(dev->dev, "The TxDMA channel is not enabled\n");
+ return -EINVAL;
+ }
+
+ if (!buffer || buf_len <= 0) {
+ dev_err(dev->dev, "Invalid input parameters, buffer %p\n, buf_len %zu\n",
+ buffer, buf_len);
+ return -EINVAL;
+ }
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_WRITE_INT_STS_OFFSET, &sts);
+ if (sts & THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE) {
+ dev_err_once(dev->dev, "THC TxDMA is till active and can't start again\n");
+ return -EBUSY;
+ }
+
+ if (mutex_lock_interruptible(&dev->thc_bus_lock))
+ return -EINTR;
+
+ regmap_read(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET, &ctrl);
+
+ ret = write_dma_buffer(dev, buffer, buf_len);
+ if (ret)
+ goto end;
+
+ if (dev->perf_limit && !(ctrl & THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS)) {
+ ret = thc_interrupt_quiesce(dev, true);
+ if (ret)
+ goto end;
+
+ restore_interrupts = true;
+ }
+
+ dev->write_done = false;
+
+ dma_set_start_bit(dev, &dev->dma_ctx->dma_config[THC_TXDMA]);
+
+ ret = wait_event_interruptible_timeout(dev->write_complete_wait, dev->write_done, 1 * HZ);
+ if (ret <= 0 || !dev->write_done) {
+ dev_err_once(dev->dev, "timeout for waiting TxDMA completion\n");
+ ret = -ETIMEDOUT;
+ goto end;
+ }
+
+ thc_dma_write_completion(dev);
+ mutex_unlock(&dev->thc_bus_lock);
+ return 0;
+
+end:
+ mutex_unlock(&dev->thc_bus_lock);
+
+ if (restore_interrupts)
+ ret = thc_interrupt_quiesce(dev, false);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(thc_dma_write, "INTEL_THC");
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h
new file mode 100644
index 000000000000..ca923ff2bef9
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _INTEL_THC_DMA_H_
+#define _INTEL_THC_DMA_H_
+
+#include <linux/bits.h>
+#include <linux/dma-mapping.h>
+#include <linux/sizes.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+
+#define THC_POINTER_MASK GENMASK(6, 0)
+#define THC_POINTER_WRAPAROUND 0x80
+#define THC_WRAPAROUND_VALUE_ODD 0x10
+#define THC_WRAPAROUND_VALUE_EVEN 0x90
+#define THC_MIN_BYTES_PER_SG_LIST_ENTRY SZ_4K
+
+#define THC_DEFAULT_RXDMA_POLLING_US_INTERVAL 100
+#define THC_DEFAULT_RXDMA_POLLING_US_TIMEOUT (10 * USEC_PER_MSEC)
+
+/*
+ * THC needs 1KB aligned address, dest_addr is 54 bits, not 64,
+ * so don't need to send the lower 10-bits of address.
+ */
+#define THC_ADDRESS_SHIFT 10
+
+/**
+ * THC DMA channels:
+ * @THC_RXDMA1: legacy channel, reserved for raw data reading
+ * @THC_RXDMA2: DMA to read HID data from touch device
+ * @THC_TXDMA: DMA to write to touch device
+ * @THC_SWDMA: SW triggered DMA to write and read from touch device
+ */
+enum thc_dma_channel {
+ THC_RXDMA1 = 0,
+ THC_RXDMA2 = 1,
+ THC_TXDMA = 2,
+ THC_SWDMA = 3,
+ MAX_THC_DMA_CHANNEL
+};
+
+/**
+ * THC DMA Physical Memory Descriptor (PRD)
+ * @dest_addr: bit[53:0], destination address in system memory
+ * @int_on_completion: bit[63], if set, thc will trigger interrupt to driver
+ * @len: bit[87:64], length of this entry
+ * @end_of_prd: bit[88], if set, this entry is last one of current PRD table
+ * @hw_status: bit[90:89], hw status bits
+ */
+struct thc_prd_entry {
+ u64 dest_addr : 54;
+ u64 reserved1 : 9;
+ u64 int_on_completion : 1;
+ u64 len : 24;
+ u64 end_of_prd : 1;
+ u64 hw_status : 2;
+ u64 reserved2 : 37;
+};
+
+/*
+ * Max OS memory fragmentation will be at a 4KB boundary, thus to address 1MB
+ * of virtually contiguous memory 256 PRD entries are required for a single
+ * PRD Table. SW writes the number of PRD Entries for each PRD table in the
+ * THC_M_PRT_RPRD_CNTRL.PTEC register field. The PRD entry's length must be
+ * multiple of 4KB except for the last entry in a PRD table.
+ * This is the max possible number of etries supported by HW, in practise we
+ * there will be less entries in each prd table(the actual number will be
+ * given by scatter-gather list allocation).
+ */
+#define PRD_ENTRIES_NUM 16
+
+/*
+ * Number of PRD tables equals to number of data buffers.
+ * The max number of PRD tables supported by the HW is 128,
+ * but we allocate only 16.
+ */
+#define PRD_TABLES_NUM 16
+
+/* THC DMA Physical Memory Descriptor Table */
+struct thc_prd_table {
+ struct thc_prd_entry entries[PRD_ENTRIES_NUM];
+};
+
+#define PRD_TABLE_SIZE sizeof(struct thc_prd_table)
+
+/**
+ * struct thc_dma_configuration - THC DMA configure
+ * @dma_channel: DMA channel for current DMA configuration
+ * @prd_tbls_dma_handle: DMA buffer handle
+ * @dir: direction of DMA for this config
+ * @prd_tbls: PRD tables for current DMA
+ * @sgls: array of pointers to scatter-gather lists
+ * @sgls_nent: actual number of entries per sg list
+ * @prd_tbl_num: actual number of PRD tables
+ * @max_packet_size: size of the buffer needed for 1 DMA message (1 PRD table)
+ * @prd_base_addr_high: High 32bits memory address where stores PRD table
+ * @prd_base_addr_low: low 32bits memory address where stores PRD table
+ * @prd_cntrl: PRD control register value
+ * @dma_cntrl: DMA control register value
+ */
+struct thc_dma_configuration {
+ enum thc_dma_channel dma_channel;
+ dma_addr_t prd_tbls_dma_handle;
+ enum dma_data_direction dir;
+ bool is_enabled;
+
+ struct thc_prd_table *prd_tbls;
+ struct scatterlist *sgls[PRD_TABLES_NUM];
+ u8 sgls_nent[PRD_TABLES_NUM];
+ u8 prd_tbl_num;
+
+ size_t max_packet_size;
+ u32 prd_base_addr_high;
+ u32 prd_base_addr_low;
+ u32 prd_cntrl;
+ u32 dma_cntrl;
+};
+
+/*
+ * THC DMA context
+ * Store all THC Channel configures
+ */
+struct thc_dma_context {
+ struct thc_dma_configuration dma_config[MAX_THC_DMA_CHANNEL];
+ u8 use_write_interrupts;
+};
+
+struct thc_device;
+
+int thc_dma_set_max_packet_sizes(struct thc_device *dev,
+ size_t mps_read1, size_t mps_read2,
+ size_t mps_write, size_t mps_swdma);
+int thc_dma_allocate(struct thc_device *dev);
+int thc_dma_configure(struct thc_device *dev);
+void thc_dma_unconfigure(struct thc_device *dev);
+void thc_dma_release(struct thc_device *dev);
+int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel,
+ void *read_buff, size_t *read_len, int *read_finished);
+int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len,
+ u32 *prd_tbl_len, void *read_buff, size_t *read_len);
+int thc_dma_write(struct thc_device *dev, void *buffer, size_t buf_len);
+
+struct thc_dma_context *thc_dma_init(struct thc_device *dev);
+
+#endif /* _INTEL_THC_DMA_H_ */
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h
new file mode 100644
index 000000000000..6729c4c25dab
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h
@@ -0,0 +1,881 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#ifndef _INTEL_THC_HW_H_
+#define _INTEL_THC_HW_H_
+
+#include <linux/bits.h>
+
+/* THC registers offset */
+/* Touch Host Controller Control Register */
+#define THC_M_PRT_CONTROL_OFFSET 0x1008
+/* THC SPI Bus Configuration Register */
+#define THC_M_PRT_SPI_CFG_OFFSET 0x1010
+/* THC SPI Bus Read Opcode Register */
+#define THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET 0x1014
+/* THC SPI Bus Read Opcode Register */
+#define THC_M_PRT_SPI_DMARD_OPCODE_OFFSET 0x1018
+/* THC SPI Bus Write Opcode Register */
+#define THC_M_PRT_SPI_WR_OPCODE_OFFSET 0x101C
+/* THC Interrupt Enable Register */
+#define THC_M_PRT_INT_EN_OFFSET 0x1020
+/* THC Interrupt Status Register */
+#define THC_M_PRT_INT_STATUS_OFFSET 0x1024
+/* THC Error Cause Register */
+#define THC_M_PRT_ERR_CAUSE_OFFSET 0x1028
+/* THC SW sequencing Control */
+#define THC_M_PRT_SW_SEQ_CNTRL_OFFSET 0x1040
+/* THC SW sequencing Status */
+#define THC_M_PRT_SW_SEQ_STS_OFFSET 0x1044
+/* THC SW Sequencing Data DW0 or SPI Address Register */
+#define THC_M_PRT_SW_SEQ_DATA0_ADDR_OFFSET 0x1048
+/* THC SW sequencing Data DW1 */
+#define THC_M_PRT_SW_SEQ_DATA1_OFFSET 0x104C
+/* THC SW sequencing Data DW2 */
+#define THC_M_PRT_SW_SEQ_DATA2_OFFSET 0x1050
+/* THC SW sequencing Data DW3 */
+#define THC_M_PRT_SW_SEQ_DATA3_OFFSET 0x1054
+/* THC SW sequencing Data DW4 */
+#define THC_M_PRT_SW_SEQ_DATA4_OFFSET 0x1058
+/* THC SW sequencing Data DW5 */
+#define THC_M_PRT_SW_SEQ_DATA5_OFFSET 0x105C
+/* THC SW sequencing Data DW6 */
+#define THC_M_PRT_SW_SEQ_DATA6_OFFSET 0x1060
+/* THC SW sequencing Data DW7 */
+#define THC_M_PRT_SW_SEQ_DATA7_OFFSET 0x1064
+/* THC SW sequencing Data DW8 */
+#define THC_M_PRT_SW_SEQ_DATA8_OFFSET 0x1068
+/* THC SW sequencing Data DW9 */
+#define THC_M_PRT_SW_SEQ_DATA9_OFFSET 0x106C
+/* THC SW sequencing Data DW10 */
+#define THC_M_PRT_SW_SEQ_DATA10_OFFSET 0x1070
+/* THC SW sequencing Data DW11 */
+#define THC_M_PRT_SW_SEQ_DATA11_OFFSET 0x1074
+/* THC SW sequencing Data DW12 */
+#define THC_M_PRT_SW_SEQ_DATA12_OFFSET 0x1078
+/* THC SW sequencing Data DW13 */
+#define THC_M_PRT_SW_SEQ_DATA13_OFFSET 0x107C
+/* THC SW sequencing Data DW14 */
+#define THC_M_PRT_SW_SEQ_DATA14_OFFSET 0x1080
+/* THC SW sequencing Data DW15 */
+#define THC_M_PRT_SW_SEQ_DATA15_OFFSET 0x1084
+/* THC SW sequencing Data DW16 */
+#define THC_M_PRT_SW_SEQ_DATA16_OFFSET 0x1088
+/* THC Write PRD Base Address Register Low */
+#define THC_M_PRT_WPRD_BA_LOW_OFFSET 0x1090
+/* THC Write PRD Base Address Register High */
+#define THC_M_PRT_WPRD_BA_HI_OFFSET 0x1094
+/* THC Write DMA Control */
+#define THC_M_PRT_WRITE_DMA_CNTRL_OFFSET 0x1098
+/* THC Write Interrupt Status */
+#define THC_M_PRT_WRITE_INT_STS_OFFSET 0x109C
+/* THC Write DMA Error Register */
+#define THC_M_PRT_WRITE_DMA_ERR_OFFSET 0x10A0
+/* THC device address for the bulk write */
+#define THC_M_PRT_WR_BULK_ADDR_OFFSET 0x10B4
+/* THC Device Interrupt Cause Register Address */
+#define THC_M_PRT_DEV_INT_CAUSE_ADDR_OFFSET 0x10B8
+/* THC Device Interrupt Cause Register Value */
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_OFFSET 0x10BC
+/* THC TXDMA Frame Count */
+#define THC_M_PRT_TX_FRM_CNT_OFFSET 0x10E0
+/* THC TXDMA Packet Count */
+#define THC_M_PRT_TXDMA_PKT_CNT_OFFSET 0x10E4
+/* THC Device Interrupt Count on this port */
+#define THC_M_PRT_DEVINT_CNT_OFFSET 0x10E8
+/* Touch Device Interrupt Cause register Format Configuration Register 1 */
+#define THC_M_PRT_DEVINT_CFG_1_OFFSET 0x10EC
+/* Touch Device Interrupt Cause register Format Configuration Register 2 */
+#define THC_M_PRT_DEVINT_CFG_2_OFFSET 0x10F0
+/* THC Read PRD Base Address Low for the 1st RXDMA */
+#define THC_M_PRT_RPRD_BA_LOW_1_OFFSET 0x1100
+/* THC Read PRD Base Address High for the 1st RXDMA */
+#define THC_M_PRT_RPRD_BA_HI_1_OFFSET 0x1104
+/* THC Read PRD Control for the 1st RXDMA */
+#define THC_M_PRT_RPRD_CNTRL_1_OFFSET 0x1108
+/* THC Read DMA Control for the 1st RXDMA */
+#define THC_M_PRT_READ_DMA_CNTRL_1_OFFSET 0x110C
+/* THC Read Interrupt Status for the 1st RXDMA */
+#define THC_M_PRT_READ_DMA_INT_STS_1_OFFSET 0x1110
+/* THC Read DMA Error Register for the 1st RXDMA */
+#define THC_M_PRT_READ_DMA_ERR_1_OFFSET 0x1114
+/* Touch Sequencer GuC Tail Offset Address Low for the 1st RXDMA */
+#define THC_M_PRT_GUC_OFFSET_LOW_1_OFFSET 0x1118
+/* Touch Sequencer GuC Tail Offset Address High for the 1st RXDMA */
+#define THC_M_PRT_GUC_OFFSET_HI_1_OFFSET 0x111C
+/* Touch Host Controller GuC Work Queue Item Size for the 1st RXDMA */
+#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_1_OFFSET 0x1120
+/* Touch Host Controller GuC Control register for the 1st RXDMA */
+#define THC_M_PRT_GUC_WORKQ_SZ_1_OFFSET 0x1124
+/* Touch Sequencer Control for the 1st DMA */
+#define THC_M_PRT_TSEQ_CNTRL_1_OFFSET 0x1128
+/* Touch Sequencer GuC Doorbell Address Low for the 1st RXDMA */
+#define THC_M_PRT_GUC_DB_ADDR_LOW_1_OFFSET 0x1130
+/* Touch Sequencer GuC Doorbell Address High for the 1st RXDMA */
+#define THC_M_PRT_GUC_DB_ADDR_HI_1_OFFSET 0x1134
+/* Touch Sequencer GuC Doorbell Data */
+#define THC_M_PRT_GUC_DB_DATA_1_OFFSET 0x1138
+/* Touch Sequencer GuC Tail Offset Initial Value for the 1st RXDMA */
+#define THC_M_PRT_GUC_OFFSET_INITVAL_1_OFFSET 0x1140
+/* THC Device Address for the bulk/touch data read for the 1st RXDMA */
+#define THC_M_PRT_RD_BULK_ADDR_1_OFFSET 0x1170
+/* THC Gfx/SW Doorbell Count from the 1st Stream RXDMA on this port */
+#define THC_M_PRT_DB_CNT_1_OFFSET 0x11A0
+/* THC Frame Count from the 1st Stream RXDMA on this port */
+#define THC_M_PRT_FRM_CNT_1_OFFSET 0x11A4
+/* THC Micro Frame Count from the 1st Stream RXDMA on this port */
+#define THC_M_PRT_UFRM_CNT_1_OFFSET 0x11A8
+/* THC Packet Count from the 1st Stream RXDMA on this port */
+#define THC_M_PRT_RXDMA_PKT_CNT_1_OFFSET 0x11AC
+/*
+ * THC Software Interrupt Count from the 1st Stream RXDMA
+ * on this port
+ */
+#define THC_M_PRT_SWINT_CNT_1_OFFSET 0x11B0
+/* Touch Sequencer Frame Drop Counter for the 1st RXDMA */
+#define THC_M_PRT_FRAME_DROP_CNT_1_OFFSET 0x11B4
+/* THC Coaescing 1 */
+#define THC_M_PRT_COALESCE_1_OFFSET 0x11B8
+/* THC Read PRD Base Address Low for the 2nd RXDMA */
+#define THC_M_PRT_RPRD_BA_LOW_2_OFFSET 0x1200
+/* THC Read PRD Base Address High for the 2nd RXDMA */
+#define THC_M_PRT_RPRD_BA_HI_2_OFFSET 0x1204
+/* THC Read PRD Control for the 2nd RXDMA */
+#define THC_M_PRT_RPRD_CNTRL_2_OFFSET 0x1208
+/* THC Read DMA Control for the 2nd RXDMA */
+#define THC_M_PRT_READ_DMA_CNTRL_2_OFFSET 0x120C
+/* THC Read Interrupt Status for the 2nd RXDMA */
+#define THC_M_PRT_READ_DMA_INT_STS_2_OFFSET 0x1210
+/* THC Read DMA Error Register for the 2nd RXDMA */
+#define THC_M_PRT_READ_DMA_ERR_2_OFFSET 0x1214
+/* Touch Sequencer GuC Tail Offset Address Low for the 2nd RXDMA */
+#define THC_M_PRT_GUC_OFFSET_LOW_2_OFFSET 0x1218
+/* Touch Sequencer GuC Tail Offset Address High for the 2nd RXDMA */
+#define THC_M_PRT_GUC_OFFSET_HI_2_OFFSET 0x121C
+/* Touch Host Controller GuC Work Queue Item Size for the 2nd RXDMA */
+#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_2_OFFSET 0x1220
+/* Touch Host Controller GuC Control register for the 2nd RXDMA */
+#define THC_M_PRT_GUC_WORKQ_SZ_2_OFFSET 0x1224
+/* Touch Sequencer Control for the 2nd DMA */
+#define THC_M_PRT_TSEQ_CNTRL_2_OFFSET 0x1228
+/* Touch Sequencer GuC Doorbell Address Low for the 2nd RXDMA */
+#define THC_M_PRT_GUC_DB_ADDR_LOW_2_OFFSET 0x1230
+/* Touch Sequencer GuC Doorbell Address High for the 2nd RXDMA */
+#define THC_M_PRT_GUC_DB_ADDR_HI_2_OFFSET 0x1234
+/* Touch Sequencer GuC Doorbell Data for PRD2 */
+#define THC_M_PRT_GUC_DB_DATA_2_OFFSET 0x1238
+/* Touch Sequencer GuC Tail Offset Initial Value for the 2nd RXDMA */
+#define THC_M_PRT_GUC_OFFSET_INITVAL_2_OFFSET 0x1240
+/* THC Device Address for the bulk/touch data read for the 2nd RXDMA */
+#define THC_M_PRT_RD_BULK_ADDR_2_OFFSET 0x1270
+/* THC Gfx/SW Doorbell Count from the 2nd Stream RXDMA on this port */
+#define THC_M_PRT_DB_CNT_2_OFFSET 0x12A0
+/* THC Frame Count from the 2nd Stream RXDMA on this port */
+#define THC_M_PRT_FRM_CNT_2_OFFSET 0x12A4
+/* THC Micro Frame Count from the 2nd Stream RXDMA on this port */
+#define THC_M_PRT_UFRM_CNT_2_OFFSET 0x12A8
+/* THC Packet Count from the 2nd Stream RXDMA on this port */
+#define THC_M_PRT_RXDMA_PKT_CNT_2_OFFSET 0x12AC
+/*
+ * THC Software Interrupt Count from the 2nd Stream RXDMA
+ * on this port
+ */
+#define THC_M_PRT_SWINT_CNT_2_OFFSET 0x12B0
+/* Touch Sequencer Frame Drop Counter for the 2nd RXDMA */
+#define THC_M_PRT_FRAME_DROP_CNT_2_OFFSET 0x12B4
+/* THC Coaescing 2 */
+#define THC_M_PRT_COALESCE_2_OFFSET 0x12B8
+/* THC SPARE REGISTER */
+#define THC_M_PRT_SPARE_REG_OFFSET 0x12BC
+/* THC Read PRD Base Address Low for the SW RXDMA */
+#define THC_M_PRT_RPRD_BA_LOW_SW_OFFSET 0x12C0
+/* THC Read PRD Base Address High for the SW RXDMA */
+#define THC_M_PRT_RPRD_BA_HI_SW_OFFSET 0x12C4
+/* THC Read PRD Control for the SW RXDMA */
+#define THC_M_PRT_RPRD_CNTRL_SW_OFFSET 0x12C8
+/* THC Read DMA Control for the SW RXDMA */
+#define THC_M_PRT_READ_DMA_CNTRL_SW_OFFSET 0x12CC
+/* THC Read Interrupt Status for the SW RXDMA */
+#define THC_M_PRT_READ_DMA_INT_STS_SW_OFFSET 0x12D0
+/* Touch Sequencer Control for the SW DMA */
+#define THC_M_PRT_TSEQ_CNTRL_SW_OFFSET 0x12D4
+/* Address for the bulk read for SW DMA engine */
+#define THC_M_PRT_RD_BULK_ADDR_SW_OFFSET 0x12D8
+/* THC Frame Count from the SW RXDMA on this port */
+#define THC_M_PRT_FRM_CNT_SW_OFFSET 0x12DC
+/* THC Packet Count from the SW RXDMA on this port */
+#define THC_M_PRT_RXDMA_PKT_CNT_SW_OFFSET 0x12E0
+/* SW DMA PRD Table Length */
+#define THC_M_PRT_SW_DMA_PRD_TABLE_LEN_OFFSET 0x12E4
+/* THC timing based Frame/Interrupt caolescing control register for 1st RXDMA */
+#define THC_M_PRT_COALESCE_CNTRL_1_OFFSET 0x12E8
+/* THC timing based Frame/Interrupt caolescing control register for 2nd RXDMA */
+#define THC_M_PRT_COALESCE_CNTRL_2_OFFSET 0x12EC
+/* Touch Sequencer PRD Table Empty Counter for the 1st RXDMA */
+#define THC_M_PRT_PRD_EMPTY_CNT_1_OFFSET 0x12F0
+/* Touch Sequencer PRD Table Empty Counter for the 2nd RXDM */
+#define THC_M_PRT_PRD_EMPTY_CNT_2_OFFSET 0x12F4
+/* THC coalescing status to reflect the current coalescing FSM state for 1st RXDMA */
+#define THC_M_PRT_COALESCE_STS_1_OFFSET 0x12F8
+/* THC coalescing status to reflect the current coalescing FSM state for 2nd RXDMA */
+#define THC_M_PRT_COALESCE_STS_2_OFFSET 0x12FC
+/* THC Register for the SPI Port Duty Cycle Configuration */
+#define THC_M_PRT_SPI_DUTYC_CFG_OFFSET 0x1300
+/* THC Register for SW I2C Wtite Sequecning control */
+#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_OFFSET 0x1304
+/* THC current Timestamp Register for RXDMA1 */
+#define THC_M_PRT_TIMESTAMP_1_OFFSET 0x1308
+/* THC current Timestamp Register for RXDMA2 */
+#define THC_M_PRT_TIMESTAMP_2_OFFSET 0x130C
+/* Current SYNC Event Timestamp Register */
+#define THC_M_PRT_SYNC_TIMESTAMP_OFFSET 0x1310
+/* THC Display Sync Register */
+#define THC_M_PRT_DISP_SYNC_OFFSET 0x1314
+/* THC Display Sync Register */
+#define THC_M_PRT_DISP_SYNC_2_OFFSET 0x1318
+/* THC Register for SW I2C Wtite Sequecning control */
+#define THC_M_PRT_I2C_CFG_OFFSET 0x131C
+
+/* THC register bits definition */
+#define TXN_ERR_INT_STS_BIT BIT(28)
+#define TXN_FATAL_INT_STS_BIT BIT(30)
+
+#define NONDMA_INT_STS_BIT BIT(4)
+#define EOF_INT_STS_BIT BIT(5)
+
+#define THC_CFG_DID_VID_VID GENMASK(15, 0)
+#define THC_CFG_DID_VID_DID GENMASK(31, 16)
+
+#define THC_CFG_STS_CMD_IOSE BIT(0)
+#define THC_CFG_STS_CMD_MSE BIT(1)
+#define THC_CFG_STS_CMD_BME BIT(2)
+#define THC_CFG_STS_CMD_SPCYC BIT(3)
+#define THC_CFG_STS_CMD_MWRIEN BIT(4)
+#define THC_CFG_STS_CMD_VGAPS BIT(5)
+#define THC_CFG_STS_CMD_PERRR BIT(6)
+#define THC_CFG_STS_CMD_SERREN BIT(8)
+#define THC_CFG_STS_CMD_FBTBEN BIT(9)
+#define THC_CFG_STS_CMD_INTD BIT(10)
+#define THC_CFG_STS_CMD_INTS BIT(19)
+#define THC_CFG_STS_CMD_CAPL BIT(20)
+#define THC_CFG_STS_CMD_MCAP BIT(21)
+#define THC_CFG_STS_CMD_FBTBC BIT(23)
+#define THC_CFG_STS_CMD_MDPE BIT(24)
+#define THC_CFG_STS_CMD_DEVT GENMASK(26, 25)
+#define THC_CFG_STS_CMD_STA BIT(27)
+#define THC_CFG_STS_CMD_RTA BIT(28)
+#define THC_CFG_STS_CMD_RMA BIT(29)
+#define THC_CFG_STS_CMD_SSE BIT(30)
+#define THC_CFG_STS_CMD_DPE BIT(31)
+
+#define THC_CFG_CC_RID_RID GENMASK(7, 0)
+#define THC_CFG_CC_RID_PI GENMASK(15, 8)
+#define THC_CFG_CC_RID_SCC GENMASK(23, 16)
+#define THC_CFG_CC_RID_BCC GENMASK(31, 24)
+
+#define THC_CFG_BIST_HTYPE_LT_CLS_CLSZ GENMASK(7, 0)
+#define THC_CFG_BIST_HTYPE_LT_CLS_LT GENMASK(15, 8)
+#define THC_CFG_BIST_HTYPE_LT_CLS_HTYPE GENMASK(22, 16)
+#define THC_CFG_BIST_HTYPE_LT_CLS_MFD BIT(23)
+
+#define THC_CFG_BAR0_LOW_MEMSPACE BIT(0)
+#define THC_CFG_BAR0_LOW_TYP GENMASK(2, 1)
+#define THC_CFG_BAR0_LOW_PREFETCH BIT(3)
+#define THC_CFG_BAR0_LOW_MEMSIZE GENMASK(14, 4)
+#define THC_CFG_BAR0_LOW_MEMBAR GENMASK(31, 15)
+#define THC_CFG_BAR0_HI_MEMBAR GENMASK(31, 0)
+
+#define THC_CFG_SID_SVID_SSVID GENMASK(15, 0)
+#define THC_CFG_SID_SVID_SSID GENMASK(31, 16)
+
+#define THC_CFG_CAPP_CP GENMASK(7, 0)
+
+#define THC_CFG_INT_ILINE GENMASK(7, 0)
+#define THC_CFG_INT_IPIN GENMASK(15, 8)
+
+#define THC_CFG_UR_STS_CTL_URRE BIT(0)
+#define THC_CFG_UR_STS_CTL_URD BIT(1)
+#define THC_CFG_UR_STS_CTL_FD BIT(2)
+
+#define THC_CFG_MSIMC_MSINP_MSICID_CAPID GENMASK(7, 0)
+#define THC_CFG_MSIMC_MSINP_MSICID_NXTP GENMASK(15, 8)
+#define THC_CFG_MSIMC_MSINP_MSICID_MSIE BIT(16)
+#define THC_CFG_MSIMC_MSINP_MSICID_MMC GENMASK(19, 17)
+#define THC_CFG_MSIMC_MSINP_MSICID_MMEN GENMASK(22, 20)
+#define THC_CFG_MSIMC_MSINP_MSICID_XAC BIT(23)
+#define THC_CFG_MSIMC_MSINP_MSICID_PVMC BIT(24)
+#define THC_CFG_MSIMA_MADDR GENMASK(31, 2)
+#define THC_CFG_MSIMUA_MAUDDR GENMASK(31, 0)
+#define THC_CFG_MSIMD_MDAT GENMASK(15, 0)
+
+#define THC_CFG_PMCAP_PMNP_PMCID_CAPP GENMASK(7, 0)
+#define THC_CFG_PMCAP_PMNP_PMCID_NXTP GENMASK(15, 8)
+#define THC_CFG_PMCAP_PMNP_PMCID_VER GENMASK(18, 16)
+#define THC_CFG_PMCAP_PMNP_PMCID_PMECLK BIT(19)
+#define THC_CFG_PMCAP_PMNP_PMCID_DSI BIT(21)
+#define THC_CFG_PMCAP_PMNP_PMCID_AUXC GENMASK(24, 22)
+#define THC_CFG_PMCAP_PMNP_PMCID_D1S BIT(25)
+#define THC_CFG_PMCAP_PMNP_PMCID_D2S BIT(26)
+#define THC_CFG_PMCAP_PMNP_PMCID_PMES GENMASK(31, 27)
+
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_PWRST GENMASK(1, 0)
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_NSR BIT(3)
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_PMEEN BIT(8)
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_DSEL GENMASK(12, 9)
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_DS GENMASK(14, 13)
+#define THC_CFG_PMD_PMCSRBSE_PMCSR_PMESTS BIT(15)
+
+#define THC_CFG_DEVIDLE_CAPPID GENMASK(7, 0)
+#define THC_CFG_DEVIDLE_NCAPPP GENMASK(15, 8)
+#define THC_CFG_DEVIDLE_LENGTH GENMASK(23, 16)
+#define THC_CFG_DEVIDLE_REV GENMASK(27, 24)
+#define THC_CFG_DEVIDLE_VID GENMASK(31, 28)
+
+#define THC_CFG_VSHDR_VSECID GENMASK(15, 0)
+#define THC_CFG_VSHDR_VSECR GENMASK(19, 16)
+#define THC_CFG_VSHDR_VSECL GENMASK(31, 20)
+
+#define THC_CFG_SWLTRPTR_VALID BIT(0)
+#define THC_CFG_SWLTRPTR_BARNUM GENMASK(3, 1)
+#define THC_CFG_SWLTRPTR_SWLTRLOC GENMASK(31, 4)
+
+#define THC_CFG_DEVIDLEPTR_VALID BIT(0)
+#define THC_CFG_DEVIDLEPTR_BARNUM GENMASK(3, 1)
+#define THC_CFG_DEVIDLEPTR_DEVIDLELOC GENMASK(31, 4)
+#define THC_CFG_DEVIDLEPOL_POLV GENMASK(9, 0)
+#define THC_CFG_DEVIDLEPOL_POLS GENMASK(12, 10)
+
+#define THC_CFG_PCE_SPE BIT(0)
+#define THC_CFG_PCE_I3E BIT(1)
+#define THC_CFG_PCE_D3HE BIT(2)
+#define THC_CFG_PCE_SE BIT(3)
+#define THC_CFG_PCE_HAE BIT(5)
+
+#define THC_CFG_MANID_PROC GENMASK(7, 0)
+#define THC_CFG_MANID_MID GENMASK(15, 8)
+#define THC_CFG_MANID_MSID GENMASK(23, 16)
+#define THC_CFG_MANID_DOT GENMASK(27, 24)
+
+#define THC_M_CMN_DEVIDLECTRL_CIP BIT(0)
+#define THC_M_CMN_DEVIDLECTRL_IR BIT(1)
+#define THC_M_CMN_DEVIDLECTRL_DEVIDLE BIT(2)
+#define THC_M_CMN_DEVIDLECTRL_RR BIT(3)
+#define THC_M_CMN_DEVIDLECTRL_IRC BIT(4)
+
+#define THC_M_CMN_LTR_CTRL_OFFSET 0x14
+#define THC_M_CMN_LTR_CTRL_ACTIVE_LTR_REQ BIT(0)
+#define THC_M_CMN_LTR_CTRL_ACTIVE_LTR_EN BIT(1)
+#define THC_M_CMN_LTR_CTRL_LP_LTR_REQ BIT(2)
+#define THC_M_CMN_LTR_CTRL_LP_LTR_EN BIT(3)
+#define THC_M_CMN_LTR_CTRL_LP_LTR_SCALE GENMASK(6, 4)
+#define THC_M_CMN_LTR_CTRL_LP_LTR_VAL GENMASK(16, 7)
+#define THC_M_CMN_LTR_CTRL_ACT_LTR_SCALE GENMASK(19, 17)
+#define THC_M_CMN_LTR_CTRL_ACT_LTR_VAL GENMASK(29, 20)
+#define THC_M_CMN_LTR_CTRL_LAST_LTR_SENT GENMASK(31, 30)
+
+#define THC_M_PRT_CONTROL_TSFTRST BIT(0)
+#define THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN BIT(1)
+#define THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS BIT(2)
+#define THC_M_PRT_CONTROL_DEVRST BIT(3)
+#define THC_M_PRT_CONTROL_THC_DRV_LOCK_EN BIT(13)
+#define THC_M_PRT_CONTROL_THC_INSTANCE_INDEX GENMASK(18, 16)
+#define THC_M_PRT_CONTROL_PORT_INDEX GENMASK(22, 20)
+#define THC_M_PRT_CONTROL_THC_ARB_POLICY GENMASK(25, 24)
+#define THC_M_PRT_CONTROL_THC_BIOS_LOCK_EN BIT(27)
+#define THC_M_PRT_CONTROL_PORT_SUPPORTED BIT(28)
+#define THC_M_PRT_CONTROL_SPI_IO_RDY BIT(29)
+#define THC_M_PRT_CONTROL_PORT_TYPE GENMASK(31, 30)
+
+#define THC_M_PRT_SPI_CFG_SPI_TRDC GENMASK(1, 0)
+#define THC_M_PRT_SPI_CFG_SPI_TRMODE GENMASK(3, 2)
+#define THC_M_PRT_SPI_CFG_SPI_TCRF GENMASK(6, 4)
+#define THC_M_PRT_SPI_CFG_SPI_RD_MPS GENMASK(15, 7)
+#define THC_M_PRT_SPI_CFG_SPI_TWMODE GENMASK(19, 18)
+#define THC_M_PRT_SPI_CFG_SPI_TCWF GENMASK(22, 20)
+#define THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN BIT(23)
+#define THC_M_PRT_SPI_CFG_SPI_WR_MPS GENMASK(31, 24)
+
+#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_SIO GENMASK(31, 24)
+#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_DIO GENMASK(23, 16)
+#define THC_M_PRT_SPI_ICRRD_OPCODE_SPI_QIO GENMASK(15, 8)
+
+#define THC_M_PRT_INT_EN_SIPE BIT(0)
+#define THC_M_PRT_INT_EN_SBO BIT(1)
+#define THC_M_PRT_INT_EN_SIDR BIT(2)
+#define THC_M_PRT_INT_EN_SOFB BIT(3)
+#define THC_M_PRT_INT_EN_INVLD_DEV_ENTRY_INT_EN BIT(9)
+#define THC_M_PRT_INT_EN_FRAME_BABBLE_ERR_INT_EN BIT(10)
+#define THC_M_PRT_INT_EN_BUF_OVRRUN_ERR_INT_EN BIT(12)
+#define THC_M_PRT_INT_EN_PRD_ENTRY_ERR_INT_EN BIT(13)
+#define THC_M_PRT_INT_EN_DISP_SYNC_EVT_INT_EN BIT(14)
+#define THC_M_PRT_INT_EN_DEV_RAW_INT_EN BIT(15)
+#define THC_M_PRT_INT_EN_FATAL_ERR_INT_EN BIT(16)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_UNDER_INT_EN BIT(17)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_OVER_INT_EN BIT(18)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_RX_FULL_INT_EN BIT(19)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_OVER_INT_EN BIT(20)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_EMPTY_INT_EN BIT(21)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_TX_ABRT_INT_EN BIT(22)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_SCL_STUCK_AT_LOW_DET_INT_EN BIT(24)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_STOP_DET_INT_EN BIT(25)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_START_DET_INT_EN BIT(26)
+#define THC_M_PRT_INT_EN_THC_I2C_IC_MST_ON_HOLD_INT_EN BIT(27)
+#define THC_M_PRT_INT_EN_TXN_ERR_INT_EN BIT(29)
+#define THC_M_PRT_INT_EN_GBL_INT_EN BIT(31)
+
+#define THC_M_PRT_INT_STATUS_DISP_SYNC_EVT_INT_STS BIT(14)
+#define THC_M_PRT_INT_STATUS_DEV_RAW_INT_STS BIT(15)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_UNDER_INT_STS BIT(17)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_OVER_INT_STS BIT(18)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_RX_FULL_INT_STS BIT(19)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_OVER_INT_STS BIT(20)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_EMPTY_INT_STS BIT(21)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_TX_ABRT_INT_STS BIT(22)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_ACTIVITY_INT_STS BIT(23)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_SCL_STUCK_AT_LOW_INT_STS BIT(24)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_STOP_DET_INT_STS BIT(25)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_START_DET_INT_STS BIT(26)
+#define THC_M_PRT_INT_STATUS_THC_I2C_IC_MST_ON_HOLD_INT_STS BIT(27)
+#define THC_M_PRT_INT_STATUS_TXN_ERR_INT_STS BIT(28)
+#define THC_M_PRT_INT_STATUS_FATAL_ERR_INT_STS BIT(30)
+
+#define THC_M_PRT_ERR_CAUSE_INVLD_DEV_ENTRY BIT(9)
+#define THC_M_PRT_ERR_CAUSE_FRAME_BABBLE_ERR BIT(10)
+#define THC_M_PRT_ERR_CAUSE_BUF_OVRRUN_ERR BIT(12)
+#define THC_M_PRT_ERR_CAUSE_PRD_ENTRY_ERR BIT(13)
+#define THC_M_PRT_ERR_CAUSE_FATAL_ERR_CAUSE GENMASK(23, 16)
+
+#define THC_M_PRT_SW_SEQ_CNTRL_TSSGO BIT(0)
+#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CD_IE BIT(1)
+#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_CMD GENMASK(15, 8)
+#define THC_M_PRT_SW_SEQ_CNTRL_THC_SS_BC GENMASK(31, 16)
+#define THC_M_PRT_SW_SEQ_STS_TSSDONE BIT(0)
+#define THC_M_PRT_SW_SEQ_STS_THC_SS_ERR BIT(1)
+#define THC_M_PRT_SW_SEQ_STS_THC_SS_CIP BIT(3)
+#define THC_M_PRT_SW_SEQ_DATA0_ADDR_THC_SW_SEQ_DATA0_ADDR GENMASK(31, 0)
+#define THC_M_PRT_SW_SEQ_DATA1_THC_SW_SEQ_DATA1 GENMASK(31, 0)
+
+#define THC_M_PRT_WPRD_BA_LOW_THC_M_PRT_WPRD_BA_LOW GENMASK(31, 12)
+#define THC_M_PRT_WPRD_BA_HI_THC_M_PRT_WPRD_BA_HI GENMASK(31, 0)
+
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_START BIT(0)
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_ERROR BIT(1)
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC BIT(2)
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_IE_IOC_DMACPL BIT(3)
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_UHS BIT(23)
+#define THC_M_PRT_WRITE_DMA_CNTRL_THC_WRDMA_PTEC GENMASK(31, 24)
+
+#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS BIT(0)
+#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ERROR_STS BIT(1)
+#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_IOC_STS BIT(2)
+#define THC_M_PRT_WRITE_INT_STS_THC_WRDMA_ACTIVE BIT(3)
+
+#define THC_M_PRT_WR_BULK_ADDR_THC_M_PRT_WR_BULK_ADDR GENMASK(31, 0)
+
+#define THC_M_PRT_DEV_INT_CAUSE_ADDR_THC_M_PRT_DEV_INT_CAUSE_ADDR GENMASK(31, 0)
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_INTERRUPT_TYPE GENMASK(3, 0)
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_MICRO_FRAME_SIZE GENMASK(23, 4)
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_BEGINNING_OF_FRAME BIT(29)
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_END_OF_FRAME BIT(30)
+#define THC_M_PRT_DEV_INT_CAUSE_REG_VAL_FRAME_TYPE BIT(31)
+
+#define THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT GENMASK(30, 0)
+#define THC_M_PRT_TX_FRM_CNT_THC_M_PRT_TX_FRM_CNT_RST BIT(31)
+
+#define THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT GENMASK(30, 0)
+#define THC_M_PRT_TXDMA_PKT_CNT_THC_M_PRT_TXDMA_PKT_CNT_RST BIT(31)
+
+#define THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT GENMASK(30, 0)
+#define THC_M_PRT_DEVINT_CNT_THC_M_PRT_DEVINT_CNT_RST BIT(31)
+
+#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_OFFSET GENMASK(4, 0)
+#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_LEN GENMASK(9, 5)
+#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_EOF_OFFSET GENMASK(14, 10)
+#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_SEND_ICR_US_EN BIT(15)
+#define THC_M_PRT_DEVINT_CFG_1_THC_M_PRT_INTTYP_DATA_VAL GENMASK(31, 16)
+
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_OFFSET GENMASK(4, 0)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_LEN GENMASK(9, 5)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_UFSIZE_UNIT GENMASK(15, 12)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_IGNORE BIT(16)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_FTYPE_VAL BIT(17)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_RXDMA_ADDRINC_DIS BIT(24)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_TXDMA_ADDRINC_DIS BIT(25)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_RXDMA_PKT_STRM_EN BIT(26)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_TXDMA_PKT_STRM_EN BIT(27)
+#define THC_M_PRT_DEVINT_CFG_2_THC_M_PRT_DEVINT_POL BIT(28)
+
+#define THC_M_PRT_RPRD_BA_LOW_1_THC_M_PRT_RPRD_BA_LOW GENMASK(31, 12)
+#define THC_M_PRT_RPRD_BA_HI_1_THC_M_PRT_RPRD_BA_HI GENMASK(31, 0)
+
+#define THC_M_PRT_RPRD_CNTRL_PCD GENMASK(6, 0)
+#define THC_M_PRT_RPRD_CNTRL_PTEC GENMASK(15, 8)
+#define THC_M_PRT_RPRD_CNTRL_PREFETCH_WM GENMASK(19, 16)
+
+#define THC_M_PRT_READ_DMA_CNTRL_START BIT(0)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_ERROR BIT(1)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_IOC BIT(2)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_STALL BIT(3)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_NDDI BIT(4)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_EOF BIT(5)
+#define THC_M_PRT_READ_DMA_CNTRL_IE_DMACPL BIT(7)
+#define THC_M_PRT_READ_DMA_CNTRL_TPCRP GENMASK(15, 8)
+#define THC_M_PRT_READ_DMA_CNTRL_TPCWP GENMASK(23, 16)
+#define THC_M_PRT_READ_DMA_CNTRL_INT_SW_DMA_EN BIT(28)
+#define THC_M_PRT_READ_DMA_CNTRL_SOO BIT(29)
+#define THC_M_PRT_READ_DMA_CNTRL_UHS BIT(30)
+#define THC_M_PRT_READ_DMA_CNTRL_TPCPR BIT(31)
+
+#define THC_M_PRT_READ_DMA_INT_STS_DMACPL_STS BIT(0)
+#define THC_M_PRT_READ_DMA_INT_STS_ERROR_STS BIT(1)
+#define THC_M_PRT_READ_DMA_INT_STS_IOC_STS BIT(2)
+#define THC_M_PRT_READ_DMA_INT_STS_STALL_STS BIT(3)
+#define THC_M_PRT_READ_DMA_INT_STS_NONDMA_INT_STS BIT(4)
+#define THC_M_PRT_READ_DMA_INT_STS_EOF_INT_STS BIT(5)
+#define THC_M_PRT_READ_DMA_INT_STS_ACTIVE BIT(8)
+
+#define THC_M_PRT_READ_DMA_ERR_1_DLERR BIT(0)
+
+#define THC_M_PRT_GUC_OFFSET_LOW_1_THC_M_PRT_GUC_OFFSET_LOW GENMASK(31, 3)
+#define THC_M_PRT_GUC_OFFSET_HI_1_THC_M_PRT_GUC_OFFSET_HI GENMASK(31, 0)
+#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_1_WORKQ_ITEM_SZ GENMASK(23, 0)
+#define THC_M_PRT_GUC_WORKQ_SZ_1_WORKQ_SZ GENMASK(23, 0)
+#define THC_M_PRT_GUC_WORKQ_SZ_1_FCD GENMASK(27, 24)
+#define THC_M_PRT_GUC_WORKQ_SZ_1_GIC GENMASK(31, 28)
+
+#define THC_M_PRT_TSEQ_CNTRL_1_RGD BIT(2)
+#define THC_M_PRT_TSEQ_CNTRL_1_EGP BIT(3)
+#define THC_M_PRT_TSEQ_CNTRL_1_RTO BIT(4)
+#define THC_M_PRT_TSEQ_CNTRL_1_EWOG BIT(5)
+#define THC_M_PRT_TSEQ_CNTRL_1_RWOGC BIT(6)
+#define THC_M_PRT_TSEQ_CNTRL_1_RX_DATA_FIFO_WR_WM GENMASK(25, 16)
+#define THC_M_PRT_TSEQ_CNTRL_1_RESET_PREP_CHICKEN BIT(30)
+#define THC_M_PRT_TSEQ_CNTRL_1_INT_EDG_DET_EN BIT(31)
+
+#define THC_M_PRT_GUC_DB_ADDR_LOW_1_GUC_DB_ADDR_LOW GENMASK(31, 2)
+#define THC_M_PRT_GUC_DB_ADDR_HI_1_GUC_DB_ADDR_HI GENMASK(31, 0)
+#define THC_M_PRT_GUC_DB_DATA_1_GUC_DB_DATA GENMASK(31, 0)
+#define THC_M_PRT_GUC_OFFSET_INITVAL_1_THC_M_PRT_GUC_OFFSET_INITVAL GENMASK(31, 0)
+
+#define THC_M_PRT_RD_BULK_ADDR_1_THC_M_PRT_RD_BULK_ADDR GENMASK(31, 0)
+
+#define THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT GENMASK(30, 0)
+#define THC_M_PRT_DB_CNT_1_THC_M_PRT_DB_CNT_RST BIT(31)
+
+#define THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT GENMASK(30, 0)
+#define THC_M_PRT_FRM_CNT_1_THC_M_PRT_FRM_CNT_RST BIT(31)
+
+#define THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT GENMASK(30, 0)
+#define THC_M_PRT_UFRM_CNT_1_THC_M_PRT_UFRM_CNT_RST BIT(31)
+
+#define THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT GENMASK(30, 0)
+#define THC_M_PRT_RXDMA_PKT_CNT_1_THC_M_PRT_RXDMA_PKT_CNT_RST BIT(31)
+
+#define THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT GENMASK(30, 0)
+#define THC_M_PRT_SWINT_CNT_1_THC_M_PRT_SWINT_CNT_RST BIT(31)
+
+#define THC_M_PRT_FRAME_DROP_CNT_1_NOFD GENMASK(30, 0)
+#define THC_M_PRT_FRAME_DROP_CNT_1_RFDC BIT(31)
+
+#define THC_M_PRT_COALESCE_1_COALESCE_TIMEOUT GENMASK(6, 0)
+
+#define THC_M_PRT_RPRD_BA_LOW_2_THC_M_PRT_RPRD_BA_LOW GENMASK(31, 12)
+#define THC_M_PRT_RPRD_BA_HI_2_THC_M_PRT_RPRD_BA_HI GENMASK(31, 0)
+
+#define THC_M_PRT_READ_DMA_ERR_2_DLERR BIT(0)
+
+#define THC_M_PRT_GUC_OFFSET_LOW_2_THC_M_PRT_GUC_OFFSET_LOW GENMASK(31, 3)
+#define THC_M_PRT_GUC_OFFSET_HI_2_THC_M_PRT_GUC_OFFSET_HI GENMASK(31, 0)
+
+#define THC_M_PRT_GUC_WORKQ_ITEM_SZ_2_WORKQ_ITEM_SZ GENMASK(23, 0)
+#define THC_M_PRT_GUC_WORKQ_SZ_2_WORKQ_SZ GENMASK(23, 0)
+#define THC_M_PRT_GUC_WORKQ_SZ_2_FCD GENMASK(27, 24)
+#define THC_M_PRT_GUC_WORKQ_SZ_2_GIC GENMASK(31, 28)
+
+#define THC_M_PRT_TSEQ_CNTRL_2_RGD BIT(2)
+#define THC_M_PRT_TSEQ_CNTRL_2_EGP BIT(3)
+#define THC_M_PRT_TSEQ_CNTRL_2_RTO BIT(4)
+
+#define THC_M_PRT_GUC_DB_ADDR_LOW_2_GUC_DB_ADDR_LOW GENMASK(31, 2)
+#define THC_M_PRT_GUC_DB_ADDR_HI_2_GUC_DB_ADDR_HI GENMASK(31, 0)
+
+#define THC_M_PRT_GUC_DB_DATA_2_GUC_DB_DATA GENMASK(31, 0)
+
+#define THC_M_PRT_GUC_OFFSET_INITVAL_2_THC_M_PRT_GUC_OFFSET_INITVAL GENMASK(31, 0)
+
+#define THC_M_PRT_RD_BULK_ADDR_2_THC_M_PRT_RD_BULK_ADDR GENMASK(31, 0)
+
+#define THC_M_PRT_DB_CNT_2_THC_M_PRT_DB_CNT GENMASK(30, 0)
+#define THC_M_PRT_DB_CNT_2_THC_M_PRT_DB_CNT_RST BIT(31)
+
+#define THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT GENMASK(30, 0)
+#define THC_M_PRT_FRM_CNT_2_THC_M_PRT_FRM_CNT_RST BIT(31)
+
+#define THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT GENMASK(30, 0)
+#define THC_M_PRT_UFRM_CNT_2_THC_M_PRT_UFRM_CNT_RST BIT(31)
+
+#define THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT GENMASK(30, 0)
+#define THC_M_PRT_RXDMA_PKT_CNT_2_THC_M_PRT_RXDMA_PKT_CNT_RST BIT(31)
+
+#define THC_M_PRT_SWINT_CNT_2_THC_M_PRT_SWINT_CNT GENMASK(30, 0)
+#define THC_M_PRT_SWINT_CNT_2_THC_M_PRT_SWINT_CNT_RST BIT(31)
+
+#define THC_M_PRT_FRAME_DROP_CNT_2_NOFD GENMASK(30, 0)
+#define THC_M_PRT_FRAME_DROP_CNT_2_RFDC BIT(31)
+
+#define THC_M_PRT_COALESCE_2_COALESCE_TIMEOUT GENMASK(6, 0)
+
+#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_I2C_RW_PIO_EN BIT(23)
+#define THC_M_PRT_SW_SEQ_I2C_WR_CNTRL_THC_PIO_I2C_WBC GENMASK(31, 26)
+
+#define THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_RX_DLEN_EN BIT(23)
+#define THC_M_PRT_RPRD_CNTRL_SW_THC_SWDMA_I2C_WBC GENMASK(31, 26)
+
+#define THC_M_PRT_PRD_EMPTY_CNT_1_RPTEC BIT(31)
+#define THC_M_PRT_PRD_EMPTY_CNT_2_RPTEC BIT(31)
+
+#define THC_M_PRT_SW_DMA_PRD_TABLE_LEN_THC_M_PRT_SW_DMA_PRD_TABLE_LEN GENMASK(23, 0)
+
+#define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_VAL GENMASK(3, 0)
+#define THC_M_PRT_SPI_DUTYC_CFG_SPI_CSA_CK_DELAY_EN BIT(25)
+
+/* CS Assertion delay default value */
+#define THC_CSA_CK_DELAY_VAL_DEFAULT 4
+
+/* ARB policy definition */
+/* Arbiter switches on packet boundary */
+#define THC_ARB_POLICY_PACKET_BOUNDARY 0
+/* Arbiter switches on Micro Frame boundary */
+#define THC_ARB_POLICY_UFRAME_BOUNDARY 1
+/* Arbiter switches on Frame boundary */
+#define THC_ARB_POLICY_FRAME_BOUNDARY 2
+
+#define THC_REGMAP_POLLING_INTERVAL_US 10 /* 10us */
+#define THC_PIO_DONE_TIMEOUT_US USEC_PER_SEC /* 1s */
+
+/* Default configures for HIDSPI */
+#define THC_BIT_OFFSET_INTERRUPT_TYPE 4
+/* input_report_type is 4 bits for HIDSPI */
+#define THC_BIT_LENGTH_INTERRUPT_TYPE 4
+/* Last fragment indicator is bit 15 for HIDSPI */
+#define THC_BIT_OFFSET_LAST_FRAGMENT_FLAG 22
+#define THC_BIT_OFFSET_MICROFRAME_SIZE 8
+/* input_report_length is 14 bits for HIDSPI */
+#define THC_BIT_LENGTH_MICROFRAME_SIZE 14
+/* MFS unit in power of 2 */
+#define THC_UNIT_MICROFRAME_SIZE 2
+#define THC_BITMASK_INTERRUPT_TYPE_DATA 1
+#define THC_BITMASK_INVALID_TYPE_DATA 2
+
+/* Interrupt Quiesce default timeout value */
+#define THC_QUIESCE_EN_TIMEOUT_US USEC_PER_SEC /* 1s */
+
+/* LTR definition */
+/*
+ * THC uses scale to calculate final LTR value.
+ * Scale is geometric progression of 2^5 step, starting from 2^0.
+ * For example, THC_LTR_SCALE_2(2) means 2^(5 * 2) = 1024, unit is ns.
+ */
+#define THC_LTR_SCALE_0 0
+#define THC_LTR_SCALE_1 1
+#define THC_LTR_SCALE_2 2
+#define THC_LTR_SCALE_3 3
+#define THC_LTR_SCALE_4 4
+#define THC_LTR_SCALE_5 5
+#define THC_LTR_MODE_ACTIVE 0
+#define THC_LTR_MODE_LP 1
+#define THC_LTR_MIN_VAL_SCALE_3 BIT(10)
+#define THC_LTR_MAX_VAL_SCALE_3 BIT(15)
+#define THC_LTR_MIN_VAL_SCALE_4 BIT(15)
+#define THC_LTR_MAX_VAL_SCALE_4 BIT(20)
+#define THC_LTR_MIN_VAL_SCALE_5 BIT(20)
+#define THC_LTR_MAX_VAL_SCALE_5 BIT(25)
+
+/*
+ * THC PIO opcode default value
+ * @THC_PIO_OP_SPI_TIC_READ: THC opcode for SPI PIO read
+ * @THC_PIO_OP_SPI_TIC_WRITE: THC opcode for SPI PIO write
+ * @THC_PIO_OP_I2C_SUBSYSTEM_READ: THC opcode for read I2C subsystem registers
+ * @THC_PIO_OP_I2C_SUBSYSTEM_WRITE: THC opcode for write I2C subsystem registers
+ * @THC_PIO_OP_I2C_TIC_READ: THC opcode for read I2C device
+ * @THC_PIO_OP_I2C_TIC_WRITE: THC opcode for write I2C device
+ * @THC_PIO_OP_I2C_TIC_WRITE_AND_READ: THC opcode for write followed by read I2C device
+ */
+enum thc_pio_opcode {
+ THC_PIO_OP_SPI_TIC_READ = 0x4,
+ THC_PIO_OP_SPI_TIC_WRITE = 0x6,
+ THC_PIO_OP_I2C_SUBSYSTEM_READ = 0x12,
+ THC_PIO_OP_I2C_SUBSYSTEM_WRITE = 0x13,
+ THC_PIO_OP_I2C_TIC_READ = 0x14,
+ THC_PIO_OP_I2C_TIC_WRITE = 0x18,
+ THC_PIO_OP_I2C_TIC_WRITE_AND_READ = 0x1C,
+};
+
+/**
+ * THC SPI IO mode
+ * @THC_SINGLE_IO: single IO mode, 1(opcode) - 1(address) - 1(data)
+ * @THC_DUAL_IO: dual IO mode, 1(opcode) - 2(address) - 2(data)
+ * @THC_QUAD_IO: quad IO mode, 1(opcode) - 4(address) - 4(data)
+ * @THC_QUAD_PARALLEL_IO: parallel quad IO mode, 4(opcode) - 4(address) - 4(data)
+ */
+enum thc_spi_iomode {
+ THC_SINGLE_IO = 0,
+ THC_DUAL_IO = 1,
+ THC_QUAD_IO = 2,
+ THC_QUAD_PARALLEL_IO = 3,
+};
+
+/**
+ * THC SPI frequency divider
+ *
+ * This DIV final value is determined by THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN bit.
+ * If THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN isn't be set, THC takes the DIV value directly;
+ * If THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN is set, THC takes the DIV value multiply by 8.
+ *
+ * For example, if THC input clock is 125MHz:
+ * When THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN isn't set, THC_SPI_FRQ_DIV_3 means DIV is 3,
+ * THC final clock is 125 / 3 = 41.667MHz;
+ * When THC_M_PRT_SPI_CFG_SPI_LOW_FREQ_EN is set, THC_SPI_FRQ_DIV_3 means DIV is 3 * 8,
+ * THC final clock is 125 / (3 * 8) = 5.208MHz;
+ */
+enum thc_spi_frq_div {
+ THC_SPI_FRQ_RESERVED = 0,
+ THC_SPI_FRQ_DIV_1 = 1,
+ THC_SPI_FRQ_DIV_2 = 2,
+ THC_SPI_FRQ_DIV_3 = 3,
+ THC_SPI_FRQ_DIV_4 = 4,
+ THC_SPI_FRQ_DIV_5 = 5,
+ THC_SPI_FRQ_DIV_6 = 6,
+ THC_SPI_FRQ_DIV_7 = 7,
+};
+
+/* THC I2C sub-system registers */
+#define THC_I2C_IC_CON_OFFSET 0x0
+#define THC_I2C_IC_TAR_OFFSET 0x4
+#define THC_I2C_IC_SAR_OFFSET 0x8
+#define THC_I2C_IC_HS_MADDR_OFFSET 0xC
+#define THC_I2C_IC_DATA_CMD_OFFSET 0x10
+#define THC_I2C_IC_SS_SCL_HCNT_OFFSET 0x14
+#define THC_I2C_IC_UFM_SCL_HCNT_OFFSET 0x14
+#define THC_I2C_IC_SS_SCL_LCNT_OFFSET 0x18
+#define THC_I2C_IC_UFM_SCL_LCNT_OFFSET 0x18
+#define THC_I2C_IC_FS_SCL_HCNT_OFFSET 0x1C
+#define THC_I2C_IC_UFM_TBUF_CNT_OFFSET 0x1C
+#define THC_I2C_IC_FS_SCL_LCNT_OFFSET 0x20
+#define THC_I2C_IC_HS_SCL_HCNT_OFFSET 0x24
+#define THC_I2C_IC_HS_SCL_LCNT_OFFSET 0x28
+#define THC_I2C_IC_INTR_STAT_OFFSET 0x2C
+#define THC_I2C_IC_INTR_MASK_OFFSET 0x30
+#define THC_I2C_IC_RAW_INTR_STAT_OFFSET 0x34
+#define THC_I2C_IC_RX_TL_OFFSET 0x38
+#define THC_I2C_IC_TX_TL_OFFSET 0x3C
+#define THC_I2C_IC_CLR_INTR_OFFSET 0x40
+#define THC_I2C_IC_CLR_RX_UNDER_OFFSET 0x44
+#define THC_I2C_IC_CLR_RX_OVER_OFFSET 0x48
+#define THC_I2C_IC_CLR_TX_OVER_OFFSET 0x4C
+#define THC_I2C_IC_CLR_RD_REQ_OFFSET 0x50
+#define THC_I2C_IC_CLR_TX_ABRT_OFFSET 0x54
+#define THC_I2C_IC_CLR_RX_DONE_OFFSET 0x58
+#define THC_I2C_IC_CLR_ACTIVITY_OFFSET 0x5C
+#define THC_I2C_IC_CLR_STOP_DET_OFFSET 0x60
+#define THC_I2C_IC_CLR_START_DET_OFFSET 0x64
+#define THC_I2C_IC_CLR_GEN_CALL_OFFSET 0x68
+#define THC_I2C_IC_ENABLE_OFFSET 0x6C
+#define THC_I2C_IC_STATUS_OFFSET 0x70
+#define THC_I2C_IC_TXFLR_OFFSET 0x74
+#define THC_I2C_IC_RXFLR_OFFSET 0x78
+#define THC_I2C_IC_SDA_HOLD_OFFSET 0x7C
+#define THC_I2C_IC_TX_ABRT_SOURCE_OFFSET 0x80
+#define THC_I2C_IC_SLV_DATA_NACK_ONLY_OFFSET 0x84
+#define THC_I2C_IC_DMA_CR_OFFSET 0x88
+#define THC_I2C_IC_DMA_TDLR_OFFSET 0x8C
+#define THC_I2C_IC_DMA_RDLR_OFFSET 0x90
+#define THC_I2C_IC_SDA_SETUP_OFFSET 0x94
+#define THC_I2C_IC_ACK_GENERAL_CALL_OFFSET 0x98
+#define THC_I2C_IC_ENABLE_STATUS_OFFSET 0x9C
+#define THC_I2C_IC_FS_SPKLEN_OFFSET 0xA0
+#define THC_I2C_IC_UFM_SPKLEN_OFFSET 0xA0
+#define THC_I2C_IC_HS_SPKLEN_OFFSET 0xA4
+#define THC_I2C_IC_CLR_RESTART_DET_OFFSET 0xA8
+#define THC_I2C_IC_SCL_STUCK_AT_LOW_TIMEOUT_OFFSET 0xAC
+#define THC_I2C_IC_SDA_STUCK_AT_LOW_TIMEOUT_OFFSET 0xB0
+#define THC_I2C_IC_CLR_SCL_STUCK_DET_OFFSET 0xB4
+#define THC_I2C_IC_DEVICE_ID_OFFSET 0xB8
+#define THC_I2C_IC_SMBUS_CLK_LOW_SEXT_OFFSET 0xBC
+#define THC_I2C_IC_SMBUS_CLK_LOW_MEXT_OFFSET 0xC0
+#define THC_I2C_IC_SMBUS_THIGH_MAX_IDLE_COUNT_OFFSET 0xC4
+#define THC_I2C_IC_SMBUS_INTR_STAT_OFFSET 0xC8
+#define THC_I2C_IC_SMBUS_INTR_MASK_OFFSET 0xCC
+#define THC_I2C_IC_SMBUS_RAW_INTR_STAT_OFFSET 0xD0
+#define THC_I2C_IC_CLR_SMBUS_INTR_OFFSET 0xD4
+#define THC_I2C_IC_OPTIONAL_SAR_OFFSET 0xD8
+#define THC_I2C_IC_SMBUS_UDID_LSB_OFFSET 0xDC
+#define THC_I2C_IC_SMBUS_UDID_WORD0_OFFSET 0xDC
+#define THC_I2C_IC_SMBUS_UDID_WORD1_OFFSET 0xE0
+#define THC_I2C_IC_SMBUS_UDID_WORD2_OFFSET 0xE4
+#define THC_I2C_IC_SMBUS_UDID_WORD3_OFFSET 0xE8
+#define THC_I2C_IC_COMP_PARAM_1_OFFSET 0xF4
+#define THC_I2C_IC_COMP_VERSION_OFFSET 0xF8
+#define THC_I2C_IC_COMP_TYPE_OFFSET 0xFC
+
+/**
+ * THC I2C sub-system supported speed mode
+ */
+enum THC_I2C_SPEED_MODE {
+ THC_I2C_STANDARD = 1,
+ THC_I2C_FAST_AND_PLUS = 2,
+ THC_I2C_HIGH_SPEED = 3,
+};
+
+/* THC I2C sub-system register bits definition */
+#define THC_I2C_IC_ENABLE_ENABLE BIT(0)
+#define THC_I2C_IC_ENABLE_ABORT BIT(1)
+#define THC_I2C_IC_ENABLE_TX_CMD_BLOCK BIT(2)
+#define THC_I2C_IC_ENABLE_SDA_STUCK_RECOVERY_ENABLE BIT(3)
+#define THC_I2C_IC_ENABLE_SMBUS_CLK_RESET BIT(16)
+#define THC_I2C_IC_ENABLE_SMBUS_SUSPEND_EN BIT(17)
+#define THC_I2C_IC_ENABLE_SMBUS_ALERT_EN BIT(18)
+
+#define THC_I2C_IC_CON_MASTER_MODE BIT(0)
+#define THC_I2C_IC_CON_SPEED GENMASK(2, 1)
+#define THC_I2C_IC_CON_IC_10BITADDR_SLAVE BIT(3)
+#define THC_I2C_IC_CON_IC_10BITADDR_MASTER BIT(4)
+#define THC_I2C_IC_CON_IC_RESTART_EN BIT(5)
+#define THC_I2C_IC_CON_IC_SLAVE_DISABLE BIT(6)
+#define THC_I2C_IC_CON_STOP_DET_IFADDRESSED BIT(7)
+#define THC_I2C_IC_CON_TX_EMPTY_CTRL BIT(8)
+#define THC_I2C_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9)
+#define THC_I2C_IC_CON_STOP_DET_IF_MASTER_ACTIVE BIT(10)
+#define THC_I2C_IC_CON_BUS_CLEAR_FEATURE_CTRL BIT(11)
+#define THC_I2C_IC_CON_OPTIONAL_SAR_CTRL BIT(16)
+#define THC_I2C_IC_CON_SMBUS_SLAVE_QUICK_EN BIT(17)
+#define THC_I2C_IC_CON_SMBUS_ARP_EN BIT(18)
+#define THC_I2C_IC_CON_SMBUS_PERSISTENT_SLV_ADDR_EN BIT(19)
+
+#define THC_I2C_IC_TAR_IC_TAR GENMASK(9, 0)
+#define THC_I2C_IC_TAR_GC_OR_START BIT(10)
+#define THC_I2C_IC_TAR_SPECIAL BIT(11)
+#define THC_I2C_IC_TAR_IC_10BITADDR_MASTER BIT(12)
+#define THC_I2C_IC_TAR_DEVICE_ID BIT(13)
+#define THC_I2C_IC_TAR_SMBUS_QUICK_CMD BIT(16)
+
+#define THC_I2C_IC_INTR_MASK_M_RX_UNDER BIT(0)
+#define THC_I2C_IC_INTR_MASK_M_RX_OVER BIT(1)
+#define THC_I2C_IC_INTR_MASK_M_RX_FULL BIT(2)
+#define THC_I2C_IC_INTR_MASK_M_TX_OVER BIT(3)
+#define THC_I2C_IC_INTR_MASK_M_TX_EMPTY BIT(4)
+#define THC_I2C_IC_INTR_MASK_M_RD_REQ BIT(5)
+#define THC_I2C_IC_INTR_MASK_M_TX_ABRT BIT(6)
+#define THC_I2C_IC_INTR_MASK_M_RX_DONE BIT(7)
+#define THC_I2C_IC_INTR_MASK_M_ACTIVITY BIT(8)
+#define THC_I2C_IC_INTR_MASK_M_STOP_DET BIT(9)
+#define THC_I2C_IC_INTR_MASK_M_START_DET BIT(10)
+#define THC_I2C_IC_INTR_MASK_M_GEN_CALL BIT(11)
+#define THC_I2C_IC_INTR_MASK_M_RESTART_DET BIT(12)
+#define THC_I2C_IC_INTR_MASK_M_MASTER_ON_HOLD BIT(13)
+#define THC_I2C_IC_INTR_MASK_M_SCL_STUCK_AT_LOW BIT(14)
+
+#define THC_I2C_IC_DMA_CR_RDMAE BIT(0)
+#define THC_I2C_IC_DMA_CR_TDMAE BIT(1)
+
+#endif /* _INTEL_THC_HW_H_ */
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
index 7ce9b5d641eb..d0cfd0d29926 100644
--- a/drivers/hid/surface-hid/Kconfig
+++ b/drivers/hid/surface-hid/Kconfig
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
menu "Surface System Aggregator Module HID support"
depends on SURFACE_AGGREGATOR
- depends on INPUT
config SURFACE_HID
tristate "HID transport driver for Surface System Aggregator Module"
@@ -39,4 +38,3 @@ endmenu
config SURFACE_HID_CORE
tristate
- select HID
diff --git a/drivers/hid/usbhid/Kconfig b/drivers/hid/usbhid/Kconfig
index 7c2032f7f44d..f3194767a45e 100644
--- a/drivers/hid/usbhid/Kconfig
+++ b/drivers/hid/usbhid/Kconfig
@@ -5,8 +5,7 @@ menu "USB HID support"
config USB_HID
tristate "USB HID transport layer"
default y
- depends on USB && INPUT
- select HID
+ depends on HID
help
Say Y here if you want to connect USB keyboards,
mice, joysticks, graphic tablets, or any other HID based devices
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index a6eb6fe6130d..d4cbecc668ec 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -35,6 +35,7 @@
#include <linux/hid-debug.h>
#include <linux/hidraw.h>
#include "usbhid.h"
+#include "hid-pidff.h"
/*
* Version Information
@@ -983,12 +984,11 @@ static int usbhid_parse(struct hid_device *hid)
struct usb_host_interface *interface = intf->cur_altsetting;
struct usb_device *dev = interface_to_usbdev (intf);
struct hid_descriptor *hdesc;
+ struct hid_class_descriptor *hcdesc;
u32 quirks = 0;
unsigned int rsize = 0;
char *rdesc;
- int ret, n;
- int num_descriptors;
- size_t offset = offsetof(struct hid_descriptor, desc);
+ int ret;
quirks = hid_lookup_quirk(hid);
@@ -1010,20 +1010,19 @@ static int usbhid_parse(struct hid_device *hid)
return -ENODEV;
}
- if (hdesc->bLength < sizeof(struct hid_descriptor)) {
- dbg_hid("hid descriptor is too short\n");
+ if (!hdesc->bNumDescriptors ||
+ hdesc->bLength != sizeof(*hdesc) +
+ (hdesc->bNumDescriptors - 1) * sizeof(*hcdesc)) {
+ dbg_hid("hid descriptor invalid, bLen=%hhu bNum=%hhu\n",
+ hdesc->bLength, hdesc->bNumDescriptors);
return -EINVAL;
}
hid->version = le16_to_cpu(hdesc->bcdHID);
hid->country = hdesc->bCountryCode;
- num_descriptors = min_t(int, hdesc->bNumDescriptors,
- (hdesc->bLength - offset) / sizeof(struct hid_class_descriptor));
-
- for (n = 0; n < num_descriptors; n++)
- if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
- rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
+ if (hdesc->rpt_desc.bDescriptorType == HID_DT_REPORT)
+ rsize = le16_to_cpu(hdesc->rpt_desc.wDescriptorLength);
if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
dbg_hid("weird size of report descriptor (%u)\n", rsize);
@@ -1051,6 +1050,11 @@ static int usbhid_parse(struct hid_device *hid)
goto err;
}
+ if (hdesc->bNumDescriptors > 1)
+ hid_warn(intf,
+ "%u unsupported optional hid class descriptors\n",
+ (int)(hdesc->bNumDescriptors - 1));
+
hid->quirks |= quirks;
return 0;
@@ -1461,13 +1465,13 @@ static void usbhid_disconnect(struct usb_interface *intf)
static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid)
{
- del_timer_sync(&usbhid->io_retry);
+ timer_delete_sync(&usbhid->io_retry);
cancel_work_sync(&usbhid->reset_work);
}
static void hid_cease_io(struct usbhid_device *usbhid)
{
- del_timer_sync(&usbhid->io_retry);
+ timer_delete_sync(&usbhid->io_retry);
usb_kill_urb(usbhid->urbin);
usb_kill_urb(usbhid->urbctrl);
usb_kill_urb(usbhid->urbout);
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
index 3b4ee21cd811..8dfd2c554a27 100644
--- a/drivers/hid/usbhid/hid-pidff.c
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -3,27 +3,27 @@
* Force feedback driver for USB HID PID compliant devices
*
* Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ * Upgraded 2025 by Oleg Makarenko and Tomasz Pakuła
*/
-/*
- */
-
-/* #define DEBUG */
-
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include "hid-pidff.h"
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/usb.h>
-
#include <linux/hid.h>
+#include <linux/minmax.h>
-#include "usbhid.h"
#define PID_EFFECTS_MAX 64
+#define PID_INFINITE U16_MAX
-/* Report usage table used to put reports into an array */
+/* Linux Force Feedback API uses miliseconds as time unit */
+#define FF_TIME_EXPONENT -3
+#define FF_INFINITE 0
+/* Report usage table used to put reports into an array */
#define PID_SET_EFFECT 0
#define PID_EFFECT_OPERATION 1
#define PID_DEVICE_GAIN 2
@@ -44,12 +44,19 @@ static const u8 pidff_reports[] = {
0x21, 0x77, 0x7d, 0x7f, 0x89, 0x90, 0x96, 0xab,
0x5a, 0x5f, 0x6e, 0x73, 0x74
};
+/*
+ * device_control is really 0x95, but 0x96 specified
+ * as it is the usage of the only field in that report.
+ */
-/* device_control is really 0x95, but 0x96 specified as it is the usage of
-the only field in that report */
+/* PID special fields */
+#define PID_EFFECT_TYPE 0x25
+#define PID_DIRECTION 0x57
+#define PID_EFFECT_OPERATION_ARRAY 0x78
+#define PID_BLOCK_LOAD_STATUS 0x8b
+#define PID_DEVICE_CONTROL_ARRAY 0x96
/* Value usage tables used to put fields and values into arrays */
-
#define PID_EFFECT_BLOCK_INDEX 0
#define PID_DURATION 1
@@ -107,10 +114,13 @@ static const u8 pidff_device_gain[] = { 0x7e };
static const u8 pidff_pool[] = { 0x80, 0x83, 0xa9 };
/* Special field key tables used to put special field keys into arrays */
-
#define PID_ENABLE_ACTUATORS 0
-#define PID_RESET 1
-static const u8 pidff_device_control[] = { 0x97, 0x9a };
+#define PID_DISABLE_ACTUATORS 1
+#define PID_STOP_ALL_EFFECTS 2
+#define PID_RESET 3
+#define PID_PAUSE 4
+#define PID_CONTINUE 5
+static const u8 pidff_device_control[] = { 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c };
#define PID_CONSTANT 0
#define PID_RAMP 1
@@ -130,12 +140,16 @@ static const u8 pidff_effect_types[] = {
#define PID_BLOCK_LOAD_SUCCESS 0
#define PID_BLOCK_LOAD_FULL 1
-static const u8 pidff_block_load_status[] = { 0x8c, 0x8d };
+#define PID_BLOCK_LOAD_ERROR 2
+static const u8 pidff_block_load_status[] = { 0x8c, 0x8d, 0x8e};
#define PID_EFFECT_START 0
#define PID_EFFECT_STOP 1
static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b };
+/* Polar direction 90 degrees (East) */
+#define PIDFF_FIXED_WHEEL_DIRECTION 0x4000
+
struct pidff_usage {
struct hid_field *field;
s32 *value;
@@ -159,8 +173,10 @@ struct pidff_device {
struct pidff_usage effect_operation[sizeof(pidff_effect_operation)];
struct pidff_usage block_free[sizeof(pidff_block_free)];
- /* Special field is a field that is not composed of
- usage<->value pairs that pidff_usage values are */
+ /*
+ * Special field is a field that is not composed of
+ * usage<->value pairs that pidff_usage values are
+ */
/* Special field in create_new_effect */
struct hid_field *create_new_effect_type;
@@ -184,30 +200,61 @@ struct pidff_device {
int operation_id[sizeof(pidff_effect_operation_status)];
int pid_id[PID_EFFECTS_MAX];
+
+ u32 quirks;
+ u8 effect_count;
};
/*
+ * Clamp value for a given field
+ */
+static s32 pidff_clamp(s32 i, struct hid_field *field)
+{
+ s32 clamped = clamp(i, field->logical_minimum, field->logical_maximum);
+ pr_debug("clamped from %d to %d", i, clamped);
+ return clamped;
+}
+
+/*
* Scale an unsigned value with range 0..max for the given field
*/
static int pidff_rescale(int i, int max, struct hid_field *field)
{
return i * (field->logical_maximum - field->logical_minimum) / max +
- field->logical_minimum;
+ field->logical_minimum;
}
/*
- * Scale a signed value in range -0x8000..0x7fff for the given field
+ * Scale a signed value in range S16_MIN..S16_MAX for the given field
*/
static int pidff_rescale_signed(int i, struct hid_field *field)
{
- return i == 0 ? 0 : i >
- 0 ? i * field->logical_maximum / 0x7fff : i *
- field->logical_minimum / -0x8000;
+ if (i > 0) return i * field->logical_maximum / S16_MAX;
+ if (i < 0) return i * field->logical_minimum / S16_MIN;
+ return 0;
+}
+
+/*
+ * Scale time value from Linux default (ms) to field units
+ */
+static u32 pidff_rescale_time(u16 time, struct hid_field *field)
+{
+ u32 scaled_time = time;
+ int exponent = field->unit_exponent;
+ pr_debug("time field exponent: %d\n", exponent);
+
+ for (;exponent < FF_TIME_EXPONENT; exponent++)
+ scaled_time *= 10;
+ for (;exponent > FF_TIME_EXPONENT; exponent--)
+ scaled_time /= 10;
+
+ pr_debug("time calculated from %d to %d\n", time, scaled_time);
+ return scaled_time;
}
static void pidff_set(struct pidff_usage *usage, u16 value)
{
- usage->value[0] = pidff_rescale(value, 0xffff, usage->field);
+ usage->value[0] = pidff_rescale(value, U16_MAX, usage->field);
pr_debug("calculated from %d to %d\n", value, usage->value[0]);
}
@@ -218,14 +265,35 @@ static void pidff_set_signed(struct pidff_usage *usage, s16 value)
else {
if (value < 0)
usage->value[0] =
- pidff_rescale(-value, 0x8000, usage->field);
+ pidff_rescale(-value, -S16_MIN, usage->field);
else
usage->value[0] =
- pidff_rescale(value, 0x7fff, usage->field);
+ pidff_rescale(value, S16_MAX, usage->field);
}
pr_debug("calculated from %d to %d\n", value, usage->value[0]);
}
+static void pidff_set_time(struct pidff_usage *usage, u16 time)
+{
+ u32 modified_time = pidff_rescale_time(time, usage->field);
+ usage->value[0] = pidff_clamp(modified_time, usage->field);
+}
+
+static void pidff_set_duration(struct pidff_usage *usage, u16 duration)
+{
+ /* Infinite value conversion from Linux API -> PID */
+ if (duration == FF_INFINITE)
+ duration = PID_INFINITE;
+
+ /* PID defines INFINITE as the max possible value for duration field */
+ if (duration == PID_INFINITE) {
+ usage->value[0] = (1U << usage->field->report_size) - 1;
+ return;
+ }
+
+ pidff_set_time(usage, duration);
+}
+
/*
* Send envelope report to the device
*/
@@ -233,19 +301,21 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
struct ff_envelope *envelope)
{
pidff->set_envelope[PID_EFFECT_BLOCK_INDEX].value[0] =
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
pidff->set_envelope[PID_ATTACK_LEVEL].value[0] =
- pidff_rescale(envelope->attack_level >
- 0x7fff ? 0x7fff : envelope->attack_level, 0x7fff,
- pidff->set_envelope[PID_ATTACK_LEVEL].field);
+ pidff_rescale(envelope->attack_level >
+ S16_MAX ? S16_MAX : envelope->attack_level, S16_MAX,
+ pidff->set_envelope[PID_ATTACK_LEVEL].field);
pidff->set_envelope[PID_FADE_LEVEL].value[0] =
- pidff_rescale(envelope->fade_level >
- 0x7fff ? 0x7fff : envelope->fade_level, 0x7fff,
- pidff->set_envelope[PID_FADE_LEVEL].field);
+ pidff_rescale(envelope->fade_level >
+ S16_MAX ? S16_MAX : envelope->fade_level, S16_MAX,
+ pidff->set_envelope[PID_FADE_LEVEL].field);
- pidff->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length;
- pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length;
+ pidff_set_time(&pidff->set_envelope[PID_ATTACK_TIME],
+ envelope->attack_length);
+ pidff_set_time(&pidff->set_envelope[PID_FADE_TIME],
+ envelope->fade_length);
hid_dbg(pidff->hid, "attack %u => %d\n",
envelope->attack_level,
@@ -261,10 +331,22 @@ static void pidff_set_envelope_report(struct pidff_device *pidff,
static int pidff_needs_set_envelope(struct ff_envelope *envelope,
struct ff_envelope *old)
{
- return envelope->attack_level != old->attack_level ||
- envelope->fade_level != old->fade_level ||
+ bool needs_new_envelope;
+ needs_new_envelope = envelope->attack_level != 0 ||
+ envelope->fade_level != 0 ||
+ envelope->attack_length != 0 ||
+ envelope->fade_length != 0;
+
+ if (!needs_new_envelope)
+ return false;
+
+ if (!old)
+ return needs_new_envelope;
+
+ return envelope->attack_level != old->attack_level ||
+ envelope->fade_level != old->fade_level ||
envelope->attack_length != old->attack_length ||
- envelope->fade_length != old->fade_length;
+ envelope->fade_length != old->fade_length;
}
/*
@@ -301,17 +383,27 @@ static void pidff_set_effect_report(struct pidff_device *pidff,
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
pidff->set_effect_type->value[0] =
pidff->create_new_effect_type->value[0];
- pidff->set_effect[PID_DURATION].value[0] = effect->replay.length;
+
+ pidff_set_duration(&pidff->set_effect[PID_DURATION],
+ effect->replay.length);
+
pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button;
- pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] =
- effect->trigger.interval;
+ pidff_set_time(&pidff->set_effect[PID_TRIGGER_REPEAT_INT],
+ effect->trigger.interval);
pidff->set_effect[PID_GAIN].value[0] =
pidff->set_effect[PID_GAIN].field->logical_maximum;
pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
- pidff->effect_direction->value[0] =
- pidff_rescale(effect->direction, 0xffff,
- pidff->effect_direction);
- pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay;
+
+ /* Use fixed direction if needed */
+ pidff->effect_direction->value[0] = pidff_rescale(
+ pidff->quirks & HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION ?
+ PIDFF_FIXED_WHEEL_DIRECTION : effect->direction,
+ U16_MAX, pidff->effect_direction);
+
+ /* Omit setting delay field if it's missing */
+ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DELAY))
+ pidff_set_time(&pidff->set_effect[PID_START_DELAY],
+ effect->replay.delay);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
HID_REQ_SET_REPORT);
@@ -343,11 +435,11 @@ static void pidff_set_periodic_report(struct pidff_device *pidff,
pidff_set_signed(&pidff->set_periodic[PID_OFFSET],
effect->u.periodic.offset);
pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase);
- pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period;
+ pidff_set_time(&pidff->set_periodic[PID_PERIOD],
+ effect->u.periodic.period);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC],
HID_REQ_SET_REPORT);
-
}
/*
@@ -368,13 +460,19 @@ static int pidff_needs_set_periodic(struct ff_effect *effect,
static void pidff_set_condition_report(struct pidff_device *pidff,
struct ff_effect *effect)
{
- int i;
+ int i, max_axis;
+
+ /* Devices missing Parameter Block Offset can only have one axis */
+ max_axis = pidff->quirks & HID_PIDFF_QUIRK_MISSING_PBO ? 1 : 2;
pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] =
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
- for (i = 0; i < 2; i++) {
- pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i;
+ for (i = 0; i < max_axis; i++) {
+ /* Omit Parameter Block Offset if missing */
+ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_PBO))
+ pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i;
+
pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET],
effect->u.condition[i].center);
pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT],
@@ -442,8 +540,103 @@ static int pidff_needs_set_ramp(struct ff_effect *effect, struct ff_effect *old)
}
/*
+ * Set device gain
+ */
+static void pidff_set_gain_report(struct pidff_device *pidff, u16 gain)
+{
+ if (!pidff->device_gain[PID_DEVICE_GAIN_FIELD].field)
+ return;
+
+ pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain);
+ hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
+ HID_REQ_SET_REPORT);
+}
+
+/*
+ * Send device control report to the device
+ */
+static void pidff_set_device_control(struct pidff_device *pidff, int field)
+{
+ int i, index;
+ int field_index = pidff->control_id[field];
+
+ if (field_index < 1)
+ return;
+
+ /* Detect if the field is a bitmask variable or an array */
+ if (pidff->device_control->flags & HID_MAIN_ITEM_VARIABLE) {
+ hid_dbg(pidff->hid, "DEVICE_CONTROL is a bitmask\n");
+
+ /* Clear current bitmask */
+ for(i = 0; i < sizeof(pidff_device_control); i++) {
+ index = pidff->control_id[i];
+ if (index < 1)
+ continue;
+
+ pidff->device_control->value[index - 1] = 0;
+ }
+
+ pidff->device_control->value[field_index - 1] = 1;
+ } else {
+ hid_dbg(pidff->hid, "DEVICE_CONTROL is an array\n");
+ pidff->device_control->value[0] = field_index;
+ }
+
+ hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
+ hid_hw_wait(pidff->hid);
+}
+
+/*
+ * Modify actuators state
+ */
+static void pidff_set_actuators(struct pidff_device *pidff, bool enable)
+{
+ hid_dbg(pidff->hid, "%s actuators\n", enable ? "Enable" : "Disable");
+ pidff_set_device_control(pidff,
+ enable ? PID_ENABLE_ACTUATORS : PID_DISABLE_ACTUATORS);
+}
+
+/*
+ * Reset the device, stop all effects, enable actuators
+ */
+static void pidff_reset(struct pidff_device *pidff)
+{
+ /* We reset twice as sometimes hid_wait_io isn't waiting long enough */
+ pidff_set_device_control(pidff, PID_RESET);
+ pidff_set_device_control(pidff, PID_RESET);
+ pidff->effect_count = 0;
+
+ pidff_set_device_control(pidff, PID_STOP_ALL_EFFECTS);
+ pidff_set_actuators(pidff, 1);
+}
+
+/*
+ * Fetch pool report
+ */
+static void pidff_fetch_pool(struct pidff_device *pidff)
+{
+ int i;
+ struct hid_device *hid = pidff->hid;
+
+ /* Repeat if PID_SIMULTANEOUS_MAX < 2 to make sure it's correct */
+ for(i = 0; i < 20; i++) {
+ hid_hw_request(hid, pidff->reports[PID_POOL], HID_REQ_GET_REPORT);
+ hid_hw_wait(hid);
+
+ if (!pidff->pool[PID_SIMULTANEOUS_MAX].value)
+ return;
+ if (pidff->pool[PID_SIMULTANEOUS_MAX].value[0] >= 2)
+ return;
+ }
+ hid_warn(hid, "device reports %d simultaneous effects\n",
+ pidff->pool[PID_SIMULTANEOUS_MAX].value[0]);
+}
+
+/*
* Send a request for effect upload to the device
*
+ * Reset and enable actuators if no effects were present on the device
+ *
* Returns 0 if device reported success, -ENOSPC if the device reported memory
* is full. Upon unknown response the function will retry for 60 times, if
* still unsuccessful -EIO is returned.
@@ -452,6 +645,9 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
{
int j;
+ if (!pidff->effect_count)
+ pidff_reset(pidff);
+
pidff->create_new_effect_type->value[0] = efnum;
hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
HID_REQ_SET_REPORT);
@@ -471,6 +667,8 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
hid_dbg(pidff->hid, "device reported free memory: %d bytes\n",
pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+
+ pidff->effect_count++;
return 0;
}
if (pidff->block_load_status->value[0] ==
@@ -480,6 +678,11 @@ static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
return -ENOSPC;
}
+ if (pidff->block_load_status->value[0] ==
+ pidff->status_id[PID_BLOCK_LOAD_ERROR]) {
+ hid_dbg(pidff->hid, "device error during effect creation\n");
+ return -EREMOTEIO;
+ }
}
hid_err(pidff->hid, "pid_block_load failed 60 times\n");
return -EIO;
@@ -498,7 +701,8 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
} else {
pidff->effect_operation_status->value[0] =
pidff->operation_id[PID_EFFECT_START];
- pidff->effect_operation[PID_LOOP_COUNT].value[0] = n;
+ pidff->effect_operation[PID_LOOP_COUNT].value[0] =
+ pidff_clamp(n, pidff->effect_operation[PID_LOOP_COUNT].field);
}
hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
@@ -511,20 +715,22 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
static int pidff_playback(struct input_dev *dev, int effect_id, int value)
{
struct pidff_device *pidff = dev->ff->private;
-
pidff_playback_pid(pidff, pidff->pid_id[effect_id], value);
-
return 0;
}
/*
* Erase effect with PID id
+ * Decrease the device effect counter
*/
static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
{
pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE],
HID_REQ_SET_REPORT);
+
+ if (pidff->effect_count > 0)
+ pidff->effect_count--;
}
/*
@@ -537,8 +743,11 @@ static int pidff_erase_effect(struct input_dev *dev, int effect_id)
hid_dbg(pidff->hid, "starting to erase %d/%d\n",
effect_id, pidff->pid_id[effect_id]);
- /* Wait for the queue to clear. We do not want a full fifo to
- prevent the effect removal. */
+
+ /*
+ * Wait for the queue to clear. We do not want
+ * a full fifo to prevent the effect removal.
+ */
hid_hw_wait(pidff->hid);
pidff_playback_pid(pidff, pid_id, 0);
pidff_erase_pid(pidff, pid_id);
@@ -574,11 +783,9 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
pidff_set_effect_report(pidff, effect);
if (!old || pidff_needs_set_constant(effect, old))
pidff_set_constant_force_report(pidff, effect);
- if (!old ||
- pidff_needs_set_envelope(&effect->u.constant.envelope,
- &old->u.constant.envelope))
- pidff_set_envelope_report(pidff,
- &effect->u.constant.envelope);
+ if (pidff_needs_set_envelope(&effect->u.constant.envelope,
+ old ? &old->u.constant.envelope : NULL))
+ pidff_set_envelope_report(pidff, &effect->u.constant.envelope);
break;
case FF_PERIODIC:
@@ -604,6 +811,9 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
return -EINVAL;
}
+ if (pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
+ type_id = PID_SINE;
+
error = pidff_request_effect_upload(pidff,
pidff->type_id[type_id]);
if (error)
@@ -613,11 +823,9 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
pidff_set_effect_report(pidff, effect);
if (!old || pidff_needs_set_periodic(effect, old))
pidff_set_periodic_report(pidff, effect);
- if (!old ||
- pidff_needs_set_envelope(&effect->u.periodic.envelope,
- &old->u.periodic.envelope))
- pidff_set_envelope_report(pidff,
- &effect->u.periodic.envelope);
+ if (pidff_needs_set_envelope(&effect->u.periodic.envelope,
+ old ? &old->u.periodic.envelope : NULL))
+ pidff_set_envelope_report(pidff, &effect->u.periodic.envelope);
break;
case FF_RAMP:
@@ -631,56 +839,32 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
pidff_set_effect_report(pidff, effect);
if (!old || pidff_needs_set_ramp(effect, old))
pidff_set_ramp_force_report(pidff, effect);
- if (!old ||
- pidff_needs_set_envelope(&effect->u.ramp.envelope,
- &old->u.ramp.envelope))
- pidff_set_envelope_report(pidff,
- &effect->u.ramp.envelope);
+ if (pidff_needs_set_envelope(&effect->u.ramp.envelope,
+ old ? &old->u.ramp.envelope : NULL))
+ pidff_set_envelope_report(pidff, &effect->u.ramp.envelope);
break;
case FF_SPRING:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_SPRING]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_condition(effect, old))
- pidff_set_condition_report(pidff, effect);
- break;
-
- case FF_FRICTION:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_FRICTION]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_condition(effect, old))
- pidff_set_condition_report(pidff, effect);
- break;
-
case FF_DAMPER:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_DAMPER]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- pidff_set_effect_report(pidff, effect);
- if (!old || pidff_needs_set_condition(effect, old))
- pidff_set_condition_report(pidff, effect);
- break;
-
case FF_INERTIA:
+ case FF_FRICTION:
if (!old) {
+ switch(effect->type) {
+ case FF_SPRING:
+ type_id = PID_SPRING;
+ break;
+ case FF_DAMPER:
+ type_id = PID_DAMPER;
+ break;
+ case FF_INERTIA:
+ type_id = PID_INERTIA;
+ break;
+ case FF_FRICTION:
+ type_id = PID_FRICTION;
+ break;
+ }
error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_INERTIA]);
+ pidff->type_id[type_id]);
if (error)
return error;
}
@@ -709,11 +893,7 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
*/
static void pidff_set_gain(struct input_dev *dev, u16 gain)
{
- struct pidff_device *pidff = dev->ff->private;
-
- pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain);
- hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
- HID_REQ_SET_REPORT);
+ pidff_set_gain_report(dev->ff->private, gain);
}
static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
@@ -736,7 +916,10 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0;
pidff_set(&pidff->set_effect[PID_GAIN], magnitude);
pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
- pidff->set_effect[PID_START_DELAY].value[0] = 0;
+
+ /* Omit setting delay field if it's missing */
+ if (!(pidff->quirks & HID_PIDFF_QUIRK_MISSING_DELAY))
+ pidff->set_effect[PID_START_DELAY].value[0] = 0;
hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
HID_REQ_SET_REPORT);
@@ -747,9 +930,7 @@ static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
*/
static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
- struct pidff_device *pidff = dev->ff->private;
-
- pidff_autocenter(pidff, magnitude);
+ pidff_autocenter(dev->ff->private, magnitude);
}
/*
@@ -758,7 +939,13 @@ static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude)
static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
struct hid_report *report, int count, int strict)
{
+ if (!report) {
+ pr_debug("pidff_find_fields, null report\n");
+ return -1;
+ }
+
int i, j, k, found;
+ int return_value = 0;
for (k = 0; k < count; k++) {
found = 0;
@@ -783,12 +970,22 @@ static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
if (found)
break;
}
- if (!found && strict) {
+ if (!found && table[k] == pidff_set_effect[PID_START_DELAY]) {
+ pr_debug("Delay field not found, but that's OK\n");
+ pr_debug("Setting MISSING_DELAY quirk\n");
+ return_value |= HID_PIDFF_QUIRK_MISSING_DELAY;
+ }
+ else if (!found && table[k] == pidff_set_condition[PID_PARAM_BLOCK_OFFSET]) {
+ pr_debug("PBO field not found, but that's OK\n");
+ pr_debug("Setting MISSING_PBO quirk\n");
+ return_value |= HID_PIDFF_QUIRK_MISSING_PBO;
+ }
+ else if (!found && strict) {
pr_debug("failed to locate %d\n", k);
return -1;
}
}
- return 0;
+ return return_value;
}
/*
@@ -871,6 +1068,11 @@ static int pidff_reports_ok(struct pidff_device *pidff)
static struct hid_field *pidff_find_special_field(struct hid_report *report,
int usage, int enforce_min)
{
+ if (!report) {
+ pr_debug("pidff_find_special_field, null report\n");
+ return NULL;
+ }
+
int i;
for (i = 0; i < report->maxfield; i++) {
@@ -923,22 +1125,24 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
pidff->create_new_effect_type =
pidff_find_special_field(pidff->reports[PID_CREATE_NEW_EFFECT],
- 0x25, 1);
+ PID_EFFECT_TYPE, 1);
pidff->set_effect_type =
pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
- 0x25, 1);
+ PID_EFFECT_TYPE, 1);
pidff->effect_direction =
pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
- 0x57, 0);
+ PID_DIRECTION, 0);
pidff->device_control =
pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL],
- 0x96, 1);
+ PID_DEVICE_CONTROL_ARRAY,
+ !(pidff->quirks & HID_PIDFF_QUIRK_PERMISSIVE_CONTROL));
+
pidff->block_load_status =
pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD],
- 0x8b, 1);
+ PID_BLOCK_LOAD_STATUS, 1);
pidff->effect_operation_status =
pidff_find_special_field(pidff->reports[PID_EFFECT_OPERATION],
- 0x78, 1);
+ PID_EFFECT_OPERATION_ARRAY, 1);
hid_dbg(pidff->hid, "search done\n");
@@ -967,10 +1171,6 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
return -1;
}
- pidff_find_special_keys(pidff->control_id, pidff->device_control,
- pidff_device_control,
- sizeof(pidff_device_control));
-
PIDFF_FIND_SPECIAL_KEYS(control_id, device_control, device_control);
if (!PIDFF_FIND_SPECIAL_KEYS(type_id, create_new_effect_type,
@@ -1049,7 +1249,6 @@ static int pidff_find_effects(struct pidff_device *pidff,
set_bit(FF_FRICTION, dev->ffbit);
return 0;
-
}
#define PIDFF_FIND_FIELDS(name, report, strict) \
@@ -1062,12 +1261,19 @@ static int pidff_find_effects(struct pidff_device *pidff,
*/
static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
{
- int envelope_ok = 0;
+ int status = 0;
- if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
+ /* Save info about the device not having the DELAY ffb field. */
+ status = PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1);
+ if (status == -1) {
hid_err(pidff->hid, "unknown set_effect report layout\n");
return -ENODEV;
}
+ pidff->quirks |= status;
+
+ if (status & HID_PIDFF_QUIRK_MISSING_DELAY)
+ hid_dbg(pidff->hid, "Adding MISSING_DELAY quirk\n");
+
PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0);
if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) {
@@ -1085,13 +1291,10 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
return -ENODEV;
}
- if (!PIDFF_FIND_FIELDS(set_envelope, PID_SET_ENVELOPE, 1))
- envelope_ok = 1;
-
if (pidff_find_special_fields(pidff) || pidff_find_effects(pidff, dev))
return -ENODEV;
- if (!envelope_ok) {
+ if (PIDFF_FIND_FIELDS(set_envelope, PID_SET_ENVELOPE, 1)) {
if (test_and_clear_bit(FF_CONSTANT, dev->ffbit))
hid_warn(pidff->hid,
"has constant effect but no envelope\n");
@@ -1116,16 +1319,20 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
clear_bit(FF_RAMP, dev->ffbit);
}
- if ((test_bit(FF_SPRING, dev->ffbit) ||
- test_bit(FF_DAMPER, dev->ffbit) ||
- test_bit(FF_FRICTION, dev->ffbit) ||
- test_bit(FF_INERTIA, dev->ffbit)) &&
- PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
- hid_warn(pidff->hid, "unknown condition effect layout\n");
- clear_bit(FF_SPRING, dev->ffbit);
- clear_bit(FF_DAMPER, dev->ffbit);
- clear_bit(FF_FRICTION, dev->ffbit);
- clear_bit(FF_INERTIA, dev->ffbit);
+ if (test_bit(FF_SPRING, dev->ffbit) ||
+ test_bit(FF_DAMPER, dev->ffbit) ||
+ test_bit(FF_FRICTION, dev->ffbit) ||
+ test_bit(FF_INERTIA, dev->ffbit)) {
+ status = PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1);
+
+ if (status < 0) {
+ hid_warn(pidff->hid, "unknown condition effect layout\n");
+ clear_bit(FF_SPRING, dev->ffbit);
+ clear_bit(FF_DAMPER, dev->ffbit);
+ clear_bit(FF_FRICTION, dev->ffbit);
+ clear_bit(FF_INERTIA, dev->ffbit);
+ }
+ pidff->quirks |= status;
}
if (test_bit(FF_PERIODIC, dev->ffbit) &&
@@ -1143,46 +1350,6 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
}
/*
- * Reset the device
- */
-static void pidff_reset(struct pidff_device *pidff)
-{
- struct hid_device *hid = pidff->hid;
- int i = 0;
-
- pidff->device_control->value[0] = pidff->control_id[PID_RESET];
- /* We reset twice as sometimes hid_wait_io isn't waiting long enough */
- hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
- hid_hw_wait(hid);
- hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
- hid_hw_wait(hid);
-
- pidff->device_control->value[0] =
- pidff->control_id[PID_ENABLE_ACTUATORS];
- hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
- hid_hw_wait(hid);
-
- /* pool report is sometimes messed up, refetch it */
- hid_hw_request(hid, pidff->reports[PID_POOL], HID_REQ_GET_REPORT);
- hid_hw_wait(hid);
-
- if (pidff->pool[PID_SIMULTANEOUS_MAX].value) {
- while (pidff->pool[PID_SIMULTANEOUS_MAX].value[0] < 2) {
- if (i++ > 20) {
- hid_warn(pidff->hid,
- "device reports %d simultaneous effects\n",
- pidff->pool[PID_SIMULTANEOUS_MAX].value[0]);
- break;
- }
- hid_dbg(pidff->hid, "pid_pool requested again\n");
- hid_hw_request(hid, pidff->reports[PID_POOL],
- HID_REQ_GET_REPORT);
- hid_hw_wait(hid);
- }
- }
-}
-
-/*
* Test if autocenter modification is using the supported method
*/
static int pidff_check_autocenter(struct pidff_device *pidff,
@@ -1206,24 +1373,23 @@ static int pidff_check_autocenter(struct pidff_device *pidff,
if (pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] ==
pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + 1) {
- pidff_autocenter(pidff, 0xffff);
+ pidff_autocenter(pidff, U16_MAX);
set_bit(FF_AUTOCENTER, dev->ffbit);
} else {
hid_notice(pidff->hid,
"device has unknown autocenter control method\n");
}
-
pidff_erase_pid(pidff,
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]);
return 0;
-
}
/*
* Check if the device is PID and initialize it
+ * Set initial quirks
*/
-int hid_pidff_init(struct hid_device *hid)
+int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks)
{
struct pidff_device *pidff;
struct hid_input *hidinput = list_entry(hid->inputs.next,
@@ -1245,6 +1411,8 @@ int hid_pidff_init(struct hid_device *hid)
return -ENOMEM;
pidff->hid = hid;
+ pidff->quirks = initial_quirks;
+ pidff->effect_count = 0;
hid_device_io_start(hid);
@@ -1261,14 +1429,9 @@ int hid_pidff_init(struct hid_device *hid)
if (error)
goto fail;
- pidff_reset(pidff);
-
- if (test_bit(FF_GAIN, dev->ffbit)) {
- pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], 0xffff);
- hid_hw_request(hid, pidff->reports[PID_DEVICE_GAIN],
- HID_REQ_SET_REPORT);
- }
-
+ /* pool report is sometimes messed up, refetch it */
+ pidff_fetch_pool(pidff);
+ pidff_set_gain_report(pidff, U16_MAX);
error = pidff_check_autocenter(pidff, dev);
if (error)
goto fail;
@@ -1311,6 +1474,7 @@ int hid_pidff_init(struct hid_device *hid)
ff->playback = pidff_playback;
hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+ hid_dbg(dev, "Active quirks mask: 0x%x\n", pidff->quirks);
hid_device_io_stop(hid);
@@ -1322,3 +1486,14 @@ int hid_pidff_init(struct hid_device *hid)
kfree(pidff);
return error;
}
+EXPORT_SYMBOL_GPL(hid_pidff_init_with_quirks);
+
+/*
+ * Check if the device is PID and initialize it
+ * Wrapper made to keep the compatibility with old
+ * init function
+ */
+int hid_pidff_init(struct hid_device *hid)
+{
+ return hid_pidff_init_with_quirks(hid, 0);
+}
diff --git a/drivers/hid/usbhid/hid-pidff.h b/drivers/hid/usbhid/hid-pidff.h
new file mode 100644
index 000000000000..dda571e0a5bd
--- /dev/null
+++ b/drivers/hid/usbhid/hid-pidff.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __HID_PIDFF_H
+#define __HID_PIDFF_H
+
+#include <linux/hid.h>
+
+/* HID PIDFF quirks */
+
+/* Delay field (0xA7) missing. Skip it during set effect report upload */
+#define HID_PIDFF_QUIRK_MISSING_DELAY BIT(0)
+
+/* Missing Paramter block offset (0x23). Skip it during SET_CONDITION
+ report upload */
+#define HID_PIDFF_QUIRK_MISSING_PBO BIT(1)
+
+/* Initialise device control field even if logical_minimum != 1 */
+#define HID_PIDFF_QUIRK_PERMISSIVE_CONTROL BIT(2)
+
+/* Use fixed 0x4000 direction during SET_EFFECT report upload */
+#define HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION BIT(3)
+
+/* Force all periodic effects to be uploaded as SINE */
+#define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4)
+
+#ifdef CONFIG_HID_PID
+int hid_pidff_init(struct hid_device *hid);
+int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks);
+#else
+#define hid_pidff_init NULL
+#define hid_pidff_init_with_quirks NULL
+#endif
+
+#endif
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c
index c439ed2f16db..af6bc76dbf64 100644
--- a/drivers/hid/usbhid/usbkbd.c
+++ b/drivers/hid/usbhid/usbkbd.c
@@ -160,7 +160,7 @@ static int usb_kbd_event(struct input_dev *dev, unsigned int type,
return -1;
spin_lock_irqsave(&kbd->leds_lock, flags);
- kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
+ kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 4) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
(!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) |
(!!test_bit(LED_NUML, dev->led));
diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h
index 6f1443999d1d..1deacb4568cb 100644
--- a/drivers/hid/wacom.h
+++ b/drivers/hid/wacom.h
@@ -218,6 +218,14 @@ static inline __u32 wacom_s32tou(s32 value, __u8 n)
return value & (1 << (n - 1)) ? value & (~(~0U << n)) : value;
}
+static inline u32 wacom_rescale(u32 value, u32 in_max, u32 out_max)
+{
+ if (in_max == 0 || out_max == 0)
+ return 0;
+ value = clamp(value, 0, in_max);
+ return DIV_ROUND_CLOSEST(value * out_max, in_max);
+}
+
extern const struct hid_device_id wacom_ids[];
void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len);
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index 34428349fa31..9a57504e51a1 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -69,16 +69,34 @@ static void wacom_wac_queue_flush(struct hid_device *hdev,
struct kfifo_rec_ptr_2 *fifo)
{
while (!kfifo_is_empty(fifo)) {
- u8 buf[WACOM_PKGLEN_MAX];
- int size;
+ int size = kfifo_peek_len(fifo);
+ u8 *buf;
+ unsigned int count;
int err;
- size = kfifo_out(fifo, buf, sizeof(buf));
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf) {
+ kfifo_skip(fifo);
+ continue;
+ }
+
+ count = kfifo_out(fifo, buf, size);
+ if (count != size) {
+ // Hard to say what is the "right" action in this
+ // circumstance. Skipping the entry and continuing
+ // to flush seems reasonable enough, however.
+ hid_warn(hdev, "%s: removed fifo entry with unexpected size\n",
+ __func__);
+ kfree(buf);
+ continue;
+ }
err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false);
if (err) {
hid_warn(hdev, "%s: unable to flush event due to error %d\n",
__func__, err);
}
+
+ kfree(buf);
}
}
@@ -158,13 +176,10 @@ static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report,
if (wacom->wacom_wac.features.type == BOOTLOADER)
return 0;
- if (size > WACOM_PKGLEN_MAX)
- return 1;
-
if (wacom_wac_pen_serial_enforce(hdev, report, raw_data, size))
return -1;
- memcpy(wacom->wacom_wac.data, raw_data, size);
+ wacom->wacom_wac.data = raw_data;
wacom_wac_irq(&wacom->wacom_wac, size);
@@ -1084,6 +1099,17 @@ static ssize_t wacom_luminance_store(struct wacom *wacom, u8 *dest,
mutex_lock(&wacom->lock);
*dest = value & 0x7f;
+ for (unsigned int i = 0; i < wacom->led.count; i++) {
+ struct wacom_group_leds *group = &wacom->led.groups[i];
+
+ for (unsigned int j = 0; j < group->count; j++) {
+ if (dest == &wacom->led.llv)
+ group->leds[j].llv = *dest;
+ else if (dest == &wacom->led.hlv)
+ group->leds[j].hlv = *dest;
+ }
+ }
+
err = wacom_led_control(wacom);
mutex_unlock(&wacom->lock);
@@ -1275,6 +1301,7 @@ static void wacom_devm_kfifo_release(struct device *dev, void *res)
static int wacom_devm_kfifo_alloc(struct wacom *wacom)
{
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+ int fifo_size = min(PAGE_SIZE, 10 * wacom_wac->features.pktlen);
struct kfifo_rec_ptr_2 *pen_fifo;
int error;
@@ -1285,7 +1312,7 @@ static int wacom_devm_kfifo_alloc(struct wacom *wacom)
if (!pen_fifo)
return -ENOMEM;
- error = kfifo_alloc(pen_fifo, WACOM_PKGLEN_MAX, GFP_KERNEL);
+ error = kfifo_alloc(pen_fifo, fifo_size, GFP_KERNEL);
if (error) {
devres_free(pen_fifo);
return error;
@@ -1302,10 +1329,10 @@ enum led_brightness wacom_leds_brightness_get(struct wacom_led *led)
struct wacom *wacom = led->wacom;
if (wacom->led.max_hlv)
- return led->hlv * LED_FULL / wacom->led.max_hlv;
+ return wacom_rescale(led->hlv, wacom->led.max_hlv, LED_FULL);
if (wacom->led.max_llv)
- return led->llv * LED_FULL / wacom->led.max_llv;
+ return wacom_rescale(led->llv, wacom->led.max_llv, LED_FULL);
/* device doesn't support brightness tuning */
return LED_FULL;
@@ -1337,8 +1364,8 @@ static int wacom_led_brightness_set(struct led_classdev *cdev,
goto out;
}
- led->llv = wacom->led.llv = wacom->led.max_llv * brightness / LED_FULL;
- led->hlv = wacom->led.hlv = wacom->led.max_hlv * brightness / LED_FULL;
+ led->llv = wacom->led.llv = wacom_rescale(brightness, LED_FULL, wacom->led.max_llv);
+ led->hlv = wacom->led.hlv = wacom_rescale(brightness, LED_FULL, wacom->led.max_hlv);
wacom->led.groups[led->group].select = led->id;
@@ -2021,14 +2048,18 @@ static int wacom_initialize_remotes(struct wacom *wacom)
remote->remote_dir = kobject_create_and_add("wacom_remote",
&wacom->hdev->dev.kobj);
- if (!remote->remote_dir)
+ if (!remote->remote_dir) {
+ kfifo_free(&remote->remote_fifo);
return -ENOMEM;
+ }
error = sysfs_create_files(remote->remote_dir, remote_unpair_attrs);
if (error) {
hid_err(wacom->hdev,
"cannot create sysfs group err: %d\n", error);
+ kfifo_free(&remote->remote_fifo);
+ kobject_put(remote->remote_dir);
return error;
}
@@ -2341,12 +2372,16 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
unsigned int connect_mask = HID_CONNECT_HIDRAW;
features->pktlen = wacom_compute_pktlen(hdev);
- if (features->pktlen > WACOM_PKGLEN_MAX)
- return -EINVAL;
+ if (!features->pktlen)
+ return -ENODEV;
if (!devres_open_group(&hdev->dev, wacom, GFP_KERNEL))
return -ENOMEM;
+ error = wacom_devm_kfifo_alloc(wacom);
+ if (error)
+ goto fail;
+
wacom->resources = true;
error = wacom_allocate_inputs(wacom);
@@ -2810,10 +2845,6 @@ static int wacom_probe(struct hid_device *hdev,
if (features->check_for_hid_type && features->hid_type != hdev->type)
return -ENODEV;
- error = wacom_devm_kfifo_alloc(wacom);
- if (error)
- return error;
-
wacom_wac->hid_data.inputmode = -1;
wacom_wac->mode_report = -1;
@@ -2874,11 +2905,12 @@ static void wacom_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
cancel_delayed_work_sync(&wacom->init_work);
+ cancel_delayed_work_sync(&wacom->aes_battery_work);
cancel_work_sync(&wacom->wireless_work);
cancel_work_sync(&wacom->battery_work);
cancel_work_sync(&wacom->remote_work);
cancel_work_sync(&wacom->mode_change_work);
- del_timer_sync(&wacom->idleprox_timer);
+ timer_delete_sync(&wacom->idleprox_timer);
if (hdev->bus == BUS_BLUETOOTH)
device_remove_file(&hdev->dev, &dev_attr_speed);
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index b60bfafc6a8f..5107a676e24f 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -1201,12 +1201,10 @@ static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
{
- unsigned char data[WACOM_PKGLEN_MAX];
+ u8 *data = kmemdup(wacom->data, len, GFP_KERNEL);
int i = 1;
unsigned power_raw, battery_capacity, bat_charging, ps_connected;
- memcpy(data, wacom->data, len);
-
switch (data[0]) {
case 0x04:
wacom_intuos_bt_process_data(wacom, data + i);
@@ -1230,8 +1228,10 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
dev_dbg(wacom->pen_input->dev.parent,
"Unknown report: %d,%d size:%zu\n",
data[0], data[1], len);
- return 0;
+ break;
}
+
+ kfree(data);
return 0;
}
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index 0c3c6a6aaae9..d4f7d8ca1e7e 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -7,9 +7,6 @@
#include <linux/hid.h>
#include <linux/kfifo.h>
-/* maximum packet length for USB/BT devices */
-#define WACOM_PKGLEN_MAX 361
-
#define WACOM_NAME_MAX 64
#define WACOM_MAX_REMOTES 5
#define WACOM_STATUS_UNKNOWN 255
@@ -277,7 +274,7 @@ struct wacom_features {
unsigned touch_max;
int oVid;
int oPid;
- int pktlen;
+ unsigned int pktlen;
bool check_for_hid_type;
int hid_type;
};
@@ -341,7 +338,7 @@ struct wacom_wac {
char pen_name[WACOM_NAME_MAX];
char touch_name[WACOM_NAME_MAX];
char pad_name[WACOM_NAME_MAX];
- unsigned char data[WACOM_PKGLEN_MAX];
+ u8 *data;
int tool[2];
int id[2];
__u64 serial[2];