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
559847struct 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