linux/drivers/hwmon/aquacomputer_d5next.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * hwmon driver for Aquacomputer D5 Next watercooling pump
   4 *
   5 * The D5 Next sends HID reports (with ID 0x01) every second to report sensor values
   6 * (coolant temperature, pump and fan speed, voltage, current and power). It responds to
   7 * Get_Report requests, but returns a dummy value of no use.
   8 *
   9 * Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
  10 */
  11
  12#include <asm/unaligned.h>
  13#include <linux/debugfs.h>
  14#include <linux/hid.h>
  15#include <linux/hwmon.h>
  16#include <linux/jiffies.h>
  17#include <linux/module.h>
  18#include <linux/seq_file.h>
  19
  20#define DRIVER_NAME                     "aquacomputer-d5next"
  21
  22#define D5NEXT_STATUS_REPORT_ID 0x01
  23#define D5NEXT_STATUS_UPDATE_INTERVAL   (2 * HZ) /* In seconds */
  24
  25/* Register offsets for the D5 Next pump */
  26
  27#define D5NEXT_SERIAL_FIRST_PART        3
  28#define D5NEXT_SERIAL_SECOND_PART       5
  29#define D5NEXT_FIRMWARE_VERSION 13
  30#define D5NEXT_POWER_CYCLES             24
  31
  32#define D5NEXT_COOLANT_TEMP             87
  33
  34#define D5NEXT_PUMP_SPEED               116
  35#define D5NEXT_FAN_SPEED                103
  36
  37#define D5NEXT_PUMP_POWER               114
  38#define D5NEXT_FAN_POWER                101
  39
  40#define D5NEXT_PUMP_VOLTAGE             110
  41#define D5NEXT_FAN_VOLTAGE              97
  42#define D5NEXT_5V_VOLTAGE               57
  43
  44#define D5NEXT_PUMP_CURRENT             112
  45#define D5NEXT_FAN_CURRENT              99
  46
  47/* Labels for provided values */
  48
  49#define L_COOLANT_TEMP                  "Coolant temp"
  50
  51#define L_PUMP_SPEED                    "Pump speed"
  52#define L_FAN_SPEED                     "Fan speed"
  53
  54#define L_PUMP_POWER                    "Pump power"
  55#define L_FAN_POWER                     "Fan power"
  56
  57#define L_PUMP_VOLTAGE                  "Pump voltage"
  58#define L_FAN_VOLTAGE                   "Fan voltage"
  59#define L_5V_VOLTAGE                    "+5V voltage"
  60
  61#define L_PUMP_CURRENT                  "Pump current"
  62#define L_FAN_CURRENT                   "Fan current"
  63
  64static const char *const label_speeds[] = {
  65        L_PUMP_SPEED,
  66        L_FAN_SPEED,
  67};
  68
  69static const char *const label_power[] = {
  70        L_PUMP_POWER,
  71        L_FAN_POWER,
  72};
  73
  74static const char *const label_voltages[] = {
  75        L_PUMP_VOLTAGE,
  76        L_FAN_VOLTAGE,
  77        L_5V_VOLTAGE,
  78};
  79
  80static const char *const label_current[] = {
  81        L_PUMP_CURRENT,
  82        L_FAN_CURRENT,
  83};
  84
  85struct d5next_data {
  86        struct hid_device *hdev;
  87        struct device *hwmon_dev;
  88        struct dentry *debugfs;
  89        s32 temp_input;
  90        u16 speed_input[2];
  91        u32 power_input[2];
  92        u16 voltage_input[3];
  93        u16 current_input[2];
  94        u32 serial_number[2];
  95        u16 firmware_version;
  96        u32 power_cycles; /* How many times the device was powered on */
  97        unsigned long updated;
  98};
  99
 100static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
 101                                 int channel)
 102{
 103        return 0444;
 104}
 105
 106static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
 107                       long *val)
 108{
 109        struct d5next_data *priv = dev_get_drvdata(dev);
 110
 111        if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL))
 112                return -ENODATA;
 113
 114        switch (type) {
 115        case hwmon_temp:
 116                *val = priv->temp_input;
 117                break;
 118        case hwmon_fan:
 119                *val = priv->speed_input[channel];
 120                break;
 121        case hwmon_power:
 122                *val = priv->power_input[channel];
 123                break;
 124        case hwmon_in:
 125                *val = priv->voltage_input[channel];
 126                break;
 127        case hwmon_curr:
 128                *val = priv->current_input[channel];
 129                break;
 130        default:
 131                return -EOPNOTSUPP;
 132        }
 133
 134        return 0;
 135}
 136
 137static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 138                              int channel, const char **str)
 139{
 140        switch (type) {
 141        case hwmon_temp:
 142                *str = L_COOLANT_TEMP;
 143                break;
 144        case hwmon_fan:
 145                *str = label_speeds[channel];
 146                break;
 147        case hwmon_power:
 148                *str = label_power[channel];
 149                break;
 150        case hwmon_in:
 151                *str = label_voltages[channel];
 152                break;
 153        case hwmon_curr:
 154                *str = label_current[channel];
 155                break;
 156        default:
 157                return -EOPNOTSUPP;
 158        }
 159
 160        return 0;
 161}
 162
 163static const struct hwmon_ops d5next_hwmon_ops = {
 164        .is_visible = d5next_is_visible,
 165        .read = d5next_read,
 166        .read_string = d5next_read_string,
 167};
 168
 169static const struct hwmon_channel_info *d5next_info[] = {
 170        HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
 171        HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL),
 172        HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL),
 173        HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
 174                           HWMON_I_INPUT | HWMON_I_LABEL),
 175        HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL),
 176        NULL
 177};
 178
 179static const struct hwmon_chip_info d5next_chip_info = {
 180        .ops = &d5next_hwmon_ops,
 181        .info = d5next_info,
 182};
 183
 184static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
 185{
 186        struct d5next_data *priv;
 187
 188        if (report->id != D5NEXT_STATUS_REPORT_ID)
 189                return 0;
 190
 191        priv = hid_get_drvdata(hdev);
 192
 193        /* Info provided with every report */
 194
 195        priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART);
 196        priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART);
 197
 198        priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION);
 199        priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
 200
 201        /* Sensor readings */
 202
 203        priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
 204
 205        priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
 206        priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
 207
 208        priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
 209        priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
 210
 211        priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
 212        priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
 213        priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
 214
 215        priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
 216        priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
 217
 218        priv->updated = jiffies;
 219
 220        return 0;
 221}
 222
 223#ifdef CONFIG_DEBUG_FS
 224
 225static int serial_number_show(struct seq_file *seqf, void *unused)
 226{
 227        struct d5next_data *priv = seqf->private;
 228
 229        seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]);
 230
 231        return 0;
 232}
 233DEFINE_SHOW_ATTRIBUTE(serial_number);
 234
 235static int firmware_version_show(struct seq_file *seqf, void *unused)
 236{
 237        struct d5next_data *priv = seqf->private;
 238
 239        seq_printf(seqf, "%u\n", priv->firmware_version);
 240
 241        return 0;
 242}
 243DEFINE_SHOW_ATTRIBUTE(firmware_version);
 244
 245static int power_cycles_show(struct seq_file *seqf, void *unused)
 246{
 247        struct d5next_data *priv = seqf->private;
 248
 249        seq_printf(seqf, "%u\n", priv->power_cycles);
 250
 251        return 0;
 252}
 253DEFINE_SHOW_ATTRIBUTE(power_cycles);
 254
 255static void d5next_debugfs_init(struct d5next_data *priv)
 256{
 257        char name[32];
 258
 259        scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
 260
 261        priv->debugfs = debugfs_create_dir(name, NULL);
 262        debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
 263        debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
 264        debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
 265}
 266
 267#else
 268
 269static void d5next_debugfs_init(struct d5next_data *priv)
 270{
 271}
 272
 273#endif
 274
 275static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
 276{
 277        struct d5next_data *priv;
 278        int ret;
 279
 280        priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
 281        if (!priv)
 282                return -ENOMEM;
 283
 284        priv->hdev = hdev;
 285        hid_set_drvdata(hdev, priv);
 286
 287        priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL;
 288
 289        ret = hid_parse(hdev);
 290        if (ret)
 291                return ret;
 292
 293        ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 294        if (ret)
 295                return ret;
 296
 297        ret = hid_hw_open(hdev);
 298        if (ret)
 299                goto fail_and_stop;
 300
 301        priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv,
 302                                                          &d5next_chip_info, NULL);
 303
 304        if (IS_ERR(priv->hwmon_dev)) {
 305                ret = PTR_ERR(priv->hwmon_dev);
 306                goto fail_and_close;
 307        }
 308
 309        d5next_debugfs_init(priv);
 310
 311        return 0;
 312
 313fail_and_close:
 314        hid_hw_close(hdev);
 315fail_and_stop:
 316        hid_hw_stop(hdev);
 317        return ret;
 318}
 319
 320static void d5next_remove(struct hid_device *hdev)
 321{
 322        struct d5next_data *priv = hid_get_drvdata(hdev);
 323
 324        debugfs_remove_recursive(priv->debugfs);
 325        hwmon_device_unregister(priv->hwmon_dev);
 326
 327        hid_hw_close(hdev);
 328        hid_hw_stop(hdev);
 329}
 330
 331static const struct hid_device_id d5next_table[] = {
 332        { HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */
 333        {},
 334};
 335
 336MODULE_DEVICE_TABLE(hid, d5next_table);
 337
 338static struct hid_driver d5next_driver = {
 339        .name = DRIVER_NAME,
 340        .id_table = d5next_table,
 341        .probe = d5next_probe,
 342        .remove = d5next_remove,
 343        .raw_event = d5next_raw_event,
 344};
 345
 346static int __init d5next_init(void)
 347{
 348        return hid_register_driver(&d5next_driver);
 349}
 350
 351static void __exit d5next_exit(void)
 352{
 353        hid_unregister_driver(&d5next_driver);
 354}
 355
 356/* Request to initialize after the HID bus to ensure it's not being loaded before */
 357
 358late_initcall(d5next_init);
 359module_exit(d5next_exit);
 360
 361MODULE_LICENSE("GPL");
 362MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
 363MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump");
 364