diff options
| author | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
|---|---|---|
| committer | Simon Rettberg | 2026-01-28 12:53:53 +0100 |
| commit | 8e82785c584dc13e20f9229decb95bd17bbe9cd1 (patch) | |
| tree | a8b359e59196be5b2e3862bed189107f4bc9975f /src/drivers/uart | |
| parent | Merge branch 'master' into openslx (diff) | |
| parent | [prefix] Make unlzma.S compatible with 386 class CPUs (diff) | |
| download | ipxe-openslx.tar.gz ipxe-openslx.tar.xz ipxe-openslx.zip | |
Merge branch 'master' into openslxopenslx
Diffstat (limited to 'src/drivers/uart')
| -rw-r--r-- | src/drivers/uart/dwuart.c | 129 | ||||
| -rw-r--r-- | src/drivers/uart/ns16550.c | 177 |
2 files changed, 306 insertions, 0 deletions
diff --git a/src/drivers/uart/dwuart.c b/src/drivers/uart/dwuart.c new file mode 100644 index 000000000..ce08e8ebf --- /dev/null +++ b/src/drivers/uart/dwuart.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * DesignWare UART + * + */ + +#include <errno.h> +#include <ipxe/uart.h> +#include <ipxe/ns16550.h> +#include <ipxe/devtree.h> +#include <ipxe/fdt.h> + +/** + * Probe devicetree device + * + * @v dt Devicetree device + * @v offset Starting node offset + * @ret rc Return status code + */ +static int dwuart_probe ( struct dt_device *dt, unsigned int offset ) { + struct ns16550_uart *ns16550; + struct uart *uart; + uint32_t shift; + uint32_t clock; + int rc; + + /* Allocate and initialise UART */ + uart = alloc_uart ( sizeof ( *ns16550 ) ); + if ( ! uart ) { + rc = -ENOMEM; + goto err_alloc; + } + uart->name = dt->name; + uart->op = &ns16550_operations; + ns16550 = uart->priv; + dt_set_drvdata ( dt, uart ); + + /* Map registers */ + ns16550->base = dt_ioremap ( dt, offset, 0, 0 ); + if ( ! ns16550->base ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Get register shift */ + if ( ( rc = fdt_u32 ( &sysfdt, offset, "reg-shift", &shift ) ) != 0 ) + shift = 0; + ns16550->shift = shift; + + /* Get clock rate */ + if ( ( rc = fdt_u32 ( &sysfdt, offset, "clock-frequency", + &clock ) ) != 0 ) { + clock = NS16550_CLK_DEFAULT; + } + ns16550->clock = clock; + + /* Register UART */ + if ( ( rc = uart_register ( uart ) ) != 0 ) + goto err_register; + + return 0; + + uart_unregister ( uart ); + err_register: + iounmap ( ns16550->base ); + err_ioremap: + uart_nullify ( uart ); + uart_put ( uart ); + err_alloc: + return rc; +} + +/** + * Remove devicetree device + * + * @v dt Devicetree device + */ +static void dwuart_remove ( struct dt_device *dt ) { + struct uart *uart = dt_get_drvdata ( dt ); + struct ns16550_uart *ns16550 = uart->priv; + + /* Unregister UART */ + uart_unregister ( uart ); + + /* Free UART */ + iounmap ( ns16550->base ); + uart_nullify ( uart ); + uart_put ( uart ); +} + +/** DesignWare UART compatible model identifiers */ +static const char * dwuart_ids[] = { + "snps,dw-apb-uart", + "ns16550a", +}; + +/** DesignWare UART devicetree driver */ +struct dt_driver dwuart_driver __dt_driver = { + .name = "dwuart", + .ids = dwuart_ids, + .id_count = ( sizeof ( dwuart_ids ) / sizeof ( dwuart_ids[0] ) ), + .probe = dwuart_probe, + .remove = dwuart_remove, +}; diff --git a/src/drivers/uart/ns16550.c b/src/drivers/uart/ns16550.c new file mode 100644 index 000000000..f00e834b4 --- /dev/null +++ b/src/drivers/uart/ns16550.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * 16550-compatible UART + * + */ + +#include <unistd.h> +#include <errno.h> +#include <ipxe/uart.h> +#include <ipxe/ns16550.h> + +/** Timeout for transmit holding register to become empty */ +#define NS16550_THRE_TIMEOUT_MS 100 + +/** Timeout for transmitter to become empty */ +#define NS16550_TEMT_TIMEOUT_MS 1000 + +/** + * Transmit data + * + * @v uart UART + * @v data Data + */ +static void ns16550_transmit ( struct uart *uart, uint8_t data ) { + struct ns16550_uart *ns16550 = uart->priv; + unsigned int i; + uint8_t lsr; + + /* Wait for transmitter holding register to become empty */ + for ( i = 0 ; i < NS16550_THRE_TIMEOUT_MS ; i++ ) { + lsr = ns16550_read ( ns16550, NS16550_LSR ); + if ( lsr & NS16550_LSR_THRE ) + break; + mdelay ( 1 ); + } + + /* Transmit data (even if we timed out) */ + ns16550_write ( ns16550, NS16550_THR, data ); +} + +/** + * Check if data is ready + * + * @v uart UART + * @ret ready Data is ready + */ +static int ns16550_data_ready ( struct uart *uart ) { + struct ns16550_uart *ns16550 = uart->priv; + uint8_t lsr; + + /* Check for receive data ready */ + lsr = ns16550_read ( ns16550, NS16550_LSR ); + return ( lsr & NS16550_LSR_DR ); +} + +/** + * Receive data + * + * @v uart UART + * @ret data Data + */ +static uint8_t ns16550_receive ( struct uart *uart ) { + struct ns16550_uart *ns16550 = uart->priv; + uint8_t rbr; + + /* Receive byte */ + rbr = ns16550_read ( ns16550, NS16550_RBR ); + return rbr; +} + +/** + * Flush transmitted data + * + * @v uart UART + */ +static void ns16550_flush ( struct uart *uart ) { + struct ns16550_uart *ns16550 = uart->priv; + unsigned int i; + uint8_t lsr; + + /* Wait for transmitter to become empty */ + for ( i = 0 ; i < NS16550_TEMT_TIMEOUT_MS ; i++ ) { + lsr = ns16550_read ( ns16550, NS16550_LSR ); + if ( lsr & NS16550_LSR_TEMT ) + break; + } +} + +/** + * Initialise UART + * + * @v uart UART + * @ret rc Return status code + */ +static int ns16550_init ( struct uart *uart ) { + struct ns16550_uart *ns16550 = uart->priv; + uint8_t dlm; + uint8_t dll; + + /* Fail if UART scratch register seems not to be present */ + ns16550_write ( ns16550, NS16550_SCR, 0x18 ); + if ( ns16550_read ( ns16550, NS16550_SCR ) != 0x18 ) + return -ENODEV; + ns16550_write ( ns16550, NS16550_SCR, 0xae ); + if ( ns16550_read ( ns16550, NS16550_SCR ) != 0xae ) + return -ENODEV; + + /* Wait for UART to become idle before modifying LCR */ + ns16550_flush ( uart ); + + /* Configure divisor and line control register, if applicable */ + ns16550_write ( ns16550, NS16550_LCR, + ( NS16550_LCR_8N1 | NS16550_LCR_DLAB ) ); + if ( uart->baud ) { + ns16550->divisor = ( ( ns16550->clock / uart->baud ) / + NS16550_CLK_BIT ); + dlm = ( ( ns16550->divisor >> 8 ) & 0xff ); + dll = ( ( ns16550->divisor >> 0 ) & 0xff ); + ns16550_write ( ns16550, NS16550_DLM, dlm ); + ns16550_write ( ns16550, NS16550_DLL, dll ); + } else { + dlm = ns16550_read ( ns16550, NS16550_DLM ); + dll = ns16550_read ( ns16550, NS16550_DLL ); + ns16550->divisor = ( ( dlm << 8 ) | dll ); + } + ns16550_write ( ns16550, NS16550_LCR, NS16550_LCR_8N1 ); + + /* Disable interrupts */ + ns16550_write ( ns16550, NS16550_IER, 0 ); + + /* Enable FIFOs */ + ns16550_write ( ns16550, NS16550_FCR, NS16550_FCR_FE ); + + /* Assert DTR and RTS */ + ns16550_write ( ns16550, NS16550_MCR, + ( NS16550_MCR_DTR | NS16550_MCR_RTS ) ); + + /* Flush any stale received data */ + while ( ns16550_data_ready ( uart ) ) + ns16550_receive ( uart ); + + return 0; +} + +/** 16550 UART operations */ +struct uart_operations ns16550_operations = { + .transmit = ns16550_transmit, + .data_ready = ns16550_data_ready, + .receive = ns16550_receive, + .init = ns16550_init, + .flush = ns16550_flush, +}; |
