326 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * acpi_ac.c - ACPI AC Adapter Driver (Revision: 27)4 *5 * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>6 * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>7 */8 9#define pr_fmt(fmt) "ACPI: AC: " fmt10 11#include <linux/kernel.h>12#include <linux/module.h>13#include <linux/slab.h>14#include <linux/init.h>15#include <linux/types.h>16#include <linux/dmi.h>17#include <linux/delay.h>18#include <linux/platform_device.h>19#include <linux/power_supply.h>20#include <linux/string_choices.h>21#include <linux/acpi.h>22#include <acpi/battery.h>23 24#define ACPI_AC_CLASS "ac_adapter"25#define ACPI_AC_DEVICE_NAME "AC Adapter"26#define ACPI_AC_FILE_STATE "state"27#define ACPI_AC_NOTIFY_STATUS 0x8028#define ACPI_AC_STATUS_OFFLINE 0x0029#define ACPI_AC_STATUS_ONLINE 0x0130#define ACPI_AC_STATUS_UNKNOWN 0xFF31 32MODULE_AUTHOR("Paul Diefenbaugh");33MODULE_DESCRIPTION("ACPI AC Adapter Driver");34MODULE_LICENSE("GPL");35 36static int acpi_ac_probe(struct platform_device *pdev);37static void acpi_ac_remove(struct platform_device *pdev);38 39static void acpi_ac_notify(acpi_handle handle, u32 event, void *data);40 41static const struct acpi_device_id ac_device_ids[] = {42 {"ACPI0003", 0},43 {"", 0},44};45MODULE_DEVICE_TABLE(acpi, ac_device_ids);46 47#ifdef CONFIG_PM_SLEEP48static int acpi_ac_resume(struct device *dev);49#endif50static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume);51 52static int ac_sleep_before_get_state_ms;53static int ac_only;54 55struct acpi_ac {56 struct power_supply *charger;57 struct power_supply_desc charger_desc;58 struct acpi_device *device;59 unsigned long long state;60 struct notifier_block battery_nb;61};62 63#define to_acpi_ac(x) power_supply_get_drvdata(x)64 65/* AC Adapter Management */66static int acpi_ac_get_state(struct acpi_ac *ac)67{68 acpi_status status = AE_OK;69 70 if (!ac)71 return -EINVAL;72 73 if (ac_only) {74 ac->state = 1;75 return 0;76 }77 78 status = acpi_evaluate_integer(ac->device->handle, "_PSR", NULL,79 &ac->state);80 if (ACPI_FAILURE(status)) {81 acpi_handle_info(ac->device->handle,82 "Error reading AC Adapter state: %s\n",83 acpi_format_exception(status));84 ac->state = ACPI_AC_STATUS_UNKNOWN;85 return -ENODEV;86 }87 88 return 0;89}90 91/* sysfs I/F */92static int get_ac_property(struct power_supply *psy,93 enum power_supply_property psp,94 union power_supply_propval *val)95{96 struct acpi_ac *ac = to_acpi_ac(psy);97 98 if (!ac)99 return -ENODEV;100 101 if (acpi_ac_get_state(ac))102 return -ENODEV;103 104 switch (psp) {105 case POWER_SUPPLY_PROP_ONLINE:106 val->intval = ac->state;107 break;108 default:109 return -EINVAL;110 }111 112 return 0;113}114 115static const enum power_supply_property ac_props[] = {116 POWER_SUPPLY_PROP_ONLINE,117};118 119/* Driver Model */120static void acpi_ac_notify(acpi_handle handle, u32 event, void *data)121{122 struct acpi_ac *ac = data;123 struct acpi_device *adev = ac->device;124 125 switch (event) {126 default:127 acpi_handle_debug(adev->handle, "Unsupported event [0x%x]\n",128 event);129 fallthrough;130 case ACPI_AC_NOTIFY_STATUS:131 case ACPI_NOTIFY_BUS_CHECK:132 case ACPI_NOTIFY_DEVICE_CHECK:133 /*134 * A buggy BIOS may notify AC first and then sleep for135 * a specific time before doing actual operations in the136 * EC event handler (_Qxx). This will cause the AC state137 * reported by the ACPI event to be incorrect, so wait for a138 * specific time for the EC event handler to make progress.139 */140 if (ac_sleep_before_get_state_ms > 0)141 msleep(ac_sleep_before_get_state_ms);142 143 acpi_ac_get_state(ac);144 acpi_bus_generate_netlink_event(adev->pnp.device_class,145 dev_name(&adev->dev), event,146 (u32) ac->state);147 acpi_notifier_call_chain(adev, event, (u32) ac->state);148 power_supply_changed(ac->charger);149 }150}151 152static int acpi_ac_battery_notify(struct notifier_block *nb,153 unsigned long action, void *data)154{155 struct acpi_ac *ac = container_of(nb, struct acpi_ac, battery_nb);156 struct acpi_bus_event *event = (struct acpi_bus_event *)data;157 158 /*159 * On HP Pavilion dv6-6179er AC status notifications aren't triggered160 * when adapter is plugged/unplugged. However, battery status161 * notifications are triggered when battery starts charging or162 * discharging. Re-reading AC status triggers lost AC notifications,163 * if AC status has changed.164 */165 if (strcmp(event->device_class, ACPI_BATTERY_CLASS) == 0 &&166 event->type == ACPI_BATTERY_NOTIFY_STATUS)167 acpi_ac_get_state(ac);168 169 return NOTIFY_OK;170}171 172static int __init thinkpad_e530_quirk(const struct dmi_system_id *d)173{174 ac_sleep_before_get_state_ms = 1000;175 return 0;176}177 178static int __init ac_only_quirk(const struct dmi_system_id *d)179{180 ac_only = 1;181 return 0;182}183 184/* Please keep this list alphabetically sorted */185static const struct dmi_system_id ac_dmi_table[] __initconst = {186 {187 /* Kodlix GK45 returning incorrect state */188 .callback = ac_only_quirk,189 .matches = {190 DMI_MATCH(DMI_PRODUCT_NAME, "GK45"),191 },192 },193 {194 /* Lenovo Thinkpad e530, see comment in acpi_ac_notify() */195 .callback = thinkpad_e530_quirk,196 .matches = {197 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),198 DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"),199 },200 },201 {},202};203 204static int acpi_ac_probe(struct platform_device *pdev)205{206 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);207 struct power_supply_config psy_cfg = {};208 struct acpi_ac *ac;209 int result;210 211 ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL);212 if (!ac)213 return -ENOMEM;214 215 ac->device = adev;216 strscpy(acpi_device_name(adev), ACPI_AC_DEVICE_NAME);217 strscpy(acpi_device_class(adev), ACPI_AC_CLASS);218 219 platform_set_drvdata(pdev, ac);220 221 result = acpi_ac_get_state(ac);222 if (result)223 goto err_release_ac;224 225 psy_cfg.drv_data = ac;226 227 ac->charger_desc.name = acpi_device_bid(adev);228 ac->charger_desc.type = POWER_SUPPLY_TYPE_MAINS;229 ac->charger_desc.properties = ac_props;230 ac->charger_desc.num_properties = ARRAY_SIZE(ac_props);231 ac->charger_desc.get_property = get_ac_property;232 ac->charger = power_supply_register(&pdev->dev,233 &ac->charger_desc, &psy_cfg);234 if (IS_ERR(ac->charger)) {235 result = PTR_ERR(ac->charger);236 goto err_release_ac;237 }238 239 pr_info("%s [%s] (%s-line)\n", acpi_device_name(adev),240 acpi_device_bid(adev), str_on_off(ac->state));241 242 ac->battery_nb.notifier_call = acpi_ac_battery_notify;243 register_acpi_notifier(&ac->battery_nb);244 245 result = acpi_dev_install_notify_handler(adev, ACPI_ALL_NOTIFY,246 acpi_ac_notify, ac);247 if (result)248 goto err_unregister;249 250 return 0;251 252err_unregister:253 power_supply_unregister(ac->charger);254 unregister_acpi_notifier(&ac->battery_nb);255err_release_ac:256 kfree(ac);257 258 return result;259}260 261#ifdef CONFIG_PM_SLEEP262static int acpi_ac_resume(struct device *dev)263{264 struct acpi_ac *ac = dev_get_drvdata(dev);265 unsigned int old_state;266 267 old_state = ac->state;268 if (acpi_ac_get_state(ac))269 return 0;270 if (old_state != ac->state)271 power_supply_changed(ac->charger);272 273 return 0;274}275#else276#define acpi_ac_resume NULL277#endif278 279static void acpi_ac_remove(struct platform_device *pdev)280{281 struct acpi_ac *ac = platform_get_drvdata(pdev);282 283 acpi_dev_remove_notify_handler(ac->device, ACPI_ALL_NOTIFY,284 acpi_ac_notify);285 power_supply_unregister(ac->charger);286 unregister_acpi_notifier(&ac->battery_nb);287 288 kfree(ac);289}290 291static struct platform_driver acpi_ac_driver = {292 .probe = acpi_ac_probe,293 .remove_new = acpi_ac_remove,294 .driver = {295 .name = "ac",296 .acpi_match_table = ac_device_ids,297 .pm = &acpi_ac_pm,298 },299};300 301static int __init acpi_ac_init(void)302{303 int result;304 305 if (acpi_disabled)306 return -ENODEV;307 308 if (acpi_quirk_skip_acpi_ac_and_battery())309 return -ENODEV;310 311 dmi_check_system(ac_dmi_table);312 313 result = platform_driver_register(&acpi_ac_driver);314 if (result < 0)315 return -ENODEV;316 317 return 0;318}319 320static void __exit acpi_ac_exit(void)321{322 platform_driver_unregister(&acpi_ac_driver);323}324module_init(acpi_ac_init);325module_exit(acpi_ac_exit);326