diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3191,6 +3191,7 @@ dev/uart/uart_dev_msm.c optional uart uart_msm fdt dev/uart/uart_dev_mvebu.c optional uart uart_mvebu fdt dev/uart/uart_dev_ns8250.c optional uart uart_ns8250 | uart uart_snps +dev/uart/uart_dev_dnv8250.c optional uart uart_ns8250 dev/uart/uart_dev_pl011.c optional uart pl011 dev/uart/uart_dev_quicc.c optional uart quicc dev/uart/uart_dev_snps.c optional uart uart_snps fdt diff --git a/sys/dev/ic/ns16550.h b/sys/dev/ic/ns16550.h --- a/sys/dev/ic/ns16550.h +++ b/sys/dev/ic/ns16550.h @@ -148,6 +148,10 @@ #define REG_DLL com_dll #define REG_DLH com_dlm +#define REG_PSR 0x30 /* Pre-Scalar register */ +#define REG_UCMR 0x34 /* UART Clock Gen Multiplier Register */ +#define REG_UCDR 0x38 /* UART Clock Gen Divisor Register */ + /* 16450 register #7. Not multiplexed. */ #define com_scr 7 /* scratch register (R/W) */ diff --git a/sys/dev/uart/uart.h b/sys/dev/uart/uart.h --- a/sys/dev/uart/uart.h +++ b/sys/dev/uart/uart.h @@ -45,6 +45,7 @@ u_int regshft; u_int regiowidth; u_int busy_detect; + u_int noinit; }; #define uart_regofs(bas, reg) ((reg) << (bas)->regshft) @@ -102,6 +103,7 @@ struct uart_class; extern struct uart_class uart_ns8250_class __attribute__((weak)); +extern struct uart_class uart_dnv8250_class __attribute__((weak)); extern struct uart_class uart_quicc_class __attribute__((weak)); extern struct uart_class uart_z8530_class __attribute__((weak)); diff --git a/sys/dev/uart/uart_bus.h b/sys/dev/uart/uart_bus.h --- a/sys/dev/uart/uart_bus.h +++ b/sys/dev/uart/uart_bus.h @@ -59,6 +59,7 @@ /* UART quirk flags */ #define UART_F_BUSY_DETECT 0x1 #define UART_F_IGNORE_SPCR_REGSHFT 0x2 +#define UART_F_NOINIT 0x4 /* * UART class & instance (=softc) diff --git a/sys/dev/uart/uart_bus_pci.c b/sys/dev/uart/uart_bus_pci.c --- a/sys/dev/uart/uart_bus_pci.c +++ b/sys/dev/uart/uart_bus_pci.c @@ -85,6 +85,11 @@ #define PCI_NO_MSI 0x40000000 #define PCI_RID_MASK 0x0000ffff +static const struct pci_id pci_denverton_ids[] = { +{ 0x8086, 0x19d8, 0xffff, 0, "Intel Denverton UART", 0x10 }, +{ 0xffff, 0, 0xffff, 0, NULL, 0, 0 } +}; + static const struct pci_id pci_ns8250_ids[] = { { 0x1028, 0x0008, 0xffff, 0, "Dell Remote Access Card III", 0x14, 128 * DEFAULT_RCLK }, @@ -143,7 +148,6 @@ { 0x8086, 0x0f0c, 0xffff, 0, "Intel ValleyView LPIO1 HSUART#2", 0x10, 24 * DEFAULT_RCLK, 2 }, { 0x8086, 0x108f, 0xffff, 0, "Intel AMT - SOL", 0x10 }, -{ 0x8086, 0x19d8, 0xffff, 0, "Intel Denverton UART", 0x10 }, { 0x8086, 0x1c3d, 0xffff, 0, "Intel AMT - KT Controller", 0x10 }, { 0x8086, 0x1d3d, 0xffff, 0, "Intel C600/X79 Series Chipset KT Controller", 0x10 }, @@ -274,6 +278,7 @@ .desc = "Generic SimpleComm PCI device", }; int result; + int quirks = 0; sc = device_get_softc(dev); @@ -290,12 +295,20 @@ sc->sc_class = &uart_ns8250_class; goto match; } + /* Add checks for non-ns8250 IDs here. */ + id = uart_pci_match(dev, pci_denverton_ids); + if (id != NULL) { + sc->sc_class = &uart_dnv8250_class; + quirks = UART_F_NOINIT; + goto match; + } + return (ENXIO); match: result = uart_bus_probe(dev, id->regshft, 0, id->rclk, - id->rid & PCI_RID_MASK, 0, 0); + id->rid & PCI_RID_MASK, 0, quirks); /* Bail out on error. */ if (result > 0) return (result); diff --git a/sys/dev/uart/uart_core.c b/sys/dev/uart/uart_core.c --- a/sys/dev/uart/uart_core.c +++ b/sys/dev/uart/uart_core.c @@ -556,6 +556,7 @@ sc->sc_bas.regiowidth = regiowidth; sc->sc_bas.rclk = (rclk == 0) ? sc->sc_class->uc_rclk : rclk; sc->sc_bas.busy_detect = !!(quirks & UART_F_BUSY_DETECT); + sc->sc_bas.noinit = !!(quirks & UART_F_NOINIT); SLIST_FOREACH(sysdev, &uart_sysdevs, next) { if (chan == sysdev->bas.chan && diff --git a/sys/dev/uart/uart_dev_dnv8250.c b/sys/dev/uart/uart_dev_dnv8250.c new file mode 100644 --- /dev/null +++ b/sys/dev/uart/uart_dev_dnv8250.c @@ -0,0 +1,242 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Juniper Networks. Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "opt_platform.h" +#include "opt_uart.h" + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include "uart_if.h" + +#define DEF_RCLK 133333333 +#define BIT(x) (1 << (x)) + + +/* + * High-level UART interface. + */ +struct dnv8250_softc { + struct ns8250_softc ns8250_base; +}; + +int dnv8250_bus_param(struct uart_softc *, int, int, int, int); + +static void +best_rational_approximation(unsigned long n_input, unsigned long d_input, + unsigned long n_max, unsigned long d_max, + unsigned long *n_out, unsigned long *d_out) +{ + unsigned long n_prev, d_prev; /* previous convergents */ + unsigned long n_cur, d_cur; /* current convergents */ + unsigned long n_rem, d_rem; /* fraction remainder */ + unsigned long tmp, fact; + + /* Initialize fraction remainder */ + n_rem = n_input; + d_rem = d_input; + + /* Init convergents to 0/1 and 1/0 */ + n_prev = 0; + d_prev = 1; + n_cur = 1; + d_cur = 0; + + while (d_rem != 0 && n_cur < n_max && d_cur < d_max) { + /* Factor for this step. */ + fact = n_rem / d_rem; + + /* Adjust fraction remainder */ + tmp = d_rem; + d_rem = n_rem % d_rem; + n_rem = tmp; + + /* Compute new numerator and save last one */ + tmp = n_prev + fact * n_cur; + n_prev = n_cur; + n_cur = tmp; + + /* Compute new denominator and save last one */ + tmp = d_prev + fact * d_cur; + d_prev = d_cur; + d_cur = tmp; + } + + if (n_cur > n_max || d_cur > d_max) { + *n_out = n_prev; + *d_out = d_prev; + } else { + *n_out = n_cur; + *d_out = d_cur; + } + return; +} + +/* Round val down to nearest power of two */ +static unsigned long +rounddown_pow2(unsigned long val) +{ + if (!(val & (val - 1))) { /* power of 2 */ + return val; + } else { + return (1UL << (flsl(val) - 1)); /* one less than msb */ + } +} + +static int +dnv8250_param(struct uart_bas *bas, int baudrate, int databits, int stopbits, + int parity) +{ + uint8_t lcr; + unsigned short ps = 16; + unsigned long fuart = baudrate * ps; + unsigned long w = BIT(24) - 1; + unsigned long mul, div; + unsigned long rclk = bas->rclk; + + lcr = 0; + if (databits >= 8) + lcr |= LCR_8BITS; + else if (databits == 7) + lcr |= LCR_7BITS; + else if (databits == 6) + lcr |= LCR_6BITS; + else + lcr |= LCR_5BITS; + if (stopbits > 1) + lcr |= LCR_STOPB; + lcr |= parity << 3; + + if (rclk < fuart) { + /* Find prescaler value that satisfies fuart < rclk */ + if (rclk > baudrate) { + ps = rclk / baudrate; /* baud rate too high */ + } else { + ps = 1; /* PLL case */ + } + fuart = ps * baudrate; + } else { + /* Get fuart closer to rclk */ + fuart *= rounddown_pow2(rclk / fuart); + } + best_rational_approximation(fuart, rclk, w, w, &mul, &div); + + uart_setreg(bas, REG_PSR, ps); /* set PS */ + uart_barrier(bas); + uart_setreg(bas, REG_UCMR, mul); /* set MUL */ + uart_barrier(bas); + uart_setreg(bas, REG_UCDR, div); /* set DIV */ + uart_barrier(bas); + uart_setreg(bas, REG_LCR, lcr); /* set LCR */ + uart_barrier(bas); + + return 0; +} + +int +dnv8250_bus_param(struct uart_softc *sc, int baudrate, int databits, + int stopbits, int parity) +{ + struct dnv8250_softc *dnv8250; + struct uart_bas *bas; + int error, limit; + + dnv8250 = (struct dnv8250_softc*)sc; + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + /* + * When using DW UART with BUSY detection it is necessary to wait + * until all serial transfers are finished before manipulating the + * line control. LCR will not be affected when UART is busy. + */ + if (dnv8250->ns8250_base.busy_detect != 0) { + /* + * Pick an arbitrary high limit to avoid getting stuck in + * an infinite loop in case when the hardware is broken. + */ + limit = 10 * 1024; + while (((uart_getreg(bas, DW_REG_USR) & USR_BUSY) != 0) && + --limit) + DELAY(4); + + if (limit <= 0) { + /* UART appears to be stuck */ + uart_unlock(sc->sc_hwmtx); + return (EIO); + } + } + + error = dnv8250_param(bas, baudrate, databits, stopbits, parity); + uart_unlock(sc->sc_hwmtx); + return (error); +} + +static kobj_method_t dnv8250_methods[] = { + KOBJMETHOD(uart_probe, ns8250_bus_probe), + KOBJMETHOD(uart_attach, ns8250_bus_attach), + KOBJMETHOD(uart_detach, ns8250_bus_detach), + KOBJMETHOD(uart_flush, ns8250_bus_flush), + KOBJMETHOD(uart_getsig, ns8250_bus_getsig), + KOBJMETHOD(uart_ioctl, ns8250_bus_ioctl), + KOBJMETHOD(uart_ipend, ns8250_bus_ipend), + KOBJMETHOD(uart_param, dnv8250_bus_param), + KOBJMETHOD(uart_receive, ns8250_bus_receive), + KOBJMETHOD(uart_setsig, ns8250_bus_setsig), + KOBJMETHOD(uart_transmit, ns8250_bus_transmit), + KOBJMETHOD_END +}; + +struct uart_class uart_dnv8250_class = { + "dnv8250", + dnv8250_methods, + sizeof(struct dnv8250_softc), + .uc_ops = &uart_ns8250_ops, + .uc_range = 8, + .uc_rclk = DEF_RCLK, + .uc_rshift = 0 +}; diff --git a/sys/dev/uart/uart_dev_ns8250.c b/sys/dev/uart/uart_dev_ns8250.c --- a/sys/dev/uart/uart_dev_ns8250.c +++ b/sys/dev/uart/uart_dev_ns8250.c @@ -837,7 +837,10 @@ return (error); mcr = MCR_IE; - if (sc->sc_sysdev == NULL) { + /* + * Skip init if sysdev is empty or quirk set in uart bus structure + */ + if (sc->sc_sysdev == NULL && !bas->noinit) { /* By using ns8250_init() we also set DTR and RTS. */ ns8250_init(bas, 115200, 8, 1, UART_PARITY_NONE); } else diff --git a/sys/dev/uart/uart_subr.c b/sys/dev/uart/uart_subr.c --- a/sys/dev/uart/uart_subr.c +++ b/sys/dev/uart/uart_subr.c @@ -53,6 +53,9 @@ static struct uart_class *uart_classes[] = { &uart_ns8250_class, +#if defined(__i386__) || defined(__x86_64__) + &uart_dnv8250_class, +#endif &uart_z8530_class, }; diff --git a/sys/modules/uart/Makefile b/sys/modules/uart/Makefile --- a/sys/modules/uart/Makefile +++ b/sys/modules/uart/Makefile @@ -13,7 +13,7 @@ ofw_bus_if= ofw_bus_if.h .endif -.if ${MACHINE} == "i386" || ${MACHINE} == "amd64" +.if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" _uart_cpu=uart_cpu_x86.c .else _uart_cpu=uart_cpu_${MACHINE}.c @@ -29,11 +29,15 @@ .endif .endif +.if ${MACHINE:Ni386:Namd64} == "" +uart_dev_dnv8250=uart_dev_dnv8250.c +.endif + KMOD= uart SRCS= ${uart_bus_acpi} uart_bus_isa.c \ uart_bus_pci.c uart_bus_puc.c uart_bus_scc.c \ uart_core.c ${uart_cpu_acpi} ${uart_cpu_machine} uart_dbg.c \ - ${uart_dev_mvebu} uart_dev_ns8250.c ${uart_dev_mu} \ + ${uart_dev_mvebu} uart_dev_ns8250.c ${uart_dev_dnv8250} ${uart_dev_mu} \ uart_dev_quicc.c uart_dev_z8530.c \ uart_if.c uart_if.h uart_subr.c uart_tty.c