linux/drivers/hwmon/aquacomputer_d5next.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360)
   4 *
   5 * Aquacomputer devices send HID reports (with ID 0x01) every second to report
   6 * sensor values.
   7 *
   8 * Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
   9 */
  10
  11#include <linux/debugfs.h>
  12#include <linux/hid.h>
  13#include <linux/hwmon.h>
  14#include <linux/jiffies.h>
  15#include <linux/module.h>
  16#include <linux/seq_file.h>
  17#include <asm/unaligned.h>
  18
  19#define USB_VENDOR_ID_AQUACOMPUTER      0x0c70
  20#define USB_PRODUCT_ID_D5NEXT           0xf00e
  21#define USB_PRODUCT_ID_FARBWERK360      0xf010
  22
  23enum kinds { d5next, farbwerk360 };
  24
  25static const char *const aqc_device_names[] = {
  26        [d5next] = "d5next",
  27        [farbwerk360] = "farbwerk360"
  28};
  29
  30#define DRIVER_NAME                     "aquacomputer_d5next"
  31
  32#define STATUS_REPORT_ID                0x01
  33#define STATUS_UPDATE_INTERVAL          (2 * HZ)        /* In seconds */
  34#define SERIAL_FIRST_PART               3
  35#define SERIAL_SECOND_PART              5
  36#define FIRMWARE_VERSION                13
  37
  38/* Register offsets for the D5 Next pump */
  39#define D5NEXT_POWER_CYCLES             24
  40
  41#define D5NEXT_COOLANT_TEMP             87
  42
  43#define D5NEXT_PUMP_SPEED               116
  44#define D5NEXT_FAN_SPEED                103
  45
  46#define D5NEXT_PUMP_POWER               114
  47#define D5NEXT_FAN_POWER                101
  48
  49#define D5NEXT_PUMP_VOLTAGE             110
  50#define D5NEXT_FAN_VOLTAGE              97
  51#define D5NEXT_5V_VOLTAGE               57
  52
  53#define D5NEXT_PUMP_CURRENT             112
  54#define D5NEXT_FAN_CURRENT              99
  55
  56/* Register offsets for the Farbwerk 360 RGB controller */
  57#define FARBWERK360_NUM_SENSORS         4
  58#define FARBWERK360_SENSOR_START                0x32
  59#define FARBWERK360_SENSOR_SIZE         0x02
  60#define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF
  61
  62/* Labels for D5 Next */
  63#define L_D5NEXT_COOLANT_TEMP           "Coolant temp"
  64
  65static const char *const label_d5next_speeds[] = {
  66        "Pump speed",
  67        "Fan speed"
  68};
  69
  70static const char *const label_d5next_power[] = {
  71        "Pump power",
  72        "Fan power"
  73};
  74
  75static const char *const label_d5next_voltages[] = {
  76        "Pump voltage",
  77        "Fan voltage",
  78        "+5V voltage"
  79};
  80
  81static const char *const label_d5next_current[] = {
  82        "Pump current",
  83        "Fan current"
  84};
  85
  86/* Labels for Farbwerk 360 temperature sensors */
  87static const char *const label_temp_sensors[] = {
  88        "Sensor 1",
  89        "Sensor 2",
  90        "Sensor 3",
  91        "Sensor 4"
  92};
  93
  94struct aqc_data {
  95        struct hid_device *hdev;
  96        struct device *hwmon_dev;
  97        struct dentry *debugfs;
  98        enum kinds kind;
  99        const char *name;
 100
 101        /* General info, same across all devices */
 102        u32 serial_number[2];
 103        u16 firmware_version;
 104
 105        /* D5 Next specific - how many times the device was powered on */
 106        u32 power_cycles;
 107
 108        /* Sensor values */
 109        s32 temp_input[4];
 110        u16 speed_input[2];
 111        u32 power_input[2];
 112        u16 voltage_input[3];
 113        u16 current_input[2];
 114
 115        unsigned long updated;
 116};
 117
 118static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
 119                              int channel)
 120{
 121        const struct aqc_data *priv = data;
 122
 123        switch (type) {
 124        case hwmon_temp:
 125                switch (priv->kind) {
 126                case d5next:
 127                        if (channel == 0)
 128                                return 0444;
 129                        break;
 130                case farbwerk360:
 131                        return 0444;
 132                default:
 133                        break;
 134                }
 135                break;
 136        case hwmon_fan:
 137        case hwmon_power:
 138        case hwmon_in:
 139        case hwmon_curr:
 140                switch (priv->kind) {
 141                case d5next:
 142                        return 0444;
 143                default:
 144                        break;
 145                }
 146                break;
 147        default:
 148                break;
 149        }
 150
 151        return 0;
 152}
 153
 154static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 155                    int channel, long *val)
 156{
 157        struct aqc_data *priv = dev_get_drvdata(dev);
 158
 159        if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL))
 160                return -ENODATA;
 161
 162        switch (type) {
 163        case hwmon_temp:
 164                if (priv->temp_input[channel] == -ENODATA)
 165                        return -ENODATA;
 166
 167                *val = priv->temp_input[channel];
 168                break;
 169        case hwmon_fan:
 170                *val = priv->speed_input[channel];
 171                break;
 172        case hwmon_power:
 173                *val = priv->power_input[channel];
 174                break;
 175        case hwmon_in:
 176                *val = priv->voltage_input[channel];
 177                break;
 178        case hwmon_curr:
 179                *val = priv->current_input[channel];
 180                break;
 181        default:
 182                return -EOPNOTSUPP;
 183        }
 184
 185        return 0;
 186}
 187
 188static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 189                           int channel, const char **str)
 190{
 191        struct aqc_data *priv = dev_get_drvdata(dev);
 192
 193        switch (type) {
 194        case hwmon_temp:
 195                switch (priv->kind) {
 196                case d5next:
 197                        *str = L_D5NEXT_COOLANT_TEMP;
 198                        break;
 199                case farbwerk360:
 200                        *str = label_temp_sensors[channel];
 201                        break;
 202                default:
 203                        break;
 204                }
 205                break;
 206        case hwmon_fan:
 207                switch (priv->kind) {
 208                case d5next:
 209                        *str = label_d5next_speeds[channel];
 210                        break;
 211                default:
 212                        break;
 213                }
 214                break;
 215        case hwmon_power:
 216                switch (priv->kind) {
 217                case d5next:
 218                        *str = label_d5next_power[channel];
 219                        break;
 220                default:
 221                        break;
 222                }
 223                break;
 224        case hwmon_in:
 225                switch (priv->kind) {
 226                case d5next:
 227                        *str = label_d5next_voltages[channel];
 228                        break;
 229                default:
 230                        break;
 231                }
 232                break;
 233        case hwmon_curr:
 234                switch (priv->kind) {
 235                case d5next:
 236                        *str = label_d5next_current[channel];
 237                        break;
 238                default:
 239                        break;
 240                }
 241                break;
 242        default:
 243                return -EOPNOTSUPP;
 244        }
 245
 246        return 0;
 247}
 248
 249static const struct hwmon_ops aqc_hwmon_ops = {
 250        .is_visible = aqc_is_visible,
 251        .read = aqc_read,
 252        .read_string = aqc_read_string,
 253};
 254
 255static const struct hwmon_channel_info *aqc_info[] = {
 256        HWMON_CHANNEL_INFO(temp,
 257                           HWMON_T_INPUT | HWMON_T_LABEL,
 258                           HWMON_T_INPUT | HWMON_T_LABEL,
 259                           HWMON_T_INPUT | HWMON_T_LABEL,
 260                           HWMON_T_INPUT | HWMON_T_LABEL),
 261        HWMON_CHANNEL_INFO(fan,
 262                           HWMON_F_INPUT | HWMON_F_LABEL,
 263                           HWMON_F_INPUT | HWMON_F_LABEL),
 264        HWMON_CHANNEL_INFO(power,
 265                           HWMON_P_INPUT | HWMON_P_LABEL,
 266                           HWMON_P_INPUT | HWMON_P_LABEL),
 267        HWMON_CHANNEL_INFO(in,
 268                           HWMON_I_INPUT | HWMON_I_LABEL,
 269                           HWMON_I_INPUT | HWMON_I_LABEL,
 270                           HWMON_I_INPUT | HWMON_I_LABEL),
 271        HWMON_CHANNEL_INFO(curr,
 272                           HWMON_C_INPUT | HWMON_C_LABEL,
 273                           HWMON_C_INPUT | HWMON_C_LABEL),
 274        NULL
 275};
 276
 277static const struct hwmon_chip_info aqc_chip_info = {
 278        .ops = &aqc_hwmon_ops,
 279        .info = aqc_info,
 280};
 281
 282static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
 283                         int size)
 284{
 285        int i, sensor_value;
 286        struct aqc_data *priv;
 287
 288        if (report->id != STATUS_REPORT_ID)
 289                return 0;
 290
 291        priv = hid_get_drvdata(hdev);
 292
 293        /* Info provided with every report */
 294        priv->serial_number[0] = get_unaligned_be16(data + SERIAL_FIRST_PART);
 295        priv->serial_number[1] = get_unaligned_be16(data + SERIAL_SECOND_PART);
 296        priv->firmware_version = get_unaligned_be16(data + FIRMWARE_VERSION);
 297
 298        /* Sensor readings */
 299        switch (priv->kind) {
 300        case d5next:
 301                priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
 302
 303                priv->temp_input[0] = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
 304
 305                priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
 306                priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
 307
 308                priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
 309                priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
 310
 311                priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
 312                priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
 313                priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
 314
 315                priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
 316                priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
 317                break;
 318        case farbwerk360:
 319                /* Temperature sensor readings */
 320                for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) {
 321                        sensor_value = get_unaligned_be16(data + FARBWERK360_SENSOR_START +
 322                                                          i * FARBWERK360_SENSOR_SIZE);
 323                        if (sensor_value == FARBWERK360_SENSOR_DISCONNECTED)
 324                                priv->temp_input[i] = -ENODATA;
 325                        else
 326                                priv->temp_input[i] = sensor_value * 10;
 327                }
 328                break;
 329        default:
 330                break;
 331        }
 332
 333        priv->updated = jiffies;
 334
 335        return 0;
 336}
 337
 338#ifdef CONFIG_DEBUG_FS
 339
 340static int serial_number_show(struct seq_file *seqf, void *unused)
 341{
 342        struct aqc_data *priv = seqf->private;
 343
 344        seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]);
 345
 346        return 0;
 347}
 348DEFINE_SHOW_ATTRIBUTE(serial_number);
 349
 350static int firmware_version_show(struct seq_file *seqf, void *unused)
 351{
 352        struct aqc_data *priv = seqf->private;
 353
 354        seq_printf(seqf, "%u\n", priv->firmware_version);
 355
 356        return 0;
 357}
 358DEFINE_SHOW_ATTRIBUTE(firmware_version);
 359
 360static int power_cycles_show(struct seq_file *seqf, void *unused)
 361{
 362        struct aqc_data *priv = seqf->private;
 363
 364        seq_printf(seqf, "%u\n", priv->power_cycles);
 365
 366        return 0;
 367}
 368DEFINE_SHOW_ATTRIBUTE(power_cycles);
 369
 370static void aqc_debugfs_init(struct aqc_data *priv)
 371{
 372        char name[64];
 373
 374        scnprintf(name, sizeof(name), "%s_%s-%s", "aquacomputer", priv->name,
 375                  dev_name(&priv->hdev->dev));
 376
 377        priv->debugfs = debugfs_create_dir(name, NULL);
 378        debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
 379        debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
 380
 381        if (priv->kind == d5next)
 382                debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
 383}
 384
 385#else
 386
 387static void aqc_debugfs_init(struct aqc_data *priv)
 388{
 389}
 390
 391#endif
 392
 393static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
 394{
 395        struct aqc_data *priv;
 396        int ret;
 397
 398        priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
 399        if (!priv)
 400                return -ENOMEM;
 401
 402        priv->hdev = hdev;
 403        hid_set_drvdata(hdev, priv);
 404
 405        priv->updated = jiffies - STATUS_UPDATE_INTERVAL;
 406
 407        ret = hid_parse(hdev);
 408        if (ret)
 409                return ret;
 410
 411        ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 412        if (ret)
 413                return ret;
 414
 415        ret = hid_hw_open(hdev);
 416        if (ret)
 417                goto fail_and_stop;
 418
 419        switch (hdev->product) {
 420        case USB_PRODUCT_ID_D5NEXT:
 421                priv->kind = d5next;
 422                break;
 423        case USB_PRODUCT_ID_FARBWERK360:
 424                priv->kind = farbwerk360;
 425                break;
 426        default:
 427                break;
 428        }
 429
 430        priv->name = aqc_device_names[priv->kind];
 431
 432        priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv,
 433                                                          &aqc_chip_info, NULL);
 434
 435        if (IS_ERR(priv->hwmon_dev)) {
 436                ret = PTR_ERR(priv->hwmon_dev);
 437                goto fail_and_close;
 438        }
 439
 440        aqc_debugfs_init(priv);
 441
 442        return 0;
 443
 444fail_and_close:
 445        hid_hw_close(hdev);
 446fail_and_stop:
 447        hid_hw_stop(hdev);
 448        return ret;
 449}
 450
 451static void aqc_remove(struct hid_device *hdev)
 452{
 453        struct aqc_data *priv = hid_get_drvdata(hdev);
 454
 455        debugfs_remove_recursive(priv->debugfs);
 456        hwmon_device_unregister(priv->hwmon_dev);
 457
 458        hid_hw_close(hdev);
 459        hid_hw_stop(hdev);
 460}
 461
 462static const struct hid_device_id aqc_table[] = {
 463        { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) },
 464        { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) },
 465        { }
 466};
 467
 468MODULE_DEVICE_TABLE(hid, aqc_table);
 469
 470static struct hid_driver aqc_driver = {
 471        .name = DRIVER_NAME,
 472        .id_table = aqc_table,
 473        .probe = aqc_probe,
 474        .remove = aqc_remove,
 475        .raw_event = aqc_raw_event,
 476};
 477
 478static int __init aqc_init(void)
 479{
 480        return hid_register_driver(&aqc_driver);
 481}
 482
 483static void __exit aqc_exit(void)
 484{
 485        hid_unregister_driver(&aqc_driver);
 486}
 487
 488/* Request to initialize after the HID bus to ensure it's not being loaded before */
 489late_initcall(aqc_init);
 490module_exit(aqc_exit);
 491
 492MODULE_LICENSE("GPL");
 493MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
 494MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices");
 495