nzxt-kraken2.c (5543B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * nzxt-kraken2.c - 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 <jonas@protocubo.io> 10 */ 11 12#include <asm/unaligned.h> 13#include <linux/hid.h> 14#include <linux/hwmon.h> 15#include <linux/jiffies.h> 16#include <linux/module.h> 17 18#define STATUS_REPORT_ID 0x04 19#define STATUS_VALIDITY 2 /* seconds; equivalent to 4 missed updates */ 20 21static const char *const kraken2_temp_label[] = { 22 "Coolant", 23}; 24 25static const char *const kraken2_fan_label[] = { 26 "Fan", 27 "Pump", 28}; 29 30struct kraken2_priv_data { 31 struct hid_device *hid_dev; 32 struct device *hwmon_dev; 33 s32 temp_input[1]; 34 u16 fan_input[2]; 35 unsigned long updated; /* jiffies */ 36}; 37 38static umode_t kraken2_is_visible(const void *data, 39 enum hwmon_sensor_types type, 40 u32 attr, int channel) 41{ 42 return 0444; 43} 44 45static int kraken2_read(struct device *dev, enum hwmon_sensor_types type, 46 u32 attr, int channel, long *val) 47{ 48 struct kraken2_priv_data *priv = dev_get_drvdata(dev); 49 50 if (time_after(jiffies, priv->updated + STATUS_VALIDITY * HZ)) 51 return -ENODATA; 52 53 switch (type) { 54 case hwmon_temp: 55 *val = priv->temp_input[channel]; 56 break; 57 case hwmon_fan: 58 *val = priv->fan_input[channel]; 59 break; 60 default: 61 return -EOPNOTSUPP; /* unreachable */ 62 } 63 64 return 0; 65} 66 67static int kraken2_read_string(struct device *dev, enum hwmon_sensor_types type, 68 u32 attr, int channel, const char **str) 69{ 70 switch (type) { 71 case hwmon_temp: 72 *str = kraken2_temp_label[channel]; 73 break; 74 case hwmon_fan: 75 *str = kraken2_fan_label[channel]; 76 break; 77 default: 78 return -EOPNOTSUPP; /* unreachable */ 79 } 80 return 0; 81} 82 83static const struct hwmon_ops kraken2_hwmon_ops = { 84 .is_visible = kraken2_is_visible, 85 .read = kraken2_read, 86 .read_string = kraken2_read_string, 87}; 88 89static const struct hwmon_channel_info *kraken2_info[] = { 90 HWMON_CHANNEL_INFO(temp, 91 HWMON_T_INPUT | HWMON_T_LABEL), 92 HWMON_CHANNEL_INFO(fan, 93 HWMON_F_INPUT | HWMON_F_LABEL, 94 HWMON_F_INPUT | HWMON_F_LABEL), 95 NULL 96}; 97 98static const struct hwmon_chip_info kraken2_chip_info = { 99 .ops = &kraken2_hwmon_ops, 100 .info = kraken2_info, 101}; 102 103static int kraken2_raw_event(struct hid_device *hdev, 104 struct hid_report *report, u8 *data, int size) 105{ 106 struct kraken2_priv_data *priv; 107 108 if (size < 7 || report->id != STATUS_REPORT_ID) 109 return 0; 110 111 priv = hid_get_drvdata(hdev); 112 113 /* 114 * The fractional byte of the coolant temperature has been observed to 115 * be in the interval [1,9], but some of these steps are also 116 * consistently skipped for certain integer parts. 117 * 118 * For the lack of a better idea, assume that the resolution is 0.1°C, 119 * and that the missing steps are artifacts of how the firmware 120 * processes the raw sensor data. 121 */ 122 priv->temp_input[0] = data[1] * 1000 + data[2] * 100; 123 124 priv->fan_input[0] = get_unaligned_be16(data + 3); 125 priv->fan_input[1] = get_unaligned_be16(data + 5); 126 127 priv->updated = jiffies; 128 129 return 0; 130} 131 132static int kraken2_probe(struct hid_device *hdev, 133 const struct hid_device_id *id) 134{ 135 struct kraken2_priv_data *priv; 136 int ret; 137 138 priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); 139 if (!priv) 140 return -ENOMEM; 141 142 priv->hid_dev = hdev; 143 hid_set_drvdata(hdev, priv); 144 145 /* 146 * Initialize ->updated to STATUS_VALIDITY seconds in the past, making 147 * the initial empty data invalid for kraken2_read without the need for 148 * a special case there. 149 */ 150 priv->updated = jiffies - STATUS_VALIDITY * HZ; 151 152 ret = hid_parse(hdev); 153 if (ret) { 154 hid_err(hdev, "hid parse failed with %d\n", ret); 155 return ret; 156 } 157 158 /* 159 * Enable hidraw so existing user-space tools can continue to work. 160 */ 161 ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 162 if (ret) { 163 hid_err(hdev, "hid hw start failed with %d\n", ret); 164 goto fail_and_stop; 165 } 166 167 ret = hid_hw_open(hdev); 168 if (ret) { 169 hid_err(hdev, "hid hw open failed with %d\n", ret); 170 goto fail_and_close; 171 } 172 173 priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "kraken2", 174 priv, &kraken2_chip_info, 175 NULL); 176 if (IS_ERR(priv->hwmon_dev)) { 177 ret = PTR_ERR(priv->hwmon_dev); 178 hid_err(hdev, "hwmon registration failed with %d\n", ret); 179 goto fail_and_close; 180 } 181 182 return 0; 183 184fail_and_close: 185 hid_hw_close(hdev); 186fail_and_stop: 187 hid_hw_stop(hdev); 188 return ret; 189} 190 191static void kraken2_remove(struct hid_device *hdev) 192{ 193 struct kraken2_priv_data *priv = hid_get_drvdata(hdev); 194 195 hwmon_device_unregister(priv->hwmon_dev); 196 197 hid_hw_close(hdev); 198 hid_hw_stop(hdev); 199} 200 201static const struct hid_device_id kraken2_table[] = { 202 { HID_USB_DEVICE(0x1e71, 0x170e) }, /* NZXT Kraken X42/X52/X62/X72 */ 203 { } 204}; 205 206MODULE_DEVICE_TABLE(hid, kraken2_table); 207 208static struct hid_driver kraken2_driver = { 209 .name = "nzxt-kraken2", 210 .id_table = kraken2_table, 211 .probe = kraken2_probe, 212 .remove = kraken2_remove, 213 .raw_event = kraken2_raw_event, 214}; 215 216static int __init kraken2_init(void) 217{ 218 return hid_register_driver(&kraken2_driver); 219} 220 221static void __exit kraken2_exit(void) 222{ 223 hid_unregister_driver(&kraken2_driver); 224} 225 226/* 227 * When compiled into the kernel, initialize after the hid bus. 228 */ 229late_initcall(kraken2_init); 230module_exit(kraken2_exit); 231 232MODULE_LICENSE("GPL"); 233MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>"); 234MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers");