692 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * corsair-cpro.c - Linux driver for Corsair Commander Pro4 * Copyright (C) 2020 Marius Zachmann <mail@mariuszachmann.de>5 *6 * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers7 * still being used. The device does not use report ids. When using hidraw and this driver8 * simultaniously, reports could be switched.9 */10 11#include <linux/bitops.h>12#include <linux/completion.h>13#include <linux/debugfs.h>14#include <linux/hid.h>15#include <linux/hwmon.h>16#include <linux/kernel.h>17#include <linux/module.h>18#include <linux/mutex.h>19#include <linux/seq_file.h>20#include <linux/slab.h>21#include <linux/spinlock.h>22#include <linux/types.h>23 24#define USB_VENDOR_ID_CORSAIR 0x1b1c25#define USB_PRODUCT_ID_CORSAIR_COMMANDERPRO 0x0c1026#define USB_PRODUCT_ID_CORSAIR_1000D 0x1d0027 28#define OUT_BUFFER_SIZE 6329#define IN_BUFFER_SIZE 1630#define LABEL_LENGTH 1131#define REQ_TIMEOUT 30032 33#define CTL_GET_FW_VER 0x02 /* returns the firmware version in bytes 1-3 */34#define CTL_GET_BL_VER 0x06 /* returns the bootloader version in bytes 1-2 */35#define CTL_GET_TMP_CNCT 0x10 /*36 * returns in bytes 1-4 for each temp sensor:37 * 0 not connected38 * 1 connected39 */40#define CTL_GET_TMP 0x11 /*41 * send: byte 1 is channel, rest zero42 * rcv: returns temp for channel in centi-degree celsius43 * in bytes 1 and 244 * returns 0x11 in byte 0 if no sensor is connected45 */46#define CTL_GET_VOLT 0x12 /*47 * send: byte 1 is rail number: 0 = 12v, 1 = 5v, 2 = 3.3v48 * rcv: returns millivolt in bytes 1,249 * returns error 0x10 if request is invalid50 */51#define CTL_GET_FAN_CNCT 0x20 /*52 * returns in bytes 1-6 for each fan:53 * 0 not connected54 * 1 3pin55 * 2 4pin56 */57#define CTL_GET_FAN_RPM 0x21 /*58 * send: byte 1 is channel, rest zero59 * rcv: returns rpm in bytes 1,260 */61#define CTL_GET_FAN_PWM 0x22 /*62 * send: byte 1 is channel, rest zero63 * rcv: returns pwm in byte 1 if it was set64 * returns error 0x12 if fan is controlled via65 * fan_target or fan curve66 */67#define CTL_SET_FAN_FPWM 0x23 /*68 * set fixed pwm69 * send: byte 1 is fan number70 * send: byte 2 is percentage from 0 - 10071 */72#define CTL_SET_FAN_TARGET 0x24 /*73 * set target rpm74 * send: byte 1 is fan number75 * send: byte 2-3 is target76 * device accepts all values from 0x00 - 0xFFFF77 */78 79#define NUM_FANS 680#define NUM_TEMP_SENSORS 481 82struct ccp_device {83 struct hid_device *hdev;84 struct device *hwmon_dev;85 struct dentry *debugfs;86 /* For reinitializing the completion below */87 spinlock_t wait_input_report_lock;88 struct completion wait_input_report;89 struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */90 u8 *cmd_buffer;91 u8 *buffer;92 int target[6];93 DECLARE_BITMAP(temp_cnct, NUM_TEMP_SENSORS);94 DECLARE_BITMAP(fan_cnct, NUM_FANS);95 char fan_label[6][LABEL_LENGTH];96 u8 firmware_ver[3];97 u8 bootloader_ver[2];98};99 100/* converts response error in buffer to errno */101static int ccp_get_errno(struct ccp_device *ccp)102{103 switch (ccp->buffer[0]) {104 case 0x00: /* success */105 return 0;106 case 0x01: /* called invalid command */107 return -EOPNOTSUPP;108 case 0x10: /* called GET_VOLT / GET_TMP with invalid arguments */109 return -EINVAL;110 case 0x11: /* requested temps of disconnected sensors */111 case 0x12: /* requested pwm of not pwm controlled channels */112 return -ENODATA;113 default:114 hid_dbg(ccp->hdev, "unknown device response error: %d", ccp->buffer[0]);115 return -EIO;116 }117}118 119/* send command, check for error in response, response in ccp->buffer */120static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2, u8 byte3)121{122 unsigned long t;123 int ret;124 125 memset(ccp->cmd_buffer, 0x00, OUT_BUFFER_SIZE);126 ccp->cmd_buffer[0] = command;127 ccp->cmd_buffer[1] = byte1;128 ccp->cmd_buffer[2] = byte2;129 ccp->cmd_buffer[3] = byte3;130 131 /*132 * Disable raw event parsing for a moment to safely reinitialize the133 * completion. Reinit is done because hidraw could have triggered134 * the raw event parsing and marked the ccp->wait_input_report135 * completion as done.136 */137 spin_lock_bh(&ccp->wait_input_report_lock);138 reinit_completion(&ccp->wait_input_report);139 spin_unlock_bh(&ccp->wait_input_report_lock);140 141 ret = hid_hw_output_report(ccp->hdev, ccp->cmd_buffer, OUT_BUFFER_SIZE);142 if (ret < 0)143 return ret;144 145 t = wait_for_completion_timeout(&ccp->wait_input_report, msecs_to_jiffies(REQ_TIMEOUT));146 if (!t)147 return -ETIMEDOUT;148 149 return ccp_get_errno(ccp);150}151 152static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)153{154 struct ccp_device *ccp = hid_get_drvdata(hdev);155 156 /* only copy buffer when requested */157 spin_lock(&ccp->wait_input_report_lock);158 if (!completion_done(&ccp->wait_input_report)) {159 memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));160 complete_all(&ccp->wait_input_report);161 }162 spin_unlock(&ccp->wait_input_report_lock);163 164 return 0;165}166 167/* requests and returns single data values depending on channel */168static int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data)169{170 int ret;171 172 mutex_lock(&ccp->mutex);173 174 ret = send_usb_cmd(ccp, command, channel, 0, 0);175 if (ret)176 goto out_unlock;177 178 ret = ccp->buffer[1];179 if (two_byte_data)180 ret = (ret << 8) + ccp->buffer[2];181 182out_unlock:183 mutex_unlock(&ccp->mutex);184 return ret;185}186 187static int set_pwm(struct ccp_device *ccp, int channel, long val)188{189 int ret;190 191 if (val < 0 || val > 255)192 return -EINVAL;193 194 /* The Corsair Commander Pro uses values from 0-100 */195 val = DIV_ROUND_CLOSEST(val * 100, 255);196 197 mutex_lock(&ccp->mutex);198 199 ret = send_usb_cmd(ccp, CTL_SET_FAN_FPWM, channel, val, 0);200 if (!ret)201 ccp->target[channel] = -ENODATA;202 203 mutex_unlock(&ccp->mutex);204 return ret;205}206 207static int set_target(struct ccp_device *ccp, int channel, long val)208{209 int ret;210 211 val = clamp_val(val, 0, 0xFFFF);212 ccp->target[channel] = val;213 214 mutex_lock(&ccp->mutex);215 ret = send_usb_cmd(ccp, CTL_SET_FAN_TARGET, channel, val >> 8, val);216 217 mutex_unlock(&ccp->mutex);218 return ret;219}220 221static int ccp_read_string(struct device *dev, enum hwmon_sensor_types type,222 u32 attr, int channel, const char **str)223{224 struct ccp_device *ccp = dev_get_drvdata(dev);225 226 switch (type) {227 case hwmon_fan:228 switch (attr) {229 case hwmon_fan_label:230 *str = ccp->fan_label[channel];231 return 0;232 default:233 break;234 }235 break;236 default:237 break;238 }239 240 return -EOPNOTSUPP;241}242 243static int ccp_read(struct device *dev, enum hwmon_sensor_types type,244 u32 attr, int channel, long *val)245{246 struct ccp_device *ccp = dev_get_drvdata(dev);247 int ret;248 249 switch (type) {250 case hwmon_temp:251 switch (attr) {252 case hwmon_temp_input:253 ret = get_data(ccp, CTL_GET_TMP, channel, true);254 if (ret < 0)255 return ret;256 *val = ret * 10;257 return 0;258 default:259 break;260 }261 break;262 case hwmon_fan:263 switch (attr) {264 case hwmon_fan_input:265 ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true);266 if (ret < 0)267 return ret;268 *val = ret;269 return 0;270 case hwmon_fan_target:271 /* how to read target values from the device is unknown */272 /* driver returns last set value or 0 */273 if (ccp->target[channel] < 0)274 return -ENODATA;275 *val = ccp->target[channel];276 return 0;277 default:278 break;279 }280 break;281 case hwmon_pwm:282 switch (attr) {283 case hwmon_pwm_input:284 ret = get_data(ccp, CTL_GET_FAN_PWM, channel, false);285 if (ret < 0)286 return ret;287 *val = DIV_ROUND_CLOSEST(ret * 255, 100);288 return 0;289 default:290 break;291 }292 break;293 case hwmon_in:294 switch (attr) {295 case hwmon_in_input:296 ret = get_data(ccp, CTL_GET_VOLT, channel, true);297 if (ret < 0)298 return ret;299 *val = ret;300 return 0;301 default:302 break;303 }304 break;305 default:306 break;307 }308 309 return -EOPNOTSUPP;310};311 312static int ccp_write(struct device *dev, enum hwmon_sensor_types type,313 u32 attr, int channel, long val)314{315 struct ccp_device *ccp = dev_get_drvdata(dev);316 317 switch (type) {318 case hwmon_pwm:319 switch (attr) {320 case hwmon_pwm_input:321 return set_pwm(ccp, channel, val);322 default:323 break;324 }325 break;326 case hwmon_fan:327 switch (attr) {328 case hwmon_fan_target:329 return set_target(ccp, channel, val);330 default:331 break;332 }333 break;334 default:335 break;336 }337 338 return -EOPNOTSUPP;339};340 341static umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type,342 u32 attr, int channel)343{344 const struct ccp_device *ccp = data;345 346 switch (type) {347 case hwmon_temp:348 if (!test_bit(channel, ccp->temp_cnct))349 break;350 351 switch (attr) {352 case hwmon_temp_input:353 return 0444;354 case hwmon_temp_label:355 return 0444;356 default:357 break;358 }359 break;360 case hwmon_fan:361 if (!test_bit(channel, ccp->fan_cnct))362 break;363 364 switch (attr) {365 case hwmon_fan_input:366 return 0444;367 case hwmon_fan_label:368 return 0444;369 case hwmon_fan_target:370 return 0644;371 default:372 break;373 }374 break;375 case hwmon_pwm:376 if (!test_bit(channel, ccp->fan_cnct))377 break;378 379 switch (attr) {380 case hwmon_pwm_input:381 return 0644;382 default:383 break;384 }385 break;386 case hwmon_in:387 switch (attr) {388 case hwmon_in_input:389 return 0444;390 default:391 break;392 }393 break;394 default:395 break;396 }397 398 return 0;399};400 401static const struct hwmon_ops ccp_hwmon_ops = {402 .is_visible = ccp_is_visible,403 .read = ccp_read,404 .read_string = ccp_read_string,405 .write = ccp_write,406};407 408static const struct hwmon_channel_info * const ccp_info[] = {409 HWMON_CHANNEL_INFO(chip,410 HWMON_C_REGISTER_TZ),411 HWMON_CHANNEL_INFO(temp,412 HWMON_T_INPUT,413 HWMON_T_INPUT,414 HWMON_T_INPUT,415 HWMON_T_INPUT416 ),417 HWMON_CHANNEL_INFO(fan,418 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,419 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,420 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,421 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,422 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET,423 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET424 ),425 HWMON_CHANNEL_INFO(pwm,426 HWMON_PWM_INPUT,427 HWMON_PWM_INPUT,428 HWMON_PWM_INPUT,429 HWMON_PWM_INPUT,430 HWMON_PWM_INPUT,431 HWMON_PWM_INPUT432 ),433 HWMON_CHANNEL_INFO(in,434 HWMON_I_INPUT,435 HWMON_I_INPUT,436 HWMON_I_INPUT437 ),438 NULL439};440 441static const struct hwmon_chip_info ccp_chip_info = {442 .ops = &ccp_hwmon_ops,443 .info = ccp_info,444};445 446/* read fan connection status and set labels */447static int get_fan_cnct(struct ccp_device *ccp)448{449 int channel;450 int mode;451 int ret;452 453 ret = send_usb_cmd(ccp, CTL_GET_FAN_CNCT, 0, 0, 0);454 if (ret)455 return ret;456 457 for (channel = 0; channel < NUM_FANS; channel++) {458 mode = ccp->buffer[channel + 1];459 if (mode == 0)460 continue;461 462 set_bit(channel, ccp->fan_cnct);463 ccp->target[channel] = -ENODATA;464 465 switch (mode) {466 case 1:467 scnprintf(ccp->fan_label[channel], LABEL_LENGTH,468 "fan%d 3pin", channel + 1);469 break;470 case 2:471 scnprintf(ccp->fan_label[channel], LABEL_LENGTH,472 "fan%d 4pin", channel + 1);473 break;474 default:475 scnprintf(ccp->fan_label[channel], LABEL_LENGTH,476 "fan%d other", channel + 1);477 break;478 }479 }480 481 return 0;482}483 484/* read temp sensor connection status */485static int get_temp_cnct(struct ccp_device *ccp)486{487 int channel;488 int mode;489 int ret;490 491 ret = send_usb_cmd(ccp, CTL_GET_TMP_CNCT, 0, 0, 0);492 if (ret)493 return ret;494 495 for (channel = 0; channel < NUM_TEMP_SENSORS; channel++) {496 mode = ccp->buffer[channel + 1];497 if (mode == 0)498 continue;499 500 set_bit(channel, ccp->temp_cnct);501 }502 503 return 0;504}505 506/* read firmware version */507static int get_fw_version(struct ccp_device *ccp)508{509 int ret;510 511 ret = send_usb_cmd(ccp, CTL_GET_FW_VER, 0, 0, 0);512 if (ret) {513 hid_notice(ccp->hdev, "Failed to read firmware version.\n");514 return ret;515 }516 ccp->firmware_ver[0] = ccp->buffer[1];517 ccp->firmware_ver[1] = ccp->buffer[2];518 ccp->firmware_ver[2] = ccp->buffer[3];519 520 return 0;521}522 523/* read bootloader version */524static int get_bl_version(struct ccp_device *ccp)525{526 int ret;527 528 ret = send_usb_cmd(ccp, CTL_GET_BL_VER, 0, 0, 0);529 if (ret) {530 hid_notice(ccp->hdev, "Failed to read bootloader version.\n");531 return ret;532 }533 ccp->bootloader_ver[0] = ccp->buffer[1];534 ccp->bootloader_ver[1] = ccp->buffer[2];535 536 return 0;537}538 539static int firmware_show(struct seq_file *seqf, void *unused)540{541 struct ccp_device *ccp = seqf->private;542 543 seq_printf(seqf, "%d.%d.%d\n",544 ccp->firmware_ver[0],545 ccp->firmware_ver[1],546 ccp->firmware_ver[2]);547 548 return 0;549}550DEFINE_SHOW_ATTRIBUTE(firmware);551 552static int bootloader_show(struct seq_file *seqf, void *unused)553{554 struct ccp_device *ccp = seqf->private;555 556 seq_printf(seqf, "%d.%d\n",557 ccp->bootloader_ver[0],558 ccp->bootloader_ver[1]);559 560 return 0;561}562DEFINE_SHOW_ATTRIBUTE(bootloader);563 564static void ccp_debugfs_init(struct ccp_device *ccp)565{566 char name[32];567 int ret;568 569 scnprintf(name, sizeof(name), "corsaircpro-%s", dev_name(&ccp->hdev->dev));570 ccp->debugfs = debugfs_create_dir(name, NULL);571 572 ret = get_fw_version(ccp);573 if (!ret)574 debugfs_create_file("firmware_version", 0444,575 ccp->debugfs, ccp, &firmware_fops);576 577 ret = get_bl_version(ccp);578 if (!ret)579 debugfs_create_file("bootloader_version", 0444,580 ccp->debugfs, ccp, &bootloader_fops);581}582 583static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)584{585 struct ccp_device *ccp;586 int ret;587 588 ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL);589 if (!ccp)590 return -ENOMEM;591 592 ccp->cmd_buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL);593 if (!ccp->cmd_buffer)594 return -ENOMEM;595 596 ccp->buffer = devm_kmalloc(&hdev->dev, IN_BUFFER_SIZE, GFP_KERNEL);597 if (!ccp->buffer)598 return -ENOMEM;599 600 ret = hid_parse(hdev);601 if (ret)602 return ret;603 604 ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);605 if (ret)606 return ret;607 608 ret = hid_hw_open(hdev);609 if (ret)610 goto out_hw_stop;611 612 ccp->hdev = hdev;613 hid_set_drvdata(hdev, ccp);614 615 mutex_init(&ccp->mutex);616 spin_lock_init(&ccp->wait_input_report_lock);617 init_completion(&ccp->wait_input_report);618 619 hid_device_io_start(hdev);620 621 /* temp and fan connection status only updates when device is powered on */622 ret = get_temp_cnct(ccp);623 if (ret)624 goto out_hw_close;625 626 ret = get_fan_cnct(ccp);627 if (ret)628 goto out_hw_close;629 630 ccp_debugfs_init(ccp);631 632 ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro",633 ccp, &ccp_chip_info, NULL);634 if (IS_ERR(ccp->hwmon_dev)) {635 ret = PTR_ERR(ccp->hwmon_dev);636 goto out_hw_close;637 }638 639 return 0;640 641out_hw_close:642 hid_hw_close(hdev);643out_hw_stop:644 hid_hw_stop(hdev);645 return ret;646}647 648static void ccp_remove(struct hid_device *hdev)649{650 struct ccp_device *ccp = hid_get_drvdata(hdev);651 652 debugfs_remove_recursive(ccp->debugfs);653 hwmon_device_unregister(ccp->hwmon_dev);654 hid_hw_close(hdev);655 hid_hw_stop(hdev);656}657 658static const struct hid_device_id ccp_devices[] = {659 { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_COMMANDERPRO) },660 { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_1000D) },661 { }662};663 664static struct hid_driver ccp_driver = {665 .name = "corsair-cpro",666 .id_table = ccp_devices,667 .probe = ccp_probe,668 .remove = ccp_remove,669 .raw_event = ccp_raw_event,670};671 672MODULE_DEVICE_TABLE(hid, ccp_devices);673MODULE_DESCRIPTION("Corsair Commander Pro controller driver");674MODULE_LICENSE("GPL");675 676static int __init ccp_init(void)677{678 return hid_register_driver(&ccp_driver);679}680 681static void __exit ccp_exit(void)682{683 hid_unregister_driver(&ccp_driver);684}685 686/*687 * When compiling this driver as built-in, hwmon initcalls will get called before the688 * hid driver and this driver would fail to register. late_initcall solves this.689 */690late_initcall(ccp_init);691module_exit(ccp_exit);692