blob: 99bf0e0dc0dc2e40febeff281b917e47c5b63f1a [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdlib.h>
#include <platsupport/serial.h>
#include <platsupport/plat/serial.h>
#include <string.h>
#include <stdio.h>
#include "../../chardev.h"
#define AXI_UARTLITE_CR_RST_TX_FIFO BIT(0)
#define AXI_UARTLITE_CR_RST_RX_FIFO BIT(1)
#define AXI_UARTLITE_CR_ENABLE_INTR BIT(4)
#define AXI_UARTLITE_SR_RX_FIFO_VALID BIT(0)
#define AXI_UARTLITE_SR_RX_FIFO_FULL BIT(1)
#define AXI_UARTLITE_SR_TX_FIFO_EMPTY BIT(2)
#define AXI_UARTLITE_SR_TX_FIFO_FULL BIT(3)
#define AXI_UARTLITE_SR_INTR_ENABLED BIT(4)
#define AXI_UARTLITE_SR_OVERRUN_ERROR BIT(5)
#define AXI_UARTLITE_SR_FRAME_ERROR BIT(6)
#define AXI_UARTLITE_SR_PARITY_ERROR BIT(7)
struct zynq_axi_uartlite_regs {
uint32_t rx_fifo; /* 0x0 Receive FIFO */
uint32_t tx_fifo; /* 0x4 Transmit FIFO */
uint32_t sr; /* 0x8 Status Register */
uint32_t cr; /* 0xC Control Register */
};
typedef volatile struct zynq_axi_uartlite_regs zynq_axi_uartlite_regs_t;
static inline zynq_axi_uartlite_regs_t*
zynq_axi_uartlite_get_priv(ps_chardevice_t *d)
{
return (zynq_axi_uartlite_regs_t*)d->vaddr;
}
static int axi_uartlite_getchar(ps_chardevice_t *d)
{
zynq_axi_uartlite_regs_t *regs =
zynq_axi_uartlite_get_priv(d);
int c = -1;
/* check if there is at least one byte in the fifo */
if (regs->sr & AXI_UARTLITE_SR_RX_FIFO_VALID) {
c = regs->rx_fifo;
}
return c;
}
static int axi_uartlite_putchar(ps_chardevice_t *d, int c)
{
static int needs_newline = 0;
zynq_axi_uartlite_regs_t *regs =
zynq_axi_uartlite_get_priv(d);
/* check if fifo is full */
if (regs->sr & AXI_UARTLITE_SR_TX_FIFO_FULL) {
return -1;
} else {
if (needs_newline) {
/* if the last putchar was a '\n' and the fifo filled after
* only the '\r' was sent, send the remaining '\n' here */
regs->tx_fifo = '\n';
needs_newline = 0;
if (regs->sr & AXI_UARTLITE_SR_TX_FIFO_FULL) {
return -1;
}
}
if (c == '\n') {
regs->tx_fifo = '\r';
/* the fifo may have filled after sending the '\r' */
if (regs->sr & AXI_UARTLITE_SR_TX_FIFO_FULL) {
needs_newline = 1;
/* even if the '\n' didn't get sent on this call, still
* return '\n', as it will still eventually be sent */
} else {
regs->tx_fifo = '\n';
}
} else {
regs->tx_fifo = c;
}
return c;
}
}
static ssize_t axi_uartlite_write(ps_chardevice_t* d, const void* vdata,
size_t count, chardev_callback_t rcb UNUSED,
void* token UNUSED)
{
const char *data = (const char*)vdata;
for (int i = 0; i < count; i++) {
if (axi_uartlite_putchar(d, *data++) < 0) {
return i;
}
}
return count;
}
static ssize_t axi_uartlite_read(ps_chardevice_t* d, void* vdata,
size_t count, chardev_callback_t rcb UNUSED,
void* token UNUSED)
{
char *data = (char*)vdata;
for (int i = 0; i < count; i++) {
int ch = axi_uartlite_getchar(d);
if (ch != EOF) {
*data++ = ch;
} else {
return i;
}
}
return count;
}
int axi_uartlite_init(void* vaddr, ps_chardevice_t* dev)
{
memset(dev, 0, sizeof(*dev));
dev->vaddr = vaddr;
dev->read = &axi_uartlite_read;
dev->write = &axi_uartlite_write;
zynq_axi_uartlite_regs_t *regs = zynq_axi_uartlite_get_priv(dev);
// clear the fifos
regs->cr |= (AXI_UARTLITE_CR_RST_TX_FIFO | AXI_UARTLITE_CR_RST_RX_FIFO);
// disable interrupts
regs->cr &= ~AXI_UARTLITE_CR_ENABLE_INTR;
return 0;
}
int axi_uartlite_init_defn(const struct dev_defn* defn,
const ps_io_ops_t* ops,
ps_chardevice_t* dev)
{
void *vaddr = chardev_map(defn, ops);
if (vaddr == NULL) {
return -1;
}
axi_uartlite_init(vaddr, dev);
dev->id = defn->id;
dev->irqs = defn->irqs;
dev->ioops = *ops;
return 0;
}