862 lines
20 KiB
C
862 lines
20 KiB
C
/*
|
|
* Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT atmel_sam0_i2c
|
|
|
|
|
|
#include <errno.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
#include <soc.h>
|
|
#include <drivers/i2c.h>
|
|
#include <drivers/dma.h>
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(i2c_sam0, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
#include "i2c-priv.h"
|
|
|
|
#ifndef SERCOM_I2CM_CTRLA_MODE_I2C_MASTER
|
|
#define SERCOM_I2CM_CTRLA_MODE_I2C_MASTER SERCOM_I2CM_CTRLA_MODE(5)
|
|
#endif
|
|
|
|
struct i2c_sam0_dev_config {
|
|
SercomI2cm *regs;
|
|
u32_t bitrate;
|
|
#ifdef MCLK
|
|
volatile uint32_t *mclk;
|
|
u32_t mclk_mask;
|
|
u16_t gclk_core_id;
|
|
#else
|
|
u32_t pm_apbcmask;
|
|
u16_t gclk_clkctrl_id;
|
|
#endif
|
|
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
|
|
break;
|
|
|
|
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;
|
|
|
|
#ifdef MCLK
|
|
/* Enable the GCLK */
|
|
GCLK->PCHCTRL[cfg->gclk_core_id].reg = GCLK_PCHCTRL_GEN_GCLK0 |
|
|
GCLK_PCHCTRL_CHEN;
|
|
/* Enable SERCOM clock in MCLK */
|
|
*cfg->mclk |= cfg->mclk_mask;
|
|
#else
|
|
/* 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;
|
|
#endif
|
|
/* 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 DT_ATMEL_SAM0_I2C_SERCOM_IRQ(n, m) DT_ATMEL_SAM0_I2C_SERCOM_ ## n ## _IRQ_ ## m
|
|
#define DT_ATMEL_SAM0_I2C_SERCOM_IRQ_PRIORITY(n, m) DT_ATMEL_SAM0_I2C_SERCOM_ ## n ## _IRQ_ ## m ## _PRIORITY
|
|
|
|
#define SAM0_I2C_IRQ_CONNECT(n, m) \
|
|
do { \
|
|
IRQ_CONNECT(DT_ATMEL_SAM0_I2C_SERCOM_IRQ(n, m), \
|
|
DT_ATMEL_SAM0_I2C_SERCOM_IRQ_PRIORITY(n, m), \
|
|
i2c_sam0_isr, DEVICE_GET(i2c_sam0_##n), 0); \
|
|
irq_enable(DT_ATMEL_SAM0_I2C_SERCOM_IRQ(n, m)); \
|
|
} while (0)
|
|
|
|
#if DT_INST_IRQ_HAS_IDX(0, 3)
|
|
#define I2C_SAM0_IRQ_HANDLER(n) \
|
|
static void i2c_sam0_irq_config_##n(struct device *dev) \
|
|
{ \
|
|
SAM0_I2C_IRQ_CONNECT(n, 0); \
|
|
SAM0_I2C_IRQ_CONNECT(n, 1); \
|
|
SAM0_I2C_IRQ_CONNECT(n, 2); \
|
|
SAM0_I2C_IRQ_CONNECT(n, 3); \
|
|
}
|
|
#else
|
|
#define I2C_SAM0_IRQ_HANDLER(n) \
|
|
static void i2c_sam0_irq_config_##n(struct device *dev) \
|
|
{ \
|
|
SAM0_I2C_IRQ_CONNECT(n, 0); \
|
|
}
|
|
#endif
|
|
|
|
#ifdef MCLK
|
|
#define I2C_SAM0_CONFIG(n) \
|
|
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, \
|
|
.mclk = MCLK_SERCOM##n, \
|
|
.mclk_mask = MCLK_SERCOM##n##_MASK, \
|
|
.gclk_core_id = SERCOM##n##_GCLK_ID_CORE, \
|
|
.irq_config_func = &i2c_sam0_irq_config_##n \
|
|
I2C_SAM0_DMA_CHANNELS(n) \
|
|
}
|
|
#else /* !MCLK */
|
|
#define I2C_SAM0_CONFIG(n) \
|
|
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_sam0_irq_config_##n, \
|
|
I2C_SAM0_DMA_CHANNELS(n) \
|
|
}
|
|
#endif
|
|
|
|
#define I2C_SAM0_DEVICE(n) \
|
|
static void i2c_sam0_irq_config_##n(struct device *dev); \
|
|
I2C_SAM0_CONFIG(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);\
|
|
I2C_SAM0_IRQ_HANDLER(n)
|
|
|
|
#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
|