blob: 280196587eb38374e97918547a469a837bbffe32 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <autoconf.h>
#include <platsupport/gen_config.h>
#include <stdlib.h>
#include <platsupport/serial.h>
#include <platsupport/plat/serial.h>
#include <string.h>
#include "../../../chardev.h"
#ifndef UART_REF_CLK
#error "UART_REF_CLK undefined"
#endif
#define UART_SR1_RRDY BIT( 9)
#define UART_SR1_TRDY BIT(13)
/* CR1 */
#define UART_CR1_UARTEN BIT( 0)
#define UART_CR1_RRDYEN BIT( 9)
/* CR2 */
#define UART_CR2_SRST BIT( 0)
#define UART_CR2_RXEN BIT( 1)
#define UART_CR2_TXEN BIT( 2)
#define UART_CR2_ATEN BIT( 3)
#define UART_CR2_RTSEN BIT( 4)
#define UART_CR2_WS BIT( 5)
#define UART_CR2_STPB BIT( 6)
#define UART_CR2_PROE BIT( 7)
#define UART_CR2_PREN BIT( 8)
#define UART_CR2_RTEC BIT( 9)
#define UART_CR2_ESCEN BIT(11)
#define UART_CR2_CTS BIT(12)
#define UART_CR2_CTSC BIT(13)
#define UART_CR2_IRTS BIT(14)
#define UART_CR2_ESCI BIT(15)
/* CR3 */
#define UART_CR3_RXDMUXDEL BIT( 2)
/* FCR */
#define UART_FCR_RFDIV(x) ((x) * BIT(7))
#define UART_FCR_RFDIV_MASK UART_FCR_RFDIV(0x7)
#define UART_FCR_RXTL(x) ((x) * BIT(0))
#define UART_FCR_RXTL_MASK UART_FCR_RXTL(0x1F)
/* SR2 */
#define UART_SR2_RXFIFO_RDR BIT(0)
#define UART_SR2_TXFIFO_EMPTY BIT(14)
/* RXD */
#define UART_URXD_READY_MASK BIT(15)
#define UART_BYTE_MASK 0xFF
struct imx_uart_regs {
uint32_t rxd; /* 0x000 Receiver Register */
uint32_t res0[15];
uint32_t txd; /* 0x040 Transmitter Register */
uint32_t res1[15];
uint32_t cr1; /* 0x080 Control Register 1 */
uint32_t cr2; /* 0x084 Control Register 2 */
uint32_t cr3; /* 0x088 Control Register 3 */
uint32_t cr4; /* 0x08C Control Register 4 */
uint32_t fcr; /* 0x090 FIFO Control Register */
uint32_t sr1; /* 0x094 Status Register 1 */
uint32_t sr2; /* 0x098 Status Register 2 */
uint32_t esc; /* 0x09c Escape Character Register */
uint32_t tim; /* 0x0a0 Escape Timer Register */
uint32_t bir; /* 0x0a4 BRM Incremental Register */
uint32_t bmr; /* 0x0a8 BRM Modulator Register */
uint32_t brc; /* 0x0ac Baud Rate Counter Register */
uint32_t onems; /* 0x0b0 One Millisecond Register */
uint32_t ts; /* 0x0b4 Test Register */
};
typedef volatile struct imx_uart_regs imx_uart_regs_t;
static inline imx_uart_regs_t *imx_uart_get_priv(
ps_chardevice_t *d)
{
return (imx_uart_regs_t *)d->vaddr;
}
int uart_getchar(
ps_chardevice_t *d)
{
imx_uart_regs_t *regs = imx_uart_get_priv(d);
uint32_t reg = 0;
int c = -1;
if (regs->sr2 & UART_SR2_RXFIFO_RDR) {
reg = regs->rxd;
if (reg & UART_URXD_READY_MASK) {
c = reg & UART_BYTE_MASK;
}
}
return c;
}
static int internal_is_tx_fifo_busy(
imx_uart_regs_t *regs)
{
/* check the TXFE (transmit buffer FIFO empty) flag, which is cleared
* automatically when data is written to the TxFIFO. Even though the flag
* is set, the actual data transmission via the UART's 32 byte FIFO buffer
* might still be in progress.
*/
return (0 == (regs->sr2 & UART_SR2_TXFIFO_EMPTY));
}
int uart_putchar(
ps_chardevice_t *d,
int c)
{
imx_uart_regs_t *regs = imx_uart_get_priv(d);
if (internal_is_tx_fifo_busy(regs)) {
return -1;
}
if (c == '\n' && (d->flags & SERIAL_AUTO_CR)) {
/* write CR first */
regs->txd = '\r';
/* if we transform a '\n' (LF) into '\r\n' (CR+LF) this shall become an
* atom, ie we don't want CR to be sent and then fail at sending LF
* because the TX FIFO is full. Basically there are two options:
* - check if the FIFO can hold CR+LF and either send both or none
* - send CR, then block until the FIFO has space and send LF.
* Assuming that if SERIAL_AUTO_CR is set, it's likely this is a serial
* console for logging, so blocking seems acceptable in this special
* case. The IMX6's TX FIFO size is 32 byte and TXFIFO_EMPTY is cleared
* automatically as soon as data is written from regs->txd into the
* FIFO. Thus the worst case blocking is roughly the time it takes to
* send 1 byte to have room in the FIFO again. At 115200 baud with 8N1
* this takes 10 bit-times, which is 10/115200 = 86,8 usec.
*/
while (internal_is_tx_fifo_busy(regs)) {
/* busy loop */
}
}
regs->txd = c;
return c;
}
static void uart_handle_irq(ps_chardevice_t *d UNUSED)
{
/* TODO */
}
/*
* BaudRate = RefFreq / (16 * (BMR + 1)/(BIR + 1) )
* BMR and BIR are 16 bit
*/
static void imx_uart_set_baud(
ps_chardevice_t *d,
long bps)
{
imx_uart_regs_t *regs = imx_uart_get_priv(d);
uint32_t bmr, bir, fcr;
fcr = regs->fcr;
fcr &= ~UART_FCR_RFDIV_MASK;
fcr |= UART_FCR_RFDIV(4);
bir = 0xf;
bmr = UART_REF_CLK / bps - 1;
regs->bir = bir;
regs->bmr = bmr;
regs->fcr = fcr;
}
int serial_configure(
ps_chardevice_t *d,
long bps,
int char_size,
enum serial_parity parity,
int stop_bits)
{
imx_uart_regs_t *regs = imx_uart_get_priv(d);
uint32_t cr2;
/* Character size */
cr2 = regs->cr2;
if (char_size == 8) {
cr2 |= UART_CR2_WS;
} else if (char_size == 7) {
cr2 &= ~UART_CR2_WS;
} else {
return -1;
}
/* Stop bits */
if (stop_bits == 2) {
cr2 |= UART_CR2_STPB;
} else if (stop_bits == 1) {
cr2 &= ~UART_CR2_STPB;
} else {
return -1;
}
/* Parity */
if (parity == PARITY_NONE) {
cr2 &= ~UART_CR2_PREN;
} else if (parity == PARITY_ODD) {
/* ODD */
cr2 |= UART_CR2_PREN;
cr2 |= UART_CR2_PROE;
} else if (parity == PARITY_EVEN) {
/* Even */
cr2 |= UART_CR2_PREN;
cr2 &= ~UART_CR2_PROE;
} else {
return -1;
}
/* Apply the changes */
regs->cr2 = cr2;
/* Now set the board rate */
imx_uart_set_baud(d, bps);
return 0;
}
int uart_init(
const struct dev_defn *defn,
const ps_io_ops_t *ops,
ps_chardevice_t *dev)
{
/* Attempt to map the virtual address, assure this works */
void *vaddr = chardev_map(defn, ops);
if (vaddr == NULL) {
return -1;
}
memset(dev, 0, sizeof(*dev));
/* Set up all the device properties. */
dev->id = defn->id;
dev->vaddr = (void *)vaddr;
dev->read = &uart_read;
dev->write = &uart_write;
dev->handle_irq = &uart_handle_irq;
dev->irqs = defn->irqs;
dev->ioops = *ops;
dev->flags = SERIAL_AUTO_CR;
imx_uart_regs_t *regs = imx_uart_get_priv(dev);
/* Software reset */
regs->cr2 &= ~UART_CR2_SRST;
while (!(regs->cr2 & UART_CR2_SRST));
/* Line configuration */
serial_configure(dev, 115200, 8, PARITY_NONE, 1);
/* Enable the UART */
regs->cr1 |= UART_CR1_UARTEN; /* Enable The uart. */
regs->cr2 |= UART_CR2_RXEN | UART_CR2_TXEN; /* RX/TX enable */
regs->cr2 |= UART_CR2_IRTS; /* Ignore RTS */
regs->cr3 |= UART_CR3_RXDMUXDEL; /* Configure the RX MUX */
/* Initialise the receiver interrupt. */
regs->cr1 &= ~UART_CR1_RRDYEN; /* Disable recv interrupt. */
regs->fcr &= ~UART_FCR_RXTL_MASK; /* Clear the rx trigger level value. */
regs->fcr |= UART_FCR_RXTL(1); /* Set the rx tigger level to 1. */
regs->cr1 |= UART_CR1_RRDYEN; /* Enable recv interrupt. */
#ifdef CONFIG_PLAT_IMX6
#include <platsupport/plat/mux.h>
/* The UART1 on the IMX6 has the problem that the MUX is not correctly set,
* and the RX PIN is not routed correctly.
*/
if ((defn->id == IMX_UART1) && mux_sys_valid(&ops->mux_sys)) {
if (mux_feature_enable(&ops->mux_sys, MUX_UART1, 0)) {
/* Failed to configure the mux */
return -1;
}
}
#endif
return 0;
}