On Sun, Oct 31, 2021 at 09:30:58AM +0600, Aleksandr Mezin wrote: > This driver implements monitoring and control of fans plugged into the > device. Besides typical speed monitoring and PWM duty cycle control, > voltage and current are reported for every fan. > > The device also has 2 connectors for RGB LEDs, support for them isn't > implemented (mainly because there is no standardized sysfs interface). > > Also, the device has a noise sensor, but the sensor seems to be completely > useless (and very imprecise), so support for it isn't implemented too. > > The driver coexists with userspace tools that access the device through > hidraw interface with no known issues. > > The driver has been tested on x86_64, built in and as a module. > > Some changes/improvements were suggested by Jonas Malaco. > > Signed-off-by: Aleksandr Mezin <mezin.alexander@xxxxxxxxx> Applied. Thanks, Guenter > --- > Changes in v2: > - Renamed the driver to nzxt-smart2. > - Removed lockdep assert, mutex locking during initialization and reset_resume. > - Removed unnecessary READ_ONCE, WRITE_ONCE > - Consistent error handling, always goto/return in error case. > - Tried to improve comments and documentation a bit. > > Documentation/hwmon/index.rst | 1 + > Documentation/hwmon/nzxt-smart2.rst | 62 +++ > MAINTAINERS | 7 + > drivers/hwmon/Kconfig | 10 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/nzxt-smart2.c | 829 ++++++++++++++++++++++++++++ > 6 files changed, 910 insertions(+) > create mode 100644 Documentation/hwmon/nzxt-smart2.rst > create mode 100644 drivers/hwmon/nzxt-smart2.c > > diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst > index f790f1260c33..1020eadf8ab3 100644 > --- a/Documentation/hwmon/index.rst > +++ b/Documentation/hwmon/index.rst > @@ -149,6 +149,7 @@ Hardware Monitoring Kernel Drivers > nsa320 > ntc_thermistor > nzxt-kraken2 > + nzxt-smart2 > occ > pc87360 > pc87427 > diff --git a/Documentation/hwmon/nzxt-smart2.rst b/Documentation/hwmon/nzxt-smart2.rst > new file mode 100644 > index 000000000000..d9d1b2742665 > --- /dev/null > +++ b/Documentation/hwmon/nzxt-smart2.rst > @@ -0,0 +1,62 @@ > +.. SPDX-License-Identifier: GPL-2.0-or-later > + > +Kernel driver nzxt-smart2 > +========================= > + > +Supported devices: > + > +- NZXT RGB & Fan controller > +- NZXT Smart Device v2 > + > +Description > +----------- > + > +This driver implements monitoring and control of fans plugged into the device. > +Besides typical speed monitoring and PWM duty cycle control, voltage and current > +is reported for every fan. > + > +The device also has two connectors for RGB LEDs; support for them isn't > +implemented (mainly because there is no standardized sysfs interface). > + > +Also, the device has a noise sensor, but the sensor seems to be completely > +useless (and very imprecise), so support for it isn't implemented too. > + > +Usage Notes > +----------- > + > +The device should be autodetected, and the driver should load automatically. > + > +If fans are plugged in/unplugged while the system is powered on, the driver > +must be reloaded to detect configuration changes; otherwise, new fans can't > +be controlled (`pwm*` changes will be ignored). It is necessary because the > +device has a dedicated "detect fans" command, and currently, it is executed only > +during initialization. Speed, voltage, current monitoring will work even without > +reload. As an alternative to reloading the module, a userspace tool (like > +`liquidctl`_) can be used to run "detect fans" command through hidraw interface. > + > +The driver coexists with userspace tools that access the device through hidraw > +interface with no known issues. > + > +.. _liquidctl: https://github.com/liquidctl/liquidctl > + > +Sysfs entries > +------------- > + > +======================= ======================================================== > +fan[1-3]_input Fan speed monitoring (in rpm). > +curr[1-3]_input Current supplied to the fan (in milliamperes). > +in[0-2]_input Voltage supplied to the fan (in millivolts). > +pwm[1-3] Controls fan speed: PWM duty cycle for PWM-controlled > + fans, voltage for other fans. Voltage can be changed in > + 9-12 V range, but the value of the sysfs attribute is > + always in 0-255 range (1 = 9V, 255 = 12V). Setting the > + attribute to 0 turns off the fan completely. > +pwm[1-3]_enable 1 if the fan can be controlled by writing to the > + corresponding pwm* attribute, 0 otherwise. The device > + can control only the fans it detected itself, so the > + attribute is read-only. > +pwm[1-3]_mode Read-only, 1 for PWM-controlled fans, 0 for other fans > + (or if no fan connected). > +update_interval The interval at which all inputs are updated (in > + milliseconds). The default is 1000ms. Minimum is 250ms. > +======================= ======================================================== > diff --git a/MAINTAINERS b/MAINTAINERS > index f26920f0fa65..8b2342317dc6 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -13543,6 +13543,13 @@ S: Maintained > F: Documentation/hwmon/nzxt-kraken2.rst > F: drivers/hwmon/nzxt-kraken2.c > > +NZXT-SMART2 HARDWARE MONITORING DRIVER > +M: Aleksandr Mezin <mezin.alexander@xxxxxxxxx> > +L: linux-hwmon@xxxxxxxxxxxxxxx > +S: Maintained > +F: Documentation/hwmon/nzxt-smart2.rst > +F: drivers/hwmon/nzxt-smart2.c > + > OBJAGG > M: Jiri Pirko <jiri@xxxxxxxxxx> > L: netdev@xxxxxxxxxxxxxxx > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index c4578e8f34bb..5301b7fcfa49 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -1502,6 +1502,16 @@ config SENSORS_NZXT_KRAKEN2 > This driver can also be built as a module. If so, the module > will be called nzxt-kraken2. > > +config SENSORS_NZXT_SMART2 > + tristate "NZXT RGB & Fan Controller/Smart Device v2" > + depends on USB_HID > + help > + If you say yes here you get support for hardware monitoring for the > + NZXT RGB & Fan Controller/Smart Device v2. > + > + This driver can also be built as a module. If so, the module > + will be called nzxt-smart2. > + > source "drivers/hwmon/occ/Kconfig" > > config SENSORS_PCF8591 > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index 162940270661..335a3b45c862 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -156,6 +156,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o > obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o > obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o > obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o > +obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o > obj-$(CONFIG_SENSORS_PC87360) += pc87360.o > obj-$(CONFIG_SENSORS_PC87427) += pc87427.o > obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o > diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c > new file mode 100644 > index 000000000000..534d39b8908e > --- /dev/null > +++ b/drivers/hwmon/nzxt-smart2.c > @@ -0,0 +1,829 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Reverse-engineered NZXT RGB & Fan Controller/Smart Device v2 driver. > + * > + * Copyright (c) 2021 Aleksandr Mezin > + */ > + > +#include <linux/hid.h> > +#include <linux/hwmon.h> > +#include <linux/math.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/spinlock.h> > +#include <linux/wait.h> > + > +#include <asm/byteorder.h> > +#include <asm/unaligned.h> > + > +/* > + * The device has only 3 fan channels/connectors. But all HID reports have > + * space reserved for up to 8 channels. > + */ > +#define FAN_CHANNELS 3 > +#define FAN_CHANNELS_MAX 8 > + > +#define UPDATE_INTERVAL_DEFAULT_MS 1000 > + > +/* These strings match labels on the device exactly */ > +static const char *const fan_label[] = { > + "FAN 1", > + "FAN 2", > + "FAN 3", > +}; > + > +static const char *const curr_label[] = { > + "FAN 1 Current", > + "FAN 2 Current", > + "FAN 3 Current", > +}; > + > +static const char *const in_label[] = { > + "FAN 1 Voltage", > + "FAN 2 Voltage", > + "FAN 3 Voltage", > +}; > + > +enum { > + INPUT_REPORT_ID_FAN_CONFIG = 0x61, > + INPUT_REPORT_ID_FAN_STATUS = 0x67, > +}; > + > +enum { > + FAN_STATUS_REPORT_SPEED = 0x02, > + FAN_STATUS_REPORT_VOLTAGE = 0x04, > +}; > + > +enum { > + FAN_TYPE_NONE = 0, > + FAN_TYPE_DC = 1, > + FAN_TYPE_PWM = 2, > +}; > + > +struct unknown_static_data { > + /* > + * Some configuration data? Stays the same after fan speed changes, > + * changes in fan configuration, reboots and driver reloads. > + * > + * The same data in multiple report types. > + * > + * Byte 12 seems to be the number of fan channels, but I am not sure. > + */ > + u8 unknown1[14]; > +} __packed; > + > +/* > + * The device sends this input report in response to "detect fans" command: > + * a 2-byte output report { 0x60, 0x03 }. > + */ > +struct fan_config_report { > + /* report_id should be INPUT_REPORT_ID_FAN_CONFIG = 0x61 */ > + u8 report_id; > + /* Always 0x03 */ > + u8 magic; > + struct unknown_static_data unknown_data; > + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ > + u8 fan_type[FAN_CHANNELS_MAX]; > +} __packed; > + > +/* > + * The device sends these reports at a fixed interval (update interval) - > + * one report with type = FAN_STATUS_REPORT_SPEED, and one report with type = > + * FAN_STATUS_REPORT_VOLTAGE per update interval. > + */ > +struct fan_status_report { > + /* report_id should be INPUT_REPORT_ID_STATUS = 0x67 */ > + u8 report_id; > + /* FAN_STATUS_REPORT_SPEED = 0x02 or FAN_STATUS_REPORT_VOLTAGE = 0x04 */ > + u8 type; > + struct unknown_static_data unknown_data; > + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ > + u8 fan_type[FAN_CHANNELS_MAX]; > + > + union { > + /* When type == FAN_STATUS_REPORT_SPEED */ > + struct { > + /* > + * Fan speed, in RPM. Zero for channels without fans > + * connected. > + */ > + __le16 fan_rpm[FAN_CHANNELS_MAX]; > + /* > + * Fan duty cycle, in percent. Non-zero even for > + * channels without fans connected. > + */ > + u8 duty_percent[FAN_CHANNELS_MAX]; > + /* > + * Exactly the same values as duty_percent[], non-zero > + * for disconnected fans too. > + */ > + u8 duty_percent_dup[FAN_CHANNELS_MAX]; > + /* "Case Noise" in db */ > + u8 noise_db; > + } __packed fan_speed; > + /* When type == FAN_STATUS_REPORT_VOLTAGE */ > + struct { > + /* > + * Voltage, in millivolts. Non-zero even when fan is > + * not connected. > + */ > + __le16 fan_in[FAN_CHANNELS_MAX]; > + /* > + * Current, in milliamperes. Near-zero when > + * disconnected. > + */ > + __le16 fan_current[FAN_CHANNELS_MAX]; > + } __packed fan_voltage; > + } __packed; > +} __packed; > + > +#define OUTPUT_REPORT_SIZE 64 > + > +enum { > + OUTPUT_REPORT_ID_INIT_COMMAND = 0x60, > + OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62, > +}; > + > +enum { > + INIT_COMMAND_SET_UPDATE_INTERVAL = 0x02, > + INIT_COMMAND_DETECT_FANS = 0x03, > +}; > + > +/* > + * This output report sets pwm duty cycle/target fan speed for one or more > + * channels. > + */ > +struct set_fan_speed_report { > + /* report_id should be OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62 */ > + u8 report_id; > + /* Should be 0x01 */ > + u8 magic; > + /* To change fan speed on i-th channel, set i-th bit here */ > + u8 channel_bit_mask; > + /* > + * Fan duty cycle/target speed in percent. For voltage-controlled fans, > + * the minimal voltage (duty_percent = 1) is about 9V. > + * Setting duty_percent to 0 (if the channel is selected in > + * channel_bit_mask) turns off the fan completely (regardless of the > + * control mode). > + */ > + u8 duty_percent[FAN_CHANNELS_MAX]; > +} __packed; > + > +struct drvdata { > + struct hid_device *hid; > + struct device *hwmon; > + > + u8 fan_duty_percent[FAN_CHANNELS]; > + u16 fan_rpm[FAN_CHANNELS]; > + bool pwm_status_received; > + > + u16 fan_in[FAN_CHANNELS]; > + u16 fan_curr[FAN_CHANNELS]; > + bool voltage_status_received; > + > + u8 fan_type[FAN_CHANNELS]; > + bool fan_config_received; > + > + /* > + * wq is used to wait for *_received flags to become true. > + * All accesses to *_received flags and fan_* arrays are performed with > + * wq.lock held. > + */ > + wait_queue_head_t wq; > + /* > + * mutex is used to: > + * 1) Prevent concurrent conflicting changes to update interval and pwm > + * values (after sending an output hid report, the corresponding field > + * in drvdata must be updated, and only then new output reports can be > + * sent). > + * 2) Synchronize access to output_buffer (well, the buffer is here, > + * because synchronization is necessary anyway - so why not get rid of > + * a kmalloc?). > + */ > + struct mutex mutex; > + long update_interval; > + u8 output_buffer[OUTPUT_REPORT_SIZE]; > +}; > + > +static long scale_pwm_value(long val, long orig_max, long new_max) > +{ > + if (val <= 0) > + return 0; > + > + /* > + * Positive values should not become zero: 0 completely turns off the > + * fan. > + */ > + return max(1L, DIV_ROUND_CLOSEST(min(val, orig_max) * new_max, orig_max)); > +} > + > +static void handle_fan_config_report(struct drvdata *drvdata, void *data, int size) > +{ > + struct fan_config_report *report = data; > + int i; > + > + if (size < sizeof(struct fan_config_report)) > + return; > + > + if (report->magic != 0x03) > + return; > + > + spin_lock(&drvdata->wq.lock); > + > + for (i = 0; i < FAN_CHANNELS; i++) > + drvdata->fan_type[i] = report->fan_type[i]; > + > + drvdata->fan_config_received = true; > + wake_up_all_locked(&drvdata->wq); > + spin_unlock(&drvdata->wq.lock); > +} > + > +static void handle_fan_status_report(struct drvdata *drvdata, void *data, int size) > +{ > + struct fan_status_report *report = data; > + int i; > + > + if (size < sizeof(struct fan_status_report)) > + return; > + > + spin_lock(&drvdata->wq.lock); > + > + /* > + * The device sends INPUT_REPORT_ID_FAN_CONFIG = 0x61 report in response > + * to "detect fans" command. Only accept other data after getting 0x61, > + * to make sure that fan detection is complete. In particular, fan > + * detection resets pwm values. > + */ > + if (!drvdata->fan_config_received) { > + spin_unlock(&drvdata->wq.lock); > + return; > + } > + > + for (i = 0; i < FAN_CHANNELS; i++) { > + if (drvdata->fan_type[i] == report->fan_type[i]) > + continue; > + > + /* > + * This should not happen (if my expectations about the device > + * are correct). > + * > + * Even if the userspace sends fan detect command through > + * hidraw, fan config report should arrive first. > + */ > + hid_warn_once(drvdata->hid, > + "Fan %d type changed unexpectedly from %d to %d", > + i, drvdata->fan_type[i], report->fan_type[i]); > + drvdata->fan_type[i] = report->fan_type[i]; > + } > + > + switch (report->type) { > + case FAN_STATUS_REPORT_SPEED: > + for (i = 0; i < FAN_CHANNELS; i++) { > + drvdata->fan_rpm[i] = > + get_unaligned_le16(&report->fan_speed.fan_rpm[i]); > + drvdata->fan_duty_percent[i] = > + report->fan_speed.duty_percent[i]; > + } > + > + drvdata->pwm_status_received = true; > + wake_up_all_locked(&drvdata->wq); > + break; > + > + case FAN_STATUS_REPORT_VOLTAGE: > + for (i = 0; i < FAN_CHANNELS; i++) { > + drvdata->fan_in[i] = > + get_unaligned_le16(&report->fan_voltage.fan_in[i]); > + drvdata->fan_curr[i] = > + get_unaligned_le16(&report->fan_voltage.fan_current[i]); > + } > + > + drvdata->voltage_status_received = true; > + wake_up_all_locked(&drvdata->wq); > + break; > + } > + > + spin_unlock(&drvdata->wq.lock); > +} > + > +static umode_t nzxt_smart2_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_enable: > + return 0644; > + > + default: > + return 0444; > + } > + > + case hwmon_chip: > + switch (attr) { > + case hwmon_chip_update_interval: > + return 0644; > + > + default: > + return 0444; > + } > + > + default: > + return 0444; > + } > +} > + > +static int nzxt_smart2_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + struct drvdata *drvdata = dev_get_drvdata(dev); > + int res = -EINVAL; > + > + if (type == hwmon_chip) { > + switch (attr) { > + case hwmon_chip_update_interval: > + *val = drvdata->update_interval; > + return 0; > + > + default: > + return -EINVAL; > + } > + } > + > + spin_lock_irq(&drvdata->wq.lock); > + > + switch (type) { > + case hwmon_pwm: > + /* > + * fancontrol: > + * 1) remembers pwm* values when it starts > + * 2) needs pwm*_enable to be 1 on controlled fans > + * So make sure we have correct data before allowing pwm* reads. > + * Returning errors for pwm of fan speed read can even cause > + * fancontrol to shut down. So the wait is unavoidable. > + */ > + switch (attr) { > + case hwmon_pwm_enable: > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->fan_config_received); > + if (res) > + goto unlock; > + > + *val = drvdata->fan_type[channel] != FAN_TYPE_NONE; > + break; > + > + case hwmon_pwm_mode: > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->fan_config_received); > + if (res) > + goto unlock; > + > + *val = drvdata->fan_type[channel] == FAN_TYPE_PWM; > + break; > + > + case hwmon_pwm_input: > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->pwm_status_received); > + if (res) > + goto unlock; > + > + *val = scale_pwm_value(drvdata->fan_duty_percent[channel], > + 100, 255); > + break; > + } > + break; > + > + case hwmon_fan: > + /* > + * It's not strictly necessary to wait for *_received in the > + * remaining cases (fancontrol doesn't care about them). But I'm > + * doing it to have consistent behavior. > + */ > + if (attr == hwmon_fan_input) { > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->pwm_status_received); > + if (res) > + goto unlock; > + > + *val = drvdata->fan_rpm[channel]; > + } > + break; > + > + case hwmon_in: > + if (attr == hwmon_in_input) { > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->voltage_status_received); > + if (res) > + goto unlock; > + > + *val = drvdata->fan_in[channel]; > + } > + break; > + > + case hwmon_curr: > + if (attr == hwmon_curr_input) { > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->voltage_status_received); > + if (res) > + goto unlock; > + > + *val = drvdata->fan_curr[channel]; > + } > + break; > + > + default: > + break; > + } > + > +unlock: > + spin_unlock_irq(&drvdata->wq.lock); > + return res; > +} > + > +static int send_output_report(struct drvdata *drvdata, const void *data, > + size_t data_size) > +{ > + int ret; > + > + if (data_size > sizeof(drvdata->output_buffer)) > + return -EINVAL; > + > + memcpy(drvdata->output_buffer, data, data_size); > + > + if (data_size < sizeof(drvdata->output_buffer)) > + memset(drvdata->output_buffer + data_size, 0, > + sizeof(drvdata->output_buffer) - data_size); > + > + ret = hid_hw_output_report(drvdata->hid, drvdata->output_buffer, > + sizeof(drvdata->output_buffer)); > + return ret < 0 ? ret : 0; > +} > + > +static int set_pwm(struct drvdata *drvdata, int channel, long val) > +{ > + int ret; > + u8 duty_percent = scale_pwm_value(val, 255, 100); > + > + struct set_fan_speed_report report = { > + .report_id = OUTPUT_REPORT_ID_SET_FAN_SPEED, > + .magic = 1, > + .channel_bit_mask = 1 << channel > + }; > + > + ret = mutex_lock_interruptible(&drvdata->mutex); > + if (ret) > + return ret; > + > + report.duty_percent[channel] = duty_percent; > + ret = send_output_report(drvdata, &report, sizeof(report)); > + if (ret) > + goto unlock; > + > + /* > + * pwmconfig and fancontrol scripts expect pwm writes to take effect > + * immediately (i. e. read from pwm* sysfs should return the value > + * written into it). The device seems to always accept pwm values - even > + * when there is no fan connected - so update pwm status without waiting > + * for a report, to make pwmconfig and fancontrol happy. Worst case - > + * if the device didn't accept new pwm value for some reason (never seen > + * this in practice) - it will be reported incorrectly only until next > + * update. This avoids "fan stuck" messages from pwmconfig, and > + * fancontrol setting fan speed to 100% during shutdown. > + */ > + spin_lock_bh(&drvdata->wq.lock); > + drvdata->fan_duty_percent[channel] = duty_percent; > + spin_unlock_bh(&drvdata->wq.lock); > + > +unlock: > + mutex_unlock(&drvdata->mutex); > + return ret; > +} > + > +/* > + * Workaround for fancontrol/pwmconfig trying to write to pwm*_enable even if it > + * already is 1 and read-only. Otherwise, fancontrol won't restore pwm on > + * shutdown properly. > + */ > +static int set_pwm_enable(struct drvdata *drvdata, int channel, long val) > +{ > + long expected_val; > + int res; > + > + spin_lock_irq(&drvdata->wq.lock); > + > + res = wait_event_interruptible_locked_irq(drvdata->wq, > + drvdata->fan_config_received); > + if (res) { > + spin_unlock_irq(&drvdata->wq.lock); > + return res; > + } > + > + expected_val = drvdata->fan_type[channel] != FAN_TYPE_NONE; > + > + spin_unlock_irq(&drvdata->wq.lock); > + > + return (val == expected_val) ? 0 : -EOPNOTSUPP; > +} > + > +/* > + * Control byte | Actual update interval in seconds > + * 0xff | 65.5 > + * 0xf7 | 63.46 > + * 0x7f | 32.74 > + * 0x3f | 16.36 > + * 0x1f | 8.17 > + * 0x0f | 4.07 > + * 0x07 | 2.02 > + * 0x03 | 1.00 > + * 0x02 | 0.744 > + * 0x01 | 0.488 > + * 0x00 | 0.25 > + */ > +static u8 update_interval_to_control_byte(long interval) > +{ > + if (interval <= 250) > + return 0; > + > + return clamp_val(1 + DIV_ROUND_CLOSEST(interval - 488, 256), 0, 255); > +} > + > +static long control_byte_to_update_interval(u8 control_byte) > +{ > + if (control_byte == 0) > + return 250; > + > + return 488 + (control_byte - 1) * 256; > +} > + > +static int set_update_interval(struct drvdata *drvdata, long val) > +{ > + u8 control = update_interval_to_control_byte(val); > + u8 report[] = { > + OUTPUT_REPORT_ID_INIT_COMMAND, > + INIT_COMMAND_SET_UPDATE_INTERVAL, > + 0x01, > + 0xe8, > + control, > + 0x01, > + 0xe8, > + control, > + }; > + int ret; > + > + ret = send_output_report(drvdata, report, sizeof(report)); > + if (ret) > + return ret; > + > + drvdata->update_interval = control_byte_to_update_interval(control); > + return 0; > +} > + > +static int init_device(struct drvdata *drvdata, long update_interval) > +{ > + int ret; > + u8 detect_fans_report[] = { > + OUTPUT_REPORT_ID_INIT_COMMAND, > + INIT_COMMAND_DETECT_FANS, > + }; > + > + ret = send_output_report(drvdata, detect_fans_report, > + sizeof(detect_fans_report)); > + if (ret) > + return ret; > + > + return set_update_interval(drvdata, update_interval); > +} > + > +static int nzxt_smart2_hwmon_write(struct device *dev, > + enum hwmon_sensor_types type, u32 attr, > + int channel, long val) > +{ > + struct drvdata *drvdata = dev_get_drvdata(dev); > + int ret; > + > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_enable: > + return set_pwm_enable(drvdata, channel, val); > + > + case hwmon_pwm_input: > + return set_pwm(drvdata, channel, val); > + > + default: > + return -EINVAL; > + } > + > + case hwmon_chip: > + switch (attr) { > + case hwmon_chip_update_interval: > + ret = mutex_lock_interruptible(&drvdata->mutex); > + if (ret) > + return ret; > + > + ret = set_update_interval(drvdata, val); > + > + mutex_unlock(&drvdata->mutex); > + return ret; > + > + default: > + return -EINVAL; > + } > + > + default: > + return -EINVAL; > + } > +} > + > +static int nzxt_smart2_hwmon_read_string(struct device *dev, > + enum hwmon_sensor_types type, u32 attr, > + int channel, const char **str) > +{ > + switch (type) { > + case hwmon_fan: > + *str = fan_label[channel]; > + return 0; > + case hwmon_curr: > + *str = curr_label[channel]; > + return 0; > + case hwmon_in: > + *str = in_label[channel]; > + return 0; > + default: > + return -EINVAL; > + } > +} > + > +static const struct hwmon_ops nzxt_smart2_hwmon_ops = { > + .is_visible = nzxt_smart2_hwmon_is_visible, > + .read = nzxt_smart2_hwmon_read, > + .read_string = nzxt_smart2_hwmon_read_string, > + .write = nzxt_smart2_hwmon_write, > +}; > + > +static const struct hwmon_channel_info *nzxt_smart2_channel_info[] = { > + HWMON_CHANNEL_INFO(fan, 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_MODE | HWMON_PWM_ENABLE, > + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, > + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE), > + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, > + HWMON_I_INPUT | HWMON_I_LABEL, > + HWMON_I_INPUT | HWMON_I_LABEL), > + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, > + HWMON_C_INPUT | HWMON_C_LABEL, > + HWMON_C_INPUT | HWMON_C_LABEL), > + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), > + NULL > +}; > + > +static const struct hwmon_chip_info nzxt_smart2_chip_info = { > + .ops = &nzxt_smart2_hwmon_ops, > + .info = nzxt_smart2_channel_info, > +}; > + > +static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, > + struct hid_report *report, u8 *data, int size) > +{ > + struct drvdata *drvdata = hid_get_drvdata(hdev); > + u8 report_id = *data; > + > + switch (report_id) { > + case INPUT_REPORT_ID_FAN_CONFIG: > + handle_fan_config_report(drvdata, data, size); > + break; > + > + case INPUT_REPORT_ID_FAN_STATUS: > + handle_fan_status_report(drvdata, data, size); > + break; > + } > + > + return 0; > +} > + > +static int nzxt_smart2_hid_reset_resume(struct hid_device *hdev) > +{ > + struct drvdata *drvdata = hid_get_drvdata(hdev); > + > + /* > + * Userspace is still frozen (so no concurrent sysfs attribute access > + * is possible), but raw_event can already be called concurrently. > + */ > + spin_lock_bh(&drvdata->wq.lock); > + drvdata->fan_config_received = false; > + drvdata->pwm_status_received = false; > + drvdata->voltage_status_received = false; > + spin_unlock_bh(&drvdata->wq.lock); > + > + return init_device(drvdata, drvdata->update_interval); > +} > + > +static int nzxt_smart2_hid_probe(struct hid_device *hdev, > + const struct hid_device_id *id) > +{ > + struct drvdata *drvdata; > + int ret; > + > + drvdata = devm_kzalloc(&hdev->dev, sizeof(struct drvdata), GFP_KERNEL); > + if (!drvdata) > + return -ENOMEM; > + > + drvdata->hid = hdev; > + hid_set_drvdata(hdev, drvdata); > + > + init_waitqueue_head(&drvdata->wq); > + > + mutex_init(&drvdata->mutex); > + devm_add_action(&hdev->dev, (void (*)(void *))mutex_destroy, > + &drvdata->mutex); > + > + 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 out_hw_stop; > + > + hid_device_io_start(hdev); > + > + init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); > + > + drvdata->hwmon = > + hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, > + &nzxt_smart2_chip_info, NULL); > + if (IS_ERR(drvdata->hwmon)) { > + ret = PTR_ERR(drvdata->hwmon); > + goto out_hw_close; > + } > + > + return 0; > + > +out_hw_close: > + hid_hw_close(hdev); > + > +out_hw_stop: > + hid_hw_stop(hdev); > + return ret; > +} > + > +static void nzxt_smart2_hid_remove(struct hid_device *hdev) > +{ > + struct drvdata *drvdata = hid_get_drvdata(hdev); > + > + hwmon_device_unregister(drvdata->hwmon); > + > + hid_hw_close(hdev); > + hid_hw_stop(hdev); > +} > + > +static const struct hid_device_id nzxt_smart2_hid_id_table[] = { > + { HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */ > + { HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */ > + { HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */ > + { HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */ > + { HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */ > + {}, > +}; > + > +static struct hid_driver nzxt_smart2_hid_driver = { > + .name = "nzxt-smart2", > + .id_table = nzxt_smart2_hid_id_table, > + .probe = nzxt_smart2_hid_probe, > + .remove = nzxt_smart2_hid_remove, > + .raw_event = nzxt_smart2_hid_raw_event, > +#ifdef CONFIG_PM > + .reset_resume = nzxt_smart2_hid_reset_resume, > +#endif > +}; > + > +static int __init nzxt_smart2_init(void) > +{ > + return hid_register_driver(&nzxt_smart2_hid_driver); > +} > + > +static void __exit nzxt_smart2_exit(void) > +{ > + hid_unregister_driver(&nzxt_smart2_hid_driver); > +} > + > +MODULE_DEVICE_TABLE(hid, nzxt_smart2_hid_id_table); > +MODULE_AUTHOR("Aleksandr Mezin <mezin.alexander@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Driver for NZXT RGB & Fan Controller/Smart Device V2"); > +MODULE_LICENSE("GPL"); > + > +/* > + * With module_init()/module_hid_driver() and the driver built into the kernel: > + * > + * Driver 'nzxt_smart2' was unable to register with bus_type 'hid' because the > + * bus was not initialized. > + */ > +late_initcall(nzxt_smart2_init); > +module_exit(nzxt_smart2_exit);