forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
5.4 KiB
234 lines
5.4 KiB
// 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 <[email protected]> |
|
*/ |
|
|
|
#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 *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 <[email protected]>"); |
|
MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X42/X52/X62/X72 coolers");
|
|
|