diff mbox series

Add IdeaPad quick_charge attribute to sysfs

Message ID 20220911161718.24284-1-p.jungkamp@gmx.net (mailing list archive)
State Changes Requested, archived
Headers show
Series Add IdeaPad quick_charge attribute to sysfs | expand

Commit Message

Philipp Jungkamp Sept. 11, 2022, 4:17 p.m. UTC
More recent IdeaPads allow USB-C quick-charging to be controlled via
ACPI. This seems to be mutually exclusive with the ACPI conservation
mode.

Expose a readable and writable 'quick_charge' sysfs attribute next when
support is indicated in ACPI.
---
I deduced the indicator bits from their names in the DSDT (QCHO and
QCHX). I don't have an IdeaPad except mine on hand and can't check
whether these are indeed the intended uses or their behaviour on other
IdeaPads. I can confirm that a change in the quick_charge toggle is
visible in Lenovo Vantage when dual booting into Windows 11.

Greetings,
Philipp Jungkamp

 drivers/platform/x86/ideapad-laptop.c | 64 ++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 2 deletions(-)

--
2.37.3

Comments

Barnabás Pőcze Sept. 11, 2022, 6:08 p.m. UTC | #1
Hi


2022. szeptember 11., vasárnap 18:17 keltezéssel, Philipp Jungkamp írta:
> More recent IdeaPads allow USB-C quick-charging to be controlled via
> ACPI. This seems to be mutually exclusive with the ACPI conservation
> mode.
> 
> Expose a readable and writable 'quick_charge' sysfs attribute next when
> support is indicated in ACPI.
> ---
> I deduced the indicator bits from their names in the DSDT (QCHO and
> QCHX). I don't have an IdeaPad except mine on hand and can't check
> whether these are indeed the intended uses or their behaviour on other
> IdeaPads. I can confirm that a change in the quick_charge toggle is
> visible in Lenovo Vantage when dual booting into Windows 11.
> 
> Greetings,
> Philipp Jungkamp

There is already an entry for this on the kernel bugzilla:
https://bugzilla.kernel.org/show_bug.cgi?id=216176

I have two concerns: one, it adds a new driver specific attribute for a somewhat
generic functionality; two, Lenovo Vantage does not only check this single bit
before allowing this mode to be enabled (as far as I can recall).


Regards,
Barnabás Pőcze


