--- /dev/null
+/**
+ * \file
+ * <!--
+ * 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 2010 Develer S.r.l. (http://www.develer.com/)
+ *
+ * -->
+ *
+ * \brief LPC23xx UART driver.
+ *
+ * \author Andrea Righi <arighi@develer.com>
+ */
+
+#include <cfg/macros.h> /* for BV() */
+#include <cpu/power.h> /* cpu_relax() */
+#include <drv/ser_p.h>
+#include <drv/ser.h>
+#include <drv/vic_lpc2.h> /* vic_handler_t */
+#include <io/lpc23xx.h>
+#include "cfg/cfg_ser.h"
+#include "ser_lpc2.h"
+
+#define INT_UART0 6
+#define INT_UART1 7
+#define INT_UART2 28
+#define INT_UART3 29
+
+/* From the high-level serial driver */
+extern struct Serial *ser_handles[SER_CNT];
+
+struct LPC2Serial
+{
+ struct SerialHardware hw;
+ bool sending;
+ int irq;
+};
+
+/* Forward declaration */
+static struct LPC2Serial UARTDesc[SER_CNT];
+
+struct uart_config
+{
+ uint32_t base;
+ uint32_t pconp;
+ uint32_t pclksel0;
+ uint32_t pclksel0_mask;
+ uint32_t pclksel1;
+ uint32_t pclksel1_mask;
+ uint32_t pinsel0;
+ uint32_t pinsel0_mask;
+ uint32_t pinsel4;
+ uint32_t pinsel4_mask;
+};
+
+/* UART registers configuration */
+static const struct uart_config uart_param[] =
+{
+ /* UART0 */
+ {
+ .base = UART0_BASE_ADDR,
+ .pconp = BV(3),
+
+ .pclksel0 = BV(6),
+ .pclksel0_mask = BV(7) | BV(6),
+
+ .pclksel1 = 0,
+ .pclksel1_mask = 0,
+
+ .pinsel0 = BV(6) | BV(4),
+ .pinsel0_mask = BV(7) | BV(6) | BV(5) | BV(4),
+
+ .pinsel4 = 0,
+ .pinsel4_mask = 0,
+ },
+ /* UART1 */
+ {
+ .base = UART1_BASE_ADDR,
+ .pconp = BV(4),
+
+ .pclksel0 = BV(8),
+ .pclksel0_mask = BV(9) | BV(8),
+
+ .pclksel1 = 0,
+ .pclksel1_mask = 0,
+
+ .pinsel0 = 0,
+ .pinsel0_mask = 0,
+
+ .pinsel4 = BV(3) | BV(1),
+ .pinsel4_mask = BV(3) | BV(2) | BV(1) | BV(0),
+ },
+ /* UART2 */
+ {
+ .base = UART2_BASE_ADDR,
+ .pconp = BV(24),
+
+ .pclksel0 = 0,
+ .pclksel0_mask = 0,
+
+ .pclksel1 = BV(16),
+ .pclksel1_mask = BV(17) | BV(16),
+
+ .pinsel0 = 0,
+ .pinsel0_mask = 0,
+
+ .pinsel4 = BV(19) | BV(17),
+ .pinsel4_mask = BV(19) | BV(18) | BV(17) | BV(16),
+ },
+ /* UART3 */
+ {
+ .base = UART3_BASE_ADDR,
+ .pconp = BV(25),
+
+ .pclksel0 = 0,
+ .pclksel0_mask = 0,
+
+ .pclksel1 = BV(18),
+ .pclksel1_mask = BV(19) | BV(18),
+
+ .pinsel0 = BV(3) | BV(1),
+ .pinsel0_mask = BV(3) | BV(2) | BV(1) | BV(0),
+
+ .pinsel4 = 0,
+ .pinsel4_mask = 0,
+ },
+};
+
+static void lpc2_uartSetBaudRate(int port, unsigned long baud)
+{
+ cpu_flags_t flags;
+
+ IRQ_SAVE_DISABLE(flags);
+
+ /* LCR: DLAB = 1 (enable divisor modify) */
+ *(reg8_t *)(uart_param[port].base + 0x0c) |= 0x80;
+ /* DLL */
+ *(reg8_t *)(uart_param[port].base + 0x00) =
+ DIV_ROUND(CPU_FREQ, 16 * baud) & 0xFF;
+ /* DLM */
+ *(reg8_t *)(uart_param[port].base + 0x04) =
+ (DIV_ROUND(CPU_FREQ, 16 * baud) >> 8) & 0xFF;
+ *(reg32_t *)(uart_param[port].base + 0x0c) &= ~0x80;
+ /* LCR: DLAB = 0 (disable divisor modify) */
+ *(reg8_t *)(uart_param[port].base + 0x0c) &= ~0x80;
+
+ IRQ_RESTORE(flags);
+}
+
+static void lpc2_uartSetParity(int port, int parity)
+{
+ /* Set 8-bit word, one stop bit by default */
+ uint32_t config = BV(1) | BV(0);
+
+ cpu_flags_t flags;
+
+ IRQ_SAVE_DISABLE(flags);
+
+ switch(parity)
+ {
+ case SER_PARITY_NONE:
+ break;
+ case SER_PARITY_ODD:
+ config |= BV(3);
+ break;
+ case SER_PARITY_EVEN:
+ config |= BV(4) | BV(3);
+ break;
+ default:
+ ASSERT(0);
+ IRQ_RESTORE(flags);
+ return;
+ }
+ /* LCR */
+ *(reg8_t *)(uart_param[port].base + 0x0c) = config;
+
+ IRQ_RESTORE(flags);
+}
+
+static void lpc2_uartPutChar(uint32_t base, uint8_t c)
+{
+ reg8_t *lsr = (reg8_t *)base + 0x14;
+ reg8_t *thr = (reg8_t *)base + 0x00;
+
+ while (!(*lsr & BV(6)))
+ cpu_relax();
+ *thr = c;
+}
+
+void lpc2_uartInit(int port)
+{
+ cpu_flags_t flags;
+
+ IRQ_SAVE_DISABLE(flags);
+
+ /* Power-on the device */
+ PCONP |= uart_param[port].pconp;
+ /* Set UART clk to CPU_FREQ */
+ PCLKSEL0 &= ~uart_param[port].pclksel0_mask;
+ PCLKSEL0 |= uart_param[port].pclksel0;
+ PCLKSEL1 &= ~uart_param[port].pclksel1_mask;
+ PCLKSEL1 |= uart_param[port].pclksel1;
+
+ /* LCR: 8bit, 1 stop bit, no parity, DLAB = 1 (enable divisor modify) */
+ *(reg8_t *)(uart_param[port].base + 0x0c) = 0x83;
+ /* DLL */
+ *(reg8_t *)(uart_param[port].base + 0x00) =
+ DIV_ROUND(CPU_FREQ, 16 * CONFIG_KDEBUG_BAUDRATE) & 0xFF;
+ /* DLM */
+ *(reg8_t *)(uart_param[port].base + 0x04) =
+ (DIV_ROUND(CPU_FREQ, 16 * CONFIG_KDEBUG_BAUDRATE) >> 8) & 0xFF;
+ /* FDR */
+ *(reg32_t *)(uart_param[port].base + 0x28) = 0x10;
+
+ /* Assign TX pin to UART0*/
+ PINSEL0 &= ~uart_param[port].pinsel0_mask;
+ PINSEL0 |= uart_param[port].pinsel0;
+ PINSEL4 &= ~uart_param[port].pinsel4_mask;
+ PINSEL4 |= uart_param[port].pinsel4;
+ /* LCR: set 8bit, 1 stop bit, no parity, DLAB = 0 (disable divisor modify) */
+ *(reg8_t *)(uart_param[port].base + 0x0c) = 0x03;
+
+ /* TER: Enable transmitter */
+ *(reg8_t *)(uart_param[port].base + 0x30) = BV(7);
+ /* IER: Enable RBR interrupt */
+ *(reg8_t *)(uart_param[port].base + 0x04) = BV(0);
+
+ IRQ_RESTORE(flags);
+}
+
+static bool tx_sending(struct SerialHardware *_hw)
+{
+ struct LPC2Serial *hw = (struct LPC2Serial *)_hw;
+ return hw->sending;
+}
+
+INLINE bool lpc2_uartRxReady(int port)
+{
+ /* LSR: check Receiver Data Ready (RDR) bit */
+ return *(reg8_t *)(uart_param[port].base + 0x14) & BV(0) ? true : false;
+}
+
+static void uart_irq_rx(int port)
+{
+ struct FIFOBuffer *rxfifo = &ser_handles[port]->rxfifo;
+ char c;
+
+ while (lpc2_uartRxReady(port))
+ {
+ c = *(reg8_t *)(uart_param[port].base + 0x00);
+ if (fifo_isfull(rxfifo))
+ ser_handles[port]->status |= SERRF_RXFIFOOVERRUN;
+ else
+ fifo_push(rxfifo, c);
+ }
+}
+
+INLINE bool lpc2_uartTxReady(int port)
+{
+ /* LSR: check Transmitter Holding Register Empty (THRE) bit */
+ return *(reg8_t *)(uart_param[port].base + 0x14) & BV(5) ? true : false;
+}
+
+static void uart_irq_tx(int port)
+{
+ struct FIFOBuffer *txfifo = &ser_handles[port]->txfifo;
+
+ while (lpc2_uartTxReady(port))
+ {
+ /*
+ * Disable TX empty interrupts if there're no more
+ * characters to transmit.
+ */
+ if (fifo_isempty(txfifo))
+ {
+ /* IER: Disable THRE interrupt */
+ *(reg8_t *)(uart_param[port].base + 0x04) &= ~BV(1);
+ UARTDesc[port].sending = false;
+ break;
+ }
+ /* THR: put a character to the Transmit Holding Register */
+ *(reg8_t *)(uart_param[port].base + 0x00) = fifo_pop(txfifo);
+ }
+}
+
+static void uart_common_irq_handler(int port)
+{
+ /* IIR: identify the interrupt source */
+ uint32_t status = *(reg32_t *)(uart_param[port].base + 0x08) >> 1 & 0x7;
+
+ /* Receiver Data Ready (RDR) */
+ if (status == 0x02)
+ uart_irq_rx(port);
+ /* Transmit Holding Register Empty (THRE) */
+ else if (status == 0x01)
+ uart_irq_tx(port);
+ /* Signal the VIC we have completed the ISR */
+ VICVectAddr = 0;
+}
+
+static void lpc2_uartIRQEnable(int port, vic_handler_t handler)
+{
+ vic_setVector(UARTDesc[port].irq, handler);
+ vic_enable(UARTDesc[port].irq);
+}
+
+static void lpc2_uartIRQDisable(int port)
+{
+ vic_disable(UARTDesc[port].irq);
+}
+
+/* UART class definition */
+#define UART_PORT(port) \
+ /* UART TX and RX buffers */ \
+ static unsigned char \
+ uart ## port ## _txbuffer[CONFIG_UART ## port ## _TXBUFSIZE]; \
+ static unsigned char \
+ uart ## port ## _rxbuffer[CONFIG_UART ## port ## _RXBUFSIZE]; \
+ \
+ /* UART interrupt handler */ \
+ static DECLARE_ISR(uart ## port ## _irq_handler) \
+ { \
+ uart_common_irq_handler(port); \
+ } \
+ \
+ /* UART public methods */ \
+ static void \
+ uart ## port ## _txStart(struct SerialHardware *_hw) \
+ { \
+ struct FIFOBuffer *txfifo = &ser_handles[port]->txfifo; \
+ struct LPC2Serial *hw = (struct LPC2Serial *)_hw; \
+ \
+ if (hw->sending) \
+ return; \
+ lpc2_uartPutChar(UART ## port ## _BASE_ADDR, fifo_pop(txfifo)); \
+ if (!fifo_isempty(txfifo)) \
+ { \
+ hw->sending = true; \
+ /* IER: Enable THRE interrupt */ \
+ *(reg8_t *)(uart_param[port].base + 0x04) |= BV(1); \
+ } \
+ } \
+ \
+ static void \
+ uart ## port ## _setbaudrate(UNUSED_ARG(struct SerialHardware *, hw), \
+ unsigned long baud) \
+ { \
+ lpc2_uartSetBaudRate(port, baud); \
+ } \
+ \
+ static void \
+ uart ## port ## _setparity(UNUSED_ARG(struct SerialHardware *, hw), \
+ int parity) \
+ { \
+ lpc2_uartSetParity(port, parity); \
+ } \
+ \
+ static void \
+ uart ## port ## _cleanup(struct SerialHardware *_hw) \
+ { \
+ struct LPC2Serial *hw = (struct LPC2Serial *)_hw; \
+ \
+ hw->sending = false; \
+ lpc2_uartIRQDisable(port); \
+ } \
+ \
+ static void \
+ uart ## port ## _init(UNUSED_ARG(struct SerialHardware *, hw), \
+ UNUSED_ARG(struct Serial *, ser)) \
+ { \
+ lpc2_uartInit(port); \
+ lpc2_uartIRQEnable(port, uart ## port ## _irq_handler); \
+ } \
+ \
+ /* UART operations */ \
+ static const struct SerialHardwareVT UART ## port ## _VT = \
+ { \
+ .init = uart ## port ## _init, \
+ .cleanup = uart ## port ## _cleanup, \
+ .setBaudrate = uart ## port ## _setbaudrate, \
+ .setParity = uart ## port ## _setparity, \
+ .txStart = uart ## port ## _txStart, \
+ .txSending = tx_sending, \
+ };
+
+/* UART port instances */
+UART_PORT(0)
+UART_PORT(1)
+UART_PORT(2)
+UART_PORT(3)
+
+static struct LPC2Serial UARTDesc[SER_CNT] =
+{
+ {
+ .hw = {
+ .table = &UART0_VT,
+ .txbuffer = uart0_txbuffer,
+ .rxbuffer = uart0_rxbuffer,
+ .txbuffer_size = sizeof(uart0_txbuffer),
+ .rxbuffer_size = sizeof(uart0_rxbuffer),
+ },
+ .sending = false,
+ .irq = INT_UART0,
+ },
+ {
+ .hw = {
+ .table = &UART1_VT,
+ .txbuffer = uart1_txbuffer,
+ .rxbuffer = uart1_rxbuffer,
+ .txbuffer_size = sizeof(uart1_txbuffer),
+ .rxbuffer_size = sizeof(uart1_rxbuffer),
+ },
+ .sending = false,
+ .irq = INT_UART1,
+ },
+ {
+ .hw = {
+ .table = &UART2_VT,
+ .txbuffer = uart2_txbuffer,
+ .rxbuffer = uart2_rxbuffer,
+ .txbuffer_size = sizeof(uart2_txbuffer),
+ .rxbuffer_size = sizeof(uart2_rxbuffer),
+ },
+ .sending = false,
+ .irq = INT_UART2,
+ },
+ {
+ .hw = {
+ .table = &UART3_VT,
+ .txbuffer = uart3_txbuffer,
+ .rxbuffer = uart3_rxbuffer,
+ .txbuffer_size = sizeof(uart3_txbuffer),
+ .rxbuffer_size = sizeof(uart3_rxbuffer),
+ },
+ .sending = false,
+ .irq = INT_UART3,
+ },
+};
+
+struct SerialHardware *ser_hw_getdesc(int port)
+{
+ ASSERT(port >= 0 && port < SER_CNT);
+ return &UARTDesc[port].hw;
+}