Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2016-2017 ARM Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <i2c.h>
#include <soc.h>
#include <nrf.h>
#include <misc/util.h>
#include <gpio.h>

#define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_LEVEL
#include <logging/sys_log.h>

/* @todo
 *
 * Only one instance of twi0 and spi0 may be active at any point in time.
 * Only one instance of twi1, spi1 and spis1 may be active at a time.
 */

#define NRF5_TWI_INT_STOPPED \
	(TWI_INTENSET_STOPPED_Set << TWI_INTENSET_STOPPED_Pos)
#define NRF5_TWI_INT_RXDREADY \
	(TWI_INTENSET_RXDREADY_Set << TWI_INTENSET_RXDREADY_Pos)
#define NRF5_TWI_INT_TXDSENT \
	(TWI_INTENSET_TXDSENT_Set << TWI_INTENSET_TXDSENT_Pos)
#define NRF5_TWI_INT_ERROR \
	(TWI_INTENSET_ERROR_Set << TWI_INTENSET_ERROR_Pos)


struct i2c_nrf5_config {
	volatile NRF_TWI_Type *base;
	void (*irq_config_func)(struct device *dev);
	union dev_config default_cfg;
};


struct i2c_nrf5_data {
	struct k_sem sem;
	u32_t rxd:1;
	u32_t txd:1;
	u32_t err:1;
	u32_t stopped:1;
	struct device *gpio;
};


static int i2c_nrf5_configure(struct device *dev, u32_t dev_config_raw)
{
	const struct i2c_nrf5_config *config = dev->config->config_info;
	union dev_config dev_config = (union dev_config)dev_config_raw;
	volatile NRF_TWI_Type *twi = config->base;

	SYS_LOG_DBG("");

	if (dev_config.bits.use_10_bit_addr) {
		return -EINVAL;
	}

	switch (dev_config.bits.speed) {
	case I2C_SPEED_STANDARD:
		twi->FREQUENCY = TWI_FREQUENCY_FREQUENCY_K100;
		break;
	case I2C_SPEED_FAST:
		twi->FREQUENCY = TWI_FREQUENCY_FREQUENCY_K400;
		break;
	default:
		SYS_LOG_ERR("unsupported speed");
		return -EINVAL;
	}

	return 0;
}

static int i2c_nrf5_read(struct device *dev, struct i2c_msg *msg)
{
	const struct i2c_nrf5_config *config = dev->config->config_info;
	struct i2c_nrf5_data *data = dev->driver_data;
	volatile NRF_TWI_Type *twi = config->base;

	__ASSERT_NO_MSG(msg->len);

	if (msg->flags & I2C_MSG_RESTART) {
		/* No special behaviour required for
		 * repeated start.
		 */
	}

	for (int offset = 0; offset < msg->len; offset++) {
		if (offset == msg->len-1) {
			SYS_LOG_DBG("SHORTS=2");
			twi->SHORTS = 2; /* BB->STOP */
		} else {
			SYS_LOG_DBG("SHORTS=1");
			twi->SHORTS = 1; /* BB->SUSPEND */
		}

		if (offset == 0) {
			SYS_LOG_DBG("STARTRX");
			twi->TASKS_STARTRX = 1;
		} else {
			SYS_LOG_DBG("RESUME");
			twi->TASKS_RESUME = 1;
		}

		k_sem_take(&data->sem, K_FOREVER);

		if (data->err) {
			data->err = 0;
			SYS_LOG_DBG("rx error 0x%x", twi->ERRORSRC);
			twi->TASKS_STOP = 1;
			twi->ENABLE = TWI_ENABLE_ENABLE_Disabled;
			return -EIO;
		}

		__ASSERT_NO_MSG(data->rxd);

		SYS_LOG_DBG("RXD");
		data->rxd = 0;
		msg->buf[offset] = twi->RXD;
	}

	if (msg->flags & I2C_MSG_STOP) {
		SYS_LOG_DBG("TASK_STOP");
		k_sem_take(&data->sem, K_FOREVER);
		SYS_LOG_DBG("err=%d txd=%d rxd=%d stopped=%d errsrc=0x%x",
			    data->err, data->txd, data->rxd,
			    data->stopped, twi->ERRORSRC);
		__ASSERT_NO_MSG(data->stopped);
		data->stopped = 0;
	}

	return 0;
}

static int i2c_nrf5_write(struct device *dev,
			  struct i2c_msg *msg)
{
	const struct i2c_nrf5_config *config = dev->config->config_info;
	struct i2c_nrf5_data *data = dev->driver_data;
	volatile NRF_TWI_Type *twi = config->base;

	__ASSERT_NO_MSG(msg->len);

