Hi,

On 1/5/21 2:14 PM, Jiaxun Yang wrote:
> Add support to ideapad-laptop for Lenovo platforms that have DYTC
> version 5 support or newer to use the platform profile feature.
> 
> Mostly based on Mark Pearson <markpear...@lenovo.com>'s thinkpad-acpi
> work but massaged to fit ideapad driver.
> 
> Note that different from ThinkPads, IdeaPads's Thermal Hotkey won't
> trigger profile switch itself, we'll leave it for userspace programs.
> 
> Tested on Lenovo Yoga-14S ARE Chinese Edition.
> 
> Signed-off-by: Jiaxun Yang <jiaxun.y...@flygoat.com>

Now that the acpi/platform_profile stuff has landed I've merged this patch.

Note I've made one small but important change I've replaced all
occurrences of QUIET with LOW_POWER as was done in the latest revision
of the thinkpad_acpi change.

This is not only a cosmetical thing, it also fixes an actual bug:

> +static int convert_dytc_to_profile(int dytcmode, enum 
> platform_profile_option *profile)
> +{
> +     switch (dytcmode) {
> +     case DYTC_MODE_QUIET:
> +             *profile = PLATFORM_PROFILE_LOW_POWER;
> +             break;

QUIET -> LOW_POWER, ok there actually is a PLATFORM_PROFILE_QUIET
too, but according to the docs LOW_POWER actually is a better match
(hence the rename).

> +     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_QUIET:
> +             *perfmode = DYTC_MODE_QUIET;

QUIET -> QUIET erm, but we weren't using PLATFORM_PROFILE_QUIET in this driver 
at all, also see:

> +     /* Setup supported modes */
> +     set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices);
> +     set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices);
> +     set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices);

See we are not advertising PLATFORM_PROFILE_LOW_POWER and above
we actually map DYTC_MODE_QUIET -> PLATFORM_PROFILE_LOW_POWER

So we should do the reverse here.

Anyways replacing all instances of QUIET with LOW_POWER clears up
the confusion which lead to this bug and also actually fixes the bug.

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

Thank you for your patch, I've applied this patch to my review-hans 
branch:
https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans

Note it will show up in my review-hans branch once I've pushed my
local branch there, which might take a while.

Once I've run some tests on this branch the patches there will be
added to the platform-drivers-x86/for-next branch and eventually
will be included in the pdx86 pull-request to Linus for the next
merge-window.

Regards,

Hans




