/* * Copyright (C) 2004 by Jan Dubiec. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY JAN DUBIEC AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAN DUBIEC * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * $Log$ * Revision 1.2 2005/10/19 09:34:25 hwmaier * Changed doxygen group to xgUartH8 so it doesn't * overload arch/avr/dev/usartavr.c's documentation * * Revision 1.1 2005/07/26 18:02:40 haraldkipp * Moved from dev. * * Revision 1.4 2005/01/24 21:12:02 freckle * renamed NutEventPostFromIRQ into NutEventPostFromIrq * * Revision 1.3 2005/01/21 16:49:46 freckle * Seperated calls to NutEventPostAsync between Threads and IRQs * * Revision 1.2 2004/03/18 14:06:52 haraldkipp * Deprecated header file replaced * * Revision 1.1 2004/03/16 16:48:27 haraldkipp * Added Jan Dubiec's H8/300 port. * */ #include #include #include #include #include #include #include #include #include #include #include /* #include */ /* * Not nice because stdio already defined them. But in order to save memory, * we do the whole buffering and including stdio here would be more weird. */ #ifndef _IOFBF #define _IOFBF 0x00 #define _IOLBF 0x01 #define _IONBF 0x02 #endif /*! * \addtogroup xgUartH8 */ /*@{*/ /* Baud rate defintions for a 18.432Mhz clock source. */ #define BR_150 150UL #define BR_300 300UL #define BR_600 600UL #define BR_1200 1200UL #define BR_2400 2400UL #define BR_4800 4800UL #define BR_9600 9600UL #define BR_19200 19200UL #define BR_38400 38400UL #define BR_57600 57600UL #define BR_115200 115200UL #define BR_230400 230400UL /* SCR bit definitions. */ #define TXI_ENABLE 0x80 #define RXI_ENABLE 0x40 #define TX_ENABLE 0x20 #define RX_ENABLE 0x10 #define MPI_ENABLE 0x08 #define TXEI_ENABLE 0x04 #define RXEI_ENABLE 0x02 #define CK0_ENABLE 0x01 /* SSR bit definitions. */ #define TDRE 0x80 #define RDRF 0x40 #define ORER 0x20 #define FER 0x10 #define PER 0x08 #define TEND 0x04 /* SMR bit definitions. */ #define DATABITS_8 0 #define DATABITS_7 0x40 #define NOPARITY 0 #define EVENPARITY 0x20 #define ODDPARITY 0x30 #define STOP1BIT 0 #define STOP2BITS 0x08 #define NODIVIDE 0 #define DIVIDEBY4 1 #define DIVIDEBY16 2 #define DIVIDEBY64 3 /* * Returns H8/300H SCI structure pointer based on NUTDEVICE->dev_base * or NULL if there is no such device. */ #define GET_SCI(p) ( ((p) == 0) ? &SCI0 : ( ((p) == 1) ? &SCI1 : \ ( ((p) == 2) ? &SCI2 : NULL ) ) ) /* * Handle H8/300H SCI transmit complete. */ static void TxComplete(void *arg) { NUTDEVICE *dev = (NUTDEVICE *) arg; IFSTREAM *ifs = dev->dev_icb; UARTDCB *dcb; volatile struct st_sci *sci = GET_SCI(dev->dev_base); if (ifs->if_tx_idx != ifs->if_wr_idx) { sci->TDR = ifs->if_tx_buf[ifs->if_tx_idx]; sci->SSR.BYTE &= ~TDRE; ifs->if_tx_idx++; } else { /* Disable SCI interrupts or they just keep coming */ sci->SCR.BYTE &= ~TXI_ENABLE; ifs->if_tx_act = 0; dcb = dev->dev_dcb; NutEventPostFromIrq(&dcb->dcb_tx_rdy); } } /* * Handle H8/300H SCI receive complete. * * Note, that this function modifies the receive index in * interrupt context. This requires, that any non atomic * access of this index requires interrupts being disabled. * Thanks to Mike Cornelius, who pointed this out. */ static void RxComplete(void *arg) { NUTDEVICE *dev = (NUTDEVICE *) arg; IFSTREAM *ifs = dev->dev_icb; UARTDCB *dcb; volatile struct st_sci *sci = GET_SCI(dev->dev_base); ifs->if_rx_buf[ifs->if_rx_idx] = sci->RDR; sci->SSR.BYTE &= ~RDRF; if (ifs->if_rd_idx == ifs->if_rx_idx++) { dcb = dev->dev_dcb; NutEventPostFromIrq(&dcb->dcb_rx_rdy); } } /*! * \brief Wait for input. * * This function checks the input buffer for any data. If * the buffer is empty, the calling \ref xrThread "thread" * will be blocked until at least one new character is * received or a timeout occurs. * * \param dev Indicates the SCI device. * * \return 0 on success, -1 on timeout. */ int SciH8Input(NUTDEVICE * dev) { int rc = 0; IFSTREAM *ifs = dev->dev_icb; UARTDCB *dcb; NutEnterCritical(); if (ifs->if_rd_idx == ifs->if_rx_idx) { dcb = dev->dev_dcb; /* * Changing if into a while loop fixes a serious bug: * Previous receiver events without any waiting thread * set the event handle to the signaled state. So the * wait returns immediately. Unfortunately the calling * routines rely on a filled buffer when we return 0. * Thanks to Mike Cornelius, who found this bug. */ do { rc = NutEventWait(&dcb->dcb_rx_rdy, dcb->dcb_rtimeout); } while (rc == 0 && ifs->if_rd_idx == ifs->if_rx_idx); } NutExitCritical(); return rc; } /*! * \brief Initiate output. * * This function checks the output buffer for any data. If * the buffer contains at least one character, the transmitter * is started, if not already running. The function returns * immediately, without waiting for the character being * completely transmitted. Any remaining characters in the * output buffer are transmitted in the background. * * \param dev Indicates the SCI device. * * \return 0 on success, -1 otherwise. */ int SciH8Output(NUTDEVICE * dev) { IFSTREAM *ifs = dev->dev_icb; volatile struct st_sci *sci = GET_SCI(dev->dev_base); NutEnterCritical(); if (ifs->if_tx_act == 0 && ifs->if_tx_idx != ifs->if_wr_idx) { ifs->if_tx_act = 1; sci->TDR = ifs->if_tx_buf[ifs->if_tx_idx]; sci->SCR.BYTE |= TXI_ENABLE; sci->SSR.BYTE &= ~TDRE; ifs->if_tx_idx++; } NutExitCritical(); return 0; } /*! * \brief Wait for output buffer empty. * * If the output buffer contains any data, the calling * thread is suspended until all data has been transmitted. * * \param dev Indicates the SCI device. * * \return 0 on success, -1 otherwise. */ int SciH8Flush(NUTDEVICE * dev) { IFSTREAM *ifs = dev->dev_icb; UARTDCB *dcb = dev->dev_dcb; /* * Start any pending output. */ if (SciH8Output(dev)) return -1; /* * Wait until output buffer empty. */ NutEnterCritical(); while (ifs->if_tx_idx != ifs->if_wr_idx) NutEventWait(&dcb->dcb_tx_rdy, 100); NutExitCritical(); return 0; } /* * * \param dev Indicates the SCI device. * * \return 0 on success, -1 otherwise. */ static int SciH8GetStatus(NUTDEVICE * dev, u_long * status) { IFSTREAM *ifs = dev->dev_icb; u_char us; volatile struct st_sci *sci = GET_SCI(dev->dev_base); *status = 0; us = sci->SSR.BYTE; if (us & FER) *status |= UART_FRAMINGERROR; if (us & ORER) *status |= UART_OVERRUNERROR; if (ifs->if_tx_idx == ifs->if_wr_idx) *status |= UART_TXBUFFEREMPTY; if (ifs->if_rd_idx == ifs->if_rx_idx) *status |= UART_RXBUFFEREMPTY; return 0; } /* * Carefully enable SCI functions. */ static void SciH8Enable(u_short base) { volatile struct st_sci *sci = GET_SCI(base); NutEnterCritical(); //sci->SCR.BYTE |= (TXI_ENABLE | /*RXI_ENABLE |*/ RX_ENABLE | TX_ENABLE); sci->SCR.BYTE |= (TXI_ENABLE | RXI_ENABLE | RX_ENABLE | TX_ENABLE); NutExitCritical(); } /* * Carefully disable SCI functions. */ static void SciH8Disable(u_short base) { volatile struct st_sci *sci = GET_SCI(base); /* * Disable SCI interrupts. */ NutEnterCritical(); sci->SCR.BYTE &= ~(TXI_ENABLE | RXI_ENABLE); NutExitCritical(); /* * Allow incoming or outgoing character to finish. */ NutDelay(10); /* * Now disable SCI functions. */ sci->SCR.BYTE &= ~(RX_ENABLE | TX_ENABLE); } /*! * \brief Perform on-chip UART control functions. * * \param dev Identifies the device that receives the device-control * function. * \param req Requested control function. May be set to one of the * following constants: * - UART_SETSPEED, conf points to an u_long value containing the baudrate. * - UART_GETSPEED, conf points to an u_long value receiving the current baudrate. * - UART_SETDATABITS, conf points to an u_long value containing the number of data bits, 5, 6, 7 or 8. * - UART_GETDATABITS, conf points to an u_long value receiving the number of data bits, 5, 6, 7 or 8. * - UART_SETPARITY, conf points to an u_long value containing the parity, 0 (no), 1 (odd) or 2 (even). * - UART_GETPARITY, conf points to an u_long value receiving the parity, 0 (no), 1 (odd) or 2 (even). * - UART_SETSTOPBITS, conf points to an u_long value containing the number of stop bits 1 or 2. * - UART_GETSTOPBITS, conf points to an u_long value receiving the number of stop bits 1 or 2. * - UART_SETSTATUS * - UART_GETSTATUS * - UART_SETREADTIMEOUT, conf points to an u_long value containing the read timeout. * - UART_GETREADTIMEOUT, conf points to an u_long value receiving the read timeout. * - UART_SETWRITETIMEOUT, conf points to an u_long value containing the write timeout. * - UART_GETWRITETIMEOUT, conf points to an u_long value receiving the write timeout. * - UART_SETLOCALECHO, conf points to an u_long value containing 0 (off) or 1 (on). * - UART_GETLOCALECHO, conf points to an u_long value receiving 0 (off) or 1 (on). * - UART_SETFLOWCONTROL, conf points to an u_long value containing combined UART_FCTL_ values. * - UART_GETFLOWCONTROL, conf points to an u_long value containing receiving UART_FCTL_ values. * - UART_SETCOOKEDMODE, conf points to an u_long value containing 0 (off) or 1 (on). * - UART_GETCOOKEDMODE, conf points to an u_long value receiving 0 (off) or 1 (on). * * \param conf Points to a buffer that contains any data required for * the given control function or receives data from that * function. * \return 0 on success, -1 otherwise. * * \warning Timeout values are given in milliseconds and are limited to * the granularity of the system timer. * * \bug For ATmega103, only 8 data bits, 1 stop bit and no parity are allowed. * */ int SciH8IOCtl(NUTDEVICE * dev, int req, void *conf) { int rc = 0; UARTDCB *dcb; u_long *lvp = (u_long *) conf; u_long lv = *lvp; u_char bv = (u_char) lv; u_char devnum; volatile struct st_sci *sci = GET_SCI(dev->dev_base); if (dev == 0) dev = &devSci0; devnum = dev->dev_base; dcb = dev->dev_dcb; switch (req) { case UART_SETSPEED: SciH8Disable(devnum); u_char cks = 0; u_long brr = ((NutGetCpuClock() / (64UL / 2UL)) / *lvp) - 1; if (brr > 0xff) { cks = 1; brr = ((NutGetCpuClock() / (64UL * 2UL)) / *lvp) - 1; if (brr > 0xff) { cks = 2; brr = ((NutGetCpuClock() / (64UL * 8UL)) / *lvp) - 1; if (brr > 0xff) { cks = 3; brr = ((NutGetCpuClock() / (64UL * 32UL)) / *lvp) - 1; } } } sci->SMR.BIT.CKS = cks; sci->BRR = (u_char) brr; dcb->dcb_baudSelect = brr; NutDelay(2); /* wait for a while */ SciH8Enable(devnum); break; case UART_GETSPEED: switch (sci->SMR.BIT.CKS) { case 0: *lvp = NutGetCpuClock() / ((64UL / 2UL) * (u_long) (sci->BRR + 1)); break; case 1: *lvp = NutGetCpuClock() / ((64UL * 2UL) * (u_long) (sci->BRR + 1)); break; case 2: *lvp = NutGetCpuClock() / ((64UL * 8UL) * (u_long) (sci->BRR + 1)); break; case 3: *lvp = NutGetCpuClock() / ((64UL * 32UL) * (u_long) (sci->BRR + 1)); break; } break; case UART_SETDATABITS: SciH8Disable(devnum); switch (bv) { case 8: /* 8 data bits */ sci->SMR.BIT.CHR = 0; break; case 7: /* 7 data bits */ sci->SMR.BIT.CHR = 1; break; default: rc = -1; } SciH8Enable(devnum); break; case UART_GETDATABITS: switch (sci->SMR.BIT.CHR) { case 0: /* 8 data bits */ *lvp = 8; break; case 1: /* 7 data bits */ *lvp = 7; break; } break; case UART_SETPARITY: SciH8Disable(devnum); switch (bv) { case 'n': /* none */ sci->SMR.BIT.PE = 0; break; case 'e': /* even */ sci->SMR.BIT.PE = 1; sci->SMR.BIT.OE = 0; break; case 'o': /* odd */ sci->SMR.BIT.PE = 1; sci->SMR.BIT.OE = 1; break; default: rc = -1; } SciH8Enable(devnum); break; case UART_GETPARITY: if (sci->SMR.BIT.PE == 0) { /* none */ bv = 'n'; } else if (sci->SMR.BIT.OE == 0) { /* even */ bv = 'e'; } else { /* odd */ bv = 'o'; } break; case UART_SETSTOPBITS: SciH8Disable(devnum); switch (bv) { case 1: /* 1 stop bit */ sci->SMR.BIT.STOP = 0; break; case 2: /* 2 stop bits */ sci->SMR.BIT.STOP = 1; break; default: rc = -1; } SciH8Enable(devnum); break; case UART_GETSTOPBITS: switch (sci->SMR.BIT.STOP) { case 0: /* 1 stop bit */ *lvp = 1; break; case 1: /* 2 stop bits */ *lvp = 2; break; } break; case UART_GETSTATUS: SciH8GetStatus(dev, lvp); break; case UART_SETSTATUS: rc = -1; break; case UART_SETREADTIMEOUT: dcb->dcb_rtimeout = lv; break; case UART_GETREADTIMEOUT: *lvp = dcb->dcb_rtimeout; break; case UART_SETWRITETIMEOUT: dcb->dcb_wtimeout = lv; break; case UART_GETWRITETIMEOUT: *lvp = dcb->dcb_wtimeout; break; case UART_SETLOCALECHO: if (bv) dcb->dcb_modeflags |= UART_MF_LOCALECHO; else dcb->dcb_modeflags &= ~UART_MF_LOCALECHO; break; case UART_GETLOCALECHO: if (dcb->dcb_modeflags & UART_MF_LOCALECHO) *lvp = 1; else *lvp = 0; break; case UART_SETFLOWCONTROL: if (bv) dcb->dcb_modeflags |= UART_MF_LOCALECHO; else dcb->dcb_modeflags &= ~UART_MF_LOCALECHO; break; case UART_GETFLOWCONTROL: break; case UART_SETCOOKEDMODE: if (bv) dcb->dcb_modeflags |= UART_MF_COOKEDMODE; else dcb->dcb_modeflags &= ~UART_MF_COOKEDMODE; break; case UART_GETCOOKEDMODE: if (dcb->dcb_modeflags & UART_MF_COOKEDMODE) *lvp = 1; else *lvp = 0; break; default: rc = -1; break; } return rc; } /*! * \brief Initialize on chip uart device. * * Prepares the device for subsequent reading or writing. * Enables SCI transmitter and receiver interrupts. * * \param dev Identifies the device to initialize. * * \return 0 on success, -1 otherwise. */ int SciH8Init(NUTDEVICE * dev) { IFSTREAM *ifs; UARTDCB *dcb; u_long baudrate = 9600; volatile struct st_sci *sci = GET_SCI(dev->dev_base); /* * We only support character devices for on-chip SCIs. */ if (dev->dev_type != IFTYP_STREAM || dev->dev_irq != 0 || (dev->dev_base != 0 && dev->dev_base != 1 && dev->dev_base != 2)) return -1; /* * Initialize interface control block. */ ifs = dev->dev_icb; memset(ifs, 0, sizeof(IFSTREAM)); ifs->if_input = SciH8Input; ifs->if_output = SciH8Output; ifs->if_flush = SciH8Flush; /* * Initialize driver control block. */ dcb = dev->dev_dcb; memset(dcb, 0, sizeof(UARTDCB)); /* dcb->dcb_baudSelect = 7; */ dcb->dcb_modeflags = UART_MF_NOBUFFER; /* * Register interrupt handler. */ if (dev->dev_base == 2) { if (NutRegisterIrqHandler(&sig_RXI2, RxComplete, dev)) return -1; if (NutRegisterIrqHandler(&sig_TXI2, TxComplete, dev)) return -1; } else if (dev->dev_base == 1) { if (NutRegisterIrqHandler(&sig_RXI1, RxComplete, dev)) return -1; if (NutRegisterIrqHandler(&sig_TXI1, TxComplete, dev)) return -1; } else if (dev->dev_base == 0) { if (NutRegisterIrqHandler(&sig_RXI0, RxComplete, dev)) return -1; if (NutRegisterIrqHandler(&sig_TXI0, TxComplete, dev)) return -1; } sci->SCR.BYTE = 0; /* Set for Async, data bits, parity, stop bit and an undivided clock. */ sci->SMR.BYTE = DATABITS_8 | NOPARITY | STOP1BIT | NODIVIDE; /* Clear relevant status bits. */ sci->SSR.BYTE = ~(PER | FER | ORER | RDRF); sci->SCR.BYTE = (RXI_ENABLE | RX_ENABLE | TX_ENABLE); /* * Set baudrate and handshake default. This will also * enable the UART functions. */ SciH8IOCtl(dev, UART_SETSPEED, &baudrate); return 0; } /*! * \brief Read from device. */ int SciH8Read(NUTFILE * fp, void *buffer, int size) { int rc; NUTDEVICE *dev; IFSTREAM *ifs; UARTDCB *dcb; u_char elmode; u_char ch; u_char *cp = buffer; dev = fp->nf_dev; ifs = (IFSTREAM *) dev->dev_icb; dcb = dev->dev_dcb; if (dcb->dcb_modeflags & UART_MF_COOKEDMODE) elmode = 1; else elmode = 0; /* * Call without data pointer discards receive buffer. */ if (buffer == 0) { ifs->if_rd_idx = ifs->if_rx_idx; return 0; } /* * Get characters from receive buffer. */ for (rc = 0; rc < size;) { if (ifs->if_rd_idx == ifs->if_rx_idx) { if (rc) break; while (ifs->if_rd_idx == ifs->if_rx_idx) { if (SciH8Input(dev)) { return 0; } } } ch = ifs->if_rx_buf[ifs->if_rd_idx++]; if (elmode && (ch == '\r' || ch == '\n')) { if (ifs->if_last_eol == 0 || ifs->if_last_eol == ch) { ifs->if_last_eol = ch; *cp++ = '\n'; rc++; } } else { ifs->if_last_eol = 0; *cp++ = ch; rc++; } } return rc; } /*! * \brief Write to device. */ int SciH8Put(NUTDEVICE * dev, CONST void *buffer, int len, int pflg) { int rc; IFSTREAM *ifs; UARTDCB *dcb; CONST u_char *cp; u_char lbmode; u_char elmode; u_char ch; ifs = dev->dev_icb; dcb = dev->dev_dcb; if (dcb->dcb_modeflags & UART_MF_LINEBUFFER) lbmode = 1; else lbmode = 0; if (dcb->dcb_modeflags & UART_MF_COOKEDMODE) elmode = 1; else elmode = 0; /* * Call without data pointer starts transmission. */ if (buffer == 0) { rc = SciH8Flush(dev); return rc; } /* * Put characters in transmit buffer. */ cp = buffer; for (rc = 0; rc < len;) { if ((u_char) (ifs->if_wr_idx + 1) == ifs->if_tx_idx) { if (SciH8Flush(dev)) { return -1; } } ch = pflg ? PRG_RDB(cp) : *cp; if (elmode == 1 && ch == '\n') { elmode = 2; if (lbmode == 1) lbmode = 2; ch = '\r'; } else { if (elmode == 2) elmode = 1; cp++; rc++; } ifs->if_tx_buf[ifs->if_wr_idx++] = ch; } if (lbmode > 1 || (dcb->dcb_modeflags & UART_MF_NOBUFFER) != 0) { if (SciH8Flush(dev)) rc = -1; } return rc; } int SciH8Write(NUTFILE * fp, CONST void *buffer, int len) { return SciH8Put(fp->nf_dev, buffer, len, 0); } /*! * \brief Open a device or file. */ NUTFILE *SciH8Open(NUTDEVICE * dev, CONST char *name, int mode, int acc) { NUTFILE *fp = NutHeapAlloc(sizeof(NUTFILE)); UARTDCB *dcb; if (fp == 0) return NUTFILE_EOF; dcb = dev->dev_dcb; if (mode & _O_BINARY) dcb->dcb_modeflags &= ~UART_MF_COOKEDMODE; else dcb->dcb_modeflags |= UART_MF_COOKEDMODE; fp->nf_dev = dev; fp->nf_fcb = NULL; return fp; } /*! * \brief Close a device or file. */ int SciH8Close(NUTFILE * fp) { NutHeapFree(fp); return 0; } /*@}*/