summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig156
-rw-r--r--drivers/hid/Makefile20
-rw-r--r--drivers/hid/amd-sfh-hid/Kconfig2
-rw-r--r--drivers/hid/amd-sfh-hid/Makefile2
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_client.c67
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_common.h27
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_hid.c4
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_hid.h4
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_pcie.c185
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_pcie.h7
-rw-r--r--drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c7
-rw-r--r--drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h3
-rw-r--r--drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h21
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c35
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c125
-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.c107
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h41
-rw-r--r--drivers/hid/bpf/Kconfig2
-rw-r--r--drivers/hid/bpf/Makefile2
-rw-r--r--drivers/hid/bpf/entrypoints/README4
-rw-r--r--drivers/hid/bpf/entrypoints/entrypoints.bpf.c25
-rw-r--r--drivers/hid/bpf/entrypoints/entrypoints.lskel.h248
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.c587
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.h13
-rw-r--r--drivers/hid/bpf/hid_bpf_jmp_table.c565
-rw-r--r--drivers/hid/bpf/hid_bpf_struct_ops.c326
-rw-r--r--drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c190
-rw-r--r--drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c62
-rw-r--r--drivers/hid/bpf/progs/Huion__Dial-2.bpf.c636
-rw-r--r--drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c563
-rw-r--r--drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c579
-rw-r--r--drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c366
-rw-r--r--drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c1395
-rw-r--r--drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c724
-rw-r--r--drivers/hid/bpf/progs/Huion__KeydialK20.bpf.c531
-rw-r--r--drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c63
-rw-r--r--drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c86
-rw-r--r--drivers/hid/bpf/progs/Makefile (renamed from drivers/hid/bpf/entrypoints/Makefile)18
-rw-r--r--drivers/hid/bpf/progs/Microsoft__Xbox-Elite-2.bpf.c136
-rw-r--r--drivers/hid/bpf/progs/Mistel__MD770.bpf.c154
-rw-r--r--drivers/hid/bpf/progs/README102
-rw-r--r--drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c148
-rw-r--r--drivers/hid/bpf/progs/TUXEDO__Sirius-16-Gen1-and-Gen2.bpf.c47
-rw-r--r--drivers/hid/bpf/progs/Thrustmaster__TCA-Yoke-Boeing.bpf.c144
-rw-r--r--drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c321
-rw-r--r--drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c177
-rw-r--r--drivers/hid/bpf/progs/XPPen__ACK05.bpf.c331
-rw-r--r--drivers/hid/bpf/progs/XPPen__Artist24.bpf.c231
-rw-r--r--drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c324
-rw-r--r--drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c305
-rw-r--r--drivers/hid/bpf/progs/XPPen__Deco02.bpf.c359
-rw-r--r--drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c231
-rw-r--r--drivers/hid/bpf/progs/hid_bpf.h21
-rw-r--r--drivers/hid/bpf/progs/hid_bpf_async.h219
-rw-r--r--drivers/hid/bpf/progs/hid_bpf_helpers.h188
-rw-r--r--drivers/hid/bpf/progs/hid_report_helpers.h2982
-rw-r--r--drivers/hid/hid-a4tech.c1
-rw-r--r--drivers/hid/hid-alps.c2
-rw-r--r--drivers/hid/hid-apple.c344
-rw-r--r--drivers/hid/hid-appleir.c6
-rw-r--r--drivers/hid/hid-appletb-bl.c204
-rw-r--r--drivers/hid/hid-appletb-kbd.c519
-rw-r--r--drivers/hid/hid-asus.c300
-rw-r--r--drivers/hid/hid-aureal.c3
-rw-r--r--drivers/hid/hid-belkin.c1
-rw-r--r--drivers/hid/hid-betopff.c1
-rw-r--r--drivers/hid/hid-bigbenff.c7
-rw-r--r--drivers/hid/hid-cherry.c3
-rw-r--r--drivers/hid/hid-chicony.c5
-rw-r--r--drivers/hid/hid-cmedia.c6
-rw-r--r--drivers/hid/hid-core.c476
-rw-r--r--drivers/hid/hid-corsair-void.c832
-rw-r--r--drivers/hid/hid-corsair.c8
-rw-r--r--drivers/hid/hid-cougar.c6
-rw-r--r--drivers/hid/hid-cp2112.c286
-rw-r--r--drivers/hid/hid-cypress.c3
-rw-r--r--drivers/hid/hid-debug.c3453
-rw-r--r--drivers/hid/hid-dr.c9
-rw-r--r--drivers/hid/hid-elecom.c17
-rw-r--r--drivers/hid/hid-elo.c1
-rw-r--r--drivers/hid/hid-emsff.c1
-rw-r--r--drivers/hid/hid-evision.c22
-rw-r--r--drivers/hid/hid-ezkey.c1
-rw-r--r--drivers/hid/hid-gaff.c1
-rw-r--r--drivers/hid/hid-gembird.c2
-rw-r--r--drivers/hid/hid-generic.c14
-rw-r--r--drivers/hid/hid-glorious.c18
-rw-r--r--drivers/hid/hid-goodix-spi.c818
-rw-r--r--drivers/hid/hid-google-hammer.c36
-rw-r--r--drivers/hid/hid-google-stadiaff.c159
-rw-r--r--drivers/hid/hid-gyration.c1
-rw-r--r--drivers/hid/hid-haptic.c580
-rw-r--r--drivers/hid/hid-haptic.h127
-rw-r--r--drivers/hid/hid-holtek-kbd.c11
-rw-r--r--drivers/hid/hid-holtek-mouse.c5
-rw-r--r--drivers/hid/hid-hyperv.c62
-rw-r--r--drivers/hid/hid-ids.h163
-rw-r--r--drivers/hid/hid-input-test.c10
-rw-r--r--drivers/hid/hid-input.c194
-rw-r--r--drivers/hid/hid-ite.c3
-rw-r--r--drivers/hid/hid-kensington.c3
-rw-r--r--drivers/hid/hid-keytouch.c9
-rw-r--r--drivers/hid/hid-kye.c80
-rw-r--r--drivers/hid/hid-kysona.c290
-rw-r--r--drivers/hid/hid-lcpower.c1
-rw-r--r--drivers/hid/hid-lenovo.c298
-rw-r--r--drivers/hid/hid-letsketch.c6
-rw-r--r--drivers/hid/hid-lg-g15.c608
-rw-r--r--drivers/hid/hid-lg.c31
-rw-r--r--drivers/hid/hid-lg3ff.c4
-rw-r--r--drivers/hid/hid-lg4ff.c15
-rw-r--r--drivers/hid/hid-logitech-dj.c200
-rw-r--r--drivers/hid/hid-logitech-hidpp.c433
-rw-r--r--drivers/hid/hid-macally.c4
-rw-r--r--drivers/hid/hid-magicmouse.c184
-rw-r--r--drivers/hid/hid-maltron.c9
-rw-r--r--drivers/hid/hid-mcp2200.c397
-rw-r--r--drivers/hid/hid-mcp2221.c200
-rw-r--r--drivers/hid/hid-megaworld.c1
-rw-r--r--drivers/hid/hid-mf.c1
-rw-r--r--drivers/hid/hid-microsoft.c3
-rw-r--r--drivers/hid/hid-monterey.c3
-rw-r--r--drivers/hid/hid-multitouch.c314
-rw-r--r--drivers/hid/hid-nintendo.c1231
-rw-r--r--drivers/hid/hid-nti.c2
-rw-r--r--drivers/hid/hid-ntrig.c11
-rw-r--r--drivers/hid/hid-nvidia-shield.c452
-rw-r--r--drivers/hid/hid-ortek.c3
-rw-r--r--drivers/hid/hid-petalynx.c3
-rw-r--r--drivers/hid/hid-picolcd_backlight.c12
-rw-r--r--drivers/hid/hid-picolcd_core.c20
-rw-r--r--drivers/hid/hid-picolcd_fb.c95
-rw-r--r--drivers/hid/hid-picolcd_lcd.c8
-rw-r--r--drivers/hid/hid-pl.c1
-rw-r--r--drivers/hid/hid-plantronics.c121
-rw-r--r--drivers/hid/hid-playstation.c1175
-rw-r--r--drivers/hid/hid-primax.c1
-rw-r--r--drivers/hid/hid-prodikeys.c130
-rw-r--r--drivers/hid/hid-pxrc.c6
-rw-r--r--drivers/hid/hid-quirks.c71
-rw-r--r--drivers/hid/hid-razer.c1
-rw-r--r--drivers/hid/hid-redragon.c3
-rw-r--r--drivers/hid/hid-retrode.c1
-rw-r--r--drivers/hid/hid-rmi.c10
-rw-r--r--drivers/hid/hid-roccat-arvo.c38
-rw-r--r--drivers/hid/hid-roccat-common.h14
-rw-r--r--drivers/hid/hid-roccat-isku.c35
-rw-r--r--drivers/hid/hid-roccat-kone.c52
-rw-r--r--drivers/hid/hid-roccat-koneplus.c58
-rw-r--r--drivers/hid/hid-roccat-konepure.c24
-rw-r--r--drivers/hid/hid-roccat-kovaplus.c58
-rw-r--r--drivers/hid/hid-roccat-lua.c6
-rw-r--r--drivers/hid/hid-roccat-pyra.c64
-rw-r--r--drivers/hid/hid-roccat-ryos.c22
-rw-r--r--drivers/hid/hid-roccat-savu.c22
-rw-r--r--drivers/hid/hid-roccat.c2
-rw-r--r--drivers/hid/hid-saitek.c3
-rw-r--r--drivers/hid/hid-samsung.c440
-rw-r--r--drivers/hid/hid-semitek.c5
-rw-r--r--drivers/hid/hid-sensor-custom.c19
-rw-r--r--drivers/hid/hid-sensor-hub.c25
-rw-r--r--drivers/hid/hid-sigmamicro.c4
-rw-r--r--drivers/hid/hid-sjoy.c1
-rw-r--r--drivers/hid/hid-sony.c33
-rw-r--r--drivers/hid/hid-speedlink.c1
-rw-r--r--drivers/hid/hid-steam.c788
-rw-r--r--drivers/hid/hid-steelseries.c493
-rw-r--r--drivers/hid/hid-sunplus.c3
-rw-r--r--drivers/hid/hid-thrustmaster.c9
-rw-r--r--drivers/hid/hid-tivo.c1
-rw-r--r--drivers/hid/hid-tmff.c1
-rw-r--r--drivers/hid/hid-topre.c11
-rw-r--r--drivers/hid/hid-topseed.c1
-rw-r--r--drivers/hid/hid-twinhan.c1
-rw-r--r--drivers/hid/hid-uclogic-core-test.c7
-rw-r--r--drivers/hid/hid-uclogic-core.c113
-rw-r--r--drivers/hid/hid-uclogic-params-test.c16
-rw-r--r--drivers/hid/hid-uclogic-params.c184
-rw-r--r--drivers/hid/hid-uclogic-params.h20
-rw-r--r--drivers/hid/hid-uclogic-rdesc-test.c2
-rw-r--r--drivers/hid/hid-uclogic-rdesc.c202
-rw-r--r--drivers/hid/hid-uclogic-rdesc.h32
-rw-r--r--drivers/hid/hid-universal-pidff.c204
-rw-r--r--drivers/hid/hid-viewsonic.c9
-rw-r--r--drivers/hid/hid-vivaldi-common.c1
-rw-r--r--drivers/hid/hid-vrc2.c6
-rw-r--r--drivers/hid/hid-waltop.c31
-rw-r--r--drivers/hid/hid-wiimote-core.c4
-rw-r--r--drivers/hid/hid-wiimote-debug.c10
-rw-r--r--drivers/hid/hid-winwing.c268
-rw-r--r--drivers/hid/hid-xiaomi.c8
-rw-r--r--drivers/hid/hid-xinmo.c1
-rw-r--r--drivers/hid/hid-zpff.c1
-rw-r--r--drivers/hid/hid-zydacron.c3
-rw-r--r--drivers/hid/hidraw.c282
-rw-r--r--drivers/hid/i2c-hid/Kconfig8
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-acpi.c13
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c632
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of-elan.c128
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of.c7
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.h2
-rw-r--r--drivers/hid/intel-ish-hid/Kconfig1
-rw-r--r--drivers/hid/intel-ish-hid/Makefile3
-rw-r--r--drivers/hid/intel-ish-hid/ipc/hw-ish.h47
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c154
-rw-r--r--drivers/hid/intel-ish-hid/ipc/pci-ish.c228
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-fw-loader.c64
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid-client.c104
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid.c4
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid.h11
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/bus.c29
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/bus.h1
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client-buffers.c21
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.c216
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.h3
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/hbm.c23
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/init.c38
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h51
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/loader.c432
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/loader.h265
-rw-r--r--drivers/hid/intel-thc-hid/Kconfig42
-rw-r--r--drivers/hid/intel-thc-hid/Makefile23
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c1044
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h227
-rw-r--r--drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-hid.c165
-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.c1006
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h176
-rw-r--r--drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c164
-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.c413
-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.c1719
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h133
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c1009
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h154
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h886
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c94
-rw-r--r--drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h26
-rw-r--r--drivers/hid/surface-hid/Kconfig2
-rw-r--r--drivers/hid/surface-hid/surface_hid.c2
-rw-r--r--drivers/hid/surface-hid/surface_hid_core.c2
-rw-r--r--drivers/hid/surface-hid/surface_kbd.c5
-rw-r--r--drivers/hid/uhid.c16
-rw-r--r--drivers/hid/usbhid/Kconfig3
-rw-r--r--drivers/hid/usbhid/hid-core.c49
-rw-r--r--drivers/hid/usbhid/hid-pidff.c1059
-rw-r--r--drivers/hid/usbhid/hid-pidff.h32
-rw-r--r--drivers/hid/usbhid/usbkbd.c2
-rw-r--r--drivers/hid/wacom.h12
-rw-r--r--drivers/hid/wacom_sys.c214
-rw-r--r--drivers/hid/wacom_wac.c255
-rw-r--r--drivers/hid/wacom_wac.h21
256 files changed, 38712 insertions, 6080 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e11c1c803676..920a64b66b25 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -92,6 +92,17 @@ config HID_GENERIC
If unsure, say Y.
+config HID_HAPTIC
+ bool "Haptic touchpad support"
+ default n
+ help
+ Support for touchpads with force sensors and haptic actuators instead of a
+ traditional button.
+ Adds extra parsing and FF device for the hid multitouch driver.
+ It can be used for Elan 2703 haptic touchpad.
+
+ If unsure, say N.
+
menu "Special HID drivers"
config HID_A4TECH
@@ -148,6 +159,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
@@ -213,13 +251,16 @@ config HID_CHICONY
config HID_CORSAIR
tristate "Corsair devices"
depends on USB_HID && LEDS_CLASS
+ select POWER_SUPPLY
help
Support for Corsair devices that are not fully compliant with the
HID standard.
+ Support for Corsair Void headsets.
Supported devices:
- Vengeance K90
- Scimitar PRO RGB
+ - Corsair Void headsets
config HID_COUGAR
tristate "Cougar devices"
@@ -342,6 +383,7 @@ config HID_EVISION
help
Support for some EVision keyboards. Note that this is needed only when
applying customization using userspace programs.
+ Support for some EVision devices requiring report descriptor fixups.
config HID_EZKEY
tristate "Ezkey BTC 8193 keyboard"
@@ -404,6 +446,12 @@ config HID_VIVALDI_COMMON
option so that drivers can use common code to parse the HID
descriptors for vivaldi function row keymap.
+config HID_GOODIX_SPI
+ tristate "Goodix GT7986U SPI HID touchscreen"
+ depends on SPI_MASTER
+ help
+ Support for Goodix GT7986U SPI HID touchscreen device.
+
config HID_GOOGLE_HAMMER
tristate "Google Hammer Keyboard"
select HID_VIVALDI_COMMON
@@ -412,6 +460,13 @@ config HID_GOOGLE_HAMMER
help
Say Y here if you have a Google Hammer device.
+config HID_GOOGLE_STADIA_FF
+ tristate "Google Stadia force feedback"
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you want to enable force feedback support for the Google
+ Stadia controller.
+
config HID_VIVALDI
tristate "Vivaldi Keyboard"
select HID_VIVALDI_COMMON
@@ -452,6 +507,15 @@ config HID_KYE
- MousePen i608X tablet
- EasyPen M610X tablet
+config HID_KYSONA
+ tristate "Kysona devices"
+ depends on USB_HID
+ help
+ Support for Kysona mice.
+
+ Say Y here if you have a Kysona M600 mouse
+ and want to be able to read its battery capacity.
+
config HID_UCLOGIC
tristate "UC-Logic"
depends on USB_HID
@@ -576,6 +640,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.
@@ -716,6 +781,7 @@ config HID_MULTITOUCH
Say Y here if you have one of the following devices:
- 3M PCT touch screens
- ActionStar dual touch panels
+ - Apple Touch Bar on x86 MacBook Pros
- Atmel panels
- Cando dual touch panels
- Chunghwa panels
@@ -754,14 +820,15 @@ config HID_MULTITOUCH
module will be called hid-multitouch.
config HID_NINTENDO
- tristate "Nintendo Joy-Con and Pro Controller support"
+ tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
depends on NEW_LEDS
depends on LEDS_CLASS
select POWER_SUPPLY
help
- Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
+ Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
All controllers support bluetooth, and the Pro Controller also supports
- its USB mode.
+ its USB mode. This also includes support for the Nintendo Switch Online
+ 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.
@@ -772,9 +839,9 @@ config NINTENDO_FF
select INPUT_FF_MEMLESS
help
Say Y here if you have a Nintendo Switch controller and want to enable
- force feedback support for it. This works for both joy-cons and the pro
- controller. For the pro controller, both rumble motors can be controlled
- individually.
+ force feedback support for it. This works for both joy-cons, the pro
+ controller, and the NSO N64 controller. For the pro controller, both
+ rumble motors can be controlled individually.
config HID_NTI
tristate "NTI keyboard adapters"
@@ -792,6 +859,8 @@ config HID_NVIDIA_SHIELD
tristate "NVIDIA SHIELD devices"
depends on USB_HID
depends on BT_HIDP
+ depends on LEDS_CLASS
+ select POWER_SUPPLY
help
Support for NVIDIA SHIELD accessories.
@@ -871,11 +940,7 @@ config HID_PICOLCD_FB
default !EXPERT
depends on HID_PICOLCD
depends on HID_PICOLCD=FB || FB=y
- select FB_DEFERRED_IO
- select FB_SYS_FILLRECT
- select FB_SYS_COPYAREA
- select FB_SYS_IMAGEBLIT
- select FB_SYS_FOPS
+ select FB_SYSMEM_HELPERS_DEFERRED
help
Provide access to PicoLCD's 256x64 monochrome display via a
framebuffer device.
@@ -1037,7 +1102,7 @@ config HID_SONY
* Guitar Hero PS3 and PC guitar dongles
config SONY_FF
- bool "Sony PS2/3/4 accessories force feedback support"
+ bool "Sony PS2/3/4 accessories force feedback support"
depends on HID_SONY
select INPUT_FF_MEMLESS
help
@@ -1066,9 +1131,11 @@ config STEAM_FF
Deck.
config HID_STEELSERIES
- tristate "Steelseries SRW-S1 steering wheel support"
+ tristate "Steelseries devices support"
+ depends on USB_HID
help
- Support for Steelseries SRW-S1 steering wheel
+ Support for Steelseries SRW-S1 steering wheel, and the Steelseries
+ Arctis 1 Wireless for XBox headset.
config HID_SUNPLUS
tristate "Sunplus wireless desktop"
@@ -1082,6 +1149,7 @@ config HID_RMI
select RMI4_F11
select RMI4_F12
select RMI4_F30
+ select RMI4_F3A
help
Support for Synaptics RMI4 touchpads.
Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
@@ -1104,7 +1172,7 @@ config GREENASIA_FF
config HID_HYPERV_MOUSE
tristate "Microsoft Hyper-V mouse driver"
- depends on HYPERV
+ depends on HYPERV_VMBUS
help
Select this option to enable the Hyper-V mouse driver.
@@ -1140,7 +1208,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"
@@ -1184,12 +1253,26 @@ config HID_U2FZERO
U2F Zero supports custom commands for blinking the LED
and getting data from the internal hardware RNG.
- The internal hardware can be used to feed the enthropy pool.
+ The internal hardware can be used to feed the entropy pool.
U2F Zero only supports blinking its LED, so this driver doesn't
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
@@ -1228,6 +1311,24 @@ config HID_WIIMOTE
To compile this driver as a module, choose M here: the
module will be called hid-wiimote.
+config HID_WINWING
+ tristate "WinWing Orion2 throttle support"
+ depends on USB_HID
+ depends on NEW_LEDS
+ depends on LEDS_CLASS
+ help
+ Support for WinWing Orion2 throttle base with the following grips:
+
+ * TGRIP-15E
+ * TGRIP-15EX
+ * TGRIP-16EX
+ * TGRIP-18
+
+ This driver enables all buttons and switches on the throttle base.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hid-winwing.
+
config HID_XINMO
tristate "Xin-Mo non-fully compliant devices"
help
@@ -1289,6 +1390,15 @@ config HID_ALPS
Say Y here if you have a Alps touchpads over i2c-hid or usbhid
and want support for its special functionalities.
+config HID_MCP2200
+ tristate "Microchip MCP2200 HID USB-to-GPIO bridge"
+ depends on USB_HID && GPIOLIB
+ help
+ Provides GPIO functionality over USB-HID through MCP2200 device.
+
+ To compile this driver as a module, choose M here: the module
+ will be called hid-mcp2200.ko.
+
config HID_MCP2221
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
depends on USB_HID && I2C
@@ -1322,10 +1432,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"
@@ -1334,4 +1440,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 7a9e160158f7..361a7daedeb8 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -4,6 +4,7 @@
#
hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
+hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
obj-$(CONFIG_HID_BPF) += bpf/
@@ -29,6 +30,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
@@ -38,7 +41,7 @@ obj-$(CONFIG_HID_BIGBEN_FF) += hid-bigbenff.o
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o
-obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o
+obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o hid-corsair-void.o
obj-$(CONFIG_HID_COUGAR) += hid-cougar.o
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
@@ -54,7 +57,9 @@ obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
obj-$(CONFIG_HID_GFRM) += hid-gfrm.o
obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o
obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o
+obj-$(CONFIG_HID_GOODIX_SPI) += hid-goodix-spi.o
obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.o
+obj-$(CONFIG_HID_GOOGLE_STADIA_FF) += hid-google-stadiaff.o
obj-$(CONFIG_HID_VIVALDI) += hid-vivaldi.o
obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
@@ -68,6 +73,7 @@ obj-$(CONFIG_HID_JABRA) += hid-jabra.o
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
obj-$(CONFIG_HID_KYE) += hid-kye.o
+obj-$(CONFIG_HID_KYSONA) += hid-kysona.o
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
@@ -78,6 +84,7 @@ obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o
obj-$(CONFIG_HID_MACALLY) += hid-macally.o
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
obj-$(CONFIG_HID_MALTRON) += hid-maltron.o
+obj-$(CONFIG_HID_MCP2200) += hid-mcp2200.o
obj-$(CONFIG_HID_MCP2221) += hid-mcp2221.o
obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
@@ -136,6 +143,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
@@ -148,13 +156,12 @@ wacom-objs := wacom_wac.o wacom_sys.o
obj-$(CONFIG_HID_WACOM) += wacom.o
obj-$(CONFIG_HID_WALTOP) += hid-waltop.o
obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
+obj-$(CONFIG_HID_WINWING) += hid-winwing.o
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
-hid-uclogic-test-objs := hid-uclogic-rdesc.o \
- hid-uclogic-params.o \
- hid-uclogic-rdesc-test.o
-obj-$(CONFIG_HID_KUNIT_TEST) += hid-uclogic-test.o
+hid-uclogic-test-objs := hid-uclogic-rdesc-test.o
+obj-$(CONFIG_HID_KUNIT_TEST) += hid-uclogic.o hid-uclogic-test.o
obj-$(CONFIG_USB_HID) += usbhid/
obj-$(CONFIG_USB_MOUSE) += usbhid/
@@ -163,8 +170,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 af752dd3a340..3291786a5ee6 100644
--- a/drivers/hid/amd-sfh-hid/Kconfig
+++ b/drivers/hid/amd-sfh-hid/Kconfig
@@ -5,7 +5,7 @@ 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
AMD Sensor Fusion Hub.
diff --git a/drivers/hid/amd-sfh-hid/Makefile b/drivers/hid/amd-sfh-hid/Makefile
index 0222170ab7ad..106514b54d16 100644
--- a/drivers/hid/amd-sfh-hid/Makefile
+++ b/drivers/hid/amd-sfh-hid/Makefile
@@ -13,4 +13,4 @@ amd_sfh-objs += sfh1_1/amd_sfh_init.o
amd_sfh-objs += sfh1_1/amd_sfh_interface.o
amd_sfh-objs += sfh1_1/amd_sfh_desc.o
-ccflags-y += -I $(srctree)/$(src)/
+ccflags-y += -I $(src)/
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
index bdb578e0899f..7017bfa59093 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
@@ -39,8 +39,12 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
struct amdtp_hid_data *hid_data = hid->driver_data;
struct amdtp_cl_data *cli_data = hid_data->cli_data;
struct request_list *req_list = &cli_data->req_list;
+ struct amd_input_data *in_data = cli_data->in_data;
+ struct amd_mp2_dev *mp2;
int i;
+ mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
+ guard(mutex)(&mp2->lock);
for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->hid_sensor_hubs[i] == hid) {
struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL);
@@ -75,6 +79,8 @@ void amd_sfh_work(struct work_struct *work)
u8 report_id, node_type;
u8 report_size = 0;
+ mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
+ guard(mutex)(&mp2->lock);
req_node = list_last_entry(&req_list->list, struct request_list, list);
list_del(&req_node->list);
current_index = req_node->current_index;
@@ -83,7 +89,6 @@ void amd_sfh_work(struct work_struct *work)
node_type = req_node->report_type;
kfree(req_node);
- mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
mp2_ops = mp2->mp2_ops;
if (node_type == HID_FEATURE_REPORT) {
report_size = mp2_ops->get_feat_rep(sensor_index, report_id,
@@ -107,6 +112,8 @@ void amd_sfh_work(struct work_struct *work)
cli_data->cur_hid_dev = current_index;
cli_data->sensor_requested_cnt[current_index] = 0;
amdtp_hid_wakeup(cli_data->hid_sensor_hubs[current_index]);
+ if (!list_empty(&req_list->list))
+ schedule_delayed_work(&cli_data->work, 0);
}
void amd_sfh_work_buffer(struct work_struct *work)
@@ -117,9 +124,10 @@ void amd_sfh_work_buffer(struct work_struct *work)
u8 report_size;
int i;
+ mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
+ guard(mutex)(&mp2->lock);
for (i = 0; i < cli_data->num_hid_devices; i++) {
if (cli_data->sensor_sts[i] == SENSOR_ENABLED) {
- mp2 = container_of(in_data, struct amd_mp2_dev, in_data);
report_size = mp2->mp2_ops->get_in_rep(i, cli_data->sensor_idx[i],
cli_data->report_id[i], in_data);
hid_input_report(cli_data->hid_sensor_hubs[i], HID_INPUT_REPORT,
@@ -146,6 +154,8 @@ static const char *get_sensor_name(int idx)
return "gyroscope";
case mag_idx:
return "magnetometer";
+ case op_idx:
+ return "operating-mode";
case als_idx:
case ACS_IDX: /* ambient color sensor */
return "ALS";
@@ -236,13 +246,27 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->in_data = in_data;
for (i = 0; i < cl_data->num_hid_devices; i++) {
- in_data->sensor_virt_addr[i] = dma_alloc_coherent(dev, sizeof(int) * 8,
- &cl_data->sensor_dma_addr[i],
- GFP_KERNEL);
+ in_data->sensor_virt_addr[i] = dmam_alloc_coherent(dev, sizeof(int) * 8,
+ &cl_data->sensor_dma_addr[i],
+ GFP_KERNEL);
if (!in_data->sensor_virt_addr[i]) {
rc = -ENOMEM;
goto cleanup;
}
+
+ if (cl_data->sensor_idx[i] == op_idx) {
+ info.period = AMD_SFH_IDLE_LOOP;
+ info.sensor_idx = cl_data->sensor_idx[i];
+ info.dma_address = cl_data->sensor_dma_addr[i];
+ mp2_ops->start(privdata, info);
+ cl_data->sensor_sts[i] = amd_sfh_wait_for_response(privdata,
+ cl_data->sensor_idx[i],
+ SENSOR_ENABLED);
+ if (cl_data->sensor_sts[i] == SENSOR_ENABLED)
+ cl_data->is_any_sensor_enabled = true;
+ continue;
+ }
+
cl_data->sensor_sts[i] = SENSOR_DISABLED;
cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i;
@@ -288,12 +312,29 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
mp2_ops->start(privdata, info);
cl_data->sensor_sts[i] = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], SENSOR_ENABLED);
+
+ if (cl_data->sensor_sts[i] == SENSOR_ENABLED)
+ cl_data->is_any_sensor_enabled = true;
+ }
+
+ if (!cl_data->is_any_sensor_enabled ||
+ (mp2_ops->discovery_status && mp2_ops->discovery_status(privdata) == 0)) {
+ dev_warn(dev, "Failed to discover, sensors not enabled is %d\n",
+ cl_data->is_any_sensor_enabled);
+ rc = -EOPNOTSUPP;
+ goto cleanup;
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
cl_data->cur_hid_dev = i;
+ if (cl_data->sensor_idx[i] == op_idx) {
+ dev_dbg(dev, "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]);
+ continue;
+ }
+
if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
- cl_data->is_any_sensor_enabled = true;
rc = amdtp_hid_probe(i, cl_data);
if (rc)
goto cleanup;
@@ -305,12 +346,6 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->sensor_sts[i]);
}
- if (!cl_data->is_any_sensor_enabled ||
- (mp2_ops->discovery_status && mp2_ops->discovery_status(privdata) == 0)) {
- dev_warn(dev, "Failed to discover, sensors not enabled is %d\n", cl_data->is_any_sensor_enabled);
- rc = -EOPNOTSUPP;
- goto cleanup;
- }
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
return 0;
@@ -327,7 +362,6 @@ cleanup:
int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
{
struct amdtp_cl_data *cl_data = privdata->cl_data;
- struct amd_input_data *in_data = cl_data->in_data;
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
@@ -347,12 +381,5 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
cancel_delayed_work_sync(&cl_data->work_buffer);
amdtp_hid_remove(cl_data);
- for (i = 0; i < cl_data->num_hid_devices; i++) {
- if (in_data->sensor_virt_addr[i]) {
- dma_free_coherent(&privdata->pdev->dev, 8 * sizeof(int),
- in_data->sensor_virt_addr[i],
- cl_data->sensor_dma_addr[i]);
- }
- }
return 0;
}
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h
index 2643bb14fee2..78f830c133e5 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h
@@ -10,6 +10,7 @@
#ifndef AMD_SFH_COMMON_H
#define AMD_SFH_COMMON_H
+#include <linux/mutex.h>
#include <linux/pci.h>
#include "amd_sfh_hid.h"
@@ -19,6 +20,9 @@
#define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4))
#define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4))
+#define AMD_C2P_MSG_V1(regno) (0x10900 + ((regno) * 4))
+#define AMD_P2C_MSG_V1(regno) (0x10500 + ((regno) * 4))
+
#define SENSOR_ENABLED 4
#define SENSOR_DISABLED 5
@@ -37,6 +41,13 @@ struct amd_mp2_sensor_info {
dma_addr_t dma_address;
};
+struct sfh_dev_status {
+ bool is_hpd_present;
+ bool is_hpd_enabled;
+ bool is_als_present;
+ bool is_sra_present;
+};
+
struct amd_mp2_dev {
struct pci_dev *pdev;
struct amdtp_cl_data *cl_data;
@@ -47,6 +58,12 @@ struct amd_mp2_dev {
struct amd_input_data in_data;
/* mp2 active control status */
u32 mp2_acs;
+ struct sfh_dev_status dev_en;
+ struct work_struct work;
+ /* mp2 to protect data */
+ struct mutex lock;
+ u8 init_done;
+ u8 rver;
};
struct amd_mp2_ops {
@@ -73,4 +90,14 @@ void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata);
int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata);
void amd_sfh_clear_intr(struct amd_mp2_dev *privdata);
int amd_sfh_irq_init(struct amd_mp2_dev *privdata);
+
+static inline u64 amd_get_c2p_val(struct amd_mp2_dev *mp2, u32 idx)
+{
+ return mp2->rver == 1 ? AMD_C2P_MSG_V1(idx) : AMD_C2P_MSG(idx);
+}
+
+static inline u64 amd_get_p2c_val(struct amd_mp2_dev *mp2, u32 idx)
+{
+ return mp2->rver == 1 ? AMD_P2C_MSG_V1(idx) : AMD_P2C_MSG(idx);
+}
#endif
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
index 705b52337068..81f3024b7b1b 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
@@ -171,11 +171,13 @@ err_hid_data:
void amdtp_hid_remove(struct amdtp_cl_data *cli_data)
{
int i;
+ struct amdtp_hid_data *hid_data;
for (i = 0; i < cli_data->num_hid_devices; ++i) {
if (cli_data->hid_sensor_hubs[i]) {
- kfree(cli_data->hid_sensor_hubs[i]->driver_data);
+ hid_data = cli_data->hid_sensor_hubs[i]->driver_data;
hid_destroy_device(cli_data->hid_sensor_hubs[i]);
+ kfree(hid_data);
cli_data->hid_sensor_hubs[i] = NULL;
}
}
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
index 97296f587bc7..7452b0302953 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
@@ -11,7 +11,7 @@
#ifndef AMDSFH_HID_H
#define AMDSFH_HID_H
-#define MAX_HID_DEVICES 6
+#define MAX_HID_DEVICES 7
#define AMD_SFH_HID_VENDOR 0x1022
#define AMD_SFH_HID_PRODUCT 0x0001
@@ -73,8 +73,6 @@ struct amdtp_hid_data {
};
/* Interface functions between HID LL driver and AMD SFH client */
-void hid_amdtp_set_feature(struct hid_device *hid, char *buf, u32 len, int report_id);
-void hid_amdtp_get_report(struct hid_device *hid, int report_id, int report_type);
int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data);
void amdtp_hid_remove(struct amdtp_cl_data *cli_data);
int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type);
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
index 2530fa98b568..1d9f955573aa 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
@@ -10,6 +10,7 @@
#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/devm-helpers.h>
#include <linux/dma-mapping.h>
#include <linux/dmi.h>
#include <linux/interrupt.h>
@@ -17,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"
@@ -27,6 +29,7 @@
#define ACEL_EN BIT(0)
#define GYRO_EN BIT(1)
#define MAGNO_EN BIT(2)
+#define OP_EN BIT(15)
#define HPD_EN BIT(16)
#define ALS_EN BIT(19)
#define ACS_EN BIT(22)
@@ -35,15 +38,17 @@ static int sensor_mask_override = -1;
module_param_named(sensor_mask, sensor_mask_override, int, 0444);
MODULE_PARM_DESC(sensor_mask, "override the detected sensors mask");
+static bool intr_disable = true;
+
static int amd_sfh_wait_response_v2(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
{
union cmd_response cmd_resp;
- /* Get response with status within a max of 1600 ms timeout */
+ /* Get response with status within a max of 10 seconds timeout */
if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
(cmd_resp.response_v2.response == sensor_sts &&
cmd_resp.response_v2.status == 0 && (sid == 0xff ||
- cmd_resp.response_v2.sensor_id == sid)), 500, 1600000))
+ cmd_resp.response_v2.sensor_id == sid)), 500, 10000000))
return cmd_resp.response_v2.response;
return SENSOR_DISABLED;
@@ -55,7 +60,7 @@ static void amd_start_sensor_v2(struct amd_mp2_dev *privdata, struct amd_mp2_sen
cmd_base.ul = 0;
cmd_base.cmd_v2.cmd_id = ENABLE_SENSOR;
- cmd_base.cmd_v2.intr_disable = 1;
+ cmd_base.cmd_v2.intr_disable = intr_disable;
cmd_base.cmd_v2.period = info.period;
cmd_base.cmd_v2.sensor_id = info.sensor_idx;
cmd_base.cmd_v2.length = 16;
@@ -73,7 +78,7 @@ static void amd_stop_sensor_v2(struct amd_mp2_dev *privdata, u16 sensor_idx)
cmd_base.ul = 0;
cmd_base.cmd_v2.cmd_id = DISABLE_SENSOR;
- cmd_base.cmd_v2.intr_disable = 1;
+ cmd_base.cmd_v2.intr_disable = intr_disable;
cmd_base.cmd_v2.period = 0;
cmd_base.cmd_v2.sensor_id = sensor_idx;
cmd_base.cmd_v2.length = 16;
@@ -87,7 +92,7 @@ static void amd_stop_all_sensor_v2(struct amd_mp2_dev *privdata)
union sfh_cmd_base cmd_base;
cmd_base.cmd_v2.cmd_id = STOP_ALL_SENSORS;
- cmd_base.cmd_v2.intr_disable = 1;
+ cmd_base.cmd_v2.intr_disable = intr_disable;
cmd_base.cmd_v2.period = 0;
cmd_base.cmd_v2.sensor_id = 0;
@@ -96,9 +101,9 @@ static void amd_stop_all_sensor_v2(struct amd_mp2_dev *privdata)
void amd_sfh_clear_intr_v2(struct amd_mp2_dev *privdata)
{
- if (readl(privdata->mmio + AMD_P2C_MSG(4))) {
- writel(0, privdata->mmio + AMD_P2C_MSG(4));
- writel(0xf, privdata->mmio + AMD_P2C_MSG(5));
+ if (readl(privdata->mmio + amd_get_p2c_val(privdata, 4))) {
+ writel(0, privdata->mmio + amd_get_p2c_val(privdata, 4));
+ writel(0xf, privdata->mmio + amd_get_p2c_val(privdata, 5));
}
}
@@ -119,7 +124,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);
@@ -228,6 +233,9 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
if (MAGNO_EN & activestatus)
sensor_id[num_of_sensors++] = mag_idx;
+ if (OP_EN & activestatus)
+ sensor_id[num_of_sensors++] = op_idx;
+
if (ALS_EN & activestatus)
sensor_id[num_of_sensors++] = als_idx;
@@ -245,7 +253,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);
}
@@ -292,6 +300,26 @@ int amd_sfh_irq_init(struct amd_mp2_dev *privdata)
return 0;
}
+static int mp2_disable_intr(const struct dmi_system_id *id)
+{
+ intr_disable = false;
+ return 0;
+}
+
+static const struct dmi_system_id dmi_sfh_table[] = {
+ {
+ /*
+ * https://bugzilla.kernel.org/show_bug.cgi?id=218104
+ */
+ .callback = mp2_disable_intr,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 435 G7"),
+ },
+ },
+ {}
+};
+
static const struct dmi_system_id dmi_nodevs[] = {
{
/*
@@ -307,6 +335,101 @@ 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);
+ int rc;
+
+ rc = mp2->sfh1_1_ops->init(mp2);
+ if (rc)
+ return;
+
+ 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)
+{
+ struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
+ struct pci_dev *pdev = mp2->pdev;
+ int rc;
+
+ rc = amd_sfh_hid_client_init(mp2);
+ if (rc) {
+ amd_sfh_clear_intr(mp2);
+ dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc);
+ return;
+ }
+
+ amd_sfh_clear_intr(mp2);
+ mp2->init_done = 1;
+}
+
+static void amd_sfh_remove(struct pci_dev *pdev)
+{
+ struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
+
+ flush_work(&mp2->work);
+ if (mp2->init_done)
+ mp2->mp2_ops->remove(mp2);
+}
+
static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct amd_mp2_dev *privdata;
@@ -315,6 +438,8 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
if (dmi_first_match(dmi_nodevs))
return -ENODEV;
+ dmi_check_system(dmi_sfh_table);
+
privdata = devm_kzalloc(&pdev->dev, sizeof(*privdata), GFP_KERNEL);
if (!privdata)
return -ENOMEM;
@@ -341,12 +466,21 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
if (!privdata->cl_data)
return -ENOMEM;
+ rc = devm_mutex_init(&pdev->dev, &privdata->lock);
+ if (rc)
+ return rc;
+
privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data;
if (privdata->sfh1_1_ops) {
- rc = privdata->sfh1_1_ops->init(privdata);
+ if (boot_cpu_data.x86 >= 0x1A)
+ privdata->rver = 1;
+
+ rc = devm_work_autocancel(&pdev->dev, &privdata->work, sfh1_1_init_work);
if (rc)
return rc;
- goto init_done;
+
+ schedule_work(&privdata->work);
+ return 0;
}
mp2_select_ops(privdata);
@@ -357,33 +491,34 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
return rc;
}
- rc = amd_sfh_hid_client_init(privdata);
+ rc = devm_work_autocancel(&pdev->dev, &privdata->work, sfh_init_work);
if (rc) {
amd_sfh_clear_intr(privdata);
- if (rc != -EOPNOTSUPP)
- dev_err(&pdev->dev, "amd_sfh_hid_client_init failed\n");
return rc;
}
-init_done:
- amd_sfh_clear_intr(privdata);
-
- return devm_add_action_or_reset(&pdev->dev, privdata->mp2_ops->remove, privdata);
+ schedule_work(&privdata->work);
+ return 0;
}
static void amd_sfh_shutdown(struct pci_dev *pdev)
{
struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
- if (mp2 && mp2->mp2_ops)
- mp2->mp2_ops->stop_all(mp2);
+ if (mp2) {
+ flush_work(&mp2->work);
+ if (mp2->init_done)
+ mp2->mp2_ops->stop_all(mp2);
+ }
}
static int __maybe_unused amd_mp2_pci_resume(struct device *dev)
{
struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
- mp2->mp2_ops->resume(mp2);
+ flush_work(&mp2->work);
+ if (mp2->init_done)
+ mp2->mp2_ops->resume(mp2);
return 0;
}
@@ -392,7 +527,9 @@ static int __maybe_unused amd_mp2_pci_suspend(struct device *dev)
{
struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
- mp2->mp2_ops->suspend(mp2);
+ flush_work(&mp2->work);
+ if (mp2->init_done)
+ mp2->mp2_ops->suspend(mp2);
return 0;
}
@@ -414,6 +551,8 @@ static struct pci_driver amd_mp2_pci_driver = {
.probe = amd_mp2_pci_probe,
.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/amd_sfh_pcie.h b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
index 70add75fc506..2eb61f4e8434 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
@@ -79,6 +79,7 @@ enum sensor_idx {
accel_idx = 0,
gyro_idx = 1,
mag_idx = 2,
+ op_idx = 15,
als_idx = 19
};
@@ -90,10 +91,10 @@ enum mem_use_type {
struct hpd_status {
union {
struct {
- u32 human_presence_report : 4;
- u32 human_presence_actual : 4;
- u32 probablity : 8;
u32 object_distance : 16;
+ u32 probablity : 8;
+ u32 human_presence_actual : 4;
+ u32 human_presence_report : 4;
} shpd;
u32 val;
};
diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
index 8716a05950c8..ef1f9be8b893 100644
--- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
@@ -257,6 +257,13 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
else
als_input.illuminance_value =
(int)sensor_virt_addr[0] / AMD_SFH_FW_MULTIPLIER;
+
+ if (sensor_idx == ACS_IDX) {
+ als_input.light_color_temp = sensor_virt_addr[1];
+ als_input.chromaticity_x_value = sensor_virt_addr[2];
+ als_input.chromaticity_y_value = sensor_virt_addr[3];
+ }
+
report_size = sizeof(als_input);
memcpy(input_report, &als_input, sizeof(als_input));
break;
diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
index ebd55675eb62..882434b1501f 100644
--- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
@@ -99,6 +99,9 @@ struct als_input_report {
struct common_input_property common_property;
/* values specific to this sensor */
int illuminance_value;
+ int light_color_temp;
+ int chromaticity_x_value;
+ int chromaticity_y_value;
} __packed;
struct hpd_feature_report {
diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
index 697f2791ea9c..67ec2d6a417d 100644
--- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
@@ -641,6 +641,27 @@ static const u8 als_report_descriptor[] = {
0x75, 32, /* HID report size(32) */
0x95, 1, /* HID report count (1) */
0X81, 0x02, /* HID Input (Data_Arr_Abs) */
+0x0A, 0xD2, 0x04, /* HID usage sensor data light temperature */
+0x17, 0x00, 0x00, 0x01, 0x80, /* HID logical Min_32 */
+0x27, 0xFF, 0xFF, 0xFF, 0x7F, /* HID logical Max_32 */
+0x55, 0x0, /* HID unit exponent(0x0) */
+0x75, 32, /* HID report size(32) */
+0x95, 1, /* HID report count (1) */
+0X81, 0x02, /* HID Input (Data_Arr_Abs) */
+0x0A, 0xD4, 0x04, /* HID usage sensor data light chromaticity_x */
+0x17, 0x00, 0x00, 0x01, 0x80, /* HID logical Min_32 */
+0x27, 0xFF, 0xFF, 0xFF, 0x7F, /* HID logical Max_32 */
+0x55, 0x0, /* HID unit exponent(0x0) */
+0x75, 32, /* HID report size(32) */
+0x95, 1, /* HID report count(1) */
+0X81, 0x02, /* HID Input (Data_Var_Abs) */
+0x0A, 0xD5, 0x04, /* HID usage sensor data light chromaticity_y */
+0x17, 0x00, 0x00, 0x01, 0x80, /* HID logical Min_32 */
+0x27, 0xFF, 0xFF, 0xFF, 0x7F, /* HID logical Max_32 */
+0x55, 0x0, /* HID unit exponent(0x0) */
+0x75, 32, /* HID report size(32) */
+0x95, 1, /* HID report count (1) */
+0X81, 0x02, /* HID Input (Data_Var_Abs) */
0xC0 /* HID end collection */
};
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c
index 06bdcf072d10..c8916afefa62 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c
@@ -132,7 +132,7 @@ static void get_common_inputs(struct common_input_property *common, int report_i
common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM;
}
-static int float_to_int(u32 flt32_val)
+int amd_sfh_float_to_int(u32 flt32_val)
{
int fraction, shift, mantissa, sign, exp, zeropre;
@@ -188,6 +188,7 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
struct sfh_mag_data mag_data;
struct sfh_als_data als_data;
struct hpd_status hpdstatus;
+ struct sfh_base_info binfo;
void __iomem *sensoraddr;
u8 report_size = 0;
@@ -200,9 +201,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&accel_data, sensoraddr, sizeof(struct sfh_accel_data));
get_common_inputs(&acc_input.common_property, report_id);
- acc_input.in_accel_x_value = float_to_int(accel_data.acceldata.x) / 100;
- acc_input.in_accel_y_value = float_to_int(accel_data.acceldata.y) / 100;
- acc_input.in_accel_z_value = float_to_int(accel_data.acceldata.z) / 100;
+ acc_input.in_accel_x_value = amd_sfh_float_to_int(accel_data.acceldata.x) / 100;
+ acc_input.in_accel_y_value = amd_sfh_float_to_int(accel_data.acceldata.y) / 100;
+ acc_input.in_accel_z_value = amd_sfh_float_to_int(accel_data.acceldata.z) / 100;
memcpy(input_report, &acc_input, sizeof(acc_input));
report_size = sizeof(acc_input);
break;
@@ -211,9 +212,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&gyro_data, sensoraddr, sizeof(struct sfh_gyro_data));
get_common_inputs(&gyro_input.common_property, report_id);
- gyro_input.in_angel_x_value = float_to_int(gyro_data.gyrodata.x) / 1000;
- gyro_input.in_angel_y_value = float_to_int(gyro_data.gyrodata.y) / 1000;
- gyro_input.in_angel_z_value = float_to_int(gyro_data.gyrodata.z) / 1000;
+ gyro_input.in_angel_x_value = amd_sfh_float_to_int(gyro_data.gyrodata.x) / 1000;
+ gyro_input.in_angel_y_value = amd_sfh_float_to_int(gyro_data.gyrodata.y) / 1000;
+ gyro_input.in_angel_z_value = amd_sfh_float_to_int(gyro_data.gyrodata.z) / 1000;
memcpy(input_report, &gyro_input, sizeof(gyro_input));
report_size = sizeof(gyro_input);
break;
@@ -222,9 +223,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&mag_data, sensoraddr, sizeof(struct sfh_mag_data));
get_common_inputs(&magno_input.common_property, report_id);
- magno_input.in_magno_x = float_to_int(mag_data.magdata.x) / 100;
- magno_input.in_magno_y = float_to_int(mag_data.magdata.y) / 100;
- magno_input.in_magno_z = float_to_int(mag_data.magdata.z) / 100;
+ magno_input.in_magno_x = amd_sfh_float_to_int(mag_data.magdata.x) / 100;
+ magno_input.in_magno_y = amd_sfh_float_to_int(mag_data.magdata.y) / 100;
+ magno_input.in_magno_z = amd_sfh_float_to_int(mag_data.magdata.z) / 100;
magno_input.in_magno_accuracy = mag_data.accuracy / 100;
memcpy(input_report, &magno_input, sizeof(magno_input));
report_size = sizeof(magno_input);
@@ -234,13 +235,23 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
get_common_inputs(&als_input.common_property, report_id);
- als_input.illuminance_value = float_to_int(als_data.lux);
+ als_input.illuminance_value = amd_sfh_float_to_int(als_data.lux);
+
+ memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
+ if (binfo.sbase.s_prop[ALS_IDX].sf.feat & 0x2) {
+ als_input.light_color_temp = als_data.light_color_temp;
+ als_input.chromaticity_x_value =
+ amd_sfh_float_to_int(als_data.chromaticity_x);
+ als_input.chromaticity_y_value =
+ amd_sfh_float_to_int(als_data.chromaticity_y);
+ }
+
report_size = sizeof(als_input);
memcpy(input_report, &als_input, sizeof(als_input));
break;
case HPD_IDX:
get_common_inputs(&hpd_input.common_property, report_id);
- hpdstatus.val = readl(mp2->mmio + AMD_C2P_MSG(4));
+ hpdstatus.val = readl(mp2->mmio + amd_get_c2p_val(mp2, 4));
hpd_input.human_presence = hpdstatus.shpd.presence;
report_size = sizeof(hpd_input);
memcpy(input_report, &hpd_input, sizeof(hpd_input));
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 e9c6413af24a..b0bab2a1ddcc 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:
@@ -73,6 +76,18 @@ static int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
int i, status;
for (i = 0; i < cl_data->num_hid_devices; i++) {
+ switch (cl_data->sensor_idx[i]) {
+ case HPD_IDX:
+ privdata->dev_en.is_hpd_present = false;
+ break;
+ 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) {
privdata->mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
status = amd_sfh_wait_for_response
@@ -121,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];
@@ -163,7 +194,9 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
if (rc)
goto cleanup;
- writel(0, privdata->mmio + AMD_P2C_MSG(0));
+ mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
+ amd_sfh_wait_for_response(privdata, cl_data->sensor_idx[i], DISABLE_SENSOR);
+ 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);
@@ -172,12 +205,24 @@ 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;
rc = amdtp_hid_probe(i, cl_data);
if (rc)
goto cleanup;
+ 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;
+ break;
+ }
}
dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
@@ -185,7 +230,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
}
if (!cl_data->is_any_sensor_enabled) {
- dev_warn(dev, "Failed to discover, sensors not enabled is %d\n",
+ dev_warn(dev, "No sensor registered, sensors not enabled is %d\n",
cl_data->is_any_sensor_enabled);
rc = -EOPNOTSUPP;
goto cleanup;
@@ -197,6 +242,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]);
@@ -210,7 +257,16 @@ static void amd_sfh_resume(struct amd_mp2_dev *mp2)
struct amd_mp2_sensor_info info;
int i, status;
+ if (!cl_data->is_any_sensor_enabled) {
+ amd_sfh_clear_intr(mp2);
+ return;
+ }
+
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);
@@ -235,9 +291,16 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
struct amdtp_cl_data *cl_data = mp2->cl_data;
int i, status;
+ if (!cl_data->is_any_sensor_enabled) {
+ amd_sfh_clear_intr(mp2);
+ return;
+ }
+
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);
@@ -255,13 +318,52 @@ 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;
+ 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);
}
@@ -271,8 +373,8 @@ static void amd_sfh_set_ops(struct amd_mp2_dev *mp2)
sfh_interface_init(mp2);
mp2_ops = mp2->mp2_ops;
- mp2_ops->clear_intr = amd_sfh_clear_intr_v2,
- mp2_ops->init_intr = amd_sfh_irq_init_v2,
+ mp2_ops->clear_intr = amd_sfh_clear_intr_v2;
+ mp2_ops->init_intr = amd_sfh_irq_init_v2;
mp2_ops->suspend = amd_sfh_suspend;
mp2_ops->resume = amd_sfh_resume;
mp2_ops->remove = amd_mp2_pci_remove;
@@ -280,7 +382,7 @@ static void amd_sfh_set_ops(struct amd_mp2_dev *mp2)
int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
{
- u32 phy_base = readl(mp2->mmio + AMD_C2P_MSG(22));
+ u32 phy_base = readl(mp2->mmio + amd_get_c2p_val(mp2, 22));
struct device *dev = &mp2->pdev->dev;
struct sfh_base_info binfo;
int rc;
@@ -302,7 +404,7 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
if (binfo.sbase.fw_info.fw_ver == 0 || binfo.sbase.s_list.sl.sensors == 0) {
- dev_dbg(dev, "failed to get sensors\n");
+ dev_dbg(dev, "No sensor registered\n");
return -EOPNOTSUPP;
}
dev_dbg(dev, "firmware version 0x%x\n", binfo.sbase.fw_info.fw_ver);
@@ -311,13 +413,16 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
rc = amd_sfh_irq_init(mp2);
if (rc) {
+ sfh_deinit_emp2();
dev_err(dev, "amd_sfh_irq_init failed\n");
return rc;
}
rc = amd_sfh1_1_hid_client_init(mp2);
if (rc) {
- dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
+ sfh_deinit_emp2();
+ if ((rc != -ENODEV) && (rc != -EOPNOTSUPP))
+ dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
return rc;
}
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 4f81ef2d4f56..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
@@ -7,17 +7,20 @@
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
+#include <linux/amd-pmf-io.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/iopoll.h>
#include "amd_sfh_interface.h"
+static struct amd_mp2_dev *emp2;
+
static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
{
struct sfh_cmd_response cmd_resp;
/* Get response with status within a max of 10000 ms timeout */
- if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG(0), cmd_resp.resp,
+ if (!readl_poll_timeout(mp2->mmio + amd_get_p2c_val(mp2, 0), cmd_resp.resp,
(cmd_resp.response.response == 0 &&
cmd_resp.response.cmd_id == cmd_id && (sid == 0xff ||
cmd_resp.response.sensor_id == sid)), 500, 10000000))
@@ -36,7 +39,7 @@ static void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor
cmd_base.cmd.sub_cmd_value = 1;
cmd_base.cmd.sensor_id = info.sensor_idx;
- writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
+ writel(cmd_base.ul, privdata->mmio + amd_get_c2p_val(privdata, 0));
}
static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
@@ -49,8 +52,8 @@ static void amd_stop_sensor(struct amd_mp2_dev *privdata, u16 sensor_idx)
cmd_base.cmd.sub_cmd_value = 1;
cmd_base.cmd.sensor_id = sensor_idx;
- writeq(0x0, privdata->mmio + AMD_C2P_MSG(1));
- writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
+ writeq(0x0, privdata->mmio + amd_get_c2p_val(privdata, 1));
+ writel(cmd_base.ul, privdata->mmio + amd_get_c2p_val(privdata, 0));
}
static void amd_stop_all_sensor(struct amd_mp2_dev *privdata)
@@ -63,7 +66,7 @@ static void amd_stop_all_sensor(struct amd_mp2_dev *privdata)
/* 0xf indicates all sensors */
cmd_base.cmd.sensor_id = 0xf;
- writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG(0));
+ writel(cmd_base.ul, privdata->mmio + amd_get_c2p_val(privdata, 0));
}
static struct amd_mp2_ops amd_sfh_ops = {
@@ -73,7 +76,101 @@ static struct amd_mp2_ops amd_sfh_ops = {
.response = amd_sfh_wait_response,
};
+void sfh_deinit_emp2(void)
+{
+ emp2 = NULL;
+}
+
void sfh_interface_init(struct amd_mp2_dev *mp2)
{
mp2->mp2_ops = &amd_sfh_ops;
+ 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;
+
+ if (!user_present)
+ return -EINVAL;
+
+ 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));
+ *user_present = hpdstatus.shpd.presence;
+
+ return 0;
+}
+
+static int amd_sfh_als_info(u32 *ambient_light)
+{
+ struct sfh_als_data als_data;
+ void __iomem *sensoraddr;
+
+ if (!ambient_light)
+ return -EINVAL;
+
+ if (!emp2 || !emp2->dev_en.is_als_present)
+ return -ENODEV;
+
+ sensoraddr = emp2->vsbase +
+ (ALS_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
+ OFFSET_SENSOR_DATA_DEFAULT;
+ memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
+ *ambient_light = amd_sfh_float_to_int(als_data.lux);
+
+ return 0;
+}
+
+int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
+{
+ if (sfh_info) {
+ switch (op) {
+ case MT_HPD:
+ 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;
}
+EXPORT_SYMBOL_GPL(amd_get_sfh_info);
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 9d31d5b510eb..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,
};
@@ -88,6 +89,16 @@ struct sfh_sensor_list {
};
};
+struct sfh_sensor_prop {
+ union {
+ u32 sprop;
+ struct {
+ u32 elist : 16;
+ u32 feat : 16;
+ } sf;
+ };
+};
+
struct sfh_base_info {
union {
u32 sfh_base[24];
@@ -95,6 +106,8 @@ struct sfh_base_info {
struct sfh_platform_info plat_info;
struct sfh_firmware_info fw_info;
struct sfh_sensor_list s_list;
+ u32 rsvd;
+ struct sfh_sensor_prop s_prop[16];
} sbase;
};
};
@@ -134,6 +147,9 @@ struct sfh_mag_data {
struct sfh_als_data {
struct sfh_common_data commondata;
u32 lux;
+ u32 light_color_temp;
+ u32 chromaticity_x;
+ u32 chromaticity_y;
};
struct hpd_status {
@@ -149,6 +165,27 @@ 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);
+int amd_sfh_float_to_int(u32 flt32_val);
#endif
diff --git a/drivers/hid/bpf/Kconfig b/drivers/hid/bpf/Kconfig
index 83214bae6768..d65482e02a6c 100644
--- a/drivers/hid/bpf/Kconfig
+++ b/drivers/hid/bpf/Kconfig
@@ -3,7 +3,7 @@ menu "HID-BPF support"
config HID_BPF
bool "HID-BPF support"
- depends on BPF
+ depends on BPF_JIT
depends on BPF_SYSCALL
depends on DYNAMIC_FTRACE_WITH_DIRECT_CALLS
help
diff --git a/drivers/hid/bpf/Makefile b/drivers/hid/bpf/Makefile
index cf55120cf7d6..d1f2b81788ca 100644
--- a/drivers/hid/bpf/Makefile
+++ b/drivers/hid/bpf/Makefile
@@ -8,4 +8,4 @@ LIBBPF_INCLUDE = $(srctree)/tools/lib
obj-$(CONFIG_HID_BPF) += hid_bpf.o
CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
-hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
+hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_struct_ops.o
diff --git a/drivers/hid/bpf/entrypoints/README b/drivers/hid/bpf/entrypoints/README
deleted file mode 100644
index 147e0d41509f..000000000000
--- a/drivers/hid/bpf/entrypoints/README
+++ /dev/null
@@ -1,4 +0,0 @@
-WARNING:
-If you change "entrypoints.bpf.c" do "make -j" in this directory to rebuild "entrypoints.skel.h".
-Make sure to have clang 10 installed.
-See Documentation/bpf/bpf_devel_QA.rst
diff --git a/drivers/hid/bpf/entrypoints/entrypoints.bpf.c b/drivers/hid/bpf/entrypoints/entrypoints.bpf.c
deleted file mode 100644
index c22921125a1a..000000000000
--- a/drivers/hid/bpf/entrypoints/entrypoints.bpf.c
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright (c) 2022 Benjamin Tissoires */
-
-#include ".output/vmlinux.h"
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_tracing.h>
-
-#define HID_BPF_MAX_PROGS 1024
-
-struct {
- __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
- __uint(max_entries, HID_BPF_MAX_PROGS);
- __uint(key_size, sizeof(__u32));
- __uint(value_size, sizeof(__u32));
-} hid_jmp_table SEC(".maps");
-
-SEC("fmod_ret/__hid_bpf_tail_call")
-int BPF_PROG(hid_tail_call, struct hid_bpf_ctx *hctx)
-{
- bpf_tail_call(ctx, &hid_jmp_table, hctx->index);
-
- return 0;
-}
-
-char LICENSE[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/entrypoints/entrypoints.lskel.h b/drivers/hid/bpf/entrypoints/entrypoints.lskel.h
deleted file mode 100644
index 35618051598c..000000000000
--- a/drivers/hid/bpf/entrypoints/entrypoints.lskel.h
+++ /dev/null
@@ -1,248 +0,0 @@
-/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
-/* THIS FILE IS AUTOGENERATED BY BPFTOOL! */
-#ifndef __ENTRYPOINTS_BPF_SKEL_H__
-#define __ENTRYPOINTS_BPF_SKEL_H__
-
-#include <bpf/skel_internal.h>
-
-struct entrypoints_bpf {
- struct bpf_loader_ctx ctx;
- struct {
- struct bpf_map_desc hid_jmp_table;
- } maps;
- struct {
- struct bpf_prog_desc hid_tail_call;
- } progs;
- struct {
- int hid_tail_call_fd;
- } links;
-};
-
-static inline int
-entrypoints_bpf__hid_tail_call__attach(struct entrypoints_bpf *skel)
-{
- int prog_fd = skel->progs.hid_tail_call.prog_fd;
- int fd = skel_raw_tracepoint_open(NULL, prog_fd);
-
- if (fd > 0)
- skel->links.hid_tail_call_fd = fd;
- return fd;
-}
-
-static inline int
-entrypoints_bpf__attach(struct entrypoints_bpf *skel)
-{
- int ret = 0;
-
- ret = ret < 0 ? ret : entrypoints_bpf__hid_tail_call__attach(skel);
- return ret < 0 ? ret : 0;
-}
-
-static inline void
-entrypoints_bpf__detach(struct entrypoints_bpf *skel)
-{
- skel_closenz(skel->links.hid_tail_call_fd);
-}
-static void
-entrypoints_bpf__destroy(struct entrypoints_bpf *skel)
-{
- if (!skel)
- return;
- entrypoints_bpf__detach(skel);
- skel_closenz(skel->progs.hid_tail_call.prog_fd);
- skel_closenz(skel->maps.hid_jmp_table.map_fd);
- skel_free(skel);
-}
-static inline struct entrypoints_bpf *
-entrypoints_bpf__open(void)
-{
- struct entrypoints_bpf *skel;
-
- skel = skel_alloc(sizeof(*skel));
- if (!skel)
- goto cleanup;
- skel->ctx.sz = (void *)&skel->links - (void *)skel;
- return skel;
-cleanup:
- entrypoints_bpf__destroy(skel);
- return NULL;
-}
-
-static inline int
-entrypoints_bpf__load(struct entrypoints_bpf *skel)
-{
- struct bpf_load_and_run_opts opts = {};
- int err;
-
- opts.ctx = (struct bpf_loader_ctx *)skel;
- opts.data_sz = 2856;
- opts.data = (void *)"\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x9f\xeb\x01\0\
-\x18\0\0\0\0\0\0\0\x60\x02\0\0\x60\x02\0\0\x12\x02\0\0\0\0\0\0\0\0\0\x02\x03\0\
-\0\0\x01\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\
-\0\0\x04\0\0\0\x03\0\0\0\x05\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\
-\x02\x06\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\
-\0\0\0\x02\x08\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\x04\0\0\0\0\
-\0\0\0\x04\0\0\x04\x20\0\0\0\x19\0\0\0\x01\0\0\0\0\0\0\0\x1e\0\0\0\x05\0\0\0\
-\x40\0\0\0\x2a\0\0\0\x07\0\0\0\x80\0\0\0\x33\0\0\0\x07\0\0\0\xc0\0\0\0\x3e\0\0\
-\0\0\0\0\x0e\x09\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\x02\x0c\0\0\0\x4c\0\0\0\0\0\0\
-\x01\x08\0\0\0\x40\0\0\0\0\0\0\0\x01\0\0\x0d\x02\0\0\0\x5f\0\0\0\x0b\0\0\0\x63\
-\0\0\0\x01\0\0\x0c\x0d\0\0\0\x09\x01\0\0\x05\0\0\x04\x20\0\0\0\x15\x01\0\0\x10\
-\0\0\0\0\0\0\0\x1b\x01\0\0\x12\0\0\0\x40\0\0\0\x1f\x01\0\0\x10\0\0\0\x80\0\0\0\
-\x2e\x01\0\0\x14\0\0\0\xa0\0\0\0\0\0\0\0\x15\0\0\0\xc0\0\0\0\x3a\x01\0\0\0\0\0\
-\x08\x11\0\0\0\x40\x01\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\x02\x13\
-\0\0\0\0\0\0\0\0\0\0\x0a\x1c\0\0\0\x4d\x01\0\0\x04\0\0\x06\x04\0\0\0\x5d\x01\0\
-\0\0\0\0\0\x6e\x01\0\0\x01\0\0\0\x80\x01\0\0\x02\0\0\0\x93\x01\0\0\x03\0\0\0\0\
-\0\0\0\x02\0\0\x05\x04\0\0\0\xa4\x01\0\0\x16\0\0\0\0\0\0\0\xab\x01\0\0\x16\0\0\
-\0\0\0\0\0\xb0\x01\0\0\0\0\0\x08\x02\0\0\0\xec\x01\0\0\0\0\0\x01\x01\0\0\0\x08\
-\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x17\0\0\0\x04\0\0\0\x04\0\0\0\xf1\x01\0\0\0\
-\0\0\x0e\x18\0\0\0\x01\0\0\0\xf9\x01\0\0\x01\0\0\x0f\x20\0\0\0\x0a\0\0\0\0\0\0\
-\0\x20\0\0\0\xff\x01\0\0\x01\0\0\x0f\x04\0\0\0\x19\0\0\0\0\0\0\0\x04\0\0\0\x07\
-\x02\0\0\0\0\0\x07\0\0\0\0\0\x69\x6e\x74\0\x5f\x5f\x41\x52\x52\x41\x59\x5f\x53\
-\x49\x5a\x45\x5f\x54\x59\x50\x45\x5f\x5f\0\x74\x79\x70\x65\0\x6d\x61\x78\x5f\
-\x65\x6e\x74\x72\x69\x65\x73\0\x6b\x65\x79\x5f\x73\x69\x7a\x65\0\x76\x61\x6c\
-\x75\x65\x5f\x73\x69\x7a\x65\0\x68\x69\x64\x5f\x6a\x6d\x70\x5f\x74\x61\x62\x6c\
-\x65\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x6c\x6f\x6e\x67\x20\x6c\x6f\x6e\x67\
-\0\x63\x74\x78\0\x68\x69\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\x66\x6d\
-\x6f\x64\x5f\x72\x65\x74\x2f\x5f\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\
-\x69\x6c\x5f\x63\x61\x6c\x6c\0\x2f\x68\x6f\x6d\x65\x2f\x62\x74\x69\x73\x73\x6f\
-\x69\x72\x2f\x53\x72\x63\x2f\x68\x69\x64\x2f\x64\x72\x69\x76\x65\x72\x73\x2f\
-\x68\x69\x64\x2f\x62\x70\x66\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\
-\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\x2e\x62\x70\x66\x2e\x63\0\x69\
-\x6e\x74\x20\x42\x50\x46\x5f\x50\x52\x4f\x47\x28\x68\x69\x64\x5f\x74\x61\x69\
-\x6c\x5f\x63\x61\x6c\x6c\x2c\x20\x73\x74\x72\x75\x63\x74\x20\x68\x69\x64\x5f\
-\x62\x70\x66\x5f\x63\x74\x78\x20\x2a\x68\x63\x74\x78\x29\0\x68\x69\x64\x5f\x62\
-\x70\x66\x5f\x63\x74\x78\0\x69\x6e\x64\x65\x78\0\x68\x69\x64\0\x61\x6c\x6c\x6f\
-\x63\x61\x74\x65\x64\x5f\x73\x69\x7a\x65\0\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\
-\x70\x65\0\x5f\x5f\x75\x33\x32\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x69\x6e\
-\x74\0\x68\x69\x64\x5f\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\x70\x65\0\x48\x49\
-\x44\x5f\x49\x4e\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x4f\
-\x55\x54\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x46\x45\x41\
-\x54\x55\x52\x45\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x52\x45\x50\x4f\
-\x52\x54\x5f\x54\x59\x50\x45\x53\0\x72\x65\x74\x76\x61\x6c\0\x73\x69\x7a\x65\0\
-\x5f\x5f\x73\x33\x32\0\x30\x3a\x30\0\x09\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\
-\x63\x61\x6c\x6c\x28\x63\x74\x78\x2c\x20\x26\x68\x69\x64\x5f\x6a\x6d\x70\x5f\
-\x74\x61\x62\x6c\x65\x2c\x20\x68\x63\x74\x78\x2d\x3e\x69\x6e\x64\x65\x78\x29\
-\x3b\0\x63\x68\x61\x72\0\x4c\x49\x43\x45\x4e\x53\x45\0\x2e\x6d\x61\x70\x73\0\
-\x6c\x69\x63\x65\x6e\x73\x65\0\x68\x69\x64\x5f\x64\x65\x76\x69\x63\x65\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x8a\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x03\
-\0\0\0\x04\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\x64\x5f\
-\x6a\x6d\x70\x5f\x74\x61\x62\x6c\x65\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\x47\x50\x4c\0\0\0\0\0\x79\x12\0\0\0\0\0\0\x61\x23\0\0\0\0\
-\0\0\x18\x52\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x85\0\0\0\x0c\0\0\0\xb7\0\0\0\0\0\0\0\
-\x95\0\0\0\0\0\0\0\0\0\0\0\x0e\0\0\0\0\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\x48\0\0\
-\x01\0\0\0\x8e\0\0\0\xba\x01\0\0\x02\x50\0\0\x05\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\
-\x48\0\0\x08\0\0\0\x0f\0\0\0\xb6\x01\0\0\0\0\0\0\x1a\0\0\0\x07\0\0\0\0\0\0\0\0\
-\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\
-\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\0\0\0\0\x1a\0\0\0\0\0\0\0\
-\x08\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x10\0\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\x01\0\
-\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x10\0\0\0\0\0\0\0\x5f\
-\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\
-\0\0";
- opts.insns_sz = 1192;
- opts.insns = (void *)"\
-\xbf\x16\0\0\0\0\0\0\xbf\xa1\0\0\0\0\0\0\x07\x01\0\0\x78\xff\xff\xff\xb7\x02\0\
-\0\x88\0\0\0\xb7\x03\0\0\0\0\0\0\x85\0\0\0\x71\0\0\0\x05\0\x11\0\0\0\0\0\x61\
-\xa1\x78\xff\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x7c\xff\
-\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x80\xff\0\0\0\0\xd5\
-\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\
-\x01\0\0\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\
-\xbf\x70\0\0\0\0\0\0\x95\0\0\0\0\0\0\0\x61\x60\x08\0\0\0\0\0\x18\x61\0\0\0\0\0\
-\0\0\0\0\0\xa8\x09\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\0\0\0\0\x18\x61\0\0\0\
-\0\0\0\0\0\0\0\xa4\x09\0\0\x63\x01\0\0\0\0\0\0\x79\x60\x10\0\0\0\0\0\x18\x61\0\
-\0\0\0\0\0\0\0\0\0\x98\x09\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
-\0\x05\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\x7b\x01\0\0\0\0\0\0\xb7\x01\
-\0\0\x12\0\0\0\x18\x62\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\xb7\x03\0\0\x1c\0\0\0\
-\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\xd7\xff\0\0\0\0\x63\x7a\x78\
-\xff\0\0\0\0\x61\x60\x1c\0\0\0\0\0\x15\0\x03\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\
-\0\0\xbc\x09\0\0\x63\x01\0\0\0\0\0\0\xb7\x01\0\0\0\0\0\0\x18\x62\0\0\0\0\0\0\0\
-\0\0\0\xb0\x09\0\0\xb7\x03\0\0\x48\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\
-\0\xc5\x07\xca\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x63\x71\0\0\0\0\
-\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf8\x09\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\
-\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\x0a\0\0\x18\x61\0\0\
-\0\0\0\0\0\0\0\0\x88\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
-\x38\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xd0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\
-\x60\0\0\0\0\0\0\0\0\0\0\x40\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xe0\x0a\0\0\
-\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x70\x0a\0\0\x18\x61\0\0\0\0\0\
-\0\0\0\0\0\0\x0b\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
-\x18\x61\0\0\0\0\0\0\0\0\0\0\xf8\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\x60\x08\0\0\0\
-\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x98\x0a\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\
-\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x9c\x0a\0\0\x63\x01\0\0\0\0\0\0\x79\x60\
-\x10\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xa0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\
-\xa0\x78\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xc8\x0a\0\0\x63\x01\0\0\0\0\0\
-\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x10\x0b\0\0\xb7\x02\0\0\x14\0\0\0\xb7\x03\0\0\
-\x0c\0\0\0\xb7\x04\0\0\0\0\0\0\x85\0\0\0\xa7\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\
-\x91\xff\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x80\x0a\0\0\x63\x70\x6c\0\0\0\0\0\
-\x77\x07\0\0\x20\0\0\0\x63\x70\x70\0\0\0\0\0\xb7\x01\0\0\x05\0\0\0\x18\x62\0\0\
-\0\0\0\0\0\0\0\0\x80\x0a\0\0\xb7\x03\0\0\x8c\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\
-\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf0\x0a\0\0\x61\x01\0\0\0\0\0\0\xd5\
-\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\xc5\x07\x7f\xff\0\0\
-\0\0\x63\x7a\x80\xff\0\0\0\0\x61\xa1\x78\xff\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\
-\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa0\x80\xff\0\0\0\0\x63\x06\x28\0\0\0\
-\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\x10\0\0\0\0\0\0\x63\x06\x18\0\0\0\
-\0\0\xb7\0\0\0\0\0\0\0\x95\0\0\0\0\0\0\0";
- err = bpf_load_and_run(&opts);
- if (err < 0)
- return err;
- return 0;
-}
-
-static inline struct entrypoints_bpf *
-entrypoints_bpf__open_and_load(void)
-{
- struct entrypoints_bpf *skel;
-
- skel = entrypoints_bpf__open();
- if (!skel)
- return NULL;
- if (entrypoints_bpf__load(skel)) {
- entrypoints_bpf__destroy(skel);
- return NULL;
- }
- return skel;
-}
-
-__attribute__((unused)) static void
-entrypoints_bpf__assert(struct entrypoints_bpf *s __attribute__((unused)))
-{
-#ifdef __cplusplus
-#define _Static_assert static_assert
-#endif
-#ifdef __cplusplus
-#undef _Static_assert
-#endif
-}
-
-#endif /* __ENTRYPOINTS_BPF_SKEL_H__ */
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index d9ef45fcaeab..9a06f9b0e4ef 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -3,7 +3,7 @@
/*
* HID-BPF support for Linux
*
- * Copyright (c) 2022 Benjamin Tissoires
+ * Copyright (c) 2022-2024 Benjamin Tissoires
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -17,49 +17,30 @@
#include <linux/kfifo.h>
#include <linux/minmax.h>
#include <linux/module.h>
-#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
-#include "entrypoints/entrypoints.lskel.h"
-struct hid_bpf_ops *hid_bpf_ops;
-EXPORT_SYMBOL(hid_bpf_ops);
-
-/**
- * hid_bpf_device_event - Called whenever an event is coming in from the device
- *
- * @ctx: The HID-BPF context
- *
- * @return %0 on success and keep processing; a positive value to change the
- * incoming size buffer; a negative error code to interrupt the processing
- * of this event
- *
- * Declare an %fmod_ret tracing bpf program to this function and attach this
- * program through hid_bpf_attach_prog() to have this helper called for
- * any incoming event from the device itself.
- *
- * The function is called while on IRQ context, so we can not sleep.
- */
-/* never used by the kernel but declared so we can load and attach a tracepoint */
-__weak noinline int hid_bpf_device_event(struct hid_bpf_ctx *ctx)
-{
- return 0;
-}
+const struct hid_ops *hid_ops;
+EXPORT_SYMBOL(hid_ops);
u8 *
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
- u32 *size, int interrupt)
+ u32 *size, int interrupt, u64 source, bool from_bpf)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
- .report_type = type,
.allocated_size = hdev->bpf.allocated_data,
.size = *size,
},
.data = hdev->bpf.device_data,
+ .from_bpf = from_bpf,
};
+ 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);
@@ -70,10 +51,22 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
memset(ctx_kern.data, 0, hdev->bpf.allocated_data);
memcpy(ctx_kern.data, data, *size);
- ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
- if (ret < 0)
- return ERR_PTR(ret);
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
+ if (e->hid_device_event) {
+ ret = e->hid_device_event(&ctx_kern.ctx, type, source);
+ if (ret < 0) {
+ rcu_read_unlock();
+ return ERR_PTR(ret);
+ }
+
+ if (ret)
+ ctx_kern.ctx.size = ret;
+ }
+ }
+ rcu_read_unlock();
+ ret = ctx_kern.ctx.size;
if (ret) {
if (ret > ctx_kern.ctx.allocated_size)
return ERR_PTR(-EINVAL);
@@ -85,27 +78,86 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
-/**
- * hid_bpf_rdesc_fixup - Called when the probe function parses the report
- * descriptor of the HID device
- *
- * @ctx: The HID-BPF context
- *
- * @return 0 on success and keep processing; a positive value to change the
- * incoming size buffer; a negative error code to interrupt the processing
- * of this event
- *
- * Declare an %fmod_ret tracing bpf program to this function and attach this
- * program through hid_bpf_attach_prog() to have this helper called before any
- * parsing of the report descriptor by HID.
- */
-/* never used by the kernel but declared so we can load and attach a tracepoint */
-__weak noinline int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
+int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+ unsigned char reportnum, u8 *buf,
+ u32 size, enum hid_report_type rtype,
+ enum hid_class_request reqtype,
+ u64 source, bool from_bpf)
{
- return 0;
+ struct hid_bpf_ctx_kern ctx_kern = {
+ .ctx = {
+ .hid = hdev,
+ .allocated_size = size,
+ .size = size,
+ },
+ .data = buf,
+ .from_bpf = from_bpf,
+ };
+ struct hid_bpf_ops *e;
+ int ret, idx;
+
+ if (unlikely(hdev->bpf.destroyed))
+ return -ENODEV;
+
+ if (rtype >= HID_REPORT_TYPES)
+ return -EINVAL;
+
+ 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)) {
+ if (!e->hid_hw_request)
+ continue;
+
+ ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source);
+ if (ret)
+ goto out;
+ }
+ ret = 0;
+
+out:
+ srcu_read_unlock(&hdev->bpf.srcu, idx);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dispatch_hid_bpf_raw_requests);
+
+int dispatch_hid_bpf_output_report(struct hid_device *hdev,
+ __u8 *buf, u32 size, u64 source,
+ bool from_bpf)
+{
+ struct hid_bpf_ctx_kern ctx_kern = {
+ .ctx = {
+ .hid = hdev,
+ .allocated_size = size,
+ .size = size,
+ },
+ .data = buf,
+ .from_bpf = from_bpf,
+ };
+ 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)) {
+ if (!e->hid_hw_output_report)
+ continue;
+
+ ret = e->hid_hw_output_report(&ctx_kern.ctx, source);
+ if (ret)
+ goto out;
+ }
+ ret = 0;
+
+out:
+ srcu_read_unlock(&hdev->bpf.srcu, idx);
+ return ret;
}
+EXPORT_SYMBOL_GPL(dispatch_hid_bpf_output_report);
-u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
+const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size)
{
int ret;
struct hid_bpf_ctx_kern ctx_kern = {
@@ -116,13 +168,16 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
},
};
+ if (!hdev->bpf.rdesc_ops)
+ goto ignore_bpf;
+
ctx_kern.data = kzalloc(ctx_kern.ctx.allocated_size, GFP_KERNEL);
if (!ctx_kern.data)
goto ignore_bpf;
memcpy(ctx_kern.data, rdesc, min_t(unsigned int, *size, HID_MAX_DESCRIPTOR_SIZE));
- ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_RDESC_FIXUP, &ctx_kern);
+ ret = hdev->bpf.rdesc_ops->hid_rdesc_fixup(&ctx_kern.ctx);
if (ret < 0)
goto ignore_bpf;
@@ -133,59 +188,38 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
*size = ret;
}
- rdesc = krealloc(ctx_kern.data, *size, GFP_KERNEL);
-
- return rdesc;
+ return krealloc(ctx_kern.data, *size, GFP_KERNEL);
ignore_bpf:
kfree(ctx_kern.data);
- return kmemdup(rdesc, *size, GFP_KERNEL);
+ return rdesc;
}
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
-/**
- * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
- *
- * @ctx: The HID-BPF context
- * @offset: The offset within the memory
- * @rdwr_buf_size: the const size of the buffer
- *
- * @returns %NULL on error, an %__u8 memory pointer on success
- */
-noinline __u8 *
-hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
+static int device_match_id(struct device *dev, const void *id)
{
- struct hid_bpf_ctx_kern *ctx_kern;
-
- if (!ctx)
- return NULL;
+ struct hid_device *hdev = to_hid_device(dev);
- ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+ return hdev->id == *(int *)id;
+}
- if (rdwr_buf_size + offset > ctx->allocated_size)
- return NULL;
+struct hid_device *hid_get_device(unsigned int hid_id)
+{
+ struct device *dev;
- return ctx_kern->data + offset;
-}
+ if (!hid_ops)
+ return ERR_PTR(-EINVAL);
-/*
- * The following set contains all functions we agree BPF programs
- * can use.
- */
-BTF_SET8_START(hid_bpf_kfunc_ids)
-BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
-BTF_SET8_END(hid_bpf_kfunc_ids)
+ dev = bus_find_device(hid_ops->bus_type, NULL, &hid_id, device_match_id);
+ if (!dev)
+ return ERR_PTR(-EINVAL);
-static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
- .owner = THIS_MODULE,
- .set = &hid_bpf_kfunc_ids,
-};
+ return to_hid_device(dev);
+}
-static int device_match_id(struct device *dev, const void *id)
+void hid_put_device(struct hid_device *hid)
{
- struct hid_device *hdev = to_hid_device(dev);
-
- return hdev->id == *(int *)id;
+ put_device(&hid->dev);
}
static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size)
@@ -224,7 +258,7 @@ static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size
return 0;
}
-static int hid_bpf_allocate_event_data(struct hid_device *hdev)
+int hid_bpf_allocate_event_data(struct hid_device *hdev)
{
/* hdev->bpf.device_data is already allocated, abort */
if (hdev->bpf.device_data)
@@ -235,68 +269,41 @@ static int hid_bpf_allocate_event_data(struct hid_device *hdev)
int hid_bpf_reconnect(struct hid_device *hdev)
{
- if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
+ if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status)) {
+ /* trigger call to call_hid_bpf_rdesc_fixup() during the next probe */
+ hdev->bpf_rsize = 0;
return device_reprobe(&hdev->dev);
+ }
return 0;
}
+/* Disables missing prototype warnings */
+__bpf_kfunc_start_defs();
+
/**
- * hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
+ * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
*
- * @hid_id: the system unique identifier of the HID device
- * @prog_fd: an fd in the user process representing the program to attach
- * @flags: any logical OR combination of &enum hid_bpf_attach_flags
+ * @ctx: The HID-BPF context
+ * @offset: The offset within the memory
+ * @rdwr_buf_size: the const size of the buffer
*
- * @returns an fd of a bpf_link object on success (> %0), an error code otherwise.
- * Closing this fd will detach the program from the HID device (unless the bpf_link
- * is pinned to the BPF file system).
+ * @returns %NULL on error, an %__u8 memory pointer on success
*/
-/* called from syscall */
-noinline int
-hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
+__bpf_kfunc __u8 *
+hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
{
- struct hid_device *hdev;
- struct device *dev;
- int fd, err, prog_type = hid_bpf_get_prog_attach_type(prog_fd);
-
- if (!hid_bpf_ops)
- return -EINVAL;
-
- if (prog_type < 0)
- return prog_type;
-
- if (prog_type >= HID_BPF_PROG_TYPE_MAX)
- return -EINVAL;
-
- if ((flags & ~HID_BPF_FLAG_MASK))
- return -EINVAL;
-
- dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
- if (!dev)
- return -EINVAL;
-
- hdev = to_hid_device(dev);
+ struct hid_bpf_ctx_kern *ctx_kern;
- if (prog_type == HID_BPF_PROG_TYPE_DEVICE_EVENT) {
- err = hid_bpf_allocate_event_data(hdev);
- if (err)
- return err;
- }
+ if (!ctx)
+ return NULL;
- fd = __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
- if (fd < 0)
- return fd;
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
- if (prog_type == HID_BPF_PROG_TYPE_RDESC_FIXUP) {
- err = hid_bpf_reconnect(hdev);
- if (err) {
- close_fd(fd);
- return err;
- }
- }
+ if (rdwr_buf_size + offset > ctx->allocated_size)
+ return NULL;
- return fd;
+ return ctx_kern->data + offset;
}
/**
@@ -306,25 +313,21 @@ hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
*
* @returns A pointer to &struct hid_bpf_ctx on success, %NULL on error.
*/
-noinline struct hid_bpf_ctx *
+__bpf_kfunc struct hid_bpf_ctx *
hid_bpf_allocate_context(unsigned int hid_id)
{
struct hid_device *hdev;
struct hid_bpf_ctx_kern *ctx_kern = NULL;
- struct device *dev;
- if (!hid_bpf_ops)
+ hdev = hid_get_device(hid_id);
+ if (IS_ERR(hdev))
return NULL;
- dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
- if (!dev)
- return NULL;
-
- hdev = to_hid_device(dev);
-
ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL);
- if (!ctx_kern)
+ if (!ctx_kern) {
+ hid_put_device(hdev);
return NULL;
+ }
ctx_kern->ctx.hid = hdev;
@@ -337,14 +340,56 @@ hid_bpf_allocate_context(unsigned int hid_id)
* @ctx: the HID-BPF context to release
*
*/
-noinline void
+__bpf_kfunc void
hid_bpf_release_context(struct hid_bpf_ctx *ctx)
{
struct hid_bpf_ctx_kern *ctx_kern;
+ struct hid_device *hid;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+ hid = (struct hid_device *)ctx_kern->ctx.hid; /* ignore const */
kfree(ctx_kern);
+
+ /* get_device() is called by bus_find_device() */
+ hid_put_device(hid);
+}
+
+static int
+__hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
+ enum hid_report_type rtype)
+{
+ struct hid_report_enum *report_enum;
+ struct hid_report *report;
+ u32 report_len;
+
+ /* check arguments */
+ if (!ctx || !hid_ops || !buf)
+ return -EINVAL;
+
+ switch (rtype) {
+ case HID_INPUT_REPORT:
+ case HID_OUTPUT_REPORT:
+ case HID_FEATURE_REPORT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (*buf__sz < 1)
+ return -EINVAL;
+
+ report_enum = ctx->hid->report_enum + rtype;
+ report = hid_ops->hid_get_report(report_enum, buf);
+ if (!report)
+ return -EINVAL;
+
+ report_len = hid_report_len(report);
+
+ if (*buf__sz > report_len)
+ *buf__sz = report_len;
+
+ return 0;
}
/**
@@ -358,29 +403,24 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)
*
* @returns %0 on success, a negative error code otherwise.
*/
-noinline int
+__bpf_kfunc int
hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
enum hid_report_type rtype, enum hid_class_request reqtype)
{
- struct hid_device *hdev;
- struct hid_report *report;
- struct hid_report_enum *report_enum;
+ struct hid_bpf_ctx_kern *ctx_kern;
+ size_t size = buf__sz;
u8 *dma_data;
- u32 report_len;
int ret;
- /* check arguments */
- if (!ctx || !hid_bpf_ops || !buf)
- return -EINVAL;
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
- switch (rtype) {
- case HID_INPUT_REPORT:
- case HID_OUTPUT_REPORT:
- case HID_FEATURE_REPORT:
- break;
- default:
- return -EINVAL;
- }
+ if (ctx_kern->from_bpf)
+ return -EDEADLOCK;
+
+ /* check arguments */
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype);
+ if (ret)
+ return ret;
switch (reqtype) {
case HID_REQ_GET_REPORT:
@@ -394,31 +434,18 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
return -EINVAL;
}
- if (buf__sz < 1)
- return -EINVAL;
-
- hdev = (struct hid_device *)ctx->hid; /* discard const */
-
- report_enum = hdev->report_enum + rtype;
- report = hid_bpf_ops->hid_get_report(report_enum, buf);
- if (!report)
- return -EINVAL;
-
- report_len = hid_report_len(report);
-
- if (buf__sz > report_len)
- buf__sz = report_len;
-
- dma_data = kmemdup(buf, buf__sz, GFP_KERNEL);
+ dma_data = kmemdup(buf, size, GFP_KERNEL);
if (!dma_data)
return -ENOMEM;
- ret = hid_bpf_ops->hid_hw_raw_request(hdev,
+ ret = hid_ops->hid_hw_raw_request(ctx->hid,
dma_data[0],
dma_data,
- buf__sz,
+ size,
rtype,
- reqtype);
+ reqtype,
+ (u64)(long)ctx,
+ true); /* prevent infinite recursions */
if (ret > 0)
memcpy(buf, dma_data, ret);
@@ -427,25 +454,144 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
return ret;
}
-/* our HID-BPF entrypoints */
-BTF_SET8_START(hid_bpf_fmodret_ids)
-BTF_ID_FLAGS(func, hid_bpf_device_event)
-BTF_ID_FLAGS(func, hid_bpf_rdesc_fixup)
-BTF_ID_FLAGS(func, __hid_bpf_tail_call)
-BTF_SET8_END(hid_bpf_fmodret_ids)
+/**
+ * hid_bpf_hw_output_report - Send an output report to a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ *
+ * Returns the number of bytes transferred on success, a negative error code otherwise.
+ */
+__bpf_kfunc int
+hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+ size_t size = buf__sz;
+ u8 *dma_data;
+ int ret;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+ if (ctx_kern->from_bpf)
+ return -EDEADLOCK;
+
+ /* check arguments */
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT);
+ if (ret)
+ return ret;
+
+ dma_data = kmemdup(buf, size, GFP_KERNEL);
+ if (!dma_data)
+ return -ENOMEM;
-static const struct btf_kfunc_id_set hid_bpf_fmodret_set = {
+ ret = hid_ops->hid_hw_output_report(ctx->hid, dma_data, size, (u64)(long)ctx, true);
+
+ kfree(dma_data);
+ return ret;
+}
+
+static int
+__hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
+ size_t size, bool lock_already_taken)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+ int ret;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+ if (ctx_kern->from_bpf)
+ return -EDEADLOCK;
+
+ /* check arguments */
+ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
+ if (ret)
+ return ret;
+
+ return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
+ lock_already_taken);
+}
+
+/**
+ * hid_bpf_try_input_report - Inject a HID report in the kernel from a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ *
+ * Returns %0 on success, a negative error code otherwise. This function will immediately
+ * fail if the device is not available, thus can be safely used in IRQ context.
+ */
+__bpf_kfunc int
+hid_bpf_try_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
+ const size_t buf__sz)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+ bool from_hid_event_hook;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+ from_hid_event_hook = ctx_kern->data && ctx_kern->data == ctx->hid->bpf.device_data;
+
+ return __hid_bpf_input_report(ctx, type, buf, buf__sz, from_hid_event_hook);
+}
+
+/**
+ * hid_bpf_input_report - Inject a HID report in the kernel from a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ *
+ * Returns %0 on success, a negative error code otherwise. This function will wait for the
+ * device to be available before injecting the event, thus needs to be called in sleepable
+ * context.
+ */
+__bpf_kfunc int
+hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
+ const size_t buf__sz)
+{
+ int ret;
+
+ ret = down_interruptible(&ctx->hid->driver_input_lock);
+ if (ret)
+ return ret;
+
+ /* check arguments */
+ ret = __hid_bpf_input_report(ctx, type, buf, buf__sz, true /* lock_already_taken */);
+
+ up(&ctx->hid->driver_input_lock);
+
+ return ret;
+}
+__bpf_kfunc_end_defs();
+
+/*
+ * The following set contains all functions we agree BPF programs
+ * can use.
+ */
+BTF_KFUNCS_START(hid_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
+BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, hid_bpf_try_input_report)
+BTF_KFUNCS_END(hid_bpf_kfunc_ids)
+
+static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
.owner = THIS_MODULE,
- .set = &hid_bpf_fmodret_ids,
+ .set = &hid_bpf_kfunc_ids,
};
/* for syscall HID-BPF */
-BTF_SET8_START(hid_bpf_syscall_kfunc_ids)
-BTF_ID_FLAGS(func, hid_bpf_attach_prog)
+BTF_KFUNCS_START(hid_bpf_syscall_kfunc_ids)
BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE)
BTF_ID_FLAGS(func, hid_bpf_hw_request)
-BTF_SET8_END(hid_bpf_syscall_kfunc_ids)
+BTF_ID_FLAGS(func, hid_bpf_hw_output_report)
+BTF_ID_FLAGS(func, hid_bpf_input_report)
+BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
.owner = THIS_MODULE,
@@ -454,14 +600,20 @@ static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
int hid_bpf_connect_device(struct hid_device *hdev)
{
- struct hid_bpf_prog_list *prog_list;
+ bool need_to_allocate = false;
+ struct hid_bpf_ops *e;
rcu_read_lock();
- prog_list = rcu_dereference(hdev->bpf.progs[HID_BPF_PROG_TYPE_DEVICE_EVENT]);
+ list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
+ if (e->hid_device_event) {
+ need_to_allocate = true;
+ break;
+ }
+ }
rcu_read_unlock();
/* only allocate BPF data if there are programs attached */
- if (!prog_list)
+ if (!need_to_allocate)
return 0;
return hid_bpf_allocate_event_data(hdev);
@@ -484,13 +636,18 @@ void hid_bpf_destroy_device(struct hid_device *hdev)
/* mark the device as destroyed in bpf so we don't reattach it */
hdev->bpf.destroyed = true;
- __hid_bpf_destroy_device(hdev);
+ __hid_bpf_ops_destroy_device(hdev);
+
+ synchronize_srcu(&hdev->bpf.srcu);
+ cleanup_srcu_struct(&hdev->bpf.srcu);
}
EXPORT_SYMBOL_GPL(hid_bpf_destroy_device);
-void hid_bpf_device_init(struct hid_device *hdev)
+int hid_bpf_device_init(struct hid_device *hdev)
{
- spin_lock_init(&hdev->bpf.progs_lock);
+ INIT_LIST_HEAD(&hdev->bpf.prog_list);
+ mutex_init(&hdev->bpf.prog_list_lock);
+ return init_srcu_struct(&hdev->bpf.srcu);
}
EXPORT_SYMBOL_GPL(hid_bpf_device_init);
@@ -501,30 +658,15 @@ static int __init hid_bpf_init(void)
/* Note: if we exit with an error any time here, we would entirely break HID, which
* is probably not something we want. So we log an error and return success.
*
- * This is not a big deal: the syscall allowing to attach a BPF program to a HID device
- * will not be available, so nobody will be able to use the functionality.
+ * This is not a big deal: nobody will be able to use the functionality.
*/
- err = register_btf_fmodret_id_set(&hid_bpf_fmodret_set);
- if (err) {
- pr_warn("error while registering fmodret entrypoints: %d", err);
- return 0;
- }
-
- err = hid_bpf_preload_skel();
- if (err) {
- pr_warn("error while preloading HID BPF dispatcher: %d", err);
- return 0;
- }
-
- /* register tracing kfuncs after we are sure we can load our preloaded bpf program */
- err = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &hid_bpf_kfunc_set);
+ err = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &hid_bpf_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF tracing kfuncs: %d", err);
return 0;
}
- /* register syscalls after we are sure we can load our preloaded bpf program */
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &hid_bpf_syscall_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF syscall kfuncs: %d", err);
@@ -534,15 +676,6 @@ static int __init hid_bpf_init(void)
return 0;
}
-static void __exit hid_bpf_exit(void)
-{
- /* HID depends on us, so if we hit that code, we are guaranteed that hid
- * has been removed and thus we do not need to clear the HID devices
- */
- hid_bpf_free_links_and_skel();
-}
-
late_initcall(hid_bpf_init);
-module_exit(hid_bpf_exit);
MODULE_AUTHOR("Benjamin Tissoires");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.h b/drivers/hid/bpf/hid_bpf_dispatch.h
index 63dfc8605cd2..44c6ea22233f 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.h
+++ b/drivers/hid/bpf/hid_bpf_dispatch.h
@@ -8,16 +8,13 @@
struct hid_bpf_ctx_kern {
struct hid_bpf_ctx ctx;
u8 *data;
+ bool from_bpf;
};
-int hid_bpf_preload_skel(void);
-void hid_bpf_free_links_and_skel(void);
-int hid_bpf_get_prog_attach_type(int prog_fd);
-int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type, int prog_fd,
- __u32 flags);
-void __hid_bpf_destroy_device(struct hid_device *hdev);
-int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
- struct hid_bpf_ctx_kern *ctx_kern);
+struct hid_device *hid_get_device(unsigned int hid_id);
+void hid_put_device(struct hid_device *hid);
+int hid_bpf_allocate_event_data(struct hid_device *hdev);
+void __hid_bpf_ops_destroy_device(struct hid_device *hdev);
int hid_bpf_reconnect(struct hid_device *hdev);
struct bpf_prog;
diff --git a/drivers/hid/bpf/hid_bpf_jmp_table.c b/drivers/hid/bpf/hid_bpf_jmp_table.c
deleted file mode 100644
index eca34b7372f9..000000000000
--- a/drivers/hid/bpf/hid_bpf_jmp_table.c
+++ /dev/null
@@ -1,565 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-
-/*
- * HID-BPF support for Linux
- *
- * Copyright (c) 2022 Benjamin Tissoires
- */
-
-#include <linux/bitops.h>
-#include <linux/btf.h>
-#include <linux/btf_ids.h>
-#include <linux/circ_buf.h>
-#include <linux/filter.h>
-#include <linux/hid.h>
-#include <linux/hid_bpf.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/workqueue.h>
-#include "hid_bpf_dispatch.h"
-#include "entrypoints/entrypoints.lskel.h"
-
-#define HID_BPF_MAX_PROGS 1024 /* keep this in sync with preloaded bpf,
- * needs to be a power of 2 as we use it as
- * a circular buffer
- */
-
-#define NEXT(idx) (((idx) + 1) & (HID_BPF_MAX_PROGS - 1))
-#define PREV(idx) (((idx) - 1) & (HID_BPF_MAX_PROGS - 1))
-
-/*
- * represents one attached program stored in the hid jump table
- */
-struct hid_bpf_prog_entry {
- struct bpf_prog *prog;
- struct hid_device *hdev;
- enum hid_bpf_prog_type type;
- u16 idx;
-};
-
-struct hid_bpf_jmp_table {
- struct bpf_map *map;
- struct hid_bpf_prog_entry entries[HID_BPF_MAX_PROGS]; /* compacted list, circular buffer */
- int tail, head;
- struct bpf_prog *progs[HID_BPF_MAX_PROGS]; /* idx -> progs mapping */
- unsigned long enabled[BITS_TO_LONGS(HID_BPF_MAX_PROGS)];
-};
-
-#define FOR_ENTRIES(__i, __start, __end) \
- for (__i = __start; CIRC_CNT(__end, __i, HID_BPF_MAX_PROGS); __i = NEXT(__i))
-
-static struct hid_bpf_jmp_table jmp_table;
-
-static DEFINE_MUTEX(hid_bpf_attach_lock); /* held when attaching/detaching programs */
-
-static void hid_bpf_release_progs(struct work_struct *work);
-
-static DECLARE_WORK(release_work, hid_bpf_release_progs);
-
-BTF_ID_LIST(hid_bpf_btf_ids)
-BTF_ID(func, hid_bpf_device_event) /* HID_BPF_PROG_TYPE_DEVICE_EVENT */
-BTF_ID(func, hid_bpf_rdesc_fixup) /* HID_BPF_PROG_TYPE_RDESC_FIXUP */
-
-static int hid_bpf_max_programs(enum hid_bpf_prog_type type)
-{
- switch (type) {
- case HID_BPF_PROG_TYPE_DEVICE_EVENT:
- return HID_BPF_MAX_PROGS_PER_DEV;
- case HID_BPF_PROG_TYPE_RDESC_FIXUP:
- return 1;
- default:
- return -EINVAL;
- }
-}
-
-static int hid_bpf_program_count(struct hid_device *hdev,
- struct bpf_prog *prog,
- enum hid_bpf_prog_type type)
-{
- int i, n = 0;
-
- if (type >= HID_BPF_PROG_TYPE_MAX)
- return -EINVAL;
-
- FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
- struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
-
- if (type != HID_BPF_PROG_TYPE_UNDEF && entry->type != type)
- continue;
-
- if (hdev && entry->hdev != hdev)
- continue;
-
- if (prog && entry->prog != prog)
- continue;
-
- n++;
- }
-
- return n;
-}
-
-__weak noinline int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx)
-{
- return 0;
-}
-
-int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
- struct hid_bpf_ctx_kern *ctx_kern)
-{
- struct hid_bpf_prog_list *prog_list;
- int i, idx, err = 0;
-
- rcu_read_lock();
- prog_list = rcu_dereference(hdev->bpf.progs[type]);
-
- if (!prog_list)
- goto out_unlock;
-
- for (i = 0; i < prog_list->prog_cnt; i++) {
- idx = prog_list->prog_idx[i];
-
- if (!test_bit(idx, jmp_table.enabled))
- continue;
-
- ctx_kern->ctx.index = idx;
- err = __hid_bpf_tail_call(&ctx_kern->ctx);
- if (err < 0)
- break;
- if (err)
- ctx_kern->ctx.retval = err;
- }
-
- out_unlock:
- rcu_read_unlock();
-
- return err;
-}
-
-/*
- * assign the list of programs attached to a given hid device.
- */
-static void __hid_bpf_set_hdev_progs(struct hid_device *hdev, struct hid_bpf_prog_list *new_list,
- enum hid_bpf_prog_type type)
-{
- struct hid_bpf_prog_list *old_list;
-
- spin_lock(&hdev->bpf.progs_lock);
- old_list = rcu_dereference_protected(hdev->bpf.progs[type],
- lockdep_is_held(&hdev->bpf.progs_lock));
- rcu_assign_pointer(hdev->bpf.progs[type], new_list);
- spin_unlock(&hdev->bpf.progs_lock);
- synchronize_rcu();
-
- kfree(old_list);
-}
-
-/*
- * allocate and populate the list of programs attached to a given hid device.
- *
- * Must be called under lock.
- */
-static int hid_bpf_populate_hdev(struct hid_device *hdev, enum hid_bpf_prog_type type)
-{
- struct hid_bpf_prog_list *new_list;
- int i;
-
- if (type >= HID_BPF_PROG_TYPE_MAX || !hdev)
- return -EINVAL;
-
- if (hdev->bpf.destroyed)
- return 0;
-
- new_list = kzalloc(sizeof(*new_list), GFP_KERNEL);
- if (!new_list)
- return -ENOMEM;
-
- FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
- struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
-
- if (entry->type == type && entry->hdev == hdev &&
- test_bit(entry->idx, jmp_table.enabled))
- new_list->prog_idx[new_list->prog_cnt++] = entry->idx;
- }
-
- __hid_bpf_set_hdev_progs(hdev, new_list, type);
-
- return 0;
-}
-
-static void __hid_bpf_do_release_prog(int map_fd, unsigned int idx)
-{
- skel_map_delete_elem(map_fd, &idx);
- jmp_table.progs[idx] = NULL;
-}
-
-static void hid_bpf_release_progs(struct work_struct *work)
-{
- int i, j, n, map_fd = -1;
-
- if (!jmp_table.map)
- return;
-
- /* retrieve a fd of our prog_array map in BPF */
- map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
- if (map_fd < 0)
- return;
-
- mutex_lock(&hid_bpf_attach_lock); /* protects against attaching new programs */
-
- /* detach unused progs from HID devices */
- FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
- struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
- enum hid_bpf_prog_type type;
- struct hid_device *hdev;
-
- if (test_bit(entry->idx, jmp_table.enabled))
- continue;
-
- /* we have an attached prog */
- if (entry->hdev) {
- hdev = entry->hdev;
- type = entry->type;
-
- hid_bpf_populate_hdev(hdev, type);
-
- /* mark all other disabled progs from hdev of the given type as detached */
- FOR_ENTRIES(j, i, jmp_table.head) {
- struct hid_bpf_prog_entry *next;
-
- next = &jmp_table.entries[j];
-
- if (test_bit(next->idx, jmp_table.enabled))
- continue;
-
- if (next->hdev == hdev && next->type == type)
- next->hdev = NULL;
- }
-
- /* if type was rdesc fixup, reconnect device */
- if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP)
- hid_bpf_reconnect(hdev);
- }
- }
-
- /* remove all unused progs from the jump table */
- FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
- struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
-
- if (test_bit(entry->idx, jmp_table.enabled))
- continue;
-
- if (entry->prog)
- __hid_bpf_do_release_prog(map_fd, entry->idx);
- }
-
- /* compact the entry list */
- n = jmp_table.tail;
- FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
- struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
-
- if (!test_bit(entry->idx, jmp_table.enabled))
- continue;
-
- jmp_table.entries[n] = jmp_table.entries[i];
- n = NEXT(n);
- }
-
- jmp_table.head = n;
-
- mutex_unlock(&hid_bpf_attach_lock);
-
- if (map_fd >= 0)
- close_fd(map_fd);
-}
-
-static void hid_bpf_release_prog_at(int idx)
-{
- int map_fd = -1;
-
- /* retrieve a fd of our prog_array map in BPF */
- map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
- if (map_fd < 0)
- return;
-
- __hid_bpf_do_release_prog(map_fd, idx);
-
- close(map_fd);
-}
-
-/*
- * Insert the given BPF program represented by its fd in the jmp table.
- * Returns the index in the jump table or a negative error.
- */
-static int hid_bpf_insert_prog(int prog_fd, struct bpf_prog *prog)
-{
- int i, index = -1, map_fd = -1, err = -EINVAL;
-
- /* retrieve a fd of our prog_array map in BPF */
- map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
-
- if (map_fd < 0) {
- err = -EINVAL;
- goto out;
- }
-
- /* find the first available index in the jmp_table */
- for (i = 0; i < HID_BPF_MAX_PROGS; i++) {
- if (!jmp_table.progs[i] && index < 0) {
- /* mark the index as used */
- jmp_table.progs[i] = prog;
- index = i;
- __set_bit(i, jmp_table.enabled);
- }
- }
- if (index < 0) {
- err = -ENOMEM;
- goto out;
- }
-
- /* insert the program in the jump table */
- err = skel_map_update_elem(map_fd, &index, &prog_fd, 0);
- if (err)
- goto out;
-
- /* return the index */
- err = index;
-
- out:
- if (err < 0)
- __hid_bpf_do_release_prog(map_fd, index);
- if (map_fd >= 0)
- close_fd(map_fd);
- return err;
-}
-
-int hid_bpf_get_prog_attach_type(int prog_fd)
-{
- struct bpf_prog *prog = NULL;
- int i;
- int prog_type = HID_BPF_PROG_TYPE_UNDEF;
-
- prog = bpf_prog_get(prog_fd);
- if (IS_ERR(prog))
- return PTR_ERR(prog);
-
- for (i = 0; i < HID_BPF_PROG_TYPE_MAX; i++) {
- if (hid_bpf_btf_ids[i] == prog->aux->attach_btf_id) {
- prog_type = i;
- break;
- }
- }
-
- bpf_prog_put(prog);
-
- return prog_type;
-}
-
-static void hid_bpf_link_release(struct bpf_link *link)
-{
- struct hid_bpf_link *hid_link =
- container_of(link, struct hid_bpf_link, link);
-
- __clear_bit(hid_link->hid_table_index, jmp_table.enabled);
- schedule_work(&release_work);
-}
-
-static void hid_bpf_link_dealloc(struct bpf_link *link)
-{
- struct hid_bpf_link *hid_link =
- container_of(link, struct hid_bpf_link, link);
-
- kfree(hid_link);
-}
-
-static void hid_bpf_link_show_fdinfo(const struct bpf_link *link,
- struct seq_file *seq)
-{
- seq_printf(seq,
- "attach_type:\tHID-BPF\n");
-}
-
-static const struct bpf_link_ops hid_bpf_link_lops = {
- .release = hid_bpf_link_release,
- .dealloc = hid_bpf_link_dealloc,
- .show_fdinfo = hid_bpf_link_show_fdinfo,
-};
-
-/* called from syscall */
-noinline int
-__hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type,
- int prog_fd, __u32 flags)
-{
- struct bpf_link_primer link_primer;
- struct hid_bpf_link *link;
- struct bpf_prog *prog = NULL;
- struct hid_bpf_prog_entry *prog_entry;
- int cnt, err = -EINVAL, prog_table_idx = -1;
-
- /* take a ref on the prog itself */
- prog = bpf_prog_get(prog_fd);
- if (IS_ERR(prog))
- return PTR_ERR(prog);
-
- mutex_lock(&hid_bpf_attach_lock);
-
- link = kzalloc(sizeof(*link), GFP_USER);
- if (!link) {
- err = -ENOMEM;
- goto err_unlock;
- }
-
- bpf_link_init(&link->link, BPF_LINK_TYPE_UNSPEC,
- &hid_bpf_link_lops, prog);
-
- /* do not attach too many programs to a given HID device */
- cnt = hid_bpf_program_count(hdev, NULL, prog_type);
- if (cnt < 0) {
- err = cnt;
- goto err_unlock;
- }
-
- if (cnt >= hid_bpf_max_programs(prog_type)) {
- err = -E2BIG;
- goto err_unlock;
- }
-
- prog_table_idx = hid_bpf_insert_prog(prog_fd, prog);
- /* if the jmp table is full, abort */
- if (prog_table_idx < 0) {
- err = prog_table_idx;
- goto err_unlock;
- }
-
- if (flags & HID_BPF_FLAG_INSERT_HEAD) {
- /* take the previous prog_entry slot */
- jmp_table.tail = PREV(jmp_table.tail);
- prog_entry = &jmp_table.entries[jmp_table.tail];
- } else {
- /* take the next prog_entry slot */
- prog_entry = &jmp_table.entries[jmp_table.head];
- jmp_table.head = NEXT(jmp_table.head);
- }
-
- /* we steal the ref here */
- prog_entry->prog = prog;
- prog_entry->idx = prog_table_idx;
- prog_entry->hdev = hdev;
- prog_entry->type = prog_type;
-
- /* finally store the index in the device list */
- err = hid_bpf_populate_hdev(hdev, prog_type);
- if (err) {
- hid_bpf_release_prog_at(prog_table_idx);
- goto err_unlock;
- }
-
- link->hid_table_index = prog_table_idx;
-
- err = bpf_link_prime(&link->link, &link_primer);
- if (err)
- goto err_unlock;
-
- mutex_unlock(&hid_bpf_attach_lock);
-
- return bpf_link_settle(&link_primer);
-
- err_unlock:
- mutex_unlock(&hid_bpf_attach_lock);
-
- bpf_prog_put(prog);
- kfree(link);
-
- return err;
-}
-
-void __hid_bpf_destroy_device(struct hid_device *hdev)
-{
- int type, i;
- struct hid_bpf_prog_list *prog_list;
-
- rcu_read_lock();
-
- for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++) {
- prog_list = rcu_dereference(hdev->bpf.progs[type]);
-
- if (!prog_list)
- continue;
-
- for (i = 0; i < prog_list->prog_cnt; i++)
- __clear_bit(prog_list->prog_idx[i], jmp_table.enabled);
- }
-
- rcu_read_unlock();
-
- for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++)
- __hid_bpf_set_hdev_progs(hdev, NULL, type);
-
- /* schedule release of all detached progs */
- schedule_work(&release_work);
-}
-
-#define HID_BPF_PROGS_COUNT 1
-
-static struct bpf_link *links[HID_BPF_PROGS_COUNT];
-static struct entrypoints_bpf *skel;
-
-void hid_bpf_free_links_and_skel(void)
-{
- int i;
-
- /* the following is enough to release all programs attached to hid */
- if (jmp_table.map)
- bpf_map_put_with_uref(jmp_table.map);
-
- for (i = 0; i < ARRAY_SIZE(links); i++) {
- if (!IS_ERR_OR_NULL(links[i]))
- bpf_link_put(links[i]);
- }
- entrypoints_bpf__destroy(skel);
-}
-
-#define ATTACH_AND_STORE_LINK(__name) do { \
- err = entrypoints_bpf__##__name##__attach(skel); \
- if (err) \
- goto out; \
- \
- links[idx] = bpf_link_get_from_fd(skel->links.__name##_fd); \
- if (IS_ERR(links[idx])) { \
- err = PTR_ERR(links[idx]); \
- goto out; \
- } \
- \
- /* Avoid taking over stdin/stdout/stderr of init process. Zeroing out \
- * makes skel_closenz() a no-op later in iterators_bpf__destroy(). \
- */ \
- close_fd(skel->links.__name##_fd); \
- skel->links.__name##_fd = 0; \
- idx++; \
-} while (0)
-
-int hid_bpf_preload_skel(void)
-{
- int err, idx = 0;
-
- skel = entrypoints_bpf__open();
- if (!skel)
- return -ENOMEM;
-
- err = entrypoints_bpf__load(skel);
- if (err)
- goto out;
-
- jmp_table.map = bpf_map_get_with_uref(skel->maps.hid_jmp_table.map_fd);
- if (IS_ERR(jmp_table.map)) {
- err = PTR_ERR(jmp_table.map);
- goto out;
- }
-
- ATTACH_AND_STORE_LINK(hid_tail_call);
-
- return 0;
-out:
- hid_bpf_free_links_and_skel();
- return err;
-}
diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
new file mode 100644
index 000000000000..702c22fae136
--- /dev/null
+++ b/drivers/hid/bpf/hid_bpf_struct_ops.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * HID-BPF support for Linux
+ *
+ * Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include <linux/bitops.h>
+#include <linux/bpf_verifier.h>
+#include <linux/bpf.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/filter.h>
+#include <linux/hid.h>
+#include <linux/hid_bpf.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/workqueue.h>
+#include "hid_bpf_dispatch.h"
+
+static struct btf *hid_bpf_ops_btf;
+
+static int hid_bpf_ops_init(struct btf *btf)
+{
+ hid_bpf_ops_btf = btf;
+ return 0;
+}
+
+static bool hid_bpf_ops_is_valid_access(int off, int size,
+ enum bpf_access_type type,
+ const struct bpf_prog *prog,
+ struct bpf_insn_access_aux *info)
+{
+ return bpf_tracing_btf_ctx_access(off, size, type, prog, info);
+}
+
+static int hid_bpf_ops_check_member(const struct btf_type *t,
+ const struct btf_member *member,
+ const struct bpf_prog *prog)
+{
+ u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
+ case offsetof(struct hid_bpf_ops, hid_hw_request):
+ case offsetof(struct hid_bpf_ops, hid_hw_output_report):
+ break;
+ default:
+ if (prog->sleepable)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct hid_bpf_offset_write_range {
+ const char *struct_name;
+ u32 struct_length;
+ u32 start;
+ u32 end;
+};
+
+static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
+ const struct bpf_reg_state *reg,
+ int off, int size)
+{
+#define WRITE_RANGE(_name, _field, _is_string) \
+ { \
+ .struct_name = #_name, \
+ .struct_length = sizeof(struct _name), \
+ .start = offsetof(struct _name, _field), \
+ .end = offsetofend(struct _name, _field) - !!(_is_string), \
+ }
+
+ const struct hid_bpf_offset_write_range write_ranges[] = {
+ WRITE_RANGE(hid_bpf_ctx, retval, false),
+ WRITE_RANGE(hid_device, name, true),
+ WRITE_RANGE(hid_device, uniq, true),
+ WRITE_RANGE(hid_device, phys, true),
+ };
+#undef WRITE_RANGE
+ const struct btf_type *state = NULL;
+ const struct btf_type *t;
+ const char *cur = NULL;
+ int i;
+
+ t = btf_type_by_id(reg->btf, reg->btf_id);
+
+ for (i = 0; i < ARRAY_SIZE(write_ranges); i++) {
+ const struct hid_bpf_offset_write_range *write_range = &write_ranges[i];
+ s32 type_id;
+
+ /* we already found a writeable struct, but there is a
+ * new one, let's break the loop.
+ */
+ if (t == state && write_range->struct_name != cur)
+ break;
+
+ /* new struct to look for */
+ if (write_range->struct_name != cur) {
+ type_id = btf_find_by_name_kind(reg->btf, write_range->struct_name,
+ BTF_KIND_STRUCT);
+ if (type_id < 0)
+ return -EINVAL;
+
+ state = btf_type_by_id(reg->btf, type_id);
+ }
+
+ /* this is not the struct we are looking for */
+ if (t != state) {
+ cur = write_range->struct_name;
+ continue;
+ }
+
+ /* first time we see this struct, check for out of bounds */
+ if (cur != write_range->struct_name &&
+ off + size > write_range->struct_length) {
+ bpf_log(log, "write access for struct %s at off %d with size %d\n",
+ write_range->struct_name, off, size);
+ return -EACCES;
+ }
+
+ /* now check if we are in our boundaries */
+ if (off >= write_range->start && off + size <= write_range->end)
+ return NOT_INIT;
+
+ cur = write_range->struct_name;
+ }
+
+
+ if (t != state)
+ bpf_log(log, "write access to this struct is not supported\n");
+ else
+ bpf_log(log,
+ "write access at off %d with size %d on read-only part of %s\n",
+ off, size, cur);
+
+ return -EACCES;
+}
+
+static const struct bpf_verifier_ops hid_bpf_verifier_ops = {
+ .get_func_proto = bpf_base_func_proto,
+ .is_valid_access = hid_bpf_ops_is_valid_access,
+ .btf_struct_access = hid_bpf_ops_btf_struct_access,
+};
+
+static int hid_bpf_ops_init_member(const struct btf_type *t,
+ const struct btf_member *member,
+ void *kdata, const void *udata)
+{
+ const struct hid_bpf_ops *uhid_bpf_ops;
+ struct hid_bpf_ops *khid_bpf_ops;
+ u32 moff;
+
+ uhid_bpf_ops = (const struct hid_bpf_ops *)udata;
+ khid_bpf_ops = (struct hid_bpf_ops *)kdata;
+
+ moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct hid_bpf_ops, hid_id):
+ /* For hid_id and flags fields, this function has to copy it
+ * and return 1 to indicate that the data has been handled by
+ * the struct_ops type, or the verifier will reject the map if
+ * the value of those fields is not zero.
+ */
+ khid_bpf_ops->hid_id = uhid_bpf_ops->hid_id;
+ return 1;
+ case offsetof(struct hid_bpf_ops, flags):
+ if (uhid_bpf_ops->flags & ~BPF_F_BEFORE)
+ return -EINVAL;
+ khid_bpf_ops->flags = uhid_bpf_ops->flags;
+ return 1;
+ }
+ return 0;
+}
+
+static int hid_bpf_reg(void *kdata, struct bpf_link *link)
+{
+ struct hid_bpf_ops *ops = kdata;
+ struct hid_device *hdev;
+ int count, err = 0;
+
+ /* prevent multiple attach of the same struct_ops */
+ if (ops->hdev)
+ return -EINVAL;
+
+ hdev = hid_get_device(ops->hid_id);
+ if (IS_ERR(hdev))
+ return PTR_ERR(hdev);
+
+ ops->hdev = hdev;
+
+ mutex_lock(&hdev->bpf.prog_list_lock);
+
+ count = list_count_nodes(&hdev->bpf.prog_list);
+ if (count >= HID_BPF_MAX_PROGS_PER_DEV) {
+ err = -E2BIG;
+ goto out_unlock;
+ }
+
+ if (ops->hid_rdesc_fixup) {
+ if (hdev->bpf.rdesc_ops) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ hdev->bpf.rdesc_ops = ops;
+ }
+
+ if (ops->hid_device_event) {
+ err = hid_bpf_allocate_event_data(hdev);
+ if (err)
+ goto out_unlock;
+ }
+
+ if (ops->flags & BPF_F_BEFORE)
+ list_add_rcu(&ops->list, &hdev->bpf.prog_list);
+ else
+ list_add_tail_rcu(&ops->list, &hdev->bpf.prog_list);
+ synchronize_srcu(&hdev->bpf.srcu);
+
+out_unlock:
+ mutex_unlock(&hdev->bpf.prog_list_lock);
+
+ if (err) {
+ if (hdev->bpf.rdesc_ops == ops)
+ hdev->bpf.rdesc_ops = NULL;
+ hid_put_device(hdev);
+ } else if (ops->hid_rdesc_fixup) {
+ hid_bpf_reconnect(hdev);
+ }
+
+ return err;
+}
+
+static void hid_bpf_unreg(void *kdata, struct bpf_link *link)
+{
+ struct hid_bpf_ops *ops = kdata;
+ struct hid_device *hdev;
+ bool reconnect = false;
+
+ hdev = ops->hdev;
+
+ /* check if __hid_bpf_ops_destroy_device() has been called */
+ if (!hdev)
+ return;
+
+ mutex_lock(&hdev->bpf.prog_list_lock);
+
+ list_del_rcu(&ops->list);
+ synchronize_srcu(&hdev->bpf.srcu);
+ ops->hdev = NULL;
+
+ reconnect = hdev->bpf.rdesc_ops == ops;
+ if (reconnect)
+ hdev->bpf.rdesc_ops = NULL;
+
+ mutex_unlock(&hdev->bpf.prog_list_lock);
+
+ if (reconnect)
+ hid_bpf_reconnect(hdev);
+
+ hid_put_device(hdev);
+}
+
+static int __hid_bpf_device_event(struct hid_bpf_ctx *ctx, enum hid_report_type type, u64 source)
+{
+ return 0;
+}
+
+static int __hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
+{
+ return 0;
+}
+
+static int __hid_bpf_hw_request(struct hid_bpf_ctx *ctx, unsigned char reportnum,
+ enum hid_report_type rtype, enum hid_class_request reqtype,
+ u64 source)
+{
+ return 0;
+}
+
+static int __hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, u64 source)
+{
+ return 0;
+}
+
+static struct hid_bpf_ops __bpf_hid_bpf_ops = {
+ .hid_device_event = __hid_bpf_device_event,
+ .hid_rdesc_fixup = __hid_bpf_rdesc_fixup,
+ .hid_hw_request = __hid_bpf_hw_request,
+ .hid_hw_output_report = __hid_bpf_hw_output_report,
+};
+
+static struct bpf_struct_ops bpf_hid_bpf_ops = {
+ .verifier_ops = &hid_bpf_verifier_ops,
+ .init = hid_bpf_ops_init,
+ .check_member = hid_bpf_ops_check_member,
+ .init_member = hid_bpf_ops_init_member,
+ .reg = hid_bpf_reg,
+ .unreg = hid_bpf_unreg,
+ .name = "hid_bpf_ops",
+ .cfi_stubs = &__bpf_hid_bpf_ops,
+ .owner = THIS_MODULE,
+};
+
+void __hid_bpf_ops_destroy_device(struct hid_device *hdev)
+{
+ struct hid_bpf_ops *e;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
+ hid_put_device(hdev);
+ e->hdev = NULL;
+ }
+ rcu_read_unlock();
+}
+
+static int __init hid_bpf_struct_ops_init(void)
+{
+ return register_bpf_struct_ops(&bpf_hid_bpf_ops, hid_bpf_ops);
+}
+late_initcall(hid_bpf_struct_ops_init);
diff --git a/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c
new file mode 100644
index 000000000000..caec91391d32
--- /dev/null
+++ b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_BETOP_2185PC 0x11C0
+#define PID_RAPTOR_MACH_2 0x5606
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_BETOP_2185PC, PID_RAPTOR_MACH_2),
+);
+
+/*
+ * For reference, this is the fixed report descriptor
+ *
+ * static const __u8 fixed_rdesc[] = {
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x04, // Usage (Joystick) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 6
+ * 0x85, 0x01, // Report ID (1) 8
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 10
+ * 0x09, 0x30, // Usage (X) 12
+ * 0x75, 0x10, // Report Size (16) 14
+ * 0x95, 0x01, // Report Count (1) 16
+ * 0x15, 0x00, // Logical Minimum (0) 18
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 20
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 23
+ * 0x81, 0x02, // Input (Data,Var,Abs) 26
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 28
+ * 0x09, 0x31, // Usage (Y) 30
+ * 0x75, 0x10, // Report Size (16) 32
+ * 0x95, 0x01, // Report Count (1) 34
+ * 0x15, 0x00, // Logical Minimum (0) 36
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 38
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 41
+ * 0x81, 0x02, // Input (Data,Var,Abs) 44
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 46
+ * 0x09, 0x33, // Usage (Rx) 48
+ * 0x75, 0x10, // Report Size (16) 50
+ * 0x95, 0x01, // Report Count (1) 52
+ * 0x15, 0x00, // Logical Minimum (0) 54
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 56
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 59
+ * 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * 0x05, 0x00, // Usage Page (Undefined) 64
+ * 0x09, 0x00, // Usage (Undefined) 66
+ * 0x75, 0x10, // Report Size (16) 68
+ * 0x95, 0x01, // Report Count (1) 70
+ * 0x15, 0x00, // Logical Minimum (0) 72
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 74
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 77
+ * 0x81, 0x02, // Input (Data,Var,Abs) 80
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 82
+ * 0x09, 0x32, // Usage (Z) 84
+ * 0x75, 0x10, // Report Size (16) 86
+ * 0x95, 0x01, // Report Count (1) 88
+ * 0x15, 0x00, // Logical Minimum (0) 90
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 92
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 95
+ * 0x81, 0x02, // Input (Data,Var,Abs) 98
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 100
+ * 0x09, 0x35, // Usage (Rz) 102
+ * 0x75, 0x10, // Report Size (16) 104
+ * 0x95, 0x01, // Report Count (1) 106
+ * 0x15, 0x00, // Logical Minimum (0) 108
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 110
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 113
+ * 0x81, 0x02, // Input (Data,Var,Abs) 116
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 118
+ * 0x09, 0x34, // Usage (Ry) 120
+ * 0x75, 0x10, // Report Size (16) 122
+ * 0x95, 0x01, // Report Count (1) 124
+ * 0x15, 0x00, // Logical Minimum (0) 126
+ * 0x26, 0xff, 0x07, // Logical Maximum (2047) 128
+ * 0x46, 0xff, 0x07, // Physical Maximum (2047) 131
+ * 0x81, 0x02, // Input (Data,Var,Abs) 134
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 136
+ * 0x09, 0x36, // Usage (Slider) 138
+ * 0x75, 0x10, // Report Size (16) 140
+ * 0x95, 0x01, // Report Count (1) 142
+ * 0x15, 0x00, // Logical Minimum (0) 144
+ * 0x26, 0xff, 0x03, // Logical Maximum (1023) 146
+ * 0x46, 0xff, 0x03, // Physical Maximum (1023) 149
+ * 0x81, 0x02, // Input (Data,Var,Abs) 152
+ * 0x05, 0x09, // Usage Page (Button) 154
+ * 0x19, 0x01, // Usage Minimum (1) 156
+ * 0x2a, 0x1d, 0x00, // Usage Maximum (29) 158
+ * 0x15, 0x00, // Logical Minimum (0) 161
+ * 0x25, 0x01, // Logical Maximum (1) 163
+ * 0x75, 0x01, // Report Size (1) 165
+ * 0x96, 0x80, 0x00, // Report Count (128) 167
+ * 0x81, 0x02, // Input (Data,Var,Abs) 170
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 172
+ * 0x09, 0x39, // Usage (Hat switch) 174
+ * 0x26, 0x07, 0x00, // Logical Maximum (7) 176 // changed (was 239)
+ * 0x46, 0x68, 0x01, // Physical Maximum (360) 179
+ * 0x65, 0x14, // Unit (EnglishRotation: deg) 182
+ * 0x75, 0x10, // Report Size (16) 184
+ * 0x95, 0x01, // Report Count (1) 186
+ * 0x81, 0x42, // Input (Data,Var,Abs,Null) 188
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 190
+ * 0x09, 0x00, // Usage (Undefined) 192
+ * 0x75, 0x08, // Report Size (8) 194
+ * 0x95, 0x1d, // Report Count (29) 196
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 198
+ * 0x15, 0x00, // Logical Minimum (0) 200
+ * 0x26, 0xef, 0x00, // Logical Maximum (239) 202
+ * 0x85, 0x58, // Report ID (88) 205
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 207
+ * 0x46, 0xff, 0x00, // Physical Maximum (255) 210
+ * 0x75, 0x08, // Report Size (8) 213
+ * 0x95, 0x3f, // Report Count (63) 215
+ * 0x09, 0x00, // Usage (Undefined) 217
+ * 0x91, 0x02, // Output (Data,Var,Abs) 219
+ * 0x85, 0x59, // Report ID (89) 221
+ * 0x75, 0x08, // Report Size (8) 223
+ * 0x95, 0x80, // Report Count (128) 225
+ * 0x09, 0x00, // Usage (Undefined) 227
+ * 0xb1, 0x02, // Feature (Data,Var,Abs) 229
+ * 0xc0, // End Collection 231
+ * };
+ */
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the joystick sends its hat_switch data between 0 and 239 but
+ * the kernel expects the logical max to stick into a signed 8 bits
+ * integer. We thus divide it by 30 to match what other joysticks are
+ * doing
+ */
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_raptor_mach_2, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ data[177] = 0x07;
+
+ return 0;
+}
+
+/*
+ * The hat_switch value at offsets 33 and 34 (16 bits) needs
+ * to be reduced to a single 8 bit signed integer. So we
+ * divide it by 30.
+ * Byte 34 is always null, so it is ignored.
+ */
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(raptor_mach_2_fix_hat_switch, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 64 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != 0x01) /* not the joystick report ID */
+ return 0;
+
+ data[33] /= 30;
+
+ return 0;
+}
+
+HID_BPF_OPS(raptor_mach_2) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_raptor_mach_2,
+ .hid_device_event = (void *)raptor_mach_2_fix_hat_switch,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != 232;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[177] != 0xef) /* Logical Max of 239 */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c
new file mode 100644
index 000000000000..c2413fa80543
--- /dev/null
+++ b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HP 0x03F0
+#define PID_ELITE_PRESENTER 0x464A
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HP, PID_ELITE_PRESENTER)
+);
+
+/*
+ * Already fixed as of commit 0db117359e47 ("HID: add quirk for 03f0:464a
+ * HP Elite Presenter Mouse") in the kernel, but this is a slightly better
+ * fix.
+ *
+ * The HP Elite Presenter Mouse HID Record Descriptor shows
+ * two mice (Report ID 0x1 and 0x2), one keypad (Report ID 0x5),
+ * two Consumer Controls (Report IDs 0x6 and 0x3).
+ * Prior to these fixes it registers one mouse, one keypad
+ * and one Consumer Control, and it was usable only as a
+ * digital laser pointer (one of the two mouses).
+ * We replace the second mouse collection with a pointer collection,
+ * allowing to use the device both as a mouse and a digital laser
+ * pointer.
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* replace application mouse by application pointer on the second collection */
+ if (data[79] == 0x02)
+ data[79] = 0x01;
+
+ return 0;
+}
+
+HID_BPF_OPS(hp_elite_presenter) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != 264;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Huion__Dial-2.bpf.c b/drivers/hid/bpf/progs/Huion__Dial-2.bpf.c
new file mode 100644
index 000000000000..9670e5ef8d54
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Dial-2.bpf.c
@@ -0,0 +1,636 @@
+// 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_DIAL_2 0x0060
+
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_DIAL_2),
+);
+
+/* 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_T21j_221221
+ */
+char EXPECTED_FIRMWARE_ID[] = "HUION_T216_";
+
+/* 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
+ * and the dial as keyboard combinations. 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 works for both modes. 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 Tablet_Q630M
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 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 1) 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 rdesc does nothing until the tablet is switched to raw mode, see
+ * https://github.com/whot/huion-switcher
+ *
+ *
+ * Second hidraw node is the Pen. This one sends events until the tablet is
+ * switched to raw mode, then it's mute.
+ *
+ * # Report descriptor length: 93 bytes
+ * # HUION Huion Tablet_Q630M
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x15, 0x00, // Logical Minimum (0) 20
+ * # 0x25, 0x01, // Logical Maximum (1) 22
+ * # 0x75, 0x01, // Report Size (1) 24
+ * # 0x95, 0x06, // Report Count (6) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x09, 0x32, // Usage (In Range) 30
+ * # 0x75, 0x01, // Report Size (1) 32
+ * # 0x95, 0x01, // Report Count (1) 34
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 40
+ * # 0x09, 0x30, // Usage (X) 42
+ * # 0x09, 0x31, // Usage (Y) 44
+ * # 0x55, 0x0d, // Unit Exponent (-3) 46
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
+ * # 0x35, 0x00, // Physical Minimum (0) 53
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55
+ * # 0x75, 0x10, // Report Size (16) 58
+ * # 0x95, 0x02, // Report Count (2) 60
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 64
+ * # 0x09, 0x30, // Usage (Tip Pressure) 66
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
+ * # 0x75, 0x10, // Report Size (16) 71
+ * # 0x95, 0x01, // Report Count (1) 73
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * # 0x09, 0x3d, // Usage (X Tilt) 77
+ * # 0x09, 0x3e, // Usage (Y Tilt) 79
+ * # 0x15, 0x81, // Logical Minimum (-127) 81
+ * # 0x25, 0x7f, // Logical Maximum (127) 83
+ * # 0x75, 0x08, // Report Size (8) 85
+ * # 0x95, 0x02, // Report Count (2) 87
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 89
+ * # 0xc0, // End Collection 91
+ * # 0xc0, // End Collection 92
+ * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d 09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
+ *
+ * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
+ * the tablet is switched to raw mode, then it's mute:
+ *
+ * # Report descriptor length: 148 bytes
+ * # HUION Huion Tablet_Q630M
+ * # 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 (Vendor Usage 0x01) 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
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 108
+ * # 0x09, 0x06, // Usage (Keyboard) 110
+ * # 0xa1, 0x01, // Collection (Application) 112
+ * # 0x85, 0x03, // Report ID (3) 114
+ * # 0x05, 0x07, // Usage Page (Keyboard) 116
+ * # 0x19, 0xe0, // Usage Minimum (224) 118
+ * # 0x29, 0xe7, // Usage Maximum (231) 120
+ * # 0x15, 0x00, // Logical Minimum (0) 122
+ * # 0x25, 0x01, // Logical Maximum (1) 124
+ * # 0x75, 0x01, // Report Size (1) 126
+ * # 0x95, 0x08, // Report Count (8) 128
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 130
+ * # 0x05, 0x07, // Usage Page (Keyboard) 132
+ * # 0x19, 0x00, // Usage Minimum (0) 134
+ * # 0x29, 0xff, // Usage Maximum (255) 136
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 138
+ * # 0x75, 0x08, // Report Size (8) 141
+ * # 0x95, 0x06, // Report Count (6) 143
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 145
+ * # 0xc0, // End Collection 147
+ * R: 148 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 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
+ */
+
+#define PAD_REPORT_DESCRIPTOR_LENGTH 148
+#define PEN_REPORT_DESCRIPTOR_LENGTH 93
+#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
+#define PAD_REPORT_ID 3
+#define DIAL_REPORT_ID 17
+#define PEN_REPORT_ID 10
+#define VENDOR_REPORT_ID 8
+#define PAD_REPORT_LENGTH 9
+#define PEN_REPORT_LENGTH 10
+#define VENDOR_REPORT_LENGTH 12
+
+
+__u8 last_button_state;
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PAD_REPORT_ID)
+ LogicalMaximum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 in report - just exists so we get to be a tablet pad
+ Usage_Dig_BarrelSwitch // BTN_STYLUS
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // padding
+ Input(Const)
+ // Bytes 2/3 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 4 in report is the dial
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ // Byte 5 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(0x08)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(7)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(1) // padding
+ Input(Const)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+ )
+};
+
+static const __u8 fixed_rdesc_pen[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PEN_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // -- Byte 1 in report
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(266)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_X
+ Input(Var|Abs) // Bytes 2+3
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(166)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_Y
+ Input(Var|Abs) // Bytes 4+5
+ )
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ Input(Var|Abs) // Byte 6+7
+ ReportSize(8)
+ ReportCount(2)
+ LogicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ Usage_Dig_XTilt
+ Usage_Dig_YTilt
+ Input(Var|Abs) // Byte 8+9
+ )
+ )
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // Byte 0
+ // We leave the pen on the vendor report ID
+ ReportId(VENDOR_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ // Note: reported logical range differs
+ // from the pen report ID for x and y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(53340)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(266)
+ // Bytes 2/3 in report
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(33340)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(166)
+ // Bytes 4/5 in report
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ // Bytes 6/7 in report
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ // Bytes 8/9 in report
+ ReportCount(1) // Padding
+ Input(Const)
+ LogicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ // Byte 10 in report
+ Usage_Dig_XTilt
+ // Byte 11 in report
+ Usage_Dig_YTilt
+ ReportSize(8)
+ ReportCount(2)
+ Input(Var|Abs)
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ 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
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Byte 4 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0x8)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(8)
+ ReportSize(1)
+ Input(Var|Abs)
+ // Byte 5 is the top dial
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ // Byte 6 is the bottom dial
+ UsagePage_Consumer
+ Usage_Con_ACPan
+ Input(Var|Rel)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+static const __u8 disabled_rdesc_pen[] = {
+ FixedSizeVendorReport(PEN_REPORT_LENGTH)
+};
+
+static const __u8 disabled_rdesc_pad[] = {
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(dial_2_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/pen 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);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+ if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
+ return sizeof(disabled_rdesc_pen);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
+ return sizeof(fixed_rdesc_pen);
+ }
+ /* 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(dial_2_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 16 /* size */);
+ static __u8 button;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Only sent if tablet is in default mode */
+ if (data[0] == PAD_REPORT_ID) {
+ /* Nicely enough, this device only supports one button down at a time so
+ * the reports are easy to match. Buttons numbered from the top
+ * Button released: 03 00 00 00 00 00 00 00
+ * Button 1: 03 00 05 00 00 00 00 00 -> b
+ * Button 2: 03 00 08 00 00 00 00 00 -> e
+ * Button 3: 03 00 0c 00 00 00 00 00 -> i
+ * Button 4: 03 00 e0 16 00 00 00 00 -> Ctrl S
+ * Button 5: 03 00 2c 00 00 00 00 00 -> space
+ * Button 6: 03 00 e0 e2 1d 00 00 00 -> Ctrl Alt Z
+ */
+ button &= 0xc0;
+
+ switch ((data[2] << 16) | (data[3] << 8) | data[4]) {
+ case 0x000000:
+ break;
+ case 0x050000:
+ button |= BIT(0);
+ break;
+ case 0x080000:
+ button |= BIT(1);
+ break;
+ case 0x0c0000:
+ button |= BIT(2);
+ break;
+ case 0xe01600:
+ button |= BIT(3);
+ break;
+ case 0x2c0000:
+ button |= BIT(4);
+ break;
+ case 0xe0e21d:
+ button |= BIT(5);
+ break;
+ }
+
+ __u8 report[8] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, 0x00, button};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return sizeof(report);
+ }
+
+ /* Only sent if tablet is in default mode */
+ if (data[0] == DIAL_REPORT_ID) {
+ /*
+ * In default mode, both dials are merged together:
+ *
+ * Dial down: 11 00 ff ff 00 00 00 00 00 -> Dial -1
+ * Dial up: 11 00 01 00 00 00 00 00 00 -> Dial 1
+ */
+ __u16 dial = data[3] << 8 | data[2];
+
+ button &= 0x3f;
+ button |= !!data[1] << 6;
+
+ __u8 report[] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, dial, button};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return sizeof(report);
+ }
+
+ /* Nothing to do for the PEN_REPORT_ID, it's already mapped */
+
+ /* Only sent if tablet is in raw mode */
+ if (data[0] == VENDOR_REPORT_ID) {
+ /* Pad reports */
+ if (data[1] & 0x20) {
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus;
+ __u8 x;
+ __u8 y;
+ __u8 buttons;
+ __u8 dial_1;
+ __u8 dial_2;
+ } __attribute__((packed)) *pad_report;
+ __u8 dial_1 = 0, dial_2 = 0;
+
+ /* Dial report */
+ if (data[1] == 0xf1) {
+ __u8 d = 0;
+
+ if (data[5] == 2)
+ d = 0xff;
+ else
+ d = data[5];
+
+ if (data[3] == 1)
+ dial_1 = d;
+ else
+ dial_2 = d;
+ } else {
+ /* data[4] are the buttons, mapped correctly */
+ last_button_state = data[4];
+ dial_1 = 0; // dial
+ dial_2 = 0;
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = PAD_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->dial_1 = dial_1;
+ pad_report->dial_2 = dial_2;
+
+ return sizeof(struct pad_report);
+ }
+
+ /* Pen reports need nothing done */
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(inspiroy_dial2) = {
+ .hid_device_event = (void *)dial_2_fix_events,
+ .hid_rdesc_fixup = (void *)dial_2_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_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/Huion__Inspiroy-2-M.bpf.c b/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
new file mode 100644
index 000000000000..183d408d893a
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Inspiroy-2-M.bpf.c
@@ -0,0 +1,563 @@
+// 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_INSPIROY_2_M 0x0067
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_M),
+);
+
+/* 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_T21j_221221
+ */
+char EXPECTED_FIRMWARE_ID[] = "HUION_T21k_";
+
+/* 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
+ * and the dial as keyboard combinations. 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 works for both modes. 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 Tablet_H641P
+ * # 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 rdesc does nothing until the tablet is switched to raw mode, see
+ * https://github.com/whot/huion-switcher
+ *
+ *
+ * Second hidraw node is the Pen. This one sends events until the tablet is
+ * switched to raw mode, then it's mute.
+ *
+ * # Report descriptor length: 93 bytes
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
+ * # 0x15, 0x00, // Logical Minimum (0) 20
+ * # 0x25, 0x01, // Logical Maximum (1) 22
+ * # 0x75, 0x01, // Report Size (1) 24
+ * # 0x95, 0x06, // Report Count (6) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x09, 0x32, // Usage (In Range) 30
+ * # 0x75, 0x01, // Report Size (1) 32
+ * # 0x95, 0x01, // Report Count (1) 34
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 40
+ * # 0x09, 0x30, // Usage (X) 42
+ * # 0x09, 0x31, // Usage (Y) 44
+ * # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
+ * # 0x35, 0x00, // Physical Minimum (0) 53
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
+ * # 0x75, 0x10, // Report Size (16) 58
+ * # 0x95, 0x02, // Report Count (2) 60
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 64
+ * # 0x09, 0x30, // Usage (Tip Pressure) 66
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
+ * # 0x75, 0x10, // Report Size (16) 71
+ * # 0x95, 0x01, // Report Count (1) 73
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
+ * # 0x09, 0x3e, // Usage (Y Tilt) 79
+ * # 0x15, 0x81, // Logical Minimum (-127) 81
+ * # 0x25, 0x7f, // Logical Maximum (127) 83
+ * # 0x75, 0x08, // Report Size (8) 85
+ * # 0x95, 0x02, // Report Count (2) 87
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 89
+ * # 0xc0, // End Collection 91
+ * # 0xc0, // End Collection 92
+ * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
+ *
+ * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
+ * the tablet is switched to raw mode, then it's mute:
+ *
+ * # Report descriptor length: 65 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
+ * # 0x19, 0x00, // UsageMinimum (0) 48
+ * # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
+ * # 0x15, 0x00, // Logical Minimum (0) 53
+ * # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
+ * # 0x95, 0x01, // Report Count (1) 58
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 62
+ * # 0xc0, // End Collection 64
+ * R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
+ * N: HUION Huion Tablet_H641P
+ */
+
+#define PAD_REPORT_DESCRIPTOR_LENGTH 133
+#define PEN_REPORT_DESCRIPTOR_LENGTH 93
+#define VENDOR_REPORT_DESCRIPTOR_LENGTH 36
+#define PAD_REPORT_ID 3
+#define PEN_REPORT_ID 10
+#define VENDOR_REPORT_ID 8
+#define PAD_REPORT_LENGTH 8
+#define PEN_REPORT_LENGTH 10
+#define VENDOR_REPORT_LENGTH 12
+
+
+__u16 last_button_state;
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 in report - just exists so we get to be a tablet pad
+ Usage_Dig_BarrelSwitch // BTN_STYLUS
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // padding
+ Input(Const)
+ // Bytes 2/3 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 4 in report is the wheel
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ // Byte 5 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0x8)
+ LogicalMinimum_i8(0x1)
+ LogicalMaximum_i8(0x8)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Arr|Abs)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+ )
+};
+
+static const __u8 fixed_rdesc_pen[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PEN_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // -- Byte 1 in report
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_X
+ Input(Var|Abs) // Bytes 2+3
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_Y
+ Input(Var|Abs) // Bytes 4+5
+ )
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ Input(Var|Abs) // Byte 6+7
+ // Two bytes padding so we don't need to change the report at all
+ ReportSize(8)
+ ReportCount(2)
+ Input(Const) // Byte 6+7
+ )
+ )
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // Byte 0
+ // We leave the pen on the vendor report ID
+ ReportId(VENDOR_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ // Note: reported logical range differs
+ // from the pen report ID for x and y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ // Bytes 2/3 in report
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(20000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ // Bytes 4/5 in report
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ // Bytes 6/7 in report
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8192)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ 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
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Bytes 4 and 5 are the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0xa)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(10)
+ ReportSize(1)
+ Input(Var|Abs)
+ Usage_i8(0x31) // maps to BTN_SOUTH
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportCount(5)
+ Input(Const)
+ // 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)
+ )
+};
+
+static const __u8 disabled_rdesc_pen[] = {
+ FixedSizeVendorReport(PEN_REPORT_LENGTH)
+};
+
+static const __u8 disabled_rdesc_pad[] = {
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_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/pen 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);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+ if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
+ return sizeof(disabled_rdesc_pen);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
+ return sizeof(fixed_rdesc_pen);
+ }
+ /* 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(inspiroy_2_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 default mode */
+ if (data[0] == PAD_REPORT_ID) {
+ /* Nicely enough, this device only supports one button down at a time so
+ * the reports are easy to match. Buttons numbered from the top
+ * Button released: 03 00 00 00 00 00 00 00
+ * Button 1: 03 00 05 00 00 00 00 00 -> b
+ * Button 2: 03 07 11 00 00 00 00 00 -> Ctrl Shift N
+ * Button 3: 03 00 08 00 00 00 00 00 -> e
+ * Button 4: 03 00 0c 00 00 00 00 00 -> i
+ * Button 5: 03 00 2c 00 00 00 00 00 -> space
+ * Button 6: 03 01 08 00 00 00 00 00 -> Ctrl E
+ * Button 7: 03 01 16 00 00 00 00 00 -> Ctrl S
+ * Button 8: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
+ *
+ * Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
+ * Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
+ */
+ __u8 button = 0;
+ __u8 wheel = 0;
+
+ switch (data[1] << 8 | data[2]) {
+ case 0x0000:
+ break;
+ case 0x0005:
+ button = 1;
+ break;
+ case 0x0711:
+ button = 2;
+ break;
+ case 0x0008:
+ button = 3;
+ break;
+ case 0x000c:
+ button = 4;
+ break;
+ case 0x002c:
+ button = 5;
+ break;
+ case 0x0108:
+ button = 6;
+ break;
+ case 0x0116:
+ button = 7;
+ break;
+ case 0x051d:
+ button = 8;
+ break;
+ case 0x012d:
+ wheel = -1;
+ break;
+ case 0x012e:
+ wheel = 1;
+ break;
+ }
+
+ __u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return sizeof(report);
+ }
+
+ /* Nothing to do for the PEN_REPORT_ID, it's already mapped */
+
+ /* Only sent if tablet is in raw mode */
+ if (data[0] == VENDOR_REPORT_ID) {
+ /* Pad reports */
+ if (data[1] & 0x20) {
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus;
+ __u8 x;
+ __u8 y;
+ __u16 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] and data[5] are the buttons, mapped correctly */
+ last_button_state = data[4] | (data[5] << 8);
+ wheel = 0; // wheel
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = PAD_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);
+ }
+
+ /* Pen reports need nothing done */
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(inspiroy_2) = {
+ .hid_device_event = (void *)inspiroy_2_fix_events,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_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/Huion__Inspiroy-2-S.bpf.c b/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
new file mode 100644
index 000000000000..79453362bf97
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Inspiroy-2-S.bpf.c
@@ -0,0 +1,579 @@
+// 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_INSPIROY_2_S 0x0066
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_S),
+);
+
+/* 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_T21j_221221
+ */
+char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
+
+/* 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
+ * and the dial as keyboard combinations. 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 works for both modes. 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 Tablet_H641P
+ * # 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 rdesc does nothing until the tablet is switched to raw mode, see
+ * https://github.com/whot/huion-switcher
+ *
+ *
+ * Second hidraw node is the Pen. This one sends events until the tablet is
+ * switched to raw mode, then it's mute.
+ *
+ * # Report descriptor length: 93 bytes
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
+ * # 0x15, 0x00, // Logical Minimum (0) 20
+ * # 0x25, 0x01, // Logical Maximum (1) 22
+ * # 0x75, 0x01, // Report Size (1) 24
+ * # 0x95, 0x06, // Report Count (6) 26
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * # 0x09, 0x32, // Usage (In Range) 30
+ * # 0x75, 0x01, // Report Size (1) 32
+ * # 0x95, 0x01, // Report Count (1) 34
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 36
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 40
+ * # 0x09, 0x30, // Usage (X) 42
+ * # 0x09, 0x31, // Usage (Y) 44
+ * # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
+ * # 0x35, 0x00, // Physical Minimum (0) 53
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
+ * # 0x75, 0x10, // Report Size (16) 58
+ * # 0x95, 0x02, // Report Count (2) 60
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 62
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 64
+ * # 0x09, 0x30, // Usage (Tip Pressure) 66
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
+ * # 0x75, 0x10, // Report Size (16) 71
+ * # 0x95, 0x01, // Report Count (1) 73
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
+ * # 0x09, 0x3e, // Usage (Y Tilt) 79
+ * # 0x15, 0x81, // Logical Minimum (-127) 81
+ * # 0x25, 0x7f, // Logical Maximum (127) 83
+ * # 0x75, 0x08, // Report Size (8) 85
+ * # 0x95, 0x02, // Report Count (2) 87
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 89
+ * # 0xc0, // End Collection 91
+ * # 0xc0, // End Collection 92
+ * R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
+ *
+ * Third hidraw node is the pad which sends a combination of keyboard shortcuts until
+ * the tablet is switched to raw mode, then it's mute:
+ *
+ * # Report descriptor length: 65 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
+ * # 0x19, 0x00, // UsageMinimum (0) 48
+ * # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
+ * # 0x15, 0x00, // Logical Minimum (0) 53
+ * # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
+ * # 0x95, 0x01, // Report Count (1) 58
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 62
+ * # 0xc0, // End Collection 64
+ * R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
+ * N: HUION Huion Tablet_H641P
+ */
+
+#define PAD_REPORT_DESCRIPTOR_LENGTH 65
+#define PEN_REPORT_DESCRIPTOR_LENGTH 93
+#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
+#define PAD_REPORT_ID 3
+#define PEN_REPORT_ID 10
+#define VENDOR_REPORT_ID 8
+#define PAD_REPORT_LENGTH 8
+#define PEN_REPORT_LENGTH 10
+#define VENDOR_REPORT_LENGTH 12
+
+
+__u8 last_button_state;
+__u8 last_tip_state;
+__u8 last_sec_barrel_state;
+__u8 force_tip_down_count;
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 in report - just exists so we get to be a tablet pad
+ Usage_Dig_BarrelSwitch // BTN_STYLUS
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) // padding
+ Input(Const)
+ // Bytes 2/3 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 4 in report is the wheel
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Var|Rel)
+ // Byte 5 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0x6)
+ LogicalMinimum_i8(0x1)
+ LogicalMaximum_i8(0x6)
+ ReportCount(1)
+ ReportSize(8)
+ Input(Arr|Abs)
+ )
+ // Make sure we match our original report length
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+ )
+};
+
+static const __u8 fixed_rdesc_pen[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // -- Byte 0 in report
+ ReportId(PEN_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // -- Byte 1 in report
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_X
+ Input(Var|Abs) // Bytes 2+3
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32767)
+ Usage_GD_Y
+ Input(Var|Abs) // Bytes 4+5
+ )
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ Input(Var|Abs) // Byte 6+7
+ // Two bytes padding so we don't need to change the report at all
+ ReportSize(8)
+ ReportCount(2)
+ Input(Const) // Byte 6+7
+ )
+ )
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ // Byte 0
+ // We leave the pen on the vendor report ID
+ ReportId(VENDOR_REPORT_ID)
+ Usage_Dig_Pen
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ ReportCount(3)
+ Input(Var|Abs)
+ ReportCount(4) // Padding
+ Input(Const)
+ Usage_Dig_InRange
+ ReportCount(1)
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-1)
+ // Note: reported logical range differs
+ // from the pen report ID for x and y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(32000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(160)
+ // Bytes 2/3 in report
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(20000)
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(100)
+ // Bytes 4/5 in report
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ // Bytes 6/7 in report
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8192)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0
+ ReportId(PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 are the buttons
+ 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
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ // Byte 4 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x1)
+ UsageMaximum_i8(0x6)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(6)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(2)
+ Input(Const)
+ // Byte 5 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)
+ )
+};
+
+static const __u8 disabled_rdesc_pen[] = {
+ FixedSizeVendorReport(PEN_REPORT_LENGTH)
+};
+
+static const __u8 disabled_rdesc_pad[] = {
+ FixedSizeVendorReport(PAD_REPORT_LENGTH)
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_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/pen 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);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+ if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
+ if (have_fw_id) {
+ __builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
+ return sizeof(disabled_rdesc_pen);
+ }
+
+ __builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
+ return sizeof(fixed_rdesc_pen);
+ }
+ /* 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(inspiroy_2_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 default mode */
+ if (data[0] == PAD_REPORT_ID) {
+ /* Nicely enough, this device only supports one button down at a time so
+ * the reports are easy to match. Buttons numbered from the top
+ * Button released: 03 00 00 00 00 00 00 00
+ * Button 1: 03 00 05 00 00 00 00 00 -> b
+ * Button 2: 03 00 0c 00 00 00 00 00 -> i
+ * Button 3: 03 00 08 00 00 00 00 00 -> e
+ * Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S
+ * Button 5: 03 00 2c 00 00 00 00 00 -> space
+ * Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
+ *
+ * Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
+ * Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
+ */
+ __u8 button = 0;
+ __u8 wheel = 0;
+
+ switch (data[1] << 8 | data[2]) {
+ case 0x0000:
+ break;
+ case 0x0005:
+ button = 1;
+ break;
+ case 0x000c:
+ button = 2;
+ break;
+ case 0x0008:
+ button = 3;
+ break;
+ case 0x0116:
+ button = 4;
+ break;
+ case 0x002c:
+ button = 5;
+ break;
+ case 0x051d:
+ button = 6;
+ break;
+ case 0x012d:
+ wheel = -1;
+ break;
+ case 0x012e:
+ wheel = 1;
+ break;
+
+ }
+
+ __u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return sizeof(report);
+ }
+
+ /* Nothing to do for the PEN_REPORT_ID, it's already mapped */
+
+ /* Only sent if tablet is in raw mode */
+ if (data[0] == VENDOR_REPORT_ID) {
+ /* Pad reports */
+ if (data[1] & 0x20) {
+ /* See fixed_rdesc_pad */
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus;
+ __u8 x;
+ __u8 y;
+ __u8 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] are the buttons, mapped correctly */
+ last_button_state = data[4];
+ wheel = 0; // wheel
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = PAD_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);
+ } else if (data[1] & 0x80) { /* Pen reports with InRange 1 */
+ __u8 tip_state = data[1] & 0x1;
+ __u8 sec_barrel_state = data[1] & 0x4;
+
+ if (force_tip_down_count > 0) {
+ data[1] |= 0x1;
+ --force_tip_down_count;
+ if (tip_state)
+ force_tip_down_count = 0;
+ }
+
+ /* Tip was down and we just pressed or released the
+ * secondary barrel switch (the physical eraser
+ * button). The device will send up to 4
+ * reports with Tip Switch 0 and sometimes
+ * this report has Tip Switch 0.
+ */
+ if (last_tip_state &&
+ last_sec_barrel_state != sec_barrel_state) {
+ force_tip_down_count = 4;
+ data[1] |= 0x1;
+ }
+ last_tip_state = tip_state;
+ last_sec_barrel_state = sec_barrel_state;
+ }
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(inspiroy_2) = {
+ .hid_device_event = (void *)inspiroy_2_fix_events,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_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/Huion__Kamvas-Pro-19.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
new file mode 100644
index 000000000000..5f43e4071848
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HUION 0x256C
+#define PID_KAMVAS_PRO_19 0x006B
+#define PID_KAMVAS_PRO_27 0x006c
+#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
+#define NAME_KAMVAS_PRO_27 "HUION Huion Tablet_GT2701"
+
+#define TEST_PREFIX "uhid test "
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_27),
+);
+
+bool prev_was_out_of_range;
+bool in_eraser_mode;
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the second button is reported through Secondary Tip Switch instead of Secondary Barrel Switch
+ * - the third button is reported through Invert, and we need some room to report it.
+ *
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x0a, // Report ID (10) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x01, // Collection (Application) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from Secondary Tip Switch */
+ 0x09, 0x3c, // Usage (Invert) 18
+ 0x09, 0x45, // Usage (Eraser) 20
+ 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 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 */
+ 0x95, 0x01, // Report Count (1) /* inserted */
+ 0x81, 0x02, // Input (Data,Var,Abs) /* inserted */
+ 0x05, 0x0d, // Usage Page (Digitizers) /* inserted */
+ 0x09, 0x32, // Usage (In Range) 32
+ 0x75, 0x01, // Report Size (1) 34
+ 0x95, 0x01, // Report Count (1) 36
+ 0x81, 0x02, // Input (Data,Var,Abs) 38
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ 0x09, 0x30, // Usage (X) 44
+ 0x09, 0x31, // Usage (Y) 46
+ 0x55, 0x0d, // Unit Exponent (-3) 48
+ 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ 0x35, 0x00, // Physical Minimum (0) 55
+ 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ 0x75, 0x10, // Report Size (16) 60
+ 0x95, 0x02, // Report Count (2) 62
+ 0x81, 0x02, // Input (Data,Var,Abs) 64
+ 0x05, 0x0d, // Usage Page (Digitizers) 66
+ 0x09, 0x30, // Usage (Tip Pressure) 68
+ 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ 0x75, 0x10, // Report Size (16) 73
+ 0x95, 0x01, // Report Count (1) 75
+ 0x81, 0x02, // Input (Data,Var,Abs) 77
+ 0x09, 0x3d, // Usage (X Tilt) 79
+ 0x09, 0x3e, // Usage (Y Tilt) 81
+ 0x15, 0xa6, // Logical Minimum (-90) 83
+ 0x25, 0x5a, // Logical Maximum (90) 85
+ 0x75, 0x08, // Report Size (8) 87
+ 0x95, 0x02, // Report Count (2) 89
+ 0x81, 0x02, // Input (Data,Var,Abs) 91
+ 0xc0, // End Collection 93
+ 0xc0, // End Collection 94
+ 0x05, 0x0d, // Usage Page (Digitizers) 95
+ 0x09, 0x04, // Usage (Touch Screen) 97
+ 0xa1, 0x01, // Collection (Application) 99
+ 0x85, 0x04, // Report ID (4) 101
+ 0x09, 0x22, // Usage (Finger) 103
+ 0xa1, 0x02, // Collection (Logical) 105
+ 0x05, 0x0d, // Usage Page (Digitizers) 107
+ 0x95, 0x01, // Report Count (1) 109
+ 0x75, 0x06, // Report Size (6) 111
+ 0x09, 0x51, // Usage (Contact Id) 113
+ 0x15, 0x00, // Logical Minimum (0) 115
+ 0x25, 0x3f, // Logical Maximum (63) 117
+ 0x81, 0x02, // Input (Data,Var,Abs) 119
+ 0x09, 0x42, // Usage (Tip Switch) 121
+ 0x25, 0x01, // Logical Maximum (1) 123
+ 0x75, 0x01, // Report Size (1) 125
+ 0x95, 0x01, // Report Count (1) 127
+ 0x81, 0x02, // Input (Data,Var,Abs) 129
+ 0x75, 0x01, // Report Size (1) 131
+ 0x95, 0x01, // Report Count (1) 133
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ 0x75, 0x10, // Report Size (16) 139
+ 0x55, 0x0e, // Unit Exponent (-2) 141
+ 0x65, 0x11, // Unit (SILinear: cm) 143
+ 0x09, 0x30, // Usage (X) 145
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ 0x35, 0x00, // Physical Minimum (0) 150
+ 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ 0x09, 0x31, // Usage (Y) 157
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ 0x05, 0x0d, // Usage Page (Digitizers) 167
+ 0x09, 0x30, // Usage (Tip Pressure) 169
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ 0x75, 0x10, // Report Size (16) 174
+ 0x95, 0x01, // Report Count (1) 176
+ 0x81, 0x02, // Input (Data,Var,Abs) 178
+ 0xc0, // End Collection 180
+ 0x05, 0x0d, // Usage Page (Digitizers) 181
+ 0x09, 0x22, // Usage (Finger) 183
+ 0xa1, 0x02, // Collection (Logical) 185
+ 0x05, 0x0d, // Usage Page (Digitizers) 187
+ 0x95, 0x01, // Report Count (1) 189
+ 0x75, 0x06, // Report Size (6) 191
+ 0x09, 0x51, // Usage (Contact Id) 193
+ 0x15, 0x00, // Logical Minimum (0) 195
+ 0x25, 0x3f, // Logical Maximum (63) 197
+ 0x81, 0x02, // Input (Data,Var,Abs) 199
+ 0x09, 0x42, // Usage (Tip Switch) 201
+ 0x25, 0x01, // Logical Maximum (1) 203
+ 0x75, 0x01, // Report Size (1) 205
+ 0x95, 0x01, // Report Count (1) 207
+ 0x81, 0x02, // Input (Data,Var,Abs) 209
+ 0x75, 0x01, // Report Size (1) 211
+ 0x95, 0x01, // Report Count (1) 213
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ 0x75, 0x10, // Report Size (16) 219
+ 0x55, 0x0e, // Unit Exponent (-2) 221
+ 0x65, 0x11, // Unit (SILinear: cm) 223
+ 0x09, 0x30, // Usage (X) 225
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ 0x35, 0x00, // Physical Minimum (0) 230
+ 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ 0x09, 0x31, // Usage (Y) 237
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ 0x05, 0x0d, // Usage Page (Digitizers) 247
+ 0x09, 0x30, // Usage (Tip Pressure) 249
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ 0x75, 0x10, // Report Size (16) 254
+ 0x95, 0x01, // Report Count (1) 256
+ 0x81, 0x02, // Input (Data,Var,Abs) 258
+ 0xc0, // End Collection 260
+ 0x05, 0x0d, // Usage Page (Digitizers) 261
+ 0x09, 0x56, // Usage (Scan Time) 263
+ 0x55, 0x00, // Unit Exponent (0) 265
+ 0x65, 0x00, // Unit (None) 267
+ 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ 0x95, 0x01, // Report Count (1) 274
+ 0x75, 0x20, // Report Size (32) 276
+ 0x81, 0x02, // Input (Data,Var,Abs) 278
+ 0x09, 0x54, // Usage (Contact Count) 280
+ 0x25, 0x7f, // Logical Maximum (127) 282
+ 0x95, 0x01, // Report Count (1) 284
+ 0x75, 0x08, // Report Size (8) 286
+ 0x81, 0x02, // Input (Data,Var,Abs) 288
+ 0x75, 0x08, // Report Size (8) 290
+ 0x95, 0x08, // Report Count (8) 292
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ 0x85, 0x05, // Report ID (5) 296
+ 0x09, 0x55, // Usage (Contact Max) 298
+ 0x25, 0x0a, // Logical Maximum (10) 300
+ 0x75, 0x08, // Report Size (8) 302
+ 0x95, 0x01, // Report Count (1) 304
+ 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
+ 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ 0x85, 0x06, // Report ID (6) 313
+ 0x15, 0x00, // Logical Minimum (0) 315
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ 0x75, 0x08, // Report Size (8) 320
+ 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)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ 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 PRE_240524_RDESC_FIXED_SIZE;
+}
+
+/*
+ * This tablet reports the 3rd button through invert, but this conflict
+ * with the normal eraser mode.
+ * Fortunately, before entering eraser mode, (so Invert = 1),
+ * the tablet always sends an out-of-proximity event.
+ * So we can detect that single event and:
+ * - if there was none but the invert bit was toggled: this is the
+ * third button
+ * - if there was this out-of-proximity event, we are entering
+ * eraser mode, and we will until the next out-of-proximity.
+ */
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != 0x0a) /* not the pen report ID */
+ return 0;
+
+ /* stylus is out of range */
+ if (!(data[1] & 0x40)) {
+ prev_was_out_of_range = true;
+ in_eraser_mode = false;
+ return 0;
+ }
+
+ /* going into eraser mode (Invert = 1) only happens after an
+ * out of range event
+ */
+ if (prev_was_out_of_range && (data[1] & 0x18))
+ in_eraser_mode = true;
+
+ /* eraser mode works fine */
+ if (in_eraser_mode)
+ return 0;
+
+ /* copy the Invert bit reported for the 3rd button in bit 7 */
+ if (data[1] & 0x08)
+ data[1] |= 0x20;
+
+ /* clear Invert bit now that it was copied */
+ data[1] &= 0xf7;
+
+ prev_was_out_of_range = false;
+
+ return 0;
+}
+
+HID_BPF_OPS(huion_Kamvas_pro_19) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas_pro_19,
+ .hid_device_event = (void *)kamvas_pro_19_fix_3rd_button,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+
+ ctx->retval = !((ctx->rdesc_size == PRE_240524_RDESC_SIZE) ||
+ (ctx->rdesc_size == FW_240524_RDESC_SIZE));
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x43) /* Secondary Tip Switch */
+ ctx->retval = -EINVAL;
+
+ struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
+
+ if (!hctx) {
+ return ctx->retval = -EINVAL;
+ return 0;
+ }
+
+ const char *name = hctx->hid->name;
+
+ /* strip out TEST_PREFIX */
+ if (!__builtin_memcmp(name, TEST_PREFIX, sizeof(TEST_PREFIX) - 1))
+ name += sizeof(TEST_PREFIX) - 1;
+
+ if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)) &&
+ __builtin_memcmp(name, NAME_KAMVAS_PRO_27, sizeof(NAME_KAMVAS_PRO_27)))
+ ctx->retval = -EINVAL;
+
+ hid_bpf_release_context(hctx);
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c
new file mode 100644
index 000000000000..b63f9a48ea45
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas13Gen3.bpf.c
@@ -0,0 +1,1395 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Nicholas LaPointe
+ */
+
+#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_KAMVAS13_GEN3 0x2008
+
+#define VENDOR_DESCRIPTOR_LENGTH 36
+#define TABLET_DESCRIPTOR_LENGTH 368
+#define WHEEL_DESCRIPTOR_LENGTH 108
+
+#define VENDOR_REPORT_ID 8
+#define VENDOR_REPORT_LENGTH 14
+
+#define VENDOR_REPORT_SUBTYPE_PEN 0x08
+#define VENDOR_REPORT_SUBTYPE_PEN_OUT 0x00
+#define VENDOR_REPORT_SUBTYPE_BUTTONS 0x0e
+#define VENDOR_REPORT_SUBTYPE_WHEELS 0x0f
+
+/* For the reports that we create ourselves */
+#define CUSTOM_PAD_REPORT_ID 9
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_HUION, PID_KAMVAS13_GEN3),
+);
+
+
+/*
+ * This tablet can send reports using one of two different data formats,
+ * depending on what "mode" the tablet is in.
+ *
+ * By default, the tablet will send reports that can be decoded using its
+ * included HID descriptors (descriptors 1 and 2, shown below).
+ * This mode will be called "firmware mode" throughout this file.
+ *
+ * The HID descriptor that describes pen events in firmware mode (descriptor 1)
+ * has multiple bugs:
+ * * "Secondary Tip Switch" instead of "Secondary Barrel Switch"
+ * * "Invert" instead of (or potentially shared with) third barrel button
+ * * Specified tablet area of 2048 in³ instead of 293.8 x 165.2mm
+ * * Specified tilt range of -90 to +90 instead of -60 to +60
+ *
+ * While these can be easily patched up by editing the descriptor, a larger
+ * problem with the firmware mode exists: it is impossible to tell which of the
+ * two wheels are being rotated (or having their central button pressed).
+ *
+ *
+ * By using a tool such as huion-switcher (https://github.com/whot/huion-switcher),
+ * the tablet can be made to send reports using a proprietary format that is not
+ * adequately described by its relevant descriptor (descriptor 0, shown below).
+ * This mode will be called "vendor mode" throughout this file.
+ *
+ * The reports sent while in vendor mode allow for proper decoding of the wheels.
+ *
+ * For simplicity and maximum functionality, this BPF focuses strictly on
+ * enabling one to make use of the vendor mode.
+ */
+
+/*
+ * DESCRIPTORS
+ * DESCRIPTOR 0
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # ┅ 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x68, // Report Size (104) 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
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 18
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 21
+ * # 0xa1, 0x01, // Collection (Application) 23
+ * # ┅ 0x85, 0x16, // Report ID (22) 25
+ * # 0x75, 0x08, // Report Size (8) 27
+ * # 0x95, 0x07, // Report Count (7) 29
+ * # 0x09, 0x01, // Usage (Vendor Usage 0x01) 31
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 33
+ * # 0xc0, // End Collection 35
+ * R: 36 06 00 ff 09 01 a1 01 85 08 75 68 95 01 09 01 81 02 c0 06 00 ff 09 01 a1 01 85 16 75 08 95 07 09 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ * DESCRIPTOR 1
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # ┅ 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x43, // Usage (Secondary Tip Switch) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x45, // Usage (Eraser) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x06, // Report Count (6) 28
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x09, 0x32, // Usage (In Range) 32
+ * # 0x75, 0x01, // Report Size (1) 34
+ * # 0x95, 0x01, // Report Count (1) 36
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x30, // Usage (X) 44
+ * # 0x09, 0x31, // Usage (Y) 46
+ * # 0x55, 0x0d, // Unit Exponent (-3) 48
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ * # 0x35, 0x00, // Physical Minimum (0) 55
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x95, 0x02, // Report Count (2) 62
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 64
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 66
+ * # 0x09, 0x30, // Usage (Tip Pressure) 68
+ * # 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ * # 0x75, 0x10, // Report Size (16) 73
+ * # 0x95, 0x01, // Report Count (1) 75
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 77
+ * # 0x09, 0x3d, // Usage (X Tilt) 79
+ * # 0x09, 0x3e, // Usage (Y Tilt) 81
+ * # 0x15, 0xa6, // Logical Minimum (-90) 83
+ * # 0x25, 0x5a, // Logical Maximum (90) 85
+ * # 0x75, 0x08, // Report Size (8) 87
+ * # 0x95, 0x02, // Report Count (2) 89
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 91
+ * # 0xc0, // End Collection 93
+ * # 0xc0, // End Collection 94
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 95
+ * # 0x09, 0x04, // Usage (Touch Screen) 97
+ * # 0xa1, 0x01, // Collection (Application) 99
+ * # ┅ 0x85, 0x04, // Report ID (4) 101
+ * # 0x09, 0x22, // Usage (Finger) 103
+ * # 0xa1, 0x02, // Collection (Logical) 105
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 107
+ * # 0x95, 0x01, // Report Count (1) 109
+ * # 0x75, 0x06, // Report Size (6) 111
+ * # 0x09, 0x51, // Usage (Contact Identifier) 113
+ * # 0x15, 0x00, // Logical Minimum (0) 115
+ * # 0x25, 0x3f, // Logical Maximum (63) 117
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 119
+ * # 0x09, 0x42, // Usage (Tip Switch) 121
+ * # 0x25, 0x01, // Logical Maximum (1) 123
+ * # 0x75, 0x01, // Report Size (1) 125
+ * # 0x95, 0x01, // Report Count (1) 127
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 129
+ * # 0x75, 0x01, // Report Size (1) 131
+ * # 0x95, 0x01, // Report Count (1) 133
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ * # 0x75, 0x10, // Report Size (16) 139
+ * # 0x55, 0x0e, // Unit Exponent (-2) 141
+ * # 0x65, 0x11, // Unit (SILinear: cm) 143
+ * # 0x09, 0x30, // Usage (X) 145
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ * # 0x35, 0x00, // Physical Minimum (0) 150
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ * # 0x09, 0x31, // Usage (Y) 157
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 167
+ * # 0x09, 0x30, // Usage (Tip Pressure) 169
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ * # 0x75, 0x10, // Report Size (16) 174
+ * # 0x95, 0x01, // Report Count (1) 176
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 178
+ * # 0xc0, // End Collection 180
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 181
+ * # 0x09, 0x22, // Usage (Finger) 183
+ * # 0xa1, 0x02, // Collection (Logical) 185
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 187
+ * # 0x95, 0x01, // Report Count (1) 189
+ * # 0x75, 0x06, // Report Size (6) 191
+ * # 0x09, 0x51, // Usage (Contact Identifier) 193
+ * # 0x15, 0x00, // Logical Minimum (0) 195
+ * # 0x25, 0x3f, // Logical Maximum (63) 197
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 199
+ * # 0x09, 0x42, // Usage (Tip Switch) 201
+ * # 0x25, 0x01, // Logical Maximum (1) 203
+ * # 0x75, 0x01, // Report Size (1) 205
+ * # 0x95, 0x01, // Report Count (1) 207
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 209
+ * # 0x75, 0x01, // Report Size (1) 211
+ * # 0x95, 0x01, // Report Count (1) 213
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ * # 0x75, 0x10, // Report Size (16) 219
+ * # 0x55, 0x0e, // Unit Exponent (-2) 221
+ * # 0x65, 0x11, // Unit (SILinear: cm) 223
+ * # 0x09, 0x30, // Usage (X) 225
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ * # 0x35, 0x00, // Physical Minimum (0) 230
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ * # 0x09, 0x31, // Usage (Y) 237
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ * # ┇ 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 247
+ * # 0x09, 0x30, // Usage (Tip Pressure) 249
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ * # 0x75, 0x10, // Report Size (16) 254
+ * # 0x95, 0x01, // Report Count (1) 256
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 258
+ * # 0xc0, // End Collection 260
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 261
+ * # 0x09, 0x56, // Usage (Scan Time) 263
+ * # 0x55, 0x00, // Unit Exponent (0) 265
+ * # 0x65, 0x00, // Unit (None) 267
+ * # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ * # 0x95, 0x01, // Report Count (1) 274
+ * # 0x75, 0x20, // Report Size (32) 276
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 278
+ * # 0x09, 0x54, // Usage (Contact Count) 280
+ * # 0x25, 0x7f, // Logical Maximum (127) 282
+ * # 0x95, 0x01, // Report Count (1) 284
+ * # 0x75, 0x08, // Report Size (8) 286
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 288
+ * # 0x75, 0x08, // Report Size (8) 290
+ * # 0x95, 0x08, // Report Count (8) 292
+ * # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ * # ┅ 0x85, 0x05, // Report ID (5) 296
+ * # 0x09, 0x55, // Usage (Contact Count Maximum) 298
+ * # 0x25, 0x0a, // Logical Maximum (10) 300
+ * # 0x75, 0x08, // Report Size (8) 302
+ * # 0x95, 0x01, // Report Count (1) 304
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page FF00) 308
+ * # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ * # ┅ 0x85, 0x06, // Report ID (6) 313
+ * # 0x15, 0x00, // Logical Minimum (0) 315
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ * # 0x75, 0x08, // Report Size (8) 320
+ * # 0x96, 0x00, 0x01, // Report Count (256) 322
+ * # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 325
+ * # 0xc0, // End Collection 327
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 328
+ * # 0x09, 0x06, // Usage (Keyboard) 330
+ * # 0xa1, 0x01, // Collection (Application) 332
+ * # ┅ 0x85, 0x03, // Report ID (3) 334
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 336
+ * # 0x19, 0xe0, // UsageMinimum (224) 338
+ * # 0x29, 0xe7, // UsageMaximum (231) 340
+ * # 0x15, 0x00, // Logical Minimum (0) 342
+ * # 0x25, 0x01, // Logical Maximum (1) 344
+ * # 0x75, 0x01, // Report Size (1) 346
+ * # 0x95, 0x08, // Report Count (8) 348
+ * # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 350
+ * # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 352
+ * # 0x19, 0x00, // UsageMinimum (0) 354
+ * # 0x29, 0xff, // UsageMaximum (255) 356
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 358
+ * # 0x75, 0x08, // Report Size (8) 361
+ * # 0x95, 0x06, // Report Count (6) 363
+ * # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 365
+ * # 0xc0, // End Collection 367
+ * R: 368 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 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
+ * N: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ * DESCRIPTOR 2
+ * # 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
+ * N: HUION Huion Tablet_GS1333
+ * I: 3 256c 2008
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * VENDOR MODE
+ * HUION_FIRMWARE_ID="HUION_M22c_240606"
+ * HUION_MAGIC_BYTES="140388e500108100ff3fd8130307008008004010"
+ *
+ * MAGIC BYTES
+ * [LogicalMaximum, X] [LogicalMaximum, Y] [LogicalMaximum, Pressure] [ LPI]
+ * 14 03 [ 88 e5] 00 [ 10 81] 00 [ ff 3f] [d8 13] 03 07 00 80 08 00 40 10
+ *
+ *
+ * HIDRAW 0
+ * DESCRIPTIONS
+ * report_subtype = (data[1] >> 4) & 0x0f
+ *
+ * REPORT SUBTYPES
+ * 0x0e Buttons
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6 (top wheel button)
+ * (data[4] & 0x40) button 7 (bottom wheel button)
+ *
+ * All tablet buttons release with the same report:
+ * 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ * Despite data[4] looking like a bit field, only one button
+ * can be unambiguously tracked at a time.
+ * (See NOTES ON SIMULTANEOUS BUTTON HOLDS at the end of this
+ * comment for examples of the confusion this can create.)
+ *
+ * All buttons, with the exceptions of 6 and 7, will repeatedly
+ * report a press event approximately every 225ms while held.
+ *
+ * 0x0f Wheels
+ * data[3] == 1: top wheel
+ * data[3] == 2: bottom wheel
+ * data[5] == 1: clockwise
+ * data[5] == 2: counter-clockwise
+ *
+ * 0x08/0x00 Pen
+ * report_subtype == 0x08: in-range
+ * report_subtype == 0x00: out-of-range
+ * For clarity, this is also equivalent to:
+ * (data[1] & 0x80) in-range
+ *
+ * Switches
+ * (data[1] & 0x01) tip switch
+ * (data[1] & 0x02) barrel switch
+ * (data[1] & 0x04) secondary barrel switch
+ * (data[1] & 0x08) third barrel switch
+ *
+ * Unfortunately, I don't have a pen with an eraser, so I can't
+ * confirm where the invert and eraser bits reside.
+ * If we guess using the definitions from HID descriptor 1,
+ * then they might be...
+ * (data[1] & 0x08) invert (conflicts with third barrel switch)
+ * (data[1] & 0x10) eraser
+ *
+ * data[2], data[3] X (little-endian, maximum 0xe588)
+ *
+ * data[4], data[5] Y (little-endian, maximum 0x8110)
+ *
+ * data[6], data[7] Pressure (little-endian, maximum 0x3fff)
+ *
+ * data[10] X tilt (signed, -60 to +60)
+ * data[11] Y tilt (signed, -60 to +60, inverted)
+ *
+ *
+ * EXAMPLE REPORTS
+ * Top wheel button, press, hold, then release
+ * E: 000000.000040 14 08 e0 01 01 20 00 00 00 00 00 00 00 00 00
+ * E: 000001.531559 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel button, press, hold, then release
+ * E: 000002.787603 14 08 e0 01 01 40 00 00 00 00 00 00 00 00 00
+ * E: 000004.215609 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00
+ *
+ *
+ * Top wheel rotation, one detent CW
+ * E: 000194.003899 14 08 f1 01 01 00 01 00 00 00 00 00 00 00 00
+ *
+ * Top wheel rotation, one detent CCW
+ * E: 000194.997812 14 08 f1 01 01 00 02 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel rotation, one detent CW
+ * E: 000196.693840 14 08 f1 01 02 00 01 00 00 00 00 00 00 00 00
+ *
+ * Bottom wheel rotation, one detent CCW
+ * E: 000197.757895 14 08 f1 01 02 00 02 00 00 00 00 00 00 00 00
+ *
+ *
+ * Button 1, press, hold, then release
+ * E: 000000.000149 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00 < press
+ * E: 000000.447598 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000000.673586 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000000.900582 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.126703 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.347706 14 08 e0 01 01 01 00 00 00 00 00 00 00 00 00
+ * E: 000001.533721 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 2, press, hold, then release
+ * E: 000003.304735 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00 < press
+ * E: 000003.746743 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000003.973741 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.199832 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.426732 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.647738 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.874733 14 08 e0 01 01 02 00 00 00 00 00 00 00 00 00
+ * E: 000004.930713 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 3, press, hold, then release
+ * E: 000006.650346 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00 < press
+ * E: 000007.051782 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000007.273738 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.499794 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.726725 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000007.947765 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000008.174755 14 08 e0 01 01 04 00 00 00 00 00 00 00 00 00
+ * E: 000008.328786 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 4, press, hold, then release
+ * E: 000009.893820 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00 < press
+ * E: 000010.274781 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000010.500931 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000010.722777 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000010.948778 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.175799 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.401153 14 08 e0 01 01 08 00 00 00 00 00 00 00 00 00
+ * E: 000011.432114 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ * Button 5, press, hold, then release
+ * E: 000013.007778 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00 < press
+ * E: 000013.424741 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00 < starting to auto-repeat, every ~225ms
+ * E: 000013.651715 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000013.872763 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.099789 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.325734 14 08 e0 01 01 10 00 00 00 00 00 00 00 00 00
+ * E: 000014.438080 14 08 e0 01 01 00 00 00 00 00 00 00 00 00 00 < release
+ *
+ *
+ * Pen: Top-left, then out of range
+ * E: 000368.572184 14 08 80 00 00 00 00 00 00 00 00 fb ed 03 00
+ * E: 000368.573030 14 08 00 00 00 00 00 00 00 00 00 fb ed 03 00
+ *
+ * Pen: Bottom-right, then out of range
+ * E: 000544.433185 14 08 80 88 e5 10 81 00 00 00 00 00 00 03 00
+ * E: 000544.434183 14 08 00 88 e5 10 81 00 00 00 00 00 00 03 00
+ *
+ * Pen: Max Y tilt (tip of pen points down)
+ * E: 000002.231927 14 08 80 f5 5d 6c 36 00 00 00 00 09 3c 03 00
+ *
+ * Pen: Min Y Tilt (tip of pen points up)
+ * E: 000657.593338 14 08 80 5f 69 fa 2c 00 00 00 00 fe c4 03 00
+ *
+ * Pen: Max X tilt (tip of pen points left)
+ * E: 000742.246503 14 08 80 2a 4f c4 38 00 00 00 00 3c ed 03 00
+ *
+ * Pen: Min X Tilt (tip of pen points right)
+ * E: 000776.404446 14 08 00 18 85 7c 3b 00 00 00 00 c4 ed 03 00
+ *
+ * Pen: Tip switch, max pressure, then low pressure
+ * E: 001138.935675 14 08 81 d2 66 04 40 ff 3f 00 00 00 08 03 00
+ *
+ * E: 001142.403715 14 08 81 9d 69 47 3e 82 04 00 00 00 07 03 00
+ *
+ * Pen: Barrel switch
+ * E: 001210.645652 14 08 82 0d 72 ea 2b 00 00 00 00 db c4 03 00
+ *
+ * Pen: Secondary barrel switch
+ * E: 001211.519729 14 08 84 2c 71 51 2b 00 00 00 00 da c4 03 00
+ *
+ * Pen: Third switch
+ * E: 001212.443722 14 08 88 1d 72 df 2b 00 00 00 00 dc c4 03 00
+ *
+ *
+ * HIDRAW 1
+ * No reports
+ *
+ *
+ * HIDRAW 2
+ * No reports
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * FIRMWARE MODE
+ * HIDRAW 0
+ * No reports
+ *
+ *
+ * HIDRAW 1
+ * EXAMPLE REPORTS
+ * Top wheel button, *release*
+ * E: 000067.043739 8 03 00 00 00 00 00 00 00
+ *
+ * Bottom wheel button, *release*
+ * E: 000068.219161 8 03 00 00 00 00 00 00 00
+ *
+ *
+ * Button 1, press, then release
+ * E: 000163.767870 8 03 00 05 00 00 00 00 00
+ * E: 000165.969193 8 03 00 00 00 00 00 00 00
+ *
+ * Button 2, press, then release
+ * E: 000261.728935 8 03 05 11 00 00 00 00 00
+ * E: 000262.956220 8 03 00 00 00 00 00 00 00
+ *
+ * Button 3, press, then release
+ * E: 000289.127881 8 03 01 16 00 00 00 00 00
+ * E: 000290.014594 8 03 00 00 00 00 00 00 00
+ *
+ * Button 4, press, then release
+ * E: 000303.025839 8 03 00 2c 00 00 00 00 00
+ * E: 000303.994479 8 03 00 00 00 00 00 00 00
+ *
+ * Button 5, press, then release
+ * E: 000315.500835 8 03 05 1d 00 00 00 00 00
+ * E: 000316.603274 8 03 00 00 00 00 00 00 00
+ *
+ * BUTTON SUMMARY
+ * 1 E: 000163.767870 8 03 00 05 00 00 00 00 00 Keyboard: B
+ * 2 E: 000261.728935 8 03 05 11 00 00 00 00 00 Keyboard: LCtrl+LAlt N
+ * 3 E: 000289.127881 8 03 01 16 00 00 00 00 00 Keyboard: LCtrl S
+ * 4 E: 000303.025839 8 03 00 2c 00 00 00 00 00 Keyboard: Space
+ * 5 E: 000315.500835 8 03 05 1d 00 00 00 00 00 Keyboard: LCtrl+LAlt
+ *
+ * All buttons (including the wheel buttons) release the same way:
+ * 03 00 00 00 00 00 00 00
+ *
+ *
+ * Pen: Top-left, then out of range
+ * E: 000063.196828 10 0a c0 00 00 00 00 00 00 00 02
+ * E: 000063.197762 10 0a 00 00 00 00 00 00 00 00 02
+ *
+ * Pen: Bottom-right, then out of range
+ * E: 000197.123138 10 0a c0 ff 7f ff 7f 00 00 00 00
+ * E: 000197.124915 10 0a 00 ff 7f ff 7f 00 00 00 00
+ *
+ * Pen: Max Y Tilt (tip of pen points up)
+ * E: 000291.399541 10 0a c0 19 32 0b 58 00 00 00 3c
+ *
+ * Pen: Min Y tilt (tip of pen points down)
+ * E: 000340.888288 10 0a c0 85 40 89 6e 00 00 17 c4
+ *
+ * Pen: Max X tilt (tip of pen points left)
+ * E: 000165.575115 10 0a c0 a7 34 99 42 00 00 3c f4
+ *
+ * Pen: Min X Tilt (tip of pen points right)
+ * E: 000129.507883 10 0a c0 ea 4b 08 40 00 00 c4 1a
+ *
+ * Pen: Tip switch, max pressure, then low pressure
+ * E: 000242.077160 10 0a c1 7e 3c 12 31 ff 3f 03 fd
+ *
+ * E: 000339.139188 10 0a c1 ee 3a 9e 32 b5 00 06 f6
+ *
+ * Pen: Barrel switch
+ * E: 000037.949777 10 0a c2 5c 28 47 2a 00 00 f6 3c
+ *
+ * Pen: Secondary barrel switch
+ * E: 000038.320840 10 0a c4 e4 27 fd 29 00 00 f3 38
+ *
+ * Pen: Third switch
+ * E: 000038.923822 10 0a c8 97 27 5f 29 00 00 f2 33
+ *
+ *
+ * HIDRAW 2
+ * EXAMPLE REPORTS
+ * Either wheel rotation, one detent CW
+ * E: 000097.276573 9 11 00 01 00 00 00 00 00 00
+ *
+ * Either wheel rotation, one detent CCW
+ * E: 000153.416538 9 11 00 ff ff 00 00 00 00 00
+ *
+ * Either wheel rotation, increasing rotation speed CW
+ * (Note that the wheels on my particular tablet may be
+ * damaged, so the false rotation direction changes
+ * that can be observed might not happen on other units.)
+ * E: 000210.514925 9 11 00 01 00 00 00 00 00 00
+ * E: 000210.725718 9 11 00 01 00 00 00 00 00 00
+ * E: 000210.924009 9 11 00 01 00 00 00 00 00 00
+ * E: 000211.205629 9 11 00 01 00 00 00 00 00 00
+ * E: 000211.280521 9 11 00 0b 00 00 00 00 00 00
+ * E: 000211.340121 9 11 00 0e 00 00 00 00 00 00
+ * E: 000211.404018 9 11 00 0d 00 00 00 00 00 00
+ * E: 000211.462060 9 11 00 0e 00 00 00 00 00 00
+ * E: 000211.544886 9 11 00 0a 00 00 00 00 00 00
+ * E: 000211.606130 9 11 00 0d 00 00 00 00 00 00
+ * E: 000211.674560 9 11 00 0c 00 00 00 00 00 00
+ * E: 000211.712039 9 11 00 16 00 00 00 00 00 00
+ * E: 000211.748076 9 11 00 17 00 00 00 00 00 00
+ * E: 000211.786016 9 11 00 17 00 00 00 00 00 00
+ * E: 000211.832960 9 11 00 11 00 00 00 00 00 00
+ * E: 000211.874081 9 11 00 14 00 00 00 00 00 00
+ * E: 000211.925094 9 11 00 10 00 00 00 00 00 00
+ * E: 000211.959048 9 11 00 18 00 00 00 00 00 00
+ * E: 000212.006937 9 11 00 11 00 00 00 00 00 00
+ * E: 000212.050055 9 11 00 13 00 00 00 00 00 00
+ * E: 000212.091947 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.122989 9 11 00 1a 00 00 00 00 00 00
+ * E: 000212.160866 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.194002 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.242249 9 11 00 11 00 00 00 00 00 00
+ * E: 000212.278061 9 11 00 18 00 00 00 00 00 00
+ * E: 000212.328899 9 11 00 10 00 00 00 00 00 00
+ * E: 000212.354005 9 11 00 22 00 00 00 00 00 00
+ * E: 000212.398995 9 11 00 12 00 00 00 00 00 00
+ * E: 000212.432050 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.471164 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.507047 9 11 00 17 00 00 00 00 00 00
+ * E: 000212.540964 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.567942 9 11 00 1f 00 00 00 00 00 00
+ * E: 000212.610007 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.641101 9 11 00 1b 00 00 00 00 00 00
+ * E: 000212.674113 9 11 00 19 00 00 00 00 00 00
+ * E: 000212.674909 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.677062 9 11 00 00 02 00 00 00 00 00
+ * E: 000212.679048 9 11 00 55 01 00 00 00 00 00
+ * E: 000212.682166 9 11 00 55 01 00 00 00 00 00
+ * E: 000212.682788 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.683899 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.685827 9 11 00 67 fe 00 00 00 00 00
+ * E: 000212.686941 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.727840 9 11 00 14 00 00 00 00 00 00
+ * E: 000212.772884 9 11 00 13 00 00 00 00 00 00
+ * E: 000212.810975 9 11 00 16 00 00 00 00 00 00
+ * E: 000212.811793 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.812683 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.813905 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.814909 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.816942 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.817851 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.818752 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.819910 9 11 00 56 fd 00 00 00 00 00
+ * E: 000212.820781 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.821811 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.822920 9 11 00 00 08 00 00 00 00 00
+ * E: 000212.823861 9 11 00 00 02 00 00 00 00 00
+ * E: 000212.828781 9 11 00 ba 00 00 00 00 00 00
+ * E: 000212.874097 9 11 00 12 00 00 00 00 00 00
+ * E: 000212.874872 9 11 00 00 fc 00 00 00 00 00
+ * E: 000212.876136 9 11 00 00 fc 00 00 00 00 00
+ * E: 000212.877036 9 11 00 00 f8 00 00 00 00 00
+ * E: 000212.877993 9 11 00 00 f8 00 00 00 00 00
+ * E: 000212.879748 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.880728 9 11 00 01 00 00 00 00 00 00
+ * E: 000212.881956 9 11 00 00 04 00 00 00 00 00
+ * E: 000212.885065 9 11 00 ff ff 00 00 00 00 00
+ * E: 000212.917060 9 11 00 1a 00 00 00 00 00 00
+ * E: 000212.936458 9 11 00 2d 00 00 00 00 00 00
+ * E: 000212.957860 9 11 00 25 00 00 00 00 00 00
+ * E: 000212.984019 9 11 00 20 00 00 00 00 00 00
+ * E: 000213.017915 9 11 00 19 00 00 00 00 00 00
+ * E: 000213.039973 9 11 00 27 00 00 00 00 00 00
+ * E: 000213.065933 9 11 00 21 00 00 00 00 00 00
+ * E: 000213.085807 9 11 00 28 00 00 00 00 00 00
+ * E: 000213.108888 9 11 00 25 00 00 00 00 00 00
+ * E: 000213.129726 9 11 00 29 00 00 00 00 00 00
+ * E: 000213.172043 9 11 00 14 00 00 00 00 00 00
+ * E: 000213.195873 9 11 00 23 00 00 00 00 00 00
+ * E: 000213.222884 9 11 00 20 00 00 00 00 00 00
+ * E: 000213.243220 9 11 00 2a 00 00 00 00 00 00
+ * E: 000213.266778 9 11 00 24 00 00 00 00 00 00
+ * E: 000213.285951 9 11 00 2b 00 00 00 00 00 00
+ * E: 000213.306045 9 11 00 2a 00 00 00 00 00 00
+ * E: 000213.306796 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.307755 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.308820 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.309971 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.310980 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.311853 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.312861 9 11 00 aa 02 00 00 00 00 00
+ * E: 000213.313884 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.315111 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.315992 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.316955 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.346065 9 11 00 1d 00 00 00 00 00 00
+ * E: 000213.346963 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.347874 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.348736 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.349795 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.350791 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.351791 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.352729 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.353811 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.354755 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.355795 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.356813 9 11 00 01 00 00 00 00 00 00
+ * E: 000213.357817 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.393838 9 11 00 17 00 00 00 00 00 00
+ * E: 000213.394719 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.395682 9 11 00 00 08 00 00 00 00 00
+ * E: 000213.396679 9 11 00 00 04 00 00 00 00 00
+ * E: 000213.397651 9 11 00 00 fc 00 00 00 00 00
+ * E: 000213.398661 9 11 00 ff ff 00 00 00 00 00
+ * E: 000213.400308 9 11 00 56 fd 00 00 00 00 00
+ * E: 000213.400909 9 11 00 00 f8 00 00 00 00 00
+ * E: 000213.401837 9 11 00 01 00 00 00 00 00 00
+ *
+ * Either wheel rotation, increasing rotation speed CCW
+ * (Note that the wheels on my particular tablet may be
+ * damaged, so the false rotation direction changes
+ * that can be observed might not happen on other units.)
+ * E: 000040.527820 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.816644 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.880423 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000040.882570 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.883381 9 11 00 ff ff 00 00 00 00 00
+ * E: 000040.885463 9 11 00 aa 02 00 00 00 00 00
+ * E: 000040.924106 9 11 00 ea ff 00 00 00 00 00
+ * E: 000041.006155 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.085799 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.168492 9 11 00 f6 ff 00 00 00 00 00
+ * E: 000041.233453 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000041.296641 9 11 00 f3 ff 00 00 00 00 00
+ * E: 000041.370302 9 11 00 f5 ff 00 00 00 00 00
+ * E: 000041.437410 9 11 00 f4 ff 00 00 00 00 00
+ * E: 000041.474514 9 11 00 e9 ff 00 00 00 00 00
+ * E: 000041.522171 9 11 00 ef ff 00 00 00 00 00
+ * E: 000041.568160 9 11 00 ee ff 00 00 00 00 00
+ * E: 000041.608146 9 11 00 ec ff 00 00 00 00 00
+ * E: 000041.627132 9 11 00 d3 ff 00 00 00 00 00
+ * E: 000041.656151 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.682264 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000041.714186 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000041.740339 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000041.772087 9 11 00 e5 ff 00 00 00 00 00
+ * E: 000041.801093 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.834051 9 11 00 e7 ff 00 00 00 00 00
+ * E: 000041.863094 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000041.901016 9 11 00 ea ff 00 00 00 00 00
+ * E: 000041.901956 9 11 00 00 04 00 00 00 00 00
+ * E: 000041.902837 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.903927 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.905066 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.907214 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.909011 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.909953 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.910917 9 11 00 00 08 00 00 00 00 00
+ * E: 000041.913280 9 11 00 00 fe 00 00 00 00 00
+ * E: 000041.914121 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.915346 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.962101 9 11 00 ee ff 00 00 00 00 00
+ * E: 000041.964062 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.964978 9 11 00 00 fc 00 00 00 00 00
+ * E: 000041.968058 9 11 00 24 01 00 00 00 00 00
+ * E: 000041.968880 9 11 00 56 fd 00 00 00 00 00
+ * E: 000041.970977 9 11 00 aa 02 00 00 00 00 00
+ * E: 000041.971932 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.972943 9 11 00 01 00 00 00 00 00 00
+ * E: 000041.975291 9 11 00 ff ff 00 00 00 00 00
+ * E: 000041.978274 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.035079 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.041283 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.042057 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.045169 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.051242 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.056099 9 11 00 63 ff 00 00 00 00 00
+ * E: 000042.106329 9 11 00 ef ff 00 00 00 00 00
+ * E: 000042.108601 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.116259 9 11 00 6b 00 00 00 00 00 00
+ * E: 000042.119140 9 11 00 55 01 00 00 00 00 00
+ * E: 000042.126101 9 11 00 88 ff 00 00 00 00 00
+ * E: 000042.158009 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000042.172108 9 11 00 be ff 00 00 00 00 00
+ * E: 000042.207417 9 11 00 e8 ff 00 00 00 00 00
+ * E: 000042.223155 9 11 00 cc ff 00 00 00 00 00
+ * E: 000042.255185 9 11 00 e6 ff 00 00 00 00 00
+ * E: 000042.276280 9 11 00 d7 ff 00 00 00 00 00
+ * E: 000042.302128 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.317423 9 11 00 c8 ff 00 00 00 00 00
+ * E: 000042.345226 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.357243 9 11 00 bc ff 00 00 00 00 00
+ * E: 000042.381308 9 11 00 dc ff 00 00 00 00 00
+ * E: 000042.383180 9 11 00 dc fe 00 00 00 00 00
+ * E: 000042.412288 9 11 00 e3 ff 00 00 00 00 00
+ * E: 000042.451216 9 11 00 eb ff 00 00 00 00 00
+ * E: 000042.478372 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.502116 9 11 00 dd ff 00 00 00 00 00
+ * E: 000042.520105 9 11 00 d3 ff 00 00 00 00 00
+ * E: 000042.540345 9 11 00 d6 ff 00 00 00 00 00
+ * E: 000042.541021 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.542009 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.543045 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.544279 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.545097 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.546074 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.547237 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.548029 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.549304 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.553123 9 11 00 00 ff 00 00 00 00 00
+ * E: 000042.581186 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.582238 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.583150 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.584273 9 11 00 00 f8 00 00 00 00 00
+ * E: 000042.585019 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.586059 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.589012 9 11 00 67 fe 00 00 00 00 00
+ * E: 000042.590066 9 11 00 00 fc 00 00 00 00 00
+ * E: 000042.592916 9 11 00 dc fe 00 00 00 00 00
+ * E: 000042.621124 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.622092 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.623069 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.624030 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.625006 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.626068 9 11 00 00 04 00 00 00 00 00
+ * E: 000042.626876 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.628392 9 11 00 00 08 00 00 00 00 00
+ * E: 000042.628918 9 11 00 01 00 00 00 00 00 00
+ * E: 000042.630009 9 11 00 ff ff 00 00 00 00 00
+ * E: 000042.631934 9 11 00 00 fe 00 00 00 00 00
+ * E: 000042.656285 9 11 00 dd ff 00 00 00 00 00
+ * E: 000042.659870 9 11 00 cc 00 00 00 00 00 00
+ * E: 000042.666128 9 11 00 9d 00 00 00 00 00 00
+ * E: 000042.672458 9 11 00 80 ff 00 00 00 00 00
+ * E: 000042.696106 9 11 00 dc ff 00 00 00 00 00
+ * E: 000042.705129 9 11 00 61 00 00 00 00 00 00
+ * E: 000042.731303 9 11 00 e0 ff 00 00 00 00 00
+ * E: 000042.741278 9 11 00 ab ff 00 00 00 00 00
+ * E: 000042.788181 9 11 00 ee ff 00 00 00 00 00
+ * E: 000042.810441 9 11 00 db ff 00 00 00 00 00
+ * E: 000042.838073 9 11 00 e1 ff 00 00 00 00 00
+ * E: 000042.852235 9 11 00 c4 ff 00 00 00 00 00
+ * E: 000042.882290 9 11 00 e4 ff 00 00 00 00 00
+ *
+ * Either wheel button, press, hold, then release
+ * E: 000202.084982 9 11 02 00 00 00 00 00 00 00
+ * E: 000202.090172 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.094139 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.099172 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.105055 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.109132 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.114185 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.119212 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.124264 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.130147 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.135138 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.140072 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.145146 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.150157 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.155339 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.160064 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.165026 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.170037 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.175154 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.180044 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.186280 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.191281 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.196106 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.201083 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.206166 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.211084 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.216175 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.221036 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.226271 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.231150 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.235924 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.242046 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.247164 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.252359 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.257295 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.262167 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.267081 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.272175 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.277085 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.282596 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.287078 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.292191 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.298196 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.303004 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.308113 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.313079 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.318243 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.323309 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.328190 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.333050 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.338162 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.343022 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.348113 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.354133 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.359132 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.364053 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.369034 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.374144 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.379027 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.384238 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.389249 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.394049 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.398949 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.404203 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.410098 9 11 03 00 00 00 00 00 00 00
+ * E: 000202.415237 9 11 00 00 00 00 00 00 00 00
+ *
+ *
+ * Top wheel button press and release while holding bottom wheel button
+ * (The reverse action (a bottom wheel button press while holding the top wheel button) is invisible.)
+ * E: 000071.126966 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.133117 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.137481 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.142036 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.147027 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.151988 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.157945 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.163657 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.168240 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.173109 9 11 02 00 00 00 00 00 00 00 < top wheel button press?
+ * E: 000071.178119 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.183046 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.187983 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.192996 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.198341 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.203122 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.208998 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.214037 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.218945 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.223835 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.228987 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.234082 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.239028 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.244307 9 11 00 00 00 00 00 00 00 00 < top wheel button release?
+ * E: 000071.245867 9 11 03 00 00 00 00 00 00 00 < continued hold of bottom button
+ * E: 000071.249959 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.255032 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.259972 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.265409 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.270156 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.275530 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.279975 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.285046 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.290906 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.296146 9 11 03 00 00 00 00 00 00 00
+ * E: 000071.301288 9 11 03 00 00 00 00 00 00 00
+ *
+ * Top wheel button hold while top wheel rotate CCW
+ * (I did not test the other combinations of this)
+ * E: 000022.253144 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.258157 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.262011 9 11 00 ff ff 00 00 00 00 00
+ * E: 000022.264015 9 11 03 00 00 00 00 00 00 00
+ * E: 000022.268976 9 11 03 00 00 00 00 00 00 00
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * NOTES ON SIMULTANEOUS BUTTON HOLDS
+ * (applies to vendor mode only)
+ * Value replacements for ease of reading:
+ * .7 = 0x40 (button 7, a wheel button)
+ * .1 = 0x01 (button 1, a pad button)
+ * rr = 0x00 (no buttons pressed)
+ *
+ * Press 7
+ * Press 1
+ * Release 7
+ * Release 1
+ * B: 000000.000152 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000000.781784 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000000.869845 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.095688 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.322635 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.543643 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.770652 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000001.885659 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 7
+ * B: 000001.993620 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.220671 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.446589 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.672559 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000002.765183 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1
+ *
+ * Press 7
+ * Press 1
+ * Release 1
+ * Release 7
+ * B: 000017.071517 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000018.270461 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.419486 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.646438 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000018.872493 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000019.094422 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000019.320488 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000020.360505 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1 is not reported until 7 is released, then both are rapidly reported
+ * B: 000020.361091 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00
+ *
+ * Press 1
+ * Press 7
+ * Release 7
+ * Release 1
+ * B: 000031.516315 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000031.922299 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.144165 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.370262 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.396242 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000032.597270 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000032.818187 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.045143 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.267535 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 7
+ * B: 000033.272602 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.494246 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.721266 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000033.947237 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000034.169294 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000034.183585 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1
+ *
+ * Press 1
+ * Press 7
+ * Release 1
+ * Release 7
+ * B: 000056.628429 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.046348 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.272044 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.494434 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.601224 42 08 e0 01 01 .7 00 00 00 00 00 00 00 00 00
+ * B: 000057.719262 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000057.946941 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000058.172346 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000058.393994 42 08 e0 01 01 .1 00 00 00 00 00 00 00 00 00
+ * B: 000059.434576 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00 release of 1 is not reported until 7 is released, then both are rapidly reported
+ * B: 000059.435857 42 08 e0 01 01 rr 00 00 00 00 00 00 00 00 00
+ */
+
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+char EXPECTED_FIRMWARE_ID[] = "HUION_M22c_";
+
+__u8 last_button_state;
+
+static const __u8 disabled_rdesc_tablet[] = {
+ FixedSizeVendorReport(28) /* Input report 4 */
+};
+
+static const __u8 disabled_rdesc_wheel[] = {
+ FixedSizeVendorReport(9) /* Input report 17 */
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionPhysical(
+ /*
+ * I have only examined the tablet's behavior while using
+ * the PW600L pen, which does not have an eraser.
+ * Because of this, I don't know where the Eraser and Invert
+ * bits will go, or if they work as one would expect.
+ *
+ * For the time being, there is no expectation that a pen
+ * with an eraser will work without modifications here.
+ */
+ ReportSize(1)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportCount(3)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ Input(Var|Abs)
+ PushPop(
+ ReportCount(1)
+ UsagePage_Button
+ Usage_i8(0x4a) /* (BTN_STYLUS3 + 1) & 0xff */
+ Input(Var|Abs)
+ )
+ ReportCount(3)
+ Input(Const)
+ ReportCount(1)
+ Usage_Dig_InRange
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-2)
+ LogicalMinimum_i16(0)
+ PhysicalMinimum_i16(0)
+ /*
+ * The tablet has a logical maximum of 58760 x 33040
+ * and a claimed resolution of 5080 LPI (200 L/mm)
+ * This works out to a physical maximum of
+ * 293.8 x 165.2mm, which matches Huion's advertised
+ * active area dimensions from
+ * https://www.huion.com/products/pen_display/Kamvas/kamvas-13-gen-3.html
+ */
+ LogicalMaximum_i16(58760)
+ PhysicalMaximum_i16(2938)
+ Usage_GD_X
+ Input(Var|Abs)
+ LogicalMaximum_i16(33040)
+ PhysicalMaximum_i16(1652)
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(16383)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ ReportCount(1)
+ Input(Const)
+ ReportSize(8)
+ ReportCount(2)
+ PushPop(
+ Unit(deg)
+ UnitExponent(0)
+ LogicalMinimum_i8(-60)
+ PhysicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ PhysicalMaximum_i8(60)
+ Usage_Dig_XTilt
+ Usage_Dig_YTilt
+ Input(Var|Abs)
+ )
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(CUSTOM_PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /*
+ * The first 3 bytes are somewhat vestigial and will
+ * always be set to zero. Their presence here is needed
+ * to ensure that this device will be detected as a
+ * tablet pad by software that otherwise wouldn't know
+ * any better.
+ */
+ /* (data[1] & 0x01) barrel switch */
+ ReportSize(1)
+ ReportCount(1)
+ Usage_Dig_BarrelSwitch
+ Input(Var|Abs)
+ ReportCount(7)
+ Input(Const)
+ /* data[2] X */
+ /* data[3] Y */
+ ReportSize(8)
+ ReportCount(2)
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ Input(Var|Abs)
+ /*
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6 (top wheel button)
+ * (data[4] & 0x40) button 7 (bottom wheel button)
+ */
+ ReportSize(1)
+ ReportCount(7)
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(7)
+ Input(Var|Abs)
+ ReportCount(1)
+ Input(Const)
+ /* data[5] top wheel (signed, positive clockwise) */
+ ReportSize(8)
+ ReportCount(1)
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ Input(Var|Rel)
+ /* data[6] bottom wheel (signed, positive clockwise) */
+ UsagePage_Consumer
+ Usage_Con_ACPan
+ Input(Var|Rel)
+ )
+ /*
+ * The kernel will drop reports that are bigger than the
+ * largest report specified in the HID descriptor.
+ * Therefore, our modified descriptor needs to have at least one
+ * HID report that is as long as, or longer than, the largest
+ * report in the original descriptor.
+ *
+ * This macro expands to a no-op report that is padded to the
+ * provided length.
+ */
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_huion_kamvas13_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hid_ctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+
+ if (have_fw_id) {
+ /*
+ * Tablet should be in vendor mode.
+ * Disable the unused devices
+ */
+ if (rdesc_size == TABLET_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_tablet,
+ sizeof(disabled_rdesc_tablet));
+ return sizeof(disabled_rdesc_tablet);
+ }
+
+ if (rdesc_size == WHEEL_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_wheel,
+ sizeof(disabled_rdesc_wheel));
+ return sizeof(disabled_rdesc_wheel);
+ }
+ }
+
+ /*
+ * Regardless of which mode the tablet is in, always fix the vendor
+ * descriptor in case the udev property just happened to not be set
+ */
+ if (rdesc_size == VENDOR_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(hid_fix_event_huion_kamvas13_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, VENDOR_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Handle vendor reports only */
+ if (hid_ctx->size != VENDOR_REPORT_LENGTH)
+ return 0;
+ if (data[0] != VENDOR_REPORT_ID)
+ return 0;
+
+ __u8 report_subtype = (data[1] >> 4) & 0x0f;
+
+ if (report_subtype == VENDOR_REPORT_SUBTYPE_PEN ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_PEN_OUT) {
+ /* Invert Y tilt */
+ data[11] = -data[11];
+
+ } else if (report_subtype == VENDOR_REPORT_SUBTYPE_BUTTONS ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_WHEELS) {
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 padding:7;
+ __u8 x;
+ __u8 y;
+ __u8 buttons;
+ __s8 top_wheel;
+ __s8 bottom_wheel;
+ } __attribute__((packed)) *pad_report;
+
+ __s8 top_wheel = 0;
+ __s8 bottom_wheel = 0;
+
+ switch (report_subtype) {
+ case VENDOR_REPORT_SUBTYPE_WHEELS:
+ /*
+ * The wheel direction byte is 1 for clockwise rotation
+ * and 2 for counter-clockwise.
+ * Change it to 1 and -1, respectively.
+ */
+ switch (data[3]) {
+ case 1:
+ top_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ case 2:
+ bottom_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ }
+ break;
+
+ case VENDOR_REPORT_SUBTYPE_BUTTONS:
+ /*
+ * If a button is already being held, ignore any new
+ * button event unless it's a release.
+ *
+ * The tablet only cleanly handles one button being held
+ * at a time, and trying to hold multiple buttons
+ * (particularly wheel+pad buttons) can result in sequences
+ * of reports that look like imaginary presses and releases.
+ *
+ * This is an imperfect way to filter out some of these
+ * reports.
+ */
+ if (last_button_state != 0x00 && data[4] != 0x00)
+ break;
+
+ last_button_state = data[4];
+ break;
+ }
+
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = CUSTOM_PAD_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->top_wheel = top_wheel;
+ pad_report->bottom_wheel = bottom_wheel;
+
+ return sizeof(struct pad_report);
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(huion_kamvas13_gen3) = {
+ .hid_device_event = (void *)hid_fix_event_huion_kamvas13_gen3,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas13_gen3,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case VENDOR_DESCRIPTOR_LENGTH:
+ case TABLET_DESCRIPTOR_LENGTH:
+ case WHEEL_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c
new file mode 100644
index 000000000000..ac66c6e65eb4
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas16Gen3.bpf.c
@@ -0,0 +1,724 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Nicholas LaPointe
+ * Copyright (c) 2025 Higgins Dragon
+ */
+
+#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_KAMVAS16_GEN3 0x2009
+
+#define VENDOR_DESCRIPTOR_LENGTH 36
+#define TABLET_DESCRIPTOR_LENGTH 328
+#define WHEEL_DESCRIPTOR_LENGTH 200
+
+#define VENDOR_REPORT_ID 8
+#define VENDOR_REPORT_LENGTH 14
+
+#define VENDOR_REPORT_SUBTYPE_PEN 0x08
+#define VENDOR_REPORT_SUBTYPE_PEN_OUT 0x00
+#define VENDOR_REPORT_SUBTYPE_BUTTONS 0x0e
+#define VENDOR_REPORT_SUBTYPE_WHEELS 0x0f
+
+/* For the reports that we create ourselves */
+#define CUSTOM_PAD_REPORT_ID 9
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_HUION, PID_KAMVAS16_GEN3),
+);
+
+/*
+ * This tablet can send reports using one of two different data formats,
+ * depending on what "mode" the tablet is in.
+ *
+ * By default, the tablet will send reports that can be decoded using its
+ * included HID descriptors (descriptors 1 and 2, shown below).
+ * This mode will be called "firmware mode" throughout this file.
+ *
+ * The HID descriptor that describes pen events in firmware mode (descriptor 1)
+ * has multiple bugs:
+ * * "Secondary Tip Switch" instead of "Secondary Barrel Switch"
+ * * "Invert" instead of (or potentially shared with) third barrel button
+ * * Specified tablet area of 2048 in³ instead of 293.8 x 165.2mm
+ * * Specified tilt range of -90 to +90 instead of -60 to +60
+ *
+ * While these can be easily patched up by editing the descriptor, a larger
+ * problem with the firmware mode exists: it is impossible to tell which of the
+ * two wheels are being rotated (or having their central button pressed).
+ *
+ *
+ * By using a tool such as huion-switcher (https://github.com/whot/huion-switcher),
+ * the tablet can be made to send reports using a proprietary format that is not
+ * adequately described by its relevant descriptor (descriptor 0, shown below).
+ * This mode will be called "vendor mode" throughout this file.
+ *
+ * The reports sent while in vendor mode allow for proper decoding of the wheels.
+ *
+ * For simplicity and maximum functionality, this BPF focuses strictly on
+ * enabling one to make use of the vendor mode.
+ */
+
+/*
+ * DESCRIPTORS
+ * DESCRIPTOR 0
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 3
+ * # 0xa1, 0x01, // Collection (Application) 5
+ * # 0x85, 0x08, // Report ID (8) 7
+ * # 0x75, 0x68, // Report Size (104) 9
+ * # 0x95, 0x01, // Report Count (1) 11
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 13
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 15
+ * # 0xc0, // End Collection 17
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 18
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 21
+ * # 0xa1, 0x01, // Collection (Application) 23
+ * # 0x85, 0x16, // Report ID (22) 25
+ * # 0x75, 0x08, // Report Size (8) 27
+ * # 0x95, 0x07, // Report Count (7) 29
+ * # 0x09, 0x01, // Usage (Vendor Usage 1) 31
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 33
+ * # 0xc0, // End Collection 35
+ * #
+ * R: 36 06 00 ff 09 01 a1 01 85 08 75 68 95 01 09 01 81 02 c0 06 00 ff 09 01 a1 01 85 16 75 08 95 07 09 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ *
+ * DESCRIPTOR 1
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x0a, // Report ID (10) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x01, // Collection (Application) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x43, // Usage (Secondary Tip Switch) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x45, // Usage (Eraser) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x06, // Report Count (6) 28
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x09, 0x32, // Usage (In Range) 32
+ * # 0x75, 0x01, // Report Size (1) 34
+ * # 0x95, 0x01, // Report Count (1) 36
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 42
+ * # 0x09, 0x30, // Usage (X) 44
+ * # 0x09, 0x31, // Usage (Y) 46
+ * # 0x55, 0x0d, // Unit Exponent (-3) 48
+ * # 0x65, 0x33, // Unit (EnglishLinear: in³) 50
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
+ * # 0x35, 0x00, // Physical Minimum (0) 55
+ * # 0x46, 0x00, 0x08, // Physical Maximum (2048) 57
+ * # 0x75, 0x10, // Report Size (16) 60
+ * # 0x95, 0x02, // Report Count (2) 62
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 64
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 66
+ * # 0x09, 0x30, // Usage (Tip Pressure) 68
+ * # 0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
+ * # 0x75, 0x10, // Report Size (16) 73
+ * # 0x95, 0x01, // Report Count (1) 75
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 77
+ * # 0x09, 0x3d, // Usage (X Tilt) 79
+ * # 0x09, 0x3e, // Usage (Y Tilt) 81
+ * # 0x15, 0xa6, // Logical Minimum (-90) 83
+ * # 0x25, 0x5a, // Logical Maximum (90) 85
+ * # 0x75, 0x08, // Report Size (8) 87
+ * # 0x95, 0x02, // Report Count (2) 89
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 91
+ * # 0xc0, // End Collection 93
+ * # 0xc0, // End Collection 94
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 95
+ * # 0x09, 0x04, // Usage (Touch Screen) 97
+ * # 0xa1, 0x01, // Collection (Application) 99
+ * # 0x85, 0x04, // Report ID (4) 101
+ * # 0x09, 0x22, // Usage (Finger) 103
+ * # 0xa1, 0x02, // Collection (Logical) 105
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 107
+ * # 0x95, 0x01, // Report Count (1) 109
+ * # 0x75, 0x06, // Report Size (6) 111
+ * # 0x09, 0x51, // Usage (Contact Id) 113
+ * # 0x15, 0x00, // Logical Minimum (0) 115
+ * # 0x25, 0x3f, // Logical Maximum (63) 117
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 119
+ * # 0x09, 0x42, // Usage (Tip Switch) 121
+ * # 0x25, 0x01, // Logical Maximum (1) 123
+ * # 0x75, 0x01, // Report Size (1) 125
+ * # 0x95, 0x01, // Report Count (1) 127
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 129
+ * # 0x75, 0x01, // Report Size (1) 131
+ * # 0x95, 0x01, // Report Count (1) 133
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 135
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 137
+ * # 0x75, 0x10, // Report Size (16) 139
+ * # 0x55, 0x0e, // Unit Exponent (-2) 141
+ * # 0x65, 0x11, // Unit (SILinear: cm) 143
+ * # 0x09, 0x30, // Usage (X) 145
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
+ * # 0x35, 0x00, // Physical Minimum (0) 150
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 155
+ * # 0x09, 0x31, // Usage (Y) 157
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 165
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 167
+ * # 0x09, 0x30, // Usage (Tip Pressure) 169
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
+ * # 0x75, 0x10, // Report Size (16) 174
+ * # 0x95, 0x01, // Report Count (1) 176
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 178
+ * # 0xc0, // End Collection 180
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 181
+ * # 0x09, 0x22, // Usage (Finger) 183
+ * # 0xa1, 0x02, // Collection (Logical) 185
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 187
+ * # 0x95, 0x01, // Report Count (1) 189
+ * # 0x75, 0x06, // Report Size (6) 191
+ * # 0x09, 0x51, // Usage (Contact Id) 193
+ * # 0x15, 0x00, // Logical Minimum (0) 195
+ * # 0x25, 0x3f, // Logical Maximum (63) 197
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 199
+ * # 0x09, 0x42, // Usage (Tip Switch) 201
+ * # 0x25, 0x01, // Logical Maximum (1) 203
+ * # 0x75, 0x01, // Report Size (1) 205
+ * # 0x95, 0x01, // Report Count (1) 207
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 209
+ * # 0x75, 0x01, // Report Size (1) 211
+ * # 0x95, 0x01, // Report Count (1) 213
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 215
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 217
+ * # 0x75, 0x10, // Report Size (16) 219
+ * # 0x55, 0x0e, // Unit Exponent (-2) 221
+ * # 0x65, 0x11, // Unit (SILinear: cm) 223
+ * # 0x09, 0x30, // Usage (X) 225
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
+ * # 0x35, 0x00, // Physical Minimum (0) 230
+ * # 0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 235
+ * # 0x09, 0x31, // Usage (Y) 237
+ * # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
+ * # 0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
+ * # 0x81, 0x42, // Input (Data,Var,Abs,Null) 245
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 247
+ * # 0x09, 0x30, // Usage (Tip Pressure) 249
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
+ * # 0x75, 0x10, // Report Size (16) 254
+ * # 0x95, 0x01, // Report Count (1) 256
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 258
+ * # 0xc0, // End Collection 260
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 261
+ * # 0x09, 0x56, // Usage (Scan Time) 263
+ * # 0x55, 0x00, // Unit Exponent (0) 265
+ * # 0x65, 0x00, // Unit (None) 267
+ * # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
+ * # 0x95, 0x01, // Report Count (1) 274
+ * # 0x75, 0x20, // Report Size (32) 276
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 278
+ * # 0x09, 0x54, // Usage (Contact Count) 280
+ * # 0x25, 0x7f, // Logical Maximum (127) 282
+ * # 0x95, 0x01, // Report Count (1) 284
+ * # 0x75, 0x08, // Report Size (8) 286
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 288
+ * # 0x75, 0x08, // Report Size (8) 290
+ * # 0x95, 0x08, // Report Count (8) 292
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 294
+ * # 0x85, 0x05, // Report ID (5) 296
+ * # 0x09, 0x55, // Usage (Contact Max) 298
+ * # 0x25, 0x0a, // Logical Maximum (10) 300
+ * # 0x75, 0x08, // Report Size (8) 302
+ * # 0x95, 0x01, // Report Count (1) 304
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 306
+ * # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
+ * # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
+ * # 0x85, 0x06, // Report ID (6) 313
+ * # 0x15, 0x00, // Logical Minimum (0) 315
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 317
+ * # 0x75, 0x08, // Report Size (8) 320
+ * # 0x96, 0x00, 0x01, // Report Count (256) 322
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 325
+ * # 0xc0, // End Collection 327
+ * #
+ * R: 328 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ * DESCRIPTOR 2
+ * # 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 (Vendor Usage 0x01) 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
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 108
+ * # 0x09, 0x06, // Usage (Keyboard) 110
+ * # 0xa1, 0x01, // Collection (Application) 112
+ * # 0x85, 0x03, // Report ID (3) 114
+ * # 0x05, 0x07, // Usage Page (Keyboard) 116
+ * # 0x19, 0xe0, // Usage Minimum (224) 118
+ * # 0x29, 0xe7, // Usage Maximum (231) 120
+ * # 0x15, 0x00, // Logical Minimum (0) 122
+ * # 0x25, 0x01, // Logical Maximum (1) 124
+ * # 0x75, 0x01, // Report Size (1) 126
+ * # 0x95, 0x08, // Report Count (8) 128
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 130
+ * # 0x05, 0x07, // Usage Page (Keyboard) 132
+ * # 0x19, 0x00, // Usage Minimum (0) 134
+ * # 0x29, 0xff, // Usage Maximum (255) 136
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 138
+ * # 0x75, 0x08, // Report Size (8) 141
+ * # 0x95, 0x06, // Report Count (6) 143
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 145
+ * # 0xc0, // End Collection 147
+ * # 0x05, 0x0c, // Usage Page (Consumer Devices) 148
+ * # 0x09, 0x01, // Usage (Consumer Control) 150
+ * # 0xa1, 0x01, // Collection (Application) 152
+ * # 0x85, 0x04, // Report ID (4) 154
+ * # 0x19, 0x01, // Usage Minimum (1) 156
+ * # 0x2a, 0x9c, 0x02, // Usage Maximum (668) 158
+ * # 0x15, 0x01, // Logical Minimum (1) 161
+ * # 0x26, 0x9c, 0x02, // Logical Maximum (668) 163
+ * # 0x95, 0x01, // Report Count (1) 166
+ * # 0x75, 0x10, // Report Size (16) 168
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 170
+ * # 0xc0, // End Collection 172
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 173
+ * # 0x09, 0x80, // Usage (System Control) 175
+ * # 0xa1, 0x01, // Collection (Application) 177
+ * # 0x85, 0x05, // Report ID (5) 179
+ * # 0x19, 0x81, // Usage Minimum (129) 181
+ * # 0x29, 0x83, // Usage Maximum (131) 183
+ * # 0x15, 0x00, // Logical Minimum (0) 185
+ * # 0x25, 0x01, // Logical Maximum (1) 187
+ * # 0x75, 0x01, // Report Size (1) 189
+ * # 0x95, 0x03, // Report Count (3) 191
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 193
+ * # 0x95, 0x05, // Report Count (5) 195
+ * # 0x81, 0x01, // Input (Cnst,Arr,Abs) 197
+ * # 0xc0, // End Collection 199
+ * #
+ * R: 200 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 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 19 01 2a 9c 02 15 01 26 9c 02 95 01 75 10 81 00 c0 05 01 09 80 a1 01 85 05 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 c0
+ * N: HUION Huion Tablet_GS1563
+ * I: 3 256c 2009
+ *
+ *
+ *
+ * VENDOR MODE
+ * HUION_FIRMWARE_ID="HUION_M22d_241101"
+ * HUION_MAGIC_BYTES="1403201101ac9900ff3fd81305080080083c4010"
+ *
+ * MAGIC BYTES
+ * [LogicalMaximum, X ] [LogicalMaximum, Y ] [LogicalMaximum, Pressure] [ LPI]
+ * 14 03 [ 20 11 01] [ ac 99 00] [ ff 3f] [d8 13] 05 08 00 80 08 3c 40 10
+ *
+ * See Huion__Kamvas13Gen3.bpf.c for more details on detailed button/dial reports and caveats. It's very
+ * similar to the Kamvas 16 Gen 3.
+ */
+
+
+/* Filled in by udev-hid-bpf */
+char UDEV_PROP_HUION_FIRMWARE_ID[64];
+
+char EXPECTED_FIRMWARE_ID[] = "HUION_M22d_";
+
+__u8 last_button_state;
+
+static const __u8 disabled_rdesc_tablet[] = {
+ FixedSizeVendorReport(28) /* Input report 4 */
+};
+
+static const __u8 disabled_rdesc_wheel[] = {
+ FixedSizeVendorReport(9) /* Input report 17 */
+};
+
+static const __u8 fixed_rdesc_vendor[] = {
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(VENDOR_REPORT_ID)
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionPhysical(
+ /*
+ * I have only examined the tablet's behavior while using
+ * the PW600L pen, which does not have an eraser.
+ * Because of this, I don't know where the Eraser and Invert
+ * bits will go, or if they work as one would expect.
+ *
+ * For the time being, there is no expectation that a pen
+ * with an eraser will work without modifications here.
+ */
+ ReportSize(1)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportCount(3)
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_SecondaryBarrelSwitch
+ Input(Var|Abs)
+ PushPop(
+ ReportCount(1)
+ UsagePage_Button
+ Usage_i8(0x4a) /* (BTN_STYLUS3 + 1) & 0xff */
+ Input(Var|Abs)
+ )
+ ReportCount(3)
+ Input(Const)
+ ReportCount(1)
+ Usage_Dig_InRange
+ Input(Var|Abs)
+ ReportSize(16)
+ ReportCount(1)
+ PushPop(
+ UsagePage_GenericDesktop
+ Unit(cm)
+ UnitExponent(-2)
+ LogicalMinimum_i16(0)
+ PhysicalMinimum_i16(0)
+ /*
+ * The tablet has a logical maximum of 69920 x 39340
+ * and a claimed resolution of 5080 LPI (200 L/mm)
+ * This works out to a physical maximum of
+ * 349.6 x 196.7mm, which matches Huion's advertised
+ * (rounded) active area dimensions from
+ * https://www.huion.com/products/pen_display/Kamvas/kamvas-16-gen-3.html
+ *
+ * The Kamvas uses data[8] for the 3rd byte of the X-axis, and adding
+ * that after data[2] and data[3] makes a contiguous little-endian
+ * 24-bit value. (See BPF_PROG below)
+ */
+ ReportSize(24)
+ LogicalMaximum_i32(69920)
+ PhysicalMaximum_i16(3496)
+ Usage_GD_X
+ Input(Var|Abs)
+ ReportSize(16)
+ LogicalMaximum_i16(39340)
+ PhysicalMaximum_i16(1967)
+ Usage_GD_Y
+ Input(Var|Abs)
+ )
+ ReportSize(16)
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(16383)
+ Usage_Dig_TipPressure
+ Input(Var|Abs)
+ ReportSize(8)
+ ReportCount(1)
+ Input(Const)
+ ReportCount(2)
+ PushPop(
+ Unit(deg)
+ UnitExponent(0)
+ LogicalMinimum_i8(-60)
+ PhysicalMinimum_i8(-60)
+ LogicalMaximum_i8(60)
+ PhysicalMaximum_i8(60)
+ Usage_Dig_XTilt
+ Usage_Dig_YTilt
+ Input(Var|Abs)
+ )
+ )
+ )
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(CUSTOM_PAD_REPORT_ID)
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /*
+ * The first 3 bytes are somewhat vestigial and will
+ * always be set to zero. Their presence here is needed
+ * to ensure that this device will be detected as a
+ * tablet pad by software that otherwise wouldn't know
+ * any better.
+ */
+ /* (data[1] & 0x01) barrel switch */
+ ReportSize(1)
+ ReportCount(1)
+ Usage_Dig_BarrelSwitch
+ Input(Var|Abs)
+ ReportCount(7)
+ Input(Const)
+ /* data[2] X */
+ /* data[3] Y */
+ ReportSize(8)
+ ReportCount(2)
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ Input(Var|Abs)
+ /*
+ * (data[4] & 0x01) button 1
+ * (data[4] & 0x02) button 2
+ * (data[4] & 0x04) button 3
+ * (data[4] & 0x08) button 4
+ * (data[4] & 0x10) button 5
+ * (data[4] & 0x20) button 6
+ * (data[4] & 0x40) button 7 (top wheel button)
+ * (data[4] & 0x80) button 8 (bottom wheel button)
+ */
+ ReportSize(1)
+ ReportCount(8)
+ UsagePage_Button
+ UsageMinimum_i8(1)
+ UsageMaximum_i8(8)
+ Input(Var|Abs)
+ /* data[5] top wheel (signed, positive clockwise) */
+ ReportSize(8)
+ ReportCount(1)
+ UsagePage_GenericDesktop
+ Usage_GD_Wheel
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ Input(Var|Rel)
+ /* data[6] bottom wheel (signed, positive clockwise) */
+ UsagePage_Consumer
+ Usage_Con_ACPan
+ Input(Var|Rel)
+ )
+ /*
+ * The kernel will drop reports that are bigger than the
+ * largest report specified in the HID descriptor.
+ * Therefore, our modified descriptor needs to have at least one
+ * HID report that is as long as, or longer than, the largest
+ * report in the original descriptor.
+ *
+ * This macro expands to a no-op report that is padded to the
+ * provided length.
+ */
+ FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+ __s32 rdesc_size = hid_ctx->size;
+ __u8 have_fw_id;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
+ EXPECTED_FIRMWARE_ID,
+ sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
+
+ if (have_fw_id) {
+ /*
+ * Tablet should be in vendor mode.
+ * Disable the unused devices
+ */
+ if (rdesc_size == TABLET_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_tablet,
+ sizeof(disabled_rdesc_tablet));
+ return sizeof(disabled_rdesc_tablet);
+ }
+
+ if (rdesc_size == WHEEL_DESCRIPTOR_LENGTH) {
+ __builtin_memcpy(data, disabled_rdesc_wheel,
+ sizeof(disabled_rdesc_wheel));
+ return sizeof(disabled_rdesc_wheel);
+ }
+ }
+
+ /*
+ * Regardless of which mode the tablet is in, always fix the vendor
+ * descriptor in case the udev property just happened to not be set
+ */
+ if (rdesc_size == VENDOR_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(hid_fix_event_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, VENDOR_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Handle vendor reports only */
+ if (hid_ctx->size != VENDOR_REPORT_LENGTH)
+ return 0;
+ if (data[0] != VENDOR_REPORT_ID)
+ return 0;
+
+ __u8 report_subtype = (data[1] >> 4) & 0x0f;
+
+ if (report_subtype == VENDOR_REPORT_SUBTYPE_PEN ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_PEN_OUT) {
+ /* Invert Y tilt */
+ data[11] = -data[11];
+
+ /*
+ * Rearrange the bytes of the report so that
+ * [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+ * will be arranged as
+ * [0, 1, 2, 3, 8, 4, 5, 6, 7, 9, 10, 11, 12, 13]
+ */
+ __u8 x_24 = data[8];
+
+ data[8] = data[7];
+ data[7] = data[6];
+ data[6] = data[5];
+ data[5] = data[4];
+
+ data[4] = x_24;
+
+ } else if (report_subtype == VENDOR_REPORT_SUBTYPE_BUTTONS ||
+ report_subtype == VENDOR_REPORT_SUBTYPE_WHEELS) {
+ struct pad_report {
+ __u8 report_id;
+ __u8 btn_stylus:1;
+ __u8 padding:7;
+ __u8 x;
+ __u8 y;
+ __u8 buttons;
+ __s8 top_wheel;
+ __s8 bottom_wheel;
+ } __attribute__((packed)) *pad_report;
+
+ __s8 top_wheel = 0;
+ __s8 bottom_wheel = 0;
+
+ switch (report_subtype) {
+ case VENDOR_REPORT_SUBTYPE_WHEELS:
+ /*
+ * The wheel direction byte is 1 for clockwise rotation
+ * and 2 for counter-clockwise.
+ * Change it to 1 and -1, respectively.
+ */
+ switch (data[3]) {
+ case 1:
+ top_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ case 2:
+ bottom_wheel = (data[5] == 1) ? 1 : -1;
+ break;
+ }
+ break;
+
+ case VENDOR_REPORT_SUBTYPE_BUTTONS:
+ /*
+ * If a button is already being held, ignore any new
+ * button event unless it's a release.
+ *
+ * The tablet only cleanly handles one button being held
+ * at a time, and trying to hold multiple buttons
+ * (particularly wheel+pad buttons) can result in sequences
+ * of reports that look like imaginary presses and releases.
+ *
+ * This is an imperfect way to filter out some of these
+ * reports.
+ */
+ if (last_button_state != 0x00 && data[4] != 0x00)
+ break;
+
+ last_button_state = data[4];
+ break;
+ }
+
+ pad_report = (struct pad_report *)data;
+
+ pad_report->report_id = CUSTOM_PAD_REPORT_ID;
+ pad_report->btn_stylus = 0;
+ pad_report->x = 0;
+ pad_report->y = 0;
+ pad_report->buttons = last_button_state;
+ pad_report->top_wheel = top_wheel;
+ pad_report->bottom_wheel = bottom_wheel;
+
+ return sizeof(struct pad_report);
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(huion_kamvas16_gen3) = {
+ .hid_device_event = (void *)hid_fix_event_huion_kamvas16_gen3,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas16_gen3,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case VENDOR_DESCRIPTOR_LENGTH:
+ case TABLET_DESCRIPTOR_LENGTH:
+ case WHEEL_DESCRIPTOR_LENGTH:
+ ctx->retval = 0;
+ break;
+ default:
+ ctx->retval = -EINVAL;
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
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/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
new file mode 100644
index 000000000000..82f1950445dd
--- /dev/null
+++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_IOGEAR 0x258A /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_MOMENTUM 0x0027
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_IOGEAR, PID_MOMENTUM)
+);
+
+/*
+ * The IOGear Kaliber Gaming MMOmentum Pro mouse has multiple buttons (12)
+ * but only 5 are accessible out of the box because the report descriptor
+ * marks the other buttons as constants.
+ * We just fix the report descriptor to enable those missing 7 buttons.
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ const u8 offsets[] = {84, 112, 140};
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* if not Keyboard */
+ if (data[3] != 0x06)
+ return 0;
+
+ for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
+ u8 offset = offsets[idx];
+
+ /* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
+ if (data[offset] == 0x81 && data[offset + 1] == 0x03)
+ data[offset + 1] = 0x02;
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(iogear_kaliber_momentum) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* only bind to the keyboard interface */
+ ctx->retval = ctx->rdesc_size != 213;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c b/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c
new file mode 100644
index 000000000000..b17719d6d9c7
--- /dev/null
+++ b/drivers/hid/bpf/progs/Logitech__SpaceNavigator.bpf.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Curran Muhlberger
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_LOGITECH 0x046D
+#define PID_SPACENAVIGATOR 0xC626
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_LOGITECH, PID_SPACENAVIGATOR)
+);
+
+/*
+ * The 3Dconnexion SpaceNavigator 3D Mouse is a multi-axis controller with 6
+ * axes (grouped as X,Y,Z and Rx,Ry,Rz). Axis data is absolute, but the report
+ * descriptor erroneously declares it to be relative. We fix the report
+ * descriptor to mark both axis collections as absolute.
+ *
+ * The kernel attempted to fix this in commit 24985cf68612 (HID: support
+ * Logitech/3DConnexion SpaceTraveler and SpaceNavigator), but the descriptor
+ * data offsets are incorrect for at least some SpaceNavigator units.
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Offset of Input item in X,Y,Z and Rx,Ry,Rz collections for all known
+ * firmware variants.
+ * - 2009 model: X,Y,Z @ 32-33, Rx,Ry,Rz @ 49-50 (fixup originally
+ * applied in kernel)
+ * - 2016 model (size==228): X,Y,Z @ 36-37, Rx,Ry,Rz @ 53-54
+ *
+ * The descriptor size of the 2009 model is not known, and there is evidence
+ * for at least two other variants (with sizes 202 & 217) besides the 2016
+ * model, so we try all known offsets regardless of descriptor size.
+ */
+ const u8 offsets[] = {32, 36, 49, 53};
+
+ for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
+ u8 offset = offsets[idx];
+
+ /* if Input (Data,Var,Rel) , make it Input (Data,Var,Abs) */
+ if (data[offset] == 0x81 && data[offset + 1] == 0x06)
+ data[offset + 1] = 0x02;
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(logitech_spacenavigator) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* Ensure report descriptor size matches one of the known variants. */
+ if (ctx->rdesc_size != 202 &&
+ ctx->rdesc_size != 217 &&
+ ctx->rdesc_size != 228) {
+ ctx->retval = -EINVAL;
+ return 0;
+ }
+
+ /* Check whether the kernel has already applied the fix. */
+ if ((ctx->rdesc[32] == 0x81 && ctx->rdesc[33] == 0x02 &&
+ ctx->rdesc[49] == 0x81 && ctx->rdesc[50] == 0x02) ||
+ (ctx->rdesc[36] == 0x81 && ctx->rdesc[37] == 0x02 &&
+ ctx->rdesc[53] == 0x81 && ctx->rdesc[54] == 0x02))
+ ctx->retval = -EINVAL;
+ else
+ ctx->retval = 0;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/entrypoints/Makefile b/drivers/hid/bpf/progs/Makefile
index a12edcfa4fe3..ec1fc642fd63 100644
--- a/drivers/hid/bpf/entrypoints/Makefile
+++ b/drivers/hid/bpf/progs/Makefile
@@ -45,24 +45,22 @@ endif
.PHONY: all clean
-all: entrypoints.lskel.h
+SOURCES = $(wildcard *.bpf.c)
+TARGETS = $(SOURCES:.bpf.c=.bpf.o)
+
+all: $(TARGETS)
clean:
$(call msg,CLEAN)
- $(Q)rm -rf $(OUTPUT) entrypoints
-
-entrypoints.lskel.h: $(OUTPUT)/entrypoints.bpf.o | $(BPFTOOL)
- $(call msg,GEN-SKEL,$@)
- $(Q)$(BPFTOOL) gen skeleton -L $< > $@
-
+ $(Q)rm -rf $(OUTPUT) $(TARGETS)
-$(OUTPUT)/entrypoints.bpf.o: entrypoints.bpf.c $(OUTPUT)/vmlinux.h $(BPFOBJ) | $(OUTPUT)
+%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
- $(Q)$(CLANG) -g -O2 -target bpf $(INCLUDES) \
+ $(Q)$(CLANG) -g -O2 --target=bpf -Wall -Werror $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
-$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
+vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
ifeq ($(VMLINUX_H),)
$(call msg,GEN,,$@)
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
diff --git a/drivers/hid/bpf/progs/Microsoft__Xbox-Elite-2.bpf.c b/drivers/hid/bpf/progs/Microsoft__Xbox-Elite-2.bpf.c
new file mode 100644
index 000000000000..382bba735a2c
--- /dev/null
+++ b/drivers/hid/bpf/progs/Microsoft__Xbox-Elite-2.bpf.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_MICROSOFT 0x045e
+#define PID_XBOX_ELITE_2 0x0b22
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
+);
+
+/*
+ * When using the Xbox Wireless Controller Elite 2 over Bluetooth,
+ * the device exports the paddles on the back of the device as a single
+ * bitfield value of usage "Assign Selection".
+ *
+ * The kernel doesn't process the paddles usage properly and reports KEY_UNKNOWN.
+ *
+ * SDL doesn't know how to interpret KEY_UNKNOWN and thus ignores the paddles.
+ *
+ * Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
+ * can tweak the report descriptor to make the kernel interpret it properly:
+ * - We need an application collection of gamepad (so we have to close the current
+ * Consumer Control one)
+ * - We need to change the usage to be buttons from 0x15 to 0x18
+ */
+
+#define OFFSET_ASSIGN_SELECTION 211
+#define ORIGINAL_RDESC_SIZE 464
+
+const __u8 rdesc_assign_selection[] = {
+ 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
+ 0x15, 0x00, // Logical Minimum (0) 214
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 216
+ 0x95, 0x01, // Report Count (1) 219
+ 0x75, 0x04, // Report Size (4) 221
+ 0x81, 0x02, // Input (Data,Var,Abs) 223
+ 0x15, 0x00, // Logical Minimum (0) 225
+ 0x25, 0x00, // Logical Maximum (0) 227
+ 0x95, 0x01, // Report Count (1) 229
+ 0x75, 0x04, // Report Size (4) 231
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 233
+ 0x0a, 0x81, 0x00, // Usage (Assign Selection) 235
+ 0x15, 0x00, // Logical Minimum (0) 238
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 240
+ 0x95, 0x01, // Report Count (1) 243
+ 0x75, 0x04, // Report Size (4) 245
+ 0x81, 0x02, // Input (Data,Var,Abs) 247
+};
+
+/*
+ * we replace the above report descriptor extract
+ * with the one below.
+ * To make things equal in size, we take out a larger
+ * portion than just the "Assign Selection" range, because
+ * we need to insert a new application collection to force
+ * the kernel to use BTN_TRIGGER_HAPPY[4-7].
+ */
+const __u8 fixed_rdesc_assign_selection[] = {
+ 0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
+ 0x15, 0x00, // Logical Minimum (0) 214
+ 0x26, 0xff, 0x00, // Logical Maximum (255) 216
+ 0x95, 0x01, // Report Count (1) 219
+ 0x75, 0x04, // Report Size (4) 221
+ 0x81, 0x02, // Input (Data,Var,Abs) 223
+ /* 0x15, 0x00, */ // Logical Minimum (0) ignored
+ 0x25, 0x01, // Logical Maximum (1) 225
+ 0x95, 0x04, // Report Count (4) 227
+ 0x75, 0x01, // Report Size (1) 229
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 231
+ 0xc0, // End Collection 233
+ 0x05, 0x01, // Usage Page (Generic Desktop) 234
+ 0x0a, 0x05, 0x00, // Usage (Game Pad) 236
+ 0xa1, 0x01, // Collection (Application) 239
+ 0x05, 0x09, // Usage Page (Button) 241
+ 0x19, 0x15, // Usage Minimum (21) 243
+ 0x29, 0x18, // Usage Maximum (24) 245
+ /* 0x15, 0x00, */ // Logical Minimum (0) ignored
+ /* 0x25, 0x01, */ // Logical Maximum (1) ignored
+ /* 0x95, 0x01, */ // Report Size (1) ignored
+ /* 0x75, 0x04, */ // Report Count (4) ignored
+ 0x81, 0x02, // Input (Data,Var,Abs) 247
+};
+
+_Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
+ "Rdesc and fixed rdesc of different size");
+_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
+ "Rdesc at given offset is too big");
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* Check that the device is compatible */
+ if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
+ rdesc_assign_selection,
+ sizeof(rdesc_assign_selection)))
+ return 0;
+
+ __builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
+ fixed_rdesc_assign_selection,
+ sizeof(fixed_rdesc_assign_selection));
+
+ return 0;
+}
+
+HID_BPF_OPS(xbox_elite_2) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* only bind to the keyboard interface */
+ ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
+ rdesc_assign_selection,
+ sizeof(rdesc_assign_selection)))
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Mistel__MD770.bpf.c b/drivers/hid/bpf/progs/Mistel__MD770.bpf.c
new file mode 100644
index 000000000000..fb8b5a6968b1
--- /dev/null
+++ b/drivers/hid/bpf/progs/Mistel__MD770.bpf.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Tatsuyuki Ishi
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_HOLTEK 0x04D9
+#define PID_MD770 0x0339
+#define RDESC_SIZE 203
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HOLTEK, PID_MD770)
+);
+
+/*
+ * The Mistel MD770 keyboard reports the first 6 simultaneous key presses
+ * through the first interface, and anything beyond that through a second
+ * interface. Unfortunately, the second interface's report descriptor has an
+ * error, causing events to be malformed and ignored. This HID-BPF driver
+ * fixes the descriptor to allow NKRO to work again.
+ *
+ * For reference, this is the original report descriptor:
+ *
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x80, // Usage (System Control) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x01, // Report ID (1) 6
+ * 0x19, 0x81, // Usage Minimum (129) 8
+ * 0x29, 0x83, // Usage Maximum (131) 10
+ * 0x15, 0x00, // Logical Minimum (0) 12
+ * 0x25, 0x01, // Logical Maximum (1) 14
+ * 0x95, 0x03, // Report Count (3) 16
+ * 0x75, 0x01, // Report Size (1) 18
+ * 0x81, 0x02, // Input (Data,Var,Abs) 20
+ * 0x95, 0x01, // Report Count (1) 22
+ * 0x75, 0x05, // Report Size (5) 24
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 26
+ * 0xc0, // End Collection 28
+ * 0x05, 0x0c, // Usage Page (Consumer Devices) 29
+ * 0x09, 0x01, // Usage (Consumer Control) 31
+ * 0xa1, 0x01, // Collection (Application) 33
+ * 0x85, 0x02, // Report ID (2) 35
+ * 0x15, 0x00, // Logical Minimum (0) 37
+ * 0x25, 0x01, // Logical Maximum (1) 39
+ * 0x95, 0x12, // Report Count (18) 41
+ * 0x75, 0x01, // Report Size (1) 43
+ * 0x0a, 0x83, 0x01, // Usage (AL Consumer Control Config) 45
+ * 0x0a, 0x8a, 0x01, // Usage (AL Email Reader) 48
+ * 0x0a, 0x92, 0x01, // Usage (AL Calculator) 51
+ * 0x0a, 0x94, 0x01, // Usage (AL Local Machine Browser) 54
+ * 0x09, 0xcd, // Usage (Play/Pause) 57
+ * 0x09, 0xb7, // Usage (Stop) 59
+ * 0x09, 0xb6, // Usage (Scan Previous Track) 61
+ * 0x09, 0xb5, // Usage (Scan Next Track) 63
+ * 0x09, 0xe2, // Usage (Mute) 65
+ * 0x09, 0xea, // Usage (Volume Down) 67
+ * 0x09, 0xe9, // Usage (Volume Up) 69
+ * 0x0a, 0x21, 0x02, // Usage (AC Search) 71
+ * 0x0a, 0x23, 0x02, // Usage (AC Home) 74
+ * 0x0a, 0x24, 0x02, // Usage (AC Back) 77
+ * 0x0a, 0x25, 0x02, // Usage (AC Forward) 80
+ * 0x0a, 0x26, 0x02, // Usage (AC Stop) 83
+ * 0x0a, 0x27, 0x02, // Usage (AC Refresh) 86
+ * 0x0a, 0x2a, 0x02, // Usage (AC Bookmarks) 89
+ * 0x81, 0x02, // Input (Data,Var,Abs) 92
+ * 0x95, 0x01, // Report Count (1) 94
+ * 0x75, 0x0e, // Report Size (14) 96
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 98
+ * 0xc0, // End Collection 100
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 101
+ * 0x09, 0x02, // Usage (Mouse) 103
+ * 0xa1, 0x01, // Collection (Application) 105
+ * 0x09, 0x01, // Usage (Pointer) 107
+ * 0xa1, 0x00, // Collection (Physical) 109
+ * 0x85, 0x03, // Report ID (3) 111
+ * 0x05, 0x09, // Usage Page (Button) 113
+ * 0x19, 0x01, // Usage Minimum (1) 115
+ * 0x29, 0x08, // Usage Maximum (8) 117
+ * 0x15, 0x00, // Logical Minimum (0) 119
+ * 0x25, 0x01, // Logical Maximum (1) 121
+ * 0x75, 0x01, // Report Size (1) 123
+ * 0x95, 0x08, // Report Count (8) 125
+ * 0x81, 0x02, // Input (Data,Var,Abs) 127
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 129
+ * 0x09, 0x30, // Usage (X) 131
+ * 0x09, 0x31, // Usage (Y) 133
+ * 0x16, 0x01, 0x80, // Logical Minimum (-32767) 135
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 138
+ * 0x75, 0x10, // Report Size (16) 141
+ * 0x95, 0x02, // Report Count (2) 143
+ * 0x81, 0x06, // Input (Data,Var,Rel) 145
+ * 0x09, 0x38, // Usage (Wheel) 147
+ * 0x15, 0x81, // Logical Minimum (-127) 149
+ * 0x25, 0x7f, // Logical Maximum (127) 151
+ * 0x75, 0x08, // Report Size (8) 153
+ * 0x95, 0x01, // Report Count (1) 155
+ * 0x81, 0x06, // Input (Data,Var,Rel) 157
+ * 0x05, 0x0c, // Usage Page (Consumer Devices) 159
+ * 0x0a, 0x38, 0x02, // Usage (AC Pan) 161
+ * 0x95, 0x01, // Report Count (1) 164
+ * 0x81, 0x06, // Input (Data,Var,Rel) 166
+ * 0xc0, // End Collection 168
+ * 0xc0, // End Collection 169
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 170
+ * 0x09, 0x06, // Usage (Keyboard) 172
+ * 0xa1, 0x01, // Collection (Application) 174
+ * 0x85, 0x04, // Report ID (4) 176
+ * 0x05, 0x07, // Usage Page (Keyboard) 178
+ * 0x95, 0x01, // Report Count (1) 180
+ * 0x75, 0x08, // Report Size (8) 182
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 184
+ * 0x95, 0xe8, // Report Count (232) 186
+ * 0x75, 0x01, // Report Size (1) 188
+ * 0x15, 0x00, // Logical Minimum (0) 190
+ * 0x25, 0x01, // Logical Maximum (1) 192
+ * 0x05, 0x07, // Usage Page (Keyboard) 194
+ * 0x19, 0x00, // Usage Minimum (0) 196
+ * 0x29, 0xe7, // Usage Maximum (231) 198
+ * 0x81, 0x00, // Input (Data,Arr,Abs) 200 <- change to 0x81, 0x02 (Data,Var,Abs)
+ * 0xc0, // End Collection 202
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_rdesc_fixup_mistel_md770, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[201] == 0x00)
+ data[201] = 0x02;
+
+ return 0;
+}
+
+HID_BPF_OPS(mistel_md770) = {
+ .hid_rdesc_fixup = (void *)hid_rdesc_fixup_mistel_md770,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != RDESC_SIZE;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/README b/drivers/hid/bpf/progs/README
new file mode 100644
index 000000000000..20b0928f385b
--- /dev/null
+++ b/drivers/hid/bpf/progs/README
@@ -0,0 +1,102 @@
+# HID-BPF programs
+
+This directory contains various fixes for devices. They add new features or
+fix some behaviors without being entirely mandatory. It is better to load them
+when you have such a device, but they should not be a requirement for a device
+to be working during the boot stage.
+
+The .bpf.c files provided here are not automatically compiled in the kernel.
+They should be loaded in the kernel by `udev-hid-bpf`:
+
+https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
+
+The main reasons for these fixes to be here is to have a central place to
+"upstream" them, but also this way we can test them thanks to the HID
+selftests.
+
+Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
+in the `src/bpf/stable` directory, and distributions are encouraged to
+only ship those bpf objects. So adding a file here should eventually
+land in distributions when they update `udev-hid-bpf`
+
+## Compilation
+
+Just run `make`
+
+## Installation
+
+### Automated way
+
+Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
+
+### Manual way
+
+- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
+- create a new udev rule to automatically load it
+
+The following should do the trick (assuming udev-hid-bpf is available in
+/usr/bin):
+
+```
+$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
+$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
+[
+ {
+ "name": "xppen-ArtistPro16Gen2.bpf.o",
+ "devices": [
+ {
+ "bus": "0x0003",
+ "group": "0x0001",
+ "vid": "0x28BD",
+ "pid": "0x095A"
+ },
+ {
+ "bus": "0x0003",
+ "group": "0x0001",
+ "vid": "0x28BD",
+ "pid": "0x095B"
+ }
+ ],
+...
+$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
+ACTION!="add|remove", GOTO="hid_bpf_end"
+SUBSYSTEM!="hid", GOTO="hid_bpf_end"
+
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+
+LABEL="hid_bpf_end"
+EOF
+$> udevadm control --reload
+```
+
+Then unplug and replug the device.
+
+## Checks
+
+### udev rule
+
+You can check that the udev rule is correctly working by issuing
+
+```
+$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
+...
+run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
+```
+
+### program loaded
+
+You can check that the program has been properly loaded with `bpftool`
+
+```
+$> bpftool prog
+...
+247: tracing name xppen_16_fix_eraser tag 18d389353ed2ef07 gpl
+ loaded_at 2024-03-28T16:02:28+0100 uid 0
+ xlated 120B jited 77B memlock 4096B
+ btf_id 487
+```
diff --git a/drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c b/drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c
new file mode 100644
index 000000000000..6b379e45f531
--- /dev/null
+++ b/drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 José Expósito
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_RAPOO 0x24AE
+#define PID_M50 0x2015
+#define RDESC_SIZE 186
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_RAPOO, PID_M50)
+);
+
+/*
+ * The Rapoo M50 Plus Silent mouse has 2 side buttons in addition to the left,
+ * right and middle buttons. However, its original HID descriptor has a Usage
+ * Maximum of 3, preventing the side buttons to work. This HID-BPF driver
+ * changes that usage to 5.
+ *
+ * For reference, this is the original report descriptor:
+ *
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x02, // Usage (Mouse) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x01, // Report ID (1) 6
+ * 0x09, 0x01, // Usage (Pointer) 8
+ * 0xa1, 0x00, // Collection (Physical) 10
+ * 0x05, 0x09, // Usage Page (Button) 12
+ * 0x19, 0x01, // Usage Minimum (1) 14
+ * 0x29, 0x03, // Usage Maximum (3) 16 <- change to 0x05
+ * 0x15, 0x00, // Logical Minimum (0) 18
+ * 0x25, 0x01, // Logical Maximum (1) 20
+ * 0x75, 0x01, // Report Size (1) 22
+ * 0x95, 0x05, // Report Count (5) 24
+ * 0x81, 0x02, // Input (Data,Var,Abs) 26
+ * 0x75, 0x03, // Report Size (3) 28
+ * 0x95, 0x01, // Report Count (1) 30
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 32
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 34
+ * 0x09, 0x30, // Usage (X) 36
+ * 0x09, 0x31, // Usage (Y) 38
+ * 0x16, 0x01, 0x80, // Logical Minimum (-32767) 40
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 43
+ * 0x75, 0x10, // Report Size (16) 46
+ * 0x95, 0x02, // Report Count (2) 48
+ * 0x81, 0x06, // Input (Data,Var,Rel) 50
+ * 0x09, 0x38, // Usage (Wheel) 52
+ * 0x15, 0x81, // Logical Minimum (-127) 54
+ * 0x25, 0x7f, // Logical Maximum (127) 56
+ * 0x75, 0x08, // Report Size (8) 58
+ * 0x95, 0x01, // Report Count (1) 60
+ * 0x81, 0x06, // Input (Data,Var,Rel) 62
+ * 0xc0, // End Collection 64
+ * 0xc0, // End Collection 65
+ * 0x05, 0x0c, // Usage Page (Consumer Devices) 66
+ * 0x09, 0x01, // Usage (Consumer Control) 68
+ * 0xa1, 0x01, // Collection (Application) 70
+ * 0x85, 0x02, // Report ID (2) 72
+ * 0x75, 0x10, // Report Size (16) 74
+ * 0x95, 0x01, // Report Count (1) 76
+ * 0x15, 0x01, // Logical Minimum (1) 78
+ * 0x26, 0x8c, 0x02, // Logical Maximum (652) 80
+ * 0x19, 0x01, // Usage Minimum (1) 83
+ * 0x2a, 0x8c, 0x02, // Usage Maximum (652) 85
+ * 0x81, 0x00, // Input (Data,Arr,Abs) 88
+ * 0xc0, // End Collection 90
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 91
+ * 0x09, 0x80, // Usage (System Control) 93
+ * 0xa1, 0x01, // Collection (Application) 95
+ * 0x85, 0x03, // Report ID (3) 97
+ * 0x09, 0x82, // Usage (System Sleep) 99
+ * 0x09, 0x81, // Usage (System Power Down) 101
+ * 0x09, 0x83, // Usage (System Wake Up) 103
+ * 0x15, 0x00, // Logical Minimum (0) 105
+ * 0x25, 0x01, // Logical Maximum (1) 107
+ * 0x19, 0x01, // Usage Minimum (1) 109
+ * 0x29, 0x03, // Usage Maximum (3) 111
+ * 0x75, 0x01, // Report Size (1) 113
+ * 0x95, 0x03, // Report Count (3) 115
+ * 0x81, 0x02, // Input (Data,Var,Abs) 117
+ * 0x95, 0x05, // Report Count (5) 119
+ * 0x81, 0x01, // Input (Cnst,Arr,Abs) 121
+ * 0xc0, // End Collection 123
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 124
+ * 0x09, 0x00, // Usage (Undefined) 126
+ * 0xa1, 0x01, // Collection (Application) 128
+ * 0x85, 0x05, // Report ID (5) 130
+ * 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 132
+ * 0x09, 0x01, // Usage (Vendor Usage 1) 135
+ * 0x15, 0x81, // Logical Minimum (-127) 137
+ * 0x25, 0x7f, // Logical Maximum (127) 139
+ * 0x75, 0x08, // Report Size (8) 141
+ * 0x95, 0x07, // Report Count (7) 143
+ * 0xb1, 0x02, // Feature (Data,Var,Abs) 145
+ * 0xc0, // End Collection 147
+ * 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 148
+ * 0x09, 0x0e, // Usage (Vendor Usage 0x0e) 151
+ * 0xa1, 0x01, // Collection (Application) 153
+ * 0x85, 0xba, // Report ID (186) 155
+ * 0x95, 0x1f, // Report Count (31) 157
+ * 0x75, 0x08, // Report Size (8) 159
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 161
+ * 0x15, 0x00, // Logical Minimum (0) 164
+ * 0x09, 0x01, // Usage (Vendor Usage 1) 166
+ * 0x91, 0x02, // Output (Data,Var,Abs) 168
+ * 0x85, 0xba, // Report ID (186) 170
+ * 0x95, 0x1f, // Report Count (31) 172
+ * 0x75, 0x08, // Report Size (8) 174
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 176
+ * 0x15, 0x00, // Logical Minimum (0) 179
+ * 0x09, 0x01, // Usage (Vendor Usage 1) 181
+ * 0x81, 0x02, // Input (Data,Var,Abs) 183
+ * 0xc0, // End Collection 185
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_rdesc_fixup_rapoo_m50, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[17] == 0x03)
+ data[17] = 0x05;
+
+ return 0;
+}
+
+HID_BPF_OPS(rapoo_m50) = {
+ .hid_rdesc_fixup = (void *)hid_rdesc_fixup_rapoo_m50,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != RDESC_SIZE;
+ if (ctx->retval)
+ 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/Thrustmaster__TCA-Yoke-Boeing.bpf.c b/drivers/hid/bpf/progs/Thrustmaster__TCA-Yoke-Boeing.bpf.c
new file mode 100644
index 000000000000..ecf775a99346
--- /dev/null
+++ b/drivers/hid/bpf/progs/Thrustmaster__TCA-Yoke-Boeing.bpf.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Kumar Swarnam Iyer (kumar.s.iyer65@gmail.com)
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_THRUSTMASTER 0x044F
+#define PID_TCA_YOKE_BOEING 0x0409
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_THRUSTMASTER, PID_TCA_YOKE_BOEING)
+);
+
+/* The original HID descriptor of the Thrustmaster TCA Yoke Boeing joystick contains
+ * an Input field that shows up as an axis, ABS_MISC in Linux. But it is not possible
+ * to assign an actual physical control to this axis as they're all taken up. There
+ * are 2 vendor-defined inputs where the Input type appears to be defined wrongly.
+ * This bpf attempts to fix this by changing the Inputs so that it doesn't show up in
+ * Linux at all.
+ * This version is the short version fix that only changes 2 fields in the descriptor
+ * instead of the whole report descriptor.
+ * For reference, this is the original report descriptor:
+ *
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 0
+ * 0x09, 0x04, // Usage (Joystick) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x01, // Report ID (1) 6
+ * 0x09, 0x39, // Usage (Hat switch) 8
+ * 0x15, 0x00, // Logical Minimum (0) 10
+ * 0x25, 0x07, // Logical Maximum (7) 12
+ * 0x35, 0x00, // Physical Minimum (0) 14
+ * 0x46, 0x3b, 0x01, // Physical Maximum (315) 16
+ * 0x65, 0x14, // Unit (EnglishRotation: deg) 19
+ * 0x75, 0x04, // Report Size (4) 21
+ * 0x95, 0x01, // Report Count (1) 23
+ * 0x81, 0x42, // Input (Data,Var,Abs,Null) 25
+ * 0x65, 0x00, // Unit (None) 27
+ * 0x05, 0x09, // Usage Page (Button) 29
+ * 0x19, 0x01, // Usage Minimum (1) 31
+ * 0x29, 0x12, // Usage Maximum (18) 33
+ * 0x15, 0x00, // Logical Minimum (0) 35
+ * 0x25, 0x01, // Logical Maximum (1) 37
+ * 0x75, 0x01, // Report Size (1) 39
+ * 0x95, 0x12, // Report Count (18) 41
+ * 0x81, 0x02, // Input (Data,Var,Abs) 43
+ * 0x95, 0x02, // Report Count (2) 45
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 47
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 49
+ * 0x09, 0x31, // Usage (Y) 51
+ * 0x09, 0x30, // Usage (X) 53
+ * 0x09, 0x32, // Usage (Z) 55
+ * 0x09, 0x34, // Usage (Ry) 57
+ * 0x09, 0x33, // Usage (Rx) 59
+ * 0x09, 0x35, // Usage (Rz) 61
+ * 0x15, 0x00, // Logical Minimum (0) 63
+ * 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 65
+ * 0x75, 0x10, // Report Size (16) 70
+ * 0x95, 0x06, // Report Count (6) 72
+ * 0x81, 0x02, // Input (Data,Var,Abs) 74
+ * 0x06, 0xf0, 0xff, // Usage Page (Vendor Usage Page 0xfff0) 76
+ * 0x09, 0x59, // Usage (Vendor Usage 0x59) 79
+ * 0x15, 0x00, // Logical Minimum (0) 81
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 83
+ * 0x75, 0x08, // Report Size (8) 86
+ * 0x95, 0x01, // Report Count (1) 88
+ * 0x81, 0x02, // Input (Data,Var,Abs) 90 --> Needs to be changed
+ * 0x09, 0x51, // Usage (Vendor Usage 0x51) 92
+ * 0x15, 0x00, // Logical Minimum (0) 94
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 96
+ * 0x75, 0x08, // Report Size (8) 99
+ * 0x95, 0x20, // Report Count (32) 101 --> Needs to be changed
+ * 0x81, 0x02, // Input (Data,Var,Abs) 103
+ * 0x09, 0x50, // Usage (Vendor Usage 0x50) 105
+ * 0x15, 0x00, // Logical Minimum (0) 107
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 109
+ * 0x75, 0x08, // Report Size (8) 112
+ * 0x95, 0x0f, // Report Count (15) 114
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 116
+ * 0x09, 0x47, // Usage (Vendor Usage 0x47) 118
+ * 0x85, 0xf2, // Report ID (242) 120
+ * 0x15, 0x00, // Logical Minimum (0) 122
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 124
+ * 0x75, 0x08, // Report Size (8) 127
+ * 0x95, 0x3f, // Report Count (63) 129
+ * 0xb1, 0x02, // Feature (Data,Var,Abs) 131
+ * 0x09, 0x48, // Usage (Vendor Usage 0x48) 133
+ * 0x85, 0xf3, // Report ID (243) 135
+ * 0x15, 0x00, // Logical Minimum (0) 137
+ * 0x26, 0xff, 0x00, // Logical Maximum (255) 139
+ * 0x75, 0x08, // Report Size (8) 142
+ * 0x95, 0x3f, // Report Count (63) 144
+ * 0xb1, 0x02, // Feature (Data,Var,Abs) 146
+ * 0xc0, // End Collection 148
+ */
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_tca_yoke, struct hid_bpf_ctx *hctx)
+{
+ const int expected_length = 148;
+
+ if (hctx->size != expected_length)
+ return 0;
+
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ if (!data)
+ return 0; /* EPERM */
+
+ /* Safety check, our probe() should take care of this though */
+ if (data[1] != 0x01 /* Generic Desktop */ || data[3] != 0x04 /* Joystick */)
+ return 0;
+
+ /* The report descriptor sets incorrect Input items in 2 places, resulting in a
+ * non-existing axis showing up.
+ * This change sets the correct Input which prevents the axis from showing up in Linux.
+ */
+
+ if (data[90] == 0x81 && /* Input */
+ data[103] == 0x81) { /* Input */
+ data[91] = 0x03; /* Input set to 0x03 Constant, Variable Absolute */
+ data[104] = 0x03; /* Input set to 0X03 Constant, Variable Absolute */
+ }
+
+ return 0;
+}
+
+HID_BPF_OPS(tca_yoke) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_tca_yoke,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[91] != 0x02) /* Input for 0x59 Usage type has changed */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c b/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c
new file mode 100644
index 000000000000..156d75af516d
--- /dev/null
+++ b/drivers/hid/bpf/progs/WALTOP__Batteryless-Tablet.bpf.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Red Hat
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_WALTOP 0x172F
+#define PID_BATTERYLESS_TABLET 0x0505
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_WALTOP, PID_BATTERYLESS_TABLET)
+);
+
+#define EXPECTED_RDESC_SIZE 335
+#define PEN_REPORT_ID 16
+
+#define TIP_SWITCH BIT(0)
+#define BARREL_SWITCH BIT(1)
+#define SECONDARY_BARREL_SWITCH BIT(5)
+
+static __u8 last_button_state;
+
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x02, // Usage (Mouse)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x01, // Report ID (1)
+ 0x09, 0x01, // Usage (Pointer)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x19, 0x01, // Usage Minimum (1)
+ 0x29, 0x05, // Usage Maximum (5)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x05, // Report Count (5)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x75, 0x03, // Report Size (3)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x03, // Input (Cnst,Var,Abs)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x09, 0x38, // Usage (Wheel)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0x05, 0x0c, // Usage Page (Consumer)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7f, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x0a, 0x38, 0x02, // Usage (AC Pan)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0xc0, // End Collection
+ 0xc0, // End Collection
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x02, // Usage (Pen)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x02, // Report ID (2)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x09, // Report Count (9)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x09, 0x3f, // Usage (Azimuth)
+ 0x09, 0x40, // Usage (Altitude)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x05, // Report ID (5)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x07, // Report Count (7)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x0a, // Report ID (10)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x00, // Usage (0x0000)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x07, // Report Count (7)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, 0x10, // Report ID (16)
+ 0x09, 0x20, // Usage (Stylus)
+ 0xa1, 0x00, // Collection (Physical)
+ 0x09, 0x42, // Usage (Tip Switch)
+ 0x09, 0x44, // Usage (Barrel Switch)
+ 0x09, 0x3c, // Usage (Invert)
+ 0x09, 0x45, // Usage (Eraser)
+ 0x09, 0x32, // Usage (In Range)
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) <-- added
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x06, // Report Count (6) <--- changed from 5
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x95, 0x02, // Report Count (2) <--- changed from 3
+ 0x81, 0x03, // Input (Cnst,Var,Abs)
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x30, // Usage (X)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0xa4, // Push
+ 0x55, 0x0d, // Unit Exponent (-3)
+ 0x65, 0x33, // Unit (EnglishLinear: in³)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0x00, 0x7d, // Logical Maximum (32000)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0x00, 0x7d, // Physical Maximum (32000)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x09, 0x31, // Usage (Y)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0x20, 0x4e, // Logical Maximum (20000)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0x20, 0x4e, // Physical Maximum (20000)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x30, // Usage (Tip Pressure)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x07, // Logical Maximum (2047)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x46, 0xff, 0x07, // Physical Maximum (2047)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x05, 0x0d, // Usage Page (Digitizers)
+ 0x09, 0x3d, // Usage (X Tilt)
+ 0x09, 0x3e, // Usage (Y Tilt)
+ 0x15, 0xc4, // Logical Minimum (-60) <- changed from -127
+ 0x25, 0x3c, // Logical Maximum (60) <- changed from 127
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0xc0, // End Collection
+ 0x05, 0x01, // Usage Page (Generic Desktop)
+ 0x09, 0x06, // Usage (Keyboard)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x0d, // Report ID (13)
+ 0x05, 0x07, // Usage Page (Keyboard/Keypad)
+ 0x19, 0xe0, // Usage Minimum (224)
+ 0x29, 0xe7, // Usage Maximum (231)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input (Data,Var,Abs)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x01, // Input (Cnst,Arr,Abs)
+ 0x05, 0x07, // Usage Page (Keyboard/Keypad)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x29, 0x65, // Usage Maximum (101)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x65, // Logical Maximum (101)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x05, // Report Count (5)
+ 0x81, 0x00, // Input (Data,Arr,Abs)
+ 0xc0, // End Collection
+ 0x05, 0x0c, // Usage Page (Consumer)
+ 0x09, 0x01, // Usage (Consumer Control)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, 0x0c, // Report ID (12)
+ 0x09, 0xe9, // Usage (Volume Increment)
+ 0x09, 0xea, // Usage (Volume Decrement)
+ 0x09, 0xe2, // Usage (Mute)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x06, // Input (Data,Var,Rel)
+ 0x75, 0x05, // Report Size (5)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x07, // Input (Cnst,Var,Rel)
+ 0xc0, // End Collection
+};
+
+static inline unsigned int bitwidth32(__u32 x)
+{
+ return 32 - __builtin_clzg(x, 32);
+}
+
+static inline unsigned int floor_log2_32(__u32 x)
+{
+ return bitwidth32(x) - 1;
+}
+
+/* Maps the interval [0, 2047] to itself using a scaled
+ * approximation of the function log2(x+1).
+ */
+static unsigned int scaled_log2(__u16 v)
+{
+ const unsigned int XMAX = 2047;
+ const unsigned int YMAX = 11; /* log2(2048) = 11 */
+
+ unsigned int x = v + 1;
+ unsigned int n = floor_log2_32(x);
+ unsigned int b = 1 << n;
+
+ /* Fixed-point fraction in [0, 1), linearly
+ * interpolated using delta-y = 1 and
+ * delta-x = (2b - b) = b.
+ */
+ unsigned int frac = (x - b) << YMAX;
+ unsigned int lerp = frac / b;
+ unsigned int log2 = (n << YMAX) + lerp;
+
+ return ((log2 * XMAX) / YMAX) >> YMAX;
+}
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ return sizeof(fixed_rdesc);
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(waltop_fix_events, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __u8 report_id = data[0];
+
+ if (report_id != PEN_REPORT_ID)
+ return 0;
+
+ /* On this tablet if the secondary barrel switch is pressed,
+ * the tablet sends tip down and barrel down. Change this to
+ * just secondary barrel down when there is no ambiguity.
+ *
+ * It's possible that there is a bug in the firmware and the
+ * device intends to set invert + eraser instead (i.e. the
+ * pysical button is an eraser button) but since
+ * the pressure is always zero, said eraser button
+ * would be useless anyway.
+ *
+ * So let's just change the button to secondary barrel down.
+ */
+
+ __u8 tip_switch = data[1] & TIP_SWITCH;
+ __u8 barrel_switch = data[1] & BARREL_SWITCH;
+
+ __u8 tip_held = last_button_state & TIP_SWITCH;
+ __u8 barrel_held = last_button_state & BARREL_SWITCH;
+
+ if (tip_switch && barrel_switch && !tip_held && !barrel_held) {
+ data[1] &= ~(TIP_SWITCH | BARREL_SWITCH); /* release tip and barrel */
+ data[1] |= SECONDARY_BARREL_SWITCH; /* set secondary barrel switch */
+ }
+
+ last_button_state = data[1];
+
+ /* The pressure sensor on this tablet maps around half of the
+ * logical pressure range into the interval [0-100]. Further
+ * pressure causes the sensor value to increase exponentially
+ * up to a maximum value of 2047.
+ *
+ * The values 12 and 102 were chosen to have an integer slope
+ * with smooth transition between the two curves around the
+ * value 100.
+ */
+
+ __u16 pressure = (((__u16)data[6]) << 0) | (((__u16)data[7]) << 8);
+
+ if (pressure <= 102)
+ pressure *= 12;
+ else
+ pressure = scaled_log2(pressure);
+
+ data[6] = pressure >> 0;
+ data[7] = pressure >> 8;
+
+ return 0;
+}
+
+HID_BPF_OPS(waltop_batteryless) = {
+ .hid_device_event = (void *)waltop_fix_events,
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ if (ctx->rdesc_size == EXPECTED_RDESC_SIZE)
+ ctx->retval = 0;
+ else
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
new file mode 100644
index 000000000000..2da680bc4e11
--- /dev/null
+++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_WACOM 0x056a
+#define ART_PEN_ID 0x0804
+#define PID_INTUOS_PRO_2_M 0x0357
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
+);
+
+/*
+ * This filter is here for the Art Pen stylus only:
+ * - when used on some Wacom devices (see the list of attached PIDs), this pen
+ * reports pressure every other events.
+ * - to solve that, given that we know that the next event will be the same as
+ * the current one, we can emulate a smoother pressure reporting by reporting
+ * the mean of the previous value and the current one.
+ *
+ * We are effectively delaying the pressure by one event every other event, but
+ * that's less of an annoyance compared to the chunkiness of the reported data.
+ *
+ * For example, let's assume the following set of events:
+ * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 1> <Y 1> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 3> <Y 3> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
+ *
+ * The filter will report:
+ * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
+ * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
+ * <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
+ * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
+ * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
+ *
+ */
+
+struct wacom_params {
+ __u16 pid;
+ __u16 rdesc_len;
+ __u8 report_id;
+ __u8 report_len;
+ struct {
+ __u8 tip_switch;
+ __u8 pressure;
+ __u8 tool_type;
+ } offsets;
+};
+
+/*
+ * Multiple device can support the same stylus, so
+ * we need to know which device has which offsets
+ */
+static const struct wacom_params devices[] = {
+ {
+ .pid = PID_INTUOS_PRO_2_M,
+ .rdesc_len = 949,
+ .report_id = 16,
+ .report_len = 27,
+ .offsets = {
+ .tip_switch = 1,
+ .pressure = 8,
+ .tool_type = 25,
+ },
+ },
+};
+
+static struct wacom_params params = { 0 };
+
+/* HID-BPF reports a 64 bytes chunk anyway, so this ensures
+ * the verifier to know we are addressing the memory correctly
+ */
+#define PEN_REPORT_LEN 64
+
+/* only odd frames are modified */
+static bool odd;
+
+static __u16 prev_pressure;
+
+static inline void *get_bits(__u8 *data, unsigned int byte_offset)
+{
+ return data + byte_offset;
+}
+
+static inline __u16 *get_u16(__u8 *data, unsigned int offset)
+{
+ return (__u16 *)get_bits(data, offset);
+}
+
+static inline __u8 *get_u8(__u8 *data, unsigned int offset)
+{
+ return (__u8 *)get_bits(data, offset);
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
+ __u16 *pressure, *tool_type;
+ __u8 *tip_switch;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != params.report_id ||
+ params.offsets.tip_switch >= PEN_REPORT_LEN ||
+ params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
+ params.offsets.tool_type >= PEN_REPORT_LEN - 1)
+ return 0; /* invalid report or parameters */
+
+ tool_type = get_u16(data, params.offsets.tool_type);
+ if (*tool_type != ART_PEN_ID)
+ return 0;
+
+ tip_switch = get_u8(data, params.offsets.tip_switch);
+ if ((*tip_switch & 0x01) == 0) {
+ prev_pressure = 0;
+ odd = true;
+ return 0;
+ }
+
+ pressure = get_u16(data, params.offsets.pressure);
+
+ if (odd)
+ *pressure = (*pressure + prev_pressure) / 2;
+
+ prev_pressure = *pressure;
+ odd = !odd;
+
+ return 0;
+}
+
+HID_BPF_OPS(wacom_artpen) = {
+ .hid_device_event = (void *)artpen_pressure_interpolate,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ struct hid_bpf_ctx *hid_ctx;
+ __u16 pid;
+ int i;
+
+ /* get a struct hid_device to access the actual pid of the device */
+ hid_ctx = hid_bpf_allocate_context(ctx->hid);
+ if (!hid_ctx) {
+ ctx->retval = -ENODEV;
+ return -1; /* EPERM check */
+ }
+ pid = hid_ctx->hid->product;
+
+ ctx->retval = -EINVAL;
+
+ /* Match the given device with the list of known devices */
+ for (i = 0; i < ARRAY_SIZE(devices); i++) {
+ const struct wacom_params *device = &devices[i];
+
+ if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
+ params = *device;
+ ctx->retval = 0;
+ }
+ }
+
+ hid_bpf_release_context(hid_ctx);
+ return 0;
+}
+
+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__Artist24.bpf.c b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
new file mode 100644
index 000000000000..c24fe905c50d
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_ARTIST_24 0x093A
+#define PID_ARTIST_24_PRO 0x092D
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
+);
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the device reports Eraser instead of using Secondary Barrel Switch
+ * - the pen doesn't have a rubber tail, so basically we are removing any
+ * eraser/invert bits
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x07, // Report ID (7) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x00, // Collection (Physical) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
+ 0x15, 0x00, // Logical Minimum (0) 18
+ 0x25, 0x01, // Logical Maximum (1) 20
+ 0x75, 0x01, // Report Size (1) 22
+ 0x95, 0x03, // Report Count (3) 24
+ 0x81, 0x02, // Input (Data,Var,Abs) 26
+ 0x95, 0x02, // Report Count (2) 28
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 30
+ 0x09, 0x32, // Usage (In Range) 32
+ 0x95, 0x01, // Report Count (1) 34
+ 0x81, 0x02, // Input (Data,Var,Abs) 36
+ 0x95, 0x02, // Report Count (2) 38
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 40
+ 0x75, 0x10, // Report Size (16) 42
+ 0x95, 0x01, // Report Count (1) 44
+ 0x35, 0x00, // Physical Minimum (0) 46
+ 0xa4, // Push 48
+ 0x05, 0x01, // Usage Page (Generic Desktop) 49
+ 0x09, 0x30, // Usage (X) 51
+ 0x65, 0x13, // Unit (EnglishLinear: in) 53
+ 0x55, 0x0d, // Unit Exponent (-3) 55
+ 0x46, 0xf0, 0x50, // Physical Maximum (20720) 57
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 60
+ 0x81, 0x02, // Input (Data,Var,Abs) 63
+ 0x09, 0x31, // Usage (Y) 65
+ 0x46, 0x91, 0x2d, // Physical Maximum (11665) 67
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 70
+ 0x81, 0x02, // Input (Data,Var,Abs) 73
+ 0xb4, // Pop 75
+ 0x09, 0x30, // Usage (Tip Pressure) 76
+ 0x45, 0x00, // Physical Maximum (0) 78
+ 0x26, 0xff, 0x1f, // Logical Maximum (8191) 80
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 83
+ 0x09, 0x3d, // Usage (X Tilt) 85
+ 0x15, 0x81, // Logical Minimum (-127) 87
+ 0x25, 0x7f, // Logical Maximum (127) 89
+ 0x75, 0x08, // Report Size (8) 91
+ 0x95, 0x01, // Report Count (1) 93
+ 0x81, 0x02, // Input (Data,Var,Abs) 95
+ 0x09, 0x3e, // Usage (Y Tilt) 97
+ 0x15, 0x81, // Logical Minimum (-127) 99
+ 0x25, 0x7f, // Logical Maximum (127) 101
+ 0x81, 0x02, // Input (Data,Var,Abs) 103
+ 0xc0, // End Collection 105
+ 0xc0, // End Collection 106
+};
+
+#define TIP_SWITCH BIT(0)
+#define BARREL_SWITCH BIT(1)
+#define ERASER BIT(2)
+/* padding BIT(3) */
+/* padding BIT(4) */
+#define IN_RANGE BIT(5)
+/* padding BIT(6) */
+/* padding BIT(7) */
+
+#define U16(index) (data[index] | (data[index + 1] << 8))
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ return sizeof(fixed_rdesc);
+}
+
+static __u8 prev_state = 0;
+
+/*
+ * There are a few cases where the device is sending wrong event
+ * sequences, all related to the second button (the pen doesn't
+ * have an eraser switch on the tail end):
+ *
+ * whenever the second button gets pressed or released, an
+ * out-of-proximity event is generated and then the firmware
+ * compensate for the missing state (and the firmware uses
+ * eraser for that button):
+ *
+ * - if the pen is in range, an extra out-of-range is sent
+ * when the second button is pressed/released:
+ * // Pen is in range
+ * E: InRange
+ *
+ * // Second button is pressed
+ * E:
+ * E: Eraser InRange
+ *
+ * // Second button is released
+ * E:
+ * E: InRange
+ *
+ * This case is ignored by this filter, it's "valid"
+ * and userspace knows how to deal with it, there are just
+ * a few out-of-prox events generated, but the user doesn´t
+ * see them.
+ *
+ * - if the pen is in contact, 2 extra events are added when
+ * the second button is pressed/released: an out of range
+ * and an in range:
+ *
+ * // Pen is in contact
+ * E: TipSwitch InRange
+ *
+ * // Second button is pressed
+ * E: <- false release, needs to be filtered out
+ * E: Eraser InRange <- false release, needs to be filtered out
+ * E: TipSwitch Eraser InRange
+ *
+ * // Second button is released
+ * E: <- false release, needs to be filtered out
+ * E: InRange <- false release, needs to be filtered out
+ * E: TipSwitch InRange
+ *
+ */
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+ __u8 current_state, changed_state;
+ bool prev_tip;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ current_state = data[1];
+
+ /* if the state is identical to previously, early return */
+ if (current_state == prev_state)
+ return 0;
+
+ prev_tip = !!(prev_state & TIP_SWITCH);
+
+ /*
+ * Illegal transition: pen is in range with the tip pressed, and
+ * it goes into out of proximity.
+ *
+ * Ideally we should hold the event, start a timer and deliver it
+ * only if the timer ends, but we are not capable of that now.
+ *
+ * And it doesn't matter because when we are in such cases, this
+ * means we are detecting a false release.
+ */
+ if ((current_state & IN_RANGE) == 0) {
+ if (prev_tip)
+ return HID_IGNORE_EVENT;
+ return 0;
+ }
+
+ /*
+ * XOR to only set the bits that have changed between
+ * previous and current state
+ */
+ changed_state = prev_state ^ current_state;
+
+ /* Store the new state for future processing */
+ prev_state = current_state;
+
+ /*
+ * We get both a tipswitch and eraser change in the same HID report:
+ * this is not an authorized transition and is unlikely to happen
+ * in real life.
+ * This is likely to be added by the firmware to emulate the
+ * eraser mode so we can skip the event.
+ */
+ if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
+ return HID_IGNORE_EVENT;
+
+ return 0;
+}
+
+HID_BPF_OPS(xppen_artist_24) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artist24,
+ .hid_device_event = (void *)xppen_24_fix_eraser,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /*
+ * The device exports 3 interfaces.
+ */
+ ctx->retval = ctx->rdesc_size != 107;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x45) /* Eraser */
+ ctx->retval = -EINVAL;
+
+ 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
new file mode 100644
index 000000000000..0c7e5cc5dc7e
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#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_PRO19_GEN2)
+);
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the device reports Eraser instead of using Secondary Barrel Switch
+ * - 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 the physical dimensions of the 16" device.
+ */
+static const __u8 fixed_rdesc[] = {
+ 0x05, 0x0d, // Usage Page (Digitizers) 0
+ 0x09, 0x02, // Usage (Pen) 2
+ 0xa1, 0x01, // Collection (Application) 4
+ 0x85, 0x07, // Report ID (7) 6
+ 0x09, 0x20, // Usage (Stylus) 8
+ 0xa1, 0x00, // Collection (Physical) 10
+ 0x09, 0x42, // Usage (Tip Switch) 12
+ 0x09, 0x44, // Usage (Barrel Switch) 14
+ 0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
+ 0x09, 0x3c, // Usage (Invert) 18
+ 0x09, 0x45, // Usage (Eraser) 16 /* created over a padding bit at offset 29-33 */
+ 0x15, 0x00, // Logical Minimum (0) 20
+ 0x25, 0x01, // Logical Maximum (1) 22
+ 0x75, 0x01, // Report Size (1) 24
+ 0x95, 0x05, // Report Count (5) 26 /* changed from 4 to 5 */
+ 0x81, 0x02, // Input (Data,Var,Abs) 28
+ 0x09, 0x32, // Usage (In Range) 34
+ 0x15, 0x00, // Logical Minimum (0) 36
+ 0x25, 0x01, // Logical Maximum (1) 38
+ 0x95, 0x01, // Report Count (1) 40
+ 0x81, 0x02, // Input (Data,Var,Abs) 42
+ 0x95, 0x02, // Report Count (2) 44
+ 0x81, 0x03, // Input (Cnst,Var,Abs) 46
+ 0x75, 0x10, // Report Size (16) 48
+ 0x95, 0x01, // Report Count (1) 50
+ 0x35, 0x00, // Physical Minimum (0) 52
+ 0xa4, // Push 54
+ 0x05, 0x01, // Usage Page (Generic Desktop) 55
+ 0x09, 0x30, // Usage (X) 57
+ 0x65, 0x13, // Unit (EnglishLinear: in) 59
+ 0x55, 0x0d, // Unit Exponent (-3) 61
+ 0x46, 0xff, 0x34, // Physical Maximum (13567) 63
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 66
+ 0x81, 0x02, // Input (Data,Var,Abs) 69
+ 0x09, 0x31, // Usage (Y) 71
+ 0x46, 0x20, 0x21, // Physical Maximum (8480) 73
+ 0x26, 0xff, 0x7f, // Logical Maximum (32767) 76
+ 0x81, 0x02, // Input (Data,Var,Abs) 79
+ 0xb4, // Pop 81
+ 0x09, 0x30, // Usage (Tip Pressure) 82
+ 0x45, 0x00, // Physical Maximum (0) 84
+ 0x26, 0xff, 0x3f, // Logical Maximum (16383) 86
+ 0x81, 0x42, // Input (Data,Var,Abs,Null) 89
+ 0x09, 0x3d, // Usage (X Tilt) 91
+ 0x15, 0x81, // Logical Minimum (-127) 93
+ 0x25, 0x7f, // Logical Maximum (127) 95
+ 0x75, 0x08, // Report Size (8) 97
+ 0x95, 0x01, // Report Count (1) 99
+ 0x81, 0x02, // Input (Data,Var,Abs) 101
+ 0x09, 0x3e, // Usage (Y Tilt) 103
+ 0x15, 0x81, // Logical Minimum (-127) 105
+ 0x25, 0x7f, // Logical Maximum (127) 107
+ 0x81, 0x02, // Input (Data,Var,Abs) 109
+ 0xc0, // End Collection 111
+ 0xc0, // End Collection 112
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ __builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+ /* Fix the Physical maximum values for different sizes of the device
+ * The 14" screen device descriptor size is 11.874" x 7.421"
+ */
+ if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
+ data[63] = 0x2e;
+ 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);
+}
+
+static int xppen_16_fix_eraser(struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
+ return 0;
+
+ /* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
+ data[1] ^= 0x19;
+
+ return 0;
+}
+
+/*
+ * Static coordinate offset table based on positive only angles
+ * Two tables are needed, because the logical coordinates are scaled
+ *
+ * The table can be generated by Python like this:
+ * >>> full_scale = 11.874 # the display width/height in inches
+ * >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
+ * >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
+ * >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
+ * [0, 13, 26, ....]
+ */
+
+/* 14" inch screen 11.874" x 7.421" */
+static const __u16 angle_offsets_horizontal_14[128] = {
+ 0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
+ 55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
+ 101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
+ 129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
+ 147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
+ 154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
+ 148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
+ 132, 130, 129, 127, 126, 124, 123
+};
+static const __u16 angle_offsets_vertical_14[128] = {
+ 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
+ 88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
+ 151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
+ 199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
+ 231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
+ 245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
+ 240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
+ 217, 215, 213, 211, 208, 206, 204, 201, 199, 196
+};
+
+/* 16" inch screen 13.567" x 8.480" */
+static const __u16 angle_offsets_horizontal_16[128] = {
+ 0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
+ 50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
+ 92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
+ 116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
+ 130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
+ 134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
+ 128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
+ 111, 110, 109, 107
+};
+static const __u16 angle_offsets_vertical_16[128] = {
+ 0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
+ 77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
+ 135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
+ 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
+ 203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
+ 215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
+ 210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
+ 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])
+{
+ __u16 coords = data[idx+1];
+
+ coords <<= 8;
+ coords += data[idx];
+
+ __u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
+ __u8 angle = tilt > 0 ? tilt : -tilt;
+
+ if (angle > 127)
+ return;
+
+ __u16 compensation = (*compensation_table)[angle];
+
+ if (direction == 0) {
+ coords = (coords > compensation) ? coords - compensation : 0;
+ } else {
+ const __u16 logical_maximum = 32767;
+ __u16 max = logical_maximum - compensation;
+
+ coords = (coords < max) ? coords + compensation : logical_maximum;
+ }
+
+ data[idx] = coords & 0xff;
+ data[idx+1] = coords >> 8;
+}
+
+static int xppen_16_fix_angle_offset(struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /*
+ * Compensate X and Y offset caused by tilt.
+ *
+ * The magnetic center moves when the pen is tilted, because the coil
+ * is not touching the screen.
+ *
+ * a (tilt angle)
+ * | /... h (coil distance from tip)
+ * | /
+ * |/______
+ * |x (position offset)
+ *
+ * x = sin a * h
+ *
+ * Subtract the offset from the coordinates. Use the precomputed table!
+ *
+ * bytes 0 - report id
+ * 1 - buttons
+ * 2-3 - X coords (logical)
+ * 4-5 - Y coords
+ * 6-7 - pressure (ignore)
+ * 8 - tilt X
+ * 9 - tilt Y
+ */
+
+ __s8 tilt_x = (__s8) data[8];
+ __s8 tilt_y = (__s8) data[9];
+
+ 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);
+ 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;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_artist_pro_16_device_event, struct hid_bpf_ctx *hctx)
+{
+ int ret = xppen_16_fix_angle_offset(hctx);
+
+ if (ret)
+ return ret;
+
+ return xppen_16_fix_eraser(hctx);
+}
+
+HID_BPF_OPS(xppen_artist_pro_16) = {
+ .hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artistpro16gen2,
+ .hid_device_event = (void *)xppen_artist_pro_16_device_event,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /*
+ * The device exports 3 interfaces.
+ */
+ ctx->retval = ctx->rdesc_size != 113;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ /* ensure the kernel isn't fixed already */
+ if (ctx->rdesc[17] != 0x45) /* Eraser */
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c b/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c
new file mode 100644
index 000000000000..2502fcc9ede6
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Deco01V3.bpf.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2025 Red Hat
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_DECO_01_V3 0x0947
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_01_V3),
+);
+
+/*
+ * Default report descriptor reports:
+ * - a report descriptor for the pad buttons, reported as key sequences
+ * - a report descriptor for the pen
+ * - a vendor-specific report descriptor
+ *
+ * The Pad report descriptor, see
+ * https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/issues/54
+ *
+ * # 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
+ *
+ * And key events for buttons top->bottom are:
+ * Buttons released: 06 00 00 00 00 00 00 00
+ * Button1: 06 00 05 00 00 00 00 00 -> b
+ * Button2: 06 00 08 00 00 00 00 00 -> e
+ * Button3: 06 04 00 00 00 00 00 00 -> LAlt
+ * Button4: 06 00 2c 00 00 00 00 00 -> Space
+ * Button5: 06 01 16 00 00 00 00 00 -> LControl + s
+ * Button6: 06 01 1d 00 00 00 00 00 -> LControl + z
+ * Button7: 06 01 57 00 00 00 00 00 -> LControl + Keypad Plus
+ * Button8: 06 01 56 00 00 00 00 00 -> LControl + Keypad Dash
+ *
+ * When multiple buttons are pressed at the same time, the values used to
+ * identify the buttons are identical, but they appear in different bytes of the
+ * record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
+ * this is the report:
+ *
+ * Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b
+ *
+ * Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
+ * report.
+ *
+ * Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
+ * and 5 generates this report:
+ *
+ * Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s
+ * -- --
+ * | |
+ * | `- Button 5 (0x16)
+ * `- 0x05 = 0101. Button 3 is pressed
+ * ^
+ *
+ * pad_buttons contains a list of buttons that can be matched in
+ * HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
+ *
+ *
+ * The Pen report descriptor announces a wrong tilt range:
+ *
+ * Report descriptor length: 109 bytes
+ * 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * 0x09, 0x02, // Usage (Pen) 2
+ * 0xa1, 0x01, // Collection (Application) 4
+ * 0x85, 0x07, // Report ID (7) 6
+ * 0x09, 0x20, // Usage (Stylus) 8
+ * 0xa1, 0x01, // Collection (Application) 10
+ * 0x09, 0x42, // Usage (Tip Switch) 12
+ * 0x09, 0x44, // Usage (Barrel Switch) 14
+ * 0x09, 0x45, // Usage (Eraser) 16
+ * 0x09, 0x3c, // Usage (Invert) 18
+ * 0x15, 0x00, // Logical Minimum (0) 20
+ * 0x25, 0x01, // Logical Maximum (1) 22
+ * 0x75, 0x01, // Report Size (1) 24
+ * 0x95, 0x04, // Report Count (4) 26
+ * 0x81, 0x02, // Input (Data,Var,Abs) 28
+ * 0x95, 0x01, // Report Count (1) 30
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 32
+ * 0x09, 0x32, // Usage (In Range) 34
+ * 0x95, 0x01, // Report Count (1) 36
+ * 0x81, 0x02, // Input (Data,Var,Abs) 38
+ * 0x95, 0x02, // Report Count (2) 40
+ * 0x81, 0x03, // Input (Cnst,Var,Abs) 42
+ * 0x75, 0x10, // Report Size (16) 44
+ * 0x95, 0x01, // Report Count (1) 46
+ * 0x35, 0x00, // Physical Minimum (0) 48
+ * 0xa4, // Push 50
+ * 0x05, 0x01, // Usage Page (Generic Desktop) 51
+ * 0x09, 0x30, // Usage (X) 53
+ * 0x65, 0x13, // Unit (EnglishLinear: in) 55
+ * 0x55, 0x0d, // Unit Exponent (-3) 57
+ * 0x46, 0x10, 0x27, // Physical Maximum (10000) 59
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 62
+ * 0x81, 0x02, // Input (Data,Var,Abs) 65
+ * 0x09, 0x31, // Usage (Y) 67
+ * 0x46, 0x6a, 0x18, // Physical Maximum (6250) 69
+ * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 72
+ * 0x81, 0x02, // Input (Data,Var,Abs) 75
+ * 0xb4, // Pop 77
+ * 0x09, 0x30, // Usage (X) 78
+ * 0x45, 0x00, // Physical Maximum (0) 80
+ * 0x26, 0xff, 0x3f, // Logical Maximum (16383) 82
+ * 0x81, 0x42, // Input (Data,Var,Abs,Null) 85
+ * 0x09, 0x3d, // Usage (Start) 87
+ * 0x15, 0x81, // Logical Minimum (-127) 89 <- Change from -127 to -60
+ * 0x25, 0x7f, // Logical Maximum (127) 91 <- Change from 127 to 60
+ * 0x75, 0x08, // Report Size (8) 93
+ * 0x95, 0x01, // Report Count (1) 95
+ * 0x81, 0x02, // Input (Data,Var,Abs) 97
+ * 0x09, 0x3e, // Usage (Select) 99
+ * 0x15, 0x81, // Logical Minimum (-127) 101 <- Change from -127 to -60
+ * 0x25, 0x7f, // Logical Maximum (127) 103 <- Change from 127 to 60
+ * 0x81, 0x02, // Input (Data,Var,Abs) 105
+ * 0xc0, // End Collection 107
+ * 0xc0, // End Collection 108
+ */
+
+#define PEN_REPORT_DESCRIPTOR_LENGTH 109
+#define PAD_REPORT_DESCRIPTOR_LENGTH 102
+#define PAD_REPORT_LENGTH 8
+#define PAD_REPORT_ID 6
+#define PAD_NUM_BUTTONS 8
+
+static const __u8 fixed_rdesc_pad[] = {
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ // Byte 0 in report is the report ID
+ ReportId(PAD_REPORT_ID)
+ ReportCount(1)
+ ReportSize(8)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ // Byte 1 is the button state
+ UsagePage_Button
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(PAD_NUM_BUTTONS)
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ ReportCount(PAD_NUM_BUTTONS)
+ ReportSize(1)
+ Input(Var|Abs)
+ // Byte 2 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 3/4 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 5-7 are padding so we match the original report lengtth
+ ReportCount(3)
+ ReportSize(8)
+ Input(Const)
+ )
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(xppen_deco01v3_rdesc_fixup, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
+
+ const __u8 wrong_logical_range[] = {0x15, 0x81, 0x25, 0x7f};
+ const __u8 correct_logical_range[] = {0x15, 0xc4, 0x25, 0x3c};
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ switch (hctx->size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ case PEN_REPORT_DESCRIPTOR_LENGTH:
+ if (__builtin_memcmp(&data[89], wrong_logical_range,
+ sizeof(wrong_logical_range)) == 0)
+ __builtin_memcpy(&data[89], correct_logical_range,
+ sizeof(correct_logical_range));
+ if (__builtin_memcmp(&data[101], wrong_logical_range,
+ sizeof(wrong_logical_range)) == 0)
+ __builtin_memcpy(&data[101], correct_logical_range,
+ sizeof(correct_logical_range));
+ break;
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_deco01v3_device_event, struct hid_bpf_ctx *hctx)
+{
+ static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2c, 0x16, 0x1d, 0x57, 0x56 };
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] == PAD_REPORT_ID) {
+ __u8 button_mask = 0;
+ size_t d, b;
+
+ /* data[1] stores the status of BTN_2 in the 3rd bit*/
+ if (data[1] & BIT(2))
+ button_mask |= BIT(2);
+
+ /* The rest of the descriptor stores the buttons as in pad_buttons */
+ for (d = 2; d < 8; d++) {
+ for (b = 0; b < sizeof(pad_buttons); b++) {
+ if (data[d] != 0 && data[d] == pad_buttons[b])
+ button_mask |= BIT(b);
+ }
+ }
+
+ __u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
+
+ __builtin_memcpy(data, report, sizeof(report));
+ }
+ return 0;
+}
+
+HID_BPF_OPS(xppen_deco01v3) = {
+ .hid_rdesc_fixup = (void *)xppen_deco01v3_rdesc_fixup,
+ .hid_device_event = (void *)xppen_deco01v3_device_event,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ switch (ctx->rdesc_size) {
+ case PAD_REPORT_DESCRIPTOR_LENGTH:
+ case PEN_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/XPPen__Deco02.bpf.c b/drivers/hid/bpf/progs/XPPen__Deco02.bpf.c
new file mode 100644
index 000000000000..4b2549031e56
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Deco02.bpf.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include "hid_report_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD
+#define PID_DECO_02 0x0803
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_02),
+);
+
+/*
+ * Devices are:
+ * - Pad input, including pen (This is the only one we are interested in)
+ * - Pen input as mouse
+ * - Vendor
+ *
+ * Descriptors on main device are:
+ * - 7: Pen
+ * - 6: Vendor settings? Unclear
+ * - 3: Keyboard (This is what we want to modify)
+ * - 5: Feature report
+ *
+ * This creates three event nodes:
+ * - XP-PEN DECO 02 Stylus
+ * - XP-PEN DECO 02
+ * - XP-PEN DECO 02 Keyboard (Again, what we want to modify)
+ *
+ * # Report descriptor length: 188 bytes
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 0
+ * # 0x09, 0x02, // Usage (Pen) 2
+ * # 0xa1, 0x01, // Collection (Application) 4
+ * # 0x85, 0x07, // Report ID (7) 6
+ * # 0x09, 0x20, // Usage (Stylus) 8
+ * # 0xa1, 0x00, // Collection (Physical) 10
+ * # 0x09, 0x42, // Usage (Tip Switch) 12
+ * # 0x09, 0x44, // Usage (Barrel Switch) 14
+ * # 0x09, 0x45, // Usage (Eraser) 16
+ * # 0x09, 0x3c, // Usage (Invert) 18
+ * # 0x09, 0x32, // Usage (In Range) 20
+ * # 0x15, 0x00, // Logical Minimum (0) 22
+ * # 0x25, 0x01, // Logical Maximum (1) 24
+ * # 0x75, 0x01, // Report Size (1) 26
+ * # 0x95, 0x05, // Report Count (5) 28
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 30
+ * # 0x95, 0x03, // Report Count (3) 32
+ * # 0x81, 0x03, // Input (Cnst,Var,Abs) 34
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 36
+ * # 0x09, 0x30, // Usage (X) 38
+ * # 0x15, 0x00, // Logical Minimum (0) 40
+ * # 0x26, 0x50, 0x57, // Logical Maximum (22352) 42
+ * # 0x55, 0x0d, // Unit Exponent (-3) 45
+ * # 0x65, 0x13, // Unit (EnglishLinear: in) 47
+ * # 0x35, 0x00, // Physical Minimum (0) 49
+ * # 0x46, 0x50, 0x57, // Physical Maximum (22352) 51
+ * # 0x75, 0x10, // Report Size (16) 54
+ * # 0x95, 0x01, // Report Count (1) 56
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 58
+ * # 0x09, 0x31, // Usage (Y) 60
+ * # 0x15, 0x00, // Logical Minimum (0) 62
+ * # 0x26, 0x92, 0x36, // Logical Maximum (13970) 64
+ * # 0x55, 0x0d, // Unit Exponent (-3) 67
+ * # 0x65, 0x13, // Unit (EnglishLinear: in) 69
+ * # 0x35, 0x00, // Physical Minimum (0) 71
+ * # 0x46, 0x92, 0x36, // Physical Maximum (13970) 73
+ * # 0x75, 0x10, // Report Size (16) 76
+ * # 0x95, 0x01, // Report Count (1) 78
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 80
+ * # 0x05, 0x0d, // Usage Page (Digitizers) 82
+ * # 0x09, 0x30, // Usage (Tip Pressure) 84
+ * # 0x15, 0x00, // Logical Minimum (0) 86
+ * # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 88
+ * # 0x75, 0x10, // Report Size (16) 91
+ * # 0x95, 0x01, // Report Count (1) 93
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 95
+ * # 0xc0, // End Collection 97
+ * # 0xc0, // End Collection 98
+ * # 0x09, 0x0e, // Usage (Device Configuration) 99
+ * # 0xa1, 0x01, // Collection (Application) 101
+ * # 0x85, 0x05, // Report ID (5) 103
+ * # 0x09, 0x23, // Usage (Device Settings) 105
+ * # 0xa1, 0x02, // Collection (Logical) 107
+ * # 0x09, 0x52, // Usage (Inputmode) 109
+ * # 0x09, 0x53, // Usage (Device Index) 111
+ * # 0x25, 0x0a, // Logical Maximum (10) 113
+ * # 0x75, 0x08, // Report Size (8) 115
+ * # 0x95, 0x02, // Report Count (2) 117
+ * # 0xb1, 0x02, // Feature (Data,Var,Abs) 119
+ * # 0xc0, // End Collection 121
+ * # 0xc0, // End Collection 122
+ * # 0x05, 0x0c, // Usage Page (Consumer Devices) 123
+ * # 0x09, 0x36, // Usage (Function Buttons) 125
+ * # 0xa1, 0x00, // Collection (Physical) 127
+ * # 0x85, 0x06, // Report ID (6) 129
+ * # 0x05, 0x09, // Usage Page (Button) 131
+ * # 0x19, 0x01, // Usage Minimum (1) 133
+ * # 0x29, 0x20, // Usage Maximum (32) 135
+ * # 0x15, 0x00, // Logical Minimum (0) 137
+ * # 0x25, 0x01, // Logical Maximum (1) 139
+ * # 0x95, 0x20, // Report Count (32) 141
+ * # 0x75, 0x01, // Report Size (1) 143
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 145
+ * # 0xc0, // End Collection 147
+ * # 0x05, 0x01, // Usage Page (Generic Desktop) 148
+ * # 0x09, 0x06, // Usage (Keyboard) 150
+ * # 0xa1, 0x01, // Collection (Application) 152
+ * # 0x85, 0x03, // Report ID (3) 154
+ * # 0x05, 0x07, // Usage Page (Keyboard) 156
+ * # 0x19, 0xe0, // Usage Minimum (224) 158
+ * # 0x29, 0xe7, // Usage Maximum (231) 160
+ * # 0x15, 0x00, // Logical Minimum (0) 162
+ * # 0x25, 0x01, // Logical Maximum (1) 164
+ * # 0x75, 0x01, // Report Size (1) 166
+ * # 0x95, 0x08, // Report Count (8) 168
+ * # 0x81, 0x02, // Input (Data,Var,Abs) 170
+ * # 0x05, 0x07, // Usage Page (Keyboard) 172
+ * # 0x19, 0x00, // Usage Minimum (0) 174
+ * # 0x29, 0xff, // Usage Maximum (255) 176
+ * # 0x26, 0xff, 0x00, // Logical Maximum (255) 178
+ * # 0x75, 0x08, // Report Size (8) 181
+ * # 0x95, 0x06, // Report Count (6) 183
+ * # 0x81, 0x00, // Input (Data,Arr,Abs) 185
+ * # 0xc0, // End Collection 187
+ *
+ * Key events; top to bottom:
+ * Buttons released: 03 00 00 00 00 00 00 00
+ * Button1: 03 00 05 00 00 00 00 00 -> 'b and B'
+ * Button2: 03 00 2c 00 00 00 00 00 -> 'Spacebar'
+ * Button3: 03 00 08 00 00 00 00 00 -> 'e and E'
+ * Button4: 03 00 0c 00 00 00 00 00 -> 'i and I'
+ * Button5: 03 05 1d 00 00 00 00 00 -> LeftControl + LeftAlt + 'z and Z'
+ * Button6: 03 01 16 00 00 00 00 00 -> LeftControl + 's and S'
+ *
+ * Dial Events:
+ * Clockwise: 03 01 2e 00 00 00 00 00 -> LeftControl + '= and +'
+ * Anticlockwise: 03 01 2d 00 00 00 00 00 -> LeftControl + '- and (underscore)'
+ *
+ * NOTE: Input event descriptions begin at byte 2, and progressively build
+ * towards byte 7 as each new key is pressed maintaining the press order.
+ * For example:
+ * BTN1 followed by BTN2 is 03 00 05 2c 00 00 00 00
+ * BTN2 followed by BTN1 is 03 00 2c 05 00 00 00 00
+ *
+ * Releasing a button causes its byte to be freed, and the next item in the list
+ * is pushed forwards. Dial events are released immediately after an event is
+ * registered (i.e. after each "click"), so will continually appear pushed
+ * backwards in the report.
+ *
+ * When a button with a modifier key is pressed, the button identifier stacks in
+ * an abnormal way, where the highest modifier byte always supersedes others.
+ * In these cases, the button with the higher modifier is always last.
+ * For example:
+ * BTN6 followed by BTN5 is 03 05 1d 16 00 00 00 00
+ * BTN5 followed by BTN6 is 03 05 1d 16 00 00 00 00
+ * BTN5 followed by BTN1 is 03 05 05 1d 00 00 00 00
+ *
+ * For three button presses in order, demonstrating strictly above rules:
+ * BTN6, BTN1, BTN5 is 03 05 05 1d 16 00 00 00
+ * BTN5, BTN1, BTN6 is 03 05 05 1d 16 00 00 00
+ *
+ * In short, when BTN5/6 are pressed, the order of operations is lost, as they
+ * will always float to the end when pressed in combination with others.
+ *
+ * Fortunately, all states are recorded in the same way, with no overlaps.
+ * Byte 1 can be used as a spare for the wheel, since this is for mod keys.
+ */
+
+#define RDESC_SIZE_PAD 188
+#define REPORT_SIZE_PAD 8
+#define REPORT_ID_BUTTONS 3
+#define PAD_BUTTON_COUNT 6
+#define RDESC_KEYBOARD_OFFSET 148
+
+static const __u8 fixed_rdesc_pad[] = {
+ /* Copy of pen descriptor to avoid losing functionality */
+ UsagePage_Digitizers
+ Usage_Dig_Pen
+ CollectionApplication(
+ ReportId(7)
+ Usage_Dig_Stylus
+ CollectionPhysical(
+ Usage_Dig_TipSwitch
+ Usage_Dig_BarrelSwitch
+ Usage_Dig_Eraser
+ Usage_Dig_Invert
+ Usage_Dig_InRange
+ LogicalMinimum_i8(0)
+ LogicalMaximum_i8(1)
+ ReportSize(1)
+ ReportCount(5)
+ Input(Var|Abs)
+ ReportCount(3)
+ Input(Const) /* Input (Const, Var, Abs) */
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(22352)
+ UnitExponent(-3)
+ Unit(in) /* (EnglishLinear: in) */
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(22352)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ Usage_GD_Y
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(13970)
+ UnitExponent(-3)
+ Unit(in) /* (EnglishLinear: in) */
+ PhysicalMinimum_i16(0)
+ PhysicalMaximum_i16(13970)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ UsagePage_Digitizers
+ Usage_Dig_TipPressure
+ LogicalMinimum_i16(0)
+ LogicalMaximum_i16(8191)
+ ReportSize(16)
+ ReportCount(1)
+ Input(Var|Abs)
+ )
+ )
+
+ /* FIXES BEGIN */
+ UsagePage_GenericDesktop
+ Usage_GD_Keypad
+ CollectionApplication(
+ ReportId(REPORT_ID_BUTTONS) /* Retain original ID on byte 0 */
+ ReportCount(1)
+ ReportSize(REPORT_SIZE_PAD)
+ UsagePage_Digitizers
+ Usage_Dig_TabletFunctionKeys
+ CollectionPhysical(
+ /* Byte 1: Dial state */
+ UsagePage_GenericDesktop
+ Usage_GD_Dial
+ LogicalMinimum_i8(-1)
+ LogicalMaximum_i8(1)
+ ReportCount(1)
+ ReportSize(REPORT_SIZE_PAD)
+ Input(Var|Rel)
+ /* Byte 2: Button state */
+ UsagePage_Button
+ ReportSize(1)
+ ReportCount(PAD_BUTTON_COUNT)
+ UsageMinimum_i8(0x01)
+ UsageMaximum_i8(PAD_BUTTON_COUNT) /* Number of buttons */
+ LogicalMinimum_i8(0x0)
+ LogicalMaximum_i8(0x1)
+ Input(Var|Abs)
+ /* Byte 3: Exists to be tablet pad */
+ UsagePage_Digitizers
+ Usage_Dig_BarrelSwitch
+ ReportCount(1)
+ ReportSize(1)
+ Input(Var|Abs)
+ ReportCount(7) /* Padding, to fill full report space */
+ Input(Const)
+ /* Byte 4/5: Exists to be a tablet pad */
+ UsagePage_GenericDesktop
+ Usage_GD_X
+ Usage_GD_Y
+ ReportCount(2)
+ ReportSize(8)
+ Input(Var|Abs)
+ /* Bytes 6/7: Padding, to match original length */
+ ReportCount(2)
+ ReportSize(8)
+ Input(Const)
+ )
+ FixedSizeVendorReport(RDESC_SIZE_PAD)
+ )
+};
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(xppen_deco02_rdesc_fixup, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
+
+ if (!data)
+ return 0; /* EPERM Check */
+
+ if (hctx->size == RDESC_SIZE_PAD) {
+ __builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
+ return sizeof(fixed_rdesc_pad);
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(xppen_deco02_device_event, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, REPORT_SIZE_PAD);
+
+ if (!data || data[0] != REPORT_ID_BUTTONS)
+ return 0; /* EPERM or wrong report */
+
+ __u8 dial_code = 0;
+ __u8 button_mask = 0;
+ size_t d;
+
+ /* Start from 2; 0 is report ID, 1 is modifier keys, replaced by dial */
+ for (d = 2; d < 8; d++) {
+ switch (data[d]) {
+ case 0x2e:
+ dial_code = 1;
+ break;
+ case 0x2d:
+ dial_code = -1;
+ break;
+ /* below are buttons, top to bottom */
+ case 0x05:
+ button_mask |= BIT(0);
+ break;
+ case 0x2c:
+ button_mask |= BIT(1);
+ break;
+ case 0x08:
+ button_mask |= BIT(2);
+ break;
+ case 0x0c:
+ button_mask |= BIT(3);
+ break;
+ case 0x1d:
+ button_mask |= BIT(4);
+ break;
+ case 0x16:
+ button_mask |= BIT(05);
+ break;
+ default:
+ break;
+ }
+ }
+
+ __u8 report[8] = { REPORT_ID_BUTTONS, dial_code, button_mask, 0x00 };
+
+ __builtin_memcpy(data, report, sizeof(report));
+ return 0;
+}
+
+HID_BPF_OPS(xppen_deco02) = {
+ .hid_rdesc_fixup = (void *)xppen_deco02_rdesc_fixup,
+ .hid_device_event = (void *)xppen_deco02_device_event,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD ? -EINVAL : 0;
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c
new file mode 100644
index 000000000000..46d5c459d0c9
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024 José Expósito
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD
+#define PID_DECO_MINI_4 0x0929
+#define RDESC_SIZE_PAD 177
+#define RDESC_SIZE_PEN 109
+#define PAD_REPORT_ID 0x06
+
+/*
+ * XP-Pen devices return a descriptor with the values the driver should use when
+ * one of its interfaces is queried. For this device the descriptor is:
+ *
+ * 0E 03 60 4F 88 3B 06 00 FF 1F D8 13
+ * ----- ----- ----- -----
+ * | | | |
+ * | | | `- Resolution: 5080 (13d8)
+ * | | `- Maximum pressure: 8191 (1FFF)
+ * | `- Logical maximum Y: 15240 (3B88)
+ * `- Logical maximum X: 20320 (4F60)
+ *
+ * The physical maximum is calculated as (logical_max * 1000) / resolution.
+ */
+#define LOGICAL_MAX_X 0x60, 0x4F
+#define LOGICAL_MAX_Y 0x88, 0x3B
+#define PHYSICAL_MAX_X 0xA0, 0x0F
+#define PHYSICAL_MAX_Y 0xB8, 0x0B
+#define PRESSURE_MAX 0xFF, 0x1F
+
+HID_BPF_CONFIG(
+ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_MINI_4)
+);
+
+/*
+ * The tablet send these values when the pad buttons are pressed individually:
+ *
+ * Buttons released: 06 00 00 00 00 00 00 00
+ * Button 1: 06 00 05 00 00 00 00 00 -> b
+ * Button 2: 06 00 08 00 00 00 00 00 -> e
+ * Button 3: 06 04 00 00 00 00 00 00 -> LAlt
+ * Button 4: 06 00 2c 00 00 00 00 00 -> Space
+ * Button 5: 06 01 16 00 00 00 00 00 -> LControl + s
+ * Button 6: 06 01 1d 00 00 00 00 00 -> LControl + z
+ *
+ * When multiple buttons are pressed at the same time, the values used to
+ * identify the buttons are identical, but they appear in different bytes of the
+ * record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
+ * this is the report:
+ *
+ * Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b
+ *
+ * Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
+ * report.
+ *
+ * Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
+ * and 5 generates this report:
+ *
+ * Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s
+ * -- --
+ * | |
+ * | `- Button 5 (0x16)
+ * `- 0x05 = 0101. Button 3 is pressed
+ * ^
+ *
+ * pad_buttons contains a list of buttons that can be matched in
+ * HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
+ */
+static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2C, 0x16, 0x1D };
+
+static const __u8 fixed_pad_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x06, /* Report ID (6), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x06, /* Report Count (6), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x06, /* Usage Maximum (06h), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x32, /* Report Count (50), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
+};
+
+static const __u8 fixed_pen_rdesc[] = {
+ 0x05, 0x0d, /* Usage Page (Digitizers), */
+ 0x09, 0x01, /* Usage (Digitizer), */
+ 0xa1, 0x01, /* Collection (Application), */
+ 0x85, 0x07, /* Report ID (7), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xa1, 0x00, /* Collection (Physical), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x35, 0x00, /* Physical Minimum (0), */
+ 0xa4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x55, 0x0d, /* Unit Exponent (-3), */
+ 0x26, LOGICAL_MAX_X, /* Logical Maximum, */
+ 0x46, PHYSICAL_MAX_X, /* Physical Maximum, */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x26, LOGICAL_MAX_Y, /* Logical Maximum, */
+ 0x46, PHYSICAL_MAX_Y, /* Physical Maximum, */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xb4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x45, 0x00, /* Physical Maximum (0), */
+ 0x26, PRESSURE_MAX, /* Logical Maximum, */
+ 0x75, 0x0D, /* Report Size (13), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x13, /* Report Count (19), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0xc0, /* End Collection, */
+ 0xc0, /* End Collection */
+};
+
+static const size_t fixed_pad_rdesc_size = sizeof(fixed_pad_rdesc);
+static const size_t fixed_pen_rdesc_size = sizeof(fixed_pen_rdesc);
+
+SEC(HID_BPF_RDESC_FIXUP)
+int BPF_PROG(hid_rdesc_fixup_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (hctx->size == RDESC_SIZE_PAD) {
+ __builtin_memcpy(data, fixed_pad_rdesc, fixed_pad_rdesc_size);
+ return fixed_pad_rdesc_size;
+ } else if (hctx->size == RDESC_SIZE_PEN) {
+ __builtin_memcpy(data, fixed_pen_rdesc, fixed_pen_rdesc_size);
+ return fixed_pen_rdesc_size;
+ }
+
+ return 0;
+}
+
+SEC(HID_BPF_DEVICE_EVENT)
+int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
+{
+ __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
+ __u8 button_mask = 0;
+ int d, b;
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ if (data[0] != PAD_REPORT_ID)
+ return 0;
+
+ /* data[1] stores the status of BTN_2 in the 3rd bit*/
+ if (data[1] & BIT(2))
+ button_mask |= BIT(2);
+
+ /* The rest of the descriptor stores the buttons as in pad_buttons */
+ for (d = 2; d < 8; d++) {
+ for (b = 0; b < sizeof(pad_buttons); b++) {
+ if (data[d] != 0 && data[d] == pad_buttons[b])
+ button_mask |= BIT(b);
+ }
+ }
+
+ __u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
+
+ __builtin_memcpy(data, report, sizeof(report));
+
+ return 0;
+}
+
+HID_BPF_OPS(deco_mini_4) = {
+ .hid_device_event = (void *)hid_device_event_xppen_deco_mini_4,
+ .hid_rdesc_fixup = (void *)hid_rdesc_fixup_xppen_deco_mini_4,
+};
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+ /*
+ * The device has 2 modes: The compatibility mode, enabled by default,
+ * and the raw mode, that can be activated by sending a buffer of magic
+ * data to a certain USB endpoint.
+ *
+ * Depending on the mode, different interfaces of the device are used:
+ * - First interface: Pad in compatibility mode
+ * - Second interface: Pen in compatibility mode
+ * - Third interface: Only used in raw mode
+ *
+ * We'll use the device in compatibility mode.
+ */
+ ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD &&
+ ctx->rdesc_size != RDESC_SIZE_PEN;
+ if (ctx->retval)
+ ctx->retval = -EINVAL;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h
new file mode 100644
index 000000000000..7cabd1b2e837
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef ____HID_BPF__H
+#define ____HID_BPF__H
+
+#define HID_BPF_DEVICE_EVENT "struct_ops/hid_device_event"
+#define HID_BPF_RDESC_FIXUP "struct_ops/hid_rdesc_fixup"
+#define HID_BPF_OPS(name) SEC(".struct_ops.link") \
+ struct hid_bpf_ops name
+#define hid_set_name(_hdev, _name) __builtin_memcpy(_hdev->name, _name, sizeof(_name))
+
+struct hid_bpf_probe_args {
+ unsigned int hid;
+ unsigned int rdesc_size;
+ unsigned char rdesc[4096];
+ int retval;
+};
+
+#endif /* ____HID_BPF__H */
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
new file mode 100644
index 000000000000..bf19785a6b06
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_HELPERS_H
+#define __HID_BPF_HELPERS_H
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <linux/errno.h>
+
+extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
+ unsigned int offset,
+ const size_t __sz) __ksym;
+extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
+extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
+extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
+ __u8 *data,
+ 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
+
+/* extracted from <linux/input.h> */
+#define BUS_ANY 0x00
+#define BUS_PCI 0x01
+#define BUS_ISAPNP 0x02
+#define BUS_USB 0x03
+#define BUS_HIL 0x04
+#define BUS_BLUETOOTH 0x05
+#define BUS_VIRTUAL 0x06
+#define BUS_ISA 0x10
+#define BUS_I8042 0x11
+#define BUS_XTKBD 0x12
+#define BUS_RS232 0x13
+#define BUS_GAMEPORT 0x14
+#define BUS_PARPORT 0x15
+#define BUS_AMIGA 0x16
+#define BUS_ADB 0x17
+#define BUS_I2C 0x18
+#define BUS_HOST 0x19
+#define BUS_GSC 0x1A
+#define BUS_ATARI 0x1B
+#define BUS_SPI 0x1C
+#define BUS_RMI 0x1D
+#define BUS_CEC 0x1E
+#define BUS_INTEL_ISHTP 0x1F
+#define BUS_AMD_SFH 0x20
+
+/* extracted from <linux/hid.h> */
+#define HID_GROUP_ANY 0x0000
+#define HID_GROUP_GENERIC 0x0001
+#define HID_GROUP_MULTITOUCH 0x0002
+#define HID_GROUP_SENSOR_HUB 0x0003
+#define HID_GROUP_MULTITOUCH_WIN_8 0x0004
+#define HID_GROUP_RMI 0x0100
+#define HID_GROUP_WACOM 0x0101
+#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102
+#define HID_GROUP_STEAM 0x0103
+#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104
+#define HID_GROUP_VIVALDI 0x0105
+
+/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
+#define HID_VID_ANY 0x0000
+#define HID_PID_ANY 0x0000
+
+#define BIT(n) (1UL << (n))
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for
+ * field/variable names
+ */
+#define COMBINE1(X, Y) X ## Y
+#define COMBINE(X, Y) COMBINE1(X, Y)
+
+/* Macro magic:
+ * __uint(foo, 123) creates a int (*foo)[1234]
+ *
+ * We use that macro to declare an anonymous struct with several
+ * fields, each is the declaration of an pointer to an array of size
+ * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
+ * would be sizeof(pointer) rather than sizeof(array). Not that we ever
+ * instantiate it anyway).
+ *
+ * This is only used for BTF introspection, we can later check "what size
+ * is the bus array" in the introspection data and thus extract the bus ID
+ * again.
+ *
+ * And we use the __LINE__ to give each of our structs a unique name so the
+ * BPF program writer doesn't have to.
+ *
+ * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
+ * shows the inspection data, start by searching for .hid_bpf_config
+ * and working backwards from that (each entry references the type_id of the
+ * content).
+ */
+
+#define HID_DEVICE(b, g, ven, prod) \
+ struct { \
+ __uint(name, 0); \
+ __uint(bus, (b)); \
+ __uint(group, (g)); \
+ __uint(vid, (ven)); \
+ __uint(pid, (prod)); \
+ } COMBINE(_entry, __LINE__)
+
+/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
+ * we can pass multiple HID_DEVICE() invocations in.
+ *
+ * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
+ *
+ * union {
+ * HID_DEVICE(...);
+ * HID_DEVICE(...);
+ * } _device_ids SEC(".hid_bpf_config")
+ *
+ */
+
+/* Returns the number of macro arguments, this expands
+ * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
+ * NTH_ARG always returns the 16th argument which in our case is 3.
+ *
+ * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
+ * updated.
+ */
+#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN)
+#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
+
+/* Add to this if we need more than 16 args */
+#define _COUNTDOWN \
+ 15, 14, 13, 12, 11, 10, 9, 8, \
+ 7, 6, 5, 4, 3, 2, 1, 0
+
+/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
+ * 1-indexed.
+ */
+#define _NTH_ARG( \
+ _1, _2, _3, _4, _5, _6, _7, _8, \
+ _9, _10, _11, _12, _13, _14, _15,\
+ N, ...) N
+
+/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
+#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
+
+/* And now define all the ARG macros for each number of args we want to accept */
+#define _ARG1(_1) _1;
+#define _ARG2(_1, _2) _1; _2;
+#define _ARG3(_1, _2, _3) _1; _2; _3;
+#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4;
+#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5;
+#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6;
+#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7;
+#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8;
+#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9;
+#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
+#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
+#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
+#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
+#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
+#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
+
+
+#define HID_BPF_CONFIG(...) union { \
+ _EXPAND(_ARG, __VA_ARGS__) \
+} _device_ids SEC(".hid_bpf_config")
+
+#endif /* __HID_BPF_HELPERS_H */
diff --git a/drivers/hid/bpf/progs/hid_report_helpers.h b/drivers/hid/bpf/progs/hid_report_helpers.h
new file mode 100644
index 000000000000..9944ff54d31d
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_report_helpers.h
@@ -0,0 +1,2982 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2024 Red Hat, Inc
+ */
+
+// THIS FILE IS GENERATED, DO NOT EDIT
+
+#pragma once
+
+
+/* Macros for composing HID reports.
+ *
+ * HID Fields are added manually to the template, please add to it as needed
+ * for any individual device. The Usage Pages and Usages are generated.
+ *
+ * Some macros have a _i8, _i16, or _i32 suffix. Pick the
+ * right suffix given the passed-in value.
+ */
+
+/*
+ * This macro behaves like static_assert(), failing to
+ * compile if its argument is not true. However, it always
+ * returns 0, which allows using it everywhere an expression
+ * can be used.
+ */
+#define must_be(e, msg_) \
+( \
+ 0 * (int) sizeof( \
+ struct { \
+ _Static_assert(e, msg_); \
+ int ISO_C_forbids_a_struct_with_no_members; \
+ } \
+ ) \
+)
+
+/* Ensure the given value fits within 8/16/32 bits */
+#define i4(v_) (((__u8)(v_) & 0xf) + must_be((v_) >= -0x8 && (v_) <= 0x7, "not a i4"))
+#define i8(v_) ((__u8)(v_) + must_be((v_) >= -0x80 && (v_) <= 0xff, "not a i8/u8"))
+#define i16(v_) ((__u16)(v_) + must_be((v_) >= -0x8000 && (v_) <= 0xffff, "not a i16/u16"))
+#define i32(v_) ((__u32)(v_) + must_be((v_) >= -0x80000000L && (v_) <= 0xffffffffL, \
+ "not a i32/u16"))
+
+/* Split a value across multiple bytes in LE order */
+#define LE16(v_) i16(v_) & 0xff, ((v_) >> 8) & 0xff
+#define LE32(v_) i32(v_) & 0xff, ((v_) >> 8) & 0xff, ((v_) >> 16) & 0xff, ((v_) >> 24) & 0xff
+
+/* Collections require two items in the report descriptor, the start
+ * of the collection (0xa?) and the EndCollection item (0xc?).
+ * This macro provides both, use like this:
+ *
+ * static const __u8 fixed_rdesc[] = {
+ * UsagePage_Generic_Desktop
+ * Usage_GD_Keyboard
+ * CollectionApplication( ← Open the collection
+ * ReportId(3)
+ * LogicalMinimum_i8(0)
+ * LogicalMaximum_i8(1)
+ * // other fields
+ * ) ← End EndCollection
+ *
+ * Collections may be nested.
+ */
+#define Collection(col_, ...) 0xa1, i8(col_), __VA_ARGS__ 0xc0,
+#define CollectionPhysical(...) Collection(0x00, __VA_ARGS__)
+#define CollectionApplication(...) Collection(0x01, __VA_ARGS__)
+#define CollectionLogical(...) Collection(0x02, __VA_ARGS__)
+
+/* See Collections, this macro provides Push and Pop with
+ * elements in between
+ */
+#define PushPop(...) 0xa4, __VA_ARGS__ 0xb4,
+
+/* Arguments to use in bitwise-or for Input, Output, Feature */
+#define Const 0x1
+#define Var 0x2
+#define Arr 0x0
+#define Abs 0x0
+#define Rel 0x4
+#define Null 0x40
+#define Buff 0x0100
+
+/* Use like this: Input(Var|Abs) */
+#define Input(i_) 0x081, i8(i_),
+#define Output(i_) 0x091, i8(i_),
+#define Feature(i_) 0x0b1, i8(i_),
+
+#define Input_i16(i_) 0x082, LE16(i_),
+#define Output_i16(i_) 0x092, LE16(i_),
+#define Feature_i16(i_) 0x0b2, LE16(i_),
+
+#define ReportId(id_) 0x85, i8(id_),
+#define ReportSize(sz_) 0x75, i8(sz_),
+#define ReportCount(cnt_) 0x95, i8(cnt_),
+
+#define LogicalMinimum_i8(min_) 0x15, i8(min_),
+#define LogicalMinimum_i16(min_) 0x16, LE16(min_),
+#define LogicalMinimum_i32(min_) 0x17, LE32(min_),
+
+#define LogicalMaximum_i8(max_) 0x25, i8(max_),
+#define LogicalMaximum_i16(max_) 0x26, LE16(max_),
+#define LogicalMaximum_i32(max_) 0x27, LE32(max_),
+
+#define PhysicalMinimum_i8(min_) 0x35, i8(min_),
+#define PhysicalMinimum_i16(min_) 0x36, LE16(min_),
+#define PhysicalMinimum_i32(min_) 0x37, LE32(min_),
+
+#define PhysicalMaximum_i8(max_) 0x45, i8(max_),
+#define PhysicalMaximum_i16(max_) 0x46, LE16(max_),
+#define PhysicalMaximum_i32(max_) 0x47, LE32(max_),
+
+#define UsageMinimum_i8(min_) 0x19, i8(min_),
+#define UsageMinimum_i16(min_) 0x1a, LE16(min_),
+
+#define UsageMaximum_i8(max_) 0x29, i8(max_),
+#define UsageMaximum_i16(max_) 0x2a, LE16(max_),
+
+#define UsagePage_i8(p_) 0x05, i8(p_),
+#define UsagePage_i16(p_) 0x06, LE16(p_),
+
+#define Usage_i8(u_) 0x09, i8(u_),
+#define Usage_i16(u_) 0x0a, LE16(u_),
+#define Usage_i32(u_) 0x0b, LE32(u_),
+
+#define SILinear 0x1
+#define SIRotation 0x2
+#define EnglishLinear 0x3
+#define EnglishRotation 0x4
+#define cm (SILinear | (0x1 << 4))
+#define rad (SIRotation | (0x1 << 4))
+#define deg (EnglishRotation | (0x1 << 4))
+#define in (EnglishLinear | (0x1 << 4))
+/* Use as Unit(cm) or Unit(rad) or similar.
+ * This macro currently defaults to exponent 1 only, so no
+ * cm^2 or others
+ */
+#define Unit(u_) Unit_i8(u_)
+#define Unit_i8(u_) 0x65, i8(u_),
+#define Unit_i16(u_) 0x66, i16(u_),
+#define Unit_i32(u_) 0x67, i32(u_),
+
+#define UnitExponent(u_) 0x55, i4(u_),
+
+/* A macro to generate a vendor-specific padding-only
+ * report with Report ID 0xac of the given size in bytes.
+ * The size is inclusive of the 1 byte Report ID prefix.
+ *
+ * The kernel discards any HID reports that are larger
+ * than the largest report in a HID report descriptor.
+ * Thus at least one report must have (at least)
+ * the same size as the largest original report from
+ * the device.
+ * The easy way to ensure that is to add this
+ * macro as the last element of your CollectionApplication
+ * other reports can be of any size less than this.
+ *
+ * e.g.
+ * static __u8 fixed_rdesc = [
+ * UsagePage_Generic_Desktop
+ * Usage_GD_Keyboard
+ * CollectionApplication(
+ * ... intended rdesc items go here ...
+ * FixedSizeVendorReport(12)
+ * )
+ * ];
+ *
+ * If the FixedSizeVendorReport is placed outside
+ * a CollectionApplication it will result in
+ * an extra useless evdev node being created.
+ */
+#define FixedSizeVendorReport(bytes_) \
+ UsagePage_Vendor(0xffff) \
+ Usage_i8(0x01) \
+ CollectionPhysical( \
+ ReportId(0xac) \
+ ReportSize(8) \
+ ReportCount((bytes_) - 1) \
+ Input(Const) \
+ )
+
+/* ----- Generated Usage Pages and Usages ------ */
+#define UsagePage_GenericDesktop UsagePage_i8(0x1)
+#define UsagePage_SimulationControls UsagePage_i8(0x2)
+#define UsagePage_VRControls UsagePage_i8(0x3)
+#define UsagePage_SportControls UsagePage_i8(0x4)
+#define UsagePage_GameControls UsagePage_i8(0x5)
+#define UsagePage_GenericDeviceControls UsagePage_i8(0x6)
+#define UsagePage_KeyboardKeypad UsagePage_i8(0x7)
+#define UsagePage_LED UsagePage_i8(0x8)
+#define UsagePage_Button UsagePage_i8(0x9)
+#define UsagePage_Ordinal UsagePage_i8(0xa)
+#define UsagePage_TelephonyDevice UsagePage_i8(0xb)
+#define UsagePage_Consumer UsagePage_i8(0xc)
+#define UsagePage_Digitizers UsagePage_i8(0xd)
+#define UsagePage_Haptics UsagePage_i8(0xe)
+#define UsagePage_PhysicalInputDevice UsagePage_i8(0xf)
+#define UsagePage_Unicode UsagePage_i8(0x10)
+#define UsagePage_SoC UsagePage_i8(0x11)
+#define UsagePage_EyeandHeadTrackers UsagePage_i8(0x12)
+#define UsagePage_AuxiliaryDisplay UsagePage_i8(0x14)
+#define UsagePage_Sensors UsagePage_i8(0x20)
+#define UsagePage_MedicalInstrument UsagePage_i8(0x40)
+#define UsagePage_BrailleDisplay UsagePage_i8(0x41)
+#define UsagePage_LightingAndIllumination UsagePage_i8(0x59)
+#define UsagePage_Monitor UsagePage_i8(0x80)
+#define UsagePage_MonitorEnumerated UsagePage_i8(0x81)
+#define UsagePage_VESAVirtualControls UsagePage_i8(0x82)
+#define UsagePage_Power UsagePage_i8(0x84)
+#define UsagePage_BatterySystem UsagePage_i8(0x85)
+#define UsagePage_BarcodeScanner UsagePage_i8(0x8c)
+#define UsagePage_Scales UsagePage_i8(0x8d)
+#define UsagePage_MagneticStripeReader UsagePage_i8(0x8e)
+#define UsagePage_CameraControl UsagePage_i8(0x90)
+#define UsagePage_Arcade UsagePage_i8(0x91)
+#define UsagePage_FIDOAlliance UsagePage_i16(0xf1d0)
+#define UsagePage_Vendor(u_) \
+ UsagePage_i16((u_) + must_be(((u_) & 0xff00) == 0xff00, "not a 0xff00 vendor page"))
+
+#define Usage_GD_Pointer Usage_i8(0x1)
+#define Usage_GD_Mouse Usage_i8(0x2)
+#define Usage_GD_Joystick Usage_i8(0x4)
+#define Usage_GD_Gamepad Usage_i8(0x5)
+#define Usage_GD_Keyboard Usage_i8(0x6)
+#define Usage_GD_Keypad Usage_i8(0x7)
+#define Usage_GD_MultiaxisController Usage_i8(0x8)
+#define Usage_GD_TabletPCSystemControls Usage_i8(0x9)
+#define Usage_GD_WaterCoolingDevice Usage_i8(0xa)
+#define Usage_GD_ComputerChassisDevice Usage_i8(0xb)
+#define Usage_GD_WirelessRadioControls Usage_i8(0xc)
+#define Usage_GD_PortableDeviceControl Usage_i8(0xd)
+#define Usage_GD_SystemMultiAxisController Usage_i8(0xe)
+#define Usage_GD_SpatialController Usage_i8(0xf)
+#define Usage_GD_AssistiveControl Usage_i8(0x10)
+#define Usage_GD_DeviceDock Usage_i8(0x11)
+#define Usage_GD_DockableDevice Usage_i8(0x12)
+#define Usage_GD_CallStateManagementControl Usage_i8(0x13)
+#define Usage_GD_X Usage_i8(0x30)
+#define Usage_GD_Y Usage_i8(0x31)
+#define Usage_GD_Z Usage_i8(0x32)
+#define Usage_GD_Rx Usage_i8(0x33)
+#define Usage_GD_Ry Usage_i8(0x34)
+#define Usage_GD_Rz Usage_i8(0x35)
+#define Usage_GD_Slider Usage_i8(0x36)
+#define Usage_GD_Dial Usage_i8(0x37)
+#define Usage_GD_Wheel Usage_i8(0x38)
+#define Usage_GD_HatSwitch Usage_i8(0x39)
+#define Usage_GD_CountedBuffer Usage_i8(0x3a)
+#define Usage_GD_ByteCount Usage_i8(0x3b)
+#define Usage_GD_MotionWakeup Usage_i8(0x3c)
+#define Usage_GD_Start Usage_i8(0x3d)
+#define Usage_GD_Select Usage_i8(0x3e)
+#define Usage_GD_Vx Usage_i8(0x40)
+#define Usage_GD_Vy Usage_i8(0x41)
+#define Usage_GD_Vz Usage_i8(0x42)
+#define Usage_GD_Vbrx Usage_i8(0x43)
+#define Usage_GD_Vbry Usage_i8(0x44)
+#define Usage_GD_Vbrz Usage_i8(0x45)
+#define Usage_GD_Vno Usage_i8(0x46)
+#define Usage_GD_FeatureNotification Usage_i8(0x47)
+#define Usage_GD_ResolutionMultiplier Usage_i8(0x48)
+#define Usage_GD_Qx Usage_i8(0x49)
+#define Usage_GD_Qy Usage_i8(0x4a)
+#define Usage_GD_Qz Usage_i8(0x4b)
+#define Usage_GD_Qw Usage_i8(0x4c)
+#define Usage_GD_SystemControl Usage_i8(0x80)
+#define Usage_GD_SystemPowerDown Usage_i8(0x81)
+#define Usage_GD_SystemSleep Usage_i8(0x82)
+#define Usage_GD_SystemWakeUp Usage_i8(0x83)
+#define Usage_GD_SystemContextMenu Usage_i8(0x84)
+#define Usage_GD_SystemMainMenu Usage_i8(0x85)
+#define Usage_GD_SystemAppMenu Usage_i8(0x86)
+#define Usage_GD_SystemMenuHelp Usage_i8(0x87)
+#define Usage_GD_SystemMenuExit Usage_i8(0x88)
+#define Usage_GD_SystemMenuSelect Usage_i8(0x89)
+#define Usage_GD_SystemMenuRight Usage_i8(0x8a)
+#define Usage_GD_SystemMenuLeft Usage_i8(0x8b)
+#define Usage_GD_SystemMenuUp Usage_i8(0x8c)
+#define Usage_GD_SystemMenuDown Usage_i8(0x8d)
+#define Usage_GD_SystemColdRestart Usage_i8(0x8e)
+#define Usage_GD_SystemWarmRestart Usage_i8(0x8f)
+#define Usage_GD_DpadUp Usage_i8(0x90)
+#define Usage_GD_DpadDown Usage_i8(0x91)
+#define Usage_GD_DpadRight Usage_i8(0x92)
+#define Usage_GD_DpadLeft Usage_i8(0x93)
+#define Usage_GD_IndexTrigger Usage_i8(0x94)
+#define Usage_GD_PalmTrigger Usage_i8(0x95)
+#define Usage_GD_Thumbstick Usage_i8(0x96)
+#define Usage_GD_SystemFunctionShift Usage_i8(0x97)
+#define Usage_GD_SystemFunctionShiftLock Usage_i8(0x98)
+#define Usage_GD_SystemFunctionShiftLockIndicator Usage_i8(0x99)
+#define Usage_GD_SystemDismissNotification Usage_i8(0x9a)
+#define Usage_GD_SystemDoNotDisturb Usage_i8(0x9b)
+#define Usage_GD_SystemDock Usage_i8(0xa0)
+#define Usage_GD_SystemUndock Usage_i8(0xa1)
+#define Usage_GD_SystemSetup Usage_i8(0xa2)
+#define Usage_GD_SystemBreak Usage_i8(0xa3)
+#define Usage_GD_SystemDebuggerBreak Usage_i8(0xa4)
+#define Usage_GD_ApplicationBreak Usage_i8(0xa5)
+#define Usage_GD_ApplicationDebuggerBreak Usage_i8(0xa6)
+#define Usage_GD_SystemSpeakerMute Usage_i8(0xa7)
+#define Usage_GD_SystemHibernate Usage_i8(0xa8)
+#define Usage_GD_SystemMicrophoneMute Usage_i8(0xa9)
+#define Usage_GD_SystemAccessibilityBinding Usage_i8(0xaa)
+#define Usage_GD_SystemDisplayInvert Usage_i8(0xb0)
+#define Usage_GD_SystemDisplayInternal Usage_i8(0xb1)
+#define Usage_GD_SystemDisplayExternal Usage_i8(0xb2)
+#define Usage_GD_SystemDisplayBoth Usage_i8(0xb3)
+#define Usage_GD_SystemDisplayDual Usage_i8(0xb4)
+#define Usage_GD_SystemDisplayToggleIntExtMode Usage_i8(0xb5)
+#define Usage_GD_SystemDisplaySwapPrimarySecondary Usage_i8(0xb6)
+#define Usage_GD_SystemDisplayToggleLCDAutoscale Usage_i8(0xb7)
+#define Usage_GD_SensorZone Usage_i8(0xc0)
+#define Usage_GD_RPM Usage_i8(0xc1)
+#define Usage_GD_CoolantLevel Usage_i8(0xc2)
+#define Usage_GD_CoolantCriticalLevel Usage_i8(0xc3)
+#define Usage_GD_CoolantPump Usage_i8(0xc4)
+#define Usage_GD_ChassisEnclosure Usage_i8(0xc5)
+#define Usage_GD_WirelessRadioButton Usage_i8(0xc6)
+#define Usage_GD_WirelessRadioLED Usage_i8(0xc7)
+#define Usage_GD_WirelessRadioSliderSwitch Usage_i8(0xc8)
+#define Usage_GD_SystemDisplayRotationLockButton Usage_i8(0xc9)
+#define Usage_GD_SystemDisplayRotationLockSliderSwitch Usage_i8(0xca)
+#define Usage_GD_ControlEnable Usage_i8(0xcb)
+#define Usage_GD_DockableDeviceUniqueID Usage_i8(0xd0)
+#define Usage_GD_DockableDeviceVendorID Usage_i8(0xd1)
+#define Usage_GD_DockableDevicePrimaryUsagePage Usage_i8(0xd2)
+#define Usage_GD_DockableDevicePrimaryUsageID Usage_i8(0xd3)
+#define Usage_GD_DockableDeviceDockingState Usage_i8(0xd4)
+#define Usage_GD_DockableDeviceDisplayOcclusion Usage_i8(0xd5)
+#define Usage_GD_DockableDeviceObjectType Usage_i8(0xd6)
+#define Usage_GD_CallActiveLED Usage_i8(0xe0)
+#define Usage_GD_CallMuteToggle Usage_i8(0xe1)
+#define Usage_GD_CallMuteLED Usage_i8(0xe2)
+#define Usage_SC_FlightSimulationDevice Usage_i8(0x1)
+#define Usage_SC_AutomobileSimulationDevice Usage_i8(0x2)
+#define Usage_SC_TankSimulationDevice Usage_i8(0x3)
+#define Usage_SC_SpaceshipSimulationDevice Usage_i8(0x4)
+#define Usage_SC_SubmarineSimulationDevice Usage_i8(0x5)
+#define Usage_SC_SailingSimulationDevice Usage_i8(0x6)
+#define Usage_SC_MotorcycleSimulationDevice Usage_i8(0x7)
+#define Usage_SC_SportsSimulationDevice Usage_i8(0x8)
+#define Usage_SC_AirplaneSimulationDevice Usage_i8(0x9)
+#define Usage_SC_HelicopterSimulationDevice Usage_i8(0xa)
+#define Usage_SC_MagicCarpetSimulationDevice Usage_i8(0xb)
+#define Usage_SC_BicycleSimulationDevice Usage_i8(0xc)
+#define Usage_SC_FlightControlStick Usage_i8(0x20)
+#define Usage_SC_FlightStick Usage_i8(0x21)
+#define Usage_SC_CyclicControl Usage_i8(0x22)
+#define Usage_SC_CyclicTrim Usage_i8(0x23)
+#define Usage_SC_FlightYoke Usage_i8(0x24)
+#define Usage_SC_TrackControl Usage_i8(0x25)
+#define Usage_SC_Aileron Usage_i8(0xb0)
+#define Usage_SC_AileronTrim Usage_i8(0xb1)
+#define Usage_SC_AntiTorqueControl Usage_i8(0xb2)
+#define Usage_SC_AutopilotEnable Usage_i8(0xb3)
+#define Usage_SC_ChaffRelease Usage_i8(0xb4)
+#define Usage_SC_CollectiveControl Usage_i8(0xb5)
+#define Usage_SC_DiveBrake Usage_i8(0xb6)
+#define Usage_SC_ElectronicCountermeasures Usage_i8(0xb7)
+#define Usage_SC_Elevator Usage_i8(0xb8)
+#define Usage_SC_ElevatorTrim Usage_i8(0xb9)
+#define Usage_SC_Rudder Usage_i8(0xba)
+#define Usage_SC_Throttle Usage_i8(0xbb)
+#define Usage_SC_FlightCommunications Usage_i8(0xbc)
+#define Usage_SC_FlareRelease Usage_i8(0xbd)
+#define Usage_SC_LandingGear Usage_i8(0xbe)
+#define Usage_SC_ToeBrake Usage_i8(0xbf)
+#define Usage_SC_Trigger Usage_i8(0xc0)
+#define Usage_SC_WeaponsArm Usage_i8(0xc1)
+#define Usage_SC_WeaponsSelect Usage_i8(0xc2)
+#define Usage_SC_WingFlaps Usage_i8(0xc3)
+#define Usage_SC_Accelerator Usage_i8(0xc4)
+#define Usage_SC_Brake Usage_i8(0xc5)
+#define Usage_SC_Clutch Usage_i8(0xc6)
+#define Usage_SC_Shifter Usage_i8(0xc7)
+#define Usage_SC_Steering Usage_i8(0xc8)
+#define Usage_SC_TurretDirection Usage_i8(0xc9)
+#define Usage_SC_BarrelElevation Usage_i8(0xca)
+#define Usage_SC_DivePlane Usage_i8(0xcb)
+#define Usage_SC_Ballast Usage_i8(0xcc)
+#define Usage_SC_BicycleCrank Usage_i8(0xcd)
+#define Usage_SC_HandleBars Usage_i8(0xce)
+#define Usage_SC_FrontBrake Usage_i8(0xcf)
+#define Usage_SC_RearBrake Usage_i8(0xd0)
+#define Usage_VRC_Belt Usage_i8(0x1)
+#define Usage_VRC_BodySuit Usage_i8(0x2)
+#define Usage_VRC_Flexor Usage_i8(0x3)
+#define Usage_VRC_Glove Usage_i8(0x4)
+#define Usage_VRC_HeadTracker Usage_i8(0x5)
+#define Usage_VRC_HeadMountedDisplay Usage_i8(0x6)
+#define Usage_VRC_HandTracker Usage_i8(0x7)
+#define Usage_VRC_Oculometer Usage_i8(0x8)
+#define Usage_VRC_Vest Usage_i8(0x9)
+#define Usage_VRC_AnimatronicDevice Usage_i8(0xa)
+#define Usage_VRC_StereoEnable Usage_i8(0x20)
+#define Usage_VRC_DisplayEnable Usage_i8(0x21)
+#define Usage_SC_BaseballBat Usage_i8(0x1)
+#define Usage_SC_GolfClub Usage_i8(0x2)
+#define Usage_SC_RowingMachine Usage_i8(0x3)
+#define Usage_SC_Treadmill Usage_i8(0x4)
+#define Usage_SC_Oar Usage_i8(0x30)
+#define Usage_SC_Slope Usage_i8(0x31)
+#define Usage_SC_Rate Usage_i8(0x32)
+#define Usage_SC_StickSpeed Usage_i8(0x33)
+#define Usage_SC_StickFaceAngle Usage_i8(0x34)
+#define Usage_SC_StickHeelToe Usage_i8(0x35)
+#define Usage_SC_StickFollowThrough Usage_i8(0x36)
+#define Usage_SC_StickTempo Usage_i8(0x37)
+#define Usage_SC_StickType Usage_i8(0x38)
+#define Usage_SC_StickHeight Usage_i8(0x39)
+#define Usage_SC_Putter Usage_i8(0x50)
+#define Usage_SC_OneIron Usage_i8(0x51)
+#define Usage_SC_TwoIron Usage_i8(0x52)
+#define Usage_SC_ThreeIron Usage_i8(0x53)
+#define Usage_SC_FourIron Usage_i8(0x54)
+#define Usage_SC_FiveIron Usage_i8(0x55)
+#define Usage_SC_SixIron Usage_i8(0x56)
+#define Usage_SC_SevenIron Usage_i8(0x57)
+#define Usage_SC_EightIron Usage_i8(0x58)
+#define Usage_SC_NineIron Usage_i8(0x59)
+#define Usage_SC_One0Iron Usage_i8(0x5a)
+#define Usage_SC_One1Iron Usage_i8(0x5b)
+#define Usage_SC_SandWedge Usage_i8(0x5c)
+#define Usage_SC_LoftWedge Usage_i8(0x5d)
+#define Usage_SC_PowerWedge Usage_i8(0x5e)
+#define Usage_SC_OneWood Usage_i8(0x5f)
+#define Usage_SC_ThreeWood Usage_i8(0x60)
+#define Usage_SC_FiveWood Usage_i8(0x61)
+#define Usage_SC_SevenWood Usage_i8(0x62)
+#define Usage_SC_NineWood Usage_i8(0x63)
+#define Usage_GC_ThreeDGameController Usage_i8(0x1)
+#define Usage_GC_PinballDevice Usage_i8(0x2)
+#define Usage_GC_GunDevice Usage_i8(0x3)
+#define Usage_GC_PointofView Usage_i8(0x20)
+#define Usage_GC_TurnRightLeft Usage_i8(0x21)
+#define Usage_GC_PitchForwardBackward Usage_i8(0x22)
+#define Usage_GC_RollRightLeft Usage_i8(0x23)
+#define Usage_GC_MoveRightLeft Usage_i8(0x24)
+#define Usage_GC_MoveForwardBackward Usage_i8(0x25)
+#define Usage_GC_MoveUpDown Usage_i8(0x26)
+#define Usage_GC_LeanRightLeft Usage_i8(0x27)
+#define Usage_GC_LeanForwardBackward Usage_i8(0x28)
+#define Usage_GC_HeightofPOV Usage_i8(0x29)
+#define Usage_GC_Flipper Usage_i8(0x2a)
+#define Usage_GC_SecondaryFlipper Usage_i8(0x2b)
+#define Usage_GC_Bump Usage_i8(0x2c)
+#define Usage_GC_NewGame Usage_i8(0x2d)
+#define Usage_GC_ShootBall Usage_i8(0x2e)
+#define Usage_GC_Player Usage_i8(0x2f)
+#define Usage_GC_GunBolt Usage_i8(0x30)
+#define Usage_GC_GunClip Usage_i8(0x31)
+#define Usage_GC_GunSelector Usage_i8(0x32)
+#define Usage_GC_GunSingleShot Usage_i8(0x33)
+#define Usage_GC_GunBurst Usage_i8(0x34)
+#define Usage_GC_GunAutomatic Usage_i8(0x35)
+#define Usage_GC_GunSafety Usage_i8(0x36)
+#define Usage_GC_GamepadFireJump Usage_i8(0x37)
+#define Usage_GC_GamepadTrigger Usage_i8(0x39)
+#define Usage_GC_FormfittingGamepad Usage_i8(0x3a)
+#define Usage_GDC_BackgroundNonuserControls Usage_i8(0x1)
+#define Usage_GDC_BatteryStrength Usage_i8(0x20)
+#define Usage_GDC_WirelessChannel Usage_i8(0x21)
+#define Usage_GDC_WirelessID Usage_i8(0x22)
+#define Usage_GDC_DiscoverWirelessControl Usage_i8(0x23)
+#define Usage_GDC_SecurityCodeCharacterEntered Usage_i8(0x24)
+#define Usage_GDC_SecurityCodeCharacterErased Usage_i8(0x25)
+#define Usage_GDC_SecurityCodeCleared Usage_i8(0x26)
+#define Usage_GDC_SequenceID Usage_i8(0x27)
+#define Usage_GDC_SequenceIDReset Usage_i8(0x28)
+#define Usage_GDC_RFSignalStrength Usage_i8(0x29)
+#define Usage_GDC_SoftwareVersion Usage_i8(0x2a)
+#define Usage_GDC_ProtocolVersion Usage_i8(0x2b)
+#define Usage_GDC_HardwareVersion Usage_i8(0x2c)
+#define Usage_GDC_Major Usage_i8(0x2d)
+#define Usage_GDC_Minor Usage_i8(0x2e)
+#define Usage_GDC_Revision Usage_i8(0x2f)
+#define Usage_GDC_Handedness Usage_i8(0x30)
+#define Usage_GDC_EitherHand Usage_i8(0x31)
+#define Usage_GDC_LeftHand Usage_i8(0x32)
+#define Usage_GDC_RightHand Usage_i8(0x33)
+#define Usage_GDC_BothHands Usage_i8(0x34)
+#define Usage_GDC_GripPoseOffset Usage_i8(0x40)
+#define Usage_GDC_PointerPoseOffset Usage_i8(0x41)
+#define Usage_KK_ErrorRollOver Usage_i8(0x1)
+#define Usage_KK_POSTFail Usage_i8(0x2)
+#define Usage_KK_ErrorUndefined Usage_i8(0x3)
+#define Usage_KK_KeyboardA Usage_i8(0x4)
+#define Usage_KK_KeyboardB Usage_i8(0x5)
+#define Usage_KK_KeyboardC Usage_i8(0x6)
+#define Usage_KK_KeyboardD Usage_i8(0x7)
+#define Usage_KK_KeyboardE Usage_i8(0x8)
+#define Usage_KK_KeyboardF Usage_i8(0x9)
+#define Usage_KK_KeyboardG Usage_i8(0xa)
+#define Usage_KK_KeyboardH Usage_i8(0xb)
+#define Usage_KK_KeyboardI Usage_i8(0xc)
+#define Usage_KK_KeyboardJ Usage_i8(0xd)
+#define Usage_KK_KeyboardK Usage_i8(0xe)
+#define Usage_KK_KeyboardL Usage_i8(0xf)
+#define Usage_KK_KeyboardM Usage_i8(0x10)
+#define Usage_KK_KeyboardN Usage_i8(0x11)
+#define Usage_KK_KeyboardO Usage_i8(0x12)
+#define Usage_KK_KeyboardP Usage_i8(0x13)
+#define Usage_KK_KeyboardQ Usage_i8(0x14)
+#define Usage_KK_KeyboardR Usage_i8(0x15)
+#define Usage_KK_KeyboardS Usage_i8(0x16)
+#define Usage_KK_KeyboardT Usage_i8(0x17)
+#define Usage_KK_KeyboardU Usage_i8(0x18)
+#define Usage_KK_KeyboardV Usage_i8(0x19)
+#define Usage_KK_KeyboardW Usage_i8(0x1a)
+#define Usage_KK_KeyboardX Usage_i8(0x1b)
+#define Usage_KK_KeyboardY Usage_i8(0x1c)
+#define Usage_KK_KeyboardZ Usage_i8(0x1d)
+#define Usage_KK_Keyboard1andBang Usage_i8(0x1e)
+#define Usage_KK_Keyboard2andAt Usage_i8(0x1f)
+#define Usage_KK_Keyboard3andHash Usage_i8(0x20)
+#define Usage_KK_Keyboard4andDollar Usage_i8(0x21)
+#define Usage_KK_Keyboard5andPercent Usage_i8(0x22)
+#define Usage_KK_Keyboard6andCaret Usage_i8(0x23)
+#define Usage_KK_Keyboard7andAmpersand Usage_i8(0x24)
+#define Usage_KK_Keyboard8andStar Usage_i8(0x25)
+#define Usage_KK_Keyboard9andLeftBracket Usage_i8(0x26)
+#define Usage_KK_Keyboard0andRightBracket Usage_i8(0x27)
+#define Usage_KK_KeyboardReturnEnter Usage_i8(0x28)
+#define Usage_KK_KeyboardEscape Usage_i8(0x29)
+#define Usage_KK_KeyboardDelete Usage_i8(0x2a)
+#define Usage_KK_KeyboardTab Usage_i8(0x2b)
+#define Usage_KK_KeyboardSpacebar Usage_i8(0x2c)
+#define Usage_KK_KeyboardDashandUnderscore Usage_i8(0x2d)
+#define Usage_KK_KeyboardEqualsandPlus Usage_i8(0x2e)
+#define Usage_KK_KeyboardLeftBrace Usage_i8(0x2f)
+#define Usage_KK_KeyboardRightBrace Usage_i8(0x30)
+#define Usage_KK_KeyboardBackslashandPipe Usage_i8(0x31)
+#define Usage_KK_KeyboardNonUSHashandTilde Usage_i8(0x32)
+#define Usage_KK_KeyboardSemiColonandColon Usage_i8(0x33)
+#define Usage_KK_KeyboardLeftAposandDouble Usage_i8(0x34)
+#define Usage_KK_KeyboardGraveAccentandTilde Usage_i8(0x35)
+#define Usage_KK_KeyboardCommaandLessThan Usage_i8(0x36)
+#define Usage_KK_KeyboardPeriodandGreaterThan Usage_i8(0x37)
+#define Usage_KK_KeyboardForwardSlashandQuestionMark Usage_i8(0x38)
+#define Usage_KK_KeyboardCapsLock Usage_i8(0x39)
+#define Usage_KK_KeyboardF1 Usage_i8(0x3a)
+#define Usage_KK_KeyboardF2 Usage_i8(0x3b)
+#define Usage_KK_KeyboardF3 Usage_i8(0x3c)
+#define Usage_KK_KeyboardF4 Usage_i8(0x3d)
+#define Usage_KK_KeyboardF5 Usage_i8(0x3e)
+#define Usage_KK_KeyboardF6 Usage_i8(0x3f)
+#define Usage_KK_KeyboardF7 Usage_i8(0x40)
+#define Usage_KK_KeyboardF8 Usage_i8(0x41)
+#define Usage_KK_KeyboardF9 Usage_i8(0x42)
+#define Usage_KK_KeyboardF10 Usage_i8(0x43)
+#define Usage_KK_KeyboardF11 Usage_i8(0x44)
+#define Usage_KK_KeyboardF12 Usage_i8(0x45)
+#define Usage_KK_KeyboardPrintScreen Usage_i8(0x46)
+#define Usage_KK_KeyboardScrollLock Usage_i8(0x47)
+#define Usage_KK_KeyboardPause Usage_i8(0x48)
+#define Usage_KK_KeyboardInsert Usage_i8(0x49)
+#define Usage_KK_KeyboardHome Usage_i8(0x4a)
+#define Usage_KK_KeyboardPageUp Usage_i8(0x4b)
+#define Usage_KK_KeyboardDeleteForward Usage_i8(0x4c)
+#define Usage_KK_KeyboardEnd Usage_i8(0x4d)
+#define Usage_KK_KeyboardPageDown Usage_i8(0x4e)
+#define Usage_KK_KeyboardRightArrow Usage_i8(0x4f)
+#define Usage_KK_KeyboardLeftArrow Usage_i8(0x50)
+#define Usage_KK_KeyboardDownArrow Usage_i8(0x51)
+#define Usage_KK_KeyboardUpArrow Usage_i8(0x52)
+#define Usage_KK_KeypadNumLockandClear Usage_i8(0x53)
+#define Usage_KK_KeypadForwardSlash Usage_i8(0x54)
+#define Usage_KK_KeypadStar Usage_i8(0x55)
+#define Usage_KK_KeypadDash Usage_i8(0x56)
+#define Usage_KK_KeypadPlus Usage_i8(0x57)
+#define Usage_KK_KeypadENTER Usage_i8(0x58)
+#define Usage_KK_Keypad1andEnd Usage_i8(0x59)
+#define Usage_KK_Keypad2andDownArrow Usage_i8(0x5a)
+#define Usage_KK_Keypad3andPageDn Usage_i8(0x5b)
+#define Usage_KK_Keypad4andLeftArrow Usage_i8(0x5c)
+#define Usage_KK_Keypad5 Usage_i8(0x5d)
+#define Usage_KK_Keypad6andRightArrow Usage_i8(0x5e)
+#define Usage_KK_Keypad7andHome Usage_i8(0x5f)
+#define Usage_KK_Keypad8andUpArrow Usage_i8(0x60)
+#define Usage_KK_Keypad9andPageUp Usage_i8(0x61)
+#define Usage_KK_Keypad0andInsert Usage_i8(0x62)
+#define Usage_KK_KeypadPeriodandDelete Usage_i8(0x63)
+#define Usage_KK_KeyboardNonUSBackslashandPipe Usage_i8(0x64)
+#define Usage_KK_KeyboardApplication Usage_i8(0x65)
+#define Usage_KK_KeyboardPower Usage_i8(0x66)
+#define Usage_KK_KeypadEquals Usage_i8(0x67)
+#define Usage_KK_KeyboardF13 Usage_i8(0x68)
+#define Usage_KK_KeyboardF14 Usage_i8(0x69)
+#define Usage_KK_KeyboardF15 Usage_i8(0x6a)
+#define Usage_KK_KeyboardF16 Usage_i8(0x6b)
+#define Usage_KK_KeyboardF17 Usage_i8(0x6c)
+#define Usage_KK_KeyboardF18 Usage_i8(0x6d)
+#define Usage_KK_KeyboardF19 Usage_i8(0x6e)
+#define Usage_KK_KeyboardF20 Usage_i8(0x6f)
+#define Usage_KK_KeyboardF21 Usage_i8(0x70)
+#define Usage_KK_KeyboardF22 Usage_i8(0x71)
+#define Usage_KK_KeyboardF23 Usage_i8(0x72)
+#define Usage_KK_KeyboardF24 Usage_i8(0x73)
+#define Usage_KK_KeyboardExecute Usage_i8(0x74)
+#define Usage_KK_KeyboardHelp Usage_i8(0x75)
+#define Usage_KK_KeyboardMenu Usage_i8(0x76)
+#define Usage_KK_KeyboardSelect Usage_i8(0x77)
+#define Usage_KK_KeyboardStop Usage_i8(0x78)
+#define Usage_KK_KeyboardAgain Usage_i8(0x79)
+#define Usage_KK_KeyboardUndo Usage_i8(0x7a)
+#define Usage_KK_KeyboardCut Usage_i8(0x7b)
+#define Usage_KK_KeyboardCopy Usage_i8(0x7c)
+#define Usage_KK_KeyboardPaste Usage_i8(0x7d)
+#define Usage_KK_KeyboardFind Usage_i8(0x7e)
+#define Usage_KK_KeyboardMute Usage_i8(0x7f)
+#define Usage_KK_KeyboardVolumeUp Usage_i8(0x80)
+#define Usage_KK_KeyboardVolumeDown Usage_i8(0x81)
+#define Usage_KK_KeyboardLockingCapsLock Usage_i8(0x82)
+#define Usage_KK_KeyboardLockingNumLock Usage_i8(0x83)
+#define Usage_KK_KeyboardLockingScrollLock Usage_i8(0x84)
+#define Usage_KK_KeypadComma Usage_i8(0x85)
+#define Usage_KK_KeypadEqualSign Usage_i8(0x86)
+#define Usage_KK_KeyboardInternational1 Usage_i8(0x87)
+#define Usage_KK_KeyboardInternational2 Usage_i8(0x88)
+#define Usage_KK_KeyboardInternational3 Usage_i8(0x89)
+#define Usage_KK_KeyboardInternational4 Usage_i8(0x8a)
+#define Usage_KK_KeyboardInternational5 Usage_i8(0x8b)
+#define Usage_KK_KeyboardInternational6 Usage_i8(0x8c)
+#define Usage_KK_KeyboardInternational7 Usage_i8(0x8d)
+#define Usage_KK_KeyboardInternational8 Usage_i8(0x8e)
+#define Usage_KK_KeyboardInternational9 Usage_i8(0x8f)
+#define Usage_KK_KeyboardLANG1 Usage_i8(0x90)
+#define Usage_KK_KeyboardLANG2 Usage_i8(0x91)
+#define Usage_KK_KeyboardLANG3 Usage_i8(0x92)
+#define Usage_KK_KeyboardLANG4 Usage_i8(0x93)
+#define Usage_KK_KeyboardLANG5 Usage_i8(0x94)
+#define Usage_KK_KeyboardLANG6 Usage_i8(0x95)
+#define Usage_KK_KeyboardLANG7 Usage_i8(0x96)
+#define Usage_KK_KeyboardLANG8 Usage_i8(0x97)
+#define Usage_KK_KeyboardLANG9 Usage_i8(0x98)
+#define Usage_KK_KeyboardAlternateErase Usage_i8(0x99)
+#define Usage_KK_KeyboardSysReqAttention Usage_i8(0x9a)
+#define Usage_KK_KeyboardCancel Usage_i8(0x9b)
+#define Usage_KK_KeyboardClear Usage_i8(0x9c)
+#define Usage_KK_KeyboardPrior Usage_i8(0x9d)
+#define Usage_KK_KeyboardReturn Usage_i8(0x9e)
+#define Usage_KK_KeyboardSeparator Usage_i8(0x9f)
+#define Usage_KK_KeyboardOut Usage_i8(0xa0)
+#define Usage_KK_KeyboardOper Usage_i8(0xa1)
+#define Usage_KK_KeyboardClearAgain Usage_i8(0xa2)
+#define Usage_KK_KeyboardCrSelProps Usage_i8(0xa3)
+#define Usage_KK_KeyboardExSel Usage_i8(0xa4)
+#define Usage_KK_KeypadDouble0 Usage_i8(0xb0)
+#define Usage_KK_KeypadTriple0 Usage_i8(0xb1)
+#define Usage_KK_ThousandsSeparator Usage_i8(0xb2)
+#define Usage_KK_DecimalSeparator Usage_i8(0xb3)
+#define Usage_KK_CurrencyUnit Usage_i8(0xb4)
+#define Usage_KK_CurrencySubunit Usage_i8(0xb5)
+#define Usage_KK_KeypadLeftBracket Usage_i8(0xb6)
+#define Usage_KK_KeypadRightBracket Usage_i8(0xb7)
+#define Usage_KK_KeypadLeftBrace Usage_i8(0xb8)
+#define Usage_KK_KeypadRightBrace Usage_i8(0xb9)
+#define Usage_KK_KeypadTab Usage_i8(0xba)
+#define Usage_KK_KeypadBackspace Usage_i8(0xbb)
+#define Usage_KK_KeypadA Usage_i8(0xbc)
+#define Usage_KK_KeypadB Usage_i8(0xbd)
+#define Usage_KK_KeypadC Usage_i8(0xbe)
+#define Usage_KK_KeypadD Usage_i8(0xbf)
+#define Usage_KK_KeypadE Usage_i8(0xc0)
+#define Usage_KK_KeypadF Usage_i8(0xc1)
+#define Usage_KK_KeypadXOR Usage_i8(0xc2)
+#define Usage_KK_KeypadCaret Usage_i8(0xc3)
+#define Usage_KK_KeypadPercentage Usage_i8(0xc4)
+#define Usage_KK_KeypadLess Usage_i8(0xc5)
+#define Usage_KK_KeypadGreater Usage_i8(0xc6)
+#define Usage_KK_KeypadAmpersand Usage_i8(0xc7)
+#define Usage_KK_KeypadDoubleAmpersand Usage_i8(0xc8)
+#define Usage_KK_KeypadBar Usage_i8(0xc9)
+#define Usage_KK_KeypadDoubleBar Usage_i8(0xca)
+#define Usage_KK_KeypadColon Usage_i8(0xcb)
+#define Usage_KK_KeypadHash Usage_i8(0xcc)
+#define Usage_KK_KeypadSpace Usage_i8(0xcd)
+#define Usage_KK_KeypadAt Usage_i8(0xce)
+#define Usage_KK_KeypadBang Usage_i8(0xcf)
+#define Usage_KK_KeypadMemoryStore Usage_i8(0xd0)
+#define Usage_KK_KeypadMemoryRecall Usage_i8(0xd1)
+#define Usage_KK_KeypadMemoryClear Usage_i8(0xd2)
+#define Usage_KK_KeypadMemoryAdd Usage_i8(0xd3)
+#define Usage_KK_KeypadMemorySubtract Usage_i8(0xd4)
+#define Usage_KK_KeypadMemoryMultiply Usage_i8(0xd5)
+#define Usage_KK_KeypadMemoryDivide Usage_i8(0xd6)
+#define Usage_KK_KeypadPlusMinus Usage_i8(0xd7)
+#define Usage_KK_KeypadClear Usage_i8(0xd8)
+#define Usage_KK_KeypadClearEntry Usage_i8(0xd9)
+#define Usage_KK_KeypadBinary Usage_i8(0xda)
+#define Usage_KK_KeypadOctal Usage_i8(0xdb)
+#define Usage_KK_KeypadDecimal Usage_i8(0xdc)
+#define Usage_KK_KeypadHexadecimal Usage_i8(0xdd)
+#define Usage_KK_KeyboardLeftControl Usage_i8(0xe0)
+#define Usage_KK_KeyboardLeftShift Usage_i8(0xe1)
+#define Usage_KK_KeyboardLeftAlt Usage_i8(0xe2)
+#define Usage_KK_KeyboardLeftGUI Usage_i8(0xe3)
+#define Usage_KK_KeyboardRightControl Usage_i8(0xe4)
+#define Usage_KK_KeyboardRightShift Usage_i8(0xe5)
+#define Usage_KK_KeyboardRightAlt Usage_i8(0xe6)
+#define Usage_KK_KeyboardRightGUI Usage_i8(0xe7)
+#define Usage_LED_NumLock Usage_i8(0x1)
+#define Usage_LED_CapsLock Usage_i8(0x2)
+#define Usage_LED_ScrollLock Usage_i8(0x3)
+#define Usage_LED_Compose Usage_i8(0x4)
+#define Usage_LED_Kana Usage_i8(0x5)
+#define Usage_LED_Power Usage_i8(0x6)
+#define Usage_LED_Shift Usage_i8(0x7)
+#define Usage_LED_DoNotDisturb Usage_i8(0x8)
+#define Usage_LED_Mute Usage_i8(0x9)
+#define Usage_LED_ToneEnable Usage_i8(0xa)
+#define Usage_LED_HighCutFilter Usage_i8(0xb)
+#define Usage_LED_LowCutFilter Usage_i8(0xc)
+#define Usage_LED_EqualizerEnable Usage_i8(0xd)
+#define Usage_LED_SoundFieldOn Usage_i8(0xe)
+#define Usage_LED_SurroundOn Usage_i8(0xf)
+#define Usage_LED_Repeat Usage_i8(0x10)
+#define Usage_LED_Stereo Usage_i8(0x11)
+#define Usage_LED_SamplingRateDetect Usage_i8(0x12)
+#define Usage_LED_Spinning Usage_i8(0x13)
+#define Usage_LED_CAV Usage_i8(0x14)
+#define Usage_LED_CLV Usage_i8(0x15)
+#define Usage_LED_RecordingFormatDetect Usage_i8(0x16)
+#define Usage_LED_OffHook Usage_i8(0x17)
+#define Usage_LED_Ring Usage_i8(0x18)
+#define Usage_LED_MessageWaiting Usage_i8(0x19)
+#define Usage_LED_DataMode Usage_i8(0x1a)
+#define Usage_LED_BatteryOperation Usage_i8(0x1b)
+#define Usage_LED_BatteryOK Usage_i8(0x1c)
+#define Usage_LED_BatteryLow Usage_i8(0x1d)
+#define Usage_LED_Speaker Usage_i8(0x1e)
+#define Usage_LED_Headset Usage_i8(0x1f)
+#define Usage_LED_Hold Usage_i8(0x20)
+#define Usage_LED_Microphone Usage_i8(0x21)
+#define Usage_LED_Coverage Usage_i8(0x22)
+#define Usage_LED_NightMode Usage_i8(0x23)
+#define Usage_LED_SendCalls Usage_i8(0x24)
+#define Usage_LED_CallPickup Usage_i8(0x25)
+#define Usage_LED_Conference Usage_i8(0x26)
+#define Usage_LED_Standby Usage_i8(0x27)
+#define Usage_LED_CameraOn Usage_i8(0x28)
+#define Usage_LED_CameraOff Usage_i8(0x29)
+#define Usage_LED_OnLine Usage_i8(0x2a)
+#define Usage_LED_OffLine Usage_i8(0x2b)
+#define Usage_LED_Busy Usage_i8(0x2c)
+#define Usage_LED_Ready Usage_i8(0x2d)
+#define Usage_LED_PaperOut Usage_i8(0x2e)
+#define Usage_LED_PaperJam Usage_i8(0x2f)
+#define Usage_LED_Remote Usage_i8(0x30)
+#define Usage_LED_Forward Usage_i8(0x31)
+#define Usage_LED_Reverse Usage_i8(0x32)
+#define Usage_LED_Stop Usage_i8(0x33)
+#define Usage_LED_Rewind Usage_i8(0x34)
+#define Usage_LED_FastForward Usage_i8(0x35)
+#define Usage_LED_Play Usage_i8(0x36)
+#define Usage_LED_Pause Usage_i8(0x37)
+#define Usage_LED_Record Usage_i8(0x38)
+#define Usage_LED_Error Usage_i8(0x39)
+#define Usage_LED_UsageSelectedIndicator Usage_i8(0x3a)
+#define Usage_LED_UsageInUseIndicator Usage_i8(0x3b)
+#define Usage_LED_UsageMultiModeIndicator Usage_i8(0x3c)
+#define Usage_LED_IndicatorOn Usage_i8(0x3d)
+#define Usage_LED_IndicatorFlash Usage_i8(0x3e)
+#define Usage_LED_IndicatorSlowBlink Usage_i8(0x3f)
+#define Usage_LED_IndicatorFastBlink Usage_i8(0x40)
+#define Usage_LED_IndicatorOff Usage_i8(0x41)
+#define Usage_LED_FlashOnTime Usage_i8(0x42)
+#define Usage_LED_SlowBlinkOnTime Usage_i8(0x43)
+#define Usage_LED_SlowBlinkOffTime Usage_i8(0x44)
+#define Usage_LED_FastBlinkOnTime Usage_i8(0x45)
+#define Usage_LED_FastBlinkOffTime Usage_i8(0x46)
+#define Usage_LED_UsageIndicatorColor Usage_i8(0x47)
+#define Usage_LED_IndicatorRed Usage_i8(0x48)
+#define Usage_LED_IndicatorGreen Usage_i8(0x49)
+#define Usage_LED_IndicatorAmber Usage_i8(0x4a)
+#define Usage_LED_GenericIndicator Usage_i8(0x4b)
+#define Usage_LED_SystemSuspend Usage_i8(0x4c)
+#define Usage_LED_ExternalPowerConnected Usage_i8(0x4d)
+#define Usage_LED_IndicatorBlue Usage_i8(0x4e)
+#define Usage_LED_IndicatorOrange Usage_i8(0x4f)
+#define Usage_LED_GoodStatus Usage_i8(0x50)
+#define Usage_LED_WarningStatus Usage_i8(0x51)
+#define Usage_LED_RGBLED Usage_i8(0x52)
+#define Usage_LED_RedLEDChannel Usage_i8(0x53)
+#define Usage_LED_BlueLEDChannel Usage_i8(0x54)
+#define Usage_LED_GreenLEDChannel Usage_i8(0x55)
+#define Usage_LED_LEDIntensity Usage_i8(0x56)
+#define Usage_LED_SystemMicrophoneMute Usage_i8(0x57)
+#define Usage_LED_PlayerIndicator Usage_i8(0x60)
+#define Usage_LED_Player1 Usage_i8(0x61)
+#define Usage_LED_Player2 Usage_i8(0x62)
+#define Usage_LED_Player3 Usage_i8(0x63)
+#define Usage_LED_Player4 Usage_i8(0x64)
+#define Usage_LED_Player5 Usage_i8(0x65)
+#define Usage_LED_Player6 Usage_i8(0x66)
+#define Usage_LED_Player7 Usage_i8(0x67)
+#define Usage_LED_Player8 Usage_i8(0x68)
+#define Usage_TD_Phone Usage_i8(0x1)
+#define Usage_TD_AnsweringMachine Usage_i8(0x2)
+#define Usage_TD_MessageControls Usage_i8(0x3)
+#define Usage_TD_Handset Usage_i8(0x4)
+#define Usage_TD_Headset Usage_i8(0x5)
+#define Usage_TD_TelephonyKeyPad Usage_i8(0x6)
+#define Usage_TD_ProgrammableButton Usage_i8(0x7)
+#define Usage_TD_HookSwitch Usage_i8(0x20)
+#define Usage_TD_Flash Usage_i8(0x21)
+#define Usage_TD_Feature Usage_i8(0x22)
+#define Usage_TD_Hold Usage_i8(0x23)
+#define Usage_TD_Redial Usage_i8(0x24)
+#define Usage_TD_Transfer Usage_i8(0x25)
+#define Usage_TD_Drop Usage_i8(0x26)
+#define Usage_TD_Park Usage_i8(0x27)
+#define Usage_TD_ForwardCalls Usage_i8(0x28)
+#define Usage_TD_AlternateFunction Usage_i8(0x29)
+#define Usage_TD_Line Usage_i8(0x2a)
+#define Usage_TD_SpeakerPhone Usage_i8(0x2b)
+#define Usage_TD_Conference Usage_i8(0x2c)
+#define Usage_TD_RingEnable Usage_i8(0x2d)
+#define Usage_TD_RingSelect Usage_i8(0x2e)
+#define Usage_TD_PhoneMute Usage_i8(0x2f)
+#define Usage_TD_CallerID Usage_i8(0x30)
+#define Usage_TD_Send Usage_i8(0x31)
+#define Usage_TD_SpeedDial Usage_i8(0x50)
+#define Usage_TD_StoreNumber Usage_i8(0x51)
+#define Usage_TD_RecallNumber Usage_i8(0x52)
+#define Usage_TD_PhoneDirectory Usage_i8(0x53)
+#define Usage_TD_VoiceMail Usage_i8(0x70)
+#define Usage_TD_ScreenCalls Usage_i8(0x71)
+#define Usage_TD_DoNotDisturb Usage_i8(0x72)
+#define Usage_TD_Message Usage_i8(0x73)
+#define Usage_TD_AnswerOnOff Usage_i8(0x74)
+#define Usage_TD_InsideDialTone Usage_i8(0x90)
+#define Usage_TD_OutsideDialTone Usage_i8(0x91)
+#define Usage_TD_InsideRingTone Usage_i8(0x92)
+#define Usage_TD_OutsideRingTone Usage_i8(0x93)
+#define Usage_TD_PriorityRingTone Usage_i8(0x94)
+#define Usage_TD_InsideRingback Usage_i8(0x95)
+#define Usage_TD_PriorityRingback Usage_i8(0x96)
+#define Usage_TD_LineBusyTone Usage_i8(0x97)
+#define Usage_TD_ReorderTone Usage_i8(0x98)
+#define Usage_TD_CallWaitingTone Usage_i8(0x99)
+#define Usage_TD_ConfirmationTone1 Usage_i8(0x9a)
+#define Usage_TD_ConfirmationTone2 Usage_i8(0x9b)
+#define Usage_TD_TonesOff Usage_i8(0x9c)
+#define Usage_TD_OutsideRingback Usage_i8(0x9d)
+#define Usage_TD_Ringer Usage_i8(0x9e)
+#define Usage_TD_PhoneKey0 Usage_i8(0xb0)
+#define Usage_TD_PhoneKey1 Usage_i8(0xb1)
+#define Usage_TD_PhoneKey2 Usage_i8(0xb2)
+#define Usage_TD_PhoneKey3 Usage_i8(0xb3)
+#define Usage_TD_PhoneKey4 Usage_i8(0xb4)
+#define Usage_TD_PhoneKey5 Usage_i8(0xb5)
+#define Usage_TD_PhoneKey6 Usage_i8(0xb6)
+#define Usage_TD_PhoneKey7 Usage_i8(0xb7)
+#define Usage_TD_PhoneKey8 Usage_i8(0xb8)
+#define Usage_TD_PhoneKey9 Usage_i8(0xb9)
+#define Usage_TD_PhoneKeyStar Usage_i8(0xba)
+#define Usage_TD_PhoneKeyPound Usage_i8(0xbb)
+#define Usage_TD_PhoneKeyA Usage_i8(0xbc)
+#define Usage_TD_PhoneKeyB Usage_i8(0xbd)
+#define Usage_TD_PhoneKeyC Usage_i8(0xbe)
+#define Usage_TD_PhoneKeyD Usage_i8(0xbf)
+#define Usage_TD_PhoneCallHistoryKey Usage_i8(0xc0)
+#define Usage_TD_PhoneCallerIDKey Usage_i8(0xc1)
+#define Usage_TD_PhoneSettingsKey Usage_i8(0xc2)
+#define Usage_TD_HostControl Usage_i8(0xf0)
+#define Usage_TD_HostAvailable Usage_i8(0xf1)
+#define Usage_TD_HostCallActive Usage_i8(0xf2)
+#define Usage_TD_ActivateHandsetAudio Usage_i8(0xf3)
+#define Usage_TD_RingType Usage_i8(0xf4)
+#define Usage_TD_RedialablePhoneNumber Usage_i8(0xf5)
+#define Usage_TD_StopRingTone Usage_i8(0xf8)
+#define Usage_TD_PSTNRingTone Usage_i8(0xf9)
+#define Usage_TD_HostRingTone Usage_i8(0xfa)
+#define Usage_TD_AlertSoundError Usage_i8(0xfb)
+#define Usage_TD_AlertSoundConfirm Usage_i8(0xfc)
+#define Usage_TD_AlertSoundNotification Usage_i8(0xfd)
+#define Usage_TD_SilentRing Usage_i8(0xfe)
+#define Usage_TD_EmailMessageWaiting Usage_i16(0x108)
+#define Usage_TD_VoicemailMessageWaiting Usage_i16(0x109)
+#define Usage_TD_HostHold Usage_i16(0x10a)
+#define Usage_TD_IncomingCallHistoryCount Usage_i16(0x110)
+#define Usage_TD_OutgoingCallHistoryCount Usage_i16(0x111)
+#define Usage_TD_IncomingCallHistory Usage_i16(0x112)
+#define Usage_TD_OutgoingCallHistory Usage_i16(0x113)
+#define Usage_TD_PhoneLocale Usage_i16(0x114)
+#define Usage_TD_PhoneTimeSecond Usage_i16(0x140)
+#define Usage_TD_PhoneTimeMinute Usage_i16(0x141)
+#define Usage_TD_PhoneTimeHour Usage_i16(0x142)
+#define Usage_TD_PhoneDateDay Usage_i16(0x143)
+#define Usage_TD_PhoneDateMonth Usage_i16(0x144)
+#define Usage_TD_PhoneDateYear Usage_i16(0x145)
+#define Usage_TD_HandsetNickname Usage_i16(0x146)
+#define Usage_TD_AddressBookID Usage_i16(0x147)
+#define Usage_TD_CallDuration Usage_i16(0x14a)
+#define Usage_TD_DualModePhone Usage_i16(0x14b)
+#define Usage_Con_ConsumerControl Usage_i8(0x1)
+#define Usage_Con_NumericKeyPad Usage_i8(0x2)
+#define Usage_Con_ProgrammableButtons Usage_i8(0x3)
+#define Usage_Con_Microphone Usage_i8(0x4)
+#define Usage_Con_Headphone Usage_i8(0x5)
+#define Usage_Con_GraphicEqualizer Usage_i8(0x6)
+#define Usage_Con_Plus10 Usage_i8(0x20)
+#define Usage_Con_Plus100 Usage_i8(0x21)
+#define Usage_Con_AMPM Usage_i8(0x22)
+#define Usage_Con_Power Usage_i8(0x30)
+#define Usage_Con_Reset Usage_i8(0x31)
+#define Usage_Con_Sleep Usage_i8(0x32)
+#define Usage_Con_SleepAfter Usage_i8(0x33)
+#define Usage_Con_SleepMode Usage_i8(0x34)
+#define Usage_Con_Illumination Usage_i8(0x35)
+#define Usage_Con_FunctionButtons Usage_i8(0x36)
+#define Usage_Con_Menu Usage_i8(0x40)
+#define Usage_Con_MenuPick Usage_i8(0x41)
+#define Usage_Con_MenuUp Usage_i8(0x42)
+#define Usage_Con_MenuDown Usage_i8(0x43)
+#define Usage_Con_MenuLeft Usage_i8(0x44)
+#define Usage_Con_MenuRight Usage_i8(0x45)
+#define Usage_Con_MenuEscape Usage_i8(0x46)
+#define Usage_Con_MenuValueIncrease Usage_i8(0x47)
+#define Usage_Con_MenuValueDecrease Usage_i8(0x48)
+#define Usage_Con_DataOnScreen Usage_i8(0x60)
+#define Usage_Con_ClosedCaption Usage_i8(0x61)
+#define Usage_Con_ClosedCaptionSelect Usage_i8(0x62)
+#define Usage_Con_VCRTV Usage_i8(0x63)
+#define Usage_Con_BroadcastMode Usage_i8(0x64)
+#define Usage_Con_Snapshot Usage_i8(0x65)
+#define Usage_Con_Still Usage_i8(0x66)
+#define Usage_Con_PictureinPictureToggle Usage_i8(0x67)
+#define Usage_Con_PictureinPictureSwap Usage_i8(0x68)
+#define Usage_Con_RedMenuButton Usage_i8(0x69)
+#define Usage_Con_GreenMenuButton Usage_i8(0x6a)
+#define Usage_Con_BlueMenuButton Usage_i8(0x6b)
+#define Usage_Con_YellowMenuButton Usage_i8(0x6c)
+#define Usage_Con_Aspect Usage_i8(0x6d)
+#define Usage_Con_ThreeDModeSelect Usage_i8(0x6e)
+#define Usage_Con_DisplayBrightnessIncrement Usage_i8(0x6f)
+#define Usage_Con_DisplayBrightnessDecrement Usage_i8(0x70)
+#define Usage_Con_DisplayBrightness Usage_i8(0x71)
+#define Usage_Con_DisplayBacklightToggle Usage_i8(0x72)
+#define Usage_Con_DisplaySetBrightnesstoMinimum Usage_i8(0x73)
+#define Usage_Con_DisplaySetBrightnesstoMaximum Usage_i8(0x74)
+#define Usage_Con_DisplaySetAutoBrightness Usage_i8(0x75)
+#define Usage_Con_CameraAccessEnabled Usage_i8(0x76)
+#define Usage_Con_CameraAccessDisabled Usage_i8(0x77)
+#define Usage_Con_CameraAccessToggle Usage_i8(0x78)
+#define Usage_Con_KeyboardBrightnessIncrement Usage_i8(0x79)
+#define Usage_Con_KeyboardBrightnessDecrement Usage_i8(0x7a)
+#define Usage_Con_KeyboardBacklightSetLevel Usage_i8(0x7b)
+#define Usage_Con_KeyboardBacklightOOC Usage_i8(0x7c)
+#define Usage_Con_KeyboardBacklightSetMinimum Usage_i8(0x7d)
+#define Usage_Con_KeyboardBacklightSetMaximum Usage_i8(0x7e)
+#define Usage_Con_KeyboardBacklightAuto Usage_i8(0x7f)
+#define Usage_Con_Selection Usage_i8(0x80)
+#define Usage_Con_AssignSelection Usage_i8(0x81)
+#define Usage_Con_ModeStep Usage_i8(0x82)
+#define Usage_Con_RecallLast Usage_i8(0x83)
+#define Usage_Con_EnterChannel Usage_i8(0x84)
+#define Usage_Con_OrderMovie Usage_i8(0x85)
+#define Usage_Con_Channel Usage_i8(0x86)
+#define Usage_Con_MediaSelection Usage_i8(0x87)
+#define Usage_Con_MediaSelectComputer Usage_i8(0x88)
+#define Usage_Con_MediaSelectTV Usage_i8(0x89)
+#define Usage_Con_MediaSelectWWW Usage_i8(0x8a)
+#define Usage_Con_MediaSelectDVD Usage_i8(0x8b)
+#define Usage_Con_MediaSelectTelephone Usage_i8(0x8c)
+#define Usage_Con_MediaSelectProgramGuide Usage_i8(0x8d)
+#define Usage_Con_MediaSelectVideoPhone Usage_i8(0x8e)
+#define Usage_Con_MediaSelectGames Usage_i8(0x8f)
+#define Usage_Con_MediaSelectMessages Usage_i8(0x90)
+#define Usage_Con_MediaSelectCD Usage_i8(0x91)
+#define Usage_Con_MediaSelectVCR Usage_i8(0x92)
+#define Usage_Con_MediaSelectTuner Usage_i8(0x93)
+#define Usage_Con_Quit Usage_i8(0x94)
+#define Usage_Con_Help Usage_i8(0x95)
+#define Usage_Con_MediaSelectTape Usage_i8(0x96)
+#define Usage_Con_MediaSelectCable Usage_i8(0x97)
+#define Usage_Con_MediaSelectSatellite Usage_i8(0x98)
+#define Usage_Con_MediaSelectSecurity Usage_i8(0x99)
+#define Usage_Con_MediaSelectHome Usage_i8(0x9a)
+#define Usage_Con_MediaSelectCall Usage_i8(0x9b)
+#define Usage_Con_ChannelIncrement Usage_i8(0x9c)
+#define Usage_Con_ChannelDecrement Usage_i8(0x9d)
+#define Usage_Con_MediaSelectSAP Usage_i8(0x9e)
+#define Usage_Con_VCRPlus Usage_i8(0xa0)
+#define Usage_Con_Once Usage_i8(0xa1)
+#define Usage_Con_Daily Usage_i8(0xa2)
+#define Usage_Con_Weekly Usage_i8(0xa3)
+#define Usage_Con_Monthly Usage_i8(0xa4)
+#define Usage_Con_Play Usage_i8(0xb0)
+#define Usage_Con_Pause Usage_i8(0xb1)
+#define Usage_Con_Record Usage_i8(0xb2)
+#define Usage_Con_FastForward Usage_i8(0xb3)
+#define Usage_Con_Rewind Usage_i8(0xb4)
+#define Usage_Con_ScanNextTrack Usage_i8(0xb5)
+#define Usage_Con_ScanPreviousTrack Usage_i8(0xb6)
+#define Usage_Con_Stop Usage_i8(0xb7)
+#define Usage_Con_Eject Usage_i8(0xb8)
+#define Usage_Con_RandomPlay Usage_i8(0xb9)
+#define Usage_Con_SelectDisc Usage_i8(0xba)
+#define Usage_Con_EnterDisc Usage_i8(0xbb)
+#define Usage_Con_Repeat Usage_i8(0xbc)
+#define Usage_Con_Tracking Usage_i8(0xbd)
+#define Usage_Con_TrackNormal Usage_i8(0xbe)
+#define Usage_Con_SlowTracking Usage_i8(0xbf)
+#define Usage_Con_FrameForward Usage_i8(0xc0)
+#define Usage_Con_FrameBack Usage_i8(0xc1)
+#define Usage_Con_Mark Usage_i8(0xc2)
+#define Usage_Con_ClearMark Usage_i8(0xc3)
+#define Usage_Con_RepeatFromMark Usage_i8(0xc4)
+#define Usage_Con_ReturnToMark Usage_i8(0xc5)
+#define Usage_Con_SearchMarkForward Usage_i8(0xc6)
+#define Usage_Con_SearchMarkBackwards Usage_i8(0xc7)
+#define Usage_Con_CounterReset Usage_i8(0xc8)
+#define Usage_Con_ShowCounter Usage_i8(0xc9)
+#define Usage_Con_TrackingIncrement Usage_i8(0xca)
+#define Usage_Con_TrackingDecrement Usage_i8(0xcb)
+#define Usage_Con_StopEject Usage_i8(0xcc)
+#define Usage_Con_PlayPause Usage_i8(0xcd)
+#define Usage_Con_PlaySkip Usage_i8(0xce)
+#define Usage_Con_VoiceCommand Usage_i8(0xcf)
+#define Usage_Con_InvokeCaptureInterface Usage_i8(0xd0)
+#define Usage_Con_StartorStopGameRecording Usage_i8(0xd1)
+#define Usage_Con_HistoricalGameCapture Usage_i8(0xd2)
+#define Usage_Con_CaptureGameScreenshot Usage_i8(0xd3)
+#define Usage_Con_ShoworHideRecordingIndicator Usage_i8(0xd4)
+#define Usage_Con_StartorStopMicrophoneCapture Usage_i8(0xd5)
+#define Usage_Con_StartorStopCameraCapture Usage_i8(0xd6)
+#define Usage_Con_StartorStopGameBroadcast Usage_i8(0xd7)
+#define Usage_Con_StartorStopVoiceDictationSession Usage_i8(0xd8)
+#define Usage_Con_InvokeDismissEmojiPicker Usage_i8(0xd9)
+#define Usage_Con_Volume Usage_i8(0xe0)
+#define Usage_Con_Balance Usage_i8(0xe1)
+#define Usage_Con_Mute Usage_i8(0xe2)
+#define Usage_Con_Bass Usage_i8(0xe3)
+#define Usage_Con_Treble Usage_i8(0xe4)
+#define Usage_Con_BassBoost Usage_i8(0xe5)
+#define Usage_Con_SurroundMode Usage_i8(0xe6)
+#define Usage_Con_Loudness Usage_i8(0xe7)
+#define Usage_Con_MPX Usage_i8(0xe8)
+#define Usage_Con_VolumeIncrement Usage_i8(0xe9)
+#define Usage_Con_VolumeDecrement Usage_i8(0xea)
+#define Usage_Con_SpeedSelect Usage_i8(0xf0)
+#define Usage_Con_PlaybackSpeed Usage_i8(0xf1)
+#define Usage_Con_StandardPlay Usage_i8(0xf2)
+#define Usage_Con_LongPlay Usage_i8(0xf3)
+#define Usage_Con_ExtendedPlay Usage_i8(0xf4)
+#define Usage_Con_Slow Usage_i8(0xf5)
+#define Usage_Con_FanEnable Usage_i16(0x100)
+#define Usage_Con_FanSpeed Usage_i16(0x101)
+#define Usage_Con_LightEnable Usage_i16(0x102)
+#define Usage_Con_LightIlluminationLevel Usage_i16(0x103)
+#define Usage_Con_ClimateControlEnable Usage_i16(0x104)
+#define Usage_Con_RoomTemperature Usage_i16(0x105)
+#define Usage_Con_SecurityEnable Usage_i16(0x106)
+#define Usage_Con_FireAlarm Usage_i16(0x107)
+#define Usage_Con_PoliceAlarm Usage_i16(0x108)
+#define Usage_Con_Proximity Usage_i16(0x109)
+#define Usage_Con_Motion Usage_i16(0x10a)
+#define Usage_Con_DuressAlarm Usage_i16(0x10b)
+#define Usage_Con_HoldupAlarm Usage_i16(0x10c)
+#define Usage_Con_MedicalAlarm Usage_i16(0x10d)
+#define Usage_Con_BalanceRight Usage_i16(0x150)
+#define Usage_Con_BalanceLeft Usage_i16(0x151)
+#define Usage_Con_BassIncrement Usage_i16(0x152)
+#define Usage_Con_BassDecrement Usage_i16(0x153)
+#define Usage_Con_TrebleIncrement Usage_i16(0x154)
+#define Usage_Con_TrebleDecrement Usage_i16(0x155)
+#define Usage_Con_SpeakerSystem Usage_i16(0x160)
+#define Usage_Con_ChannelLeft Usage_i16(0x161)
+#define Usage_Con_ChannelRight Usage_i16(0x162)
+#define Usage_Con_ChannelCenter Usage_i16(0x163)
+#define Usage_Con_ChannelFront Usage_i16(0x164)
+#define Usage_Con_ChannelCenterFront Usage_i16(0x165)
+#define Usage_Con_ChannelSide Usage_i16(0x166)
+#define Usage_Con_ChannelSurround Usage_i16(0x167)
+#define Usage_Con_ChannelLowFrequencyEnhancement Usage_i16(0x168)
+#define Usage_Con_ChannelTop Usage_i16(0x169)
+#define Usage_Con_ChannelUnknown Usage_i16(0x16a)
+#define Usage_Con_Subchannel Usage_i16(0x170)
+#define Usage_Con_SubchannelIncrement Usage_i16(0x171)
+#define Usage_Con_SubchannelDecrement Usage_i16(0x172)
+#define Usage_Con_AlternateAudioIncrement Usage_i16(0x173)
+#define Usage_Con_AlternateAudioDecrement Usage_i16(0x174)
+#define Usage_Con_ApplicationLaunchButtons Usage_i16(0x180)
+#define Usage_Con_ALLaunchButtonConfigurationTool Usage_i16(0x181)
+#define Usage_Con_ALProgrammableButtonConfiguration Usage_i16(0x182)
+#define Usage_Con_ALConsumerControlConfiguration Usage_i16(0x183)
+#define Usage_Con_ALWordProcessor Usage_i16(0x184)
+#define Usage_Con_ALTextEditor Usage_i16(0x185)
+#define Usage_Con_ALSpreadsheet Usage_i16(0x186)
+#define Usage_Con_ALGraphicsEditor Usage_i16(0x187)
+#define Usage_Con_ALPresentationApp Usage_i16(0x188)
+#define Usage_Con_ALDatabaseApp Usage_i16(0x189)
+#define Usage_Con_ALEmailReader Usage_i16(0x18a)
+#define Usage_Con_ALNewsreader Usage_i16(0x18b)
+#define Usage_Con_ALVoicemail Usage_i16(0x18c)
+#define Usage_Con_ALContactsAddressBook Usage_i16(0x18d)
+#define Usage_Con_ALCalendarSchedule Usage_i16(0x18e)
+#define Usage_Con_ALTaskProjectManager Usage_i16(0x18f)
+#define Usage_Con_ALLogJournalTimecard Usage_i16(0x190)
+#define Usage_Con_ALCheckbookFinance Usage_i16(0x191)
+#define Usage_Con_ALCalculator Usage_i16(0x192)
+#define Usage_Con_ALAVCapturePlayback Usage_i16(0x193)
+#define Usage_Con_ALLocalMachineBrowser Usage_i16(0x194)
+#define Usage_Con_ALLANWANBrowser Usage_i16(0x195)
+#define Usage_Con_ALInternetBrowser Usage_i16(0x196)
+#define Usage_Con_ALRemoteNetworkingISPConnect Usage_i16(0x197)
+#define Usage_Con_ALNetworkConference Usage_i16(0x198)
+#define Usage_Con_ALNetworkChat Usage_i16(0x199)
+#define Usage_Con_ALTelephonyDialer Usage_i16(0x19a)
+#define Usage_Con_ALLogon Usage_i16(0x19b)
+#define Usage_Con_ALLogoff Usage_i16(0x19c)
+#define Usage_Con_ALLogonLogoff Usage_i16(0x19d)
+#define Usage_Con_ALTerminalLockScreensaver Usage_i16(0x19e)
+#define Usage_Con_ALControlPanel Usage_i16(0x19f)
+#define Usage_Con_ALCommandLineProcessorRun Usage_i16(0x1a0)
+#define Usage_Con_ALProcessTaskManager Usage_i16(0x1a1)
+#define Usage_Con_ALSelectTaskApplication Usage_i16(0x1a2)
+#define Usage_Con_ALNextTaskApplication Usage_i16(0x1a3)
+#define Usage_Con_ALPreviousTaskApplication Usage_i16(0x1a4)
+#define Usage_Con_ALPreemptiveHaltTaskApplication Usage_i16(0x1a5)
+#define Usage_Con_ALIntegratedHelpCenter Usage_i16(0x1a6)
+#define Usage_Con_ALDocuments Usage_i16(0x1a7)
+#define Usage_Con_ALThesaurus Usage_i16(0x1a8)
+#define Usage_Con_ALDictionary Usage_i16(0x1a9)
+#define Usage_Con_ALDesktop Usage_i16(0x1aa)
+#define Usage_Con_ALSpellCheck Usage_i16(0x1ab)
+#define Usage_Con_ALGrammarCheck Usage_i16(0x1ac)
+#define Usage_Con_ALWirelessStatus Usage_i16(0x1ad)
+#define Usage_Con_ALKeyboardLayout Usage_i16(0x1ae)
+#define Usage_Con_ALVirusProtection Usage_i16(0x1af)
+#define Usage_Con_ALEncryption Usage_i16(0x1b0)
+#define Usage_Con_ALScreenSaver Usage_i16(0x1b1)
+#define Usage_Con_ALAlarms Usage_i16(0x1b2)
+#define Usage_Con_ALClock Usage_i16(0x1b3)
+#define Usage_Con_ALFileBrowser Usage_i16(0x1b4)
+#define Usage_Con_ALPowerStatus Usage_i16(0x1b5)
+#define Usage_Con_ALImageBrowser Usage_i16(0x1b6)
+#define Usage_Con_ALAudioBrowser Usage_i16(0x1b7)
+#define Usage_Con_ALMovieBrowser Usage_i16(0x1b8)
+#define Usage_Con_ALDigitalRightsManager Usage_i16(0x1b9)
+#define Usage_Con_ALDigitalWallet Usage_i16(0x1ba)
+#define Usage_Con_ALInstantMessaging Usage_i16(0x1bc)
+#define Usage_Con_ALOEMFeaturesTipsTutorialBrowser Usage_i16(0x1bd)
+#define Usage_Con_ALOEMHelp Usage_i16(0x1be)
+#define Usage_Con_ALOnlineCommunity Usage_i16(0x1bf)
+#define Usage_Con_ALEntertainmentContentBrowser Usage_i16(0x1c0)
+#define Usage_Con_ALOnlineShoppingBrowser Usage_i16(0x1c1)
+#define Usage_Con_ALSmartCardInformationHelp Usage_i16(0x1c2)
+#define Usage_Con_ALMarketMonitorFinanceBrowser Usage_i16(0x1c3)
+#define Usage_Con_ALCustomizedCorporateNewsBrowser Usage_i16(0x1c4)
+#define Usage_Con_ALOnlineActivityBrowser Usage_i16(0x1c5)
+#define Usage_Con_ALResearchSearchBrowser Usage_i16(0x1c6)
+#define Usage_Con_ALAudioPlayer Usage_i16(0x1c7)
+#define Usage_Con_ALMessageStatus Usage_i16(0x1c8)
+#define Usage_Con_ALContactSync Usage_i16(0x1c9)
+#define Usage_Con_ALNavigation Usage_i16(0x1ca)
+#define Usage_Con_ALContextawareDesktopAssistant Usage_i16(0x1cb)
+#define Usage_Con_GenericGUIApplicationControls Usage_i16(0x200)
+#define Usage_Con_ACNew Usage_i16(0x201)
+#define Usage_Con_ACOpen Usage_i16(0x202)
+#define Usage_Con_ACClose Usage_i16(0x203)
+#define Usage_Con_ACExit Usage_i16(0x204)
+#define Usage_Con_ACMaximize Usage_i16(0x205)
+#define Usage_Con_ACMinimize Usage_i16(0x206)
+#define Usage_Con_ACSave Usage_i16(0x207)
+#define Usage_Con_ACPrint Usage_i16(0x208)
+#define Usage_Con_ACProperties Usage_i16(0x209)
+#define Usage_Con_ACUndo Usage_i16(0x21a)
+#define Usage_Con_ACCopy Usage_i16(0x21b)
+#define Usage_Con_ACCut Usage_i16(0x21c)
+#define Usage_Con_ACPaste Usage_i16(0x21d)
+#define Usage_Con_ACSelectAll Usage_i16(0x21e)
+#define Usage_Con_ACFind Usage_i16(0x21f)
+#define Usage_Con_ACFindandReplace Usage_i16(0x220)
+#define Usage_Con_ACSearch Usage_i16(0x221)
+#define Usage_Con_ACGoTo Usage_i16(0x222)
+#define Usage_Con_ACHome Usage_i16(0x223)
+#define Usage_Con_ACBack Usage_i16(0x224)
+#define Usage_Con_ACForward Usage_i16(0x225)
+#define Usage_Con_ACStop Usage_i16(0x226)
+#define Usage_Con_ACRefresh Usage_i16(0x227)
+#define Usage_Con_ACPreviousLink Usage_i16(0x228)
+#define Usage_Con_ACNextLink Usage_i16(0x229)
+#define Usage_Con_ACBookmarks Usage_i16(0x22a)
+#define Usage_Con_ACHistory Usage_i16(0x22b)
+#define Usage_Con_ACSubscriptions Usage_i16(0x22c)
+#define Usage_Con_ACZoomIn Usage_i16(0x22d)
+#define Usage_Con_ACZoomOut Usage_i16(0x22e)
+#define Usage_Con_ACZoom Usage_i16(0x22f)
+#define Usage_Con_ACFullScreenView Usage_i16(0x230)
+#define Usage_Con_ACNormalView Usage_i16(0x231)
+#define Usage_Con_ACViewToggle Usage_i16(0x232)
+#define Usage_Con_ACScrollUp Usage_i16(0x233)
+#define Usage_Con_ACScrollDown Usage_i16(0x234)
+#define Usage_Con_ACScroll Usage_i16(0x235)
+#define Usage_Con_ACPanLeft Usage_i16(0x236)
+#define Usage_Con_ACPanRight Usage_i16(0x237)
+#define Usage_Con_ACPan Usage_i16(0x238)
+#define Usage_Con_ACNewWindow Usage_i16(0x239)
+#define Usage_Con_ACTileHorizontally Usage_i16(0x23a)
+#define Usage_Con_ACTileVertically Usage_i16(0x23b)
+#define Usage_Con_ACFormat Usage_i16(0x23c)
+#define Usage_Con_ACEdit Usage_i16(0x23d)
+#define Usage_Con_ACBold Usage_i16(0x23e)
+#define Usage_Con_ACItalics Usage_i16(0x23f)
+#define Usage_Con_ACUnderline Usage_i16(0x240)
+#define Usage_Con_ACStrikethrough Usage_i16(0x241)
+#define Usage_Con_ACSubscript Usage_i16(0x242)
+#define Usage_Con_ACSuperscript Usage_i16(0x243)
+#define Usage_Con_ACAllCaps Usage_i16(0x244)
+#define Usage_Con_ACRotate Usage_i16(0x245)
+#define Usage_Con_ACResize Usage_i16(0x246)
+#define Usage_Con_ACFlipHorizontal Usage_i16(0x247)
+#define Usage_Con_ACFlipVertical Usage_i16(0x248)
+#define Usage_Con_ACMirrorHorizontal Usage_i16(0x249)
+#define Usage_Con_ACMirrorVertical Usage_i16(0x24a)
+#define Usage_Con_ACFontSelect Usage_i16(0x24b)
+#define Usage_Con_ACFontColor Usage_i16(0x24c)
+#define Usage_Con_ACFontSize Usage_i16(0x24d)
+#define Usage_Con_ACJustifyLeft Usage_i16(0x24e)
+#define Usage_Con_ACJustifyCenterH Usage_i16(0x24f)
+#define Usage_Con_ACJustifyRight Usage_i16(0x250)
+#define Usage_Con_ACJustifyBlockH Usage_i16(0x251)
+#define Usage_Con_ACJustifyTop Usage_i16(0x252)
+#define Usage_Con_ACJustifyCenterV Usage_i16(0x253)
+#define Usage_Con_ACJustifyBottom Usage_i16(0x254)
+#define Usage_Con_ACJustifyBlockV Usage_i16(0x255)
+#define Usage_Con_ACIndentDecrease Usage_i16(0x256)
+#define Usage_Con_ACIndentIncrease Usage_i16(0x257)
+#define Usage_Con_ACNumberedList Usage_i16(0x258)
+#define Usage_Con_ACRestartNumbering Usage_i16(0x259)
+#define Usage_Con_ACBulletedList Usage_i16(0x25a)
+#define Usage_Con_ACPromote Usage_i16(0x25b)
+#define Usage_Con_ACDemote Usage_i16(0x25c)
+#define Usage_Con_ACYes Usage_i16(0x25d)
+#define Usage_Con_ACNo Usage_i16(0x25e)
+#define Usage_Con_ACCancel Usage_i16(0x25f)
+#define Usage_Con_ACCatalog Usage_i16(0x260)
+#define Usage_Con_ACBuyCheckout Usage_i16(0x261)
+#define Usage_Con_ACAddtoCart Usage_i16(0x262)
+#define Usage_Con_ACExpand Usage_i16(0x263)
+#define Usage_Con_ACExpandAll Usage_i16(0x264)
+#define Usage_Con_ACCollapse Usage_i16(0x265)
+#define Usage_Con_ACCollapseAll Usage_i16(0x266)
+#define Usage_Con_ACPrintPreview Usage_i16(0x267)
+#define Usage_Con_ACPasteSpecial Usage_i16(0x268)
+#define Usage_Con_ACInsertMode Usage_i16(0x269)
+#define Usage_Con_ACDelete Usage_i16(0x26a)
+#define Usage_Con_ACLock Usage_i16(0x26b)
+#define Usage_Con_ACUnlock Usage_i16(0x26c)
+#define Usage_Con_ACProtect Usage_i16(0x26d)
+#define Usage_Con_ACUnprotect Usage_i16(0x26e)
+#define Usage_Con_ACAttachComment Usage_i16(0x26f)
+#define Usage_Con_ACDeleteComment Usage_i16(0x270)
+#define Usage_Con_ACViewComment Usage_i16(0x271)
+#define Usage_Con_ACSelectWord Usage_i16(0x272)
+#define Usage_Con_ACSelectSentence Usage_i16(0x273)
+#define Usage_Con_ACSelectParagraph Usage_i16(0x274)
+#define Usage_Con_ACSelectColumn Usage_i16(0x275)
+#define Usage_Con_ACSelectRow Usage_i16(0x276)
+#define Usage_Con_ACSelectTable Usage_i16(0x277)
+#define Usage_Con_ACSelectObject Usage_i16(0x278)
+#define Usage_Con_ACRedoRepeat Usage_i16(0x279)
+#define Usage_Con_ACSort Usage_i16(0x27a)
+#define Usage_Con_ACSortAscending Usage_i16(0x27b)
+#define Usage_Con_ACSortDescending Usage_i16(0x27c)
+#define Usage_Con_ACFilter Usage_i16(0x27d)
+#define Usage_Con_ACSetClock Usage_i16(0x27e)
+#define Usage_Con_ACViewClock Usage_i16(0x27f)
+#define Usage_Con_ACSelectTimeZone Usage_i16(0x280)
+#define Usage_Con_ACEditTimeZones Usage_i16(0x281)
+#define Usage_Con_ACSetAlarm Usage_i16(0x282)
+#define Usage_Con_ACClearAlarm Usage_i16(0x283)
+#define Usage_Con_ACSnoozeAlarm Usage_i16(0x284)
+#define Usage_Con_ACResetAlarm Usage_i16(0x285)
+#define Usage_Con_ACSynchronize Usage_i16(0x286)
+#define Usage_Con_ACSendReceive Usage_i16(0x287)
+#define Usage_Con_ACSendTo Usage_i16(0x288)
+#define Usage_Con_ACReply Usage_i16(0x289)
+#define Usage_Con_ACReplyAll Usage_i16(0x28a)
+#define Usage_Con_ACForwardMsg Usage_i16(0x28b)
+#define Usage_Con_ACSend Usage_i16(0x28c)
+#define Usage_Con_ACAttachFile Usage_i16(0x28d)
+#define Usage_Con_ACUpload Usage_i16(0x28e)
+#define Usage_Con_ACDownloadSaveTargetAs Usage_i16(0x28f)
+#define Usage_Con_ACSetBorders Usage_i16(0x290)
+#define Usage_Con_ACInsertRow Usage_i16(0x291)
+#define Usage_Con_ACInsertColumn Usage_i16(0x292)
+#define Usage_Con_ACInsertFile Usage_i16(0x293)
+#define Usage_Con_ACInsertPicture Usage_i16(0x294)
+#define Usage_Con_ACInsertObject Usage_i16(0x295)
+#define Usage_Con_ACInsertSymbol Usage_i16(0x296)
+#define Usage_Con_ACSaveandClose Usage_i16(0x297)
+#define Usage_Con_ACRename Usage_i16(0x298)
+#define Usage_Con_ACMerge Usage_i16(0x299)
+#define Usage_Con_ACSplit Usage_i16(0x29a)
+#define Usage_Con_ACDisributeHorizontally Usage_i16(0x29b)
+#define Usage_Con_ACDistributeVertically Usage_i16(0x29c)
+#define Usage_Con_ACNextKeyboardLayoutSelect Usage_i16(0x29d)
+#define Usage_Con_ACNavigationGuidance Usage_i16(0x29e)
+#define Usage_Con_ACDesktopShowAllWindows Usage_i16(0x29f)
+#define Usage_Con_ACSoftKeyLeft Usage_i16(0x2a0)
+#define Usage_Con_ACSoftKeyRight Usage_i16(0x2a1)
+#define Usage_Con_ACDesktopShowAllApplications Usage_i16(0x2a2)
+#define Usage_Con_ACIdleKeepAlive Usage_i16(0x2b0)
+#define Usage_Con_ExtendedKeyboardAttributesCollection Usage_i16(0x2c0)
+#define Usage_Con_KeyboardFormFactor Usage_i16(0x2c1)
+#define Usage_Con_KeyboardKeyType Usage_i16(0x2c2)
+#define Usage_Con_KeyboardPhysicalLayout Usage_i16(0x2c3)
+#define Usage_Con_VendorSpecificKeyboardPhysicalLayout Usage_i16(0x2c4)
+#define Usage_Con_KeyboardIETFLanguageTagIndex Usage_i16(0x2c5)
+#define Usage_Con_ImplementedKeyboardInputAssistControls Usage_i16(0x2c6)
+#define Usage_Con_KeyboardInputAssistPrevious Usage_i16(0x2c7)
+#define Usage_Con_KeyboardInputAssistNext Usage_i16(0x2c8)
+#define Usage_Con_KeyboardInputAssistPreviousGroup Usage_i16(0x2c9)
+#define Usage_Con_KeyboardInputAssistNextGroup Usage_i16(0x2ca)
+#define Usage_Con_KeyboardInputAssistAccept Usage_i16(0x2cb)
+#define Usage_Con_KeyboardInputAssistCancel Usage_i16(0x2cc)
+#define Usage_Con_PrivacyScreenToggle Usage_i16(0x2d0)
+#define Usage_Con_PrivacyScreenLevelDecrement Usage_i16(0x2d1)
+#define Usage_Con_PrivacyScreenLevelIncrement Usage_i16(0x2d2)
+#define Usage_Con_PrivacyScreenLevelMinimum Usage_i16(0x2d3)
+#define Usage_Con_PrivacyScreenLevelMaximum Usage_i16(0x2d4)
+#define Usage_Con_ContactEdited Usage_i16(0x500)
+#define Usage_Con_ContactAdded Usage_i16(0x501)
+#define Usage_Con_ContactRecordActive Usage_i16(0x502)
+#define Usage_Con_ContactIndex Usage_i16(0x503)
+#define Usage_Con_ContactNickname Usage_i16(0x504)
+#define Usage_Con_ContactFirstName Usage_i16(0x505)
+#define Usage_Con_ContactLastName Usage_i16(0x506)
+#define Usage_Con_ContactFullName Usage_i16(0x507)
+#define Usage_Con_ContactPhoneNumberPersonal Usage_i16(0x508)
+#define Usage_Con_ContactPhoneNumberBusiness Usage_i16(0x509)
+#define Usage_Con_ContactPhoneNumberMobile Usage_i16(0x50a)
+#define Usage_Con_ContactPhoneNumberPager Usage_i16(0x50b)
+#define Usage_Con_ContactPhoneNumberFax Usage_i16(0x50c)
+#define Usage_Con_ContactPhoneNumberOther Usage_i16(0x50d)
+#define Usage_Con_ContactEmailPersonal Usage_i16(0x50e)
+#define Usage_Con_ContactEmailBusiness Usage_i16(0x50f)
+#define Usage_Con_ContactEmailOther Usage_i16(0x510)
+#define Usage_Con_ContactEmailMain Usage_i16(0x511)
+#define Usage_Con_ContactSpeedDialNumber Usage_i16(0x512)
+#define Usage_Con_ContactStatusFlag Usage_i16(0x513)
+#define Usage_Con_ContactMisc Usage_i16(0x514)
+#define Usage_Dig_Digitizer Usage_i8(0x1)
+#define Usage_Dig_Pen Usage_i8(0x2)
+#define Usage_Dig_LightPen Usage_i8(0x3)
+#define Usage_Dig_TouchScreen Usage_i8(0x4)
+#define Usage_Dig_TouchPad Usage_i8(0x5)
+#define Usage_Dig_Whiteboard Usage_i8(0x6)
+#define Usage_Dig_CoordinateMeasuringMachine Usage_i8(0x7)
+#define Usage_Dig_ThreeDDigitizer Usage_i8(0x8)
+#define Usage_Dig_StereoPlotter Usage_i8(0x9)
+#define Usage_Dig_ArticulatedArm Usage_i8(0xa)
+#define Usage_Dig_Armature Usage_i8(0xb)
+#define Usage_Dig_MultiplePointDigitizer Usage_i8(0xc)
+#define Usage_Dig_FreeSpaceWand Usage_i8(0xd)
+#define Usage_Dig_DeviceConfiguration Usage_i8(0xe)
+#define Usage_Dig_CapacitiveHeatMapDigitizer Usage_i8(0xf)
+#define Usage_Dig_Stylus Usage_i8(0x20)
+#define Usage_Dig_Puck Usage_i8(0x21)
+#define Usage_Dig_Finger Usage_i8(0x22)
+#define Usage_Dig_Devicesettings Usage_i8(0x23)
+#define Usage_Dig_CharacterGesture Usage_i8(0x24)
+#define Usage_Dig_TipPressure Usage_i8(0x30)
+#define Usage_Dig_BarrelPressure Usage_i8(0x31)
+#define Usage_Dig_InRange Usage_i8(0x32)
+#define Usage_Dig_Touch Usage_i8(0x33)
+#define Usage_Dig_Untouch Usage_i8(0x34)
+#define Usage_Dig_Tap Usage_i8(0x35)
+#define Usage_Dig_Quality Usage_i8(0x36)
+#define Usage_Dig_DataValid Usage_i8(0x37)
+#define Usage_Dig_TransducerIndex Usage_i8(0x38)
+#define Usage_Dig_TabletFunctionKeys Usage_i8(0x39)
+#define Usage_Dig_ProgramChangeKeys Usage_i8(0x3a)
+#define Usage_Dig_BatteryStrength Usage_i8(0x3b)
+#define Usage_Dig_Invert Usage_i8(0x3c)
+#define Usage_Dig_XTilt Usage_i8(0x3d)
+#define Usage_Dig_YTilt Usage_i8(0x3e)
+#define Usage_Dig_Azimuth Usage_i8(0x3f)
+#define Usage_Dig_Altitude Usage_i8(0x40)
+#define Usage_Dig_Twist Usage_i8(0x41)
+#define Usage_Dig_TipSwitch Usage_i8(0x42)
+#define Usage_Dig_SecondaryTipSwitch Usage_i8(0x43)
+#define Usage_Dig_BarrelSwitch Usage_i8(0x44)
+#define Usage_Dig_Eraser Usage_i8(0x45)
+#define Usage_Dig_TabletPick Usage_i8(0x46)
+#define Usage_Dig_TouchValid Usage_i8(0x47)
+#define Usage_Dig_Width Usage_i8(0x48)
+#define Usage_Dig_Height Usage_i8(0x49)
+#define Usage_Dig_ContactIdentifier Usage_i8(0x51)
+#define Usage_Dig_DeviceMode Usage_i8(0x52)
+#define Usage_Dig_DeviceIdentifier Usage_i8(0x53)
+#define Usage_Dig_ContactCount Usage_i8(0x54)
+#define Usage_Dig_ContactCountMaximum Usage_i8(0x55)
+#define Usage_Dig_ScanTime Usage_i8(0x56)
+#define Usage_Dig_SurfaceSwitch Usage_i8(0x57)
+#define Usage_Dig_ButtonSwitch Usage_i8(0x58)
+#define Usage_Dig_PadType Usage_i8(0x59)
+#define Usage_Dig_SecondaryBarrelSwitch Usage_i8(0x5a)
+#define Usage_Dig_TransducerSerialNumber Usage_i8(0x5b)
+#define Usage_Dig_PreferredColor Usage_i8(0x5c)
+#define Usage_Dig_PreferredColorisLocked Usage_i8(0x5d)
+#define Usage_Dig_PreferredLineWidth Usage_i8(0x5e)
+#define Usage_Dig_PreferredLineWidthisLocked Usage_i8(0x5f)
+#define Usage_Dig_LatencyMode Usage_i8(0x60)
+#define Usage_Dig_GestureCharacterQuality Usage_i8(0x61)
+#define Usage_Dig_CharacterGestureDataLength Usage_i8(0x62)
+#define Usage_Dig_CharacterGestureData Usage_i8(0x63)
+#define Usage_Dig_GestureCharacterEncoding Usage_i8(0x64)
+#define Usage_Dig_UTF8CharacterGestureEncoding Usage_i8(0x65)
+#define Usage_Dig_UTF16LittleEndianCharacterGestureEncoding Usage_i8(0x66)
+#define Usage_Dig_UTF16BigEndianCharacterGestureEncoding Usage_i8(0x67)
+#define Usage_Dig_UTF32LittleEndianCharacterGestureEncoding Usage_i8(0x68)
+#define Usage_Dig_UTF32BigEndianCharacterGestureEncoding Usage_i8(0x69)
+#define Usage_Dig_CapacitiveHeatMapProtocolVendorID Usage_i8(0x6a)
+#define Usage_Dig_CapacitiveHeatMapProtocolVersion Usage_i8(0x6b)
+#define Usage_Dig_CapacitiveHeatMapFrameData Usage_i8(0x6c)
+#define Usage_Dig_GestureCharacterEnable Usage_i8(0x6d)
+#define Usage_Dig_TransducerSerialNumberPart2 Usage_i8(0x6e)
+#define Usage_Dig_NoPreferredColor Usage_i8(0x6f)
+#define Usage_Dig_PreferredLineStyle Usage_i8(0x70)
+#define Usage_Dig_PreferredLineStyleisLocked Usage_i8(0x71)
+#define Usage_Dig_Ink Usage_i8(0x72)
+#define Usage_Dig_Pencil Usage_i8(0x73)
+#define Usage_Dig_Highlighter Usage_i8(0x74)
+#define Usage_Dig_ChiselMarker Usage_i8(0x75)
+#define Usage_Dig_Brush Usage_i8(0x76)
+#define Usage_Dig_NoPreference Usage_i8(0x77)
+#define Usage_Dig_DigitizerDiagnostic Usage_i8(0x80)
+#define Usage_Dig_DigitizerError Usage_i8(0x81)
+#define Usage_Dig_ErrNormalStatus Usage_i8(0x82)
+#define Usage_Dig_ErrTransducersExceeded Usage_i8(0x83)
+#define Usage_Dig_ErrFullTransFeaturesUnavailable Usage_i8(0x84)
+#define Usage_Dig_ErrChargeLow Usage_i8(0x85)
+#define Usage_Dig_TransducerSoftwareInfo Usage_i8(0x90)
+#define Usage_Dig_TransducerVendorId Usage_i8(0x91)
+#define Usage_Dig_TransducerProductId Usage_i8(0x92)
+#define Usage_Dig_DeviceSupportedProtocols Usage_i8(0x93)
+#define Usage_Dig_TransducerSupportedProtocols Usage_i8(0x94)
+#define Usage_Dig_NoProtocol Usage_i8(0x95)
+#define Usage_Dig_WacomAESProtocol Usage_i8(0x96)
+#define Usage_Dig_USIProtocol Usage_i8(0x97)
+#define Usage_Dig_MicrosoftPenProtocol Usage_i8(0x98)
+#define Usage_Dig_SupportedReportRates Usage_i8(0xa0)
+#define Usage_Dig_ReportRate Usage_i8(0xa1)
+#define Usage_Dig_TransducerConnected Usage_i8(0xa2)
+#define Usage_Dig_SwitchDisabled Usage_i8(0xa3)
+#define Usage_Dig_SwitchUnimplemented Usage_i8(0xa4)
+#define Usage_Dig_TransducerSwitches Usage_i8(0xa5)
+#define Usage_Dig_TransducerIndexSelector Usage_i8(0xa6)
+#define Usage_Dig_ButtonPressThreshold Usage_i8(0xb0)
+#define Usage_Hap_SimpleHapticController Usage_i8(0x1)
+#define Usage_Hap_WaveformList Usage_i8(0x10)
+#define Usage_Hap_DurationList Usage_i8(0x11)
+#define Usage_Hap_AutoTrigger Usage_i8(0x20)
+#define Usage_Hap_ManualTrigger Usage_i8(0x21)
+#define Usage_Hap_AutoTriggerAssociatedControl Usage_i8(0x22)
+#define Usage_Hap_Intensity Usage_i8(0x23)
+#define Usage_Hap_RepeatCount Usage_i8(0x24)
+#define Usage_Hap_RetriggerPeriod Usage_i8(0x25)
+#define Usage_Hap_WaveformVendorPage Usage_i8(0x26)
+#define Usage_Hap_WaveformVendorID Usage_i8(0x27)
+#define Usage_Hap_WaveformCutoffTime Usage_i8(0x28)
+#define Usage_Hap_WaveformNone Usage_i16(0x1001)
+#define Usage_Hap_WaveformStop Usage_i16(0x1002)
+#define Usage_Hap_WaveformClick Usage_i16(0x1003)
+#define Usage_Hap_WaveformBuzzContinuous Usage_i16(0x1004)
+#define Usage_Hap_WaveformRumbleContinuous Usage_i16(0x1005)
+#define Usage_Hap_WaveformPress Usage_i16(0x1006)
+#define Usage_Hap_WaveformRelease Usage_i16(0x1007)
+#define Usage_Hap_WaveformHover Usage_i16(0x1008)
+#define Usage_Hap_WaveformSuccess Usage_i16(0x1009)
+#define Usage_Hap_WaveformError Usage_i16(0x100a)
+#define Usage_Hap_WaveformInkContinuous Usage_i16(0x100b)
+#define Usage_Hap_WaveformPencilContinuous Usage_i16(0x100c)
+#define Usage_Hap_WaveformMarkerContinuous Usage_i16(0x100d)
+#define Usage_Hap_WaveformChiselMarkerContinuous Usage_i16(0x100e)
+#define Usage_Hap_WaveformBrushContinuous Usage_i16(0x100f)
+#define Usage_Hap_WaveformEraserContinuous Usage_i16(0x1010)
+#define Usage_Hap_WaveformSparkleContinuous Usage_i16(0x1011)
+#define Usage_PID_PhysicalInputDevice Usage_i8(0x1)
+#define Usage_PID_Normal Usage_i8(0x20)
+#define Usage_PID_SetEffectReport Usage_i8(0x21)
+#define Usage_PID_EffectParameterBlockIndex Usage_i8(0x22)
+#define Usage_PID_ParameterBlockOffset Usage_i8(0x23)
+#define Usage_PID_ROMFlag Usage_i8(0x24)
+#define Usage_PID_EffectType Usage_i8(0x25)
+#define Usage_PID_ETConstantForce Usage_i8(0x26)
+#define Usage_PID_ETRamp Usage_i8(0x27)
+#define Usage_PID_ETCustomForce Usage_i8(0x28)
+#define Usage_PID_ETSquare Usage_i8(0x30)
+#define Usage_PID_ETSine Usage_i8(0x31)
+#define Usage_PID_ETTriangle Usage_i8(0x32)
+#define Usage_PID_ETSawtoothUp Usage_i8(0x33)
+#define Usage_PID_ETSawtoothDown Usage_i8(0x34)
+#define Usage_PID_ETSpring Usage_i8(0x40)
+#define Usage_PID_ETDamper Usage_i8(0x41)
+#define Usage_PID_ETInertia Usage_i8(0x42)
+#define Usage_PID_ETFriction Usage_i8(0x43)
+#define Usage_PID_Duration Usage_i8(0x50)
+#define Usage_PID_SamplePeriod Usage_i8(0x51)
+#define Usage_PID_Gain Usage_i8(0x52)
+#define Usage_PID_TriggerButton Usage_i8(0x53)
+#define Usage_PID_TriggerRepeatInterval Usage_i8(0x54)
+#define Usage_PID_AxesEnable Usage_i8(0x55)
+#define Usage_PID_DirectionEnable Usage_i8(0x56)
+#define Usage_PID_Direction Usage_i8(0x57)
+#define Usage_PID_TypeSpecificBlockOffset Usage_i8(0x58)
+#define Usage_PID_BlockType Usage_i8(0x59)
+#define Usage_PID_SetEnvelopeReport Usage_i8(0x5a)
+#define Usage_PID_AttackLevel Usage_i8(0x5b)
+#define Usage_PID_AttackTime Usage_i8(0x5c)
+#define Usage_PID_FadeLevel Usage_i8(0x5d)
+#define Usage_PID_FadeTime Usage_i8(0x5e)
+#define Usage_PID_SetConditionReport Usage_i8(0x5f)
+#define Usage_PID_CenterPointOffset Usage_i8(0x60)
+#define Usage_PID_PositiveCoefficient Usage_i8(0x61)
+#define Usage_PID_NegativeCoefficient Usage_i8(0x62)
+#define Usage_PID_PositiveSaturation Usage_i8(0x63)
+#define Usage_PID_NegativeSaturation Usage_i8(0x64)
+#define Usage_PID_DeadBand Usage_i8(0x65)
+#define Usage_PID_DownloadForceSample Usage_i8(0x66)
+#define Usage_PID_IsochCustomForceEnable Usage_i8(0x67)
+#define Usage_PID_CustomForceDataReport Usage_i8(0x68)
+#define Usage_PID_CustomForceData Usage_i8(0x69)
+#define Usage_PID_CustomForceVendorDefinedData Usage_i8(0x6a)
+#define Usage_PID_SetCustomForceReport Usage_i8(0x6b)
+#define Usage_PID_CustomForceDataOffset Usage_i8(0x6c)
+#define Usage_PID_SampleCount Usage_i8(0x6d)
+#define Usage_PID_SetPeriodicReport Usage_i8(0x6e)
+#define Usage_PID_Offset Usage_i8(0x6f)
+#define Usage_PID_Magnitude Usage_i8(0x70)
+#define Usage_PID_Phase Usage_i8(0x71)
+#define Usage_PID_Period Usage_i8(0x72)
+#define Usage_PID_SetConstantForceReport Usage_i8(0x73)
+#define Usage_PID_SetRampForceReport Usage_i8(0x74)
+#define Usage_PID_RampStart Usage_i8(0x75)
+#define Usage_PID_RampEnd Usage_i8(0x76)
+#define Usage_PID_EffectOperationReport Usage_i8(0x77)
+#define Usage_PID_EffectOperation Usage_i8(0x78)
+#define Usage_PID_OpEffectStart Usage_i8(0x79)
+#define Usage_PID_OpEffectStartSolo Usage_i8(0x7a)
+#define Usage_PID_OpEffectStop Usage_i8(0x7b)
+#define Usage_PID_LoopCount Usage_i8(0x7c)
+#define Usage_PID_DeviceGainReport Usage_i8(0x7d)
+#define Usage_PID_DeviceGain Usage_i8(0x7e)
+#define Usage_PID_ParameterBlockPoolsReport Usage_i8(0x7f)
+#define Usage_PID_RAMPoolSize Usage_i8(0x80)
+#define Usage_PID_ROMPoolSize Usage_i8(0x81)
+#define Usage_PID_ROMEffectBlockCount Usage_i8(0x82)
+#define Usage_PID_SimultaneousEffectsMax Usage_i8(0x83)
+#define Usage_PID_PoolAlignment Usage_i8(0x84)
+#define Usage_PID_ParameterBlockMoveReport Usage_i8(0x85)
+#define Usage_PID_MoveSource Usage_i8(0x86)
+#define Usage_PID_MoveDestination Usage_i8(0x87)
+#define Usage_PID_MoveLength Usage_i8(0x88)
+#define Usage_PID_EffectParameterBlockLoadReport Usage_i8(0x89)
+#define Usage_PID_EffectParameterBlockLoadStatus Usage_i8(0x8b)
+#define Usage_PID_BlockLoadSuccess Usage_i8(0x8c)
+#define Usage_PID_BlockLoadFull Usage_i8(0x8d)
+#define Usage_PID_BlockLoadError Usage_i8(0x8e)
+#define Usage_PID_BlockHandle Usage_i8(0x8f)
+#define Usage_PID_EffectParameterBlockFreeReport Usage_i8(0x90)
+#define Usage_PID_TypeSpecificBlockHandle Usage_i8(0x91)
+#define Usage_PID_PIDStateReport Usage_i8(0x92)
+#define Usage_PID_EffectPlaying Usage_i8(0x94)
+#define Usage_PID_PIDDeviceControlReport Usage_i8(0x95)
+#define Usage_PID_PIDDeviceControl Usage_i8(0x96)
+#define Usage_PID_DCEnableActuators Usage_i8(0x97)
+#define Usage_PID_DCDisableActuators Usage_i8(0x98)
+#define Usage_PID_DCStopAllEffects Usage_i8(0x99)
+#define Usage_PID_DCReset Usage_i8(0x9a)
+#define Usage_PID_DCPause Usage_i8(0x9b)
+#define Usage_PID_DCContinue Usage_i8(0x9c)
+#define Usage_PID_DevicePaused Usage_i8(0x9f)
+#define Usage_PID_ActuatorsEnabled Usage_i8(0xa0)
+#define Usage_PID_SafetySwitch Usage_i8(0xa4)
+#define Usage_PID_ActuatorOverrideSwitch Usage_i8(0xa5)
+#define Usage_PID_ActuatorPower Usage_i8(0xa6)
+#define Usage_PID_StartDelay Usage_i8(0xa7)
+#define Usage_PID_ParameterBlockSize Usage_i8(0xa8)
+#define Usage_PID_DeviceManagedPool Usage_i8(0xa9)
+#define Usage_PID_SharedParameterBlocks Usage_i8(0xaa)
+#define Usage_PID_CreateNewEffectParameterBlockReport Usage_i8(0xab)
+#define Usage_PID_RAMPoolAvailable Usage_i8(0xac)
+#define Usage_SC_SocControl Usage_i8(0x1)
+#define Usage_SC_FirmwareTransfer Usage_i8(0x2)
+#define Usage_SC_FirmwareFileId Usage_i8(0x3)
+#define Usage_SC_FileOffsetInBytes Usage_i8(0x4)
+#define Usage_SC_FileTransferSizeMaxInBytes Usage_i8(0x5)
+#define Usage_SC_FilePayload Usage_i8(0x6)
+#define Usage_SC_FilePayloadSizeInBytes Usage_i8(0x7)
+#define Usage_SC_FilePayloadContainsLastBytes Usage_i8(0x8)
+#define Usage_SC_FileTransferStop Usage_i8(0x9)
+#define Usage_SC_FileTransferTillEnd Usage_i8(0xa)
+#define Usage_EHT_EyeTracker Usage_i8(0x1)
+#define Usage_EHT_HeadTracker Usage_i8(0x2)
+#define Usage_EHT_TrackingData Usage_i8(0x10)
+#define Usage_EHT_Capabilities Usage_i8(0x11)
+#define Usage_EHT_Configuration Usage_i8(0x12)
+#define Usage_EHT_Status Usage_i8(0x13)
+#define Usage_EHT_Control Usage_i8(0x14)
+#define Usage_EHT_SensorTimestamp Usage_i8(0x20)
+#define Usage_EHT_PositionX Usage_i8(0x21)
+#define Usage_EHT_PositionY Usage_i8(0x22)
+#define Usage_EHT_PositionZ Usage_i8(0x23)
+#define Usage_EHT_GazePoint Usage_i8(0x24)
+#define Usage_EHT_LeftEyePosition Usage_i8(0x25)
+#define Usage_EHT_RightEyePosition Usage_i8(0x26)
+#define Usage_EHT_HeadPosition Usage_i8(0x27)
+#define Usage_EHT_HeadDirectionPoint Usage_i8(0x28)
+#define Usage_EHT_RotationaboutXaxis Usage_i8(0x29)
+#define Usage_EHT_RotationaboutYaxis Usage_i8(0x2a)
+#define Usage_EHT_RotationaboutZaxis Usage_i8(0x2b)
+#define Usage_EHT_TrackerQuality Usage_i16(0x100)
+#define Usage_EHT_MinimumTrackingDistance Usage_i16(0x101)
+#define Usage_EHT_OptimumTrackingDistance Usage_i16(0x102)
+#define Usage_EHT_MaximumTrackingDistance Usage_i16(0x103)
+#define Usage_EHT_MaximumScreenPlaneWidth Usage_i16(0x104)
+#define Usage_EHT_MaximumScreenPlaneHeight Usage_i16(0x105)
+#define Usage_EHT_DisplayManufacturerID Usage_i16(0x200)
+#define Usage_EHT_DisplayProductID Usage_i16(0x201)
+#define Usage_EHT_DisplaySerialNumber Usage_i16(0x202)
+#define Usage_EHT_DisplayManufacturerDate Usage_i16(0x203)
+#define Usage_EHT_CalibratedScreenWidth Usage_i16(0x204)
+#define Usage_EHT_CalibratedScreenHeight Usage_i16(0x205)
+#define Usage_EHT_SamplingFrequency Usage_i16(0x300)
+#define Usage_EHT_ConfigurationStatus Usage_i16(0x301)
+#define Usage_EHT_DeviceModeRequest Usage_i16(0x400)
+#define Usage_AD_AlphanumericDisplay Usage_i8(0x1)
+#define Usage_AD_AuxiliaryDisplay Usage_i8(0x2)
+#define Usage_AD_DisplayAttributesReport Usage_i8(0x20)
+#define Usage_AD_ASCIICharacterSet Usage_i8(0x21)
+#define Usage_AD_DataReadBack Usage_i8(0x22)
+#define Usage_AD_FontReadBack Usage_i8(0x23)
+#define Usage_AD_DisplayControlReport Usage_i8(0x24)
+#define Usage_AD_ClearDisplay Usage_i8(0x25)
+#define Usage_AD_DisplayEnable Usage_i8(0x26)
+#define Usage_AD_ScreenSaverDelay Usage_i8(0x27)
+#define Usage_AD_ScreenSaverEnable Usage_i8(0x28)
+#define Usage_AD_VerticalScroll Usage_i8(0x29)
+#define Usage_AD_HorizontalScroll Usage_i8(0x2a)
+#define Usage_AD_CharacterReport Usage_i8(0x2b)
+#define Usage_AD_DisplayData Usage_i8(0x2c)
+#define Usage_AD_DisplayStatus Usage_i8(0x2d)
+#define Usage_AD_StatNotReady Usage_i8(0x2e)
+#define Usage_AD_StatReady Usage_i8(0x2f)
+#define Usage_AD_ErrNotaloadablecharacter Usage_i8(0x30)
+#define Usage_AD_ErrFontdatacannotberead Usage_i8(0x31)
+#define Usage_AD_CursorPositionReport Usage_i8(0x32)
+#define Usage_AD_Row Usage_i8(0x33)
+#define Usage_AD_Column Usage_i8(0x34)
+#define Usage_AD_Rows Usage_i8(0x35)
+#define Usage_AD_Columns Usage_i8(0x36)
+#define Usage_AD_CursorPixelPositioning Usage_i8(0x37)
+#define Usage_AD_CursorMode Usage_i8(0x38)
+#define Usage_AD_CursorEnable Usage_i8(0x39)
+#define Usage_AD_CursorBlink Usage_i8(0x3a)
+#define Usage_AD_FontReport Usage_i8(0x3b)
+#define Usage_AD_FontData Usage_i8(0x3c)
+#define Usage_AD_CharacterWidth Usage_i8(0x3d)
+#define Usage_AD_CharacterHeight Usage_i8(0x3e)
+#define Usage_AD_CharacterSpacingHorizontal Usage_i8(0x3f)
+#define Usage_AD_CharacterSpacingVertical Usage_i8(0x40)
+#define Usage_AD_UnicodeCharacterSet Usage_i8(0x41)
+#define Usage_AD_Font7Segment Usage_i8(0x42)
+#define Usage_AD_SevenSegmentDirectMap Usage_i8(0x43)
+#define Usage_AD_Font14Segment Usage_i8(0x44)
+#define Usage_AD_One4SegmentDirectMap Usage_i8(0x45)
+#define Usage_AD_DisplayBrightness Usage_i8(0x46)
+#define Usage_AD_DisplayContrast Usage_i8(0x47)
+#define Usage_AD_CharacterAttribute Usage_i8(0x48)
+#define Usage_AD_AttributeReadback Usage_i8(0x49)
+#define Usage_AD_AttributeData Usage_i8(0x4a)
+#define Usage_AD_CharAttrEnhance Usage_i8(0x4b)
+#define Usage_AD_CharAttrUnderline Usage_i8(0x4c)
+#define Usage_AD_CharAttrBlink Usage_i8(0x4d)
+#define Usage_AD_BitmapSizeX Usage_i8(0x80)
+#define Usage_AD_BitmapSizeY Usage_i8(0x81)
+#define Usage_AD_MaxBlitSize Usage_i8(0x82)
+#define Usage_AD_BitDepthFormat Usage_i8(0x83)
+#define Usage_AD_DisplayOrientation Usage_i8(0x84)
+#define Usage_AD_PaletteReport Usage_i8(0x85)
+#define Usage_AD_PaletteDataSize Usage_i8(0x86)
+#define Usage_AD_PaletteDataOffset Usage_i8(0x87)
+#define Usage_AD_PaletteData Usage_i8(0x88)
+#define Usage_AD_BlitReport Usage_i8(0x8a)
+#define Usage_AD_BlitRectangleX1 Usage_i8(0x8b)
+#define Usage_AD_BlitRectangleY1 Usage_i8(0x8c)
+#define Usage_AD_BlitRectangleX2 Usage_i8(0x8d)
+#define Usage_AD_BlitRectangleY2 Usage_i8(0x8e)
+#define Usage_AD_BlitData Usage_i8(0x8f)
+#define Usage_AD_SoftButton Usage_i8(0x90)
+#define Usage_AD_SoftButtonID Usage_i8(0x91)
+#define Usage_AD_SoftButtonSide Usage_i8(0x92)
+#define Usage_AD_SoftButtonOffset1 Usage_i8(0x93)
+#define Usage_AD_SoftButtonOffset2 Usage_i8(0x94)
+#define Usage_AD_SoftButtonReport Usage_i8(0x95)
+#define Usage_AD_SoftKeys Usage_i8(0xc2)
+#define Usage_AD_DisplayDataExtensions Usage_i8(0xcc)
+#define Usage_AD_CharacterMapping Usage_i8(0xcf)
+#define Usage_AD_UnicodeEquivalent Usage_i8(0xdd)
+#define Usage_AD_CharacterPageMapping Usage_i8(0xdf)
+#define Usage_AD_RequestReport Usage_i16(0xff)
+#define Usage_Sen_Sensor Usage_i8(0x1)
+#define Usage_Sen_Biometric Usage_i8(0x10)
+#define Usage_Sen_BiometricHumanPresence Usage_i8(0x11)
+#define Usage_Sen_BiometricHumanProximity Usage_i8(0x12)
+#define Usage_Sen_BiometricHumanTouch Usage_i8(0x13)
+#define Usage_Sen_BiometricBloodPressure Usage_i8(0x14)
+#define Usage_Sen_BiometricBodyTemperature Usage_i8(0x15)
+#define Usage_Sen_BiometricHeartRate Usage_i8(0x16)
+#define Usage_Sen_BiometricHeartRateVariability Usage_i8(0x17)
+#define Usage_Sen_BiometricPeripheralOxygenSaturation Usage_i8(0x18)
+#define Usage_Sen_BiometricRespiratoryRate Usage_i8(0x19)
+#define Usage_Sen_Electrical Usage_i8(0x20)
+#define Usage_Sen_ElectricalCapacitance Usage_i8(0x21)
+#define Usage_Sen_ElectricalCurrent Usage_i8(0x22)
+#define Usage_Sen_ElectricalPower Usage_i8(0x23)
+#define Usage_Sen_ElectricalInductance Usage_i8(0x24)
+#define Usage_Sen_ElectricalResistance Usage_i8(0x25)
+#define Usage_Sen_ElectricalVoltage Usage_i8(0x26)
+#define Usage_Sen_ElectricalPotentiometer Usage_i8(0x27)
+#define Usage_Sen_ElectricalFrequency Usage_i8(0x28)
+#define Usage_Sen_ElectricalPeriod Usage_i8(0x29)
+#define Usage_Sen_Environmental Usage_i8(0x30)
+#define Usage_Sen_EnvironmentalAtmosphericPressure Usage_i8(0x31)
+#define Usage_Sen_EnvironmentalHumidity Usage_i8(0x32)
+#define Usage_Sen_EnvironmentalTemperature Usage_i8(0x33)
+#define Usage_Sen_EnvironmentalWindDirection Usage_i8(0x34)
+#define Usage_Sen_EnvironmentalWindSpeed Usage_i8(0x35)
+#define Usage_Sen_EnvironmentalAirQuality Usage_i8(0x36)
+#define Usage_Sen_EnvironmentalHeatIndex Usage_i8(0x37)
+#define Usage_Sen_EnvironmentalSurfaceTemperature Usage_i8(0x38)
+#define Usage_Sen_EnvironmentalVolatileOrganicCompounds Usage_i8(0x39)
+#define Usage_Sen_EnvironmentalObjectPresence Usage_i8(0x3a)
+#define Usage_Sen_EnvironmentalObjectProximity Usage_i8(0x3b)
+#define Usage_Sen_Light Usage_i8(0x40)
+#define Usage_Sen_LightAmbientLight Usage_i8(0x41)
+#define Usage_Sen_LightConsumerInfrared Usage_i8(0x42)
+#define Usage_Sen_LightInfraredLight Usage_i8(0x43)
+#define Usage_Sen_LightVisibleLight Usage_i8(0x44)
+#define Usage_Sen_LightUltravioletLight Usage_i8(0x45)
+#define Usage_Sen_Location Usage_i8(0x50)
+#define Usage_Sen_LocationBroadcast Usage_i8(0x51)
+#define Usage_Sen_LocationDeadReckoning Usage_i8(0x52)
+#define Usage_Sen_LocationGPSGlobalPositioningSystem Usage_i8(0x53)
+#define Usage_Sen_LocationLookup Usage_i8(0x54)
+#define Usage_Sen_LocationOther Usage_i8(0x55)
+#define Usage_Sen_LocationStatic Usage_i8(0x56)
+#define Usage_Sen_LocationTriangulation Usage_i8(0x57)
+#define Usage_Sen_Mechanical Usage_i8(0x60)
+#define Usage_Sen_MechanicalBooleanSwitch Usage_i8(0x61)
+#define Usage_Sen_MechanicalBooleanSwitchArray Usage_i8(0x62)
+#define Usage_Sen_MechanicalMultivalueSwitch Usage_i8(0x63)
+#define Usage_Sen_MechanicalForce Usage_i8(0x64)
+#define Usage_Sen_MechanicalPressure Usage_i8(0x65)
+#define Usage_Sen_MechanicalStrain Usage_i8(0x66)
+#define Usage_Sen_MechanicalWeight Usage_i8(0x67)
+#define Usage_Sen_MechanicalHapticVibrator Usage_i8(0x68)
+#define Usage_Sen_MechanicalHallEffectSwitch Usage_i8(0x69)
+#define Usage_Sen_Motion Usage_i8(0x70)
+#define Usage_Sen_MotionAccelerometer1D Usage_i8(0x71)
+#define Usage_Sen_MotionAccelerometer2D Usage_i8(0x72)
+#define Usage_Sen_MotionAccelerometer3D Usage_i8(0x73)
+#define Usage_Sen_MotionGyrometer1D Usage_i8(0x74)
+#define Usage_Sen_MotionGyrometer2D Usage_i8(0x75)
+#define Usage_Sen_MotionGyrometer3D Usage_i8(0x76)
+#define Usage_Sen_MotionMotionDetector Usage_i8(0x77)
+#define Usage_Sen_MotionSpeedometer Usage_i8(0x78)
+#define Usage_Sen_MotionAccelerometer Usage_i8(0x79)
+#define Usage_Sen_MotionGyrometer Usage_i8(0x7a)
+#define Usage_Sen_MotionGravityVector Usage_i8(0x7b)
+#define Usage_Sen_MotionLinearAccelerometer Usage_i8(0x7c)
+#define Usage_Sen_Orientation Usage_i8(0x80)
+#define Usage_Sen_OrientationCompass1D Usage_i8(0x81)
+#define Usage_Sen_OrientationCompass2D Usage_i8(0x82)
+#define Usage_Sen_OrientationCompass3D Usage_i8(0x83)
+#define Usage_Sen_OrientationInclinometer1D Usage_i8(0x84)
+#define Usage_Sen_OrientationInclinometer2D Usage_i8(0x85)
+#define Usage_Sen_OrientationInclinometer3D Usage_i8(0x86)
+#define Usage_Sen_OrientationDistance1D Usage_i8(0x87)
+#define Usage_Sen_OrientationDistance2D Usage_i8(0x88)
+#define Usage_Sen_OrientationDistance3D Usage_i8(0x89)
+#define Usage_Sen_OrientationDeviceOrientation Usage_i8(0x8a)
+#define Usage_Sen_OrientationCompass Usage_i8(0x8b)
+#define Usage_Sen_OrientationInclinometer Usage_i8(0x8c)
+#define Usage_Sen_OrientationDistance Usage_i8(0x8d)
+#define Usage_Sen_OrientationRelativeOrientation Usage_i8(0x8e)
+#define Usage_Sen_OrientationSimpleOrientation Usage_i8(0x8f)
+#define Usage_Sen_Scanner Usage_i8(0x90)
+#define Usage_Sen_ScannerBarcode Usage_i8(0x91)
+#define Usage_Sen_ScannerRFID Usage_i8(0x92)
+#define Usage_Sen_ScannerNFC Usage_i8(0x93)
+#define Usage_Sen_Time Usage_i8(0xa0)
+#define Usage_Sen_TimeAlarmTimer Usage_i8(0xa1)
+#define Usage_Sen_TimeRealTimeClock Usage_i8(0xa2)
+#define Usage_Sen_PersonalActivity Usage_i8(0xb0)
+#define Usage_Sen_PersonalActivityActivityDetection Usage_i8(0xb1)
+#define Usage_Sen_PersonalActivityDevicePosition Usage_i8(0xb2)
+#define Usage_Sen_PersonalActivityFloorTracker Usage_i8(0xb3)
+#define Usage_Sen_PersonalActivityPedometer Usage_i8(0xb4)
+#define Usage_Sen_PersonalActivityStepDetection Usage_i8(0xb5)
+#define Usage_Sen_OrientationExtended Usage_i8(0xc0)
+#define Usage_Sen_OrientationExtendedGeomagneticOrientation Usage_i8(0xc1)
+#define Usage_Sen_OrientationExtendedMagnetometer Usage_i8(0xc2)
+#define Usage_Sen_Gesture Usage_i8(0xd0)
+#define Usage_Sen_GestureChassisFlipGesture Usage_i8(0xd1)
+#define Usage_Sen_GestureHingeFoldGesture Usage_i8(0xd2)
+#define Usage_Sen_Other Usage_i8(0xe0)
+#define Usage_Sen_OtherCustom Usage_i8(0xe1)
+#define Usage_Sen_OtherGeneric Usage_i8(0xe2)
+#define Usage_Sen_OtherGenericEnumerator Usage_i8(0xe3)
+#define Usage_Sen_OtherHingeAngle Usage_i8(0xe4)
+#define Usage_Sen_VendorReserved1 Usage_i8(0xf0)
+#define Usage_Sen_VendorReserved2 Usage_i8(0xf1)
+#define Usage_Sen_VendorReserved3 Usage_i8(0xf2)
+#define Usage_Sen_VendorReserved4 Usage_i8(0xf3)
+#define Usage_Sen_VendorReserved5 Usage_i8(0xf4)
+#define Usage_Sen_VendorReserved6 Usage_i8(0xf5)
+#define Usage_Sen_VendorReserved7 Usage_i8(0xf6)
+#define Usage_Sen_VendorReserved8 Usage_i8(0xf7)
+#define Usage_Sen_VendorReserved9 Usage_i8(0xf8)
+#define Usage_Sen_VendorReserved10 Usage_i8(0xf9)
+#define Usage_Sen_VendorReserved11 Usage_i8(0xfa)
+#define Usage_Sen_VendorReserved12 Usage_i8(0xfb)
+#define Usage_Sen_VendorReserved13 Usage_i8(0xfc)
+#define Usage_Sen_VendorReserved14 Usage_i8(0xfd)
+#define Usage_Sen_VendorReserved15 Usage_i8(0xfe)
+#define Usage_Sen_VendorReserved16 Usage_i16(0xff)
+#define Usage_Sen_Event Usage_i16(0x200)
+#define Usage_Sen_EventSensorState Usage_i16(0x201)
+#define Usage_Sen_EventSensorEvent Usage_i16(0x202)
+#define Usage_Sen_Property Usage_i16(0x300)
+#define Usage_Sen_PropertyFriendlyName Usage_i16(0x301)
+#define Usage_Sen_PropertyPersistentUniqueID Usage_i16(0x302)
+#define Usage_Sen_PropertySensorStatus Usage_i16(0x303)
+#define Usage_Sen_PropertyMinimumReportInterval Usage_i16(0x304)
+#define Usage_Sen_PropertySensorManufacturer Usage_i16(0x305)
+#define Usage_Sen_PropertySensorModel Usage_i16(0x306)
+#define Usage_Sen_PropertySensorSerialNumber Usage_i16(0x307)
+#define Usage_Sen_PropertySensorDescription Usage_i16(0x308)
+#define Usage_Sen_PropertySensorConnectionType Usage_i16(0x309)
+#define Usage_Sen_PropertySensorDevicePath Usage_i16(0x30a)
+#define Usage_Sen_PropertyHardwareRevision Usage_i16(0x30b)
+#define Usage_Sen_PropertyFirmwareVersion Usage_i16(0x30c)
+#define Usage_Sen_PropertyReleaseDate Usage_i16(0x30d)
+#define Usage_Sen_PropertyReportInterval Usage_i16(0x30e)
+#define Usage_Sen_PropertyChangeSensitivityAbsolute Usage_i16(0x30f)
+#define Usage_Sen_PropertyChangeSensitivityPercentofRange Usage_i16(0x310)
+#define Usage_Sen_PropertyChangeSensitivityPercentRelative Usage_i16(0x311)
+#define Usage_Sen_PropertyAccuracy Usage_i16(0x312)
+#define Usage_Sen_PropertyResolution Usage_i16(0x313)
+#define Usage_Sen_PropertyMaximum Usage_i16(0x314)
+#define Usage_Sen_PropertyMinimum Usage_i16(0x315)
+#define Usage_Sen_PropertyReportingState Usage_i16(0x316)
+#define Usage_Sen_PropertySamplingRate Usage_i16(0x317)
+#define Usage_Sen_PropertyResponseCurve Usage_i16(0x318)
+#define Usage_Sen_PropertyPowerState Usage_i16(0x319)
+#define Usage_Sen_PropertyMaximumFIFOEvents Usage_i16(0x31a)
+#define Usage_Sen_PropertyReportLatency Usage_i16(0x31b)
+#define Usage_Sen_PropertyFlushFIFOEvents Usage_i16(0x31c)
+#define Usage_Sen_PropertyMaximumPowerConsumption Usage_i16(0x31d)
+#define Usage_Sen_PropertyIsPrimary Usage_i16(0x31e)
+#define Usage_Sen_PropertyHumanPresenceDetectionType Usage_i16(0x31f)
+#define Usage_Sen_DataFieldLocation Usage_i16(0x400)
+#define Usage_Sen_DataFieldAltitudeAntennaSeaLevel Usage_i16(0x402)
+#define Usage_Sen_DataFieldDifferentialReferenceStationID Usage_i16(0x403)
+#define Usage_Sen_DataFieldAltitudeEllipsoidError Usage_i16(0x404)
+#define Usage_Sen_DataFieldAltitudeEllipsoid Usage_i16(0x405)
+#define Usage_Sen_DataFieldAltitudeSeaLevelError Usage_i16(0x406)
+#define Usage_Sen_DataFieldAltitudeSeaLevel Usage_i16(0x407)
+#define Usage_Sen_DataFieldDifferentialGPSDataAge Usage_i16(0x408)
+#define Usage_Sen_DataFieldErrorRadius Usage_i16(0x409)
+#define Usage_Sen_DataFieldFixQuality Usage_i16(0x40a)
+#define Usage_Sen_DataFieldFixType Usage_i16(0x40b)
+#define Usage_Sen_DataFieldGeoidalSeparation Usage_i16(0x40c)
+#define Usage_Sen_DataFieldGPSOperationMode Usage_i16(0x40d)
+#define Usage_Sen_DataFieldGPSSelectionMode Usage_i16(0x40e)
+#define Usage_Sen_DataFieldGPSStatus Usage_i16(0x40f)
+#define Usage_Sen_DataFieldPositionDilutionofPrecision Usage_i16(0x410)
+#define Usage_Sen_DataFieldHorizontalDilutionofPrecision Usage_i16(0x411)
+#define Usage_Sen_DataFieldVerticalDilutionofPrecision Usage_i16(0x412)
+#define Usage_Sen_DataFieldLatitude Usage_i16(0x413)
+#define Usage_Sen_DataFieldLongitude Usage_i16(0x414)
+#define Usage_Sen_DataFieldTrueHeading Usage_i16(0x415)
+#define Usage_Sen_DataFieldMagneticHeading Usage_i16(0x416)
+#define Usage_Sen_DataFieldMagneticVariation Usage_i16(0x417)
+#define Usage_Sen_DataFieldSpeed Usage_i16(0x418)
+#define Usage_Sen_DataFieldSatellitesinView Usage_i16(0x419)
+#define Usage_Sen_DataFieldSatellitesinViewAzimuth Usage_i16(0x41a)
+#define Usage_Sen_DataFieldSatellitesinViewElevation Usage_i16(0x41b)
+#define Usage_Sen_DataFieldSatellitesinViewIDs Usage_i16(0x41c)
+#define Usage_Sen_DataFieldSatellitesinViewPRNs Usage_i16(0x41d)
+#define Usage_Sen_DataFieldSatellitesinViewSNRatios Usage_i16(0x41e)
+#define Usage_Sen_DataFieldSatellitesUsedCount Usage_i16(0x41f)
+#define Usage_Sen_DataFieldSatellitesUsedPRNs Usage_i16(0x420)
+#define Usage_Sen_DataFieldNMEASentence Usage_i16(0x421)
+#define Usage_Sen_DataFieldAddressLine1 Usage_i16(0x422)
+#define Usage_Sen_DataFieldAddressLine2 Usage_i16(0x423)
+#define Usage_Sen_DataFieldCity Usage_i16(0x424)
+#define Usage_Sen_DataFieldStateorProvince Usage_i16(0x425)
+#define Usage_Sen_DataFieldCountryorRegion Usage_i16(0x426)
+#define Usage_Sen_DataFieldPostalCode Usage_i16(0x427)
+#define Usage_Sen_PropertyLocation Usage_i16(0x42a)
+#define Usage_Sen_PropertyLocationDesiredAccuracy Usage_i16(0x42b)
+#define Usage_Sen_DataFieldEnvironmental Usage_i16(0x430)
+#define Usage_Sen_DataFieldAtmosphericPressure Usage_i16(0x431)
+#define Usage_Sen_DataFieldRelativeHumidity Usage_i16(0x433)
+#define Usage_Sen_DataFieldTemperature Usage_i16(0x434)
+#define Usage_Sen_DataFieldWindDirection Usage_i16(0x435)
+#define Usage_Sen_DataFieldWindSpeed Usage_i16(0x436)
+#define Usage_Sen_DataFieldAirQualityIndex Usage_i16(0x437)
+#define Usage_Sen_DataFieldEquivalentCO2 Usage_i16(0x438)
+#define Usage_Sen_DataFieldVolatileOrganicCompoundConcentration Usage_i16(0x439)
+#define Usage_Sen_DataFieldObjectPresence Usage_i16(0x43a)
+#define Usage_Sen_DataFieldObjectProximityRange Usage_i16(0x43b)
+#define Usage_Sen_DataFieldObjectProximityOutofRange Usage_i16(0x43c)
+#define Usage_Sen_PropertyEnvironmental Usage_i16(0x440)
+#define Usage_Sen_PropertyReferencePressure Usage_i16(0x441)
+#define Usage_Sen_DataFieldMotion Usage_i16(0x450)
+#define Usage_Sen_DataFieldMotionState Usage_i16(0x451)
+#define Usage_Sen_DataFieldAcceleration Usage_i16(0x452)
+#define Usage_Sen_DataFieldAccelerationAxisX Usage_i16(0x453)
+#define Usage_Sen_DataFieldAccelerationAxisY Usage_i16(0x454)
+#define Usage_Sen_DataFieldAccelerationAxisZ Usage_i16(0x455)
+#define Usage_Sen_DataFieldAngularVelocity Usage_i16(0x456)
+#define Usage_Sen_DataFieldAngularVelocityaboutXAxis Usage_i16(0x457)
+#define Usage_Sen_DataFieldAngularVelocityaboutYAxis Usage_i16(0x458)
+#define Usage_Sen_DataFieldAngularVelocityaboutZAxis Usage_i16(0x459)
+#define Usage_Sen_DataFieldAngularPosition Usage_i16(0x45a)
+#define Usage_Sen_DataFieldAngularPositionaboutXAxis Usage_i16(0x45b)
+#define Usage_Sen_DataFieldAngularPositionaboutYAxis Usage_i16(0x45c)
+#define Usage_Sen_DataFieldAngularPositionaboutZAxis Usage_i16(0x45d)
+#define Usage_Sen_DataFieldMotionSpeed Usage_i16(0x45e)
+#define Usage_Sen_DataFieldMotionIntensity Usage_i16(0x45f)
+#define Usage_Sen_DataFieldOrientation Usage_i16(0x470)
+#define Usage_Sen_DataFieldHeading Usage_i16(0x471)
+#define Usage_Sen_DataFieldHeadingXAxis Usage_i16(0x472)
+#define Usage_Sen_DataFieldHeadingYAxis Usage_i16(0x473)
+#define Usage_Sen_DataFieldHeadingZAxis Usage_i16(0x474)
+#define Usage_Sen_DataFieldHeadingCompensatedMagneticNorth Usage_i16(0x475)
+#define Usage_Sen_DataFieldHeadingCompensatedTrueNorth Usage_i16(0x476)
+#define Usage_Sen_DataFieldHeadingMagneticNorth Usage_i16(0x477)
+#define Usage_Sen_DataFieldHeadingTrueNorth Usage_i16(0x478)
+#define Usage_Sen_DataFieldDistance Usage_i16(0x479)
+#define Usage_Sen_DataFieldDistanceXAxis Usage_i16(0x47a)
+#define Usage_Sen_DataFieldDistanceYAxis Usage_i16(0x47b)
+#define Usage_Sen_DataFieldDistanceZAxis Usage_i16(0x47c)
+#define Usage_Sen_DataFieldDistanceOutofRange Usage_i16(0x47d)
+#define Usage_Sen_DataFieldTilt Usage_i16(0x47e)
+#define Usage_Sen_DataFieldTiltXAxis Usage_i16(0x47f)
+#define Usage_Sen_DataFieldTiltYAxis Usage_i16(0x480)
+#define Usage_Sen_DataFieldTiltZAxis Usage_i16(0x481)
+#define Usage_Sen_DataFieldRotationMatrix Usage_i16(0x482)
+#define Usage_Sen_DataFieldQuaternion Usage_i16(0x483)
+#define Usage_Sen_DataFieldMagneticFlux Usage_i16(0x484)
+#define Usage_Sen_DataFieldMagneticFluxXAxis Usage_i16(0x485)
+#define Usage_Sen_DataFieldMagneticFluxYAxis Usage_i16(0x486)
+#define Usage_Sen_DataFieldMagneticFluxZAxis Usage_i16(0x487)
+#define Usage_Sen_DataFieldMagnetometerAccuracy Usage_i16(0x488)
+#define Usage_Sen_DataFieldSimpleOrientationDirection Usage_i16(0x489)
+#define Usage_Sen_DataFieldMechanical Usage_i16(0x490)
+#define Usage_Sen_DataFieldBooleanSwitchState Usage_i16(0x491)
+#define Usage_Sen_DataFieldBooleanSwitchArrayStates Usage_i16(0x492)
+#define Usage_Sen_DataFieldMultivalueSwitchValue Usage_i16(0x493)
+#define Usage_Sen_DataFieldForce Usage_i16(0x494)
+#define Usage_Sen_DataFieldAbsolutePressure Usage_i16(0x495)
+#define Usage_Sen_DataFieldGaugePressure Usage_i16(0x496)
+#define Usage_Sen_DataFieldStrain Usage_i16(0x497)
+#define Usage_Sen_DataFieldWeight Usage_i16(0x498)
+#define Usage_Sen_PropertyMechanical Usage_i16(0x4a0)
+#define Usage_Sen_PropertyVibrationState Usage_i16(0x4a1)
+#define Usage_Sen_PropertyForwardVibrationSpeed Usage_i16(0x4a2)
+#define Usage_Sen_PropertyBackwardVibrationSpeed Usage_i16(0x4a3)
+#define Usage_Sen_DataFieldBiometric Usage_i16(0x4b0)
+#define Usage_Sen_DataFieldHumanPresence Usage_i16(0x4b1)
+#define Usage_Sen_DataFieldHumanProximityRange Usage_i16(0x4b2)
+#define Usage_Sen_DataFieldHumanProximityOutofRange Usage_i16(0x4b3)
+#define Usage_Sen_DataFieldHumanTouchState Usage_i16(0x4b4)
+#define Usage_Sen_DataFieldBloodPressure Usage_i16(0x4b5)
+#define Usage_Sen_DataFieldBloodPressureDiastolic Usage_i16(0x4b6)
+#define Usage_Sen_DataFieldBloodPressureSystolic Usage_i16(0x4b7)
+#define Usage_Sen_DataFieldHeartRate Usage_i16(0x4b8)
+#define Usage_Sen_DataFieldRestingHeartRate Usage_i16(0x4b9)
+#define Usage_Sen_DataFieldHeartbeatInterval Usage_i16(0x4ba)
+#define Usage_Sen_DataFieldRespiratoryRate Usage_i16(0x4bb)
+#define Usage_Sen_DataFieldSpO2 Usage_i16(0x4bc)
+#define Usage_Sen_DataFieldHumanAttentionDetected Usage_i16(0x4bd)
+#define Usage_Sen_DataFieldHumanHeadAzimuth Usage_i16(0x4be)
+#define Usage_Sen_DataFieldHumanHeadAltitude Usage_i16(0x4bf)
+#define Usage_Sen_DataFieldHumanHeadRoll Usage_i16(0x4c0)
+#define Usage_Sen_DataFieldHumanHeadPitch Usage_i16(0x4c1)
+#define Usage_Sen_DataFieldHumanHeadYaw Usage_i16(0x4c2)
+#define Usage_Sen_DataFieldHumanCorrelationId Usage_i16(0x4c3)
+#define Usage_Sen_DataFieldLight Usage_i16(0x4d0)
+#define Usage_Sen_DataFieldIlluminance Usage_i16(0x4d1)
+#define Usage_Sen_DataFieldColorTemperature Usage_i16(0x4d2)
+#define Usage_Sen_DataFieldChromaticity Usage_i16(0x4d3)
+#define Usage_Sen_DataFieldChromaticityX Usage_i16(0x4d4)
+#define Usage_Sen_DataFieldChromaticityY Usage_i16(0x4d5)
+#define Usage_Sen_DataFieldConsumerIRSentenceReceive Usage_i16(0x4d6)
+#define Usage_Sen_DataFieldInfraredLight Usage_i16(0x4d7)
+#define Usage_Sen_DataFieldRedLight Usage_i16(0x4d8)
+#define Usage_Sen_DataFieldGreenLight Usage_i16(0x4d9)
+#define Usage_Sen_DataFieldBlueLight Usage_i16(0x4da)
+#define Usage_Sen_DataFieldUltravioletALight Usage_i16(0x4db)
+#define Usage_Sen_DataFieldUltravioletBLight Usage_i16(0x4dc)
+#define Usage_Sen_DataFieldUltravioletIndex Usage_i16(0x4dd)
+#define Usage_Sen_DataFieldNearInfraredLight Usage_i16(0x4de)
+#define Usage_Sen_PropertyLight Usage_i16(0x4df)
+#define Usage_Sen_PropertyConsumerIRSentenceSend Usage_i16(0x4e0)
+#define Usage_Sen_PropertyAutoBrightnessPreferred Usage_i16(0x4e2)
+#define Usage_Sen_PropertyAutoColorPreferred Usage_i16(0x4e3)
+#define Usage_Sen_DataFieldScanner Usage_i16(0x4f0)
+#define Usage_Sen_DataFieldRFIDTag40Bit Usage_i16(0x4f1)
+#define Usage_Sen_DataFieldNFCSentenceReceive Usage_i16(0x4f2)
+#define Usage_Sen_PropertyScanner Usage_i16(0x4f8)
+#define Usage_Sen_PropertyNFCSentenceSend Usage_i16(0x4f9)
+#define Usage_Sen_DataFieldElectrical Usage_i16(0x500)
+#define Usage_Sen_DataFieldCapacitance Usage_i16(0x501)
+#define Usage_Sen_DataFieldCurrent Usage_i16(0x502)
+#define Usage_Sen_DataFieldElectricalPower Usage_i16(0x503)
+#define Usage_Sen_DataFieldInductance Usage_i16(0x504)
+#define Usage_Sen_DataFieldResistance Usage_i16(0x505)
+#define Usage_Sen_DataFieldVoltage Usage_i16(0x506)
+#define Usage_Sen_DataFieldFrequency Usage_i16(0x507)
+#define Usage_Sen_DataFieldPeriod Usage_i16(0x508)
+#define Usage_Sen_DataFieldPercentofRange Usage_i16(0x509)
+#define Usage_Sen_DataFieldTime Usage_i16(0x520)
+#define Usage_Sen_DataFieldYear Usage_i16(0x521)
+#define Usage_Sen_DataFieldMonth Usage_i16(0x522)
+#define Usage_Sen_DataFieldDay Usage_i16(0x523)
+#define Usage_Sen_DataFieldDayofWeek Usage_i16(0x524)
+#define Usage_Sen_DataFieldHour Usage_i16(0x525)
+#define Usage_Sen_DataFieldMinute Usage_i16(0x526)
+#define Usage_Sen_DataFieldSecond Usage_i16(0x527)
+#define Usage_Sen_DataFieldMillisecond Usage_i16(0x528)
+#define Usage_Sen_DataFieldTimestamp Usage_i16(0x529)
+#define Usage_Sen_DataFieldJulianDayofYear Usage_i16(0x52a)
+#define Usage_Sen_DataFieldTimeSinceSystemBoot Usage_i16(0x52b)
+#define Usage_Sen_PropertyTime Usage_i16(0x530)
+#define Usage_Sen_PropertyTimeZoneOffsetfromUTC Usage_i16(0x531)
+#define Usage_Sen_PropertyTimeZoneName Usage_i16(0x532)
+#define Usage_Sen_PropertyDaylightSavingsTimeObserved Usage_i16(0x533)
+#define Usage_Sen_PropertyTimeTrimAdjustment Usage_i16(0x534)
+#define Usage_Sen_PropertyArmAlarm Usage_i16(0x535)
+#define Usage_Sen_DataFieldCustom Usage_i16(0x540)
+#define Usage_Sen_DataFieldCustomUsage Usage_i16(0x541)
+#define Usage_Sen_DataFieldCustomBooleanArray Usage_i16(0x542)
+#define Usage_Sen_DataFieldCustomValue Usage_i16(0x543)
+#define Usage_Sen_DataFieldCustomValue1 Usage_i16(0x544)
+#define Usage_Sen_DataFieldCustomValue2 Usage_i16(0x545)
+#define Usage_Sen_DataFieldCustomValue3 Usage_i16(0x546)
+#define Usage_Sen_DataFieldCustomValue4 Usage_i16(0x547)
+#define Usage_Sen_DataFieldCustomValue5 Usage_i16(0x548)
+#define Usage_Sen_DataFieldCustomValue6 Usage_i16(0x549)
+#define Usage_Sen_DataFieldCustomValue7 Usage_i16(0x54a)
+#define Usage_Sen_DataFieldCustomValue8 Usage_i16(0x54b)
+#define Usage_Sen_DataFieldCustomValue9 Usage_i16(0x54c)
+#define Usage_Sen_DataFieldCustomValue10 Usage_i16(0x54d)
+#define Usage_Sen_DataFieldCustomValue11 Usage_i16(0x54e)
+#define Usage_Sen_DataFieldCustomValue12 Usage_i16(0x54f)
+#define Usage_Sen_DataFieldCustomValue13 Usage_i16(0x550)
+#define Usage_Sen_DataFieldCustomValue14 Usage_i16(0x551)
+#define Usage_Sen_DataFieldCustomValue15 Usage_i16(0x552)
+#define Usage_Sen_DataFieldCustomValue16 Usage_i16(0x553)
+#define Usage_Sen_DataFieldCustomValue17 Usage_i16(0x554)
+#define Usage_Sen_DataFieldCustomValue18 Usage_i16(0x555)
+#define Usage_Sen_DataFieldCustomValue19 Usage_i16(0x556)
+#define Usage_Sen_DataFieldCustomValue20 Usage_i16(0x557)
+#define Usage_Sen_DataFieldCustomValue21 Usage_i16(0x558)
+#define Usage_Sen_DataFieldCustomValue22 Usage_i16(0x559)
+#define Usage_Sen_DataFieldCustomValue23 Usage_i16(0x55a)
+#define Usage_Sen_DataFieldCustomValue24 Usage_i16(0x55b)
+#define Usage_Sen_DataFieldCustomValue25 Usage_i16(0x55c)
+#define Usage_Sen_DataFieldCustomValue26 Usage_i16(0x55d)
+#define Usage_Sen_DataFieldCustomValue27 Usage_i16(0x55e)
+#define Usage_Sen_DataFieldCustomValue28 Usage_i16(0x55f)
+#define Usage_Sen_DataFieldGeneric Usage_i16(0x560)
+#define Usage_Sen_DataFieldGenericGUIDorPROPERTYKEY Usage_i16(0x561)
+#define Usage_Sen_DataFieldGenericCategoryGUID Usage_i16(0x562)
+#define Usage_Sen_DataFieldGenericTypeGUID Usage_i16(0x563)
+#define Usage_Sen_DataFieldGenericEventPROPERTYKEY Usage_i16(0x564)
+#define Usage_Sen_DataFieldGenericPropertyPROPERTYKEY Usage_i16(0x565)
+#define Usage_Sen_DataFieldGenericDataFieldPROPERTYKEY Usage_i16(0x566)
+#define Usage_Sen_DataFieldGenericEvent Usage_i16(0x567)
+#define Usage_Sen_DataFieldGenericProperty Usage_i16(0x568)
+#define Usage_Sen_DataFieldGenericDataField Usage_i16(0x569)
+#define Usage_Sen_DataFieldEnumeratorTableRowIndex Usage_i16(0x56a)
+#define Usage_Sen_DataFieldEnumeratorTableRowCount Usage_i16(0x56b)
+#define Usage_Sen_DataFieldGenericGUIDorPROPERTYKEYkind Usage_i16(0x56c)
+#define Usage_Sen_DataFieldGenericGUID Usage_i16(0x56d)
+#define Usage_Sen_DataFieldGenericPROPERTYKEY Usage_i16(0x56e)
+#define Usage_Sen_DataFieldGenericTopLevelCollectionID Usage_i16(0x56f)
+#define Usage_Sen_DataFieldGenericReportID Usage_i16(0x570)
+#define Usage_Sen_DataFieldGenericReportItemPositionIndex Usage_i16(0x571)
+#define Usage_Sen_DataFieldGenericFirmwareVARTYPE Usage_i16(0x572)
+#define Usage_Sen_DataFieldGenericUnitofMeasure Usage_i16(0x573)
+#define Usage_Sen_DataFieldGenericUnitExponent Usage_i16(0x574)
+#define Usage_Sen_DataFieldGenericReportSize Usage_i16(0x575)
+#define Usage_Sen_DataFieldGenericReportCount Usage_i16(0x576)
+#define Usage_Sen_PropertyGeneric Usage_i16(0x580)
+#define Usage_Sen_PropertyEnumeratorTableRowIndex Usage_i16(0x581)
+#define Usage_Sen_PropertyEnumeratorTableRowCount Usage_i16(0x582)
+#define Usage_Sen_DataFieldPersonalActivity Usage_i16(0x590)
+#define Usage_Sen_DataFieldActivityType Usage_i16(0x591)
+#define Usage_Sen_DataFieldActivityState Usage_i16(0x592)
+#define Usage_Sen_DataFieldDevicePosition Usage_i16(0x593)
+#define Usage_Sen_DataFieldStepCount Usage_i16(0x594)
+#define Usage_Sen_DataFieldStepCountReset Usage_i16(0x595)
+#define Usage_Sen_DataFieldStepDuration Usage_i16(0x596)
+#define Usage_Sen_DataFieldStepType Usage_i16(0x597)
+#define Usage_Sen_PropertyMinimumActivityDetectionInterval Usage_i16(0x5a0)
+#define Usage_Sen_PropertySupportedActivityTypes Usage_i16(0x5a1)
+#define Usage_Sen_PropertySubscribedActivityTypes Usage_i16(0x5a2)
+#define Usage_Sen_PropertySupportedStepTypes Usage_i16(0x5a3)
+#define Usage_Sen_PropertySubscribedStepTypes Usage_i16(0x5a4)
+#define Usage_Sen_PropertyFloorHeight Usage_i16(0x5a5)
+#define Usage_Sen_DataFieldCustomTypeID Usage_i16(0x5b0)
+#define Usage_Sen_PropertyCustom Usage_i16(0x5c0)
+#define Usage_Sen_PropertyCustomValue1 Usage_i16(0x5c1)
+#define Usage_Sen_PropertyCustomValue2 Usage_i16(0x5c2)
+#define Usage_Sen_PropertyCustomValue3 Usage_i16(0x5c3)
+#define Usage_Sen_PropertyCustomValue4 Usage_i16(0x5c4)
+#define Usage_Sen_PropertyCustomValue5 Usage_i16(0x5c5)
+#define Usage_Sen_PropertyCustomValue6 Usage_i16(0x5c6)
+#define Usage_Sen_PropertyCustomValue7 Usage_i16(0x5c7)
+#define Usage_Sen_PropertyCustomValue8 Usage_i16(0x5c8)
+#define Usage_Sen_PropertyCustomValue9 Usage_i16(0x5c9)
+#define Usage_Sen_PropertyCustomValue10 Usage_i16(0x5ca)
+#define Usage_Sen_PropertyCustomValue11 Usage_i16(0x5cb)
+#define Usage_Sen_PropertyCustomValue12 Usage_i16(0x5cc)
+#define Usage_Sen_PropertyCustomValue13 Usage_i16(0x5cd)
+#define Usage_Sen_PropertyCustomValue14 Usage_i16(0x5ce)
+#define Usage_Sen_PropertyCustomValue15 Usage_i16(0x5cf)
+#define Usage_Sen_PropertyCustomValue16 Usage_i16(0x5d0)
+#define Usage_Sen_DataFieldHinge Usage_i16(0x5e0)
+#define Usage_Sen_DataFieldHingeAngle Usage_i16(0x5e1)
+#define Usage_Sen_DataFieldGestureSensor Usage_i16(0x5f0)
+#define Usage_Sen_DataFieldGestureState Usage_i16(0x5f1)
+#define Usage_Sen_DataFieldHingeFoldInitialAngle Usage_i16(0x5f2)
+#define Usage_Sen_DataFieldHingeFoldFinalAngle Usage_i16(0x5f3)
+#define Usage_Sen_DataFieldHingeFoldContributingPanel Usage_i16(0x5f4)
+#define Usage_Sen_DataFieldHingeFoldType Usage_i16(0x5f5)
+#define Usage_Sen_SensorStateUndefined Usage_i16(0x800)
+#define Usage_Sen_SensorStateReady Usage_i16(0x801)
+#define Usage_Sen_SensorStateNotAvailable Usage_i16(0x802)
+#define Usage_Sen_SensorStateNoData Usage_i16(0x803)
+#define Usage_Sen_SensorStateInitializing Usage_i16(0x804)
+#define Usage_Sen_SensorStateAccessDenied Usage_i16(0x805)
+#define Usage_Sen_SensorStateError Usage_i16(0x806)
+#define Usage_Sen_SensorEventUnknown Usage_i16(0x810)
+#define Usage_Sen_SensorEventStateChanged Usage_i16(0x811)
+#define Usage_Sen_SensorEventPropertyChanged Usage_i16(0x812)
+#define Usage_Sen_SensorEventDataUpdated Usage_i16(0x813)
+#define Usage_Sen_SensorEventPollResponse Usage_i16(0x814)
+#define Usage_Sen_SensorEventChangeSensitivity Usage_i16(0x815)
+#define Usage_Sen_SensorEventRangeMaximumReached Usage_i16(0x816)
+#define Usage_Sen_SensorEventRangeMinimumReached Usage_i16(0x817)
+#define Usage_Sen_SensorEventHighThresholdCrossUpward Usage_i16(0x818)
+#define Usage_Sen_SensorEventHighThresholdCrossDownward Usage_i16(0x819)
+#define Usage_Sen_SensorEventLowThresholdCrossUpward Usage_i16(0x81a)
+#define Usage_Sen_SensorEventLowThresholdCrossDownward Usage_i16(0x81b)
+#define Usage_Sen_SensorEventZeroThresholdCrossUpward Usage_i16(0x81c)
+#define Usage_Sen_SensorEventZeroThresholdCrossDownward Usage_i16(0x81d)
+#define Usage_Sen_SensorEventPeriodExceeded Usage_i16(0x81e)
+#define Usage_Sen_SensorEventFrequencyExceeded Usage_i16(0x81f)
+#define Usage_Sen_SensorEventComplexTrigger Usage_i16(0x820)
+#define Usage_Sen_ConnectionTypePCIntegrated Usage_i16(0x830)
+#define Usage_Sen_ConnectionTypePCAttached Usage_i16(0x831)
+#define Usage_Sen_ConnectionTypePCExternal Usage_i16(0x832)
+#define Usage_Sen_ReportingStateReportNoEvents Usage_i16(0x840)
+#define Usage_Sen_ReportingStateReportAllEvents Usage_i16(0x841)
+#define Usage_Sen_ReportingStateReportThresholdEvents Usage_i16(0x842)
+#define Usage_Sen_ReportingStateWakeOnNoEvents Usage_i16(0x843)
+#define Usage_Sen_ReportingStateWakeOnAllEvents Usage_i16(0x844)
+#define Usage_Sen_ReportingStateWakeOnThresholdEvents Usage_i16(0x845)
+#define Usage_Sen_ReportingStateAnytime Usage_i16(0x846)
+#define Usage_Sen_PowerStateUndefined Usage_i16(0x850)
+#define Usage_Sen_PowerStateD0FullPower Usage_i16(0x851)
+#define Usage_Sen_PowerStateD1LowPower Usage_i16(0x852)
+#define Usage_Sen_PowerStateD2StandbyPowerwithWakeup Usage_i16(0x853)
+#define Usage_Sen_PowerStateD3SleepwithWakeup Usage_i16(0x854)
+#define Usage_Sen_PowerStateD4PowerOff Usage_i16(0x855)
+#define Usage_Sen_AccuracyDefault Usage_i16(0x860)
+#define Usage_Sen_AccuracyHigh Usage_i16(0x861)
+#define Usage_Sen_AccuracyMedium Usage_i16(0x862)
+#define Usage_Sen_AccuracyLow Usage_i16(0x863)
+#define Usage_Sen_FixQualityNoFix Usage_i16(0x870)
+#define Usage_Sen_FixQualityGPS Usage_i16(0x871)
+#define Usage_Sen_FixQualityDGPS Usage_i16(0x872)
+#define Usage_Sen_FixTypeNoFix Usage_i16(0x880)
+#define Usage_Sen_FixTypeGPSSPSModeFixValid Usage_i16(0x881)
+#define Usage_Sen_FixTypeDGPSSPSModeFixValid Usage_i16(0x882)
+#define Usage_Sen_FixTypeGPSPPSModeFixValid Usage_i16(0x883)
+#define Usage_Sen_FixTypeRealTimeKinematic Usage_i16(0x884)
+#define Usage_Sen_FixTypeFloatRTK Usage_i16(0x885)
+#define Usage_Sen_FixTypeEstimateddeadreckoned Usage_i16(0x886)
+#define Usage_Sen_FixTypeManualInputMode Usage_i16(0x887)
+#define Usage_Sen_FixTypeSimulatorMode Usage_i16(0x888)
+#define Usage_Sen_GPSOperationModeManual Usage_i16(0x890)
+#define Usage_Sen_GPSOperationModeAutomatic Usage_i16(0x891)
+#define Usage_Sen_GPSSelectionModeAutonomous Usage_i16(0x8a0)
+#define Usage_Sen_GPSSelectionModeDGPS Usage_i16(0x8a1)
+#define Usage_Sen_GPSSelectionModeEstimateddeadreckoned Usage_i16(0x8a2)
+#define Usage_Sen_GPSSelectionModeManualInput Usage_i16(0x8a3)
+#define Usage_Sen_GPSSelectionModeSimulator Usage_i16(0x8a4)
+#define Usage_Sen_GPSSelectionModeDataNotValid Usage_i16(0x8a5)
+#define Usage_Sen_GPSStatusDataValid Usage_i16(0x8b0)
+#define Usage_Sen_GPSStatusDataNotValid Usage_i16(0x8b1)
+#define Usage_Sen_DayofWeekSunday Usage_i16(0x8c0)
+#define Usage_Sen_DayofWeekMonday Usage_i16(0x8c1)
+#define Usage_Sen_DayofWeekTuesday Usage_i16(0x8c2)
+#define Usage_Sen_DayofWeekWednesday Usage_i16(0x8c3)
+#define Usage_Sen_DayofWeekThursday Usage_i16(0x8c4)
+#define Usage_Sen_DayofWeekFriday Usage_i16(0x8c5)
+#define Usage_Sen_DayofWeekSaturday Usage_i16(0x8c6)
+#define Usage_Sen_KindCategory Usage_i16(0x8d0)
+#define Usage_Sen_KindType Usage_i16(0x8d1)
+#define Usage_Sen_KindEvent Usage_i16(0x8d2)
+#define Usage_Sen_KindProperty Usage_i16(0x8d3)
+#define Usage_Sen_KindDataField Usage_i16(0x8d4)
+#define Usage_Sen_MagnetometerAccuracyLow Usage_i16(0x8e0)
+#define Usage_Sen_MagnetometerAccuracyMedium Usage_i16(0x8e1)
+#define Usage_Sen_MagnetometerAccuracyHigh Usage_i16(0x8e2)
+#define Usage_Sen_SimpleOrientationDirectionNotRotated Usage_i16(0x8f0)
+#define Usage_Sen_SimpleOrientationDirectionRotated90DegreesCCW Usage_i16(0x8f1)
+#define Usage_Sen_SimpleOrientationDirectionRotated180DegreesCCW Usage_i16(0x8f2)
+#define Usage_Sen_SimpleOrientationDirectionRotated270DegreesCCW Usage_i16(0x8f3)
+#define Usage_Sen_SimpleOrientationDirectionFaceUp Usage_i16(0x8f4)
+#define Usage_Sen_SimpleOrientationDirectionFaceDown Usage_i16(0x8f5)
+#define Usage_Sen_VT_NULL Usage_i16(0x900)
+#define Usage_Sen_VT_BOOL Usage_i16(0x901)
+#define Usage_Sen_VT_UI1 Usage_i16(0x902)
+#define Usage_Sen_VT_I1 Usage_i16(0x903)
+#define Usage_Sen_VT_UI2 Usage_i16(0x904)
+#define Usage_Sen_VT_I2 Usage_i16(0x905)
+#define Usage_Sen_VT_UI4 Usage_i16(0x906)
+#define Usage_Sen_VT_I4 Usage_i16(0x907)
+#define Usage_Sen_VT_UI8 Usage_i16(0x908)
+#define Usage_Sen_VT_I8 Usage_i16(0x909)
+#define Usage_Sen_VT_R4 Usage_i16(0x90a)
+#define Usage_Sen_VT_R8 Usage_i16(0x90b)
+#define Usage_Sen_VT_WSTR Usage_i16(0x90c)
+#define Usage_Sen_VT_STR Usage_i16(0x90d)
+#define Usage_Sen_VT_CLSID Usage_i16(0x90e)
+#define Usage_Sen_VT_VECTORVT_UI1 Usage_i16(0x90f)
+#define Usage_Sen_VT_F16E0 Usage_i16(0x910)
+#define Usage_Sen_VT_F16E1 Usage_i16(0x911)
+#define Usage_Sen_VT_F16E2 Usage_i16(0x912)
+#define Usage_Sen_VT_F16E3 Usage_i16(0x913)
+#define Usage_Sen_VT_F16E4 Usage_i16(0x914)
+#define Usage_Sen_VT_F16E5 Usage_i16(0x915)
+#define Usage_Sen_VT_F16E6 Usage_i16(0x916)
+#define Usage_Sen_VT_F16E7 Usage_i16(0x917)
+#define Usage_Sen_VT_F16E8 Usage_i16(0x918)
+#define Usage_Sen_VT_F16E9 Usage_i16(0x919)
+#define Usage_Sen_VT_F16EA Usage_i16(0x91a)
+#define Usage_Sen_VT_F16EB Usage_i16(0x91b)
+#define Usage_Sen_VT_F16EC Usage_i16(0x91c)
+#define Usage_Sen_VT_F16ED Usage_i16(0x91d)
+#define Usage_Sen_VT_F16EE Usage_i16(0x91e)
+#define Usage_Sen_VT_F16EF Usage_i16(0x91f)
+#define Usage_Sen_VT_F32E0 Usage_i16(0x920)
+#define Usage_Sen_VT_F32E1 Usage_i16(0x921)
+#define Usage_Sen_VT_F32E2 Usage_i16(0x922)
+#define Usage_Sen_VT_F32E3 Usage_i16(0x923)
+#define Usage_Sen_VT_F32E4 Usage_i16(0x924)
+#define Usage_Sen_VT_F32E5 Usage_i16(0x925)
+#define Usage_Sen_VT_F32E6 Usage_i16(0x926)
+#define Usage_Sen_VT_F32E7 Usage_i16(0x927)
+#define Usage_Sen_VT_F32E8 Usage_i16(0x928)
+#define Usage_Sen_VT_F32E9 Usage_i16(0x929)
+#define Usage_Sen_VT_F32EA Usage_i16(0x92a)
+#define Usage_Sen_VT_F32EB Usage_i16(0x92b)
+#define Usage_Sen_VT_F32EC Usage_i16(0x92c)
+#define Usage_Sen_VT_F32ED Usage_i16(0x92d)
+#define Usage_Sen_VT_F32EE Usage_i16(0x92e)
+#define Usage_Sen_VT_F32EF Usage_i16(0x92f)
+#define Usage_Sen_ActivityTypeUnknown Usage_i16(0x930)
+#define Usage_Sen_ActivityTypeStationary Usage_i16(0x931)
+#define Usage_Sen_ActivityTypeFidgeting Usage_i16(0x932)
+#define Usage_Sen_ActivityTypeWalking Usage_i16(0x933)
+#define Usage_Sen_ActivityTypeRunning Usage_i16(0x934)
+#define Usage_Sen_ActivityTypeInVehicle Usage_i16(0x935)
+#define Usage_Sen_ActivityTypeBiking Usage_i16(0x936)
+#define Usage_Sen_ActivityTypeIdle Usage_i16(0x937)
+#define Usage_Sen_UnitNotSpecified Usage_i16(0x940)
+#define Usage_Sen_UnitLux Usage_i16(0x941)
+#define Usage_Sen_UnitDegreesKelvin Usage_i16(0x942)
+#define Usage_Sen_UnitDegreesCelsius Usage_i16(0x943)
+#define Usage_Sen_UnitPascal Usage_i16(0x944)
+#define Usage_Sen_UnitNewton Usage_i16(0x945)
+#define Usage_Sen_UnitMetersSecond Usage_i16(0x946)
+#define Usage_Sen_UnitKilogram Usage_i16(0x947)
+#define Usage_Sen_UnitMeter Usage_i16(0x948)
+#define Usage_Sen_UnitMetersSecondSecond Usage_i16(0x949)
+#define Usage_Sen_UnitFarad Usage_i16(0x94a)
+#define Usage_Sen_UnitAmpere Usage_i16(0x94b)
+#define Usage_Sen_UnitWatt Usage_i16(0x94c)
+#define Usage_Sen_UnitHenry Usage_i16(0x94d)
+#define Usage_Sen_UnitOhm Usage_i16(0x94e)
+#define Usage_Sen_UnitVolt Usage_i16(0x94f)
+#define Usage_Sen_UnitHertz Usage_i16(0x950)
+#define Usage_Sen_UnitBar Usage_i16(0x951)
+#define Usage_Sen_UnitDegreesAnticlockwise Usage_i16(0x952)
+#define Usage_Sen_UnitDegreesClockwise Usage_i16(0x953)
+#define Usage_Sen_UnitDegrees Usage_i16(0x954)
+#define Usage_Sen_UnitDegreesSecond Usage_i16(0x955)
+#define Usage_Sen_UnitDegreesSecondSecond Usage_i16(0x956)
+#define Usage_Sen_UnitKnot Usage_i16(0x957)
+#define Usage_Sen_UnitPercent Usage_i16(0x958)
+#define Usage_Sen_UnitSecond Usage_i16(0x959)
+#define Usage_Sen_UnitMillisecond Usage_i16(0x95a)
+#define Usage_Sen_UnitG Usage_i16(0x95b)
+#define Usage_Sen_UnitBytes Usage_i16(0x95c)
+#define Usage_Sen_UnitMilligauss Usage_i16(0x95d)
+#define Usage_Sen_UnitBits Usage_i16(0x95e)
+#define Usage_Sen_ActivityStateNoStateChange Usage_i16(0x960)
+#define Usage_Sen_ActivityStateStartActivity Usage_i16(0x961)
+#define Usage_Sen_ActivityStateEndActivity Usage_i16(0x962)
+#define Usage_Sen_Exponent0 Usage_i16(0x970)
+#define Usage_Sen_Exponent1 Usage_i16(0x971)
+#define Usage_Sen_Exponent2 Usage_i16(0x972)
+#define Usage_Sen_Exponent3 Usage_i16(0x973)
+#define Usage_Sen_Exponent4 Usage_i16(0x974)
+#define Usage_Sen_Exponent5 Usage_i16(0x975)
+#define Usage_Sen_Exponent6 Usage_i16(0x976)
+#define Usage_Sen_Exponent7 Usage_i16(0x977)
+#define Usage_Sen_Exponent8 Usage_i16(0x978)
+#define Usage_Sen_Exponent9 Usage_i16(0x979)
+#define Usage_Sen_ExponentA Usage_i16(0x97a)
+#define Usage_Sen_ExponentB Usage_i16(0x97b)
+#define Usage_Sen_ExponentC Usage_i16(0x97c)
+#define Usage_Sen_ExponentD Usage_i16(0x97d)
+#define Usage_Sen_ExponentE Usage_i16(0x97e)
+#define Usage_Sen_ExponentF Usage_i16(0x97f)
+#define Usage_Sen_DevicePositionUnknown Usage_i16(0x980)
+#define Usage_Sen_DevicePositionUnchanged Usage_i16(0x981)
+#define Usage_Sen_DevicePositionOnDesk Usage_i16(0x982)
+#define Usage_Sen_DevicePositionInHand Usage_i16(0x983)
+#define Usage_Sen_DevicePositionMovinginBag Usage_i16(0x984)
+#define Usage_Sen_DevicePositionStationaryinBag Usage_i16(0x985)
+#define Usage_Sen_StepTypeUnknown Usage_i16(0x990)
+#define Usage_Sen_StepTypeWalking Usage_i16(0x991)
+#define Usage_Sen_StepTypeRunning Usage_i16(0x992)
+#define Usage_Sen_GestureStateUnknown Usage_i16(0x9a0)
+#define Usage_Sen_GestureStateStarted Usage_i16(0x9a1)
+#define Usage_Sen_GestureStateCompleted Usage_i16(0x9a2)
+#define Usage_Sen_GestureStateCancelled Usage_i16(0x9a3)
+#define Usage_Sen_HingeFoldContributingPanelUnknown Usage_i16(0x9b0)
+#define Usage_Sen_HingeFoldContributingPanelPanel1 Usage_i16(0x9b1)
+#define Usage_Sen_HingeFoldContributingPanelPanel2 Usage_i16(0x9b2)
+#define Usage_Sen_HingeFoldContributingPanelBoth Usage_i16(0x9b3)
+#define Usage_Sen_HingeFoldTypeUnknown Usage_i16(0x9b4)
+#define Usage_Sen_HingeFoldTypeIncreasing Usage_i16(0x9b5)
+#define Usage_Sen_HingeFoldTypeDecreasing Usage_i16(0x9b6)
+#define Usage_Sen_HumanPresenceDetectionTypeVendorDefinedNonBiometric Usage_i16(0x9c0)
+#define Usage_Sen_HumanPresenceDetectionTypeVendorDefinedBiometric Usage_i16(0x9c1)
+#define Usage_Sen_HumanPresenceDetectionTypeFacialBiometric Usage_i16(0x9c2)
+#define Usage_Sen_HumanPresenceDetectionTypeAudioBiometric Usage_i16(0x9c3)
+#define Usage_Sen_ModifierChangeSensitivityAbsolute Usage_i16(0x1000)
+#define Usage_Sen_ModifierMaximum Usage_i16(0x2000)
+#define Usage_Sen_ModifierMinimum Usage_i16(0x3000)
+#define Usage_Sen_ModifierAccuracy Usage_i16(0x4000)
+#define Usage_Sen_ModifierResolution Usage_i16(0x5000)
+#define Usage_Sen_ModifierThresholdHigh Usage_i16(0x6000)
+#define Usage_Sen_ModifierThresholdLow Usage_i16(0x7000)
+#define Usage_Sen_ModifierCalibrationOffset Usage_i16(0x8000)
+#define Usage_Sen_ModifierCalibrationMultiplier Usage_i16(0x9000)
+#define Usage_Sen_ModifierReportInterval Usage_i16(0xa000)
+#define Usage_Sen_ModifierFrequencyMax Usage_i16(0xb000)
+#define Usage_Sen_ModifierPeriodMax Usage_i16(0xc000)
+#define Usage_Sen_ModifierChangeSensitivityPercentofRange Usage_i16(0xd000)
+#define Usage_Sen_ModifierChangeSensitivityPercentRelative Usage_i16(0xe000)
+#define Usage_Sen_ModifierVendorReserved Usage_i16(0xf000)
+#define Usage_MI_MedicalUltrasound Usage_i8(0x1)
+#define Usage_MI_VCRAcquisition Usage_i8(0x20)
+#define Usage_MI_FreezeThaw Usage_i8(0x21)
+#define Usage_MI_ClipStore Usage_i8(0x22)
+#define Usage_MI_Update Usage_i8(0x23)
+#define Usage_MI_Next Usage_i8(0x24)
+#define Usage_MI_Save Usage_i8(0x25)
+#define Usage_MI_Print Usage_i8(0x26)
+#define Usage_MI_MicrophoneEnable Usage_i8(0x27)
+#define Usage_MI_Cine Usage_i8(0x40)
+#define Usage_MI_TransmitPower Usage_i8(0x41)
+#define Usage_MI_Volume Usage_i8(0x42)
+#define Usage_MI_Focus Usage_i8(0x43)
+#define Usage_MI_Depth Usage_i8(0x44)
+#define Usage_MI_SoftStepPrimary Usage_i8(0x60)
+#define Usage_MI_SoftStepSecondary Usage_i8(0x61)
+#define Usage_MI_DepthGainCompensation Usage_i8(0x70)
+#define Usage_MI_ZoomSelect Usage_i8(0x80)
+#define Usage_MI_ZoomAdjust Usage_i8(0x81)
+#define Usage_MI_SpectralDopplerModeSelect Usage_i8(0x82)
+#define Usage_MI_SpectralDopplerAdjust Usage_i8(0x83)
+#define Usage_MI_ColorDopplerModeSelect Usage_i8(0x84)
+#define Usage_MI_ColorDopplerAdjust Usage_i8(0x85)
+#define Usage_MI_MotionModeSelect Usage_i8(0x86)
+#define Usage_MI_MotionModeAdjust Usage_i8(0x87)
+#define Usage_MI_TwoDModeSelect Usage_i8(0x88)
+#define Usage_MI_TwoDModeAdjust Usage_i8(0x89)
+#define Usage_MI_SoftControlSelect Usage_i8(0xa0)
+#define Usage_MI_SoftControlAdjust Usage_i8(0xa1)
+#define Usage_BD_BrailleDisplay Usage_i8(0x1)
+#define Usage_BD_BrailleRow Usage_i8(0x2)
+#define Usage_BD_EightDotBrailleCell Usage_i8(0x3)
+#define Usage_BD_SixDotBrailleCell Usage_i8(0x4)
+#define Usage_BD_NumberofBrailleCells Usage_i8(0x5)
+#define Usage_BD_ScreenReaderControl Usage_i8(0x6)
+#define Usage_BD_ScreenReaderIdentifier Usage_i8(0x7)
+#define Usage_BD_RouterSet1 Usage_i8(0xfa)
+#define Usage_BD_RouterSet2 Usage_i8(0xfb)
+#define Usage_BD_RouterSet3 Usage_i8(0xfc)
+#define Usage_BD_RouterKey Usage_i16(0x100)
+#define Usage_BD_RowRouterKey Usage_i16(0x101)
+#define Usage_BD_BrailleButtons Usage_i16(0x200)
+#define Usage_BD_BrailleKeyboardDot1 Usage_i16(0x201)
+#define Usage_BD_BrailleKeyboardDot2 Usage_i16(0x202)
+#define Usage_BD_BrailleKeyboardDot3 Usage_i16(0x203)
+#define Usage_BD_BrailleKeyboardDot4 Usage_i16(0x204)
+#define Usage_BD_BrailleKeyboardDot5 Usage_i16(0x205)
+#define Usage_BD_BrailleKeyboardDot6 Usage_i16(0x206)
+#define Usage_BD_BrailleKeyboardDot7 Usage_i16(0x207)
+#define Usage_BD_BrailleKeyboardDot8 Usage_i16(0x208)
+#define Usage_BD_BrailleKeyboardSpace Usage_i16(0x209)
+#define Usage_BD_BrailleKeyboardLeftSpace Usage_i16(0x20a)
+#define Usage_BD_BrailleKeyboardRightSpace Usage_i16(0x20b)
+#define Usage_BD_BrailleFaceControls Usage_i16(0x20c)
+#define Usage_BD_BrailleLeftControls Usage_i16(0x20d)
+#define Usage_BD_BrailleRightControls Usage_i16(0x20e)
+#define Usage_BD_BrailleTopControls Usage_i16(0x20f)
+#define Usage_BD_BrailleJoystickCenter Usage_i16(0x210)
+#define Usage_BD_BrailleJoystickUp Usage_i16(0x211)
+#define Usage_BD_BrailleJoystickDown Usage_i16(0x212)
+#define Usage_BD_BrailleJoystickLeft Usage_i16(0x213)
+#define Usage_BD_BrailleJoystickRight Usage_i16(0x214)
+#define Usage_BD_BrailleDPadCenter Usage_i16(0x215)
+#define Usage_BD_BrailleDPadUp Usage_i16(0x216)
+#define Usage_BD_BrailleDPadDown Usage_i16(0x217)
+#define Usage_BD_BrailleDPadLeft Usage_i16(0x218)
+#define Usage_BD_BrailleDPadRight Usage_i16(0x219)
+#define Usage_BD_BraillePanLeft Usage_i16(0x21a)
+#define Usage_BD_BraillePanRight Usage_i16(0x21b)
+#define Usage_BD_BrailleRockerUp Usage_i16(0x21c)
+#define Usage_BD_BrailleRockerDown Usage_i16(0x21d)
+#define Usage_BD_BrailleRockerPress Usage_i16(0x21e)
+#define Usage_LAI_LampArray Usage_i8(0x1)
+#define Usage_LAI_LampArrayAttributesReport Usage_i8(0x2)
+#define Usage_LAI_LampCount Usage_i8(0x3)
+#define Usage_LAI_BoundingBoxWidthInMicrometers Usage_i8(0x4)
+#define Usage_LAI_BoundingBoxHeightInMicrometers Usage_i8(0x5)
+#define Usage_LAI_BoundingBoxDepthInMicrometers Usage_i8(0x6)
+#define Usage_LAI_LampArrayKind Usage_i8(0x7)
+#define Usage_LAI_MinUpdateIntervalInMicroseconds Usage_i8(0x8)
+#define Usage_LAI_LampAttributesRequestReport Usage_i8(0x20)
+#define Usage_LAI_LampId Usage_i8(0x21)
+#define Usage_LAI_LampAttributesResponseReport Usage_i8(0x22)
+#define Usage_LAI_PositionXInMicrometers Usage_i8(0x23)
+#define Usage_LAI_PositionYInMicrometers Usage_i8(0x24)
+#define Usage_LAI_PositionZInMicrometers Usage_i8(0x25)
+#define Usage_LAI_LampPurposes Usage_i8(0x26)
+#define Usage_LAI_UpdateLatencyInMicroseconds Usage_i8(0x27)
+#define Usage_LAI_RedLevelCount Usage_i8(0x28)
+#define Usage_LAI_GreenLevelCount Usage_i8(0x29)
+#define Usage_LAI_BlueLevelCount Usage_i8(0x2a)
+#define Usage_LAI_IntensityLevelCount Usage_i8(0x2b)
+#define Usage_LAI_IsProgrammable Usage_i8(0x2c)
+#define Usage_LAI_InputBinding Usage_i8(0x2d)
+#define Usage_LAI_LampMultiUpdateReport Usage_i8(0x50)
+#define Usage_LAI_RedUpdateChannel Usage_i8(0x51)
+#define Usage_LAI_GreenUpdateChannel Usage_i8(0x52)
+#define Usage_LAI_BlueUpdateChannel Usage_i8(0x53)
+#define Usage_LAI_IntensityUpdateChannel Usage_i8(0x54)
+#define Usage_LAI_LampUpdateFlags Usage_i8(0x55)
+#define Usage_LAI_LampRangeUpdateReport Usage_i8(0x60)
+#define Usage_LAI_LampIdStart Usage_i8(0x61)
+#define Usage_LAI_LampIdEnd Usage_i8(0x62)
+#define Usage_LAI_LampArrayControlReport Usage_i8(0x70)
+#define Usage_LAI_AutonomousMode Usage_i8(0x71)
+#define Usage_Mon_MonitorControl Usage_i8(0x1)
+#define Usage_Mon_EDIDInformation Usage_i8(0x2)
+#define Usage_Mon_VDIFInformation Usage_i8(0x3)
+#define Usage_Mon_VESAVersion Usage_i8(0x4)
+#define Usage_VESAVC_Degauss Usage_i8(0x1)
+#define Usage_VESAVC_Brightness Usage_i8(0x10)
+#define Usage_VESAVC_Contrast Usage_i8(0x12)
+#define Usage_VESAVC_RedVideoGain Usage_i8(0x16)
+#define Usage_VESAVC_GreenVideoGain Usage_i8(0x18)
+#define Usage_VESAVC_BlueVideoGain Usage_i8(0x1a)
+#define Usage_VESAVC_Focus Usage_i8(0x1c)
+#define Usage_VESAVC_HorizontalPosition Usage_i8(0x20)
+#define Usage_VESAVC_HorizontalSize Usage_i8(0x22)
+#define Usage_VESAVC_HorizontalPincushion Usage_i8(0x24)
+#define Usage_VESAVC_HorizontalPincushionBalance Usage_i8(0x26)
+#define Usage_VESAVC_HorizontalMisconvergence Usage_i8(0x28)
+#define Usage_VESAVC_HorizontalLinearity Usage_i8(0x2a)
+#define Usage_VESAVC_HorizontalLinearityBalance Usage_i8(0x2c)
+#define Usage_VESAVC_VerticalPosition Usage_i8(0x30)
+#define Usage_VESAVC_VerticalSize Usage_i8(0x32)
+#define Usage_VESAVC_VerticalPincushion Usage_i8(0x34)
+#define Usage_VESAVC_VerticalPincushionBalance Usage_i8(0x36)
+#define Usage_VESAVC_VerticalMisconvergence Usage_i8(0x38)
+#define Usage_VESAVC_VerticalLinearity Usage_i8(0x3a)
+#define Usage_VESAVC_VerticalLinearityBalance Usage_i8(0x3c)
+#define Usage_VESAVC_ParallelogramDistortionKeyBalance Usage_i8(0x40)
+#define Usage_VESAVC_TrapezoidalDistortionKey Usage_i8(0x42)
+#define Usage_VESAVC_TiltRotation Usage_i8(0x44)
+#define Usage_VESAVC_TopCornerDistortionControl Usage_i8(0x46)
+#define Usage_VESAVC_TopCornerDistortionBalance Usage_i8(0x48)
+#define Usage_VESAVC_BottomCornerDistortionControl Usage_i8(0x4a)
+#define Usage_VESAVC_BottomCornerDistortionBalance Usage_i8(0x4c)
+#define Usage_VESAVC_HorizontalMoiré Usage_i8(0x56)
+#define Usage_VESAVC_VerticalMoiré Usage_i8(0x58)
+#define Usage_VESAVC_InputLevelSelect Usage_i8(0x5e)
+#define Usage_VESAVC_InputSourceSelect Usage_i8(0x60)
+#define Usage_VESAVC_RedVideoBlackLevel Usage_i8(0x6c)
+#define Usage_VESAVC_GreenVideoBlackLevel Usage_i8(0x6e)
+#define Usage_VESAVC_BlueVideoBlackLevel Usage_i8(0x70)
+#define Usage_VESAVC_AutoSizeCenter Usage_i8(0xa2)
+#define Usage_VESAVC_PolarityHorizontalSynchronization Usage_i8(0xa4)
+#define Usage_VESAVC_PolarityVerticalSynchronization Usage_i8(0xa6)
+#define Usage_VESAVC_SynchronizationType Usage_i8(0xa8)
+#define Usage_VESAVC_ScreenOrientation Usage_i8(0xaa)
+#define Usage_VESAVC_HorizontalFrequency Usage_i8(0xac)
+#define Usage_VESAVC_VerticalFrequency Usage_i8(0xae)
+#define Usage_VESAVC_Settings Usage_i8(0xb0)
+#define Usage_VESAVC_OnScreenDisplay Usage_i8(0xca)
+#define Usage_VESAVC_StereoMode Usage_i8(0xd4)
+#define Usage_Pow_iName Usage_i8(0x1)
+#define Usage_Pow_PresentStatus Usage_i8(0x2)
+#define Usage_Pow_ChangedStatus Usage_i8(0x3)
+#define Usage_Pow_UPS Usage_i8(0x4)
+#define Usage_Pow_PowerSupply Usage_i8(0x5)
+#define Usage_Pow_BatterySystem Usage_i8(0x10)
+#define Usage_Pow_BatterySystemId Usage_i8(0x11)
+#define Usage_Pow_Battery Usage_i8(0x12)
+#define Usage_Pow_BatteryId Usage_i8(0x13)
+#define Usage_Pow_Charger Usage_i8(0x14)
+#define Usage_Pow_ChargerId Usage_i8(0x15)
+#define Usage_Pow_PowerConverter Usage_i8(0x16)
+#define Usage_Pow_PowerConverterId Usage_i8(0x17)
+#define Usage_Pow_OutletSystem Usage_i8(0x18)
+#define Usage_Pow_OutletSystemId Usage_i8(0x19)
+#define Usage_Pow_Input Usage_i8(0x1a)
+#define Usage_Pow_InputId Usage_i8(0x1b)
+#define Usage_Pow_Output Usage_i8(0x1c)
+#define Usage_Pow_OutputId Usage_i8(0x1d)
+#define Usage_Pow_Flow Usage_i8(0x1e)
+#define Usage_Pow_FlowId Usage_i8(0x1f)
+#define Usage_Pow_Outlet Usage_i8(0x20)
+#define Usage_Pow_OutletId Usage_i8(0x21)
+#define Usage_Pow_Gang Usage_i8(0x22)
+#define Usage_Pow_GangId Usage_i8(0x23)
+#define Usage_Pow_PowerSummary Usage_i8(0x24)
+#define Usage_Pow_PowerSummaryId Usage_i8(0x25)
+#define Usage_Pow_Voltage Usage_i8(0x30)
+#define Usage_Pow_Current Usage_i8(0x31)
+#define Usage_Pow_Frequency Usage_i8(0x32)
+#define Usage_Pow_ApparentPower Usage_i8(0x33)
+#define Usage_Pow_ActivePower Usage_i8(0x34)
+#define Usage_Pow_PercentLoad Usage_i8(0x35)
+#define Usage_Pow_Temperature Usage_i8(0x36)
+#define Usage_Pow_Humidity Usage_i8(0x37)
+#define Usage_Pow_BadCount Usage_i8(0x38)
+#define Usage_Pow_ConfigVoltage Usage_i8(0x40)
+#define Usage_Pow_ConfigCurrent Usage_i8(0x41)
+#define Usage_Pow_ConfigFrequency Usage_i8(0x42)
+#define Usage_Pow_ConfigApparentPower Usage_i8(0x43)
+#define Usage_Pow_ConfigActivePower Usage_i8(0x44)
+#define Usage_Pow_ConfigPercentLoad Usage_i8(0x45)
+#define Usage_Pow_ConfigTemperature Usage_i8(0x46)
+#define Usage_Pow_ConfigHumidity Usage_i8(0x47)
+#define Usage_Pow_SwitchOnControl Usage_i8(0x50)
+#define Usage_Pow_SwitchOffControl Usage_i8(0x51)
+#define Usage_Pow_ToggleControl Usage_i8(0x52)
+#define Usage_Pow_LowVoltageTransfer Usage_i8(0x53)
+#define Usage_Pow_HighVoltageTransfer Usage_i8(0x54)
+#define Usage_Pow_DelayBeforeReboot Usage_i8(0x55)
+#define Usage_Pow_DelayBeforeStartup Usage_i8(0x56)
+#define Usage_Pow_DelayBeforeShutdown Usage_i8(0x57)
+#define Usage_Pow_Test Usage_i8(0x58)
+#define Usage_Pow_ModuleReset Usage_i8(0x59)
+#define Usage_Pow_AudibleAlarmControl Usage_i8(0x5a)
+#define Usage_Pow_Present Usage_i8(0x60)
+#define Usage_Pow_Good Usage_i8(0x61)
+#define Usage_Pow_InternalFailure Usage_i8(0x62)
+#define Usage_Pow_VoltagOutOfRange Usage_i8(0x63)
+#define Usage_Pow_FrequencyOutOfRange Usage_i8(0x64)
+#define Usage_Pow_Overload Usage_i8(0x65)
+#define Usage_Pow_OverCharged Usage_i8(0x66)
+#define Usage_Pow_OverTemperature Usage_i8(0x67)
+#define Usage_Pow_ShutdownRequested Usage_i8(0x68)
+#define Usage_Pow_ShutdownImminent Usage_i8(0x69)
+#define Usage_Pow_SwitchOnOff Usage_i8(0x6b)
+#define Usage_Pow_Switchable Usage_i8(0x6c)
+#define Usage_Pow_Used Usage_i8(0x6d)
+#define Usage_Pow_Boost Usage_i8(0x6e)
+#define Usage_Pow_Buck Usage_i8(0x6f)
+#define Usage_Pow_Initialized Usage_i8(0x70)
+#define Usage_Pow_Tested Usage_i8(0x71)
+#define Usage_Pow_AwaitingPower Usage_i8(0x72)
+#define Usage_Pow_CommunicationLost Usage_i8(0x73)
+#define Usage_Pow_iManufacturer Usage_i8(0xfd)
+#define Usage_Pow_iProduct Usage_i8(0xfe)
+#define Usage_Pow_iSerialNumber Usage_i16(0xff)
+#define Usage_BS_SmartBatteryBatteryMode Usage_i8(0x1)
+#define Usage_BS_SmartBatteryBatteryStatus Usage_i8(0x2)
+#define Usage_BS_SmartBatteryAlarmWarning Usage_i8(0x3)
+#define Usage_BS_SmartBatteryChargerMode Usage_i8(0x4)
+#define Usage_BS_SmartBatteryChargerStatus Usage_i8(0x5)
+#define Usage_BS_SmartBatteryChargerSpecInfo Usage_i8(0x6)
+#define Usage_BS_SmartBatterySelectorState Usage_i8(0x7)
+#define Usage_BS_SmartBatterySelectorPresets Usage_i8(0x8)
+#define Usage_BS_SmartBatterySelectorInfo Usage_i8(0x9)
+#define Usage_BS_OptionalMfgFunction1 Usage_i8(0x10)
+#define Usage_BS_OptionalMfgFunction2 Usage_i8(0x11)
+#define Usage_BS_OptionalMfgFunction3 Usage_i8(0x12)
+#define Usage_BS_OptionalMfgFunction4 Usage_i8(0x13)
+#define Usage_BS_OptionalMfgFunction5 Usage_i8(0x14)
+#define Usage_BS_ConnectionToSMBus Usage_i8(0x15)
+#define Usage_BS_OutputConnection Usage_i8(0x16)
+#define Usage_BS_ChargerConnection Usage_i8(0x17)
+#define Usage_BS_BatteryInsertion Usage_i8(0x18)
+#define Usage_BS_UseNext Usage_i8(0x19)
+#define Usage_BS_OKToUse Usage_i8(0x1a)
+#define Usage_BS_BatterySupported Usage_i8(0x1b)
+#define Usage_BS_SelectorRevision Usage_i8(0x1c)
+#define Usage_BS_ChargingIndicator Usage_i8(0x1d)
+#define Usage_BS_ManufacturerAccess Usage_i8(0x28)
+#define Usage_BS_RemainingCapacityLimit Usage_i8(0x29)
+#define Usage_BS_RemainingTimeLimit Usage_i8(0x2a)
+#define Usage_BS_AtRate Usage_i8(0x2b)
+#define Usage_BS_CapacityMode Usage_i8(0x2c)
+#define Usage_BS_BroadcastToCharger Usage_i8(0x2d)
+#define Usage_BS_PrimaryBattery Usage_i8(0x2e)
+#define Usage_BS_ChargeController Usage_i8(0x2f)
+#define Usage_BS_TerminateCharge Usage_i8(0x40)
+#define Usage_BS_TerminateDischarge Usage_i8(0x41)
+#define Usage_BS_BelowRemainingCapacityLimit Usage_i8(0x42)
+#define Usage_BS_RemainingTimeLimitExpired Usage_i8(0x43)
+#define Usage_BS_Charging Usage_i8(0x44)
+#define Usage_BS_Discharging Usage_i8(0x45)
+#define Usage_BS_FullyCharged Usage_i8(0x46)
+#define Usage_BS_FullyDischarged Usage_i8(0x47)
+#define Usage_BS_ConditioningFlag Usage_i8(0x48)
+#define Usage_BS_AtRateOK Usage_i8(0x49)
+#define Usage_BS_SmartBatteryErrorCode Usage_i8(0x4a)
+#define Usage_BS_NeedReplacement Usage_i8(0x4b)
+#define Usage_BS_AtRateTimeToFull Usage_i8(0x60)
+#define Usage_BS_AtRateTimeToEmpty Usage_i8(0x61)
+#define Usage_BS_AverageCurrent Usage_i8(0x62)
+#define Usage_BS_MaxError Usage_i8(0x63)
+#define Usage_BS_RelativeStateOfCharge Usage_i8(0x64)
+#define Usage_BS_AbsoluteStateOfCharge Usage_i8(0x65)
+#define Usage_BS_RemainingCapacity Usage_i8(0x66)
+#define Usage_BS_FullChargeCapacity Usage_i8(0x67)
+#define Usage_BS_RunTimeToEmpty Usage_i8(0x68)
+#define Usage_BS_AverageTimeToEmpty Usage_i8(0x69)
+#define Usage_BS_AverageTimeToFull Usage_i8(0x6a)
+#define Usage_BS_CycleCount Usage_i8(0x6b)
+#define Usage_BS_BatteryPackModelLevel Usage_i8(0x80)
+#define Usage_BS_InternalChargeController Usage_i8(0x81)
+#define Usage_BS_PrimaryBatterySupport Usage_i8(0x82)
+#define Usage_BS_DesignCapacity Usage_i8(0x83)
+#define Usage_BS_SpecificationInfo Usage_i8(0x84)
+#define Usage_BS_ManufactureDate Usage_i8(0x85)
+#define Usage_BS_SerialNumber Usage_i8(0x86)
+#define Usage_BS_iManufacturerName Usage_i8(0x87)
+#define Usage_BS_iDeviceName Usage_i8(0x88)
+#define Usage_BS_iDeviceChemistry Usage_i8(0x89)
+#define Usage_BS_ManufacturerData Usage_i8(0x8a)
+#define Usage_BS_Rechargeable Usage_i8(0x8b)
+#define Usage_BS_WarningCapacityLimit Usage_i8(0x8c)
+#define Usage_BS_CapacityGranularity1 Usage_i8(0x8d)
+#define Usage_BS_CapacityGranularity2 Usage_i8(0x8e)
+#define Usage_BS_iOEMInformation Usage_i8(0x8f)
+#define Usage_BS_InhibitCharge Usage_i8(0xc0)
+#define Usage_BS_EnablePolling Usage_i8(0xc1)
+#define Usage_BS_ResetToZero Usage_i8(0xc2)
+#define Usage_BS_ACPresent Usage_i8(0xd0)
+#define Usage_BS_BatteryPresent Usage_i8(0xd1)
+#define Usage_BS_PowerFail Usage_i8(0xd2)
+#define Usage_BS_AlarmInhibited Usage_i8(0xd3)
+#define Usage_BS_ThermistorUnderRange Usage_i8(0xd4)
+#define Usage_BS_ThermistorHot Usage_i8(0xd5)
+#define Usage_BS_ThermistorCold Usage_i8(0xd6)
+#define Usage_BS_ThermistorOverRange Usage_i8(0xd7)
+#define Usage_BS_VoltageOutOfRange Usage_i8(0xd8)
+#define Usage_BS_CurrentOutOfRange Usage_i8(0xd9)
+#define Usage_BS_CurrentNotRegulated Usage_i8(0xda)
+#define Usage_BS_VoltageNotRegulated Usage_i8(0xdb)
+#define Usage_BS_MasterMode Usage_i8(0xdc)
+#define Usage_BS_ChargerSelectorSupport Usage_i8(0xf0)
+#define Usage_BS_ChargerSpec Usage_i8(0xf1)
+#define Usage_BS_Level2 Usage_i8(0xf2)
+#define Usage_BS_Level3 Usage_i8(0xf3)
+#define Usage_BS_BarcodeBadgeReader Usage_i8(0x1)
+#define Usage_BS_BarcodeScanner Usage_i8(0x2)
+#define Usage_BS_DumbBarCodeScanner Usage_i8(0x3)
+#define Usage_BS_CordlessScannerBase Usage_i8(0x4)
+#define Usage_BS_BarCodeScannerCradle Usage_i8(0x5)
+#define Usage_BS_AttributeReport Usage_i8(0x10)
+#define Usage_BS_SettingsReport Usage_i8(0x11)
+#define Usage_BS_ScannedDataReport Usage_i8(0x12)
+#define Usage_BS_RawScannedDataReport Usage_i8(0x13)
+#define Usage_BS_TriggerReport Usage_i8(0x14)
+#define Usage_BS_StatusReport Usage_i8(0x15)
+#define Usage_BS_UPCEANControlReport Usage_i8(0x16)
+#define Usage_BS_EAN23LabelControlReport Usage_i8(0x17)
+#define Usage_BS_Code39ControlReport Usage_i8(0x18)
+#define Usage_BS_Interleaved2of5ControlReport Usage_i8(0x19)
+#define Usage_BS_Standard2of5ControlReport Usage_i8(0x1a)
+#define Usage_BS_MSIPlesseyControlReport Usage_i8(0x1b)
+#define Usage_BS_CodabarControlReport Usage_i8(0x1c)
+#define Usage_BS_Code128ControlReport Usage_i8(0x1d)
+#define Usage_BS_Misc1DControlReport Usage_i8(0x1e)
+#define Usage_BS_TwoDControlReport Usage_i8(0x1f)
+#define Usage_BS_AimingPointerMode Usage_i8(0x30)
+#define Usage_BS_BarCodePresentSensor Usage_i8(0x31)
+#define Usage_BS_Class1ALaser Usage_i8(0x32)
+#define Usage_BS_Class2Laser Usage_i8(0x33)
+#define Usage_BS_HeaterPresent Usage_i8(0x34)
+#define Usage_BS_ContactScanner Usage_i8(0x35)
+#define Usage_BS_ElectronicArticleSurveillanceNotification Usage_i8(0x36)
+#define Usage_BS_ConstantElectronicArticleSurveillance Usage_i8(0x37)
+#define Usage_BS_ErrorIndication Usage_i8(0x38)
+#define Usage_BS_FixedBeeper Usage_i8(0x39)
+#define Usage_BS_GoodDecodeIndication Usage_i8(0x3a)
+#define Usage_BS_HandsFreeScanning Usage_i8(0x3b)
+#define Usage_BS_IntrinsicallySafe Usage_i8(0x3c)
+#define Usage_BS_KlasseEinsLaser Usage_i8(0x3d)
+#define Usage_BS_LongRangeScanner Usage_i8(0x3e)
+#define Usage_BS_MirrorSpeedControl Usage_i8(0x3f)
+#define Usage_BS_NotOnFileIndication Usage_i8(0x40)
+#define Usage_BS_ProgrammableBeeper Usage_i8(0x41)
+#define Usage_BS_Triggerless Usage_i8(0x42)
+#define Usage_BS_Wand Usage_i8(0x43)
+#define Usage_BS_WaterResistant Usage_i8(0x44)
+#define Usage_BS_MultiRangeScanner Usage_i8(0x45)
+#define Usage_BS_ProximitySensor Usage_i8(0x46)
+#define Usage_BS_FragmentDecoding Usage_i8(0x4d)
+#define Usage_BS_ScannerReadConfidence Usage_i8(0x4e)
+#define Usage_BS_DataPrefix Usage_i8(0x4f)
+#define Usage_BS_PrefixAIMI Usage_i8(0x50)
+#define Usage_BS_PrefixNone Usage_i8(0x51)
+#define Usage_BS_PrefixProprietary Usage_i8(0x52)
+#define Usage_BS_ActiveTime Usage_i8(0x55)
+#define Usage_BS_AimingLaserPattern Usage_i8(0x56)
+#define Usage_BS_BarCodePresent Usage_i8(0x57)
+#define Usage_BS_BeeperState Usage_i8(0x58)
+#define Usage_BS_LaserOnTime Usage_i8(0x59)
+#define Usage_BS_LaserState Usage_i8(0x5a)
+#define Usage_BS_LockoutTime Usage_i8(0x5b)
+#define Usage_BS_MotorState Usage_i8(0x5c)
+#define Usage_BS_MotorTimeout Usage_i8(0x5d)
+#define Usage_BS_PowerOnResetScanner Usage_i8(0x5e)
+#define Usage_BS_PreventReadofBarcodes Usage_i8(0x5f)
+#define Usage_BS_InitiateBarcodeRead Usage_i8(0x60)
+#define Usage_BS_TriggerState Usage_i8(0x61)
+#define Usage_BS_TriggerMode Usage_i8(0x62)
+#define Usage_BS_TriggerModeBlinkingLaserOn Usage_i8(0x63)
+#define Usage_BS_TriggerModeContinuousLaserOn Usage_i8(0x64)
+#define Usage_BS_TriggerModeLaseronwhilePulled Usage_i8(0x65)
+#define Usage_BS_TriggerModeLaserstaysonafterrelease Usage_i8(0x66)
+#define Usage_BS_CommitParameterstoNVM Usage_i8(0x6d)
+#define Usage_BS_ParameterScanning Usage_i8(0x6e)
+#define Usage_BS_ParametersChanged Usage_i8(0x6f)
+#define Usage_BS_Setparameterdefaultvalues Usage_i8(0x70)
+#define Usage_BS_ScannerInCradle Usage_i8(0x75)
+#define Usage_BS_ScannerInRange Usage_i8(0x76)
+#define Usage_BS_AimDuration Usage_i8(0x7a)
+#define Usage_BS_GoodReadLampDuration Usage_i8(0x7b)
+#define Usage_BS_GoodReadLampIntensity Usage_i8(0x7c)
+#define Usage_BS_GoodReadLED Usage_i8(0x7d)
+#define Usage_BS_GoodReadToneFrequency Usage_i8(0x7e)
+#define Usage_BS_GoodReadToneLength Usage_i8(0x7f)
+#define Usage_BS_GoodReadToneVolume Usage_i8(0x80)
+#define Usage_BS_NoReadMessage Usage_i8(0x82)
+#define Usage_BS_NotonFileVolume Usage_i8(0x83)
+#define Usage_BS_PowerupBeep Usage_i8(0x84)
+#define Usage_BS_SoundErrorBeep Usage_i8(0x85)
+#define Usage_BS_SoundGoodReadBeep Usage_i8(0x86)
+#define Usage_BS_SoundNotOnFileBeep Usage_i8(0x87)
+#define Usage_BS_GoodReadWhentoWrite Usage_i8(0x88)
+#define Usage_BS_GRWTIAfterDecode Usage_i8(0x89)
+#define Usage_BS_GRWTIBeepLampaftertransmit Usage_i8(0x8a)
+#define Usage_BS_GRWTINoBeepLampuseatall Usage_i8(0x8b)
+#define Usage_BS_BooklandEAN Usage_i8(0x91)
+#define Usage_BS_ConvertEAN8to13Type Usage_i8(0x92)
+#define Usage_BS_ConvertUPCAtoEAN13 Usage_i8(0x93)
+#define Usage_BS_ConvertUPCEtoA Usage_i8(0x94)
+#define Usage_BS_EAN13 Usage_i8(0x95)
+#define Usage_BS_EAN8 Usage_i8(0x96)
+#define Usage_BS_EAN99128Mandatory Usage_i8(0x97)
+#define Usage_BS_EAN99P5128Optional Usage_i8(0x98)
+#define Usage_BS_EnableEANTwoLabel Usage_i8(0x99)
+#define Usage_BS_UPCEAN Usage_i8(0x9a)
+#define Usage_BS_UPCEANCouponCode Usage_i8(0x9b)
+#define Usage_BS_UPCEANPeriodicals Usage_i8(0x9c)
+#define Usage_BS_UPCA Usage_i8(0x9d)
+#define Usage_BS_UPCAwith128Mandatory Usage_i8(0x9e)
+#define Usage_BS_UPCAwith128Optional Usage_i8(0x9f)
+#define Usage_BS_UPCAwithP5Optional Usage_i8(0xa0)
+#define Usage_BS_UPCE Usage_i8(0xa1)
+#define Usage_BS_UPCE1 Usage_i8(0xa2)
+#define Usage_BS_Periodical Usage_i8(0xa9)
+#define Usage_BS_PeriodicalAutoDiscriminatePlus2 Usage_i8(0xaa)
+#define Usage_BS_PeriodicalOnlyDecodewithPlus2 Usage_i8(0xab)
+#define Usage_BS_PeriodicalIgnorePlus2 Usage_i8(0xac)
+#define Usage_BS_PeriodicalAutoDiscriminatePlus5 Usage_i8(0xad)
+#define Usage_BS_PeriodicalOnlyDecodewithPlus5 Usage_i8(0xae)
+#define Usage_BS_PeriodicalIgnorePlus5 Usage_i8(0xaf)
+#define Usage_BS_Check Usage_i8(0xb0)
+#define Usage_BS_CheckDisablePrice Usage_i8(0xb1)
+#define Usage_BS_CheckEnable4digitPrice Usage_i8(0xb2)
+#define Usage_BS_CheckEnable5digitPrice Usage_i8(0xb3)
+#define Usage_BS_CheckEnableEuropean4digitPrice Usage_i8(0xb4)
+#define Usage_BS_CheckEnableEuropean5digitPrice Usage_i8(0xb5)
+#define Usage_BS_EANTwoLabel Usage_i8(0xb7)
+#define Usage_BS_EANThreeLabel Usage_i8(0xb8)
+#define Usage_BS_EAN8FlagDigit1 Usage_i8(0xb9)
+#define Usage_BS_EAN8FlagDigit2 Usage_i8(0xba)
+#define Usage_BS_EAN8FlagDigit3 Usage_i8(0xbb)
+#define Usage_BS_EAN13FlagDigit1 Usage_i8(0xbc)
+#define Usage_BS_EAN13FlagDigit2 Usage_i8(0xbd)
+#define Usage_BS_EAN13FlagDigit3 Usage_i8(0xbe)
+#define Usage_BS_AddEAN23LabelDefinition Usage_i8(0xbf)
+#define Usage_BS_ClearallEAN23LabelDefinitions Usage_i8(0xc0)
+#define Usage_BS_Codabar Usage_i8(0xc3)
+#define Usage_BS_Code128 Usage_i8(0xc4)
+#define Usage_BS_Code39 Usage_i8(0xc7)
+#define Usage_BS_Code93 Usage_i8(0xc8)
+#define Usage_BS_FullASCIIConversion Usage_i8(0xc9)
+#define Usage_BS_Interleaved2of5 Usage_i8(0xca)
+#define Usage_BS_ItalianPharmacyCode Usage_i8(0xcb)
+#define Usage_BS_MSIPlessey Usage_i8(0xcc)
+#define Usage_BS_Standard2of5IATA Usage_i8(0xcd)
+#define Usage_BS_Standard2of5 Usage_i8(0xce)
+#define Usage_BS_TransmitStartStop Usage_i8(0xd3)
+#define Usage_BS_TriOptic Usage_i8(0xd4)
+#define Usage_BS_UCCEAN128 Usage_i8(0xd5)
+#define Usage_BS_CheckDigit Usage_i8(0xd6)
+#define Usage_BS_CheckDigitDisable Usage_i8(0xd7)
+#define Usage_BS_CheckDigitEnableInterleaved2of5OPCC Usage_i8(0xd8)
+#define Usage_BS_CheckDigitEnableInterleaved2of5USS Usage_i8(0xd9)
+#define Usage_BS_CheckDigitEnableStandard2of5OPCC Usage_i8(0xda)
+#define Usage_BS_CheckDigitEnableStandard2of5USS Usage_i8(0xdb)
+#define Usage_BS_CheckDigitEnableOneMSIPlessey Usage_i8(0xdc)
+#define Usage_BS_CheckDigitEnableTwoMSIPlessey Usage_i8(0xdd)
+#define Usage_BS_CheckDigitCodabarEnable Usage_i8(0xde)
+#define Usage_BS_CheckDigitCode39Enable Usage_i8(0xdf)
+#define Usage_BS_TransmitCheckDigit Usage_i8(0xf0)
+#define Usage_BS_DisableCheckDigitTransmit Usage_i8(0xf1)
+#define Usage_BS_EnableCheckDigitTransmit Usage_i8(0xf2)
+#define Usage_BS_SymbologyIdentifier1 Usage_i8(0xfb)
+#define Usage_BS_SymbologyIdentifier2 Usage_i8(0xfc)
+#define Usage_BS_SymbologyIdentifier3 Usage_i8(0xfd)
+#define Usage_BS_DecodedData Usage_i8(0xfe)
+#define Usage_BS_DecodeDataContinued Usage_i16(0xff)
+#define Usage_BS_BarSpaceData Usage_i16(0x100)
+#define Usage_BS_ScannerDataAccuracy Usage_i16(0x101)
+#define Usage_BS_RawDataPolarity Usage_i16(0x102)
+#define Usage_BS_PolarityInvertedBarCode Usage_i16(0x103)
+#define Usage_BS_PolarityNormalBarCode Usage_i16(0x104)
+#define Usage_BS_MinimumLengthtoDecode Usage_i16(0x106)
+#define Usage_BS_MaximumLengthtoDecode Usage_i16(0x107)
+#define Usage_BS_DiscreteLengthtoDecode1 Usage_i16(0x108)
+#define Usage_BS_DiscreteLengthtoDecode2 Usage_i16(0x109)
+#define Usage_BS_DataLengthMethod Usage_i16(0x10a)
+#define Usage_BS_DLMethodReadany Usage_i16(0x10b)
+#define Usage_BS_DLMethodCheckinRange Usage_i16(0x10c)
+#define Usage_BS_DLMethodCheckforDiscrete Usage_i16(0x10d)
+#define Usage_BS_AztecCode Usage_i16(0x110)
+#define Usage_BS_BC412 Usage_i16(0x111)
+#define Usage_BS_ChannelCode Usage_i16(0x112)
+#define Usage_BS_Code16 Usage_i16(0x113)
+#define Usage_BS_Code32 Usage_i16(0x114)
+#define Usage_BS_Code49 Usage_i16(0x115)
+#define Usage_BS_CodeOne Usage_i16(0x116)
+#define Usage_BS_Colorcode Usage_i16(0x117)
+#define Usage_BS_DataMatrix Usage_i16(0x118)
+#define Usage_BS_MaxiCode Usage_i16(0x119)
+#define Usage_BS_MicroPDF Usage_i16(0x11a)
+#define Usage_BS_PDF417 Usage_i16(0x11b)
+#define Usage_BS_PosiCode Usage_i16(0x11c)
+#define Usage_BS_QRCode Usage_i16(0x11d)
+#define Usage_BS_SuperCode Usage_i16(0x11e)
+#define Usage_BS_UltraCode Usage_i16(0x11f)
+#define Usage_BS_USD5SlugCode Usage_i16(0x120)
+#define Usage_BS_VeriCode Usage_i16(0x121)
+#define Usage_Sca_Scales Usage_i8(0x1)
+#define Usage_Sca_ScaleDevice Usage_i8(0x20)
+#define Usage_Sca_ScaleClass Usage_i8(0x21)
+#define Usage_Sca_ScaleClassIMetric Usage_i8(0x22)
+#define Usage_Sca_ScaleClassIIMetric Usage_i8(0x23)
+#define Usage_Sca_ScaleClassIIIMetric Usage_i8(0x24)
+#define Usage_Sca_ScaleClassIIILMetric Usage_i8(0x25)
+#define Usage_Sca_ScaleClassIVMetric Usage_i8(0x26)
+#define Usage_Sca_ScaleClassIIIEnglish Usage_i8(0x27)
+#define Usage_Sca_ScaleClassIIILEnglish Usage_i8(0x28)
+#define Usage_Sca_ScaleClassIVEnglish Usage_i8(0x29)
+#define Usage_Sca_ScaleClassGeneric Usage_i8(0x2a)
+#define Usage_Sca_ScaleAttributeReport Usage_i8(0x30)
+#define Usage_Sca_ScaleControlReport Usage_i8(0x31)
+#define Usage_Sca_ScaleDataReport Usage_i8(0x32)
+#define Usage_Sca_ScaleStatusReport Usage_i8(0x33)
+#define Usage_Sca_ScaleWeightLimitReport Usage_i8(0x34)
+#define Usage_Sca_ScaleStatisticsReport Usage_i8(0x35)
+#define Usage_Sca_DataWeight Usage_i8(0x40)
+#define Usage_Sca_DataScaling Usage_i8(0x41)
+#define Usage_Sca_WeightUnit Usage_i8(0x50)
+#define Usage_Sca_WeightUnitMilligram Usage_i8(0x51)
+#define Usage_Sca_WeightUnitGram Usage_i8(0x52)
+#define Usage_Sca_WeightUnitKilogram Usage_i8(0x53)
+#define Usage_Sca_WeightUnitCarats Usage_i8(0x54)
+#define Usage_Sca_WeightUnitTaels Usage_i8(0x55)
+#define Usage_Sca_WeightUnitGrains Usage_i8(0x56)
+#define Usage_Sca_WeightUnitPennyweights Usage_i8(0x57)
+#define Usage_Sca_WeightUnitMetricTon Usage_i8(0x58)
+#define Usage_Sca_WeightUnitAvoirTon Usage_i8(0x59)
+#define Usage_Sca_WeightUnitTroyOunce Usage_i8(0x5a)
+#define Usage_Sca_WeightUnitOunce Usage_i8(0x5b)
+#define Usage_Sca_WeightUnitPound Usage_i8(0x5c)
+#define Usage_Sca_CalibrationCount Usage_i8(0x60)
+#define Usage_Sca_ReZeroCount Usage_i8(0x61)
+#define Usage_Sca_ScaleStatus Usage_i8(0x70)
+#define Usage_Sca_ScaleStatusFault Usage_i8(0x71)
+#define Usage_Sca_ScaleStatusStableatCenterofZero Usage_i8(0x72)
+#define Usage_Sca_ScaleStatusInMotion Usage_i8(0x73)
+#define Usage_Sca_ScaleStatusWeightStable Usage_i8(0x74)
+#define Usage_Sca_ScaleStatusUnderZero Usage_i8(0x75)
+#define Usage_Sca_ScaleStatusOverWeightLimit Usage_i8(0x76)
+#define Usage_Sca_ScaleStatusRequiresCalibration Usage_i8(0x77)
+#define Usage_Sca_ScaleStatusRequiresRezeroing Usage_i8(0x78)
+#define Usage_Sca_ZeroScale Usage_i8(0x80)
+#define Usage_Sca_EnforcedZeroReturn Usage_i8(0x81)
+#define Usage_MSR_MSRDeviceReadOnly Usage_i8(0x1)
+#define Usage_MSR_Track1Length Usage_i8(0x11)
+#define Usage_MSR_Track2Length Usage_i8(0x12)
+#define Usage_MSR_Track3Length Usage_i8(0x13)
+#define Usage_MSR_TrackJISLength Usage_i8(0x14)
+#define Usage_MSR_TrackData Usage_i8(0x20)
+#define Usage_MSR_Track1Data Usage_i8(0x21)
+#define Usage_MSR_Track2Data Usage_i8(0x22)
+#define Usage_MSR_Track3Data Usage_i8(0x23)
+#define Usage_MSR_TrackJISData Usage_i8(0x24)
+#define Usage_CC_CameraAutofocus Usage_i8(0x20)
+#define Usage_CC_CameraShutter Usage_i8(0x21)
+#define Usage_Arc_GeneralPurposeIOCard Usage_i8(0x1)
+#define Usage_Arc_CoinDoor Usage_i8(0x2)
+#define Usage_Arc_WatchdogTimer Usage_i8(0x3)
+#define Usage_Arc_GeneralPurposeAnalogInputState Usage_i8(0x30)
+#define Usage_Arc_GeneralPurposeDigitalInputState Usage_i8(0x31)
+#define Usage_Arc_GeneralPurposeOpticalInputState Usage_i8(0x32)
+#define Usage_Arc_GeneralPurposeDigitalOutputState Usage_i8(0x33)
+#define Usage_Arc_NumberofCoinDoors Usage_i8(0x34)
+#define Usage_Arc_CoinDrawerDropCount Usage_i8(0x35)
+#define Usage_Arc_CoinDrawerStart Usage_i8(0x36)
+#define Usage_Arc_CoinDrawerService Usage_i8(0x37)
+#define Usage_Arc_CoinDrawerTilt Usage_i8(0x38)
+#define Usage_Arc_CoinDoorTest Usage_i8(0x39)
+#define Usage_Arc_CoinDoorLockout Usage_i8(0x40)
+#define Usage_Arc_WatchdogTimeout Usage_i8(0x41)
+#define Usage_Arc_WatchdogAction Usage_i8(0x42)
+#define Usage_Arc_WatchdogReboot Usage_i8(0x43)
+#define Usage_Arc_WatchdogRestart Usage_i8(0x44)
+#define Usage_Arc_AlarmInput Usage_i8(0x45)
+#define Usage_Arc_CoinDoorCounter Usage_i8(0x46)
+#define Usage_Arc_IODirectionMapping Usage_i8(0x47)
+#define Usage_Arc_SetIODirectionMapping Usage_i8(0x48)
+#define Usage_Arc_ExtendedOpticalInputState Usage_i8(0x49)
+#define Usage_Arc_PinPadInputState Usage_i8(0x4a)
+#define Usage_Arc_PinPadStatus Usage_i8(0x4b)
+#define Usage_Arc_PinPadOutput Usage_i8(0x4c)
+#define Usage_Arc_PinPadCommand Usage_i8(0x4d)
+#define Usage_FIDOA_U2FAuthenticatorDevice Usage_i8(0x1)
+#define Usage_FIDOA_InputReportData Usage_i8(0x20)
+#define Usage_FIDOA_OutputReportData Usage_i8(0x21)
diff --git a/drivers/hid/hid-a4tech.c b/drivers/hid/hid-a4tech.c
index 2cbc32dda7f7..54bfaf61182b 100644
--- a/drivers/hid/hid-a4tech.c
+++ b/drivers/hid/hid-a4tech.c
@@ -163,4 +163,5 @@ static struct hid_driver a4_driver = {
};
module_hid_driver(a4_driver);
+MODULE_DESCRIPTION("HID driver for some a4tech \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c
index 669d769ea1dc..ba00f6e6324b 100644
--- a/drivers/hid/hid-alps.c
+++ b/drivers/hid/hid-alps.c
@@ -8,7 +8,7 @@
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/module.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
/* ALPS Device Product ID */
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index d7b932925730..57da4f86a9fa 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -8,6 +8,8 @@
* Copyright (c) 2006-2007 Jiri Kosina
* Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
* Copyright (c) 2019 Paul Pawlowski <paul@mrarm.io>
+ * Copyright (c) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
+ * Copyright (c) 2024 Aditya Garg <gargaditya08@live.com>
*/
/*
@@ -23,11 +25,12 @@
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/leds.h>
+#include <dt-bindings/leds/common.h>
#include "hid-ids.h"
#define APPLE_RDESC_JIS BIT(0)
-#define APPLE_IGNORE_MOUSE BIT(1)
+/* BIT(1) reserved, was: APPLE_IGNORE_MOUSE */
#define APPLE_HAS_FN BIT(2)
/* BIT(3) reserved, was: APPLE_HIDDEV */
#define APPLE_ISO_TILDE_QUIRK BIT(4)
@@ -38,16 +41,23 @@
#define APPLE_RDESC_BATTERY BIT(9)
#define APPLE_BACKLIGHT_CTL BIT(10)
#define APPLE_IS_NON_APPLE BIT(11)
+#define APPLE_MAGIC_BACKLIGHT BIT(12)
+#define APPLE_DISABLE_FKEYS BIT(13)
-#define APPLE_FLAG_FKEY 0x01
+#define APPLE_FLAG_FKEY BIT(0)
+#define APPLE_FLAG_TB_FKEY BIT(1)
#define HID_COUNTRY_INTERNATIONAL_ISO 13
-#define APPLE_BATTERY_TIMEOUT_MS 60000
+#define APPLE_BATTERY_TIMEOUT_SEC 60
+
+#define HID_USAGE_MAGIC_BL 0xff00000f
+#define APPLE_MAGIC_REPORT_ID_POWER 3
+#define APPLE_MAGIC_REPORT_ID_BRIGHTNESS 1
static unsigned int fnmode = 3;
module_param(fnmode, uint, 0644);
MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, "
- "1 = fkeyslast, 2 = fkeysfirst, [3] = auto)");
+ "1 = fkeyslast, 2 = fkeysfirst, [3] = auto, 4 = fkeysdisabled)");
static int iso_layout = -1;
module_param(iso_layout, int, 0644);
@@ -79,7 +89,25 @@ struct apple_non_apple_keyboard {
struct apple_sc_backlight {
struct led_classdev cdev;
struct hid_device *hdev;
- unsigned short backlight_off, backlight_on_min, backlight_on_max;
+};
+
+struct apple_backlight_config_report {
+ u8 report_id;
+ u8 version;
+ u16 backlight_off, backlight_on_min, backlight_on_max;
+};
+
+struct apple_backlight_set_report {
+ u8 report_id;
+ u8 version;
+ u16 backlight;
+ u16 rate;
+};
+
+struct apple_magic_backlight {
+ struct led_classdev cdev;
+ struct hid_report *brightness;
+ struct hid_report *power;
};
struct apple_sc {
@@ -95,7 +123,7 @@ struct apple_sc {
struct apple_key_translation {
u16 from;
u16 to;
- u8 flags;
+ unsigned long flags;
};
static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = {
@@ -139,21 +167,7 @@ static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = {
{ }
};
-struct apple_backlight_config_report {
- u8 report_id;
- u8 version;
- u16 backlight_off, backlight_on_min, backlight_on_max;
-};
-
-struct apple_backlight_set_report {
- u8 report_id;
- u8 version;
- u16 backlight;
- u16 rate;
-};
-
-
-static const struct apple_key_translation apple2021_fn_keys[] = {
+static const struct apple_key_translation magic_keyboard_2021_and_2024_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
{ KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
@@ -199,19 +213,19 @@ static const struct apple_key_translation macbookair_fn_keys[] = {
static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
- { KEY_GRAVE, KEY_ESC },
- { KEY_1, KEY_F1 },
- { KEY_2, KEY_F2 },
- { KEY_3, KEY_F3 },
- { KEY_4, KEY_F4 },
- { KEY_5, KEY_F5 },
- { KEY_6, KEY_F6 },
- { KEY_7, KEY_F7 },
- { KEY_8, KEY_F8 },
- { KEY_9, KEY_F9 },
- { KEY_0, KEY_F10 },
- { KEY_MINUS, KEY_F11 },
- { KEY_EQUAL, KEY_F12 },
+ { KEY_GRAVE, KEY_ESC, APPLE_FLAG_TB_FKEY },
+ { KEY_1, KEY_F1, APPLE_FLAG_TB_FKEY },
+ { KEY_2, KEY_F2, APPLE_FLAG_TB_FKEY },
+ { KEY_3, KEY_F3, APPLE_FLAG_TB_FKEY },
+ { KEY_4, KEY_F4, APPLE_FLAG_TB_FKEY },
+ { KEY_5, KEY_F5, APPLE_FLAG_TB_FKEY },
+ { KEY_6, KEY_F6, APPLE_FLAG_TB_FKEY },
+ { KEY_7, KEY_F7, APPLE_FLAG_TB_FKEY },
+ { KEY_8, KEY_F8, APPLE_FLAG_TB_FKEY },
+ { KEY_9, KEY_F9, APPLE_FLAG_TB_FKEY },
+ { KEY_0, KEY_F10, APPLE_FLAG_TB_FKEY },
+ { KEY_MINUS, KEY_F11, APPLE_FLAG_TB_FKEY },
+ { KEY_EQUAL, KEY_F12, APPLE_FLAG_TB_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
@@ -222,18 +236,18 @@ static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = {
static const struct apple_key_translation macbookpro_dedicated_esc_fn_keys[] = {
{ KEY_BACKSPACE, KEY_DELETE },
{ KEY_ENTER, KEY_INSERT },
- { KEY_1, KEY_F1 },
- { KEY_2, KEY_F2 },
- { KEY_3, KEY_F3 },
- { KEY_4, KEY_F4 },
- { KEY_5, KEY_F5 },
- { KEY_6, KEY_F6 },
- { KEY_7, KEY_F7 },
- { KEY_8, KEY_F8 },
- { KEY_9, KEY_F9 },
- { KEY_0, KEY_F10 },
- { KEY_MINUS, KEY_F11 },
- { KEY_EQUAL, KEY_F12 },
+ { KEY_1, KEY_F1, APPLE_FLAG_TB_FKEY },
+ { KEY_2, KEY_F2, APPLE_FLAG_TB_FKEY },
+ { KEY_3, KEY_F3, APPLE_FLAG_TB_FKEY },
+ { KEY_4, KEY_F4, APPLE_FLAG_TB_FKEY },
+ { KEY_5, KEY_F5, APPLE_FLAG_TB_FKEY },
+ { KEY_6, KEY_F6, APPLE_FLAG_TB_FKEY },
+ { KEY_7, KEY_F7, APPLE_FLAG_TB_FKEY },
+ { KEY_8, KEY_F8, APPLE_FLAG_TB_FKEY },
+ { KEY_9, KEY_F9, APPLE_FLAG_TB_FKEY },
+ { KEY_0, KEY_F10, APPLE_FLAG_TB_FKEY },
+ { KEY_MINUS, KEY_F11, APPLE_FLAG_TB_FKEY },
+ { KEY_EQUAL, KEY_F12, APPLE_FLAG_TB_FKEY },
{ KEY_UP, KEY_PAGEUP },
{ KEY_DOWN, KEY_PAGEDOWN },
{ KEY_LEFT, KEY_HOME },
@@ -341,9 +355,15 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
{ "SONiX USB DEVICE" },
+ { "SONiX AK870 PRO" },
{ "Keychron" },
{ "AONE" },
- { "GANSS" }
+ { "GANSS" },
+ { "Hailuck" },
+ { "Jamesdonkey" },
+ { "A3R" },
+ { "hfd.cn" },
+ { "WKB603" },
};
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
@@ -360,6 +380,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)
{
@@ -401,7 +427,12 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
unsigned int real_fnmode;
if (fnmode == 3) {
- real_fnmode = (asc->quirks & APPLE_IS_NON_APPLE) ? 2 : 1;
+ if (asc->quirks & APPLE_DISABLE_FKEYS)
+ real_fnmode = 4;
+ else if (asc->quirks & APPLE_IS_NON_APPLE)
+ real_fnmode = 2;
+ else
+ real_fnmode = 1;
} else {
real_fnmode = fnmode;
}
@@ -442,41 +473,54 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
asc->fn_on = !!value;
if (real_fnmode) {
- if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO ||
- hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS)
+ switch (hid->product) {
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS:
table = magic_keyboard_alu_fn_keys;
- else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 ||
- hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015)
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015:
+ case 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_FINGERPRINT_2021 ||
- hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021)
- table = apple2021_fn_keys;
- else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 ||
- hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 ||
- hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213)
- table = macbookpro_no_esc_fn_keys;
- else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K ||
- hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 ||
- hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F)
- table = macbookpro_dedicated_esc_fn_keys;
- else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K ||
- hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K)
- table = apple_fn_keys;
- else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
- hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
- table = macbookair_fn_keys;
- else if (hid->product < 0x21d || hid->product >= 0x300)
- table = powerbook_fn_keys;
- else
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024:
+ table = magic_keyboard_2021_and_2024_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT:
+ table = macbookpro_no_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223:
+ table = macbookpro_dedicated_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K:
table = apple_fn_keys;
+ break;
+ default:
+ if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
+ hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
+ table = macbookair_fn_keys;
+ else if (hid->product < 0x21d || hid->product >= 0x300)
+ table = powerbook_fn_keys;
+ else
+ table = apple_fn_keys;
+ }
trans = apple_find_translation(table, code);
@@ -499,9 +543,17 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
do_translate = asc->fn_on;
break;
default:
- /* should never happen */
+ /* case 4 */
do_translate = false;
}
+ } else if (trans->flags & APPLE_FLAG_TB_FKEY) {
+ switch (real_fnmode) {
+ case 4:
+ do_translate = false;
+ break;
+ default:
+ do_translate = asc->fn_on;
+ }
} else {
do_translate = asc->fn_on;
}
@@ -589,12 +641,12 @@ static int apple_fetch_battery(struct hid_device *hdev)
static void apple_battery_timer_tick(struct timer_list *t)
{
- struct apple_sc *asc = from_timer(asc, t, battery_timer);
+ struct apple_sc *asc = timer_container_of(asc, t, battery_timer);
struct hid_device *hdev = asc->hdev;
if (apple_fetch_battery(hdev) == 0) {
mod_timer(&asc->battery_timer,
- jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS));
+ jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
}
}
@@ -602,7 +654,7 @@ static void apple_battery_timer_tick(struct timer_list *t)
* MacBook JIS keyboard has wrong logical maximum
* Magic Keyboard JIS has wrong logical maximum
*/
-static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
@@ -657,7 +709,7 @@ static void apple_setup_input(struct input_dev *input)
apple_setup_key_translation(input, apple_iso_keyboard);
apple_setup_key_translation(input, magic_keyboard_alu_fn_keys);
apple_setup_key_translation(input, magic_keyboard_2015_fn_keys);
- apple_setup_key_translation(input, apple2021_fn_keys);
+ apple_setup_key_translation(input, magic_keyboard_2021_and_2024_fn_keys);
apple_setup_key_translation(input, macbookpro_no_esc_fn_keys);
apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys);
}
@@ -706,7 +758,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;
}
@@ -818,6 +870,67 @@ cleanup_and_exit:
return ret;
}
+static void apple_magic_backlight_report_set(struct hid_report *rep, s32 value, u8 rate)
+{
+ rep->field[0]->value[0] = value;
+ rep->field[1]->value[0] = 0x5e; /* Mimic Windows */
+ rep->field[1]->value[0] |= rate << 8;
+
+ hid_hw_request(rep->device, rep, HID_REQ_SET_REPORT);
+}
+
+static void apple_magic_backlight_set(struct apple_magic_backlight *backlight,
+ int brightness, char rate)
+{
+ apple_magic_backlight_report_set(backlight->power, brightness ? 1 : 0, rate);
+ if (brightness)
+ apple_magic_backlight_report_set(backlight->brightness, brightness, rate);
+}
+
+static int apple_magic_backlight_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct apple_magic_backlight *backlight = container_of(led_cdev,
+ struct apple_magic_backlight, cdev);
+
+ apple_magic_backlight_set(backlight, brightness, 1);
+ return 0;
+}
+
+static int apple_magic_backlight_init(struct hid_device *hdev)
+{
+ struct apple_magic_backlight *backlight;
+ struct hid_report_enum *report_enum;
+
+ /*
+ * Ensure this usb endpoint is for the keyboard backlight, not touchbar
+ * backlight.
+ */
+ if (hdev->collection[0].usage != HID_USAGE_MAGIC_BL)
+ return -ENODEV;
+
+ backlight = devm_kzalloc(&hdev->dev, sizeof(*backlight), GFP_KERNEL);
+ if (!backlight)
+ return -ENOMEM;
+
+ report_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+ backlight->brightness = report_enum->report_id_hash[APPLE_MAGIC_REPORT_ID_BRIGHTNESS];
+ backlight->power = report_enum->report_id_hash[APPLE_MAGIC_REPORT_ID_POWER];
+
+ if (!backlight->brightness || backlight->brightness->maxfield < 2 ||
+ !backlight->power || backlight->power->maxfield < 2)
+ return -ENODEV;
+
+ backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
+ backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
+ backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
+
+ apple_magic_backlight_set(backlight, 0, 0);
+
+ return devm_led_classdev_register(&hdev->dev, &backlight->cdev);
+
+}
+
static int apple_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -848,22 +961,38 @@ static int apple_probe(struct hid_device *hdev,
return ret;
}
- timer_setup(&asc->battery_timer, apple_battery_timer_tick, 0);
- mod_timer(&asc->battery_timer,
- jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS));
- apple_fetch_battery(hdev);
+ if (quirks & APPLE_RDESC_BATTERY) {
+ timer_setup(&asc->battery_timer, apple_battery_timer_tick, 0);
+ mod_timer(&asc->battery_timer,
+ jiffies + secs_to_jiffies(APPLE_BATTERY_TIMEOUT_SEC));
+ apple_fetch_battery(hdev);
+ }
if (quirks & APPLE_BACKLIGHT_CTL)
apple_backlight_init(hdev);
+ if (quirks & APPLE_MAGIC_BACKLIGHT) {
+ ret = apple_magic_backlight_init(hdev);
+ if (ret)
+ goto out_err;
+ }
+
return 0;
+
+out_err:
+ if (quirks & APPLE_RDESC_BATTERY)
+ timer_delete_sync(&asc->battery_timer);
+
+ hid_hw_stop(hdev);
+ return ret;
}
static void apple_remove(struct hid_device *hdev)
{
struct apple_sc *asc = hid_get_drvdata(hdev);
- del_timer_sync(&asc->battery_timer);
+ if (asc->quirks & APPLE_RDESC_BATTERY)
+ timer_delete_sync(&asc->battery_timer);
hid_hw_stop(hdev);
}
@@ -1033,19 +1162,25 @@ static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K),
.driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT),
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK |
+ APPLE_DISABLE_FKEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K),
- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223),
- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K),
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F),
- .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_DISABLE_FKEYS },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
@@ -1069,6 +1204,20 @@ 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_NUMPAD_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_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_FINGERPRINT_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_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_NUMPAD_2024),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
+ .driver_data = APPLE_MAGIC_BACKLIGHT },
{ }
};
@@ -1087,4 +1236,5 @@ static struct hid_driver apple_driver = {
};
module_hid_driver(apple_driver);
+MODULE_DESCRIPTION("Apple USB HID quirks support for Linux");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
index 8deded185725..5e8ced7bc05a 100644
--- a/drivers/hid/hid-appleir.c
+++ b/drivers/hid/hid-appleir.c
@@ -167,7 +167,7 @@ static void battery_flat(struct appleir *appleir)
static void key_up_tick(struct timer_list *t)
{
- struct appleir *appleir = from_timer(appleir, t, key_up_timer);
+ struct appleir *appleir = timer_container_of(appleir, t, key_up_timer);
struct hid_device *hid = appleir->hid;
unsigned long flags;
@@ -188,7 +188,7 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
static const u8 flatbattery[] = { 0x25, 0x87, 0xe0 };
unsigned long flags;
- if (len != 5)
+ if (len != 5 || !(hid->claimed & HID_CLAIMED_INPUT))
goto out;
if (!memcmp(data, keydown, sizeof(keydown))) {
@@ -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..b00687e67ce8
--- /dev/null
+++ b/drivers/hid/hid-appletb-kbd.c
@@ -0,0 +1,519 @@
+// 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 = timer_container_of(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 + secs_to_jiffies(appletb_tb_idle_timeout));
+ } 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 + secs_to_jiffies(appletb_tb_dim_timeout));
+ }
+}
+
+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 + secs_to_jiffies(appletb_tb_dim_timeout));
+ }
+
+ 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 fd61dba88233..472bca54642b 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -27,6 +27,7 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
+#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
@@ -52,6 +53,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 +89,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 | \
@@ -335,36 +341,20 @@ static int asus_raw_event(struct hid_device *hdev,
if (drvdata->quirks & QUIRK_MEDION_E1239T)
return asus_e1239t_event(drvdata, data, size);
- if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) {
+ /*
+ * Skip these report ID, the device emits a continuous stream associated
+ * with the AURA mode it is in which looks like an 'echo'.
+ */
+ if (report->id == FEATURE_KBD_LED_REPORT_ID1 || report->id == FEATURE_KBD_LED_REPORT_ID2)
+ return -1;
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
/*
- * Skip these report ID, the device emits a continuous stream associated
- * with the AURA mode it is in which looks like an 'echo'.
+ * G713 and G733 send these codes on some keypresses, depending on
+ * the key pressed it can trigger a shutdown event if not caught.
*/
- if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
- report->id == FEATURE_KBD_LED_REPORT_ID2) {
+ if (data[0] == 0x02 && data[1] == 0x30) {
return -1;
- /* Additional report filtering */
- } else if (report->id == FEATURE_KBD_REPORT_ID) {
- /*
- * G14 and G15 send these codes on some keypresses with no
- * discernable reason for doing so. We'll filter them out to avoid
- * unmapped warning messages later.
- */
- if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
- data[1] == 0x8a || data[1] == 0x9e) {
- return -1;
- }
}
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
- /*
- * G713 and G733 send these codes on some keypresses, depending on
- * the key pressed it can trigger a shutdown event if not caught.
- */
- if(data[0] == 0x02 && data[1] == 0x30) {
- return -1;
- }
- }
-
}
if (drvdata->quirks & QUIRK_ROG_CLAYMORE_II_KEYBOARD) {
@@ -381,7 +371,7 @@ static int asus_raw_event(struct hid_device *hdev,
return 0;
}
-static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size)
+static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t buf_size)
{
unsigned char *dmabuf;
int ret;
@@ -402,9 +392,9 @@ static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size
return ret;
}
-static int asus_kbd_init(struct hid_device *hdev)
+static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
- u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
+ const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
@@ -416,9 +406,10 @@ static int asus_kbd_init(struct hid_device *hdev)
}
static int asus_kbd_get_functions(struct hid_device *hdev,
- unsigned char *kbd_func)
+ unsigned char *kbd_func,
+ u8 report_id)
{
- u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 };
+ const u8 buf[] = { report_id, 0x05, 0x20, 0x31, 0x00, 0x08 };
u8 *readbuf;
int ret;
@@ -447,49 +438,24 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
return ret;
}
-static int rog_nkey_led_init(struct hid_device *hdev)
+static int asus_kbd_disable_oobe(struct hid_device *hdev)
{
- u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
- u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
- 0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
- u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
- 0x05, 0x20, 0x31, 0x00, 0x08 };
+ const u8 init[][6] = {
+ { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 },
+ { FEATURE_KBD_REPORT_ID, 0xBA, 0xC5, 0xC4 },
+ { FEATURE_KBD_REPORT_ID, 0xD0, 0x8F, 0x01 },
+ { FEATURE_KBD_REPORT_ID, 0xD0, 0x85, 0xFF }
+ };
int ret;
- hid_info(hdev, "Asus initialise N-KEY Device");
- /* The first message is an init start */
- ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
- if (ret < 0) {
- hid_warn(hdev, "Asus failed to send init start command: %d\n", ret);
- return ret;
- }
- /* Followed by a string */
- ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
- if (ret < 0) {
- hid_warn(hdev, "Asus failed to send init command 1.0: %d\n", ret);
- return ret;
- }
- /* Followed by a string */
- ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
- if (ret < 0) {
- hid_warn(hdev, "Asus failed to send init command 1.1: %d\n", ret);
- return ret;
- }
-
- /* begin second report ID with same data */
- buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
- buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
-
- ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
- if (ret < 0) {
- hid_warn(hdev, "Asus failed to send init command 2.0: %d\n", ret);
- return ret;
+ for (size_t i = 0; i < ARRAY_SIZE(init); i++) {
+ ret = asus_kbd_set_report(hdev, init[i], sizeof(init[i]));
+ if (ret < 0)
+ return ret;
}
- ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
- if (ret < 0)
- hid_warn(hdev, "Asus failed to send init command 2.1: %d\n", ret);
- return ret;
+ hid_info(hdev, "Disabled OOBE for keyboard\n");
+ return 0;
}
static void asus_schedule_work(struct asus_kbd_leds *led)
@@ -552,12 +518,19 @@ static void asus_kbd_backlight_work(struct work_struct *work)
*/
static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
{
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
u32 value;
int ret;
if (!IS_ENABLED(CONFIG_ASUS_WMI))
return false;
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
+ dmi_check_system(asus_use_hid_led_dmi_ids)) {
+ hid_info(hdev, "using HID for asus::kbd_backlight\n");
+ return false;
+ }
+
ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
@@ -567,24 +540,141 @@ 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);
+ } else {
+ set_ally_mcu_hack(ASUS_WMI_ALLY_MCU_HACK_DISABLED);
+ set_ally_mcu_powersave(true);
+ }
+}
+
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;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
- ret = rog_nkey_led_init(hdev);
+ /* Initialize keyboard */
+ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
+ if (ret < 0)
+ return ret;
+
+ /* The LED endpoint is initialised in two HID */
+ ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
+ if (ret < 0)
+ return ret;
+
+ ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
if (ret < 0)
return ret;
+
+ if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
+ ret = asus_kbd_disable_oobe(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);
+ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
- ret = asus_kbd_get_functions(hdev, &kbd_func);
+ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
@@ -885,7 +975,10 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break;
case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN); break;
case 0xc7: asus_map_key_clear(KEY_KBDILLUMTOGGLE); break;
+ case 0x4e: asus_map_key_clear(KEY_FN_ESC); break;
+ case 0x7e: asus_map_key_clear(KEY_EMOJI_PICKER); break;
+ case 0x8b: asus_map_key_clear(KEY_PROG1); break; /* ProArt Creator Hub key */
case 0x6b: asus_map_key_clear(KEY_F21); break; /* ASUS touchpad toggle */
case 0x38: asus_map_key_clear(KEY_PROG1); break; /* ROG key */
case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+C ASUS Splendid */
@@ -897,7 +990,10 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0xb3: asus_map_key_clear(KEY_PROG3); break; /* Fn+Left next aura */
case 0x6a: asus_map_key_clear(KEY_F13); break; /* Screenpad toggle */
case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */
-
+ case 0xa5: asus_map_key_clear(KEY_F15); break; /* ROG Ally left back */
+ case 0xa6: asus_map_key_clear(KEY_F16); break; /* ROG Ally QAM button */
+ case 0xa7: asus_map_key_clear(KEY_F17); break; /* ROG Ally ROG long-press */
+ case 0xa8: asus_map_key_clear(KEY_F18); break; /* ROG Ally ROG long-press-release */
default:
/* ASUS lazily declares 256 usages, ignore the rest,
@@ -1000,6 +1096,24 @@ static int asus_start_multitouch(struct hid_device *hdev)
return 0;
}
+static int __maybe_unused asus_resume(struct hid_device *hdev) {
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret = 0;
+
+ if (drvdata->kbd_backlight) {
+ const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
+ drvdata->kbd_backlight->cdev.brightness };
+ ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
+ if (ret < 0) {
+ hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
+ goto asus_resume_err;
+ }
+ }
+
+asus_resume_err:
+ return ret;
+}
+
static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
@@ -1103,7 +1217,13 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
- if (!drvdata->input) {
+ /*
+ * Check that input registration succeeded. Checking that
+ * HID_CLAIMED_INPUT is set prevents a UAF when all input devices
+ * were freed during registration due to no usages being mapped,
+ * leaving drvdata->input pointing to freed memory.
+ */
+ if (!drvdata->input || !(hdev->claimed & HID_CLAIMED_INPUT)) {
hid_err(hdev, "Asus input not registered\n");
ret = -ENOMEM;
goto err_stop_hw;
@@ -1148,7 +1268,7 @@ static const __u8 asus_g752_fixed_rdesc[] = {
0x2A, 0xFF, 0x00, /* Usage Maximum (0xFF) */
};
-static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
@@ -1205,7 +1325,7 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
*rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
- /* report is missing usage mninum and maximum */
+ /* report is missing usage minimum and maximum */
__u8 *new_rdesc;
size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);
@@ -1232,6 +1352,19 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
rdesc[205] = 0x01;
}
+ /* match many more n-key devices */
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && *rsize > 15) {
+ for (int i = 0; i < *rsize - 15; i++) {
+ /* offset to the count from 0x5a report part always 14 */
+ if (rdesc[i] == 0x85 && rdesc[i + 1] == 0x5a &&
+ rdesc[i + 14] == 0x95 && rdesc[i + 15] == 0x05) {
+ hid_info(hdev, "Fixing up Asus N-Key report descriptor\n");
+ rdesc[i + 15] = 0x01;
+ break;
+ }
+ }
+ }
+
return rdesc;
}
@@ -1256,9 +1389,15 @@ static const struct hid_device_id asus_devices[] = {
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
- USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
+ USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
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_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_ROG_ALLY_XPAD },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
QUIRK_ROG_CLAYMORE_II_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
@@ -1279,6 +1418,9 @@ static const struct hid_device_id asus_devices[] = {
* part, while letting hid-multitouch.c handle the touchpad.
*/
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
{ }
};
@@ -1294,10 +1436,12 @@ static struct hid_driver asus_driver = {
.input_configured = asus_input_configured,
#ifdef CONFIG_PM
.reset_resume = asus_reset_resume,
+ .resume = asus_resume,
#endif
.event = asus_event,
.raw_event = asus_raw_event
};
module_hid_driver(asus_driver);
-MODULE_LICENSE("GPL"); \ No newline at end of file
+MODULE_IMPORT_NS("ASUS_WMI");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-aureal.c b/drivers/hid/hid-aureal.c
index ac8946f80e22..896304148a87 100644
--- a/drivers/hid/hid-aureal.c
+++ b/drivers/hid/hid-aureal.c
@@ -18,7 +18,7 @@
#include "hid-ids.h"
-static __u8 *aureal_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *aureal_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
@@ -41,4 +41,5 @@ static struct hid_driver aureal_driver = {
};
module_hid_driver(aureal_driver);
+MODULE_DESCRIPTION("HID driver for Aureal Cy se W-01RN USB_V3.1 devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-belkin.c b/drivers/hid/hid-belkin.c
index fc0b3bb383cc..75aaed35ee9f 100644
--- a/drivers/hid/hid-belkin.c
+++ b/drivers/hid/hid-belkin.c
@@ -85,4 +85,5 @@ static struct hid_driver belkin_driver = {
};
module_hid_driver(belkin_driver);
+MODULE_DESCRIPTION("HID driver for some belkin \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c
index 25ed7b9a917e..a6d5f030d023 100644
--- a/drivers/hid/hid-betopff.c
+++ b/drivers/hid/hid-betopff.c
@@ -162,4 +162,5 @@ static struct hid_driver betop_driver = {
};
module_hid_driver(betop_driver);
+MODULE_DESCRIPTION("Force feedback support for Betop based devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c
index a02cb517b4c4..9f05465358d9 100644
--- a/drivers/hid/hid-bigbenff.c
+++ b/drivers/hid/hid-bigbenff.c
@@ -99,7 +99,7 @@
* - map previously unused analog trigger data to Z/RZ
* - simplify feature and output descriptor
*/
-static __u8 pid0902_rdesc_fixed[] = {
+static const __u8 pid0902_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
0x09, 0x05, /* Usage (Game Pad) */
0xA1, 0x01, /* Collection (Application) */
@@ -464,12 +464,12 @@ error_hw_stop:
return error;
}
-static __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc,
+static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == PID0902_RDESC_ORIG_SIZE) {
- rdesc = pid0902_rdesc_fixed;
*rsize = sizeof(pid0902_rdesc_fixed);
+ return pid0902_rdesc_fixed;
} else
hid_warn(hid, "unexpected rdesc, please submit for review\n");
return rdesc;
@@ -490,4 +490,5 @@ static struct hid_driver bigben_driver = {
};
module_hid_driver(bigben_driver);
+MODULE_DESCRIPTION("LED & force feedback support for BigBen Interactive");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-cherry.c b/drivers/hid/hid-cherry.c
index 6a71187b5cf6..a504632febfc 100644
--- a/drivers/hid/hid-cherry.c
+++ b/drivers/hid/hid-cherry.c
@@ -22,7 +22,7 @@
* Cherry Cymotion keyboard have an invalid HID report descriptor,
* that needs fixing before we can parse it.
*/
-static __u8 *ch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *ch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 18 && rdesc[11] == 0x3c && rdesc[12] == 0x02) {
@@ -68,4 +68,5 @@ static struct hid_driver ch_driver = {
};
module_hid_driver(ch_driver);
+MODULE_DESCRIPTION("HID driver for some cherry \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c
index f04d2aa23efe..5776ec2e7159 100644
--- a/drivers/hid/hid-chicony.c
+++ b/drivers/hid/hid-chicony.c
@@ -88,8 +88,8 @@ static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 1;
}
-static __u8 *ch_switch12_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *ch_switch12_report_fixup(struct hid_device *hdev,
+ __u8 *rdesc, unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -152,4 +152,5 @@ static struct hid_driver ch_driver = {
};
module_hid_driver(ch_driver);
+MODULE_DESCRIPTION("HID driver for some chicony \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c
index cab42047bc99..528d7f361215 100644
--- a/drivers/hid/hid-cmedia.c
+++ b/drivers/hid/hid-cmedia.c
@@ -26,7 +26,7 @@ MODULE_LICENSE("GPL");
/* Fixed report descriptor of HS-100B audio chip
* Bit 4 is an abolute Microphone mute usage instead of being unassigned.
*/
-static __u8 hs100b_rdesc_fixed[] = {
+static const __u8 hs100b_rdesc_fixed[] = {
0x05, 0x0C, /* Usage Page (Consumer), */
0x09, 0x01, /* Usage (Consumer Control), */
0xA1, 0x01, /* Collection (Application), */
@@ -199,13 +199,13 @@ static struct hid_driver cmhid_driver = {
.input_mapping = cmhid_input_mapping,
};
-static __u8 *cmhid_hs100b_report_fixup(struct hid_device *hid, __u8 *rdesc,
+static const __u8 *cmhid_hs100b_report_fixup(struct hid_device *hid, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == HS100B_RDESC_ORIG_SIZE) {
hid_info(hid, "Fixing CMedia HS-100B report descriptor\n");
- rdesc = hs100b_rdesc_fixed;
*rsize = sizeof(hs100b_rdesc_fixed);
+ return hs100b_rdesc_fixed;
}
return rdesc;
}
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 8992e3c1e769..a5b3a8ca2fcb 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -20,7 +20,7 @@
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/spinlock.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <asm/byteorder.h>
#include <linux/input.h>
#include <linux/wait.h>
@@ -46,6 +46,38 @@ module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600
MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
/*
+ * Convert a signed n-bit integer to signed 32-bit integer.
+ */
+
+static s32 snto32(__u32 value, unsigned int n)
+{
+ if (!value || !n)
+ return 0;
+
+ if (n > 32)
+ n = 32;
+
+ return sign_extend32(value, n - 1);
+}
+
+/*
+ * Convert a signed 32-bit integer to a signed n-bit integer.
+ */
+
+static u32 s32ton(__s32 value, unsigned int n)
+{
+ s32 a;
+
+ if (!value || !n)
+ return 0;
+
+ a = value >> (n - 1);
+ if (a && a != -1)
+ return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
+ return value & ((1 << n) - 1);
+}
+
+/*
* Register a new report for a device.
*/
@@ -95,9 +127,9 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
return NULL;
}
- field = kzalloc((sizeof(struct hid_field) +
- usages * sizeof(struct hid_usage) +
- 3 * usages * sizeof(unsigned int)), GFP_KERNEL);
+ field = kvzalloc((sizeof(struct hid_field) +
+ usages * sizeof(struct hid_usage) +
+ 3 * usages * sizeof(unsigned int)), GFP_KERNEL);
if (!field)
return NULL;
@@ -425,7 +457,7 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
* both this and the standard encoding. */
raw_value = item_sdata(item);
if (!(raw_value & 0xfffffff0))
- parser->global.unit_exponent = hid_snto32(raw_value, 4);
+ parser->global.unit_exponent = snto32(raw_value, 4);
else
parser->global.unit_exponent = raw_value;
return 0;
@@ -629,7 +661,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_ratelimited(parser->device, "reserved main item tag 0x%x\n", item->tag);
+ else
+ hid_warn_ratelimited(parser->device, "unknown main item tag 0x%x\n", item->tag);
ret = 0;
}
@@ -661,7 +697,7 @@ static void hid_free_report(struct hid_report *report)
kfree(report->field_entries);
for (n = 0; n < report->maxfield; n++)
- kfree(report->field[n]);
+ kvfree(report->field[n]);
kfree(report);
}
@@ -685,7 +721,14 @@ static void hid_close_report(struct hid_device *device)
INIT_LIST_HEAD(&report_enum->report_list);
}
- kfree(device->rdesc);
+ /*
+ * If the HID driver had a rdesc_fixup() callback, dev->rdesc
+ * will be allocated by hid-core and needs to be freed.
+ * Otherwise, it is either equal to dev_rdesc or bpf_rdesc, in
+ * which cases it'll be freed later on device removal or destroy.
+ */
+ if (device->rdesc != device->dev_rdesc && device->rdesc != device->bpf_rdesc)
+ kfree(device->rdesc);
device->rdesc = NULL;
device->rsize = 0;
@@ -698,25 +741,41 @@ static void hid_close_report(struct hid_device *device)
device->status &= ~HID_STAT_PARSED;
}
+static inline void hid_free_bpf_rdesc(struct hid_device *hdev)
+{
+ /* bpf_rdesc is either equal to dev_rdesc or allocated by call_hid_bpf_rdesc_fixup() */
+ if (hdev->bpf_rdesc != hdev->dev_rdesc)
+ kfree(hdev->bpf_rdesc);
+ hdev->bpf_rdesc = NULL;
+}
+
/*
* Free a device structure, all reports, and all fields.
*/
-static void hid_device_release(struct device *dev)
+void hiddev_free(struct kref *ref)
{
- struct hid_device *hid = to_hid_device(dev);
+ struct hid_device *hid = container_of(ref, struct hid_device, ref);
hid_close_report(hid);
+ hid_free_bpf_rdesc(hid);
kfree(hid->dev_rdesc);
kfree(hid);
}
+static void hid_device_release(struct device *dev)
+{
+ struct hid_device *hid = to_hid_device(dev);
+
+ kref_put(&hid->ref, hiddev_free);
+}
+
/*
* Fetch a report description item from the data stream. We support long
* items, though they are not used yet.
*/
-static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item)
+static const u8 *fetch_item(const __u8 *start, const __u8 *end, struct hid_item *item)
{
u8 b;
@@ -747,35 +806,29 @@ static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item)
}
item->format = HID_ITEM_FORMAT_SHORT;
- item->size = b & 3;
+ item->size = BIT(b & 3) >> 1; /* 0, 1, 2, 3 -> 0, 1, 2, 4 */
+
+ if (end - start < item->size)
+ return NULL;
switch (item->size) {
case 0:
- return start;
+ break;
case 1:
- if ((end - start) < 1)
- return NULL;
- item->data.u8 = *start++;
- return start;
+ item->data.u8 = *start;
+ break;
case 2:
- if ((end - start) < 2)
- return NULL;
item->data.u16 = get_unaligned_le16(start);
- start = (__u8 *)((__le16 *)start + 1);
- return start;
+ break;
- case 3:
- item->size++;
- if ((end - start) < 4)
- return NULL;
+ case 4:
item->data.u32 = get_unaligned_le32(start);
- start = (__u8 *)((__le32 *)start + 1);
- return start;
+ break;
}
- return NULL;
+ return start + item->size;
}
static void hid_scan_input_usage(struct hid_parser *parser, u32 usage)
@@ -873,8 +926,8 @@ static int hid_scan_report(struct hid_device *hid)
{
struct hid_parser *parser;
struct hid_item item;
- __u8 *start = hid->dev_rdesc;
- __u8 *end = start + hid->dev_rsize;
+ const __u8 *start = hid->dev_rdesc;
+ const __u8 *end = start + hid->dev_rsize;
static int (*dispatch_type[])(struct hid_parser *parser,
struct hid_item *item) = {
hid_scan_main,
@@ -891,6 +944,15 @@ static int hid_scan_report(struct hid_device *hid)
hid->group = HID_GROUP_GENERIC;
/*
+ * In case we are re-scanning after a BPF has been loaded,
+ * we need to use the bpf report descriptor, not the original one.
+ */
+ if (hid->bpf_rdesc && hid->bpf_rsize) {
+ start = hid->bpf_rdesc;
+ end = start + hid->bpf_rsize;
+ }
+
+ /*
* The parsing is simpler than the one in hid_open_report() as we should
* be robust against hid errors. Those errors will be raised by
* hid_open_report() anyway.
@@ -939,7 +1001,7 @@ static int hid_scan_report(struct hid_device *hid)
* Allocate the device report as read by the bus driver. This function should
* only be called from parse() in ll drivers.
*/
-int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size)
+int hid_parse_report(struct hid_device *hid, const __u8 *start, unsigned size)
{
hid->dev_rdesc = kmemdup(start, size, GFP_KERNEL);
if (!hid->dev_rdesc)
@@ -1118,6 +1180,8 @@ static void hid_apply_multiplier(struct hid_device *hid,
while (multiplier_collection->parent_idx != -1 &&
multiplier_collection->type != HID_COLLECTION_LOGICAL)
multiplier_collection = &hid->collection[multiplier_collection->parent_idx];
+ if (multiplier_collection->type != HID_COLLECTION_LOGICAL)
+ multiplier_collection = NULL;
effective_multiplier = hid_calculate_multiplier(hid, multiplier);
@@ -1197,10 +1261,9 @@ int hid_open_report(struct hid_device *device)
struct hid_parser *parser;
struct hid_item item;
unsigned int size;
- __u8 *start;
- __u8 *buf;
- __u8 *end;
- __u8 *next;
+ const __u8 *start;
+ const __u8 *end;
+ const __u8 *next;
int ret;
int i;
static int (*dispatch_type[])(struct hid_parser *parser,
@@ -1214,25 +1277,34 @@ int hid_open_report(struct hid_device *device)
if (WARN_ON(device->status & HID_STAT_PARSED))
return -EBUSY;
- start = device->dev_rdesc;
+ start = device->bpf_rdesc;
if (WARN_ON(!start))
return -ENODEV;
- size = device->dev_rsize;
+ size = device->bpf_rsize;
- /* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
- buf = call_hid_bpf_rdesc_fixup(device, start, &size);
- if (buf == NULL)
- return -ENOMEM;
+ if (device->driver->report_fixup) {
+ /*
+ * device->driver->report_fixup() needs to work
+ * on a copy of our report descriptor so it can
+ * change it.
+ */
+ __u8 *buf = kmemdup(start, size, GFP_KERNEL);
+
+ if (buf == NULL)
+ return -ENOMEM;
- if (device->driver->report_fixup)
start = device->driver->report_fixup(device, buf, &size);
- else
- start = buf;
- start = kmemdup(start, size, GFP_KERNEL);
- kfree(buf);
- if (start == NULL)
- return -ENOMEM;
+ /*
+ * The second kmemdup is required in case report_fixup() returns
+ * a static read-only memory, but we have no idea if that memory
+ * needs to be cleaned up or not at the end.
+ */
+ start = kmemdup(start, size, GFP_KERNEL);
+ kfree(buf);
+ if (start == NULL)
+ return -ENOMEM;
+ }
device->rdesc = start;
device->rsize = size;
@@ -1309,46 +1381,6 @@ alloc_err:
EXPORT_SYMBOL_GPL(hid_open_report);
/*
- * Convert a signed n-bit integer to signed 32-bit integer. Common
- * cases are done through the compiler, the screwed things has to be
- * done by hand.
- */
-
-static s32 snto32(__u32 value, unsigned n)
-{
- if (!value || !n)
- return 0;
-
- if (n > 32)
- n = 32;
-
- switch (n) {
- case 8: return ((__s8)value);
- case 16: return ((__s16)value);
- case 32: return ((__s32)value);
- }
- return value & (1 << (n - 1)) ? value | (~0U << n) : value;
-}
-
-s32 hid_snto32(__u32 value, unsigned n)
-{
- return snto32(value, n);
-}
-EXPORT_SYMBOL_GPL(hid_snto32);
-
-/*
- * Convert a signed 32-bit integer to a signed n-bit integer.
- */
-
-static u32 s32ton(__s32 value, unsigned n)
-{
- s32 a = value >> (n - 1);
- if (a && a != -1)
- return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
- return value & ((1 << n) - 1);
-}
-
-/*
* Extract/implement a data field from/to a little endian report (bit array).
*
* Code sort-of follows HID spec:
@@ -1441,7 +1473,6 @@ static void implement(const struct hid_device *hid, u8 *report,
hid_warn(hid,
"%s() called with too large value %d (n: %d)! (%s)\n",
__func__, value, n, current->comm);
- WARN_ON(1);
value &= m;
}
}
@@ -1865,11 +1896,14 @@ 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 kmalloc(len, flags);
+ return kzalloc(len, flags);
}
EXPORT_SYMBOL_GPL(hid_alloc_report_buf);
@@ -1906,6 +1940,31 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
}
EXPORT_SYMBOL_GPL(hid_set_field);
+struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_type,
+ unsigned int application, unsigned int usage)
+{
+ struct list_head *report_list = &hdev->report_enum[report_type].report_list;
+ struct hid_report *report;
+ int i, j;
+
+ list_for_each_entry(report, report_list, list) {
+ if (report->application != application)
+ continue;
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ for (j = 0; j < field->maxusage; j++) {
+ if (field->usage[j].hid == usage)
+ return field;
+ }
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(hid_find_field);
+
static struct hid_report *hid_get_report(struct hid_report_enum *report_enum,
const u8 *data)
{
@@ -1930,7 +1989,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;
@@ -1938,13 +1997,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;
@@ -2019,19 +2084,10 @@ out:
}
EXPORT_SYMBOL_GPL(hid_report_raw_event);
-/**
- * hid_input_report - report data from lower layer (usb, bt...)
- *
- * @hid: hid device
- * @type: HID report type (HID_*_REPORT)
- * @data: report contents
- * @size: size of data parameter
- * @interrupt: distinguish between interrupt and control transfers
- *
- * This is data entry for lower layers.
- */
-int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
- int interrupt)
+
+static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
+ u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
+ bool lock_already_taken)
{
struct hid_report_enum *report_enum;
struct hid_driver *hdrv;
@@ -2041,8 +2097,13 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
if (!hid)
return -ENODEV;
- if (down_trylock(&hid->driver_input_lock))
+ ret = down_trylock(&hid->driver_input_lock);
+ if (lock_already_taken && !ret) {
+ up(&hid->driver_input_lock);
+ return -EINVAL;
+ } else if (!lock_already_taken && ret) {
return -EBUSY;
+ }
if (!hid->driver) {
ret = -ENODEV;
@@ -2051,7 +2112,7 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
report_enum = hid->report_enum + type;
hdrv = hid->driver;
- data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt);
+ data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto unlock;
@@ -2083,9 +2144,29 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
ret = hid_report_raw_event(hid, type, data, size, interrupt);
unlock:
- up(&hid->driver_input_lock);
+ if (!lock_already_taken)
+ up(&hid->driver_input_lock);
return ret;
}
+
+/**
+ * hid_input_report - report data from lower layer (usb, bt...)
+ *
+ * @hid: hid device
+ * @type: HID report type (HID_*_REPORT)
+ * @data: report contents
+ * @size: size of data parameter
+ * @interrupt: distinguish between interrupt and control transfers
+ *
+ * This is data entry for lower layers.
+ */
+int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
+ int interrupt)
+{
+ return __hid_input_report(hid, type, data, size, interrupt, 0,
+ false, /* from_bpf */
+ false /* lock_already_taken */);
+}
EXPORT_SYMBOL_GPL(hid_input_report);
bool hid_match_one_id(const struct hid_device *hdev,
@@ -2121,9 +2202,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);
@@ -2140,24 +2221,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)
{
@@ -2242,6 +2316,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
case BUS_I2C:
bus = "I2C";
break;
+ case BUS_SDW:
+ bus = "SOUNDWIRE";
+ break;
case BUS_VIRTUAL:
bus = "VIRTUAL";
break;
@@ -2344,6 +2421,9 @@ int hid_hw_open(struct hid_device *hdev)
ret = hdev->ll_driver->open(hdev);
if (ret)
hdev->ll_open_count--;
+
+ if (hdev->driver->on_hid_hw_open)
+ hdev->driver->on_hid_hw_open(hdev);
}
mutex_unlock(&hdev->ll_open_lock);
@@ -2363,8 +2443,12 @@ EXPORT_SYMBOL_GPL(hid_hw_open);
void hid_hw_close(struct hid_device *hdev)
{
mutex_lock(&hdev->ll_open_lock);
- if (!--hdev->ll_open_count)
+ if (!--hdev->ll_open_count) {
hdev->ll_driver->close(hdev);
+
+ if (hdev->driver->on_hid_hw_close)
+ hdev->driver->on_hid_hw_close(hdev);
+ }
mutex_unlock(&hdev->ll_open_lock);
}
EXPORT_SYMBOL_GPL(hid_hw_close);
@@ -2386,6 +2470,30 @@ void hid_hw_request(struct hid_device *hdev,
}
EXPORT_SYMBOL_GPL(hid_hw_request);
+int __hid_hw_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, __u8 *buf,
+ size_t len, enum hid_report_type rtype,
+ enum hid_class_request reqtype,
+ u64 source, bool from_bpf)
+{
+ unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
+ int ret;
+
+ if (hdev->ll_driver->max_buffer_size)
+ max_buffer_size = hdev->ll_driver->max_buffer_size;
+
+ if (len < 1 || len > max_buffer_size || !buf)
+ return -EINVAL;
+
+ ret = dispatch_hid_bpf_raw_requests(hdev, reportnum, buf, len, rtype,
+ reqtype, source, from_bpf);
+ if (ret)
+ return ret;
+
+ return hdev->ll_driver->raw_request(hdev, reportnum, buf, len,
+ rtype, reqtype);
+}
+
/**
* hid_hw_raw_request - send report request to device
*
@@ -2404,7 +2512,15 @@ int hid_hw_raw_request(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype, enum hid_class_request reqtype)
{
+ return __hid_hw_raw_request(hdev, reportnum, buf, len, rtype, reqtype, 0, false);
+}
+EXPORT_SYMBOL_GPL(hid_hw_raw_request);
+
+int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, u64 source,
+ bool from_bpf)
+{
unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
+ int ret;
if (hdev->ll_driver->max_buffer_size)
max_buffer_size = hdev->ll_driver->max_buffer_size;
@@ -2412,10 +2528,15 @@ int hid_hw_raw_request(struct hid_device *hdev,
if (len < 1 || len > max_buffer_size || !buf)
return -EINVAL;
- return hdev->ll_driver->raw_request(hdev, reportnum, buf, len,
- rtype, reqtype);
+ ret = dispatch_hid_bpf_output_report(hdev, buf, len, source, from_bpf);
+ if (ret)
+ return ret;
+
+ if (hdev->ll_driver->output_report)
+ return hdev->ll_driver->output_report(hdev, buf, len);
+
+ return -ENOSYS;
}
-EXPORT_SYMBOL_GPL(hid_hw_raw_request);
/**
* hid_hw_output_report - send output report to device
@@ -2428,18 +2549,7 @@ EXPORT_SYMBOL_GPL(hid_hw_raw_request);
*/
int hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len)
{
- unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
-
- if (hdev->ll_driver->max_buffer_size)
- max_buffer_size = hdev->ll_driver->max_buffer_size;
-
- if (len < 1 || len > max_buffer_size || !buf)
- return -EINVAL;
-
- if (hdev->ll_driver->output_report)
- return hdev->ll_driver->output_report(hdev, buf, len);
-
- return -ENOSYS;
+ return __hid_hw_output_report(hdev, buf, len, 0, false);
}
EXPORT_SYMBOL_GPL(hid_hw_output_report);
@@ -2556,7 +2666,7 @@ const struct hid_device_id *hid_match_device(struct hid_device *hdev,
}
EXPORT_SYMBOL_GPL(hid_match_device);
-static int hid_bus_match(struct device *dev, struct device_driver *drv)
+static int hid_bus_match(struct device *dev, const struct device_driver *drv)
{
struct hid_driver *hdrv = to_hid_driver(drv);
struct hid_device *hdev = to_hid_device(dev);
@@ -2601,9 +2711,24 @@ static bool hid_check_device_match(struct hid_device *hdev,
/*
* hid-generic implements .match(), so we must be dealing with a
* different HID driver here, and can simply check if
- * hid_ignore_special_drivers is set or not.
+ * hid_ignore_special_drivers or HID_QUIRK_IGNORE_SPECIAL_DRIVER
+ * are set or not.
*/
- return !hid_ignore_special_drivers;
+ return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
+}
+
+static void hid_set_group(struct hid_device *hdev)
+{
+ int ret;
+
+ if (hid_ignore_special_drivers) {
+ hdev->group = HID_GROUP_GENERIC;
+ } else if (!hdev->group &&
+ !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
+ ret = hid_scan_report(hdev);
+ if (ret)
+ hid_warn(hdev, "bad device descriptor (%d)\n", ret);
+ }
}
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
@@ -2611,6 +2736,30 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
const struct hid_device_id *id;
int ret;
+ if (!hdev->bpf_rsize) {
+ /* we keep a reference to the currently scanned report descriptor */
+ const __u8 *original_rdesc = hdev->bpf_rdesc;
+
+ if (!original_rdesc)
+ original_rdesc = hdev->dev_rdesc;
+
+ /* in case a bpf program gets detached, we need to free the old one */
+ hid_free_bpf_rdesc(hdev);
+
+ /* keep this around so we know we called it once */
+ hdev->bpf_rsize = hdev->dev_rsize;
+
+ /* call_hid_bpf_rdesc_fixup will always return a valid pointer */
+ hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
+ &hdev->bpf_rsize);
+
+ /* the report descriptor changed, we need to re-scan it */
+ if (original_rdesc != hdev->bpf_rdesc) {
+ hdev->group = 0;
+ hid_set_group(hdev);
+ }
+ }
+
if (!hid_check_device_match(hdev, hdrv, &id))
return -ENODEV;
@@ -2699,7 +2848,7 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
{
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
- return scnprintf(buf, PAGE_SIZE, "hid:b%04Xg%04Xv%08Xp%08X\n",
+ return sysfs_emit(buf, "hid:b%04Xg%04Xv%08Xp%08X\n",
hdev->bus, hdev->group, hdev->vendor, hdev->product);
}
static DEVICE_ATTR_RO(modalias);
@@ -2708,8 +2857,8 @@ 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 = {
@@ -2742,7 +2891,7 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
return 0;
}
-struct bus_type hid_bus_type = {
+const struct bus_type hid_bus_type = {
.name = "hid",
.dev_groups = hid_dev_groups,
.drv_groups = hid_drv_groups,
@@ -2789,14 +2938,7 @@ int hid_add_device(struct hid_device *hdev)
/*
* Scan generic devices for group information
*/
- if (hid_ignore_special_drivers) {
- hdev->group = HID_GROUP_GENERIC;
- } else if (!hdev->group &&
- !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
- ret = hid_scan_report(hdev);
- if (ret)
- hid_warn(hdev, "bad device descriptor (%d)\n", ret);
- }
+ hid_set_group(hdev);
hdev->id = atomic_inc_return(&id);
@@ -2846,10 +2988,17 @@ struct hid_device *hid_allocate_device(void)
spin_lock_init(&hdev->debug_list_lock);
sema_init(&hdev->driver_input_lock, 1);
mutex_init(&hdev->ll_open_lock);
+ kref_init(&hdev->ref);
- hid_bpf_device_init(hdev);
+ ret = hid_bpf_device_init(hdev);
+ if (ret)
+ goto out_err;
return hdev;
+
+out_err:
+ hid_destroy_device(hdev);
+ return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(hid_allocate_device);
@@ -2860,9 +3009,11 @@ static void hid_remove_device(struct hid_device *hdev)
hid_debug_unregister(hdev);
hdev->status &= ~HID_STAT_ADDED;
}
+ hid_free_bpf_rdesc(hdev);
kfree(hdev->dev_rdesc);
hdev->dev_rdesc = NULL;
hdev->dev_rsize = 0;
+ hdev->bpf_rsize = 0;
}
/**
@@ -2963,9 +3114,11 @@ int hid_check_keys_pressed(struct hid_device *hid)
EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
#ifdef CONFIG_HID_BPF
-static struct hid_bpf_ops hid_ops = {
+static const struct hid_ops __hid_ops = {
.hid_get_report = hid_get_report,
- .hid_hw_raw_request = hid_hw_raw_request,
+ .hid_hw_raw_request = __hid_hw_raw_request,
+ .hid_hw_output_report = __hid_hw_output_report,
+ .hid_input_report = __hid_input_report,
.owner = THIS_MODULE,
.bus_type = &hid_bus_type,
};
@@ -2982,7 +3135,7 @@ static int __init hid_init(void)
}
#ifdef CONFIG_HID_BPF
- hid_bpf_ops = &hid_ops;
+ hid_ops = &__hid_ops;
#endif
ret = hidraw_init();
@@ -3001,7 +3154,7 @@ err:
static void __exit hid_exit(void)
{
#ifdef CONFIG_HID_BPF
- hid_bpf_ops = NULL;
+ hid_ops = NULL;
#endif
hid_debug_exit();
hidraw_exit();
@@ -3015,4 +3168,5 @@ module_exit(hid_exit);
MODULE_AUTHOR("Andreas Gal");
MODULE_AUTHOR("Vojtech Pavlik");
MODULE_AUTHOR("Jiri Kosina");
+MODULE_DESCRIPTION("HID support for Linux");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c
new file mode 100644
index 000000000000..5e9a5b8f7f16
--- /dev/null
+++ b/drivers/hid/hid-corsair-void.c
@@ -0,0 +1,832 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for Corsair Void headsets
+ *
+ * Copyright (C) 2023-2024 Stuart Hayhurst
+ */
+
+/* -------------------------------------------------------------------------- */
+/* Receiver report information: (ID 100) */
+/* -------------------------------------------------------------------------- */
+/*
+ * When queried, the receiver reponds with 5 bytes to describe the battery
+ * The power button, mute button and moving the mic also trigger this report
+ * This includes power button + mic + connection + battery status and capacity
+ * The information below may not be perfect, it's been gathered through guesses
+ *
+ * 0: REPORT ID
+ * 100 for the battery packet
+ *
+ * 1: POWER BUTTON + (?)
+ * Largest bit is 1 when power button pressed
+ *
+ * 2: BATTERY CAPACITY + MIC STATUS
+ * Battery capacity:
+ * Seems to report ~54 higher than reality when charging
+ * Capped at 100, charging or not
+ * Microphone status:
+ * Largest bit is set to 1 when the mic is physically up
+ * No bits change when the mic is muted, only when physically moved
+ * This report is sent every time the mic is moved, no polling required
+ *
+ * 3: CONNECTION STATUS
+ * 16: Wired headset
+ * 38: Initialising
+ * 49: Lost connection
+ * 51: Disconnected, searching
+ * 52: Disconnected, not searching
+ * 177: Normal
+ *
+ * 4: BATTERY STATUS
+ * 0: Disconnected
+ * 1: Normal
+ * 2: Low
+ * 3: Critical - sent during shutdown
+ * 4: Fully charged
+ * 5: Charging
+ */
+/* -------------------------------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* Receiver report information: (ID 102) */
+/* -------------------------------------------------------------------------- */
+/*
+ * When queried, the recevier responds with 4 bytes to describe the firmware
+ * The first 2 bytes are for the receiver, the second 2 are the headset
+ * The headset firmware version will be 0 if no headset is connected
+ *
+ * 0: Recevier firmware major version
+ * Major version of the receiver's firmware
+ *
+ * 1: Recevier firmware minor version
+ * Minor version of the receiver's firmware
+ *
+ * 2: Headset firmware major version
+ * Major version of the headset's firmware
+ *
+ * 3: Headset firmware minor version
+ * Minor version of the headset's firmware
+ */
+/* -------------------------------------------------------------------------- */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <asm/byteorder.h>
+
+#include "hid-ids.h"
+
+#define CORSAIR_VOID_DEVICE(id, type) { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, (id)), \
+ .driver_data = (type) }
+#define CORSAIR_VOID_WIRELESS_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRELESS)
+#define CORSAIR_VOID_WIRED_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRED)
+
+#define CORSAIR_VOID_STATUS_REQUEST_ID 0xC9
+#define CORSAIR_VOID_NOTIF_REQUEST_ID 0xCA
+#define CORSAIR_VOID_SIDETONE_REQUEST_ID 0xFF
+#define CORSAIR_VOID_STATUS_REPORT_ID 0x64
+#define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66
+
+#define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1
+#define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21
+#define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200
+#define CORSAIR_VOID_USB_SIDETONE_INDEX 0xB00
+
+#define CORSAIR_VOID_MIC_MASK GENMASK(7, 7)
+#define CORSAIR_VOID_CAPACITY_MASK GENMASK(6, 0)
+
+#define CORSAIR_VOID_WIRELESS_CONNECTED 177
+
+#define CORSAIR_VOID_SIDETONE_MAX_WIRELESS 55
+#define CORSAIR_VOID_SIDETONE_MAX_WIRED 4096
+
+enum {
+ CORSAIR_VOID_WIRELESS,
+ CORSAIR_VOID_WIRED,
+};
+
+enum {
+ CORSAIR_VOID_BATTERY_NORMAL = 1,
+ CORSAIR_VOID_BATTERY_LOW = 2,
+ CORSAIR_VOID_BATTERY_CRITICAL = 3,
+ CORSAIR_VOID_BATTERY_CHARGED = 4,
+ CORSAIR_VOID_BATTERY_CHARGING = 5,
+};
+
+enum {
+ CORSAIR_VOID_ADD_BATTERY = 0,
+ CORSAIR_VOID_REMOVE_BATTERY = 1,
+ CORSAIR_VOID_UPDATE_BATTERY = 2,
+};
+
+static enum power_supply_property corsair_void_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+struct corsair_void_battery_data {
+ int status;
+ bool present;
+ int capacity;
+ int capacity_level;
+};
+
+struct corsair_void_drvdata {
+ struct hid_device *hid_dev;
+ struct device *dev;
+
+ char *name;
+ bool is_wired;
+ unsigned int sidetone_max;
+
+ struct corsair_void_battery_data battery_data;
+ bool mic_up;
+ bool connected;
+ int fw_receiver_major;
+ int fw_receiver_minor;
+ int fw_headset_major;
+ int fw_headset_minor;
+
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+
+ struct delayed_work delayed_status_work;
+ struct delayed_work delayed_firmware_work;
+
+ unsigned long battery_work_flags;
+ struct work_struct battery_work;
+};
+
+/*
+ * Functions to process receiver data
+*/
+
+static void corsair_void_set_wireless_status(struct corsair_void_drvdata *drvdata)
+{
+ struct usb_interface *usb_if = to_usb_interface(drvdata->dev->parent);
+
+ if (drvdata->is_wired)
+ return;
+
+ usb_set_wireless_status(usb_if, drvdata->connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
+static void corsair_void_set_unknown_batt(struct corsair_void_drvdata *drvdata)
+{
+ struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
+
+ battery_data->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ battery_data->present = false;
+ battery_data->capacity = 0;
+ battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+}
+
+/* Reset data that may change between wireless connections */
+static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata *drvdata)
+{
+ /* Only 0 out headset, receiver is always known if relevant */
+ drvdata->fw_headset_major = 0;
+ drvdata->fw_headset_minor = 0;
+
+ drvdata->connected = false;
+ drvdata->mic_up = false;
+
+ corsair_void_set_wireless_status(drvdata);
+}
+
+static void corsair_void_process_receiver(struct corsair_void_drvdata *drvdata,
+ int raw_battery_capacity,
+ int raw_connection_status,
+ int raw_battery_status)
+{
+ struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
+ struct corsair_void_battery_data orig_battery_data;
+
+ /* Save initial battery data, to compare later */
+ orig_battery_data = *battery_data;
+
+ /* Headset not connected, or it's wired */
+ if (raw_connection_status != CORSAIR_VOID_WIRELESS_CONNECTED)
+ goto unknown_battery;
+
+ /* Battery information unavailable */
+ if (raw_battery_status == 0)
+ goto unknown_battery;
+
+ /* Battery must be connected then */
+ battery_data->present = true;
+ battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+
+ /* Set battery status */
+ switch (raw_battery_status) {
+ case CORSAIR_VOID_BATTERY_NORMAL:
+ case CORSAIR_VOID_BATTERY_LOW:
+ case CORSAIR_VOID_BATTERY_CRITICAL:
+ battery_data->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ if (raw_battery_status == CORSAIR_VOID_BATTERY_LOW)
+ battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (raw_battery_status == CORSAIR_VOID_BATTERY_CRITICAL)
+ battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+ break;
+ case CORSAIR_VOID_BATTERY_CHARGED:
+ battery_data->status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case CORSAIR_VOID_BATTERY_CHARGING:
+ battery_data->status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ default:
+ hid_warn(drvdata->hid_dev, "unknown battery status '%d'",
+ raw_battery_status);
+ goto unknown_battery;
+ break;
+ }
+
+ battery_data->capacity = raw_battery_capacity;
+ corsair_void_set_wireless_status(drvdata);
+
+ goto success;
+unknown_battery:
+ corsair_void_set_unknown_batt(drvdata);
+success:
+
+ /* Inform power supply if battery values changed */
+ if (memcmp(&orig_battery_data, battery_data, sizeof(*battery_data))) {
+ set_bit(CORSAIR_VOID_UPDATE_BATTERY,
+ &drvdata->battery_work_flags);
+ schedule_work(&drvdata->battery_work);
+ }
+}
+
+/*
+ * Functions to report stored data
+*/
+
+static int corsair_void_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct corsair_void_drvdata *drvdata = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ if (!strncmp(drvdata->hid_dev->name, "Corsair ", 8))
+ val->strval = drvdata->hid_dev->name + 8;
+ else
+ val->strval = drvdata->hid_dev->name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Corsair";
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = drvdata->battery_data.status;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = drvdata->battery_data.present;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = drvdata->battery_data.capacity;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = drvdata->battery_data.capacity_level;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t microphone_up_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (!drvdata->connected)
+ return -ENODEV;
+
+ return sysfs_emit(buf, "%d\n", drvdata->mic_up);
+}
+
+static ssize_t fw_version_receiver_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (drvdata->fw_receiver_major == 0 && drvdata->fw_receiver_minor == 0)
+ return -ENODATA;
+
+ return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_receiver_major,
+ drvdata->fw_receiver_minor);
+}
+
+
+static ssize_t fw_version_headset_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (drvdata->fw_headset_major == 0 && drvdata->fw_headset_minor == 0)
+ return -ENODATA;
+
+ return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_headset_major,
+ drvdata->fw_headset_minor);
+}
+
+static ssize_t sidetone_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", drvdata->sidetone_max);
+}
+
+/*
+ * Functions to send data to headset
+*/
+
+static ssize_t send_alert_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+ struct hid_device *hid_dev = drvdata->hid_dev;
+ unsigned char alert_id;
+ unsigned char *send_buf __free(kfree) = NULL;
+ int ret;
+
+ if (!drvdata->connected || drvdata->is_wired)
+ return -ENODEV;
+
+ /* Only accept 0 or 1 for alert ID */
+ if (kstrtou8(buf, 10, &alert_id) || alert_id >= 2)
+ return -EINVAL;
+
+ send_buf = kmalloc(3, GFP_KERNEL);
+ if (!send_buf)
+ return -ENOMEM;
+
+ /* Packet format to send alert with ID alert_id */
+ send_buf[0] = CORSAIR_VOID_NOTIF_REQUEST_ID;
+ send_buf[1] = 0x02;
+ send_buf[2] = alert_id;
+
+ ret = hid_hw_raw_request(hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID,
+ send_buf, 3, HID_OUTPUT_REPORT,
+ HID_REQ_SET_REPORT);
+ if (ret < 0)
+ hid_warn(hid_dev, "failed to send alert request (reason: %d)",
+ ret);
+ else
+ ret = count;
+
+ return ret;
+}
+
+static int corsair_void_set_sidetone_wired(struct device *dev, const char *buf,
+ unsigned int sidetone)
+{
+ struct usb_interface *usb_if = to_usb_interface(dev->parent);
+ struct usb_device *usb_dev = interface_to_usbdev(usb_if);
+
+ /* Packet format to set sidetone for wired headsets */
+ __le16 sidetone_le = cpu_to_le16(sidetone);
+
+ return usb_control_msg_send(usb_dev, 0,
+ CORSAIR_VOID_USB_SIDETONE_REQUEST,
+ CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE,
+ CORSAIR_VOID_USB_SIDETONE_VALUE,
+ CORSAIR_VOID_USB_SIDETONE_INDEX,
+ &sidetone_le, 2, USB_CTRL_SET_TIMEOUT,
+ GFP_KERNEL);
+}
+
+static int corsair_void_set_sidetone_wireless(struct device *dev,
+ const char *buf,
+ unsigned char sidetone)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+ struct hid_device *hid_dev = drvdata->hid_dev;
+ unsigned char *send_buf __free(kfree) = NULL;
+
+ send_buf = kmalloc(12, GFP_KERNEL);
+ if (!send_buf)
+ return -ENOMEM;
+
+ /* Packet format to set sidetone for wireless headsets */
+ send_buf[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID;
+ send_buf[1] = 0x0B;
+ send_buf[2] = 0x00;
+ send_buf[3] = 0xFF;
+ send_buf[4] = 0x04;
+ send_buf[5] = 0x0E;
+ send_buf[6] = 0xFF;
+ send_buf[7] = 0x05;
+ send_buf[8] = 0x01;
+ send_buf[9] = 0x04;
+ send_buf[10] = 0x00;
+ send_buf[11] = sidetone + 200;
+
+ return hid_hw_raw_request(hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID,
+ send_buf, 12, HID_FEATURE_REPORT,
+ HID_REQ_SET_REPORT);
+}
+
+static ssize_t set_sidetone_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
+ struct hid_device *hid_dev = drvdata->hid_dev;
+ unsigned int sidetone;
+ int ret;
+
+ if (!drvdata->connected)
+ return -ENODEV;
+
+ /* sidetone must be between 0 and drvdata->sidetone_max inclusive */
+ if (kstrtouint(buf, 10, &sidetone) || sidetone > drvdata->sidetone_max)
+ return -EINVAL;
+
+ if (drvdata->is_wired)
+ ret = corsair_void_set_sidetone_wired(dev, buf, sidetone);
+ else
+ ret = corsair_void_set_sidetone_wireless(dev, buf, sidetone);
+
+ if (ret < 0)
+ hid_warn(hid_dev, "failed to send sidetone (reason: %d)", ret);
+ else
+ ret = count;
+
+ return ret;
+}
+
+static int corsair_void_request_status(struct hid_device *hid_dev, int id)
+{
+ unsigned char *send_buf __free(kfree) = NULL;
+
+ send_buf = kmalloc(2, GFP_KERNEL);
+ if (!send_buf)
+ return -ENOMEM;
+
+ /* Packet format to request data item (status / firmware) refresh */
+ send_buf[0] = CORSAIR_VOID_STATUS_REQUEST_ID;
+ send_buf[1] = id;
+
+ /* Send request for data refresh */
+ return hid_hw_raw_request(hid_dev, CORSAIR_VOID_STATUS_REQUEST_ID,
+ send_buf, 2, HID_OUTPUT_REPORT,
+ HID_REQ_SET_REPORT);
+}
+
+/*
+ * Headset connect / disconnect handlers and work handlers
+*/
+
+static void corsair_void_status_work_handler(struct work_struct *work)
+{
+ struct corsair_void_drvdata *drvdata;
+ struct delayed_work *delayed_work;
+ int battery_ret;
+
+ delayed_work = to_delayed_work(work);
+ drvdata = container_of(delayed_work, struct corsair_void_drvdata,
+ delayed_status_work);
+
+ battery_ret = corsair_void_request_status(drvdata->hid_dev,
+ CORSAIR_VOID_STATUS_REPORT_ID);
+ if (battery_ret < 0) {
+ hid_warn(drvdata->hid_dev,
+ "failed to request battery (reason: %d)", battery_ret);
+ }
+}
+
+static void corsair_void_firmware_work_handler(struct work_struct *work)
+{
+ struct corsair_void_drvdata *drvdata;
+ struct delayed_work *delayed_work;
+ int firmware_ret;
+
+ delayed_work = to_delayed_work(work);
+ drvdata = container_of(delayed_work, struct corsair_void_drvdata,
+ delayed_firmware_work);
+
+ firmware_ret = corsair_void_request_status(drvdata->hid_dev,
+ CORSAIR_VOID_FIRMWARE_REPORT_ID);
+ if (firmware_ret < 0) {
+ hid_warn(drvdata->hid_dev,
+ "failed to request firmware (reason: %d)", firmware_ret);
+ }
+
+}
+
+static void corsair_void_add_battery(struct corsair_void_drvdata *drvdata)
+{
+ struct power_supply_config psy_cfg = {};
+ struct power_supply *new_supply;
+
+ if (drvdata->battery)
+ return;
+
+ psy_cfg.drv_data = drvdata;
+ new_supply = power_supply_register(drvdata->dev,
+ &drvdata->battery_desc,
+ &psy_cfg);
+
+ if (IS_ERR(new_supply)) {
+ hid_err(drvdata->hid_dev,
+ "failed to register battery '%s' (reason: %pe)\n",
+ drvdata->battery_desc.name, new_supply);
+ return;
+ }
+
+ if (power_supply_powers(new_supply, drvdata->dev)) {
+ power_supply_unregister(new_supply);
+ return;
+ }
+
+ drvdata->battery = new_supply;
+}
+
+static void corsair_void_battery_work_handler(struct work_struct *work)
+{
+ struct corsair_void_drvdata *drvdata = container_of(work,
+ struct corsair_void_drvdata, battery_work);
+
+ bool add_battery = test_and_clear_bit(CORSAIR_VOID_ADD_BATTERY,
+ &drvdata->battery_work_flags);
+ bool remove_battery = test_and_clear_bit(CORSAIR_VOID_REMOVE_BATTERY,
+ &drvdata->battery_work_flags);
+ bool update_battery = test_and_clear_bit(CORSAIR_VOID_UPDATE_BATTERY,
+ &drvdata->battery_work_flags);
+
+ if (add_battery && !remove_battery) {
+ corsair_void_add_battery(drvdata);
+ } else if (remove_battery && !add_battery && drvdata->battery) {
+ power_supply_unregister(drvdata->battery);
+ drvdata->battery = NULL;
+ }
+
+ if (update_battery && drvdata->battery)
+ power_supply_changed(drvdata->battery);
+
+}
+
+static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata)
+{
+ set_bit(CORSAIR_VOID_ADD_BATTERY, &drvdata->battery_work_flags);
+ schedule_work(&drvdata->battery_work);
+ schedule_delayed_work(&drvdata->delayed_firmware_work,
+ msecs_to_jiffies(100));
+}
+
+static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata)
+{
+ set_bit(CORSAIR_VOID_REMOVE_BATTERY, &drvdata->battery_work_flags);
+ schedule_work(&drvdata->battery_work);
+
+ corsair_void_set_unknown_wireless_data(drvdata);
+ corsair_void_set_unknown_batt(drvdata);
+}
+
+/*
+ * Driver setup, probing and HID event handling
+*/
+
+static DEVICE_ATTR_RO(fw_version_receiver);
+static DEVICE_ATTR_RO(fw_version_headset);
+static DEVICE_ATTR_RO(microphone_up);
+static DEVICE_ATTR_RO(sidetone_max);
+
+static DEVICE_ATTR_WO(send_alert);
+static DEVICE_ATTR_WO(set_sidetone);
+
+static struct attribute *corsair_void_attrs[] = {
+ &dev_attr_fw_version_receiver.attr,
+ &dev_attr_fw_version_headset.attr,
+ &dev_attr_microphone_up.attr,
+ &dev_attr_send_alert.attr,
+ &dev_attr_set_sidetone.attr,
+ &dev_attr_sidetone_max.attr,
+ NULL,
+};
+
+static const struct attribute_group corsair_void_attr_group = {
+ .attrs = corsair_void_attrs,
+};
+
+static int corsair_void_probe(struct hid_device *hid_dev,
+ const struct hid_device_id *hid_id)
+{
+ int ret;
+ struct corsair_void_drvdata *drvdata;
+ char *name;
+
+ if (!hid_is_usb(hid_dev))
+ return -EINVAL;
+
+ drvdata = devm_kzalloc(&hid_dev->dev, sizeof(*drvdata),
+ GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ hid_set_drvdata(hid_dev, drvdata);
+ dev_set_drvdata(&hid_dev->dev, drvdata);
+
+ drvdata->dev = &hid_dev->dev;
+ drvdata->hid_dev = hid_dev;
+ drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED;
+
+ drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS;
+ if (drvdata->is_wired)
+ drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED;
+
+ /* Set initial values for no wireless headset attached */
+ /* If a headset is attached, it'll be prompted later */
+ corsair_void_set_unknown_wireless_data(drvdata);
+ corsair_void_set_unknown_batt(drvdata);
+
+ /* Receiver version won't be reset after init */
+ /* Headset version already set via set_unknown_wireless_data */
+ drvdata->fw_receiver_major = 0;
+ drvdata->fw_receiver_minor = 0;
+
+ ret = hid_parse(hid_dev);
+ if (ret) {
+ hid_err(hid_dev, "parse failed (reason: %d)\n", ret);
+ return ret;
+ }
+
+ name = devm_kasprintf(drvdata->dev, GFP_KERNEL,
+ "corsair-void-%d-battery", hid_dev->id);
+ if (!name)
+ return -ENOMEM;
+
+ drvdata->battery_desc.name = name;
+ drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drvdata->battery_desc.properties = corsair_void_battery_props;
+ drvdata->battery_desc.num_properties = ARRAY_SIZE(corsair_void_battery_props);
+ drvdata->battery_desc.get_property = corsair_void_battery_get_property;
+
+ drvdata->battery = NULL;
+ INIT_WORK(&drvdata->battery_work, corsair_void_battery_work_handler);
+
+ ret = sysfs_create_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
+ if (ret)
+ return ret;
+
+ /* Any failures after here will need to call hid_hw_stop */
+ ret = hid_hw_start(hid_dev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret);
+ goto failed_after_sysfs;
+ }
+
+ /* Refresh battery data, in case wireless headset is already connected */
+ INIT_DELAYED_WORK(&drvdata->delayed_status_work,
+ corsair_void_status_work_handler);
+ schedule_delayed_work(&drvdata->delayed_status_work,
+ msecs_to_jiffies(100));
+
+ /* Refresh firmware versions */
+ INIT_DELAYED_WORK(&drvdata->delayed_firmware_work,
+ corsair_void_firmware_work_handler);
+ schedule_delayed_work(&drvdata->delayed_firmware_work,
+ msecs_to_jiffies(100));
+
+ return 0;
+
+failed_after_sysfs:
+ sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
+ return ret;
+}
+
+static void corsair_void_remove(struct hid_device *hid_dev)
+{
+ struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
+
+ hid_hw_stop(hid_dev);
+ cancel_work_sync(&drvdata->battery_work);
+ if (drvdata->battery)
+ power_supply_unregister(drvdata->battery);
+
+ cancel_delayed_work_sync(&drvdata->delayed_status_work);
+ cancel_delayed_work_sync(&drvdata->delayed_firmware_work);
+ sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
+}
+
+static int corsair_void_raw_event(struct hid_device *hid_dev,
+ struct hid_report *hid_report,
+ u8 *data, int size)
+{
+ struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
+ bool was_connected = drvdata->connected;
+
+ /* Description of packets are documented at the top of this file */
+ if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
+ drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
+ drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
+ drvdata->is_wired;
+
+ corsair_void_process_receiver(drvdata,
+ FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
+ data[3], data[4]);
+ } else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
+ drvdata->fw_receiver_major = data[1];
+ drvdata->fw_receiver_minor = data[2];
+ drvdata->fw_headset_major = data[3];
+ drvdata->fw_headset_minor = data[4];
+ }
+
+ /* Handle wireless headset connect / disconnect */
+ if ((was_connected != drvdata->connected) && !drvdata->is_wired) {
+ if (drvdata->connected)
+ corsair_void_headset_connected(drvdata);
+ else
+ corsair_void_headset_disconnected(drvdata);
+ }
+
+ return 0;
+}
+
+static const struct hid_device_id corsair_void_devices[] = {
+ /* Corsair Void Wireless */
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x1b23),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x1b25),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x1b27),
+
+ /* Corsair Void USB */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a0f),
+ CORSAIR_VOID_WIRED_DEVICE(0x1b1c),
+ CORSAIR_VOID_WIRED_DEVICE(0x1b29),
+ CORSAIR_VOID_WIRED_DEVICE(0x1b2a),
+
+ /* Corsair Void Surround */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a30),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a31),
+
+ /* Corsair Void Pro Wireless */
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a14),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a16),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a),
+
+ /* Corsair Void Pro USB */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a17),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a1d),
+
+ /* Corsair Void Pro Surround */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a18),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a1e),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a1f),
+
+ /* Corsair Void Elite Wireless */
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a51),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a55),
+ CORSAIR_VOID_WIRELESS_DEVICE(0x0a75),
+
+ /* Corsair Void Elite USB */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a52),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a56),
+
+ /* Corsair Void Elite Surround */
+ CORSAIR_VOID_WIRED_DEVICE(0x0a53),
+ CORSAIR_VOID_WIRED_DEVICE(0x0a57),
+
+ {}
+};
+
+MODULE_DEVICE_TABLE(hid, corsair_void_devices);
+
+static struct hid_driver corsair_void_driver = {
+ .name = "hid-corsair-void",
+ .id_table = corsair_void_devices,
+ .probe = corsair_void_probe,
+ .remove = corsair_void_remove,
+ .raw_event = corsair_void_raw_event,
+};
+
+module_hid_driver(corsair_void_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>");
+MODULE_DESCRIPTION("HID driver for Corsair Void headsets");
diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c
index 8c895c820b67..62b99f5c3cf8 100644
--- a/drivers/hid/hid-corsair.c
+++ b/drivers/hid/hid-corsair.c
@@ -298,7 +298,7 @@ static ssize_t k90_show_macro_mode(struct device *dev,
goto out;
}
- ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
+ ret = sysfs_emit(buf, "%s\n", macro_mode);
out:
kfree(data);
@@ -367,7 +367,7 @@ static ssize_t k90_show_current_profile(struct device *dev,
goto out;
}
- ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
+ ret = sysfs_emit(buf, "%d\n", current_profile);
out:
kfree(data);
@@ -690,8 +690,8 @@ static int corsair_input_mapping(struct hid_device *dev,
* - USB ID 1b1c:1b3e, sold as Scimitar RGB Pro Gaming mouse
*/
-static __u8 *corsair_mouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *corsair_mouse_report_fixup(struct hid_device *hdev,
+ __u8 *rdesc, unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c
index cb8bd8aae15b..5596dd940322 100644
--- a/drivers/hid/hid-cougar.c
+++ b/drivers/hid/hid-cougar.c
@@ -103,10 +103,10 @@ static void cougar_fix_g6_mapping(void)
/*
* Constant-friendly rdesc fixup for mouse interface
*/
-static __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
- if (rdesc[2] == 0x09 && rdesc[3] == 0x02 &&
+ if (*rsize >= 117 && rdesc[2] == 0x09 && rdesc[3] == 0x02 &&
(rdesc[115] | rdesc[116] << 8) >= HID_MAX_USAGES) {
hid_info(hdev,
"usage count exceeds max: fixing up report descriptor\n");
diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c
index 27cadadda7c9..803b883ae875 100644
--- a/drivers/hid/hid-cp2112.c
+++ b/drivers/hid/hid-cp2112.c
@@ -16,14 +16,16 @@
* https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf
*/
-#include <linux/gpio/consumer.h>
-#include <linux/gpio/machine.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/gpio/driver.h>
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/nls.h>
+#include <linux/string_choices.h>
#include <linux/usb/ch9.h>
#include "hid-ids.h"
@@ -31,6 +33,8 @@
#define CP2112_GPIO_CONFIG_LENGTH 5
#define CP2112_GPIO_GET_LENGTH 2
#define CP2112_GPIO_SET_LENGTH 3
+#define CP2112_GPIO_MAX_GPIO 8
+#define CP2112_GPIO_ALL_GPIO_MASK GENMASK(7, 0)
enum {
CP2112_GPIO_CONFIG = 0x02,
@@ -163,19 +167,17 @@ struct cp2112_device {
atomic_t read_avail;
atomic_t xfer_avail;
struct gpio_chip gc;
- struct irq_chip irq;
u8 *in_out_buffer;
struct mutex lock;
- struct gpio_desc *desc[8];
bool gpio_poll;
struct delayed_work gpio_poll_worker;
unsigned long irq_mask;
u8 gpio_prev_state;
};
-static int gpio_push_pull = 0xFF;
-module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR);
+static int gpio_push_pull = CP2112_GPIO_ALL_GPIO_MASK;
+module_param(gpio_push_pull, int, 0644);
MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask");
static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
@@ -185,7 +187,7 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
u8 *buf = dev->in_out_buffer;
int ret;
- mutex_lock(&dev->lock);
+ guard(mutex)(&dev->lock);
ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
@@ -194,10 +196,10 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
hid_err(hdev, "error requesting GPIO config: %d\n", ret);
if (ret >= 0)
ret = -EIO;
- goto exit;
+ return ret;
}
- buf[1] &= ~(1 << offset);
+ buf[1] &= ~BIT(offset);
buf[2] = gpio_push_pull;
ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
@@ -207,36 +209,42 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
hid_err(hdev, "error setting GPIO config: %d\n", ret);
if (ret >= 0)
ret = -EIO;
- goto exit;
+ return ret;
}
- ret = 0;
-
-exit:
- mutex_unlock(&dev->lock);
- return ret;
+ return 0;
}
-static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+static int cp2112_gpio_set_unlocked(struct cp2112_device *dev,
+ unsigned int offset, int value)
{
- struct cp2112_device *dev = gpiochip_get_data(chip);
struct hid_device *hdev = dev->hdev;
u8 *buf = dev->in_out_buffer;
int ret;
- mutex_lock(&dev->lock);
-
buf[0] = CP2112_GPIO_SET;
- buf[1] = value ? 0xff : 0;
- buf[2] = 1 << offset;
+ buf[1] = value ? CP2112_GPIO_ALL_GPIO_MASK : 0;
+ buf[2] = BIT(offset);
ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf,
CP2112_GPIO_SET_LENGTH, HID_FEATURE_REPORT,
HID_REQ_SET_REPORT);
- if (ret < 0)
+ if (ret != CP2112_GPIO_SET_LENGTH) {
hid_err(hdev, "error setting GPIO values: %d\n", ret);
+ return ret < 0 ? ret : -EIO;
+ }
- mutex_unlock(&dev->lock);
+ return 0;
+}
+
+static int cp2112_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct cp2112_device *dev = gpiochip_get_data(chip);
+
+ guard(mutex)(&dev->lock);
+
+ return cp2112_gpio_set_unlocked(dev, offset, value);
}
static int cp2112_gpio_get_all(struct gpio_chip *chip)
@@ -246,23 +254,17 @@ static int cp2112_gpio_get_all(struct gpio_chip *chip)
u8 *buf = dev->in_out_buffer;
int ret;
- mutex_lock(&dev->lock);
+ guard(mutex)(&dev->lock);
ret = hid_hw_raw_request(hdev, CP2112_GPIO_GET, buf,
CP2112_GPIO_GET_LENGTH, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret != CP2112_GPIO_GET_LENGTH) {
hid_err(hdev, "error requesting GPIO values: %d\n", ret);
- ret = ret < 0 ? ret : -EIO;
- goto exit;
+ return ret < 0 ? ret : -EIO;
}
- ret = buf[1];
-
-exit:
- mutex_unlock(&dev->lock);
-
- return ret;
+ return buf[1];
}
static int cp2112_gpio_get(struct gpio_chip *chip, unsigned int offset)
@@ -284,14 +286,14 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
u8 *buf = dev->in_out_buffer;
int ret;
- mutex_lock(&dev->lock);
+ guard(mutex)(&dev->lock);
ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret != CP2112_GPIO_CONFIG_LENGTH) {
hid_err(hdev, "error requesting GPIO config: %d\n", ret);
- goto fail;
+ return ret < 0 ? ret : -EIO;
}
buf[1] |= 1 << offset;
@@ -302,22 +304,14 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
HID_REQ_SET_REPORT);
if (ret < 0) {
hid_err(hdev, "error setting GPIO config: %d\n", ret);
- goto fail;
+ return ret;
}
- mutex_unlock(&dev->lock);
-
/*
* Set gpio value when output direction is already set,
* as specified in AN495, Rev. 0.2, cpt. 4.4
*/
- cp2112_gpio_set(chip, offset, value);
-
- return 0;
-
-fail:
- mutex_unlock(&dev->lock);
- return ret < 0 ? ret : -EIO;
+ return cp2112_gpio_set_unlocked(dev, offset, value);
}
static int cp2112_hid_get(struct hid_device *hdev, unsigned char report_number,
@@ -532,15 +526,13 @@ static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
hid_dbg(hdev, "I2C %d messages\n", num);
if (num == 1) {
+ hid_dbg(hdev, "I2C %s %#04x len %d\n",
+ str_read_write(msgs->flags & I2C_M_RD), msgs->addr, msgs->len);
if (msgs->flags & I2C_M_RD) {
- hid_dbg(hdev, "I2C read %#04x len %d\n",
- msgs->addr, msgs->len);
read_length = msgs->len;
read_buf = msgs->buf;
count = cp2112_read_req(buf, msgs->addr, msgs->len);
} else {
- hid_dbg(hdev, "I2C write %#04x len %d\n",
- msgs->addr, msgs->len);
count = cp2112_i2c_write_req(buf, msgs->addr,
msgs->buf, msgs->len);
}
@@ -648,7 +640,7 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
int ret;
hid_dbg(hdev, "%s addr 0x%x flags 0x%x cmd 0x%x size %d\n",
- read_write == I2C_SMBUS_WRITE ? "write" : "read",
+ str_write_read(read_write == I2C_SMBUS_WRITE),
addr, flags, command, size);
switch (size) {
@@ -697,7 +689,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
count = cp2112_write_read_req(buf, addr, read_length,
command, NULL, 0);
} else {
- count = cp2112_write_req(buf, addr, command,
+ /* Copy starts from data->block[1] so the length can
+ * be at max I2C_SMBUS_CLOCK_MAX + 1
+ */
+
+ if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
+ count = -EINVAL;
+ else
+ count = cp2112_write_req(buf, addr, command,
data->block + 1,
data->block[0]);
}
@@ -708,7 +707,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
I2C_SMBUS_BLOCK_MAX,
command, NULL, 0);
} else {
- count = cp2112_write_req(buf, addr, command,
+ /* data_length here is data->block[0] + 1
+ * so make sure that the data->block[0] is
+ * less than or equals I2C_SMBUS_BLOCK_MAX + 1
+ */
+ if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
+ count = -EINVAL;
+ else
+ count = cp2112_write_req(buf, addr, command,
data->block,
data->block[0] + 1);
}
@@ -717,7 +723,14 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
size = I2C_SMBUS_BLOCK_DATA;
read_write = I2C_SMBUS_READ;
- count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX,
+ /* data_length is data->block[0] + 1, so
+ * so data->block[0] should be less than or
+ * equal to the I2C_SMBUS_BLOCK_MAX + 1
+ */
+ if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
+ count = -EINVAL;
+ else
+ count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX,
command, data->block,
data->block[0] + 1);
break;
@@ -854,7 +867,8 @@ static int cp2112_set_usb_config(struct hid_device *hdev,
{
int ret;
- BUG_ON(cfg->report != CP2112_USB_CONFIG);
+ if (WARN_ON(cfg->report != CP2112_USB_CONFIG))
+ return -EINVAL;
ret = cp2112_hid_output(hdev, (u8 *)cfg, sizeof(*cfg),
HID_FEATURE_REPORT);
@@ -895,7 +909,7 @@ static ssize_t name##_show(struct device *kdev, \
int ret = cp2112_get_usb_config(hdev, &cfg); \
if (ret) \
return ret; \
- return scnprintf(buf, PAGE_SIZE, format, ##__VA_ARGS__); \
+ return sysfs_emit(buf, format, ##__VA_ARGS__); \
} \
static DEVICE_ATTR_RW(name);
@@ -946,18 +960,10 @@ CP2112_CONFIG_ATTR(release_version, ({
#undef CP2112_CONFIG_ATTR
-struct cp2112_pstring_attribute {
- struct device_attribute attr;
- unsigned char report;
-};
-
-static ssize_t pstr_store(struct device *kdev,
- struct device_attribute *kattr, const char *buf,
- size_t count)
+static ssize_t pstr_store(struct device *kdev, struct device_attribute *kattr,
+ const char *buf, size_t count, int number)
{
struct hid_device *hdev = to_hid_device(kdev);
- struct cp2112_pstring_attribute *attr =
- container_of(kattr, struct cp2112_pstring_attribute, attr);
struct cp2112_string_report report;
int ret;
@@ -965,7 +971,7 @@ static ssize_t pstr_store(struct device *kdev,
ret = utf8s_to_utf16s(buf, count, UTF16_LITTLE_ENDIAN,
report.string, ARRAY_SIZE(report.string));
- report.report = attr->report;
+ report.report = number;
report.length = ret * sizeof(report.string[0]) + 2;
report.type = USB_DT_STRING;
@@ -983,17 +989,15 @@ static ssize_t pstr_store(struct device *kdev,
return count;
}
-static ssize_t pstr_show(struct device *kdev,
- struct device_attribute *kattr, char *buf)
+static ssize_t pstr_show(struct device *kdev, struct device_attribute *kattr,
+ char *buf, int number)
{
struct hid_device *hdev = to_hid_device(kdev);
- struct cp2112_pstring_attribute *attr =
- container_of(kattr, struct cp2112_pstring_attribute, attr);
struct cp2112_string_report report;
u8 length;
int ret;
- ret = cp2112_hid_get(hdev, attr->report, (u8 *)&report.contents,
+ ret = cp2112_hid_get(hdev, number, (u8 *)&report.contents,
sizeof(report.contents), HID_FEATURE_REPORT);
if (ret < 3) {
hid_err(hdev, "error reading %s string: %d\n", kattr->attr.name,
@@ -1018,10 +1022,16 @@ static ssize_t pstr_show(struct device *kdev,
}
#define CP2112_PSTR_ATTR(name, _report) \
-static struct cp2112_pstring_attribute dev_attr_##name = { \
- .attr = __ATTR(name, (S_IWUSR | S_IRUGO), pstr_show, pstr_store), \
- .report = _report, \
-};
+static ssize_t name##_store(struct device *kdev, struct device_attribute *kattr, \
+ const char *buf, size_t count) \
+{ \
+ return pstr_store(kdev, kattr, buf, count, _report); \
+} \
+static ssize_t name##_show(struct device *kdev, struct device_attribute *kattr, char *buf) \
+{ \
+ return pstr_show(kdev, kattr, buf, _report); \
+} \
+static DEVICE_ATTR_RW(name);
CP2112_PSTR_ATTR(manufacturer, CP2112_MANUFACTURER_STRING);
CP2112_PSTR_ATTR(product, CP2112_PRODUCT_STRING);
@@ -1036,9 +1046,9 @@ static const struct attribute_group cp2112_attr_group = {
&dev_attr_max_power.attr,
&dev_attr_power_mode.attr,
&dev_attr_release_version.attr,
- &dev_attr_manufacturer.attr.attr,
- &dev_attr_product.attr.attr,
- &dev_attr_serial.attr.attr,
+ &dev_attr_manufacturer.attr,
+ &dev_attr_product.attr,
+ &dev_attr_serial.attr,
NULL
}
};
@@ -1063,7 +1073,7 @@ static void chmod_sysfs_attrs(struct hid_device *hdev)
}
for (attr = cp2112_attr_group.attrs; *attr; ++attr) {
- umode_t mode = (buf[1] & 1) ? S_IWUSR | S_IRUGO : S_IRUGO;
+ umode_t mode = (buf[1] & 1) ? 0644 : 0444;
ret = sysfs_chmod_file(&hdev->dev.kobj, *attr, mode);
if (ret < 0)
hid_err(hdev, "error chmoding sysfs file %s\n",
@@ -1080,25 +1090,27 @@ static void cp2112_gpio_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct cp2112_device *dev = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
- __clear_bit(d->hwirq, &dev->irq_mask);
+ __clear_bit(hwirq, &dev->irq_mask);
+ gpiochip_disable_irq(gc, hwirq);
}
static void cp2112_gpio_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct cp2112_device *dev = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
- __set_bit(d->hwirq, &dev->irq_mask);
+ gpiochip_enable_irq(gc, hwirq);
+ __set_bit(hwirq, &dev->irq_mask);
}
static void cp2112_gpio_poll_callback(struct work_struct *work)
{
struct cp2112_device *dev = container_of(work, struct cp2112_device,
gpio_poll_worker.work);
- struct irq_data *d;
u8 gpio_mask;
- u8 virqs = (u8)dev->irq_mask;
u32 irq_type;
int irq, virq, ret;
@@ -1109,21 +1121,14 @@ static void cp2112_gpio_poll_callback(struct work_struct *work)
goto exit;
gpio_mask = ret;
-
- while (virqs) {
- virq = ffs(virqs) - 1;
- virqs &= ~BIT(virq);
-
- if (!dev->gc.to_irq)
- break;
-
- irq = dev->gc.to_irq(&dev->gc, virq);
-
- d = irq_get_irq_data(irq);
- if (!d)
+ for_each_set_bit(virq, &dev->irq_mask, CP2112_GPIO_MAX_GPIO) {
+ irq = irq_find_mapping(dev->gc.irq.domain, virq);
+ if (!irq)
continue;
- irq_type = irqd_get_trigger_type(d);
+ irq_type = irq_get_trigger_type(irq);
+ if (!irq_type)
+ continue;
if (gpio_mask & BIT(virq)) {
/* Level High */
@@ -1159,8 +1164,6 @@ static unsigned int cp2112_gpio_irq_startup(struct irq_data *d)
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct cp2112_device *dev = gpiochip_get_data(gc);
- INIT_DELAYED_WORK(&dev->gpio_poll_worker, cp2112_gpio_poll_callback);
-
if (!dev->gpio_poll) {
dev->gpio_poll = true;
schedule_delayed_work(&dev->gpio_poll_worker, 0);
@@ -1175,7 +1178,12 @@ static void cp2112_gpio_irq_shutdown(struct irq_data *d)
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct cp2112_device *dev = gpiochip_get_data(gc);
- cancel_delayed_work_sync(&dev->gpio_poll_worker);
+ cp2112_gpio_irq_mask(d);
+
+ if (!dev->irq_mask) {
+ dev->gpio_poll = false;
+ cancel_delayed_work_sync(&dev->gpio_poll_worker);
+ }
}
static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type)
@@ -1183,50 +1191,17 @@ static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type)
return 0;
}
-static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev,
- int pin)
-{
- int ret;
-
- if (dev->desc[pin])
- return -EINVAL;
-
- dev->desc[pin] = gpiochip_request_own_desc(&dev->gc, pin,
- "HID/I2C:Event",
- GPIO_ACTIVE_HIGH,
- GPIOD_IN);
- if (IS_ERR(dev->desc[pin])) {
- dev_err(dev->gc.parent, "Failed to request GPIO\n");
- return PTR_ERR(dev->desc[pin]);
- }
-
- ret = cp2112_gpio_direction_input(&dev->gc, pin);
- if (ret < 0) {
- dev_err(dev->gc.parent, "Failed to set GPIO to input dir\n");
- goto err_desc;
- }
-
- ret = gpiochip_lock_as_irq(&dev->gc, pin);
- if (ret) {
- dev_err(dev->gc.parent, "Failed to lock GPIO as interrupt\n");
- goto err_desc;
- }
-
- ret = gpiod_to_irq(dev->desc[pin]);
- if (ret < 0) {
- dev_err(dev->gc.parent, "Failed to translate GPIO to IRQ\n");
- goto err_lock;
- }
-
- return ret;
-
-err_lock:
- gpiochip_unlock_as_irq(&dev->gc, pin);
-err_desc:
- gpiochip_free_own_desc(dev->desc[pin]);
- dev->desc[pin] = NULL;
- return ret;
-}
+static const struct irq_chip cp2112_gpio_irqchip = {
+ .name = "cp2112-gpio",
+ .irq_startup = cp2112_gpio_irq_startup,
+ .irq_shutdown = cp2112_gpio_irq_shutdown,
+ .irq_ack = cp2112_gpio_irq_ack,
+ .irq_mask = cp2112_gpio_irq_mask,
+ .irq_unmask = cp2112_gpio_irq_unmask,
+ .irq_set_type = cp2112_gpio_irq_type,
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
@@ -1245,7 +1220,11 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (!dev->in_out_buffer)
return -ENOMEM;
- mutex_init(&dev->lock);
+ ret = devm_mutex_init(&hdev->dev, &dev->lock);
+ if (ret) {
+ hid_err(hdev, "mutex init failed\n");
+ return ret;
+ }
ret = hid_parse(hdev);
if (ret) {
@@ -1333,21 +1312,12 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
dev->gc.set = cp2112_gpio_set;
dev->gc.get = cp2112_gpio_get;
dev->gc.base = -1;
- dev->gc.ngpio = 8;
+ dev->gc.ngpio = CP2112_GPIO_MAX_GPIO;
dev->gc.can_sleep = 1;
dev->gc.parent = &hdev->dev;
- dev->irq.name = "cp2112-gpio";
- dev->irq.irq_startup = cp2112_gpio_irq_startup;
- dev->irq.irq_shutdown = cp2112_gpio_irq_shutdown;
- dev->irq.irq_ack = cp2112_gpio_irq_ack;
- dev->irq.irq_mask = cp2112_gpio_irq_mask;
- dev->irq.irq_unmask = cp2112_gpio_irq_unmask;
- dev->irq.irq_set_type = cp2112_gpio_irq_type;
- dev->irq.flags = IRQCHIP_MASK_ON_SUSPEND;
-
girq = &dev->gc.irq;
- girq->chip = &dev->irq;
+ gpio_irq_chip_set_chip(girq, &cp2112_gpio_irqchip);
/* The event comes from the outside so no parent handler */
girq->parent_handler = NULL;
girq->num_parents = 0;
@@ -1356,6 +1326,8 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
girq->handler = handle_simple_irq;
girq->threaded = true;
+ INIT_DELAYED_WORK(&dev->gpio_poll_worker, cp2112_gpio_poll_callback);
+
ret = gpiochip_add_data(&dev->gc, dev);
if (ret < 0) {
hid_err(hdev, "error registering gpio chip\n");
@@ -1389,7 +1361,6 @@ err_hid_stop:
static void cp2112_remove(struct hid_device *hdev)
{
struct cp2112_device *dev = hid_get_drvdata(hdev);
- int i;
sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group);
i2c_del_adapter(&dev->adap);
@@ -1399,11 +1370,6 @@ static void cp2112_remove(struct hid_device *hdev)
cancel_delayed_work_sync(&dev->gpio_poll_worker);
}
- for (i = 0; i < ARRAY_SIZE(dev->desc); i++) {
- gpiochip_unlock_as_irq(&dev->gc, i);
- gpiochip_free_own_desc(dev->desc[i]);
- }
-
gpiochip_remove(&dev->gc);
/* i2c_del_adapter has finished removing all i2c devices from our
* adapter. Well behaved devices should no longer call our cp2112_xfer
diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c
index b88f889b3932..98548201feec 100644
--- a/drivers/hid/hid-cypress.c
+++ b/drivers/hid/hid-cypress.c
@@ -67,7 +67,7 @@ static __u8 *va_logical_boundary_fixup(struct hid_device *hdev, __u8 *rdesc,
return rdesc;
}
-static __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
@@ -176,4 +176,5 @@ static struct hid_driver cp_driver = {
};
module_hid_driver(cp_driver);
+MODULE_DESCRIPTION("HID driver for some cypress \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index e7ef1ea107c9..337d2dc81b4c 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -37,437 +37,2808 @@ struct hid_usage_entry {
};
static const struct hid_usage_entry hid_usage_table[] = {
- { 0, 0, "Undefined" },
- { 1, 0, "GenericDesktop" },
- {0, 0x01, "Pointer"},
- {0, 0x02, "Mouse"},
- {0, 0x04, "Joystick"},
- {0, 0x05, "GamePad"},
- {0, 0x06, "Keyboard"},
- {0, 0x07, "Keypad"},
- {0, 0x08, "MultiAxis"},
- {0, 0x30, "X"},
- {0, 0x31, "Y"},
- {0, 0x32, "Z"},
- {0, 0x33, "Rx"},
- {0, 0x34, "Ry"},
- {0, 0x35, "Rz"},
- {0, 0x36, "Slider"},
- {0, 0x37, "Dial"},
- {0, 0x38, "Wheel"},
- {0, 0x39, "HatSwitch"},
- {0, 0x3a, "CountedBuffer"},
- {0, 0x3b, "ByteCount"},
- {0, 0x3c, "MotionWakeup"},
- {0, 0x3d, "Start"},
- {0, 0x3e, "Select"},
- {0, 0x40, "Vx"},
- {0, 0x41, "Vy"},
- {0, 0x42, "Vz"},
- {0, 0x43, "Vbrx"},
- {0, 0x44, "Vbry"},
- {0, 0x45, "Vbrz"},
- {0, 0x46, "Vno"},
- {0, 0x80, "SystemControl"},
- {0, 0x81, "SystemPowerDown"},
- {0, 0x82, "SystemSleep"},
- {0, 0x83, "SystemWakeUp"},
- {0, 0x84, "SystemContextMenu"},
- {0, 0x85, "SystemMainMenu"},
- {0, 0x86, "SystemAppMenu"},
- {0, 0x87, "SystemMenuHelp"},
- {0, 0x88, "SystemMenuExit"},
- {0, 0x89, "SystemMenuSelect"},
- {0, 0x8a, "SystemMenuRight"},
- {0, 0x8b, "SystemMenuLeft"},
- {0, 0x8c, "SystemMenuUp"},
- {0, 0x8d, "SystemMenuDown"},
- {0, 0x90, "D-PadUp"},
- {0, 0x91, "D-PadDown"},
- {0, 0x92, "D-PadRight"},
- {0, 0x93, "D-PadLeft"},
- { 2, 0, "Simulation" },
- {0, 0xb0, "Aileron"},
- {0, 0xb1, "AileronTrim"},
- {0, 0xb2, "Anti-Torque"},
- {0, 0xb3, "Autopilot"},
- {0, 0xb4, "Chaff"},
- {0, 0xb5, "Collective"},
- {0, 0xb6, "DiveBrake"},
- {0, 0xb7, "ElectronicCountermeasures"},
- {0, 0xb8, "Elevator"},
- {0, 0xb9, "ElevatorTrim"},
- {0, 0xba, "Rudder"},
- {0, 0xbb, "Throttle"},
- {0, 0xbc, "FlightCommunications"},
- {0, 0xbd, "FlareRelease"},
- {0, 0xbe, "LandingGear"},
- {0, 0xbf, "ToeBrake"},
- { 6, 0, "GenericDeviceControls" },
- {0, 0x20, "BatteryStrength" },
- {0, 0x21, "WirelessChannel" },
- {0, 0x22, "WirelessID" },
- {0, 0x23, "DiscoverWirelessControl" },
- {0, 0x24, "SecurityCodeCharacterEntered" },
- {0, 0x25, "SecurityCodeCharactedErased" },
- {0, 0x26, "SecurityCodeCleared" },
- { 7, 0, "Keyboard" },
- { 8, 0, "LED" },
- {0, 0x01, "NumLock"},
- {0, 0x02, "CapsLock"},
- {0, 0x03, "ScrollLock"},
- {0, 0x04, "Compose"},
- {0, 0x05, "Kana"},
- {0, 0x4b, "GenericIndicator"},
- { 9, 0, "Button" },
- { 10, 0, "Ordinal" },
- { 12, 0, "Consumer" },
- {0, 0x003, "ProgrammableButtons"},
- {0, 0x238, "HorizontalWheel"},
- { 13, 0, "Digitizers" },
- {0, 0x01, "Digitizer"},
- {0, 0x02, "Pen"},
- {0, 0x03, "LightPen"},
- {0, 0x04, "TouchScreen"},
- {0, 0x05, "TouchPad"},
- {0, 0x0e, "DeviceConfiguration"},
- {0, 0x20, "Stylus"},
- {0, 0x21, "Puck"},
- {0, 0x22, "Finger"},
- {0, 0x23, "DeviceSettings"},
- {0, 0x30, "TipPressure"},
- {0, 0x31, "BarrelPressure"},
- {0, 0x32, "InRange"},
- {0, 0x33, "Touch"},
- {0, 0x34, "UnTouch"},
- {0, 0x35, "Tap"},
- {0, 0x38, "Transducer Index"},
- {0, 0x39, "TabletFunctionKey"},
- {0, 0x3a, "ProgramChangeKey"},
- {0, 0x3B, "Battery Strength"},
- {0, 0x3c, "Invert"},
- {0, 0x42, "TipSwitch"},
- {0, 0x43, "SecondaryTipSwitch"},
- {0, 0x44, "BarrelSwitch"},
- {0, 0x45, "Eraser"},
- {0, 0x46, "TabletPick"},
- {0, 0x47, "Confidence"},
- {0, 0x48, "Width"},
- {0, 0x49, "Height"},
- {0, 0x51, "ContactID"},
- {0, 0x52, "InputMode"},
- {0, 0x53, "DeviceIndex"},
- {0, 0x54, "ContactCount"},
- {0, 0x55, "ContactMaximumNumber"},
- {0, 0x59, "ButtonType"},
- {0, 0x5A, "SecondaryBarrelSwitch"},
- {0, 0x5B, "TransducerSerialNumber"},
- {0, 0x5C, "Preferred Color"},
- {0, 0x5D, "Preferred Color is Locked"},
- {0, 0x5E, "Preferred Line Width"},
- {0, 0x5F, "Preferred Line Width is Locked"},
- {0, 0x6e, "TransducerSerialNumber2"},
- {0, 0x70, "Preferred Line Style"},
- {0, 0x71, "Preferred Line Style is Locked"},
- {0, 0x72, "Ink"},
- {0, 0x73, "Pencil"},
- {0, 0x74, "Highlighter"},
- {0, 0x75, "Chisel Marker"},
- {0, 0x76, "Brush"},
- {0, 0x77, "No Preference"},
- {0, 0x80, "Digitizer Diagnostic"},
- {0, 0x81, "Digitizer Error"},
- {0, 0x82, "Err Normal Status"},
- {0, 0x83, "Err Transducers Exceeded"},
- {0, 0x84, "Err Full Trans Features Unavailable"},
- {0, 0x85, "Err Charge Low"},
- {0, 0x90, "Transducer Software Info"},
- {0, 0x91, "Transducer Vendor Id"},
- {0, 0x92, "Transducer Product Id"},
- {0, 0x93, "Device Supported Protocols"},
- {0, 0x94, "Transducer Supported Protocols"},
- {0, 0x95, "No Protocol"},
- {0, 0x96, "Wacom AES Protocol"},
- {0, 0x97, "USI Protocol"},
- {0, 0x98, "Microsoft Pen Protocol"},
- {0, 0xA0, "Supported Report Rates"},
- {0, 0xA1, "Report Rate"},
- {0, 0xA2, "Transducer Connected"},
- {0, 0xA3, "Switch Disabled"},
- {0, 0xA4, "Switch Unimplemented"},
- {0, 0xA5, "Transducer Switches"},
- { 15, 0, "PhysicalInterfaceDevice" },
- {0, 0x00, "Undefined"},
- {0, 0x01, "Physical_Interface_Device"},
- {0, 0x20, "Normal"},
- {0, 0x21, "Set_Effect_Report"},
- {0, 0x22, "Effect_Block_Index"},
- {0, 0x23, "Parameter_Block_Offset"},
- {0, 0x24, "ROM_Flag"},
- {0, 0x25, "Effect_Type"},
- {0, 0x26, "ET_Constant_Force"},
- {0, 0x27, "ET_Ramp"},
- {0, 0x28, "ET_Custom_Force_Data"},
- {0, 0x30, "ET_Square"},
- {0, 0x31, "ET_Sine"},
- {0, 0x32, "ET_Triangle"},
- {0, 0x33, "ET_Sawtooth_Up"},
- {0, 0x34, "ET_Sawtooth_Down"},
- {0, 0x40, "ET_Spring"},
- {0, 0x41, "ET_Damper"},
- {0, 0x42, "ET_Inertia"},
- {0, 0x43, "ET_Friction"},
- {0, 0x50, "Duration"},
- {0, 0x51, "Sample_Period"},
- {0, 0x52, "Gain"},
- {0, 0x53, "Trigger_Button"},
- {0, 0x54, "Trigger_Repeat_Interval"},
- {0, 0x55, "Axes_Enable"},
- {0, 0x56, "Direction_Enable"},
- {0, 0x57, "Direction"},
- {0, 0x58, "Type_Specific_Block_Offset"},
- {0, 0x59, "Block_Type"},
- {0, 0x5A, "Set_Envelope_Report"},
- {0, 0x5B, "Attack_Level"},
- {0, 0x5C, "Attack_Time"},
- {0, 0x5D, "Fade_Level"},
- {0, 0x5E, "Fade_Time"},
- {0, 0x5F, "Set_Condition_Report"},
- {0, 0x60, "CP_Offset"},
- {0, 0x61, "Positive_Coefficient"},
- {0, 0x62, "Negative_Coefficient"},
- {0, 0x63, "Positive_Saturation"},
- {0, 0x64, "Negative_Saturation"},
- {0, 0x65, "Dead_Band"},
- {0, 0x66, "Download_Force_Sample"},
- {0, 0x67, "Isoch_Custom_Force_Enable"},
- {0, 0x68, "Custom_Force_Data_Report"},
- {0, 0x69, "Custom_Force_Data"},
- {0, 0x6A, "Custom_Force_Vendor_Defined_Data"},
- {0, 0x6B, "Set_Custom_Force_Report"},
- {0, 0x6C, "Custom_Force_Data_Offset"},
- {0, 0x6D, "Sample_Count"},
- {0, 0x6E, "Set_Periodic_Report"},
- {0, 0x6F, "Offset"},
- {0, 0x70, "Magnitude"},
- {0, 0x71, "Phase"},
- {0, 0x72, "Period"},
- {0, 0x73, "Set_Constant_Force_Report"},
- {0, 0x74, "Set_Ramp_Force_Report"},
- {0, 0x75, "Ramp_Start"},
- {0, 0x76, "Ramp_End"},
- {0, 0x77, "Effect_Operation_Report"},
- {0, 0x78, "Effect_Operation"},
- {0, 0x79, "Op_Effect_Start"},
- {0, 0x7A, "Op_Effect_Start_Solo"},
- {0, 0x7B, "Op_Effect_Stop"},
- {0, 0x7C, "Loop_Count"},
- {0, 0x7D, "Device_Gain_Report"},
- {0, 0x7E, "Device_Gain"},
- {0, 0x7F, "PID_Pool_Report"},
- {0, 0x80, "RAM_Pool_Size"},
- {0, 0x81, "ROM_Pool_Size"},
- {0, 0x82, "ROM_Effect_Block_Count"},
- {0, 0x83, "Simultaneous_Effects_Max"},
- {0, 0x84, "Pool_Alignment"},
- {0, 0x85, "PID_Pool_Move_Report"},
- {0, 0x86, "Move_Source"},
- {0, 0x87, "Move_Destination"},
- {0, 0x88, "Move_Length"},
- {0, 0x89, "PID_Block_Load_Report"},
- {0, 0x8B, "Block_Load_Status"},
- {0, 0x8C, "Block_Load_Success"},
- {0, 0x8D, "Block_Load_Full"},
- {0, 0x8E, "Block_Load_Error"},
- {0, 0x8F, "Block_Handle"},
- {0, 0x90, "PID_Block_Free_Report"},
- {0, 0x91, "Type_Specific_Block_Handle"},
- {0, 0x92, "PID_State_Report"},
- {0, 0x94, "Effect_Playing"},
- {0, 0x95, "PID_Device_Control_Report"},
- {0, 0x96, "PID_Device_Control"},
- {0, 0x97, "DC_Enable_Actuators"},
- {0, 0x98, "DC_Disable_Actuators"},
- {0, 0x99, "DC_Stop_All_Effects"},
- {0, 0x9A, "DC_Device_Reset"},
- {0, 0x9B, "DC_Device_Pause"},
- {0, 0x9C, "DC_Device_Continue"},
- {0, 0x9F, "Device_Paused"},
- {0, 0xA0, "Actuators_Enabled"},
- {0, 0xA4, "Safety_Switch"},
- {0, 0xA5, "Actuator_Override_Switch"},
- {0, 0xA6, "Actuator_Power"},
- {0, 0xA7, "Start_Delay"},
- {0, 0xA8, "Parameter_Block_Size"},
- {0, 0xA9, "Device_Managed_Pool"},
- {0, 0xAA, "Shared_Parameter_Blocks"},
- {0, 0xAB, "Create_New_Effect_Report"},
- {0, 0xAC, "RAM_Pool_Available"},
- { 0x20, 0, "Sensor" },
- { 0x20, 0x01, "Sensor" },
- { 0x20, 0x10, "Biometric" },
- { 0x20, 0x11, "BiometricHumanPresence" },
- { 0x20, 0x12, "BiometricHumanProximity" },
- { 0x20, 0x13, "BiometricHumanTouch" },
- { 0x20, 0x20, "Electrical" },
- { 0x20, 0x21, "ElectricalCapacitance" },
- { 0x20, 0x22, "ElectricalCurrent" },
- { 0x20, 0x23, "ElectricalPower" },
- { 0x20, 0x24, "ElectricalInductance" },
- { 0x20, 0x25, "ElectricalResistance" },
- { 0x20, 0x26, "ElectricalVoltage" },
- { 0x20, 0x27, "ElectricalPoteniometer" },
- { 0x20, 0x28, "ElectricalFrequency" },
- { 0x20, 0x29, "ElectricalPeriod" },
- { 0x20, 0x30, "Environmental" },
- { 0x20, 0x31, "EnvironmentalAtmosphericPressure" },
- { 0x20, 0x32, "EnvironmentalHumidity" },
- { 0x20, 0x33, "EnvironmentalTemperature" },
- { 0x20, 0x34, "EnvironmentalWindDirection" },
- { 0x20, 0x35, "EnvironmentalWindSpeed" },
- { 0x20, 0x40, "Light" },
- { 0x20, 0x41, "LightAmbientLight" },
- { 0x20, 0x42, "LightConsumerInfrared" },
- { 0x20, 0x50, "Location" },
- { 0x20, 0x51, "LocationBroadcast" },
- { 0x20, 0x52, "LocationDeadReckoning" },
- { 0x20, 0x53, "LocationGPS" },
- { 0x20, 0x54, "LocationLookup" },
- { 0x20, 0x55, "LocationOther" },
- { 0x20, 0x56, "LocationStatic" },
- { 0x20, 0x57, "LocationTriangulation" },
- { 0x20, 0x60, "Mechanical" },
- { 0x20, 0x61, "MechanicalBooleanSwitch" },
- { 0x20, 0x62, "MechanicalBooleanSwitchArray" },
- { 0x20, 0x63, "MechanicalMultivalueSwitch" },
- { 0x20, 0x64, "MechanicalForce" },
- { 0x20, 0x65, "MechanicalPressure" },
- { 0x20, 0x66, "MechanicalStrain" },
- { 0x20, 0x67, "MechanicalWeight" },
- { 0x20, 0x68, "MechanicalHapticVibrator" },
- { 0x20, 0x69, "MechanicalHallEffectSwitch" },
- { 0x20, 0x70, "Motion" },
- { 0x20, 0x71, "MotionAccelerometer1D" },
- { 0x20, 0x72, "MotionAccelerometer2D" },
- { 0x20, 0x73, "MotionAccelerometer3D" },
- { 0x20, 0x74, "MotionGyrometer1D" },
- { 0x20, 0x75, "MotionGyrometer2D" },
- { 0x20, 0x76, "MotionGyrometer3D" },
- { 0x20, 0x77, "MotionMotionDetector" },
- { 0x20, 0x78, "MotionSpeedometer" },
- { 0x20, 0x79, "MotionAccelerometer" },
- { 0x20, 0x7A, "MotionGyrometer" },
- { 0x20, 0x80, "Orientation" },
- { 0x20, 0x81, "OrientationCompass1D" },
- { 0x20, 0x82, "OrientationCompass2D" },
- { 0x20, 0x83, "OrientationCompass3D" },
- { 0x20, 0x84, "OrientationInclinometer1D" },
- { 0x20, 0x85, "OrientationInclinometer2D" },
- { 0x20, 0x86, "OrientationInclinometer3D" },
- { 0x20, 0x87, "OrientationDistance1D" },
- { 0x20, 0x88, "OrientationDistance2D" },
- { 0x20, 0x89, "OrientationDistance3D" },
- { 0x20, 0x8A, "OrientationDeviceOrientation" },
- { 0x20, 0x8B, "OrientationCompass" },
- { 0x20, 0x8C, "OrientationInclinometer" },
- { 0x20, 0x8D, "OrientationDistance" },
- { 0x20, 0x90, "Scanner" },
- { 0x20, 0x91, "ScannerBarcode" },
- { 0x20, 0x91, "ScannerRFID" },
- { 0x20, 0x91, "ScannerNFC" },
- { 0x20, 0xA0, "Time" },
- { 0x20, 0xA1, "TimeAlarmTimer" },
- { 0x20, 0xA2, "TimeRealTimeClock" },
- { 0x20, 0xE0, "Other" },
- { 0x20, 0xE1, "OtherCustom" },
- { 0x20, 0xE2, "OtherGeneric" },
- { 0x20, 0xE3, "OtherGenericEnumerator" },
- { 0x84, 0, "Power Device" },
- { 0x84, 0x02, "PresentStatus" },
- { 0x84, 0x03, "ChangeStatus" },
- { 0x84, 0x04, "UPS" },
- { 0x84, 0x05, "PowerSupply" },
- { 0x84, 0x10, "BatterySystem" },
- { 0x84, 0x11, "BatterySystemID" },
- { 0x84, 0x12, "Battery" },
- { 0x84, 0x13, "BatteryID" },
- { 0x84, 0x14, "Charger" },
- { 0x84, 0x15, "ChargerID" },
- { 0x84, 0x16, "PowerConverter" },
- { 0x84, 0x17, "PowerConverterID" },
- { 0x84, 0x18, "OutletSystem" },
- { 0x84, 0x19, "OutletSystemID" },
- { 0x84, 0x1a, "Input" },
- { 0x84, 0x1b, "InputID" },
- { 0x84, 0x1c, "Output" },
- { 0x84, 0x1d, "OutputID" },
- { 0x84, 0x1e, "Flow" },
- { 0x84, 0x1f, "FlowID" },
- { 0x84, 0x20, "Outlet" },
- { 0x84, 0x21, "OutletID" },
- { 0x84, 0x22, "Gang" },
- { 0x84, 0x24, "PowerSummary" },
- { 0x84, 0x25, "PowerSummaryID" },
- { 0x84, 0x30, "Voltage" },
- { 0x84, 0x31, "Current" },
- { 0x84, 0x32, "Frequency" },
- { 0x84, 0x33, "ApparentPower" },
- { 0x84, 0x35, "PercentLoad" },
- { 0x84, 0x40, "ConfigVoltage" },
- { 0x84, 0x41, "ConfigCurrent" },
- { 0x84, 0x43, "ConfigApparentPower" },
- { 0x84, 0x53, "LowVoltageTransfer" },
- { 0x84, 0x54, "HighVoltageTransfer" },
- { 0x84, 0x56, "DelayBeforeStartup" },
- { 0x84, 0x57, "DelayBeforeShutdown" },
- { 0x84, 0x58, "Test" },
- { 0x84, 0x5a, "AudibleAlarmControl" },
- { 0x84, 0x60, "Present" },
- { 0x84, 0x61, "Good" },
- { 0x84, 0x62, "InternalFailure" },
- { 0x84, 0x65, "Overload" },
- { 0x84, 0x66, "OverCharged" },
- { 0x84, 0x67, "OverTemperature" },
- { 0x84, 0x68, "ShutdownRequested" },
- { 0x84, 0x69, "ShutdownImminent" },
- { 0x84, 0x6b, "SwitchOn/Off" },
- { 0x84, 0x6c, "Switchable" },
- { 0x84, 0x6d, "Used" },
- { 0x84, 0x6e, "Boost" },
- { 0x84, 0x73, "CommunicationLost" },
- { 0x84, 0xfd, "iManufacturer" },
- { 0x84, 0xfe, "iProduct" },
- { 0x84, 0xff, "iSerialNumber" },
- { 0x85, 0, "Battery System" },
- { 0x85, 0x01, "SMBBatteryMode" },
- { 0x85, 0x02, "SMBBatteryStatus" },
- { 0x85, 0x03, "SMBAlarmWarning" },
- { 0x85, 0x04, "SMBChargerMode" },
- { 0x85, 0x05, "SMBChargerStatus" },
- { 0x85, 0x06, "SMBChargerSpecInfo" },
- { 0x85, 0x07, "SMBSelectorState" },
- { 0x85, 0x08, "SMBSelectorPresets" },
- { 0x85, 0x09, "SMBSelectorInfo" },
- { 0x85, 0x29, "RemainingCapacityLimit" },
- { 0x85, 0x2c, "CapacityMode" },
- { 0x85, 0x42, "BelowRemainingCapacityLimit" },
- { 0x85, 0x44, "Charging" },
- { 0x85, 0x45, "Discharging" },
- { 0x85, 0x4b, "NeedReplacement" },
- { 0x85, 0x65, "AbsoluteStateOfCharge" },
- { 0x85, 0x66, "RemainingCapacity" },
- { 0x85, 0x68, "RunTimeToEmpty" },
- { 0x85, 0x6a, "AverageTimeToFull" },
- { 0x85, 0x83, "DesignCapacity" },
- { 0x85, 0x85, "ManufacturerDate" },
- { 0x85, 0x89, "iDeviceChemistry" },
- { 0x85, 0x8b, "Rechargeable" },
- { 0x85, 0x8f, "iOEMInformation" },
- { 0x85, 0x8d, "CapacityGranularity1" },
- { 0x85, 0xd0, "ACPresent" },
- /* pages 0xff00 to 0xffff are vendor-specific */
- { 0xffff, 0, "Vendor-specific-FF" },
- { 0, 0, NULL }
+ { 0x00, 0, "Undefined" },
+ { 0x01, 0, "GenericDesktop" },
+ { 0x01, 0x0001, "Pointer" },
+ { 0x01, 0x0002, "Mouse" },
+ { 0x01, 0x0004, "Joystick" },
+ { 0x01, 0x0005, "Gamepad" },
+ { 0x01, 0x0006, "Keyboard" },
+ { 0x01, 0x0007, "Keypad" },
+ { 0x01, 0x0008, "MultiaxisController" },
+ { 0x01, 0x0009, "TabletPCSystemControls" },
+ { 0x01, 0x000a, "WaterCoolingDevice" },
+ { 0x01, 0x000b, "ComputerChassisDevice" },
+ { 0x01, 0x000c, "WirelessRadioControls" },
+ { 0x01, 0x000d, "PortableDeviceControl" },
+ { 0x01, 0x000e, "SystemMultiAxisController" },
+ { 0x01, 0x000f, "SpatialController" },
+ { 0x01, 0x0010, "AssistiveControl" },
+ { 0x01, 0x0011, "DeviceDock" },
+ { 0x01, 0x0012, "DockableDevice" },
+ { 0x01, 0x0013, "CallStateManagementControl" },
+ { 0x01, 0x0030, "X" },
+ { 0x01, 0x0031, "Y" },
+ { 0x01, 0x0032, "Z" },
+ { 0x01, 0x0033, "Rx" },
+ { 0x01, 0x0034, "Ry" },
+ { 0x01, 0x0035, "Rz" },
+ { 0x01, 0x0036, "Slider" },
+ { 0x01, 0x0037, "Dial" },
+ { 0x01, 0x0038, "Wheel" },
+ { 0x01, 0x0039, "HatSwitch" },
+ { 0x01, 0x003a, "CountedBuffer" },
+ { 0x01, 0x003b, "ByteCount" },
+ { 0x01, 0x003c, "MotionWakeup" },
+ { 0x01, 0x003d, "Start" },
+ { 0x01, 0x003e, "Select" },
+ { 0x01, 0x0040, "Vx" },
+ { 0x01, 0x0041, "Vy" },
+ { 0x01, 0x0042, "Vz" },
+ { 0x01, 0x0043, "Vbrx" },
+ { 0x01, 0x0044, "Vbry" },
+ { 0x01, 0x0045, "Vbrz" },
+ { 0x01, 0x0046, "Vno" },
+ { 0x01, 0x0047, "FeatureNotification" },
+ { 0x01, 0x0048, "ResolutionMultiplier" },
+ { 0x01, 0x0049, "Qx" },
+ { 0x01, 0x004a, "Qy" },
+ { 0x01, 0x004b, "Qz" },
+ { 0x01, 0x004c, "Qw" },
+ { 0x01, 0x0080, "SystemControl" },
+ { 0x01, 0x0081, "SystemPowerDown" },
+ { 0x01, 0x0082, "SystemSleep" },
+ { 0x01, 0x0083, "SystemWakeUp" },
+ { 0x01, 0x0084, "SystemContextMenu" },
+ { 0x01, 0x0085, "SystemMainMenu" },
+ { 0x01, 0x0086, "SystemAppMenu" },
+ { 0x01, 0x0087, "SystemMenuHelp" },
+ { 0x01, 0x0088, "SystemMenuExit" },
+ { 0x01, 0x0089, "SystemMenuSelect" },
+ { 0x01, 0x008a, "SystemMenuRight" },
+ { 0x01, 0x008b, "SystemMenuLeft" },
+ { 0x01, 0x008c, "SystemMenuUp" },
+ { 0x01, 0x008d, "SystemMenuDown" },
+ { 0x01, 0x008e, "SystemColdRestart" },
+ { 0x01, 0x008f, "SystemWarmRestart" },
+ { 0x01, 0x0090, "DpadUp" },
+ { 0x01, 0x0091, "DpadDown" },
+ { 0x01, 0x0092, "DpadRight" },
+ { 0x01, 0x0093, "DpadLeft" },
+ { 0x01, 0x0094, "IndexTrigger" },
+ { 0x01, 0x0095, "PalmTrigger" },
+ { 0x01, 0x0096, "Thumbstick" },
+ { 0x01, 0x0097, "SystemFunctionShift" },
+ { 0x01, 0x0098, "SystemFunctionShiftLock" },
+ { 0x01, 0x0099, "SystemFunctionShiftLockIndicator" },
+ { 0x01, 0x009a, "SystemDismissNotification" },
+ { 0x01, 0x009b, "SystemDoNotDisturb" },
+ { 0x01, 0x00a0, "SystemDock" },
+ { 0x01, 0x00a1, "SystemUndock" },
+ { 0x01, 0x00a2, "SystemSetup" },
+ { 0x01, 0x00a3, "SystemBreak" },
+ { 0x01, 0x00a4, "SystemDebuggerBreak" },
+ { 0x01, 0x00a5, "ApplicationBreak" },
+ { 0x01, 0x00a6, "ApplicationDebuggerBreak" },
+ { 0x01, 0x00a7, "SystemSpeakerMute" },
+ { 0x01, 0x00a8, "SystemHibernate" },
+ { 0x01, 0x00a9, "SystemMicrophoneMute" },
+ { 0x01, 0x00b0, "SystemDisplayInvert" },
+ { 0x01, 0x00b1, "SystemDisplayInternal" },
+ { 0x01, 0x00b2, "SystemDisplayExternal" },
+ { 0x01, 0x00b3, "SystemDisplayBoth" },
+ { 0x01, 0x00b4, "SystemDisplayDual" },
+ { 0x01, 0x00b5, "SystemDisplayToggleIntExtMode" },
+ { 0x01, 0x00b6, "SystemDisplaySwapPrimarySecondary" },
+ { 0x01, 0x00b7, "SystemDisplayToggleLCDAutoscale" },
+ { 0x01, 0x00c0, "SensorZone" },
+ { 0x01, 0x00c1, "RPM" },
+ { 0x01, 0x00c2, "CoolantLevel" },
+ { 0x01, 0x00c3, "CoolantCriticalLevel" },
+ { 0x01, 0x00c4, "CoolantPump" },
+ { 0x01, 0x00c5, "ChassisEnclosure" },
+ { 0x01, 0x00c6, "WirelessRadioButton" },
+ { 0x01, 0x00c7, "WirelessRadioLED" },
+ { 0x01, 0x00c8, "WirelessRadioSliderSwitch" },
+ { 0x01, 0x00c9, "SystemDisplayRotationLockButton" },
+ { 0x01, 0x00ca, "SystemDisplayRotationLockSliderSwitch" },
+ { 0x01, 0x00cb, "ControlEnable" },
+ { 0x01, 0x00d0, "DockableDeviceUniqueID" },
+ { 0x01, 0x00d1, "DockableDeviceVendorID" },
+ { 0x01, 0x00d2, "DockableDevicePrimaryUsagePage" },
+ { 0x01, 0x00d3, "DockableDevicePrimaryUsageID" },
+ { 0x01, 0x00d4, "DockableDeviceDockingState" },
+ { 0x01, 0x00d5, "DockableDeviceDisplayOcclusion" },
+ { 0x01, 0x00d6, "DockableDeviceObjectType" },
+ { 0x01, 0x00e0, "CallActiveLED" },
+ { 0x01, 0x00e1, "CallMuteToggle" },
+ { 0x01, 0x00e2, "CallMuteLED" },
+ { 0x02, 0, "SimulationControls" },
+ { 0x02, 0x0001, "FlightSimulationDevice" },
+ { 0x02, 0x0002, "AutomobileSimulationDevice" },
+ { 0x02, 0x0003, "TankSimulationDevice" },
+ { 0x02, 0x0004, "SpaceshipSimulationDevice" },
+ { 0x02, 0x0005, "SubmarineSimulationDevice" },
+ { 0x02, 0x0006, "SailingSimulationDevice" },
+ { 0x02, 0x0007, "MotorcycleSimulationDevice" },
+ { 0x02, 0x0008, "SportsSimulationDevice" },
+ { 0x02, 0x0009, "AirplaneSimulationDevice" },
+ { 0x02, 0x000a, "HelicopterSimulationDevice" },
+ { 0x02, 0x000b, "MagicCarpetSimulationDevice" },
+ { 0x02, 0x000c, "BicycleSimulationDevice" },
+ { 0x02, 0x0020, "FlightControlStick" },
+ { 0x02, 0x0021, "FlightStick" },
+ { 0x02, 0x0022, "CyclicControl" },
+ { 0x02, 0x0023, "CyclicTrim" },
+ { 0x02, 0x0024, "FlightYoke" },
+ { 0x02, 0x0025, "TrackControl" },
+ { 0x02, 0x00b0, "Aileron" },
+ { 0x02, 0x00b1, "AileronTrim" },
+ { 0x02, 0x00b2, "AntiTorqueControl" },
+ { 0x02, 0x00b3, "AutopilotEnable" },
+ { 0x02, 0x00b4, "ChaffRelease" },
+ { 0x02, 0x00b5, "CollectiveControl" },
+ { 0x02, 0x00b6, "DiveBrake" },
+ { 0x02, 0x00b7, "ElectronicCountermeasures" },
+ { 0x02, 0x00b8, "Elevator" },
+ { 0x02, 0x00b9, "ElevatorTrim" },
+ { 0x02, 0x00ba, "Rudder" },
+ { 0x02, 0x00bb, "Throttle" },
+ { 0x02, 0x00bc, "FlightCommunications" },
+ { 0x02, 0x00bd, "FlareRelease" },
+ { 0x02, 0x00be, "LandingGear" },
+ { 0x02, 0x00bf, "ToeBrake" },
+ { 0x02, 0x00c0, "Trigger" },
+ { 0x02, 0x00c1, "WeaponsArm" },
+ { 0x02, 0x00c2, "WeaponsSelect" },
+ { 0x02, 0x00c3, "WingFlaps" },
+ { 0x02, 0x00c4, "Accelerator" },
+ { 0x02, 0x00c5, "Brake" },
+ { 0x02, 0x00c6, "Clutch" },
+ { 0x02, 0x00c7, "Shifter" },
+ { 0x02, 0x00c8, "Steering" },
+ { 0x02, 0x00c9, "TurretDirection" },
+ { 0x02, 0x00ca, "BarrelElevation" },
+ { 0x02, 0x00cb, "DivePlane" },
+ { 0x02, 0x00cc, "Ballast" },
+ { 0x02, 0x00cd, "BicycleCrank" },
+ { 0x02, 0x00ce, "HandleBars" },
+ { 0x02, 0x00cf, "FrontBrake" },
+ { 0x02, 0x00d0, "RearBrake" },
+ { 0x03, 0, "VRControls" },
+ { 0x03, 0x0001, "Belt" },
+ { 0x03, 0x0002, "BodySuit" },
+ { 0x03, 0x0003, "Flexor" },
+ { 0x03, 0x0004, "Glove" },
+ { 0x03, 0x0005, "HeadTracker" },
+ { 0x03, 0x0006, "HeadMountedDisplay" },
+ { 0x03, 0x0007, "HandTracker" },
+ { 0x03, 0x0008, "Oculometer" },
+ { 0x03, 0x0009, "Vest" },
+ { 0x03, 0x000a, "AnimatronicDevice" },
+ { 0x03, 0x0020, "StereoEnable" },
+ { 0x03, 0x0021, "DisplayEnable" },
+ { 0x04, 0, "SportControls" },
+ { 0x04, 0x0001, "BaseballBat" },
+ { 0x04, 0x0002, "GolfClub" },
+ { 0x04, 0x0003, "RowingMachine" },
+ { 0x04, 0x0004, "Treadmill" },
+ { 0x04, 0x0030, "Oar" },
+ { 0x04, 0x0031, "Slope" },
+ { 0x04, 0x0032, "Rate" },
+ { 0x04, 0x0033, "StickSpeed" },
+ { 0x04, 0x0034, "StickFaceAngle" },
+ { 0x04, 0x0035, "StickHeelToe" },
+ { 0x04, 0x0036, "StickFollowThrough" },
+ { 0x04, 0x0037, "StickTempo" },
+ { 0x04, 0x0038, "StickType" },
+ { 0x04, 0x0039, "StickHeight" },
+ { 0x04, 0x0050, "Putter" },
+ { 0x04, 0x0051, "1Iron" },
+ { 0x04, 0x0052, "2Iron" },
+ { 0x04, 0x0053, "3Iron" },
+ { 0x04, 0x0054, "4Iron" },
+ { 0x04, 0x0055, "5Iron" },
+ { 0x04, 0x0056, "6Iron" },
+ { 0x04, 0x0057, "7Iron" },
+ { 0x04, 0x0058, "8Iron" },
+ { 0x04, 0x0059, "9Iron" },
+ { 0x04, 0x005a, "10Iron" },
+ { 0x04, 0x005b, "11Iron" },
+ { 0x04, 0x005c, "SandWedge" },
+ { 0x04, 0x005d, "LoftWedge" },
+ { 0x04, 0x005e, "PowerWedge" },
+ { 0x04, 0x005f, "1Wood" },
+ { 0x04, 0x0060, "3Wood" },
+ { 0x04, 0x0061, "5Wood" },
+ { 0x04, 0x0062, "7Wood" },
+ { 0x04, 0x0063, "9Wood" },
+ { 0x05, 0, "GameControls" },
+ { 0x05, 0x0001, "3DGameController" },
+ { 0x05, 0x0002, "PinballDevice" },
+ { 0x05, 0x0003, "GunDevice" },
+ { 0x05, 0x0020, "PointofView" },
+ { 0x05, 0x0021, "TurnRightLeft" },
+ { 0x05, 0x0022, "PitchForwardBackward" },
+ { 0x05, 0x0023, "RollRightLeft" },
+ { 0x05, 0x0024, "MoveRightLeft" },
+ { 0x05, 0x0025, "MoveForwardBackward" },
+ { 0x05, 0x0026, "MoveUpDown" },
+ { 0x05, 0x0027, "LeanRightLeft" },
+ { 0x05, 0x0028, "LeanForwardBackward" },
+ { 0x05, 0x0029, "HeightofPOV" },
+ { 0x05, 0x002a, "Flipper" },
+ { 0x05, 0x002b, "SecondaryFlipper" },
+ { 0x05, 0x002c, "Bump" },
+ { 0x05, 0x002d, "NewGame" },
+ { 0x05, 0x002e, "ShootBall" },
+ { 0x05, 0x002f, "Player" },
+ { 0x05, 0x0030, "GunBolt" },
+ { 0x05, 0x0031, "GunClip" },
+ { 0x05, 0x0032, "GunSelector" },
+ { 0x05, 0x0033, "GunSingleShot" },
+ { 0x05, 0x0034, "GunBurst" },
+ { 0x05, 0x0035, "GunAutomatic" },
+ { 0x05, 0x0036, "GunSafety" },
+ { 0x05, 0x0037, "GamepadFireJump" },
+ { 0x05, 0x0039, "GamepadTrigger" },
+ { 0x05, 0x003a, "FormfittingGamepad" },
+ { 0x06, 0, "GenericDeviceControls" },
+ { 0x06, 0x0001, "BackgroundNonuserControls" },
+ { 0x06, 0x0020, "BatteryStrength" },
+ { 0x06, 0x0021, "WirelessChannel" },
+ { 0x06, 0x0022, "WirelessID" },
+ { 0x06, 0x0023, "DiscoverWirelessControl" },
+ { 0x06, 0x0024, "SecurityCodeCharacterEntered" },
+ { 0x06, 0x0025, "SecurityCodeCharacterErased" },
+ { 0x06, 0x0026, "SecurityCodeCleared" },
+ { 0x06, 0x0027, "SequenceID" },
+ { 0x06, 0x0028, "SequenceIDReset" },
+ { 0x06, 0x0029, "RFSignalStrength" },
+ { 0x06, 0x002a, "SoftwareVersion" },
+ { 0x06, 0x002b, "ProtocolVersion" },
+ { 0x06, 0x002c, "HardwareVersion" },
+ { 0x06, 0x002d, "Major" },
+ { 0x06, 0x002e, "Minor" },
+ { 0x06, 0x002f, "Revision" },
+ { 0x06, 0x0030, "Handedness" },
+ { 0x06, 0x0031, "EitherHand" },
+ { 0x06, 0x0032, "LeftHand" },
+ { 0x06, 0x0033, "RightHand" },
+ { 0x06, 0x0034, "BothHands" },
+ { 0x06, 0x0040, "GripPoseOffset" },
+ { 0x06, 0x0041, "PointerPoseOffset" },
+ { 0x07, 0, "KeyboardKeypad" },
+ { 0x07, 0x0001, "ErrorRollOver" },
+ { 0x07, 0x0002, "POSTFail" },
+ { 0x07, 0x0003, "ErrorUndefined" },
+ { 0x07, 0x0004, "KeyboardA" },
+ { 0x07, 0x0005, "KeyboardB" },
+ { 0x07, 0x0006, "KeyboardC" },
+ { 0x07, 0x0007, "KeyboardD" },
+ { 0x07, 0x0008, "KeyboardE" },
+ { 0x07, 0x0009, "KeyboardF" },
+ { 0x07, 0x000a, "KeyboardG" },
+ { 0x07, 0x000b, "KeyboardH" },
+ { 0x07, 0x000c, "KeyboardI" },
+ { 0x07, 0x000d, "KeyboardJ" },
+ { 0x07, 0x000e, "KeyboardK" },
+ { 0x07, 0x000f, "KeyboardL" },
+ { 0x07, 0x0010, "KeyboardM" },
+ { 0x07, 0x0011, "KeyboardN" },
+ { 0x07, 0x0012, "KeyboardO" },
+ { 0x07, 0x0013, "KeyboardP" },
+ { 0x07, 0x0014, "KeyboardQ" },
+ { 0x07, 0x0015, "KeyboardR" },
+ { 0x07, 0x0016, "KeyboardS" },
+ { 0x07, 0x0017, "KeyboardT" },
+ { 0x07, 0x0018, "KeyboardU" },
+ { 0x07, 0x0019, "KeyboardV" },
+ { 0x07, 0x001a, "KeyboardW" },
+ { 0x07, 0x001b, "KeyboardX" },
+ { 0x07, 0x001c, "KeyboardY" },
+ { 0x07, 0x001d, "KeyboardZ" },
+ { 0x07, 0x001e, "Keyboard1andBang" },
+ { 0x07, 0x001f, "Keyboard2andAt" },
+ { 0x07, 0x0020, "Keyboard3andHash" },
+ { 0x07, 0x0021, "Keyboard4andDollar" },
+ { 0x07, 0x0022, "Keyboard5andPercent" },
+ { 0x07, 0x0023, "Keyboard6andCaret" },
+ { 0x07, 0x0024, "Keyboard7andAmpersand" },
+ { 0x07, 0x0025, "Keyboard8andStar" },
+ { 0x07, 0x0026, "Keyboard9andLeftBracket" },
+ { 0x07, 0x0027, "Keyboard0andRightBracket" },
+ { 0x07, 0x0028, "KeyboardReturnEnter" },
+ { 0x07, 0x0029, "KeyboardEscape" },
+ { 0x07, 0x002a, "KeyboardDelete" },
+ { 0x07, 0x002b, "KeyboardTab" },
+ { 0x07, 0x002c, "KeyboardSpacebar" },
+ { 0x07, 0x002d, "KeyboardDashandUnderscore" },
+ { 0x07, 0x002e, "KeyboardEqualsandPlus" },
+ { 0x07, 0x002f, "KeyboardLeftBrace" },
+ { 0x07, 0x0030, "KeyboardRightBrace" },
+ { 0x07, 0x0031, "KeyboardBackslashandPipe" },
+ { 0x07, 0x0032, "KeyboardNonUSHashandTilde" },
+ { 0x07, 0x0033, "KeyboardSemiColonandColon" },
+ { 0x07, 0x0034, "KeyboardLeftAposandDouble" },
+ { 0x07, 0x0035, "KeyboardGraveAccentandTilde" },
+ { 0x07, 0x0036, "KeyboardCommaandLessThan" },
+ { 0x07, 0x0037, "KeyboardPeriodandGreaterThan" },
+ { 0x07, 0x0038, "KeyboardForwardSlashandQuestionMark" },
+ { 0x07, 0x0039, "KeyboardCapsLock" },
+ { 0x07, 0x003a, "KeyboardF1" },
+ { 0x07, 0x003b, "KeyboardF2" },
+ { 0x07, 0x003c, "KeyboardF3" },
+ { 0x07, 0x003d, "KeyboardF4" },
+ { 0x07, 0x003e, "KeyboardF5" },
+ { 0x07, 0x003f, "KeyboardF6" },
+ { 0x07, 0x0040, "KeyboardF7" },
+ { 0x07, 0x0041, "KeyboardF8" },
+ { 0x07, 0x0042, "KeyboardF9" },
+ { 0x07, 0x0043, "KeyboardF10" },
+ { 0x07, 0x0044, "KeyboardF11" },
+ { 0x07, 0x0045, "KeyboardF12" },
+ { 0x07, 0x0046, "KeyboardPrintScreen" },
+ { 0x07, 0x0047, "KeyboardScrollLock" },
+ { 0x07, 0x0048, "KeyboardPause" },
+ { 0x07, 0x0049, "KeyboardInsert" },
+ { 0x07, 0x004a, "KeyboardHome" },
+ { 0x07, 0x004b, "KeyboardPageUp" },
+ { 0x07, 0x004c, "KeyboardDeleteForward" },
+ { 0x07, 0x004d, "KeyboardEnd" },
+ { 0x07, 0x004e, "KeyboardPageDown" },
+ { 0x07, 0x004f, "KeyboardRightArrow" },
+ { 0x07, 0x0050, "KeyboardLeftArrow" },
+ { 0x07, 0x0051, "KeyboardDownArrow" },
+ { 0x07, 0x0052, "KeyboardUpArrow" },
+ { 0x07, 0x0053, "KeypadNumLockandClear" },
+ { 0x07, 0x0054, "KeypadForwardSlash" },
+ { 0x07, 0x0055, "KeypadStar" },
+ { 0x07, 0x0056, "KeypadDash" },
+ { 0x07, 0x0057, "KeypadPlus" },
+ { 0x07, 0x0058, "KeypadENTER" },
+ { 0x07, 0x0059, "Keypad1andEnd" },
+ { 0x07, 0x005a, "Keypad2andDownArrow" },
+ { 0x07, 0x005b, "Keypad3andPageDn" },
+ { 0x07, 0x005c, "Keypad4andLeftArrow" },
+ { 0x07, 0x005d, "Keypad5" },
+ { 0x07, 0x005e, "Keypad6andRightArrow" },
+ { 0x07, 0x005f, "Keypad7andHome" },
+ { 0x07, 0x0060, "Keypad8andUpArrow" },
+ { 0x07, 0x0061, "Keypad9andPageUp" },
+ { 0x07, 0x0062, "Keypad0andInsert" },
+ { 0x07, 0x0063, "KeypadPeriodandDelete" },
+ { 0x07, 0x0064, "KeyboardNonUSBackslashandPipe" },
+ { 0x07, 0x0065, "KeyboardApplication" },
+ { 0x07, 0x0066, "KeyboardPower" },
+ { 0x07, 0x0067, "KeypadEquals" },
+ { 0x07, 0x0068, "KeyboardF13" },
+ { 0x07, 0x0069, "KeyboardF14" },
+ { 0x07, 0x006a, "KeyboardF15" },
+ { 0x07, 0x006b, "KeyboardF16" },
+ { 0x07, 0x006c, "KeyboardF17" },
+ { 0x07, 0x006d, "KeyboardF18" },
+ { 0x07, 0x006e, "KeyboardF19" },
+ { 0x07, 0x006f, "KeyboardF20" },
+ { 0x07, 0x0070, "KeyboardF21" },
+ { 0x07, 0x0071, "KeyboardF22" },
+ { 0x07, 0x0072, "KeyboardF23" },
+ { 0x07, 0x0073, "KeyboardF24" },
+ { 0x07, 0x0074, "KeyboardExecute" },
+ { 0x07, 0x0075, "KeyboardHelp" },
+ { 0x07, 0x0076, "KeyboardMenu" },
+ { 0x07, 0x0077, "KeyboardSelect" },
+ { 0x07, 0x0078, "KeyboardStop" },
+ { 0x07, 0x0079, "KeyboardAgain" },
+ { 0x07, 0x007a, "KeyboardUndo" },
+ { 0x07, 0x007b, "KeyboardCut" },
+ { 0x07, 0x007c, "KeyboardCopy" },
+ { 0x07, 0x007d, "KeyboardPaste" },
+ { 0x07, 0x007e, "KeyboardFind" },
+ { 0x07, 0x007f, "KeyboardMute" },
+ { 0x07, 0x0080, "KeyboardVolumeUp" },
+ { 0x07, 0x0081, "KeyboardVolumeDown" },
+ { 0x07, 0x0082, "KeyboardLockingCapsLock" },
+ { 0x07, 0x0083, "KeyboardLockingNumLock" },
+ { 0x07, 0x0084, "KeyboardLockingScrollLock" },
+ { 0x07, 0x0085, "KeypadComma" },
+ { 0x07, 0x0086, "KeypadEqualSign" },
+ { 0x07, 0x0087, "KeyboardInternational1" },
+ { 0x07, 0x0088, "KeyboardInternational2" },
+ { 0x07, 0x0089, "KeyboardInternational3" },
+ { 0x07, 0x008a, "KeyboardInternational4" },
+ { 0x07, 0x008b, "KeyboardInternational5" },
+ { 0x07, 0x008c, "KeyboardInternational6" },
+ { 0x07, 0x008d, "KeyboardInternational7" },
+ { 0x07, 0x008e, "KeyboardInternational8" },
+ { 0x07, 0x008f, "KeyboardInternational9" },
+ { 0x07, 0x0090, "KeyboardLANG1" },
+ { 0x07, 0x0091, "KeyboardLANG2" },
+ { 0x07, 0x0092, "KeyboardLANG3" },
+ { 0x07, 0x0093, "KeyboardLANG4" },
+ { 0x07, 0x0094, "KeyboardLANG5" },
+ { 0x07, 0x0095, "KeyboardLANG6" },
+ { 0x07, 0x0096, "KeyboardLANG7" },
+ { 0x07, 0x0097, "KeyboardLANG8" },
+ { 0x07, 0x0098, "KeyboardLANG9" },
+ { 0x07, 0x0099, "KeyboardAlternateErase" },
+ { 0x07, 0x009a, "KeyboardSysReqAttention" },
+ { 0x07, 0x009b, "KeyboardCancel" },
+ { 0x07, 0x009c, "KeyboardClear" },
+ { 0x07, 0x009d, "KeyboardPrior" },
+ { 0x07, 0x009e, "KeyboardReturn" },
+ { 0x07, 0x009f, "KeyboardSeparator" },
+ { 0x07, 0x00a0, "KeyboardOut" },
+ { 0x07, 0x00a1, "KeyboardOper" },
+ { 0x07, 0x00a2, "KeyboardClearAgain" },
+ { 0x07, 0x00a3, "KeyboardCrSelProps" },
+ { 0x07, 0x00a4, "KeyboardExSel" },
+ { 0x07, 0x00b0, "KeypadDouble0" },
+ { 0x07, 0x00b1, "KeypadTriple0" },
+ { 0x07, 0x00b2, "ThousandsSeparator" },
+ { 0x07, 0x00b3, "DecimalSeparator" },
+ { 0x07, 0x00b4, "CurrencyUnit" },
+ { 0x07, 0x00b5, "CurrencySubunit" },
+ { 0x07, 0x00b6, "KeypadLeftBracket" },
+ { 0x07, 0x00b7, "KeypadRightBracket" },
+ { 0x07, 0x00b8, "KeypadLeftBrace" },
+ { 0x07, 0x00b9, "KeypadRightBrace" },
+ { 0x07, 0x00ba, "KeypadTab" },
+ { 0x07, 0x00bb, "KeypadBackspace" },
+ { 0x07, 0x00bc, "KeypadA" },
+ { 0x07, 0x00bd, "KeypadB" },
+ { 0x07, 0x00be, "KeypadC" },
+ { 0x07, 0x00bf, "KeypadD" },
+ { 0x07, 0x00c0, "KeypadE" },
+ { 0x07, 0x00c1, "KeypadF" },
+ { 0x07, 0x00c2, "KeypadXOR" },
+ { 0x07, 0x00c3, "KeypadCaret" },
+ { 0x07, 0x00c4, "KeypadPercentage" },
+ { 0x07, 0x00c5, "KeypadLess" },
+ { 0x07, 0x00c6, "KeypadGreater" },
+ { 0x07, 0x00c7, "KeypadAmpersand" },
+ { 0x07, 0x00c8, "KeypadDoubleAmpersand" },
+ { 0x07, 0x00c9, "KeypadBar" },
+ { 0x07, 0x00ca, "KeypadDoubleBar" },
+ { 0x07, 0x00cb, "KeypadColon" },
+ { 0x07, 0x00cc, "KeypadHash" },
+ { 0x07, 0x00cd, "KeypadSpace" },
+ { 0x07, 0x00ce, "KeypadAt" },
+ { 0x07, 0x00cf, "KeypadBang" },
+ { 0x07, 0x00d0, "KeypadMemoryStore" },
+ { 0x07, 0x00d1, "KeypadMemoryRecall" },
+ { 0x07, 0x00d2, "KeypadMemoryClear" },
+ { 0x07, 0x00d3, "KeypadMemoryAdd" },
+ { 0x07, 0x00d4, "KeypadMemorySubtract" },
+ { 0x07, 0x00d5, "KeypadMemoryMultiply" },
+ { 0x07, 0x00d6, "KeypadMemoryDivide" },
+ { 0x07, 0x00d7, "KeypadPlusMinus" },
+ { 0x07, 0x00d8, "KeypadClear" },
+ { 0x07, 0x00d9, "KeypadClearEntry" },
+ { 0x07, 0x00da, "KeypadBinary" },
+ { 0x07, 0x00db, "KeypadOctal" },
+ { 0x07, 0x00dc, "KeypadDecimal" },
+ { 0x07, 0x00dd, "KeypadHexadecimal" },
+ { 0x07, 0x00e0, "KeyboardLeftControl" },
+ { 0x07, 0x00e1, "KeyboardLeftShift" },
+ { 0x07, 0x00e2, "KeyboardLeftAlt" },
+ { 0x07, 0x00e3, "KeyboardLeftGUI" },
+ { 0x07, 0x00e4, "KeyboardRightControl" },
+ { 0x07, 0x00e5, "KeyboardRightShift" },
+ { 0x07, 0x00e6, "KeyboardRightAlt" },
+ { 0x07, 0x00e7, "KeyboardRightGUI" },
+ { 0x08, 0, "LED" },
+ { 0x08, 0x0001, "NumLock" },
+ { 0x08, 0x0002, "CapsLock" },
+ { 0x08, 0x0003, "ScrollLock" },
+ { 0x08, 0x0004, "Compose" },
+ { 0x08, 0x0005, "Kana" },
+ { 0x08, 0x0006, "Power" },
+ { 0x08, 0x0007, "Shift" },
+ { 0x08, 0x0008, "DoNotDisturb" },
+ { 0x08, 0x0009, "Mute" },
+ { 0x08, 0x000a, "ToneEnable" },
+ { 0x08, 0x000b, "HighCutFilter" },
+ { 0x08, 0x000c, "LowCutFilter" },
+ { 0x08, 0x000d, "EqualizerEnable" },
+ { 0x08, 0x000e, "SoundFieldOn" },
+ { 0x08, 0x000f, "SurroundOn" },
+ { 0x08, 0x0010, "Repeat" },
+ { 0x08, 0x0011, "Stereo" },
+ { 0x08, 0x0012, "SamplingRateDetect" },
+ { 0x08, 0x0013, "Spinning" },
+ { 0x08, 0x0014, "CAV" },
+ { 0x08, 0x0015, "CLV" },
+ { 0x08, 0x0016, "RecordingFormatDetect" },
+ { 0x08, 0x0017, "OffHook" },
+ { 0x08, 0x0018, "Ring" },
+ { 0x08, 0x0019, "MessageWaiting" },
+ { 0x08, 0x001a, "DataMode" },
+ { 0x08, 0x001b, "BatteryOperation" },
+ { 0x08, 0x001c, "BatteryOK" },
+ { 0x08, 0x001d, "BatteryLow" },
+ { 0x08, 0x001e, "Speaker" },
+ { 0x08, 0x001f, "Headset" },
+ { 0x08, 0x0020, "Hold" },
+ { 0x08, 0x0021, "Microphone" },
+ { 0x08, 0x0022, "Coverage" },
+ { 0x08, 0x0023, "NightMode" },
+ { 0x08, 0x0024, "SendCalls" },
+ { 0x08, 0x0025, "CallPickup" },
+ { 0x08, 0x0026, "Conference" },
+ { 0x08, 0x0027, "Standby" },
+ { 0x08, 0x0028, "CameraOn" },
+ { 0x08, 0x0029, "CameraOff" },
+ { 0x08, 0x002a, "OnLine" },
+ { 0x08, 0x002b, "OffLine" },
+ { 0x08, 0x002c, "Busy" },
+ { 0x08, 0x002d, "Ready" },
+ { 0x08, 0x002e, "PaperOut" },
+ { 0x08, 0x002f, "PaperJam" },
+ { 0x08, 0x0030, "Remote" },
+ { 0x08, 0x0031, "Forward" },
+ { 0x08, 0x0032, "Reverse" },
+ { 0x08, 0x0033, "Stop" },
+ { 0x08, 0x0034, "Rewind" },
+ { 0x08, 0x0035, "FastForward" },
+ { 0x08, 0x0036, "Play" },
+ { 0x08, 0x0037, "Pause" },
+ { 0x08, 0x0038, "Record" },
+ { 0x08, 0x0039, "Error" },
+ { 0x08, 0x003a, "UsageSelectedIndicator" },
+ { 0x08, 0x003b, "UsageInUseIndicator" },
+ { 0x08, 0x003c, "UsageMultiModeIndicator" },
+ { 0x08, 0x003d, "IndicatorOn" },
+ { 0x08, 0x003e, "IndicatorFlash" },
+ { 0x08, 0x003f, "IndicatorSlowBlink" },
+ { 0x08, 0x0040, "IndicatorFastBlink" },
+ { 0x08, 0x0041, "IndicatorOff" },
+ { 0x08, 0x0042, "FlashOnTime" },
+ { 0x08, 0x0043, "SlowBlinkOnTime" },
+ { 0x08, 0x0044, "SlowBlinkOffTime" },
+ { 0x08, 0x0045, "FastBlinkOnTime" },
+ { 0x08, 0x0046, "FastBlinkOffTime" },
+ { 0x08, 0x0047, "UsageIndicatorColor" },
+ { 0x08, 0x0048, "IndicatorRed" },
+ { 0x08, 0x0049, "IndicatorGreen" },
+ { 0x08, 0x004a, "IndicatorAmber" },
+ { 0x08, 0x004b, "GenericIndicator" },
+ { 0x08, 0x004c, "SystemSuspend" },
+ { 0x08, 0x004d, "ExternalPowerConnected" },
+ { 0x08, 0x004e, "IndicatorBlue" },
+ { 0x08, 0x004f, "IndicatorOrange" },
+ { 0x08, 0x0050, "GoodStatus" },
+ { 0x08, 0x0051, "WarningStatus" },
+ { 0x08, 0x0052, "RGBLED" },
+ { 0x08, 0x0053, "RedLEDChannel" },
+ { 0x08, 0x0054, "BlueLEDChannel" },
+ { 0x08, 0x0055, "GreenLEDChannel" },
+ { 0x08, 0x0056, "LEDIntensity" },
+ { 0x08, 0x0057, "SystemMicrophoneMute" },
+ { 0x08, 0x0060, "PlayerIndicator" },
+ { 0x08, 0x0061, "Player1" },
+ { 0x08, 0x0062, "Player2" },
+ { 0x08, 0x0063, "Player3" },
+ { 0x08, 0x0064, "Player4" },
+ { 0x08, 0x0065, "Player5" },
+ { 0x08, 0x0066, "Player6" },
+ { 0x08, 0x0067, "Player7" },
+ { 0x08, 0x0068, "Player8" },
+ { 0x09, 0, "Button" },
+ { 0x0a, 0, "Ordinal" },
+ { 0x0b, 0, "TelephonyDevice" },
+ { 0x0b, 0x0001, "Phone" },
+ { 0x0b, 0x0002, "AnsweringMachine" },
+ { 0x0b, 0x0003, "MessageControls" },
+ { 0x0b, 0x0004, "Handset" },
+ { 0x0b, 0x0005, "Headset" },
+ { 0x0b, 0x0006, "TelephonyKeyPad" },
+ { 0x0b, 0x0007, "ProgrammableButton" },
+ { 0x0b, 0x0020, "HookSwitch" },
+ { 0x0b, 0x0021, "Flash" },
+ { 0x0b, 0x0022, "Feature" },
+ { 0x0b, 0x0023, "Hold" },
+ { 0x0b, 0x0024, "Redial" },
+ { 0x0b, 0x0025, "Transfer" },
+ { 0x0b, 0x0026, "Drop" },
+ { 0x0b, 0x0027, "Park" },
+ { 0x0b, 0x0028, "ForwardCalls" },
+ { 0x0b, 0x0029, "AlternateFunction" },
+ { 0x0b, 0x002a, "Line" },
+ { 0x0b, 0x002b, "SpeakerPhone" },
+ { 0x0b, 0x002c, "Conference" },
+ { 0x0b, 0x002d, "RingEnable" },
+ { 0x0b, 0x002e, "RingSelect" },
+ { 0x0b, 0x002f, "PhoneMute" },
+ { 0x0b, 0x0030, "CallerID" },
+ { 0x0b, 0x0031, "Send" },
+ { 0x0b, 0x0050, "SpeedDial" },
+ { 0x0b, 0x0051, "StoreNumber" },
+ { 0x0b, 0x0052, "RecallNumber" },
+ { 0x0b, 0x0053, "PhoneDirectory" },
+ { 0x0b, 0x0070, "VoiceMail" },
+ { 0x0b, 0x0071, "ScreenCalls" },
+ { 0x0b, 0x0072, "DoNotDisturb" },
+ { 0x0b, 0x0073, "Message" },
+ { 0x0b, 0x0074, "AnswerOnOff" },
+ { 0x0b, 0x0090, "InsideDialTone" },
+ { 0x0b, 0x0091, "OutsideDialTone" },
+ { 0x0b, 0x0092, "InsideRingTone" },
+ { 0x0b, 0x0093, "OutsideRingTone" },
+ { 0x0b, 0x0094, "PriorityRingTone" },
+ { 0x0b, 0x0095, "InsideRingback" },
+ { 0x0b, 0x0096, "PriorityRingback" },
+ { 0x0b, 0x0097, "LineBusyTone" },
+ { 0x0b, 0x0098, "ReorderTone" },
+ { 0x0b, 0x0099, "CallWaitingTone" },
+ { 0x0b, 0x009a, "ConfirmationTone1" },
+ { 0x0b, 0x009b, "ConfirmationTone2" },
+ { 0x0b, 0x009c, "TonesOff" },
+ { 0x0b, 0x009d, "OutsideRingback" },
+ { 0x0b, 0x009e, "Ringer" },
+ { 0x0b, 0x00b0, "PhoneKey0" },
+ { 0x0b, 0x00b1, "PhoneKey1" },
+ { 0x0b, 0x00b2, "PhoneKey2" },
+ { 0x0b, 0x00b3, "PhoneKey3" },
+ { 0x0b, 0x00b4, "PhoneKey4" },
+ { 0x0b, 0x00b5, "PhoneKey5" },
+ { 0x0b, 0x00b6, "PhoneKey6" },
+ { 0x0b, 0x00b7, "PhoneKey7" },
+ { 0x0b, 0x00b8, "PhoneKey8" },
+ { 0x0b, 0x00b9, "PhoneKey9" },
+ { 0x0b, 0x00ba, "PhoneKeyStar" },
+ { 0x0b, 0x00bb, "PhoneKeyPound" },
+ { 0x0b, 0x00bc, "PhoneKeyA" },
+ { 0x0b, 0x00bd, "PhoneKeyB" },
+ { 0x0b, 0x00be, "PhoneKeyC" },
+ { 0x0b, 0x00bf, "PhoneKeyD" },
+ { 0x0b, 0x00c0, "PhoneCallHistoryKey" },
+ { 0x0b, 0x00c1, "PhoneCallerIDKey" },
+ { 0x0b, 0x00c2, "PhoneSettingsKey" },
+ { 0x0b, 0x00f0, "HostControl" },
+ { 0x0b, 0x00f1, "HostAvailable" },
+ { 0x0b, 0x00f2, "HostCallActive" },
+ { 0x0b, 0x00f3, "ActivateHandsetAudio" },
+ { 0x0b, 0x00f4, "RingType" },
+ { 0x0b, 0x00f5, "RedialablePhoneNumber" },
+ { 0x0b, 0x00f8, "StopRingTone" },
+ { 0x0b, 0x00f9, "PSTNRingTone" },
+ { 0x0b, 0x00fa, "HostRingTone" },
+ { 0x0b, 0x00fb, "AlertSoundError" },
+ { 0x0b, 0x00fc, "AlertSoundConfirm" },
+ { 0x0b, 0x00fd, "AlertSoundNotification" },
+ { 0x0b, 0x00fe, "SilentRing" },
+ { 0x0b, 0x0108, "EmailMessageWaiting" },
+ { 0x0b, 0x0109, "VoicemailMessageWaiting" },
+ { 0x0b, 0x010a, "HostHold" },
+ { 0x0b, 0x0110, "IncomingCallHistoryCount" },
+ { 0x0b, 0x0111, "OutgoingCallHistoryCount" },
+ { 0x0b, 0x0112, "IncomingCallHistory" },
+ { 0x0b, 0x0113, "OutgoingCallHistory" },
+ { 0x0b, 0x0114, "PhoneLocale" },
+ { 0x0b, 0x0140, "PhoneTimeSecond" },
+ { 0x0b, 0x0141, "PhoneTimeMinute" },
+ { 0x0b, 0x0142, "PhoneTimeHour" },
+ { 0x0b, 0x0143, "PhoneDateDay" },
+ { 0x0b, 0x0144, "PhoneDateMonth" },
+ { 0x0b, 0x0145, "PhoneDateYear" },
+ { 0x0b, 0x0146, "HandsetNickname" },
+ { 0x0b, 0x0147, "AddressBookID" },
+ { 0x0b, 0x014a, "CallDuration" },
+ { 0x0b, 0x014b, "DualModePhone" },
+ { 0x0c, 0, "Consumer" },
+ { 0x0c, 0x0001, "ConsumerControl" },
+ { 0x0c, 0x0002, "NumericKeyPad" },
+ { 0x0c, 0x0003, "ProgrammableButtons" },
+ { 0x0c, 0x0004, "Microphone" },
+ { 0x0c, 0x0005, "Headphone" },
+ { 0x0c, 0x0006, "GraphicEqualizer" },
+ { 0x0c, 0x0020, "10" },
+ { 0x0c, 0x0021, "100" },
+ { 0x0c, 0x0022, "AMPM" },
+ { 0x0c, 0x0030, "Power" },
+ { 0x0c, 0x0031, "Reset" },
+ { 0x0c, 0x0032, "Sleep" },
+ { 0x0c, 0x0033, "SleepAfter" },
+ { 0x0c, 0x0034, "SleepMode" },
+ { 0x0c, 0x0035, "Illumination" },
+ { 0x0c, 0x0036, "FunctionButtons" },
+ { 0x0c, 0x0040, "Menu" },
+ { 0x0c, 0x0041, "MenuPick" },
+ { 0x0c, 0x0042, "MenuUp" },
+ { 0x0c, 0x0043, "MenuDown" },
+ { 0x0c, 0x0044, "MenuLeft" },
+ { 0x0c, 0x0045, "MenuRight" },
+ { 0x0c, 0x0046, "MenuEscape" },
+ { 0x0c, 0x0047, "MenuValueIncrease" },
+ { 0x0c, 0x0048, "MenuValueDecrease" },
+ { 0x0c, 0x0060, "DataOnScreen" },
+ { 0x0c, 0x0061, "ClosedCaption" },
+ { 0x0c, 0x0062, "ClosedCaptionSelect" },
+ { 0x0c, 0x0063, "VCRTV" },
+ { 0x0c, 0x0064, "BroadcastMode" },
+ { 0x0c, 0x0065, "Snapshot" },
+ { 0x0c, 0x0066, "Still" },
+ { 0x0c, 0x0067, "PictureinPictureToggle" },
+ { 0x0c, 0x0068, "PictureinPictureSwap" },
+ { 0x0c, 0x0069, "RedMenuButton" },
+ { 0x0c, 0x006a, "GreenMenuButton" },
+ { 0x0c, 0x006b, "BlueMenuButton" },
+ { 0x0c, 0x006c, "YellowMenuButton" },
+ { 0x0c, 0x006d, "Aspect" },
+ { 0x0c, 0x006e, "3DModeSelect" },
+ { 0x0c, 0x006f, "DisplayBrightnessIncrement" },
+ { 0x0c, 0x0070, "DisplayBrightnessDecrement" },
+ { 0x0c, 0x0071, "DisplayBrightness" },
+ { 0x0c, 0x0072, "DisplayBacklightToggle" },
+ { 0x0c, 0x0073, "DisplaySetBrightnesstoMinimum" },
+ { 0x0c, 0x0074, "DisplaySetBrightnesstoMaximum" },
+ { 0x0c, 0x0075, "DisplaySetAutoBrightness" },
+ { 0x0c, 0x0076, "CameraAccessEnabled" },
+ { 0x0c, 0x0077, "CameraAccessDisabled" },
+ { 0x0c, 0x0078, "CameraAccessToggle" },
+ { 0x0c, 0x0079, "KeyboardBrightnessIncrement" },
+ { 0x0c, 0x007a, "KeyboardBrightnessDecrement" },
+ { 0x0c, 0x007b, "KeyboardBacklightSetLevel" },
+ { 0x0c, 0x007c, "KeyboardBacklightOOC" },
+ { 0x0c, 0x007d, "KeyboardBacklightSetMinimum" },
+ { 0x0c, 0x007e, "KeyboardBacklightSetMaximum" },
+ { 0x0c, 0x007f, "KeyboardBacklightAuto" },
+ { 0x0c, 0x0080, "Selection" },
+ { 0x0c, 0x0081, "AssignSelection" },
+ { 0x0c, 0x0082, "ModeStep" },
+ { 0x0c, 0x0083, "RecallLast" },
+ { 0x0c, 0x0084, "EnterChannel" },
+ { 0x0c, 0x0085, "OrderMovie" },
+ { 0x0c, 0x0086, "Channel" },
+ { 0x0c, 0x0087, "MediaSelection" },
+ { 0x0c, 0x0088, "MediaSelectComputer" },
+ { 0x0c, 0x0089, "MediaSelectTV" },
+ { 0x0c, 0x008a, "MediaSelectWWW" },
+ { 0x0c, 0x008b, "MediaSelectDVD" },
+ { 0x0c, 0x008c, "MediaSelectTelephone" },
+ { 0x0c, 0x008d, "MediaSelectProgramGuide" },
+ { 0x0c, 0x008e, "MediaSelectVideoPhone" },
+ { 0x0c, 0x008f, "MediaSelectGames" },
+ { 0x0c, 0x0090, "MediaSelectMessages" },
+ { 0x0c, 0x0091, "MediaSelectCD" },
+ { 0x0c, 0x0092, "MediaSelectVCR" },
+ { 0x0c, 0x0093, "MediaSelectTuner" },
+ { 0x0c, 0x0094, "Quit" },
+ { 0x0c, 0x0095, "Help" },
+ { 0x0c, 0x0096, "MediaSelectTape" },
+ { 0x0c, 0x0097, "MediaSelectCable" },
+ { 0x0c, 0x0098, "MediaSelectSatellite" },
+ { 0x0c, 0x0099, "MediaSelectSecurity" },
+ { 0x0c, 0x009a, "MediaSelectHome" },
+ { 0x0c, 0x009b, "MediaSelectCall" },
+ { 0x0c, 0x009c, "ChannelIncrement" },
+ { 0x0c, 0x009d, "ChannelDecrement" },
+ { 0x0c, 0x009e, "MediaSelectSAP" },
+ { 0x0c, 0x00a0, "VCRPlus" },
+ { 0x0c, 0x00a1, "Once" },
+ { 0x0c, 0x00a2, "Daily" },
+ { 0x0c, 0x00a3, "Weekly" },
+ { 0x0c, 0x00a4, "Monthly" },
+ { 0x0c, 0x00b0, "Play" },
+ { 0x0c, 0x00b1, "Pause" },
+ { 0x0c, 0x00b2, "Record" },
+ { 0x0c, 0x00b3, "FastForward" },
+ { 0x0c, 0x00b4, "Rewind" },
+ { 0x0c, 0x00b5, "ScanNextTrack" },
+ { 0x0c, 0x00b6, "ScanPreviousTrack" },
+ { 0x0c, 0x00b7, "Stop" },
+ { 0x0c, 0x00b8, "Eject" },
+ { 0x0c, 0x00b9, "RandomPlay" },
+ { 0x0c, 0x00ba, "SelectDisc" },
+ { 0x0c, 0x00bb, "EnterDisc" },
+ { 0x0c, 0x00bc, "Repeat" },
+ { 0x0c, 0x00bd, "Tracking" },
+ { 0x0c, 0x00be, "TrackNormal" },
+ { 0x0c, 0x00bf, "SlowTracking" },
+ { 0x0c, 0x00c0, "FrameForward" },
+ { 0x0c, 0x00c1, "FrameBack" },
+ { 0x0c, 0x00c2, "Mark" },
+ { 0x0c, 0x00c3, "ClearMark" },
+ { 0x0c, 0x00c4, "RepeatFromMark" },
+ { 0x0c, 0x00c5, "ReturnToMark" },
+ { 0x0c, 0x00c6, "SearchMarkForward" },
+ { 0x0c, 0x00c7, "SearchMarkBackwards" },
+ { 0x0c, 0x00c8, "CounterReset" },
+ { 0x0c, 0x00c9, "ShowCounter" },
+ { 0x0c, 0x00ca, "TrackingIncrement" },
+ { 0x0c, 0x00cb, "TrackingDecrement" },
+ { 0x0c, 0x00cc, "StopEject" },
+ { 0x0c, 0x00cd, "PlayPause" },
+ { 0x0c, 0x00ce, "PlaySkip" },
+ { 0x0c, 0x00cf, "VoiceCommand" },
+ { 0x0c, 0x00d0, "InvokeCaptureInterface" },
+ { 0x0c, 0x00d1, "StartorStopGameRecording" },
+ { 0x0c, 0x00d2, "HistoricalGameCapture" },
+ { 0x0c, 0x00d3, "CaptureGameScreenshot" },
+ { 0x0c, 0x00d4, "ShoworHideRecordingIndicator" },
+ { 0x0c, 0x00d5, "StartorStopMicrophoneCapture" },
+ { 0x0c, 0x00d6, "StartorStopCameraCapture" },
+ { 0x0c, 0x00d7, "StartorStopGameBroadcast" },
+ { 0x0c, 0x00d8, "StartorStopVoiceDictationSession" },
+ { 0x0c, 0x00d9, "InvokeDismissEmojiPicker" },
+ { 0x0c, 0x00e0, "Volume" },
+ { 0x0c, 0x00e1, "Balance" },
+ { 0x0c, 0x00e2, "Mute" },
+ { 0x0c, 0x00e3, "Bass" },
+ { 0x0c, 0x00e4, "Treble" },
+ { 0x0c, 0x00e5, "BassBoost" },
+ { 0x0c, 0x00e6, "SurroundMode" },
+ { 0x0c, 0x00e7, "Loudness" },
+ { 0x0c, 0x00e8, "MPX" },
+ { 0x0c, 0x00e9, "VolumeIncrement" },
+ { 0x0c, 0x00ea, "VolumeDecrement" },
+ { 0x0c, 0x00f0, "SpeedSelect" },
+ { 0x0c, 0x00f1, "PlaybackSpeed" },
+ { 0x0c, 0x00f2, "StandardPlay" },
+ { 0x0c, 0x00f3, "LongPlay" },
+ { 0x0c, 0x00f4, "ExtendedPlay" },
+ { 0x0c, 0x00f5, "Slow" },
+ { 0x0c, 0x0100, "FanEnable" },
+ { 0x0c, 0x0101, "FanSpeed" },
+ { 0x0c, 0x0102, "LightEnable" },
+ { 0x0c, 0x0103, "LightIlluminationLevel" },
+ { 0x0c, 0x0104, "ClimateControlEnable" },
+ { 0x0c, 0x0105, "RoomTemperature" },
+ { 0x0c, 0x0106, "SecurityEnable" },
+ { 0x0c, 0x0107, "FireAlarm" },
+ { 0x0c, 0x0108, "PoliceAlarm" },
+ { 0x0c, 0x0109, "Proximity" },
+ { 0x0c, 0x010a, "Motion" },
+ { 0x0c, 0x010b, "DuressAlarm" },
+ { 0x0c, 0x010c, "HoldupAlarm" },
+ { 0x0c, 0x010d, "MedicalAlarm" },
+ { 0x0c, 0x0150, "BalanceRight" },
+ { 0x0c, 0x0151, "BalanceLeft" },
+ { 0x0c, 0x0152, "BassIncrement" },
+ { 0x0c, 0x0153, "BassDecrement" },
+ { 0x0c, 0x0154, "TrebleIncrement" },
+ { 0x0c, 0x0155, "TrebleDecrement" },
+ { 0x0c, 0x0160, "SpeakerSystem" },
+ { 0x0c, 0x0161, "ChannelLeft" },
+ { 0x0c, 0x0162, "ChannelRight" },
+ { 0x0c, 0x0163, "ChannelCenter" },
+ { 0x0c, 0x0164, "ChannelFront" },
+ { 0x0c, 0x0165, "ChannelCenterFront" },
+ { 0x0c, 0x0166, "ChannelSide" },
+ { 0x0c, 0x0167, "ChannelSurround" },
+ { 0x0c, 0x0168, "ChannelLowFrequencyEnhancement" },
+ { 0x0c, 0x0169, "ChannelTop" },
+ { 0x0c, 0x016a, "ChannelUnknown" },
+ { 0x0c, 0x0170, "Subchannel" },
+ { 0x0c, 0x0171, "SubchannelIncrement" },
+ { 0x0c, 0x0172, "SubchannelDecrement" },
+ { 0x0c, 0x0173, "AlternateAudioIncrement" },
+ { 0x0c, 0x0174, "AlternateAudioDecrement" },
+ { 0x0c, 0x0180, "ApplicationLaunchButtons" },
+ { 0x0c, 0x0181, "ALLaunchButtonConfigurationTool" },
+ { 0x0c, 0x0182, "ALProgrammableButtonConfiguration" },
+ { 0x0c, 0x0183, "ALConsumerControlConfiguration" },
+ { 0x0c, 0x0184, "ALWordProcessor" },
+ { 0x0c, 0x0185, "ALTextEditor" },
+ { 0x0c, 0x0186, "ALSpreadsheet" },
+ { 0x0c, 0x0187, "ALGraphicsEditor" },
+ { 0x0c, 0x0188, "ALPresentationApp" },
+ { 0x0c, 0x0189, "ALDatabaseApp" },
+ { 0x0c, 0x018a, "ALEmailReader" },
+ { 0x0c, 0x018b, "ALNewsreader" },
+ { 0x0c, 0x018c, "ALVoicemail" },
+ { 0x0c, 0x018d, "ALContactsAddressBook" },
+ { 0x0c, 0x018e, "ALCalendarSchedule" },
+ { 0x0c, 0x018f, "ALTaskProjectManager" },
+ { 0x0c, 0x0190, "ALLogJournalTimecard" },
+ { 0x0c, 0x0191, "ALCheckbookFinance" },
+ { 0x0c, 0x0192, "ALCalculator" },
+ { 0x0c, 0x0193, "ALAVCapturePlayback" },
+ { 0x0c, 0x0194, "ALLocalMachineBrowser" },
+ { 0x0c, 0x0195, "ALLANWANBrowser" },
+ { 0x0c, 0x0196, "ALInternetBrowser" },
+ { 0x0c, 0x0197, "ALRemoteNetworkingISPConnect" },
+ { 0x0c, 0x0198, "ALNetworkConference" },
+ { 0x0c, 0x0199, "ALNetworkChat" },
+ { 0x0c, 0x019a, "ALTelephonyDialer" },
+ { 0x0c, 0x019b, "ALLogon" },
+ { 0x0c, 0x019c, "ALLogoff" },
+ { 0x0c, 0x019d, "ALLogonLogoff" },
+ { 0x0c, 0x019e, "ALTerminalLockScreensaver" },
+ { 0x0c, 0x019f, "ALControlPanel" },
+ { 0x0c, 0x01a0, "ALCommandLineProcessorRun" },
+ { 0x0c, 0x01a1, "ALProcessTaskManager" },
+ { 0x0c, 0x01a2, "ALSelectTaskApplication" },
+ { 0x0c, 0x01a3, "ALNextTaskApplication" },
+ { 0x0c, 0x01a4, "ALPreviousTaskApplication" },
+ { 0x0c, 0x01a5, "ALPreemptiveHaltTaskApplication" },
+ { 0x0c, 0x01a6, "ALIntegratedHelpCenter" },
+ { 0x0c, 0x01a7, "ALDocuments" },
+ { 0x0c, 0x01a8, "ALThesaurus" },
+ { 0x0c, 0x01a9, "ALDictionary" },
+ { 0x0c, 0x01aa, "ALDesktop" },
+ { 0x0c, 0x01ab, "ALSpellCheck" },
+ { 0x0c, 0x01ac, "ALGrammarCheck" },
+ { 0x0c, 0x01ad, "ALWirelessStatus" },
+ { 0x0c, 0x01ae, "ALKeyboardLayout" },
+ { 0x0c, 0x01af, "ALVirusProtection" },
+ { 0x0c, 0x01b0, "ALEncryption" },
+ { 0x0c, 0x01b1, "ALScreenSaver" },
+ { 0x0c, 0x01b2, "ALAlarms" },
+ { 0x0c, 0x01b3, "ALClock" },
+ { 0x0c, 0x01b4, "ALFileBrowser" },
+ { 0x0c, 0x01b5, "ALPowerStatus" },
+ { 0x0c, 0x01b6, "ALImageBrowser" },
+ { 0x0c, 0x01b7, "ALAudioBrowser" },
+ { 0x0c, 0x01b8, "ALMovieBrowser" },
+ { 0x0c, 0x01b9, "ALDigitalRightsManager" },
+ { 0x0c, 0x01ba, "ALDigitalWallet" },
+ { 0x0c, 0x01bc, "ALInstantMessaging" },
+ { 0x0c, 0x01bd, "ALOEMFeaturesTipsTutorialBrowser" },
+ { 0x0c, 0x01be, "ALOEMHelp" },
+ { 0x0c, 0x01bf, "ALOnlineCommunity" },
+ { 0x0c, 0x01c0, "ALEntertainmentContentBrowser" },
+ { 0x0c, 0x01c1, "ALOnlineShoppingBrowser" },
+ { 0x0c, 0x01c2, "ALSmartCardInformationHelp" },
+ { 0x0c, 0x01c3, "ALMarketMonitorFinanceBrowser" },
+ { 0x0c, 0x01c4, "ALCustomizedCorporateNewsBrowser" },
+ { 0x0c, 0x01c5, "ALOnlineActivityBrowser" },
+ { 0x0c, 0x01c6, "ALResearchSearchBrowser" },
+ { 0x0c, 0x01c7, "ALAudioPlayer" },
+ { 0x0c, 0x01c8, "ALMessageStatus" },
+ { 0x0c, 0x01c9, "ALContactSync" },
+ { 0x0c, 0x01ca, "ALNavigation" },
+ { 0x0c, 0x01cb, "ALContextawareDesktopAssistant" },
+ { 0x0c, 0x0200, "GenericGUIApplicationControls" },
+ { 0x0c, 0x0201, "ACNew" },
+ { 0x0c, 0x0202, "ACOpen" },
+ { 0x0c, 0x0203, "ACClose" },
+ { 0x0c, 0x0204, "ACExit" },
+ { 0x0c, 0x0205, "ACMaximize" },
+ { 0x0c, 0x0206, "ACMinimize" },
+ { 0x0c, 0x0207, "ACSave" },
+ { 0x0c, 0x0208, "ACPrint" },
+ { 0x0c, 0x0209, "ACProperties" },
+ { 0x0c, 0x021a, "ACUndo" },
+ { 0x0c, 0x021b, "ACCopy" },
+ { 0x0c, 0x021c, "ACCut" },
+ { 0x0c, 0x021d, "ACPaste" },
+ { 0x0c, 0x021e, "ACSelectAll" },
+ { 0x0c, 0x021f, "ACFind" },
+ { 0x0c, 0x0220, "ACFindandReplace" },
+ { 0x0c, 0x0221, "ACSearch" },
+ { 0x0c, 0x0222, "ACGoTo" },
+ { 0x0c, 0x0223, "ACHome" },
+ { 0x0c, 0x0224, "ACBack" },
+ { 0x0c, 0x0225, "ACForward" },
+ { 0x0c, 0x0226, "ACStop" },
+ { 0x0c, 0x0227, "ACRefresh" },
+ { 0x0c, 0x0228, "ACPreviousLink" },
+ { 0x0c, 0x0229, "ACNextLink" },
+ { 0x0c, 0x022a, "ACBookmarks" },
+ { 0x0c, 0x022b, "ACHistory" },
+ { 0x0c, 0x022c, "ACSubscriptions" },
+ { 0x0c, 0x022d, "ACZoomIn" },
+ { 0x0c, 0x022e, "ACZoomOut" },
+ { 0x0c, 0x022f, "ACZoom" },
+ { 0x0c, 0x0230, "ACFullScreenView" },
+ { 0x0c, 0x0231, "ACNormalView" },
+ { 0x0c, 0x0232, "ACViewToggle" },
+ { 0x0c, 0x0233, "ACScrollUp" },
+ { 0x0c, 0x0234, "ACScrollDown" },
+ { 0x0c, 0x0235, "ACScroll" },
+ { 0x0c, 0x0236, "ACPanLeft" },
+ { 0x0c, 0x0237, "ACPanRight" },
+ { 0x0c, 0x0238, "ACPan" },
+ { 0x0c, 0x0239, "ACNewWindow" },
+ { 0x0c, 0x023a, "ACTileHorizontally" },
+ { 0x0c, 0x023b, "ACTileVertically" },
+ { 0x0c, 0x023c, "ACFormat" },
+ { 0x0c, 0x023d, "ACEdit" },
+ { 0x0c, 0x023e, "ACBold" },
+ { 0x0c, 0x023f, "ACItalics" },
+ { 0x0c, 0x0240, "ACUnderline" },
+ { 0x0c, 0x0241, "ACStrikethrough" },
+ { 0x0c, 0x0242, "ACSubscript" },
+ { 0x0c, 0x0243, "ACSuperscript" },
+ { 0x0c, 0x0244, "ACAllCaps" },
+ { 0x0c, 0x0245, "ACRotate" },
+ { 0x0c, 0x0246, "ACResize" },
+ { 0x0c, 0x0247, "ACFlipHorizontal" },
+ { 0x0c, 0x0248, "ACFlipVertical" },
+ { 0x0c, 0x0249, "ACMirrorHorizontal" },
+ { 0x0c, 0x024a, "ACMirrorVertical" },
+ { 0x0c, 0x024b, "ACFontSelect" },
+ { 0x0c, 0x024c, "ACFontColor" },
+ { 0x0c, 0x024d, "ACFontSize" },
+ { 0x0c, 0x024e, "ACJustifyLeft" },
+ { 0x0c, 0x024f, "ACJustifyCenterH" },
+ { 0x0c, 0x0250, "ACJustifyRight" },
+ { 0x0c, 0x0251, "ACJustifyBlockH" },
+ { 0x0c, 0x0252, "ACJustifyTop" },
+ { 0x0c, 0x0253, "ACJustifyCenterV" },
+ { 0x0c, 0x0254, "ACJustifyBottom" },
+ { 0x0c, 0x0255, "ACJustifyBlockV" },
+ { 0x0c, 0x0256, "ACIndentDecrease" },
+ { 0x0c, 0x0257, "ACIndentIncrease" },
+ { 0x0c, 0x0258, "ACNumberedList" },
+ { 0x0c, 0x0259, "ACRestartNumbering" },
+ { 0x0c, 0x025a, "ACBulletedList" },
+ { 0x0c, 0x025b, "ACPromote" },
+ { 0x0c, 0x025c, "ACDemote" },
+ { 0x0c, 0x025d, "ACYes" },
+ { 0x0c, 0x025e, "ACNo" },
+ { 0x0c, 0x025f, "ACCancel" },
+ { 0x0c, 0x0260, "ACCatalog" },
+ { 0x0c, 0x0261, "ACBuyCheckout" },
+ { 0x0c, 0x0262, "ACAddtoCart" },
+ { 0x0c, 0x0263, "ACExpand" },
+ { 0x0c, 0x0264, "ACExpandAll" },
+ { 0x0c, 0x0265, "ACCollapse" },
+ { 0x0c, 0x0266, "ACCollapseAll" },
+ { 0x0c, 0x0267, "ACPrintPreview" },
+ { 0x0c, 0x0268, "ACPasteSpecial" },
+ { 0x0c, 0x0269, "ACInsertMode" },
+ { 0x0c, 0x026a, "ACDelete" },
+ { 0x0c, 0x026b, "ACLock" },
+ { 0x0c, 0x026c, "ACUnlock" },
+ { 0x0c, 0x026d, "ACProtect" },
+ { 0x0c, 0x026e, "ACUnprotect" },
+ { 0x0c, 0x026f, "ACAttachComment" },
+ { 0x0c, 0x0270, "ACDeleteComment" },
+ { 0x0c, 0x0271, "ACViewComment" },
+ { 0x0c, 0x0272, "ACSelectWord" },
+ { 0x0c, 0x0273, "ACSelectSentence" },
+ { 0x0c, 0x0274, "ACSelectParagraph" },
+ { 0x0c, 0x0275, "ACSelectColumn" },
+ { 0x0c, 0x0276, "ACSelectRow" },
+ { 0x0c, 0x0277, "ACSelectTable" },
+ { 0x0c, 0x0278, "ACSelectObject" },
+ { 0x0c, 0x0279, "ACRedoRepeat" },
+ { 0x0c, 0x027a, "ACSort" },
+ { 0x0c, 0x027b, "ACSortAscending" },
+ { 0x0c, 0x027c, "ACSortDescending" },
+ { 0x0c, 0x027d, "ACFilter" },
+ { 0x0c, 0x027e, "ACSetClock" },
+ { 0x0c, 0x027f, "ACViewClock" },
+ { 0x0c, 0x0280, "ACSelectTimeZone" },
+ { 0x0c, 0x0281, "ACEditTimeZones" },
+ { 0x0c, 0x0282, "ACSetAlarm" },
+ { 0x0c, 0x0283, "ACClearAlarm" },
+ { 0x0c, 0x0284, "ACSnoozeAlarm" },
+ { 0x0c, 0x0285, "ACResetAlarm" },
+ { 0x0c, 0x0286, "ACSynchronize" },
+ { 0x0c, 0x0287, "ACSendReceive" },
+ { 0x0c, 0x0288, "ACSendTo" },
+ { 0x0c, 0x0289, "ACReply" },
+ { 0x0c, 0x028a, "ACReplyAll" },
+ { 0x0c, 0x028b, "ACForwardMsg" },
+ { 0x0c, 0x028c, "ACSend" },
+ { 0x0c, 0x028d, "ACAttachFile" },
+ { 0x0c, 0x028e, "ACUpload" },
+ { 0x0c, 0x028f, "ACDownloadSaveTargetAs" },
+ { 0x0c, 0x0290, "ACSetBorders" },
+ { 0x0c, 0x0291, "ACInsertRow" },
+ { 0x0c, 0x0292, "ACInsertColumn" },
+ { 0x0c, 0x0293, "ACInsertFile" },
+ { 0x0c, 0x0294, "ACInsertPicture" },
+ { 0x0c, 0x0295, "ACInsertObject" },
+ { 0x0c, 0x0296, "ACInsertSymbol" },
+ { 0x0c, 0x0297, "ACSaveandClose" },
+ { 0x0c, 0x0298, "ACRename" },
+ { 0x0c, 0x0299, "ACMerge" },
+ { 0x0c, 0x029a, "ACSplit" },
+ { 0x0c, 0x029b, "ACDisributeHorizontally" },
+ { 0x0c, 0x029c, "ACDistributeVertically" },
+ { 0x0c, 0x029d, "ACNextKeyboardLayoutSelect" },
+ { 0x0c, 0x029e, "ACNavigationGuidance" },
+ { 0x0c, 0x029f, "ACDesktopShowAllWindows" },
+ { 0x0c, 0x02a0, "ACSoftKeyLeft" },
+ { 0x0c, 0x02a1, "ACSoftKeyRight" },
+ { 0x0c, 0x02a2, "ACDesktopShowAllApplications" },
+ { 0x0c, 0x02b0, "ACIdleKeepAlive" },
+ { 0x0c, 0x02c0, "ExtendedKeyboardAttributesCollection" },
+ { 0x0c, 0x02c1, "KeyboardFormFactor" },
+ { 0x0c, 0x02c2, "KeyboardKeyType" },
+ { 0x0c, 0x02c3, "KeyboardPhysicalLayout" },
+ { 0x0c, 0x02c4, "VendorSpecificKeyboardPhysicalLayout" },
+ { 0x0c, 0x02c5, "KeyboardIETFLanguageTagIndex" },
+ { 0x0c, 0x02c6, "ImplementedKeyboardInputAssistControls" },
+ { 0x0c, 0x02c7, "KeyboardInputAssistPrevious" },
+ { 0x0c, 0x02c8, "KeyboardInputAssistNext" },
+ { 0x0c, 0x02c9, "KeyboardInputAssistPreviousGroup" },
+ { 0x0c, 0x02ca, "KeyboardInputAssistNextGroup" },
+ { 0x0c, 0x02cb, "KeyboardInputAssistAccept" },
+ { 0x0c, 0x02cc, "KeyboardInputAssistCancel" },
+ { 0x0c, 0x02d0, "PrivacyScreenToggle" },
+ { 0x0c, 0x02d1, "PrivacyScreenLevelDecrement" },
+ { 0x0c, 0x02d2, "PrivacyScreenLevelIncrement" },
+ { 0x0c, 0x02d3, "PrivacyScreenLevelMinimum" },
+ { 0x0c, 0x02d4, "PrivacyScreenLevelMaximum" },
+ { 0x0c, 0x0500, "ContactEdited" },
+ { 0x0c, 0x0501, "ContactAdded" },
+ { 0x0c, 0x0502, "ContactRecordActive" },
+ { 0x0c, 0x0503, "ContactIndex" },
+ { 0x0c, 0x0504, "ContactNickname" },
+ { 0x0c, 0x0505, "ContactFirstName" },
+ { 0x0c, 0x0506, "ContactLastName" },
+ { 0x0c, 0x0507, "ContactFullName" },
+ { 0x0c, 0x0508, "ContactPhoneNumberPersonal" },
+ { 0x0c, 0x0509, "ContactPhoneNumberBusiness" },
+ { 0x0c, 0x050a, "ContactPhoneNumberMobile" },
+ { 0x0c, 0x050b, "ContactPhoneNumberPager" },
+ { 0x0c, 0x050c, "ContactPhoneNumberFax" },
+ { 0x0c, 0x050d, "ContactPhoneNumberOther" },
+ { 0x0c, 0x050e, "ContactEmailPersonal" },
+ { 0x0c, 0x050f, "ContactEmailBusiness" },
+ { 0x0c, 0x0510, "ContactEmailOther" },
+ { 0x0c, 0x0511, "ContactEmailMain" },
+ { 0x0c, 0x0512, "ContactSpeedDialNumber" },
+ { 0x0c, 0x0513, "ContactStatusFlag" },
+ { 0x0c, 0x0514, "ContactMisc" },
+ { 0x0d, 0, "Digitizers" },
+ { 0x0d, 0x0001, "Digitizer" },
+ { 0x0d, 0x0002, "Pen" },
+ { 0x0d, 0x0003, "LightPen" },
+ { 0x0d, 0x0004, "TouchScreen" },
+ { 0x0d, 0x0005, "TouchPad" },
+ { 0x0d, 0x0006, "Whiteboard" },
+ { 0x0d, 0x0007, "CoordinateMeasuringMachine" },
+ { 0x0d, 0x0008, "3DDigitizer" },
+ { 0x0d, 0x0009, "StereoPlotter" },
+ { 0x0d, 0x000a, "ArticulatedArm" },
+ { 0x0d, 0x000b, "Armature" },
+ { 0x0d, 0x000c, "MultiplePointDigitizer" },
+ { 0x0d, 0x000d, "FreeSpaceWand" },
+ { 0x0d, 0x000e, "DeviceConfiguration" },
+ { 0x0d, 0x000f, "CapacitiveHeatMapDigitizer" },
+ { 0x0d, 0x0020, "Stylus" },
+ { 0x0d, 0x0021, "Puck" },
+ { 0x0d, 0x0022, "Finger" },
+ { 0x0d, 0x0023, "Devicesettings" },
+ { 0x0d, 0x0024, "CharacterGesture" },
+ { 0x0d, 0x0030, "TipPressure" },
+ { 0x0d, 0x0031, "BarrelPressure" },
+ { 0x0d, 0x0032, "InRange" },
+ { 0x0d, 0x0033, "Touch" },
+ { 0x0d, 0x0034, "Untouch" },
+ { 0x0d, 0x0035, "Tap" },
+ { 0x0d, 0x0036, "Quality" },
+ { 0x0d, 0x0037, "DataValid" },
+ { 0x0d, 0x0038, "TransducerIndex" },
+ { 0x0d, 0x0039, "TabletFunctionKeys" },
+ { 0x0d, 0x003a, "ProgramChangeKeys" },
+ { 0x0d, 0x003b, "BatteryStrength" },
+ { 0x0d, 0x003c, "Invert" },
+ { 0x0d, 0x003d, "XTilt" },
+ { 0x0d, 0x003e, "YTilt" },
+ { 0x0d, 0x003f, "Azimuth" },
+ { 0x0d, 0x0040, "Altitude" },
+ { 0x0d, 0x0041, "Twist" },
+ { 0x0d, 0x0042, "TipSwitch" },
+ { 0x0d, 0x0043, "SecondaryTipSwitch" },
+ { 0x0d, 0x0044, "BarrelSwitch" },
+ { 0x0d, 0x0045, "Eraser" },
+ { 0x0d, 0x0046, "TabletPick" },
+ { 0x0d, 0x0047, "TouchValid" },
+ { 0x0d, 0x0048, "Width" },
+ { 0x0d, 0x0049, "Height" },
+ { 0x0d, 0x0051, "ContactIdentifier" },
+ { 0x0d, 0x0052, "DeviceMode" },
+ { 0x0d, 0x0053, "DeviceIdentifier" },
+ { 0x0d, 0x0054, "ContactCount" },
+ { 0x0d, 0x0055, "ContactCountMaximum" },
+ { 0x0d, 0x0056, "ScanTime" },
+ { 0x0d, 0x0057, "SurfaceSwitch" },
+ { 0x0d, 0x0058, "ButtonSwitch" },
+ { 0x0d, 0x0059, "PadType" },
+ { 0x0d, 0x005a, "SecondaryBarrelSwitch" },
+ { 0x0d, 0x005b, "TransducerSerialNumber" },
+ { 0x0d, 0x005c, "PreferredColor" },
+ { 0x0d, 0x005d, "PreferredColorisLocked" },
+ { 0x0d, 0x005e, "PreferredLineWidth" },
+ { 0x0d, 0x005f, "PreferredLineWidthisLocked" },
+ { 0x0d, 0x0060, "LatencyMode" },
+ { 0x0d, 0x0061, "GestureCharacterQuality" },
+ { 0x0d, 0x0062, "CharacterGestureDataLength" },
+ { 0x0d, 0x0063, "CharacterGestureData" },
+ { 0x0d, 0x0064, "GestureCharacterEncoding" },
+ { 0x0d, 0x0065, "UTF8CharacterGestureEncoding" },
+ { 0x0d, 0x0066, "UTF16LittleEndianCharacterGestureEncoding" },
+ { 0x0d, 0x0067, "UTF16BigEndianCharacterGestureEncoding" },
+ { 0x0d, 0x0068, "UTF32LittleEndianCharacterGestureEncoding" },
+ { 0x0d, 0x0069, "UTF32BigEndianCharacterGestureEncoding" },
+ { 0x0d, 0x006a, "CapacitiveHeatMapProtocolVendorID" },
+ { 0x0d, 0x006b, "CapacitiveHeatMapProtocolVersion" },
+ { 0x0d, 0x006c, "CapacitiveHeatMapFrameData" },
+ { 0x0d, 0x006d, "GestureCharacterEnable" },
+ { 0x0d, 0x006e, "TransducerSerialNumberPart2" },
+ { 0x0d, 0x006f, "NoPreferredColor" },
+ { 0x0d, 0x0070, "PreferredLineStyle" },
+ { 0x0d, 0x0071, "PreferredLineStyleisLocked" },
+ { 0x0d, 0x0072, "Ink" },
+ { 0x0d, 0x0073, "Pencil" },
+ { 0x0d, 0x0074, "Highlighter" },
+ { 0x0d, 0x0075, "ChiselMarker" },
+ { 0x0d, 0x0076, "Brush" },
+ { 0x0d, 0x0077, "NoPreference" },
+ { 0x0d, 0x0080, "DigitizerDiagnostic" },
+ { 0x0d, 0x0081, "DigitizerError" },
+ { 0x0d, 0x0082, "ErrNormalStatus" },
+ { 0x0d, 0x0083, "ErrTransducersExceeded" },
+ { 0x0d, 0x0084, "ErrFullTransFeaturesUnavailable" },
+ { 0x0d, 0x0085, "ErrChargeLow" },
+ { 0x0d, 0x0090, "TransducerSoftwareInfo" },
+ { 0x0d, 0x0091, "TransducerVendorId" },
+ { 0x0d, 0x0092, "TransducerProductId" },
+ { 0x0d, 0x0093, "DeviceSupportedProtocols" },
+ { 0x0d, 0x0094, "TransducerSupportedProtocols" },
+ { 0x0d, 0x0095, "NoProtocol" },
+ { 0x0d, 0x0096, "WacomAESProtocol" },
+ { 0x0d, 0x0097, "USIProtocol" },
+ { 0x0d, 0x0098, "MicrosoftPenProtocol" },
+ { 0x0d, 0x00a0, "SupportedReportRates" },
+ { 0x0d, 0x00a1, "ReportRate" },
+ { 0x0d, 0x00a2, "TransducerConnected" },
+ { 0x0d, 0x00a3, "SwitchDisabled" },
+ { 0x0d, 0x00a4, "SwitchUnimplemented" },
+ { 0x0d, 0x00a5, "TransducerSwitches" },
+ { 0x0d, 0x00a6, "TransducerIndexSelector" },
+ { 0x0d, 0x00b0, "ButtonPressThreshold" },
+ { 0x0e, 0, "Haptics" },
+ { 0x0e, 0x0001, "SimpleHapticController" },
+ { 0x0e, 0x0010, "WaveformList" },
+ { 0x0e, 0x0011, "DurationList" },
+ { 0x0e, 0x0020, "AutoTrigger" },
+ { 0x0e, 0x0021, "ManualTrigger" },
+ { 0x0e, 0x0022, "AutoTriggerAssociatedControl" },
+ { 0x0e, 0x0023, "Intensity" },
+ { 0x0e, 0x0024, "RepeatCount" },
+ { 0x0e, 0x0025, "RetriggerPeriod" },
+ { 0x0e, 0x0026, "WaveformVendorPage" },
+ { 0x0e, 0x0027, "WaveformVendorID" },
+ { 0x0e, 0x0028, "WaveformCutoffTime" },
+ { 0x0e, 0x1001, "WaveformNone" },
+ { 0x0e, 0x1002, "WaveformStop" },
+ { 0x0e, 0x1003, "WaveformClick" },
+ { 0x0e, 0x1004, "WaveformBuzzContinuous" },
+ { 0x0e, 0x1005, "WaveformRumbleContinuous" },
+ { 0x0e, 0x1006, "WaveformPress" },
+ { 0x0e, 0x1007, "WaveformRelease" },
+ { 0x0e, 0x1008, "WaveformHover" },
+ { 0x0e, 0x1009, "WaveformSuccess" },
+ { 0x0e, 0x100a, "WaveformError" },
+ { 0x0e, 0x100b, "WaveformInkContinuous" },
+ { 0x0e, 0x100c, "WaveformPencilContinuous" },
+ { 0x0e, 0x100d, "WaveformMarkerContinuous" },
+ { 0x0e, 0x100e, "WaveformChiselMarkerContinuous" },
+ { 0x0e, 0x100f, "WaveformBrushContinuous" },
+ { 0x0e, 0x1010, "WaveformEraserContinuous" },
+ { 0x0e, 0x1011, "WaveformSparkleContinuous" },
+ { 0x0f, 0, "PhysicalInputDevice" },
+ { 0x0f, 0x0001, "PhysicalInputDevice" },
+ { 0x0f, 0x0020, "Normal" },
+ { 0x0f, 0x0021, "SetEffectReport" },
+ { 0x0f, 0x0022, "EffectParameterBlockIndex" },
+ { 0x0f, 0x0023, "ParameterBlockOffset" },
+ { 0x0f, 0x0024, "ROMFlag" },
+ { 0x0f, 0x0025, "EffectType" },
+ { 0x0f, 0x0026, "ETConstantForce" },
+ { 0x0f, 0x0027, "ETRamp" },
+ { 0x0f, 0x0028, "ETCustomForce" },
+ { 0x0f, 0x0030, "ETSquare" },
+ { 0x0f, 0x0031, "ETSine" },
+ { 0x0f, 0x0032, "ETTriangle" },
+ { 0x0f, 0x0033, "ETSawtoothUp" },
+ { 0x0f, 0x0034, "ETSawtoothDown" },
+ { 0x0f, 0x0040, "ETSpring" },
+ { 0x0f, 0x0041, "ETDamper" },
+ { 0x0f, 0x0042, "ETInertia" },
+ { 0x0f, 0x0043, "ETFriction" },
+ { 0x0f, 0x0050, "Duration" },
+ { 0x0f, 0x0051, "SamplePeriod" },
+ { 0x0f, 0x0052, "Gain" },
+ { 0x0f, 0x0053, "TriggerButton" },
+ { 0x0f, 0x0054, "TriggerRepeatInterval" },
+ { 0x0f, 0x0055, "AxesEnable" },
+ { 0x0f, 0x0056, "DirectionEnable" },
+ { 0x0f, 0x0057, "Direction" },
+ { 0x0f, 0x0058, "TypeSpecificBlockOffset" },
+ { 0x0f, 0x0059, "BlockType" },
+ { 0x0f, 0x005a, "SetEnvelopeReport" },
+ { 0x0f, 0x005b, "AttackLevel" },
+ { 0x0f, 0x005c, "AttackTime" },
+ { 0x0f, 0x005d, "FadeLevel" },
+ { 0x0f, 0x005e, "FadeTime" },
+ { 0x0f, 0x005f, "SetConditionReport" },
+ { 0x0f, 0x0060, "CenterPointOffset" },
+ { 0x0f, 0x0061, "PositiveCoefficient" },
+ { 0x0f, 0x0062, "NegativeCoefficient" },
+ { 0x0f, 0x0063, "PositiveSaturation" },
+ { 0x0f, 0x0064, "NegativeSaturation" },
+ { 0x0f, 0x0065, "DeadBand" },
+ { 0x0f, 0x0066, "DownloadForceSample" },
+ { 0x0f, 0x0067, "IsochCustomForceEnable" },
+ { 0x0f, 0x0068, "CustomForceDataReport" },
+ { 0x0f, 0x0069, "CustomForceData" },
+ { 0x0f, 0x006a, "CustomForceVendorDefinedData" },
+ { 0x0f, 0x006b, "SetCustomForceReport" },
+ { 0x0f, 0x006c, "CustomForceDataOffset" },
+ { 0x0f, 0x006d, "SampleCount" },
+ { 0x0f, 0x006e, "SetPeriodicReport" },
+ { 0x0f, 0x006f, "Offset" },
+ { 0x0f, 0x0070, "Magnitude" },
+ { 0x0f, 0x0071, "Phase" },
+ { 0x0f, 0x0072, "Period" },
+ { 0x0f, 0x0073, "SetConstantForceReport" },
+ { 0x0f, 0x0074, "SetRampForceReport" },
+ { 0x0f, 0x0075, "RampStart" },
+ { 0x0f, 0x0076, "RampEnd" },
+ { 0x0f, 0x0077, "EffectOperationReport" },
+ { 0x0f, 0x0078, "EffectOperation" },
+ { 0x0f, 0x0079, "OpEffectStart" },
+ { 0x0f, 0x007a, "OpEffectStartSolo" },
+ { 0x0f, 0x007b, "OpEffectStop" },
+ { 0x0f, 0x007c, "LoopCount" },
+ { 0x0f, 0x007d, "DeviceGainReport" },
+ { 0x0f, 0x007e, "DeviceGain" },
+ { 0x0f, 0x007f, "ParameterBlockPoolsReport" },
+ { 0x0f, 0x0080, "RAMPoolSize" },
+ { 0x0f, 0x0081, "ROMPoolSize" },
+ { 0x0f, 0x0082, "ROMEffectBlockCount" },
+ { 0x0f, 0x0083, "SimultaneousEffectsMax" },
+ { 0x0f, 0x0084, "PoolAlignment" },
+ { 0x0f, 0x0085, "ParameterBlockMoveReport" },
+ { 0x0f, 0x0086, "MoveSource" },
+ { 0x0f, 0x0087, "MoveDestination" },
+ { 0x0f, 0x0088, "MoveLength" },
+ { 0x0f, 0x0089, "EffectParameterBlockLoadReport" },
+ { 0x0f, 0x008b, "EffectParameterBlockLoadStatus" },
+ { 0x0f, 0x008c, "BlockLoadSuccess" },
+ { 0x0f, 0x008d, "BlockLoadFull" },
+ { 0x0f, 0x008e, "BlockLoadError" },
+ { 0x0f, 0x008f, "BlockHandle" },
+ { 0x0f, 0x0090, "EffectParameterBlockFreeReport" },
+ { 0x0f, 0x0091, "TypeSpecificBlockHandle" },
+ { 0x0f, 0x0092, "PIDStateReport" },
+ { 0x0f, 0x0094, "EffectPlaying" },
+ { 0x0f, 0x0095, "PIDDeviceControlReport" },
+ { 0x0f, 0x0096, "PIDDeviceControl" },
+ { 0x0f, 0x0097, "DCEnableActuators" },
+ { 0x0f, 0x0098, "DCDisableActuators" },
+ { 0x0f, 0x0099, "DCStopAllEffects" },
+ { 0x0f, 0x009a, "DCReset" },
+ { 0x0f, 0x009b, "DCPause" },
+ { 0x0f, 0x009c, "DCContinue" },
+ { 0x0f, 0x009f, "DevicePaused" },
+ { 0x0f, 0x00a0, "ActuatorsEnabled" },
+ { 0x0f, 0x00a4, "SafetySwitch" },
+ { 0x0f, 0x00a5, "ActuatorOverrideSwitch" },
+ { 0x0f, 0x00a6, "ActuatorPower" },
+ { 0x0f, 0x00a7, "StartDelay" },
+ { 0x0f, 0x00a8, "ParameterBlockSize" },
+ { 0x0f, 0x00a9, "DeviceManagedPool" },
+ { 0x0f, 0x00aa, "SharedParameterBlocks" },
+ { 0x0f, 0x00ab, "CreateNewEffectParameterBlockReport" },
+ { 0x0f, 0x00ac, "RAMPoolAvailable" },
+ { 0x11, 0, "SoC" },
+ { 0x11, 0x0001, "SocControl" },
+ { 0x11, 0x0002, "FirmwareTransfer" },
+ { 0x11, 0x0003, "FirmwareFileId" },
+ { 0x11, 0x0004, "FileOffsetInBytes" },
+ { 0x11, 0x0005, "FileTransferSizeMaxInBytes" },
+ { 0x11, 0x0006, "FilePayload" },
+ { 0x11, 0x0007, "FilePayloadSizeInBytes" },
+ { 0x11, 0x0008, "FilePayloadContainsLastBytes" },
+ { 0x11, 0x0009, "FileTransferStop" },
+ { 0x11, 0x000a, "FileTransferTillEnd" },
+ { 0x12, 0, "EyeandHeadTrackers" },
+ { 0x12, 0x0001, "EyeTracker" },
+ { 0x12, 0x0002, "HeadTracker" },
+ { 0x12, 0x0010, "TrackingData" },
+ { 0x12, 0x0011, "Capabilities" },
+ { 0x12, 0x0012, "Configuration" },
+ { 0x12, 0x0013, "Status" },
+ { 0x12, 0x0014, "Control" },
+ { 0x12, 0x0020, "SensorTimestamp" },
+ { 0x12, 0x0021, "PositionX" },
+ { 0x12, 0x0022, "PositionY" },
+ { 0x12, 0x0023, "PositionZ" },
+ { 0x12, 0x0024, "GazePoint" },
+ { 0x12, 0x0025, "LeftEyePosition" },
+ { 0x12, 0x0026, "RightEyePosition" },
+ { 0x12, 0x0027, "HeadPosition" },
+ { 0x12, 0x0028, "HeadDirectionPoint" },
+ { 0x12, 0x0029, "RotationaboutXaxis" },
+ { 0x12, 0x002a, "RotationaboutYaxis" },
+ { 0x12, 0x002b, "RotationaboutZaxis" },
+ { 0x12, 0x0100, "TrackerQuality" },
+ { 0x12, 0x0101, "MinimumTrackingDistance" },
+ { 0x12, 0x0102, "OptimumTrackingDistance" },
+ { 0x12, 0x0103, "MaximumTrackingDistance" },
+ { 0x12, 0x0104, "MaximumScreenPlaneWidth" },
+ { 0x12, 0x0105, "MaximumScreenPlaneHeight" },
+ { 0x12, 0x0200, "DisplayManufacturerID" },
+ { 0x12, 0x0201, "DisplayProductID" },
+ { 0x12, 0x0202, "DisplaySerialNumber" },
+ { 0x12, 0x0203, "DisplayManufacturerDate" },
+ { 0x12, 0x0204, "CalibratedScreenWidth" },
+ { 0x12, 0x0205, "CalibratedScreenHeight" },
+ { 0x12, 0x0300, "SamplingFrequency" },
+ { 0x12, 0x0301, "ConfigurationStatus" },
+ { 0x12, 0x0400, "DeviceModeRequest" },
+ { 0x14, 0, "AuxiliaryDisplay" },
+ { 0x14, 0x0001, "AlphanumericDisplay" },
+ { 0x14, 0x0002, "AuxiliaryDisplay" },
+ { 0x14, 0x0020, "DisplayAttributesReport" },
+ { 0x14, 0x0021, "ASCIICharacterSet" },
+ { 0x14, 0x0022, "DataReadBack" },
+ { 0x14, 0x0023, "FontReadBack" },
+ { 0x14, 0x0024, "DisplayControlReport" },
+ { 0x14, 0x0025, "ClearDisplay" },
+ { 0x14, 0x0026, "DisplayEnable" },
+ { 0x14, 0x0027, "ScreenSaverDelay" },
+ { 0x14, 0x0028, "ScreenSaverEnable" },
+ { 0x14, 0x0029, "VerticalScroll" },
+ { 0x14, 0x002a, "HorizontalScroll" },
+ { 0x14, 0x002b, "CharacterReport" },
+ { 0x14, 0x002c, "DisplayData" },
+ { 0x14, 0x002d, "DisplayStatus" },
+ { 0x14, 0x002e, "StatNotReady" },
+ { 0x14, 0x002f, "StatReady" },
+ { 0x14, 0x0030, "ErrNotaloadablecharacter" },
+ { 0x14, 0x0031, "ErrFontdatacannotberead" },
+ { 0x14, 0x0032, "CursorPositionReport" },
+ { 0x14, 0x0033, "Row" },
+ { 0x14, 0x0034, "Column" },
+ { 0x14, 0x0035, "Rows" },
+ { 0x14, 0x0036, "Columns" },
+ { 0x14, 0x0037, "CursorPixelPositioning" },
+ { 0x14, 0x0038, "CursorMode" },
+ { 0x14, 0x0039, "CursorEnable" },
+ { 0x14, 0x003a, "CursorBlink" },
+ { 0x14, 0x003b, "FontReport" },
+ { 0x14, 0x003c, "FontData" },
+ { 0x14, 0x003d, "CharacterWidth" },
+ { 0x14, 0x003e, "CharacterHeight" },
+ { 0x14, 0x003f, "CharacterSpacingHorizontal" },
+ { 0x14, 0x0040, "CharacterSpacingVertical" },
+ { 0x14, 0x0041, "UnicodeCharacterSet" },
+ { 0x14, 0x0042, "Font7Segment" },
+ { 0x14, 0x0043, "7SegmentDirectMap" },
+ { 0x14, 0x0044, "Font14Segment" },
+ { 0x14, 0x0045, "14SegmentDirectMap" },
+ { 0x14, 0x0046, "DisplayBrightness" },
+ { 0x14, 0x0047, "DisplayContrast" },
+ { 0x14, 0x0048, "CharacterAttribute" },
+ { 0x14, 0x0049, "AttributeReadback" },
+ { 0x14, 0x004a, "AttributeData" },
+ { 0x14, 0x004b, "CharAttrEnhance" },
+ { 0x14, 0x004c, "CharAttrUnderline" },
+ { 0x14, 0x004d, "CharAttrBlink" },
+ { 0x14, 0x0080, "BitmapSizeX" },
+ { 0x14, 0x0081, "BitmapSizeY" },
+ { 0x14, 0x0082, "MaxBlitSize" },
+ { 0x14, 0x0083, "BitDepthFormat" },
+ { 0x14, 0x0084, "DisplayOrientation" },
+ { 0x14, 0x0085, "PaletteReport" },
+ { 0x14, 0x0086, "PaletteDataSize" },
+ { 0x14, 0x0087, "PaletteDataOffset" },
+ { 0x14, 0x0088, "PaletteData" },
+ { 0x14, 0x008a, "BlitReport" },
+ { 0x14, 0x008b, "BlitRectangleX1" },
+ { 0x14, 0x008c, "BlitRectangleY1" },
+ { 0x14, 0x008d, "BlitRectangleX2" },
+ { 0x14, 0x008e, "BlitRectangleY2" },
+ { 0x14, 0x008f, "BlitData" },
+ { 0x14, 0x0090, "SoftButton" },
+ { 0x14, 0x0091, "SoftButtonID" },
+ { 0x14, 0x0092, "SoftButtonSide" },
+ { 0x14, 0x0093, "SoftButtonOffset1" },
+ { 0x14, 0x0094, "SoftButtonOffset2" },
+ { 0x14, 0x0095, "SoftButtonReport" },
+ { 0x14, 0x00c2, "SoftKeys" },
+ { 0x14, 0x00cc, "DisplayDataExtensions" },
+ { 0x14, 0x00cf, "CharacterMapping" },
+ { 0x14, 0x00dd, "UnicodeEquivalent" },
+ { 0x14, 0x00df, "CharacterPageMapping" },
+ { 0x14, 0x00ff, "RequestReport" },
+ { 0x20, 0, "Sensors" },
+ { 0x20, 0x0001, "Sensor" },
+ { 0x20, 0x0010, "Biometric" },
+ { 0x20, 0x0011, "BiometricHumanPresence" },
+ { 0x20, 0x0012, "BiometricHumanProximity" },
+ { 0x20, 0x0013, "BiometricHumanTouch" },
+ { 0x20, 0x0014, "BiometricBloodPressure" },
+ { 0x20, 0x0015, "BiometricBodyTemperature" },
+ { 0x20, 0x0016, "BiometricHeartRate" },
+ { 0x20, 0x0017, "BiometricHeartRateVariability" },
+ { 0x20, 0x0018, "BiometricPeripheralOxygenSaturation" },
+ { 0x20, 0x0019, "BiometricRespiratoryRate" },
+ { 0x20, 0x0020, "Electrical" },
+ { 0x20, 0x0021, "ElectricalCapacitance" },
+ { 0x20, 0x0022, "ElectricalCurrent" },
+ { 0x20, 0x0023, "ElectricalPower" },
+ { 0x20, 0x0024, "ElectricalInductance" },
+ { 0x20, 0x0025, "ElectricalResistance" },
+ { 0x20, 0x0026, "ElectricalVoltage" },
+ { 0x20, 0x0027, "ElectricalPotentiometer" },
+ { 0x20, 0x0028, "ElectricalFrequency" },
+ { 0x20, 0x0029, "ElectricalPeriod" },
+ { 0x20, 0x0030, "Environmental" },
+ { 0x20, 0x0031, "EnvironmentalAtmosphericPressure" },
+ { 0x20, 0x0032, "EnvironmentalHumidity" },
+ { 0x20, 0x0033, "EnvironmentalTemperature" },
+ { 0x20, 0x0034, "EnvironmentalWindDirection" },
+ { 0x20, 0x0035, "EnvironmentalWindSpeed" },
+ { 0x20, 0x0036, "EnvironmentalAirQuality" },
+ { 0x20, 0x0037, "EnvironmentalHeatIndex" },
+ { 0x20, 0x0038, "EnvironmentalSurfaceTemperature" },
+ { 0x20, 0x0039, "EnvironmentalVolatileOrganicCompounds" },
+ { 0x20, 0x003a, "EnvironmentalObjectPresence" },
+ { 0x20, 0x003b, "EnvironmentalObjectProximity" },
+ { 0x20, 0x0040, "Light" },
+ { 0x20, 0x0041, "LightAmbientLight" },
+ { 0x20, 0x0042, "LightConsumerInfrared" },
+ { 0x20, 0x0043, "LightInfraredLight" },
+ { 0x20, 0x0044, "LightVisibleLight" },
+ { 0x20, 0x0045, "LightUltravioletLight" },
+ { 0x20, 0x0050, "Location" },
+ { 0x20, 0x0051, "LocationBroadcast" },
+ { 0x20, 0x0052, "LocationDeadReckoning" },
+ { 0x20, 0x0053, "LocationGPSGlobalPositioningSystem" },
+ { 0x20, 0x0054, "LocationLookup" },
+ { 0x20, 0x0055, "LocationOther" },
+ { 0x20, 0x0056, "LocationStatic" },
+ { 0x20, 0x0057, "LocationTriangulation" },
+ { 0x20, 0x0060, "Mechanical" },
+ { 0x20, 0x0061, "MechanicalBooleanSwitch" },
+ { 0x20, 0x0062, "MechanicalBooleanSwitchArray" },
+ { 0x20, 0x0063, "MechanicalMultivalueSwitch" },
+ { 0x20, 0x0064, "MechanicalForce" },
+ { 0x20, 0x0065, "MechanicalPressure" },
+ { 0x20, 0x0066, "MechanicalStrain" },
+ { 0x20, 0x0067, "MechanicalWeight" },
+ { 0x20, 0x0068, "MechanicalHapticVibrator" },
+ { 0x20, 0x0069, "MechanicalHallEffectSwitch" },
+ { 0x20, 0x0070, "Motion" },
+ { 0x20, 0x0071, "MotionAccelerometer1D" },
+ { 0x20, 0x0072, "MotionAccelerometer2D" },
+ { 0x20, 0x0073, "MotionAccelerometer3D" },
+ { 0x20, 0x0074, "MotionGyrometer1D" },
+ { 0x20, 0x0075, "MotionGyrometer2D" },
+ { 0x20, 0x0076, "MotionGyrometer3D" },
+ { 0x20, 0x0077, "MotionMotionDetector" },
+ { 0x20, 0x0078, "MotionSpeedometer" },
+ { 0x20, 0x0079, "MotionAccelerometer" },
+ { 0x20, 0x007a, "MotionGyrometer" },
+ { 0x20, 0x007b, "MotionGravityVector" },
+ { 0x20, 0x007c, "MotionLinearAccelerometer" },
+ { 0x20, 0x0080, "Orientation" },
+ { 0x20, 0x0081, "OrientationCompass1D" },
+ { 0x20, 0x0082, "OrientationCompass2D" },
+ { 0x20, 0x0083, "OrientationCompass3D" },
+ { 0x20, 0x0084, "OrientationInclinometer1D" },
+ { 0x20, 0x0085, "OrientationInclinometer2D" },
+ { 0x20, 0x0086, "OrientationInclinometer3D" },
+ { 0x20, 0x0087, "OrientationDistance1D" },
+ { 0x20, 0x0088, "OrientationDistance2D" },
+ { 0x20, 0x0089, "OrientationDistance3D" },
+ { 0x20, 0x008a, "OrientationDeviceOrientation" },
+ { 0x20, 0x008b, "OrientationCompass" },
+ { 0x20, 0x008c, "OrientationInclinometer" },
+ { 0x20, 0x008d, "OrientationDistance" },
+ { 0x20, 0x008e, "OrientationRelativeOrientation" },
+ { 0x20, 0x008f, "OrientationSimpleOrientation" },
+ { 0x20, 0x0090, "Scanner" },
+ { 0x20, 0x0091, "ScannerBarcode" },
+ { 0x20, 0x0092, "ScannerRFID" },
+ { 0x20, 0x0093, "ScannerNFC" },
+ { 0x20, 0x00a0, "Time" },
+ { 0x20, 0x00a1, "TimeAlarmTimer" },
+ { 0x20, 0x00a2, "TimeRealTimeClock" },
+ { 0x20, 0x00b0, "PersonalActivity" },
+ { 0x20, 0x00b1, "PersonalActivityActivityDetection" },
+ { 0x20, 0x00b2, "PersonalActivityDevicePosition" },
+ { 0x20, 0x00b3, "PersonalActivityFloorTracker" },
+ { 0x20, 0x00b4, "PersonalActivityPedometer" },
+ { 0x20, 0x00b5, "PersonalActivityStepDetection" },
+ { 0x20, 0x00c0, "OrientationExtended" },
+ { 0x20, 0x00c1, "OrientationExtendedGeomagneticOrientation" },
+ { 0x20, 0x00c2, "OrientationExtendedMagnetometer" },
+ { 0x20, 0x00d0, "Gesture" },
+ { 0x20, 0x00d1, "GestureChassisFlipGesture" },
+ { 0x20, 0x00d2, "GestureHingeFoldGesture" },
+ { 0x20, 0x00e0, "Other" },
+ { 0x20, 0x00e1, "OtherCustom" },
+ { 0x20, 0x00e2, "OtherGeneric" },
+ { 0x20, 0x00e3, "OtherGenericEnumerator" },
+ { 0x20, 0x00e4, "OtherHingeAngle" },
+ { 0x20, 0x00f0, "VendorReserved1" },
+ { 0x20, 0x00f1, "VendorReserved2" },
+ { 0x20, 0x00f2, "VendorReserved3" },
+ { 0x20, 0x00f3, "VendorReserved4" },
+ { 0x20, 0x00f4, "VendorReserved5" },
+ { 0x20, 0x00f5, "VendorReserved6" },
+ { 0x20, 0x00f6, "VendorReserved7" },
+ { 0x20, 0x00f7, "VendorReserved8" },
+ { 0x20, 0x00f8, "VendorReserved9" },
+ { 0x20, 0x00f9, "VendorReserved10" },
+ { 0x20, 0x00fa, "VendorReserved11" },
+ { 0x20, 0x00fb, "VendorReserved12" },
+ { 0x20, 0x00fc, "VendorReserved13" },
+ { 0x20, 0x00fd, "VendorReserved14" },
+ { 0x20, 0x00fe, "VendorReserved15" },
+ { 0x20, 0x00ff, "VendorReserved16" },
+ { 0x20, 0x0200, "Event" },
+ { 0x20, 0x0201, "EventSensorState" },
+ { 0x20, 0x0202, "EventSensorEvent" },
+ { 0x20, 0x0300, "Property" },
+ { 0x20, 0x0301, "PropertyFriendlyName" },
+ { 0x20, 0x0302, "PropertyPersistentUniqueID" },
+ { 0x20, 0x0303, "PropertySensorStatus" },
+ { 0x20, 0x0304, "PropertyMinimumReportInterval" },
+ { 0x20, 0x0305, "PropertySensorManufacturer" },
+ { 0x20, 0x0306, "PropertySensorModel" },
+ { 0x20, 0x0307, "PropertySensorSerialNumber" },
+ { 0x20, 0x0308, "PropertySensorDescription" },
+ { 0x20, 0x0309, "PropertySensorConnectionType" },
+ { 0x20, 0x030a, "PropertySensorDevicePath" },
+ { 0x20, 0x030b, "PropertyHardwareRevision" },
+ { 0x20, 0x030c, "PropertyFirmwareVersion" },
+ { 0x20, 0x030d, "PropertyReleaseDate" },
+ { 0x20, 0x030e, "PropertyReportInterval" },
+ { 0x20, 0x030f, "PropertyChangeSensitivityAbsolute" },
+ { 0x20, 0x0310, "PropertyChangeSensitivityPercentofRange" },
+ { 0x20, 0x0311, "PropertyChangeSensitivityPercentRelative" },
+ { 0x20, 0x0312, "PropertyAccuracy" },
+ { 0x20, 0x0313, "PropertyResolution" },
+ { 0x20, 0x0314, "PropertyMaximum" },
+ { 0x20, 0x0315, "PropertyMinimum" },
+ { 0x20, 0x0316, "PropertyReportingState" },
+ { 0x20, 0x0317, "PropertySamplingRate" },
+ { 0x20, 0x0318, "PropertyResponseCurve" },
+ { 0x20, 0x0319, "PropertyPowerState" },
+ { 0x20, 0x031a, "PropertyMaximumFIFOEvents" },
+ { 0x20, 0x031b, "PropertyReportLatency" },
+ { 0x20, 0x031c, "PropertyFlushFIFOEvents" },
+ { 0x20, 0x031d, "PropertyMaximumPowerConsumption" },
+ { 0x20, 0x031e, "PropertyIsPrimary" },
+ { 0x20, 0x031f, "PropertyHumanPresenceDetectionType" },
+ { 0x20, 0x0400, "DataFieldLocation" },
+ { 0x20, 0x0402, "DataFieldAltitudeAntennaSeaLevel" },
+ { 0x20, 0x0403, "DataFieldDifferentialReferenceStationID" },
+ { 0x20, 0x0404, "DataFieldAltitudeEllipsoidError" },
+ { 0x20, 0x0405, "DataFieldAltitudeEllipsoid" },
+ { 0x20, 0x0406, "DataFieldAltitudeSeaLevelError" },
+ { 0x20, 0x0407, "DataFieldAltitudeSeaLevel" },
+ { 0x20, 0x0408, "DataFieldDifferentialGPSDataAge" },
+ { 0x20, 0x0409, "DataFieldErrorRadius" },
+ { 0x20, 0x040a, "DataFieldFixQuality" },
+ { 0x20, 0x040b, "DataFieldFixType" },
+ { 0x20, 0x040c, "DataFieldGeoidalSeparation" },
+ { 0x20, 0x040d, "DataFieldGPSOperationMode" },
+ { 0x20, 0x040e, "DataFieldGPSSelectionMode" },
+ { 0x20, 0x040f, "DataFieldGPSStatus" },
+ { 0x20, 0x0410, "DataFieldPositionDilutionofPrecision" },
+ { 0x20, 0x0411, "DataFieldHorizontalDilutionofPrecision" },
+ { 0x20, 0x0412, "DataFieldVerticalDilutionofPrecision" },
+ { 0x20, 0x0413, "DataFieldLatitude" },
+ { 0x20, 0x0414, "DataFieldLongitude" },
+ { 0x20, 0x0415, "DataFieldTrueHeading" },
+ { 0x20, 0x0416, "DataFieldMagneticHeading" },
+ { 0x20, 0x0417, "DataFieldMagneticVariation" },
+ { 0x20, 0x0418, "DataFieldSpeed" },
+ { 0x20, 0x0419, "DataFieldSatellitesinView" },
+ { 0x20, 0x041a, "DataFieldSatellitesinViewAzimuth" },
+ { 0x20, 0x041b, "DataFieldSatellitesinViewElevation" },
+ { 0x20, 0x041c, "DataFieldSatellitesinViewIDs" },
+ { 0x20, 0x041d, "DataFieldSatellitesinViewPRNs" },
+ { 0x20, 0x041e, "DataFieldSatellitesinViewSNRatios" },
+ { 0x20, 0x041f, "DataFieldSatellitesUsedCount" },
+ { 0x20, 0x0420, "DataFieldSatellitesUsedPRNs" },
+ { 0x20, 0x0421, "DataFieldNMEASentence" },
+ { 0x20, 0x0422, "DataFieldAddressLine1" },
+ { 0x20, 0x0423, "DataFieldAddressLine2" },
+ { 0x20, 0x0424, "DataFieldCity" },
+ { 0x20, 0x0425, "DataFieldStateorProvince" },
+ { 0x20, 0x0426, "DataFieldCountryorRegion" },
+ { 0x20, 0x0427, "DataFieldPostalCode" },
+ { 0x20, 0x042a, "PropertyLocation" },
+ { 0x20, 0x042b, "PropertyLocationDesiredAccuracy" },
+ { 0x20, 0x0430, "DataFieldEnvironmental" },
+ { 0x20, 0x0431, "DataFieldAtmosphericPressure" },
+ { 0x20, 0x0433, "DataFieldRelativeHumidity" },
+ { 0x20, 0x0434, "DataFieldTemperature" },
+ { 0x20, 0x0435, "DataFieldWindDirection" },
+ { 0x20, 0x0436, "DataFieldWindSpeed" },
+ { 0x20, 0x0437, "DataFieldAirQualityIndex" },
+ { 0x20, 0x0438, "DataFieldEquivalentCO2" },
+ { 0x20, 0x0439, "DataFieldVolatileOrganicCompoundConcentration" },
+ { 0x20, 0x043a, "DataFieldObjectPresence" },
+ { 0x20, 0x043b, "DataFieldObjectProximityRange" },
+ { 0x20, 0x043c, "DataFieldObjectProximityOutofRange" },
+ { 0x20, 0x0440, "PropertyEnvironmental" },
+ { 0x20, 0x0441, "PropertyReferencePressure" },
+ { 0x20, 0x0450, "DataFieldMotion" },
+ { 0x20, 0x0451, "DataFieldMotionState" },
+ { 0x20, 0x0452, "DataFieldAcceleration" },
+ { 0x20, 0x0453, "DataFieldAccelerationAxisX" },
+ { 0x20, 0x0454, "DataFieldAccelerationAxisY" },
+ { 0x20, 0x0455, "DataFieldAccelerationAxisZ" },
+ { 0x20, 0x0456, "DataFieldAngularVelocity" },
+ { 0x20, 0x0457, "DataFieldAngularVelocityaboutXAxis" },
+ { 0x20, 0x0458, "DataFieldAngularVelocityaboutYAxis" },
+ { 0x20, 0x0459, "DataFieldAngularVelocityaboutZAxis" },
+ { 0x20, 0x045a, "DataFieldAngularPosition" },
+ { 0x20, 0x045b, "DataFieldAngularPositionaboutXAxis" },
+ { 0x20, 0x045c, "DataFieldAngularPositionaboutYAxis" },
+ { 0x20, 0x045d, "DataFieldAngularPositionaboutZAxis" },
+ { 0x20, 0x045e, "DataFieldMotionSpeed" },
+ { 0x20, 0x045f, "DataFieldMotionIntensity" },
+ { 0x20, 0x0470, "DataFieldOrientation" },
+ { 0x20, 0x0471, "DataFieldHeading" },
+ { 0x20, 0x0472, "DataFieldHeadingXAxis" },
+ { 0x20, 0x0473, "DataFieldHeadingYAxis" },
+ { 0x20, 0x0474, "DataFieldHeadingZAxis" },
+ { 0x20, 0x0475, "DataFieldHeadingCompensatedMagneticNorth" },
+ { 0x20, 0x0476, "DataFieldHeadingCompensatedTrueNorth" },
+ { 0x20, 0x0477, "DataFieldHeadingMagneticNorth" },
+ { 0x20, 0x0478, "DataFieldHeadingTrueNorth" },
+ { 0x20, 0x0479, "DataFieldDistance" },
+ { 0x20, 0x047a, "DataFieldDistanceXAxis" },
+ { 0x20, 0x047b, "DataFieldDistanceYAxis" },
+ { 0x20, 0x047c, "DataFieldDistanceZAxis" },
+ { 0x20, 0x047d, "DataFieldDistanceOutofRange" },
+ { 0x20, 0x047e, "DataFieldTilt" },
+ { 0x20, 0x047f, "DataFieldTiltXAxis" },
+ { 0x20, 0x0480, "DataFieldTiltYAxis" },
+ { 0x20, 0x0481, "DataFieldTiltZAxis" },
+ { 0x20, 0x0482, "DataFieldRotationMatrix" },
+ { 0x20, 0x0483, "DataFieldQuaternion" },
+ { 0x20, 0x0484, "DataFieldMagneticFlux" },
+ { 0x20, 0x0485, "DataFieldMagneticFluxXAxis" },
+ { 0x20, 0x0486, "DataFieldMagneticFluxYAxis" },
+ { 0x20, 0x0487, "DataFieldMagneticFluxZAxis" },
+ { 0x20, 0x0488, "DataFieldMagnetometerAccuracy" },
+ { 0x20, 0x0489, "DataFieldSimpleOrientationDirection" },
+ { 0x20, 0x0490, "DataFieldMechanical" },
+ { 0x20, 0x0491, "DataFieldBooleanSwitchState" },
+ { 0x20, 0x0492, "DataFieldBooleanSwitchArrayStates" },
+ { 0x20, 0x0493, "DataFieldMultivalueSwitchValue" },
+ { 0x20, 0x0494, "DataFieldForce" },
+ { 0x20, 0x0495, "DataFieldAbsolutePressure" },
+ { 0x20, 0x0496, "DataFieldGaugePressure" },
+ { 0x20, 0x0497, "DataFieldStrain" },
+ { 0x20, 0x0498, "DataFieldWeight" },
+ { 0x20, 0x04a0, "PropertyMechanical" },
+ { 0x20, 0x04a1, "PropertyVibrationState" },
+ { 0x20, 0x04a2, "PropertyForwardVibrationSpeed" },
+ { 0x20, 0x04a3, "PropertyBackwardVibrationSpeed" },
+ { 0x20, 0x04b0, "DataFieldBiometric" },
+ { 0x20, 0x04b1, "DataFieldHumanPresence" },
+ { 0x20, 0x04b2, "DataFieldHumanProximityRange" },
+ { 0x20, 0x04b3, "DataFieldHumanProximityOutofRange" },
+ { 0x20, 0x04b4, "DataFieldHumanTouchState" },
+ { 0x20, 0x04b5, "DataFieldBloodPressure" },
+ { 0x20, 0x04b6, "DataFieldBloodPressureDiastolic" },
+ { 0x20, 0x04b7, "DataFieldBloodPressureSystolic" },
+ { 0x20, 0x04b8, "DataFieldHeartRate" },
+ { 0x20, 0x04b9, "DataFieldRestingHeartRate" },
+ { 0x20, 0x04ba, "DataFieldHeartbeatInterval" },
+ { 0x20, 0x04bb, "DataFieldRespiratoryRate" },
+ { 0x20, 0x04bc, "DataFieldSpO2" },
+ { 0x20, 0x04bd, "DataFieldHumanAttentionDetected" },
+ { 0x20, 0x04be, "DataFieldHumanHeadAzimuth" },
+ { 0x20, 0x04bf, "DataFieldHumanHeadAltitude" },
+ { 0x20, 0x04c0, "DataFieldHumanHeadRoll" },
+ { 0x20, 0x04c1, "DataFieldHumanHeadPitch" },
+ { 0x20, 0x04c2, "DataFieldHumanHeadYaw" },
+ { 0x20, 0x04c3, "DataFieldHumanCorrelationId" },
+ { 0x20, 0x04d0, "DataFieldLight" },
+ { 0x20, 0x04d1, "DataFieldIlluminance" },
+ { 0x20, 0x04d2, "DataFieldColorTemperature" },
+ { 0x20, 0x04d3, "DataFieldChromaticity" },
+ { 0x20, 0x04d4, "DataFieldChromaticityX" },
+ { 0x20, 0x04d5, "DataFieldChromaticityY" },
+ { 0x20, 0x04d6, "DataFieldConsumerIRSentenceReceive" },
+ { 0x20, 0x04d7, "DataFieldInfraredLight" },
+ { 0x20, 0x04d8, "DataFieldRedLight" },
+ { 0x20, 0x04d9, "DataFieldGreenLight" },
+ { 0x20, 0x04da, "DataFieldBlueLight" },
+ { 0x20, 0x04db, "DataFieldUltravioletALight" },
+ { 0x20, 0x04dc, "DataFieldUltravioletBLight" },
+ { 0x20, 0x04dd, "DataFieldUltravioletIndex" },
+ { 0x20, 0x04de, "DataFieldNearInfraredLight" },
+ { 0x20, 0x04df, "PropertyLight" },
+ { 0x20, 0x04e0, "PropertyConsumerIRSentenceSend" },
+ { 0x20, 0x04e2, "PropertyAutoBrightnessPreferred" },
+ { 0x20, 0x04e3, "PropertyAutoColorPreferred" },
+ { 0x20, 0x04f0, "DataFieldScanner" },
+ { 0x20, 0x04f1, "DataFieldRFIDTag40Bit" },
+ { 0x20, 0x04f2, "DataFieldNFCSentenceReceive" },
+ { 0x20, 0x04f8, "PropertyScanner" },
+ { 0x20, 0x04f9, "PropertyNFCSentenceSend" },
+ { 0x20, 0x0500, "DataFieldElectrical" },
+ { 0x20, 0x0501, "DataFieldCapacitance" },
+ { 0x20, 0x0502, "DataFieldCurrent" },
+ { 0x20, 0x0503, "DataFieldElectricalPower" },
+ { 0x20, 0x0504, "DataFieldInductance" },
+ { 0x20, 0x0505, "DataFieldResistance" },
+ { 0x20, 0x0506, "DataFieldVoltage" },
+ { 0x20, 0x0507, "DataFieldFrequency" },
+ { 0x20, 0x0508, "DataFieldPeriod" },
+ { 0x20, 0x0509, "DataFieldPercentofRange" },
+ { 0x20, 0x0520, "DataFieldTime" },
+ { 0x20, 0x0521, "DataFieldYear" },
+ { 0x20, 0x0522, "DataFieldMonth" },
+ { 0x20, 0x0523, "DataFieldDay" },
+ { 0x20, 0x0524, "DataFieldDayofWeek" },
+ { 0x20, 0x0525, "DataFieldHour" },
+ { 0x20, 0x0526, "DataFieldMinute" },
+ { 0x20, 0x0527, "DataFieldSecond" },
+ { 0x20, 0x0528, "DataFieldMillisecond" },
+ { 0x20, 0x0529, "DataFieldTimestamp" },
+ { 0x20, 0x052a, "DataFieldJulianDayofYear" },
+ { 0x20, 0x052b, "DataFieldTimeSinceSystemBoot" },
+ { 0x20, 0x0530, "PropertyTime" },
+ { 0x20, 0x0531, "PropertyTimeZoneOffsetfromUTC" },
+ { 0x20, 0x0532, "PropertyTimeZoneName" },
+ { 0x20, 0x0533, "PropertyDaylightSavingsTimeObserved" },
+ { 0x20, 0x0534, "PropertyTimeTrimAdjustment" },
+ { 0x20, 0x0535, "PropertyArmAlarm" },
+ { 0x20, 0x0540, "DataFieldCustom" },
+ { 0x20, 0x0541, "DataFieldCustomUsage" },
+ { 0x20, 0x0542, "DataFieldCustomBooleanArray" },
+ { 0x20, 0x0543, "DataFieldCustomValue" },
+ { 0x20, 0x0544, "DataFieldCustomValue1" },
+ { 0x20, 0x0545, "DataFieldCustomValue2" },
+ { 0x20, 0x0546, "DataFieldCustomValue3" },
+ { 0x20, 0x0547, "DataFieldCustomValue4" },
+ { 0x20, 0x0548, "DataFieldCustomValue5" },
+ { 0x20, 0x0549, "DataFieldCustomValue6" },
+ { 0x20, 0x054a, "DataFieldCustomValue7" },
+ { 0x20, 0x054b, "DataFieldCustomValue8" },
+ { 0x20, 0x054c, "DataFieldCustomValue9" },
+ { 0x20, 0x054d, "DataFieldCustomValue10" },
+ { 0x20, 0x054e, "DataFieldCustomValue11" },
+ { 0x20, 0x054f, "DataFieldCustomValue12" },
+ { 0x20, 0x0550, "DataFieldCustomValue13" },
+ { 0x20, 0x0551, "DataFieldCustomValue14" },
+ { 0x20, 0x0552, "DataFieldCustomValue15" },
+ { 0x20, 0x0553, "DataFieldCustomValue16" },
+ { 0x20, 0x0554, "DataFieldCustomValue17" },
+ { 0x20, 0x0555, "DataFieldCustomValue18" },
+ { 0x20, 0x0556, "DataFieldCustomValue19" },
+ { 0x20, 0x0557, "DataFieldCustomValue20" },
+ { 0x20, 0x0558, "DataFieldCustomValue21" },
+ { 0x20, 0x0559, "DataFieldCustomValue22" },
+ { 0x20, 0x055a, "DataFieldCustomValue23" },
+ { 0x20, 0x055b, "DataFieldCustomValue24" },
+ { 0x20, 0x055c, "DataFieldCustomValue25" },
+ { 0x20, 0x055d, "DataFieldCustomValue26" },
+ { 0x20, 0x055e, "DataFieldCustomValue27" },
+ { 0x20, 0x055f, "DataFieldCustomValue28" },
+ { 0x20, 0x0560, "DataFieldGeneric" },
+ { 0x20, 0x0561, "DataFieldGenericGUIDorPROPERTYKEY" },
+ { 0x20, 0x0562, "DataFieldGenericCategoryGUID" },
+ { 0x20, 0x0563, "DataFieldGenericTypeGUID" },
+ { 0x20, 0x0564, "DataFieldGenericEventPROPERTYKEY" },
+ { 0x20, 0x0565, "DataFieldGenericPropertyPROPERTYKEY" },
+ { 0x20, 0x0566, "DataFieldGenericDataFieldPROPERTYKEY" },
+ { 0x20, 0x0567, "DataFieldGenericEvent" },
+ { 0x20, 0x0568, "DataFieldGenericProperty" },
+ { 0x20, 0x0569, "DataFieldGenericDataField" },
+ { 0x20, 0x056a, "DataFieldEnumeratorTableRowIndex" },
+ { 0x20, 0x056b, "DataFieldEnumeratorTableRowCount" },
+ { 0x20, 0x056c, "DataFieldGenericGUIDorPROPERTYKEYkind" },
+ { 0x20, 0x056d, "DataFieldGenericGUID" },
+ { 0x20, 0x056e, "DataFieldGenericPROPERTYKEY" },
+ { 0x20, 0x056f, "DataFieldGenericTopLevelCollectionID" },
+ { 0x20, 0x0570, "DataFieldGenericReportID" },
+ { 0x20, 0x0571, "DataFieldGenericReportItemPositionIndex" },
+ { 0x20, 0x0572, "DataFieldGenericFirmwareVARTYPE" },
+ { 0x20, 0x0573, "DataFieldGenericUnitofMeasure" },
+ { 0x20, 0x0574, "DataFieldGenericUnitExponent" },
+ { 0x20, 0x0575, "DataFieldGenericReportSize" },
+ { 0x20, 0x0576, "DataFieldGenericReportCount" },
+ { 0x20, 0x0580, "PropertyGeneric" },
+ { 0x20, 0x0581, "PropertyEnumeratorTableRowIndex" },
+ { 0x20, 0x0582, "PropertyEnumeratorTableRowCount" },
+ { 0x20, 0x0590, "DataFieldPersonalActivity" },
+ { 0x20, 0x0591, "DataFieldActivityType" },
+ { 0x20, 0x0592, "DataFieldActivityState" },
+ { 0x20, 0x0593, "DataFieldDevicePosition" },
+ { 0x20, 0x0594, "DataFieldStepCount" },
+ { 0x20, 0x0595, "DataFieldStepCountReset" },
+ { 0x20, 0x0596, "DataFieldStepDuration" },
+ { 0x20, 0x0597, "DataFieldStepType" },
+ { 0x20, 0x05a0, "PropertyMinimumActivityDetectionInterval" },
+ { 0x20, 0x05a1, "PropertySupportedActivityTypes" },
+ { 0x20, 0x05a2, "PropertySubscribedActivityTypes" },
+ { 0x20, 0x05a3, "PropertySupportedStepTypes" },
+ { 0x20, 0x05a4, "PropertySubscribedStepTypes" },
+ { 0x20, 0x05a5, "PropertyFloorHeight" },
+ { 0x20, 0x05b0, "DataFieldCustomTypeID" },
+ { 0x20, 0x05c0, "PropertyCustom" },
+ { 0x20, 0x05c1, "PropertyCustomValue1" },
+ { 0x20, 0x05c2, "PropertyCustomValue2" },
+ { 0x20, 0x05c3, "PropertyCustomValue3" },
+ { 0x20, 0x05c4, "PropertyCustomValue4" },
+ { 0x20, 0x05c5, "PropertyCustomValue5" },
+ { 0x20, 0x05c6, "PropertyCustomValue6" },
+ { 0x20, 0x05c7, "PropertyCustomValue7" },
+ { 0x20, 0x05c8, "PropertyCustomValue8" },
+ { 0x20, 0x05c9, "PropertyCustomValue9" },
+ { 0x20, 0x05ca, "PropertyCustomValue10" },
+ { 0x20, 0x05cb, "PropertyCustomValue11" },
+ { 0x20, 0x05cc, "PropertyCustomValue12" },
+ { 0x20, 0x05cd, "PropertyCustomValue13" },
+ { 0x20, 0x05ce, "PropertyCustomValue14" },
+ { 0x20, 0x05cf, "PropertyCustomValue15" },
+ { 0x20, 0x05d0, "PropertyCustomValue16" },
+ { 0x20, 0x05e0, "DataFieldHinge" },
+ { 0x20, 0x05e1, "DataFieldHingeAngle" },
+ { 0x20, 0x05f0, "DataFieldGestureSensor" },
+ { 0x20, 0x05f1, "DataFieldGestureState" },
+ { 0x20, 0x05f2, "DataFieldHingeFoldInitialAngle" },
+ { 0x20, 0x05f3, "DataFieldHingeFoldFinalAngle" },
+ { 0x20, 0x05f4, "DataFieldHingeFoldContributingPanel" },
+ { 0x20, 0x05f5, "DataFieldHingeFoldType" },
+ { 0x20, 0x0800, "SensorStateUndefined" },
+ { 0x20, 0x0801, "SensorStateReady" },
+ { 0x20, 0x0802, "SensorStateNotAvailable" },
+ { 0x20, 0x0803, "SensorStateNoData" },
+ { 0x20, 0x0804, "SensorStateInitializing" },
+ { 0x20, 0x0805, "SensorStateAccessDenied" },
+ { 0x20, 0x0806, "SensorStateError" },
+ { 0x20, 0x0810, "SensorEventUnknown" },
+ { 0x20, 0x0811, "SensorEventStateChanged" },
+ { 0x20, 0x0812, "SensorEventPropertyChanged" },
+ { 0x20, 0x0813, "SensorEventDataUpdated" },
+ { 0x20, 0x0814, "SensorEventPollResponse" },
+ { 0x20, 0x0815, "SensorEventChangeSensitivity" },
+ { 0x20, 0x0816, "SensorEventRangeMaximumReached" },
+ { 0x20, 0x0817, "SensorEventRangeMinimumReached" },
+ { 0x20, 0x0818, "SensorEventHighThresholdCrossUpward" },
+ { 0x20, 0x0819, "SensorEventHighThresholdCrossDownward" },
+ { 0x20, 0x081a, "SensorEventLowThresholdCrossUpward" },
+ { 0x20, 0x081b, "SensorEventLowThresholdCrossDownward" },
+ { 0x20, 0x081c, "SensorEventZeroThresholdCrossUpward" },
+ { 0x20, 0x081d, "SensorEventZeroThresholdCrossDownward" },
+ { 0x20, 0x081e, "SensorEventPeriodExceeded" },
+ { 0x20, 0x081f, "SensorEventFrequencyExceeded" },
+ { 0x20, 0x0820, "SensorEventComplexTrigger" },
+ { 0x20, 0x0830, "ConnectionTypePCIntegrated" },
+ { 0x20, 0x0831, "ConnectionTypePCAttached" },
+ { 0x20, 0x0832, "ConnectionTypePCExternal" },
+ { 0x20, 0x0840, "ReportingStateReportNoEvents" },
+ { 0x20, 0x0841, "ReportingStateReportAllEvents" },
+ { 0x20, 0x0842, "ReportingStateReportThresholdEvents" },
+ { 0x20, 0x0843, "ReportingStateWakeOnNoEvents" },
+ { 0x20, 0x0844, "ReportingStateWakeOnAllEvents" },
+ { 0x20, 0x0845, "ReportingStateWakeOnThresholdEvents" },
+ { 0x20, 0x0846, "ReportingStateAnytime" },
+ { 0x20, 0x0850, "PowerStateUndefined" },
+ { 0x20, 0x0851, "PowerStateD0FullPower" },
+ { 0x20, 0x0852, "PowerStateD1LowPower" },
+ { 0x20, 0x0853, "PowerStateD2StandbyPowerwithWakeup" },
+ { 0x20, 0x0854, "PowerStateD3SleepwithWakeup" },
+ { 0x20, 0x0855, "PowerStateD4PowerOff" },
+ { 0x20, 0x0860, "AccuracyDefault" },
+ { 0x20, 0x0861, "AccuracyHigh" },
+ { 0x20, 0x0862, "AccuracyMedium" },
+ { 0x20, 0x0863, "AccuracyLow" },
+ { 0x20, 0x0870, "FixQualityNoFix" },
+ { 0x20, 0x0871, "FixQualityGPS" },
+ { 0x20, 0x0872, "FixQualityDGPS" },
+ { 0x20, 0x0880, "FixTypeNoFix" },
+ { 0x20, 0x0881, "FixTypeGPSSPSModeFixValid" },
+ { 0x20, 0x0882, "FixTypeDGPSSPSModeFixValid" },
+ { 0x20, 0x0883, "FixTypeGPSPPSModeFixValid" },
+ { 0x20, 0x0884, "FixTypeRealTimeKinematic" },
+ { 0x20, 0x0885, "FixTypeFloatRTK" },
+ { 0x20, 0x0886, "FixTypeEstimateddeadreckoned" },
+ { 0x20, 0x0887, "FixTypeManualInputMode" },
+ { 0x20, 0x0888, "FixTypeSimulatorMode" },
+ { 0x20, 0x0890, "GPSOperationModeManual" },
+ { 0x20, 0x0891, "GPSOperationModeAutomatic" },
+ { 0x20, 0x08a0, "GPSSelectionModeAutonomous" },
+ { 0x20, 0x08a1, "GPSSelectionModeDGPS" },
+ { 0x20, 0x08a2, "GPSSelectionModeEstimateddeadreckoned" },
+ { 0x20, 0x08a3, "GPSSelectionModeManualInput" },
+ { 0x20, 0x08a4, "GPSSelectionModeSimulator" },
+ { 0x20, 0x08a5, "GPSSelectionModeDataNotValid" },
+ { 0x20, 0x08b0, "GPSStatusDataValid" },
+ { 0x20, 0x08b1, "GPSStatusDataNotValid" },
+ { 0x20, 0x08c0, "DayofWeekSunday" },
+ { 0x20, 0x08c1, "DayofWeekMonday" },
+ { 0x20, 0x08c2, "DayofWeekTuesday" },
+ { 0x20, 0x08c3, "DayofWeekWednesday" },
+ { 0x20, 0x08c4, "DayofWeekThursday" },
+ { 0x20, 0x08c5, "DayofWeekFriday" },
+ { 0x20, 0x08c6, "DayofWeekSaturday" },
+ { 0x20, 0x08d0, "KindCategory" },
+ { 0x20, 0x08d1, "KindType" },
+ { 0x20, 0x08d2, "KindEvent" },
+ { 0x20, 0x08d3, "KindProperty" },
+ { 0x20, 0x08d4, "KindDataField" },
+ { 0x20, 0x08e0, "MagnetometerAccuracyLow" },
+ { 0x20, 0x08e1, "MagnetometerAccuracyMedium" },
+ { 0x20, 0x08e2, "MagnetometerAccuracyHigh" },
+ { 0x20, 0x08f0, "SimpleOrientationDirectionNotRotated" },
+ { 0x20, 0x08f1, "SimpleOrientationDirectionRotated90DegreesCCW" },
+ { 0x20, 0x08f2, "SimpleOrientationDirectionRotated180DegreesCCW" },
+ { 0x20, 0x08f3, "SimpleOrientationDirectionRotated270DegreesCCW" },
+ { 0x20, 0x08f4, "SimpleOrientationDirectionFaceUp" },
+ { 0x20, 0x08f5, "SimpleOrientationDirectionFaceDown" },
+ { 0x20, 0x0900, "VT_NULL" },
+ { 0x20, 0x0901, "VT_BOOL" },
+ { 0x20, 0x0902, "VT_UI1" },
+ { 0x20, 0x0903, "VT_I1" },
+ { 0x20, 0x0904, "VT_UI2" },
+ { 0x20, 0x0905, "VT_I2" },
+ { 0x20, 0x0906, "VT_UI4" },
+ { 0x20, 0x0907, "VT_I4" },
+ { 0x20, 0x0908, "VT_UI8" },
+ { 0x20, 0x0909, "VT_I8" },
+ { 0x20, 0x090a, "VT_R4" },
+ { 0x20, 0x090b, "VT_R8" },
+ { 0x20, 0x090c, "VT_WSTR" },
+ { 0x20, 0x090d, "VT_STR" },
+ { 0x20, 0x090e, "VT_CLSID" },
+ { 0x20, 0x090f, "VT_VECTORVT_UI1" },
+ { 0x20, 0x0910, "VT_F16E0" },
+ { 0x20, 0x0911, "VT_F16E1" },
+ { 0x20, 0x0912, "VT_F16E2" },
+ { 0x20, 0x0913, "VT_F16E3" },
+ { 0x20, 0x0914, "VT_F16E4" },
+ { 0x20, 0x0915, "VT_F16E5" },
+ { 0x20, 0x0916, "VT_F16E6" },
+ { 0x20, 0x0917, "VT_F16E7" },
+ { 0x20, 0x0918, "VT_F16E8" },
+ { 0x20, 0x0919, "VT_F16E9" },
+ { 0x20, 0x091a, "VT_F16EA" },
+ { 0x20, 0x091b, "VT_F16EB" },
+ { 0x20, 0x091c, "VT_F16EC" },
+ { 0x20, 0x091d, "VT_F16ED" },
+ { 0x20, 0x091e, "VT_F16EE" },
+ { 0x20, 0x091f, "VT_F16EF" },
+ { 0x20, 0x0920, "VT_F32E0" },
+ { 0x20, 0x0921, "VT_F32E1" },
+ { 0x20, 0x0922, "VT_F32E2" },
+ { 0x20, 0x0923, "VT_F32E3" },
+ { 0x20, 0x0924, "VT_F32E4" },
+ { 0x20, 0x0925, "VT_F32E5" },
+ { 0x20, 0x0926, "VT_F32E6" },
+ { 0x20, 0x0927, "VT_F32E7" },
+ { 0x20, 0x0928, "VT_F32E8" },
+ { 0x20, 0x0929, "VT_F32E9" },
+ { 0x20, 0x092a, "VT_F32EA" },
+ { 0x20, 0x092b, "VT_F32EB" },
+ { 0x20, 0x092c, "VT_F32EC" },
+ { 0x20, 0x092d, "VT_F32ED" },
+ { 0x20, 0x092e, "VT_F32EE" },
+ { 0x20, 0x092f, "VT_F32EF" },
+ { 0x20, 0x0930, "ActivityTypeUnknown" },
+ { 0x20, 0x0931, "ActivityTypeStationary" },
+ { 0x20, 0x0932, "ActivityTypeFidgeting" },
+ { 0x20, 0x0933, "ActivityTypeWalking" },
+ { 0x20, 0x0934, "ActivityTypeRunning" },
+ { 0x20, 0x0935, "ActivityTypeInVehicle" },
+ { 0x20, 0x0936, "ActivityTypeBiking" },
+ { 0x20, 0x0937, "ActivityTypeIdle" },
+ { 0x20, 0x0940, "UnitNotSpecified" },
+ { 0x20, 0x0941, "UnitLux" },
+ { 0x20, 0x0942, "UnitDegreesKelvin" },
+ { 0x20, 0x0943, "UnitDegreesCelsius" },
+ { 0x20, 0x0944, "UnitPascal" },
+ { 0x20, 0x0945, "UnitNewton" },
+ { 0x20, 0x0946, "UnitMetersSecond" },
+ { 0x20, 0x0947, "UnitKilogram" },
+ { 0x20, 0x0948, "UnitMeter" },
+ { 0x20, 0x0949, "UnitMetersSecondSecond" },
+ { 0x20, 0x094a, "UnitFarad" },
+ { 0x20, 0x094b, "UnitAmpere" },
+ { 0x20, 0x094c, "UnitWatt" },
+ { 0x20, 0x094d, "UnitHenry" },
+ { 0x20, 0x094e, "UnitOhm" },
+ { 0x20, 0x094f, "UnitVolt" },
+ { 0x20, 0x0950, "UnitHertz" },
+ { 0x20, 0x0951, "UnitBar" },
+ { 0x20, 0x0952, "UnitDegreesAnticlockwise" },
+ { 0x20, 0x0953, "UnitDegreesClockwise" },
+ { 0x20, 0x0954, "UnitDegrees" },
+ { 0x20, 0x0955, "UnitDegreesSecond" },
+ { 0x20, 0x0956, "UnitDegreesSecondSecond" },
+ { 0x20, 0x0957, "UnitKnot" },
+ { 0x20, 0x0958, "UnitPercent" },
+ { 0x20, 0x0959, "UnitSecond" },
+ { 0x20, 0x095a, "UnitMillisecond" },
+ { 0x20, 0x095b, "UnitG" },
+ { 0x20, 0x095c, "UnitBytes" },
+ { 0x20, 0x095d, "UnitMilligauss" },
+ { 0x20, 0x095e, "UnitBits" },
+ { 0x20, 0x0960, "ActivityStateNoStateChange" },
+ { 0x20, 0x0961, "ActivityStateStartActivity" },
+ { 0x20, 0x0962, "ActivityStateEndActivity" },
+ { 0x20, 0x0970, "Exponent0" },
+ { 0x20, 0x0971, "Exponent1" },
+ { 0x20, 0x0972, "Exponent2" },
+ { 0x20, 0x0973, "Exponent3" },
+ { 0x20, 0x0974, "Exponent4" },
+ { 0x20, 0x0975, "Exponent5" },
+ { 0x20, 0x0976, "Exponent6" },
+ { 0x20, 0x0977, "Exponent7" },
+ { 0x20, 0x0978, "Exponent8" },
+ { 0x20, 0x0979, "Exponent9" },
+ { 0x20, 0x097a, "ExponentA" },
+ { 0x20, 0x097b, "ExponentB" },
+ { 0x20, 0x097c, "ExponentC" },
+ { 0x20, 0x097d, "ExponentD" },
+ { 0x20, 0x097e, "ExponentE" },
+ { 0x20, 0x097f, "ExponentF" },
+ { 0x20, 0x0980, "DevicePositionUnknown" },
+ { 0x20, 0x0981, "DevicePositionUnchanged" },
+ { 0x20, 0x0982, "DevicePositionOnDesk" },
+ { 0x20, 0x0983, "DevicePositionInHand" },
+ { 0x20, 0x0984, "DevicePositionMovinginBag" },
+ { 0x20, 0x0985, "DevicePositionStationaryinBag" },
+ { 0x20, 0x0990, "StepTypeUnknown" },
+ { 0x20, 0x0991, "StepTypeWalking" },
+ { 0x20, 0x0992, "StepTypeRunning" },
+ { 0x20, 0x09a0, "GestureStateUnknown" },
+ { 0x20, 0x09a1, "GestureStateStarted" },
+ { 0x20, 0x09a2, "GestureStateCompleted" },
+ { 0x20, 0x09a3, "GestureStateCancelled" },
+ { 0x20, 0x09b0, "HingeFoldContributingPanelUnknown" },
+ { 0x20, 0x09b1, "HingeFoldContributingPanelPanel1" },
+ { 0x20, 0x09b2, "HingeFoldContributingPanelPanel2" },
+ { 0x20, 0x09b3, "HingeFoldContributingPanelBoth" },
+ { 0x20, 0x09b4, "HingeFoldTypeUnknown" },
+ { 0x20, 0x09b5, "HingeFoldTypeIncreasing" },
+ { 0x20, 0x09b6, "HingeFoldTypeDecreasing" },
+ { 0x20, 0x09c0, "HumanPresenceDetectionTypeVendorDefinedNonBiometric" },
+ { 0x20, 0x09c1, "HumanPresenceDetectionTypeVendorDefinedBiometric" },
+ { 0x20, 0x09c2, "HumanPresenceDetectionTypeFacialBiometric" },
+ { 0x20, 0x09c3, "HumanPresenceDetectionTypeAudioBiometric" },
+ { 0x20, 0x1000, "ModifierChangeSensitivityAbsolute" },
+ { 0x20, 0x2000, "ModifierMaximum" },
+ { 0x20, 0x3000, "ModifierMinimum" },
+ { 0x20, 0x4000, "ModifierAccuracy" },
+ { 0x20, 0x5000, "ModifierResolution" },
+ { 0x20, 0x6000, "ModifierThresholdHigh" },
+ { 0x20, 0x7000, "ModifierThresholdLow" },
+ { 0x20, 0x8000, "ModifierCalibrationOffset" },
+ { 0x20, 0x9000, "ModifierCalibrationMultiplier" },
+ { 0x20, 0xa000, "ModifierReportInterval" },
+ { 0x20, 0xb000, "ModifierFrequencyMax" },
+ { 0x20, 0xc000, "ModifierPeriodMax" },
+ { 0x20, 0xd000, "ModifierChangeSensitivityPercentofRange" },
+ { 0x20, 0xe000, "ModifierChangeSensitivityPercentRelative" },
+ { 0x20, 0xf000, "ModifierVendorReserved" },
+ { 0x40, 0, "MedicalInstrument" },
+ { 0x40, 0x0001, "MedicalUltrasound" },
+ { 0x40, 0x0020, "VCRAcquisition" },
+ { 0x40, 0x0021, "FreezeThaw" },
+ { 0x40, 0x0022, "ClipStore" },
+ { 0x40, 0x0023, "Update" },
+ { 0x40, 0x0024, "Next" },
+ { 0x40, 0x0025, "Save" },
+ { 0x40, 0x0026, "Print" },
+ { 0x40, 0x0027, "MicrophoneEnable" },
+ { 0x40, 0x0040, "Cine" },
+ { 0x40, 0x0041, "TransmitPower" },
+ { 0x40, 0x0042, "Volume" },
+ { 0x40, 0x0043, "Focus" },
+ { 0x40, 0x0044, "Depth" },
+ { 0x40, 0x0060, "SoftStepPrimary" },
+ { 0x40, 0x0061, "SoftStepSecondary" },
+ { 0x40, 0x0070, "DepthGainCompensation" },
+ { 0x40, 0x0080, "ZoomSelect" },
+ { 0x40, 0x0081, "ZoomAdjust" },
+ { 0x40, 0x0082, "SpectralDopplerModeSelect" },
+ { 0x40, 0x0083, "SpectralDopplerAdjust" },
+ { 0x40, 0x0084, "ColorDopplerModeSelect" },
+ { 0x40, 0x0085, "ColorDopplerAdjust" },
+ { 0x40, 0x0086, "MotionModeSelect" },
+ { 0x40, 0x0087, "MotionModeAdjust" },
+ { 0x40, 0x0088, "2DModeSelect" },
+ { 0x40, 0x0089, "2DModeAdjust" },
+ { 0x40, 0x00a0, "SoftControlSelect" },
+ { 0x40, 0x00a1, "SoftControlAdjust" },
+ { 0x41, 0, "BrailleDisplay" },
+ { 0x41, 0x0001, "BrailleDisplay" },
+ { 0x41, 0x0002, "BrailleRow" },
+ { 0x41, 0x0003, "8DotBrailleCell" },
+ { 0x41, 0x0004, "6DotBrailleCell" },
+ { 0x41, 0x0005, "NumberofBrailleCells" },
+ { 0x41, 0x0006, "ScreenReaderControl" },
+ { 0x41, 0x0007, "ScreenReaderIdentifier" },
+ { 0x41, 0x00fa, "RouterSet1" },
+ { 0x41, 0x00fb, "RouterSet2" },
+ { 0x41, 0x00fc, "RouterSet3" },
+ { 0x41, 0x0100, "RouterKey" },
+ { 0x41, 0x0101, "RowRouterKey" },
+ { 0x41, 0x0200, "BrailleButtons" },
+ { 0x41, 0x0201, "BrailleKeyboardDot1" },
+ { 0x41, 0x0202, "BrailleKeyboardDot2" },
+ { 0x41, 0x0203, "BrailleKeyboardDot3" },
+ { 0x41, 0x0204, "BrailleKeyboardDot4" },
+ { 0x41, 0x0205, "BrailleKeyboardDot5" },
+ { 0x41, 0x0206, "BrailleKeyboardDot6" },
+ { 0x41, 0x0207, "BrailleKeyboardDot7" },
+ { 0x41, 0x0208, "BrailleKeyboardDot8" },
+ { 0x41, 0x0209, "BrailleKeyboardSpace" },
+ { 0x41, 0x020a, "BrailleKeyboardLeftSpace" },
+ { 0x41, 0x020b, "BrailleKeyboardRightSpace" },
+ { 0x41, 0x020c, "BrailleFaceControls" },
+ { 0x41, 0x020d, "BrailleLeftControls" },
+ { 0x41, 0x020e, "BrailleRightControls" },
+ { 0x41, 0x020f, "BrailleTopControls" },
+ { 0x41, 0x0210, "BrailleJoystickCenter" },
+ { 0x41, 0x0211, "BrailleJoystickUp" },
+ { 0x41, 0x0212, "BrailleJoystickDown" },
+ { 0x41, 0x0213, "BrailleJoystickLeft" },
+ { 0x41, 0x0214, "BrailleJoystickRight" },
+ { 0x41, 0x0215, "BrailleDPadCenter" },
+ { 0x41, 0x0216, "BrailleDPadUp" },
+ { 0x41, 0x0217, "BrailleDPadDown" },
+ { 0x41, 0x0218, "BrailleDPadLeft" },
+ { 0x41, 0x0219, "BrailleDPadRight" },
+ { 0x41, 0x021a, "BraillePanLeft" },
+ { 0x41, 0x021b, "BraillePanRight" },
+ { 0x41, 0x021c, "BrailleRockerUp" },
+ { 0x41, 0x021d, "BrailleRockerDown" },
+ { 0x41, 0x021e, "BrailleRockerPress" },
+ { 0x59, 0, "LightingAndIllumination" },
+ { 0x59, 0x0001, "LampArray" },
+ { 0x59, 0x0002, "LampArrayAttributesReport" },
+ { 0x59, 0x0003, "LampCount" },
+ { 0x59, 0x0004, "BoundingBoxWidthInMicrometers" },
+ { 0x59, 0x0005, "BoundingBoxHeightInMicrometers" },
+ { 0x59, 0x0006, "BoundingBoxDepthInMicrometers" },
+ { 0x59, 0x0007, "LampArrayKind" },
+ { 0x59, 0x0008, "MinUpdateIntervalInMicroseconds" },
+ { 0x59, 0x0020, "LampAttributesRequestReport" },
+ { 0x59, 0x0021, "LampId" },
+ { 0x59, 0x0022, "LampAttributesResponseReport" },
+ { 0x59, 0x0023, "PositionXInMicrometers" },
+ { 0x59, 0x0024, "PositionYInMicrometers" },
+ { 0x59, 0x0025, "PositionZInMicrometers" },
+ { 0x59, 0x0026, "LampPurposes" },
+ { 0x59, 0x0027, "UpdateLatencyInMicroseconds" },
+ { 0x59, 0x0028, "RedLevelCount" },
+ { 0x59, 0x0029, "GreenLevelCount" },
+ { 0x59, 0x002a, "BlueLevelCount" },
+ { 0x59, 0x002b, "IntensityLevelCount" },
+ { 0x59, 0x002c, "IsProgrammable" },
+ { 0x59, 0x002d, "InputBinding" },
+ { 0x59, 0x0050, "LampMultiUpdateReport" },
+ { 0x59, 0x0051, "RedUpdateChannel" },
+ { 0x59, 0x0052, "GreenUpdateChannel" },
+ { 0x59, 0x0053, "BlueUpdateChannel" },
+ { 0x59, 0x0054, "IntensityUpdateChannel" },
+ { 0x59, 0x0055, "LampUpdateFlags" },
+ { 0x59, 0x0060, "LampRangeUpdateReport" },
+ { 0x59, 0x0061, "LampIdStart" },
+ { 0x59, 0x0062, "LampIdEnd" },
+ { 0x59, 0x0070, "LampArrayControlReport" },
+ { 0x59, 0x0071, "AutonomousMode" },
+ { 0x80, 0, "Monitor" },
+ { 0x80, 0x0001, "MonitorControl" },
+ { 0x80, 0x0002, "EDIDInformation" },
+ { 0x80, 0x0003, "VDIFInformation" },
+ { 0x80, 0x0004, "VESAVersion" },
+ { 0x81, 0, "MonitorEnumerated" },
+ { 0x82, 0, "VESAVirtualControls" },
+ { 0x82, 0x0001, "Degauss" },
+ { 0x82, 0x0010, "Brightness" },
+ { 0x82, 0x0012, "Contrast" },
+ { 0x82, 0x0016, "RedVideoGain" },
+ { 0x82, 0x0018, "GreenVideoGain" },
+ { 0x82, 0x001a, "BlueVideoGain" },
+ { 0x82, 0x001c, "Focus" },
+ { 0x82, 0x0020, "HorizontalPosition" },
+ { 0x82, 0x0022, "HorizontalSize" },
+ { 0x82, 0x0024, "HorizontalPincushion" },
+ { 0x82, 0x0026, "HorizontalPincushionBalance" },
+ { 0x82, 0x0028, "HorizontalMisconvergence" },
+ { 0x82, 0x002a, "HorizontalLinearity" },
+ { 0x82, 0x002c, "HorizontalLinearityBalance" },
+ { 0x82, 0x0030, "VerticalPosition" },
+ { 0x82, 0x0032, "VerticalSize" },
+ { 0x82, 0x0034, "VerticalPincushion" },
+ { 0x82, 0x0036, "VerticalPincushionBalance" },
+ { 0x82, 0x0038, "VerticalMisconvergence" },
+ { 0x82, 0x003a, "VerticalLinearity" },
+ { 0x82, 0x003c, "VerticalLinearityBalance" },
+ { 0x82, 0x0040, "ParallelogramDistortionKeyBalance" },
+ { 0x82, 0x0042, "TrapezoidalDistortionKey" },
+ { 0x82, 0x0044, "TiltRotation" },
+ { 0x82, 0x0046, "TopCornerDistortionControl" },
+ { 0x82, 0x0048, "TopCornerDistortionBalance" },
+ { 0x82, 0x004a, "BottomCornerDistortionControl" },
+ { 0x82, 0x004c, "BottomCornerDistortionBalance" },
+ { 0x82, 0x0056, "HorizontalMoire" },
+ { 0x82, 0x0058, "VerticalMoire" },
+ { 0x82, 0x005e, "InputLevelSelect" },
+ { 0x82, 0x0060, "InputSourceSelect" },
+ { 0x82, 0x006c, "RedVideoBlackLevel" },
+ { 0x82, 0x006e, "GreenVideoBlackLevel" },
+ { 0x82, 0x0070, "BlueVideoBlackLevel" },
+ { 0x82, 0x00a2, "AutoSizeCenter" },
+ { 0x82, 0x00a4, "PolarityHorizontalSynchronization" },
+ { 0x82, 0x00a6, "PolarityVerticalSynchronization" },
+ { 0x82, 0x00a8, "SynchronizationType" },
+ { 0x82, 0x00aa, "ScreenOrientation" },
+ { 0x82, 0x00ac, "HorizontalFrequency" },
+ { 0x82, 0x00ae, "VerticalFrequency" },
+ { 0x82, 0x00b0, "Settings" },
+ { 0x82, 0x00ca, "OnScreenDisplay" },
+ { 0x82, 0x00d4, "StereoMode" },
+ { 0x84, 0, "Power" },
+ { 0x84, 0x0001, "iName" },
+ { 0x84, 0x0002, "PresentStatus" },
+ { 0x84, 0x0003, "ChangedStatus" },
+ { 0x84, 0x0004, "UPS" },
+ { 0x84, 0x0005, "PowerSupply" },
+ { 0x84, 0x0010, "BatterySystem" },
+ { 0x84, 0x0011, "BatterySystemId" },
+ { 0x84, 0x0012, "Battery" },
+ { 0x84, 0x0013, "BatteryId" },
+ { 0x84, 0x0014, "Charger" },
+ { 0x84, 0x0015, "ChargerId" },
+ { 0x84, 0x0016, "PowerConverter" },
+ { 0x84, 0x0017, "PowerConverterId" },
+ { 0x84, 0x0018, "OutletSystem" },
+ { 0x84, 0x0019, "OutletSystemId" },
+ { 0x84, 0x001a, "Input" },
+ { 0x84, 0x001b, "InputId" },
+ { 0x84, 0x001c, "Output" },
+ { 0x84, 0x001d, "OutputId" },
+ { 0x84, 0x001e, "Flow" },
+ { 0x84, 0x001f, "FlowId" },
+ { 0x84, 0x0020, "Outlet" },
+ { 0x84, 0x0021, "OutletId" },
+ { 0x84, 0x0022, "Gang" },
+ { 0x84, 0x0023, "GangId" },
+ { 0x84, 0x0024, "PowerSummary" },
+ { 0x84, 0x0025, "PowerSummaryId" },
+ { 0x84, 0x0030, "Voltage" },
+ { 0x84, 0x0031, "Current" },
+ { 0x84, 0x0032, "Frequency" },
+ { 0x84, 0x0033, "ApparentPower" },
+ { 0x84, 0x0034, "ActivePower" },
+ { 0x84, 0x0035, "PercentLoad" },
+ { 0x84, 0x0036, "Temperature" },
+ { 0x84, 0x0037, "Humidity" },
+ { 0x84, 0x0038, "BadCount" },
+ { 0x84, 0x0040, "ConfigVoltage" },
+ { 0x84, 0x0041, "ConfigCurrent" },
+ { 0x84, 0x0042, "ConfigFrequency" },
+ { 0x84, 0x0043, "ConfigApparentPower" },
+ { 0x84, 0x0044, "ConfigActivePower" },
+ { 0x84, 0x0045, "ConfigPercentLoad" },
+ { 0x84, 0x0046, "ConfigTemperature" },
+ { 0x84, 0x0047, "ConfigHumidity" },
+ { 0x84, 0x0050, "SwitchOnControl" },
+ { 0x84, 0x0051, "SwitchOffControl" },
+ { 0x84, 0x0052, "ToggleControl" },
+ { 0x84, 0x0053, "LowVoltageTransfer" },
+ { 0x84, 0x0054, "HighVoltageTransfer" },
+ { 0x84, 0x0055, "DelayBeforeReboot" },
+ { 0x84, 0x0056, "DelayBeforeStartup" },
+ { 0x84, 0x0057, "DelayBeforeShutdown" },
+ { 0x84, 0x0058, "Test" },
+ { 0x84, 0x0059, "ModuleReset" },
+ { 0x84, 0x005a, "AudibleAlarmControl" },
+ { 0x84, 0x0060, "Present" },
+ { 0x84, 0x0061, "Good" },
+ { 0x84, 0x0062, "InternalFailure" },
+ { 0x84, 0x0063, "VoltagOutOfRange" },
+ { 0x84, 0x0064, "FrequencyOutOfRange" },
+ { 0x84, 0x0065, "Overload" },
+ { 0x84, 0x0066, "OverCharged" },
+ { 0x84, 0x0067, "OverTemperature" },
+ { 0x84, 0x0068, "ShutdownRequested" },
+ { 0x84, 0x0069, "ShutdownImminent" },
+ { 0x84, 0x006b, "SwitchOnOff" },
+ { 0x84, 0x006c, "Switchable" },
+ { 0x84, 0x006d, "Used" },
+ { 0x84, 0x006e, "Boost" },
+ { 0x84, 0x006f, "Buck" },
+ { 0x84, 0x0070, "Initialized" },
+ { 0x84, 0x0071, "Tested" },
+ { 0x84, 0x0072, "AwaitingPower" },
+ { 0x84, 0x0073, "CommunicationLost" },
+ { 0x84, 0x00fd, "iManufacturer" },
+ { 0x84, 0x00fe, "iProduct" },
+ { 0x84, 0x00ff, "iSerialNumber" },
+ { 0x85, 0, "BatterySystem" },
+ { 0x85, 0x0001, "SmartBatteryBatteryMode" },
+ { 0x85, 0x0002, "SmartBatteryBatteryStatus" },
+ { 0x85, 0x0003, "SmartBatteryAlarmWarning" },
+ { 0x85, 0x0004, "SmartBatteryChargerMode" },
+ { 0x85, 0x0005, "SmartBatteryChargerStatus" },
+ { 0x85, 0x0006, "SmartBatteryChargerSpecInfo" },
+ { 0x85, 0x0007, "SmartBatterySelectorState" },
+ { 0x85, 0x0008, "SmartBatterySelectorPresets" },
+ { 0x85, 0x0009, "SmartBatterySelectorInfo" },
+ { 0x85, 0x0010, "OptionalMfgFunction1" },
+ { 0x85, 0x0011, "OptionalMfgFunction2" },
+ { 0x85, 0x0012, "OptionalMfgFunction3" },
+ { 0x85, 0x0013, "OptionalMfgFunction4" },
+ { 0x85, 0x0014, "OptionalMfgFunction5" },
+ { 0x85, 0x0015, "ConnectionToSMBus" },
+ { 0x85, 0x0016, "OutputConnection" },
+ { 0x85, 0x0017, "ChargerConnection" },
+ { 0x85, 0x0018, "BatteryInsertion" },
+ { 0x85, 0x0019, "UseNext" },
+ { 0x85, 0x001a, "OKToUse" },
+ { 0x85, 0x001b, "BatterySupported" },
+ { 0x85, 0x001c, "SelectorRevision" },
+ { 0x85, 0x001d, "ChargingIndicator" },
+ { 0x85, 0x0028, "ManufacturerAccess" },
+ { 0x85, 0x0029, "RemainingCapacityLimit" },
+ { 0x85, 0x002a, "RemainingTimeLimit" },
+ { 0x85, 0x002b, "AtRate" },
+ { 0x85, 0x002c, "CapacityMode" },
+ { 0x85, 0x002d, "BroadcastToCharger" },
+ { 0x85, 0x002e, "PrimaryBattery" },
+ { 0x85, 0x002f, "ChargeController" },
+ { 0x85, 0x0040, "TerminateCharge" },
+ { 0x85, 0x0041, "TerminateDischarge" },
+ { 0x85, 0x0042, "BelowRemainingCapacityLimit" },
+ { 0x85, 0x0043, "RemainingTimeLimitExpired" },
+ { 0x85, 0x0044, "Charging" },
+ { 0x85, 0x0045, "Discharging" },
+ { 0x85, 0x0046, "FullyCharged" },
+ { 0x85, 0x0047, "FullyDischarged" },
+ { 0x85, 0x0048, "ConditioningFlag" },
+ { 0x85, 0x0049, "AtRateOK" },
+ { 0x85, 0x004a, "SmartBatteryErrorCode" },
+ { 0x85, 0x004b, "NeedReplacement" },
+ { 0x85, 0x0060, "AtRateTimeToFull" },
+ { 0x85, 0x0061, "AtRateTimeToEmpty" },
+ { 0x85, 0x0062, "AverageCurrent" },
+ { 0x85, 0x0063, "MaxError" },
+ { 0x85, 0x0064, "RelativeStateOfCharge" },
+ { 0x85, 0x0065, "AbsoluteStateOfCharge" },
+ { 0x85, 0x0066, "RemainingCapacity" },
+ { 0x85, 0x0067, "FullChargeCapacity" },
+ { 0x85, 0x0068, "RunTimeToEmpty" },
+ { 0x85, 0x0069, "AverageTimeToEmpty" },
+ { 0x85, 0x006a, "AverageTimeToFull" },
+ { 0x85, 0x006b, "CycleCount" },
+ { 0x85, 0x0080, "BatteryPackModelLevel" },
+ { 0x85, 0x0081, "InternalChargeController" },
+ { 0x85, 0x0082, "PrimaryBatterySupport" },
+ { 0x85, 0x0083, "DesignCapacity" },
+ { 0x85, 0x0084, "SpecificationInfo" },
+ { 0x85, 0x0085, "ManufactureDate" },
+ { 0x85, 0x0086, "SerialNumber" },
+ { 0x85, 0x0087, "iManufacturerName" },
+ { 0x85, 0x0088, "iDeviceName" },
+ { 0x85, 0x0089, "iDeviceChemistry" },
+ { 0x85, 0x008a, "ManufacturerData" },
+ { 0x85, 0x008b, "Rechargeable" },
+ { 0x85, 0x008c, "WarningCapacityLimit" },
+ { 0x85, 0x008d, "CapacityGranularity1" },
+ { 0x85, 0x008e, "CapacityGranularity2" },
+ { 0x85, 0x008f, "iOEMInformation" },
+ { 0x85, 0x00c0, "InhibitCharge" },
+ { 0x85, 0x00c1, "EnablePolling" },
+ { 0x85, 0x00c2, "ResetToZero" },
+ { 0x85, 0x00d0, "ACPresent" },
+ { 0x85, 0x00d1, "BatteryPresent" },
+ { 0x85, 0x00d2, "PowerFail" },
+ { 0x85, 0x00d3, "AlarmInhibited" },
+ { 0x85, 0x00d4, "ThermistorUnderRange" },
+ { 0x85, 0x00d5, "ThermistorHot" },
+ { 0x85, 0x00d6, "ThermistorCold" },
+ { 0x85, 0x00d7, "ThermistorOverRange" },
+ { 0x85, 0x00d8, "VoltageOutOfRange" },
+ { 0x85, 0x00d9, "CurrentOutOfRange" },
+ { 0x85, 0x00da, "CurrentNotRegulated" },
+ { 0x85, 0x00db, "VoltageNotRegulated" },
+ { 0x85, 0x00dc, "MasterMode" },
+ { 0x85, 0x00f0, "ChargerSelectorSupport" },
+ { 0x85, 0x00f1, "ChargerSpec" },
+ { 0x85, 0x00f2, "Level2" },
+ { 0x85, 0x00f3, "Level3" },
+ { 0x8c, 0, "BarcodeScanner" },
+ { 0x8c, 0x0001, "BarcodeBadgeReader" },
+ { 0x8c, 0x0002, "BarcodeScanner" },
+ { 0x8c, 0x0003, "DumbBarCodeScanner" },
+ { 0x8c, 0x0004, "CordlessScannerBase" },
+ { 0x8c, 0x0005, "BarCodeScannerCradle" },
+ { 0x8c, 0x0010, "AttributeReport" },
+ { 0x8c, 0x0011, "SettingsReport" },
+ { 0x8c, 0x0012, "ScannedDataReport" },
+ { 0x8c, 0x0013, "RawScannedDataReport" },
+ { 0x8c, 0x0014, "TriggerReport" },
+ { 0x8c, 0x0015, "StatusReport" },
+ { 0x8c, 0x0016, "UPCEANControlReport" },
+ { 0x8c, 0x0017, "EAN23LabelControlReport" },
+ { 0x8c, 0x0018, "Code39ControlReport" },
+ { 0x8c, 0x0019, "Interleaved2of5ControlReport" },
+ { 0x8c, 0x001a, "Standard2of5ControlReport" },
+ { 0x8c, 0x001b, "MSIPlesseyControlReport" },
+ { 0x8c, 0x001c, "CodabarControlReport" },
+ { 0x8c, 0x001d, "Code128ControlReport" },
+ { 0x8c, 0x001e, "Misc1DControlReport" },
+ { 0x8c, 0x001f, "2DControlReport" },
+ { 0x8c, 0x0030, "AimingPointerMode" },
+ { 0x8c, 0x0031, "BarCodePresentSensor" },
+ { 0x8c, 0x0032, "Class1ALaser" },
+ { 0x8c, 0x0033, "Class2Laser" },
+ { 0x8c, 0x0034, "HeaterPresent" },
+ { 0x8c, 0x0035, "ContactScanner" },
+ { 0x8c, 0x0036, "ElectronicArticleSurveillanceNotification" },
+ { 0x8c, 0x0037, "ConstantElectronicArticleSurveillance" },
+ { 0x8c, 0x0038, "ErrorIndication" },
+ { 0x8c, 0x0039, "FixedBeeper" },
+ { 0x8c, 0x003a, "GoodDecodeIndication" },
+ { 0x8c, 0x003b, "HandsFreeScanning" },
+ { 0x8c, 0x003c, "IntrinsicallySafe" },
+ { 0x8c, 0x003d, "KlasseEinsLaser" },
+ { 0x8c, 0x003e, "LongRangeScanner" },
+ { 0x8c, 0x003f, "MirrorSpeedControl" },
+ { 0x8c, 0x0040, "NotOnFileIndication" },
+ { 0x8c, 0x0041, "ProgrammableBeeper" },
+ { 0x8c, 0x0042, "Triggerless" },
+ { 0x8c, 0x0043, "Wand" },
+ { 0x8c, 0x0044, "WaterResistant" },
+ { 0x8c, 0x0045, "MultiRangeScanner" },
+ { 0x8c, 0x0046, "ProximitySensor" },
+ { 0x8c, 0x004d, "FragmentDecoding" },
+ { 0x8c, 0x004e, "ScannerReadConfidence" },
+ { 0x8c, 0x004f, "DataPrefix" },
+ { 0x8c, 0x0050, "PrefixAIMI" },
+ { 0x8c, 0x0051, "PrefixNone" },
+ { 0x8c, 0x0052, "PrefixProprietary" },
+ { 0x8c, 0x0055, "ActiveTime" },
+ { 0x8c, 0x0056, "AimingLaserPattern" },
+ { 0x8c, 0x0057, "BarCodePresent" },
+ { 0x8c, 0x0058, "BeeperState" },
+ { 0x8c, 0x0059, "LaserOnTime" },
+ { 0x8c, 0x005a, "LaserState" },
+ { 0x8c, 0x005b, "LockoutTime" },
+ { 0x8c, 0x005c, "MotorState" },
+ { 0x8c, 0x005d, "MotorTimeout" },
+ { 0x8c, 0x005e, "PowerOnResetScanner" },
+ { 0x8c, 0x005f, "PreventReadofBarcodes" },
+ { 0x8c, 0x0060, "InitiateBarcodeRead" },
+ { 0x8c, 0x0061, "TriggerState" },
+ { 0x8c, 0x0062, "TriggerMode" },
+ { 0x8c, 0x0063, "TriggerModeBlinkingLaserOn" },
+ { 0x8c, 0x0064, "TriggerModeContinuousLaserOn" },
+ { 0x8c, 0x0065, "TriggerModeLaseronwhilePulled" },
+ { 0x8c, 0x0066, "TriggerModeLaserstaysonafterrelease" },
+ { 0x8c, 0x006d, "CommitParameterstoNVM" },
+ { 0x8c, 0x006e, "ParameterScanning" },
+ { 0x8c, 0x006f, "ParametersChanged" },
+ { 0x8c, 0x0070, "Setparameterdefaultvalues" },
+ { 0x8c, 0x0075, "ScannerInCradle" },
+ { 0x8c, 0x0076, "ScannerInRange" },
+ { 0x8c, 0x007a, "AimDuration" },
+ { 0x8c, 0x007b, "GoodReadLampDuration" },
+ { 0x8c, 0x007c, "GoodReadLampIntensity" },
+ { 0x8c, 0x007d, "GoodReadLED" },
+ { 0x8c, 0x007e, "GoodReadToneFrequency" },
+ { 0x8c, 0x007f, "GoodReadToneLength" },
+ { 0x8c, 0x0080, "GoodReadToneVolume" },
+ { 0x8c, 0x0082, "NoReadMessage" },
+ { 0x8c, 0x0083, "NotonFileVolume" },
+ { 0x8c, 0x0084, "PowerupBeep" },
+ { 0x8c, 0x0085, "SoundErrorBeep" },
+ { 0x8c, 0x0086, "SoundGoodReadBeep" },
+ { 0x8c, 0x0087, "SoundNotOnFileBeep" },
+ { 0x8c, 0x0088, "GoodReadWhentoWrite" },
+ { 0x8c, 0x0089, "GRWTIAfterDecode" },
+ { 0x8c, 0x008a, "GRWTIBeepLampaftertransmit" },
+ { 0x8c, 0x008b, "GRWTINoBeepLampuseatall" },
+ { 0x8c, 0x0091, "BooklandEAN" },
+ { 0x8c, 0x0092, "ConvertEAN8to13Type" },
+ { 0x8c, 0x0093, "ConvertUPCAtoEAN13" },
+ { 0x8c, 0x0094, "ConvertUPCEtoA" },
+ { 0x8c, 0x0095, "EAN13" },
+ { 0x8c, 0x0096, "EAN8" },
+ { 0x8c, 0x0097, "EAN99128Mandatory" },
+ { 0x8c, 0x0098, "EAN99P5128Optional" },
+ { 0x8c, 0x0099, "EnableEANTwoLabel" },
+ { 0x8c, 0x009a, "UPCEAN" },
+ { 0x8c, 0x009b, "UPCEANCouponCode" },
+ { 0x8c, 0x009c, "UPCEANPeriodicals" },
+ { 0x8c, 0x009d, "UPCA" },
+ { 0x8c, 0x009e, "UPCAwith128Mandatory" },
+ { 0x8c, 0x009f, "UPCAwith128Optional" },
+ { 0x8c, 0x00a0, "UPCAwithP5Optional" },
+ { 0x8c, 0x00a1, "UPCE" },
+ { 0x8c, 0x00a2, "UPCE1" },
+ { 0x8c, 0x00a9, "Periodical" },
+ { 0x8c, 0x00aa, "PeriodicalAutoDiscriminate2" },
+ { 0x8c, 0x00ab, "PeriodicalOnlyDecodewith2" },
+ { 0x8c, 0x00ac, "PeriodicalIgnore2" },
+ { 0x8c, 0x00ad, "PeriodicalAutoDiscriminate5" },
+ { 0x8c, 0x00ae, "PeriodicalOnlyDecodewith5" },
+ { 0x8c, 0x00af, "PeriodicalIgnore5" },
+ { 0x8c, 0x00b0, "Check" },
+ { 0x8c, 0x00b1, "CheckDisablePrice" },
+ { 0x8c, 0x00b2, "CheckEnable4digitPrice" },
+ { 0x8c, 0x00b3, "CheckEnable5digitPrice" },
+ { 0x8c, 0x00b4, "CheckEnableEuropean4digitPrice" },
+ { 0x8c, 0x00b5, "CheckEnableEuropean5digitPrice" },
+ { 0x8c, 0x00b7, "EANTwoLabel" },
+ { 0x8c, 0x00b8, "EANThreeLabel" },
+ { 0x8c, 0x00b9, "EAN8FlagDigit1" },
+ { 0x8c, 0x00ba, "EAN8FlagDigit2" },
+ { 0x8c, 0x00bb, "EAN8FlagDigit3" },
+ { 0x8c, 0x00bc, "EAN13FlagDigit1" },
+ { 0x8c, 0x00bd, "EAN13FlagDigit2" },
+ { 0x8c, 0x00be, "EAN13FlagDigit3" },
+ { 0x8c, 0x00bf, "AddEAN23LabelDefinition" },
+ { 0x8c, 0x00c0, "ClearallEAN23LabelDefinitions" },
+ { 0x8c, 0x00c3, "Codabar" },
+ { 0x8c, 0x00c4, "Code128" },
+ { 0x8c, 0x00c7, "Code39" },
+ { 0x8c, 0x00c8, "Code93" },
+ { 0x8c, 0x00c9, "FullASCIIConversion" },
+ { 0x8c, 0x00ca, "Interleaved2of5" },
+ { 0x8c, 0x00cb, "ItalianPharmacyCode" },
+ { 0x8c, 0x00cc, "MSIPlessey" },
+ { 0x8c, 0x00cd, "Standard2of5IATA" },
+ { 0x8c, 0x00ce, "Standard2of5" },
+ { 0x8c, 0x00d3, "TransmitStartStop" },
+ { 0x8c, 0x00d4, "TriOptic" },
+ { 0x8c, 0x00d5, "UCCEAN128" },
+ { 0x8c, 0x00d6, "CheckDigit" },
+ { 0x8c, 0x00d7, "CheckDigitDisable" },
+ { 0x8c, 0x00d8, "CheckDigitEnableInterleaved2of5OPCC" },
+ { 0x8c, 0x00d9, "CheckDigitEnableInterleaved2of5USS" },
+ { 0x8c, 0x00da, "CheckDigitEnableStandard2of5OPCC" },
+ { 0x8c, 0x00db, "CheckDigitEnableStandard2of5USS" },
+ { 0x8c, 0x00dc, "CheckDigitEnableOneMSIPlessey" },
+ { 0x8c, 0x00dd, "CheckDigitEnableTwoMSIPlessey" },
+ { 0x8c, 0x00de, "CheckDigitCodabarEnable" },
+ { 0x8c, 0x00df, "CheckDigitCode39Enable" },
+ { 0x8c, 0x00f0, "TransmitCheckDigit" },
+ { 0x8c, 0x00f1, "DisableCheckDigitTransmit" },
+ { 0x8c, 0x00f2, "EnableCheckDigitTransmit" },
+ { 0x8c, 0x00fb, "SymbologyIdentifier1" },
+ { 0x8c, 0x00fc, "SymbologyIdentifier2" },
+ { 0x8c, 0x00fd, "SymbologyIdentifier3" },
+ { 0x8c, 0x00fe, "DecodedData" },
+ { 0x8c, 0x00ff, "DecodeDataContinued" },
+ { 0x8c, 0x0100, "BarSpaceData" },
+ { 0x8c, 0x0101, "ScannerDataAccuracy" },
+ { 0x8c, 0x0102, "RawDataPolarity" },
+ { 0x8c, 0x0103, "PolarityInvertedBarCode" },
+ { 0x8c, 0x0104, "PolarityNormalBarCode" },
+ { 0x8c, 0x0106, "MinimumLengthtoDecode" },
+ { 0x8c, 0x0107, "MaximumLengthtoDecode" },
+ { 0x8c, 0x0108, "DiscreteLengthtoDecode1" },
+ { 0x8c, 0x0109, "DiscreteLengthtoDecode2" },
+ { 0x8c, 0x010a, "DataLengthMethod" },
+ { 0x8c, 0x010b, "DLMethodReadany" },
+ { 0x8c, 0x010c, "DLMethodCheckinRange" },
+ { 0x8c, 0x010d, "DLMethodCheckforDiscrete" },
+ { 0x8c, 0x0110, "AztecCode" },
+ { 0x8c, 0x0111, "BC412" },
+ { 0x8c, 0x0112, "ChannelCode" },
+ { 0x8c, 0x0113, "Code16" },
+ { 0x8c, 0x0114, "Code32" },
+ { 0x8c, 0x0115, "Code49" },
+ { 0x8c, 0x0116, "CodeOne" },
+ { 0x8c, 0x0117, "Colorcode" },
+ { 0x8c, 0x0118, "DataMatrix" },
+ { 0x8c, 0x0119, "MaxiCode" },
+ { 0x8c, 0x011a, "MicroPDF" },
+ { 0x8c, 0x011b, "PDF417" },
+ { 0x8c, 0x011c, "PosiCode" },
+ { 0x8c, 0x011d, "QRCode" },
+ { 0x8c, 0x011e, "SuperCode" },
+ { 0x8c, 0x011f, "UltraCode" },
+ { 0x8c, 0x0120, "USD5SlugCode" },
+ { 0x8c, 0x0121, "VeriCode" },
+ { 0x8d, 0, "Scales" },
+ { 0x8d, 0x0001, "Scales" },
+ { 0x8d, 0x0020, "ScaleDevice" },
+ { 0x8d, 0x0021, "ScaleClass" },
+ { 0x8d, 0x0022, "ScaleClassIMetric" },
+ { 0x8d, 0x0023, "ScaleClassIIMetric" },
+ { 0x8d, 0x0024, "ScaleClassIIIMetric" },
+ { 0x8d, 0x0025, "ScaleClassIIILMetric" },
+ { 0x8d, 0x0026, "ScaleClassIVMetric" },
+ { 0x8d, 0x0027, "ScaleClassIIIEnglish" },
+ { 0x8d, 0x0028, "ScaleClassIIILEnglish" },
+ { 0x8d, 0x0029, "ScaleClassIVEnglish" },
+ { 0x8d, 0x002a, "ScaleClassGeneric" },
+ { 0x8d, 0x0030, "ScaleAttributeReport" },
+ { 0x8d, 0x0031, "ScaleControlReport" },
+ { 0x8d, 0x0032, "ScaleDataReport" },
+ { 0x8d, 0x0033, "ScaleStatusReport" },
+ { 0x8d, 0x0034, "ScaleWeightLimitReport" },
+ { 0x8d, 0x0035, "ScaleStatisticsReport" },
+ { 0x8d, 0x0040, "DataWeight" },
+ { 0x8d, 0x0041, "DataScaling" },
+ { 0x8d, 0x0050, "WeightUnit" },
+ { 0x8d, 0x0051, "WeightUnitMilligram" },
+ { 0x8d, 0x0052, "WeightUnitGram" },
+ { 0x8d, 0x0053, "WeightUnitKilogram" },
+ { 0x8d, 0x0054, "WeightUnitCarats" },
+ { 0x8d, 0x0055, "WeightUnitTaels" },
+ { 0x8d, 0x0056, "WeightUnitGrains" },
+ { 0x8d, 0x0057, "WeightUnitPennyweights" },
+ { 0x8d, 0x0058, "WeightUnitMetricTon" },
+ { 0x8d, 0x0059, "WeightUnitAvoirTon" },
+ { 0x8d, 0x005a, "WeightUnitTroyOunce" },
+ { 0x8d, 0x005b, "WeightUnitOunce" },
+ { 0x8d, 0x005c, "WeightUnitPound" },
+ { 0x8d, 0x0060, "CalibrationCount" },
+ { 0x8d, 0x0061, "ReZeroCount" },
+ { 0x8d, 0x0070, "ScaleStatus" },
+ { 0x8d, 0x0071, "ScaleStatusFault" },
+ { 0x8d, 0x0072, "ScaleStatusStableatCenterofZero" },
+ { 0x8d, 0x0073, "ScaleStatusInMotion" },
+ { 0x8d, 0x0074, "ScaleStatusWeightStable" },
+ { 0x8d, 0x0075, "ScaleStatusUnderZero" },
+ { 0x8d, 0x0076, "ScaleStatusOverWeightLimit" },
+ { 0x8d, 0x0077, "ScaleStatusRequiresCalibration" },
+ { 0x8d, 0x0078, "ScaleStatusRequiresRezeroing" },
+ { 0x8d, 0x0080, "ZeroScale" },
+ { 0x8d, 0x0081, "EnforcedZeroReturn" },
+ { 0x8e, 0, "MagneticStripeReader" },
+ { 0x8e, 0x0001, "MSRDeviceReadOnly" },
+ { 0x8e, 0x0011, "Track1Length" },
+ { 0x8e, 0x0012, "Track2Length" },
+ { 0x8e, 0x0013, "Track3Length" },
+ { 0x8e, 0x0014, "TrackJISLength" },
+ { 0x8e, 0x0020, "TrackData" },
+ { 0x8e, 0x0021, "Track1Data" },
+ { 0x8e, 0x0022, "Track2Data" },
+ { 0x8e, 0x0023, "Track3Data" },
+ { 0x8e, 0x0024, "TrackJISData" },
+ { 0x90, 0, "CameraControl" },
+ { 0x90, 0x0020, "CameraAutofocus" },
+ { 0x90, 0x0021, "CameraShutter" },
+ { 0x91, 0, "Arcade" },
+ { 0x91, 0x0001, "GeneralPurposeIOCard" },
+ { 0x91, 0x0002, "CoinDoor" },
+ { 0x91, 0x0003, "WatchdogTimer" },
+ { 0x91, 0x0030, "GeneralPurposeAnalogInputState" },
+ { 0x91, 0x0031, "GeneralPurposeDigitalInputState" },
+ { 0x91, 0x0032, "GeneralPurposeOpticalInputState" },
+ { 0x91, 0x0033, "GeneralPurposeDigitalOutputState" },
+ { 0x91, 0x0034, "NumberofCoinDoors" },
+ { 0x91, 0x0035, "CoinDrawerDropCount" },
+ { 0x91, 0x0036, "CoinDrawerStart" },
+ { 0x91, 0x0037, "CoinDrawerService" },
+ { 0x91, 0x0038, "CoinDrawerTilt" },
+ { 0x91, 0x0039, "CoinDoorTest" },
+ { 0x91, 0x0040, "CoinDoorLockout" },
+ { 0x91, 0x0041, "WatchdogTimeout" },
+ { 0x91, 0x0042, "WatchdogAction" },
+ { 0x91, 0x0043, "WatchdogReboot" },
+ { 0x91, 0x0044, "WatchdogRestart" },
+ { 0x91, 0x0045, "AlarmInput" },
+ { 0x91, 0x0046, "CoinDoorCounter" },
+ { 0x91, 0x0047, "IODirectionMapping" },
+ { 0x91, 0x0048, "SetIODirectionMapping" },
+ { 0x91, 0x0049, "ExtendedOpticalInputState" },
+ { 0x91, 0x004a, "PinPadInputState" },
+ { 0x91, 0x004b, "PinPadStatus" },
+ { 0x91, 0x004c, "PinPadOutput" },
+ { 0x91, 0x004d, "PinPadCommand" },
+ { 0xf1d0, 0, "FIDOAlliance" },
+ { 0xf1d0, 0x0001, "U2FAuthenticatorDevice" },
+ { 0xf1d0, 0x0020, "InputReportData" },
+ { 0xf1d0, 0x0021, "OutputReportData" },
+ /* pages 0xff00 to 0xffff are vendor-specific */
+ { 0xffff, 0, "Vendor-specific-FF" },
+ { 0, 0, NULL }
};
/* Either output directly into simple seq_file, or (if f == NULL)
@@ -510,8 +2881,12 @@ static char *resolv_usage_page(unsigned page, struct seq_file *f) {
char *hid_resolv_usage(unsigned usage, struct seq_file *f) {
const struct hid_usage_entry *p;
+ const struct hid_usage_entry *m;
char *buf = NULL;
int len = 0;
+ const char *modifier = NULL;
+ unsigned int usage_modifier = usage & 0xF000;
+ unsigned int usage_actual = usage & 0xFFFF;
buf = resolv_usage_page(usage >> 16, f);
if (IS_ERR(buf)) {
@@ -529,16 +2904,33 @@ char *hid_resolv_usage(unsigned usage, struct seq_file *f) {
}
for (p = hid_usage_table; p->description; p++)
if (p->page == (usage >> 16)) {
+ if (p->page == 0x20 && usage_modifier) {
+ for (m = p; m->description; m++) {
+ if (p->page == m->page && m->usage
+ == usage_modifier) {
+ modifier = m->description;
+ break;
+ }
+ }
+ if (modifier)
+ usage_actual = usage_actual & 0x0FFF;
+ }
+
+ if (!modifier)
+ modifier = "";
+
for(++p; p->description && p->usage != 0; p++)
- if (p->usage == (usage & 0xffff)) {
+ if (p->usage == usage_actual) {
if (!f)
snprintf(buf + len,
HID_DEBUG_BUFSIZE - len,
- "%s", p->description);
+ "%s%s", p->description,
+ modifier);
else
seq_printf(f,
- "%s",
- p->description);
+ "%s%s",
+ p->description,
+ modifier);
return buf;
}
break;
@@ -753,12 +3145,12 @@ static const char *events[EV_MAX + 1] = {
[EV_MSC] = "Misc", [EV_LED] = "LED",
[EV_SND] = "Sound", [EV_REP] = "Repeat",
[EV_FF] = "ForceFeedback", [EV_PWR] = "Power",
- [EV_FF_STATUS] = "ForceFeedbackStatus",
+ [EV_FF_STATUS] = "ForceFeedbackStatus", [EV_SW] = "Software",
};
-static const char *syncs[3] = {
+static const char *syncs[SYN_CNT] = {
[SYN_REPORT] = "Report", [SYN_CONFIG] = "Config",
- [SYN_MT_REPORT] = "MT Report",
+ [SYN_MT_REPORT] = "MT Report", [SYN_DROPPED] = "Dropped",
};
static const char *keys[KEY_MAX + 1] = {
@@ -899,6 +3291,8 @@ static const char *keys[KEY_MAX + 1] = {
[BTN_TR2] = "BtnTR2", [BTN_SELECT] = "BtnSelect",
[BTN_START] = "BtnStart", [BTN_MODE] = "BtnMode",
[BTN_THUMBL] = "BtnThumbL", [BTN_THUMBR] = "BtnThumbR",
+ [BTN_GRIPL] = "BtnGripL", [BTN_GRIPR] = "BtnGripR",
+ [BTN_GRIPL2] = "BtnGripL2", [BTN_GRIPR2] = "BtnGripR2",
[BTN_TOOL_PEN] = "ToolPen", [BTN_TOOL_RUBBER] = "ToolRubber",
[BTN_TOOL_BRUSH] = "ToolBrush", [BTN_TOOL_PENCIL] = "ToolPencil",
[BTN_TOOL_AIRBRUSH] = "ToolAirbrush", [BTN_TOOL_FINGER] = "ToolFinger",
@@ -906,8 +3300,8 @@ static const char *keys[KEY_MAX + 1] = {
[BTN_TOUCH] = "Touch", [BTN_STYLUS] = "Stylus",
[BTN_STYLUS2] = "Stylus2", [BTN_TOOL_DOUBLETAP] = "ToolDoubleTap",
[BTN_TOOL_TRIPLETAP] = "ToolTripleTap", [BTN_TOOL_QUADTAP] = "ToolQuadrupleTap",
- [BTN_GEAR_DOWN] = "WheelBtn",
- [BTN_GEAR_UP] = "Gear up", [KEY_OK] = "Ok",
+ [BTN_GEAR_DOWN] = "BtnGearDown", [BTN_GEAR_UP] = "BtnGearUp",
+ [KEY_OK] = "Ok",
[KEY_SELECT] = "Select", [KEY_GOTO] = "Goto",
[KEY_CLEAR] = "Clear", [KEY_POWER2] = "Power2",
[KEY_OPTION] = "Option", [KEY_INFO] = "Info",
@@ -917,9 +3311,9 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_EPG] = "EPG", [KEY_PVR] = "PVR",
[KEY_MHP] = "MHP", [KEY_LANGUAGE] = "Language",
[KEY_TITLE] = "Title", [KEY_SUBTITLE] = "Subtitle",
- [KEY_ANGLE] = "Angle", [KEY_ZOOM] = "Zoom",
+ [KEY_ANGLE] = "Angle",
[KEY_MODE] = "Mode", [KEY_KEYBOARD] = "Keyboard",
- [KEY_SCREEN] = "Screen", [KEY_PC] = "PC",
+ [KEY_PC] = "PC",
[KEY_TV] = "TV", [KEY_TV2] = "TV2",
[KEY_VCR] = "VCR", [KEY_VCR2] = "VCR2",
[KEY_SAT] = "Sat", [KEY_SAT2] = "Sat2",
@@ -974,6 +3368,8 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_CAMERA_ACCESS_ENABLE] = "CameraAccessEnable",
[KEY_CAMERA_ACCESS_DISABLE] = "CameraAccessDisable",
[KEY_CAMERA_ACCESS_TOGGLE] = "CameraAccessToggle",
+ [KEY_ACCESSIBILITY] = "Accessibility",
+ [KEY_DO_NOT_DISTURB] = "DoNotDisturb",
[KEY_DICTATE] = "Dictate",
[KEY_MICMUTE] = "MicrophoneMute",
[KEY_BRIGHTNESS_MIN] = "BrightnessMin",
@@ -995,6 +3391,102 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_MACRO22] = "Macro22", [KEY_MACRO23] = "Macro23", [KEY_MACRO24] = "Macro24",
[KEY_MACRO25] = "Macro25", [KEY_MACRO26] = "Macro26", [KEY_MACRO27] = "Macro27",
[KEY_MACRO28] = "Macro28", [KEY_MACRO29] = "Macro29", [KEY_MACRO30] = "Macro30",
+ [BTN_TRIGGER_HAPPY1] = "TriggerHappy1", [BTN_TRIGGER_HAPPY2] = "TriggerHappy2",
+ [BTN_TRIGGER_HAPPY3] = "TriggerHappy3", [BTN_TRIGGER_HAPPY4] = "TriggerHappy4",
+ [BTN_TRIGGER_HAPPY5] = "TriggerHappy5", [BTN_TRIGGER_HAPPY6] = "TriggerHappy6",
+ [BTN_TRIGGER_HAPPY7] = "TriggerHappy7", [BTN_TRIGGER_HAPPY8] = "TriggerHappy8",
+ [BTN_TRIGGER_HAPPY9] = "TriggerHappy9", [BTN_TRIGGER_HAPPY10] = "TriggerHappy10",
+ [BTN_TRIGGER_HAPPY11] = "TriggerHappy11", [BTN_TRIGGER_HAPPY12] = "TriggerHappy12",
+ [BTN_TRIGGER_HAPPY13] = "TriggerHappy13", [BTN_TRIGGER_HAPPY14] = "TriggerHappy14",
+ [BTN_TRIGGER_HAPPY15] = "TriggerHappy15", [BTN_TRIGGER_HAPPY16] = "TriggerHappy16",
+ [BTN_TRIGGER_HAPPY17] = "TriggerHappy17", [BTN_TRIGGER_HAPPY18] = "TriggerHappy18",
+ [BTN_TRIGGER_HAPPY19] = "TriggerHappy19", [BTN_TRIGGER_HAPPY20] = "TriggerHappy20",
+ [BTN_TRIGGER_HAPPY21] = "TriggerHappy21", [BTN_TRIGGER_HAPPY22] = "TriggerHappy22",
+ [BTN_TRIGGER_HAPPY23] = "TriggerHappy23", [BTN_TRIGGER_HAPPY24] = "TriggerHappy24",
+ [BTN_TRIGGER_HAPPY25] = "TriggerHappy25", [BTN_TRIGGER_HAPPY26] = "TriggerHappy26",
+ [BTN_TRIGGER_HAPPY27] = "TriggerHappy27", [BTN_TRIGGER_HAPPY28] = "TriggerHappy28",
+ [BTN_TRIGGER_HAPPY29] = "TriggerHappy29", [BTN_TRIGGER_HAPPY30] = "TriggerHappy30",
+ [BTN_TRIGGER_HAPPY31] = "TriggerHappy31", [BTN_TRIGGER_HAPPY32] = "TriggerHappy32",
+ [BTN_TRIGGER_HAPPY33] = "TriggerHappy33", [BTN_TRIGGER_HAPPY34] = "TriggerHappy34",
+ [BTN_TRIGGER_HAPPY35] = "TriggerHappy35", [BTN_TRIGGER_HAPPY36] = "TriggerHappy36",
+ [BTN_TRIGGER_HAPPY37] = "TriggerHappy37", [BTN_TRIGGER_HAPPY38] = "TriggerHappy38",
+ [BTN_TRIGGER_HAPPY39] = "TriggerHappy39", [BTN_TRIGGER_HAPPY40] = "TriggerHappy40",
+ [BTN_STYLUS3] = "Stylus3", [BTN_TOOL_QUINTTAP] = "ToolQuintTap",
+ [KEY_10CHANNELSDOWN] = "10ChannelsDown",
+ [KEY_10CHANNELSUP] = "10ChannelsUp",
+ [KEY_3D_MODE] = "3DMode", [KEY_ADDRESSBOOK] = "Addressbook",
+ [KEY_ALS_TOGGLE] = "ALSToggle", [KEY_ASPECT_RATIO] = "AspectRatio",
+ [KEY_ATTENDANT_OFF] = "AttendantOff", [KEY_ATTENDANT_ON] = "AttendantOn",
+ [KEY_ATTENDANT_TOGGLE] = "AttendantToggle",
+ [KEY_AUDIO_DESC] = "AudioDesc",
+ [KEY_AUTOPILOT_ENGAGE_TOGGLE] = "AutoPiloteEngage",
+ [KEY_BATTERY] = "Battery", [KEY_BLUETOOTH] = "BlueTooth",
+ [KEY_BRIGHTNESS_CYCLE] = "BrightnessCycle",
+ [KEY_BRIGHTNESS_MENU] = "BrightnessMenu",
+ [KEY_BRL_DOT1] = "BrlDot1", [KEY_BRL_DOT10] = "BrlDot10",
+ [KEY_BRL_DOT2] = "BrlDot2", [KEY_BRL_DOT3] = "BrlDot3",
+ [KEY_BRL_DOT4] = "BrlDot4", [KEY_BRL_DOT5] = "BrlDot5",
+ [KEY_BRL_DOT6] = "BrlDot6", [KEY_BRL_DOT7] = "BrlDot7",
+ [KEY_BRL_DOT8] = "BrlDot8", [KEY_BRL_DOT9] = "BrlDot9",
+ [KEY_CAMERA_DOWN] = "CameraDown", [KEY_CAMERA_FOCUS] = "CameraFocus",
+ [KEY_CAMERA_LEFT] = "CameraLeft", [KEY_CAMERA_RIGHT] = "CameraRight",
+ [KEY_CAMERA_UP] = "CameraUp", [KEY_CAMERA_ZOOMIN] = "CameraZoomIn",
+ [KEY_CAMERA_ZOOMOUT] = "CameraZoomOut", [KEY_CLEARVU_SONAR] = "ClearVUSonar",
+ [KEY_CONTEXT_MENU] = "ContextMenu", [KEY_DATA] = "Data",
+ [KEY_DATABASE] = "DataBase", [KEY_DISPLAY_OFF] = "DisplayOff",
+ [KEY_DISPLAYTOGGLE] = "DisplayToggle", [KEY_DOLLAR] = "Dollar",
+ [KEY_DUAL_RANGE_RADAR] = "DualRangeRadat",
+ [KEY_EDITOR] = "Editor", [KEY_EURO] = "Euro",
+ [KEY_FASTREVERSE] = "FastReverse", [KEY_FISHING_CHART] = "FishingChart",
+ [KEY_FN_RIGHT_SHIFT] = "FnRightShift", [KEY_FRAMEBACK] = "FrameBack",
+ [KEY_FRAMEFORWARD] = "FrameForward", [KEY_FULL_SCREEN] = "FullScreen",
+ [KEY_GAMES] = "Games", [KEY_GRAPHICSEDITOR] = "GraphicsEditor",
+ [KEY_HANGUP_PHONE] = "HangUpPhone",
+ [KEY_IMAGES] = "Images", [KEY_KBD_LCD_MENU1] = "KbdLcdMenu1",
+ [KEY_KBD_LCD_MENU2] = "KbdLcdMenu2", [KEY_KBD_LCD_MENU3] = "KbdLcdMenu3",
+ [KEY_KBD_LCD_MENU4] = "KbdLcdMenu4", [KEY_KBD_LCD_MENU5] = "KbdLcdMenu5",
+ [KEY_LEFT_DOWN] = "LeftDown", [KEY_LEFT_UP] = "LeftUp",
+ [KEY_LIGHTS_TOGGLE] = "LightToggle", [KEY_MACRO_PRESET1] = "MacroPreset1",
+ [KEY_MACRO_PRESET2] = "MacroPreset2", [KEY_MACRO_PRESET3] = "MacroPrest3",
+ [KEY_MACRO_PRESET_CYCLE] = "MacroPresetCycle",
+ [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] = "Messenger",
+ [KEY_NAV_CHART] = "NavChar", [KEY_NAV_INFO] = "NavInfo",
+ [KEY_NEWS] = "News", [KEY_NEXT_ELEMENT] = "NextElement",
+ [KEY_NEXT_FAVORITE] = "NextFavorite", [KEY_NOTIFICATION_CENTER] = "NotificationCenter",
+ [KEY_NUMERIC_0] = "Numeric0", [KEY_NUMERIC_1] = "Numeric1",
+ [KEY_NUMERIC_11] = "Numceric11", [KEY_NUMERIC_12] = "Numeric12",
+ [KEY_NUMERIC_2] = "Numeric2", [KEY_NUMERIC_3] = "Numeric3",
+ [KEY_NUMERIC_4] = "Numeric4", [KEY_NUMERIC_5] = "Numeric5",
+ [KEY_NUMERIC_6] = "Numeric6", [KEY_NUMERIC_7] = "Numeric7",
+ [KEY_NUMERIC_8] = "Numeric8", [KEY_NUMERIC_9] = "Numeric9",
+ [KEY_NUMERIC_A] = "NumericA", [KEY_NUMERIC_B] = "NumericB",
+ [KEY_NUMERIC_C] = "NumericC", [KEY_NUMERIC_D] = "NumericD",
+ [KEY_NUMERIC_POUND] = "NumericPound", [KEY_NUMERIC_STAR] = "NumericStar",
+ [KEY_ONSCREEN_KEYBOARD] = "OnScreenKeyBoard",
+ [KEY_PAUSE_RECORD] = "PauseRecord", [KEY_PICKUP_PHONE] = "PickUpPhone",
+ [KEY_PRESENTATION] = "Presentation", [KEY_PREVIOUS_ELEMENT] = "PreviousElement",
+ [KEY_PRIVACY_SCREEN_TOGGLE] = "PrivacyScreenToggle",
+ [KEY_RADAR_OVERLAY] = "RadarOverLay",
+ [KEY_RFKILL] = "RFKill", [KEY_RIGHT_DOWN] = "RightDown",
+ [KEY_RIGHT_UP] = "RightUp", [KEY_ROOT_MENU] = "RootMenu",
+ [KEY_ROTATE_LOCK_TOGGLE] = "RotateLockToggle",
+ [KEY_SCALE] = "Scale", [KEY_SELECTIVE_SCREENSHOT] = "SelectiveScreenshot",
+ [KEY_SIDEVU_SONAR] = "SideVUSonar", [KEY_SINGLE_RANGE_RADAR] = "SingleRangeRadar",
+ [KEY_SLOWREVERSE] = "SlowReverse", [KEY_SOS] = "SOS",
+ [KEY_SPREADSHEET] = "SpreadSheet", [KEY_STOP_RECORD] = "StopRecord",
+ [KEY_TOUCHPAD_OFF] = "TouchPadOff", [KEY_TOUCHPAD_ON] = "TouchPadOn",
+ [KEY_TOUCHPAD_TOGGLE] = "TouchPadToggle",
+ [KEY_TRADITIONAL_SONAR] = "TraditionalSonar",
+ [KEY_UNMUTE] = "Unmute", [KEY_UWB] = "UWB",
+ [KEY_VIDEO_NEXT] = "VideoNext", [KEY_VIDEOPHONE] = "VideoPhone",
+ [KEY_VIDEO_PREV] = "VideoPrev", [KEY_VOD] = "VOD",
+ [KEY_VOICEMAIL] = "VoiceMail", [KEY_WLAN] = "WLAN",
+ [KEY_WORDPROCESSOR] = "WordProcessor", [KEY_WPS_BUTTON] = "WPSButton",
+ [KEY_WWAN] = "WWAN", [KEY_ZOOMIN] = "ZoomIn",
+ [KEY_ZOOMOUT] = "ZoomOut", [KEY_ZOOMRESET] = "ZoomReset",
};
static const char *relatives[REL_MAX + 1] = {
@@ -1003,6 +3495,8 @@ static const char *relatives[REL_MAX + 1] = {
[REL_RY] = "Ry", [REL_RZ] = "Rz",
[REL_HWHEEL] = "HWheel", [REL_DIAL] = "Dial",
[REL_WHEEL] = "Wheel", [REL_MISC] = "Misc",
+ [REL_WHEEL_HI_RES] = "WheelHiRes",
+ [REL_HWHEEL_HI_RES] = "HWheelHiRes"
};
static const char *absolutes[ABS_CNT] = {
@@ -1020,6 +3514,7 @@ static const char *absolutes[ABS_CNT] = {
[ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth",
[ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile",
[ABS_MISC] = "Misc",
+ [ABS_MT_SLOT] = "MTSlot",
[ABS_MT_TOUCH_MAJOR] = "MTMajor",
[ABS_MT_TOUCH_MINOR] = "MTMinor",
[ABS_MT_WIDTH_MAJOR] = "MTMajorW",
@@ -1029,11 +3524,17 @@ static const char *absolutes[ABS_CNT] = {
[ABS_MT_POSITION_Y] = "MTPositionY",
[ABS_MT_TOOL_TYPE] = "MTToolType",
[ABS_MT_BLOB_ID] = "MTBlobID",
+ [ABS_MT_TRACKING_ID] = "MTTrackingID",
+ [ABS_MT_PRESSURE] = "MTPressure",
+ [ABS_MT_DISTANCE] = "MTDistance",
+ [ABS_MT_TOOL_X] = "MTToolX",
+ [ABS_MT_TOOL_Y] = "MTToolY",
};
static const char *misc[MSC_MAX + 1] = {
[MSC_SERIAL] = "Serial", [MSC_PULSELED] = "Pulseled",
- [MSC_GESTURE] = "Gesture", [MSC_RAW] = "RawData"
+ [MSC_GESTURE] = "Gesture", [MSC_RAW] = "RawData",
+ [MSC_SCAN] = "Scan", [MSC_TIMESTAMP] = "TimeStamp",
};
static const char *leds[LED_MAX + 1] = {
@@ -1041,7 +3542,8 @@ static const char *leds[LED_MAX + 1] = {
[LED_SCROLLL] = "ScrollLock", [LED_COMPOSE] = "Compose",
[LED_KANA] = "Kana", [LED_SLEEP] = "Sleep",
[LED_SUSPEND] = "Suspend", [LED_MUTE] = "Mute",
- [LED_MISC] = "Misc",
+ [LED_MISC] = "Misc", [LED_MAIL] = "Mail",
+ [LED_CHARGING] = "Charging",
};
static const char *repeats[REP_MAX + 1] = {
@@ -1053,17 +3555,71 @@ static const char *sounds[SND_MAX + 1] = {
[SND_TONE] = "Tone"
};
+static const char *software[SW_CNT] = {
+ [SW_LID] = "Lid",
+ [SW_TABLET_MODE] = "TabletMode",
+ [SW_HEADPHONE_INSERT] = "HeadPhoneInsert",
+ [SW_RFKILL_ALL] = "RFKillAll",
+ [SW_MICROPHONE_INSERT] = "MicrophoneInsert",
+ [SW_DOCK] = "Dock",
+ [SW_LINEOUT_INSERT] = "LineOutInsert",
+ [SW_JACK_PHYSICAL_INSERT] = "JackPhysicalInsert",
+ [SW_VIDEOOUT_INSERT] = "VideoOutInsert",
+ [SW_CAMERA_LENS_COVER] = "CameraLensCover",
+ [SW_KEYPAD_SLIDE] = "KeyPadSlide",
+ [SW_FRONT_PROXIMITY] = "FrontProximity",
+ [SW_ROTATE_LOCK] = "RotateLock",
+ [SW_LINEIN_INSERT] = "LineInInsert",
+ [SW_MUTE_DEVICE] = "MuteDevice",
+ [SW_PEN_INSERTED] = "PenInserted",
+ [SW_MACHINE_COVER] = "MachineCover",
+};
+
+static const char *force[FF_CNT] = {
+ [FF_RUMBLE] = "FF_RUMBLE",
+ [FF_PERIODIC] = "FF_PERIODIC",
+ [FF_CONSTANT] = "FF_CONSTANT",
+ [FF_SPRING] = "FF_SPRING",
+ [FF_FRICTION] = "FF_FRICTION",
+ [FF_DAMPER] = "FF_DAMPER",
+ [FF_INERTIA] = "FF_INERTIA",
+ [FF_RAMP] = "FF_RAMP",
+ [FF_SQUARE] = "FF_SQUARE",
+ [FF_TRIANGLE] = "FF_TRIANGLE",
+ [FF_SINE] = "FF_SINE",
+ [FF_SAW_UP] = "FF_SAW_UP",
+ [FF_SAW_DOWN] = "FF_SAW_DOWN",
+ [FF_CUSTOM] = "FF_CUSTOM",
+ [FF_GAIN] = "FF_GAIN",
+ [FF_AUTOCENTER] = "FF_AUTOCENTER",
+ [FF_MAX] = "FF_MAX",
+};
+
+static const char *force_status[FF_STATUS_MAX + 1] = {
+ [FF_STATUS_STOPPED] = "FF_STATUS_STOPPED",
+ [FF_STATUS_PLAYING] = "FF_STATUS_PLAYING",
+};
+
static const char **names[EV_MAX + 1] = {
[EV_SYN] = syncs, [EV_KEY] = keys,
[EV_REL] = relatives, [EV_ABS] = absolutes,
[EV_MSC] = misc, [EV_LED] = leds,
[EV_SND] = sounds, [EV_REP] = repeats,
+ [EV_SW] = software, [EV_FF] = force,
+ [EV_FF_STATUS] = force_status,
};
static void hid_resolv_event(__u8 type, __u16 code, struct seq_file *f)
{
- seq_printf(f, "%s.%s", events[type] ? events[type] : "?",
- names[type] ? (names[type][code] ? names[type][code] : "?") : "?");
+ if (events[type])
+ seq_printf(f, "%s.", events[type]);
+ else
+ seq_printf(f, "%02x.", type);
+
+ if (names[type] && names[type][code])
+ seq_printf(f, "%s", names[type][code]);
+ else
+ seq_printf(f, "%04x", code);
}
static void hid_dump_input_mapping(struct hid_device *hid, struct seq_file *f)
@@ -1135,6 +3691,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
goto out;
}
list->hdev = (struct hid_device *) inode->i_private;
+ kref_get(&list->hdev->ref);
file->private_data = list;
mutex_init(&list->read_mutex);
@@ -1171,7 +3728,7 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
*/
if (!list->hdev || !list->hdev->debug) {
ret = -EIO;
- set_current_state(TASK_RUNNING);
+ __set_current_state(TASK_RUNNING);
goto out;
}
@@ -1227,6 +3784,8 @@ static int hid_debug_events_release(struct inode *inode, struct file *file)
list_del(&list->node);
spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
kfifo_free(&list->hid_debug_fifo);
+
+ kref_put(&list->hdev->ref, hiddev_free);
kfree(list);
return 0;
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
index 947f19f8685f..84e1e90a266b 100644
--- a/drivers/hid/hid-dr.c
+++ b/drivers/hid/hid-dr.c
@@ -199,7 +199,7 @@ static inline int drff_init(struct hid_device *hid)
#define PID0011_RDESC_ORIG_SIZE 101
/* Fixed report descriptor for PID 0x011 joystick */
-static __u8 pid0011_rdesc_fixed[] = {
+static const __u8 pid0011_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -228,14 +228,14 @@ static __u8 pid0011_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 *dr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *dr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
switch (hdev->product) {
case 0x0011:
if (*rsize == PID0011_RDESC_ORIG_SIZE) {
- rdesc = pid0011_rdesc_fixed;
*rsize = sizeof(pid0011_rdesc_fixed);
+ return pid0011_rdesc_fixed;
}
break;
}
@@ -316,4 +316,5 @@ static struct hid_driver dr_driver = {
};
module_hid_driver(dr_driver);
+MODULE_DESCRIPTION("Force feedback support for DragonRise Inc. game controllers");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
index 4fa45ee77503..981d1b6e9658 100644
--- a/drivers/hid/hid-elecom.c
+++ b/drivers/hid/hid-elecom.c
@@ -53,7 +53,7 @@ static void mouse_button_fixup(struct hid_device *hdev,
rdesc[padding_bit + 1] = MOUSE_BUTTONS_MAX - nbuttons;
}
-static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
@@ -75,7 +75,8 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*/
mouse_button_fixup(hdev, rdesc, *rsize, 20, 28, 22, 14, 8);
break;
- case USB_DEVICE_ID_ELECOM_M_XT3URBK:
+ case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB:
+ case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F:
case USB_DEVICE_ID_ELECOM_M_XT3DRBK:
case USB_DEVICE_ID_ELECOM_M_XT4DRBK:
/*
@@ -89,7 +90,8 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
break;
case USB_DEVICE_ID_ELECOM_M_DT1URBK:
case USB_DEVICE_ID_ELECOM_M_DT1DRBK:
- case USB_DEVICE_ID_ELECOM_M_HT1URBK:
+ case USB_DEVICE_ID_ELECOM_M_HT1URBK_010C:
+ case USB_DEVICE_ID_ELECOM_M_HT1URBK_019B:
case USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D:
/*
* Report descriptor format:
@@ -100,6 +102,7 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*/
mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8);
break;
+ case USB_DEVICE_ID_ELECOM_M_DT2DRBK:
case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C:
/*
* Report descriptor format:
@@ -117,12 +120,15 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
static const struct hid_device_id elecom_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_010C) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
{ }
@@ -136,4 +142,5 @@ static struct hid_driver elecom_driver = {
};
module_hid_driver(elecom_driver);
+MODULE_DESCRIPTION("HID driver for ELECOM devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c
index 2876cb6a7dca..cf17bdd14d9c 100644
--- a/drivers/hid/hid-elo.c
+++ b/drivers/hid/hid-elo.c
@@ -313,4 +313,5 @@ static void __exit elo_driver_exit(void)
module_exit(elo_driver_exit);
MODULE_AUTHOR("Jiri Slaby <jslaby@suse.cz>");
+MODULE_DESCRIPTION("HID driver for ELO usb touchscreen 4000/4500");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c
index c34f2e5a049f..60bfb6a924d7 100644
--- a/drivers/hid/hid-emsff.c
+++ b/drivers/hid/hid-emsff.c
@@ -144,5 +144,6 @@ static struct hid_driver ems_driver = {
};
module_hid_driver(ems_driver);
+MODULE_DESCRIPTION("Force feedback support for EMS Trio Linker Plus II");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-evision.c b/drivers/hid/hid-evision.c
index ef6b4b435215..3e7f43ab80bb 100644
--- a/drivers/hid/hid-evision.c
+++ b/drivers/hid/hid-evision.c
@@ -18,6 +18,10 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
+ /* mapping only applies to USB_DEVICE_ID_EVISION_ICL01 */
+ if (hdev->product != USB_DEVICE_ID_EVISION_ICL01)
+ return 0;
+
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
@@ -37,8 +41,24 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
+#define REP_DSC_SIZE 236
+#define USAGE_MAX_INDEX 59
+
+static const __u8 *evision_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ if (hdev->product == USB_DEVICE_ID_EV_TELINK_RECEIVER &&
+ *rsize == REP_DSC_SIZE && rdesc[USAGE_MAX_INDEX] == 0x29 &&
+ rdesc[USAGE_MAX_INDEX + 1] == 3) {
+ hid_info(hdev, "fixing EVision:TeLink Receiver report descriptor\n");
+ rdesc[USAGE_MAX_INDEX + 1] = 5; // change usage max from 3 to 5
+ }
+ return rdesc;
+}
+
static const struct hid_device_id evision_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EVISION_ICL01) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EV_TELINK_RECEIVER) },
{ }
};
MODULE_DEVICE_TABLE(hid, evision_devices);
@@ -47,7 +67,9 @@ static struct hid_driver evision_driver = {
.name = "evision",
.id_table = evision_devices,
.input_mapping = evision_input_mapping,
+ .report_fixup = evision_report_fixup,
};
module_hid_driver(evision_driver);
+MODULE_DESCRIPTION("HID driver for EVision devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ezkey.c b/drivers/hid/hid-ezkey.c
index d14f91d78c96..0e28bc0b87fa 100644
--- a/drivers/hid/hid-ezkey.c
+++ b/drivers/hid/hid-ezkey.c
@@ -75,4 +75,5 @@ static struct hid_driver ez_driver = {
};
module_hid_driver(ez_driver);
+MODULE_DESCRIPTION("HID driver for some ezkey \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
index ecbd3995a4eb..c6db8b6cc8ee 100644
--- a/drivers/hid/hid-gaff.c
+++ b/drivers/hid/hid-gaff.c
@@ -169,4 +169,5 @@ static struct hid_driver ga_driver = {
};
module_hid_driver(ga_driver);
+MODULE_DESCRIPTION("Force feedback support for GreenAsia (Product ID 0x12) based devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gembird.c b/drivers/hid/hid-gembird.c
index c42593fe7116..20a8de766e56 100644
--- a/drivers/hid/hid-gembird.c
+++ b/drivers/hid/hid-gembird.c
@@ -57,7 +57,7 @@ static const __u8 gembird_jpd_fixed_rdesc[] = {
0x81, 0x02, /* Input (Data,Var,Abs) */
};
-static __u8 *gembird_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *gembird_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
__u8 *new_rdesc;
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index f9db991d3c5a..c2de916747de 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -16,7 +16,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <asm/byteorder.h>
#include <linux/hid.h>
@@ -40,6 +40,9 @@ static bool hid_generic_match(struct hid_device *hdev,
if (ignore_special_driver)
return true;
+ if (hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER)
+ return true;
+
if (hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)
return false;
@@ -67,6 +70,14 @@ static int hid_generic_probe(struct hid_device *hdev,
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
+static int hid_generic_reset_resume(struct hid_device *hdev)
+{
+ if (hdev->claimed & HID_CLAIMED_INPUT)
+ hidinput_reset_resume(hdev);
+
+ return 0;
+}
+
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
{ }
@@ -78,6 +89,7 @@ static struct hid_driver hid_generic = {
.id_table = hid_table,
.match = hid_generic_match,
.probe = hid_generic_probe,
+ .reset_resume = hid_generic_reset_resume,
};
module_hid_driver(hid_generic);
diff --git a/drivers/hid/hid-glorious.c b/drivers/hid/hid-glorious.c
index 558eb08c19ef..5bbd81248053 100644
--- a/drivers/hid/hid-glorious.c
+++ b/drivers/hid/hid-glorious.c
@@ -21,8 +21,12 @@ MODULE_DESCRIPTION("HID driver for Glorious PC Gaming Race mice");
* Glorious Model O and O- specify the const flag in the consumer input
* report descriptor, which leads to inputs being ignored. Fix this
* by patching the descriptor.
+ *
+ * Glorious Model I incorrectly specifes the Usage Minimum for its
+ * keyboard HID report, causing keycodes to be misinterpreted.
+ * Fix this by setting Usage Minimum to 0 in that report.
*/
-static __u8 *glorious_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *glorious_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == 213 &&
@@ -32,6 +36,10 @@ static __u8 *glorious_report_fixup(struct hid_device *hdev, __u8 *rdesc,
rdesc[85] = rdesc[113] = rdesc[141] = \
HID_MAIN_ITEM_VARIABLE | HID_MAIN_ITEM_RELATIVE;
}
+ if (*rsize == 156 && rdesc[41] == 1) {
+ hid_info(hdev, "patching Glorious Model I keyboard report descriptor\n");
+ rdesc[41] = 0;
+ }
return rdesc;
}
@@ -44,6 +52,8 @@ static void glorious_update_name(struct hid_device *hdev)
model = "Model O"; break;
case USB_DEVICE_ID_GLORIOUS_MODEL_D:
model = "Model D"; break;
+ case USB_DEVICE_ID_GLORIOUS_MODEL_I:
+ model = "Model I"; break;
}
snprintf(hdev->name, sizeof(hdev->name), "%s %s", "Glorious", model);
@@ -66,10 +76,12 @@ static int glorious_probe(struct hid_device *hdev,
}
static const struct hid_device_id glorious_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_GLORIOUS,
+ { HID_USB_DEVICE(USB_VENDOR_ID_SINOWEALTH,
USB_DEVICE_ID_GLORIOUS_MODEL_O) },
- { HID_USB_DEVICE(USB_VENDOR_ID_GLORIOUS,
+ { HID_USB_DEVICE(USB_VENDOR_ID_SINOWEALTH,
USB_DEVICE_ID_GLORIOUS_MODEL_D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LAVIEW,
+ USB_DEVICE_ID_GLORIOUS_MODEL_I) },
{ }
};
MODULE_DEVICE_TABLE(hid, glorious_devices);
diff --git a/drivers/hid/hid-goodix-spi.c b/drivers/hid/hid-goodix-spi.c
new file mode 100644
index 000000000000..80c0288a3a38
--- /dev/null
+++ b/drivers/hid/hid-goodix-spi.c
@@ -0,0 +1,818 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Goodix GT7986U SPI Driver Code for HID.
+ *
+ * Copyright (C) 2024 Godix, Inc.
+ */
+#include <linux/unaligned.h>
+#include <linux/delay.h>
+#include <linux/hid.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/sizes.h>
+#include <linux/spi/spi.h>
+
+#define GOODIX_DEV_CONFIRM_ADDR 0x10000
+#define GOODIX_HID_DESC_ADDR 0x1058C
+#define GOODIX_HID_REPORT_DESC_ADDR 0x105AA
+#define GOODIX_HID_SIGN_ADDR 0x10D32
+#define GOODIX_HID_CMD_ADDR 0x10364
+#define GOODIX_HID_REPORT_ADDR 0x22C8C
+
+#define GOODIX_HID_GET_REPORT_CMD 0x02
+#define GOODIX_HID_SET_REPORT_CMD 0x03
+
+#define GOODIX_HID_MAX_INBUF_SIZE 128
+#define GOODIX_HID_ACK_READY_FLAG 0x01
+#define GOODIX_HID_REPORT_READY_FLAG 0x80
+
+#define GOODIX_DEV_CONFIRM_VAL 0xAA
+
+#define GOODIX_SPI_WRITE_FLAG 0xF0
+#define GOODIX_SPI_READ_FLAG 0xF1
+#define GOODIX_SPI_TRANS_PREFIX_LEN 1
+#define GOODIX_REGISTER_WIDTH 4
+#define GOODIX_SPI_READ_DUMMY_LEN 3
+#define GOODIX_SPI_READ_PREFIX_LEN (GOODIX_SPI_TRANS_PREFIX_LEN + \
+ GOODIX_REGISTER_WIDTH + \
+ GOODIX_SPI_READ_DUMMY_LEN)
+#define GOODIX_SPI_WRITE_PREFIX_LEN (GOODIX_SPI_TRANS_PREFIX_LEN + \
+ GOODIX_REGISTER_WIDTH)
+
+#define GOODIX_CHECKSUM_SIZE sizeof(u16)
+#define GOODIX_NORMAL_RESET_DELAY_MS 150
+
+struct goodix_hid_report_header {
+ u8 flag;
+ __le16 size;
+} __packed;
+#define GOODIX_HID_ACK_HEADER_SIZE sizeof(struct goodix_hid_report_header)
+
+struct goodix_hid_report_package {
+ __le16 size;
+ u8 data[];
+};
+
+#define GOODIX_HID_PKG_LEN_SIZE sizeof(u16)
+#define GOODIX_HID_COOR_DATA_LEN 82
+#define GOODIX_HID_COOR_PKG_LEN (GOODIX_HID_PKG_LEN_SIZE + \
+ GOODIX_HID_COOR_DATA_LEN)
+
+/* power state */
+#define GOODIX_SPI_POWER_ON 0x00
+#define GOODIX_SPI_POWER_SLEEP 0x01
+
+/* flags used to record the current device operating state */
+#define GOODIX_HID_STARTED 0
+
+struct goodix_hid_report_event {
+ struct goodix_hid_report_header hdr;
+ u8 data[GOODIX_HID_COOR_PKG_LEN];
+} __packed;
+
+struct goodix_hid_desc {
+ __le16 desc_length;
+ __le16 bcd_version;
+ __le16 report_desc_length;
+ __le16 report_desc_register;
+ __le16 input_register;
+ __le16 max_input_length;
+ __le16 output_register;
+ __le16 max_output_length;
+ __le16 cmd_register;
+ __le16 data_register;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 version_id;
+ __le32 reserved;
+} __packed;
+
+struct goodix_ts_data {
+ struct device *dev;
+ struct spi_device *spi;
+ struct hid_device *hid;
+ struct goodix_hid_desc hid_desc;
+
+ struct gpio_desc *reset_gpio;
+ u32 hid_report_addr;
+
+ unsigned long flags;
+ /* lock for hid raw request operation */
+ struct mutex hid_request_lock;
+ /* buffer used to store hid report event */
+ u8 *event_buf;
+ u32 hid_max_event_sz;
+ /* buffer used to do spi data transfer */
+ u8 xfer_buf[SZ_2K] ____cacheline_aligned;
+};
+
+static void *goodix_get_event_report(struct goodix_ts_data *ts, u32 addr,
+ u8 *data, size_t len)
+{
+ struct spi_device *spi = to_spi_device(&ts->spi->dev);
+ struct spi_transfer xfers;
+ struct spi_message spi_msg;
+ int error;
+
+ /* buffer format: 0xF1 + addr(4bytes) + dummy(3bytes) + data */
+ data[0] = GOODIX_SPI_READ_FLAG;
+ put_unaligned_be32(addr, data + GOODIX_SPI_TRANS_PREFIX_LEN);
+
+ spi_message_init(&spi_msg);
+ memset(&xfers, 0, sizeof(xfers));
+ xfers.tx_buf = data;
+ xfers.rx_buf = data;
+ xfers.len = GOODIX_SPI_READ_PREFIX_LEN + len;
+ spi_message_add_tail(&xfers, &spi_msg);
+
+ error = spi_sync(spi, &spi_msg);
+ if (error) {
+ dev_err(ts->dev, "spi transfer error: %d", error);
+ return NULL;
+ }
+
+ return data + GOODIX_SPI_READ_PREFIX_LEN;
+}
+
+static int goodix_spi_read(struct goodix_ts_data *ts, u32 addr,
+ void *data, size_t len)
+{
+ struct spi_device *spi = to_spi_device(&ts->spi->dev);
+ struct spi_transfer xfers;
+ struct spi_message spi_msg;
+ int error;
+
+ if (GOODIX_SPI_READ_PREFIX_LEN + len > sizeof(ts->xfer_buf)) {
+ dev_err(ts->dev, "read data len exceed limit %zu",
+ sizeof(ts->xfer_buf) - GOODIX_SPI_READ_PREFIX_LEN);
+ return -EINVAL;
+ }
+
+ /* buffer format: 0xF1 + addr(4bytes) + dummy(3bytes) + data */
+ ts->xfer_buf[0] = GOODIX_SPI_READ_FLAG;
+ put_unaligned_be32(addr, ts->xfer_buf + GOODIX_SPI_TRANS_PREFIX_LEN);
+
+ spi_message_init(&spi_msg);
+ memset(&xfers, 0, sizeof(xfers));
+ xfers.tx_buf = ts->xfer_buf;
+ xfers.rx_buf = ts->xfer_buf;
+ xfers.len = GOODIX_SPI_READ_PREFIX_LEN + len;
+ spi_message_add_tail(&xfers, &spi_msg);
+
+ error = spi_sync(spi, &spi_msg);
+ if (error)
+ dev_err(ts->dev, "spi transfer error: %d", error);
+ else
+ memcpy(data, ts->xfer_buf + GOODIX_SPI_READ_PREFIX_LEN, len);
+
+ return error;
+}
+
+static int goodix_spi_write(struct goodix_ts_data *ts, u32 addr,
+ const void *data, size_t len)
+{
+ struct spi_device *spi = to_spi_device(&ts->spi->dev);
+ struct spi_transfer xfers;
+ struct spi_message spi_msg;
+ int error;
+
+ if (GOODIX_SPI_WRITE_PREFIX_LEN + len > sizeof(ts->xfer_buf)) {
+ dev_err(ts->dev, "write data len exceed limit %zu",
+ sizeof(ts->xfer_buf) - GOODIX_SPI_WRITE_PREFIX_LEN);
+ return -EINVAL;
+ }
+
+ /* buffer format: 0xF0 + addr(4bytes) + data */
+ ts->xfer_buf[0] = GOODIX_SPI_WRITE_FLAG;
+ put_unaligned_be32(addr, ts->xfer_buf + GOODIX_SPI_TRANS_PREFIX_LEN);
+ memcpy(ts->xfer_buf + GOODIX_SPI_WRITE_PREFIX_LEN, data, len);
+
+ spi_message_init(&spi_msg);
+ memset(&xfers, 0, sizeof(xfers));
+ xfers.tx_buf = ts->xfer_buf;
+ xfers.len = GOODIX_SPI_WRITE_PREFIX_LEN + len;
+ spi_message_add_tail(&xfers, &spi_msg);
+
+ error = spi_sync(spi, &spi_msg);
+ if (error)
+ dev_err(ts->dev, "spi transfer error: %d", error);
+
+ return error;
+}
+
+static int goodix_dev_confirm(struct goodix_ts_data *ts)
+{
+ u8 tx_buf[8], rx_buf[8];
+ int retry = 3;
+ int error;
+
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+ usleep_range(4000, 4100);
+
+ memset(tx_buf, GOODIX_DEV_CONFIRM_VAL, sizeof(tx_buf));
+ while (retry--) {
+ error = goodix_spi_write(ts, GOODIX_DEV_CONFIRM_ADDR,
+ tx_buf, sizeof(tx_buf));
+ if (error)
+ return error;
+
+ error = goodix_spi_read(ts, GOODIX_DEV_CONFIRM_ADDR,
+ rx_buf, sizeof(rx_buf));
+ if (error)
+ return error;
+
+ if (!memcmp(tx_buf, rx_buf, sizeof(tx_buf)))
+ return 0;
+
+ usleep_range(5000, 5100);
+ }
+
+ dev_err(ts->dev, "device confirm failed, rx_buf: %*ph", 8, rx_buf);
+ return -EINVAL;
+}
+
+/**
+ * goodix_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 goodix_hid_parse(struct hid_device *hid)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+ u16 rsize;
+ int error;
+
+ rsize = le16_to_cpu(ts->hid_desc.report_desc_length);
+ if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+ dev_err(ts->dev, "invalid report desc size, %d", rsize);
+ return -EINVAL;
+ }
+
+ u8 *rdesc __free(kfree) = kzalloc(rsize, GFP_KERNEL);
+ if (!rdesc)
+ return -ENOMEM;
+
+ error = goodix_spi_read(ts, GOODIX_HID_REPORT_DESC_ADDR, rdesc, rsize);
+ if (error) {
+ dev_err(ts->dev, "failed get report desc, %d", error);
+ return error;
+ }
+
+ error = hid_parse_report(hid, rdesc, rsize);
+ if (error) {
+ dev_err(ts->dev, "failed parse report, %d", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static int goodix_hid_get_report_length(struct hid_report *report)
+{
+ return ((report->size - 1) >> 3) + 1 +
+ report->device->report_enum[report->type].numbered + 2;
+}
+
+static void goodix_hid_find_max_report(struct hid_device *hid, unsigned int type,
+ unsigned int *max)
+{
+ struct hid_report *report;
+ unsigned int size;
+
+ list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+ size = goodix_hid_get_report_length(report);
+ if (*max < size)
+ *max = size;
+ }
+}
+
+static int goodix_hid_start(struct hid_device *hid)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+ unsigned int bufsize = GOODIX_HID_COOR_PKG_LEN;
+ u32 report_size;
+
+ goodix_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
+ goodix_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
+ goodix_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
+
+ report_size = GOODIX_SPI_READ_PREFIX_LEN +
+ GOODIX_HID_ACK_HEADER_SIZE + bufsize;
+ if (report_size <= ts->hid_max_event_sz)
+ return 0;
+
+ ts->event_buf = devm_krealloc(ts->dev, ts->event_buf,
+ report_size, GFP_KERNEL);
+ if (!ts->event_buf)
+ return -ENOMEM;
+
+ ts->hid_max_event_sz = report_size;
+ return 0;
+}
+
+static void goodix_hid_stop(struct hid_device *hid)
+{
+ hid->claimed = 0;
+}
+
+static int goodix_hid_open(struct hid_device *hid)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+
+ set_bit(GOODIX_HID_STARTED, &ts->flags);
+ return 0;
+}
+
+static void goodix_hid_close(struct hid_device *hid)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+
+ clear_bit(GOODIX_HID_STARTED, &ts->flags);
+}
+
+/* Return date length of response data */
+static int goodix_hid_check_ack_status(struct goodix_ts_data *ts, u32 *resp_len)
+{
+ struct goodix_hid_report_header hdr;
+ int retry = 20;
+ int error;
+ int len;
+
+ while (retry--) {
+ /*
+ * 3 bytes of hid request response data
+ * - byte 0: Ack flag, value of 1 for data ready
+ * - bytes 1-2: Response data length
+ */
+ error = goodix_spi_read(ts, GOODIX_HID_CMD_ADDR,
+ &hdr, sizeof(hdr));
+ if (!error && (hdr.flag & GOODIX_HID_ACK_READY_FLAG)) {
+ len = le16_to_cpu(hdr.size);
+ if (len < GOODIX_HID_PKG_LEN_SIZE) {
+ dev_err(ts->dev, "hrd.size too short: %d", len);
+ return -EINVAL;
+ }
+ *resp_len = len - GOODIX_HID_PKG_LEN_SIZE;
+ return 0;
+ }
+
+ /* Wait 10ms for another try */
+ usleep_range(10000, 11000);
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * goodix_hid_get_raw_report() - Process hidraw GET REPORT operation
+ * @hid: hid device instance
+ * @reportnum: Report ID
+ * @buf: Buffer for store the report date
+ * @len: Length fo report data
+ * @report_type: Report type
+ *
+ * The function for hid_ll_driver.get_raw_report to handle the HIDRAW ioctl
+ * get report request. The transmitted data follows the standard i2c-hid
+ * protocol with a specified header.
+ *
+ * Return: The length of the data in the buf on success, negative error code
+ */
+static int goodix_hid_get_raw_report(struct hid_device *hid,
+ unsigned char reportnum,
+ u8 *buf, size_t len,
+ unsigned char report_type)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+ u16 data_register = le16_to_cpu(ts->hid_desc.data_register);
+ u16 cmd_register = le16_to_cpu(ts->hid_desc.cmd_register);
+ u8 tmp_buf[GOODIX_HID_MAX_INBUF_SIZE];
+ int tx_len = 0, args_len = 0;
+ u32 response_data_len;
+ u8 args[3];
+ int error;
+
+ if (report_type == HID_OUTPUT_REPORT)
+ return -EINVAL;
+
+ if (reportnum == 3) {
+ /* Get win8 signature data */
+ error = goodix_spi_read(ts, GOODIX_HID_SIGN_ADDR, buf, len);
+ if (error) {
+ dev_err(ts->dev, "failed get win8 sign: %d", error);
+ return -EINVAL;
+ }
+ return len;
+ }
+
+ if (reportnum >= 0x0F)
+ args[args_len++] = reportnum;
+
+ put_unaligned_le16(data_register, args + args_len);
+ args_len += sizeof(data_register);
+
+ /* Clean 3 bytes of hid ack header data */
+ memset(tmp_buf, 0, GOODIX_HID_ACK_HEADER_SIZE);
+ tx_len += GOODIX_HID_ACK_HEADER_SIZE;
+
+ put_unaligned_le16(cmd_register, tmp_buf + tx_len);
+ tx_len += sizeof(cmd_register);
+
+ tmp_buf[tx_len] = (report_type == HID_FEATURE_REPORT ? 0x03 : 0x01) << 4;
+ tmp_buf[tx_len] |= reportnum >= 0x0F ? 0x0F : reportnum;
+ tx_len++;
+
+ tmp_buf[tx_len++] = GOODIX_HID_GET_REPORT_CMD;
+
+ memcpy(tmp_buf + tx_len, args, args_len);
+ tx_len += args_len;
+
+ /* Step1: write report request info */
+ error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, tmp_buf, tx_len);
+ if (error) {
+ dev_err(ts->dev, "failed send read feature cmd, %d", error);
+ return error;
+ }
+
+ /* No need read response data */
+ if (!len)
+ return 0;
+
+ /* Step2: check response data status */
+ error = goodix_hid_check_ack_status(ts, &response_data_len);
+ if (error)
+ return error;
+
+ /* Empty reprot response */
+ if (!response_data_len)
+ return 0;
+ len = min(len, response_data_len);
+ /* Step3: read response data(skip 2bytes of hid pkg length) */
+ error = goodix_spi_read(ts, GOODIX_HID_CMD_ADDR +
+ GOODIX_HID_ACK_HEADER_SIZE +
+ GOODIX_HID_PKG_LEN_SIZE, buf, len);
+ if (error) {
+ dev_err(ts->dev, "failed read hid response data, %d", error);
+ return error;
+ }
+
+ if (buf[0] != reportnum) {
+ dev_err(ts->dev, "incorrect report (%d vs %d expected)",
+ buf[0], reportnum);
+ return -EINVAL;
+ }
+ return len;
+}
+
+/**
+ * goodix_hid_set_raw_report() - process hidraw SET REPORT operation
+ * @hid: HID device
+ * @reportnum: Report ID
+ * @buf: Buffer for communication
+ * @len: Length of data in the buffer
+ * @report_type: Report type
+ *
+ * The function for hid_ll_driver.get_raw_report to handle the HIDRAW ioctl
+ * set report request. The transmitted data follows the standard i2c-hid
+ * protocol with a specified header.
+ *
+ * Return: The length of the data sent, negative error code on failure
+ */
+static int goodix_hid_set_raw_report(struct hid_device *hid,
+ unsigned char reportnum,
+ __u8 *buf, size_t len,
+ unsigned char report_type)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+ u16 data_register = le16_to_cpu(ts->hid_desc.data_register);
+ u16 cmd_register = le16_to_cpu(ts->hid_desc.cmd_register);
+ int tx_len = 0, args_len = 0;
+ u8 tmp_buf[GOODIX_HID_MAX_INBUF_SIZE];
+ u8 args[5];
+ int error;
+
+ if (reportnum >= 0x0F) {
+ args[args_len++] = reportnum;
+ reportnum = 0x0F;
+ }
+
+ put_unaligned_le16(data_register, args + args_len);
+ args_len += sizeof(data_register);
+
+ put_unaligned_le16(GOODIX_HID_PKG_LEN_SIZE + len, args + args_len);
+ args_len += GOODIX_HID_PKG_LEN_SIZE;
+
+ /* Clean 3 bytes of hid ack header data */
+ memset(tmp_buf, 0, GOODIX_HID_ACK_HEADER_SIZE);
+ tx_len += GOODIX_HID_ACK_HEADER_SIZE;
+
+ put_unaligned_le16(cmd_register, tmp_buf + tx_len);
+ tx_len += sizeof(cmd_register);
+
+ tmp_buf[tx_len++] = ((report_type == HID_FEATURE_REPORT ? 0x03 : 0x02) << 4) | reportnum;
+ tmp_buf[tx_len++] = GOODIX_HID_SET_REPORT_CMD;
+
+ memcpy(tmp_buf + tx_len, args, args_len);
+ tx_len += args_len;
+
+ memcpy(tmp_buf + tx_len, buf, len);
+ tx_len += len;
+
+ error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, tmp_buf, tx_len);
+ if (error) {
+ dev_err(ts->dev, "failed send report: %*ph", tx_len, tmp_buf);
+ return error;
+ }
+ return len;
+}
+
+static int goodix_hid_raw_request(struct hid_device *hid,
+ unsigned char reportnum,
+ __u8 *buf, size_t len,
+ unsigned char rtype, int reqtype)
+{
+ struct goodix_ts_data *ts = hid->driver_data;
+ int error = -EINVAL;
+
+ guard(mutex)(&ts->hid_request_lock);
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ error = goodix_hid_get_raw_report(hid, reportnum, buf,
+ len, rtype);
+ break;
+ case HID_REQ_SET_REPORT:
+ if (buf[0] == reportnum)
+ error = goodix_hid_set_raw_report(hid, reportnum,
+ buf, len, rtype);
+ break;
+ default:
+ break;
+ }
+
+ return error;
+}
+
+static struct hid_ll_driver goodix_hid_ll_driver = {
+ .parse = goodix_hid_parse,
+ .start = goodix_hid_start,
+ .stop = goodix_hid_stop,
+ .open = goodix_hid_open,
+ .close = goodix_hid_close,
+ .raw_request = goodix_hid_raw_request
+};
+
+static irqreturn_t goodix_hid_irq(int irq, void *data)
+{
+ struct goodix_ts_data *ts = data;
+ struct goodix_hid_report_event *event;
+ struct goodix_hid_report_package *pkg;
+ u16 report_size;
+
+ if (!test_bit(GOODIX_HID_STARTED, &ts->flags))
+ return IRQ_HANDLED;
+ /*
+ * First, read buffer with space for header and coordinate package:
+ * - event header = 3 bytes
+ * - coordinate event = GOODIX_HID_COOR_PKG_LEN bytes
+ *
+ * If the data size info in the event header exceeds
+ * GOODIX_HID_COOR_PKG_LEN, it means that there are other packages
+ * besides the coordinate package.
+ */
+ event = goodix_get_event_report(ts, ts->hid_report_addr, ts->event_buf,
+ GOODIX_HID_ACK_HEADER_SIZE +
+ GOODIX_HID_COOR_PKG_LEN);
+ if (!event) {
+ dev_err(ts->dev, "failed get coordinate data");
+ return IRQ_HANDLED;
+ }
+
+ /* Check coordinate data valid falg */
+ if (event->hdr.flag != GOODIX_HID_REPORT_READY_FLAG)
+ return IRQ_HANDLED;
+
+ pkg = (struct goodix_hid_report_package *)event->data;
+ if (le16_to_cpu(pkg->size) < GOODIX_HID_PKG_LEN_SIZE) {
+ dev_err(ts->dev, "invalid coordinate event package size, %d",
+ le16_to_cpu(pkg->size));
+ return IRQ_HANDLED;
+ }
+ hid_input_report(ts->hid, HID_INPUT_REPORT, pkg->data,
+ le16_to_cpu(pkg->size) - GOODIX_HID_PKG_LEN_SIZE, 1);
+
+ report_size = le16_to_cpu(event->hdr.size);
+ /* Check if there are other packages */
+ if (report_size <= GOODIX_HID_COOR_PKG_LEN)
+ return IRQ_HANDLED;
+
+ if (report_size >= ts->hid_max_event_sz) {
+ dev_err(ts->dev, "package size exceed limit %d vs %d",
+ report_size, ts->hid_max_event_sz);
+ return IRQ_HANDLED;
+ }
+
+ /* Read the package behind the coordinate data */
+ pkg = goodix_get_event_report(ts, ts->hid_report_addr + sizeof(*event),
+ ts->event_buf,
+ report_size - GOODIX_HID_COOR_PKG_LEN);
+ if (!pkg) {
+ dev_err(ts->dev, "failed read attachment data content");
+ return IRQ_HANDLED;
+ }
+
+ hid_input_report(ts->hid, HID_INPUT_REPORT, pkg->data,
+ le16_to_cpu(pkg->size) - GOODIX_HID_PKG_LEN_SIZE, 1);
+
+ return IRQ_HANDLED;
+}
+
+static int goodix_hid_init(struct goodix_ts_data *ts)
+{
+ struct hid_device *hid;
+ int error;
+
+ /* Get hid descriptor */
+ error = goodix_spi_read(ts, GOODIX_HID_DESC_ADDR, &ts->hid_desc,
+ sizeof(ts->hid_desc));
+ if (error) {
+ dev_err(ts->dev, "failed get hid desc, %d", error);
+ return error;
+ }
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ hid->driver_data = ts;
+ hid->ll_driver = &goodix_hid_ll_driver;
+ hid->bus = BUS_SPI;
+ hid->dev.parent = &ts->spi->dev;
+
+ hid->version = le16_to_cpu(ts->hid_desc.bcd_version);
+ hid->vendor = le16_to_cpu(ts->hid_desc.vendor_id);
+ hid->product = le16_to_cpu(ts->hid_desc.product_id);
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-gdix",
+ hid->vendor, hid->product);
+
+ error = hid_add_device(hid);
+ if (error) {
+ dev_err(ts->dev, "failed add hid device, %d", error);
+ hid_destroy_device(hid);
+ return error;
+ }
+
+ ts->hid = hid;
+ return 0;
+}
+
+static int goodix_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct goodix_ts_data *ts;
+ int error;
+
+ /* init spi_device */
+ spi->mode = SPI_MODE_0;
+ spi->bits_per_word = 8;
+ error = spi_setup(spi);
+ if (error)
+ return error;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return -ENOMEM;
+
+ mutex_init(&ts->hid_request_lock);
+ spi_set_drvdata(spi, ts);
+ ts->spi = spi;
+ ts->dev = dev;
+ ts->hid_max_event_sz = GOODIX_SPI_READ_PREFIX_LEN +
+ GOODIX_HID_ACK_HEADER_SIZE + GOODIX_HID_COOR_PKG_LEN;
+ ts->event_buf = devm_kmalloc(dev, ts->hid_max_event_sz, GFP_KERNEL);
+ if (!ts->event_buf)
+ return -ENOMEM;
+
+ ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(ts->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
+ "failed to request reset gpio\n");
+
+ ts->hid_report_addr = GOODIX_HID_REPORT_ADDR;
+ error = goodix_dev_confirm(ts);
+ if (error)
+ return error;
+
+ /* Waits 150ms for firmware to fully boot */
+ msleep(GOODIX_NORMAL_RESET_DELAY_MS);
+
+ error = goodix_hid_init(ts);
+ if (error) {
+ dev_err(dev, "failed init hid device");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(&ts->spi->dev, ts->spi->irq,
+ NULL, goodix_hid_irq, IRQF_ONESHOT,
+ "goodix_spi_hid", ts);
+ if (error) {
+ dev_err(ts->dev, "could not register interrupt, irq = %d, %d",
+ ts->spi->irq, error);
+ goto err_destroy_hid;
+ }
+
+ return 0;
+
+err_destroy_hid:
+ hid_destroy_device(ts->hid);
+ return error;
+}
+
+static void goodix_spi_remove(struct spi_device *spi)
+{
+ struct goodix_ts_data *ts = spi_get_drvdata(spi);
+
+ disable_irq(spi->irq);
+ hid_destroy_device(ts->hid);
+}
+
+static int goodix_spi_set_power(struct goodix_ts_data *ts, int power_state)
+{
+ u8 power_control_cmd[] = {0x00, 0x00, 0x00, 0x87, 0x02, 0x00, 0x08};
+ int error;
+
+ /* value 0 for power on, 1 for power sleep */
+ power_control_cmd[5] = power_state;
+
+ guard(mutex)(&ts->hid_request_lock);
+ error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, power_control_cmd,
+ sizeof(power_control_cmd));
+ if (error) {
+ dev_err(ts->dev, "failed set power mode: %s",
+ power_state == GOODIX_SPI_POWER_ON ? "on" : "sleep");
+ return error;
+ }
+ return 0;
+}
+
+static int goodix_spi_suspend(struct device *dev)
+{
+ struct goodix_ts_data *ts = dev_get_drvdata(dev);
+
+ disable_irq(ts->spi->irq);
+ return goodix_spi_set_power(ts, GOODIX_SPI_POWER_SLEEP);
+}
+
+static int goodix_spi_resume(struct device *dev)
+{
+ struct goodix_ts_data *ts = dev_get_drvdata(dev);
+
+ enable_irq(ts->spi->irq);
+ return goodix_spi_set_power(ts, GOODIX_SPI_POWER_ON);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(goodix_spi_pm_ops,
+ goodix_spi_suspend, goodix_spi_resume);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id goodix_spi_acpi_match[] = {
+ { "GXTS7986" },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, goodix_spi_acpi_match);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id goodix_spi_of_match[] = {
+ { .compatible = "goodix,gt7986u-spifw", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, goodix_spi_of_match);
+#endif
+
+static const struct spi_device_id goodix_spi_ids[] = {
+ { "gt7986u" },
+ { },
+};
+MODULE_DEVICE_TABLE(spi, goodix_spi_ids);
+
+static struct spi_driver goodix_spi_driver = {
+ .driver = {
+ .name = "goodix-spi-hid",
+ .acpi_match_table = ACPI_PTR(goodix_spi_acpi_match),
+ .of_match_table = of_match_ptr(goodix_spi_of_match),
+ .pm = pm_sleep_ptr(&goodix_spi_pm_ops),
+ },
+ .probe = goodix_spi_probe,
+ .remove = goodix_spi_remove,
+ .id_table = goodix_spi_ids,
+};
+module_spi_driver(goodix_spi_driver);
+
+MODULE_DESCRIPTION("Goodix SPI driver for HID touchscreen");
+MODULE_AUTHOR("Goodix, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index c6bdb9c4ef3e..4c1ccf7a267a 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -22,8 +22,7 @@
#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 <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
#include "hid-vivaldi-common.h"
@@ -255,7 +254,7 @@ out:
return retval;
}
-static int cbas_ec_remove(struct platform_device *pdev)
+static void cbas_ec_remove(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
@@ -266,14 +265,15 @@ static int cbas_ec_remove(struct platform_device *pdev)
cbas_ec_set_input(NULL);
mutex_unlock(&cbas_ec_reglock);
- return 0;
}
+#ifdef CONFIG_ACPI
static const struct acpi_device_id cbas_ec_acpi_ids[] = {
{ "GOOG000B", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids);
+#endif
#ifdef CONFIG_OF
static const struct of_device_id cbas_ec_of_match[] = {
@@ -419,38 +419,15 @@ static int hammer_event(struct hid_device *hid, struct hid_field *field,
return 0;
}
-static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type,
- unsigned application, unsigned usage)
-{
- struct hid_report_enum *re = &hdev->report_enum[report_type];
- struct hid_report *report;
- int i, j;
-
- list_for_each_entry(report, &re->report_list, list) {
- if (report->application != application)
- continue;
-
- for (i = 0; i < report->maxfield; i++) {
- struct hid_field *field = report->field[i];
-
- for (j = 0; j < field->maxusage; j++)
- if (field->usage[j].hid == usage)
- return true;
- }
- }
-
- return false;
-}
-
static bool hammer_has_folded_event(struct hid_device *hdev)
{
- return hammer_has_usage(hdev, HID_INPUT_REPORT,
+ return !!hid_find_field(hdev, HID_INPUT_REPORT,
HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED);
}
static bool hammer_has_backlight_control(struct hid_device *hdev)
{
- return hammer_has_usage(hdev, HID_OUTPUT_REPORT,
+ return !!hid_find_field(hdev, HID_OUTPUT_REPORT,
HID_GD_KEYBOARD, HID_AD_BRIGHTNESS);
}
@@ -642,4 +619,5 @@ static void __exit hammer_exit(void)
}
module_exit(hammer_exit);
+MODULE_DESCRIPTION("HID driver for Google Hammer device.");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c
new file mode 100644
index 000000000000..6b38d2421d3d
--- /dev/null
+++ b/drivers/hid/hid-google-stadiaff.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Stadia controller rumble support.
+ *
+ * Copyright 2023 Google LLC
+ */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define STADIA_FF_REPORT_ID 5
+
+struct stadiaff_device {
+ struct hid_device *hid;
+ struct hid_report *report;
+ spinlock_t lock;
+ bool removed;
+ uint16_t strong_magnitude;
+ uint16_t weak_magnitude;
+ struct work_struct work;
+};
+
+static void stadiaff_work(struct work_struct *work)
+{
+ struct stadiaff_device *stadiaff =
+ container_of(work, struct stadiaff_device, work);
+ struct hid_field *rumble_field = stadiaff->report->field[0];
+ unsigned long flags;
+
+ spin_lock_irqsave(&stadiaff->lock, flags);
+ rumble_field->value[0] = stadiaff->strong_magnitude;
+ rumble_field->value[1] = stadiaff->weak_magnitude;
+ spin_unlock_irqrestore(&stadiaff->lock, flags);
+
+ hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
+}
+
+static int stadiaff_play(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
+ unsigned long flags;
+
+ spin_lock_irqsave(&stadiaff->lock, flags);
+ if (!stadiaff->removed) {
+ stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
+ stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
+ schedule_work(&stadiaff->work);
+ }
+ spin_unlock_irqrestore(&stadiaff->lock, flags);
+
+ return 0;
+}
+
+static int stadiaff_init(struct hid_device *hid)
+{
+ struct stadiaff_device *stadiaff;
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct input_dev *dev;
+ int error;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+ dev = hidinput->input;
+
+ report = hid_validate_values(hid, HID_OUTPUT_REPORT,
+ STADIA_FF_REPORT_ID, 0, 2);
+ if (!report)
+ return -ENODEV;
+
+ stadiaff = devm_kzalloc(&hid->dev, sizeof(struct stadiaff_device),
+ GFP_KERNEL);
+ if (!stadiaff)
+ return -ENOMEM;
+
+ hid_set_drvdata(hid, stadiaff);
+
+ input_set_capability(dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(dev, NULL, stadiaff_play);
+ if (error)
+ return error;
+
+ stadiaff->removed = false;
+ stadiaff->hid = hid;
+ stadiaff->report = report;
+ INIT_WORK(&stadiaff->work, stadiaff_work);
+ spin_lock_init(&stadiaff->lock);
+
+ hid_info(hid, "Force Feedback for Google Stadia controller\n");
+
+ return 0;
+}
+
+static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = stadiaff_init(hdev);
+ if (ret) {
+ hid_err(hdev, "force feedback init failed\n");
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void stadia_remove(struct hid_device *hid)
+{
+ struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
+ unsigned long flags;
+
+ spin_lock_irqsave(&stadiaff->lock, flags);
+ stadiaff->removed = true;
+ spin_unlock_irqrestore(&stadiaff->lock, flags);
+
+ cancel_work_sync(&stadiaff->work);
+ hid_hw_stop(hid);
+}
+
+static const struct hid_device_id stadia_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, stadia_devices);
+
+static struct hid_driver stadia_driver = {
+ .name = "stadia",
+ .id_table = stadia_devices,
+ .probe = stadia_probe,
+ .remove = stadia_remove,
+};
+module_hid_driver(stadia_driver);
+
+MODULE_DESCRIPTION("Google Stadia controller rumble support.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gyration.c b/drivers/hid/hid-gyration.c
index b99a611479b3..6606b57abe83 100644
--- a/drivers/hid/hid-gyration.c
+++ b/drivers/hid/hid-gyration.c
@@ -87,4 +87,5 @@ static struct hid_driver gyration_driver = {
};
module_hid_driver(gyration_driver);
+MODULE_DESCRIPTION("HID driver for some gyration \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c
new file mode 100644
index 000000000000..fc8a9997f815
--- /dev/null
+++ b/drivers/hid/hid-haptic.c
@@ -0,0 +1,580 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID Haptic support for Linux
+ *
+ * Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+#include <linux/input/mt.h>
+#include <linux/module.h>
+
+#include "hid-haptic.h"
+
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage *usage)
+{
+ u16 usage_hid;
+
+ if (usage->hid == HID_HP_AUTOTRIGGER) {
+ if (usage->usage_index >= field->report_count) {
+ dev_err(&hdev->dev,
+ "HID_HP_AUTOTRIGGER out of range\n");
+ return;
+ }
+
+ hid_device_io_start(hdev);
+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
+ hid_hw_wait(hdev);
+ hid_device_io_stop(hdev);
+ haptic->default_auto_trigger =
+ field->value[usage->usage_index];
+ haptic->auto_trigger_report = field->report;
+ } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
+ usage_hid = usage->hid & HID_USAGE;
+ switch (field->logical) {
+ case HID_HP_WAVEFORMLIST:
+ if (usage_hid > haptic->max_waveform_id)
+ haptic->max_waveform_id = usage_hid;
+ break;
+ case HID_HP_DURATIONLIST:
+ if (usage_hid > haptic->max_duration_id)
+ haptic->max_duration_id = usage_hid;
+ break;
+ default:
+ break;
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
+
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field)
+{
+ if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
+ haptic->force_logical_minimum = field->logical_minimum;
+ haptic->force_physical_minimum = field->physical_minimum;
+ haptic->force_resolution = input_abs_get_res(hi->input,
+ ABS_MT_PRESSURE);
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
+
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if (usage->hid == HID_HP_MANUALTRIGGER) {
+ haptic->manual_trigger_report = field->report;
+ /* we don't really want to map these fields */
+ return -1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
+
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi)
+{
+
+ if (hi->application == HID_DG_TOUCHPAD) {
+ if (haptic->auto_trigger_report &&
+ haptic->manual_trigger_report) {
+ __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
+ return 1;
+ }
+ return 0;
+ }
+ return -1;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
+
+static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
+ struct hid_field *field)
+{
+ int count = field->report_count;
+ int n;
+ u16 usage_hid;
+
+ for (n = 0; n < count; n++) {
+ switch (field->usage[n].hid & HID_USAGE_PAGE) {
+ case HID_UP_ORDINAL:
+ usage_hid = field->usage[n].hid & HID_USAGE;
+ switch (field->logical) {
+ case HID_HP_WAVEFORMLIST:
+ haptic->hid_usage_map[usage_hid] = field->value[n];
+ if (field->value[n] ==
+ (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+ haptic->press_ordinal = usage_hid;
+ } else if (field->value[n] ==
+ (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+ haptic->release_ordinal = usage_hid;
+ }
+ break;
+ case HID_HP_DURATIONLIST:
+ haptic->duration_map[usage_hid] =
+ field->value[n];
+ break;
+ default:
+ break;
+ }
+ break;
+ case HID_UP_HAPTIC:
+ switch (field->usage[n].hid) {
+ case HID_HP_WAVEFORMVENDORID:
+ haptic->vendor_id = field->value[n];
+ break;
+ case HID_HP_WAVEFORMVENDORPAGE:
+ haptic->vendor_page = field->value[n];
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ /* Should not really happen */
+ break;
+ }
+ }
+}
+
+static void fill_effect_buf(struct hid_haptic_device *haptic,
+ struct ff_haptic_effect *effect,
+ struct hid_haptic_effect *haptic_effect,
+ int waveform_ordinal)
+{
+ struct hid_report *rep = haptic->manual_trigger_report;
+ struct hid_usage *usage;
+ struct hid_field *field;
+ s32 value;
+ int i, j;
+ u8 *buf = haptic_effect->report_buf;
+
+ mutex_lock(&haptic->manual_trigger_mutex);
+ for (i = 0; i < rep->maxfield; i++) {
+ field = rep->field[i];
+ /* Ignore if report count is out of bounds. */
+ if (field->report_count < 1)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ usage = &field->usage[j];
+
+ switch (usage->hid) {
+ case HID_HP_INTENSITY:
+ if (effect->intensity > 100) {
+ value = field->logical_maximum;
+ } else {
+ value = field->logical_minimum +
+ effect->intensity *
+ (field->logical_maximum -
+ field->logical_minimum) / 100;
+ }
+ break;
+ case HID_HP_REPEATCOUNT:
+ value = effect->repeat_count;
+ break;
+ case HID_HP_RETRIGGERPERIOD:
+ value = effect->retrigger_period;
+ break;
+ case HID_HP_MANUALTRIGGER:
+ value = waveform_ordinal;
+ break;
+ default:
+ break;
+ }
+
+ field->value[j] = value;
+ }
+ }
+
+ hid_output_report(rep, buf);
+ mutex_unlock(&haptic->manual_trigger_mutex);
+}
+
+static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ int mode)
+{
+ struct hid_report *rep = haptic->auto_trigger_report;
+ struct hid_field *field;
+ s32 value;
+ int i, j;
+
+ if (mode == HID_HAPTIC_MODE_HOST)
+ value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
+ else
+ value = haptic->default_auto_trigger;
+
+ mutex_lock(&haptic->auto_trigger_mutex);
+ for (i = 0; i < rep->maxfield; i++) {
+ field = rep->field[i];
+ /* Ignore if report count is out of bounds. */
+ if (field->report_count < 1)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
+ field->value[j] = value;
+ }
+ }
+
+ /* send the report */
+ hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
+ mutex_unlock(&haptic->auto_trigger_mutex);
+ haptic->mode = mode;
+}
+
+static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct ff_device *ff = dev->ff;
+ struct hid_haptic_device *haptic = ff->private;
+ int i, ordinal = 0;
+ bool switch_modes = false;
+
+ /* If vendor range, check vendor id and page */
+ if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
+ effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
+ (effect->u.haptic.vendor_id != haptic->vendor_id ||
+ effect->u.haptic.vendor_waveform_page != haptic->vendor_page))
+ return -EINVAL;
+
+ /* Check hid_usage */
+ for (i = 1; i <= haptic->max_waveform_id; i++) {
+ if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) {
+ ordinal = i;
+ break;
+ }
+ }
+ if (ordinal < 1)
+ return -EINVAL;
+
+ /* Fill the buffer for the effect id */
+ fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
+ ordinal);
+
+ if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
+ effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
+ switch_modes = true;
+
+ /* If device is in autonomous mode, and the uploaded effect signals userspace
+ * wants control of the device, change modes
+ */
+ if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
+
+ return 0;
+}
+
+static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
+ struct hid_haptic_effect *effect)
+{
+ int ret;
+
+ ret = hid_hw_output_report(hdev, effect->report_buf,
+ haptic->manual_trigger_report_len);
+ if (ret < 0) {
+ ret = hid_hw_raw_request(hdev,
+ haptic->manual_trigger_report->id,
+ effect->report_buf,
+ haptic->manual_trigger_report_len,
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ }
+
+ return ret;
+}
+
+static void haptic_work_handler(struct work_struct *work)
+{
+
+ struct hid_haptic_effect *effect = container_of(work,
+ struct hid_haptic_effect,
+ work);
+ struct input_dev *dev = effect->input_dev;
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct hid_haptic_device *haptic = dev->ff->private;
+
+ mutex_lock(&haptic->manual_trigger_mutex);
+ if (effect != &haptic->stop_effect)
+ play_effect(hdev, haptic, &haptic->stop_effect);
+
+ play_effect(hdev, haptic, effect);
+ mutex_unlock(&haptic->manual_trigger_mutex);
+
+}
+
+static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
+{
+ struct hid_haptic_device *haptic = dev->ff->private;
+
+ if (value)
+ queue_work(haptic->wq, &haptic->effect[effect_id].work);
+ else
+ queue_work(haptic->wq, &haptic->stop_effect.work);
+
+ return 0;
+}
+
+static void effect_set_default(struct ff_effect *effect)
+{
+ effect->type = FF_HAPTIC;
+ effect->id = -1;
+ effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
+ effect->u.haptic.intensity = 100;
+ effect->u.haptic.retrigger_period = 0;
+ effect->u.haptic.repeat_count = 0;
+}
+
+static int hid_haptic_erase(struct input_dev *dev, int effect_id)
+{
+ struct hid_haptic_device *haptic = dev->ff->private;
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct ff_effect effect;
+ int ordinal;
+
+ effect_set_default(&effect);
+
+ if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
+ ordinal = haptic->release_ordinal;
+ if (!ordinal) {
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ if (haptic->mode == HID_HAPTIC_MODE_HOST)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+ } else
+ effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
+
+ fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
+ ordinal);
+ } else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
+ ordinal = haptic->press_ordinal;
+ if (!ordinal) {
+ ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
+ if (haptic->mode == HID_HAPTIC_MODE_HOST)
+ switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
+ }
+ else
+ effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
+
+ fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
+ ordinal);
+ }
+
+ return 0;
+}
+
+static void hid_haptic_destroy(struct ff_device *ff)
+{
+ struct hid_haptic_device *haptic = ff->private;
+ struct hid_device *hdev = haptic->hdev;
+ int r;
+
+ if (hdev)
+ put_device(&hdev->dev);
+
+ kfree(haptic->stop_effect.report_buf);
+ haptic->stop_effect.report_buf = NULL;
+
+ if (haptic->effect) {
+ for (r = 0; r < ff->max_effects; r++)
+ kfree(haptic->effect[r].report_buf);
+ kfree(haptic->effect);
+ }
+ haptic->effect = NULL;
+
+ destroy_workqueue(haptic->wq);
+ haptic->wq = NULL;
+
+ kfree(haptic->duration_map);
+ haptic->duration_map = NULL;
+
+ kfree(haptic->hid_usage_map);
+ haptic->hid_usage_map = NULL;
+
+ module_put(THIS_MODULE);
+}
+
+int hid_haptic_init(struct hid_device *hdev,
+ struct hid_haptic_device **haptic_ptr)
+{
+ struct hid_haptic_device *haptic = *haptic_ptr;
+ struct input_dev *dev = NULL;
+ struct hid_input *hidinput;
+ struct ff_device *ff;
+ int ret = 0, r;
+ struct ff_haptic_effect stop_effect = {
+ .hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
+ };
+ const char *prefix = "hid-haptic";
+ char *name;
+ int (*flush)(struct input_dev *dev, struct file *file);
+ int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
+
+ haptic->hdev = hdev;
+ haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
+ haptic->max_duration_id = max(2u, haptic->max_duration_id);
+
+ haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
+ sizeof(u16), GFP_KERNEL);
+ if (!haptic->hid_usage_map) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
+ sizeof(u32), GFP_KERNEL);
+ if (!haptic->duration_map) {
+ ret = -ENOMEM;
+ goto usage_map;
+ }
+
+ if (haptic->max_waveform_id != haptic->max_duration_id)
+ dev_warn(&hdev->dev,
+ "Haptic duration and waveform lists have different max id (%u and %u).\n",
+ haptic->max_duration_id, haptic->max_waveform_id);
+
+ haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
+ HID_HP_WAVEFORMNONE & HID_USAGE;
+ haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
+ HID_HP_WAVEFORMSTOP & HID_USAGE;
+
+ mutex_init(&haptic->auto_trigger_mutex);
+ for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
+ parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
+
+ list_for_each_entry(hidinput, &hdev->inputs, list) {
+ if (hidinput->application == HID_DG_TOUCHPAD) {
+ dev = hidinput->input;
+ break;
+ }
+ }
+
+ if (!dev) {
+ dev_err(&hdev->dev, "Failed to find the input device\n");
+ ret = -ENODEV;
+ goto duration_map;
+ }
+
+ haptic->input_dev = dev;
+ haptic->manual_trigger_report_len =
+ hid_report_len(haptic->manual_trigger_report);
+ mutex_init(&haptic->manual_trigger_mutex);
+ name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
+ if (name) {
+ sprintf(name, "%s %s", prefix, hdev->name);
+ haptic->wq = create_singlethread_workqueue(name);
+ kfree(name);
+ }
+ if (!haptic->wq) {
+ ret = -ENOMEM;
+ goto duration_map;
+ }
+ haptic->effect = kcalloc(FF_MAX_EFFECTS,
+ sizeof(struct hid_haptic_effect), GFP_KERNEL);
+ if (!haptic->effect) {
+ ret = -ENOMEM;
+ goto output_queue;
+ }
+ for (r = 0; r < FF_MAX_EFFECTS; r++) {
+ haptic->effect[r].report_buf =
+ hid_alloc_report_buf(haptic->manual_trigger_report,
+ GFP_KERNEL);
+ if (!haptic->effect[r].report_buf) {
+ dev_err(&hdev->dev,
+ "Failed to allocate a buffer for an effect.\n");
+ ret = -ENOMEM;
+ goto buffer_free;
+ }
+ haptic->effect[r].input_dev = dev;
+ INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
+ }
+ haptic->stop_effect.report_buf =
+ hid_alloc_report_buf(haptic->manual_trigger_report,
+ GFP_KERNEL);
+ if (!haptic->stop_effect.report_buf) {
+ dev_err(&hdev->dev,
+ "Failed to allocate a buffer for stop effect.\n");
+ ret = -ENOMEM;
+ goto buffer_free;
+ }
+ haptic->stop_effect.input_dev = dev;
+ INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
+ fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
+ HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
+
+ input_set_capability(dev, EV_FF, FF_HAPTIC);
+
+ flush = dev->flush;
+ event = dev->event;
+ ret = input_ff_create(dev, FF_MAX_EFFECTS);
+ if (ret) {
+ dev_err(&hdev->dev, "Failed to create ff device.\n");
+ goto stop_buffer_free;
+ }
+
+ ff = dev->ff;
+ ff->private = haptic;
+ ff->upload = hid_haptic_upload_effect;
+ ff->playback = hid_haptic_playback;
+ ff->erase = hid_haptic_erase;
+ ff->destroy = hid_haptic_destroy;
+ if (!try_module_get(THIS_MODULE)) {
+ dev_err(&hdev->dev, "Failed to increase module count.\n");
+ goto input_free;
+ }
+ if (!get_device(&hdev->dev)) {
+ dev_err(&hdev->dev, "Failed to get hdev device.\n");
+ module_put(THIS_MODULE);
+ goto input_free;
+ }
+ return 0;
+
+input_free:
+ input_ff_destroy(dev);
+ /* Do not let double free happen, input_ff_destroy will call
+ * hid_haptic_destroy.
+ */
+ *haptic_ptr = NULL;
+ /* Restore dev flush and event */
+ dev->flush = flush;
+ dev->event = event;
+ return ret;
+stop_buffer_free:
+ kfree(haptic->stop_effect.report_buf);
+ haptic->stop_effect.report_buf = NULL;
+buffer_free:
+ while (--r >= 0)
+ kfree(haptic->effect[r].report_buf);
+ kfree(haptic->effect);
+ haptic->effect = NULL;
+output_queue:
+ destroy_workqueue(haptic->wq);
+ haptic->wq = NULL;
+duration_map:
+ kfree(haptic->duration_map);
+ haptic->duration_map = NULL;
+usage_map:
+ kfree(haptic->hid_usage_map);
+ haptic->hid_usage_map = NULL;
+exit:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_init);
+
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
+{
+ haptic->pressure_sum = 0;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
+
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure)
+{
+ haptic->pressure_sum += pressure;
+}
+EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
diff --git a/drivers/hid/hid-haptic.h b/drivers/hid/hid-haptic.h
new file mode 100644
index 000000000000..c6539ac04c1d
--- /dev/null
+++ b/drivers/hid/hid-haptic.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * HID Haptic support for Linux
+ *
+ * Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
+ */
+
+#include <linux/hid.h>
+
+#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
+#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
+
+#define HID_HAPTIC_MODE_DEVICE 0
+#define HID_HAPTIC_MODE_HOST 1
+
+struct hid_haptic_effect {
+ u8 *report_buf;
+ struct input_dev *input_dev;
+ struct work_struct work;
+ struct list_head control;
+ struct mutex control_mutex;
+};
+
+struct hid_haptic_effect_node {
+ struct list_head node;
+ struct file *file;
+};
+
+struct hid_haptic_device {
+ struct input_dev *input_dev;
+ struct hid_device *hdev;
+ struct hid_report *auto_trigger_report;
+ struct mutex auto_trigger_mutex;
+ struct workqueue_struct *wq;
+ struct hid_report *manual_trigger_report;
+ struct mutex manual_trigger_mutex;
+ size_t manual_trigger_report_len;
+ int pressed_state;
+ s32 pressure_sum;
+ s32 force_logical_minimum;
+ s32 force_physical_minimum;
+ s32 force_resolution;
+ u32 mode;
+ u32 default_auto_trigger;
+ u32 vendor_page;
+ u32 vendor_id;
+ u32 max_waveform_id;
+ u32 max_duration_id;
+ u16 *hid_usage_map;
+ u32 *duration_map;
+ u16 press_ordinal;
+ u16 release_ordinal;
+ struct hid_haptic_effect *effect;
+ struct hid_haptic_effect stop_effect;
+};
+
+#if IS_ENABLED(CONFIG_HID_HAPTIC)
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage
+ *usage);
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field);
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max);
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi);
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure);
+#else
+static inline
+void hid_haptic_feature_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_field *field, struct hid_usage
+ *usage)
+{}
+static inline
+bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
+ struct hid_input *hi, struct hid_field *field)
+{
+ return false;
+}
+static inline
+int hid_haptic_input_mapping(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi,
+ struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ return 0;
+}
+static inline
+int hid_haptic_input_configured(struct hid_device *hdev,
+ struct hid_haptic_device *haptic,
+ struct hid_input *hi)
+{
+ return 0;
+}
+static inline
+void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
+{}
+static inline
+int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
+{
+ return 0;
+}
+static inline
+void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
+static inline
+bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
+{
+ return false;
+}
+static inline
+void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
+static inline
+void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
+ __s32 pressure)
+{}
+#endif
diff --git a/drivers/hid/hid-holtek-kbd.c b/drivers/hid/hid-holtek-kbd.c
index 403506b9697e..d13543517a6c 100644
--- a/drivers/hid/hid-holtek-kbd.c
+++ b/drivers/hid/hid-holtek-kbd.c
@@ -27,7 +27,7 @@
* to the boot interface.
*/
-static __u8 holtek_kbd_rdesc_fixed[] = {
+static const __u8 holtek_kbd_rdesc_fixed[] = {
/* Original report descriptor, with reduced number of consumer usages */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x80, /* Usage (Sys Control), */
@@ -102,14 +102,14 @@ static __u8 holtek_kbd_rdesc_fixed[] = {
0xC0, /* End Collection */
};
-static __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
- rdesc = holtek_kbd_rdesc_fixed;
*rsize = sizeof(holtek_kbd_rdesc_fixed);
+ return holtek_kbd_rdesc_fixed;
}
return rdesc;
}
@@ -130,6 +130,10 @@ static int holtek_kbd_input_event(struct input_dev *dev, unsigned int type,
return -ENODEV;
boot_hid = usb_get_intfdata(boot_interface);
+ if (list_empty(&boot_hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
boot_hid_input = list_first_entry(&boot_hid->inputs,
struct hid_input, list);
@@ -176,4 +180,5 @@ static struct hid_driver holtek_kbd_driver = {
};
module_hid_driver(holtek_kbd_driver);
+MODULE_DESCRIPTION("HID driver for Holtek keyboard");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-holtek-mouse.c b/drivers/hid/hid-holtek-mouse.c
index 7c907939bfae..b618a1646c13 100644
--- a/drivers/hid/hid-holtek-mouse.c
+++ b/drivers/hid/hid-holtek-mouse.c
@@ -29,8 +29,8 @@
* - USB ID 04d9:a0c2, sold as ETEKCITY Scroll T-140 Gaming Mouse
*/
-static __u8 *holtek_mouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *holtek_mouse_report_fixup(struct hid_device *hdev,
+ __u8 *rdesc, unsigned int *rsize)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -110,4 +110,5 @@ static struct hid_driver holtek_mouse_driver = {
};
module_hid_driver(holtek_mouse_driver);
+MODULE_DESCRIPTION("HID driver for Holtek gaming mice");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
index f33485d83d24..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));
@@ -422,6 +422,25 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
return 0;
}
+static int mousevsc_hid_probe(struct hid_device *hid_dev, const struct hid_device_id *id)
+{
+ int ret;
+
+ ret = hid_parse(hid_dev);
+ if (ret) {
+ hid_err(hid_dev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
+ if (ret) {
+ hid_err(hid_dev, "hw start failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct hid_ll_driver mousevsc_ll_driver = {
.parse = mousevsc_hid_parse,
.open = mousevsc_hid_open,
@@ -431,7 +450,16 @@ static const struct hid_ll_driver mousevsc_ll_driver = {
.raw_request = mousevsc_hid_raw_request,
};
-static struct hid_driver mousevsc_hid_driver;
+static const struct hid_device_id mousevsc_devices[] = {
+ { HID_DEVICE(BUS_VIRTUAL, HID_GROUP_ANY, 0x045E, 0x0621) },
+ { }
+};
+
+static struct hid_driver mousevsc_hid_driver = {
+ .name = "hid-hyperv",
+ .id_table = mousevsc_devices,
+ .probe = mousevsc_hid_probe,
+};
static int mousevsc_probe(struct hv_device *device,
const struct hv_vmbus_device_id *dev_id)
@@ -473,7 +501,6 @@ static int mousevsc_probe(struct hv_device *device,
}
hid_dev->ll_driver = &mousevsc_ll_driver;
- hid_dev->driver = &mousevsc_hid_driver;
hid_dev->bus = BUS_VIRTUAL;
hid_dev->vendor = input_dev->hid_dev_info.vendor;
hid_dev->product = input_dev->hid_dev_info.product;
@@ -488,20 +515,6 @@ static int mousevsc_probe(struct hv_device *device,
if (ret)
goto probe_err2;
-
- ret = hid_parse(hid_dev);
- if (ret) {
- hid_err(hid_dev, "parse failed\n");
- goto probe_err2;
- }
-
- ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
-
- if (ret) {
- hid_err(hid_dev, "hw start failed\n");
- goto probe_err2;
- }
-
device_init_wakeup(&device->device, true);
input_dev->connected = true;
@@ -579,12 +592,23 @@ static struct hv_driver mousevsc_drv = {
static int __init mousevsc_init(void)
{
- return vmbus_driver_register(&mousevsc_drv);
+ int ret;
+
+ ret = hid_register_driver(&mousevsc_hid_driver);
+ if (ret)
+ return ret;
+
+ ret = vmbus_driver_register(&mousevsc_drv);
+ if (ret)
+ hid_unregister_driver(&mousevsc_hid_driver);
+
+ return ret;
}
static void __exit mousevsc_exit(void)
{
vmbus_driver_unregister(&mousevsc_drv);
+ hid_unregister_driver(&mousevsc_hid_driver);
}
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 8a310f8ff20f..d31711f1aaec 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
@@ -92,8 +96,10 @@
#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC 0x0323
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC 0x0324
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f
#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214
@@ -161,20 +167,27 @@
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS 0x0257
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 0x0267
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015 0x026c
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 0x029c
+#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_MAGIC_KEYBOARD_2024 0x0320
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024 0x0321
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024 0x0322
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291
#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272
#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273
#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280
-#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT 0x0278
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280
+#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340
#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a
#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b
#define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240
@@ -182,12 +195,15 @@
#define USB_DEVICE_ID_APPLE_IRCONTROL3 0x8241
#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_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
@@ -207,7 +223,10 @@
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
-#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c
#define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b
#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
@@ -257,6 +276,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
@@ -292,12 +315,17 @@
#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
#define USB_VENDOR_ID_CIDC 0x1677
+#define I2C_VENDOR_ID_CIRQUE 0x0488
+#define I2C_PRODUCT_ID_CIRQUE_1063 0x1063
+
#define USB_VENDOR_ID_CJTOUCH 0x24b8
#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020
#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0040 0x0040
@@ -314,6 +342,9 @@
#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST 0x1500
#define USB_DEVICE_ID_CODEMERCS_IOW_LAST 0x15ff
+#define USB_VENDOR_ID_COOLER_MASTER 0x2516
+#define USB_DEVICE_ID_COOLER_MASTER_MICE_DONGLE 0x01b7
+
#define USB_VENDOR_ID_CORSAIR 0x1b1c
#define USB_DEVICE_ID_CORSAIR_K90 0x1b02
#define USB_DEVICE_ID_CORSAIR_K70R 0x1b09
@@ -366,6 +397,7 @@
#define USB_VENDOR_ID_DELL 0x413c
#define USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE 0x301a
+#define USB_DEVICE_ID_DELL_PRO_WIRELESS_KM5221W 0x4503
#define USB_VENDOR_ID_DELORME 0x1163
#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100
@@ -410,31 +442,22 @@
#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401
#define USB_DEVICE_ID_HP_X2 0x074d
#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
-#define I2C_DEVICE_ID_HP_ENVY_X360_15 0x2d05
-#define I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100 0x29CF
-#define I2C_DEVICE_ID_HP_ENVY_X360_EU0009NV 0x2CF9
-#define I2C_DEVICE_ID_HP_SPECTRE_X360_15 0x2817
-#define I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG 0x29DF
-#define I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN 0x2BC8
-#define I2C_DEVICE_ID_ASUS_GV301RA_TOUCHSCREEN 0x2C82
#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
-#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A
-#define I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN 0x2A1C
-#define I2C_DEVICE_ID_LENOVO_YOGA_C630_TOUCHSCREEN 0x279F
-#define I2C_DEVICE_ID_HP_SPECTRE_X360_13T_AW100 0x29F5
-#define I2C_DEVICE_ID_HP_SPECTRE_X360_14T_EA100_V1 0x2BED
-#define I2C_DEVICE_ID_HP_SPECTRE_X360_14T_EA100_V2 0x2BEE
+#define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81
#define USB_VENDOR_ID_ELECOM 0x056e
#define USB_DEVICE_ID_ELECOM_BM084 0x0061
#define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6
-#define USB_DEVICE_ID_ELECOM_M_XT3URBK 0x00fb
+#define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb
+#define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f
#define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc
#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd
#define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe
#define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff
-#define USB_DEVICE_ID_ELECOM_M_HT1URBK 0x010c
+#define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d
+#define USB_DEVICE_ID_ELECOM_M_HT1URBK_010C 0x010c
+#define USB_DEVICE_ID_ELECOM_M_HT1URBK_019B 0x019b
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c
@@ -454,8 +477,14 @@
#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
#define USB_VENDOR_ID_EVISION 0x320f
+#define USB_DEVICE_ID_EV_TELINK_RECEIVER 0x226f
#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
@@ -509,12 +538,11 @@
#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a
#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100
-#define USB_VENDOR_ID_GLORIOUS 0x258a
-#define USB_DEVICE_ID_GLORIOUS_MODEL_D 0x0033
-#define USB_DEVICE_ID_GLORIOUS_MODEL_O 0x0036
-
#define I2C_VENDOR_ID_GOODIX 0x27c6
+#define I2C_DEVICE_ID_GOODIX_01E8 0x01e8
+#define I2C_DEVICE_ID_GOODIX_01E9 0x01e9
#define I2C_DEVICE_ID_GOODIX_01F0 0x01f0
+#define I2C_DEVICE_ID_GOODIX_0D42 0x0d42
#define USB_VENDOR_ID_GOODTOUCH 0x1aad
#define USB_DEVICE_ID_GOODTOUCH_000f 0x000f
@@ -531,6 +559,7 @@
#define USB_DEVICE_ID_GOOGLE_DON 0x5050
#define USB_DEVICE_ID_GOOGLE_EEL 0x5057
#define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061
+#define USB_DEVICE_ID_GOOGLE_STADIA 0x9400
#define USB_VENDOR_ID_GOTOP 0x08f2
#define USB_DEVICE_ID_SUPER_Q2 0x007f
@@ -691,6 +720,7 @@
#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350
#define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a
#define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396
+#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987
#define USB_DEVICE_ID_ITE8595 0x8595
#define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50
@@ -739,8 +769,16 @@
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501A
#define USB_DEVICE_ID_KYE_PENSKETCH_T609A 0x501B
+#define USB_VENDOR_ID_KYSONA 0x3554
+#define USB_DEVICE_ID_KYSONA_M600_DONGLE 0xF57C
+#define USB_DEVICE_ID_KYSONA_M600_WIRED 0xF57D
+
#define USB_VENDOR_ID_LABTEC 0x1020
#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
+#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE 0x8888
+
+#define USB_VENDOR_ID_LAVIEW 0x22D4
+#define USB_DEVICE_ID_GLORIOUS_MODEL_I 0x1503
#define USB_VENDOR_ID_LCPOWER 0x1241
#define USB_DEVICE_ID_LCPOWER_LC1000 0xf767
@@ -794,13 +832,17 @@
#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
#define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184
+#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed
#define USB_VENDOR_ID_LETSKETCH 0x6161
#define USB_DEVICE_ID_WP9620N 0x4d15
@@ -811,11 +853,19 @@
#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_ID_LITE_STAR_GT987 0x2141
+
#define USB_VENDOR_ID_LOGITECH 0x046d
#define USB_DEVICE_ID_LOGITECH_Z_10_SPK 0x0a07
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
#define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD 0xb309
+#define USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD 0xbb00
#define USB_DEVICE_ID_LOGITECH_C007 0xc007
#define USB_DEVICE_ID_LOGITECH_C077 0xc077
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
@@ -832,6 +882,7 @@
#define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219
+#define USB_DEVICE_ID_LOGITECH_G13 0xc21c
#define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222
#define USB_DEVICE_ID_LOGITECH_G11 0xc225
#define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227
@@ -866,7 +917,11 @@
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547
+#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
+#define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
#define USB_DEVICE_ID_SPACENAVIGATOR 0xc626
#define USB_DEVICE_ID_DINOVO_DESKTOP 0xc704
@@ -913,6 +968,7 @@
#define USB_DEVICE_ID_PICK16F1454 0x0042
#define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7
#define USB_DEVICE_ID_LUXAFOR 0xf372
+#define USB_DEVICE_ID_MCP2200 0x00df
#define USB_DEVICE_ID_MCP2221 0x00dd
#define USB_VENDOR_ID_MICROSOFT 0x045e
@@ -955,6 +1011,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
@@ -984,7 +1052,10 @@
#define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006
#define USB_DEVICE_ID_NINTENDO_JOYCONR 0x2007
#define USB_DEVICE_ID_NINTENDO_PROCON 0x2009
-#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200E
+#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200e
+#define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
+#define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
+#define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
#define USB_VENDOR_ID_NOVATEK 0x0603
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
@@ -1031,6 +1102,8 @@
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES 0xc057
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES 0xc058
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3325_SERIES 0x430c
+#define USB_DEVICE_ID_PLANTRONICS_ENCOREPRO_500_SERIES 0x431e
#define USB_VENDOR_ID_PANASONIC 0x04da
#define USB_DEVICE_ID_PANABOARD_UBT780 0x1044
@@ -1075,11 +1148,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
@@ -1136,8 +1212,15 @@
#define USB_DEVICE_ID_SAITEK_X65 0x0b6a
#define USB_VENDOR_ID_SAMSUNG 0x0419
+#define USB_VENDOR_ID_SAMSUNG_ELECTRONICS 0x04e8
#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE 0x0001
#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE 0x0600
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD 0x7021
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD 0xa000
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE 0xa004
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_BOOKCOVER 0xa005
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064
#define USB_VENDOR_ID_SEMICO 0x1a2c
#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023
@@ -1156,6 +1239,10 @@
#define USB_VENDOR_ID_SIGMATEL 0x066F
#define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780
+#define USB_VENDOR_ID_SINOWEALTH 0x258a
+#define USB_DEVICE_ID_GLORIOUS_MODEL_D 0x0033
+#define USB_DEVICE_ID_GLORIOUS_MODEL_O 0x0036
+
#define USB_VENDOR_ID_SIS_TOUCH 0x0457
#define USB_DEVICE_ID_SIS9200_TOUCH 0x9200
#define USB_DEVICE_ID_SIS817_TOUCH 0x0817
@@ -1218,6 +1305,8 @@
#define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
+#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
@@ -1275,6 +1364,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
@@ -1334,6 +1424,8 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO 0x091b
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO 0x092d
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055
@@ -1348,6 +1440,10 @@
#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_DEVICE_ID_VRS_R295 0xa44c
+
#define USB_VENDOR_ID_VTL 0x0306
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
@@ -1454,4 +1550,7 @@
#define USB_VENDOR_ID_SIGNOTEC 0x2133
#define USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011 0x0018
+#define USB_VENDOR_ID_JIELI_SDK_DEFAULT 0x4c4a
+#define USB_DEVICE_ID_JIELI_SDK_4155 0x4155
+
#endif
diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c
index 77c2d45ac62a..6f5c71660d82 100644
--- a/drivers/hid/hid-input-test.c
+++ b/drivers/hid/hid-input-test.c
@@ -7,7 +7,7 @@
#include <kunit/test.h>
-static void hid_test_input_set_battery_charge_status(struct kunit *test)
+static void hid_test_input_update_battery_charge_status(struct kunit *test)
{
struct hid_device *dev;
bool handled;
@@ -15,15 +15,15 @@ static void hid_test_input_set_battery_charge_status(struct kunit *test)
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
- handled = hidinput_set_battery_charge_status(dev, HID_DG_HEIGHT, 0);
+ handled = hidinput_update_battery_charge_status(dev, HID_DG_HEIGHT, 0);
KUNIT_EXPECT_FALSE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
- handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 0);
+ handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 0);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
- handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 1);
+ handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 1);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
}
@@ -63,7 +63,7 @@ static void hid_test_input_get_battery_property(struct kunit *test)
}
static struct kunit_case hid_input_tests[] = {
- KUNIT_CASE(hid_test_input_set_battery_charge_status),
+ KUNIT_CASE(hid_test_input_update_battery_charge_status),
KUNIT_CASE(hid_test_input_get_battery_property),
{ }
};
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 851ee86eff32..2633fcd8f910 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
}
break;
+ case ABS_PRESSURE:
+ case ABS_MT_PRESSURE:
+ if (field->unit == HID_UNIT_NEWTON) {
+ /* Convert to grams, 1 newton is 101.97 grams */
+ prev = physical_extents;
+ physical_extents *= 10197;
+ if (physical_extents < prev)
+ return 0;
+ unit_exponent -= 2;
+ } else if (field->unit != HID_UNIT_GRAM) {
+ return 0;
+ }
+ break;
default:
return 0;
}
@@ -358,6 +371,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD),
+ HID_BATTERY_QUIRK_IGNORE },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
USB_DEVICE_ID_ELECOM_BM084),
HID_BATTERY_QUIRK_IGNORE },
@@ -370,10 +386,6 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD),
HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_ASUS_GV301RA_TOUCHSCREEN),
- HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN),
@@ -384,28 +396,14 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
HID_BATTERY_QUIRK_AVOID_QUERY },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_EU0009NV),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_15),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_LENOVO_YOGA_C630_TOUCHSCREEN),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_13T_AW100),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_14T_EA100_V1),
- HID_BATTERY_QUIRK_IGNORE },
- { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_14T_EA100_V2),
- HID_BATTERY_QUIRK_IGNORE },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM),
+ HID_BATTERY_QUIRK_AVOID_QUERY },
+ /*
+ * Elan HID touchscreens seem to all report a non present battery,
+ * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices.
+ */
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE },
{}
};
@@ -611,14 +609,37 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
dev->battery = NULL;
}
-static void hidinput_update_battery(struct hid_device *dev, int value)
+static bool hidinput_update_battery_charge_status(struct hid_device *dev,
+ unsigned int usage, int value)
+{
+ switch (usage) {
+ case HID_BAT_CHARGING:
+ dev->battery_charge_status = value ?
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ return true;
+ }
+
+ return false;
+}
+
+static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
+ int value)
{
int capacity;
if (!dev->battery)
return;
- if (value == 0 || value < dev->battery_min || value > dev->battery_max)
+ if (hidinput_update_battery_charge_status(dev, usage, value)) {
+ power_supply_changed(dev->battery);
+ return;
+ }
+
+ if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0)
+ return;
+
+ if (value < dev->battery_min || value > dev->battery_max)
return;
capacity = hidinput_scale_battery_capacity(dev, value);
@@ -633,20 +654,6 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
power_supply_changed(dev->battery);
}
}
-
-static bool hidinput_set_battery_charge_status(struct hid_device *dev,
- unsigned int usage, int value)
-{
- switch (usage) {
- case HID_BAT_CHARGING:
- dev->battery_charge_status = value ?
- POWER_SUPPLY_STATUS_CHARGING :
- POWER_SUPPLY_STATUS_DISCHARGING;
- return true;
- }
-
- return false;
-}
#else /* !CONFIG_HID_BATTERY_STRENGTH */
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage)
@@ -658,15 +665,10 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
{
}
-static void hidinput_update_battery(struct hid_device *dev, int value)
+static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
+ int value)
{
}
-
-static bool hidinput_set_battery_charge_status(struct hid_device *dev,
- unsigned int usage, int value)
-{
- return false;
-}
#endif /* CONFIG_HID_BATTERY_STRENGTH */
static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field,
@@ -698,9 +700,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
if (field->report_count < 1)
goto ignore;
- /* only LED usages are supported in output fields */
+ /* only LED and HAPTIC usages are supported in output fields */
if (field->report_type == HID_OUTPUT_REPORT &&
- (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
+ (usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
+ (usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
goto ignore;
}
@@ -826,9 +829,31 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break;
}
+ if ((usage->hid & 0xf0) == 0x90) { /* SystemControl & D-pad */
+ switch (usage->hid) {
+ case HID_GD_UP: usage->hat_dir = 1; break;
+ case HID_GD_DOWN: usage->hat_dir = 5; break;
+ case HID_GD_RIGHT: usage->hat_dir = 3; break;
+ case HID_GD_LEFT: usage->hat_dir = 7; break;
+ case HID_GD_DO_NOT_DISTURB:
+ map_key_clear(KEY_DO_NOT_DISTURB); break;
+ default: goto unknown;
+ }
+
+ if (usage->hid <= HID_GD_LEFT) {
+ if (field->dpad) {
+ map_abs(field->dpad);
+ goto ignore;
+ }
+ map_abs(ABS_HAT0X);
+ }
+ break;
+ }
+
if ((usage->hid & 0xf0) == 0xa0) { /* SystemControl */
switch (usage->hid & 0xf) {
case 0x9: map_key_clear(KEY_MICMUTE); break;
+ case 0xa: map_key_clear(KEY_ACCESSIBILITY); break;
default: goto ignore;
}
break;
@@ -851,25 +876,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
if (field->application == HID_GD_SYSTEM_CONTROL)
goto ignore;
- if ((usage->hid & 0xf0) == 0x90) { /* D-pad */
- switch (usage->hid) {
- case HID_GD_UP: usage->hat_dir = 1; break;
- case HID_GD_DOWN: usage->hat_dir = 5; break;
- case HID_GD_RIGHT: usage->hat_dir = 3; break;
- case HID_GD_LEFT: usage->hat_dir = 7; break;
- default: goto unknown;
- }
- if (field->dpad) {
- map_abs(field->dpad);
- goto ignore;
- }
- map_abs(ABS_HAT0X);
- break;
- }
-
switch (usage->hid) {
/* These usage IDs map directly to the usage codes. */
- case HID_GD_X: case HID_GD_Y: case HID_GD_Z:
+ case HID_GD_X: case HID_GD_Y:
case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ:
if (field->flags & HID_MAIN_ITEM_RELATIVE)
map_rel(usage->hid & 0xf);
@@ -877,6 +886,22 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
map_abs_clear(usage->hid & 0xf);
break;
+ case HID_GD_Z:
+ /* HID_GD_Z is mapped to ABS_DISTANCE for stylus/pen */
+ if (field->flags & HID_MAIN_ITEM_RELATIVE) {
+ map_rel(usage->hid & 0xf);
+ } else {
+ if (field->application == HID_DG_PEN ||
+ field->physical == HID_DG_PEN ||
+ field->logical == HID_DG_STYLUS ||
+ field->physical == HID_DG_STYLUS ||
+ field->application == HID_DG_DIGITIZER)
+ map_abs_clear(ABS_DISTANCE);
+ else
+ map_abs_clear(usage->hid & 0xf);
+ }
+ break;
+
case HID_GD_WHEEL:
if (field->flags & HID_MAIN_ITEM_RELATIVE) {
set_bit(REL_WHEEL, input->relbit);
@@ -988,6 +1013,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
return;
case 0x3c: /* Invert */
+ device->quirks &= ~HID_QUIRK_NOINVERT;
map_key_clear(BTN_TOOL_RUBBER);
break;
@@ -1013,9 +1039,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x45: /* ERASER */
/*
* This event is reported when eraser tip touches the surface.
- * Actual eraser (BTN_TOOL_RUBBER) is set by Invert usage when
- * tool gets in proximity.
+ * Actual eraser (BTN_TOOL_RUBBER) is set and released either
+ * by Invert if tool reports proximity or by Eraser directly.
*/
+ if (!test_bit(BTN_TOOL_RUBBER, input->keybit)) {
+ device->quirks |= HID_QUIRK_NOINVERT;
+ set_bit(BTN_TOOL_RUBBER, input->keybit);
+ }
map_key_clear(BTN_TOUCH);
break;
@@ -1520,11 +1550,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return;
if (usage->type == EV_PWR) {
- bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value);
-
- if (!handled)
- hidinput_update_battery(hid, value);
-
+ hidinput_update_battery(hid, usage->hid, value);
return;
}
@@ -1580,6 +1606,15 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
else if (report->tool != BTN_TOOL_RUBBER)
/* value is off, tool is not rubber, ignore */
return;
+ else if (*quirks & HID_QUIRK_NOINVERT &&
+ !test_bit(BTN_TOUCH, input->key)) {
+ /*
+ * There is no invert to release the tool, let hid_input
+ * send BTN_TOUCH with scancode and release the tool after.
+ */
+ hid_report_release_tool(report, input, BTN_TOOL_RUBBER);
+ return;
+ }
/* let hid-input set BTN_TOUCH */
break;
@@ -2339,7 +2374,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
}
if (list_empty(&hid->inputs)) {
- hid_err(hid, "No inputs registered, leaving\n");
+ hid_dbg(hid, "No inputs registered, leaving\n");
goto out_unwind;
}
@@ -2381,6 +2416,13 @@ void hidinput_disconnect(struct hid_device *hid)
}
EXPORT_SYMBOL_GPL(hidinput_disconnect);
+void hidinput_reset_resume(struct hid_device *hid)
+{
+ /* renegotiate host-device shared state after reset */
+ hidinput_change_resolution_multipliers(hid);
+}
+EXPORT_SYMBOL_GPL(hidinput_reset_resume);
+
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-input-test.c"
#endif
diff --git a/drivers/hid/hid-ite.c b/drivers/hid/hid-ite.c
index 75ebfcf31889..8e42780a2663 100644
--- a/drivers/hid/hid-ite.c
+++ b/drivers/hid/hid-ite.c
@@ -13,7 +13,7 @@
#define QUIRK_TOUCHPAD_ON_OFF_REPORT BIT(0)
-static __u8 *ite_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
+static const __u8 *ite_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
{
unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
@@ -141,4 +141,5 @@ static struct hid_driver ite_driver = {
module_hid_driver(ite_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("HID driver for some ITE \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-kensington.c b/drivers/hid/hid-kensington.c
index b31f7f431a3f..16839027981f 100644
--- a/drivers/hid/hid-kensington.c
+++ b/drivers/hid/hid-kensington.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * HID driver for Kensigton Slimblade Trackball
+ * HID driver for Kensington Slimblade Trackball
*
* Copyright (c) 2009 Jiri Kosina
*/
@@ -46,4 +46,5 @@ static struct hid_driver ks_driver = {
};
module_hid_driver(ks_driver);
+MODULE_DESCRIPTION("HID driver for Kensington Slimblade Trackball");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-keytouch.c b/drivers/hid/hid-keytouch.c
index 73bf8642dfe3..b9abd53a5864 100644
--- a/drivers/hid/hid-keytouch.c
+++ b/drivers/hid/hid-keytouch.c
@@ -16,7 +16,7 @@
/* Replace the broken report descriptor of this device with rather
* a default one */
-static __u8 keytouch_fixed_rdesc[] = {
+static const __u8 keytouch_fixed_rdesc[] = {
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15,
0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08,
0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91,
@@ -24,15 +24,13 @@ static __u8 keytouch_fixed_rdesc[] = {
0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0
};
-static __u8 *keytouch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *keytouch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
hid_info(hdev, "fixing up Keytouch IEC report descriptor\n");
- rdesc = keytouch_fixed_rdesc;
*rsize = sizeof(keytouch_fixed_rdesc);
-
- return rdesc;
+ return keytouch_fixed_rdesc;
}
static const struct hid_device_id keytouch_devices[] = {
@@ -48,5 +46,6 @@ static struct hid_driver keytouch_driver = {
};
module_hid_driver(keytouch_driver);
+MODULE_DESCRIPTION("HID driver for Keytouch devices not fully compliant with HID standard");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jiri Kosina");
diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c
index eb9bf2829937..bd96bfa7af70 100644
--- a/drivers/hid/hid-kye.c
+++ b/drivers/hid/hid-kye.c
@@ -8,7 +8,7 @@
* Copyright (c) 2023 David Yang
*/
-#include <asm-generic/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
@@ -209,7 +209,7 @@ static const __u8 pensketch_t609a_control_rdesc[] = {
0xC0 /* End Collection */
};
-/* Fix indexes in kye_tablet_fixup if you change this */
+/* Fix indexes in kye_tablet_fixup() if you change this */
static const __u8 kye_tablet_rdesc[] = {
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
@@ -262,12 +262,16 @@ static const __u8 kye_tablet_rdesc[] = {
0x27, 0xFF, 0x07, 0x00, 0x00, /* Logical Maximum (2047), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
- 0xC0, /* End Collection, */
- 0x05, 0x0D, /* Usage Page (Digitizer), */
- 0x09, 0x21, /* Usage (Puck), */
+ 0xC0 /* End Collection, */
+};
+
+/* Fix indexes in kye_tablet_fixup() if you change this */
+static const __u8 kye_tablet_mouse_rdesc[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x11, /* Report ID (17), */
- 0x09, 0x21, /* Usage (Puck), */
+ 0x09, 0x01, /* Usage (Pointer), */
0xA0, /* Collection (Physical), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
@@ -280,7 +284,7 @@ static const __u8 kye_tablet_rdesc[] = {
0x95, 0x04, /* Report Count (4), */
0x81, 0x01, /* Input (Constant), */
0x05, 0x0D, /* Usage Page (Digitizer), */
- 0x09, 0x32, /* Usage (In Range), */
+ 0x09, 0x37, /* Usage (Data Valid), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
@@ -317,7 +321,7 @@ static const struct kye_tablet_info {
__s32 y_physical_maximum;
__s8 unit_exponent;
__s8 unit;
- bool has_punk;
+ bool has_mouse;
unsigned int control_rsize;
const __u8 *control_rdesc;
} kye_tablets_info[] = {
@@ -402,7 +406,7 @@ static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
{
const struct kye_tablet_info *info;
- unsigned int newsize;
+ __u8 *newdesc = rdesc;
if (*rsize < sizeof(kye_tablet_rdesc)) {
hid_warn(hdev,
@@ -420,40 +424,49 @@ static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int
return rdesc;
}
- newsize = info->has_punk ? sizeof(kye_tablet_rdesc) : 112;
- memcpy(rdesc, kye_tablet_rdesc, newsize);
-
- put_unaligned_le32(info->x_logical_maximum, rdesc + 66);
- put_unaligned_le32(info->x_physical_maximum, rdesc + 72);
- rdesc[77] = info->unit;
- rdesc[79] = info->unit_exponent;
- put_unaligned_le32(info->y_logical_maximum, rdesc + 87);
- put_unaligned_le32(info->y_physical_maximum, rdesc + 92);
- put_unaligned_le32(info->pressure_logical_maximum, rdesc + 104);
-
- if (info->has_punk) {
- put_unaligned_le32(info->x_logical_maximum, rdesc + 156);
- put_unaligned_le32(info->x_physical_maximum, rdesc + 162);
- rdesc[167] = info->unit;
- rdesc[169] = info->unit_exponent;
- put_unaligned_le32(info->y_logical_maximum, rdesc + 177);
- put_unaligned_le32(info->y_physical_maximum, rdesc + 182);
+ memcpy(newdesc, kye_tablet_rdesc, sizeof(kye_tablet_rdesc));
+
+ put_unaligned_le32(info->x_logical_maximum, newdesc + 66);
+ put_unaligned_le32(info->x_physical_maximum, newdesc + 72);
+ newdesc[77] = info->unit;
+ newdesc[79] = info->unit_exponent;
+ put_unaligned_le32(info->y_logical_maximum, newdesc + 87);
+ put_unaligned_le32(info->y_physical_maximum, newdesc + 92);
+ put_unaligned_le32(info->pressure_logical_maximum, newdesc + 104);
+
+ newdesc += sizeof(kye_tablet_rdesc);
+
+ if (info->has_mouse) {
+ if (newdesc + sizeof(kye_tablet_mouse_rdesc) > rdesc + *rsize)
+ hid_err(hdev, "control desc unexpectedly large\n");
+ else {
+ memcpy(newdesc, kye_tablet_mouse_rdesc, sizeof(kye_tablet_mouse_rdesc));
+
+ put_unaligned_le32(info->x_logical_maximum, newdesc + 44);
+ put_unaligned_le32(info->x_physical_maximum, newdesc + 50);
+ newdesc[55] = info->unit;
+ newdesc[57] = info->unit_exponent;
+ put_unaligned_le32(info->y_logical_maximum, newdesc + 65);
+ put_unaligned_le32(info->y_physical_maximum, newdesc + 70);
+
+ newdesc += sizeof(kye_tablet_mouse_rdesc);
+ }
}
if (info->control_rsize) {
- if (newsize + info->control_rsize > *rsize)
- hid_err(hdev, "control rdesc unexpectedly large");
+ if (newdesc + info->control_rsize > rdesc + *rsize)
+ hid_err(hdev, "control desc unexpectedly large\n");
else {
- memcpy(rdesc + newsize, info->control_rdesc, info->control_rsize);
- newsize += info->control_rsize;
+ memcpy(newdesc, info->control_rdesc, info->control_rsize);
+ newdesc += info->control_rsize;
}
}
- *rsize = newsize;
+ *rsize = newdesc - rdesc;
return rdesc;
}
-static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
@@ -658,4 +671,5 @@ static struct hid_driver kye_driver = {
};
module_hid_driver(kye_driver);
+MODULE_DESCRIPTION("HID driver for Kye/Genius devices not fully compliant with HID standard");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-kysona.c b/drivers/hid/hid-kysona.c
new file mode 100644
index 000000000000..09bfe30d02cb
--- /dev/null
+++ b/drivers/hid/hid-kysona.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * USB HID driver for Kysona
+ * Kysona M600 mice.
+ *
+ * Copyright (c) 2024 Lode Willems <me@lodewillems.com>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+#define BATTERY_TIMEOUT_MS 5000
+
+#define ONLINE_REPORT_ID 3
+#define BATTERY_REPORT_ID 4
+
+struct kysona_drvdata {
+ struct hid_device *hdev;
+ bool online;
+
+ struct power_supply_desc battery_desc;
+ struct power_supply *battery;
+ u8 battery_capacity;
+ bool battery_charging;
+ u16 battery_voltage;
+ struct delayed_work battery_work;
+};
+
+static enum power_supply_property kysona_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_ONLINE
+};
+
+static int kysona_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct kysona_drvdata *drv_data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = drv_data->online;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (drv_data->online)
+ val->intval = drv_data->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;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = drv_data->battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* hardware reports voltage in mV. sysfs expects uV */
+ val->intval = drv_data->battery_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = drv_data->hdev->name;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static const char kysona_online_request[] = {
+ 0x08, ONLINE_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a
+};
+
+static const char kysona_battery_request[] = {
+ 0x08, BATTERY_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49
+};
+
+static int kysona_m600_fetch_online(struct hid_device *hdev)
+{
+ u8 *write_buf;
+ int ret;
+
+ /* Request online information */
+ write_buf = kmemdup(kysona_online_request, sizeof(kysona_online_request), GFP_KERNEL);
+ if (!write_buf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, kysona_online_request[0],
+ write_buf, sizeof(kysona_online_request),
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ if (ret < (int)sizeof(kysona_online_request)) {
+ hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
+ ret = -ENODATA;
+ }
+ kfree(write_buf);
+ return ret;
+}
+
+static void kysona_fetch_online(struct hid_device *hdev)
+{
+ int ret = kysona_m600_fetch_online(hdev);
+
+ if (ret < 0)
+ hid_dbg(hdev,
+ "Online query failed (err: %d)\n", ret);
+}
+
+static int kysona_m600_fetch_battery(struct hid_device *hdev)
+{
+ u8 *write_buf;
+ int ret;
+
+ /* Request battery information */
+ write_buf = kmemdup(kysona_battery_request, sizeof(kysona_battery_request), GFP_KERNEL);
+ if (!write_buf)
+ return -ENOMEM;
+
+ ret = hid_hw_raw_request(hdev, kysona_battery_request[0],
+ write_buf, sizeof(kysona_battery_request),
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+ if (ret < (int)sizeof(kysona_battery_request)) {
+ hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
+ ret = -ENODATA;
+ }
+ kfree(write_buf);
+ return ret;
+}
+
+static void kysona_fetch_battery(struct hid_device *hdev)
+{
+ int ret = kysona_m600_fetch_battery(hdev);
+
+ if (ret < 0)
+ hid_dbg(hdev,
+ "Battery query failed (err: %d)\n", ret);
+}
+
+static void kysona_battery_timer_tick(struct work_struct *work)
+{
+ struct kysona_drvdata *drv_data = container_of(work,
+ struct kysona_drvdata, battery_work.work);
+ struct hid_device *hdev = drv_data->hdev;
+
+ kysona_fetch_online(hdev);
+ kysona_fetch_battery(hdev);
+ schedule_delayed_work(&drv_data->battery_work,
+ msecs_to_jiffies(BATTERY_TIMEOUT_MS));
+}
+
+static int kysona_battery_probe(struct hid_device *hdev)
+{
+ struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
+ struct power_supply_config pscfg = { .drv_data = drv_data };
+ int ret = 0;
+
+ drv_data->online = false;
+ drv_data->battery_capacity = 100;
+ drv_data->battery_voltage = 4200;
+
+ drv_data->battery_desc.properties = kysona_battery_props;
+ drv_data->battery_desc.num_properties = ARRAY_SIZE(kysona_battery_props);
+ drv_data->battery_desc.get_property = kysona_battery_get_property;
+ drv_data->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drv_data->battery_desc.use_for_apm = 0;
+ drv_data->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "kysona-%s-battery",
+ strlen(hdev->uniq) ?
+ hdev->uniq : dev_name(&hdev->dev));
+ if (!drv_data->battery_desc.name)
+ return -ENOMEM;
+
+ drv_data->battery = devm_power_supply_register(&hdev->dev,
+ &drv_data->battery_desc, &pscfg);
+ if (IS_ERR(drv_data->battery)) {
+ ret = PTR_ERR(drv_data->battery);
+ drv_data->battery = NULL;
+ hid_err(hdev, "Unable to register battery device\n");
+ return ret;
+ }
+
+ power_supply_powers(drv_data->battery, &hdev->dev);
+
+ INIT_DELAYED_WORK(&drv_data->battery_work, kysona_battery_timer_tick);
+ kysona_fetch_online(hdev);
+ kysona_fetch_battery(hdev);
+ schedule_delayed_work(&drv_data->battery_work,
+ msecs_to_jiffies(BATTERY_TIMEOUT_MS));
+
+ return ret;
+}
+
+static int kysona_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+ struct kysona_drvdata *drv_data;
+ struct usb_interface *usbif;
+
+ if (!hid_is_usb(hdev))
+ return -EINVAL;
+
+ usbif = to_usb_interface(hdev->dev.parent);
+
+ drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ hid_set_drvdata(hdev, drv_data);
+ drv_data->hdev = hdev;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return ret;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ if (usbif->cur_altsetting->desc.bInterfaceNumber == 1) {
+ if (kysona_battery_probe(hdev) < 0)
+ hid_err(hdev, "Kysona hid battery_probe failed: %d\n", ret);
+ }
+
+ return 0;
+}
+
+static int kysona_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
+
+ if (size == sizeof(kysona_online_request) &&
+ data[0] == 8 && data[1] == ONLINE_REPORT_ID) {
+ drv_data->online = data[6];
+ }
+
+ if (size == sizeof(kysona_battery_request) &&
+ data[0] == 8 && data[1] == BATTERY_REPORT_ID) {
+ drv_data->battery_capacity = data[6];
+ drv_data->battery_charging = data[7];
+ drv_data->battery_voltage = (data[8] << 8) | data[9];
+ }
+
+ return 0;
+}
+
+static void kysona_remove(struct hid_device *hdev)
+{
+ struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
+
+ if (drv_data->battery)
+ cancel_delayed_work_sync(&drv_data->battery_work);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id kysona_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, kysona_devices);
+
+static struct hid_driver kysona_driver = {
+ .name = "kysona",
+ .id_table = kysona_devices,
+ .probe = kysona_probe,
+ .raw_event = kysona_raw_event,
+ .remove = kysona_remove
+};
+module_hid_driver(kysona_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID driver for Kysona devices");
+MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");
diff --git a/drivers/hid/hid-lcpower.c b/drivers/hid/hid-lcpower.c
index 8acd3ee5ada5..58953d11ded7 100644
--- a/drivers/hid/hid-lcpower.c
+++ b/drivers/hid/hid-lcpower.c
@@ -53,4 +53,5 @@ static struct hid_driver ts_driver = {
};
module_hid_driver(ts_driver);
+MODULE_DESCRIPTION("HID driver for LC Power Model RC1000MCE");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index 44763c0da444..9cc3e029e9f6 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -37,6 +37,13 @@
/* 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;
@@ -51,8 +58,13 @@ struct lenovo_drvdata {
int select_right;
int sensitivity;
int press_speed;
- u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
+ /* 0: Up
+ * 1: Down (undecided)
+ * 2: Scrolling
+ */
+ u8 middlebutton_state;
bool fn_lock;
+ bool middleclick_workaround_cptkbd;
};
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
@@ -66,6 +78,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)
{
@@ -128,7 +148,15 @@ static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = {
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */
};
-static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 lenovo_yoga7x_kbd_need_fixup_collection[] = {
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x65, // Logical Maximum (101)
+ 0x05, 0x07, // Usage Page (Keyboard)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x29, 0xDD, // Usage Maximum (221)
+};
+
+static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
@@ -157,6 +185,13 @@ static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
rdesc[260] = 0x01; /* report count (2) = 0x01 */
}
break;
+ case I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD:
+ if (*rsize == 176 &&
+ memcmp(&rdesc[52], lenovo_yoga7x_kbd_need_fixup_collection,
+ sizeof(lenovo_yoga7x_kbd_need_fixup_collection)) == 0) {
+ rdesc[55] = rdesc[61]; // logical maximum = usage maximum
+ }
+ break;
}
return rdesc;
}
@@ -467,7 +502,11 @@ 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:
return 0;
@@ -521,6 +560,22 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev)
int ret;
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
+ /*
+ * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
+ * regular keys (Compact only)
+ */
+ 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);
+ if (ret)
+ hid_warn(hdev, "Failed to switch middle button: %d\n", ret);
+
ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
if (ret)
hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
@@ -537,7 +592,7 @@ static ssize_t attr_fn_lock_show(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock);
+ return sysfs_emit(buf, "%u\n", data->fn_lock);
}
static ssize_t attr_fn_lock_store(struct device *dev,
@@ -563,8 +618,12 @@ 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)
return ret;
@@ -581,8 +640,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n",
- cptkbd_data->sensitivity);
+ return sysfs_emit(buf, "%u\n", cptkbd_data->sensitivity);
}
static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
@@ -603,6 +661,36 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
return count;
}
+static ssize_t attr_middleclick_workaround_show_cptkbd(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
+
+ return sysfs_emit(buf, "%u\n",
+ cptkbd_data->middleclick_workaround_cptkbd);
+}
+
+static ssize_t attr_middleclick_workaround_store_cptkbd(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
+ int value;
+
+ if (kstrtoint(buf, 10, &value))
+ return -EINVAL;
+ if (value < 0 || value > 1)
+ return -EINVAL;
+
+ cptkbd_data->middleclick_workaround_cptkbd = !!value;
+
+ return count;
+}
+
static struct device_attribute dev_attr_fn_lock =
__ATTR(fn_lock, S_IWUSR | S_IRUGO,
@@ -614,10 +702,16 @@ static struct device_attribute dev_attr_sensitivity_cptkbd =
attr_sensitivity_show_cptkbd,
attr_sensitivity_store_cptkbd);
+static struct device_attribute dev_attr_middleclick_workaround_cptkbd =
+ __ATTR(middleclick_workaround, S_IWUSR | S_IRUGO,
+ attr_middleclick_workaround_show_cptkbd,
+ attr_middleclick_workaround_store_cptkbd);
+
static struct attribute *lenovo_attributes_cptkbd[] = {
&dev_attr_fn_lock.attr,
&dev_attr_sensitivity_cptkbd.attr,
+ &dev_attr_middleclick_workaround_cptkbd.attr,
NULL
};
@@ -625,6 +719,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;
+ }
+ report_key_event(input, KEY_PERFORMANCE);
+ 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)
{
@@ -642,6 +787,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;
}
@@ -668,31 +822,33 @@ static int lenovo_event_cptkbd(struct hid_device *hdev,
{
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
- /* "wheel" scroll events */
- if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
- usage->code == REL_HWHEEL)) {
- /* Scroll events disable middle-click event */
- cptkbd_data->middlebutton_state = 2;
- return 0;
- }
+ if (cptkbd_data->middleclick_workaround_cptkbd) {
+ /* "wheel" scroll events */
+ if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
+ usage->code == REL_HWHEEL)) {
+ /* Scroll events disable middle-click event */
+ cptkbd_data->middlebutton_state = 2;
+ return 0;
+ }
- /* Middle click events */
- if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
- if (value == 1) {
- cptkbd_data->middlebutton_state = 1;
- } else if (value == 0) {
- if (cptkbd_data->middlebutton_state == 1) {
- /* No scrolling inbetween, send middle-click */
- input_event(field->hidinput->input,
- EV_KEY, BTN_MIDDLE, 1);
- input_sync(field->hidinput->input);
- input_event(field->hidinput->input,
- EV_KEY, BTN_MIDDLE, 0);
- input_sync(field->hidinput->input);
+ /* Middle click events */
+ if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
+ if (value == 1) {
+ cptkbd_data->middlebutton_state = 1;
+ } else if (value == 0) {
+ if (cptkbd_data->middlebutton_state == 1) {
+ /* No scrolling inbetween, send middle-click */
+ input_event(field->hidinput->input,
+ EV_KEY, BTN_MIDDLE, 1);
+ input_sync(field->hidinput->input);
+ input_event(field->hidinput->input,
+ EV_KEY, BTN_MIDDLE, 0);
+ input_sync(field->hidinput->input);
+ }
+ cptkbd_data->middlebutton_state = 0;
}
- cptkbd_data->middlebutton_state = 0;
+ return 1;
}
- return 1;
}
if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
@@ -719,8 +875,12 @@ 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:
return 0;
@@ -753,7 +913,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
+ return sysfs_emit(buf, "%u\n", data_pointer->press_to_select);
}
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
@@ -783,7 +943,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
+ return sysfs_emit(buf, "%u\n", data_pointer->dragging);
}
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
@@ -813,7 +973,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
+ return sysfs_emit(buf, "%u\n", data_pointer->release_to_select);
}
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
@@ -843,7 +1003,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
+ return sysfs_emit(buf, "%u\n", data_pointer->select_right);
}
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
@@ -873,8 +1033,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n",
- data_pointer->sensitivity);
+ return sysfs_emit(buf, "%u\n", data_pointer->sensitivity);
}
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
@@ -902,8 +1061,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
- return snprintf(buf, PAGE_SIZE, "%u\n",
- data_pointer->press_speed);
+ return sysfs_emit(buf, "%u\n", data_pointer->press_speed);
}
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
@@ -1001,8 +1159,12 @@ 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;
}
@@ -1126,26 +1288,11 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev)
}
hid_set_drvdata(hdev, cptkbd_data);
- /*
- * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
- * regular keys (Compact only)
- */
- 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);
- if (ret)
- hid_warn(hdev, "Failed to switch middle button: %d\n", ret);
-
/* Set keyboard settings to known state */
cptkbd_data->middlebutton_state = 0;
cptkbd_data->fn_lock = true;
cptkbd_data->sensitivity = 0x05;
+ cptkbd_data->middleclick_workaround_cptkbd = true;
lenovo_features_set_cptkbd(hdev);
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
@@ -1201,8 +1348,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);
@@ -1246,8 +1400,12 @@ 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;
default:
@@ -1264,6 +1422,24 @@ err:
return ret;
}
+#ifdef CONFIG_PM
+static int lenovo_reset_resume(struct hid_device *hdev)
+{
+ switch (hdev->product) {
+ case USB_DEVICE_ID_LENOVO_CUSBKBD:
+ case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
+ if (hdev->type == HID_TYPE_USBMOUSE)
+ lenovo_features_set_cptkbd(hdev);
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
static void lenovo_remove_tpkbd(struct hid_device *hdev)
{
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
@@ -1314,8 +1490,12 @@ 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;
}
@@ -1365,6 +1545,16 @@ 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) },
+ { HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD) },
{ }
};
@@ -1380,7 +1570,11 @@ static struct hid_driver lenovo_driver = {
.raw_event = lenovo_raw_event,
.event = lenovo_event,
.report_fixup = lenovo_report_fixup,
+#ifdef CONFIG_PM
+ .reset_resume = lenovo_reset_resume,
+#endif
};
module_hid_driver(lenovo_driver);
+MODULE_DESCRIPTION("HID driver for IBM/Lenovo");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-letsketch.c b/drivers/hid/hid-letsketch.c
index 97f047f18136..11e21f988723 100644
--- a/drivers/hid/hid-letsketch.c
+++ b/drivers/hid/hid-letsketch.c
@@ -41,7 +41,7 @@
#include <linux/timer.h>
#include <linux/usb.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
@@ -155,7 +155,8 @@ static int letsketch_setup_input_tablet_pad(struct letsketch_data *data)
static void letsketch_inrange_timeout(struct timer_list *t)
{
- struct letsketch_data *data = from_timer(data, t, inrange_timer);
+ struct letsketch_data *data = timer_container_of(data, t,
+ inrange_timer);
struct input_dev *input = data->input_tablet;
input_report_key(input, BTN_TOOL_PEN, 0);
@@ -319,4 +320,5 @@ static struct hid_driver letsketch_driver = {
module_hid_driver(letsketch_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Driver for the LetSketch / VSON WP9620N drawing tablet");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg-g15.c b/drivers/hid/hid-lg-g15.c
index acbec1dcf196..1a88bc44ada4 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"
@@ -24,7 +26,24 @@
#define LG_G510_FEATURE_BACKLIGHT_RGB 0x05
#define LG_G510_FEATURE_POWER_ON_RGB 0x06
+#define LG_G510_INPUT_MACRO_KEYS 0x03
+#define LG_G510_INPUT_KBD_BACKLIGHT 0x04
+
+#define LG_G13_INPUT_REPORT 0x01
+#define LG_G13_FEATURE_M_KEYS_LEDS 0x05
+#define LG_G13_FEATURE_BACKLIGHT_RGB 0x07
+#define LG_G13_BACKLIGHT_HW_ON_BIT 23
+
+/**
+ * g13_input_report.keybits[] is not 32-bit aligned, so we can't use the bitops macros.
+ *
+ * @ary: Pointer to array of u8s
+ * @b: Bit index into ary, LSB first. Not range checked.
+ */
+#define TEST_BIT(ary, b) ((1 << ((b) & 7)) & (ary)[(b) >> 3])
+
enum lg_g15_model {
+ LG_G13,
LG_G15,
LG_G15_V2,
LG_G510,
@@ -43,10 +62,20 @@ enum lg_g15_led_type {
LG_G15_LED_MAX
};
+struct g13_input_report {
+ u8 report_id; /* Report ID is always set to 1. */
+ u8 joy_x, joy_y;
+ u8 keybits[5];
+};
+
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;
};
@@ -57,12 +86,188 @@ struct lg_g15_data {
struct mutex mutex;
struct work_struct work;
struct input_dev *input;
+ struct input_dev *input_js; /* Separate joystick device for G13. */
struct hid_device *hdev;
enum lg_g15_model model;
struct lg_g15_led leds[LG_G15_LED_MAX];
bool game_mode_enabled;
+ bool backlight_disabled; /* true == HW backlight toggled *OFF* */
};
+/********* G13 LED functions ***********/
+/*
+ * G13 retains no state across power cycles, and always powers up with the backlight on,
+ * color #5AFF6E, all macro key LEDs off.
+ */
+static int lg_g13_get_leds_state(struct lg_g15_data *g15)
+{
+ u8 * const tbuf = g15->transfer_buf;
+ int ret, high;
+
+ /* RGB backlight. */
+ ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB,
+ tbuf, 5,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret != 5) {
+ hid_err(g15->hdev, "Error getting backlight brightness: %d\n", ret);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ /* Normalize RGB intensities against the highest component. */
+ high = max3(tbuf[1], tbuf[2], tbuf[3]);
+ if (high) {
+ g15->leds[LG_G15_KBD_BRIGHTNESS].red =
+ DIV_ROUND_CLOSEST(tbuf[1] * 255, high);
+ g15->leds[LG_G15_KBD_BRIGHTNESS].green =
+ DIV_ROUND_CLOSEST(tbuf[2] * 255, high);
+ g15->leds[LG_G15_KBD_BRIGHTNESS].blue =
+ DIV_ROUND_CLOSEST(tbuf[3] * 255, high);
+ g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = high;
+ } else {
+ g15->leds[LG_G15_KBD_BRIGHTNESS].red = 255;
+ g15->leds[LG_G15_KBD_BRIGHTNESS].green = 255;
+ g15->leds[LG_G15_KBD_BRIGHTNESS].blue = 255;
+ g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = 0;
+ }
+
+ /* Macro LEDs. */
+ ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS,
+ tbuf, 5,
+ HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+ if (ret != 5) {
+ hid_err(g15->hdev, "Error getting macro LED brightness: %d\n", ret);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ for (int i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i)
+ g15->leds[i].brightness = !!(tbuf[1] & (1 << (i - LG_G15_MACRO_PRESET1)));
+
+ /*
+ * Bit 23 of g13_input_report.keybits[] contains the backlight's
+ * current HW toggle state. Retrieve it from the device.
+ */
+ ret = hid_hw_raw_request(g15->hdev, LG_G13_INPUT_REPORT,
+ tbuf, sizeof(struct g13_input_report),
+ HID_INPUT_REPORT, HID_REQ_GET_REPORT);
+ if (ret != sizeof(struct g13_input_report)) {
+ hid_err(g15->hdev, "Error getting backlight on/off state: %d\n", ret);
+ return (ret < 0) ? ret : -EIO;
+ }
+ g15->backlight_disabled =
+ !TEST_BIT(((struct g13_input_report *) tbuf)->keybits,
+ LG_G13_BACKLIGHT_HW_ON_BIT);
+
+ return 0;
+}
+
+static int lg_g13_kbd_led_write(struct lg_g15_data *g15,
+ struct lg_g15_led *g15_led,
+ enum led_brightness brightness)
+{
+ struct mc_subled const * const subleds = g15_led->mcdev.subled_info;
+ u8 * const tbuf = g15->transfer_buf;
+ int ret;
+
+ guard(mutex)(&g15->mutex);
+
+ led_mc_calc_color_components(&g15_led->mcdev, brightness);
+
+ tbuf[0] = 5;
+ tbuf[1] = subleds[0].brightness;
+ tbuf[2] = subleds[1].brightness;
+ tbuf[3] = subleds[2].brightness;
+ tbuf[4] = 0;
+
+ ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB,
+ tbuf, 5,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret != 5) {
+ hid_err(g15->hdev, "Error setting backlight brightness: %d\n", ret);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ g15_led->brightness = brightness;
+ return 0;
+}
+
+static int lg_g13_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(mc, struct lg_g15_led, mcdev);
+ struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
+
+ /* Ignore LED off on unregister / keyboard unplug */
+ if (led_cdev->flags & LED_UNREGISTERING)
+ return 0;
+
+ return lg_g13_kbd_led_write(g15, g15_led, brightness);
+}
+
+static enum led_brightness lg_g13_kbd_led_get(struct led_classdev *led_cdev)
+{
+ struct led_classdev_mc const * const mc = lcdev_to_mccdev(led_cdev);
+ struct lg_g15_led const *g15_led =
+ container_of(mc, struct lg_g15_led, mcdev);
+
+ return g15_led->brightness;
+}
+
+static int lg_g13_mkey_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
+{
+ 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);
+ int i, ret;
+ u8 * const tbuf = g15->transfer_buf;
+ u8 val, mask = 0;
+
+ /* Ignore LED off on unregister / keyboard unplug */
+ if (led_cdev->flags & LED_UNREGISTERING)
+ return 0;
+
+ guard(mutex)(&g15->mutex);
+
+ for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i) {
+ if (i == g15_led->led)
+ val = brightness;
+ else
+ val = g15->leds[i].brightness;
+
+ if (val)
+ mask |= 1 << (i - LG_G15_MACRO_PRESET1);
+ }
+
+ tbuf[0] = 5;
+ tbuf[1] = mask;
+ tbuf[2] = 0;
+ tbuf[3] = 0;
+ tbuf[4] = 0;
+
+ ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS,
+ tbuf, 5,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret != 5) {
+ hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
+ return (ret < 0) ? ret : -EIO;
+ }
+
+ g15_led->brightness = brightness;
+ return 0;
+}
+
+static enum led_brightness lg_g13_mkey_led_get(struct led_classdev *led_cdev)
+{
+ /*
+ * G13 doesn't change macro key LEDs behind our back, so they're
+ * whatever we last set them to.
+ */
+ struct lg_g15_led *g15_led =
+ container_of(led_cdev, struct lg_g15_led, cdev);
+
+ return g15_led->brightness;
+}
+
/******** G15 and G15 v2 LED functions ********/
static int lg_g15_update_led_brightness(struct lg_g15_data *g15)
@@ -221,6 +426,20 @@ static int lg_g510_get_initial_led_brightness(struct lg_g15_data *g15, int i)
g15->leds[i].brightness = 0;
}
+ if (i)
+ return 0;
+
+ ret = hid_hw_raw_request(g15->hdev, LG_G510_INPUT_KBD_BACKLIGHT,
+ g15->transfer_buf, 2,
+ HID_INPUT_REPORT, HID_REQ_GET_REPORT);
+ if (ret != 2) {
+ /* This can happen when a KVM switch is used, so only warn. */
+ hid_warn(g15->hdev, "Error getting backlight state: %d\n", ret);
+ return 0;
+ }
+
+ g15->backlight_disabled = g15->transfer_buf[1] & 0x04;
+
return 0;
}
@@ -229,15 +448,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 +477,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 +496,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);
}
@@ -445,6 +603,8 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
int ret;
switch (g15->model) {
+ case LG_G13:
+ return lg_g13_get_leds_state(g15);
case LG_G15:
case LG_G15_V2:
return lg_g15_update_led_brightness(g15);
@@ -472,6 +632,108 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
/******** Input functions ********/
+/* Table mapping keybits[] bit positions to event codes. */
+/* Note: Indices are discontinuous to aid readability. */
+static const u16 g13_keys_for_bits[] = {
+ /* Main keypad - keys G1 - G22 */
+ [0] = KEY_MACRO1,
+ [1] = KEY_MACRO2,
+ [2] = KEY_MACRO3,
+ [3] = KEY_MACRO4,
+ [4] = KEY_MACRO5,
+ [5] = KEY_MACRO6,
+ [6] = KEY_MACRO7,
+ [7] = KEY_MACRO8,
+ [8] = KEY_MACRO9,
+ [9] = KEY_MACRO10,
+ [10] = KEY_MACRO11,
+ [11] = KEY_MACRO12,
+ [12] = KEY_MACRO13,
+ [13] = KEY_MACRO14,
+ [14] = KEY_MACRO15,
+ [15] = KEY_MACRO16,
+ [16] = KEY_MACRO17,
+ [17] = KEY_MACRO18,
+ [18] = KEY_MACRO19,
+ [19] = KEY_MACRO20,
+ [20] = KEY_MACRO21,
+ [21] = KEY_MACRO22,
+
+ /* LCD menu buttons. */
+ [24] = KEY_KBD_LCD_MENU5, /* "Next page" button */
+ [25] = KEY_KBD_LCD_MENU1, /* Left-most */
+ [26] = KEY_KBD_LCD_MENU2,
+ [27] = KEY_KBD_LCD_MENU3,
+ [28] = KEY_KBD_LCD_MENU4, /* Right-most */
+
+ /* Macro preset and record buttons; have red LEDs under them. */
+ [29] = KEY_MACRO_PRESET1,
+ [30] = KEY_MACRO_PRESET2,
+ [31] = KEY_MACRO_PRESET3,
+ [32] = KEY_MACRO_RECORD_START,
+
+ /* 33-35 handled by joystick device. */
+
+ /* Backlight toggle. */
+ [37] = KEY_LIGHTS_TOGGLE,
+};
+
+#define G13_JS_KEYBITS_OFFSET 33
+
+static const u16 g13_keys_for_bits_js[] = {
+ /* Joystick buttons */
+ /* These keybits are at bit indices 33, 34, and 35. */
+ BTN_BASE, /* Left side */
+ BTN_BASE2, /* Bottom side */
+ BTN_THUMB, /* Stick depress */
+};
+
+static int lg_g13_event(struct lg_g15_data *g15, u8 const *data)
+{
+ struct g13_input_report const * const rep = (struct g13_input_report *) data;
+ int i, val;
+ bool backlight_disabled;
+
+ /*
+ * Main macropad and menu keys.
+ * Emit key events defined for each bit position.
+ */
+ for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) {
+ if (g13_keys_for_bits[i]) {
+ val = TEST_BIT(rep->keybits, i);
+ input_report_key(g15->input, g13_keys_for_bits[i], val);
+ }
+ }
+ input_sync(g15->input);
+
+ /*
+ * Joystick.
+ * Emit button and deflection events.
+ */
+ for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i) {
+ val = TEST_BIT(rep->keybits, i + G13_JS_KEYBITS_OFFSET);
+ input_report_key(g15->input_js, g13_keys_for_bits_js[i], val);
+ }
+ input_report_abs(g15->input_js, ABS_X, rep->joy_x);
+ input_report_abs(g15->input_js, ABS_Y, rep->joy_y);
+ input_sync(g15->input_js);
+
+ /*
+ * Bit 23 of keybits[] reports the current backlight on/off state. If
+ * it has changed from the last cached value, apply an update.
+ */
+ backlight_disabled = !TEST_BIT(rep->keybits, LG_G13_BACKLIGHT_HW_ON_BIT);
+ if (backlight_disabled ^ g15->backlight_disabled) {
+ led_classdev_notify_brightness_hw_changed(
+ &g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev,
+ backlight_disabled
+ ? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
+ g15->backlight_disabled = backlight_disabled;
+ }
+
+ return 0;
+}
+
/* On the G15 Mark I Logitech has been quite creative with which bit is what */
static void lg_g15_handle_lcd_menu_keys(struct lg_g15_data *g15, u8 *data)
{
@@ -604,14 +866,24 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data)
static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data)
{
+ struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS];
bool backlight_disabled;
+ backlight_disabled = data[1] & 0x04;
+ if (backlight_disabled == g15->backlight_disabled)
+ return 0;
+
+ led_classdev_notify_brightness_hw_changed(
+ &g15_led->mcdev.led_cdev,
+ backlight_disabled ? 0 : g15_led->brightness);
+
+ g15->backlight_disabled = backlight_disabled;
+
/*
* The G510 ignores backlight updates when the backlight is turned off
* through the light toggle button on the keyboard, to work around this
* we queue a workitem to sync values when the backlight is turned on.
*/
- backlight_disabled = data[1] & 0x04;
if (!backlight_disabled)
schedule_work(&g15->work);
@@ -627,6 +899,10 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
return 0;
switch (g15->model) {
+ case LG_G13:
+ if (data[0] == 0x01 && size == sizeof(struct g13_input_report))
+ return lg_g13_event(g15, data);
+ break;
case LG_G15:
if (data[0] == 0x02 && size == 9)
return lg_g15_event(g15, data);
@@ -643,9 +919,9 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
break;
case LG_G510:
case LG_G510_USB_AUDIO:
- if (data[0] == 0x03 && size == 5)
+ if (data[0] == LG_G510_INPUT_MACRO_KEYS && size == 5)
return lg_g510_event(g15, data);
- if (data[0] == 0x04 && size == 2)
+ if (data[0] == LG_G510_INPUT_KBD_BACKLIGHT && size == 2)
return lg_g510_leds_event(g15, data);
break;
}
@@ -667,12 +943,78 @@ 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;
+ struct lg_g15_led * const gled = &g15->leds[index];
+
+ if (g15->model == LG_G13) {
+ gled->mcdev.led_cdev.brightness_set_blocking =
+ lg_g13_kbd_led_set;
+ gled->mcdev.led_cdev.brightness_get =
+ lg_g13_kbd_led_get;
+ gled->mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
+ } else {
+ gled->mcdev.led_cdev.brightness_set_blocking =
+ lg_g510_kbd_led_set;
+ gled->mcdev.led_cdev.brightness_get =
+ lg_g510_kbd_led_get;
+ if (index == LG_G15_KBD_BRIGHTNESS)
+ g15->leds[index].mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
+ }
+ gled->mcdev.led_cdev.max_brightness = 255;
+ gled->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 = gled->red;
+ break;
+ case LED_COLOR_ID_GREEN:
+ subled_info[i].color_index = LED_COLOR_ID_GREEN;
+ subled_info[i].intensity = gled->green;
+ break;
+ case LED_COLOR_ID_BLUE:
+ subled_info[i].color_index = LED_COLOR_ID_BLUE;
+ subled_info[i].intensity = gled->blue;
+ break;
+ }
+ subled_info[i].channel = i;
+ }
+ gled->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;
switch (g15->model) {
+ case LG_G13:
+ if (i < LG_G15_BRIGHTNESS_MAX) {
+ /* RGB backlight. */
+ lg_g15_setup_led_rgb(g15, i);
+ ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev,
+ &g15->leds[i].mcdev,
+ NULL);
+ } else {
+ /* Macro keys */
+ g15->leds[i].cdev.brightness_set_blocking = lg_g13_mkey_led_set;
+ g15->leds[i].cdev.brightness_get = lg_g13_mkey_led_get;
+ g15->leds[i].cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(&g15->hdev->dev,
+ &g15->leds[i].cdev);
+ }
+ break;
case LG_G15:
case LG_G15_V2:
g15->leds[i].cdev.brightness_get = lg_g15_led_get;
@@ -685,6 +1027,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 +1040,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,19 +1052,18 @@ 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 */
-static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
- const char *name)
+static void lg_g15_init_input_dev_core(struct hid_device *hdev, struct input_dev *input,
+ char const *name)
{
- int i;
-
input->name = name;
input->phys = hdev->phys;
input->uniq = hdev->uniq;
@@ -733,12 +1074,42 @@ static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *inp
input->dev.parent = &hdev->dev;
input->open = lg_g15_input_open;
input->close = lg_g15_input_close;
+}
+
+static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
+ const char *name)
+{
+ int i;
+
+ lg_g15_init_input_dev_core(hdev, input, name);
/* Keys below the LCD, intended for controlling a menu on the LCD */
for (i = 0; i < 5; i++)
input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i);
}
+static void lg_g13_init_input_dev(struct hid_device *hdev,
+ struct input_dev *input, const char *name,
+ struct input_dev *input_js, const char *name_js)
+{
+ /* Macropad. */
+ lg_g15_init_input_dev_core(hdev, input, name);
+ for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) {
+ if (g13_keys_for_bits[i])
+ input_set_capability(input, EV_KEY, g13_keys_for_bits[i]);
+ }
+
+ /* OBTW, we're a joystick, too... */
+ lg_g15_init_input_dev_core(hdev, input_js, name_js);
+ for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i)
+ input_set_capability(input_js, EV_KEY, g13_keys_for_bits_js[i]);
+
+ input_set_capability(input_js, EV_ABS, ABS_X);
+ input_set_abs_params(input_js, ABS_X, 0, 255, 0, 0);
+ input_set_capability(input_js, EV_ABS, ABS_Y);
+ input_set_abs_params(input_js, ABS_Y, 0, 255, 0, 0);
+}
+
static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
static const char * const led_names[] = {
@@ -755,7 +1126,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
unsigned int connect_mask = 0;
bool has_ff000000 = false;
struct lg_g15_data *g15;
- struct input_dev *input;
+ struct input_dev *input, *input_js;
struct hid_report *rep;
int ret, i, gkeys = 0;
@@ -794,6 +1165,25 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_set_drvdata(hdev, (void *)g15);
switch (g15->model) {
+ case LG_G13:
+ /*
+ * The G13 has an analog thumbstick with nearby buttons. Some
+ * libraries and applications are known to ignore devices that
+ * don't "look like" a joystick, and a device with two ABS axes
+ * and 25+ macro keys would confuse them.
+ *
+ * Create an additional input device dedicated to appear as a
+ * simplified joystick (two ABS axes, three BTN buttons).
+ */
+ input_js = devm_input_allocate_device(&hdev->dev);
+ if (!input_js)
+ return -ENOMEM;
+ g15->input_js = input_js;
+ input_set_drvdata(input_js, hdev);
+
+ connect_mask = HID_CONNECT_HIDRAW;
+ gkeys = 25;
+ break;
case LG_G15:
INIT_WORK(&g15->work, lg_g15_leds_changed_work);
/*
@@ -875,6 +1265,38 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto error_hw_stop;
return 0; /* All done */
+ } else if (g15->model == LG_G13) {
+ static char const * const g13_led_names[] = {
+ /* Backlight is shared between LCD and keys. */
+ "g13:rgb:kbd_backlight",
+ NULL, /* Keep in sync with led_type enum */
+ "g13:red:macro_preset_1",
+ "g13:red:macro_preset_2",
+ "g13:red:macro_preset_3",
+ "g13:red:macro_record",
+ };
+ lg_g13_init_input_dev(hdev,
+ input, "Logitech G13 Gaming Keypad",
+ input_js, "Logitech G13 Thumbstick");
+ ret = input_register_device(input);
+ if (ret)
+ goto error_hw_stop;
+ ret = input_register_device(input_js);
+ if (ret)
+ goto error_hw_stop;
+
+ for (i = 0; i < ARRAY_SIZE(g13_led_names); ++i) {
+ if (g13_led_names[i]) {
+ ret = lg_g15_register_led(g15, i, g13_led_names[i]);
+ if (ret)
+ goto error_hw_stop;
+ }
+ }
+ led_classdev_notify_brightness_hw_changed(
+ &g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev,
+ g15->backlight_disabled
+ ? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
+ return 0;
}
/* Setup and register input device */
@@ -919,6 +1341,13 @@ error_hw_stop:
}
static const struct hid_device_id lg_g15_devices[] = {
+ /*
+ * The G13 is a macropad-only device with an LCD, LED backlighing,
+ * and joystick.
+ */
+ { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_G13),
+ .driver_data = LG_G13 },
/* The G11 is a G15 without the LCD, treat it as a G15 */
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_G11),
@@ -954,4 +1383,5 @@ static struct hid_driver lg_g15_driver = {
module_hid_driver(lg_g15_driver);
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("HID driver for gaming keys on Logitech gaming keyboards");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
index fb3f7258009c..9a2cfa018bd3 100644
--- a/drivers/hid/hid-lg.c
+++ b/drivers/hid/hid-lg.c
@@ -58,7 +58,7 @@
* These descriptors remove the combined Y axis and instead report
* separate throttle (Y) and brake (RZ).
*/
-static __u8 df_rdesc_fixed[] = {
+static const __u8 df_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -124,7 +124,7 @@ static __u8 df_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 dfp_rdesc_fixed[] = {
+static const __u8 dfp_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -172,7 +172,7 @@ static __u8 dfp_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 fv_rdesc_fixed[] = {
+static const __u8 fv_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -239,7 +239,7 @@ static __u8 fv_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 momo_rdesc_fixed[] = {
+static const __u8 momo_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -285,7 +285,7 @@ static __u8 momo_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 momo2_rdesc_fixed[] = {
+static const __u8 momo2_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -333,7 +333,7 @@ static __u8 momo2_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 ffg_rdesc_fixed[] = {
+static const __u8 ffg_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
@@ -379,7 +379,7 @@ static __u8 ffg_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 fg_rdesc_fixed[] = {
+static const __u8 fg_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystik), */
0xA1, 0x01, /* Collection (Application), */
@@ -427,7 +427,7 @@ static __u8 fg_rdesc_fixed[] = {
* above the logical maximum described in descriptor. This extends
* the original value of 0x28c of logical maximum to 0x104d
*/
-static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
@@ -453,8 +453,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == FG_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Wingman Formula GP report descriptor\n");
- rdesc = fg_rdesc_fixed;
*rsize = sizeof(fg_rdesc_fixed);
+ return fg_rdesc_fixed;
} else {
hid_info(hdev,
"rdesc size test failed for formula gp\n");
@@ -466,8 +466,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == FFG_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Wingman Formula Force GP report descriptor\n");
- rdesc = ffg_rdesc_fixed;
*rsize = sizeof(ffg_rdesc_fixed);
+ return ffg_rdesc_fixed;
}
break;
@@ -476,8 +476,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == DF_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Driving Force report descriptor\n");
- rdesc = df_rdesc_fixed;
*rsize = sizeof(df_rdesc_fixed);
+ return df_rdesc_fixed;
}
break;
@@ -485,8 +485,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == MOMO_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Momo Force (Red) report descriptor\n");
- rdesc = momo_rdesc_fixed;
*rsize = sizeof(momo_rdesc_fixed);
+ return momo_rdesc_fixed;
}
break;
@@ -494,8 +494,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == MOMO2_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Momo Racing Force (Black) report descriptor\n");
- rdesc = momo2_rdesc_fixed;
*rsize = sizeof(momo2_rdesc_fixed);
+ return momo2_rdesc_fixed;
}
break;
@@ -503,8 +503,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == FV_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Formula Vibration report descriptor\n");
- rdesc = fv_rdesc_fixed;
*rsize = sizeof(fv_rdesc_fixed);
+ return fv_rdesc_fixed;
}
break;
@@ -512,8 +512,8 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (*rsize == DFP_RDESC_ORIG_SIZE) {
hid_info(hdev,
"fixing up Logitech Driving Force Pro report descriptor\n");
- rdesc = dfp_rdesc_fixed;
*rsize = sizeof(dfp_rdesc_fixed);
+ return dfp_rdesc_fixed;
}
break;
@@ -942,4 +942,5 @@ module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
#endif
+MODULE_DESCRIPTION("HID driver for some logitech \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
index b7e1949f3cf7..109735b89b7a 100644
--- a/drivers/hid/hid-lg3ff.c
+++ b/drivers/hid/hid-lg3ff.c
@@ -41,10 +41,6 @@
* I'm sure these are effects that I don't know enough about them
*/
-struct lg3ff_device {
- struct hid_report *report;
-};
-
static int hid_lg3ff_play(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index e3fcf1353fb3..32b711723f2a 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -823,7 +823,7 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr
for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
if (entry->wdata.alternate_modes & BIT(i)) {
/* Print tag and full name */
- count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
+ count += sysfs_emit_at(buf, count, "%s: %s",
lg4ff_alternate_modes[i].tag,
!lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name);
if (count >= PAGE_SIZE - 1)
@@ -832,9 +832,9 @@ static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attr
/* Mark the currently active mode with an asterisk */
if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
(lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
- count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
+ count += sysfs_emit_at(buf, count, " *\n");
else
- count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+ count += sysfs_emit_at(buf, count, "\n");
if (count >= PAGE_SIZE - 1)
return count;
@@ -956,7 +956,7 @@ static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *a
return 0;
}
- count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine);
+ count = sysfs_emit(buf, "%u\n", entry->wdata.combine);
return count;
}
@@ -1009,7 +1009,7 @@ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *att
return 0;
}
- count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range);
+ count = sysfs_emit(buf, "%u\n", entry->wdata.range);
return count;
}
@@ -1073,7 +1073,7 @@ static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *a
return 0;
}
- count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name);
+ count = sysfs_emit(buf, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name);
return count;
}
@@ -1350,7 +1350,8 @@ int lg4ff_init(struct hid_device *hid)
/* Initialize device properties */
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
- BUG_ON(mmode_idx == -1);
+ if (WARN_ON(mmode_idx == -1))
+ return -EINVAL;
mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
}
lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index 62180414efcc..44b716697510 100644
--- a/drivers/hid/hid-logitech-dj.c
+++ b/drivers/hid/hid-logitech-dj.c
@@ -13,7 +13,7 @@
#include <linux/kfifo.h>
#include <linux/delay.h>
#include <linux/usb.h> /* For to_usb_interface for kvm extra intf check */
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
#define DJ_MAX_PAIRED_DEVICES 7
@@ -116,6 +116,7 @@ enum recvr_type {
recvr_type_dj,
recvr_type_hidpp,
recvr_type_gaming_hidpp,
+ recvr_type_gaming_hidpp_ls_1_3,
recvr_type_mouse_only,
recvr_type_27mhz,
recvr_type_bluetooth,
@@ -148,6 +149,7 @@ struct dj_receiver_dev {
struct kfifo notif_fifo;
unsigned long last_query; /* in jiffies */
bool ready;
+ bool dj_mode;
enum recvr_type type;
unsigned int unnumbered_application;
spinlock_t lock;
@@ -211,6 +213,44 @@ static const char kbd_descriptor[] = {
0xC0
};
+/* Gaming Keyboard descriptor (1) */
+static const char kbd_lightspeed_1_3_descriptor[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x06, /* Usage (Keyboard) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x01, /* Report ID (1) */
+ 0x05, 0x07, /* Usage Page (Kbrd/Keypad) */
+ 0x19, 0xE0, /* Usage Minimum (0xE0) */
+ 0x29, 0xE7, /* Usage Maximum (0xE7) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x95, 0x08, /* Report Count (8) */
+ 0x81, 0x02, /* Input (Data,Var) */
+ 0x95, 0x70, /* Report Count (112) */
+ 0x19, 0x04, /* Usage Minimum (0x04) */
+ 0x29, 0x73, /* Usage Maximum (0x73) */
+ 0x81, 0x02, /* Input (Data,Var,Abs) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x19, 0x87, /* Usage Minimum (0x87) */
+ 0x29, 0x8B, /* Usage Maximum (0x8B) */
+ 0x81, 0x02, /* Input (Data,Var,Abs) */
+ 0x95, 0x03, /* Report Count (3) */
+ 0x19, 0x90, /* Usage Minimum (0x90) */
+ 0x29, 0x92, /* Usage Maximum (0x92) */
+ 0x81, 0x02, /* Input (Data,Var,Abs) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x85, 0x0E, /* Report ID (14) */
+ 0x05, 0x08, /* Usage Page (LEDs) */
+ 0x19, 0x01, /* Usage Minimum (Num Lock) */
+ 0x29, 0x05, /* Usage Maximum (Kana) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x03, /* Report Size (3) */
+ 0x91, 0x03, /* Output (Const,Var,Abs) */
+ 0xC0, /* End Collection */
+};
+
/* Mouse descriptor (2) */
static const char mse_descriptor[] = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
@@ -415,6 +455,51 @@ static const char mse_high_res_descriptor[] = {
0xC0, /* END_COLLECTION */
};
+/* Gaming Mouse descriptor with vendor data (2) */
+static const char mse_high_res_ls_1_3_descriptor[] = {
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x02, /* Usage (Mouse) */
+ 0xA1, 0x01, /* Collection (Application) */
+ 0x85, 0x02, /* Report ID (2) */
+ 0x09, 0x01, /* Usage (Pointer) */
+ 0xA1, 0x00, /* Collection (Physical) */
+ 0x95, 0x10, /* Report Count (16) */
+ 0x75, 0x01, /* Report Size (1) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x25, 0x01, /* Logical Maximum (1) */
+ 0x05, 0x09, /* Usage Page (Button) */
+ 0x19, 0x01, /* Usage Minimum (0x01) */
+ 0x29, 0x10, /* Usage Maximum (0x10) */
+ 0x81, 0x02, /* Input (Data,Var,Abs) */
+ 0x95, 0x02, /* Report Count (2) */
+ 0x75, 0x10, /* Report Size (16) */
+ 0x16, 0x01, 0x80, /* Logical Minimum (-32767) */
+ 0x26, 0xFF, 0x7F, /* Logical Maximum (32767) */
+ 0x05, 0x01, /* Usage Page (Generic Desktop) */
+ 0x09, 0x30, /* Usage (X) */
+ 0x09, 0x31, /* Usage (Y) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x15, 0x81, /* Logical Minimum (-127) */
+ 0x25, 0x7F, /* Logical Maximum (127) */
+ 0x09, 0x38, /* Usage (Wheel) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0x95, 0x01, /* Report Count (1) */
+ 0x05, 0x0C, /* Usage Page (Consumer) */
+ 0x0A, 0x38, 0x02, /* Usage (AC Pan) */
+ 0x81, 0x06, /* Input (Data,Var,Rel) */
+ 0xC0, /* End Collection */
+ 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */
+ 0x09, 0xF1, /* Usage (0xF1) */
+ 0x75, 0x08, /* Report Size (8) */
+ 0x95, 0x05, /* Report Count (5) */
+ 0x15, 0x00, /* Logical Minimum (0) */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
+ 0x81, 0x00, /* Input (Data,Array,Abs) */
+ 0xC0, /* End Collection */
+};
+
/* Consumer Control descriptor (3) */
static const char consumer_descriptor[] = {
0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */
@@ -520,9 +605,9 @@ static const char hidpp_descriptor[] = {
/* Maximum size of all defined hid reports in bytes (including report id) */
#define MAX_REPORT_SIZE 8
-/* Make sure all descriptors are present here */
+/* Make sure the largest of each descriptor type is present here */
#define MAX_RDESC_SIZE \
- (sizeof(kbd_descriptor) + \
+ (sizeof(kbd_lightspeed_1_3_descriptor) +\
sizeof(mse_bluetooth_descriptor) + \
sizeof(mse5_bluetooth_descriptor) + \
sizeof(consumer_descriptor) + \
@@ -557,6 +642,8 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
static const struct hid_ll_driver logi_dj_ll_driver;
static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);
+static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
+ unsigned int timeout);
static void delayedwork_callback(struct work_struct *work);
static LIST_HEAD(dj_hdev_list);
@@ -805,7 +892,6 @@ static void delayedwork_callback(struct work_struct *work)
struct dj_workitem workitem;
unsigned long flags;
int count;
- int retval;
dbg_hid("%s\n", __func__);
@@ -842,11 +928,10 @@ static void delayedwork_callback(struct work_struct *work)
logi_dj_recv_destroy_djhid_device(djrcv_dev, &workitem);
break;
case WORKITEM_TYPE_UNKNOWN:
- retval = logi_dj_recv_query_paired_devices(djrcv_dev);
- if (retval) {
- hid_err(djrcv_dev->hidpp, "%s: logi_dj_recv_query_paired_devices error: %d\n",
- __func__, retval);
- }
+ if (!djrcv_dev->dj_mode)
+ logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
+
+ logi_dj_recv_query_paired_devices(djrcv_dev);
break;
case WORKITEM_TYPE_EMPTY:
dbg_hid("%s: device list is empty\n", __func__);
@@ -965,9 +1050,7 @@ static void logi_hidpp_dev_conn_notif_equad(struct hid_device *hdev,
}
break;
case REPORT_TYPE_MOUSE:
- workitem->reports_supported |= STD_MOUSE | HIDPP;
- if (djrcv_dev->type == recvr_type_mouse_only)
- workitem->reports_supported |= MULTIMEDIA;
+ workitem->reports_supported |= STD_MOUSE | HIDPP | MULTIMEDIA;
break;
}
}
@@ -1241,8 +1324,13 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
djrcv_dev->last_query = jiffies;
- if (djrcv_dev->type != recvr_type_dj)
- return logi_dj_recv_query_hidpp_devices(djrcv_dev);
+ if (!djrcv_dev->dj_mode)
+ return 0;
+
+ if (djrcv_dev->type != recvr_type_dj) {
+ retval = logi_dj_recv_query_hidpp_devices(djrcv_dev);
+ goto out;
+ }
dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
if (!dj_report)
@@ -1252,6 +1340,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
dj_report->report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES;
retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
kfree(dj_report);
+out:
+ if (retval < 0)
+ hid_err(djrcv_dev->hidpp, "%s error:%d\n", __func__, retval);
+
return retval;
}
@@ -1277,6 +1369,8 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
(u8)timeout;
retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
+ if (retval)
+ goto out;
/*
* Ugly sleep to work around a USB 3.0 bug when the receiver is
@@ -1306,11 +1400,17 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
buf[5] = 0x09;
buf[6] = 0x00;
- hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf,
+ retval = hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf,
HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT,
HID_REQ_SET_REPORT);
+out:
kfree(dj_report);
+
+ if (retval < 0)
+ hid_err(hdev, "%s error:%d\n", __func__, retval);
+
+ djrcv_dev->dj_mode = retval >= 0;
return retval;
}
@@ -1371,12 +1471,19 @@ static int logi_dj_ll_raw_request(struct hid_device *hid,
return -EINVAL;
if (djrcv_dev->type != recvr_type_dj && count >= 2) {
+ unsigned char led_report_id = 0;
+
if (!djrcv_dev->keyboard) {
hid_warn(hid, "Received REPORT_TYPE_LEDS request before the keyboard interface was enumerated\n");
return 0;
}
+
+ /* This Lightspeed receiver expects LED reports with report ID 1 */
+ if (djrcv_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+ led_report_id = 1;
+
/* usbhid overrides the report ID and ignores the first byte */
- return hid_hw_raw_request(djrcv_dev->keyboard, 0, buf, count,
+ return hid_hw_raw_request(djrcv_dev->keyboard, led_report_id, buf, count,
report_type, reqtype);
}
@@ -1423,7 +1530,11 @@ static int logi_dj_ll_parse(struct hid_device *hid)
if (djdev->reports_supported & STD_KEYBOARD) {
dbg_hid("%s: sending a kbd descriptor, reports_supported: %llx\n",
__func__, djdev->reports_supported);
- rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
+ if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+ rdcat(rdesc, &rsize, kbd_lightspeed_1_3_descriptor,
+ sizeof(kbd_lightspeed_1_3_descriptor));
+ else
+ rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
}
if (djdev->reports_supported & STD_MOUSE) {
@@ -1433,6 +1544,9 @@ static int logi_dj_ll_parse(struct hid_device *hid)
djdev->dj_receiver_dev->type == recvr_type_mouse_only)
rdcat(rdesc, &rsize, mse_high_res_descriptor,
sizeof(mse_high_res_descriptor));
+ else if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
+ rdcat(rdesc, &rsize, mse_high_res_ls_1_3_descriptor,
+ sizeof(mse_high_res_ls_1_3_descriptor));
else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
rdcat(rdesc, &rsize, mse_27mhz_descriptor,
sizeof(mse_27mhz_descriptor));
@@ -1692,11 +1806,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
}
/*
* Mouse-only receivers send unnumbered mouse data. The 27 MHz
- * receiver uses 6 byte packets, the nano receiver 8 bytes.
+ * receiver uses 6 byte packets, the nano receiver 8 bytes,
+ * the lightspeed receiver (Pro X Superlight) 13 bytes.
*/
if (djrcv_dev->unnumbered_application == HID_GD_MOUSE &&
- size <= 8) {
- u8 mouse_report[9];
+ size <= 13){
+ u8 mouse_report[14];
/* Prepend report id */
mouse_report[0] = REPORT_TYPE_MOUSE;
@@ -1773,6 +1888,7 @@ static int logi_dj_probe(struct hid_device *hdev,
case recvr_type_dj: no_dj_interfaces = 3; break;
case recvr_type_hidpp: no_dj_interfaces = 2; break;
case recvr_type_gaming_hidpp: no_dj_interfaces = 3; break;
+ case recvr_type_gaming_hidpp_ls_1_3: no_dj_interfaces = 3; break;
case recvr_type_mouse_only: no_dj_interfaces = 2; break;
case recvr_type_27mhz: no_dj_interfaces = 2; break;
case recvr_type_bluetooth: no_dj_interfaces = 2; break;
@@ -1831,12 +1947,11 @@ static int logi_dj_probe(struct hid_device *hdev,
}
if (has_hidpp) {
- retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
- if (retval < 0) {
- hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n",
- __func__, retval);
- goto switch_to_dj_mode_fail;
- }
+ /*
+ * This can fail with a KVM. Ignore errors to let the probe
+ * succeed, logi_dj_recv_queue_unknown_work will retry later.
+ */
+ logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
}
/* This is enabling the polling urb on the IN endpoint */
@@ -1854,21 +1969,13 @@ static int logi_dj_probe(struct hid_device *hdev,
spin_lock_irqsave(&djrcv_dev->lock, flags);
djrcv_dev->ready = true;
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
- retval = logi_dj_recv_query_paired_devices(djrcv_dev);
- if (retval < 0) {
- hid_err(hdev, "%s: logi_dj_recv_query_paired_devices error:%d\n",
- __func__, retval);
- /*
- * This can happen with a KVM, let the probe succeed,
- * logi_dj_recv_queue_unknown_work will retry later.
- */
- }
+ /* This too can fail with a KVM, ignore errors. */
+ logi_dj_recv_query_paired_devices(djrcv_dev);
}
return 0;
llopen_failed:
-switch_to_dj_mode_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
@@ -1879,18 +1986,12 @@ hid_hw_start_fail:
#ifdef CONFIG_PM
static int logi_dj_reset_resume(struct hid_device *hdev)
{
- int retval;
struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
if (!djrcv_dev || djrcv_dev->hidpp != hdev)
return 0;
- retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
- if (retval < 0) {
- hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n",
- __func__, retval);
- }
-
+ logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
return 0;
}
#endif
@@ -1980,6 +2081,18 @@ static const struct hid_device_id logi_dj_receivers[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
.driver_data = recvr_type_gaming_hidpp},
+ { /* Logitech lightspeed receiver (0xc543) */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2),
+ .driver_data = recvr_type_gaming_hidpp},
+ { /* Logitech lightspeed receiver (0xc547) */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
+ .driver_data = recvr_type_gaming_hidpp_ls_1_3},
+ { /* Logitech lightspeed receiver (0xc54d) */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4),
+ .driver_data = recvr_type_gaming_hidpp_ls_1_3},
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
@@ -2044,6 +2157,7 @@ static struct hid_driver logi_djreceiver_driver = {
module_hid_driver(logi_djreceiver_driver);
+MODULE_DESCRIPTION("HID driver for Logitech receivers");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Logitech");
MODULE_AUTHOR("Nestor Lopez Casado");
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 129b01be488d..d5011a5d0890 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -23,10 +23,11 @@
#include <linux/workqueue.h>
#include <linux/atomic.h>
#include <linux/fixp-arith.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "usbhid/usbhid.h"
#include "hid-ids.h"
+MODULE_DESCRIPTION("Support for Logitech devices relying on the HID++ specification");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
@@ -69,12 +70,12 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
#define HIDPP_QUIRK_DELAYED_INIT BIT(23)
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
-#define HIDPP_QUIRK_UNIFYING BIT(25)
-#define HIDPP_QUIRK_HIDPP_WHEELS BIT(26)
-#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
-#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
-#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(29)
-#define HIDPP_QUIRK_WIRELESS_STATUS BIT(30)
+#define HIDPP_QUIRK_HIDPP_WHEELS BIT(25)
+#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(26)
+#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(27)
+#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28)
+#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29)
+#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -193,8 +194,8 @@ struct hidpp_device {
void *private_data;
struct work_struct work;
+ struct work_struct reset_hi_res_work;
struct kfifo delayed_work_fifo;
- atomic_t connected;
struct input_dev *delayed_input;
unsigned long quirks;
@@ -205,6 +206,8 @@ struct hidpp_device {
struct hidpp_scroll_counter vertical_wheel_counter;
u8 wireless_feature_index;
+
+ bool connected_once;
};
/* HID++ 1.0 error codes */
@@ -228,15 +231,13 @@ struct hidpp_device {
#define HIDPP20_ERROR_INVALID_ARGS 0x02
#define HIDPP20_ERROR_OUT_OF_RANGE 0x03
#define HIDPP20_ERROR_HW_ERROR 0x04
-#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05
+#define HIDPP20_ERROR_NOT_ALLOWED 0x05
#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
#define HIDPP20_ERROR_BUSY 0x08
#define HIDPP20_ERROR_UNSUPPORTED 0x09
#define HIDPP20_ERROR 0xff
-static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
-
static int __hidpp_send_report(struct hid_device *hdev,
struct hidpp_report *hidpp_report)
{
@@ -275,21 +276,22 @@ static int __hidpp_send_report(struct hid_device *hdev,
}
/*
- * hidpp_send_message_sync() returns 0 in case of success, and something else
- * in case of a failure.
- * - If ' something else' is positive, that means that an error has been raised
- * by the protocol itself.
- * - If ' something else' is negative, that means that we had a classic error
- * (-ENOMEM, -EPIPE, etc...)
+ * Effectively send the message to the device, waiting for its answer.
+ *
+ * Must be called with hidpp->send_mutex locked
+ *
+ * Same return protocol than hidpp_send_message_sync():
+ * - success on 0
+ * - negative error means transport error
+ * - positive value means protocol error
*/
-static int hidpp_send_message_sync(struct hidpp_device *hidpp,
+static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
struct hidpp_report *message,
struct hidpp_report *response)
{
- int ret = -1;
- int max_retries = 3;
+ int ret;
- mutex_lock(&hidpp->send_mutex);
+ __must_hold(&hidpp->send_mutex);
hidpp->send_receive_buf = response;
hidpp->answer_available = false;
@@ -300,47 +302,79 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
*/
*response = *message;
- for (; max_retries != 0 && ret; max_retries--) {
- ret = __hidpp_send_report(hidpp->hid_dev, message);
+ ret = __hidpp_send_report(hidpp->hid_dev, message);
+ if (ret) {
+ dbg_hid("__hidpp_send_report returned err: %d\n", ret);
+ memset(response, 0, sizeof(struct hidpp_report));
+ return ret;
+ }
- if (ret) {
- dbg_hid("__hidpp_send_report returned err: %d\n", ret);
- memset(response, 0, sizeof(struct hidpp_report));
- break;
- }
+ if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
+ 5*HZ)) {
+ dbg_hid("%s:timeout waiting for response\n", __func__);
+ memset(response, 0, sizeof(struct hidpp_report));
+ return -ETIMEDOUT;
+ }
- if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
- 5*HZ)) {
- dbg_hid("%s:timeout waiting for response\n", __func__);
- memset(response, 0, sizeof(struct hidpp_report));
- ret = -ETIMEDOUT;
- break;
- }
+ if (response->report_id == REPORT_ID_HIDPP_SHORT &&
+ response->rap.sub_id == HIDPP_ERROR) {
+ ret = response->rap.params[1];
+ dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
+ return ret;
+ }
+
+ if ((response->report_id == REPORT_ID_HIDPP_LONG ||
+ response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
+ response->fap.feature_index == HIDPP20_ERROR) {
+ ret = response->fap.params[1];
+ dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * hidpp_send_message_sync() returns 0 in case of success, and something else
+ * in case of a failure.
+ *
+ * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
+ * value.
+ */
+static int hidpp_send_message_sync(struct hidpp_device *hidpp,
+ struct hidpp_report *message,
+ struct hidpp_report *response)
+{
+ int ret;
+ int max_retries = 3;
+
+ mutex_lock(&hidpp->send_mutex);
+
+ do {
+ ret = __do_hidpp_send_message_sync(hidpp, message, response);
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
- response->rap.sub_id == HIDPP_ERROR) {
- ret = response->rap.params[1];
- dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
+ ret != HIDPP_ERROR_BUSY)
break;
- }
-
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
- response->fap.feature_index == HIDPP20_ERROR) {
- ret = response->fap.params[1];
- if (ret != HIDPP20_ERROR_BUSY) {
- dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
- break;
- }
- dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
- }
- }
+ ret != HIDPP20_ERROR_BUSY)
+ break;
+
+ dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret);
+ } while (--max_retries);
mutex_unlock(&hidpp->send_mutex);
return ret;
}
+/*
+ * hidpp_send_fap_command_sync() returns 0 in case of success, and something else
+ * in case of a failure.
+ *
+ * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
+ * value.
+ */
static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
struct hidpp_report *response)
@@ -373,6 +407,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
return ret;
}
+/*
+ * hidpp_send_rap_command_sync() returns 0 in case of success, and something else
+ * in case of a failure.
+ *
+ * See __do_hidpp_send_message_sync() for a detailed explanation of the returned
+ * value.
+ */
static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
struct hidpp_report *response)
@@ -415,13 +456,6 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
return ret;
}
-static void delayed_work_cb(struct work_struct *work)
-{
- struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
- work);
- hidpp_connect_event(hidpp);
-}
-
static inline bool hidpp_match_answer(struct hidpp_report *question,
struct hidpp_report *answer)
{
@@ -901,7 +935,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
- u8 *feature_index, u8 *feature_type)
+ u8 *feature_index)
{
struct hidpp_report response;
int ret;
@@ -918,7 +952,6 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
return -ENOENT;
*feature_index = response.fap.params[0];
- *feature_type = response.fap.params[1];
return ret;
}
@@ -943,7 +976,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
}
/* the device might not be connected */
- if (ret == HIDPP_ERROR_RESOURCE_ERROR)
+ if (ret == HIDPP_ERROR_RESOURCE_ERROR ||
+ ret == HIDPP_ERROR_UNKNOWN_DEVICE)
return -EIO;
if (ret > 0) {
@@ -964,8 +998,13 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
hidpp->protocol_minor = response.rap.params[1];
print_version:
- hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
- hidpp->protocol_major, hidpp->protocol_minor);
+ if (!hidpp->connected_once) {
+ hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
+ hidpp->connected_once = true;
+ } else
+ hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
+ hidpp->protocol_major, hidpp->protocol_minor);
return 0;
}
@@ -980,13 +1019,11 @@ print_version:
static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
{
struct hidpp_report response;
- u8 feature_type;
u8 feature_index;
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
- &feature_index,
- &feature_type);
+ &feature_index);
if (ret)
return ret;
@@ -1093,7 +1130,6 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
static char *hidpp_get_device_name(struct hidpp_device *hidpp)
{
- u8 feature_type;
u8 feature_index;
u8 __name_length;
char *name;
@@ -1101,7 +1137,7 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
- &feature_index, &feature_type);
+ &feature_index);
if (ret)
return NULL;
@@ -1268,15 +1304,13 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
{
- u8 feature_type;
int ret;
int status, capacity, next_capacity, level;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_BATTERY_LEVEL_STATUS,
- &hidpp->battery.feature_index,
- &feature_type);
+ &hidpp->battery.feature_index);
if (ret)
return ret;
}
@@ -1457,14 +1491,12 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
{
- u8 feature_type;
int ret;
int status, voltage, level, charge_type;
if (hidpp->battery.voltage_feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
- &hidpp->battery.voltage_feature_index,
- &feature_type);
+ &hidpp->battery.voltage_feature_index);
if (ret)
return ret;
}
@@ -1660,7 +1692,6 @@ static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp,
static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
{
- u8 feature_type;
int ret;
u8 state_of_charge;
int status, level;
@@ -1668,8 +1699,7 @@ static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_UNIFIED_BATTERY,
- &hidpp->battery.feature_index,
- &feature_type);
+ &hidpp->battery.feature_index);
if (ret)
return ret;
}
@@ -1800,17 +1830,11 @@ static int hidpp_battery_get_property(struct power_supply *psy,
/* -------------------------------------------------------------------------- */
#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS 0x1d4b
-static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
+static int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index)
{
- u8 feature_type;
- int ret;
-
- ret = hidpp_root_get_feature(hidpp,
- HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
- &hidpp->wireless_feature_index,
- &feature_type);
-
- return ret;
+ return hidpp_root_get_feature(hidpp,
+ HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
+ feature_index);
}
/* -------------------------------------------------------------------------- */
@@ -1921,14 +1945,11 @@ static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
{
- u8 feature_type;
-
if (hidpp->battery.adc_measurement_feature_index == 0xff) {
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
- &hidpp->battery.adc_measurement_feature_index,
- &feature_type);
+ &hidpp->battery.adc_measurement_feature_index);
if (ret)
return ret;
@@ -1983,15 +2004,13 @@ static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
bool enabled, u8 *multiplier)
{
u8 feature_index;
- u8 feature_type;
int ret;
u8 params[1];
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
- &feature_index,
- &feature_type);
+ &feature_index);
if (ret)
return ret;
@@ -2018,12 +2037,11 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
u8 *multiplier)
{
u8 feature_index;
- u8 feature_type;
int ret;
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
- &feature_index, &feature_type);
+ &feature_index);
if (ret)
goto return_default;
@@ -2045,13 +2063,12 @@ static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
bool high_resolution, bool use_hidpp)
{
u8 feature_index;
- u8 feature_type;
int ret;
u8 params[1];
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
- &feature_index, &feature_type);
+ &feature_index);
if (ret)
return ret;
@@ -2080,14 +2097,12 @@ static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
{
struct hidpp_report response;
u8 params[2] = { 1, 1 };
- u8 feature_type;
int ret;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_SOLAR_KEYBOARD,
- &hidpp->battery.solar_feature_index,
- &feature_type);
+ &hidpp->battery.solar_feature_index);
if (ret)
return ret;
}
@@ -2491,7 +2506,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
/* regular effect destroyed */
data->effect_ids[wd->params[0]-1] = -1;
else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
- /* autocenter spring destoyed */
+ /* autocenter spring destroyed */
data->slot_autocenter = 0;
break;
case HIDPP_FF_SET_GLOBAL_GAINS:
@@ -3067,11 +3082,10 @@ static int wtp_get_config(struct hidpp_device *hidpp)
{
struct wtp_data *wd = hidpp->private_data;
struct hidpp_touchpad_raw_info raw_info = {0};
- u8 feature_type;
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
- &wd->mt_feature_index, &feature_type);
+ &wd->mt_feature_index);
if (ret)
/* means that the device is not powered up */
return ret;
@@ -3107,7 +3121,7 @@ static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
return 0;
};
-static int wtp_connect(struct hid_device *hdev, bool connected)
+static int wtp_connect(struct hid_device *hdev)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
struct wtp_data *wd = hidpp->private_data;
@@ -3169,7 +3183,7 @@ static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
#define M560_SUB_ID 0x0a
#define M560_BUTTON_MODE_REGISTER 0x35
-static int m560_send_config_command(struct hid_device *hdev, bool connected)
+static int m560_send_config_command(struct hid_device *hdev)
{
struct hidpp_report response;
struct hidpp_device *hidpp_dev;
@@ -3265,13 +3279,13 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
120);
}
- v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
+ v = sign_extend32(hid_field_extract(hdev, data + 3, 0, 12), 11);
input_report_rel(hidpp->input, REL_X, v);
- v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
+ v = sign_extend32(hid_field_extract(hdev, data + 3, 12, 12), 11);
input_report_rel(hidpp->input, REL_Y, v);
- v = hid_snto32(data[6], 8);
+ v = sign_extend32(data[6], 7);
if (v != 0)
hidpp_scroll_counter_handle_scroll(hidpp->input,
&hidpp->vertical_wheel_counter, v);
@@ -3331,12 +3345,11 @@ static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
struct k400_private_data *k400 = hidpp->private_data;
struct hidpp_touchpad_fw_items items = {};
int ret;
- u8 feature_type;
if (!k400->feature_index) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
- &k400->feature_index, &feature_type);
+ &k400->feature_index);
if (ret)
/* means that the device is not powered up */
return ret;
@@ -3364,7 +3377,7 @@ static int k400_allocate(struct hid_device *hdev)
return 0;
};
-static int k400_connect(struct hid_device *hdev, bool connected)
+static int k400_connect(struct hid_device *hdev)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
@@ -3408,14 +3421,13 @@ static int g920_get_config(struct hidpp_device *hidpp,
struct hidpp_ff_private_data *data)
{
struct hidpp_report response;
- u8 feature_type;
int ret;
memset(data, 0, sizeof(*data));
/* Find feature and store for later use */
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
- &data->feature_index, &feature_type);
+ &data->feature_index);
if (ret)
return ret;
@@ -3704,17 +3716,16 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
if (hidpp->protocol_major >= 2) {
u8 feature_index;
- u8 feature_type;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
- &feature_index, &feature_type);
+ &feature_index);
if (!ret) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n");
return 0;
}
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
- &feature_index, &feature_type);
+ &feature_index);
if (!ret) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
@@ -3736,8 +3747,8 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
/* Generic HID++ devices */
/* -------------------------------------------------------------------------- */
-static u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc,
- unsigned int *rsize)
+static const u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc,
+ unsigned int *rsize)
{
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
@@ -3833,6 +3844,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
struct hidpp_report *answer = hidpp->send_receive_buf;
struct hidpp_report *report = (struct hidpp_report *)data;
int ret;
+ int last_online;
/*
* If the mutex is locked then we have a pending answer from a
@@ -3859,8 +3871,6 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
}
if (unlikely(hidpp_report_is_connect_event(hidpp, report))) {
- atomic_set(&hidpp->connected,
- !(report->rap.params[0] & (1 << 6)));
if (schedule_work(&hidpp->work) == 0)
dbg_hid("%s: connect event already queued\n", __func__);
return 1;
@@ -3876,6 +3886,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
"See: https://gitlab.freedesktop.org/jwrdegoede/logitech-27mhz-keyboard-encryption-setup/\n");
}
+ last_online = hidpp->battery.online;
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
ret = hidpp20_battery_event_1000(hidpp, data, size);
if (ret != 0)
@@ -3900,6 +3911,11 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
return ret;
}
+ if (hidpp->quirks & HIDPP_QUIRK_RESET_HI_RES_SCROLL) {
+ if (last_online == 0 && hidpp->battery.online == 1)
+ schedule_work(&hidpp->reset_hi_res_work);
+ }
+
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
ret = hidpp10_wheel_raw_event(hidpp, data, size);
if (ret != 0)
@@ -4096,24 +4112,22 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
return ret;
}
-static void hidpp_overwrite_name(struct hid_device *hdev)
+/* Get name + serial for USB and Bluetooth HID++ devices */
+static void hidpp_non_unifying_init(struct hidpp_device *hidpp)
{
- struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ struct hid_device *hdev = hidpp->hid_dev;
char *name;
- if (hidpp->protocol_major < 2)
- return;
+ /* Bluetooth devices already have their serialnr set */
+ if (hid_is_usb(hdev))
+ hidpp_serial_init(hidpp);
name = hidpp_get_device_name(hidpp);
-
- if (!name) {
- hid_err(hdev, "unable to retrieve the name of the device");
- } else {
+ if (name) {
dbg_hid("HID++: Got name: %s\n", name);
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+ kfree(name);
}
-
- kfree(name);
}
static int hidpp_input_open(struct input_dev *dev)
@@ -4154,15 +4168,18 @@ static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
return input_dev;
}
-static void hidpp_connect_event(struct hidpp_device *hidpp)
+static void hidpp_connect_event(struct work_struct *work)
{
+ struct hidpp_device *hidpp = container_of(work, struct hidpp_device, work);
struct hid_device *hdev = hidpp->hid_dev;
- int ret = 0;
- bool connected = atomic_read(&hidpp->connected);
struct input_dev *input;
char *name, *devm_name;
+ int ret;
- if (!connected) {
+ /* Get device version to check if it is connected */
+ ret = hidpp_root_get_protocol_version(hidpp);
+ if (ret) {
+ hid_dbg(hidpp->hid_dev, "Disconnected\n");
if (hidpp->battery.ps) {
hidpp->battery.online = false;
hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN;
@@ -4173,15 +4190,15 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
- ret = wtp_connect(hdev, connected);
+ ret = wtp_connect(hdev);
if (ret)
return;
} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
- ret = m560_send_config_command(hdev, connected);
+ ret = m560_send_config_command(hdev);
if (ret)
return;
} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
- ret = k400_connect(hdev, connected);
+ ret = k400_connect(hdev);
if (ret)
return;
}
@@ -4204,14 +4221,11 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
return;
}
- /* the device is already connected, we can ask for its name and
- * protocol */
- if (!hidpp->protocol_major) {
- ret = hidpp_root_get_protocol_version(hidpp);
- if (ret) {
- hid_err(hdev, "Can not get the protocol version.\n");
- return;
- }
+ if (hidpp->protocol_major >= 2) {
+ u8 feature_index;
+
+ if (!hidpp_get_wireless_feature_index(hidpp, &feature_index))
+ hidpp->wireless_feature_index = feature_index;
}
if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) {
@@ -4275,6 +4289,13 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
hidpp->delayed_input = input;
}
+static void hidpp_reset_hi_res_handler(struct work_struct *work)
+{
+ struct hidpp_device *hidpp = container_of(work, struct hidpp_device, reset_hi_res_work);
+
+ hi_res_scroll_enable(hidpp);
+}
+
static DEVICE_ATTR(builtin_power_supply, 0000, NULL, NULL);
static struct attribute *sysfs_attrs[] = {
@@ -4356,10 +4377,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
int ret;
- bool connected;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
- struct hidpp_ff_private_data data;
- bool will_restart = false;
/* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
@@ -4388,9 +4406,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
- if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
- hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
-
if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE &&
hidpp_application_equals(hdev, HID_GD_MOUSE))
hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS |
@@ -4410,11 +4425,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
- if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT ||
- hidpp->quirks & HIDPP_QUIRK_UNIFYING)
- will_restart = true;
-
- INIT_WORK(&hidpp->work, delayed_work_cb);
+ INIT_WORK(&hidpp->work, hidpp_connect_event);
+ INIT_WORK(&hidpp->reset_hi_res_work, hidpp_reset_hi_res_handler);
mutex_init(&hidpp->send_mutex);
init_waitqueue_head(&hidpp->wait);
@@ -4425,10 +4437,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hdev->name);
/*
- * Plain USB connections need to actually call start and open
- * on the transport driver to allow incoming data.
+ * First call hid_hw_start(hdev, 0) to allow IO without connecting any
+ * hid subdrivers (hid-input, hidraw). This allows retrieving the dev's
+ * name and serial number and store these in hdev->name and hdev->uniq,
+ * before the hid-input and hidraw drivers expose these to userspace.
*/
- ret = hid_hw_start(hdev, will_restart ? 0 : connect_mask);
+ ret = hid_hw_start(hdev, 0);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto hid_hw_start_fail;
@@ -4444,69 +4458,46 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
/* Allow incoming packets */
hid_device_io_start(hdev);
- if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
+ /* Get name + serial, store in hdev->name + hdev->uniq */
+ if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
hidpp_unifying_init(hidpp);
- else if (hid_is_usb(hidpp->hid_dev))
- hidpp_serial_init(hidpp);
-
- connected = hidpp_root_get_protocol_version(hidpp) == 0;
- atomic_set(&hidpp->connected, connected);
- if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) {
- if (!connected) {
- ret = -ENODEV;
- hid_err(hdev, "Device not connected");
- goto hid_hw_init_fail;
- }
-
- hidpp_overwrite_name(hdev);
- }
+ else
+ hidpp_non_unifying_init(hidpp);
- if (connected && hidpp->protocol_major >= 2) {
- ret = hidpp_set_wireless_feature_index(hidpp);
- if (ret == -ENOENT)
- hidpp->wireless_feature_index = 0;
- else if (ret)
- goto hid_hw_init_fail;
- ret = 0;
- }
+ if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
+ connect_mask &= ~HID_CONNECT_HIDINPUT;
- if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
- ret = wtp_get_config(hidpp);
- if (ret)
- goto hid_hw_init_fail;
- } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
- ret = g920_get_config(hidpp, &data);
- if (ret)
- goto hid_hw_init_fail;
+ /* Now export the actual inputs and hidraw nodes to the world */
+ hid_device_io_stop(hdev);
+ ret = hid_connect(hdev, connect_mask);
+ if (ret) {
+ hid_err(hdev, "%s:hid_connect returned error %d\n", __func__, ret);
+ goto hid_hw_init_fail;
}
- hidpp_connect_event(hidpp);
-
- if (will_restart) {
- /* Reset the HID node state */
- hid_device_io_stop(hdev);
- hid_hw_close(hdev);
- hid_hw_stop(hdev);
+ /* Check for connected devices now that incoming packets will not be disabled again */
+ hid_device_io_start(hdev);
+ schedule_work(&hidpp->work);
+ flush_work(&hidpp->work);
- if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
- connect_mask &= ~HID_CONNECT_HIDINPUT;
+ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+ struct hidpp_ff_private_data data;
- /* Now export the actual inputs and hidraw nodes to the world */
- ret = hid_hw_start(hdev, connect_mask);
- if (ret) {
- hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
- goto hid_hw_start_fail;
- }
- }
+ ret = g920_get_config(hidpp, &data);
+ if (!ret)
+ ret = hidpp_ff_init(hidpp, &data);
- if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
- ret = hidpp_ff_init(hidpp, &data);
if (ret)
hid_warn(hidpp->hid_dev,
"Unable to initialize force feedback support, errno %d\n",
ret);
}
+ /*
+ * This relies on logi_dj_ll_close() being a no-op so that DJ connection
+ * events will still be received.
+ */
+ hid_hw_close(hdev);
return ret;
hid_hw_init_fail:
@@ -4531,6 +4522,7 @@ static void hidpp_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
cancel_work_sync(&hidpp->work);
+ cancel_work_sync(&hidpp->reset_hi_res_work);
mutex_destroy(&hidpp->send_mutex);
}
@@ -4578,6 +4570,9 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Keyboard MX5500 (Bluetooth-receiver in HID proxy mode) */
LDJ_DEVICE(0xb30b),
.driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
+ { /* Logitech G502 Lightspeed Wireless Gaming Mouse */
+ LDJ_DEVICE(0x407f),
+ .driver_data = HIDPP_QUIRK_RESET_HI_RES_SCROLL },
{ LDJ_DEVICE(HID_ANY_ID) },
@@ -4608,6 +4603,12 @@ static const struct hid_device_id hidpp_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) },
{ /* Logitech G903 Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC086) },
+ { /* Logitech G Pro Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
+ { /* MX Vertical over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) },
+ { /* Logitech G703 Hero Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
{ /* Logitech G903 Hero Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
{ /* Logitech G915 TKL Keyboard over USB */
@@ -4618,8 +4619,12 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Logitech G923 Wheel (Xbox version) over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
- { /* Logitech G Pro Gaming Mouse over USB */
- HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
+ { /* Logitech G Pro X Superlight Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) },
+ { /* Logitech G Pro X Superlight 2 Gaming Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC09b) },
+ { /* Logitech G PRO 2 LIGHTSPEED Wireless Mouse over USB */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xc09a) },
{ /* G935 Gaming Headset */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
@@ -4640,15 +4645,25 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb008) },
{ /* MX Master mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) },
+ { /* M720 Triathlon mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb015) },
+ { /* MX Master 2S mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb019) },
{ /* MX Ergo trackball over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
+ { /* MX Vertical mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
{ /* Signature M650 over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
{ /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
+ { /* MX Anywhere 3 mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) },
{ /* MX Master 3S mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
+ { /* MX Anywhere 3SB mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
{}
};
diff --git a/drivers/hid/hid-macally.c b/drivers/hid/hid-macally.c
index aea46e522008..fe7576458afa 100644
--- a/drivers/hid/hid-macally.c
+++ b/drivers/hid/hid-macally.c
@@ -18,8 +18,8 @@ MODULE_LICENSE("GPL");
* The Macally ikey keyboard says that its logical and usage maximums are both
* 101, but the power key is 102 and the equals key is 103
*/
-static __u8 *macally_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *macally_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
if (*rsize >= 60 && rdesc[53] == 0x65 && rdesc[59] == 0x65) {
hid_info(hdev,
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index c9c968d4b36a..7d4a25c6de0e 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
@@ -59,7 +60,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
#define MOUSE_REPORT_ID 0x29
#define MOUSE2_REPORT_ID 0x12
#define DOUBLE_REPORT_ID 0xf7
-#define USB_BATTERY_TIMEOUT_MS 60000
+#define USB_BATTERY_TIMEOUT_SEC 60
/* These definitions are not precise, but they're close enough. (Bits
* 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
@@ -120,6 +121,9 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
* @scroll_jiffies: Time of last scroll motion.
* @touches: Most recent data for a touch, indexed by tracking ID.
* @tracking_ids: Mapping of current touch input data to @touches.
+ * @hdev: Pointer to the underlying HID device.
+ * @work: Workqueue to handle initialization retry for quirky devices.
+ * @battery_timer: Timer for obtaining battery level information.
*/
struct magicmouse_sc {
struct input_dev *input;
@@ -214,7 +218,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
int pressure = 0;
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
- input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) {
id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
x = (tdata[1] << 28 | tdata[0] << 20) >> 20;
y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);
@@ -224,7 +229,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
touch_minor = tdata[4];
state = tdata[7] & TOUCH_STATE_MASK;
down = state != TOUCH_STATE_NONE;
- } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
id = tdata[8] & 0xf;
x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
@@ -256,8 +263,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
/* If requested, emulate a scroll wheel by detecting small
* vertical touch motions.
*/
- if (emulate_scroll_wheel && (input->id.product !=
- USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
+ if (emulate_scroll_wheel &&
+ input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
+ input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
unsigned long now = jiffies;
int step_x = msc->touches[id].scroll_x - x;
int step_y = msc->touches[id].scroll_y - y;
@@ -356,15 +364,20 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
- if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
+ if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)
input_report_abs(input, ABS_MT_PRESSURE, pressure);
if (report_undeciphered) {
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
- input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)
input_event(input, EV_MSC, MSC_RAW, tdata[7]);
else if (input->id.product !=
- USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
+ input->id.product !=
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)
input_event(input, EV_MSC, MSC_RAW, tdata[8]);
}
}
@@ -486,11 +499,14 @@ static int magicmouse_raw_event(struct hid_device *hdev,
}
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
- input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) {
magicmouse_emit_buttons(msc, clicks & 3);
input_report_rel(input, REL_X, x);
input_report_rel(input, REL_Y, y);
- } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
input_mt_sync_frame(input);
input_report_key(input, BTN_MOUSE, clicks & 1);
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
@@ -506,7 +522,8 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
- if (msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
+ if ((msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) &&
field->report->id == MOUSE2_REPORT_ID) {
/*
* magic_mouse_raw_event has done all the work. Skip hidinput.
@@ -527,7 +544,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
__set_bit(EV_KEY, input->evbit);
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
- input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) {
__set_bit(BTN_LEFT, input->keybit);
__set_bit(BTN_RIGHT, input->keybit);
if (emulate_3button)
@@ -542,7 +560,9 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
__set_bit(REL_WHEEL_HI_RES, input->relbit);
__set_bit(REL_HWHEEL_HI_RES, input->relbit);
}
- } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
/* If the trackpad has been connected to a Mac, the name is
* automatically personalized, e.g., "José Expósito's Trackpad".
* When connected through Bluetooth, the personalized name is
@@ -553,9 +573,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;
}
@@ -607,7 +630,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
* inverse of the reported Y.
*/
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
- input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC) {
input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,
MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
@@ -618,7 +642,9 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
MOUSE_RES_X);
input_abs_set_res(input, ABS_MT_POSITION_Y,
MOUSE_RES_Y);
- } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0);
input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0);
input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
@@ -657,7 +683,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
input_set_events_per_packet(input, 60);
if (report_undeciphered &&
- input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+ input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
+ input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
__set_bit(EV_MSC, input->evbit);
__set_bit(MSC_RAW, input->mscbit);
}
@@ -682,7 +709,9 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
/* Magic Trackpad does not give relative data after switching to MT */
if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
- hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
+ hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ hi->input->id.product ==
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) &&
field->flags & HID_MAIN_ITEM_RELATIVE)
return -1;
@@ -718,18 +747,25 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
int ret;
int feature_size;
- if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
- if (hdev->vendor == BT_VENDOR_ID_APPLE) {
+ switch (hdev->product) {
+ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2:
+ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC:
+ switch (hdev->vendor) {
+ case BT_VENDOR_ID_APPLE:
feature_size = sizeof(feature_mt_trackpad2_bt);
feature = feature_mt_trackpad2_bt;
- } else { /* USB_VENDOR_ID_APPLE */
+ break;
+ default: /* USB_VENDOR_ID_APPLE */
feature_size = sizeof(feature_mt_trackpad2_usb);
feature = feature_mt_trackpad2_usb;
}
- } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ break;
+ case USB_DEVICE_ID_APPLE_MAGICMOUSE2:
+ case USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC:
feature_size = sizeof(feature_mt_mouse2);
feature = feature_mt_mouse2;
- } else {
+ break;
+ default:
feature_size = sizeof(feature_mt);
feature = feature_mt;
}
@@ -755,15 +791,31 @@ static void magicmouse_enable_mt_work(struct work_struct *work)
hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
}
+static bool is_usb_magicmouse2(__u32 vendor, __u32 product)
+{
+ if (vendor != USB_VENDOR_ID_APPLE)
+ return false;
+ return product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC;
+}
+
+static bool is_usb_magictrackpad2(__u32 vendor, __u32 product)
+{
+ if (vendor != USB_VENDOR_ID_APPLE)
+ return false;
+ return product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
+ product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
+}
+
static int magicmouse_fetch_battery(struct hid_device *hdev)
{
#ifdef CONFIG_HID_BATTERY_STRENGTH
struct hid_report_enum *report_enum;
struct hid_report *report;
- if (!hdev->battery || hdev->vendor != USB_VENDOR_ID_APPLE ||
- (hdev->product != USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
- hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2))
+ if (!hdev->battery ||
+ (!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
+ !is_usb_magictrackpad2(hdev->vendor, hdev->product)))
return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type];
@@ -784,12 +836,12 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
static void magicmouse_battery_timer_tick(struct timer_list *t)
{
- struct magicmouse_sc *msc = from_timer(msc, t, battery_timer);
+ struct magicmouse_sc *msc = timer_container_of(msc, t, battery_timer);
struct hid_device *hdev = msc->hdev;
if (magicmouse_fetch_battery(hdev) == 0) {
mod_timer(&msc->battery_timer,
- jiffies + msecs_to_jiffies(USB_BATTERY_TIMEOUT_MS));
+ jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
}
}
@@ -825,14 +877,17 @@ static int magicmouse_probe(struct hid_device *hdev,
return ret;
}
- timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
- mod_timer(&msc->battery_timer,
- jiffies + msecs_to_jiffies(USB_BATTERY_TIMEOUT_MS));
- magicmouse_fetch_battery(hdev);
+ if (is_usb_magicmouse2(id->vendor, id->product) ||
+ is_usb_magictrackpad2(id->vendor, id->product)) {
+ timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
+ mod_timer(&msc->battery_timer,
+ jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+ magicmouse_fetch_battery(hdev);
+ }
- if (id->vendor == USB_VENDOR_ID_APPLE &&
- (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
- (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 && hdev->type != HID_TYPE_USBMOUSE)))
+ if (is_usb_magicmouse2(id->vendor, id->product) ||
+ (is_usb_magictrackpad2(id->vendor, id->product) &&
+ hdev->type != HID_TYPE_USBMOUSE))
return 0;
if (!msc->input) {
@@ -841,20 +896,27 @@ static int magicmouse_probe(struct hid_device *hdev,
goto err_stop_hw;
}
- if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
- report = hid_register_report(hdev, HID_INPUT_REPORT,
- MOUSE_REPORT_ID, 0);
- else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
- report = hid_register_report(hdev, HID_INPUT_REPORT,
- MOUSE2_REPORT_ID, 0);
- else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
- if (id->vendor == BT_VENDOR_ID_APPLE)
+ switch (id->product) {
+ case USB_DEVICE_ID_APPLE_MAGICMOUSE:
+ report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE_REPORT_ID, 0);
+ break;
+ case USB_DEVICE_ID_APPLE_MAGICMOUSE2:
+ case USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC:
+ report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE2_REPORT_ID, 0);
+ break;
+ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2:
+ case USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC:
+ switch (id->vendor) {
+ case BT_VENDOR_ID_APPLE:
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD2_BT_REPORT_ID, 0);
- else /* USB_VENDOR_ID_APPLE */
+ break;
+ default:
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD2_USB_REPORT_ID, 0);
- } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+ }
+ break;
+ default: /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
report = hid_register_report(hdev, HID_INPUT_REPORT,
TRACKPAD_REPORT_ID, 0);
report = hid_register_report(hdev, HID_INPUT_REPORT,
@@ -881,13 +943,17 @@ static int magicmouse_probe(struct hid_device *hdev,
hid_err(hdev, "unable to request touch data (%d)\n", ret);
goto err_stop_hw;
}
- if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+ if (ret == -EIO && (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
+ id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC)) {
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
}
return 0;
err_stop_hw:
- del_timer_sync(&msc->battery_timer);
+ if (is_usb_magicmouse2(id->vendor, id->product) ||
+ is_usb_magictrackpad2(id->vendor, id->product))
+ timer_delete_sync(&msc->battery_timer);
+
hid_hw_stop(hdev);
return ret;
}
@@ -898,14 +964,16 @@ static void magicmouse_remove(struct hid_device *hdev)
if (msc) {
cancel_delayed_work_sync(&msc->work);
- del_timer_sync(&msc->battery_timer);
+ if (is_usb_magicmouse2(hdev->vendor, hdev->product) ||
+ is_usb_magictrackpad2(hdev->vendor, hdev->product))
+ timer_delete_sync(&msc->battery_timer);
}
hid_hw_stop(hdev);
}
-static __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
/*
* Change the usage from:
@@ -915,9 +983,8 @@ static __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
* 0x09, 0x02, // Usage (Mouse) 2
*/
- if (hdev->vendor == USB_VENDOR_ID_APPLE &&
- (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
- hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
+ if ((is_usb_magicmouse2(hdev->vendor, hdev->product) ||
+ is_usb_magictrackpad2(hdev->vendor, hdev->product)) &&
*rsize == 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) {
hid_info(hdev,
"fixing up magicmouse battery report descriptor\n");
@@ -942,12 +1009,20 @@ static const struct hid_device_id magic_mice[] = {
USB_DEVICE_ID_APPLE_MAGICMOUSE2), .driver_data = 0 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICMOUSE2), .driver_data = 0 },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC), .driver_data = 0 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_MAGICMOUSE2_USBC), .driver_data = 0 },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 },
{ }
};
MODULE_DEVICE_TABLE(hid, magic_mice);
@@ -965,4 +1040,5 @@ static struct hid_driver magicmouse_driver = {
};
module_hid_driver(magicmouse_driver);
+MODULE_DESCRIPTION("Apple \"Magic\" Wireless Mouse driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-maltron.c b/drivers/hid/hid-maltron.c
index dcd6db6a646e..f0aad1ba2e6d 100644
--- a/drivers/hid/hid-maltron.c
+++ b/drivers/hid/hid-maltron.c
@@ -22,7 +22,7 @@
#include "hid-ids.h"
/* The original buggy USB descriptor */
-static u8 maltron_rdesc_o[] = {
+static const u8 maltron_rdesc_o[] = {
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
0x09, 0x80, /* Usage (Sys Control) */
0xA1, 0x01, /* Collection (Application) */
@@ -79,7 +79,7 @@ static u8 maltron_rdesc_o[] = {
};
/* The patched descriptor, allowing media key events to be accepted as valid */
-static u8 maltron_rdesc[] = {
+static const u8 maltron_rdesc[] = {
0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
0x09, 0x80, /* Usage (Sys Control) */
0xA1, 0x01, /* Collection (Application) */
@@ -137,8 +137,8 @@ static u8 maltron_rdesc[] = {
0xC0 /* End Collection */
};
-static __u8 *maltron_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *maltron_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
if (*rsize == sizeof(maltron_rdesc_o) &&
!memcmp(maltron_rdesc_o, rdesc, sizeof(maltron_rdesc_o))) {
@@ -162,4 +162,5 @@ static struct hid_driver maltron_driver = {
};
module_hid_driver(maltron_driver);
+MODULE_DESCRIPTION("HID driver for Maltron L90");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-mcp2200.c b/drivers/hid/hid-mcp2200.c
new file mode 100644
index 000000000000..dafdd5b4a079
--- /dev/null
+++ b/drivers/hid/hid-mcp2200.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MCP2200 - Microchip USB to GPIO bridge
+ *
+ * Copyright (c) 2023, Johannes Roith <johannes@gnu-linux.rocks>
+ *
+ * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22228A.pdf
+ * App Note for HID: https://ww1.microchip.com/downloads/en/DeviceDoc/93066A.pdf
+ */
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/hid.h>
+#include <linux/hidraw.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include "hid-ids.h"
+
+/* Commands codes in a raw output report */
+#define SET_CLEAR_OUTPUTS 0x08
+#define CONFIGURE 0x10
+#define READ_EE 0x20
+#define WRITE_EE 0x40
+#define READ_ALL 0x80
+
+/* MCP GPIO direction encoding */
+enum MCP_IO_DIR {
+ MCP2200_DIR_OUT = 0x00,
+ MCP2200_DIR_IN = 0x01,
+};
+
+/* Altternative pin assignments */
+#define TXLED 2
+#define RXLED 3
+#define USBCFG 6
+#define SSPND 7
+#define MCP_NGPIO 8
+
+/* CMD to set or clear a GPIO output */
+struct mcp_set_clear_outputs {
+ u8 cmd;
+ u8 dummys1[10];
+ u8 set_bmap;
+ u8 clear_bmap;
+ u8 dummys2[3];
+} __packed;
+
+/* CMD to configure the IOs */
+struct mcp_configure {
+ u8 cmd;
+ u8 dummys1[3];
+ u8 io_bmap;
+ u8 config_alt_pins;
+ u8 io_default_val_bmap;
+ u8 config_alt_options;
+ u8 baud_h;
+ u8 baud_l;
+ u8 dummys2[6];
+} __packed;
+
+/* CMD to read all parameters */
+struct mcp_read_all {
+ u8 cmd;
+ u8 dummys[15];
+} __packed;
+
+/* Response to the read all cmd */
+struct mcp_read_all_resp {
+ u8 cmd;
+ u8 eep_addr;
+ u8 dummy;
+ u8 eep_val;
+ u8 io_bmap;
+ u8 config_alt_pins;
+ u8 io_default_val_bmap;
+ u8 config_alt_options;
+ u8 baud_h;
+ u8 baud_l;
+ u8 io_port_val_bmap;
+ u8 dummys[5];
+} __packed;
+
+struct mcp2200 {
+ struct hid_device *hdev;
+ struct mutex lock;
+ struct completion wait_in_report;
+ u8 gpio_dir;
+ u8 gpio_val;
+ u8 gpio_inval;
+ u8 baud_h;
+ u8 baud_l;
+ u8 config_alt_pins;
+ u8 gpio_reset_val;
+ u8 config_alt_options;
+ int status;
+ struct gpio_chip gc;
+ u8 hid_report[16];
+};
+
+/* this executes the READ_ALL cmd */
+static int mcp_cmd_read_all(struct mcp2200 *mcp)
+{
+ struct mcp_read_all *read_all;
+ int len, t;
+
+ reinit_completion(&mcp->wait_in_report);
+
+ mutex_lock(&mcp->lock);
+
+ read_all = (struct mcp_read_all *) mcp->hid_report;
+ read_all->cmd = READ_ALL;
+ len = hid_hw_output_report(mcp->hdev, (u8 *) read_all,
+ sizeof(struct mcp_read_all));
+
+ mutex_unlock(&mcp->lock);
+
+ if (len != sizeof(struct mcp_read_all))
+ return -EINVAL;
+
+ t = wait_for_completion_timeout(&mcp->wait_in_report,
+ msecs_to_jiffies(4000));
+ if (!t)
+ return -ETIMEDOUT;
+
+ /* return status, negative value if wrong response was received */
+ return mcp->status;
+}
+
+static int mcp_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ struct mcp2200 *mcp = gpiochip_get_data(gc);
+ u8 value;
+ int status;
+ struct mcp_set_clear_outputs *cmd;
+
+ mutex_lock(&mcp->lock);
+ cmd = (struct mcp_set_clear_outputs *) mcp->hid_report;
+
+ value = mcp->gpio_val & ~*mask;
+ value |= (*mask & *bits);
+
+ cmd->cmd = SET_CLEAR_OUTPUTS;
+ cmd->set_bmap = value;
+ cmd->clear_bmap = ~(value);
+
+ status = hid_hw_output_report(mcp->hdev, (u8 *) cmd,
+ sizeof(struct mcp_set_clear_outputs));
+
+ if (status == sizeof(struct mcp_set_clear_outputs))
+ mcp->gpio_val = value;
+ else
+ status = -EIO;
+
+ mutex_unlock(&mcp->lock);
+
+ return status;
+}
+
+static int mcp_set(struct gpio_chip *gc, unsigned int gpio_nr, int value)
+{
+ unsigned long mask = 1 << gpio_nr;
+ unsigned long bmap_value = value << gpio_nr;
+
+ return mcp_set_multiple(gc, &mask, &bmap_value);
+}
+
+static int mcp_get_multiple(struct gpio_chip *gc, unsigned long *mask,
+ unsigned long *bits)
+{
+ u32 val;
+ struct mcp2200 *mcp = gpiochip_get_data(gc);
+ int status;
+
+ status = mcp_cmd_read_all(mcp);
+ if (status)
+ return status;
+
+ val = mcp->gpio_inval;
+ *bits = (val & *mask);
+ return 0;
+}
+
+static int mcp_get(struct gpio_chip *gc, unsigned int gpio_nr)
+{
+ unsigned long mask = 0, bits = 0;
+
+ mask = (1 << gpio_nr);
+ mcp_get_multiple(gc, &mask, &bits);
+ return bits > 0;
+}
+
+static int mcp_get_direction(struct gpio_chip *gc, unsigned int gpio_nr)
+{
+ struct mcp2200 *mcp = gpiochip_get_data(gc);
+
+ return (mcp->gpio_dir & (MCP2200_DIR_IN << gpio_nr))
+ ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int mcp_set_direction(struct gpio_chip *gc, unsigned int gpio_nr,
+ enum MCP_IO_DIR io_direction)
+{
+ struct mcp2200 *mcp = gpiochip_get_data(gc);
+ struct mcp_configure *conf;
+ int status;
+ /* after the configure cmd we will need to set the outputs again */
+ unsigned long mask = ~(mcp->gpio_dir); /* only set outputs */
+ unsigned long bits = mcp->gpio_val;
+ /* Offsets of alternative pins in config_alt_pins, 0 is not used */
+ u8 alt_pin_conf[8] = {SSPND, USBCFG, 0, 0, 0, 0, RXLED, TXLED};
+ u8 config_alt_pins = mcp->config_alt_pins;
+
+ /* Read in the reset baudrate first, we need it later */
+ status = mcp_cmd_read_all(mcp);
+ if (status != 0)
+ return status;
+
+ mutex_lock(&mcp->lock);
+ conf = (struct mcp_configure *) mcp->hid_report;
+
+ /* configure will reset the chip! */
+ conf->cmd = CONFIGURE;
+ conf->io_bmap = (mcp->gpio_dir & ~(1 << gpio_nr))
+ | (io_direction << gpio_nr);
+ /* Don't overwrite the reset parameters */
+ conf->baud_h = mcp->baud_h;
+ conf->baud_l = mcp->baud_l;
+ conf->config_alt_options = mcp->config_alt_options;
+ conf->io_default_val_bmap = mcp->gpio_reset_val;
+ /* Adjust alt. func if necessary */
+ if (alt_pin_conf[gpio_nr])
+ config_alt_pins &= ~(1 << alt_pin_conf[gpio_nr]);
+ conf->config_alt_pins = config_alt_pins;
+
+ status = hid_hw_output_report(mcp->hdev, (u8 *) conf,
+ sizeof(struct mcp_set_clear_outputs));
+
+ if (status == sizeof(struct mcp_set_clear_outputs)) {
+ mcp->gpio_dir = conf->io_bmap;
+ mcp->config_alt_pins = config_alt_pins;
+ } else {
+ mutex_unlock(&mcp->lock);
+ return -EIO;
+ }
+
+ mutex_unlock(&mcp->lock);
+
+ /* Configure CMD will clear all IOs -> rewrite them */
+ mcp_set_multiple(gc, &mask, &bits);
+ return 0;
+}
+
+static int mcp_direction_input(struct gpio_chip *gc, unsigned int gpio_nr)
+{
+ return mcp_set_direction(gc, gpio_nr, MCP2200_DIR_IN);
+}
+
+static int mcp_direction_output(struct gpio_chip *gc, unsigned int gpio_nr,
+ int value)
+{
+ int ret;
+ unsigned long mask, bmap_value;
+
+ mask = 1 << gpio_nr;
+ bmap_value = value << gpio_nr;
+
+ ret = mcp_set_direction(gc, gpio_nr, MCP2200_DIR_OUT);
+ if (ret)
+ return ret;
+
+ return mcp_set_multiple(gc, &mask, &bmap_value);
+}
+
+static const struct gpio_chip template_chip = {
+ .label = "mcp2200",
+ .owner = THIS_MODULE,
+ .get_direction = mcp_get_direction,
+ .direction_input = mcp_direction_input,
+ .direction_output = mcp_direction_output,
+ .set = mcp_set,
+ .set_multiple = mcp_set_multiple,
+ .get = mcp_get,
+ .get_multiple = mcp_get_multiple,
+ .base = -1,
+ .ngpio = MCP_NGPIO,
+ .can_sleep = true,
+};
+
+/*
+ * MCP2200 uses interrupt endpoint for input reports. This function
+ * is called by HID layer when it receives i/p report from mcp2200,
+ * which is actually a response to the previously sent command.
+ */
+static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct mcp2200 *mcp = hid_get_drvdata(hdev);
+ struct mcp_read_all_resp *all_resp;
+
+ switch (data[0]) {
+ case READ_ALL:
+ all_resp = (struct mcp_read_all_resp *) data;
+ mcp->status = 0;
+ mcp->gpio_inval = all_resp->io_port_val_bmap;
+ mcp->baud_h = all_resp->baud_h;
+ mcp->baud_l = all_resp->baud_l;
+ mcp->gpio_reset_val = all_resp->io_default_val_bmap;
+ mcp->config_alt_pins = all_resp->config_alt_pins;
+ mcp->config_alt_options = all_resp->config_alt_options;
+ break;
+ default:
+ mcp->status = -EIO;
+ break;
+ }
+
+ complete(&mcp->wait_in_report);
+ return 0;
+}
+
+static int mcp2200_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+ struct mcp2200 *mcp;
+
+ mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL);
+ if (!mcp)
+ return -ENOMEM;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "can't parse reports\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, 0);
+ if (ret) {
+ hid_err(hdev, "can't start hardware\n");
+ return ret;
+ }
+
+ hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8,
+ hdev->version & 0xff, hdev->name, hdev->phys);
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "can't open device\n");
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ mutex_init(&mcp->lock);
+ init_completion(&mcp->wait_in_report);
+ hid_set_drvdata(hdev, mcp);
+ mcp->hdev = hdev;
+
+ mcp->gc = template_chip;
+ mcp->gc.parent = &hdev->dev;
+
+ ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp);
+ if (ret < 0) {
+ hid_err(hdev, "Unable to register gpiochip\n");
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mcp2200_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id mcp2200_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2200) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, mcp2200_devices);
+
+static struct hid_driver mcp2200_driver = {
+ .name = "mcp2200",
+ .id_table = mcp2200_devices,
+ .probe = mcp2200_probe,
+ .remove = mcp2200_remove,
+ .raw_event = mcp2200_raw_event,
+};
+
+/* Register with HID core */
+module_hid_driver(mcp2200_driver);
+
+MODULE_AUTHOR("Johannes Roith <johannes@gnu-linux.rocks>");
+MODULE_DESCRIPTION("MCP2200 Microchip HID USB to GPIO bridge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index 72883e0ce757..33603b019f97 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -18,6 +18,7 @@
#include <linux/i2c.h>
#include <linux/gpio/driver.h>
#include <linux/iio/iio.h>
+#include <linux/minmax.h>
#include "hid-ids.h"
/* Commands codes in a raw output report */
@@ -49,11 +50,33 @@ enum {
MCP2221_I2C_MASK_ADDR_NACK = 0x40,
MCP2221_I2C_WRADDRL_SEND = 0x21,
MCP2221_I2C_ADDR_NACK = 0x25,
+ MCP2221_I2C_READ_PARTIAL = 0x54,
MCP2221_I2C_READ_COMPL = 0x55,
MCP2221_ALT_F_NOT_GPIOV = 0xEE,
MCP2221_ALT_F_NOT_GPIOD = 0xEF,
};
+/* MCP SRAM read offsets cmd: MCP2221_GET_SRAM_SETTINGS */
+enum {
+ MCP2221_SRAM_RD_GP0 = 22,
+ MCP2221_SRAM_RD_GP1 = 23,
+ MCP2221_SRAM_RD_GP2 = 24,
+ MCP2221_SRAM_RD_GP3 = 25,
+};
+
+/* MCP SRAM write offsets cmd: MCP2221_SET_SRAM_SETTINGS */
+enum {
+ MCP2221_SRAM_WR_GP_ENA_ALTER = 7,
+ MCP2221_SRAM_WR_GP0 = 8,
+ MCP2221_SRAM_WR_GP1 = 9,
+ MCP2221_SRAM_WR_GP2 = 10,
+ MCP2221_SRAM_WR_GP3 = 11,
+};
+
+#define MCP2221_SRAM_GP_DESIGN_MASK 0x07
+#define MCP2221_SRAM_GP_DIRECTION_MASK 0x08
+#define MCP2221_SRAM_GP_VALUE_MASK 0x10
+
/* MCP GPIO direction encoding */
enum {
MCP2221_DIR_OUT = 0x00,
@@ -187,6 +210,25 @@ static int mcp_cancel_last_cmd(struct mcp2221 *mcp)
return mcp_send_data_req_status(mcp, mcp->txbuf, 8);
}
+/* Check if the last command succeeded or failed and return the result.
+ * If the command did fail, cancel that command which will free the i2c bus.
+ */
+static int mcp_chk_last_cmd_status_free_bus(struct mcp2221 *mcp)
+{
+ int ret;
+
+ ret = mcp_chk_last_cmd_status(mcp);
+ if (ret) {
+ /* The last command was a failure.
+ * Send a cancel which will also free the bus.
+ */
+ usleep_range(980, 1000);
+ mcp_cancel_last_cmd(mcp);
+ }
+
+ return ret;
+}
+
static int mcp_set_i2c_speed(struct mcp2221 *mcp)
{
int ret;
@@ -221,10 +263,7 @@ static int mcp_i2c_write(struct mcp2221 *mcp,
idx = 0;
sent = 0;
- if (msg->len < 60)
- len = msg->len;
- else
- len = 60;
+ len = min(msg->len, 60);
do {
mcp->txbuf[0] = type;
@@ -241,7 +280,7 @@ static int mcp_i2c_write(struct mcp2221 *mcp,
usleep_range(980, 1000);
if (last_status) {
- ret = mcp_chk_last_cmd_status(mcp);
+ ret = mcp_chk_last_cmd_status_free_bus(mcp);
if (ret)
return ret;
}
@@ -251,10 +290,7 @@ static int mcp_i2c_write(struct mcp2221 *mcp,
break;
idx = idx + len;
- if ((msg->len - sent) < 60)
- len = msg->len - sent;
- else
- len = 60;
+ len = min(msg->len - sent, 60);
/*
* Testing shows delay is needed between successive writes
@@ -278,6 +314,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
{
int ret;
u16 total_len;
+ int retries = 0;
mcp->txbuf[0] = type;
if (msg) {
@@ -301,20 +338,31 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
mcp->rxbuf_idx = 0;
do {
+ /* Wait for the data to be read by the device */
+ usleep_range(980, 1000);
+
memset(mcp->txbuf, 0, 4);
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
- if (ret)
- return ret;
-
- ret = mcp_chk_last_cmd_status(mcp);
- if (ret)
- return ret;
-
- usleep_range(980, 1000);
+ if (ret) {
+ if (retries < 5) {
+ /* The data wasn't ready to read.
+ * Wait a bit longer and try again.
+ */
+ usleep_range(90, 100);
+ retries++;
+ } else {
+ return ret;
+ }
+ } else {
+ retries = 0;
+ }
} while (mcp->rxbuf_idx < total_len);
+ usleep_range(980, 1000);
+ ret = mcp_chk_last_cmd_status_free_bus(mcp);
+
return ret;
}
@@ -328,11 +376,6 @@ static int mcp_i2c_xfer(struct i2c_adapter *adapter,
mutex_lock(&mcp->lock);
- /* Setting speed before every transaction is required for mcp2221 */
- ret = mcp_set_i2c_speed(mcp);
- if (ret)
- goto exit;
-
if (num == 1) {
if (msgs->flags & I2C_M_RD) {
ret = mcp_i2c_smbus_read(mcp, msgs, MCP2221_I2C_RD_DATA,
@@ -417,9 +460,7 @@ static int mcp_smbus_write(struct mcp2221 *mcp, u16 addr,
if (last_status) {
usleep_range(980, 1000);
- ret = mcp_chk_last_cmd_status(mcp);
- if (ret)
- return ret;
+ ret = mcp_chk_last_cmd_status_free_bus(mcp);
}
return ret;
@@ -437,10 +478,6 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
mutex_lock(&mcp->lock);
- ret = mcp_set_i2c_speed(mcp);
- if (ret)
- goto exit;
-
switch (size) {
case I2C_SMBUS_QUICK:
@@ -586,6 +623,80 @@ static const struct i2c_algorithm mcp_i2c_algo = {
};
#if IS_REACHABLE(CONFIG_GPIOLIB)
+static int mcp_gpio_read_sram(struct mcp2221 *mcp)
+{
+ int ret;
+
+ memset(mcp->txbuf, 0, 64);
+ mcp->txbuf[0] = MCP2221_GET_SRAM_SETTINGS;
+
+ mutex_lock(&mcp->lock);
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 64);
+ mutex_unlock(&mcp->lock);
+
+ return ret;
+}
+
+/*
+ * If CONFIG_IIO is not enabled, check for the gpio pins
+ * if they are in gpio mode. For the ones which are not
+ * in gpio mode, set them into gpio mode.
+ */
+static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp)
+{
+ int i;
+ int needgpiofix = 0;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_IIO))
+ return 0;
+
+ ret = mcp_gpio_read_sram(mcp);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < MCP_NGPIO; i++) {
+ if ((mcp->mode[i] & MCP2221_SRAM_GP_DESIGN_MASK) != 0x0) {
+ dev_warn(&mcp->hdev->dev,
+ "GPIO %d not in gpio mode\n", i);
+ needgpiofix = 1;
+ }
+ }
+
+ if (!needgpiofix)
+ return 0;
+
+ /*
+ * Set all bytes to 0, so Bit 7 is not set. The chip
+ * only changes content of a register when bit 7 is set.
+ */
+ memset(mcp->txbuf, 0, 64);
+ mcp->txbuf[0] = MCP2221_SET_SRAM_SETTINGS;
+
+ /*
+ * Set bit 7 in MCP2221_SRAM_WR_GP_ENA_ALTER to enable
+ * loading of a new set of gpio settings to GP SRAM
+ */
+ mcp->txbuf[MCP2221_SRAM_WR_GP_ENA_ALTER] = 0x80;
+ for (i = 0; i < MCP_NGPIO; i++) {
+ if ((mcp->mode[i] & MCP2221_SRAM_GP_DESIGN_MASK) == 0x0) {
+ /* write current GPIO mode */
+ mcp->txbuf[MCP2221_SRAM_WR_GP0 + i] = mcp->mode[i];
+ } else {
+ /* pin is not in gpio mode, set it to input mode */
+ mcp->txbuf[MCP2221_SRAM_WR_GP0 + i] = 0x08;
+ dev_warn(&mcp->hdev->dev,
+ "Set GPIO mode for gpio pin %d!\n", i);
+ }
+ }
+
+ mutex_lock(&mcp->lock);
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 64);
+ mutex_unlock(&mcp->lock);
+
+ return ret;
+}
+
static int mcp_gpio_get(struct gpio_chip *gc,
unsigned int offset)
{
@@ -603,10 +714,10 @@ static int mcp_gpio_get(struct gpio_chip *gc,
return ret;
}
-static void mcp_gpio_set(struct gpio_chip *gc,
- unsigned int offset, int value)
+static int mcp_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct mcp2221 *mcp = gpiochip_get_data(gc);
+ int ret;
memset(mcp->txbuf, 0, 18);
mcp->txbuf[0] = MCP2221_GPIO_SET;
@@ -617,8 +728,10 @@ static void mcp_gpio_set(struct gpio_chip *gc,
mcp->txbuf[mcp->gp_idx] = !!value;
mutex_lock(&mcp->lock);
- mcp_send_data_req_status(mcp, mcp->txbuf, 18);
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 18);
mutex_unlock(&mcp->lock);
+
+ return ret;
}
static int mcp_gpio_dir_set(struct mcp2221 *mcp,
@@ -791,7 +904,12 @@ static int mcp2221_raw_event(struct hid_device *hdev,
mcp->status = -EIO;
break;
}
- if (data[2] == MCP2221_I2C_READ_COMPL) {
+ if (data[2] == MCP2221_I2C_READ_COMPL ||
+ data[2] == MCP2221_I2C_READ_PARTIAL) {
+ if (!mcp->rxbuf || mcp->rxbuf_idx < 0 || data[3] > 60) {
+ mcp->status = -EINVAL;
+ break;
+ }
buf = mcp->rxbuf;
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
@@ -922,9 +1040,11 @@ static void mcp2221_hid_unregister(void *ptr)
/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
static void mcp2221_remove(struct hid_device *hdev)
{
+#if IS_REACHABLE(CONFIG_IIO)
struct mcp2221 *mcp = hid_get_drvdata(hdev);
cancel_delayed_work_sync(&mcp->init_work);
+#endif
}
#if IS_REACHABLE(CONFIG_IIO)
@@ -1024,7 +1144,7 @@ static int mcp_iio_channels(struct mcp2221 *mcp)
break;
default:
continue;
- };
+ }
chan->type = IIO_VOLTAGE;
chan->indexed = 1;
@@ -1142,27 +1262,35 @@ static int mcp2221_probe(struct hid_device *hdev,
if (ret)
return ret;
+ hid_device_io_start(hdev);
+
/* Set I2C bus clock diviser */
if (i2c_clk_freq > 400)
i2c_clk_freq = 400;
if (i2c_clk_freq < 50)
i2c_clk_freq = 50;
mcp->cur_i2c_clk_div = (12000000 / (i2c_clk_freq * 1000)) - 3;
+ ret = mcp_set_i2c_speed(mcp);
+ if (ret) {
+ hid_err(hdev, "can't set i2c speed: %d\n", ret);
+ return ret;
+ }
mcp->adapter.owner = THIS_MODULE;
mcp->adapter.class = I2C_CLASS_HWMON;
mcp->adapter.algo = &mcp_i2c_algo;
mcp->adapter.retries = 1;
mcp->adapter.dev.parent = &hdev->dev;
+ ACPI_COMPANION_SET(&mcp->adapter.dev, ACPI_COMPANION(hdev->dev.parent));
snprintf(mcp->adapter.name, sizeof(mcp->adapter.name),
"MCP2221 usb-i2c bridge");
+ i2c_set_adapdata(&mcp->adapter, mcp);
ret = devm_i2c_add_adapter(&hdev->dev, &mcp->adapter);
if (ret) {
hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret);
return ret;
}
- i2c_set_adapdata(&mcp->adapter, mcp);
#if IS_REACHABLE(CONFIG_GPIOLIB)
/* Setup GPIO chip */
@@ -1184,6 +1312,8 @@ static int mcp2221_probe(struct hid_device *hdev,
ret = devm_gpiochip_add_data(&hdev->dev, mcp->gc, mcp);
if (ret)
return ret;
+
+ mcp2221_check_gpio_pinfunc(mcp);
#endif
#if IS_REACHABLE(CONFIG_IIO)
diff --git a/drivers/hid/hid-megaworld.c b/drivers/hid/hid-megaworld.c
index 599657863cb9..0476d7d16e7f 100644
--- a/drivers/hid/hid-megaworld.c
+++ b/drivers/hid/hid-megaworld.c
@@ -122,4 +122,5 @@ static struct hid_driver mwctrl_driver = {
};
module_hid_driver(mwctrl_driver);
+MODULE_DESCRIPTION("Vibration support for Mega World controllers");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c
index 92d7ecd41a78..49a4052a1496 100644
--- a/drivers/hid/hid-mf.c
+++ b/drivers/hid/hid-mf.c
@@ -166,4 +166,5 @@ static struct hid_driver mf_driver = {
};
module_hid_driver(mf_driver);
+MODULE_DESCRIPTION("Force feedback support for Mayflash game controller adapters.");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c
index 9345e2bfd56e..18ac21c0bcb2 100644
--- a/drivers/hid/hid-microsoft.c
+++ b/drivers/hid/hid-microsoft.c
@@ -56,7 +56,7 @@ struct xb1s_ff_report {
__u8 loop_count;
} __packed;
-static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct ms_data *ms = hid_get_drvdata(hdev);
@@ -475,4 +475,5 @@ static struct hid_driver ms_driver = {
};
module_hid_driver(ms_driver);
+MODULE_DESCRIPTION("HID driver for some microsoft \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-monterey.c b/drivers/hid/hid-monterey.c
index c63f9f1e61b8..3089be990afe 100644
--- a/drivers/hid/hid-monterey.c
+++ b/drivers/hid/hid-monterey.c
@@ -18,7 +18,7 @@
#include "hid-ids.h"
-static __u8 *mr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *mr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 31 && rdesc[29] == 0x05 && rdesc[30] == 0x09) {
@@ -62,4 +62,5 @@ static struct hid_driver mr_driver = {
};
module_hid_driver(mr_driver);
+MODULE_DESCRIPTION("HID driver for some monterey \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e31be0cb8b85..179dc316b4b5 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -31,6 +31,7 @@
* [1] https://gitlab.freedesktop.org/libevdev/hid-tools
*/
+#include <linux/bits.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
@@ -48,6 +49,8 @@ MODULE_LICENSE("GPL");
#include "hid-ids.h"
+#include "hid-haptic.h"
+
/* quirks to control the device */
#define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0)
#define MT_QUIRK_SLOT_IS_CONTACTID BIT(1)
@@ -72,6 +75,7 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_FORCE_MULTI_INPUT BIT(20)
#define MT_QUIRK_DISABLE_WAKEUP BIT(21)
#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
+#define MT_QUIRK_APPLE_TOUCHBAR BIT(23)
#define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03
@@ -83,9 +87,15 @@ enum latency_mode {
HID_LATENCY_HIGH = 1,
};
-#define MT_IO_FLAGS_RUNNING 0
-#define MT_IO_FLAGS_ACTIVE_SLOTS 1
-#define MT_IO_FLAGS_PENDING_SLOTS 2
+enum report_mode {
+ TOUCHPAD_REPORT_NONE = 0,
+ TOUCHPAD_REPORT_BUTTONS = BIT(0),
+ TOUCHPAD_REPORT_CONTACTS = BIT(1),
+ TOUCHPAD_REPORT_ALL = TOUCHPAD_REPORT_BUTTONS | TOUCHPAD_REPORT_CONTACTS,
+};
+
+#define MT_IO_SLOTS_MASK GENMASK(7, 0) /* reserve first 8 bits for slot tracking */
+#define MT_IO_FLAGS_RUNNING 32
static const bool mtrue = true; /* default for true */
static const bool mfalse; /* default for false */
@@ -130,7 +140,6 @@ struct mt_application {
* > 1 means hybrid (multitouch) protocol
*/
- __s32 dev_time; /* the scan time provided by the device */
unsigned long jiffies; /* the frame's jiffies */
int timestamp; /* the timestamp to be sent */
int prev_scantime; /* scantime reported previously */
@@ -160,11 +169,17 @@ struct mt_report_data {
struct mt_device {
struct mt_class mtclass; /* our mt device class */
struct timer_list release_timer; /* to release sticky fingers */
+ struct hid_haptic_device *haptic; /* haptic related configuration */
struct hid_device *hdev; /* hid_device we're attached to */
- unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */
+ unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING)
+ * first 8 bits are reserved for keeping the slot
+ * states, this is fine because we only support up
+ * to 250 slots (MT_MAX_MAXCONTACT)
+ */
__u8 inputmode_value; /* InputMode HID feature value */
__u8 maxcontacts;
bool is_buttonpad; /* is this device a button pad? */
+ bool is_haptic_touchpad; /* is this device a haptic touchpad? */
bool serial_maybe; /* need to check for serial protocol */
struct list_head applications;
@@ -213,6 +228,8 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
#define MT_CLS_GOOGLE 0x0111
#define MT_CLS_RAZER_BLADE_STEALTH 0x0112
#define MT_CLS_SMART_TECH 0x0113
+#define MT_CLS_APPLE_TOUCHBAR 0x0114
+#define MT_CLS_SIS 0x0457
#define MT_DEFAULT_MAXCONTACT 10
#define MT_MAX_MAXCONTACT 250
@@ -397,6 +414,17 @@ static const struct mt_class mt_classes[] = {
MT_QUIRK_CONTACT_CNT_ACCURATE |
MT_QUIRK_SEPARATE_APP_REPORT,
},
+ { .name = MT_CLS_APPLE_TOUCHBAR,
+ .quirks = MT_QUIRK_HOVERING |
+ MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE |
+ MT_QUIRK_APPLE_TOUCHBAR,
+ .maxcontacts = 11,
+ },
+ { .name = MT_CLS_SIS,
+ .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+ MT_QUIRK_ALWAYS_VALID |
+ MT_QUIRK_CONTACT_CNT_ACCURATE,
+ },
{ }
};
@@ -512,6 +540,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
mt_get_feature(hdev, field->report);
break;
}
+
+ hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
}
static void set_abs(struct input_dev *input, unsigned int code,
@@ -612,6 +642,7 @@ static struct mt_application *mt_find_application(struct mt_device *td,
static struct mt_report_data *mt_allocate_report_data(struct mt_device *td,
struct hid_report *report)
{
+ struct mt_class *cls = &td->mtclass;
struct mt_report_data *rdata;
struct hid_field *field;
int r, n;
@@ -636,7 +667,11 @@ static struct mt_report_data *mt_allocate_report_data(struct mt_device *td,
if (field->logical == HID_DG_FINGER || td->hdev->group != HID_GROUP_MULTITOUCH_WIN_8) {
for (n = 0; n < field->report_count; n++) {
- if (field->usage[n].hid == HID_DG_CONTACTID) {
+ unsigned int hid = field->usage[n].hid;
+
+ if (hid == HID_DG_CONTACTID ||
+ (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR &&
+ hid == HID_DG_TRANSDUCER_INDEX)) {
rdata->is_mt_collection = true;
break;
}
@@ -808,12 +843,31 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
MT_STORE_FIELD(confidence_state);
return 1;
+ case HID_DG_TOUCH:
+ /*
+ * Legacy devices use TIPSWITCH and not TOUCH.
+ * One special case here is of the Apple Touch Bars.
+ * In these devices, the tip state is contained in
+ * fields with the HID_DG_TOUCH usage.
+ * Let's just ignore this field for other devices.
+ */
+ if (!(cls->quirks & MT_QUIRK_APPLE_TOUCHBAR))
+ return -1;
+ fallthrough;
case HID_DG_TIPSWITCH:
if (field->application != HID_GD_SYSTEM_MULTIAXIS)
input_set_capability(hi->input,
EV_KEY, BTN_TOUCH);
MT_STORE_FIELD(tip_state);
return 1;
+ case HID_DG_TRANSDUCER_INDEX:
+ /*
+ * Contact ID in case of Apple Touch Bars is contained
+ * in fields with HID_DG_TRANSDUCER_INDEX usage.
+ */
+ if (!(cls->quirks & MT_QUIRK_APPLE_TOUCHBAR))
+ return 0;
+ fallthrough;
case HID_DG_CONTACTID:
MT_STORE_FIELD(contactid);
app->touches_by_report++;
@@ -843,6 +897,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
case HID_DG_TIPPRESSURE:
set_abs(hi->input, ABS_MT_PRESSURE, field,
cls->sn_pressure);
+ td->is_haptic_touchpad =
+ hid_haptic_check_pressure_unit(td->haptic,
+ hi, field);
MT_STORE_FIELD(p);
return 1;
case HID_DG_SCANTIME:
@@ -870,10 +927,6 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
case HID_DG_CONTACTMAX:
/* contact max are global to the report */
return -1;
- case HID_DG_TOUCH:
- /* Legacy devices use TIPSWITCH and not TOUCH.
- * Let's just ignore this field. */
- return -1;
}
/* let hid-input decide for the others */
return 0;
@@ -936,6 +989,7 @@ static void mt_release_pending_palms(struct mt_device *td,
for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) {
clear_bit(slotnum, app->pending_palm_slots);
+ clear_bit(slotnum, &td->mt_io_flags);
input_mt_slot(input, slotnum);
input_mt_report_slot_inactive(input);
@@ -967,12 +1021,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
app->num_received = 0;
app->left_button_state = 0;
-
- if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
- set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
- else
- clear_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
- clear_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags);
+ if (td->is_haptic_touchpad)
+ hid_haptic_pressure_reset(td->haptic);
}
static int mt_compute_timestamp(struct mt_application *app, __s32 value)
@@ -1124,6 +1174,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1;
}
+ if (td->is_haptic_touchpad)
+ hid_haptic_pressure_increase(td->haptic, *slot->p);
+
x = hdev->quirks & HID_QUIRK_X_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
*slot->x;
@@ -1147,7 +1200,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
- set_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags);
+ set_bit(slotnum, &td->mt_io_flags);
+ } else {
+ clear_bit(slotnum, &td->mt_io_flags);
}
return 0;
@@ -1282,11 +1337,11 @@ static void mt_touch_report(struct hid_device *hid,
* defect.
*/
if (app->quirks & MT_QUIRK_STICKY_FINGERS) {
- if (test_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags))
+ if (td->mt_io_flags & MT_IO_SLOTS_MASK)
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);
@@ -1301,6 +1356,13 @@ static int mt_touch_input_configured(struct hid_device *hdev,
struct input_dev *input = hi->input;
int ret;
+ /*
+ * HID_DG_CONTACTMAX field is not present on Apple Touch Bars,
+ * but the maximum contact count is greater than the default.
+ */
+ if (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR && cls->maxcontacts)
+ td->maxcontacts = cls->maxcontacts;
+
if (!td->maxcontacts)
td->maxcontacts = MT_DEFAULT_MAXCONTACT;
@@ -1308,9 +1370,19 @@ static int mt_touch_input_configured(struct hid_device *hdev,
if (td->serial_maybe)
mt_post_parse_default_settings(td, app);
+ /*
+ * The application for Apple Touch Bars is HID_DG_TOUCHPAD,
+ * but these devices are direct.
+ */
+ if (cls->quirks & MT_QUIRK_APPLE_TOUCHBAR)
+ app->mt_flags |= INPUT_MT_DIRECT;
+
if (cls->is_indirect)
app->mt_flags |= INPUT_MT_POINTER;
+ if (td->is_haptic_touchpad)
+ app->mt_flags |= INPUT_MT_TOTAL_FORCE;
+
if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
app->mt_flags |= INPUT_MT_DROP_UNUSED;
@@ -1346,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct mt_device *td = hid_get_drvdata(hdev);
struct mt_application *application;
struct mt_report_data *rdata;
+ int ret;
rdata = mt_find_report_data(td, field->report);
if (!rdata) {
@@ -1408,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
if (field->physical == HID_DG_STYLUS)
hi->application = HID_DG_STYLUS;
+ ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
+ max);
+ if (ret != 0)
+ return ret;
+
/* let hid-core decide for the others */
return 0;
}
@@ -1442,6 +1520,38 @@ static int mt_event(struct hid_device *hid, struct hid_field *field,
return 0;
}
+static const __u8 *mt_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *size)
+{
+ if (hdev->vendor == I2C_VENDOR_ID_GOODIX &&
+ (hdev->product == I2C_DEVICE_ID_GOODIX_01E8 ||
+ hdev->product == I2C_DEVICE_ID_GOODIX_01E9)) {
+ if (*size < 608) {
+ dev_info(
+ &hdev->dev,
+ "GT7868Q fixup: report descriptor is only %u bytes, skipping\n",
+ *size);
+ return rdesc;
+ }
+
+ if (rdesc[607] == 0x15) {
+ rdesc[607] = 0x25;
+ dev_info(
+ &hdev->dev,
+ "GT7868Q report descriptor fixup is applied.\n");
+ } else {
+ dev_info(
+ &hdev->dev,
+ "The byte is not expected for fixing the report descriptor. \
+It's possible that the touchpad firmware is not suitable for applying the fix. \
+got: %x\n",
+ rdesc[607]);
+ }
+ }
+
+ return rdesc;
+}
+
static void mt_report(struct hid_device *hid, struct hid_report *report)
{
struct mt_device *td = hid_get_drvdata(hid);
@@ -1463,8 +1573,7 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
struct hid_field *field,
struct hid_usage *usage,
enum latency_mode latency,
- bool surface_switch,
- bool button_switch,
+ enum report_mode report_mode,
bool *inputmode_found)
{
struct mt_device *td = hid_get_drvdata(hdev);
@@ -1519,11 +1628,11 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
return true;
case HID_DG_SURFACESWITCH:
- field->value[index] = surface_switch;
+ field->value[index] = !!(report_mode & TOUCHPAD_REPORT_CONTACTS);
return true;
case HID_DG_BUTTONSWITCH:
- field->value[index] = button_switch;
+ field->value[index] = !!(report_mode & TOUCHPAD_REPORT_BUTTONS);
return true;
}
@@ -1531,7 +1640,7 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
}
static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
- bool surface_switch, bool button_switch)
+ enum report_mode report_mode)
{
struct hid_report_enum *rep_enum;
struct hid_report *rep;
@@ -1556,8 +1665,7 @@ static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
rep->field[i],
usage,
latency,
- surface_switch,
- button_switch,
+ report_mode,
&inputmode_found))
update_report = true;
}
@@ -1594,13 +1702,20 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app)
static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
{
struct mt_device *td = hid_get_drvdata(hdev);
- char *name;
const char *suffix = NULL;
struct mt_report_data *rdata;
struct mt_application *mt_application = NULL;
struct hid_report *report;
int ret;
+ if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
+ td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
+ if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0)
+ td->is_haptic_touchpad = false;
+ } else {
+ td->is_haptic_touchpad = false;
+ }
+
list_for_each_entry(report, &hi->reports, hidinput_list) {
rdata = mt_find_report_data(td, report);
if (!rdata) {
@@ -1627,6 +1742,7 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
case HID_CP_CONSUMER_CONTROL:
case HID_GD_WIRELESS_RADIO_CTLS:
case HID_GD_SYSTEM_MULTIAXIS:
+ case HID_DG_PEN:
/* already handled by hid core */
break;
case HID_DG_TOUCHSCREEN:
@@ -1646,13 +1762,10 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
}
if (suffix) {
- name = devm_kzalloc(&hi->input->dev,
- strlen(hdev->name) + strlen(suffix) + 2,
- GFP_KERNEL);
- if (name) {
- sprintf(name, "%s %s", hdev->name, suffix);
- hi->input->name = name;
- }
+ hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "%s %s", hdev->name, suffix);
+ if (!hi->input->name)
+ return -ENOMEM;
}
return 0;
@@ -1701,6 +1814,7 @@ static void mt_release_contacts(struct hid_device *hid)
for (i = 0; i < mt->num_slots; i++) {
input_mt_slot(input_dev, i);
input_mt_report_slot_inactive(input_dev);
+ clear_bit(i, &td->mt_io_flags);
}
input_mt_sync_frame(input_dev);
input_sync(input_dev);
@@ -1714,7 +1828,7 @@ static void mt_release_contacts(struct hid_device *hid)
static void mt_expired_timeout(struct timer_list *t)
{
- struct mt_device *td = from_timer(td, t, release_timer);
+ struct mt_device *td = timer_container_of(td, t, release_timer);
struct hid_device *hdev = td->hdev;
/*
@@ -1723,7 +1837,7 @@ static void mt_expired_timeout(struct timer_list *t)
*/
if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
return;
- if (test_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags))
+ if (td->mt_io_flags & MT_IO_SLOTS_MASK)
mt_release_contacts(hdev);
clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
}
@@ -1746,6 +1860,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
dev_err(&hdev->dev, "cannot allocate multitouch data\n");
return -ENOMEM;
}
+ td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL);
+ if (!td->haptic)
+ return -ENOMEM;
+
+ td->haptic->hdev = hdev;
td->hdev = hdev;
td->mtclass = *mtclass;
td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
@@ -1792,9 +1911,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret != 0)
return ret;
+ if (mtclass->name == MT_CLS_APPLE_TOUCHBAR &&
+ !hid_find_field(hdev, HID_INPUT_REPORT,
+ HID_DG_TOUCHPAD, HID_DG_TRANSDUCER_INDEX))
+ return -ENODEV;
+
if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID)
mt_fix_const_fields(hdev, HID_DG_CONTACTID);
+ if (hdev->vendor == USB_VENDOR_ID_SIS_TOUCH)
+ hdev->quirks |= HID_QUIRK_NOGET;
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret)
return ret;
@@ -1804,12 +1931,22 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
dev_warn(&hdev->dev, "Cannot allocate sysfs group for %s\n",
hdev->name);
- mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+ mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
+
+ if (td->is_haptic_touchpad) {
+ if (hid_haptic_init(hdev, &td->haptic)) {
+ dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
+ hdev->name);
+ td->is_haptic_touchpad = false;
+ devm_kfree(&hdev->dev, td->haptic);
+ }
+ } else {
+ devm_kfree(&hdev->dev, td->haptic);
+ }
return 0;
}
-#ifdef CONFIG_PM
static int mt_suspend(struct hid_device *hdev, pm_message_t state)
{
struct mt_device *td = hid_get_drvdata(hdev);
@@ -1817,9 +1954,9 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state)
/* High latency is desirable for power savings during S3/S0ix */
if ((td->mtclass.quirks & MT_QUIRK_DISABLE_WAKEUP) ||
!hid_hw_may_wakeup(hdev))
- mt_set_modes(hdev, HID_LATENCY_HIGH, false, false);
+ mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE);
else
- mt_set_modes(hdev, HID_LATENCY_HIGH, true, true);
+ mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_ALL);
return 0;
}
@@ -1827,7 +1964,7 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state)
static int mt_reset_resume(struct hid_device *hdev)
{
mt_release_contacts(hdev);
- mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+ mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
return 0;
}
@@ -1839,22 +1976,31 @@ static int mt_resume(struct hid_device *hdev)
hid_hw_idle(hdev, 0, 0, HID_REQ_SET_IDLE);
- mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+ mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
return 0;
}
-#endif
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);
}
+static void mt_on_hid_hw_open(struct hid_device *hdev)
+{
+ mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
+}
+
+static void mt_on_hid_hw_close(struct hid_device *hdev)
+{
+ mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE);
+}
+
/*
* This list contains only:
* - VID/PID of products not working with the default multitouch handling
@@ -2002,6 +2148,10 @@ static const struct hid_device_id mt_devices[] = {
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_ELAN, 0x3148) },
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_ELAN, 0x32ae) },
+
/* Elitegroup panel */
{ .driver_data = MT_CLS_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_ELITEGROUP,
@@ -2045,6 +2195,14 @@ static const struct hid_device_id mt_devices[] = {
MT_BT_DEVICE(USB_VENDOR_ID_FRUCTEL,
USB_DEVICE_ID_GAMETEL_MT_MODE) },
+ /* Goodix GT7868Q devices */
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
+ HID_DEVICE(BUS_I2C, HID_GROUP_ANY, I2C_VENDOR_ID_GOODIX,
+ I2C_DEVICE_ID_GOODIX_01E8) },
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
+ HID_DEVICE(BUS_I2C, HID_GROUP_ANY, I2C_VENDOR_ID_GOODIX,
+ I2C_DEVICE_ID_GOODIX_01E9) },
+
/* GoodTouch panels */
{ .driver_data = MT_CLS_NSMU,
MT_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH,
@@ -2055,6 +2213,16 @@ static const struct hid_device_id mt_devices[] = {
MT_USB_DEVICE(USB_VENDOR_ID_HANVON_ALT,
USB_DEVICE_ID_HANVON_ALT_MULTITOUCH) },
+ /* HONOR GLO-GXXX panel */
+ { .driver_data = MT_CLS_VTL,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ 0x347d, 0x7853) },
+
+ /* HONOR MagicBook Art 14 touchpad */
+ { .driver_data = MT_CLS_VTL,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ 0x35cc, 0x0104) },
+
/* Ilitek dual touch panel */
{ .driver_data = MT_CLS_NSMU,
MT_USB_DEVICE(USB_VENDOR_ID_ILITEK,
@@ -2068,12 +2236,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,
@@ -2086,6 +2260,22 @@ static const struct hid_device_id mt_devices[] = {
USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X12_TAB) },
+ /* Lenovo X12 TAB Gen 2 */
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LENOVO,
+ USB_DEVICE_ID_LENOVO_X12_TAB2) },
+
+ /* Logitech devices */
+ { .driver_data = MT_CLS_NSMU,
+ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD) },
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LOGITECH,
+ USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER) },
+
/* MosArt panels */
{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
MT_USB_DEVICE(USB_VENDOR_ID_ASUS,
@@ -2153,6 +2343,14 @@ static const struct hid_device_id mt_devices[] = {
/* Synaptics devices */
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_SYNAPTICS, 0xcd7e) },
+
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_SYNAPTICS, 0xcddc) },
+
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_SYNAPTICS, 0xce08) },
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
@@ -2226,6 +2424,11 @@ static const struct hid_device_id mt_devices[] = {
MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
USB_DEVICE_ID_XIROKU_CSR2) },
+ /* Apple Touch Bar */
+ { .driver_data = MT_CLS_APPLE_TOUCHBAR,
+ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
+ USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
+
/* Google MT devices */
{ .driver_data = MT_CLS_GOOGLE,
HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE,
@@ -2234,6 +2437,16 @@ static const struct hid_device_id mt_devices[] = {
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_GOOGLE,
USB_DEVICE_ID_GOOGLE_WHISKERS) },
+ /* sis */
+ { .driver_data = MT_CLS_SIS,
+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_SIS_TOUCH,
+ HID_ANY_ID) },
+
+ /* Hantick */
+ { .driver_data = MT_CLS_NSMU,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) },
+
/* Generic MT device */
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) },
@@ -2261,11 +2474,12 @@ static struct hid_driver mt_driver = {
.feature_mapping = mt_feature_mapping,
.usage_table = mt_grabbed_usages,
.event = mt_event,
+ .report_fixup = mt_report_fixup,
.report = mt_report,
-#ifdef CONFIG_PM
- .suspend = mt_suspend,
- .reset_resume = mt_reset_resume,
- .resume = mt_resume,
-#endif
+ .suspend = pm_ptr(mt_suspend),
+ .reset_resume = pm_ptr(mt_reset_resume),
+ .resume = pm_ptr(mt_resume),
+ .on_hid_hw_open = mt_on_hid_hw_open,
+ .on_hid_hw_close = mt_on_hid_hw_close,
};
module_hid_driver(mt_driver);
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 250f5d2f888a..7ac9217d9096 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -3,6 +3,9 @@
* HID driver for Nintendo Switch Joy-Cons and Pro Controllers
*
* Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
+ * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
+ * Copyright (c) 2022 Emily Strickland <linux@emily.st>
+ * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
*
* The following resources/projects were referenced for this driver:
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
@@ -17,17 +20,21 @@
* This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The
* Pro Controllers can either be used over USB or Bluetooth.
*
+ * This driver also incorporates support for Nintendo Switch Online controllers
+ * for the NES, SNES, Sega Genesis, and N64.
+ *
* The driver will retrieve the factory calibration info from the controllers,
* so little to no user calibration should be required.
*
*/
#include "hid-ids.h"
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/hid.h>
+#include <linux/idr.h>
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/leds.h>
@@ -301,13 +308,19 @@ 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 */
enum joycon_ctlr_type {
- JOYCON_CTLR_TYPE_JCL = 0x01,
- JOYCON_CTLR_TYPE_JCR = 0x02,
- JOYCON_CTLR_TYPE_PRO = 0x03,
+ JOYCON_CTLR_TYPE_JCL = 0x01,
+ JOYCON_CTLR_TYPE_JCR = 0x02,
+ JOYCON_CTLR_TYPE_PRO = 0x03,
+ JOYCON_CTLR_TYPE_NESL = 0x09,
+ JOYCON_CTLR_TYPE_NESR = 0x0A,
+ JOYCON_CTLR_TYPE_SNES = 0x0B,
+ JOYCON_CTLR_TYPE_GEN = 0x0D,
+ JOYCON_CTLR_TYPE_N64 = 0x0C,
};
struct joycon_stick_cal {
@@ -325,28 +338,155 @@ struct joycon_imu_cal {
* All the controller's button values are stored in a u32.
* They can be accessed with bitwise ANDs.
*/
-static const u32 JC_BTN_Y = BIT(0);
-static const u32 JC_BTN_X = BIT(1);
-static const u32 JC_BTN_B = BIT(2);
-static const u32 JC_BTN_A = BIT(3);
-static const u32 JC_BTN_SR_R = BIT(4);
-static const u32 JC_BTN_SL_R = BIT(5);
-static const u32 JC_BTN_R = BIT(6);
-static const u32 JC_BTN_ZR = BIT(7);
-static const u32 JC_BTN_MINUS = BIT(8);
-static const u32 JC_BTN_PLUS = BIT(9);
-static const u32 JC_BTN_RSTICK = BIT(10);
-static const u32 JC_BTN_LSTICK = BIT(11);
-static const u32 JC_BTN_HOME = BIT(12);
-static const u32 JC_BTN_CAP = BIT(13); /* capture button */
-static const u32 JC_BTN_DOWN = BIT(16);
-static const u32 JC_BTN_UP = BIT(17);
-static const u32 JC_BTN_RIGHT = BIT(18);
-static const u32 JC_BTN_LEFT = BIT(19);
-static const u32 JC_BTN_SR_L = BIT(20);
-static const u32 JC_BTN_SL_L = BIT(21);
-static const u32 JC_BTN_L = BIT(22);
-static const u32 JC_BTN_ZL = BIT(23);
+#define JC_BTN_Y BIT(0)
+#define JC_BTN_X BIT(1)
+#define JC_BTN_B BIT(2)
+#define JC_BTN_A BIT(3)
+#define JC_BTN_SR_R BIT(4)
+#define JC_BTN_SL_R BIT(5)
+#define JC_BTN_R BIT(6)
+#define JC_BTN_ZR BIT(7)
+#define JC_BTN_MINUS BIT(8)
+#define JC_BTN_PLUS BIT(9)
+#define JC_BTN_RSTICK BIT(10)
+#define JC_BTN_LSTICK BIT(11)
+#define JC_BTN_HOME BIT(12)
+#define JC_BTN_CAP BIT(13) /* capture button */
+#define JC_BTN_DOWN BIT(16)
+#define JC_BTN_UP BIT(17)
+#define JC_BTN_RIGHT BIT(18)
+#define JC_BTN_LEFT BIT(19)
+#define JC_BTN_SR_L BIT(20)
+#define JC_BTN_SL_L BIT(21)
+#define JC_BTN_L BIT(22)
+#define JC_BTN_ZL BIT(23)
+
+struct joycon_ctlr_button_mapping {
+ u32 code;
+ u32 bit;
+};
+
+/*
+ * D-pad is configured as buttons for the left Joy-Con only!
+ */
+static const struct joycon_ctlr_button_mapping left_joycon_button_mappings[] = {
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TL2, JC_BTN_ZL, },
+ { BTN_SELECT, JC_BTN_MINUS, },
+ { BTN_THUMBL, JC_BTN_LSTICK, },
+ { BTN_DPAD_UP, JC_BTN_UP, },
+ { BTN_DPAD_DOWN, JC_BTN_DOWN, },
+ { BTN_DPAD_LEFT, JC_BTN_LEFT, },
+ { BTN_DPAD_RIGHT, JC_BTN_RIGHT, },
+ { BTN_Z, JC_BTN_CAP, },
+ { /* sentinel */ },
+};
+
+/*
+ * The unused *right*-side triggers become the SL/SR triggers for the *left*
+ * Joy-Con, if and only if we're not using a charging grip.
+ */
+static const struct joycon_ctlr_button_mapping left_joycon_s_button_mappings[] = {
+ { BTN_TR, JC_BTN_SL_L, },
+ { BTN_TR2, JC_BTN_SR_L, },
+ { /* sentinel */ },
+};
+
+static const struct joycon_ctlr_button_mapping right_joycon_button_mappings[] = {
+ { BTN_EAST, JC_BTN_A, },
+ { BTN_SOUTH, JC_BTN_B, },
+ { BTN_NORTH, JC_BTN_X, },
+ { BTN_WEST, JC_BTN_Y, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_TR2, JC_BTN_ZR, },
+ { BTN_START, JC_BTN_PLUS, },
+ { BTN_THUMBR, JC_BTN_RSTICK, },
+ { BTN_MODE, JC_BTN_HOME, },
+ { /* sentinel */ },
+};
+
+/*
+ * The unused *left*-side triggers become the SL/SR triggers for the *right*
+ * Joy-Con, if and only if we're not using a charging grip.
+ */
+static const struct joycon_ctlr_button_mapping right_joycon_s_button_mappings[] = {
+ { BTN_TL, JC_BTN_SL_R, },
+ { BTN_TL2, JC_BTN_SR_R, },
+ { /* sentinel */ },
+};
+
+static const struct joycon_ctlr_button_mapping procon_button_mappings[] = {
+ { BTN_EAST, JC_BTN_A, },
+ { BTN_SOUTH, JC_BTN_B, },
+ { BTN_NORTH, JC_BTN_X, },
+ { BTN_WEST, JC_BTN_Y, },
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_TL2, JC_BTN_ZL, },
+ { BTN_TR2, JC_BTN_ZR, },
+ { BTN_SELECT, JC_BTN_MINUS, },
+ { BTN_START, JC_BTN_PLUS, },
+ { BTN_THUMBL, JC_BTN_LSTICK, },
+ { BTN_THUMBR, JC_BTN_RSTICK, },
+ { BTN_MODE, JC_BTN_HOME, },
+ { BTN_Z, JC_BTN_CAP, },
+ { /* sentinel */ },
+};
+
+static const struct joycon_ctlr_button_mapping nescon_button_mappings[] = {
+ { BTN_SOUTH, JC_BTN_A, },
+ { BTN_EAST, JC_BTN_B, },
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_SELECT, JC_BTN_MINUS, },
+ { BTN_START, JC_BTN_PLUS, },
+ { /* sentinel */ },
+};
+
+static const struct joycon_ctlr_button_mapping snescon_button_mappings[] = {
+ { BTN_EAST, JC_BTN_A, },
+ { BTN_SOUTH, JC_BTN_B, },
+ { BTN_NORTH, JC_BTN_X, },
+ { BTN_WEST, JC_BTN_Y, },
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_TL2, JC_BTN_ZL, },
+ { BTN_TR2, JC_BTN_ZR, },
+ { BTN_SELECT, JC_BTN_MINUS, },
+ { BTN_START, JC_BTN_PLUS, },
+ { /* sentinel */ },
+};
+
+static const struct joycon_ctlr_button_mapping gencon_button_mappings[] = {
+ { 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 */ },
+};
+
+static const struct joycon_ctlr_button_mapping n64con_button_mappings[] = {
+ { BTN_A, JC_BTN_A, },
+ { BTN_B, JC_BTN_B, },
+ { BTN_TL2, JC_BTN_ZL, }, /* Z */
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_TR2, JC_BTN_LSTICK, }, /* ZR */
+ { BTN_START, JC_BTN_PLUS, },
+ { BTN_SELECT, JC_BTN_Y, }, /* C UP */
+ { BTN_X, JC_BTN_ZR, }, /* C DOWN */
+ { BTN_Y, JC_BTN_X, }, /* C LEFT */
+ { BTN_C, JC_BTN_MINUS, }, /* C RIGHT */
+ { BTN_MODE, JC_BTN_HOME, },
+ { BTN_Z, JC_BTN_CAP, },
+ { /* sentinel */ },
+};
enum joycon_msg_type {
JOYCON_MSG_TYPE_NONE,
@@ -410,11 +550,24 @@ static const char * const joycon_player_led_names[] = {
LED_FUNCTION_PLAYER4,
};
#define JC_NUM_LEDS ARRAY_SIZE(joycon_player_led_names)
+#define JC_NUM_LED_PATTERNS 8
+/* Taken from https://www.nintendo.com/my/support/qa/detail/33822 */
+static const enum led_brightness joycon_player_led_patterns[JC_NUM_LED_PATTERNS][JC_NUM_LEDS] = {
+ { 1, 0, 0, 0 },
+ { 1, 1, 0, 0 },
+ { 1, 1, 1, 0 },
+ { 1, 1, 1, 1 },
+ { 1, 0, 0, 1 },
+ { 1, 0, 1, 0 },
+ { 1, 0, 1, 1 },
+ { 0, 1, 1, 0 },
+};
/* Each physical controller is associated with a joycon_ctlr struct */
struct joycon_ctlr {
struct hid_device *hdev;
struct input_dev *input;
+ u32 player_id;
struct led_classdev leds[JC_NUM_LEDS]; /* player leds */
struct led_classdev home_led;
enum joycon_ctlr_state ctlr_state;
@@ -494,13 +647,130 @@ struct joycon_ctlr {
/* Does this controller have inputs associated with left joycon? */
#define jc_type_has_left(ctlr) \
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL || \
- ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO)
+ ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO || \
+ ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64)
/* Does this controller have inputs associated with right joycon? */
#define jc_type_has_right(ctlr) \
(ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \
ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO)
+/*
+ * Controller device helpers
+ *
+ * These look at the device ID known to the HID subsystem to identify a device,
+ * but take caution: some NSO devices lie about themselves (NES Joy-Cons and
+ * Sega Genesis controller). See type helpers below.
+ *
+ * These helpers are most useful early during the HID probe or in conjunction
+ * with the capability helpers below.
+ */
+static inline bool joycon_device_is_chrggrip(struct joycon_ctlr *ctlr)
+{
+ return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP;
+}
+
+/*
+ * Controller type helpers
+ *
+ * These are slightly different than the device-ID-based helpers above. They are
+ * generally more reliable, since they can distinguish between, e.g., Genesis
+ * versus SNES, or NES Joy-Cons versus regular Switch Joy-Cons. They're most
+ * useful for reporting available inputs. For other kinds of distinctions, see
+ * the capability helpers below.
+ *
+ * They have two major drawbacks: (1) they're not available until after we set
+ * the reporting method and then request the device info; (2) they can't
+ * distinguish all controllers (like the Charging Grip from the Pro controller.)
+ */
+static inline bool joycon_type_is_left_joycon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL;
+}
+
+static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR;
+}
+
+static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO;
+}
+
+static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_SNES;
+}
+
+static inline bool joycon_type_is_gencon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_GEN;
+}
+
+static inline bool joycon_type_is_n64con(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64;
+}
+
+static inline bool joycon_type_is_left_nescon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESL;
+}
+
+static inline bool joycon_type_is_right_nescon(struct joycon_ctlr *ctlr)
+{
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESR;
+}
+
+static inline bool joycon_type_is_any_joycon(struct joycon_ctlr *ctlr)
+{
+ return joycon_type_is_left_joycon(ctlr) ||
+ joycon_type_is_right_joycon(ctlr) ||
+ joycon_device_is_chrggrip(ctlr);
+}
+
+static inline bool joycon_type_is_any_nescon(struct joycon_ctlr *ctlr)
+{
+ return joycon_type_is_left_nescon(ctlr) ||
+ joycon_type_is_right_nescon(ctlr);
+}
+
+/*
+ * Controller capability helpers
+ *
+ * These helpers combine the use of the helpers above to detect certain
+ * capabilities during initialization. They are always accurate but (since they
+ * use type helpers) cannot be used early in the HID probe.
+ */
+static inline bool joycon_has_imu(struct joycon_ctlr *ctlr)
+{
+ return joycon_device_is_chrggrip(ctlr) ||
+ joycon_type_is_any_joycon(ctlr) ||
+ joycon_type_is_procon(ctlr);
+}
+
+static inline bool joycon_has_joysticks(struct joycon_ctlr *ctlr)
+{
+ return joycon_device_is_chrggrip(ctlr) ||
+ joycon_type_is_any_joycon(ctlr) ||
+ joycon_type_is_procon(ctlr) ||
+ joycon_type_is_n64con(ctlr);
+}
+
+static inline bool joycon_has_rumble(struct joycon_ctlr *ctlr)
+{
+ return joycon_device_is_chrggrip(ctlr) ||
+ joycon_type_is_any_joycon(ctlr) ||
+ joycon_type_is_procon(ctlr) ||
+ joycon_type_is_n64con(ctlr);
+}
+
+static inline bool joycon_using_usb(struct joycon_ctlr *ctlr)
+{
+ return ctlr->hdev->bus == BUS_USB;
+}
+
static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len)
{
u8 *buf;
@@ -549,7 +819,7 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
#define JC_INPUT_REPORT_MAX_DELTA 17
#define JC_SUBCMD_TX_OFFSET_MS 4
#define JC_SUBCMD_VALID_DELTA_REQ 3
-#define JC_SUBCMD_RATE_MAX_ATTEMPTS 500
+#define JC_SUBCMD_RATE_MAX_ATTEMPTS 25
#define JC_SUBCMD_RATE_LIMITER_USB_MS 20
#define JC_SUBCMD_RATE_LIMITER_BT_MS 60
#define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
@@ -699,6 +969,25 @@ static int joycon_set_player_leds(struct joycon_ctlr *ctlr, u8 flash, u8 on)
return joycon_send_subcmd(ctlr, req, 1, HZ/4);
}
+static int joycon_set_home_led(struct joycon_ctlr *ctlr, enum led_brightness brightness)
+{
+ struct joycon_subcmd_request *req;
+ u8 buffer[sizeof(*req) + 5] = { 0 };
+ u8 *data;
+
+ req = (struct joycon_subcmd_request *)buffer;
+ req->subcmd_id = JC_SUBCMD_SET_HOME_LIGHT;
+ data = req->data;
+ data[0] = 0x01;
+ data[1] = brightness << 4;
+ data[2] = brightness | (brightness << 4);
+ data[3] = 0x11;
+ data[4] = 0x11;
+
+ hid_dbg(ctlr->hdev, "setting home led brightness\n");
+ return joycon_send_subcmd(ctlr, req, 5, HZ/4);
+}
+
static int joycon_request_spi_flash_read(struct joycon_ctlr *ctlr,
u32 start_addr, u8 size, u8 **reply)
{
@@ -896,14 +1185,27 @@ static int joycon_request_calibration(struct joycon_ctlr *ctlr)
*/
static void joycon_calc_imu_cal_divisors(struct joycon_ctlr *ctlr)
{
- int i;
+ int i, divz = 0;
for (i = 0; i < 3; i++) {
ctlr->imu_cal_accel_divisor[i] = ctlr->accel_cal.scale[i] -
ctlr->accel_cal.offset[i];
ctlr->imu_cal_gyro_divisor[i] = ctlr->gyro_cal.scale[i] -
ctlr->gyro_cal.offset[i];
+
+ if (ctlr->imu_cal_accel_divisor[i] == 0) {
+ ctlr->imu_cal_accel_divisor[i] = 1;
+ divz++;
+ }
+
+ if (ctlr->imu_cal_gyro_divisor[i] == 0) {
+ ctlr->imu_cal_gyro_divisor[i] = 1;
+ divz++;
+ }
}
+
+ if (divz)
+ hid_warn(ctlr->hdev, "inaccurate IMU divisors (%d)\n", divz);
}
static const s16 DFLT_ACCEL_OFFSET /*= 0*/;
@@ -1132,16 +1434,16 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
JC_IMU_SAMPLES_PER_DELTA_AVG) {
ctlr->imu_avg_delta_ms = ctlr->imu_delta_samples_sum /
ctlr->imu_delta_samples_count;
- /* don't ever want divide by zero shenanigans */
- if (ctlr->imu_avg_delta_ms == 0) {
- ctlr->imu_avg_delta_ms = 1;
- hid_warn(ctlr->hdev,
- "calculated avg imu delta of 0\n");
- }
ctlr->imu_delta_samples_count = 0;
ctlr->imu_delta_samples_sum = 0;
}
+ /* don't ever want divide by zero shenanigans */
+ if (ctlr->imu_avg_delta_ms == 0) {
+ ctlr->imu_avg_delta_ms = 1;
+ hid_warn(ctlr->hdev, "calculated avg imu delta of 0\n");
+ }
+
/* useful for debugging IMU sample rate */
hid_dbg(ctlr->hdev,
"imu_report: ms=%u last_ms=%u delta=%u avg_delta=%u\n",
@@ -1153,10 +1455,10 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
ctlr->imu_avg_delta_ms;
ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
- hid_warn(ctlr->hdev,
+ hid_warn_ratelimited(ctlr->hdev,
"compensating for %u dropped IMU reports\n",
dropped_pkts);
- hid_warn(ctlr->hdev,
+ hid_warn_ratelimited(ctlr->hdev,
"delta=%u avg_delta=%u\n",
delta, ctlr->imu_avg_delta_ms);
}
@@ -1252,15 +1554,10 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
}
}
-static void joycon_parse_report(struct joycon_ctlr *ctlr,
- struct joycon_input_report *rep)
+static void joycon_handle_rumble_report(struct joycon_ctlr *ctlr, struct joycon_input_report *rep)
{
- struct input_dev *dev = ctlr->input;
unsigned long flags;
- u8 tmp;
- u32 btns;
unsigned long msecs = jiffies_to_msecs(jiffies);
- unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;
spin_lock_irqsave(&ctlr->lock, flags);
if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report &&
@@ -1279,11 +1576,21 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
queue_work(ctlr->rumble_queue, &ctlr->rumble_worker);
}
- /* Parse the battery status */
+ spin_unlock_irqrestore(&ctlr->lock, flags);
+}
+
+static void joycon_parse_battery_status(struct joycon_ctlr *ctlr, struct joycon_input_report *rep)
+{
+ u8 tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctlr->lock, flags);
+
tmp = rep->bat_con;
ctlr->host_powered = tmp & BIT(0);
ctlr->battery_charging = tmp & BIT(4);
tmp = tmp >> 5;
+
switch (tmp) {
case 0: /* empty */
ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
@@ -1305,102 +1612,121 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
hid_warn(ctlr->hdev, "Invalid battery status\n");
break;
}
+
spin_unlock_irqrestore(&ctlr->lock, flags);
+}
- /* Parse the buttons and sticks */
- btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
-
- if (jc_type_has_left(ctlr)) {
- u16 raw_x;
- u16 raw_y;
- s32 x;
- s32 y;
-
- /* get raw stick values */
- raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12);
- raw_y = hid_field_extract(ctlr->hdev,
- rep->left_stick + 1, 4, 12);
- /* map the stick values */
- x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x);
- y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y);
- /* report sticks */
- input_report_abs(dev, ABS_X, x);
- input_report_abs(dev, ABS_Y, y);
-
- /* report buttons */
- input_report_key(dev, BTN_TL, btns & JC_BTN_L);
- input_report_key(dev, BTN_TL2, btns & JC_BTN_ZL);
- input_report_key(dev, BTN_SELECT, btns & JC_BTN_MINUS);
- input_report_key(dev, BTN_THUMBL, btns & JC_BTN_LSTICK);
- input_report_key(dev, BTN_Z, btns & JC_BTN_CAP);
-
- if (jc_type_is_joycon(ctlr)) {
- /* Report the S buttons as the non-existent triggers */
- input_report_key(dev, BTN_TR, btns & JC_BTN_SL_L);
- input_report_key(dev, BTN_TR2, btns & JC_BTN_SR_L);
-
- /* Report d-pad as digital buttons for the joy-cons */
- input_report_key(dev, BTN_DPAD_DOWN,
- btns & JC_BTN_DOWN);
- input_report_key(dev, BTN_DPAD_UP, btns & JC_BTN_UP);
- input_report_key(dev, BTN_DPAD_RIGHT,
- btns & JC_BTN_RIGHT);
- input_report_key(dev, BTN_DPAD_LEFT,
- btns & JC_BTN_LEFT);
- } else {
- int hatx = 0;
- int haty = 0;
-
- /* d-pad x */
- if (btns & JC_BTN_LEFT)
- hatx = -1;
- else if (btns & JC_BTN_RIGHT)
- hatx = 1;
- input_report_abs(dev, ABS_HAT0X, hatx);
-
- /* d-pad y */
- if (btns & JC_BTN_UP)
- haty = -1;
- else if (btns & JC_BTN_DOWN)
- haty = 1;
- input_report_abs(dev, ABS_HAT0Y, haty);
- }
- }
- if (jc_type_has_right(ctlr)) {
- u16 raw_x;
- u16 raw_y;
- s32 x;
- s32 y;
-
- /* get raw stick values */
- raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12);
- raw_y = hid_field_extract(ctlr->hdev,
- rep->right_stick + 1, 4, 12);
- /* map stick values */
- x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x);
- y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y);
- /* report sticks */
- input_report_abs(dev, ABS_RX, x);
- input_report_abs(dev, ABS_RY, y);
-
- /* report buttons */
- input_report_key(dev, BTN_TR, btns & JC_BTN_R);
- input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR);
- if (jc_type_is_joycon(ctlr)) {
- /* Report the S buttons as the non-existent triggers */
- input_report_key(dev, BTN_TL, btns & JC_BTN_SL_R);
- input_report_key(dev, BTN_TL2, btns & JC_BTN_SR_R);
- }
- input_report_key(dev, BTN_START, btns & JC_BTN_PLUS);
- input_report_key(dev, BTN_THUMBR, btns & JC_BTN_RSTICK);
- input_report_key(dev, BTN_MODE, btns & JC_BTN_HOME);
- input_report_key(dev, BTN_WEST, btns & JC_BTN_Y);
- input_report_key(dev, BTN_NORTH, btns & JC_BTN_X);
- input_report_key(dev, BTN_EAST, btns & JC_BTN_A);
- input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B);
+static void joycon_report_left_stick(struct joycon_ctlr *ctlr,
+ struct joycon_input_report *rep)
+{
+ u16 raw_x;
+ u16 raw_y;
+ s32 x;
+ s32 y;
+
+ raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12);
+ raw_y = hid_field_extract(ctlr->hdev, rep->left_stick + 1, 4, 12);
+
+ x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x);
+ y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y);
+
+ input_report_abs(ctlr->input, ABS_X, x);
+ input_report_abs(ctlr->input, ABS_Y, y);
+}
+
+static void joycon_report_right_stick(struct joycon_ctlr *ctlr,
+ struct joycon_input_report *rep)
+{
+ u16 raw_x;
+ u16 raw_y;
+ s32 x;
+ s32 y;
+
+ raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12);
+ raw_y = hid_field_extract(ctlr->hdev, rep->right_stick + 1, 4, 12);
+
+ x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x);
+ y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y);
+
+ input_report_abs(ctlr->input, ABS_RX, x);
+ input_report_abs(ctlr->input, ABS_RY, y);
+}
+
+static void joycon_report_dpad(struct joycon_ctlr *ctlr,
+ struct joycon_input_report *rep)
+{
+ int hatx = 0;
+ int haty = 0;
+ u32 btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
+
+ if (btns & JC_BTN_LEFT)
+ hatx = -1;
+ else if (btns & JC_BTN_RIGHT)
+ hatx = 1;
+
+ if (btns & JC_BTN_UP)
+ haty = -1;
+ else if (btns & JC_BTN_DOWN)
+ haty = 1;
+
+ input_report_abs(ctlr->input, ABS_HAT0X, hatx);
+ input_report_abs(ctlr->input, ABS_HAT0Y, haty);
+}
+
+static void joycon_report_buttons(struct joycon_ctlr *ctlr,
+ struct joycon_input_report *rep,
+ const struct joycon_ctlr_button_mapping button_mappings[])
+{
+ const struct joycon_ctlr_button_mapping *button;
+ u32 status = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24);
+
+ for (button = button_mappings; button->code; button++)
+ input_report_key(ctlr->input, button->code, status & button->bit);
+}
+
+static void joycon_parse_report(struct joycon_ctlr *ctlr,
+ struct joycon_input_report *rep)
+{
+ unsigned long flags;
+ unsigned long msecs = jiffies_to_msecs(jiffies);
+ unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs;
+
+ if (joycon_has_rumble(ctlr))
+ joycon_handle_rumble_report(ctlr, rep);
+
+ joycon_parse_battery_status(ctlr, rep);
+
+ if (joycon_type_is_left_joycon(ctlr)) {
+ joycon_report_left_stick(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, left_joycon_button_mappings);
+ if (!joycon_device_is_chrggrip(ctlr))
+ joycon_report_buttons(ctlr, rep, left_joycon_s_button_mappings);
+ } else if (joycon_type_is_right_joycon(ctlr)) {
+ joycon_report_right_stick(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, right_joycon_button_mappings);
+ if (!joycon_device_is_chrggrip(ctlr))
+ joycon_report_buttons(ctlr, rep, right_joycon_s_button_mappings);
+ } else if (joycon_type_is_procon(ctlr)) {
+ joycon_report_left_stick(ctlr, rep);
+ joycon_report_right_stick(ctlr, rep);
+ joycon_report_dpad(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, procon_button_mappings);
+ } else if (joycon_type_is_any_nescon(ctlr)) {
+ joycon_report_dpad(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, nescon_button_mappings);
+ } else if (joycon_type_is_snescon(ctlr)) {
+ joycon_report_dpad(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, snescon_button_mappings);
+ } else if (joycon_type_is_gencon(ctlr)) {
+ joycon_report_dpad(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, gencon_button_mappings);
+ } else if (joycon_type_is_n64con(ctlr)) {
+ joycon_report_left_stick(ctlr, rep);
+ joycon_report_dpad(ctlr, rep);
+ joycon_report_buttons(ctlr, rep, n64con_button_mappings);
}
- input_sync(dev);
+ input_sync(ctlr->input);
spin_lock_irqsave(&ctlr->lock, flags);
ctlr->last_input_report_msecs = msecs;
@@ -1440,7 +1766,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
}
/* parse IMU data if present */
- if (rep->id == JC_INPUT_IMU_DATA)
+ if ((rep->id == JC_INPUT_IMU_DATA) && joycon_has_imu(ctlr))
joycon_parse_imu_report(ctlr, rep);
}
@@ -1653,123 +1979,65 @@ static int joycon_play_effect(struct input_dev *dev, void *data,
}
#endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */
-static const unsigned int joycon_button_inputs_l[] = {
- BTN_SELECT, BTN_Z, BTN_THUMBL,
- BTN_TL, BTN_TL2,
- 0 /* 0 signals end of array */
-};
-
-static const unsigned int joycon_button_inputs_r[] = {
- BTN_START, BTN_MODE, BTN_THUMBR,
- BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST,
- BTN_TR, BTN_TR2,
- 0 /* 0 signals end of array */
-};
-
-/* We report joy-con d-pad inputs as buttons and pro controller as a hat. */
-static const unsigned int joycon_dpad_inputs_jc[] = {
- BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
- 0 /* 0 signals end of array */
-};
-
-static int joycon_input_create(struct joycon_ctlr *ctlr)
+static void joycon_config_left_stick(struct input_dev *idev)
{
- struct hid_device *hdev;
- const char *name;
- const char *imu_name;
- int ret;
- int i;
-
- hdev = ctlr->hdev;
+ input_set_abs_params(idev,
+ ABS_X,
+ -JC_MAX_STICK_MAG,
+ JC_MAX_STICK_MAG,
+ JC_STICK_FUZZ,
+ JC_STICK_FLAT);
+ input_set_abs_params(idev,
+ ABS_Y,
+ -JC_MAX_STICK_MAG,
+ JC_MAX_STICK_MAG,
+ JC_STICK_FUZZ,
+ JC_STICK_FLAT);
+}
- switch (hdev->product) {
- case USB_DEVICE_ID_NINTENDO_PROCON:
- name = "Nintendo Switch Pro Controller";
- imu_name = "Nintendo Switch Pro Controller IMU";
- break;
- case USB_DEVICE_ID_NINTENDO_CHRGGRIP:
- if (jc_type_has_left(ctlr)) {
- name = "Nintendo Switch Left Joy-Con (Grip)";
- imu_name = "Nintendo Switch Left Joy-Con IMU (Grip)";
- } else {
- name = "Nintendo Switch Right Joy-Con (Grip)";
- imu_name = "Nintendo Switch Right Joy-Con IMU (Grip)";
- }
- break;
- case USB_DEVICE_ID_NINTENDO_JOYCONL:
- name = "Nintendo Switch Left Joy-Con";
- imu_name = "Nintendo Switch Left Joy-Con IMU";
- break;
- case USB_DEVICE_ID_NINTENDO_JOYCONR:
- name = "Nintendo Switch Right Joy-Con";
- imu_name = "Nintendo Switch Right Joy-Con IMU";
- break;
- default: /* Should be impossible */
- hid_err(hdev, "Invalid hid product\n");
- return -EINVAL;
- }
+static void joycon_config_right_stick(struct input_dev *idev)
+{
+ input_set_abs_params(idev,
+ ABS_RX,
+ -JC_MAX_STICK_MAG,
+ JC_MAX_STICK_MAG,
+ JC_STICK_FUZZ,
+ JC_STICK_FLAT);
+ input_set_abs_params(idev,
+ ABS_RY,
+ -JC_MAX_STICK_MAG,
+ JC_MAX_STICK_MAG,
+ JC_STICK_FUZZ,
+ JC_STICK_FLAT);
+}
- ctlr->input = devm_input_allocate_device(&hdev->dev);
- if (!ctlr->input)
- return -ENOMEM;
- ctlr->input->id.bustype = hdev->bus;
- ctlr->input->id.vendor = hdev->vendor;
- ctlr->input->id.product = hdev->product;
- ctlr->input->id.version = hdev->version;
- ctlr->input->uniq = ctlr->mac_addr_str;
- ctlr->input->name = name;
- ctlr->input->phys = hdev->phys;
- input_set_drvdata(ctlr->input, ctlr);
+static void joycon_config_dpad(struct input_dev *idev)
+{
+ input_set_abs_params(idev,
+ ABS_HAT0X,
+ -JC_MAX_DPAD_MAG,
+ JC_MAX_DPAD_MAG,
+ JC_DPAD_FUZZ,
+ JC_DPAD_FLAT);
+ input_set_abs_params(idev,
+ ABS_HAT0Y,
+ -JC_MAX_DPAD_MAG,
+ JC_MAX_DPAD_MAG,
+ JC_DPAD_FUZZ,
+ JC_DPAD_FLAT);
+}
- /* set up sticks and buttons */
- if (jc_type_has_left(ctlr)) {
- input_set_abs_params(ctlr->input, ABS_X,
- -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
- JC_STICK_FUZZ, JC_STICK_FLAT);
- input_set_abs_params(ctlr->input, ABS_Y,
- -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
- JC_STICK_FUZZ, JC_STICK_FLAT);
-
- for (i = 0; joycon_button_inputs_l[i] > 0; i++)
- input_set_capability(ctlr->input, EV_KEY,
- joycon_button_inputs_l[i]);
-
- /* configure d-pad differently for joy-con vs pro controller */
- if (hdev->product != USB_DEVICE_ID_NINTENDO_PROCON) {
- for (i = 0; joycon_dpad_inputs_jc[i] > 0; i++)
- input_set_capability(ctlr->input, EV_KEY,
- joycon_dpad_inputs_jc[i]);
- } else {
- input_set_abs_params(ctlr->input, ABS_HAT0X,
- -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG,
- JC_DPAD_FUZZ, JC_DPAD_FLAT);
- input_set_abs_params(ctlr->input, ABS_HAT0Y,
- -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG,
- JC_DPAD_FUZZ, JC_DPAD_FLAT);
- }
- }
- if (jc_type_has_right(ctlr)) {
- input_set_abs_params(ctlr->input, ABS_RX,
- -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
- JC_STICK_FUZZ, JC_STICK_FLAT);
- input_set_abs_params(ctlr->input, ABS_RY,
- -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG,
- JC_STICK_FUZZ, JC_STICK_FLAT);
-
- for (i = 0; joycon_button_inputs_r[i] > 0; i++)
- input_set_capability(ctlr->input, EV_KEY,
- joycon_button_inputs_r[i]);
- }
+static void joycon_config_buttons(struct input_dev *idev,
+ const struct joycon_ctlr_button_mapping button_mappings[])
+{
+ const struct joycon_ctlr_button_mapping *button;
- /* Let's report joy-con S triggers separately */
- if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL) {
- input_set_capability(ctlr->input, EV_KEY, BTN_TR);
- input_set_capability(ctlr->input, EV_KEY, BTN_TR2);
- } else if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR) {
- input_set_capability(ctlr->input, EV_KEY, BTN_TL);
- input_set_capability(ctlr->input, EV_KEY, BTN_TL2);
- }
+ for (button = button_mappings; button->code; button++)
+ input_set_capability(idev, EV_KEY, button->code);
+}
+static void joycon_config_rumble(struct joycon_ctlr *ctlr)
+{
#if IS_ENABLED(CONFIG_NINTENDO_FF)
/* set up rumble */
input_set_capability(ctlr->input, EV_FF, FF_RUMBLE);
@@ -1782,10 +2050,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
joycon_set_rumble(ctlr, 0, 0, false);
ctlr->rumble_msecs = jiffies_to_msecs(jiffies);
#endif
+}
- ret = input_register_device(ctlr->input);
- if (ret)
- return ret;
+static int joycon_imu_input_create(struct joycon_ctlr *ctlr)
+{
+ struct hid_device *hdev;
+ const char *imu_name;
+ int ret;
+
+ hdev = ctlr->hdev;
/* configure the imu input device */
ctlr->imu_input = devm_input_allocate_device(&hdev->dev);
@@ -1797,8 +2070,14 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
ctlr->imu_input->id.product = hdev->product;
ctlr->imu_input->id.version = hdev->version;
ctlr->imu_input->uniq = ctlr->mac_addr_str;
- ctlr->imu_input->name = imu_name;
ctlr->imu_input->phys = hdev->phys;
+
+ imu_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s (IMU)", ctlr->input->name);
+ if (!imu_name)
+ return -ENOMEM;
+
+ ctlr->imu_input->name = imu_name;
+
input_set_drvdata(ctlr->imu_input, ctlr);
/* configure imu axes */
@@ -1840,6 +2119,72 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
return 0;
}
+static int joycon_input_create(struct joycon_ctlr *ctlr)
+{
+ struct hid_device *hdev;
+ int ret;
+
+ hdev = ctlr->hdev;
+
+ ctlr->input = devm_input_allocate_device(&hdev->dev);
+ if (!ctlr->input)
+ return -ENOMEM;
+ ctlr->input->id.bustype = hdev->bus;
+ ctlr->input->id.vendor = hdev->vendor;
+ ctlr->input->id.product = hdev->product;
+ ctlr->input->id.version = hdev->version;
+ ctlr->input->uniq = ctlr->mac_addr_str;
+ ctlr->input->name = hdev->name;
+ ctlr->input->phys = hdev->phys;
+ input_set_drvdata(ctlr->input, ctlr);
+
+ ret = input_register_device(ctlr->input);
+ if (ret)
+ return ret;
+
+ if (joycon_type_is_right_joycon(ctlr)) {
+ joycon_config_right_stick(ctlr->input);
+ joycon_config_buttons(ctlr->input, right_joycon_button_mappings);
+ if (!joycon_device_is_chrggrip(ctlr))
+ joycon_config_buttons(ctlr->input, right_joycon_s_button_mappings);
+ } else if (joycon_type_is_left_joycon(ctlr)) {
+ joycon_config_left_stick(ctlr->input);
+ joycon_config_buttons(ctlr->input, left_joycon_button_mappings);
+ if (!joycon_device_is_chrggrip(ctlr))
+ joycon_config_buttons(ctlr->input, left_joycon_s_button_mappings);
+ } else if (joycon_type_is_procon(ctlr)) {
+ joycon_config_left_stick(ctlr->input);
+ joycon_config_right_stick(ctlr->input);
+ joycon_config_dpad(ctlr->input);
+ joycon_config_buttons(ctlr->input, procon_button_mappings);
+ } else if (joycon_type_is_any_nescon(ctlr)) {
+ joycon_config_dpad(ctlr->input);
+ joycon_config_buttons(ctlr->input, nescon_button_mappings);
+ } else if (joycon_type_is_snescon(ctlr)) {
+ joycon_config_dpad(ctlr->input);
+ joycon_config_buttons(ctlr->input, snescon_button_mappings);
+ } else if (joycon_type_is_gencon(ctlr)) {
+ joycon_config_dpad(ctlr->input);
+ joycon_config_buttons(ctlr->input, gencon_button_mappings);
+ } else if (joycon_type_is_n64con(ctlr)) {
+ joycon_config_dpad(ctlr->input);
+ joycon_config_left_stick(ctlr->input);
+ joycon_config_buttons(ctlr->input, n64con_button_mappings);
+ }
+
+ if (joycon_has_imu(ctlr)) {
+ ret = joycon_imu_input_create(ctlr);
+ if (ret)
+ return ret;
+ }
+
+ if (joycon_has_rumble(ctlr))
+ joycon_config_rumble(ctlr);
+
+ return 0;
+}
+
+/* Because the subcommand sets all the leds at once, the brightness argument is ignored */
static int joycon_player_led_brightness_set(struct led_classdev *led,
enum led_brightness brightness)
{
@@ -1849,7 +2194,6 @@ static int joycon_player_led_brightness_set(struct led_classdev *led,
int val = 0;
int i;
int ret;
- int num;
ctlr = hid_get_drvdata(hdev);
if (!ctlr) {
@@ -1857,21 +2201,10 @@ static int joycon_player_led_brightness_set(struct led_classdev *led,
return -ENODEV;
}
- /* determine which player led this is */
- for (num = 0; num < JC_NUM_LEDS; num++) {
- if (&ctlr->leds[num] == led)
- break;
- }
- if (num >= JC_NUM_LEDS)
- return -EINVAL;
+ for (i = 0; i < JC_NUM_LEDS; i++)
+ val |= ctlr->leds[i].brightness << i;
mutex_lock(&ctlr->output_mutex);
- for (i = 0; i < JC_NUM_LEDS; i++) {
- if (i == num)
- val |= brightness << i;
- else
- val |= ctlr->leds[i].brightness << i;
- }
ret = joycon_set_player_leds(ctlr, 0, val);
mutex_unlock(&ctlr->output_mutex);
@@ -1884,9 +2217,6 @@ static int joycon_home_led_brightness_set(struct led_classdev *led,
struct device *dev = led->dev->parent;
struct hid_device *hdev = to_hid_device(dev);
struct joycon_ctlr *ctlr;
- struct joycon_subcmd_request *req;
- u8 buffer[sizeof(*req) + 5] = { 0 };
- u8 *data;
int ret;
ctlr = hid_get_drvdata(hdev);
@@ -1894,75 +2224,73 @@ static int joycon_home_led_brightness_set(struct led_classdev *led,
hid_err(hdev, "No controller data\n");
return -ENODEV;
}
-
- req = (struct joycon_subcmd_request *)buffer;
- req->subcmd_id = JC_SUBCMD_SET_HOME_LIGHT;
- data = req->data;
- data[0] = 0x01;
- data[1] = brightness << 4;
- data[2] = brightness | (brightness << 4);
- data[3] = 0x11;
- data[4] = 0x11;
-
- hid_dbg(hdev, "setting home led brightness\n");
mutex_lock(&ctlr->output_mutex);
- ret = joycon_send_subcmd(ctlr, req, 5, HZ/4);
+ ret = joycon_set_home_led(ctlr, brightness);
mutex_unlock(&ctlr->output_mutex);
-
return ret;
}
-static DEFINE_MUTEX(joycon_input_num_mutex);
+static DEFINE_IDA(nintendo_player_id_allocator);
+
static int joycon_leds_create(struct joycon_ctlr *ctlr)
{
struct hid_device *hdev = ctlr->hdev;
struct device *dev = &hdev->dev;
const char *d_name = dev_name(dev);
struct led_classdev *led;
+ int led_val = 0;
char *name;
- int ret = 0;
+ int ret;
int i;
- static int input_num = 1;
-
- /* Set the default controller player leds based on controller number */
- mutex_lock(&joycon_input_num_mutex);
- mutex_lock(&ctlr->output_mutex);
- ret = joycon_set_player_leds(ctlr, 0, 0xF >> (4 - input_num));
- if (ret)
- hid_warn(ctlr->hdev, "Failed to set leds; ret=%d\n", ret);
- mutex_unlock(&ctlr->output_mutex);
+ int player_led_pattern;
/* configure the player LEDs */
+ ctlr->player_id = U32_MAX;
+ ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
+ if (ret < 0) {
+ hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
+ goto home_led;
+ }
+ ctlr->player_id = ret;
+ player_led_pattern = ret % JC_NUM_LED_PATTERNS;
+ hid_info(ctlr->hdev, "assigned player %d led pattern", player_led_pattern + 1);
+
for (i = 0; i < JC_NUM_LEDS; i++) {
name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s:%s",
d_name,
"green",
joycon_player_led_names[i]);
- if (!name) {
- mutex_unlock(&joycon_input_num_mutex);
+ if (!name)
return -ENOMEM;
- }
led = &ctlr->leds[i];
led->name = name;
- led->brightness = ((i + 1) <= input_num) ? 1 : 0;
+ led->brightness = joycon_player_led_patterns[player_led_pattern][i];
led->max_brightness = 1;
led->brightness_set_blocking =
joycon_player_led_brightness_set;
led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
+ led_val |= joycon_player_led_patterns[player_led_pattern][i] << i;
+ }
+ mutex_lock(&ctlr->output_mutex);
+ ret = joycon_set_player_leds(ctlr, 0, led_val);
+ mutex_unlock(&ctlr->output_mutex);
+ if (ret) {
+ hid_warn(hdev, "Failed to set players LEDs, skipping registration; ret=%d\n", ret);
+ goto home_led;
+ }
+
+ for (i = 0; i < JC_NUM_LEDS; i++) {
+ led = &ctlr->leds[i];
ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
- hid_err(hdev, "Failed registering %s LED\n", led->name);
- mutex_unlock(&joycon_input_num_mutex);
+ hid_err(hdev, "Failed to register player %d LED; ret=%d\n", i + 1, ret);
return ret;
}
}
- if (++input_num > 4)
- input_num = 1;
- mutex_unlock(&joycon_input_num_mutex);
-
+home_led:
/* configure the home LED */
if (jc_type_has_right(ctlr)) {
name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s:%s",
@@ -1978,16 +2306,20 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr)
led->max_brightness = 0xF;
led->brightness_set_blocking = joycon_home_led_brightness_set;
led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
- ret = devm_led_classdev_register(&hdev->dev, led);
+
+ /* Set the home LED to 0 as default state */
+ mutex_lock(&ctlr->output_mutex);
+ ret = joycon_set_home_led(ctlr, 0);
+ mutex_unlock(&ctlr->output_mutex);
if (ret) {
- hid_err(hdev, "Failed registering home led\n");
- return ret;
+ hid_warn(hdev, "Failed to set home LED, skipping registration; ret=%d\n", ret);
+ return 0;
}
- /* Set the home LED to 0 as default state */
- ret = joycon_home_led_brightness_set(led, 0);
+
+ ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
- hid_warn(hdev, "Failed to set home LED default, unregistering home LED");
- devm_led_classdev_unregister(&hdev->dev, led);
+ hid_err(hdev, "Failed to register home LED; ret=%d\n", ret);
+ return ret;
}
}
@@ -2088,7 +2420,7 @@ static int joycon_read_info(struct joycon_ctlr *ctlr)
struct joycon_input_report *report;
req.subcmd_id = JC_SUBCMD_REQ_DEV_INFO;
- ret = joycon_send_subcmd(ctlr, &req, 0, HZ);
+ ret = joycon_send_subcmd(ctlr, &req, 0, 2 * HZ);
if (ret) {
hid_err(ctlr->hdev, "Failed to get joycon info; ret=%d\n", ret);
return ret;
@@ -2111,12 +2443,116 @@ static int joycon_read_info(struct joycon_ctlr *ctlr)
return -ENOMEM;
hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str);
- /* Retrieve the type so we can distinguish for charging grip */
+ /*
+ * Retrieve the type so we can distinguish the controller type
+ * Unfortantly the hdev->product can't always be used due to a ?bug?
+ * with the NSO Genesis controller. Over USB, it will report the
+ * PID as 0x201E, but over bluetooth it will report the PID as 0x2017
+ * which is the same as the NSO SNES controller. This is different from
+ * the rest of the controllers which will report the same PID over USB
+ * and bluetooth.
+ */
ctlr->ctlr_type = report->subcmd_reply.data[2];
+ hid_dbg(ctlr->hdev, "controller type = 0x%02X\n", ctlr->ctlr_type);
return 0;
}
+static int joycon_init(struct hid_device *hdev)
+{
+ struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
+ int ret = 0;
+
+ mutex_lock(&ctlr->output_mutex);
+ /* if handshake command fails, assume ble pro controller */
+ if (joycon_using_usb(ctlr) && !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) {
+ hid_dbg(hdev, "detected USB controller\n");
+ /* set baudrate for improved latency */
+ ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
+ if (ret) {
+ /*
+ * We can function with the default baudrate.
+ * Provide a warning, and continue on.
+ */
+ hid_warn(hdev, "Failed to set baudrate (ret=%d), continuing anyway\n", ret);
+ }
+ /* handshake */
+ ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ);
+ if (ret) {
+ hid_err(hdev, "Failed handshake; ret=%d\n", ret);
+ goto out_unlock;
+ }
+ /*
+ * Set no timeout (to keep controller in USB mode).
+ * This doesn't send a response, so ignore the timeout.
+ */
+ joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10);
+ } else if (jc_type_is_chrggrip(ctlr)) {
+ hid_err(hdev, "Failed charging grip handshake\n");
+ ret = -ETIMEDOUT;
+ goto out_unlock;
+ }
+
+ /* needed to retrieve the controller type */
+ ret = joycon_read_info(ctlr);
+ if (ret) {
+ hid_err(hdev, "Failed to retrieve controller info; ret=%d\n",
+ ret);
+ goto out_unlock;
+ }
+
+ if (joycon_has_joysticks(ctlr)) {
+ /* get controller calibration data, and parse it */
+ ret = joycon_request_calibration(ctlr);
+ if (ret) {
+ /*
+ * We can function with default calibration, but it may be
+ * inaccurate. Provide a warning, and continue on.
+ */
+ hid_warn(hdev, "Analog stick positions may be inaccurate\n");
+ }
+ }
+
+ if (joycon_has_imu(ctlr)) {
+ /* get IMU calibration data, and parse it */
+ ret = joycon_request_imu_calibration(ctlr);
+ if (ret) {
+ /*
+ * We can function with default calibration, but it may be
+ * inaccurate. Provide a warning, and continue on.
+ */
+ hid_warn(hdev, "Unable to read IMU calibration data\n");
+ }
+
+ /* Enable the IMU */
+ ret = joycon_enable_imu(ctlr);
+ if (ret) {
+ hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
+ goto out_unlock;
+ }
+ }
+
+ /* Set the reporting mode to 0x30, which is the full report mode */
+ ret = joycon_set_report_mode(ctlr);
+ if (ret) {
+ hid_err(hdev, "Failed to set report mode; ret=%d\n", ret);
+ goto out_unlock;
+ }
+
+ if (joycon_has_rumble(ctlr)) {
+ /* Enable rumble */
+ ret = joycon_enable_rumble(ctlr);
+ if (ret) {
+ hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
+ goto out_unlock;
+ }
+ }
+
+out_unlock:
+ mutex_unlock(&ctlr->output_mutex);
+ return ret;
+}
+
/* Common handler for parsing inputs */
static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,
int size)
@@ -2212,7 +2648,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
init_waitqueue_head(&ctlr->wait);
spin_lock_init(&ctlr->lock);
ctlr->rumble_queue = alloc_workqueue("hid-nintendo-rumble_wq",
- WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+ WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU,
+ 0);
if (!ctlr->rumble_queue) {
ret = -ENOMEM;
goto err;
@@ -2248,85 +2685,12 @@ static int nintendo_hid_probe(struct hid_device *hdev,
hid_device_io_start(hdev);
- /* Initialize the controller */
- mutex_lock(&ctlr->output_mutex);
- /* if handshake command fails, assume ble pro controller */
- if ((jc_type_is_procon(ctlr) || jc_type_is_chrggrip(ctlr)) &&
- !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) {
- hid_dbg(hdev, "detected USB controller\n");
- /* set baudrate for improved latency */
- ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
- if (ret) {
- hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret);
- goto err_mutex;
- }
- /* handshake */
- ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ);
- if (ret) {
- hid_err(hdev, "Failed handshake; ret=%d\n", ret);
- goto err_mutex;
- }
- /*
- * Set no timeout (to keep controller in USB mode).
- * This doesn't send a response, so ignore the timeout.
- */
- joycon_send_usb(ctlr, JC_USB_CMD_NO_TIMEOUT, HZ/10);
- } else if (jc_type_is_chrggrip(ctlr)) {
- hid_err(hdev, "Failed charging grip handshake\n");
- ret = -ETIMEDOUT;
- goto err_mutex;
- }
-
- /* get controller calibration data, and parse it */
- ret = joycon_request_calibration(ctlr);
- if (ret) {
- /*
- * We can function with default calibration, but it may be
- * inaccurate. Provide a warning, and continue on.
- */
- hid_warn(hdev, "Analog stick positions may be inaccurate\n");
- }
-
- /* get IMU calibration data, and parse it */
- ret = joycon_request_imu_calibration(ctlr);
- if (ret) {
- /*
- * We can function with default calibration, but it may be
- * inaccurate. Provide a warning, and continue on.
- */
- hid_warn(hdev, "Unable to read IMU calibration data\n");
- }
-
- /* Set the reporting mode to 0x30, which is the full report mode */
- ret = joycon_set_report_mode(ctlr);
+ ret = joycon_init(hdev);
if (ret) {
- hid_err(hdev, "Failed to set report mode; ret=%d\n", ret);
- goto err_mutex;
- }
-
- /* Enable rumble */
- ret = joycon_enable_rumble(ctlr);
- if (ret) {
- hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
- goto err_mutex;
- }
-
- /* Enable the IMU */
- ret = joycon_enable_imu(ctlr);
- if (ret) {
- hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
- goto err_mutex;
- }
-
- ret = joycon_read_info(ctlr);
- if (ret) {
- hid_err(hdev, "Failed to retrieve controller info; ret=%d\n",
- ret);
- goto err_mutex;
+ hid_err(hdev, "Failed to initialize controller; ret=%d\n", ret);
+ goto err_close;
}
- mutex_unlock(&ctlr->output_mutex);
-
/* Initialize the leds */
ret = joycon_leds_create(ctlr);
if (ret) {
@@ -2338,13 +2702,13 @@ static int nintendo_hid_probe(struct hid_device *hdev,
ret = joycon_power_supply_create(ctlr);
if (ret) {
hid_err(hdev, "Failed to create power_supply; ret=%d\n", ret);
- goto err_close;
+ goto err_ida;
}
ret = joycon_input_create(ctlr);
if (ret) {
hid_err(hdev, "Failed to create input device; ret=%d\n", ret);
- goto err_close;
+ goto err_ida;
}
ctlr->ctlr_state = JOYCON_CTLR_STATE_READ;
@@ -2352,8 +2716,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
hid_dbg(hdev, "probe - success\n");
return 0;
-err_mutex:
- mutex_unlock(&ctlr->output_mutex);
+err_ida:
+ ida_free(&nintendo_player_id_allocator, ctlr->player_id);
err_close:
hid_hw_close(hdev);
err_stop:
@@ -2378,14 +2742,67 @@ static void nintendo_hid_remove(struct hid_device *hdev)
spin_unlock_irqrestore(&ctlr->lock, flags);
destroy_workqueue(ctlr->rumble_queue);
+ ida_free(&nintendo_player_id_allocator, ctlr->player_id);
hid_hw_close(hdev);
hid_hw_stop(hdev);
}
+#ifdef CONFIG_PM
+
+static int nintendo_hid_resume(struct hid_device *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: %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[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_PROCON) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_SNESCON) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_GENCON) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_N64CON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_PROCON) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
@@ -2394,6 +2811,12 @@ static const struct hid_device_id nintendo_hid_devices[] = {
USB_DEVICE_ID_NINTENDO_JOYCONL) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_JOYCONR) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_SNESCON) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_GENCON) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_N64CON) },
{ }
};
MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
@@ -2404,10 +2827,28 @@ static struct hid_driver nintendo_hid_driver = {
.probe = nintendo_hid_probe,
.remove = nintendo_hid_remove,
.raw_event = nintendo_hid_event,
+
+#ifdef CONFIG_PM
+ .resume = nintendo_hid_resume,
+ .suspend = nintendo_hid_suspend,
+#endif
};
-module_hid_driver(nintendo_hid_driver);
+static int __init nintendo_init(void)
+{
+ return hid_register_driver(&nintendo_hid_driver);
+}
+
+static void __exit nintendo_exit(void)
+{
+ hid_unregister_driver(&nintendo_hid_driver);
+ ida_destroy(&nintendo_player_id_allocator);
+}
+
+module_init(nintendo_init);
+module_exit(nintendo_exit);
MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
+MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
-
diff --git a/drivers/hid/hid-nti.c b/drivers/hid/hid-nti.c
index 1952e9ca5f45..03f7dd491228 100644
--- a/drivers/hid/hid-nti.c
+++ b/drivers/hid/hid-nti.c
@@ -29,7 +29,7 @@ MODULE_DESCRIPTION("HID driver for Network Technologies USB-SUN keyboard adapter
/*
* NTI Sun keyboard adapter has wrong logical maximum in report descriptor
*/
-static __u8 *nti_usbsun_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *nti_usbsun_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 60 && rdesc[53] == 0x65 && rdesc[59] == 0x65) {
diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c
index b5d26f03fe6b..a7f10c45f62b 100644
--- a/drivers/hid/hid-ntrig.c
+++ b/drivers/hid/hid-ntrig.c
@@ -142,10 +142,13 @@ static void ntrig_report_version(struct hid_device *hdev)
int ret;
char buf[20];
struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- unsigned char *data = kmalloc(8, GFP_KERNEL);
+ unsigned char *data __free(kfree) = kmalloc(8, GFP_KERNEL);
+
+ if (!hid_is_usb(hdev))
+ return;
if (!data)
- goto err_free;
+ return;
ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
USB_REQ_CLEAR_FEATURE,
@@ -160,9 +163,6 @@ static void ntrig_report_version(struct hid_device *hdev)
hid_info(hdev, "Firmware version: %s (%02x%02x %02x%02x)\n",
buf, data[2], data[3], data[4], data[5]);
}
-
-err_free:
- kfree(data);
}
static ssize_t show_phys_width(struct device *dev,
@@ -1029,4 +1029,5 @@ static struct hid_driver ntrig_driver = {
};
module_hid_driver(ntrig_driver);
+MODULE_DESCRIPTION("HID driver for N-Trig touchscreens");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c
index a928ad2be62d..b0c40a245bf8 100644
--- a/drivers/hid/hid-nvidia-shield.c
+++ b/drivers/hid/hid-nvidia-shield.c
@@ -6,11 +6,15 @@
*/
#include <linux/hid.h>
+#include <linux/idr.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
+#include <linux/jiffies.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/power_supply.h>
#include <linux/spinlock.h>
+#include <linux/timer.h>
#include <linux/workqueue.h>
#include "hid-ids.h"
@@ -30,6 +34,8 @@ enum {
enum {
SHIELD_FW_VERSION_INITIALIZED = 0,
SHIELD_BOARD_INFO_INITIALIZED,
+ SHIELD_BATTERY_STATS_INITIALIZED,
+ SHIELD_CHARGER_STATE_INITIALIZED,
};
enum {
@@ -37,6 +43,7 @@ enum {
THUNDERSTRIKE_BOARD_INFO_UPDATE,
THUNDERSTRIKE_HAPTICS_UPDATE,
THUNDERSTRIKE_LED_UPDATE,
+ THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE,
};
enum {
@@ -48,10 +55,46 @@ enum {
enum {
THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1,
THUNDERSTRIKE_HOSTCMD_ID_LED = 6,
+ THUNDERSTRIKE_HOSTCMD_ID_BATTERY,
THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16,
THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53,
THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57,
- THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58,
+ THUNDERSTRIKE_HOSTCMD_ID_CHARGER,
+};
+
+struct power_supply_dev {
+ struct power_supply *psy;
+ struct power_supply_desc desc;
+};
+
+struct thunderstrike_psy_prop_values {
+ int voltage_min;
+ int voltage_now;
+ int voltage_avg;
+ int voltage_boot;
+ int capacity;
+ int status;
+ int charge_type;
+ int temp;
+};
+
+static const enum power_supply_property thunderstrike_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_BOOT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_MIN,
+ POWER_SUPPLY_PROP_TEMP_MAX,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
};
enum thunderstrike_led_state {
@@ -60,6 +103,38 @@ enum thunderstrike_led_state {
} __packed;
static_assert(sizeof(enum thunderstrike_led_state) == 1);
+struct thunderstrike_hostcmd_battery {
+ __le16 voltage_avg;
+ u8 reserved_at_10;
+ __le16 thermistor;
+ __le16 voltage_min;
+ __le16 voltage_boot;
+ __le16 voltage_now;
+ u8 capacity;
+} __packed;
+
+enum thunderstrike_charger_type {
+ THUNDERSTRIKE_CHARGER_TYPE_NONE = 0,
+ THUNDERSTRIKE_CHARGER_TYPE_TRICKLE,
+ THUNDERSTRIKE_CHARGER_TYPE_NORMAL,
+} __packed;
+static_assert(sizeof(enum thunderstrike_charger_type) == 1);
+
+enum thunderstrike_charger_state {
+ THUNDERSTRIKE_CHARGER_STATE_UNKNOWN = 0,
+ THUNDERSTRIKE_CHARGER_STATE_DISABLED,
+ THUNDERSTRIKE_CHARGER_STATE_CHARGING,
+ THUNDERSTRIKE_CHARGER_STATE_FULL,
+ THUNDERSTRIKE_CHARGER_STATE_FAILED = 8,
+} __packed;
+static_assert(sizeof(enum thunderstrike_charger_state) == 1);
+
+struct thunderstrike_hostcmd_charger {
+ u8 connected;
+ enum thunderstrike_charger_type type;
+ enum thunderstrike_charger_state state;
+} __packed;
+
struct thunderstrike_hostcmd_board_info {
__le16 revision;
__le16 serial[7];
@@ -80,6 +155,8 @@ struct thunderstrike_hostcmd_resp_report {
struct thunderstrike_hostcmd_haptics motors;
__le16 fw_version;
enum thunderstrike_led_state led_state;
+ struct thunderstrike_hostcmd_battery battery;
+ struct thunderstrike_hostcmd_charger charger;
u8 payload[30];
} __packed;
} __packed;
@@ -109,6 +186,7 @@ static_assert(sizeof(struct thunderstrike_hostcmd_req_report) ==
/* Common struct for shield accessories. */
struct shield_device {
struct hid_device *hdev;
+ struct power_supply_dev battery_dev;
unsigned long initialized_flags;
const char *codename;
@@ -119,9 +197,17 @@ struct shield_device {
} board_info;
};
+/*
+ * Non-trivial to uniquely identify Thunderstrike controllers at initialization
+ * time. Use an ID allocator to help with this.
+ */
+static DEFINE_IDA(thunderstrike_ida);
+
struct thunderstrike {
struct shield_device base;
+ int id;
+
/* Sub-devices */
struct input_dev *haptics_dev;
struct led_classdev led_dev;
@@ -133,6 +219,9 @@ struct thunderstrike {
spinlock_t haptics_update_lock;
u8 led_state : 1;
enum thunderstrike_led_state led_value;
+ struct thunderstrike_psy_prop_values psy_stats;
+ spinlock_t psy_stats_lock;
+ struct timer_list psy_stats_timer;
struct work_struct hostcmd_req_work;
};
@@ -164,7 +253,7 @@ static struct input_dev *shield_allocate_input_dev(struct hid_device *hdev,
idev->id.product = hdev->product;
idev->id.version = hdev->version;
idev->uniq = hdev->uniq;
- idev->name = devm_kasprintf(&idev->dev, GFP_KERNEL, "%s %s", hdev->name,
+ idev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name,
name_suffix);
if (!idev->name)
goto err_name;
@@ -194,7 +283,9 @@ static struct input_dev *shield_haptics_create(
return haptics;
input_set_capability(haptics, EV_FF, FF_RUMBLE);
- input_ff_create_memless(haptics, NULL, play_effect);
+ ret = input_ff_create_memless(haptics, NULL, play_effect);
+ if (ret)
+ goto err;
ret = input_register_device(haptics);
if (ret)
@@ -247,6 +338,16 @@ static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work)
thunderstrike_send_hostcmd_request(ts);
}
+ if (test_and_clear_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags)) {
+ thunderstrike_hostcmd_req_report_init(
+ report, THUNDERSTRIKE_HOSTCMD_ID_BATTERY);
+ thunderstrike_send_hostcmd_request(ts);
+
+ thunderstrike_hostcmd_req_report_init(
+ report, THUNDERSTRIKE_HOSTCMD_ID_CHARGER);
+ thunderstrike_send_hostcmd_request(ts);
+ }
+
if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) {
thunderstrike_hostcmd_req_report_init(
report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO);
@@ -352,6 +453,93 @@ static void thunderstrike_led_set_brightness(struct led_classdev *led,
schedule_work(&ts->hostcmd_req_work);
}
+static int thunderstrike_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct shield_device *shield_dev = power_supply_get_drvdata(psy);
+ struct thunderstrike_psy_prop_values prop_values;
+ struct thunderstrike *ts;
+ int ret = 0;
+
+ ts = container_of(shield_dev, struct thunderstrike, base);
+ spin_lock(&ts->psy_stats_lock);
+ prop_values = ts->psy_stats;
+ spin_unlock(&ts->psy_stats_lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = prop_values.status;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = prop_values.charge_type;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = prop_values.voltage_min;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = 2900000; /* 2.9 V */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = 2200000; /* 2.2 V */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = prop_values.voltage_now;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = prop_values.voltage_avg;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
+ val->intval = prop_values.voltage_boot;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = prop_values.capacity;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = prop_values.temp;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ val->intval = 0; /* 0 C */
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ val->intval = 400; /* 40 C */
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ val->intval = 15; /* 1.5 C */
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ val->intval = 380; /* 38 C */
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static inline void thunderstrike_request_psy_stats(struct thunderstrike *ts)
+{
+ set_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags);
+ schedule_work(&ts->hostcmd_req_work);
+}
+
+static void thunderstrike_psy_stats_timer_handler(struct timer_list *timer)
+{
+ struct thunderstrike *ts =
+ container_of(timer, struct thunderstrike, psy_stats_timer);
+
+ thunderstrike_request_psy_stats(ts);
+ /* Query battery statistics from device every five minutes */
+ mod_timer(timer, jiffies + 300 * HZ);
+}
+
static void
thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev,
__le16 fw_version)
@@ -416,13 +604,138 @@ thunderstrike_parse_led_payload(struct shield_device *shield_dev,
hid_dbg(shield_dev->hdev, "Thunderstrike led HOSTCMD response, 0x%02X\n", led_state);
}
+static void thunderstrike_parse_battery_payload(
+ struct shield_device *shield_dev,
+ struct thunderstrike_hostcmd_battery *battery)
+{
+ struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
+ u16 hostcmd_voltage_boot = le16_to_cpu(battery->voltage_boot);
+ u16 hostcmd_voltage_avg = le16_to_cpu(battery->voltage_avg);
+ u16 hostcmd_voltage_min = le16_to_cpu(battery->voltage_min);
+ u16 hostcmd_voltage_now = le16_to_cpu(battery->voltage_now);
+ u16 hostcmd_thermistor = le16_to_cpu(battery->thermistor);
+ int voltage_boot, voltage_avg, voltage_min, voltage_now;
+ struct hid_device *hdev = shield_dev->hdev;
+ u8 capacity = battery->capacity;
+ int temp;
+
+ /* Convert thunderstrike device values to µV and tenths of degree Celsius */
+ voltage_boot = hostcmd_voltage_boot * 1000;
+ voltage_avg = hostcmd_voltage_avg * 1000;
+ voltage_min = hostcmd_voltage_min * 1000;
+ voltage_now = hostcmd_voltage_now * 1000;
+ temp = (1378 - (int)hostcmd_thermistor) * 10 / 19;
+
+ /* Copy converted values */
+ spin_lock(&ts->psy_stats_lock);
+ ts->psy_stats.voltage_boot = voltage_boot;
+ ts->psy_stats.voltage_avg = voltage_avg;
+ ts->psy_stats.voltage_min = voltage_min;
+ ts->psy_stats.voltage_now = voltage_now;
+ ts->psy_stats.capacity = capacity;
+ ts->psy_stats.temp = temp;
+ spin_unlock(&ts->psy_stats_lock);
+
+ set_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags);
+
+ hid_dbg(hdev,
+ "Thunderstrike battery HOSTCMD response, voltage_avg: %u voltage_now: %u\n",
+ hostcmd_voltage_avg, hostcmd_voltage_now);
+ hid_dbg(hdev,
+ "Thunderstrike battery HOSTCMD response, voltage_boot: %u voltage_min: %u\n",
+ hostcmd_voltage_boot, hostcmd_voltage_min);
+ hid_dbg(hdev,
+ "Thunderstrike battery HOSTCMD response, thermistor: %u\n",
+ hostcmd_thermistor);
+ hid_dbg(hdev,
+ "Thunderstrike battery HOSTCMD response, capacity: %u%%\n",
+ capacity);
+}
+
+static void thunderstrike_parse_charger_payload(
+ struct shield_device *shield_dev,
+ struct thunderstrike_hostcmd_charger *charger)
+{
+ struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
+ int charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ struct hid_device *hdev = shield_dev->hdev;
+ int status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ switch (charger->type) {
+ case THUNDERSTRIKE_CHARGER_TYPE_NONE:
+ charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case THUNDERSTRIKE_CHARGER_TYPE_TRICKLE:
+ charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case THUNDERSTRIKE_CHARGER_TYPE_NORMAL:
+ charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ break;
+ default:
+ hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD type, %u\n",
+ charger->type);
+ break;
+ }
+
+ switch (charger->state) {
+ case THUNDERSTRIKE_CHARGER_STATE_UNKNOWN:
+ status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ case THUNDERSTRIKE_CHARGER_STATE_DISABLED:
+ /* Indicates charger is disconnected */
+ break;
+ case THUNDERSTRIKE_CHARGER_STATE_CHARGING:
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case THUNDERSTRIKE_CHARGER_STATE_FULL:
+ status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case THUNDERSTRIKE_CHARGER_STATE_FAILED:
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ hid_err(hdev, "Thunderstrike device failed to charge\n");
+ break;
+ default:
+ hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD state, %u\n",
+ charger->state);
+ break;
+ }
+
+ if (!charger->connected)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ spin_lock(&ts->psy_stats_lock);
+ ts->psy_stats.charge_type = charge_type;
+ ts->psy_stats.status = status;
+ spin_unlock(&ts->psy_stats_lock);
+
+ set_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags);
+
+ hid_dbg(hdev,
+ "Thunderstrike charger HOSTCMD response, connected: %u, type: %u, state: %u\n",
+ charger->connected, charger->type, charger->state);
+}
+
+static inline void thunderstrike_device_init_info(struct shield_device *shield_dev)
+{
+ struct thunderstrike *ts =
+ container_of(shield_dev, struct thunderstrike, base);
+
+ if (!test_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags))
+ thunderstrike_request_firmware_version(ts);
+
+ if (!test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags))
+ thunderstrike_request_board_info(ts);
+
+ if (!test_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags) ||
+ !test_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags))
+ thunderstrike_psy_stats_timer_handler(&ts->psy_stats_timer);
+}
+
static int thunderstrike_parse_report(struct shield_device *shield_dev,
struct hid_report *report, u8 *data,
int size)
{
struct thunderstrike_hostcmd_resp_report *hostcmd_resp_report;
- struct thunderstrike *ts =
- container_of(shield_dev, struct thunderstrike, base);
struct hid_device *hdev = shield_dev->hdev;
switch (report->id) {
@@ -445,6 +758,10 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
case THUNDERSTRIKE_HOSTCMD_ID_LED:
thunderstrike_parse_led_payload(shield_dev, hostcmd_resp_report->led_state);
break;
+ case THUNDERSTRIKE_HOSTCMD_ID_BATTERY:
+ thunderstrike_parse_battery_payload(shield_dev,
+ &hostcmd_resp_report->battery);
+ break;
case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO:
thunderstrike_parse_board_info_payload(
shield_dev, &hostcmd_resp_report->board_info);
@@ -453,14 +770,17 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev,
thunderstrike_parse_haptics_payload(
shield_dev, &hostcmd_resp_report->motors);
break;
-
case THUNDERSTRIKE_HOSTCMD_ID_USB_INIT:
- case THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT:
/* May block HOSTCMD requests till received initially */
- thunderstrike_request_firmware_version(ts);
- thunderstrike_request_board_info(ts);
- /* Only HOSTCMD that can be triggered without a request */
- return 0;
+ thunderstrike_device_init_info(shield_dev);
+ break;
+ case THUNDERSTRIKE_HOSTCMD_ID_CHARGER:
+ /* May block HOSTCMD requests till received initially */
+ thunderstrike_device_init_info(shield_dev);
+
+ thunderstrike_parse_charger_payload(
+ shield_dev, &hostcmd_resp_report->charger);
+ break;
default:
hid_warn(hdev,
"Unhandled Thunderstrike HOSTCMD id %d\n",
@@ -480,15 +800,64 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts)
{
struct led_classdev *led = &ts->led_dev;
- led->name = "thunderstrike:blue:led";
+ led->name = devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL,
+ "thunderstrike%d:blue:led", ts->id);
+ if (!led->name)
+ return -ENOMEM;
led->max_brightness = 1;
- led->flags = LED_CORE_SUSPENDRESUME;
+ led->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
led->brightness_get = &thunderstrike_led_get_brightness;
led->brightness_set = &thunderstrike_led_set_brightness;
return led_classdev_register(&ts->base.hdev->dev, led);
}
+static inline int thunderstrike_psy_create(struct shield_device *shield_dev)
+{
+ struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base);
+ struct power_supply_config psy_cfg = { .drv_data = shield_dev, };
+ struct hid_device *hdev = shield_dev->hdev;
+ int ret;
+
+ /*
+ * Set an initial capacity and temperature value to avoid prematurely
+ * triggering alerts. Will be replaced by values queried from initial
+ * HOSTCMD requests.
+ */
+ ts->psy_stats.capacity = 100;
+ ts->psy_stats.temp = 182;
+
+ shield_dev->battery_dev.desc.properties = thunderstrike_battery_props;
+ shield_dev->battery_dev.desc.num_properties =
+ ARRAY_SIZE(thunderstrike_battery_props);
+ shield_dev->battery_dev.desc.get_property = thunderstrike_battery_get_property;
+ shield_dev->battery_dev.desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ shield_dev->battery_dev.desc.name =
+ devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL,
+ "thunderstrike_%d", ts->id);
+ if (!shield_dev->battery_dev.desc.name)
+ return -ENOMEM;
+
+ shield_dev->battery_dev.psy = power_supply_register(
+ &hdev->dev, &shield_dev->battery_dev.desc, &psy_cfg);
+ if (IS_ERR(shield_dev->battery_dev.psy)) {
+ hid_err(hdev, "Failed to register Thunderstrike battery device\n");
+ return PTR_ERR(shield_dev->battery_dev.psy);
+ }
+
+ ret = power_supply_powers(shield_dev->battery_dev.psy, &hdev->dev);
+ if (ret) {
+ hid_err(hdev, "Failed to associate battery device to Thunderstrike\n");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ power_supply_unregister(shield_dev->battery_dev.psy);
+ return ret;
+}
+
static struct shield_device *thunderstrike_create(struct hid_device *hdev)
{
struct shield_device *shield_dev;
@@ -509,26 +878,56 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev)
shield_dev->codename = "Thunderstrike";
spin_lock_init(&ts->haptics_update_lock);
+ spin_lock_init(&ts->psy_stats_lock);
INIT_WORK(&ts->hostcmd_req_work, thunderstrike_hostcmd_req_work_handler);
hid_set_drvdata(hdev, shield_dev);
+ ts->id = ida_alloc(&thunderstrike_ida, GFP_KERNEL);
+ if (ts->id < 0)
+ return ERR_PTR(ts->id);
+
+ ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect);
+ if (IS_ERR(ts->haptics_dev)) {
+ hid_err(hdev, "Failed to create Thunderstrike haptics instance\n");
+ ret = PTR_ERR(ts->haptics_dev);
+ goto err_id;
+ }
+
+ ret = thunderstrike_psy_create(shield_dev);
+ if (ret) {
+ hid_err(hdev, "Failed to create Thunderstrike power supply instance\n");
+ goto err_haptics;
+ }
+
ret = thunderstrike_led_create(ts);
if (ret) {
hid_err(hdev, "Failed to create Thunderstrike LED instance\n");
- return ERR_PTR(ret);
+ goto err_psy;
}
- ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect);
- if (IS_ERR(ts->haptics_dev))
- goto err;
+ timer_setup(&ts->psy_stats_timer, thunderstrike_psy_stats_timer_handler, 0);
hid_info(hdev, "Registered Thunderstrike controller\n");
return shield_dev;
-err:
+err_psy:
+ power_supply_unregister(shield_dev->battery_dev.psy);
+err_haptics:
+ if (ts->haptics_dev)
+ input_unregister_device(ts->haptics_dev);
+err_id:
+ ida_free(&thunderstrike_ida, ts->id);
+ return ERR_PTR(ret);
+}
+
+static void thunderstrike_destroy(struct thunderstrike *ts)
+{
led_classdev_unregister(&ts->led_dev);
- return ERR_CAST(ts->haptics_dev);
+ power_supply_unregister(ts->base.battery_dev.psy);
+ if (ts->haptics_dev)
+ input_unregister_device(ts->haptics_dev);
+ ida_free(&thunderstrike_ida, ts->id);
}
static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@@ -674,7 +1073,7 @@ static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id)
ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT);
if (ret) {
hid_err(hdev, "Failed to start HID device\n");
- goto err_haptics;
+ goto err_ts_create;
}
ret = hid_hw_open(hdev);
@@ -683,16 +1082,14 @@ static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err_stop;
}
- thunderstrike_request_firmware_version(ts);
- thunderstrike_request_board_info(ts);
+ thunderstrike_device_init_info(shield_dev);
return ret;
err_stop:
hid_hw_stop(hdev);
-err_haptics:
- if (ts->haptics_dev)
- input_unregister_device(ts->haptics_dev);
+err_ts_create:
+ thunderstrike_destroy(ts);
return ret;
}
@@ -704,9 +1101,8 @@ static void shield_remove(struct hid_device *hdev)
ts = container_of(dev, struct thunderstrike, base);
hid_hw_close(hdev);
- led_classdev_unregister(&ts->led_dev);
- if (ts->haptics_dev)
- input_unregister_device(ts->haptics_dev);
+ thunderstrike_destroy(ts);
+ timer_delete_sync(&ts->psy_stats_timer);
cancel_work_sync(&ts->hostcmd_req_work);
hid_hw_stop(hdev);
}
diff --git a/drivers/hid/hid-ortek.c b/drivers/hid/hid-ortek.c
index 9a4770d79c64..f27297269a7f 100644
--- a/drivers/hid/hid-ortek.c
+++ b/drivers/hid/hid-ortek.c
@@ -22,7 +22,7 @@
#include "hid-ids.h"
-static __u8 *ortek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *ortek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x01) {
@@ -51,4 +51,5 @@ static struct hid_driver ortek_driver = {
};
module_hid_driver(ortek_driver);
+MODULE_DESCRIPTION("HID driver for Ortek PKB-1700/WKB-2000/Skycable wireless keyboard and mouse trackpad");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-petalynx.c b/drivers/hid/hid-petalynx.c
index ea0af9f7ad90..1a986f077ce1 100644
--- a/drivers/hid/hid-petalynx.c
+++ b/drivers/hid/hid-petalynx.c
@@ -19,7 +19,7 @@
#include "hid-ids.h"
/* Petalynx Maxter Remote has maximum for consumer page set too low */
-static __u8 *pl_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *pl_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 62 && rdesc[39] == 0x2a && rdesc[40] == 0xf5 &&
@@ -102,4 +102,5 @@ static struct hid_driver pl_driver = {
};
module_hid_driver(pl_driver);
+MODULE_DESCRIPTION("HID driver for some petalynx \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-picolcd_backlight.c b/drivers/hid/hid-picolcd_backlight.c
index 5bd2a8c4bbd6..4b43b64537a3 100644
--- a/drivers/hid/hid-picolcd_backlight.c
+++ b/drivers/hid/hid-picolcd_backlight.c
@@ -9,7 +9,6 @@
#include <linux/hid.h>
-#include <linux/fb.h>
#include <linux/backlight.h>
#include "hid-picolcd.h"
@@ -32,22 +31,17 @@ static int picolcd_set_brightness(struct backlight_device *bdev)
data->lcd_brightness = bdev->props.brightness & 0x0ff;
data->lcd_power = bdev->props.power;
spin_lock_irqsave(&data->lock, flags);
- hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0);
+ hid_set_field(report->field[0], 0,
+ data->lcd_power == BACKLIGHT_POWER_ON ? data->lcd_brightness : 0);
if (!(data->status & PICOLCD_FAILED))
hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&data->lock, flags);
return 0;
}
-static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb)
-{
- return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev));
-}
-
static const struct backlight_ops picolcd_blops = {
.update_status = picolcd_set_brightness,
.get_brightness = picolcd_get_brightness,
- .check_fb = picolcd_check_bl_fb,
};
int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report)
@@ -101,7 +95,7 @@ void picolcd_suspend_backlight(struct picolcd_data *data)
if (!data->backlight)
return;
- data->backlight->props.power = FB_BLANK_POWERDOWN;
+ data->backlight->props.power = BACKLIGHT_POWER_OFF;
picolcd_set_brightness(data->backlight);
data->lcd_power = data->backlight->props.power = bl_power;
}
diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c
index bbda231a7ce3..297103be3381 100644
--- a/drivers/hid/hid-picolcd_core.c
+++ b/drivers/hid/hid-picolcd_core.c
@@ -256,9 +256,9 @@ static ssize_t picolcd_operation_mode_show(struct device *dev,
struct picolcd_data *data = dev_get_drvdata(dev);
if (data->status & PICOLCD_BOOTLOADER)
- return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
+ return sysfs_emit(buf, "[bootloader] lcd\n");
else
- return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
+ return sysfs_emit(buf, "bootloader [lcd]\n");
}
static ssize_t picolcd_operation_mode_store(struct device *dev,
@@ -301,7 +301,7 @@ static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
{
struct picolcd_data *data = dev_get_drvdata(dev);
- return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
+ return sysfs_emit(buf, "%hu\n", data->opmode_delay);
}
static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
@@ -474,11 +474,6 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
if (error)
goto err;
- /* Set up the framebuffer device */
- error = picolcd_init_framebuffer(data);
- if (error)
- goto err;
-
/* Setup lcd class device */
error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev));
if (error)
@@ -489,6 +484,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
if (error)
goto err;
+ /* Set up the framebuffer device */
+ error = picolcd_init_framebuffer(data);
+ if (error)
+ goto err;
+
/* Setup the LED class devices */
error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev));
if (error)
@@ -502,9 +502,9 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
return 0;
err:
picolcd_exit_leds(data);
+ picolcd_exit_framebuffer(data);
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
- picolcd_exit_framebuffer(data);
picolcd_exit_cir(data);
picolcd_exit_keys(data);
return error;
@@ -623,9 +623,9 @@ static void picolcd_remove(struct hid_device *hdev)
/* Cleanup LED */
picolcd_exit_leds(data);
/* Clean up the framebuffer */
+ picolcd_exit_framebuffer(data);
picolcd_exit_backlight(data);
picolcd_exit_lcd(data);
- picolcd_exit_framebuffer(data);
/* Cleanup input */
picolcd_exit_cir(data);
picolcd_exit_keys(data);
diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c
index dabcd054dad9..8c28e982e09d 100644
--- a/drivers/hid/hid-picolcd_fb.c
+++ b/drivers/hid/hid-picolcd_fb.c
@@ -283,54 +283,6 @@ out:
mutex_unlock(&info->lock);
}
-/* Stub to call the system default and update the image on the picoLCD */
-static void picolcd_fb_fillrect(struct fb_info *info,
- const struct fb_fillrect *rect)
-{
- if (!info->par)
- return;
- sys_fillrect(info, rect);
-
- schedule_delayed_work(&info->deferred_work, 0);
-}
-
-/* Stub to call the system default and update the image on the picoLCD */
-static void picolcd_fb_copyarea(struct fb_info *info,
- const struct fb_copyarea *area)
-{
- if (!info->par)
- return;
- sys_copyarea(info, area);
-
- schedule_delayed_work(&info->deferred_work, 0);
-}
-
-/* Stub to call the system default and update the image on the picoLCD */
-static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
-{
- if (!info->par)
- return;
- sys_imageblit(info, image);
-
- schedule_delayed_work(&info->deferred_work, 0);
-}
-
-/*
- * this is the slow path from userspace. they can seek and write to
- * the fb. it's inefficient to do anything less than a full screen draw
- */
-static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
- size_t count, loff_t *ppos)
-{
- ssize_t ret;
- if (!info->par)
- return -ENODEV;
- ret = fb_sys_write(info, buf, count, ppos);
- if (ret >= 0)
- schedule_delayed_work(&info->deferred_work, 0);
- return ret;
-}
-
static int picolcd_fb_blank(int blank, struct fb_info *info)
{
/* We let fb notification do this for us via lcd/backlight device */
@@ -344,7 +296,7 @@ static void picolcd_fb_destroy(struct fb_info *info)
/* make sure no work is deferred */
fb_deferred_io_cleanup(info);
- /* No thridparty should ever unregister our framebuffer! */
+ /* No thirdparty should ever unregister our framebuffer! */
WARN_ON(fbdata->picolcd != NULL);
vfree((u8 *)info->fix.smem_start);
@@ -417,18 +369,31 @@ static int picolcd_set_par(struct fb_info *info)
return 0;
}
+static void picolcdfb_ops_damage_range(struct fb_info *info, off_t off, size_t len)
+{
+ if (!info->par)
+ return;
+ schedule_delayed_work(&info->deferred_work, 0);
+}
+
+static void picolcdfb_ops_damage_area(struct fb_info *info, u32 x, u32 y, u32 width, u32 height)
+{
+ if (!info->par)
+ return;
+ schedule_delayed_work(&info->deferred_work, 0);
+}
+
+FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(picolcdfb_ops,
+ picolcdfb_ops_damage_range,
+ picolcdfb_ops_damage_area)
+
static const struct fb_ops picolcdfb_ops = {
.owner = THIS_MODULE,
+ FB_DEFAULT_DEFERRED_OPS(picolcdfb_ops),
.fb_destroy = picolcd_fb_destroy,
- .fb_read = fb_sys_read,
- .fb_write = picolcd_fb_write,
.fb_blank = picolcd_fb_blank,
- .fb_fillrect = picolcd_fb_fillrect,
- .fb_copyarea = picolcd_fb_copyarea,
- .fb_imageblit = picolcd_fb_imageblit,
.fb_check_var = picolcd_fb_check_var,
.fb_set_par = picolcd_set_par,
- .fb_mmap = fb_deferred_io_mmap,
};
@@ -456,12 +421,10 @@ static ssize_t picolcd_fb_update_rate_show(struct device *dev,
size_t ret = 0;
for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
- if (ret >= PAGE_SIZE)
- break;
- else if (i == fb_update_rate)
- ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
+ if (i == fb_update_rate)
+ ret += sysfs_emit_at(buf, ret, "[%u] ", i);
else
- ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
+ ret += sysfs_emit_at(buf, ret, "%u ", i);
if (ret > 0)
buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
return ret;
@@ -527,7 +490,16 @@ int picolcd_init_framebuffer(struct picolcd_data *data)
info->var = picolcdfb_var;
info->fix = picolcdfb_fix;
info->fix.smem_len = PICOLCDFB_SIZE*8;
- info->flags = FBINFO_FLAG_DEFAULT;
+
+#ifdef CONFIG_FB_BACKLIGHT
+#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
+ info->bl_dev = data->backlight;
+#endif
+#endif
+
+#ifdef CONFIG_HID_PICOLCD_LCD
+ info->lcd_dev = data->lcd;
+#endif
fbdata = info->par;
spin_lock_init(&fbdata->lock);
@@ -541,6 +513,7 @@ int picolcd_init_framebuffer(struct picolcd_data *data)
dev_err(dev, "can't get a free page for framebuffer\n");
goto err_nomem;
}
+ info->flags |= FBINFO_VIRTFB;
info->screen_buffer = fbdata->bitmap;
info->fix.smem_start = (unsigned long)fbdata->bitmap;
memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
diff --git a/drivers/hid/hid-picolcd_lcd.c b/drivers/hid/hid-picolcd_lcd.c
index 0c4b76de8ae5..318f19eac0e7 100644
--- a/drivers/hid/hid-picolcd_lcd.c
+++ b/drivers/hid/hid-picolcd_lcd.c
@@ -41,15 +41,9 @@ static int picolcd_set_contrast(struct lcd_device *ldev, int contrast)
return 0;
}
-static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb)
-{
- return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev));
-}
-
-static struct lcd_ops picolcd_lcdops = {
+static const struct lcd_ops picolcd_lcdops = {
.get_contrast = picolcd_get_contrast,
.set_contrast = picolcd_set_contrast,
- .check_fb = picolcd_check_lcd_fb,
};
int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report)
diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c
index 93fb07ec3180..3c8827081dea 100644
--- a/drivers/hid/hid-pl.c
+++ b/drivers/hid/hid-pl.c
@@ -219,4 +219,5 @@ static struct hid_driver pl_driver = {
};
module_hid_driver(pl_driver);
+MODULE_DESCRIPTION("Force feedback support for PantherLord/GreenAsia based devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c
index 3d414ae194ac..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,28 +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_DOUBLE_KEY_TIMEOUT 5 /* 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,
@@ -56,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:
@@ -93,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,
@@ -103,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:
@@ -121,23 +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. */
- if (!value) /* Handle key presses only. */
- return 0;
+ /* 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;
- 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. */
+ 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;
- 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. */
}
-
return 0;
}
@@ -179,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);
@@ -193,23 +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, HID_ANY_ID) },
{ }
};
@@ -218,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-playstation.c b/drivers/hid/hid-playstation.c
index 8ac8f7b8e317..128aa6abd10b 100644
--- a/drivers/hid/hid-playstation.c
+++ b/drivers/hid/hid-playstation.c
@@ -5,7 +5,9 @@
* Copyright (c) 2020-2022 Sony Interactive Entertainment
*/
+#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/crc32.h>
#include <linux/device.h>
#include <linux/hid.h>
@@ -15,7 +17,7 @@
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
@@ -27,23 +29,28 @@ static DEFINE_IDA(ps_player_id_allocator);
#define HID_PLAYSTATION_VERSION_PATCH 0x8000
+enum PS_TYPE {
+ PS_TYPE_PS4_DUALSHOCK4,
+ PS_TYPE_PS5_DUALSENSE,
+};
+
/* Base class for playstation devices. */
struct ps_device {
struct list_head list;
struct hid_device *hdev;
- spinlock_t lock;
+ spinlock_t lock; /* Sync between event handler and workqueue */
- uint32_t player_id;
+ u32 player_id;
struct power_supply_desc battery_desc;
struct power_supply *battery;
- uint8_t battery_capacity;
+ u8 battery_capacity;
int battery_status;
const char *input_dev_name; /* Name of primary input device. */
- uint8_t mac_address[6]; /* Note: stored in little endian order. */
- uint32_t hw_version;
- uint32_t fw_version;
+ u8 mac_address[6]; /* Note: stored in little endian order. */
+ u32 hw_version;
+ u32 fw_version;
int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
void (*remove)(struct ps_device *dev);
@@ -105,41 +112,62 @@ struct ps_led_info {
#define DS_BUTTONS2_TOUCHPAD BIT(1)
#define DS_BUTTONS2_MIC_MUTE BIT(2)
-/* Status field of DualSense input report. */
-#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0)
-#define DS_STATUS_CHARGING GENMASK(7, 4)
-#define DS_STATUS_CHARGING_SHIFT 4
+/* Status fields of DualSense input report. */
+#define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
+#define DS_STATUS0_CHARGING GENMASK(7, 4)
+#define DS_STATUS1_HP_DETECT BIT(0)
+#define DS_STATUS1_MIC_DETECT BIT(1)
+#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT)
+#define DS_STATUS1_MIC_MUTE BIT(2)
/* Feature version from DualSense Firmware Info report. */
-#define DS_FEATURE_VERSION(major, minor) ((major & 0xff) << 8 | (minor & 0xff))
-
+#define DS_FEATURE_VERSION_MINOR GENMASK(7, 0)
+#define DS_FEATURE_VERSION_MAJOR GENMASK(15, 8)
+#define DS_FEATURE_VERSION(major, minor) (FIELD_PREP(DS_FEATURE_VERSION_MAJOR, major) | \
+ FIELD_PREP(DS_FEATURE_VERSION_MINOR, minor))
/*
* Status of a DualSense touch point contact.
* Contact IDs, with highest bit set are 'inactive'
* and any associated data is then invalid.
*/
-#define DS_TOUCH_POINT_INACTIVE BIT(7)
+#define DS_TOUCH_POINT_INACTIVE BIT(7)
+#define DS_TOUCH_POINT_X_LO GENMASK(7, 0)
+#define DS_TOUCH_POINT_X_HI GENMASK(11, 8)
+#define DS_TOUCH_POINT_X(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_X_HI, hi) | \
+ FIELD_PREP(DS_TOUCH_POINT_X_LO, lo))
+#define DS_TOUCH_POINT_Y_LO GENMASK(3, 0)
+#define DS_TOUCH_POINT_Y_HI GENMASK(11, 4)
+#define DS_TOUCH_POINT_Y(hi, lo) (FIELD_PREP(DS_TOUCH_POINT_Y_HI, hi) | \
+ FIELD_PREP(DS_TOUCH_POINT_Y_LO, lo))
/* Magic value required in tag field of Bluetooth output report. */
-#define DS_OUTPUT_TAG 0x10
+#define DS_OUTPUT_TAG 0x10
+#define DS_OUTPUT_SEQ_TAG GENMASK(3, 0)
+#define DS_OUTPUT_SEQ_NO GENMASK(7, 4)
/* Flags for DualSense output report. */
-#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
-#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
-#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0)
-#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1)
-#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2)
-#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
-#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
-#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
-#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
-#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
-#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
+#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
+#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
+#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5)
+#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6)
+#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7)
+#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0)
+#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1)
+#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2)
+#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
+#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
+#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7)
+#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
+#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
+#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4)
+#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0)
+#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
+#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
/* DualSense hardware limits */
#define DS_ACC_RES_PER_G 8192
-#define DS_ACC_RANGE (4*DS_ACC_RES_PER_G)
+#define DS_ACC_RANGE (4 * DS_ACC_RES_PER_G)
#define DS_GYRO_RES_PER_DEG_S 1024
-#define DS_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S)
+#define DS_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S)
#define DS_TOUCHPAD_WIDTH 1920
#define DS_TOUCHPAD_HEIGHT 1080
@@ -148,9 +176,10 @@ struct dualsense {
struct input_dev *gamepad;
struct input_dev *sensors;
struct input_dev *touchpad;
+ struct input_dev *jack;
/* Update version is used as a feature/capability version. */
- uint16_t update_version;
+ u16 update_version;
/* Calibration data for accelerometer and gyroscope. */
struct ps_calibration_data accel_calib_data[3];
@@ -158,21 +187,26 @@ struct dualsense {
/* Timestamp for sensor data */
bool sensor_timestamp_initialized;
- uint32_t prev_sensor_timestamp;
- uint32_t sensor_timestamp_us;
+ u32 prev_sensor_timestamp;
+ u32 sensor_timestamp_us;
/* Compatible rumble state */
bool use_vibration_v2;
bool update_rumble;
- uint8_t motor_left;
- uint8_t motor_right;
+ u8 motor_left;
+ u8 motor_right;
/* RGB lightbar */
struct led_classdev_mc lightbar;
bool update_lightbar;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+
+ /* Audio Jack plugged state */
+ u8 plugged_state;
+ u8 prev_plugged_state;
+ bool prev_plugged_state_valid;
/* Microphone */
bool update_mic_mute;
@@ -181,90 +215,94 @@ struct dualsense {
/* Player leds */
bool update_player_leds;
- uint8_t player_leds_state;
+ u8 player_leds_state;
struct led_classdev player_leds[5];
struct work_struct output_worker;
bool output_worker_initialized;
void *output_report_dmabuf;
- uint8_t output_seq; /* Sequence number for output report. */
+ u8 output_seq; /* Sequence number for output report. */
};
struct dualsense_touch_point {
- uint8_t contact;
- uint8_t x_lo;
- uint8_t x_hi:4, y_lo:4;
- uint8_t y_hi;
+ u8 contact;
+ u8 x_lo;
+ u8 x_hi:4, y_lo:4;
+ u8 y_hi;
} __packed;
static_assert(sizeof(struct dualsense_touch_point) == 4);
/* Main DualSense input report excluding any BT/USB specific headers. */
struct dualsense_input_report {
- uint8_t x, y;
- uint8_t rx, ry;
- uint8_t z, rz;
- uint8_t seq_number;
- uint8_t buttons[4];
- uint8_t reserved[4];
+ u8 x, y;
+ u8 rx, ry;
+ u8 z, rz;
+ u8 seq_number;
+ u8 buttons[4];
+ u8 reserved[4];
/* Motion sensors */
__le16 gyro[3]; /* x, y, z */
__le16 accel[3]; /* x, y, z */
__le32 sensor_timestamp;
- uint8_t reserved2;
+ u8 reserved2;
/* Touchpad */
struct dualsense_touch_point points[2];
- uint8_t reserved3[12];
- uint8_t status;
- uint8_t reserved4[10];
+ u8 reserved3[12];
+ u8 status[3];
+ u8 reserved4[8];
} __packed;
/* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */
static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1);
/* Common data between DualSense BT/USB main output report. */
struct dualsense_output_report_common {
- uint8_t valid_flag0;
- uint8_t valid_flag1;
+ u8 valid_flag0;
+ u8 valid_flag1;
/* For DualShock 4 compatibility mode. */
- uint8_t motor_right;
- uint8_t motor_left;
+ u8 motor_right;
+ u8 motor_left;
/* Audio controls */
- uint8_t reserved[4];
- uint8_t mute_button_led;
+ u8 headphone_volume; /* 0x0 - 0x7f */
+ u8 speaker_volume; /* 0x0 - 0xff */
+ u8 mic_volume; /* 0x0 - 0x40 */
+ u8 audio_control;
+ u8 mute_button_led;
- uint8_t power_save_control;
- uint8_t reserved2[28];
+ u8 power_save_control;
+ u8 reserved2[27];
+ u8 audio_control2;
/* LEDs and lightbar */
- uint8_t valid_flag2;
- uint8_t reserved3[2];
- uint8_t lightbar_setup;
- uint8_t led_brightness;
- uint8_t player_leds;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
+ u8 valid_flag2;
+ u8 reserved3[2];
+ u8 lightbar_setup;
+ u8 led_brightness;
+ u8 player_leds;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
} __packed;
static_assert(sizeof(struct dualsense_output_report_common) == 47);
struct dualsense_output_report_bt {
- uint8_t report_id; /* 0x31 */
- uint8_t seq_tag;
- uint8_t tag;
+ u8 report_id; /* 0x31 */
+ u8 seq_tag;
+ u8 tag;
struct dualsense_output_report_common common;
- uint8_t reserved[24];
+ u8 reserved[24];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualsense_output_report_bt) == DS_OUTPUT_REPORT_BT_SIZE);
struct dualsense_output_report_usb {
- uint8_t report_id; /* 0x02 */
+ u8 report_id; /* 0x02 */
struct dualsense_output_report_common common;
- uint8_t reserved[15];
+ u8 reserved[15];
} __packed;
static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB_SIZE);
@@ -274,8 +312,8 @@ static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB
* This structure hide the differences between the two to simplify sending output reports.
*/
struct dualsense_output_report {
- uint8_t *data; /* Start of data */
- uint8_t len; /* Size of output report */
+ u8 *data; /* Start of data */
+ u8 len; /* Size of output report */
/* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
struct dualsense_output_report_bt *bt;
@@ -287,6 +325,8 @@ struct dualsense_output_report {
#define DS4_INPUT_REPORT_USB 0x01
#define DS4_INPUT_REPORT_USB_SIZE 64
+#define DS4_INPUT_REPORT_BT_MINIMAL 0x01
+#define DS4_INPUT_REPORT_BT_MINIMAL_SIZE 10
#define DS4_INPUT_REPORT_BT 0x11
#define DS4_INPUT_REPORT_BT_SIZE 78
#define DS4_OUTPUT_REPORT_USB 0x05
@@ -308,7 +348,9 @@ struct dualsense_output_report {
* Contact IDs, with highest bit set are 'inactive'
* and any associated data is then invalid.
*/
-#define DS4_TOUCH_POINT_INACTIVE BIT(7)
+#define DS4_TOUCH_POINT_INACTIVE BIT(7)
+#define DS4_TOUCH_POINT_X(hi, lo) DS_TOUCH_POINT_X(hi, lo)
+#define DS4_TOUCH_POINT_Y(hi, lo) DS_TOUCH_POINT_Y(hi, lo)
/* Status field of DualShock4 input report. */
#define DS4_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
@@ -316,7 +358,7 @@ struct dualsense_output_report {
/* Battery status within batery_status field. */
#define DS4_BATTERY_STATUS_FULL 11
/* Status1 bit2 contains dongle connection state:
- * 0 = connectd
+ * 0 = connected
* 1 = disconnected
*/
#define DS4_STATUS1_DONGLE_STATE BIT(2)
@@ -342,9 +384,9 @@ struct dualsense_output_report {
/* DualShock4 hardware limits */
#define DS4_ACC_RES_PER_G 8192
-#define DS4_ACC_RANGE (4*DS_ACC_RES_PER_G)
+#define DS4_ACC_RANGE (4 * DS_ACC_RES_PER_G)
#define DS4_GYRO_RES_PER_DEG_S 1024
-#define DS4_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S)
+#define DS4_GYRO_RANGE (2048 * DS_GYRO_RES_PER_DEG_S)
#define DS4_LIGHTBAR_MAX_BLINK 255 /* 255 centiseconds */
#define DS4_TOUCHPAD_WIDTH 1920
#define DS4_TOUCHPAD_HEIGHT 942
@@ -373,26 +415,26 @@ struct dualshock4 {
/* Timestamp for sensor data */
bool sensor_timestamp_initialized;
- uint32_t prev_sensor_timestamp;
- uint32_t sensor_timestamp_us;
+ u32 prev_sensor_timestamp;
+ u32 sensor_timestamp_us;
/* Bluetooth poll interval */
bool update_bt_poll_interval;
- uint8_t bt_poll_interval;
+ u8 bt_poll_interval;
bool update_rumble;
- uint8_t motor_left;
- uint8_t motor_right;
+ u8 motor_left;
+ u8 motor_right;
/* Lightbar leds */
bool update_lightbar;
bool update_lightbar_blink;
bool lightbar_enabled; /* For use by global LED control. */
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
- uint8_t lightbar_blink_on; /* In increments of 10ms. */
- uint8_t lightbar_blink_off; /* In increments of 10ms. */
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+ u8 lightbar_blink_on; /* In increments of 10ms. */
+ u8 lightbar_blink_off; /* In increments of 10ms. */
struct led_classdev lightbar_leds[4];
struct work_struct output_worker;
@@ -401,88 +443,88 @@ struct dualshock4 {
};
struct dualshock4_touch_point {
- uint8_t contact;
- uint8_t x_lo;
- uint8_t x_hi:4, y_lo:4;
- uint8_t y_hi;
+ u8 contact;
+ u8 x_lo;
+ u8 x_hi:4, y_lo:4;
+ u8 y_hi;
} __packed;
static_assert(sizeof(struct dualshock4_touch_point) == 4);
struct dualshock4_touch_report {
- uint8_t timestamp;
+ u8 timestamp;
struct dualshock4_touch_point points[2];
} __packed;
static_assert(sizeof(struct dualshock4_touch_report) == 9);
/* Main DualShock4 input report excluding any BT/USB specific headers. */
struct dualshock4_input_report_common {
- uint8_t x, y;
- uint8_t rx, ry;
- uint8_t buttons[3];
- uint8_t z, rz;
+ u8 x, y;
+ u8 rx, ry;
+ u8 buttons[3];
+ u8 z, rz;
/* Motion sensors */
__le16 sensor_timestamp;
- uint8_t sensor_temperature;
+ u8 sensor_temperature;
__le16 gyro[3]; /* x, y, z */
__le16 accel[3]; /* x, y, z */
- uint8_t reserved2[5];
+ u8 reserved2[5];
- uint8_t status[2];
- uint8_t reserved3;
+ u8 status[2];
+ u8 reserved3;
} __packed;
static_assert(sizeof(struct dualshock4_input_report_common) == 32);
struct dualshock4_input_report_usb {
- uint8_t report_id; /* 0x01 */
+ u8 report_id; /* 0x01 */
struct dualshock4_input_report_common common;
- uint8_t num_touch_reports;
+ u8 num_touch_reports;
struct dualshock4_touch_report touch_reports[3];
- uint8_t reserved[3];
+ u8 reserved[3];
} __packed;
static_assert(sizeof(struct dualshock4_input_report_usb) == DS4_INPUT_REPORT_USB_SIZE);
struct dualshock4_input_report_bt {
- uint8_t report_id; /* 0x11 */
- uint8_t reserved[2];
+ u8 report_id; /* 0x11 */
+ u8 reserved[2];
struct dualshock4_input_report_common common;
- uint8_t num_touch_reports;
+ u8 num_touch_reports;
struct dualshock4_touch_report touch_reports[4]; /* BT has 4 compared to 3 for USB */
- uint8_t reserved2[2];
+ u8 reserved2[2];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualshock4_input_report_bt) == DS4_INPUT_REPORT_BT_SIZE);
/* Common data between Bluetooth and USB DualShock4 output reports. */
struct dualshock4_output_report_common {
- uint8_t valid_flag0;
- uint8_t valid_flag1;
+ u8 valid_flag0;
+ u8 valid_flag1;
- uint8_t reserved;
+ u8 reserved;
- uint8_t motor_right;
- uint8_t motor_left;
+ u8 motor_right;
+ u8 motor_left;
- uint8_t lightbar_red;
- uint8_t lightbar_green;
- uint8_t lightbar_blue;
- uint8_t lightbar_blink_on;
- uint8_t lightbar_blink_off;
+ u8 lightbar_red;
+ u8 lightbar_green;
+ u8 lightbar_blue;
+ u8 lightbar_blink_on;
+ u8 lightbar_blink_off;
} __packed;
struct dualshock4_output_report_usb {
- uint8_t report_id; /* 0x5 */
+ u8 report_id; /* 0x5 */
struct dualshock4_output_report_common common;
- uint8_t reserved[21];
+ u8 reserved[21];
} __packed;
static_assert(sizeof(struct dualshock4_output_report_usb) == DS4_OUTPUT_REPORT_USB_SIZE);
struct dualshock4_output_report_bt {
- uint8_t report_id; /* 0x11 */
- uint8_t hw_control;
- uint8_t audio_control;
+ u8 report_id; /* 0x11 */
+ u8 hw_control;
+ u8 audio_control;
struct dualshock4_output_report_common common;
- uint8_t reserved[61];
+ u8 reserved[61];
__le32 crc32;
} __packed;
static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT_SIZE);
@@ -493,8 +535,8 @@ static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT
* This structure hide the differences between the two to simplify sending output reports.
*/
struct dualshock4_output_report {
- uint8_t *data; /* Start of data */
- uint8_t len; /* Size of output report */
+ u8 *data; /* Start of data */
+ u8 len; /* Size of output report */
/* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
struct dualshock4_output_report_bt *bt;
@@ -533,7 +575,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
static int dualshock4_get_calibration_data(struct dualshock4 *ds4);
static inline void dualsense_schedule_work(struct dualsense *ds);
static inline void dualshock4_schedule_work(struct dualshock4 *ds4);
-static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);
+static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue);
static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4);
/*
@@ -545,26 +587,25 @@ static int ps_devices_list_add(struct ps_device *dev)
{
struct ps_device *entry;
- mutex_lock(&ps_devices_lock);
+ guard(mutex)(&ps_devices_lock);
+
list_for_each_entry(entry, &ps_devices_list, list) {
if (!memcmp(entry->mac_address, dev->mac_address, sizeof(dev->mac_address))) {
hid_err(dev->hdev, "Duplicate device found for MAC address %pMR.\n",
- dev->mac_address);
- mutex_unlock(&ps_devices_lock);
+ dev->mac_address);
return -EEXIST;
}
}
list_add_tail(&dev->list, &ps_devices_list);
- mutex_unlock(&ps_devices_lock);
return 0;
}
static int ps_devices_list_remove(struct ps_device *dev)
{
- mutex_lock(&ps_devices_lock);
+ guard(mutex)(&ps_devices_lock);
+
list_del(&dev->list);
- mutex_unlock(&ps_devices_lock);
return 0;
}
@@ -586,7 +627,8 @@ static void ps_device_release_player_id(struct ps_device *dev)
dev->player_id = U32_MAX;
}
-static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix)
+static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev,
+ const char *name_suffix)
{
struct input_dev *input_dev;
@@ -601,8 +643,8 @@ static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const ch
input_dev->uniq = hdev->uniq;
if (name_suffix) {
- input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name,
- name_suffix);
+ input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s",
+ hdev->name, name_suffix);
if (!input_dev->name)
return ERR_PTR(-ENOMEM);
} else {
@@ -622,19 +664,18 @@ static enum power_supply_property ps_power_supply_props[] = {
};
static int ps_battery_get_property(struct power_supply *psy,
- enum power_supply_property psp,
- union power_supply_propval *val)
+ enum power_supply_property psp,
+ union power_supply_propval *val)
{
struct ps_device *dev = power_supply_get_drvdata(psy);
- uint8_t battery_capacity;
+ u8 battery_capacity;
int battery_status;
- unsigned long flags;
int ret = 0;
- spin_lock_irqsave(&dev->lock, flags);
- battery_capacity = dev->battery_capacity;
- battery_status = dev->battery_status;
- spin_unlock_irqrestore(&dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &dev->lock) {
+ battery_capacity = dev->battery_capacity;
+ battery_status = dev->battery_status;
+ }
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -668,7 +709,7 @@ static int ps_device_register_battery(struct ps_device *dev)
dev->battery_desc.num_properties = ARRAY_SIZE(ps_power_supply_props);
dev->battery_desc.get_property = ps_battery_get_property;
dev->battery_desc.name = devm_kasprintf(&dev->hdev->dev, GFP_KERNEL,
- "ps-controller-battery-%pMR", dev->mac_address);
+ "ps-controller-battery-%pMR", dev->mac_address);
if (!dev->battery_desc.name)
return -ENOMEM;
@@ -690,9 +731,9 @@ static int ps_device_register_battery(struct ps_device *dev)
}
/* Compute crc32 of HID data and compare against expected CRC. */
-static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t report_crc)
+static bool ps_check_crc32(u8 seed, u8 *data, size_t len, u32 report_crc)
{
- uint32_t crc;
+ u32 crc;
crc = crc32_le(0xFFFFFFFF, &seed, 1);
crc = ~crc32_le(crc, data, len);
@@ -700,8 +741,9 @@ static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t rep
return crc == report_crc;
}
-static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
- int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
+static struct input_dev *
+ps_gamepad_create(struct hid_device *hdev,
+ int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
{
struct input_dev *gamepad;
unsigned int i;
@@ -738,8 +780,8 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
return gamepad;
}
-static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size,
- bool check_crc)
+static int ps_get_report(struct hid_device *hdev, u8 report_id, u8 *buf,
+ size_t size, bool check_crc)
{
int ret;
@@ -762,8 +804,8 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
if (hdev->bus == BUS_BLUETOOTH && check_crc) {
/* Last 4 bytes contains crc32. */
- uint8_t crc_offset = size - 4;
- uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]);
+ u8 crc_offset = size - 4;
+ u32 report_crc = get_unaligned_le32(&buf[crc_offset]);
if (!ps_check_crc32(PS_FEATURE_CRC32_SEED, buf, crc_offset, report_crc)) {
hid_err(hdev, "CRC check failed for reportID=%d\n", report_id);
@@ -775,17 +817,20 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
}
static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
- const struct ps_led_info *led_info)
+ const struct ps_led_info *led_info)
{
int ret;
if (led_info->name) {
- led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
- "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s:%s",
+ ps_dev->input_dev_name, led_info->color,
+ led_info->name);
} else {
- /* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */
- led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
- "%s:%s", ps_dev->input_dev_name, led_info->color);
+ /* Backwards compatible mode for hid-sony, but not compliant
+ * with LED class spec.
+ */
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL, "%s:%s",
+ ps_dev->input_dev_name, led_info->color);
}
if (!led->name)
@@ -809,7 +854,7 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
/* Register a DualSense/DualShock4 RGB lightbar represented by a multicolor LED. */
static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc *lightbar_mc_dev,
- int (*brightness_set)(struct led_classdev *, enum led_brightness))
+ int (*brightness_set)(struct led_classdev *, enum led_brightness))
{
struct hid_device *hdev = ps_dev->hdev;
struct mc_subled *mc_led_info;
@@ -830,7 +875,7 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc
led_cdev = &lightbar_mc_dev->led_cdev;
led_cdev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s:rgb:indicator",
- ps_dev->input_dev_name);
+ ps_dev->input_dev_name);
if (!led_cdev->name)
return -ENOMEM;
led_cdev->brightness = 255;
@@ -846,8 +891,8 @@ static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc
return 0;
}
-static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range, int accel_res,
- int gyro_range, int gyro_res)
+static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_range,
+ int accel_res, int gyro_range, int gyro_res)
{
struct input_dev *sensors;
int ret;
@@ -883,8 +928,8 @@ static struct input_dev *ps_sensors_create(struct hid_device *hdev, int accel_ra
return sensors;
}
-static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width, int height,
- unsigned int num_contacts)
+static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width,
+ int height, unsigned int num_contacts)
{
struct input_dev *touchpad;
int ret;
@@ -911,9 +956,27 @@ static struct input_dev *ps_touchpad_create(struct hid_device *hdev, int width,
return touchpad;
}
+static struct input_dev *ps_headset_jack_create(struct hid_device *hdev)
+{
+ struct input_dev *jack;
+ int ret;
+
+ jack = ps_allocate_input_dev(hdev, "Headset Jack");
+ if (IS_ERR(jack))
+ return ERR_CAST(jack);
+
+ input_set_capability(jack, EV_SW, SW_HEADPHONE_INSERT);
+ input_set_capability(jack, EV_SW, SW_MICROPHONE_INSERT);
+
+ ret = input_register_device(jack);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return jack;
+}
+
static ssize_t firmware_version_show(struct device *dev,
- struct device_attribute
- *attr, char *buf)
+ struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct ps_device *ps_dev = hid_get_drvdata(hdev);
@@ -924,8 +987,7 @@ static ssize_t firmware_version_show(struct device *dev,
static DEVICE_ATTR_RO(firmware_version);
static ssize_t hardware_version_show(struct device *dev,
- struct device_attribute
- *attr, char *buf)
+ struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct ps_device *ps_dev = hid_get_drvdata(hdev);
@@ -956,14 +1018,14 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
int range_2g;
int ret = 0;
int i;
- uint8_t *buf;
+ u8 *buf;
buf = kzalloc(DS_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_CALIBRATION, buf,
- DS_FEATURE_REPORT_CALIBRATION_SIZE, true);
+ DS_FEATURE_REPORT_CALIBRATION_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense calibration info: %d\n", ret);
goto err_free;
@@ -994,19 +1056,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds->gyro_calib_data[0].abs_code = ABS_RX;
ds->gyro_calib_data[0].bias = 0;
- ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[0].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds->gyro_calib_data[1].abs_code = ABS_RY;
ds->gyro_calib_data[1].bias = 0;
- ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[1].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds->gyro_calib_data[2].abs_code = ABS_RZ;
ds->gyro_calib_data[2].bias = 0;
- ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
+ ds->gyro_calib_data[2].sens_numer = speed_2x * DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
@@ -1017,8 +1079,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
for (i = 0; i < ARRAY_SIZE(ds->gyro_calib_data); i++) {
if (ds->gyro_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
- ds->gyro_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds->gyro_calib_data[i].abs_code);
ds->gyro_calib_data[i].bias = 0;
ds->gyro_calib_data[i].sens_numer = DS_GYRO_RANGE;
ds->gyro_calib_data[i].sens_denom = S16_MAX;
@@ -1032,19 +1095,19 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
range_2g = acc_x_plus - acc_x_minus;
ds->accel_calib_data[0].abs_code = ABS_X;
ds->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
- ds->accel_calib_data[0].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[0].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[0].sens_denom = range_2g;
range_2g = acc_y_plus - acc_y_minus;
ds->accel_calib_data[1].abs_code = ABS_Y;
ds->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
- ds->accel_calib_data[1].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[1].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[1].sens_denom = range_2g;
range_2g = acc_z_plus - acc_z_minus;
ds->accel_calib_data[2].abs_code = ABS_Z;
ds->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
- ds->accel_calib_data[2].sens_numer = 2*DS_ACC_RES_PER_G;
+ ds->accel_calib_data[2].sens_numer = 2 * DS_ACC_RES_PER_G;
ds->accel_calib_data[2].sens_denom = range_2g;
/*
@@ -1054,8 +1117,9 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
for (i = 0; i < ARRAY_SIZE(ds->accel_calib_data); i++) {
if (ds->accel_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
- ds->accel_calib_data[i].abs_code);
+ hid_warn(hdev,
+ "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds->accel_calib_data[i].abs_code);
ds->accel_calib_data[i].bias = 0;
ds->accel_calib_data[i].sens_numer = DS_ACC_RANGE;
ds->accel_calib_data[i].sens_denom = S16_MAX;
@@ -1067,10 +1131,9 @@ err_free:
return ret;
}
-
static int dualsense_get_firmware_info(struct dualsense *ds)
{
- uint8_t *buf;
+ u8 *buf;
int ret;
buf = kzalloc(DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL);
@@ -1078,7 +1141,7 @@ static int dualsense_get_firmware_info(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_FIRMWARE_INFO, buf,
- DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true);
+ DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense firmware info: %d\n", ret);
goto err_free;
@@ -1103,7 +1166,7 @@ err_free:
static int dualsense_get_mac_address(struct dualsense *ds)
{
- uint8_t *buf;
+ u8 *buf;
int ret = 0;
buf = kzalloc(DS_FEATURE_REPORT_PAIRING_INFO_SIZE, GFP_KERNEL);
@@ -1111,7 +1174,7 @@ static int dualsense_get_mac_address(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf,
- DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true);
+ DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret);
goto err_free;
@@ -1125,11 +1188,11 @@ err_free:
}
static int dualsense_lightbar_set_brightness(struct led_classdev *cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct dualsense *ds = container_of(mc_cdev, struct dualsense, lightbar);
- uint8_t red, green, blue;
+ u8 red, green, blue;
led_mc_calc_color_components(mc_cdev, brightness);
red = mc_cdev->subled_info[0].brightness;
@@ -1152,27 +1215,25 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualsense *ds = hid_get_drvdata(hdev);
- unsigned long flags;
unsigned int led_index;
- spin_lock_irqsave(&ds->base.lock, flags);
-
- led_index = led - ds->player_leds;
- if (value == LED_OFF)
- ds->player_leds_state &= ~BIT(led_index);
- else
- ds->player_leds_state |= BIT(led_index);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ led_index = led - ds->player_leds;
+ if (value == LED_OFF)
+ ds->player_leds_state &= ~BIT(led_index);
+ else
+ ds->player_leds_state |= BIT(led_index);
- ds->update_player_leds = true;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ ds->update_player_leds = true;
+ }
dualsense_schedule_work(ds);
return 0;
}
-static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp,
- void *buf)
+static void dualsense_init_output_report(struct dualsense *ds,
+ struct dualsense_output_report *rp, void *buf)
{
struct hid_device *hdev = ds->base.hdev;
@@ -1187,7 +1248,8 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
* Highest 4-bit is a sequence number, which needs to be increased
* every report. Lowest 4-bit is tag and can be zero for now.
*/
- bt->seq_tag = (ds->output_seq << 4) | 0x0;
+ bt->seq_tag = FIELD_PREP(DS_OUTPUT_SEQ_NO, ds->output_seq) |
+ FIELD_PREP(DS_OUTPUT_SEQ_TAG, 0x0);
if (++ds->output_seq == 16)
ds->output_seq = 0;
@@ -1212,12 +1274,10 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
static inline void dualsense_schedule_work(struct dualsense *ds)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds->base.lock, flags);
- if (ds->output_worker_initialized)
- schedule_work(&ds->output_worker);
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ /* Using scoped_guard() instead of guard() to make sparse happy */
+ scoped_guard(spinlock_irqsave, &ds->base.lock)
+ if (ds->output_worker_initialized)
+ schedule_work(&ds->output_worker);
}
/*
@@ -1225,14 +1285,14 @@ static inline void dualsense_schedule_work(struct dualsense *ds)
* for Bluetooth reports.
*/
static void dualsense_send_output_report(struct dualsense *ds,
- struct dualsense_output_report *report)
+ struct dualsense_output_report *report)
{
struct hid_device *hdev = ds->base.hdev;
/* Bluetooth packets need to be signed with a CRC in the last 4 bytes. */
if (report->bt) {
- uint32_t crc;
- uint8_t seed = PS_OUTPUT_CRC32_SEED;
+ u32 crc;
+ u8 seed = PS_OUTPUT_CRC32_SEED;
crc = crc32_le(0xFFFFFFFF, &seed, 1);
crc = ~crc32_le(crc, report->data, report->len - 4);
@@ -1248,74 +1308,125 @@ static void dualsense_output_worker(struct work_struct *work)
struct dualsense *ds = container_of(work, struct dualsense, output_worker);
struct dualsense_output_report report;
struct dualsense_output_report_common *common;
- unsigned long flags;
dualsense_init_output_report(ds, &report, ds->output_report_dmabuf);
common = report.common;
- spin_lock_irqsave(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ if (ds->update_rumble) {
+ /* Select classic rumble style haptics and enable it. */
+ common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
+ if (ds->use_vibration_v2)
+ common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2;
+ else
+ common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
+ common->motor_left = ds->motor_left;
+ common->motor_right = ds->motor_right;
+ ds->update_rumble = false;
+ }
- if (ds->update_rumble) {
- /* Select classic rumble style haptics and enable it. */
- common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
- if (ds->use_vibration_v2)
- common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2;
- else
- common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
- common->motor_left = ds->motor_left;
- common->motor_right = ds->motor_right;
- ds->update_rumble = false;
- }
+ if (ds->update_lightbar) {
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE;
+ common->lightbar_red = ds->lightbar_red;
+ common->lightbar_green = ds->lightbar_green;
+ common->lightbar_blue = ds->lightbar_blue;
- if (ds->update_lightbar) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE;
- common->lightbar_red = ds->lightbar_red;
- common->lightbar_green = ds->lightbar_green;
- common->lightbar_blue = ds->lightbar_blue;
+ ds->update_lightbar = false;
+ }
- ds->update_lightbar = false;
- }
+ if (ds->update_player_leds) {
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE;
+ common->player_leds = ds->player_leds_state;
- if (ds->update_player_leds) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE;
- common->player_leds = ds->player_leds_state;
+ ds->update_player_leds = false;
+ }
- ds->update_player_leds = false;
- }
+ if (ds->plugged_state != ds->prev_plugged_state) {
+ u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT;
+
+ if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) {
+ common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE;
+ /*
+ * _--------> Output path setup in audio_flag0
+ * / _------> Headphone (HP) Left channel sink
+ * | / _----> Headphone (HP) Right channel sink
+ * | | / _--> Internal Speaker (SP) sink
+ * | | | /
+ * | | | | L/R - Left/Right channel source
+ * 0 L-R X X - Unrouted (muted) channel source
+ * 1 L-L X
+ * 2 L-L R
+ * 3 X-X R
+ */
+ if (val) {
+ /* Mute SP and route L+R channels to HP */
+ common->audio_control = 0;
+ } else {
+ /* Mute HP and route R channel to SP */
+ common->audio_control =
+ FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL,
+ 0x3);
+ /*
+ * Set SP hardware volume to 100%.
+ * Note the accepted range seems to be [0x3d..0x64]
+ */
+ common->valid_flag0 |=
+ DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE;
+ common->speaker_volume = 0x64;
+ /* Set SP preamp gain to +6dB */
+ common->valid_flag1 =
+ DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE;
+ common->audio_control2 =
+ FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN,
+ 0x2);
+ }
- if (ds->update_mic_mute) {
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
- common->mute_button_led = ds->mic_muted;
+ input_report_switch(ds->jack, SW_HEADPHONE_INSERT, val);
+ }
- if (ds->mic_muted) {
- /* Disable microphone */
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
- common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
- } else {
- /* Enable microphone */
- common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
- common->power_save_control &= ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ val = ds->plugged_state & DS_STATUS1_MIC_DETECT;
+ if (val != (ds->prev_plugged_state & DS_STATUS1_MIC_DETECT))
+ input_report_switch(ds->jack, SW_MICROPHONE_INSERT, val);
+
+ input_sync(ds->jack);
+ ds->prev_plugged_state = ds->plugged_state;
}
- ds->update_mic_mute = false;
- }
+ if (ds->update_mic_mute) {
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
+ common->mute_button_led = ds->mic_muted;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ if (ds->mic_muted) {
+ /* Disable microphone */
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ } else {
+ /* Enable microphone */
+ common->valid_flag1 |=
+ DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control &=
+ ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ }
+
+ ds->update_mic_mute = false;
+ }
+ }
dualsense_send_output_report(ds, &report);
}
static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct hid_device *hdev = ps_dev->hdev;
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
struct dualsense_input_report *ds_report;
- uint8_t battery_data, battery_capacity, charging_status, value;
+ u8 battery_data, battery_capacity, charging_status, value;
int battery_status;
- uint32_t sensor_timestamp;
+ u32 sensor_timestamp;
bool btn_mic_state;
- unsigned long flags;
int i;
/*
@@ -1324,12 +1435,12 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
* the full report using reportID 49.
*/
if (hdev->bus == BUS_USB && report->id == DS_INPUT_REPORT_USB &&
- size == DS_INPUT_REPORT_USB_SIZE) {
+ size == DS_INPUT_REPORT_USB_SIZE) {
ds_report = (struct dualsense_input_report *)&data[1];
} else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT &&
- size == DS_INPUT_REPORT_BT_SIZE) {
+ size == DS_INPUT_REPORT_BT_SIZE) {
/* Last 4 bytes of input report contain crc32 */
- uint32_t report_crc = get_unaligned_le32(&data[size - 4]);
+ u32 report_crc = get_unaligned_le32(&data[size - 4]);
if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) {
hid_err(hdev, "DualSense input CRC's check failed\n");
@@ -1377,16 +1488,42 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
*/
btn_mic_state = !!(ds_report->buttons[2] & DS_BUTTONS2_MIC_MUTE);
if (btn_mic_state && !ds->last_btn_mic_state) {
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds->update_mic_mute = true;
- ds->mic_muted = !ds->mic_muted; /* toggle */
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->update_mic_mute = true;
+ ds->mic_muted = !ds->mic_muted; /* toggle */
+ }
/* Schedule updating of microphone state at hardware level. */
dualsense_schedule_work(ds);
}
ds->last_btn_mic_state = btn_mic_state;
+ /*
+ * Parse HP/MIC plugged state data for USB use case, since Bluetooth
+ * audio is currently not supported.
+ */
+ if (hdev->bus == BUS_USB) {
+ value = ds_report->status[1] & DS_STATUS1_JACK_DETECT;
+
+ if (!ds->prev_plugged_state_valid) {
+ /* Initial handling of the plugged state report */
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT;
+ ds->prev_plugged_state_valid = true;
+ }
+ }
+
+ if (value != ds->plugged_state) {
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ds->prev_plugged_state = ds->plugged_state;
+ ds->plugged_state = value;
+ }
+
+ /* Schedule audio routing towards active endpoint. */
+ dualsense_schedule_work(ds);
+ }
+ }
+
/* Parse and calibrate gyroscope data. */
for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);
@@ -1412,7 +1549,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
ds->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp, 3);
ds->sensor_timestamp_initialized = true;
} else {
- uint32_t delta;
+ u32 delta;
if (ds->prev_sensor_timestamp > sensor_timestamp)
delta = (U32_MAX - ds->prev_sensor_timestamp + sensor_timestamp + 1);
@@ -1432,19 +1569,18 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
input_mt_report_slot_state(ds->touchpad, MT_TOOL_FINGER, active);
if (active) {
- int x = (point->x_hi << 8) | point->x_lo;
- int y = (point->y_hi << 4) | point->y_lo;
-
- input_report_abs(ds->touchpad, ABS_MT_POSITION_X, x);
- input_report_abs(ds->touchpad, ABS_MT_POSITION_Y, y);
+ input_report_abs(ds->touchpad, ABS_MT_POSITION_X,
+ DS_TOUCH_POINT_X(point->x_hi, point->x_lo));
+ input_report_abs(ds->touchpad, ABS_MT_POSITION_Y,
+ DS_TOUCH_POINT_Y(point->y_hi, point->y_lo));
}
}
input_mt_sync_frame(ds->touchpad);
input_report_key(ds->touchpad, BTN_LEFT, ds_report->buttons[2] & DS_BUTTONS2_TOUCHPAD);
input_sync(ds->touchpad);
- battery_data = ds_report->status & DS_STATUS_BATTERY_CAPACITY;
- charging_status = (ds_report->status & DS_STATUS_CHARGING) >> DS_STATUS_CHARGING_SHIFT;
+ battery_data = FIELD_GET(DS_STATUS0_BATTERY_CAPACITY, ds_report->status[0]);
+ charging_status = FIELD_GET(DS_STATUS0_CHARGING, ds_report->status[0]);
switch (charging_status) {
case 0x0:
@@ -1474,10 +1610,10 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
- spin_lock_irqsave(&ps_dev->lock, flags);
- ps_dev->battery_capacity = battery_capacity;
- ps_dev->battery_status = battery_status;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ps_dev->battery_capacity = battery_capacity;
+ ps_dev->battery_status = battery_status;
+ }
return 0;
}
@@ -1486,16 +1622,15 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
{
struct hid_device *hdev = input_get_drvdata(dev);
struct dualsense *ds = hid_get_drvdata(hdev);
- unsigned long flags;
if (effect->type != FF_RUMBLE)
return 0;
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->update_rumble = true;
- ds->motor_left = effect->u.rumble.strong_magnitude / 256;
- ds->motor_right = effect->u.rumble.weak_magnitude / 256;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ ds->update_rumble = true;
+ ds->motor_left = effect->u.rumble.strong_magnitude / 256;
+ ds->motor_right = effect->u.rumble.weak_magnitude / 256;
+ }
dualsense_schedule_work(ds);
return 0;
@@ -1504,11 +1639,9 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
static void dualsense_remove(struct ps_device *ps_dev)
{
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
- unsigned long flags;
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->output_worker_initialized = false;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock)
+ ds->output_worker_initialized = false;
cancel_work_sync(&ds->output_worker);
}
@@ -1516,9 +1649,9 @@ static void dualsense_remove(struct ps_device *ps_dev)
static int dualsense_reset_leds(struct dualsense *ds)
{
struct dualsense_output_report report;
- uint8_t *buf;
+ struct dualsense_output_report_bt *buf;
- buf = kzalloc(sizeof(struct dualsense_output_report_bt), GFP_KERNEL);
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
if (!buf)
return -ENOMEM;
@@ -1538,16 +1671,14 @@ static int dualsense_reset_leds(struct dualsense *ds)
return 0;
}
-static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue)
+static void dualsense_set_lightbar(struct dualsense *ds, u8 red, u8 green, u8 blue)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds->base.lock, flags);
- ds->update_lightbar = true;
- ds->lightbar_red = red;
- ds->lightbar_green = green;
- ds->lightbar_blue = blue;
- spin_unlock_irqrestore(&ds->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds->base.lock) {
+ ds->update_lightbar = true;
+ ds->lightbar_red = red;
+ ds->lightbar_green = green;
+ ds->lightbar_blue = blue;
+ }
dualsense_schedule_work(ds);
}
@@ -1568,7 +1699,7 @@ static void dualsense_set_player_leds(struct dualsense *ds)
BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0)
};
- uint8_t player_id = ds->base.player_id % ARRAY_SIZE(player_ids);
+ u8 player_id = ds->base.player_id % ARRAY_SIZE(player_ids);
ds->update_player_leds = true;
ds->player_leds_state = player_ids[player_id];
@@ -1579,7 +1710,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
{
struct dualsense *ds;
struct ps_device *ps_dev;
- uint8_t max_output_report_size;
+ u8 max_output_report_size;
int i, ret;
static const struct ps_led_info player_leds_info[] = {
@@ -1668,7 +1799,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
ps_dev->input_dev_name = dev_name(&ds->gamepad->dev);
ds->sensors = ps_sensors_create(hdev, DS_ACC_RANGE, DS_ACC_RES_PER_G,
- DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S);
+ DS_GYRO_RANGE, DS_GYRO_RES_PER_DEG_S);
if (IS_ERR(ds->sensors)) {
ret = PTR_ERR(ds->sensors);
goto err;
@@ -1680,6 +1811,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
goto err;
}
+ /* Bluetooth audio is currently not supported. */
+ if (hdev->bus == BUS_USB) {
+ ds->jack = ps_headset_jack_create(hdev);
+ if (IS_ERR(ds->jack)) {
+ ret = PTR_ERR(ds->jack);
+ goto err;
+ }
+ }
+
ret = ps_device_register_battery(ps_dev);
if (ret)
goto err;
@@ -1722,7 +1862,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
* can change behavior.
*/
hid_info(hdev, "Registered DualSense controller hw_version=0x%08x fw_version=0x%08x\n",
- ds->base.hw_version, ds->base.fw_version);
+ ds->base.hw_version, ds->base.fw_version);
return &ds->base;
@@ -1734,7 +1874,6 @@ err:
static void dualshock4_dongle_calibration_work(struct work_struct *work)
{
struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker);
- unsigned long flags;
enum dualshock4_dongle_state dongle_state;
int ret;
@@ -1746,16 +1885,16 @@ static void dualshock4_dongle_calibration_work(struct work_struct *work)
* DS4 hotplug is detect from sony_raw_event as any issues
* are likely resolved then (the dongle is quite stupid).
*/
- hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
+ hid_err(ds4->base.hdev,
+ "DualShock 4 USB dongle: calibration failed, disabling device\n");
dongle_state = DONGLE_DISABLED;
} else {
hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n");
dongle_state = DONGLE_CONNECTED;
}
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->dongle_state = dongle_state;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ ds4->dongle_state = dongle_state;
}
static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
@@ -1772,14 +1911,16 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
int range_2g;
int ret = 0;
int i;
- uint8_t *buf;
+ u8 *buf;
if (ds4->base.hdev->bus == BUS_USB) {
int retries;
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
+ if (!buf) {
+ ret = -ENOMEM;
+ goto transfer_failed;
+ }
/* We should normally receive the feature report data we asked
* for, but hidraw applications such as Steam can issue feature
@@ -1789,33 +1930,42 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
*/
for (retries = 0; retries < 3; retries++) {
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
- DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
+ DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
if (ret) {
if (retries < 2) {
- hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
+ hid_warn(hdev,
+ "Retrying DualShock 4 get calibration report (0x02) request\n");
continue;
}
- hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+ hid_warn(hdev,
+ "Failed to retrieve DualShock4 calibration info: %d\n",
+ ret);
ret = -EILSEQ;
- goto err_free;
+ kfree(buf);
+ goto transfer_failed;
} else {
break;
}
}
} else { /* Bluetooth */
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
+ if (!buf) {
+ ret = -ENOMEM;
+ goto transfer_failed;
+ }
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf,
- DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
+ DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
+
if (ret) {
- hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
- goto err_free;
+ hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+ kfree(buf);
+ goto transfer_failed;
}
}
+ /* Transfer succeeded - parse the calibration data received. */
gyro_pitch_bias = get_unaligned_le16(&buf[1]);
gyro_yaw_bias = get_unaligned_le16(&buf[3]);
gyro_roll_bias = get_unaligned_le16(&buf[5]);
@@ -1844,6 +1994,9 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
acc_z_plus = get_unaligned_le16(&buf[31]);
acc_z_minus = get_unaligned_le16(&buf[33]);
+ /* Done parsing the buffer, so let's free it. */
+ kfree(buf);
+
/*
* Set gyroscope calibration and normalization parameters.
* Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
@@ -1851,59 +2004,62 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds4->gyro_calib_data[0].abs_code = ABS_RX;
ds4->gyro_calib_data[0].bias = 0;
- ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[0].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds4->gyro_calib_data[1].abs_code = ABS_RY;
ds4->gyro_calib_data[1].bias = 0;
- ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[1].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds4->gyro_calib_data[2].abs_code = ABS_RZ;
ds4->gyro_calib_data[2].bias = 0;
- ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ ds4->gyro_calib_data[2].sens_numer = speed_2x * DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
/*
- * Sanity check gyro calibration data. This is needed to prevent crashes
- * during report handling of virtual, clone or broken devices not implementing
- * calibration data properly.
- */
- for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
- if (ds4->gyro_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
- ds4->gyro_calib_data[i].abs_code);
- ds4->gyro_calib_data[i].bias = 0;
- ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
- ds4->gyro_calib_data[i].sens_denom = S16_MAX;
- }
- }
-
- /*
* Set accelerometer calibration and normalization parameters.
* Data values will be normalized to 1/DS4_ACC_RES_PER_G g.
*/
range_2g = acc_x_plus - acc_x_minus;
ds4->accel_calib_data[0].abs_code = ABS_X;
ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
- ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[0].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[0].sens_denom = range_2g;
range_2g = acc_y_plus - acc_y_minus;
ds4->accel_calib_data[1].abs_code = ABS_Y;
ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
- ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[1].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[1].sens_denom = range_2g;
range_2g = acc_z_plus - acc_z_minus;
ds4->accel_calib_data[2].abs_code = ABS_Z;
ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
- ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[2].sens_numer = 2 * DS4_ACC_RES_PER_G;
ds4->accel_calib_data[2].sens_denom = range_2g;
+transfer_failed:
+ /*
+ * Sanity check gyro calibration data. This is needed to prevent crashes
+ * during report handling of virtual, clone or broken devices not implementing
+ * calibration data properly.
+ */
+ for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
+ if (ds4->gyro_calib_data[i].sens_denom == 0) {
+ ds4->gyro_calib_data[i].abs_code = ABS_RX + i;
+ hid_warn(hdev,
+ "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds4->gyro_calib_data[i].abs_code);
+ ds4->gyro_calib_data[i].bias = 0;
+ ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
+ ds4->gyro_calib_data[i].sens_denom = S16_MAX;
+ }
+ }
+
/*
* Sanity check accelerometer calibration data. This is needed to prevent crashes
* during report handling of virtual, clone or broken devices not implementing calibration
@@ -1911,22 +2067,22 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
*/
for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) {
if (ds4->accel_calib_data[i].sens_denom == 0) {
- hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
- ds4->accel_calib_data[i].abs_code);
+ ds4->accel_calib_data[i].abs_code = ABS_X + i;
+ hid_warn(hdev,
+ "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds4->accel_calib_data[i].abs_code);
ds4->accel_calib_data[i].bias = 0;
ds4->accel_calib_data[i].sens_numer = DS4_ACC_RANGE;
ds4->accel_calib_data[i].sens_denom = S16_MAX;
}
}
-err_free:
- kfree(buf);
return ret;
}
static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
{
- uint8_t *buf;
+ u8 *buf;
int ret;
buf = kzalloc(DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL);
@@ -1937,7 +2093,7 @@ static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
* lacks CRC support, so must be disabled in ps_get_report.
*/
ret = ps_get_report(ds4->base.hdev, DS4_FEATURE_REPORT_FIRMWARE_INFO, buf,
- DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false);
+ DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false);
if (ret) {
hid_err(ds4->base.hdev, "Failed to retrieve DualShock4 firmware info: %d\n", ret);
goto err_free;
@@ -1954,7 +2110,7 @@ err_free:
static int dualshock4_get_mac_address(struct dualshock4 *ds4)
{
struct hid_device *hdev = ds4->base.hdev;
- uint8_t *buf;
+ u8 *buf;
int ret = 0;
if (hdev->bus == BUS_USB) {
@@ -1963,7 +2119,7 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4)
return -ENOMEM;
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_PAIRING_INFO, buf,
- DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false);
+ DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false);
if (ret) {
hid_err(hdev, "Failed to retrieve DualShock4 pairing info: %d\n", ret);
goto err_free;
@@ -1976,9 +2132,9 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4)
return -EINVAL;
ret = sscanf(hdev->uniq, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
- &ds4->base.mac_address[5], &ds4->base.mac_address[4],
- &ds4->base.mac_address[3], &ds4->base.mac_address[2],
- &ds4->base.mac_address[1], &ds4->base.mac_address[0]);
+ &ds4->base.mac_address[5], &ds4->base.mac_address[4],
+ &ds4->base.mac_address[3], &ds4->base.mac_address[2],
+ &ds4->base.mac_address[1], &ds4->base.mac_address[0]);
if (ret != sizeof(ds4->base.mac_address))
return -EINVAL;
@@ -2013,32 +2169,32 @@ static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *le
}
static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *delay_on,
- unsigned long *delay_off)
+ unsigned long *delay_off)
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
- spin_lock_irqsave(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ if (!*delay_on && !*delay_off) {
+ /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */
+ ds4->lightbar_blink_on = 50;
+ ds4->lightbar_blink_off = 50;
+ } else {
+ /* Blink delays in centiseconds. */
+ ds4->lightbar_blink_on = min_t(unsigned long, *delay_on / 10,
+ DS4_LIGHTBAR_MAX_BLINK);
+ ds4->lightbar_blink_off = min_t(unsigned long, *delay_off / 10,
+ DS4_LIGHTBAR_MAX_BLINK);
+ }
- if (!*delay_on && !*delay_off) {
- /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */
- ds4->lightbar_blink_on = 50;
- ds4->lightbar_blink_off = 50;
- } else {
- /* Blink delays in centiseconds. */
- ds4->lightbar_blink_on = min_t(unsigned long, *delay_on/10, DS4_LIGHTBAR_MAX_BLINK);
- ds4->lightbar_blink_off = min_t(unsigned long, *delay_off/10, DS4_LIGHTBAR_MAX_BLINK);
+ ds4->update_lightbar_blink = true;
}
- ds4->update_lightbar_blink = true;
-
- spin_unlock_irqrestore(&ds4->base.lock, flags);
-
dualshock4_schedule_work(ds4);
- *delay_on = ds4->lightbar_blink_on;
- *delay_off = ds4->lightbar_blink_off;
+ /* Report scaled values back to LED subsystem */
+ *delay_on = ds4->lightbar_blink_on * 10;
+ *delay_off = ds4->lightbar_blink_off * 10;
return 0;
}
@@ -2047,37 +2203,41 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig
{
struct hid_device *hdev = to_hid_device(led->dev->parent);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
unsigned int led_index;
- spin_lock_irqsave(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ led_index = led - ds4->lightbar_leds;
+ switch (led_index) {
+ case 0:
+ ds4->lightbar_red = value;
+ break;
+ case 1:
+ ds4->lightbar_green = value;
+ break;
+ case 2:
+ ds4->lightbar_blue = value;
+ break;
+ case 3:
+ ds4->lightbar_enabled = !!value;
+
+ /* brightness = 0 also cancels blinking in Linux. */
+ if (!ds4->lightbar_enabled) {
+ ds4->lightbar_blink_off = 0;
+ ds4->lightbar_blink_on = 0;
+ ds4->update_lightbar_blink = true;
+ }
+ }
- led_index = led - ds4->lightbar_leds;
- switch (led_index) {
- case 0:
- ds4->lightbar_red = value;
- break;
- case 1:
- ds4->lightbar_green = value;
- break;
- case 2:
- ds4->lightbar_blue = value;
- break;
- case 3:
- ds4->lightbar_enabled = !!value;
+ ds4->update_lightbar = true;
}
- ds4->update_lightbar = true;
-
- spin_unlock_irqrestore(&ds4->base.lock, flags);
-
dualshock4_schedule_work(ds4);
return 0;
}
static void dualshock4_init_output_report(struct dualshock4 *ds4,
- struct dualshock4_output_report *rp, void *buf)
+ struct dualshock4_output_report *rp, void *buf)
{
struct hid_device *hdev = ds4->base.hdev;
@@ -2111,46 +2271,63 @@ static void dualshock4_output_worker(struct work_struct *work)
struct dualshock4 *ds4 = container_of(work, struct dualshock4, output_worker);
struct dualshock4_output_report report;
struct dualshock4_output_report_common *common;
- unsigned long flags;
dualshock4_init_output_report(ds4, &report, ds4->output_report_dmabuf);
common = report.common;
- spin_lock_irqsave(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ /*
+ * Some 3rd party gamepads expect updates to rumble and lightbar
+ * together, and setting one may cancel the other.
+ *
+ * Let's maximise compatibility by always sending rumble and lightbar
+ * updates together, even when only one has been scheduled, resulting
+ * in:
+ *
+ * ds4->valid_flag0 >= 0x03
+ *
+ * Hopefully this will maximise compatibility with third-party pads.
+ *
+ * Any further update bits, such as 0x04 for lightbar blinking, will
+ * be or'd on top of this like before.
+ */
+ if (ds4->update_rumble || ds4->update_lightbar) {
+ ds4->update_rumble = true; /* 0x01 */
+ ds4->update_lightbar = true; /* 0x02 */
+ }
- if (ds4->update_rumble) {
- /* Select classic rumble style haptics and enable it. */
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR;
- common->motor_left = ds4->motor_left;
- common->motor_right = ds4->motor_right;
- ds4->update_rumble = false;
- }
+ if (ds4->update_rumble) {
+ /* Select classic rumble style haptics and enable it. */
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR;
+ common->motor_left = ds4->motor_left;
+ common->motor_right = ds4->motor_right;
+ ds4->update_rumble = false;
+ }
- if (ds4->update_lightbar) {
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
- /* Comptabile behavior with hid-sony, which used a dummy global LED to
- * allow enabling/disabling the lightbar. The global LED maps to
- * lightbar_enabled.
- */
- common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
- common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
- common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
- ds4->update_lightbar = false;
- }
+ if (ds4->update_lightbar) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
+ /* Compatible behavior with hid-sony, which used a dummy global LED to
+ * allow enabling/disabling the lightbar. The global LED maps to
+ * lightbar_enabled.
+ */
+ common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
+ common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
+ common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
+ ds4->update_lightbar = false;
+ }
- if (ds4->update_lightbar_blink) {
- common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK;
- common->lightbar_blink_on = ds4->lightbar_blink_on;
- common->lightbar_blink_off = ds4->lightbar_blink_off;
- ds4->update_lightbar_blink = false;
+ if (ds4->update_lightbar_blink) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK;
+ common->lightbar_blink_on = ds4->lightbar_blink_on;
+ common->lightbar_blink_off = ds4->lightbar_blink_off;
+ ds4->update_lightbar_blink = false;
+ }
}
- spin_unlock_irqrestore(&ds4->base.lock, flags);
-
/* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */
if (report.bt) {
- uint32_t crc;
- uint8_t seed = PS_OUTPUT_CRC32_SEED;
+ u32 crc;
+ u8 seed = PS_OUTPUT_CRC32_SEED;
/* Hardware control flags need to set to let the device know
* there is HID data as well as CRC.
@@ -2172,16 +2349,16 @@ static void dualshock4_output_worker(struct work_struct *work)
}
static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct hid_device *hdev = ps_dev->hdev;
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
struct dualshock4_input_report_common *ds4_report;
struct dualshock4_touch_report *touch_reports;
- uint8_t battery_capacity, num_touch_reports, value;
+ u8 battery_capacity, num_touch_reports, value;
int battery_status, i, j;
- uint16_t sensor_timestamp;
- unsigned long flags;
+ u16 sensor_timestamp;
+ bool is_minimal = false;
/*
* DualShock4 in USB uses the full HID report for reportID 1, but
@@ -2189,16 +2366,17 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
* the full report using reportID 17.
*/
if (hdev->bus == BUS_USB && report->id == DS4_INPUT_REPORT_USB &&
- size == DS4_INPUT_REPORT_USB_SIZE) {
- struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data;
+ size == DS4_INPUT_REPORT_USB_SIZE) {
+ struct dualshock4_input_report_usb *usb =
+ (struct dualshock4_input_report_usb *)data;
ds4_report = &usb->common;
num_touch_reports = usb->num_touch_reports;
touch_reports = usb->touch_reports;
} else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT &&
- size == DS4_INPUT_REPORT_BT_SIZE) {
+ size == DS4_INPUT_REPORT_BT_SIZE) {
struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data;
- uint32_t report_crc = get_unaligned_le32(&bt->crc32);
+ u32 report_crc = get_unaligned_le32(&bt->crc32);
/* Last 4 bytes of input report contains CRC. */
if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) {
@@ -2209,6 +2387,18 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
ds4_report = &bt->common;
num_touch_reports = bt->num_touch_reports;
touch_reports = bt->touch_reports;
+ } else if (hdev->bus == BUS_BLUETOOTH &&
+ report->id == DS4_INPUT_REPORT_BT_MINIMAL &&
+ size == DS4_INPUT_REPORT_BT_MINIMAL_SIZE) {
+ /* Some third-party pads never switch to the full 0x11 report.
+ * The short 0x01 report is 10 bytes long:
+ * u8 report_id == 0x01
+ * u8 first_bytes_of_full_report[9]
+ * So let's reuse the full report parser, and stop it after
+ * parsing the buttons.
+ */
+ ds4_report = (struct dualshock4_input_report_common *)&data[1];
+ is_minimal = true;
} else {
hid_err(hdev, "Unhandled reportID=%d\n", report->id);
return -1;
@@ -2242,6 +2432,9 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
input_report_key(ds4->gamepad, BTN_MODE, ds4_report->buttons[2] & DS_BUTTONS2_PS_HOME);
input_sync(ds4->gamepad);
+ if (is_minimal)
+ return 0;
+
/* Parse and calibrate gyroscope data. */
for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
@@ -2264,16 +2457,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
/* Convert timestamp (in 5.33us unit) to timestamp_us */
sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp);
if (!ds4->sensor_timestamp_initialized) {
- ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3);
+ ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp * 16, 3);
ds4->sensor_timestamp_initialized = true;
} else {
- uint16_t delta;
+ u16 delta;
if (ds4->prev_sensor_timestamp > sensor_timestamp)
delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1);
else
delta = sensor_timestamp - ds4->prev_sensor_timestamp;
- ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3);
+ ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta * 16, 3);
}
ds4->prev_sensor_timestamp = sensor_timestamp;
input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us);
@@ -2290,11 +2483,10 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
input_mt_report_slot_state(ds4->touchpad, MT_TOOL_FINGER, active);
if (active) {
- int x = (point->x_hi << 8) | point->x_lo;
- int y = (point->y_hi << 4) | point->y_lo;
-
- input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, x);
- input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, y);
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_X,
+ DS4_TOUCH_POINT_X(point->x_hi, point->x_lo));
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y,
+ DS4_TOUCH_POINT_Y(point->y_hi, point->y_lo));
}
}
input_mt_sync_frame(ds4->touchpad);
@@ -2313,7 +2505,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
* - 15: charge error
*/
if (ds4_report->status[0] & DS4_STATUS0_CABLE_STATE) {
- uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+ u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
if (battery_data < 10) {
/* Take the mid-point for each battery capacity value,
@@ -2334,7 +2526,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
} else {
- uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+ u8 battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
if (battery_data < 10)
battery_capacity = battery_data * 10 + 5;
@@ -2344,16 +2536,16 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
}
- spin_lock_irqsave(&ps_dev->lock, flags);
- ps_dev->battery_capacity = battery_capacity;
- ps_dev->battery_status = battery_status;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock) {
+ ps_dev->battery_capacity = battery_capacity;
+ ps_dev->battery_status = battery_status;
+ }
return 0;
}
static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
bool connected = false;
@@ -2364,8 +2556,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
* parsing code.
*/
if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) {
- struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1];
- unsigned long flags;
+ struct dualshock4_input_report_common *ds4_report =
+ (struct dualshock4_input_report_common *)&data[1];
connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true;
@@ -2374,9 +2566,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
dualshock4_set_default_lightbar_colors(ds4);
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds4->dongle_state = DONGLE_CALIBRATING;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock)
+ ds4->dongle_state = DONGLE_CALIBRATING;
schedule_work(&ds4->dongle_hotplug_worker);
@@ -2388,9 +2579,8 @@ static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_r
ds4->dongle_state == DONGLE_DISABLED) && !connected) {
hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n");
- spin_lock_irqsave(&ps_dev->lock, flags);
- ds4->dongle_state = DONGLE_DISCONNECTED;
- spin_unlock_irqrestore(&ps_dev->lock, flags);
+ scoped_guard(spinlock_irqsave, &ps_dev->lock)
+ ds4->dongle_state = DONGLE_DISCONNECTED;
/* Return 0, so hidraw can get the report. */
return 0;
@@ -2412,16 +2602,15 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e
{
struct hid_device *hdev = input_get_drvdata(dev);
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
- unsigned long flags;
if (effect->type != FF_RUMBLE)
return 0;
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->update_rumble = true;
- ds4->motor_left = effect->u.rumble.strong_magnitude / 256;
- ds4->motor_right = effect->u.rumble.weak_magnitude / 256;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock) {
+ ds4->update_rumble = true;
+ ds4->motor_left = effect->u.rumble.strong_magnitude / 256;
+ ds4->motor_right = effect->u.rumble.weak_magnitude / 256;
+ }
dualshock4_schedule_work(ds4);
return 0;
@@ -2430,11 +2619,9 @@ static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_e
static void dualshock4_remove(struct ps_device *ps_dev)
{
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
- unsigned long flags;
- spin_lock_irqsave(&ds4->base.lock, flags);
- ds4->output_worker_initialized = false;
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ ds4->output_worker_initialized = false;
cancel_work_sync(&ds4->output_worker);
@@ -2444,15 +2631,13 @@ static void dualshock4_remove(struct ps_device *ps_dev)
static inline void dualshock4_schedule_work(struct dualshock4 *ds4)
{
- unsigned long flags;
-
- spin_lock_irqsave(&ds4->base.lock, flags);
- if (ds4->output_worker_initialized)
- schedule_work(&ds4->output_worker);
- spin_unlock_irqrestore(&ds4->base.lock, flags);
+ /* Using scoped_guard() instead of guard() to make sparse happy */
+ scoped_guard(spinlock_irqsave, &ds4->base.lock)
+ if (ds4->output_worker_initialized)
+ schedule_work(&ds4->output_worker);
}
-static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval)
+static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, u8 interval)
{
ds4->bt_poll_interval = interval;
ds4->update_bt_poll_interval = true;
@@ -2472,7 +2657,7 @@ static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4)
{ 0x20, 0x00, 0x20 } /* Pink */
};
- uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
+ u8 player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
ds4->lightbar_enabled = true;
ds4->lightbar_red = player_colors[player_id][0];
@@ -2487,7 +2672,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
{
struct dualshock4 *ds4;
struct ps_device *ps_dev;
- uint8_t max_output_report_size;
+ u8 max_output_report_size;
int i, ret;
/* The DualShock4 has an RGB lightbar, which the original hid-sony driver
@@ -2500,11 +2685,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
* existing applications (e.g. Android). Nothing matches against MAC address.
*/
static const struct ps_led_info lightbar_leds_info[] = {
- { NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
- { NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness,
- dualshock4_led_set_blink },
+ { NULL, "red", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "green", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "blue", 255, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness },
+ { NULL, "global", 1, dualshock4_led_get_brightness,
+ dualshock4_led_set_brightness, dualshock4_led_set_blink },
};
ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL);
@@ -2550,8 +2738,8 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
ret = dualshock4_get_firmware_info(ds4);
if (ret) {
- hid_err(hdev, "Failed to get firmware info from DualShock4\n");
- return ERR_PTR(ret);
+ hid_warn(hdev, "Failed to get firmware info from DualShock4\n");
+ hid_warn(hdev, "HW/FW version data in sysfs will be invalid.\n");
}
ret = ps_devices_list_add(ps_dev);
@@ -2560,8 +2748,8 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
ret = dualshock4_get_calibration_data(ds4);
if (ret) {
- hid_err(hdev, "Failed to get calibration data from DualShock4\n");
- goto err;
+ hid_warn(hdev, "Failed to get calibration data from DualShock4\n");
+ hid_warn(hdev, "Gyroscope and accelerometer will be inaccurate.\n");
}
ds4->gamepad = ps_gamepad_create(hdev, dualshock4_play_effect);
@@ -2574,7 +2762,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev);
ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G,
- DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
+ DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
if (IS_ERR(ds4->sensors)) {
ret = PTR_ERR(ds4->sensors);
goto err;
@@ -2613,7 +2801,7 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
* can change behavior.
*/
hid_info(hdev, "Registered DualShock4 controller hw_version=0x%08x fw_version=0x%08x\n",
- ds4->base.hw_version, ds4->base.fw_version);
+ ds4->base.hw_version, ds4->base.fw_version);
return &ds4->base;
err:
@@ -2622,7 +2810,7 @@ err:
}
static int ps_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
+ u8 *data, int size)
{
struct ps_device *dev = hid_get_drvdata(hdev);
@@ -2655,17 +2843,14 @@ static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err_stop;
}
- if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER ||
- hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 ||
- hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
+ if (id->driver_data == PS_TYPE_PS4_DUALSHOCK4) {
dev = dualshock4_create(hdev);
if (IS_ERR(dev)) {
hid_err(hdev, "Failed to create dualshock4.\n");
ret = PTR_ERR(dev);
goto err_close;
}
- } else if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER ||
- hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) {
+ } else if (id->driver_data == PS_TYPE_PS5_DUALSENSE) {
dev = dualsense_create(hdev);
if (IS_ERR(dev)) {
hid_err(hdev, "Failed to create dualsense.\n");
@@ -2699,16 +2884,26 @@ static void ps_remove(struct hid_device *hdev)
static const struct hid_device_id ps_devices[] = {
/* Sony DualShock 4 controllers for PS4 */
- { 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) },
- { 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_2) },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
+ .driver_data = PS_TYPE_PS4_DUALSHOCK4 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
+ .driver_data = PS_TYPE_PS4_DUALSHOCK4 },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+ .driver_data = PS_TYPE_PS4_DUALSHOCK4 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+ .driver_data = PS_TYPE_PS4_DUALSHOCK4 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE),
+ .driver_data = PS_TYPE_PS4_DUALSHOCK4 },
+
/* Sony DualSense controllers for PS5 */
- { 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) },
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
- { 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),
+ .driver_data = PS_TYPE_PS5_DUALSENSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER),
+ .driver_data = PS_TYPE_PS5_DUALSENSE },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2),
+ .driver_data = PS_TYPE_PS5_DUALSENSE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2),
+ .driver_data = PS_TYPE_PS5_DUALSENSE },
{ }
};
MODULE_DEVICE_TABLE(hid, ps_devices);
diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c
index 1e6413d07cae..e44d79dff8de 100644
--- a/drivers/hid/hid-primax.c
+++ b/drivers/hid/hid-primax.c
@@ -70,4 +70,5 @@ static struct hid_driver px_driver = {
module_hid_driver(px_driver);
MODULE_AUTHOR("Terry Lambert <tlambert@google.com>");
+MODULE_DESCRIPTION("HID driver for primax and similar keyboards with in-band modifiers");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
index e4e9471d0f1e..74bddb2c3e82 100644
--- a/drivers/hid/hid-prodikeys.c
+++ b/drivers/hid/hid-prodikeys.c
@@ -32,13 +32,6 @@
struct pcmidi_snd;
-struct pk_device {
- unsigned long quirks;
-
- struct hid_device *hdev;
- struct pcmidi_snd *pm; /* pcmidi device context */
-};
-
struct pcmidi_sustain {
unsigned long in_use;
struct pcmidi_snd *pm;
@@ -50,7 +43,7 @@ struct pcmidi_sustain {
#define PCMIDI_SUSTAINED_MAX 32
struct pcmidi_snd {
- struct pk_device *pk;
+ struct hid_device *hdev;
unsigned short ifnum;
struct hid_report *pcmidi_report6;
struct input_dev *input_ep82;
@@ -66,9 +59,7 @@ struct pcmidi_snd {
struct snd_card *card;
struct snd_rawmidi *rwmidi;
struct snd_rawmidi_substream *in_substream;
- struct snd_rawmidi_substream *out_substream;
unsigned long in_triggered;
- unsigned long out_active;
};
#define PK_QUIRK_NOGET 0x00010000
@@ -100,11 +91,11 @@ static ssize_t show_channel(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
- dbg_hid("pcmidi sysfs read channel=%u\n", pk->pm->midi_channel);
+ dbg_hid("pcmidi sysfs read channel=%u\n", pm->midi_channel);
- return sprintf(buf, "%u (min:%u, max:%u)\n", pk->pm->midi_channel,
+ return sprintf(buf, "%u (min:%u, max:%u)\n", pm->midi_channel,
PCMIDI_CHANNEL_MIN, PCMIDI_CHANNEL_MAX);
}
@@ -113,13 +104,13 @@ static ssize_t store_channel(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
unsigned channel = 0;
if (sscanf(buf, "%u", &channel) > 0 && channel <= PCMIDI_CHANNEL_MAX) {
dbg_hid("pcmidi sysfs write channel=%u\n", channel);
- pk->pm->midi_channel = channel;
+ pm->midi_channel = channel;
return strlen(buf);
}
return -EINVAL;
@@ -137,11 +128,11 @@ static ssize_t show_sustain(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
- dbg_hid("pcmidi sysfs read sustain=%u\n", pk->pm->midi_sustain);
+ dbg_hid("pcmidi sysfs read sustain=%u\n", pm->midi_sustain);
- return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pk->pm->midi_sustain,
+ return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pm->midi_sustain,
PCMIDI_SUSTAIN_MIN, PCMIDI_SUSTAIN_MAX);
}
@@ -150,15 +141,14 @@ static ssize_t store_sustain(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
unsigned sustain = 0;
if (sscanf(buf, "%u", &sustain) > 0 && sustain <= PCMIDI_SUSTAIN_MAX) {
dbg_hid("pcmidi sysfs write sustain=%u\n", sustain);
- pk->pm->midi_sustain = sustain;
- pk->pm->midi_sustain_mode =
- (0 == sustain || !pk->pm->midi_mode) ? 0 : 1;
+ pm->midi_sustain = sustain;
+ pm->midi_sustain_mode = (0 == sustain || !pm->midi_mode) ? 0 : 1;
return strlen(buf);
}
return -EINVAL;
@@ -176,11 +166,11 @@ static ssize_t show_octave(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
- dbg_hid("pcmidi sysfs read octave=%d\n", pk->pm->midi_octave);
+ dbg_hid("pcmidi sysfs read octave=%d\n", pm->midi_octave);
- return sprintf(buf, "%d (min:%d, max:%d)\n", pk->pm->midi_octave,
+ return sprintf(buf, "%d (min:%d, max:%d)\n", pm->midi_octave,
PCMIDI_OCTAVE_MIN, PCMIDI_OCTAVE_MAX);
}
@@ -189,14 +179,14 @@ static ssize_t store_octave(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
int octave = 0;
if (sscanf(buf, "%d", &octave) > 0 &&
octave >= PCMIDI_OCTAVE_MIN && octave <= PCMIDI_OCTAVE_MAX) {
dbg_hid("pcmidi sysfs write octave=%d\n", octave);
- pk->pm->midi_octave = octave;
+ pm->midi_octave = octave;
return strlen(buf);
}
return -EINVAL;
@@ -237,7 +227,7 @@ drop_note:
static void pcmidi_sustained_note_release(struct timer_list *t)
{
- struct pcmidi_sustain *pms = from_timer(pms, t, timer);
+ struct pcmidi_sustain *pms = timer_container_of(pms, t, timer);
pcmidi_send_note(pms->pm, pms->status, pms->note, pms->velocity);
pms->in_use = 0;
@@ -264,13 +254,13 @@ 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);
}
}
static int pcmidi_get_output_report(struct pcmidi_snd *pm)
{
- struct hid_device *hdev = pm->pk->hdev;
+ struct hid_device *hdev = pm->hdev;
struct hid_report *report;
list_for_each_entry(report,
@@ -295,7 +285,7 @@ static int pcmidi_get_output_report(struct pcmidi_snd *pm)
static void pcmidi_submit_output_report(struct pcmidi_snd *pm, int state)
{
- struct hid_device *hdev = pm->pk->hdev;
+ struct hid_device *hdev = pm->hdev;
struct hid_report *report = pm->pcmidi_report6;
report->field[0]->value[0] = 0x01;
report->field[0]->value[1] = state;
@@ -622,7 +612,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
/* Setup sound card */
- err = snd_card_new(&pm->pk->hdev->dev, index[dev], id[dev],
+ err = snd_card_new(&pm->hdev->dev, index[dev], id[dev],
THIS_MODULE, 0, &card);
if (err < 0) {
pk_error("failed to create pc-midi sound card\n");
@@ -639,9 +629,9 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
goto fail;
}
- strncpy(card->driver, shortname, sizeof(card->driver));
- strncpy(card->shortname, shortname, sizeof(card->shortname));
- strncpy(card->longname, longname, sizeof(card->longname));
+ strscpy(card->driver, shortname, sizeof(card->driver));
+ strscpy(card->shortname, shortname, sizeof(card->shortname));
+ strscpy(card->longname, longname, sizeof(card->longname));
/* Set up rawmidi */
err = snd_rawmidi_new(card, card->shortname, 0,
@@ -652,7 +642,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
goto fail;
}
pm->rwmidi = rwmidi;
- strncpy(rwmidi->name, card->shortname, sizeof(rwmidi->name));
+ strscpy(rwmidi->name, card->shortname, sizeof(rwmidi->name));
rwmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT;
rwmidi->private_data = pm;
@@ -660,7 +650,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
&pcmidi_in_ops);
/* create sysfs variables */
- err = device_create_file(&pm->pk->hdev->dev,
+ err = device_create_file(&pm->hdev->dev,
sysfs_device_attr_channel);
if (err < 0) {
pk_error("failed to create sysfs attribute channel: error %d\n",
@@ -668,7 +658,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
goto fail;
}
- err = device_create_file(&pm->pk->hdev->dev,
+ err = device_create_file(&pm->hdev->dev,
sysfs_device_attr_sustain);
if (err < 0) {
pk_error("failed to create sysfs attribute sustain: error %d\n",
@@ -676,7 +666,7 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
goto fail_attr_sustain;
}
- err = device_create_file(&pm->pk->hdev->dev,
+ err = device_create_file(&pm->hdev->dev,
sysfs_device_attr_octave);
if (err < 0) {
pk_error("failed to create sysfs attribute octave: error %d\n",
@@ -706,11 +696,11 @@ static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
fail_register:
stop_sustain_timers(pm);
- device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_octave);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_octave);
fail_attr_octave:
- device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_sustain);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_sustain);
fail_attr_sustain:
- device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_channel);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_channel);
fail:
if (pm->card) {
snd_card_free(pm->card);
@@ -724,12 +714,9 @@ static int pcmidi_snd_terminate(struct pcmidi_snd *pm)
if (pm->card) {
stop_sustain_timers(pm);
- device_remove_file(&pm->pk->hdev->dev,
- sysfs_device_attr_channel);
- device_remove_file(&pm->pk->hdev->dev,
- sysfs_device_attr_sustain);
- device_remove_file(&pm->pk->hdev->dev,
- sysfs_device_attr_octave);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_channel);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_sustain);
+ device_remove_file(&pm->hdev->dev, sysfs_device_attr_octave);
snd_card_disconnect(pm->card);
snd_card_free_when_closed(pm->card);
@@ -741,7 +728,7 @@ static int pcmidi_snd_terminate(struct pcmidi_snd *pm)
/*
* PC-MIDI report descriptor for report id is wrong.
*/
-static __u8 *pk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *pk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == 178 &&
@@ -759,10 +746,7 @@ static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
- struct pk_device *pk = hid_get_drvdata(hdev);
- struct pcmidi_snd *pm;
-
- pm = pk->pm;
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
if (HID_UP_MSVENDOR == (usage->hid & HID_USAGE_PAGE) &&
1 == pm->ifnum) {
@@ -777,16 +761,16 @@ static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi,
static int pk_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
- struct pk_device *pk = hid_get_drvdata(hdev);
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
int ret = 0;
- if (1 == pk->pm->ifnum) {
+ if (1 == pm->ifnum) {
if (report->id == data[0])
switch (report->id) {
case 0x01: /* midi keys (qwerty)*/
case 0x03: /* midi keyboard (musical)*/
case 0x04: /* extra/midi keys (qwerty)*/
- ret = pcmidi_handle_report(pk->pm,
+ ret = pcmidi_handle_report(pm,
report->id, data, size);
break;
}
@@ -801,8 +785,7 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
struct usb_interface *intf;
unsigned short ifnum;
unsigned long quirks = id->driver_data;
- struct pk_device *pk;
- struct pcmidi_snd *pm = NULL;
+ struct pcmidi_snd *pm;
if (!hid_is_usb(hdev))
return -EINVAL;
@@ -810,26 +793,16 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
intf = to_usb_interface(hdev->dev.parent);
ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
- pk = kzalloc(sizeof(*pk), GFP_KERNEL);
- if (pk == NULL) {
- hid_err(hdev, "can't alloc descriptor\n");
- return -ENOMEM;
- }
-
- pk->hdev = hdev;
-
pm = kzalloc(sizeof(*pm), GFP_KERNEL);
if (pm == NULL) {
hid_err(hdev, "can't alloc descriptor\n");
- ret = -ENOMEM;
- goto err_free_pk;
+ return -ENOMEM;
}
- pm->pk = pk;
- pk->pm = pm;
+ pm->hdev = hdev;
pm->ifnum = ifnum;
- hid_set_drvdata(hdev, pk);
+ hid_set_drvdata(hdev, pm);
ret = hid_parse(hdev);
if (ret) {
@@ -856,26 +829,18 @@ err_stop:
hid_hw_stop(hdev);
err_free:
kfree(pm);
-err_free_pk:
- kfree(pk);
return ret;
}
static void pk_remove(struct hid_device *hdev)
{
- struct pk_device *pk = hid_get_drvdata(hdev);
- struct pcmidi_snd *pm;
-
- pm = pk->pm;
- if (pm) {
- pcmidi_snd_terminate(pm);
- kfree(pm);
- }
+ struct pcmidi_snd *pm = hid_get_drvdata(hdev);
+ pcmidi_snd_terminate(pm);
hid_hw_stop(hdev);
- kfree(pk);
+ kfree(pm);
}
static const struct hid_device_id pk_devices[] = {
@@ -897,4 +862,5 @@ static struct hid_driver pk_driver = {
};
module_hid_driver(pk_driver);
+MODULE_DESCRIPTION("HID driver for the Prodikeys PC-MIDI Keyboard");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-pxrc.c b/drivers/hid/hid-pxrc.c
index b0e517f9cde7..71fe0c06ddcd 100644
--- a/drivers/hid/hid-pxrc.c
+++ b/drivers/hid/hid-pxrc.c
@@ -17,7 +17,7 @@ struct pxrc_priv {
bool alternate;
};
-static __u8 pxrc_rdesc_fixed[] = {
+static const __u8 pxrc_rdesc_fixed[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
@@ -42,8 +42,8 @@ static __u8 pxrc_rdesc_fixed[] = {
0xC0, // End Collection
};
-static __u8 *pxrc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *pxrc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
hid_info(hdev, "fixing up PXRC report descriptor\n");
*rsize = sizeof(pxrc_rdesc_fixed);
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 3983b4f282f8..c89a015686c0 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -27,12 +27,15 @@
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 },
{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI, USB_DEVICE_ID_AKAI_MPKMINI2), HID_QUIRK_NO_INIT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_AMI, USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM), HID_QUIRK_NOGET },
@@ -54,6 +57,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_PEDALS), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_THROTTLE), HID_QUIRK_NOGET },
+ { HID_USB_DEVICE(USB_VENDOR_ID_COOLER_MASTER, USB_DEVICE_ID_COOLER_MASTER_MICE_DONGLE), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB), HID_QUIRK_NO_INIT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB), HID_QUIRK_NO_INIT_REPORTS },
@@ -66,6 +70,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PRO_WIRELESS_KM5221W), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC), HID_QUIRK_NOGET },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES), HID_QUIRK_MULTI_INPUT },
@@ -119,6 +124,9 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406XE), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_T609A), HID_QUIRK_MULTI_INPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT), HID_QUIRK_MULTI_INPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL },
@@ -199,6 +207,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_WP5540), HID_QUIRK_MULTI_INPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_R295), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH), HID_QUIRK_MULTI_INPUT },
{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET), HID_QUIRK_MULTI_INPUT },
@@ -309,6 +318,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) },
@@ -325,8 +335,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) },
@@ -335,6 +343,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) },
@@ -396,12 +410,15 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_ELECOM)
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_010C) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
#endif
@@ -592,6 +609,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
@@ -661,11 +689,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) },
@@ -675,6 +698,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
#endif
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
#endif
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
@@ -742,6 +767,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) },
@@ -888,6 +915,7 @@ 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) },
{ }
};
@@ -953,14 +981,6 @@ static const struct hid_device_id hid_mouse_ignore_list[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) },
- { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
{ }
@@ -1044,10 +1064,22 @@ bool hid_ignore(struct hid_device *hdev)
strlen(elan_acpi_id[i].id)))
return true;
break;
+ case USB_VENDOR_ID_JIELI_SDK_DEFAULT:
+ /*
+ * Multiple USB devices with identical IDs (mic & touchscreen).
+ * The touch screen requires hid core processing, but the
+ * microphone does not. They can be distinguished by manufacturer
+ * and serial number.
+ */
+ if (hdev->product == USB_DEVICE_ID_JIELI_SDK_4155 &&
+ strncmp(hdev->name, "SmartlinkTechnology", 19) == 0 &&
+ strncmp(hdev->uniq, "20201111000001", 14) == 0)
+ return true;
+ break;
}
if (hdev->type == HID_TYPE_USBMOUSE &&
- hid_match_id(hdev, hid_mouse_ignore_list))
+ hdev->quirks & HID_QUIRK_IGNORE_MOUSE)
return true;
return !!hid_match_id(hdev, hid_ignore_list);
@@ -1251,6 +1283,9 @@ static unsigned long hid_gets_squirk(const struct hid_device *hdev)
if (hid_match_id(hdev, hid_ignore_list))
quirks |= HID_QUIRK_IGNORE;
+ if (hid_match_id(hdev, hid_mouse_ignore_list))
+ quirks |= HID_QUIRK_IGNORE_MOUSE;
+
if (hid_match_id(hdev, hid_have_special_driver))
quirks |= HID_QUIRK_HAVE_SPECIAL_DRIVER;
diff --git a/drivers/hid/hid-razer.c b/drivers/hid/hid-razer.c
index 740df148b0ef..7f48258c61f7 100644
--- a/drivers/hid/hid-razer.c
+++ b/drivers/hid/hid-razer.c
@@ -122,4 +122,5 @@ static struct hid_driver razer_driver = {
module_hid_driver(razer_driver);
MODULE_AUTHOR("Jelle van der Waa <jvanderwaa@redhat.com>");
+MODULE_DESCRIPTION("HID driver for gaming keys on Razer Blackwidow gaming keyboards");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-redragon.c b/drivers/hid/hid-redragon.c
index 73c9d4c4fa34..20d28ed75c1e 100644
--- a/drivers/hid/hid-redragon.c
+++ b/drivers/hid/hid-redragon.c
@@ -33,7 +33,7 @@
* key codes are generated.
*/
-static __u8 *redragon_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *redragon_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 102 && rdesc[100] == 0x81 && rdesc[101] == 0x00) {
@@ -59,4 +59,5 @@ static struct hid_driver redragon_driver = {
module_hid_driver(redragon_driver);
+MODULE_DESCRIPTION("HID driver for Redragon keyboards");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-retrode.c b/drivers/hid/hid-retrode.c
index 6a08e25aa296..7997627fdccc 100644
--- a/drivers/hid/hid-retrode.c
+++ b/drivers/hid/hid-retrode.c
@@ -94,4 +94,5 @@ static struct hid_driver retrode_driver = {
module_hid_driver(retrode_driver);
+MODULE_DESCRIPTION("HID driver for Retrode 2 controller adapter and plug-in extensions");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index 84e7ba5314d3..d4af17fdba46 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -436,7 +436,6 @@ static void rmi_report(struct hid_device *hid, struct hid_report *report)
input_sync(field->hidinput->input);
}
-#ifdef CONFIG_PM
static int rmi_suspend(struct hid_device *hdev, pm_message_t message)
{
struct rmi_data *data = hid_get_drvdata(hdev);
@@ -483,7 +482,6 @@ out:
hid_hw_close(hdev);
return ret;
}
-#endif /* CONFIG_PM */
static int rmi_hid_reset(struct rmi_transport_dev *xport, u16 reset_addr)
{
@@ -774,11 +772,9 @@ static struct hid_driver rmi_driver = {
.report = rmi_report,
.input_mapping = rmi_input_mapping,
.input_configured = rmi_input_configured,
-#ifdef CONFIG_PM
- .suspend = rmi_suspend,
- .resume = rmi_post_resume,
- .reset_resume = rmi_post_resume,
-#endif
+ .suspend = pm_ptr(rmi_suspend),
+ .resume = pm_ptr(rmi_post_resume),
+ .reset_resume = pm_ptr(rmi_post_resume),
};
module_hid_driver(rmi_driver);
diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c
index ea6b79b3aeeb..7b09adfa44a1 100644
--- a/drivers/hid/hid-roccat-arvo.c
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -23,8 +23,6 @@
#include "hid-roccat-common.h"
#include "hid-roccat-arvo.h"
-static struct class *arvo_class;
-
static ssize_t arvo_sysfs_show_mode_key(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -226,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,
@@ -252,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,
@@ -268,6 +266,11 @@ static const struct attribute_group *arvo_groups[] = {
NULL,
};
+static const struct class arvo_class = {
+ .name = "arvo",
+ .dev_groups = arvo_groups,
+};
+
static int arvo_init_arvo_device_struct(struct usb_device *usb_dev,
struct arvo_device *arvo)
{
@@ -309,7 +312,7 @@ static int arvo_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(arvo_class, hdev,
+ retval = roccat_connect(&arvo_class, hdev,
sizeof(struct arvo_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
@@ -433,21 +436,20 @@ static int __init arvo_init(void)
{
int retval;
- arvo_class = class_create("arvo");
- if (IS_ERR(arvo_class))
- return PTR_ERR(arvo_class);
- arvo_class->dev_groups = arvo_groups;
+ retval = class_register(&arvo_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&arvo_driver);
if (retval)
- class_destroy(arvo_class);
+ class_unregister(&arvo_class);
return retval;
}
static void __exit arvo_exit(void)
{
hid_unregister_driver(&arvo_driver);
- class_destroy(arvo_class);
+ class_unregister(&arvo_class);
}
module_init(arvo_init);
diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h
index 839ddfd931f0..e931d0b48efe 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,7 +68,7 @@ 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, \
@@ -77,7 +77,7 @@ static struct bin_attribute bin_attr_ ## 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, \
@@ -85,7 +85,7 @@ static struct bin_attribute bin_attr_ ## 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 \
diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c
index 3903a2cea00c..339378771ed5 100644
--- a/drivers/hid/hid-roccat-isku.c
+++ b/drivers/hid/hid-roccat-isku.c
@@ -23,8 +23,6 @@
#include "hid-roccat-common.h"
#include "hid-roccat-isku.h"
-static struct class *isku_class;
-
static void isku_profile_activated(struct isku_device *isku, uint new_profile)
{
isku->actual_profile = new_profile;
@@ -63,7 +61,7 @@ static ssize_t isku_sysfs_show_actual_profile(struct device *dev,
{
struct isku_device *isku =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile);
+ return sysfs_emit(buf, "%d\n", isku->actual_profile);
}
static ssize_t isku_sysfs_set_actual_profile(struct device *dev,
@@ -158,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, \
@@ -167,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, \
@@ -180,7 +178,7 @@ 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, \
@@ -189,7 +187,7 @@ static struct bin_attribute bin_attr_##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, \
@@ -197,7 +195,7 @@ static struct bin_attribute bin_attr_##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 \
@@ -219,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,
@@ -248,6 +246,11 @@ static const struct attribute_group *isku_groups[] = {
NULL,
};
+static const struct class isku_class = {
+ .name = "isku",
+ .dev_groups = isku_groups,
+};
+
static int isku_init_isku_device_struct(struct usb_device *usb_dev,
struct isku_device *isku)
{
@@ -289,7 +292,7 @@ static int isku_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(isku_class, hdev,
+ retval = roccat_connect(&isku_class, hdev,
sizeof(struct isku_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
@@ -435,21 +438,21 @@ static struct hid_driver isku_driver = {
static int __init isku_init(void)
{
int retval;
- isku_class = class_create("isku");
- if (IS_ERR(isku_class))
- return PTR_ERR(isku_class);
- isku_class->dev_groups = isku_groups;
+
+ retval = class_register(&isku_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&isku_driver);
if (retval)
- class_destroy(isku_class);
+ class_unregister(&isku_class);
return retval;
}
static void __exit isku_exit(void)
{
hid_unregister_driver(&isku_driver);
- class_destroy(isku_class);
+ class_unregister(&isku_class);
}
module_init(isku_init);
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
index 945ae236fb45..fabc08efcfd8 100644
--- a/drivers/hid/hid-roccat-kone.c
+++ b/drivers/hid/hid-roccat-kone.c
@@ -89,9 +89,6 @@ static int kone_send(struct usb_device *usb_dev, uint usb_command,
return ((len < 0) ? len : ((len != size) ? -EIO : 0));
}
-/* kone_class is used for creating sysfs attributes via roccat char device */
-static struct class *kone_class;
-
static void kone_set_settings_checksum(struct kone_settings *settings)
{
uint16_t checksum = 0;
@@ -264,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));
@@ -288,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));
@@ -330,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));
@@ -354,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));
@@ -385,7 +382,7 @@ 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, \
@@ -403,7 +400,7 @@ static ssize_t kone_sysfs_show_actual_profile(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
+ return sysfs_emit(buf, "%d\n", kone->actual_profile);
}
static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
@@ -412,7 +409,7 @@ static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
+ return sysfs_emit(buf, "%d\n", kone->actual_dpi);
}
static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
@@ -435,7 +432,7 @@ static ssize_t kone_sysfs_show_weight(struct device *dev,
if (retval)
return retval;
- return snprintf(buf, PAGE_SIZE, "%d\n", weight);
+ return sysfs_emit(buf, "%d\n", weight);
}
static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
@@ -444,7 +441,7 @@ static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
+ return sysfs_emit(buf, "%d\n", kone->firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, kone_sysfs_show_firmware_version,
NULL);
@@ -454,7 +451,7 @@ static ssize_t kone_sysfs_show_tcu(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
+ return sysfs_emit(buf, "%d\n", kone->settings.tcu);
}
static int kone_tcu_command(struct usb_device *usb_dev, int number)
@@ -556,7 +553,7 @@ static ssize_t kone_sysfs_show_startup_profile(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile);
+ return sysfs_emit(buf, "%d\n", kone->settings.startup_profile);
}
static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
@@ -637,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,
@@ -657,6 +654,12 @@ static const struct attribute_group *kone_groups[] = {
NULL,
};
+/* kone_class is used for creating sysfs attributes via roccat char device */
+static const struct class kone_class = {
+ .name = "kone",
+ .dev_groups = kone_groups,
+};
+
static int kone_init_kone_device_struct(struct usb_device *usb_dev,
struct kone_device *kone)
{
@@ -712,8 +715,8 @@ static int kone_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(kone_class, hdev,
- sizeof(struct kone_roccat_report));
+ retval = roccat_connect(&kone_class, hdev,
+ sizeof(struct kone_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
/* be tolerant about not getting chrdev */
@@ -890,21 +893,20 @@ static int __init kone_init(void)
int retval;
/* class name has to be same as driver name */
- kone_class = class_create("kone");
- if (IS_ERR(kone_class))
- return PTR_ERR(kone_class);
- kone_class->dev_groups = kone_groups;
+ retval = class_register(&kone_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&kone_driver);
if (retval)
- class_destroy(kone_class);
+ class_unregister(&kone_class);
return retval;
}
static void __exit kone_exit(void)
{
hid_unregister_driver(&kone_driver);
- class_destroy(kone_class);
+ class_unregister(&kone_class);
}
module_init(kone_init);
diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c
index 97b83b6f53dd..77d45d36421a 100644
--- a/drivers/hid/hid-roccat-koneplus.c
+++ b/drivers/hid/hid-roccat-koneplus.c
@@ -26,8 +26,6 @@
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
-static struct class *koneplus_class;
-
static void koneplus_profile_activated(struct koneplus_device *koneplus,
uint new_profile)
{
@@ -130,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); \
@@ -139,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); \
@@ -152,7 +150,7 @@ 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, \
@@ -161,7 +159,7 @@ static struct bin_attribute bin_attr_##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, \
@@ -169,7 +167,7 @@ static struct bin_attribute bin_attr_##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 \
@@ -185,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));
@@ -203,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));
@@ -221,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 = 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 = koneplus_sysfs_read_profilex_buttons, \
.private = &profile_numbers[number-1], \
};
PROFILE_ATTR(1);
@@ -244,7 +242,7 @@ static ssize_t koneplus_sysfs_show_actual_profile(struct device *dev,
{
struct koneplus_device *koneplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", koneplus->actual_profile);
+ return sysfs_emit(buf, "%d\n", koneplus->actual_profile);
}
static ssize_t koneplus_sysfs_set_actual_profile(struct device *dev,
@@ -311,7 +309,7 @@ static ssize_t koneplus_sysfs_show_firmware_version(struct device *dev,
&info, KONEPLUS_SIZE_INFO);
mutex_unlock(&koneplus->koneplus_lock);
- return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+ return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
koneplus_sysfs_show_firmware_version, NULL);
@@ -323,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,
@@ -356,6 +354,11 @@ static const struct attribute_group *koneplus_groups[] = {
NULL,
};
+static const struct class koneplus_class = {
+ .name = "koneplus",
+ .dev_groups = koneplus_groups,
+};
+
static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev,
struct koneplus_device *koneplus)
{
@@ -394,8 +397,8 @@ static int koneplus_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(koneplus_class, hdev,
- sizeof(struct koneplus_roccat_report));
+ retval = roccat_connect(&koneplus_class, hdev,
+ sizeof(struct koneplus_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
@@ -549,21 +552,20 @@ static int __init koneplus_init(void)
int retval;
/* class name has to be same as driver name */
- koneplus_class = class_create("koneplus");
- if (IS_ERR(koneplus_class))
- return PTR_ERR(koneplus_class);
- koneplus_class->dev_groups = koneplus_groups;
+ retval = class_register(&koneplus_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&koneplus_driver);
if (retval)
- class_destroy(koneplus_class);
+ class_unregister(&koneplus_class);
return retval;
}
static void __exit koneplus_exit(void)
{
hid_unregister_driver(&koneplus_driver);
- class_destroy(koneplus_class);
+ class_unregister(&koneplus_class);
}
module_init(koneplus_init);
diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c
index a297756f2410..027bfc55ef9c 100644
--- a/drivers/hid/hid-roccat-konepure.c
+++ b/drivers/hid/hid-roccat-konepure.c
@@ -36,8 +36,6 @@ struct konepure_mouse_report_button {
uint8_t unknown[2];
} __packed;
-static struct class *konepure_class;
-
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(actual_profile, 0x05, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_settings, 0x06, 0x1f);
@@ -49,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,
@@ -72,6 +70,11 @@ static const struct attribute_group *konepure_groups[] = {
NULL,
};
+static const struct class konepure_class = {
+ .name = "konepure",
+ .dev_groups = konepure_groups,
+};
+
static int konepure_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -98,8 +101,8 @@ static int konepure_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(konepure_class, hdev,
- sizeof(struct konepure_mouse_report_button));
+ retval = roccat_connect(&konepure_class, hdev,
+ sizeof(struct konepure_mouse_report_button));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
@@ -207,21 +210,20 @@ static int __init konepure_init(void)
{
int retval;
- konepure_class = class_create("konepure");
- if (IS_ERR(konepure_class))
- return PTR_ERR(konepure_class);
- konepure_class->dev_groups = konepure_groups;
+ retval = class_register(&konepure_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&konepure_driver);
if (retval)
- class_destroy(konepure_class);
+ class_unregister(&konepure_class);
return retval;
}
static void __exit konepure_exit(void)
{
hid_unregister_driver(&konepure_driver);
- class_destroy(konepure_class);
+ class_unregister(&konepure_class);
}
module_init(konepure_init);
diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c
index 1a1d96e11683..a66f1b4730f3 100644
--- a/drivers/hid/hid-roccat-kovaplus.c
+++ b/drivers/hid/hid-roccat-kovaplus.c
@@ -24,8 +24,6 @@
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
-static struct class *kovaplus_class;
-
static uint kovaplus_convert_event_cpi(uint value)
{
return (value == 7 ? 4 : (value == 4 ? 3 : value));
@@ -173,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); \
@@ -182,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); \
@@ -195,7 +193,7 @@ 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, \
@@ -204,7 +202,7 @@ static struct bin_attribute bin_attr_##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 \
@@ -215,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));
@@ -233,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));
@@ -251,13 +249,13 @@ 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, \
.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, \
@@ -274,7 +272,7 @@ static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);
+ return sysfs_emit(buf, "%d\n", kovaplus->actual_profile);
}
static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
@@ -327,7 +325,7 @@ static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);
+ return sysfs_emit(buf, "%d\n", kovaplus->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);
@@ -336,7 +334,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);
+ return sysfs_emit(buf, "%d\n", kovaplus->actual_x_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_x, 0440,
kovaplus_sysfs_show_actual_sensitivity_x, NULL);
@@ -346,7 +344,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);
+ return sysfs_emit(buf, "%d\n", kovaplus->actual_y_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_y, 0440,
kovaplus_sysfs_show_actual_sensitivity_y, NULL);
@@ -367,7 +365,7 @@ static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,
&info, KOVAPLUS_SIZE_INFO);
mutex_unlock(&kovaplus->kovaplus_lock);
- return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+ return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
kovaplus_sysfs_show_firmware_version, NULL);
@@ -381,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,
@@ -409,6 +407,11 @@ static const struct attribute_group *kovaplus_groups[] = {
NULL,
};
+static const struct class kovaplus_class = {
+ .name = "kovaplus",
+ .dev_groups = kovaplus_groups,
+};
+
static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev,
struct kovaplus_device *kovaplus)
{
@@ -463,8 +466,8 @@ static int kovaplus_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(kovaplus_class, hdev,
- sizeof(struct kovaplus_roccat_report));
+ retval = roccat_connect(&kovaplus_class, hdev,
+ sizeof(struct kovaplus_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
} else {
@@ -638,21 +641,20 @@ static int __init kovaplus_init(void)
{
int retval;
- kovaplus_class = class_create("kovaplus");
- if (IS_ERR(kovaplus_class))
- return PTR_ERR(kovaplus_class);
- kovaplus_class->dev_groups = kovaplus_groups;
+ retval = class_register(&kovaplus_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&kovaplus_driver);
if (retval)
- class_destroy(kovaplus_class);
+ class_unregister(&kovaplus_class);
return retval;
}
static void __exit kovaplus_exit(void)
{
hid_unregister_driver(&kovaplus_driver);
- class_destroy(kovaplus_class);
+ class_unregister(&kovaplus_class);
}
module_init(kovaplus_init);
diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c
index d5ddf0d68346..45e30549c236 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,7 +85,7 @@ 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, \
diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c
index 15528c3b013c..de2da6086e0b 100644
--- a/drivers/hid/hid-roccat-pyra.c
+++ b/drivers/hid/hid-roccat-pyra.c
@@ -26,9 +26,6 @@
static uint profile_numbers[5] = {0, 1, 2, 3, 4};
-/* pyra_class is used for creating sysfs attributes via roccat char device */
-static struct class *pyra_class;
-
static void profile_activated(struct pyra_device *pyra,
unsigned int new_profile)
{
@@ -132,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); \
@@ -141,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); \
@@ -154,7 +151,7 @@ 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, \
@@ -163,15 +160,15 @@ static struct bin_attribute bin_attr_##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, \
+ .size_new = PYRA_SIZE_ ## THINGY, \
.read = 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 \
@@ -183,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));
@@ -201,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));
@@ -219,13 +216,13 @@ 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, \
.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, \
@@ -238,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));
@@ -276,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);
@@ -286,7 +283,7 @@ static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev,
{
struct pyra_device *pyra =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
- return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi);
+ return sysfs_emit(buf, "%d\n", pyra->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL);
@@ -303,7 +300,7 @@ static ssize_t pyra_sysfs_show_actual_profile(struct device *dev,
&settings, PYRA_SIZE_SETTINGS);
mutex_unlock(&pyra->pyra_lock);
- return snprintf(buf, PAGE_SIZE, "%d\n", settings.startup_profile);
+ return sysfs_emit(buf, "%d\n", settings.startup_profile);
}
static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
static DEVICE_ATTR(startup_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
@@ -324,7 +321,7 @@ static ssize_t pyra_sysfs_show_firmware_version(struct device *dev,
&info, PYRA_SIZE_INFO);
mutex_unlock(&pyra->pyra_lock);
- return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+ return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, pyra_sysfs_show_firmware_version,
NULL);
@@ -337,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,
@@ -366,6 +363,12 @@ static const struct attribute_group *pyra_groups[] = {
NULL,
};
+/* pyra_class is used for creating sysfs attributes via roccat char device */
+static const struct class pyra_class = {
+ .name = "pyra",
+ .dev_groups = pyra_groups,
+};
+
static int pyra_init_pyra_device_struct(struct usb_device *usb_dev,
struct pyra_device *pyra)
{
@@ -413,7 +416,7 @@ static int pyra_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(pyra_class, hdev,
+ retval = roccat_connect(&pyra_class, hdev,
sizeof(struct pyra_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
@@ -585,21 +588,20 @@ static int __init pyra_init(void)
int retval;
/* class name has to be same as driver name */
- pyra_class = class_create("pyra");
- if (IS_ERR(pyra_class))
- return PTR_ERR(pyra_class);
- pyra_class->dev_groups = pyra_groups;
+ retval = class_register(&pyra_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&pyra_driver);
if (retval)
- class_destroy(pyra_class);
+ class_unregister(&pyra_class);
return retval;
}
static void __exit pyra_exit(void)
{
hid_unregister_driver(&pyra_driver);
- class_destroy(pyra_class);
+ class_unregister(&pyra_class);
}
module_init(pyra_init);
diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c
index 0eb17a3b925d..36911c9da4fe 100644
--- a/drivers/hid/hid-roccat-ryos.c
+++ b/drivers/hid/hid-roccat-ryos.c
@@ -28,8 +28,6 @@ struct ryos_report_special {
uint8_t data[4];
} __packed;
-static struct class *ryos_class;
-
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x05, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_primary, 0x06, 0x7d);
@@ -49,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,
@@ -80,6 +78,11 @@ static const struct attribute_group *ryos_groups[] = {
NULL,
};
+static const struct class ryos_class = {
+ .name = "ryos",
+ .dev_groups = ryos_groups,
+};
+
static int ryos_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -106,7 +109,7 @@ static int ryos_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(ryos_class, hdev,
+ retval = roccat_connect(&ryos_class, hdev,
sizeof(struct ryos_report_special));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
@@ -216,21 +219,20 @@ static int __init ryos_init(void)
{
int retval;
- ryos_class = class_create("ryos");
- if (IS_ERR(ryos_class))
- return PTR_ERR(ryos_class);
- ryos_class->dev_groups = ryos_groups;
+ retval = class_register(&ryos_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&ryos_driver);
if (retval)
- class_destroy(ryos_class);
+ class_unregister(&ryos_class);
return retval;
}
static void __exit ryos_exit(void)
{
hid_unregister_driver(&ryos_driver);
- class_destroy(ryos_class);
+ class_unregister(&ryos_class);
}
module_init(ryos_init);
diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c
index 93be7acef673..fb2e464c3ada 100644
--- a/drivers/hid/hid-roccat-savu.c
+++ b/drivers/hid/hid-roccat-savu.c
@@ -22,8 +22,6 @@
#include "hid-roccat-common.h"
#include "hid-roccat-savu.h"
-static struct class *savu_class;
-
ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x4, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x5, 0x03);
ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(general, 0x6, 0x10);
@@ -32,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,
@@ -52,6 +50,11 @@ static const struct attribute_group *savu_groups[] = {
NULL,
};
+static const struct class savu_class = {
+ .name = "savu",
+ .dev_groups = savu_groups,
+};
+
static int savu_init_specials(struct hid_device *hdev)
{
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
@@ -78,7 +81,7 @@ static int savu_init_specials(struct hid_device *hdev)
goto exit_free;
}
- retval = roccat_connect(savu_class, hdev,
+ retval = roccat_connect(&savu_class, hdev,
sizeof(struct savu_roccat_report));
if (retval < 0) {
hid_err(hdev, "couldn't init char dev\n");
@@ -204,21 +207,20 @@ static int __init savu_init(void)
{
int retval;
- savu_class = class_create("savu");
- if (IS_ERR(savu_class))
- return PTR_ERR(savu_class);
- savu_class->dev_groups = savu_groups;
+ retval = class_register(&savu_class);
+ if (retval)
+ return retval;
retval = hid_register_driver(&savu_driver);
if (retval)
- class_destroy(savu_class);
+ class_unregister(&savu_class);
return retval;
}
static void __exit savu_exit(void)
{
hid_unregister_driver(&savu_driver);
- class_destroy(savu_class);
+ class_unregister(&savu_class);
}
module_init(savu_init);
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
index 6da80e442fdd..c7f7562e22e5 100644
--- a/drivers/hid/hid-roccat.c
+++ b/drivers/hid/hid-roccat.c
@@ -295,7 +295,7 @@ EXPORT_SYMBOL_GPL(roccat_report_event);
* Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on
* success, a negative error code on failure.
*/
-int roccat_connect(struct class *klass, struct hid_device *hid, int report_size)
+int roccat_connect(const struct class *klass, struct hid_device *hid, int report_size)
{
unsigned int minor;
struct roccat_device *device;
diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c
index b84e975977c4..6fe7c087c594 100644
--- a/drivers/hid/hid-saitek.c
+++ b/drivers/hid/hid-saitek.c
@@ -66,7 +66,7 @@ static int saitek_probe(struct hid_device *hdev,
return 0;
}
-static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct saitek_sc *ssc = hid_get_drvdata(hdev);
@@ -204,4 +204,5 @@ static struct hid_driver saitek_driver = {
};
module_hid_driver(saitek_driver);
+MODULE_DESCRIPTION("HID driver for Saitek devices.");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-samsung.c b/drivers/hid/hid-samsung.c
index cf5992e97094..f3908a9e04e6 100644
--- a/drivers/hid/hid-samsung.c
+++ b/drivers/hid/hid-samsung.c
@@ -58,33 +58,25 @@ static inline void samsung_irda_dev_trace(struct hid_device *hdev,
static __u8 *samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
- if (*rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 &&
- rdesc[177] == 0x75 && rdesc[178] == 0x30 &&
- rdesc[179] == 0x95 && rdesc[180] == 0x01 &&
+ if (*rsize == 184 && !memcmp(&rdesc[175], "\x25\x40\x75\x30\x95\x01", 6) &&
rdesc[182] == 0x40) {
samsung_irda_dev_trace(hdev, 184);
rdesc[176] = 0xff;
rdesc[178] = 0x08;
rdesc[180] = 0x06;
rdesc[182] = 0x42;
- } else
- if (*rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 &&
- rdesc[194] == 0x25 && rdesc[195] == 0x12) {
+ } else if (*rsize == 203 && !memcmp(&rdesc[192], "\x15\x00\x25\x12", 4)) {
samsung_irda_dev_trace(hdev, 203);
- rdesc[193] = 0x1;
- rdesc[195] = 0xf;
- } else
- if (*rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 &&
- rdesc[126] == 0x25 && rdesc[127] == 0x11) {
+ rdesc[193] = 0x01;
+ rdesc[195] = 0x0f;
+ } else if (*rsize == 135 && !memcmp(&rdesc[124], "\x15\x00\x25\x11", 4)) {
samsung_irda_dev_trace(hdev, 135);
- rdesc[125] = 0x1;
- rdesc[127] = 0xe;
- } else
- if (*rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 &&
- rdesc[162] == 0x25 && rdesc[163] == 0x01) {
+ rdesc[125] = 0x01;
+ rdesc[127] = 0x0e;
+ } else if (*rsize == 171 && !memcmp(&rdesc[160], "\x15\x00\x25\x01", 4)) {
samsung_irda_dev_trace(hdev, 171);
- rdesc[161] = 0x1;
- rdesc[163] = 0x3;
+ rdesc[161] = 0x01;
+ rdesc[163] = 0x03;
}
return rdesc;
}
@@ -99,7 +91,7 @@ static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
- if (1 != ifnum || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
+ if (ifnum != 1 || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
return 0;
dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n",
@@ -107,17 +99,39 @@ static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
switch (usage->hid & HID_USAGE) {
/* report 2 */
- case 0x183: samsung_kbd_mouse_map_key_clear(KEY_MEDIA); break;
- case 0x195: samsung_kbd_mouse_map_key_clear(KEY_EMAIL); break;
- case 0x196: samsung_kbd_mouse_map_key_clear(KEY_CALC); break;
- case 0x197: samsung_kbd_mouse_map_key_clear(KEY_COMPUTER); break;
- case 0x22b: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break;
- case 0x22c: samsung_kbd_mouse_map_key_clear(KEY_WWW); break;
- case 0x22d: samsung_kbd_mouse_map_key_clear(KEY_BACK); break;
- case 0x22e: samsung_kbd_mouse_map_key_clear(KEY_FORWARD); break;
- case 0x22f: samsung_kbd_mouse_map_key_clear(KEY_FAVORITES); break;
- case 0x230: samsung_kbd_mouse_map_key_clear(KEY_REFRESH); break;
- case 0x231: samsung_kbd_mouse_map_key_clear(KEY_STOP); break;
+ case 0x183:
+ samsung_kbd_mouse_map_key_clear(KEY_MEDIA);
+ break;
+ case 0x195:
+ samsung_kbd_mouse_map_key_clear(KEY_EMAIL);
+ break;
+ case 0x196:
+ samsung_kbd_mouse_map_key_clear(KEY_CALC);
+ break;
+ case 0x197:
+ samsung_kbd_mouse_map_key_clear(KEY_COMPUTER);
+ break;
+ case 0x22b:
+ samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
+ break;
+ case 0x22c:
+ samsung_kbd_mouse_map_key_clear(KEY_WWW);
+ break;
+ case 0x22d:
+ samsung_kbd_mouse_map_key_clear(KEY_BACK);
+ break;
+ case 0x22e:
+ samsung_kbd_mouse_map_key_clear(KEY_FORWARD);
+ break;
+ case 0x22f:
+ samsung_kbd_mouse_map_key_clear(KEY_FAVORITES);
+ break;
+ case 0x230:
+ samsung_kbd_mouse_map_key_clear(KEY_REFRESH);
+ break;
+ case 0x231:
+ samsung_kbd_mouse_map_key_clear(KEY_STOP);
+ break;
default:
return 0;
}
@@ -125,10 +139,340 @@ static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
return 1;
}
-static __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static int samsung_kbd_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if (!(HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE) ||
+ HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)))
+ return 0;
+
+ dbg_hid("samsung wireless keyboard input mapping event [0x%x]\n",
+ usage->hid & HID_USAGE);
+
+ if (HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)) {
+ set_bit(EV_REP, hi->input->evbit);
+ switch (usage->hid & HID_USAGE) {
+ case 0x32:
+ samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH);
+ break;
+ case 0x64:
+ samsung_kbd_mouse_map_key_clear(KEY_102ND);
+ break;
+ /* Only for BR keyboard */
+ case 0x87:
+ samsung_kbd_mouse_map_key_clear(KEY_RO);
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
+ switch (usage->hid & HID_USAGE) {
+ /* report 2 */
+ /* MENU */
+ case 0x040:
+ samsung_kbd_mouse_map_key_clear(KEY_MENU);
+ break;
+ case 0x18a:
+ samsung_kbd_mouse_map_key_clear(KEY_MAIL);
+ break;
+ case 0x196:
+ samsung_kbd_mouse_map_key_clear(KEY_WWW);
+ break;
+ case 0x19e:
+ samsung_kbd_mouse_map_key_clear(KEY_SCREENLOCK);
+ break;
+ case 0x221:
+ samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
+ break;
+ case 0x223:
+ samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
+ break;
+ /* Smtart Voice Key */
+ case 0x300:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY13);
+ break;
+ /* RECENTAPPS */
+ case 0x301:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY1);
+ break;
+ /* APPLICATION */
+ case 0x302:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY2);
+ break;
+ /* Voice search */
+ case 0x305:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY4);
+ break;
+ /* QPANEL on/off */
+ case 0x306:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY5);
+ break;
+ /* SIP on/off */
+ case 0x307:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY3);
+ break;
+ /* LANG */
+ case 0x308:
+ samsung_kbd_mouse_map_key_clear(KEY_LANGUAGE);
+ break;
+ case 0x30a:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
+ break;
+ case 0x30b:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int samsung_gamepad_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if (!(HID_UP_BUTTON == (usage->hid & HID_USAGE_PAGE) ||
+ HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)))
+ return 0;
+
+ dbg_hid("samsung wireless gamepad input mapping event [0x%x], %ld, %ld, [0x%x]\n",
+ usage->hid & HID_USAGE, hi->input->evbit[0], hi->input->absbit[0], usage->hid & HID_USAGE_PAGE);
+
+ if (HID_UP_BUTTON == (usage->hid & HID_USAGE_PAGE)) {
+ switch (usage->hid & HID_USAGE) {
+ case 0x01:
+ samsung_kbd_mouse_map_key_clear(BTN_A);
+ break;
+ case 0x02:
+ samsung_kbd_mouse_map_key_clear(BTN_B);
+ break;
+ case 0x03:
+ samsung_kbd_mouse_map_key_clear(BTN_C);
+ break;
+ case 0x04:
+ samsung_kbd_mouse_map_key_clear(BTN_X);
+ break;
+ case 0x05:
+ samsung_kbd_mouse_map_key_clear(BTN_Y);
+ break;
+ case 0x06:
+ samsung_kbd_mouse_map_key_clear(BTN_Z);
+ break;
+ case 0x07:
+ samsung_kbd_mouse_map_key_clear(BTN_TL);
+ break;
+ case 0x08:
+ samsung_kbd_mouse_map_key_clear(BTN_TR);
+ break;
+ case 0x09:
+ samsung_kbd_mouse_map_key_clear(BTN_TL2);
+ break;
+ case 0x0a:
+ samsung_kbd_mouse_map_key_clear(BTN_TR2);
+ break;
+ case 0x0b:
+ samsung_kbd_mouse_map_key_clear(BTN_SELECT);
+ break;
+ case 0x0c:
+ samsung_kbd_mouse_map_key_clear(BTN_START);
+ break;
+ case 0x0d:
+ samsung_kbd_mouse_map_key_clear(BTN_MODE);
+ break;
+ case 0x0e:
+ samsung_kbd_mouse_map_key_clear(BTN_THUMBL);
+ break;
+ case 0x0f:
+ samsung_kbd_mouse_map_key_clear(BTN_THUMBR);
+ break;
+ case 0x10:
+ samsung_kbd_mouse_map_key_clear(0x13f);
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
+ switch (usage->hid & HID_USAGE) {
+ case 0x040:
+ samsung_kbd_mouse_map_key_clear(KEY_MENU);
+ break;
+ case 0x223:
+ samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
+ break;
+ case 0x224:
+ samsung_kbd_mouse_map_key_clear(KEY_BACK);
+ break;
+
+ /* Screen Capture */
+ case 0x303:
+ samsung_kbd_mouse_map_key_clear(KEY_SYSRQ);
+ break;
+
+ default:
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int samsung_actionmouse_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+
+ dbg_hid("samsung wireless actionmouse input mapping event [0x%x], [0x%x], %ld, %ld, [0x%x]\n",
+ usage->hid, usage->hid & HID_USAGE, hi->input->evbit[0], hi->input->absbit[0],
+ usage->hid & HID_USAGE_PAGE);
+
+ if (((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) && ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON))
+ return 0;
+
+ switch (usage->hid & HID_USAGE) {
+ case 0x301:
+ samsung_kbd_mouse_map_key_clear(254);
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+static int samsung_universal_kbd_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ if (!(HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE) ||
+ HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)))
+ return 0;
+
+ dbg_hid("samsung wireless keyboard input mapping event [0x%x]\n",
+ usage->hid & HID_USAGE);
+
+ if (HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)) {
+ set_bit(EV_REP, hi->input->evbit);
+ switch (usage->hid & HID_USAGE) {
+ case 0x32:
+ samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH);
+ break;
+ case 0x64:
+ samsung_kbd_mouse_map_key_clear(KEY_102ND);
+ break;
+ /* Only for BR keyboard */
+ case 0x87:
+ samsung_kbd_mouse_map_key_clear(KEY_RO);
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
+ switch (usage->hid & HID_USAGE) {
+ /* report 2 */
+ /* MENU */
+ case 0x040:
+ samsung_kbd_mouse_map_key_clear(KEY_MENU);
+ break;
+ case 0x18a:
+ samsung_kbd_mouse_map_key_clear(KEY_MAIL);
+ break;
+ case 0x196:
+ samsung_kbd_mouse_map_key_clear(KEY_WWW);
+ break;
+ case 0x19e:
+ samsung_kbd_mouse_map_key_clear(KEY_SCREENLOCK);
+ break;
+ case 0x221:
+ samsung_kbd_mouse_map_key_clear(KEY_SEARCH);
+ break;
+ case 0x223:
+ samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE);
+ break;
+ /* RECENTAPPS */
+ case 0x301:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY1);
+ break;
+ /* APPLICATION */
+ case 0x302:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY2);
+ break;
+ /* Voice search */
+ case 0x305:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY4);
+ break;
+ /* QPANEL on/off */
+ case 0x306:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY5);
+ break;
+ /* SIP on/off */
+ case 0x307:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY3);
+ break;
+ /* LANG */
+ case 0x308:
+ samsung_kbd_mouse_map_key_clear(KEY_LANGUAGE);
+ break;
+ case 0x30a:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
+ break;
+ case 0x070:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN);
+ break;
+ case 0x30b:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
+ break;
+ case 0x06f:
+ samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP);
+ break;
+ /* S-Finder */
+ case 0x304:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY7);
+ break;
+ /* Screen Capture */
+ case 0x303:
+ samsung_kbd_mouse_map_key_clear(KEY_SYSRQ);
+ break;
+ /* Multi Window */
+ case 0x309:
+ samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY9);
+ break;
+ /* HotKey App 1 */
+ case 0x071:
+ samsung_kbd_mouse_map_key_clear(0x2f5);
+ break;
+ /* HotKey App 2 */
+ case 0x072:
+ samsung_kbd_mouse_map_key_clear(0x2f6);
+ break;
+ /* HotKey App 3 */
+ case 0x073:
+ samsung_kbd_mouse_map_key_clear(0x2f7);
+ break;
+ /* Dex */
+ case 0x06e:
+ samsung_kbd_mouse_map_key_clear(0x2bd);
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static const __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
- if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product)
+ if (hdev->product == USB_DEVICE_ID_SAMSUNG_IR_REMOTE && hid_is_usb(hdev))
rdesc = samsung_irda_report_fixup(hdev, rdesc, rsize);
return rdesc;
}
@@ -139,9 +483,24 @@ static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi,
{
int ret = 0;
- if (USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE == hdev->product)
+ if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE && hid_is_usb(hdev))
ret = samsung_kbd_mouse_input_mapping(hdev,
hi, field, usage, bit, max);
+ else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD)
+ ret = samsung_kbd_input_mapping(hdev,
+ hi, field, usage, bit, max);
+ else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD)
+ ret = samsung_gamepad_input_mapping(hdev,
+ hi, field, usage, bit, max);
+ else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE)
+ ret = samsung_actionmouse_input_mapping(hdev,
+ hi, field, usage, bit, max);
+ else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD)
+ ret = samsung_universal_kbd_input_mapping(hdev,
+ hi, field, usage, bit, max);
+ else if (hdev->product == USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD)
+ ret = samsung_universal_kbd_input_mapping(hdev,
+ hi, field, usage, bit, max);
return ret;
}
@@ -152,16 +511,17 @@ static int samsung_probe(struct hid_device *hdev,
int ret;
unsigned int cmask = HID_CONNECT_DEFAULT;
- if (!hid_is_usb(hdev))
- return -EINVAL;
-
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_free;
}
- if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) {
+ if (hdev->product == USB_DEVICE_ID_SAMSUNG_IR_REMOTE) {
+ if (!hid_is_usb(hdev)) {
+ ret = -EINVAL;
+ goto err_free;
+ }
if (hdev->rsize == 184) {
/* disable hidinput, force hiddev */
cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
@@ -183,6 +543,11 @@ err_free:
static const struct hid_device_id samsung_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_GAMEPAD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_ACTIONMOUSE) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD) },
{ }
};
MODULE_DEVICE_TABLE(hid, samsung_devices);
@@ -196,4 +561,5 @@ static struct hid_driver samsung_driver = {
};
module_hid_driver(samsung_driver);
+MODULE_DESCRIPTION("HID driver for some samsung \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-semitek.c b/drivers/hid/hid-semitek.c
index ba6607d5e051..4fbec5fd87ce 100644
--- a/drivers/hid/hid-semitek.c
+++ b/drivers/hid/hid-semitek.c
@@ -11,8 +11,8 @@
#include "hid-ids.h"
-static __u8 *semitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *semitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
/* In the report descriptor for interface 2, fix the incorrect
description of report ID 0x04 (the report contains a
@@ -37,4 +37,5 @@ static struct hid_driver semitek_driver = {
};
module_hid_driver(semitek_driver);
+MODULE_DESCRIPTION("HID driver for Semitek keyboards");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index d85398721659..761760668f6d 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -155,7 +155,7 @@ static ssize_t enable_sensor_show(struct device *dev,
{
struct hid_sensor_custom *sensor_inst = dev_get_drvdata(dev);
- return sprintf(buf, "%d\n", sensor_inst->enable);
+ return sysfs_emit(buf, "%d\n", sensor_inst->enable);
}
static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
@@ -372,14 +372,13 @@ static ssize_t show_value(struct device *dev, struct device_attribute *attr,
sizeof(struct hid_custom_usage_desc),
usage_id_cmp);
if (usage_desc)
- return snprintf(buf, PAGE_SIZE, "%s\n",
- usage_desc->desc);
+ return sysfs_emit(buf, "%s\n", usage_desc->desc);
else
- return sprintf(buf, "not-specified\n");
+ return sysfs_emit(buf, "not-specified\n");
} else
return -EINVAL;
- return sprintf(buf, "%d\n", value);
+ return sysfs_emit(buf, "%d\n", value);
}
static ssize_t store_value(struct device *dev, struct device_attribute *attr,
@@ -733,7 +732,7 @@ static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
sensor_inst->custom_dev.minor = MISC_DYNAMIC_MINOR;
sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev);
- sensor_inst->custom_dev.fops = &hid_sensor_custom_fops,
+ sensor_inst->custom_dev.fops = &hid_sensor_custom_fops;
ret = misc_register(&sensor_inst->custom_dev);
if (ret) {
kfifo_free(&sensor_inst->data_fifo);
@@ -947,7 +946,7 @@ hid_sensor_register_platform_device(struct platform_device *pdev,
memcpy(real_usage, match->luid, 4);
- /* usage id are all lowcase */
+ /* usage id are all lowercase */
for (c = real_usage; *c != '\0'; c++)
*c = tolower(*c);
@@ -1032,14 +1031,14 @@ err_remove_callback:
return ret;
}
-static int hid_sensor_custom_remove(struct platform_device *pdev)
+static void hid_sensor_custom_remove(struct platform_device *pdev)
{
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
if (sensor_inst->custom_pdev) {
platform_device_unregister(sensor_inst->custom_pdev);
- return 0;
+ return;
}
hid_sensor_custom_dev_if_remove(sensor_inst);
@@ -1047,8 +1046,6 @@ static int hid_sensor_custom_remove(struct platform_device *pdev)
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
sensor_hub_remove_callback(hsdev, hsdev->usage);
-
- return 0;
}
static const struct platform_device_id hid_sensor_custom_ids[] = {
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index 83237b86c8ff..4c94c03cb573 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -580,7 +580,7 @@ void sensor_hub_device_close(struct hid_sensor_hub_device *hsdev)
}
EXPORT_SYMBOL_GPL(sensor_hub_device_close);
-static __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
/*
@@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev,
}
INIT_LIST_HEAD(&hdev->inputs);
- ret = hid_hw_start(hdev, 0);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | HID_CONNECT_DRIVER);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
@@ -730,23 +730,30 @@ err_stop_hw:
return ret;
}
+static int sensor_hub_finalize_pending_fn(struct device *dev, void *data)
+{
+ struct hid_sensor_hub_device *hsdev = dev->platform_data;
+
+ if (hsdev->pending.status)
+ complete(&hsdev->pending.ready);
+
+ return 0;
+}
+
static void sensor_hub_remove(struct hid_device *hdev)
{
struct sensor_hub_data *data = hid_get_drvdata(hdev);
unsigned long flags;
- int i;
hid_dbg(hdev, " hardware removed\n");
hid_hw_close(hdev);
hid_hw_stop(hdev);
+
spin_lock_irqsave(&data->lock, flags);
- for (i = 0; i < data->hid_sensor_client_cnt; ++i) {
- struct hid_sensor_hub_device *hsdev =
- data->hid_sensor_hub_client_devs[i].platform_data;
- if (hsdev->pending.status)
- complete(&hsdev->pending.ready);
- }
+ device_for_each_child(&hdev->dev, NULL,
+ sensor_hub_finalize_pending_fn);
spin_unlock_irqrestore(&data->lock, flags);
+
mfd_remove_devices(&hdev->dev);
mutex_destroy(&data->mutex);
}
diff --git a/drivers/hid/hid-sigmamicro.c b/drivers/hid/hid-sigmamicro.c
index 2e7058ac0e9d..c87276d7ba0d 100644
--- a/drivers/hid/hid-sigmamicro.c
+++ b/drivers/hid/hid-sigmamicro.c
@@ -99,8 +99,8 @@ static const __u8 sm_0059_rdesc[] = {
0xc0, /* End Collection 166 */
};
-static __u8 *sm_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *sm_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
if (*rsize == sizeof(sm_0059_rdesc) &&
!memcmp(sm_0059_rdesc, rdesc, *rsize)) {
diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c
index 49971be7c3ff..d3a777f52a3f 100644
--- a/drivers/hid/hid-sjoy.c
+++ b/drivers/hid/hid-sjoy.c
@@ -168,6 +168,7 @@ static struct hid_driver sjoy_driver = {
};
module_hid_driver(sjoy_driver);
+MODULE_DESCRIPTION("Force feedback support for SmartJoy PLUS PS2->USB adapter");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jussi Kivilinna");
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index dd942061fd77..b966e4044238 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -40,7 +40,7 @@
#include <linux/crc32.h>
#include <linux/usb.h>
#include <linux/timer.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include "hid-ids.h"
@@ -99,7 +99,7 @@ static const char ghl_ps4_magic_data[] = {
};
/* PS/3 Motion controller */
-static u8 motion_rdesc[] = {
+static const u8 motion_rdesc[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x04, /* Usage (Joystick), */
0xA1, 0x01, /* Collection (Application), */
@@ -195,7 +195,7 @@ static u8 motion_rdesc[] = {
0xC0 /* End Collection */
};
-static u8 ps3remote_rdesc[] = {
+static const u8 ps3remote_rdesc[] = {
0x05, 0x01, /* GUsagePage Generic Desktop */
0x09, 0x05, /* LUsage 0x05 [Game Pad] */
0xA1, 0x01, /* MCollection Application (mouse, keyboard) */
@@ -545,7 +545,7 @@ static void ghl_magic_poke_cb(struct urb *urb)
static void ghl_magic_poke(struct timer_list *t)
{
int ret;
- struct sony_sc *sc = from_timer(sc, t, ghl_poke_timer);
+ struct sony_sc *sc = timer_container_of(sc, t, ghl_poke_timer);
ret = usb_submit_urb(sc->ghl_urb, GFP_ATOMIC);
if (ret < 0)
@@ -599,15 +599,15 @@ static int guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc,
- unsigned int *rsize)
+static const u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc,
+ unsigned int *rsize)
{
*rsize = sizeof(motion_rdesc);
return motion_rdesc;
}
-static u8 *ps3remote_fixup(struct hid_device *hdev, u8 *rdesc,
- unsigned int *rsize)
+static const u8 *ps3remote_fixup(struct hid_device *hdev, u8 *rdesc,
+ unsigned int *rsize)
{
*rsize = sizeof(ps3remote_rdesc);
return ps3remote_rdesc;
@@ -743,7 +743,7 @@ static int sixaxis_mapping(struct hid_device *hdev, struct hid_input *hi,
return -1;
}
-static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
+static const u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
unsigned int *rsize)
{
struct sony_sc *sc = hid_get_drvdata(hdev);
@@ -1379,7 +1379,8 @@ static int sony_leds_init(struct sony_sc *sc)
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
u8 use_hw_blink[MAX_LEDS] = { 0 };
- BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
+ if (WARN_ON(!(sc->quirks & SONY_LED_SUPPORT)))
+ return -EINVAL;
if (sc->quirks & BUZZ_CONTROLLER) {
sc->led_count = 4;
@@ -1844,8 +1845,7 @@ static int sony_set_device_id(struct sony_sc *sc)
* All others are set to -1.
*/
if (sc->quirks & SIXAXIS_CONTROLLER) {
- ret = ida_simple_get(&sony_device_id_allocator, 0, 0,
- GFP_KERNEL);
+ ret = ida_alloc(&sony_device_id_allocator, GFP_KERNEL);
if (ret < 0) {
sc->device_id = -1;
return ret;
@@ -1861,7 +1861,7 @@ static int sony_set_device_id(struct sony_sc *sc)
static void sony_release_device_id(struct sony_sc *sc)
{
if (sc->device_id >= 0) {
- ida_simple_remove(&sony_device_id_allocator, sc->device_id);
+ ida_free(&sony_device_id_allocator, sc->device_id);
sc->device_id = -1;
}
}
@@ -2016,8 +2016,6 @@ static int sony_input_configured(struct hid_device *hdev,
} else if (sc->quirks & MOTION_CONTROLLER) {
sony_init_output_report(sc, motion_send_output_report);
- } else {
- ret = 0;
}
if (sc->quirks & SONY_LED_SUPPORT) {
@@ -2155,6 +2153,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
err:
+ usb_free_urb(sc->ghl_urb);
+
hid_hw_stop(hdev);
return ret;
}
@@ -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);
}
@@ -2309,4 +2309,5 @@ static void __exit sony_exit(void)
module_init(sony_init);
module_exit(sony_exit);
+MODULE_DESCRIPTION("HID driver for Sony / PS2 / PS3 / PS4 BD devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-speedlink.c b/drivers/hid/hid-speedlink.c
index 9e75f1aae0ca..22ee078c42c6 100644
--- a/drivers/hid/hid-speedlink.c
+++ b/drivers/hid/hid-speedlink.c
@@ -75,4 +75,5 @@ static struct hid_driver speedlink_driver = {
};
module_hid_driver(speedlink_driver);
+MODULE_DESCRIPTION("HID driver for Speedlink Vicious and Divine Cezanne (USB mouse)");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index b110818fc945..197126d6e081 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -45,6 +45,7 @@
#include <linux/power_supply.h>
#include "hid-ids.h"
+MODULE_DESCRIPTION("HID driver for Valve Steam Controller");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>");
@@ -66,66 +67,237 @@ static LIST_HEAD(steam_devices);
#define STEAM_DECK_TRIGGER_RESOLUTION 5461
/* Joystick runs are about 5 mm and 32768 units */
#define STEAM_DECK_JOYSTICK_RESOLUTION 6553
+/* Accelerometer has 16 bit resolution and a range of +/- 2g */
+#define STEAM_DECK_ACCEL_RES_PER_G 16384
+#define STEAM_DECK_ACCEL_RANGE 32768
+#define STEAM_DECK_ACCEL_FUZZ 32
+/* Gyroscope has 16 bit resolution and a range of +/- 2000 dps */
+#define STEAM_DECK_GYRO_RES_PER_DPS 16
+#define STEAM_DECK_GYRO_RANGE 32768
+#define STEAM_DECK_GYRO_FUZZ 1
#define STEAM_PAD_FUZZ 256
/*
* Commands that can be sent in a feature report.
- * Thanks to Valve for some valuable hints.
+ * Thanks to Valve and SDL for the names.
*/
-#define STEAM_CMD_SET_MAPPINGS 0x80
-#define STEAM_CMD_CLEAR_MAPPINGS 0x81
-#define STEAM_CMD_GET_MAPPINGS 0x82
-#define STEAM_CMD_GET_ATTRIB 0x83
-#define STEAM_CMD_GET_ATTRIB_LABEL 0x84
-#define STEAM_CMD_DEFAULT_MAPPINGS 0x85
-#define STEAM_CMD_FACTORY_RESET 0x86
-#define STEAM_CMD_WRITE_REGISTER 0x87
-#define STEAM_CMD_CLEAR_REGISTER 0x88
-#define STEAM_CMD_READ_REGISTER 0x89
-#define STEAM_CMD_GET_REGISTER_LABEL 0x8a
-#define STEAM_CMD_GET_REGISTER_MAX 0x8b
-#define STEAM_CMD_GET_REGISTER_DEFAULT 0x8c
-#define STEAM_CMD_SET_MODE 0x8d
-#define STEAM_CMD_DEFAULT_MOUSE 0x8e
-#define STEAM_CMD_FORCEFEEDBAK 0x8f
-#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
-#define STEAM_CMD_GET_SERIAL 0xae
-#define STEAM_CMD_HAPTIC_RUMBLE 0xeb
-
-/* Some useful register ids */
-#define STEAM_REG_LPAD_MODE 0x07
-#define STEAM_REG_RPAD_MODE 0x08
-#define STEAM_REG_RPAD_MARGIN 0x18
-#define STEAM_REG_LED 0x2d
-#define STEAM_REG_GYRO_MODE 0x30
-#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
-#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
-
-/* Raw event identifiers */
-#define STEAM_EV_INPUT_DATA 0x01
-#define STEAM_EV_CONNECT 0x03
-#define STEAM_EV_BATTERY 0x04
-#define STEAM_EV_DECK_INPUT_DATA 0x09
+enum {
+ ID_SET_DIGITAL_MAPPINGS = 0x80,
+ ID_CLEAR_DIGITAL_MAPPINGS = 0x81,
+ ID_GET_DIGITAL_MAPPINGS = 0x82,
+ ID_GET_ATTRIBUTES_VALUES = 0x83,
+ ID_GET_ATTRIBUTE_LABEL = 0x84,
+ ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85,
+ ID_FACTORY_RESET = 0x86,
+ ID_SET_SETTINGS_VALUES = 0x87,
+ ID_CLEAR_SETTINGS_VALUES = 0x88,
+ ID_GET_SETTINGS_VALUES = 0x89,
+ ID_GET_SETTING_LABEL = 0x8A,
+ ID_GET_SETTINGS_MAXS = 0x8B,
+ ID_GET_SETTINGS_DEFAULTS = 0x8C,
+ ID_SET_CONTROLLER_MODE = 0x8D,
+ ID_LOAD_DEFAULT_SETTINGS = 0x8E,
+ ID_TRIGGER_HAPTIC_PULSE = 0x8F,
+ ID_TURN_OFF_CONTROLLER = 0x9F,
+
+ ID_GET_DEVICE_INFO = 0xA1,
+
+ ID_CALIBRATE_TRACKPADS = 0xA7,
+ ID_RESERVED_0 = 0xA8,
+ ID_SET_SERIAL_NUMBER = 0xA9,
+ ID_GET_TRACKPAD_CALIBRATION = 0xAA,
+ ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB,
+ ID_GET_TRACKPAD_RAW_DATA = 0xAC,
+ ID_ENABLE_PAIRING = 0xAD,
+ ID_GET_STRING_ATTRIBUTE = 0xAE,
+ ID_RADIO_ERASE_RECORDS = 0xAF,
+ ID_RADIO_WRITE_RECORD = 0xB0,
+ ID_SET_DONGLE_SETTING = 0xB1,
+ ID_DONGLE_DISCONNECT_DEVICE = 0xB2,
+ ID_DONGLE_COMMIT_DEVICE = 0xB3,
+ ID_DONGLE_GET_WIRELESS_STATE = 0xB4,
+ ID_CALIBRATE_GYRO = 0xB5,
+ ID_PLAY_AUDIO = 0xB6,
+ ID_AUDIO_UPDATE_START = 0xB7,
+ ID_AUDIO_UPDATE_DATA = 0xB8,
+ ID_AUDIO_UPDATE_COMPLETE = 0xB9,
+ ID_GET_CHIPID = 0xBA,
+
+ ID_CALIBRATE_JOYSTICK = 0xBF,
+ ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0,
+ ID_SET_AUDIO_MAPPING = 0xC1,
+ ID_CHECK_GYRO_FW_LOAD = 0xC2,
+ ID_CALIBRATE_ANALOG = 0xC3,
+ ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4,
+
+ ID_RESET_IMU = 0xCE,
+
+ ID_TRIGGER_HAPTIC_CMD = 0xEA,
+ ID_TRIGGER_RUMBLE_CMD = 0xEB,
+};
+
+/* Settings IDs */
+enum {
+ /* 0 */
+ SETTING_MOUSE_SENSITIVITY,
+ SETTING_MOUSE_ACCELERATION,
+ SETTING_TRACKBALL_ROTATION_ANGLE,
+ SETTING_HAPTIC_INTENSITY_UNUSED,
+ SETTING_LEFT_GAMEPAD_STICK_ENABLED,
+ SETTING_RIGHT_GAMEPAD_STICK_ENABLED,
+ SETTING_USB_DEBUG_MODE,
+ SETTING_LEFT_TRACKPAD_MODE,
+ SETTING_RIGHT_TRACKPAD_MODE,
+ SETTING_MOUSE_POINTER_ENABLED,
+
+ /* 10 */
+ SETTING_DPAD_DEADZONE,
+ SETTING_MINIMUM_MOMENTUM_VEL,
+ SETTING_MOMENTUM_DECAY_AMMOUNT,
+ SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL,
+ SETTING_HAPTIC_INCREMENT,
+ SETTING_DPAD_ANGLE_SIN,
+ SETTING_DPAD_ANGLE_COS,
+ SETTING_MOMENTUM_VERTICAL_DIVISOR,
+ SETTING_MOMENTUM_MAXIMUM_VELOCITY,
+ SETTING_TRACKPAD_Z_ON,
+
+ /* 20 */
+ SETTING_TRACKPAD_Z_OFF,
+ SETTING_SENSITIVY_SCALE_AMMOUNT,
+ SETTING_LEFT_TRACKPAD_SECONDARY_MODE,
+ SETTING_RIGHT_TRACKPAD_SECONDARY_MODE,
+ SETTING_SMOOTH_ABSOLUTE_MOUSE,
+ SETTING_STEAMBUTTON_POWEROFF_TIME,
+ SETTING_UNUSED_1,
+ SETTING_TRACKPAD_OUTER_RADIUS,
+ SETTING_TRACKPAD_Z_ON_LEFT,
+ SETTING_TRACKPAD_Z_OFF_LEFT,
+
+ /* 30 */
+ SETTING_TRACKPAD_OUTER_SPIN_VEL,
+ SETTING_TRACKPAD_OUTER_SPIN_RADIUS,
+ SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY,
+ SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE,
+ SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL,
+ SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y,
+ SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED,
+ SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD,
+ SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT,
+ SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION,
+
+ /* 40 */
+ SETTING_RADIAL_MODE_ANGLE,
+ SETTING_HAPTIC_INTENSITY_MOUSE_MODE,
+ SETTING_LEFT_DPAD_REQUIRES_CLICK,
+ SETTING_RIGHT_DPAD_REQUIRES_CLICK,
+ SETTING_LED_BASELINE_BRIGHTNESS,
+ SETTING_LED_USER_BRIGHTNESS,
+ SETTING_ENABLE_RAW_JOYSTICK,
+ SETTING_ENABLE_FAST_SCAN,
+ SETTING_IMU_MODE,
+ SETTING_WIRELESS_PACKET_VERSION,
+
+ /* 50 */
+ SETTING_SLEEP_INACTIVITY_TIMEOUT,
+ SETTING_TRACKPAD_NOISE_THRESHOLD,
+ SETTING_LEFT_TRACKPAD_CLICK_PRESSURE,
+ SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE,
+ SETTING_LEFT_BUMPER_CLICK_PRESSURE,
+ SETTING_RIGHT_BUMPER_CLICK_PRESSURE,
+ SETTING_LEFT_GRIP_CLICK_PRESSURE,
+ SETTING_RIGHT_GRIP_CLICK_PRESSURE,
+ SETTING_LEFT_GRIP2_CLICK_PRESSURE,
+ SETTING_RIGHT_GRIP2_CLICK_PRESSURE,
+
+ /* 60 */
+ SETTING_PRESSURE_MODE,
+ SETTING_CONTROLLER_TEST_MODE,
+ SETTING_TRIGGER_MODE,
+ SETTING_TRACKPAD_Z_THRESHOLD,
+ SETTING_FRAME_RATE,
+ SETTING_TRACKPAD_FILT_CTRL,
+ SETTING_TRACKPAD_CLIP,
+ SETTING_DEBUG_OUTPUT_SELECT,
+ SETTING_TRIGGER_THRESHOLD_PERCENT,
+ SETTING_TRACKPAD_FREQUENCY_HOPPING,
+
+ /* 70 */
+ SETTING_HAPTICS_ENABLED,
+ SETTING_STEAM_WATCHDOG_ENABLE,
+ SETTING_TIMP_TOUCH_THRESHOLD_ON,
+ SETTING_TIMP_TOUCH_THRESHOLD_OFF,
+ SETTING_FREQ_HOPPING,
+ SETTING_TEST_CONTROL,
+ SETTING_HAPTIC_MASTER_GAIN_DB,
+ SETTING_THUMB_TOUCH_THRESH,
+ SETTING_DEVICE_POWER_STATUS,
+ SETTING_HAPTIC_INTENSITY,
+
+ /* 80 */
+ SETTING_STABILIZER_ENABLED,
+ SETTING_TIMP_MODE_MTE,
+};
+
+/* Input report identifiers */
+enum
+{
+ ID_CONTROLLER_STATE = 1,
+ ID_CONTROLLER_DEBUG = 2,
+ ID_CONTROLLER_WIRELESS = 3,
+ ID_CONTROLLER_STATUS = 4,
+ ID_CONTROLLER_DEBUG2 = 5,
+ ID_CONTROLLER_SECONDARY_STATE = 6,
+ ID_CONTROLLER_BLE_STATE = 7,
+ ID_CONTROLLER_DECK_STATE = 9
+};
+
+/* String attribute identifiers */
+enum {
+ ATTRIB_STR_BOARD_SERIAL,
+ ATTRIB_STR_UNIT_SERIAL,
+};
/* Values for GYRO_MODE (bitmask) */
-#define STEAM_GYRO_MODE_OFF 0x0000
-#define STEAM_GYRO_MODE_STEERING 0x0001
-#define STEAM_GYRO_MODE_TILT 0x0002
-#define STEAM_GYRO_MODE_SEND_ORIENTATION 0x0004
-#define STEAM_GYRO_MODE_SEND_RAW_ACCEL 0x0008
-#define STEAM_GYRO_MODE_SEND_RAW_GYRO 0x0010
+enum {
+ SETTING_GYRO_MODE_OFF = 0,
+ SETTING_GYRO_MODE_STEERING = BIT(0),
+ SETTING_GYRO_MODE_TILT = BIT(1),
+ SETTING_GYRO_MODE_SEND_ORIENTATION = BIT(2),
+ SETTING_GYRO_MODE_SEND_RAW_ACCEL = BIT(3),
+ SETTING_GYRO_MODE_SEND_RAW_GYRO = BIT(4),
+};
+
+/* Trackpad modes */
+enum {
+ TRACKPAD_ABSOLUTE_MOUSE,
+ TRACKPAD_RELATIVE_MOUSE,
+ TRACKPAD_DPAD_FOUR_WAY_DISCRETE,
+ TRACKPAD_DPAD_FOUR_WAY_OVERLAP,
+ TRACKPAD_DPAD_EIGHT_WAY,
+ TRACKPAD_RADIAL_MODE,
+ TRACKPAD_ABSOLUTE_DPAD,
+ TRACKPAD_NONE,
+ TRACKPAD_GESTURE_KEYBOARD,
+};
+
+/* Pad identifiers for the deck */
+#define STEAM_PAD_LEFT 0
+#define STEAM_PAD_RIGHT 1
+#define STEAM_PAD_BOTH 2
/* Other random constants */
-#define STEAM_SERIAL_LEN 10
+#define STEAM_SERIAL_LEN 0x15
struct steam_device {
struct list_head list;
spinlock_t lock;
struct hid_device *hdev, *client_hdev;
- struct mutex mutex;
- bool client_opened;
+ struct mutex report_mutex;
+ unsigned long client_opened;
struct input_dev __rcu *input;
+ struct input_dev __rcu *sensors;
unsigned long quirks;
struct work_struct work_connect;
bool connected;
@@ -134,10 +306,14 @@ struct steam_device {
struct power_supply __rcu *battery;
u8 battery_charge;
u16 voltage;
- struct delayed_work heartbeat;
+ struct delayed_work mode_switch;
+ bool did_mode_switch;
+ bool gamepad_mode;
struct work_struct rumble_work;
u16 rumble_left;
u16 rumble_right;
+ unsigned int sensor_timestamp_us;
+ struct work_struct unregister_work;
};
static int steam_recv_report(struct steam_device *steam,
@@ -226,13 +402,13 @@ static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd)
return steam_send_report(steam, &cmd, 1);
}
-static int steam_write_registers(struct steam_device *steam,
+static int steam_write_settings(struct steam_device *steam,
/* u8 reg, u16 val */...)
{
/* Send: 0x87 len (reg valLo valHi)* */
u8 reg;
u16 val;
- u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
+ u8 cmd[64] = {ID_SET_SETTINGS_VALUES, 0x00};
int ret;
va_list args;
@@ -265,23 +441,29 @@ static int steam_get_serial(struct steam_device *steam)
{
/*
* Send: 0xae 0x15 0x01
- * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+ * Recv: 0xae 0x15 0x01 serialnumber
*/
- int ret;
- u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01};
+ int ret = 0;
+ u8 cmd[] = {ID_GET_STRING_ATTRIBUTE, sizeof(steam->serial_no), ATTRIB_STR_UNIT_SERIAL};
u8 reply[3 + STEAM_SERIAL_LEN + 1];
+ mutex_lock(&steam->report_mutex);
ret = steam_send_report(steam, cmd, sizeof(cmd));
if (ret < 0)
- return ret;
+ goto out;
ret = steam_recv_report(steam, reply, sizeof(reply));
if (ret < 0)
- return ret;
- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01)
- return -EIO;
+ goto out;
+ if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
+ reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
+ ret = -EIO;
+ goto out;
+ }
reply[3 + STEAM_SERIAL_LEN] = 0;
- strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
- return 0;
+ strscpy(steam->serial_no, reply + 3, reply[1]);
+out:
+ mutex_unlock(&steam->report_mutex);
+ return ret;
}
/*
@@ -291,14 +473,50 @@ static int steam_get_serial(struct steam_device *steam)
*/
static inline int steam_request_conn_status(struct steam_device *steam)
{
- return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
+ int ret;
+ mutex_lock(&steam->report_mutex);
+ ret = steam_send_report_byte(steam, ID_DONGLE_GET_WIRELESS_STATE);
+ mutex_unlock(&steam->report_mutex);
+ return ret;
+}
+
+/*
+ * Send a haptic pulse to the trackpads
+ * Duration and interval are measured in microseconds, count is the number
+ * of pulses to send for duration time with interval microseconds between them
+ * and gain is measured in decibels, ranging from -24 to +6
+ */
+static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
+ u16 duration, u16 interval, u16 count, u8 gain)
+{
+ int ret;
+ u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8};
+
+ /* Left and right are swapped on this report for legacy reasons */
+ if (pad < STEAM_PAD_BOTH)
+ pad ^= 1;
+
+ report[2] = pad;
+ report[3] = duration & 0xFF;
+ report[4] = duration >> 8;
+ report[5] = interval & 0xFF;
+ report[6] = interval >> 8;
+ report[7] = count & 0xFF;
+ report[8] = count >> 8;
+ report[9] = gain;
+
+ mutex_lock(&steam->report_mutex);
+ ret = steam_send_report(steam, report, sizeof(report));
+ mutex_unlock(&steam->report_mutex);
+ return ret;
}
static inline int steam_haptic_rumble(struct steam_device *steam,
u16 intensity, u16 left_speed, u16 right_speed,
u8 left_gain, u8 right_gain)
{
- u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
+ int ret;
+ u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9};
report[3] = intensity & 0xFF;
report[4] = intensity >> 8;
@@ -309,7 +527,10 @@ static inline int steam_haptic_rumble(struct steam_device *steam,
report[9] = left_gain;
report[10] = right_gain;
- return steam_send_report(steam, report, sizeof(report));
+ mutex_lock(&steam->report_mutex);
+ ret = steam_send_report(steam, report, sizeof(report));
+ mutex_unlock(&steam->report_mutex);
+ return ret;
}
static void steam_haptic_rumble_cb(struct work_struct *work)
@@ -335,63 +556,72 @@ static int steam_play_effect(struct input_dev *dev, void *data,
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) {
/* enable esc, enter, cursors */
- steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
- /* enable mouse */
- steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE);
- steam_write_registers(steam,
- STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
- 0);
-
- cancel_delayed_work_sync(&steam->heartbeat);
+ steam_send_report_byte(steam, ID_SET_DEFAULT_DIGITAL_MAPPINGS);
+ /* reset settings */
+ steam_send_report_byte(steam, ID_LOAD_DEFAULT_SETTINGS);
} else {
/* disable esc, enter, cursor */
- steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+ steam_send_report_byte(steam, ID_CLEAR_DIGITAL_MAPPINGS);
if (steam->quirks & STEAM_QUIRK_DECK) {
- steam_write_registers(steam,
- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
- STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
- STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ steam_write_settings(steam,
+ SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
+ SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
+ SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */
+ SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */
+ SETTING_STEAM_WATCHDOG_ENABLE, 0, /* disable watchdog that tests if Steam is active */
0);
- /*
- * The Steam Deck has a watchdog that automatically enables
- * lizard mode if it doesn't see any traffic for too long
- */
- if (!work_busy(&steam->heartbeat.work))
- schedule_delayed_work(&steam->heartbeat, 5 * HZ);
} else {
- steam_write_registers(steam,
- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+ 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);
}
static int steam_input_open(struct input_dev *dev)
{
struct steam_device *steam = input_get_drvdata(dev);
+ unsigned long flags;
+ bool set_lizard_mode;
+
+ /*
+ * Disabling lizard mode automatically is only done on the Steam
+ * Controller. On the Steam Deck, this is toggled manually by holding
+ * the options button instead, handled by steam_mode_switch_cb.
+ */
+ if (!(steam->quirks & STEAM_QUIRK_DECK)) {
+ spin_lock_irqsave(&steam->lock, flags);
+ set_lizard_mode = !steam->client_opened && lizard_mode;
+ spin_unlock_irqrestore(&steam->lock, flags);
+ if (set_lizard_mode)
+ steam_set_lizard_mode(steam, false);
+ }
- mutex_lock(&steam->mutex);
- if (!steam->client_opened && lizard_mode)
- steam_set_lizard_mode(steam, false);
- mutex_unlock(&steam->mutex);
return 0;
}
static void steam_input_close(struct input_dev *dev)
{
struct steam_device *steam = input_get_drvdata(dev);
+ unsigned long flags;
+ bool set_lizard_mode;
- mutex_lock(&steam->mutex);
- if (!steam->client_opened && lizard_mode)
- steam_set_lizard_mode(steam, true);
- mutex_unlock(&steam->mutex);
+ if (!(steam->quirks & STEAM_QUIRK_DECK)) {
+ spin_lock_irqsave(&steam->lock, flags);
+ set_lizard_mode = !steam->client_opened && lizard_mode;
+ spin_unlock_irqrestore(&steam->lock, flags);
+ if (set_lizard_mode)
+ steam_set_lizard_mode(steam, true);
+ }
}
static enum power_supply_property steam_battery_props[] = {
@@ -525,15 +755,12 @@ static int steam_input_register(struct steam_device *steam)
input_set_capability(input, EV_KEY, BTN_THUMBL);
input_set_capability(input, EV_KEY, BTN_THUMB);
input_set_capability(input, EV_KEY, BTN_THUMB2);
+ input_set_capability(input, EV_KEY, BTN_GRIPL);
+ input_set_capability(input, EV_KEY, BTN_GRIPR);
if (steam->quirks & STEAM_QUIRK_DECK) {
input_set_capability(input, EV_KEY, BTN_BASE);
- input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY1);
- input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY2);
- input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY3);
- input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY4);
- } else {
- input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
- input_set_capability(input, EV_KEY, BTN_GEAR_UP);
+ input_set_capability(input, EV_KEY, BTN_GRIPL2);
+ input_set_capability(input, EV_KEY, BTN_GRIPR2);
}
input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
@@ -604,6 +831,74 @@ input_register_fail:
return ret;
}
+static int steam_sensors_register(struct steam_device *steam)
+{
+ struct hid_device *hdev = steam->hdev;
+ struct input_dev *sensors;
+ int ret;
+
+ if (!(steam->quirks & STEAM_QUIRK_DECK))
+ return 0;
+
+ rcu_read_lock();
+ sensors = rcu_dereference(steam->sensors);
+ rcu_read_unlock();
+ if (sensors) {
+ dbg_hid("%s: already connected\n", __func__);
+ return 0;
+ }
+
+ sensors = input_allocate_device();
+ if (!sensors)
+ return -ENOMEM;
+
+ input_set_drvdata(sensors, steam);
+ sensors->dev.parent = &hdev->dev;
+
+ sensors->name = "Steam Deck Motion Sensors";
+ sensors->phys = hdev->phys;
+ sensors->uniq = steam->serial_no;
+ sensors->id.bustype = hdev->bus;
+ sensors->id.vendor = hdev->vendor;
+ sensors->id.product = hdev->product;
+ sensors->id.version = hdev->version;
+
+ __set_bit(INPUT_PROP_ACCELEROMETER, sensors->propbit);
+ __set_bit(EV_MSC, sensors->evbit);
+ __set_bit(MSC_TIMESTAMP, sensors->mscbit);
+
+ input_set_abs_params(sensors, ABS_X, -STEAM_DECK_ACCEL_RANGE,
+ STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
+ input_set_abs_params(sensors, ABS_Y, -STEAM_DECK_ACCEL_RANGE,
+ STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
+ input_set_abs_params(sensors, ABS_Z, -STEAM_DECK_ACCEL_RANGE,
+ STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
+ input_abs_set_res(sensors, ABS_X, STEAM_DECK_ACCEL_RES_PER_G);
+ input_abs_set_res(sensors, ABS_Y, STEAM_DECK_ACCEL_RES_PER_G);
+ input_abs_set_res(sensors, ABS_Z, STEAM_DECK_ACCEL_RES_PER_G);
+
+ input_set_abs_params(sensors, ABS_RX, -STEAM_DECK_GYRO_RANGE,
+ STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
+ input_set_abs_params(sensors, ABS_RY, -STEAM_DECK_GYRO_RANGE,
+ STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
+ input_set_abs_params(sensors, ABS_RZ, -STEAM_DECK_GYRO_RANGE,
+ STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
+ input_abs_set_res(sensors, ABS_RX, STEAM_DECK_GYRO_RES_PER_DPS);
+ input_abs_set_res(sensors, ABS_RY, STEAM_DECK_GYRO_RES_PER_DPS);
+ input_abs_set_res(sensors, ABS_RZ, STEAM_DECK_GYRO_RES_PER_DPS);
+
+ ret = input_register_device(sensors);
+ if (ret)
+ goto sensors_register_fail;
+
+ rcu_assign_pointer(steam->sensors, sensors);
+ return 0;
+
+sensors_register_fail:
+ input_free_device(sensors);
+ return ret;
+}
+
static void steam_input_unregister(struct steam_device *steam)
{
struct input_dev *input;
@@ -617,6 +912,24 @@ static void steam_input_unregister(struct steam_device *steam)
input_unregister_device(input);
}
+static void steam_sensors_unregister(struct steam_device *steam)
+{
+ struct input_dev *sensors;
+
+ if (!(steam->quirks & STEAM_QUIRK_DECK))
+ return;
+
+ rcu_read_lock();
+ sensors = rcu_dereference(steam->sensors);
+ rcu_read_unlock();
+
+ if (!sensors)
+ return;
+ RCU_INIT_POINTER(steam->sensors, NULL);
+ synchronize_rcu();
+ input_unregister_device(sensors);
+}
+
static void steam_battery_unregister(struct steam_device *steam)
{
struct power_supply *battery;
@@ -635,7 +948,8 @@ static void steam_battery_unregister(struct steam_device *steam)
static int steam_register(struct steam_device *steam)
{
int ret;
- bool client_opened;
+ unsigned long client_opened;
+ unsigned long flags;
/*
* This function can be called several times in a row with the
@@ -648,11 +962,9 @@ static int steam_register(struct steam_device *steam)
* Unlikely, but getting the serial could fail, and it is not so
* important, so make up a serial number and go on.
*/
- mutex_lock(&steam->mutex);
if (steam_get_serial(steam) < 0)
strscpy(steam->serial_no, "XXXXXXXXXX",
sizeof(steam->serial_no));
- mutex_unlock(&steam->mutex);
hid_info(steam->hdev, "Steam Controller '%s' connected",
steam->serial_no);
@@ -667,23 +979,31 @@ static int steam_register(struct steam_device *steam)
mutex_unlock(&steam_devices_lock);
}
- mutex_lock(&steam->mutex);
+ spin_lock_irqsave(&steam->lock, flags);
client_opened = steam->client_opened;
- if (!client_opened)
- steam_set_lizard_mode(steam, lizard_mode);
- mutex_unlock(&steam->mutex);
+ spin_unlock_irqrestore(&steam->lock, flags);
- if (!client_opened)
+ if (!client_opened) {
+ steam_set_lizard_mode(steam, lizard_mode);
ret = steam_input_register(steam);
- else
- ret = 0;
+ if (ret != 0)
+ goto steam_register_input_fail;
+ ret = steam_sensors_register(steam);
+ if (ret != 0)
+ goto steam_register_sensors_fail;
+ }
+ return 0;
+steam_register_sensors_fail:
+ steam_input_unregister(steam);
+steam_register_input_fail:
return ret;
}
static void steam_unregister(struct steam_device *steam)
{
steam_battery_unregister(steam);
+ steam_sensors_unregister(steam);
steam_input_unregister(steam);
if (steam->serial_no[0]) {
hid_info(steam->hdev, "Steam Controller '%s' disconnected",
@@ -719,6 +1039,59 @@ static void steam_work_connect_cb(struct work_struct *work)
}
}
+static void steam_mode_switch_cb(struct work_struct *work)
+{
+ struct steam_device *steam = container_of(to_delayed_work(work),
+ struct steam_device, mode_switch);
+ unsigned long flags;
+ bool client_opened;
+ if (!lizard_mode)
+ return;
+
+ steam->gamepad_mode = !steam->gamepad_mode;
+ if (steam->gamepad_mode)
+ steam_set_lizard_mode(steam, false);
+ else {
+ spin_lock_irqsave(&steam->lock, flags);
+ client_opened = steam->client_opened;
+ spin_unlock_irqrestore(&steam->lock, flags);
+ if (!client_opened)
+ steam_set_lizard_mode(steam, lizard_mode);
+ }
+
+ steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
+ if (steam->gamepad_mode) {
+ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0);
+ } else {
+ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0);
+ }
+}
+
+static void steam_work_unregister_cb(struct work_struct *work)
+{
+ struct steam_device *steam = container_of(work, struct steam_device,
+ unregister_work);
+ unsigned long flags;
+ bool connected;
+ bool opened;
+
+ spin_lock_irqsave(&steam->lock, flags);
+ opened = steam->client_opened;
+ connected = steam->connected;
+ spin_unlock_irqrestore(&steam->lock, flags);
+
+ if (connected) {
+ if (opened) {
+ steam_sensors_unregister(steam);
+ steam_input_unregister(steam);
+ } else {
+ steam_set_lizard_mode(steam, lizard_mode);
+ steam_input_register(steam);
+ steam_sensors_register(steam);
+ }
+ }
+}
+
static bool steam_is_valve_interface(struct hid_device *hdev)
{
struct hid_report_enum *rep_enum;
@@ -738,22 +1111,6 @@ static bool steam_is_valve_interface(struct hid_device *hdev)
return !list_empty(&rep_enum->report_list);
}
-static void steam_lizard_mode_heartbeat(struct work_struct *work)
-{
- struct steam_device *steam = container_of(work, struct steam_device,
- heartbeat.work);
-
- mutex_lock(&steam->mutex);
- if (!steam->client_opened && steam->client_hdev) {
- steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
- steam_write_registers(steam,
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
- 0);
- schedule_delayed_work(&steam->heartbeat, 5 * HZ);
- }
- mutex_unlock(&steam->mutex);
-}
-
static int steam_client_ll_parse(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
@@ -774,12 +1131,13 @@ static void steam_client_ll_stop(struct hid_device *hdev)
static int steam_client_ll_open(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
+ unsigned long flags;
- mutex_lock(&steam->mutex);
- steam->client_opened = true;
- mutex_unlock(&steam->mutex);
+ spin_lock_irqsave(&steam->lock, flags);
+ steam->client_opened++;
+ spin_unlock_irqrestore(&steam->lock, flags);
- steam_input_unregister(steam);
+ schedule_work(&steam->unregister_work);
return 0;
}
@@ -789,20 +1147,12 @@ 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);
- connected = steam->connected;
+ steam->client_opened--;
spin_unlock_irqrestore(&steam->lock, flags);
- mutex_lock(&steam->mutex);
- steam->client_opened = false;
- if (connected)
- steam_set_lizard_mode(steam, lizard_mode);
- mutex_unlock(&steam->mutex);
-
- if (connected)
- steam_input_register(steam);
+ schedule_work(&steam->unregister_work);
}
static int steam_client_ll_raw_request(struct hid_device *hdev,
@@ -881,26 +1231,20 @@ static int steam_probe(struct hid_device *hdev,
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
steam = devm_kzalloc(&hdev->dev, sizeof(*steam), GFP_KERNEL);
- if (!steam) {
- ret = -ENOMEM;
- goto steam_alloc_fail;
- }
+ if (!steam)
+ return -ENOMEM;
+
steam->hdev = hdev;
hid_set_drvdata(hdev, steam);
spin_lock_init(&steam->lock);
- mutex_init(&steam->mutex);
+ mutex_init(&steam->report_mutex);
steam->quirks = id->driver_data;
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
+ INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
INIT_LIST_HEAD(&steam->list);
- INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
-
- steam->client_hdev = steam_create_client_hid(hdev);
- if (IS_ERR(steam->client_hdev)) {
- ret = PTR_ERR(steam->client_hdev);
- goto client_hdev_fail;
- }
- steam->client_hdev->driver_data = steam;
+ steam->sensor_timestamp_us = 0;
+ INIT_WORK(&steam->unregister_work, steam_work_unregister_cb);
/*
* With the real steam controller interface, do not connect hidraw.
@@ -908,18 +1252,14 @@ static int steam_probe(struct hid_device *hdev,
*/
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_HIDRAW);
if (ret)
- goto hid_hw_start_fail;
-
- ret = hid_add_device(steam->client_hdev);
- if (ret)
- goto client_hdev_add_fail;
+ goto err_cancel_work;
ret = hid_hw_open(hdev);
if (ret) {
hid_err(hdev,
"%s:hid_hw_open\n",
__func__);
- goto hid_hw_open_fail;
+ goto err_hw_stop;
}
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
@@ -935,25 +1275,38 @@ static int steam_probe(struct hid_device *hdev,
hid_err(hdev,
"%s:steam_register failed with error %d\n",
__func__, ret);
- goto input_register_fail;
+ goto err_hw_close;
}
}
+ steam->client_hdev = steam_create_client_hid(hdev);
+ if (IS_ERR(steam->client_hdev)) {
+ ret = PTR_ERR(steam->client_hdev);
+ goto err_steam_unregister;
+ }
+ steam->client_hdev->driver_data = steam;
+
+ ret = hid_add_device(steam->client_hdev);
+ if (ret)
+ goto err_destroy;
+
return 0;
-input_register_fail:
-hid_hw_open_fail:
-client_hdev_add_fail:
- hid_hw_stop(hdev);
-hid_hw_start_fail:
+err_destroy:
hid_destroy_device(steam->client_hdev);
-client_hdev_fail:
+err_steam_unregister:
+ if (steam->connected)
+ steam_unregister(steam);
+err_hw_close:
+ hid_hw_close(hdev);
+err_hw_stop:
+ hid_hw_stop(hdev);
+err_cancel_work:
cancel_work_sync(&steam->work_connect);
- cancel_delayed_work_sync(&steam->heartbeat);
+ cancel_delayed_work_sync(&steam->mode_switch);
cancel_work_sync(&steam->rumble_work);
-steam_alloc_fail:
- hid_err(hdev, "%s: failed with error %d\n",
- __func__, ret);
+ cancel_work_sync(&steam->unregister_work);
+
return ret;
}
@@ -967,12 +1320,12 @@ static void steam_remove(struct hid_device *hdev)
}
hid_destroy_device(steam->client_hdev);
- mutex_lock(&steam->mutex);
- steam->client_hdev = NULL;
- steam->client_opened = false;
- cancel_delayed_work_sync(&steam->heartbeat);
- mutex_unlock(&steam->mutex);
+ cancel_delayed_work_sync(&steam->mode_switch);
cancel_work_sync(&steam->work_connect);
+ cancel_work_sync(&steam->rumble_work);
+ cancel_work_sync(&steam->unregister_work);
+ steam->client_hdev = NULL;
+ steam->client_opened = 0;
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
hid_info(hdev, "Steam wireless receiver disconnected");
}
@@ -1063,8 +1416,8 @@ static inline s16 steam_le16(u8 *data)
* 9.4 | BTN_SELECT | menu left
* 9.5 | BTN_MODE | steam logo
* 9.6 | BTN_START | menu right
- * 9.7 | BTN_GEAR_DOWN | left back lever
- * 10.0 | BTN_GEAR_UP | right back lever
+ * 9.7 | BTN_GRIPL | left back lever
+ * 10.0 | BTN_GRIPR | right back lever
* 10.1 | -- | left-pad clicked
* 10.2 | BTN_THUMBR | right-pad clicked
* 10.3 | BTN_THUMB | left-pad touched (but see explanation below)
@@ -1129,8 +1482,8 @@ static void steam_do_input_event(struct steam_device *steam,
input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4)));
input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5)));
input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6)));
- input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & BIT(7)));
- input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & BIT(0)));
+ input_event(input, EV_KEY, BTN_GRIPL, !!(b9 & BIT(7)));
+ input_event(input, EV_KEY, BTN_GRIPR, !!(b10 & BIT(0)));
input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & BIT(2)));
input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6)));
input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
@@ -1154,12 +1507,12 @@ static void steam_do_input_event(struct steam_device *steam,
* 18-19 | s16 | ABS_HAT0Y | left-pad Y value
* 20-21 | s16 | ABS_HAT1X | right-pad X value
* 22-23 | s16 | ABS_HAT1Y | right-pad Y value
- * 24-25 | s16 | -- | accelerometer X value
- * 26-27 | s16 | -- | accelerometer Y value
- * 28-29 | s16 | -- | accelerometer Z value
- * 30-31 | s16 | -- | gyro X value
- * 32-33 | s16 | -- | gyro Y value
- * 34-35 | s16 | -- | gyro Z value
+ * 24-25 | s16 | IMU ABS_X | accelerometer X value
+ * 26-27 | s16 | IMU ABS_Z | accelerometer Y value
+ * 28-29 | s16 | IMU ABS_Y | accelerometer Z value
+ * 30-31 | s16 | IMU ABS_RX | gyro X value
+ * 32-33 | s16 | IMU ABS_RZ | gyro Y value
+ * 34-35 | s16 | IMU ABS_RY | gyro Z value
* 36-37 | s16 | -- | quaternion W value
* 38-39 | s16 | -- | quaternion X value
* 40-41 | s16 | -- | quaternion Y value
@@ -1191,8 +1544,8 @@ static void steam_do_input_event(struct steam_device *steam,
* 9.4 | BTN_SELECT | menu left
* 9.5 | BTN_MODE | steam logo
* 9.6 | BTN_START | menu right
- * 9.7 | BTN_TRIGGER_HAPPY3 | left bottom grip button
- * 10.0 | BTN_TRIGGER_HAPPY4 | right bottom grip button
+ * 9.7 | BTN_GRIPL2 | left bottom grip button
+ * 10.0 | BTN_GRIPR2 | right bottom grip button
* 10.1 | BTN_THUMB | left pad pressed
* 10.2 | BTN_THUMB2 | right pad pressed
* 10.3 | -- | left pad touched
@@ -1217,8 +1570,8 @@ static void steam_do_input_event(struct steam_device *steam,
* 12.6 | -- | unknown
* 12.7 | -- | unknown
* 13.0 | -- | unknown
- * 13.1 | BTN_TRIGGER_HAPPY1 | left top grip button
- * 13.2 | BTN_TRIGGER_HAPPY2 | right top grip button
+ * 13.1 | BTN_GRIPL | left top grip button
+ * 13.2 | BTN_GRIPR | right top grip button
* 13.3 | -- | unknown
* 13.4 | -- | unknown
* 13.5 | -- | unknown
@@ -1254,6 +1607,17 @@ static void steam_do_deck_input_event(struct steam_device *steam,
b13 = data[13];
b14 = data[14];
+ if (!(b9 & BIT(6)) && steam->did_mode_switch) {
+ steam->did_mode_switch = false;
+ cancel_delayed_work(&steam->mode_switch);
+ } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) {
+ steam->did_mode_switch = true;
+ schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
+ }
+
+ if (!steam->gamepad_mode && lizard_mode)
+ return;
+
lpad_touched = b10 & BIT(3);
rpad_touched = b10 & BIT(4);
@@ -1292,8 +1656,8 @@ static void steam_do_deck_input_event(struct steam_device *steam,
input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4)));
input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5)));
input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6)));
- input_event(input, EV_KEY, BTN_TRIGGER_HAPPY3, !!(b9 & BIT(7)));
- input_event(input, EV_KEY, BTN_TRIGGER_HAPPY4, !!(b10 & BIT(0)));
+ input_event(input, EV_KEY, BTN_GRIPL2, !!(b9 & BIT(7)));
+ input_event(input, EV_KEY, BTN_GRIPR2, !!(b10 & BIT(0)));
input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6)));
input_event(input, EV_KEY, BTN_THUMBR, !!(b11 & BIT(2)));
input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0)));
@@ -1302,13 +1666,39 @@ static void steam_do_deck_input_event(struct steam_device *steam,
input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3)));
input_event(input, EV_KEY, BTN_THUMB, !!(b10 & BIT(1)));
input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(2)));
- input_event(input, EV_KEY, BTN_TRIGGER_HAPPY1, !!(b13 & BIT(1)));
- input_event(input, EV_KEY, BTN_TRIGGER_HAPPY2, !!(b13 & BIT(2)));
+ input_event(input, EV_KEY, BTN_GRIPL, !!(b13 & BIT(1)));
+ input_event(input, EV_KEY, BTN_GRIPR, !!(b13 & BIT(2)));
input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2)));
input_sync(input);
}
+static void steam_do_deck_sensors_event(struct steam_device *steam,
+ struct input_dev *sensors, u8 *data)
+{
+ /*
+ * The deck input report is received every 4 ms on average,
+ * with a jitter of +/- 4 ms even though the USB descriptor claims
+ * that it uses 1 kHz.
+ * Since the HID report does not include a sensor timestamp,
+ * use a fixed increment here.
+ */
+ steam->sensor_timestamp_us += 4000;
+
+ if (!steam->gamepad_mode && lizard_mode)
+ return;
+
+ input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us);
+ input_report_abs(sensors, ABS_X, steam_le16(data + 24));
+ input_report_abs(sensors, ABS_Z, -steam_le16(data + 26));
+ input_report_abs(sensors, ABS_Y, steam_le16(data + 28));
+ input_report_abs(sensors, ABS_RX, steam_le16(data + 30));
+ input_report_abs(sensors, ABS_RZ, -steam_le16(data + 32));
+ input_report_abs(sensors, ABS_RY, steam_le16(data + 34));
+
+ input_sync(sensors);
+}
+
/*
* The size for this message payload is 11.
* The known values are:
@@ -1346,6 +1736,7 @@ static int steam_raw_event(struct hid_device *hdev,
{
struct steam_device *steam = hid_get_drvdata(hdev);
struct input_dev *input;
+ struct input_dev *sensors;
struct power_supply *battery;
if (!steam)
@@ -1375,7 +1766,7 @@ static int steam_raw_event(struct hid_device *hdev,
return 0;
switch (data[2]) {
- case STEAM_EV_INPUT_DATA:
+ case ID_CONTROLLER_STATE:
if (steam->client_opened)
return 0;
rcu_read_lock();
@@ -1384,16 +1775,19 @@ static int steam_raw_event(struct hid_device *hdev,
steam_do_input_event(steam, input, data);
rcu_read_unlock();
break;
- case STEAM_EV_DECK_INPUT_DATA:
+ case ID_CONTROLLER_DECK_STATE:
if (steam->client_opened)
return 0;
rcu_read_lock();
input = rcu_dereference(steam->input);
if (likely(input))
steam_do_deck_input_event(steam, input, data);
+ sensors = rcu_dereference(steam->sensors);
+ if (likely(sensors))
+ steam_do_deck_sensors_event(steam, sensors, data);
rcu_read_unlock();
break;
- case STEAM_EV_CONNECT:
+ case ID_CONTROLLER_WIRELESS:
/*
* The payload of this event is a single byte:
* 0x01: disconnected.
@@ -1408,7 +1802,7 @@ static int steam_raw_event(struct hid_device *hdev,
break;
}
break;
- case STEAM_EV_BATTERY:
+ case ID_CONTROLLER_STATUS:
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
rcu_read_lock();
battery = rcu_dereference(steam->battery);
@@ -1439,10 +1833,8 @@ static int steam_param_set_lizard_mode(const char *val,
mutex_lock(&steam_devices_lock);
list_for_each_entry(steam, &steam_devices, list) {
- mutex_lock(&steam->mutex);
if (!steam->client_opened)
steam_set_lizard_mode(steam, lizard_mode);
- mutex_unlock(&steam->mutex);
}
mutex_unlock(&steam_devices_lock);
return 0;
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index aae3afc4107a..f98435631aa1 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * HID driver for Steelseries SRW-S1
+ * HID driver for Steelseries devices
*
* Copyright (c) 2013 Simon Wood
+ * Copyright (c) 2023 Bastien Nocera
*/
/*
@@ -11,10 +12,30 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
+#include <linux/usb.h>
#include <linux/leds.h>
#include "hid-ids.h"
+#define STEELSERIES_SRWS1 BIT(0)
+#define STEELSERIES_ARCTIS_1 BIT(1)
+#define STEELSERIES_ARCTIS_9 BIT(2)
+
+struct steelseries_device {
+ struct hid_device *hdev;
+ unsigned long quirks;
+
+ struct delayed_work battery_work;
+ spinlock_t lock;
+ bool removed;
+
+ struct power_supply_desc battery_desc;
+ struct power_supply *battery;
+ uint8_t battery_capacity;
+ bool headset_connected;
+ bool battery_charging;
+};
+
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
#define SRWS1_NUMBER_LEDS 15
@@ -32,7 +53,7 @@ struct steelseries_srws1_data {
* appear in the 'Generic Desktop' usage.
*/
-static __u8 steelseries_srws1_rdesc_fixed[] = {
+static const __u8 steelseries_srws1_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop) */
0x09, 0x08, /* Usage (MultiAxis), Changed */
0xA1, 0x01, /* Collection (Application), */
@@ -228,11 +249,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
{
int ret, i;
struct led_classdev *led;
+ struct steelseries_srws1_data *drv_data;
size_t name_sz;
char *name;
- struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
-
+ drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
if (drv_data == NULL) {
hid_err(hdev, "can't alloc SRW-S1 memory\n");
return -ENOMEM;
@@ -243,18 +264,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
- goto err_free;
+ goto err;
}
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
ret = -ENODEV;
- goto err_free;
+ goto err;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
- goto err_free;
+ goto err;
}
/* register led subsystem */
@@ -267,10 +288,10 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
name_sz = strlen(hdev->uniq) + 16;
/* 'ALL', for setting all LEDs simultaneously */
- led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED ALL\n");
- goto err_led;
+ goto out;
}
name = (void *)(&led[1]);
@@ -282,16 +303,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
led->brightness_set = steelseries_srws1_led_all_set_brightness;
drv_data->led[SRWS1_NUMBER_LEDS] = led;
- ret = led_classdev_register(&hdev->dev, led);
- if (ret)
- goto err_led;
+ ret = devm_led_classdev_register(&hdev->dev, led);
+ if (ret) {
+ hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
+ goto out; /* let the driver continue without LEDs */
+ }
/* Each individual LED */
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
- led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+ led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
if (!led) {
hid_err(hdev, "can't allocate memory for LED %d\n", i);
- goto err_led;
+ break;
}
name = (void *)(&led[1]);
@@ -303,84 +326,428 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
led->brightness_set = steelseries_srws1_led_set_brightness;
drv_data->led[i] = led;
- ret = led_classdev_register(&hdev->dev, led);
+ ret = devm_led_classdev_register(&hdev->dev, led);
if (ret) {
hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
-err_led:
- /* Deregister all LEDs (if any) */
- for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
- led = drv_data->led[i];
- drv_data->led[i] = NULL;
- if (!led)
- continue;
- led_classdev_unregister(led);
- kfree(led);
- }
- goto out; /* but let the driver continue without LEDs */
+ break; /* but let the driver continue without LEDs */
}
}
out:
return 0;
-err_free:
- kfree(drv_data);
+err:
return ret;
}
+#endif
+
+#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
-static void steelseries_srws1_remove(struct hid_device *hdev)
+#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_request_battery(struct hid_device *hdev,
+ const char *request, size_t len)
{
- int i;
- struct led_classdev *led;
+ u8 *write_buf;
+ int ret;
- struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev);
-
- if (drv_data) {
- /* Deregister LEDs (if any) */
- for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
- led = drv_data->led[i];
- drv_data->led[i] = NULL;
- if (!led)
- continue;
- led_classdev_unregister(led);
- kfree(led);
- }
+ /* Request battery information */
+ write_buf = kmemdup(request, len, GFP_KERNEL);
+ if (!write_buf)
+ return -ENOMEM;
+ 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)len) {
+ hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
+ ret = -ENODATA;
}
- hid_hw_stop(hdev);
- kfree(drv_data);
- return;
+ kfree(write_buf);
+ return ret;
+}
+
+static void steelseries_headset_fetch_battery(struct hid_device *hdev)
+{
+ int ret = 0;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_1_battery_request, sizeof(arctis_1_battery_request));
+ else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
+ ret = steelseries_headset_request_battery(hdev,
+ arctis_9_battery_request, sizeof(arctis_9_battery_request));
+
+ if (ret < 0)
+ hid_dbg(hdev,
+ "Battery query failed (err: %d)\n", ret);
+}
+
+static int battery_capacity_to_level(int capacity)
+{
+ if (capacity >= 50)
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ if (capacity >= 20)
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+}
+
+static void steelseries_headset_battery_timer_tick(struct work_struct *work)
+{
+ struct steelseries_device *sd = container_of(work,
+ struct steelseries_device, battery_work.work);
+ struct hid_device *hdev = sd->hdev;
+
+ 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)
+{
+ struct steelseries_device *sd = power_supply_get_drvdata(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:
+ 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;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = sd->battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = battery_capacity_to_level(sd->battery_capacity);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void
+steelseries_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
+{
+ struct usb_interface *intf;
+
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
+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,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+static int steelseries_headset_battery_register(struct steelseries_device *sd)
+{
+ static atomic_t battery_no = ATOMIC_INIT(0);
+ struct power_supply_config battery_cfg = { .drv_data = sd, };
+ unsigned long n;
+ int ret;
+
+ sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ sd->battery_desc.properties = steelseries_headset_battery_props;
+ sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
+ sd->battery_desc.get_property = steelseries_headset_battery_get_property;
+ sd->battery_desc.use_for_apm = 0;
+ n = atomic_inc_return(&battery_no) - 1;
+ sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
+ "steelseries_headset_battery_%ld", n);
+ if (!sd->battery_desc.name)
+ return -ENOMEM;
+
+ /* 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);
+ if (IS_ERR(sd->battery)) {
+ ret = PTR_ERR(sd->battery);
+ hid_err(sd->hdev,
+ "%s:power_supply_register failed with error %d\n",
+ __func__, ret);
+ return ret;
+ }
+ power_supply_powers(sd->battery, &sd->hdev->dev);
+
+ 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;
+ int ret;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ return steelseries_srws1_probe(hdev, id);
+#else
+ return -ENODEV;
+#endif
+ }
+
+ sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+ if (!sd)
+ return -ENOMEM;
+ hid_set_drvdata(hdev, sd);
+ sd->hdev = hdev;
+ sd->quirks = id->driver_data;
+
+ ret = hid_parse(hdev);
+ 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");
+
+ return ret;
}
+
+static void steelseries_remove(struct hid_device *hdev)
+{
+ struct steelseries_device *sd;
+ unsigned long flags;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+ hid_hw_stop(hdev);
#endif
+ return;
+ }
+
+ sd = hid_get_drvdata(hdev);
+
+ spin_lock_irqsave(&sd->lock, flags);
+ sd->removed = true;
+ spin_unlock_irqrestore(&sd->lock, flags);
+
+ cancel_delayed_work_sync(&sd->battery_work);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
-static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
+ __u8 *rdesc, unsigned int *rsize)
{
+ if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
+ hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
+ return rdesc;
+
if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
- rdesc = steelseries_srws1_rdesc_fixed;
*rsize = sizeof(steelseries_srws1_rdesc_fixed);
+ return steelseries_srws1_rdesc_fixed;
}
return rdesc;
}
-static const struct hid_device_id steelseries_srws1_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+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)
+{
+ 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 */
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ return 0;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
+ hid_dbg(sd->hdev,
+ "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
+ if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
+ memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
+ if (!delayed_work_pending(&sd->battery_work))
+ goto request_battery;
+ return 0;
+ }
+ if (read_buf[2] == 0x01) {
+ connected = false;
+ capacity = 100;
+ } else {
+ connected = true;
+ capacity = read_buf[3];
+ }
+ }
+
+ if (hdev->product == USB_DEVICE_ID_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",
+ sd->headset_connected ? "" : "not ",
+ connected ? "" : "not ");
+ sd->headset_connected = connected;
+ steelseries_headset_set_wireless_status(hdev, connected);
+ }
+
+ if (capacity != sd->battery_capacity) {
+ hid_dbg(sd->hdev,
+ "Battery capacity changed from %d%% to %d%%\n",
+ sd->battery_capacity, capacity);
+ sd->battery_capacity = capacity;
+ 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)
+ schedule_delayed_work(&sd->battery_work,
+ msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
+ spin_unlock_irqrestore(&sd->lock, flags);
+
+ return 0;
+}
+
+static const struct hid_device_id steelseries_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
+ .driver_data = STEELSERIES_SRWS1 },
+
+ { /* SteelSeries Arctis 1 Wireless for XBox */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
+ .driver_data = STEELSERIES_ARCTIS_1 },
+
+ { /* SteelSeries Arctis 9 Wireless for XBox */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
+ .driver_data = STEELSERIES_ARCTIS_9 },
+
{ }
};
-MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices);
-
-static struct hid_driver steelseries_srws1_driver = {
- .name = "steelseries_srws1",
- .id_table = steelseries_srws1_devices,
-#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
- (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
- .probe = steelseries_srws1_probe,
- .remove = steelseries_srws1_remove,
-#endif
- .report_fixup = steelseries_srws1_report_fixup
+MODULE_DEVICE_TABLE(hid, steelseries_devices);
+
+static struct hid_driver steelseries_driver = {
+ .name = "steelseries",
+ .id_table = steelseries_devices,
+ .probe = steelseries_probe,
+ .remove = steelseries_remove,
+ .report_fixup = steelseries_srws1_report_fixup,
+ .raw_event = steelseries_headset_raw_event,
};
-module_hid_driver(steelseries_srws1_driver);
+module_hid_driver(steelseries_driver);
+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-sunplus.c b/drivers/hid/hid-sunplus.c
index aa2855c2ed4e..64e4cff8ca1d 100644
--- a/drivers/hid/hid-sunplus.c
+++ b/drivers/hid/hid-sunplus.c
@@ -18,7 +18,7 @@
#include "hid-ids.h"
-static __u8 *sp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *sp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 112 && rdesc[104] == 0x26 && rdesc[105] == 0x80 &&
@@ -62,4 +62,5 @@ static struct hid_driver sp_driver = {
};
module_hid_driver(sp_driver);
+MODULE_DESCRIPTION("HID driver for some sunplus \"special\" devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c
index cf1679b0d4fb..0bf70664c35e 100644
--- a/drivers/hid/hid-thrustmaster.c
+++ b/drivers/hid/hid-thrustmaster.c
@@ -170,6 +170,15 @@ static void thrustmaster_interrupts(struct hid_device *hdev)
ep = &usbif->cur_altsetting->endpoint[1];
b_ep = ep->desc.bEndpointAddress;
+ /* Are the expected endpoints present? */
+ 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;
+ }
+
for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) {
memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]);
diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c
index 68eb08b63945..827bf67abeb9 100644
--- a/drivers/hid/hid-tivo.c
+++ b/drivers/hid/hid-tivo.c
@@ -73,5 +73,6 @@ static struct hid_driver tivo_driver = {
};
module_hid_driver(tivo_driver);
+MODULE_DESCRIPTION("HID driver for TiVo Slide Bluetooth remote");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jarod Wilson <jarod@redhat.com>");
diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c
index 4040cd98dafe..fcd859aa3a8c 100644
--- a/drivers/hid/hid-tmff.c
+++ b/drivers/hid/hid-tmff.c
@@ -265,4 +265,5 @@ static struct hid_driver tm_driver = {
};
module_hid_driver(tm_driver);
+MODULE_DESCRIPTION("Force feedback support for various HID compliant devices by ThrustMaster");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-topre.c b/drivers/hid/hid-topre.c
index d1d5ca310ead..ccedf8721722 100644
--- a/drivers/hid/hid-topre.c
+++ b/drivers/hid/hid-topre.c
@@ -21,14 +21,19 @@ MODULE_LICENSE("GPL");
* events it's actually sending. It claims to send array events but is instead
* sending variable events.
*/
-static __u8 *topre_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *topre_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
if (*rsize >= 119 && rdesc[69] == 0x29 && rdesc[70] == 0xe7 &&
rdesc[71] == 0x81 && rdesc[72] == 0x00) {
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-topseed.c b/drivers/hid/hid-topseed.c
index 2125327b8de1..645e36cd83a6 100644
--- a/drivers/hid/hid-topseed.c
+++ b/drivers/hid/hid-topseed.c
@@ -78,4 +78,5 @@ static struct hid_driver ts_driver = {
};
module_hid_driver(ts_driver);
+MODULE_DESCRIPTION("HID driver for TopSeed Cyberlink remote");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-twinhan.c b/drivers/hid/hid-twinhan.c
index 14af794146c0..0ef5194085b2 100644
--- a/drivers/hid/hid-twinhan.c
+++ b/drivers/hid/hid-twinhan.c
@@ -131,4 +131,5 @@ static struct hid_driver twinhan_driver = {
};
module_hid_driver(twinhan_driver);
+MODULE_DESCRIPTION("HID driver for TwinHan IR remote control");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-uclogic-core-test.c b/drivers/hid/hid-uclogic-core-test.c
index 2bb916226a38..cb274cde3ad2 100644
--- a/drivers/hid/hid-uclogic-core-test.c
+++ b/drivers/hid/hid-uclogic-core-test.c
@@ -56,6 +56,11 @@ static struct uclogic_raw_event_hook_test test_events[] = {
},
};
+static void fake_work(struct work_struct *work)
+{
+
+}
+
static void hid_test_uclogic_exec_event_hook_test(struct kunit *test)
{
struct uclogic_params p = {0, };
@@ -77,6 +82,8 @@ static void hid_test_uclogic_exec_event_hook_test(struct kunit *test)
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter->event);
memcpy(filter->event, &hook_events[n].event[0], filter->size);
+ INIT_WORK(&filter->work, fake_work);
+
list_add_tail(&filter->list, &p.event_hooks->list);
}
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index f67835f9ed4c..90ebb81041ea 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -32,8 +32,8 @@
*/
static void uclogic_inrange_timeout(struct timer_list *t)
{
- struct uclogic_drvdata *drvdata = from_timer(drvdata, t,
- inrange_timer);
+ struct uclogic_drvdata *drvdata = timer_container_of(drvdata, t,
+ inrange_timer);
struct input_dev *input = drvdata->pen_input;
if (input == NULL)
@@ -50,18 +50,42 @@ static void uclogic_inrange_timeout(struct timer_list *t)
input_sync(input);
}
-static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
if (drvdata->desc_ptr != NULL) {
- rdesc = drvdata->desc_ptr;
*rsize = drvdata->desc_size;
+ return drvdata->desc_ptr;
}
return rdesc;
}
+/* Buttons considered valid tablet pad inputs. */
+static const unsigned int uclogic_extra_input_mapping[] = {
+ BTN_0,
+ BTN_1,
+ BTN_2,
+ BTN_3,
+ BTN_4,
+ BTN_5,
+ BTN_6,
+ BTN_7,
+ BTN_8,
+ BTN_RIGHT,
+ BTN_MIDDLE,
+ BTN_SIDE,
+ BTN_EXTRA,
+ BTN_FORWARD,
+ BTN_BACK,
+ BTN_B,
+ BTN_A,
+ BTN_BASE,
+ BTN_BASE2,
+ BTN_X
+};
+
static int uclogic_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field,
@@ -72,9 +96,27 @@ static int uclogic_input_mapping(struct hid_device *hdev,
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params;
- /* Discard invalid pen usages */
- if (params->pen.usage_invalid && (field->application == HID_DG_PEN))
- return -1;
+ if (field->application == HID_GD_KEYPAD) {
+ /*
+ * Remap input buttons to sensible ones that are not invalid.
+ * This only affects previous behavior for devices with more than ten or so buttons.
+ */
+ const int key = (usage->hid & HID_USAGE) - 1;
+
+ if (key < ARRAY_SIZE(uclogic_extra_input_mapping)) {
+ hid_map_usage(hi,
+ usage,
+ bit,
+ max,
+ EV_KEY,
+ uclogic_extra_input_mapping[key]);
+ return 1;
+ }
+ } else if (field->application == HID_DG_PEN) {
+ /* Discard invalid pen usages */
+ if (params->pen.usage_invalid)
+ return -1;
+ }
/* Let hid-core decide what to do */
return 0;
@@ -85,10 +127,8 @@ static int uclogic_input_configured(struct hid_device *hdev,
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params;
- char *name;
const char *suffix = NULL;
struct hid_field *field;
- size_t len;
size_t i;
const struct uclogic_params_frame *frame;
@@ -144,15 +184,11 @@ static int uclogic_input_configured(struct hid_device *hdev,
suffix = "System Control";
break;
}
- }
-
- if (suffix) {
- len = strlen(hdev->name) + 2 + strlen(suffix);
- name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
- if (name) {
- snprintf(name, len, "%s %s", hdev->name, suffix);
- hi->input->name = name;
- }
+ } else {
+ hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "%s %s", hdev->name, suffix);
+ if (!hi->input->name)
+ return -ENOMEM;
}
return 0;
@@ -326,6 +362,23 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata,
data[8] = pressure_low_byte;
data[9] = pressure_high_byte;
}
+ if (size == 12 && pen->fragmented_hires2) {
+ // 00 00 when on the left side, 01 00 in the right
+ // we move these to the end of the x coord (u16) to create a correct x coord (u32)
+ u8 lsb_low_byte = data[10];
+ u8 lsb_high_byte = data[11];
+
+ // shift everything right by 2 bytes, to make space for the moved lsb
+ data[11] = data[9];
+ data[10] = data[8];
+ data[9] = data[7];
+ data[8] = data[6];
+ data[7] = data[5];
+ data[6] = data[4];
+
+ data[4] = lsb_low_byte;
+ data[5] = lsb_high_byte;
+ }
/* If we need to emulate in-range detection */
if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) {
/* Set in-range bit */
@@ -413,8 +466,22 @@ static int uclogic_raw_event_frame(
/* If need to, and can, transform the bitmap dial reports */
if (frame->bitmap_dial_byte > 0 && frame->bitmap_dial_byte < size) {
- if (data[frame->bitmap_dial_byte] == 2)
+ switch (data[frame->bitmap_dial_byte]) {
+ case 2:
data[frame->bitmap_dial_byte] = -1;
+ break;
+
+ /* Everything below here is for tablets that shove multiple dials into 1 byte */
+ case 16:
+ data[frame->bitmap_dial_byte] = 0;
+ data[frame->bitmap_second_dial_destination_byte] = 1;
+ break;
+
+ case 32:
+ data[frame->bitmap_dial_byte] = 0;
+ data[frame->bitmap_second_dial_destination_byte] = -1;
+ break;
+ }
}
return 0;
@@ -481,7 +548,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);
@@ -552,6 +619,10 @@ static const struct hid_device_id uclogic_devices[] = {
.driver_data = UCLOGIC_MOUSE_FRAME_QUIRK | UCLOGIC_BATTERY_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO) },
{ }
};
MODULE_DEVICE_TABLE(hid, uclogic_devices);
@@ -574,7 +645,9 @@ module_hid_driver(uclogic_driver);
MODULE_AUTHOR("Martin Rusko");
MODULE_AUTHOR("Nikolai Kondrashov");
+MODULE_DESCRIPTION("HID driver for UC-Logic devices not fully compliant with HID standard");
MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID driver for UC-Logic devices not fully compliant with HID standard");
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-uclogic-core-test.c"
diff --git a/drivers/hid/hid-uclogic-params-test.c b/drivers/hid/hid-uclogic-params-test.c
index 678f50cbb160..a30121419a29 100644
--- a/drivers/hid/hid-uclogic-params-test.c
+++ b/drivers/hid/hid-uclogic-params-test.c
@@ -174,12 +174,26 @@ static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test)
KUNIT_EXPECT_EQ(test, params->frame_type, frame_type);
}
+struct fake_device {
+ unsigned long quirks;
+};
+
static void hid_test_uclogic_params_cleanup_event_hooks(struct kunit *test)
{
int res, n;
+ struct hid_device *hdev;
+ struct fake_device *fake_dev;
struct uclogic_params p = {0, };
- res = uclogic_params_ugee_v2_init_event_hooks(NULL, &p);
+ hdev = kunit_kzalloc(test, sizeof(struct hid_device), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hdev);
+
+ fake_dev = kunit_kzalloc(test, sizeof(struct fake_device), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_dev);
+
+ hid_set_drvdata(hdev, fake_dev);
+
+ res = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
KUNIT_ASSERT_EQ(test, res, 0);
/* Check that the function can be called repeatedly */
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 9859dad36495..e28176d9d9c9 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -19,7 +19,8 @@
#include "hid-ids.h"
#include <linux/ctype.h>
#include <linux/string.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
+#include <linux/string_choices.h>
/**
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
@@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
size_t i;
hid_dbg(hdev, "\t.usage_invalid = %s\n",
- (pen->usage_invalid ? "true" : "false"));
+ str_true_false(pen->usage_invalid));
hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
hid_dbg(hdev, "\t.id = %u\n", pen->id);
@@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
hid_dbg(hdev, "\t.inrange = %s\n",
uclogic_params_pen_inrange_to_str(pen->inrange));
hid_dbg(hdev, "\t.fragmented_hires = %s\n",
- (pen->fragmented_hires ? "true" : "false"));
+ str_true_false(pen->fragmented_hires));
hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
- (pen->tilt_y_flipped ? "true" : "false"));
+ str_true_false(pen->tilt_y_flipped));
}
/**
@@ -103,6 +104,8 @@ static void uclogic_params_frame_hid_dbg(
frame->touch_flip_at);
hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n",
frame->bitmap_dial_byte);
+ hid_dbg(hdev, "\t\t.bitmap_second_dial_destination_byte = %u\n",
+ frame->bitmap_second_dial_destination_byte);
}
/**
@@ -117,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
{
size_t i;
- hid_dbg(hdev, ".invalid = %s\n",
- params->invalid ? "true" : "false");
+ hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid));
hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
hid_dbg(hdev, ".pen = {\n");
@@ -681,7 +683,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
* -ENOMEM, if failed to allocate memory.
*/
int uclogic_params_get_desc(const struct uclogic_params *params,
- __u8 **pdesc,
+ const __u8 **pdesc,
unsigned int *psize)
{
int rc = -ENOMEM;
@@ -769,7 +771,7 @@ static void uclogic_params_init_invalid(struct uclogic_params *params)
static int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
struct hid_device *hdev,
unsigned int orig_desc_size,
- __u8 *desc_ptr,
+ const __u8 *desc_ptr,
unsigned int desc_size)
{
__u8 *desc_copy_ptr = NULL;
@@ -842,7 +844,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
@@ -884,6 +886,9 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
goto cleanup;
}
+ /* The firmware is used in userspace as unique identifier */
+ strscpy(hdev->uniq, ver_ptr, sizeof(hdev->uniq));
+
/* If this is a transition firmware */
if (strcmp(ver_ptr, transition_ver) == 0) {
hid_dbg(hdev,
@@ -1118,6 +1123,9 @@ static int uclogic_params_parse_ugee_v2_desc(const __u8 *str_desc,
return -EINVAL;
pen_x_lm = get_unaligned_le16(str_desc + 2);
+ if (str_desc_size > 12)
+ pen_x_lm += (u8)str_desc[12] << 16;
+
pen_y_lm = get_unaligned_le16(str_desc + 4);
frame_num_buttons = str_desc[6];
*frame_type = str_desc[7];
@@ -1338,7 +1346,7 @@ static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
struct uclogic_params *p)
{
struct uclogic_raw_event_hook *event_hook;
- __u8 reconnect_event[] = {
+ static const __u8 reconnect_event[] = {
/* Event received on wireless tablet reconnection */
0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
@@ -1364,8 +1372,10 @@ static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
event_hook->hdev = hdev;
event_hook->size = ARRAY_SIZE(reconnect_event);
event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
- if (!event_hook->event)
+ if (!event_hook->event) {
+ kfree(event_hook);
return -ENOMEM;
+ }
list_add_tail(&event_hook->list, &p->event_hooks->list);
@@ -1526,6 +1536,128 @@ cleanup:
return rc;
}
+/*
+ * uclogic_params_init_ugee_xppen_pro() - Initializes a UGEE XP-Pen Pro tablet device.
+ *
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_init_ugee_xppen_pro(struct uclogic_params *params,
+ struct hid_device *hdev,
+ const u8 rdesc_pen_arr[],
+ const size_t rdesc_pen_size,
+ const u8 rdesc_frame_arr[],
+ const size_t rdesc_frame_size,
+ size_t str_desc_len)
+{
+ int rc = 0;
+ struct usb_interface *iface;
+ __u8 bInterfaceNumber;
+ u8 *str_desc = NULL;
+ __u8 *rdesc_pen = NULL;
+ s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
+ enum uclogic_params_frame_type frame_type;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+
+ if (!hdev || !params) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ iface = to_usb_interface(hdev->dev.parent);
+ bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+
+ /* Ignore non-pen interfaces */
+ if (bInterfaceNumber != 2) {
+ rc = -EINVAL;
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ /*
+ * Initialize the interface by sending magic data.
+ * This magic data is the same as other UGEE v2 tablets.
+ */
+ rc = uclogic_probe_interface(hdev,
+ uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
+ if (rc) {
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ /**
+ * Read the string descriptor containing pen and frame parameters.
+ * These are slightly different than typical UGEE v2 devices.
+ */
+ rc = uclogic_params_get_str_desc(&str_desc, hdev, 100, str_desc_len);
+ if (rc != str_desc_len) {
+ rc = (rc < 0) ? rc : -EINVAL;
+ hid_err(hdev, "failed retrieving pen and frame parameters: %d\n", rc);
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ rc = uclogic_params_parse_ugee_v2_desc(str_desc, str_desc_len,
+ desc_params,
+ ARRAY_SIZE(desc_params),
+ &frame_type);
+ if (rc)
+ goto cleanup;
+
+ // str_desc doesn't report the correct amount of buttons, so manually fix it
+ desc_params[UCLOGIC_RDESC_FRAME_PH_ID_UM] = 20;
+
+ kfree(str_desc);
+ str_desc = NULL;
+
+ /* Initialize the pen interface */
+ rdesc_pen = uclogic_rdesc_template_apply(
+ rdesc_pen_arr,
+ rdesc_pen_size,
+ desc_params, ARRAY_SIZE(desc_params));
+ if (!rdesc_pen) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ p.pen.desc_ptr = rdesc_pen;
+ p.pen.desc_size = rdesc_pen_size;
+ p.pen.id = 0x02;
+ p.pen.subreport_list[0].value = 0xf0;
+ p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
+
+ /* Initialize the frame interface */
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame_list[0],
+ rdesc_frame_arr,
+ rdesc_frame_size,
+ UCLOGIC_RDESC_V1_FRAME_ID);
+ if (rc < 0) {
+ hid_err(hdev, "initializing frame params failed: %d\n", rc);
+ goto cleanup;
+ }
+
+ p.frame_list[0].bitmap_dial_byte = 7;
+ p.frame_list[0].bitmap_second_dial_destination_byte = 8;
+
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+cleanup:
+ kfree(str_desc);
+ uclogic_params_cleanup(&p);
+ return rc;
+}
+
/**
* uclogic_params_init() - initialize a tablet interface and discover its
* parameters.
@@ -1843,6 +1975,36 @@ int uclogic_params_init(struct uclogic_params *params,
}
break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO):
+ rc = uclogic_params_init_ugee_xppen_pro(&p,
+ hdev,
+ uclogic_rdesc_ugee_v2_pen_template_arr,
+ uclogic_rdesc_ugee_v2_pen_template_size,
+ uclogic_rdesc_xppen_artist_22r_pro_frame_arr,
+ uclogic_rdesc_xppen_artist_22r_pro_frame_size,
+ 12);
+ if (rc != 0)
+ goto cleanup;
+
+ break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO):
+ rc = uclogic_params_init_ugee_xppen_pro(&p,
+ hdev,
+ uclogic_rdesc_xppen_artist_24_pro_pen_template_arr,
+ uclogic_rdesc_xppen_artist_24_pro_pen_template_size,
+ uclogic_rdesc_xppen_artist_24_pro_frame_arr,
+ uclogic_rdesc_xppen_artist_24_pro_frame_size,
+ 14);
+
+ // The 24 Pro has a fragmented X Coord.
+ p.pen.fragmented_hires2 = true;
+
+ if (rc != 0)
+ goto cleanup;
+
+ break;
}
#undef VID_PID
diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h
index d6ffadb2f601..c84ff17fb5d5 100644
--- a/drivers/hid/hid-uclogic-params.h
+++ b/drivers/hid/hid-uclogic-params.h
@@ -79,7 +79,7 @@ struct uclogic_params_pen {
* Pointer to report descriptor part describing the pen inputs.
* Allocated with kmalloc. NULL if the part is not specified.
*/
- __u8 *desc_ptr;
+ const __u8 *desc_ptr;
/*
* Size of the report descriptor.
* Only valid, if "desc_ptr" is not NULL.
@@ -103,6 +103,11 @@ struct uclogic_params_pen {
* Only valid if "id" is not zero.
*/
bool tilt_y_flipped;
+ /*
+ * True, if reports include fragmented high resolution X coords.
+ * This moves bytes 10-11 to the LSB of the X coordinate.
+ */
+ bool fragmented_hires2;
};
/*
@@ -118,7 +123,7 @@ struct uclogic_params_frame {
* Pointer to report descriptor part describing the frame inputs.
* Allocated with kmalloc. NULL if the part is not specified.
*/
- __u8 *desc_ptr;
+ const __u8 *desc_ptr;
/*
* Size of the report descriptor.
* Only valid, if "desc_ptr" is not NULL.
@@ -175,6 +180,11 @@ struct uclogic_params_frame {
* counterclockwise, as opposed to the normal 1 and -1.
*/
unsigned int bitmap_dial_byte;
+ /*
+ * Destination offset for the second bitmap dial byte, if the tablet
+ * supports a second dial at all.
+ */
+ unsigned int bitmap_second_dial_destination_byte;
};
/*
@@ -212,7 +222,7 @@ struct uclogic_params {
* allocated with kmalloc. NULL if no common part is needed.
* Only valid, if "invalid" is false.
*/
- __u8 *desc_ptr;
+ const __u8 *desc_ptr;
/*
* Size of the common part of the replacement report descriptor.
* Only valid, if "desc_ptr" is valid and not NULL.
@@ -239,7 +249,7 @@ struct uclogic_drvdata {
/* Interface parameters */
struct uclogic_params params;
/* Pointer to the replacement report descriptor. NULL if none. */
- __u8 *desc_ptr;
+ const __u8 *desc_ptr;
/*
* Size of the replacement report descriptor.
* Only valid if desc_ptr is not NULL
@@ -261,7 +271,7 @@ extern int uclogic_params_init(struct uclogic_params *params,
/* Get a replacement report descriptor for a tablet's interface. */
extern int uclogic_params_get_desc(const struct uclogic_params *params,
- __u8 **pdesc,
+ const __u8 **pdesc,
unsigned int *psize);
/* Free resources used by tablet interface's parameters */
diff --git a/drivers/hid/hid-uclogic-rdesc-test.c b/drivers/hid/hid-uclogic-rdesc-test.c
index 90bf4e586e01..066df622b6f2 100644
--- a/drivers/hid/hid-uclogic-rdesc-test.c
+++ b/drivers/hid/hid-uclogic-rdesc-test.c
@@ -9,6 +9,8 @@
#include <kunit/test.h>
#include "./hid-uclogic-rdesc.h"
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+
struct uclogic_template_case {
const char *name;
const __u8 *template;
diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c
index b6dfdf6356a6..a1b31511b625 100644
--- a/drivers/hid/hid-uclogic-rdesc.c
+++ b/drivers/hid/hid-uclogic-rdesc.c
@@ -16,10 +16,11 @@
#include "hid-uclogic-rdesc.h"
#include <linux/slab.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
+#include <kunit/visibility.h>
/* Fixed WP4030U report descriptor */
-__u8 uclogic_rdesc_wp4030u_fixed_arr[] = {
+const __u8 uclogic_rdesc_wp4030u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -64,7 +65,7 @@ const size_t uclogic_rdesc_wp4030u_fixed_size =
sizeof(uclogic_rdesc_wp4030u_fixed_arr);
/* Fixed WP5540U report descriptor */
-__u8 uclogic_rdesc_wp5540u_fixed_arr[] = {
+const __u8 uclogic_rdesc_wp5540u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -141,7 +142,7 @@ const size_t uclogic_rdesc_wp5540u_fixed_size =
sizeof(uclogic_rdesc_wp5540u_fixed_arr);
/* Fixed WP8060U report descriptor */
-__u8 uclogic_rdesc_wp8060u_fixed_arr[] = {
+const __u8 uclogic_rdesc_wp8060u_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -218,7 +219,7 @@ const size_t uclogic_rdesc_wp8060u_fixed_size =
sizeof(uclogic_rdesc_wp8060u_fixed_arr);
/* Fixed WP1062 report descriptor */
-__u8 uclogic_rdesc_wp1062_fixed_arr[] = {
+const __u8 uclogic_rdesc_wp1062_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -266,7 +267,7 @@ const size_t uclogic_rdesc_wp1062_fixed_size =
sizeof(uclogic_rdesc_wp1062_fixed_arr);
/* Fixed PF1209 report descriptor */
-__u8 uclogic_rdesc_pf1209_fixed_arr[] = {
+const __u8 uclogic_rdesc_pf1209_fixed_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -343,7 +344,7 @@ const size_t uclogic_rdesc_pf1209_fixed_size =
sizeof(uclogic_rdesc_pf1209_fixed_arr);
/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */
-__u8 uclogic_rdesc_twhl850_fixed0_arr[] = {
+const __u8 uclogic_rdesc_twhl850_fixed0_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -389,7 +390,7 @@ const size_t uclogic_rdesc_twhl850_fixed0_size =
sizeof(uclogic_rdesc_twhl850_fixed0_arr);
/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */
-__u8 uclogic_rdesc_twhl850_fixed1_arr[] = {
+const __u8 uclogic_rdesc_twhl850_fixed1_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
@@ -429,7 +430,7 @@ const size_t uclogic_rdesc_twhl850_fixed1_size =
sizeof(uclogic_rdesc_twhl850_fixed1_arr);
/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */
-__u8 uclogic_rdesc_twhl850_fixed2_arr[] = {
+const __u8 uclogic_rdesc_twhl850_fixed2_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
@@ -455,7 +456,7 @@ const size_t uclogic_rdesc_twhl850_fixed2_size =
sizeof(uclogic_rdesc_twhl850_fixed2_arr);
/* Fixed TWHA60 report descriptor, interface 0 (stylus) */
-__u8 uclogic_rdesc_twha60_fixed0_arr[] = {
+const __u8 uclogic_rdesc_twha60_fixed0_arr[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -504,7 +505,7 @@ const size_t uclogic_rdesc_twha60_fixed0_size =
sizeof(uclogic_rdesc_twha60_fixed0_arr);
/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */
-__u8 uclogic_rdesc_twha60_fixed1_arr[] = {
+const __u8 uclogic_rdesc_twha60_fixed1_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x06, /* Usage (Keyboard), */
0xA1, 0x01, /* Collection (Application), */
@@ -689,10 +690,10 @@ const size_t uclogic_rdesc_v2_pen_template_size =
0xA0, /* Collection (Physical), */ \
0x05, 0x09, /* Usage Page (Button), */ \
0x19, 0x01, /* Usage Minimum (01h), */ \
- 0x29, 0x03, /* Usage Maximum (03h), */ \
- 0x95, 0x03, /* Report Count (3), */ \
+ 0x29, 0x0A, /* Usage Maximum (0Ah), */ \
+ 0x95, 0x0A, /* Report Count (10), */ \
0x81, 0x02, /* Input (Variable), */ \
- 0x95, ((_size) * 8 - 45), \
+ 0x95, ((_size) * 8 - 52), \
/* Report Count (padding), */ \
0x81, 0x01, /* Input (Constant), */ \
0xC0, /* End Collection, */ \
@@ -789,7 +790,8 @@ const __u8 uclogic_rdesc_v2_frame_touch_strip_arr[] = {
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
- 0x09, 0x38, /* Usage (Wheel), */
+ 0x09, 0x33, /* Usage (Rx), */
+ 0x09, 0x34, /* Usage (Ry), */
0x95, 0x01, /* Report Count (1), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x07, /* Logical Maximum (7), */
@@ -1191,6 +1193,175 @@ const __u8 uclogic_rdesc_xppen_deco01_frame_arr[] = {
const size_t uclogic_rdesc_xppen_deco01_frame_size =
sizeof(uclogic_rdesc_xppen_deco01_frame_arr);
+/* Fixed report descriptor for XP-Pen Arist 22R Pro frame */
+const __u8 uclogic_rdesc_xppen_artist_22r_pro_frame_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, UCLOGIC_RDESC_V1_FRAME_ID,
+ /* Report ID (Virtual report), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x08, /* Report Count (8), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x14, /* Usage Maximum (14h), */
+ 0x95, 0x14, /* Report Count (20), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x14, /* Report Count (20), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x38, /* Usage (Wheel), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0xFF, /* Logical Minimum (-1), */
+ 0x25, 0x08, /* Logical Maximum (8), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0x05, 0x0C, /* Usage Page (Consumer Devices), */
+ 0x0A, 0x38, 0x02, /* Usage (AC PAN), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection */
+ 0xC0, /* End Collection */
+};
+
+const size_t uclogic_rdesc_xppen_artist_22r_pro_frame_size =
+ sizeof(uclogic_rdesc_xppen_artist_22r_pro_frame_arr);
+
+/* Fixed report descriptor template for XP-PEN 24 Pro reports
+ * Mostly identical to uclogic_rdesc_ugee_v2_pen_template_arr except that the X coordinate has to be
+ * 32-bits instead of 16-bits.
+ */
+const __u8 uclogic_rdesc_xppen_artist_24_pro_pen_template_arr[] = {
+ 0x05, 0x0d, /* Usage Page (Digitizers), */
+ 0x09, 0x01, /* Usage (Digitizer), */
+ 0xa1, 0x01, /* Collection (Application), */
+ 0x85, 0x02, /* Report ID (2), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xa1, 0x00, /* Collection (Physical), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x35, 0x00, /* Physical Minimum (0), */
+ 0xa4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x55, 0x0d, /* Unit Exponent (-3), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(X_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x75, 0x20, /* Report Size (32), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xb4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x45, 0x00, /* Physical Maximum (0), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x75, 0x0D, /* Report Size (13), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x09, 0x3d, /* Usage (X Tilt), */
+ 0x35, 0xC3, /* Physical Minimum (-61), */
+ 0x45, 0x3C, /* Physical Maximum (60), */
+ 0x15, 0xC3, /* Logical Minimum (-61), */
+ 0x25, 0x3C, /* Logical Maximum (60), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x3e, /* Usage (Y Tilt), */
+ 0x35, 0xC3, /* Physical Minimum (-61), */
+ 0x45, 0x3C, /* Physical Maximum (60), */
+ 0x15, 0xC3, /* Logical Minimum (-61), */
+ 0x25, 0x3C, /* Logical Maximum (60), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xc0, /* End Collection, */
+ 0xc0, /* End Collection */
+};
+const size_t uclogic_rdesc_xppen_artist_24_pro_pen_template_size =
+ sizeof(uclogic_rdesc_xppen_artist_24_pro_pen_template_arr);
+
+/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */
+const __u8 uclogic_rdesc_xppen_artist_24_pro_frame_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, UCLOGIC_RDESC_V1_FRAME_ID,
+ /* Report ID (Virtual report), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x08, /* Report Count (8), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x14, /* Usage Maximum (14h), */
+ 0x95, 0x14, /* Report Count (20), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x14, /* Report Count (20), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x38, /* Usage (Wheel), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0xFF, /* Logical Minimum (-1), */
+ 0x25, 0x08, /* Logical Maximum (8), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0x05, 0x0C, /* Usage Page (Consumer Devices), */
+ 0x0A, 0x38, 0x02, /* Usage (AC PAN), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x06, /* Input (Variable, Relative), */
+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 16, /* Report Count (16), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0xC0, /* End Collection */
+ 0xC0, /* End Collection */
+};
+
+const size_t uclogic_rdesc_xppen_artist_24_pro_frame_size =
+ sizeof(uclogic_rdesc_xppen_artist_24_pro_frame_arr);
+
/**
* uclogic_rdesc_template_apply() - apply report descriptor parameters to a
* report descriptor template, creating a report descriptor. Copies the
@@ -1242,3 +1413,4 @@ __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
return rdesc_ptr;
}
+EXPORT_SYMBOL_IF_KUNIT(uclogic_rdesc_template_apply);
diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
index 906d068f50a9..0619daa6849d 100644
--- a/drivers/hid/hid-uclogic-rdesc.h
+++ b/drivers/hid/hid-uclogic-rdesc.h
@@ -23,15 +23,15 @@
#define UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE 212
/* Fixed WP4030U report descriptor */
-extern __u8 uclogic_rdesc_wp4030u_fixed_arr[];
+extern const __u8 uclogic_rdesc_wp4030u_fixed_arr[];
extern const size_t uclogic_rdesc_wp4030u_fixed_size;
/* Fixed WP5540U report descriptor */
-extern __u8 uclogic_rdesc_wp5540u_fixed_arr[];
+extern const __u8 uclogic_rdesc_wp5540u_fixed_arr[];
extern const size_t uclogic_rdesc_wp5540u_fixed_size;
/* Fixed WP8060U report descriptor */
-extern __u8 uclogic_rdesc_wp8060u_fixed_arr[];
+extern const __u8 uclogic_rdesc_wp8060u_fixed_arr[];
extern const size_t uclogic_rdesc_wp8060u_fixed_size;
/* Size of the original descriptor of the new WP5540U tablet */
@@ -41,14 +41,14 @@ extern const size_t uclogic_rdesc_wp8060u_fixed_size;
#define UCLOGIC_RDESC_WP1062_ORIG_SIZE 254
/* Fixed WP1062 report descriptor */
-extern __u8 uclogic_rdesc_wp1062_fixed_arr[];
+extern const __u8 uclogic_rdesc_wp1062_fixed_arr[];
extern const size_t uclogic_rdesc_wp1062_fixed_size;
/* Size of the original descriptor of PF1209 tablet */
#define UCLOGIC_RDESC_PF1209_ORIG_SIZE 234
/* Fixed PF1209 report descriptor */
-extern __u8 uclogic_rdesc_pf1209_fixed_arr[];
+extern const __u8 uclogic_rdesc_pf1209_fixed_arr[];
extern const size_t uclogic_rdesc_pf1209_fixed_size;
/* Size of the original descriptors of TWHL850 tablet */
@@ -57,15 +57,15 @@ extern const size_t uclogic_rdesc_pf1209_fixed_size;
#define UCLOGIC_RDESC_TWHL850_ORIG2_SIZE 92
/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */
-extern __u8 uclogic_rdesc_twhl850_fixed0_arr[];
+extern const __u8 uclogic_rdesc_twhl850_fixed0_arr[];
extern const size_t uclogic_rdesc_twhl850_fixed0_size;
/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */
-extern __u8 uclogic_rdesc_twhl850_fixed1_arr[];
+extern const __u8 uclogic_rdesc_twhl850_fixed1_arr[];
extern const size_t uclogic_rdesc_twhl850_fixed1_size;
/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */
-extern __u8 uclogic_rdesc_twhl850_fixed2_arr[];
+extern const __u8 uclogic_rdesc_twhl850_fixed2_arr[];
extern const size_t uclogic_rdesc_twhl850_fixed2_size;
/* Size of the original descriptors of TWHA60 tablet */
@@ -73,11 +73,11 @@ extern const size_t uclogic_rdesc_twhl850_fixed2_size;
#define UCLOGIC_RDESC_TWHA60_ORIG1_SIZE 139
/* Fixed TWHA60 report descriptor, interface 0 (stylus) */
-extern __u8 uclogic_rdesc_twha60_fixed0_arr[];
+extern const __u8 uclogic_rdesc_twha60_fixed0_arr[];
extern const size_t uclogic_rdesc_twha60_fixed0_size;
/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */
-extern __u8 uclogic_rdesc_twha60_fixed1_arr[];
+extern const __u8 uclogic_rdesc_twha60_fixed1_arr[];
extern const size_t uclogic_rdesc_twha60_fixed1_size;
/* Report descriptor template placeholder head */
@@ -210,4 +210,16 @@ extern const size_t uclogic_rdesc_ugee_g5_frame_size;
/* Least-significant bit of Ugee G5 frame rotary encoder state */
#define UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB 38
+/* Fixed report descriptor for XP-Pen Arist 22R Pro frame */
+extern const __u8 uclogic_rdesc_xppen_artist_22r_pro_frame_arr[];
+extern const size_t uclogic_rdesc_xppen_artist_22r_pro_frame_size;
+
+/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */
+extern const __u8 uclogic_rdesc_xppen_artist_24_pro_pen_template_arr[];
+extern const size_t uclogic_rdesc_xppen_artist_24_pro_pen_template_size;
+
+/* Fixed report descriptor for XP-Pen Arist 24 Pro frame */
+extern const __u8 uclogic_rdesc_xppen_artist_24_pro_frame_arr[];
+extern const size_t uclogic_rdesc_xppen_artist_24_pro_frame_size;
+
#endif /* _HID_UCLOGIC_RDESC_H */
diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c
new file mode 100644
index 000000000000..549dac555d40
--- /dev/null
+++ b/drivers/hid/hid-universal-pidff.c
@@ -0,0 +1,204 @@
+// 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 "hid-ids.h"
+#include "usbhid/hid-pidff.h"
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input-event-codes.h>
+#include <linux/module.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_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
+ .driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_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_ID_LITE_STAR_GT987),
+ .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-viewsonic.c b/drivers/hid/hid-viewsonic.c
index 8024b1d370e2..532bed88bdf8 100644
--- a/drivers/hid/hid-viewsonic.c
+++ b/drivers/hid/hid-viewsonic.c
@@ -22,7 +22,7 @@
#define PD1011_RDESC_ORIG_SIZE 408
/* Fixed report descriptor of PD1011 signature pad */
-static __u8 pd1011_rdesc_fixed[] = {
+static const __u8 pd1011_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x01, /* Usage (Digitizer), */
0xA1, 0x01, /* Collection (Application), */
@@ -70,15 +70,15 @@ static __u8 pd1011_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 *viewsonic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *viewsonic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_VIEWSONIC_PD1011:
case USB_DEVICE_ID_SIGNOTEC_VIEWSONIC_PD1011:
if (*rsize == PD1011_RDESC_ORIG_SIZE) {
- rdesc = pd1011_rdesc_fixed;
*rsize = sizeof(pd1011_rdesc_fixed);
+ return pd1011_rdesc_fixed;
}
break;
}
@@ -102,4 +102,5 @@ static struct hid_driver viewsonic_driver = {
};
module_hid_driver(viewsonic_driver);
+MODULE_DESCRIPTION("HID driver for ViewSonic devices not fully compliant with HID standard");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-vivaldi-common.c b/drivers/hid/hid-vivaldi-common.c
index b0af2be94895..bf734055d4b6 100644
--- a/drivers/hid/hid-vivaldi-common.c
+++ b/drivers/hid/hid-vivaldi-common.c
@@ -138,4 +138,5 @@ const struct attribute_group *vivaldi_attribute_groups[] = {
};
EXPORT_SYMBOL_GPL(vivaldi_attribute_groups);
+MODULE_DESCRIPTION("Helpers for ChromeOS HID Vivaldi keyboards");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-vrc2.c b/drivers/hid/hid-vrc2.c
index 80a2b7ef5e66..7dc41e92f488 100644
--- a/drivers/hid/hid-vrc2.c
+++ b/drivers/hid/hid-vrc2.c
@@ -16,7 +16,7 @@
#define USB_VENDOR_ID_VRC2 (0x07c0)
#define USB_DEVICE_ID_VRC2 (0x1125)
-static __u8 vrc2_rdesc_fixed[] = {
+static const __u8 vrc2_rdesc_fixed[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
@@ -38,8 +38,8 @@ static __u8 vrc2_rdesc_fixed[] = {
0xC0, // End Collection
};
-static __u8 *vrc2_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *vrc2_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
hid_info(hdev, "fixing up VRC-2 report descriptor\n");
*rsize = sizeof(vrc2_rdesc_fixed);
diff --git a/drivers/hid/hid-waltop.c b/drivers/hid/hid-waltop.c
index bc355b1a5b30..be34be27d4d5 100644
--- a/drivers/hid/hid-waltop.c
+++ b/drivers/hid/hid-waltop.c
@@ -43,7 +43,7 @@
#define SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE 222
/* Fixed Slim Tablet 5.8 inch descriptor */
-static __u8 slim_tablet_5_8_inch_rdesc_fixed[] = {
+static const __u8 slim_tablet_5_8_inch_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -94,7 +94,7 @@ static __u8 slim_tablet_5_8_inch_rdesc_fixed[] = {
#define SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE 269
/* Fixed Slim Tablet 12.1 inch descriptor */
-static __u8 slim_tablet_12_1_inch_rdesc_fixed[] = {
+static const __u8 slim_tablet_12_1_inch_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -145,7 +145,7 @@ static __u8 slim_tablet_12_1_inch_rdesc_fixed[] = {
#define Q_PAD_RDESC_ORIG_SIZE 241
/* Fixed Q Pad descriptor */
-static __u8 q_pad_rdesc_fixed[] = {
+static const __u8 q_pad_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -198,7 +198,7 @@ static __u8 q_pad_rdesc_fixed[] = {
/*
* Fixed report descriptor for tablet with PID 0038.
*/
-static __u8 pid_0038_rdesc_fixed[] = {
+static const __u8 pid_0038_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -249,7 +249,7 @@ static __u8 pid_0038_rdesc_fixed[] = {
#define MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE 300
/* Fixed Media Tablet 10.6 inch descriptor */
-static __u8 media_tablet_10_6_inch_rdesc_fixed[] = {
+static const __u8 media_tablet_10_6_inch_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -362,7 +362,7 @@ static __u8 media_tablet_10_6_inch_rdesc_fixed[] = {
#define MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE 309
/* Fixed Media Tablet 14.1 inch descriptor */
-static __u8 media_tablet_14_1_inch_rdesc_fixed[] = {
+static const __u8 media_tablet_14_1_inch_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -473,7 +473,7 @@ static __u8 media_tablet_14_1_inch_rdesc_fixed[] = {
#define SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE 335
/* Fixed Sirius Battery Free Tablet descriptor */
-static __u8 sirius_battery_free_tablet_rdesc_fixed[] = {
+static const __u8 sirius_battery_free_tablet_rdesc_fixed[] = {
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x02, /* Usage (Pen), */
0xA1, 0x01, /* Collection (Application), */
@@ -599,50 +599,50 @@ static __u8 sirius_battery_free_tablet_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 *waltop_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *waltop_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH:
if (*rsize == SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE) {
- rdesc = slim_tablet_5_8_inch_rdesc_fixed;
*rsize = sizeof(slim_tablet_5_8_inch_rdesc_fixed);
+ return slim_tablet_5_8_inch_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH:
if (*rsize == SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE) {
- rdesc = slim_tablet_12_1_inch_rdesc_fixed;
*rsize = sizeof(slim_tablet_12_1_inch_rdesc_fixed);
+ return slim_tablet_12_1_inch_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_Q_PAD:
if (*rsize == Q_PAD_RDESC_ORIG_SIZE) {
- rdesc = q_pad_rdesc_fixed;
*rsize = sizeof(q_pad_rdesc_fixed);
+ return q_pad_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_PID_0038:
if (*rsize == PID_0038_RDESC_ORIG_SIZE) {
- rdesc = pid_0038_rdesc_fixed;
*rsize = sizeof(pid_0038_rdesc_fixed);
+ return pid_0038_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH:
if (*rsize == MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE) {
- rdesc = media_tablet_10_6_inch_rdesc_fixed;
*rsize = sizeof(media_tablet_10_6_inch_rdesc_fixed);
+ return media_tablet_10_6_inch_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH:
if (*rsize == MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE) {
- rdesc = media_tablet_14_1_inch_rdesc_fixed;
*rsize = sizeof(media_tablet_14_1_inch_rdesc_fixed);
+ return media_tablet_14_1_inch_rdesc_fixed;
}
break;
case USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET:
if (*rsize == SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE) {
- rdesc = sirius_battery_free_tablet_rdesc_fixed;
*rsize = sizeof(sirius_battery_free_tablet_rdesc_fixed);
+ return sirius_battery_free_tablet_rdesc_fixed;
}
break;
}
@@ -742,4 +742,5 @@ static struct hid_driver waltop_driver = {
};
module_hid_driver(waltop_driver);
+MODULE_DESCRIPTION("HID driver for Waltop devices not fully compliant with HID standard");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
index 26167cfb696f..5b5fc460a4c5 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) &&
@@ -1240,7 +1240,7 @@ static void wiimote_schedule(struct wiimote_data *wdata)
static void wiimote_init_timeout(struct timer_list *t)
{
- struct wiimote_data *wdata = from_timer(wdata, t, timer);
+ struct wiimote_data *wdata = timer_container_of(wdata, t, timer);
wiimote_schedule(wdata);
}
diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c
index a99dcca2e099..00f9be55f148 100644
--- a/drivers/hid/hid-wiimote-debug.c
+++ b/drivers/hid/hid-wiimote-debug.c
@@ -173,7 +173,6 @@ int wiidebug_init(struct wiimote_data *wdata)
{
struct wiimote_debug *dbg;
unsigned long flags;
- int ret = -ENOMEM;
dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
if (!dbg)
@@ -183,13 +182,9 @@ int wiidebug_init(struct wiimote_data *wdata)
dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR,
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops);
- if (!dbg->eeprom)
- goto err;
dbg->drm = debugfs_create_file("drm", S_IRUSR,
dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops);
- if (!dbg->drm)
- goto err_drm;
spin_lock_irqsave(&wdata->state.lock, flags);
wdata->debug = dbg;
@@ -197,11 +192,6 @@ int wiidebug_init(struct wiimote_data *wdata)
return 0;
-err_drm:
- debugfs_remove(dbg->eeprom);
-err:
- kfree(dbg);
- return ret;
}
void wiidebug_deinit(struct wiimote_data *wdata)
diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c
new file mode 100644
index 000000000000..ab65dc12d1e0
--- /dev/null
+++ b/drivers/hid/hid-winwing.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * HID driver for WinWing Orion 2 throttle
+ *
+ * Copyright (c) 2023 Ivan Gorinov
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/hidraw.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#define MAX_REPORT 16
+
+struct winwing_led {
+ struct led_classdev cdev;
+ struct hid_device *hdev;
+ int number;
+};
+
+struct winwing_led_info {
+ int number;
+ int max_brightness;
+ const char *led_name;
+};
+
+static const struct winwing_led_info led_info[3] = {
+ { 0, 255, "backlight" },
+ { 1, 1, "a-a" },
+ { 2, 1, "a-g" },
+};
+
+struct winwing_drv_data {
+ struct hid_device *hdev;
+ __u8 *report_buf;
+ struct mutex lock;
+ int map_more_buttons;
+ unsigned int num_leds;
+ struct winwing_led leds[];
+};
+
+static int winwing_led_write(struct led_classdev *cdev,
+ enum led_brightness br)
+{
+ struct winwing_led *led = (struct winwing_led *) cdev;
+ struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
+ __u8 *buf = data->report_buf;
+ int ret;
+
+ mutex_lock(&data->lock);
+
+ buf[0] = 0x02;
+ buf[1] = 0x60;
+ buf[2] = 0xbe;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ buf[5] = 0x03;
+ buf[6] = 0x49;
+ buf[7] = led->number;
+ buf[8] = br;
+ buf[9] = 0x00;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+
+ ret = hid_hw_output_report(led->hdev, buf, 14);
+
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int winwing_init_led(struct hid_device *hdev,
+ struct input_dev *input)
+{
+ struct winwing_drv_data *data;
+ struct winwing_led *led;
+ int ret;
+ int i;
+
+ data = hid_get_drvdata(hdev);
+
+ if (!data)
+ return -EINVAL;
+
+ data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
+
+ if (!data->report_buf)
+ return -ENOMEM;
+
+ for (i = 0; i < 3; i += 1) {
+ const struct winwing_led_info *info = &led_info[i];
+
+ led = &data->leds[i];
+ led->hdev = hdev;
+ led->number = info->number;
+ led->cdev.max_brightness = info->max_brightness;
+ led->cdev.brightness_set_blocking = winwing_led_write;
+ led->cdev.flags = LED_HW_PLUGGABLE;
+ led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "%s::%s",
+ dev_name(&input->dev),
+ info->led_name);
+
+ if (!led->cdev.name)
+ return -ENOMEM;
+
+ ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int winwing_map_button(int button, int map_more_buttons)
+{
+ if (button < 1)
+ return KEY_RESERVED;
+
+ if (button > 112)
+ return KEY_RESERVED;
+
+ if (button <= 16) {
+ /*
+ * Grip buttons [1 .. 16] are mapped to
+ * key codes BTN_TRIGGER .. BTN_DEAD
+ */
+ return (button - 1) + BTN_JOYSTICK;
+ }
+
+ if (button >= 65) {
+ /*
+ * Base buttons [65 .. 112] are mapped to
+ * key codes BTN_TRIGGER_HAPPY17 .. KEY_MAX
+ */
+ return (button - 65) + BTN_TRIGGER_HAPPY17;
+ }
+
+ if (!map_more_buttons) {
+ /*
+ * Not mapping numbers [33 .. 64] which
+ * are not assigned to any real buttons
+ */
+ if (button >= 33)
+ return KEY_RESERVED;
+ /*
+ * Grip buttons [17 .. 32] are mapped to
+ * BTN_TRIGGER_HAPPY1 .. BTN_TRIGGER_HAPPY16
+ */
+ return (button - 17) + BTN_TRIGGER_HAPPY1;
+ }
+
+ if (button >= 49) {
+ /*
+ * Grip buttons [49 .. 64] are mapped to
+ * BTN_TRIGGER_HAPPY1 .. BTN_TRIGGER_HAPPY16
+ */
+ return (button - 49) + BTN_TRIGGER_HAPPY1;
+ }
+
+ /*
+ * Grip buttons [17 .. 44] are mapped to
+ * key codes KEY_MACRO1 .. KEY_MACRO28;
+ * also mapping numbers [45 .. 48] which
+ * are not assigned to any real buttons.
+ */
+ return (button - 17) + KEY_MACRO1;
+}
+
+static int winwing_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+ unsigned long **bit, int *max)
+{
+ struct winwing_drv_data *data;
+ int code = KEY_RESERVED;
+ int button = 0;
+
+ data = hid_get_drvdata(hdev);
+
+ if (!data)
+ return -EINVAL;
+
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+ return 0;
+
+ if (field->application != HID_GD_JOYSTICK)
+ return 0;
+
+ /* Button numbers start with 1 */
+ button = usage->hid & HID_USAGE;
+
+ code = winwing_map_button(button, data->map_more_buttons);
+
+ hid_map_usage(hi, usage, bit, max, EV_KEY, code);
+
+ return 1;
+}
+
+static int winwing_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct winwing_drv_data *data;
+ size_t data_size = struct_size(data, leds, 3);
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL);
+
+ if (!data)
+ return -ENOMEM;
+
+ data->map_more_buttons = id->driver_data;
+
+ hid_set_drvdata(hdev, data);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int winwing_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ int ret;
+
+ ret = winwing_init_led(hdev, hidinput->input);
+
+ if (ret)
+ hid_err(hdev, "led init failed\n");
+
+ return ret;
+}
+
+static const struct hid_device_id winwing_devices[] = {
+ { HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */
+ { HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */
+ { HID_USB_DEVICE(0x4098, 0xbe68), .driver_data = 0 }, /* TGRIP-16EX */
+ { HID_USB_DEVICE(0x4098, 0xbe62), .driver_data = 0 }, /* TGRIP-18 */
+ {}
+};
+
+MODULE_DEVICE_TABLE(hid, winwing_devices);
+
+static struct hid_driver winwing_driver = {
+ .name = "winwing",
+ .id_table = winwing_devices,
+ .input_configured = winwing_input_configured,
+ .input_mapping = winwing_input_mapping,
+ .probe = winwing_probe,
+};
+module_hid_driver(winwing_driver);
+
+MODULE_DESCRIPTION("HID driver for WinWing Orion 2 throttle");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-xiaomi.c b/drivers/hid/hid-xiaomi.c
index a97a90afad33..ef6598550a40 100644
--- a/drivers/hid/hid-xiaomi.c
+++ b/drivers/hid/hid-xiaomi.c
@@ -14,7 +14,7 @@
/* Fixed Mi Silent Mouse report descriptor */
/* Button's Usage Maximum changed from 3 to 5 to make side buttons work */
#define MI_SILENT_MOUSE_ORIG_RDESC_LENGTH 87
-static __u8 mi_silent_mouse_rdesc_fixed[] = {
+static const __u8 mi_silent_mouse_rdesc_fixed[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
@@ -61,15 +61,15 @@ static __u8 mi_silent_mouse_rdesc_fixed[] = {
0xC0 /* End Collection */
};
-static __u8 *xiaomi_report_fixup(struct hid_device *hdev, __u8 *rdesc,
- unsigned int *rsize)
+static const __u8 *xiaomi_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_MI_SILENT_MOUSE:
if (*rsize == MI_SILENT_MOUSE_ORIG_RDESC_LENGTH) {
hid_info(hdev, "fixing up Mi Silent Mouse report descriptor\n");
- rdesc = mi_silent_mouse_rdesc_fixed;
*rsize = sizeof(mi_silent_mouse_rdesc_fixed);
+ return mi_silent_mouse_rdesc_fixed;
}
break;
}
diff --git a/drivers/hid/hid-xinmo.c b/drivers/hid/hid-xinmo.c
index 5c2860a9d8c9..66b8bfb6e647 100644
--- a/drivers/hid/hid-xinmo.c
+++ b/drivers/hid/hid-xinmo.c
@@ -56,4 +56,5 @@ static struct hid_driver xinmo_driver = {
};
module_hid_driver(xinmo_driver);
+MODULE_DESCRIPTION("HID driver for Xin-Mo devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c
index 3abaca045869..aacf7f137b18 100644
--- a/drivers/hid/hid-zpff.c
+++ b/drivers/hid/hid-zpff.c
@@ -138,4 +138,5 @@ static struct hid_driver zp_driver = {
};
module_hid_driver(zp_driver);
+MODULE_DESCRIPTION("Force feedback support for Zeroplus based devices");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c
index 0d003caee113..3bdb26f45592 100644
--- a/drivers/hid/hid-zydacron.c
+++ b/drivers/hid/hid-zydacron.c
@@ -24,7 +24,7 @@ struct zc_device {
* Zydacron remote control has an invalid HID report descriptor,
* that needs fixing before we can parse it.
*/
-static __u8 *zc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+static const __u8 *zc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
if (*rsize >= 253 &&
@@ -205,4 +205,5 @@ static struct hid_driver zc_driver = {
};
module_hid_driver(zc_driver);
+MODULE_DESCRIPTION("HID driver for zydacron remote control");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index e63c56a0d57f..bbd6f23bce78 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -32,16 +32,26 @@
static int hidraw_major;
static struct cdev hidraw_cdev;
-static struct class *hidraw_class;
+static const struct class hidraw_class = {
+ .name = "hidraw",
+};
static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
static DECLARE_RWSEM(minors_rwsem);
+static inline bool hidraw_is_revoked(struct hidraw_list *list)
+{
+ return list->revoked;
+}
+
static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
struct hidraw_list *list = file->private_data;
int ret = 0, len;
DECLARE_WAITQUEUE(wait, current);
+ if (hidraw_is_revoked(list))
+ return -ENODEV;
+
mutex_lock(&list->read_mutex);
while (ret == 0) {
@@ -138,7 +148,7 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
if ((report_type == HID_OUTPUT_REPORT) &&
!(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
- ret = hid_hw_output_report(dev, buf, count);
+ ret = __hid_hw_output_report(dev, buf, count, (u64)(long)file, false);
/*
* compatibility with old implementation of USB-HID and I2C-HID:
* if the device does not support receiving output reports,
@@ -148,8 +158,8 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
goto out_free;
}
- ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type,
- HID_REQ_SET_REPORT);
+ ret = __hid_hw_raw_request(dev, buf[0], buf, count, report_type,
+ HID_REQ_SET_REPORT, (u64)(long)file, false);
out_free:
kfree(buf);
@@ -159,9 +169,13 @@ out:
static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
+ struct hidraw_list *list = file->private_data;
ssize_t ret;
down_read(&minors_rwsem);
- ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
+ if (hidraw_is_revoked(list))
+ ret = -ENODEV;
+ else
+ ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
up_read(&minors_rwsem);
return ret;
}
@@ -225,8 +239,8 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t
goto out_free;
}
- ret = hid_hw_raw_request(dev, report_number, buf, count, report_type,
- HID_REQ_GET_REPORT);
+ ret = __hid_hw_raw_request(dev, report_number, buf, count, report_type,
+ HID_REQ_GET_REPORT, (u64)(long)file, false);
if (ret < 0)
goto out_free;
@@ -254,7 +268,7 @@ static __poll_t hidraw_poll(struct file *file, poll_table *wait)
poll_wait(file, &list->hidraw->wait, wait);
if (list->head != list->tail)
mask |= EPOLLIN | EPOLLRDNORM;
- if (!list->hidraw->exist)
+ if (!list->hidraw->exist || hidraw_is_revoked(list))
mask |= EPOLLERR | EPOLLHUP;
return mask;
}
@@ -318,6 +332,9 @@ static int hidraw_fasync(int fd, struct file *file, int on)
{
struct hidraw_list *list = file->private_data;
+ if (hidraw_is_revoked(list))
+ return -ENODEV;
+
return fasync_helper(fd, file, on, &list->fasync);
}
@@ -329,7 +346,7 @@ static void drop_ref(struct hidraw *hidraw, int exists_bit)
hid_hw_close(hidraw->hid);
wake_up_interruptible(&hidraw->wait);
}
- device_destroy(hidraw_class,
+ device_destroy(&hidraw_class,
MKDEV(hidraw_major, hidraw->minor));
} else {
--hidraw->open;
@@ -355,8 +372,11 @@ static int hidraw_release(struct inode * inode, struct file * file)
down_write(&minors_rwsem);
spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
- for (int i = list->tail; i < list->head; i++)
- kfree(list->buffer[i].value);
+ while (list->tail != list->head) {
+ kfree(list->buffer[list->tail].value);
+ list->buffer[list->tail].value = NULL;
+ list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
+ }
list_del(&list->node);
spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
kfree(list);
@@ -367,26 +387,22 @@ static int hidraw_release(struct inode * inode, struct file * file)
return 0;
}
-static long hidraw_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static int hidraw_revoke(struct hidraw_list *list)
{
- struct inode *inode = file_inode(file);
- unsigned int minor = iminor(inode);
- long ret = 0;
- struct hidraw *dev;
- void __user *user_arg = (void __user*) arg;
+ list->revoked = true;
- down_read(&minors_rwsem);
- dev = hidraw_table[minor];
- if (!dev || !dev->exist) {
- ret = -ENODEV;
- goto out;
- }
+ return 0;
+}
+
+static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *arg)
+{
+ struct hid_device *hid = dev->hid;
switch (cmd) {
case HIDIOCGRDESCSIZE:
- if (put_user(dev->hid->rsize, (int __user *)arg))
- ret = -EFAULT;
+ if (put_user(hid->rsize, (int __user *)arg))
+ return -EFAULT;
break;
case HIDIOCGRDESC:
@@ -394,105 +410,145 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
__u32 len;
if (get_user(len, (int __user *)arg))
- ret = -EFAULT;
- else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
- ret = -EINVAL;
- else if (copy_to_user(user_arg + offsetof(
- struct hidraw_report_descriptor,
- value[0]),
- dev->hid->rdesc,
- min(dev->hid->rsize, len)))
- ret = -EFAULT;
+ return -EFAULT;
+
+ if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
+ return -EINVAL;
+
+ if (copy_to_user(arg + offsetof(
+ struct hidraw_report_descriptor,
+ value[0]),
+ hid->rdesc,
+ min(hid->rsize, len)))
+ return -EFAULT;
+
break;
}
case HIDIOCGRAWINFO:
{
struct hidraw_devinfo dinfo;
- dinfo.bustype = dev->hid->bus;
- dinfo.vendor = dev->hid->vendor;
- dinfo.product = dev->hid->product;
- if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
- ret = -EFAULT;
+ dinfo.bustype = hid->bus;
+ dinfo.vendor = hid->vendor;
+ dinfo.product = hid->product;
+ if (copy_to_user(arg, &dinfo, sizeof(dinfo)))
+ return -EFAULT;
break;
}
- default:
+ case HIDIOCREVOKE:
{
- struct hid_device *hid = dev->hid;
- if (_IOC_TYPE(cmd) != 'H') {
- ret = -EINVAL;
- break;
- }
+ struct hidraw_list *list = file->private_data;
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
- break;
- }
+ if (arg)
+ return -EINVAL;
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
- break;
- }
+ return hidraw_revoke(list);
+ }
+ default:
+ /*
+ * None of the above ioctls can return -EAGAIN, so
+ * use it as a marker that we need to check variable
+ * length ioctls.
+ */
+ return -EAGAIN;
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
- break;
- }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
- int len = _IOC_SIZE(cmd);
- ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
- break;
- }
+ return 0;
+}
- /* Begin Read-only ioctls. */
- if (_IOC_DIR(cmd) != _IOC_READ) {
- ret = -EINVAL;
- break;
- }
+static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *user_arg)
+{
+ int len = _IOC_SIZE(cmd);
+
+ switch (cmd & ~IOCSIZE_MASK) {
+ case HIDIOCSFEATURE(0):
+ return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
+ case HIDIOCGFEATURE(0):
+ return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
+ case HIDIOCSINPUT(0):
+ return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
+ case HIDIOCGINPUT(0):
+ return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
+ case HIDIOCSOUTPUT(0):
+ return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
+ case HIDIOCGOUTPUT(0):
+ return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
- int len = strlen(hid->name) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->name, len) ?
- -EFAULT : len;
- break;
- }
+ return -EINVAL;
+}
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
- int len = strlen(hid->phys) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->phys, len) ?
- -EFAULT : len;
- break;
- }
+static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
+ void __user *user_arg)
+{
+ struct hid_device *hid = dev->hid;
+ int len = _IOC_SIZE(cmd);
+ int field_len;
+
+ switch (cmd & ~IOCSIZE_MASK) {
+ case HIDIOCGRAWNAME(0):
+ field_len = strlen(hid->name) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len;
+ case HIDIOCGRAWPHYS(0):
+ field_len = strlen(hid->phys) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len;
+ case HIDIOCGRAWUNIQ(0):
+ field_len = strlen(hid->uniq) + 1;
+ if (len > field_len)
+ len = field_len;
+ return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len;
+ }
- if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
- int len = strlen(hid->uniq) + 1;
- if (len > _IOC_SIZE(cmd))
- len = _IOC_SIZE(cmd);
- ret = copy_to_user(user_arg, hid->uniq, len) ?
- -EFAULT : len;
- break;
- }
- }
+ return -EINVAL;
+}
+
+static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct inode *inode = file_inode(file);
+ unsigned int minor = iminor(inode);
+ struct hidraw *dev;
+ struct hidraw_list *list = file->private_data;
+ void __user *user_arg = (void __user *)arg;
+ int ret;
+
+ down_read(&minors_rwsem);
+ dev = hidraw_table[minor];
+ if (!dev || !dev->exist || hidraw_is_revoked(list)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (_IOC_TYPE(cmd) != 'H') {
+ ret = -EINVAL;
+ goto out;
+ }
+ if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {
ret = -ENOTTY;
+ goto out;
}
+
+ ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg);
+ if (ret != -EAGAIN)
+ goto out;
+
+ switch (_IOC_DIR(cmd)) {
+ case (_IOC_READ | _IOC_WRITE):
+ ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg);
+ break;
+ case _IOC_READ:
+ ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg);
+ break;
+ default:
+ /* Any other IOC_DIR is wrong */
+ ret = -EINVAL;
+ }
+
out:
up_read(&minors_rwsem);
return ret;
@@ -522,7 +578,7 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
list_for_each_entry(list, &dev->list, node) {
int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
- if (new_head == list->tail)
+ if (hidraw_is_revoked(list) || new_head == list->tail)
continue;
if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
@@ -569,7 +625,7 @@ int hidraw_connect(struct hid_device *hid)
goto out;
}
- dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),
+ dev->dev = device_create(&hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),
NULL, "%s%d", "hidraw", minor);
if (IS_ERR(dev->dev)) {
@@ -623,11 +679,9 @@ int __init hidraw_init(void)
hidraw_major = MAJOR(dev_id);
- hidraw_class = class_create("hidraw");
- if (IS_ERR(hidraw_class)) {
- result = PTR_ERR(hidraw_class);
+ result = class_register(&hidraw_class);
+ if (result)
goto error_cdev;
- }
cdev_init(&hidraw_cdev, &hidraw_ops);
result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES);
@@ -639,7 +693,7 @@ out:
return result;
error_class:
- class_destroy(hidraw_class);
+ class_unregister(&hidraw_class);
error_cdev:
unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
goto out;
@@ -650,7 +704,7 @@ void hidraw_exit(void)
dev_t dev_id = MKDEV(hidraw_major, 0);
cdev_del(&hidraw_cdev);
- class_destroy(hidraw_class);
+ class_unregister(&hidraw_class);
unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
}
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index 3be17109301a..e8d51f410cc1 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -2,13 +2,14 @@
menuconfig I2C_HID
tristate "I2C HID support"
default y
- depends on I2C && INPUT && HID
+ depends on I2C
if I2C_HID
config I2C_HID_ACPI
tristate "HID over I2C transport layer ACPI driver"
depends on ACPI
+ depends on DRM || !DRM
select I2C_HID_CORE
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
@@ -25,6 +26,7 @@ config I2C_HID_OF
tristate "HID over I2C transport layer Open Firmware driver"
# No "depends on OF" because this can also be used for manually
# (board-file) instantiated "hid-over-i2c" type i2c-clients.
+ depends on DRM || !DRM
select I2C_HID_CORE
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
@@ -41,6 +43,7 @@ config I2C_HID_OF
config I2C_HID_OF_ELAN
tristate "Driver for Elan hid-i2c based devices on OF systems"
depends on OF
+ depends on DRM || !DRM
select I2C_HID_CORE
help
Say Y here if you want support for Elan i2c devices that use
@@ -56,6 +59,7 @@ config I2C_HID_OF_ELAN
config I2C_HID_OF_GOODIX
tristate "Driver for Goodix hid-i2c based devices on OF systems"
depends on OF
+ depends on DRM || !DRM
select I2C_HID_CORE
help
Say Y here if you want support for Goodix i2c devices that use
@@ -70,5 +74,7 @@ config I2C_HID_OF_GOODIX
config I2C_HID_CORE
tristate
+ # We need to call into panel code so if DRM=m, this can't be 'y'
+ depends on DRM || !DRM
endif
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
index ac918a9ea8d3..abd700a101f4 100644
--- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -40,6 +40,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
* ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
*/
{ "CHPN0001" },
+ /*
+ * The IDEA5002 ACPI device causes high interrupt usage and spurious
+ * wakeups from suspend.
+ */
+ { "IDEA5002" },
{ }
};
@@ -71,6 +76,13 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
return hid_descriptor_address;
}
+static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
+{
+ struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
+
+ i2c_hid_acpi_get_descriptor(ihid_acpi);
+}
+
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
{
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
@@ -91,6 +103,7 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
+ ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
if (ret < 0)
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index efbba0465eef..63f46a2e5788 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -36,18 +36,22 @@
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/mutex.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
+
+#include <drm/drm_panel.h>
#include "../hid-ids.h"
#include "i2c-hid.h"
/* quirks to control the device */
-#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0)
-#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1)
-#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4)
-#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5)
-#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6)
-#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7)
+#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(0)
+#define I2C_HID_QUIRK_BOGUS_IRQ BIT(1)
+#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(2)
+#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(3)
+#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
@@ -62,7 +66,6 @@
/* flags */
#define I2C_HID_STARTED 0
#define I2C_HID_RESET_PENDING 1
-#define I2C_HID_READ_PENDING 2
#define I2C_HID_PWR_ON 0x00
#define I2C_HID_PWR_SLEEP 0x01
@@ -104,9 +107,14 @@ struct i2c_hid {
wait_queue_head_t wait; /* For waiting the interrupt */
+ struct mutex cmd_lock; /* protects cmdbuf and rawbuf */
struct mutex reset_lock;
struct i2chid_ops *ops;
+ struct drm_panel_follower panel_follower;
+ struct work_struct panel_follower_work;
+ bool is_panel_follower;
+ bool panel_follower_work_finished;
};
static const struct i2c_hid_quirks {
@@ -114,8 +122,6 @@ static const struct i2c_hid_quirks {
__u16 idProduct;
__u32 quirks;
} i2c_hid_quirks[] = {
- { USB_VENDOR_ID_WEIDA, HID_ANY_ID,
- I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
{ I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15,
@@ -128,12 +134,21 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_RESET_ON_RESUME },
{ USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720,
I2C_HID_QUIRK_BAD_INPUT_SIZE },
+ { 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,
I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET |
I2C_HID_QUIRK_BOGUS_IRQ },
+ { I2C_VENDOR_ID_GOODIX, I2C_DEVICE_ID_GOODIX_0D42,
+ I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME },
{ 0, 0 }
};
@@ -158,6 +173,24 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
return quirks;
}
+static int i2c_hid_probe_address(struct i2c_hid *ihid)
+{
+ int ret;
+
+ /*
+ * Some STM-based devices need 400µs after a rising clock edge to wake
+ * from deep sleep, in which case the first read will fail. Try after a
+ * short sleep to see if the device came alive on the bus. Certain
+ * Weida Tech devices also need this.
+ */
+ ret = i2c_smbus_read_byte(ihid->client);
+ if (ret < 0) {
+ usleep_range(400, 500);
+ ret = i2c_smbus_read_byte(ihid->client);
+ }
+ return ret < 0 ? ret : 0;
+}
+
static int i2c_hid_xfer(struct i2c_hid *ihid,
u8 *send_buf, int send_len, u8 *recv_buf, int recv_len)
{
@@ -184,15 +217,10 @@ static int i2c_hid_xfer(struct i2c_hid *ihid,
msgs[n].len = recv_len;
msgs[n].buf = recv_buf;
n++;
-
- set_bit(I2C_HID_READ_PENDING, &ihid->flags);
}
ret = i2c_transfer(client->adapter, msgs, n);
- if (recv_len)
- clear_bit(I2C_HID_READ_PENDING, &ihid->flags);
-
if (ret != n)
return ret < 0 ? ret : -EIO;
@@ -202,6 +230,8 @@ static int i2c_hid_xfer(struct i2c_hid *ihid,
static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg,
void *buf, size_t len)
{
+ guard(mutex)(&ihid->cmd_lock);
+
*(__le16 *)ihid->cmdbuf = reg;
return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len);
@@ -234,6 +264,8 @@ static int i2c_hid_get_report(struct i2c_hid *ihid,
i2c_hid_dbg(ihid, "%s\n", __func__);
+ guard(mutex)(&ihid->cmd_lock);
+
/* Command register goes first */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length += sizeof(__le16);
@@ -258,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;
}
@@ -324,6 +356,8 @@ static int i2c_hid_set_or_send_report(struct i2c_hid *ihid,
if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0)
return -ENOSYS;
+ guard(mutex)(&ihid->cmd_lock);
+
if (do_set) {
/* Command register goes first */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
@@ -366,6 +400,8 @@ static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state)
{
size_t length;
+ guard(mutex)(&ihid->cmd_lock);
+
/* SET_POWER uses command register */
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
length = sizeof(__le16);
@@ -385,26 +421,22 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
i2c_hid_dbg(ihid, "%s\n", __func__);
/*
- * Some devices require to send a command to wakeup before power on.
- * The call will get a return value (EREMOTEIO) but device will be
- * triggered and activated. After that, it goes like a normal device.
+ * Some STM-based devices need 400µs after a rising clock edge to wake
+ * from deep sleep, in which case the first request will fail due to
+ * the address not being acknowledged. Try after a short sleep to see
+ * if the device came alive on the bus. Certain Weida Tech devices also
+ * need this.
*/
- if (power_state == I2C_HID_PWR_ON &&
- ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) {
+ ret = i2c_hid_set_power_command(ihid, power_state);
+ if (ret && power_state == I2C_HID_PWR_ON) {
+ usleep_range(400, 500);
ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON);
-
- /* Device was already activated */
- if (!ret)
- goto set_pwr_exit;
}
- ret = i2c_hid_set_power_command(ihid, power_state);
if (ret)
dev_err(&ihid->client->dev,
"failed to change power setting.\n");
-set_pwr_exit:
-
/*
* The HID over I2C specification states that if a DEVICE needs time
* after the PWR_ON request, it should utilise CLOCK stretching.
@@ -420,78 +452,71 @@ set_pwr_exit:
return ret;
}
-static int i2c_hid_execute_reset(struct i2c_hid *ihid)
+static int i2c_hid_start_hwreset(struct i2c_hid *ihid)
{
size_t length = 0;
int ret;
- i2c_hid_dbg(ihid, "resetting...\n");
+ i2c_hid_dbg(ihid, "%s\n", __func__);
- /* Prepare reset command. Command register goes first. */
- *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
- length += sizeof(__le16);
- /* Next is RESET command itself */
- length += i2c_hid_encode_command(ihid->cmdbuf + length,
- I2C_HID_OPCODE_RESET, 0, 0);
+ /*
+ * This prevents sending feature reports while the device is
+ * being reset. Otherwise we may lose the reset complete
+ * interrupt.
+ */
+ lockdep_assert_held(&ihid->reset_lock);
- set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
+ ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+ if (ret)
+ return ret;
- ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
- if (ret) {
- dev_err(&ihid->client->dev, "failed to reset device.\n");
- goto out;
- }
+ scoped_guard(mutex, &ihid->cmd_lock) {
+ /* Prepare reset command. Command register goes first. */
+ *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
+ length += sizeof(__le16);
+ /* Next is RESET command itself */
+ length += i2c_hid_encode_command(ihid->cmdbuf + length,
+ I2C_HID_OPCODE_RESET, 0, 0);
- if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) {
- msleep(100);
- goto out;
- }
+ set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
- i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
- if (!wait_event_timeout(ihid->wait,
- !test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
- msecs_to_jiffies(5000))) {
- ret = -ENODATA;
- goto out;
+ ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
+ if (ret) {
+ dev_err(&ihid->client->dev,
+ "failed to reset device: %d\n", ret);
+ break;
+ }
+
+ return 0;
}
- i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
-out:
+ /* Clean up if sending reset command failed */
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
+ i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
return ret;
}
-static int i2c_hid_hwreset(struct i2c_hid *ihid)
+static int i2c_hid_finish_hwreset(struct i2c_hid *ihid)
{
- int ret;
-
- i2c_hid_dbg(ihid, "%s\n", __func__);
+ int ret = 0;
- /*
- * This prevents sending feature reports while the device is
- * being reset. Otherwise we may lose the reset complete
- * interrupt.
- */
- mutex_lock(&ihid->reset_lock);
-
- ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
- if (ret)
- goto out_unlock;
+ i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
- ret = i2c_hid_execute_reset(ihid);
- if (ret) {
- dev_err(&ihid->client->dev,
- "failed to reset device: %d\n", ret);
- i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
- goto out_unlock;
+ if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) {
+ msleep(100);
+ clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
+ } else if (!wait_event_timeout(ihid->wait,
+ !test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
+ msecs_to_jiffies(1000))) {
+ dev_warn(&ihid->client->dev, "device did not ack reset within 1000 ms\n");
+ clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
}
+ i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
/* At least some SIS devices need this after reset */
if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET))
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
-out_unlock:
- mutex_unlock(&ihid->reset_lock);
return ret;
}
@@ -560,9 +585,6 @@ static irqreturn_t i2c_hid_irq(int irq, void *dev_id)
{
struct i2c_hid *ihid = dev_id;
- if (test_bit(I2C_HID_READ_PENDING, &ihid->flags))
- return IRQ_HANDLED;
-
i2c_hid_get_input(ihid);
return IRQ_HANDLED;
@@ -723,11 +745,10 @@ static int i2c_hid_parse(struct hid_device *hid)
struct i2c_client *client = hid->driver_data;
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct i2c_hid_desc *hdesc = &ihid->hdesc;
+ char *rdesc = NULL, *use_override = NULL;
unsigned int rsize;
- char *rdesc;
int ret;
int tries = 3;
- char *use_override;
i2c_hid_dbg(ihid, "entering %s\n", __func__);
@@ -737,11 +758,15 @@ static int i2c_hid_parse(struct hid_device *hid)
return -EINVAL;
}
+ mutex_lock(&ihid->reset_lock);
do {
- ret = i2c_hid_hwreset(ihid);
- if (ret)
+ ret = i2c_hid_start_hwreset(ihid);
+ if (ret == 0)
+ ret = i2c_hid_finish_hwreset(ihid);
+ else
msleep(1000);
} while (tries-- > 0 && ret);
+ mutex_unlock(&ihid->reset_lock);
if (ret)
return ret;
@@ -754,11 +779,8 @@ static int i2c_hid_parse(struct hid_device *hid)
i2c_hid_dbg(ihid, "Using a HID report descriptor override\n");
} else {
rdesc = kzalloc(rsize, GFP_KERNEL);
-
- if (!rdesc) {
- dbg_hid("couldn't allocate rdesc memory\n");
+ if (!rdesc)
return -ENOMEM;
- }
i2c_hid_dbg(ihid, "asking HID report descriptor\n");
@@ -767,23 +789,21 @@ static int i2c_hid_parse(struct hid_device *hid)
rdesc, rsize);
if (ret) {
hid_err(hid, "reading report descriptor failed\n");
- kfree(rdesc);
- return -EIO;
+ goto out;
}
}
i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc);
ret = hid_parse_report(hid, rdesc, rsize);
+ if (ret)
+ dbg_hid("parsing report descriptor failed\n");
+
+out:
if (!use_override)
kfree(rdesc);
- if (ret) {
- dbg_hid("parsing report descriptor failed\n");
- return ret;
- }
-
- return 0;
+ return ret;
}
static int i2c_hid_start(struct hid_device *hid)
@@ -855,7 +875,8 @@ static int i2c_hid_init_irq(struct i2c_client *client)
irqflags = IRQF_TRIGGER_LOW;
ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
- irqflags | IRQF_ONESHOT, client->name, ihid);
+ irqflags | IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ client->name, ihid);
if (ret < 0) {
dev_warn(&client->dev,
"Could not register for %s interrupt, irq = %d,"
@@ -940,6 +961,264 @@ static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid)
ihid->ops->shutdown_tail(ihid->ops);
}
+static void i2c_hid_core_restore_sequence(struct i2c_hid *ihid)
+{
+ if (!ihid->ops->restore_sequence)
+ return;
+
+ ihid->ops->restore_sequence(ihid->ops);
+}
+
+static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff)
+{
+ struct i2c_client *client = ihid->client;
+ struct hid_device *hid = ihid->hid;
+ int ret;
+
+ ret = hid_driver_suspend(hid, PMSG_SUSPEND);
+ if (ret < 0)
+ return ret;
+
+ /* Save some power */
+ if (!(ihid->quirks & I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND))
+ i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
+
+ disable_irq(client->irq);
+
+ if (force_poweroff || !device_may_wakeup(&client->dev))
+ i2c_hid_core_power_down(ihid);
+
+ return 0;
+}
+
+static int i2c_hid_core_resume(struct i2c_hid *ihid)
+{
+ struct i2c_client *client = ihid->client;
+ struct hid_device *hid = ihid->hid;
+ int ret;
+
+ if (!device_may_wakeup(&client->dev))
+ i2c_hid_core_power_up(ihid);
+
+ enable_irq(client->irq);
+
+ /* On Goodix 27c6:0d42 wait extra time before device wakeup.
+ * It's not clear why but if we send wakeup too early, the device will
+ * never trigger input interrupts.
+ */
+ if (ihid->quirks & I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME)
+ msleep(1500);
+
+ /* Instead of resetting device, simply powers the device on. This
+ * solves "incomplete reports" on Raydium devices 2386:3118 and
+ * 2386:4B33 and fixes various SIS touchscreens no longer sending
+ * data after a suspend/resume.
+ *
+ * However some ALPS touchpads generate IRQ storm without reset, so
+ * let's still reset them here.
+ */
+ if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) {
+ mutex_lock(&ihid->reset_lock);
+ ret = i2c_hid_start_hwreset(ihid);
+ if (ret == 0)
+ ret = i2c_hid_finish_hwreset(ihid);
+ mutex_unlock(&ihid->reset_lock);
+ } else {
+ ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+ }
+
+ if (ret)
+ return ret;
+
+ return hid_driver_reset_resume(hid);
+}
+
+/*
+ * Check that the device exists and parse the HID descriptor.
+ */
+static int __i2c_hid_core_probe(struct i2c_hid *ihid)
+{
+ struct i2c_client *client = ihid->client;
+ struct hid_device *hid = ihid->hid;
+ int ret;
+
+ ret = i2c_hid_probe_address(ihid);
+ if (ret < 0) {
+ i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
+ return -ENXIO;
+ }
+
+ ret = i2c_hid_fetch_hid_descriptor(ihid);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to fetch the HID Descriptor\n");
+ return ret;
+ }
+
+ hid->version = le16_to_cpu(ihid->hdesc.bcdVersion);
+ hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
+ hid->product = le16_to_cpu(ihid->hdesc.wProductID);
+
+ hid->initial_quirks |= i2c_hid_get_dmi_quirks(hid->vendor,
+ hid->product);
+
+ snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
+ client->name, (u16)hid->vendor, (u16)hid->product);
+ strscpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
+
+ ihid->quirks = i2c_hid_lookup_quirk(hid->vendor, hid->product);
+
+ return 0;
+}
+
+static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
+{
+ struct i2c_client *client = ihid->client;
+ struct hid_device *hid = ihid->hid;
+ int ret;
+
+ enable_irq(client->irq);
+
+ ret = hid_add_device(hid);
+ if (ret) {
+ if (ret != -ENODEV)
+ hid_err(client, "can't add hid device: %d\n", ret);
+ disable_irq(client->irq);
+ return ret;
+ }
+
+ /* 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)
+{
+ int ret;
+
+ ret = i2c_hid_core_power_up(ihid);
+ if (ret)
+ return ret;
+
+ ret = __i2c_hid_core_probe(ihid);
+ if (ret)
+ goto err_power_down;
+
+ ret = i2c_hid_core_register_hid(ihid);
+ if (ret)
+ goto err_power_down;
+
+ return 0;
+
+err_power_down:
+ i2c_hid_core_power_down(ihid);
+
+ return ret;
+}
+
+static void ihid_core_panel_follower_work(struct work_struct *work)
+{
+ struct i2c_hid *ihid = container_of(work, struct i2c_hid,
+ panel_follower_work);
+ struct hid_device *hid = ihid->hid;
+ int ret;
+
+ /*
+ * hid->version is set on the first power up. If it's still zero then
+ * this is the first power on so we should perform initial power up
+ * steps.
+ */
+ if (!hid->version)
+ ret = i2c_hid_core_probe_panel_follower(ihid);
+ else
+ ret = i2c_hid_core_resume(ihid);
+
+ if (ret)
+ dev_warn(&ihid->client->dev, "Power on failed: %d\n", ret);
+ else
+ WRITE_ONCE(ihid->panel_follower_work_finished, true);
+
+ /*
+ * The work APIs provide a number of memory ordering guarantees
+ * including one that says that memory writes before schedule_work()
+ * are always visible to the work function, but they don't appear to
+ * guarantee that a write that happened in the work is visible after
+ * cancel_work_sync(). We'll add a write memory barrier here to match
+ * with i2c_hid_core_panel_unpreparing() to ensure that our write to
+ * panel_follower_work_finished is visible there.
+ */
+ smp_wmb();
+}
+
+static int i2c_hid_core_panel_follower_resume(struct drm_panel_follower *follower)
+{
+ struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
+
+ /*
+ * Powering on a touchscreen can be a slow process. Queue the work to
+ * the system workqueue so we don't block the panel's power up.
+ */
+ WRITE_ONCE(ihid->panel_follower_work_finished, false);
+ schedule_work(&ihid->panel_follower_work);
+
+ return 0;
+}
+
+static int i2c_hid_core_panel_follower_suspend(struct drm_panel_follower *follower)
+{
+ struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
+
+ cancel_work_sync(&ihid->panel_follower_work);
+
+ /* Match with ihid_core_panel_follower_work() */
+ smp_rmb();
+ if (!READ_ONCE(ihid->panel_follower_work_finished))
+ return 0;
+
+ return i2c_hid_core_suspend(ihid, true);
+}
+
+static const struct drm_panel_follower_funcs
+ i2c_hid_core_panel_follower_prepare_funcs = {
+ .panel_prepared = i2c_hid_core_panel_follower_resume,
+ .panel_unpreparing = i2c_hid_core_panel_follower_suspend,
+};
+
+static const struct drm_panel_follower_funcs
+ i2c_hid_core_panel_follower_enable_funcs = {
+ .panel_enabled = i2c_hid_core_panel_follower_resume,
+ .panel_disabling = i2c_hid_core_panel_follower_suspend,
+};
+
+static int i2c_hid_core_register_panel_follower(struct i2c_hid *ihid)
+{
+ struct device *dev = &ihid->client->dev;
+ int ret;
+
+ if (ihid->hid->initial_quirks & HID_QUIRK_POWER_ON_AFTER_BACKLIGHT)
+ ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_enable_funcs;
+ else
+ ihid->panel_follower.funcs = &i2c_hid_core_panel_follower_prepare_funcs;
+
+ /*
+ * If we're not in control of our own power up/power down then we can't
+ * do the logic to manage wakeups. Give a warning if a user thought
+ * that was possible then force the capability off.
+ */
+ if (device_can_wakeup(dev)) {
+ dev_warn(dev, "Can't wakeup if following panel\n");
+ device_set_wakeup_capable(dev, false);
+ }
+
+ ret = drm_panel_add_follower(dev, &ihid->panel_follower);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
u16 hid_descriptor_address, u32 quirks)
{
@@ -966,53 +1245,30 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
if (!ihid)
return -ENOMEM;
- ihid->ops = ops;
-
- ret = i2c_hid_core_power_up(ihid);
- if (ret)
- return ret;
-
i2c_set_clientdata(client, ihid);
+ ihid->ops = ops;
ihid->client = client;
-
ihid->wHIDDescRegister = cpu_to_le16(hid_descriptor_address);
+ ihid->is_panel_follower = drm_is_panel_follower(&client->dev);
init_waitqueue_head(&ihid->wait);
+ mutex_init(&ihid->cmd_lock);
mutex_init(&ihid->reset_lock);
+ INIT_WORK(&ihid->panel_follower_work, ihid_core_panel_follower_work);
/* we need to allocate the command buffer without knowing the maximum
* size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
* real computation later. */
ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE);
if (ret < 0)
- goto err_powered;
-
+ return ret;
device_enable_async_suspend(&client->dev);
- /* Make sure there is something at this address */
- ret = i2c_smbus_read_byte(client);
- if (ret < 0) {
- i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
- ret = -ENXIO;
- goto err_powered;
- }
-
- ret = i2c_hid_fetch_hid_descriptor(ihid);
- if (ret < 0) {
- dev_err(&client->dev,
- "Failed to fetch the HID Descriptor\n");
- goto err_powered;
- }
-
- ret = i2c_hid_init_irq(client);
- if (ret < 0)
- goto err_powered;
-
hid = hid_allocate_device();
if (IS_ERR(hid)) {
ret = PTR_ERR(hid);
- goto err_irq;
+ goto err_free_buffers;
}
ihid->hid = hid;
@@ -1021,38 +1277,46 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
hid->ll_driver = &i2c_hid_ll_driver;
hid->dev.parent = &client->dev;
hid->bus = BUS_I2C;
- hid->version = le16_to_cpu(ihid->hdesc.bcdVersion);
- hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
- hid->product = le16_to_cpu(ihid->hdesc.wProductID);
-
hid->initial_quirks = quirks;
- hid->initial_quirks |= i2c_hid_get_dmi_quirks(hid->vendor,
- hid->product);
- snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
- client->name, (u16)hid->vendor, (u16)hid->product);
- strscpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
-
- ihid->quirks = i2c_hid_lookup_quirk(hid->vendor, hid->product);
+ /* Power on and probe unless device is a panel follower. */
+ if (!ihid->is_panel_follower) {
+ ret = i2c_hid_core_power_up(ihid);
+ if (ret < 0)
+ goto err_destroy_device;
- ret = hid_add_device(hid);
- if (ret) {
- if (ret != -ENODEV)
- hid_err(client, "can't add hid device: %d\n", ret);
- goto err_mem_free;
+ ret = __i2c_hid_core_probe(ihid);
+ if (ret < 0)
+ goto err_power_down;
}
- return 0;
+ ret = i2c_hid_init_irq(client);
+ if (ret < 0)
+ goto err_power_down;
-err_mem_free:
- hid_destroy_device(hid);
+ /*
+ * If we're a panel follower, we'll register when the panel turns on;
+ * otherwise we do it right away.
+ */
+ if (ihid->is_panel_follower)
+ ret = i2c_hid_core_register_panel_follower(ihid);
+ else
+ ret = i2c_hid_core_register_hid(ihid);
+ if (ret)
+ goto err_free_irq;
-err_irq:
- free_irq(client->irq, ihid);
+ return 0;
-err_powered:
- i2c_hid_core_power_down(ihid);
+err_free_irq:
+ free_irq(client->irq, ihid);
+err_power_down:
+ if (!ihid->is_panel_follower)
+ i2c_hid_core_power_down(ihid);
+err_destroy_device:
+ hid_destroy_device(hid);
+err_free_buffers:
i2c_hid_free_buffers(ihid);
+
return ret;
}
EXPORT_SYMBOL_GPL(i2c_hid_core_probe);
@@ -1062,6 +1326,15 @@ void i2c_hid_core_remove(struct i2c_client *client)
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid;
+ /*
+ * If we're a follower, the act of unfollowing will cause us to be
+ * powered down. Otherwise we need to manually do it.
+ */
+ if (ihid->is_panel_follower)
+ drm_panel_remove_follower(&ihid->panel_follower);
+ else
+ i2c_hid_core_suspend(ihid, true);
+
hid = ihid->hid;
hid_destroy_device(hid);
@@ -1069,8 +1342,6 @@ void i2c_hid_core_remove(struct i2c_client *client)
if (ihid->bufsize)
i2c_hid_free_buffers(ihid);
-
- i2c_hid_core_power_down(ihid);
}
EXPORT_SYMBOL_GPL(i2c_hid_core_remove);
@@ -1085,63 +1356,48 @@ void i2c_hid_core_shutdown(struct i2c_client *client)
}
EXPORT_SYMBOL_GPL(i2c_hid_core_shutdown);
-#ifdef CONFIG_PM_SLEEP
-static int i2c_hid_core_suspend(struct device *dev)
+static int i2c_hid_core_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct i2c_hid *ihid = i2c_get_clientdata(client);
- struct hid_device *hid = ihid->hid;
- int ret;
- ret = hid_driver_suspend(hid, PMSG_SUSPEND);
- if (ret < 0)
- return ret;
+ if (ihid->is_panel_follower)
+ return 0;
- /* Save some power */
- i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
+ return i2c_hid_core_suspend(ihid, false);
+}
- disable_irq(client->irq);
+static int i2c_hid_core_pm_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct i2c_hid *ihid = i2c_get_clientdata(client);
- if (!device_may_wakeup(&client->dev))
- i2c_hid_core_power_down(ihid);
+ if (ihid->is_panel_follower)
+ return 0;
- return 0;
+ return i2c_hid_core_resume(ihid);
}
-static int i2c_hid_core_resume(struct device *dev)
+static int i2c_hid_core_pm_restore(struct device *dev)
{
- int ret;
struct i2c_client *client = to_i2c_client(dev);
struct i2c_hid *ihid = i2c_get_clientdata(client);
- struct hid_device *hid = ihid->hid;
-
- if (!device_may_wakeup(&client->dev))
- i2c_hid_core_power_up(ihid);
- enable_irq(client->irq);
-
- /* Instead of resetting device, simply powers the device on. This
- * solves "incomplete reports" on Raydium devices 2386:3118 and
- * 2386:4B33 and fixes various SIS touchscreens no longer sending
- * data after a suspend/resume.
- *
- * However some ALPS touchpads generate IRQ storm without reset, so
- * let's still reset them here.
- */
- if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME)
- ret = i2c_hid_hwreset(ihid);
- else
- ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+ if (ihid->is_panel_follower)
+ return 0;
- if (ret)
- return ret;
+ i2c_hid_core_restore_sequence(ihid);
- return hid_driver_reset_resume(hid);
+ return i2c_hid_core_resume(ihid);
}
-#endif
const struct dev_pm_ops i2c_hid_core_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_core_suspend, i2c_hid_core_resume)
+ .suspend = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .resume = pm_sleep_ptr(i2c_hid_core_pm_resume),
+ .freeze = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .thaw = pm_sleep_ptr(i2c_hid_core_pm_resume),
+ .poweroff = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+ .restore = pm_sleep_ptr(i2c_hid_core_pm_restore),
};
EXPORT_SYMBOL_GPL(i2c_hid_core_pm);
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
index 029045d9661c..0215f217f6d8 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
@@ -8,6 +8,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
+#include <linux/hid.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -18,9 +19,12 @@
#include "i2c-hid.h"
struct elan_i2c_hid_chip_data {
- unsigned int post_gpio_reset_delay_ms;
+ unsigned int post_gpio_reset_on_delay_ms;
+ unsigned int post_gpio_reset_off_delay_ms;
unsigned int post_power_delay_ms;
u16 hid_descriptor_address;
+ const char *main_supply_name;
+ bool power_after_backlight;
};
struct i2c_hid_of_elan {
@@ -29,6 +33,7 @@ struct i2c_hid_of_elan {
struct regulator *vcc33;
struct regulator *vccio;
struct gpio_desc *reset_gpio;
+ bool no_reset_on_power_off;
const struct elan_i2c_hid_chip_data *chip_data;
};
@@ -38,24 +43,35 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops)
container_of(ops, struct i2c_hid_of_elan, ops);
int ret;
- ret = regulator_enable(ihid_elan->vcc33);
- if (ret)
- return ret;
+ gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1);
- ret = regulator_enable(ihid_elan->vccio);
- if (ret) {
- regulator_disable(ihid_elan->vcc33);
- return ret;
+ if (ihid_elan->vcc33) {
+ ret = regulator_enable(ihid_elan->vcc33);
+ if (ret)
+ goto err_deassert_reset;
}
+ ret = regulator_enable(ihid_elan->vccio);
+ if (ret)
+ goto err_disable_vcc33;
+
if (ihid_elan->chip_data->post_power_delay_ms)
msleep(ihid_elan->chip_data->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
- if (ihid_elan->chip_data->post_gpio_reset_delay_ms)
- msleep(ihid_elan->chip_data->post_gpio_reset_delay_ms);
+ if (ihid_elan->chip_data->post_gpio_reset_on_delay_ms)
+ msleep(ihid_elan->chip_data->post_gpio_reset_on_delay_ms);
return 0;
+
+err_disable_vcc33:
+ if (ihid_elan->vcc33)
+ regulator_disable(ihid_elan->vcc33);
+err_deassert_reset:
+ if (ihid_elan->no_reset_on_power_off)
+ gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
+
+ return ret;
}
static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
@@ -63,14 +79,27 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
struct i2c_hid_of_elan *ihid_elan =
container_of(ops, struct i2c_hid_of_elan, ops);
- gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1);
+ /*
+ * Do not assert reset when the hardware allows for it to remain
+ * deasserted regardless of the state of the (shared) power supply to
+ * avoid wasting power when the supply is left on.
+ */
+ if (!ihid_elan->no_reset_on_power_off)
+ gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1);
+
+ if (ihid_elan->chip_data->post_gpio_reset_off_delay_ms)
+ msleep(ihid_elan->chip_data->post_gpio_reset_off_delay_ms);
+
regulator_disable(ihid_elan->vccio);
- regulator_disable(ihid_elan->vcc33);
+ if (ihid_elan->vcc33)
+ regulator_disable(ihid_elan->vcc33);
}
static int i2c_hid_of_elan_probe(struct i2c_client *client)
{
struct i2c_hid_of_elan *ihid_elan;
+ int ret;
+ u32 quirks = 0;
ihid_elan = devm_kzalloc(&client->dev, sizeof(*ihid_elan), GFP_KERNEL);
if (!ihid_elan)
@@ -85,28 +114,85 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client)
if (IS_ERR(ihid_elan->reset_gpio))
return PTR_ERR(ihid_elan->reset_gpio);
- ihid_elan->vccio = devm_regulator_get(&client->dev, "vccio");
- if (IS_ERR(ihid_elan->vccio))
- return PTR_ERR(ihid_elan->vccio);
+ ihid_elan->no_reset_on_power_off = of_property_read_bool(client->dev.of_node,
+ "no-reset-on-power-off");
- ihid_elan->vcc33 = devm_regulator_get(&client->dev, "vcc33");
- if (IS_ERR(ihid_elan->vcc33))
- return PTR_ERR(ihid_elan->vcc33);
+ ihid_elan->vccio = devm_regulator_get(&client->dev, "vccio");
+ if (IS_ERR(ihid_elan->vccio)) {
+ ret = PTR_ERR(ihid_elan->vccio);
+ goto err_deassert_reset;
+ }
ihid_elan->chip_data = device_get_match_data(&client->dev);
- return i2c_hid_core_probe(client, &ihid_elan->ops,
- ihid_elan->chip_data->hid_descriptor_address, 0);
+ if (ihid_elan->chip_data->main_supply_name) {
+ ihid_elan->vcc33 = devm_regulator_get(&client->dev,
+ ihid_elan->chip_data->main_supply_name);
+ if (IS_ERR(ihid_elan->vcc33)) {
+ ret = PTR_ERR(ihid_elan->vcc33);
+ goto err_deassert_reset;
+ }
+ }
+
+ if (ihid_elan->chip_data->power_after_backlight)
+ quirks = HID_QUIRK_POWER_ON_AFTER_BACKLIGHT;
+
+ ret = i2c_hid_core_probe(client, &ihid_elan->ops,
+ ihid_elan->chip_data->hid_descriptor_address,
+ quirks);
+ if (ret)
+ goto err_deassert_reset;
+
+ return 0;
+
+err_deassert_reset:
+ if (ihid_elan->no_reset_on_power_off)
+ gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0);
+
+ return ret;
}
static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = {
.post_power_delay_ms = 1,
- .post_gpio_reset_delay_ms = 300,
+ .post_gpio_reset_on_delay_ms = 300,
+ .hid_descriptor_address = 0x0001,
+ .main_supply_name = "vcc33",
+ .power_after_backlight = true,
+};
+
+static const struct elan_i2c_hid_chip_data elan_ekth6a12nay_chip_data = {
+ .post_power_delay_ms = 10,
+ .post_gpio_reset_on_delay_ms = 300,
+ .hid_descriptor_address = 0x0001,
+ .main_supply_name = "vcc33",
+ .power_after_backlight = true,
+};
+
+static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = {
+ .post_power_delay_ms = 1,
+ .post_gpio_reset_on_delay_ms = 200,
+ .post_gpio_reset_off_delay_ms = 65,
+ .hid_descriptor_address = 0x0001,
+ /*
+ * this touchscreen is tightly integrated with the panel and assumes
+ * that the relevant power rails (other than the IO rail) have already
+ * been turned on by the panel driver because we're a panel follower.
+ */
+ .main_supply_name = NULL,
+};
+
+static const struct elan_i2c_hid_chip_data ilitek_ili2901_chip_data = {
+ .post_power_delay_ms = 10,
+ .post_gpio_reset_on_delay_ms = 100,
.hid_descriptor_address = 0x0001,
+ .main_supply_name = "vcc33",
};
static const struct of_device_id elan_i2c_hid_of_match[] = {
{ .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data },
+ { .compatible = "elan,ekth6a12nay", .data = &elan_ekth6a12nay_chip_data },
+ { .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data },
+ { .compatible = "ilitek,ili2901", .data = &ilitek_ili2901_chip_data },
{ }
};
MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match);
diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c
index c4e1fa0273c8..57379b77e977 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of.c
@@ -87,6 +87,7 @@ static int i2c_hid_of_probe(struct i2c_client *client)
if (!ihid_of)
return -ENOMEM;
+ ihid_of->client = client;
ihid_of->ops.power_up = i2c_hid_of_power_up;
ihid_of->ops.power_down = i2c_hid_of_power_down;
@@ -143,9 +144,9 @@ MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
#endif
static const struct i2c_device_id i2c_hid_of_id_table[] = {
- { "hid", 0 },
- { "hid-over-i2c", 0 },
- { },
+ { "hid" },
+ { "hid-over-i2c" },
+ { }
};
MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table);
diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h
index 2c7b66d5caa0..1724a435c783 100644
--- a/drivers/hid/i2c-hid/i2c-hid.h
+++ b/drivers/hid/i2c-hid/i2c-hid.h
@@ -27,11 +27,13 @@ static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
* @power_up: do sequencing to power up the device.
* @power_down: do sequencing to power down the device.
* @shutdown_tail: called at the end of shutdown.
+ * @restore_sequence: hibernation restore sequence.
*/
struct i2chid_ops {
int (*power_up)(struct i2chid_ops *ops);
void (*power_down)(struct i2chid_ops *ops);
void (*shutdown_tail)(struct i2chid_ops *ops);
+ void (*restore_sequence)(struct i2chid_ops *ops);
};
int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
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/Makefile b/drivers/hid/intel-ish-hid/Makefile
index f0a82b1c7cb9..e1e062e4b542 100644
--- a/drivers/hid/intel-ish-hid/Makefile
+++ b/drivers/hid/intel-ish-hid/Makefile
@@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o
intel-ishtp-objs += ishtp/bus.o
intel-ishtp-objs += ishtp/dma-if.o
intel-ishtp-objs += ishtp/client-buffers.o
+intel-ishtp-objs += ishtp/loader.o
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
intel-ish-ipc-objs := ipc/ipc.o
@@ -23,4 +24,4 @@ intel-ishtp-hid-objs += ishtp-hid-client.o
obj-$(CONFIG_INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ishtp-loader.o
intel-ishtp-loader-objs += ishtp-fw-loader.o
-ccflags-y += -I $(srctree)/$(src)/ishtp
+ccflags-y += -I $(src)/ishtp
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
index e99f3a3c65e1..fa5d68c36313 100644
--- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
@@ -13,27 +13,32 @@
#include "hw-ish-regs.h"
#include "ishtp-dev.h"
-#define CHV_DEVICE_ID 0x22D8
-#define BXT_Ax_DEVICE_ID 0x0AA2
-#define BXT_Bx_DEVICE_ID 0x1AA2
-#define APL_Ax_DEVICE_ID 0x5AA2
-#define SPT_Ax_DEVICE_ID 0x9D35
-#define CNL_Ax_DEVICE_ID 0x9DFC
-#define GLK_Ax_DEVICE_ID 0x31A2
-#define CNL_H_DEVICE_ID 0xA37C
-#define ICL_MOBILE_DEVICE_ID 0x34FC
-#define SPT_H_DEVICE_ID 0xA135
-#define CML_LP_DEVICE_ID 0x02FC
-#define CMP_H_DEVICE_ID 0x06FC
-#define EHL_Ax_DEVICE_ID 0x4BB3
-#define TGL_LP_DEVICE_ID 0xA0FC
-#define TGL_H_DEVICE_ID 0x43FC
-#define ADL_S_DEVICE_ID 0x7AF8
-#define ADL_P_DEVICE_ID 0x51FC
-#define ADL_N_DEVICE_ID 0x54FC
-#define RPL_S_DEVICE_ID 0x7A78
-#define MTL_P_DEVICE_ID 0x7E45
-#define ARL_H_DEVICE_ID 0x7745
+#define PCI_DEVICE_ID_INTEL_ISH_CHV 0x22D8
+#define PCI_DEVICE_ID_INTEL_ISH_BXT_Ax 0x0AA2
+#define PCI_DEVICE_ID_INTEL_ISH_BXT_Bx 0x1AA2
+#define PCI_DEVICE_ID_INTEL_ISH_APL_Ax 0x5AA2
+#define PCI_DEVICE_ID_INTEL_ISH_SPT_Ax 0x9D35
+#define PCI_DEVICE_ID_INTEL_ISH_CNL_Ax 0x9DFC
+#define PCI_DEVICE_ID_INTEL_ISH_GLK_Ax 0x31A2
+#define PCI_DEVICE_ID_INTEL_ISH_CNL_H 0xA37C
+#define PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE 0x34FC
+#define PCI_DEVICE_ID_INTEL_ISH_SPT_H 0xA135
+#define PCI_DEVICE_ID_INTEL_ISH_CML_LP 0x02FC
+#define PCI_DEVICE_ID_INTEL_ISH_CMP_H 0x06FC
+#define PCI_DEVICE_ID_INTEL_ISH_EHL_Ax 0x4BB3
+#define PCI_DEVICE_ID_INTEL_ISH_TGL_LP 0xA0FC
+#define PCI_DEVICE_ID_INTEL_ISH_TGL_H 0x43FC
+#define PCI_DEVICE_ID_INTEL_ISH_ADL_S 0x7AF8
+#define PCI_DEVICE_ID_INTEL_ISH_ADL_P 0x51FC
+#define PCI_DEVICE_ID_INTEL_ISH_ADL_N 0x54FC
+#define PCI_DEVICE_ID_INTEL_ISH_RPL_S 0x7A78
+#define PCI_DEVICE_ID_INTEL_ISH_MTL_P 0x7E45
+#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 PCI_DEVICE_ID_INTEL_ISH_WCL 0x4D45
#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 a49c6affd7c4..abf9c9a31c39 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -78,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev)
bool interrupt_generated = true;
uint32_t pisr_val = 0;
- if (dev->pdev->device == CHV_DEVICE_ID) {
+ if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
interrupt_generated =
IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
@@ -117,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev)
*/
static void set_host_ready(struct ishtp_device *dev)
{
- if (dev->pdev->device == CHV_DEVICE_ID) {
+ if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
(dev->pdev->revision & REVISION_ID_SI_MASK) ==
REVISION_ID_CHT_Ax_SI)
@@ -481,6 +481,20 @@ out:
return ret;
}
+static void ish_send_reset_notify_ack(struct ishtp_device *dev)
+{
+ /* Read reset ID */
+ u32 reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
+
+ /*
+ * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
+ * RESET_NOTIFY_ACK - FW will be checking for it
+ */
+ ish_set_host_rdy(dev);
+ /* Send RESET_NOTIFY_ACK (with reset_id) */
+ ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, sizeof(u32));
+}
+
#define TIME_SLICE_FOR_FW_RDY_MS 100
#define TIME_SLICE_FOR_INPUT_RDY_MS 100
#define TIMEOUT_FOR_FW_RDY_MS 2000
@@ -496,11 +510,8 @@ out:
*/
static int ish_fw_reset_handler(struct ishtp_device *dev)
{
- uint32_t reset_id;
unsigned long flags;
-
- /* Read reset ID */
- reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
+ int ret;
/* Clear IPC output queue */
spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
@@ -510,26 +521,21 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/* ISHTP notification in IPC_RESET */
ishtp_reset_handler(dev);
- if (!ish_is_input_ready(dev))
- timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
- TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
-
+ ret = timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
+ TIME_SLICE_FOR_INPUT_RDY_MS,
+ TIMEOUT_FOR_INPUT_RDY_MS);
/* ISH FW is dead */
- if (!ish_is_input_ready(dev))
+ if (ret)
return -EPIPE;
- /*
- * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
- * RESET_NOTIFY_ACK - FW will be checking for it
- */
- ish_set_host_rdy(dev);
- /* Send RESET_NOTIFY_ACK (with reset_id) */
- ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id,
- sizeof(uint32_t));
+
+ /* Send clock sync at once after reset */
+ ishtp_dev->prev_sync = 0;
/* Wait for ISH FW'es ILUP and ISHTP_READY */
- timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
- TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
- if (!ishtp_fw_is_ready(dev)) {
+ ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
+ TIME_SLICE_FOR_FW_RDY_MS,
+ TIMEOUT_FOR_FW_RDY_MS);
+ if (ret) {
/* ISH FW is dead */
uint32_t ish_status;
@@ -546,11 +552,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/**
* fw_reset_work_fn() - FW reset worker function
- * @unused: not used
+ * @work: Work item
*
* Call ish_fw_reset_handler to complete FW reset
*/
-static void fw_reset_work_fn(struct work_struct *unused)
+static void fw_reset_work_fn(struct work_struct *work)
{
int rv;
@@ -558,11 +564,10 @@ static void fw_reset_work_fn(struct work_struct *unused)
if (!rv) {
/* ISH is ILUP & ISHTP-ready. Restart ISHTP */
msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS);
- ishtp_dev->recvd_hw_ready = 1;
- wake_up_interruptible(&ishtp_dev->wait_hw_ready);
/* ISHTP notification in IPC_RESET sequence completion */
- ishtp_reset_compl_handler(ishtp_dev);
+ if (!work_pending(work))
+ ishtp_reset_compl_handler(ishtp_dev);
} else
dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
rv);
@@ -576,15 +581,14 @@ static void fw_reset_work_fn(struct work_struct *unused)
*/
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));
}
/**
@@ -620,15 +624,14 @@ static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val)
break;
case MNG_RESET_NOTIFY:
- if (!ishtp_dev) {
- ishtp_dev = dev;
- }
- schedule_work(&fw_reset_work);
- break;
+ ish_send_reset_notify_ack(ishtp_dev);
+ fallthrough;
case MNG_RESET_NOTIFY_ACK:
dev->recvd_hw_ready = 1;
wake_up_interruptible(&dev->wait_hw_ready);
+ if (!work_pending(&fw_reset_work))
+ queue_work(dev->unbound_wq, &fw_reset_work);
break;
}
}
@@ -725,22 +728,28 @@ int ish_disable_dma(struct ishtp_device *dev)
* ish_wakeup() - wakeup ishfw from waiting-for-host state
* @dev: ishtp device pointer
*
- * Set the dma enable bit and send a void message to FW,
+ * Set the dma enable bit and send a IPC RESET message to FW,
* it wil wakeup FW from waiting-for-host state.
+ *
+ * Return: 0 for success else error code.
*/
-static void ish_wakeup(struct ishtp_device *dev)
+static int ish_wakeup(struct ishtp_device *dev)
{
+ int ret;
+
/* Set dma enable bit */
ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
/*
- * Send 0 IPC message so that ISH FW wakes up if it was already
+ * Send IPC RESET message so that ISH FW wakes up if it was already
* asleep.
*/
- ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
+ ret = ish_ipc_reset(dev);
/* Flush writes to doorbell and REMAP2 */
ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+ return ret;
}
/**
@@ -789,11 +798,11 @@ static int _ish_hw_reset(struct ishtp_device *dev)
pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
/* Now we can enable ISH DMA operation and wakeup ISHFW */
- ish_wakeup(dev);
-
- return 0;
+ return ish_wakeup(dev);
}
+#define RECVD_HW_READY_TIMEOUT (10 * HZ)
+
/**
* _ish_ipc_reset() - IPC reset
* @dev: ishtp device pointer
@@ -828,7 +837,8 @@ static int _ish_ipc_reset(struct ishtp_device *dev)
}
wait_event_interruptible_timeout(dev->wait_hw_ready,
- dev->recvd_hw_ready, 2 * HZ);
+ dev->recvd_hw_ready,
+ RECVD_HW_READY_TIMEOUT);
if (!dev->recvd_hw_ready) {
dev_err(dev->devc, "Timed out waiting for HW ready\n");
rv = -ENODEV;
@@ -852,21 +862,7 @@ int ish_hw_start(struct ishtp_device *dev)
set_host_ready(dev);
/* After that we can enable ISH DMA operation and wakeup ISHFW */
- ish_wakeup(dev);
-
- /* wait for FW-initiated reset flow */
- if (!dev->recvd_hw_ready)
- wait_event_interruptible_timeout(dev->wait_hw_ready,
- dev->recvd_hw_ready,
- 10 * HZ);
-
- if (!dev->recvd_hw_ready) {
- dev_err(dev->devc,
- "[ishtp-ish]: Timed out waiting for FW-initiated reset\n");
- return -ENODEV;
- }
-
- return 0;
+ return ish_wakeup(dev);
}
/**
@@ -909,11 +905,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
*/
static bool _dma_no_cache_snooping(struct ishtp_device *dev)
{
- return (dev->pdev->device == EHL_Ax_DEVICE_ID ||
- dev->pdev->device == TGL_LP_DEVICE_ID ||
- dev->pdev->device == TGL_H_DEVICE_ID ||
- dev->pdev->device == ADL_S_DEVICE_ID ||
- dev->pdev->device == ADL_P_DEVICE_ID);
+ return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax ||
+ dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP ||
+ dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H ||
+ dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S ||
+ dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P);
}
static const struct ishtp_hw_ops ish_hw_ops = {
@@ -928,6 +924,25 @@ static const struct ishtp_hw_ops ish_hw_ops = {
.dma_no_cache_snooping = _dma_no_cache_snooping
};
+static void ishtp_free_workqueue(void *wq)
+{
+ destroy_workqueue(wq);
+}
+
+static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev)
+{
+ struct workqueue_struct *wq;
+
+ wq = alloc_workqueue("ishtp_unbound_%d", WQ_UNBOUND, 0, dev->id);
+ if (!wq)
+ return NULL;
+
+ if (devm_add_action_or_reset(dev, ishtp_free_workqueue, wq))
+ return NULL;
+
+ return wq;
+}
+
/**
* ish_dev_init() -Initialize ISH devoce
* @pdev: PCI device
@@ -948,6 +963,11 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
if (!dev)
return NULL;
+ dev->unbound_wq = devm_ishtp_alloc_workqueue(&pdev->dev);
+ if (!dev->unbound_wq)
+ return NULL;
+
+ dev->devc = &pdev->dev;
ishtp_device_init(dev);
init_waitqueue_head(&dev->wait_hw_ready);
@@ -976,6 +996,7 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
list_add_tail(&tx_buf->link, &dev->wr_free_list);
}
+ ishtp_dev = dev;
ret = devm_work_autocancel(&pdev->dev, &fw_reset_work, fw_reset_work_fn);
if (ret) {
dev_err(dev->devc, "Failed to initialise FW reset work\n");
@@ -983,7 +1004,6 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
}
dev->ops = &ish_hw_ops;
- dev->devc = &pdev->dev;
dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr);
return dev;
}
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index 55cb25038e63..1612e8cb23f0 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -23,29 +23,60 @@
#include "ishtp-dev.h"
#include "hw-ish.h"
+enum ishtp_driver_data_index {
+ ISHTP_DRIVER_DATA_NONE,
+ ISHTP_DRIVER_DATA_LNL_M,
+ ISHTP_DRIVER_DATA_PTL,
+ ISHTP_DRIVER_DATA_WCL,
+};
+
+#define ISH_FW_GEN_LNL_M "lnlm"
+#define ISH_FW_GEN_PTL "ptl"
+#define ISH_FW_GEN_WCL "wcl"
+
+#define ISH_FIRMWARE_PATH(gen) "intel/ish/ish_" gen ".bin"
+#define ISH_FIRMWARE_PATH_ALL "intel/ish/ish_*.bin"
+
+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,
+ },
+ [ISHTP_DRIVER_DATA_WCL] = {
+ .fw_generation = ISH_FW_GEN_WCL,
+ },
+};
+
static const struct pci_device_id ish_pci_tbl[] = {
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MTL_P_DEVICE_ID)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_H_DEVICE_ID)},
- {0, }
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CHV)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Bx)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_APL_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_GLK_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_H)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_H)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CML_LP)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CMP_H)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_LP)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_H)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_S)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_P)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_N)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_RPL_S)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_MTL_P)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)},
+ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)},
+ {PCI_DEVICE_DATA(INTEL, ISH_LNL_M, ISHTP_DRIVER_DATA_LNL_M)},
+ {PCI_DEVICE_DATA(INTEL, ISH_PTL_H, ISHTP_DRIVER_DATA_PTL)},
+ {PCI_DEVICE_DATA(INTEL, ISH_PTL_P, ISHTP_DRIVER_DATA_PTL)},
+ {PCI_DEVICE_DATA(INTEL, ISH_WCL, ISHTP_DRIVER_DATA_WCL)},
+ {}
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
@@ -104,55 +135,25 @@ static int ish_init(struct ishtp_device *dev)
static const struct pci_device_id ish_invalid_pci_ids[] = {
/* Mehlow platform special pci ids */
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
- {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
+ {PCI_VDEVICE(INTEL, 0xA309)},
+ {PCI_VDEVICE(INTEL, 0xA30A)},
{}
};
static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
{
- return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID;
+ return !pm_suspend_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
{
- return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
-}
-
-static int enable_gpe(struct device *dev)
-{
-#ifdef CONFIG_ACPI
- acpi_status acpi_sts;
- struct acpi_device *adev;
- struct acpi_device_wakeup *wakeup;
-
- adev = ACPI_COMPANION(dev);
- if (!adev) {
- dev_err(dev, "get acpi handle failed\n");
- return -ENODEV;
- }
- wakeup = &adev->wakeup;
-
- acpi_sts = acpi_enable_gpe(wakeup->gpe_device, wakeup->gpe_number);
- if (ACPI_FAILURE(acpi_sts)) {
- dev_err(dev, "enable ose_gpe failed\n");
- return -EIO;
- }
+ struct ishtp_device *dev = pci_get_drvdata(pdev);
+ u32 fwsts = dev->ops->get_fw_status(dev);
- return 0;
-#else
- return -ENODEV;
-#endif
-}
+ if (dev->suspend_flag || !IPC_IS_ISH_ILUP(fwsts))
+ return false;
-static void enable_pme_wake(struct pci_dev *pdev)
-{
- if ((pci_pme_capable(pdev, PCI_D0) ||
- pci_pme_capable(pdev, PCI_D3hot) ||
- pci_pme_capable(pdev, PCI_D3cold)) && !enable_gpe(&pdev->dev)) {
- pci_pme_active(pdev, true);
- dev_dbg(&pdev->dev, "ish ipc driver pme wake enabled\n");
- }
+ return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
/**
@@ -201,6 +202,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
}
hw = to_ish_hw(ishtp);
ishtp->print_log = ish_event_tracer;
+ ishtp->driver_data = &ishtp_driver_data[ent->driver_data];
/* mapping IO device memory */
hw->mem_addr = pcim_iomap_table(pdev)[0];
@@ -208,6 +210,11 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
/* request and enable interrupt */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
+ if (ret < 0) {
+ dev_err(dev, "ISH: Failed to allocate IRQ vectors\n");
+ return ret;
+ }
+
if (!pdev->msi_enabled && !pdev->msix_enabled)
irq_flag = IRQF_SHARED;
@@ -224,8 +231,8 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
init_waitqueue_head(&ishtp->resume_wait);
/* Enable PME for EHL */
- if (pdev->device == EHL_Ax_DEVICE_ID)
- enable_pme_wake(pdev);
+ if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
+ device_init_wakeup(dev, true);
ret = ish_init(ishtp);
if (ret)
@@ -248,10 +255,20 @@ static void ish_remove(struct pci_dev *pdev)
ish_device_disable(ishtp_dev);
}
-static struct device __maybe_unused *ish_resume_device;
-/* 50ms to get resume response */
-#define WAIT_FOR_RESUME_ACK_MS 50
+/**
+ * ish_shutdown() - PCI driver shutdown callback
+ * @pdev: pci device
+ *
+ * This function sets up wakeup for S5
+ */
+static void ish_shutdown(struct pci_dev *pdev)
+{
+ if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
+ pci_prepare_to_sleep(pdev);
+}
+
+static struct device __maybe_unused *ish_resume_device;
/**
* ish_resume_handler() - Work function to complete resume
@@ -266,10 +283,8 @@ static void __maybe_unused ish_resume_handler(struct work_struct *work)
{
struct pci_dev *pdev = to_pci_dev(ish_resume_device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
- uint32_t fwsts = dev->ops->get_fw_status(dev);
- if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag
- && IPC_IS_ISH_ILUP(fwsts)) {
+ if (ish_should_leave_d0i3(pdev)) {
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(pdev->irq);
@@ -370,29 +385,85 @@ static int __maybe_unused ish_resume(struct device *device)
struct pci_dev *pdev = to_pci_dev(device);
struct ishtp_device *dev = pci_get_drvdata(pdev);
- /* add this to finish power flow for EHL */
- if (dev->pdev->device == EHL_Ax_DEVICE_ID) {
- pci_set_power_state(pdev, PCI_D0);
- enable_pme_wake(pdev);
- dev_dbg(dev->devc, "set power state to D0 for ehl\n");
- }
-
ish_resume_device = device;
dev->resume_flag = 1;
- schedule_work(&resume_work);
+ /* If ISH resume from D3, reset ishtp clients before return */
+ if (!ish_should_leave_d0i3(pdev))
+ ishtp_reset_handler(dev);
+
+ queue_work(dev->unbound_wq, &resume_work);
return 0;
}
-static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
+static int __maybe_unused ish_freeze(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+
+ return pci_save_state(pdev);
+}
+
+static const struct dev_pm_ops __maybe_unused ish_pm_ops = {
+ .suspend = pm_sleep_ptr(ish_suspend),
+ .resume = pm_sleep_ptr(ish_resume),
+ .freeze = pm_sleep_ptr(ish_freeze),
+ .restore = pm_sleep_ptr(ish_resume),
+ .poweroff = pm_sleep_ptr(ish_suspend),
+};
+
+static ssize_t base_version_show(struct device *cdev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ishtp_device *dev = dev_get_drvdata(cdev);
+
+ return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->base_ver.major,
+ dev->base_ver.minor, dev->base_ver.hotfix,
+ dev->base_ver.build);
+}
+static DEVICE_ATTR_RO(base_version);
+
+static ssize_t project_version_show(struct device *cdev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ishtp_device *dev = dev_get_drvdata(cdev);
+
+ return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->prj_ver.major,
+ dev->prj_ver.minor, dev->prj_ver.hotfix,
+ dev->prj_ver.build);
+}
+static DEVICE_ATTR_RO(project_version);
+
+static struct attribute *ish_firmware_attrs[] = {
+ &dev_attr_base_version.attr,
+ &dev_attr_project_version.attr,
+ NULL
+};
+
+static umode_t firmware_is_visible(struct kobject *kobj, struct attribute *attr,
+ int i)
+{
+ struct ishtp_device *dev = dev_get_drvdata(kobj_to_dev(kobj));
+
+ return dev->driver_data->fw_generation ? attr->mode : 0;
+}
+
+static const struct attribute_group ish_firmware_group = {
+ .name = "firmware",
+ .attrs = ish_firmware_attrs,
+ .is_visible = firmware_is_visible,
+};
+
+__ATTRIBUTE_GROUPS(ish_firmware);
static struct pci_driver ish_driver = {
.name = KBUILD_MODNAME,
.id_table = ish_pci_tbl,
.probe = ish_probe,
.remove = ish_remove,
+ .shutdown = ish_shutdown,
.driver.pm = &ish_pm_ops,
+ .dev_groups = ish_firmware_groups,
};
module_pci_driver(ish_driver);
@@ -404,3 +475,6 @@ MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE(ISH_FIRMWARE_PATH(ISH_FW_GEN_LNL_M));
+MODULE_FIRMWARE(ISH_FIRMWARE_PATH_ALL);
diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
index 16aa030af845..f4a671d6386c 100644
--- a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
+++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
@@ -635,7 +635,7 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
const struct firmware *fw,
const struct shim_fw_info fw_info)
{
- int rv;
+ int rv = 0;
void *dma_buf;
dma_addr_t dma_buf_phy;
u32 fragment_offset, fragment_size, payload_max_size;
@@ -793,7 +793,7 @@ static int load_fw_from_host(struct ishtp_cl_data *client_data)
if (rv < 0)
goto end_err_fw_release;
- /* Step 3: Start ISH main firmware exeuction */
+ /* Step 3: Start ISH main firmware execution */
rv = ish_fw_start(client_data);
if (rv < 0)
@@ -840,43 +840,22 @@ static void load_fw_from_host_handler(struct work_struct *work)
*
* Return: 0 for success, negative error code for failure
*/
-static int loader_init(struct ishtp_cl *loader_ishtp_cl, int reset)
+static int loader_init(struct ishtp_cl *loader_ishtp_cl, bool reset)
{
int rv;
- struct ishtp_fw_client *fw_client;
struct ishtp_cl_data *client_data =
ishtp_get_client_data(loader_ishtp_cl);
dev_dbg(cl_data_to_dev(client_data), "reset flag: %d\n", reset);
- rv = ishtp_cl_link(loader_ishtp_cl);
- if (rv < 0) {
- dev_err(cl_data_to_dev(client_data), "ishtp_cl_link failed\n");
- return rv;
- }
-
- /* Connect to firmware client */
- ishtp_set_tx_ring_size(loader_ishtp_cl, LOADER_CL_TX_RING_SIZE);
- ishtp_set_rx_ring_size(loader_ishtp_cl, LOADER_CL_RX_RING_SIZE);
-
- fw_client =
- ishtp_fw_cl_get_client(ishtp_get_ishtp_device(loader_ishtp_cl),
- &loader_ishtp_id_table[0].guid);
- if (!fw_client) {
- dev_err(cl_data_to_dev(client_data),
- "ISH client uuid not found\n");
- rv = -ENOENT;
- goto err_cl_unlink;
- }
-
- ishtp_cl_set_fw_client_id(loader_ishtp_cl,
- ishtp_get_fw_client_id(fw_client));
- ishtp_set_connection_state(loader_ishtp_cl, ISHTP_CL_CONNECTING);
-
- rv = ishtp_cl_connect(loader_ishtp_cl);
+ rv = ishtp_cl_establish_connection(loader_ishtp_cl,
+ &loader_ishtp_id_table[0].guid,
+ LOADER_CL_TX_RING_SIZE,
+ LOADER_CL_RX_RING_SIZE,
+ reset);
if (rv < 0) {
dev_err(cl_data_to_dev(client_data), "Client connect fail\n");
- goto err_cl_unlink;
+ goto err_cl_disconnect;
}
dev_dbg(cl_data_to_dev(client_data), "Client connected\n");
@@ -885,17 +864,14 @@ static int loader_init(struct ishtp_cl *loader_ishtp_cl, int reset)
return 0;
-err_cl_unlink:
- ishtp_cl_unlink(loader_ishtp_cl);
+err_cl_disconnect:
+ ishtp_cl_destroy_connection(loader_ishtp_cl, reset);
return rv;
}
static void loader_deinit(struct ishtp_cl *loader_ishtp_cl)
{
- ishtp_set_connection_state(loader_ishtp_cl, ISHTP_CL_DISCONNECTING);
- ishtp_cl_disconnect(loader_ishtp_cl);
- ishtp_cl_unlink(loader_ishtp_cl);
- ishtp_cl_flush_queues(loader_ishtp_cl);
+ ishtp_cl_destroy_connection(loader_ishtp_cl, false);
/* Disband and free all Tx and Rx client-level rings */
ishtp_cl_free(loader_ishtp_cl);
@@ -914,19 +890,7 @@ static void reset_handler(struct work_struct *work)
loader_ishtp_cl = client_data->loader_ishtp_cl;
cl_device = client_data->cl_device;
- /* Unlink, flush queues & start again */
- ishtp_cl_unlink(loader_ishtp_cl);
- ishtp_cl_flush_queues(loader_ishtp_cl);
- ishtp_cl_free(loader_ishtp_cl);
-
- loader_ishtp_cl = ishtp_cl_allocate(cl_device);
- if (!loader_ishtp_cl)
- return;
-
- ishtp_set_drvdata(cl_device, loader_ishtp_cl);
- ishtp_set_client_data(loader_ishtp_cl, client_data);
- client_data->loader_ishtp_cl = loader_ishtp_cl;
- client_data->cl_device = cl_device;
+ ishtp_cl_destroy_connection(loader_ishtp_cl, true);
rv = loader_init(loader_ishtp_cl, 1);
if (rv < 0) {
@@ -974,7 +938,7 @@ static int loader_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
INIT_WORK(&client_data->work_fw_load,
load_fw_from_host_handler);
- rv = loader_init(loader_ishtp_cl, 0);
+ rv = loader_init(loader_ishtp_cl, false);
if (rv < 0) {
ishtp_cl_free(loader_ishtp_cl);
return rv;
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
index e3d70c5460e9..f37b3bc2bb7d 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
@@ -70,10 +70,10 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
unsigned char *payload;
struct device_info *dev_info;
int i, j;
- size_t payload_len, total_len, cur_pos, raw_len;
+ size_t payload_len, total_len, cur_pos, raw_len, msg_len;
int report_type;
struct report_list *reports_list;
- char *reports;
+ struct report *report;
size_t report_len;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int curr_hid_dev = client_data->cur_hid_dev;
@@ -280,14 +280,13 @@ do_get_report:
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
report_type = HID_INPUT_REPORT;
reports_list = (struct report_list *)payload;
- reports = (char *)reports_list->reports;
+ report = reports_list->reports;
for (j = 0; j < reports_list->num_of_reports; j++) {
- recv_msg = (struct hostif_msg *)(reports +
- sizeof(uint16_t));
- report_len = *(uint16_t *)reports;
- payload = reports + sizeof(uint16_t) +
- sizeof(struct hostif_msg_hdr);
+ recv_msg = container_of(&report->msg,
+ struct hostif_msg, hdr);
+ report_len = report->size;
+ payload = recv_msg->payload;
payload_len = report_len -
sizeof(struct hostif_msg_hdr);
@@ -304,7 +303,7 @@ do_get_report:
0);
}
- reports += sizeof(uint16_t) + report_len;
+ report += sizeof(*report) + payload_len;
}
break;
default:
@@ -316,12 +315,12 @@ do_get_report:
}
- if (!cur_pos && cur_pos + payload_len +
- sizeof(struct hostif_msg) < total_len)
+ msg_len = payload_len + sizeof(struct hostif_msg);
+ if (!cur_pos && cur_pos + msg_len < total_len)
++client_data->multi_packet_cnt;
- cur_pos += payload_len + sizeof(struct hostif_msg);
- payload += payload_len + sizeof(struct hostif_msg);
+ cur_pos += msg_len;
+ payload += msg_len;
} while (cur_pos < total_len);
}
@@ -639,47 +638,26 @@ static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl,
*
* Return: 0 on success, non zero on error
*/
-static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
+static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, bool reset)
{
- struct ishtp_device *dev;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
- struct ishtp_fw_client *fw_client;
int i;
int rv;
dev_dbg(cl_data_to_dev(client_data), "%s\n", __func__);
hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset);
- rv = ishtp_cl_link(hid_ishtp_cl);
- if (rv) {
- dev_err(cl_data_to_dev(client_data),
- "ishtp_cl_link failed\n");
- return -ENOMEM;
- }
-
client_data->init_done = 0;
- dev = ishtp_get_ishtp_device(hid_ishtp_cl);
-
- /* Connect to FW client */
- ishtp_set_tx_ring_size(hid_ishtp_cl, HID_CL_TX_RING_SIZE);
- ishtp_set_rx_ring_size(hid_ishtp_cl, HID_CL_RX_RING_SIZE);
-
- fw_client = ishtp_fw_cl_get_client(dev, &hid_ishtp_id_table[0].guid);
- if (!fw_client) {
- dev_err(cl_data_to_dev(client_data),
- "ish client uuid not found\n");
- return -ENOENT;
- }
- ishtp_cl_set_fw_client_id(hid_ishtp_cl,
- ishtp_get_fw_client_id(fw_client));
- ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_CONNECTING);
-
- rv = ishtp_cl_connect(hid_ishtp_cl);
+ rv = ishtp_cl_establish_connection(hid_ishtp_cl,
+ &hid_ishtp_id_table[0].guid,
+ HID_CL_TX_RING_SIZE,
+ HID_CL_RX_RING_SIZE,
+ reset);
if (rv) {
dev_err(cl_data_to_dev(client_data),
"client connect fail\n");
- goto err_cl_unlink;
+ goto err_cl_disconnect;
}
hid_ishtp_trace(client_data, "%s client connected\n", __func__);
@@ -723,10 +701,7 @@ static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
return 0;
err_cl_disconnect:
- ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING);
- ishtp_cl_disconnect(hid_ishtp_cl);
-err_cl_unlink:
- ishtp_cl_unlink(hid_ishtp_cl);
+ ishtp_cl_destroy_connection(hid_ishtp_cl, reset);
return rv;
}
@@ -738,8 +713,7 @@ err_cl_unlink:
*/
static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl)
{
- ishtp_cl_unlink(hid_ishtp_cl);
- ishtp_cl_flush_queues(hid_ishtp_cl);
+ ishtp_cl_destroy_connection(hid_ishtp_cl, false);
/* disband and free all Tx and Rx client-level rings */
ishtp_cl_free(hid_ishtp_cl);
@@ -749,33 +723,23 @@ static void hid_ishtp_cl_reset_handler(struct work_struct *work)
{
struct ishtp_cl_data *client_data;
struct ishtp_cl *hid_ishtp_cl;
- struct ishtp_cl_device *cl_device;
int retry;
int rv;
client_data = container_of(work, struct ishtp_cl_data, work);
hid_ishtp_cl = client_data->hid_ishtp_cl;
- cl_device = client_data->cl_device;
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
dev_dbg(ishtp_device(client_data->cl_device), "%s\n", __func__);
- hid_ishtp_cl_deinit(hid_ishtp_cl);
-
- hid_ishtp_cl = ishtp_cl_allocate(cl_device);
- if (!hid_ishtp_cl)
- return;
-
- ishtp_set_drvdata(cl_device, hid_ishtp_cl);
- ishtp_set_client_data(hid_ishtp_cl, client_data);
- client_data->hid_ishtp_cl = hid_ishtp_cl;
+ ishtp_cl_destroy_connection(hid_ishtp_cl, true);
client_data->num_hid_devices = 0;
for (retry = 0; retry < 3; ++retry) {
- rv = hid_ishtp_cl_init(hid_ishtp_cl, 1);
+ rv = hid_ishtp_cl_init(hid_ishtp_cl, true);
if (!rv)
break;
dev_err(cl_data_to_dev(client_data), "Retry reset init\n");
@@ -793,8 +757,18 @@ static void hid_ishtp_cl_resume_handler(struct work_struct *work)
struct ishtp_cl *hid_ishtp_cl = client_data->hid_ishtp_cl;
if (ishtp_wait_resume(ishtp_get_ishtp_device(hid_ishtp_cl))) {
- client_data->suspended = false;
- wake_up_interruptible(&client_data->ishtp_resume_wait);
+ /*
+ * Clear the suspended flag only when the connection is established.
+ * If the connection is not established, the suspended flag will be cleared after
+ * the connection is made.
+ */
+ if (ishtp_get_connection_state(hid_ishtp_cl) == ISHTP_CL_CONNECTED) {
+ client_data->suspended = false;
+ wake_up_interruptible(&client_data->ishtp_resume_wait);
+ }
+ } else {
+ hid_ishtp_trace(client_data, "hid client: wait for resume timed out");
+ dev_err(cl_data_to_dev(client_data), "wait for resume timed out");
}
}
@@ -841,7 +815,7 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
ishtp_hid_print_trace = ishtp_trace_callback(cl_device);
- rv = hid_ishtp_cl_init(hid_ishtp_cl, 0);
+ rv = hid_ishtp_cl_init(hid_ishtp_cl, false);
if (rv) {
ishtp_cl_free(hid_ishtp_cl);
return rv;
@@ -868,8 +842,6 @@ static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
hid_ishtp_cl);
dev_dbg(ishtp_device(cl_device), "%s\n", __func__);
- ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING);
- ishtp_cl_disconnect(hid_ishtp_cl);
ishtp_put_device(cl_device);
ishtp_hid_remove(client_data);
hid_ishtp_cl_deinit(hid_ishtp_cl);
@@ -895,7 +867,7 @@ static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
- schedule_work(&client_data->work);
+ queue_work(ishtp_get_workqueue(cl_device), &client_data->work);
return 0;
}
@@ -937,7 +909,7 @@ static int hid_ishtp_cl_resume(struct device *device)
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
- schedule_work(&client_data->resume_work);
+ queue_work(ishtp_get_workqueue(cl_device), &client_data->resume_work);
return 0;
}
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c
index 00c6f0ebf356..be2c62fc8251 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.c
@@ -261,12 +261,14 @@ err_hid_data:
*/
void ishtp_hid_remove(struct ishtp_cl_data *client_data)
{
+ void *data;
int i;
for (i = 0; i < client_data->num_hid_devices; ++i) {
if (client_data->hid_sensor_hubs[i]) {
- kfree(client_data->hid_sensor_hubs[i]->driver_data);
+ data = client_data->hid_sensor_hubs[i]->driver_data;
hid_destroy_device(client_data->hid_sensor_hubs[i]);
+ kfree(data);
client_data->hid_sensor_hubs[i] = NULL;
}
}
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.h b/drivers/hid/intel-ish-hid/ishtp-hid.h
index 35dddc5015b3..2bc19e8ba13e 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid.h
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.h
@@ -31,6 +31,7 @@ struct hostif_msg_hdr {
struct hostif_msg {
struct hostif_msg_hdr hdr;
+ uint8_t payload[];
} __packed;
struct hostif_msg_to_sensor {
@@ -52,15 +53,17 @@ struct ishtp_version {
uint16_t build;
} __packed;
+struct report {
+ uint16_t size;
+ struct hostif_msg_hdr msg;
+} __packed;
+
/* struct for ISHTP aggregated input data */
struct report_list {
uint16_t total_size;
uint8_t num_of_reports;
uint8_t flags;
- struct {
- uint16_t size_of_report;
- uint8_t report[1];
- } __packed reports[1];
+ struct report reports[];
} __packed;
/* HOSTIF commands */
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c
index 7fc738a22375..c6ce37244e49 100644
--- a/drivers/hid/intel-ish-hid/ishtp/bus.c
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.c
@@ -236,7 +236,7 @@ static int ishtp_cl_device_probe(struct device *dev)
*
* Return: 1 if dev & drv matches, 0 otherwise.
*/
-static int ishtp_cl_bus_match(struct device *dev, struct device_driver *drv)
+static int ishtp_cl_bus_match(struct device *dev, const struct device_driver *drv)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver = to_ishtp_cl_driver(drv);
@@ -378,7 +378,7 @@ static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = {
.restore = ishtp_cl_device_resume,
};
-static struct bus_type ishtp_cl_bus_type = {
+static const struct bus_type ishtp_cl_bus_type = {
.name = "ishtp",
.dev_groups = ishtp_cl_dev_groups,
.probe = ishtp_cl_device_probe,
@@ -541,7 +541,7 @@ void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device)
return;
if (device->event_cb)
- schedule_work(&device->event_work);
+ queue_work(device->ishtp_dev->unbound_wq, &device->event_work);
}
/**
@@ -722,6 +722,8 @@ void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags);
list_for_each_entry(cl, &ishtp_dev->cl_list, link) {
cl->state = ISHTP_CL_DISCONNECTED;
+ if (warm_reset && cl->device->reference_count)
+ continue;
/*
* Wake any pending process. The waiter would check dev->state
@@ -842,6 +844,7 @@ EXPORT_SYMBOL(ishtp_device);
/**
* ishtp_wait_resume() - Wait for IPC resume
+ * @dev: ishtp device
*
* Wait for IPC resume
*
@@ -849,9 +852,6 @@ EXPORT_SYMBOL(ishtp_device);
*/
bool ishtp_wait_resume(struct ishtp_device *dev)
{
- /* 50ms to get resume response */
- #define WAIT_FOR_RESUME_ACK_MS 50
-
/* Waiting to get resume response */
if (dev->resume_flag)
wait_event_interruptible_timeout(dev->resume_wait,
@@ -877,6 +877,22 @@ struct device *ishtp_get_pci_device(struct ishtp_cl_device *device)
EXPORT_SYMBOL(ishtp_get_pci_device);
/**
+ * ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device
+ * @cl_device: Pointer to the ISHTP client device structure
+ *
+ * Returns the workqueue_struct pointer (unbound_wq) associated with the given
+ * ISHTP client device. This workqueue is typically used for scheduling work
+ * related to the device.
+ *
+ * Return: Pointer to struct workqueue_struct.
+ */
+struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device)
+{
+ return cl_device->ishtp_dev->unbound_wq;
+}
+EXPORT_SYMBOL(ishtp_get_workqueue);
+
+/**
* ishtp_trace_callback() - Return trace callback
* @cl_device: ISH-TP client device instance
*
@@ -929,4 +945,5 @@ static void __exit ishtp_bus_unregister(void)
module_init(ishtp_bus_register);
module_exit(ishtp_bus_unregister);
+MODULE_DESCRIPTION("ISHTP bus driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h
index 5bb85c932e4c..53645ac89ee8 100644
--- a/drivers/hid/intel-ish-hid/ishtp/bus.h
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.h
@@ -46,7 +46,6 @@ struct ishtp_cl_device {
};
int ishtp_bus_new_client(struct ishtp_device *dev);
-void ishtp_remove_all_clients(struct ishtp_device *dev);
int ishtp_cl_device_bind(struct ishtp_cl *cl);
void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device);
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 2d92fc129ce4..40f510b1c072 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
@@ -49,7 +30,9 @@ static void ishtp_read_list_flush(struct ishtp_cl *cl)
list_for_each_entry_safe(rb, next, &cl->dev->read_list.list, list)
if (rb->cl && ishtp_cl_cmp_id(cl, rb->cl)) {
list_del(&rb->list);
- ishtp_io_rb_free(rb);
+ spin_lock(&cl->free_list_spinlock);
+ list_add_tail(&rb->list, &cl->free_rb_list.list);
+ spin_unlock(&cl->free_list_spinlock);
}
spin_unlock_irqrestore(&cl->dev->read_list_spinlock, flags);
}
@@ -339,16 +322,17 @@ static bool ishtp_cl_is_other_connecting(struct ishtp_cl *cl)
}
/**
- * ishtp_cl_connect() - Send connect request to firmware
+ * ishtp_cl_connect_to_fw() - Send connect request to firmware
* @cl: client device instance
*
- * Send a connect request for a client to firmware. If successful it will
- * RX and TX ring buffers
+ * Send a connect request to the firmware and wait for firmware response.
+ * If there is successful connection response from the firmware, change
+ * client state to ISHTP_CL_CONNECTED, and bind client to related
+ * firmware client_id.
*
- * Return: 0 if successful connect response from the firmware and able
- * to bind and allocate ring buffers or error code on failure
+ * Return: 0 for success and error code on failure
*/
-int ishtp_cl_connect(struct ishtp_cl *cl)
+static int ishtp_cl_connect_to_fw(struct ishtp_cl *cl)
{
struct ishtp_device *dev;
int rets;
@@ -358,8 +342,6 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
dev = cl->dev;
- dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state);
-
if (ishtp_cl_is_other_connecting(cl)) {
dev->print_log(dev, "%s() Busy\n", __func__);
return -EBUSY;
@@ -405,6 +387,38 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
return rets;
}
+ return rets;
+}
+
+/**
+ * ishtp_cl_connect() - Build connection with firmware
+ * @cl: client device instance
+ *
+ * Call ishtp_cl_connect_to_fw() to connect and bind to firmware. If successful,
+ * allocate RX and TX ring buffers, and start flow control with firmware to
+ * start communication.
+ *
+ * Return: 0 if there is successful connection to the firmware, allocate
+ * ring buffers.
+ */
+int ishtp_cl_connect(struct ishtp_cl *cl)
+{
+ struct ishtp_device *dev;
+ int rets;
+
+ if (!cl || !cl->dev)
+ return -ENODEV;
+
+ dev = cl->dev;
+
+ dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state);
+
+ rets = ishtp_cl_connect_to_fw(cl);
+ if (rets) {
+ dev->print_log(dev, "%s() Connect to fw failed\n", __func__);
+ return rets;
+ }
+
rets = ishtp_cl_alloc_rx_ring(cl);
if (rets) {
dev->print_log(dev, "%s() Alloc RX ring failed\n", __func__);
@@ -422,16 +436,148 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
return rets;
}
- /* Upon successful connection and allocation, emit flow-control */
+ /*
+ * Upon successful connection and allocation, start flow-control.
+ */
rets = ishtp_cl_read_start(cl);
- dev->print_log(dev, "%s() successful\n", __func__);
-
return rets;
}
EXPORT_SYMBOL(ishtp_cl_connect);
/**
+ * ishtp_cl_establish_connection() - Establish connection with the firmware
+ * @cl: client device instance
+ * @uuid: uuid of the client to search
+ * @tx_size: TX ring buffer size
+ * @rx_size: RX ring buffer size
+ * @reset: true if called for reset connection, otherwise for first connection
+ *
+ * This is a helper function for client driver to build connection with firmware.
+ * If it's first time connecting to the firmware, set reset to false, this
+ * function will link client to bus, find client id and send connect request to
+ * the firmware.
+ *
+ * If it's called for reset handler where client lost connection after
+ * firmware reset, set reset to true, this function will reinit client state and
+ * establish connection again. In this case, this function reuses current client
+ * structure and ring buffers to avoid allocation failure and memory fragments.
+ *
+ * Return: 0 for successful connection with the firmware,
+ * or error code on failure
+ */
+int ishtp_cl_establish_connection(struct ishtp_cl *cl, const guid_t *uuid,
+ int tx_size, int rx_size, bool reset)
+{
+ struct ishtp_device *dev;
+ struct ishtp_fw_client *fw_client;
+ int rets;
+
+ if (!cl || !cl->dev)
+ return -ENODEV;
+
+ dev = cl->dev;
+
+ ishtp_set_connection_state(cl, ISHTP_CL_INITIALIZING);
+
+ /* reinit ishtp_cl structure if call for reset */
+ if (reset) {
+ cl->host_client_id = 0;
+ cl->fw_client_id = 0;
+ cl->ishtp_flow_ctrl_creds = 0;
+ cl->out_flow_ctrl_creds = 0;
+
+ cl->last_tx_path = CL_TX_PATH_IPC;
+ cl->last_dma_acked = 1;
+ cl->last_dma_addr = NULL;
+ cl->last_ipc_acked = 1;
+
+ cl->sending = 0;
+ cl->err_send_msg = 0;
+ cl->err_send_fc = 0;
+
+ cl->send_msg_cnt_ipc = 0;
+ cl->send_msg_cnt_dma = 0;
+ cl->recv_msg_cnt_ipc = 0;
+ cl->recv_msg_cnt_dma = 0;
+ cl->recv_msg_num_frags = 0;
+ cl->ishtp_flow_ctrl_cnt = 0;
+ cl->out_flow_ctrl_cnt = 0;
+ }
+
+ /* link to bus */
+ rets = ishtp_cl_link(cl);
+ if (rets) {
+ dev->print_log(dev, "%s() ishtp_cl_link failed\n", __func__);
+ return rets;
+ }
+
+ /* find firmware client */
+ fw_client = ishtp_fw_cl_get_client(dev, uuid);
+ if (!fw_client) {
+ dev->print_log(dev,
+ "%s() ish client uuid not found\n", __func__);
+ return -ENOENT;
+ }
+
+ ishtp_set_tx_ring_size(cl, tx_size);
+ ishtp_set_rx_ring_size(cl, rx_size);
+
+ ishtp_cl_set_fw_client_id(cl, ishtp_get_fw_client_id(fw_client));
+
+ ishtp_set_connection_state(cl, ISHTP_CL_CONNECTING);
+
+ /*
+ * For reset case, not allocate tx/rx ring buffer which are already
+ * done in ishtp_cl_connect() during first connection.
+ */
+ if (reset) {
+ rets = ishtp_cl_connect_to_fw(cl);
+ if (!rets)
+ rets = ishtp_cl_read_start(cl);
+ else
+ dev->print_log(dev,
+ "%s() connect to fw failed\n", __func__);
+ } else {
+ rets = ishtp_cl_connect(cl);
+ }
+
+ return rets;
+}
+EXPORT_SYMBOL(ishtp_cl_establish_connection);
+
+/**
+ * ishtp_cl_destroy_connection() - Disconnect with the firmware
+ * @cl: client device instance
+ * @reset: true if called for firmware reset, false for normal disconnection
+ *
+ * This is a helper function for client driver to disconnect with firmware,
+ * unlink to bus and flush message queue.
+ */
+void ishtp_cl_destroy_connection(struct ishtp_cl *cl, bool reset)
+{
+ if (!cl)
+ return;
+
+ if (reset) {
+ /*
+ * For reset case, connection is already lost during fw reset.
+ * Just set state to DISCONNECTED is enough.
+ */
+ ishtp_set_connection_state(cl, ISHTP_CL_DISCONNECTED);
+ } else {
+ if (cl->state != ISHTP_CL_DISCONNECTED) {
+ ishtp_set_connection_state(cl, ISHTP_CL_DISCONNECTING);
+ ishtp_cl_disconnect(cl);
+ }
+ }
+
+ ishtp_cl_unlink(cl);
+ ishtp_cl_flush_queues(cl);
+}
+EXPORT_SYMBOL(ishtp_cl_destroy_connection);
+
+/**
* ishtp_cl_read_start() - Prepare to read client message
* @cl: client device instance
*
@@ -698,7 +844,7 @@ static void ipc_tx_send(void *prm)
/* Send ipc fragment */
ishtp_hdr.length = dev->mtu;
ishtp_hdr.msg_complete = 0;
- /* All fregments submitted to IPC queue with no callback */
+ /* All fragments submitted to IPC queue with no callback */
ishtp_write_message(dev, &ishtp_hdr, pmsg);
cl->tx_offs += dev->mtu;
rem = cl_msg->send_buf.size - cl->tx_offs;
@@ -1115,6 +1261,12 @@ void ishtp_set_connection_state(struct ishtp_cl *cl, int state)
}
EXPORT_SYMBOL(ishtp_set_connection_state);
+int ishtp_get_connection_state(struct ishtp_cl *cl)
+{
+ return cl->state;
+}
+EXPORT_SYMBOL(ishtp_get_connection_state);
+
void ishtp_cl_set_fw_client_id(struct ishtp_cl *cl, int fw_client_id)
{
cl->fw_client_id = fw_client_id;
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h
index fc62dd1495da..0efd49dd2530 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.h
+++ b/drivers/hid/intel-ish-hid/ishtp/client.h
@@ -109,7 +109,6 @@ struct ishtp_cl {
};
/* Client connection managenment internal functions */
-int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, guid_t *uuid);
int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id);
void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl);
void recv_ishtp_cl_msg(struct ishtp_device *dev,
@@ -121,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/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c
index 9c031a06e4c4..97c4fcd9e3c6 100644
--- a/drivers/hid/intel-ish-hid/ishtp/hbm.c
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c
@@ -13,6 +13,7 @@
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
+#include "loader.h"
/**
* ishtp_hbm_fw_cl_allocate() - Allocate FW clients
@@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
return;
}
+ /* Start firmware loading process if it has loader capability */
+ if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
+ queue_work(dev->unbound_wq, &dev->work_fw_loader);
+
dev->version.major_version = HBM_MAJOR_VERSION;
dev->version.minor_version = HBM_MINOR_VERSION;
if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
@@ -859,12 +864,26 @@ void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr)
dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) %
(RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE);
spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
- schedule_work(&dev->bh_hbm_work);
+ queue_work(dev->unbound_wq, &dev->bh_hbm_work);
eoi:
return;
}
/**
+ * ishtp_loader_recv_msg() - Receive a message from the ISHTP device
+ * @dev: The ISHTP device
+ * @buf: The buffer containing the message
+ */
+static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf)
+{
+ if (dev->fw_loader_rx_buf)
+ memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size);
+
+ dev->fw_loader_received = true;
+ wake_up_interruptible(&dev->wait_loader_recvd_msg);
+}
+
+/**
* recv_fixed_cl_msg() - Receive fixed client message
* @dev: ISHTP device instance
* @ishtp_hdr: received bus message
@@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev,
else
dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
msg_hdr->cmd);
+ } else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) {
+ ishtp_loader_recv_msg(dev, rd_msg_buf);
}
}
diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c
index 02a00cc2dd11..26bf9045a8de 100644
--- a/drivers/hid/intel-ish-hid/ishtp/init.c
+++ b/drivers/hid/intel-ish-hid/ishtp/init.c
@@ -5,42 +5,14 @@
* Copyright (c) 2003-2016, Intel Corporation.
*/
+#include <linux/devm-helpers.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.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";
- }
-}
+#include "loader.h"
/**
* ishtp_device_init() - ishtp device init
@@ -51,6 +23,8 @@ const char *ishtp_dev_state_str(int state)
*/
void ishtp_device_init(struct ishtp_device *dev)
{
+ int ret;
+
dev->dev_state = ISHTP_DEV_INITIALIZING;
INIT_LIST_HEAD(&dev->cl_list);
INIT_LIST_HEAD(&dev->device_list);
@@ -59,6 +33,7 @@ void ishtp_device_init(struct ishtp_device *dev)
spin_lock_init(&dev->rd_msg_spinlock);
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
+ init_waitqueue_head(&dev->wait_loader_recvd_msg);
spin_lock_init(&dev->read_list_spinlock);
spin_lock_init(&dev->device_lock);
spin_lock_init(&dev->device_list_lock);
@@ -76,6 +51,9 @@ void ishtp_device_init(struct ishtp_device *dev)
INIT_LIST_HEAD(&dev->read_list.list);
+ ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
+ if (ret)
+ dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
}
EXPORT_SYMBOL(ishtp_device_init);
diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
index 32142c7d9a04..4b0596eadf1c 100644
--- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
+++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
@@ -47,6 +47,9 @@
#define MAX_DMA_DELAY 20
+/* 300ms to get resume response */
+#define WAIT_FOR_RESUME_ACK_MS 300
+
/* ISHTP device states */
enum ishtp_dev_state {
ISHTP_DEV_INITIALIZING = 0,
@@ -57,7 +60,6 @@ enum ishtp_dev_state {
ISHTP_DEV_POWER_DOWN,
ISHTP_DEV_POWER_UP
};
-const char *ishtp_dev_state_str(int state);
struct ishtp_cl;
@@ -123,11 +125,37 @@ struct ishtp_hw_ops {
};
/**
+ * struct ishtp_driver_data - Driver-specific data for ISHTP devices
+ *
+ * This structure holds driver-specific data that can be associated with each
+ * ISHTP device instance. It allows for the storage of data that is unique to
+ * a particular driver or hardware variant.
+ *
+ * @fw_generation: The generation name associated with a specific hardware
+ * variant of the Intel Integrated Sensor Hub (ISH). This allows
+ * the driver to load the correct firmware based on the device's
+ * hardware variant. For example, "lnlm" for the Lunar Lake-M
+ * platform. The generation name must not exceed 8 characters
+ * in length.
+ */
+struct ishtp_driver_data {
+ char *fw_generation;
+};
+
+struct ish_version {
+ u16 major;
+ u16 minor;
+ u16 hotfix;
+ u16 build;
+};
+
+/**
* struct ishtp_device - ISHTP private device struct
*/
struct ishtp_device {
struct device *devc; /* pointer to lowest device */
struct pci_dev *pdev; /* PCI device to get device ids */
+ struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */
/* waitq for waiting for suspend response */
wait_queue_head_t suspend_wait;
@@ -147,6 +175,20 @@ struct ishtp_device {
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
+ /* Alloc a dedicated unbound workqueue for ishtp device */
+ struct workqueue_struct *unbound_wq;
+
+ /* work structure for scheduling firmware loading tasks */
+ struct work_struct work_fw_loader;
+ /* waitq for waiting for command response from the firmware loader */
+ wait_queue_head_t wait_loader_recvd_msg;
+ /* indicating whether a message from the firmware loader has been received */
+ bool fw_loader_received;
+ /* pointer to a buffer for receiving messages from the firmware loader */
+ void *fw_loader_rx_buf;
+ /* size of the buffer pointed to by fw_loader_rx_buf */
+ int fw_loader_rx_size;
+
/* ishtp device states */
enum ishtp_dev_state dev_state;
enum ishtp_hbm_state hbm_state;
@@ -206,12 +248,19 @@ struct ishtp_device {
/* Dump to trace buffers if enabled*/
ishtp_print_log print_log;
+ /* Base version of Intel's released firmware */
+ struct ish_version base_ver;
+ /* Vendor-customized project version */
+ struct ish_version prj_ver;
+
/* Debug stats */
unsigned int ipc_rx_cnt;
unsigned long long ipc_rx_bytes_cnt;
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-ish-hid/ishtp/loader.c b/drivers/hid/intel-ish-hid/ishtp/loader.c
new file mode 100644
index 000000000000..f34086b29cf0
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/loader.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ISHTP firmware loader function
+ *
+ * Copyright (c) 2024, Intel Corporation.
+ *
+ * This module implements the functionality to load the main ISH firmware from the host, starting
+ * with the Lunar Lake generation. It leverages a new method that enhances space optimization and
+ * flexibility by dividing the ISH firmware into a bootloader and main firmware.
+ *
+ * Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on
+ * flows.
+ *
+ * Additionally, address potential error scenarios to ensure graceful failure handling.
+ * - Firmware Image Not Found:
+ * Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will
+ * remain in a state awaiting firmware loading from the host, with no further action from
+ * the ISHTP driver.
+ * Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
+ *
+ * - DMA Buffer Allocation Failure:
+ * This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware
+ * will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and
+ * firmware without further actions.
+ * Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
+ *
+ * - Incorrect Firmware Image:
+ * Using an incorrect firmware image will initiate the firmware loading process but will
+ * eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by
+ * returning an error code. The ISHTP driver will stop attempting after three tries.
+ * Recovery: A platform reset is required to retry firmware loading from the host.
+ */
+
+#define dev_fmt(fmt) "ISH loader: " fmt
+
+#include <linux/cacheflush.h>
+#include <linux/container_of.h>
+#include <linux/crc32.h>
+#include <linux/dev_printk.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmi.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/gfp_types.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pfn.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "hbm.h"
+#include "loader.h"
+
+/**
+ * loader_write_message() - Write a message to the ISHTP device
+ * @dev: The ISHTP device
+ * @buf: The buffer containing the message
+ * @len: The length of the message
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int loader_write_message(struct ishtp_device *dev, void *buf, int len)
+{
+ struct ishtp_msg_hdr ishtp_hdr = {
+ .fw_addr = ISHTP_LOADER_CLIENT_ADDR,
+ .length = len,
+ .msg_complete = 1,
+ };
+
+ dev->fw_loader_received = false;
+
+ return ishtp_write_message(dev, &ishtp_hdr, buf);
+}
+
+/**
+ * loader_xfer_cmd() - Transfer a command to the ISHTP device
+ * @dev: The ISHTP device
+ * @req: The request buffer
+ * @req_len: The length of the request
+ * @resp: The response buffer
+ * @resp_len: The length of the response
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len,
+ void *resp, int resp_len)
+{
+ union loader_msg_header req_hdr;
+ union loader_msg_header resp_hdr;
+ struct device *devc = dev->devc;
+ int rv;
+
+ dev->fw_loader_rx_buf = resp;
+ dev->fw_loader_rx_size = resp_len;
+
+ rv = loader_write_message(dev, req, req_len);
+ req_hdr.val32 = le32_to_cpup(req);
+
+ if (rv < 0) {
+ dev_err(devc, "write cmd %u failed:%d\n", req_hdr.command, rv);
+ return rv;
+ }
+
+ /* Wait the ACK */
+ wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received,
+ ISHTP_LOADER_TIMEOUT);
+ resp_hdr.val32 = le32_to_cpup(resp);
+ dev->fw_loader_rx_size = 0;
+ dev->fw_loader_rx_buf = NULL;
+ if (!dev->fw_loader_received) {
+ dev_err(devc, "wait response of cmd %u timeout\n", req_hdr.command);
+ return -ETIMEDOUT;
+ }
+
+ if (!resp_hdr.is_response) {
+ dev_err(devc, "not a response for %u\n", req_hdr.command);
+ return -EBADMSG;
+ }
+
+ if (req_hdr.command != resp_hdr.command) {
+ dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr.command,
+ resp_hdr.command);
+ return -EBADMSG;
+ }
+
+ if (resp_hdr.status) {
+ dev_err(devc, "cmd %u failed %u\n", req_hdr.command, resp_hdr.status);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * release_dma_bufs() - Release the DMA buffer for transferring firmware fragments
+ * @dev: The ISHTP device
+ * @fragment: The ISHTP firmware fragment descriptor
+ * @dma_bufs: The array of DMA fragment buffers
+ * @fragment_size: The size of a single DMA fragment
+ */
+static void release_dma_bufs(struct ishtp_device *dev,
+ struct loader_xfer_dma_fragment *fragment,
+ void **dma_bufs, u32 fragment_size)
+{
+ dma_addr_t dma_addr;
+ int i;
+
+ for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
+ if (dma_bufs[i]) {
+ dma_addr = le64_to_cpu(fragment->fragment_tbl[i].ddr_adrs);
+ dma_free_coherent(dev->devc, fragment_size, dma_bufs[i], dma_addr);
+ dma_bufs[i] = NULL;
+ }
+ }
+}
+
+/**
+ * prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments
+ * @dev: The ISHTP device
+ * @ish_fw: The ISH firmware
+ * @fragment: The ISHTP firmware fragment descriptor
+ * @dma_bufs: The array of DMA fragment buffers
+ * @fragment_size: The size of a single DMA fragment
+ * @fragment_count: Number of fragments
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int prepare_dma_bufs(struct ishtp_device *dev,
+ const struct firmware *ish_fw,
+ struct loader_xfer_dma_fragment *fragment,
+ void **dma_bufs, u32 fragment_size, u32 fragment_count)
+{
+ dma_addr_t dma_addr;
+ u32 offset = 0;
+ u32 length;
+ int i;
+
+ for (i = 0; i < fragment_count && offset < ish_fw->size; i++) {
+ dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size, &dma_addr, GFP_KERNEL);
+ if (!dma_bufs[i])
+ return -ENOMEM;
+
+ fragment->fragment_tbl[i].ddr_adrs = cpu_to_le64(dma_addr);
+ length = clamp(ish_fw->size - offset, 0, fragment_size);
+ fragment->fragment_tbl[i].length = cpu_to_le32(length);
+ fragment->fragment_tbl[i].fw_off = cpu_to_le32(offset);
+ memcpy(dma_bufs[i], ish_fw->data + offset, length);
+ clflush_cache_range(dma_bufs[i], fragment_size);
+
+ offset += length;
+ }
+
+ return 0;
+}
+
+#define ISH_FW_FILE_VENDOR_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
+#define ISH_FW_FILE_VENDOR_SKU_FMT "intel/ish/ish_%s_%08x_%08x.bin"
+#define ISH_FW_FILE_VENDOR_NAME_FMT "intel/ish/ish_%s_%08x_%08x.bin"
+#define ISH_FW_FILE_VENDOR_FMT "intel/ish/ish_%s_%08x.bin"
+#define ISH_FW_FILE_DEFAULT_FMT "intel/ish/ish_%s.bin"
+
+#define ISH_FW_FILENAME_LEN_MAX 56
+
+#define ISH_CRC_INIT (~0u)
+#define ISH_CRC_XOROUT (~0u)
+
+static int _request_ish_firmware(const struct firmware **firmware_p,
+ const char *name, struct device *dev)
+{
+ int ret;
+
+ dev_dbg(dev, "Try to load firmware: %s\n", name);
+ ret = firmware_request_nowarn(firmware_p, name, dev);
+ if (!ret)
+ dev_info(dev, "load firmware: %s\n", name);
+
+ return ret;
+}
+
+/**
+ * request_ish_firmware() - Request and load the ISH firmware.
+ * @firmware_p: Pointer to the firmware image.
+ * @dev: Device for which firmware is being requested.
+ *
+ * This function attempts to load the Integrated Sensor Hub (ISH) firmware
+ * for the given device in the following order, prioritizing custom firmware
+ * with more precise matching patterns:
+ *
+ * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin
+ * ish_${fw_generation}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin
+ * ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32).bin
+ * ish_${fw_generation}_${SYS_VENDOR_CRC32}.bin
+ * ish_${fw_generation}.bin
+ *
+ * The driver will load the first matching firmware and skip the rest. If no
+ * matching firmware is found, it will proceed to the next pattern in the
+ * specified order. If all searches fail, the default Intel firmware, listed
+ * last in the order above, will be loaded.
+ *
+ * The firmware file name is constructed using CRC32 checksums of strings.
+ * This is done to create a valid file name that does not contain spaces
+ * or special characters which may be present in the original strings.
+ *
+ * The CRC-32 algorithm uses the following parameters:
+ * Poly: 0x04C11DB7
+ * Init: 0xFFFFFFFF
+ * RefIn: true
+ * RefOut: true
+ * XorOut: 0xFFFFFFFF
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int request_ish_firmware(const struct firmware **firmware_p,
+ struct device *dev)
+{
+ const char *gen, *sys_vendor, *product_name, *product_sku;
+ struct ishtp_device *ishtp = dev_get_drvdata(dev);
+ u32 vendor_crc, name_crc, sku_crc;
+ char filename[ISH_FW_FILENAME_LEN_MAX];
+ int ret;
+
+ gen = ishtp->driver_data->fw_generation;
+ sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR);
+ product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
+ product_sku = dmi_get_system_info(DMI_PRODUCT_SKU);
+
+ if (sys_vendor)
+ vendor_crc = crc32(ISH_CRC_INIT, sys_vendor, strlen(sys_vendor)) ^ ISH_CRC_XOROUT;
+ if (product_name)
+ name_crc = crc32(ISH_CRC_INIT, product_name, strlen(product_name)) ^ ISH_CRC_XOROUT;
+ if (product_sku)
+ sku_crc = crc32(ISH_CRC_INIT, product_sku, strlen(product_sku)) ^ ISH_CRC_XOROUT;
+
+ if (sys_vendor && product_name && product_sku) {
+ snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_SKU_FMT, gen,
+ vendor_crc, name_crc, sku_crc);
+ ret = _request_ish_firmware(firmware_p, filename, dev);
+ if (!ret)
+ return 0;
+ }
+
+ if (sys_vendor && product_sku) {
+ snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_SKU_FMT, gen, vendor_crc,
+ sku_crc);
+ ret = _request_ish_firmware(firmware_p, filename, dev);
+ if (!ret)
+ return 0;
+ }
+
+ if (sys_vendor && product_name) {
+ snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_FMT, gen, vendor_crc,
+ name_crc);
+ ret = _request_ish_firmware(firmware_p, filename, dev);
+ if (!ret)
+ return 0;
+ }
+
+ if (sys_vendor) {
+ snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_FMT, gen, vendor_crc);
+ ret = _request_ish_firmware(firmware_p, filename, dev);
+ if (!ret)
+ return 0;
+ }
+
+ snprintf(filename, sizeof(filename), ISH_FW_FILE_DEFAULT_FMT, gen);
+ return _request_ish_firmware(firmware_p, filename, dev);
+}
+
+static int copy_manifest(const struct firmware *fw, struct ish_global_manifest *manifest)
+{
+ u32 offset;
+
+ for (offset = 0; offset + sizeof(*manifest) < fw->size; offset += ISH_MANIFEST_ALIGNMENT) {
+ memcpy(manifest, fw->data + offset, sizeof(*manifest));
+
+ if (le32_to_cpu(manifest->sig_fourcc) == ISH_GLOBAL_SIG)
+ return 0;
+ }
+
+ return -1;
+}
+
+static void copy_ish_version(struct version_in_manifest *src, struct ish_version *dst)
+{
+ dst->major = le16_to_cpu(src->major);
+ dst->minor = le16_to_cpu(src->minor);
+ dst->hotfix = le16_to_cpu(src->hotfix);
+ dst->build = le16_to_cpu(src->build);
+}
+
+/**
+ * ishtp_loader_work() - Load the ISHTP firmware
+ * @work: The work structure
+ *
+ * The ISH Loader attempts to load firmware by sending a series of commands
+ * to the ISH device. If a command fails to be acknowledged by the ISH device,
+ * the loader will retry sending the command, up to a maximum of
+ * ISHTP_LOADER_RETRY_TIMES.
+ *
+ * After the maximum number of retries has been reached without success, the
+ * ISH bootloader will return an error status code and will no longer respond
+ * to the driver's commands. This behavior indicates that the ISH Loader has
+ * encountered a critical error during the firmware loading process.
+ *
+ * In such a case, where the ISH bootloader is unresponsive after all retries
+ * have been exhausted, a platform reset is required to restore communication
+ * with the ISH device and to recover from this error state.
+ */
+void ishtp_loader_work(struct work_struct *work)
+{
+ DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM);
+ struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader);
+ union loader_msg_header query_hdr = { .command = LOADER_CMD_XFER_QUERY, };
+ union loader_msg_header start_hdr = { .command = LOADER_CMD_START, };
+ union loader_msg_header fragment_hdr = { .command = LOADER_CMD_XFER_FRAGMENT, };
+ struct loader_xfer_query query = { .header = cpu_to_le32(query_hdr.val32), };
+ struct loader_start start = { .header = cpu_to_le32(start_hdr.val32), };
+ union loader_recv_message recv_msg;
+ struct ish_global_manifest manifest;
+ const struct firmware *ish_fw;
+ void *dma_bufs[FRAGMENT_MAX_NUM] = {};
+ u32 fragment_size;
+ u32 fragment_count;
+ int retry = ISHTP_LOADER_RETRY_TIMES;
+ int rv;
+
+ rv = request_ish_firmware(&ish_fw, dev->devc);
+ if (rv < 0) {
+ dev_err(dev->devc, "request ISH firmware failed:%d\n", rv);
+ return;
+ }
+
+ fragment->fragment.header = cpu_to_le32(fragment_hdr.val32);
+ fragment->fragment.xfer_mode = cpu_to_le32(LOADER_XFER_MODE_DMA);
+ fragment->fragment.is_last = cpu_to_le32(1);
+ fragment->fragment.size = cpu_to_le32(ish_fw->size);
+ /* Calculate the size of a single DMA fragment */
+ fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM));
+ /* Calculate the count of DMA fragments */
+ fragment_count = DIV_ROUND_UP(ish_fw->size, fragment_size);
+ fragment->fragment_cnt = cpu_to_le32(fragment_count);
+
+ rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size, fragment_count);
+ if (rv) {
+ dev_err(dev->devc, "prepare DMA buffer failed.\n");
+ goto out;
+ }
+
+ do {
+ query.image_size = cpu_to_le32(ish_fw->size);
+ rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data,
+ sizeof(struct loader_xfer_query_ack));
+ if (rv)
+ continue; /* try again if failed */
+
+ dev_dbg(dev->devc, "ISH Bootloader Version %u.%u.%u.%u\n",
+ recv_msg.query_ack.version_major,
+ recv_msg.query_ack.version_minor,
+ recv_msg.query_ack.version_hotfix,
+ recv_msg.query_ack.version_build);
+
+ rv = loader_xfer_cmd(dev, fragment,
+ struct_size(fragment, fragment_tbl, fragment_count),
+ recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack));
+ if (rv)
+ continue; /* try again if failed */
+
+ rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data,
+ sizeof(struct loader_start_ack));
+ if (rv)
+ continue; /* try again if failed */
+
+ dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
+ if (!copy_manifest(ish_fw, &manifest)) {
+ copy_ish_version(&manifest.base_ver, &dev->base_ver);
+ copy_ish_version(&manifest.prj_ver, &dev->prj_ver);
+ dev_info(dev->devc, "FW base version: %u.%u.%u.%u\n",
+ dev->base_ver.major, dev->base_ver.minor,
+ dev->base_ver.hotfix, dev->base_ver.build);
+ dev_info(dev->devc, "FW project version: %u.%u.%u.%u\n",
+ dev->prj_ver.major, dev->prj_ver.minor,
+ dev->prj_ver.hotfix, dev->prj_ver.build);
+ }
+ break;
+ } while (--retry);
+
+out:
+ release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
+ release_firmware(ish_fw);
+}
diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.h b/drivers/hid/intel-ish-hid/ishtp/loader.h
new file mode 100644
index 000000000000..4dda038b4947
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/loader.h
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * ISHTP firmware loader header
+ *
+ * Copyright (c) 2024, Intel Corporation.
+ */
+
+#ifndef _ISHTP_LOADER_H_
+#define _ISHTP_LOADER_H_
+
+#include <linux/bits.h>
+#include <linux/jiffies.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#include "ishtp-dev.h"
+
+struct work_struct;
+
+#define LOADER_MSG_SIZE \
+ (IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr))
+
+/*
+ * ISHTP firmware loader protocol definition
+ */
+#define LOADER_CMD_XFER_QUERY 0 /* SW -> FW */
+#define LOADER_CMD_XFER_FRAGMENT 1 /* SW -> FW */
+#define LOADER_CMD_START 2 /* SW -> FW */
+
+/* Only support DMA mode */
+#define LOADER_XFER_MODE_DMA BIT(0)
+
+/**
+ * union loader_msg_header - ISHTP firmware loader message header
+ * @command: Command type
+ * @is_response: Indicates if the message is a response
+ * @has_next: Indicates if there is a next message
+ * @reserved: Reserved for future use
+ * @status: Status of the message
+ * @val32: entire header as a 32-bit value
+ */
+union loader_msg_header {
+ struct {
+ __u32 command:7;
+ __u32 is_response:1;
+ __u32 has_next:1;
+ __u32 reserved:15;
+ __u32 status:8;
+ };
+ __u32 val32;
+};
+
+/**
+ * struct loader_xfer_query - ISHTP firmware loader transfer query packet
+ * @header: Header of the message
+ * @image_size: Size of the image
+ */
+struct loader_xfer_query {
+ __le32 header;
+ __le32 image_size;
+};
+
+/**
+ * struct loader_version - ISHTP firmware loader version
+ * @value: Value of the version
+ * @major: Major version
+ * @minor: Minor version
+ * @hotfix: Hotfix version
+ * @build: Build version
+ */
+struct loader_version {
+ union {
+ __le32 value;
+ struct {
+ __u8 major;
+ __u8 minor;
+ __u8 hotfix;
+ __u8 build;
+ };
+ };
+};
+
+/**
+ * struct loader_capability - ISHTP firmware loader capability
+ * @max_fw_image_size: Maximum firmware image size
+ * @support_mode: Support mode
+ * @reserved: Reserved for future use
+ * @platform: Platform
+ * @max_dma_buf_size: Maximum DMA buffer size, multiples of 4096
+ */
+struct loader_capability {
+ __le32 max_fw_image_size;
+ __le16 support_mode;
+ __u8 reserved;
+ __u8 platform;
+ __le32 max_dma_buf_size;
+};
+
+/**
+ * struct loader_xfer_query_ack - ISHTP firmware loader transfer query acknowledgment
+ * @header: Header of the message
+ * @version_major: ISH Major version
+ * @version_minor: ISH Minor version
+ * @version_hotfix: ISH Hotfix version
+ * @version_build: ISH Build version
+ * @protocol_version: Protocol version
+ * @loader_version: Loader version
+ * @capability: Loader capability
+ */
+struct loader_xfer_query_ack {
+ __le32 header;
+ __le16 version_major;
+ __le16 version_minor;
+ __le16 version_hotfix;
+ __le16 version_build;
+ __le32 protocol_version;
+ struct loader_version loader_version;
+ struct loader_capability capability;
+};
+
+/**
+ * struct loader_xfer_fragment - ISHTP firmware loader transfer fragment
+ * @header: Header of the message
+ * @xfer_mode: Transfer mode
+ * @offset: Offset
+ * @size: Size
+ * @is_last: Is last
+ */
+struct loader_xfer_fragment {
+ __le32 header;
+ __le32 xfer_mode;
+ __le32 offset;
+ __le32 size;
+ __le32 is_last;
+};
+
+/**
+ * struct loader_xfer_fragment_ack - ISHTP firmware loader transfer fragment acknowledgment
+ * @header: Header of the message
+ */
+struct loader_xfer_fragment_ack {
+ __le32 header;
+};
+
+/**
+ * struct fragment_dscrpt - ISHTP firmware loader fragment descriptor
+ * @ddr_adrs: The address in host DDR
+ * @fw_off: The offset of the fragment in the fw image
+ * @length: The length of the fragment
+ */
+struct fragment_dscrpt {
+ __le64 ddr_adrs;
+ __le32 fw_off;
+ __le32 length;
+};
+
+#define FRAGMENT_MAX_NUM \
+ ((LOADER_MSG_SIZE - sizeof(struct loader_xfer_dma_fragment)) / \
+ sizeof(struct fragment_dscrpt))
+
+/**
+ * struct loader_xfer_dma_fragment - ISHTP firmware loader transfer DMA fragment
+ * @fragment: Fragment
+ * @fragment_cnt: How many descriptors in the fragment_tbl
+ * @fragment_tbl: Fragment table
+ */
+struct loader_xfer_dma_fragment {
+ struct loader_xfer_fragment fragment;
+ __le32 fragment_cnt;
+ struct fragment_dscrpt fragment_tbl[] __counted_by(fragment_cnt);
+};
+
+/**
+ * struct loader_start - ISHTP firmware loader start
+ * @header: Header of the message
+ */
+struct loader_start {
+ __le32 header;
+};
+
+/**
+ * struct loader_start_ack - ISHTP firmware loader start acknowledgment
+ * @header: Header of the message
+ */
+struct loader_start_ack {
+ __le32 header;
+};
+
+union loader_recv_message {
+ __le32 header;
+ struct loader_xfer_query_ack query_ack;
+ struct loader_xfer_fragment_ack fragment_ack;
+ struct loader_start_ack start_ack;
+ __u8 raw_data[LOADER_MSG_SIZE];
+};
+
+/*
+ * ISHTP firmware loader internal use
+ */
+/* ISHTP firmware loader command timeout */
+#define ISHTP_LOADER_TIMEOUT msecs_to_jiffies(100)
+
+/* ISHTP firmware loader retry times */
+#define ISHTP_LOADER_RETRY_TIMES 3
+
+/**
+ * struct ish_firmware_variant - ISH firmware variant
+ * @device: PCI Device ID
+ * @filename: The firmware file name
+ */
+struct ish_firmware_variant {
+ unsigned short device;
+ const char *filename;
+};
+
+/*
+ * ISHTP firmware loader API for ISHTP hbm
+ */
+
+/* ISHTP capability bit for firmware loader */
+#define ISHTP_SUPPORT_CAP_LOADER BIT(4)
+
+/* Firmware loader address */
+#define ISHTP_LOADER_CLIENT_ADDR 16
+
+/**
+ * ishtp_loader_work - The work function to start the firmware loading process
+ * @work: The work structure
+ */
+void ishtp_loader_work(struct work_struct *work);
+
+/* ISH Manifest alignment in binary is 4KB aligned */
+#define ISH_MANIFEST_ALIGNMENT SZ_4K
+
+/* Signature for ISH global manifest */
+#define ISH_GLOBAL_SIG 0x47485349 /* FourCC 'I', 'S', 'H', 'G' */
+
+struct version_in_manifest {
+ __le16 major;
+ __le16 minor;
+ __le16 hotfix;
+ __le16 build;
+};
+
+/**
+ * struct ish_global_manifest - global manifest for ISH
+ * @sig_fourcc: Signature FourCC, should be 'I', 'S', 'H', 'G'.
+ * @len: Length of the manifest.
+ * @header_version: Version of the manifest header.
+ * @flags: Flags for additional information.
+ * @base_ver: Base version of Intel's released firmware.
+ * @reserved: Reserved space for future use.
+ * @prj_ver: Vendor-customized project version.
+ */
+struct ish_global_manifest {
+ __le32 sig_fourcc;
+ __le32 len;
+ __le32 header_version;
+ __le32 flags;
+ struct version_in_manifest base_ver;
+ __le32 reserved[13];
+ struct version_in_manifest prj_ver;
+};
+
+#endif /* _ISHTP_LOADER_H_ */
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..f1182253b5b7
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/Makefile
@@ -0,0 +1,23 @@
+# 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
+intel-thc-objs += intel-thc/intel-thc-wot.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..cfda66ee4895
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
@@ -0,0 +1,1044 @@
+/* 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 <linux/gpio/consumer.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-hw.h"
+#include "intel-thc-wot.h"
+
+#include "quicki2c-dev.h"
+#include "quicki2c-hid.h"
+#include "quicki2c-protocol.h"
+
+static struct quicki2c_ddata ptl_ddata = {
+ .max_detect_size = MAX_RX_DETECT_SIZE_PTL,
+ .max_interrupt_delay = MAX_RX_INTERRUPT_DELAY,
+};
+
+/* 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 Wake-on-Touch GPIO resource */
+static const struct acpi_gpio_params wake_gpio = { 0, 0, true };
+
+static const struct acpi_gpio_mapping quicki2c_gpios[] = {
+ { "wake-on-touch", &wake_gpio, 1 },
+ { }
+};
+
+/**
+ * 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 failure.
+ */
+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 structure
+ *
+ * This function gets all QuickI2C devices' ACPI resource.
+ *
+ * Return: 0 if success or error code on failure.
+ */
+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;
+ }
+
+ if (qcdev->ddata) {
+ qcdev->i2c_max_frame_size_enable = i2c_config.FSEN;
+ qcdev->i2c_int_delay_enable = i2c_config.INDE;
+
+ if (i2c_config.FSVL <= qcdev->ddata->max_detect_size)
+ qcdev->i2c_max_frame_size = i2c_config.FSVL;
+ else
+ qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size;
+
+ if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay)
+ qcdev->i2c_int_delay = i2c_config.INDV;
+ else
+ qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay;
+ }
+
+ return 0;
+}
+
+/**
+ * quicki2c_irq_quick_handler - The ISR of the QuickI2C driver
+ * @irq: The irq number
+ * @dev_id: Pointer to the quicki2c_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 structure
+ *
+ * This function is an error handler, called when fatal error happens.
+ * It try to reset touch device and re-configure THC to recovery
+ * communication between touch device and THC.
+ *
+ * Return: 0 if successful or error code on failure
+ */
+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_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
+ * @ddata: Point to quicki2c_ddata structure
+ *
+ * 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 failure.
+ */
+static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __iomem *mem_addr,
+ const struct quicki2c_ddata *ddata)
+{
+ 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;
+ qcdev->ddata = ddata;
+
+ init_waitqueue_head(&qcdev->reset_ack_wq);
+
+ /* THC hardware 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);
+
+ thc_wot_config(qcdev->thc_hw, &quicki2c_gpios[0]);
+
+ 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_quiesce(qcdev->thc_hw, true);
+ thc_interrupt_enable(qcdev->thc_hw, false);
+ thc_ltr_unconfig(qcdev->thc_hw);
+ thc_wot_unconfig(qcdev->thc_hw);
+
+ qcdev->state = QUICKI2C_DISABLED;
+}
+
+/**
+ * quicki2c_dma_adv_enable - Configure and enable DMA advanced features
+ * @qcdev: Pointer to the quicki2c_device structure
+ *
+ * If platform supports THC DMA advanced features, such as max input size
+ * control or interrupt delay, configures and enables them.
+ */
+static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
+{
+ /*
+ * If platform supports max input size control feature and touch device
+ * max input length <= THC detect capability, enable the feature with device
+ * max input length.
+ */
+ if (qcdev->i2c_max_frame_size_enable) {
+ if (qcdev->i2c_max_frame_size >=
+ le16_to_cpu(qcdev->dev_desc.max_input_len)) {
+ thc_i2c_set_rx_max_size(qcdev->thc_hw,
+ le16_to_cpu(qcdev->dev_desc.max_input_len));
+ } else {
+ dev_warn(qcdev->dev,
+ "Max frame size is smaller than hid max input length!");
+ thc_i2c_set_rx_max_size(qcdev->thc_hw,
+ qcdev->i2c_max_frame_size);
+ }
+ thc_i2c_rx_max_size_enable(qcdev->thc_hw, true);
+ }
+
+ /* If platform supports interrupt delay feature, enable it with given delay */
+ if (qcdev->i2c_int_delay_enable) {
+ thc_i2c_set_rx_int_delay(qcdev->thc_hw,
+ qcdev->i2c_int_delay * 10);
+ thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true);
+ }
+}
+
+/**
+ * quicki2c_dma_adv_disable - Disable DMA advanced features
+ * @qcdev: Pointer to the quicki2c device structure
+ *
+ * Disable all DMA advanced features if platform supports.
+ */
+static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev)
+{
+ if (qcdev->i2c_max_frame_size_enable)
+ thc_i2c_rx_max_size_enable(qcdev->thc_hw, false);
+
+ if (qcdev->i2c_int_delay_enable)
+ thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false);
+}
+
+/**
+ * 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 failure.
+ */
+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;
+ }
+
+ if (qcdev->ddata)
+ quicki2c_dma_adv_enable(qcdev);
+
+ return 0;
+}
+
+/**
+ * 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);
+
+ if (qcdev->ddata)
+ quicki2c_dma_adv_disable(qcdev);
+}
+
+/**
+ * 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 failure.
+ */
+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 failure.
+ */
+static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ const struct quicki2c_ddata *ddata = (const struct quicki2c_ddata *)id->driver_data;
+ 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, ddata);
+ 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_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: Point to 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: Point to 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_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT1, NULL) },
+ { PCI_DEVICE_DATA(INTEL, THC_LNL_DEVICE_ID_I2C_PORT2, NULL) },
+ { PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT1, &ptl_ddata) },
+ { PCI_DEVICE_DATA(INTEL, THC_PTL_H_DEVICE_ID_I2C_PORT2, &ptl_ddata) },
+ { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT1, &ptl_ddata) },
+ { PCI_DEVICE_DATA(INTEL, THC_PTL_U_DEVICE_ID_I2C_PORT2, &ptl_ddata) },
+ { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT1, &ptl_ddata) },
+ { PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_I2C_PORT2, &ptl_ddata) },
+ { }
+};
+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..2cb5471a8133
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-dev.h
@@ -0,0 +1,227 @@
+/* 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 PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT1 0xA848
+#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A
+#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348
+#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT2 0xE34A
+#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT1 0xE448
+#define PCI_DEVICE_ID_INTEL_THC_PTL_U_DEVICE_ID_I2C_PORT2 0xE44A
+#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT1 0x4D48
+#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_I2C_PORT2 0x4D4A
+
+/* 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
+
+/* PTL Max packet size detection capability is 255 Bytes */
+#define MAX_RX_DETECT_SIZE_PTL 255
+/* Max interrupt delay capability is 2.56ms */
+#define MAX_RX_INTERRUPT_DELAY 256
+
+/* Default interrupt delay is 1ms, suitable for most devices */
+#define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC)
+
+/*
+ * 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;
+ u8 reserved;
+} __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
+ * @FSEN: Maximum Frame Size Feature Enable Control
+ * @FSVL: Maximum Frame Size Value (unit in Bytes)
+ * @INDE: Interrupt Delay Feature Enable Control
+ * @INDV: Interrupt Delay Value (unit in 10 us)
+ *
+ * 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;
+
+ u64 FSEN;
+ u64 FSVL;
+ u64 INDE;
+ u64 INDV;
+ u8 reserved;
+};
+
+/**
+ * struct quicki2c_ddata - Driver specific data for quicki2c device
+ * @max_detect_size: Identify max packet size detect for rx
+ * @interrupt_delay: Identify max interrupt detect delay for rx
+ */
+struct quicki2c_ddata {
+ u32 max_detect_size;
+ u32 max_interrupt_delay;
+};
+
+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
+ * @ddata: Point to QuickI2C platform 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
+ * @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not
+ * @i2c_max_frame_size: Max RX frame size (unit in Bytes)
+ * @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not
+ * @i2c_int_delay: Interrupt detection delay value (unit in 10 us)
+ */
+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;
+ const struct quicki2c_ddata *ddata;
+ 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;
+
+ u32 i2c_max_frame_size_enable;
+ u32 i2c_max_frame_size;
+ u32 i2c_int_delay_enable;
+ u32 i2c_int_delay;
+};
+
+#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..834a537b6780
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quicki2c/quicki2c-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 "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_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..ad6bd59963b2
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
@@ -0,0 +1,1006 @@
+/* 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 <linux/gpio/consumer.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-hw.h"
+#include "intel-thc-wot.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,
+};
+
+struct quickspi_driver_data arl = {
+ .max_packet_size_value = MAX_PACKET_SIZE_VALUE_MTL,
+};
+
+/* 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);
+
+
+/* QuickSPI Wake-on-Touch GPIO resource */
+static const struct acpi_gpio_params wake_gpio = { 0, 0, true };
+
+static const struct acpi_gpio_mapping quickspi_gpios[] = {
+ { "wake-on-touch", &wake_gpio, 1 },
+ { }
+};
+
+/**
+ * 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_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);
+
+ thc_wot_config(qsdev->thc_hw, &quickspi_gpios[0]);
+
+ 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);
+ thc_wot_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_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), },
+ {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT1, &ptl), },
+ {PCI_DEVICE_DATA(INTEL, THC_WCL_DEVICE_ID_SPI_PORT2, &ptl), },
+ {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT1, &arl), },
+ {PCI_DEVICE_DATA(INTEL, THC_ARL_DEVICE_ID_SPI_PORT2, &arl), },
+ {}
+};
+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..c30e1a42eb09
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
@@ -0,0 +1,176 @@
+/* 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
+#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT1 0x4D49
+#define PCI_DEVICE_ID_INTEL_THC_WCL_DEVICE_ID_SPI_PORT2 0x4D4B
+#define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT1 0x7749
+#define PCI_DEVICE_ID_INTEL_THC_ARL_DEVICE_ID_SPI_PORT2 0x774B
+
+/* 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..82c72bfa2795
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-hid.c
@@ -0,0 +1,164 @@
+/* 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_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..16f780bc879b
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-protocol.c
@@ -0,0 +1,413 @@
+/* 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;
+
+ thc_int_trigger_type_select(qsdev->thc_hw, true);
+
+ 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..636a68306501
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c
@@ -0,0 +1,1719 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2024 Intel Corporation */
+
+#include <linux/bitfield.h>
+#include <linux/math.h>
+#include <linux/regmap.h>
+#include <linux/string_choices.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",
+ str_true_false(int_quiesce));
+ 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)
+{
+ static const 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,
+ };
+ static const 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, &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, &dev->i2c_subip_regs[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_subip_regs_restore, "INTEL_THC");
+
+/**
+ * thc_i2c_set_rx_max_size - Set I2C Rx transfer max input size
+ * @dev: The pointer of THC private device context
+ * @max_rx_size: Max input report packet size for input report
+ *
+ * Set @max_rx_size for I2C RxDMA max input size control feature.
+ *
+ * Return: 0 on success, other error codes on failure.
+ */
+int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size)
+{
+ u32 val;
+ int ret;
+
+ if (!dev)
+ return -EINVAL;
+
+ if (!max_rx_size)
+ return -EOPNOTSUPP;
+
+ ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val);
+ if (ret)
+ return ret;
+
+ val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE, max_rx_size);
+
+ ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val);
+ if (ret)
+ return ret;
+
+ dev->i2c_max_rx_size = max_rx_size;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_max_size, "INTEL_THC");
+
+/**
+ * thc_i2c_rx_max_size_enable - Enable I2C Rx max input size control
+ * @dev: The pointer of THC private device context
+ * @enable: Enable max input size control or not
+ *
+ * Enable or disable I2C RxDMA max input size control feature.
+ * Max input size control only can be enabled after max input size
+ * was set by thc_i2c_set_rx_max_size().
+ *
+ * Return: 0 on success, other error codes on failure.
+ */
+int thc_i2c_rx_max_size_enable(struct thc_device *dev, bool enable)
+{
+ u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE_EN;
+ u32 val = enable ? mask : 0;
+ int ret;
+
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->i2c_max_rx_size)
+ return -EOPNOTSUPP;
+
+ ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val);
+ if (ret)
+ return ret;
+
+ dev->i2c_max_rx_size_en = enable;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_max_size_enable, "INTEL_THC");
+
+/**
+ * thc_i2c_set_rx_int_delay - Set I2C Rx input interrupt delay value
+ * @dev: The pointer of THC private device context
+ * @delay_us: Interrupt delay value, unit is us
+ *
+ * Set @delay_us for I2C RxDMA input interrupt delay feature.
+ *
+ * Return: 0 on success, other error codes on failure.
+ */
+int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us)
+{
+ u32 val;
+ int ret;
+
+ if (!dev)
+ return -EINVAL;
+
+ if (!delay_us)
+ return -EOPNOTSUPP;
+
+ ret = regmap_read(dev->thc_regmap, THC_M_PRT_SW_SEQ_STS_OFFSET, &val);
+ if (ret)
+ return ret;
+
+ /* THC hardware counts at 10us unit */
+ val |= FIELD_PREP(THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL, DIV_ROUND_UP(delay_us, 10));
+
+ ret = regmap_write(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, val);
+ if (ret)
+ return ret;
+
+ dev->i2c_int_delay_us = delay_us;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_set_rx_int_delay, "INTEL_THC");
+
+/**
+ * thc_i2c_rx_int_delay_enable - Enable I2C Rx interrupt delay
+ * @dev: The pointer of THC private device context
+ * @enable: Enable interrupt delay or not
+ *
+ * Enable or disable I2C RxDMA input interrupt delay feature.
+ * Input interrupt delay can only be enabled after interrupt delay value
+ * was set by thc_i2c_set_rx_int_delay().
+ *
+ * Return: 0 on success, other error codes on failure.
+ */
+int thc_i2c_rx_int_delay_enable(struct thc_device *dev, bool enable)
+{
+ u32 mask = THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL_EN;
+ u32 val = enable ? mask : 0;
+ int ret;
+
+ if (!dev)
+ return -EINVAL;
+
+ if (!dev->i2c_int_delay_us)
+ return -EOPNOTSUPP;
+
+ ret = regmap_write_bits(dev->thc_regmap, THC_M_PRT_SPI_ICRRD_OPCODE_OFFSET, mask, val);
+ if (ret)
+ return ret;
+
+ dev->i2c_int_delay_en = enable;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(thc_i2c_rx_int_delay_enable, "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..0db435335e24
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.h
@@ -0,0 +1,133 @@
+/* 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"
+#include "intel-thc-wot.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
+ * @wot: THC Wake-on-Touch 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 sequence 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
+ * @i2c_max_rx_size: I2C Rx transfer max input size
+ * @i2c_int_delay_us: I2C input interrupt delay, unit is us
+ * @i2c_max_rx_size_en: Bool value that indicates I2C max input size control enabled or not
+ * @i2c_int_delay_en: Bool value that indicates I2C input interrupt delay enabled or not
+ */
+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;
+
+ struct thc_wot wot;
+
+ 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;
+
+ u32 i2c_max_rx_size;
+ u32 i2c_int_delay_us;
+ bool i2c_max_rx_size_en;
+ bool i2c_int_delay_en;
+};
+
+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);
+int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size);
+int thc_i2c_rx_max_size_enable(struct thc_device *dev, bool enable);
+int thc_i2c_set_rx_int_delay(struct thc_device *dev, u32 delay_us);
+int thc_i2c_rx_int_delay_enable(struct thc_device *dev, bool enable);
+
+#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..82b8854843e0
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
@@ -0,0 +1,1009 @@
+/* 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);
+
+ /*
+ * Max input size control feature is only available for RxDMA, it must keep disabled
+ * during SWDMA operation, and restore to previous state after SWDMA is done.
+ * Max input size variables in THC device context track hardware state, and keep change
+ * when feature state was changed, so those variables cannot be used to record feature
+ * state after state was changed during SWDMA operation. Here have to use a temp variable
+ * in DMA context to record feature state before SWDMA operation.
+ */
+ if (dev->i2c_max_rx_size_en) {
+ thc_i2c_rx_max_size_enable(dev, false);
+ dev->dma_ctx->rx_max_size_en = true;
+ }
+
+ /*
+ * Interrupt delay feature is in the same situation with max input size control feature,
+ * needs record feature state before SWDMA.
+ */
+ if (dev->i2c_int_delay_en) {
+ thc_i2c_rx_int_delay_enable(dev, false);
+ dev->dma_ctx->rx_int_delay_en = true;
+ }
+
+ 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;
+
+ /*
+ * Restore max input size control feature to previous state after SWDMA if it was
+ * enabled before SWDMA, and reset temp rx_max_size_en variable for next time.
+ */
+ if (dev->dma_ctx->rx_max_size_en) {
+ thc_i2c_rx_max_size_enable(dev, true);
+ dev->dma_ctx->rx_max_size_en = false;
+ }
+
+ /*
+ * Restore input interrupt delay feature to previous state after SWDMA if it was
+ * enabled before SWDMA, and reset temp rx_int_delay_en variable for next time.
+ */
+ if (dev->dma_ctx->rx_int_delay_en) {
+ thc_i2c_rx_int_delay_enable(dev, true);
+ dev->dma_ctx->rx_int_delay_en = false;
+ }
+
+ 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..78917400492c
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.h
@@ -0,0 +1,154 @@
+/* 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], hardware 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 scatter-gather 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;
+};
+
+/**
+ * struct thc_dma_context - THC DMA context
+ * @thc_dma_configuration: Array of all THC Channel configures
+ * @use_write_interrupts: Indicate TxDMA using interrupt or polling
+ * @rx_max_size_en: Temp flag to indicate THC I2C Rx max input size control feature
+ * enabled or not, only be used during SWDMA operation.
+ * @rx_int_delay_en: Temp flag to indicate THC I2C Rx interrupt delay feature
+ * enabled or not, only be used during SWDMA operation.
+ */
+struct thc_dma_context {
+ struct thc_dma_configuration dma_config[MAX_THC_DMA_CHANNEL];
+ u8 use_write_interrupts;
+
+ bool rx_max_size_en;
+ bool rx_int_delay_en;
+};
+
+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..413730f8e3f7
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-hw.h
@@ -0,0 +1,886 @@
+/* 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_SPI_ICRRD_OPCODE_I2C_MAX_SIZE GENMASK(15, 0)
+#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL GENMASK(23, 16)
+#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_INTERVAL_EN BIT(30)
+#define THC_M_PRT_SPI_ICRRD_OPCODE_I2C_MAX_SIZE_EN BIT(31)
+
+#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/intel-thc-hid/intel-thc/intel-thc-wot.c b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c
new file mode 100644
index 000000000000..1291b4ea2cd8
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Intel Corporation */
+
+#include <linux/acpi.h>
+#include <linux/pm_wakeirq.h>
+
+#include "intel-thc-dev.h"
+#include "intel-thc-wot.h"
+
+/**
+ * thc_wot_config - Query and configure wake-on-touch feature
+ * @thc_dev: Point to thc_device structure
+ * @gpio_map: Point to ACPI GPIO resource mapping structure
+ *
+ * THC ACPI device only provides _CRS with GpioInt() resources, doesn't contain
+ * _DSD to map this GPIO resource, so this function first registers wake GPIO
+ * mapping manually, then queries wake-on-touch GPIO resource from ACPI,
+ * if it exists and is wake-able, configure driver to enable it, otherwise,
+ * return immediately.
+ * This function will not return error as it doesn't impact major function.
+ */
+void thc_wot_config(struct thc_device *thc_dev, const struct acpi_gpio_mapping *gpio_map)
+{
+ struct acpi_device *adev;
+ struct thc_wot *wot;
+ int ret;
+
+ if (!thc_dev)
+ return;
+
+ adev = ACPI_COMPANION(thc_dev->dev);
+ if (!adev)
+ return;
+
+ wot = &thc_dev->wot;
+
+ ret = acpi_dev_add_driver_gpios(adev, gpio_map);
+ if (ret) {
+ dev_warn(thc_dev->dev, "Can't add wake GPIO resource, ret = %d\n", ret);
+ return;
+ }
+
+ wot->gpio_irq = acpi_dev_gpio_irq_wake_get_by(adev, "wake-on-touch", 0,
+ &wot->gpio_irq_wakeable);
+ if (wot->gpio_irq <= 0) {
+ dev_warn(thc_dev->dev, "Can't find wake GPIO resource\n");
+ return;
+ }
+
+ if (!wot->gpio_irq_wakeable) {
+ dev_warn(thc_dev->dev, "GPIO resource isn't wakeable\n");
+ return;
+ }
+
+ ret = device_init_wakeup(thc_dev->dev, true);
+ if (ret) {
+ dev_warn(thc_dev->dev, "Failed to init wake up.\n");
+ return;
+ }
+
+ ret = dev_pm_set_dedicated_wake_irq(thc_dev->dev, wot->gpio_irq);
+ if (ret) {
+ dev_warn(thc_dev->dev, "Failed to set wake up IRQ.\n");
+ device_init_wakeup(thc_dev->dev, false);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(thc_wot_config, "INTEL_THC");
+
+/**
+ * thc_wot_unconfig - Unconfig wake-on-touch feature
+ * @thc_dev: Point to thc_device structure
+ *
+ * Configure driver to disable wake-on-touch and release ACPI resource.
+ */
+void thc_wot_unconfig(struct thc_device *thc_dev)
+{
+ struct acpi_device *adev;
+
+ if (!thc_dev)
+ return;
+
+ adev = ACPI_COMPANION(thc_dev->dev);
+ if (!adev)
+ return;
+
+ if (thc_dev->wot.gpio_irq_wakeable)
+ device_init_wakeup(thc_dev->dev, false);
+
+ if (thc_dev->wot.gpio_irq > 0) {
+ dev_pm_clear_wake_irq(thc_dev->dev);
+ acpi_dev_remove_driver_gpios(adev);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(thc_wot_unconfig, "INTEL_THC");
diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h
new file mode 100644
index 000000000000..6c700621b242
--- /dev/null
+++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-wot.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Intel Corporation */
+
+#ifndef _INTEL_THC_WOT_H_
+#define _INTEL_THC_WOT_H_
+
+#include <linux/types.h>
+
+#include <linux/gpio/consumer.h>
+
+/**
+ * struct thc_wot - THC Wake-on-Touch data structure
+ * @gpio_irq : GPIO interrupt IRQ number for wake-on-touch
+ * @gpio_irq_wakeable : Indicate GPIO IRQ workable or not
+ */
+struct thc_wot {
+ int gpio_irq;
+ bool gpio_irq_wakeable;
+};
+
+struct thc_device;
+
+void thc_wot_config(struct thc_device *thc_dev, const struct acpi_gpio_mapping *gpio_map);
+void thc_wot_unconfig(struct thc_device *thc_dev);
+
+#endif /* _INTEL_THC_WOT_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/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
index 61e5814b0ad7..eae47e0d95ed 100644
--- a/drivers/hid/surface-hid/surface_hid.c
+++ b/drivers/hid/surface-hid/surface_hid.c
@@ -8,7 +8,7 @@
* Maximilian Luz <luzmaximilian@gmail.com>
*/
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
index a3e9cceddfac..6690c24f28f0 100644
--- a/drivers/hid/surface-hid/surface_hid_core.c
+++ b/drivers/hid/surface-hid/surface_hid_core.c
@@ -7,7 +7,7 @@
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
*/
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
index 4fbce201db6a..0be01b5e7425 100644
--- a/drivers/hid/surface-hid/surface_kbd.c
+++ b/drivers/hid/surface-hid/surface_kbd.c
@@ -7,7 +7,7 @@
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
*/
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <linux/hid.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -271,10 +271,9 @@ static int surface_kbd_probe(struct platform_device *pdev)
return surface_hid_device_add(shid);
}
-static int surface_kbd_remove(struct platform_device *pdev)
+static void surface_kbd_remove(struct platform_device *pdev)
{
surface_hid_device_destroy(platform_get_drvdata(pdev));
- return 0;
}
static const struct acpi_device_id surface_kbd_match[] = {
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
index 4588d2cd4ea4..21a70420151e 100644
--- a/drivers/hid/uhid.c
+++ b/drivers/hid/uhid.c
@@ -490,7 +490,7 @@ static int uhid_dev_create2(struct uhid_device *uhid,
const struct uhid_event *ev)
{
struct hid_device *hid;
- size_t rd_size, len;
+ size_t rd_size;
void *rd_data;
int ret;
@@ -514,13 +514,12 @@ static int uhid_dev_create2(struct uhid_device *uhid,
goto err_free;
}
- /* @hid is zero-initialized, strncpy() is correct, strlcpy() not */
- len = min(sizeof(hid->name), sizeof(ev->u.create2.name)) - 1;
- strncpy(hid->name, ev->u.create2.name, len);
- len = min(sizeof(hid->phys), sizeof(ev->u.create2.phys)) - 1;
- strncpy(hid->phys, ev->u.create2.phys, len);
- len = min(sizeof(hid->uniq), sizeof(ev->u.create2.uniq)) - 1;
- strncpy(hid->uniq, ev->u.create2.uniq, len);
+ BUILD_BUG_ON(sizeof(hid->name) != sizeof(ev->u.create2.name));
+ strscpy(hid->name, ev->u.create2.name, sizeof(hid->name));
+ BUILD_BUG_ON(sizeof(hid->phys) != sizeof(ev->u.create2.phys));
+ strscpy(hid->phys, ev->u.create2.phys, sizeof(hid->phys));
+ BUILD_BUG_ON(sizeof(hid->uniq) != sizeof(ev->u.create2.uniq));
+ strscpy(hid->uniq, ev->u.create2.uniq, sizeof(hid->uniq));
hid->ll_driver = &uhid_hid_driver;
hid->bus = ev->u.create2.bus;
@@ -804,7 +803,6 @@ static const struct file_operations uhid_fops = {
.read = uhid_char_read,
.write = uhid_char_write,
.poll = uhid_char_poll,
- .llseek = no_llseek,
};
static struct miscdevice uhid_misc = {
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 257dd73e37bf..aac0051a2cf6 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -19,8 +19,9 @@
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/mutex.h>
+#include <linux/property.h>
#include <linux/spinlock.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <asm/byteorder.h>
#include <linux/input.h>
#include <linux/wait.h>
@@ -34,6 +35,7 @@
#include <linux/hid-debug.h>
#include <linux/hidraw.h>
#include "usbhid.h"
+#include "hid-pidff.h"
/*
* Version Information
@@ -104,7 +106,7 @@ static int hid_start_in(struct hid_device *hid)
/* I/O retry timer routine */
static void hid_retry_timeout(struct timer_list *t)
{
- struct usbhid_device *usbhid = from_timer(usbhid, t, io_retry);
+ struct usbhid_device *usbhid = timer_container_of(usbhid, t, io_retry);
struct hid_device *hid = usbhid->hid;
dev_dbg(&usbhid->intf->dev, "retrying intr urb\n");
@@ -982,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);
@@ -1009,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);
@@ -1050,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;
@@ -1099,7 +1104,7 @@ static int usbhid_start(struct hid_device *hid)
interval = endpoint->bInterval;
- /* Some vendors give fullspeed interval on highspeed devides */
+ /* Some vendors give fullspeed interval on highspeed devices */
if (hid->quirks & HID_QUIRK_FULLSPEED_INTERVAL &&
dev->speed == USB_SPEED_HIGH) {
interval = fls(endpoint->bInterval*8);
@@ -1374,6 +1379,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
hid->hiddev_report_event = hiddev_report_event;
#endif
hid->dev.parent = &intf->dev;
+ device_set_node(&hid->dev, dev_fwnode(&intf->dev));
hid->bus = BUS_USB;
hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
hid->product = le16_to_cpu(dev->descriptor.idProduct);
@@ -1459,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);
@@ -1562,7 +1568,6 @@ static int hid_post_reset(struct usb_interface *intf)
return 0;
}
-#ifdef CONFIG_PM
static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
{
int status = 0;
@@ -1654,8 +1659,6 @@ static int hid_reset_resume(struct usb_interface *intf)
return status;
}
-#endif /* CONFIG_PM */
-
static const struct usb_device_id hid_usb_ids[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
.bInterfaceClass = USB_INTERFACE_CLASS_HID },
@@ -1668,11 +1671,9 @@ static struct usb_driver hid_driver = {
.name = "usbhid",
.probe = usbhid_probe,
.disconnect = usbhid_disconnect,
-#ifdef CONFIG_PM
- .suspend = hid_suspend,
- .resume = hid_resume,
- .reset_resume = hid_reset_resume,
-#endif
+ .suspend = pm_ptr(hid_suspend),
+ .resume = pm_ptr(hid_resume),
+ .reset_resume = pm_ptr(hid_reset_resume),
.pre_reset = hid_pre_reset,
.post_reset = hid_post_reset,
.id_table = hid_usb_ids,
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
index 3b4ee21cd811..95377c5f6335 100644
--- a/drivers/hid/usbhid/hid-pidff.c
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -3,27 +3,26 @@
* 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/hid.h>
#include <linux/input.h>
+#include <linux/minmax.h>
#include <linux/slab.h>
#include <linux/usb.h>
-#include <linux/hid.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
@@ -33,7 +32,7 @@
#define PID_DEVICE_CONTROL 6
#define PID_CREATE_NEW_EFFECT 7
-#define PID_REQUIRED_REPORTS 7
+#define PID_REQUIRED_REPORTS 8
#define PID_SET_ENVELOPE 8
#define PID_SET_CONDITION 9
@@ -44,12 +43,20 @@ 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_AXES_ENABLE 0x55
+#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,37 +140,80 @@ 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 };
+#define PID_DIRECTION_NORTH 0x0000
+#define PID_DIRECTION_EAST 0x4000
+#define PID_DIRECTION_SOUTH 0x8000
+#define PID_DIRECTION_WEST 0xc000
+
+#define PIDFF_FIXED_WHEEL_DIRECTION PID_DIRECTION_EAST
+
+/* AXES_ENABLE and DIRECTION axes */
+enum pid_axes {
+ PID_AXIS_X,
+ PID_AXIS_Y,
+ PID_AXIS_Z,
+ PID_AXIS_RX,
+ PID_AXIS_RY,
+ PID_AXIS_RZ,
+ PID_AXIS_SLIDER,
+ PID_AXIS_DIAL,
+ PID_AXIS_WHEEL,
+ PID_AXES_COUNT,
+};
+static const u8 pidff_direction_axis[] = {
+ HID_USAGE & HID_GD_X,
+ HID_USAGE & HID_GD_Y,
+ HID_USAGE & HID_GD_Z,
+ HID_USAGE & HID_GD_RX,
+ HID_USAGE & HID_GD_RY,
+ HID_USAGE & HID_GD_RZ,
+ HID_USAGE & HID_GD_SLIDER,
+ HID_USAGE & HID_GD_DIAL,
+ HID_USAGE & HID_GD_WHEEL,
+};
+
struct pidff_usage {
struct hid_field *field;
s32 *value;
};
+struct pidff_effect {
+ int pid_id;
+ int is_infinite;
+ unsigned int loop_count;
+};
+
struct pidff_device {
struct hid_device *hid;
- struct hid_report *reports[sizeof(pidff_reports)];
+ struct hid_report *reports[ARRAY_SIZE(pidff_reports)];
- struct pidff_usage set_effect[sizeof(pidff_set_effect)];
- struct pidff_usage set_envelope[sizeof(pidff_set_envelope)];
- struct pidff_usage set_condition[sizeof(pidff_set_condition)];
- struct pidff_usage set_periodic[sizeof(pidff_set_periodic)];
- struct pidff_usage set_constant[sizeof(pidff_set_constant)];
- struct pidff_usage set_ramp[sizeof(pidff_set_ramp)];
+ struct pidff_usage set_effect[ARRAY_SIZE(pidff_set_effect)];
+ struct pidff_usage set_envelope[ARRAY_SIZE(pidff_set_envelope)];
+ struct pidff_usage set_condition[ARRAY_SIZE(pidff_set_condition)];
+ struct pidff_usage set_periodic[ARRAY_SIZE(pidff_set_periodic)];
+ struct pidff_usage set_constant[ARRAY_SIZE(pidff_set_constant)];
+ struct pidff_usage set_ramp[ARRAY_SIZE(pidff_set_ramp)];
- struct pidff_usage device_gain[sizeof(pidff_device_gain)];
- struct pidff_usage block_load[sizeof(pidff_block_load)];
- struct pidff_usage pool[sizeof(pidff_pool)];
- struct pidff_usage effect_operation[sizeof(pidff_effect_operation)];
- struct pidff_usage block_free[sizeof(pidff_block_free)];
+ struct pidff_usage device_gain[ARRAY_SIZE(pidff_device_gain)];
+ struct pidff_usage block_load[ARRAY_SIZE(pidff_block_load)];
+ struct pidff_usage pool[ARRAY_SIZE(pidff_pool)];
+ struct pidff_usage effect_operation[ARRAY_SIZE(pidff_effect_operation)];
+ struct pidff_usage block_free[ARRAY_SIZE(pidff_block_free)];
- /* Special field is a field that is not composed of
- usage<->value pairs that pidff_usage values are */
+ struct pidff_effect effect[PID_EFFECTS_MAX];
+
+ /*
+ * 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;
@@ -168,6 +221,7 @@ struct pidff_device {
/* Special fields in set_effect */
struct hid_field *set_effect_type;
struct hid_field *effect_direction;
+ struct hid_field *axes_enable;
/* Special field in device_control */
struct hid_field *device_control;
@@ -178,36 +232,136 @@ struct pidff_device {
/* Special field in effect_operation */
struct hid_field *effect_operation_status;
- int control_id[sizeof(pidff_device_control)];
- int type_id[sizeof(pidff_effect_types)];
- int status_id[sizeof(pidff_block_load_status)];
- int operation_id[sizeof(pidff_effect_operation_status)];
+ int control_id[ARRAY_SIZE(pidff_device_control)];
+ int type_id[ARRAY_SIZE(pidff_effect_types)];
+ int status_id[ARRAY_SIZE(pidff_block_load_status)];
+ int operation_id[ARRAY_SIZE(pidff_effect_operation_status)];
+ int direction_axis_id[ARRAY_SIZE(pidff_direction_axis)];
- int pid_id[PID_EFFECTS_MAX];
+ u32 quirks;
+ u8 effect_count;
+ u8 axis_count;
};
+static int pidff_is_effect_conditional(struct ff_effect *effect)
+{
+ return effect->type == FF_SPRING ||
+ effect->type == FF_DAMPER ||
+ effect->type == FF_INERTIA ||
+ effect->type == FF_FRICTION;
+}
+
+static int pidff_is_duration_infinite(u16 duration)
+{
+ return duration == FF_INFINITE || duration == PID_INFINITE;
+}
+
+/*
+ * Get PID effect index from FF effect type.
+ * Return 0 if invalid.
+ */
+static int pidff_effect_ff_to_pid(struct ff_effect *effect)
+{
+ switch (effect->type) {
+ case FF_CONSTANT:
+ return PID_CONSTANT;
+ case FF_RAMP:
+ return PID_RAMP;
+ case FF_SPRING:
+ return PID_SPRING;
+ case FF_DAMPER:
+ return PID_DAMPER;
+ case FF_INERTIA:
+ return PID_INERTIA;
+ case FF_FRICTION:
+ return PID_FRICTION;
+ case FF_PERIODIC:
+ switch (effect->u.periodic.waveform) {
+ case FF_SQUARE:
+ return PID_SQUARE;
+ case FF_TRIANGLE:
+ return PID_TRIANGLE;
+ case FF_SINE:
+ return PID_SINE;
+ case FF_SAW_UP:
+ return PID_SAW_UP;
+ case FF_SAW_DOWN:
+ return PID_SAW_DOWN;
+ }
+ }
+ pr_err("invalid effect type\n");
+ return -EINVAL;
+}
+
+/*
+ * Get effect id in the device descriptor.
+ * Return 0 if invalid.
+ */
+static int pidff_get_effect_type_id(struct pidff_device *pidff,
+ struct ff_effect *effect)
+{
+ int id = pidff_effect_ff_to_pid(effect);
+
+ if (id < 0)
+ return 0;
+
+ if (effect->type == FF_PERIODIC &&
+ pidff->quirks & HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY)
+ id = PID_SINE;
+
+ return pidff->type_id[id];
+}
+
+/*
+ * Clamp value for a given field
+ */
+static s32 pidff_clamp(s32 i, struct hid_field *field)
+{
+ return (s32)clamp(i, field->logical_minimum, field->logical_maximum);
+}
+
/*
* 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 +372,68 @@ 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)
+{
+ usage->value[0] = pidff_clamp(pidff_rescale_time(time, usage->field),
+ usage->field);
+}
+
+static void pidff_set_duration(struct pidff_usage *usage, u16 duration)
+{
+ /* PID defines INFINITE as the max possible value for duration field */
+ if (pidff_is_duration_infinite(duration)) {
+ usage->value[0] = (1U << usage->field->report_size) - 1;
+ return;
+ }
+
+ pidff_set_time(usage, duration);
+}
+
+static void pidff_set_effect_direction(struct pidff_device *pidff,
+ struct ff_effect *effect)
+{
+ u16 direction = effect->direction;
+ int direction_enable = 1;
+
+ /* Use fixed direction if needed */
+ if (pidff->quirks & HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION &&
+ pidff_is_effect_conditional(effect))
+ direction = PIDFF_FIXED_WHEEL_DIRECTION;
+
+ pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = direction_enable;
+ pidff->effect_direction->value[0] =
+ pidff_rescale(direction, U16_MAX, pidff->effect_direction);
+
+ if (direction_enable)
+ return;
+
+ /*
+ * For use with improved FFB API
+ * We want to read the selected axes and their direction from the effect
+ * struct and only enable those. For now, enable all axes.
+ *
+ */
+ for (int i = 0; i < PID_AXES_COUNT; i++) {
+ /* HID index starts with 1 */
+ int index = pidff->direction_axis_id[i] - 1;
+
+ if (index < 0)
+ continue;
+
+ pidff->axes_enable->value[index] = 1;
+ pidff->effect_direction->value[index] = pidff_rescale(
+ direction, U16_MAX, pidff->effect_direction);
+ }
+}
+
/*
* Send envelope report to the device
*/
@@ -233,26 +441,24 @@ 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->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length;
- pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length;
+ pidff_rescale(envelope->fade_level >
+ S16_MAX ? S16_MAX : envelope->fade_level, S16_MAX,
+ pidff->set_envelope[PID_FADE_LEVEL].field);
- hid_dbg(pidff->hid, "attack %u => %d\n",
- envelope->attack_level,
- pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
+ 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_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -261,17 +467,29 @@ 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 ||
+ int 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 0;
+ 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;
}
/*
* Send constant force report to the device
*/
-static void pidff_set_constant_force_report(struct pidff_device *pidff,
- struct ff_effect *effect)
+static void pidff_set_constant_report(struct pidff_device *pidff,
+ struct ff_effect *effect)
{
pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] =
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
@@ -279,7 +497,7 @@ static void pidff_set_constant_force_report(struct pidff_device *pidff,
effect->u.constant.level);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -301,20 +519,25 @@ 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;
+
+ pidff_set_effect_direction(pidff, effect);
+
+ /* 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);
+ HID_REQ_SET_REPORT);
}
/*
@@ -343,11 +566,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);
-
+ HID_REQ_SET_REPORT);
}
/*
@@ -368,13 +591,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],
@@ -388,7 +617,7 @@ static void pidff_set_condition_report(struct pidff_device *pidff,
pidff_set(&pidff->set_condition[PID_DEAD_BAND],
effect->u.condition[i].deadband);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
}
@@ -419,8 +648,8 @@ static int pidff_needs_set_condition(struct ff_effect *effect,
/*
* Send ramp force report to the device
*/
-static void pidff_set_ramp_force_report(struct pidff_device *pidff,
- struct ff_effect *effect)
+static void pidff_set_ramp_report(struct pidff_device *pidff,
+ struct ff_effect *effect)
{
pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] =
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
@@ -429,7 +658,7 @@ static void pidff_set_ramp_force_report(struct pidff_device *pidff,
pidff_set_signed(&pidff->set_ramp[PID_RAMP_END],
effect->u.ramp.end_level);
hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -442,29 +671,114 @@ 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)
+{
+ const 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 (int i = 0; i < ARRAY_SIZE(pidff_device_control); i++) {
+ int 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);
+ hid_dbg(pidff->hid, "Device control command 0x%02x sent",
+ pidff_device_control[field]);
+}
+
+/*
+ * 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_device_control(pidff, PID_ENABLE_ACTUATORS);
+}
+
+/*
+ * 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.
*/
static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
{
- int j;
-
pidff->create_new_effect_type->value[0] = efnum;
hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
hid_dbg(pidff->hid, "create_new_effect sent, type: %d\n", efnum);
pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
pidff->block_load_status->value[0] = 0;
hid_hw_wait(pidff->hid);
- for (j = 0; j < 60; j++) {
+ for (int i = 0; i < 60; i++) {
hid_dbg(pidff->hid, "pid_block_load requested\n");
hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_LOAD],
- HID_REQ_GET_REPORT);
+ HID_REQ_GET_REPORT);
hid_hw_wait(pidff->hid);
if (pidff->block_load_status->value[0] ==
pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) {
@@ -480,11 +794,22 @@ 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;
}
+static int pidff_needs_playback(struct pidff_device *pidff, int effect_id, int n)
+{
+ return !pidff->effect[effect_id].is_infinite ||
+ pidff->effect[effect_id].loop_count != n;
+}
+
/*
* Play the effect with PID id n times
*/
@@ -492,17 +817,21 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
{
pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+ hid_dbg(pidff->hid, "%s PID effect %d", n == 0 ? "stopping" : "playing",
+ pid_id);
+
if (n == 0) {
pidff->effect_operation_status->value[0] =
pidff->operation_id[PID_EFFECT_STOP];
} 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],
- HID_REQ_SET_REPORT);
+ HID_REQ_SET_REPORT);
}
/*
@@ -512,19 +841,26 @@ 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);
+ if (!pidff_needs_playback(pidff, effect_id, value))
+ return 0;
+
+ hid_dbg(pidff->hid, "requesting %s on FF effect %d",
+ value == 0 ? "stop" : "playback", effect_id);
+ pidff->effect[effect_id].loop_count = value;
+ pidff_playback_pid(pidff, pidff->effect[effect_id].pid_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);
+ HID_REQ_SET_REPORT);
}
/*
@@ -533,174 +869,95 @@ static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
static int pidff_erase_effect(struct input_dev *dev, int effect_id)
{
struct pidff_device *pidff = dev->ff->private;
- int pid_id = pidff->pid_id[effect_id];
+ int pid_id = pidff->effect[effect_id].pid_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. */
+ hid_dbg(pidff->hid, "starting to erase %d/%d\n", effect_id, pid_id);
+
+ /*
+ * 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);
+ if (pidff->effect_count > 0)
+ pidff->effect_count--;
+
+ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
return 0;
}
+#define PIDFF_SET_REPORT_IF_NEEDED(type, effect, old) \
+ ({ if (!old || pidff_needs_set_## type(effect, old)) \
+ pidff_set_ ##type## _report(pidff, effect); })
+
+#define PIDFF_SET_ENVELOPE_IF_NEEDED(type, effect, old) \
+ ({ if (pidff_needs_set_envelope(&effect->u.type.envelope, \
+ old ? &old->u.type.envelope : NULL)) \
+ pidff_set_envelope_report(pidff, &effect->u.type.envelope); })
+
/*
* Effect upload handler
*/
-static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *new,
struct ff_effect *old)
{
struct pidff_device *pidff = dev->ff->private;
- int type_id;
- int error;
+ const int type_id = pidff_get_effect_type_id(pidff, new);
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
- if (old) {
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
- pidff->pid_id[effect->id];
+ if (!type_id) {
+ hid_err(pidff->hid, "effect type not supported\n");
+ return -EINVAL;
}
- switch (effect->type) {
+ if (!pidff->effect_count)
+ pidff_reset(pidff);
+
+ if (!old) {
+ int error = pidff_request_effect_upload(pidff, type_id);
+
+ if (error)
+ return error;
+
+ pidff->effect_count++;
+ hid_dbg(pidff->hid, "current effect count: %d", pidff->effect_count);
+ pidff->effect[new->id].loop_count = 0;
+ pidff->effect[new->id].pid_id =
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+ }
+
+ pidff->effect[new->id].is_infinite =
+ pidff_is_duration_infinite(new->replay.length);
+
+ pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+ pidff->effect[new->id].pid_id;
+
+ PIDFF_SET_REPORT_IF_NEEDED(effect, new, old);
+ switch (new->type) {
case FF_CONSTANT:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_CONSTANT]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- 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);
+ PIDFF_SET_REPORT_IF_NEEDED(constant, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(constant, new, old);
break;
case FF_PERIODIC:
- if (!old) {
- switch (effect->u.periodic.waveform) {
- case FF_SQUARE:
- type_id = PID_SQUARE;
- break;
- case FF_TRIANGLE:
- type_id = PID_TRIANGLE;
- break;
- case FF_SINE:
- type_id = PID_SINE;
- break;
- case FF_SAW_UP:
- type_id = PID_SAW_UP;
- break;
- case FF_SAW_DOWN:
- type_id = PID_SAW_DOWN;
- break;
- default:
- hid_err(pidff->hid, "invalid waveform\n");
- return -EINVAL;
- }
-
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[type_id]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- 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);
+ PIDFF_SET_REPORT_IF_NEEDED(periodic, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(periodic, new, old);
break;
case FF_RAMP:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_RAMP]);
- if (error)
- return error;
- }
- if (!old || pidff_needs_set_effect(effect, old))
- 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);
+ PIDFF_SET_REPORT_IF_NEEDED(ramp, new, old);
+ PIDFF_SET_ENVELOPE_IF_NEEDED(ramp, new, old);
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:
- if (!old) {
- error = pidff_request_effect_upload(pidff,
- pidff->type_id[PID_INERTIA]);
- 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);
+ case FF_FRICTION:
+ PIDFF_SET_REPORT_IF_NEEDED(condition, new, old);
break;
-
- default:
- hid_err(pidff->hid, "invalid type\n");
- return -EINVAL;
}
-
- if (!old)
- pidff->pid_id[effect->id] =
- pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
-
hid_dbg(pidff->hid, "uploaded\n");
-
return 0;
}
@@ -709,11 +966,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,10 +989,13 @@ 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);
+ HID_REQ_SET_REPORT);
}
/*
@@ -747,44 +1003,85 @@ 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(dev->ff->private, magnitude);
+}
- pidff_autocenter(pidff, magnitude);
+/*
+ * Find specific usage in a given hid_field
+ */
+static int pidff_find_usage(struct hid_field *fld, unsigned int usage_code)
+{
+ for (int i = 0; i < fld->maxusage; i++) {
+ if (fld->usage[i].hid == usage_code)
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * Find hid_field with a specific usage. Return the usage index as well
+ */
+static int pidff_find_field_with_usage(int *usage_index,
+ struct hid_report *report,
+ unsigned int usage_code)
+{
+ for (int i = 0; i < report->maxfield; i++) {
+ struct hid_field *fld = report->field[i];
+
+ if (fld->maxusage != fld->report_count) {
+ pr_debug("maxusage and report_count do not match, skipping\n");
+ continue;
+ }
+
+ int index = pidff_find_usage(fld, usage_code);
+
+ if (index >= 0) {
+ *usage_index = index;
+ return i;
+ }
+ }
+ return -1;
}
/*
* Find fields from a report and fill a pidff_usage
*/
static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
- struct hid_report *report, int count, int strict)
+ struct hid_report *report, int count, int strict,
+ u32 *quirks)
{
- int i, j, k, found;
-
- for (k = 0; k < count; k++) {
- found = 0;
- for (i = 0; i < report->maxfield; i++) {
- if (report->field[i]->maxusage !=
- report->field[i]->report_count) {
- pr_debug("maxusage and report_count do not match, skipping\n");
- continue;
- }
- for (j = 0; j < report->field[i]->maxusage; j++) {
- if (report->field[i]->usage[j].hid ==
- (HID_UP_PID | table[k])) {
- pr_debug("found %d at %d->%d\n",
- k, i, j);
- usage[k].field = report->field[i];
- usage[k].value =
- &report->field[i]->value[j];
- found = 1;
- break;
- }
- }
- if (found)
- break;
+ const u8 block_offset = pidff_set_condition[PID_PARAM_BLOCK_OFFSET];
+ const u8 delay = pidff_set_effect[PID_START_DELAY];
+
+ if (!report) {
+ pr_debug("%s, null report\n", __func__);
+ return -1;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int index;
+ int found = pidff_find_field_with_usage(&index, report,
+ HID_UP_PID | table[i]);
+
+ if (found >= 0) {
+ pr_debug("found %d at %d->%d\n", i, found, index);
+ usage[i].field = report->field[found];
+ usage[i].value = &report->field[found]->value[index];
+ continue;
}
- if (!found && strict) {
- pr_debug("failed to locate %d\n", k);
+
+ if (table[i] == delay) {
+ pr_debug("Delay field not found, but that's OK\n");
+ pr_debug("Setting MISSING_DELAY quirk\n");
+ *quirks |= HID_PIDFF_QUIRK_MISSING_DELAY;
+
+ } else if (table[i] == block_offset) {
+ pr_debug("PBO field not found, but that's OK\n");
+ pr_debug("Setting MISSING_PBO quirk\n");
+ *quirks |= HID_PIDFF_QUIRK_MISSING_PBO;
+
+ } else if (strict) {
+ pr_debug("failed to locate %d\n", i);
return -1;
}
}
@@ -798,7 +1095,7 @@ static int pidff_check_usage(int usage)
{
int i;
- for (i = 0; i < sizeof(pidff_reports); i++)
+ for (i = 0; i < ARRAY_SIZE(pidff_reports); i++)
if (usage == (HID_UP_PID | pidff_reports[i]))
return i;
@@ -853,9 +1150,7 @@ static void pidff_find_reports(struct hid_device *hid, int report_type,
*/
static int pidff_reports_ok(struct pidff_device *pidff)
{
- int i;
-
- for (i = 0; i <= PID_REQUIRED_REPORTS; i++) {
+ for (int i = 0; i < PID_REQUIRED_REPORTS; i++) {
if (!pidff->reports[i]) {
hid_dbg(pidff->hid, "%d missing\n", i);
return 0;
@@ -871,18 +1166,20 @@ 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)
{
- int i;
+ if (!report) {
+ pr_debug("%s, null report\n", __func__);
+ return NULL;
+ }
- for (i = 0; i < report->maxfield; i++) {
+ for (int i = 0; i < report->maxfield; i++) {
if (report->field[i]->logical == (HID_UP_PID | usage) &&
report->field[i]->report_count > 0) {
if (!enforce_min ||
report->field[i]->logical_minimum == 1)
return report->field[i];
- else {
- pr_err("logical_minimum is not 1 as it should be\n");
- return NULL;
- }
+
+ pr_err("logical_minimum is not 1 as it should be\n");
+ return NULL;
}
}
return NULL;
@@ -892,27 +1189,29 @@ static struct hid_field *pidff_find_special_field(struct hid_report *report,
* Fill a pidff->*_id struct table
*/
static int pidff_find_special_keys(int *keys, struct hid_field *fld,
- const u8 *usagetable, int count)
+ const u8 *usagetable, int count,
+ unsigned int usage_page)
{
-
- int i, j;
int found = 0;
- for (i = 0; i < count; i++) {
- for (j = 0; j < fld->maxusage; j++) {
- if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) {
- keys[i] = j + 1;
- found++;
- break;
- }
- }
+ if (!fld)
+ return 0;
+
+ for (int i = 0; i < count; i++) {
+ keys[i] = pidff_find_usage(fld, usage_page | usagetable[i]) + 1;
+ if (keys[i])
+ found++;
}
return found;
}
#define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \
pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
- sizeof(pidff_ ## name))
+ ARRAY_SIZE(pidff_ ## name), HID_UP_PID)
+
+#define PIDFF_FIND_GENERAL_DESKTOP(keys, field, name) \
+ pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
+ ARRAY_SIZE(pidff_ ## name), HID_UP_GENDESK)
/*
* Find and check the special fields
@@ -923,22 +1222,35 @@ 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->axes_enable =
+ pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+ PID_AXES_ENABLE, 0);
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, 1);
+
+ /* Detect and set permissive control quirk */
+ if (!pidff->device_control) {
+ pr_debug("Setting PERMISSIVE_CONTROL quirk\n");
+ pidff->quirks |= HID_PIDFF_QUIRK_PERMISSIVE_CONTROL;
+ pidff->device_control = pidff_find_special_field(
+ pidff->reports[PID_DEVICE_CONTROL],
+ PID_DEVICE_CONTROL_ARRAY, 0);
+ }
+
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 +1279,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,
@@ -981,7 +1289,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status,
block_load_status) !=
- sizeof(pidff_block_load_status)) {
+ ARRAY_SIZE(pidff_block_load_status)) {
hid_err(pidff->hid,
"block load status identifiers not found\n");
return -1;
@@ -989,11 +1297,37 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status,
effect_operation_status) !=
- sizeof(pidff_effect_operation_status)) {
+ ARRAY_SIZE(pidff_effect_operation_status)) {
hid_err(pidff->hid, "effect operation identifiers not found\n");
return -1;
}
+ if (!pidff->axes_enable) {
+ hid_info(pidff->hid, "axes enable field not found!\n");
+ return 0;
+ }
+
+ hid_dbg(pidff->hid, "axes enable report count: %u\n",
+ pidff->axes_enable->report_count);
+
+ uint found = PIDFF_FIND_GENERAL_DESKTOP(direction_axis_id, axes_enable,
+ direction_axis);
+
+ pidff->axis_count = found;
+ hid_dbg(pidff->hid, "found direction axes: %u", found);
+
+ for (int i = 0; i < ARRAY_SIZE(pidff_direction_axis); i++) {
+ if (!pidff->direction_axis_id[i])
+ continue;
+
+ hid_dbg(pidff->hid, "axis %d, usage: 0x%04x, index: %d", i + 1,
+ pidff_direction_axis[i], pidff->direction_axis_id[i]);
+ }
+
+ if (pidff->axes_enable && found != pidff->axes_enable->report_count)
+ hid_warn(pidff->hid, "axes_enable: %u != direction axes: %u",
+ pidff->axes_enable->report_count, found);
+
return 0;
}
@@ -1005,8 +1339,9 @@ static int pidff_find_effects(struct pidff_device *pidff,
{
int i;
- for (i = 0; i < sizeof(pidff_effect_types); i++) {
+ for (i = 0; i < ARRAY_SIZE(pidff_effect_types); i++) {
int pidff_type = pidff->type_id[i];
+
if (pidff->set_effect_type->usage[pidff_type].hid !=
pidff->create_new_effect_type->usage[pidff_type].hid) {
hid_err(pidff->hid,
@@ -1049,21 +1384,18 @@ static int pidff_find_effects(struct pidff_device *pidff,
set_bit(FF_FRICTION, dev->ffbit);
return 0;
-
}
#define PIDFF_FIND_FIELDS(name, report, strict) \
pidff_find_fields(pidff->name, pidff_ ## name, \
pidff->reports[report], \
- sizeof(pidff_ ## name), strict)
+ ARRAY_SIZE(pidff_ ## name), strict, &pidff->quirks)
/*
* Fill and check the pidff_usages
*/
static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
{
- int envelope_ok = 0;
-
if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
hid_err(pidff->hid, "unknown set_effect report layout\n");
return -ENODEV;
@@ -1085,13 +1417,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");
@@ -1104,35 +1433,25 @@ static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
"has periodic effect but no envelope\n");
}
- if (test_bit(FF_CONSTANT, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) {
+ if (PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1) &&
+ test_and_clear_bit(FF_CONSTANT, dev->ffbit))
hid_warn(pidff->hid, "unknown constant effect layout\n");
- clear_bit(FF_CONSTANT, dev->ffbit);
- }
- if (test_bit(FF_RAMP, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) {
+ if (PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1) &&
+ test_and_clear_bit(FF_RAMP, dev->ffbit))
hid_warn(pidff->hid, "unknown ramp effect layout\n");
- 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 (PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
+ if (test_and_clear_bit(FF_SPRING, dev->ffbit) ||
+ test_and_clear_bit(FF_DAMPER, dev->ffbit) ||
+ test_and_clear_bit(FF_FRICTION, dev->ffbit) ||
+ test_and_clear_bit(FF_INERTIA, dev->ffbit))
+ hid_warn(pidff->hid, "unknown condition effect layout\n");
}
- if (test_bit(FF_PERIODIC, dev->ffbit) &&
- PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) {
+ if (PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1) &&
+ test_and_clear_bit(FF_PERIODIC, dev->ffbit))
hid_warn(pidff->hid, "unknown periodic effect layout\n");
- clear_bit(FF_PERIODIC, dev->ffbit);
- }
PIDFF_FIND_FIELDS(pool, PID_POOL, 0);
@@ -1143,46 +1462,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,28 +1485,27 @@ 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,
- struct hid_input, list);
+ struct hid_input *hidinput =
+ list_entry(hid->inputs.next, struct hid_input, list);
struct input_dev *dev = hidinput->input;
struct ff_device *ff;
int max_effects;
@@ -1245,6 +1523,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 +1541,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;
@@ -1310,15 +1585,27 @@ int hid_pidff_init(struct hid_device *hid)
ff->set_autocenter = pidff_set_autocenter;
ff->playback = pidff_playback;
- hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+ hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula\n");
+ hid_dbg(dev, "Active quirks mask: 0x%08x\n", pidff->quirks);
hid_device_io_stop(hid);
return 0;
- fail:
+fail:
hid_device_io_stop(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..f321f675e131
--- /dev/null
+++ b/drivers/hid/usbhid/hid-pidff.h
@@ -0,0 +1,32 @@
+/* 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 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_CONDITIONAL_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 4da50e19808e..1deacb4568cb 100644
--- a/drivers/hid/wacom.h
+++ b/drivers/hid/wacom.h
@@ -89,7 +89,7 @@
#include <linux/usb/input.h>
#include <linux/power_supply.h>
#include <linux/timer.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
/*
* Version Information
@@ -150,6 +150,7 @@ struct wacom_remote {
struct input_dev *input;
bool registered;
struct wacom_battery battery;
+ ktime_t active_time;
} remotes[WACOM_MAX_REMOTES];
};
@@ -163,6 +164,7 @@ struct wacom {
struct work_struct battery_work;
struct work_struct remote_work;
struct delayed_work init_work;
+ struct delayed_work aes_battery_work;
struct wacom_remote *remote;
struct work_struct mode_change_work;
struct timer_list idleprox_timer;
@@ -216,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 76e5353aca0c..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;
@@ -1370,17 +1397,6 @@ static int wacom_led_register_one(struct device *dev, struct wacom *wacom,
if (!name)
return -ENOMEM;
- if (!read_only) {
- led->trigger.name = name;
- error = devm_led_trigger_register(dev, &led->trigger);
- if (error) {
- hid_err(wacom->hdev,
- "failed to register LED trigger %s: %d\n",
- led->cdev.name, error);
- return error;
- }
- }
-
led->group = group;
led->id = id;
led->wacom = wacom;
@@ -1397,6 +1413,19 @@ static int wacom_led_register_one(struct device *dev, struct wacom *wacom,
led->cdev.brightness_set = wacom_led_readonly_brightness_set;
}
+ if (!read_only) {
+ led->trigger.name = name;
+ if (id == wacom->led.groups[group].select)
+ led->trigger.brightness = wacom_leds_brightness_get(led);
+ error = devm_led_trigger_register(dev, &led->trigger);
+ if (error) {
+ hid_err(wacom->hdev,
+ "failed to register LED trigger %s: %d\n",
+ led->cdev.name, error);
+ return error;
+ }
+ }
+
error = devm_led_classdev_register(dev, &led->cdev);
if (error) {
hid_err(wacom->hdev,
@@ -1813,6 +1842,13 @@ static void wacom_destroy_battery(struct wacom *wacom)
}
}
+static void wacom_aes_battery_handler(struct work_struct *work)
+{
+ struct wacom *wacom = container_of(work, struct wacom, aes_battery_work.work);
+
+ wacom_destroy_battery(wacom);
+}
+
static ssize_t wacom_show_speed(struct device *dev,
struct device_attribute
*attr, char *buf)
@@ -1997,7 +2033,7 @@ static int wacom_initialize_remotes(struct wacom *wacom)
spin_lock_init(&remote->remote_lock);
error = kfifo_alloc(&remote->remote_fifo,
- 5 * sizeof(struct wacom_remote_data),
+ 5 * sizeof(struct wacom_remote_work_data),
GFP_KERNEL);
if (error) {
hid_err(wacom->hdev, "failed allocating remote_fifo\n");
@@ -2012,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;
}
@@ -2080,7 +2120,7 @@ static int wacom_allocate_inputs(struct wacom *wacom)
return 0;
}
-static int wacom_register_inputs(struct wacom *wacom)
+static int wacom_setup_inputs(struct wacom *wacom)
{
struct input_dev *pen_input_dev, *touch_input_dev, *pad_input_dev;
struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
@@ -2099,10 +2139,6 @@ static int wacom_register_inputs(struct wacom *wacom)
input_free_device(pen_input_dev);
wacom_wac->pen_input = NULL;
pen_input_dev = NULL;
- } else {
- error = input_register_device(pen_input_dev);
- if (error)
- goto fail;
}
error = wacom_setup_touch_input_capabilities(touch_input_dev, wacom_wac);
@@ -2111,10 +2147,6 @@ static int wacom_register_inputs(struct wacom *wacom)
input_free_device(touch_input_dev);
wacom_wac->touch_input = NULL;
touch_input_dev = NULL;
- } else {
- error = input_register_device(touch_input_dev);
- if (error)
- goto fail;
}
error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac);
@@ -2123,7 +2155,34 @@ static int wacom_register_inputs(struct wacom *wacom)
input_free_device(pad_input_dev);
wacom_wac->pad_input = NULL;
pad_input_dev = NULL;
- } else {
+ }
+
+ return 0;
+}
+
+static int wacom_register_inputs(struct wacom *wacom)
+{
+ struct input_dev *pen_input_dev, *touch_input_dev, *pad_input_dev;
+ struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+ int error = 0;
+
+ pen_input_dev = wacom_wac->pen_input;
+ touch_input_dev = wacom_wac->touch_input;
+ pad_input_dev = wacom_wac->pad_input;
+
+ if (pen_input_dev) {
+ error = input_register_device(pen_input_dev);
+ if (error)
+ goto fail;
+ }
+
+ if (touch_input_dev) {
+ error = input_register_device(touch_input_dev);
+ if (error)
+ goto fail;
+ }
+
+ if (pad_input_dev) {
error = input_register_device(pad_input_dev);
if (error)
goto fail;
@@ -2215,7 +2274,8 @@ static void wacom_update_name(struct wacom *wacom, const char *suffix)
if (hid_is_usb(wacom->hdev)) {
struct usb_interface *intf = to_usb_interface(wacom->hdev->dev.parent);
struct usb_device *dev = interface_to_usbdev(intf);
- product_name = dev->product;
+ if (dev->product != NULL)
+ product_name = dev->product;
}
if (wacom->hdev->bus == BUS_I2C) {
@@ -2312,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);
@@ -2376,6 +2440,20 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
if (error)
goto fail;
+ error = wacom_setup_inputs(wacom);
+ if (error)
+ goto fail;
+
+ if (features->type == HID_GENERIC)
+ connect_mask |= HID_CONNECT_DRIVER;
+
+ /* Regular HID work starts now */
+ error = hid_hw_start(hdev, connect_mask);
+ if (error) {
+ hid_err(hdev, "hw start failed\n");
+ goto fail;
+ }
+
error = wacom_register_inputs(wacom);
if (error)
goto fail;
@@ -2390,16 +2468,6 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
goto fail;
}
- if (features->type == HID_GENERIC)
- connect_mask |= HID_CONNECT_DRIVER;
-
- /* Regular HID work starts now */
- error = hid_hw_start(hdev, connect_mask);
- if (error) {
- hid_err(hdev, "hw start failed\n");
- goto fail;
- }
-
if (!wireless) {
/* Note that if query fails it is not a hard failure */
wacom_query_tablet_data(wacom);
@@ -2523,6 +2591,18 @@ fail:
return;
}
+static void wacom_remote_destroy_battery(struct wacom *wacom, int index)
+{
+ struct wacom_remote *remote = wacom->remote;
+
+ if (remote->remotes[index].battery.battery) {
+ devres_release_group(&wacom->hdev->dev,
+ &remote->remotes[index].battery.bat_desc);
+ remote->remotes[index].battery.battery = NULL;
+ remote->remotes[index].active_time = 0;
+ }
+}
+
static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
{
struct wacom_remote *remote = wacom->remote;
@@ -2537,9 +2617,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
remote->remotes[i].registered = false;
spin_unlock_irqrestore(&remote->remote_lock, flags);
- if (remote->remotes[i].battery.battery)
- devres_release_group(&wacom->hdev->dev,
- &remote->remotes[i].battery.bat_desc);
+ wacom_remote_destroy_battery(wacom, i);
if (remote->remotes[i].group.name)
devres_release_group(&wacom->hdev->dev,
@@ -2547,7 +2625,6 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
remote->remotes[i].serial = 0;
remote->remotes[i].group.name = NULL;
- remote->remotes[i].battery.battery = NULL;
wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN;
}
}
@@ -2632,6 +2709,9 @@ static int wacom_remote_attach_battery(struct wacom *wacom, int index)
if (remote->remotes[index].battery.battery)
return 0;
+ if (!remote->remotes[index].active_time)
+ return 0;
+
if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN)
return 0;
@@ -2647,17 +2727,19 @@ static void wacom_remote_work(struct work_struct *work)
{
struct wacom *wacom = container_of(work, struct wacom, remote_work);
struct wacom_remote *remote = wacom->remote;
- struct wacom_remote_data data;
+ ktime_t kt = ktime_get();
+ struct wacom_remote_work_data remote_work_data;
unsigned long flags;
unsigned int count;
- u32 serial;
+ u32 work_serial;
int i;
spin_lock_irqsave(&remote->remote_lock, flags);
- count = kfifo_out(&remote->remote_fifo, &data, sizeof(data));
+ count = kfifo_out(&remote->remote_fifo, &remote_work_data,
+ sizeof(remote_work_data));
- if (count != sizeof(data)) {
+ if (count != sizeof(remote_work_data)) {
hid_err(wacom->hdev,
"workitem triggered without status available\n");
spin_unlock_irqrestore(&remote->remote_lock, flags);
@@ -2670,10 +2752,14 @@ static void wacom_remote_work(struct work_struct *work)
spin_unlock_irqrestore(&remote->remote_lock, flags);
for (i = 0; i < WACOM_MAX_REMOTES; i++) {
- serial = data.remote[i].serial;
- if (data.remote[i].connected) {
+ work_serial = remote_work_data.remote[i].serial;
+ if (work_serial) {
- if (remote->remotes[i].serial == serial) {
+ if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT
+ && remote->remotes[i].active_time != 0)
+ wacom_remote_destroy_battery(wacom, i);
+
+ if (remote->remotes[i].serial == work_serial) {
wacom_remote_attach_battery(wacom, i);
continue;
}
@@ -2681,7 +2767,7 @@ static void wacom_remote_work(struct work_struct *work)
if (remote->remotes[i].serial)
wacom_remote_destroy_one(wacom, i);
- wacom_remote_create_one(wacom, serial, i);
+ wacom_remote_create_one(wacom, work_serial, i);
} else if (remote->remotes[i].serial) {
wacom_remote_destroy_one(wacom, i);
@@ -2759,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;
@@ -2776,6 +2858,7 @@ static int wacom_probe(struct hid_device *hdev,
mutex_init(&wacom->lock);
INIT_DELAYED_WORK(&wacom->init_work, wacom_init_work);
+ INIT_DELAYED_WORK(&wacom->aes_battery_work, wacom_aes_battery_handler);
INIT_WORK(&wacom->wireless_work, wacom_wireless_work);
INIT_WORK(&wacom->battery_work, wacom_battery_work);
INIT_WORK(&wacom->remote_work, wacom_remote_work);
@@ -2822,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 174bf03908d7..9b2c710f8da1 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -63,7 +63,7 @@ static void wacom_force_proxout(struct wacom_wac *wacom_wac)
void wacom_idleprox_timeout(struct timer_list *list)
{
- struct wacom *wacom = from_timer(wacom, list, idleprox_timer);
+ struct wacom *wacom = timer_container_of(wacom, list, idleprox_timer);
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
if (!wacom_wac->hid_data.sense_state) {
@@ -684,6 +684,7 @@ static bool wacom_is_art_pen(int tool_id)
case 0x885: /* Intuos3 Marker Pen */
case 0x804: /* Intuos4/5 13HD/24HD Marker Pen */
case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
+ case 0x204: /* Art Pen 2 */
is_art_pen = true;
break;
}
@@ -692,78 +693,28 @@ static bool wacom_is_art_pen(int tool_id)
static int wacom_intuos_get_tool_type(int tool_id)
{
- int tool_type = BTN_TOOL_PEN;
-
- if (wacom_is_art_pen(tool_id))
- return tool_type;
-
switch (tool_id) {
case 0x812: /* Inking pen */
case 0x801: /* Intuos3 Inking pen */
case 0x12802: /* Intuos4/5 Inking Pen */
case 0x012:
- tool_type = BTN_TOOL_PENCIL;
- break;
-
- case 0x822: /* Pen */
- case 0x842:
- case 0x852:
- case 0x823: /* Intuos3 Grip Pen */
- case 0x813: /* Intuos3 Classic Pen */
- case 0x802: /* Intuos4/5 13HD/24HD General Pen */
- case 0x8e2: /* IntuosHT2 pen */
- case 0x022:
- case 0x200: /* Pro Pen 3 */
- case 0x04200: /* Pro Pen 3 */
- case 0x10842: /* MobileStudio Pro Pro Pen slim */
- case 0x14802: /* Intuos4/5 13HD/24HD Classic Pen */
- case 0x16802: /* Cintiq 13HD Pro Pen */
- case 0x18802: /* DTH2242 Pen */
- case 0x10802: /* Intuos4/5 13HD/24HD General Pen */
- case 0x80842: /* Intuos Pro and Cintiq Pro 3D Pen */
- tool_type = BTN_TOOL_PEN;
- break;
+ return BTN_TOOL_PENCIL;
case 0x832: /* Stroke pen */
case 0x032:
- tool_type = BTN_TOOL_BRUSH;
- break;
+ return BTN_TOOL_BRUSH;
case 0x007: /* Mouse 4D and 2D */
case 0x09c:
case 0x094:
case 0x017: /* Intuos3 2D Mouse */
case 0x806: /* Intuos4 Mouse */
- tool_type = BTN_TOOL_MOUSE;
- break;
+ return BTN_TOOL_MOUSE;
case 0x096: /* Lens cursor */
case 0x097: /* Intuos3 Lens cursor */
case 0x006: /* Intuos4 Lens cursor */
- tool_type = BTN_TOOL_LENS;
- break;
-
- case 0x82a: /* Eraser */
- case 0x84a:
- case 0x85a:
- case 0x91a:
- case 0xd1a:
- case 0x0fa:
- case 0x82b: /* Intuos3 Grip Pen Eraser */
- case 0x81b: /* Intuos3 Classic Pen Eraser */
- case 0x91b: /* Intuos3 Airbrush Eraser */
- case 0x80c: /* Intuos4/5 13HD/24HD Marker Pen Eraser */
- case 0x80a: /* Intuos4/5 13HD/24HD General Pen Eraser */
- case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
- case 0x1480a: /* Intuos4/5 13HD/24HD Classic Pen Eraser */
- case 0x1090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
- case 0x1080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
- case 0x1084a: /* MobileStudio Pro Pro Pen slim Eraser */
- case 0x1680a: /* Cintiq 13HD Pro Pen Eraser */
- case 0x1880a: /* DTH2242 Eraser */
- case 0x1080a: /* Intuos4/5 13HD/24HD General Pen Eraser */
- tool_type = BTN_TOOL_RUBBER;
- break;
+ return BTN_TOOL_LENS;
case 0xd12:
case 0x912:
@@ -771,10 +722,13 @@ static int wacom_intuos_get_tool_type(int tool_id)
case 0x913: /* Intuos3 Airbrush */
case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
case 0x10902: /* Intuos4/5 13HD/24HD Airbrush */
- tool_type = BTN_TOOL_AIRBRUSH;
- break;
+ return BTN_TOOL_AIRBRUSH;
+
+ default:
+ if (tool_id & 0x0008)
+ return BTN_TOOL_RUBBER;
+ return BTN_TOOL_PEN;
}
- return tool_type;
}
static void wacom_exit_report(struct wacom_wac *wacom)
@@ -1134,6 +1088,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
if (index < 0 || !remote->remotes[index].registered)
goto out;
+ remote->remotes[i].active_time = ktime_get();
input = remote->remotes[index].input;
input_report_key(input, BTN_0, (data[9] & 0x01));
@@ -1196,22 +1151,20 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len)
struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
unsigned char *data = wacom_wac->data;
struct wacom_remote *remote = wacom->remote;
- struct wacom_remote_data remote_data;
+ struct wacom_remote_work_data remote_data;
unsigned long flags;
int i, ret;
if (data[0] != WACOM_REPORT_DEVICE_LIST)
return;
- memset(&remote_data, 0, sizeof(struct wacom_remote_data));
+ memset(&remote_data, 0, sizeof(struct wacom_remote_work_data));
for (i = 0; i < WACOM_MAX_REMOTES; i++) {
int j = i * 6;
int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4];
- bool connected = data[j+2];
remote_data.remote[i].serial = serial;
- remote_data.remote[i].connected = connected;
}
spin_lock_irqsave(&remote->remote_lock, flags);
@@ -1249,12 +1202,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);
@@ -1278,8 +1229,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;
}
@@ -1401,9 +1354,9 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
rotation -= 1800;
input_report_abs(pen_input, ABS_TILT_X,
- (char)frame[7]);
+ (signed char)frame[7]);
input_report_abs(pen_input, ABS_TILT_Y,
- (char)frame[8]);
+ (signed char)frame[8]);
input_report_abs(pen_input, ABS_Z, rotation);
input_report_abs(pen_input, ABS_WHEEL,
get_unaligned_le16(&frame[11]));
@@ -1926,12 +1879,14 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
int fmax = field->logical_maximum;
unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
int resolution_code = code;
- int resolution = hidinput_calc_abs_res(field, resolution_code);
+ int resolution;
if (equivalent_usage == HID_DG_TWIST) {
resolution_code = ABS_RZ;
}
+ resolution = hidinput_calc_abs_res(field, resolution_code);
+
if (equivalent_usage == HID_GD_X) {
fmin += features->offset_left;
fmax -= features->offset_right;
@@ -1952,11 +1907,12 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
if ((code == ABS_X || code == ABS_Y) && !resolution) {
resolution = WACOM_INTUOS_RES;
hid_warn(input,
- "Wacom usage (%d) missing resolution \n",
- code);
+ "Using default resolution for axis type 0x%x code 0x%x\n",
+ type, code);
}
input_abs_set_res(input, code, resolution);
break;
+ case EV_REL:
case EV_KEY:
case EV_MSC:
case EV_SW:
@@ -2093,7 +2049,23 @@ static void wacom_wac_pad_usage_mapping(struct hid_device *hdev,
features->device_type |= WACOM_DEVICETYPE_PAD;
break;
case WACOM_HID_WD_TOUCHRING:
- wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
+ if (field->flags & HID_MAIN_ITEM_RELATIVE) {
+ wacom_wac->relring_count++;
+ if (wacom_wac->relring_count == 1) {
+ wacom_map_usage(input, usage, field, EV_REL, REL_WHEEL_HI_RES, 0);
+ set_bit(REL_WHEEL, input->relbit);
+ }
+ else if (wacom_wac->relring_count == 2) {
+ wacom_map_usage(input, usage, field, EV_REL, REL_HWHEEL_HI_RES, 0);
+ set_bit(REL_HWHEEL, input->relbit);
+ }
+ } else {
+ wacom_wac->absring_count++;
+ if (wacom_wac->absring_count == 1)
+ wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
+ else if (wacom_wac->absring_count == 2)
+ wacom_map_usage(input, usage, field, EV_ABS, ABS_THROTTLE, 0);
+ }
features->device_type |= WACOM_DEVICETYPE_PAD;
break;
case WACOM_HID_WD_TOUCHRINGSTATUS:
@@ -2158,7 +2130,10 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
return;
if (wacom_equivalent_usage(field->physical) == HID_DG_TABLETFUNCTIONKEY) {
- if (usage->hid != WACOM_HID_WD_TOUCHRING)
+ bool is_abs_touchring = usage->hid == WACOM_HID_WD_TOUCHRING &&
+ !(field->flags & HID_MAIN_ITEM_RELATIVE);
+
+ if (!is_abs_touchring)
wacom_wac->hid_data.inrange_state |= value;
}
@@ -2211,6 +2186,52 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
hdev->product == 0x3AA)
value = wacom_offset_rotation(input, usage, value, 1, 2);
}
+ else if (field->flags & HID_MAIN_ITEM_RELATIVE) {
+ int hires_value = value * 120 / usage->resolution_multiplier;
+ int *ring_value;
+ int lowres_code;
+
+ if (usage->code == REL_WHEEL_HI_RES) {
+ /* We must invert the sign for vertical
+ * relative scrolling. Clockwise
+ * rotation produces positive values
+ * from HW, but userspace treats
+ * positive REL_WHEEL as a scroll *up*!
+ */
+ hires_value = -hires_value;
+ ring_value = &wacom_wac->hid_data.ring_value;
+ lowres_code = REL_WHEEL;
+ }
+ else if (usage->code == REL_HWHEEL_HI_RES) {
+ /* No need to invert the sign for
+ * horizontal relative scrolling.
+ * Clockwise rotation produces positive
+ * values from HW and userspace treats
+ * positive REL_HWHEEL as a scroll
+ * right.
+ */
+ ring_value = &wacom_wac->hid_data.ring2_value;
+ lowres_code = REL_HWHEEL;
+ }
+ else {
+ hid_err(wacom->hdev, "unrecognized relative wheel with code %d\n",
+ usage->code);
+ break;
+ }
+
+ value = hires_value;
+ *ring_value += hires_value;
+
+ /* Emulate a legacy wheel click for every 120
+ * units of hi-res travel.
+ */
+ if (*ring_value >= 120 || *ring_value <= -120) {
+ int clicks = *ring_value / 120;
+
+ input_event(input, usage->type, lowres_code, clicks);
+ *ring_value -= clicks * 120;
+ }
+ }
else {
value = wacom_offset_rotation(input, usage, value, 1, 4);
}
@@ -2368,6 +2389,9 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS3, 0);
features->quirks &= ~WACOM_QUIRK_PEN_BUTTON3;
break;
+ case WACOM_HID_WD_SEQUENCENUMBER:
+ wacom_wac->hid_data.sequence_number = -1;
+ break;
}
}
@@ -2399,9 +2423,11 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
wacom_wac->hid_data.sense_state = value;
return;
case HID_DG_INVERT:
- wacom_wac->hid_data.invert_state = value;
+ wacom_wac->hid_data.eraser |= value;
return;
case HID_DG_ERASER:
+ wacom_wac->hid_data.eraser |= value;
+ fallthrough;
case HID_DG_TIPSWITCH:
wacom_wac->hid_data.tipswitch |= value;
return;
@@ -2492,9 +2518,15 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
wacom_wac->hid_data.barrelswitch3 = value;
return;
case WACOM_HID_WD_SEQUENCENUMBER:
- if (wacom_wac->hid_data.sequence_number != value)
- hid_warn(hdev, "Dropped %hu packets", (unsigned short)(value - wacom_wac->hid_data.sequence_number));
+ if (wacom_wac->hid_data.sequence_number != value &&
+ wacom_wac->hid_data.sequence_number >= 0) {
+ int sequence_size = field->logical_maximum - field->logical_minimum + 1;
+ int drop_count = (value - wacom_wac->hid_data.sequence_number) % sequence_size;
+ hid_warn(hdev, "Dropped %d packets", drop_count);
+ }
wacom_wac->hid_data.sequence_number = value + 1;
+ if (wacom_wac->hid_data.sequence_number > field->logical_maximum)
+ wacom_wac->hid_data.sequence_number = field->logical_minimum;
return;
}
@@ -2529,14 +2561,17 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
struct input_dev *input = wacom_wac->pen_input;
bool range = wacom_wac->hid_data.inrange_state;
bool sense = wacom_wac->hid_data.sense_state;
+ bool entering_range = !wacom_wac->tool[0] && range;
if (wacom_wac->is_invalid_bt_frame)
return;
- if (!wacom_wac->tool[0] && range) { /* first in range */
+ if (entering_range) { /* first in range */
/* Going into range select tool */
- if (wacom_wac->hid_data.invert_state)
+ if (wacom_wac->hid_data.eraser)
wacom_wac->tool[0] = BTN_TOOL_RUBBER;
+ else if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN)
+ wacom_wac->tool[0] = BTN_TOOL_PEN;
else if (wacom_wac->id[0])
wacom_wac->tool[0] = wacom_intuos_get_tool_type(wacom_wac->id[0]);
else
@@ -2575,15 +2610,32 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
wacom_wac->hid_data.tipswitch);
input_report_key(input, wacom_wac->tool[0], sense);
if (wacom_wac->serial[0]) {
- input_event(input, EV_MSC, MSC_SERIAL, wacom_wac->serial[0]);
+ /*
+ * xf86-input-wacom does not accept a serial number
+ * of '0'. Report the low 32 bits if possible, but
+ * if they are zero, report the upper ones instead.
+ */
+ __u32 serial_lo = wacom_wac->serial[0] & 0xFFFFFFFFu;
+ __u32 serial_hi = wacom_wac->serial[0] >> 32;
+ input_event(input, EV_MSC, MSC_SERIAL, (int)(serial_lo ? serial_lo : serial_hi));
input_report_abs(input, ABS_MISC, sense ? id : 0);
}
wacom_wac->hid_data.tipswitch = false;
+ wacom_wac->hid_data.eraser = false;
input_sync(input);
}
+ /* Handle AES battery timeout behavior */
+ if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN) {
+ if (entering_range)
+ cancel_delayed_work(&wacom->aes_battery_work);
+ if (!sense)
+ schedule_delayed_work(&wacom->aes_battery_work,
+ msecs_to_jiffies(WACOM_AES_BATTERY_TIMEOUT));
+ }
+
if (!sense) {
wacom_wac->tool[0] = 0;
wacom_wac->id[0] = 0;
@@ -2650,8 +2702,8 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
{
struct hid_data *hid_data = &wacom_wac->hid_data;
bool mt = wacom_wac->features.touch_max > 1;
- bool prox = hid_data->tipswitch &&
- report_touch_events(wacom_wac);
+ bool touch_down = hid_data->tipswitch && hid_data->confidence;
+ bool prox = touch_down && report_touch_events(wacom_wac);
if (touch_is_muted(wacom_wac)) {
if (!wacom_wac->shared->touch_down)
@@ -2701,24 +2753,6 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
}
}
-static bool wacom_wac_slot_is_active(struct input_dev *dev, int key)
-{
- struct input_mt *mt = dev->mt;
- struct input_mt_slot *s;
-
- if (!mt)
- return false;
-
- for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
- if (s->key == key &&
- input_mt_get_value(s, ABS_MT_TRACKING_ID) >= 0) {
- return true;
- }
- }
-
- return false;
-}
-
static void wacom_wac_finger_event(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage, __s32 value)
{
@@ -2769,14 +2803,8 @@ static void wacom_wac_finger_event(struct hid_device *hdev,
}
if (usage->usage_index + 1 == field->report_count) {
- if (equivalent_usage == wacom_wac->hid_data.last_slot_field) {
- bool touch_removed = wacom_wac_slot_is_active(wacom_wac->touch_input,
- wacom_wac->hid_data.id) && !wacom_wac->hid_data.tipswitch;
-
- if (wacom_wac->hid_data.confidence || touch_removed) {
- wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input);
- }
- }
+ if (equivalent_usage == wacom_wac->hid_data.last_slot_field)
+ wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input);
}
}
@@ -2998,11 +3026,11 @@ void wacom_wac_report(struct hid_device *hdev, struct hid_report *report)
wacom_wac_battery_pre_report(hdev, report);
- if (pad_in_hid_field && wacom->wacom_wac.pad_input)
+ if (pad_in_hid_field && wacom_wac->pad_input)
wacom_wac_pad_pre_report(hdev, report);
- if (pen_in_hid_field && wacom->wacom_wac.pen_input)
+ if (pen_in_hid_field && wacom_wac->pen_input)
wacom_wac_pen_pre_report(hdev, report);
- if (finger_in_hid_field && wacom->wacom_wac.touch_input)
+ if (finger_in_hid_field && wacom_wac->touch_input)
wacom_wac_finger_pre_report(hdev, report);
for (r = 0; r < report->maxfield; r++) {
@@ -3018,7 +3046,7 @@ void wacom_wac_report(struct hid_device *hdev, struct hid_report *report)
wacom_wac_battery_report(hdev, report);
- if (true_pad && wacom->wacom_wac.pad_input)
+ if (true_pad && wacom_wac->pad_input)
wacom_wac_pad_report(hdev, report, field);
}
@@ -4919,6 +4947,10 @@ static const struct wacom_features wacom_features_0x94 =
HID_DEVICE(BUS_I2C, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
.driver_data = (kernel_ulong_t)&wacom_features_##prod
+#define PCI_DEVICE_WACOM(prod) \
+ HID_DEVICE(BUS_PCI, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+ .driver_data = (kernel_ulong_t)&wacom_features_##prod
+
#define USB_DEVICE_LENOVO(prod) \
HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, prod), \
.driver_data = (kernel_ulong_t)&wacom_features_##prod
@@ -5088,6 +5120,7 @@ const struct hid_device_id wacom_ids[] = {
{ USB_DEVICE_WACOM(HID_ANY_ID) },
{ I2C_DEVICE_WACOM(HID_ANY_ID) },
+ { PCI_DEVICE_WACOM(HID_ANY_ID) },
{ BT_DEVICE_WACOM(HID_ANY_ID) },
{ }
};
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index ee21bb260f22..d4f7d8ca1e7e 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -7,12 +7,11 @@
#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
+#define WACOM_REMOTE_BATTERY_TIMEOUT 21000000000ll
+#define WACOM_AES_BATTERY_TIMEOUT 1800000
/* packet length for individual models */
#define WACOM_PKGLEN_BBFUN 9
@@ -275,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;
};
@@ -298,7 +297,7 @@ struct hid_data {
__s16 inputmode_index; /* InputMode HID feature index in the report */
bool sense_state;
bool inrange_state;
- bool invert_state;
+ bool eraser;
bool tipswitch;
bool barrelswitch;
bool barrelswitch2;
@@ -307,10 +306,11 @@ struct hid_data {
bool confidence;
int x;
int y;
- int pressure;
int width;
int height;
int id;
+ int ring_value;
+ int ring2_value;
int cc_report;
int cc_index;
int cc_value_index;
@@ -323,14 +323,13 @@ struct hid_data {
int bat_connected;
int ps_connected;
bool pad_input_event_flag;
- unsigned short sequence_number;
+ int sequence_number;
ktime_t time_delayed;
};
-struct wacom_remote_data {
+struct wacom_remote_work_data {
struct {
u32 serial;
- bool connected;
} remote[WACOM_MAX_REMOTES];
};
@@ -339,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];
@@ -355,6 +354,8 @@ struct wacom_wac {
int num_contacts_left;
u8 bt_features;
u8 bt_high_speed;
+ u8 absring_count;
+ u8 relring_count;
int mode_report;
int mode_value;
struct hid_data hid_data;