// SPDX-License-Identifier: GPL-2.0 /* * Special driver to handle of-platdata * * Copyright 2019 Google LLC * * Some code from coreboot lpss.c */ #include #include #include #include #include #include #include #include #include /* Low-power Subsystem (LPSS) clock register */ enum { LPSS_CLOCK_CTL_REG = 0x200, LPSS_CNT_CLOCK_EN = 1, LPSS_CNT_CLK_UPDATE = 1U << 31, LPSS_CLOCK_DIV_N_SHIFT = 16, LPSS_CLOCK_DIV_N_MASK = 0x7fff << LPSS_CLOCK_DIV_N_SHIFT, LPSS_CLOCK_DIV_M_SHIFT = 1, LPSS_CLOCK_DIV_M_MASK = 0x7fff << LPSS_CLOCK_DIV_M_SHIFT, /* These set the UART input clock speed */ LPSS_UART_CLK_M_VAL = 0x25a, LPSS_UART_CLK_N_VAL = 0x7fff, }; static void lpss_clk_update(void *regs, u32 clk_m_val, u32 clk_n_val) { u32 clk_sel; clk_sel = clk_n_val << LPSS_CLOCK_DIV_N_SHIFT | clk_m_val << LPSS_CLOCK_DIV_M_SHIFT; clk_sel |= LPSS_CNT_CLK_UPDATE | LPSS_CNT_CLOCK_EN; writel(clk_sel, regs + LPSS_CLOCK_CTL_REG); } static void uart_lpss_init(void *regs) { /* Take UART out of reset */ lpss_reset_release(regs); /* Set M and N divisor inputs and enable clock */ lpss_clk_update(regs, LPSS_UART_CLK_M_VAL, LPSS_UART_CLK_N_VAL); } void apl_uart_init(pci_dev_t bdf, ulong base) { /* Set UART base address */ pci_x86_write_config(bdf, PCI_BASE_ADDRESS_0, base, PCI_SIZE_32); /* Enable memory access and bus master */ pci_x86_write_config(bdf, PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER, PCI_SIZE_32); uart_lpss_init((void *)base); } /* * This driver uses its own compatible string but almost everything else from * the standard ns16550 driver. This allows us to provide an of-platdata * implementation, since the platdata produced by of-platdata does not match * struct ns16550_plat. * * When running with of-platdata (generally TPL), the platdata is converted to * something that ns16550 expects. When running withoutof-platdata (SPL, U-Boot * proper), we use ns16550's of_to_plat routine. */ static int apl_ns16550_probe(struct udevice *dev) { struct ns16550_plat *plat = dev_get_plat(dev); if (!CONFIG_IS_ENABLED(PCI)) apl_uart_init(plat->bdf, plat->base); return ns16550_serial_probe(dev); } static int apl_ns16550_of_to_plat(struct udevice *dev) { #if CONFIG_IS_ENABLED(OF_PLATDATA) struct dtd_intel_apl_ns16550 *dtplat = dev_get_plat(dev); struct ns16550_plat *plat; /* * Convert our plat to the ns16550's plat, so we can just use * that driver */ plat = malloc(sizeof(*plat)); if (!plat) return -ENOMEM; plat->base = dtplat->early_regs[0]; plat->reg_width = 1; plat->reg_shift = dtplat->reg_shift; plat->reg_offset = 0; plat->clock = dtplat->clock_frequency; plat->fcr = UART_FCR_DEFVAL; plat->bdf = pci_ofplat_get_devfn(dtplat->reg[0]); dev->plat = plat; #else int ret; ret = ns16550_serial_of_to_plat(dev); if (ret) return ret; #endif /* OF_PLATDATA */ return 0; } static const struct udevice_id apl_ns16550_serial_ids[] = { { .compatible = "intel,apl-ns16550" }, { }, }; U_BOOT_DRIVER(intel_apl_ns16550) = { .name = "intel_apl_ns16550", .id = UCLASS_SERIAL, .of_match = apl_ns16550_serial_ids, .plat_auto = sizeof(struct ns16550_plat), .priv_auto = sizeof(struct NS16550), .ops = &ns16550_serial_ops, .of_to_plat = apl_ns16550_of_to_plat, .probe = apl_ns16550_probe, };