/**************************************************************************** * arch/risc-v/src/c906/c906_serial.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "riscv_internal.h" #include "c906_config.h" #include "chip.h" #include "c906.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* If we are not using the serial driver for the console, then we still must * provide some minimal implementation of up_putc. */ #ifdef USE_SERIALDRIVER /* Which UART with be tty0/console and which tty1? The console will always * be ttyS0. If there is no console then will use the lowest numbered UART. */ #ifdef HAVE_SERIAL_CONSOLE # if defined(CONFIG_UART0_SERIAL_CONSOLE) # define CONSOLE_DEV g_uart0port /* UART0 is console */ # define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */ # undef TTYS1_DEV /* No ttyS1 */ # define SERIAL_CONSOLE 1 # else # error "I'm confused... Do we have a serial console or not?" # endif #else # undef CONSOLE_DEV /* No console */ # undef CONFIG_UART0_SERIAL_CONSOLE # if defined(CONFIG_C906_UART0) # define TTYS0_DEV g_uart0port /* UART0 is ttyS0 */ # undef TTYS1_DEV /* No ttyS1 */ # define SERIAL_CONSOLE 1 # else # undef TTYS0_DEV # undef TTYS1_DEV # endif #endif /* Common initialization logic will not not know that the all of the UARTs * have been disabled. So, as a result, we may still have to provide * stub implementations of riscv_earlyserialinit(), riscv_serialinit(), and * up_putc(). */ #ifdef HAVE_UART_DEVICE /**************************************************************************** * Private Types ****************************************************************************/ struct up_dev_s { uintptr_t uartbase; /* Base address of UART registers */ uint32_t baud; /* Configured baud */ uint8_t irq; /* IRQ associated with this UART */ uint8_t im; /* Interrupt mask state */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Low-level helpers */ static uint32_t up_serialin(struct up_dev_s *priv, int offset); static void up_serialout(struct up_dev_s *priv, int offset, uint32_t value); static void up_restoreuartint(struct up_dev_s *priv, uint8_t im); static void up_disableuartint(struct up_dev_s *priv, uint8_t *im); /* Serial driver methods */ static int up_setup(struct uart_dev_s *dev); static void up_shutdown(struct uart_dev_s *dev); static int up_attach(struct uart_dev_s *dev); static void up_detach(struct uart_dev_s *dev); static int up_interrupt(int irq, void *context, void *arg); static int up_ioctl(struct file *filep, int cmd, unsigned long arg); static int up_receive(struct uart_dev_s *dev, uint32_t *status); static void up_rxint(struct uart_dev_s *dev, bool enable); static bool up_rxavailable(struct uart_dev_s *dev); static void up_send(struct uart_dev_s *dev, int ch); static void up_txint(struct uart_dev_s *dev, bool enable); static bool up_txready(struct uart_dev_s *dev); static bool up_txempty(struct uart_dev_s *dev); /**************************************************************************** * Private Data ****************************************************************************/ static const struct uart_ops_s g_uart_ops = { .setup = up_setup, .shutdown = up_shutdown, .attach = up_attach, .detach = up_detach, .ioctl = up_ioctl, .receive = up_receive, .rxint = up_rxint, .rxavailable = up_rxavailable, #ifdef CONFIG_SERIAL_IFLOWCONTROL .rxflowcontrol = NULL, #endif .send = up_send, .txint = up_txint, .txready = up_txready, .txempty = up_txempty, }; /* I/O buffers */ #ifdef CONFIG_C906_UART0 static char g_uart0rxbuffer[CONFIG_UART0_RXBUFSIZE]; static char g_uart0txbuffer[CONFIG_UART0_TXBUFSIZE]; static uint32_t g_rxdata; #endif #ifdef CONFIG_C906_UART0 static struct up_dev_s g_uart0priv = { .uartbase = C906_UART0_BASE, .baud = CONFIG_UART0_BAUD, .irq = C906_IRQ_UART0, }; static uart_dev_t g_uart0port = { #if SERIAL_CONSOLE == 1 .isconsole = 1, #endif .recv = { .size = CONFIG_UART0_RXBUFSIZE, .buffer = g_uart0rxbuffer, }, .xmit = { .size = CONFIG_UART0_TXBUFSIZE, .buffer = g_uart0txbuffer, }, .ops = &g_uart_ops, .priv = &g_uart0priv, }; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: up_serialin ****************************************************************************/ static uint32_t up_serialin(struct up_dev_s *priv, int offset) { return getreg32(priv->uartbase + offset); } /**************************************************************************** * Name: up_serialout ****************************************************************************/ static void up_serialout(struct up_dev_s *priv, int offset, uint32_t value) { putreg32(value, priv->uartbase + offset); } /**************************************************************************** * Name: up_restoreuartint ****************************************************************************/ static void up_restoreuartint(struct up_dev_s *priv, uint8_t im) { irqstate_t flags = enter_critical_section(); priv->im = im; up_serialout(priv, UART_IE_OFFSET, im); leave_critical_section(flags); } /**************************************************************************** * Name: up_disableuartint ****************************************************************************/ static void up_disableuartint(struct up_dev_s *priv, uint8_t *im) { irqstate_t flags = enter_critical_section(); /* Return the current interrupt mask value */ if (im) { *im = priv->im; } /* Disable all interrupts */ priv->im = 0; up_serialout(priv, UART_IE_OFFSET, 0); leave_critical_section(flags); } /**************************************************************************** * Name: up_setup * * Description: * Configure the UART baud, bits, parity, etc. This method is called the * first time that the serial port is opened. * ****************************************************************************/ static int up_setup(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; /* Enable RX */ up_serialout(priv, UART_RXCTL_OFFSET, IER_RDA_INT_ENABLE | IIR_RECV_LINE_ENABLE); /* nothing to be done for QEMU */ return OK; } /**************************************************************************** * Name: up_shutdown * * Description: * Disable the UART. This method is called when the serial * port is closed * ****************************************************************************/ static void up_shutdown(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; /* Disable interrupts */ up_disableuartint(priv, NULL); } /**************************************************************************** * Name: up_attach * * Description: * Configure the UART to operation in interrupt driven mode. This method is * called when the serial port is opened. Normally, this is just after the * the setup() method is called, however, the serial console may operate in * a non-interrupt driven mode during the boot phase. * * RX and TX interrupts are not enabled by the attach method (unless the * hardware supports multiple levels of interrupt enabling). The RX and TX * interrupts are not enabled until the txint() and rxint() are called. * ****************************************************************************/ static int up_attach(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; int ret; /* Initialize interrupt generation on the peripheral */ up_serialout(priv, UART_IE_OFFSET, IER_RDA_INT_ENABLE | IER_THRE_INT_ENABLE); ret = irq_attach(priv->irq, up_interrupt, dev); if (ret == OK) { /* Enable the interrupt (RX and TX interrupts are still disabled * in the UART */ up_enable_irq(priv->irq); } return ret; } /**************************************************************************** * Name: up_detach * * Description: * Detach UART interrupts. This method is called when the serial port is * closed normally just before the shutdown method is called. The exception * is the serial console which is never shutdown. * ****************************************************************************/ static void up_detach(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; /* Disable interrupts */ up_disable_irq(priv->irq); /* Detach from the interrupt */ irq_detach(priv->irq); } /**************************************************************************** * Name: up_interrupt * * Description: * This is the UART interrupt handler. It will be invoked when an * interrupt is received on the 'irq'. It should call uart_xmitchars or * uart_recvchars to perform the appropriate data transfers. The * interrupt handling logic must be able to map the 'arg' to the * appropriate uart_dev_s structure in order to call these functions. * ****************************************************************************/ /* TODO: why no data ready flag LSR_DATA_READY in LSR under QEMU ??? */ #ifdef CONFIG_C906_WITH_QEMU static volatile uint32_t lsr_data_ready = 0; #endif static int up_interrupt(int irq, void *context, void *arg) { struct uart_dev_s *dev = (struct uart_dev_s *)arg; struct up_dev_s *priv; uint32_t status; int passes; DEBUGASSERT(dev != NULL && dev->priv != NULL); priv = (struct up_dev_s *)dev->priv; /* Loop until there are no characters to be transferred or, * until we have been looping for a long time. */ for (passes = 0; passes < 256; passes++) { /* Retrieve interrupt pending status */ status = up_serialin(priv, UART_IIR_OFFSET); if (status == 0) { break; } if (status & DW_IIR_RECV_DATA) { /* Process incoming bytes */ #ifdef CONFIG_C906_WITH_QEMU lsr_data_ready = 1; #endif uart_recvchars(dev); } if (status & DW_IIR_THR_EMPTY) { /* Process outgoing bytes */ uart_xmitchars(dev); } } return OK; } /**************************************************************************** * Name: up_ioctl * * Description: * All ioctl calls will be routed through this method * ****************************************************************************/ static int up_ioctl(struct file *filep, int cmd, unsigned long arg) { return -ENOTTY; } /**************************************************************************** * Name: up_receive * * Description: * Called (usually) from the interrupt level to receive one * character from the UART. Error bits associated with the * receipt are provided in the return 'status'. * ****************************************************************************/ static int up_receive(struct uart_dev_s *dev, uint32_t *status) { /* Return status information */ if (status) { *status = 0; /* We are not yet tracking serial errors */ } /* Return cached data */ return g_rxdata; } /**************************************************************************** * Name: up_rxint * * Description: * Call to enable or disable RX interrupts * ****************************************************************************/ static void up_rxint(struct uart_dev_s *dev, bool enable) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; irqstate_t flags = enter_critical_section(); if (enable) { #ifndef CONFIG_SUPPRESS_SERIAL_INTS priv->im |= IER_RDA_INT_ENABLE; #endif } else { priv->im &= ~IER_RDA_INT_ENABLE; } up_serialout(priv, UART_IE_OFFSET, priv->im); leave_critical_section(flags); } /**************************************************************************** * Name: up_rxavailable * * Description: * Return true if the receive register is not empty * ****************************************************************************/ static bool up_rxavailable(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; uint32_t dr = 0; /* Return true is data is available in the receive data buffer */ uint32_t rxdata = up_serialin(priv, UART_RXDATA_OFFSET); g_rxdata = rxdata & 0xff; #ifdef CONFIG_C906_WITH_QEMU dr = lsr_data_ready; lsr_data_ready = 0; #else dr = up_serialin(priv, UART_LSR_OFFSET) & LSR_DATA_READY; #endif return !!dr; } /**************************************************************************** * Name: up_send * * Description: * This method will send one byte on the UART. * ****************************************************************************/ static void up_send(struct uart_dev_s *dev, int ch) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; up_serialout(priv, UART_TXDATA_OFFSET, (uint32_t)ch); } /**************************************************************************** * Name: up_txint * * Description: * Call to enable or disable TX interrupts * ****************************************************************************/ static void up_txint(struct uart_dev_s *dev, bool enable) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; irqstate_t flags; flags = enter_critical_section(); if (enable) { /* Enable the TX interrupt */ #ifndef CONFIG_SUPPRESS_SERIAL_INTS priv->im |= IER_THRE_INT_ENABLE; up_serialout(priv, UART_IE_OFFSET, priv->im); /* Fake a TX interrupt here by just calling uart_xmitchars() with * interrupts disabled (note this may recurse). */ uart_xmitchars(dev); #endif } else { /* Disable the TX interrupt */ priv->im &= ~IER_THRE_INT_ENABLE; up_serialout(priv, UART_IE_OFFSET, priv->im); } leave_critical_section(flags); } /**************************************************************************** * Name: up_txready * * Description: * Return true if the tranmsit data register is not full * ****************************************************************************/ static bool up_txready(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; /* Return TRUE if the TX FIFO is not full */ return !!(up_serialin(priv, UART_LSR_OFFSET) & DW_LSR_TRANS_EMPTY); } /**************************************************************************** * Name: up_txempty * * Description: * Return true if the tranmsit data register is empty * ****************************************************************************/ static bool up_txempty(struct uart_dev_s *dev) { struct up_dev_s *priv = (struct up_dev_s *)dev->priv; /* Return TRUE if the TX wartermak is pending */ return !!(up_serialin(priv, UART_LSR_OFFSET) & DW_LSR_TRANS_EMPTY); } /**************************************************************************** * Public Functions ****************************************************************************/ #ifdef USE_EARLYSERIALINIT /**************************************************************************** * Name: riscv_earlyserialinit * * Description: * Performs the low level UART initialization early in debug so that the * serial console will be available during bootup. This must be called * before riscv_serialinit. NOTE: This function depends on GPIO pin * configuration performed in up_consoleinit() and main clock iniialization * performed in up_clkinitialize(). * ****************************************************************************/ void riscv_earlyserialinit(void) { /* Disable interrupts from all UARTS. The console is enabled in * c906_consoleinit(). */ up_disableuartint(TTYS0_DEV.priv, NULL); #ifdef TTYS1_DEV up_disableuartint(TTYS1_DEV.priv, NULL); #endif /* Configuration whichever one is the console */ #ifdef HAVE_SERIAL_CONSOLE CONSOLE_DEV.isconsole = true; up_setup(&CONSOLE_DEV); #endif } #endif /**************************************************************************** * Name: riscv_serialinit * * Description: * Register serial console and serial ports. This assumes * that riscv_earlyserialinit was called previously. * ****************************************************************************/ void riscv_serialinit(void) { /* Register the console */ #ifdef HAVE_SERIAL_CONSOLE uart_register("/dev/console", &CONSOLE_DEV); #endif /* Register all UARTs */ uart_register("/dev/ttyS0", &TTYS0_DEV); #ifdef TTYS1_DEV uart_register("/dev/ttyS1", &TTYS1_DEV); #endif } /**************************************************************************** * Name: up_putc * * Description: * Provide priority, low-level access to support OS debug writes * ****************************************************************************/ int up_putc(int ch) { #ifdef HAVE_SERIAL_CONSOLE struct up_dev_s *priv = (struct up_dev_s *)CONSOLE_DEV.priv; uint8_t imr; up_disableuartint(priv, &imr); /* Check for LF */ if (ch == '\n') { /* Add CR */ riscv_lowputc('\r'); } riscv_lowputc(ch); up_restoreuartint(priv, imr); #endif return ch; } /**************************************************************************** * Name: riscv_earlyserialinit, riscv_serialinit, and up_putc * * Description: * stubs that may be needed. These stubs would be used if all UARTs are * disabled. In that case, the logic in common/up_initialize() is not * smart enough to know that there are not UARTs and will still expect * these interfaces to be provided. * ****************************************************************************/ #else /* HAVE_UART_DEVICE */ void riscv_earlyserialinit(void) { } void riscv_serialinit(void) { } int up_putc(int ch) { return ch; } #endif /* HAVE_UART_DEVICE */ #else /* USE_SERIALDRIVER */ /**************************************************************************** * Name: up_putc * * Description: * Provide priority, low-level access to support OS debug writes * ****************************************************************************/ int up_putc(int ch) { #ifdef HAVE_SERIAL_CONSOLE /* Check for LF */ if (ch == '\n') { /* Add CR */ riscv_lowputc('\r'); } riscv_lowputc(ch); #endif return ch; } #endif /* USE_SERIALDRIVER */