Re: [PATCH v4 3/3] platform/x86: thinkpad_acpi: Add platform profile support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

On 11/26/20 5:51 PM, Mark Pearson wrote:
> 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@xxxxxxxxxx>
> ---
> 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>

Please group this together with the other linux/foo.h includes.

>  
>  /* 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;

Are we sure the event-handler will have run before we do this second
setting of the ignore_next_event bool? Maybe make it an atomic integer
and increment / decrement the variable ?

E.g.:

Declaration:

static atomic_t dytc_ignore_next_event = ATOMIC_INIT();

Ignore next event:
		atomic_inc(&dytc_ignore_next_event);
		
Check if event should be ignored:

		if (!atomic_add_unless(&dytc_ignore_next_event, -1, 0))
			dytc_profile_refresh();

Note atomic_add_unless may needs some explanation, it adds -1 unless
the atomic_t already contains 0. And it returns true if the addition
was done. so if it returns true then dytc_ignore_next_event was not 0
(it might be zero afterwards).

IOW if atomic_add_unless returns true then dytc_ignore_next_event was true,
so then we should NOT continue with the refresh.




> +		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;

Can't we used a cached value here ? I presume we get an
event when this is changed by the hotkey ? Esp. with the
whole enable/disable CQL dance getting the value seems a
bit expensive, so using a cached value might be better?

> +
> +	/* 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;
> +		}

This seems somewhat duplicated from the get() code-path. Also you already doing
a DYTC_DISABLE_CQL and DYTC_ENABLE_CQL in dytc_perfmode_get, which is not necessary
to just get the funcmode which is all you need here AFAICT.

IOW it seems that when CQL is active you are now doing:

1. dytc_perfmode_get() calls DYTC_CMD_GET
2. dytc_perfmode_get() calls DYTC_DISABLE_CQL
3. dytc_perfmode_get() calls DYTC_CMD_GET again, result is ignored (not used by dytc_profile_set)
4. dytc_perfmode_get() calls DYTC_ENABLE_CQL
5. dytc_profile_set() calls DYTC_DISABLE_CQL
6. dytc_profile_set() calls DYTC_SET_COMMAND
7. dytc_profile_set() calls DYTC_ENABLE_CQL

And you can really skip step 2-4 here.

I think it would be good to add a bunch of helpers:

1. dytc_get_modes() -> DYTC_CMD_GET wrapper gets both modes, sets perfmode
to -1 when funcmode is CQL
2. dytc_disable_cql_if_necessary() which takes funcmode as argument and is
a no-op when funcmode != CQL
3. dytc_re_enable_cql_if_necessary() idem.

And then the flow in dytc_perfmode_get could look something like this
(pseudo code minus error handling):

	dytc_get_modes(&funcmode, &perfmode)
	if (funcmode != CQL) /* or alternatively check for perfmode != -1 */
		return success;

	dytc_disable_cql_if_necessary(funcmode);
	dytc_get_modes(NULL, &perfmode);
	dytc_disable_cql_if_necessary(funcmode);

And in the non-balanced path of dytc_profile_set:

	dytc_get_modes(&funcmode, NULL)

	dytc_disable_cql_if_necessary(funcmode);
	dytc_set_mode(...);
	dytc_disable_cql_if_necessary(funcmode);

Note the NULL could be a dummy, but I find NULL a bit cleaner
(at the cost of having to check for it in dytc_get_modes).

This is is just from a quick peek, when you implement this
it might turn out to be less then ideal, IOW this is just
a suggestion, feel free to deviate.

###

Since this will require a bit of work, timing wise (wrt the 5.11 merge-window)
it might be best to just keep this patch as is for v5, and only change
patch 1 and 2 of the set, so that those will hopefully be ready for
merging in time for the 5.11 window. I plan to pick this one up
once 5.11-rc1 is out (and has the necessary ACPI bits) so we have some
more time to get this one in shape.

For patch 1/2 the most important thing is to have a consumer of the
new internal APIs (almost) ready and this code fulfills that in
its current form.

Regards,

Hans









> +		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)
> 




[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux