<commit_before>/*
 * Copyright (c) 2019 <NAME> <<EMAIL>>
 *
 * 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;
	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 sem;
	struct i2c_sam0_msg msg;
	struct i2c_msg *msgs;
	uint8_t num_msgs;
};

#define DEV_NAME(dev) ((dev)->name)
#define DEV_CFG(dev) \
	((const struct i2c_sam0_dev_config *const)(dev)->config)
#define DEV_DATA(dev) \
	((struct i2c_sam0_dev_data *const)(dev)->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(const 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 (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;
	k_sem_give(&data->sem);
	return true;
}

static void i2c_sam0_isr(const 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;

	/* 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;
			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;
		}

		*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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;

	ARG_UNUSED(dma_dev);
	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(const 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 (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(dev), 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(dev), 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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;

	ARG_UNUSED(dma_dev);
	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(const 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 (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(dev), 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(dev), 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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;
	uint32_t addr_reg;

	if (!num_msgs) {
		return 0;
	}
	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) {
				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 = 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
			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 ((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(dev));
				return -EAGAIN;
			}

			LOG_ERR("Transaction error on %s: %08X",
				DEV_NAME(dev), data->msg.status);
			return -EIO;
		}

		if (data->msgs->flags & I2C_MSG_STOP) {
			i2c->CTRLB.bit.CMD = 3;
		} else if ((data->msgs->flags & I2C_MSG_RESTART) && data->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;
		}

		data->num_msgs--;
		data->msgs++;
	}

	return 0;
}

static int i2c_sam0_set_apply_bitrate(const struct device *dev,
				      uint32_t config)
{
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;<commit_msg>drivers: i2c: sam0: Ensure the bus returns to idle after a read<commit_after>/*
 * Copyright (c) 2019 <NAME> <<EMAIL>>
 *
 * 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;
	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 sem;
	struct i2c_sam0_msg msg;
	struct i2c_msg *msgs;
	uint8_t num_msgs;
};

#define DEV_NAME(dev) ((dev)->name)
#define DEV_CFG(dev) \
	((const struct i2c_sam0_dev_config *const)(dev)->config)
#define DEV_DATA(dev) \
	((struct i2c_sam0_dev_data *const)(dev)->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(const 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 (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;
	k_sem_give(&data->sem);
	return true;
}

static void i2c_sam0_isr(const 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;

	/* 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;
			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;
		}

		*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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;

	ARG_UNUSED(dma_dev);
	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(const 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 (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(dev), 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(dev), 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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;

	ARG_UNUSED(dma_dev);
	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(const 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 (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(dev), 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(dev), 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(dev);
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;
	uint32_t addr_reg;

	if (!num_msgs) {
		return 0;
	}
	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) {
				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 = 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
			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 ((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(dev));
				return -EAGAIN;
			}

			LOG_ERR("Transaction error on %s: %08X",
				DEV_NAME(dev), data->msg.status);
			return -EIO;
		}

		/*
		 * Only send a stop if after this message:
		 *   - it is explicitly requested that we send a stop, or
		 *   - we are not conducting a restart, with more messages to follow
		 *
		 * Note: nothing validates the flags, so default to a stop if a stop is
		 * requested... do not let a restart request override it
		 */
		bool send_stop = (data->msgs->flags & I2C_MSG_STOP)
			|| !((data->msgs->flags & I2C_MSG_RESTART) && (data->num_msgs > 1));

		if (send_stop) {
			while (!i2c->STATUS.bit.CLKHOLD) {
			}
			i2c->CTRLB.bit.CMD = 3;
		}

		data->num_msgs--;
		data->msgs++;
	}

	return 0;
}

static int i2c_sam0_set_apply_bitrate(const struct device *dev,
				      uint32_t config)
{
	const struct i2c_sam0_dev_config *const cfg = DEV_CFG(dev);
	SercomI2cm *i2c = cfg->regs;
{'fraction_non_alphanumeric': 0.12450119712689545, 'fraction_numerical': 0.02438148443735036, 'mean_word_length': 4.0061925689172995, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 287, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5734195', 'n_tokens_mistral': 11443, 'n_tokens_neox': 10492, 'n_words': 2104}