/* * Copyright (c) 2016-2017 ARM Ltd. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_LEVEL #include /* @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 */