├── .checkpatch.conf ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── all.config │ ├── build.yml │ ├── checkpatch.yml │ ├── dkms.yml │ └── gcc-problem-matcher.json ├── .gitignore ├── Documentation ├── hwmon │ ├── nzxt-grid3.rst │ ├── nzxt-kraken2.rst │ ├── nzxt-kraken3.rst │ ├── nzxt-smart2.rst │ └── nzxt-smartdevice.rst └── internal │ ├── 1e71:170e.lsusb │ ├── 1e71:170e.rdesc │ ├── 1e71:1714.lsusb │ ├── 1e71:1714.rdesc │ ├── notes.md │ └── unreliable-notes-on-hid.txt ├── LICENSE.txt ├── Makefile ├── README.md ├── Vagrantfile ├── drivers └── hwmon │ ├── Makefile │ ├── dkms.conf.in │ ├── nzxt-grid3.c │ ├── nzxt-kraken2.c │ ├── nzxt-kraken3.c │ └── nzxt-smart2.c ├── gitversion.sh └── tools └── vagrant ├── cd-to-vagrant.yml └── dev-tools.yml /.checkpatch.conf: -------------------------------------------------------------------------------- 1 | --strict 2 | --no-tree 3 | --ignore LINUX_VERSION_CODE 4 | --codespell 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | gitversion.sh export-subst 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/all.config: -------------------------------------------------------------------------------- 1 | CONFIG_MODULES=y 2 | CONFIG_INPUT=y 3 | CONFIG_HID_SUPPORT=y 4 | CONFIG_USB_SUPPORT=y 5 | CONFIG_USB=y 6 | CONFIG_USB_HID=y 7 | CONFIG_HWMON=y 8 | CONFIG_WERROR=y 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | kernel_version: 11 | - master 12 | - v6.8 13 | kconfig_pm: 14 | - "-e CONFIG_PM" 15 | - "-d CONFIG_PM" 16 | kconfig_debug_fs: 17 | - "-e CONFIG_DEBUG_FS" 18 | - "-d CONFIG_DEBUG_FS" 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | path: src 24 | - run: echo "::add-matcher::src/.github/workflows/gcc-problem-matcher.json" 25 | - run: sudo apt-get update 26 | - run: sudo apt-get install -y libelf-dev sparse 27 | - uses: actions/checkout@v4 28 | with: 29 | repository: torvalds/linux 30 | path: linux 31 | ref: ${{ matrix.kernel_version }} 32 | - run: cp src/.github/workflows/all.config linux/ 33 | - run: scripts/config --file all.config ${{ matrix.kconfig_pm }} ${{ matrix.kconfig_debug_fs }} 34 | working-directory: linux 35 | - run: KCONFIG_ALLCONFIG=1 KCFLAGS=-Werror make C=1 allnoconfig 36 | working-directory: linux 37 | - run: KCFLAGS=-Werror make C=1 -j$(nproc) 38 | working-directory: linux 39 | - run: KCFLAGS=-Werror make C=1 -k "KDIR=${{ github.workspace }}/linux" 40 | working-directory: src 41 | -------------------------------------------------------------------------------- /.github/workflows/checkpatch.yml: -------------------------------------------------------------------------------- 1 | name: checkpatch 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | checkpatch: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - run: sudo apt-get update 10 | - run: sudo apt-get install -y codespell 11 | - uses: actions/checkout@v4 12 | with: 13 | path: src 14 | - uses: actions/checkout@v4 15 | with: 16 | repository: torvalds/linux 17 | path: linux 18 | - run: make KDIR="${{ github.workspace }}/linux" checkpatch 19 | working-directory: src 20 | -------------------------------------------------------------------------------- /.github/workflows/dkms.yml: -------------------------------------------------------------------------------- 1 | name: dkms 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | dkms: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | - run: echo "::add-matcher::.github/workflows/gcc-problem-matcher.json" 13 | - run: sudo apt-get update 14 | - run: sudo apt-get install -y dkms 15 | - run: sudo make dkms_install 16 | - run: sudo dkms install -m liquidtux -v $(./gitversion.sh) 17 | -------------------------------------------------------------------------------- /.github/workflows/gcc-problem-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "gcc", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "severity": 4, 12 | "message": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.ko 3 | *.mod* 4 | *.o* 5 | Module.symvers 6 | 7 | !.gitignore 8 | !.github/ 9 | !.checkpatch.conf 10 | !.gitattributes 11 | 12 | dkms.conf 13 | -------------------------------------------------------------------------------- /Documentation/hwmon/nzxt-grid3.rst: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | Kernel driver nzxt-grid3 4 | ======================== 5 | 6 | Supported devices: 7 | 8 | * NZXT Grid+ V3 9 | * NZXT Smart Device (V1) 10 | 11 | Author: Jonas Malaco 12 | 13 | Description 14 | ----------- 15 | 16 | This driver enables hardware monitoring support for NZXT Grid+ V3 and Smart 17 | Device (V1) fan hubs. For each fan channel, three sensors are available: 18 | speed, current and voltage. The Grid+ V3 has six fan channels; the Smart 19 | Device, three. The devices also support fan control. 20 | 21 | Addressable RGB LED control, supported by the Smart Device, is not exposed; 22 | this feature can be found in existing user-space tools (e.g. `liquidctl`_). 23 | 24 | .. _liquidctl: https://github.com/liquidctl/liquidctl 25 | 26 | Usage Notes 27 | ----------- 28 | 29 | As these are USB HIDs, the driver can be loaded automatically by the kernel and 30 | supports hot swapping. 31 | 32 | When the driver is bound to the device, or when it resumes from a suspended 33 | state where it was powered off, all channels are reset to the device's default 34 | of 40% PWM, and the device attempts to (re)detect the appropriate control mode 35 | (DC or PWM) for each channel. The control mode is not periodically adjusted 36 | and will not track fans that have been added, removed, or replaced. 37 | 38 | Sysfs entries 39 | ------------- 40 | 41 | ======================= ======= ================================================ 42 | fan[1-6]_input RO Fan speed (in rpm) 43 | curr[1-6]_input RO Fan current draw (in milliampere) 44 | in[0-5]_input RO Fan supply voltage (in millivolt) 45 | pwm[1-6] RW Fan target duty cycle (integer from 0 to 255) 46 | pwm[1-6]_mode RO Fan control mode (0: DC; 1: PWM) 47 | ======================= ======= ================================================ 48 | 49 | The PWM value set for a channel cannot actually be read from the hardware. 50 | However, to avoid breaking the reasonable expectation that ``pwm[1-*]`` is 51 | readable, the driver keeps track of the PWM values that have been set through 52 | it. Reads from ``pwm[1-*]`` will return these values, which are only accurate 53 | as long as no PWM change has been issued bypassing the driver (e.g. through 54 | hidraw). 55 | 56 | The hardware accepts ``pwm[1-*]`` writes for channels with no detectable [#f1]_ 57 | fans, but these changes have no immediate effect. 58 | 59 | .. [#f1] At the time it attempted to detect the appropriate control mode for each channel. 60 | -------------------------------------------------------------------------------- /Documentation/hwmon/nzxt-kraken2.rst: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | Kernel driver nzxt-kraken2 4 | ========================== 5 | 6 | Supported devices: 7 | 8 | * NZXT Kraken X42 9 | * NZXT Kraken X52 10 | * NZXT Kraken X62 11 | * NZXT Kraken X72 12 | 13 | Author: Jonas Malaco 14 | 15 | Description 16 | ----------- 17 | 18 | This driver enables hardware monitoring support for NZXT Kraken X42/X52/X62/X72 19 | all-in-one CPU liquid coolers. Three sensors are available: fan speed, pump 20 | speed and coolant temperature. 21 | 22 | Fan and pump control, while supported by the firmware, are not currently 23 | exposed. The addressable RGB LEDs, present in the integrated CPU water block 24 | and pump head, are not supported either. But both features can be found in 25 | existing user-space tools (e.g. `liquidctl`_). 26 | 27 | .. _liquidctl: https://github.com/liquidctl/liquidctl 28 | 29 | Usage Notes 30 | ----------- 31 | 32 | As these are USB HIDs, the driver can be loaded automatically by the kernel and 33 | supports hot swapping. 34 | 35 | Sysfs entries 36 | ------------- 37 | 38 | ======================= ======================================================== 39 | fan1_input Fan speed (in rpm) 40 | fan2_input Pump speed (in rpm) 41 | temp1_input Coolant temperature (in millidegrees Celsius) 42 | ======================= ======================================================== 43 | -------------------------------------------------------------------------------- /Documentation/hwmon/nzxt-kraken3.rst: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | Kernel driver nzxt-kraken3 4 | ========================== 5 | 6 | Supported devices: 7 | 8 | * NZXT Kraken X53 9 | * NZXT Kraken X63 10 | * NZXT Kraken X73 11 | * NZXT Kraken Z53 12 | * NZXT Kraken Z63 13 | * NZXT Kraken Z73 14 | * NZXT Kraken 2023 15 | * NZXT Kraken 2023 Elite 16 | 17 | Author: Jonas Malaco, Aleksa Savic 18 | 19 | Description 20 | ----------- 21 | 22 | This driver enables hardware monitoring support for NZXT Kraken X53/X63/X73, 23 | Z53/Z63/Z73 and Kraken 2023 (standard and Elite) all-in-one CPU liquid coolers. 24 | All models expose liquid temperature and pump speed (in RPM), as well as PWM 25 | control (either as a fixed value or through a temp-PWM curve). The Z-series and 26 | Kraken 2023 models additionally expose the speed and duty of an optionally connected 27 | fan, with the same PWM control capabilities. 28 | 29 | Pump and fan duty control mode can be set through pwm[1-2]_enable, where 1 is 30 | for the manual control mode and 2 is for the liquid temp to PWM curve mode. 31 | Writing a 0 disables control of the channel through the driver after setting its 32 | duty to 100%. 33 | 34 | The temperature of the curves relates to the fixed [20-59] range, correlating to 35 | the detected liquid temperature. Only PWM values (ranging from 0-255) can be set. 36 | If in curve mode, setting point values should be done in moderation - the devices 37 | require complete curves to be sent for each change; they can lock up or discard 38 | the changes if they are too numerous at once. Suggestion is to set them while 39 | in an another mode, and then apply them by switching to curve. 40 | 41 | The devices can report if they are faulty. The driver supports that situation 42 | and will issue a warning. This can also happen when the USB cable is connected, 43 | but SATA power is not. 44 | 45 | The addressable RGB LEDs and LCD screen (only on Z-series and Kraken 2023 models) 46 | are not supported in this driver, but can be controlled through existing userspace 47 | tools, such as `liquidctl`_. 48 | 49 | .. _liquidctl: https://github.com/liquidctl/liquidctl 50 | 51 | Usage Notes 52 | ----------- 53 | 54 | As these are USB HIDs, the driver can be loaded automatically by the kernel and 55 | supports hot swapping. 56 | 57 | Possible pwm_enable values are: 58 | 59 | ====== ========================================================================== 60 | 0 Set fan to 100% 61 | 1 Direct PWM mode (applies value in corresponding PWM entry) 62 | 2 Curve control mode (applies the temp-PWM duty curve based on coolant temp) 63 | ====== ========================================================================== 64 | 65 | Sysfs entries 66 | ------------- 67 | 68 | ============================== ================================================================ 69 | fan1_input Pump speed (in rpm) 70 | fan2_input Fan speed (in rpm) 71 | temp1_input Coolant temperature (in millidegrees Celsius) 72 | pwm1 Pump duty (value between 0-255) 73 | pwm1_enable Pump duty control mode (0: disabled, 1: manual, 2: curve) 74 | pwm2 Fan duty (value between 0-255) 75 | pwm2_enable Fan duty control mode (0: disabled, 1: manual, 2: curve) 76 | temp[1-2]_auto_point[1-40]_pwm Temp-PWM duty curves (for pump and fan), related to coolant temp 77 | ============================== ================================================================ 78 | -------------------------------------------------------------------------------- /Documentation/hwmon/nzxt-smart2.rst: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | Kernel driver nzxt-smart2 4 | ========================= 5 | 6 | Supported devices: 7 | 8 | - NZXT RGB & Fan controller 9 | - NZXT Smart Device v2 10 | 11 | Description 12 | ----------- 13 | 14 | This driver implements monitoring and control of fans plugged into the device. 15 | Besides typical speed monitoring and PWM duty cycle control, voltage and current 16 | is reported for every fan. 17 | 18 | The device also has two connectors for RGB LEDs; support for them isn't 19 | implemented (mainly because there is no standardized sysfs interface). 20 | 21 | Also, the device has a noise sensor, but the sensor seems to be completely 22 | useless (and very imprecise), so support for it isn't implemented too. 23 | 24 | Usage Notes 25 | ----------- 26 | 27 | The device should be autodetected, and the driver should load automatically. 28 | 29 | If fans are plugged in/unplugged while the system is powered on, the driver 30 | must be reloaded to detect configuration changes; otherwise, new fans can't 31 | be controlled (`pwm*` changes will be ignored). It is necessary because the 32 | device has a dedicated "detect fans" command, and currently, it is executed only 33 | during initialization. Speed, voltage, current monitoring will work even without 34 | reload. As an alternative to reloading the module, a userspace tool (like 35 | `liquidctl`_) can be used to run "detect fans" command through hidraw interface. 36 | 37 | The driver coexists with userspace tools that access the device through hidraw 38 | interface with no known issues. 39 | 40 | .. _liquidctl: https://github.com/liquidctl/liquidctl 41 | 42 | Sysfs entries 43 | ------------- 44 | 45 | ======================= ======================================================== 46 | fan[1-3]_input Fan speed monitoring (in rpm). 47 | curr[1-3]_input Current supplied to the fan (in milliamperes). 48 | in[0-2]_input Voltage supplied to the fan (in millivolts). 49 | pwm[1-3] Controls fan speed: PWM duty cycle for PWM-controlled 50 | fans, voltage for other fans. Voltage can be changed in 51 | 9-12 V range, but the value of the sysfs attribute is 52 | always in 0-255 range (1 = 9V, 255 = 12V). Setting the 53 | attribute to 0 turns off the fan completely. 54 | pwm[1-3]_enable 1 if the fan can be controlled by writing to the 55 | corresponding pwm* attribute, 0 otherwise. The device 56 | can control only the fans it detected itself, so the 57 | attribute is read-only. 58 | pwm[1-3]_mode Read-only, 1 for PWM-controlled fans, 0 for other fans 59 | (or if no fan connected). 60 | update_interval The interval at which all inputs are updated (in 61 | milliseconds). The default is 1000ms. Minimum is 250ms. 62 | ======================= ======================================================== 63 | -------------------------------------------------------------------------------- /Documentation/hwmon/nzxt-smartdevice.rst: -------------------------------------------------------------------------------- 1 | .. SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | Kernel driver nzxt-smartdevice 4 | ============================== 5 | 6 | Supported devices: 7 | 8 | * NZXT Grid+ V3 9 | * NZXT Smart Device (V1) 10 | 11 | Author: Jonas Malaco 12 | 13 | Description 14 | ----------- 15 | 16 | This driver enables hardware monitoring support for NZXT Smart Device (V1) and 17 | Grid+ V3 fan hubs. For each fan channel, three sensors are available: speed, 18 | current and voltage. The Smart Device features three fan channels; the Grid+ 19 | V3, six. 20 | 21 | Fans, when detected, can be controlled with manual PWM. The control mode (DC 22 | or PWM) is inferred automatically by device. 23 | 24 | Addressable RGB LED control, supported by the Smart Device, is not exposed. 25 | This feature can be found in existing user-space tools (e.g. `liquidctl`_). 26 | 27 | .. _liquidctl: https://github.com/liquidctl/liquidctl 28 | 29 | Usage Notes 30 | ----------- 31 | 32 | As these are USB HIDs, the driver can be loaded automatically by the kernel and 33 | supports hot swapping. 34 | 35 | Sysfs entries 36 | ------------- 37 | 38 | ======================= ======= ================================================ 39 | fan[1-*]_input RO Fan speed (in rpm) 40 | curr[1-*]_input RO Fan current draw (in milliampere) 41 | in[0-*]_input RO Fan supply voltage (in millivolt) 42 | pwm[1-*] RW Fan target duty cycle (integer from 0 to 255) 43 | pwm[1-*]_enable RO Fan control method (0: disabled; 1: manual) 44 | pwm[1-*]_mode RO Fan control mode (0: DC; 1: PWM) 45 | ======================= ======= ================================================ 46 | 47 | PWM duty cycles cannot actually be read from the device, but the driver keeps 48 | track of previously set PWM values and reports those, to avoid breaking the 49 | user-space expectation that `pwm[1-*]` is readable. These values are only 50 | accurate as long as no PWM change has been issued in a way that bypasses the 51 | driver (e.g. `liquidctl`). 52 | 53 | Whether fan control is disabled or in manual mode cannot be changed at runtime; 54 | this is always selected automatically by the device during its initialization 55 | routine. Once again to avoid breaking the user-space expectation that 56 | `pwm[1-*]_enable` is writable, the driver accepts but ignores any attempts to 57 | change it. This is equivalent to the device immediately reverting back to the 58 | previously selected method. 59 | .. FIXME remove, probably 60 | 61 | .. TODO probe resets pwm[1-*] to 102 (40%) 62 | .. TODO explain the initialization routine? 63 | -------------------------------------------------------------------------------- /Documentation/internal/1e71:170e.lsusb: -------------------------------------------------------------------------------- 1 | 2 | Bus 001 Device 004: ID 1e71:170e NZXT 3 | Device Descriptor: 4 | bLength 18 5 | bDescriptorType 1 6 | bcdUSB 2.00 7 | bDeviceClass 0 8 | bDeviceSubClass 0 9 | bDeviceProtocol 0 10 | bMaxPacketSize0 64 11 | idVendor 0x1e71 NZXT 12 | idProduct 0x170e 13 | bcdDevice 2.00 14 | iManufacturer 1 NZXT.-Inc. 15 | iProduct 2 NZXT USB Device 16 | iSerial 3 49874481333 17 | bNumConfigurations 1 18 | Configuration Descriptor: 19 | bLength 9 20 | bDescriptorType 2 21 | wTotalLength 0x0029 22 | bNumInterfaces 1 23 | bConfigurationValue 1 24 | iConfiguration 0 25 | bmAttributes 0xc0 26 | Self Powered 27 | MaxPower 100mA 28 | Interface Descriptor: 29 | bLength 9 30 | bDescriptorType 4 31 | bInterfaceNumber 0 32 | bAlternateSetting 0 33 | bNumEndpoints 2 34 | bInterfaceClass 3 Human Interface Device 35 | bInterfaceSubClass 0 36 | bInterfaceProtocol 0 37 | iInterface 0 38 | HID Device Descriptor: 39 | bLength 9 40 | bDescriptorType 33 41 | bcdHID 1.10 42 | bCountryCode 0 Not supported 43 | bNumDescriptors 1 44 | bDescriptorType 34 Report 45 | wDescriptorLength 143 46 | Report Descriptor: (length is 143) 47 | Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 48 | (null) 49 | Item(Local ): Usage, data= [ 0x01 ] 1 50 | (null) 51 | Item(Main ): Collection, data= [ 0x01 ] 1 52 | Application 53 | Item(Global): Report ID, data= [ 0x01 ] 1 54 | Item(Local ): Usage, data= [ 0x01 ] 1 55 | (null) 56 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 57 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 58 | Item(Global): Report Size, data= [ 0x08 ] 8 59 | Item(Global): Report Count, data= [ 0x01 ] 1 60 | Item(Main ): Feature, data= [ 0x82 ] 130 61 | Data Variable Absolute No_Wrap Linear 62 | Preferred_State No_Null_Position Volatile Bitfield 63 | Item(Global): Report ID, data= [ 0x01 ] 1 64 | Item(Local ): Usage, data= [ 0x01 ] 1 65 | (null) 66 | Item(Main ): Output, data= [ 0x82 ] 130 67 | Data Variable Absolute No_Wrap Linear 68 | Preferred_State No_Null_Position Volatile Bitfield 69 | Item(Global): Report ID, data= [ 0x02 ] 2 70 | Item(Local ): Usage, data= [ 0x02 ] 2 71 | (null) 72 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 73 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 74 | Item(Global): Report Size, data= [ 0x08 ] 8 75 | Item(Global): Report Count, data= [ 0x40 ] 64 76 | Item(Main ): Feature, data= [ 0x82 ] 130 77 | Data Variable Absolute No_Wrap Linear 78 | Preferred_State No_Null_Position Volatile Bitfield 79 | Item(Global): Report ID, data= [ 0x02 ] 2 80 | Item(Local ): Usage, data= [ 0x02 ] 2 81 | (null) 82 | Item(Main ): Output, data= [ 0x82 ] 130 83 | Data Variable Absolute No_Wrap Linear 84 | Preferred_State No_Null_Position Volatile Bitfield 85 | Item(Global): Report ID, data= [ 0x03 ] 3 86 | Item(Local ): Usage, data= [ 0x03 ] 3 87 | (null) 88 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 89 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 90 | Item(Global): Report Size, data= [ 0x08 ] 8 91 | Item(Global): Report Count, data= [ 0x40 ] 64 92 | Item(Main ): Feature, data= [ 0x82 ] 130 93 | Data Variable Absolute No_Wrap Linear 94 | Preferred_State No_Null_Position Volatile Bitfield 95 | Item(Global): Report ID, data= [ 0x03 ] 3 96 | Item(Local ): Usage, data= [ 0x03 ] 3 97 | (null) 98 | Item(Main ): Output, data= [ 0x82 ] 130 99 | Data Variable Absolute No_Wrap Linear 100 | Preferred_State No_Null_Position Volatile Bitfield 101 | Item(Global): Report ID, data= [ 0xf0 ] 240 102 | Item(Local ): Usage, data= [ 0x04 ] 4 103 | (null) 104 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 105 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 106 | Item(Global): Report Size, data= [ 0x08 ] 8 107 | Item(Global): Report Count, data= [ 0x40 ] 64 108 | Item(Main ): Feature, data= [ 0x82 ] 130 109 | Data Variable Absolute No_Wrap Linear 110 | Preferred_State No_Null_Position Volatile Bitfield 111 | Item(Global): Report ID, data= [ 0xf0 ] 240 112 | Item(Local ): Usage, data= [ 0x04 ] 4 113 | (null) 114 | Item(Main ): Output, data= [ 0x82 ] 130 115 | Data Variable Absolute No_Wrap Linear 116 | Preferred_State No_Null_Position Volatile Bitfield 117 | Item(Global): Report ID, data= [ 0x10 ] 16 118 | Item(Local ): Usage, data= [ 0x05 ] 5 119 | (null) 120 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 121 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 122 | Item(Global): Report Size, data= [ 0x08 ] 8 123 | Item(Global): Report Count, data= [ 0x40 ] 64 124 | Item(Main ): Feature, data= [ 0x82 ] 130 125 | Data Variable Absolute No_Wrap Linear 126 | Preferred_State No_Null_Position Volatile Bitfield 127 | Item(Global): Report ID, data= [ 0x10 ] 16 128 | Item(Local ): Usage, data= [ 0x05 ] 5 129 | (null) 130 | Item(Main ): Output, data= [ 0x82 ] 130 131 | Data Variable Absolute No_Wrap Linear 132 | Preferred_State No_Null_Position Volatile Bitfield 133 | Item(Global): Report ID, data= [ 0xf1 ] 241 134 | Item(Local ): Usage, data= [ 0x06 ] 6 135 | (null) 136 | Item(Global): Report Size, data= [ 0x08 ] 8 137 | Item(Global): Report Count, data= [ 0x3f ] 63 138 | Item(Main ): Input, data= [ 0x82 ] 130 139 | Data Variable Absolute No_Wrap Linear 140 | Preferred_State No_Null_Position Volatile Bitfield 141 | Item(Global): Report ID, data= [ 0x11 ] 17 142 | Item(Local ): Usage, data= [ 0x07 ] 7 143 | (null) 144 | Item(Global): Report Size, data= [ 0x08 ] 8 145 | Item(Global): Report Count, data= [ 0x3f ] 63 146 | Item(Main ): Input, data= [ 0x82 ] 130 147 | Data Variable Absolute No_Wrap Linear 148 | Preferred_State No_Null_Position Volatile Bitfield 149 | Item(Global): Report ID, data= [ 0x04 ] 4 150 | Item(Local ): Usage, data= [ 0x08 ] 8 151 | (null) 152 | Item(Global): Report Size, data= [ 0x08 ] 8 153 | Item(Global): Report Count, data= [ 0x10 ] 16 154 | Item(Main ): Input, data= [ 0x82 ] 130 155 | Data Variable Absolute No_Wrap Linear 156 | Preferred_State No_Null_Position Volatile Bitfield 157 | Item(Main ): End Collection, data=none 158 | Endpoint Descriptor: 159 | bLength 7 160 | bDescriptorType 5 161 | bEndpointAddress 0x81 EP 1 IN 162 | bmAttributes 3 163 | Transfer Type Interrupt 164 | Synch Type None 165 | Usage Type Data 166 | wMaxPacketSize 0x0040 1x 64 bytes 167 | bInterval 1 168 | Endpoint Descriptor: 169 | bLength 7 170 | bDescriptorType 5 171 | bEndpointAddress 0x01 EP 1 OUT 172 | bmAttributes 3 173 | Transfer Type Interrupt 174 | Synch Type None 175 | Usage Type Data 176 | wMaxPacketSize 0x0040 1x 64 bytes 177 | bInterval 1 178 | Device Status: 0x0000 179 | (Bus Powered) 180 | -------------------------------------------------------------------------------- /Documentation/internal/1e71:170e.rdesc: -------------------------------------------------------------------------------- 1 | 06 00 ff 09 01 a1 01 85 01 09 01 15 00 26 ff 00 75 08 95 01 b1 82 85 01 09 01 91 82 85 02 09 02 15 00 26 ff 00 75 08 95 40 b1 82 85 02 09 02 91 82 85 03 09 03 15 00 26 ff 00 75 08 95 40 b1 82 85 03 09 03 91 82 85 f0 09 04 15 00 26 ff 00 75 08 95 40 b1 82 85 f0 09 04 91 82 85 10 09 05 15 00 26 ff 00 75 08 95 40 b1 82 85 10 09 05 91 82 85 f1 09 06 75 08 95 3f 81 82 85 11 09 07 75 08 95 3f 81 82 85 04 09 08 75 08 95 10 81 82 c0 2 | 3 | INPUT(241)[INPUT] 4 | Field(0) 5 | Application(ff00.0001) 6 | Usage(63) 7 | ff00.0006 8 | ff00.0006 9 | ff00.0006 10 | ff00.0006 11 | ff00.0006 12 | ff00.0006 13 | ff00.0006 14 | ff00.0006 15 | ff00.0006 16 | ff00.0006 17 | ff00.0006 18 | ff00.0006 19 | ff00.0006 20 | ff00.0006 21 | ff00.0006 22 | ff00.0006 23 | ff00.0006 24 | ff00.0006 25 | ff00.0006 26 | ff00.0006 27 | ff00.0006 28 | ff00.0006 29 | ff00.0006 30 | ff00.0006 31 | ff00.0006 32 | ff00.0006 33 | ff00.0006 34 | ff00.0006 35 | ff00.0006 36 | ff00.0006 37 | ff00.0006 38 | ff00.0006 39 | ff00.0006 40 | ff00.0006 41 | ff00.0006 42 | ff00.0006 43 | ff00.0006 44 | ff00.0006 45 | ff00.0006 46 | ff00.0006 47 | ff00.0006 48 | ff00.0006 49 | ff00.0006 50 | ff00.0006 51 | ff00.0006 52 | ff00.0006 53 | ff00.0006 54 | ff00.0006 55 | ff00.0006 56 | ff00.0006 57 | ff00.0006 58 | ff00.0006 59 | ff00.0006 60 | ff00.0006 61 | ff00.0006 62 | ff00.0006 63 | ff00.0006 64 | ff00.0006 65 | ff00.0006 66 | ff00.0006 67 | ff00.0006 68 | ff00.0006 69 | ff00.0006 70 | Logical Minimum(0) 71 | Logical Maximum(255) 72 | Report Size(8) 73 | Report Count(63) 74 | Report Offset(0) 75 | Flags( Variable Absolute Volatile ) 76 | INPUT(17)[INPUT] 77 | Field(0) 78 | Application(ff00.0001) 79 | Usage(63) 80 | ff00.0007 81 | ff00.0007 82 | ff00.0007 83 | ff00.0007 84 | ff00.0007 85 | ff00.0007 86 | ff00.0007 87 | ff00.0007 88 | ff00.0007 89 | ff00.0007 90 | ff00.0007 91 | ff00.0007 92 | ff00.0007 93 | ff00.0007 94 | ff00.0007 95 | ff00.0007 96 | ff00.0007 97 | ff00.0007 98 | ff00.0007 99 | ff00.0007 100 | ff00.0007 101 | ff00.0007 102 | ff00.0007 103 | ff00.0007 104 | ff00.0007 105 | ff00.0007 106 | ff00.0007 107 | ff00.0007 108 | ff00.0007 109 | ff00.0007 110 | ff00.0007 111 | ff00.0007 112 | ff00.0007 113 | ff00.0007 114 | ff00.0007 115 | ff00.0007 116 | ff00.0007 117 | ff00.0007 118 | ff00.0007 119 | ff00.0007 120 | ff00.0007 121 | ff00.0007 122 | ff00.0007 123 | ff00.0007 124 | ff00.0007 125 | ff00.0007 126 | ff00.0007 127 | ff00.0007 128 | ff00.0007 129 | ff00.0007 130 | ff00.0007 131 | ff00.0007 132 | ff00.0007 133 | ff00.0007 134 | ff00.0007 135 | ff00.0007 136 | ff00.0007 137 | ff00.0007 138 | ff00.0007 139 | ff00.0007 140 | ff00.0007 141 | ff00.0007 142 | ff00.0007 143 | Logical Minimum(0) 144 | Logical Maximum(255) 145 | Report Size(8) 146 | Report Count(63) 147 | Report Offset(0) 148 | Flags( Variable Absolute Volatile ) 149 | INPUT(4)[INPUT] 150 | Field(0) 151 | Application(ff00.0001) 152 | Usage(16) 153 | ff00.0008 154 | ff00.0008 155 | ff00.0008 156 | ff00.0008 157 | ff00.0008 158 | ff00.0008 159 | ff00.0008 160 | ff00.0008 161 | ff00.0008 162 | ff00.0008 163 | ff00.0008 164 | ff00.0008 165 | ff00.0008 166 | ff00.0008 167 | ff00.0008 168 | ff00.0008 169 | Logical Minimum(0) 170 | Logical Maximum(255) 171 | Report Size(8) 172 | Report Count(16) 173 | Report Offset(0) 174 | Flags( Variable Absolute Volatile ) 175 | OUTPUT(1)[OUTPUT] 176 | Field(0) 177 | Application(ff00.0001) 178 | Usage(1) 179 | ff00.0001 180 | Logical Minimum(0) 181 | Logical Maximum(255) 182 | Report Size(8) 183 | Report Count(1) 184 | Report Offset(0) 185 | Flags( Variable Absolute Volatile ) 186 | OUTPUT(2)[OUTPUT] 187 | Field(0) 188 | Application(ff00.0001) 189 | Usage(64) 190 | ff00.0002 191 | ff00.0002 192 | ff00.0002 193 | ff00.0002 194 | ff00.0002 195 | ff00.0002 196 | ff00.0002 197 | ff00.0002 198 | ff00.0002 199 | ff00.0002 200 | ff00.0002 201 | ff00.0002 202 | ff00.0002 203 | ff00.0002 204 | ff00.0002 205 | ff00.0002 206 | ff00.0002 207 | ff00.0002 208 | ff00.0002 209 | ff00.0002 210 | ff00.0002 211 | ff00.0002 212 | ff00.0002 213 | ff00.0002 214 | ff00.0002 215 | ff00.0002 216 | ff00.0002 217 | ff00.0002 218 | ff00.0002 219 | ff00.0002 220 | ff00.0002 221 | ff00.0002 222 | ff00.0002 223 | ff00.0002 224 | ff00.0002 225 | ff00.0002 226 | ff00.0002 227 | ff00.0002 228 | ff00.0002 229 | ff00.0002 230 | ff00.0002 231 | ff00.0002 232 | ff00.0002 233 | ff00.0002 234 | ff00.0002 235 | ff00.0002 236 | ff00.0002 237 | ff00.0002 238 | ff00.0002 239 | ff00.0002 240 | ff00.0002 241 | ff00.0002 242 | ff00.0002 243 | ff00.0002 244 | ff00.0002 245 | ff00.0002 246 | ff00.0002 247 | ff00.0002 248 | ff00.0002 249 | ff00.0002 250 | ff00.0002 251 | ff00.0002 252 | ff00.0002 253 | ff00.0002 254 | Logical Minimum(0) 255 | Logical Maximum(255) 256 | Report Size(8) 257 | Report Count(64) 258 | Report Offset(0) 259 | Flags( Variable Absolute Volatile ) 260 | OUTPUT(3)[OUTPUT] 261 | Field(0) 262 | Application(ff00.0001) 263 | Usage(64) 264 | ff00.0003 265 | ff00.0003 266 | ff00.0003 267 | ff00.0003 268 | ff00.0003 269 | ff00.0003 270 | ff00.0003 271 | ff00.0003 272 | ff00.0003 273 | ff00.0003 274 | ff00.0003 275 | ff00.0003 276 | ff00.0003 277 | ff00.0003 278 | ff00.0003 279 | ff00.0003 280 | ff00.0003 281 | ff00.0003 282 | ff00.0003 283 | ff00.0003 284 | ff00.0003 285 | ff00.0003 286 | ff00.0003 287 | ff00.0003 288 | ff00.0003 289 | ff00.0003 290 | ff00.0003 291 | ff00.0003 292 | ff00.0003 293 | ff00.0003 294 | ff00.0003 295 | ff00.0003 296 | ff00.0003 297 | ff00.0003 298 | ff00.0003 299 | ff00.0003 300 | ff00.0003 301 | ff00.0003 302 | ff00.0003 303 | ff00.0003 304 | ff00.0003 305 | ff00.0003 306 | ff00.0003 307 | ff00.0003 308 | ff00.0003 309 | ff00.0003 310 | ff00.0003 311 | ff00.0003 312 | ff00.0003 313 | ff00.0003 314 | ff00.0003 315 | ff00.0003 316 | ff00.0003 317 | ff00.0003 318 | ff00.0003 319 | ff00.0003 320 | ff00.0003 321 | ff00.0003 322 | ff00.0003 323 | ff00.0003 324 | ff00.0003 325 | ff00.0003 326 | ff00.0003 327 | ff00.0003 328 | Logical Minimum(0) 329 | Logical Maximum(255) 330 | Report Size(8) 331 | Report Count(64) 332 | Report Offset(0) 333 | Flags( Variable Absolute Volatile ) 334 | OUTPUT(240)[OUTPUT] 335 | Field(0) 336 | Application(ff00.0001) 337 | Usage(64) 338 | ff00.0004 339 | ff00.0004 340 | ff00.0004 341 | ff00.0004 342 | ff00.0004 343 | ff00.0004 344 | ff00.0004 345 | ff00.0004 346 | ff00.0004 347 | ff00.0004 348 | ff00.0004 349 | ff00.0004 350 | ff00.0004 351 | ff00.0004 352 | ff00.0004 353 | ff00.0004 354 | ff00.0004 355 | ff00.0004 356 | ff00.0004 357 | ff00.0004 358 | ff00.0004 359 | ff00.0004 360 | ff00.0004 361 | ff00.0004 362 | ff00.0004 363 | ff00.0004 364 | ff00.0004 365 | ff00.0004 366 | ff00.0004 367 | ff00.0004 368 | ff00.0004 369 | ff00.0004 370 | ff00.0004 371 | ff00.0004 372 | ff00.0004 373 | ff00.0004 374 | ff00.0004 375 | ff00.0004 376 | ff00.0004 377 | ff00.0004 378 | ff00.0004 379 | ff00.0004 380 | ff00.0004 381 | ff00.0004 382 | ff00.0004 383 | ff00.0004 384 | ff00.0004 385 | ff00.0004 386 | ff00.0004 387 | ff00.0004 388 | ff00.0004 389 | ff00.0004 390 | ff00.0004 391 | ff00.0004 392 | ff00.0004 393 | ff00.0004 394 | ff00.0004 395 | ff00.0004 396 | ff00.0004 397 | ff00.0004 398 | ff00.0004 399 | ff00.0004 400 | ff00.0004 401 | ff00.0004 402 | Logical Minimum(0) 403 | Logical Maximum(255) 404 | Report Size(8) 405 | Report Count(64) 406 | Report Offset(0) 407 | Flags( Variable Absolute Volatile ) 408 | OUTPUT(16)[OUTPUT] 409 | Field(0) 410 | Application(ff00.0001) 411 | Usage(64) 412 | ff00.0005 413 | ff00.0005 414 | ff00.0005 415 | ff00.0005 416 | ff00.0005 417 | ff00.0005 418 | ff00.0005 419 | ff00.0005 420 | ff00.0005 421 | ff00.0005 422 | ff00.0005 423 | ff00.0005 424 | ff00.0005 425 | ff00.0005 426 | ff00.0005 427 | ff00.0005 428 | ff00.0005 429 | ff00.0005 430 | ff00.0005 431 | ff00.0005 432 | ff00.0005 433 | ff00.0005 434 | ff00.0005 435 | ff00.0005 436 | ff00.0005 437 | ff00.0005 438 | ff00.0005 439 | ff00.0005 440 | ff00.0005 441 | ff00.0005 442 | ff00.0005 443 | ff00.0005 444 | ff00.0005 445 | ff00.0005 446 | ff00.0005 447 | ff00.0005 448 | ff00.0005 449 | ff00.0005 450 | ff00.0005 451 | ff00.0005 452 | ff00.0005 453 | ff00.0005 454 | ff00.0005 455 | ff00.0005 456 | ff00.0005 457 | ff00.0005 458 | ff00.0005 459 | ff00.0005 460 | ff00.0005 461 | ff00.0005 462 | ff00.0005 463 | ff00.0005 464 | ff00.0005 465 | ff00.0005 466 | ff00.0005 467 | ff00.0005 468 | ff00.0005 469 | ff00.0005 470 | ff00.0005 471 | ff00.0005 472 | ff00.0005 473 | ff00.0005 474 | ff00.0005 475 | ff00.0005 476 | Logical Minimum(0) 477 | Logical Maximum(255) 478 | Report Size(8) 479 | Report Count(64) 480 | Report Offset(0) 481 | Flags( Variable Absolute Volatile ) 482 | FEATURE(1)[FEATURE] 483 | Field(0) 484 | Application(ff00.0001) 485 | Usage(1) 486 | ff00.0001 487 | Logical Minimum(0) 488 | Logical Maximum(255) 489 | Report Size(8) 490 | Report Count(1) 491 | Report Offset(0) 492 | Flags( Variable Absolute Volatile ) 493 | FEATURE(2)[FEATURE] 494 | Field(0) 495 | Application(ff00.0001) 496 | Usage(64) 497 | ff00.0002 498 | ff00.0002 499 | ff00.0002 500 | ff00.0002 501 | ff00.0002 502 | ff00.0002 503 | ff00.0002 504 | ff00.0002 505 | ff00.0002 506 | ff00.0002 507 | ff00.0002 508 | ff00.0002 509 | ff00.0002 510 | ff00.0002 511 | ff00.0002 512 | ff00.0002 513 | ff00.0002 514 | ff00.0002 515 | ff00.0002 516 | ff00.0002 517 | ff00.0002 518 | ff00.0002 519 | ff00.0002 520 | ff00.0002 521 | ff00.0002 522 | ff00.0002 523 | ff00.0002 524 | ff00.0002 525 | ff00.0002 526 | ff00.0002 527 | ff00.0002 528 | ff00.0002 529 | ff00.0002 530 | ff00.0002 531 | ff00.0002 532 | ff00.0002 533 | ff00.0002 534 | ff00.0002 535 | ff00.0002 536 | ff00.0002 537 | ff00.0002 538 | ff00.0002 539 | ff00.0002 540 | ff00.0002 541 | ff00.0002 542 | ff00.0002 543 | ff00.0002 544 | ff00.0002 545 | ff00.0002 546 | ff00.0002 547 | ff00.0002 548 | ff00.0002 549 | ff00.0002 550 | ff00.0002 551 | ff00.0002 552 | ff00.0002 553 | ff00.0002 554 | ff00.0002 555 | ff00.0002 556 | ff00.0002 557 | ff00.0002 558 | ff00.0002 559 | ff00.0002 560 | ff00.0002 561 | Logical Minimum(0) 562 | Logical Maximum(255) 563 | Report Size(8) 564 | Report Count(64) 565 | Report Offset(0) 566 | Flags( Variable Absolute Volatile ) 567 | FEATURE(3)[FEATURE] 568 | Field(0) 569 | Application(ff00.0001) 570 | Usage(64) 571 | ff00.0003 572 | ff00.0003 573 | ff00.0003 574 | ff00.0003 575 | ff00.0003 576 | ff00.0003 577 | ff00.0003 578 | ff00.0003 579 | ff00.0003 580 | ff00.0003 581 | ff00.0003 582 | ff00.0003 583 | ff00.0003 584 | ff00.0003 585 | ff00.0003 586 | ff00.0003 587 | ff00.0003 588 | ff00.0003 589 | ff00.0003 590 | ff00.0003 591 | ff00.0003 592 | ff00.0003 593 | ff00.0003 594 | ff00.0003 595 | ff00.0003 596 | ff00.0003 597 | ff00.0003 598 | ff00.0003 599 | ff00.0003 600 | ff00.0003 601 | ff00.0003 602 | ff00.0003 603 | ff00.0003 604 | ff00.0003 605 | ff00.0003 606 | ff00.0003 607 | ff00.0003 608 | ff00.0003 609 | ff00.0003 610 | ff00.0003 611 | ff00.0003 612 | ff00.0003 613 | ff00.0003 614 | ff00.0003 615 | ff00.0003 616 | ff00.0003 617 | ff00.0003 618 | ff00.0003 619 | ff00.0003 620 | ff00.0003 621 | ff00.0003 622 | ff00.0003 623 | ff00.0003 624 | ff00.0003 625 | ff00.0003 626 | ff00.0003 627 | ff00.0003 628 | ff00.0003 629 | ff00.0003 630 | ff00.0003 631 | ff00.0003 632 | ff00.0003 633 | ff00.0003 634 | ff00.0003 635 | Logical Minimum(0) 636 | Logical Maximum(255) 637 | Report Size(8) 638 | Report Count(64) 639 | Report Offset(0) 640 | Flags( Variable Absolute Volatile ) 641 | FEATURE(240)[FEATURE] 642 | Field(0) 643 | Application(ff00.0001) 644 | Usage(64) 645 | ff00.0004 646 | ff00.0004 647 | ff00.0004 648 | ff00.0004 649 | ff00.0004 650 | ff00.0004 651 | ff00.0004 652 | ff00.0004 653 | ff00.0004 654 | ff00.0004 655 | ff00.0004 656 | ff00.0004 657 | ff00.0004 658 | ff00.0004 659 | ff00.0004 660 | ff00.0004 661 | ff00.0004 662 | ff00.0004 663 | ff00.0004 664 | ff00.0004 665 | ff00.0004 666 | ff00.0004 667 | ff00.0004 668 | ff00.0004 669 | ff00.0004 670 | ff00.0004 671 | ff00.0004 672 | ff00.0004 673 | ff00.0004 674 | ff00.0004 675 | ff00.0004 676 | ff00.0004 677 | ff00.0004 678 | ff00.0004 679 | ff00.0004 680 | ff00.0004 681 | ff00.0004 682 | ff00.0004 683 | ff00.0004 684 | ff00.0004 685 | ff00.0004 686 | ff00.0004 687 | ff00.0004 688 | ff00.0004 689 | ff00.0004 690 | ff00.0004 691 | ff00.0004 692 | ff00.0004 693 | ff00.0004 694 | ff00.0004 695 | ff00.0004 696 | ff00.0004 697 | ff00.0004 698 | ff00.0004 699 | ff00.0004 700 | ff00.0004 701 | ff00.0004 702 | ff00.0004 703 | ff00.0004 704 | ff00.0004 705 | ff00.0004 706 | ff00.0004 707 | ff00.0004 708 | ff00.0004 709 | Logical Minimum(0) 710 | Logical Maximum(255) 711 | Report Size(8) 712 | Report Count(64) 713 | Report Offset(0) 714 | Flags( Variable Absolute Volatile ) 715 | FEATURE(16)[FEATURE] 716 | Field(0) 717 | Application(ff00.0001) 718 | Usage(64) 719 | ff00.0005 720 | ff00.0005 721 | ff00.0005 722 | ff00.0005 723 | ff00.0005 724 | ff00.0005 725 | ff00.0005 726 | ff00.0005 727 | ff00.0005 728 | ff00.0005 729 | ff00.0005 730 | ff00.0005 731 | ff00.0005 732 | ff00.0005 733 | ff00.0005 734 | ff00.0005 735 | ff00.0005 736 | ff00.0005 737 | ff00.0005 738 | ff00.0005 739 | ff00.0005 740 | ff00.0005 741 | ff00.0005 742 | ff00.0005 743 | ff00.0005 744 | ff00.0005 745 | ff00.0005 746 | ff00.0005 747 | ff00.0005 748 | ff00.0005 749 | ff00.0005 750 | ff00.0005 751 | ff00.0005 752 | ff00.0005 753 | ff00.0005 754 | ff00.0005 755 | ff00.0005 756 | ff00.0005 757 | ff00.0005 758 | ff00.0005 759 | ff00.0005 760 | ff00.0005 761 | ff00.0005 762 | ff00.0005 763 | ff00.0005 764 | ff00.0005 765 | ff00.0005 766 | ff00.0005 767 | ff00.0005 768 | ff00.0005 769 | ff00.0005 770 | ff00.0005 771 | ff00.0005 772 | ff00.0005 773 | ff00.0005 774 | ff00.0005 775 | ff00.0005 776 | ff00.0005 777 | ff00.0005 778 | ff00.0005 779 | ff00.0005 780 | ff00.0005 781 | ff00.0005 782 | ff00.0005 783 | Logical Minimum(0) 784 | Logical Maximum(255) 785 | Report Size(8) 786 | Report Count(64) 787 | Report Offset(0) 788 | Flags( Variable Absolute Volatile ) 789 | 790 | ff00.0006 ---> Sync.Report 791 | ff00.0006 ---> Sync.Report 792 | ff00.0006 ---> Sync.Report 793 | ff00.0006 ---> Sync.Report 794 | ff00.0006 ---> Sync.Report 795 | ff00.0006 ---> Sync.Report 796 | ff00.0006 ---> Sync.Report 797 | ff00.0006 ---> Sync.Report 798 | ff00.0006 ---> Sync.Report 799 | ff00.0006 ---> Sync.Report 800 | ff00.0006 ---> Sync.Report 801 | ff00.0006 ---> Sync.Report 802 | ff00.0006 ---> Sync.Report 803 | ff00.0006 ---> Sync.Report 804 | ff00.0006 ---> Sync.Report 805 | ff00.0006 ---> Sync.Report 806 | ff00.0006 ---> Sync.Report 807 | ff00.0006 ---> Sync.Report 808 | ff00.0006 ---> Sync.Report 809 | ff00.0006 ---> Sync.Report 810 | ff00.0006 ---> Sync.Report 811 | ff00.0006 ---> Sync.Report 812 | ff00.0006 ---> Sync.Report 813 | ff00.0006 ---> Sync.Report 814 | ff00.0006 ---> Sync.Report 815 | ff00.0006 ---> Sync.Report 816 | ff00.0006 ---> Sync.Report 817 | ff00.0006 ---> Sync.Report 818 | ff00.0006 ---> Sync.Report 819 | ff00.0006 ---> Sync.Report 820 | ff00.0006 ---> Sync.Report 821 | ff00.0006 ---> Sync.Report 822 | ff00.0006 ---> Sync.Report 823 | ff00.0006 ---> Sync.Report 824 | ff00.0006 ---> Sync.Report 825 | ff00.0006 ---> Sync.Report 826 | ff00.0006 ---> Sync.Report 827 | ff00.0006 ---> Sync.Report 828 | ff00.0006 ---> Sync.Report 829 | ff00.0006 ---> Sync.Report 830 | ff00.0006 ---> Sync.Report 831 | ff00.0006 ---> Sync.Report 832 | ff00.0006 ---> Sync.Report 833 | ff00.0006 ---> Sync.Report 834 | ff00.0006 ---> Sync.Report 835 | ff00.0006 ---> Sync.Report 836 | ff00.0006 ---> Sync.Report 837 | ff00.0006 ---> Sync.Report 838 | ff00.0006 ---> Sync.Report 839 | ff00.0006 ---> Sync.Report 840 | ff00.0006 ---> Sync.Report 841 | ff00.0006 ---> Sync.Report 842 | ff00.0006 ---> Sync.Report 843 | ff00.0006 ---> Sync.Report 844 | ff00.0006 ---> Sync.Report 845 | ff00.0006 ---> Sync.Report 846 | ff00.0006 ---> Sync.Report 847 | ff00.0006 ---> Sync.Report 848 | ff00.0006 ---> Sync.Report 849 | ff00.0006 ---> Sync.Report 850 | ff00.0006 ---> Sync.Report 851 | ff00.0006 ---> Sync.Report 852 | ff00.0006 ---> Sync.Report 853 | ff00.0007 ---> Sync.Report 854 | ff00.0007 ---> Sync.Report 855 | ff00.0007 ---> Sync.Report 856 | ff00.0007 ---> Sync.Report 857 | ff00.0007 ---> Sync.Report 858 | ff00.0007 ---> Sync.Report 859 | ff00.0007 ---> Sync.Report 860 | ff00.0007 ---> Sync.Report 861 | ff00.0007 ---> Sync.Report 862 | ff00.0007 ---> Sync.Report 863 | ff00.0007 ---> Sync.Report 864 | ff00.0007 ---> Sync.Report 865 | ff00.0007 ---> Sync.Report 866 | ff00.0007 ---> Sync.Report 867 | ff00.0007 ---> Sync.Report 868 | ff00.0007 ---> Sync.Report 869 | ff00.0007 ---> Sync.Report 870 | ff00.0007 ---> Sync.Report 871 | ff00.0007 ---> Sync.Report 872 | ff00.0007 ---> Sync.Report 873 | ff00.0007 ---> Sync.Report 874 | ff00.0007 ---> Sync.Report 875 | ff00.0007 ---> Sync.Report 876 | ff00.0007 ---> Sync.Report 877 | ff00.0007 ---> Sync.Report 878 | ff00.0007 ---> Sync.Report 879 | ff00.0007 ---> Sync.Report 880 | ff00.0007 ---> Sync.Report 881 | ff00.0007 ---> Sync.Report 882 | ff00.0007 ---> Sync.Report 883 | ff00.0007 ---> Sync.Report 884 | ff00.0007 ---> Sync.Report 885 | ff00.0007 ---> Sync.Report 886 | ff00.0007 ---> Sync.Report 887 | ff00.0007 ---> Sync.Report 888 | ff00.0007 ---> Sync.Report 889 | ff00.0007 ---> Sync.Report 890 | ff00.0007 ---> Sync.Report 891 | ff00.0007 ---> Sync.Report 892 | ff00.0007 ---> Sync.Report 893 | ff00.0007 ---> Sync.Report 894 | ff00.0007 ---> Sync.Report 895 | ff00.0007 ---> Sync.Report 896 | ff00.0007 ---> Sync.Report 897 | ff00.0007 ---> Sync.Report 898 | ff00.0007 ---> Sync.Report 899 | ff00.0007 ---> Sync.Report 900 | ff00.0007 ---> Sync.Report 901 | ff00.0007 ---> Sync.Report 902 | ff00.0007 ---> Sync.Report 903 | ff00.0007 ---> Sync.Report 904 | ff00.0007 ---> Sync.Report 905 | ff00.0007 ---> Sync.Report 906 | ff00.0007 ---> Sync.Report 907 | ff00.0007 ---> Sync.Report 908 | ff00.0007 ---> Sync.Report 909 | ff00.0007 ---> Sync.Report 910 | ff00.0007 ---> Sync.Report 911 | ff00.0007 ---> Sync.Report 912 | ff00.0007 ---> Sync.Report 913 | ff00.0007 ---> Sync.Report 914 | ff00.0007 ---> Sync.Report 915 | ff00.0007 ---> Sync.Report 916 | ff00.0008 ---> Sync.Report 917 | ff00.0008 ---> Sync.Report 918 | ff00.0008 ---> Sync.Report 919 | ff00.0008 ---> Sync.Report 920 | ff00.0008 ---> Sync.Report 921 | ff00.0008 ---> Sync.Report 922 | ff00.0008 ---> Sync.Report 923 | ff00.0008 ---> Sync.Report 924 | ff00.0008 ---> Sync.Report 925 | ff00.0008 ---> Sync.Report 926 | ff00.0008 ---> Sync.Report 927 | ff00.0008 ---> Sync.Report 928 | ff00.0008 ---> Sync.Report 929 | ff00.0008 ---> Sync.Report 930 | ff00.0008 ---> Sync.Report 931 | ff00.0008 ---> Sync.Report 932 | ff00.0001 ---> Sync.Report 933 | ff00.0002 ---> Sync.Report 934 | ff00.0002 ---> Sync.Report 935 | ff00.0002 ---> Sync.Report 936 | ff00.0002 ---> Sync.Report 937 | ff00.0002 ---> Sync.Report 938 | ff00.0002 ---> Sync.Report 939 | ff00.0002 ---> Sync.Report 940 | ff00.0002 ---> Sync.Report 941 | ff00.0002 ---> Sync.Report 942 | ff00.0002 ---> Sync.Report 943 | ff00.0002 ---> Sync.Report 944 | ff00.0002 ---> Sync.Report 945 | ff00.0002 ---> Sync.Report 946 | ff00.0002 ---> Sync.Report 947 | ff00.0002 ---> Sync.Report 948 | ff00.0002 ---> Sync.Report 949 | ff00.0002 ---> Sync.Report 950 | ff00.0002 ---> Sync.Report 951 | ff00.0002 ---> Sync.Report 952 | ff00.0002 ---> Sync.Report 953 | ff00.0002 ---> Sync.Report 954 | ff00.0002 ---> Sync.Report 955 | ff00.0002 ---> Sync.Report 956 | ff00.0002 ---> Sync.Report 957 | ff00.0002 ---> Sync.Report 958 | ff00.0002 ---> Sync.Report 959 | ff00.0002 ---> Sync.Report 960 | ff00.0002 ---> Sync.Report 961 | ff00.0002 ---> Sync.Report 962 | ff00.0002 ---> Sync.Report 963 | ff00.0002 ---> Sync.Report 964 | ff00.0002 ---> Sync.Report 965 | ff00.0002 ---> Sync.Report 966 | ff00.0002 ---> Sync.Report 967 | ff00.0002 ---> Sync.Report 968 | ff00.0002 ---> Sync.Report 969 | ff00.0002 ---> Sync.Report 970 | ff00.0002 ---> Sync.Report 971 | ff00.0002 ---> Sync.Report 972 | ff00.0002 ---> Sync.Report 973 | ff00.0002 ---> Sync.Report 974 | ff00.0002 ---> Sync.Report 975 | ff00.0002 ---> Sync.Report 976 | ff00.0002 ---> Sync.Report 977 | ff00.0002 ---> Sync.Report 978 | ff00.0002 ---> Sync.Report 979 | ff00.0002 ---> Sync.Report 980 | ff00.0002 ---> Sync.Report 981 | ff00.0002 ---> Sync.Report 982 | ff00.0002 ---> Sync.Report 983 | ff00.0002 ---> Sync.Report 984 | ff00.0002 ---> Sync.Report 985 | ff00.0002 ---> Sync.Report 986 | ff00.0002 ---> Sync.Report 987 | ff00.0002 ---> Sync.Report 988 | ff00.0002 ---> Sync.Report 989 | ff00.0002 ---> Sync.Report 990 | ff00.0002 ---> Sync.Report 991 | ff00.0002 ---> Sync.Report 992 | ff00.0002 ---> Sync.Report 993 | ff00.0002 ---> Sync.Report 994 | ff00.0002 ---> Sync.Report 995 | ff00.0002 ---> Sync.Report 996 | ff00.0002 ---> Sync.Report 997 | ff00.0003 ---> Sync.Report 998 | ff00.0003 ---> Sync.Report 999 | ff00.0003 ---> Sync.Report 1000 | ff00.0003 ---> Sync.Report 1001 | ff00.0003 ---> Sync.Report 1002 | ff00.0003 ---> Sync.Report 1003 | ff00.0003 ---> Sync.Report 1004 | ff00.0003 ---> Sync.Report 1005 | ff00.0003 ---> Sync.Report 1006 | ff00.0003 ---> Sync.Report 1007 | ff00.0003 ---> Sync.Report 1008 | ff00.0003 ---> Sync.Report 1009 | ff00.0003 ---> Sync.Report 1010 | ff00.0003 ---> Sync.Report 1011 | ff00.0003 ---> Sync.Report 1012 | ff00.0003 ---> Sync.Report 1013 | ff00.0003 ---> Sync.Report 1014 | ff00.0003 ---> Sync.Report 1015 | ff00.0003 ---> Sync.Report 1016 | ff00.0003 ---> Sync.Report 1017 | ff00.0003 ---> Sync.Report 1018 | ff00.0003 ---> Sync.Report 1019 | ff00.0003 ---> Sync.Report 1020 | ff00.0003 ---> Sync.Report 1021 | ff00.0003 ---> Sync.Report 1022 | ff00.0003 ---> Sync.Report 1023 | ff00.0003 ---> Sync.Report 1024 | ff00.0003 ---> Sync.Report 1025 | ff00.0003 ---> Sync.Report 1026 | ff00.0003 ---> Sync.Report 1027 | ff00.0003 ---> Sync.Report 1028 | ff00.0003 ---> Sync.Report 1029 | ff00.0003 ---> Sync.Report 1030 | ff00.0003 ---> Sync.Report 1031 | ff00.0003 ---> Sync.Report 1032 | ff00.0003 ---> Sync.Report 1033 | ff00.0003 ---> Sync.Report 1034 | ff00.0003 ---> Sync.Report 1035 | ff00.0003 ---> Sync.Report 1036 | ff00.0003 ---> Sync.Report 1037 | ff00.0003 ---> Sync.Report 1038 | ff00.0003 ---> Sync.Report 1039 | ff00.0003 ---> Sync.Report 1040 | ff00.0003 ---> Sync.Report 1041 | ff00.0003 ---> Sync.Report 1042 | ff00.0003 ---> Sync.Report 1043 | ff00.0003 ---> Sync.Report 1044 | ff00.0003 ---> Sync.Report 1045 | ff00.0003 ---> Sync.Report 1046 | ff00.0003 ---> Sync.Report 1047 | ff00.0003 ---> Sync.Report 1048 | ff00.0003 ---> Sync.Report 1049 | ff00.0003 ---> Sync.Report 1050 | ff00.0003 ---> Sync.Report 1051 | ff00.0003 ---> Sync.Report 1052 | ff00.0003 ---> Sync.Report 1053 | ff00.0003 ---> Sync.Report 1054 | ff00.0003 ---> Sync.Report 1055 | ff00.0003 ---> Sync.Report 1056 | ff00.0003 ---> Sync.Report 1057 | ff00.0003 ---> Sync.Report 1058 | ff00.0003 ---> Sync.Report 1059 | ff00.0003 ---> Sync.Report 1060 | ff00.0003 ---> Sync.Report 1061 | ff00.0004 ---> Sync.Report 1062 | ff00.0004 ---> Sync.Report 1063 | ff00.0004 ---> Sync.Report 1064 | ff00.0004 ---> Sync.Report 1065 | ff00.0004 ---> Sync.Report 1066 | ff00.0004 ---> Sync.Report 1067 | ff00.0004 ---> Sync.Report 1068 | ff00.0004 ---> Sync.Report 1069 | ff00.0004 ---> Sync.Report 1070 | ff00.0004 ---> Sync.Report 1071 | ff00.0004 ---> Sync.Report 1072 | ff00.0004 ---> Sync.Report 1073 | ff00.0004 ---> Sync.Report 1074 | ff00.0004 ---> Sync.Report 1075 | ff00.0004 ---> Sync.Report 1076 | ff00.0004 ---> Sync.Report 1077 | ff00.0004 ---> Sync.Report 1078 | ff00.0004 ---> Sync.Report 1079 | ff00.0004 ---> Sync.Report 1080 | ff00.0004 ---> Sync.Report 1081 | ff00.0004 ---> Sync.Report 1082 | ff00.0004 ---> Sync.Report 1083 | ff00.0004 ---> Sync.Report 1084 | ff00.0004 ---> Sync.Report 1085 | ff00.0004 ---> Sync.Report 1086 | ff00.0004 ---> Sync.Report 1087 | ff00.0004 ---> Sync.Report 1088 | ff00.0004 ---> Sync.Report 1089 | ff00.0004 ---> Sync.Report 1090 | ff00.0004 ---> Sync.Report 1091 | ff00.0004 ---> Sync.Report 1092 | ff00.0004 ---> Sync.Report 1093 | ff00.0004 ---> Sync.Report 1094 | ff00.0004 ---> Sync.Report 1095 | ff00.0004 ---> Sync.Report 1096 | ff00.0004 ---> Sync.Report 1097 | ff00.0004 ---> Sync.Report 1098 | ff00.0004 ---> Sync.Report 1099 | ff00.0004 ---> Sync.Report 1100 | ff00.0004 ---> Sync.Report 1101 | ff00.0004 ---> Sync.Report 1102 | ff00.0004 ---> Sync.Report 1103 | ff00.0004 ---> Sync.Report 1104 | ff00.0004 ---> Sync.Report 1105 | ff00.0004 ---> Sync.Report 1106 | ff00.0004 ---> Sync.Report 1107 | ff00.0004 ---> Sync.Report 1108 | ff00.0004 ---> Sync.Report 1109 | ff00.0004 ---> Sync.Report 1110 | ff00.0004 ---> Sync.Report 1111 | ff00.0004 ---> Sync.Report 1112 | ff00.0004 ---> Sync.Report 1113 | ff00.0004 ---> Sync.Report 1114 | ff00.0004 ---> Sync.Report 1115 | ff00.0004 ---> Sync.Report 1116 | ff00.0004 ---> Sync.Report 1117 | ff00.0004 ---> Sync.Report 1118 | ff00.0004 ---> Sync.Report 1119 | ff00.0004 ---> Sync.Report 1120 | ff00.0004 ---> Sync.Report 1121 | ff00.0004 ---> Sync.Report 1122 | ff00.0004 ---> Sync.Report 1123 | ff00.0004 ---> Sync.Report 1124 | ff00.0004 ---> Sync.Report 1125 | ff00.0005 ---> Sync.Report 1126 | ff00.0005 ---> Sync.Report 1127 | ff00.0005 ---> Sync.Report 1128 | ff00.0005 ---> Sync.Report 1129 | ff00.0005 ---> Sync.Report 1130 | ff00.0005 ---> Sync.Report 1131 | ff00.0005 ---> Sync.Report 1132 | ff00.0005 ---> Sync.Report 1133 | ff00.0005 ---> Sync.Report 1134 | ff00.0005 ---> Sync.Report 1135 | ff00.0005 ---> Sync.Report 1136 | ff00.0005 ---> Sync.Report 1137 | ff00.0005 ---> Sync.Report 1138 | ff00.0005 ---> Sync.Report 1139 | ff00.0005 ---> Sync.Report 1140 | ff00.0005 ---> Sync.Report 1141 | ff00.0005 ---> Sync.Report 1142 | ff00.0005 ---> Sync.Report 1143 | ff00.0005 ---> Sync.Report 1144 | ff00.0005 ---> Sync.Report 1145 | ff00.0005 ---> Sync.Report 1146 | ff00.0005 ---> Sync.Report 1147 | ff00.0005 ---> Sync.Report 1148 | ff00.0005 ---> Sync.Report 1149 | ff00.0005 ---> Sync.Report 1150 | ff00.0005 ---> Sync.Report 1151 | ff00.0005 ---> Sync.Report 1152 | ff00.0005 ---> Sync.Report 1153 | ff00.0005 ---> Sync.Report 1154 | ff00.0005 ---> Sync.Report 1155 | ff00.0005 ---> Sync.Report 1156 | ff00.0005 ---> Sync.Report 1157 | ff00.0005 ---> Sync.Report 1158 | ff00.0005 ---> Sync.Report 1159 | ff00.0005 ---> Sync.Report 1160 | ff00.0005 ---> Sync.Report 1161 | ff00.0005 ---> Sync.Report 1162 | ff00.0005 ---> Sync.Report 1163 | ff00.0005 ---> Sync.Report 1164 | ff00.0005 ---> Sync.Report 1165 | ff00.0005 ---> Sync.Report 1166 | ff00.0005 ---> Sync.Report 1167 | ff00.0005 ---> Sync.Report 1168 | ff00.0005 ---> Sync.Report 1169 | ff00.0005 ---> Sync.Report 1170 | ff00.0005 ---> Sync.Report 1171 | ff00.0005 ---> Sync.Report 1172 | ff00.0005 ---> Sync.Report 1173 | ff00.0005 ---> Sync.Report 1174 | ff00.0005 ---> Sync.Report 1175 | ff00.0005 ---> Sync.Report 1176 | ff00.0005 ---> Sync.Report 1177 | ff00.0005 ---> Sync.Report 1178 | ff00.0005 ---> Sync.Report 1179 | ff00.0005 ---> Sync.Report 1180 | ff00.0005 ---> Sync.Report 1181 | ff00.0005 ---> Sync.Report 1182 | ff00.0005 ---> Sync.Report 1183 | ff00.0005 ---> Sync.Report 1184 | ff00.0005 ---> Sync.Report 1185 | ff00.0005 ---> Sync.Report 1186 | ff00.0005 ---> Sync.Report 1187 | ff00.0005 ---> Sync.Report 1188 | ff00.0005 ---> Sync.Report 1189 | -------------------------------------------------------------------------------- /Documentation/internal/1e71:1714.lsusb: -------------------------------------------------------------------------------- 1 | 2 | Bus 001 Device 005: ID 1e71:1714 NZXT 3 | Device Descriptor: 4 | bLength 18 5 | bDescriptorType 1 6 | bcdUSB 2.00 7 | bDeviceClass 0 8 | bDeviceSubClass 0 9 | bDeviceProtocol 0 10 | bMaxPacketSize0 64 11 | idVendor 0x1e71 NZXT 12 | idProduct 0x1714 13 | bcdDevice 2.00 14 | iManufacturer 1 NZXT.-Inc. 15 | iProduct 2 NZXT USB Device 16 | iSerial 3 497C47533332 17 | bNumConfigurations 1 18 | Configuration Descriptor: 19 | bLength 9 20 | bDescriptorType 2 21 | wTotalLength 0x0029 22 | bNumInterfaces 1 23 | bConfigurationValue 1 24 | iConfiguration 0 25 | bmAttributes 0xc0 26 | Self Powered 27 | MaxPower 100mA 28 | Interface Descriptor: 29 | bLength 9 30 | bDescriptorType 4 31 | bInterfaceNumber 0 32 | bAlternateSetting 0 33 | bNumEndpoints 2 34 | bInterfaceClass 3 Human Interface Device 35 | bInterfaceSubClass 0 36 | bInterfaceProtocol 0 37 | iInterface 0 38 | HID Device Descriptor: 39 | bLength 9 40 | bDescriptorType 33 41 | bcdHID 1.10 42 | bCountryCode 0 Not supported 43 | bNumDescriptors 1 44 | bDescriptorType 34 Report 45 | wDescriptorLength 91 46 | Report Descriptor: (length is 91) 47 | Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 48 | (null) 49 | Item(Local ): Usage, data= [ 0x01 ] 1 50 | (null) 51 | Item(Main ): Collection, data= [ 0x01 ] 1 52 | Application 53 | Item(Global): Report ID, data= [ 0x01 ] 1 54 | Item(Local ): Usage, data= [ 0x01 ] 1 55 | (null) 56 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 57 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 58 | Item(Global): Report Size, data= [ 0x08 ] 8 59 | Item(Global): Report Count, data= [ 0x01 ] 1 60 | Item(Main ): Feature, data= [ 0x82 ] 130 61 | Data Variable Absolute No_Wrap Linear 62 | Preferred_State No_Null_Position Volatile Bitfield 63 | Item(Global): Report ID, data= [ 0x01 ] 1 64 | Item(Local ): Usage, data= [ 0x01 ] 1 65 | (null) 66 | Item(Main ): Output, data= [ 0x82 ] 130 67 | Data Variable Absolute No_Wrap Linear 68 | Preferred_State No_Null_Position Volatile Bitfield 69 | Item(Global): Report ID, data= [ 0x02 ] 2 70 | Item(Local ): Usage, data= [ 0x02 ] 2 71 | (null) 72 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 73 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 74 | Item(Global): Report Size, data= [ 0x08 ] 8 75 | Item(Global): Report Count, data= [ 0x40 ] 64 76 | Item(Main ): Feature, data= [ 0x82 ] 130 77 | Data Variable Absolute No_Wrap Linear 78 | Preferred_State No_Null_Position Volatile Bitfield 79 | Item(Global): Report ID, data= [ 0x02 ] 2 80 | Item(Local ): Usage, data= [ 0x02 ] 2 81 | (null) 82 | Item(Main ): Output, data= [ 0x82 ] 130 83 | Data Variable Absolute No_Wrap Linear 84 | Preferred_State No_Null_Position Volatile Bitfield 85 | Item(Global): Report ID, data= [ 0x03 ] 3 86 | Item(Local ): Usage, data= [ 0x03 ] 3 87 | (null) 88 | Item(Global): Logical Minimum, data= [ 0x00 ] 0 89 | Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 90 | Item(Global): Report Size, data= [ 0x08 ] 8 91 | Item(Global): Report Count, data= [ 0x40 ] 64 92 | Item(Main ): Feature, data= [ 0x82 ] 130 93 | Data Variable Absolute No_Wrap Linear 94 | Preferred_State No_Null_Position Volatile Bitfield 95 | Item(Global): Report ID, data= [ 0x03 ] 3 96 | Item(Local ): Usage, data= [ 0x03 ] 3 97 | (null) 98 | Item(Main ): Output, data= [ 0x82 ] 130 99 | Data Variable Absolute No_Wrap Linear 100 | Preferred_State No_Null_Position Volatile Bitfield 101 | Item(Global): Report ID, data= [ 0x04 ] 4 102 | Item(Local ): Usage, data= [ 0x04 ] 4 103 | (null) 104 | Item(Global): Report Size, data= [ 0x08 ] 8 105 | Item(Global): Report Count, data= [ 0x14 ] 20 106 | Item(Main ): Input, data= [ 0x82 ] 130 107 | Data Variable Absolute No_Wrap Linear 108 | Preferred_State No_Null_Position Volatile Bitfield 109 | Item(Global): Report ID, data= [ 0x05 ] 5 110 | Item(Local ): Usage, data= [ 0x05 ] 5 111 | (null) 112 | Item(Global): Report Size, data= [ 0x08 ] 8 113 | Item(Global): Report Count, data= [ 0x01 ] 1 114 | Item(Main ): Input, data= [ 0x82 ] 130 115 | Data Variable Absolute No_Wrap Linear 116 | Preferred_State No_Null_Position Volatile Bitfield 117 | Item(Main ): End Collection, data=none 118 | Endpoint Descriptor: 119 | bLength 7 120 | bDescriptorType 5 121 | bEndpointAddress 0x81 EP 1 IN 122 | bmAttributes 3 123 | Transfer Type Interrupt 124 | Synch Type None 125 | Usage Type Data 126 | wMaxPacketSize 0x0040 1x 64 bytes 127 | bInterval 1 128 | Endpoint Descriptor: 129 | bLength 7 130 | bDescriptorType 5 131 | bEndpointAddress 0x01 EP 1 OUT 132 | bmAttributes 3 133 | Transfer Type Interrupt 134 | Synch Type None 135 | Usage Type Data 136 | wMaxPacketSize 0x0040 1x 64 bytes 137 | bInterval 1 138 | Device Status: 0x0000 139 | (Bus Powered) 140 | -------------------------------------------------------------------------------- /Documentation/internal/1e71:1714.rdesc: -------------------------------------------------------------------------------- 1 | 06 00 ff 09 01 a1 01 85 01 09 01 15 00 26 ff 00 75 08 95 01 b1 82 85 01 09 01 91 82 85 02 09 02 15 00 26 ff 00 75 08 95 40 b1 82 85 02 09 02 91 82 85 03 09 03 15 00 26 ff 00 75 08 95 40 b1 82 85 03 09 03 91 82 85 04 09 04 75 08 95 14 81 82 85 05 09 05 75 08 95 01 81 82 c0 2 | 3 | INPUT(4)[INPUT] 4 | Field(0) 5 | Application(ff00.0001) 6 | Usage(20) 7 | ff00.0004 8 | ff00.0004 9 | ff00.0004 10 | ff00.0004 11 | ff00.0004 12 | ff00.0004 13 | ff00.0004 14 | ff00.0004 15 | ff00.0004 16 | ff00.0004 17 | ff00.0004 18 | ff00.0004 19 | ff00.0004 20 | ff00.0004 21 | ff00.0004 22 | ff00.0004 23 | ff00.0004 24 | ff00.0004 25 | ff00.0004 26 | ff00.0004 27 | Logical Minimum(0) 28 | Logical Maximum(255) 29 | Report Size(8) 30 | Report Count(20) 31 | Report Offset(0) 32 | Flags( Variable Absolute Volatile ) 33 | INPUT(5)[INPUT] 34 | Field(0) 35 | Application(ff00.0001) 36 | Usage(1) 37 | ff00.0005 38 | Logical Minimum(0) 39 | Logical Maximum(255) 40 | Report Size(8) 41 | Report Count(1) 42 | Report Offset(0) 43 | Flags( Variable Absolute Volatile ) 44 | OUTPUT(1)[OUTPUT] 45 | Field(0) 46 | Application(ff00.0001) 47 | Usage(1) 48 | ff00.0001 49 | Logical Minimum(0) 50 | Logical Maximum(255) 51 | Report Size(8) 52 | Report Count(1) 53 | Report Offset(0) 54 | Flags( Variable Absolute Volatile ) 55 | OUTPUT(2)[OUTPUT] 56 | Field(0) 57 | Application(ff00.0001) 58 | Usage(64) 59 | ff00.0002 60 | ff00.0002 61 | ff00.0002 62 | ff00.0002 63 | ff00.0002 64 | ff00.0002 65 | ff00.0002 66 | ff00.0002 67 | ff00.0002 68 | ff00.0002 69 | ff00.0002 70 | ff00.0002 71 | ff00.0002 72 | ff00.0002 73 | ff00.0002 74 | ff00.0002 75 | ff00.0002 76 | ff00.0002 77 | ff00.0002 78 | ff00.0002 79 | ff00.0002 80 | ff00.0002 81 | ff00.0002 82 | ff00.0002 83 | ff00.0002 84 | ff00.0002 85 | ff00.0002 86 | ff00.0002 87 | ff00.0002 88 | ff00.0002 89 | ff00.0002 90 | ff00.0002 91 | ff00.0002 92 | ff00.0002 93 | ff00.0002 94 | ff00.0002 95 | ff00.0002 96 | ff00.0002 97 | ff00.0002 98 | ff00.0002 99 | ff00.0002 100 | ff00.0002 101 | ff00.0002 102 | ff00.0002 103 | ff00.0002 104 | ff00.0002 105 | ff00.0002 106 | ff00.0002 107 | ff00.0002 108 | ff00.0002 109 | ff00.0002 110 | ff00.0002 111 | ff00.0002 112 | ff00.0002 113 | ff00.0002 114 | ff00.0002 115 | ff00.0002 116 | ff00.0002 117 | ff00.0002 118 | ff00.0002 119 | ff00.0002 120 | ff00.0002 121 | ff00.0002 122 | ff00.0002 123 | Logical Minimum(0) 124 | Logical Maximum(255) 125 | Report Size(8) 126 | Report Count(64) 127 | Report Offset(0) 128 | Flags( Variable Absolute Volatile ) 129 | OUTPUT(3)[OUTPUT] 130 | Field(0) 131 | Application(ff00.0001) 132 | Usage(64) 133 | ff00.0003 134 | ff00.0003 135 | ff00.0003 136 | ff00.0003 137 | ff00.0003 138 | ff00.0003 139 | ff00.0003 140 | ff00.0003 141 | ff00.0003 142 | ff00.0003 143 | ff00.0003 144 | ff00.0003 145 | ff00.0003 146 | ff00.0003 147 | ff00.0003 148 | ff00.0003 149 | ff00.0003 150 | ff00.0003 151 | ff00.0003 152 | ff00.0003 153 | ff00.0003 154 | ff00.0003 155 | ff00.0003 156 | ff00.0003 157 | ff00.0003 158 | ff00.0003 159 | ff00.0003 160 | ff00.0003 161 | ff00.0003 162 | ff00.0003 163 | ff00.0003 164 | ff00.0003 165 | ff00.0003 166 | ff00.0003 167 | ff00.0003 168 | ff00.0003 169 | ff00.0003 170 | ff00.0003 171 | ff00.0003 172 | ff00.0003 173 | ff00.0003 174 | ff00.0003 175 | ff00.0003 176 | ff00.0003 177 | ff00.0003 178 | ff00.0003 179 | ff00.0003 180 | ff00.0003 181 | ff00.0003 182 | ff00.0003 183 | ff00.0003 184 | ff00.0003 185 | ff00.0003 186 | ff00.0003 187 | ff00.0003 188 | ff00.0003 189 | ff00.0003 190 | ff00.0003 191 | ff00.0003 192 | ff00.0003 193 | ff00.0003 194 | ff00.0003 195 | ff00.0003 196 | ff00.0003 197 | Logical Minimum(0) 198 | Logical Maximum(255) 199 | Report Size(8) 200 | Report Count(64) 201 | Report Offset(0) 202 | Flags( Variable Absolute Volatile ) 203 | FEATURE(1)[FEATURE] 204 | Field(0) 205 | Application(ff00.0001) 206 | Usage(1) 207 | ff00.0001 208 | Logical Minimum(0) 209 | Logical Maximum(255) 210 | Report Size(8) 211 | Report Count(1) 212 | Report Offset(0) 213 | Flags( Variable Absolute Volatile ) 214 | FEATURE(2)[FEATURE] 215 | Field(0) 216 | Application(ff00.0001) 217 | Usage(64) 218 | ff00.0002 219 | ff00.0002 220 | ff00.0002 221 | ff00.0002 222 | ff00.0002 223 | ff00.0002 224 | ff00.0002 225 | ff00.0002 226 | ff00.0002 227 | ff00.0002 228 | ff00.0002 229 | ff00.0002 230 | ff00.0002 231 | ff00.0002 232 | ff00.0002 233 | ff00.0002 234 | ff00.0002 235 | ff00.0002 236 | ff00.0002 237 | ff00.0002 238 | ff00.0002 239 | ff00.0002 240 | ff00.0002 241 | ff00.0002 242 | ff00.0002 243 | ff00.0002 244 | ff00.0002 245 | ff00.0002 246 | ff00.0002 247 | ff00.0002 248 | ff00.0002 249 | ff00.0002 250 | ff00.0002 251 | ff00.0002 252 | ff00.0002 253 | ff00.0002 254 | ff00.0002 255 | ff00.0002 256 | ff00.0002 257 | ff00.0002 258 | ff00.0002 259 | ff00.0002 260 | ff00.0002 261 | ff00.0002 262 | ff00.0002 263 | ff00.0002 264 | ff00.0002 265 | ff00.0002 266 | ff00.0002 267 | ff00.0002 268 | ff00.0002 269 | ff00.0002 270 | ff00.0002 271 | ff00.0002 272 | ff00.0002 273 | ff00.0002 274 | ff00.0002 275 | ff00.0002 276 | ff00.0002 277 | ff00.0002 278 | ff00.0002 279 | ff00.0002 280 | ff00.0002 281 | ff00.0002 282 | Logical Minimum(0) 283 | Logical Maximum(255) 284 | Report Size(8) 285 | Report Count(64) 286 | Report Offset(0) 287 | Flags( Variable Absolute Volatile ) 288 | FEATURE(3)[FEATURE] 289 | Field(0) 290 | Application(ff00.0001) 291 | Usage(64) 292 | ff00.0003 293 | ff00.0003 294 | ff00.0003 295 | ff00.0003 296 | ff00.0003 297 | ff00.0003 298 | ff00.0003 299 | ff00.0003 300 | ff00.0003 301 | ff00.0003 302 | ff00.0003 303 | ff00.0003 304 | ff00.0003 305 | ff00.0003 306 | ff00.0003 307 | ff00.0003 308 | ff00.0003 309 | ff00.0003 310 | ff00.0003 311 | ff00.0003 312 | ff00.0003 313 | ff00.0003 314 | ff00.0003 315 | ff00.0003 316 | ff00.0003 317 | ff00.0003 318 | ff00.0003 319 | ff00.0003 320 | ff00.0003 321 | ff00.0003 322 | ff00.0003 323 | ff00.0003 324 | ff00.0003 325 | ff00.0003 326 | ff00.0003 327 | ff00.0003 328 | ff00.0003 329 | ff00.0003 330 | ff00.0003 331 | ff00.0003 332 | ff00.0003 333 | ff00.0003 334 | ff00.0003 335 | ff00.0003 336 | ff00.0003 337 | ff00.0003 338 | ff00.0003 339 | ff00.0003 340 | ff00.0003 341 | ff00.0003 342 | ff00.0003 343 | ff00.0003 344 | ff00.0003 345 | ff00.0003 346 | ff00.0003 347 | ff00.0003 348 | ff00.0003 349 | ff00.0003 350 | ff00.0003 351 | ff00.0003 352 | ff00.0003 353 | ff00.0003 354 | ff00.0003 355 | ff00.0003 356 | Logical Minimum(0) 357 | Logical Maximum(255) 358 | Report Size(8) 359 | Report Count(64) 360 | Report Offset(0) 361 | Flags( Variable Absolute Volatile ) 362 | 363 | ff00.0004 ---> Sync.Report 364 | ff00.0004 ---> Sync.Report 365 | ff00.0004 ---> Sync.Report 366 | ff00.0004 ---> Sync.Report 367 | ff00.0004 ---> Sync.Report 368 | ff00.0004 ---> Sync.Report 369 | ff00.0004 ---> Sync.Report 370 | ff00.0004 ---> Sync.Report 371 | ff00.0004 ---> Sync.Report 372 | ff00.0004 ---> Sync.Report 373 | ff00.0004 ---> Sync.Report 374 | ff00.0004 ---> Sync.Report 375 | ff00.0004 ---> Sync.Report 376 | ff00.0004 ---> Sync.Report 377 | ff00.0004 ---> Sync.Report 378 | ff00.0004 ---> Sync.Report 379 | ff00.0004 ---> Sync.Report 380 | ff00.0004 ---> Sync.Report 381 | ff00.0004 ---> Sync.Report 382 | ff00.0004 ---> Sync.Report 383 | ff00.0005 ---> Sync.Report 384 | ff00.0001 ---> Sync.Report 385 | ff00.0002 ---> Sync.Report 386 | ff00.0002 ---> Sync.Report 387 | ff00.0002 ---> Sync.Report 388 | ff00.0002 ---> Sync.Report 389 | ff00.0002 ---> Sync.Report 390 | ff00.0002 ---> Sync.Report 391 | ff00.0002 ---> Sync.Report 392 | ff00.0002 ---> Sync.Report 393 | ff00.0002 ---> Sync.Report 394 | ff00.0002 ---> Sync.Report 395 | ff00.0002 ---> Sync.Report 396 | ff00.0002 ---> Sync.Report 397 | ff00.0002 ---> Sync.Report 398 | ff00.0002 ---> Sync.Report 399 | ff00.0002 ---> Sync.Report 400 | ff00.0002 ---> Sync.Report 401 | ff00.0002 ---> Sync.Report 402 | ff00.0002 ---> Sync.Report 403 | ff00.0002 ---> Sync.Report 404 | ff00.0002 ---> Sync.Report 405 | ff00.0002 ---> Sync.Report 406 | ff00.0002 ---> Sync.Report 407 | ff00.0002 ---> Sync.Report 408 | ff00.0002 ---> Sync.Report 409 | ff00.0002 ---> Sync.Report 410 | ff00.0002 ---> Sync.Report 411 | ff00.0002 ---> Sync.Report 412 | ff00.0002 ---> Sync.Report 413 | ff00.0002 ---> Sync.Report 414 | ff00.0002 ---> Sync.Report 415 | ff00.0002 ---> Sync.Report 416 | ff00.0002 ---> Sync.Report 417 | ff00.0002 ---> Sync.Report 418 | ff00.0002 ---> Sync.Report 419 | ff00.0002 ---> Sync.Report 420 | ff00.0002 ---> Sync.Report 421 | ff00.0002 ---> Sync.Report 422 | ff00.0002 ---> Sync.Report 423 | ff00.0002 ---> Sync.Report 424 | ff00.0002 ---> Sync.Report 425 | ff00.0002 ---> Sync.Report 426 | ff00.0002 ---> Sync.Report 427 | ff00.0002 ---> Sync.Report 428 | ff00.0002 ---> Sync.Report 429 | ff00.0002 ---> Sync.Report 430 | ff00.0002 ---> Sync.Report 431 | ff00.0002 ---> Sync.Report 432 | ff00.0002 ---> Sync.Report 433 | ff00.0002 ---> Sync.Report 434 | ff00.0002 ---> Sync.Report 435 | ff00.0002 ---> Sync.Report 436 | ff00.0002 ---> Sync.Report 437 | ff00.0002 ---> Sync.Report 438 | ff00.0002 ---> Sync.Report 439 | ff00.0002 ---> Sync.Report 440 | ff00.0002 ---> Sync.Report 441 | ff00.0002 ---> Sync.Report 442 | ff00.0002 ---> Sync.Report 443 | ff00.0002 ---> Sync.Report 444 | ff00.0002 ---> Sync.Report 445 | ff00.0002 ---> Sync.Report 446 | ff00.0002 ---> Sync.Report 447 | ff00.0002 ---> Sync.Report 448 | ff00.0002 ---> Sync.Report 449 | ff00.0003 ---> Sync.Report 450 | ff00.0003 ---> Sync.Report 451 | ff00.0003 ---> Sync.Report 452 | ff00.0003 ---> Sync.Report 453 | ff00.0003 ---> Sync.Report 454 | ff00.0003 ---> Sync.Report 455 | ff00.0003 ---> Sync.Report 456 | ff00.0003 ---> Sync.Report 457 | ff00.0003 ---> Sync.Report 458 | ff00.0003 ---> Sync.Report 459 | ff00.0003 ---> Sync.Report 460 | ff00.0003 ---> Sync.Report 461 | ff00.0003 ---> Sync.Report 462 | ff00.0003 ---> Sync.Report 463 | ff00.0003 ---> Sync.Report 464 | ff00.0003 ---> Sync.Report 465 | ff00.0003 ---> Sync.Report 466 | ff00.0003 ---> Sync.Report 467 | ff00.0003 ---> Sync.Report 468 | ff00.0003 ---> Sync.Report 469 | ff00.0003 ---> Sync.Report 470 | ff00.0003 ---> Sync.Report 471 | ff00.0003 ---> Sync.Report 472 | ff00.0003 ---> Sync.Report 473 | ff00.0003 ---> Sync.Report 474 | ff00.0003 ---> Sync.Report 475 | ff00.0003 ---> Sync.Report 476 | ff00.0003 ---> Sync.Report 477 | ff00.0003 ---> Sync.Report 478 | ff00.0003 ---> Sync.Report 479 | ff00.0003 ---> Sync.Report 480 | ff00.0003 ---> Sync.Report 481 | ff00.0003 ---> Sync.Report 482 | ff00.0003 ---> Sync.Report 483 | ff00.0003 ---> Sync.Report 484 | ff00.0003 ---> Sync.Report 485 | ff00.0003 ---> Sync.Report 486 | ff00.0003 ---> Sync.Report 487 | ff00.0003 ---> Sync.Report 488 | ff00.0003 ---> Sync.Report 489 | ff00.0003 ---> Sync.Report 490 | ff00.0003 ---> Sync.Report 491 | ff00.0003 ---> Sync.Report 492 | ff00.0003 ---> Sync.Report 493 | ff00.0003 ---> Sync.Report 494 | ff00.0003 ---> Sync.Report 495 | ff00.0003 ---> Sync.Report 496 | ff00.0003 ---> Sync.Report 497 | ff00.0003 ---> Sync.Report 498 | ff00.0003 ---> Sync.Report 499 | ff00.0003 ---> Sync.Report 500 | ff00.0003 ---> Sync.Report 501 | ff00.0003 ---> Sync.Report 502 | ff00.0003 ---> Sync.Report 503 | ff00.0003 ---> Sync.Report 504 | ff00.0003 ---> Sync.Report 505 | ff00.0003 ---> Sync.Report 506 | ff00.0003 ---> Sync.Report 507 | ff00.0003 ---> Sync.Report 508 | ff00.0003 ---> Sync.Report 509 | ff00.0003 ---> Sync.Report 510 | ff00.0003 ---> Sync.Report 511 | ff00.0003 ---> Sync.Report 512 | ff00.0003 ---> Sync.Report 513 | -------------------------------------------------------------------------------- /Documentation/internal/notes.md: -------------------------------------------------------------------------------- 1 | # Notes on the Kraken X.2 and Smart Device (V1)/Grid+ V3 drivers 2 | 3 | ## Kraken X.2 4 | 5 | Device has three sensors: coolant temperature, fan(s) speed, and pump speed. 6 | 7 | Good labels: Coolant, Fans, Pump? Probably yes, they are cohesive with 8 | coretemp. 9 | 10 | ### Stuff to do 11 | 12 | #### Critical 13 | 14 | - [x] remove data races 15 | - [x] check for other UB (even ran with UBSAN) 16 | - [x] check for suspend issues (the hwmon device doesn't break if hid hasn't resumed in time) 17 | - [x] fix parsing of the cooling temperature (good enough for now) 18 | 19 | ### Medium 20 | 21 | - [x] no `hwmon_device_unregister` in `*_remove`? 22 | 23 | #### Kernel/hwmon coding style 24 | 25 | - [x] `sizeof(*data_value)` or `sizeof(struct data_type)` (i think the former is less prone to errors) 26 | - [x] `ldata` or `priv`? (the latter is a common idiom in hwmon) 27 | - [x] `devm_hwmon_device_register_with_info` or 28 | `hwmon_device_register_with_info`? (use the latter is if `.remove` is needed) 29 | - [x] `kraken2_device_data` or `kraken2_data` or `kraken2_priv_data`? (the latter is clearer) 30 | 31 | ### Optimizations 32 | 33 | - [ ] try to use `hid_driver.report_table` to only do work for report ID 0x04 34 | (but for some reason `hid_report_id` works with report *types*) 35 | 36 | #### Low severity 37 | 38 | - [x] decide on good names for the modules and the corresponding hwmon devices 39 | - [x] decide on good labels for the sensors 40 | - [x] adjust copyright header 41 | 42 | 43 | ## Smart Device (V1)/Grid+ V3 44 | 45 | Device has several sensors for each fan channel (speed, voltage, current, 46 | control mode), plus a noise pressure level sensor. 47 | 48 | 49 | ## Naming modules and hwmon devices 50 | 51 | Current: 52 | - Kraken X.2 -> nzxt-kraken2 (kraken2) 53 | - Smart Device (V1)/Grid+ V3 -> nzxt-smart-device (smartdevice/gridplus3) 54 | 55 | Future: 56 | - Kraken X.3 -> nzxt-kraken3 (krakenx3) 57 | - Kraken Z.3 -> nzxt-kraken3 (krakenz3) 58 | - Smart Device (V2)/RGB & Fan Controller -> nzxt-smart-device2 (smartdevice2/rgbfancontroller) 59 | 60 | 61 | ## ... 62 | 63 | `memcpy within spin_lock_irqsave` (outdated): 64 | 65 | ``` 66 | 0000000000000130 : 67 | 130: e8 00 00 00 00 call 135 68 | 135: 41 55 push r13 69 | 137: 41 54 push r12 70 | 139: 45 31 e4 xor r12d,r12d 71 | 13c: 55 push rbp 72 | 13d: 53 push rbx 73 | 13e: 83 7e 20 04 cmp DWORD PTR [rsi+0x20],0x4 74 | 142: 75 2d jne 171 75 | 144: 83 f9 07 cmp ecx,0x7 76 | 147: 7e 32 jle 17b 77 | 149: 4c 8b af 48 19 00 00 mov r13,QWORD PTR [rdi+0x1948] 78 | 150: 48 89 d3 mov rbx,rdx 79 | 153: 49 8d 6d 10 lea rbp,[r13+0x10] 80 | 157: 48 89 ef mov rdi,rbp 81 | 15a: e8 00 00 00 00 call 15f 82 | 15f: 48 89 ef mov rdi,rbp 83 | 162: 48 89 c6 mov rsi,rax 84 | 165: 48 8b 03 mov rax,QWORD PTR [rbx] 85 | 168: 49 89 45 14 mov QWORD PTR [r13+0x14],rax 86 | 16c: e8 00 00 00 00 call 171 87 | 171: 5b pop rbx 88 | 172: 44 89 e0 mov eax,r12d 89 | 175: 5d pop rbp 90 | 176: 41 5c pop r12 91 | 178: 41 5d pop r13 92 | 17a: c3 ret 93 | 17b: 41 bc c3 ff ff ff mov r12d,0xffffffc3 94 | 181: eb ee jmp 171 95 | ``` 96 | -------------------------------------------------------------------------------- /Documentation/internal/unreliable-notes-on-hid.txt: -------------------------------------------------------------------------------- 1 | hid_parse - parse HW reports 2 | ---------------------------- 3 | 4 | static inline int __must_check hid_parse(struct hid_device *hdev) 5 | 6 | Call this from probe after you set up the device (if needed). Your 7 | report_fixup will be called (if non-NULL) after reading raw report from 8 | device before passing it to hid layer for real parsing. 9 | 10 | Just a wraper over hid_open_report. 11 | 12 | 13 | hid_hw_start - start underlying HW 14 | ---------------------------------- 15 | 16 | int hid_hw_start(struct hid_device *hdev, unsigned int connect_mask) 17 | 18 | Call this in probe function *after* hid_parse. This will setup HW 19 | buffers and start the device (if not defeirred to device open). 20 | hid_hw_stop must be called if this was successful. 21 | 22 | Calls ll_driver->start and, if connect_mask is not zero, ll_driver->connect. 23 | 24 | 25 | hid_hw_open - signal underlying HW to start delivering events 26 | ------------------------------------------------------------- 27 | 28 | int hid_hw_open(struct hid_device *hdev) 29 | 30 | Tell underlying HW to start delivering events from the device. 31 | This function should be called sometime after successful call 32 | to hid_hw_start(). 33 | 34 | Calls ll_driver->open, if ll_driver->open_count still zero. 35 | 36 | On device removal it's necessary to call hid_hw_close, otherwise subsequent 37 | probes will not be able to restart the events. 38 | 39 | Furthermore, hid_hw_close is not automatically called from the default remove() 40 | implementation. Thus it's necessary to supply one that does that, not 41 | forgetting about other necessary cleanup tasks like calling hid_hw_stop. 42 | 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Valid-License-Identifier: GPL-2.0 2 | Valid-License-Identifier: GPL-2.0-only 3 | Valid-License-Identifier: GPL-2.0+ 4 | Valid-License-Identifier: GPL-2.0-or-later 5 | SPDX-URL: https://spdx.org/licenses/GPL-2.0.html 6 | Usage-Guide: 7 | To use this license in source code, put one of the following SPDX 8 | tag/value pairs into a comment according to the placement 9 | guidelines in the licensing rules documentation. 10 | For 'GNU General Public License (GPL) version 2 only' use: 11 | SPDX-License-Identifier: GPL-2.0 12 | or 13 | SPDX-License-Identifier: GPL-2.0-only 14 | For 'GNU General Public License (GPL) version 2 or any later version' use: 15 | SPDX-License-Identifier: GPL-2.0+ 16 | or 17 | SPDX-License-Identifier: GPL-2.0-or-later 18 | License-Text: 19 | 20 | GNU GENERAL PUBLIC LICENSE 21 | Version 2, June 1991 22 | 23 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 24 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 25 | Everyone is permitted to copy and distribute verbatim copies 26 | of this license document, but changing it is not allowed. 27 | 28 | Preamble 29 | 30 | The licenses for most software are designed to take away your 31 | freedom to share and change it. By contrast, the GNU General Public 32 | License is intended to guarantee your freedom to share and change free 33 | software--to make sure the software is free for all its users. This 34 | General Public License applies to most of the Free Software 35 | Foundation's software and to any other program whose authors commit to 36 | using it. (Some other Free Software Foundation software is covered by 37 | the GNU Library General Public License instead.) You can apply it to 38 | your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Our General Public Licenses are designed to make sure that you 42 | have the freedom to distribute copies of free software (and charge for 43 | this service if you wish), that you receive source code or can get it 44 | if you want it, that you can change the software or use pieces of it 45 | in new free programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must show them these terms so they know their 56 | rights. 57 | 58 | We protect your rights with two steps: (1) copyright the software, and 59 | (2) offer you this license which gives you legal permission to copy, 60 | distribute and/or modify the software. 61 | 62 | Also, for each author's protection and ours, we want to make certain 63 | that everyone understands that there is no warranty for this free 64 | software. If the software is modified by someone else and passed on, we 65 | want its recipients to know that what they have is not the original, so 66 | that any problems introduced by others will not reflect on the original 67 | authors' reputations. 68 | 69 | Finally, any free program is threatened constantly by software 70 | patents. We wish to avoid the danger that redistributors of a free 71 | program will individually obtain patent licenses, in effect making the 72 | program proprietary. To prevent this, we have made it clear that any 73 | patent must be licensed for everyone's free use or not licensed at all. 74 | 75 | The precise terms and conditions for copying, distribution and 76 | modification follow. 77 | 78 | GNU GENERAL PUBLIC LICENSE 79 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 80 | 81 | 0. This License applies to any program or other work which contains 82 | a notice placed by the copyright holder saying it may be distributed 83 | under the terms of this General Public License. The "Program", below, 84 | refers to any such program or work, and a "work based on the Program" 85 | means either the Program or any derivative work under copyright law: 86 | that is to say, a work containing the Program or a portion of it, 87 | either verbatim or with modifications and/or translated into another 88 | language. (Hereinafter, translation is included without limitation in 89 | the term "modification".) Each licensee is addressed as "you". 90 | 91 | Activities other than copying, distribution and modification are not 92 | covered by this License; they are outside its scope. The act of 93 | running the Program is not restricted, and the output from the Program 94 | is covered only if its contents constitute a work based on the 95 | Program (independent of having been made by running the Program). 96 | Whether that is true depends on what the Program does. 97 | 98 | 1. You may copy and distribute verbatim copies of the Program's 99 | source code as you receive it, in any medium, provided that you 100 | conspicuously and appropriately publish on each copy an appropriate 101 | copyright notice and disclaimer of warranty; keep intact all the 102 | notices that refer to this License and to the absence of any warranty; 103 | and give any other recipients of the Program a copy of this License 104 | along with the Program. 105 | 106 | You may charge a fee for the physical act of transferring a copy, and 107 | you may at your option offer warranty protection in exchange for a fee. 108 | 109 | 2. You may modify your copy or copies of the Program or any portion 110 | of it, thus forming a work based on the Program, and copy and 111 | distribute such modifications or work under the terms of Section 1 112 | above, provided that you also meet all of these conditions: 113 | 114 | a) You must cause the modified files to carry prominent notices 115 | stating that you changed the files and the date of any change. 116 | 117 | b) You must cause any work that you distribute or publish, that in 118 | whole or in part contains or is derived from the Program or any 119 | part thereof, to be licensed as a whole at no charge to all third 120 | parties under the terms of this License. 121 | 122 | c) If the modified program normally reads commands interactively 123 | when run, you must cause it, when started running for such 124 | interactive use in the most ordinary way, to print or display an 125 | announcement including an appropriate copyright notice and a 126 | notice that there is no warranty (or else, saying that you provide 127 | a warranty) and that users may redistribute the program under 128 | these conditions, and telling the user how to view a copy of this 129 | License. (Exception: if the Program itself is interactive but 130 | does not normally print such an announcement, your work based on 131 | the Program is not required to print an announcement.) 132 | 133 | These requirements apply to the modified work as a whole. If 134 | identifiable sections of that work are not derived from the Program, 135 | and can be reasonably considered independent and separate works in 136 | themselves, then this License, and its terms, do not apply to those 137 | sections when you distribute them as separate works. But when you 138 | distribute the same sections as part of a whole which is a work based 139 | on the Program, the distribution of the whole must be on the terms of 140 | this License, whose permissions for other licensees extend to the 141 | entire whole, and thus to each and every part regardless of who wrote it. 142 | 143 | Thus, it is not the intent of this section to claim rights or contest 144 | your rights to work written entirely by you; rather, the intent is to 145 | exercise the right to control the distribution of derivative or 146 | collective works based on the Program. 147 | 148 | In addition, mere aggregation of another work not based on the Program 149 | with the Program (or with a work based on the Program) on a volume of 150 | a storage or distribution medium does not bring the other work under 151 | the scope of this License. 152 | 153 | 3. You may copy and distribute the Program (or a work based on it, 154 | under Section 2) in object code or executable form under the terms of 155 | Sections 1 and 2 above provided that you also do one of the following: 156 | 157 | a) Accompany it with the complete corresponding machine-readable 158 | source code, which must be distributed under the terms of Sections 159 | 1 and 2 above on a medium customarily used for software interchange; or, 160 | 161 | b) Accompany it with a written offer, valid for at least three 162 | years, to give any third party, for a charge no more than your 163 | cost of physically performing source distribution, a complete 164 | machine-readable copy of the corresponding source code, to be 165 | distributed under the terms of Sections 1 and 2 above on a medium 166 | customarily used for software interchange; or, 167 | 168 | c) Accompany it with the information you received as to the offer 169 | to distribute corresponding source code. (This alternative is 170 | allowed only for noncommercial distribution and only if you 171 | received the program in object code or executable form with such 172 | an offer, in accord with Subsection b above.) 173 | 174 | The source code for a work means the preferred form of the work for 175 | making modifications to it. For an executable work, complete source 176 | code means all the source code for all modules it contains, plus any 177 | associated interface definition files, plus the scripts used to 178 | control compilation and installation of the executable. However, as a 179 | special exception, the source code distributed need not include 180 | anything that is normally distributed (in either source or binary 181 | form) with the major components (compiler, kernel, and so on) of the 182 | operating system on which the executable runs, unless that component 183 | itself accompanies the executable. 184 | 185 | If distribution of executable or object code is made by offering 186 | access to copy from a designated place, then offering equivalent 187 | access to copy the source code from the same place counts as 188 | distribution of the source code, even though third parties are not 189 | compelled to copy the source along with the object code. 190 | 191 | 4. You may not copy, modify, sublicense, or distribute the Program 192 | except as expressly provided under this License. Any attempt 193 | otherwise to copy, modify, sublicense or distribute the Program is 194 | void, and will automatically terminate your rights under this License. 195 | However, parties who have received copies, or rights, from you under 196 | this License will not have their licenses terminated so long as such 197 | parties remain in full compliance. 198 | 199 | 5. You are not required to accept this License, since you have not 200 | signed it. However, nothing else grants you permission to modify or 201 | distribute the Program or its derivative works. These actions are 202 | prohibited by law if you do not accept this License. Therefore, by 203 | modifying or distributing the Program (or any work based on the 204 | Program), you indicate your acceptance of this License to do so, and 205 | all its terms and conditions for copying, distributing or modifying 206 | the Program or works based on it. 207 | 208 | 6. Each time you redistribute the Program (or any work based on the 209 | Program), the recipient automatically receives a license from the 210 | original licensor to copy, distribute or modify the Program subject to 211 | these terms and conditions. You may not impose any further 212 | restrictions on the recipients' exercise of the rights granted herein. 213 | You are not responsible for enforcing compliance by third parties to 214 | this License. 215 | 216 | 7. If, as a consequence of a court judgment or allegation of patent 217 | infringement or for any other reason (not limited to patent issues), 218 | conditions are imposed on you (whether by court order, agreement or 219 | otherwise) that contradict the conditions of this License, they do not 220 | excuse you from the conditions of this License. If you cannot 221 | distribute so as to satisfy simultaneously your obligations under this 222 | License and any other pertinent obligations, then as a consequence you 223 | may not distribute the Program at all. For example, if a patent 224 | license would not permit royalty-free redistribution of the Program by 225 | all those who receive copies directly or indirectly through you, then 226 | the only way you could satisfy both it and this License would be to 227 | refrain entirely from distribution of the Program. 228 | 229 | If any portion of this section is held invalid or unenforceable under 230 | any particular circumstance, the balance of the section is intended to 231 | apply and the section as a whole is intended to apply in other 232 | circumstances. 233 | 234 | It is not the purpose of this section to induce you to infringe any 235 | patents or other property right claims or to contest validity of any 236 | such claims; this section has the sole purpose of protecting the 237 | integrity of the free software distribution system, which is 238 | implemented by public license practices. Many people have made 239 | generous contributions to the wide range of software distributed 240 | through that system in reliance on consistent application of that 241 | system; it is up to the author/donor to decide if he or she is willing 242 | to distribute software through any other system and a licensee cannot 243 | impose that choice. 244 | 245 | This section is intended to make thoroughly clear what is believed to 246 | be a consequence of the rest of this License. 247 | 248 | 8. If the distribution and/or use of the Program is restricted in 249 | certain countries either by patents or by copyrighted interfaces, the 250 | original copyright holder who places the Program under this License 251 | may add an explicit geographical distribution limitation excluding 252 | those countries, so that distribution is permitted only in or among 253 | countries not thus excluded. In such case, this License incorporates 254 | the limitation as if written in the body of this License. 255 | 256 | 9. The Free Software Foundation may publish revised and/or new versions 257 | of the General Public License from time to time. Such new versions will 258 | be similar in spirit to the present version, but may differ in detail to 259 | address new problems or concerns. 260 | 261 | Each version is given a distinguishing version number. If the Program 262 | specifies a version number of this License which applies to it and "any 263 | later version", you have the option of following the terms and conditions 264 | either of that version or of any later version published by the Free 265 | Software Foundation. If the Program does not specify a version number of 266 | this License, you may choose any version ever published by the Free Software 267 | Foundation. 268 | 269 | 10. If you wish to incorporate parts of the Program into other free 270 | programs whose distribution conditions are different, write to the author 271 | to ask for permission. For software which is copyrighted by the Free 272 | Software Foundation, write to the Free Software Foundation; we sometimes 273 | make exceptions for this. Our decision will be guided by the two goals 274 | of preserving the free status of all derivatives of our free software and 275 | of promoting the sharing and reuse of software generally. 276 | 277 | NO WARRANTY 278 | 279 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 280 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 281 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 282 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 283 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 284 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 285 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 286 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 287 | REPAIR OR CORRECTION. 288 | 289 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 290 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 291 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 292 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 293 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 294 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 295 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 296 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 297 | POSSIBILITY OF SUCH DAMAGES. 298 | 299 | END OF TERMS AND CONDITIONS 300 | 301 | How to Apply These Terms to Your New Programs 302 | 303 | If you develop a new program, and you want it to be of the greatest 304 | possible use to the public, the best way to achieve this is to make it 305 | free software which everyone can redistribute and change under these terms. 306 | 307 | To do so, attach the following notices to the program. It is safest 308 | to attach them to the start of each source file to most effectively 309 | convey the exclusion of warranty; and each file should have at least 310 | the "copyright" line and a pointer to where the full notice is found. 311 | 312 | 313 | Copyright (C) 314 | 315 | This program is free software; you can redistribute it and/or modify 316 | it under the terms of the GNU General Public License as published by 317 | the Free Software Foundation; either version 2 of the License, or 318 | (at your option) any later version. 319 | 320 | This program is distributed in the hope that it will be useful, 321 | but WITHOUT ANY WARRANTY; without even the implied warranty of 322 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 323 | GNU General Public License for more details. 324 | 325 | You should have received a copy of the GNU General Public License 326 | along with this program; if not, write to the Free Software 327 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 328 | 329 | 330 | Also add information on how to contact you by electronic and paper mail. 331 | 332 | If the program is interactive, make it output a short notice like this 333 | when it starts in an interactive mode: 334 | 335 | Gnomovision version 69, Copyright (C) year name of author 336 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 337 | This is free software, and you are welcome to redistribute it 338 | under certain conditions; type `show c' for details. 339 | 340 | The hypothetical commands `show w' and `show c' should show the appropriate 341 | parts of the General Public License. Of course, the commands you use may 342 | be called something other than `show w' and `show c'; they could even be 343 | mouse-clicks or menu items--whatever suits your program. 344 | 345 | You should also get your employer (if you work as a programmer) or your 346 | school, if any, to sign a "copyright disclaimer" for the program, if 347 | necessary. Here is a sample; alter the names: 348 | 349 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 350 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 351 | 352 | , 1 April 1989 353 | Ty Coon, President of Vice 354 | 355 | This General Public License does not permit incorporating your program into 356 | proprietary programs. If your program is a subroutine library, you may 357 | consider it more useful to permit linking proprietary applications with the 358 | library. If this is what you want to do, use the GNU Library General 359 | Public License instead of this License. 360 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KDIR ?= /lib/modules/$(shell uname -r)/build 2 | SRC_DIR := drivers/hwmon 3 | 4 | modules: # default target 5 | 6 | %: 7 | $(MAKE) W=1 -C $(KDIR) M=$(abspath $(SRC_DIR)) $@ 8 | 9 | $(SRC_DIR)/%: 10 | $(MAKE) W=1 -C $(KDIR) M=$(abspath $(SRC_DIR)) $* 11 | 12 | include $(SRC_DIR)/Makefile 13 | 14 | SOURCES := $(patsubst %.o,%.c,$(obj-m)) 15 | SOURCES := $(addprefix $(SRC_DIR)/,$(SOURCES)) 16 | 17 | checkpatch: 18 | $(KDIR)/scripts/checkpatch.pl $(SOURCES) Documentation/hwmon/*.rst 19 | 20 | PKGVER ?= $(shell ./gitversion.sh) 21 | 22 | $(SRC_DIR)/dkms.conf: $(SRC_DIR)/dkms.conf.in 23 | sed -e "s/@PKGVER@/$(PKGVER)/" $< >$@ 24 | 25 | # Generate dkms.conf again every time to avoid stale version numbers 26 | .PHONY: $(SRC_DIR)/dkms.conf 27 | 28 | # https://www.gnu.org/software/make/manual/html_node/Command-Variables.html 29 | INSTALL := install 30 | INSTALL_PROGRAM := $(INSTALL) 31 | INSTALL_DATA := $(INSTALL) -m 644 32 | 33 | # https://www.gnu.org/software/make/manual/html_node/Directory-Variables.html 34 | prefix := /usr 35 | 36 | dkms_install: DKMS_INSTALL_BASE_DIR = $(DESTDIR)$(prefix)/src/liquidtux-$(PKGVER) 37 | 38 | dkms_install: $(SRC_DIR)/dkms.conf $(SRC_DIR)/Makefile $(SOURCES) 39 | mkdir -p $(DKMS_INSTALL_BASE_DIR) 40 | $(INSTALL_DATA) $^ $(DKMS_INSTALL_BASE_DIR)/ 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # liquidtux 2 | 3 | _Linux kernel hwmon drivers for AIO liquid coolers and other devices_ 4 | 5 | The goal of this project is to offer hardware monitoring drivers for 6 | closed-loop liquid coolers and other devices supported by [liquidctl], making 7 | their sensor data available in `/sys/class/hwmon/hwmon*`. 8 | 9 | By using the standard hwmon sysfs interface, `sensors`, tools using 10 | `libsensors`, as well as programs that read directly from the raw sysfs 11 | interface can access these devices' sensors. For more information, read the 12 | documentation of the [hwmon sysfs interface] and check the [lm-sensors] 13 | repository. 14 | 15 | ## Device support 16 | 17 | As the drivers mature, they will be proposed to, and hopefully reach, the 18 | mainline kernel. 19 | 20 | This is the current state of the drivers in regards to this process: 21 | 22 | | Device | Driver name | hwmon name | Upstream | 23 | | --- | --- | --- | --- | 24 | | NZXT Grid+ V3/Smart Device (V1) | `nzxt-grid3` | `smartdevice` | getting ready to submit | | 25 | | NZXT Kraken X42/X52/X62/X72 | `nzxt-kraken2` | `kraken2` | in Linux 5.13 ([patch][p-kraken2-v2]) | 26 | | NZXT Kraken X53/X63/X73, Z53/Z63/Z73, Kraken 2023 (standard, Elite) | `nzxt-kraken3` | `kraken3` | in Linux 6.9 ([patch][p-kraken3]), Kraken 2023 - in Linux 6.10 ([patch][p-kraken2023]) | 27 | | NZXT Smart Device V2/RGB & Fan Controller | `nzxt-smart2` | `nzxtsmart2` | in Linux 5.17 ([patch][p-smart2]) | 28 | 29 | This repository contains the latest state of each driver, including features 30 | and bug fixes that are not yet submitted upstream. 31 | 32 | _Note: other hwmon drivers exist in the mainline kernel for devices that 33 | liquidctl also supports: [`corsair-cpro`], [`corsair-psu`]._ 34 | 35 | ## Installing with DKMS 36 | 37 | ArchLinux users can try the 38 | [liquidtux-dkms-gitAUR][liquidtux-dkms-git-aur] 39 | package. After the package is installed, manually load the desired drivers. 40 | 41 | ``` 42 | $ sudo modprobe nzxt-grid3 # NZXT Grid+ V3/Smart Device (V1) 43 | $ sudo modprobe nzxt-kraken2 # NZXT Kraken X42/X52/X62/X72 44 | $ sudo modprobe nzxt-kraken3 # NZXT Kraken X53/X63/X73, Z53/Z63/Z73, Kraken 2023 (standard, Elite) 45 | $ sudo modprobe nzxt-smart2 # NZXT Smart Device V2/RGB & Fan Controller 46 | ``` 47 | 48 | Those on other distros can install DKMS files using `dkms_install` `Makefile` 49 | target: 50 | 51 | ``` 52 | $ sudo make dkms_install 53 | ``` 54 | 55 | Then build and install the modules using: 56 | 57 | ``` 58 | $ sudo dkms install -m liquidtux -v $(./gitversion.sh) 59 | ``` 60 | 61 | Also, `dkms_install` supports `DESTDIR` variable, so it could be used for 62 | building distribution-specific packages. 63 | 64 | ## Manually building, inserting and installing 65 | 66 | The drivers should be built with the [kbuild system]. 67 | 68 | A simple Makefile is provided that simplifies this in normal scenarios. The 69 | built modules can then be loaded with `insmod`. 70 | 71 | ``` 72 | $ make 73 | $ sudo insmod drivers/hwmon/nzxt-grid3.ko # NZXT Grid+ V3/Smart Device (V1) 74 | $ sudo insmod drivers/hwmon/nzxt-kraken2.ko # NZXT Kraken X42/X52/X62/X72 75 | $ sudo insmod drivers/hwmon/nzxt-kraken3.ko # NZXT Kraken X53/X63/X73, Z53/Z63/Z73, Kraken 2023 (standard, Elite) 76 | $ sudo insmod drivers/hwmon/nzxt-smart2.ko # NZXT Smart Device V2/RGB & Fan Controller 77 | ``` 78 | 79 | To unload them, use `rmmod` or `modprobe -r`. 80 | 81 | If testing was successful the modules can be installed to the system with the 82 | `modules_install` target: 83 | 84 | ``` 85 | $ sudo make modules_install 86 | ``` 87 | 88 | [`corsair-cpro`]: https://www.kernel.org/doc/html/latest/hwmon/corsair-cpro.html 89 | [`corsair-psu`]: https://www.kernel.org/doc/html/latest/hwmon/corsair-psu.html 90 | [dkms.conf]: dkms.conf 91 | [hwmon sysfs interface]: https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface 92 | [kbuild system]: https://github.com/torvalds/linux/blob/master/Documentation/kbuild/modules.txt 93 | [liquidctl]: https://github.com/jonasmalacofilho/liquidctl 94 | [liquidtux-dkms-git-aur]: https://aur.archlinux.org/packages/liquidtux-dkms-git/ 95 | [lm-sensors]: https://github.com/lm-sensors/lm-sensors 96 | [p-kraken2-v2]: https://patchwork.kernel.org/project/linux-hwmon/patch/20210319045544.416138-1-jonas@protocubo.io/ 97 | [p-kraken3]: https://patchwork.kernel.org/project/linux-hwmon/patch/20240129111932.368232-1-savicaleksa83@gmail.com/ 98 | [p-kraken2023]: https://patchwork.kernel.org/project/linux-hwmon/patch/20240428104812.14037-3-savicaleksa83@gmail.com/ 99 | [p-smart2]: https://patchwork.kernel.org/project/linux-hwmon/patch/20211031033058.151014-1-mezin.alexander@gmail.com/ 100 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | CPUS = 4 5 | MEMORY = 1024 6 | USB_IDS = [ 7 | # nzxt-grid3 8 | { :vendor => "0x1e71", :product => "0x1711" }, 9 | { :vendor => "0x1e71", :product => "0x1714" }, 10 | # nzxt-kraken2 11 | { :vendor => "0x1e71", :product => "0x170e" }, 12 | # nzxt-kraken3 13 | { :vendor => "0x1e71", :product => "0x2007" }, 14 | { :vendor => "0x1e71", :product => "0x2014" }, 15 | { :vendor => "0x1e71", :product => "0x3008" }, 16 | # nzxt-smart2 17 | { :vendor => "0x1e71", :product => "0x2006" }, 18 | { :vendor => "0x1e71", :product => "0x200d" }, 19 | { :vendor => "0x1e71", :product => "0x2009" }, 20 | { :vendor => "0x1e71", :product => "0x200e" }, 21 | { :vendor => "0x1e71", :product => "0x200f" }, 22 | { :vendor => "0x1e71", :product => "0x2010" }, 23 | { :vendor => "0x1e71", :product => "0x2011" }, 24 | { :vendor => "0x1e71", :product => "0x2019" }, 25 | ] 26 | 27 | Vagrant.configure("2") do |config| 28 | config.vm.box = "fedora/39-cloud-base" 29 | 30 | config.vm.provider "virtualbox" do |virtualbox, override| 31 | virtualbox.cpus = CPUS 32 | virtualbox.memory = MEMORY 33 | virtualbox.default_nic_type = "virtio" 34 | 35 | virtualbox.customize ["modifyvm", :id, "--usbxhci", "on"] 36 | 37 | # HACK: only create usb filters once, if vm doesn't exist yet 38 | if not File.exist? File.join(".vagrant", "machines", "default", "virtualbox", "id") 39 | USB_IDS.each_with_index do |usb_id, index| 40 | virtualbox.customize ["usbfilter", "add", index.to_s, "--target", :id, "--name", "dev-#{usb_id[:vendor]}-#{usb_id[:product]}", "--vendorid", usb_id[:vendor], "--productid", usb_id[:product]] 41 | end 42 | end 43 | end 44 | 45 | config.vm.provider "libvirt" do |libvirt, override| 46 | libvirt.cpus = CPUS 47 | libvirt.memory = MEMORY 48 | 49 | # Error while creating domain: Error saving the server: Call to virDomainDefineXML failed: 50 | # unsupported configuration: chardev 'spicevmc' not supported without spice graphics 51 | libvirt.graphics_type = "spice" 52 | libvirt.channel :type => "spicevmc", :target_name => "com.redhat.spice.0", :target_type => "virtio" 53 | 54 | libvirt.usb_controller :model => "qemu-xhci" 55 | libvirt.redirdev :type => "spicevmc" 56 | 57 | USB_IDS.each do |usb_id| 58 | libvirt.usb usb_id.merge :startupPolicy => "optional" 59 | end 60 | 61 | if Vagrant.has_plugin?("vagrant-libvirt", "> 0.5.3") 62 | libvirt.channel :type => "unix", :target_name => "org.qemu.guest_agent.0", :target_type => "virtio" 63 | libvirt.qemu_use_agent = true 64 | end 65 | end 66 | 67 | config.vm.provision "ansible" do |ansible| 68 | ansible.playbook = "tools/vagrant/cd-to-vagrant.yml" 69 | end 70 | 71 | config.vm.provision "ansible" do |ansible| 72 | ansible.playbook = "tools/vagrant/dev-tools.yml" 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /drivers/hwmon/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := nzxt-kraken2.o nzxt-grid3.o nzxt-kraken3.o nzxt-smart2.o 2 | -------------------------------------------------------------------------------- /drivers/hwmon/dkms.conf.in: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME="liquidtux" 2 | PACKAGE_VERSION="@PKGVER@" 3 | 4 | BUILT_MODULE_NAME[0]="nzxt-kraken2" 5 | DEST_MODULE_LOCATION[0]="/kernel/drivers/hwmon" 6 | 7 | BUILT_MODULE_NAME[1]="nzxt-grid3" 8 | DEST_MODULE_LOCATION[1]="/kernel/drivers/hwmon" 9 | 10 | BUILT_MODULE_NAME[2]="nzxt-kraken3" 11 | DEST_MODULE_LOCATION[2]="/kernel/drivers/hwmon" 12 | 13 | BUILT_MODULE_NAME[3]="nzxt-smart2" 14 | DEST_MODULE_LOCATION[3]="/kernel/drivers/hwmon" 15 | 16 | AUTOINSTALL="yes" 17 | -------------------------------------------------------------------------------- /drivers/hwmon/nzxt-grid3.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /* 3 | * hwmon driver for NZXT Grid+ V3 and Smart Device (V1) 4 | * 5 | * The device asynchronously sends HID reports five times a second to 6 | * communicate fan speed, current, voltage and control mode. It does not 7 | * respond to Get_Report or honor Set_Idle requests for this status report. 8 | * 9 | * Fan speeds can be controlled through output HID reports, but duty cycles 10 | * cannot be read back from the device. 11 | * 12 | * A special initialization routine causes the device to detect the fan 13 | * channels in use and their appropriate control mode (DC or PWM); once 14 | * requested, the initialization routine is executed asynchronously by the 15 | * device. 16 | * 17 | * Before initialization: 18 | * - all fans default to 40% PWM; 19 | * - PWM value changes are sometimes honored, other times ignored; 20 | * - the device does not send status reports. 21 | * 22 | * After initialization: 23 | * - the device sends status reports five times a second; 24 | * - for channels in use, the control mode has been detected and PWM changes 25 | * are honored; 26 | * - for channels that were not detected as in use, fan speeds, current and 27 | * voltage are still measured, and PWM changes are still accepted even though 28 | * they have no immediate effect. 29 | * 30 | * Control mode and PWM settings only persist as long as the USB device is 31 | * connected and powered on. 32 | * 33 | * Copyright 2019-2021 Jonas Malaco 34 | */ 35 | 36 | #include 37 | 38 | #if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE 39 | #include 40 | #else 41 | #include 42 | #endif 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #define VID_NZXT 0x1e71 54 | #define PID_GRIDPLUS3 0x1711 55 | #define PID_SMARTDEVICE 0x1714 56 | 57 | #define REPORT_REQ_INIT 0x01 58 | #define REQ_INIT_DETECT 0x5c 59 | #define REQ_INIT_OPEN 0x5d 60 | 61 | #define REPORT_STATUS 0x04 62 | #define STATUS_VALIDITY 3 /* seconds */ 63 | 64 | #define REPORT_CONFIG 0x02 65 | #define CONFIG_FAN_PWM 0x4d 66 | 67 | #define DC_FAN BIT(0) 68 | #define PWM_FAN BIT(1) 69 | 70 | /** 71 | * struct grid3_channel_status - Last known data for a given channel. 72 | * @rpms: Fan speed in rpm. 73 | * @centiamps: Fan current draw in centiamperes. 74 | * @centivolts: Fan supply voltage in centivolts. 75 | * @pwm: Fan PWM value (last set value, device does not report it). 76 | * @fan_type: Fan type (no fan, DC, PWM). 77 | * @updated: Last update in jiffies. 78 | * 79 | * Centiamperes and centivolts are used to save some space. 80 | */ 81 | struct grid3_channel_status { 82 | u16 rpms; 83 | u16 centiamps; 84 | u16 centivolts; 85 | u8 pwm; 86 | u8 fan_type; 87 | unsigned long updated; 88 | }; 89 | 90 | /** 91 | * struct grid3_data - Driver private data. 92 | * @hid_dev: HID device. 93 | * @hwmon_dev: HWMON device. 94 | * @lock: Protects the output buffer @out and writes to @status[].pwm. 95 | * @out: DMA-safe output buffer for HID output reports. 96 | * @channels: Number of channels. 97 | * @status: Last known status for each channel. 98 | */ 99 | struct grid3_data { 100 | struct hid_device *hid_dev; 101 | struct device *hwmon_dev; 102 | 103 | struct mutex lock; /* see comment above */ 104 | u8 out[8]; 105 | 106 | int channels; 107 | struct grid3_channel_status status[]; 108 | }; 109 | 110 | static umode_t grid3_is_visible(const void *data, enum hwmon_sensor_types type, 111 | u32 attr, int channel) 112 | { 113 | const struct grid3_data *priv = data; 114 | 115 | if (channel >= priv->channels) 116 | return 0; 117 | 118 | switch (type) { 119 | case hwmon_fan: 120 | case hwmon_curr: 121 | case hwmon_in: 122 | return 0444; 123 | case hwmon_pwm: 124 | switch (attr) { 125 | case hwmon_pwm_input: 126 | return 0644; 127 | case hwmon_pwm_mode: 128 | return 0444; 129 | default: 130 | return 0; 131 | } 132 | default: 133 | return 0; 134 | } 135 | } 136 | 137 | static int grid3_read_pwm(struct grid3_data *priv, u32 attr, int channel, long *val) 138 | { 139 | switch (attr) { 140 | case hwmon_pwm_input: 141 | *val = priv->status[channel].pwm; 142 | break; 143 | case hwmon_pwm_mode: 144 | /* 145 | * For fan control, the device treats undetected == PWM. 146 | */ 147 | *val = priv->status[channel].fan_type != DC_FAN; 148 | break; 149 | default: 150 | return -EOPNOTSUPP; 151 | } 152 | 153 | return 0; 154 | } 155 | 156 | static int grid3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, 157 | int channel, long *val) 158 | { 159 | struct grid3_data *priv = dev_get_drvdata(dev); 160 | unsigned long expires; 161 | 162 | expires = priv->status[channel].updated + STATUS_VALIDITY * HZ; 163 | if (time_after(jiffies, expires)) 164 | return -ENODATA; 165 | 166 | switch (type) { 167 | case hwmon_fan: 168 | *val = priv->status[channel].rpms; 169 | break; 170 | case hwmon_curr: 171 | *val = priv->status[channel].centiamps * 10; 172 | break; 173 | case hwmon_in: 174 | *val = priv->status[channel].centivolts * 10; 175 | break; 176 | case hwmon_pwm: 177 | return grid3_read_pwm(priv, attr, channel, val); 178 | default: 179 | return -EOPNOTSUPP; 180 | } 181 | 182 | return 0; 183 | } 184 | 185 | /* 186 | * Caller must hold priv->lock or otherwise ensure exclusive access to 187 | * priv->out and priv->status[*].pwm. 188 | */ 189 | static int grid3_write_pwm_assume_locked(struct grid3_data *priv, int channel, long val) 190 | { 191 | int ret; 192 | 193 | val = clamp_val(val, 0, 255); 194 | 195 | priv->out[0] = REPORT_CONFIG; 196 | priv->out[1] = CONFIG_FAN_PWM; 197 | priv->out[2] = channel; 198 | priv->out[3] = 0x00; 199 | priv->out[4] = val * 100 / 255; 200 | 201 | ret = hid_hw_output_report(priv->hid_dev, priv->out, 5); 202 | if (ret < 0) 203 | return ret; 204 | if (ret != 5) 205 | return -EIO; /* FIXME */ 206 | 207 | /* 208 | * Store the value that was just set; the device does not support 209 | * reading it later, but user-space needs it. 210 | */ 211 | priv->status[channel].pwm = val; 212 | 213 | return 0; 214 | } 215 | 216 | static int grid3_write_pwm_input(struct device *dev, enum hwmon_sensor_types type, 217 | u32 attr, int channel, long val) 218 | { 219 | struct grid3_data *priv = dev_get_drvdata(dev); 220 | int ret; 221 | 222 | if (mutex_lock_interruptible(&priv->lock)) 223 | return -ERESTARTSYS; 224 | 225 | ret = grid3_write_pwm_assume_locked(priv, channel, val); 226 | 227 | mutex_unlock(&priv->lock); 228 | return ret; 229 | } 230 | 231 | static const struct hwmon_ops grid3_hwmon_ops = { 232 | .is_visible = grid3_is_visible, 233 | .read = grid3_read, 234 | .write = grid3_write_pwm_input, 235 | }; 236 | 237 | static const struct hwmon_channel_info *grid3_info[] = { 238 | HWMON_CHANNEL_INFO(fan, 239 | HWMON_F_INPUT, 240 | HWMON_F_INPUT, 241 | HWMON_F_INPUT, 242 | HWMON_F_INPUT, 243 | HWMON_F_INPUT, 244 | HWMON_F_INPUT), 245 | HWMON_CHANNEL_INFO(curr, 246 | HWMON_C_INPUT, 247 | HWMON_C_INPUT, 248 | HWMON_C_INPUT, 249 | HWMON_C_INPUT, 250 | HWMON_C_INPUT, 251 | HWMON_C_INPUT), 252 | HWMON_CHANNEL_INFO(in, 253 | HWMON_I_INPUT, 254 | HWMON_I_INPUT, 255 | HWMON_I_INPUT, 256 | HWMON_I_INPUT, 257 | HWMON_I_INPUT, 258 | HWMON_I_INPUT), 259 | HWMON_CHANNEL_INFO(pwm, 260 | HWMON_PWM_INPUT | HWMON_PWM_MODE, 261 | HWMON_PWM_INPUT | HWMON_PWM_MODE, 262 | HWMON_PWM_INPUT | HWMON_PWM_MODE, 263 | HWMON_PWM_INPUT | HWMON_PWM_MODE, 264 | HWMON_PWM_INPUT | HWMON_PWM_MODE, 265 | HWMON_PWM_INPUT | HWMON_PWM_MODE), 266 | NULL 267 | }; 268 | 269 | static const struct hwmon_chip_info grid3_chip_info = { 270 | .ops = &grid3_hwmon_ops, 271 | .info = grid3_info, 272 | }; 273 | 274 | static int grid3_raw_event(struct hid_device *hdev, struct hid_report *report, 275 | u8 *data, int size) 276 | { 277 | struct grid3_data *priv; 278 | int channel; 279 | 280 | if (size < 16 || report->id != REPORT_STATUS) 281 | return 0; 282 | 283 | priv = hid_get_drvdata(hdev); 284 | 285 | channel = data[15] >> 4; 286 | 287 | if (channel > priv->channels) 288 | return 0; 289 | 290 | priv->status[channel].rpms = get_unaligned_be16(data + 3); 291 | priv->status[channel].centiamps = data[9] * 100 + data[10]; 292 | priv->status[channel].centivolts = data[7] * 100 + data[8]; 293 | priv->status[channel].fan_type = data[15] & 0x3; 294 | 295 | priv->status[channel].updated = jiffies; 296 | 297 | return 0; 298 | } 299 | 300 | /* 301 | * Caller must hold priv->lock or otherwise ensure exclusive access to 302 | * priv->out. 303 | */ 304 | static int grid3_req_init_assume_locked(struct hid_device *hdev, u8 *buf) 305 | { 306 | u8 cmds[2] = {REQ_INIT_DETECT, REQ_INIT_OPEN}; 307 | int i, ret; 308 | 309 | buf[0] = REPORT_REQ_INIT; 310 | 311 | for (i = 0; i < ARRAY_SIZE(cmds); i++) { 312 | buf[1] = cmds[i]; 313 | ret = hid_hw_output_report(hdev, buf, 2); 314 | if (ret < 0) 315 | return ret; 316 | if (ret != 2) 317 | return -EIO; /* FIXME */ 318 | } 319 | 320 | return 0; 321 | } 322 | 323 | /* 324 | * Caller must hold priv->lock or otherwise ensure exclusive access to 325 | * priv->out and priv->status[*].pwm. 326 | */ 327 | static int grid3_driver_init_assume_locked(struct grid3_data *priv) 328 | { 329 | int i, ret; 330 | 331 | ret = grid3_req_init_assume_locked(priv->hid_dev, priv->out); 332 | if (ret) { 333 | hid_err(priv->hid_dev, "request init failed with %d\n", ret); 334 | return ret; 335 | } 336 | 337 | for (i = 0; i < priv->channels; i++) { 338 | /* 339 | * Initialize ->updated to STATUS_VALIDITY seconds in the past, 340 | * making the initial empty data invalid for grid3_read 341 | * without the need for a special case there. 342 | */ 343 | priv->status[i].updated = jiffies - STATUS_VALIDITY * HZ; 344 | 345 | /* 346 | * Mimic the behavior of the device after being powered on, 347 | * ensuring predictable behavior if the driver has been 348 | * previously removed. 349 | */ 350 | ret = grid3_write_pwm_assume_locked(priv, i, 40 * 255 / 100); 351 | if (ret) { 352 | hid_err(priv->hid_dev, "write pwm failed with %d\n", ret); 353 | return ret; 354 | } 355 | } 356 | return 0; 357 | } 358 | 359 | static int __maybe_unused grid3_reset_resume(struct hid_device *hdev) 360 | { 361 | struct grid3_data *priv = hid_get_drvdata(hdev); 362 | int ret; 363 | 364 | mutex_lock(&priv->lock); 365 | ret = grid3_driver_init_assume_locked(priv); 366 | mutex_unlock(&priv->lock); 367 | 368 | if (ret) 369 | hid_err(hdev, "req init (reset_resume) failed with %d\n", ret); 370 | 371 | return ret; 372 | } 373 | 374 | static int grid3_probe(struct hid_device *hdev, const struct hid_device_id *id) 375 | { 376 | struct grid3_data *priv; 377 | char *hwmon_name; 378 | int channels, ret; 379 | 380 | switch (id->product) { 381 | case PID_GRIDPLUS3: 382 | channels = 6; 383 | hwmon_name = "gridplus3"; 384 | break; 385 | case PID_SMARTDEVICE: 386 | channels = 3; 387 | hwmon_name = "smartdevice"; 388 | break; 389 | default: 390 | return -EINVAL; /* unreachable */ 391 | } 392 | 393 | priv = devm_kzalloc(&hdev->dev, struct_size(priv, status, channels), GFP_KERNEL); 394 | if (!priv) 395 | return -ENOMEM; 396 | 397 | priv->hid_dev = hdev; 398 | priv->channels = channels; 399 | mutex_init(&priv->lock); 400 | 401 | hid_set_drvdata(hdev, priv); 402 | 403 | ret = hid_parse(hdev); 404 | if (ret) { 405 | hid_err(hdev, "hid parse failed with %d\n", ret); 406 | goto fail_mutex_destroy; 407 | } 408 | 409 | /* 410 | * Enable hidraw so existing user-space tools can continue to work. 411 | */ 412 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 413 | if (ret) { 414 | hid_err(hdev, "hid hw start failed with %d\n", ret); 415 | goto fail_mutex_destroy; 416 | } 417 | 418 | ret = hid_hw_open(hdev); 419 | if (ret) { 420 | hid_err(hdev, "hid hw open failed with %d\n", ret); 421 | goto fail_hid_stop; 422 | } 423 | 424 | /* 425 | * Concurrent access to priv->out or priv->status[*].pwm is not 426 | * possible yet. 427 | */ 428 | ret = grid3_driver_init_assume_locked(priv); 429 | if (ret) { 430 | hid_err(hdev, "driver init failed with %d\n", ret); 431 | goto fail_hid_close; 432 | } 433 | 434 | priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, hwmon_name, 435 | priv, &grid3_chip_info, 436 | NULL); 437 | if (IS_ERR(priv->hwmon_dev)) { 438 | ret = PTR_ERR(priv->hwmon_dev); 439 | hid_err(hdev, "hwmon registration failed with %d\n", ret); 440 | goto fail_hid_close; 441 | } 442 | 443 | return 0; 444 | 445 | fail_hid_close: 446 | hid_hw_close(hdev); 447 | fail_hid_stop: 448 | hid_hw_stop(hdev); 449 | fail_mutex_destroy: 450 | mutex_destroy(&priv->lock); 451 | return ret; 452 | } 453 | 454 | static void grid3_remove(struct hid_device *hdev) 455 | { 456 | struct grid3_data *priv = hid_get_drvdata(hdev); 457 | 458 | hwmon_device_unregister(priv->hwmon_dev); 459 | 460 | hid_hw_close(hdev); 461 | hid_hw_stop(hdev); 462 | 463 | mutex_destroy(&priv->lock); 464 | } 465 | 466 | static const struct hid_device_id grid3_table[] = { 467 | { HID_USB_DEVICE(VID_NZXT, PID_GRIDPLUS3) }, 468 | { HID_USB_DEVICE(VID_NZXT, PID_SMARTDEVICE) }, 469 | { } 470 | }; 471 | 472 | MODULE_DEVICE_TABLE(hid, grid3_table); 473 | 474 | static struct hid_driver grid3_driver = { 475 | .name = "nzxt-grid3", 476 | .id_table = grid3_table, 477 | .probe = grid3_probe, 478 | .remove = grid3_remove, 479 | .raw_event = grid3_raw_event, 480 | #ifdef CONFIG_PM 481 | .reset_resume = grid3_reset_resume, 482 | #endif 483 | }; 484 | 485 | static int __init grid3_init(void) 486 | { 487 | return hid_register_driver(&grid3_driver); 488 | } 489 | 490 | static void __exit grid3_exit(void) 491 | { 492 | hid_unregister_driver(&grid3_driver); 493 | } 494 | 495 | /* 496 | * When compiled into the kernel, initialize after the hid bus. 497 | */ 498 | late_initcall(grid3_init); 499 | module_exit(grid3_exit); 500 | 501 | MODULE_LICENSE("GPL"); 502 | MODULE_AUTHOR("Jonas Malaco "); 503 | MODULE_DESCRIPTION("Hwmon driver for NZXT Smart Device (V1) and Grid+ V3"); 504 | -------------------------------------------------------------------------------- /drivers/hwmon/nzxt-kraken2.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /* 3 | * hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers 4 | * 5 | * The device asynchronously sends HID reports (with id 0x04) twice a second to 6 | * communicate current fan speed, pump speed and coolant temperature. The 7 | * device does not respond to Get_Report requests for this status report. 8 | * 9 | * Copyright 2019-2021 Jonas Malaco 10 | */ 11 | 12 | #include 13 | 14 | #if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define STATUS_REPORT_ID 0x04 26 | #define STATUS_VALIDITY 2 /* seconds; equivalent to 4 missed updates */ 27 | 28 | static const char *const kraken2_temp_label[] = { 29 | "Coolant", 30 | }; 31 | 32 | static const char *const kraken2_fan_label[] = { 33 | "Fan", 34 | "Pump", 35 | }; 36 | 37 | struct kraken2_priv_data { 38 | struct hid_device *hid_dev; 39 | struct device *hwmon_dev; 40 | s32 temp_input[1]; 41 | u16 fan_input[2]; 42 | unsigned long updated; /* jiffies */ 43 | }; 44 | 45 | static umode_t kraken2_is_visible(const void *data, 46 | enum hwmon_sensor_types type, 47 | u32 attr, int channel) 48 | { 49 | return 0444; 50 | } 51 | 52 | static int kraken2_read(struct device *dev, enum hwmon_sensor_types type, 53 | u32 attr, int channel, long *val) 54 | { 55 | struct kraken2_priv_data *priv = dev_get_drvdata(dev); 56 | 57 | if (time_after(jiffies, priv->updated + STATUS_VALIDITY * HZ)) 58 | return -ENODATA; 59 | 60 | switch (type) { 61 | case hwmon_temp: 62 | *val = priv->temp_input[channel]; 63 | break; 64 | case hwmon_fan: 65 | *val = priv->fan_input[channel]; 66 | break; 67 | default: 68 | return -EOPNOTSUPP; /* unreachable */ 69 | } 70 | 71 | return 0; 72 | } 73 | 74 | static int kraken2_read_string(struct device *dev, enum hwmon_sensor_types type, 75 | u32 attr, int channel, const char **str) 76 | { 77 | switch (type) { 78 | case hwmon_temp: 79 | *str = kraken2_temp_label[channel]; 80 | break; 81 | case hwmon_fan: 82 | *str = kraken2_fan_label[channel]; 83 | break; 84 | default: 85 | return -EOPNOTSUPP; /* unreachable */ 86 | } 87 | return 0; 88 | } 89 | 90 | static const struct hwmon_ops kraken2_hwmon_ops = { 91 | .is_visible = kraken2_is_visible, 92 | .read = kraken2_read, 93 | .read_string = kraken2_read_string, 94 | }; 95 | 96 | static const struct hwmon_channel_info *kraken2_info[] = { 97 | HWMON_CHANNEL_INFO(temp, 98 | HWMON_T_INPUT | HWMON_T_LABEL), 99 | HWMON_CHANNEL_INFO(fan, 100 | HWMON_F_INPUT | HWMON_F_LABEL, 101 | HWMON_F_INPUT | HWMON_F_LABEL), 102 | NULL 103 | }; 104 | 105 | static const struct hwmon_chip_info kraken2_chip_info = { 106 | .ops = &kraken2_hwmon_ops, 107 | .info = kraken2_info, 108 | }; 109 | 110 | static int kraken2_raw_event(struct hid_device *hdev, 111 | struct hid_report *report, u8 *data, int size) 112 | { 113 | struct kraken2_priv_data *priv; 114 | 115 | if (size < 7 || report->id != STATUS_REPORT_ID) 116 | return 0; 117 | 118 | priv = hid_get_drvdata(hdev); 119 | 120 | /* 121 | * The fractional byte of the coolant temperature has been observed to 122 | * be in the interval [1,9], but some of these steps are also 123 | * consistently skipped for certain integer parts. 124 | * 125 | * For the lack of a better idea, assume that the resolution is 0.1°C, 126 | * and that the missing steps are artifacts of how the firmware 127 | * processes the raw sensor data. 128 | */ 129 | priv->temp_input[0] = data[1] * 1000 + data[2] * 100; 130 | 131 | priv->fan_input[0] = get_unaligned_be16(data + 3); 132 | priv->fan_input[1] = get_unaligned_be16(data + 5); 133 | 134 | priv->updated = jiffies; 135 | 136 | return 0; 137 | } 138 | 139 | static int kraken2_probe(struct hid_device *hdev, 140 | const struct hid_device_id *id) 141 | { 142 | struct kraken2_priv_data *priv; 143 | int ret; 144 | 145 | priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); 146 | if (!priv) 147 | return -ENOMEM; 148 | 149 | priv->hid_dev = hdev; 150 | hid_set_drvdata(hdev, priv); 151 | 152 | /* 153 | * Initialize ->updated to STATUS_VALIDITY seconds in the past, making 154 | * the initial empty data invalid for kraken2_read without the need for 155 | * a special case there. 156 | */ 157 | priv->updated = jiffies - STATUS_VALIDITY * HZ; 158 | 159 | ret = hid_parse(hdev); 160 | if (ret) { 161 | hid_err(hdev, "hid parse failed with %d\n", ret); 162 | return ret; 163 | } 164 | 165 | /* 166 | * Enable hidraw so existing user-space tools can continue to work. 167 | */ 168 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 169 | if (ret) { 170 | hid_err(hdev, "hid hw start failed with %d\n", ret); 171 | return ret; 172 | } 173 | 174 | ret = hid_hw_open(hdev); 175 | if (ret) { 176 | hid_err(hdev, "hid hw open failed with %d\n", ret); 177 | goto fail_and_stop; 178 | } 179 | 180 | priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "kraken2", 181 | priv, &kraken2_chip_info, 182 | NULL); 183 | if (IS_ERR(priv->hwmon_dev)) { 184 | ret = PTR_ERR(priv->hwmon_dev); 185 | hid_err(hdev, "hwmon registration failed with %d\n", ret); 186 | goto fail_and_close; 187 | } 188 | 189 | return 0; 190 | 191 | fail_and_close: 192 | hid_hw_close(hdev); 193 | fail_and_stop: 194 | hid_hw_stop(hdev); 195 | return ret; 196 | } 197 | 198 | static void kraken2_remove(struct hid_device *hdev) 199 | { 200 | struct kraken2_priv_data *priv = hid_get_drvdata(hdev); 201 | 202 | hwmon_device_unregister(priv->hwmon_dev); 203 | 204 | hid_hw_close(hdev); 205 | hid_hw_stop(hdev); 206 | } 207 | 208 | static const struct hid_device_id kraken2_table[] = { 209 | { HID_USB_DEVICE(0x1e71, 0x170e) }, /* NZXT Kraken X42/X52/X62/X72 */ 210 | { } 211 | }; 212 | 213 | MODULE_DEVICE_TABLE(hid, kraken2_table); 214 | 215 | static struct hid_driver kraken2_driver = { 216 | .name = "nzxt-kraken2", 217 | .id_table = kraken2_table, 218 | .probe = kraken2_probe, 219 | .remove = kraken2_remove, 220 | .raw_event = kraken2_raw_event, 221 | }; 222 | 223 | static int __init kraken2_init(void) 224 | { 225 | return hid_register_driver(&kraken2_driver); 226 | } 227 | 228 | static void __exit kraken2_exit(void) 229 | { 230 | hid_unregister_driver(&kraken2_driver); 231 | } 232 | 233 | /* 234 | * When compiled into the kernel, initialize after the hid bus. 235 | */ 236 | late_initcall(kraken2_init); 237 | module_exit(kraken2_exit); 238 | 239 | MODULE_LICENSE("GPL"); 240 | MODULE_AUTHOR("Jonas Malaco "); 241 | MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers"); 242 | -------------------------------------------------------------------------------- /drivers/hwmon/nzxt-kraken3.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /* 3 | * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. 4 | * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). 5 | * 2023 models use the Z53 code paths. 6 | * 7 | * Copyright 2021 Jonas Malaco 8 | * Copyright 2022 Aleksa Savic 9 | */ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE 24 | #include 25 | #else 26 | #include 27 | #endif 28 | 29 | #define USB_VENDOR_ID_NZXT 0x1e71 30 | #define USB_PRODUCT_ID_X53 0x2007 31 | #define USB_PRODUCT_ID_X53_SECOND 0x2014 32 | #define USB_PRODUCT_ID_Z53 0x3008 33 | #define USB_PRODUCT_ID_KRAKEN2023 0x300E 34 | #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C 35 | 36 | enum kinds { X53, Z53, KRAKEN2023 } __packed; 37 | enum pwm_enable { off, manual, curve } __packed; 38 | 39 | #define DRIVER_NAME "nzxt_kraken3" 40 | #define STATUS_REPORT_ID 0x75 41 | #define FIRMWARE_REPORT_ID 0x11 42 | #define STATUS_VALIDITY 2000 /* In ms, equivalent to period of four status reports */ 43 | #define CUSTOM_CURVE_POINTS 40 /* For temps from 20C to 59C (critical temp) */ 44 | #define PUMP_DUTY_MIN 20 /* In percent */ 45 | 46 | /* Sensor report offsets for Kraken X53 and Z53 */ 47 | #define TEMP_SENSOR_START_OFFSET 15 48 | #define TEMP_SENSOR_END_OFFSET 16 49 | #define PUMP_SPEED_OFFSET 17 50 | #define PUMP_DUTY_OFFSET 19 51 | 52 | /* Firmware version report offset for Kraken X53 and Z53 */ 53 | #define FIRMWARE_VERSION_OFFSET 17 54 | 55 | /* Sensor report offsets for Kraken Z53 */ 56 | #define Z53_FAN_SPEED_OFFSET 23 57 | #define Z53_FAN_DUTY_OFFSET 25 58 | 59 | /* Report offsets for control commands for Kraken X53 and Z53 */ 60 | #define SET_DUTY_ID_OFFSET 1 61 | 62 | /* Control commands and their lengths for Kraken X53 and Z53 */ 63 | 64 | /* Last byte sets the report interval at 0.5s */ 65 | static const u8 set_interval_cmd[] = { 0x70, 0x02, 0x01, 0xB8, 1 }; 66 | static const u8 finish_init_cmd[] = { 0x70, 0x01 }; 67 | static const u8 __maybe_unused get_fw_version_cmd[] = { 0x10, 0x01 }; 68 | static const u8 set_pump_duty_cmd_header[] = { 0x72, 0x00, 0x00, 0x00 }; 69 | static const u8 z53_get_status_cmd[] = { 0x74, 0x01 }; 70 | 71 | #define SET_INTERVAL_CMD_LENGTH 5 72 | #define FINISH_INIT_CMD_LENGTH 2 73 | #define GET_FW_VERSION_CMD_LENGTH 2 74 | #define MAX_REPORT_LENGTH 64 75 | #define MIN_REPORT_LENGTH 20 76 | #define SET_CURVE_DUTY_CMD_HEADER_LENGTH 4 77 | /* 4 byte header and 40 duty offsets */ 78 | #define SET_CURVE_DUTY_CMD_LENGTH (4 + 40) 79 | #define Z53_GET_STATUS_CMD_LENGTH 2 80 | 81 | static const char *const kraken3_temp_label[] = { 82 | "Coolant temp", 83 | }; 84 | 85 | static const char *const kraken3_fan_label[] = { 86 | "Pump speed", 87 | "Fan speed" 88 | }; 89 | 90 | struct kraken3_channel_info { 91 | enum pwm_enable mode; 92 | 93 | /* Both values are PWM */ 94 | u16 reported_duty; 95 | u16 fixed_duty; /* Manually set fixed duty */ 96 | 97 | u8 pwm_points[CUSTOM_CURVE_POINTS]; 98 | }; 99 | 100 | struct kraken3_data { 101 | struct hid_device *hdev; 102 | struct device *hwmon_dev; 103 | struct dentry *debugfs; 104 | struct mutex buffer_lock; /* For locking access to buffer */ 105 | struct mutex z53_status_request_lock; 106 | struct completion fw_version_processed; 107 | /* 108 | * For X53 devices, tracks whether an initial (one) sensor report was received to 109 | * make fancontrol not bail outright. For Z53 devices, whether a status report 110 | * was processed after requesting one. 111 | */ 112 | struct completion status_report_processed; 113 | /* For locking the above completion */ 114 | spinlock_t status_completion_lock; 115 | 116 | u8 *buffer; 117 | struct kraken3_channel_info channel_info[2]; /* Pump and fan */ 118 | bool is_device_faulty; 119 | 120 | /* Sensor values */ 121 | s32 temp_input[1]; 122 | u16 fan_input[2]; 123 | 124 | enum kinds kind; 125 | u8 firmware_version[3]; 126 | 127 | unsigned long updated; /* jiffies */ 128 | }; 129 | 130 | static umode_t kraken3_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, 131 | int channel) 132 | { 133 | const struct kraken3_data *priv = data; 134 | 135 | switch (type) { 136 | case hwmon_temp: 137 | if (channel < 1) 138 | return 0444; 139 | break; 140 | case hwmon_fan: 141 | switch (priv->kind) { 142 | case X53: 143 | /* Just the pump */ 144 | if (channel < 1) 145 | return 0444; 146 | break; 147 | case Z53: 148 | case KRAKEN2023: 149 | /* Pump and fan */ 150 | if (channel < 2) 151 | return 0444; 152 | break; 153 | default: 154 | break; 155 | } 156 | break; 157 | case hwmon_pwm: 158 | switch (attr) { 159 | case hwmon_pwm_enable: 160 | case hwmon_pwm_input: 161 | switch (priv->kind) { 162 | case X53: 163 | /* Just the pump */ 164 | if (channel < 1) 165 | return 0644; 166 | break; 167 | case Z53: 168 | case KRAKEN2023: 169 | /* Pump and fan */ 170 | if (channel < 2) 171 | return 0644; 172 | break; 173 | default: 174 | break; 175 | } 176 | break; 177 | default: 178 | break; 179 | } 180 | break; 181 | default: 182 | break; 183 | } 184 | 185 | return 0; 186 | } 187 | 188 | /* 189 | * Writes the command to the device with the rest of the report (up to 64 bytes) filled 190 | * with zeroes. 191 | */ 192 | static int kraken3_write_expanded(struct kraken3_data *priv, const u8 *cmd, int cmd_length) 193 | { 194 | int ret; 195 | 196 | mutex_lock(&priv->buffer_lock); 197 | 198 | memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00); 199 | ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH); 200 | 201 | mutex_unlock(&priv->buffer_lock); 202 | return ret; 203 | } 204 | 205 | static int kraken3_percent_to_pwm(long val) 206 | { 207 | return DIV_ROUND_CLOSEST(val * 255, 100); 208 | } 209 | 210 | static int kraken3_pwm_to_percent(long val, int channel) 211 | { 212 | int percent_value; 213 | 214 | if (val < 0 || val > 255) 215 | return -EINVAL; 216 | 217 | percent_value = DIV_ROUND_CLOSEST(val * 100, 255); 218 | 219 | /* Bring up pump duty to min value if needed */ 220 | if (channel == 0 && percent_value < PUMP_DUTY_MIN) 221 | percent_value = PUMP_DUTY_MIN; 222 | 223 | return percent_value; 224 | } 225 | 226 | static int kraken3_read_x53(struct kraken3_data *priv) 227 | { 228 | int ret; 229 | 230 | if (completion_done(&priv->status_report_processed)) 231 | /* 232 | * We're here because data is stale. This means that sensor reports haven't 233 | * been received for some time in kraken3_raw_event(). On X-series sensor data 234 | * can't be manually requested, so return an error. 235 | */ 236 | return -ENODATA; 237 | 238 | /* 239 | * Data needs to be read, but a sensor report wasn't yet received. It's usually 240 | * fancontrol that requests data this early and it exits if it reads an error code. 241 | * So, wait for the first report to be parsed (but up to STATUS_VALIDITY). 242 | * This does not concern the Z series devices, because they send a sensor report 243 | * only when requested. 244 | */ 245 | ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed, 246 | msecs_to_jiffies(STATUS_VALIDITY)); 247 | if (ret == 0) 248 | return -ETIMEDOUT; 249 | else if (ret < 0) 250 | return ret; 251 | 252 | /* The first sensor report was parsed on time and reading can continue */ 253 | return 0; 254 | } 255 | 256 | /* Covers Z53 and KRAKEN2023 device kinds */ 257 | static int kraken3_read_z53(struct kraken3_data *priv) 258 | { 259 | int ret = mutex_lock_interruptible(&priv->z53_status_request_lock); 260 | 261 | if (ret < 0) 262 | return ret; 263 | 264 | if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { 265 | /* Data is up to date */ 266 | goto unlock_and_return; 267 | } 268 | 269 | /* 270 | * Disable interrupts for a moment to safely reinit the completion, 271 | * as hidraw calls could have allowed one or more readers to complete. 272 | */ 273 | spin_lock_bh(&priv->status_completion_lock); 274 | reinit_completion(&priv->status_report_processed); 275 | spin_unlock_bh(&priv->status_completion_lock); 276 | 277 | /* Send command for getting status */ 278 | ret = kraken3_write_expanded(priv, z53_get_status_cmd, Z53_GET_STATUS_CMD_LENGTH); 279 | if (ret < 0) 280 | goto unlock_and_return; 281 | 282 | /* Wait for completion from kraken3_raw_event() */ 283 | ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed, 284 | msecs_to_jiffies(STATUS_VALIDITY)); 285 | if (ret == 0) 286 | ret = -ETIMEDOUT; 287 | 288 | unlock_and_return: 289 | mutex_unlock(&priv->z53_status_request_lock); 290 | if (ret < 0) 291 | return ret; 292 | 293 | return 0; 294 | } 295 | 296 | static int kraken3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, 297 | long *val) 298 | { 299 | struct kraken3_data *priv = dev_get_drvdata(dev); 300 | int ret; 301 | 302 | if (time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { 303 | if (priv->kind == X53) 304 | ret = kraken3_read_x53(priv); 305 | else 306 | ret = kraken3_read_z53(priv); 307 | 308 | if (ret < 0) 309 | return ret; 310 | 311 | if (priv->is_device_faulty) 312 | return -ENODATA; 313 | } 314 | 315 | switch (type) { 316 | case hwmon_temp: 317 | *val = priv->temp_input[channel]; 318 | break; 319 | case hwmon_fan: 320 | *val = priv->fan_input[channel]; 321 | break; 322 | case hwmon_pwm: 323 | switch (attr) { 324 | case hwmon_pwm_enable: 325 | *val = priv->channel_info[channel].mode; 326 | break; 327 | case hwmon_pwm_input: 328 | *val = priv->channel_info[channel].reported_duty; 329 | break; 330 | default: 331 | return -EOPNOTSUPP; 332 | } 333 | break; 334 | default: 335 | return -EOPNOTSUPP; 336 | } 337 | 338 | return 0; 339 | } 340 | 341 | static int kraken3_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, 342 | int channel, const char **str) 343 | { 344 | switch (type) { 345 | case hwmon_temp: 346 | *str = kraken3_temp_label[channel]; 347 | break; 348 | case hwmon_fan: 349 | *str = kraken3_fan_label[channel]; 350 | break; 351 | default: 352 | return -EOPNOTSUPP; 353 | } 354 | 355 | return 0; 356 | } 357 | 358 | /* Writes custom curve to device */ 359 | static int kraken3_write_curve(struct kraken3_data *priv, u8 *curve_array, int channel) 360 | { 361 | u8 fixed_duty_cmd[SET_CURVE_DUTY_CMD_LENGTH]; 362 | int ret; 363 | 364 | /* Copy command header */ 365 | memcpy(fixed_duty_cmd, set_pump_duty_cmd_header, SET_CURVE_DUTY_CMD_HEADER_LENGTH); 366 | 367 | /* Set the correct ID for writing pump/fan duty (0x01 or 0x02, respectively) */ 368 | fixed_duty_cmd[SET_DUTY_ID_OFFSET] = channel + 1; 369 | 370 | if (priv->kind == KRAKEN2023) { 371 | /* These require 1s in the next one or two slots after SET_DUTY_ID_OFFSET */ 372 | fixed_duty_cmd[SET_DUTY_ID_OFFSET + 1] = 1; 373 | if (channel == 1) /* Fan */ 374 | fixed_duty_cmd[SET_DUTY_ID_OFFSET + 2] = 1; 375 | } 376 | 377 | /* Copy curve to command */ 378 | memcpy(fixed_duty_cmd + SET_CURVE_DUTY_CMD_HEADER_LENGTH, curve_array, CUSTOM_CURVE_POINTS); 379 | 380 | ret = kraken3_write_expanded(priv, fixed_duty_cmd, SET_CURVE_DUTY_CMD_LENGTH); 381 | return ret; 382 | } 383 | 384 | static int kraken3_write_fixed_duty(struct kraken3_data *priv, long val, int channel) 385 | { 386 | u8 fixed_curve_points[CUSTOM_CURVE_POINTS]; 387 | int ret, percent_val, i; 388 | 389 | percent_val = kraken3_pwm_to_percent(val, channel); 390 | if (percent_val < 0) 391 | return percent_val; 392 | 393 | /* 394 | * The devices can only control the duty through a curve. 395 | * Since we're setting a fixed duty here, fill the whole curve 396 | * (ranging from 20C to 59C) with the same duty, except for 397 | * the last point, the critical temperature, where it's maxed 398 | * out for safety. 399 | */ 400 | 401 | /* Fill the custom curve with the fixed value we're setting */ 402 | for (i = 0; i < CUSTOM_CURVE_POINTS - 1; i++) 403 | fixed_curve_points[i] = percent_val; 404 | 405 | /* Force duty to 100% at critical temp */ 406 | fixed_curve_points[CUSTOM_CURVE_POINTS - 1] = 100; 407 | 408 | /* Write the fixed duty curve to the device */ 409 | ret = kraken3_write_curve(priv, fixed_curve_points, channel); 410 | return ret; 411 | } 412 | 413 | static int kraken3_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, 414 | long val) 415 | { 416 | struct kraken3_data *priv = dev_get_drvdata(dev); 417 | int ret; 418 | 419 | switch (type) { 420 | case hwmon_pwm: 421 | switch (attr) { 422 | case hwmon_pwm_input: 423 | /* Remember the last set fixed duty for channel */ 424 | priv->channel_info[channel].fixed_duty = val; 425 | 426 | if (priv->channel_info[channel].mode == manual) { 427 | ret = kraken3_write_fixed_duty(priv, val, channel); 428 | if (ret < 0) 429 | return ret; 430 | 431 | /* 432 | * Lock onto this value and report it until next interrupt status 433 | * report is received, so userspace tools can continue to work. 434 | */ 435 | priv->channel_info[channel].reported_duty = val; 436 | } 437 | break; 438 | case hwmon_pwm_enable: 439 | if (val < 0 || val > 2) 440 | return -EINVAL; 441 | 442 | switch (val) { 443 | case 0: 444 | /* Set channel to 100%, direct duty value */ 445 | ret = kraken3_write_fixed_duty(priv, 255, channel); 446 | if (ret < 0) 447 | return ret; 448 | 449 | /* We don't control anything anymore */ 450 | priv->channel_info[channel].mode = off; 451 | break; 452 | case 1: 453 | /* Apply the last known direct duty value */ 454 | ret = 455 | kraken3_write_fixed_duty(priv, 456 | priv->channel_info[channel].fixed_duty, 457 | channel); 458 | if (ret < 0) 459 | return ret; 460 | 461 | priv->channel_info[channel].mode = manual; 462 | break; 463 | case 2: 464 | /* Apply the curve and note as enabled */ 465 | ret = 466 | kraken3_write_curve(priv, 467 | priv->channel_info[channel].pwm_points, 468 | channel); 469 | if (ret < 0) 470 | return ret; 471 | 472 | priv->channel_info[channel].mode = curve; 473 | break; 474 | default: 475 | break; 476 | } 477 | break; 478 | default: 479 | return -EOPNOTSUPP; 480 | } 481 | break; 482 | default: 483 | return -EOPNOTSUPP; 484 | } 485 | 486 | return 0; 487 | } 488 | 489 | static ssize_t kraken3_fan_curve_pwm_store(struct device *dev, struct device_attribute *attr, 490 | const char *buf, size_t count) 491 | { 492 | struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); 493 | struct kraken3_data *priv = dev_get_drvdata(dev); 494 | long val; 495 | int ret; 496 | 497 | if (kstrtol(buf, 10, &val) < 0) 498 | return -EINVAL; 499 | 500 | val = kraken3_pwm_to_percent(val, dev_attr->nr); 501 | if (val < 0) 502 | return val; 503 | 504 | priv->channel_info[dev_attr->nr].pwm_points[dev_attr->index] = val; 505 | 506 | if (priv->channel_info[dev_attr->nr].mode == curve) { 507 | /* Apply the curve */ 508 | ret = 509 | kraken3_write_curve(priv, 510 | priv->channel_info[dev_attr->nr].pwm_points, dev_attr->nr); 511 | if (ret < 0) 512 | return ret; 513 | } 514 | 515 | return count; 516 | } 517 | 518 | static umode_t kraken3_curve_props_are_visible(struct kobject *kobj, struct attribute *attr, 519 | int index) 520 | { 521 | struct device *dev = kobj_to_dev(kobj); 522 | struct kraken3_data *priv = dev_get_drvdata(dev); 523 | 524 | /* X53 does not have a fan */ 525 | if (index >= CUSTOM_CURVE_POINTS && priv->kind == X53) 526 | return 0; 527 | 528 | return attr->mode; 529 | } 530 | 531 | /* Custom pump curve from 20C to 59C (critical temp) */ 532 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point1_pwm, kraken3_fan_curve_pwm, 0, 0); 533 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point2_pwm, kraken3_fan_curve_pwm, 0, 1); 534 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point3_pwm, kraken3_fan_curve_pwm, 0, 2); 535 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point4_pwm, kraken3_fan_curve_pwm, 0, 3); 536 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point5_pwm, kraken3_fan_curve_pwm, 0, 4); 537 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point6_pwm, kraken3_fan_curve_pwm, 0, 5); 538 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point7_pwm, kraken3_fan_curve_pwm, 0, 6); 539 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point8_pwm, kraken3_fan_curve_pwm, 0, 7); 540 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point9_pwm, kraken3_fan_curve_pwm, 0, 8); 541 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point10_pwm, kraken3_fan_curve_pwm, 0, 9); 542 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point11_pwm, kraken3_fan_curve_pwm, 0, 10); 543 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point12_pwm, kraken3_fan_curve_pwm, 0, 11); 544 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point13_pwm, kraken3_fan_curve_pwm, 0, 12); 545 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point14_pwm, kraken3_fan_curve_pwm, 0, 13); 546 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point15_pwm, kraken3_fan_curve_pwm, 0, 14); 547 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point16_pwm, kraken3_fan_curve_pwm, 0, 15); 548 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point17_pwm, kraken3_fan_curve_pwm, 0, 16); 549 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point18_pwm, kraken3_fan_curve_pwm, 0, 17); 550 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point19_pwm, kraken3_fan_curve_pwm, 0, 18); 551 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point20_pwm, kraken3_fan_curve_pwm, 0, 19); 552 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point21_pwm, kraken3_fan_curve_pwm, 0, 20); 553 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point22_pwm, kraken3_fan_curve_pwm, 0, 21); 554 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point23_pwm, kraken3_fan_curve_pwm, 0, 22); 555 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point24_pwm, kraken3_fan_curve_pwm, 0, 23); 556 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point25_pwm, kraken3_fan_curve_pwm, 0, 24); 557 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point26_pwm, kraken3_fan_curve_pwm, 0, 25); 558 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point27_pwm, kraken3_fan_curve_pwm, 0, 26); 559 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point28_pwm, kraken3_fan_curve_pwm, 0, 27); 560 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point29_pwm, kraken3_fan_curve_pwm, 0, 28); 561 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point30_pwm, kraken3_fan_curve_pwm, 0, 29); 562 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point31_pwm, kraken3_fan_curve_pwm, 0, 30); 563 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point32_pwm, kraken3_fan_curve_pwm, 0, 31); 564 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point33_pwm, kraken3_fan_curve_pwm, 0, 32); 565 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point34_pwm, kraken3_fan_curve_pwm, 0, 33); 566 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point35_pwm, kraken3_fan_curve_pwm, 0, 34); 567 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point36_pwm, kraken3_fan_curve_pwm, 0, 35); 568 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point37_pwm, kraken3_fan_curve_pwm, 0, 36); 569 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point38_pwm, kraken3_fan_curve_pwm, 0, 37); 570 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point39_pwm, kraken3_fan_curve_pwm, 0, 38); 571 | static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point40_pwm, kraken3_fan_curve_pwm, 0, 39); 572 | 573 | /* Custom fan curve from 20C to 59C (critical temp) */ 574 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point1_pwm, kraken3_fan_curve_pwm, 1, 0); 575 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point2_pwm, kraken3_fan_curve_pwm, 1, 1); 576 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point3_pwm, kraken3_fan_curve_pwm, 1, 2); 577 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point4_pwm, kraken3_fan_curve_pwm, 1, 3); 578 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point5_pwm, kraken3_fan_curve_pwm, 1, 4); 579 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point6_pwm, kraken3_fan_curve_pwm, 1, 5); 580 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point7_pwm, kraken3_fan_curve_pwm, 1, 6); 581 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point8_pwm, kraken3_fan_curve_pwm, 1, 7); 582 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point9_pwm, kraken3_fan_curve_pwm, 1, 8); 583 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point10_pwm, kraken3_fan_curve_pwm, 1, 9); 584 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point11_pwm, kraken3_fan_curve_pwm, 1, 10); 585 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point12_pwm, kraken3_fan_curve_pwm, 1, 11); 586 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point13_pwm, kraken3_fan_curve_pwm, 1, 12); 587 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point14_pwm, kraken3_fan_curve_pwm, 1, 13); 588 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point15_pwm, kraken3_fan_curve_pwm, 1, 14); 589 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point16_pwm, kraken3_fan_curve_pwm, 1, 15); 590 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point17_pwm, kraken3_fan_curve_pwm, 1, 16); 591 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point18_pwm, kraken3_fan_curve_pwm, 1, 17); 592 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point19_pwm, kraken3_fan_curve_pwm, 1, 18); 593 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point20_pwm, kraken3_fan_curve_pwm, 1, 19); 594 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point21_pwm, kraken3_fan_curve_pwm, 1, 20); 595 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point22_pwm, kraken3_fan_curve_pwm, 1, 21); 596 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point23_pwm, kraken3_fan_curve_pwm, 1, 22); 597 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point24_pwm, kraken3_fan_curve_pwm, 1, 23); 598 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point25_pwm, kraken3_fan_curve_pwm, 1, 24); 599 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point26_pwm, kraken3_fan_curve_pwm, 1, 25); 600 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point27_pwm, kraken3_fan_curve_pwm, 1, 26); 601 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point28_pwm, kraken3_fan_curve_pwm, 1, 27); 602 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point29_pwm, kraken3_fan_curve_pwm, 1, 28); 603 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point30_pwm, kraken3_fan_curve_pwm, 1, 29); 604 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point31_pwm, kraken3_fan_curve_pwm, 1, 30); 605 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point32_pwm, kraken3_fan_curve_pwm, 1, 31); 606 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point33_pwm, kraken3_fan_curve_pwm, 1, 32); 607 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point34_pwm, kraken3_fan_curve_pwm, 1, 33); 608 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point35_pwm, kraken3_fan_curve_pwm, 1, 34); 609 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point36_pwm, kraken3_fan_curve_pwm, 1, 35); 610 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point37_pwm, kraken3_fan_curve_pwm, 1, 36); 611 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point38_pwm, kraken3_fan_curve_pwm, 1, 37); 612 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point39_pwm, kraken3_fan_curve_pwm, 1, 38); 613 | static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point40_pwm, kraken3_fan_curve_pwm, 1, 39); 614 | 615 | static struct attribute *kraken3_curve_attrs[] = { 616 | /* Pump control curve */ 617 | &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr, 618 | &sensor_dev_attr_temp1_auto_point2_pwm.dev_attr.attr, 619 | &sensor_dev_attr_temp1_auto_point3_pwm.dev_attr.attr, 620 | &sensor_dev_attr_temp1_auto_point4_pwm.dev_attr.attr, 621 | &sensor_dev_attr_temp1_auto_point5_pwm.dev_attr.attr, 622 | &sensor_dev_attr_temp1_auto_point6_pwm.dev_attr.attr, 623 | &sensor_dev_attr_temp1_auto_point7_pwm.dev_attr.attr, 624 | &sensor_dev_attr_temp1_auto_point8_pwm.dev_attr.attr, 625 | &sensor_dev_attr_temp1_auto_point9_pwm.dev_attr.attr, 626 | &sensor_dev_attr_temp1_auto_point10_pwm.dev_attr.attr, 627 | &sensor_dev_attr_temp1_auto_point11_pwm.dev_attr.attr, 628 | &sensor_dev_attr_temp1_auto_point12_pwm.dev_attr.attr, 629 | &sensor_dev_attr_temp1_auto_point13_pwm.dev_attr.attr, 630 | &sensor_dev_attr_temp1_auto_point14_pwm.dev_attr.attr, 631 | &sensor_dev_attr_temp1_auto_point15_pwm.dev_attr.attr, 632 | &sensor_dev_attr_temp1_auto_point16_pwm.dev_attr.attr, 633 | &sensor_dev_attr_temp1_auto_point17_pwm.dev_attr.attr, 634 | &sensor_dev_attr_temp1_auto_point18_pwm.dev_attr.attr, 635 | &sensor_dev_attr_temp1_auto_point19_pwm.dev_attr.attr, 636 | &sensor_dev_attr_temp1_auto_point20_pwm.dev_attr.attr, 637 | &sensor_dev_attr_temp1_auto_point21_pwm.dev_attr.attr, 638 | &sensor_dev_attr_temp1_auto_point22_pwm.dev_attr.attr, 639 | &sensor_dev_attr_temp1_auto_point23_pwm.dev_attr.attr, 640 | &sensor_dev_attr_temp1_auto_point24_pwm.dev_attr.attr, 641 | &sensor_dev_attr_temp1_auto_point25_pwm.dev_attr.attr, 642 | &sensor_dev_attr_temp1_auto_point26_pwm.dev_attr.attr, 643 | &sensor_dev_attr_temp1_auto_point27_pwm.dev_attr.attr, 644 | &sensor_dev_attr_temp1_auto_point28_pwm.dev_attr.attr, 645 | &sensor_dev_attr_temp1_auto_point29_pwm.dev_attr.attr, 646 | &sensor_dev_attr_temp1_auto_point30_pwm.dev_attr.attr, 647 | &sensor_dev_attr_temp1_auto_point31_pwm.dev_attr.attr, 648 | &sensor_dev_attr_temp1_auto_point32_pwm.dev_attr.attr, 649 | &sensor_dev_attr_temp1_auto_point33_pwm.dev_attr.attr, 650 | &sensor_dev_attr_temp1_auto_point34_pwm.dev_attr.attr, 651 | &sensor_dev_attr_temp1_auto_point35_pwm.dev_attr.attr, 652 | &sensor_dev_attr_temp1_auto_point36_pwm.dev_attr.attr, 653 | &sensor_dev_attr_temp1_auto_point37_pwm.dev_attr.attr, 654 | &sensor_dev_attr_temp1_auto_point38_pwm.dev_attr.attr, 655 | &sensor_dev_attr_temp1_auto_point39_pwm.dev_attr.attr, 656 | &sensor_dev_attr_temp1_auto_point40_pwm.dev_attr.attr, 657 | /* Fan control curve (Z53 only) */ 658 | &sensor_dev_attr_temp2_auto_point1_pwm.dev_attr.attr, 659 | &sensor_dev_attr_temp2_auto_point2_pwm.dev_attr.attr, 660 | &sensor_dev_attr_temp2_auto_point3_pwm.dev_attr.attr, 661 | &sensor_dev_attr_temp2_auto_point4_pwm.dev_attr.attr, 662 | &sensor_dev_attr_temp2_auto_point5_pwm.dev_attr.attr, 663 | &sensor_dev_attr_temp2_auto_point6_pwm.dev_attr.attr, 664 | &sensor_dev_attr_temp2_auto_point7_pwm.dev_attr.attr, 665 | &sensor_dev_attr_temp2_auto_point8_pwm.dev_attr.attr, 666 | &sensor_dev_attr_temp2_auto_point9_pwm.dev_attr.attr, 667 | &sensor_dev_attr_temp2_auto_point10_pwm.dev_attr.attr, 668 | &sensor_dev_attr_temp2_auto_point11_pwm.dev_attr.attr, 669 | &sensor_dev_attr_temp2_auto_point12_pwm.dev_attr.attr, 670 | &sensor_dev_attr_temp2_auto_point13_pwm.dev_attr.attr, 671 | &sensor_dev_attr_temp2_auto_point14_pwm.dev_attr.attr, 672 | &sensor_dev_attr_temp2_auto_point15_pwm.dev_attr.attr, 673 | &sensor_dev_attr_temp2_auto_point16_pwm.dev_attr.attr, 674 | &sensor_dev_attr_temp2_auto_point17_pwm.dev_attr.attr, 675 | &sensor_dev_attr_temp2_auto_point18_pwm.dev_attr.attr, 676 | &sensor_dev_attr_temp2_auto_point19_pwm.dev_attr.attr, 677 | &sensor_dev_attr_temp2_auto_point20_pwm.dev_attr.attr, 678 | &sensor_dev_attr_temp2_auto_point21_pwm.dev_attr.attr, 679 | &sensor_dev_attr_temp2_auto_point22_pwm.dev_attr.attr, 680 | &sensor_dev_attr_temp2_auto_point23_pwm.dev_attr.attr, 681 | &sensor_dev_attr_temp2_auto_point24_pwm.dev_attr.attr, 682 | &sensor_dev_attr_temp2_auto_point25_pwm.dev_attr.attr, 683 | &sensor_dev_attr_temp2_auto_point26_pwm.dev_attr.attr, 684 | &sensor_dev_attr_temp2_auto_point27_pwm.dev_attr.attr, 685 | &sensor_dev_attr_temp2_auto_point28_pwm.dev_attr.attr, 686 | &sensor_dev_attr_temp2_auto_point29_pwm.dev_attr.attr, 687 | &sensor_dev_attr_temp2_auto_point30_pwm.dev_attr.attr, 688 | &sensor_dev_attr_temp2_auto_point31_pwm.dev_attr.attr, 689 | &sensor_dev_attr_temp2_auto_point32_pwm.dev_attr.attr, 690 | &sensor_dev_attr_temp2_auto_point33_pwm.dev_attr.attr, 691 | &sensor_dev_attr_temp2_auto_point34_pwm.dev_attr.attr, 692 | &sensor_dev_attr_temp2_auto_point35_pwm.dev_attr.attr, 693 | &sensor_dev_attr_temp2_auto_point36_pwm.dev_attr.attr, 694 | &sensor_dev_attr_temp2_auto_point37_pwm.dev_attr.attr, 695 | &sensor_dev_attr_temp2_auto_point38_pwm.dev_attr.attr, 696 | &sensor_dev_attr_temp2_auto_point39_pwm.dev_attr.attr, 697 | &sensor_dev_attr_temp2_auto_point40_pwm.dev_attr.attr, 698 | NULL 699 | }; 700 | 701 | static const struct attribute_group kraken3_curves_group = { 702 | .attrs = kraken3_curve_attrs, 703 | .is_visible = kraken3_curve_props_are_visible 704 | }; 705 | 706 | static const struct attribute_group *kraken3_groups[] = { 707 | &kraken3_curves_group, 708 | NULL 709 | }; 710 | 711 | static const struct hwmon_ops kraken3_hwmon_ops = { 712 | .is_visible = kraken3_is_visible, 713 | .read = kraken3_read, 714 | .read_string = kraken3_read_string, 715 | .write = kraken3_write 716 | }; 717 | 718 | static const struct hwmon_channel_info *kraken3_info[] = { 719 | HWMON_CHANNEL_INFO(temp, 720 | HWMON_T_INPUT | HWMON_T_LABEL), 721 | HWMON_CHANNEL_INFO(fan, 722 | HWMON_F_INPUT | HWMON_F_LABEL, 723 | HWMON_F_INPUT | HWMON_F_LABEL, 724 | HWMON_F_INPUT | HWMON_F_LABEL, 725 | HWMON_F_INPUT | HWMON_F_LABEL), 726 | HWMON_CHANNEL_INFO(pwm, 727 | HWMON_PWM_INPUT | HWMON_PWM_ENABLE, 728 | HWMON_PWM_INPUT | HWMON_PWM_ENABLE), 729 | NULL 730 | }; 731 | 732 | static const struct hwmon_chip_info kraken3_chip_info = { 733 | .ops = &kraken3_hwmon_ops, 734 | .info = kraken3_info, 735 | }; 736 | 737 | static int kraken3_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) 738 | { 739 | struct kraken3_data *priv = hid_get_drvdata(hdev); 740 | int i; 741 | 742 | if (size < MIN_REPORT_LENGTH) 743 | return 0; 744 | 745 | if (report->id == FIRMWARE_REPORT_ID) { 746 | /* Read firmware version */ 747 | for (i = 0; i < 3; i++) 748 | priv->firmware_version[i] = data[FIRMWARE_VERSION_OFFSET + i]; 749 | 750 | if (!completion_done(&priv->fw_version_processed)) 751 | complete_all(&priv->fw_version_processed); 752 | 753 | return 0; 754 | } 755 | 756 | if (report->id != STATUS_REPORT_ID) 757 | return 0; 758 | 759 | if (data[TEMP_SENSOR_START_OFFSET] == 0xff && data[TEMP_SENSOR_END_OFFSET] == 0xff) { 760 | hid_err_once(hdev, 761 | "firmware or device is possibly damaged (is SATA power connected?), not parsing reports\n"); 762 | 763 | /* 764 | * Mark first X-series device report as received, 765 | * as well as all for Z-series, if faulty. 766 | */ 767 | spin_lock(&priv->status_completion_lock); 768 | if (priv->kind != X53 || !completion_done(&priv->status_report_processed)) { 769 | priv->is_device_faulty = true; 770 | complete_all(&priv->status_report_processed); 771 | } 772 | spin_unlock(&priv->status_completion_lock); 773 | 774 | return 0; 775 | } 776 | 777 | /* Received normal data */ 778 | priv->is_device_faulty = false; 779 | 780 | /* Temperature and fan sensor readings */ 781 | priv->temp_input[0] = 782 | data[TEMP_SENSOR_START_OFFSET] * 1000 + data[TEMP_SENSOR_END_OFFSET] * 100; 783 | 784 | priv->fan_input[0] = get_unaligned_le16(data + PUMP_SPEED_OFFSET); 785 | priv->channel_info[0].reported_duty = kraken3_percent_to_pwm(data[PUMP_DUTY_OFFSET]); 786 | 787 | spin_lock(&priv->status_completion_lock); 788 | if (priv->kind == X53 && !completion_done(&priv->status_report_processed)) { 789 | /* Mark first X-series device report as received */ 790 | complete_all(&priv->status_report_processed); 791 | } else if (priv->kind == Z53 || priv->kind == KRAKEN2023) { 792 | /* Additional readings for Z53 and KRAKEN2023 */ 793 | priv->fan_input[1] = get_unaligned_le16(data + Z53_FAN_SPEED_OFFSET); 794 | priv->channel_info[1].reported_duty = 795 | kraken3_percent_to_pwm(data[Z53_FAN_DUTY_OFFSET]); 796 | 797 | if (!completion_done(&priv->status_report_processed)) 798 | complete_all(&priv->status_report_processed); 799 | } 800 | spin_unlock(&priv->status_completion_lock); 801 | 802 | priv->updated = jiffies; 803 | 804 | return 0; 805 | } 806 | 807 | static int kraken3_init_device(struct hid_device *hdev) 808 | { 809 | struct kraken3_data *priv = hid_get_drvdata(hdev); 810 | int ret; 811 | 812 | /* Set the polling interval */ 813 | ret = kraken3_write_expanded(priv, set_interval_cmd, SET_INTERVAL_CMD_LENGTH); 814 | if (ret < 0) 815 | return ret; 816 | 817 | /* Finalize the init process */ 818 | ret = kraken3_write_expanded(priv, finish_init_cmd, FINISH_INIT_CMD_LENGTH); 819 | if (ret < 0) 820 | return ret; 821 | 822 | return 0; 823 | } 824 | 825 | static int kraken3_get_fw_ver(struct hid_device *hdev) 826 | { 827 | struct kraken3_data *priv = hid_get_drvdata(hdev); 828 | int ret; 829 | 830 | ret = kraken3_write_expanded(priv, get_fw_version_cmd, GET_FW_VERSION_CMD_LENGTH); 831 | if (ret < 0) 832 | return ret; 833 | 834 | ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed, 835 | msecs_to_jiffies(STATUS_VALIDITY)); 836 | if (ret == 0) 837 | return -ETIMEDOUT; 838 | else if (ret < 0) 839 | return ret; 840 | 841 | return 0; 842 | } 843 | 844 | static int __maybe_unused kraken3_reset_resume(struct hid_device *hdev) 845 | { 846 | int ret; 847 | 848 | ret = kraken3_init_device(hdev); 849 | if (ret) 850 | hid_err(hdev, "req init (reset_resume) failed with %d\n", ret); 851 | 852 | return ret; 853 | } 854 | 855 | static int firmware_version_show(struct seq_file *seqf, void *unused) 856 | { 857 | struct kraken3_data *priv = seqf->private; 858 | 859 | seq_printf(seqf, "%u.%u.%u\n", priv->firmware_version[0], priv->firmware_version[1], 860 | priv->firmware_version[2]); 861 | 862 | return 0; 863 | } 864 | DEFINE_SHOW_ATTRIBUTE(firmware_version); 865 | 866 | static void kraken3_debugfs_init(struct kraken3_data *priv, const char *device_name) 867 | { 868 | char name[64]; 869 | 870 | if (!priv->firmware_version[0]) 871 | return; /* Nothing to display in debugfs */ 872 | 873 | scnprintf(name, sizeof(name), "%s_%s-%s", DRIVER_NAME, device_name, 874 | dev_name(&priv->hdev->dev)); 875 | 876 | priv->debugfs = debugfs_create_dir(name, NULL); 877 | debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); 878 | } 879 | 880 | static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id) 881 | { 882 | struct kraken3_data *priv; 883 | const char *device_name; 884 | int ret; 885 | 886 | priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); 887 | if (!priv) 888 | return -ENOMEM; 889 | 890 | priv->hdev = hdev; 891 | hid_set_drvdata(hdev, priv); 892 | 893 | /* 894 | * Initialize ->updated to STATUS_VALIDITY seconds in the past, making 895 | * the initial empty data invalid for kraken3_read without the need for 896 | * a special case there. 897 | */ 898 | priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY); 899 | 900 | ret = hid_parse(hdev); 901 | if (ret) { 902 | hid_err(hdev, "hid parse failed with %d\n", ret); 903 | return ret; 904 | } 905 | 906 | /* Enable hidraw so existing user-space tools can continue to work */ 907 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 908 | if (ret) { 909 | hid_err(hdev, "hid hw start failed with %d\n", ret); 910 | return ret; 911 | } 912 | 913 | ret = hid_hw_open(hdev); 914 | if (ret) { 915 | hid_err(hdev, "hid hw open failed with %d\n", ret); 916 | goto fail_and_stop; 917 | } 918 | 919 | switch (hdev->product) { 920 | case USB_PRODUCT_ID_X53: 921 | case USB_PRODUCT_ID_X53_SECOND: 922 | priv->kind = X53; 923 | device_name = "x53"; 924 | break; 925 | case USB_PRODUCT_ID_Z53: 926 | priv->kind = Z53; 927 | device_name = "z53"; 928 | break; 929 | case USB_PRODUCT_ID_KRAKEN2023: 930 | priv->kind = KRAKEN2023; 931 | device_name = "kraken2023"; 932 | break; 933 | case USB_PRODUCT_ID_KRAKEN2023_ELITE: 934 | priv->kind = KRAKEN2023; 935 | device_name = "kraken2023elite"; 936 | break; 937 | default: 938 | break; 939 | } 940 | 941 | priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); 942 | if (!priv->buffer) { 943 | ret = -ENOMEM; 944 | goto fail_and_close; 945 | } 946 | 947 | mutex_init(&priv->buffer_lock); 948 | mutex_init(&priv->z53_status_request_lock); 949 | init_completion(&priv->fw_version_processed); 950 | init_completion(&priv->status_report_processed); 951 | spin_lock_init(&priv->status_completion_lock); 952 | 953 | hid_device_io_start(hdev); 954 | ret = kraken3_init_device(hdev); 955 | if (ret < 0) { 956 | hid_err(hdev, "device init failed with %d\n", ret); 957 | goto fail_and_close; 958 | } 959 | 960 | ret = kraken3_get_fw_ver(hdev); 961 | if (ret < 0) 962 | hid_warn(hdev, "fw version request failed with %d\n", ret); 963 | 964 | priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, device_name, priv, 965 | &kraken3_chip_info, kraken3_groups); 966 | if (IS_ERR(priv->hwmon_dev)) { 967 | ret = PTR_ERR(priv->hwmon_dev); 968 | hid_err(hdev, "hwmon registration failed with %d\n", ret); 969 | goto fail_and_close; 970 | } 971 | 972 | kraken3_debugfs_init(priv, device_name); 973 | 974 | return 0; 975 | 976 | fail_and_close: 977 | hid_hw_close(hdev); 978 | fail_and_stop: 979 | hid_hw_stop(hdev); 980 | return ret; 981 | } 982 | 983 | static void kraken3_remove(struct hid_device *hdev) 984 | { 985 | struct kraken3_data *priv = hid_get_drvdata(hdev); 986 | 987 | debugfs_remove_recursive(priv->debugfs); 988 | hwmon_device_unregister(priv->hwmon_dev); 989 | 990 | hid_hw_close(hdev); 991 | hid_hw_stop(hdev); 992 | } 993 | 994 | static const struct hid_device_id kraken3_table[] = { 995 | /* NZXT Kraken X53/X63/X73 have two possible product IDs */ 996 | { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53) }, 997 | { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53_SECOND) }, 998 | { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, 999 | { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, 1000 | { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, 1001 | { } 1002 | }; 1003 | 1004 | MODULE_DEVICE_TABLE(hid, kraken3_table); 1005 | 1006 | static struct hid_driver kraken3_driver = { 1007 | .name = DRIVER_NAME, 1008 | .id_table = kraken3_table, 1009 | .probe = kraken3_probe, 1010 | .remove = kraken3_remove, 1011 | .raw_event = kraken3_raw_event, 1012 | #ifdef CONFIG_PM 1013 | .reset_resume = kraken3_reset_resume, 1014 | #endif 1015 | }; 1016 | 1017 | static int __init kraken3_init(void) 1018 | { 1019 | return hid_register_driver(&kraken3_driver); 1020 | } 1021 | 1022 | static void __exit kraken3_exit(void) 1023 | { 1024 | hid_unregister_driver(&kraken3_driver); 1025 | } 1026 | 1027 | /* When compiled into the kernel, initialize after the HID bus */ 1028 | late_initcall(kraken3_init); 1029 | module_exit(kraken3_exit); 1030 | 1031 | MODULE_LICENSE("GPL"); 1032 | MODULE_AUTHOR("Jonas Malaco "); 1033 | MODULE_AUTHOR("Aleksa Savic "); 1034 | MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers"); 1035 | -------------------------------------------------------------------------------- /drivers/hwmon/nzxt-smart2.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | /* 3 | * Reverse-engineered NZXT RGB & Fan Controller/Smart Device v2 driver. 4 | * 5 | * Copyright (c) 2021 Aleksandr Mezin 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #if KERNEL_VERSION(5, 11, 0) <= LINUX_VERSION_CODE 13 | #include 14 | #else 15 | #include 16 | #endif 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE 25 | #include 26 | #else 27 | #include 28 | #endif 29 | 30 | /* 31 | * The device has only 3 fan channels/connectors. But all HID reports have 32 | * space reserved for up to 8 channels. 33 | */ 34 | #define FAN_CHANNELS 3 35 | #define FAN_CHANNELS_MAX 8 36 | 37 | #define UPDATE_INTERVAL_DEFAULT_MS 1000 38 | 39 | /* These strings match labels on the device exactly */ 40 | static const char *const fan_label[] = { 41 | "FAN 1", 42 | "FAN 2", 43 | "FAN 3", 44 | }; 45 | 46 | static const char *const curr_label[] = { 47 | "FAN 1 Current", 48 | "FAN 2 Current", 49 | "FAN 3 Current", 50 | }; 51 | 52 | static const char *const in_label[] = { 53 | "FAN 1 Voltage", 54 | "FAN 2 Voltage", 55 | "FAN 3 Voltage", 56 | }; 57 | 58 | enum { 59 | INPUT_REPORT_ID_FAN_CONFIG = 0x61, 60 | INPUT_REPORT_ID_FAN_STATUS = 0x67, 61 | }; 62 | 63 | enum { 64 | FAN_STATUS_REPORT_SPEED = 0x02, 65 | FAN_STATUS_REPORT_VOLTAGE = 0x04, 66 | }; 67 | 68 | enum { 69 | FAN_TYPE_NONE = 0, 70 | FAN_TYPE_DC = 1, 71 | FAN_TYPE_PWM = 2, 72 | }; 73 | 74 | struct unknown_static_data { 75 | /* 76 | * Some configuration data? Stays the same after fan speed changes, 77 | * changes in fan configuration, reboots and driver reloads. 78 | * 79 | * The same data in multiple report types. 80 | * 81 | * Byte 12 seems to be the number of fan channels, but I am not sure. 82 | */ 83 | u8 unknown1[14]; 84 | } __packed; 85 | 86 | /* 87 | * The device sends this input report in response to "detect fans" command: 88 | * a 2-byte output report { 0x60, 0x03 }. 89 | */ 90 | struct fan_config_report { 91 | /* report_id should be INPUT_REPORT_ID_FAN_CONFIG = 0x61 */ 92 | u8 report_id; 93 | /* Always 0x03 */ 94 | u8 magic; 95 | struct unknown_static_data unknown_data; 96 | /* Fan type as detected by the device. See FAN_TYPE_* enum. */ 97 | u8 fan_type[FAN_CHANNELS_MAX]; 98 | } __packed; 99 | 100 | /* 101 | * The device sends these reports at a fixed interval (update interval) - 102 | * one report with type = FAN_STATUS_REPORT_SPEED, and one report with type = 103 | * FAN_STATUS_REPORT_VOLTAGE per update interval. 104 | */ 105 | struct fan_status_report { 106 | /* report_id should be INPUT_REPORT_ID_STATUS = 0x67 */ 107 | u8 report_id; 108 | /* FAN_STATUS_REPORT_SPEED = 0x02 or FAN_STATUS_REPORT_VOLTAGE = 0x04 */ 109 | u8 type; 110 | struct unknown_static_data unknown_data; 111 | /* Fan type as detected by the device. See FAN_TYPE_* enum. */ 112 | u8 fan_type[FAN_CHANNELS_MAX]; 113 | 114 | union { 115 | /* When type == FAN_STATUS_REPORT_SPEED */ 116 | struct { 117 | /* 118 | * Fan speed, in RPM. Zero for channels without fans 119 | * connected. 120 | */ 121 | __le16 fan_rpm[FAN_CHANNELS_MAX]; 122 | /* 123 | * Fan duty cycle, in percent. Non-zero even for 124 | * channels without fans connected. 125 | */ 126 | u8 duty_percent[FAN_CHANNELS_MAX]; 127 | /* 128 | * Exactly the same values as duty_percent[], non-zero 129 | * for disconnected fans too. 130 | */ 131 | u8 duty_percent_dup[FAN_CHANNELS_MAX]; 132 | /* "Case Noise" in db */ 133 | u8 noise_db; 134 | } __packed fan_speed; 135 | /* When type == FAN_STATUS_REPORT_VOLTAGE */ 136 | struct { 137 | /* 138 | * Voltage, in millivolts. Non-zero even when fan is 139 | * not connected. 140 | */ 141 | __le16 fan_in[FAN_CHANNELS_MAX]; 142 | /* 143 | * Current, in milliamperes. Near-zero when 144 | * disconnected. 145 | */ 146 | __le16 fan_current[FAN_CHANNELS_MAX]; 147 | } __packed fan_voltage; 148 | } __packed; 149 | } __packed; 150 | 151 | #define OUTPUT_REPORT_SIZE 64 152 | 153 | enum { 154 | OUTPUT_REPORT_ID_INIT_COMMAND = 0x60, 155 | OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62, 156 | }; 157 | 158 | enum { 159 | INIT_COMMAND_SET_UPDATE_INTERVAL = 0x02, 160 | INIT_COMMAND_DETECT_FANS = 0x03, 161 | }; 162 | 163 | /* 164 | * This output report sets pwm duty cycle/target fan speed for one or more 165 | * channels. 166 | */ 167 | struct set_fan_speed_report { 168 | /* report_id should be OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62 */ 169 | u8 report_id; 170 | /* Should be 0x01 */ 171 | u8 magic; 172 | /* To change fan speed on i-th channel, set i-th bit here */ 173 | u8 channel_bit_mask; 174 | /* 175 | * Fan duty cycle/target speed in percent. For voltage-controlled fans, 176 | * the minimal voltage (duty_percent = 1) is about 9V. 177 | * Setting duty_percent to 0 (if the channel is selected in 178 | * channel_bit_mask) turns off the fan completely (regardless of the 179 | * control mode). 180 | */ 181 | u8 duty_percent[FAN_CHANNELS_MAX]; 182 | } __packed; 183 | 184 | struct drvdata { 185 | struct hid_device *hid; 186 | struct device *hwmon; 187 | 188 | u8 fan_duty_percent[FAN_CHANNELS]; 189 | u16 fan_rpm[FAN_CHANNELS]; 190 | bool pwm_status_received; 191 | 192 | u16 fan_in[FAN_CHANNELS]; 193 | u16 fan_curr[FAN_CHANNELS]; 194 | bool voltage_status_received; 195 | 196 | u8 fan_type[FAN_CHANNELS]; 197 | bool fan_config_received; 198 | 199 | /* 200 | * wq is used to wait for *_received flags to become true. 201 | * All accesses to *_received flags and fan_* arrays are performed with 202 | * wq.lock held. 203 | */ 204 | wait_queue_head_t wq; 205 | /* 206 | * mutex is used to: 207 | * 1) Prevent concurrent conflicting changes to update interval and pwm 208 | * values (after sending an output hid report, the corresponding field 209 | * in drvdata must be updated, and only then new output reports can be 210 | * sent). 211 | * 2) Synchronize access to output_buffer (well, the buffer is here, 212 | * because synchronization is necessary anyway - so why not get rid of 213 | * a kmalloc?). 214 | */ 215 | struct mutex mutex; 216 | long update_interval; 217 | u8 output_buffer[OUTPUT_REPORT_SIZE]; 218 | }; 219 | 220 | static long scale_pwm_value(long val, long orig_max, long new_max) 221 | { 222 | if (val <= 0) 223 | return 0; 224 | 225 | /* 226 | * Positive values should not become zero: 0 completely turns off the 227 | * fan. 228 | */ 229 | return max(1L, DIV_ROUND_CLOSEST(min(val, orig_max) * new_max, orig_max)); 230 | } 231 | 232 | static void handle_fan_config_report(struct drvdata *drvdata, void *data, int size) 233 | { 234 | struct fan_config_report *report = data; 235 | int i; 236 | 237 | if (size < sizeof(struct fan_config_report)) 238 | return; 239 | 240 | if (report->magic != 0x03) 241 | return; 242 | 243 | spin_lock(&drvdata->wq.lock); 244 | 245 | for (i = 0; i < FAN_CHANNELS; i++) 246 | drvdata->fan_type[i] = report->fan_type[i]; 247 | 248 | drvdata->fan_config_received = true; 249 | wake_up_all_locked(&drvdata->wq); 250 | spin_unlock(&drvdata->wq.lock); 251 | } 252 | 253 | static void handle_fan_status_report(struct drvdata *drvdata, void *data, int size) 254 | { 255 | struct fan_status_report *report = data; 256 | int i; 257 | 258 | if (size < sizeof(struct fan_status_report)) 259 | return; 260 | 261 | spin_lock(&drvdata->wq.lock); 262 | 263 | /* 264 | * The device sends INPUT_REPORT_ID_FAN_CONFIG = 0x61 report in response 265 | * to "detect fans" command. Only accept other data after getting 0x61, 266 | * to make sure that fan detection is complete. In particular, fan 267 | * detection resets pwm values. 268 | */ 269 | if (!drvdata->fan_config_received) { 270 | spin_unlock(&drvdata->wq.lock); 271 | return; 272 | } 273 | 274 | for (i = 0; i < FAN_CHANNELS; i++) { 275 | if (drvdata->fan_type[i] == report->fan_type[i]) 276 | continue; 277 | 278 | /* 279 | * This should not happen (if my expectations about the device 280 | * are correct). 281 | * 282 | * Even if the userspace sends fan detect command through 283 | * hidraw, fan config report should arrive first. 284 | */ 285 | hid_warn_once(drvdata->hid, 286 | "Fan %d type changed unexpectedly from %d to %d", 287 | i, drvdata->fan_type[i], report->fan_type[i]); 288 | drvdata->fan_type[i] = report->fan_type[i]; 289 | } 290 | 291 | switch (report->type) { 292 | case FAN_STATUS_REPORT_SPEED: 293 | for (i = 0; i < FAN_CHANNELS; i++) { 294 | drvdata->fan_rpm[i] = 295 | get_unaligned_le16(&report->fan_speed.fan_rpm[i]); 296 | drvdata->fan_duty_percent[i] = 297 | report->fan_speed.duty_percent[i]; 298 | } 299 | 300 | drvdata->pwm_status_received = true; 301 | wake_up_all_locked(&drvdata->wq); 302 | break; 303 | 304 | case FAN_STATUS_REPORT_VOLTAGE: 305 | for (i = 0; i < FAN_CHANNELS; i++) { 306 | drvdata->fan_in[i] = 307 | get_unaligned_le16(&report->fan_voltage.fan_in[i]); 308 | drvdata->fan_curr[i] = 309 | get_unaligned_le16(&report->fan_voltage.fan_current[i]); 310 | } 311 | 312 | drvdata->voltage_status_received = true; 313 | wake_up_all_locked(&drvdata->wq); 314 | break; 315 | } 316 | 317 | spin_unlock(&drvdata->wq.lock); 318 | } 319 | 320 | static umode_t nzxt_smart2_hwmon_is_visible(const void *data, 321 | enum hwmon_sensor_types type, 322 | u32 attr, int channel) 323 | { 324 | switch (type) { 325 | case hwmon_pwm: 326 | switch (attr) { 327 | case hwmon_pwm_input: 328 | case hwmon_pwm_enable: 329 | return 0644; 330 | 331 | default: 332 | return 0444; 333 | } 334 | 335 | case hwmon_chip: 336 | switch (attr) { 337 | case hwmon_chip_update_interval: 338 | return 0644; 339 | 340 | default: 341 | return 0444; 342 | } 343 | 344 | default: 345 | return 0444; 346 | } 347 | } 348 | 349 | static int nzxt_smart2_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 350 | u32 attr, int channel, long *val) 351 | { 352 | struct drvdata *drvdata = dev_get_drvdata(dev); 353 | int res = -EINVAL; 354 | 355 | if (type == hwmon_chip) { 356 | switch (attr) { 357 | case hwmon_chip_update_interval: 358 | *val = drvdata->update_interval; 359 | return 0; 360 | 361 | default: 362 | return -EINVAL; 363 | } 364 | } 365 | 366 | spin_lock_irq(&drvdata->wq.lock); 367 | 368 | switch (type) { 369 | case hwmon_pwm: 370 | /* 371 | * fancontrol: 372 | * 1) remembers pwm* values when it starts 373 | * 2) needs pwm*_enable to be 1 on controlled fans 374 | * So make sure we have correct data before allowing pwm* reads. 375 | * Returning errors for pwm of fan speed read can even cause 376 | * fancontrol to shut down. So the wait is unavoidable. 377 | */ 378 | switch (attr) { 379 | case hwmon_pwm_enable: 380 | res = wait_event_interruptible_locked_irq(drvdata->wq, 381 | drvdata->fan_config_received); 382 | if (res) 383 | goto unlock; 384 | 385 | *val = drvdata->fan_type[channel] != FAN_TYPE_NONE; 386 | break; 387 | 388 | case hwmon_pwm_mode: 389 | res = wait_event_interruptible_locked_irq(drvdata->wq, 390 | drvdata->fan_config_received); 391 | if (res) 392 | goto unlock; 393 | 394 | *val = drvdata->fan_type[channel] == FAN_TYPE_PWM; 395 | break; 396 | 397 | case hwmon_pwm_input: 398 | res = wait_event_interruptible_locked_irq(drvdata->wq, 399 | drvdata->pwm_status_received); 400 | if (res) 401 | goto unlock; 402 | 403 | *val = scale_pwm_value(drvdata->fan_duty_percent[channel], 404 | 100, 255); 405 | break; 406 | } 407 | break; 408 | 409 | case hwmon_fan: 410 | /* 411 | * It's not strictly necessary to wait for *_received in the 412 | * remaining cases (fancontrol doesn't care about them). But I'm 413 | * doing it to have consistent behavior. 414 | */ 415 | if (attr == hwmon_fan_input) { 416 | res = wait_event_interruptible_locked_irq(drvdata->wq, 417 | drvdata->pwm_status_received); 418 | if (res) 419 | goto unlock; 420 | 421 | *val = drvdata->fan_rpm[channel]; 422 | } 423 | break; 424 | 425 | case hwmon_in: 426 | if (attr == hwmon_in_input) { 427 | res = wait_event_interruptible_locked_irq(drvdata->wq, 428 | drvdata->voltage_status_received); 429 | if (res) 430 | goto unlock; 431 | 432 | *val = drvdata->fan_in[channel]; 433 | } 434 | break; 435 | 436 | case hwmon_curr: 437 | if (attr == hwmon_curr_input) { 438 | res = wait_event_interruptible_locked_irq(drvdata->wq, 439 | drvdata->voltage_status_received); 440 | if (res) 441 | goto unlock; 442 | 443 | *val = drvdata->fan_curr[channel]; 444 | } 445 | break; 446 | 447 | default: 448 | break; 449 | } 450 | 451 | unlock: 452 | spin_unlock_irq(&drvdata->wq.lock); 453 | return res; 454 | } 455 | 456 | static int send_output_report(struct drvdata *drvdata, const void *data, 457 | size_t data_size) 458 | { 459 | int ret; 460 | 461 | if (data_size > sizeof(drvdata->output_buffer)) 462 | return -EINVAL; 463 | 464 | memcpy(drvdata->output_buffer, data, data_size); 465 | 466 | if (data_size < sizeof(drvdata->output_buffer)) 467 | memset(drvdata->output_buffer + data_size, 0, 468 | sizeof(drvdata->output_buffer) - data_size); 469 | 470 | ret = hid_hw_output_report(drvdata->hid, drvdata->output_buffer, 471 | sizeof(drvdata->output_buffer)); 472 | return ret < 0 ? ret : 0; 473 | } 474 | 475 | static int set_pwm(struct drvdata *drvdata, int channel, long val) 476 | { 477 | int ret; 478 | u8 duty_percent = scale_pwm_value(val, 255, 100); 479 | 480 | struct set_fan_speed_report report = { 481 | .report_id = OUTPUT_REPORT_ID_SET_FAN_SPEED, 482 | .magic = 1, 483 | .channel_bit_mask = 1 << channel 484 | }; 485 | 486 | ret = mutex_lock_interruptible(&drvdata->mutex); 487 | if (ret) 488 | return ret; 489 | 490 | report.duty_percent[channel] = duty_percent; 491 | ret = send_output_report(drvdata, &report, sizeof(report)); 492 | if (ret) 493 | goto unlock; 494 | 495 | /* 496 | * pwmconfig and fancontrol scripts expect pwm writes to take effect 497 | * immediately (i. e. read from pwm* sysfs should return the value 498 | * written into it). The device seems to always accept pwm values - even 499 | * when there is no fan connected - so update pwm status without waiting 500 | * for a report, to make pwmconfig and fancontrol happy. Worst case - 501 | * if the device didn't accept new pwm value for some reason (never seen 502 | * this in practice) - it will be reported incorrectly only until next 503 | * update. This avoids "fan stuck" messages from pwmconfig, and 504 | * fancontrol setting fan speed to 100% during shutdown. 505 | */ 506 | spin_lock_bh(&drvdata->wq.lock); 507 | drvdata->fan_duty_percent[channel] = duty_percent; 508 | spin_unlock_bh(&drvdata->wq.lock); 509 | 510 | unlock: 511 | mutex_unlock(&drvdata->mutex); 512 | return ret; 513 | } 514 | 515 | /* 516 | * Workaround for fancontrol/pwmconfig trying to write to pwm*_enable even if it 517 | * already is 1 and read-only. Otherwise, fancontrol won't restore pwm on 518 | * shutdown properly. 519 | */ 520 | static int set_pwm_enable(struct drvdata *drvdata, int channel, long val) 521 | { 522 | long expected_val; 523 | int res; 524 | 525 | spin_lock_irq(&drvdata->wq.lock); 526 | 527 | res = wait_event_interruptible_locked_irq(drvdata->wq, 528 | drvdata->fan_config_received); 529 | if (res) { 530 | spin_unlock_irq(&drvdata->wq.lock); 531 | return res; 532 | } 533 | 534 | expected_val = drvdata->fan_type[channel] != FAN_TYPE_NONE; 535 | 536 | spin_unlock_irq(&drvdata->wq.lock); 537 | 538 | return (val == expected_val) ? 0 : -EOPNOTSUPP; 539 | } 540 | 541 | /* 542 | * Control byte | Actual update interval in seconds 543 | * 0xff | 65.5 544 | * 0xf7 | 63.46 545 | * 0x7f | 32.74 546 | * 0x3f | 16.36 547 | * 0x1f | 8.17 548 | * 0x0f | 4.07 549 | * 0x07 | 2.02 550 | * 0x03 | 1.00 551 | * 0x02 | 0.744 552 | * 0x01 | 0.488 553 | * 0x00 | 0.25 554 | */ 555 | static u8 update_interval_to_control_byte(long interval) 556 | { 557 | if (interval <= 250) 558 | return 0; 559 | 560 | return clamp_val(1 + DIV_ROUND_CLOSEST(interval - 488, 256), 0, 255); 561 | } 562 | 563 | static long control_byte_to_update_interval(u8 control_byte) 564 | { 565 | if (control_byte == 0) 566 | return 250; 567 | 568 | return 488 + (control_byte - 1) * 256; 569 | } 570 | 571 | static int set_update_interval(struct drvdata *drvdata, long val) 572 | { 573 | u8 control = update_interval_to_control_byte(val); 574 | u8 report[] = { 575 | OUTPUT_REPORT_ID_INIT_COMMAND, 576 | INIT_COMMAND_SET_UPDATE_INTERVAL, 577 | 0x01, 578 | 0xe8, 579 | control, 580 | 0x01, 581 | 0xe8, 582 | control, 583 | }; 584 | int ret; 585 | 586 | ret = send_output_report(drvdata, report, sizeof(report)); 587 | if (ret) 588 | return ret; 589 | 590 | drvdata->update_interval = control_byte_to_update_interval(control); 591 | return 0; 592 | } 593 | 594 | static int init_device(struct drvdata *drvdata, long update_interval) 595 | { 596 | int ret; 597 | static const u8 detect_fans_report[] = { 598 | OUTPUT_REPORT_ID_INIT_COMMAND, 599 | INIT_COMMAND_DETECT_FANS, 600 | }; 601 | 602 | ret = send_output_report(drvdata, detect_fans_report, 603 | sizeof(detect_fans_report)); 604 | if (ret) 605 | return ret; 606 | 607 | return set_update_interval(drvdata, update_interval); 608 | } 609 | 610 | static int nzxt_smart2_hwmon_write(struct device *dev, 611 | enum hwmon_sensor_types type, u32 attr, 612 | int channel, long val) 613 | { 614 | struct drvdata *drvdata = dev_get_drvdata(dev); 615 | int ret; 616 | 617 | switch (type) { 618 | case hwmon_pwm: 619 | switch (attr) { 620 | case hwmon_pwm_enable: 621 | return set_pwm_enable(drvdata, channel, val); 622 | 623 | case hwmon_pwm_input: 624 | return set_pwm(drvdata, channel, val); 625 | 626 | default: 627 | return -EINVAL; 628 | } 629 | 630 | case hwmon_chip: 631 | switch (attr) { 632 | case hwmon_chip_update_interval: 633 | ret = mutex_lock_interruptible(&drvdata->mutex); 634 | if (ret) 635 | return ret; 636 | 637 | ret = set_update_interval(drvdata, val); 638 | 639 | mutex_unlock(&drvdata->mutex); 640 | return ret; 641 | 642 | default: 643 | return -EINVAL; 644 | } 645 | 646 | default: 647 | return -EINVAL; 648 | } 649 | } 650 | 651 | static int nzxt_smart2_hwmon_read_string(struct device *dev, 652 | enum hwmon_sensor_types type, u32 attr, 653 | int channel, const char **str) 654 | { 655 | switch (type) { 656 | case hwmon_fan: 657 | *str = fan_label[channel]; 658 | return 0; 659 | case hwmon_curr: 660 | *str = curr_label[channel]; 661 | return 0; 662 | case hwmon_in: 663 | *str = in_label[channel]; 664 | return 0; 665 | default: 666 | return -EINVAL; 667 | } 668 | } 669 | 670 | static const struct hwmon_ops nzxt_smart2_hwmon_ops = { 671 | .is_visible = nzxt_smart2_hwmon_is_visible, 672 | .read = nzxt_smart2_hwmon_read, 673 | .read_string = nzxt_smart2_hwmon_read_string, 674 | .write = nzxt_smart2_hwmon_write, 675 | }; 676 | 677 | static const struct hwmon_channel_info *nzxt_smart2_channel_info[] = { 678 | HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, 679 | HWMON_F_INPUT | HWMON_F_LABEL, 680 | HWMON_F_INPUT | HWMON_F_LABEL), 681 | HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, 682 | HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, 683 | HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE), 684 | HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, 685 | HWMON_I_INPUT | HWMON_I_LABEL, 686 | HWMON_I_INPUT | HWMON_I_LABEL), 687 | HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, 688 | HWMON_C_INPUT | HWMON_C_LABEL, 689 | HWMON_C_INPUT | HWMON_C_LABEL), 690 | HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), 691 | NULL 692 | }; 693 | 694 | static const struct hwmon_chip_info nzxt_smart2_chip_info = { 695 | .ops = &nzxt_smart2_hwmon_ops, 696 | .info = nzxt_smart2_channel_info, 697 | }; 698 | 699 | static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, 700 | struct hid_report *report, u8 *data, int size) 701 | { 702 | struct drvdata *drvdata = hid_get_drvdata(hdev); 703 | u8 report_id = *data; 704 | 705 | switch (report_id) { 706 | case INPUT_REPORT_ID_FAN_CONFIG: 707 | handle_fan_config_report(drvdata, data, size); 708 | break; 709 | 710 | case INPUT_REPORT_ID_FAN_STATUS: 711 | handle_fan_status_report(drvdata, data, size); 712 | break; 713 | } 714 | 715 | return 0; 716 | } 717 | 718 | static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev) 719 | { 720 | struct drvdata *drvdata = hid_get_drvdata(hdev); 721 | 722 | /* 723 | * Userspace is still frozen (so no concurrent sysfs attribute access 724 | * is possible), but raw_event can already be called concurrently. 725 | */ 726 | spin_lock_bh(&drvdata->wq.lock); 727 | drvdata->fan_config_received = false; 728 | drvdata->pwm_status_received = false; 729 | drvdata->voltage_status_received = false; 730 | spin_unlock_bh(&drvdata->wq.lock); 731 | 732 | return init_device(drvdata, drvdata->update_interval); 733 | } 734 | 735 | static void mutex_fini(void *lock) 736 | { 737 | mutex_destroy(lock); 738 | } 739 | 740 | static int nzxt_smart2_hid_probe(struct hid_device *hdev, 741 | const struct hid_device_id *id) 742 | { 743 | struct drvdata *drvdata; 744 | int ret; 745 | 746 | drvdata = devm_kzalloc(&hdev->dev, sizeof(struct drvdata), GFP_KERNEL); 747 | if (!drvdata) 748 | return -ENOMEM; 749 | 750 | drvdata->hid = hdev; 751 | hid_set_drvdata(hdev, drvdata); 752 | 753 | init_waitqueue_head(&drvdata->wq); 754 | 755 | mutex_init(&drvdata->mutex); 756 | ret = devm_add_action_or_reset(&hdev->dev, mutex_fini, &drvdata->mutex); 757 | if (ret) 758 | return ret; 759 | 760 | ret = hid_parse(hdev); 761 | if (ret) 762 | return ret; 763 | 764 | ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 765 | if (ret) 766 | return ret; 767 | 768 | ret = hid_hw_open(hdev); 769 | if (ret) 770 | goto out_hw_stop; 771 | 772 | hid_device_io_start(hdev); 773 | 774 | init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); 775 | 776 | drvdata->hwmon = 777 | hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, 778 | &nzxt_smart2_chip_info, NULL); 779 | if (IS_ERR(drvdata->hwmon)) { 780 | ret = PTR_ERR(drvdata->hwmon); 781 | goto out_hw_close; 782 | } 783 | 784 | return 0; 785 | 786 | out_hw_close: 787 | hid_hw_close(hdev); 788 | 789 | out_hw_stop: 790 | hid_hw_stop(hdev); 791 | return ret; 792 | } 793 | 794 | static void nzxt_smart2_hid_remove(struct hid_device *hdev) 795 | { 796 | struct drvdata *drvdata = hid_get_drvdata(hdev); 797 | 798 | hwmon_device_unregister(drvdata->hwmon); 799 | 800 | hid_hw_close(hdev); 801 | hid_hw_stop(hdev); 802 | } 803 | 804 | static const struct hid_device_id nzxt_smart2_hid_id_table[] = { 805 | { HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */ 806 | { HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */ 807 | { HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */ 808 | { HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */ 809 | { HID_USB_DEVICE(0x1e71, 0x200f) }, /* NZXT Smart Device V2 */ 810 | { HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */ 811 | { HID_USB_DEVICE(0x1e71, 0x2011) }, /* NZXT RGB & Fan Controller (6 RGB) */ 812 | { HID_USB_DEVICE(0x1e71, 0x2019) }, /* NZXT RGB & Fan Controller (6 RGB) */ 813 | { HID_USB_DEVICE(0x1e71, 0x2020) }, /* NZXT RGB & Fan Controller (6 RGB) */ 814 | {}, 815 | }; 816 | 817 | static struct hid_driver nzxt_smart2_hid_driver = { 818 | .name = "nzxt-smart2", 819 | .id_table = nzxt_smart2_hid_id_table, 820 | .probe = nzxt_smart2_hid_probe, 821 | .remove = nzxt_smart2_hid_remove, 822 | .raw_event = nzxt_smart2_hid_raw_event, 823 | #ifdef CONFIG_PM 824 | .reset_resume = nzxt_smart2_hid_reset_resume, 825 | #endif 826 | }; 827 | 828 | static int __init nzxt_smart2_init(void) 829 | { 830 | return hid_register_driver(&nzxt_smart2_hid_driver); 831 | } 832 | 833 | static void __exit nzxt_smart2_exit(void) 834 | { 835 | hid_unregister_driver(&nzxt_smart2_hid_driver); 836 | } 837 | 838 | MODULE_DEVICE_TABLE(hid, nzxt_smart2_hid_id_table); 839 | MODULE_AUTHOR("Aleksandr Mezin "); 840 | MODULE_DESCRIPTION("Driver for NZXT RGB & Fan Controller/Smart Device V2"); 841 | MODULE_LICENSE("GPL"); 842 | 843 | /* 844 | * With module_init()/module_hid_driver() and the driver built into the kernel: 845 | * 846 | * Driver 'nzxt_smart2' was unable to register with bus_type 'hid' because the 847 | * bus was not initialized. 848 | */ 849 | late_initcall(nzxt_smart2_init); 850 | module_exit(nzxt_smart2_exit); 851 | -------------------------------------------------------------------------------- /gitversion.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" 6 | 7 | # Should be replaced with 'git describe' output by git-archive. 8 | # Unfortunately, '--long' isn't supported. 9 | ARCHIVE_VERSION='v0.1.0-163-g4613127' 10 | 11 | if [[ "$ARCHIVE_VERSION" == "$"* ]]; then 12 | # ARCHIVE_VERSION contains the original placeholder - not a git archive 13 | git describe --long --abbrev=7 | sed -e 's/^v//' 14 | else 15 | echo "$ARCHIVE_VERSION" | sed -e 's/^v//' 16 | fi 17 | -------------------------------------------------------------------------------- /tools/vagrant/cd-to-vagrant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | tasks: 4 | - ansible.builtin.lineinfile: 5 | path: ~/.bash_profile 6 | line: cd /vagrant 7 | -------------------------------------------------------------------------------- /tools/vagrant/dev-tools.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: yes 4 | tasks: 5 | - ansible.builtin.dnf: 6 | name: [kernel-debug, kernel-debug-devel, lm_sensors, usbutils, bc] 7 | state: latest 8 | - ansible.builtin.shell: rpm -ql kernel-debug-core | grep /boot/vmlinuz- 9 | register: kernel_file 10 | - ansible.builtin.shell: "grubby --set-default={{ kernel_file.stdout }}" 11 | - ansible.builtin.reboot: 12 | when: kernel_file.stdout != "/boot/vmlinuz-" + ansible_kernel 13 | --------------------------------------------------------------------------------