diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 00ac164a67e7ea..2e9f74729844ee 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -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 diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index c15bb453d520f0..5607d2c4c59bc2 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -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 diff --git a/drivers/i2c/Kconfig.sam0 b/drivers/i2c/Kconfig.sam0 new file mode 100644 index 00000000000000..9e0b39456f6fb9 --- /dev/null +++ b/drivers/i2c/Kconfig.sam0 @@ -0,0 +1,26 @@ +# +# Copyright (c) 2019 Derek Hageman +# +# 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 diff --git a/drivers/i2c/i2c_sam0.c b/drivers/i2c/i2c_sam0.c new file mode 100644 index 00000000000000..dfa53bbfb4d200 --- /dev/null +++ b/drivers/i2c/i2c_sam0.c @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2019 Derek Hageman + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include +#include +#include +#include +#include + +#include +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 diff --git a/dts/arm/atmel/samd.dtsi b/dts/arm/atmel/samd.dtsi index 70bc0a795ea68a..3d5b2ee6406708 100644 --- a/dts/arm/atmel/samd.dtsi +++ b/dts/arm/atmel/samd.dtsi @@ -6,6 +6,7 @@ #include #include +#include / { cpus { diff --git a/dts/bindings/i2c/atmel,sam0-i2c.yaml b/dts/bindings/i2c/atmel,sam0-i2c.yaml new file mode 100644 index 00000000000000..7d90bc1117688b --- /dev/null +++ b/dts/bindings/i2c/atmel,sam0-i2c.yaml @@ -0,0 +1,40 @@ +# +# Copyright (c) 2019 Derek Hageman +# +# 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 +...