drivers: i2c: Add SAM0 I2C driver

This adds a SERCOM I2C driver for SAM0 series chips.

Tested with a SAMD21 chip on a SSD1306 display and a MLX90393
sensor.  Only compile tested for SAMD20 and SAMR21.

Signed-off-by: Derek Hageman <hageman@inthat.cloud>
This commit is contained in:
Derek Hageman 2019-03-06 18:03:48 -07:00 committed by Anas Nashif
commit 46730e1f57
6 changed files with 874 additions and 0 deletions

View file

@ -21,6 +21,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_SIFIVE i2c_sifive.c)
zephyr_library_sources_ifdef(CONFIG_I2C_NIOS2 i2c_nios2.c)
zephyr_library_sources_ifdef(CONFIG_I2C_GECKO i2c_gecko.c)
zephyr_library_sources_ifdef(CONFIG_I2C_RV32M1_LPI2C i2c_rv32m1_lpi2c.c)
zephyr_library_sources_ifdef(CONFIG_I2C_SAM0 i2c_sam0.c)
zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1
i2c_ll_stm32_v1.c

View file

@ -27,6 +27,7 @@ source "drivers/i2c/Kconfig.qmsi"
source "drivers/i2c/Kconfig.sbcon"
source "drivers/i2c/Kconfig.sifive"
source "drivers/i2c/Kconfig.stm32"
source "drivers/i2c/Kconfig.sam0"
config I2C_INIT_PRIORITY

26
drivers/i2c/Kconfig.sam0 Normal file
View file

@ -0,0 +1,26 @@
#
# Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
#
# SPDX-License-Identifier: Apache-2.0
#
if SOC_FAMILY_SAM0
menuconfig I2C_SAM0
bool "SAM0 series I2C SERCOM driver"
default y
select HAS_DTS_I2C
help
Enable the SAM0 series SERCOM I2C driver.
config I2C_SAM0_DMA_DRIVEN
bool "Enable DMA support for SAM0 I2C devices"
depends on I2C_SAM0
select DMA
help
This enables DMA driven transactions for the I2C peripheral.
DMA driven mode requires fewer interrupts to handle the
transaction and ensures that high speed modes are not delayed
by data reloading.
endif # SOC_FAMILY_SAM0

805
drivers/i2c/i2c_sam0.c Normal file
View file

