nuttx/arch/arm/src/cxd56xx/cxd56_uart.c
Xiang Xiao 001e7c3e76 sched: Don't include nuttx/sched.h inside sched.h
But let nuttx/sched.h include sched.h instead to
avoid expose nuttx kernel API to userspace.

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2021-05-24 12:11:53 +09:00

529 lines
14 KiB
C

/****************************************************************************
* arch/arm/src/cxd56xx/cxd56_uart.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 <nuttx/config.h>
#include <stdint.h>
#include <nuttx/spinlock.h>
#include <arch/board/board.h>
#include <errno.h>
#include "arm_internal.h"
#include "arm_arch.h"
#include "chip.h"
#include "cxd56_config.h"
#include "cxd56_clock.h"
#include "cxd56_uart.h"
#include "cxd56_pinconfig.h"
#include "cxd56_powermgr.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Select UART parameters for the selected console */
#if defined(CONFIG_UART0_SERIAL_CONSOLE)
#define CONSOLE_BASE CXD56_UART0_BASE
#define CONSOLE_BASEFREQ BOARD_UART0_BASEFREQ
#define CONSOLE_BAUD CONFIG_UART0_BAUD
#define CONSOLE_BITS CONFIG_UART0_BITS
#define CONSOLE_PARITY CONFIG_UART0_PARITY
#define CONSOLE_2STOP CONFIG_UART0_2STOP
#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
#define CONSOLE_BASE CXD56_UART1_BASE
#define CONSOLE_BASEFREQ BOARD_UART1_BASEFREQ
#define CONSOLE_BAUD CONFIG_UART1_BAUD
#define CONSOLE_BITS CONFIG_UART1_BITS
#define CONSOLE_PARITY CONFIG_UART1_PARITY
#define CONSOLE_2STOP CONFIG_UART1_2STOP
#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
#define CONSOLE_BASE CXD56_UART2_BASE
#define CONSOLE_BASEFREQ BOARD_UART2_BASEFREQ
#define CONSOLE_BAUD CONFIG_UART2_BAUD
#define CONSOLE_BITS CONFIG_UART2_BITS
#define CONSOLE_PARITY CONFIG_UART2_PARITY
#define CONSOLE_2STOP CONFIG_UART2_2STOP
#elif defined(HAVE_CONSOLE)
#error "No CONFIG_UARTn_SERIAL_CONSOLE Setting"
#endif
/* Get word length setting for the console */
#if CONSOLE_BITS >= 5 && CONSOLE_BITS <= 8
#define CONSOLE_LCR_WLS UART_LCR_WLEN(CONSOLE_BITS)
#elif defined(HAVE_CONSOLE)
#error "Invalid CONFIG_UARTn_BITS setting for console "
#endif
/* Get parity setting for the console */
#if CONSOLE_PARITY == 0
#define CONSOLE_LCR_PAR 0
#elif CONSOLE_PARITY == 1
#define CONSOLE_LCR_PAR (UART_LCR_PEN)
#elif CONSOLE_PARITY == 2
#define CONSOLE_LCR_PAR (UART_LCR_PEN | UART_LCR_EPS)
#elif CONSOLE_PARITY == 3
#define CONSOLE_LCR_PAR (UART_LCR_PEN | UART_LCR_SPS)
#elif CONSOLE_PARITY == 4
#define CONSOLE_LCR_PAR (UART_LCR_PEN | UART_LCR_EPS | UART_LCR_SPS)
#elif defined(HAVE_CONSOLE)
#error "Invalid CONFIG_UARTn_PARITY setting for CONSOLE"
#endif
/* Get stop-bit setting for the console and UART0/1/2 */
#if CONSOLE_2STOP != 0
#define CONSOLE_LCR_STOP UART_LCR_STP2
#else
#define CONSOLE_LCR_STOP 0
#endif
/* LCR and FCR values for the console */
#define CONSOLE_LCR_VALUE (CONSOLE_LCR_WLS | CONSOLE_LCR_PAR | CONSOLE_LCR_STOP)
/****************************************************************************
* Private Types
****************************************************************************/
struct uartdev
{
uintptr_t uartbase; /* Base address of UART registers */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
static struct uartdev g_uartdevs[] =
{
{
CXD56_UART0_BASE
},
{
CXD56_UART1_BASE
},
{
CXD56_UART2_BASE
}
};
static uint32_t g_lcr;
static uint32_t g_cr;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: cxd56_uart_pincontrol
*
* Description:
* Configure the UART pin
*
* Input Parameter:
* ch - channel number
* on - true: enable pin, false: disable pin
*
****************************************************************************/
static void cxd56_uart_pincontrol(int ch, bool on)
{
switch (ch)
{
#ifdef CONFIG_CXD56_UART1
case 1:
if (on)
{
CXD56_PIN_CONFIGS(PINCONFS_SPI0A_UART1);
}
else
{
CXD56_PIN_CONFIGS(PINCONFS_SPI0A_GPIO);
}
break;
#endif
#ifdef CONFIG_CXD56_UART2
case 2:
if (on)
{
CXD56_PIN_CONFIGS(PINCONFS_UART2);
}
else
{
CXD56_PIN_CONFIGS(PINCONFS_UART2_GPIO);
}
break;
#endif
default:
break;
}
}
/****************************************************************************
* Name: cxd56_uart_start
*
* Description:
* Start a UART. These functions are used by the serial driver when a
* UART is start.
*
****************************************************************************/
static void cxd56_uart_start(int ch)
{
irqstate_t flags = enter_critical_section();
cxd56_setbaud(CONSOLE_BASE, CONSOLE_BASEFREQ, CONSOLE_BAUD);
putreg32(g_lcr, g_uartdevs[ch].uartbase + CXD56_UART_LCR_H);
putreg32(g_cr, g_uartdevs[ch].uartbase + CXD56_UART_CR);
leave_critical_section(flags);
}
/****************************************************************************
* Name: cxd56_uart_stop
*
* Description:
* Stop a UART. These functions are used by the serial driver when a
* UART is stop.
*
****************************************************************************/
static void cxd56_uart_stop(int ch)
{
uint32_t cr;
irqstate_t flags = enter_critical_section();
while (UART_FR_BUSY & getreg32(g_uartdevs[ch].uartbase + CXD56_UART_FR));
cr = getreg32(g_uartdevs[ch].uartbase + CXD56_UART_CR);
g_cr = cr;
cr &= ~UART_CR_EN;
putreg32(cr, g_uartdevs[ch].uartbase + CXD56_UART_CR);
g_lcr = getreg32(g_uartdevs[ch].uartbase + CXD56_UART_LCR_H);
putreg32(0, g_uartdevs[ch].uartbase + CXD56_UART_LCR_H);
leave_critical_section(flags);
}
/****************************************************************************
* Name: cxd56_uart_have_rxdata
*
* Description:
* Check if there is the remaining data in received FIFO or not
*
****************************************************************************/
static int cxd56_uart_have_rxdata(int ch)
{
if (UART_FR_RXFE & getreg32(g_uartdevs[ch].uartbase + CXD56_UART_FR))
{
return 0; /* Rx FIFO is empty */
}
else
{
return 1; /* Rx FIFO have data */
}
}
/****************************************************************************
* Name: cxd56_uart_clockchange
*
* Description:
* pm event callback for uart console
*
****************************************************************************/
static int cxd56_uart_clockchange(uint8_t id)
{
#ifdef HAVE_CONSOLE
#if defined(CONFIG_UART0_SERIAL_CONSOLE)
int ch = 0;
#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
int ch = 1;
#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
int ch = 2;
#endif
switch (id)
{
case CXD56_PM_CALLBACK_ID_CLK_CHG_START:
cxd56_uart_stop(ch);
break;
case CXD56_PM_CALLBACK_ID_CLK_CHG_END:
cxd56_uart_start(ch);
break;
case CXD56_PM_CALLBACK_ID_HOT_BOOT:
if (cxd56_uart_have_rxdata(ch))
{
return -1; /* don't restart if processing data in rxfifo */
}
cxd56_uart_stop(ch);
cxd56_uart_start(ch);
break;
default:
break;
}
#endif
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: arm_lowputc
*
* Description:
* Output one byte on the serial console
*
****************************************************************************/
void arm_lowputc(char ch)
{
#if defined HAVE_UART && defined HAVE_CONSOLE
/* Wait for the transmitter to be available */
while ((getreg32(CONSOLE_BASE + CXD56_UART_FR) & UART_FLAG_TXFF));
/* Send the character */
putreg32((uint32_t)ch, CONSOLE_BASE + CXD56_UART_DR);
#endif
}
/****************************************************************************
* Name: cxd56_lowsetup
*
* Description:
* This performs basic initialization of the UART used for the serial
* console. Its purpose is to get the console output available as soon
* as possible.
*
* The USART0/2/3 and UART1 peripherals are configured using the following
* registers:
* 1. Baud rate: In the LCR register, set bit DLAB = 1. This enables access
* to registers DLL and DLM for setting the baud rate. Also, if needed,
* set the fractional baud rate in the fractional divider
* 2. UART FIFO: Use bit FIFO enable (bit 0) in FCR register to
* enable FIFO.
* 3. Pins: Select UART pins through the PINSEL registers and pin modes
* through the PINMODE registers. UART receive pins should not have
* pull-down resistors enabled.
* 4. Interrupts: To enable UART interrupts set bit DLAB = 0 in the LCRF
* register. This enables access to IER. Interrupts are enabled
* in the NVIC using the appropriate Interrupt Set Enable register.
* 5. DMA: UART transmit and receive functions can operate with the
* GPDMA controller.
*
****************************************************************************/
void cxd56_lowsetup(void)
{
#ifdef HAVE_UART
/* Enable clocking and for all console UART and disable power for
* other UARTs
*/
#if defined(CONFIG_UART0_SERIAL_CONSOLE)
cxd56_uart_setup(0);
#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
cxd56_uart_setup(1);
#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
cxd56_uart_setup(2);
#endif
/* Configure the console (only) */
#if defined(HAVE_CONSOLE) && !defined(CONFIG_SUPPRESS_UART_CONFIG)
{
uint32_t val;
val = getreg32(CONSOLE_BASE + CXD56_UART_CR);
if (val & UART_CR_EN)
{
return;
}
}
putreg32(CONSOLE_LCR_VALUE, CONSOLE_BASE + CXD56_UART_LCR_H);
cxd56_setbaud(CONSOLE_BASE, CONSOLE_BASEFREQ, CONSOLE_BAUD);
putreg32(0, CONSOLE_BASE + CXD56_UART_IFLS);
putreg32(UART_INTR_ALL, CONSOLE_BASE + CXD56_UART_ICR);
#endif
#endif
}
/****************************************************************************
* Name: cxd56_uart_reset
*
* Description:
* Reset a UART. These functions are used by the serial driver when a
* UART is closed.
*
****************************************************************************/
void cxd56_uart_reset(int ch)
{
/* Configure pin */
cxd56_uart_pincontrol(ch, false);
/* TODO: clock down */
#ifdef CONFIG_CXD56_UART2
if (ch == 2)
{
cxd56_img_uart_clock_disable();
}
#endif
}
void cxd56_uart_setup(int ch)
{
uint32_t cr;
uint32_t lcr;
/* TODO: clock configuration */
#ifdef CONFIG_CXD56_UART2
if (ch == 2)
{
cxd56_img_uart_clock_enable();
}
#endif
cr = getreg32(g_uartdevs[ch].uartbase + CXD56_UART_CR);
putreg32(cr & ~(1 << 0), g_uartdevs[ch].uartbase + CXD56_UART_CR);
lcr = getreg32(g_uartdevs[ch].uartbase + CXD56_UART_LCR_H);
putreg32(lcr & ~(1 << 4), g_uartdevs[ch].uartbase + CXD56_UART_LCR_H);
putreg32(cr, g_uartdevs[ch].uartbase + CXD56_UART_CR);
/* Configure pin */
cxd56_uart_pincontrol(ch, true);
}
/****************************************************************************
* Name: cxd56_setbaud
*
****************************************************************************/
void cxd56_setbaud(uintptr_t uartbase, uint32_t basefreq, uint32_t baud)
{
uint32_t ibrd;
uint32_t fbrd;
uint32_t div;
uint32_t lcr_h;
irqstate_t flags = spin_lock_irqsave(NULL);
if (uartbase == CXD56_UART2_BASE)
{
basefreq = cxd56_get_img_uart_baseclock();
}
else if (uartbase == CXD56_UART1_BASE)
{
basefreq = cxd56_get_com_baseclock();
}
else
{
spin_unlock_irqrestore(NULL, flags);
return;
}
div = basefreq / (16 * baud / 100);
ibrd = div / 100;
/* fbrd will be up to 63 ((99 * 64 + 50) / 100 = 6386 / 100 = 63) */
fbrd = (((div % 100) * 64) + 50) / 100;
/* Invalid baud rate divider setting combination */
if (ibrd == 0 || (ibrd == 65535 && fbrd != 0))
{
goto finish;
}
putreg32(ibrd, uartbase + CXD56_UART_IBRD);
putreg32(fbrd, uartbase + CXD56_UART_FBRD);
/* Baud rate is updated by writing to LCR_H */
lcr_h = getreg32(uartbase + CXD56_UART_LCR_H);
putreg32(lcr_h, uartbase + CXD56_UART_LCR_H);
finish:
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Name: cxd56_uart_initialize
*
* Description:
* Various initial registration
*
****************************************************************************/
int cxd56_uart_initialize(void)
{
#ifdef HAVE_UART
# if defined(CONFIG_UART0_SERIAL_CONSOLE)
cxd56_pm_register_callback(PM_CLOCK_HIF_UART0, cxd56_uart_clockchange);
# elif defined(CONFIG_UART1_SERIAL_CONSOLE)
cxd56_pm_register_callback(PM_CLOCK_SYS_UART1, cxd56_uart_clockchange);
# elif defined(CONFIG_UART2_SERIAL_CONSOLE)
cxd56_pm_register_callback(PM_CLOCK_APP_UART, cxd56_uart_clockchange);
# endif
#endif
return 0;
}