mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-06 13:16:22 +00:00
platform/x86: thinkpad_acpi: Add platform profile support
Add support to thinkpad_acpi for Lenovo platforms that have DYTC version 5 support or newer to use the platform profile feature. This will allow users to determine and control the platform modes between low-power, balanced operation and performance modes. Signed-off-by: Mark Pearson <markpearson@lenovo.com> Reviewed-by: Hans de Goede <hdegoede@redhat.com> Link: https://lore.kernel.org/r/20210111162237.3469-1-markpearson@lenovo.com Signed-off-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
parent
effe55add0
commit
c3bfcd4c67
@ -66,6 +66,7 @@
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/initval.h>
|
||||
@ -9855,16 +9856,27 @@ static bool has_lapsensor;
|
||||
static bool palm_state;
|
||||
static bool lap_state;
|
||||
|
||||
static int lapsensor_get(bool *present, bool *state)
|
||||
static int dytc_command(int command, int *output)
|
||||
{
|
||||
acpi_handle dytc_handle;
|
||||
int output;
|
||||
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) {
|
||||
/* Platform doesn't support DYTC */
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!acpi_evalf(dytc_handle, output, NULL, "dd", command))
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lapsensor_get(bool *present, bool *state)
|
||||
{
|
||||
int output, err;
|
||||
|
||||
*present = false;
|
||||
if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle)))
|
||||
return -ENODEV;
|
||||
if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET))
|
||||
return -EIO;
|
||||
err = dytc_command(DYTC_CMD_GET, &output);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
*present = true; /*If we get his far, we have lapmode support*/
|
||||
*state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false;
|
||||
@ -9983,6 +9995,264 @@ static struct ibm_struct proxsensor_driver_data = {
|
||||
.exit = proxsensor_exit,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE)
|
||||
|
||||
/*************************************************************************
|
||||
* DYTC Platform Profile interface
|
||||
*/
|
||||
|
||||
#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */
|
||||
#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */
|
||||
#define DYTC_CMD_RESET 0x1ff /* To reset back to default */
|
||||
|
||||
#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */
|
||||
#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */
|
||||
#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */
|
||||
|
||||
#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */
|
||||
#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */
|
||||
|
||||
#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */
|
||||
#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */
|
||||
#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */
|
||||
|
||||
#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */
|
||||
#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */
|
||||
#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */
|
||||
|
||||
#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */
|
||||
#define DYTC_MODE_LOWPOWER 3 /* Low power mode */
|
||||
#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */
|
||||
|
||||
#define DYTC_SET_COMMAND(function, mode, on) \
|
||||
(DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \
|
||||
(mode) << DYTC_SET_MODE_BIT | \
|
||||
(on) << DYTC_SET_VALID_BIT)
|
||||
|
||||
#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0)
|
||||
|
||||
#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1)
|
||||
|
||||
static bool dytc_profile_available;
|
||||
static enum platform_profile_option dytc_current_profile;
|
||||
static atomic_t dytc_ignore_event = ATOMIC_INIT(0);
|
||||
static DEFINE_MUTEX(dytc_mutex);
|
||||
|
||||
static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
|
||||
{
|
||||
switch (dytcmode) {
|
||||
case DYTC_MODE_LOWPOWER:
|
||||
*profile = PLATFORM_PROFILE_LOW_POWER;
|
||||
break;
|
||||
case DYTC_MODE_BALANCE:
|
||||
*profile = PLATFORM_PROFILE_BALANCED;
|
||||
break;
|
||||
case DYTC_MODE_PERFORM:
|
||||
*profile = PLATFORM_PROFILE_PERFORMANCE;
|
||||
break;
|
||||
default: /* Unknown mode */
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode)
|
||||
{
|
||||
switch (profile) {
|
||||
case PLATFORM_PROFILE_LOW_POWER:
|
||||
*perfmode = DYTC_MODE_LOWPOWER;
|
||||
break;
|
||||
case PLATFORM_PROFILE_BALANCED:
|
||||
*perfmode = DYTC_MODE_BALANCE;
|
||||
break;
|
||||
case PLATFORM_PROFILE_PERFORMANCE:
|
||||
*perfmode = DYTC_MODE_PERFORM;
|
||||
break;
|
||||
default: /* Unknown profile */
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* dytc_profile_get: Function to register with platform_profile
|
||||
* handler. Returns current platform profile.
|
||||
*/
|
||||
int dytc_profile_get(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option *profile)
|
||||
{
|
||||
*profile = dytc_current_profile;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function - check if we are in CQL mode and if we are
|
||||
* - disable CQL,
|
||||
* - run the command
|
||||
* - enable CQL
|
||||
* If not in CQL mode, just run the command
|
||||
*/
|
||||
int dytc_cql_command(int command, int *output)
|
||||
{
|
||||
int err, cmd_err, dummy;
|
||||
int cur_funcmode;
|
||||
|
||||
/* Determine if we are in CQL mode. This alters the commands we do */
|
||||
err = dytc_command(DYTC_CMD_GET, output);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF;
|
||||
/* Check if we're OK to return immediately */
|
||||
if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL))
|
||||
return 0;
|
||||
|
||||
if (cur_funcmode == DYTC_FUNCTION_CQL) {
|
||||
atomic_inc(&dytc_ignore_event);
|
||||
err = dytc_command(DYTC_DISABLE_CQL, &dummy);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
cmd_err = dytc_command(command, output);
|
||||
/* Check return condition after we've restored CQL state */
|
||||
|
||||
if (cur_funcmode == DYTC_FUNCTION_CQL) {
|
||||
err = dytc_command(DYTC_ENABLE_CQL, &dummy);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return cmd_err;
|
||||
}
|
||||
|
||||
/*
|
||||
* dytc_profile_set: Function to register with platform_profile
|
||||
* handler. Sets current platform profile.
|
||||
*/
|
||||
int dytc_profile_set(struct platform_profile_handler *pprof,
|
||||
enum platform_profile_option profile)
|
||||
{
|
||||
int output;
|
||||
int err;
|
||||
|
||||
if (!dytc_profile_available)
|
||||
return -ENODEV;
|
||||
|
||||
err = mutex_lock_interruptible(&dytc_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (profile == PLATFORM_PROFILE_BALANCED) {
|
||||
/* To get back to balanced mode we just issue a reset command */
|
||||
err = dytc_command(DYTC_CMD_RESET, &output);
|
||||
if (err)
|
||||
goto unlock;
|
||||
} else {
|
||||
int perfmode;
|
||||
|
||||
err = convert_profile_to_dytc(profile, &perfmode);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
/* Determine if we are in CQL mode. This alters the commands we do */
|
||||
err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output);
|
||||
if (err)
|
||||
goto unlock;
|
||||
}
|
||||
/* Success - update current profile */
|
||||
dytc_current_profile = profile;
|
||||
unlock:
|
||||
mutex_unlock(&dytc_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void dytc_profile_refresh(void)
|
||||
{
|
||||
enum platform_profile_option profile;
|
||||
int output, err;
|
||||
int perfmode;
|
||||
|
||||
mutex_lock(&dytc_mutex);
|
||||
err = dytc_cql_command(DYTC_CMD_GET, &output);
|
||||
mutex_unlock(&dytc_mutex);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
|
||||
convert_dytc_to_profile(perfmode, &profile);
|
||||
if (profile != dytc_current_profile) {
|
||||
dytc_current_profile = profile;
|
||||
platform_profile_notify();
|
||||
}
|
||||
}
|
||||
|
||||
static struct platform_profile_handler dytc_profile = {
|
||||
.profile_get = dytc_profile_get,
|
||||
.profile_set = dytc_profile_set,
|
||||
};
|
||||
|
||||
static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
int err, output;
|
||||
|
||||
/* Setup supported modes */
|
||||
set_bit(PLATFORM_PROFILE_LOW_POWER, dytc_profile.choices);
|
||||
set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices);
|
||||
set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices);
|
||||
|
||||
dytc_profile_available = false;
|
||||
err = dytc_command(DYTC_CMD_QUERY, &output);
|
||||
/*
|
||||
* If support isn't available (ENODEV) then don't return an error
|
||||
* and don't create the sysfs group
|
||||
*/
|
||||
if (err == -ENODEV)
|
||||
return 0;
|
||||
/* For all other errors we can flag the failure */
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Check DYTC is enabled and supports mode setting */
|
||||
if (output & BIT(DYTC_QUERY_ENABLE_BIT)) {
|
||||
/* Only DYTC v5.0 and later has this feature. */
|
||||
int dytc_version;
|
||||
|
||||
dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF;
|
||||
if (dytc_version >= 5) {
|
||||
dbg_printk(TPACPI_DBG_INIT,
|
||||
"DYTC version %d: thermal mode available\n", dytc_version);
|
||||
/* Create platform_profile structure and register */
|
||||
err = platform_profile_register(&dytc_profile);
|
||||
/*
|
||||
* If for some reason platform_profiles aren't enabled
|
||||
* don't quit terminally.
|
||||
*/
|
||||
if (err)
|
||||
return 0;
|
||||
|
||||
dytc_profile_available = true;
|
||||
/* Ensure initial values are correct */
|
||||
dytc_profile_refresh();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dytc_profile_exit(void)
|
||||
{
|
||||
if (dytc_profile_available) {
|
||||
dytc_profile_available = false;
|
||||
platform_profile_remove();
|
||||
}
|
||||
}
|
||||
|
||||
static struct ibm_struct dytc_profile_driver_data = {
|
||||
.name = "dytc-profile",
|
||||
.exit = dytc_profile_exit,
|
||||
};
|
||||
#endif /* CONFIG_ACPI_PLATFORM_PROFILE */
|
||||
|
||||
/*************************************************************************
|
||||
* Keyboard language interface
|
||||
*/
|
||||
@ -10204,8 +10474,14 @@ static void tpacpi_driver_event(const unsigned int hkey_event)
|
||||
mutex_unlock(&kbdlight_mutex);
|
||||
}
|
||||
|
||||
if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED)
|
||||
if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) {
|
||||
lapsensor_refresh();
|
||||
#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE)
|
||||
/* If we are already accessing DYTC then skip dytc update */
|
||||
if (!atomic_add_unless(&dytc_ignore_event, -1, 0))
|
||||
dytc_profile_refresh();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void hotkey_driver_event(const unsigned int scancode)
|
||||
@ -10648,6 +10924,12 @@ static struct ibm_init_struct ibms_init[] __initdata = {
|
||||
.init = tpacpi_proxsensor_init,
|
||||
.data = &proxsensor_driver_data,
|
||||
},
|
||||
#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE)
|
||||
{
|
||||
.init = tpacpi_dytc_profile_init,
|
||||
.data = &dytc_profile_driver_data,
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.init = tpacpi_kbdlang_init,
|
||||
.data = &kbdlang_driver_data,
|
||||
|
Loading…
Reference in New Issue
Block a user