From owner-svn-src-all@freebsd.org Tue Dec 31 10:54:13 2019 Return-Path: Delivered-To: svn-src-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id D5F5B1D1504; Tue, 31 Dec 2019 10:54:13 +0000 (UTC) (envelope-from kp@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 47nB4j5K2Nz48qR; Tue, 31 Dec 2019 10:54:13 +0000 (UTC) (envelope-from kp@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id B1A0519281; Tue, 31 Dec 2019 10:54:13 +0000 (UTC) (envelope-from kp@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id xBVAsDVa059116; Tue, 31 Dec 2019 10:54:13 GMT (envelope-from kp@FreeBSD.org) Received: (from kp@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id xBVAsDQs059114; Tue, 31 Dec 2019 10:54:13 GMT (envelope-from kp@FreeBSD.org) Message-Id: <201912311054.xBVAsDQs059114@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: kp set sender to kp@FreeBSD.org using -f From: Kristof Provost Date: Tue, 31 Dec 2019 10:54:13 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r356222 - head/sys/riscv/sifive X-SVN-Group: head X-SVN-Commit-Author: kp X-SVN-Commit-Paths: head/sys/riscv/sifive X-SVN-Commit-Revision: 356222 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 31 Dec 2019 10:54:13 -0000 Author: kp Date: Tue Dec 31 10:54:13 2019 New Revision: 356222 URL: https://svnweb.freebsd.org/changeset/base/356222 Log: sifive: uart driver Implement support for the UART as found on the SiFive FU540. It should also work on, but has not been tested with, the FU310. Reviewed by: philip Sponsored by: Axiado Added: head/sys/riscv/sifive/sifive_uart.c (contents, props changed) Modified: head/sys/riscv/sifive/files.sifive Modified: head/sys/riscv/sifive/files.sifive ============================================================================== --- head/sys/riscv/sifive/files.sifive Tue Dec 31 10:53:03 2019 (r356221) +++ head/sys/riscv/sifive/files.sifive Tue Dec 31 10:54:13 2019 (r356222) @@ -2,3 +2,4 @@ riscv/sifive/fu540_prci.c standard riscv/sifive/fu540_spi.c optional fu540spi spibus +riscv/sifive/sifive_uart.c standard Added: head/sys/riscv/sifive/sifive_uart.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/riscv/sifive/sifive_uart.c Tue Dec 31 10:54:13 2019 (r356222) @@ -0,0 +1,543 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Axiado Corporation + * All rights reserved. + * + * This software was developed in part by Kristof Provost under contract for + * Axiado Corporation. + * + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "uart_if.h" + +#define SFUART_TXDATA 0x00 +#define SFUART_TXDATA_FULL (1 << 31) +#define SFUART_RXDATA 0x04 +#define SFUART_RXDATA_EMPTY (1 << 31) +#define SFUART_TXCTRL 0x08 +#define SFUART_TXCTRL_ENABLE 0x01 +#define SFUART_TXCTRL_NSTOP 0x02 +#define SFUART_TXCTRL_TXCNT 0xb0000 +#define SFUART_TXCTRL_TXCNT_SHIFT 16 +#define SFUART_RXCTRL 0x0c +#define SFUART_RXCTRL_ENABLE 0x01 +#define SFUART_RXCTRL_RXCNT 0xb0000 +#define SFUART_RXCTRL_RXCNT_SHIFT 16 +#define SFUART_IRQ_ENABLE 0x10 +#define SFUART_IRQ_ENABLE_TXWM 0x01 +#define SFUART_IRQ_ENABLE_RXWM 0x02 +#define SFUART_IRQ_PENDING 0x14 +#define SFUART_IRQ_PENDING_TXWM 0x01 +#define SFUART_IRQ_PENDING_RXQM 0x02 +#define SFUART_DIV 0x18 +#define SFUART_REGS_SIZE 0x1c + +#define SFUART_RX_FIFO_DEPTH 8 +#define SFUART_TX_FIFO_DEPTH 8 + +struct sfuart_softc { + struct uart_softc uart_softc; + clk_t clk; +}; + +static int +sfuart_probe(struct uart_bas *bas) +{ + + bas->regiowidth = 4; + + return (0); +} + +static void +sfuart_init(struct uart_bas *bas, int baudrate, int databits, int stopbits, + int parity) +{ + uint32_t reg; + + uart_setreg(bas, SFUART_IRQ_ENABLE, 0); + + /* Enable RX and configure the watermark so that we get an interrupt + * when a single character arrives (if interrupts are enabled). */ + reg = SFUART_RXCTRL_ENABLE; + reg |= (0 << SFUART_RXCTRL_RXCNT_SHIFT); + uart_setreg(bas, SFUART_RXCTRL, reg); + + /* Enable TX and configure the watermark so that we get an interrupt + * when there's room for one more character in the TX fifo (if + * interrupts are enabled). */ + reg = SFUART_TXCTRL_ENABLE; + reg |= (1 << SFUART_TXCTRL_TXCNT_SHIFT); + if (stopbits == 2) + reg |= SFUART_TXCTRL_NSTOP; + uart_setreg(bas, SFUART_TXCTRL, reg); + + /* Don't touch DIV. Assume that's set correctly until we can + * reconfigure. */ +} + +static void +sfuart_putc(struct uart_bas *bas, int c) +{ + + while ((uart_getreg(bas, SFUART_TXDATA) & SFUART_TXDATA_FULL) + != 0) + cpu_spinwait(); + + uart_setreg(bas, SFUART_TXDATA, c); +} + +static int +sfuart_rxready(struct uart_bas *bas) +{ + + return ((uart_getreg(bas, SFUART_RXDATA) & + SFUART_RXDATA_EMPTY) == 0); +} + +static int +sfuart_getc(struct uart_bas *bas, struct mtx *hwmtx) +{ + int c; + + uart_lock(hwmtx); + + while (((c = uart_getreg(bas, SFUART_RXDATA)) & + SFUART_RXDATA_EMPTY) != 0) { + uart_unlock(hwmtx); + DELAY(4); + uart_lock(hwmtx); + } + + uart_unlock(hwmtx); + + return (c & 0xff); +} + +static int +sfuart_bus_probe(struct uart_softc *sc) +{ + int error; + + error = sfuart_probe(&sc->sc_bas); + if (error) + return (error); + + sc->sc_rxfifosz = SFUART_RX_FIFO_DEPTH; + sc->sc_txfifosz = SFUART_TX_FIFO_DEPTH; + sc->sc_hwiflow = 0; + sc->sc_hwoflow = 0; + + device_set_desc(sc->sc_dev, "SiFive UART"); + + return (0); +} + +static int +sfuart_bus_attach(struct uart_softc *sc) +{ + struct uart_bas *bas; + struct sfuart_softc *sfsc; + uint64_t freq; + uint32_t reg; + int error; + + sfsc = (struct sfuart_softc *)sc; + bas = &sc->sc_bas; + + error = clk_get_by_ofw_index(sc->sc_dev, 0, 0, &sfsc->clk); + if (error) { + device_printf(sc->sc_dev, "couldn't allocate clock\n"); + return (ENXIO); + } + + error = clk_enable(sfsc->clk); + if (error) { + device_printf(sc->sc_dev, "couldn't enable clock\n"); + return (ENXIO); + } + + error = clk_get_freq(sfsc->clk, &freq); + if (error || freq == 0) { + clk_disable(sfsc->clk); + device_printf(sc->sc_dev, "couldn't get clock frequency\n"); + return (ENXIO); + } + + bas->rclk = freq; + + /* Enable RX/RX */ + reg = SFUART_RXCTRL_ENABLE; + reg |= (0 << SFUART_RXCTRL_RXCNT_SHIFT); + uart_setreg(bas, SFUART_RXCTRL, reg); + + reg = SFUART_TXCTRL_ENABLE; + reg |= (1 << SFUART_TXCTRL_TXCNT_SHIFT); + uart_setreg(bas, SFUART_TXCTRL, reg); + + /* Enable RX interrupt */ + uart_setreg(bas, SFUART_IRQ_ENABLE, SFUART_IRQ_ENABLE_RXWM); + + return (0); +} + +static int +sfuart_bus_detach(struct uart_softc *sc) +{ + struct sfuart_softc *sfsc; + struct uart_bas *bas; + + sfsc = (struct sfuart_softc *)sc; + bas = &sc->sc_bas; + + /* Disable RX/TX */ + uart_setreg(bas, SFUART_RXCTRL, 0); + uart_setreg(bas, SFUART_TXCTRL, 0); + + /* Disable interrupts */ + uart_setreg(bas, SFUART_IRQ_ENABLE, 0); + + clk_disable(sfsc->clk); + + return (0); +} + +static int +sfuart_bus_flush(struct uart_softc *sc, int what) +{ + struct uart_bas *bas; + uint32_t reg; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + if (what & UART_FLUSH_TRANSMITTER) { + do { + reg = uart_getreg(bas, SFUART_TXDATA); + } while ((reg & SFUART_TXDATA_FULL) != 0); + } + + if (what & UART_FLUSH_RECEIVER) { + do { + reg = uart_getreg(bas, SFUART_RXDATA); + } while ((reg & SFUART_RXDATA_EMPTY) == 0); + } + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +#define SIGCHG(c, i, s, d) \ + do { \ + if (c) \ + i |= ((i) & (s)) ? (s) : (s) | (d); \ + else \ + i = ((i) & (s)) ? (i) & ~(s) | (d) : (i); \ + } while (0) + +static int +sfuart_bus_getsig(struct uart_softc *sc) +{ + uint32_t new, old, sig; + + do { + old = sc->sc_hwsig; + sig = old; + SIGCHG(1, sig, SER_DSR, SER_DDSR); + SIGCHG(1, sig, SER_DCD, SER_DDCD); + SIGCHG(1, sig, SER_CTS, SER_DCTS); + new = sig & ~SER_MASK_DELTA; + } while (!atomic_cmpset_32(&sc->sc_hwsig, old, new)); + + return (sig); +} + +static int +sfuart_bus_setsig(struct uart_softc *sc, int sig) +{ + uint32_t new, old; + + do { + old = sc->sc_hwsig; + new = old; + if (sig & SER_DDTR) { + SIGCHG(sig & SER_DTR, new, SER_DTR, SER_DDTR); + } + if (sig & SER_DRTS) { + SIGCHG(sig & SER_RTS, new, SER_RTS, SER_DRTS); + } + } while (!atomic_cmpset_32(&sc->sc_hwsig, old, new)); + + return (0); +} + +static int +sfuart_bus_ioctl(struct uart_softc *sc, int request, intptr_t data) +{ + struct uart_bas *bas; + uint32_t reg; + int error; + + bas = &sc->sc_bas; + + uart_lock(sc->sc_hwmtx); + + switch (request) { + case UART_IOCTL_BAUD: + reg = uart_getreg(bas, SFUART_DIV); + if (reg == 0) { + /* Possible if the divisor hasn't been set up yet. */ + error = ENXIO; + break; + } + *(int*)data = bas->rclk / (reg + 1); + error = 0; + break; + default: + error = EINVAL; + break; + } + + uart_unlock(sc->sc_hwmtx); + + return (error); +} + +static int +sfuart_bus_ipend(struct uart_softc *sc) +{ + struct uart_bas *bas; + int ipend; + uint32_t reg, ie; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + ipend = 0; + reg = uart_getreg(bas, SFUART_IRQ_PENDING); + ie = uart_getreg(bas, SFUART_IRQ_ENABLE); + + if ((reg & SFUART_IRQ_PENDING_TXWM) != 0 && + (ie & SFUART_IRQ_ENABLE_TXWM) != 0) { + ipend |= SER_INT_TXIDLE; + + /* Disable TX interrupt */ + ie &= ~(SFUART_IRQ_ENABLE_TXWM); + uart_setreg(bas, SFUART_IRQ_ENABLE, ie); + } + + if ((reg & SFUART_IRQ_PENDING_RXQM) != 0) + ipend |= SER_INT_RXREADY; + + uart_unlock(sc->sc_hwmtx); + + return (ipend); +} + +static int +sfuart_bus_param(struct uart_softc *sc, int baudrate, int databits, + int stopbits, int parity) +{ + struct uart_bas *bas; + uint32_t reg; + + bas = &sc->sc_bas; + + if (databits != 8) + return (EINVAL); + + if (parity != UART_PARITY_NONE) + return (EINVAL); + + uart_lock(sc->sc_hwmtx); + + reg = uart_getreg(bas, SFUART_TXCTRL); + if (stopbits == 2) { + reg |= SFUART_TXCTRL_NSTOP; + } else if (stopbits == 1) { + reg &= ~SFUART_TXCTRL_NSTOP; + } else { + uart_unlock(sc->sc_hwmtx); + return (EINVAL); + } + + if (baudrate > 0 && bas->rclk != 0) { + reg = (bas->rclk / baudrate) - 1; + uart_setreg(bas, SFUART_DIV, reg); + } + + uart_unlock(sc->sc_hwmtx); + return (0); +} + +static int +sfuart_bus_receive(struct uart_softc *sc) +{ + struct uart_bas *bas; + uint32_t reg; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + reg = uart_getreg(bas, SFUART_RXDATA); + while ((reg & SFUART_RXDATA_EMPTY) == 0) { + if (uart_rx_full(sc)) { + sc->sc_rxbuf[sc->sc_rxput] = UART_STAT_OVERRUN; + break; + } + + uart_rx_put(sc, reg & 0xff); + + reg = uart_getreg(bas, SFUART_RXDATA); + } + + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static int +sfuart_bus_transmit(struct uart_softc *sc) +{ + struct uart_bas *bas; + int i; + uint32_t reg; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + reg = uart_getreg(bas, SFUART_IRQ_ENABLE); + reg |= SFUART_IRQ_ENABLE_TXWM; + uart_setreg(bas, SFUART_IRQ_ENABLE, reg); + + for (i = 0; i < sc->sc_txdatasz; i++) + sfuart_putc(bas, sc->sc_txbuf[i]); + + sc->sc_txbusy = 1; + + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static void +sfuart_bus_grab(struct uart_softc *sc) +{ + struct uart_bas *bas; + uint32_t reg; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + reg = uart_getreg(bas, SFUART_IRQ_ENABLE); + reg &= ~(SFUART_IRQ_ENABLE_TXWM | SFUART_IRQ_PENDING_RXQM); + uart_setreg(bas, SFUART_IRQ_ENABLE, reg); + + uart_unlock(sc->sc_hwmtx); +} + +static void +sfuart_bus_ungrab(struct uart_softc *sc) +{ + struct uart_bas *bas; + uint32_t reg; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + reg = uart_getreg(bas, SFUART_IRQ_ENABLE); + reg |= SFUART_IRQ_ENABLE_TXWM | SFUART_IRQ_PENDING_RXQM; + uart_setreg(bas, SFUART_IRQ_ENABLE, reg); + + uart_unlock(sc->sc_hwmtx); +} + +static kobj_method_t sfuart_methods[] = { + KOBJMETHOD(uart_probe, sfuart_bus_probe), + KOBJMETHOD(uart_attach, sfuart_bus_attach), + KOBJMETHOD(uart_detach, sfuart_bus_detach), + KOBJMETHOD(uart_flush, sfuart_bus_flush), + KOBJMETHOD(uart_getsig, sfuart_bus_getsig), + KOBJMETHOD(uart_setsig, sfuart_bus_setsig), + KOBJMETHOD(uart_ioctl, sfuart_bus_ioctl), + KOBJMETHOD(uart_ipend, sfuart_bus_ipend), + KOBJMETHOD(uart_param, sfuart_bus_param), + KOBJMETHOD(uart_receive, sfuart_bus_receive), + KOBJMETHOD(uart_transmit, sfuart_bus_transmit), + KOBJMETHOD(uart_grab, sfuart_bus_grab), + KOBJMETHOD(uart_ungrab, sfuart_bus_ungrab), + KOBJMETHOD_END +}; + +static struct uart_ops sfuart_ops = { + .probe = sfuart_probe, + .init = sfuart_init, + .term = NULL, + .putc = sfuart_putc, + .rxready = sfuart_rxready, + .getc = sfuart_getc, +}; + +struct uart_class sfuart_class = { + "sifiveuart", + sfuart_methods, + sizeof(struct sfuart_softc), + .uc_ops = &sfuart_ops, + .uc_range = SFUART_REGS_SIZE, + .uc_rclk = 0, + .uc_rshift = 0 +}; + +static struct ofw_compat_data compat_data[] = { + { "sifive,uart0", (uintptr_t)&sfuart_class }, + { NULL, (uintptr_t)NULL } +}; + +UART_FDT_CLASS_AND_DEVICE(compat_data);