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