mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
610 lines
17 KiB
C
610 lines
17 KiB
C
|
// SPDX-License-Identifier: GPL-2.0+
|
||
|
/*
|
||
|
* hwmon driver for Asus ROG Ryujin II 360 AIO cooler.
|
||
|
*
|
||
|
* Copyright 2024 Aleksa Savic <savicaleksa83@gmail.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/hid.h>
|
||
|
#include <linux/hwmon.h>
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <asm/unaligned.h>
|
||
|
|
||
|
#define DRIVER_NAME "asus_rog_ryujin"
|
||
|
|
||
|
#define USB_VENDOR_ID_ASUS_ROG 0x0b05
|
||
|
#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */
|
||
|
|
||
|
#define STATUS_VALIDITY 1500 /* ms */
|
||
|
#define MAX_REPORT_LENGTH 65
|
||
|
|
||
|
/* Cooler status report offsets */
|
||
|
#define RYUJIN_TEMP_SENSOR_1 3
|
||
|
#define RYUJIN_TEMP_SENSOR_2 4
|
||
|
#define RYUJIN_PUMP_SPEED 5
|
||
|
#define RYUJIN_INTERNAL_FAN_SPEED 7
|
||
|
|
||
|
/* Cooler duty report offsets */
|
||
|
#define RYUJIN_PUMP_DUTY 4
|
||
|
#define RYUJIN_INTERNAL_FAN_DUTY 5
|
||
|
|
||
|
/* Controller status (speeds) report offsets */
|
||
|
#define RYUJIN_CONTROLLER_SPEED_1 5
|
||
|
#define RYUJIN_CONTROLLER_SPEED_2 7
|
||
|
#define RYUJIN_CONTROLLER_SPEED_3 9
|
||
|
#define RYUJIN_CONTROLLER_SPEED_4 3
|
||
|
|
||
|
/* Controller duty report offsets */
|
||
|
#define RYUJIN_CONTROLLER_DUTY 4
|
||
|
|
||
|
/* Control commands and their inner offsets */
|
||
|
#define RYUJIN_CMD_PREFIX 0xEC
|
||
|
|
||
|
static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 };
|
||
|
static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A };
|
||
|
static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 };
|
||
|
static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 };
|
||
|
|
||
|
#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3
|
||
|
#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4
|
||
|
static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 };
|
||
|
|
||
|
#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4
|
||
|
static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 };
|
||
|
|
||
|
/* Command lengths */
|
||
|
#define GET_CMD_LENGTH 2 /* Same length for all get commands */
|
||
|
#define SET_CMD_LENGTH 5 /* Same length for all set commands */
|
||
|
|
||
|
/* Command response headers */
|
||
|
#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE 0x19
|
||
|
#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A
|
||
|
#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20
|
||
|
#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21
|
||
|
|
||
|
static const char *const rog_ryujin_temp_label[] = {
|
||
|
"Coolant temp"
|
||
|
};
|
||
|
|
||
|
static const char *const rog_ryujin_speed_label[] = {
|
||
|
"Pump speed",
|
||
|
"Internal fan speed",
|
||
|
"Controller fan 1 speed",
|
||
|
"Controller fan 2 speed",
|
||
|
"Controller fan 3 speed",
|
||
|
"Controller fan 4 speed",
|
||
|
};
|
||
|
|
||
|
struct rog_ryujin_data {
|
||
|
struct hid_device *hdev;
|
||
|
struct device *hwmon_dev;
|
||
|
/* For locking access to buffer */
|
||
|
struct mutex buffer_lock;
|
||
|
/* For queueing multiple readers */
|
||
|
struct mutex status_report_request_mutex;
|
||
|
/* For reinitializing the completions below */
|
||
|
spinlock_t status_report_request_lock;
|
||
|
struct completion cooler_status_received;
|
||
|
struct completion controller_status_received;
|
||
|
struct completion cooler_duty_received;
|
||
|
struct completion controller_duty_received;
|
||
|
struct completion cooler_duty_set;
|
||
|
struct completion controller_duty_set;
|
||
|
|
||
|
/* Sensor data */
|
||
|
s32 temp_input[1];
|
||
|
u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */
|
||
|
u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */
|
||
|
|
||
|
u8 *buffer;
|
||
|
unsigned long updated; /* jiffies */
|
||
|
};
|
||
|
|
||
|
static int rog_ryujin_percent_to_pwm(u16 val)
|
||
|
{
|
||
|
return DIV_ROUND_CLOSEST(val * 255, 100);
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_pwm_to_percent(long val)
|
||
|
{
|
||
|
return DIV_ROUND_CLOSEST(val * 100, 255);
|
||
|
}
|
||
|
|
||
|
static umode_t rog_ryujin_is_visible(const void *data,
|
||
|
enum hwmon_sensor_types type, u32 attr, int channel)
|
||
|
{
|
||
|
switch (type) {
|
||
|
case hwmon_temp:
|
||
|
switch (attr) {
|
||
|
case hwmon_temp_label:
|
||
|
case hwmon_temp_input:
|
||
|
return 0444;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case hwmon_fan:
|
||
|
switch (attr) {
|
||
|
case hwmon_fan_label:
|
||
|
case hwmon_fan_input:
|
||
|
return 0444;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case hwmon_pwm:
|
||
|
switch (attr) {
|
||
|
case hwmon_pwm_input:
|
||
|
return 0644;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Writes the command to the device with the rest of the report filled with zeroes */
|
||
|
static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
mutex_lock(&priv->buffer_lock);
|
||
|
|
||
|
memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00);
|
||
|
ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH);
|
||
|
|
||
|
mutex_unlock(&priv->buffer_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Assumes priv->status_report_request_mutex is locked */
|
||
|
static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length,
|
||
|
struct completion *status_completion)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
/*
|
||
|
* Disable raw event parsing for a moment to safely reinitialize the
|
||
|
* completion. Reinit is done because hidraw could have triggered
|
||
|
* the raw event parsing and marked the passed in completion as done.
|
||
|
*/
|
||
|
spin_lock_bh(&priv->status_report_request_lock);
|
||
|
reinit_completion(status_completion);
|
||
|
spin_unlock_bh(&priv->status_report_request_lock);
|
||
|
|
||
|
/* Send command for getting data */
|
||
|
ret = rog_ryujin_write_expanded(priv, cmd, cmd_length);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
ret = wait_for_completion_interruptible_timeout(status_completion,
|
||
|
msecs_to_jiffies(STATUS_VALIDITY));
|
||
|
if (ret == 0)
|
||
|
return -ETIMEDOUT;
|
||
|
else if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_get_status(struct rog_ryujin_data *priv)
|
||
|
{
|
||
|
int ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
|
||
|
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) {
|
||
|
/* Data is up to date */
|
||
|
goto unlock_and_return;
|
||
|
}
|
||
|
|
||
|
/* Retrieve cooler status */
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH,
|
||
|
&priv->cooler_status_received);
|
||
|
if (ret < 0)
|
||
|
goto unlock_and_return;
|
||
|
|
||
|
/* Retrieve controller status (speeds) */
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH,
|
||
|
&priv->controller_status_received);
|
||
|
if (ret < 0)
|
||
|
goto unlock_and_return;
|
||
|
|
||
|
/* Retrieve cooler duty */
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
|
||
|
&priv->cooler_duty_received);
|
||
|
if (ret < 0)
|
||
|
goto unlock_and_return;
|
||
|
|
||
|
/* Retrieve controller duty */
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH,
|
||
|
&priv->controller_duty_received);
|
||
|
if (ret < 0)
|
||
|
goto unlock_and_return;
|
||
|
|
||
|
priv->updated = jiffies;
|
||
|
|
||
|
unlock_and_return:
|
||
|
mutex_unlock(&priv->status_report_request_mutex);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type,
|
||
|
u32 attr, int channel, long *val)
|
||
|
{
|
||
|
struct rog_ryujin_data *priv = dev_get_drvdata(dev);
|
||
|
int ret = rog_ryujin_get_status(priv);
|
||
|
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
switch (type) {
|
||
|
case hwmon_temp:
|
||
|
*val = priv->temp_input[channel];
|
||
|
break;
|
||
|
case hwmon_fan:
|
||
|
*val = priv->speed_input[channel];
|
||
|
break;
|
||
|
case hwmon_pwm:
|
||
|
switch (attr) {
|
||
|
case hwmon_pwm_input:
|
||
|
*val = priv->duty_input[channel];
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP; /* unreachable */
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||
|
u32 attr, int channel, const char **str)
|
||
|
{
|
||
|
switch (type) {
|
||
|
case hwmon_temp:
|
||
|
*str = rog_ryujin_temp_label[channel];
|
||
|
break;
|
||
|
case hwmon_fan:
|
||
|
*str = rog_ryujin_speed_label[channel];
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP; /* unreachable */
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val)
|
||
|
{
|
||
|
u8 set_cmd[SET_CMD_LENGTH];
|
||
|
int ret;
|
||
|
|
||
|
if (channel < 2) {
|
||
|
/*
|
||
|
* Retrieve cooler duty since both pump and internal fan are set
|
||
|
* together, then write back with one of them modified.
|
||
|
*/
|
||
|
ret = mutex_lock_interruptible(&priv->status_report_request_mutex);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH,
|
||
|
&priv->cooler_duty_received);
|
||
|
if (ret < 0)
|
||
|
goto unlock_and_return;
|
||
|
|
||
|
memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH);
|
||
|
|
||
|
/* Cooler duties are set as 0-100% */
|
||
|
val = rog_ryujin_pwm_to_percent(val);
|
||
|
|
||
|
if (channel == 0) {
|
||
|
/* Cooler pump duty */
|
||
|
set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val;
|
||
|
set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] =
|
||
|
rog_ryujin_pwm_to_percent(priv->duty_input[1]);
|
||
|
} else if (channel == 1) {
|
||
|
/* Cooler internal fan duty */
|
||
|
set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] =
|
||
|
rog_ryujin_pwm_to_percent(priv->duty_input[0]);
|
||
|
set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val;
|
||
|
}
|
||
|
|
||
|
ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set);
|
||
|
unlock_and_return:
|
||
|
mutex_unlock(&priv->status_report_request_mutex);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
} else {
|
||
|
/*
|
||
|
* Controller fan duty (channel == 2). No need to retrieve current
|
||
|
* duty, so just send the command.
|
||
|
*/
|
||
|
memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH);
|
||
|
set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val;
|
||
|
|
||
|
ret =
|
||
|
rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH,
|
||
|
&priv->controller_duty_set);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Lock onto this value until next refresh cycle */
|
||
|
priv->duty_input[channel] = val;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
|
||
|
long val)
|
||
|
{
|
||
|
struct rog_ryujin_data *priv = dev_get_drvdata(dev);
|
||
|
int ret;
|
||
|
|
||
|
switch (type) {
|
||
|
case hwmon_pwm:
|
||
|
switch (attr) {
|
||
|
case hwmon_pwm_input:
|
||
|
if (val < 0 || val > 255)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = rog_ryujin_write_fixed_duty(priv, channel, val);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct hwmon_ops rog_ryujin_hwmon_ops = {
|
||
|
.is_visible = rog_ryujin_is_visible,
|
||
|
.read = rog_ryujin_read,
|
||
|
.read_string = rog_ryujin_read_string,
|
||
|
.write = rog_ryujin_write
|
||
|
};
|
||
|
|
||
|
static const struct hwmon_channel_info *rog_ryujin_info[] = {
|
||
|
HWMON_CHANNEL_INFO(temp,
|
||
|
HWMON_T_INPUT | HWMON_T_LABEL),
|
||
|
HWMON_CHANNEL_INFO(fan,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL,
|
||
|
HWMON_F_INPUT | HWMON_F_LABEL),
|
||
|
HWMON_CHANNEL_INFO(pwm,
|
||
|
HWMON_PWM_INPUT,
|
||
|
HWMON_PWM_INPUT,
|
||
|
HWMON_PWM_INPUT),
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct hwmon_chip_info rog_ryujin_chip_info = {
|
||
|
.ops = &rog_ryujin_hwmon_ops,
|
||
|
.info = rog_ryujin_info,
|
||
|
};
|
||
|
|
||
|
static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
|
||
|
int size)
|
||
|
{
|
||
|
struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
|
||
|
|
||
|
if (data[0] != RYUJIN_CMD_PREFIX)
|
||
|
return 0;
|
||
|
|
||
|
if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) {
|
||
|
/* Received coolant temp and speeds of pump and internal fan */
|
||
|
priv->temp_input[0] =
|
||
|
data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100;
|
||
|
priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED);
|
||
|
priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED);
|
||
|
|
||
|
if (!completion_done(&priv->cooler_status_received))
|
||
|
complete_all(&priv->cooler_status_received);
|
||
|
} else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) {
|
||
|
/* Received speeds of four fans attached to the controller */
|
||
|
priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1);
|
||
|
priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2);
|
||
|
priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3);
|
||
|
priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4);
|
||
|
|
||
|
if (!completion_done(&priv->controller_status_received))
|
||
|
complete_all(&priv->controller_status_received);
|
||
|
} else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) {
|
||
|
/* Received report for pump and internal fan duties (in %) */
|
||
|
if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) {
|
||
|
/*
|
||
|
* We received a report with zeroes for duty in both places.
|
||
|
* The device returns this as a confirmation that setting values
|
||
|
* is successful. If we initiated a write, mark it as complete.
|
||
|
*/
|
||
|
if (!completion_done(&priv->cooler_duty_set))
|
||
|
complete_all(&priv->cooler_duty_set);
|
||
|
else if (!completion_done(&priv->cooler_duty_received))
|
||
|
/*
|
||
|
* We didn't initiate a write, but received both zeroes.
|
||
|
* This means that either both duties are actually zero,
|
||
|
* or that we received a success report caused by userspace.
|
||
|
* We're expecting a report, so parse it.
|
||
|
*/
|
||
|
goto read_cooler_duty;
|
||
|
return 0;
|
||
|
}
|
||
|
read_cooler_duty:
|
||
|
priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]);
|
||
|
priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]);
|
||
|
|
||
|
if (!completion_done(&priv->cooler_duty_received))
|
||
|
complete_all(&priv->cooler_duty_received);
|
||
|
} else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) {
|
||
|
/* Received report for controller duty for fans (in PWM) */
|
||
|
if (data[RYUJIN_CONTROLLER_DUTY] == 0) {
|
||
|
/*
|
||
|
* We received a report with a zero for duty. The device returns this as
|
||
|
* a confirmation that setting the controller duty value was successful.
|
||
|
* If we initiated a write, mark it as complete.
|
||
|
*/
|
||
|
if (!completion_done(&priv->controller_duty_set))
|
||
|
complete_all(&priv->controller_duty_set);
|
||
|
else if (!completion_done(&priv->controller_duty_received))
|
||
|
/*
|
||
|
* We didn't initiate a write, but received a zero for duty.
|
||
|
* This means that either the duty is actually zero, or that
|
||
|
* we received a success report caused by userspace.
|
||
|
* We're expecting a report, so parse it.
|
||
|
*/
|
||
|
goto read_controller_duty;
|
||
|
return 0;
|
||
|
}
|
||
|
read_controller_duty:
|
||
|
priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY];
|
||
|
|
||
|
if (!completion_done(&priv->controller_duty_received))
|
||
|
complete_all(&priv->controller_duty_received);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||
|
{
|
||
|
struct rog_ryujin_data *priv;
|
||
|
int ret;
|
||
|
|
||
|
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
|
||
|
if (!priv)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
priv->hdev = hdev;
|
||
|
hid_set_drvdata(hdev, priv);
|
||
|
|
||
|
/*
|
||
|
* Initialize priv->updated to STATUS_VALIDITY seconds in the past, making
|
||
|
* the initial empty data invalid for rog_ryujin_read() without the need for
|
||
|
* a special case there.
|
||
|
*/
|
||
|
priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY);
|
||
|
|
||
|
ret = hid_parse(hdev);
|
||
|
if (ret) {
|
||
|
hid_err(hdev, "hid parse failed with %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Enable hidraw so existing user-space tools can continue to work */
|
||
|
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
|
||
|
if (ret) {
|
||
|
hid_err(hdev, "hid hw start failed with %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = hid_hw_open(hdev);
|
||
|
if (ret) {
|
||
|
hid_err(hdev, "hid hw open failed with %d\n", ret);
|
||
|
goto fail_and_stop;
|
||
|
}
|
||
|
|
||
|
priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL);
|
||
|
if (!priv->buffer) {
|
||
|
ret = -ENOMEM;
|
||
|
goto fail_and_close;
|
||
|
}
|
||
|
|
||
|
mutex_init(&priv->status_report_request_mutex);
|
||
|
mutex_init(&priv->buffer_lock);
|
||
|
spin_lock_init(&priv->status_report_request_lock);
|
||
|
init_completion(&priv->cooler_status_received);
|
||
|
init_completion(&priv->controller_status_received);
|
||
|
init_completion(&priv->cooler_duty_received);
|
||
|
init_completion(&priv->controller_duty_received);
|
||
|
init_completion(&priv->cooler_duty_set);
|
||
|
init_completion(&priv->controller_duty_set);
|
||
|
|
||
|
priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin",
|
||
|
priv, &rog_ryujin_chip_info, NULL);
|
||
|
if (IS_ERR(priv->hwmon_dev)) {
|
||
|
ret = PTR_ERR(priv->hwmon_dev);
|
||
|
hid_err(hdev, "hwmon registration failed with %d\n", ret);
|
||
|
goto fail_and_close;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
fail_and_close:
|
||
|
hid_hw_close(hdev);
|
||
|
fail_and_stop:
|
||
|
hid_hw_stop(hdev);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void rog_ryujin_remove(struct hid_device *hdev)
|
||
|
{
|
||
|
struct rog_ryujin_data *priv = hid_get_drvdata(hdev);
|
||
|
|
||
|
hwmon_device_unregister(priv->hwmon_dev);
|
||
|
|
||
|
hid_hw_close(hdev);
|
||
|
hid_hw_stop(hdev);
|
||
|
}
|
||
|
|
||
|
static const struct hid_device_id rog_ryujin_table[] = {
|
||
|
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) },
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(hid, rog_ryujin_table);
|
||
|
|
||
|
static struct hid_driver rog_ryujin_driver = {
|
||
|
.name = "rog_ryujin",
|
||
|
.id_table = rog_ryujin_table,
|
||
|
.probe = rog_ryujin_probe,
|
||
|
.remove = rog_ryujin_remove,
|
||
|
.raw_event = rog_ryujin_raw_event,
|
||
|
};
|
||
|
|
||
|
static int __init rog_ryujin_init(void)
|
||
|
{
|
||
|
return hid_register_driver(&rog_ryujin_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit rog_ryujin_exit(void)
|
||
|
{
|
||
|
hid_unregister_driver(&rog_ryujin_driver);
|
||
|
}
|
||
|
|
||
|
/* When compiled into the kernel, initialize after the HID bus */
|
||
|
late_initcall(rog_ryujin_init);
|
||
|
module_exit(rog_ryujin_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
|
||
|
MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler");
|