Commit | Line | Data |
---|---|---|
c942fddf | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
9104457e | 3 | * acpi_ac.c - ACPI AC Adapter Driver (Revision: 27) |
1da177e4 LT |
4 | * |
5 | * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> | |
6 | * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> | |
1da177e4 LT |
7 | */ |
8 | ||
2249ff34 RW |
9 | #define pr_fmt(fmt) "ACPI: AC: " fmt |
10 | ||
1da177e4 LT |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> | |
5a0e3ad6 | 13 | #include <linux/slab.h> |
1da177e4 LT |
14 | #include <linux/init.h> |
15 | #include <linux/types.h> | |
0ab5bb64 LT |
16 | #include <linux/dmi.h> |
17 | #include <linux/delay.h> | |
cc8ef527 | 18 | #include <linux/platform_device.h> |
d5b4a3d0 | 19 | #include <linux/power_supply.h> |
8b48463f | 20 | #include <linux/acpi.h> |
fa93854f | 21 | #include <acpi/battery.h> |
1da177e4 | 22 | |
1da177e4 | 23 | #define ACPI_AC_CLASS "ac_adapter" |
1da177e4 LT |
24 | #define ACPI_AC_DEVICE_NAME "AC Adapter" |
25 | #define ACPI_AC_FILE_STATE "state" | |
26 | #define ACPI_AC_NOTIFY_STATUS 0x80 | |
27 | #define ACPI_AC_STATUS_OFFLINE 0x00 | |
28 | #define ACPI_AC_STATUS_ONLINE 0x01 | |
29 | #define ACPI_AC_STATUS_UNKNOWN 0xFF | |
30 | ||
f52fd66d | 31 | MODULE_AUTHOR("Paul Diefenbaugh"); |
7cda93e0 | 32 | MODULE_DESCRIPTION("ACPI AC Adapter Driver"); |
1da177e4 LT |
33 | MODULE_LICENSE("GPL"); |
34 | ||
98012849 | 35 | static int acpi_ac_add(struct acpi_device *device); |
6c0eb5ba | 36 | static void acpi_ac_remove(struct acpi_device *device); |
98012849 GR |
37 | static void acpi_ac_notify(struct acpi_device *device, u32 event); |
38 | ||
39 | static const struct acpi_device_id ac_device_ids[] = { | |
40 | {"ACPI0003", 0}, | |
41 | {"", 0}, | |
42 | }; | |
43 | MODULE_DEVICE_TABLE(acpi, ac_device_ids); | |
44 | ||
45 | #ifdef CONFIG_PM_SLEEP | |
46 | static int acpi_ac_resume(struct device *dev); | |
47 | #endif | |
48 | static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume); | |
49 | ||
0ab5bb64 | 50 | static int ac_sleep_before_get_state_ms; |
3d730ee6 | 51 | static int ac_only; |
0ab5bb64 | 52 | |
98012849 GR |
53 | static struct acpi_driver acpi_ac_driver = { |
54 | .name = "ac", | |
55 | .class = ACPI_AC_CLASS, | |
56 | .ids = ac_device_ids, | |
57 | .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, | |
58 | .ops = { | |
59 | .add = acpi_ac_add, | |
60 | .remove = acpi_ac_remove, | |
61 | .notify = acpi_ac_notify, | |
62 | }, | |
63 | .drv.pm = &acpi_ac_pm, | |
64 | }; | |
65 | ||
1da177e4 | 66 | struct acpi_ac { |
297d716f KK |
67 | struct power_supply *charger; |
68 | struct power_supply_desc charger_desc; | |
9104457e | 69 | struct acpi_device *device; |
27663c58 | 70 | unsigned long long state; |
4eee4f03 | 71 | struct notifier_block battery_nb; |
1da177e4 LT |
72 | }; |
73 | ||
297d716f | 74 | #define to_acpi_ac(x) power_supply_get_drvdata(x) |
d5b4a3d0 | 75 | |
9104457e | 76 | /* AC Adapter Management */ |
4be44fcd | 77 | static int acpi_ac_get_state(struct acpi_ac *ac) |
1da177e4 | 78 | { |
98012849 GR |
79 | acpi_status status = AE_OK; |
80 | ||
81 | if (!ac) | |
82 | return -EINVAL; | |
1da177e4 | 83 | |
3d730ee6 SS |
84 | if (ac_only) { |
85 | ac->state = 1; | |
86 | return 0; | |
87 | } | |
88 | ||
98012849 | 89 | status = acpi_evaluate_integer(ac->device->handle, "_PSR", NULL, |
cc8ef527 | 90 | &ac->state); |
1da177e4 | 91 | if (ACPI_FAILURE(status)) { |
2249ff34 RW |
92 | acpi_handle_info(ac->device->handle, |
93 | "Error reading AC Adapter state: %s\n", | |
94 | acpi_format_exception(status)); | |
1da177e4 | 95 | ac->state = ACPI_AC_STATUS_UNKNOWN; |
d550d98d | 96 | return -ENODEV; |
1da177e4 | 97 | } |
4be44fcd | 98 | |
d550d98d | 99 | return 0; |
1da177e4 LT |
100 | } |
101 | ||
9104457e | 102 | /* sysfs I/F */ |
3151dbb0 ZR |
103 | static int get_ac_property(struct power_supply *psy, |
104 | enum power_supply_property psp, | |
105 | union power_supply_propval *val) | |
106 | { | |
107 | struct acpi_ac *ac = to_acpi_ac(psy); | |
108 | ||
109 | if (!ac) | |
110 | return -ENODEV; | |
111 | ||
112 | if (acpi_ac_get_state(ac)) | |
113 | return -ENODEV; | |
114 | ||
115 | switch (psp) { | |
116 | case POWER_SUPPLY_PROP_ONLINE: | |
117 | val->intval = ac->state; | |
118 | break; | |
119 | default: | |
120 | return -EINVAL; | |
121 | } | |
4c19851c | 122 | |
3151dbb0 ZR |
123 | return 0; |
124 | } | |
125 | ||
126 | static enum power_supply_property ac_props[] = { | |
127 | POWER_SUPPLY_PROP_ONLINE, | |
128 | }; | |
129 | ||
9104457e | 130 | /* Driver Model */ |
98012849 | 131 | static void acpi_ac_notify(struct acpi_device *device, u32 event) |
1da177e4 | 132 | { |
98012849 | 133 | struct acpi_ac *ac = acpi_driver_data(device); |
1da177e4 LT |
134 | |
135 | if (!ac) | |
d550d98d | 136 | return; |
1da177e4 | 137 | |
1da177e4 | 138 | switch (event) { |
f163ff51 | 139 | default: |
2249ff34 RW |
140 | acpi_handle_debug(device->handle, "Unsupported event [0x%x]\n", |
141 | event); | |
57d2dd4b | 142 | fallthrough; |
1da177e4 | 143 | case ACPI_AC_NOTIFY_STATUS: |
03d78252 CL |
144 | case ACPI_NOTIFY_BUS_CHECK: |
145 | case ACPI_NOTIFY_DEVICE_CHECK: | |
0ab5bb64 LT |
146 | /* |
147 | * A buggy BIOS may notify AC first and then sleep for | |
148 | * a specific time before doing actual operations in the | |
149 | * EC event handler (_Qxx). This will cause the AC state | |
150 | * reported by the ACPI event to be incorrect, so wait for a | |
151 | * specific time for the EC event handler to make progress. | |
152 | */ | |
153 | if (ac_sleep_before_get_state_ms > 0) | |
154 | msleep(ac_sleep_before_get_state_ms); | |
155 | ||
1da177e4 | 156 | acpi_ac_get_state(ac); |
98012849 GR |
157 | acpi_bus_generate_netlink_event(device->pnp.device_class, |
158 | dev_name(&device->dev), event, | |
159 | (u32) ac->state); | |
160 | acpi_notifier_call_chain(device, event, (u32) ac->state); | |
297d716f | 161 | kobject_uevent(&ac->charger->dev.kobj, KOBJ_CHANGE); |
1da177e4 | 162 | } |
1da177e4 LT |
163 | } |
164 | ||
4eee4f03 AM |
165 | static int acpi_ac_battery_notify(struct notifier_block *nb, |
166 | unsigned long action, void *data) | |
167 | { | |
168 | struct acpi_ac *ac = container_of(nb, struct acpi_ac, battery_nb); | |
169 | struct acpi_bus_event *event = (struct acpi_bus_event *)data; | |
170 | ||
171 | /* | |
172 | * On HP Pavilion dv6-6179er AC status notifications aren't triggered | |
173 | * when adapter is plugged/unplugged. However, battery status | |
935ab850 | 174 | * notifications are triggered when battery starts charging or |
4eee4f03 AM |
175 | * discharging. Re-reading AC status triggers lost AC notifications, |
176 | * if AC status has changed. | |
177 | */ | |
178 | if (strcmp(event->device_class, ACPI_BATTERY_CLASS) == 0 && | |
179 | event->type == ACPI_BATTERY_NOTIFY_STATUS) | |
180 | acpi_ac_get_state(ac); | |
181 | ||
182 | return NOTIFY_OK; | |
183 | } | |
184 | ||
91ea5b1d | 185 | static int __init thinkpad_e530_quirk(const struct dmi_system_id *d) |
0ab5bb64 LT |
186 | { |
187 | ac_sleep_before_get_state_ms = 1000; | |
188 | return 0; | |
189 | } | |
190 | ||
3d730ee6 SS |
191 | static int __init ac_only_quirk(const struct dmi_system_id *d) |
192 | { | |
193 | ac_only = 1; | |
194 | return 0; | |
195 | } | |
196 | ||
04900fa3 | 197 | /* Please keep this list alphabetically sorted */ |
91ea5b1d | 198 | static const struct dmi_system_id ac_dmi_table[] __initconst = { |
3d730ee6 SS |
199 | { |
200 | /* Kodlix GK45 returning incorrect state */ | |
201 | .callback = ac_only_quirk, | |
202 | .matches = { | |
203 | DMI_MATCH(DMI_PRODUCT_NAME, "GK45"), | |
0ab5bb64 LT |
204 | }, |
205 | }, | |
91ea5b1d | 206 | { |
04900fa3 HG |
207 | /* Lenovo Thinkpad e530, see comment in acpi_ac_notify() */ |
208 | .callback = thinkpad_e530_quirk, | |
91ea5b1d | 209 | .matches = { |
04900fa3 HG |
210 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), |
211 | DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"), | |
91ea5b1d CC |
212 | }, |
213 | }, | |
0ab5bb64 LT |
214 | {}, |
215 | }; | |
216 | ||
98012849 | 217 | static int acpi_ac_add(struct acpi_device *device) |
1da177e4 | 218 | { |
297d716f | 219 | struct power_supply_config psy_cfg = {}; |
4be44fcd | 220 | int result = 0; |
4be44fcd | 221 | struct acpi_ac *ac = NULL; |
1da177e4 | 222 | |
1da177e4 | 223 | |
98012849 GR |
224 | if (!device) |
225 | return -EINVAL; | |
cc8ef527 | 226 | |
36bcbec7 | 227 | ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL); |
1da177e4 | 228 | if (!ac) |
d550d98d | 229 | return -ENOMEM; |
1da177e4 | 230 | |
98012849 GR |
231 | ac->device = device; |
232 | strcpy(acpi_device_name(device), ACPI_AC_DEVICE_NAME); | |
233 | strcpy(acpi_device_class(device), ACPI_AC_CLASS); | |
234 | device->driver_data = ac; | |
1da177e4 LT |
235 | |
236 | result = acpi_ac_get_state(ac); | |
237 | if (result) | |
238 | goto end; | |
239 | ||
297d716f KK |
240 | psy_cfg.drv_data = ac; |
241 | ||
242 | ac->charger_desc.name = acpi_device_bid(device); | |
297d716f KK |
243 | ac->charger_desc.type = POWER_SUPPLY_TYPE_MAINS; |
244 | ac->charger_desc.properties = ac_props; | |
245 | ac->charger_desc.num_properties = ARRAY_SIZE(ac_props); | |
246 | ac->charger_desc.get_property = get_ac_property; | |
247 | ac->charger = power_supply_register(&ac->device->dev, | |
248 | &ac->charger_desc, &psy_cfg); | |
249 | if (IS_ERR(ac->charger)) { | |
250 | result = PTR_ERR(ac->charger); | |
f197ac13 | 251 | goto end; |
297d716f | 252 | } |
1da177e4 | 253 | |
2249ff34 RW |
254 | pr_info("%s [%s] (%s)\n", acpi_device_name(device), |
255 | acpi_device_bid(device), ac->state ? "on-line" : "off-line"); | |
1da177e4 | 256 | |
4eee4f03 AM |
257 | ac->battery_nb.notifier_call = acpi_ac_battery_notify; |
258 | register_acpi_notifier(&ac->battery_nb); | |
ab0fd674 | 259 | end: |
9104457e | 260 | if (result) |
1da177e4 | 261 | kfree(ac); |
1da177e4 | 262 | |
d550d98d | 263 | return result; |
1da177e4 LT |
264 | } |
265 | ||
90692404 | 266 | #ifdef CONFIG_PM_SLEEP |
ccda7069 | 267 | static int acpi_ac_resume(struct device *dev) |
5bfeca31 AS |
268 | { |
269 | struct acpi_ac *ac; | |
9104457e | 270 | unsigned int old_state; |
ccda7069 RW |
271 | |
272 | if (!dev) | |
273 | return -EINVAL; | |
274 | ||
98012849 | 275 | ac = acpi_driver_data(to_acpi_device(dev)); |
ccda7069 | 276 | if (!ac) |
5bfeca31 | 277 | return -EINVAL; |
ccda7069 | 278 | |
5bfeca31 AS |
279 | old_state = ac->state; |
280 | if (acpi_ac_get_state(ac)) | |
281 | return 0; | |
282 | if (old_state != ac->state) | |
297d716f | 283 | kobject_uevent(&ac->charger->dev.kobj, KOBJ_CHANGE); |
4c19851c | 284 | |
5bfeca31 AS |
285 | return 0; |
286 | } | |
06521c2e SK |
287 | #else |
288 | #define acpi_ac_resume NULL | |
90692404 | 289 | #endif |
5bfeca31 | 290 | |
6c0eb5ba | 291 | static void acpi_ac_remove(struct acpi_device *device) |
1da177e4 | 292 | { |
98012849 GR |
293 | struct acpi_ac *ac = NULL; |
294 | ||
98012849 | 295 | if (!device || !acpi_driver_data(device)) |
6c0eb5ba | 296 | return; |
1da177e4 | 297 | |
98012849 | 298 | ac = acpi_driver_data(device); |
1da177e4 | 299 | |
297d716f | 300 | power_supply_unregister(ac->charger); |
4eee4f03 | 301 | unregister_acpi_notifier(&ac->battery_nb); |
cc8ef527 | 302 | |
1da177e4 | 303 | kfree(ac); |
1da177e4 LT |
304 | } |
305 | ||
4be44fcd | 306 | static int __init acpi_ac_init(void) |
1da177e4 | 307 | { |
3f86b832 | 308 | int result; |
1da177e4 | 309 | |
4d8316d5 PM |
310 | if (acpi_disabled) |
311 | return -ENODEV; | |
1da177e4 | 312 | |
57a18322 HG |
313 | if (acpi_quirk_skip_acpi_ac_and_battery()) |
314 | return -ENODEV; | |
91ea5b1d | 315 | |
57a18322 | 316 | dmi_check_system(ac_dmi_table); |
af3ec837 | 317 | |
98012849 | 318 | result = acpi_bus_register_driver(&acpi_ac_driver); |
9104457e | 319 | if (result < 0) |
d550d98d | 320 | return -ENODEV; |
1da177e4 | 321 | |
d550d98d | 322 | return 0; |
1da177e4 LT |
323 | } |
324 | ||
4be44fcd | 325 | static void __exit acpi_ac_exit(void) |
1da177e4 | 326 | { |
98012849 | 327 | acpi_bus_unregister_driver(&acpi_ac_driver); |
1da177e4 | 328 | } |
1da177e4 LT |
329 | module_init(acpi_ac_init); |
330 | module_exit(acpi_ac_exit); |