Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 39 | * returns 0x11 in byte 0 if no sensor is connected |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 44 | * returns error 0x10 if request is invalid |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | */ |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 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 | */ |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | |
| 77 | struct 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; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 89 | /* converts response error in buffer to errno */ |
| 90 | static 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 | |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 108 | /* send command, check for error in response, response in ccp->buffer */ |
| 109 | static 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 | |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 130 | return ccp_get_errno(ccp); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | static 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 */ |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 148 | static int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data) |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 158 | ret = ccp->buffer[1]; |
| 159 | if (two_byte_data) |
| 160 | ret = (ret << 8) + ccp->buffer[2]; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 161 | |
| 162 | out_unlock: |
| 163 | mutex_unlock(&ccp->mutex); |
| 164 | return ret; |
| 165 | } |
| 166 | |
| 167 | static 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 | |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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); |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 180 | if (!ret) |
| 181 | ccp->target[channel] = -ENODATA; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 182 | |
| 183 | mutex_unlock(&ccp->mutex); |
| 184 | return ret; |
| 185 | } |
| 186 | |
| 187 | static 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); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | |
| 201 | static 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 | |
| 223 | static 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: |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 233 | ret = get_data(ccp, CTL_GET_TMP, channel, true); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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: |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 245 | ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 */ |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 253 | if (ccp->target[channel] < 0) |
| 254 | return -ENODATA; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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: |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 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); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 268 | return 0; |
| 269 | default: |
| 270 | break; |
| 271 | } |
| 272 | break; |
| 273 | case hwmon_in: |
| 274 | switch (attr) { |
| 275 | case hwmon_in_input: |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 276 | ret = get_data(ccp, CTL_GET_VOLT, channel, true); |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 | |
| 292 | static 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 | } |
Gustavo A. R. Silva | 3752445 | 2020-11-20 12:36:04 -0600 | [diff] [blame] | 313 | break; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 314 | default: |
| 315 | break; |
| 316 | } |
| 317 | |
| 318 | return -EOPNOTSUPP; |
| 319 | }; |
| 320 | |
| 321 | static 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 | |
| 381 | static 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 | |
| 388 | static 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 | |
| 421 | static 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 */ |
| 427 | static 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); |
Marius Zachmann | fa4dac3 | 2020-07-21 10:54:47 +0200 | [diff] [blame] | 443 | ccp->target[channel] = -ENODATA; |
Marius Zachmann | 40c3a44 | 2020-06-26 07:59:36 +0200 | [diff] [blame] | 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 */ |
| 465 | static 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 | |
| 486 | static 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 | |
| 535 | out_hw_close: |
| 536 | hid_hw_close(hdev); |
| 537 | out_hw_stop: |
| 538 | hid_hw_stop(hdev); |
| 539 | return ret; |
| 540 | } |
| 541 | |
| 542 | static 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 | |
| 551 | static 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 | |
| 557 | static 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 | |
| 565 | MODULE_DEVICE_TABLE(hid, ccp_devices); |
| 566 | MODULE_LICENSE("GPL"); |
| 567 | |
| 568 | static int __init ccp_init(void) |
| 569 | { |
| 570 | return hid_register_driver(&ccp_driver); |
| 571 | } |
| 572 | |
| 573 | static 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 | */ |
| 582 | late_initcall(ccp_init); |
| 583 | module_exit(ccp_exit); |