/* The HID report that the official software always sends * after writing values, currently same for all devices
*/ #define SECONDARY_CTRL_REPORT_ID 0x02 #define SECONDARY_CTRL_REPORT_SIZE 0x0B
/* Control report offsets for the D5 Next pump */ #define D5NEXT_TEMP_CTRL_OFFSET 0x2D /* Temperature sensor offsets location */ static u16 d5next_ctrl_fan_offsets[] = { 0x97, 0x42 }; /* Pump and fan speed (from 0-100%) */
/* Specs of the Aquastream Ultimate pump */ /* Pump does not follow the standard structure, so only consider the fan */ #define AQUASTREAMULT_NUM_FANS 1 #define AQUASTREAMULT_NUM_SENSORS 2
/* Fan structure offsets for all devices except those above */ staticstruct aqc_fan_structure_offsets aqc_general_fan_structure = {
.voltage = AQC_FAN_VOLTAGE_OFFSET,
.curr = AQC_FAN_CURRENT_OFFSET,
.power = AQC_FAN_POWER_OFFSET,
.speed = AQC_FAN_SPEED_OFFSET
};
struct aqc_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; struct mutex mutex; /* Used for locking access when reading and writing PWM values */ enum kinds kind; constchar *name;
int status_report_id; /* Used for legacy devices, report is stored in buffer */ int ctrl_report_id; int secondary_ctrl_report_id; int secondary_ctrl_report_size;
u8 *secondary_ctrl_report;
ktime_t last_ctrl_report_op; int ctrl_report_delay; /* Delay between two ctrl report operations, in ms */
int buffer_size;
u8 *buffer; int checksum_start; int checksum_length; int checksum_offset;
int num_fans;
u16 *fan_sensor_offsets;
u16 *fan_ctrl_offsets; int num_temp_sensors; int temp_sensor_start_offset; int num_virtual_temp_sensors; int virtual_temp_sensor_start_offset; int num_calc_virt_temp_sensors; int calc_virt_temp_sensor_start_offset;
u16 temp_ctrl_offset;
u16 power_cycle_count_offset; int num_flow_sensors;
u8 flow_sensors_start_offset;
u8 flow_pulses_ctrl_offset; struct aqc_fan_structure_offsets *fan_structure;
/* General info, same across all devices */
u8 serial_number_start_offset;
u32 serial_number[2];
u8 firmware_version_offset;
u16 firmware_version;
/* How many times the device was powered on, if available */
u32 power_cycles;
/* Sensor values */
s32 temp_input[20]; /* Max 4 physical and 16 virtual or 8 physical and 12 virtual */
s32 speed_input[9];
u32 speed_input_min[1];
u32 speed_input_target[1];
u32 speed_input_max[1];
u32 power_input[8];
u16 voltage_input[8];
u16 current_input[8];
/* Converts to centi-percent */ staticint aqc_pwm_to_percent(long val)
{ if (val < 0 || val > 255) return -EINVAL;
return DIV_ROUND_CLOSEST(val * 100 * 100, 255);
}
/* Converts raw value for Aquastream XT pump speed to RPM */ staticint aqc_aquastreamxt_convert_pump_rpm(u16 val)
{ if (val > 0) return DIV_ROUND_CLOSEST(AQUASTREAMXT_PUMP_CONVERSION_CONST, val); return 0;
}
/* Converts raw value for Aquastream XT fan speed to RPM */ staticint aqc_aquastreamxt_convert_fan_rpm(u16 val)
{ if (val > 0) return DIV_ROUND_CLOSEST(AQUASTREAMXT_FAN_CONVERSION_CONST, val); return 0;
}
staticvoid aqc_delay_ctrl_report(struct aqc_data *priv)
{ /* * If previous read or write is too close to this one, delay the current operation * to give the device enough time to process the previous one.
*/ if (priv->ctrl_report_delay) {
s64 delta = ktime_ms_delta(ktime_get(), priv->last_ctrl_report_op);
if (delta < priv->ctrl_report_delay)
msleep(priv->ctrl_report_delay - delta);
}
}
/* Expects the mutex to be locked */ staticint aqc_get_ctrl_data(struct aqc_data *priv)
{ int ret;
aqc_delay_ctrl_report(priv);
memset(priv->buffer, 0x00, priv->buffer_size);
ret = hid_hw_raw_request(priv->hdev, priv->ctrl_report_id, priv->buffer, priv->buffer_size,
HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0)
ret = -ENODATA;
priv->last_ctrl_report_op = ktime_get();
return ret;
}
/* Expects the mutex to be locked */ staticint aqc_send_ctrl_data(struct aqc_data *priv)
{ int ret;
u16 checksum;
aqc_delay_ctrl_report(priv);
/* Checksum is not needed for Aquaero */ if (priv->kind != aquaero) { /* Init and xorout value for CRC-16/USB is 0xffff */
checksum = crc16(0xffff, priv->buffer + priv->checksum_start,
priv->checksum_length);
checksum ^= 0xffff;
/* Place the new checksum at the end of the report */
put_unaligned_be16(checksum, priv->buffer + priv->checksum_offset);
}
/* Send the patched up report back to the device */
ret = hid_hw_raw_request(priv->hdev, priv->ctrl_report_id, priv->buffer, priv->buffer_size,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret < 0) goto record_access_and_ret;
/* The official software sends this report after every change, so do it here as well */
ret = hid_hw_raw_request(priv->hdev, priv->secondary_ctrl_report_id,
priv->secondary_ctrl_report, priv->secondary_ctrl_report_size,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
/* Refreshes the control buffer and stores value at offset in val */ staticint aqc_get_ctrl_val(struct aqc_data *priv, int offset, long *val, int type)
{ int ret;
mutex_lock(&priv->mutex);
ret = aqc_get_ctrl_data(priv); if (ret < 0) goto unlock_and_return;
switch (type) { case AQC_BE16:
*val = (s16)get_unaligned_be16(priv->buffer + offset); break; case AQC_8:
*val = priv->buffer[offset]; break; default:
ret = -EINVAL;
}
switch (type) { case hwmon_temp: if (channel < priv->num_temp_sensors) { switch (attr) { case hwmon_temp_label: case hwmon_temp_input: return 0444; case hwmon_temp_offset: if (priv->temp_ctrl_offset != 0) return 0644; break; default: break;
}
}
if (channel <
priv->num_temp_sensors + priv->num_virtual_temp_sensors +
priv->num_calc_virt_temp_sensors) switch (attr) { case hwmon_temp_label: case hwmon_temp_input: return 0444; default: break;
} break; case hwmon_pwm: if (priv->fan_ctrl_offsets && channel < priv->num_fans) { switch (attr) { case hwmon_pwm_input: return 0644; default: break;
}
} break; case hwmon_fan: switch (attr) { case hwmon_fan_input: case hwmon_fan_label: switch (priv->kind) { case aquastreamult: /* * Special case to support pump RPM, fan RPM, * pressure and flow sensor
*/ if (channel < 4) return 0444; break; case highflownext: /* Special case to support flow sensor, water quality * and conductivity
*/ if (channel < 3) return 0444; break; case leakshield: /* Special case for Leakshield sensors */ if (channel < 5) return 0444; break; case aquaero: case octo: case quadro: case highflow: /* Special case to support flow sensors */ if (channel < priv->num_fans + priv->num_flow_sensors) return 0444; break; default: if (channel < priv->num_fans) return 0444; break;
} break; case hwmon_fan_pulses: /* Special case for Quadro/Octo flow sensor */ if (channel == priv->num_fans) { switch (priv->kind) { case quadro: case octo: return 0644; default: break;
}
} break; case hwmon_fan_min: case hwmon_fan_max: case hwmon_fan_target: /* Special case for Leakshield pressure sensor */ if (priv->kind == leakshield && channel == 0) return 0444; break; default: break;
} break; case hwmon_power: switch (priv->kind) { case aquastreamult: /* Special case to support pump and fan power */ if (channel < 2) return 0444; break; case highflownext: /* Special case to support one power sensor */ if (channel == 0) return 0444; break; case aquastreamxt: break; default: if (channel < priv->num_fans) return 0444; break;
} break; case hwmon_curr: switch (priv->kind) { case aquastreamult: /* Special case to support pump and fan current */ if (channel < 2) return 0444; break; case aquastreamxt: /* Special case to support pump current */ if (channel == 0) return 0444; break; default: if (channel < priv->num_fans) return 0444; break;
} break; case hwmon_in: switch (priv->kind) { case d5next: /* Special case to support +5V and +12V voltage sensors */ if (channel < priv->num_fans + 2) return 0444; break; case aquastreamult: case highflownext: /* Special case to support two voltage sensors */ if (channel < 2) return 0444; break; default: if (channel < priv->num_fans) return 0444; break;
} break; default: break;
}
return 0;
}
/* Read device sensors by manually requesting the sensor report (legacy way) */ staticint 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); if (sensor_value == AQC_SENSOR_NA)
priv->temp_input[i] = -ENODATA; else
priv->temp_input[i] = sensor_value * 10;
}
/* Special-case sensor readings */ switch (priv->kind) { case aquastreamxt: /* Info provided with every report */
priv->serial_number[0] = get_unaligned_le16(priv->buffer +
priv->serial_number_start_offset);
priv->firmware_version =
get_unaligned_le16(priv->buffer + priv->firmware_version_offset);
/* Number of sensors that are not calculated */ int num_non_calc_sensors = priv->num_temp_sensors + priv->num_virtual_temp_sensors;
switch (type) { case hwmon_temp: if (channel < priv->num_temp_sensors) {
*str = priv->temp_label[channel];
} else { if (priv->kind == aquaero && channel >= num_non_calc_sensors)
*str =
priv->calc_virt_temp_label[channel - num_non_calc_sensors]; else
*str = priv->virtual_temp_label[channel - priv->num_temp_sensors];
} break; case hwmon_fan:
*str = priv->speed_label[channel]; break; case hwmon_power:
*str = priv->power_label[channel]; break; case hwmon_in:
*str = priv->voltage_label[channel]; break; case hwmon_curr:
*str = priv->current_label[channel]; break; default: return -EOPNOTSUPP;
}
return 0;
}
staticint aqc_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val)
{ int ret, pwm_value; /* Arrays for setting multiple values at once in the control report */ int ctrl_values_offsets[4]; long ctrl_values[4]; int ctrl_values_types[4]; struct aqc_data *priv = dev_get_drvdata(dev);
switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_offset: /* Limit temp offset to +/- 15K as in the official software */
val = clamp_val(val, -15000, 15000) / 10;
ret =
aqc_set_ctrl_val(priv, priv->temp_ctrl_offset +
channel * AQC_SENSOR_SIZE, val, AQC_BE16); if (ret < 0) return ret; break; default: return -EOPNOTSUPP;
} break; case hwmon_fan: switch (attr) { case hwmon_fan_pulses:
val = clamp_val(val, 10, 1000);
ret = aqc_set_ctrl_val(priv, priv->flow_pulses_ctrl_offset,
val, AQC_BE16); if (ret < 0) return ret; break; default: break;
} break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input:
pwm_value = aqc_pwm_to_percent(val); if (pwm_value < 0) return pwm_value;
switch (priv->kind) { case aquaero: /* Write pwm value to preset corresponding to the channel */
ctrl_values_offsets[0] = AQUAERO_CTRL_PRESET_START +
channel * AQUAERO_CTRL_PRESET_SIZE;
ctrl_values[0] = pwm_value;
ctrl_values_types[0] = AQC_BE16;
/* Write preset number in fan control source */
ctrl_values_offsets[1] = priv->fan_ctrl_offsets[channel] +
AQUAERO_FAN_CTRL_SRC_OFFSET;
ctrl_values[1] = AQUAERO_CTRL_PRESET_ID + channel;
ctrl_values_types[1] = AQC_BE16;
/* Set minimum power to 0 to allow the fan to turn off */
ctrl_values_offsets[2] = priv->fan_ctrl_offsets[channel] +
AQUAERO_FAN_CTRL_MIN_PWR_OFFSET;
ctrl_values[2] = 0;
ctrl_values_types[2] = AQC_BE16;
/* Set maximum power to 255 to allow the fan to reach max speed */
ctrl_values_offsets[3] = priv->fan_ctrl_offsets[channel] +
AQUAERO_FAN_CTRL_MAX_PWR_OFFSET;
ctrl_values[3] = aqc_pwm_to_percent(255);
ctrl_values_types[3] = AQC_BE16;
ret = aqc_set_ctrl_vals(priv, ctrl_values_offsets, ctrl_values,
ctrl_values_types, 4); if (ret < 0) return ret; break; default:
ret = aqc_set_ctrl_val(priv, priv->fan_ctrl_offsets[channel],
pwm_value, AQC_BE16); if (ret < 0) return ret; break;
} break; default: break;
} break; default: return -EOPNOTSUPP;
}
/* Second temp sensor is not positioned after the first one, read it here */
priv->temp_input[1] = get_unaligned_be16(data + LEAKSHIELD_TEMPERATURE_2) * 10; break; default: break;
}
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
priv->updated = jiffies - STATUS_UPDATE_INTERVAL;
ret = hid_parse(hdev); if (ret) return ret;
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); if (ret) return ret;
ret = hid_hw_open(hdev); if (ret) goto fail_and_stop;
switch (hdev->product) { case USB_PRODUCT_ID_AQUAERO: /* * Aquaero presents itself as three HID devices under the same product ID: * "aquaero keyboard/mouse", "aquaero System Control" and "aquaero Device", * which is the one we want to communicate with. Unlike most other Aquacomputer * devices, Aquaero does not return meaningful data when explicitly requested * using GET_FEATURE_REPORT. * * The difference between "aquaero Device" and the other two is in the collections * they present. The two other devices have the type of the second element in * their respective collections set to 1, while the real device has it set to 0.
*/ if (hdev->collection[1].type != 0) {
ret = -ENODEV; goto fail_and_close;
}
priv->temp_label = label_highflownext_temp_sensors;
priv->speed_label = label_highflownext_fan_speed;
priv->power_label = label_highflownext_power;
priv->voltage_label = label_highflownext_voltage; break; case USB_PRODUCT_ID_LEAKSHIELD: /* * Choose the right Leakshield device, because * the other one acts as a keyboard
*/ if (hdev->type != 2) {
ret = -ENODEV; goto fail_and_close;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.