/* * (C) Copyright 2000 * Rob Taylor, Flying Pig Systems. robt@flyingpig.com. * * (C) Copyright 2004 * ARM Ltd. * Philippe Robin, * * SPDX-License-Identifier: GPL-2.0+ */ /* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */ #include #include #include #include #include #include #include #include #include "serial_pl01x_internal.h" #ifndef CONFIG_DM_SERIAL static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS; static enum pl01x_type pl01x_type __attribute__ ((section(".data"))); static struct pl01x_regs *base_regs __attribute__ ((section(".data"))); #define NUM_PORTS (sizeof(port)/sizeof(port[0])) DECLARE_GLOBAL_DATA_PTR; #endif static int pl01x_putc(struct pl01x_regs *regs, char c) { /* Wait until there is space in the FIFO */ if (readl(®s->fr) & UART_PL01x_FR_TXFF) return -EAGAIN; /* Send the character */ writel(c, ®s->dr); return 0; } static int pl01x_getc(struct pl01x_regs *regs) { unsigned int data; /* Wait until there is data in the FIFO */ if (readl(®s->fr) & UART_PL01x_FR_RXFE) return -EAGAIN; data = readl(®s->dr); /* Check for an error flag */ if (data & 0xFFFFFF00) { /* Clear the error */ writel(0xFFFFFFFF, ®s->ecr); return -1; } return (int) data; } static int pl01x_tstc(struct pl01x_regs *regs) { WATCHDOG_RESET(); return !(readl(®s->fr) & UART_PL01x_FR_RXFE); } static int pl01x_generic_serial_init(struct pl01x_regs *regs, enum pl01x_type type) { switch (type) { case TYPE_PL010: /* disable everything */ writel(0, ®s->pl010_cr); break; case TYPE_PL011: #ifdef CONFIG_PL011_SERIAL_FLUSH_ON_INIT /* Empty RX fifo if necessary */ if (readl(®s->pl011_cr) & UART_PL011_CR_UARTEN) { while (!(readl(®s->fr) & UART_PL01x_FR_RXFE)) readl(®s->dr); } #endif /* disable everything */ writel(0, ®s->pl011_cr); break; default: return -EINVAL; } return 0; } static int set_line_control(struct pl01x_regs *regs) { unsigned int lcr; /* * Internal update of baud rate register require line * control register write */ lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN; #ifdef CONFIG_PL011_SERIAL_RLCR { int i; /* * Program receive line control register after waiting * 10 bus cycles. Delay be writing to readonly register * 10 times */ for (i = 0; i < 10; i++) writel(lcr, ®s->fr); writel(lcr, ®s->pl011_rlcr); } #endif writel(lcr, ®s->pl011_lcrh); return 0; } static int pl01x_generic_setbrg(struct pl01x_regs *regs, enum pl01x_type type, int clock, int baudrate) { switch (type) { case TYPE_PL010: { unsigned int divisor; switch (baudrate) { case 9600: divisor = UART_PL010_BAUD_9600; break; case 19200: divisor = UART_PL010_BAUD_9600; break; case 38400: divisor = UART_PL010_BAUD_38400; break; case 57600: divisor = UART_PL010_BAUD_57600; break; case 115200: divisor = UART_PL010_BAUD_115200; break; default: divisor = UART_PL010_BAUD_38400; } writel((divisor & 0xf00) >> 8, ®s->pl010_lcrm); writel(divisor & 0xff, ®s->pl010_lcrl); /* Finally, enable the UART */ writel(UART_PL010_CR_UARTEN, ®s->pl010_cr); break; } case TYPE_PL011: { unsigned int temp; unsigned int divider; unsigned int remainder; unsigned int fraction; /* * Set baud rate * * IBRD = UART_CLK / (16 * BAUD_RATE) * FBRD = RND((64 * MOD(UART_CLK,(16 * BAUD_RATE))) * / (16 * BAUD_RATE)) */ temp = 16 * baudrate; divider = clock / temp; remainder = clock % temp; temp = (8 * remainder) / baudrate; fraction = (temp >> 1) + (temp & 1); writel(divider, ®s->pl011_ibrd); writel(fraction, ®s->pl011_fbrd); set_line_control(regs); /* Finally, enable the UART */ writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE | UART_PL011_CR_RTS, ®s->pl011_cr); break; } default: return -EINVAL; } return 0; } #ifndef CONFIG_DM_SERIAL static void pl01x_serial_init_baud(int baudrate) { int clock = 0; #if defined(CONFIG_PL010_SERIAL) pl01x_type = TYPE_PL010; #elif defined(CONFIG_PL011_SERIAL) pl01x_type = TYPE_PL011; clock = CONFIG_PL011_CLOCK; #endif base_regs = (struct pl01x_regs *)port[CONFIG_CONS_INDEX]; pl01x_generic_serial_init(base_regs, pl01x_type); pl01x_generic_setbrg(base_regs, pl01x_type, clock, baudrate); } /* * Integrator AP has two UARTs, we use the first one, at 38400-8-N-1 * Integrator CP has two UARTs, use the first one, at 38400-8-N-1 * Versatile PB has four UARTs. */ int pl01x_serial_init(void) { pl01x_serial_init_baud(CONFIG_BAUDRATE); return 0; } static void pl01x_serial_putc(const char c) { if (c == '\n') while (pl01x_putc(base_regs, '\r') == -EAGAIN); while (pl01x_putc(base_regs, c) == -EAGAIN); } static int pl01x_serial_getc(void) { while (1) { int ch = pl01x_getc(base_regs); if (ch == -EAGAIN) { WATCHDOG_RESET(); continue; } return ch; } } static int pl01x_serial_tstc(void) { return pl01x_tstc(base_regs); } static void pl01x_serial_setbrg(void) { /* * Flush FIFO and wait for non-busy before changing baudrate to avoid * crap in console */ while (!(readl(&base_regs->fr) & UART_PL01x_FR_TXFE)) WATCHDOG_RESET(); while (readl(&base_regs->fr) & UART_PL01x_FR_BUSY) WATCHDOG_RESET(); pl01x_serial_init_baud(gd->baudrate); } static struct serial_device pl01x_serial_drv = { .name = "pl01x_serial", .start = pl01x_serial_init, .stop = NULL, .setbrg = pl01x_serial_setbrg, .putc = pl01x_serial_putc, .puts = default_serial_puts, .getc = pl01x_serial_getc, .tstc = pl01x_serial_tstc, }; void pl01x_serial_initialize(void) { serial_register(&pl01x_serial_drv); } __weak struct serial_device *default_serial_console(void) { return &pl01x_serial_drv; } #endif /* nCONFIG_DM_SERIAL */ #ifdef CONFIG_DM_SERIAL struct pl01x_priv { struct pl01x_regs *regs; enum pl01x_type type; }; static int pl01x_serial_setbrg(struct udevice *dev, int baudrate) { struct pl01x_serial_platdata *plat = dev_get_platdata(dev); struct pl01x_priv *priv = dev_get_priv(dev); pl01x_generic_setbrg(priv->regs, priv->type, plat->clock, baudrate); return 0; } static int pl01x_serial_probe(struct udevice *dev) { struct pl01x_serial_platdata *plat = dev_get_platdata(dev); struct pl01x_priv *priv = dev_get_priv(dev); priv->regs = (struct pl01x_regs *)plat->base; priv->type = plat->type; return pl01x_generic_serial_init(priv->regs, priv->type); } static int pl01x_serial_getc(struct udevice *dev) { struct pl01x_priv *priv = dev_get_priv(dev); return pl01x_getc(priv->regs); } static int pl01x_serial_putc(struct udevice *dev, const char ch) { struct pl01x_priv *priv = dev_get_priv(dev); return pl01x_putc(priv->regs, ch); } static int pl01x_serial_pending(struct udevice *dev, bool input) { struct pl01x_priv *priv = dev_get_priv(dev); unsigned int fr = readl(&priv->regs->fr); if (input) return pl01x_tstc(priv->regs); else return fr & UART_PL01x_FR_TXFF ? 0 : 1; } static const struct dm_serial_ops pl01x_serial_ops = { .putc = pl01x_serial_putc, .pending = pl01x_serial_pending, .getc = pl01x_serial_getc, .setbrg = pl01x_serial_setbrg, }; U_BOOT_DRIVER(serial_pl01x) = { .name = "serial_pl01x", .id = UCLASS_SERIAL, .probe = pl01x_serial_probe, .ops = &pl01x_serial_ops, .flags = DM_FLAG_PRE_RELOC, .priv_auto_alloc_size = sizeof(struct pl01x_priv), }; #endif