blob: c0f810e5a7bff08d680c179cd4a28d453516d73c [file] [log] [blame]
/*
* 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.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "syscfg/syscfg.h"
#include "mcu/da1469x_pd.h"
#include "mcu/da1469x_hal.h"
#include "mcu/mcu.h"
#include "hal/hal_uart.h"
#include "hal/hal_gpio.h"
#include "defs/error.h"
#include "os/os_trace_api.h"
#include "os/util.h"
#define DA1469X_UART_COUNT 3
struct da1469x_uart {
/* Use UART2 as common type since UART does not have flow control */
UART2_Type *regs;
IRQn_Type irqn;
volatile uint8_t rx_stalled : 1;
volatile uint8_t tx_started : 1;
/* RX temporarily switched to GPIO with interrupt */
volatile uint8_t rx_disconnected : 1;
volatile uint8_t rx_data;
/*
* Stores rx pin func and pointer to cfg. Needed to enable/disable pullup
* when the uart is opened/closed
*/
mcu_gpio_func rx_pin_func;
struct da1469x_uart_cfg *cfg;
hal_uart_rx_char rx_func;
hal_uart_tx_char tx_func;
hal_uart_tx_done tx_done;
void *func_arg;
};
#if MYNEWT_VAL(UART_0)
static struct da1469x_uart da1469x_uart_0;
#endif
#if MYNEWT_VAL(UART_1)
static struct da1469x_uart da1469x_uart_1;
#endif
#if MYNEWT_VAL(UART_2)
static struct da1469x_uart da1469x_uart_2;
#endif
static struct da1469x_uart * const da1469x_uarts[DA1469X_UART_COUNT] = {
#if MYNEWT_VAL(UART_0)
&da1469x_uart_0,
#else
NULL,
#endif
#if MYNEWT_VAL(UART_1)
&da1469x_uart_1,
#else
NULL,
#endif
#if MYNEWT_VAL(UART_2)
&da1469x_uart_2,
#else
NULL,
#endif
};
struct da1469x_uart_baudrate {
uint32_t baudrate;
uint32_t cfg; /* DLH=cfg[23:16] DLL=cfg[15:8] DLF=cfg[7:0] */
};
static const struct da1469x_uart_baudrate da1469x_uart_baudrates[] = {
{ 1000000, 0x00000200 },
{ 500000, 0x00000400 },
{ 230400, 0x0000080b },
{ 115200, 0x00001106 },
{ 57600, 0x0000220c },
{ 38400, 0x00003401 },
{ 28800, 0x00004507 },
{ 19200, 0x00006803 },
{ 14400, 0x00008a0e },
{ 9600, 0x0000d005 },
{ 4800, 0x0001a00b },
};
static inline struct da1469x_uart *
da1469x_uart_resolve(unsigned uart_num)
{
if (uart_num >= DA1469X_UART_COUNT) {
return NULL;
}
return da1469x_uarts[uart_num];
}
static inline uint32_t
da1469x_uart_find_baudrate_cfg(uint32_t baudrate)
{
int i;
for (i = 0; i < ARRAY_SIZE(da1469x_uart_baudrates); i++) {
if (da1469x_uart_baudrates[i].baudrate == baudrate) {
return da1469x_uart_baudrates[i].cfg;
}
}
return 0;
}
static inline void
da1469x_uart_tx_intr_enable(struct da1469x_uart *uart)
{
uart->regs->UART2_IER_DLH_REG |= UART2_UART2_IER_DLH_REG_PTIME_DLH7_Msk |
UART2_UART2_IER_DLH_REG_ETBEI_DLH1_Msk;
}
static inline void
da1469x_uart_tx_intr_disable(struct da1469x_uart *uart)
{
uart->regs->UART2_IER_DLH_REG &= ~(UART2_UART2_IER_DLH_REG_PTIME_DLH7_Msk |
UART2_UART2_IER_DLH_REG_ETBEI_DLH1_Msk);
}
static inline void
da1469x_uart_rx_intr_enable(struct da1469x_uart *uart)
{
uart->regs->UART2_IER_DLH_REG |= UART2_UART2_IER_DLH_REG_ERBFI_DLH0_Msk;
}
static inline void
da1469x_uart_rx_intr_disable(struct da1469x_uart *uart)
{
uart->regs->UART2_IER_DLH_REG &= ~UART2_UART2_IER_DLH_REG_ERBFI_DLH0_Msk;
}
static void
da1469x_uart_set_rx_pin(struct da1469x_uart *uart)
{
int mode = uart->cfg->rx_pullup ? MCU_GPIO_MODE_INPUT_PULLUP : MCU_GPIO_MODE_INPUT;
if (uart->cfg->pin_rx >= 0) {
mcu_gpio_set_pin_function(uart->cfg->pin_rx, mode, uart->rx_pin_func);
}
}
static void
da1469x_uart_set_rx_pin_as_gpio(struct da1469x_uart *uart)
{
int mode = uart->cfg->rx_pullup ? MCU_GPIO_MODE_INPUT_PULLUP : MCU_GPIO_MODE_INPUT;
if (uart->cfg->pin_rx >= 0) {
mcu_gpio_set_pin_function(uart->cfg->pin_rx, mode, MCU_GPIO_FUNC_GPIO);
}
}
#if MYNEWT_VAL(UART_0) || MYNEWT_VAL(UART_1) || MYNEWT_VAL(UART_2)
static void
da1469x_uart_isr_thr_empty(struct da1469x_uart *uart)
{
UART2_Type *regs;
int ch;
regs = uart->regs;
while (regs->UART2_USR_REG & UART2_UART2_USR_REG_UART_TFNF_Msk) {
ch = uart->tx_func(uart->func_arg);
if (ch < 0) {
da1469x_uart_tx_intr_disable(uart);
uart->tx_started = 0;
if (uart->tx_done) {
uart->tx_done(uart->func_arg);
}
break;
}
regs->UART2_RBR_THR_DLL_REG = ch;
}
}
static void
da1469x_uart_isr_recv_data(struct da1469x_uart *uart)
{
UART2_Type *regs;
int ch;
regs = uart->regs;
uart->rx_data = regs->UART2_RBR_THR_DLL_REG;
ch = uart->rx_func(uart->func_arg, uart->rx_data);
if (ch < 0) {
da1469x_uart_rx_intr_disable(uart);
uart->rx_stalled = 1;
}
}
static void
da1469x_uart_reconnect_rx(void *arg)
{
struct da1469x_uart *uart = (struct da1469x_uart *)arg;
/* RX pin high, reconfigure RX pin for UART */
if (hal_gpio_read(uart->cfg->pin_rx) == 1) {
hal_gpio_irq_release(uart->cfg->pin_rx);
uart->rx_disconnected = 0;
da1469x_uart_set_rx_pin(uart);
}
}
void
da1469x_uart_busy(struct da1469x_uart *uart)
{
UART2_Type *regs = uart->regs;
int8_t pin_rx = uart->cfg->pin_rx;
hal_gpio_pull_t mode = uart->cfg->rx_pullup ? HAL_GPIO_PULL_UP : HAL_GPIO_PULL_NONE;
(void)regs->UART2_USR_REG;
(void)regs->UART2_LSR_REG;
/* Busy due to low RX */
if (MYNEWT_VAL(MCU_UART_DICONNECT_RX_ON_BUSY) && hal_gpio_read(pin_rx) == 0) {
hal_gpio_irq_init(pin_rx, da1469x_uart_reconnect_rx, uart,
HAL_GPIO_TRIG_RISING, mode);
uart->rx_disconnected = 1;
hal_gpio_irq_enable(pin_rx);
return;
}
assert(0);
}
static void
da1469x_uart_common_isr(struct da1469x_uart *uart)
{
UART2_Type *regs;
bool no_intr = false;
os_trace_isr_enter();
regs = uart->regs;
while (!no_intr) {
/*
* XXX should be UART2_UART2_IIR_FCR_REG_IIR_FCR_Msk below but this is
* defined (incorrectly) as 0xFF so incorrect value is read.
*/
switch (regs->UART2_IIR_FCR_REG & 0x0F) {
case 0x01: /* no interrupt pending */
no_intr = true;
break;
case 0x02: /* THR empty */
da1469x_uart_isr_thr_empty(uart);
break;
case 0x04: /* received data avaialable */
da1469x_uart_isr_recv_data(uart);
break;
case 0x06: /* receiver line status */
break;
case 0x07: /* busy detect */
da1469x_uart_busy(uart);
break;
case 0x0c: /* character timeout */
break;
default:
assert(0);
break;
}
}
os_trace_isr_exit();
}
#endif
#if MYNEWT_VAL(UART_0)
static void
da1469x_uart_isr(void)
{
da1469x_uart_common_isr(&da1469x_uart_0);
}
#endif
#if MYNEWT_VAL(UART_1)
static void
da1469x_uart2_isr(void)
{
da1469x_uart_common_isr(&da1469x_uart_1);
}
#endif
#if MYNEWT_VAL(UART_2)
static void
da1469x_uart3_isr(void)
{
da1469x_uart_common_isr(&da1469x_uart_2);
}
#endif
void
hal_uart_start_rx(int port)
{
struct da1469x_uart *uart;
uint32_t primask;
int ch;
uart = da1469x_uart_resolve(port);
if (!uart) {
return;
}
if (uart->cfg->pin_rx < 0) {
return;
}
__HAL_DISABLE_INTERRUPTS(primask);
if (uart->rx_stalled) {
ch = uart->rx_func(uart->func_arg, uart->rx_data);
if (ch >= 0) {
uart->rx_stalled = 0;
da1469x_uart_rx_intr_enable(uart);
}
}
__HAL_ENABLE_INTERRUPTS(primask);
}
void
hal_uart_start_tx(int port)
{
struct da1469x_uart *uart;
uint32_t primask;
uart = da1469x_uart_resolve(port);
if (!uart) {
return;
}
__HAL_DISABLE_INTERRUPTS(primask);
if (uart->tx_started == 0) {
uart->tx_started = 1;
da1469x_uart_tx_intr_enable(uart);
}
__HAL_ENABLE_INTERRUPTS(primask);
}
void
hal_uart_blocking_tx(int port, uint8_t data)
{
struct da1469x_uart *uart;
UART2_Type *regs;
uart = da1469x_uart_resolve(port);
if (!uart) {
return;
}
regs = uart->regs;
while (!(regs->UART2_USR_REG & UART2_UART2_USR_REG_UART_TFNF_Msk)) {
/* Wait until FIFO has free space */
}
regs->UART2_RBR_THR_DLL_REG = data;
while (!(regs->UART2_USR_REG & UART2_UART2_USR_REG_UART_TFE_Msk) ||
(regs->UART2_USR_REG & UART2_UART2_USR_REG_UART_BUSY_Msk)) {
/* Wait until FIFO is empty and UART finished tx */
}
}
int
hal_uart_init_cbs(int port, hal_uart_tx_char tx_func, hal_uart_tx_done tx_done,
hal_uart_rx_char rx_func, void *func_arg)
{
struct da1469x_uart *uart;
uart = da1469x_uart_resolve(port);
if (!uart) {
return SYS_EINVAL;
}
uart->rx_func = rx_func;
uart->tx_func = tx_func;
uart->tx_done = tx_done;
uart->func_arg = func_arg;
return 0;
}
int
hal_uart_init(int port, void *arg)
{
struct da1469x_uart_cfg *cfg = arg;
struct da1469x_uart *uart;
UART2_Type *regs;
IRQn_Type irqn;
void (* isr)(void);
mcu_gpio_func gpiofunc[4]; /* RX, TX, RTS, CTS */
uart = da1469x_uart_resolve(port);
if (!uart) {
return SYS_EINVAL;
}
switch (port) {
#if MYNEWT_VAL(UART_0)
case 0:
regs = (void *)UART_BASE;
irqn = UART_IRQn;
isr = da1469x_uart_isr;
gpiofunc[0] = MCU_GPIO_FUNC_UART_TX;
gpiofunc[1] = MCU_GPIO_FUNC_UART_RX;
gpiofunc[2] = 0;
gpiofunc[3] = 0;
break;
#endif
#if MYNEWT_VAL(UART_1)
case 1:
regs = (void *)UART2_BASE;
irqn = UART2_IRQn;
isr = da1469x_uart2_isr;
gpiofunc[0] = MCU_GPIO_FUNC_UART2_TX;
gpiofunc[1] = MCU_GPIO_FUNC_UART2_RX;
gpiofunc[2] = MCU_GPIO_FUNC_UART2_RTSN;
gpiofunc[3] = MCU_GPIO_FUNC_UART2_CTSN;
break;
#endif
#if MYNEWT_VAL(UART_2)
case 2:
regs = (void *)UART3_BASE;
irqn = UART3_IRQn;
isr = da1469x_uart3_isr;
gpiofunc[0] = MCU_GPIO_FUNC_UART3_TX;
gpiofunc[1] = MCU_GPIO_FUNC_UART3_RX;
gpiofunc[2] = MCU_GPIO_FUNC_UART3_RTSN;
gpiofunc[3] = MCU_GPIO_FUNC_UART3_CTSN;
break;
#endif
default:
return SYS_EINVAL;
}
if (((cfg->pin_rts >= 0) && (gpiofunc[2] == 0)) ||
((cfg->pin_cts >= 0) && (gpiofunc[3] == 0))) {
return SYS_ENOTSUP;
}
uart->regs = regs;
uart->irqn = irqn;
/* These are for uart open/close to enable a pullup on the rx line */
uart->rx_pin_func = gpiofunc[1];
uart->cfg = cfg;
mcu_gpio_set_pin_function(cfg->pin_tx, MCU_GPIO_MODE_OUTPUT, gpiofunc[0]);
da1469x_uart_set_rx_pin(uart);
if (cfg->pin_rts >= 0) {
mcu_gpio_set_pin_function(cfg->pin_rts, MCU_GPIO_MODE_OUTPUT, gpiofunc[2]);
}
if (cfg->pin_cts >= 0) {
mcu_gpio_set_pin_function(cfg->pin_cts, MCU_GPIO_MODE_INPUT, gpiofunc[3]);
}
NVIC_DisableIRQ(irqn);
NVIC_SetPriority(irqn, (1 << __NVIC_PRIO_BITS) - 1);
NVIC_SetVector(irqn, (uint32_t)isr);
return 0;
}
int
hal_uart_config(int port, int32_t baudrate, uint8_t databits, uint8_t stopbits,
enum hal_uart_parity parity, enum hal_uart_flow_ctl flow_ctl)
{
struct da1469x_uart *uart;
UART2_Type *regs;
uint32_t reg;
uint32_t baudrate_cfg;
uart = da1469x_uart_resolve(port);
if (!uart) {
return SYS_EINVAL;
}
regs = uart->regs;
if ((databits < 5) || (databits > 8) || (stopbits < 1) || (stopbits > 2)) {
return SYS_EINVAL;
}
switch (port) {
case 0:
CRG_COM->SET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART_ENABLE_Msk;
break;
case 1:
CRG_COM->SET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART2_ENABLE_Msk;
CRG_COM->RESET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART2_CLK_SEL_Msk;
break;
case 2:
CRG_COM->SET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART3_ENABLE_Msk;
CRG_COM->RESET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART3_CLK_SEL_Msk;
break;
default:
assert(0);
break;
}
/* Set baudrate */
baudrate_cfg = da1469x_uart_find_baudrate_cfg(baudrate);
if (!baudrate_cfg) {
return SYS_ENOTSUP;
}
if (uart->cfg->pin_rx >= 0) {
/*
* Switch to GPIO during configuration to prevent landing up in
* busy state if RX line is low.
*/
da1469x_uart_set_rx_pin_as_gpio(uart);
}
regs->UART2_LCR_REG |= UART2_UART2_LCR_REG_UART_DLAB_Msk;
regs->UART2_IER_DLH_REG = (baudrate_cfg >> 16) & 0xff;
regs->UART2_RBR_THR_DLL_REG = (baudrate_cfg >> 8) & 0xff;
regs->UART2_DLF_REG = baudrate_cfg & 0xff;
regs->UART2_LCR_REG &= ~UART2_UART2_LCR_REG_UART_DLAB_Msk;
/* Configure frame */
reg = 0;
switch (parity) {
case HAL_UART_PARITY_NONE:
break;
case HAL_UART_PARITY_EVEN:
reg |= UART2_UART2_LCR_REG_UART_EPS_Msk;
/* no break */
case HAL_UART_PARITY_ODD:
reg |= UART2_UART2_LCR_REG_UART_PEN_Msk;
break;
default:
return SYS_EINVAL;
}
reg |= (stopbits - 1) << UART2_UART2_LCR_REG_UART_STOP_Pos;
reg |= (databits - 5) << UART2_UART2_LCR_REG_UART_DLS_Pos;
regs->UART2_LCR_REG = reg;
/* Enable hardware FIFO */
regs->UART2_SFE_REG = UART2_UART2_SFE_REG_UART_SHADOW_FIFO_ENABLE_Msk;
regs->UART2_SRT_REG = 0;
regs->UART2_STET_REG = 0;
/* Enable flow-control if requested and supported */
if (flow_ctl == HAL_UART_FLOW_CTL_RTS_CTS) {
#if MYNEWT_VAL(UART_0)
if (uart == &da1469x_uart_0) {
return SYS_ENOTSUP;
}
#endif
regs->UART2_MCR_REG |= UART2_UART2_MCR_REG_UART_AFCE_Msk |
UART2_UART2_MCR_REG_UART_RTS_Msk;
}
uart->rx_stalled = 0;
uart->tx_started = 0;
/* Setup interrupt */
NVIC_DisableIRQ(uart->irqn);
NVIC_ClearPendingIRQ(uart->irqn);
NVIC_EnableIRQ(uart->irqn);
/*
* We can acquire PD_COM here to be sure it's acquired only if everything is
* set properly. It's ok to setup UART without acquiring that domain earlier
* explicitly because hal_uart_config shall only be called after hal_uart_init
* and the latter configures GPIOs. Since at least one GPIO is configured, we
* can assume PD_COM is enabled while in active mode.
*/
da1469x_pd_acquire(MCU_PD_DOMAIN_COM);
/* Reconnect RX pin to UART and enable RX interrupt. */
if (uart->cfg->pin_rx >= 0) {
da1469x_uart_rx_intr_enable(uart);
da1469x_uart_set_rx_pin(uart);
}
return 0;
}
int
hal_uart_close(int port)
{
struct da1469x_uart *uart;
int sr;
uart = da1469x_uart_resolve(port);
if (!uart) {
return SYS_EINVAL;
}
da1469x_uart_tx_intr_disable(uart);
da1469x_uart_rx_intr_disable(uart);
switch (port) {
case 0:
CRG_COM->RESET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART_ENABLE_Msk;
break;
case 1:
CRG_COM->RESET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART2_ENABLE_Msk;
break;
case 2:
CRG_COM->RESET_CLK_COM_REG = CRG_COM_SET_CLK_COM_REG_UART3_ENABLE_Msk;
break;
default:
assert(0);
break;
}
/*
* If RX pin was changed to GPIO due busy state, change it back to UART RX
* releasing interrupt that was setup.
*/
if (uart->cfg->pin_rx >= 0) {
OS_ENTER_CRITICAL(sr);
if (uart->rx_disconnected) {
hal_gpio_irq_release(uart->cfg->pin_rx);
uart->rx_disconnected = 0;
da1469x_uart_set_rx_pin(uart);
}
OS_EXIT_CRITICAL(sr);
}
da1469x_pd_release(MCU_PD_DOMAIN_COM);
return 0;
}