platform/x86: dell-pc: Implement platform_profile

Some Dell laptops support configuration of preset fan modes through
smbios tables.

If the platform supports these fan modes, set up platform_profile to
change these modes. If not supported, skip enabling platform_profile.

Signed-off-by: Lyndon Sanche <lsanche@lyndeno.ca>
Link: https://lore.kernel.org/r/20240529174843.13226-4-lsanche@lyndeno.ca
[ij: Adjust #includes into alphabetical order]
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
This commit is contained in:
Lyndon Sanche 2024-05-29 11:47:46 -06:00 committed by Ilpo Järvinen
parent 33245680ae
commit 996ad41298
No known key found for this signature in database
GPG Key ID: 59AC4F6153E5CE31
6 changed files with 329 additions and 0 deletions

View File

@ -6116,6 +6116,12 @@ F: Documentation/ABI/obsolete/procfs-i8k
F: drivers/hwmon/dell-smm-hwmon.c
F: include/uapi/linux/i8k.h
DELL PC DRIVER
M: Lyndon Sanche <lsanche@lyndeno.ca>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell/dell-pc.c
DELL REMOTE BIOS UPDATE DRIVER
M: Stuart Hayes <stuart.w.hayes@gmail.com>
L: platform-driver-x86@vger.kernel.org

View File

@ -91,6 +91,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

View File

@ -9,6 +9,7 @@ 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-objs := dell-smbios-base.o
dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o

View File

@ -0,0 +1,307 @@
// 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/bits.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 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)
static struct platform_profile_handler *thermal_handler;
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);
/* Thermal function not supported */
if (ret == -ENXIO) {
*supported_bits = 0;
return 0;
}
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 platform_profile_handler *pprof,
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 platform_profile_handler *pprof,
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_init(void)
{
int ret;
int supported_modes;
/* If thermal commands are not supported, exit without error */
if (!dell_smbios_class_is_supported(CLASS_INFO))
return 0;
/* If thermal modes are not supported, exit without error */
ret = thermal_get_supported_modes(&supported_modes);
if (ret < 0)
return ret;
if (!supported_modes)
return 0;
thermal_handler = kzalloc(sizeof(*thermal_handler), GFP_KERNEL);
if (!thermal_handler)
return -ENOMEM;
thermal_handler->profile_get = thermal_platform_profile_get;
thermal_handler->profile_set = thermal_platform_profile_set;
if (supported_modes & DELL_QUIET)
set_bit(PLATFORM_PROFILE_QUIET, thermal_handler->choices);
if (supported_modes & DELL_COOL_BOTTOM)
set_bit(PLATFORM_PROFILE_COOL, thermal_handler->choices);
if (supported_modes & DELL_BALANCED)
set_bit(PLATFORM_PROFILE_BALANCED, thermal_handler->choices);
if (supported_modes & DELL_PERFORMANCE)
set_bit(PLATFORM_PROFILE_PERFORMANCE, thermal_handler->choices);
/* Clean up if failed */
ret = platform_profile_register(thermal_handler);
if (ret)
kfree(thermal_handler);
return ret;
}
static void thermal_cleanup(void)
{
if (thermal_handler) {
platform_profile_remove();
kfree(thermal_handler);
}
}
static int __init dell_init(void)
{
int ret;
if (!dmi_check_system(dell_device_table))
return -ENODEV;
/* Do not fail module if thermal modes not supported, just skip */
ret = thermal_init();
if (ret)
goto fail_thermal;
return 0;
fail_thermal:
thermal_cleanup();
return ret;
}
static void __exit dell_exit(void)
{
thermal_cleanup();
}
module_init(dell_init);
module_exit(dell_exit);
MODULE_AUTHOR("Lyndon Sanche <lsanche@lyndeno.ca>");
MODULE_DESCRIPTION("Dell PC driver");
MODULE_LICENSE("GPL");

View File

@ -71,6 +71,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 {

View File

@ -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