> ---
>  drivers/platform/x86/Kconfig          |   1 +
>  drivers/platform/x86/ideapad-laptop.c | 289 ++++++++++++++++++++++++++
>  2 files changed, 290 insertions(+)
> 
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 91e6176cdfbd..8059143c21bb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -624,6 +624,7 @@ config IDEAPAD_LAPTOP
>       depends on BACKLIGHT_CLASS_DEVICE
>       depends on ACPI_VIDEO || ACPI_VIDEO = n
>       depends on ACPI_WMI || ACPI_WMI = n
> +     depends on ACPI_PLATFORM_PROFILE
>       select INPUT_SPARSEKMAP
>       help
>         This is a driver for Lenovo IdeaPad netbooks contains drivers for
> diff --git a/drivers/platform/x86/ideapad-laptop.c 
> b/drivers/platform/x86/ideapad-laptop.c
> index 7598cd46cf60..5512367c604a 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -15,6 +15,7 @@
>  #include <linux/acpi.h>
>  #include <linux/rfkill.h>
>  #include <linux/platform_device.h>
> +#include <linux/platform_profile.h>
>  #include <linux/input.h>
>  #include <linux/input/sparse-keymap.h>
>  #include <linux/backlight.h>
> @@ -77,6 +78,13 @@ enum {
>       VPCCMD_W_BL_POWER = 0x33,
>  };
>  
> +struct ideapad_dytc_priv {
> +     enum platform_profile_option current_profile;
> +     struct platform_profile_handler pprof;
> +     struct mutex mutex;
> +     struct ideapad_private *priv;
> +};
> +
>  struct ideapad_rfk_priv {
>       int dev;
>       struct ideapad_private *priv;
> @@ -89,6 +97,7 @@ struct ideapad_private {
>       struct platform_device *platform_device;
>       struct input_dev *inputdev;
>       struct backlight_device *blightdev;
> +     struct ideapad_dytc_priv *dytc;
>       struct dentry *debug;
>       unsigned long cfg;
>       bool has_hw_rfkill_switch;
> @@ -136,6 +145,28 @@ static int method_int1(acpi_handle handle, char *method, 
> int cmd)
>       return ACPI_FAILURE(status) ? -1 : 0;
>  }
>  
> +static int method_dytc(acpi_handle handle, int cmd, int *ret)
> +{
> +     acpi_status status;
> +     unsigned long long result;
> +     struct acpi_object_list params;
> +     union acpi_object in_obj;
> +
> +     params.count = 1;
> +     params.pointer = &in_obj;
> +     in_obj.type = ACPI_TYPE_INTEGER;
> +     in_obj.integer.value = cmd;
> +
> +     status = acpi_evaluate_integer(handle, "DYTC", &params, &result);
> +
> +     if (ACPI_FAILURE(status)) {
> +             *ret = -1;
> +             return -1;
> +     }
> +     *ret = result;
> +     return 0;
> +}
> +
>  static int method_vpcr(acpi_handle handle, int cmd, int *ret)
>  {
>       acpi_status status;
> @@ -546,6 +577,257 @@ static const struct attribute_group 
> ideapad_attribute_group = {
>       .attrs = ideapad_attributes
>  };
>  
> +/*
> + * DYTC Platform profile
> + */
> +#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_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 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 int convert_dytc_to_profile(int dytcmode, enum 
> platform_profile_option *profile)
> +{
> +     switch (dytcmode) {
> +     case DYTC_MODE_QUIET:
> +             *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_QUIET:
> +             *perfmode = DYTC_MODE_QUIET;
> +             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)
> +{
> +     struct ideapad_dytc_priv *dytc;
> +
> +     dytc = container_of(pprof, struct ideapad_dytc_priv, pprof);
> +     *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(struct ideapad_private *priv, 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 = method_dytc(priv->adev->handle, 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) {
> +             err = method_dytc(priv->adev->handle, DYTC_DISABLE_CQL, &dummy);
> +             if (err)
> +                     return err;
> +     }
> +
> +     cmd_err = method_dytc(priv->adev->handle, command,      output);
> +     /* Check return condition after we've restored CQL state */
> +
> +     if (cur_funcmode == DYTC_FUNCTION_CQL) {
> +             err = method_dytc(priv->adev->handle, 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)
> +{
> +     struct ideapad_dytc_priv *dytc;
> +     struct ideapad_private *priv;
> +     int output;
> +     int err;
> +
> +     dytc = container_of(pprof, struct ideapad_dytc_priv, pprof);
> +     priv = dytc->priv;
> +
> +     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 = method_dytc(priv->adev->handle, 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(priv,
> +                             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(struct ideapad_private *priv)
> +{
> +     enum platform_profile_option profile;
> +     int output, err;
> +     int perfmode;
> +
> +     mutex_lock(&priv->dytc->mutex);
> +     err = dytc_cql_command(priv, DYTC_CMD_GET, &output);
> +     mutex_unlock(&priv->dytc->mutex);
> +     if (err)
> +             return;
> +
> +     perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF;
> +     convert_dytc_to_profile(perfmode, &profile);
> +     if (profile != priv->dytc->current_profile) {
> +             priv->dytc->current_profile = profile;
> +             platform_profile_notify();
> +     }
> +}
> +
> +static int ideapad_dytc_profile_init(struct ideapad_private *priv)
> +{
> +     int err, output, dytc_version;
> +
> +     err = method_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output);
> +     /* 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)))
> +             return -ENODEV;
> +
> +     dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF;
> +     if (dytc_version < 5)
> +             return -ENODEV;
> +
> +     priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL);
> +     if (!priv->dytc)
> +             return -ENOMEM;
> +
> +     mutex_init(&priv->dytc->mutex);
> +
> +     priv->dytc->priv = priv;
> +     priv->dytc->pprof.profile_get = dytc_profile_get;
> +     priv->dytc->pprof.profile_set = dytc_profile_set;
> +
> +     /* Setup supported modes */
> +     set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices);
> +     set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices);
> +     set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices);
> +
> +     /* Create platform_profile structure and register */
> +     err = platform_profile_register(&priv->dytc->pprof);
> +     if (err)
> +             goto mutex_destroy;
> +
> +     /* Ensure initial values are correct */
> +     dytc_profile_refresh(priv);
> +
> +     return 0;
> +
> +mutex_destroy:
> +     mutex_destroy(&priv->dytc->mutex);
> +     kfree(priv->dytc);
> +     priv->dytc = NULL;
> +     return err;
> +}
> +
> +static void ideapad_dytc_profile_exit(struct ideapad_private *priv)
> +{
> +     if (!priv->dytc)
> +             return;
> +
> +     platform_profile_remove();
> +     mutex_destroy(&priv->dytc->mutex);
> +     kfree(priv->dytc);
> +     priv->dytc = NULL;
> +}
> +
>  /*
>   * Rfkill
>   */
> @@ -1013,6 +1295,8 @@ static int ideapad_acpi_add(struct platform_device 
> *pdev)
>       ideapad_sync_rfk_state(priv);
>       ideapad_sync_touchpad_state(priv);
>  
> +     ideapad_dytc_profile_init(priv);
> +
>       if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
>               ret = ideapad_backlight_init(priv);
>               if (ret && ret != -ENODEV)
> @@ -1066,6 +1350,7 @@ static int ideapad_acpi_remove(struct platform_device 
> *pdev)
>       acpi_remove_notify_handler(priv->adev->handle,
>               ACPI_DEVICE_NOTIFY, ideapad_acpi_notify);
>       ideapad_backlight_exit(priv);
> +     ideapad_dytc_profile_exit(priv);
>       for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++)
>               ideapad_unregister_rfkill(priv, i);
>       ideapad_input_exit(priv);
> @@ -1087,6 +1372,10 @@ static int ideapad_acpi_resume(struct device *device)
>  
>       ideapad_sync_rfk_state(priv);
>       ideapad_sync_touchpad_state(priv);
> +
> +     if (priv->dytc)
> +             dytc_profile_refresh(priv);
> +
>       return 0;
>  }
>  #endif
> 

Reply via email to