linux/drivers/hwmon/corsair-cpro.c

// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * corsair-cpro.c - Linux driver for Corsair Commander Pro
 * Copyright (C) 2020 Marius Zachmann <[email protected]>
 *
 * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers
 * still being used. The device does not use report ids. When using hidraw and this driver
 * simultaniously, reports could be switched.
 */

#include <linux/bitops.h>
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/hid.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>

#define USB_VENDOR_ID_CORSAIR
#define USB_PRODUCT_ID_CORSAIR_COMMANDERPRO
#define USB_PRODUCT_ID_CORSAIR_1000D

#define OUT_BUFFER_SIZE
#define IN_BUFFER_SIZE
#define LABEL_LENGTH
#define REQ_TIMEOUT

#define CTL_GET_FW_VER
#define CTL_GET_BL_VER
#define CTL_GET_TMP_CNCT
#define CTL_GET_TMP
#define CTL_GET_VOLT
#define CTL_GET_FAN_CNCT
#define CTL_GET_FAN_RPM
#define CTL_GET_FAN_PWM
#define CTL_SET_FAN_FPWM
#define CTL_SET_FAN_TARGET

#define NUM_FANS
#define NUM_TEMP_SENSORS

struct ccp_device {};

/* converts response error in buffer to errno */
static int ccp_get_errno(struct ccp_device *ccp)
{}

/* send command, check for error in response, response in ccp->buffer */
static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2, u8 byte3)
{}

static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
{}

/* requests and returns single data values depending on channel */
static int get_data(struct ccp_device *ccp, int command, int channel, bool two_byte_data)
{}

static int set_pwm(struct ccp_device *ccp, int channel, long val)
{}

static int set_target(struct ccp_device *ccp, int channel, long val)
{}

static int ccp_read_string(struct device *dev, enum hwmon_sensor_types type,
			   u32 attr, int channel, const char **str)
{}

static int ccp_read(struct device *dev, enum hwmon_sensor_types type,
		    u32 attr, int channel, long *val)
{
	struct ccp_device *ccp = dev_get_drvdata(dev);
	int ret;

	switch (type) {
	case hwmon_temp:
		switch (attr) {
		case hwmon_temp_input:
			ret = get_data(ccp, CTL_GET_TMP, channel, true);
			if (ret < 0)
				return ret;
			*val = ret * 10;
			return 0;
		default:
			break;
		}
		break;
	case hwmon_fan:
		switch (attr) {
		case hwmon_fan_input:
			ret = get_data(ccp, CTL_GET_FAN_RPM, channel, true);
			if (ret < 0)
				return ret;
			*val = ret;
			return 0;
		case hwmon_fan_target:
			/* how to read target values from the device is unknown */
			/* driver returns last set value or 0			*/
			if (ccp->target[channel] < 0)
				return -ENODATA;
			*val = ccp->target[channel];
			return 0;
		default:
			break;
		}
		break;
	case hwmon_pwm:
		switch (attr) {
		case hwmon_pwm_input:
			ret = get_data(ccp, CTL_GET_FAN_PWM, channel, false);
			if (ret < 0)
				return ret;
			*val = DIV_ROUND_CLOSEST(ret * 255, 100);
			return 0;
		default:
			break;
		}
		break;
	case hwmon_in:
		switch (attr) {
		case hwmon_in_input:
			ret = get_data(ccp, CTL_GET_VOLT, channel, true);
			if (ret < 0)
				return ret;
			*val = ret;
			return 0;
		default:
			break;
		}
		break;
	default:
		break;
	}

	return -EOPNOTSUPP;
};

static int ccp_write(struct device *dev, enum hwmon_sensor_types type,
		     u32 attr, int channel, long val)
{
	struct ccp_device *ccp = dev_get_drvdata(dev);

	switch (type) {
	case hwmon_pwm:
		switch (attr) {
		case hwmon_pwm_input:
			return set_pwm(ccp, channel, val);
		default:
			break;
		}
		break;
	case hwmon_fan:
		switch (attr) {
		case hwmon_fan_target:
			return set_target(ccp, channel, val);
		default:
			break;
		}
		break;
	default:
		break;
	}

	return -EOPNOTSUPP;
};

static umode_t ccp_is_visible(const void *data, enum hwmon_sensor_types type,
			      u32 attr, int channel)
{
	const struct ccp_device *ccp = data;

	switch (type) {
	case hwmon_temp:
		if (!test_bit(channel, ccp->temp_cnct))
			break;

		switch (attr) {
		case hwmon_temp_input:
			return 0444;
		case hwmon_temp_label:
			return 0444;
		default:
			break;
		}
		break;
	case hwmon_fan:
		if (!test_bit(channel, ccp->fan_cnct))
			break;

		switch (attr) {
		case hwmon_fan_input:
			return 0444;
		case hwmon_fan_label:
			return 0444;
		case hwmon_fan_target:
			return 0644;
		default:
			break;
		}
		break;
	case hwmon_pwm:
		if (!test_bit(channel, ccp->fan_cnct))
			break;

		switch (attr) {
		case hwmon_pwm_input:
			return 0644;
		default:
			break;
		}
		break;
	case hwmon_in:
		switch (attr) {
		case hwmon_in_input:
			return 0444;
		default:
			break;
		}
		break;
	default:
		break;
	}

	return 0;
};

static const struct hwmon_ops ccp_hwmon_ops =;

static const struct hwmon_channel_info * const ccp_info[] =;

static const struct hwmon_chip_info ccp_chip_info =;

/* read fan connection status and set labels */
static int get_fan_cnct(struct ccp_device *ccp)
{}

/* read temp sensor connection status */
static int get_temp_cnct(struct ccp_device *ccp)
{}

/* read firmware version */
static int get_fw_version(struct ccp_device *ccp)
{}

/* read bootloader version */
static int get_bl_version(struct ccp_device *ccp)
{}

static int firmware_show(struct seq_file *seqf, void *unused)
{}
DEFINE_SHOW_ATTRIBUTE();

static int bootloader_show(struct seq_file *seqf, void *unused)
{}
DEFINE_SHOW_ATTRIBUTE();

static void ccp_debugfs_init(struct ccp_device *ccp)
{}

static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{}

static void ccp_remove(struct hid_device *hdev)
{}

static const struct hid_device_id ccp_devices[] =;

static struct hid_driver ccp_driver =;

MODULE_DEVICE_TABLE(hid, ccp_devices);
MODULE_DESCRIPTION();
MODULE_LICENSE();

static int __init ccp_init(void)
{}

static void __exit ccp_exit(void)
{}

/*
 * When compiling this driver as built-in, hwmon initcalls will get called before the
 * hid driver and this driver would fail to register. late_initcall solves this.
 */
late_initcall(ccp_init);
module_exit(ccp_exit);