freebsd-dev/sys/riscv/sifive/sifive_uart.c
Jessica Clarke a1f9cdb1ab sifive_uart: Fix input character dropping in ddb and at a mountroot prompt
These use the raw console interface and poll. Unfortunately, the SiFive
UART puts the FIFO empty bit inside the FIFO data register, which means
that the act of checking whether a character is available also dequeues
any character from the FIFO, requiring the user to press each key twice.
However, since we configure the watermark to be 0 and, when the UART has
been grabbed for the console, we have interrupts off, we can abuse the
interrupt pending register to act as a substitute for the FIFO empty
bit.

This perhaps suggests that the console interface should move from having
rxready and getc to having getc_nonblock and getc (or make getc take a
bool), as all the places that call rxready do so to avoid blocking on
getc when there is no character available.

Reviewed by:	kp, philip
MFC after:	1 week
Differential Revision:	https://reviews.freebsd.org/D31025
2021-07-21 02:51:25 +01:00

550 lines
12 KiB
C

/*-
* 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 0x70000
#define SFUART_TXCTRL_TXCNT_SHIFT 16
#define SFUART_RXCTRL 0x0c
#define SFUART_RXCTRL_ENABLE 0x01
#define SFUART_RXCTRL_RXCNT 0x70000
#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)
{
/*
* Unfortunately the FIFO empty flag is in the FIFO data register so
* reading it would dequeue the character. Instead, rely on the fact
* we've configured the watermark to be 0 and that interrupts are off
* when using the low-level console function, and read the interrupt
* pending state instead.
*/
return ((uart_getreg(bas, SFUART_IRQ_PENDING) &
SFUART_IRQ_PENDING_RXQM) != 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);