1 /*
2  * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 /** @file
27  *
28  * 16550-compatible UART
29  *
30  */
31 
32 #include <unistd.h>
33 #include <errno.h>
34 #include <ipxe/uart.h>
35 
36 /** Timeout for transmit holding register to become empty */
37 #define UART_THRE_TIMEOUT_MS 100
38 
39 /** Timeout for transmitter to become empty */
40 #define UART_TEMT_TIMEOUT_MS 1000
41 
42 /**
43  * Transmit data
44  *
45  * @v uart		UART
46  * @v data		Data
47  */
uart_transmit(struct uart * uart,uint8_t data)48 void uart_transmit ( struct uart *uart, uint8_t data ) {
49 	unsigned int i;
50 	uint8_t lsr;
51 
52 	/* Wait for transmitter holding register to become empty */
53 	for ( i = 0 ; i < UART_THRE_TIMEOUT_MS ; i++ ) {
54 		lsr = uart_read ( uart, UART_LSR );
55 		if ( lsr & UART_LSR_THRE )
56 			break;
57 		mdelay ( 1 );
58 	}
59 
60 	/* Transmit data (even if we timed out) */
61 	uart_write ( uart, UART_THR, data );
62 }
63 
64 /**
65  * Flush data
66  *
67  * @v uart		UART
68  */
uart_flush(struct uart * uart)69 void uart_flush ( struct uart *uart ) {
70 	unsigned int i;
71 	uint8_t lsr;
72 
73 	/* Wait for transmitter and receiver to become empty */
74 	for ( i = 0 ; i < UART_TEMT_TIMEOUT_MS ; i++ ) {
75 		uart_read ( uart, UART_RBR );
76 		lsr = uart_read ( uart, UART_LSR );
77 		if ( ( lsr & UART_LSR_TEMT ) && ! ( lsr & UART_LSR_DR ) )
78 			break;
79 	}
80 }
81 
82 /**
83  * Check for existence of UART
84  *
85  * @v uart		UART
86  * @ret rc		Return status code
87  */
uart_exists(struct uart * uart)88 int uart_exists ( struct uart *uart ) {
89 
90 	/* Fail if no UART port is defined */
91 	if ( ! uart->base )
92 		return -ENODEV;
93 
94 	/* Fail if UART scratch register seems not to be present */
95 	uart_write ( uart, UART_SCR, 0x18 );
96 	if ( uart_read ( uart, UART_SCR ) != 0x18 )
97 		return -ENODEV;
98 	uart_write ( uart, UART_SCR, 0xae );
99 	if ( uart_read ( uart, UART_SCR ) != 0xae )
100 		return -ENODEV;
101 
102 	return 0;
103 }
104 
105 /**
106  * Initialise UART
107  *
108  * @v uart		UART
109  * @v baud		Baud rate, or zero to leave unchanged
110  * @v lcr		Line control register value, or zero to leave unchanged
111  * @ret rc		Return status code
112  */
uart_init(struct uart * uart,unsigned int baud,uint8_t lcr)113 int uart_init ( struct uart *uart, unsigned int baud, uint8_t lcr ) {
114 	uint8_t dlm;
115 	uint8_t dll;
116 	int rc;
117 
118 	/* Check for existence of UART */
119 	if ( ( rc = uart_exists ( uart ) ) != 0 )
120 		return rc;
121 
122 	/* Configure divisor and line control register, if applicable */
123 	if ( ! lcr )
124 		lcr = uart_read ( uart, UART_LCR );
125 	uart->lcr = lcr;
126 	uart_write ( uart, UART_LCR, ( lcr | UART_LCR_DLAB ) );
127 	if ( baud ) {
128 		uart->divisor = ( UART_MAX_BAUD / baud );
129 		dlm = ( ( uart->divisor >> 8 ) & 0xff );
130 		dll = ( ( uart->divisor >> 0 ) & 0xff );
131 		uart_write ( uart, UART_DLM, dlm );
132 		uart_write ( uart, UART_DLL, dll );
133 	} else {
134 		dlm = uart_read ( uart, UART_DLM );
135 		dll = uart_read ( uart, UART_DLL );
136 		uart->divisor = ( ( dlm << 8 ) | dll );
137 	}
138 	uart_write ( uart, UART_LCR, ( lcr & ~UART_LCR_DLAB ) );
139 
140 	/* Disable interrupts */
141 	uart_write ( uart, UART_IER, 0 );
142 
143 	/* Enable FIFOs */
144 	uart_write ( uart, UART_FCR, UART_FCR_FE );
145 
146 	/* Assert DTR and RTS */
147 	uart_write ( uart, UART_MCR, ( UART_MCR_DTR | UART_MCR_RTS ) );
148 
149 	/* Flush any stale data */
150 	uart_flush ( uart );
151 
152 	return 0;
153 }
154