OSDN Git Service

platform/x86: thinkpad_acpi: Add platform profile support
authorMark Pearson <markpearson@lenovo.com>
Mon, 11 Jan 2021 16:22:37 +0000 (11:22 -0500)
committerHans de Goede <hdegoede@redhat.com>
Tue, 2 Feb 2021 14:42:35 +0000 (15:42 +0100)
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>
drivers/platform/x86/thinkpad_acpi.c

index 48575ef..18b3901 100644 (file)
@@ -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;
 
-       *present = false;
-       if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle)))
+       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", DYTC_CMD_GET))
+       }
+       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;
+       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,