linux/drivers/hwmon/corsair-cpro.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * corsair-cpro.c - Linux driver for Corsair Commander Pro
   4 * Copyright (C) 2020 Marius Zachmann <mail@mariuszachmann.de>
   5 *
   6 * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers
   7 * still being used. The device does not use report ids. When using hidraw and this driver
   8 * simultaniously, reports could be switched.
   9 */
  10
  11#include <linux/bitops.h>
  12#include <linux/completion.h>
  13#include <linux/hid.h>
  14#include <linux/hwmon.h>
  15#include <linux/kernel.h>
  16#include <linux/module.h>
  17#include <linux/mutex.h>
  18#include <linux/slab.h>
  19#include <linux/types.h>
  20
  21#define USB_VENDOR_ID_CORSAIR                   0x1b1c
  22#define USB_PRODUCT_ID_CORSAIR_COMMANDERPRO     0x0c10
  23#define USB_PRODUCT_ID_CORSAIR_1000D            0x1d00
  24
  25#define OUT_BUFFER_SIZE         63
  26#define IN_BUFFER_SIZE          16
  27#define LABEL_LENGTH            11
  28#define REQ_TIMEOUT             300
  29
  30#define CTL_GET_TMP_CNCT        0x10    /*
  31                                         * returns in bytes 1-4 for each temp sensor:
  32                                         * 0 not connected
  33                                         * 1 connected
  34                                         */
  35#define CTL_GET_TMP             0x11    /*
  36                                         * send: byte 1 is channel, rest zero
  37                                         * rcv:  returns temp for channel in centi-degree celsius
  38                                         * in bytes 1 and 2
  39                                         * returns 0x11 in byte 0 if no sensor is connected
  40                                         */
  41#define CTL_GET_VOLT            0x12    /*
  42                                         * send: byte 1 is rail number: 0 = 12v, 1 = 5v, 2 = 3.3v
  43                                         * rcv:  returns millivolt in bytes 1,2
  44                                         * returns error 0x10 if request is invalid
  45                                         */
  46#define CTL_GET_FAN_CNCT        0x20    /*
  47                                         * returns in bytes 1-6 for each fan:
  48                                         * 0 not connected
  49                                         * 1 3pin
  50                                         * 2 4pin
  51                                         */
  52#define CTL_GET_FAN_RPM         0x21    /*
  53                                         * send: byte 1 is channel, rest zero
  54                                         * rcv:  returns rpm in bytes 1,2
  55                                         */
  56#define CTL_GET_FAN_PWM         0x22    /*
  57                                         * send: byte 1 is channel, rest zero
  58                                         * rcv:  returns pwm in byte 1 if it was set
  59                                         *       returns error 0x12 if fan is controlled via
  60                                         *       fan_target or fan curve
  61                                         */
  62#define CTL_SET_FAN_FPWM        0x23    /*
  63                                         * set fixed pwm
  64                                         * send: byte 1 is fan number
  65                                         * send: byte 2 is percentage from 0 - 100
  66                                         */
  67#define CTL_SET_FAN_TARGET      0x24    /*
  68                                         * set target rpm
  69                                         * send: byte 1 is fan number
  70                                         * send: byte 2-3 is target
  71                                         * device accepts all values from 0x00 - 0xFFFF
  72                                         */
  73
  74#define NUM_FANS                6
  75#define NUM_TEMP_SENSORS        4
  76
  77struct ccp_device {
  78        struct hid_device *hdev;
  79        struct device *hwmon_dev;
  80        struct completion wait_input_report;
  81        struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */
  82        u8 *buffer;
  83        int target[6];
  84        DECLARE_BITMAP(temp_cnct, NUM_TEMP_SENSORS);
  85        DECLARE_BITMAP(fan_cnct, NUM_FANS);
  86        char fan_label[6][LABEL_LENGTH];
  87};
  88
  89/* converts response error in buffer to errno */
  90static int ccp_get_errno(struct ccp_device *ccp)
  91{
  92        switch (ccp->buffer[0]) {
  93        case 0x00: /* success */
  94                return 0;
  95        case 0x01: /* called invalid command */
  96                return -EOPNOTSUPP;
  97        case 0x10: /* called GET_VOLT / GET_TMP with invalid arguments */
  98                return -EINVAL;
  99        case 0x11: /* requested temps of disconnected sensors */
 100        case 0x12: /* requested pwm of not pwm controlled channels */
 101                return -ENODATA;
 102        default:
 103                hid_dbg(ccp->hdev, "unknown device response error: %d", ccp->buffer[0]);
 104                return -EIO;
 105        }
 106}
 107
 108/* send command, check for error in response, response in ccp->buffer */
 109static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2, u8 byte3)
 110{
 111        unsigned long t;
 112        int ret;
 113
 114        memset(ccp->buffer, 0x00, OUT_BUFFER_SIZE);
 115        ccp->buffer[0] = command;
 116        ccp->buffer[1] = byte1;
 117        ccp->buffer[2] = byte2;
 118        ccp->buffer[3] = byte3;
 119
 120        reinit_completion(&ccp->wait_input_report);
 121
 122        ret = hid_hw_output_report(ccp->hdev, ccp->buffer, OUT_BUFFER_SIZE);
 123        if (ret < 0)
 124                return ret;
 125
 126        t = wait_for_completion_timeout(&ccp->wait_input_report, msecs_to_jiffies(REQ_TIMEOUT));
 127        if (!t)
 128                return -ETIMEDOUT;
 129
 130        return ccp_get_errno(ccp);
 131}
 132
 133static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
 134{
 135        struct ccp_device *ccp = hid_get_drvdata(hdev);
 136
 137        /* only copy buffer when requested */
 138        if (completion_done(&ccp->wait_input_report))
 139                return 0;
 140
 141        memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
 142        complete(&ccp->wait_input_report);
 143
 144        return 0;
 145}
 146
 147/* requests and returns single data values depending on channel */
 148static int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data)
 149{
 150        int ret;
 151
 152        mutex_lock(&ccp->mutex);
 153
 154        ret = send_usb_cmd(ccp, command, channel, 0, 0);
 155        if (ret)
 156                goto out_unlock;
 157
 158        ret = ccp->buffer[1];
 159        if (two_byte_data)
 160                ret = (ret << 8) + ccp->buffer[2];
 161
 162out_unlock:
 163        mutex_unlock(&ccp->mutex);
 164        return ret;
 165}
 166
 167static int set_pwm(struct ccp_device *ccp, int channel, long val)
 168{
 169        int ret;
 170
 171        if (val < 0 || val > 255)
 172                return -EINVAL;
 173
 174        /* The Corsair Commander Pro uses values from 0-100 */
 175        val = DIV_ROUND_CLOSEST(val * 100, 255);
 176
 177        mutex_lock(&ccp->mutex);
 178
 179        ret = send_usb_cmd(ccp, CTL_SET_FAN_FPWM, channel, val, 0);
 180        if (!ret)
 181                ccp->target[channel] = -ENODATA;
 182
 183        mutex_unlock(&ccp->mutex);
 184        return ret;
 185}
 186
 187static int set_target(struct ccp_device *ccp, int channel, long val)
 188{
 189        int ret;
 190
 191        val = clamp_val(val, 0, 0xFFFF);
 192        ccp->target[channel] = val;
 193
 194        mutex_lock(&ccp->mutex);
 195        ret = send_usb_cmd(ccp, CTL_SET_FAN_TARGET, channel, val >> 8, val);
 196
 197        mutex_unlock(&ccp->mutex);
 198        return ret;
 199}
 200
 201static int ccp_read_string(struct device *dev, enum hwmon_sensor_types type,
 202                           u32 attr, int channel, const char **str)
 203{
 204        struct ccp_device *ccp = dev_get_drvdata(dev);
 205
 206        switch (type) {
 207        case hwmon_fan:
 208                switch (attr) {
 209                case hwmon_fan_label:
 210                        *str = ccp->fan_label[channel];
 211                        return 0;
 212                default:
 213                        break;
 214                }
 215                break;
 216        default:
 217                break;
 218        }
 219
 220        return -EOPNOTSUPP;
 221}
 222
 223static int ccp_read(struct device *dev, enum hwmon_sensor_types type,
 224                    u32 attr, int channel, long *val)
 225{
 226        struct ccp_device *ccp = dev_get_drvdata(dev);
 227        int ret;
 228
 229        switch (type) {
 230        case hwmon_temp:
 231                switch (attr) {
 232                case hwmon_temp_input:
 233                        ret = get_data(ccp, CTL_GET_TMP, channel, true);
 234                        if (ret < 0)
 235                                return ret;
 236                        *val = ret * 10;
 237                        return 0;
 238                default:
 239                        break;
 240                }
 241                break;
 242        case hwmon_fan:
 243                switch (attr) {
 244                case hwmon_fan_input:
 245                        ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true);
 246                        if (ret < 0)
 247                                return ret;
 248                        *val = ret;
 249                        return 0;
 250                case hwmon_fan_target:
 251                        /* how to read target values from the device is unknown */
 252                        /* driver returns last set value or 0                   */
 253                        if (ccp->target[channel] < 0)
 254                                return -ENODATA;
 255                        *val = ccp->target[channel];
 256                        return 0;
 257                default:
 258                        break;
 259                }
 260                break;
 261        case hwmon_pwm:
 262                switch (attr) {
 263                case hwmon_pwm_input:
 264                        ret = get_data(ccp, CTL_GET_FAN_PWM, channel, false);
 265                        if (ret < 0)
 266                                return ret;
 267                        *val = DIV_ROUND_CLOSEST(ret * 255, 100);
 268                        return 0;
 269                default:
 270                        break;
 271                }
 272                break;
 273        case hwmon_in:
 274                switch (attr) {
 275                case hwmon_in_input:
 276                        ret = get_data(ccp, CTL_GET_VOLT, channel, true);
 277                        if (ret < 0)
 278                                return ret;
 279                        *val = ret;
 280                        return 0;
 281                default:
 282                        break;
 283                }
 284                break;
 285        default:
 286                break;
 287        }
 288
 289        return -EOPNOTSUPP;
 290};
 291
 292static int ccp_write(struct device *dev, enum hwmon_sensor_types type,
 293                     u32 attr, int channel, long val)
 294{
 295        struct ccp_device *ccp = dev_get_drvdata(dev);
 296
 297        switch (type) {
 298        case hwmon_pwm:
 299                switch (attr) {
 300                case hwmon_pwm_input:
 301                        return set_pwm(ccp, channel, val);
 302                default:
 303                        break;
 304                }
 305                break;
 306        case hwmon_fan:
 307                switch (attr) {
 308                case hwmon_fan_target:
 309                        return set_target(ccp, channel, val);
 310                default:
 311                        break;
 312                }
 313        default:
 314                break;
 315        }
 316
 317        return -EOPNOTSUPP;
 318};
 319
 320static umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type,
 321                              u32 attr, int channel)
 322{
 323        const struct ccp_device *ccp = data;
 324
 325        switch (type) {
 326        case hwmon_temp:
 327                if (!test_bit(channel, ccp->temp_cnct))
 328                        break;
 329
 330                switch (attr) {
 331                case hwmon_temp_input:
 332                        return 0444;
 333                case hwmon_temp_label:
 334                        return 0444;
 335                default:
 336                        break;
 337                }
 338                break;
 339        case hwmon_fan:
 340                if (!test_bit(channel, ccp->fan_cnct))
 341                        break;
 342
 343                switch (attr) {
 344                case hwmon_fan_input:
 345                        return 0444;
 346                case hwmon_fan_label:
 347                        return 0444;
 348                case hwmon_fan_target:
 349                        return 0644;
 350                default:
 351                        break;
 352                }
 353                break;
 354        case hwmon_pwm:
 355                if (!test_bit(channel, ccp->fan_cnct))
 356                        break;
 357
 358                switch (attr) {
 359                case hwmon_pwm_input:
 360                        return 0644;
 361                default:
 362                        break;
 363                }
 364                break;
 365        case hwmon_in:
 366                switch (attr) {
 367                case hwmon_in_input:
 368                        return 0444;
 369                default:
 370                        break;
 371                }
 372                break;
 373        default:
 374                break;
 375        }
 376
 377        return 0;
 378};
 379
 380static const struct hwmon_ops ccp_hwmon_ops = {
 381        .is_visible = ccp_is_visible,
 382        .read = ccp_read,
 383        .read_string = ccp_read_string,
 384        .write = ccp_write,
 385};
 386
 387static const struct hwmon_channel_info *ccp_info[] = {
 388        HWMON_CHANNEL_INFO(chip,
 389                           HWMON_C_REGISTER_TZ),
 390        HWMON_CHANNEL_INFO(temp,
 391                           HWMON_T_INPUT,
 392                           HWMON_T_INPUT,
 393                           HWMON_T_INPUT,
 394                           HWMON_T_INPUT
 395                           ),
 396        HWMON_CHANNEL_INFO(fan,
 397                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
 398                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
 399                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
 400                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
 401                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,
 402                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET
 403                           ),
 404        HWMON_CHANNEL_INFO(pwm,
 405                           HWMON_PWM_INPUT,
 406                           HWMON_PWM_INPUT,
 407                           HWMON_PWM_INPUT,
 408                           HWMON_PWM_INPUT,
 409                           HWMON_PWM_INPUT,
 410                           HWMON_PWM_INPUT
 411                           ),
 412        HWMON_CHANNEL_INFO(in,
 413                           HWMON_I_INPUT,
 414                           HWMON_I_INPUT,
 415                           HWMON_I_INPUT
 416                           ),
 417        NULL
 418};
 419
 420static const struct hwmon_chip_info ccp_chip_info = {
 421        .ops = &ccp_hwmon_ops,
 422        .info = ccp_info,
 423};
 424
 425/* read fan connection status and set labels */
 426static int get_fan_cnct(struct ccp_device *ccp)
 427{
 428        int channel;
 429        int mode;
 430        int ret;
 431
 432        ret = send_usb_cmd(ccp, CTL_GET_FAN_CNCT, 0, 0, 0);
 433        if (ret)
 434                return ret;
 435
 436        for (channel = 0; channel < NUM_FANS; channel++) {
 437                mode = ccp->buffer[channel + 1];
 438                if (mode == 0)
 439                        continue;
 440
 441                set_bit(channel, ccp->fan_cnct);
 442                ccp->target[channel] = -ENODATA;
 443
 444                switch (mode) {
 445                case 1:
 446                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 447                                  "fan%d 3pin", channel + 1);
 448                        break;
 449                case 2:
 450                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 451                                  "fan%d 4pin", channel + 1);
 452                        break;
 453                default:
 454                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 455                                  "fan%d other", channel + 1);
 456                        break;
 457                }
 458        }
 459
 460        return 0;
 461}
 462
 463/* read temp sensor connection status */
 464static int get_temp_cnct(struct ccp_device *ccp)
 465{
 466        int channel;
 467        int mode;
 468        int ret;
 469
 470        ret = send_usb_cmd(ccp, CTL_GET_TMP_CNCT, 0, 0, 0);
 471        if (ret)
 472                return ret;
 473
 474        for (channel = 0; channel < NUM_TEMP_SENSORS; channel++) {
 475                mode = ccp->buffer[channel + 1];
 476                if (mode == 0)
 477                        continue;
 478
 479                set_bit(channel, ccp->temp_cnct);
 480        }
 481
 482        return 0;
 483}
 484
 485static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 486{
 487        struct ccp_device *ccp;
 488        int ret;
 489
 490        ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL);
 491        if (!ccp)
 492                return -ENOMEM;
 493
 494        ccp->buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);
 495        if (!ccp->buffer)
 496                return -ENOMEM;
 497
 498        ret = hid_parse(hdev);
 499        if (ret)
 500                return ret;
 501
 502        ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 503        if (ret)
 504                return ret;
 505
 506        ret = hid_hw_open(hdev);
 507        if (ret)
 508                goto out_hw_stop;
 509
 510        ccp->hdev = hdev;
 511        hid_set_drvdata(hdev, ccp);
 512        mutex_init(&ccp->mutex);
 513        init_completion(&ccp->wait_input_report);
 514
 515        hid_device_io_start(hdev);
 516
 517        /* temp and fan connection status only updates when device is powered on */
 518        ret = get_temp_cnct(ccp);
 519        if (ret)
 520                goto out_hw_close;
 521
 522        ret = get_fan_cnct(ccp);
 523        if (ret)
 524                goto out_hw_close;
 525        ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
 526                                                         ccp, &ccp_chip_info, 0);
 527        if (IS_ERR(ccp->hwmon_dev)) {
 528                ret = PTR_ERR(ccp->hwmon_dev);
 529                goto out_hw_close;
 530        }
 531
 532        return 0;
 533
 534out_hw_close:
 535        hid_hw_close(hdev);
 536out_hw_stop:
 537        hid_hw_stop(hdev);
 538        return ret;
 539}
 540
 541static void ccp_remove(struct hid_device *hdev)
 542{
 543        struct ccp_device *ccp = hid_get_drvdata(hdev);
 544
 545        hwmon_device_unregister(ccp->hwmon_dev);
 546        hid_hw_close(hdev);
 547        hid_hw_stop(hdev);
 548}
 549
 550static const struct hid_device_id ccp_devices[] = {
 551        { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_COMMANDERPRO) },
 552        { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_1000D) },
 553        { }
 554};
 555
 556static struct hid_driver ccp_driver = {
 557        .name = "corsair-cpro",
 558        .id_table = ccp_devices,
 559        .probe = ccp_probe,
 560        .remove = ccp_remove,
 561        .raw_event = ccp_raw_event,
 562};
 563
 564MODULE_DEVICE_TABLE(hid, ccp_devices);
 565MODULE_LICENSE("GPL");
 566
 567static int __init ccp_init(void)
 568{
 569        return hid_register_driver(&ccp_driver);
 570}
 571
 572static void __exit ccp_exit(void)
 573{
 574        hid_unregister_driver(&ccp_driver);
 575}
 576
 577/*
 578 * When compiling this driver as built-in, hwmon initcalls will get called before the
 579 * hid driver and this driver would fail to register. late_initcall solves this.
 580 */
 581late_initcall(ccp_init);
 582module_exit(ccp_exit);
 583