338 lines
8.2 KiB
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
|