199461Sobrien/*
299461Sobrien * Copyright 2020, DornerWorks
399461Sobrien *
499461Sobrien * This software may be distributed and modified according to the terms of
599461Sobrien * the BSD 2-Clause license. Note that NO WARRANTY is provided.
699461Sobrien * See "LICENSE_BSD2.txt" for details.
799461Sobrien *
899461Sobrien * @TAG(DORNERWORKS_BSD)
999461Sobrien */
1099461Sobrien
1199461Sobrien#include <autoconf.h>
1299461Sobrien
1399461Sobrien#include <stdlib.h>
1499461Sobrien#include <platsupport/serial.h>
1599461Sobrien#include <platsupport/plat/serial.h>
1699461Sobrien#include <platsupport/plat/icicle_mss.h>
1799461Sobrien#include <string.h>
1899461Sobrien
1999461Sobrien#include "../../chardev.h"
2099461Sobrien
2199461Sobrienstatic inline uart_regs_t *uart_get_priv(ps_chardevice_t *d)
2299461Sobrien{
2399461Sobrien    return (uart_regs_t *)d->vaddr;
2499461Sobrien}
2599461Sobrien
2699461Sobrienint uart_getchar(ps_chardevice_t *d)
2799461Sobrien{
2899461Sobrien    uart_regs_t *regs = uart_get_priv(d);
2999461Sobrien    if (regs->line_status & LSR_DATA_READY_MASK) {
3099461Sobrien        return regs->rx_buffer;
3199461Sobrien    }
3299461Sobrien    return -1;
3399461Sobrien}
3499461Sobrien
3599461Sobrienstatic void busy_wait_fifo_empty_and_tx_char(uart_regs_t *regs, int c)
3699461Sobrien{
3799461Sobrien    // wait until FIFO empty
3899461Sobrien    while ((regs->line_status & LSR_TX_HOLD_REG_EMPTY_MASK) == 0) {
3999461Sobrien        // busy loop
4099461Sobrien    }
4199461Sobrien
4299461Sobrien    regs->tx_buffer = c;
4399461Sobrien}
4499461Sobrien
4599461Sobrienint uart_putchar(ps_chardevice_t *d, int c)
4699461Sobrien{
4799461Sobrien    uart_regs_t *regs = uart_get_priv(d);
4899461Sobrien
4999461Sobrien    // turn LF into CR+LF if SERIAL_AUTO_CR is active
5099461Sobrien    if ((c == '\n') && (d->flags & SERIAL_AUTO_CR)) {
5199461Sobrien        busy_wait_fifo_empty_and_tx_char(regs, '\r');
5299461Sobrien    }
5399461Sobrien
5499461Sobrien    busy_wait_fifo_empty_and_tx_char(regs, c);
5599461Sobrien    return c;
5699461Sobrien}
5799461Sobrien
5899461Sobrienstatic void uart_handle_irq(ps_chardevice_t *d UNUSED)
5999461Sobrien{
6099461Sobrien    // IRQs are cleared when the TX/RX watermark conditions are no longer met
6199461Sobrien    // so there is nothing to do here.
6299461Sobrien}
6399461Sobrien
6499461Sobrienint uart_init(const struct dev_defn *defn,
6599461Sobrien              const ps_io_ops_t *ops,
6699461Sobrien              ps_chardevice_t *dev)
6799461Sobrien{
6899461Sobrien    uart_regs_t *regs;
6999461Sobrien    uint32_t divisor;
7099461Sobrien    uint32_t divisor_by_128;
7199461Sobrien    uint32_t divisor_by_64;
7299461Sobrien    uint32_t fractional_divisor;
7399461Sobrien    /* Attempt to map the virtual address, assure this works */
7499461Sobrien    void *vaddr = chardev_map(defn, ops);
7599461Sobrien    if (vaddr == NULL) {
7699461Sobrien        return -1;
7799461Sobrien    }
7899461Sobrien
7999461Sobrien    memset(dev, 0, sizeof(*dev));
8099461Sobrien
8199461Sobrien    /* Set up all the  device properties. */
8299461Sobrien    dev->id         = defn->id;
8399461Sobrien    dev->vaddr      = (void *)vaddr;
8499461Sobrien    dev->read       = &uart_read;
8599461Sobrien    dev->write      = &uart_write;
8699461Sobrien    dev->handle_irq = &uart_handle_irq;
8799461Sobrien    dev->irqs       = defn->irqs;
8899461Sobrien    dev->ioops      = *ops;
8999461Sobrien    dev->flags      = SERIAL_AUTO_CR;
9099461Sobrien
9199461Sobrien    regs = uart_get_priv(dev);
9299461Sobrien
9399461Sobrien    regs->line_control = LCR_WORD_LEN_8;
9499461Sobrien    regs->modem_control = 0;
9599461Sobrien    regs->multi_mode_control_0 = 0;
9699461Sobrien    regs->multi_mode_control_1 = 0;
9799461Sobrien    regs->multi_mode_control_2 = 0;
9899461Sobrien    regs->glitch_filter = 0;
9999461Sobrien    regs->transmitter_time_guard = 0;
10099461Sobrien    regs->receiver_time_out = 0;
10199461Sobrien
10299461Sobrien    /*
10399461Sobrien     * Configure baud rate divisors. This uses the fractional baud rate divisor
10499461Sobrien     * where possible to provide the most accurate baud rate possible.
10599461Sobrien     * This algorithm is taken from the Microchip MSS UART Driver (LIC:MIT)
10699461Sobrien     */
10799461Sobrien    divisor_by_128 = (uint32_t)((8UL * POLARFIRE_PCLK) / POLARFIRE_BAUD);
10899461Sobrien    divisor_by_64 = divisor_by_128 / 2u;
10999461Sobrien    divisor = divisor_by_64 / 64u;
11099461Sobrien    fractional_divisor = divisor_by_64 - (divisor * 64u);
11199461Sobrien    fractional_divisor += (divisor_by_128 - (divisor * 128u))
11299461Sobrien                          - (fractional_divisor * 2u);
11399461Sobrien
11499461Sobrien    // div: 81, frac: 24 => 115207
11599461Sobrien
11699461Sobrien    regs->line_control |= LCR_DIV_LATCH_MASK;
11799461Sobrien    regs->divisor_latch_msb = (uint8_t)(divisor >> 8);
11899461Sobrien    regs->divisor_latch_lsb = (uint8_t)divisor;
11999461Sobrien    regs->line_control &= ~LCR_DIV_LATCH_MASK;
12099461Sobrien
12199461Sobrien    regs->multi_mode_control_0 |= MM0_ENABLE_FRAC_MASK;
12299461Sobrien    regs->fractional_divisor = (uint8_t)fractional_divisor;
12399461Sobrien    regs->line_control = LCR_WORD_LEN_8;
12499461Sobrien
12599461Sobrien    return 0;
12699461Sobrien}
12799461Sobrien