Skip to content

Commit 65707a3

Browse files
pastaq1Naim
authored andcommitted
platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
Add charge-type power supply extension for devices that support WMI based charge enable/disable. Lenovo Legion devices that implement WMI function and capdata ID 0x03010001 in their BIOS are able to enable or disable charging at 80% through the lenovo-wmi-other interface. Add a charge_type power supply extension to expose this capability to the sysfs. The ideapad_laptop driver can also provide the charge_type attribute. To avoid conflicts between the drivers, get the acpi_handle and do the same check that ideapad_laptop does when it enables the feature. If the feature is supported in ideapad_laptop, abort adding the extension from lenovo-wmi-other. The ACPI method is more reliable when both are present, from my testing, so we can prefer that implementation and do not need to worry about de-conflicting from inside that driver. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, if desired. Reviewed-by: Rong Zhang <i@rong.moe> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
1 parent fbce680 commit 65707a3

3 files changed

Lines changed: 294 additions & 1 deletion

File tree

drivers/platform/x86/lenovo/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ config LENOVO_WMI_GAMEZONE
262262
config LENOVO_WMI_TUNING
263263
tristate "Lenovo Other Mode WMI Driver"
264264
depends on ACPI_WMI
265+
depends on ACPI_BATTERY
265266
select HWMON
266267
select FW_ATTR_CLASS
267268
select LENOVO_WMI_CAPDATA

drivers/platform/x86/lenovo/wmi-capdata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
enum lwmi_device_id {
2222
LWMI_DEVICE_ID_CPU = 0x01,
2323
LWMI_DEVICE_ID_GPU = 0x02,
24+
LWMI_DEVICE_ID_PSU = 0x03,
2425
LWMI_DEVICE_ID_FAN = 0x04,
2526
};
2627

drivers/platform/x86/lenovo/wmi-other.c

Lines changed: 292 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@
4040
#include <linux/limits.h>
4141
#include <linux/module.h>
4242
#include <linux/platform_profile.h>
43+
#include <linux/power_supply.h>
4344
#include <linux/types.h>
4445
#include <linux/wmi.h>
4546

47+
#include <acpi/battery.h>
48+
4649
#include "wmi-capdata.h"
4750
#include "wmi-events.h"
4851
#include "wmi-helpers.h"
@@ -74,9 +77,11 @@ enum lwmi_feature_id_gpu {
7477
LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
7578
};
7679

77-
#define LWMI_FEATURE_ID_FAN_RPM 0x03
80+
#define LWMI_FEATURE_ID_FAN_RPM 0x03
81+
#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE 0x01
7882

7983
#define LWMI_TYPE_ID_CROSSLOAD 0x01
84+
#define LWMI_TYPE_ID_PSU_AC 0x01
8085

8186
#define LWMI_FEATURE_VALUE_GET 17
8287
#define LWMI_FEATURE_VALUE_SET 18
@@ -87,10 +92,17 @@ enum lwmi_feature_id_gpu {
8792

8893
#define LWMI_FAN_DIV 100
8994

95+
#define LWMI_CHARGE_TYPE_STANDARD 0x00
96+
#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
97+
9098
#define LWMI_ATTR_ID_FAN_RPM(x) \
9199
lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
92100
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
93101

102+
#define LWMI_ATTR_ID_PSU(feat, type) \
103+
lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
104+
LWMI_GZ_THERMAL_MODE_NONE, type)
105+
94106
#define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
95107
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
96108

@@ -130,6 +142,9 @@ struct lwmi_om_priv {
130142
bool capdata00_collected : 1;
131143
bool capdata_fan_collected : 1;
132144
} fan_flags;
145+
146+
struct acpi_battery_hook battery_hook;
147+
bool bh_registered;
133148
};
134149

135150
/*
@@ -554,6 +569,279 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
554569
lwmi_om_hwmon_add(priv);
555570
}
556571

572+
/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
573+
574+
/**
575+
* lwmi_psy_ext_get_prop() - Get a power_supply_ext property
576+
* @ps: The battery that was extended
577+
* @ext: The extension
578+
* @ext_data: Pointer the lwmi_om_priv drvdata
579+
* @prop: The property to read
580+
* @val: The value to return
581+
*
582+
* Writes the given value to the power_supply_ext property
583+
*
584+
* Return: 0 on success, or an error
585+
*/
586+
static int lwmi_psy_ext_get_prop(struct power_supply *ps,
587+
const struct power_supply_ext *ext,
588+
void *ext_data,
589+
enum power_supply_property prop,
590+
union power_supply_propval *val)
591+
{
592+
struct wmi_method_args_32 args = { 0x0, 0x0 };
593+
struct lwmi_om_priv *priv = ext_data;
594+
u32 retval;
595+
int ret;
596+
597+
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
598+
599+
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
600+
(unsigned char *)&args, sizeof(args),
601+
&retval);
602+
if (ret)
603+
return ret;
604+
605+
dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop);
606+
607+
switch (retval) {
608+
case LWMI_CHARGE_TYPE_LONGLIFE:
609+
val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
610+
break;
611+
case LWMI_CHARGE_TYPE_STANDARD:
612+
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
613+
break;
614+
default:
615+
dev_err(&priv->wdev->dev, "Got invalid charge value: %#x\n", retval);
616+
return -EINVAL;
617+
}
618+
619+
return 0;
620+
}
621+
622+
/**
623+
* lwmi_psy_ext_set_prop() - Set a power_supply_ext property
624+
* @ps: The battery that was extended
625+
* @ext: The extension
626+
* @ext_data: Pointer the lwmi_om_priv drvdata
627+
* @prop: The property to write
628+
* @val: The value to write
629+
*
630+
* Writes the given value to the power_supply_ext property
631+
*
632+
* Return: 0 on success, or an error
633+
*/
634+
static int lwmi_psy_ext_set_prop(struct power_supply *ps,
635+
const struct power_supply_ext *ext,
636+
void *ext_data,
637+
enum power_supply_property prop,
638+
const union power_supply_propval *val)
639+
{
640+
struct wmi_method_args_32 args = { 0x0, 0x0 };
641+
struct lwmi_om_priv *priv = ext_data;
642+
643+
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
644+
switch (val->intval) {
645+
case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
646+
args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
647+
break;
648+
case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
649+
args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
650+
break;
651+
default:
652+
dev_err(&priv->wdev->dev, "Got invalid charge value: %#x\n", val->intval);
653+
return -EINVAL;
654+
}
655+
656+
dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n",
657+
args.arg0, prop, args.arg1);
658+
659+
return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
660+
(unsigned char *)&args, sizeof(args), NULL);
661+
}
662+
663+
/**
664+
* lwmi_psy_prop_is_supported() - Determine if the property is supported
665+
* @priv: Pointer the lwmi_om_priv drvdata
666+
*
667+
* Checks capdata 00 to determine if the property is supported.
668+
*
669+
* Return: true if readable, or false
670+
*/
671+
static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv)
672+
{
673+
u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
674+
struct capdata00 capdata;
675+
int ret;
676+
677+
ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
678+
if (ret)
679+
return false;
680+
681+
dev_dbg(&priv->wdev->dev, "Battery charge mode (%#010x) support level: %#x\n",
682+
attribute_id, capdata.supported);
683+
684+
return ((capdata.supported & LWMI_SUPP_VALID) && (capdata.supported & LWMI_SUPP_GET));
685+
}
686+
687+
/**
688+
* lwmi_psy_prop_is_writeable() - Determine if the property is writeable
689+
* @ps: The battery that was extended
690+
* @ext: The extension
691+
* @ext_data: Pointer the lwmi_om_priv drvdata
692+
* @prop: The property to check
693+
*
694+
* Checks capdata 00 to determine if the property is writable.
695+
*
696+
* Return: true if writable, or false
697+
*/
698+
static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
699+
const struct power_supply_ext *ext,
700+
void *ext_data,
701+
enum power_supply_property prop)
702+
{
703+
u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
704+
struct lwmi_om_priv *priv = ext_data;
705+
struct capdata00 capdata;
706+
int ret;
707+
708+
ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
709+
if (ret)
710+
return false;
711+
712+
return !!(capdata.supported & LWMI_SUPP_SET);
713+
}
714+
715+
static const enum power_supply_property lwmi_psy_ext_props[] = {
716+
POWER_SUPPLY_PROP_CHARGE_TYPES,
717+
};
718+
719+
static const struct power_supply_ext lwmi_psy_ext = {
720+
.name = LWMI_OM_SYSFS_NAME,
721+
.properties = lwmi_psy_ext_props,
722+
.num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
723+
.charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
724+
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
725+
.get_property = lwmi_psy_ext_get_prop,
726+
.set_property = lwmi_psy_ext_set_prop,
727+
.property_is_writeable = lwmi_psy_prop_is_writeable,
728+
};
729+
730+
/**
731+
* lwmi_add_battery() - Connect the power_supply_ext
732+
* @battery: The battery to extend
733+
* @hook: The driver hook used to extend the battery
734+
*
735+
* Return: 0 on success, or an error.
736+
*/
737+
static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
738+
{
739+
struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
740+
741+
return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
742+
}
743+
744+
/**
745+
* lwmi_remove_battery() - Disconnect the power_supply_ext
746+
* @battery: The battery that was extended
747+
* @hook: The driver hook used to extend the battery
748+
*
749+
* Return: 0 on success, or an error.
750+
*/
751+
static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
752+
{
753+
power_supply_unregister_extension(battery, &lwmi_psy_ext);
754+
return 0;
755+
}
756+
757+
/**
758+
* lwmi_acpi_match() - Attempts to return the ideapad acpi handle
759+
* @handle: The ACPI handle that manages battery charging
760+
* @lvl: Unused
761+
* @context: Void pointer to the acpi_handle object to return
762+
* @retval: Unused
763+
*
764+
* Checks if the ideapad_laptop driver is going to manage charge_type first,
765+
* then if not, hooks the battery to our WMI methods.
766+
*
767+
* Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
768+
*/
769+
static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
770+
void *context, void **retval)
771+
{
772+
acpi_handle *ahand = context;
773+
774+
if (!handle)
775+
return AE_OK;
776+
777+
*ahand = handle;
778+
779+
return AE_CTRL_TERMINATE;
780+
}
781+
782+
static bool force_load_psy_ext;
783+
module_param(force_load_psy_ext, bool, 0444);
784+
MODULE_PARM_DESC(force_load_psy_ext,
785+
"This option will skip checking if the ideapad_laptop driver will conflict "
786+
"with adding an extension to set the battery charge type. It is recommended "
787+
"to blacklist the ideapad driver before using this option.");
788+
789+
/**
790+
* lwmi_om_psy_ext_init() - Hooks power supply extension to device battery
791+
* @priv: Driver private data
792+
*
793+
* Checks if the ideapad_laptop driver is going to manage charge_type first,
794+
* then if not, hooks the battery to our WMI methods.
795+
*/
796+
static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
797+
{
798+
static const char * const ideapad_hid = "VPC2004";
799+
acpi_handle handle = NULL;
800+
int ret;
801+
802+
priv->bh_registered = false;
803+
804+
/* Deconflict ideapad_laptop driver */
805+
if (force_load_psy_ext)
806+
goto load_psy_ext;
807+
808+
if (!lwmi_psy_prop_is_supported(priv))
809+
return;
810+
811+
ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
812+
if (ret)
813+
return;
814+
815+
if (handle && acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
816+
dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device\n");
817+
return;
818+
}
819+
820+
load_psy_ext:
821+
/* Add battery hooks */
822+
priv->battery_hook.add_battery = lwmi_add_battery;
823+
priv->battery_hook.remove_battery = lwmi_remove_battery;
824+
priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
825+
priv->bh_registered = true;
826+
827+
battery_hook_register(&priv->battery_hook);
828+
}
829+
830+
/**
831+
* lwmi_om_psy_remove() - Unregister battery hook
832+
* @priv: Driver private data
833+
*
834+
* Unregisters the battery hook if applicable.
835+
*/
836+
static void lwmi_om_psy_remove(struct lwmi_om_priv *priv)
837+
{
838+
if (!priv->bh_registered)
839+
return;
840+
841+
battery_hook_unregister(&priv->battery_hook);
842+
priv->bh_registered = false;
843+
}
844+
557845
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
558846

559847
struct tunable_attr_01 {
@@ -1228,6 +1516,7 @@ static int lwmi_om_master_bind(struct device *dev)
12281516
}
12291517

12301518
lwmi_om_fan_info_collect_cd00(priv);
1519+
lwmi_om_psy_ext_init(priv);
12311520

12321521
lwmi_om_fw_attr_add(priv);
12331522

@@ -1250,6 +1539,8 @@ static void lwmi_om_master_unbind(struct device *dev)
12501539

12511540
lwmi_om_hwmon_remove(priv);
12521541

1542+
lwmi_om_psy_remove(priv);
1543+
12531544
component_unbind_all(dev, NULL);
12541545
}
12551546

0 commit comments

Comments
 (0)