383 lines
9.1 KiB
C
383 lines
9.1 KiB
C
/*
|
|
* 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.is_slave_read) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
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 */
|