	SYS_LOG_DBG("");

	data->stopped = 0;
	data->txd = 0;

	twi->EVENTS_TXDSENT = 0;
	twi->SHORTS = 0;

	for (int offset = 0; offset < msg->len; offset++) {
		SYS_LOG_DBG("txd=0x%x", msg->buf[offset]);
		twi->TXD = msg->buf[offset];

		if (offset == 0) {
			SYS_LOG_DBG("STARTTX");
			twi->TASKS_STARTTX = 1;
		}

		SYS_LOG_DBG("wait for sync");
		k_sem_take(&data->sem, K_FOREVER);
		SYS_LOG_DBG("err=%d txd=%d stopped=%d errsrc=0x%x",
			    data->err, data->txd,
			    data->stopped, twi->ERRORSRC);

		if (data->err) {
			data->err = 0;
			SYS_LOG_ERR("tx error 0x%x",
				    twi->ERRORSRC);
			twi->ERRORSRC = twi->ERRORSRC;
			twi->TASKS_STOP = 1;
			return -EIO;
		}

		__ASSERT_NO_MSG(data->txd);
		data->txd = 0;
		SYS_LOG_DBG("txdsent arrived");
	}

	if (msg->flags & I2C_MSG_STOP) {
		SYS_LOG_DBG("TASK_STOP");
		twi->TASKS_STOP = 1;
		k_sem_take(&data->sem, K_FOREVER);
		SYS_LOG_DBG("err=%d txd=%d rxd=%d stopped=%d errsrc=0x%x",
			    data->err, data->txd, data->rxd,
			    data->stopped, twi->ERRORSRC);
		data->stopped = 0;
	}

	return 0;
}

static int i2c_nrf5_transfer(struct device *dev, struct i2c_msg *msgs,
			     u8_t num_msgs, u16_t addr)
{
	const struct i2c_nrf5_config *config = dev->config->config_info;
	volatile NRF_TWI_Type *twi = config->base;

	SYS_LOG_DBG("transaction-start addr=0x%x", addr);

	/* @todo The NRF5 imposes constraints on which peripherals can
	 * be simultaneously active.  We should take steps here to
	 * enforce appropriate mutual exclusion between SPI, TWI and
	 * SPIS drivers.
	 */

	twi->ENABLE = TWI_ENABLE_ENABLE_Enabled;
	twi->ADDRESS = addr;
	for (int i = 0; i < num_msgs; i++) {
		int r;

		SYS_LOG_DBG("msg len=%d %s%s%s", msgs[i].len,
			    (msgs[i].flags & I2C_MSG_READ) ? "R":"W",
			    (msgs[i].flags & I2C_MSG_STOP) ? "S":"-",
			    (msgs[i].flags & I2C_MSG_RESTART) ? "+":"-");

		if (msgs[i].flags & I2C_MSG_READ) {
			twi->EVENTS_RXDREADY = 0;
			twi->INTENSET = (NRF5_TWI_INT_TXDSENT
					 | NRF5_TWI_INT_RXDREADY
					 | NRF5_TWI_INT_ERROR
					 | NRF5_TWI_INT_STOPPED);
			r = i2c_nrf5_read(dev, msgs + i);
		} else {
			r = i2c_nrf5_write(dev, msgs + i);
		}

		if (r != 0) {
			twi->ENABLE = TWI_ENABLE_ENABLE_Disabled;
			return r;
		}
	}
	twi->ENABLE = TWI_ENABLE_ENABLE_Disabled;

	return 0;
}

static void i2c_nrf5_isr(void *arg)
{
	struct device *dev = (struct device *)arg;
	const struct i2c_nrf5_config *config = dev->config->config_info;
	struct i2c_nrf5_data *data = dev->driver_data;
	volatile NRF_TWI_Type *twi = config->base;

	if (twi->EVENTS_RXDREADY) {
		data->rxd = 1;
		twi->EVENTS_RXDREADY = 0;
		k_sem_give(&data->sem);
	}

	if (twi->EVENTS_TXDSENT) {
		data->txd = 1;
		twi->EVENTS_TXDSENT = 0;
		k_sem_give(&data->sem);
	}

	if (twi->EVENTS_ERROR) {
		data->err = 1;
		twi->EVENTS_ERROR = 0;
		k_sem_give(&data->sem);
	}

	if (twi->EVENTS_STOPPED) {
		data->stopped = 1;
		twi->EVENTS_STOPPED = 0;
		k_sem_give(&data->sem);
	}
}

