blob: 22888c803b2ed4e334ddfd3b88c537505662726c [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdlib.h>
#include <string.h>
#include <utils/util.h>
#include "../../chardev.h"
/*
* Port offsets
* W - write
* R - read
* RW - read and write
* DLAB - Alternate register function bit
*/
#define SERIAL_THR 0 /* Transmitter Holding Buffer (W ) DLAB = 0 */
#define SERIAL_RBR 0 /* Receiver Buffer (R ) DLAB = 0 */
#define SERIAL_DLL 0 /* Divisor Latch Low Byte (RW) DLAB = 1 */
#define SERIAL_IER 1 /* Interrupt Enable Register (RW) DLAB = 0 */
#define SERIAL_DLH 1 /* Divisor Latch High Byte (RW) DLAB = 1 */
#define SERIAL_IIR 2 /* Interrupt Identification (R ) */
#define SERIAL_FCR 2 /* FIFO Control Register (W ) */
#define SERIAL_LCR 3 /* Line Control Register (RW) */
#define SERIAL_MCR 4 /* Modem Control Register (RW) */
#define SERIAL_LSR 5 /* Line Status Register (R ) */
#define SERIAL_MSR 6 /* Modem Status Register (R ) */
#define SERIAL_SR 7 /* Scratch Register (RW) */
#define CONSOLE(port, label) ((port) + (SERIAL_##label))
#define SERIAL_DLAB BIT(7)
#define SERIAL_LSR_DATA_READY BIT(0)
#define SERIAL_LSR_TRANSMITTER_EMPTY BIT(5)
int uart_getchar(ps_chardevice_t *device)
{
uint32_t res;
uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
/* Check if character is available. */
int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res);
if (error != 0) {
return -1;
}
if (!(res & SERIAL_LSR_DATA_READY)) {
return -1;
}
/* retrieve character */
error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &res);
if (error != 0) {
return -1;
}
return (int) res;
}
static int serial_ready(ps_chardevice_t* device)
{
uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
uint32_t res;
int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res);
if (error != 0) {
return 0;
}
return res & SERIAL_LSR_TRANSMITTER_EMPTY;
}
int uart_putchar(ps_chardevice_t* device, int c)
{
uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
/* Check if serial is ready. */
if (!serial_ready(device)) {
return -1;
}
/* Write out the next character. */
ps_io_port_out(&device->ioops.io_port_ops, CONSOLE(io_port, THR), 1, c);
if (c == '\n') {
/* If we output immediately then odds are the transmit buffer
* will be full, so we have to wait */
while (!serial_ready(device));
uart_putchar(device, '\r');
}
return c;
}
static void uart_handle_irq(ps_chardevice_t* device UNUSED)
{
/* No IRQ handling required here. */
}
int
uart_init(const struct dev_defn* defn, const ps_io_ops_t* ops, ps_chardevice_t* dev)
{
memset(dev, 0, sizeof(*dev));
/* Set up all the device properties. */
dev->id = defn->id;
dev->vaddr = (void*) defn->paddr; /* Save the IO port base number. */
dev->read = &uart_read;
dev->write = &uart_write;
dev->handle_irq = &uart_handle_irq;
dev->irqs = defn->irqs;
dev->ioops = *ops;
/* Initialise the device. */
uint32_t io_port = (uint32_t) (uintptr_t)dev->vaddr;
/* clear DLAB - Divisor Latch Access Bit */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 & ~SERIAL_DLAB) != 0) {
return -1;
}
/* disable generating interrupts */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x00) != 0) {
return -1;
}
/* set DLAB to*/
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 | SERIAL_DLAB) != 0) {
return -1;
}
/* set low byte of divisor to 0x01 = 115200 baud */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLL), 1, 0x01) != 0) {
return -1;
}
/* set high byte of divisor to 0x00 */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLH), 1, 0x00) != 0) {
return -1;
}
/* line control register: set 8 bit, no parity, 1 stop bit; clear DLAB */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x03 & ~SERIAL_DLAB) != 0) {
return -1;
}
/* modem control register: set DTR/RTS/OUT2 */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, MCR), 1, 0x0b) != 0) {
return -1;
}
uint32_t temp;
/* clear receiver port */
if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &temp) != 0) {
return -1;
}
/* clear line status port */
if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &temp) != 0) {
return -1;
}
/* clear modem status port */
if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, MSR), 1, &temp) != 0) {
return -1;
}
/* Enable the receiver interrupt. */
if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x01) != 0) {
return -1;
}
return 0;
}