1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <stdlib.h>
14#include <string.h>
15#include <utils/util.h>
16
17#include "../../chardev.h"
18
19/*
20 * Port offsets
21 * W    - write
22 * R    - read
23 * RW   - read and write
24 * DLAB - Alternate register function bit
25 */
26
27#define SERIAL_THR  0 /* Transmitter Holding Buffer (W ) DLAB = 0 */
28#define SERIAL_RBR  0 /* Receiver Buffer            (R ) DLAB = 0 */
29#define SERIAL_DLL  0 /* Divisor Latch Low Byte     (RW) DLAB = 1 */
30#define SERIAL_IER  1 /* Interrupt Enable Register  (RW) DLAB = 0 */
31#define SERIAL_DLH  1 /* Divisor Latch High Byte    (RW) DLAB = 1 */
32#define SERIAL_IIR  2 /* Interrupt Identification   (R ) */
33#define SERIAL_FCR  2 /* FIFO Control Register      (W ) */
34#define SERIAL_LCR  3 /* Line Control Register      (RW) */
35#define SERIAL_MCR  4 /* Modem Control Register     (RW) */
36#define SERIAL_LSR  5 /* Line Status Register       (R ) */
37#define SERIAL_MSR  6 /* Modem Status Register      (R ) */
38#define SERIAL_SR   7 /* Scratch Register           (RW) */
39#define CONSOLE(port, label) ((port) + (SERIAL_##label))
40#define SERIAL_DLAB BIT(7)
41#define SERIAL_LSR_DATA_READY BIT(0)
42#define SERIAL_LSR_TRANSMITTER_EMPTY BIT(5)
43
44int uart_getchar(ps_chardevice_t *device)
45{
46    uint32_t res;
47    uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
48
49    /* Check if character is available. */
50    int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res);
51    if (error != 0) {
52        return -1;
53    }
54    if (!(res & SERIAL_LSR_DATA_READY)) {
55        return -1;
56    }
57
58    /* retrieve character */
59    error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &res);
60    if (error != 0) {
61        return -1;
62    }
63
64    return (int) res;
65}
66
67static int serial_ready(ps_chardevice_t* device)
68{
69    uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
70    uint32_t res;
71    int error = ps_io_port_in(&device->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &res);
72    if (error != 0) {
73        return 0;
74    }
75    return res & SERIAL_LSR_TRANSMITTER_EMPTY;
76}
77
78int uart_putchar(ps_chardevice_t* device, int c)
79{
80    uint32_t io_port = (uint32_t) (uintptr_t)device->vaddr;
81
82    /* Check if serial is ready. */
83    if (!serial_ready(device)) {
84        return -1;
85    }
86
87    /* Write out the next character. */
88    ps_io_port_out(&device->ioops.io_port_ops, CONSOLE(io_port, THR), 1, c);
89
90    if (c == '\n') {
91        /* If we output immediately then odds are the transmit buffer
92         * will be full, so we have to wait */
93        while (!serial_ready(device));
94        uart_putchar(device, '\r');
95    }
96
97    return c;
98}
99
100static void uart_handle_irq(ps_chardevice_t* device UNUSED)
101{
102    /* No IRQ handling required here. */
103}
104
105int
106uart_init(const struct dev_defn* defn, const ps_io_ops_t* ops, ps_chardevice_t* dev)
107{
108    memset(dev, 0, sizeof(*dev));
109    /* Set up all the  device properties. */
110    dev->id         = defn->id;
111    dev->vaddr      = (void*) defn->paddr; /* Save the IO port base number. */
112    dev->read       = &uart_read;
113    dev->write      = &uart_write;
114    dev->handle_irq = &uart_handle_irq;
115    dev->irqs       = defn->irqs;
116    dev->ioops      = *ops;
117
118    /* Initialise the device. */
119    uint32_t io_port = (uint32_t) (uintptr_t)dev->vaddr;
120
121    /* clear DLAB - Divisor Latch Access Bit */
122    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 & ~SERIAL_DLAB) != 0) {
123        return -1;
124    }
125
126    /* disable generating interrupts */
127    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x00) != 0) {
128        return -1;
129    }
130
131    /* set DLAB to*/
132    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x00 | SERIAL_DLAB) != 0) {
133        return -1;
134    }
135    /* set low byte of divisor to 0x01 = 115200 baud */
136    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLL), 1, 0x01) != 0) {
137        return -1;
138    }
139    /* set high byte of divisor to 0x00 */
140    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, DLH), 1, 0x00) != 0) {
141        return -1;
142    }
143
144    /* line control register: set 8 bit, no parity, 1 stop bit; clear DLAB */
145    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, LCR), 1, 0x03 & ~SERIAL_DLAB) != 0) {
146        return -1;
147    }
148    /* modem control register: set DTR/RTS/OUT2 */
149    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, MCR), 1, 0x0b) != 0) {
150        return -1;
151    }
152
153    uint32_t temp;
154    /* clear receiver port */
155    if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, RBR), 1, &temp) != 0) {
156        return -1;
157    }
158    /* clear line status port */
159    if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, LSR), 1, &temp) != 0) {
160        return -1;
161    }
162    /* clear modem status port */
163    if (ps_io_port_in(&dev->ioops.io_port_ops, CONSOLE(io_port, MSR), 1, &temp) != 0) {
164        return -1;
165    }
166
167    /* Enable the receiver interrupt. */
168    if (ps_io_port_out(&dev->ioops.io_port_ops, CONSOLE(io_port, IER), 1, 0x01) != 0) {
169        return -1;
170    }
171
172    return 0;
173}
174