static int i2c_nrf5_init(struct device *dev)
{
	const struct i2c_nrf5_config *config = dev->config->config_info;
	struct i2c_nrf5_data *data = dev->driver_data;
	volatile NRF_TWI_Type *twi = config->base;
	int status;

	SYS_LOG_DBG("");

	data->gpio = device_get_binding(CONFIG_GPIO_NRF5_P0_DEV_NAME);

	k_sem_init(&data->sem, 0, UINT_MAX);

	config->irq_config_func(dev);

	twi->ENABLE = TWI_ENABLE_ENABLE_Disabled;

	status = gpio_pin_configure(data->gpio, CONFIG_I2C_NRF5_GPIO_SCL_PIN,
				    GPIO_DIR_IN
				    | GPIO_PUD_PULL_UP
				    | GPIO_DS_DISCONNECT_HIGH);
	__ASSERT_NO_MSG(status == 0);

	status = gpio_pin_configure(data->gpio, CONFIG_I2C_NRF5_GPIO_SCA_PIN,
				    GPIO_DIR_IN
				    | GPIO_PUD_PULL_UP
				    | GPIO_DS_DISCONNECT_HIGH);
	__ASSERT_NO_MSG(status == 0);

	twi->PSELSCL = CONFIG_I2C_NRF5_GPIO_SCL_PIN;
	twi->PSELSDA = CONFIG_I2C_NRF5_GPIO_SCA_PIN;
	twi->ERRORSRC = twi->ERRORSRC;
	twi->EVENTS_TXDSENT = 0;
	twi->EVENTS_RXDREADY = 0;
	twi->EVENTS_ERROR = 0;
	twi->INTENSET = (NRF5_TWI_INT_TXDSENT
			 | NRF5_TWI_INT_RXDREADY
			 | NRF5_TWI_INT_ERROR
			 | NRF5_TWI_INT_STOPPED);

	status = i2c_nrf5_configure(dev, config->default_cfg.raw);
	if (status) {
		return status;
	}

	return 0;
}

static const struct i2c_driver_api i2c_nrf5_driver_api = {
	.configure = i2c_nrf5_configure,
	.transfer = i2c_nrf5_transfer,
};

/* i2c & spi instance with the same id (e.g. I2C_0 and SPI_0) can NOT be used
 * at the same time on nRF5x chip family.
 */
#if defined(CONFIG_I2C_0) && !defined(CONFIG_SPI_0)
static void i2c_nrf5_config_func_0(struct device *dev);

static const struct i2c_nrf5_config i2c_nrf5_config_0 = {
	.base = NRF_TWI0,
	.irq_config_func = i2c_nrf5_config_func_0,
	.default_cfg.raw = CONFIG_I2C_0_DEFAULT_CFG,
};

static struct i2c_nrf5_data i2c_nrf5_data_0;

DEVICE_AND_API_INIT(i2c_nrf5_0, CONFIG_I2C_0_NAME, i2c_nrf5_init,
		    &i2c_nrf5_data_0, &i2c_nrf5_config_0,
		    POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
		    &i2c_nrf5_driver_api);

static void i2c_nrf5_config_func_0(struct device *dev)
{
	IRQ_CONNECT(NRF5_IRQ_SPI0_TWI0_IRQn, CONFIG_I2C_0_IRQ_PRI,
		    i2c_nrf5_isr, DEVICE_GET(i2c_nrf5_0), 0);

	irq_enable(NRF5_IRQ_SPI0_TWI0_IRQn);
}
#endif /* CONFIG_I2C_0 && !CONFIG_SPI_0 */

#if defined(CONFIG_I2C_1) && !defined(CONFIG_SPI_1)
static void i2c_nrf5_config_func_1(struct device *dev);

static const struct i2c_nrf5_config i2c_nrf5_config_1 = {
	.base = NRF_TWI1,
	.irq_config_func = i2c_nrf5_config_func_1,
	.default_cfg.raw = CONFIG_I2C_1_DEFAULT_CFG,
};

static struct i2c_nrf5_data i2c_nrf5_data_1;

DEVICE_AND_API_INIT(i2c_nrf5_1, CONFIG_I2C_1_NAME, i2c_nrf5_init,
		    &i2c_nrf5_data_1, &i2c_nrf5_config_1,
		    POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
		    &i2c_nrf5_driver_api);

static void i2c_nrf5_config_func_1(struct device *dev)
{
	IRQ_CONNECT(NRF5_IRQ_SPI1_TWI1_IRQn, CONFIG_I2C_1_IRQ_PRI,
		    i2c_nrf5_isr, DEVICE_GET(i2c_nrf5_1), 0);

	irq_enable(NRF5_IRQ_SPI1_TWI1_IRQn);
}
#endif /* CONFIG_I2C_1 && !CONFIG_SPI_1 */