Commit | Line | Data |
---|---|---|
62a5f689 HG |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * DMI based code to deal with broken DSDTs on X86 tablets which ship with | |
4 | * Android as (part of) the factory image. The factory kernels shipped on these | |
5 | * devices typically have a bunch of things hardcoded, rather than specified | |
6 | * in their DSDT. | |
7 | * | |
8 | * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> | |
9 | */ | |
10 | ||
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
12 | ||
13 | #include <linux/acpi.h> | |
14 | #include <linux/dmi.h> | |
4014ae23 | 15 | #include <linux/gpio/consumer.h> |
62a5f689 HG |
16 | #include <linux/gpio/machine.h> |
17 | #include <linux/irq.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/serdev.h> | |
21 | #include <linux/string.h> | |
22 | ||
23 | #include "x86-android-tablets.h" | |
812a79b5 | 24 | #include "../serdev_helpers.h" |
62a5f689 | 25 | |
8b57d33a HG |
26 | static struct platform_device *x86_android_tablet_device; |
27 | ||
eee9cd5d HG |
28 | /* |
29 | * This helper allows getting a gpio_desc *before* the actual device consuming | |
30 | * the GPIO has been instantiated. This function _must_ only be used to handle | |
31 | * this special case such as e.g. : | |
32 | * | |
33 | * 1. Getting an IRQ from a GPIO for i2c_board_info.irq which is passed to | |
34 | * i2c_client_new() to instantiate i2c_client-s; or | |
35 | * 2. Calling desc_to_gpio() to get an old style GPIO number for gpio_keys | |
36 | * platform_data which still uses old style GPIO numbers. | |
37 | * | |
38 | * Since the consuming device has not been instatiated yet a dynamic lookup | |
39 | * is generated using the special x86_android_tablet dev for dev_id. | |
40 | * | |
41 | * For normal GPIO lookups a standard static gpiod_lookup_table _must_ be used. | |
42 | */ | |
4014ae23 HG |
43 | int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id, |
44 | bool active_low, enum gpiod_flags dflags, | |
45 | struct gpio_desc **desc) | |
62a5f689 | 46 | { |
4014ae23 | 47 | struct gpiod_lookup_table *lookup; |
62a5f689 | 48 | struct gpio_desc *gpiod; |
62a5f689 | 49 | |
4014ae23 HG |
50 | lookup = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL); |
51 | if (!lookup) | |
52 | return -ENOMEM; | |
53 | ||
54 | lookup->dev_id = KBUILD_MODNAME; | |
55 | lookup->table[0].key = chip; | |
56 | lookup->table[0].chip_hwnum = pin; | |
57 | lookup->table[0].con_id = con_id; | |
58 | lookup->table[0].flags = active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH; | |
59 | ||
60 | gpiod_add_lookup_table(lookup); | |
61 | gpiod = devm_gpiod_get(&x86_android_tablet_device->dev, con_id, dflags); | |
62 | gpiod_remove_lookup_table(lookup); | |
63 | kfree(lookup); | |
62a5f689 | 64 | |
62a5f689 | 65 | if (IS_ERR(gpiod)) { |
4014ae23 | 66 | pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), chip, pin); |
62a5f689 HG |
67 | return PTR_ERR(gpiod); |
68 | } | |
69 | ||
4014ae23 HG |
70 | if (desc) |
71 | *desc = gpiod; | |
72 | ||
62a5f689 HG |
73 | return 0; |
74 | } | |
75 | ||
76 | int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) | |
77 | { | |
78 | struct irq_fwspec fwspec = { }; | |
79 | struct irq_domain *domain; | |
80 | struct acpi_device *adev; | |
81 | struct gpio_desc *gpiod; | |
82 | unsigned int irq_type; | |
83 | acpi_handle handle; | |
84 | acpi_status status; | |
85 | int irq, ret; | |
86 | ||
87 | switch (data->type) { | |
88 | case X86_ACPI_IRQ_TYPE_APIC: | |
89 | /* | |
90 | * The DSDT may already reference the GSI in a device skipped by | |
91 | * acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI | |
92 | * to avoid EBUSY errors in this case. | |
93 | */ | |
94 | acpi_unregister_gsi(data->index); | |
95 | irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity); | |
96 | if (irq < 0) | |
97 | pr_err("error %d getting APIC IRQ %d\n", irq, data->index); | |
98 | ||
99 | return irq; | |
100 | case X86_ACPI_IRQ_TYPE_GPIOINT: | |
101 | /* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */ | |
4014ae23 HG |
102 | ret = x86_android_tablet_get_gpiod(data->chip, data->index, data->con_id, |
103 | false, GPIOD_ASIS, &gpiod); | |
62a5f689 HG |
104 | if (ret) |
105 | return ret; | |
106 | ||
107 | irq = gpiod_to_irq(gpiod); | |
108 | if (irq < 0) { | |
109 | pr_err("error %d getting IRQ %s %d\n", irq, data->chip, data->index); | |
110 | return irq; | |
111 | } | |
112 | ||
113 | irq_type = acpi_dev_get_irq_type(data->trigger, data->polarity); | |
114 | if (irq_type != IRQ_TYPE_NONE && irq_type != irq_get_trigger_type(irq)) | |
115 | irq_set_irq_type(irq, irq_type); | |
116 | ||
bd8905d7 HG |
117 | if (data->free_gpio) |
118 | devm_gpiod_put(&x86_android_tablet_device->dev, gpiod); | |
119 | ||
62a5f689 HG |
120 | return irq; |
121 | case X86_ACPI_IRQ_TYPE_PMIC: | |
122 | status = acpi_get_handle(NULL, data->chip, &handle); | |
123 | if (ACPI_FAILURE(status)) { | |
124 | pr_err("error could not get %s handle\n", data->chip); | |
125 | return -ENODEV; | |
126 | } | |
127 | ||
128 | adev = acpi_fetch_acpi_dev(handle); | |
129 | if (!adev) { | |
130 | pr_err("error could not get %s adev\n", data->chip); | |
131 | return -ENODEV; | |
132 | } | |
133 | ||
134 | fwspec.fwnode = acpi_fwnode_handle(adev); | |
135 | domain = irq_find_matching_fwspec(&fwspec, data->domain); | |
136 | if (!domain) { | |
137 | pr_err("error could not find IRQ domain for %s\n", data->chip); | |
138 | return -ENODEV; | |
139 | } | |
140 | ||
141 | return irq_create_mapping(domain, data->index); | |
142 | default: | |
143 | return 0; | |
144 | } | |
145 | } | |
146 | ||
147 | static int i2c_client_count; | |
70505ea6 | 148 | static int spi_dev_count; |
62a5f689 HG |
149 | static int pdev_count; |
150 | static int serdev_count; | |
151 | static struct i2c_client **i2c_clients; | |
70505ea6 | 152 | static struct spi_device **spi_devs; |
62a5f689 HG |
153 | static struct platform_device **pdevs; |
154 | static struct serdev_device **serdevs; | |
6dc6c0c1 | 155 | static struct gpio_keys_button *buttons; |
62a5f689 HG |
156 | static struct gpiod_lookup_table * const *gpiod_lookup_tables; |
157 | static const struct software_node *bat_swnode; | |
158 | static void (*exit_handler)(void); | |
159 | ||
160 | static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info, | |
161 | int idx) | |
162 | { | |
163 | const struct x86_i2c_client_info *client_info = &dev_info->i2c_client_info[idx]; | |
164 | struct i2c_board_info board_info = client_info->board_info; | |
165 | struct i2c_adapter *adap; | |
166 | acpi_handle handle; | |
167 | acpi_status status; | |
168 | ||
169 | board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data); | |
170 | if (board_info.irq < 0) | |
171 | return board_info.irq; | |
172 | ||
173 | status = acpi_get_handle(NULL, client_info->adapter_path, &handle); | |
174 | if (ACPI_FAILURE(status)) { | |
175 | pr_err("Error could not get %s handle\n", client_info->adapter_path); | |
176 | return -ENODEV; | |
177 | } | |
178 | ||
179 | adap = i2c_acpi_find_adapter_by_handle(handle); | |
180 | if (!adap) { | |
181 | pr_err("error could not get %s adapter\n", client_info->adapter_path); | |
182 | return -ENODEV; | |
183 | } | |
184 | ||
185 | i2c_clients[idx] = i2c_new_client_device(adap, &board_info); | |
186 | put_device(&adap->dev); | |
187 | if (IS_ERR(i2c_clients[idx])) | |
188 | return dev_err_probe(&adap->dev, PTR_ERR(i2c_clients[idx]), | |
189 | "creating I2C-client %d\n", idx); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
70505ea6 HG |
194 | static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, int idx) |
195 | { | |
196 | const struct x86_spi_dev_info *spi_dev_info = &dev_info->spi_dev_info[idx]; | |
197 | struct spi_board_info board_info = spi_dev_info->board_info; | |
198 | struct spi_controller *controller; | |
199 | struct acpi_device *adev; | |
200 | acpi_handle handle; | |
201 | acpi_status status; | |
202 | ||
203 | board_info.irq = x86_acpi_irq_helper_get(&spi_dev_info->irq_data); | |
204 | if (board_info.irq < 0) | |
205 | return board_info.irq; | |
206 | ||
207 | status = acpi_get_handle(NULL, spi_dev_info->ctrl_path, &handle); | |
208 | if (ACPI_FAILURE(status)) { | |
209 | pr_err("Error could not get %s handle\n", spi_dev_info->ctrl_path); | |
210 | return -ENODEV; | |
211 | } | |
212 | ||
213 | adev = acpi_fetch_acpi_dev(handle); | |
214 | if (!adev) { | |
215 | pr_err("Error could not get adev for %s\n", spi_dev_info->ctrl_path); | |
216 | return -ENODEV; | |
217 | } | |
218 | ||
219 | controller = acpi_spi_find_controller_by_adev(adev); | |
220 | if (!controller) { | |
221 | pr_err("Error could not get SPI controller for %s\n", spi_dev_info->ctrl_path); | |
222 | return -ENODEV; | |
223 | } | |
224 | ||
225 | spi_devs[idx] = spi_new_device(controller, &board_info); | |
226 | put_device(&controller->dev); | |
35ddd61c DC |
227 | if (!spi_devs[idx]) |
228 | return dev_err_probe(&controller->dev, -ENOMEM, | |
70505ea6 HG |
229 | "creating SPI-device %d\n", idx); |
230 | ||
231 | return 0; | |
232 | } | |
233 | ||
62a5f689 HG |
234 | static __init int x86_instantiate_serdev(const struct x86_serdev_info *info, int idx) |
235 | { | |
812a79b5 | 236 | struct acpi_device *serdev_adev; |
62a5f689 HG |
237 | struct serdev_device *serdev; |
238 | struct device *ctrl_dev; | |
239 | int ret = -ENODEV; | |
240 | ||
812a79b5 HG |
241 | ctrl_dev = get_serdev_controller(info->ctrl_hid, info->ctrl_uid, 0, |
242 | info->ctrl_devname); | |
243 | if (IS_ERR(ctrl_dev)) | |
244 | return PTR_ERR(ctrl_dev); | |
62a5f689 HG |
245 | |
246 | serdev_adev = acpi_dev_get_first_match_dev(info->serdev_hid, NULL, -1); | |
247 | if (!serdev_adev) { | |
248 | pr_err("error could not get %s serdev adev\n", info->serdev_hid); | |
812a79b5 | 249 | goto put_ctrl_dev; |
62a5f689 HG |
250 | } |
251 | ||
252 | serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev)); | |
253 | if (!serdev) { | |
254 | ret = -ENOMEM; | |
255 | goto put_serdev_adev; | |
256 | } | |
257 | ||
258 | ACPI_COMPANION_SET(&serdev->dev, serdev_adev); | |
259 | acpi_device_set_enumerated(serdev_adev); | |
260 | ||
261 | ret = serdev_device_add(serdev); | |
262 | if (ret) { | |
263 | dev_err(&serdev->dev, "error %d adding serdev\n", ret); | |
264 | serdev_device_put(serdev); | |
265 | goto put_serdev_adev; | |
266 | } | |
267 | ||
268 | serdevs[idx] = serdev; | |
269 | ||
270 | put_serdev_adev: | |
271 | acpi_dev_put(serdev_adev); | |
812a79b5 HG |
272 | put_ctrl_dev: |
273 | put_device(ctrl_dev); | |
62a5f689 HG |
274 | return ret; |
275 | } | |
276 | ||
8b57d33a | 277 | static void x86_android_tablet_remove(struct platform_device *pdev) |
62a5f689 HG |
278 | { |
279 | int i; | |
280 | ||
281 | for (i = 0; i < serdev_count; i++) { | |
282 | if (serdevs[i]) | |
283 | serdev_device_remove(serdevs[i]); | |
284 | } | |
285 | ||
286 | kfree(serdevs); | |
287 | ||
288 | for (i = 0; i < pdev_count; i++) | |
289 | platform_device_unregister(pdevs[i]); | |
290 | ||
291 | kfree(pdevs); | |
6dc6c0c1 | 292 | kfree(buttons); |
62a5f689 | 293 | |
70505ea6 HG |
294 | for (i = 0; i < spi_dev_count; i++) |
295 | spi_unregister_device(spi_devs[i]); | |
296 | ||
297 | kfree(spi_devs); | |
298 | ||
62a5f689 HG |
299 | for (i = 0; i < i2c_client_count; i++) |
300 | i2c_unregister_device(i2c_clients[i]); | |
301 | ||
302 | kfree(i2c_clients); | |
303 | ||
304 | if (exit_handler) | |
305 | exit_handler(); | |
306 | ||
307 | for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) | |
308 | gpiod_remove_lookup_table(gpiod_lookup_tables[i]); | |
309 | ||
310 | software_node_unregister(bat_swnode); | |
311 | } | |
312 | ||
8b57d33a | 313 | static __init int x86_android_tablet_probe(struct platform_device *pdev) |
62a5f689 HG |
314 | { |
315 | const struct x86_dev_info *dev_info; | |
316 | const struct dmi_system_id *id; | |
62a5f689 HG |
317 | int i, ret = 0; |
318 | ||
319 | id = dmi_first_match(x86_android_tablet_ids); | |
320 | if (!id) | |
321 | return -ENODEV; | |
322 | ||
323 | dev_info = id->driver_data; | |
8b57d33a HG |
324 | /* Allow x86_android_tablet_device use before probe() exits */ |
325 | x86_android_tablet_device = pdev; | |
62a5f689 | 326 | |
62a5f689 HG |
327 | /* |
328 | * Since this runs from module_init() it cannot use -EPROBE_DEFER, | |
329 | * instead pre-load any modules which are listed as requirements. | |
330 | */ | |
331 | for (i = 0; dev_info->modules && dev_info->modules[i]; i++) | |
332 | request_module(dev_info->modules[i]); | |
333 | ||
334 | bat_swnode = dev_info->bat_swnode; | |
335 | if (bat_swnode) { | |
336 | ret = software_node_register(bat_swnode); | |
337 | if (ret) | |
338 | return ret; | |
339 | } | |
340 | ||
341 | gpiod_lookup_tables = dev_info->gpiod_lookup_tables; | |
342 | for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) | |
343 | gpiod_add_lookup_table(gpiod_lookup_tables[i]); | |
344 | ||
345 | if (dev_info->init) { | |
346 | ret = dev_info->init(); | |
347 | if (ret < 0) { | |
8b57d33a | 348 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
349 | return ret; |
350 | } | |
351 | exit_handler = dev_info->exit; | |
352 | } | |
353 | ||
354 | i2c_clients = kcalloc(dev_info->i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL); | |
355 | if (!i2c_clients) { | |
8b57d33a | 356 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
357 | return -ENOMEM; |
358 | } | |
359 | ||
360 | i2c_client_count = dev_info->i2c_client_count; | |
361 | for (i = 0; i < i2c_client_count; i++) { | |
362 | ret = x86_instantiate_i2c_client(dev_info, i); | |
363 | if (ret < 0) { | |
8b57d33a | 364 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
365 | return ret; |
366 | } | |
367 | } | |
368 | ||
70505ea6 HG |
369 | spi_devs = kcalloc(dev_info->spi_dev_count, sizeof(*spi_devs), GFP_KERNEL); |
370 | if (!spi_devs) { | |
371 | x86_android_tablet_remove(pdev); | |
372 | return -ENOMEM; | |
373 | } | |
374 | ||
375 | spi_dev_count = dev_info->spi_dev_count; | |
376 | for (i = 0; i < spi_dev_count; i++) { | |
377 | ret = x86_instantiate_spi_dev(dev_info, i); | |
378 | if (ret < 0) { | |
379 | x86_android_tablet_remove(pdev); | |
380 | return ret; | |
381 | } | |
382 | } | |
383 | ||
e2200d3f HG |
384 | /* + 1 to make space for (optional) gpio_keys_button pdev */ |
385 | pdevs = kcalloc(dev_info->pdev_count + 1, sizeof(*pdevs), GFP_KERNEL); | |
62a5f689 | 386 | if (!pdevs) { |
8b57d33a | 387 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
388 | return -ENOMEM; |
389 | } | |
390 | ||
391 | pdev_count = dev_info->pdev_count; | |
392 | for (i = 0; i < pdev_count; i++) { | |
393 | pdevs[i] = platform_device_register_full(&dev_info->pdev_info[i]); | |
394 | if (IS_ERR(pdevs[i])) { | |
8b57d33a | 395 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
396 | return PTR_ERR(pdevs[i]); |
397 | } | |
398 | } | |
399 | ||
400 | serdevs = kcalloc(dev_info->serdev_count, sizeof(*serdevs), GFP_KERNEL); | |
401 | if (!serdevs) { | |
8b57d33a | 402 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
403 | return -ENOMEM; |
404 | } | |
405 | ||
406 | serdev_count = dev_info->serdev_count; | |
407 | for (i = 0; i < serdev_count; i++) { | |
408 | ret = x86_instantiate_serdev(&dev_info->serdev_info[i], i); | |
409 | if (ret < 0) { | |
8b57d33a | 410 | x86_android_tablet_remove(pdev); |
62a5f689 HG |
411 | return ret; |
412 | } | |
413 | } | |
414 | ||
6dc6c0c1 HG |
415 | if (dev_info->gpio_button_count) { |
416 | struct gpio_keys_platform_data pdata = { }; | |
e2200d3f HG |
417 | struct gpio_desc *gpiod; |
418 | ||
6dc6c0c1 HG |
419 | buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL); |
420 | if (!buttons) { | |
8b57d33a | 421 | x86_android_tablet_remove(pdev); |
6dc6c0c1 HG |
422 | return -ENOMEM; |
423 | } | |
424 | ||
425 | for (i = 0; i < dev_info->gpio_button_count; i++) { | |
426 | ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip, | |
4014ae23 HG |
427 | dev_info->gpio_button[i].pin, |
428 | dev_info->gpio_button[i].button.desc, | |
429 | false, GPIOD_IN, &gpiod); | |
6dc6c0c1 | 430 | if (ret < 0) { |
8b57d33a | 431 | x86_android_tablet_remove(pdev); |
6dc6c0c1 HG |
432 | return ret; |
433 | } | |
434 | ||
435 | buttons[i] = dev_info->gpio_button[i].button; | |
436 | buttons[i].gpio = desc_to_gpio(gpiod); | |
4014ae23 HG |
437 | /* Release gpiod so that gpio-keys can request it */ |
438 | devm_gpiod_put(&x86_android_tablet_device->dev, gpiod); | |
e2200d3f HG |
439 | } |
440 | ||
6dc6c0c1 HG |
441 | pdata.buttons = buttons; |
442 | pdata.nbuttons = dev_info->gpio_button_count; | |
e2200d3f | 443 | |
61226c1c | 444 | pdevs[pdev_count] = platform_device_register_data(&pdev->dev, "gpio-keys", |
e2200d3f HG |
445 | PLATFORM_DEVID_AUTO, |
446 | &pdata, sizeof(pdata)); | |
447 | if (IS_ERR(pdevs[pdev_count])) { | |
8b57d33a | 448 | x86_android_tablet_remove(pdev); |
e2200d3f HG |
449 | return PTR_ERR(pdevs[pdev_count]); |
450 | } | |
451 | pdev_count++; | |
452 | } | |
453 | ||
62a5f689 HG |
454 | return 0; |
455 | } | |
456 | ||
8b57d33a HG |
457 | static struct platform_driver x86_android_tablet_driver = { |
458 | .driver = { | |
459 | .name = KBUILD_MODNAME, | |
460 | }, | |
461 | .remove_new = x86_android_tablet_remove, | |
462 | }; | |
463 | ||
464 | static int __init x86_android_tablet_init(void) | |
465 | { | |
466 | x86_android_tablet_device = platform_create_bundle(&x86_android_tablet_driver, | |
467 | x86_android_tablet_probe, | |
468 | NULL, 0, NULL, 0); | |
469 | ||
470 | return PTR_ERR_OR_ZERO(x86_android_tablet_device); | |
471 | } | |
62a5f689 | 472 | module_init(x86_android_tablet_init); |
8b57d33a HG |
473 | |
474 | static void __exit x86_android_tablet_exit(void) | |
475 | { | |
476 | platform_device_unregister(x86_android_tablet_device); | |
477 | platform_driver_unregister(&x86_android_tablet_driver); | |
478 | } | |
479 | module_exit(x86_android_tablet_exit); | |
62a5f689 HG |
480 | |
481 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
482 | MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver"); | |
483 | MODULE_LICENSE("GPL"); |