1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * UART support for U-Boot when launched from Coreboot
4  *
5  * Copyright 2019 Google LLC
6  */
7 
8 #define LOG_CATGEGORY	UCLASS_SERIAL
9 
10 #include <common.h>
11 #include <dm.h>
12 #include <log.h>
13 #include <ns16550.h>
14 #include <serial.h>
15 #include <acpi/acpi_table.h>
16 #include <asm/cb_sysinfo.h>
17 
18 DECLARE_GLOBAL_DATA_PTR;
19 
read_dbg2(struct ns16550_plat * plat)20 static int read_dbg2(struct ns16550_plat *plat)
21 {
22 	struct acpi_table_header *tab;
23 	struct acpi_dbg2_header *hdr;
24 	struct acpi_dbg2_device *dbg;
25 	struct acpi_gen_regaddr *addr;
26 	u32 *addr_size;
27 
28 	log_debug("Looking for DBG2 in ACPI tables\n");
29 	if (!gd->acpi_start) {
30 		log_debug("No ACPI tables\n");
31 		return -ENOENT;
32 	}
33 
34 	tab = acpi_find_table("DBG2");
35 	if (!tab) {
36 		log_debug("No DBG2 table\n");
37 		return -ENOENT;
38 	}
39 	hdr = container_of(tab, struct acpi_dbg2_header, header);
40 
41 	/* We only use the first device, but check that there is at least one */
42 	if (!hdr->devices_count) {
43 		log_debug("No devices\n");
44 		return -ENOENT;
45 	}
46 	if (hdr->devices_offset >= tab->length) {
47 		log_debug("Invalid offset\n");
48 		return -EINVAL;
49 	}
50 	dbg = (void *)hdr + hdr->devices_offset;
51 	if (dbg->revision) {
52 		log_debug("Invalid revision %d\n", dbg->revision);
53 		return -EINVAL;
54 	}
55 	if (!dbg->address_count) {
56 		log_debug("No addresses\n");
57 		return -EINVAL;
58 	}
59 	if (dbg->port_type != ACPI_DBG2_SERIAL_PORT) {
60 		log_debug("Not a serial port\n");
61 		return -EPROTOTYPE;
62 	}
63 	if (dbg->port_subtype != ACPI_DBG2_16550_COMPATIBLE) {
64 		log_debug("Incompatible serial port\n");
65 		return -EPROTOTYPE;
66 	}
67 	if (dbg->base_address_offset >= dbg->length ||
68 	    dbg->address_size_offset >= dbg->length) {
69 		log_debug("Invalid base address/size offsets %d, %d\n",
70 			  dbg->base_address_offset, dbg->address_size_offset);
71 		return -EINVAL;
72 	}
73 	addr_size = (void *)dbg + dbg->address_size_offset;
74 	if (!*addr_size) {
75 		log_debug("Zero address size\n");
76 		return -EINVAL;
77 	}
78 	addr = (void *)dbg + dbg->base_address_offset;
79 	if (addr->space_id != ACPI_ADDRESS_SPACE_MEMORY) {
80 		log_debug("Incompatible space %d\n", addr->space_id);
81 		return -EPROTOTYPE;
82 	}
83 
84 	plat->base = addr->addrl;
85 
86 	/* ACPI_ACCESS_SIZE_DWORD_ACCESS is 3; we want 2 */
87 	plat->reg_shift = addr->access_size - 1;
88 	plat->reg_width = 4; /* coreboot sets bit_width to 0 */
89 	plat->clock = 1843200;
90 	plat->fcr = UART_FCR_DEFVAL;
91 	plat->flags = 0;
92 	log_debug("Collected UART from ACPI DBG2 table\n");
93 
94 	return 0;
95 }
96 
coreboot_of_to_plat(struct udevice * dev)97 static int coreboot_of_to_plat(struct udevice *dev)
98 {
99 	struct ns16550_plat *plat = dev_get_plat(dev);
100 	struct cb_serial *cb_info = lib_sysinfo.serial;
101 	int ret = -ENOENT;
102 
103 	if (cb_info) {
104 		plat->base = cb_info->baseaddr;
105 		plat->reg_shift = cb_info->regwidth == 4 ? 2 : 0;
106 		plat->reg_width = cb_info->regwidth;
107 		plat->clock = cb_info->input_hertz;
108 		plat->fcr = UART_FCR_DEFVAL;
109 		plat->flags = 0;
110 		if (cb_info->type == CB_SERIAL_TYPE_IO_MAPPED)
111 			plat->flags |= NS16550_FLAG_IO;
112 		ret = 0;
113 	} else if (IS_ENABLED(CONFIG_COREBOOT_SERIAL_FROM_DBG2)) {
114 		ret = read_dbg2(plat);
115 	}
116 
117 	if (ret) {
118 		/*
119 		 * Returning an error will cause U-Boot to complain that
120 		 * there is no UART, which may panic. So stay silent and
121 		 * pray that the video console will work.
122 		 */
123 		log_debug("Cannot detect UART\n");
124 	}
125 
126 	return 0;
127 }
128 
129 static const struct udevice_id coreboot_serial_ids[] = {
130 	{ .compatible = "coreboot-serial" },
131 	{ },
132 };
133 
134 U_BOOT_DRIVER(coreboot_uart) = {
135 	.name	= "coreboot_uart",
136 	.id	= UCLASS_SERIAL,
137 	.of_match	= coreboot_serial_ids,
138 	.priv_auto	= sizeof(struct ns16550),
139 	.plat_auto	= sizeof(struct ns16550_plat),
140 	.of_to_plat  = coreboot_of_to_plat,
141 	.probe	= ns16550_serial_probe,
142 	.ops	= &ns16550_serial_ops,
143 	.flags	= DM_FLAG_PRE_RELOC,
144 };
145