diff options
author | gonzo <gonzo@FreeBSD.org> | 2012-08-30 20:31:53 +0000 |
---|---|---|
committer | gonzo <gonzo@FreeBSD.org> | 2012-08-30 20:31:53 +0000 |
commit | da02f17baac746980ae553303880f79449d0a766 (patch) | |
tree | 113cfc5a71598be83833344242de9475f7ea0b43 | |
parent | 353407bd01046f0b9832399ddf2d649956ad5bff (diff) | |
download | FreeBSD-src-da02f17baac746980ae553303880f79449d0a766.zip FreeBSD-src-da02f17baac746980ae553303880f79449d0a766.tar.gz |
Add PrimeCell UART (PL011) driver
Obtained from: Semihalf
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/dev/uart/uart.h | 1 | ||||
-rw-r--r-- | sys/dev/uart/uart_bus_fdt.c | 4 | ||||
-rw-r--r-- | sys/dev/uart/uart_dev_pl011.c | 436 |
4 files changed, 442 insertions, 0 deletions
diff --git a/sys/conf/files b/sys/conf/files index 957402c..3f4025b 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2065,6 +2065,7 @@ dev/uart/uart_bus_scc.c optional uart scc dev/uart/uart_core.c optional uart dev/uart/uart_dbg.c optional uart gdb dev/uart/uart_dev_ns8250.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_sab82532.c optional uart uart_sab82532 dev/uart/uart_dev_sab82532.c optional uart scc diff --git a/sys/dev/uart/uart.h b/sys/dev/uart/uart.h index 288af0ed..7647188 100644 --- a/sys/dev/uart/uart.h +++ b/sys/dev/uart/uart.h @@ -70,6 +70,7 @@ extern struct uart_class uart_sab82532_class __attribute__((weak)); extern struct uart_class uart_sbbc_class __attribute__((weak)); extern struct uart_class uart_z8530_class __attribute__((weak)); extern struct uart_class uart_lpc_class __attribute__((weak)); +extern struct uart_class uart_pl011_class __attribute__((weak)); #ifdef PC98 struct uart_class *uart_pc98_getdev(u_long port); diff --git a/sys/dev/uart/uart_bus_fdt.c b/sys/dev/uart/uart_bus_fdt.c index 6517007..8dbbb25 100644 --- a/sys/dev/uart/uart_bus_fdt.c +++ b/sys/dev/uart/uart_bus_fdt.c @@ -105,6 +105,8 @@ uart_fdt_probe(device_t dev) sc->sc_class = &uart_ns8250_class; else if (ofw_bus_is_compatible(dev, "lpc,uart")) sc->sc_class = &uart_lpc_class; + else if (ofw_bus_is_compatible(dev, "arm,pl011")) + sc->sc_class = &uart_pl011_class; else return (ENXIO); @@ -188,6 +190,8 @@ uart_cpu_getdev(int devtype, struct uart_devinfo *di) class = &uart_lpc_class; if (fdt_is_compatible(node, "ns16550")) class = &uart_ns8250_class; + if (fdt_is_compatible(node, "arm,pl011")) + class = &uart_pl011_class; di->bas.chan = 0; di->bas.regshft = (u_int)shift; diff --git a/sys/dev/uart/uart_dev_pl011.c b/sys/dev/uart/uart_dev_pl011.c new file mode 100644 index 0000000..e7e0a15 --- /dev/null +++ b/sys/dev/uart/uart_dev_pl011.c @@ -0,0 +1,436 @@ +/*- + * Copyright (c) 2012 Semihalf. + * 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 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/kernel.h> +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/uart/uart.h> +#include <dev/uart/uart_cpu.h> +#include <dev/uart/uart_bus.h> +#include "uart_if.h" + +#include <sys/kdb.h> + +/* PL011 UART registers and masks*/ +#define UART_DR 0x00 /* Data register */ +#define DR_FE (1 << 8) /* Framing error */ +#define DR_PE (1 << 9) /* Parity error */ +#define DR_BE (1 << 10) /* Break error */ +#define DR_OE (1 << 11) /* Overrun error */ + +#define UART_FR 0x06 /* Flag register */ +#define FR_RXFF (1 << 6) /* Receive FIFO/reg full */ +#define FR_TXFE (1 << 7) /* Transmit FIFO/reg empty */ + +#define UART_IBRD 0x09 /* Integer baud rate register */ +#define IBRD_BDIVINT 0xffff /* Significant part of int. divisor value */ + +#define UART_FBRD 0x0a /* Fractional baud rate register */ +#define FBRD_BDIVFRAC 0x3f /* Significant part of frac. divisor value */ + +#define UART_LCR_H 0x0b /* Line control register */ +#define LCR_H_WLEN8 (0x3 << 5) +#define LCR_H_WLEN7 (0x2 << 5) +#define LCR_H_WLEN6 (0x1 << 5) +#define LCR_H_FEN (1 << 4) /* FIFO mode enable */ +#define LCR_H_STP2 (1 << 3) /* 2 stop frames at the end */ +#define LCR_H_EPS (1 << 2) /* Even parity select */ +#define LCR_H_PEN (1 << 1) /* Parity enable */ + +#define UART_CR 0x0c /* Control register */ +#define CR_RXE (1 << 9) /* Receive enable */ +#define CR_TXE (1 << 8) /* Transmit enable */ +#define CR_UARTEN (1 << 0) /* UART enable */ + +#define UART_IMSC 0x0e /* Interrupt mask set/clear register */ +#define IMSC_MASK_ALL 0x7ff /* Mask all interrupts */ + +#define UART_RIS 0x0f /* Raw interrupt status register */ +#define UART_RXREADY (1 << 4) /* RX buffer full */ +#define UART_TXEMPTY (1 << 5) /* TX buffer empty */ +#define RIS_FE (1 << 7) /* Framing error interrupt status */ +#define RIS_PE (1 << 8) /* Parity error interrupt status */ +#define RIS_BE (1 << 9) /* Break error interrupt status */ +#define RIS_OE (1 << 10) /* Overrun interrupt status */ + +#define UART_MIS 0x10 /* Masked interrupt status register */ +#define UART_ICR 0x11 /* Interrupt clear register */ + +/* + * FIXME: actual register size is SoC-dependent, we need to handle it + */ +#define __uart_getreg(bas, reg) \ + bus_space_read_4((bas)->bst, (bas)->bsh, uart_regofs(bas, reg)) +#define __uart_setreg(bas, reg, value) \ + bus_space_write_4((bas)->bst, (bas)->bsh, uart_regofs(bas, reg), value) + +/* + * Low-level UART interface. + */ +static int uart_pl011_probe(struct uart_bas *bas); +static void uart_pl011_init(struct uart_bas *bas, int, int, int, int); +static void uart_pl011_term(struct uart_bas *bas); +static void uart_pl011_putc(struct uart_bas *bas, int); +static int uart_pl011_rxready(struct uart_bas *bas); +static int uart_pl011_getc(struct uart_bas *bas, struct mtx *); + +static struct uart_ops uart_pl011_ops = { + .probe = uart_pl011_probe, + .init = uart_pl011_init, + .term = uart_pl011_term, + .putc = uart_pl011_putc, + .rxready = uart_pl011_rxready, + .getc = uart_pl011_getc, +}; + +static int +uart_pl011_probe(struct uart_bas *bas) +{ + + return (0); +} + +static void +uart_pl011_init(struct uart_bas *bas, int baudrate, int databits, int stopbits, + int parity) +{ + uint32_t ctrl, line; + uint32_t baud; + + /* Mask all interrupts */ + __uart_setreg(bas, UART_IMSC, __uart_getreg(bas, UART_IMSC) & + ~IMSC_MASK_ALL); + + /* + * Zero all settings to make sure + * UART is disabled and not configured + */ + ctrl = line = 0x0; + __uart_setreg(bas, UART_CR, ctrl); + + /* As we know UART is disabled we may setup the line */ + switch (databits) { + case 7: + line |= LCR_H_WLEN7; + break; + case 6: + line |= LCR_H_WLEN6; + break; + case 8: + default: + line |= LCR_H_WLEN8; + break; + } + + /* TODO: Calculate divisors */ + baud = (0x1 << 16) | 0x28; + + if (stopbits == 2) + line |= LCR_H_STP2; + else + line &= ~LCR_H_STP2; + + if (parity) + line |= LCR_H_PEN; + else + line &= ~LCR_H_PEN; + + /* Configure the rest */ + line &= ~LCR_H_FEN; + ctrl |= (CR_RXE | CR_TXE | CR_UARTEN); + + __uart_setreg(bas, UART_IBRD, ((uint32_t)(baud >> 16)) & IBRD_BDIVINT); + __uart_setreg(bas, UART_FBRD, (uint32_t)(baud) & FBRD_BDIVFRAC); + + /* Add config. to line before reenabling UART */ + __uart_setreg(bas, UART_LCR_H, (__uart_getreg(bas, UART_LCR_H) & + ~0xff) | line); + + __uart_setreg(bas, UART_CR, ctrl); +} + +static void +uart_pl011_term(struct uart_bas *bas) +{ +} + +static void +uart_pl011_putc(struct uart_bas *bas, int c) +{ + + while (!(__uart_getreg(bas, UART_FR) & FR_TXFE)) + ; + __uart_setreg(bas, UART_DR, c & 0xff); +} + +static int +uart_pl011_rxready(struct uart_bas *bas) +{ + + return (__uart_getreg(bas, UART_FR) & FR_RXFF); +} + +static int +uart_pl011_getc(struct uart_bas *bas, struct mtx *hwmtx) +{ + int c; + + while (!uart_pl011_rxready(bas)) + ; + c = __uart_getreg(bas, UART_DR) & 0xff; + + return (c); +} + +/* + * High-level UART interface. + */ +struct uart_pl011_softc { + struct uart_softc base; + uint8_t fcr; + uint8_t ier; + uint8_t mcr; + + uint8_t ier_mask; + uint8_t ier_rxbits; +}; + +static int uart_pl011_bus_attach(struct uart_softc *); +static int uart_pl011_bus_detach(struct uart_softc *); +static int uart_pl011_bus_flush(struct uart_softc *, int); +static int uart_pl011_bus_getsig(struct uart_softc *); +static int uart_pl011_bus_ioctl(struct uart_softc *, int, intptr_t); +static int uart_pl011_bus_ipend(struct uart_softc *); +static int uart_pl011_bus_param(struct uart_softc *, int, int, int, int); +static int uart_pl011_bus_probe(struct uart_softc *); +static int uart_pl011_bus_receive(struct uart_softc *); +static int uart_pl011_bus_setsig(struct uart_softc *, int); +static int uart_pl011_bus_transmit(struct uart_softc *); + +static kobj_method_t uart_pl011_methods[] = { + KOBJMETHOD(uart_attach, uart_pl011_bus_attach), + KOBJMETHOD(uart_detach, uart_pl011_bus_detach), + KOBJMETHOD(uart_flush, uart_pl011_bus_flush), + KOBJMETHOD(uart_getsig, uart_pl011_bus_getsig), + KOBJMETHOD(uart_ioctl, uart_pl011_bus_ioctl), + KOBJMETHOD(uart_ipend, uart_pl011_bus_ipend), + KOBJMETHOD(uart_param, uart_pl011_bus_param), + KOBJMETHOD(uart_probe, uart_pl011_bus_probe), + KOBJMETHOD(uart_receive, uart_pl011_bus_receive), + KOBJMETHOD(uart_setsig, uart_pl011_bus_setsig), + KOBJMETHOD(uart_transmit, uart_pl011_bus_transmit), + { 0, 0 } +}; + +struct uart_class uart_pl011_class = { + "uart_pl011", + uart_pl011_methods, + sizeof(struct uart_pl011_softc), + .uc_ops = &uart_pl011_ops, + .uc_range = 0x48, + .uc_rclk = 0 +}; + +static int +uart_pl011_bus_attach(struct uart_softc *sc) +{ + struct uart_bas *bas; + + bas = &sc->sc_bas; + /* Enable RX & TX interrupts */ + __uart_setreg(bas, UART_IMSC, (UART_RXREADY | UART_TXEMPTY)); + /* Clear RX & TX interrupts */ + __uart_setreg(bas, UART_ICR, IMSC_MASK_ALL); + + sc->sc_rxfifosz = 1; + sc->sc_txfifosz = 1; + + return (0); +} + +static int +uart_pl011_bus_detach(struct uart_softc *sc) +{ + + return (0); +} + +static int +uart_pl011_bus_flush(struct uart_softc *sc, int what) +{ + + return (0); +} + +static int +uart_pl011_bus_getsig(struct uart_softc *sc) +{ + + return (0); +} + +static int +uart_pl011_bus_ioctl(struct uart_softc *sc, int request, intptr_t data) +{ + struct uart_bas *bas; + int error; + + bas = &sc->sc_bas; + error = 0; + uart_lock(sc->sc_hwmtx); + switch (request) { + case UART_IOCTL_BREAK: + break; + case UART_IOCTL_BAUD: + *(int*)data = 115200; + break; + default: + error = EINVAL; + break; + } + uart_unlock(sc->sc_hwmtx); + + return (error); +} + +static int +uart_pl011_bus_ipend(struct uart_softc *sc) +{ + struct uart_bas *bas; + int ipend; + uint32_t ints; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + ints = __uart_getreg(bas, UART_MIS); + ipend = 0; + + if (ints & UART_RXREADY) + ipend |= SER_INT_RXREADY; + if (ints & RIS_BE) + ipend |= SER_INT_BREAK; + if (ints & RIS_OE) + ipend |= SER_INT_OVERRUN; + if (ints & UART_TXEMPTY) { + if (sc->sc_txbusy) + ipend |= SER_INT_TXIDLE; + + __uart_setreg(bas, UART_IMSC, UART_RXREADY); + } + + uart_unlock(sc->sc_hwmtx); + + return (ipend); +} + +static int +uart_pl011_bus_param(struct uart_softc *sc, int baudrate, int databits, + int stopbits, int parity) +{ + + uart_lock(sc->sc_hwmtx); + uart_pl011_init(&sc->sc_bas, baudrate, databits, stopbits, parity); + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static int +uart_pl011_bus_probe(struct uart_softc *sc) +{ + + device_set_desc(sc->sc_dev, "PrimeCell UART (PL011)"); + + return (0); +} + +static int +uart_pl011_bus_receive(struct uart_softc *sc) +{ + struct uart_bas *bas; + int rx; + uint32_t ints, xc; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + ints = __uart_getreg(bas, UART_MIS); + while (ints & UART_RXREADY) { + if (uart_rx_full(sc)) { + sc->sc_rxbuf[sc->sc_rxput] = UART_STAT_OVERRUN; + break; + } + xc = __uart_getreg(bas, UART_DR); + rx = xc & 0xff; + + if (xc & DR_FE) + rx |= UART_STAT_FRAMERR; + if (xc & DR_PE) + rx |= UART_STAT_PARERR; + + __uart_setreg(bas, UART_ICR, UART_RXREADY); + + uart_rx_put(sc, rx); + ints = __uart_getreg(bas, UART_MIS); + } + + uart_unlock(sc->sc_hwmtx); + + return (0); +} + +static int +uart_pl011_bus_setsig(struct uart_softc *sc, int sig) +{ + + return (0); +} + +static int +uart_pl011_bus_transmit(struct uart_softc *sc) +{ + struct uart_bas *bas; + int i; + + bas = &sc->sc_bas; + uart_lock(sc->sc_hwmtx); + + for (i = 0; i < sc->sc_txdatasz; i++) { + __uart_setreg(bas, UART_DR, sc->sc_txbuf[i]); + uart_barrier(bas); + } + sc->sc_txbusy = 1; + __uart_setreg(bas, UART_IMSC, (UART_RXREADY | UART_TXEMPTY)); + uart_unlock(sc->sc_hwmtx); + + return (0); +} |