diff options
| -rw-r--r-- | drivers/acpi/battery.c | 147 | ||||
| -rw-r--r-- | drivers/acpi/fan.h | 27 | ||||
| -rw-r--r-- | drivers/acpi/scan.c | 4 | ||||
| -rw-r--r-- | drivers/acpi/utils.c | 17 | ||||
| -rw-r--r-- | drivers/acpi/video_detect.c | 7 | ||||
| -rw-r--r-- | drivers/i2c/i2c-core-acpi.c | 15 | ||||
| -rw-r--r-- | drivers/pci/vgaarb.c | 4 | ||||
| -rw-r--r-- | drivers/platform/x86/lenovo/thinkpad_acpi.c | 2 | ||||
| -rw-r--r-- | include/linux/acpi.h | 1 |
9 files changed, 162 insertions, 62 deletions
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f5e0eb299610..0084f308b790 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -10,10 +10,12 @@ #define pr_fmt(fmt) "ACPI: battery: " fmt +#include <linux/ctype.h> #include <linux/delay.h> #include <linux/dmi.h> #include <linux/jiffies.h> #include <linux/kernel.h> +#include <linux/kfifo.h> #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> @@ -21,6 +23,7 @@ #include <linux/slab.h> #include <linux/suspend.h> #include <linux/types.h> +#include <linux/workqueue.h> #include <linux/unaligned.h> @@ -43,6 +46,9 @@ #define MAX_STRING_LENGTH 64 +#define MAX_QUEUED_EVENTS 16 +#define NOTIF_MERGING_MS 10 + MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>"); MODULE_DESCRIPTION("ACPI Battery Driver"); @@ -95,6 +101,8 @@ struct acpi_battery { struct power_supply_desc bat_desc; struct acpi_device *device; struct device *phys_dev; + struct kfifo acpi_notif_fifo; + struct delayed_work acpi_notif_dwork; struct notifier_block pm_nb; struct list_head list; unsigned long update_time; @@ -150,27 +158,28 @@ static int acpi_battery_technology(struct acpi_battery *battery) static int acpi_battery_get_state(struct acpi_battery *battery); -static int acpi_battery_is_charged(struct acpi_battery *battery) +static bool acpi_battery_is_full(struct acpi_battery *battery) { - /* charging, discharging, critical low or charge limited */ - if (battery->state != 0) - return 0; - /* battery not reporting charge */ if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN || battery->capacity_now == 0) - return 0; + return false; /* good batteries update full_charge as the batteries degrade */ if (battery->full_charge_capacity == battery->capacity_now) - return 1; + return true; /* fallback to using design values for broken batteries */ - if (battery->design_capacity <= battery->capacity_now) - return 1; + return battery->design_capacity <= battery->capacity_now; +} - /* we don't do any sort of metric based on percentages */ - return 0; +static int acpi_battery_is_charged(struct acpi_battery *battery) +{ + /* charging, discharging, critical low or charge limited */ + if (battery->state != 0) + return 0; + + return acpi_battery_is_full(battery); } static bool acpi_battery_is_degraded(struct acpi_battery *battery) @@ -211,13 +220,14 @@ static int acpi_battery_get_property(struct power_supply *psy, if (battery->state & ACPI_BATTERY_STATE_DISCHARGING) val->intval = acpi_battery_handle_discharging(battery); else if (battery->state & ACPI_BATTERY_STATE_CHARGING) - /* Validate the status by checking the current. */ - if (battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN && - battery->rate_now == 0) { - /* On charge but no current (0W/0mA). */ - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { + /* Check the rate and capacity to validate the status. */ + if (!acpi_battery_is_full(battery) || + (battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN && + battery->rate_now > 0)) { val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else { + /* Full and zero rate. */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } else if (battery->state & ACPI_BATTERY_STATE_CHARGE_LIMITING) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; @@ -483,6 +493,15 @@ static int acpi_battery_get_status(struct acpi_battery *battery) return 0; } +static void acpi_battery_clean_unprintable_chars(char *str, size_t length) +{ + for (unsigned int i = 0; i < length; i++) { + if (!isascii(str[i]) || !isprint(str[i])) { + str[i] = '\0'; + break; + } + } +} static int extract_battery_info(const int use_bix, struct acpi_battery *battery, @@ -524,6 +543,10 @@ static int extract_battery_info(const int use_bix, battery->capacity_now > battery->full_charge_capacity) battery->capacity_now = battery->full_charge_capacity; + if (!result) + acpi_battery_clean_unprintable_chars(battery->model_number, + ARRAY_SIZE(battery->model_number)); + return result; } @@ -668,9 +691,13 @@ static ssize_t acpi_battery_alarm_store(struct device *dev, { unsigned long x; struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev)); + int err; + + err = kstrtoul(buf, 10, &x); + if (err) + return err; - if (sscanf(buf, "%lu\n", &x) == 1) - battery->alarm = x/1000; + battery->alarm = x / 1000; if (acpi_battery_present(battery)) acpi_battery_set_alarm(battery); return count; @@ -1059,14 +1086,24 @@ static void acpi_battery_refresh(struct acpi_battery *battery) } /* Driver Interface */ -static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +static void acpi_battery_notification_worker(struct work_struct *work) { - struct acpi_battery *battery = data; + struct acpi_battery *battery = container_of(work, struct acpi_battery, + acpi_notif_dwork.work); struct acpi_device *device = battery->device; + u32 events[MAX_QUEUED_EVENTS]; struct power_supply *old; + unsigned int count, i; guard(mutex)(&battery->update_lock); + count = kfifo_out(&battery->acpi_notif_fifo, events, sizeof(events)); + count /= sizeof(events[0]); + if (!count) + return; + + pr_debug("merged %u battery notifications within %dms\n", count, NOTIF_MERGING_MS); + old = battery->bat; /* * On Acer Aspire V5-573G notifications are sometimes triggered too @@ -1076,19 +1113,46 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) */ if (battery_notification_delay_ms > 0) msleep(battery_notification_delay_ms); - if (event == ACPI_BATTERY_NOTIFY_INFO) - acpi_battery_refresh(battery); + + for (i = 0; i < count; i++) { + if (events[i] == ACPI_BATTERY_NOTIFY_INFO) { + acpi_battery_refresh(battery); + break; + } + } + acpi_battery_update(battery, false); - acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, - dev_name(&device->dev), event, - acpi_battery_present(battery)); - acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), - event, acpi_battery_present(battery)); + + for (i = 0; i < count; i++) { + acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, + dev_name(&device->dev), events[i], + acpi_battery_present(battery)); + acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), + events[i], acpi_battery_present(battery)); + } + /* acpi_battery_update could remove power_supply object */ if (old && battery->bat) power_supply_changed(battery->bat); } +static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +{ + struct acpi_battery *battery = data; + + guard(mutex)(&battery->update_lock); + + if (kfifo_avail(&battery->acpi_notif_fifo) >= sizeof(event)) { + kfifo_in(&battery->acpi_notif_fifo, &event, sizeof(event)); + schedule_delayed_work(&battery->acpi_notif_dwork, + msecs_to_jiffies(NOTIF_MERGING_MS)); + + return; + } + + pr_err_ratelimited("too many battery notifications within %dms\n", NOTIF_MERGING_MS); +} + static int battery_notify(struct notifier_block *nb, unsigned long mode, void *_unused) { @@ -1231,6 +1295,29 @@ static int devm_acpi_battery_update_retry(struct device *dev, return ret; } +static void acpi_battery_notify_dwork_cleanup(void *data) +{ + struct acpi_battery *battery = data; + + cancel_delayed_work_sync(&battery->acpi_notif_dwork); + kfifo_free(&battery->acpi_notif_fifo); +} + +static int devm_acpi_battery_init_notify_dwork(struct device *dev, + struct acpi_battery *battery) +{ + int ret; + + INIT_DELAYED_WORK(&battery->acpi_notif_dwork, acpi_battery_notification_worker); + + ret = kfifo_alloc(&battery->acpi_notif_fifo, + MAX_QUEUED_EVENTS * sizeof(u32), GFP_KERNEL); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, acpi_battery_notify_dwork_cleanup, battery); +} + static int acpi_battery_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1272,6 +1359,10 @@ static int acpi_battery_probe(struct platform_device *pdev) if (result) return result; + result = devm_acpi_battery_init_notify_dwork(dev, battery); + if (result) + return result; + result = devm_acpi_install_notify_handler(dev, ACPI_ALL_NOTIFY, acpi_battery_notify, battery); if (result) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index 97ce3212edf3..e20d6ad9df80 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -10,20 +10,21 @@ #ifndef _ACPI_FAN_H_ #define _ACPI_FAN_H_ -#include <linux/kconfig.h> +#include <linux/device.h> #include <linux/limits.h> +#include <linux/types.h> -#define ACPI_FAN_DEVICE_IDS \ - {"INT3404", }, /* Fan */ \ - {"INTC1044", }, /* Fan for Tiger Lake generation */ \ - {"INTC1048", }, /* Fan for Alder Lake generation */ \ - {"INTC1063", }, /* Fan for Meteor Lake generation */ \ - {"INTC106A", }, /* Fan for Lunar Lake generation */ \ - {"INTC10A2", }, /* Fan for Raptor Lake generation */ \ - {"INTC10D6", }, /* Fan for Panther Lake generation */ \ - {"INTC10FE", }, /* Fan for Wildcat Lake generation */ \ - {"INTC10F5", }, /* Fan for Nova Lake generation */ \ - {"PNP0C0B", } /* Generic ACPI fan */ +#define ACPI_FAN_DEVICE_IDS \ + { .id = "INT3404" }, /* Fan */ \ + { .id = "INTC1044" }, /* Fan for Tiger Lake generation */ \ + { .id = "INTC1048" }, /* Fan for Alder Lake generation */ \ + { .id = "INTC1063" }, /* Fan for Meteor Lake generation */ \ + { .id = "INTC106A" }, /* Fan for Lunar Lake generation */ \ + { .id = "INTC10A2" }, /* Fan for Raptor Lake generation */ \ + { .id = "INTC10D6" }, /* Fan for Panther Lake generation */ \ + { .id = "INTC10FE" }, /* Fan for Wildcat Lake generation */ \ + { .id = "INTC10F5" }, /* Fan for Nova Lake generation */ \ + { .id = "PNP0C0B" } /* Generic ACPI fan */ #define ACPI_FPS_NAME_LEN 20 @@ -69,7 +70,7 @@ struct acpi_fan { /** * acpi_fan_speed_valid - Check if fan speed value is valid - * @speeed: Speed value returned by the ACPI firmware + * @speed: Speed value returned by the ACPI firmware * * Check if the fan speed value returned by the ACPI firmware is valid. This function is * necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 2bbe1fae2c52..e9e374f2ad66 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1765,7 +1765,6 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. */ - {ACPI_VIDEO_HID, }, {"MSHW0028", }, /* * HIDs of device with an UartSerialBusV2 resource for which userspace @@ -1788,6 +1787,9 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) fwnode_property_present(&device->fwnode, "baud"))) return true; + if (acpi_dev_is_video_device(device)) + return false; + if (!acpi_match_device_ids(device, ignore_serial_bus_ids)) return false; diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 6ab27e4826d1..d499b72574ab 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -1048,6 +1048,23 @@ static int __init acpi_backlight(char *str) __setup("acpi_backlight=", acpi_backlight); /** + * acpi_dev_is_video_device - test if device matches against ACPI video device IDs + * @adev: ACPI device to test + * + * Return: true when matches, otherwise false. + */ +bool acpi_dev_is_video_device(struct acpi_device *adev) +{ + static const struct acpi_device_id video_device_ids[] = { + { .id = ACPI_VIDEO_HID }, + { } + }; + + return adev && !acpi_match_device_ids(adev, video_device_ids); +} +EXPORT_SYMBOL(acpi_dev_is_video_device); + +/** * acpi_match_platform_list - Check if the system matches with a given list * @plat: pointer to acpi_platform_list table terminated by a NULL entry * diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index c5b08c19e847..5b0dc679e322 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -67,12 +67,7 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) long *cap = context; struct pci_dev *dev; - static const struct acpi_device_id video_ids[] = { - {ACPI_VIDEO_HID, 0}, - {"", 0}, - }; - - if (acpi_dev && !acpi_match_device_ids(acpi_dev, video_ids)) { + if (acpi_dev_is_video_device(acpi_dev)) { dev = acpi_dev_get_pci_dev(acpi_dev); if (!dev) return AE_OK; diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index e5fddacae9a4..42a7e777346b 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -131,15 +131,6 @@ static int i2c_acpi_fill_info(struct acpi_resource *ares, void *data) return 1; } -static const struct acpi_device_id i2c_acpi_ignored_device_ids[] = { - /* - * ACPI video acpi_devices, which are handled by the acpi-video driver - * sometimes contain a SERIAL_TYPE_I2C ACPI resource, ignore these. - */ - { ACPI_VIDEO_HID, 0 }, - {} -}; - struct i2c_acpi_irq_context { int irq; bool wake_capable; @@ -158,7 +149,11 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, if (!acpi_dev_ready_for_enumeration(adev)) return -ENODEV; - if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) + /* + * ACPI video devices, which are handled by the acpi-video driver, + * sometimes contain a SERIAL_TYPE_I2C ACPI resource, ignore these. + */ + if (acpi_dev_is_video_device(adev)) return -ENODEV; memset(info, 0, sizeof(*info)); diff --git a/drivers/pci/vgaarb.c b/drivers/pci/vgaarb.c index c360eee11dd9..3de05aee7859 100644 --- a/drivers/pci/vgaarb.c +++ b/drivers/pci/vgaarb.c @@ -575,9 +575,7 @@ static bool vga_is_firmware_default(struct pci_dev *pdev) static bool vga_arb_integrated_gpu(struct device *dev) { #if defined(CONFIG_ACPI) - struct acpi_device *adev = ACPI_COMPANION(dev); - - return adev && !strcmp(acpi_device_hid(adev), ACPI_VIDEO_HID); + return acpi_dev_is_video_device(ACPI_COMPANION(dev)); #else return false; #endif diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 445e1403308e..b528a1e67dd3 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -768,7 +768,7 @@ static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, if (!strcmp(context, "video")) { struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + if (!acpi_dev_is_video_device(dev)) return AE_OK; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 8d20a25f1a62..ddacac812094 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -470,6 +470,7 @@ extern char *wmi_get_acpi_device_uid(const char *guid); #define ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VIDEO 0x0800 extern char acpi_video_backlight_string[]; +extern bool acpi_dev_is_video_device(struct acpi_device *adev); extern long acpi_is_video_device(acpi_handle handle); extern void acpi_osi_setup(char *str); |
