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