1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * pnpacpi -- PnP ACPI driver |
4 | * |
5 | * Copyright (c) 2004 Matthieu Castet <castet.matthieu@free.fr> |
6 | * Copyright (c) 2004 Li Shaohua <shaohua.li@intel.com> |
7 | */ |
8 | |
9 | #include <linux/export.h> |
10 | #include <linux/acpi.h> |
11 | #include <linux/pnp.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/mod_devicetable.h> |
14 | |
15 | #include "../base.h" |
16 | #include "pnpacpi.h" |
17 | |
18 | static int num; |
19 | |
20 | /* |
21 | * Compatible Device IDs |
22 | */ |
23 | #define TEST_HEX(c) \ |
24 | if (!(('0' <= (c) && (c) <= '9') || ('A' <= (c) && (c) <= 'F'))) \ |
25 | return 0 |
26 | #define TEST_ALPHA(c) \ |
27 | if (!('A' <= (c) && (c) <= 'Z')) \ |
28 | return 0 |
29 | static int __init ispnpidacpi(const char *id) |
30 | { |
31 | TEST_ALPHA(id[0]); |
32 | TEST_ALPHA(id[1]); |
33 | TEST_ALPHA(id[2]); |
34 | TEST_HEX(id[3]); |
35 | TEST_HEX(id[4]); |
36 | TEST_HEX(id[5]); |
37 | TEST_HEX(id[6]); |
38 | if (id[7] != '\0') |
39 | return 0; |
40 | return 1; |
41 | } |
42 | |
43 | static int pnpacpi_get_resources(struct pnp_dev *dev) |
44 | { |
45 | pnp_dbg(&dev->dev, "get resources\n" ); |
46 | return pnpacpi_parse_allocated_resource(dev); |
47 | } |
48 | |
49 | static int pnpacpi_set_resources(struct pnp_dev *dev) |
50 | { |
51 | struct acpi_device *acpi_dev; |
52 | acpi_handle handle; |
53 | int ret = 0; |
54 | |
55 | pnp_dbg(&dev->dev, "set resources\n" ); |
56 | |
57 | acpi_dev = ACPI_COMPANION(&dev->dev); |
58 | if (!acpi_dev) { |
59 | dev_dbg(&dev->dev, "ACPI device not found in %s!\n" , __func__); |
60 | return -ENODEV; |
61 | } |
62 | |
63 | if (WARN_ON_ONCE(acpi_dev != dev->data)) |
64 | dev->data = acpi_dev; |
65 | |
66 | handle = acpi_dev->handle; |
67 | if (acpi_has_method(handle, METHOD_NAME__SRS)) { |
68 | struct acpi_buffer buffer; |
69 | |
70 | ret = pnpacpi_build_resource_template(dev, &buffer); |
71 | if (ret) |
72 | return ret; |
73 | |
74 | ret = pnpacpi_encode_resources(dev, &buffer); |
75 | if (!ret) { |
76 | acpi_status status; |
77 | |
78 | status = acpi_set_current_resources(device: handle, in_buffer: &buffer); |
79 | if (ACPI_FAILURE(status)) |
80 | ret = -EIO; |
81 | } |
82 | kfree(objp: buffer.pointer); |
83 | } |
84 | if (!ret && acpi_device_power_manageable(adev: acpi_dev)) |
85 | ret = acpi_device_set_power(device: acpi_dev, ACPI_STATE_D0); |
86 | |
87 | return ret; |
88 | } |
89 | |
90 | static int pnpacpi_disable_resources(struct pnp_dev *dev) |
91 | { |
92 | struct acpi_device *acpi_dev; |
93 | acpi_status status; |
94 | |
95 | dev_dbg(&dev->dev, "disable resources\n" ); |
96 | |
97 | acpi_dev = ACPI_COMPANION(&dev->dev); |
98 | if (!acpi_dev) { |
99 | dev_dbg(&dev->dev, "ACPI device not found in %s!\n" , __func__); |
100 | return 0; |
101 | } |
102 | |
103 | /* acpi_unregister_gsi(pnp_irq(dev, 0)); */ |
104 | if (acpi_device_power_manageable(adev: acpi_dev)) |
105 | acpi_device_set_power(device: acpi_dev, ACPI_STATE_D3_COLD); |
106 | |
107 | /* continue even if acpi_device_set_power() fails */ |
108 | status = acpi_evaluate_object(object: acpi_dev->handle, pathname: "_DIS" , NULL, NULL); |
109 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) |
110 | return -ENODEV; |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | #ifdef CONFIG_ACPI_SLEEP |
116 | static bool pnpacpi_can_wakeup(struct pnp_dev *dev) |
117 | { |
118 | struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); |
119 | |
120 | if (!acpi_dev) { |
121 | dev_dbg(&dev->dev, "ACPI device not found in %s!\n" , __func__); |
122 | return false; |
123 | } |
124 | |
125 | return acpi_bus_can_wakeup(handle: acpi_dev->handle); |
126 | } |
127 | |
128 | static int pnpacpi_suspend(struct pnp_dev *dev, pm_message_t state) |
129 | { |
130 | struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); |
131 | int error = 0; |
132 | |
133 | if (!acpi_dev) { |
134 | dev_dbg(&dev->dev, "ACPI device not found in %s!\n" , __func__); |
135 | return 0; |
136 | } |
137 | |
138 | if (device_can_wakeup(dev: &dev->dev)) { |
139 | error = acpi_pm_set_device_wakeup(dev: &dev->dev, |
140 | enable: device_may_wakeup(dev: &dev->dev)); |
141 | if (error) |
142 | return error; |
143 | } |
144 | |
145 | if (acpi_device_power_manageable(adev: acpi_dev)) { |
146 | int power_state = acpi_pm_device_sleep_state(&dev->dev, NULL, |
147 | ACPI_STATE_D3_COLD); |
148 | if (power_state < 0) |
149 | power_state = (state.event == PM_EVENT_ON) ? |
150 | ACPI_STATE_D0 : ACPI_STATE_D3_COLD; |
151 | |
152 | /* |
153 | * acpi_device_set_power() can fail (keyboard port can't be |
154 | * powered-down?), and in any case, our return value is ignored |
155 | * by pnp_bus_suspend(). Hence we don't revert the wakeup |
156 | * setting if the set_power fails. |
157 | */ |
158 | error = acpi_device_set_power(device: acpi_dev, state: power_state); |
159 | } |
160 | |
161 | return error; |
162 | } |
163 | |
164 | static int pnpacpi_resume(struct pnp_dev *dev) |
165 | { |
166 | struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); |
167 | int error = 0; |
168 | |
169 | if (!acpi_dev) { |
170 | dev_dbg(&dev->dev, "ACPI device not found in %s!\n" , __func__); |
171 | return -ENODEV; |
172 | } |
173 | |
174 | if (device_may_wakeup(dev: &dev->dev)) |
175 | acpi_pm_set_device_wakeup(dev: &dev->dev, enable: false); |
176 | |
177 | if (acpi_device_power_manageable(adev: acpi_dev)) |
178 | error = acpi_device_set_power(device: acpi_dev, ACPI_STATE_D0); |
179 | |
180 | return error; |
181 | } |
182 | #endif |
183 | |
184 | struct pnp_protocol pnpacpi_protocol = { |
185 | .name = "Plug and Play ACPI" , |
186 | .get = pnpacpi_get_resources, |
187 | .set = pnpacpi_set_resources, |
188 | .disable = pnpacpi_disable_resources, |
189 | #ifdef CONFIG_ACPI_SLEEP |
190 | .can_wakeup = pnpacpi_can_wakeup, |
191 | .suspend = pnpacpi_suspend, |
192 | .resume = pnpacpi_resume, |
193 | #endif |
194 | }; |
195 | EXPORT_SYMBOL(pnpacpi_protocol); |
196 | |
197 | static const char *__init pnpacpi_get_id(struct acpi_device *device) |
198 | { |
199 | struct acpi_hardware_id *id; |
200 | |
201 | list_for_each_entry(id, &device->pnp.ids, list) { |
202 | if (ispnpidacpi(id: id->id)) |
203 | return id->id; |
204 | } |
205 | |
206 | return NULL; |
207 | } |
208 | |
209 | static int __init pnpacpi_add_device(struct acpi_device *device) |
210 | { |
211 | struct pnp_dev *dev; |
212 | const char *pnpid; |
213 | struct acpi_hardware_id *id; |
214 | int error; |
215 | |
216 | /* Skip devices that are already bound */ |
217 | if (device->physical_node_count) |
218 | return 0; |
219 | |
220 | /* |
221 | * If a PnPacpi device is not present , the device |
222 | * driver should not be loaded. |
223 | */ |
224 | if (!acpi_has_method(handle: device->handle, name: "_CRS" )) |
225 | return 0; |
226 | |
227 | pnpid = pnpacpi_get_id(device); |
228 | if (!pnpid) |
229 | return 0; |
230 | |
231 | if (!device->status.present) |
232 | return 0; |
233 | |
234 | dev = pnp_alloc_dev(&pnpacpi_protocol, id: num, pnpid); |
235 | if (!dev) |
236 | return -ENOMEM; |
237 | |
238 | ACPI_COMPANION_SET(&dev->dev, device); |
239 | dev->data = device; |
240 | /* .enabled means the device can decode the resources */ |
241 | dev->active = device->status.enabled; |
242 | if (acpi_has_method(handle: device->handle, name: "_SRS" )) |
243 | dev->capabilities |= PNP_CONFIGURABLE; |
244 | dev->capabilities |= PNP_READ; |
245 | if (device->flags.dynamic_status && (dev->capabilities & PNP_CONFIGURABLE)) |
246 | dev->capabilities |= PNP_WRITE; |
247 | if (device->flags.removable) |
248 | dev->capabilities |= PNP_REMOVABLE; |
249 | if (acpi_has_method(handle: device->handle, name: "_DIS" )) |
250 | dev->capabilities |= PNP_DISABLE; |
251 | |
252 | if (strlen(acpi_device_name(device))) |
253 | strscpy(p: dev->name, acpi_device_name(device), size: sizeof(dev->name)); |
254 | else |
255 | strscpy(p: dev->name, acpi_device_bid(device), size: sizeof(dev->name)); |
256 | |
257 | if (dev->active) |
258 | pnpacpi_parse_allocated_resource(dev); |
259 | |
260 | if (dev->capabilities & PNP_CONFIGURABLE) |
261 | pnpacpi_parse_resource_option_data(dev); |
262 | |
263 | list_for_each_entry(id, &device->pnp.ids, list) { |
264 | if (!strcmp(id->id, pnpid)) |
265 | continue; |
266 | if (!ispnpidacpi(id: id->id)) |
267 | continue; |
268 | pnp_add_id(dev, id: id->id); |
269 | } |
270 | |
271 | /* clear out the damaged flags */ |
272 | if (!dev->active) |
273 | pnp_init_resources(dev); |
274 | |
275 | error = pnp_add_device(dev); |
276 | if (error) { |
277 | put_device(dev: &dev->dev); |
278 | return error; |
279 | } |
280 | |
281 | num++; |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | static acpi_status __init pnpacpi_add_device_handler(acpi_handle handle, |
287 | u32 lvl, void *context, |
288 | void **rv) |
289 | { |
290 | struct acpi_device *device = acpi_fetch_acpi_dev(handle); |
291 | |
292 | if (!device) |
293 | return AE_CTRL_DEPTH; |
294 | if (acpi_is_pnp_device(device)) |
295 | pnpacpi_add_device(device); |
296 | return AE_OK; |
297 | } |
298 | |
299 | int pnpacpi_disabled __initdata; |
300 | static int __init pnpacpi_init(void) |
301 | { |
302 | if (acpi_disabled || pnpacpi_disabled) { |
303 | printk(KERN_INFO "pnp: PnP ACPI: disabled\n" ); |
304 | return 0; |
305 | } |
306 | printk(KERN_INFO "pnp: PnP ACPI init\n" ); |
307 | pnp_register_protocol(protocol: &pnpacpi_protocol); |
308 | acpi_get_devices(NULL, user_function: pnpacpi_add_device_handler, NULL, NULL); |
309 | printk(KERN_INFO "pnp: PnP ACPI: found %d devices\n" , num); |
310 | pnp_platform_devices = 1; |
311 | return 0; |
312 | } |
313 | |
314 | fs_initcall(pnpacpi_init); |
315 | |
316 | static int __init pnpacpi_setup(char *str) |
317 | { |
318 | if (str == NULL) |
319 | return 1; |
320 | if (!strncmp(str, "off" , 3)) |
321 | pnpacpi_disabled = 1; |
322 | return 1; |
323 | } |
324 | |
325 | __setup("pnpacpi=" , pnpacpi_setup); |
326 | |