zephyr/drivers/i2c/i2c_nrf5.c
Marcus Shawcroft f5eeaf9527 i2c/nrf5: Remove r/w to POWER register.
The reset code that toggles the POWER register fails to build on
NRF52.  In discussion with the Nordic folks it transpires that we
don't actually need this code.

For future reference there is a related errata here:
http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.Rev1.errata/anomaly_832_89.html?cp=2_2_1_0_1_26

Change-Id: I2ba8c2bb814f8f95ff3f340862c75a8bc437c573
Signed-off-by: Marcus Shawcroft <marcus.shawcroft@arm.com>
2017-02-01 00:35:35 +00:00

380 lines
8.9 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;
uint32_t rxd:1;
uint32_t txd:1;
uint32_t err:1;
uint32_t stopped:1;
struct device *gpio;
};
static int i2c_nrf5_configure(struct device *dev, uint32_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,
uint8_t num_msgs, uint16_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,
};
#ifdef CONFIG_I2C_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 */
#ifdef CONFIG_I2C_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 */