* Re: [PATCH v2 7/7] platform/x86: uniwill-laptop: Add support for battery charge modes
[not found] ` <c475d7de-48c8-4fe0-b414-2d32988f52dd@tuxedocomputers.com>
@ 2026-05-06 7:47 ` Armin Wolf
2026-05-11 17:40 ` Werner Sembach
0 siblings, 1 reply; 3+ messages in thread
From: Armin Wolf @ 2026-05-06 7:47 UTC (permalink / raw)
To: Werner Sembach, Ilpo Järvinen
Cc: Hans de Goede, platform-driver-x86, LKML
Am 04.05.26 um 10:44 schrieb Werner Sembach:
>
> Am 30.04.26 um 15:41 schrieb Armin Wolf:
>> Am 30.04.26 um 15:22 schrieb Ilpo Järvinen:
>>> On Fri, 17 Apr 2026, Armin Wolf wrote:
>>>
>>>> Many Uniwill-based devices do not supports the already existing
>>>> charge limit functionality, but instead support an alternative
>>>> interface for controlling the battery charge algorithm.
>>>>
>>>> Add support for this interface and update the documentation.
>>>>
>>>> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
>>>> ---
>>>> .../admin-guide/laptops/uniwill-laptop.rst | 19 +-
>>>> drivers/platform/x86/uniwill/uniwill-acpi.c | 243 +++++++++++++
>>>> +----
>>>> drivers/platform/x86/uniwill/uniwill-wmi.c | 5 +-
>>>> 3 files changed, 215 insertions(+), 52 deletions(-)
>>>>
>>>> diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/
>>>> Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>> index 1f3ca84c7d88..24b41dbab886 100644
>>>> --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>> +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>> @@ -46,11 +46,20 @@ Battery Charging Control
>>>> .. warning:: Some devices do not properly implement the charging
>>>> threshold interface. Forcing
>>>> the driver to enable access to said interface on such
>>>> devices might damage the
>>>> battery [1]_. Because of this the driver will not
>>>> enable said feature even when
>>>> - using the ``force`` module parameter.
>>>> -
>>>> -The ``uniwill-laptop`` driver supports controlling the battery
>>>> charge limit. This happens over
>>>> -the standard ``charge_control_end_threshold`` power supply sysfs
>>>> attribute. All values
>>>> -between 1 and 100 percent are supported.
>>>> + using the ``force`` module parameter. The charging
>>>> profile interface will be
>>>> + available instead.
>>>> +
>>>> +The ``uniwill-laptop`` driver supports controlling the battery
>>>> charge limit. This either happens
>>>> +over the standard ``charge_control_end_threshold`` or
>>>> ``charge_types`` power supply sysfs attribute,
>>>> +depending on the device. When using the
>>>> ``charge_control_end_threshold`` sysfs attribute, all values
>>>> +between 1 and 100 percent are supported. When using the
>>>> ``charge_types`` sysfs attribute, the driver
>>>> +supports switching between the ``Standard``, ``Trickle`` and ``Long
>>>> Life`` profiles.
>>>> +
>>>> +Keep in mind that when using the ``charge_types`` sysfs attribute,
>>>> the EC firmware will hide the
>>>> +true charging status of the battery from the operating system,
>>>> potentially misleading users into
>>>> +thinking that the charging profile does not work. Checking the
>>>> ``current_now`` sysfs attribute
>>>> +tells you the true charging status of the battery even when using
>>>> the ``charge_types`` sysfs
>>>> +attribute (0 means that the battery is currently not charging).
>>>> Additionally the driver signals the presence of battery charging
>>>> issues through the standard
>>>> ``health`` power supply sysfs attribute.
>>>> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/
>>>> platform/x86/uniwill/uniwill-acpi.c
>>>> index d4abcaf87e39..e11b6c8aeb0d 100644
>>>> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
>>>> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
>>>> @@ -254,6 +254,10 @@
>>>> #define EC_ADDR_OEM_4 0x07A6
>>>> #define OVERBOOST_DYN_TEMP_OFF BIT(1)
>>>> +#define CHARGING_PROFILE_MASK GENMASK(5, 4)
>>>> +#define CHARGING_PROFILE_HIGH_CAPACITY 0x00
>>>> +#define CHARGING_PROFILE_BALANCED 0x01
>>>> +#define CHARGING_PROFILE_STATIONARY 0x02
>>>> #define TOUCHPAD_TOGGLE_OFF BIT(6)
>>>> #define EC_ADDR_CHARGE_CTRL 0x07B9
>>>> @@ -320,13 +324,15 @@
>>>> #define UNIWILL_FEATURE_SUPER_KEY BIT(1)
>>>> #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2)
>>>> #define UNIWILL_FEATURE_LIGHTBAR BIT(3)
>>>> -#define UNIWILL_FEATURE_BATTERY BIT(4)
>>>> -#define UNIWILL_FEATURE_CPU_TEMP BIT(5)
>>>> -#define UNIWILL_FEATURE_GPU_TEMP BIT(6)
>>>> -#define UNIWILL_FEATURE_PRIMARY_FAN BIT(7)
>>>> -#define UNIWILL_FEATURE_SECONDARY_FAN BIT(8)
>>>> -#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(9)
>>>> -#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(10)
>>>> +#define UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT BIT(4)
>>>> +/* Mutually exclusive with the charge limit feature */
>>>> +#define UNIWILL_FEATURE_BATTERY_CHARGE_MODES BIT(5)
>>>
>>> This feature seems to be only available through force parameter?
>>>
>>
>> Yes, this is expected to change as soon as Tuxedo can verify if their
>> devices support this feature. I tested it on my Tuxedo device, but i do
>> not want to split the feature descriptors too much.
> Shouldn't require too much testing, will spin up a patch for it.
Thank you :)
Armin Wolf
>>
>> Thanks,
>> Armin Wolf
>>
>>> --
>>> i.
>>>
>>>> +#define UNIWILL_FEATURE_CPU_TEMP BIT(6)
>>>> +#define UNIWILL_FEATURE_GPU_TEMP BIT(7)
>>>> +#define UNIWILL_FEATURE_PRIMARY_FAN BIT(8)
>>>> +#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9)
>>>> +#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10)
>>>> +#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11)
>>>> enum usb_c_power_priority_options {
>>>> USB_C_POWER_PRIORITY_CHARGING = 0,
>>>> @@ -339,8 +345,15 @@ struct uniwill_data {
>>>> struct regmap *regmap;
>>>> unsigned int features;
>>>> struct acpi_battery_hook hook;
>>>> - unsigned int last_charge_ctrl;
>>>> struct mutex battery_lock; /* Protects the list of
>>>> currently registered batteries */
>>>> + union {
>>>> + struct {
>>>> + /* Protects writes to last_charge_type */
>>>> + struct mutex charge_type_lock;
>>>> + enum power_supply_charge_type last_charge_type;
>>>> + };
>>>> + unsigned int last_charge_ctrl;
>>>> + };
>>>> bool last_fn_lock_state;
>>>> bool last_super_key_enable_state;
>>>> bool last_touchpad_toggle_enable_state;
>>>> @@ -447,6 +460,12 @@ static inline bool
>>>> uniwill_device_supports(const struct uniwill_data *data,
>>>> return (data->features & features) == features;
>>>> }
>>>> +static inline bool uniwill_device_supports_any(const struct
>>>> uniwill_data *data,
>>>> + unsigned int features)
>>>> +{
>>>> + return data->features & features;
>>>> +}
>>>> +
>>>> static int uniwill_ec_reg_write(void *context, unsigned int reg,
>>>> unsigned int val)
>>>> {
>>>> union acpi_object params[2] = {
>>>> @@ -1421,6 +1440,30 @@ static int uniwill_led_init(struct
>>>> uniwill_data *data)
>>>> &init_data);
>>>> }
>>>> +static int uniwill_read_charge_type(struct uniwill_data *data,
>>>> enum power_supply_charge_type *type)
>>>> +{
>>>> + unsigned int value;
>>>> + int ret;
>>>> +
>>>> + ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + switch (FIELD_GET(CHARGING_PROFILE_MASK, value)) {
>>>> + case CHARGING_PROFILE_HIGH_CAPACITY:
>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
>>>> + return 0;
>>>> + case CHARGING_PROFILE_BALANCED:
>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
>>>> + return 0;
>>>> + case CHARGING_PROFILE_STATIONARY:
>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
>>>> + return 0;
>>>> + default:
>>>> + return -EPROTO;
>>>> + }
>>>> +}
>>>> +
>>>> static int uniwill_get_property(struct power_supply *psy, const
>>>> struct power_supply_ext *ext,
>>>> void *drvdata, enum power_supply_property psp,
>>>> union power_supply_propval *val)
>>>> @@ -1431,6 +1474,16 @@ static int uniwill_get_property(struct
>>>> power_supply *psy, const struct power_sup
>>>> int ret;
>>>> switch (psp) {
>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>> + /*
>>>> + * We need to use the cached value here because the
>>>> charging mode
>>>> + * reported by the EC might temporarily change when a
>>>> external power
>>>> + * source has been connected.
>>>> + */
>>>> + mutex_lock(&data->charge_type_lock);
>>>> + val->intval = data->last_charge_type;
>>>> + mutex_unlock(&data->charge_type_lock);
>>>> + return 0;
>>>> case POWER_SUPPLY_PROP_HEALTH:
>>>> ret = power_supply_get_property_direct(psy,
>>>> POWER_SUPPLY_PROP_PRESENT, &prop);
>>>> if (ret < 0)
>>>> @@ -1479,13 +1532,52 @@ static int uniwill_get_property(struct
>>>> power_supply *psy, const struct power_sup
>>>> }
>>>> }
>>>> +static int uniwill_write_charge_type(struct uniwill_data *data,
>>>> enum power_supply_charge_type type)
>>>> +{
>>>> + unsigned int value;
>>>> +
>>>> + switch (type) {
>>>> + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>> CHARGING_PROFILE_STATIONARY);
>>>> + break;
>>>> + case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>> CHARGING_PROFILE_HIGH_CAPACITY);
>>>> + break;
>>>> + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>> CHARGING_PROFILE_BALANCED);
>>>> + break;
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + return regmap_update_bits(data->regmap, EC_ADDR_OEM_4,
>>>> CHARGING_PROFILE_MASK, value);
>>>> +}
>>>> +
>>>> +static int uniwill_restore_charge_type(struct uniwill_data *data)
>>>> +{
>>>> + guard(mutex)(&data->charge_type_lock);
>>>> +
>>>> + return uniwill_write_charge_type(data, data->last_charge_type);
>>>> +}
>>>> +
>>>> static int uniwill_set_property(struct power_supply *psy, const
>>>> struct power_supply_ext *ext,
>>>> void *drvdata, enum power_supply_property psp,
>>>> const union power_supply_propval *val)
>>>> {
>>>> struct uniwill_data *data = drvdata;
>>>> + int ret;
>>>> switch (psp) {
>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>> + mutex_lock(&data->charge_type_lock);
>>>> +
>>>> + ret = uniwill_write_charge_type(data, val->intval);
>>>> + if (ret >= 0)
>>>> + data->last_charge_type = val->intval;
>>>> +
>>>> + mutex_unlock(&data->charge_type_lock);
>>>> +
>>>> + return ret;
>>>> case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
>>>> if (val->intval < 0 || val->intval > 100)
>>>> return -EINVAL;
>>>> @@ -1501,21 +1593,41 @@ static int
>>>> uniwill_property_is_writeable(struct power_supply *psy,
>>>> const struct power_supply_ext *ext, void
>>>> *drvdata,
>>>> enum power_supply_property psp)
>>>> {
>>>> - if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD)
>>>> + switch (psp) {
>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
>>>> return true;
>>>> -
>>>> - return false;
>>>> + default:
>>>> + return false;
>>>> + }
>>>> }
>>>> -static const enum power_supply_property uniwill_properties[] = {
>>>> +static const enum power_supply_property
>>>> uniwill_charge_limit_properties[] = {
>>>> POWER_SUPPLY_PROP_HEALTH,
>>>> POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
>>>> };
>>>> -static const struct power_supply_ext uniwill_extension = {
>>>> +static const struct power_supply_ext uniwill_charge_limit_extension
>>>> = {
>>>> .name = DRIVER_NAME,
>>>> - .properties = uniwill_properties,
>>>> - .num_properties = ARRAY_SIZE(uniwill_properties),
>>>> + .properties = uniwill_charge_limit_properties,
>>>> + .num_properties = ARRAY_SIZE(uniwill_charge_limit_properties),
>>>> + .get_property = uniwill_get_property,
>>>> + .set_property = uniwill_set_property,
>>>> + .property_is_writeable = uniwill_property_is_writeable,
>>>> +};
>>>> +
>>>> +static const enum power_supply_property
>>>> uniwill_charge_modes_properties[] = {
>>>> + POWER_SUPPLY_PROP_CHARGE_TYPES,
>>>> + POWER_SUPPLY_PROP_HEALTH,
>>>> +};
>>>> +
>>>> +static const struct power_supply_ext uniwill_charge_modes_extension
>>>> = {
>>>> + .name = DRIVER_NAME,
>>>> + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) |
>>>> + BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
>>>> + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE),
>>>> + .properties = uniwill_charge_modes_properties,
>>>> + .num_properties = ARRAY_SIZE(uniwill_charge_modes_properties),
>>>> .get_property = uniwill_get_property,
>>>> .set_property = uniwill_set_property,
>>>> .property_is_writeable = uniwill_property_is_writeable,
>>>> @@ -1531,7 +1643,13 @@ static int uniwill_add_battery(struct
>>>> power_supply *battery, struct acpi_battery
>>>> if (!entry)
>>>> return -ENOMEM;
>>>> - ret = power_supply_register_extension(battery,
>>>> &uniwill_extension, data->dev, data);
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>> + ret = power_supply_register_extension(battery,
>>>> &uniwill_charge_limit_extension,
>>>> + data->dev, data);
>>>> + else
>>>> + ret = power_supply_register_extension(battery,
>>>> &uniwill_charge_modes_extension,
>>>> + data->dev, data);
>>>> +
>>>> if (ret < 0) {
>>>> kfree(entry);
>>>> return ret;
>>>> @@ -1560,7 +1678,10 @@ static int uniwill_remove_battery(struct
>>>> power_supply *battery, struct acpi_batt
>>>> }
>>>> }
>>>> - power_supply_unregister_extension(battery, &uniwill_extension);
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>> + power_supply_unregister_extension(battery,
>>>> &uniwill_charge_limit_extension);
>>>> + else
>>>> + power_supply_unregister_extension(battery,
>>>> &uniwill_charge_modes_extension);
>>>> return 0;
>>>> }
>>>> @@ -1570,27 +1691,36 @@ static int uniwill_battery_init(struct
>>>> uniwill_data *data)
>>>> unsigned int value, threshold;
>>>> int ret;
>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>> - return 0;
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) {
>>>> + ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> - ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
>>>> - if (ret < 0)
>>>> - return ret;
>>>> + /*
>>>> + * The charge control threshold might be initialized with 0 by
>>>> + * the EC to signal that said threshold is uninitialized.
>>>> We thus
>>>> + * need to replace this value with 100 to signal that we
>>>> want to
>>>> + * take control of battery charging. For the sake of
>>>> completeness
>>>> + * we also set the charging threshold to 100 if the EC-
>>>> provided
>>>> + * value is invalid.
>>>> + */
>>>> + threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
>>>> + if (threshold == 0 || threshold > 100) {
>>>> + FIELD_MODIFY(CHARGE_CTRL_MASK, &value, 100);
>>>> + ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL,
>>>> value);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> + }
>>>> + } else if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
>>>> + ret = devm_mutex_init(data->dev, &data->charge_type_lock);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> - /*
>>>> - * The charge control threshold might be initialized with 0 by
>>>> - * the EC to signal that said threshold is uninitialized. We thus
>>>> - * need to replace this value with 100 to signal that we want to
>>>> - * take control of battery charging. For the sake of completeness
>>>> - * we also set the charging threshold to 100 if the EC-provided
>>>> - * value is invalid.
>>>> - */
>>>> - threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
>>>> - if (threshold == 0 || threshold > 100) {
>>>> - FIELD_MODIFY(CHARGE_CTRL_MASK, &value, 100);
>>>> - ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
>>>> + ret = uniwill_read_charge_type(data, &data->last_charge_type);
>>>> if (ret < 0)
>>>> return ret;
>>>> + } else {
>>>> + return 0;
>>>> }
>>>> ret = devm_mutex_init(data->dev, &data->battery_lock);
>>>> @@ -1609,10 +1739,13 @@ static int uniwill_notifier_call(struct
>>>> notifier_block *nb, unsigned long action
>>>> {
>>>> struct uniwill_data *data = container_of(nb, struct
>>>> uniwill_data, nb);
>>>> struct uniwill_battery_entry *entry;
>>>> + int ret;
>>>> switch (action) {
>>>> case UNIWILL_OSD_BATTERY_ALERT:
>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>> + if (!uniwill_device_supports_any(data,
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
>>>> return NOTIFY_DONE;
>>>> mutex_lock(&data->battery_lock);
>>>> @@ -1623,10 +1756,24 @@ static int uniwill_notifier_call(struct
>>>> notifier_block *nb, unsigned long action
>>>> return NOTIFY_OK;
>>>> case UNIWILL_OSD_DC_ADAPTER_CHANGED:
>>>> - if (!uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
>>>> + if (!uniwill_device_supports_any(data,
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
>>>> + UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
>>>> return NOTIFY_DONE;
>>>> - return
>>>> notifier_from_errno(usb_c_power_priority_restore(data));
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
>>>> + ret = uniwill_restore_charge_type(data);
>>>> + if (ret < 0)
>>>> + return notifier_from_errno(ret);
>>>> + }
>>>> +
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) {
>>>> + ret = usb_c_power_priority_restore(data);
>>>> + if (ret < 0)
>>>> + return notifier_from_errno(ret);
>>>> + }
>>>> +
>>>> + return NOTIFY_OK;
>>>> case UNIWILL_OSD_FN_LOCK:
>>>> if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
>>>> return NOTIFY_DONE;
>>>> @@ -1810,7 +1957,7 @@ static int
>>>> uniwill_suspend_touchpad_toggle(struct uniwill_data *data)
>>>> static int uniwill_suspend_battery(struct uniwill_data *data)
>>>> {
>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>> + if (!uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>> return 0;
>>>> /*
>>>> @@ -1887,11 +2034,15 @@ static int
>>>> uniwill_resume_touchpad_toggle(struct uniwill_data *data)
>>>> static int uniwill_resume_battery(struct uniwill_data *data)
>>>> {
>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>> - return 0;
>>>> - return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL,
>>>> CHARGE_CTRL_MASK,
>>>> - data->last_charge_ctrl);
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
>>>> + return uniwill_restore_charge_type(data);
>>>> +
>>>> + if (uniwill_device_supports(data,
>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>> + return regmap_update_bits(data->regmap,
>>>> EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
>>>> + data->last_charge_ctrl);
>>>> +
>>>> + return 0;
>>>> }
>>>> static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
>>>> @@ -1970,7 +2121,7 @@ static struct platform_driver uniwill_driver = {
>>>> static struct uniwill_device_descriptor
>>>> lapqc71a_lapqc71b_descriptor __initdata = {
>>>> .features = UNIWILL_FEATURE_SUPER_KEY |
>>>> - UNIWILL_FEATURE_BATTERY |
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>> @@ -1981,7 +2132,7 @@ static struct uniwill_device_descriptor
>>>> lapac71h_descriptor __initdata = {
>>>> .features = UNIWILL_FEATURE_FN_LOCK |
>>>> UNIWILL_FEATURE_SUPER_KEY |
>>>> UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
>>>> - UNIWILL_FEATURE_BATTERY |
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>> @@ -1993,7 +2144,7 @@ static struct uniwill_device_descriptor
>>>> lapkc71f_descriptor __initdata = {
>>>> UNIWILL_FEATURE_SUPER_KEY |
>>>> UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
>>>> UNIWILL_FEATURE_LIGHTBAR |
>>>> - UNIWILL_FEATURE_BATTERY |
>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>> @@ -2579,7 +2730,7 @@ static int __init uniwill_init(void)
>>>> if (force) {
>>>> /* Assume that the device supports all features except the
>>>> charge limit */
>>>> - device_descriptor.features = UINT_MAX &
>>>> ~UNIWILL_FEATURE_BATTERY;
>>>> + device_descriptor.features = UINT_MAX &
>>>> ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT;
>>>> pr_warn("Enabling potentially unsupported features\n");
>>>> }
>>>> diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/
>>>> platform/x86/uniwill/uniwill-wmi.c
>>>> index 31d9c39f14ab..f1b89bc63df6 100644
>>>> --- a/drivers/platform/x86/uniwill/uniwill-wmi.c
>>>> +++ b/drivers/platform/x86/uniwill/uniwill-wmi.c
>>>> @@ -48,6 +48,7 @@ int devm_uniwill_wmi_register_notifier(struct
>>>> device *dev, struct notifier_block
>>>> static void uniwill_wmi_notify(struct wmi_device *wdev, union
>>>> acpi_object *obj)
>>>> {
>>>> u32 value;
>>>> + int ret;
>>>> if (obj->type != ACPI_TYPE_INTEGER)
>>>> return;
>>>> @@ -56,7 +57,9 @@ static void uniwill_wmi_notify(struct wmi_device
>>>> *wdev, union acpi_object *obj)
>>>> dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
>>>> - blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
>>>> + ret = blocking_notifier_call_chain(&uniwill_wmi_chain_head,
>>>> value, NULL);
>>>> + if (notifier_to_errno(ret) < 0)
>>>> + dev_err(&wdev->dev, "Failed to handle event %u\n", value);
>>>> }
>>>> /*
>>>>
>>>
>>
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2 1/7] platform/x86: uniwill-laptop: Properly initialize charging threshold
[not found] ` <903a013e-a3fb-49ea-a6f9-ad6577d57a44@gmx.de>
@ 2026-05-06 13:42 ` Ilpo Järvinen
0 siblings, 0 replies; 3+ messages in thread
From: Ilpo Järvinen @ 2026-05-06 13:42 UTC (permalink / raw)
To: Armin Wolf; +Cc: Hans de Goede, wse, platform-driver-x86, LKML
[-- Attachment #1: Type: text/plain, Size: 2680 bytes --]
On Sun, 3 May 2026, Armin Wolf wrote:
> Am 30.04.26 um 14:53 schrieb Ilpo Järvinen:
> > On Fri, 17 Apr 2026, Armin Wolf wrote:
> >
> > > The EC might initialize the charge threshold with 0 to signal that
> > > said threshold is uninitialized. Detect this and replace said value
> > > with 100 to signal the EC that we want to take control of battery
> > > charging. Also set the threshold to 100 if the EC-provided value
> > > is invalid.
> > >
> > > Fixes: d050479693bb ("platform/x86: Add Uniwill laptop driver")
> > > Reviewed-by: Werner Sembach <wse@tuxedocomputers.com>
> > > Signed-off-by: Armin Wolf <W_Armin@gmx.de>
> > > ---
> > > drivers/platform/x86/uniwill/uniwill-acpi.c | 28 ++++++++++++++++++++-
> > > 1 file changed, 27 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c
> > > b/drivers/platform/x86/uniwill/uniwill-acpi.c
> > > index faade4cf08be..8f16c94221aa 100644
> > > --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
> > > +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
> > > @@ -1404,7 +1404,12 @@ static int uniwill_get_property(struct power_supply
> > > *psy, const struct power_sup
> > > if (ret < 0)
> > > return ret;
> > > - val->intval = clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval),
> > > 0, 100);
> > > + regval = FIELD_GET(CHARGE_CTRL_MASK, regval);
> > > + if (!regval)
> > > + val->intval = 100;
> > > + else
> > > + val->intval = min(regval, 100);
> >
> > ...
> >
> > > + /*
> > > + * The charge control threshold might be initialized with 0 by
> > > + * the EC to signal that said threshold is uninitialized. We thus
> > > + * need to replace this value with 100 to signal that we want to
> > > + * take control of battery charging. For the sake of completeness
> > > + * we also set the charging threshold to 100 if the EC-provided
> > > + * value is invalid.
> > > + */
> > > + threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
> > > + if (threshold == 0 || threshold > 100) {
> > > + FIELD_MODIFY(CHARGE_CTRL_MASK, &value, 100);
> >
> > AFAICT, this does exactly the same thing as the other code above (but
> > looks very different on surface). Wouldn't it make sense to have them
> > share code?
>
> I do not think that this would be a good idea. The two call sides are two
> different, creating a helper function for both would likely be very
> difficult.
Both seem to be sanitizing the charge control threshold (AFAICT, both map
0 and out-of-range values to 100) isn't that the case? Why cannot we have
uniwill_charge_ctrl_thres_sanitize() or something along those lines?
--
i.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2 7/7] platform/x86: uniwill-laptop: Add support for battery charge modes
2026-05-06 7:47 ` [PATCH v2 7/7] platform/x86: uniwill-laptop: Add support for battery charge modes Armin Wolf
@ 2026-05-11 17:40 ` Werner Sembach
0 siblings, 0 replies; 3+ messages in thread
From: Werner Sembach @ 2026-05-11 17:40 UTC (permalink / raw)
To: Armin Wolf, Ilpo Järvinen; +Cc: Hans de Goede, platform-driver-x86, LKML
Hi,
Am 06.05.26 um 09:47 schrieb Armin Wolf:
> Am 04.05.26 um 10:44 schrieb Werner Sembach:
>>
>> Am 30.04.26 um 15:41 schrieb Armin Wolf:
>>> Am 30.04.26 um 15:22 schrieb Ilpo Järvinen:
>>>> On Fri, 17 Apr 2026, Armin Wolf wrote:
>>>>
>>>>> Many Uniwill-based devices do not supports the already existing
>>>>> charge limit functionality, but instead support an alternative
>>>>> interface for controlling the battery charge algorithm.
>>>>>
>>>>> Add support for this interface and update the documentation.
>>>>>
>>>>> Signed-off-by: Armin Wolf <W_Armin@gmx.de>
>>>>> ---
>>>>> .../admin-guide/laptops/uniwill-laptop.rst | 19 +-
>>>>> drivers/platform/x86/uniwill/uniwill-acpi.c | 243 +++++++++++++ +----
>>>>> drivers/platform/x86/uniwill/uniwill-wmi.c | 5 +-
>>>>> 3 files changed, 215 insertions(+), 52 deletions(-)
>>>>>
>>>>> diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/
>>>>> Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>>> index 1f3ca84c7d88..24b41dbab886 100644
>>>>> --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>>> +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst
>>>>> @@ -46,11 +46,20 @@ Battery Charging Control
>>>>> .. warning:: Some devices do not properly implement the charging
>>>>> threshold interface. Forcing
>>>>> the driver to enable access to said interface on such
>>>>> devices might damage the
>>>>> battery [1]_. Because of this the driver will not enable
>>>>> said feature even when
>>>>> - using the ``force`` module parameter.
>>>>> -
>>>>> -The ``uniwill-laptop`` driver supports controlling the battery charge
>>>>> limit. This happens over
>>>>> -the standard ``charge_control_end_threshold`` power supply sysfs
>>>>> attribute. All values
>>>>> -between 1 and 100 percent are supported.
>>>>> + using the ``force`` module parameter. The charging profile
>>>>> interface will be
>>>>> + available instead.
>>>>> +
>>>>> +The ``uniwill-laptop`` driver supports controlling the battery charge
>>>>> limit. This either happens
>>>>> +over the standard ``charge_control_end_threshold`` or ``charge_types``
>>>>> power supply sysfs attribute,
>>>>> +depending on the device. When using the ``charge_control_end_threshold``
>>>>> sysfs attribute, all values
>>>>> +between 1 and 100 percent are supported. When using the ``charge_types``
>>>>> sysfs attribute, the driver
>>>>> +supports switching between the ``Standard``, ``Trickle`` and ``Long
>>>>> Life`` profiles.
>>>>> +
>>>>> +Keep in mind that when using the ``charge_types`` sysfs attribute, the EC
>>>>> firmware will hide the
>>>>> +true charging status of the battery from the operating system,
>>>>> potentially misleading users into
>>>>> +thinking that the charging profile does not work. Checking the
>>>>> ``current_now`` sysfs attribute
>>>>> +tells you the true charging status of the battery even when using the
>>>>> ``charge_types`` sysfs
>>>>> +attribute (0 means that the battery is currently not charging).
>>>>> Additionally the driver signals the presence of battery charging
>>>>> issues through the standard
>>>>> ``health`` power supply sysfs attribute.
>>>>> diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/
>>>>> platform/x86/uniwill/uniwill-acpi.c
>>>>> index d4abcaf87e39..e11b6c8aeb0d 100644
>>>>> --- a/drivers/platform/x86/uniwill/uniwill-acpi.c
>>>>> +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
>>>>> @@ -254,6 +254,10 @@
>>>>> #define EC_ADDR_OEM_4 0x07A6
>>>>> #define OVERBOOST_DYN_TEMP_OFF BIT(1)
>>>>> +#define CHARGING_PROFILE_MASK GENMASK(5, 4)
>>>>> +#define CHARGING_PROFILE_HIGH_CAPACITY 0x00
>>>>> +#define CHARGING_PROFILE_BALANCED 0x01
>>>>> +#define CHARGING_PROFILE_STATIONARY 0x02
>>>>> #define TOUCHPAD_TOGGLE_OFF BIT(6)
>>>>> #define EC_ADDR_CHARGE_CTRL 0x07B9
>>>>> @@ -320,13 +324,15 @@
>>>>> #define UNIWILL_FEATURE_SUPER_KEY BIT(1)
>>>>> #define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2)
>>>>> #define UNIWILL_FEATURE_LIGHTBAR BIT(3)
>>>>> -#define UNIWILL_FEATURE_BATTERY BIT(4)
>>>>> -#define UNIWILL_FEATURE_CPU_TEMP BIT(5)
>>>>> -#define UNIWILL_FEATURE_GPU_TEMP BIT(6)
>>>>> -#define UNIWILL_FEATURE_PRIMARY_FAN BIT(7)
>>>>> -#define UNIWILL_FEATURE_SECONDARY_FAN BIT(8)
>>>>> -#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(9)
>>>>> -#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(10)
>>>>> +#define UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT BIT(4)
>>>>> +/* Mutually exclusive with the charge limit feature */
>>>>> +#define UNIWILL_FEATURE_BATTERY_CHARGE_MODES BIT(5)
>>>>
>>>> This feature seems to be only available through force parameter?
>>>>
>>>
>>> Yes, this is expected to change as soon as Tuxedo can verify if their
>>> devices support this feature. I tested it on my Tuxedo device, but i do
>>> not want to split the feature descriptors too much.
>> Shouldn't require too much testing, will spin up a patch for it.
>
> Thank you :)
Sorry for the delay, here is the patch ready to be included in v3:
https://gitlab.com/tuxedocomputers/development/packages/linux/-/commit/b367ce0952a4d641ef1db9c98e9a03c00902b2ab
best regards
Werner
>
> Armin Wolf
>
>>>
>>> Thanks,
>>> Armin Wolf
>>>
>>>> --
>>>> i.
>>>>
>>>>> +#define UNIWILL_FEATURE_CPU_TEMP BIT(6)
>>>>> +#define UNIWILL_FEATURE_GPU_TEMP BIT(7)
>>>>> +#define UNIWILL_FEATURE_PRIMARY_FAN BIT(8)
>>>>> +#define UNIWILL_FEATURE_SECONDARY_FAN BIT(9)
>>>>> +#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10)
>>>>> +#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11)
>>>>> enum usb_c_power_priority_options {
>>>>> USB_C_POWER_PRIORITY_CHARGING = 0,
>>>>> @@ -339,8 +345,15 @@ struct uniwill_data {
>>>>> struct regmap *regmap;
>>>>> unsigned int features;
>>>>> struct acpi_battery_hook hook;
>>>>> - unsigned int last_charge_ctrl;
>>>>> struct mutex battery_lock; /* Protects the list of currently
>>>>> registered batteries */
>>>>> + union {
>>>>> + struct {
>>>>> + /* Protects writes to last_charge_type */
>>>>> + struct mutex charge_type_lock;
>>>>> + enum power_supply_charge_type last_charge_type;
>>>>> + };
>>>>> + unsigned int last_charge_ctrl;
>>>>> + };
>>>>> bool last_fn_lock_state;
>>>>> bool last_super_key_enable_state;
>>>>> bool last_touchpad_toggle_enable_state;
>>>>> @@ -447,6 +460,12 @@ static inline bool uniwill_device_supports(const
>>>>> struct uniwill_data *data,
>>>>> return (data->features & features) == features;
>>>>> }
>>>>> +static inline bool uniwill_device_supports_any(const struct
>>>>> uniwill_data *data,
>>>>> + unsigned int features)
>>>>> +{
>>>>> + return data->features & features;
>>>>> +}
>>>>> +
>>>>> static int uniwill_ec_reg_write(void *context, unsigned int reg,
>>>>> unsigned int val)
>>>>> {
>>>>> union acpi_object params[2] = {
>>>>> @@ -1421,6 +1440,30 @@ static int uniwill_led_init(struct uniwill_data *data)
>>>>> &init_data);
>>>>> }
>>>>> +static int uniwill_read_charge_type(struct uniwill_data *data, enum
>>>>> power_supply_charge_type *type)
>>>>> +{
>>>>> + unsigned int value;
>>>>> + int ret;
>>>>> +
>>>>> + ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> +
>>>>> + switch (FIELD_GET(CHARGING_PROFILE_MASK, value)) {
>>>>> + case CHARGING_PROFILE_HIGH_CAPACITY:
>>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
>>>>> + return 0;
>>>>> + case CHARGING_PROFILE_BALANCED:
>>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
>>>>> + return 0;
>>>>> + case CHARGING_PROFILE_STATIONARY:
>>>>> + *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
>>>>> + return 0;
>>>>> + default:
>>>>> + return -EPROTO;
>>>>> + }
>>>>> +}
>>>>> +
>>>>> static int uniwill_get_property(struct power_supply *psy, const struct
>>>>> power_supply_ext *ext,
>>>>> void *drvdata, enum power_supply_property psp,
>>>>> union power_supply_propval *val)
>>>>> @@ -1431,6 +1474,16 @@ static int uniwill_get_property(struct power_supply
>>>>> *psy, const struct power_sup
>>>>> int ret;
>>>>> switch (psp) {
>>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>>> + /*
>>>>> + * We need to use the cached value here because the charging mode
>>>>> + * reported by the EC might temporarily change when a external power
>>>>> + * source has been connected.
>>>>> + */
>>>>> + mutex_lock(&data->charge_type_lock);
>>>>> + val->intval = data->last_charge_type;
>>>>> + mutex_unlock(&data->charge_type_lock);
>>>>> + return 0;
>>>>> case POWER_SUPPLY_PROP_HEALTH:
>>>>> ret = power_supply_get_property_direct(psy,
>>>>> POWER_SUPPLY_PROP_PRESENT, &prop);
>>>>> if (ret < 0)
>>>>> @@ -1479,13 +1532,52 @@ static int uniwill_get_property(struct
>>>>> power_supply *psy, const struct power_sup
>>>>> }
>>>>> }
>>>>> +static int uniwill_write_charge_type(struct uniwill_data *data, enum
>>>>> power_supply_charge_type type)
>>>>> +{
>>>>> + unsigned int value;
>>>>> +
>>>>> + switch (type) {
>>>>> + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
>>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>>> CHARGING_PROFILE_STATIONARY);
>>>>> + break;
>>>>> + case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
>>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>>> CHARGING_PROFILE_HIGH_CAPACITY);
>>>>> + break;
>>>>> + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
>>>>> + value = FIELD_PREP(CHARGING_PROFILE_MASK,
>>>>> CHARGING_PROFILE_BALANCED);
>>>>> + break;
>>>>> + default:
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + return regmap_update_bits(data->regmap, EC_ADDR_OEM_4,
>>>>> CHARGING_PROFILE_MASK, value);
>>>>> +}
>>>>> +
>>>>> +static int uniwill_restore_charge_type(struct uniwill_data *data)
>>>>> +{
>>>>> + guard(mutex)(&data->charge_type_lock);
>>>>> +
>>>>> + return uniwill_write_charge_type(data, data->last_charge_type);
>>>>> +}
>>>>> +
>>>>> static int uniwill_set_property(struct power_supply *psy, const struct
>>>>> power_supply_ext *ext,
>>>>> void *drvdata, enum power_supply_property psp,
>>>>> const union power_supply_propval *val)
>>>>> {
>>>>> struct uniwill_data *data = drvdata;
>>>>> + int ret;
>>>>> switch (psp) {
>>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>>> + mutex_lock(&data->charge_type_lock);
>>>>> +
>>>>> + ret = uniwill_write_charge_type(data, val->intval);
>>>>> + if (ret >= 0)
>>>>> + data->last_charge_type = val->intval;
>>>>> +
>>>>> + mutex_unlock(&data->charge_type_lock);
>>>>> +
>>>>> + return ret;
>>>>> case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
>>>>> if (val->intval < 0 || val->intval > 100)
>>>>> return -EINVAL;
>>>>> @@ -1501,21 +1593,41 @@ static int uniwill_property_is_writeable(struct
>>>>> power_supply *psy,
>>>>> const struct power_supply_ext *ext, void *drvdata,
>>>>> enum power_supply_property psp)
>>>>> {
>>>>> - if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD)
>>>>> + switch (psp) {
>>>>> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
>>>>> + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
>>>>> return true;
>>>>> -
>>>>> - return false;
>>>>> + default:
>>>>> + return false;
>>>>> + }
>>>>> }
>>>>> -static const enum power_supply_property uniwill_properties[] = {
>>>>> +static const enum power_supply_property uniwill_charge_limit_properties[]
>>>>> = {
>>>>> POWER_SUPPLY_PROP_HEALTH,
>>>>> POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
>>>>> };
>>>>> -static const struct power_supply_ext uniwill_extension = {
>>>>> +static const struct power_supply_ext uniwill_charge_limit_extension = {
>>>>> .name = DRIVER_NAME,
>>>>> - .properties = uniwill_properties,
>>>>> - .num_properties = ARRAY_SIZE(uniwill_properties),
>>>>> + .properties = uniwill_charge_limit_properties,
>>>>> + .num_properties = ARRAY_SIZE(uniwill_charge_limit_properties),
>>>>> + .get_property = uniwill_get_property,
>>>>> + .set_property = uniwill_set_property,
>>>>> + .property_is_writeable = uniwill_property_is_writeable,
>>>>> +};
>>>>> +
>>>>> +static const enum power_supply_property uniwill_charge_modes_properties[]
>>>>> = {
>>>>> + POWER_SUPPLY_PROP_CHARGE_TYPES,
>>>>> + POWER_SUPPLY_PROP_HEALTH,
>>>>> +};
>>>>> +
>>>>> +static const struct power_supply_ext uniwill_charge_modes_extension = {
>>>>> + .name = DRIVER_NAME,
>>>>> + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) |
>>>>> + BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
>>>>> + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE),
>>>>> + .properties = uniwill_charge_modes_properties,
>>>>> + .num_properties = ARRAY_SIZE(uniwill_charge_modes_properties),
>>>>> .get_property = uniwill_get_property,
>>>>> .set_property = uniwill_set_property,
>>>>> .property_is_writeable = uniwill_property_is_writeable,
>>>>> @@ -1531,7 +1643,13 @@ static int uniwill_add_battery(struct power_supply
>>>>> *battery, struct acpi_battery
>>>>> if (!entry)
>>>>> return -ENOMEM;
>>>>> - ret = power_supply_register_extension(battery, &uniwill_extension,
>>>>> data->dev, data);
>>>>> + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>>> + ret = power_supply_register_extension(battery,
>>>>> &uniwill_charge_limit_extension,
>>>>> + data->dev, data);
>>>>> + else
>>>>> + ret = power_supply_register_extension(battery,
>>>>> &uniwill_charge_modes_extension,
>>>>> + data->dev, data);
>>>>> +
>>>>> if (ret < 0) {
>>>>> kfree(entry);
>>>>> return ret;
>>>>> @@ -1560,7 +1678,10 @@ static int uniwill_remove_battery(struct
>>>>> power_supply *battery, struct acpi_batt
>>>>> }
>>>>> }
>>>>> - power_supply_unregister_extension(battery, &uniwill_extension);
>>>>> + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>>> + power_supply_unregister_extension(battery,
>>>>> &uniwill_charge_limit_extension);
>>>>> + else
>>>>> + power_supply_unregister_extension(battery,
>>>>> &uniwill_charge_modes_extension);
>>>>> return 0;
>>>>> }
>>>>> @@ -1570,27 +1691,36 @@ static int uniwill_battery_init(struct
>>>>> uniwill_data *data)
>>>>> unsigned int value, threshold;
>>>>> int ret;
>>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>>> - return 0;
>>>>> + if (uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT)) {
>>>>> + ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> - ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
>>>>> - if (ret < 0)
>>>>> - return ret;
>>>>> + /*
>>>>> + * The charge control threshold might be initialized with 0 by
>>>>> + * the EC to signal that said threshold is uninitialized. We thus
>>>>> + * need to replace this value with 100 to signal that we want to
>>>>> + * take control of battery charging. For the sake of completeness
>>>>> + * we also set the charging threshold to 100 if the EC- provided
>>>>> + * value is invalid.
>>>>> + */
>>>>> + threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
>>>>> + if (threshold == 0 || threshold > 100) {
>>>>> + FIELD_MODIFY(CHARGE_CTRL_MASK, &value, 100);
>>>>> + ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> + }
>>>>> + } else if (uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
>>>>> + ret = devm_mutex_init(data->dev, &data->charge_type_lock);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> - /*
>>>>> - * The charge control threshold might be initialized with 0 by
>>>>> - * the EC to signal that said threshold is uninitialized. We thus
>>>>> - * need to replace this value with 100 to signal that we want to
>>>>> - * take control of battery charging. For the sake of completeness
>>>>> - * we also set the charging threshold to 100 if the EC-provided
>>>>> - * value is invalid.
>>>>> - */
>>>>> - threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
>>>>> - if (threshold == 0 || threshold > 100) {
>>>>> - FIELD_MODIFY(CHARGE_CTRL_MASK, &value, 100);
>>>>> - ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
>>>>> + ret = uniwill_read_charge_type(data, &data->last_charge_type);
>>>>> if (ret < 0)
>>>>> return ret;
>>>>> + } else {
>>>>> + return 0;
>>>>> }
>>>>> ret = devm_mutex_init(data->dev, &data->battery_lock);
>>>>> @@ -1609,10 +1739,13 @@ static int uniwill_notifier_call(struct
>>>>> notifier_block *nb, unsigned long action
>>>>> {
>>>>> struct uniwill_data *data = container_of(nb, struct uniwill_data, nb);
>>>>> struct uniwill_battery_entry *entry;
>>>>> + int ret;
>>>>> switch (action) {
>>>>> case UNIWILL_OSD_BATTERY_ALERT:
>>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>>> + if (!uniwill_device_supports_any(data,
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
>>>>> return NOTIFY_DONE;
>>>>> mutex_lock(&data->battery_lock);
>>>>> @@ -1623,10 +1756,24 @@ static int uniwill_notifier_call(struct
>>>>> notifier_block *nb, unsigned long action
>>>>> return NOTIFY_OK;
>>>>> case UNIWILL_OSD_DC_ADAPTER_CHANGED:
>>>>> - if (!uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
>>>>> + if (!uniwill_device_supports_any(data,
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_MODES |
>>>>> + UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
>>>>> return NOTIFY_DONE;
>>>>> - return notifier_from_errno(usb_c_power_priority_restore(data));
>>>>> + if (uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_BATTERY_CHARGE_MODES)) {
>>>>> + ret = uniwill_restore_charge_type(data);
>>>>> + if (ret < 0)
>>>>> + return notifier_from_errno(ret);
>>>>> + }
>>>>> +
>>>>> + if (uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_USB_C_POWER_PRIORITY)) {
>>>>> + ret = usb_c_power_priority_restore(data);
>>>>> + if (ret < 0)
>>>>> + return notifier_from_errno(ret);
>>>>> + }
>>>>> +
>>>>> + return NOTIFY_OK;
>>>>> case UNIWILL_OSD_FN_LOCK:
>>>>> if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
>>>>> return NOTIFY_DONE;
>>>>> @@ -1810,7 +1957,7 @@ static int uniwill_suspend_touchpad_toggle(struct
>>>>> uniwill_data *data)
>>>>> static int uniwill_suspend_battery(struct uniwill_data *data)
>>>>> {
>>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>>> + if (!uniwill_device_supports(data,
>>>>> UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>>> return 0;
>>>>> /*
>>>>> @@ -1887,11 +2034,15 @@ static int uniwill_resume_touchpad_toggle(struct
>>>>> uniwill_data *data)
>>>>> static int uniwill_resume_battery(struct uniwill_data *data)
>>>>> {
>>>>> - if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
>>>>> - return 0;
>>>>> - return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL,
>>>>> CHARGE_CTRL_MASK,
>>>>> - data->last_charge_ctrl);
>>>>> + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_MODES))
>>>>> + return uniwill_restore_charge_type(data);
>>>>> +
>>>>> + if (uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT))
>>>>> + return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL,
>>>>> CHARGE_CTRL_MASK,
>>>>> + data->last_charge_ctrl);
>>>>> +
>>>>> + return 0;
>>>>> }
>>>>> static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
>>>>> @@ -1970,7 +2121,7 @@ static struct platform_driver uniwill_driver = {
>>>>> static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor
>>>>> __initdata = {
>>>>> .features = UNIWILL_FEATURE_SUPER_KEY |
>>>>> - UNIWILL_FEATURE_BATTERY |
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>>> @@ -1981,7 +2132,7 @@ static struct uniwill_device_descriptor
>>>>> lapac71h_descriptor __initdata = {
>>>>> .features = UNIWILL_FEATURE_FN_LOCK |
>>>>> UNIWILL_FEATURE_SUPER_KEY |
>>>>> UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
>>>>> - UNIWILL_FEATURE_BATTERY |
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>>> @@ -1993,7 +2144,7 @@ static struct uniwill_device_descriptor
>>>>> lapkc71f_descriptor __initdata = {
>>>>> UNIWILL_FEATURE_SUPER_KEY |
>>>>> UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
>>>>> UNIWILL_FEATURE_LIGHTBAR |
>>>>> - UNIWILL_FEATURE_BATTERY |
>>>>> + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT |
>>>>> UNIWILL_FEATURE_CPU_TEMP |
>>>>> UNIWILL_FEATURE_GPU_TEMP |
>>>>> UNIWILL_FEATURE_PRIMARY_FAN |
>>>>> @@ -2579,7 +2730,7 @@ static int __init uniwill_init(void)
>>>>> if (force) {
>>>>> /* Assume that the device supports all features except the
>>>>> charge limit */
>>>>> - device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY;
>>>>> + device_descriptor.features = UINT_MAX &
>>>>> ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT;
>>>>> pr_warn("Enabling potentially unsupported features\n");
>>>>> }
>>>>> diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/
>>>>> platform/x86/uniwill/uniwill-wmi.c
>>>>> index 31d9c39f14ab..f1b89bc63df6 100644
>>>>> --- a/drivers/platform/x86/uniwill/uniwill-wmi.c
>>>>> +++ b/drivers/platform/x86/uniwill/uniwill-wmi.c
>>>>> @@ -48,6 +48,7 @@ int devm_uniwill_wmi_register_notifier(struct device
>>>>> *dev, struct notifier_block
>>>>> static void uniwill_wmi_notify(struct wmi_device *wdev, union
>>>>> acpi_object *obj)
>>>>> {
>>>>> u32 value;
>>>>> + int ret;
>>>>> if (obj->type != ACPI_TYPE_INTEGER)
>>>>> return;
>>>>> @@ -56,7 +57,9 @@ static void uniwill_wmi_notify(struct wmi_device *wdev,
>>>>> union acpi_object *obj)
>>>>> dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
>>>>> - blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
>>>>> + ret = blocking_notifier_call_chain(&uniwill_wmi_chain_head, value,
>>>>> NULL);
>>>>> + if (notifier_to_errno(ret) < 0)
>>>>> + dev_err(&wdev->dev, "Failed to handle event %u\n", value);
>>>>> }
>>>>> /*
>>>>>
>>>>
>>>
>>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-05-11 17:40 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260417050912.5582-1-W_Armin@gmx.de>
[not found] ` <20260417050912.5582-8-W_Armin@gmx.de>
[not found] ` <e71bdb43-7fcf-519c-c25f-f59cf1489043@linux.intel.com>
[not found] ` <6570157d-d431-4275-9e73-d09bb1b344c8@gmx.de>
[not found] ` <c475d7de-48c8-4fe0-b414-2d32988f52dd@tuxedocomputers.com>
2026-05-06 7:47 ` [PATCH v2 7/7] platform/x86: uniwill-laptop: Add support for battery charge modes Armin Wolf
2026-05-11 17:40 ` Werner Sembach
[not found] ` <20260417050912.5582-2-W_Armin@gmx.de>
[not found] ` <9cc1d286-f41a-c920-aaad-1c24c4e6f2f7@linux.intel.com>
[not found] ` <903a013e-a3fb-49ea-a6f9-ad6577d57a44@gmx.de>
2026-05-06 13:42 ` [PATCH v2 1/7] platform/x86: uniwill-laptop: Properly initialize charging threshold Ilpo Järvinen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox