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