-/*!
+/**
* \file
* <!--
- * Copyright 2003, 2004 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2003, 2004, 2006 Develer S.r.l. (http://www.develer.com/)
* Copyright 2000 Bernardo Innocenti <bernie@codewiz.org>
- * This file is part of DevLib - See devlib/README for information.
+ *
* -->
*
* \brief Buffered serial I/O driver
* \author Bernardo Innocenti <bernie@develer.com>
*/
-/*
- * $Log$
- * Revision 1.14 2004/08/24 16:22:57 bernie
- * Thinkos; Doxygen fixes
- *
- * Revision 1.13 2004/08/24 16:20:48 bernie
- * ser_read(): Make buffer argument void * for consistency with ANSI C and ser_write()
- *
- * Revision 1.12 2004/08/24 13:49:39 bernie
- * Fix thinko.
- *
- * Revision 1.11 2004/08/15 05:32:22 bernie
- * ser_resync(): New function.
- *
- * Revision 1.10 2004/08/10 06:29:50 bernie
- * Rename timer_gettick() to timer_ticks().
- *
- * Revision 1.9 2004/08/08 06:06:20 bernie
- * Use new-style CONFIG_ idiom; Fix module-wide documentation.
- *
- * Revision 1.8 2004/07/29 22:57:09 bernie
- * ser_drain(): New function; Make Serial::is_open a debug-only feature; Switch to new-style CONFIG_* macros.
- *
- * Revision 1.7 2004/07/18 21:49:03 bernie
- * Make CONFIG_SER_DEFBAUDRATE optional.
- *
- * Revision 1.6 2004/06/07 15:56:28 aleph
- * Remove cast-as-lvalue extension abuse
- *
- * Revision 1.5 2004/06/06 16:41:44 bernie
- * ser_putchar(): Use fifo_push_locked() to fix potential race on 8bit processors.
- *
- * Revision 1.4 2004/06/03 11:27:09 bernie
- * Add dual-license information.
- *
- * Revision 1.3 2004/06/02 21:35:24 aleph
- * Serial enhancements: interruptible receive handler and 8 bit serial status for AVR; remove volatile attribute to FIFOBuffer, useless for new fifobuf routens
- *
- * Revision 1.2 2004/05/23 18:21:53 bernie
- * Trim CVS logs and cleanup header info.
- *
- */
-
-#include <mware/formatwr.h>
-#include <drv/kdebug.h>
#include "ser.h"
+#include "wdt.h"
#include "ser_p.h"
-#include "hw.h"
+#include <mware/formatwr.h>
+#include <cfg/debug.h>
+#include <appconfig.h>
+
+#include <string.h> /* memset */
+
+/*
+ * Sanity check for config parameters required by this module.
+ */
+#if !defined(CONFIG_KERNEL) || ((CONFIG_KERNEL != 0) && CONFIG_KERNEL != 1)
+ #error CONFIG_KERNEL must be set to either 0 or 1 in config.h
+#endif
+#if !defined(CONFIG_SER_RXTIMEOUT)
+ #error CONFIG_SER_TXTIMEOUT missing in config.h
+#endif
+#if !defined(CONFIG_SER_RXTIMEOUT)
+ #error CONFIG_SER_RXTIMEOUT missing in config.h
+#endif
+#if !defined(CONFIG_SER_DEFBAUDRATE)
+ #error CONFIG_SER_DEFBAUDRATE missing in config.h
+#endif
-#ifdef CONFIG_KERNEL
+#if CONFIG_KERNEL
#include <kern/proc.h>
#endif
+
#if CONFIG_SER_TXTIMEOUT != -1 || CONFIG_SER_RXTIMEOUT != -1
#include <drv/timer.h>
#endif
-/* Serial configuration parameters */
-#define SER_CTSDELAY 70 /*!< CTS line retry interval (ms) */
-#define SER_TXPOLLDELAY 2 /*!< Transmit buffer full retry interval (ms) */
-#define SER_RXPOLLDELAY 2 /*!< Receive buffer empty retry interval (ms) */
-
-
struct Serial ser_handles[SER_CNT];
-
-/*!
- * Inserisce il carattere c nel buffer di trasmissione.
- * Questa funzione mette il processo chiamante in attesa
- * quando il buffer e' pieno.
+/**
+ * Insert \a c in tx FIFO buffer.
+ * \note This function will switch out the calling process
+ * if the tx buffer is full. If the buffer is full
+ * and \a port->txtimeout is 0 return EOF immediatly.
*
- * \return EOF in caso di errore o timeout, altrimenti
- * il carattere inviato.
+ * \return EOF on error or timeout, \a c otherwise.
*/
-int ser_putchar(int c, struct Serial *port)
+static int ser_putchar(int c, struct Serial *port)
{
if (fifo_isfull_locked(&port->txfifo))
{
#if CONFIG_SER_TXTIMEOUT != -1
- time_t start_time = timer_ticks();
+ /* If timeout == 0 we don't want to wait */
+ if (port->txtimeout == 0)
+ return EOF;
+
+ ticks_t start_time = timer_clock();
#endif
- /* Attende finche' il buffer e' pieno... */
+ /* Wait while buffer is full... */
do
{
-#if defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED
+ wdt_reset();
+#if CONFIG_KERNEL && CONFIG_KERN_SCHED
/* Give up timeslice to other processes. */
proc_switch();
#endif
#if CONFIG_SER_TXTIMEOUT != -1
- if (timer_ticks() - start_time >= port->txtimeout)
+ if (timer_clock() - start_time >= port->txtimeout)
{
- port->status |= SERRF_TXTIMEOUT;
+ ATOMIC(port->status |= SERRF_TXTIMEOUT);
return EOF;
}
#endif /* CONFIG_SER_TXTIMEOUT */
fifo_push_locked(&port->txfifo, (unsigned char)c);
/* (re)trigger tx interrupt */
- port->hw->table->enabletxirq(port->hw);
+ port->hw->table->txStart(port->hw);
- /* Avoid returning signed estended char */
+ /* Avoid returning signed extended char */
return (int)((unsigned char)c);
}
-/*!
- * Preleva un carattere dal buffer di ricezione.
- * Questa funzione mette il processo chiamante in attesa
- * quando il buffer e' vuoto. L'attesa ha un timeout
- * di ser_rxtimeout millisecondi.
+/**
+ * Fetch a character from the rx FIFO buffer.
+ * \note This function will switch out the calling process
+ * if the rx buffer is empty. If the buffer is empty
+ * and \a port->rxtimeout is 0 return EOF immediatly.
*
- * \return EOF in caso di errore o timeout, altrimenti
- * il carattere ricevuto.
+ * \return EOF on error or timeout, \a c otherwise.
*/
-int ser_getchar(struct Serial *port)
+static int ser_getchar(struct Serial *port)
{
- int result;
-
if (fifo_isempty_locked(&port->rxfifo))
{
#if CONFIG_SER_RXTIMEOUT != -1
- time_t start_time = timer_ticks();
+ /* If timeout == 0 we don't want to wait for chars */
+ if (port->rxtimeout == 0)
+ return EOF;
+
+ ticks_t start_time = timer_clock();
#endif
/* Wait while buffer is empty */
do
{
-#if defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED
+ wdt_reset();
+#if CONFIG_KERNEL && CONFIG_KERN_SCHED
/* Give up timeslice to other processes. */
proc_switch();
#endif
#if CONFIG_SER_RXTIMEOUT != -1
- if (timer_ticks() - start_time >= port->rxtimeout)
+ if (timer_clock() - start_time >= port->rxtimeout)
{
- port->status |= SERRF_RXTIMEOUT;
+ ATOMIC(port->status |= SERRF_RXTIMEOUT);
return EOF;
}
#endif /* CONFIG_SER_RXTIMEOUT */
}
- while (fifo_isempty_locked(&port->rxfifo));
+ while (fifo_isempty_locked(&port->rxfifo) && (ser_getstatus(port) & SERRF_RX) == 0);
}
/*
* Get a byte from the FIFO (avoiding sign-extension),
* re-enable RTS, then return result.
*/
- result = (int)(unsigned char)fifo_pop(&port->rxfifo);
- return port->status ? EOF : result;
+ if (ser_getstatus(port) & SERRF_RX)
+ return EOF;
+ return (int)(unsigned char)fifo_pop_locked(&port->rxfifo);
}
-
-/*!
- * Preleva un carattere dal buffer di ricezione.
- * Se il buffer e' vuoto, ser_getchar_nowait() ritorna
- * immediatamente EOF.
+/**
+ * Fetch a character from the rx FIFO buffer.
+ * If the buffer is empty, ser_getchar_nowait() returns
+ * EOF immediatly.
+ * \note Deprecated, use ser_getchar with rx_timeout set to 0.
*/
-int ser_getchar_nowait(struct Serial *port)
+int ser_getchar_nowait(struct KFileSerial *fd)
{
- if (fifo_isempty_locked(&port->rxfifo))
+ if (fifo_isempty_locked(&fd->ser->rxfifo))
return EOF;
/* NOTE: the double cast prevents unwanted sign extension */
- return (int)(unsigned char)fifo_pop(&port->rxfifo);
+ return (int)(unsigned char)fifo_pop_locked(&fd->ser->rxfifo);
}
-#if CONFIG_SER_GETS
-/*!
- * Read a line long at most as size and puts it
- * in buf.
- * \return number of chars read or EOF in case
- * of error.
- */
-int ser_gets(struct Serial *port, char *buf, int size)
-{
- return ser_gets_echo(port, buf, size, false);
-}
-
-
-/*!
- * Read a line long at most as size and put it
- * in buf, with optional echo.
- *
- * \return number of chars read, or EOF in case
- * of error.
- */
-int ser_gets_echo(struct Serial *port, char *buf, int size, bool echo)
-{
- int i = 0;
- int c;
-
- for (;;)
- {
- if ((c = ser_getchar(port)) == EOF)
- {
- buf[i] = '\0';
- return -1;
- }
-
- /* FIXME */
- if (c == '\r' || c == '\n' || i >= size-1)
- {
- buf[i] = '\0';
- if (echo)
- ser_print(port, "\r\n");
- break;
- }
- buf[i++] = c;
- if (echo)
- ser_putchar(c, port);
- }
- return i;
-}
-#endif /* !CONFIG_SER_GETS */
-
-
-/*!
+/**
* Read at most \a size bytes from \a port and put them in \a buf
*
- * \return number of bytes actually read, or EOF in
- * case of error.
+ * \return number of bytes actually read.
*/
-int ser_read(struct Serial *port, void *buf, size_t size)
+static size_t ser_read(struct KFile *fd, void *_buf, size_t size)
{
+ KFileSerial *fds = KFILESERIAL(fd);
+
size_t i = 0;
- char *_buf = (char *)buf;
+ char *buf = (char *)_buf;
int c;
while (i < size)
{
- if ((c = ser_getchar(port)) == EOF)
- return EOF;
- _buf[i++] = c;
+ if ((c = ser_getchar(fds->ser)) == EOF)
+ break;
+ buf[i++] = c;
}
return i;
}
-
-/*!
- * Write a string to serial.
- * \return 0 if OK, EOF in case of error.
- */
-int ser_print(struct Serial *port, const char *s)
-{
- while (*s)
- {
- if (ser_putchar(*s++, port) == EOF)
- return EOF;
- }
- return 0;
-}
-
-
-/*!
+/**
* \brief Write a buffer to serial.
*
* \return 0 if OK, EOF in case of error.
*
* \todo Optimize with fifo_pushblock()
*/
-int ser_write(struct Serial *port, const void *_buf, size_t len)
+static size_t ser_write(struct KFile *fd, const void *_buf, size_t size)
{
- const char *buf = _buf;
+ KFileSerial *fds = KFILESERIAL(fd);
+ const char *buf = (const char *)_buf;
+ size_t i = 0;
- while (len--)
+ while (size--)
{
- if (ser_putchar(*buf++, port) == EOF)
- return EOF;
+ if (ser_putchar(*buf++, fds->ser) == EOF)
+ break;
+ i++;
}
- return 0;
-}
-
-
-#if CONFIG_PRINTF
-/*!
- * Formatted write
- */
-int ser_printf(struct Serial *port, const char *format, ...)
-{
- va_list ap;
- int len;
-
- ser_setstatus(port, 0);
- va_start(ap, format);
- len = _formatted_write(format, (void (*)(char, void *))ser_putchar, port, ap);
- va_end(ap);
-
- return len;
+ return i;
}
-#endif /* CONFIG_PRINTF */
#if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1
-void ser_settimeouts(struct Serial *port, time_t rxtimeout, time_t txtimeout)
+void ser_settimeouts(struct KFileSerial *fd, mtime_t rxtimeout, mtime_t txtimeout)
{
- port->rxtimeout = rxtimeout;
- port->txtimeout = txtimeout;
+ fd->ser->rxtimeout = ms_to_ticks(rxtimeout);
+ fd->ser->txtimeout = ms_to_ticks(txtimeout);
}
#endif /* CONFIG_SER_RXTIMEOUT || CONFIG_SER_TXTIMEOUT */
#if CONFIG_SER_RXTIMEOUT != -1
-/*!
- * Discard input to resynchronize with remote end
+/**
+ * Discard input to resynchronize with remote end.
*
* Discard incoming data until the port stops receiving
* characters for at least \a delay milliseconds.
*
* \note Serial errors are reset before and after executing the purge.
*/
-void ser_resync(struct Serial *port, time_t delay)
+void ser_resync(struct KFileSerial *fd, mtime_t delay)
{
- time_t old_rxtimeout = port->rxtimeout;
+ mtime_t old_rxtimeout = ticks_to_ms(fd->ser->rxtimeout);
- ser_settimeouts(port, delay, port->txtimeout);
+ ser_settimeouts(fd, delay, ticks_to_ms(fd->ser->txtimeout));
do
{
- ser_setstatus(port, 0);
- ser_getchar(port);
+ ser_setstatus(fd->ser, 0);
+ ser_getchar(fd->ser);
}
- while (!(ser_getstatus(port) & SERRF_RXTIMEOUT));
+ while (!(ser_getstatus(fd->ser) & SERRF_RXTIMEOUT));
/* Restore port to an usable status */
- ser_setstatus(port, 0);
- ser_settimeouts(port, old_rxtimeout, port->txtimeout);
+ ser_setstatus(fd->ser, 0);
+ ser_settimeouts(fd, old_rxtimeout, ticks_to_ms(fd->ser->txtimeout));
}
#endif /* CONFIG_SER_RXTIMEOUT */
-void ser_setbaudrate(struct Serial *port, unsigned long rate)
+void ser_setbaudrate(struct KFileSerial *fd, unsigned long rate)
+{
+ fd->ser->hw->table->setBaudrate(fd->ser->hw, rate);
+}
+
+
+void ser_setparity(struct KFileSerial *fd, int parity)
{
- port->hw->table->setbaudrate(port->hw, rate);
+ fd->ser->hw->table->setParity(fd->ser->hw, parity);
}
+static int ser_error(struct KFile *fd)
+{
+ KFileSerial *fds = KFILESERIAL(fd);
+ return ser_getstatus(fds->ser);
+}
-void ser_setparity(struct Serial *port, int parity)
+static void ser_clearerr(struct KFile *fd)
{
- port->hw->table->setparity(port->hw, parity);
+ KFileSerial *fds = KFILESERIAL(fd);
+ ser_setstatus(fds->ser, 0);
}
-/*!
+
+/**
* Flush both the RX and TX buffers.
*/
-void ser_purge(struct Serial *port)
+void ser_purge(struct KFileSerial *fd)
{
- fifo_flush_locked(&port->rxfifo);
- fifo_flush_locked(&port->txfifo);
+ ser_purgeRx(fd);
+ ser_purgeTx(fd);
+}
+
+/**
+ * Flush RX buffer.
+ */
+void ser_purgeRx(struct KFileSerial *fd)
+{
+ fifo_flush_locked(&fd->ser->rxfifo);
+}
+
+/**
+ * Flush TX buffer.
+ */
+void ser_purgeTx(struct KFileSerial *fd)
+{
+ fifo_flush_locked(&fd->ser->txfifo);
}
-/*!
+/**
* Wait until all pending output is completely
* transmitted to the other end.
*
* software transmission queue. Any hardware
* FIFOs are ignored.
*/
-void ser_drain(struct Serial *ser)
+static int ser_flush(struct KFile *fd)
{
- while (!fifo_isempty(&ser->txfifo))
+ KFileSerial *fds = KFILESERIAL(fd);
+
+ /*
+ * Wait until the FIFO becomes empty, and then until the byte currently in
+ * the hardware register gets shifted out.
+ */
+ while (!fifo_isempty(&fds->ser->txfifo)
+ || fds->ser->hw->table->txSending(fds->ser->hw))
{
-#if defined(CONFIG_KERN_SCHED) && CONFIG_KERN_SCHED
+ #if CONFIG_KERNEL && CONFIG_KERN_SCHED
/* Give up timeslice to other processes. */
proc_switch();
-#endif
+ #endif
+ wdt_reset();
}
+ return 0;
}
-/*!
- * Initialize serial
+/**
+ * Initialize a serial port.
+ *
+ * \param fd KFile Serial struct interface.
+ * \param unit Serial unit to open. Possible values are architecture dependant.
*/
-struct Serial *ser_open(unsigned int unit)
+static struct Serial *ser_open(struct KFileSerial *fd, unsigned int unit)
{
struct Serial *port;
port = &ser_handles[unit];
ASSERT(!port->is_open);
- DB(port->is_open = true;)
+ DB(port->is_open = true);
port->unit = unit;
- /* Initialize circular buffer */
- fifo_init(&port->rxfifo, port->rxbuffer, sizeof(port->rxbuffer));
- fifo_init(&port->txfifo, port->txbuffer, sizeof(port->txbuffer));
-
port->hw = ser_hw_getdesc(unit);
+
+ /* Initialize circular buffers */
+ ASSERT(port->hw->txbuffer);
+ ASSERT(port->hw->rxbuffer);
+ fifo_init(&port->txfifo, port->hw->txbuffer, port->hw->txbuffer_size);
+ fifo_init(&port->rxfifo, port->hw->rxbuffer, port->hw->rxbuffer_size);
+
port->hw->table->init(port->hw, port);
+ fd->ser = port;
/* Set default values */
#if CONFIG_SER_RXTIMEOUT != -1 || CONFIG_SER_TXTIMEOUT != -1
- ser_settimeouts(port, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT);
+ ser_settimeouts(fd, CONFIG_SER_RXTIMEOUT, CONFIG_SER_TXTIMEOUT);
#endif
#if CONFIG_SER_DEFBAUDRATE
- ser_setbaudrate(port, CONFIG_SER_DEFBAUDRATE);
+ ser_setbaudrate(fd, CONFIG_SER_DEFBAUDRATE);
#endif
+ /* Clear error flags */
+ ser_setstatus(port, 0);
+
return port;
}
-/*!
+/**
* Clean up serial port, disabling the associated hardware.
*/
-void ser_close(struct Serial *port)
+static int ser_close(struct KFile *fd)
{
+ KFileSerial *fds = KFILESERIAL(fd);
+ Serial *port = fds->ser;
+
ASSERT(port->is_open);
- DB(port->is_open = false;)
+ DB(port->is_open = false);
+
+ // Wait until we finish sending everything
+ ser_flush(fd);
port->hw->table->cleanup(port->hw);
- port->hw = NULL;
+ DB(port->hw = NULL);
+
+ /*
+ * We purge the FIFO buffer only after the low-level cleanup, so that
+ * we are sure that there are no more interrupts.
+ */
+ ser_purge(fds);
+ return 0;
}
+
+/**
+ * Reopen serial port.
+ */
+static struct KFile *ser_reopen(struct KFile *fd)
+{
+ KFileSerial *fds = KFILESERIAL(fd);
+
+ ser_close(fd);
+ ser_open(fds, fds->ser->unit);
+ return (KFile *)fds;
+}
+
+/**
+ * Init serial driver for \a unit.
+ */
+void ser_init(struct KFileSerial *fds, unsigned int unit)
+{
+ memset(fds, 0, sizeof(*fds));
+
+ DB(fds->fd._type = KFT_SERIAL);
+ fds->fd.reopen = ser_reopen;
+ fds->fd.close = ser_close;
+ fds->fd.read = ser_read;
+ fds->fd.write = ser_write;
+ fds->fd.flush = ser_flush;
+ fds->fd.error = ser_error;
+ fds->fd.clearerr = ser_clearerr;
+ ser_open(fds, unit);
+}
+
+
+/**
+ * Read data from SPI bus.
+ * Since we are master, we have to trigger slave by sending
+ * fake chars on the bus.
+ */
+static size_t spimaster_read(struct KFile *fd, void *_buf, size_t size)
+{
+ KFileSerial *fd_spi = KFILESERIAL(fd);
+
+ ser_flush(&fd_spi->fd);
+ ser_purgeRx(fd_spi);
+
+ size_t total_rd = 0;
+ uint8_t *buf = (uint8_t *)_buf;
+ int c;
+
+ while (size--)
+ {
+ /*
+ * Send and receive chars 1 by 1, otherwise the rxfifo
+ * will overrun.
+ */
+ ser_putchar(0, fd_spi->ser);
+
+ if ((c = ser_getchar(fd_spi->ser)) == EOF)
+ break;
+
+ *buf++ = c;
+ total_rd++;
+ }
+ return total_rd;
+}
+
+/**
+ * Write data to SPI bus.
+ */
+static size_t spimaster_write(struct KFile *fd, const void *buf, size_t size)
+{
+ KFileSerial *fd_spi = KFILESERIAL(fd);
+
+ ser_purgeRx(fd_spi);
+
+ return ser_write(&fd_spi->fd, buf, size);
+}
+
+
+/**
+ * Init SPI serial driver \a unit in master mode.
+ *
+ * This interface implements the SPI master protocol over a serial SPI
+ * driver. This is needed because normal serial driver send/receive data
+ * at the same time. SPI slaves like memories and other peripherals
+ * first receive and *then* send response back instead.
+ * To achieve this, when we are master and we are *sending*,
+ * we have to discard all incoming data. Then, when we want to
+ * receive, we must write fake data to SPI to trigger slave devices.
+ */
+void spimaster_init(KFileSerial *fds, unsigned int unit)
+{
+ ser_init(fds, unit);
+ fds->fd.read = spimaster_read;
+ fds->fd.write = spimaster_write;
+}
+
+