1 /******************************************************************************
2 * *
3 * License Agreement *
4 * *
5 * Copyright (c) 2009 Altera Corporation, San Jose, California, USA. *
6 * All rights reserved. *
7 * *
8 * Permission is hereby granted, free of charge, to any person obtaining a *
9 * copy of this software and associated documentation files (the "Software"), *
10 * to deal in the Software without restriction, including without limitation *
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
12 * and/or sell copies of the Software, and to permit persons to whom the *
13 * Software is furnished to do so, subject to the following conditions: *
14 * *
15 * The above copyright notice and this permission notice shall be included in *
16 * all copies or substantial portions of the Software. *
17 * *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
24 * DEALINGS IN THE SOFTWARE. *
25 * *
26 * *
27 ******************************************************************************/
28
29 #include <fcntl.h>
30
31 #include "sys/alt_dev.h"
32 #include "sys/alt_irq.h"
33 #include "sys/ioctl.h"
34 #include "sys/alt_errno.h"
35
36 #include "altera_avalon_uart.h"
37 #include "altera_avalon_uart_regs.h"
38
39 #if !defined(ALT_USE_SMALL_DRIVERS) && !defined(ALTERA_AVALON_UART_SMALL)
40
41 /* ----------------------------------------------------------- */
42 /* ------------------------- FAST DRIVER --------------------- */
43 /* ----------------------------------------------------------- */
44
45 /*
46 * altera_avalon_uart_init() is called by the auto-generated function
47 * alt_sys_init() in order to initialize a particular instance of this device.
48 * It is responsible for configuring the device and associated software
49 * constructs.
50 */
51
52 #ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT
53 static void altera_avalon_uart_irq(void* context);
54 #else
55 static void altera_avalon_uart_irq(void* context, alt_u32 id);
56 #endif
57
58 static void altera_avalon_uart_rxirq(altera_avalon_uart_state* sp,
59 alt_u32 status);
60 static void altera_avalon_uart_txirq(altera_avalon_uart_state* sp,
61 alt_u32 status);
62
63 void
altera_avalon_uart_init(altera_avalon_uart_state * sp,alt_u32 irq_controller_id,alt_u32 irq)64 altera_avalon_uart_init(altera_avalon_uart_state* sp,
65 alt_u32 irq_controller_id, alt_u32 irq)
66 {
67 void* base = sp->base;
68 int error;
69
70 /*
71 * Initialise the read and write flags and the semaphores used to
72 * protect access to the circular buffers when running in a multi-threaded
73 * environment.
74 */
75 error = ALT_FLAG_CREATE (&sp->events, 0) ||
76 ALT_SEM_CREATE (&sp->read_lock, 1) ||
77 ALT_SEM_CREATE (&sp->write_lock, 1);
78
79 if (!error)
80 {
81 /* enable interrupts at the device */
82 sp->ctrl = ALTERA_AVALON_UART_CONTROL_RTS_MSK |
83 ALTERA_AVALON_UART_CONTROL_RRDY_MSK |
84 ALTERA_AVALON_UART_CONTROL_DCTS_MSK;
85
86 IOWR_ALTERA_AVALON_UART_CONTROL(base, sp->ctrl);
87
88 /* register the interrupt handler */
89 #ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT
90 alt_ic_isr_register(irq_controller_id, irq, altera_avalon_uart_irq, sp,
91 0x0);
92 #else
93 alt_irq_register (irq, sp, altera_avalon_uart_irq);
94 #endif
95 }
96 }
97
98 /*
99 * altera_avalon_uart_irq() is the interrupt handler registered at
100 * configuration time for processing UART interrupts. It vectors
101 * interrupt requests to either altera_avalon_uart_rxirq() (for incoming
102 * data), or altera_avalon_uart_txirq() (for outgoing data).
103 */
104 #ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT
altera_avalon_uart_irq(void * context)105 static void altera_avalon_uart_irq(void* context)
106 #else
107 static void altera_avalon_uart_irq(void* context, alt_u32 id)
108 #endif
109 {
110 alt_u32 status;
111
112 altera_avalon_uart_state* sp = (altera_avalon_uart_state*) context;
113 void* base = sp->base;
114
115 /*
116 * Read the status register in order to determine the cause of the
117 * interrupt.
118 */
119
120 status = IORD_ALTERA_AVALON_UART_STATUS(base);
121
122 /* Clear any error flags set at the device */
123 IOWR_ALTERA_AVALON_UART_STATUS(base, 0);
124
125 /* Dummy read to ensure IRQ is negated before ISR returns */
126 IORD_ALTERA_AVALON_UART_STATUS(base);
127
128 /* process a read irq */
129 if (status & ALTERA_AVALON_UART_STATUS_RRDY_MSK)
130 {
131 altera_avalon_uart_rxirq(sp, status);
132 }
133
134 /* process a write irq */
135 if (status & (ALTERA_AVALON_UART_STATUS_TRDY_MSK |
136 ALTERA_AVALON_UART_STATUS_DCTS_MSK))
137 {
138 altera_avalon_uart_txirq(sp, status);
139 }
140
141
142 }
143
144 /*
145 * altera_avalon_uart_rxirq() is called by altera_avalon_uart_irq() to
146 * process a receive interrupt. It transfers the incoming character into
147 * the receive circular buffer, and sets the apropriate flags to indicate
148 * that there is data ready to be processed.
149 */
150 static void
altera_avalon_uart_rxirq(altera_avalon_uart_state * sp,alt_u32 status)151 altera_avalon_uart_rxirq(altera_avalon_uart_state* sp, alt_u32 status)
152 {
153 alt_u32 next;
154
155 /* If there was an error, discard the data */
156
157 if (status & (ALTERA_AVALON_UART_STATUS_PE_MSK |
158 ALTERA_AVALON_UART_STATUS_FE_MSK))
159 {
160 return;
161 }
162
163 /*
164 * In a multi-threaded environment, set the read event flag to indicate
165 * that there is data ready. This is only done if the circular buffer was
166 * previously empty.
167 */
168
169 if (sp->rx_end == sp->rx_start)
170 {
171 ALT_FLAG_POST (sp->events, ALT_UART_READ_RDY, OS_FLAG_SET);
172 }
173
174 /* Determine which slot to use next in the circular buffer */
175
176 next = (sp->rx_end + 1) & ALT_AVALON_UART_BUF_MSK;
177
178 /* Transfer data from the device to the circular buffer */
179
180 sp->rx_buf[sp->rx_end] = IORD_ALTERA_AVALON_UART_RXDATA(sp->base);
181
182 sp->rx_end = next;
183
184 next = (sp->rx_end + 1) & ALT_AVALON_UART_BUF_MSK;
185
186 /*
187 * If the cicular buffer was full, disable interrupts. Interrupts will be
188 * re-enabled when data is removed from the buffer.
189 */
190
191 if (next == sp->rx_start)
192 {
193 sp->ctrl &= ~ALTERA_AVALON_UART_CONTROL_RRDY_MSK;
194 IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
195 }
196 }
197
198 /*
199 * altera_avalon_uart_txirq() is called by altera_avalon_uart_irq() to
200 * process a transmit interrupt. It transfers data from the transmit
201 * buffer to the device, and sets the apropriate flags to indicate that
202 * there is data ready to be processed.
203 */
204 static void
altera_avalon_uart_txirq(altera_avalon_uart_state * sp,alt_u32 status)205 altera_avalon_uart_txirq(altera_avalon_uart_state* sp, alt_u32 status)
206 {
207 /* Transfer data if there is some ready to be transfered */
208
209 if (sp->tx_start != sp->tx_end)
210 {
211 /*
212 * If the device is using flow control (i.e. RTS/CTS), then the
213 * transmitter is required to throttle if CTS is high.
214 */
215
216 if (!(sp->flags & ALT_AVALON_UART_FC) ||
217 (status & ALTERA_AVALON_UART_STATUS_CTS_MSK))
218 {
219
220 /*
221 * In a multi-threaded environment, set the write event flag to indicate
222 * that there is space in the circular buffer. This is only done if the
223 * buffer was previously empty.
224 */
225
226 if (sp->tx_start == ((sp->tx_end + 1) & ALT_AVALON_UART_BUF_MSK))
227 {
228 ALT_FLAG_POST (sp->events,
229 ALT_UART_WRITE_RDY,
230 OS_FLAG_SET);
231 }
232
233 /* Write the data to the device */
234
235 IOWR_ALTERA_AVALON_UART_TXDATA(sp->base, sp->tx_buf[sp->tx_start]);
236
237 sp->tx_start = (++sp->tx_start) & ALT_AVALON_UART_BUF_MSK;
238
239 /*
240 * In case the tranmit interrupt had previously been disabled by
241 * detecting a low value on CTS, it is reenabled here.
242 */
243
244 sp->ctrl |= ALTERA_AVALON_UART_CONTROL_TRDY_MSK;
245 }
246 else
247 {
248 /*
249 * CTS is low and we are using flow control, so disable the transmit
250 * interrupt while we wait for CTS to go high again. This will be
251 * detected using the DCTS interrupt.
252 *
253 * There is a race condition here. "status" may indicate that
254 * CTS is low, but it actually went high before DCTS was cleared on
255 * the last write to the status register. To avoid this resulting in
256 * deadlock, it's necessary to re-check the status register here
257 * before throttling.
258 */
259
260 status = IORD_ALTERA_AVALON_UART_STATUS(sp->base);
261
262 if (!(status & ALTERA_AVALON_UART_STATUS_CTS_MSK))
263 {
264 sp->ctrl &= ~ALTERA_AVALON_UART_CONTROL_TRDY_MSK;
265 }
266 }
267 }
268
269 /*
270 * If the circular buffer is empty, disable the interrupt. This will be
271 * re-enabled when new data is placed in the buffer.
272 */
273
274 if (sp->tx_start == sp->tx_end)
275 {
276 sp->ctrl &= ~(ALTERA_AVALON_UART_CONTROL_TRDY_MSK |
277 ALTERA_AVALON_UART_CONTROL_DCTS_MSK);
278 }
279
280 IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
281 }
282
283 /*
284 * The close() routine is implemented to drain the UART transmit buffer
285 * when not in "small" mode. This routine will wait for transimt data to be
286 * emptied unless the driver flags have been set to non-blocking mode.
287 * This routine should be called indirectly (i.e. though the C library
288 * close() routine) so that the file descriptor associated with the relevant
289 * stream (i.e. stdout) can be closed as well. This routine does not manage
290 * file descriptors.
291 *
292 * The close routine is not implemented for the small driver; instead it will
293 * map to null. This is because the small driver simply waits while characters
294 * are transmitted; there is no interrupt-serviced buffer to empty
295 */
altera_avalon_uart_close(altera_avalon_uart_state * sp,int flags)296 int altera_avalon_uart_close(altera_avalon_uart_state* sp, int flags)
297 {
298 /*
299 * Wait for all transmit data to be emptied by the UART ISR.
300 */
301 while (sp->tx_start != sp->tx_end) {
302 if (flags & O_NONBLOCK) {
303 return -EWOULDBLOCK;
304 }
305 }
306
307 return 0;
308 }
309
310 #endif /* fast driver */
311