blob: c41e899dee682f1eaf5a525a52729327e6ee85cd [file] [log] [blame]
/****************************************************************************
* arch/arm/src/nrf91/nrf91_i2c.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/kmalloc.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "nrf91_gpio.h"
#include "nrf91_i2c.h"
#include "hardware/nrf91_twi.h"
#include "hardware/nrf91_utils.h"
/****************************************************************************
* Private Types
****************************************************************************/
/* I2C Device Private Data */
struct nrf91_i2c_priv_s
{
const struct i2c_ops_s *ops; /* Standard I2C operations */
uint32_t base; /* TWI base address */
uint32_t scl_pin; /* SCL pin configuration */
uint32_t sda_pin; /* SDA pin configuration */
int refs; /* Reference count */
int status; /* I2C transfer status */
#ifndef CONFIG_I2C_POLLED
uint32_t irq; /* TWI interrupt */
#endif
uint8_t msgc; /* Message count */
struct i2c_msg_s *msgv; /* Message list */
uint8_t *ptr; /* Current message buffer */
#ifdef CONFIG_NRF91_I2C_MASTER_COPY_BUF_SIZE
/* Static buffer used for continued messages */
uint8_t copy_buf[CONFIG_NRF91_I2C_MASTER_COPY_BUF_SIZE];
#endif
uint32_t freq; /* Current I2C frequency */
int dcnt; /* Current message length */
uint16_t flags; /* Current message flags */
uint16_t addr; /* Current I2C address */
mutex_t lock; /* Mutual exclusion mutex */
#ifndef CONFIG_I2C_POLLED
sem_t sem_isr; /* Interrupt wait semaphore */
#endif
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline void nrf91_i2c_putreg(struct nrf91_i2c_priv_s *priv,
uint32_t offset,
uint32_t value);
static inline uint32_t nrf91_i2c_getreg(struct nrf91_i2c_priv_s *priv,
uint32_t offset);
static int nrf91_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs,
int count);
#ifdef CONFIG_I2C_RESET
static int nrf91_i2c_reset(struct i2c_master_s *dev);
#endif
#ifndef CONFIG_I2C_POLLED
static int nrf91_i2c_isr(int irq, void *context, void *arg);
#endif
static int nrf91_i2c_deinit(struct nrf91_i2c_priv_s *priv);
static int nrf91_i2c_init(struct nrf91_i2c_priv_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
/* I2C operations */
static const struct i2c_ops_s g_nrf91_i2c_ops =
{
.transfer = nrf91_i2c_transfer
#ifdef CONFIG_I2C_RESET
, .reset = nrf91_i2c_reset
#endif
};
/* I2C0 (TWI0) device */
#ifdef CONFIG_NRF91_I2C0_MASTER
static struct nrf91_i2c_priv_s g_nrf91_i2c0_priv =
{
.ops = &g_nrf91_i2c_ops,
.base = NRF91_TWIM0_BASE,
.scl_pin = BOARD_I2C0_SCL_PIN,
.sda_pin = BOARD_I2C0_SDA_PIN,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
.irq = NRF91_IRQ_SERIAL0,
#endif
.msgc = 0,
.msgv = NULL,
.ptr = NULL,
.freq = 0,
.dcnt = 0,
.flags = 0,
.addr = 0,
};
#endif
/* I2C1 (TWI1) device */
#ifdef CONFIG_NRF91_I2C1_MASTER
static struct nrf91_i2c_priv_s g_nrf91_i2c1_priv =
{
.ops = &g_nrf91_i2c_ops,
.base = NRF91_TWIM1_BASE,
.scl_pin = BOARD_I2C1_SCL_PIN,
.sda_pin = BOARD_I2C1_SDA_PIN,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
.irq = NRF91_IRQ_SERIAL1,
#endif
.msgc = 0,
.msgv = NULL,
.ptr = NULL,
.freq = 0,
.dcnt = 0,
.flags = 0,
.addr = 0,
};
#endif
/* I2C2 (TWI3) device */
#ifdef CONFIG_NRF91_I2C2_MASTER
static struct nrf91_i2c_priv_s g_nrf91_i2c2_priv =
{
.ops = &g_nrf91_i2c_ops,
.base = NRF91_TWIM2_BASE,
.scl_pin = BOARD_I2C2_SCL_PIN,
.sda_pin = BOARD_I2C2_SDA_PIN,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
.irq = NRF91_IRQ_SERIAL2,
#endif
.msgc = 0,
.msgv = NULL,
.ptr = NULL,
.freq = 0,
.dcnt = 0,
.flags = 0,
.addr = 0,
};
#endif
/* I2C3 (TWI3) device */
#ifdef CONFIG_NRF91_I2C3_MASTER
static struct nrf91_i2c_priv_s g_nrf91_i2c3_priv =
{
.ops = &g_nrf91_i2c_ops,
.base = NRF91_TWIM2_BASE,
.scl_pin = BOARD_I2C3_SCL_PIN,
.sda_pin = BOARD_I2C3_SDA_PIN,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
#ifndef CONFIG_I2C_POLLED
.sem_isr = SEM_INITIALIZER(0),
.irq = NRF91_IRQ_SERIAL3,
#endif
.msgc = 0,
.msgv = NULL,
.ptr = NULL,
.freq = 0,
.dcnt = 0,
.flags = 0,
.addr = 0,
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nrf91_i2c_putreg
*
* Description:
* Put a 32-bit register value by offset
*
****************************************************************************/
static inline void nrf91_i2c_putreg(struct nrf91_i2c_priv_s *priv,
uint32_t offset,
uint32_t value)
{
putreg32(value, priv->base + offset);
}
/****************************************************************************
* Name: nrf91_i2c_getreg
*
* Description:
* Get a 32-bit register value by offset
*
****************************************************************************/
static inline uint32_t nrf91_i2c_getreg(struct nrf91_i2c_priv_s *priv,
uint32_t offset)
{
return getreg32(priv->base + offset);
}
/****************************************************************************
* Name: nrf91_i2c_transfer
*
* Description:
* Generic I2C transfer function
*
****************************************************************************/
static int nrf91_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs,
int count)
{
struct nrf91_i2c_priv_s *priv = (struct nrf91_i2c_priv_s *)dev;
uint32_t regval = 0;
int ret = OK;
#ifndef CONFIG_NRF91_I2C_MASTER_DISABLE_NOSTART
uint8_t *pack_buf = NULL;
#endif
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
/* Reset ptr and dcnt */
priv->dcnt = 0;
priv->ptr = NULL;
priv->msgv = msgs;
priv->msgc = count;
/* Reset I2C transfer status */
priv->status = OK;
i2cinfo("I2C TRANSFER count=%d\n", count);
/* Do we need change I2C bus frequency ? */
if (priv->msgv->frequency != priv->freq)
{
/* Get TWI frequency */
switch (priv->msgv->frequency)
{
case 100000:
{
regval = TWIM_FREQUENCY_100KBPS;
break;
}
case 250000:
{
regval = TWIM_FREQUENCY_250KBPS;
break;
}
case 400000:
{
regval = TWIM_FREQUENCY_400KBPS;
break;
}
default:
{
ret = -EINVAL;
goto errout;
}
}
/* Write TWI frequency */
nrf91_i2c_putreg(priv, NRF91_TWIM_FREQUENCY_OFFSET, regval);
/* Save the new I2C frequency */
priv->freq = priv->msgv->frequency;
}
/* I2C transfer */
do
{
/* Get current message data */
priv->ptr = priv->msgv->buffer;
priv->dcnt = priv->msgv->length;
priv->flags = priv->msgv->flags;
priv->addr = priv->msgv->addr;
i2cinfo("ptr=%p dcnt=%d flags=%d addr=%d\n",
priv->ptr, priv->dcnt, priv->flags, priv->addr);
/* Write TWI address */
regval = priv->addr;
nrf91_i2c_putreg(priv, NRF91_TWIM_ADDRESS_OFFSET, regval);
if ((priv->flags & I2C_M_READ) == 0)
{
#ifndef CONFIG_NRF91_I2C_MASTER_DISABLE_NOSTART
/* Check if we need to combine messages */
if (priv->msgc > 1)
{
if (priv->msgv[1].flags & I2C_M_NOSTART)
{
/* More than 2 messages not supported */
DEBUGASSERT(priv->msgc < 3);
/* Combine buffers */
if ((priv->msgv[0].length +
priv->msgv[1].length) <=
CONFIG_NRF91_I2C_MASTER_COPY_BUF_SIZE)
{
pack_buf = priv->copy_buf;
}
else
{
pack_buf = kmm_malloc(priv->msgv[0].length +
priv->msgv[1].length);
if (pack_buf == NULL)
{
return -ENOMEM;
}
}
/* Combine messages */
memcpy(pack_buf, priv->msgv[0].buffer,
priv->msgv[0].length);
memcpy(pack_buf + priv->msgv[0].length,
priv->msgv[1].buffer, priv->msgv[1].length);
/* Use new buffer to transmit data */
priv->ptr = pack_buf;
priv->dcnt = priv->msgv[0].length + priv->msgv[1].length;
/* Next message */
priv->msgc -= 1;
priv->msgv += 1;
}
}
#else
if (priv->msgc > 1)
{
if (priv->msgv[1].flags & I2C_M_NOSTART)
{
/* Not supported */
DEBUGPANIC();
}
}
#endif
/* Write TXD data pointer */
regval = (uint32_t)priv->ptr;
DEBUGASSERT(nrf91_easydma_valid(regval));
nrf91_i2c_putreg(priv, NRF91_TWIM_TXDPTR_OFFSET, regval);
/* Write number of bytes in TXD buffer */
regval = priv->dcnt;
nrf91_i2c_putreg(priv, NRF91_TWIM_TXDMAXCNT_OFFSET, regval);
/* Start TX sequence */
nrf91_i2c_putreg(priv, NRF91_TWIM_TASKS_STARTTX_OFFSET, 1);
/* Wait for last TX event */
#ifdef CONFIG_I2C_POLLED
while (nrf91_i2c_getreg(priv,
NRF91_TWIM_EVENTS_LASTTX_OFFSET) != 1);
while (1)
{
regval = nrf91_i2c_getreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET) & 0x7;
if (regval != 0)
{
i2cerr("Error SRC: 0x%08" PRIx32 "\n", regval);
ret = -1;
nrf91_i2c_putreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET, 0x7);
goto errout;
}
if (nrf91_i2c_getreg(priv,
NRF91_TWIM_EVENTS_LASTTX_OFFSET) == 1)
{
break;
}
}
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_LASTTX_OFFSET, 0);
#else
nxsem_wait(&priv->sem_isr);
if (priv->status < 0)
{
ret = priv->status;
goto errout;
}
#endif
}
else
{
/* Write RXD data pointer */
regval = (uint32_t)priv->ptr;
DEBUGASSERT(nrf91_easydma_valid(regval));
nrf91_i2c_putreg(priv, NRF91_TWIM_RXDPTR_OFFSET, regval);
/* Write number of bytes in RXD buffer */
regval = priv->dcnt;
nrf91_i2c_putreg(priv, NRF91_TWIM_RXDMAXCNT_OFFSET, regval);
/* Start RX sequence */
nrf91_i2c_putreg(priv, NRF91_TWIM_TASKS_STARTRX_OFFSET, 1);
/* Wait for last RX done */
#ifdef CONFIG_I2C_POLLED
while (1)
{
regval = nrf91_i2c_getreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET) & 0x7;
if (regval != 0)
{
i2cerr("Error SRC: 0x%08" PRIx32 "\n", regval);
ret = -1;
nrf91_i2c_putreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET, 0x7);
goto errout;
}
if (nrf91_i2c_getreg(priv,
NRF91_TWIM_EVENTS_LASTRX_OFFSET) == 1)
{
break;
}
}
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_LASTRX_OFFSET, 0);
#else
nxsem_wait(&priv->sem_isr);
if (priv->status < 0)
{
ret = priv->status;
goto errout;
}
#endif
}
/* Next message */
priv->msgc -= 1;
priv->msgv += 1;
}
while (priv->msgc > 0);
/* TWIM stop */
nrf91_i2c_putreg(priv, NRF91_TWIM_TASKS_STOP_OFFSET, 1);
/* Wait for stop event */
#ifdef CONFIG_I2C_POLLED
while (1)
{
regval = nrf91_i2c_getreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET) & 0x7;
if (regval != 0)
{
i2cerr("Error SRC: 0x%08" PRIx32 "\n", regval);
ret = -1;
nrf91_i2c_putreg(priv,
NRF91_TWIM_ERRORSRC_OFFSET, 0x7);
goto errout;
}
if (nrf91_i2c_getreg(priv,
NRF91_TWIM_EVENTS_STOPPED_OFFSET) == 1)
{
break;
}
}
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_STOPPED_OFFSET, 0);
#else
nxsem_wait(&priv->sem_isr);
if (priv->status < 0)
{
ret = priv->status;
goto errout;
}
#endif
errout:
#ifndef CONFIG_NRF91_I2C_MASTER_DISABLE_NOSTART
if (pack_buf != NULL && pack_buf != priv->copy_buf)
{
kmm_free(pack_buf);
}
#endif
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* Name: nrf91_i2c_reset
*
* Description:
* Perform an I2C bus reset in an attempt to break loose stuck I2C devices.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_I2C_RESET
static int nrf91_i2c_reset(struct i2c_master_s *dev)
{
#error not implemented
}
#endif
/****************************************************************************
* Name: nrf91_i2c_isr
*
* Description:
* Common I2C interrupt service routine
*
****************************************************************************/
#ifndef CONFIG_I2C_POLLED
static int nrf91_i2c_isr(int irq, void *context, void *arg)
{
struct nrf91_i2c_priv_s *priv = (struct nrf91_i2c_priv_s *)arg;
uint32_t regval = 0;
/* Reset I2C status */
priv->status = OK;
if ((priv->flags & I2C_M_READ) == 0)
{
if (nrf91_i2c_getreg(priv, NRF91_TWIM_EVENTS_LASTTX_OFFSET) == 1)
{
i2cinfo("I2C LASTTX\n");
/* TX done */
nxsem_post(&priv->sem_isr);
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_LASTTX_OFFSET, 0);
return OK;
}
}
else
{
if (nrf91_i2c_getreg(priv, NRF91_TWIM_EVENTS_LASTRX_OFFSET) == 1)
{
i2cinfo("I2C LASTRX\n");
/* RX done */
nxsem_post(&priv->sem_isr);
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_LASTRX_OFFSET, 0);
return OK;
}
}
if (nrf91_i2c_getreg(priv, NRF91_TWIM_EVENTS_STOPPED_OFFSET) == 1)
{
i2cinfo("I2C STOPPED\n");
/* STOPPED event */
nxsem_post(&priv->sem_isr);
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_STOPPED_OFFSET, 0);
}
if (nrf91_i2c_getreg(priv, NRF91_TWIM_EVENTS_ERROR_OFFSET) == 1)
{
regval = nrf91_i2c_getreg(priv, NRF91_TWIM_ERRORSRC_OFFSET) & 0x7;
i2cerr("Error SRC: 0x%08" PRIx32 "\n", regval);
/* Set ERROR status */
if (regval & TWIM_ERRORSRC_OVERRUN)
{
/* Overrun error */
priv->status = -EIO;
}
else
{
/* NACK */
priv->status = -ENXIO;
}
/* ERROR event */
nxsem_post(&priv->sem_isr);
/* Clear event */
nrf91_i2c_putreg(priv, NRF91_TWIM_EVENTS_ERROR_OFFSET, 0);
nrf91_i2c_putreg(priv, NRF91_TWIM_ERRORSRC_OFFSET, 0x7);
}
return OK;
}
#endif
/****************************************************************************
* Name: nrf91_i2c_init
*
* Description:
* Setup the I2C hardware, ready for operation with defaults
*
****************************************************************************/
static int nrf91_i2c_init(struct nrf91_i2c_priv_s *priv)
{
uint32_t regval = 0;
int pin = 0;
int port = 0;
/* Disable TWI interface */
nrf91_i2c_putreg(priv, NRF91_TWIM_ENABLE_OFFSET, TWIM_ENABLE_DIS);
/* Configure SCL and SDA pins */
nrf91_gpio_config(priv->scl_pin);
nrf91_gpio_config(priv->sda_pin);
/* Select SCL pin */
pin = GPIO_PIN_DECODE(priv->scl_pin);
port = GPIO_PORT_DECODE(priv->scl_pin);
regval = (pin << TWIM_PSELSCL_PIN_SHIFT);
regval |= (port << TWIM_PSELSCL_PORT_SHIFT);
nrf91_i2c_putreg(priv, NRF91_TWIM_PSELSCL_OFFSET, regval);
/* Select SDA pin */
pin = GPIO_PIN_DECODE(priv->sda_pin);
port = GPIO_PORT_DECODE(priv->sda_pin);
regval = (pin << TWIM_PSELSDA_PIN_SHIFT);
regval |= (port << TWIM_PSELSDA_PORT_SHIFT);
nrf91_i2c_putreg(priv, NRF91_TWIM_PSELSDA_OFFSET, regval);
/* Enable TWI interface */
nrf91_i2c_putreg(priv, NRF91_TWIM_ENABLE_OFFSET, TWIM_ENABLE_EN);
#ifndef CONFIG_I2C_POLLED
/* Enable I2C interrupts */
regval = (TWIM_INT_LASTRX | TWIM_INT_LASTTX | TWIM_INT_STOPPED |
TWIM_INT_ERROR);
nrf91_i2c_putreg(priv, NRF91_TWIM_INTEN_OFFSET, regval);
/* Attach error and event interrupts to the ISRs */
irq_attach(priv->irq, nrf91_i2c_isr, priv);
up_enable_irq(priv->irq);
#endif
return OK;
}
/****************************************************************************
* Name: nrf91_i2c_deinit
*
* Description:
* Shutdown the I2C hardware
*
****************************************************************************/
static int nrf91_i2c_deinit(struct nrf91_i2c_priv_s *priv)
{
/* Disable TWI interface */
nrf91_i2c_putreg(priv, NRF91_TWIM_ENABLE_OFFSET, TWIM_ENABLE_DIS);
/* Unconfigure GPIO pins */
nrf91_gpio_unconfig(priv->scl_pin);
nrf91_gpio_unconfig(priv->sda_pin);
/* Deatach TWI from GPIO */
nrf91_i2c_putreg(priv, NRF91_TWIM_PSELSCL_OFFSET, TWIM_PSELSCL_RESET);
nrf91_i2c_putreg(priv, NRF91_TWIM_PSELSDA_OFFSET, TWIM_PSELSDA_RESET);
/* Disable and detach interrupts */
#ifndef CONFIG_I2C_POLLED
up_disable_irq(priv->irq);
irq_detach(priv->irq);
#endif
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nrf91_i2cbus_initialize
*
* Description:
* Initialize one I2C bus
*
****************************************************************************/
struct i2c_master_s *nrf91_i2cbus_initialize(int port)
{
struct nrf91_i2c_priv_s *priv = NULL;
i2cinfo("I2C INITIALIZE port=%d\n", port);
/* Get interface */
switch (port)
{
#ifdef CONFIG_NRF91_I2C0_MASTER
case 0:
{
priv = (struct nrf91_i2c_priv_s *)&g_nrf91_i2c0_priv;
break;
}
#endif
#ifdef CONFIG_NRF91_I2C1_MASTER
case 1:
{
priv = (struct nrf91_i2c_priv_s *)&g_nrf91_i2c1_priv;
break;
}
#endif
#ifdef CONFIG_NRF91_I2C2_MASTER
case 2:
{
priv = (struct nrf91_i2c_priv_s *)&g_nrf91_i2c2_priv;
break;
}
#endif
#ifdef CONFIG_NRF91_I2C3_MASTER
case 3:
{
priv = (struct nrf91_i2c_priv_s *)&g_nrf91_i2c3_priv;
break;
}
#endif
default:
{
return NULL;
}
}
/* Initialize private data for the first time, increment reference count,
* power-up hardware and configure GPIOs.
*/
nxmutex_lock(&priv->lock);
if (priv->refs++ == 0)
{
/* Initialize I2C */
nrf91_i2c_init(priv);
}
nxmutex_unlock(&priv->lock);
return (struct i2c_master_s *)priv;
}
/****************************************************************************
* Name: nrf91_i2cbus_uninitialize
*
* Description:
* Uninitialize an I2C bus
*
****************************************************************************/
int nrf91_i2cbus_uninitialize(struct i2c_master_s *dev)
{
struct nrf91_i2c_priv_s *priv = (struct nrf91_i2c_priv_s *)dev;
DEBUGASSERT(dev);
/* Decrement reference count and check for underflow */
if (priv->refs == 0)
{
return ERROR;
}
nxmutex_lock(&priv->lock);
if (--priv->refs)
{
nxmutex_unlock(&priv->lock);
return OK;
}
/* Disable power and other HW resource (GPIO's) */
nrf91_i2c_deinit(priv);
nxmutex_unlock(&priv->lock);
return OK;
}