18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * corsair-cpro.c - Linux driver for Corsair Commander Pro 48c2ecf20Sopenharmony_ci * Copyright (C) 2020 Marius Zachmann <mail@mariuszachmann.de> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers 78c2ecf20Sopenharmony_ci * still being used. The device does not use report ids. When using hidraw and this driver 88c2ecf20Sopenharmony_ci * simultaniously, reports could be switched. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/bitops.h> 128c2ecf20Sopenharmony_ci#include <linux/completion.h> 138c2ecf20Sopenharmony_ci#include <linux/hid.h> 148c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 158c2ecf20Sopenharmony_ci#include <linux/kernel.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci#include <linux/types.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define USB_VENDOR_ID_CORSAIR 0x1b1c 228c2ecf20Sopenharmony_ci#define USB_PRODUCT_ID_CORSAIR_COMMANDERPRO 0x0c10 238c2ecf20Sopenharmony_ci#define USB_PRODUCT_ID_CORSAIR_1000D 0x1d00 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define OUT_BUFFER_SIZE 63 268c2ecf20Sopenharmony_ci#define IN_BUFFER_SIZE 16 278c2ecf20Sopenharmony_ci#define LABEL_LENGTH 11 288c2ecf20Sopenharmony_ci#define REQ_TIMEOUT 300 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define CTL_GET_TMP_CNCT 0x10 /* 318c2ecf20Sopenharmony_ci * returns in bytes 1-4 for each temp sensor: 328c2ecf20Sopenharmony_ci * 0 not connected 338c2ecf20Sopenharmony_ci * 1 connected 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_ci#define CTL_GET_TMP 0x11 /* 368c2ecf20Sopenharmony_ci * send: byte 1 is channel, rest zero 378c2ecf20Sopenharmony_ci * rcv: returns temp for channel in centi-degree celsius 388c2ecf20Sopenharmony_ci * in bytes 1 and 2 398c2ecf20Sopenharmony_ci * returns 0x11 in byte 0 if no sensor is connected 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_ci#define CTL_GET_VOLT 0x12 /* 428c2ecf20Sopenharmony_ci * send: byte 1 is rail number: 0 = 12v, 1 = 5v, 2 = 3.3v 438c2ecf20Sopenharmony_ci * rcv: returns millivolt in bytes 1,2 448c2ecf20Sopenharmony_ci * returns error 0x10 if request is invalid 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_ci#define CTL_GET_FAN_CNCT 0x20 /* 478c2ecf20Sopenharmony_ci * returns in bytes 1-6 for each fan: 488c2ecf20Sopenharmony_ci * 0 not connected 498c2ecf20Sopenharmony_ci * 1 3pin 508c2ecf20Sopenharmony_ci * 2 4pin 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_ci#define CTL_GET_FAN_RPM 0x21 /* 538c2ecf20Sopenharmony_ci * send: byte 1 is channel, rest zero 548c2ecf20Sopenharmony_ci * rcv: returns rpm in bytes 1,2 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_ci#define CTL_GET_FAN_PWM 0x22 /* 578c2ecf20Sopenharmony_ci * send: byte 1 is channel, rest zero 588c2ecf20Sopenharmony_ci * rcv: returns pwm in byte 1 if it was set 598c2ecf20Sopenharmony_ci * returns error 0x12 if fan is controlled via 608c2ecf20Sopenharmony_ci * fan_target or fan curve 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_ci#define CTL_SET_FAN_FPWM 0x23 /* 638c2ecf20Sopenharmony_ci * set fixed pwm 648c2ecf20Sopenharmony_ci * send: byte 1 is fan number 658c2ecf20Sopenharmony_ci * send: byte 2 is percentage from 0 - 100 668c2ecf20Sopenharmony_ci */ 678c2ecf20Sopenharmony_ci#define CTL_SET_FAN_TARGET 0x24 /* 688c2ecf20Sopenharmony_ci * set target rpm 698c2ecf20Sopenharmony_ci * send: byte 1 is fan number 708c2ecf20Sopenharmony_ci * send: byte 2-3 is target 718c2ecf20Sopenharmony_ci * device accepts all values from 0x00 - 0xFFFF 728c2ecf20Sopenharmony_ci */ 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci#define NUM_FANS 6 758c2ecf20Sopenharmony_ci#define NUM_TEMP_SENSORS 4 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistruct ccp_device { 788c2ecf20Sopenharmony_ci struct hid_device *hdev; 798c2ecf20Sopenharmony_ci struct device *hwmon_dev; 808c2ecf20Sopenharmony_ci struct completion wait_input_report; 818c2ecf20Sopenharmony_ci struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */ 828c2ecf20Sopenharmony_ci u8 *buffer; 838c2ecf20Sopenharmony_ci int target[6]; 848c2ecf20Sopenharmony_ci DECLARE_BITMAP(temp_cnct, NUM_TEMP_SENSORS); 858c2ecf20Sopenharmony_ci DECLARE_BITMAP(fan_cnct, NUM_FANS); 868c2ecf20Sopenharmony_ci char fan_label[6][LABEL_LENGTH]; 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci/* converts response error in buffer to errno */ 908c2ecf20Sopenharmony_cistatic int ccp_get_errno(struct ccp_device *ccp) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci switch (ccp->buffer[0]) { 938c2ecf20Sopenharmony_ci case 0x00: /* success */ 948c2ecf20Sopenharmony_ci return 0; 958c2ecf20Sopenharmony_ci case 0x01: /* called invalid command */ 968c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 978c2ecf20Sopenharmony_ci case 0x10: /* called GET_VOLT / GET_TMP with invalid arguments */ 988c2ecf20Sopenharmony_ci return -EINVAL; 998c2ecf20Sopenharmony_ci case 0x11: /* requested temps of disconnected sensors */ 1008c2ecf20Sopenharmony_ci case 0x12: /* requested pwm of not pwm controlled channels */ 1018c2ecf20Sopenharmony_ci return -ENODATA; 1028c2ecf20Sopenharmony_ci default: 1038c2ecf20Sopenharmony_ci hid_dbg(ccp->hdev, "unknown device response error: %d", ccp->buffer[0]); 1048c2ecf20Sopenharmony_ci return -EIO; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* send command, check for error in response, response in ccp->buffer */ 1098c2ecf20Sopenharmony_cistatic int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2, u8 byte3) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci unsigned long t; 1128c2ecf20Sopenharmony_ci int ret; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci memset(ccp->buffer, 0x00, OUT_BUFFER_SIZE); 1158c2ecf20Sopenharmony_ci ccp->buffer[0] = command; 1168c2ecf20Sopenharmony_ci ccp->buffer[1] = byte1; 1178c2ecf20Sopenharmony_ci ccp->buffer[2] = byte2; 1188c2ecf20Sopenharmony_ci ccp->buffer[3] = byte3; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci reinit_completion(&ccp->wait_input_report); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ret = hid_hw_output_report(ccp->hdev, ccp->buffer, OUT_BUFFER_SIZE); 1238c2ecf20Sopenharmony_ci if (ret < 0) 1248c2ecf20Sopenharmony_ci return ret; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci t = wait_for_completion_timeout(&ccp->wait_input_report, msecs_to_jiffies(REQ_TIMEOUT)); 1278c2ecf20Sopenharmony_ci if (!t) 1288c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci return ccp_get_errno(ccp); 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct ccp_device *ccp = hid_get_drvdata(hdev); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* only copy buffer when requested */ 1388c2ecf20Sopenharmony_ci if (completion_done(&ccp->wait_input_report)) 1398c2ecf20Sopenharmony_ci return 0; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size)); 1428c2ecf20Sopenharmony_ci complete(&ccp->wait_input_report); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci return 0; 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci/* requests and returns single data values depending on channel */ 1488c2ecf20Sopenharmony_cistatic int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci int ret; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci mutex_lock(&ccp->mutex); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci ret = send_usb_cmd(ccp, command, channel, 0, 0); 1558c2ecf20Sopenharmony_ci if (ret) 1568c2ecf20Sopenharmony_ci goto out_unlock; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci ret = ccp->buffer[1]; 1598c2ecf20Sopenharmony_ci if (two_byte_data) 1608c2ecf20Sopenharmony_ci ret = (ret << 8) + ccp->buffer[2]; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ciout_unlock: 1638c2ecf20Sopenharmony_ci mutex_unlock(&ccp->mutex); 1648c2ecf20Sopenharmony_ci return ret; 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic int set_pwm(struct ccp_device *ccp, int channel, long val) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci int ret; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (val < 0 || val > 255) 1728c2ecf20Sopenharmony_ci return -EINVAL; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* The Corsair Commander Pro uses values from 0-100 */ 1758c2ecf20Sopenharmony_ci val = DIV_ROUND_CLOSEST(val * 100, 255); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci mutex_lock(&ccp->mutex); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = send_usb_cmd(ccp, CTL_SET_FAN_FPWM, channel, val, 0); 1808c2ecf20Sopenharmony_ci if (!ret) 1818c2ecf20Sopenharmony_ci ccp->target[channel] = -ENODATA; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci mutex_unlock(&ccp->mutex); 1848c2ecf20Sopenharmony_ci return ret; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic int set_target(struct ccp_device *ccp, int channel, long val) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci int ret; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci val = clamp_val(val, 0, 0xFFFF); 1928c2ecf20Sopenharmony_ci ccp->target[channel] = val; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci mutex_lock(&ccp->mutex); 1958c2ecf20Sopenharmony_ci ret = send_usb_cmd(ccp, CTL_SET_FAN_TARGET, channel, val >> 8, val); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci mutex_unlock(&ccp->mutex); 1988c2ecf20Sopenharmony_ci return ret; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic int ccp_read_string(struct device *dev, enum hwmon_sensor_types type, 2028c2ecf20Sopenharmony_ci u32 attr, int channel, const char **str) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct ccp_device *ccp = dev_get_drvdata(dev); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci switch (type) { 2078c2ecf20Sopenharmony_ci case hwmon_fan: 2088c2ecf20Sopenharmony_ci switch (attr) { 2098c2ecf20Sopenharmony_ci case hwmon_fan_label: 2108c2ecf20Sopenharmony_ci *str = ccp->fan_label[channel]; 2118c2ecf20Sopenharmony_ci return 0; 2128c2ecf20Sopenharmony_ci default: 2138c2ecf20Sopenharmony_ci break; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci break; 2168c2ecf20Sopenharmony_ci default: 2178c2ecf20Sopenharmony_ci break; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 2218c2ecf20Sopenharmony_ci} 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic int ccp_read(struct device *dev, enum hwmon_sensor_types type, 2248c2ecf20Sopenharmony_ci u32 attr, int channel, long *val) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci struct ccp_device *ccp = dev_get_drvdata(dev); 2278c2ecf20Sopenharmony_ci int ret; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci switch (type) { 2308c2ecf20Sopenharmony_ci case hwmon_temp: 2318c2ecf20Sopenharmony_ci switch (attr) { 2328c2ecf20Sopenharmony_ci case hwmon_temp_input: 2338c2ecf20Sopenharmony_ci ret = get_data(ccp, CTL_GET_TMP, channel, true); 2348c2ecf20Sopenharmony_ci if (ret < 0) 2358c2ecf20Sopenharmony_ci return ret; 2368c2ecf20Sopenharmony_ci *val = ret * 10; 2378c2ecf20Sopenharmony_ci return 0; 2388c2ecf20Sopenharmony_ci default: 2398c2ecf20Sopenharmony_ci break; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci break; 2428c2ecf20Sopenharmony_ci case hwmon_fan: 2438c2ecf20Sopenharmony_ci switch (attr) { 2448c2ecf20Sopenharmony_ci case hwmon_fan_input: 2458c2ecf20Sopenharmony_ci ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true); 2468c2ecf20Sopenharmony_ci if (ret < 0) 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci *val = ret; 2498c2ecf20Sopenharmony_ci return 0; 2508c2ecf20Sopenharmony_ci case hwmon_fan_target: 2518c2ecf20Sopenharmony_ci /* how to read target values from the device is unknown */ 2528c2ecf20Sopenharmony_ci /* driver returns last set value or 0 */ 2538c2ecf20Sopenharmony_ci if (ccp->target[channel] < 0) 2548c2ecf20Sopenharmony_ci return -ENODATA; 2558c2ecf20Sopenharmony_ci *val = ccp->target[channel]; 2568c2ecf20Sopenharmony_ci return 0; 2578c2ecf20Sopenharmony_ci default: 2588c2ecf20Sopenharmony_ci break; 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci break; 2618c2ecf20Sopenharmony_ci case hwmon_pwm: 2628c2ecf20Sopenharmony_ci switch (attr) { 2638c2ecf20Sopenharmony_ci case hwmon_pwm_input: 2648c2ecf20Sopenharmony_ci ret = get_data(ccp, CTL_GET_FAN_PWM, channel, false); 2658c2ecf20Sopenharmony_ci if (ret < 0) 2668c2ecf20Sopenharmony_ci return ret; 2678c2ecf20Sopenharmony_ci *val = DIV_ROUND_CLOSEST(ret * 255, 100); 2688c2ecf20Sopenharmony_ci return 0; 2698c2ecf20Sopenharmony_ci default: 2708c2ecf20Sopenharmony_ci break; 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci break; 2738c2ecf20Sopenharmony_ci case hwmon_in: 2748c2ecf20Sopenharmony_ci switch (attr) { 2758c2ecf20Sopenharmony_ci case hwmon_in_input: 2768c2ecf20Sopenharmony_ci ret = get_data(ccp, CTL_GET_VOLT, channel, true); 2778c2ecf20Sopenharmony_ci if (ret < 0) 2788c2ecf20Sopenharmony_ci return ret; 2798c2ecf20Sopenharmony_ci *val = ret; 2808c2ecf20Sopenharmony_ci return 0; 2818c2ecf20Sopenharmony_ci default: 2828c2ecf20Sopenharmony_ci break; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci break; 2858c2ecf20Sopenharmony_ci default: 2868c2ecf20Sopenharmony_ci break; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 2908c2ecf20Sopenharmony_ci}; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_cistatic int ccp_write(struct device *dev, enum hwmon_sensor_types type, 2938c2ecf20Sopenharmony_ci u32 attr, int channel, long val) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci struct ccp_device *ccp = dev_get_drvdata(dev); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci switch (type) { 2988c2ecf20Sopenharmony_ci case hwmon_pwm: 2998c2ecf20Sopenharmony_ci switch (attr) { 3008c2ecf20Sopenharmony_ci case hwmon_pwm_input: 3018c2ecf20Sopenharmony_ci return set_pwm(ccp, channel, val); 3028c2ecf20Sopenharmony_ci default: 3038c2ecf20Sopenharmony_ci break; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci break; 3068c2ecf20Sopenharmony_ci case hwmon_fan: 3078c2ecf20Sopenharmony_ci switch (attr) { 3088c2ecf20Sopenharmony_ci case hwmon_fan_target: 3098c2ecf20Sopenharmony_ci return set_target(ccp, channel, val); 3108c2ecf20Sopenharmony_ci default: 3118c2ecf20Sopenharmony_ci break; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci default: 3148c2ecf20Sopenharmony_ci break; 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 3188c2ecf20Sopenharmony_ci}; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_cistatic umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type, 3218c2ecf20Sopenharmony_ci u32 attr, int channel) 3228c2ecf20Sopenharmony_ci{ 3238c2ecf20Sopenharmony_ci const struct ccp_device *ccp = data; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci switch (type) { 3268c2ecf20Sopenharmony_ci case hwmon_temp: 3278c2ecf20Sopenharmony_ci if (!test_bit(channel, ccp->temp_cnct)) 3288c2ecf20Sopenharmony_ci break; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci switch (attr) { 3318c2ecf20Sopenharmony_ci case hwmon_temp_input: 3328c2ecf20Sopenharmony_ci return 0444; 3338c2ecf20Sopenharmony_ci case hwmon_temp_label: 3348c2ecf20Sopenharmony_ci return 0444; 3358c2ecf20Sopenharmony_ci default: 3368c2ecf20Sopenharmony_ci break; 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci break; 3398c2ecf20Sopenharmony_ci case hwmon_fan: 3408c2ecf20Sopenharmony_ci if (!test_bit(channel, ccp->fan_cnct)) 3418c2ecf20Sopenharmony_ci break; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci switch (attr) { 3448c2ecf20Sopenharmony_ci case hwmon_fan_input: 3458c2ecf20Sopenharmony_ci return 0444; 3468c2ecf20Sopenharmony_ci case hwmon_fan_label: 3478c2ecf20Sopenharmony_ci return 0444; 3488c2ecf20Sopenharmony_ci case hwmon_fan_target: 3498c2ecf20Sopenharmony_ci return 0644; 3508c2ecf20Sopenharmony_ci default: 3518c2ecf20Sopenharmony_ci break; 3528c2ecf20Sopenharmony_ci } 3538c2ecf20Sopenharmony_ci break; 3548c2ecf20Sopenharmony_ci case hwmon_pwm: 3558c2ecf20Sopenharmony_ci if (!test_bit(channel, ccp->fan_cnct)) 3568c2ecf20Sopenharmony_ci break; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci switch (attr) { 3598c2ecf20Sopenharmony_ci case hwmon_pwm_input: 3608c2ecf20Sopenharmony_ci return 0644; 3618c2ecf20Sopenharmony_ci default: 3628c2ecf20Sopenharmony_ci break; 3638c2ecf20Sopenharmony_ci } 3648c2ecf20Sopenharmony_ci break; 3658c2ecf20Sopenharmony_ci case hwmon_in: 3668c2ecf20Sopenharmony_ci switch (attr) { 3678c2ecf20Sopenharmony_ci case hwmon_in_input: 3688c2ecf20Sopenharmony_ci return 0444; 3698c2ecf20Sopenharmony_ci default: 3708c2ecf20Sopenharmony_ci break; 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci break; 3738c2ecf20Sopenharmony_ci default: 3748c2ecf20Sopenharmony_ci break; 3758c2ecf20Sopenharmony_ci } 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci return 0; 3788c2ecf20Sopenharmony_ci}; 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_cistatic const struct hwmon_ops ccp_hwmon_ops = { 3818c2ecf20Sopenharmony_ci .is_visible = ccp_is_visible, 3828c2ecf20Sopenharmony_ci .read = ccp_read, 3838c2ecf20Sopenharmony_ci .read_string = ccp_read_string, 3848c2ecf20Sopenharmony_ci .write = ccp_write, 3858c2ecf20Sopenharmony_ci}; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic const struct hwmon_channel_info *ccp_info[] = { 3888c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(chip, 3898c2ecf20Sopenharmony_ci HWMON_C_REGISTER_TZ), 3908c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(temp, 3918c2ecf20Sopenharmony_ci HWMON_T_INPUT, 3928c2ecf20Sopenharmony_ci HWMON_T_INPUT, 3938c2ecf20Sopenharmony_ci HWMON_T_INPUT, 3948c2ecf20Sopenharmony_ci HWMON_T_INPUT 3958c2ecf20Sopenharmony_ci ), 3968c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(fan, 3978c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET, 3988c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET, 3998c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET, 4008c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET, 4018c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET, 4028c2ecf20Sopenharmony_ci HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_TARGET 4038c2ecf20Sopenharmony_ci ), 4048c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(pwm, 4058c2ecf20Sopenharmony_ci HWMON_PWM_INPUT, 4068c2ecf20Sopenharmony_ci HWMON_PWM_INPUT, 4078c2ecf20Sopenharmony_ci HWMON_PWM_INPUT, 4088c2ecf20Sopenharmony_ci HWMON_PWM_INPUT, 4098c2ecf20Sopenharmony_ci HWMON_PWM_INPUT, 4108c2ecf20Sopenharmony_ci HWMON_PWM_INPUT 4118c2ecf20Sopenharmony_ci ), 4128c2ecf20Sopenharmony_ci HWMON_CHANNEL_INFO(in, 4138c2ecf20Sopenharmony_ci HWMON_I_INPUT, 4148c2ecf20Sopenharmony_ci HWMON_I_INPUT, 4158c2ecf20Sopenharmony_ci HWMON_I_INPUT 4168c2ecf20Sopenharmony_ci ), 4178c2ecf20Sopenharmony_ci NULL 4188c2ecf20Sopenharmony_ci}; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_cistatic const struct hwmon_chip_info ccp_chip_info = { 4218c2ecf20Sopenharmony_ci .ops = &ccp_hwmon_ops, 4228c2ecf20Sopenharmony_ci .info = ccp_info, 4238c2ecf20Sopenharmony_ci}; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci/* read fan connection status and set labels */ 4268c2ecf20Sopenharmony_cistatic int get_fan_cnct(struct ccp_device *ccp) 4278c2ecf20Sopenharmony_ci{ 4288c2ecf20Sopenharmony_ci int channel; 4298c2ecf20Sopenharmony_ci int mode; 4308c2ecf20Sopenharmony_ci int ret; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci ret = send_usb_cmd(ccp, CTL_GET_FAN_CNCT, 0, 0, 0); 4338c2ecf20Sopenharmony_ci if (ret) 4348c2ecf20Sopenharmony_ci return ret; 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci for (channel = 0; channel < NUM_FANS; channel++) { 4378c2ecf20Sopenharmony_ci mode = ccp->buffer[channel + 1]; 4388c2ecf20Sopenharmony_ci if (mode == 0) 4398c2ecf20Sopenharmony_ci continue; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci set_bit(channel, ccp->fan_cnct); 4428c2ecf20Sopenharmony_ci ccp->target[channel] = -ENODATA; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci switch (mode) { 4458c2ecf20Sopenharmony_ci case 1: 4468c2ecf20Sopenharmony_ci scnprintf(ccp->fan_label[channel], LABEL_LENGTH, 4478c2ecf20Sopenharmony_ci "fan%d 3pin", channel + 1); 4488c2ecf20Sopenharmony_ci break; 4498c2ecf20Sopenharmony_ci case 2: 4508c2ecf20Sopenharmony_ci scnprintf(ccp->fan_label[channel], LABEL_LENGTH, 4518c2ecf20Sopenharmony_ci "fan%d 4pin", channel + 1); 4528c2ecf20Sopenharmony_ci break; 4538c2ecf20Sopenharmony_ci default: 4548c2ecf20Sopenharmony_ci scnprintf(ccp->fan_label[channel], LABEL_LENGTH, 4558c2ecf20Sopenharmony_ci "fan%d other", channel + 1); 4568c2ecf20Sopenharmony_ci break; 4578c2ecf20Sopenharmony_ci } 4588c2ecf20Sopenharmony_ci } 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci return 0; 4618c2ecf20Sopenharmony_ci} 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci/* read temp sensor connection status */ 4648c2ecf20Sopenharmony_cistatic int get_temp_cnct(struct ccp_device *ccp) 4658c2ecf20Sopenharmony_ci{ 4668c2ecf20Sopenharmony_ci int channel; 4678c2ecf20Sopenharmony_ci int mode; 4688c2ecf20Sopenharmony_ci int ret; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci ret = send_usb_cmd(ccp, CTL_GET_TMP_CNCT, 0, 0, 0); 4718c2ecf20Sopenharmony_ci if (ret) 4728c2ecf20Sopenharmony_ci return ret; 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci for (channel = 0; channel < NUM_TEMP_SENSORS; channel++) { 4758c2ecf20Sopenharmony_ci mode = ccp->buffer[channel + 1]; 4768c2ecf20Sopenharmony_ci if (mode == 0) 4778c2ecf20Sopenharmony_ci continue; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci set_bit(channel, ccp->temp_cnct); 4808c2ecf20Sopenharmony_ci } 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci return 0; 4838c2ecf20Sopenharmony_ci} 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_cistatic int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id) 4868c2ecf20Sopenharmony_ci{ 4878c2ecf20Sopenharmony_ci struct ccp_device *ccp; 4888c2ecf20Sopenharmony_ci int ret; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci ccp = devm_kzalloc(&hdev->dev, sizeof(*ccp), GFP_KERNEL); 4918c2ecf20Sopenharmony_ci if (!ccp) 4928c2ecf20Sopenharmony_ci return -ENOMEM; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci ccp->buffer = devm_kmalloc(&hdev->dev, OUT_BUFFER_SIZE, GFP_KERNEL); 4958c2ecf20Sopenharmony_ci if (!ccp->buffer) 4968c2ecf20Sopenharmony_ci return -ENOMEM; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci ret = hid_parse(hdev); 4998c2ecf20Sopenharmony_ci if (ret) 5008c2ecf20Sopenharmony_ci return ret; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 5038c2ecf20Sopenharmony_ci if (ret) 5048c2ecf20Sopenharmony_ci return ret; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci ret = hid_hw_open(hdev); 5078c2ecf20Sopenharmony_ci if (ret) 5088c2ecf20Sopenharmony_ci goto out_hw_stop; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci ccp->hdev = hdev; 5118c2ecf20Sopenharmony_ci hid_set_drvdata(hdev, ccp); 5128c2ecf20Sopenharmony_ci mutex_init(&ccp->mutex); 5138c2ecf20Sopenharmony_ci init_completion(&ccp->wait_input_report); 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci hid_device_io_start(hdev); 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci /* temp and fan connection status only updates when device is powered on */ 5188c2ecf20Sopenharmony_ci ret = get_temp_cnct(ccp); 5198c2ecf20Sopenharmony_ci if (ret) 5208c2ecf20Sopenharmony_ci goto out_hw_close; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci ret = get_fan_cnct(ccp); 5238c2ecf20Sopenharmony_ci if (ret) 5248c2ecf20Sopenharmony_ci goto out_hw_close; 5258c2ecf20Sopenharmony_ci ccp->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsaircpro", 5268c2ecf20Sopenharmony_ci ccp, &ccp_chip_info, 0); 5278c2ecf20Sopenharmony_ci if (IS_ERR(ccp->hwmon_dev)) { 5288c2ecf20Sopenharmony_ci ret = PTR_ERR(ccp->hwmon_dev); 5298c2ecf20Sopenharmony_ci goto out_hw_close; 5308c2ecf20Sopenharmony_ci } 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci return 0; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ciout_hw_close: 5358c2ecf20Sopenharmony_ci hid_hw_close(hdev); 5368c2ecf20Sopenharmony_ciout_hw_stop: 5378c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 5388c2ecf20Sopenharmony_ci return ret; 5398c2ecf20Sopenharmony_ci} 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_cistatic void ccp_remove(struct hid_device *hdev) 5428c2ecf20Sopenharmony_ci{ 5438c2ecf20Sopenharmony_ci struct ccp_device *ccp = hid_get_drvdata(hdev); 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci hwmon_device_unregister(ccp->hwmon_dev); 5468c2ecf20Sopenharmony_ci hid_hw_close(hdev); 5478c2ecf20Sopenharmony_ci hid_hw_stop(hdev); 5488c2ecf20Sopenharmony_ci} 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_cistatic const struct hid_device_id ccp_devices[] = { 5518c2ecf20Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_COMMANDERPRO) }, 5528c2ecf20Sopenharmony_ci { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_PRODUCT_ID_CORSAIR_1000D) }, 5538c2ecf20Sopenharmony_ci { } 5548c2ecf20Sopenharmony_ci}; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_cistatic struct hid_driver ccp_driver = { 5578c2ecf20Sopenharmony_ci .name = "corsair-cpro", 5588c2ecf20Sopenharmony_ci .id_table = ccp_devices, 5598c2ecf20Sopenharmony_ci .probe = ccp_probe, 5608c2ecf20Sopenharmony_ci .remove = ccp_remove, 5618c2ecf20Sopenharmony_ci .raw_event = ccp_raw_event, 5628c2ecf20Sopenharmony_ci}; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(hid, ccp_devices); 5658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_cistatic int __init ccp_init(void) 5688c2ecf20Sopenharmony_ci{ 5698c2ecf20Sopenharmony_ci return hid_register_driver(&ccp_driver); 5708c2ecf20Sopenharmony_ci} 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_cistatic void __exit ccp_exit(void) 5738c2ecf20Sopenharmony_ci{ 5748c2ecf20Sopenharmony_ci hid_unregister_driver(&ccp_driver); 5758c2ecf20Sopenharmony_ci} 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci/* 5788c2ecf20Sopenharmony_ci * When compiling this driver as built-in, hwmon initcalls will get called before the 5798c2ecf20Sopenharmony_ci * hid driver and this driver would fail to register. late_initcall solves this. 5808c2ecf20Sopenharmony_ci */ 5818c2ecf20Sopenharmony_cilate_initcall(ccp_init); 5828c2ecf20Sopenharmony_cimodule_exit(ccp_exit); 583