From: Mark Pearson <markpearson@lenovo.com>
To: <markpearson@lenovo.com>
Cc: <hdegoede@redhat.com>, <mgross@linux.intel.com>,
<linux-acpi@vger.kernel.org>,
<platform-driver-x86@vger.kernel.org>, <rjw@rjwysocki.net>,
<hadess@hadess.net>, <pobrn@protonmail.com>,
<mario.limnociello@dell.com>, <eliadevito@gmail.com>,
<bberg@redhat.com>, <dvhart@infradead.org>
Subject: [PATCH v4 3/3] platform/x86: thinkpad_acpi: Add platform profile support
Date: Thu, 26 Nov 2020 11:51:43 -0500 [thread overview]
Message-ID: <20201126165143.32776-3-markpearson@lenovo.com> (raw)
In-Reply-To: <20201126165143.32776-1-markpearson@lenovo.com>
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>
---
Changes in v2:
Address (hopefully) all recommendations from review including:
- use IS_ENABLED instead of IS_DEFINED
- update driver to work with all the fixes in platform_profile update
- improve error handling for invalid inputs
- move tracking of current profile mode into this driver
Changes in v3:
- version update for patch series
Changes in v4:
- Rebase on top of palm sensor patch which led to a little bit of file
restructuring/clean up
- Use BIT macro where applicable
- Formatting fixes
- Check sysfs node created on exit function
- implement and use DYTC_SET_COMMAND macro
- in case of failure setting performance mode make sure CQL mode is
enabled again before returning.
- Clean up initialisation and error handling code
drivers/platform/x86/thinkpad_acpi.c | 306 ++++++++++++++++++++++++++-
1 file changed, 305 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 6a4c54db38fb..8463170391f5 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -72,6 +72,7 @@
#include <linux/uaccess.h>
#include <acpi/battery.h>
#include <acpi/video.h>
+#include <linux/platform_profile.h>
/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
@@ -9971,6 +9972,296 @@ 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_QUIET 3 /* Low power mode aka quiet */
+#define DYTC_MODE_BALANCE 0xF /* Default mode aka balance */
+
+#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_ignore_next_event;
+static bool dytc_profile_available;
+static enum platform_profile_option dytc_current_profile;
+
+static int dytc_command(int command, int *output)
+{
+ acpi_handle 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", command))
+ return -EIO;
+ return 0;
+}
+
+static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
+{
+ switch (dytcmode) {
+ case DYTC_MODE_QUIET:
+ *profile = platform_profile_low;
+ break;
+ case DYTC_MODE_BALANCE:
+ *profile = platform_profile_balance;
+ break;
+ case DYTC_MODE_PERFORM:
+ *profile = platform_profile_perform;
+ 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:
+ *perfmode = DYTC_MODE_QUIET;
+ break;
+ case platform_profile_balance:
+ *perfmode = DYTC_MODE_BALANCE;
+ break;
+ case platform_profile_perform:
+ *perfmode = DYTC_MODE_PERFORM;
+ break;
+ default: /* Unknown profile */
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+static int dytc_perfmode_get(int *perfmode, int *funcmode)
+{
+ int output, err, cmd_err;
+
+ if (!dytc_profile_available)
+ return -ENODEV;
+
+ err = dytc_command(DYTC_CMD_GET, &output);
+ if (err)
+ return err;
+
+ *funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF;
+ if (*funcmode == DYTC_FUNCTION_CQL) {
+ int dummy;
+ /*
+ * We can't get the mode when in CQL mode - so we disable CQL
+ * mode retrieve the mode and then enable it again.
+ * As disabling/enabling CQL triggers an event we set a flag to
+ * ignore these events. This will be cleared by the event handler
+ */
+ dytc_ignore_next_event = true;
+ err = dytc_command(DYTC_DISABLE_CQL, &dummy);
+ if (err)
+ return err;
+
+ cmd_err = dytc_command(DYTC_CMD_GET, &output);
+ /* Check return condition after we've restored CQL state */
+
+ /* Again ignore this event */
+ dytc_ignore_next_event = true;
+ err = dytc_command(DYTC_ENABLE_CQL, &dummy);
+ if (err)
+ return err;
+ if (cmd_err)
+ return cmd_err;
+ }
+ *perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
+ return 0;
+}
+
+/*
+ * dytc_profile_get: Function to register with platform_profile
+ * handler. Returns current platform profile.
+ */
+int dytc_profile_get(enum platform_profile_option *profile)
+{
+ int funcmode, perfmode;
+ int err;
+
+ err = dytc_perfmode_get(&perfmode, &funcmode);
+ if (err)
+ return err;
+
+ /* Convert Lenovo DYTC profile to platform_profile */
+ err = convert_dytc_to_profile(perfmode, profile);
+ if (err)
+ return err;
+
+ dytc_current_profile = *profile;
+ return 0;
+}
+
+/*
+ * dytc_profile_set: Function to register with platform_profile
+ * handler. Sets current platform profile.
+ */
+int dytc_profile_set(enum platform_profile_option profile)
+{
+ int cur_perfmode, cur_funcmode;
+ int output;
+ int err;
+
+ if (!dytc_profile_available)
+ return -ENODEV;
+
+ if (profile == platform_profile_balance) {
+ /* To get back to balance mode we just issue a reset command */
+ err = dytc_command(DYTC_CMD_RESET, &output);
+ if (err)
+ return err;
+ } else {
+ int perfmode;
+ int cmd_err;
+
+ err = convert_profile_to_dytc(profile, &perfmode);
+ if (err)
+ return err;
+
+ /* Determine if we are in CQL mode. This alters the commands we do */
+ err = dytc_perfmode_get(&cur_perfmode, &cur_funcmode);
+ if (err)
+ return err;
+
+ if (cur_funcmode == DYTC_FUNCTION_CQL) {
+ /* To set the mode we need to disable CQL first*/
+ dytc_ignore_next_event = true; /* Ignore event */
+ err = dytc_command(DYTC_DISABLE_CQL, &output);
+ if (err)
+ return err;
+ }
+ cmd_err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1),
+ &output);
+ /* Check return condition after we've restored CQL state */
+
+ if (cur_funcmode == DYTC_FUNCTION_CQL) {
+ dytc_ignore_next_event = true; /* Ignore event */
+ err = dytc_command(DYTC_ENABLE_CQL, &output);
+ if (err)
+ return err;
+ }
+ if (cmd_err)
+ return cmd_err;
+ }
+ /* Success - update current profile */
+ dytc_current_profile = profile;
+ return 0;
+}
+
+static void dytc_profile_refresh(void)
+{
+ enum platform_profile_option profile;
+ int perfmode, funcmode;
+ int err;
+
+ err = dytc_perfmode_get(&perfmode, &funcmode);
+ if (err)
+ return;
+
+ err = 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 = {
+ .choices = BIT(platform_profile_low) |
+ BIT(platform_profile_balance) |
+ BIT(platform_profile_perform),
+ .profile_get = dytc_profile_get,
+ .profile_set = dytc_profile_set,
+};
+
+static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
+{
+ int err, output;
+
+ dytc_profile_available = false;
+ dytc_ignore_next_event = 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 */
+ do {
+ err = platform_profile_register(&dytc_profile);
+ } while (err == -EINTR);
+ /*
+ * If for some reason platform_profiles aren't enabled
+ * don't quit terminally.
+ */
+ if (err)
+ return 0;
+ dytc_profile_available = true;
+ }
+ }
+ return 0;
+}
+
+static void dytc_profile_exit(void)
+{
+ if (dytc_profile_available) {
+ dytc_profile_available = false;
+ platform_profile_unregister();
+ }
+}
+
+static struct ibm_struct dytc_profile_driver_data = {
+ .name = "dytc-profile",
+ .exit = dytc_profile_exit,
+};
+#endif /* CONFIG_ACPI_PLATFORM_PROFILE */
+
/****************************************************************************
****************************************************************************
*
@@ -10019,8 +10310,15 @@ 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 (dytc_ignore_next_event)
+ dytc_ignore_next_event = false; /*clear setting*/
+ else
+ dytc_profile_refresh();
+#endif
+ }
}
static void hotkey_driver_event(const unsigned int scancode)
@@ -10463,6 +10761,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
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
--
2.28.0
next prev parent reply other threads:[~2020-11-26 16:52 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-11-26 16:51 [PATCH v4 1/3] Documentation: Add documentation for new platform_profile sysfs attribute Mark Pearson
2020-11-26 16:51 ` [PATCH v4 2/3] ACPI: platform-profile: Add platform profile support Mark Pearson
2020-11-27 19:14 ` Barnabás Pőcze
2020-11-29 1:07 ` [External] " Mark Pearson
2020-11-28 14:08 ` Hans de Goede
2020-11-28 15:37 ` Barnabás Pőcze
2020-11-29 1:19 ` [External] " Mark Pearson
2020-11-29 11:44 ` Hans de Goede
2020-11-29 1:16 ` Mark Pearson
2020-11-26 16:51 ` Mark Pearson [this message]
2020-11-27 19:22 ` [PATCH v4 3/3] platform/x86: thinkpad_acpi: " Barnabás Pőcze
2020-11-28 15:00 ` Hans de Goede
2020-11-28 15:59 ` Barnabás Pőcze
2020-11-29 1:34 ` [External] " Mark Pearson
2020-11-28 14:55 ` Hans de Goede
2020-12-01 16:51 ` [External] " Mark Pearson
2020-12-01 20:44 ` Hans de Goede
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20201126165143.32776-3-markpearson@lenovo.com \
--to=markpearson@lenovo.com \
--cc=bberg@redhat.com \
--cc=dvhart@infradead.org \
--cc=eliadevito@gmail.com \
--cc=hadess@hadess.net \
--cc=hdegoede@redhat.com \
--cc=linux-acpi@vger.kernel.org \
--cc=mario.limnociello@dell.com \
--cc=mgross@linux.intel.com \
--cc=platform-driver-x86@vger.kernel.org \
--cc=pobrn@protonmail.com \
--cc=rjw@rjwysocki.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox