Skip to content

Commit 6e88798

Browse files
committed
[FROM-ML] 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 function ID and capdata 00 ID 0x03010001 are able to enable or disable charging through the lenovo-wmi-other interface. The ideapad_laptop driver conflicts with this if it can also provide the attribute, so we have to get the acpi_handle and check for the same ACPI methods that enable the feature in that driver. The ACPI method is more reliable from my testing when both are present, so there is no need to modify the ideapad_laptop driver instead. The power supply extension requires a name. Instead of adding a third const macro with the same information, replace LWMI_OM_FW_ATTR_BASE_PATH and LWMI_OM_HWMON_NAME with LWMI_OM_NAME and use that everywhere. Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
1 parent 3e7822c commit 6e88798

File tree

2 files changed

+233
-6
lines changed

2 files changed

+233
-6
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
enum lwmi_device_id {
2727
LWMI_DEVICE_ID_CPU = 0x01,
2828
LWMI_DEVICE_ID_GPU = 0x02,
29+
LWMI_DEVICE_ID_PSU = 0x03,
2930
LWMI_DEVICE_ID_FAN = 0x04,
3031
};
3132

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

Lines changed: 232 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* - binding to Capability Data 00 and Fan
2727
*/
2828

29+
#include <acpi/battery.h>
2930
#include <linux/acpi.h>
3031
#include <linux/bitfield.h>
3132
#include <linux/cleanup.h>
@@ -42,6 +43,7 @@
4243
#include <linux/module.h>
4344
#include <linux/notifier.h>
4445
#include <linux/platform_profile.h>
46+
#include <linux/power_supply.h>
4547
#include <linux/types.h>
4648
#include <linux/wmi.h>
4749

@@ -80,26 +82,38 @@ enum lwmi_feature_id_gpu {
8082
LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
8183
};
8284

85+
enum lwmi_feature_id_psu {
86+
LWMI_FEATURE_ID_PSU_INSTANT_MODE = 0x01,
87+
LWMI_FEATURE_ID_PSU_CHARGE_MODE = 0x02,
88+
};
89+
8390
#define LWMI_FEATURE_ID_FAN_RPM 0x03
8491

8592
#define LWMI_TYPE_ID_NONE 0x00
8693
#define LWMI_TYPE_ID_CROSSLOAD 0x01
94+
#define LWMI_TYPE_ID_PSU_AC 0x01
95+
#define LWMI_TYPE_ID_PSU_PD 0x02
8796

8897
#define LWMI_FEATURE_VALUE_GET 17
8998
#define LWMI_FEATURE_VALUE_SET 18
9099

91100
#define LWMI_FAN_ID_BASE 1
92101
#define LWMI_FAN_NR 4
93102
#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
103+
#define LWMI_FAN_DIV 100
104+
105+
#define LWMI_CHARGE_MODE_ENABLED 0x00
106+
#define LWMI_CHARGE_MODE_DISABLED 0x01
94107

95108
#define LWMI_ATTR_ID_FAN_RPM(x) \
96109
LWMI_ATTR_ID(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
97110
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
98111

99-
#define LWMI_FAN_DIV 100
112+
#define LWMI_ATTR_ID_PSU(feat, type) \
113+
LWMI_ATTR_ID(LWMI_DEVICE_ID_PSU, feat, \
114+
LWMI_GZ_THERMAL_MODE_NONE, type)
100115

101-
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
102-
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
116+
#define LWMI_OM_NAME "lenovo-wmi-other"
103117

104118
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
105119
static DEFINE_IDA(lwmi_om_ida);
@@ -139,6 +153,8 @@ struct lwmi_om_priv {
139153
bool capdata00_collected : 1;
140154
bool capdata_fan_collected : 1;
141155
} fan_flags;
156+
157+
struct acpi_battery_hook battery_hook;
142158
};
143159

144160
/*
@@ -454,7 +470,7 @@ static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
454470
}
455471

456472
priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
457-
LWMI_OM_HWMON_NAME, priv,
473+
LWMI_OM_NAME, priv,
458474
&lwmi_om_hwmon_chip_info,
459475
NULL);
460476
if (IS_ERR(priv->hwmon_dev)) {
@@ -563,6 +579,216 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
563579
lwmi_om_hwmon_add(priv);
564580
}
565581

582+
/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
583+
584+
/**
585+
* lwmi_psy_prop_is_writeable() - Get a power_supply_ext property
586+
* @ps: The battery that was extended
587+
* @ext: The extension
588+
* @ext_data: Pointer the lwmi_om_priv drvdata
589+
* @prop: The property to read
590+
* @val: The value to return
591+
*
592+
* Writes the given value to the power_supply_ext property
593+
*
594+
* Return: 0 on success, or an error
595+
*/
596+
static int lwmi_psy_ext_get_prop(struct power_supply *ps,
597+
const struct power_supply_ext *ext,
598+
void *data,
599+
enum power_supply_property prop,
600+
union power_supply_propval *val)
601+
{
602+
struct lwmi_om_priv *priv = data;
603+
struct wmi_method_args_32 args;
604+
u32 retval;
605+
int ret;
606+
607+
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC);
608+
609+
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
610+
(unsigned char *)&args, sizeof(args),
611+
&retval);
612+
if (ret)
613+
return ret;
614+
615+
dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop);
616+
617+
if (retval == LWMI_CHARGE_MODE_DISABLED)
618+
val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
619+
else
620+
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
621+
622+
return 0;
623+
}
624+
625+
/**
626+
* lwmi_psy_prop_is_writeable() - Set a power_supply_ext property
627+
* @ps: The battery that was extended
628+
* @ext: The extension
629+
* @ext_data: Pointer the lwmi_om_priv drvdata
630+
* @prop: The property to write
631+
* @val: The value to write
632+
*
633+
* Writes the given value to the power_supply_ext property
634+
*
635+
* Return: 0 on success, or an error
636+
*/
637+
static int lwmi_psy_ext_set_prop(struct power_supply *ps,
638+
const struct power_supply_ext *ext,
639+
void *ext_data,
640+
enum power_supply_property prop,
641+
const union power_supply_propval *val)
642+
{
643+
struct lwmi_om_priv *priv = ext_data;
644+
struct wmi_method_args_32 args;
645+
646+
args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC);
647+
if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
648+
args.arg1 = LWMI_CHARGE_MODE_DISABLED;
649+
else
650+
args.arg1 = LWMI_CHARGE_MODE_ENABLED;
651+
652+
dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n",
653+
args.arg0, prop, args.arg1);
654+
655+
return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
656+
(unsigned char *)&args, sizeof(args), NULL);
657+
}
658+
659+
/**
660+
* lwmi_psy_prop_is_writeable() - Determine if the property is supported
661+
* @ps: The battery that was extended
662+
* @ext: The extension
663+
* @ext_data: Pointer the lwmi_om_priv drvdata
664+
* @prop: The property to check
665+
*
666+
* Checks capdata 00 to determine if the property is supported.
667+
*
668+
* Return: Support level, or false
669+
*/
670+
static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
671+
const struct power_supply_ext *ext,
672+
void *ext_data,
673+
enum power_supply_property prop)
674+
{
675+
struct lwmi_om_priv *priv = ext_data;
676+
struct capdata00 capdata;
677+
u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC);
678+
int ret;
679+
680+
ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
681+
if (ret)
682+
return false;
683+
684+
dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n",
685+
attribute_id, capdata.supported);
686+
687+
return capdata.supported;
688+
}
689+
690+
static const enum power_supply_property lwmi_psy_ext_props[] = {
691+
POWER_SUPPLY_PROP_CHARGE_TYPES,
692+
};
693+
694+
static const struct power_supply_ext lwmi_psy_ext = {
695+
.name = LWMI_OM_NAME,
696+
.properties = lwmi_psy_ext_props,
697+
.num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
698+
.charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
699+
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
700+
.get_property = lwmi_psy_ext_get_prop,
701+
.set_property = lwmi_psy_ext_set_prop,
702+
.property_is_writeable = lwmi_psy_prop_is_writeable,
703+
};
704+
705+
/**
706+
* lwmi_add_battery() - Connect the power_supply_ext
707+
* @battery: The battery to extend
708+
* @hook: The driver hook used to extend the battery
709+
*
710+
* Return: 0 on success, or an error.
711+
*/
712+
static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
713+
{
714+
struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
715+
716+
return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
717+
}
718+
719+
/**
720+
* lwmi_remove_battery() - Disconnect the power_supply_ext
721+
* @battery: The battery that was extended
722+
* @hook: The driver hook used to extend the battery
723+
*
724+
* Return: 0 on success, or an error.
725+
*/
726+
static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
727+
{
728+
power_supply_unregister_extension(battery, &lwmi_psy_ext);
729+
return 0;
730+
}
731+
732+
/**
733+
* lwmi_acpi_match() - Attempts to return the ideapad acpi handle
734+
* @acpi_handle: The ACPI handle that manages battery charging
735+
* @lvl: Unused
736+
* @context: Void pointer to the acpi_handle object to return
737+
* @retval: Unused
738+
*
739+
* Checks if the ideapad_laptop driver is going to manage charge_type first,
740+
* thenm if not, hooks the battery to our WMI methods.
741+
*
742+
* Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
743+
*/
744+
static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
745+
void *context, void **retval)
746+
{
747+
if (!handle)
748+
return AE_OK;
749+
750+
acpi_handle *ahand = context;
751+
*ahand = handle;
752+
753+
return AE_CTRL_TERMINATE;
754+
}
755+
756+
/**
757+
* lwmi_om_ps_ext_init() - Hooks power supply extension to device battery
758+
* @priv: Driver private data
759+
*
760+
* Checks if the ideapad_laptop driver is going to manage charge_type first,
761+
* thenm if not, hooks the battery to our WMI methods.
762+
*/
763+
static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv)
764+
{
765+
static const char * const ideapad_hid = "VPC2004";
766+
acpi_handle handle = NULL;
767+
int ret;
768+
769+
/* Deconflict ideapad_laptop driver */
770+
ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
771+
if (ret)
772+
return;
773+
774+
if (!handle)
775+
return;
776+
777+
if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
778+
dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n");
779+
return;
780+
}
781+
782+
/* Add battery hooks */
783+
priv->battery_hook.add_battery = lwmi_add_battery,
784+
priv->battery_hook.remove_battery = lwmi_remove_battery,
785+
priv->battery_hook.name = "Lenovo WMI Other Battery Extension",
786+
787+
ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);
788+
if (ret)
789+
dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
790+
}
791+
566792
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
567793

568794
struct tunable_attr_01 {
@@ -1252,8 +1478,7 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
12521478

12531479
priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
12541480
MKDEV(0, 0), NULL, "%s-%u",
1255-
LWMI_OM_FW_ATTR_BASE_PATH,
1256-
priv->ida_id);
1481+
LWMI_OM_NAME, priv->ida_id);
12571482
if (IS_ERR(priv->fw_attr_dev)) {
12581483
err = PTR_ERR(priv->fw_attr_dev);
12591484
goto err_free_ida;
@@ -1345,6 +1570,7 @@ static int lwmi_om_master_bind(struct device *dev)
13451570
return -ENODEV;
13461571

13471572
lwmi_om_fan_info_collect_cd00(priv);
1573+
lwmi_om_ps_ext_init(priv);
13481574

13491575
return lwmi_om_fw_attr_add(priv);
13501576
}

0 commit comments

Comments
 (0)