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