|  | /* | 
|  | * 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 <zephyr/device.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/drivers/i2c.h> | 
|  | #include <zephyr/drivers/dma.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.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; | 
|  | const struct pinctrl_dev_config *pcfg; | 
|  | uint32_t bitrate; | 
|  | #ifdef MCLK | 
|  | volatile uint32_t *mclk; | 
|  | uint32_t mclk_mask; | 
|  | uint16_t gclk_core_id; | 
|  | #else | 
|  | uint32_t pm_apbcmask; | 
|  | uint16_t gclk_clkctrl_id; | 
|  | #endif | 
|  | void (*irq_config_func)(const struct device *dev); | 
|  |  | 
|  | #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN | 
|  | const struct device *dma_dev; | 
|  | uint8_t write_dma_request; | 
|  | uint8_t read_dma_request; | 
|  | uint8_t dma_channel; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | struct i2c_sam0_msg { | 
|  | uint8_t *buffer; | 
|  | uint32_t size; | 
|  | uint32_t status; | 
|  | }; | 
|  |  | 
|  | struct i2c_sam0_dev_data { | 
|  | struct k_sem lock; | 
|  | struct k_sem sem; | 
|  | struct i2c_sam0_msg msg; | 
|  | struct i2c_msg *msgs; | 
|  | uint8_t num_msgs; | 
|  | }; | 
|  |  | 
|  | 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(const struct device *dev) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | 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 (cfg->dma_channel != 0xFF) { | 
|  | dma_stop(cfg->dma_dev, 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; | 
|  | if (i2c->INTFLAG.reg & (SERCOM_I2CM_INTFLAG_MB | SERCOM_I2CM_INTFLAG_SB)) { | 
|  | i2c->CTRLB.bit.CMD = 3; | 
|  | } | 
|  | k_sem_give(&data->sem); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void i2c_sam0_isr(const struct device *dev) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  |  | 
|  | /* Get present interrupts and clear them */ | 
|  | uint32_t status = i2c->INTFLAG.reg; | 
|  |  | 
|  | i2c->INTFLAG.reg = status; | 
|  |  | 
|  | if (i2c_sam0_terminate_on_error(dev)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Directly send/receive next message if it is in the same direction and | 
|  | * the current message has no stop flag and the next message has no | 
|  | * restart flag. | 
|  | */ | 
|  | const bool continue_next = (data->msg.size == 1) && (data->num_msgs > 1) && | 
|  | ((data->msgs[0].flags & I2C_MSG_RW_MASK) == | 
|  | (data->msgs[1].flags & I2C_MSG_RW_MASK)) && | 
|  | !(data->msgs[0].flags & I2C_MSG_STOP) && | 
|  | !(data->msgs[1].flags & I2C_MSG_RESTART) && | 
|  | ((status & (SERCOM_I2CM_INTFLAG_MB | SERCOM_I2CM_INTFLAG_SB))); | 
|  |  | 
|  | if (status & SERCOM_I2CM_INTFLAG_MB) { | 
|  | if (!data->msg.size) { | 
|  | i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; | 
|  | i2c->CTRLB.bit.CMD = 3; | 
|  | k_sem_give(&data->sem); | 
|  | return; | 
|  | } | 
|  |  | 
|  | i2c->DATA.reg = *data->msg.buffer; | 
|  | data->msg.buffer++; | 
|  | data->msg.size--; | 
|  | } else if (status & SERCOM_I2CM_INTFLAG_SB) { | 
|  | if (!continue_next && (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; | 
|  | i2c->CTRLB.bit.CMD = 3; | 
|  | } | 
|  |  | 
|  | *data->msg.buffer = i2c->DATA.reg; | 
|  | data->msg.buffer++; | 
|  | data->msg.size--; | 
|  |  | 
|  | if (!continue_next && !data->msg.size) { | 
|  | i2c->INTENCLR.reg = SERCOM_I2CM_INTENCLR_MASK; | 
|  | k_sem_give(&data->sem); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (continue_next) { | 
|  | data->msgs++; | 
|  | data->num_msgs--; | 
|  |  | 
|  | data->msg.buffer = data->msgs->buf; | 
|  | data->msg.size = data->msgs->len; | 
|  | data->msg.status = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN | 
|  |  | 
|  | static void i2c_sam0_dma_write_done(const struct device *dma_dev, void *arg, | 
|  | uint32_t id, int error_code) | 
|  | { | 
|  | const struct device *dev = arg; | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  |  | 
|  | ARG_UNUSED(dma_dev); | 
|  | ARG_UNUSED(id); | 
|  |  | 
|  | unsigned 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, 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(const struct device *dev) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  | int retval; | 
|  |  | 
|  | 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.user_data = (void *)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 = (uint32_t)data->msg.buffer; | 
|  | dma_blk.dest_address = (uint32_t)(&(i2c->DATA.reg)); | 
|  | dma_blk.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; | 
|  |  | 
|  | retval = dma_config(cfg->dma_dev, cfg->dma_channel, &dma_cfg); | 
|  | if (retval != 0) { | 
|  | LOG_ERR("Write DMA configure on %s failed: %d", | 
|  | dev->name, retval); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | retval = dma_start(cfg->dma_dev, cfg->dma_channel); | 
|  | if (retval != 0) { | 
|  | LOG_ERR("Write DMA start on %s failed: %d", | 
|  | dev->name, retval); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void i2c_sam0_dma_read_done(const struct device *dma_dev, void *arg, | 
|  | uint32_t id, int error_code) | 
|  | { | 
|  | const struct device *dev = arg; | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  |  | 
|  | ARG_UNUSED(dma_dev); | 
|  | ARG_UNUSED(id); | 
|  |  | 
|  | unsigned 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, 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(const struct device *dev) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  | int retval; | 
|  |  | 
|  | 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.user_data = (void *)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 = (uint32_t)data->msg.buffer; | 
|  | dma_blk.source_address = (uint32_t)(&(i2c->DATA.reg)); | 
|  | dma_blk.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; | 
|  |  | 
|  | retval = dma_config(cfg->dma_dev, cfg->dma_channel, &dma_cfg); | 
|  | if (retval != 0) { | 
|  | LOG_ERR("Read DMA configure on %s failed: %d", | 
|  | dev->name, retval); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | retval = dma_start(cfg->dma_dev, cfg->dma_channel); | 
|  | if (retval != 0) { | 
|  | LOG_ERR("Read DMA start on %s failed: %d", | 
|  | dev->name, retval); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | static int i2c_sam0_transfer(const struct device *dev, struct i2c_msg *msgs, | 
|  | uint8_t num_msgs, uint16_t addr) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  | uint32_t addr_reg; | 
|  | int ret; | 
|  |  | 
|  | if (!num_msgs) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | k_sem_take(&data->lock, K_FOREVER); | 
|  |  | 
|  | data->num_msgs = num_msgs; | 
|  | data->msgs = msgs; | 
|  |  | 
|  | for (; data->num_msgs > 0;) { | 
|  | if (!data->msgs->len) { | 
|  | if ((data->msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) { | 
|  | ret = -EINVAL; | 
|  | goto unlock; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 = data->msgs->buf; | 
|  | data->msg.size = data->msgs->len; | 
|  | data->msg.status = 0; | 
|  |  | 
|  | addr_reg = addr << 1U; | 
|  | if ((data->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 (data->msgs->flags & I2C_MSG_ADDR_10_BITS) { | 
|  | #ifdef SERCOM_I2CM_ADDR_TENBITEN | 
|  | addr_reg |= SERCOM_I2CM_ADDR_TENBITEN; | 
|  | #else | 
|  | ret = -ENOTSUP; | 
|  | goto unlock; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | unsigned 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 ((data->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); | 
|  | ret = -EAGAIN; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | LOG_ERR("Transaction error on %s: %08X", | 
|  | dev->name, data->msg.status); | 
|  | ret = -EIO; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | data->num_msgs--; | 
|  | data->msgs++; | 
|  | } | 
|  |  | 
|  | ret = 0; | 
|  | unlock: | 
|  | k_sem_give(&data->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int i2c_sam0_set_apply_bitrate(const struct device *dev, | 
|  | uint32_t config) | 
|  | { | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  | uint32_t baud; | 
|  | uint32_t baud_low; | 
|  | uint32_t baud_high; | 
|  |  | 
|  | uint32_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, 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, 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 = CLAMP(baud_high, 1U, 255U); | 
|  | 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, 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 = CLAMP(baud_high, 1U, 255U); | 
|  | 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, 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(const struct device *dev, uint32_t config) | 
|  | { | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | SercomI2cm *i2c = cfg->regs; | 
|  | int retval; | 
|  |  | 
|  | if (!(config & I2C_MODE_CONTROLLER)) { | 
|  | 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(const struct device *dev) | 
|  | { | 
|  | struct i2c_sam0_dev_data *data = dev->data; | 
|  | const struct i2c_sam0_dev_config *const cfg = dev->config; | 
|  | 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; | 
|  |  | 
|  | retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); | 
|  | if (retval < 0) { | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /* 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->lock, 1, 1); | 
|  | k_sem_init(&data->sem, 0, 1); | 
|  |  | 
|  | cfg->irq_config_func(dev); | 
|  |  | 
|  | #ifdef CONFIG_I2C_SAM0_DMA_DRIVEN | 
|  | if (!device_is_ready(cfg->dma_dev)) { | 
|  | return -ENODEV; | 
|  | } | 
|  | #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 | 
|  | #define I2C_SAM0_DMA_CHANNELS(n)					\ | 
|  | .dma_dev = DEVICE_DT_GET(ATMEL_SAM0_DT_INST_DMA_CTLR(n, tx)),	\ | 
|  | .write_dma_request = ATMEL_SAM0_DT_INST_DMA_TRIGSRC(n, tx),	\ | 
|  | .read_dma_request = ATMEL_SAM0_DT_INST_DMA_TRIGSRC(n, rx),	\ | 
|  | .dma_channel = ATMEL_SAM0_DT_INST_DMA_CHANNEL(n, rx), | 
|  | #else | 
|  | #define I2C_SAM0_DMA_CHANNELS(n) | 
|  | #endif | 
|  |  | 
|  | #define SAM0_I2C_IRQ_CONNECT(n, m)					\ | 
|  | do {								\ | 
|  | IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, m, irq),		\ | 
|  | DT_INST_IRQ_BY_IDX(n, m, priority),		\ | 
|  | i2c_sam0_isr,				\ | 
|  | DEVICE_DT_INST_GET(n), 0);			\ | 
|  | irq_enable(DT_INST_IRQ_BY_IDX(n, m, irq));		\ | 
|  | } while (false) | 
|  |  | 
|  | #if DT_INST_IRQ_HAS_IDX(0, 3) | 
|  | #define I2C_SAM0_IRQ_HANDLER(n)						\ | 
|  | static void i2c_sam0_irq_config_##n(const 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(const 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_INST_REG_ADDR(n),			\ | 
|  | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\ | 
|  | .bitrate = DT_INST_PROP(n, clock_frequency),			\ | 
|  | .mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(n),	\ | 
|  | .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, bit)),	\ | 
|  | .gclk_core_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, periph_ch),\ | 
|  | .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_INST_REG_ADDR(n),			\ | 
|  | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\ | 
|  | .bitrate = DT_INST_PROP(n, clock_frequency),			\ | 
|  | .pm_apbcmask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(n, pm, bit)),	\ | 
|  | .gclk_clkctrl_id = DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, clkctrl_id),\ | 
|  | .irq_config_func = &i2c_sam0_irq_config_##n,			\ | 
|  | I2C_SAM0_DMA_CHANNELS(n)					\ | 
|  | } | 
|  | #endif | 
|  |  | 
|  | #define I2C_SAM0_DEVICE(n)						\ | 
|  | PINCTRL_DT_INST_DEFINE(n);					\ | 
|  | static void i2c_sam0_irq_config_##n(const struct device *dev);	\ | 
|  | I2C_SAM0_CONFIG(n);						\ | 
|  | static struct i2c_sam0_dev_data i2c_sam0_dev_data_##n;		\ | 
|  | I2C_DEVICE_DT_INST_DEFINE(n,					\ | 
|  | i2c_sam0_initialize,			\ | 
|  | NULL,					\ | 
|  | &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) | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(I2C_SAM0_DEVICE) |