@ -0,0 +1,805 @@
/*
* Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <device.h>
#include <init.h>
#include <soc.h>
#include <i2c.h>
#include <dma.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(i2c_sam0, CONFIG_I2C_LOG_LEVEL);
#include "i2c-priv.h"
struct i2c_sam0_dev_config {
SercomI2cm *regs;
u32_t bitrate;
u32_t pm_apbcmask;
u16_t gclk_clkctrl_id;
void (*irq_config_func)(struct device *dev);
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
u8_t write_dma_request;
u8_t read_dma_request;
u8_t dma_channel;
#endif
};
struct i2c_sam0_msg {
u8_t *buffer;
u32_t size;
u32_t status;
};
struct i2c_sam0_dev_data {
struct k_sem sem;
struct i2c_sam0_msg msg;
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
struct device *dma;
#endif
};
#define DEV_NAME(dev) ((dev)->config->name)
#define DEV_CFG(dev) \
((const struct i2c_sam0_dev_config *const)(dev)->config->config_info)
#define DEV_DATA(dev) \
((struct i2c_sam0_dev_data *const)(dev)->driver_data)
static void wait_synchronization(SercomI2cm *regs)
{
#if defined(SERCOM_I2CM_SYNCBUSY_MASK)
/* SYNCBUSY is a register */
while ((regs->SYNCBUSY.reg & SERCOM_I2CM_SYNCBUSY_MASK) != 0) {
}
#elif defined(SERCOM_I2CM_STATUS_SYNCBUSY)
/* SYNCBUSY is a bit */
while ((regs->STATUS.reg & SERCOM_I2CM_STATUS_SYNCBUSY) != 0) {
}
#else
#error Unsupported device
#endif
}
static bool i2c_sam0_terminate_on_error(struct device *dev)
{
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
if (!(i2c->STATUS.reg & (SERCOM_I2CM_STATUS_ARBLOST |
SERCOM_I2CM_STATUS_RXNACK |
#ifdef SERCOM_I2CM_STATUS_LENERR
SERCOM_I2CM_STATUS_LENERR |
#endif
#ifdef SERCOM_I2CM_STATUS_SEXTTOUT
SERCOM_I2CM_STATUS_SEXTTOUT |
#endif
#ifdef SERCOM_I2CM_STATUS_MEXTTOUT
SERCOM_I2CM_STATUS_MEXTTOUT |
#endif
SERCOM_I2CM_STATUS_LOWTOUT |
SERCOM_I2CM_STATUS_BUSERR))) {
return false;
}
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
if (data->dma && cfg->dma_channel != 0xFF) {
dma_stop(data->dma, cfg->dma_channel);
}
#endif
data->msg.status = i2c->STATUS.reg;
/*
* Clear all the flags that require an explicit clear
* (as opposed to being cleared by ADDR writes, etc)
*/
i2c->STATUS.reg = SERCOM_I2CM_STATUS_ARBLOST |
#ifdef SERCOM_I2CM_STATUS_LENERR
SERCOM_I2CM_STATUS_LENERR |
#endif
SERCOM_I2CM_STATUS_LOWTOUT |
SERCOM_I2CM_STATUS_BUSERR;
wait_synchronization(i2c);
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
k_sem_give(&data->sem);
return true;
}
static void i2c_sam0_isr(void *arg)
{
struct device *dev = (struct device *)arg;
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
/* Get present interrupts and clear them */
u32_t status = i2c->INTFLAG.reg;
i2c->INTFLAG.reg = status;
if (i2c_sam0_terminate_on_error(dev)) {
return;
}
if (status & SERCOM_I2CM_INTFLAG_MB) {
if (!data->msg.size) {
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
k_sem_give(&data->sem);
return;
}
i2c->DATA.reg = *data->msg.buffer;
data->msg.buffer++;
data->msg.size--;
return;
}
if (status & SERCOM_I2CM_INTFLAG_SB) {
if (data->msg.size == 1) {
/*
* If this is the last byte, then prepare for an auto
* NACK before doing the actual read. This does not
* require write synchronization.
*/
i2c->CTRLB.bit.ACKACT = 1;
}
*data->msg.buffer = i2c->DATA.reg;
data->msg.buffer++;
data->msg.size--;
if (!data->msg.size) {
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
k_sem_give(&data->sem);
return;
}
return;
}
}
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
static void i2c_sam0_dma_write_done(void *arg, u32_t id, int error_code)
{
struct device *dev = arg;
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
ARG_UNUSED(id);
int key = irq_lock();
if (i2c_sam0_terminate_on_error(dev)) {
irq_unlock(key);
return;
}
if (error_code < 0) {
LOG_ERR("DMA write error on %s: %d", DEV_NAME(dev), error_code);
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
irq_unlock(key);
data->msg.status = error_code;
k_sem_give(&data->sem);
return;
}
irq_unlock(key);
/*
* DMA has written the whole message now, so just wait for the
* final I2C IRQ to indicate that it's finished transmitting.
*/
data->msg.size = 0;
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_MB;
}
static bool i2c_sam0_dma_write_start(struct device *dev)
{
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
int retval;
if (!data->dma) {
return false;
}
if (cfg->dma_channel == 0xFF) {
return false;
}
if (data->msg.size <= 1) {
/*
* Catch empty writes and skip DMA on single byte transfers.
*/
return false;
}
struct dma_config dma_cfg = { 0 };
struct dma_block_config dma_blk = { 0 };
dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL;
dma_cfg.source_data_size = 1;
dma_cfg.dest_data_size = 1;
dma_cfg.callback_arg = dev;
dma_cfg.dma_callback = i2c_sam0_dma_write_done;
dma_cfg.block_count = 1;
dma_cfg.head_block = &dma_blk;
dma_cfg.dma_slot = cfg->write_dma_request;
dma_blk.block_size = data->msg.size;
dma_blk.source_address = (u32_t)data->msg.buffer;
dma_blk.dest_address = (u32_t)(&(i2c->DATA.reg));
dma_blk.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
retval = dma_config(data->dma, cfg->dma_channel, &dma_cfg);
if (retval != 0) {
LOG_ERR("Write DMA configure on %s failed: %d",
DEV_NAME(dev), retval);
return false;
}
retval = dma_start(data->dma, cfg->dma_channel);
if (retval != 0) {
LOG_ERR("Write DMA start on %s failed: %d",
DEV_NAME(dev), retval);
return false;
}
return true;
}
static void i2c_sam0_dma_read_done(void *arg, u32_t id, int error_code)
{
struct device *dev = arg;
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
ARG_UNUSED(id);
int key = irq_lock();
if (i2c_sam0_terminate_on_error(dev)) {
irq_unlock(key);
return;
}
if (error_code < 0) {
LOG_ERR("DMA read error on %s: %d", DEV_NAME(dev), error_code);
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
irq_unlock(key);
data->msg.status = error_code;
k_sem_give(&data->sem);
return;
}
irq_unlock(key);
/*
* DMA has read all but the last byte now, so let the ISR handle
* that and the terminating NACK.
*/
data->msg.buffer += data->msg.size - 1;
data->msg.size = 1;
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_SB;
}
static bool i2c_sam0_dma_read_start(struct device *dev)
{
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
int retval;
if (!data->dma) {
return false;
}
if (cfg->dma_channel == 0xFF) {
return false;
}
if (data->msg.size <= 2) {
/*
* The last byte is always handled by the I2C ISR so
* just skip a two length read as well.
*/
return false;
}
struct dma_config dma_cfg = { 0 };
struct dma_block_config dma_blk = { 0 };
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 = i2c_sam0_dma_read_done;
dma_cfg.block_count = 1;
dma_cfg.head_block = &dma_blk;
dma_cfg.dma_slot = cfg->read_dma_request;
dma_blk.block_size = data->msg.size - 1;
dma_blk.dest_address = (u32_t)data->msg.buffer;
dma_blk.source_address = (u32_t)(&(i2c->DATA.reg));
dma_blk.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
retval = dma_config(data->dma, cfg->dma_channel, &dma_cfg);
if (retval != 0) {
LOG_ERR("Read DMA configure on %s failed: %d",
DEV_NAME(dev), retval);
return false;
}
retval = dma_start(data->dma, cfg->dma_channel);
if (retval != 0) {
LOG_ERR("Read DMA start on %s failed: %d",
DEV_NAME(dev), retval);
return false;
}
return true;
}
#endif
static int i2c_sam0_transfer(struct device *dev, struct i2c_msg *msgs,
u8_t num_msgs, u16_t addr)
{
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
u32_t addr_reg;
if (!num_msgs) {
return 0;
}
for (; num_msgs > 0;) {
if (!msgs->len) {
if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
return -EINVAL;
}
}
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
i2c->INTFLAG.reg = SERCOM_I2CM_INTFLAG_MASK;
i2c->STATUS.reg = SERCOM_I2CM_STATUS_ARBLOST |
#ifdef SERCOM_I2CM_STATUS_LENERR
SERCOM_I2CM_STATUS_LENERR |
#endif
SERCOM_I2CM_STATUS_LOWTOUT |
SERCOM_I2CM_STATUS_BUSERR;
wait_synchronization(i2c);
data->msg.buffer = msgs->buf;
data->msg.size = msgs->len;
data->msg.status = 0;
addr_reg = addr << 1U;
if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
addr_reg |= 1U;
/* Set to auto ACK */
i2c->CTRLB.bit.ACKACT = 0;
wait_synchronization(i2c);
}
if (msgs->flags & I2C_MSG_ADDR_10_BITS) {
#ifdef SERCOM_I2CM_ADDR_TENBITEN
addr_reg |= SERCOM_I2CM_ADDR_TENBITEN;
#else
return -ENOTSUP;
#endif
}
int key = irq_lock();
/*
* Writing the address starts the transaction, issuing
* a start/repeated start as required.
*/
i2c->ADDR.reg = addr_reg;
/*
* Have to wait here to make sure the address write
* clears any pending requests or errors before DMA or
* ISR tries to handle it.
*/
wait_synchronization(i2c);
#ifdef SERCOM_I2CM_INTENSET_ERROR
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_ERROR;
#endif
if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
/*
* Always set MB even when reading, since that's how
* some errors are indicated.
*/
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_MB;
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
if (!i2c_sam0_dma_read_start(dev))
#endif
{
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_SB;
}
} else {
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
if (!i2c_sam0_dma_write_start(dev))
#endif
{
i2c->INTENSET.reg = SERCOM_I2CM_INTENSET_MB;
}
}
irq_unlock(key);
/* Now wait for the ISR to handle everything */
k_sem_take(&data->sem, K_FOREVER);
if (data->msg.status) {
if (data->msg.status & SERCOM_I2CM_STATUS_ARBLOST) {
LOG_DBG("Arbitration lost on %s",
DEV_NAME(dev));
return -EAGAIN;
}
LOG_ERR("Transaction error on %s: %08X",
DEV_NAME(dev), data->msg.status);
return -EIO;
}
if (msgs->flags & I2C_MSG_STOP) {
i2c->CTRLB.bit.CMD = 3;
} else if ((msgs->flags & I2C_MSG_RESTART) && num_msgs > 1) {
/*
* No action, since we do this automatically if we
* don't send an explicit stop
*/
} else {
/*
* Neither present, so assume we want to release
* the bus (by sending a stop)
*/
i2c->CTRLB.bit.CMD = 3;
}
num_msgs--;
msgs++;
}
return 0;
}
static int i2c_sam0_set_apply_bitrate(struct device *dev, u32_t config)
{
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
u32_t baud;
u32_t baud_low;
u32_t baud_high;
u32_t CTRLA = i2c->CTRLA.reg;
#ifdef SERCOM_I2CM_CTRLA_SPEED_Msk
CTRLA &= ~SERCOM_I2CM_CTRLA_SPEED_Msk;
#endif
CTRLA &= ~SERCOM_I2CM_CTRLA_SDAHOLD_Msk;
switch (I2C_SPEED_GET(config)) {
case I2C_SPEED_STANDARD:
#ifdef SERCOM_I2CM_CTRLA_SPEED
CTRLA |= SERCOM_I2CM_CTRLA_SPEED(0);
#endif
CTRLA |= SERCOM_I2CM_CTRLA_SDAHOLD(0x0);
i2c->CTRLA.reg = CTRLA;
wait_synchronization(i2c);
/* 5 is the nominal 100ns rise time from the app notes */
baud = (SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / 100000U - 5U - 10U) / 2U;
if (baud > 255U || baud < 1U) {
return -ERANGE;
}
LOG_DBG("Setting %s to standard mode with divisor %u",
DEV_NAME(dev), baud);
i2c->BAUD.reg = SERCOM_I2CM_BAUD_BAUD(baud);
break;
case I2C_SPEED_FAST:
CTRLA |= SERCOM_I2CM_CTRLA_SDAHOLD(0x0);
i2c->CTRLA.reg = CTRLA;
wait_synchronization(i2c);
/* 5 is the nominal 100ns rise time from the app notes */
baud = (SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / 400000U - 5U - 10U) / 2U;
if (baud > 255U || baud < 1U) {
return -ERANGE;
}
LOG_DBG("Setting %s to fast mode with divisor %u",
DEV_NAME(dev), baud);
i2c->BAUD.reg = SERCOM_I2CM_BAUD_BAUD(baud);
break;
case I2C_SPEED_FAST_PLUS:
#ifdef SERCOM_I2CM_CTRLA_SPEED
CTRLA |= SERCOM_I2CM_CTRLA_SPEED(1);
#endif
CTRLA |= SERCOM_I2CM_CTRLA_SDAHOLD(0x2);
i2c->CTRLA.reg = CTRLA;
wait_synchronization(i2c);
/* 5 is the nominal 100ns rise time from the app notes */
baud = (SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / 1000000U - 5U - 10U);
/* 2:1 low:high ratio */
baud_high = baud;
baud_high /= 3U;
baud_high = MAX(MIN(baud_high, 255U), 1U);
baud_low = baud - baud_high;
if (baud_low < 1U && baud_high > 1U) {
--baud_high;
++baud_low;
}
if (baud_low < 1U || baud_low > 255U) {
return -ERANGE;
}
LOG_DBG("Setting %s to fast mode plus with divisors %u/%u",
DEV_NAME(dev), baud_high, baud_low);
i2c->BAUD.reg = SERCOM_I2CM_BAUD_BAUD(baud_high) |
SERCOM_I2CM_BAUD_BAUDLOW(baud_low);
break;
case I2C_SPEED_HIGH:
#ifdef SERCOM_I2CM_CTRLA_SPEED
CTRLA |= SERCOM_I2CM_CTRLA_SPEED(2);
#endif
CTRLA |= SERCOM_I2CM_CTRLA_SDAHOLD(0x2);
i2c->CTRLA.reg = CTRLA;
wait_synchronization(i2c);
baud = (SOC_ATMEL_SAM0_GCLK0_FREQ_HZ / 3400000U) - 2U;
/* 2:1 low:high ratio */
baud_high = baud;
baud_high /= 3U;
baud_high = MAX(MIN(baud_high, 255U), 1U);
baud_low = baud - baud_high;
if (baud_low < 1U && baud_high > 1U) {
--baud_high;
++baud_low;
}
if (baud_low < 1U || baud_low > 255U) {
return -ERANGE;
}
#ifdef SERCOM_I2CM_BAUD_HSBAUD
LOG_DBG("Setting %s to high speed with divisors %u/%u",
DEV_NAME(dev), baud_high, baud_low);
/*
* 48 is just from the app notes, but the datasheet says
* it's ignored
*/
i2c->BAUD.reg = SERCOM_I2CM_BAUD_HSBAUD(baud_high) |
SERCOM_I2CM_BAUD_HSBAUDLOW(baud_low) |
SERCOM_I2CM_BAUD_BAUD(48) |
SERCOM_I2CM_BAUD_BAUDLOW(48);
#else
return -ENOTSUP;
#endif
default:
return -ENOTSUP;
}
wait_synchronization(i2c);
return 0;
}
static int i2c_sam0_configure(struct device *dev, u32_t config)
{
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
int retval;
if (!(config & I2C_MODE_MASTER)) {
return -EINVAL;
}
if (config & I2C_SPEED_MASK) {
i2c->CTRLA.bit.ENABLE = 0;
wait_synchronization(i2c);
retval = i2c_sam0_set_apply_bitrate(dev, config);
i2c->CTRLA.bit.ENABLE = 1;
wait_synchronization(i2c);
if (retval != 0) {
return retval;
}
}
return 0;
}
static int i2c_sam0_initialize(struct device *dev)
{
struct i2c_sam0_dev_data *data = DEV_DATA(dev);
const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
SercomI2cm *i2c = cfg->regs;
int retval;
/* 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 I2C interrupts */
i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK;
/* I2C mode, enable timeouts */
i2c->CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_I2C_MASTER |
#ifdef SERCOM_I2CM_CTRLA_LOWTOUTEN
SERCOM_I2CM_CTRLA_LOWTOUTEN |
#endif
SERCOM_I2CM_CTRLA_INACTOUT(0x3);
wait_synchronization(i2c);
/* Enable smart mode (auto ACK) */
i2c->CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
wait_synchronization(i2c);
retval = i2c_sam0_set_apply_bitrate(dev,
i2c_map_dt_bitrate(cfg->bitrate));
if (retval != 0) {
return retval;
}
k_sem_init(&data->sem, 0, 1);
cfg->irq_config_func(dev);
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
data->dma = device_get_binding(CONFIG_DMA_0_NAME);
#endif
i2c->CTRLA.bit.ENABLE = 1;
wait_synchronization(i2c);
/* Force bus idle */
i2c->STATUS.bit.BUSSTATE = 1;
wait_synchronization(i2c);
return 0;
}
static const struct i2c_driver_api i2c_sam0_driver_api = {
.configure = i2c_sam0_configure,
.transfer = i2c_sam0_transfer,
};
#ifdef CONFIG_I2C_SAM0_DMA_DRIVEN
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_0_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_0_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_1_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_1_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_2_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_2_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_3_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_3_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_4_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_4_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_5_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_5_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_6_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_6_DMA 0xFF
#endif
#ifndef DT_ATMEL_SAM0_I2C_SERCOM_7_DMA
#define DT_ATMEL_SAM0_I2C_SERCOM_7_DMA 0xFF
#endif
#define I2C_SAM0_DMA_CHANNELS(n) \
.write_dma_request = SERCOM##n##_DMAC_ID_TX, \
.read_dma_request = SERCOM##n##_DMAC_ID_RX, \
.dma_channel = DT_ATMEL_SAM0_I2C_SERCOM_##n##_DMA,
#else
#define I2C_SAM0_DMA_CHANNELS(n)
#endif
#define I2C_SAM0_DEVICE(n) \
static void i2c_sam_irq_config_##n(struct device *dev); \
static const struct i2c_sam0_dev_config i2c_sam0_dev_config_##n = { \
.regs = (SercomI2cm *)DT_ATMEL_SAM0_I2C_SERCOM_##n##_BASE_ADDRESS, \
.bitrate = DT_ATMEL_SAM0_I2C_SERCOM_##n##_CLOCK_FREQUENCY, \
.pm_apbcmask = PM_APBCMASK_SERCOM##n, \
.gclk_clkctrl_id = GCLK_CLKCTRL_ID_SERCOM##n##_CORE, \
.irq_config_func = &i2c_sam_irq_config_##n, \
I2C_SAM0_DMA_CHANNELS(n) \
}; \
static struct i2c_sam0_dev_data i2c_sam0_dev_data_##n; \
DEVICE_AND_API_INIT(i2c_sam0_##n, \
DT_ATMEL_SAM0_I2C_SERCOM_##n##_LABEL, \
&i2c_sam0_initialize, &i2c_sam0_dev_data_##n, \
&i2c_sam0_dev_config_##n, POST_KERNEL, \
CONFIG_I2C_INIT_PRIORITY, &i2c_sam0_driver_api);\
static void i2c_sam_irq_config_##n(struct device *dev) \
{ \
IRQ_CONNECT(DT_ATMEL_SAM0_I2C_SERCOM_##n##_IRQ, \
DT_ATMEL_SAM0_I2C_SERCOM_##n##_IRQ_PRIORITY, \
i2c_sam0_isr, DEVICE_GET(i2c_sam0_##n), \
0); \
irq_enable(DT_ATMEL_SAM0_I2C_SERCOM_##n##_IRQ); \
}
#if DT_ATMEL_SAM0_I2C_SERCOM_0_BASE_ADDRESS
I2C_SAM0_DEVICE(0);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_1_BASE_ADDRESS
I2C_SAM0_DEVICE(1);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_2_BASE_ADDRESS
I2C_SAM0_DEVICE(2);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_3_BASE_ADDRESS
I2C_SAM0_DEVICE(3);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_4_BASE_ADDRESS
I2C_SAM0_DEVICE(4);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_5_BASE_ADDRESS
I2C_SAM0_DEVICE(5);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_6_BASE_ADDRESS
I2C_SAM0_DEVICE(6);
#endif
#if DT_ATMEL_SAM0_I2C_SERCOM_7_BASE_ADDRESS
I2C_SAM0_DEVICE(7);
#endif

View file

@ -6,6 +6,7 @@
#include <arm/armv6-m.dtsi>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/i2c/i2c.h>
/ {
cpus {

View file

@ -0,0 +1,40 @@
#
# Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
#
# SPDX-License-Identifier: Apache-2.0
#
---
title: Atmel SAM0 series SERCOM I2C controller
version: 0.1
description: >
This is a representation of the Atmel SAM0 series SERCOM I2C nodes
inherits:
!include i2c.yaml
properties:
compatible:
type: string
category: required
description: compatible strings
constraint: "atmel,sam0-i2c"
reg:
type: int
description: mmio register space
generation: define
category: required
interrupts:
type: compound
category: required
description: required interrupts
generation: define
dma:
type: int
category: optional
description: DMA channel
generation: define
...