Almost none of your commentary is about the actual change. -- Jeremy Soller System76 Principal Engineer jeremy@xxxxxxxxxxxx On Thu, Aug 24, 2023, at 9:10 PM, Jeremy Soller wrote: > I give up. > > -- > Jeremy Soller > System76 > Principal Engineer > jeremy@xxxxxxxxxxxx > > On Thu, Aug 24, 2023, at 8:56 PM, Guenter Roeck wrote: >> On Thu, Aug 24, 2023 at 02:39:02PM -0600, Jeremy Soller wrote: >>> The System76 Thelio Io is a fan and power button LED controller. This >>> driver fully supports the System76 Thelio Io, by exposing fan controls >>> and sending the system suspend/resume state. >>> >> >> You might want to consider reading >> Documentation/process/submitting-patches.rst. It would tell you that >> >> ... However, for a multi-patch series, it is generally >> best to avoid using In-Reply-To: to link to older versions of the >> series. This way multiple versions of the patch don't become an >> unmanageable forest of references in email clients. If a link is >> helpful, you can use the https://lore.kernel.org/ redirector (e.g., in >> the cover email text) to link to an earlier version of the patch series. >> >> It might possibly make sense to also add a note saying something like >> "don't send X revisions of your patch in a row". >> >>> Signed-off-by: Jeremy Soller <jeremy@xxxxxxxxxxxx> >>> --- >> >> Missing change log. You are flooding me with patch revisions. Do you >> really expect me to look into each of those to figure out what you >> changed ? >> >> Random feedback below. Since the is no explanation for what has >> changed and what hasn't and why, the feedback is necessarily >> incomplete. >> >>> Documentation/hwmon/system76-thelio-io.rst | 31 ++ >> >> Missing reference in Documentation/hwmon/index.rst >> >>> MAINTAINERS | 7 + >>> drivers/hwmon/Kconfig | 10 + >>> drivers/hwmon/Makefile | 1 + >>> drivers/hwmon/system76-thelio-io.c | 424 +++++++++++++++++++++ >>> 5 files changed, 473 insertions(+) >>> create mode 100644 Documentation/hwmon/system76-thelio-io.rst >>> create mode 100644 drivers/hwmon/system76-thelio-io.c >>> >>> diff --git a/Documentation/hwmon/system76-thelio-io.rst b/Documentation/hwmon/system76-thelio-io.rst >>> new file mode 100644 >>> index 000000000000..7ca34bb47bbb >>> --- /dev/null >>> +++ b/Documentation/hwmon/system76-thelio-io.rst >>> @@ -0,0 +1,31 @@ >>> +.. SPDX-License-Identifier: GPL-2.0-or-later >>> + >>> +Kernel driver system76-thelio-io >>> +========================== >>> + >>> +Supported devices: >>> + >>> + * System76 Thelio Io (thelio_io_2) >>> + >>> +Author: Jeremy Soller >>> + >>> +Description >>> +----------- >>> + >>> +This driver implements the sysfs interface for the System76 Thelio Io. >>> +The System76 Thelio Io is a USB device with 4 fan connectors and a >>> +power button LED. >>> + >>> +Usage Notes >>> +----------- >>> + >>> +Since it is a USB device, hotswapping is possible. The device is autodetected. >>> + >>> +Sysfs entries >>> +------------- >>> + >>> +======================= ===================================================================== >>> +fan[1-4]_input Connected fan rpm. >>> +fan[1-4]_label Shows fan connector name. >>> +pwm[1-4] Sets the fan speed. Values from 0-255. >>> +======================= ===================================================================== >>> diff --git a/MAINTAINERS b/MAINTAINERS >>> index 48abe1a281f2..f4e8f7bdd1f5 100644 >>> --- a/MAINTAINERS >>> +++ b/MAINTAINERS >>> @@ -20738,6 +20738,13 @@ L: platform-driver-x86@xxxxxxxxxxxxxxx >>> S: Maintained >>> F: drivers/platform/x86/system76_acpi.c >>> >>> +SYSTEM76 THELIO IO DRIVER >>> +M: Jeremy Soller <jeremy@xxxxxxxxxxxx> >>> +M: System76 <info@xxxxxxxxxxxx> >>> +L: linux-hwmon@xxxxxxxxxxxxxxx >>> +S: Maintained >>> +F: drivers/hwmon/system76-thelio-io.c >>> + >>> SYSV FILESYSTEM >>> S: Orphan >>> F: Documentation/filesystems/sysv-fs.rst >>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig >>> index 307477b8a371..fdcf0baa2669 100644 >>> --- a/drivers/hwmon/Kconfig >>> +++ b/drivers/hwmon/Kconfig >>> @@ -1965,6 +1965,16 @@ config SENSORS_SMM665 >>> This driver can also be built as a module. If so, the module will >>> be called smm665. >>> >>> +config SENSORS_SYSTEM76_THELIO_IO >>> + tristate "System76 Thelio Io controller" >>> + depends on HID >>> + help >>> + If you say yes here you get support for the System76 Thelio Io >>> + controller. >>> + >>> + This driver can also be built as a module. If so, the module >>> + will be called system76-thelio-io. >>> + >>> config SENSORS_ADC128D818 >>> tristate "Texas Instruments ADC128D818" >>> depends on I2C >>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile >>> index 3f4b0fda0998..7cfdabb4e66f 100644 >>> --- a/drivers/hwmon/Makefile >>> +++ b/drivers/hwmon/Makefile >>> @@ -199,6 +199,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o >>> obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o >>> obj-$(CONFIG_SENSORS_STTS751) += stts751.o >>> obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o >>> +obj-$(CONFIG_SENSORS_SYSTEM76_THELIO_IO) += system76-thelio-io.o >>> obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o >>> obj-$(CONFIG_SENSORS_TC74) += tc74.o >>> obj-$(CONFIG_SENSORS_THMC50) += thmc50.o >>> diff --git a/drivers/hwmon/system76-thelio-io.c b/drivers/hwmon/system76-thelio-io.c >>> new file mode 100644 >>> index 000000000000..4d9c2229cd3d >>> --- /dev/null >>> +++ b/drivers/hwmon/system76-thelio-io.c >>> @@ -0,0 +1,424 @@ >>> +// SPDX-License-Identifier: GPL-2.0-or-later >>> +/* >>> + * >>> + * system76-thelio-io.c - Linux driver for System76 Thelio Io >>> + * Copyright (C) 2023 System76 >>> + * >>> + * Based on: >>> + * corsair-cpro.c - Linux driver for Corsair Commander Pro >>> + * Copyright (C) 2020 Marius Zachmann <mail@xxxxxxxxxxxxxxxxx> >>> + * >>> + * This driver uses hid reports to communicate with the device to allow hidraw userspace drivers >>> + * still being used. The device does not use report ids. When using hidraw and this driver >>> + * simultaniously, reports could be switched. >>> + */ >>> + >>> +#include <linux/bitops.h> >>> +#include <linux/completion.h> >>> +#include <linux/hid.h> >>> +#include <linux/hwmon.h> >>> +#include <linux/kernel.h> >>> +#include <linux/module.h> >>> +#include <linux/mutex.h> >>> +#include <linux/slab.h> >>> +#include <linux/suspend.h> >>> +#include <linux/types.h> >>> + >>> +#define BUFFER_SIZE 32 >>> +#define REQ_TIMEOUT 300 >> >> Nit: It is customary to either add a comment describing the unit >> (here: milli-seconds) or to indicate it in the define (e.g. >> REQ_TIMEOUT_MS). >>> + >>> +#define HID_CMD 0 >>> +#define HID_RES 1 >>> +#define HID_DATA 2 >>> + >>> +#define CMD_FAN_GET 7 >>> +#define CMD_FAN_SET 8 >>> +#define CMD_LED_SET_MODE 16 >>> +#define CMD_FAN_TACH 22 >>> + >>> +struct thelio_io_device { >>> + struct hid_device *hdev; >>> + struct device *hwmon_dev; >>> +#ifdef CONFIG_PM_SLEEP >>> + struct notifier_block pm_notifier; >>> +#endif >>> + struct completion wait_input_report; >>> + struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */ >>> + u8 *buffer; >>> +}; >>> + >>> +/* converts response error in buffer to errno */ >>> +static int thelio_io_get_errno(struct thelio_io_device *thelio_io) >>> +{ >>> + switch (thelio_io->buffer[HID_RES]) { >>> + case 0x00: /* success */ >>> + return 0; >>> + default: >>> + return -EIO; >>> + } >>> +} >>> + >>> +/* send command, check for error in response, response in thelio_io->buffer */ >>> +static int send_usb_cmd(struct thelio_io_device *thelio_io, u8 command, >>> + u8 byte1, u8 byte2, u8 byte3) >>> +{ >>> + int ret; >>> + >>> + memset(thelio_io->buffer, 0x00, BUFFER_SIZE); >>> + thelio_io->buffer[HID_CMD] = command; >>> + thelio_io->buffer[HID_DATA] = byte1; >>> + thelio_io->buffer[HID_DATA + 1] = byte2; >>> + thelio_io->buffer[HID_DATA + 2] = byte3; >>> + >>> + reinit_completion(&thelio_io->wait_input_report); >>> + >>> + ret = hid_hw_output_report(thelio_io->hdev, thelio_io->buffer, BUFFER_SIZE); >>> + if (ret < 0) >>> + return ret; >>> + >>> + if (!wait_for_completion_timeout(&thelio_io->wait_input_report, >>> + msecs_to_jiffies(REQ_TIMEOUT))) >>> + return -ETIMEDOUT; >>> + >>> + return thelio_io_get_errno(thelio_io); >>> +} >>> + >>> +static int thelio_io_raw_event(struct hid_device *hdev, struct hid_report *report, >>> + u8 *data, int size) >>> +{ >>> + struct thelio_io_device *thelio_io = hid_get_drvdata(hdev); >>> + >>> + /* only copy buffer when requested */ >>> + if (completion_done(&thelio_io->wait_input_report)) >>> + return 0; >>> + >>> + memcpy(thelio_io->buffer, data, min(BUFFER_SIZE, size)); >>> + complete(&thelio_io->wait_input_report); >>> + >>> + return 0; >>> +} >>> + >>> +/* requests and returns single data values depending on channel */ >>> +static int get_data(struct thelio_io_device *thelio_io, int command, int channel, >>> + bool two_byte_data) >>> +{ >>> + int ret; >>> + >>> + mutex_lock(&thelio_io->mutex); >>> + >>> + ret = send_usb_cmd(thelio_io, command, channel, 0, 0); >>> + if (ret) >>> + goto out_unlock; >>> + >>> + ret = thelio_io->buffer[HID_DATA + 1]; >>> + if (two_byte_data) >>> + ret |= thelio_io->buffer[HID_DATA + 2] << 8; >>> + >>> +out_unlock: >>> + mutex_unlock(&thelio_io->mutex); >>> + return ret; >>> +} >>> + >>> +static int set_pwm(struct thelio_io_device *thelio_io, int channel, long val) >>> +{ >>> + int ret; >>> + >>> + if (val < 0 || val > 255) >>> + return -EINVAL; >>> + >>> + mutex_lock(&thelio_io->mutex); >>> + >>> + ret = send_usb_cmd(thelio_io, CMD_FAN_SET, channel, val, 0); >>> + >>> + mutex_unlock(&thelio_io->mutex); >>> + return ret; >>> +} >>> + >>> +static int thelio_io_read_string(struct device *dev, enum hwmon_sensor_types type, >>> + u32 attr, int channel, const char **str) >>> +{ >>> + switch (type) { >>> + case hwmon_fan: >>> + switch (attr) { >>> + case hwmon_fan_label: >>> + switch (channel) { >>> + case 0: >>> + *str = "CPU Fan"; >>> + return 0; >>> + case 1: >>> + *str = "Intake Fan"; >>> + return 0; >>> + case 2: >>> + *str = "GPU Fan"; >>> + return 0; >>> + case 3: >>> + *str = "Aux Fan"; >>> + return 0; >>> + default: >>> + break; >>> + } >>> + default: >>> + break; >>> + } >>> + break; >>> + default: >>> + break; >>> + } >>> + >>> + return -EOPNOTSUPP; >>> +} >>> + >>> +static int thelio_io_read(struct device *dev, enum hwmon_sensor_types type, >>> + u32 attr, int channel, long *val) >>> +{ >>> + struct thelio_io_device *thelio_io = dev_get_drvdata(dev); >>> + int ret; >>> + >>> + switch (type) { >>> + case hwmon_fan: >>> + switch (attr) { >>> + case hwmon_fan_input: >>> + ret = get_data(thelio_io, CMD_FAN_TACH, channel, true); >>> + if (ret < 0) >>> + return ret; >>> + *val = ret; >>> + return 0; >>> + default: >>> + break; >>> + } >>> + break; >>> + case hwmon_pwm: >>> + switch (attr) { >>> + case hwmon_pwm_input: >>> + ret = get_data(thelio_io, CMD_FAN_GET, channel, false); >>> + if (ret < 0) >>> + return ret; >>> + *val = ret; >>> + return 0; >>> + default: >>> + break; >>> + } >>> + break; >>> + default: >>> + break; >>> + } >>> + >>> + return -EOPNOTSUPP; >>> +}; >>> + >>> +static int thelio_io_write(struct device *dev, enum hwmon_sensor_types type, >>> + u32 attr, int channel, long val) >>> +{ >>> + struct thelio_io_device *thelio_io = dev_get_drvdata(dev); >>> + >>> + switch (type) { >>> + case hwmon_pwm: >>> + switch (attr) { >>> + case hwmon_pwm_input: >>> + return set_pwm(thelio_io, channel, val); >>> + default: >>> + break; >>> + } >>> + break; >>> + default: >>> + break; >>> + } >>> + >>> + return -EOPNOTSUPP; >>> +}; >>> + >>> +static umode_t thelio_io_is_visible(const void *data, enum hwmon_sensor_types type, >>> + u32 attr, int channel) >>> +{ >>> + switch (type) { >>> + case hwmon_fan: >>> + switch (attr) { >>> + case hwmon_fan_input: >>> + return 0444; >>> + case hwmon_fan_label: >>> + return 0444; >>> + default: >>> + break; >>> + } >>> + break; >>> + case hwmon_pwm: >>> + switch (attr) { >>> + case hwmon_pwm_input: >>> + return 0644; >>> + default: >>> + break; >>> + } >>> + break; >>> + default: >>> + break; >>> + } >>> + >>> + return 0; >>> +}; >>> + >>> +static const struct hwmon_ops thelio_io_hwmon_ops = { >>> + .is_visible = thelio_io_is_visible, >>> + .read = thelio_io_read, >>> + .read_string = thelio_io_read_string, >>> + .write = thelio_io_write, >>> +}; >>> + >>> +static const struct hwmon_channel_info *thelio_io_info[] = { >>> + HWMON_CHANNEL_INFO(chip, >>> + HWMON_C_REGISTER_TZ), >>> + 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_CHANNEL_INFO(pwm, >>> + HWMON_PWM_INPUT, >>> + HWMON_PWM_INPUT, >>> + HWMON_PWM_INPUT, >>> + HWMON_PWM_INPUT >>> + ), >>> + NULL >>> +}; >>> + >>> +static const struct hwmon_chip_info thelio_io_chip_info = { >>> + .ops = &thelio_io_hwmon_ops, >>> + .info = thelio_io_info, >>> +}; >>> + >>> +#ifdef CONFIG_PM_SLEEP >> >> You suggested in one of your replies that is would by my responsibility >> to prove that standard PM calls are insufficient. That is not the case. >> >>> +static int thelio_io_pm(struct notifier_block *nb, unsigned long action, void *data) >>> +{ >>> + struct thelio_io_device *thelio_io = container_of(nb, struct thelio_io_device, pm_notifier); >>> + >>> + switch (action) { >>> + case PM_HIBERNATION_PREPARE: >>> + case PM_SUSPEND_PREPARE: >>> + mutex_lock(&thelio_io->mutex); >>> + send_usb_cmd(thelio_io, CMD_LED_SET_MODE, 0, 1, 0); >>> + mutex_unlock(&thelio_io->mutex); >>> + break; >>> + >>> + case PM_POST_HIBERNATION: >>> + case PM_POST_SUSPEND: >>> + mutex_lock(&thelio_io->mutex); >>> + send_usb_cmd(thelio_io, CMD_LED_SET_MODE, 0, 0, 0); >>> + mutex_unlock(&thelio_io->mutex); >>> + break; >>> + >>> + case PM_POST_RESTORE: >>> + case PM_RESTORE_PREPARE: >>> + default: >>> + break; >>> + } >>> + >>> + return NOTIFY_DONE; >>> +} >>> +#endif >>> + >>> +static int thelio_io_probe(struct hid_device *hdev, const struct hid_device_id *id) >>> +{ >>> + struct thelio_io_device *thelio_io; >>> + int ret; >>> + >>> + thelio_io = devm_kzalloc(&hdev->dev, sizeof(*thelio_io), GFP_KERNEL); >>> + if (!thelio_io) >>> + return -ENOMEM; >>> + >>> + thelio_io->buffer = devm_kmalloc(&hdev->dev, BUFFER_SIZE, GFP_KERNEL); >>> + if (!thelio_io->buffer) >>> + return -ENOMEM; >>> + >>> + ret = hid_parse(hdev); >>> + if (ret) >>> + return ret; >>> + >>> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); >>> + if (ret) >>> + return ret; >>> + >>> + ret = hid_hw_open(hdev); >>> + if (ret) >>> + goto out_hw_stop; >>> + >>> + thelio_io->hdev = hdev; >>> + hid_set_drvdata(hdev, thelio_io); >>> + mutex_init(&thelio_io->mutex); >>> + init_completion(&thelio_io->wait_input_report); >>> + >>> + hid_device_io_start(hdev); >>> + >> >> Just in case the below is _really_ necessary, which seems unlikely to me, >> why call hid_device_io_start() even if no hwmon device is going to be >> registered ? Why start IO on a device that isn't doing anything ? >> >>> + if (hdev->maxcollection == 1 && hdev->collection[0].usage == 0xFF600061) { >>> + thelio_io->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, >>> + "system76_thelio_io", >>> + thelio_io, >>> + &thelio_io_chip_info, >>> + 0); >>> + if (IS_ERR(thelio_io->hwmon_dev)) { >>> + ret = PTR_ERR(thelio_io->hwmon_dev); >>> + goto out_hw_close; >>> + } >>> + >>> + #ifdef CONFIG_PM_SLEEP >>> + thelio_io->pm_notifier.notifier_call = thelio_io_pm; >>> + register_pm_notifier(&thelio_io->pm_notifier); >>> + #endif >>> + } >> >> Ok, whatever your explanation for this is: I am not going to accept it, >> period. It doesn't make sense to instantiate a driver that doesn't do >> anything. If this would for whatever reason _really_ _really_ _really_ >> be necessary, I'd expect a lengthy comment describing why this is the >> case and why it would be necessary to keep possibly multiple instances >> of this driver around that don't do anything. >> >> And, no, I am not going to dig through all your replies trying >> to figure out your rationale for doing this. Note that, given that >> there is not a single driver in the kernel doing anything similar, >> you might have a hard time convincing me that this makes sense >> and is necessary (both the check and successfully instantiating the >> driver without registering a hwmon device). >> >>> + >>> + return 0; >>> + >>> +out_hw_close: >>> + hid_hw_close(hdev); >>> +out_hw_stop: >>> + hid_hw_stop(hdev); >>> + return ret; >>> +} >>> + >>> +static void thelio_io_remove(struct hid_device *hdev) >>> +{ >>> + struct thelio_io_device *thelio_io = hid_get_drvdata(hdev); >>> + >>> + if (thelio_io->hwmon_dev) { >>> + hwmon_device_unregister(thelio_io->hwmon_dev); >>> + >>> + #ifdef CONFIG_PM_SLEEP >>> + unregister_pm_notifier(&thelio_io->pm_notifier); >>> + #endif >>> + } >>> + >>> + hid_hw_close(hdev); >>> + hid_hw_stop(hdev); >>> +} >>> + >>> +static const struct hid_device_id thelio_io_devices[] = { >>> + { HID_USB_DEVICE(0x3384, 0x000B) }, /* thelio_io_2 */ >>> + { } >>> +}; >>> + >>> +static struct hid_driver thelio_io_driver = { >>> + .name = "system76-thelio-io", >>> + .id_table = thelio_io_devices, >>> + .probe = thelio_io_probe, >>> + .remove = thelio_io_remove, >>> + .raw_event = thelio_io_raw_event, >>> +}; >>> + >>> +MODULE_DEVICE_TABLE(hid, thelio_io_devices); >>> +MODULE_LICENSE("GPL"); >>> + >>> +static int __init thelio_io_init(void) >>> +{ >>> + return hid_register_driver(&thelio_io_driver); >>> +} >>> + >>> +static void __exit thelio_io_exit(void) >>> +{ >>> + hid_unregister_driver(&thelio_io_driver); >>> +} >>> + >>> +/* >>> + * When compiling this driver as built-in, hwmon initcalls will get called before the >>> + * hid driver and this driver would fail to register. late_initcall solves this. >>> + */ >>> +late_initcall(thelio_io_init); >>> +module_exit(thelio_io_exit); >>> -- >>> 2.34.1