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:
parent
26693830e6
commit
46730e1f57
6 changed files with 874 additions and 0 deletions
|
@ -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
|
||||
|
|
|
@ -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
26
drivers/i2c/Kconfig.sam0
Normal 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
805
drivers/i2c/i2c_sam0.c
Normal 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
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <arm/armv6-m.dtsi>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/i2c/i2c.h>
|
||||
|
||||
/ {
|
||||
cpus {
|
||||
|
|
40
dts/bindings/i2c/atmel,sam0-i2c.yaml
Normal file
40
dts/bindings/i2c/atmel,sam0-i2c.yaml
Normal 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
|
||||
...
|
Loading…
Add table
Add a link
Reference in a new issue