Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[serial] Add general abstraction of a 16550-compatible UART
Signed-off-by: Michael Brown <mcb30@ipxe.org>
- Loading branch information
Showing
6 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright (C) 2014 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 <errno.h> | ||
#include <ipxe/uart.h> | ||
|
||
/** UART port bases */ | ||
static uint16_t uart_base[] = { | ||
[COM1] = 0x3f8, | ||
[COM2] = 0x2f8, | ||
[COM3] = 0x3e8, | ||
[COM4] = 0x2e8, | ||
}; | ||
|
||
/** | ||
* Select UART port | ||
* | ||
* @v uart UART | ||
* @v port Port number, or 0 to disable | ||
* @ret rc Return status code | ||
*/ | ||
int uart_select ( struct uart *uart, unsigned int port ) { | ||
|
||
/* Clear UART base */ | ||
uart->base = NULL; | ||
|
||
/* Set new UART base */ | ||
if ( port < ( sizeof ( uart_base ) / sizeof ( uart_base[0] ) ) ) { | ||
uart->base = ( ( void * ) ( intptr_t ) uart_base[port] ); | ||
return 0; | ||
} else { | ||
return -ENODEV; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#ifndef _BITS_UART_H | ||
#define _BITS_UART_H | ||
|
||
/** @file | ||
* | ||
* 16550-compatible UART | ||
* | ||
*/ | ||
|
||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | ||
|
||
#include <stdint.h> | ||
#include <ipxe/io.h> | ||
|
||
/** | ||
* Write to UART register | ||
* | ||
* @v uart UART | ||
* @v addr Register address | ||
* @v data Data | ||
*/ | ||
static inline __attribute__ (( always_inline )) void | ||
uart_write ( struct uart *uart, unsigned int addr, uint8_t data ) { | ||
outb ( data, ( uart->base + addr ) ); | ||
} | ||
|
||
/** | ||
* Read from UART register | ||
* | ||
* @v uart UART | ||
* @v addr Register address | ||
* @ret data Data | ||
*/ | ||
static inline __attribute__ (( always_inline )) uint8_t | ||
uart_read ( struct uart *uart, unsigned int addr ) { | ||
return inb ( uart->base + addr ); | ||
} | ||
|
||
extern int uart_select ( struct uart *uart, unsigned int port ); | ||
|
||
#endif /* _BITS_UART_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* Copyright (C) 2014 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> | ||
|
||
/** Timeout for transmit holding register to become empty */ | ||
#define UART_THRE_TIMEOUT_MS 100 | ||
|
||
/** Timeout for transmitter to become empty */ | ||
#define UART_TEMT_TIMEOUT_MS 1000 | ||
|
||
/** | ||
* Transmit data | ||
* | ||
* @v uart UART | ||
* @v data Data | ||
*/ | ||
void uart_transmit ( struct uart *uart, uint8_t data ) { | ||
unsigned int i; | ||
uint8_t lsr; | ||
|
||
/* Wait for transmitter holding register to become empty */ | ||
for ( i = 0 ; i < UART_THRE_TIMEOUT_MS ; i++ ) { | ||
lsr = uart_read ( uart, UART_LSR ); | ||
if ( lsr & UART_LSR_THRE ) | ||
break; | ||
mdelay ( 1 ); | ||
} | ||
|
||
/* Transmit data (even if we timed out) */ | ||
uart_write ( uart, UART_THR, data ); | ||
} | ||
|
||
/** | ||
* Flush data | ||
* | ||
* @v uart UART | ||
*/ | ||
void uart_flush ( struct uart *uart ) { | ||
unsigned int i; | ||
uint8_t lsr; | ||
|
||
/* Wait for transmitter and receiver to become empty */ | ||
for ( i = 0 ; i < UART_TEMT_TIMEOUT_MS ; i++ ) { | ||
uart_read ( uart, UART_RBR ); | ||
lsr = uart_read ( uart, UART_LSR ); | ||
if ( ( lsr & UART_LSR_TEMT ) && ! ( lsr & UART_LSR_DR ) ) | ||
break; | ||
} | ||
} | ||
|
||
/** | ||
* Initialise UART | ||
* | ||
* @v uart UART | ||
* @v baud Baud rate, or zero to leave unchanged | ||
* @v lcr Line control register value, or zero to leave unchanged | ||
* @ret rc Return status code | ||
*/ | ||
int uart_init ( struct uart *uart, unsigned int baud, uint8_t lcr ) { | ||
uint8_t dlm; | ||
uint8_t dll; | ||
|
||
/* Check for existence of UART */ | ||
if ( ! uart->base ) | ||
return -ENODEV; | ||
uart_write ( uart, UART_SCR, 0x18 ); | ||
if ( uart_read ( uart, UART_SCR ) != 0x18 ) | ||
return -ENODEV; | ||
uart_write ( uart, UART_SCR, 0xae ); | ||
if ( uart_read ( uart, UART_SCR ) != 0xae ) | ||
return -ENODEV; | ||
|
||
/* Configure divisor and line control register, if applicable */ | ||
if ( ! lcr ) | ||
lcr = uart_read ( uart, UART_LCR ); | ||
uart->lcr = lcr; | ||
uart_write ( uart, UART_LCR, ( lcr | UART_LCR_DLAB ) ); | ||
if ( baud ) { | ||
uart->divisor = ( UART_MAX_BAUD / baud ); | ||
dlm = ( ( uart->divisor >> 8 ) & 0xff ); | ||
dll = ( ( uart->divisor >> 0 ) & 0xff ); | ||
uart_write ( uart, UART_DLM, dlm ); | ||
uart_write ( uart, UART_DLL, dll ); | ||
} else { | ||
dlm = uart_read ( uart, UART_DLM ); | ||
dll = uart_read ( uart, UART_DLL ); | ||
uart->divisor = ( ( dlm << 8 ) | dll ); | ||
} | ||
uart_write ( uart, UART_LCR, ( lcr & ~UART_LCR_DLAB ) ); | ||
|
||
/* Disable interrupts */ | ||
uart_write ( uart, UART_IER, 0 ); | ||
|
||
/* Enable FIFOs */ | ||
uart_write ( uart, UART_FCR, UART_FCR_FE ); | ||
|
||
/* Assert DTR and RTS */ | ||
uart_write ( uart, UART_MCR, ( UART_MCR_DTR | UART_MCR_RTS ) ); | ||
|
||
/* Flush any stale data */ | ||
uart_flush ( uart ); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
#ifndef _IPXE_UART_H | ||
#define _IPXE_UART_H | ||
|
||
/** @file | ||
* | ||
* 16550-compatible UART | ||
* | ||
*/ | ||
|
||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); | ||
|
||
#include <stdint.h> | ||
|
||
/** Transmitter holding register */ | ||
#define UART_THR 0x00 | ||
|
||
/** Receiver buffer register */ | ||
#define UART_RBR 0x00 | ||
|
||
/** Interrupt enable register */ | ||
#define UART_IER 0x01 | ||
|
||
/** FIFO control register */ | ||
#define UART_FCR 0x02 | ||
#define UART_FCR_FE 0x01 /**< FIFO enable */ | ||
|
||
/** Line control register */ | ||
#define UART_LCR 0x03 | ||
#define UART_LCR_WLS0 0x01 /**< Word length select bit 0 */ | ||
#define UART_LCR_WLS1 0x02 /**< Word length select bit 1 */ | ||
#define UART_LCR_STB 0x04 /**< Number of stop bits */ | ||
#define UART_LCR_PEN 0x08 /**< Parity enable */ | ||
#define UART_LCR_EPS 0x10 /**< Even parity select */ | ||
#define UART_LCR_DLAB 0x80 /**< Divisor latch access bit */ | ||
|
||
#define UART_LCR_WORD_LEN(x) ( ( (x) - 5 ) << 0 ) /**< Word length */ | ||
#define UART_LCR_STOP_BITS(x) ( ( (x) - 1 ) << 2 ) /**< Stop bits */ | ||
#define UART_LCR_PARITY(x) ( ( (x) - 0 ) << 3 ) /**< Parity */ | ||
|
||
/** | ||
* Calculate line control register value | ||
* | ||
* @v word_len Word length (5-8) | ||
* @v parity Parity (0=none, 1=odd, 3=even) | ||
* @v stop_bits Stop bits (1-2) | ||
* @ret lcr Line control register value | ||
*/ | ||
#define UART_LCR_WPS( word_len, parity, stop_bits ) \ | ||
( UART_LCR_WORD_LEN ( (word_len) ) | \ | ||
UART_LCR_PARITY ( (parity) ) | \ | ||
UART_LCR_STOP_BITS ( (stop_bits) ) ) | ||
|
||
/** Default LCR value: 8 data bits, no parity, one stop bit */ | ||
#define UART_LCR_8N1 UART_LCR_WPS ( 8, 0, 1 ) | ||
|
||
/** Modem control register */ | ||
#define UART_MCR 0x04 | ||
#define UART_MCR_DTR 0x01 /**< Data terminal ready */ | ||
#define UART_MCR_RTS 0x02 /**< Request to send */ | ||
|
||
/** Line status register */ | ||
#define UART_LSR 0x05 | ||
#define UART_LSR_DR 0x01 /**< Data ready */ | ||
#define UART_LSR_THRE 0x20 /**< Transmitter holding register empty */ | ||
#define UART_LSR_TEMT 0x40 /**< Transmitter empty */ | ||
|
||
/** Scratch register */ | ||
#define UART_SCR 0x07 | ||
|
||
/** Divisor latch (least significant byte) */ | ||
#define UART_DLL 0x00 | ||
|
||
/** Divisor latch (most significant byte) */ | ||
#define UART_DLM 0x01 | ||
|
||
/** Maximum baud rate */ | ||
#define UART_MAX_BAUD 115200 | ||
|
||
/** A 16550-compatible UART */ | ||
struct uart { | ||
/** I/O port base address */ | ||
void *base; | ||
/** Baud rate divisor */ | ||
uint16_t divisor; | ||
/** Line control register */ | ||
uint8_t lcr; | ||
}; | ||
|
||
/** Symbolic names for port indexes */ | ||
enum uart_port { | ||
COM1 = 1, | ||
COM2 = 2, | ||
COM3 = 3, | ||
COM4 = 4, | ||
}; | ||
|
||
#include <bits/uart.h> | ||
|
||
void uart_write ( struct uart *uart, unsigned int addr, uint8_t data ); | ||
uint8_t uart_read ( struct uart *uart, unsigned int addr ); | ||
int uart_select ( struct uart *uart, unsigned int port ); | ||
|
||
/** | ||
* Check if received data is ready | ||
* | ||
* @v uart UART | ||
* @ret ready Data is ready | ||
*/ | ||
static inline int uart_data_ready ( struct uart *uart ) { | ||
uint8_t lsr; | ||
|
||
lsr = uart_read ( uart, UART_LSR ); | ||
return ( lsr & UART_LSR_DR ); | ||
} | ||
|
||
/** | ||
* Receive data | ||
* | ||
* @v uart UART | ||
* @ret data Data | ||
*/ | ||
static inline uint8_t uart_receive ( struct uart *uart ) { | ||
|
||
return uart_read ( uart, UART_RBR ); | ||
} | ||
|
||
extern void uart_transmit ( struct uart *uart, uint8_t data ); | ||
extern void uart_flush ( struct uart *uart ); | ||
extern int uart_init ( struct uart *uart, unsigned int baud, uint8_t lcr ); | ||
|
||
#endif /* _IPXE_UART_H */ |