1 // SPDX-License-Identifier: GPL-2.0+
3 * nzxt-kraken2.c - hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers
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.
9 * Copyright 2019-2021 Jonas Malaco <jonas@protocubo.io>
12 #include <linux/unaligned.h>
13 #include <linux/hid.h>
14 #include <linux/hwmon.h>
15 #include <linux/jiffies.h>
16 #include <linux/module.h>
18 #define STATUS_REPORT_ID 0x04
19 #define STATUS_VALIDITY 2 /* seconds; equivalent to 4 missed updates */
21 static const char *const kraken2_temp_label
[] = {
25 static const char *const kraken2_fan_label
[] = {
30 struct kraken2_priv_data
{
31 struct hid_device
*hid_dev
;
32 struct device
*hwmon_dev
;
35 unsigned long updated
; /* jiffies */
38 static int kraken2_read(struct device
*dev
, enum hwmon_sensor_types type
,
39 u32 attr
, int channel
, long *val
)
41 struct kraken2_priv_data
*priv
= dev_get_drvdata(dev
);
43 if (time_after(jiffies
, priv
->updated
+ STATUS_VALIDITY
* HZ
))
48 *val
= priv
->temp_input
[channel
];
51 *val
= priv
->fan_input
[channel
];
54 return -EOPNOTSUPP
; /* unreachable */
60 static int kraken2_read_string(struct device
*dev
, enum hwmon_sensor_types type
,
61 u32 attr
, int channel
, const char **str
)
65 *str
= kraken2_temp_label
[channel
];
68 *str
= kraken2_fan_label
[channel
];
71 return -EOPNOTSUPP
; /* unreachable */
76 static const struct hwmon_ops kraken2_hwmon_ops
= {
79 .read_string
= kraken2_read_string
,
82 static const struct hwmon_channel_info
* const kraken2_info
[] = {
83 HWMON_CHANNEL_INFO(temp
,
84 HWMON_T_INPUT
| HWMON_T_LABEL
),
85 HWMON_CHANNEL_INFO(fan
,
86 HWMON_F_INPUT
| HWMON_F_LABEL
,
87 HWMON_F_INPUT
| HWMON_F_LABEL
),
91 static const struct hwmon_chip_info kraken2_chip_info
= {
92 .ops
= &kraken2_hwmon_ops
,
96 static int kraken2_raw_event(struct hid_device
*hdev
,
97 struct hid_report
*report
, u8
*data
, int size
)
99 struct kraken2_priv_data
*priv
;
101 if (size
< 7 || report
->id
!= STATUS_REPORT_ID
)
104 priv
= hid_get_drvdata(hdev
);
107 * The fractional byte of the coolant temperature has been observed to
108 * be in the interval [1,9], but some of these steps are also
109 * consistently skipped for certain integer parts.
111 * For the lack of a better idea, assume that the resolution is 0.1°C,
112 * and that the missing steps are artifacts of how the firmware
113 * processes the raw sensor data.
115 priv
->temp_input
[0] = data
[1] * 1000 + data
[2] * 100;
117 priv
->fan_input
[0] = get_unaligned_be16(data
+ 3);
118 priv
->fan_input
[1] = get_unaligned_be16(data
+ 5);
120 priv
->updated
= jiffies
;
125 static int kraken2_probe(struct hid_device
*hdev
,
126 const struct hid_device_id
*id
)
128 struct kraken2_priv_data
*priv
;
131 priv
= devm_kzalloc(&hdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
135 priv
->hid_dev
= hdev
;
136 hid_set_drvdata(hdev
, priv
);
139 * Initialize ->updated to STATUS_VALIDITY seconds in the past, making
140 * the initial empty data invalid for kraken2_read without the need for
141 * a special case there.
143 priv
->updated
= jiffies
- STATUS_VALIDITY
* HZ
;
145 ret
= hid_parse(hdev
);
147 hid_err(hdev
, "hid parse failed with %d\n", ret
);
152 * Enable hidraw so existing user-space tools can continue to work.
154 ret
= hid_hw_start(hdev
, HID_CONNECT_HIDRAW
);
156 hid_err(hdev
, "hid hw start failed with %d\n", ret
);
160 ret
= hid_hw_open(hdev
);
162 hid_err(hdev
, "hid hw open failed with %d\n", ret
);
166 priv
->hwmon_dev
= hwmon_device_register_with_info(&hdev
->dev
, "kraken2",
167 priv
, &kraken2_chip_info
,
169 if (IS_ERR(priv
->hwmon_dev
)) {
170 ret
= PTR_ERR(priv
->hwmon_dev
);
171 hid_err(hdev
, "hwmon registration failed with %d\n", ret
);
184 static void kraken2_remove(struct hid_device
*hdev
)
186 struct kraken2_priv_data
*priv
= hid_get_drvdata(hdev
);
188 hwmon_device_unregister(priv
->hwmon_dev
);
194 static const struct hid_device_id kraken2_table
[] = {
195 { HID_USB_DEVICE(0x1e71, 0x170e) }, /* NZXT Kraken X42/X52/X62/X72 */
199 MODULE_DEVICE_TABLE(hid
, kraken2_table
);
201 static struct hid_driver kraken2_driver
= {
202 .name
= "nzxt-kraken2",
203 .id_table
= kraken2_table
,
204 .probe
= kraken2_probe
,
205 .remove
= kraken2_remove
,
206 .raw_event
= kraken2_raw_event
,
209 static int __init
kraken2_init(void)
211 return hid_register_driver(&kraken2_driver
);
214 static void __exit
kraken2_exit(void)
216 hid_unregister_driver(&kraken2_driver
);
220 * When compiled into the kernel, initialize after the hid bus.
222 late_initcall(kraken2_init
);
223 module_exit(kraken2_exit
);
225 MODULE_LICENSE("GPL");
226 MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>");
227 MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers");