| /* |
| * (C) Copyright 2000 |
| * Rob Taylor, Flying Pig Systems. robt@flyingpig.com. |
| * |
| * (C) Copyright 2004 |
| * ARM Ltd. |
| * Philippe Robin, <philippe.robin@arm.com> |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| /* Simple U-Boot driver for the PrimeCell PL010/PL011 UARTs */ |
| |
| #include <common.h> |
| #include <watchdog.h> |
| #include <asm/io.h> |
| #include <serial.h> |
| #include <linux/compiler.h> |
| #include "serial_pl01x.h" |
| |
| /* |
| * 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. |
| */ |
| #define CONSOLE_PORT CONFIG_CONS_INDEX |
| static volatile unsigned char *const port[] = CONFIG_PL01x_PORTS; |
| #define NUM_PORTS (sizeof(port)/sizeof(port[0])) |
| |
| static void pl01x_putc (int portnum, char c); |
| static int pl01x_getc (int portnum); |
| static int pl01x_tstc (int portnum); |
| unsigned int baudrate = CONFIG_BAUDRATE; |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static struct pl01x_regs *pl01x_get_regs(int portnum) |
| { |
| return (struct pl01x_regs *) port[portnum]; |
| } |
| |
| #ifdef CONFIG_PL010_SERIAL |
| |
| static int pl01x_serial_init(void) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
| unsigned int divisor; |
| |
| /* First, disable everything */ |
| writel(0, ®s->pl010_cr); |
| |
| /* Set baud rate */ |
| 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); |
| |
| /* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */ |
| writel(UART_PL010_LCRH_WLEN_8 | UART_PL010_LCRH_FEN, ®s->pl010_lcrh); |
| |
| /* Finally, enable the UART */ |
| writel(UART_PL010_CR_UARTEN, ®s->pl010_cr); |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_PL010_SERIAL */ |
| |
| #ifdef CONFIG_PL011_SERIAL |
| |
| static int pl01x_serial_init(void) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
| unsigned int temp; |
| unsigned int divider; |
| unsigned int remainder; |
| unsigned int fraction; |
| unsigned int lcr; |
| |
| #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 |
| |
| /* First, disable everything */ |
| writel(0, ®s->pl011_cr); |
| |
| /* |
| * 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 = CONFIG_PL011_CLOCK / temp; |
| remainder = CONFIG_PL011_CLOCK % temp; |
| temp = (8 * remainder) / baudrate; |
| fraction = (temp >> 1) + (temp & 1); |
| |
| writel(divider, ®s->pl011_ibrd); |
| writel(fraction, ®s->pl011_fbrd); |
| |
| /* Set the UART to be 8 bits, 1 stop bit, no parity, fifo enabled */ |
| lcr = UART_PL011_LCRH_WLEN_8 | UART_PL011_LCRH_FEN; |
| writel(lcr, ®s->pl011_lcrh); |
| |
| #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); |
| /* lcrh needs to be set again for change to be effective */ |
| writel(lcr, ®s->pl011_lcrh); |
| } |
| #endif |
| /* Finally, enable the UART */ |
| writel(UART_PL011_CR_UARTEN | UART_PL011_CR_TXE | UART_PL011_CR_RXE | |
| UART_PL011_CR_RTS, ®s->pl011_cr); |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_PL011_SERIAL */ |
| |
| static void pl01x_serial_putc(const char c) |
| { |
| if (c == '\n') |
| pl01x_putc (CONSOLE_PORT, '\r'); |
| |
| pl01x_putc (CONSOLE_PORT, c); |
| } |
| |
| static int pl01x_serial_getc(void) |
| { |
| return pl01x_getc (CONSOLE_PORT); |
| } |
| |
| static int pl01x_serial_tstc(void) |
| { |
| return pl01x_tstc (CONSOLE_PORT); |
| } |
| |
| static void pl01x_serial_setbrg(void) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(CONSOLE_PORT); |
| |
| baudrate = gd->baudrate; |
| /* |
| * Flush FIFO and wait for non-busy before changing baudrate to avoid |
| * crap in console |
| */ |
| while (!(readl(®s->fr) & UART_PL01x_FR_TXFE)) |
| WATCHDOG_RESET(); |
| while (readl(®s->fr) & UART_PL01x_FR_BUSY) |
| WATCHDOG_RESET(); |
| serial_init(); |
| } |
| |
| static void pl01x_putc (int portnum, char c) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(portnum); |
| |
| /* Wait until there is space in the FIFO */ |
| while (readl(®s->fr) & UART_PL01x_FR_TXFF) |
| WATCHDOG_RESET(); |
| |
| /* Send the character */ |
| writel(c, ®s->dr); |
| } |
| |
| static int pl01x_getc (int portnum) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(portnum); |
| unsigned int data; |
| |
| /* Wait until there is data in the FIFO */ |
| while (readl(®s->fr) & UART_PL01x_FR_RXFE) |
| WATCHDOG_RESET(); |
| |
| 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 (int portnum) |
| { |
| struct pl01x_regs *regs = pl01x_get_regs(portnum); |
| |
| WATCHDOG_RESET(); |
| return !(readl(®s->fr) & UART_PL01x_FR_RXFE); |
| } |
| |
| 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; |
| } |