>
> Lenovo Thinkpad platforms with DYTC version 5 and newer have enhanced
> firmware to provide different performance/thermal modes.
>
> The modes can be controlled by hotkeys (FN+H, FN+M, FN+L) to switch
> the operating mode between three different modes.
>
> H - High performance. Maximum power is available and the temperature is
> allowed to increase to the maximum for the platform.
> M - Medium performance (aka balance). In this mode power will be limited
> and the laptop will have a lower maximum temperature.
> L - Low performance (aka quiet). In this mode power consumption is reduced
> and the device will be cooler.
>
> High performance mode is only available when the device is in 'desk mode'.
> If the device detects that it is on a lap then it will automatically drop
> into medium mode to maintain a safer operating temperature.
>
> This patch provides an interface to determine the current mode and to also
> allow the setting of the mode through the dytc_perfmode sysfs entry. This
> can be used by userspace for improved control.
>
> Reviewed-by: Nitin Joshi <nj...@le...>
> Signed-off-by: Mark Pearson <mar...@le...>
> ---
> Changes in v2:
> - Add userspace notification to hkey event handler. Note this got
> somewhat more complicated than expected as retrieving the performance
> mode when in lapmode generates an event itself; added a flag to
> ignore these known events.
> - Updated the values returned to be simpler (H/M*/M/L) as suggested.
> - Added ABI documentation as requested. Lap and palm sensor details
> added whilst I was doing this.
> - Cleaned up code based on recommendations as well as feedback received
> from other patch reviews.
> - Based on discussion with firmware team the lapmode sensor should only
> be made available for DYTC v5 and later. Added this to init logic.
Thanks for adapting a lot of my feedback, this looks much better to me.
>
> .../sysfs-devices-platform-thinkpad_acpi | 34 +++
> .../admin-guide/laptops/thinkpad-acpi.rst | 35 +++
> drivers/platform/x86/thinkpad_acpi.c | 267 ++++++++++++++++--
> 3 files changed, 310 insertions(+), 26 deletions(-)
> create mode 100644 Documentation/ABI/testing/sysfs-devices-platform-
> thinkpad_acpi
>
> diff --git a/Documentation/ABI/testing/sysfs-devices-platform-thinkpad_acpi
> b/Documentation/ABI/testing/sysfs-devices-platform-thinkpad_acpi
> new file mode 100644
> index 000000000000..28f07753a889
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-devices-platform-thinkpad_acpi
> @@ -0,0 +1,34 @@
> +What: /sys/devices/platform/thinkpad_acpi/dytc_perfmode
> +Date: August 2020
> +Contact: Mark Pearson <mar...@le...>
> +Description:
> + Reads return the current performance mode setting configured in
> firmware using
> + the below nomenclature.
> +
> + Writes configure the performance mode setting by using the below
> nomenclature.
> +
> + H - High performance mode. Maximum power and temperature
> available.
> + M* - High performance mode but performance is limited to medium as
> system is
> + in lapmode. Power and temperature maximums reduced to a safe
> threshold.
> + M - Medium performance mode (aka 'balance'). Lower maximum power
> and temperatures
> + but better battery life.
> + L - Low performance mode (aka 'quiet'). Reduced power setting
> gives lower
> + temperatures and extended battery life. Fans run quieter.
> +
> +What: /sys/devices/platform/thinkpad_acpi/dytc_lapmode
> +Date: August 2020
> +Contact: Mark Pearson <mar...@le...>
> +Description:
> + Reads returns the current value of the lapmode sensor.
> +
> + 0 - desk mode is detected
> + 1 - lap mode is detected
> +
> +What: /sys/devices/platform/thinkpad_acpi/psensor_state
> +Date: August 2020
> +Contact: Nitin Joshi <nj...@le...>
> +Description:
> + Reads returns the current value of the palm sensor.
> +
> + 0 - palm not detected
> + 1 - palm detected
> diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
> b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
> index 6b57c52d8f13..b98f0de9e063 100644
> --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
> +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
> @@ -52,6 +52,7 @@ detailed description):
> - LCD Shadow (PrivacyGuard) enable and disable
> - Lap mode sensor
> - Palm sensor (aka psensor)
> + - Thermal mode status and control
>
> A compatibility table by model and feature is maintained on the web
> site, http://ibm-acpi.sf.net/. I appreciate any success or failure
> @@ -1465,6 +1466,40 @@ Note - some platforms have a limitation whereby the EC
> firmware cannot
> determine if the sensor is installed or not. On these platforms the psensor
> state will always be reported as true to avoid high power being used
> incorrectly.
>
> +DYTC Thermal mode status and control
> +------------------------------------
> +
> +sysfs: dytc_perfmode
> +
> +Lenovo Thinkpad platforms with DYTC version 5 and newer have enhanced
> firmware to
> +provide improved performance control.
> +
> +The firmware can be controlled by hotkeys (FN+H, FN+M, FN+L) to switch the
> +operating mode between three different modes. This sysfs node provides a
> better
> +interface for user space to use.
> +
> +The modes available are:
> +
> +H - High performance. Maximum power is available and the temperature is
> +allowed to increase to the maximum for the platform.
> +
> +M - Medium performance (aka balance). In this mode power will be limited and
> +the laptop will remain cooler.
> +
> +L - Low performance (aka quiet). In this mode power consumption is reduced
> and
> +the device will be cooler and quieter.
> +
> +Note: High performance mode is only available when the device is in
> 'deskmode'. If
> +the device detects that it is on a lap then it will automatically drop into
> medium
> +mode to maintain a safer operating temperature.
> +
> +The sysfs entry provides the ability to return the current status and to set
> the
> +desired mode. For example::
> +
> + echo H > /sys/devices/platform/thinkpad_acpi/dytc_perfmode
> + echo M > /sys/devices/platform/thinkpad_acpi/dytc_perfmode
> + echo L > /sys/devices/platform/thinkpad_acpi/dytc_perfmode
> +
I was thinking about this some more, do you actually want another mode that "disables"
this feature? IE "O" turns it off an calls DYTC_DISABLE_CQL.
For example if a user wanted to test the recently landed code in thermald 2.3
and compare performance between the two it seems like this and that "might" fight.
As an outsider looking in - I of course may be wrong too here.
If at some point in the future thermald does a better job than this implementation you
might also want an "out" to let thermald or another piece of userland turn this off
if it's in the picture.
> EXPERIMENTAL: UWB
> -----------------
>
> diff --git a/drivers/platform/x86/thinkpad_acpi.c
> b/drivers/platform/x86/thinkpad_acpi.c
> index 41b75dd4755c..8fcb660aa5a2 100644
> --- a/drivers/platform/x86/thinkpad_acpi.c
> +++ b/drivers/platform/x86/thinkpad_acpi.c
> @@ -9817,18 +9817,43 @@ static struct ibm_struct lcdshadow_driver_data = {
> };
>
> /*************************************************************************
> - * DYTC subdriver, for the Lenovo lapmode feature
> + * DYTC subdriver, for the Lenovo lap and performance mode feature
> */
>
> +#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_GET 2 /* To get current IC function and mode */
> -#define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */
> +#define DYTC_CMD_RESET 0x1ff /* To reset back to default */
>
> -static bool dytc_lapmode;
> +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */
> +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revisision */
> +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */
>
> -static void dytc_lapmode_notify_change(void)
> -{
> - sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
> -}
> +#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */
> +#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */
> +#define DYTC_GET_LAPMODE_BIT 17 /* Bit 17 - lapmode. Set when on lap */
> +
> +#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - funct 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_DISABLE_CQL ((DYTC_MODE_BALANCE << DYTC_SET_MODE_BIT) | \
> + (DYTC_FUNCTION_CQL << DYTC_SET_FUNCTION_BIT) | \
> + DYTC_CMD_SET)
> +#define DYTC_ENABLE_CQL (DYTC_DISABLE_CQL | (1 << DYTC_SET_VALID_BIT))
> +
> +static bool dytc_lapmode;
> +static int dytc_perfmode;
> +static bool dytc_available;
> +static bool dytc_ignore_next_event;
>
> static int dytc_command(int command, int *output)
> {
> @@ -9843,6 +9868,87 @@ static int dytc_command(int command, int *output)
> return 0;
> }
>
> +static int dytc_perfmode_get(int *perfmode, int *funcmode)
> +{
> + int output, err;
> +
> + if (!dytc_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;
> + err = dytc_command(DYTC_CMD_GET, &output);
> + if (err)
> + return err;
> + /* Again ignore this event */
> + dytc_ignore_next_event = true;
> + err = dytc_command(DYTC_ENABLE_CQL, &dummy);
> + if (err)
> + return err;
> + }
> + *perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
> + return 0;
> +}
> +
> +static int dytc_perfmode_set(int perfmode)
> +{
> + int err, dytc_set;
> + int output;
> + int cur_perfmode, cur_funcmode;
> +
> + if (!dytc_available)
> + return -ENODEV;
> +
> + if (perfmode == DYTC_MODE_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 {
> + /* 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;
> + }
> + dytc_set = (1 << DYTC_SET_VALID_BIT) |
> + (DYTC_FUNCTION_MMC << DYTC_SET_FUNCTION_BIT) |
> + (perfmode << DYTC_SET_MODE_BIT) |
> + DYTC_CMD_SET;
> + err = dytc_command(dytc_set, &output);
> + if (err)
> + return err;
> + if (cur_funcmode == DYTC_FUNCTION_CQL) {
> + dytc_ignore_next_event = true; /*ignore event*/
> + err = dytc_command(DYTC_ENABLE_CQL, &output);
> + if (err)
> + return err;
> + }
> + }
> + return 0;
> +}
> +
> static int dytc_lapmode_get(bool *state)
> {
> int output, err;
> @@ -9854,45 +9960,130 @@ static int dytc_lapmode_get(bool *state)
> return 0;
> }
>
> -static void dytc_lapmode_refresh(void)
> +static void dytc_refresh(void)
> {
> - bool new_state;
> + bool lapmode;
> + int perfmode, funcmode;
> int err;
>
> - err = dytc_lapmode_get(&new_state);
> - if (err || (new_state == dytc_lapmode))
> + err = dytc_lapmode_get(&lapmode);
> + if (err)
> + return;
> + if (dytc_ignore_next_event) {
> + dytc_ignore_next_event = false; /*clear setting*/
> return;
> + }
> + if (lapmode != dytc_lapmode) {
> + dytc_lapmode = lapmode;
> + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_lapmode");
> + }
> + err = dytc_perfmode_get(&perfmode, &funcmode);
> + if (err)
> + return;
> + if (perfmode != dytc_perfmode) {
> + dytc_perfmode = perfmode;
> + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "dytc_perfmode");
> + }
> +}
> +
> +/* sysfs perfmode entry */
> +static ssize_t dytc_perfmode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int err;
> + int perfmode, funcmode;
> +
> + err = dytc_perfmode_get(&perfmode, &funcmode);
> + if (err)
> + return err;
>
> - dytc_lapmode = new_state;
> - dytc_lapmode_notify_change();
> + switch (perfmode) {
> + case DYTC_MODE_PERFORM:
> + /* High performance is only available in deskmode */
> + if (funcmode == DYTC_FUNCTION_CQL)
> + return sprintf(buf, "M*\n");
> + else
> + return sprintf(buf, "H\n");
> + case DYTC_MODE_QUIET:
> + return sprintf(buf, "L\n");
> + case DYTC_MODE_BALANCE:
> + return sprintf(buf, "M\n");
> + default:
> + return sprintf(buf, "Unknown (%d)\n", perfmode);
> + }
> }
>
> +static ssize_t dytc_perfmode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int err;
> +
> + switch (buf[0]) {
> + case 'l':
> + case 'L':
> + err = dytc_perfmode_set(DYTC_MODE_QUIET);
> + break;
> + case 'm':
> + case 'M':
> + err = dytc_perfmode_set(DYTC_MODE_BALANCE);
> + break;
> + case 'h':
> + case 'H':
> + err = dytc_perfmode_set(DYTC_MODE_PERFORM);
> + break;
> + default:
> + err = -EINVAL;
> + pr_err("Unknown operating mode. Ignoring\n");
> + break;
> + }
> + if (err)
> + return err;
> +
> + tpacpi_disclose_usertask(attr->attr.name,
> + "Performance mode set to %c\n", buf[0]);
> + return count;
> +}
> +
> +static DEVICE_ATTR_RW(dytc_perfmode);
> +
> +static struct attribute *dytc_perfmode_attributes[] = {
> + &dev_attr_dytc_perfmode.attr,
> + NULL
> +};
> +
> +static const struct attribute_group dytc_perf_attr_group = {
> + .attrs = dytc_perfmode_attributes
> +};
> +
> /* sysfs lapmode entry */
> static ssize_t dytc_lapmode_show(struct device *dev,
> struct device_attribute *attr,
> char *buf)
> {
> - return snprintf(buf, PAGE_SIZE, "%d\n", dytc_lapmode);
> + return sprintf(buf, "%d\n", dytc_lapmode);
> }
>
> static DEVICE_ATTR_RO(dytc_lapmode);
>
> -static struct attribute *dytc_attributes[] = {
> +static struct attribute *dytc_lap_attributes[] = {
> &dev_attr_dytc_lapmode.attr,
> - NULL,
> + NULL
> };
>
> -static const struct attribute_group dytc_attr_group = {
> - .attrs = dytc_attributes,
> +static const struct attribute_group dytc_lap_attr_group = {
> + .attrs = dytc_lap_attributes
> };
>
> static int tpacpi_dytc_init(struct ibm_init_struct *iibm)
> {
> - int err;
> + int err, output;
>
> - err = dytc_lapmode_get(&dytc_lapmode);
> - /* If support isn't available (ENODEV) then don't return an error
> - * but just don't create the sysfs group
> + err = dytc_command(DYTC_CMD_QUERY, &output);
> + /*
> + * If support isn't available (ENODEV) then don't return an error
> + * just don't create the sysfs group
> */
> if (err == -ENODEV)
> return 0;
> @@ -9900,14 +10091,38 @@ static int tpacpi_dytc_init(struct ibm_init_struct
> *iibm)
> if (err)
> return err;
>
> - /* Platform supports this feature - create the group */
> - err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
> + /* Check DYTC is enabled and supports mode setting */
> + dytc_available = false;
> + dytc_ignore_next_event = false;
> + 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);
> + dytc_available = true;
> + /* Platform supports this feature - create the group */
> + err = sysfs_create_group(&tpacpi_pdev->dev.kobj,
> &dytc_perf_attr_group);
> + if (err)
> + return err;
> +
> + err = dytc_lapmode_get(&dytc_lapmode);
> + if (err)
> + return err;
> + err = sysfs_create_group(&tpacpi_pdev->dev.kobj,
> &dytc_lap_attr_group);
> + }
> + }
> return err;
> }
>
> static void dytc_exit(void)
> {
> - sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_attr_group);
> + if (dytc_available) {
> + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_lap_attr_group);
> + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &dytc_perf_attr_group);
> + }
> }
>
> static struct ibm_struct dytc_driver_data = {
> @@ -10057,7 +10272,7 @@ static void tpacpi_driver_event(const unsigned int
> hkey_event)
> }
>
> if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED)
> - dytc_lapmode_refresh();
> + dytc_refresh();
>
> if ((hkey_event == TP_HKEY_EV_PALM_DETECTED) ||
> (hkey_event == TP_HKEY_EV_PALM_UNDETECTED))
> --
> 2.26.2
|