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                break;
 314        default:
 315                break;
 316        }
 317
 318        return -EOPNOTSUPP;
 319};
 320
 321static umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type,
 322                              u32 attr, int channel)
 323{
 324        const struct ccp_device *ccp = data;
 325
 326        switch (type) {
 327        case hwmon_temp:
 328                if (!test_bit(channel, ccp->temp_cnct))
 329                        break;
 330
 331                switch (attr) {
 332                case hwmon_temp_input:
 333                        return 0444;
 334                case hwmon_temp_label:
 335                        return 0444;
 336                default:
 337                        break;
 338                }
 339                break;
 340        case hwmon_fan:
 341                if (!test_bit(channel, ccp->fan_cnct))
 342                        break;
 343
 344                switch (attr) {
 345                case hwmon_fan_input:
 346                        return 0444;
 347                case hwmon_fan_label:
 348                        return 0444;
 349                case hwmon_fan_target:
 350                        return 0644;
 351                default:
 352                        break;
 353                }
 354                break;
 355        case hwmon_pwm:
 356                if (!test_bit(channel, ccp->fan_cnct))
 357                        break;
 358
 359                switch (attr) {
 360                case hwmon_pwm_input:
 361                        return 0644;
 362                default:
 363                        break;
 364                }
 365                break;
 366        case hwmon_in:
 367                switch (attr) {
 368                case hwmon_in_input:
 369                        return 0444;
 370                default:
 371                        break;
 372                }
 373                break;
 374        default:
 375                break;
 376        }
 377
 378        return 0;
 379};
 380
 381static const struct hwmon_ops ccp_hwmon_ops = {
 382        .is_visible = ccp_is_visible,
 383        .read = ccp_read,
 384        .read_string = ccp_read_string,
 385        .write = ccp_write,
 386};
 387
 388static const struct hwmon_channel_info *ccp_info[] = {
 389        HWMON_CHANNEL_INFO(chip,
 390                           HWMON_C_REGISTER_TZ),
 391        HWMON_CHANNEL_INFO(temp,
 392                           HWMON_T_INPUT,
 393                           HWMON_T_INPUT,
 394                           HWMON_T_INPUT,
 395                           HWMON_T_INPUT
 396                           ),
 397        HWMON_CHANNEL_INFO(fan,
 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                           HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET
 404                           ),
 405        HWMON_CHANNEL_INFO(pwm,
 406                           HWMON_PWM_INPUT,
 407                           HWMON_PWM_INPUT,
 408                           HWMON_PWM_INPUT,
 409                           HWMON_PWM_INPUT,
 410                           HWMON_PWM_INPUT,
 411                           HWMON_PWM_INPUT
 412                           ),
 413        HWMON_CHANNEL_INFO(in,
 414                           HWMON_I_INPUT,
 415                           HWMON_I_INPUT,
 416                           HWMON_I_INPUT
 417                           ),
 418        NULL
 419};
 420
 421static const struct hwmon_chip_info ccp_chip_info = {
 422        .ops = &ccp_hwmon_ops,
 423        .info = ccp_info,
 424};
 425
 426/* read fan connection status and set labels */
 427static int get_fan_cnct(struct ccp_device *ccp)
 428{
 429        int channel;
 430        int mode;
 431        int ret;
 432
 433        ret = send_usb_cmd(ccp, CTL_GET_FAN_CNCT, 0, 0, 0);
 434        if (ret)
 435                return ret;
 436
 437        for (channel = 0; channel < NUM_FANS; channel++) {
 438                mode = ccp->buffer[channel + 1];
 439                if (mode == 0)
 440                        continue;
 441
 442                set_bit(channel, ccp->fan_cnct);
 443                ccp->target[channel] = -ENODATA;
 444
 445                switch (mode) {
 446                case 1:
 447                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 448                                  "fan%d 3pin", channel + 1);
 449                        break;
 450                case 2:
 451                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 452                                  "fan%d 4pin", channel + 1);
 453                        break;
 454                default:
 455                        scnprintf(ccp->fan_label[channel], LABEL_LENGTH,
 456                                  "fan%d other", channel + 1);
 457                        break;
 458                }
 459        }
 460
 461        return 0;
 462}
 463
 464/* read temp sensor connection status */
 465static int get_temp_cnct(struct ccp_device *ccp)
 466{
 467        int channel;
 468        int mode;
 469        int ret;
 470
 471        ret = send_usb_cmd(ccp, CTL_GET_TMP_CNCT, 0, 0, 0);
 472        if (ret)
 473                return ret;
 474
 475        for (channel = 0; channel < NUM_TEMP_SENSORS; channel++) {
 476                mode = ccp->buffer[channel + 1];
 477                if (mode == 0)
 478                        continue;
 479
 480                set_bit(channel, ccp->temp_cnct);
 481        }
 482
 483        return 0;
 484}
 485
 486static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 487{
 488        struct ccp_device *ccp;
 489        int ret;
 490
 491        ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL);
 492        if (!ccp)
 493                return -ENOMEM;
 494
 495        ccp->buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);
 496        if (!ccp->buffer)
 497                return -ENOMEM;
 498
 499        ret = hid_parse(hdev);
 500        if (ret)
 501                return ret;
 502
 503        ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
 504        if (ret)
 505                return ret;
 506
 507        ret = hid_hw_open(hdev);
 508        if (ret)
 509                goto out_hw_stop;
 510
 511        ccp->hdev = hdev;
 512        hid_set_drvdata(hdev, ccp);
 513        mutex_init(&ccp->mutex);
 514        init_completion(&ccp->wait_input_report);
 515
 516        hid_device_io_start(hdev);
 517
 518        /* temp and fan connection status only updates when device is powered on */
 519        ret = get_temp_cnct(ccp);
 520        if (ret)
 521                goto out_hw_close;
 522
 523        ret = get_fan_cnct(ccp);
 524        if (ret)
 525                goto out_hw_close;
 526        ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",
 527                                                         ccp, &ccp_chip_info, 0);
 528        if (IS_ERR(ccp->hwmon_dev)) {
 529                ret = PTR_ERR(ccp->hwmon_dev);
 530                goto out_hw_close;
 531        }
 532
 533        return 0;
 534
 535out_hw_close:
 536        hid_hw_close(hdev);
 537out_hw_stop:
 538        hid_hw_stop(hdev);
 539        return ret;
 540}
 541
 542static void ccp_remove(struct hid_device *hdev)
 543{
 544        struct ccp_device *ccp = hid_get_drvdata(hdev);
 545
 546        hwmon_device_unregister(ccp->hwmon_dev);
 547        hid_hw_close(hdev);
 548        hid_hw_stop(hdev);
 549}
 550
 551static const struct hid_device_id ccp_devices[] = {
 552        { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_COMMANDERPRO) },
 553        { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_1000D) },
 554        { }
 555};
 556
 557static struct hid_driver ccp_driver = {
 558        .name = "corsair-cpro",
 559        .id_table = ccp_devices,
 560        .probe = ccp_probe,
 561        .remove = ccp_remove,
 562        .raw_event = ccp_raw_event,
 563};
 564
 565MODULE_DEVICE_TABLE(hid, ccp_devices);
 566MODULE_LICENSE("GPL");
 567
 568static int __init ccp_init(void)
 569{
 570        return hid_register_driver(&ccp_driver);
 571}
 572
 573static void __exit ccp_exit(void)
 574{
 575        hid_unregister_driver(&ccp_driver);
 576}
 577
 578/*
 579 * When compiling this driver as built-in, hwmon initcalls will get called before the
 580 * hid driver and this driver would fail to register. late_initcall solves this.
 581 */
 582late_initcall(ccp_init);
 583module_exit(ccp_exit);
 584