svn commit: r356222 - head/sys/riscv/sifive
Kristof Provost
kp at FreeBSD.org
Tue Dec 31 10:54:13 UTC 2019
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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+#include <machine/cpu.h>
+
+#include <dev/extres/clk/clk.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/openfirm.h>
+
+#include <dev/uart/uart.h>
+#include <dev/uart/uart_bus.h>
+#include <dev/uart/uart_cpu.h>
+#include <dev/uart/uart_cpu_fdt.h>
+
+#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);
More information about the svn-src-head
mailing list