linux/drivers/hwmon/nzxt-kraken2.c
<<
>>
Prefs
   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");
 235