// SPDX-License-Identifier: GPL-2.0+ /* * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). * 2023 models use the Z53 code paths. * * Copyright 2021 Jonas Malaco <jonas@protocubo.io> * Copyright 2022 Aleksa Savic <savicaleksa83@gmail.com>
*/
#define DRIVER_NAME "nzxt_kraken3" #define STATUS_REPORT_ID 0x75 #define FIRMWARE_REPORT_ID 0x11 #define STATUS_VALIDITY 2000 /* In ms, equivalent to period of four status reports */ #define CUSTOM_CURVE_POINTS 40 /* For temps from 20C to 59C (critical temp) */ #define PUMP_DUTY_MIN 20 /* In percent */
/* Both values are PWM */
u16 reported_duty;
u16 fixed_duty; /* Manually set fixed duty */
u8 pwm_points[CUSTOM_CURVE_POINTS];
};
struct kraken3_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; struct mutex buffer_lock; /* For locking access to buffer */ struct mutex z53_status_request_lock; struct completion fw_version_processed; /* * For X53 devices, tracks whether an initial (one) sensor report was received to * make fancontrol not bail outright. For Z53 devices, whether a status report * was processed after requesting one.
*/ struct completion status_report_processed; /* For locking the above completion */
spinlock_t status_completion_lock;
u8 *buffer; struct kraken3_channel_info channel_info[2]; /* Pump and fan */ bool is_device_faulty;
switch (type) { case hwmon_temp: if (channel < 1) return 0444; break; case hwmon_fan: switch (priv->kind) { case X53: /* Just the pump */ if (channel < 1) return 0444; break; case Z53: case KRAKEN2023: /* Pump and fan */ if (channel < 2) return 0444; break; default: break;
} break; case hwmon_pwm: switch (attr) { case hwmon_pwm_enable: case hwmon_pwm_input: switch (priv->kind) { case X53: /* Just the pump */ if (channel < 1) return 0644; break; case Z53: case KRAKEN2023: /* Pump and fan */ if (channel < 2) return 0644; break; default: break;
} break; default: break;
} break; default: break;
}
return 0;
}
/* * Writes the command to the device with the rest of the report (up to 64 bytes) filled * with zeroes.
*/ staticint kraken3_write_expanded(struct kraken3_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);
/* Bring up pump duty to min value if needed */ if (channel == 0 && percent_value < PUMP_DUTY_MIN)
percent_value = PUMP_DUTY_MIN;
return percent_value;
}
staticint kraken3_read_x53(struct kraken3_data *priv)
{ int ret;
if (completion_done(&priv->status_report_processed)) /* * We're here because data is stale. This means that sensor reports haven't * been received for some time in kraken3_raw_event(). On X-series sensor data * can't be manually requested, so return an error.
*/ return -ENODATA;
/* * Data needs to be read, but a sensor report wasn't yet received. It's usually * fancontrol that requests data this early and it exits if it reads an error code. * So, wait for the first report to be parsed (but up to STATUS_VALIDITY). * This does not concern the Z series devices, because they send a sensor report * only when requested.
*/
ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
msecs_to_jiffies(STATUS_VALIDITY)); if (ret == 0) return -ETIMEDOUT; elseif (ret < 0) return ret;
/* The first sensor report was parsed on time and reading can continue */ return 0;
}
/* Covers Z53 and KRAKEN2023 device kinds */ staticint kraken3_read_z53(struct kraken3_data *priv)
{ int ret = mutex_lock_interruptible(&priv->z53_status_request_lock);
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;
}
/* * Disable interrupts for a moment to safely reinit the completion, * as hidraw calls could have allowed one or more readers to complete.
*/
spin_lock_bh(&priv->status_completion_lock);
reinit_completion(&priv->status_report_processed);
spin_unlock_bh(&priv->status_completion_lock);
/* Send command for getting status */
ret = kraken3_write_expanded(priv, z53_get_status_cmd, Z53_GET_STATUS_CMD_LENGTH); if (ret < 0) goto unlock_and_return;
/* Wait for completion from kraken3_raw_event() */
ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed,
msecs_to_jiffies(STATUS_VALIDITY)); if (ret == 0)
ret = -ETIMEDOUT;
unlock_and_return:
mutex_unlock(&priv->z53_status_request_lock); if (ret < 0) return ret;
return 0;
}
staticint kraken3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val)
{ struct kraken3_data *priv = dev_get_drvdata(dev); int ret;
if (time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { if (priv->kind == X53)
ret = kraken3_read_x53(priv); else
ret = kraken3_read_z53(priv);
if (ret < 0) return ret;
if (priv->is_device_faulty) return -ENODATA;
}
switch (type) { case hwmon_temp:
*val = priv->temp_input[channel]; break; case hwmon_fan:
*val = priv->fan_input[channel]; break; case hwmon_pwm: switch (attr) { case hwmon_pwm_enable:
*val = priv->channel_info[channel].mode; break; case hwmon_pwm_input:
*val = priv->channel_info[channel].reported_duty; break; default: return -EOPNOTSUPP;
} break; default: return -EOPNOTSUPP;
}
/* Set the correct ID for writing pump/fan duty (0x01 or 0x02, respectively) */
fixed_duty_cmd[SET_DUTY_ID_OFFSET] = channel + 1;
if (priv->kind == KRAKEN2023) { /* These require 1s in the next one or two slots after SET_DUTY_ID_OFFSET */
fixed_duty_cmd[SET_DUTY_ID_OFFSET + 1] = 1; if (channel == 1) /* Fan */
fixed_duty_cmd[SET_DUTY_ID_OFFSET + 2] = 1;
}
ret = kraken3_write_expanded(priv, fixed_duty_cmd, SET_CURVE_DUTY_CMD_LENGTH); return ret;
}
staticint kraken3_write_fixed_duty(struct kraken3_data *priv, long val, int channel)
{
u8 fixed_curve_points[CUSTOM_CURVE_POINTS]; int ret, percent_val, i;
percent_val = kraken3_pwm_to_percent(val, channel); if (percent_val < 0) return percent_val;
/* * The devices can only control the duty through a curve. * Since we're setting a fixed duty here, fill the whole curve * (ranging from 20C to 59C) with the same duty, except for * the last point, the critical temperature, where it's maxed * out for safety.
*/
/* Fill the custom curve with the fixed value we're setting */ for (i = 0; i < CUSTOM_CURVE_POINTS - 1; i++)
fixed_curve_points[i] = percent_val;
/* Force duty to 100% at critical temp */
fixed_curve_points[CUSTOM_CURVE_POINTS - 1] = 100;
/* Write the fixed duty curve to the device */
ret = kraken3_write_curve(priv, fixed_curve_points, channel); return ret;
}
staticint kraken3_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val)
{ struct kraken3_data *priv = dev_get_drvdata(dev); int ret;
switch (type) { case hwmon_pwm: switch (attr) { case hwmon_pwm_input: /* Remember the last set fixed duty for channel */
priv->channel_info[channel].fixed_duty = val;
if (priv->channel_info[channel].mode == manual) {
ret = kraken3_write_fixed_duty(priv, val, channel); if (ret < 0) return ret;
/* * Lock onto this value and report it until next interrupt status * report is received, so userspace tools can continue to work.
*/
priv->channel_info[channel].reported_duty = val;
} break; case hwmon_pwm_enable: if (val < 0 || val > 2) return -EINVAL;
switch (val) { case 0: /* Set channel to 100%, direct duty value */
ret = kraken3_write_fixed_duty(priv, 255, channel); if (ret < 0) return ret;
/* We don't control anything anymore */
priv->channel_info[channel].mode = off; break; case 1: /* Apply the last known direct duty value */
ret =
kraken3_write_fixed_duty(priv,
priv->channel_info[channel].fixed_duty,
channel); if (ret < 0) return ret;
priv->channel_info[channel].mode = manual; break; case 2: /* Apply the curve and note as enabled */
ret =
kraken3_write_curve(priv,
priv->channel_info[channel].pwm_points,
channel); if (ret < 0) return ret;
staticint kraken3_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
{ struct kraken3_data *priv = hid_get_drvdata(hdev); int i;
if (size < MIN_REPORT_LENGTH) return 0;
if (report->id == FIRMWARE_REPORT_ID) { /* Read firmware version */ for (i = 0; i < 3; i++)
priv->firmware_version[i] = data[FIRMWARE_VERSION_OFFSET + i];
if (!completion_done(&priv->fw_version_processed))
complete_all(&priv->fw_version_processed);
return 0;
}
if (report->id != STATUS_REPORT_ID) return 0;
if (data[TEMP_SENSOR_START_OFFSET] == 0xff && data[TEMP_SENSOR_END_OFFSET] == 0xff) {
hid_err_once(hdev, "firmware or device is possibly damaged (is SATA power connected?), not parsing reports\n");
/* * Mark first X-series device report as received, * as well as all for Z-series, if faulty.
*/
spin_lock(&priv->status_completion_lock); if (priv->kind != X53 || !completion_done(&priv->status_report_processed)) {
priv->is_device_faulty = true;
complete_all(&priv->status_report_processed);
}
spin_unlock(&priv->status_completion_lock);
return 0;
}
/* Received normal data */
priv->is_device_faulty = false;
/* Temperature and fan sensor readings */
priv->temp_input[0] =
data[TEMP_SENSOR_START_OFFSET] * 1000 + data[TEMP_SENSOR_END_OFFSET] * 100;
priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->hdev = hdev;
hid_set_drvdata(hdev, priv);
/* * Initialize ->updated to STATUS_VALIDITY seconds in the past, making * the initial empty data invalid for kraken3_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;
}
switch (hdev->product) { case USB_PRODUCT_ID_X53: case USB_PRODUCT_ID_X53_SECOND:
priv->kind = X53;
device_name = "x53"; break; case USB_PRODUCT_ID_Z53:
priv->kind = Z53;
device_name = "z53"; break; case USB_PRODUCT_ID_KRAKEN2023:
priv->kind = KRAKEN2023;
device_name = "kraken2023"; break; case USB_PRODUCT_ID_KRAKEN2023_ELITE:
priv->kind = KRAKEN2023;
device_name = "kraken2023elite"; break; default:
ret = -ENODEV; goto fail_and_close;
}
priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); if (!priv->buffer) {
ret = -ENOMEM; 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.