File Index
Files and directories
Sign in
FILE

nzxt-kraken2.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hwmon/nzxt-kraken2.c C
  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 <linux/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 int kraken2_read(struct device *dev, enum hwmon_sensor_types type,
 39			u32 attr, int channel, long *val)
 40{
 41	struct kraken2_priv_data *priv = dev_get_drvdata(dev);
 42
 43	if (time_after(jiffies, priv->updated + STATUS_VALIDITY * HZ))
 44		return -ENODATA;
 45
 46	switch (type) {
 47	case hwmon_temp:
 48		*val = priv->temp_input[channel];
 49		break;
 50	case hwmon_fan:
 51		*val = priv->fan_input[channel];
 52		break;
 53	default:
 54		return -EOPNOTSUPP; /* unreachable */
 55	}
 56
 57	return 0;
 58}
 59
 60static int kraken2_read_string(struct device *dev, enum hwmon_sensor_types type,
 61			       u32 attr, int channel, const char **str)
 62{
 63	switch (type) {
 64	case hwmon_temp:
 65		*str = kraken2_temp_label[channel];
 66		break;
 67	case hwmon_fan:
 68		*str = kraken2_fan_label[channel];
 69		break;
 70	default:
 71		return -EOPNOTSUPP; /* unreachable */
 72	}
 73	return 0;
 74}
 75
 76static const struct hwmon_ops kraken2_hwmon_ops = {
 77	.visible = 0444,
 78	.read = kraken2_read,
 79	.read_string = kraken2_read_string,
 80};
 81
 82static 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),
 88	NULL
 89};
 90
 91static const struct hwmon_chip_info kraken2_chip_info = {
 92	.ops = &kraken2_hwmon_ops,
 93	.info = kraken2_info,
 94};
 95
 96static int kraken2_raw_event(struct hid_device *hdev,
 97			     struct hid_report *report, u8 *data, int size)
 98{
 99	struct kraken2_priv_data *priv;
100
101	if (size < 7 || report->id != STATUS_REPORT_ID)
102		return 0;
103
104	priv = hid_get_drvdata(hdev);
105
106	/*
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.
110	 *
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.
114	 */
115	priv->temp_input[0] = data[1] * 1000 + data[2] * 100;
116
117	priv->fan_input[0] = get_unaligned_be16(data + 3);
118	priv->fan_input[1] = get_unaligned_be16(data + 5);
119
120	priv->updated = jiffies;
121
122	return 0;
123}
124
125static int kraken2_probe(struct hid_device *hdev,
126			 const struct hid_device_id *id)
127{
128	struct kraken2_priv_data *priv;
129	int ret;
130
131	priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
132	if (!priv)
133		return -ENOMEM;
134
135	priv->hid_dev = hdev;
136	hid_set_drvdata(hdev, priv);
137
138	/*
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.
142	 */
143	priv->updated = jiffies - STATUS_VALIDITY * HZ;
144
145	ret = hid_parse(hdev);
146	if (ret) {
147		hid_err(hdev, "hid parse failed with %d\n", ret);
148		return ret;
149	}
150
151	/*
152	 * Enable hidraw so existing user-space tools can continue to work.
153	 */
154	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
155	if (ret) {
156		hid_err(hdev, "hid hw start failed with %d\n", ret);
157		return ret;
158	}
159
160	ret = hid_hw_open(hdev);
161	if (ret) {
162		hid_err(hdev, "hid hw open failed with %d\n", ret);
163		goto fail_and_stop;
164	}
165
166	priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "kraken2",
167							  priv, &kraken2_chip_info,
168							  NULL);
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);
172		goto fail_and_close;
173	}
174
175	return 0;
176
177fail_and_close:
178	hid_hw_close(hdev);
179fail_and_stop:
180	hid_hw_stop(hdev);
181	return ret;
182}
183
184static void kraken2_remove(struct hid_device *hdev)
185{
186	struct kraken2_priv_data *priv = hid_get_drvdata(hdev);
187
188	hwmon_device_unregister(priv->hwmon_dev);
189
190	hid_hw_close(hdev);
191	hid_hw_stop(hdev);
192}
193
194static const struct hid_device_id kraken2_table[] = {
195	{ HID_USB_DEVICE(0x1e71, 0x170e) }, /* NZXT Kraken X42/X52/X62/X72 */
196	{ }
197};
198
199MODULE_DEVICE_TABLE(hid, kraken2_table);
200
201static 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,
207};
208
209static int __init kraken2_init(void)
210{
211	return hid_register_driver(&kraken2_driver);
212}
213
214static void __exit kraken2_exit(void)
215{
216	hid_unregister_driver(&kraken2_driver);
217}
218
219/*
220 * When compiled into the kernel, initialize after the hid bus.
221 */
222late_initcall(kraken2_init);
223module_exit(kraken2_exit);
224
225MODULE_LICENSE("GPL");
226MODULE_AUTHOR("Jonas Malaco <jonas@protocubo.io>");
227MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers");
~/ Read-only file preview.