Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * 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 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 = 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