diff options
Diffstat (limited to 'drivers/platform/x86/dell')
26 files changed, 4188 insertions, 1157 deletions
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index e712df67fa6b..738c108c2163 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -18,14 +18,36 @@ config ALIENWARE_WMI tristate "Alienware Special feature control" default m depends on ACPI + depends on ACPI_WMI + depends on DMI depends on LEDS_CLASS depends on NEW_LEDS - depends on ACPI_WMI + depends on HWMON + help + This is a driver for controlling Alienware WMI driven features. + + On legacy devices, it exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated + AlienFX USB MCU such as the X51 and X51-R2. + + On newer devices, it exposes the AWCC thermal control interface through + known Kernel APIs. + +config ALIENWARE_WMI_LEGACY + bool "Alienware Legacy WMI device driver" + default y + depends on ALIENWARE_WMI + help + Legacy Alienware WMI driver with AlienFX LED control capabilities. + +config ALIENWARE_WMI_WMAX + bool "Alienware WMAX WMI device driver" + default y + depends on ALIENWARE_WMI + select ACPI_PLATFORM_PROFILE help - This is a driver for controlling Alienware BIOS driven - features. It exposes an interface for controlling the AlienFX - zones on Alienware machines that don't contain a dedicated AlienFX - USB MCU such as the X51 and X51-R2. + Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and + AWCC thermal control capabilities. config DCDBAS tristate "Dell Systems Management Base Driver" @@ -49,6 +71,7 @@ config DELL_LAPTOP default m depends on DMI depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_BATTERY depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL || RFKILL = n depends on DELL_WMI || DELL_WMI = n @@ -57,8 +80,6 @@ config DELL_LAPTOP select POWER_SUPPLY select LEDS_CLASS select NEW_LEDS - select LEDS_TRIGGERS - select LEDS_TRIGGER_AUDIO help This driver adds support for rfkill and backlight control to Dell laptops (except for some models covered by the Compal driver). @@ -93,6 +114,19 @@ config DELL_RBTN To compile this driver as a module, choose M here: the module will be called dell-rbtn. +config DELL_PC + tristate "Dell PC Extras" + default m + depends on ACPI + depends on DMI + depends on DELL_SMBIOS + select ACPI_PLATFORM_PROFILE + help + This driver adds support for controlling the fan modes via platform_profile + on supported Dell systems regardless of formfactor. + Module will simply do nothing if thermal management commands are not + supported. + # # The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those # backends are selected. The "depends" line prevents a configuration @@ -138,7 +172,8 @@ config DELL_SMBIOS_SMM config DELL_SMO8800 tristate "Dell Latitude freefall driver (ACPI SMO88XX)" - default m + default m if ACPI + depends on I2C depends on ACPI || COMPILE_TEST help Say Y here if you want to support SMO88XX freefall devices @@ -147,6 +182,22 @@ config DELL_SMO8800 To compile this driver as a module, choose M here: the module will be called dell-smo8800. +config DELL_UART_BACKLIGHT + tristate "Dell AIO UART Backlight driver" + depends on ACPI + depends on ACPI_VIDEO + depends on BACKLIGHT_CLASS_DEVICE + depends on SERIAL_DEV_BUS + help + Say Y here if you want to support Dell AIO UART backlight interface. + The Dell AIO machines released after 2017 come with a UART interface + to communicate with the backlight scalar board. This driver creates + a standard backlight interface and talks to the scalar board through + UART to adjust the AIO screen brightness. + + To compile this driver as a module, choose M here: the module will + be called dell_uart_backlight. + config DELL_WMI tristate "Dell WMI notifications" default m @@ -165,8 +216,8 @@ config DELL_WMI config DELL_WMI_PRIVACY bool "Dell WMI Hardware Privacy Support" - depends on LEDS_TRIGGER_AUDIO = y || DELL_WMI = LEDS_TRIGGER_AUDIO depends on DELL_WMI + depends on ACPI_EC help This option adds integration with the "Dell Hardware Privacy" feature of Dell laptops to the dell-wmi driver. diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index 1b8942426622..c7501c25e627 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -4,21 +4,27 @@ # Dell x86 Platform-Specific Drivers # -obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o -obj-$(CONFIG_DCDBAS) += dcdbas.o -obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o -obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o -obj-$(CONFIG_DELL_RBU) += dell_rbu.o -obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o -dell-smbios-objs := dell-smbios-base.o -dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o -dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o -obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o -obj-$(CONFIG_DELL_WMI) += dell-wmi.o -dell-wmi-objs := dell-wmi-base.o -dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o -obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o -obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o -obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o -obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o +alienware-wmi-y := alienware-wmi-base.o +alienware-wmi-$(CONFIG_ALIENWARE_WMI_LEGACY) += alienware-wmi-legacy.o +alienware-wmi-$(CONFIG_ALIENWARE_WMI_WMAX) += alienware-wmi-wmax.o +obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o +obj-$(CONFIG_DELL_RBU) += dell_rbu.o +obj-$(CONFIG_DELL_PC) += dell-pc.o +obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o +dell-smbios-y := dell-smbios-base.o +dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o +dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o +obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_SMO8800) += dell-lis3lv02d.o +obj-$(CONFIG_DELL_UART_BACKLIGHT) += dell-uart-backlight.o +obj-$(CONFIG_DELL_WMI) += dell-wmi.o +dell-wmi-y := dell-wmi-base.o +dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o +obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o +obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o +obj-$(CONFIG_DELL_WMI_DDV) += dell-wmi-ddv.o +obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ diff --git a/drivers/platform/x86/dell/alienware-wmi-base.c b/drivers/platform/x86/dell/alienware-wmi-base.c new file mode 100644 index 000000000000..64562b92314f --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi-base.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware special feature control + * + * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/cleanup.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmi.h> +#include <linux/leds.h> +#include "alienware-wmi.h" + +MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); +MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>"); +MODULE_DESCRIPTION("Alienware special feature control"); +MODULE_LICENSE("GPL"); + +struct alienfx_quirks *alienfx; + +static struct alienfx_quirks quirk_inspiron5675 = { + .num_zones = 2, + .hdmi_mux = false, + .amplifier = false, + .deepslp = false, +}; + +static struct alienfx_quirks quirk_unknown = { + .num_zones = 2, + .hdmi_mux = false, + .amplifier = false, + .deepslp = false, +}; + +static struct alienfx_quirks quirk_x51_r1_r2 = { + .num_zones = 3, + .hdmi_mux = false, + .amplifier = false, + .deepslp = false, +}; + +static struct alienfx_quirks quirk_x51_r3 = { + .num_zones = 4, + .hdmi_mux = false, + .amplifier = true, + .deepslp = false, +}; + +static struct alienfx_quirks quirk_asm100 = { + .num_zones = 2, + .hdmi_mux = true, + .amplifier = false, + .deepslp = false, +}; + +static struct alienfx_quirks quirk_asm200 = { + .num_zones = 2, + .hdmi_mux = true, + .amplifier = false, + .deepslp = true, +}; + +static struct alienfx_quirks quirk_asm201 = { + .num_zones = 2, + .hdmi_mux = true, + .amplifier = true, + .deepslp = true, +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + alienfx = dmi->driver_data; + return 1; +} + +static const struct dmi_system_id alienware_quirks[] __initconst = { + { + .callback = dmi_matched, + .ident = "Alienware ASM100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), + }, + .driver_data = &quirk_asm100, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM200", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), + }, + .driver_data = &quirk_asm200, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM201", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), + }, + .driver_data = &quirk_asm201, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), + }, + .driver_data = &quirk_x51_r3, + }, + { + .callback = dmi_matched, + .ident = "Dell Inc. Inspiron 5675", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), + }, + .driver_data = &quirk_inspiron5675, + }, + {} +}; + +u8 alienware_interface; + +int alienware_wmi_command(struct wmi_device *wdev, u32 method_id, + void *in_args, size_t in_size, u32 *out_data) +{ + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer in = {in_size, in_args}; + acpi_status ret; + + ret = wmidev_evaluate_method(wdev, 0, method_id, &in, out_data ? &out : NULL); + if (ACPI_FAILURE(ret)) + return -EIO; + + union acpi_object *obj __free(kfree) = out.pointer; + + if (out_data) { + if (obj && obj->type == ACPI_TYPE_INTEGER) + *out_data = (u32)obj->integer.value; + else + return -ENOMSG; + } + + return 0; +} + +/* + * Helpers used for zone control + */ +static int parse_rgb(const char *buf, struct color_platform *colors) +{ + long unsigned int rgb; + int ret; + union color_union { + struct color_platform cp; + int package; + } repackager; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + repackager.package = rgb & 0x0f0f0f0f; + pr_debug("alienware-wmi: r: %d g:%d b: %d\n", + repackager.cp.red, repackager.cp.green, repackager.cp.blue); + *colors = repackager.cp; + return 0; +} + +/* + * Individual RGB zone control + */ +static ssize_t zone_show(struct device *dev, struct device_attribute *attr, + char *buf, u8 location) +{ + struct alienfx_priv *priv = dev_get_drvdata(dev); + struct color_platform *colors = &priv->colors[location]; + + return sprintf(buf, "red: %d, green: %d, blue: %d\n", + colors->red, colors->green, colors->blue); + +} + +static ssize_t zone_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count, u8 location) +{ + struct alienfx_priv *priv = dev_get_drvdata(dev); + struct color_platform *colors = &priv->colors[location]; + struct alienfx_platdata *pdata = dev_get_platdata(dev); + int ret; + + ret = parse_rgb(buf, colors); + if (ret) + return ret; + + ret = pdata->ops.upd_led(priv, pdata->wdev, location); + + return ret ? ret : count; +} + +static ssize_t zone00_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return zone_show(dev, attr, buf, 0); +} + +static ssize_t zone00_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return zone_store(dev, attr, buf, count, 0); +} + +static DEVICE_ATTR_RW(zone00); + +static ssize_t zone01_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return zone_show(dev, attr, buf, 1); +} + +static ssize_t zone01_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return zone_store(dev, attr, buf, count, 1); +} + +static DEVICE_ATTR_RW(zone01); + +static ssize_t zone02_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return zone_show(dev, attr, buf, 2); +} + +static ssize_t zone02_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return zone_store(dev, attr, buf, count, 2); +} + +static DEVICE_ATTR_RW(zone02); + +static ssize_t zone03_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return zone_show(dev, attr, buf, 3); +} + +static ssize_t zone03_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return zone_store(dev, attr, buf, count, 3); +} + +static DEVICE_ATTR_RW(zone03); + +/* + * Lighting control state device attribute (Global) + */ +static ssize_t lighting_control_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct alienfx_priv *priv = dev_get_drvdata(dev); + + if (priv->lighting_control_state == LEGACY_BOOTING) + return sysfs_emit(buf, "[booting] running suspend\n"); + else if (priv->lighting_control_state == LEGACY_SUSPEND) + return sysfs_emit(buf, "booting running [suspend]\n"); + + return sysfs_emit(buf, "booting [running] suspend\n"); +} + +static ssize_t lighting_control_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct alienfx_priv *priv = dev_get_drvdata(dev); + u8 val; + + if (strcmp(buf, "booting\n") == 0) + val = LEGACY_BOOTING; + else if (strcmp(buf, "suspend\n") == 0) + val = LEGACY_SUSPEND; + else if (alienware_interface == LEGACY) + val = LEGACY_RUNNING; + else + val = WMAX_RUNNING; + + priv->lighting_control_state = val; + pr_debug("alienware-wmi: updated control state to %d\n", + priv->lighting_control_state); + + return count; +} + +static DEVICE_ATTR_RW(lighting_control_state); + +static umode_t zone_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + if (n < alienfx->num_zones + 1) + return attr->mode; + + return 0; +} + +static bool zone_group_visible(struct kobject *kobj) +{ + return alienfx->num_zones > 0; +} +DEFINE_SYSFS_GROUP_VISIBLE(zone); + +static struct attribute *zone_attrs[] = { + &dev_attr_lighting_control_state.attr, + &dev_attr_zone00.attr, + &dev_attr_zone01.attr, + &dev_attr_zone02.attr, + &dev_attr_zone03.attr, + NULL +}; + +static struct attribute_group zone_attribute_group = { + .name = "rgb_zones", + .is_visible = SYSFS_GROUP_VISIBLE(zone), + .attrs = zone_attrs, +}; + +/* + * LED Brightness (Global) + */ +static void global_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, + global_led); + struct alienfx_platdata *pdata = dev_get_platdata(&priv->pdev->dev); + int ret; + + priv->global_brightness = brightness; + + ret = pdata->ops.upd_brightness(priv, pdata->wdev, brightness); + if (ret) + pr_err("LED brightness update failed\n"); +} + +static enum led_brightness global_led_get(struct led_classdev *led_cdev) +{ + struct alienfx_priv *priv = container_of(led_cdev, struct alienfx_priv, + global_led); + + return priv->global_brightness; +} + +/* + * Platform Driver + */ +static int alienfx_probe(struct platform_device *pdev) +{ + struct alienfx_priv *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (alienware_interface == WMAX) + priv->lighting_control_state = WMAX_RUNNING; + else + priv->lighting_control_state = LEGACY_RUNNING; + + priv->pdev = pdev; + priv->global_led.name = "alienware::global_brightness"; + priv->global_led.brightness_set = global_led_set; + priv->global_led.brightness_get = global_led_get; + priv->global_led.max_brightness = 0x0F; + priv->global_brightness = priv->global_led.max_brightness; + platform_set_drvdata(pdev, priv); + + return devm_led_classdev_register(&pdev->dev, &priv->global_led); +} + +static const struct attribute_group *alienfx_groups[] = { + &zone_attribute_group, + WMAX_DEV_GROUPS + NULL +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "alienware-wmi", + .dev_groups = alienfx_groups, + }, + .probe = alienfx_probe, +}; + +static void alienware_alienfx_remove(void *data) +{ + struct platform_device *pdev = data; + + platform_device_unregister(pdev); +} + +int alienware_alienfx_setup(struct alienfx_platdata *pdata) +{ + struct device *dev = &pdata->wdev->dev; + struct platform_device *pdev; + int ret; + + pdev = platform_device_register_data(NULL, "alienware-wmi", + PLATFORM_DEVID_NONE, pdata, + sizeof(*pdata)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + dev_set_drvdata(dev, pdev); + ret = devm_add_action_or_reset(dev, alienware_alienfx_remove, pdev); + if (ret) + return ret; + + return 0; +} + +static int __init alienware_wmi_init(void) +{ + int ret; + + dmi_check_system(alienware_quirks); + if (!alienfx) + alienfx = &quirk_unknown; + + ret = platform_driver_register(&platform_driver); + if (ret < 0) + return ret; + + if (wmi_has_guid(WMAX_CONTROL_GUID)) { + alienware_interface = WMAX; + ret = alienware_wmax_wmi_init(); + } else { + alienware_interface = LEGACY; + ret = alienware_legacy_wmi_init(); + } + + if (ret < 0) + platform_driver_unregister(&platform_driver); + + return ret; +} + +module_init(alienware_wmi_init); + +static void __exit alienware_wmi_exit(void) +{ + if (alienware_interface == WMAX) + alienware_wmax_wmi_exit(); + else + alienware_legacy_wmi_exit(); + + platform_driver_unregister(&platform_driver); +} + +module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dell/alienware-wmi-legacy.c b/drivers/platform/x86/dell/alienware-wmi-legacy.c new file mode 100644 index 000000000000..4a84a2fe918b --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi-legacy.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware LEGACY WMI device driver + * + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/wmi.h> +#include "alienware-wmi.h" + +struct legacy_led_args { + struct color_platform colors; + u8 brightness; + u8 state; +} __packed; + + +/* + * Legacy WMI driver + */ +static int legacy_wmi_update_led(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 location) +{ + struct legacy_led_args legacy_args = { + .colors = priv->colors[location], + .brightness = priv->global_brightness, + .state = 0, + }; + struct acpi_buffer input; + acpi_status status; + + if (legacy_args.state != LEGACY_RUNNING) { + legacy_args.state = priv->lighting_control_state; + + input.length = sizeof(legacy_args); + input.pointer = &legacy_args; + + status = wmi_evaluate_method(LEGACY_POWER_CONTROL_GUID, 0, + location + 1, &input, NULL); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; + } + + return alienware_wmi_command(wdev, location + 1, &legacy_args, + sizeof(legacy_args), NULL); +} + +static int legacy_wmi_update_brightness(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 brightness) +{ + return legacy_wmi_update_led(priv, wdev, 0); +} + +static int legacy_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct alienfx_platdata pdata = { + .wdev = wdev, + .ops = { + .upd_led = legacy_wmi_update_led, + .upd_brightness = legacy_wmi_update_brightness, + }, + }; + + return alienware_alienfx_setup(&pdata); +} + +static const struct wmi_device_id alienware_legacy_device_id_table[] = { + { LEGACY_CONTROL_GUID, NULL }, + { }, +}; +MODULE_DEVICE_TABLE(wmi, alienware_legacy_device_id_table); + +static struct wmi_driver alienware_legacy_wmi_driver = { + .driver = { + .name = "alienware-wmi-alienfx", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table = alienware_legacy_device_id_table, + .probe = legacy_wmi_probe, + .no_singleton = true, +}; + +int __init alienware_legacy_wmi_init(void) +{ + return wmi_driver_register(&alienware_legacy_wmi_driver); +} + +void __exit alienware_legacy_wmi_exit(void) +{ + wmi_driver_unregister(&alienware_legacy_wmi_driver); +} diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c new file mode 100644 index 000000000000..c42f9228b0b2 --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -0,0 +1,1667 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware WMAX WMI device driver + * + * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/bits.h> +#include <linux/debugfs.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/kstrtox.h> +#include <linux/minmax.h> +#include <linux/moduleparam.h> +#include <linux/platform_profile.h> +#include <linux/pm.h> +#include <linux/seq_file.h> +#include <linux/units.h> +#include <linux/wmi.h> +#include "alienware-wmi.h" + +#define WMAX_METHOD_HDMI_SOURCE 0x1 +#define WMAX_METHOD_HDMI_STATUS 0x2 +#define WMAX_METHOD_HDMI_CABLE 0x5 +#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 +#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B +#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C +#define WMAX_METHOD_BRIGHTNESS 0x3 +#define WMAX_METHOD_ZONE_CONTROL 0x4 + +#define AWCC_METHOD_GET_FAN_SENSORS 0x13 +#define AWCC_METHOD_THERMAL_INFORMATION 0x14 +#define AWCC_METHOD_THERMAL_CONTROL 0x15 +#define AWCC_METHOD_FWUP_GPIO_CONTROL 0x20 +#define AWCC_METHOD_READ_TOTAL_GPIOS 0x21 +#define AWCC_METHOD_READ_GPIO_STATUS 0x22 +#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25 + +#define AWCC_FAILURE_CODE 0xFFFFFFFF +#define AWCC_FAILURE_CODE_2 0xFFFFFFFE + +#define AWCC_SENSOR_ID_FLAG BIT(8) +#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0) +#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4) +#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0) + +/* Arbitrary limit based on supported models */ +#define AWCC_MAX_RES_COUNT 16 +#define AWCC_ID_BITMAP_SIZE (U8_MAX + 1) +#define AWCC_ID_BITMAP_LONGS BITS_TO_LONGS(AWCC_ID_BITMAP_SIZE) + +static bool force_hwmon; +module_param_unsafe(force_hwmon, bool, 0); +MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available"); + +static bool force_platform_profile; +module_param_unsafe(force_platform_profile, bool, 0); +MODULE_PARM_DESC(force_platform_profile, "Forces auto-detecting thermal profiles without checking if WMI thermal backend is available"); + +static bool force_gmode; +module_param_unsafe(force_gmode, bool, 0); +MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected"); + +struct awcc_quirks { + bool hwmon; + bool pprof; + bool gmode; +}; + +static struct awcc_quirks g_series_quirks = { + .hwmon = true, + .pprof = true, + .gmode = true, +}; + +static struct awcc_quirks generic_quirks = { + .hwmon = true, + .pprof = true, + .gmode = false, +}; + +static struct awcc_quirks empty_quirks; + +static const struct dmi_system_id awcc_dmi_table[] __initconst = { + { + .ident = "Alienware Area-51m R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51m R2"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware m15 R7", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m15 R7"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware m16 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Alienware m16 R1 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Alienware m16 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R2"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware m17 R5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware m18 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware x15 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware x15 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R2"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Alienware x17 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"), + }, + .driver_data = &generic_quirks, + }, + { + .ident = "Dell Inc. G15 5510", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G15 5511", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G15 5515", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G16 7630", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell G16 7630"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G3 3500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G3 3590", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G5 5500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"), + }, + .driver_data = &g_series_quirks, + }, + { + .ident = "Dell Inc. G5 5505", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "G5 5505"), + }, + .driver_data = &g_series_quirks, + }, +}; + +enum AWCC_GET_FAN_SENSORS_OPERATIONS { + AWCC_OP_GET_TOTAL_FAN_TEMPS = 0x01, + AWCC_OP_GET_FAN_TEMP_ID = 0x02, +}; + +enum AWCC_THERMAL_INFORMATION_OPERATIONS { + AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02, + AWCC_OP_GET_RESOURCE_ID = 0x03, + AWCC_OP_GET_TEMPERATURE = 0x04, + AWCC_OP_GET_FAN_RPM = 0x05, + AWCC_OP_GET_FAN_MIN_RPM = 0x08, + AWCC_OP_GET_FAN_MAX_RPM = 0x09, + AWCC_OP_GET_CURRENT_PROFILE = 0x0B, + AWCC_OP_GET_FAN_BOOST = 0x0C, +}; + +enum AWCC_THERMAL_CONTROL_OPERATIONS { + AWCC_OP_ACTIVATE_PROFILE = 0x01, + AWCC_OP_SET_FAN_BOOST = 0x02, +}; + +enum AWCC_GAME_SHIFT_STATUS_OPERATIONS { + AWCC_OP_TOGGLE_GAME_SHIFT = 0x01, + AWCC_OP_GET_GAME_SHIFT_STATUS = 0x02, +}; + +enum AWCC_THERMAL_TABLES { + AWCC_THERMAL_TABLE_LEGACY = 0x9, + AWCC_THERMAL_TABLE_USTT = 0xA, +}; + +enum AWCC_SPECIAL_THERMAL_CODES { + AWCC_SPECIAL_PROFILE_CUSTOM = 0x00, + AWCC_SPECIAL_PROFILE_GMODE = 0xAB, +}; + +enum AWCC_TEMP_SENSOR_TYPES { + AWCC_TEMP_SENSOR_CPU = 0x01, + AWCC_TEMP_SENSOR_GPU = 0x06, +}; + +enum awcc_thermal_profile { + AWCC_PROFILE_USTT_BALANCED, + AWCC_PROFILE_USTT_BALANCED_PERFORMANCE, + AWCC_PROFILE_USTT_COOL, + AWCC_PROFILE_USTT_QUIET, + AWCC_PROFILE_USTT_PERFORMANCE, + AWCC_PROFILE_USTT_LOW_POWER, + AWCC_PROFILE_LEGACY_QUIET, + AWCC_PROFILE_LEGACY_BALANCED, + AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE, + AWCC_PROFILE_LEGACY_PERFORMANCE, + AWCC_PROFILE_LAST, +}; + +struct wmax_led_args { + u32 led_mask; + struct color_platform colors; + u8 state; +} __packed; + +struct wmax_brightness_args { + u32 led_mask; + u32 percentage; +}; + +struct wmax_basic_args { + u8 arg; +}; + +struct wmax_u32_args { + u8 operation; + u8 arg1; + u8 arg2; + u8 arg3; +}; + +struct awcc_fan_data { + unsigned long auto_channels_temp; + const char *label; + u32 min_rpm; + u32 max_rpm; + u8 suspend_cache; + u8 id; +}; + +struct awcc_priv { + struct wmi_device *wdev; + union { + u32 system_description; + struct { + u8 fan_count; + u8 temp_count; + u8 unknown_count; + u8 profile_count; + }; + u8 res_count[4]; + }; + + struct device *ppdev; + u8 supported_profiles[PLATFORM_PROFILE_LAST]; + + struct device *hwdev; + struct awcc_fan_data **fan_data; + unsigned long temp_sensors[AWCC_ID_BITMAP_LONGS]; + + u32 gpio_count; +}; + +static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = { + [AWCC_PROFILE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED, + [AWCC_PROFILE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [AWCC_PROFILE_USTT_COOL] = PLATFORM_PROFILE_COOL, + [AWCC_PROFILE_USTT_QUIET] = PLATFORM_PROFILE_QUIET, + [AWCC_PROFILE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, + [AWCC_PROFILE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER, + [AWCC_PROFILE_LEGACY_QUIET] = PLATFORM_PROFILE_QUIET, + [AWCC_PROFILE_LEGACY_BALANCED] = PLATFORM_PROFILE_BALANCED, + [AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE, + [AWCC_PROFILE_LEGACY_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE, +}; + +static struct awcc_quirks *awcc; + +/* + * The HDMI mux sysfs node indicates the status of the HDMI input mux. + * It can toggle between standard system GPU output and HDMI input. + */ +static ssize_t cable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args in_args = { + .arg = 0, + }; + u32 out_data; + int ret; + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_CABLE, + &in_args, sizeof(in_args), &out_data); + if (!ret) { + if (out_data == 0) + return sysfs_emit(buf, "[unconnected] connected unknown\n"); + else if (out_data == 1) + return sysfs_emit(buf, "unconnected [connected] unknown\n"); + } + + pr_err("alienware-wmi: unknown HDMI cable status: %d\n", ret); + return sysfs_emit(buf, "unconnected connected [unknown]\n"); +} + +static ssize_t source_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args in_args = { + .arg = 0, + }; + u32 out_data; + int ret; + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_STATUS, + &in_args, sizeof(in_args), &out_data); + if (!ret) { + if (out_data == 1) + return sysfs_emit(buf, "[input] gpu unknown\n"); + else if (out_data == 2) + return sysfs_emit(buf, "input [gpu] unknown\n"); + } + + pr_err("alienware-wmi: unknown HDMI source status: %u\n", ret); + return sysfs_emit(buf, "input gpu [unknown]\n"); +} + +static ssize_t source_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args args; + int ret; + + if (strcmp(buf, "gpu\n") == 0) + args.arg = 1; + else if (strcmp(buf, "input\n") == 0) + args.arg = 2; + else + args.arg = 3; + pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_HDMI_SOURCE, &args, + sizeof(args), NULL); + if (ret < 0) + pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", ret); + + return count; +} + +static DEVICE_ATTR_RO(cable); +static DEVICE_ATTR_RW(source); + +static bool hdmi_group_visible(struct kobject *kobj) +{ + return alienware_interface == WMAX && alienfx->hdmi_mux; +} +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(hdmi); + +static struct attribute *hdmi_attrs[] = { + &dev_attr_cable.attr, + &dev_attr_source.attr, + NULL, +}; + +const struct attribute_group wmax_hdmi_attribute_group = { + .name = "hdmi", + .is_visible = SYSFS_GROUP_VISIBLE(hdmi), + .attrs = hdmi_attrs, +}; + +/* + * Alienware GFX amplifier support + * - Currently supports reading cable status + * - Leaving expansion room to possibly support dock/undock events later + */ +static ssize_t status_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args in_args = { + .arg = 0, + }; + u32 out_data; + int ret; + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_AMPLIFIER_CABLE, + &in_args, sizeof(in_args), &out_data); + if (!ret) { + if (out_data == 0) + return sysfs_emit(buf, "[unconnected] connected unknown\n"); + else if (out_data == 1) + return sysfs_emit(buf, "unconnected [connected] unknown\n"); + } + + pr_err("alienware-wmi: unknown amplifier cable status: %d\n", ret); + return sysfs_emit(buf, "unconnected connected [unknown]\n"); +} + +static DEVICE_ATTR_RO(status); + +static bool amplifier_group_visible(struct kobject *kobj) +{ + return alienware_interface == WMAX && alienfx->amplifier; +} +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(amplifier); + +static struct attribute *amplifier_attrs[] = { + &dev_attr_status.attr, + NULL, +}; + +const struct attribute_group wmax_amplifier_attribute_group = { + .name = "amplifier", + .is_visible = SYSFS_GROUP_VISIBLE(amplifier), + .attrs = amplifier_attrs, +}; + +/* + * Deep Sleep Control support + * - Modifies BIOS setting for deep sleep control allowing extra wakeup events + */ +static ssize_t deepsleep_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args in_args = { + .arg = 0, + }; + u32 out_data; + int ret; + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_STATUS, + &in_args, sizeof(in_args), &out_data); + if (!ret) { + if (out_data == 0) + return sysfs_emit(buf, "[disabled] s5 s5_s4\n"); + else if (out_data == 1) + return sysfs_emit(buf, "disabled [s5] s5_s4\n"); + else if (out_data == 2) + return sysfs_emit(buf, "disabled s5 [s5_s4]\n"); + } + + pr_err("alienware-wmi: unknown deep sleep status: %d\n", ret); + return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n"); +} + +static ssize_t deepsleep_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct alienfx_platdata *pdata = dev_get_platdata(dev); + struct wmax_basic_args args; + int ret; + + if (strcmp(buf, "disabled\n") == 0) + args.arg = 0; + else if (strcmp(buf, "s5\n") == 0) + args.arg = 1; + else + args.arg = 2; + pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); + + ret = alienware_wmi_command(pdata->wdev, WMAX_METHOD_DEEP_SLEEP_CONTROL, + &args, sizeof(args), NULL); + if (!ret) + pr_err("alienware-wmi: deep sleep control failed: results: %u\n", ret); + + return count; +} + +static DEVICE_ATTR_RW(deepsleep); + +static bool deepsleep_group_visible(struct kobject *kobj) +{ + return alienware_interface == WMAX && alienfx->deepslp; +} +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(deepsleep); + +static struct attribute *deepsleep_attrs[] = { + &dev_attr_deepsleep.attr, + NULL, +}; + +const struct attribute_group wmax_deepsleep_attribute_group = { + .name = "deepsleep", + .is_visible = SYSFS_GROUP_VISIBLE(deepsleep), + .attrs = deepsleep_attrs, +}; + +/* + * AWCC Helpers + */ +static bool is_awcc_thermal_profile_id(u8 code) +{ + u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code); + u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code); + + if (mode >= AWCC_PROFILE_LAST) + return false; + + if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET) + return true; + + if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER) + return true; + + return false; +} + +static int awcc_wmi_command(struct wmi_device *wdev, u32 method_id, + struct wmax_u32_args *args, u32 *out) +{ + int ret; + + ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out); + if (ret) + return ret; + + if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2) + return -EBADRQC; + + return 0; +} + +static int awcc_get_fan_sensors(struct wmi_device *wdev, u8 operation, + u8 fan_id, u8 index, u32 *out) +{ + struct wmax_u32_args args = { + .operation = operation, + .arg1 = fan_id, + .arg2 = index, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out); +} + +static int awcc_thermal_information(struct wmi_device *wdev, u8 operation, u8 arg, + u32 *out) +{ + struct wmax_u32_args args = { + .operation = operation, + .arg1 = arg, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_fwup_gpio_control(struct wmi_device *wdev, u8 pin, u8 status) +{ + struct wmax_u32_args args = { + .operation = pin, + .arg1 = status, + .arg2 = 0, + .arg3 = 0, + }; + u32 out; + + return awcc_wmi_command(wdev, AWCC_METHOD_FWUP_GPIO_CONTROL, &args, &out); +} + +static int awcc_read_total_gpios(struct wmi_device *wdev, u32 *count) +{ + struct wmax_u32_args args = {}; + + return awcc_wmi_command(wdev, AWCC_METHOD_READ_TOTAL_GPIOS, &args, count); +} + +static int awcc_read_gpio_status(struct wmi_device *wdev, u8 pin, u32 *status) +{ + struct wmax_u32_args args = { + .operation = pin, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_READ_GPIO_STATUS, &args, status); +} + +static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation, + u32 *out) +{ + struct wmax_u32_args args = { + .operation = operation, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out); +} + +/** + * awcc_op_get_resource_id - Get the resource ID at a given index + * @wdev: AWCC WMI device + * @index: Index + * @out: Value returned by the WMI call + * + * Get the resource ID at a given @index. Resource IDs are listed in the + * following order: + * + * - Fan IDs + * - Sensor IDs + * - Unknown IDs + * - Thermal Profile IDs + * + * The total number of IDs of a given type can be obtained with + * AWCC_OP_GET_SYSTEM_DESCRIPTION. + * + * Return: 0 on success, -errno on failure + */ +static int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u8 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_RESOURCE_ID, + .arg1 = index, + .arg2 = 0, + .arg3 = 0, + }; + u32 out_data; + int ret; + + ret = awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, &out_data); + if (ret) + return ret; + + *out = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data); + + return 0; +} + +static int awcc_op_get_fan_rpm(struct wmi_device *wdev, u8 fan_id, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_FAN_RPM, + .arg1 = fan_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_temperature(struct wmi_device *wdev, u8 temp_id, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_TEMPERATURE, + .arg1 = temp_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_fan_boost(struct wmi_device *wdev, u8 fan_id, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_FAN_BOOST, + .arg1 = fan_id, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_get_current_profile(struct wmi_device *wdev, u32 *out) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_GET_CURRENT_PROFILE, + .arg1 = 0, + .arg2 = 0, + .arg3 = 0, + }; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out); +} + +static int awcc_op_activate_profile(struct wmi_device *wdev, u8 profile) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_ACTIVATE_PROFILE, + .arg1 = profile, + .arg2 = 0, + .arg3 = 0, + }; + u32 out; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out); +} + +static int awcc_op_set_fan_boost(struct wmi_device *wdev, u8 fan_id, u8 boost) +{ + struct wmax_u32_args args = { + .operation = AWCC_OP_SET_FAN_BOOST, + .arg1 = fan_id, + .arg2 = boost, + .arg3 = 0, + }; + u32 out; + + return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out); +} + +/* + * HWMON + * - Provides temperature and fan speed monitoring as well as manual fan + * control + */ +static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct awcc_priv *priv = drvdata; + unsigned int temp_count; + + switch (type) { + case hwmon_temp: + temp_count = bitmap_weight(priv->temp_sensors, AWCC_ID_BITMAP_SIZE); + + return channel < temp_count ? 0444 : 0; + case hwmon_fan: + return channel < priv->fan_count ? 0444 : 0; + case hwmon_pwm: + return channel < priv->fan_count ? 0444 : 0; + default: + return 0; + } +} + +static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + const struct awcc_fan_data *fan; + u32 state; + int ret; + u8 temp; + + switch (type) { + case hwmon_temp: + temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel); + + switch (attr) { + case hwmon_temp_input: + ret = awcc_op_get_temperature(priv->wdev, temp, &state); + if (ret) + return ret; + + *val = state * MILLIDEGREE_PER_DEGREE; + break; + default: + return -EOPNOTSUPP; + } + + break; + case hwmon_fan: + fan = priv->fan_data[channel]; + + switch (attr) { + case hwmon_fan_input: + ret = awcc_op_get_fan_rpm(priv->wdev, fan->id, &state); + if (ret) + return ret; + + *val = state; + break; + case hwmon_fan_min: + *val = fan->min_rpm; + break; + case hwmon_fan_max: + *val = fan->max_rpm; + break; + default: + return -EOPNOTSUPP; + } + + break; + case hwmon_pwm: + fan = priv->fan_data[channel]; + + switch (attr) { + case hwmon_pwm_auto_channels_temp: + *val = fan->auto_channels_temp; + break; + default: + return -EOPNOTSUPP; + } + + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + u8 temp; + + switch (type) { + case hwmon_temp: + temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel); + + switch (temp) { + case AWCC_TEMP_SENSOR_CPU: + *str = "CPU"; + break; + case AWCC_TEMP_SENSOR_GPU: + *str = "GPU"; + break; + default: + *str = "Unknown"; + break; + } + + break; + case hwmon_fan: + *str = priv->fan_data[channel]->label; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops awcc_hwmon_ops = { + .is_visible = awcc_hwmon_is_visible, + .read = awcc_hwmon_read, + .read_string = awcc_hwmon_read_string, +}; + +static const struct hwmon_channel_info * const awcc_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + 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_CHANNEL_INFO(fan, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX + ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP + ), + NULL +}; + +static const struct hwmon_chip_info awcc_hwmon_chip_info = { + .ops = &awcc_hwmon_ops, + .info = awcc_hwmon_info, +}; + +static ssize_t fan_boost_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + struct awcc_fan_data *fan = priv->fan_data[index]; + u32 boost; + int ret; + + ret = awcc_op_get_fan_boost(priv->wdev, fan->id, &boost); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", boost); +} + +static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(attr)->index; + struct awcc_fan_data *fan = priv->fan_data[index]; + unsigned long val; + int ret; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, clamp_val(val, 0, 255)); + + return ret ? ret : count; +} + +static SENSOR_DEVICE_ATTR_RW(fan1_boost, fan_boost, 0); +static SENSOR_DEVICE_ATTR_RW(fan2_boost, fan_boost, 1); +static SENSOR_DEVICE_ATTR_RW(fan3_boost, fan_boost, 2); +static SENSOR_DEVICE_ATTR_RW(fan4_boost, fan_boost, 3); +static SENSOR_DEVICE_ATTR_RW(fan5_boost, fan_boost, 4); +static SENSOR_DEVICE_ATTR_RW(fan6_boost, fan_boost, 5); + +static umode_t fan_boost_attr_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct awcc_priv *priv = dev_get_drvdata(kobj_to_dev(kobj)); + + return n < priv->fan_count ? attr->mode : 0; +} + +static bool fan_boost_group_visible(struct kobject *kobj) +{ + return true; +} + +DEFINE_SYSFS_GROUP_VISIBLE(fan_boost); + +static struct attribute *fan_boost_attrs[] = { + &sensor_dev_attr_fan1_boost.dev_attr.attr, + &sensor_dev_attr_fan2_boost.dev_attr.attr, + &sensor_dev_attr_fan3_boost.dev_attr.attr, + &sensor_dev_attr_fan4_boost.dev_attr.attr, + &sensor_dev_attr_fan5_boost.dev_attr.attr, + &sensor_dev_attr_fan6_boost.dev_attr.attr, + NULL +}; + +static const struct attribute_group fan_boost_group = { + .attrs = fan_boost_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(fan_boost), +}; + +static const struct attribute_group *awcc_hwmon_groups[] = { + &fan_boost_group, + NULL +}; + +static int awcc_hwmon_temps_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + unsigned int i; + int ret; + u8 id; + + for (i = 0; i < priv->temp_count; i++) { + /* + * Temperature sensors IDs are listed after the fan IDs at + * offset `fan_count` + */ + ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id); + if (ret) + return ret; + + __set_bit(id, priv->temp_sensors); + } + + return 0; +} + +static char *awcc_get_fan_label(unsigned long *fan_temps) +{ + unsigned int temp_count = bitmap_weight(fan_temps, AWCC_ID_BITMAP_SIZE); + char *label; + u8 temp_id; + + switch (temp_count) { + case 0: + label = "Independent Fan"; + break; + case 1: + temp_id = find_first_bit(fan_temps, AWCC_ID_BITMAP_SIZE); + + switch (temp_id) { + case AWCC_TEMP_SENSOR_CPU: + label = "Processor Fan"; + break; + case AWCC_TEMP_SENSOR_GPU: + label = "Video Fan"; + break; + default: + label = "Unknown Fan"; + break; + } + + break; + default: + label = "Shared Fan"; + break; + } + + return label; +} + +static int awcc_hwmon_fans_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + unsigned long fan_temps[AWCC_ID_BITMAP_LONGS]; + unsigned long gather[AWCC_ID_BITMAP_LONGS]; + u32 min_rpm, max_rpm, temp_count, temp_id; + struct awcc_fan_data *fan_data; + unsigned int i, j; + int ret; + u8 id; + + for (i = 0; i < priv->fan_count; i++) { + fan_data = devm_kzalloc(&wdev->dev, sizeof(*fan_data), GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + /* + * Fan IDs are listed first at offset 0 + */ + ret = awcc_op_get_resource_id(wdev, i, &id); + if (ret) + return ret; + + ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MIN_RPM, id, + &min_rpm); + if (ret) + return ret; + + ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MAX_RPM, id, + &max_rpm); + if (ret) + return ret; + + ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_TOTAL_FAN_TEMPS, id, + 0, &temp_count); + if (ret) + return ret; + + bitmap_zero(fan_temps, AWCC_ID_BITMAP_SIZE); + + for (j = 0; j < temp_count; j++) { + ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_FAN_TEMP_ID, + id, j, &temp_id); + if (ret) + break; + + temp_id = FIELD_GET(AWCC_RESOURCE_ID_MASK, temp_id); + __set_bit(temp_id, fan_temps); + } + + fan_data->id = id; + fan_data->min_rpm = min_rpm; + fan_data->max_rpm = max_rpm; + fan_data->label = awcc_get_fan_label(fan_temps); + bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE); + bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG); + priv->fan_data[i] = fan_data; + } + + return 0; +} + +static int awcc_hwmon_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + int ret; + + priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count, + sizeof(*priv->fan_data), GFP_KERNEL); + if (!priv->fan_data) + return -ENOMEM; + + ret = awcc_hwmon_temps_init(wdev); + if (ret) + return ret; + + ret = awcc_hwmon_fans_init(wdev); + if (ret) + return ret; + + priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi", + priv, &awcc_hwmon_chip_info, + awcc_hwmon_groups); + + return PTR_ERR_OR_ZERO(priv->hwdev); +} + +static void awcc_hwmon_suspend(struct device *dev) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + struct awcc_fan_data *fan; + unsigned int i; + u32 boost; + int ret; + + for (i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST, + fan->id, &boost); + if (ret) + dev_err(dev, "Failed to store Fan %u boost while suspending\n", i); + + fan->suspend_cache = ret ? 0 : clamp_val(boost, 0, 255); + + awcc_op_set_fan_boost(priv->wdev, fan->id, 0); + if (ret) + dev_err(dev, "Failed to set Fan %u boost to 0 while suspending\n", i); + } +} + +static void awcc_hwmon_resume(struct device *dev) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + struct awcc_fan_data *fan; + unsigned int i; + int ret; + + for (i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + if (!fan->suspend_cache) + continue; + + ret = awcc_op_set_fan_boost(priv->wdev, fan->id, fan->suspend_cache); + if (ret) + dev_err(dev, "Failed to restore Fan %u boost while resuming\n", i); + } +} + +/* + * Thermal Profile control + * - Provides thermal profile control through the Platform Profile API + */ +static int awcc_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + u32 out_data; + int ret; + + ret = awcc_op_get_current_profile(priv->wdev, &out_data); + if (ret) + return ret; + + switch (out_data) { + case AWCC_SPECIAL_PROFILE_CUSTOM: + *profile = PLATFORM_PROFILE_CUSTOM; + return 0; + case AWCC_SPECIAL_PROFILE_GMODE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + return 0; + default: + break; + } + + if (!is_awcc_thermal_profile_id(out_data)) + return -ENODATA; + + out_data = FIELD_GET(AWCC_THERMAL_MODE_MASK, out_data); + *profile = awcc_mode_to_platform_profile[out_data]; + + return 0; +} + +static int awcc_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct awcc_priv *priv = dev_get_drvdata(dev); + + if (awcc->gmode) { + u32 gmode_status; + int ret; + + ret = awcc_game_shift_status(priv->wdev, + AWCC_OP_GET_GAME_SHIFT_STATUS, + &gmode_status); + + if (ret < 0) + return ret; + + if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) || + (profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) { + ret = awcc_game_shift_status(priv->wdev, + AWCC_OP_TOGGLE_GAME_SHIFT, + &gmode_status); + + if (ret < 0) + return ret; + } + } + + return awcc_op_activate_profile(priv->wdev, priv->supported_profiles[profile]); +} + +static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + enum platform_profile_option profile; + struct awcc_priv *priv = drvdata; + enum awcc_thermal_profile mode; + u8 id, offset = 0; + int ret; + + /* + * Thermal profile IDs are listed last at offset + * fan_count + temp_count + unknown_count + */ + for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++) + offset += priv->res_count[i]; + + for (unsigned int i = 0; i < priv->profile_count; i++) { + ret = awcc_op_get_resource_id(priv->wdev, i + offset, &id); + /* + * Some devices report an incorrect number of thermal profiles + * so the resource ID list may end prematurely + */ + if (ret == -EBADRQC) + break; + if (ret) + return ret; + + if (!is_awcc_thermal_profile_id(id)) { + dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id); + continue; + } + + mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id); + profile = awcc_mode_to_platform_profile[mode]; + priv->supported_profiles[profile] = id; + + __set_bit(profile, choices); + } + + if (bitmap_empty(choices, PLATFORM_PROFILE_LAST)) + return -ENODEV; + + if (awcc->gmode) { + priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] = + AWCC_SPECIAL_PROFILE_GMODE; + + __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + } + + /* Every model supports the "custom" profile */ + priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] = + AWCC_SPECIAL_PROFILE_CUSTOM; + + __set_bit(PLATFORM_PROFILE_CUSTOM, choices); + + return 0; +} + +static const struct platform_profile_ops awcc_platform_profile_ops = { + .probe = awcc_platform_profile_probe, + .profile_get = awcc_platform_profile_get, + .profile_set = awcc_platform_profile_set, +}; + +static int awcc_platform_profile_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + + priv->ppdev = devm_platform_profile_register(&wdev->dev, "alienware-wmi", + priv, &awcc_platform_profile_ops); + + return PTR_ERR_OR_ZERO(priv->ppdev); +} + +/* + * DebugFS + */ +static int awcc_debugfs_system_description_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + + seq_printf(seq, "0x%08x\n", priv->system_description); + + return 0; +} + +static int awcc_debugfs_hwmon_data_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + const struct awcc_fan_data *fan; + unsigned int bit; + + seq_printf(seq, "Number of fans: %u\n", priv->fan_count); + seq_printf(seq, "Number of temperature sensors: %u\n\n", priv->temp_count); + + for (u32 i = 0; i < priv->fan_count; i++) { + fan = priv->fan_data[i]; + + seq_printf(seq, "Fan %u:\n", i); + seq_printf(seq, " ID: 0x%02x\n", fan->id); + seq_printf(seq, " Related temperature sensors bitmap: %lu\n", + fan->auto_channels_temp); + } + + seq_puts(seq, "\nTemperature sensor IDs:\n"); + for_each_set_bit(bit, priv->temp_sensors, AWCC_ID_BITMAP_SIZE) + seq_printf(seq, " 0x%02x\n", bit); + + return 0; +} + +static int awcc_debugfs_pprof_data_read(struct seq_file *seq, void *data) +{ + struct device *dev = seq->private; + struct awcc_priv *priv = dev_get_drvdata(dev); + + seq_printf(seq, "Number of thermal profiles: %u\n\n", priv->profile_count); + + for (u32 i = 0; i < PLATFORM_PROFILE_LAST; i++) { + if (!priv->supported_profiles[i]) + continue; + + seq_printf(seq, "Platform profile %u:\n", i); + seq_printf(seq, " ID: 0x%02x\n", priv->supported_profiles[i]); + } + + return 0; +} + +static int awcc_gpio_pin_show(struct seq_file *seq, void *data) +{ + unsigned long pin = debugfs_get_aux_num(seq->file); + struct wmi_device *wdev = seq->private; + u32 status; + int ret; + + ret = awcc_read_gpio_status(wdev, pin, &status); + if (ret) + return ret; + + seq_printf(seq, "%u\n", status); + + return 0; +} + +static ssize_t awcc_gpio_pin_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long pin = debugfs_get_aux_num(file); + struct seq_file *seq = file->private_data; + struct wmi_device *wdev = seq->private; + bool status; + int ret; + + if (!ppos || *ppos) + return -EINVAL; + + ret = kstrtobool_from_user(buf, count, &status); + if (ret) + return ret; + + ret = awcc_fwup_gpio_control(wdev, pin, status); + if (ret) + return ret; + + return count; +} + +DEFINE_SHOW_STORE_ATTRIBUTE(awcc_gpio_pin); + +static void awcc_debugfs_remove(void *data) +{ + struct dentry *root = data; + + debugfs_remove(root); +} + +static void awcc_debugfs_init(struct wmi_device *wdev) +{ + struct awcc_priv *priv = dev_get_drvdata(&wdev->dev); + struct dentry *root, *gpio_ctl; + u32 gpio_count; + char name[64]; + int ret; + + scnprintf(name, sizeof(name), "%s-%s", "alienware-wmi", dev_name(&wdev->dev)); + root = debugfs_create_dir(name, NULL); + + debugfs_create_devm_seqfile(&wdev->dev, "system_description", root, + awcc_debugfs_system_description_read); + + if (awcc->hwmon) + debugfs_create_devm_seqfile(&wdev->dev, "hwmon_data", root, + awcc_debugfs_hwmon_data_read); + + if (awcc->pprof) + debugfs_create_devm_seqfile(&wdev->dev, "pprof_data", root, + awcc_debugfs_pprof_data_read); + + ret = awcc_read_total_gpios(wdev, &gpio_count); + if (ret) { + dev_dbg(&wdev->dev, "Failed to get total GPIO Pin count\n"); + goto out_add_action; + } else if (gpio_count > AWCC_MAX_RES_COUNT) { + dev_dbg(&wdev->dev, "Reported GPIO Pin count may be incorrect: %u\n", gpio_count); + goto out_add_action; + } + + gpio_ctl = debugfs_create_dir("gpio_ctl", root); + + priv->gpio_count = gpio_count; + debugfs_create_u32("total_gpios", 0444, gpio_ctl, &priv->gpio_count); + + for (unsigned int i = 0; i < gpio_count; i++) { + scnprintf(name, sizeof(name), "pin%u", i); + debugfs_create_file_aux_num(name, 0644, gpio_ctl, wdev, i, + &awcc_gpio_pin_fops); + } + +out_add_action: + devm_add_action_or_reset(&wdev->dev, awcc_debugfs_remove, root); +} + +static int alienware_awcc_setup(struct wmi_device *wdev) +{ + struct awcc_priv *priv; + int ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION, + 0, &priv->system_description); + if (ret < 0) + return ret; + + /* Sanity check */ + for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count); i++) { + if (priv->res_count[i] > AWCC_MAX_RES_COUNT) { + dev_err(&wdev->dev, "Malformed system description: 0x%08x\n", + priv->system_description); + return -ENXIO; + } + } + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + + if (awcc->hwmon) { + ret = awcc_hwmon_init(wdev); + if (ret) + return ret; + } + + if (awcc->pprof) { + ret = awcc_platform_profile_init(wdev); + if (ret) + return ret; + } + + awcc_debugfs_init(wdev); + + return 0; +} + +/* + * WMAX WMI driver + */ +static int wmax_wmi_update_led(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 location) +{ + struct wmax_led_args in_args = { + .led_mask = 1 << location, + .colors = priv->colors[location], + .state = priv->lighting_control_state, + }; + + return alienware_wmi_command(wdev, WMAX_METHOD_ZONE_CONTROL, &in_args, + sizeof(in_args), NULL); +} + +static int wmax_wmi_update_brightness(struct alienfx_priv *priv, + struct wmi_device *wdev, u8 brightness) +{ + struct wmax_brightness_args in_args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + + return alienware_wmi_command(wdev, WMAX_METHOD_BRIGHTNESS, &in_args, + sizeof(in_args), NULL); +} + +static int wmax_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct alienfx_platdata pdata = { + .wdev = wdev, + .ops = { + .upd_led = wmax_wmi_update_led, + .upd_brightness = wmax_wmi_update_brightness, + }, + }; + int ret; + + if (awcc) + ret = alienware_awcc_setup(wdev); + else + ret = alienware_alienfx_setup(&pdata); + + return ret; +} + +static int wmax_wmi_suspend(struct device *dev) +{ + if (awcc->hwmon) + awcc_hwmon_suspend(dev); + + return 0; +} + +static int wmax_wmi_resume(struct device *dev) +{ + if (awcc->hwmon) + awcc_hwmon_resume(dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(wmax_wmi_pm_ops, wmax_wmi_suspend, wmax_wmi_resume); + +static const struct wmi_device_id alienware_wmax_device_id_table[] = { + { WMAX_CONTROL_GUID, NULL }, + { }, +}; +MODULE_DEVICE_TABLE(wmi, alienware_wmax_device_id_table); + +static struct wmi_driver alienware_wmax_wmi_driver = { + .driver = { + .name = "alienware-wmi-wmax", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = pm_sleep_ptr(&wmax_wmi_pm_ops), + }, + .id_table = alienware_wmax_device_id_table, + .probe = wmax_wmi_probe, + .no_singleton = true, +}; + +int __init alienware_wmax_wmi_init(void) +{ + const struct dmi_system_id *id; + + id = dmi_first_match(awcc_dmi_table); + if (id) + awcc = id->driver_data; + + if (force_hwmon) { + if (!awcc) + awcc = &empty_quirks; + + awcc->hwmon = true; + } + + if (force_platform_profile) { + if (!awcc) + awcc = &empty_quirks; + + awcc->pprof = true; + } + + if (force_gmode) { + if (awcc) + awcc->gmode = true; + else + pr_warn("force_gmode requires platform profile support\n"); + } + + return wmi_driver_register(&alienware_wmax_wmi_driver); +} + +void __exit alienware_wmax_wmi_exit(void) +{ + wmi_driver_unregister(&alienware_wmax_wmi_driver); +} diff --git a/drivers/platform/x86/dell/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c deleted file mode 100644 index f5ee62ce1753..000000000000 --- a/drivers/platform/x86/dell/alienware-wmi.c +++ /dev/null @@ -1,842 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Alienware AlienFX control - * - * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/acpi.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/dmi.h> -#include <linux/leds.h> - -#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" -#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" -#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" - -#define WMAX_METHOD_HDMI_SOURCE 0x1 -#define WMAX_METHOD_HDMI_STATUS 0x2 -#define WMAX_METHOD_BRIGHTNESS 0x3 -#define WMAX_METHOD_ZONE_CONTROL 0x4 -#define WMAX_METHOD_HDMI_CABLE 0x5 -#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 -#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B -#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C - -MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); -MODULE_DESCRIPTION("Alienware special feature control"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); -MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); - -enum INTERFACE_FLAGS { - LEGACY, - WMAX, -}; - -enum LEGACY_CONTROL_STATES { - LEGACY_RUNNING = 1, - LEGACY_BOOTING = 0, - LEGACY_SUSPEND = 3, -}; - -enum WMAX_CONTROL_STATES { - WMAX_RUNNING = 0xFF, - WMAX_BOOTING = 0, - WMAX_SUSPEND = 3, -}; - -struct quirk_entry { - u8 num_zones; - u8 hdmi_mux; - u8 amplifier; - u8 deepslp; -}; - -static struct quirk_entry *quirks; - - -static struct quirk_entry quirk_inspiron5675 = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_unknown = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r1_r2 = { - .num_zones = 3, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r3 = { - .num_zones = 4, - .hdmi_mux = 0, - .amplifier = 1, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm100 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm200 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 1, -}; - -static struct quirk_entry quirk_asm201 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 1, - .deepslp = 1, -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - quirks = dmi->driver_data; - return 1; -} - -static const struct dmi_system_id alienware_quirks[] __initconst = { - { - .callback = dmi_matched, - .ident = "Alienware X51 R3", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), - }, - .driver_data = &quirk_x51_r3, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R1", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM100", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), - }, - .driver_data = &quirk_asm100, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM200", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), - }, - .driver_data = &quirk_asm200, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM201", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), - }, - .driver_data = &quirk_asm201, - }, - { - .callback = dmi_matched, - .ident = "Dell Inc. Inspiron 5675", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), - }, - .driver_data = &quirk_inspiron5675, - }, - {} -}; - -struct color_platform { - u8 blue; - u8 green; - u8 red; -} __packed; - -struct platform_zone { - u8 location; - struct device_attribute *attr; - struct color_platform colors; -}; - -struct wmax_brightness_args { - u32 led_mask; - u32 percentage; -}; - -struct wmax_basic_args { - u8 arg; -}; - -struct legacy_led_args { - struct color_platform colors; - u8 brightness; - u8 state; -} __packed; - -struct wmax_led_args { - u32 led_mask; - struct color_platform colors; - u8 state; -} __packed; - -static struct platform_device *platform_device; -static struct device_attribute *zone_dev_attrs; -static struct attribute **zone_attrs; -static struct platform_zone *zone_data; - -static struct platform_driver platform_driver = { - .driver = { - .name = "alienware-wmi", - } -}; - -static struct attribute_group zone_attribute_group = { - .name = "rgb_zones", -}; - -static u8 interface; -static u8 lighting_control_state; -static u8 global_brightness; - -/* - * Helpers used for zone control - */ -static int parse_rgb(const char *buf, struct platform_zone *zone) -{ - long unsigned int rgb; - int ret; - union color_union { - struct color_platform cp; - int package; - } repackager; - - ret = kstrtoul(buf, 16, &rgb); - if (ret) - return ret; - - /* RGB triplet notation is 24-bit hexadecimal */ - if (rgb > 0xFFFFFF) - return -EINVAL; - - repackager.package = rgb & 0x0f0f0f0f; - pr_debug("alienware-wmi: r: %d g:%d b: %d\n", - repackager.cp.red, repackager.cp.green, repackager.cp.blue); - zone->colors = repackager.cp; - return 0; -} - -static struct platform_zone *match_zone(struct device_attribute *attr) -{ - u8 zone; - - for (zone = 0; zone < quirks->num_zones; zone++) { - if ((struct device_attribute *)zone_data[zone].attr == attr) { - pr_debug("alienware-wmi: matched zone location: %d\n", - zone_data[zone].location); - return &zone_data[zone]; - } - } - return NULL; -} - -/* - * Individual RGB zone control - */ -static int alienware_update_led(struct platform_zone *zone) -{ - int method_id; - acpi_status status; - char *guid; - struct acpi_buffer input; - struct legacy_led_args legacy_args; - struct wmax_led_args wmax_basic_args; - if (interface == WMAX) { - wmax_basic_args.led_mask = 1 << zone->location; - wmax_basic_args.colors = zone->colors; - wmax_basic_args.state = lighting_control_state; - guid = WMAX_CONTROL_GUID; - method_id = WMAX_METHOD_ZONE_CONTROL; - - input.length = (acpi_size) sizeof(wmax_basic_args); - input.pointer = &wmax_basic_args; - } else { - legacy_args.colors = zone->colors; - legacy_args.brightness = global_brightness; - legacy_args.state = 0; - if (lighting_control_state == LEGACY_BOOTING || - lighting_control_state == LEGACY_SUSPEND) { - guid = LEGACY_POWER_CONTROL_GUID; - legacy_args.state = lighting_control_state; - } else - guid = LEGACY_CONTROL_GUID; - method_id = zone->location + 1; - - input.length = (acpi_size) sizeof(legacy_args); - input.pointer = &legacy_args; - } - pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); - - status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: zone set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static ssize_t zone_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct platform_zone *target_zone; - target_zone = match_zone(attr); - if (target_zone == NULL) - return sprintf(buf, "red: -1, green: -1, blue: -1\n"); - return sprintf(buf, "red: %d, green: %d, blue: %d\n", - target_zone->colors.red, - target_zone->colors.green, target_zone->colors.blue); - -} - -static ssize_t zone_set(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct platform_zone *target_zone; - int ret; - target_zone = match_zone(attr); - if (target_zone == NULL) { - pr_err("alienware-wmi: invalid target zone\n"); - return 1; - } - ret = parse_rgb(buf, target_zone); - if (ret) - return ret; - ret = alienware_update_led(target_zone); - return ret ? ret : count; -} - -/* - * LED Brightness (Global) - */ -static int wmax_brightness(int brightness) -{ - acpi_status status; - struct acpi_buffer input; - struct wmax_brightness_args args = { - .led_mask = 0xFF, - .percentage = brightness, - }; - input.length = (acpi_size) sizeof(args); - input.pointer = &args; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - WMAX_METHOD_BRIGHTNESS, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: brightness set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static void global_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - int ret; - global_brightness = brightness; - if (interface == WMAX) - ret = wmax_brightness(brightness); - else - ret = alienware_update_led(&zone_data[0]); - if (ret) - pr_err("LED brightness update failed\n"); -} - -static enum led_brightness global_led_get(struct led_classdev *led_cdev) -{ - return global_brightness; -} - -static struct led_classdev global_led = { - .brightness_set = global_led_set, - .brightness_get = global_led_get, - .name = "alienware::global_brightness", -}; - -/* - * Lighting control state device attribute (Global) - */ -static ssize_t show_control_state(struct device *dev, - struct device_attribute *attr, char *buf) -{ - if (lighting_control_state == LEGACY_BOOTING) - return sysfs_emit(buf, "[booting] running suspend\n"); - else if (lighting_control_state == LEGACY_SUSPEND) - return sysfs_emit(buf, "booting running [suspend]\n"); - return sysfs_emit(buf, "booting [running] suspend\n"); -} - -static ssize_t store_control_state(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - long unsigned int val; - if (strcmp(buf, "booting\n") == 0) - val = LEGACY_BOOTING; - else if (strcmp(buf, "suspend\n") == 0) - val = LEGACY_SUSPEND; - else if (interface == LEGACY) - val = LEGACY_RUNNING; - else - val = WMAX_RUNNING; - lighting_control_state = val; - pr_debug("alienware-wmi: updated control state to %d\n", - lighting_control_state); - return count; -} - -static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, - store_control_state); - -static int alienware_zone_init(struct platform_device *dev) -{ - u8 zone; - char *name; - - if (interface == WMAX) { - lighting_control_state = WMAX_RUNNING; - } else if (interface == LEGACY) { - lighting_control_state = LEGACY_RUNNING; - } - global_led.max_brightness = 0x0F; - global_brightness = global_led.max_brightness; - - /* - * - zone_dev_attrs num_zones + 1 is for individual zones and then - * null terminated - * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + - * the lighting control + null terminated - * - zone_data num_zones is for the distinct zones - */ - zone_dev_attrs = - kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), - GFP_KERNEL); - if (!zone_dev_attrs) - return -ENOMEM; - - zone_attrs = - kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), - GFP_KERNEL); - if (!zone_attrs) - return -ENOMEM; - - zone_data = - kcalloc(quirks->num_zones, sizeof(struct platform_zone), - GFP_KERNEL); - if (!zone_data) - return -ENOMEM; - - for (zone = 0; zone < quirks->num_zones; zone++) { - name = kasprintf(GFP_KERNEL, "zone%02hhX", zone); - if (name == NULL) - return 1; - sysfs_attr_init(&zone_dev_attrs[zone].attr); - zone_dev_attrs[zone].attr.name = name; - zone_dev_attrs[zone].attr.mode = 0644; - zone_dev_attrs[zone].show = zone_show; - zone_dev_attrs[zone].store = zone_set; - zone_data[zone].location = zone; - zone_attrs[zone] = &zone_dev_attrs[zone].attr; - zone_data[zone].attr = &zone_dev_attrs[zone]; - } - zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; - zone_attribute_group.attrs = zone_attrs; - - led_classdev_register(&dev->dev, &global_led); - - return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); -} - -static void alienware_zone_exit(struct platform_device *dev) -{ - u8 zone; - - sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); - led_classdev_unregister(&global_led); - if (zone_dev_attrs) { - for (zone = 0; zone < quirks->num_zones; zone++) - kfree(zone_dev_attrs[zone].attr.name); - } - kfree(zone_dev_attrs); - kfree(zone_data); - kfree(zone_attrs); -} - -static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, - u32 command, int *out_data) -{ - acpi_status status; - union acpi_object *obj; - struct acpi_buffer input; - struct acpi_buffer output; - - input.length = (acpi_size) sizeof(*in_args); - input.pointer = in_args; - if (out_data) { - output.length = ACPI_ALLOCATE_BUFFER; - output.pointer = NULL; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, &output); - if (ACPI_SUCCESS(status)) { - obj = (union acpi_object *)output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - *out_data = (u32)obj->integer.value; - } - kfree(output.pointer); - } else { - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, NULL); - } - return status; -} - -/* - * The HDMI mux sysfs node indicates the status of the HDMI input mux. - * It can toggle between standard system GPU output and HDMI input. - */ -static ssize_t show_hdmi_cable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return sysfs_emit(buf, "[unconnected] connected unknown\n"); - else if (out_data == 1) - return sysfs_emit(buf, "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); - return sysfs_emit(buf, "unconnected connected [unknown]\n"); -} - -static ssize_t show_hdmi_source(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS, - (u32 *) &out_data); - - if (ACPI_SUCCESS(status)) { - if (out_data == 1) - return sysfs_emit(buf, "[input] gpu unknown\n"); - else if (out_data == 2) - return sysfs_emit(buf, "input [gpu] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); - return sysfs_emit(buf, "input gpu [unknown]\n"); -} - -static ssize_t toggle_hdmi_source(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - if (strcmp(buf, "gpu\n") == 0) - args.arg = 1; - else if (strcmp(buf, "input\n") == 0) - args.arg = 2; - else - args.arg = 3; - pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); -static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, - toggle_hdmi_source); - -static struct attribute *hdmi_attrs[] = { - &dev_attr_cable.attr, - &dev_attr_source.attr, - NULL, -}; - -static const struct attribute_group hdmi_attribute_group = { - .name = "hdmi", - .attrs = hdmi_attrs, -}; - -static void remove_hdmi(struct platform_device *dev) -{ - if (quirks->hdmi_mux > 0) - sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); -} - -static int create_hdmi(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); - if (ret) - remove_hdmi(dev); - return ret; -} - -/* - * Alienware GFX amplifier support - * - Currently supports reading cable status - * - Leaving expansion room to possibly support dock/undock events later - */ -static ssize_t show_amplifier_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return sysfs_emit(buf, "[unconnected] connected unknown\n"); - else if (out_data == 1) - return sysfs_emit(buf, "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); - return sysfs_emit(buf, "unconnected connected [unknown]\n"); -} - -static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); - -static struct attribute *amplifier_attrs[] = { - &dev_attr_status.attr, - NULL, -}; - -static const struct attribute_group amplifier_attribute_group = { - .name = "amplifier", - .attrs = amplifier_attrs, -}; - -static void remove_amplifier(struct platform_device *dev) -{ - if (quirks->amplifier > 0) - sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); -} - -static int create_amplifier(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); - if (ret) - remove_amplifier(dev); - return ret; -} - -/* - * Deep Sleep Control support - * - Modifies BIOS setting for deep sleep control allowing extra wakeup events - */ -static ssize_t show_deepsleep_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return sysfs_emit(buf, "[disabled] s5 s5_s4\n"); - else if (out_data == 1) - return sysfs_emit(buf, "disabled [s5] s5_s4\n"); - else if (out_data == 2) - return sysfs_emit(buf, "disabled s5 [s5_s4]\n"); - } - pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); - return sysfs_emit(buf, "disabled s5 s5_s4 [unknown]\n"); -} - -static ssize_t toggle_deepsleep(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - - if (strcmp(buf, "disabled\n") == 0) - args.arg = 0; - else if (strcmp(buf, "s5\n") == 0) - args.arg = 1; - else - args.arg = 2; - pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL, - NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: deep sleep control failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); - -static struct attribute *deepsleep_attrs[] = { - &dev_attr_deepsleep.attr, - NULL, -}; - -static const struct attribute_group deepsleep_attribute_group = { - .name = "deepsleep", - .attrs = deepsleep_attrs, -}; - -static void remove_deepsleep(struct platform_device *dev) -{ - if (quirks->deepslp > 0) - sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); -} - -static int create_deepsleep(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); - if (ret) - remove_deepsleep(dev); - return ret; -} - -static int __init alienware_wmi_init(void) -{ - int ret; - - if (wmi_has_guid(LEGACY_CONTROL_GUID)) - interface = LEGACY; - else if (wmi_has_guid(WMAX_CONTROL_GUID)) - interface = WMAX; - else { - pr_warn("alienware-wmi: No known WMI GUID found\n"); - return -ENODEV; - } - - dmi_check_system(alienware_quirks); - if (quirks == NULL) - quirks = &quirk_unknown; - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - platform_device = platform_device_alloc("alienware-wmi", PLATFORM_DEVID_NONE); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device2; - - if (quirks->hdmi_mux > 0) { - ret = create_hdmi(platform_device); - if (ret) - goto fail_prep_hdmi; - } - - if (quirks->amplifier > 0) { - ret = create_amplifier(platform_device); - if (ret) - goto fail_prep_amplifier; - } - - if (quirks->deepslp > 0) { - ret = create_deepsleep(platform_device); - if (ret) - goto fail_prep_deepsleep; - } - - ret = alienware_zone_init(platform_device); - if (ret) - goto fail_prep_zones; - - return 0; - -fail_prep_zones: - alienware_zone_exit(platform_device); -fail_prep_deepsleep: -fail_prep_amplifier: -fail_prep_hdmi: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - return ret; -} - -module_init(alienware_wmi_init); - -static void __exit alienware_wmi_exit(void) -{ - if (platform_device) { - alienware_zone_exit(platform_device); - remove_hdmi(platform_device); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } -} - -module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dell/alienware-wmi.h b/drivers/platform/x86/dell/alienware-wmi.h new file mode 100644 index 000000000000..68d4242211ae --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Alienware WMI special features driver + * + * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com> + * Copyright (C) 2024 Kurt Borja <kuurtb@gmail.com> + */ + +#ifndef _ALIENWARE_WMI_H_ +#define _ALIENWARE_WMI_H_ + +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/wmi.h> + +#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" +#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" +#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +enum INTERFACE_FLAGS { + LEGACY, + WMAX, +}; + +enum LEGACY_CONTROL_STATES { + LEGACY_RUNNING = 1, + LEGACY_BOOTING = 0, + LEGACY_SUSPEND = 3, +}; + +enum WMAX_CONTROL_STATES { + WMAX_RUNNING = 0xFF, + WMAX_BOOTING = 0, + WMAX_SUSPEND = 3, +}; + +struct alienfx_quirks { + u8 num_zones; + bool hdmi_mux; + bool amplifier; + bool deepslp; +}; + +struct color_platform { + u8 blue; + u8 green; + u8 red; +} __packed; + +struct alienfx_priv { + struct platform_device *pdev; + struct led_classdev global_led; + struct color_platform colors[4]; + u8 global_brightness; + u8 lighting_control_state; +}; + +struct alienfx_ops { + int (*upd_led)(struct alienfx_priv *priv, struct wmi_device *wdev, + u8 location); + int (*upd_brightness)(struct alienfx_priv *priv, struct wmi_device *wdev, + u8 brightness); +}; + +struct alienfx_platdata { + struct wmi_device *wdev; + struct alienfx_ops ops; +}; + +extern u8 alienware_interface; +extern struct alienfx_quirks *alienfx; + +int alienware_wmi_command(struct wmi_device *wdev, u32 method_id, + void *in_args, size_t in_size, u32 *out_data); + +int alienware_alienfx_setup(struct alienfx_platdata *pdata); + +#if IS_ENABLED(CONFIG_ALIENWARE_WMI_LEGACY) +int __init alienware_legacy_wmi_init(void); +void __exit alienware_legacy_wmi_exit(void); +#else +static inline int alienware_legacy_wmi_init(void) +{ + return -ENODEV; +} + +static inline void alienware_legacy_wmi_exit(void) +{ +} +#endif + +#if IS_ENABLED(CONFIG_ALIENWARE_WMI_WMAX) +extern const struct attribute_group wmax_hdmi_attribute_group; +extern const struct attribute_group wmax_amplifier_attribute_group; +extern const struct attribute_group wmax_deepsleep_attribute_group; + +#define WMAX_DEV_GROUPS &wmax_hdmi_attribute_group, \ + &wmax_amplifier_attribute_group, \ + &wmax_deepsleep_attribute_group, + +int __init alienware_wmax_wmi_init(void); +void __exit alienware_wmax_wmi_exit(void); +#else +#define WMAX_DEV_GROUPS + +static inline int alienware_wmax_wmi_init(void) +{ + return -ENODEV; +} + + +static inline void alienware_wmax_wmi_exit(void) +{ +} +#endif + +#endif diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index a60e35056387..8149be25fa26 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -29,6 +29,7 @@ #include <linux/smp.h> #include <linux/spinlock.h> #include <linux/string.h> +#include <linux/sysfs.h> #include <linux/types.h> #include <linux/mutex.h> @@ -132,14 +133,14 @@ static ssize_t smi_data_buf_phys_addr_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%x\n", (u32)smi_buf.dma); + return sysfs_emit(buf, "%x\n", (u32)smi_buf.dma); } static ssize_t smi_data_buf_size_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%lu\n", smi_buf.size); + return sysfs_emit(buf, "%lu\n", smi_buf.size); } static ssize_t smi_data_buf_size_store(struct device *dev, @@ -162,7 +163,7 @@ static ssize_t smi_data_buf_size_store(struct device *dev, } static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { ssize_t ret; @@ -175,7 +176,7 @@ static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, } static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { ssize_t ret; @@ -200,7 +201,7 @@ static ssize_t host_control_action_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%u\n", host_control_action); + return sysfs_emit(buf, "%u\n", host_control_action); } static ssize_t host_control_action_store(struct device *dev, @@ -224,7 +225,7 @@ static ssize_t host_control_smi_type_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%u\n", host_control_smi_type); + return sysfs_emit(buf, "%u\n", host_control_smi_type); } static ssize_t host_control_smi_type_store(struct device *dev, @@ -239,7 +240,7 @@ static ssize_t host_control_on_shutdown_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%u\n", host_control_on_shutdown); + return sysfs_emit(buf, "%u\n", host_control_on_shutdown); } static ssize_t host_control_on_shutdown_store(struct device *dev, @@ -635,9 +636,9 @@ static struct notifier_block dcdbas_reboot_nb = { .priority = INT_MIN }; -static DCDBAS_BIN_ATTR_RW(smi_data); +static const BIN_ATTR_ADMIN_RW(smi_data, 0); -static struct bin_attribute *dcdbas_bin_attrs[] = { +static const struct bin_attribute *const dcdbas_bin_attrs[] = { &bin_attr_smi_data, NULL }; @@ -661,7 +662,7 @@ static struct attribute *dcdbas_dev_attrs[] = { static const struct attribute_group dcdbas_attr_group = { .attrs = dcdbas_dev_attrs, - .bin_attrs = dcdbas_bin_attrs, + .bin_attrs_new = dcdbas_bin_attrs, }; static int dcdbas_probe(struct platform_device *dev) @@ -709,7 +710,7 @@ static struct platform_driver dcdbas_driver = { .name = DRIVER_NAME, }, .probe = dcdbas_probe, - .remove_new = dcdbas_remove, + .remove = dcdbas_remove, }; static const struct platform_device_info dcdbas_dev_info __initconst = { diff --git a/drivers/platform/x86/dell/dcdbas.h b/drivers/platform/x86/dell/dcdbas.h index 942a23ddded0..a05d7f667586 100644 --- a/drivers/platform/x86/dell/dcdbas.h +++ b/drivers/platform/x86/dell/dcdbas.h @@ -56,14 +56,6 @@ #define DCDBAS_DEV_ATTR_WO(_name) \ DEVICE_ATTR(_name,0200,NULL,_name##_store); -#define DCDBAS_BIN_ATTR_RW(_name) \ -struct bin_attribute bin_attr_##_name = { \ - .attr = { .name = __stringify(_name), \ - .mode = 0600 }, \ - .read = _name##_read, \ - .write = _name##_write, \ -} - struct smi_cmd { __u32 magic; __u32 ebx; diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c index 6586438356de..57748c3ea24f 100644 --- a/drivers/platform/x86/dell/dell-laptop.c +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -22,11 +22,13 @@ #include <linux/io.h> #include <linux/rfkill.h> #include <linux/power_supply.h> +#include <linux/sysfs.h> #include <linux/acpi.h> #include <linux/mm.h> #include <linux/i8042.h> #include <linux/debugfs.h> #include <linux/seq_file.h> +#include <acpi/battery.h> #include <acpi/video.h> #include "dell-rbtn.h" #include "dell-smbios.h" @@ -99,6 +101,20 @@ static bool force_rfkill; static bool micmute_led_registered; static bool mute_led_registered; +struct battery_mode_info { + int token; + enum power_supply_charge_type charge_type; +}; + +static const struct battery_mode_info battery_modes[] = { + { BAT_PRI_AC_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_TRICKLE }, + { BAT_EXPRESS_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_FAST }, + { BAT_STANDARD_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_STANDARD }, + { BAT_ADAPTIVE_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE }, + { BAT_CUSTOM_MODE_TOKEN, POWER_SUPPLY_CHARGE_TYPE_CUSTOM }, +}; +static u32 battery_supported_modes; + module_param(force_rfkill, bool, 0444); MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); @@ -353,27 +369,30 @@ static const struct dmi_system_id dell_quirks[] __initconst = { { } }; -static void dell_fill_request(struct calling_interface_buffer *buffer, - u32 arg0, u32 arg1, u32 arg2, u32 arg3) +/* -1 is a sentinel value, telling us to use token->value */ +#define USE_TVAL ((u32) -1) +static int dell_send_request_for_tokenid(struct calling_interface_buffer *buffer, + u16 class, u16 select, u16 tokenid, + u32 val) { - memset(buffer, 0, sizeof(struct calling_interface_buffer)); - buffer->input[0] = arg0; - buffer->input[1] = arg1; - buffer->input[2] = arg2; - buffer->input[3] = arg3; + struct calling_interface_token *token; + + token = dell_smbios_find_token(tokenid); + if (!token) + return -ENODEV; + + if (val == USE_TVAL) + val = token->value; + + dell_fill_request(buffer, token->location, val, 0, 0); + return dell_send_request(buffer, class, select); } -static int dell_send_request(struct calling_interface_buffer *buffer, - u16 class, u16 select) +static inline int dell_set_std_token_value(struct calling_interface_buffer *buffer, + u16 tokenid, u32 value) { - int ret; - - buffer->cmd_class = class; - buffer->cmd_select = select; - ret = dell_smbios_call(buffer); - if (ret != 0) - return ret; - return dell_smbios_error(buffer->output[0]); + return dell_send_request_for_tokenid(buffer, CLASS_TOKEN_WRITE, + SELECT_TOKEN_STD, tokenid, value); } /* @@ -706,8 +725,8 @@ static void dell_update_rfkill(struct work_struct *ignored) } static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); -static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, - struct serio *port) +static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, struct serio *port, + void *context) { static bool extended; @@ -865,7 +884,7 @@ static int __init dell_setup_rfkill(void) pr_warn("Unable to register dell rbtn notifier\n"); goto err_filter; } else { - ret = i8042_install_filter(dell_laptop_i8042_filter); + ret = i8042_install_filter(dell_laptop_i8042_filter, NULL); if (ret) { pr_warn("Unable to install key filter\n"); goto err_filter; @@ -918,43 +937,24 @@ static void dell_cleanup_rfkill(void) static int dell_send_intensity(struct backlight_device *bd) { struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, - token->location, bd->props.brightness, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); + u16 select; - return ret; + select = power_supply_is_system_supplied() > 0 ? + SELECT_TOKEN_AC : SELECT_TOKEN_BAT; + return dell_send_request_for_tokenid(&buffer, CLASS_TOKEN_WRITE, + select, BRIGHTNESS_TOKEN, bd->props.brightness); } static int dell_get_intensity(struct backlight_device *bd) { struct calling_interface_buffer buffer; - struct calling_interface_token *token; int ret; + u16 select; - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_BAT); - + select = power_supply_is_system_supplied() > 0 ? + SELECT_TOKEN_AC : SELECT_TOKEN_BAT; + ret = dell_send_request_for_tokenid(&buffer, CLASS_TOKEN_READ, + select, BRIGHTNESS_TOKEN, 0); if (ret == 0) ret = buffer.output[1]; @@ -1378,20 +1378,11 @@ static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) static int kbd_set_token_bit(u8 bit) { struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; if (bit >= ARRAY_SIZE(kbd_tokens)) return -EINVAL; - token = dell_smbios_find_token(kbd_tokens[bit]); - if (!token) - return -EINVAL; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - - return ret; + return dell_set_std_token_value(&buffer, kbd_tokens[bit], USE_TVAL); } static int kbd_get_token_bit(u8 bit) @@ -1410,11 +1401,10 @@ static int kbd_get_token_bit(u8 bit) dell_fill_request(&buffer, token->location, 0, 0, 0); ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); - val = buffer.output[1]; - if (ret) return ret; + val = buffer.output[1]; return (val == token->value); } @@ -1520,7 +1510,7 @@ static inline int kbd_init_info(void) } -static inline void kbd_init_tokens(void) +static inline void __init kbd_init_tokens(void) { int i; @@ -1529,7 +1519,7 @@ static inline void kbd_init_tokens(void) kbd_token_bits |= BIT(i); } -static void kbd_init(void) +static void __init kbd_init(void) { int ret; @@ -2154,21 +2144,11 @@ static int micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int state = brightness != LED_OFF; - - if (state == 0) - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); - else - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); - - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + u32 tokenid; - return 0; + tokenid = brightness == LED_OFF ? + GLOBAL_MIC_MUTE_DISABLE : GLOBAL_MIC_MUTE_ENABLE; + return dell_set_std_token_value(&buffer, tokenid, USE_TVAL); } static struct led_classdev micmute_led_cdev = { @@ -2182,21 +2162,11 @@ static int mute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int state = brightness != LED_OFF; - - if (state == 0) - token = dell_smbios_find_token(GLOBAL_MUTE_DISABLE); - else - token = dell_smbios_find_token(GLOBAL_MUTE_ENABLE); - - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + u32 tokenid; - return 0; + tokenid = brightness == LED_OFF ? + GLOBAL_MUTE_DISABLE : GLOBAL_MUTE_ENABLE; + return dell_set_std_token_value(&buffer, tokenid, USE_TVAL); } static struct led_classdev mute_led_cdev = { @@ -2206,9 +2176,279 @@ static struct led_classdev mute_led_cdev = { .default_trigger = "audio-mute", }; -static int __init dell_init(void) +static int dell_battery_set_mode(const u16 tokenid) +{ + struct calling_interface_buffer buffer; + + return dell_set_std_token_value(&buffer, tokenid, USE_TVAL); +} + +static int dell_battery_read(const u16 tokenid) +{ + struct calling_interface_buffer buffer; + int err; + + err = dell_send_request_for_tokenid(&buffer, CLASS_TOKEN_READ, + SELECT_TOKEN_STD, tokenid, 0); + if (err) + return err; + + if (buffer.output[1] > INT_MAX) + return -EIO; + + return buffer.output[1]; +} + +static bool dell_battery_mode_is_active(const u16 tokenid) { struct calling_interface_token *token; + int ret; + + ret = dell_battery_read(tokenid); + if (ret < 0) + return false; + + token = dell_smbios_find_token(tokenid); + /* token's already verified by dell_battery_read() */ + + return token->value == (u16) ret; +} + +/* + * The rules: the minimum start charging value is 50%. The maximum + * start charging value is 95%. The minimum end charging value is + * 55%. The maximum end charging value is 100%. And finally, there + * has to be at least a 5% difference between start & end values. + */ +#define CHARGE_START_MIN 50 +#define CHARGE_START_MAX 95 +#define CHARGE_END_MIN 55 +#define CHARGE_END_MAX 100 +#define CHARGE_MIN_DIFF 5 + +static int dell_battery_set_custom_charge_start(int start) +{ + struct calling_interface_buffer buffer; + int end; + + start = clamp(start, CHARGE_START_MIN, CHARGE_START_MAX); + end = dell_battery_read(BAT_CUSTOM_CHARGE_END); + if (end < 0) + return end; + if ((end - start) < CHARGE_MIN_DIFF) + start = end - CHARGE_MIN_DIFF; + + return dell_set_std_token_value(&buffer, BAT_CUSTOM_CHARGE_START, + start); +} + +static int dell_battery_set_custom_charge_end(int end) +{ + struct calling_interface_buffer buffer; + int start; + + end = clamp(end, CHARGE_END_MIN, CHARGE_END_MAX); + start = dell_battery_read(BAT_CUSTOM_CHARGE_START); + if (start < 0) + return start; + if ((end - start) < CHARGE_MIN_DIFF) + end = start + CHARGE_MIN_DIFF; + + return dell_set_std_token_value(&buffer, BAT_CUSTOM_CHARGE_END, end); +} + +static ssize_t charge_types_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + enum power_supply_charge_type charge_type; + int i; + + for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { + charge_type = battery_modes[i].charge_type; + + if (!(battery_supported_modes & BIT(charge_type))) + continue; + + if (!dell_battery_mode_is_active(battery_modes[i].token)) + continue; + + return power_supply_charge_types_show(dev, battery_supported_modes, + charge_type, buf); + } + + /* No active mode found */ + return -EIO; +} + +static ssize_t charge_types_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int charge_type, err, i; + + charge_type = power_supply_charge_types_parse(battery_supported_modes, buf); + if (charge_type < 0) + return charge_type; + + for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { + if (battery_modes[i].charge_type == charge_type) + break; + } + if (i == ARRAY_SIZE(battery_modes)) + return -ENOENT; + + err = dell_battery_set_mode(battery_modes[i].token); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int start; + + start = dell_battery_read(BAT_CUSTOM_CHARGE_START); + if (start < 0) + return start; + + if (start > CHARGE_START_MAX) + return -EIO; + + return sysfs_emit(buf, "%d\n", start); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, start; + + ret = kstrtoint(buf, 10, &start); + if (ret) + return ret; + if (start < 0 || start > 100) + return -EINVAL; + + ret = dell_battery_set_custom_charge_start(start); + if (ret) + return ret; + + return size; +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int end; + + end = dell_battery_read(BAT_CUSTOM_CHARGE_END); + if (end < 0) + return end; + + if (end > CHARGE_END_MAX) + return -EIO; + + return sysfs_emit(buf, "%d\n", end); +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, end; + + ret = kstrtouint(buf, 10, &end); + if (ret) + return ret; + if (end < 0 || end > 100) + return -EINVAL; + + ret = dell_battery_set_custom_charge_end(end); + if (ret) + return ret; + + return size; +} + +static DEVICE_ATTR_RW(charge_control_start_threshold); +static DEVICE_ATTR_RW(charge_control_end_threshold); +static DEVICE_ATTR_RW(charge_types); + +static struct attribute *dell_battery_attrs[] = { + &dev_attr_charge_control_start_threshold.attr, + &dev_attr_charge_control_end_threshold.attr, + &dev_attr_charge_types.attr, + NULL, +}; +ATTRIBUTE_GROUPS(dell_battery); + +static bool dell_battery_supported(struct power_supply *battery) +{ + /* We currently only support the primary battery */ + return strcmp(battery->desc->name, "BAT0") == 0; +} + +static int dell_battery_add(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + /* Return 0 instead of an error to avoid being unloaded */ + if (!dell_battery_supported(battery)) + return 0; + + return device_add_groups(&battery->dev, dell_battery_groups); +} + +static int dell_battery_remove(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + if (!dell_battery_supported(battery)) + return 0; + + device_remove_groups(&battery->dev, dell_battery_groups); + return 0; +} + +static struct acpi_battery_hook dell_battery_hook = { + .add_battery = dell_battery_add, + .remove_battery = dell_battery_remove, + .name = "Dell Primary Battery Extension", +}; + +static u32 __init battery_get_supported_modes(void) +{ + u32 modes = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(battery_modes); i++) { + if (dell_smbios_find_token(battery_modes[i].token)) + modes |= BIT(battery_modes[i].charge_type); + } + + return modes; +} + +static void __init dell_battery_init(struct device *dev) +{ + battery_supported_modes = battery_get_supported_modes(); + + if (battery_supported_modes != 0) + battery_hook_register(&dell_battery_hook); +} + +static void dell_battery_exit(void) +{ + if (battery_supported_modes != 0) + battery_hook_unregister(&dell_battery_hook); +} + +static int __init dell_init(void) +{ + struct calling_interface_buffer buffer; int max_intensity = 0; int ret; @@ -2242,6 +2482,7 @@ static int __init dell_init(void) touchpad_led_init(&platform_device->dev); kbd_led_init(&platform_device->dev); + dell_battery_init(&platform_device->dev); dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, @@ -2252,7 +2493,6 @@ static int __init dell_init(void) if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE) && !dell_privacy_has_mic_mute()) { - micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); if (ret < 0) goto fail_led; @@ -2261,7 +2501,6 @@ static int __init dell_init(void) if (dell_smbios_find_token(GLOBAL_MUTE_DISABLE) && dell_smbios_find_token(GLOBAL_MUTE_ENABLE)) { - mute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MUTE); ret = led_classdev_register(&platform_device->dev, &mute_led_cdev); if (ret < 0) goto fail_backlight; @@ -2271,16 +2510,10 @@ static int __init dell_init(void) if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return 0; - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (token) { - struct calling_interface_buffer buffer; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - if (ret == 0) - max_intensity = buffer.output[3]; - } + ret = dell_send_request_for_tokenid(&buffer, CLASS_TOKEN_READ, + SELECT_TOKEN_AC, BRIGHTNESS_TOKEN, 0); + if (ret == 0) + max_intensity = buffer.output[3]; if (max_intensity) { struct backlight_properties props; @@ -2318,6 +2551,7 @@ fail_backlight: if (mute_led_registered) led_classdev_unregister(&mute_led_cdev); fail_led: + dell_battery_exit(); dell_cleanup_rfkill(); fail_rfkill: platform_device_del(platform_device); @@ -2336,6 +2570,7 @@ static void __exit dell_exit(void) if (quirks && quirks->touchpad_led) touchpad_led_exit(); kbd_led_exit(); + dell_battery_exit(); backlight_device_unregister(dell_backlight_device); if (micmute_led_registered) led_classdev_unregister(&micmute_led_cdev); diff --git a/drivers/platform/x86/dell/dell-lis3lv02d.c b/drivers/platform/x86/dell/dell-lis3lv02d.c new file mode 100644 index 000000000000..efe26d667973 --- /dev/null +++ b/drivers/platform/x86/dell/dell-lis3lv02d.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * lis3lv02d i2c-client instantiation for ACPI SMO88xx devices without I2C resources. + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/device/bus.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include "dell-smo8800-ids.h" + +#define LIS3_WHO_AM_I 0x0f + +#define DELL_LIS3LV02D_DMI_ENTRY(product_name, i2c_addr) \ + { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), \ + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, product_name), \ + }, \ + .driver_data = (void *)(uintptr_t)(i2c_addr), \ + } + +/* + * Accelerometer's I2C address is not specified in DMI nor ACPI, + * so it is needed to define mapping table based on DMI product names. + */ +static const struct dmi_system_id lis3lv02d_devices[] __initconst = { + /* + * Dell platform team told us that these Latitude devices have + * ST microelectronics accelerometer at I2C address 0x29. + */ + DELL_LIS3LV02D_DMI_ENTRY("Latitude E5250", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E5450", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E5550", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E6440 ATG", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E6540", 0x29), + /* + * Additional individual entries were added after verification. + */ + DELL_LIS3LV02D_DMI_ENTRY("Latitude 5480", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E6330", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Latitude E6430", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Precision 3540", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Precision M6800", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("Vostro V131", 0x1d), + DELL_LIS3LV02D_DMI_ENTRY("Vostro 5568", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("XPS 15 7590", 0x29), + DELL_LIS3LV02D_DMI_ENTRY("XPS 15 9550", 0x29), + { } +}; + +static u8 i2c_addr; +static struct i2c_client *i2c_dev; +static bool notifier_registered; + +static bool probe_i2c_addr; +module_param(probe_i2c_addr, bool, 0444); +MODULE_PARM_DESC(probe_i2c_addr, "Probe the i801 I2C bus for the accelerometer on models where the address is unknown, this may be dangerous."); + +static int detect_lis3lv02d(struct i2c_adapter *adap, unsigned short addr) +{ + union i2c_smbus_data smbus_data; + int err; + + dev_info(&adap->dev, "Probing for lis3lv02d on address 0x%02x\n", addr); + + err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, LIS3_WHO_AM_I, + I2C_SMBUS_BYTE_DATA, &smbus_data); + if (err < 0) + return 0; /* Not found */ + + /* valid who-am-i values are from drivers/misc/lis3lv02d/lis3lv02d.c */ + switch (smbus_data.byte) { + case 0x32: + case 0x33: + case 0x3a: + case 0x3b: + break; + default: + dev_warn(&adap->dev, "Unknown who-am-i register value 0x%02x\n", + smbus_data.byte); + return 0; /* Not found */ + } + + dev_info(&adap->dev, + "Detected lis3lv02d on address 0x%02x, please report this upstream to platform-driver-x86@vger.kernel.org so that a quirk can be added\n", + addr); + + return 1; /* Found */ +} + +static bool i2c_adapter_is_main_i801(struct i2c_adapter *adap) +{ + /* + * Only match the main I801 adapter and reject secondary adapters + * which names start with "SMBus I801 IDF adapter". + */ + return strstarts(adap->name, "SMBus I801 adapter"); +} + +static int find_i801(struct device *dev, void *data) +{ + struct i2c_adapter *adap, **adap_ret = data; + + adap = i2c_verify_adapter(dev); + if (!adap) + return 0; + + if (!i2c_adapter_is_main_i801(adap)) + return 0; + + *adap_ret = i2c_get_adapter(adap->nr); + return 1; +} + +static void instantiate_i2c_client(struct work_struct *work) +{ + struct i2c_board_info info = { }; + struct i2c_adapter *adap = NULL; + + if (i2c_dev) + return; + + /* + * bus_for_each_dev() and not i2c_for_each_dev() to avoid + * a deadlock when find_i801() calls i2c_get_adapter(). + */ + bus_for_each_dev(&i2c_bus_type, NULL, &adap, find_i801); + if (!adap) + return; + + strscpy(info.type, "lis3lv02d", I2C_NAME_SIZE); + + if (i2c_addr) { + info.addr = i2c_addr; + i2c_dev = i2c_new_client_device(adap, &info); + } else { + /* First try address 0x29 (most used) and then try 0x1d */ + static const unsigned short addr_list[] = { 0x29, 0x1d, I2C_CLIENT_END }; + + i2c_dev = i2c_new_scanned_device(adap, &info, addr_list, detect_lis3lv02d); + } + + if (IS_ERR(i2c_dev)) { + dev_err(&adap->dev, "error %ld registering i2c_client\n", PTR_ERR(i2c_dev)); + i2c_dev = NULL; + } else { + dev_dbg(&adap->dev, "registered lis3lv02d on address 0x%02x\n", info.addr); + } + + i2c_put_adapter(adap); +} +static DECLARE_WORK(i2c_work, instantiate_i2c_client); + +static int i2c_bus_notify(struct notifier_block *nb, unsigned long action, void *data) +{ + struct device *dev = data; + struct i2c_client *client; + struct i2c_adapter *adap; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + adap = i2c_verify_adapter(dev); + if (!adap) + break; + + if (i2c_adapter_is_main_i801(adap)) + queue_work(system_long_wq, &i2c_work); + break; + case BUS_NOTIFY_REMOVED_DEVICE: + client = i2c_verify_client(dev); + if (!client) + break; + + if (i2c_dev == client) { + dev_dbg(&client->adapter->dev, "lis3lv02d i2c_client removed\n"); + i2c_dev = NULL; + } + break; + default: + break; + } + + return 0; +} +static struct notifier_block i2c_nb = { .notifier_call = i2c_bus_notify }; + +static int __init match_acpi_device_ids(struct device *dev, const void *data) +{ + return acpi_match_device(data, dev) ? 1 : 0; +} + +static int __init dell_lis3lv02d_init(void) +{ + const struct dmi_system_id *lis3lv02d_dmi_id; + struct device *dev; + int err; + + /* + * First check for a matching platform_device. This protects against + * SMO88xx ACPI fwnodes which actually do have an I2C resource, which + * will already have an i2c_client instantiated (not a platform_device). + */ + dev = bus_find_device(&platform_bus_type, NULL, smo8800_ids, match_acpi_device_ids); + if (!dev) { + pr_debug("No SMO88xx platform-device found\n"); + return 0; + } + put_device(dev); + + lis3lv02d_dmi_id = dmi_first_match(lis3lv02d_devices); + if (!lis3lv02d_dmi_id && !probe_i2c_addr) { + pr_warn("accelerometer is present on SMBus but its address is unknown, skipping registration\n"); + pr_info("Pass dell_lis3lv02d.probe_i2c_addr=1 on the kernel command line to probe, this may be dangerous!\n"); + return 0; + } + + if (lis3lv02d_dmi_id) + i2c_addr = (long)lis3lv02d_dmi_id->driver_data; + + /* + * Register i2c-bus notifier + queue initial scan for lis3lv02d + * i2c_client instantiation. + */ + err = bus_register_notifier(&i2c_bus_type, &i2c_nb); + if (err) + return err; + + notifier_registered = true; + + queue_work(system_long_wq, &i2c_work); + return 0; +} +module_init(dell_lis3lv02d_init); + +static void __exit dell_lis3lv02d_module_exit(void) +{ + if (!notifier_registered) + return; + + bus_unregister_notifier(&i2c_bus_type, &i2c_nb); + cancel_work_sync(&i2c_work); + i2c_unregister_device(i2c_dev); +} +module_exit(dell_lis3lv02d_module_exit); + +MODULE_DESCRIPTION("lis3lv02d i2c-client instantiation for ACPI SMO88xx devices"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-pc.c b/drivers/platform/x86/dell/dell-pc.c new file mode 100644 index 000000000000..48cc7511905a --- /dev/null +++ b/drivers/platform/x86/dell/dell-pc.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Dell laptop extras + * + * Copyright (c) Lyndon Sanche <lsanche@lyndeno.ca> + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/device/faux.h> +#include <linux/dmi.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_profile.h> +#include <linux/slab.h> + +#include "dell-smbios.h" + +static struct faux_device *dell_pc_fdev; +static int supported_modes; + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell Inc.", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +/* Derived from smbios-thermal-ctl + * + * cbClass 17 + * cbSelect 19 + * User Selectable Thermal Tables(USTT) + * cbArg1 determines the function to be performed + * cbArg1 0x0 = Get Thermal Information + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, byte 0 Bitmap of supported thermal modes. A mode is supported if + * its bit is set to 1 + * Bit 0 Balanced + * Bit 1 Cool Bottom + * Bit 2 Quiet + * Bit 3 Performance + * cbRES2, byte 1 Bitmap of supported Active Acoustic Controller (AAC) modes. + * Each mode corresponds to the supported thermal modes in + * byte 0. A mode is supported if its bit is set to 1. + * Bit 0 AAC (Balanced) + * Bit 1 AAC (Cool Bottom + * Bit 2 AAC (Quiet) + * Bit 3 AAC (Performance) + * cbRes3, byte 0 Current Thermal Mode + * Bit 0 Balanced + * Bit 1 Cool Bottom + * Bit 2 Quiet + * Bit 3 Performanc + * cbRes3, byte 1 AAC Configuration type + * 0 Global (AAC enable/disable applies to all supported USTT modes) + * 1 USTT mode specific + * cbRes3, byte 2 Current Active Acoustic Controller (AAC) Mode + * If AAC Configuration Type is Global, + * 0 AAC mode disabled + * 1 AAC mode enabled + * If AAC Configuration Type is USTT mode specific (multiple bits may be set), + * Bit 0 AAC (Balanced) + * Bit 1 AAC (Cool Bottom + * Bit 2 AAC (Quiet) + * Bit 3 AAC (Performance) + * cbRes3, byte 3 Current Fan Failure Mode + * Bit 0 Minimal Fan Failure (at least one fan has failed, one fan working) + * Bit 1 Catastrophic Fan Failure (all fans have failed) + * + * cbArg1 0x1 (Set Thermal Information), both desired thermal mode and + * desired AAC mode shall be applied + * cbArg2, byte 0 Desired Thermal Mode to set + * (only one bit may be set for this parameter) + * Bit 0 Balanced + * Bit 1 Cool Bottom + * Bit 2 Quiet + * Bit 3 Performance + * cbArg2, byte 1 Desired Active Acoustic Controller (AAC) Mode to set + * If AAC Configuration Type is Global, + * 0 AAC mode disabled + * 1 AAC mode enabled + * If AAC Configuration Type is USTT mode specific + * (multiple bits may be set for this parameter), + * Bit 0 AAC (Balanced) + * Bit 1 AAC (Cool Bottom + * Bit 2 AAC (Quiet) + * Bit 3 AAC (Performance) + */ + +#define DELL_ACC_GET_FIELD GENMASK(19, 16) +#define DELL_ACC_SET_FIELD GENMASK(11, 8) +#define DELL_THERMAL_SUPPORTED GENMASK(3, 0) + +enum thermal_mode_bits { + DELL_BALANCED = BIT(0), + DELL_COOL_BOTTOM = BIT(1), + DELL_QUIET = BIT(2), + DELL_PERFORMANCE = BIT(3), +}; + +static int thermal_get_mode(void) +{ + struct calling_interface_buffer buffer; + int state; + int ret; + + dell_fill_request(&buffer, 0x0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); + if (ret) + return ret; + state = buffer.output[2]; + if (state & DELL_BALANCED) + return DELL_BALANCED; + else if (state & DELL_COOL_BOTTOM) + return DELL_COOL_BOTTOM; + else if (state & DELL_QUIET) + return DELL_QUIET; + else if (state & DELL_PERFORMANCE) + return DELL_PERFORMANCE; + else + return -ENXIO; +} + +static int thermal_get_supported_modes(int *supported_bits) +{ + struct calling_interface_buffer buffer; + int ret; + + dell_fill_request(&buffer, 0x0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); + if (ret) + return ret; + *supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]); + return 0; +} + +static int thermal_get_acc_mode(int *acc_mode) +{ + struct calling_interface_buffer buffer; + int ret; + + dell_fill_request(&buffer, 0x0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); + if (ret) + return ret; + *acc_mode = FIELD_GET(DELL_ACC_GET_FIELD, buffer.output[3]); + return 0; +} + +static int thermal_set_mode(enum thermal_mode_bits state) +{ + struct calling_interface_buffer buffer; + int ret; + int acc_mode; + + ret = thermal_get_acc_mode(&acc_mode); + if (ret) + return ret; + + dell_fill_request(&buffer, 0x1, FIELD_PREP(DELL_ACC_SET_FIELD, acc_mode) | state, 0, 0); + return dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT); +} + +static int thermal_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + switch (profile) { + case PLATFORM_PROFILE_BALANCED: + return thermal_set_mode(DELL_BALANCED); + case PLATFORM_PROFILE_PERFORMANCE: + return thermal_set_mode(DELL_PERFORMANCE); + case PLATFORM_PROFILE_QUIET: + return thermal_set_mode(DELL_QUIET); + case PLATFORM_PROFILE_COOL: + return thermal_set_mode(DELL_COOL_BOTTOM); + default: + return -EOPNOTSUPP; + } +} + +static int thermal_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + int ret; + + ret = thermal_get_mode(); + if (ret < 0) + return ret; + + switch (ret) { + case DELL_BALANCED: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DELL_PERFORMANCE: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + case DELL_COOL_BOTTOM: + *profile = PLATFORM_PROFILE_COOL; + break; + case DELL_QUIET: + *profile = PLATFORM_PROFILE_QUIET; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + if (supported_modes & DELL_QUIET) + __set_bit(PLATFORM_PROFILE_QUIET, choices); + if (supported_modes & DELL_COOL_BOTTOM) + __set_bit(PLATFORM_PROFILE_COOL, choices); + if (supported_modes & DELL_BALANCED) + __set_bit(PLATFORM_PROFILE_BALANCED, choices); + if (supported_modes & DELL_PERFORMANCE) + __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops dell_pc_platform_profile_ops = { + .probe = thermal_platform_profile_probe, + .profile_get = thermal_platform_profile_get, + .profile_set = thermal_platform_profile_set, +}; + +static int dell_pc_faux_probe(struct faux_device *fdev) +{ + struct device *ppdev; + int ret; + + if (!dell_smbios_class_is_supported(CLASS_INFO)) + return -ENODEV; + + ret = thermal_get_supported_modes(&supported_modes); + if (ret < 0) + return ret; + + ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL, + &dell_pc_platform_profile_ops); + + return PTR_ERR_OR_ZERO(ppdev); +} + +static const struct faux_device_ops dell_pc_faux_ops = { + .probe = dell_pc_faux_probe, +}; + +static int __init dell_init(void) +{ + if (!dmi_check_system(dell_device_table)) + return -ENODEV; + + dell_pc_fdev = faux_device_create("dell-pc", NULL, &dell_pc_faux_ops); + if (!dell_pc_fdev) + return -ENODEV; + + return 0; +} + +static void __exit dell_exit(void) +{ + faux_device_destroy(dell_pc_fdev); +} + +module_init(dell_init); +module_exit(dell_exit); + +MODULE_AUTHOR("Lyndon Sanche <lsanche@lyndeno.ca>"); +MODULE_DESCRIPTION("Dell PC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c index c8fcb537fd65..a415c432d4c3 100644 --- a/drivers/platform/x86/dell/dell-rbtn.c +++ b/drivers/platform/x86/dell/dell-rbtn.c @@ -295,7 +295,6 @@ static struct acpi_driver rbtn_driver = { .remove = rbtn_remove, .notify = rbtn_notify, }, - .owner = THIS_MODULE, }; diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c index e61bfaf8b5c4..01c72b91a50d 100644 --- a/drivers/platform/x86/dell/dell-smbios-base.c +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -11,6 +11,7 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/container_of.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/capability.h> @@ -25,11 +26,16 @@ static u32 da_supported_commands; static int da_num_tokens; static struct platform_device *platform_device; static struct calling_interface_token *da_tokens; -static struct device_attribute *token_location_attrs; -static struct device_attribute *token_value_attrs; +static struct token_sysfs_data *token_entries; static struct attribute **token_attrs; static DEFINE_MUTEX(smbios_mutex); +struct token_sysfs_data { + struct device_attribute location_attr; + struct device_attribute value_attr; + struct calling_interface_token *token; +}; + struct smbios_device { struct list_head list; struct device *device; @@ -71,6 +77,7 @@ static struct smbios_call call_blacklist[] = { /* handled by kernel: dell-laptop */ {0x0000, CLASS_INFO, SELECT_RFKILL}, {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, + {0x0000, CLASS_INFO, SELECT_THERMAL_MANAGEMENT}, }; struct token_range { @@ -314,6 +321,31 @@ out_smbios_call: } EXPORT_SYMBOL_GPL(dell_smbios_call); +void dell_fill_request(struct calling_interface_buffer *buffer, + u32 arg0, u32 arg1, u32 arg2, u32 arg3) +{ + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = arg0; + buffer->input[1] = arg1; + buffer->input[2] = arg2; + buffer->input[3] = arg3; +} +EXPORT_SYMBOL_GPL(dell_fill_request); + +int dell_send_request(struct calling_interface_buffer *buffer, + u16 class, u16 select) +{ + int ret; + + buffer->cmd_class = class; + buffer->cmd_select = select; + ret = dell_smbios_call(buffer); + if (ret != 0) + return ret; + return dell_smbios_error(buffer->output[0]); +} +EXPORT_SYMBOL_GPL(dell_send_request); + struct calling_interface_token *dell_smbios_find_token(int tokenid) { int i; @@ -350,6 +382,15 @@ void dell_laptop_call_notifier(unsigned long action, void *data) } EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); +bool dell_smbios_class_is_supported(u16 class) +{ + /* Classes over 30 always unsupported */ + if (class > 30) + return false; + return da_supported_commands & (1 << class); +} +EXPORT_SYMBOL_GPL(dell_smbios_class_is_supported); + static void __init parse_da_table(const struct dmi_header *dm) { /* Final token is a terminator, so we don't want to copy it */ @@ -416,47 +457,26 @@ static void __init find_tokens(const struct dmi_header *dm, void *dummy) } } -static int match_attribute(struct device *dev, - struct device_attribute *attr) -{ - int i; - - for (i = 0; i < da_num_tokens * 2; i++) { - if (!token_attrs[i]) - continue; - if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) - return i/2; - } - dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); - return -EINVAL; -} - static ssize_t location_show(struct device *dev, struct device_attribute *attr, char *buf) { - int i; + struct token_sysfs_data *data = container_of(attr, struct token_sysfs_data, location_attr); if (!capable(CAP_SYS_ADMIN)) return -EPERM; - i = match_attribute(dev, attr); - if (i > 0) - return sysfs_emit(buf, "%08x", da_tokens[i].location); - return 0; + return sysfs_emit(buf, "%08x", data->token->location); } static ssize_t value_show(struct device *dev, struct device_attribute *attr, char *buf) { - int i; + struct token_sysfs_data *data = container_of(attr, struct token_sysfs_data, value_attr); if (!capable(CAP_SYS_ADMIN)) return -EPERM; - i = match_attribute(dev, attr); - if (i > 0) - return sysfs_emit(buf, "%08x", da_tokens[i].value); - return 0; + return sysfs_emit(buf, "%08x", data->token->value); } static struct attribute_group smbios_attribute_group = { @@ -473,22 +493,15 @@ static int build_tokens_sysfs(struct platform_device *dev) { char *location_name; char *value_name; - size_t size; int ret; int i, j; - /* (number of tokens + 1 for null terminated */ - size = sizeof(struct device_attribute) * (da_num_tokens + 1); - token_location_attrs = kzalloc(size, GFP_KERNEL); - if (!token_location_attrs) + token_entries = kcalloc(da_num_tokens, sizeof(*token_entries), GFP_KERNEL); + if (!token_entries) return -ENOMEM; - token_value_attrs = kzalloc(size, GFP_KERNEL); - if (!token_value_attrs) - goto out_allocate_value; /* need to store both location and value + terminator*/ - size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); - token_attrs = kzalloc(size, GFP_KERNEL); + token_attrs = kcalloc((2 * da_num_tokens) + 1, sizeof(*token_attrs), GFP_KERNEL); if (!token_attrs) goto out_allocate_attrs; @@ -496,32 +509,34 @@ static int build_tokens_sysfs(struct platform_device *dev) /* skip empty */ if (da_tokens[i].tokenID == 0) continue; + + token_entries[i].token = &da_tokens[i]; + /* add location */ location_name = kasprintf(GFP_KERNEL, "%04x_location", da_tokens[i].tokenID); if (location_name == NULL) goto out_unwind_strings; - sysfs_attr_init(&token_location_attrs[i].attr); - token_location_attrs[i].attr.name = location_name; - token_location_attrs[i].attr.mode = 0444; - token_location_attrs[i].show = location_show; - token_attrs[j++] = &token_location_attrs[i].attr; + + sysfs_attr_init(&token_entries[i].location_attr.attr); + token_entries[i].location_attr.attr.name = location_name; + token_entries[i].location_attr.attr.mode = 0444; + token_entries[i].location_attr.show = location_show; + token_attrs[j++] = &token_entries[i].location_attr.attr; /* add value */ value_name = kasprintf(GFP_KERNEL, "%04x_value", da_tokens[i].tokenID); - if (value_name == NULL) - goto loop_fail_create_value; - sysfs_attr_init(&token_value_attrs[i].attr); - token_value_attrs[i].attr.name = value_name; - token_value_attrs[i].attr.mode = 0444; - token_value_attrs[i].show = value_show; - token_attrs[j++] = &token_value_attrs[i].attr; - continue; - -loop_fail_create_value: - kfree(location_name); - goto out_unwind_strings; + if (!value_name) { + kfree(location_name); + goto out_unwind_strings; + } + + sysfs_attr_init(&token_entries[i].value_attr.attr); + token_entries[i].value_attr.attr.name = value_name; + token_entries[i].value_attr.attr.mode = 0444; + token_entries[i].value_attr.show = value_show; + token_attrs[j++] = &token_entries[i].value_attr.attr; } smbios_attribute_group.attrs = token_attrs; @@ -532,14 +547,12 @@ loop_fail_create_value: out_unwind_strings: while (i--) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); + kfree(token_entries[i].location_attr.attr.name); + kfree(token_entries[i].value_attr.attr.name); } kfree(token_attrs); out_allocate_attrs: - kfree(token_value_attrs); -out_allocate_value: - kfree(token_location_attrs); + kfree(token_entries); return -ENOMEM; } @@ -551,12 +564,11 @@ static void free_group(struct platform_device *pdev) sysfs_remove_group(&pdev->dev.kobj, &smbios_attribute_group); for (i = 0; i < da_num_tokens; i++) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); + kfree(token_entries[i].location_attr.attr.name); + kfree(token_entries[i].value_attr.attr.name); } kfree(token_attrs); - kfree(token_value_attrs); - kfree(token_location_attrs); + kfree(token_entries); } static int __init dell_smbios_init(void) @@ -564,6 +576,7 @@ static int __init dell_smbios_init(void) int ret, wmi, smm; if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Alienware", NULL) && !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { pr_err("Unable to run on non-Dell system\n"); return -ENODEV; @@ -610,7 +623,10 @@ static int __init dell_smbios_init(void) return 0; fail_sysfs: - free_group(platform_device); + if (!wmi) + exit_dell_smbios_wmi(); + if (!smm) + exit_dell_smbios_smm(); fail_create_group: platform_device_del(platform_device); diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h index eb341bf000c6..77baa15eb523 100644 --- a/drivers/platform/x86/dell/dell-smbios.h +++ b/drivers/platform/x86/dell/dell-smbios.h @@ -19,6 +19,7 @@ /* Classes and selects used only in kernel drivers */ #define CLASS_KBD_BACKLIGHT 4 #define SELECT_KBD_BACKLIGHT 11 +#define SELECT_THERMAL_MANAGEMENT 19 /* Tokens used in kernel drivers, any of these * should be filtered from userspace access @@ -32,6 +33,13 @@ #define KBD_LED_AUTO_50_TOKEN 0x02EB #define KBD_LED_AUTO_75_TOKEN 0x02EC #define KBD_LED_AUTO_100_TOKEN 0x02F6 +#define BAT_PRI_AC_MODE_TOKEN 0x0341 +#define BAT_ADAPTIVE_MODE_TOKEN 0x0342 +#define BAT_CUSTOM_MODE_TOKEN 0x0343 +#define BAT_STANDARD_MODE_TOKEN 0x0346 +#define BAT_EXPRESS_MODE_TOKEN 0x0347 +#define BAT_CUSTOM_CHARGE_START 0x0349 +#define BAT_CUSTOM_CHARGE_END 0x034A #define GLOBAL_MIC_MUTE_ENABLE 0x0364 #define GLOBAL_MIC_MUTE_DISABLE 0x0365 #define GLOBAL_MUTE_ENABLE 0x058C @@ -64,6 +72,11 @@ int dell_smbios_call_filter(struct device *d, struct calling_interface_buffer *buffer); int dell_smbios_call(struct calling_interface_buffer *buffer); +void dell_fill_request(struct calling_interface_buffer *buffer, + u32 arg0, u32 arg1, u32 arg2, u32 arg3); +int dell_send_request(struct calling_interface_buffer *buffer, + u16 class, u16 select); + struct calling_interface_token *dell_smbios_find_token(int tokenid); enum dell_laptop_notifier_actions { @@ -73,6 +86,7 @@ enum dell_laptop_notifier_actions { int dell_laptop_register_notifier(struct notifier_block *nb); int dell_laptop_unregister_notifier(struct notifier_block *nb); void dell_laptop_call_notifier(unsigned long action, void *data); +bool dell_smbios_class_is_supported(u16 class); /* for the supported backends */ #ifdef CONFIG_DELL_SMBIOS_WMI diff --git a/drivers/platform/x86/dell/dell-smo8800-ids.h b/drivers/platform/x86/dell/dell-smo8800-ids.h new file mode 100644 index 000000000000..ec58e229ba7a --- /dev/null +++ b/drivers/platform/x86/dell/dell-smo8800-ids.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ACPI SMO88XX lis3lv02d freefall / accelerometer device-ids. + * + * Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com> + * Copyright (C) 2014 Pali Rohár <pali@kernel.org> + */ +#ifndef _DELL_SMO8800_IDS_H_ +#define _DELL_SMO8800_IDS_H_ + +#include <linux/mod_devicetable.h> +#include <linux/module.h> + +static const struct acpi_device_id smo8800_ids[] = { + { "SMO8800" }, + { "SMO8801" }, + { "SMO8810" }, + { "SMO8811" }, + { "SMO8820" }, + { "SMO8821" }, + { "SMO8830" }, + { "SMO8831" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, smo8800_ids); + +#endif diff --git a/drivers/platform/x86/dell/dell-smo8800.c b/drivers/platform/x86/dell/dell-smo8800.c index f7ec17c56833..8872f9b57fce 100644 --- a/drivers/platform/x86/dell/dell-smo8800.c +++ b/drivers/platform/x86/dell/dell-smo8800.c @@ -14,10 +14,10 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/miscdevice.h> -#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/uaccess.h> +#include "dell-smo8800-ids.h" struct smo8800_device { u32 irq; /* acpi device irq */ @@ -163,23 +163,9 @@ static void smo8800_remove(struct platform_device *device) dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); } -/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ -static const struct acpi_device_id smo8800_ids[] = { - { "SMO8800", 0 }, - { "SMO8801", 0 }, - { "SMO8810", 0 }, - { "SMO8811", 0 }, - { "SMO8820", 0 }, - { "SMO8821", 0 }, - { "SMO8830", 0 }, - { "SMO8831", 0 }, - { "", 0 }, -}; -MODULE_DEVICE_TABLE(acpi, smo8800_ids); - static struct platform_driver smo8800_driver = { .probe = smo8800_probe, - .remove_new = smo8800_remove, + .remove = smo8800_remove, .driver = { .name = DRIVER_NAME, .acpi_match_table = smo8800_ids, diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c new file mode 100644 index 000000000000..8f868f845350 --- /dev/null +++ b/drivers/platform/x86/dell/dell-uart-backlight.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dell AIO Serial Backlight Driver + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + * Copyright (C) 2017 AceLan Kao <acelan.kao@canonical.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/backlight.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/serdev.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <acpi/video.h> +#include "../serdev_helpers.h" + +/* The backlight controller must respond within 1 second */ +#define DELL_BL_TIMEOUT msecs_to_jiffies(1000) +#define DELL_BL_MAX_BRIGHTNESS 100 + +/* Defines for the commands send to the controller */ + +/* 1st byte Start Of Frame 3 MSB bits: cmd-len + 01010 SOF marker */ +#define DELL_SOF(len) (((len) << 5) | 0x0a) +#define GET_CMD_LEN 3 +#define SET_CMD_LEN 4 + +/* 2nd byte command */ +#define CMD_GET_VERSION 0x06 +#define CMD_SET_BRIGHTNESS 0x0b +#define CMD_GET_BRIGHTNESS 0x0c +#define CMD_SET_BL_POWER 0x0e + +/* Indexes and other defines for response received from the controller */ +#define RESP_LEN 0 +#define RESP_CMD 1 /* Echo of CMD byte from command */ +#define RESP_DATA 2 /* Start of received data */ + +#define SET_RESP_LEN 3 +#define GET_RESP_LEN 4 +#define MIN_RESP_LEN 3 +#define MAX_RESP_LEN 80 + +struct dell_uart_backlight { + struct mutex mutex; + wait_queue_head_t wait_queue; + struct device *dev; + struct backlight_device *bl; + u8 *resp; + u8 resp_idx; + u8 resp_len; + u8 resp_max_len; + u8 pending_cmd; + int status; + int power; +}; + +/* Checksum: SUM(Length and Cmd and Data) xor 0xFF */ +static u8 dell_uart_checksum(u8 *buf, int len) +{ + u8 val = 0; + + while (len-- > 0) + val += buf[len]; + + return val ^ 0xff; +} + +static int dell_uart_bl_command(struct dell_uart_backlight *dell_bl, + const u8 *cmd, int cmd_len, + u8 *resp, int resp_max_len) +{ + int ret; + + ret = mutex_lock_killable(&dell_bl->mutex); + if (ret) + return ret; + + dell_bl->status = -EBUSY; + dell_bl->resp = resp; + dell_bl->resp_idx = 0; + dell_bl->resp_len = -1; /* Invalid / unset */ + dell_bl->resp_max_len = resp_max_len; + dell_bl->pending_cmd = cmd[1]; + + /* The TTY buffer should be big enough to take the entire cmd in one go */ + ret = serdev_device_write_buf(to_serdev_device(dell_bl->dev), cmd, cmd_len); + if (ret != cmd_len) { + dev_err(dell_bl->dev, "Error writing command: %d\n", ret); + dell_bl->status = (ret < 0) ? ret : -EIO; + goto out; + } + + ret = wait_event_timeout(dell_bl->wait_queue, dell_bl->status != -EBUSY, + DELL_BL_TIMEOUT); + if (ret == 0) { + dev_err(dell_bl->dev, "Timed out waiting for response.\n"); + /* Clear busy status to discard bytes received after this */ + dell_bl->status = -ETIMEDOUT; + } + +out: + mutex_unlock(&dell_bl->mutex); + return dell_bl->status; +} + +static int dell_uart_set_brightness(struct dell_uart_backlight *dell_bl, int brightness) +{ + u8 set_brightness[SET_CMD_LEN], resp[SET_RESP_LEN]; + + set_brightness[0] = DELL_SOF(SET_CMD_LEN); + set_brightness[1] = CMD_SET_BRIGHTNESS; + set_brightness[2] = brightness; + set_brightness[3] = dell_uart_checksum(set_brightness, 3); + + return dell_uart_bl_command(dell_bl, set_brightness, SET_CMD_LEN, resp, SET_RESP_LEN); +} + +static int dell_uart_get_brightness(struct dell_uart_backlight *dell_bl) +{ + struct device *dev = dell_bl->dev; + u8 get_brightness[GET_CMD_LEN], resp[GET_RESP_LEN]; + int ret; + + get_brightness[0] = DELL_SOF(GET_CMD_LEN); + get_brightness[1] = CMD_GET_BRIGHTNESS; + get_brightness[2] = dell_uart_checksum(get_brightness, 2); + + ret = dell_uart_bl_command(dell_bl, get_brightness, GET_CMD_LEN, resp, GET_RESP_LEN); + if (ret) + return ret; + + if (resp[RESP_LEN] != GET_RESP_LEN) { + dev_err(dev, "Unexpected get brightness response length: %d\n", resp[RESP_LEN]); + return -EIO; + } + + if (resp[RESP_DATA] > DELL_BL_MAX_BRIGHTNESS) { + dev_err(dev, "Unexpected get brightness response: %d\n", resp[RESP_DATA]); + return -EIO; + } + + return resp[RESP_DATA]; +} + +static int dell_uart_set_bl_power(struct dell_uart_backlight *dell_bl, int power) +{ + u8 set_power[SET_CMD_LEN], resp[SET_RESP_LEN]; + int ret; + + set_power[0] = DELL_SOF(SET_CMD_LEN); + set_power[1] = CMD_SET_BL_POWER; + set_power[2] = (power == BACKLIGHT_POWER_ON) ? 1 : 0; + set_power[3] = dell_uart_checksum(set_power, 3); + + ret = dell_uart_bl_command(dell_bl, set_power, SET_CMD_LEN, resp, SET_RESP_LEN); + if (ret) + return ret; + + dell_bl->power = power; + return 0; +} + +/* + * There is no command to get backlight power status, + * so we set the backlight power to "on" while initializing, + * and then track and report its status by power variable. + */ +static int dell_uart_get_bl_power(struct dell_uart_backlight *dell_bl) +{ + return dell_bl->power; +} + +static int dell_uart_update_status(struct backlight_device *bd) +{ + struct dell_uart_backlight *dell_bl = bl_get_data(bd); + int ret; + + ret = dell_uart_set_brightness(dell_bl, bd->props.brightness); + if (ret) + return ret; + + if (bd->props.power != dell_uart_get_bl_power(dell_bl)) + return dell_uart_set_bl_power(dell_bl, bd->props.power); + + return 0; +} + +static int dell_uart_get_brightness_op(struct backlight_device *bd) +{ + return dell_uart_get_brightness(bl_get_data(bd)); +} + +static const struct backlight_ops dell_uart_backlight_ops = { + .update_status = dell_uart_update_status, + .get_brightness = dell_uart_get_brightness_op, +}; + +static size_t dell_uart_bl_receive(struct serdev_device *serdev, const u8 *data, size_t len) +{ + struct dell_uart_backlight *dell_bl = serdev_device_get_drvdata(serdev); + size_t i; + u8 csum; + + dev_dbg(dell_bl->dev, "Recv: %*ph\n", (int)len, data); + + /* Throw away unexpected bytes / remainder of response after an error */ + if (dell_bl->status != -EBUSY) { + dev_warn(dell_bl->dev, "Bytes received out of band, dropping them.\n"); + return len; + } + + i = 0; + while (i < len && dell_bl->resp_idx != dell_bl->resp_len) { + dell_bl->resp[dell_bl->resp_idx] = data[i++]; + + switch (dell_bl->resp_idx) { + case RESP_LEN: /* Length byte */ + dell_bl->resp_len = dell_bl->resp[RESP_LEN]; + if (dell_bl->resp_len < MIN_RESP_LEN || + dell_bl->resp_len > dell_bl->resp_max_len) { + dev_err(dell_bl->dev, "Response length %d out if range %d - %d\n", + dell_bl->resp_len, MIN_RESP_LEN, dell_bl->resp_max_len); + dell_bl->status = -EIO; + goto wakeup; + } + break; + case RESP_CMD: /* CMD byte */ + if (dell_bl->resp[RESP_CMD] != dell_bl->pending_cmd) { + dev_err(dell_bl->dev, "Response cmd 0x%02x != pending 0x%02x\n", + dell_bl->resp[RESP_CMD], dell_bl->pending_cmd); + dell_bl->status = -EIO; + goto wakeup; + } + break; + } + dell_bl->resp_idx++; + } + + if (dell_bl->resp_idx != dell_bl->resp_len) + return len; /* Response not complete yet */ + + csum = dell_uart_checksum(dell_bl->resp, dell_bl->resp_len - 1); + if (dell_bl->resp[dell_bl->resp_len - 1] == csum) { + dell_bl->status = 0; /* Success */ + } else { + dev_err(dell_bl->dev, "Checksum mismatch got 0x%02x expected 0x%02x\n", + dell_bl->resp[dell_bl->resp_len - 1], csum); + dell_bl->status = -EIO; + } +wakeup: + wake_up(&dell_bl->wait_queue); + return i; +} + +static const struct serdev_device_ops dell_uart_bl_serdev_ops = { + .receive_buf = dell_uart_bl_receive, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int dell_uart_bl_serdev_probe(struct serdev_device *serdev) +{ + u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN]; + struct backlight_properties props = {}; + struct dell_uart_backlight *dell_bl; + struct device *dev = &serdev->dev; + int ret; + + dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL); + if (!dell_bl) + return -ENOMEM; + + mutex_init(&dell_bl->mutex); + init_waitqueue_head(&dell_bl->wait_queue); + dell_bl->dev = dev; + + serdev_device_set_drvdata(serdev, dell_bl); + serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return dev_err_probe(dev, ret, "opening UART device\n"); + + /* 9600 bps, no flow control, these are the default but set them to be sure */ + serdev_device_set_baudrate(serdev, 9600); + serdev_device_set_flow_control(serdev, false); + + get_version[0] = DELL_SOF(GET_CMD_LEN); + get_version[1] = CMD_GET_VERSION; + get_version[2] = dell_uart_checksum(get_version, 2); + + ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN); + if (ret) + return dev_err_probe(dev, ret, "getting firmware version\n"); + + dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA); + + /* Initialize bl_power to a known value */ + ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK); + if (ret) + return ret; + + ret = dell_uart_get_brightness(dell_bl); + if (ret < 0) + return ret; + + props.type = BACKLIGHT_PLATFORM; + props.brightness = ret; + props.max_brightness = DELL_BL_MAX_BRIGHTNESS; + props.power = dell_bl->power; + + dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", + dev, dell_bl, + &dell_uart_backlight_ops, + &props); + return PTR_ERR_OR_ZERO(dell_bl->bl); +} + +static struct serdev_device_driver dell_uart_bl_serdev_driver = { + .probe = dell_uart_bl_serdev_probe, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int dell_uart_bl_pdev_probe(struct platform_device *pdev) +{ + enum acpi_backlight_type bl_type; + struct serdev_device *serdev; + struct device *ctrl_dev; + int ret; + + bl_type = acpi_video_get_backlight_type(); + if (bl_type != acpi_backlight_dell_uart) { + dev_dbg(&pdev->dev, "Not loading (ACPI backlight type = %d)\n", bl_type); + return -ENODEV; + } + + ctrl_dev = get_serdev_controller("DELL0501", NULL, 0, "serial0"); + if (IS_ERR(ctrl_dev)) + return PTR_ERR(ctrl_dev); + + serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev)); + put_device(ctrl_dev); + if (!serdev) + return -ENOMEM; + + ret = serdev_device_add(serdev); + if (ret) { + dev_err(&pdev->dev, "error %d adding serdev\n", ret); + serdev_device_put(serdev); + return ret; + } + + ret = serdev_device_driver_register(&dell_uart_bl_serdev_driver); + if (ret) + goto err_remove_serdev; + + /* + * serdev device <-> driver matching relies on OF or ACPI matches and + * neither is available here, manually bind the driver. + */ + ret = device_driver_attach(&dell_uart_bl_serdev_driver.driver, &serdev->dev); + if (ret) + goto err_unregister_serdev_driver; + + /* So that dell_uart_bl_pdev_remove() can remove the serdev */ + platform_set_drvdata(pdev, serdev); + return 0; + +err_unregister_serdev_driver: + serdev_device_driver_unregister(&dell_uart_bl_serdev_driver); +err_remove_serdev: + serdev_device_remove(serdev); + return ret; +} + +static void dell_uart_bl_pdev_remove(struct platform_device *pdev) +{ + struct serdev_device *serdev = platform_get_drvdata(pdev); + + serdev_device_driver_unregister(&dell_uart_bl_serdev_driver); + serdev_device_remove(serdev); +} + +static struct platform_driver dell_uart_bl_pdev_driver = { + .probe = dell_uart_bl_pdev_probe, + .remove = dell_uart_bl_pdev_remove, + .driver = { + .name = "dell-uart-backlight", + }, +}; +module_platform_driver(dell_uart_bl_pdev_driver); + +MODULE_ALIAS("platform:dell-uart-backlight"); +MODULE_DESCRIPTION("Dell AIO Serial Backlight driver"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c index c7b7f1e403fb..54096495719b 100644 --- a/drivers/platform/x86/dell/dell-wmi-aio.c +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -70,20 +70,10 @@ static bool dell_wmi_aio_event_check(u8 *buffer, int length) return false; } -static void dell_wmi_aio_notify(u32 value, void *context) +static void dell_wmi_aio_notify(union acpi_object *obj, void *context) { - struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; struct dell_wmi_event *event; - acpi_status status; - status = wmi_get_event_data(value, &response); - if (status != AE_OK) { - pr_info("bad event status 0x%x\n", status); - return; - } - - obj = (union acpi_object *)response.pointer; if (obj) { unsigned int scancode = 0; @@ -114,7 +104,6 @@ static void dell_wmi_aio_notify(u32 value, void *context) break; } } - kfree(obj); } static int __init dell_wmi_aio_input_setup(void) diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index 502783a7adb1..841a5414d28a 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -80,6 +80,12 @@ static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { static const struct key_entry dell_wmi_keymap_type_0000[] = { { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, + /* Meta key lock */ + { KE_IGNORE, 0xe000, { KEY_RIGHTMETA } }, + + /* Meta key unlock */ + { KE_IGNORE, 0xe001, { KEY_RIGHTMETA } }, + /* Key code is followed by brightness level */ { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, @@ -264,6 +270,15 @@ static const struct key_entry dell_wmi_keymap_type_0010[] = { /*Speaker Mute*/ { KE_KEY, 0x109, { KEY_MUTE} }, + /* S2Idle screen off */ + { KE_IGNORE, 0x120, { KEY_RESERVED }}, + + /* Leaving S4 or S2Idle suspend */ + { KE_IGNORE, 0x130, { KEY_RESERVED }}, + + /* Entering S2Idle suspend */ + { KE_IGNORE, 0x140, { KEY_RESERVED }}, + /* Mic mute */ { KE_KEY, 0x150, { KEY_MICMUTE } }, diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index db1e9240dd02..67f3d7158403 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -8,6 +8,7 @@ #define pr_format(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/debugfs.h> #include <linux/device.h> #include <linux/device/driver.h> @@ -31,7 +32,7 @@ #include <acpi/battery.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #define DRIVER_NAME "dell-wmi-ddv" @@ -39,6 +40,33 @@ #define DELL_DDV_SUPPORTED_VERSION_MAX 3 #define DELL_DDV_GUID "8A42EA14-4F2A-FD45-6422-0087F7A7E608" +/* Battery indices 1, 2 and 3 */ +#define DELL_DDV_NUM_BATTERIES 3 + +#define SBS_MANUFACTURE_YEAR_MASK GENMASK(15, 9) +#define SBS_MANUFACTURE_MONTH_MASK GENMASK(8, 5) +#define SBS_MANUFACTURE_DAY_MASK GENMASK(4, 0) + +#define MA_FAILURE_MODE_MASK GENMASK(11, 8) +#define MA_FAILURE_MODE_PERMANENT 0x9 +#define MA_FAILURE_MODE_OVERHEAT 0xA +#define MA_FAILURE_MODE_OVERCURRENT 0xB + +#define MA_PERMANENT_FAILURE_CODE_MASK GENMASK(13, 12) +#define MA_PERMANENT_FAILURE_FUSE_BLOWN 0x0 +#define MA_PERMANENT_FAILURE_CELL_IMBALANCE 0x1 +#define MA_PERMANENT_FAILURE_OVERVOLTAGE 0x2 +#define MA_PERMANENT_FAILURE_FET_FAILURE 0x3 + +#define MA_OVERHEAT_FAILURE_CODE_MASK GENMASK(15, 12) +#define MA_OVERHEAT_FAILURE_START 0x5 +#define MA_OVERHEAT_FAILURE_CHARGING 0x7 +#define MA_OVERHEAT_FAILURE_DISCHARGING 0x8 + +#define MA_OVERCURRENT_FAILURE_CODE_MASK GENMASK(15, 12) +#define MA_OVERCURRENT_FAILURE_CHARGING 0x6 +#define MA_OVERCURRENT_FAILURE_DISCHARGING 0xB + #define DELL_EPPID_LENGTH 20 #define DELL_EPPID_EXT_LENGTH 23 @@ -104,8 +132,9 @@ struct dell_wmi_ddv_sensors { struct dell_wmi_ddv_data { struct acpi_battery_hook hook; - struct device_attribute temp_attr; struct device_attribute eppid_attr; + struct mutex translation_cache_lock; /* Protects the translation cache */ + struct power_supply *translation_cache[DELL_DDV_NUM_BATTERIES]; struct dell_wmi_ddv_sensors fans; struct dell_wmi_ddv_sensors temps; struct wmi_device *wdev; @@ -640,33 +669,78 @@ err_release: return ret; } -static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index) +static int dell_wmi_ddv_battery_translate(struct dell_wmi_ddv_data *data, + struct power_supply *battery, u32 *index) { - const char *uid_str; + u32 serial_dec, serial_hex, serial; + union power_supply_propval val; + int ret; - uid_str = acpi_device_uid(acpi_dev); - if (!uid_str) - return -ENODEV; + guard(mutex)(&data->translation_cache_lock); - return kstrtou32(uid_str, 10, index); -} + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + if (data->translation_cache[i] == battery) { + dev_dbg(&data->wdev->dev, "Translation cache hit for battery index %u\n", + i + 1); + *index = i + 1; + return 0; + } + } -static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct dell_wmi_ddv_data *data = container_of(attr, struct dell_wmi_ddv_data, temp_attr); - u32 index, value; - int ret; + dev_dbg(&data->wdev->dev, "Translation cache miss\n"); - ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index); + /* Perform a translation between a ACPI battery and a battery index */ + + ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_SERIAL_NUMBER, &val); if (ret < 0) return ret; - ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index, &value); + /* + * Some devices display the serial number of the ACPI battery (string!) as a decimal + * number while other devices display it as a hexadecimal number. Because of this we + * have to check both cases. + */ + ret = kstrtou32(val.strval, 16, &serial_hex); if (ret < 0) - return ret; + return ret; /* Should never fail */ + + ret = kstrtou32(val.strval, 10, &serial_dec); + if (ret < 0) + serial_dec = 0; /* Can fail, thus we only mark serial_dec as invalid */ + + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_SERIAL_NUMBER, i + 1, + &serial); + if (ret < 0) + return ret; + + /* A serial number of 0 signals that this index is not associated with a battery */ + if (!serial) + continue; + + if (serial == serial_dec || serial == serial_hex) { + dev_dbg(&data->wdev->dev, "Translation cache update for battery index %u\n", + i + 1); + data->translation_cache[i] = battery; + *index = i + 1; + return 0; + } + } + + return -ENODEV; +} + +static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data, + struct power_supply *battery) +{ + guard(mutex)(&data->translation_cache_lock); - /* Use 2731 instead of 2731.5 to avoid unnecessary rounding */ - return sysfs_emit(buf, "%d\n", value - 2731); + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + if (data->translation_cache[i] == battery) { + data->translation_cache[i] = NULL; + return; + } + } } static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -676,7 +750,7 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha u32 index; int ret; - ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index); + ret = dell_wmi_ddv_battery_translate(data, to_power_supply(dev), &index); if (ret < 0) return ret; @@ -695,24 +769,184 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha return ret; } -static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index, + union power_supply_propval *val) { - struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); - u32 index; + u32 value, code; + int ret; + + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURER_ACCESS, index, + &value); + if (ret < 0) + return ret; + + switch (FIELD_GET(MA_FAILURE_MODE_MASK, value)) { + case MA_FAILURE_MODE_PERMANENT: + code = FIELD_GET(MA_PERMANENT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_PERMANENT_FAILURE_FUSE_BLOWN: + val->intval = POWER_SUPPLY_HEALTH_BLOWN_FUSE; + return 0; + case MA_PERMANENT_FAILURE_CELL_IMBALANCE: + val->intval = POWER_SUPPLY_HEALTH_CELL_IMBALANCE; + return 0; + case MA_PERMANENT_FAILURE_OVERVOLTAGE: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + case MA_PERMANENT_FAILURE_FET_FAILURE: + val->intval = POWER_SUPPLY_HEALTH_DEAD; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown permanent failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + case MA_FAILURE_MODE_OVERHEAT: + code = FIELD_GET(MA_OVERHEAT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_OVERHEAT_FAILURE_START: + case MA_OVERHEAT_FAILURE_CHARGING: + case MA_OVERHEAT_FAILURE_DISCHARGING: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown overheat failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + case MA_FAILURE_MODE_OVERCURRENT: + code = FIELD_GET(MA_OVERCURRENT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_OVERCURRENT_FAILURE_CHARGING: + case MA_OVERCURRENT_FAILURE_DISCHARGING: + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown overcurrent failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + } +} + +static int dell_wmi_ddv_get_manufacture_date(struct dell_wmi_ddv_data *data, u32 index, + enum power_supply_property psp, + union power_supply_propval *val) +{ + u16 year, month, day; + u32 value; int ret; - /* Return 0 instead of error to avoid being unloaded */ - ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), &index); + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURE_DATE, + index, &value); if (ret < 0) + return ret; + if (value > U16_MAX) + return -ENXIO; + + /* + * Some devices report a invalid manufacture date value + * like 0.0.1980. Because of this we have to check the + * whole value before exposing parts of it to user space. + */ + year = FIELD_GET(SBS_MANUFACTURE_YEAR_MASK, value) + 1980; + month = FIELD_GET(SBS_MANUFACTURE_MONTH_MASK, value); + if (month < 1 || month > 12) + return -ENODATA; + + day = FIELD_GET(SBS_MANUFACTURE_DAY_MASK, value); + if (day < 1 || day > 31) + return -ENODATA; + + switch (psp) { + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + val->intval = year; return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + val->intval = month; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + val->intval = day; + return 0; + default: + return -EINVAL; + } +} - ret = device_create_file(&battery->dev, &data->temp_attr); +static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct power_supply_ext *ext, + void *drvdata, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct dell_wmi_ddv_data *data = drvdata; + u32 index, value; + int ret; + + ret = dell_wmi_ddv_battery_translate(data, psy, &index); if (ret < 0) return ret; + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + return dell_wmi_ddv_get_health(data, index, val); + case POWER_SUPPLY_PROP_TEMP: + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index, + &value); + if (ret < 0) + return ret; + + /* Use 2732 instead of 2731.5 to avoid unnecessary rounding and to emulate + * the behaviour of the OEM application which seems to round down the result. + */ + val->intval = value - 2732; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + return dell_wmi_ddv_get_manufacture_date(data, index, psp, val); + default: + return -EINVAL; + } +} + +static const enum power_supply_property dell_wmi_ddv_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, +}; + +static const struct power_supply_ext dell_wmi_ddv_extension = { + .name = DRIVER_NAME, + .properties = dell_wmi_ddv_properties, + .num_properties = ARRAY_SIZE(dell_wmi_ddv_properties), + .get_property = dell_wmi_ddv_get_property, +}; + +static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); + int ret; + + /* + * We cannot do the battery matching here since the battery might be absent, preventing + * us from reading the serial number. + */ + ret = device_create_file(&battery->dev, &data->eppid_attr); + if (ret < 0) + return ret; + + ret = power_supply_register_extension(battery, &dell_wmi_ddv_extension, &data->wdev->dev, + data); if (ret < 0) { - device_remove_file(&battery->dev, &data->temp_attr); + device_remove_file(&battery->dev, &data->eppid_attr); return ret; } @@ -724,38 +958,32 @@ static int dell_wmi_ddv_remove_battery(struct power_supply *battery, struct acpi { struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); - device_remove_file(&battery->dev, &data->temp_attr); device_remove_file(&battery->dev, &data->eppid_attr); + power_supply_unregister_extension(battery, &dell_wmi_ddv_extension); + + dell_wmi_battery_invalidate(data, battery); return 0; } -static void dell_wmi_ddv_battery_remove(void *data) +static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) { - struct acpi_battery_hook *hook = data; + int ret; - battery_hook_unregister(hook); -} + ret = devm_mutex_init(&data->wdev->dev, &data->translation_cache_lock); + if (ret < 0) + return ret; -static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) -{ data->hook.name = "Dell DDV Battery Extension"; data->hook.add_battery = dell_wmi_ddv_add_battery; data->hook.remove_battery = dell_wmi_ddv_remove_battery; - sysfs_attr_init(&data->temp_attr.attr); - data->temp_attr.attr.name = "temp"; - data->temp_attr.attr.mode = 0444; - data->temp_attr.show = temp_show; - sysfs_attr_init(&data->eppid_attr.attr); data->eppid_attr.attr.name = "eppid"; data->eppid_attr.attr.mode = 0444; data->eppid_attr.show = eppid_show; - battery_hook_register(&data->hook); - - return devm_add_action_or_reset(&data->wdev->dev, dell_wmi_ddv_battery_remove, &data->hook); + return devm_battery_hook_register(&data->wdev->dev, &data->hook); } static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) @@ -882,6 +1110,7 @@ static struct wmi_driver dell_wmi_ddv_driver = { }, .id_table = dell_wmi_ddv_id_table, .probe = dell_wmi_ddv_probe, + .no_singleton = true, }; module_wmi_driver(dell_wmi_ddv_driver); diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c index c517bd45dd32..4b65e1655d42 100644 --- a/drivers/platform/x86/dell/dell-wmi-privacy.c +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -288,7 +288,6 @@ static int dell_privacy_leds_setup(struct device *dev) priv->cdev.max_brightness = 1; priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set; priv->cdev.default_trigger = "audio-micmute"; - priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); return devm_led_classdev_register(dev, &priv->cdev); } @@ -298,10 +297,6 @@ static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context) struct key_entry *keymap; int ret, i, j; - ret = wmi_has_guid(DELL_PRIVACY_GUID); - if (!ret) - pr_debug("Unable to detect available Dell privacy devices!\n"); - priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile index 825fb2fbeea8..0a6df449e222 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/Makefile +++ b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o -dell-wmi-sysman-objs := sysman.o \ +dell-wmi-sysman-y := sysman.o \ enum-attributes.o \ int-attributes.o \ string-attributes.o \ diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c index 230e6ee96636..d8f1bf5e58a0 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -45,7 +45,7 @@ static ssize_t current_password_store(struct kobject *kobj, int length; length = strlen(buf); - if (buf[length-1] == '\n') + if (length && buf[length - 1] == '\n') length--; /* firmware does verifiation of min/max password length, diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index b929b4f82420..d00389b860e4 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -25,7 +25,6 @@ struct wmi_sysman_priv wmi_priv = { /* reset bios to defaults */ static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; static int reset_option = -1; -static struct class *fw_attr_class; /** @@ -521,6 +520,7 @@ static int __init sysman_init(void) int ret = 0; if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Alienware", NULL) && !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { pr_err("Unable to run on non-Dell system\n"); return -ENODEV; @@ -540,15 +540,11 @@ static int __init sysman_init(void) goto err_exit_bios_attr_pass_interface; } - ret = fw_attributes_class_get(&fw_attr_class); - if (ret) - goto err_exit_bios_attr_pass_interface; - - wmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), + wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), NULL, "%s", DRIVER_NAME); if (IS_ERR(wmi_priv.class_dev)) { ret = PTR_ERR(wmi_priv.class_dev); - goto err_unregister_class; + goto err_exit_bios_attr_pass_interface; } wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, @@ -601,10 +597,7 @@ err_release_attributes_data: release_attributes_data(); err_destroy_classdev: - device_destroy(fw_attr_class, MKDEV(0, 0)); - -err_unregister_class: - fw_attributes_class_put(); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); err_exit_bios_attr_pass_interface: exit_bios_attr_pass_interface(); @@ -618,8 +611,7 @@ err_exit_bios_attr_set_interface: static void __exit sysman_exit(void) { release_attributes_data(); - device_destroy(fw_attr_class, MKDEV(0, 0)); - fw_attributes_class_put(); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); exit_bios_attr_set_interface(); exit_bios_attr_pass_interface(); } diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c index 9f51e0fcab04..e30ca325938c 100644 --- a/drivers/platform/x86/dell/dell_rbu.c +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -475,7 +475,7 @@ static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) } static ssize_t data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { ssize_t ret_count = 0; @@ -492,7 +492,7 @@ static ssize_t data_read(struct file *filp, struct kobject *kobj, spin_unlock(&rbu_data.lock); return ret_count; } -static BIN_ATTR_RO(data, 0); +static const BIN_ATTR_RO(data, 0); static void callbackfn_rbu(const struct firmware *fw, void *context) { @@ -530,7 +530,7 @@ static void callbackfn_rbu(const struct firmware *fw, void *context) } static ssize_t image_type_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { int size = 0; @@ -540,7 +540,7 @@ static ssize_t image_type_read(struct file *filp, struct kobject *kobj, } static ssize_t image_type_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { int rc = count; @@ -597,10 +597,10 @@ static ssize_t image_type_write(struct file *filp, struct kobject *kobj, return rc; } -static BIN_ATTR_RW(image_type, 0); +static const BIN_ATTR_RW(image_type, 0); static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { int size = 0; @@ -613,7 +613,7 @@ static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, } static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t count) { unsigned long temp; @@ -626,9 +626,9 @@ static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, spin_unlock(&rbu_data.lock); return count; } -static BIN_ATTR_RW(packet_size, 0); +static const BIN_ATTR_RW(packet_size, 0); -static struct bin_attribute *rbu_bin_attrs[] = { +static const struct bin_attribute *const rbu_bin_attrs[] = { &bin_attr_data, &bin_attr_image_type, &bin_attr_packet_size, @@ -636,7 +636,7 @@ static struct bin_attribute *rbu_bin_attrs[] = { }; static const struct attribute_group rbu_group = { - .bin_attrs = rbu_bin_attrs, + .bin_attrs_new = rbu_bin_attrs, }; static int __init dcdrbu_init(void) |