Re: [PATCH v2] platform/x86/tuxedo: Implement TUXEDO TUXI ACPI TFAN via hwmon
From: Ilpo Järvinen
Date: Thu Mar 13 2025 - 09:47:34 EST
On Thu, 6 Mar 2025, Werner Sembach wrote:
> The TUXEDO Sirius 16 Gen1 & Gen2 have the custom TUXEDO Interface (TUXI)
> ACPI interface which currently consists of the TFAN device. This has ACPI
> functions to control the built in fans and monitor fan speeds and CPU and
> GPU temprature.
temperature
>
> This driver implements this TFAN device via the hwmon subsystem with an
> added temprature check that ensure a minimum fanspeed at certain
temperature
> temperatures. This allows userspace controlled, but hardware safe, custom
> fan curves.
>
> Signed-off-by: Werner Sembach <wse@xxxxxxxxxxxxxxxxxxx>
> ---
> MAINTAINERS | 6 +
> drivers/platform/x86/Kconfig | 2 +
> drivers/platform/x86/Makefile | 3 +
> drivers/platform/x86/tuxedo/Kbuild | 6 +
> drivers/platform/x86/tuxedo/Kconfig | 6 +
> drivers/platform/x86/tuxedo/nbxx/Kbuild | 7 +
> drivers/platform/x86/tuxedo/nbxx/Kconfig | 13 +
> drivers/platform/x86/tuxedo/nbxx/acpi_tuxi.c | 578 +++++++++++++++++++
> 8 files changed, 621 insertions(+)
> create mode 100644 drivers/platform/x86/tuxedo/Kbuild
> create mode 100644 drivers/platform/x86/tuxedo/Kconfig
> create mode 100644 drivers/platform/x86/tuxedo/nbxx/Kbuild
> create mode 100644 drivers/platform/x86/tuxedo/nbxx/Kconfig
> create mode 100644 drivers/platform/x86/tuxedo/nbxx/acpi_tuxi.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8e0736dc2ee0e..7139c32e96dc7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -24190,6 +24190,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
> F: tools/power/x86/turbostat/
> F: tools/testing/selftests/turbostat/
>
> +TUXEDO DRIVERS
> +M: Werner Sembach <wse@xxxxxxxxxxxxxxxxxxx>
> +L: platform-driver-x86@xxxxxxxxxxxxxxx
> +S: Supported
> +F: drivers/platform/x86/tuxedo/
> +
> TW5864 VIDEO4LINUX DRIVER
> M: Bluecherry Maintainers <maintainers@xxxxxxxxxxxxxxxxx>
> M: Andrey Utkin <andrey.utkin@xxxxxxxxxxxxxxxxxxx>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 0258dd879d64b..58b258cde4fdb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -1186,6 +1186,8 @@ config SEL3350_PLATFORM
> To compile this driver as a module, choose M here: the module
> will be called sel3350-platform.
>
> +source "drivers/platform/x86/tuxedo/Kconfig"
> +
> endif # X86_PLATFORM_DEVICES
>
> config P2SB
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e1b1429470674..1562dcd7ad9a5 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -153,3 +153,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o
>
> # SEL
> obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o
> +
> +# TUXEDO
> +obj-y += tuxedo/
> diff --git a/drivers/platform/x86/tuxedo/Kbuild b/drivers/platform/x86/tuxedo/Kbuild
> new file mode 100644
> index 0000000000000..0de6c7871db95
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/Kbuild
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +obj-y += nbxx/
> diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig
> new file mode 100644
> index 0000000000000..34aa2e89f00ba
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/Kconfig
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +source "drivers/platform/x86/tuxedo/nbxx/Kconfig"
> diff --git a/drivers/platform/x86/tuxedo/nbxx/Kbuild b/drivers/platform/x86/tuxedo/nbxx/Kbuild
> new file mode 100644
> index 0000000000000..10ddef3564d84
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/nbxx/Kbuild
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +tuxedo_nbxx_acpi_tuxi-y := acpi_tuxi.o
> +obj-$(CONFIG_TUXEDO_NBXX_ACPI_TUXI) += tuxedo_nbxx_acpi_tuxi.o
> diff --git a/drivers/platform/x86/tuxedo/nbxx/Kconfig b/drivers/platform/x86/tuxedo/nbxx/Kconfig
> new file mode 100644
> index 0000000000000..827c65c410fb2
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/nbxx/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +config TUXEDO_NBXX_ACPI_TUXI
> + tristate "TUXEDO NBxx ACPI TUXI Platform Driver"
> + help
> + This driver implements the ACPI TUXI device found on some TUXEDO
> + Notebooks. Currently this consists only of the TFAN subdevice which is
> + implemented via hwmon.
> +
> + When compiled as a module it will be called tuxedo_nbxx_acpi_tuxi
> diff --git a/drivers/platform/x86/tuxedo/nbxx/acpi_tuxi.c b/drivers/platform/x86/tuxedo/nbxx/acpi_tuxi.c
> new file mode 100644
> index 0000000000000..f42311f119956
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/nbxx/acpi_tuxi.c
> @@ -0,0 +1,578 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2024-2025 Werner Sembach wse@xxxxxxxxxxxxxxxxxxx
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/hwmon.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/units.h>
> +#include <linux/workqueue.h>
> +
> +#define TUXI_SAFEGUARD_PERIOD 1000 // 1s
> +#define TUXI_PWM_FAN_ON_MIN_SPEED 0x40 // ~25%
> +#define TUXI_TEMP_LEVEL_HYSTERESIS 1500 // 1.5°C
> +#define TUXI_FW_TEMP_OFFSET 2730 // Kelvin to Celsius
> +#define TUXI_MAX_FAN_COUNT 16 /* If this is increased, new lines must
> + * be added to hwmcinfo below.
> + */
Please use static_assert() to actually enforce what the comment says.
> +
> +static const struct acpi_device_id acpi_device_ids[] = {
> + {"TUXI0000", 0},
> + {"", 0}
> +};
> +MODULE_DEVICE_TABLE(acpi, acpi_device_ids);
> +
> +struct tuxi_driver_data_t {
> + acpi_handle tfan_handle;
> + struct device *hwmdev;
> +};
> +
> +struct tuxi_hwmon_driver_data_t {
> + struct delayed_work work;
> + struct device *hwmdev;
> + u8 fan_count;
> + const char *fan_types[TUXI_MAX_FAN_COUNT];
> + u8 temp_level[TUXI_MAX_FAN_COUNT];
> + u8 curr_speed[TUXI_MAX_FAN_COUNT];
> + u8 want_speed[TUXI_MAX_FAN_COUNT];
> + u8 pwm_enabled;
> +};
> +
> +struct tuxi_temp_high_config_t {
> + long temp;
> + u8 min_speed;
> +};
> +
> +/* Speed values in this table must be >= TUXI_PWM_FAN_ON_MIN_SPEED to avoid
> + * undefined behaviour.
> + */
> +static const struct tuxi_temp_high_config_t temp_levels[] = {
> + { 80000, 0x4d }, // ~30%
> + { 90000, 0x66 }, // ~40%
> + { 100000, 0xff }, // 100%
> + { }
> +};
> +
> +/*
> + * Set fan speed target
> + *
> + * Set a specific fan speed (needs manual mode)
> + *
> + * Arg0: Fan index
> + * Arg1: Fan speed as a fraction of maximum speed (0-255)
> + */
> +#define TUXI_TFAN_METHOD_SET_FAN_SPEED "SSPD"
> +
> +/*
> + * Get fan speed target
> + *
> + * Arg0: Fan index
> + * Returns: Current fan speed target a fraction of maximum speed (0-255)
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_SPEED "GSPD"
> +
> +/*
> + * Get fans count
> + *
> + * Returns: Number of individually controllable fans
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_COUNT "GCNT"
> +
> +/*
> + * Set fans mode
> + *
> + * Arg0: 0 = auto, 1 = manual
> + */
> +#define TUXI_TFAN_METHOD_SET_FAN_MODE "SMOD"
> +
> +/*
> + * Get fans mode
> + *
> + * Returns: 0 = auto, 1 = manual
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_MODE "GMOD"
> +
> +/*
> + * Get fan type/what the fan is pointed at
> + *
> + * Arg0: Fan index
> + * Returns: 0 = general, 1 = CPU, 2 = GPU
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_TYPE "GTYP"
> +
> +static const char * const tuxi_fan_type_labels[] = {
> + "general",
> + "cpu",
> + "gpu"
> +};
> +
> +/*
> + * Get fan temperature/temperature of what the fan is pointed at
> + *
> + * Arg0: Fan index
> + * Returns: Temperature sensor value in 10ths of degrees kelvin
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_TEMPERATURE "GTMP"
> +
> +/*
> + * Get actual fan speed in RPM
> + *
> + * Arg0: Fan index
> + * Returns: Speed sensor value in revolutions per minute
> + */
> +#define TUXI_TFAN_METHOD_GET_FAN_RPM "GRPM"
> +
> +static int tuxi_tfan_method(struct acpi_device *device, acpi_string method,
> + unsigned long long *params, u32 pcount,
> + unsigned long long *retval)
> +{
> + struct tuxi_driver_data_t *driver_data = dev_get_drvdata(&device->dev);
> + acpi_handle handle = driver_data->tfan_handle;
> + union acpi_object *obj __free(kfree) = NULL;
Please add linux/cleanup.h include.
> + struct acpi_object_list arguments;
> + unsigned long long data;
> + acpi_status status;
> + unsigned int i;
> +
> + if (pcount > 0) {
> + obj = kcalloc(pcount, sizeof(*arguments.pointer), GFP_KERNEL);
> +
> + arguments.count = pcount;
> + arguments.pointer = obj;
> + for (i = 0; i < pcount; ++i) {
> + arguments.pointer[i].type = ACPI_TYPE_INTEGER;
> + arguments.pointer[i].integer.value = params[i];
> + }
> + }
> + status = acpi_evaluate_integer(handle, method,
> + pcount ? &arguments : NULL, &data);
> + if (ACPI_FAILURE(status))
> + return_ACPI_STATUS(status);
> +
> + if (retval)
> + *retval = data;
> +
> + return 0;
> +}
> +
> +static umode_t hwm_is_visible(const void *data, enum hwmon_sensor_types type,
> + u32 __always_unused attr, int channel)
> +{
> + struct tuxi_hwmon_driver_data_t const *driver_data = data;
> +
> + if (channel >= driver_data->fan_count)
> + return 0;
> +
> + switch (type) {
> + case hwmon_fan:
> + return 0444;
> + case hwmon_pwm:
> + return 0644;
> + case hwmon_temp:
> + return 0444;
> + default:
> + break;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int hwm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> + int channel, long *val)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = dev_get_drvdata(dev);
> + struct acpi_device *pdev = to_acpi_device(dev->parent);
> + unsigned long long params[2], retval;
> + int ret;
> +
> + switch (type) {
> + case hwmon_fan:
> + params[0] = channel;
> + ret = tuxi_tfan_method(pdev,
> + TUXI_TFAN_METHOD_GET_FAN_RPM,
These fit to same line.
> + params, 1, &retval);
> + *val = retval > S32_MAX ? S32_MAX : retval;
> + return ret;
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_input:
> + if (driver_data->pwm_enabled) {
> + *val = driver_data->curr_speed[channel];
> + return 0;
> + }
> + params[0] = channel;
> + ret = tuxi_tfan_method(pdev,
> + TUXI_TFAN_METHOD_GET_FAN_SPEED,
> + params, 1, &retval);
> + *val = retval > S32_MAX ? S32_MAX : retval;
> + return ret;
> + case hwmon_pwm_enable:
> + *val = driver_data->pwm_enabled;
> + return ret;
> + }
> + break;
> + case hwmon_temp:
> + params[0] = channel;
> + ret = tuxi_tfan_method(pdev,
> + TUXI_TFAN_METHOD_GET_FAN_TEMPERATURE,
> + params, 1, &retval);
> + *val = retval > S32_MAX / 100 ?
Add linux/limits.h include.
> + S32_MAX : (retval - TUXI_FW_TEMP_OFFSET) * 100;
Is the math wrong, as you do retval - TUXI_FW_TEMP_OFFSET before
multiplying? Shouldn't it be like this:
retval -= TUXI_FW_TEMP_OFFSET;
*val = min(retval * 100, (unsigned long long)S32_MAX);
+ add include for min().
> + return ret;
> + default:
> + break;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int hwm_read_string(struct device *dev,
> + enum hwmon_sensor_types __always_unused type,
> + u32 __always_unused attr, int channel,
> + const char **str)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = dev_get_drvdata(dev);
> +
> + *str = driver_data->fan_types[channel];
> +
> + return 0;
> +}
> +
> +static int write_speed(struct device *dev, int channel, u8 val, bool force)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = dev_get_drvdata(dev);
> + struct acpi_device *pdev = to_acpi_device(dev->parent);
> + unsigned long long params[2];
> + u8 temp_level;
> + int ret;
> +
> + params[0] = channel;
> +
> + /* The heatpipe across the DRMs is shared between both fans and the DRMs
In multi-line comments:
/*
* Text starts here...
What are DRMs? I guess it's neither Digital Rights Managements nor Direct
Rendering Managers :-).
> + * are the most likely to go up in smoke. So it's better to apply the
> + * minimum fan speed to all fans if either CPU or GPU is working hard.
> + */
> + temp_level = max_array(driver_data->temp_level, driver_data->fan_count);
> + if (temp_level)
> + params[1] = max_t(u8, val,
> + temp_levels[temp_level - 1].min_speed);
Both are already u8 so why you need max_t(), max() should be sufficient
when types are already right.
Include is missing.
> + else if (val < TUXI_PWM_FAN_ON_MIN_SPEED / 2)
> + params[1] = 0;
> + else if (val < TUXI_PWM_FAN_ON_MIN_SPEED)
> + params[1] = TUXI_PWM_FAN_ON_MIN_SPEED;
> + else
> + params[1] = val;
> +
> + if (force || params[1] != driver_data->curr_speed[channel])
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_SET_FAN_SPEED,
> + params, 2, NULL);
> + if (ret)
> + return ret;
Please indent this to the correct level.
> +
> + driver_data->curr_speed[channel] = params[1];
The use of params[1] obfuscates meaning, please use local variable with a
proper name and only assign it into params[1] for the tuxi_tfan_method call.
> +
> + return 0;
> +}
> +
> +static int hwm_write(struct device *dev,
> + enum hwmon_sensor_types __always_unused type, u32 attr,
> + int channel, long val)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = dev_get_drvdata(dev);
> + struct acpi_device *pdev = to_acpi_device(dev->parent);
> + unsigned long long params[2];
> + unsigned int i;
> + int ret;
> +
> + switch (attr) {
> + case hwmon_pwm_input:
> + if (val > U8_MAX || val < 0)
> + return -EINVAL;
> +
> + if (driver_data->pwm_enabled) {
> + driver_data->want_speed[channel] = val;
> + return write_speed(dev, channel, val, false);
> + }
> +
> + return 0;
> + case hwmon_pwm_enable:
> + params[0] = val ? 1 : 0;
Can those two literals be named with defines?
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_SET_FAN_MODE,
> + params, 1, NULL);
> + if (ret)
> + return ret;
> +
> + driver_data->pwm_enabled = val;
> +
> + /* Activating PWM sets speed to 0. Alternative design decision
> + * could be to keep the current value. This would require proper
> + * setting of driver_data->curr_speed for example.
> + */
> + if (val)
Consider:
if (!val)
return 0;
So you can deindent the loop by one level.
> + for (i = 0; i < driver_data->fan_count; ++i) {
> + ret = write_speed(dev, i, 0, true);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static const struct hwmon_ops hwmops = {
> + .is_visible = hwm_is_visible,
> + .read = hwm_read,
> + .read_string = hwm_read_string,
> + .write = hwm_write,
> +};
> +
> +static const struct hwmon_channel_info * const hwmcinfo[] = {
> + 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_F_INPUT | HWMON_F_LABEL,
> + 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_F_INPUT | HWMON_F_LABEL,
> + 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_F_INPUT | HWMON_F_LABEL,
> + HWMON_F_INPUT | HWMON_F_LABEL),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
> + HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info hwminfo = {
> + .ops = &hwmops,
> + .info = hwmcinfo
> +};
> +
> +static u8 tuxi_get_temp_level(struct tuxi_hwmon_driver_data_t *driver_data,
> + u8 fan_id, long temp)
> +{
> + long temp_low, temp_high;
> + unsigned int i;
> + int ret;
> +
> + ret = driver_data->temp_level[fan_id];
> +
> + for (i = 0; temp_levels[i].temp; ++i) {
> + temp_low = i == 0 ? S32_MIN : temp_levels[i - 1].temp;
> + temp_high = temp_levels[i].temp;
> + if (ret > i)
> + temp_high -= TUXI_TEMP_LEVEL_HYSTERESIS;
> +
> + if (temp >= temp_low && temp < temp_high)
> + ret = i;
Why you cannot return i; immediately here?
> + }
> + if (temp >= temp_high)
> + ret = i;
> +
> + return ret;
> +}
> +
> +static void tuxi_periodic_hw_safeguard(struct work_struct *work)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = container_of(work,
> + struct tuxi_hwmon_driver_data_t,
> + work.work);
> + struct device *dev = driver_data->hwmdev;
> + struct acpi_device *pdev = to_acpi_device(dev->parent);
> + unsigned long long params[2], retval;
> + unsigned int i;
> + long temp;
> + int ret;
> +
> + for (i = 0; i < driver_data->fan_count; ++i) {
> + params[0] = i;
> + ret = tuxi_tfan_method(pdev,
> + TUXI_TFAN_METHOD_GET_FAN_TEMPERATURE,
> + params, 1, &retval);
> + /* If reading the temprature fails, default to a high value to
temperature
> + * be on the safe side in the worst case.
> + */
> + if (ret)
> + retval = 2720 + 1200;
TUXI_FW_TEMP_OFFSET + 1200 ?
Why it doesn't match to that define??
> +
> + temp = retval > S32_MAX / 100 ?
> + S32_MAX : (retval - TUXI_FW_TEMP_OFFSET) * 100;
Same math issue comment as above.
Why is the read+conversion code duplicated into two places?
> +
> + driver_data->temp_level[i] = tuxi_get_temp_level(driver_data, i,
> + temp);
> + }
> +
> + // Reapply want_speeds to respect eventual new temp_levels
> + for (i = 0; i < driver_data->fan_count; ++i)
> + write_speed(dev, i, driver_data->want_speed[i], false);
> +
> + schedule_delayed_work(&driver_data->work, TUXI_SAFEGUARD_PERIOD);
> +}
> +
> +static int tuxi_hwmon_add_devices(struct acpi_device *pdev, struct device **hwmdev)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data;
> + unsigned long long params[2], retval;
> + unsigned int i;
> + int ret;
> +
> + /* The first version of TUXI TFAN didn't have the Get Fan Temperature
> + * method which is integral to this driver. So probe for existence and
> + * abort otherwise.
> + *
> + * The Get Fan Speed method is also missing in that version, but was
> + * added in the same version so it doesn't have to be probe separately.
> + */
> + params[0] = 0;
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_GET_FAN_TEMPERATURE,
> + params, 1, &retval);
> + if (ret)
> + return ret;
> +
> + driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), GFP_KERNEL);
> + if (!driver_data)
> + return -ENOMEM;
> +
> + /* Loading this module sets the fan mode to auto. Alternative design
> + * decision could be to keep the current value. This would require
> + * proper initialization of driver_data->curr_speed for example.
> + */
> + params[0] = 0;
Is this 0 ..._MODE_AUTO? Please use a named define if that's the case.
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_SET_FAN_MODE, params, 1,
> + NULL);
> + if (ret)
> + return ret;
> +
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_GET_FAN_COUNT, NULL, 0,
> + &retval);
> + if (ret)
> + return ret;
> + if (retval > TUXI_MAX_FAN_COUNT)
> + return -EINVAL;
> + driver_data->fan_count = retval;
> +
> + for (i = 0; i < driver_data->fan_count; ++i) {
> + params[0] = i;
> + ret = tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_GET_FAN_TYPE,
> + params, 1, &retval);
> + if (ret)
> + return ret;
> + else if (retval >= ARRAY_SIZE(tuxi_fan_type_labels))
No need for else as there's return.
> + return -EOPNOTSUPP;
> + driver_data->fan_types[i] = tuxi_fan_type_labels[retval];
> + }
> +
> + *hwmdev = devm_hwmon_device_register_with_info(&pdev->dev,
> + "tuxedo_nbxx_acpi_tuxi",
> + driver_data, &hwminfo,
> + NULL);
> + ret = PTR_ERR_OR_ZERO(*hwmdev);
> + if (ret)
This obfuscates what you're doing here as you do not actually use 0 at
all. Please use this instead:
if (IS_ERR(*hwmdev))
return PTR_ERR(*hwmdev);
> + return ret;
> +
> + driver_data->hwmdev = *hwmdev;
> +
> + INIT_DELAYED_WORK(&driver_data->work, tuxi_periodic_hw_safeguard);
> + schedule_delayed_work(&driver_data->work, TUXI_SAFEGUARD_PERIOD);
> +
> + return 0;
> +}
> +
> +static void tuxi_hwmon_remove_devices(struct device *hwmdev)
> +{
> + struct tuxi_hwmon_driver_data_t *driver_data = dev_get_drvdata(hwmdev);
> + struct acpi_device *pdev = to_acpi_device(hwmdev->parent);
> + unsigned long long params[2];
> +
> + cancel_delayed_work_sync(&driver_data->work);
> +
> + params[0] = 0;
Use a named define?
> + tuxi_tfan_method(pdev, TUXI_TFAN_METHOD_SET_FAN_MODE, params, 1, NULL);
> +}
> +
> +static int tuxi_add(struct acpi_device *device)
> +{
> + struct tuxi_driver_data_t *driver_data;
> + acpi_status status;
> +
> + driver_data = devm_kzalloc(&device->dev, sizeof(*driver_data),
> + GFP_KERNEL);
> + if (!driver_data)
> + return -ENOMEM;
> +
> + // Find subdevices
> + status = acpi_get_handle(device->handle, "TFAN",
> + &driver_data->tfan_handle);
> + if (ACPI_FAILURE(status))
> + return_ACPI_STATUS(status);
> +
> + dev_set_drvdata(&device->dev, driver_data);
> +
> + return tuxi_hwmon_add_devices(device, &driver_data->hwmdev);
> +}
> +
> +static void tuxi_remove(struct acpi_device *device)
> +{
> + struct tuxi_driver_data_t *driver_data = dev_get_drvdata(&device->dev);
> +
> + tuxi_hwmon_remove_devices(driver_data->hwmdev);
> +}
> +
> +static struct acpi_driver acpi_driver = {
> + .name = "tuxedo_nbxx_acpi_tuxi",
> + .ids = acpi_device_ids,
> + .ops = {
> + .add = tuxi_add,
> + .remove = tuxi_remove,
> + },
> +};
> +
> +module_acpi_driver(acpi_driver);
> +
> +MODULE_DESCRIPTION("TUXEDO TUXI");
Perhaps this could be made a more specific to the actual functionality?
> +MODULE_AUTHOR("Werner Sembach <wse@xxxxxxxxxxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
>
--
i.