Commit e0f6c370 authored by Aleksa Savic's avatar Aleksa Savic Committed by Guenter Roeck

hwmon: (aquacomputer_d5next) Add support for Aquacomputer Poweradjust 3

Extend aquacomputer_d5next driver to expose the temp sensor of the
Aquacomputer Poweradjust 3 fan controller, which communicates
through a proprietary USB HID protocol.

The Poweradjust 3 is not currently known to expose firmware version
and serial number, so don't create debugfs entries if their values
can't be retrieved.

Tested by a user on Github [1].

[1] https://github.com/aleksamagicka/aquacomputer_d5next-hwmon/issues/57Signed-off-by: default avatarAleksa Savic <savicaleksa83@gmail.com>
Link: https://lore.kernel.org/r/20230131101210.8095-1-savicaleksa83@gmail.comSigned-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
parent 1c999af5
...@@ -12,6 +12,7 @@ Supported devices: ...@@ -12,6 +12,7 @@ Supported devices:
* Aquacomputer Octo fan controller * Aquacomputer Octo fan controller
* Aquacomputer Quadro fan controller * Aquacomputer Quadro fan controller
* Aquacomputer High Flow Next sensor * Aquacomputer High Flow Next sensor
* Aquacomputer Poweradjust 3 fan controller
Author: Aleksa Savic Author: Aleksa Savic
...@@ -53,6 +54,8 @@ The High Flow Next exposes +5V voltages, water quality, conductivity and flow re ...@@ -53,6 +54,8 @@ The High Flow Next exposes +5V voltages, water quality, conductivity and flow re
A temperature sensor can be connected to it, in which case it provides its reading A temperature sensor can be connected to it, in which case it provides its reading
and an estimation of the dissipated/absorbed power in the liquid cooling loop. and an estimation of the dissipated/absorbed power in the liquid cooling loop.
The Poweradjust 3 controller exposes a single external temperature sensor.
Depending on the device, not all sysfs and debugfs entries will be available. Depending on the device, not all sysfs and debugfs entries will be available.
Writing to virtual temperature sensors is not currently supported. Writing to virtual temperature sensors is not currently supported.
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
* Quadro, High Flow Next, Aquaero) * Quadro, High Flow Next, Aquaero)
* *
* Aquacomputer devices send HID reports (with ID 0x01) every second to report * Aquacomputer devices send HID reports (with ID 0x01) every second to report
* sensor values. * sensor values, except for devices that communicate through the
* legacy way (currently, Poweradjust 3).
* *
* Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com> * Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
* Copyright 2022 Jack Doan <me@jackdoan.com> * Copyright 2022 Jack Doan <me@jackdoan.com>
...@@ -28,8 +29,9 @@ ...@@ -28,8 +29,9 @@
#define USB_PRODUCT_ID_FARBWERK360 0xf010 #define USB_PRODUCT_ID_FARBWERK360 0xf010
#define USB_PRODUCT_ID_OCTO 0xf011 #define USB_PRODUCT_ID_OCTO 0xf011
#define USB_PRODUCT_ID_HIGHFLOWNEXT 0xf012 #define USB_PRODUCT_ID_HIGHFLOWNEXT 0xf012
#define USB_PRODUCT_ID_POWERADJUST3 0xf0bd
enum kinds { d5next, farbwerk, farbwerk360, octo, quadro, highflownext, aquaero }; enum kinds { d5next, farbwerk, farbwerk360, octo, quadro, highflownext, aquaero, poweradjust3 };
static const char *const aqc_device_names[] = { static const char *const aqc_device_names[] = {
[d5next] = "d5next", [d5next] = "d5next",
...@@ -38,7 +40,8 @@ static const char *const aqc_device_names[] = { ...@@ -38,7 +40,8 @@ static const char *const aqc_device_names[] = {
[octo] = "octo", [octo] = "octo",
[quadro] = "quadro", [quadro] = "quadro",
[highflownext] = "highflownext", [highflownext] = "highflownext",
[aquaero] = "aquaero" [aquaero] = "aquaero",
[poweradjust3] = "poweradjust3"
}; };
#define DRIVER_NAME "aquacomputer_d5next" #define DRIVER_NAME "aquacomputer_d5next"
...@@ -59,6 +62,9 @@ static u8 secondary_ctrl_report[] = { ...@@ -59,6 +62,9 @@ static u8 secondary_ctrl_report[] = {
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x34, 0xC6 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x34, 0xC6
}; };
/* Report IDs for legacy devices */
#define POWERADJUST3_STATUS_REPORT_ID 0x03
/* Info, sensor sizes and offsets for most Aquacomputer devices */ /* Info, sensor sizes and offsets for most Aquacomputer devices */
#define AQC_SERIAL_START 0x3 #define AQC_SERIAL_START 0x3
#define AQC_FIRMWARE_VERSION 0xD #define AQC_FIRMWARE_VERSION 0xD
...@@ -176,6 +182,13 @@ static u16 quadro_ctrl_fan_offsets[] = { 0x37, 0x8c, 0xe1, 0x136 }; /* Fan speed ...@@ -176,6 +182,13 @@ static u16 quadro_ctrl_fan_offsets[] = { 0x37, 0x8c, 0xe1, 0x136 }; /* Fan speed
#define HIGHFLOWNEXT_5V_VOLTAGE 97 #define HIGHFLOWNEXT_5V_VOLTAGE 97
#define HIGHFLOWNEXT_5V_VOLTAGE_USB 99 #define HIGHFLOWNEXT_5V_VOLTAGE_USB 99
/* Specs of the Poweradjust 3 */
#define POWERADJUST3_NUM_SENSORS 1
#define POWERADJUST3_SENSOR_REPORT_SIZE 0x32
/* Sensor report offsets for the Poweradjust 3 */
#define POWERADJUST3_SENSOR_START 0x03
/* Labels for D5 Next */ /* Labels for D5 Next */
static const char *const label_d5next_temp[] = { static const char *const label_d5next_temp[] = {
"Coolant temp" "Coolant temp"
...@@ -326,6 +339,11 @@ static const char *const label_highflownext_voltage[] = { ...@@ -326,6 +339,11 @@ static const char *const label_highflownext_voltage[] = {
"+5V USB voltage" "+5V USB voltage"
}; };
/* Labels for Poweradjust 3 */
static const char *const label_poweradjust3_temp_sensors[] = {
"External sensor"
};
struct aqc_fan_structure_offsets { struct aqc_fan_structure_offsets {
u8 voltage; u8 voltage;
u8 curr; u8 curr;
...@@ -357,6 +375,8 @@ struct aqc_data { ...@@ -357,6 +375,8 @@ struct aqc_data {
enum kinds kind; enum kinds kind;
const char *name; const char *name;
int status_report_id; /* Used for legacy devices, report is stored in buffer */
int buffer_size; int buffer_size;
u8 *buffer; u8 *buffer;
int checksum_start; int checksum_start;
...@@ -615,14 +635,49 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 ...@@ -615,14 +635,49 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3
return 0; return 0;
} }
/* Read device sensors by manually requesting the sensor report (legacy way) */
static int aqc_legacy_read(struct aqc_data *priv)
{
int ret, i, sensor_value;
mutex_lock(&priv->mutex);
memset(priv->buffer, 0x00, priv->buffer_size);
ret = hid_hw_raw_request(priv->hdev, priv->status_report_id, priv->buffer,
priv->buffer_size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
if (ret < 0)
goto unlock_and_return;
/* Temperature sensor readings */
for (i = 0; i < priv->num_temp_sensors; i++) {
sensor_value = get_unaligned_le16(priv->buffer + priv->temp_sensor_start_offset +
i * AQC_SENSOR_SIZE);
priv->temp_input[i] = sensor_value * 10;
}
priv->updated = jiffies;
unlock_and_return:
mutex_unlock(&priv->mutex);
return ret;
}
static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val) int channel, long *val)
{ {
int ret; int ret;
struct aqc_data *priv = dev_get_drvdata(dev); struct aqc_data *priv = dev_get_drvdata(dev);
if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) {
return -ENODATA; if (priv->status_report_id != 0) {
/* Legacy devices require manual reads */
ret = aqc_legacy_read(priv);
if (ret < 0)
return -ENODATA;
} else {
return -ENODATA;
}
}
switch (type) { switch (type) {
case hwmon_temp: case hwmon_temp:
...@@ -1014,9 +1069,13 @@ static void aqc_debugfs_init(struct aqc_data *priv) ...@@ -1014,9 +1069,13 @@ static void aqc_debugfs_init(struct aqc_data *priv)
dev_name(&priv->hdev->dev)); dev_name(&priv->hdev->dev));
priv->debugfs = debugfs_create_dir(name, NULL); priv->debugfs = debugfs_create_dir(name, NULL);
debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
if (priv->serial_number_start_offset != 0)
debugfs_create_file("serial_number", 0444, priv->debugfs, priv,
&serial_number_fops);
if (priv->firmware_version_offset != 0)
debugfs_create_file("firmware_version", 0444, priv->debugfs, priv,
&firmware_version_fops);
if (priv->power_cycle_count_offset != 0) if (priv->power_cycle_count_offset != 0)
debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
} }
...@@ -1214,6 +1273,17 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -1214,6 +1273,17 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->power_label = label_highflownext_power; priv->power_label = label_highflownext_power;
priv->voltage_label = label_highflownext_voltage; priv->voltage_label = label_highflownext_voltage;
break; break;
case USB_PRODUCT_ID_POWERADJUST3:
priv->kind = poweradjust3;
priv->num_fans = 0;
priv->num_temp_sensors = POWERADJUST3_NUM_SENSORS;
priv->temp_sensor_start_offset = POWERADJUST3_SENSOR_START;
priv->buffer_size = POWERADJUST3_SENSOR_REPORT_SIZE;
priv->temp_label = label_poweradjust3_temp_sensors;
break;
default: default:
break; break;
} }
...@@ -1225,6 +1295,9 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -1225,6 +1295,9 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id)
priv->fan_structure = &aqc_aquaero_fan_structure; priv->fan_structure = &aqc_aquaero_fan_structure;
break; break;
case poweradjust3:
priv->status_report_id = POWERADJUST3_STATUS_REPORT_ID;
break;
default: default:
priv->serial_number_start_offset = AQC_SERIAL_START; priv->serial_number_start_offset = AQC_SERIAL_START;
priv->firmware_version_offset = AQC_FIRMWARE_VERSION; priv->firmware_version_offset = AQC_FIRMWARE_VERSION;
...@@ -1287,6 +1360,7 @@ static const struct hid_device_id aqc_table[] = { ...@@ -1287,6 +1360,7 @@ static const struct hid_device_id aqc_table[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_QUADRO) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_QUADRO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_HIGHFLOWNEXT) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_HIGHFLOWNEXT) },
{ HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_POWERADJUST3) },
{ } { }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment