// SPDX-License-Identifier: GPL-2.0+
/*
* Support for Serial I/O using STMicroelectronics' on-chip ASC.
*
* Copyright (C) 2017, STMicroelectronics - All Rights Reserved
* Author(s): Patrice Chotard, <patrice.chotard@st.com> for STMicroelectronics.
*/
#include <common.h>
#include <dm.h>
#include <serial.h>
#include <asm/io.h>
DECLARE_GLOBAL_DATA_PTR;
#define BAUDMODE 0x00001000
#define RXENABLE 0x00000100
#define RUN 0x00000080
#define MODE 0x00000001
#define MODE_8BIT 0x0001
#define STOP_1BIT 0x0008
#define PARITYODD 0x0020
#define STA_TF BIT(9)
#define STA_RBF BIT(0)
struct sti_asc_uart {
u32 baudrate;
u32 txbuf;
u32 rxbuf;
u32 control;
u32 inten;
u32 status;
u32 guardtime;
u32 timeout;
u32 txreset;
u32 rxreset;
};
struct sti_asc_serial {
/* address of registers in physical memory */
struct sti_asc_uart *regs;
};
/* Values for the BAUDRATE Register */
#define PCLK (200ul * 1000000ul)
#define BAUDRATE_VAL_M0(bps) (PCLK / (16 * (bps)))
#define BAUDRATE_VAL_M1(bps) ((bps * (1 << 14)) + (1<<13)) / (PCLK/(1 << 6))
/*
* MODE 0
* ICCLK
* ASCBaudRate = ----------------
* baudrate * 16
*
* MODE 1
* baudrate * 16 * 2^16
* ASCBaudRate = ------------------------
* ICCLK
*
* NOTE:
* Mode 1 should be used for baudrates of 19200, and above, as it
* has a lower deviation error than Mode 0 for higher frequencies.
* Mode 0 should be used for all baudrates below 19200.
*/
static int sti_asc_pending(struct udevice *dev, bool input)
{
struct sti_asc_serial *priv = dev_get_priv(dev);
struct sti_asc_uart *const uart = priv->regs;
unsigned long status;
status = readl(&uart->status);
if (input)
return status & STA_RBF;
else
return status & STA_TF;
}
static int _sti_asc_serial_setbrg(struct sti_asc_uart *uart, int baudrate)
{
unsigned long val;
int t, mode = 1;
switch (baudrate) {
case 9600:
t = BAUDRATE_VAL_M0(9600);
mode = 0;
break;
case 19200:
t = BAUDRATE_VAL_M1(19200);
break;
case 38400:
t = BAUDRATE_VAL_M1(38400);
break;
case 57600:
t = BAUDRATE_VAL_M1(57600);
break;
default:
debug("ASC: unsupported baud rate: %d, using 115200 instead.\n",
baudrate);
case 115200:
t = BAUDRATE_VAL_M1(115200);
break;
}
/* disable the baudrate generator */
val = readl(&uart->control);
writel(val & ~RUN, &uart->control);
/* set baud generator reload value */
writel(t, &uart->baudrate);
/* reset the RX & TX buffers */
writel(1, &uart->txreset);
writel(1, &uart->rxreset);
/* set baud generator mode */
if (mode)
val |= BAUDMODE;
/* finally, write value and enable ASC */
writel(val, &uart->control);
return 0;
}
/* called to adjust baud-rate */
static int sti_asc_serial_setbrg(struct udevice *dev, int baudrate)
{
struct sti_asc_serial *priv = dev_get_priv(dev);
struct sti_asc_uart *const uart = priv->regs;
return _sti_asc_serial_setbrg(uart, baudrate);
}
/* blocking function, that returns next char */
static int sti_asc_serial_getc(struct udevice *dev)
{
struct sti_asc_serial *priv = dev_get_priv(dev);
struct sti_asc_uart *const uart = priv->regs;
/* polling wait: for a char to be read */
if (!sti_asc_pending(dev, true))
return -EAGAIN;
return readl(&uart->rxbuf);
}
/* write write out a single char */
static int sti_asc_serial_putc(struct udevice *dev, const char c)
{
struct sti_asc_serial *priv = dev_get_priv(dev);
struct sti_asc_uart *const uart = priv->regs;
/* wait till safe to write next char */
if (sti_asc_pending(dev, false))
return -EAGAIN;
/* finally, write next char */
writel(c, &uart->txbuf);
return 0;
}
/* initialize the ASC */
static int sti_asc_serial_probe(struct udevice *dev)
{
struct sti_asc_serial *priv = dev_get_priv(dev);
unsigned long val;
fdt_addr_t base;
base = devfdt_get_addr(dev);
if (base == FDT_ADDR_T_NONE)
return -EINVAL;
priv->regs = (struct sti_asc_uart *)base;
sti_asc_serial_setbrg(dev, gd->baudrate);
/*
* build up the value to be written to CONTROL
* set character length, bit stop number, odd parity
*/
val = RXENABLE | RUN | MODE_8BIT | STOP_1BIT | PARITYODD;
writel(val, &priv->regs->control);
return 0;
}
static const struct dm_serial_ops sti_asc_serial_ops = {
.putc = sti_asc_serial_putc,
.pending = sti_asc_pending,
.getc = sti_asc_serial_getc,
.setbrg = sti_asc_serial_setbrg,
};
static const struct udevice_id sti_serial_of_match[] = {
{ .compatible = "st,asc" },
{ }
};
U_BOOT_DRIVER(serial_sti_asc) = {
.name = "serial_sti_asc",
.id = UCLASS_SERIAL,
.of_match = sti_serial_of_match,
.ops = &sti_asc_serial_ops,
.probe = sti_asc_serial_probe,
.priv_auto_alloc_size = sizeof(struct sti_asc_serial),
.flags = DM_FLAG_PRE_RELOC,
};