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 *
get_serdev_controller_from_parent(struct device * ctrl_dev,int serial_ctrl_port,const char * serdev_ctrl_name)25 get_serdev_controller_from_parent(struct device *ctrl_dev,
26 				  int serial_ctrl_port,
27 				  const char *serdev_ctrl_name)
28 {
29 	struct device *child;
30 	char name[32];
31 	int i;
32 
33 	/* Walk host -> uart-ctrl -> port -> serdev-ctrl */
34 	for (i = 0; i < 3; i++) {
35 		switch (i) {
36 		case 0:
37 			snprintf(name, sizeof(name), "%s:0", dev_name(ctrl_dev));
38 			break;
39 		case 1:
40 			snprintf(name, sizeof(name), "%s.%d",
41 				 dev_name(ctrl_dev), serial_ctrl_port);
42 			break;
43 		case 2:
44 			strscpy(name, serdev_ctrl_name, sizeof(name));
45 			break;
46 		}
47 
48 		child = device_find_child_by_name(ctrl_dev, name);
49 		put_device(ctrl_dev);
50 		if (!child) {
51 			pr_err("error could not find '%s' device\n", name);
52 			return ERR_PTR(-ENODEV);
53 		}
54 
55 		ctrl_dev = child;
56 	}
57 
58 	return ctrl_dev;
59 }
60 
61 static inline struct device *
get_serdev_controller(const char * serial_ctrl_hid,const char * serial_ctrl_uid,int serial_ctrl_port,const char * serdev_ctrl_name)62 get_serdev_controller(const char *serial_ctrl_hid,
63 		      const char *serial_ctrl_uid,
64 		      int serial_ctrl_port,
65 		      const char *serdev_ctrl_name)
66 {
67 	struct acpi_device *adev;
68 	struct device *parent;
69 
70 	adev = acpi_dev_get_first_match_dev(serial_ctrl_hid, serial_ctrl_uid, -1);
71 	if (!adev) {
72 		pr_err("error could not get %s/%s serial-ctrl adev\n",
73 		       serial_ctrl_hid, serial_ctrl_uid ?: "*");
74 		return ERR_PTR(-ENODEV);
75 	}
76 
77 	/* get_first_physical_node() returns a weak ref */
78 	parent = get_device(acpi_get_first_physical_node(adev));
79 	acpi_dev_put(adev);
80 	if (!parent) {
81 		pr_err("error could not get %s/%s serial-ctrl physical node\n",
82 		       serial_ctrl_hid, serial_ctrl_uid ?: "*");
83 		return ERR_PTR(-ENODEV);
84 	}
85 
86 	/* This puts our reference on parent and returns a ref on the ctrl */
87 	return get_serdev_controller_from_parent(parent, serial_ctrl_port, serdev_ctrl_name);
88 }
89