On Fri, 4 Apr 2025, Micha³ Kopeæ wrote: > Introduce a driver for devices running Dasharo firmware. The driver > supports thermal monitoring using a new ACPI interface in Dasharo. The > initial version supports monitoring fan speeds, fan PWM duty cycles and > system temperatures as well as determining which specific interfaces are > implemented by firmware. > > It has been tested on a NovaCustom laptop running pre-release Dasharo > firmware, which implements fan and thermal monitoring for the CPU and > the discrete GPU, if present. > > Reviewed-by: Thomas Weißschuh > Signed-off-by: Micha³ Kopeæ > --- > MAINTAINERS | 6 + > drivers/platform/x86/Kconfig | 10 + > drivers/platform/x86/Makefile | 3 + > drivers/platform/x86/dasharo-acpi.c | 357 ++++++++++++++++++++++++++++ > 4 files changed, 376 insertions(+) > create mode 100644 drivers/platform/x86/dasharo-acpi.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 00e94bec401e..6d2e0909ac63 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6404,6 +6404,12 @@ F: net/ax25/ax25_out.c > F: net/ax25/ax25_timer.c > F: net/ax25/sysctl_net_ax25.c > > +DASHARO ACPI PLATFORM DRIVER > +M: Micha³ Kopeæ > +S: Maintained > +W: https://docs.dasharo.com/ > +F: drivers/platform/x86/dasharo_acpi.c > + > DATA ACCESS MONITOR > M: SeongJae Park > L: damon@lists.linux.dev > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 0258dd879d64..8168c5274a08 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -1060,6 +1060,16 @@ config LENOVO_WMI_CAMERA > To compile this driver as a module, choose M here: the module > will be called lenovo-wmi-camera. > > +config DASHARO_ACPI > + tristate "Dasharo ACPI Platform Driver" > + depends on ACPI > + depends on HWMON > + help > + This driver provides HWMON support for devices running Dasharo > + firmware. > + > + If you have a device with Dasharo firmware, choose Y or M here. > + > source "drivers/platform/x86/x86-android-tablets/Kconfig" > > config FW_ATTR_CLASS > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index e1b142947067..3ca53ae01d93 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -110,6 +110,9 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o > # Inspur > obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) += inspur_platform_profile.o > > +# Dasharo > +obj-$(CONFIG_DASHARO_ACPI) += dasharo-acpi.o > + > # Laptop drivers > obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o > obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o > diff --git a/drivers/platform/x86/dasharo-acpi.c b/drivers/platform/x86/dasharo-acpi.c > new file mode 100644 > index 000000000000..041dc44797dc > --- /dev/null > +++ b/drivers/platform/x86/dasharo-acpi.c > @@ -0,0 +1,357 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Dasharo ACPI Driver > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +enum dasharo_feature { > + DASHARO_FEATURE_TEMPERATURE = 0, > + DASHARO_FEATURE_FAN_PWM, > + DASHARO_FEATURE_FAN_TACH, > + DASHARO_FEATURE_MAX > +}; > + > +enum dasharo_temperature { > + DASHARO_TEMPERATURE_CPU_PACKAGE = 0, > + DASHARO_TEMPERATURE_CPU_CORE, > + DASHARO_TEMPERATURE_GPU, > + DASHARO_TEMPERATURE_BOARD, > + DASHARO_TEMPERATURE_CHASSIS, > + DASHARO_TEMPERATURE_MAX > +}; > + > +enum dasharo_fan { > + DASHARO_FAN_CPU = 0, > + DASHARO_FAN_GPU, > + DASHARO_FAN_CHASSIS, > + DASHARO_FAN_MAX > +}; > + > +#define MAX_GROUPS_PER_FEAT 8 > + > +static const char * const dasharo_group_names[DASHARO_FEATURE_MAX][MAX_GROUPS_PER_FEAT] = { > + [DASHARO_FEATURE_TEMPERATURE] = { > + [DASHARO_TEMPERATURE_CPU_PACKAGE] = "CPU Package", > + [DASHARO_TEMPERATURE_CPU_CORE] = "CPU Core", > + [DASHARO_TEMPERATURE_GPU] = "GPU", > + [DASHARO_TEMPERATURE_BOARD] = "Board", > + [DASHARO_TEMPERATURE_CHASSIS] = "Chassis", > + }, > + [DASHARO_FEATURE_FAN_PWM] = { > + [DASHARO_FAN_CPU] = "CPU", > + [DASHARO_FAN_GPU] = "GPU", > + [DASHARO_FAN_CHASSIS] = "Chassis", > + }, > + [DASHARO_FEATURE_FAN_TACH] = { > + [DASHARO_FAN_CPU] = "CPU", > + [DASHARO_FAN_GPU] = "GPU", > + [DASHARO_FAN_CHASSIS] = "Chassis", > + }, > +}; > + > +struct dasharo_capability { > + unsigned int group; > + unsigned int index; > + char name[16]; > +}; > + > +#define MAX_CAPS_PER_FEAT 24 > + > +struct dasharo_data { > + struct platform_device *pdev; > + int caps_found[DASHARO_FEATURE_MAX]; > + struct dasharo_capability capabilities[DASHARO_FEATURE_MAX][MAX_CAPS_PER_FEAT]; > +}; > + > +static int dasharo_get_feature_cap_count(struct dasharo_data *data, enum dasharo_feature feat, int cap) > +{ > + struct acpi_object_list obj_list; > + union acpi_object obj[2]; > + acpi_handle handle; > + acpi_status status; > + u64 count; > + > + obj[0].type = ACPI_TYPE_INTEGER; > + obj[0].integer.value = feat; > + obj[1].type = ACPI_TYPE_INTEGER; > + obj[1].integer.value = cap; > + obj_list.count = 2; > + obj_list.pointer = &obj[0]; > + > + handle = ACPI_HANDLE(&data->pdev->dev); > + status = acpi_evaluate_integer(handle, "GFCP", &obj_list, &count); > + if (ACPI_FAILURE(status)) > + return -ENODEV; > + > + return count; > +} > + > +static int dasharo_read_channel(struct dasharo_data *data, char *method, enum dasharo_feature feat, int channel, long *value) > +{ > + struct acpi_object_list obj_list; > + union acpi_object obj[2]; > + acpi_handle handle; > + acpi_status status; > + u64 val; > + > + if (feat > ARRAY_SIZE(data->capabilities)) > + return -EINVAL; > + > + if (channel > ARRAY_SIZE(data->capabilities[feat])) Shouldn't this use data->caps_found[feat] instead? > + return -EINVAL; > + > + obj[0].type = ACPI_TYPE_INTEGER; > + obj[0].integer.value = data->capabilities[feat][channel].group; > + obj[1].type = ACPI_TYPE_INTEGER; > + obj[1].integer.value = data->capabilities[feat][channel].index; > + obj_list.count = 2; > + obj_list.pointer = &obj[0]; > + > + handle = ACPI_HANDLE(&data->pdev->dev); > + status = acpi_evaluate_integer(handle, method, &obj_list, &val); > + if (ACPI_FAILURE(status)) > + return -ENODEV; > + > + *value = val; > + return 0; > +} > + > +static int dasharo_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + struct dasharo_data *data = dev_get_drvdata(dev); > + long value; > + int ret; > + > + switch (type) { > + case hwmon_temp: > + ret = dasharo_read_channel(data, "GTMP", DASHARO_FEATURE_TEMPERATURE, channel, &value); > + if (!ret) > + *val = value * MILLIDEGREE_PER_DEGREE; > + break; > + case hwmon_fan: > + ret = dasharo_read_channel(data, "GFTH", DASHARO_FEATURE_FAN_TACH, channel, &value); > + if (!ret) > + *val = value; > + break; > + case hwmon_pwm: > + ret = dasharo_read_channel(data, "GFDC", DASHARO_FEATURE_FAN_PWM, channel, &value); > + if (!ret) > + *val = value; > + break; > + default: > + return -ENODEV; > + break; > + } > + > + return ret; > +} > + > +static int dasharo_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, const char **str) > +{ > + struct dasharo_data *data = dev_get_drvdata(dev); > + > + switch (type) { > + case hwmon_temp: > + if (channel >= data->caps_found[DASHARO_FEATURE_TEMPERATURE]) > + return -EINVAL; > + > + *str = data->capabilities[DASHARO_FEATURE_TEMPERATURE][channel].name; > + break; > + case hwmon_fan: > + if (channel >= data->caps_found[DASHARO_FEATURE_FAN_TACH]) > + return -EINVAL; > + > + *str = data->capabilities[DASHARO_FEATURE_FAN_TACH][channel].name; > + break; > + default: > + return -EOPNOTSUPP; > + } > + > + return 0; > +} > + > +static umode_t dasharo_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + const struct dasharo_data *data = drvdata; > + > + switch (type) { > + case hwmon_temp: > + if (channel < data->caps_found[DASHARO_FEATURE_TEMPERATURE]) > + return 0444; > + break; > + case hwmon_pwm: > + if (channel < data->caps_found[DASHARO_FEATURE_FAN_PWM]) > + return 0444; > + break; > + case hwmon_fan: > + if (channel < data->caps_found[DASHARO_FEATURE_FAN_TACH]) > + return 0444; > + break; > + default: > + break; > + } > + > + return 0; > +} > + > +static const struct hwmon_ops dasharo_hwmon_ops = { > + .is_visible = dasharo_hwmon_is_visible, > + .read_string = dasharo_hwmon_read_string, > + .read = dasharo_hwmon_read, > +}; > + > +// Max 24 capabilities per feature > +static const struct hwmon_channel_info * const dasharo_hwmon_info[] = { > + 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_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(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, > + 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_CHANNEL_INFO(pwm, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT, > + HWMON_PWM_INPUT), > + NULL > +}; > + > +static const struct hwmon_chip_info dasharo_hwmon_chip_info = { > + .ops = &dasharo_hwmon_ops, > + .info = dasharo_hwmon_info, > +}; > + > +static void dasharo_fill_feature_caps(struct dasharo_data *data, enum dasharo_feature feat) > +{ > + struct dasharo_capability *cap; > + int cap_count = 0; > + int count; > + > + for (int group = 0; group < MAX_GROUPS_PER_FEAT; ++group) { > + count = dasharo_get_feature_cap_count(data, feat, group); > + > + if (count <= 0) > + continue; > + > + for (unsigned int i = 0; i < count && cap_count < ARRAY_SIZE(data->capabilities[feat]); ++i) { > + cap = &data->capabilities[feat][cap_count]; > + cap->group = group; > + cap->index = i; > + scnprintf(cap->name, sizeof(cap->name), "%s %d", dasharo_group_names[feat][group], i); > + cap_count++; > + } > + } > + data->caps_found[feat] = cap_count; > +} > + > +static int dasharo_probe(struct platform_device *pdev) > +{ > + struct dasharo_data *data; > + struct device *hwmon; > + > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + data->pdev = pdev; > + > + for (unsigned int i = 0; i < DASHARO_FEATURE_MAX; ++i) > + dasharo_fill_feature_caps(data, i); > + > + hwmon = devm_hwmon_device_register_with_info(&pdev->dev, "dasharo_acpi", data, > + &dasharo_hwmon_chip_info, NULL); > + > + return PTR_ERR_OR_ZERO(hwmon); > +} > + > +static const struct acpi_device_id dasharo_device_ids[] = { > + {"DSHR0001", 0}, > + {} > +}; > +MODULE_DEVICE_TABLE(acpi, dasharo_device_ids); > + > +static struct platform_driver dasharo_driver = { > + .driver = { > + .name = "dasharo-acpi", > + .acpi_match_table = dasharo_device_ids, > + }, > + .probe = dasharo_probe, > +}; > +module_platform_driver(dasharo_driver); > + > +MODULE_DESCRIPTION("Dasharo ACPI Driver"); > +MODULE_AUTHOR("Micha³ Kopeæ "); > +MODULE_LICENSE("GPL"); > -- i.