Fix spacing
[bertos.git] / drv / ser_avr.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2000 Bernardo Innocenti <bernie@codewiz.org>
5  * Copyright 2003,2004 Develer S.r.l. (http://www.develer.com/)
6  * This file is part of DevLib - See devlib/README for information.
7  * -->
8  *
9  * \brief AVR UART and SPI I/O driver
10  *
11  * Rationale for project_ks hardware.
12  *
13  * The serial 0 on the board_kf board is used to communicate with the
14  * smart card, which has the TX and RX lines connected together. To
15  * allow the smart card to drive the RX line of the CPU the CPU TX has
16  * to be in a high impedance state.
17  * Whenever a transmission is done and there is nothing more to send
18  * the transmitter is turn off. The output pin is held in input with
19  * pull-up enabled, to avoid capturing noise from the nearby RX line.
20  *
21  * The line on the KBus port must keep sending data, even when
22  * there is nothing to transmit, because a burst data transfer
23  * generates noise on the audio channels.
24  * This is accomplished using the multiprocessor mode of the
25  * ATmega64/128 serial.
26  *
27  * The receiver keeps the MPCM bit always on. When useful data
28  * is trasmitted the address bit is set. The receiver hardware
29  * consider the frame as address info and receive it.
30  * When useless fill bytes are sent the address bit is cleared
31  * and the receiver will ignore them, avoiding useless triggering
32  * of RXC interrupt.
33  *
34  * \version $Id$
35  * \author Bernardo Innocenti <bernie@develer.com>
36  * \author Stefano Fedrigo <aleph@develer.com>
37  */
38
39 /*
40  * $Log$
41  * Revision 1.9  2004/08/02 20:20:29  aleph
42  * Merge from project_ks
43  *
44  * Revision 1.8  2004/07/29 22:57:09  bernie
45  * Several tweaks to reduce code size on ATmega8.
46  *
47  * Revision 1.7  2004/07/18 21:54:23  bernie
48  * Add ATmega8 support.
49  *
50  * Revision 1.5  2004/06/27 15:25:40  aleph
51  * Add missing callbacks for SPI;
52  * Change UNUSED() macro to new version with two args;
53  * Use TX line filling only on the correct KBUS serial port;
54  * Fix nasty IRQ disabling bug in recv complete hander for port 1.
55  *
56  * Revision 1.4  2004/06/03 11:27:09  bernie
57  * Add dual-license information.
58  *
59  * Revision 1.3  2004/06/02 21:35:24  aleph
60  * Serial enhancements: interruptible receive handler and 8 bit serial status for AVR; remove volatile attribute to FIFOBuffer, useless for new fifobuf routens
61  *
62  * Revision 1.2  2004/05/23 18:21:53  bernie
63  * Trim CVS logs and cleanup header info.
64  *
65  */
66
67 #include "ser.h"
68 #include "ser_p.h"
69 #include "kdebug.h"
70 #include "config.h"
71 #include "hw.h"
72 #include <mware/fifobuf.h>
73
74 #include <avr/signal.h>
75
76
77 /* Hardware handshake (RTS/CTS).  */
78 #ifndef RTS_ON
79 #define RTS_ON      do {} while (0)
80 #endif
81 #ifndef RTS_OFF
82 #define RTS_OFF     do {} while (0)
83 #endif
84 #ifndef IS_CTS_ON
85 #define IS_CTS_ON   true
86 #endif
87
88 /* External 485 transceiver on UART0 (to be overridden in "hw.h").  */
89 #if !defined(SER_UART0_485_INIT)
90         #if defined(SER_UART0_485_RX) || defined(SER_UART0_485_TX)
91                 #error SER_UART0_485_INIT, SER_UART0_485_RX and SER_UART0_485_TX must be defined together
92         #endif
93         #define SER_UART0_485_INIT  do {} while (0)
94         #define SER_UART0_485_TX    do {} while (0)
95         /* SER_UART0_485_RX must not be defined! */
96 #elif !defined(SER_UART0_485_RX) || !defined(SER_UART0_485_TX)
97         #error SER_UART0_485_INIT, SER_UART0_485_RX and SER_UART0_485_TX must be defined together
98 #endif
99
100 /* External 485 transceiver on UART1 (to be overridden in "hw.h").  */
101 #ifndef SER_UART1_485_INIT
102         #if defined(SER_UART1_485_RX) || defined(SER_UART1_485_TX)
103                 #error SER_UART1_485_INIT, SER_UART1_485_RX and SER_UART1_485_TX must be defined together
104         #endif
105         #define SER_UART1_485_INIT  do {} while (0)
106         #define SER_UART1_485_TX    do {} while (0)
107         /* SER_UART1_485_RX must not be defined! */
108 #elif !defined(SER_UART1_485_RX) || !defined(SER_UART1_485_TX)
109         #error SER_UART1_485_INIT, SER_UART1_485_RX and SER_UART1_485_TX must be defined together
110 #endif
111
112
113 /* SPI port and pin configuration */
114 #define SPI_PORT      PORTB
115 #define SPI_DDR       DDRB
116 #define SPI_SCK_BIT   PORTB1
117 #define SPI_MOSI_BIT  PORTB2
118 #define SPI_MISO_BIT  PORTB3
119
120
121 #if defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__)
122         #define AVR_HAS_UART1 1
123 #elif defined(__AVR_ATmega8__)
124         #define AVR_HAS_UART1 0
125         #define UCSR0A UCSRA
126         #define UCSR0B UCSRB
127         #define UCSR0C UCSRC
128         #define UDR0   UDR
129         #define UBRR0L UBRRL
130         #define UBRR0H UBRRH
131         #define SIG_UART0_DATA SIG_UART_DATA
132         #define SIG_UART0_RECV SIG_UART_RECV
133 #elif defined(__AVR_ATmega103__)
134         #define AVR_HAS_UART1 0
135         #define UCSR0B UCR
136         #define UDR0   UDR
137         #define UCSR0A USR
138         #define UBRR0L UBRR
139         #define SIG_UART0_DATA SIG_UART_DATA
140         #define SIG_UART0_RECV SIG_UART_RECV
141 #else
142         #error Unknown architecture
143 #endif
144
145
146 /* Transmission fill byte */
147 #define SER_FILL_BYTE 0xAA
148
149
150 /* From the high-level serial driver */
151 extern struct Serial ser_handles[SER_CNT];
152
153 /*
154  * These are to trick GCC into *not* using
155  * absolute addressing mode when accessing
156  * ser_handles, which is very expensive.
157  *
158  * Accessing through these pointers generates
159  * much shorter (and hopefully faster) code.
160  */
161 struct Serial *ser_uart0 = &ser_handles[SER_UART0];
162 #if AVR_HAS_UART1
163 struct Serial *ser_uart1 = &ser_handles[SER_UART1];
164 #endif
165 struct Serial *ser_spi = &ser_handles[SER_SPI];
166
167
168 static void uart0_enabletxirq(UNUSED(struct SerialHardware *, ctx))
169 {
170 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 0)
171         UCSR0B = BV(RXCIE) | BV(UDRIE) | BV(RXEN) | BV(TXEN) | BV(UCSZ2);
172 #elif defined(SER_UART0_485_TX)
173         /* Disable receiver, enable transmitter, switch 485 transceiver. */
174         UCSR0B = BV(UDRIE) | BV(TXEN);
175         SER_UART0_485_TX;
176 #else
177         UCSR0B = BV(RXCIE) | BV(UDRIE) | BV(RXEN) | BV(TXEN);
178 #endif
179 }
180
181 static void uart0_init(UNUSED(struct SerialHardware *, _hw), UNUSED(struct Serial *, ser))
182 {
183 #if defined(ARCH_BOARD_KS) && (ARCH & ARCH_BOARD_KS)
184         /* Set TX port as input with pull-up enabled to avoid
185            noise on the remote RX when TX is disabled. */
186         cpuflags_t flags;
187         DISABLE_IRQSAVE(flags);
188         DDRE &= ~BV(PORTE1);
189         PORTE |= BV(PORTE1);
190         ENABLE_IRQRESTORE(flags);
191 #endif /* ARCH_BOARD_KS */
192
193 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 0)
194         /*!
195          * Set multiprocessor mode and 9 bit data frame.
196          * The receiver keep MPCM bit always on. When useful data
197          * is trasmitted the ninth bit is set and the receiver receive
198          * the frame.
199          * When useless fill bytes are sent the ninth bit is cleared
200          * and the receiver will ignore them.
201          */
202         UCSR0A = BV(MPCM);
203         UCSR0B = BV(RXCIE) | BV(RXEN) | BV(UCSZ2);
204 #else
205         UCSR0B = BV(RXCIE) | BV(RXEN);
206 #endif
207
208         SER_UART0_485_INIT;
209         RTS_ON;
210 }
211
212 static void uart0_cleanup(UNUSED(struct SerialHardware *, _hw))
213 {
214         UCSR0B = 0;
215 }
216
217 static void uart0_setbaudrate(UNUSED(struct SerialHardware *, _hw), unsigned long rate)
218 {
219         /* Compute baud-rate period */
220         uint16_t period = (((CLOCK_FREQ / 16UL) + (rate / 2)) / rate) - 1;
221
222 #ifndef __AVR_ATmega103__
223         UBRR0H = (period) >> 8;
224 #endif
225         UBRR0L = (period);
226
227         DB(kprintf("uart0_setbaudrate(rate=%lu): period=%d\n", rate, period);)
228 }
229
230 static void uart0_setparity(UNUSED(struct SerialHardware *, _hw), int parity)
231 {
232 #ifndef __AVR_ATmega103__
233         UCSR0C |= (parity) << UPM0;
234 #endif
235 }
236
237 #if AVR_HAS_UART1
238
239 static void uart1_enabletxirq(UNUSED(struct SerialHardware *, _hw))
240 {
241 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 1)
242         UCSR1B = BV(RXCIE) | BV(UDRIE) | BV(RXEN) | BV(TXEN) | BV(UCSZ2);
243 #elif defined(SER_UART1_485_TX)
244         /* Disable receiver, enable transmitter, switch 485 transceiver. */
245         UCSR1B = BV(UDRIE) | BV(TXEN);
246         SER_UART1_485_TX;
247 #else
248         UCSR1B = BV(RXCIE) | BV(UDRIE) | BV(RXEN) | BV(TXEN);
249 #endif
250 }
251
252 static void uart1_init(UNUSED(struct SerialHardware *, _hw), UNUSED(struct Serial *, ser))
253 {
254         /* Set TX port as input with pull-up enabled to avoid
255          * noise on the remote RX when TX is disabled */
256         cpuflags_t flags;
257         DISABLE_IRQSAVE(flags);
258         DDRD &= ~BV(PORTD3);
259         PORTD |= BV(PORTD3);
260         ENABLE_IRQRESTORE(flags);
261
262 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 1)
263         /*! See comment in uart0_init() */
264         UCSR1A = BV(MPCM);
265         UCSR1B = BV(RXCIE) | BV(RXEN) | BV(UCSZ2);
266 #else
267         UCSR1B = BV(RXCIE) | BV(RXEN);
268 #endif
269         SER_UART1_485_INIT;
270         RTS_ON;
271 }
272
273 static void uart1_cleanup(UNUSED(struct SerialHardware *, _hw))
274 {
275         UCSR1B = 0;
276 }
277
278 static void uart1_setbaudrate(UNUSED(struct SerialHardware *, _hw), unsigned long rate)
279 {
280         /* Compute baud-rate period */
281         uint16_t period = (((CLOCK_FREQ / 16UL) + (rate / 2)) / rate) - 1;
282
283         UBRR1H = (period) >> 8;
284         UBRR1L = (period);
285
286         DB(kprintf("uart1_setbaudrate(rate=%ld): period=%d\n", rate, period);)
287 }
288
289 static void uart1_setparity(UNUSED(struct SerialHardware *, _hw), int parity)
290 {
291         UCSR1C |= (parity) << UPM0;
292 }
293
294 #endif // AVR_HAS_UART1
295
296
297 static void spi_init(UNUSED(struct SerialHardware *, _hw), UNUSED(struct Serial *, ser))
298 {
299         /*
300          * Set MOSI and SCK ports out, MISO in.
301          *
302          * The ATmega64/128 datasheet explicitly states that the input/output
303          * state of the SPI pins is not significant, as when the SPI is
304          * active the I/O port are overrided.
305          * This is *blatantly FALSE*.
306          *
307          * Moreover, the MISO pin on the board_kc *must* be in high impedance
308          * state even when the SPI is off, because the line is wired together
309          * with the KBus serial RX, and the transmitter of the slave boards
310          * would be unable to drive the line.
311          */
312         SPI_DDR |= BV(SPI_MOSI_BIT) | BV(SPI_SCK_BIT);
313         SPI_DDR &= ~BV(SPI_MISO_BIT);
314         /* Enable SPI, IRQ on, Master, CPU_CLOCK/16 */
315         SPCR = BV(SPE) | BV(SPIE) | BV(MSTR) | BV(SPR0);
316 }
317
318 static void spi_cleanup(UNUSED(struct SerialHardware *, _hw))
319 {
320         SPCR = 0;
321         /* Set all pins as inputs */
322         SPI_DDR &= ~(BV(SPI_MISO_BIT) | BV(SPI_MOSI_BIT) | BV(SPI_SCK_BIT));
323 }
324
325 static void spi_setbaudrate(UNUSED(struct SerialHardware *, _hw), UNUSED(unsigned long, rate))
326 {
327         // nop
328 }
329
330 static void spi_setparity(UNUSED(struct SerialHardware *, _hw), UNUSED(int, parity))
331 {
332         // nop
333 }
334
335
336 #if CONFIG_SER_HWHANDSHAKE
337
338 //! This interrupt is triggered when the CTS line goes high
339 SIGNAL(SIG_CTS)
340 {
341         // Re-enable UDR empty interrupt and TX, then disable CTS interrupt
342         UCSR0B = BV(RXCIE) | BV(UDRIE) | BV(RXEN) | BV(TXEN);
343         cbi(EIMSK, EIMSKB_CTS);
344 }
345
346 #endif // CONFIG_SER_HWHANDSHAKE
347
348
349 /*!
350  * Serial 0 TX interrupt handler
351  */
352 SIGNAL(SIG_UART0_DATA)
353 {
354         struct FIFOBuffer * const txfifo = &ser_uart0->txfifo;
355
356         if (fifo_isempty(txfifo))
357         {
358 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 0)
359                 /*
360                  * To avoid audio interference: always transmit useless char.
361                  * Send the byte with the ninth bit cleared, the receiver in MCPM mode
362                  * will ignore it.
363                  */
364                 UCSR0B &= ~BV(TXB8);
365                 UDR0 = SER_FILL_BYTE;
366 #elif defined(SER_UART0_485_RX)
367                 /*
368                  * - Disable UDR empty interrupt
369                  * - Disable the transmitter (the in-progress transfer will complete)
370                  * - Enable the transmit complete interrupt for the 485 tranceiver.
371                  */
372                 UCSR0B = BV(TXCIE);
373 #else
374                 /* Disable UDR empty interrupt and transmitter */
375                 UCSR0B = BV(RXCIE) | BV(RXEN);
376 #endif
377         }
378 #if CONFIG_SER_HWHANDSHAKE
379         else if (!IS_CTS_ON)
380         {
381                 // Disable rx interrupt and tx, enable CTS interrupt
382                 UCSR0B = BV(RXCIE) | BV(RXEN);
383                 sbi(EIFR, EIMSKB_CTS);
384                 sbi(EIMSK, EIMSKB_CTS);
385         }
386 #endif // CONFIG_SER_HWHANDSHAKE
387         else
388         {
389 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 0)
390                 /* Send with ninth bit set. Receiver in MCPM mode will receive it */
391                 UCSR0B |= BV(TXB8);
392 #endif
393                 UDR0 = fifo_pop(txfifo);
394         }
395 }
396
397 #ifdef SER_UART0_485_RX
398 /*!
399  * Serial port 0 TX complete interrupt handler.
400  *
401  * This IRQ is usually disabled.  The UDR-empty interrupt
402  * enables it when there's no more data to transmit.
403  * We need to wait until the last character has been
404  * transmitted before switching the 485 transceiver to
405  * receive mode.
406  */
407 SIGNAL(SIG_UART0_TRANS)
408 {
409         /* Turn the 485 tranceiver into receive mode. */
410         SER_UART0_485_RX;
411
412         /* Enable UART receiver and receive interrupt. */
413         UCSR0B = BV(RXCIE) | BV(RXEN);
414 }
415 #endif /* SER_UART0_485_RX */
416
417
418 #if AVR_HAS_UART1
419
420 /*!
421  * Serial 1 TX interrupt handler
422  */
423 SIGNAL(SIG_UART1_DATA)
424 {
425         struct FIFOBuffer * const txfifo = &ser_uart1->txfifo;
426
427         if (fifo_isempty(txfifo))
428         {
429 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 1)
430                 /*
431                  * To avoid audio interference: always transmit useless char.
432                  * Send the byte with the ninth bit cleared, the receiver in MCPM mode
433                  * will ignore it.
434                  */
435                 UCSR1B &= ~BV(TXB8);
436                 UDR1 = SER_FILL_BYTE;
437 #elif defined(SER_UART1_485_RX)
438                 /*
439                  * - Disable UDR empty interrupt
440                  * - Disable the transmitter (the in-progress transfer will complete)
441                  * - Enable the transmit complete interrupt for the 485 tranceiver.
442                  */
443                 UCSR1B = BV(TXCIE);
444 #else
445                 /* Disable UDR empty interrupt and transmitter */
446                 UCSR1B = BV(RXCIE) | BV(RXEN);
447 #endif
448         }
449 #if CONFIG_SER_HWHANDSHAKE
450         else if (IS_CTS_OFF)
451         {
452                 // Disable rx interrupt and tx, enable CTS interrupt
453                 UCSR1B = BV(RXCIE) | BV(RXEN);
454                 sbi(EIFR, EIMSKB_CTS);
455                 sbi(EIMSK, EIMSKB_CTS);
456         }
457 #endif // CONFIG_SER_HWHANDSHAKE
458         else
459         {
460 #if CONFIG_SER_TXFILL && (CONFIG_KBUS_PORT == 1)
461                 /* Send with ninth bit set. Receiver in MCPM mode will receive it */
462                 UCSR1B |= BV(TXB8);
463 #endif
464                 UDR1 = fifo_pop(txfifo);
465         }
466 }
467
468 #ifdef SER_UART1_485_RX
469 /*!
470  * Serial port 1 TX complete interrupt handler.
471  *
472  * \sa port 0 TX complete handler.
473  */
474 SIGNAL(SIG_UART1_TRANS)
475 {
476         /* Turn the 485 tranceiver into receive mode. */
477         SER_UART1_485_RX;
478
479         /* Enable UART receiver and receive interrupt. */
480         UCSR1B = BV(RXCIE) | BV(RXEN);
481 }
482 #endif /* SER_UART1_485_RX */
483
484 #endif // AVR_HAS_UART1
485
486
487 /*!
488  * Serial 0 RX complete interrupt handler.
489  *
490  * This handler is interruptible.
491  * Interrupt are reenabled as soon as recv complete interrupt is
492  * disabled. Using INTERRUPT() is troublesome when the serial
493  * is heavily loaded, because an interrupt could be retriggered
494  * when executing the handler prologue before RXCIE is disabled.
495  */
496 SIGNAL(SIG_UART0_RECV)
497 {
498         /* Disable Recv complete IRQ */
499         UCSR0B &= ~BV(RXCIE);
500         ENABLE_INTS;
501
502         /* Should be read before UDR */
503         ser_uart0->status |= UCSR0A & (SERRF_RXSROVERRUN | SERRF_FRAMEERROR);
504
505         /* To clear the RXC flag we must _always_ read the UDR even when we're
506          * not going to accept the incoming data, otherwise a new interrupt
507          * will occur once the handler terminates.
508          */
509         char c = UDR0;
510         struct FIFOBuffer * const rxfifo = &ser_uart0->rxfifo;
511
512         if (fifo_isfull(rxfifo))
513                 ser_uart0->status |= SERRF_RXFIFOOVERRUN;
514         else
515         {
516                 fifo_push(rxfifo, c);
517 #if CONFIG_SER_HWHANDSHAKE
518                 if (fifo_isfull(rxfifo))
519                         RTS_OFF;
520 #endif
521         }
522
523         /* Reenable receive complete int */
524         UCSR0B |= BV(RXCIE);
525 }
526
527
528 #if AVR_HAS_UART1
529
530 /*!
531  * Serial 1 RX complete interrupt handler.
532  *
533  * This handler is interruptible.
534  * Interrupt are reenabled as soon as recv complete interrupt is
535  * disabled. Using INTERRUPT() is troublesome when the serial
536  * is heavily loaded, because an interrupt could be retriggered
537  * when executing the handler prologue before RXCIE is disabled.
538  */
539 SIGNAL(SIG_UART1_RECV)
540 {
541         /* Disable Recv complete IRQ */
542         UCSR1B &= ~BV(RXCIE);
543         ENABLE_INTS;
544
545         /* Should be read before UDR */
546         ser_uart1->status |= UCSR1A & (SERRF_RXSROVERRUN | SERRF_FRAMEERROR);
547
548         /* To avoid an IRQ storm, we must _always_ read the UDR even when we're
549          * not going to accept the incoming data
550          */
551         char c = UDR1;
552         struct FIFOBuffer * const rxfifo = &ser_uart1->rxfifo;
553
554         if (fifo_isfull(rxfifo))
555                 ser_uart1->status |= SERRF_RXFIFOOVERRUN;
556         else
557         {
558                 fifo_push(rxfifo, c);
559 #if CONFIG_SER_HWHANDSHAKE
560                 if (fifo_isfull(rxfifo))
561                         RTS_OFF;
562 #endif
563         }
564         /* Reenable receive complete int */
565         UCSR1B |= BV(RXCIE);
566 }
567
568 #endif // AVR_HAS_UART1
569
570
571 /*
572  * SPI Flag: true if we are transmitting/receiving with the SPI.
573  *
574  * This kludge is necessary because the SPI sends and receives bytes
575  * at the same time and the SPI IRQ is unique for send/receive.
576  * The only way to start transmission is to write data in SPDR (this
577  * is done by spi_starttx()). We do this *only* if a transfer is
578  * not already started.
579  */
580 static volatile bool spi_sending = false;
581
582 static void spi_starttx(UNUSED(struct SerialHardware *, ctx))
583 {
584         cpuflags_t flags;
585
586         DISABLE_IRQSAVE(flags);
587
588         /* Send data only if the SPI is not already transmitting */
589         if (!spi_sending && !fifo_isempty(&ser_spi->txfifo))
590         {
591                 SPDR = fifo_pop(&ser_spi->txfifo);
592                 spi_sending = true;
593         }
594
595         ENABLE_IRQRESTORE(flags);
596 }
597
598 /*!
599  * SPI interrupt handler
600  */
601 SIGNAL(SIG_SPI)
602 {
603         /* Read incoming byte. */
604         if (!fifo_isfull(&ser_spi->rxfifo))
605                 fifo_push(&ser_spi->rxfifo, SPDR);
606         /*
607          * FIXME
608         else
609                 ser_spi->status |= SERRF_RXFIFOOVERRUN;
610         */
611
612         /* Send */
613         if (!fifo_isempty(&ser_spi->txfifo))
614                 SPDR = fifo_pop(&ser_spi->txfifo);
615         else
616                 spi_sending = false;
617 }
618
619
620 static const struct SerialHardwareVT UART0_VT =
621 {
622         .init = uart0_init,
623         .cleanup = uart0_cleanup,
624         .setbaudrate = uart0_setbaudrate,
625         .setparity = uart0_setparity,
626         .enabletxirq = uart0_enabletxirq,
627 };
628
629 #if AVR_HAS_UART1
630 static const struct SerialHardwareVT UART1_VT =
631 {
632         .init = uart1_init,
633         .cleanup = uart1_cleanup,
634         .setbaudrate = uart1_setbaudrate,
635         .setparity = uart1_setparity,
636         .enabletxirq = uart1_enabletxirq,
637 };
638 #endif // AVR_HAS_UART1
639
640 static const struct SerialHardwareVT SPI_VT =
641 {
642         .init = spi_init,
643         .cleanup = spi_cleanup,
644         .setbaudrate = spi_setbaudrate,
645         .setparity = spi_setparity,
646         .enabletxirq = spi_starttx,
647 };
648
649 static struct SerialHardware UARTDescs[SER_CNT] =
650 {
651         { .table = &UART0_VT },
652 #if AVR_HAS_UART1
653         { .table = &UART1_VT },
654 #endif
655         { .table = &SPI_VT   },
656 };
657
658 struct SerialHardware* ser_hw_getdesc(int unit)
659 {
660         ASSERT(unit < SER_CNT);
661         return &UARTDescs[unit];
662 }