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