> 
>  drivers/platform/x86/ideapad-laptop.c | 64 ++++++++++++++++++++++++++-
>  1 file changed, 62 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
> index abd0c81d62c4..dea35779264a 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -54,12 +54,16 @@ enum {
>  };
> 
>  enum {
> -	GBMD_CONSERVATION_STATE_BIT = 5,
> +	GBMD_QUICK_CHARGE_STATE_BIT   = 2,
> +	GBMD_CONSERVATION_STATE_BIT   = 5,
> +	GBMD_QUICK_CHARGE_SUPPORT_BIT = 17,
>  };
> 
>  enum {
>  	SBMC_CONSERVATION_ON  = 3,
>  	SBMC_CONSERVATION_OFF = 5,
> +	SBMC_QUICK_CHARGE_ON  = 7,
> +	SBMC_QUICK_CHARGE_OFF = 8,
>  };
> 
>  enum {
> @@ -140,6 +144,7 @@ struct ideapad_private {
>  		bool kbd_bl               : 1;
>  		bool touchpad_ctrl_via_ec : 1;
>  		bool usb_charging         : 1;
> +		bool quick_charge         : 1;
>  	} features;
>  	struct {
>  		bool initialized;
> @@ -482,6 +487,12 @@ static ssize_t conservation_mode_store(struct device *dev,
>  	if (err)
>  		return err;
> 
> +	if (priv->features.quick_charge && state) {
> +		err = exec_sbmc(priv->adev->handle, SBMC_QUICK_CHARGE_OFF);
> +		if (err)
> +			return err;
> +	}
> +
>  	err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF);
>  	if (err)
>  		return err;
> @@ -491,6 +502,48 @@ static ssize_t conservation_mode_store(struct device *dev,
> 
>  static DEVICE_ATTR_RW(conservation_mode);
> 
> +static ssize_t quick_charge_show(struct device *dev,
> +				 struct device_attribute *attr,
> +				 char *buf)
> +{
> +	struct ideapad_private *priv = dev_get_drvdata(dev);
> +	unsigned long result;
> +	int err;
> +
> +	err = eval_gbmd(priv->adev->handle, &result);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_QUICK_CHARGE_STATE_BIT, &result));
> +}
> +
> +static ssize_t quick_charge_store(struct device *dev,
> +				  struct device_attribute *attr,
> +				  const char *buf, size_t count)
> +{
> +	struct ideapad_private *priv = dev_get_drvdata(dev);
> +	bool state;
> +	int err;
> +
> +	err = kstrtobool(buf, &state);
> +	if (err)
> +		return err;
> +
> +	if (priv->features.conservation_mode && state) {
> +		err = exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF);
> +		if (err)
> +			return err;
> +	}
> +
> +	err = exec_sbmc(priv->adev->handle, state ? SBMC_QUICK_CHARGE_ON : SBMC_QUICK_CHARGE_OFF);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR_RW(quick_charge);
> +
>  static ssize_t fan_mode_show(struct device *dev,
>  			     struct device_attribute *attr,
>  			     char *buf)
> @@ -641,6 +694,7 @@ static DEVICE_ATTR_RW(usb_charging);
>  static struct attribute *ideapad_attributes[] = {
>  	&dev_attr_camera_power.attr,
>  	&dev_attr_conservation_mode.attr,
> +	&dev_attr_quick_charge.attr,
>  	&dev_attr_fan_mode.attr,
>  	&dev_attr_fn_lock.attr,
>  	&dev_attr_touchpad.attr,
> @@ -660,6 +714,8 @@ static umode_t ideapad_is_visible(struct kobject *kobj,
>  		supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg);
>  	else if (attr == &dev_attr_conservation_mode.attr)
>  		supported = priv->features.conservation_mode;
> +	else if (attr == &dev_attr_quick_charge.attr)
> +		supported = priv->features.quick_charge;
>  	else if (attr == &dev_attr_fan_mode.attr)
>  		supported = priv->features.fan_mode;
>  	else if (attr == &dev_attr_fn_lock.attr)
> @@ -1546,9 +1602,13 @@ static void ideapad_check_features(struct ideapad_private *priv)
>  	if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
>  		priv->features.fan_mode = true;
> 
> -	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
> +	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
>  		priv->features.conservation_mode = true;
> 
> +		if (!eval_gbmd(handle,&val) && test_bit(GBMD_QUICK_CHARGE_SUPPORT_BIT, &val))
> +			priv->features.quick_charge = true;
> +	}
> +
>  	if (acpi_has_method(handle, "DYTC"))
>  		priv->features.dytc = true;
> 
> --
> 2.37.3
>
Hans de Goede Sept. 19, 2022, 9:08 a.m. UTC | #2
Hi,

On 9/11/22 19:08, Barnabás Pőcze wrote:
> Hi
> 
> 
> 2022. szeptember 11., vasárnap 18:17 keltezéssel, Philipp Jungkamp írta:
>> More recent IdeaPads allow USB-C quick-charging to be controlled via
>> ACPI. This seems to be mutually exclusive with the ACPI conservation
>> mode.
>>
>> Expose a readable and writable 'quick_charge' sysfs attribute next when
>> support is indicated in ACPI.
>> ---
>> I deduced the indicator bits from their names in the DSDT (QCHO and
>> QCHX). I don't have an IdeaPad except mine on hand and can't check
>> whether these are indeed the intended uses or their behaviour on other
>> IdeaPads. I can confirm that a change in the quick_charge toggle is
>> visible in Lenovo Vantage when dual booting into Windows 11.
>>
>> Greetings,
>> Philipp Jungkamp
> 
> There is already an entry for this on the kernel bugzilla:
> https://bugzilla.kernel.org/show_bug.cgi?id=216176
> 
> I have two concerns: one, it adds a new driver specific attribute for a somewhat
> generic functionality;

Right this really needs a standardized API using the power_supply sysfs class, see:
https://bugzilla.kernel.org/show_bug.cgi?id=216176#c5

Please write an API proposal for this and submit it as discussed in the
linked comment,

Regards,

Hans



> two, Lenovo Vantage does not only check this single bit
> before allowing this mode to be enabled (as far as I can recall).
> 
> 
> Regards,
> Barnabás Pőcze
> 
> 
>>
>>  drivers/platform/x86/ideapad-laptop.c | 64 ++++++++++++++++++++++++++-
>>  1 file changed, 62 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
>> index abd0c81d62c4..dea35779264a 100644
>> --- a/drivers/platform/x86/ideapad-laptop.c
>> +++ b/drivers/platform/x86/ideapad-laptop.c
>> @@ -54,12 +54,16 @@ enum {
>>  };
>>
>>  enum {
>> -	GBMD_CONSERVATION_STATE_BIT = 5,
>> +	GBMD_QUICK_CHARGE_STATE_BIT   = 2,
>> +	GBMD_CONSERVATION_STATE_BIT   = 5,
>> +	GBMD_QUICK_CHARGE_SUPPORT_BIT = 17,
>>  };
>>
>>  enum {
>>  	SBMC_CONSERVATION_ON  = 3,
>>  	SBMC_CONSERVATION_OFF = 5,
>> +	SBMC_QUICK_CHARGE_ON  = 7,
>> +	SBMC_QUICK_CHARGE_OFF = 8,
>>  };
>>
>>  enum {
>> @@ -140,6 +144,7 @@ struct ideapad_private {
>>  		bool kbd_bl               : 1;
>>  		bool touchpad_ctrl_via_ec : 1;
>>  		bool usb_charging         : 1;
>> +		bool quick_charge         : 1;
>>  	} features;
>>  	struct {
>>  		bool initialized;
>> @@ -482,6 +487,12 @@ static ssize_t conservation_mode_store(struct device *dev,
>>  	if (err)
>>  		return err;
>>
>> +	if (priv->features.quick_charge && state) {
>> +		err = exec_sbmc(priv->adev->handle, SBMC_QUICK_CHARGE_OFF);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>>  	err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF);
>>  	if (err)
>>  		return err;
>> @@ -491,6 +502,48 @@ static ssize_t conservation_mode_store(struct device *dev,
>>
>>  static DEVICE_ATTR_RW(conservation_mode);
>>
>> +static ssize_t quick_charge_show(struct device *dev,
>> +				 struct device_attribute *attr,
>> +				 char *buf)
>> +{
>> +	struct ideapad_private *priv = dev_get_drvdata(dev);
>> +	unsigned long result;
>> +	int err;
>> +
>> +	err = eval_gbmd(priv->adev->handle, &result);
>> +	if (err)
>> +		return err;
>> +
>> +	return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_QUICK_CHARGE_STATE_BIT, &result));
>> +}
>> +
>> +static ssize_t quick_charge_store(struct device *dev,
>> +				  struct device_attribute *attr,
>> +				  const char *buf, size_t count)
>> +{
>> +	struct ideapad_private *priv = dev_get_drvdata(dev);
>> +	bool state;
>> +	int err;
>> +
>> +	err = kstrtobool(buf, &state);
>> +	if (err)
>> +		return err;
>> +
>> +	if (priv->features.conservation_mode && state) {
>> +		err = exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>> +	err = exec_sbmc(priv->adev->handle, state ? SBMC_QUICK_CHARGE_ON : SBMC_QUICK_CHARGE_OFF);
>> +	if (err)
>> +		return err;
>> +
>> +	return count;
>> +}
>> +
>> +static DEVICE_ATTR_RW(quick_charge);
>> +
>>  static ssize_t fan_mode_show(struct device *dev,
>>  			     struct device_attribute *attr,
>>  			     char *buf)
>> @@ -641,6 +694,7 @@ static DEVICE_ATTR_RW(usb_charging);
>>  static struct attribute *ideapad_attributes[] = {
>>  	&dev_attr_camera_power.attr,
>>  	&dev_attr_conservation_mode.attr,
>> +	&dev_attr_quick_charge.attr,
>>  	&dev_attr_fan_mode.attr,
>>  	&dev_attr_fn_lock.attr,
>>  	&dev_attr_touchpad.attr,
>> @@ -660,6 +714,8 @@ static umode_t ideapad_is_visible(struct kobject *kobj,
>>  		supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg);
>>  	else if (attr == &dev_attr_conservation_mode.attr)
>>  		supported = priv->features.conservation_mode;
>> +	else if (attr == &dev_attr_quick_charge.attr)
>> +		supported = priv->features.quick_charge;
>>  	else if (attr == &dev_attr_fan_mode.attr)
>>  		supported = priv->features.fan_mode;
>>  	else if (attr == &dev_attr_fn_lock.attr)
>> @@ -1546,9 +1602,13 @@ static void ideapad_check_features(struct ideapad_private *priv)
>>  	if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
>>  		priv->features.fan_mode = true;
>>
>> -	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
>> +	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
>>  		priv->features.conservation_mode = true;
>>
>> +		if (!eval_gbmd(handle,&val) && test_bit(GBMD_QUICK_CHARGE_SUPPORT_BIT, &val))
>> +			priv->features.quick_charge = true;
>> +	}
>> +
>>  	if (acpi_has_method(handle, "DYTC"))
>>  		priv->features.dytc = true;
>>
>> --
>> 2.37.3
>>
>
diff mbox series

Patch

diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
index abd0c81d62c4..dea35779264a 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -54,12 +54,16 @@  enum {
 };

 enum {
-	GBMD_CONSERVATION_STATE_BIT = 5,
+	GBMD_QUICK_CHARGE_STATE_BIT   = 2,
+	GBMD_CONSERVATION_STATE_BIT   = 5,
+	GBMD_QUICK_CHARGE_SUPPORT_BIT = 17,
 };

 enum {
 	SBMC_CONSERVATION_ON  = 3,
 	SBMC_CONSERVATION_OFF = 5,
+	SBMC_QUICK_CHARGE_ON  = 7,
+	SBMC_QUICK_CHARGE_OFF = 8,
 };

 enum {
@@ -140,6 +144,7 @@  struct ideapad_private {
 		bool kbd_bl               : 1;
 		bool touchpad_ctrl_via_ec : 1;
 		bool usb_charging         : 1;
+		bool quick_charge         : 1;
 	} features;
 	struct {
 		bool initialized;
@@ -482,6 +487,12 @@  static ssize_t conservation_mode_store(struct device *dev,
 	if (err)
 		return err;

+	if (priv->features.quick_charge && state) {
+		err = exec_sbmc(priv->adev->handle, SBMC_QUICK_CHARGE_OFF);
+		if (err)
+			return err;
+	}
+
 	err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF);
 	if (err)
 		return err;
@@ -491,6 +502,48 @@  static ssize_t conservation_mode_store(struct device *dev,

 static DEVICE_ATTR_RW(conservation_mode);

+static ssize_t quick_charge_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+	unsigned long result;
+	int err;
+
+	err = eval_gbmd(priv->adev->handle, &result);
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_QUICK_CHARGE_STATE_BIT, &result));
+}
+
+static ssize_t quick_charge_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct ideapad_private *priv = dev_get_drvdata(dev);
+	bool state;
+	int err;
+
+	err = kstrtobool(buf, &state);
+	if (err)
+		return err;
+
+	if (priv->features.conservation_mode && state) {
+		err = exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF);
+		if (err)
+			return err;
+	}
+
+	err = exec_sbmc(priv->adev->handle, state ? SBMC_QUICK_CHARGE_ON : SBMC_QUICK_CHARGE_OFF);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(quick_charge);
+
 static ssize_t fan_mode_show(struct device *dev,
 			     struct device_attribute *attr,
 			     char *buf)
@@ -641,6 +694,7 @@  static DEVICE_ATTR_RW(usb_charging);
 static struct attribute *ideapad_attributes[] = {
 	&dev_attr_camera_power.attr,
 	&dev_attr_conservation_mode.attr,
+	&dev_attr_quick_charge.attr,
 	&dev_attr_fan_mode.attr,
 	&dev_attr_fn_lock.attr,
 	&dev_attr_touchpad.attr,
@@ -660,6 +714,8 @@  static umode_t ideapad_is_visible(struct kobject *kobj,
 		supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg);
 	else if (attr == &dev_attr_conservation_mode.attr)
 		supported = priv->features.conservation_mode;
+	else if (attr == &dev_attr_quick_charge.attr)
+		supported = priv->features.quick_charge;
 	else if (attr == &dev_attr_fan_mode.attr)
 		supported = priv->features.fan_mode;
 	else if (attr == &dev_attr_fn_lock.attr)
@@ -1546,9 +1602,13 @@  static void ideapad_check_features(struct ideapad_private *priv)
 	if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
 		priv->features.fan_mode = true;

-	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
+	if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
 		priv->features.conservation_mode = true;

+		if (!eval_gbmd(handle,&val) && test_bit(GBMD_QUICK_CHARGE_SUPPORT_BIT, &val))
+			priv->features.quick_charge = true;
+	}
+
 	if (acpi_has_method(handle, "DYTC"))
 		priv->features.dytc = true;