|
| 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(serial_ctrl_hid, serial_ctrl_uid, -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(-ENODEV); |
| 40 | + } |
| 41 | + |
| 42 | + /* get_first_physical_node() returns a weak ref */ |
| 43 | + ctrl_dev = get_device(acpi_get_first_physical_node(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(-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(name, sizeof(name), "%s:0", dev_name(ctrl_dev)); |
| 56 | + break; |
| 57 | + case 1: |
| 58 | + snprintf(name, sizeof(name), "%s.%d", |
| 59 | + dev_name(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(ctrl_dev, name); |
| 67 | + put_device(ctrl_dev); |
| 68 | + if (!child) { |
| 69 | + pr_err("error could not find '%s' device\n", name); |
| 70 | + ctrl_dev = ERR_PTR(-ENODEV); |
| 71 | + goto put_ctrl_adev; |
| 72 | + } |
| 73 | + |
| 74 | + ctrl_dev = child; |
| 75 | + } |
| 76 | + |
| 77 | +put_ctrl_adev: |
| 78 | + acpi_dev_put(ctrl_adev); |
| 79 | + return ctrl_dev; |
| 80 | +} |
0 commit comments