zephyr/drivers/serial/uart_sam0.c

338 lines
8.2 KiB
C

/*
* Copyright (c) 2017 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <errno.h>
#include <init.h>
#include <misc/__assert.h>
#include <soc.h>
#include <uart.h>
/* Device constant configuration parameters */
struct uart_sam0_dev_cfg {
SercomUsart *regs;
u32_t baudrate;
u32_t pads;
u32_t pm_apbcmask;
u16_t gclk_clkctrl_id;
#if CONFIG_UART_INTERRUPT_DRIVEN
void (*irq_config_func)(struct device *dev);
#endif
};
/* Device run time data */
struct uart_sam0_dev_data {
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
uart_irq_callback_user_data_t cb;
void *cb_data;
#endif
};
#define DEV_CFG(dev) \
((const struct uart_sam0_dev_cfg *const)(dev)->config->config_info)
#define DEV_DATA(dev) ((struct uart_sam0_dev_data * const)(dev)->driver_data)
static void wait_synchronization(SercomUsart *const usart)
{
#if defined(SERCOM_USART_SYNCBUSY_MASK)
/* SYNCBUSY is a register */
while ((usart->SYNCBUSY.reg & SERCOM_USART_SYNCBUSY_MASK) != 0) {
}
#elif defined(SERCOM_USART_STATUS_SYNCBUSY)
/* SYNCBUSY is a bit */
while ((usart->STATUS.reg & SERCOM_USART_STATUS_SYNCBUSY) != 0) {
}
#else
#error Unsupported device
#endif
}
static int uart_sam0_set_baudrate(SercomUsart *const usart, u32_t baudrate,
u32_t clk_freq_hz)
{
u64_t tmp;
u16_t baud;
tmp = (u64_t)baudrate << 20;
tmp = (tmp + (clk_freq_hz >> 1)) / clk_freq_hz;
/* Verify that the calculated result is within range */
if (tmp < 1 || tmp > UINT16_MAX) {
return -ERANGE;
}
baud = 65536 - (u16_t)tmp;
usart->BAUD.reg = baud;
wait_synchronization(usart);
return 0;
}
static int uart_sam0_init(struct device *dev)
{
int retval;
const struct uart_sam0_dev_cfg *const cfg = DEV_CFG(dev);
SercomUsart *const usart = cfg->regs;
/* Enable the GCLK */
GCLK->CLKCTRL.reg =
cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_CLKEN;
/* Enable SERCOM clock in PM */
PM->APBCMASK.reg |= cfg->pm_apbcmask;
/* Disable all USART interrupts */
usart->INTENCLR.reg = SERCOM_USART_INTENCLR_MASK;
wait_synchronization(usart);
/* 8 bits of data, no parity, 1 stop bit in normal mode */
usart->CTRLA.reg =
cfg->pads |
/* Internal clock */
SERCOM_USART_CTRLA_MODE_USART_INT_CLK
#if defined(SERCOM_USART_CTRLA_SAMPR)
/* 16x oversampling with arithmetic baud rate generation */
| SERCOM_USART_CTRLA_SAMPR(0)
#endif
| SERCOM_USART_CTRLA_FORM(0) |
SERCOM_USART_CTRLA_CPOL | SERCOM_USART_CTRLA_DORD;
wait_synchronization(usart);
/* Enable receiver and transmitter */
usart->CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE(0) |
SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN;
wait_synchronization(usart);
retval = uart_sam0_set_baudrate(usart, cfg->baudrate,
SOC_ATMEL_SAM0_GCLK0_FREQ_HZ);
if (retval != 0) {
return retval;
}
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
cfg->irq_config_func(dev);
#endif
usart->CTRLA.bit.ENABLE = 1;
wait_synchronization(usart);
return 0;
}
static int uart_sam0_poll_in(struct device *dev, unsigned char *c)
{
SercomUsart *const usart = DEV_CFG(dev)->regs;
if (!usart->INTFLAG.bit.RXC) {
return -EBUSY;
}
*c = (unsigned char)usart->DATA.reg;
return 0;
}
static void uart_sam0_poll_out(struct device *dev, unsigned char c)
{
SercomUsart *const usart = DEV_CFG(dev)->regs;
while (!usart->INTFLAG.bit.DRE) {
}
/* send a character */
usart->DATA.reg = c;
}
#if CONFIG_UART_INTERRUPT_DRIVEN
static void uart_sam0_isr(void *arg)
{
struct device *dev = arg;
struct uart_sam0_dev_data *const dev_data = DEV_DATA(dev);
if (dev_data->cb) {
dev_data->cb(dev_data->cb_data);
}
}
static int uart_sam0_fifo_fill(struct device *dev, const u8_t *tx_data, int len)
{
SercomUsart *regs = DEV_CFG(dev)->regs;
if (regs->INTFLAG.bit.DRE && len >= 1) {
regs->DATA.reg = tx_data[0];
return 1;
} else {
return 0;
}
}
static void uart_sam0_irq_tx_enable(struct device *dev)
{
SercomUsart *regs = DEV_CFG(dev)->regs;
regs->INTENSET.reg = SERCOM_USART_INTENCLR_DRE;
}
static void uart_sam0_irq_tx_disable(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
regs->INTENCLR.reg = SERCOM_USART_INTENCLR_DRE;
}
static int uart_sam0_irq_tx_ready(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
return regs->INTFLAG.bit.DRE != 0;
}
static void uart_sam0_irq_rx_enable(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
regs->INTENSET.reg = SERCOM_USART_INTENSET_RXC;
}
static void uart_sam0_irq_rx_disable(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
regs->INTENCLR.reg = SERCOM_USART_INTENCLR_RXC;
}
static int uart_sam0_irq_rx_ready(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
return regs->INTFLAG.bit.RXC != 0;
}
static int uart_sam0_fifo_read(struct device *dev, u8_t *rx_data,
const int size)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
if (regs->INTFLAG.bit.RXC) {
u8_t ch = regs->DATA.reg;
if (size >= 1) {
*rx_data = ch;
return 1;
} else {
return -EINVAL;
}
}
return 0;
}
static int uart_sam0_irq_is_pending(struct device *dev)
{
SercomUsart *const regs = DEV_CFG(dev)->regs;
return (regs->INTENSET.reg & regs->INTFLAG.reg) != 0;
}
static int uart_sam0_irq_update(struct device *dev) { return 1; }
static void uart_sam0_irq_callback_set(struct device *dev,
uart_irq_callback_user_data_t cb,
void *cb_data)
{
struct uart_sam0_dev_data *const dev_data = DEV_DATA(dev);
dev_data->cb = cb;
dev_data->cb_data = cb_data;
}
#endif
static const struct uart_driver_api uart_sam0_driver_api = {
.poll_in = uart_sam0_poll_in,
.poll_out = uart_sam0_poll_out,
#if CONFIG_UART_INTERRUPT_DRIVEN
.fifo_fill = uart_sam0_fifo_fill,
.fifo_read = uart_sam0_fifo_read,
.irq_tx_enable = uart_sam0_irq_tx_enable,
.irq_tx_disable = uart_sam0_irq_tx_disable,
.irq_tx_ready = uart_sam0_irq_tx_ready,
.irq_rx_enable = uart_sam0_irq_rx_enable,
.irq_rx_disable = uart_sam0_irq_rx_disable,
.irq_rx_ready = uart_sam0_irq_rx_ready,
.irq_is_pending = uart_sam0_irq_is_pending,
.irq_update = uart_sam0_irq_update,
.irq_callback_set = uart_sam0_irq_callback_set,
#endif
};
#if CONFIG_UART_INTERRUPT_DRIVEN
#define UART_SAM0_IRQ_HANDLER_DECL(n) \
static void uart_sam0_irq_config_##n(struct device *dev)
#define UART_SAM0_IRQ_HANDLER_FUNC(n) \
.irq_config_func = uart_sam0_irq_config_##n,
#define UART_SAM0_IRQ_HANDLER(n) \
static void uart_sam0_irq_config_##n(struct device *dev) \
{ \
IRQ_CONNECT(DT_UART_SAM0_SERCOM##n##_IRQ, \
DT_UART_SAM0_SERCOM##n##_IRQ_PRIORITY, \
uart_sam0_isr, DEVICE_GET(uart_sam0_##n), \
0); \
irq_enable(DT_UART_SAM0_SERCOM##n##_IRQ); \
}
#else
#define UART_SAM0_IRQ_HANDLER_DECL(n)
#define UART_SAM0_IRQ_HANDLER_FUNC(n)
#define UART_SAM0_IRQ_HANDLER(n)
#endif
#define CONFIG_UART_SAM0_SERCOM_PADS(n) \
(DT_UART_SAM0_SERCOM##n##_RXPO << SERCOM_USART_CTRLA_RXPO_Pos) | \
(DT_UART_SAM0_SERCOM##n##_TXPO << SERCOM_USART_CTRLA_TXPO_Pos)
#define UART_SAM0_CONFIG_DEFN(n) \
static const struct uart_sam0_dev_cfg uart_sam0_config_##n = { \
.regs = (SercomUsart *)DT_UART_SAM0_SERCOM##n##_BASE_ADDRESS, \
.baudrate = DT_UART_SAM0_SERCOM##n##_CURRENT_SPEED, \
.pm_apbcmask = PM_APBCMASK_SERCOM##n, \
.gclk_clkctrl_id = GCLK_CLKCTRL_ID_SERCOM##n##_CORE, \
.pads = CONFIG_UART_SAM0_SERCOM_PADS(n), \
UART_SAM0_IRQ_HANDLER_FUNC(n) \
}
#define UART_SAM0_DEVICE_INIT(n) \
static struct uart_sam0_dev_data uart_sam0_data_##n; \
UART_SAM0_IRQ_HANDLER_DECL(n); \
UART_SAM0_CONFIG_DEFN(n); \
DEVICE_AND_API_INIT(uart_sam0_##n, DT_UART_SAM0_SERCOM##n##_LABEL, \
uart_sam0_init, &uart_sam0_data_##n, \
&uart_sam0_config_##n, PRE_KERNEL_1, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&uart_sam0_driver_api); \
UART_SAM0_IRQ_HANDLER(n)
#if DT_UART_SAM0_SERCOM0_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(0)
#endif
#if DT_UART_SAM0_SERCOM1_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(1)
#endif
#if DT_UART_SAM0_SERCOM2_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(2)
#endif
#if DT_UART_SAM0_SERCOM3_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(3)
#endif
#if DT_UART_SAM0_SERCOM4_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(4)
#endif
#if DT_UART_SAM0_SERCOM5_BASE_ADDRESS
UART_SAM0_DEVICE_INIT(5)
#endif