1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
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: " fmt |
10 | |
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 0x80 |
28 | #define ACPI_AC_STATUS_OFFLINE 0x00 |
29 | #define ACPI_AC_STATUS_ONLINE 0x01 |
30 | #define ACPI_AC_STATUS_UNKNOWN 0xFF |
31 | |
32 | MODULE_AUTHOR("Paul Diefenbaugh" ); |
33 | MODULE_DESCRIPTION("ACPI AC Adapter Driver" ); |
34 | MODULE_LICENSE("GPL" ); |
35 | |
36 | static int acpi_ac_probe(struct platform_device *pdev); |
37 | static void acpi_ac_remove(struct platform_device *pdev); |
38 | |
39 | static void acpi_ac_notify(acpi_handle handle, u32 event, void *data); |
40 | |
41 | static const struct acpi_device_id ac_device_ids[] = { |
42 | {"ACPI0003" , 0}, |
43 | {"" , 0}, |
44 | }; |
45 | MODULE_DEVICE_TABLE(acpi, ac_device_ids); |
46 | |
47 | #ifdef CONFIG_PM_SLEEP |
48 | static int acpi_ac_resume(struct device *dev); |
49 | #endif |
50 | static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume); |
51 | |
52 | static int ac_sleep_before_get_state_ms; |
53 | static int ac_only; |
54 | |
55 | struct 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 */ |
66 | static 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(handle: ac->device->handle, pathname: "_PSR" , NULL, |
79 | data: &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 */ |
92 | static 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 | |
115 | static enum power_supply_property ac_props[] = { |
116 | POWER_SUPPLY_PROP_ONLINE, |
117 | }; |
118 | |
119 | /* Driver Model */ |
120 | static 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 for |
135 | * a specific time before doing actual operations in the |
136 | * EC event handler (_Qxx). This will cause the AC state |
137 | * reported by the ACPI event to be incorrect, so wait for a |
138 | * specific time for the EC event handler to make progress. |
139 | */ |
140 | if (ac_sleep_before_get_state_ms > 0) |
141 | msleep(msecs: 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(dev: &adev->dev), event, |
146 | (u32) ac->state); |
147 | acpi_notifier_call_chain(adev, event, (u32) ac->state); |
148 | kobject_uevent(kobj: &ac->charger->dev.kobj, action: KOBJ_CHANGE); |
149 | } |
150 | } |
151 | |
152 | static 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 triggered |
160 | * when adapter is plugged/unplugged. However, battery status |
161 | * notifications are triggered when battery starts charging or |
162 | * 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 | |
172 | static 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 | |
178 | static 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 */ |
185 | static 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 | |
204 | static 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(size: sizeof(struct acpi_ac), GFP_KERNEL); |
212 | if (!ac) |
213 | return -ENOMEM; |
214 | |
215 | ac->device = adev; |
216 | strcpy(acpi_device_name(adev), ACPI_AC_DEVICE_NAME); |
217 | strcpy(acpi_device_class(adev), ACPI_AC_CLASS); |
218 | |
219 | platform_set_drvdata(pdev, data: 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(parent: &pdev->dev, |
233 | desc: &ac->charger_desc, cfg: &psy_cfg); |
234 | if (IS_ERR(ptr: ac->charger)) { |
235 | result = PTR_ERR(ptr: 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 | handler: acpi_ac_notify, context: ac); |
247 | if (result) |
248 | goto err_unregister; |
249 | |
250 | return 0; |
251 | |
252 | err_unregister: |
253 | power_supply_unregister(psy: ac->charger); |
254 | unregister_acpi_notifier(&ac->battery_nb); |
255 | err_release_ac: |
256 | kfree(objp: ac); |
257 | |
258 | return result; |
259 | } |
260 | |
261 | #ifdef CONFIG_PM_SLEEP |
262 | static 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 | kobject_uevent(kobj: &ac->charger->dev.kobj, action: KOBJ_CHANGE); |
272 | |
273 | return 0; |
274 | } |
275 | #else |
276 | #define acpi_ac_resume NULL |
277 | #endif |
278 | |
279 | static 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(adev: ac->device, ACPI_ALL_NOTIFY, |
284 | handler: acpi_ac_notify); |
285 | power_supply_unregister(psy: ac->charger); |
286 | unregister_acpi_notifier(&ac->battery_nb); |
287 | |
288 | kfree(objp: ac); |
289 | } |
290 | |
291 | static 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 | |
301 | static 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(list: 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 | |
320 | static void __exit acpi_ac_exit(void) |
321 | { |
322 | platform_driver_unregister(&acpi_ac_driver); |
323 | } |
324 | module_init(acpi_ac_init); |
325 | module_exit(acpi_ac_exit); |
326 | |