4709816a69
Move SERCOM peripherals to use the raw defines generated from DTS parsing. This adds aliases to the DTS so that the SERCOM number can still be used for clocking and pinmux. Signed-off-by: Derek Hageman <hageman@inthat.cloud>
801 lines
19 KiB
C
801 lines
19 KiB
C
/*
|
|
* Copyright (c) 2017 Google LLC.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(spi_sam0);
|
|
|
|
#include "spi_context.h"
|
|
#include <errno.h>
|
|
#include <device.h>
|
|
#include <spi.h>
|
|
#include <soc.h>
|
|
#include <dma.h>
|
|
|
|
/* Device constant configuration parameters */
|
|
struct spi_sam0_config {
|
|
SercomSpi *regs;
|
|
u32_t pads;
|
|
u32_t pm_apbcmask;
|
|
u16_t gclk_clkctrl_id;
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
u8_t tx_dma_request;
|
|
u8_t tx_dma_channel;
|
|
u8_t rx_dma_request;
|
|
u8_t rx_dma_channel;
|
|
#endif
|
|
};
|
|
|
|
/* Device run time data */
|
|
struct spi_sam0_data {
|
|
struct spi_context ctx;
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
struct device *dma;
|
|
u32_t dma_segment_len;
|
|
#endif
|
|
};
|
|
|
|
static void wait_synchronization(SercomSpi *regs)
|
|
{
|
|
#if defined(SERCOM_SPI_SYNCBUSY_MASK)
|
|
/* SYNCBUSY is a register */
|
|
while ((regs->SYNCBUSY.reg & SERCOM_SPI_SYNCBUSY_MASK) != 0) {
|
|
}
|
|
#elif defined(SERCOM_SPI_STATUS_SYNCBUSY)
|
|
/* SYNCBUSY is a bit */
|
|
while ((regs->STATUS.reg & SERCOM_SPI_STATUS_SYNCBUSY) != 0) {
|
|
}
|
|
#else
|
|
#error Unsupported device
|
|
#endif
|
|
}
|
|
|
|
static int spi_sam0_configure(struct device *dev,
|
|
const struct spi_config *config)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
SercomSpi *regs = cfg->regs;
|
|
SERCOM_SPI_CTRLA_Type ctrla = {.reg = 0};
|
|
SERCOM_SPI_CTRLB_Type ctrlb = {.reg = 0};
|
|
int div;
|
|
|
|
if (spi_context_configured(&data->ctx, config)) {
|
|
return 0;
|
|
}
|
|
|
|
if (SPI_OP_MODE_GET(config->operation) != SPI_OP_MODE_MASTER) {
|
|
/* Slave mode is not implemented. */
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
ctrla.bit.MODE = SERCOM_SPI_CTRLA_MODE_SPI_MASTER_Val;
|
|
|
|
if ((config->operation & SPI_TRANSFER_LSB) != 0U) {
|
|
ctrla.bit.DORD = 1;
|
|
}
|
|
|
|
if ((config->operation & SPI_MODE_CPOL) != 0U) {
|
|
ctrla.bit.CPOL = 1;
|
|
}
|
|
|
|
if ((config->operation & SPI_MODE_CPHA) != 0U) {
|
|
ctrla.bit.CPHA = 1;
|
|
}
|
|
|
|
ctrla.reg |= cfg->pads;
|
|
|
|
if ((config->operation & SPI_MODE_LOOP) != 0U) {
|
|
/* Put MISO and MOSI on the same pad */
|
|
ctrla.bit.DOPO = 0;
|
|
ctrla.bit.DIPO = 0;
|
|
}
|
|
|
|
ctrla.bit.ENABLE = 1;
|
|
ctrlb.bit.RXEN = 1;
|
|
|
|
if (SPI_WORD_SIZE_GET(config->operation) != 8) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* 8 bits per transfer */
|
|
ctrlb.bit.CHSIZE = 0;
|
|
|
|
/* Use the requested or next higest possible frequency */
|
|
div = (SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / config->frequency) / 2U - 1;
|
|
div = MAX(0, MIN(UINT8_MAX, div));
|
|
|
|
/* Update the configuration only if it has changed */
|
|
if (regs->CTRLA.reg != ctrla.reg || regs->CTRLB.reg != ctrlb.reg ||
|
|
regs->BAUD.reg != div) {
|
|
regs->CTRLA.bit.ENABLE = 0;
|
|
wait_synchronization(regs);
|
|
|
|
regs->CTRLB = ctrlb;
|
|
wait_synchronization(regs);
|
|
regs->BAUD.reg = div;
|
|
wait_synchronization(regs);
|
|
regs->CTRLA = ctrla;
|
|
wait_synchronization(regs);
|
|
}
|
|
|
|
data->ctx.config = config;
|
|
spi_context_cs_configure(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool spi_sam0_transfer_ongoing(struct spi_sam0_data *data)
|
|
{
|
|
return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx);
|
|
}
|
|
|
|
static void spi_sam0_shift_master(SercomSpi *regs, struct spi_sam0_data *data)
|
|
{
|
|
u8_t tx;
|
|
u8_t rx;
|
|
|
|
if (spi_context_tx_buf_on(&data->ctx)) {
|
|
tx = *(u8_t *)(data->ctx.tx_buf);
|
|
} else {
|
|
tx = 0U;
|
|
}
|
|
|
|
while (!regs->INTFLAG.bit.DRE) {
|
|
}
|
|
|
|
regs->DATA.reg = tx;
|
|
spi_context_update_tx(&data->ctx, 1, 1);
|
|
|
|
while (!regs->INTFLAG.bit.RXC) {
|
|
}
|
|
|
|
rx = regs->DATA.reg;
|
|
|
|
if (spi_context_rx_buf_on(&data->ctx)) {
|
|
*data->ctx.rx_buf = rx;
|
|
}
|
|
spi_context_update_rx(&data->ctx, 1, 1);
|
|
}
|
|
|
|
/* Finish any ongoing writes and drop any remaining read data */
|
|
static void spi_sam0_finish(SercomSpi *regs)
|
|
{
|
|
while (!regs->INTFLAG.bit.TXC) {
|
|
}
|
|
|
|
while (regs->INTFLAG.bit.RXC) {
|
|
(void)regs->DATA.reg;
|
|
}
|
|
}
|
|
|
|
/* Fast path that transmits a buf */
|
|
static void spi_sam0_fast_tx(SercomSpi *regs, const struct spi_buf *tx_buf)
|
|
{
|
|
const u8_t *p = tx_buf->buf;
|
|
const u8_t *pend = (u8_t *)tx_buf->buf + tx_buf->len;
|
|
u8_t ch;
|
|
|
|
while (p != pend) {
|
|
ch = *p++;
|
|
|
|
while (!regs->INTFLAG.bit.DRE) {
|
|
}
|
|
|
|
regs->DATA.reg = ch;
|
|
}
|
|
|
|
spi_sam0_finish(regs);
|
|
}
|
|
|
|
/* Fast path that reads into a buf */
|
|
static void spi_sam0_fast_rx(SercomSpi *regs, const struct spi_buf *rx_buf)
|
|
{
|
|
u8_t *rx = rx_buf->buf;
|
|
int len = rx_buf->len;
|
|
|
|
if (len <= 0) {
|
|
return;
|
|
}
|
|
|
|
/* See the comment in spi_sam0_fast_txrx re: interleaving. */
|
|
|
|
/* Write the first byte */
|
|
regs->DATA.reg = 0;
|
|
len--;
|
|
|
|
while (len) {
|
|
/* Load byte N+1 into the transmit register */
|
|
regs->DATA.reg = 0;
|
|
len--;
|
|
|
|
/* Read byte N+0 from the receive register */
|
|
while (!regs->INTFLAG.bit.RXC) {
|
|
}
|
|
|
|
*rx++ = regs->DATA.reg;
|
|
}
|
|
|
|
/* Read the final incoming byte */
|
|
while (!regs->INTFLAG.bit.RXC) {
|
|
}
|
|
|
|
*rx = regs->DATA.reg;
|
|
|
|
spi_sam0_finish(regs);
|
|
}
|
|
|
|
/* Fast path that writes and reads bufs of the same length */
|
|
static void spi_sam0_fast_txrx(SercomSpi *regs,
|
|
const struct spi_buf *tx_buf,
|
|
const struct spi_buf *rx_buf)
|
|
{
|
|
const u8_t *tx = tx_buf->buf;
|
|
const u8_t *txend = (u8_t *)tx_buf->buf + tx_buf->len;
|
|
u8_t *rx = rx_buf->buf;
|
|
size_t len = rx_buf->len;
|
|
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The code below interleaves the transmit writes with the
|
|
* receive reads to keep the bus fully utilised. The code is
|
|
* equivalent to:
|
|
*
|
|
* Transmit byte 0
|
|
* Loop:
|
|
* - Transmit byte n+1
|
|
* - Receive byte n
|
|
* Receive the final byte
|
|
*/
|
|
|
|
/* Write the first byte */
|
|
regs->DATA.reg = *tx++;
|
|
|
|
while (tx != txend) {
|
|
/* Load byte N+1 into the transmit register. TX is
|
|
* single buffered and we have at most one byte in
|
|
* flight so skip the DRE check.
|
|
*/
|
|
regs->DATA.reg = *tx++;
|
|
|
|
/* Read byte N+0 from the receive register */
|
|
while (!regs->INTFLAG.bit.RXC) {
|
|
}
|
|
|
|
*rx++ = regs->DATA.reg;
|
|
}
|
|
|
|
/* Read the final incoming byte */
|
|
while (!regs->INTFLAG.bit.RXC) {
|
|
}
|
|
|
|
*rx = regs->DATA.reg;
|
|
|
|
spi_sam0_finish(regs);
|
|
}
|
|
|
|
/* Fast path where every overlapping tx and rx buffer is the same length */
|
|
static void spi_sam0_fast_transceive(struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
size_t tx_count = 0;
|
|
size_t rx_count = 0;
|
|
SercomSpi *regs = cfg->regs;
|
|
const struct spi_buf *tx = NULL;
|
|
const struct spi_buf *rx = NULL;
|
|
|
|
if (tx_bufs) {
|
|
tx = tx_bufs->buffers;
|
|
tx_count = tx_bufs->count;
|
|
}
|
|
|
|
if (rx_bufs) {
|
|
rx = rx_bufs->buffers;
|
|
rx_count = rx_bufs->count;
|
|
} else {
|
|
rx = NULL;
|
|
}
|
|
|
|
while (tx_count != 0 && rx_count != 0) {
|
|
if (tx->buf == NULL) {
|
|
spi_sam0_fast_rx(regs, rx);
|
|
} else if (rx->buf == NULL) {
|
|
spi_sam0_fast_tx(regs, tx);
|
|
} else {
|
|
spi_sam0_fast_txrx(regs, tx, rx);
|
|
}
|
|
|
|
tx++;
|
|
tx_count--;
|
|
rx++;
|
|
rx_count--;
|
|
}
|
|
|
|
for (; tx_count != 0; tx_count--) {
|
|
spi_sam0_fast_tx(regs, tx++);
|
|
}
|
|
|
|
for (; rx_count != 0; rx_count--) {
|
|
spi_sam0_fast_rx(regs, rx++);
|
|
}
|
|
}
|
|
|
|
/* Returns true if the request is suitable for the fast
|
|
* path. Specifically, the bufs are a sequence of:
|
|
*
|
|
* - Zero or more RX and TX buf pairs where each is the same length.
|
|
* - Zero or more trailing RX only bufs
|
|
* - Zero or more trailing TX only bufs
|
|
*/
|
|
static bool spi_sam0_is_regular(const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
const struct spi_buf *tx = NULL;
|
|
const struct spi_buf *rx = NULL;
|
|
size_t tx_count = 0;
|
|
size_t rx_count = 0;
|
|
|
|
if (tx_bufs) {
|
|
tx = tx_bufs->buffers;
|
|
tx_count = tx_bufs->count;
|
|
}
|
|
|
|
if (rx_bufs) {
|
|
rx = rx_bufs->buffers;
|
|
rx_count = rx_bufs->count;
|
|
}
|
|
|
|
if (!tx || !rx) {
|
|
return false;
|
|
}
|
|
|
|
while (tx_count != 0 && rx_count != 0) {
|
|
if (tx->len != rx->len) {
|
|
return false;
|
|
}
|
|
|
|
tx++;
|
|
tx_count--;
|
|
rx++;
|
|
rx_count--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int spi_sam0_transceive(struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
SercomSpi *regs = cfg->regs;
|
|
int err;
|
|
|
|
spi_context_lock(&data->ctx, false, NULL);
|
|
|
|
err = spi_sam0_configure(dev, config);
|
|
if (err != 0) {
|
|
goto done;
|
|
}
|
|
|
|
spi_context_cs_control(&data->ctx, true);
|
|
|
|
/* This driver special cases the common send only, receive
|
|
* only, and transmit then receive operations. This special
|
|
* casing is 4x faster than the spi_context() routines
|
|
* and allows the transmit and receive to be interleaved.
|
|
*/
|
|
if (spi_sam0_is_regular(tx_bufs, rx_bufs)) {
|
|
spi_sam0_fast_transceive(dev, config, tx_bufs, rx_bufs);
|
|
} else {
|
|
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
|
|
|
|
do {
|
|
spi_sam0_shift_master(regs, data);
|
|
} while (spi_sam0_transfer_ongoing(data));
|
|
}
|
|
|
|
spi_context_cs_control(&data->ctx, false);
|
|
|
|
done:
|
|
spi_context_release(&data->ctx, err);
|
|
return err;
|
|
}
|
|
|
|
static int spi_sam0_transceive_sync(struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
return spi_sam0_transceive(dev, config, tx_bufs, rx_bufs);
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
|
|
static void spi_sam0_dma_rx_done(void *arg, u32_t id, int error_code);
|
|
|
|
static int spi_sam0_dma_rx_load(struct device *dev, u8_t *buf,
|
|
size_t len)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
SercomSpi *regs = cfg->regs;
|
|
struct dma_config dma_cfg = { 0 };
|
|
struct dma_block_config dma_blk = { 0 };
|
|
int retval;
|
|
|
|
dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY;
|
|
dma_cfg.source_data_size = 1;
|
|
dma_cfg.dest_data_size = 1;
|
|
dma_cfg.callback_arg = dev;
|
|
dma_cfg.dma_callback = spi_sam0_dma_rx_done;
|
|
dma_cfg.block_count = 1;
|
|
dma_cfg.head_block = &dma_blk;
|
|
dma_cfg.dma_slot = cfg->rx_dma_request;
|
|
|
|
dma_blk.block_size = len;
|
|
|
|
if (buf != NULL) {
|
|
dma_blk.dest_address = (u32_t)buf;
|
|
} else {
|
|
static u8_t dummy;
|
|
|
|
dma_blk.dest_address = (u32_t)&dummy;
|
|
dma_blk.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
|
}
|
|
|
|
dma_blk.source_address = (u32_t)(&(regs->DATA.reg));
|
|
dma_blk.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
|
|
|
retval = dma_config(data->dma, cfg->rx_dma_channel,
|
|
&dma_cfg);
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
return dma_start(data->dma, cfg->rx_dma_channel);
|
|
}
|
|
|
|
static int spi_sam0_dma_tx_load(struct device *dev, const u8_t *buf,
|
|
size_t len)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
SercomSpi *regs = cfg->regs;
|
|
struct dma_config dma_cfg = { 0 };
|
|
struct dma_block_config dma_blk = { 0 };
|
|
int retval;
|
|
|
|
dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY;
|
|
dma_cfg.source_data_size = 1;
|
|
dma_cfg.dest_data_size = 1;
|
|
dma_cfg.block_count = 1;
|
|
dma_cfg.head_block = &dma_blk;
|
|
dma_cfg.dma_slot = cfg->tx_dma_request;
|
|
|
|
dma_blk.block_size = len;
|
|
|
|
if (buf != NULL) {
|
|
dma_blk.source_address = (u32_t)buf;
|
|
} else {
|
|
static const u8_t dummy;
|
|
|
|
dma_blk.source_address = (u32_t)&dummy;
|
|
dma_blk.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
|
}
|
|
|
|
dma_blk.dest_address = (u32_t)(&(regs->DATA.reg));
|
|
dma_blk.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
|
|
|
|
retval = dma_config(data->dma, cfg->tx_dma_channel,
|
|
&dma_cfg);
|
|
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
return dma_start(data->dma, cfg->tx_dma_channel);
|
|
}
|
|
|
|
static bool spi_sam0_dma_advance_segment(struct device *dev)
|
|
{
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
u32_t segment_len;
|
|
|
|
/* Pick the shorter buffer of ones that have an actual length */
|
|
if (data->ctx.rx_len != 0) {
|
|
segment_len = data->ctx.rx_len;
|
|
if (data->ctx.tx_len != 0) {
|
|
segment_len = MIN(segment_len, data->ctx.tx_len);
|
|
}
|
|
} else {
|
|
segment_len = data->ctx.tx_len;
|
|
}
|
|
|
|
if (segment_len == 0) {
|
|
return false;
|
|
}
|
|
|
|
segment_len = MIN(segment_len, 65535);
|
|
|
|
data->dma_segment_len = segment_len;
|
|
return true;
|
|
}
|
|
|
|
static int spi_sam0_dma_advance_buffers(struct device *dev)
|
|
{
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
int retval;
|
|
|
|
if (data->dma_segment_len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Load receive first, so it can accept transmit data */
|
|
if (data->ctx.rx_len) {
|
|
retval = spi_sam0_dma_rx_load(dev, data->ctx.rx_buf,
|
|
data->dma_segment_len);
|
|
} else {
|
|
retval = spi_sam0_dma_rx_load(dev, NULL, data->dma_segment_len);
|
|
}
|
|
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
/* Now load the transmit, which starts the actual bus clocking */
|
|
if (data->ctx.tx_len) {
|
|
retval = spi_sam0_dma_tx_load(dev, data->ctx.tx_buf,
|
|
data->dma_segment_len);
|
|
} else {
|
|
retval = spi_sam0_dma_tx_load(dev, NULL, data->dma_segment_len);
|
|
}
|
|
|
|
if (retval != 0) {
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void spi_sam0_dma_rx_done(void *arg, u32_t id, int error_code)
|
|
{
|
|
struct device *dev = arg;
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
int retval;
|
|
|
|
ARG_UNUSED(id);
|
|
ARG_UNUSED(error_code);
|
|
|
|
spi_context_update_tx(&data->ctx, 1, data->dma_segment_len);
|
|
spi_context_update_rx(&data->ctx, 1, data->dma_segment_len);
|
|
|
|
if (!spi_sam0_dma_advance_segment(dev)) {
|
|
/* Done */
|
|
spi_context_cs_control(&data->ctx, false);
|
|
spi_context_complete(&data->ctx, 0);
|
|
return;
|
|
}
|
|
|
|
retval = spi_sam0_dma_advance_buffers(dev);
|
|
if (retval != 0) {
|
|
dma_stop(data->dma, cfg->tx_dma_channel);
|
|
dma_stop(data->dma, cfg->rx_dma_channel);
|
|
spi_context_cs_control(&data->ctx, false);
|
|
spi_context_complete(&data->ctx, retval);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static int spi_sam0_transceive_async(struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
struct k_poll_signal *async)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
int retval;
|
|
|
|
if (!data->dma) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/*
|
|
* Transmit clocks the output and we use receive to determine when
|
|
* the transmit is done, so we always need both
|
|
*/
|
|
if (cfg->tx_dma_channel == 0xFF || cfg->rx_dma_channel == 0xFF) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
spi_context_lock(&data->ctx, true, async);
|
|
|
|
retval = spi_sam0_configure(dev, config);
|
|
if (retval != 0) {
|
|
goto err_unlock;
|
|
}
|
|
|
|
spi_context_cs_control(&data->ctx, true);
|
|
|
|
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
|
|
|
|
spi_sam0_dma_advance_segment(dev);
|
|
retval = spi_sam0_dma_advance_buffers(dev);
|
|
if (retval != 0) {
|
|
goto err_cs;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_cs:
|
|
dma_stop(data->dma, cfg->tx_dma_channel);
|
|
dma_stop(data->dma, cfg->rx_dma_channel);
|
|
|
|
spi_context_cs_control(&data->ctx, false);
|
|
|
|
err_unlock:
|
|
spi_context_release(&data->ctx, retval);
|
|
return retval;
|
|
}
|
|
#endif /* CONFIG_SPI_ASYNC */
|
|
|
|
static int spi_sam0_release(struct device *dev,
|
|
const struct spi_config *config)
|
|
{
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_sam0_init(struct device *dev)
|
|
{
|
|
const struct spi_sam0_config *cfg = dev->config->config_info;
|
|
struct spi_sam0_data *data = dev->driver_data;
|
|
SercomSpi *regs = 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 SPI interrupts */
|
|
regs->INTENCLR.reg = SERCOM_SPI_INTENCLR_MASK;
|
|
wait_synchronization(regs);
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
|
|
data->dma = device_get_binding(CONFIG_DMA_0_NAME);
|
|
|
|
#endif
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
/* The device will be configured and enabled when transceive
|
|
* is called.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_driver_api spi_sam0_driver_api = {
|
|
.transceive = spi_sam0_transceive_sync,
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
.transceive_async = spi_sam0_transceive_async,
|
|
#endif
|
|
.release = spi_sam0_release,
|
|
};
|
|
|
|
#if CONFIG_SPI_ASYNC
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_0_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_0_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_0_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_0_RXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_1_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_1_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_1_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_1_RXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_2_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_2_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_2_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_2_RXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_3_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_3_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_3_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_3_RXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_4_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_4_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_4_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_4_RXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_5_TXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_5_TXDMA 0xFF
|
|
#endif
|
|
#ifndef DT_ATMEL_SAM0_SPI_SERCOM_5_RXDMA
|
|
#define DT_ATMEL_SAM0_SPI_SERCOM_5_RXDMA 0xFF
|
|
#endif
|
|
|
|
#define SPI_SAM0_DMA_CHANNELS(n) \
|
|
.tx_dma_request = SERCOM##n##_DMAC_ID_TX, \
|
|
.tx_dma_channel = DT_ATMEL_SAM0_SPI_SERCOM_##n##_TXDMA, \
|
|
.rx_dma_request = SERCOM##n##_DMAC_ID_RX, \
|
|
.rx_dma_channel = DT_ATMEL_SAM0_SPI_SERCOM_##n##_RXDMA
|
|
#else
|
|
#define SPI_SAM0_DMA_CHANNELS(n)
|
|
#endif
|
|
|
|
#define SPI_SAM0_SERCOM_PADS(n) \
|
|
SERCOM_SPI_CTRLA_DIPO(DT_ATMEL_SAM0_SPI_SERCOM_##n##_DIPO) | \
|
|
SERCOM_SPI_CTRLA_DOPO(DT_ATMEL_SAM0_SPI_SERCOM_##n##_DOPO)
|
|
|
|
#define SPI_SAM0_DEFINE_CONFIG(n) \
|
|
static const struct spi_sam0_config spi_sam0_config_##n = { \
|
|
.regs = (SercomSpi *)DT_ATMEL_SAM0_SPI_SERCOM_##n##_BASE_ADDRESS,\
|
|
.pm_apbcmask = PM_APBCMASK_SERCOM##n, \
|
|
.gclk_clkctrl_id = GCLK_CLKCTRL_ID_SERCOM##n##_CORE, \
|
|
.pads = SPI_SAM0_SERCOM_PADS(n), \
|
|
SPI_SAM0_DMA_CHANNELS(n) \
|
|
}
|
|
|
|
#define SPI_SAM0_DEVICE_INIT(n) \
|
|
SPI_SAM0_DEFINE_CONFIG(n); \
|
|
static struct spi_sam0_data spi_sam0_dev_data_##n = { \
|
|
SPI_CONTEXT_INIT_LOCK(spi_sam0_dev_data_##n, ctx), \
|
|
SPI_CONTEXT_INIT_SYNC(spi_sam0_dev_data_##n, ctx), \
|
|
}; \
|
|
DEVICE_AND_API_INIT(spi_sam0_##n, \
|
|
DT_ATMEL_SAM0_SPI_SERCOM_##n##_LABEL, \
|
|
&spi_sam0_init, &spi_sam0_dev_data_##n, \
|
|
&spi_sam0_config_##n, POST_KERNEL, \
|
|
CONFIG_SPI_INIT_PRIORITY, &spi_sam0_driver_api)
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_0_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(0);
|
|
#endif
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_1_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(1);
|
|
#endif
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_2_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(2);
|
|
#endif
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_3_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(3);
|
|
#endif
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_4_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(4);
|
|
#endif
|
|
|
|
#if DT_ATMEL_SAM0_SPI_SERCOM_5_BASE_ADDRESS
|
|
SPI_SAM0_DEVICE_INIT(5);
|
|
#endif
|