1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
2 | /* |
3 | * In some cases UART attached devices which require an in kernel driver, |
4 | * e.g. UART attached Bluetooth HCIs are described in the ACPI tables |
5 | * by an ACPI device with a broken or missing UartSerialBusV2() resource. |
6 | * |
7 | * This causes the kernel to create a /dev/ttyS# char-device for the UART |
8 | * instead of creating an in kernel serdev-controller + serdev-device pair |
9 | * for the in kernel driver. |
10 | * |
11 | * The quirk handling in acpi_quirk_skip_serdev_enumeration() makes the kernel |
12 | * create a serdev-controller device for these UARTs instead of a /dev/ttyS#. |
13 | * |
14 | * Instantiating the actual serdev-device to bind to is up to pdx86 code, |
15 | * this header provides a helper for getting the serdev-controller device. |
16 | */ |
17 | #include <linux/acpi.h> |
18 | #include <linux/device.h> |
19 | #include <linux/err.h> |
20 | #include <linux/printk.h> |
21 | #include <linux/sprintf.h> |
22 | #include <linux/string.h> |
23 | |
24 | static inline struct device * |
25 | get_serdev_controller(const char *serial_ctrl_hid, |
26 | const char *serial_ctrl_uid, |
27 | int serial_ctrl_port, |
28 | const char *serdev_ctrl_name) |
29 | { |
30 | struct device *ctrl_dev, *child; |
31 | struct acpi_device *ctrl_adev; |
32 | char name[32]; |
33 | int i; |
34 | |
35 | ctrl_adev = acpi_dev_get_first_match_dev(hid: serial_ctrl_hid, uid: serial_ctrl_uid, hrv: -1); |
36 | if (!ctrl_adev) { |
37 | pr_err("error could not get %s/%s serial-ctrl adev\n" , |
38 | serial_ctrl_hid, serial_ctrl_uid); |
39 | return ERR_PTR(error: -ENODEV); |
40 | } |
41 | |
42 | /* get_first_physical_node() returns a weak ref */ |
43 | ctrl_dev = get_device(dev: acpi_get_first_physical_node(adev: ctrl_adev)); |
44 | if (!ctrl_dev) { |
45 | pr_err("error could not get %s/%s serial-ctrl physical node\n" , |
46 | serial_ctrl_hid, serial_ctrl_uid); |
47 | ctrl_dev = ERR_PTR(error: -ENODEV); |
48 | goto put_ctrl_adev; |
49 | } |
50 | |
51 | /* Walk host -> uart-ctrl -> port -> serdev-ctrl */ |
52 | for (i = 0; i < 3; i++) { |
53 | switch (i) { |
54 | case 0: |
55 | snprintf(buf: name, size: sizeof(name), fmt: "%s:0" , dev_name(dev: ctrl_dev)); |
56 | break; |
57 | case 1: |
58 | snprintf(buf: name, size: sizeof(name), fmt: "%s.%d" , |
59 | dev_name(dev: ctrl_dev), serial_ctrl_port); |
60 | break; |
61 | case 2: |
62 | strscpy(name, serdev_ctrl_name, sizeof(name)); |
63 | break; |
64 | } |
65 | |
66 | child = device_find_child_by_name(parent: ctrl_dev, name); |
67 | put_device(dev: ctrl_dev); |
68 | if (!child) { |
69 | pr_err("error could not find '%s' device\n" , name); |
70 | ctrl_dev = ERR_PTR(error: -ENODEV); |
71 | goto put_ctrl_adev; |
72 | } |
73 | |
74 | ctrl_dev = child; |
75 | } |
76 | |
77 | put_ctrl_adev: |
78 | acpi_dev_put(adev: ctrl_adev); |
79 | return ctrl_dev; |
80 | } |
81 | |