| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * UART support for U-Boot when launched from Coreboot | |
| * | |
| * Copyright 2019 Google LLC | |
| */ | |
| #define LOG_CATGEGORY UCLASS_SERIAL | |
| #include <dm.h> | |
| #include <log.h> | |
| #include <ns16550.h> | |
| #include <serial.h> | |
| #include <acpi/acpi_table.h> | |
| #include <asm/cb_sysinfo.h> | |
| DECLARE_GLOBAL_DATA_PTR; | |
| static int read_dbg2(struct ns16550_plat *plat) | |
| { | |
| struct acpi_table_header *tab; | |
| struct acpi_dbg2_header *hdr; | |
| struct acpi_dbg2_device *dbg; | |
| struct acpi_gen_regaddr *addr; | |
| u32 *addr_size; | |
| log_debug("Looking for DBG2 in ACPI tables\n"); | |
| if (!gd->acpi_start) { | |
| log_debug("No ACPI tables\n"); | |
| return -ENOENT; | |
| } | |
| tab = acpi_find_table("DBG2"); | |
| if (!tab) { | |
| log_debug("No DBG2 table\n"); | |
| return -ENOENT; | |
| } | |
| hdr = container_of(tab, struct acpi_dbg2_header, header); | |
| /* We only use the first device, but check that there is at least one */ | |
| if (!hdr->devices_count) { | |
| log_debug("No devices\n"); | |
| return -ENOENT; | |
| } | |
| if (hdr->devices_offset >= tab->length) { | |
| log_debug("Invalid offset\n"); | |
| return -EINVAL; | |
| } | |
| dbg = (void *)hdr + hdr->devices_offset; | |
| if (dbg->revision) { | |
| log_debug("Invalid revision %d\n", dbg->revision); | |
| return -EINVAL; | |
| } | |
| if (!dbg->address_count) { | |
| log_debug("No addresses\n"); | |
| return -EINVAL; | |
| } | |
| if (dbg->port_type != ACPI_DBG2_SERIAL_PORT) { | |
| log_debug("Not a serial port\n"); | |
| return -EPROTOTYPE; | |
| } | |
| if (dbg->port_subtype != ACPI_DBG2_16550_COMPATIBLE) { | |
| log_debug("Incompatible serial port\n"); | |
| return -EPROTOTYPE; | |
| } | |
| if (dbg->base_address_offset >= dbg->length || | |
| dbg->address_size_offset >= dbg->length) { | |
| log_debug("Invalid base address/size offsets %d, %d\n", | |
| dbg->base_address_offset, dbg->address_size_offset); | |
| return -EINVAL; | |
| } | |
| addr_size = (void *)dbg + dbg->address_size_offset; | |
| if (!*addr_size) { | |
| log_debug("Zero address size\n"); | |
| return -EINVAL; | |
| } | |
| addr = (void *)dbg + dbg->base_address_offset; | |
| if (addr->space_id != ACPI_ADDRESS_SPACE_MEMORY) { | |
| log_debug("Incompatible space %d\n", addr->space_id); | |
| return -EPROTOTYPE; | |
| } | |
| plat->base = addr->addrl; | |
| /* ACPI_ACCESS_SIZE_DWORD_ACCESS is 3; we want 2 */ | |
| plat->reg_shift = addr->access_size - 1; | |
| plat->reg_width = 4; /* coreboot sets bit_width to 0 */ | |
| plat->clock = 1843200; | |
| plat->fcr = UART_FCR_DEFVAL; | |
| plat->flags = 0; | |
| log_debug("Collected UART from ACPI DBG2 table\n"); | |
| return 0; | |
| } | |
| static int coreboot_of_to_plat(struct udevice *dev) | |
| { | |
| struct ns16550_plat *plat = dev_get_plat(dev); | |
| struct cb_serial *cb_info = lib_sysinfo.serial; | |
| int ret = -ENOENT; | |
| if (cb_info) { | |
| plat->base = cb_info->baseaddr; | |
| plat->reg_shift = cb_info->regwidth == 4 ? 2 : 0; | |
| plat->reg_width = cb_info->regwidth; | |
| plat->clock = cb_info->input_hertz; | |
| plat->fcr = UART_FCR_DEFVAL; | |
| plat->flags = 0; | |
| if (cb_info->type == CB_SERIAL_TYPE_IO_MAPPED) | |
| plat->flags |= NS16550_FLAG_IO; | |
| ret = 0; | |
| } else if (IS_ENABLED(CONFIG_COREBOOT_SERIAL_FROM_DBG2)) { | |
| ret = read_dbg2(plat); | |
| } | |
| if (ret) { | |
| /* | |
| * Returning an error will cause U-Boot to complain that | |
| * there is no UART, which may panic. So stay silent and | |
| * pray that the video console will work. | |
| */ | |
| log_debug("Cannot detect UART\n"); | |
| } | |
| return 0; | |
| } | |
| static const struct udevice_id coreboot_serial_ids[] = { | |
| { .compatible = "coreboot-serial" }, | |
| { }, | |
| }; | |
| U_BOOT_DRIVER(coreboot_uart) = { | |
| .name = "coreboot_uart", | |
| .id = UCLASS_SERIAL, | |
| .of_match = coreboot_serial_ids, | |
| .priv_auto = sizeof(struct ns16550), | |
| .plat_auto = sizeof(struct ns16550_plat), | |
| .of_to_plat = coreboot_of_to_plat, | |
| .probe = ns16550_serial_probe, | |
| .ops = &ns16550_serial_ops, | |
| .flags = DM_FLAG_PRE_RELOC, | |
| }; |