mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-26 05:16:09 +00:00
The I2C Slave Read support isn't well defined and not actually supported by any i2c driver at this point. We can add this back when slave mode is more thought out. Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
379 lines
9.0 KiB
C
379 lines
9.0 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.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 */
|