brintos

brintos / linux-shallow public Read only

0
0
Text · 15.3 KiB · e1a7f7a Raw
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