nuttx/arch/arm/src/cxd56xx/cxd56_i2c.c

1107 lines
27 KiB
C
Raw Normal View History

/****************************************************************************
* arch/arm/src/cxd56xx/cxd56_i2c.c
*
* Copyright 2018 Sony Semiconductor Solutions Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of Sony Semiconductor Solutions Corporation nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <assert.h>
#include <nuttx/arch.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/irq.h>
#include <arch/board/board.h>
#include "chip.h"
#include "arm_arch.h"
#include "arm_internal.h"
#include "cxd56_clock.h"
#include "cxd56_i2c.h"
#include "hardware/cxd56_i2c.h"
#include "cxd56_pinconfig.h"
#if defined(CONFIG_CXD56_I2C0_SCUSEQ) || defined(CONFIG_CXD56_I2C1_SCUSEQ)
#include <arch/chip/scu.h>
#endif
#ifdef CONFIG_CXD56_I2C
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define I2C_TIMEOUT (20*1000/CONFIG_USEC_PER_TICK) /* 20 mS */
#define I2C_DEFAULT_FREQUENCY 400000
#define I2C_FIFO_MAX_SIZE 32
#define I2C_INTR_ENABLE ((INTR_STOP_DET) | \
(INTR_TX_ABRT) | \
(INTR_TX_OVER) | \
(INTR_RX_OVER) | \
(INTR_RX_UNDER))
/****************************************************************************
* Private Data
****************************************************************************/
struct cxd56_i2cdev_s
{
struct i2c_master_s dev; /* Generic I2C device */
unsigned int base; /* Base address of registers */
uint16_t irqid; /* IRQ for this device */
int8_t port; /* Port number */
uint32_t base_freq; /* branch frequency */
sem_t mutex; /* Only one thread can access at a time */
sem_t wait; /* Place to wait for transfer completion */
WDOG_ID timeout; /* watchdog to timeout when bus hung */
uint32_t frequency; /* Current I2C frequency */
ssize_t reg_buff_offset;
ssize_t rw_size;
struct i2c_msg_s *msgs;
int error; /* Error status of each transfers */
int refs; /* Reference count */
};
/* Channel 0 as SCU_I2C0
* Channel 1 as SCU_I2C1
* Channel 2 as I2CM
*/
#ifdef CONFIG_CXD56_I2C0
static struct cxd56_i2cdev_s g_i2c0dev =
{
.port = 0,
.base = CXD56_SCU_I2C0_BASE,
.irqid = CXD56_IRQ_SCU_I2C0,
.refs = 0,
};
#endif
#ifdef CONFIG_CXD56_I2C1
static struct cxd56_i2cdev_s g_i2c1dev =
{
.port = 1,
.base = CXD56_SCU_I2C1_BASE,
.irqid = CXD56_IRQ_SCU_I2C1,
.refs = 0,
};
#endif
#ifdef CONFIG_CXD56_I2C2
static struct cxd56_i2cdev_s g_i2c2dev =
{
.port = 2,
.base = CXD56_I2CM_BASE,
.irqid = CXD56_IRQ_I2CM,
.refs = 0,
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
static inline uint32_t i2c_reg_read(struct cxd56_i2cdev_s *priv,
uint32_t offset);
static inline void i2c_reg_write(struct cxd56_i2cdev_s *priv,
uint32_t offset,
uint32_t val);
static inline void i2c_reg_rmw(struct cxd56_i2cdev_s *dev,
uint32_t offset,
uint32_t val, uint32_t mask);
static int cxd56_i2c_disable(struct cxd56_i2cdev_s *priv);
static void cxd56_i2c_enable(struct cxd56_i2cdev_s *priv);
static int cxd56_i2c_interrupt(int irq, FAR void *context, FAR void *arg);
static void cxd56_i2c_timeout(int argc, uint32_t arg, ...);
static void cxd56_i2c_setfrequency(struct cxd56_i2cdev_s *priv,
uint32_t frequency);
static int cxd56_i2c_transfer(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
static int cxd56_i2c_reset(FAR struct i2c_master_s * dev);
#endif
#if defined(CONFIG_CXD56_I2C0_SCUSEQ) || defined(CONFIG_CXD56_I2C1_SCUSEQ)
static int cxd56_i2c_transfer_scu(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count);
#endif
/****************************************************************************
* Name: cxd56_i2c_pincontrol
*
* Description:
* Configure the I2C pin
*
* Input Parameter:
* on - true: enable pin, false: disable pin
*
****************************************************************************/
static void cxd56_i2c_pincontrol(int ch, bool on)
{
switch (ch)
{
#ifdef CONFIG_CXD56_I2C0
case 0:
if (on)
{
CXD56_PIN_CONFIGS(PINCONFS_I2C0);
}
else
{
CXD56_PIN_CONFIGS(PINCONFS_I2C0_GPIO);
}
break;
#endif /* CONFIG_CXD56_I2C0 */
#ifdef CONFIG_CXD56_I2C1
case 1:
if (on)
{
CXD56_PIN_CONFIGS(PINCONFS_PWMB_I2C1);
}
else
{
CXD56_PIN_CONFIGS(PINCONFS_PWMB_GPIO);
}
break;
#endif /* CONFIG_CXD56_I2C1 */
#ifdef CONFIG_CXD56_I2C2
case 2:
if (on)
{
CXD56_PIN_CONFIGS(PINCONFS_SPI0B_I2C2);
}
else
{
CXD56_PIN_CONFIGS(PINCONFS_SPI0B_GPIO);
}
break;
#endif /* CONFIG_CXD56_I2C2 */
default:
break;
}
}
/****************************************************************************
* I2C device operations
****************************************************************************/
struct i2c_ops_s cxd56_i2c_ops =
{
.transfer = cxd56_i2c_transfer,
#ifdef CONFIG_I2C_RESET
.reset = cxd56_i2c_reset,
#endif
};
#if defined(CONFIG_CXD56_I2C0_SCUSEQ) || defined(CONFIG_CXD56_I2C1_SCUSEQ)
struct i2c_ops_s cxd56_i2c_scu_ops =
{
.transfer = cxd56_i2c_transfer_scu,
#ifdef CONFIG_I2C_RESET
.reset = cxd56_i2c_reset,
#endif
};
#endif
/****************************************************************************
* Name: cxd56_i2c_setfrequency
*
* Description:
* Set the frequency for the next transfer
*
****************************************************************************/
static void cxd56_i2c_setfrequency(struct cxd56_i2cdev_s *priv,
uint32_t frequency)
{
int32_t lcnt;
int32_t hcnt;
uint64_t lcnt64;
uint64_t hcnt64;
uint64_t speed;
uint64_t t_low;
uint64_t t_high;
uint32_t base = cxd56_get_i2c_baseclock(priv->port);
uint32_t spklen;
ASSERT(base);
if ((priv->frequency == frequency) && (priv->base_freq == base))
{
return;
}
priv->frequency = frequency;
priv->base_freq = base;
base /= 1000;
Merged in alinjerpelea/nuttx (pull request #1038) cxd56xx: various fixes * arch: arm: cxd56xx: Support to set any i2c frequency This commit supports to be able to set the i2c frequency other than just 100 or 400 Hz. * arch: arm: cxd56xx: Enable SD clock during access to SD card - Improve GNSS low sensitivity with SD card inserted - Reduce power consumption by stopping SD clock * arch: arm: cxd56xx: Do PANIC() when cpu rx fifo is overflow If CPU Rx FIFO is overflow, it's fatal error for system. In such a case, this commit changes to allow the user to notice by calling PANIC() for debuggability enhancement. * arch: arm: cxd56xx: Fix compile error in sdhci debug code * arch: arm: cxd56xx: Remove assertion in cpu tx queue When CPU Tx queue is overflow, it returns -EAGAIN instead of assertion to be able to retry. * arch: arm: cxd56xx: Add configurations for cpu fifo elements Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix LPADC configuration LPADC options defined in the same symbol name, so kconfig tool couldn't process them correctly. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Update loader and gnssfw ver.17660 * arch: arm: cxd56xx: Fix hang-up when error interrupt occurs Fix a problem that error interrupts are not cleared. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix sdhci semaphore Fix inital value of semaphore in sdhci drive.r Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * boards: cxd56xx: Treat the initial value of LNA power as off LNA is always turned power on when GNSS is started. So it is desirable that the initial value of the LNA power is off by default. * boards: cxd56xx: Remove initial setting of PIN_AP_CLK PIN_AP_CLK is used as a port selector of SDIO expander on the extension board, and is set output low in initalizing. Therefore, this pin cannot be used for other board. To avoid this restriction, remove this initial operation because this pin is pulled down by default on the extension board. Approved-by: Gregory Nutt <gnutt@nuttx.org>
2019-09-26 11:28:26 +00:00
if (frequency <= 100000)
{
t_low = 4700000;
t_high = 4000000;
Merged in alinjerpelea/nuttx (pull request #1038) cxd56xx: various fixes * arch: arm: cxd56xx: Support to set any i2c frequency This commit supports to be able to set the i2c frequency other than just 100 or 400 Hz. * arch: arm: cxd56xx: Enable SD clock during access to SD card - Improve GNSS low sensitivity with SD card inserted - Reduce power consumption by stopping SD clock * arch: arm: cxd56xx: Do PANIC() when cpu rx fifo is overflow If CPU Rx FIFO is overflow, it's fatal error for system. In such a case, this commit changes to allow the user to notice by calling PANIC() for debuggability enhancement. * arch: arm: cxd56xx: Fix compile error in sdhci debug code * arch: arm: cxd56xx: Remove assertion in cpu tx queue When CPU Tx queue is overflow, it returns -EAGAIN instead of assertion to be able to retry. * arch: arm: cxd56xx: Add configurations for cpu fifo elements Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix LPADC configuration LPADC options defined in the same symbol name, so kconfig tool couldn't process them correctly. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Update loader and gnssfw ver.17660 * arch: arm: cxd56xx: Fix hang-up when error interrupt occurs Fix a problem that error interrupts are not cleared. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix sdhci semaphore Fix inital value of semaphore in sdhci drive.r Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * boards: cxd56xx: Treat the initial value of LNA power as off LNA is always turned power on when GNSS is started. So it is desirable that the initial value of the LNA power is off by default. * boards: cxd56xx: Remove initial setting of PIN_AP_CLK PIN_AP_CLK is used as a port selector of SDIO expander on the extension board, and is set output low in initalizing. Therefore, this pin cannot be used for other board. To avoid this restriction, remove this initial operation because this pin is pulled down by default on the extension board. Approved-by: Gregory Nutt <gnutt@nuttx.org>
2019-09-26 11:28:26 +00:00
}
else if (frequency <= 400000)
{
t_low = 1300000;
t_high = 600000;
Merged in alinjerpelea/nuttx (pull request #1038) cxd56xx: various fixes * arch: arm: cxd56xx: Support to set any i2c frequency This commit supports to be able to set the i2c frequency other than just 100 or 400 Hz. * arch: arm: cxd56xx: Enable SD clock during access to SD card - Improve GNSS low sensitivity with SD card inserted - Reduce power consumption by stopping SD clock * arch: arm: cxd56xx: Do PANIC() when cpu rx fifo is overflow If CPU Rx FIFO is overflow, it's fatal error for system. In such a case, this commit changes to allow the user to notice by calling PANIC() for debuggability enhancement. * arch: arm: cxd56xx: Fix compile error in sdhci debug code * arch: arm: cxd56xx: Remove assertion in cpu tx queue When CPU Tx queue is overflow, it returns -EAGAIN instead of assertion to be able to retry. * arch: arm: cxd56xx: Add configurations for cpu fifo elements Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix LPADC configuration LPADC options defined in the same symbol name, so kconfig tool couldn't process them correctly. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Update loader and gnssfw ver.17660 * arch: arm: cxd56xx: Fix hang-up when error interrupt occurs Fix a problem that error interrupts are not cleared. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * arch: arm: cxd56xx: Fix sdhci semaphore Fix inital value of semaphore in sdhci drive.r Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com> * boards: cxd56xx: Treat the initial value of LNA power as off LNA is always turned power on when GNSS is started. So it is desirable that the initial value of the LNA power is off by default. * boards: cxd56xx: Remove initial setting of PIN_AP_CLK PIN_AP_CLK is used as a port selector of SDIO expander on the extension board, and is set output low in initalizing. Therefore, this pin cannot be used for other board. To avoid this restriction, remove this initial operation because this pin is pulled down by default on the extension board. Approved-by: Gregory Nutt <gnutt@nuttx.org>
2019-09-26 11:28:26 +00:00
}
else
{
t_low = 500000;
t_high = 260000;
}
if (frequency > 100000)
{
if (base < 20032)
{
spklen = 1;
}
else if (base < 40064)
{
spklen = 2;
}
else
{
spklen = 3;
}
}
else
{
spklen = 1;
}
lcnt64 = (t_low + 6500ull / 20000ull) * base;
lcnt = ((lcnt64 + 999999999ull) / 1000000000ull) - 1; /* ceil */
lcnt = lcnt < 8 ? 8 : lcnt;
hcnt64 = (t_high - 6500ull) * base;
hcnt = ((hcnt64 + 999999999ull) / 1000000000ull) - 6 - spklen; /* ceil */
hcnt = hcnt < 6 ? 6 : hcnt;
speed =
1000000000000000000ull /
(((lcnt + 1) * 1000000000000ull +
(hcnt + 6 + spklen) * 1000000000000ull) / base +
20000ull / 1000ull * 1000000ull);
if (speed > (frequency * 1000ull))
{
uint64_t adj;
adj = ((1000000000000000000ull / (frequency * 1000ull)) -
(1000000000000000000ull / speed)) *
base;
hcnt += (adj + 999999999999ull) / 1000000000000ull;
}
/* use FS register in SS and FS mode */
i2c_reg_write(priv, CXD56_IC_FS_SCL_HCNT, hcnt);
i2c_reg_write(priv, CXD56_IC_FS_SCL_LCNT, lcnt);
i2c_reg_rmw(priv, CXD56_IC_CON, IC_SPEED_FS, IC_MAX_SPEED_MODE);
i2c_reg_write(priv, CXD56_IC_FS_SPKLEN, spklen);
}
/****************************************************************************
* Name: cxd56_i2c_timeout
*
* Description:
* Watchdog timer for timeout of I2C operation
*
****************************************************************************/
static void cxd56_i2c_timeout(int argc, uint32_t arg, ...)
{
struct cxd56_i2cdev_s *priv = (struct cxd56_i2cdev_s *)arg;
irqstate_t flags = enter_critical_section();
priv->error = -ENODEV;
nxsem_post(&priv->wait);
leave_critical_section(flags);
}
/****************************************************************************
* Name: cxd56_i2c_drainrxfifo
*
* Description:
* Receive I2C data
*
****************************************************************************/
static void cxd56_i2c_drainrxfifo(struct cxd56_i2cdev_s *priv)
{
struct i2c_msg_s *msg = priv->msgs;
uint32_t status;
uint32_t dat;
ssize_t i;
DEBUGASSERT(msg != NULL);
status = i2c_reg_read(priv, CXD56_IC_STATUS);
for (i = 0; i < priv->rw_size && status & STATUS_RFNE; i++)
{
dat = i2c_reg_read(priv, CXD56_IC_DATA_CMD);
msg->buffer[priv->reg_buff_offset + i] = dat & 0xff;
status = i2c_reg_read(priv, CXD56_IC_STATUS);
}
priv->reg_buff_offset += priv->rw_size;
}
/****************************************************************************
* Name: cxd56_i2c_interrupt
*
* Description:
* The I2C Interrupt Handler
*
****************************************************************************/
static int cxd56_i2c_interrupt(int irq, FAR void *context, FAR void *arg)
{
FAR struct cxd56_i2cdev_s *priv = (FAR struct cxd56_i2cdev_s *)arg;
uint32_t state;
int ret;
state = i2c_reg_read(priv, CXD56_IC_INTR_STAT);
if (state & INTR_TX_ABRT)
{
i2c_reg_read(priv, CXD56_IC_CLR_TX_ABRT);
priv->error = -ENODEV;
}
if (state & INTR_TX_OVER)
{
i2c_reg_read(priv, CXD56_IC_CLR_TX_OVER);
priv->error = -EIO;
}
if (state & INTR_RX_OVER)
{
i2c_reg_read(priv, CXD56_IC_CLR_RX_OVER);
priv->error = -EIO;
}
if (state & INTR_RX_UNDER)
{
i2c_reg_read(priv, CXD56_IC_CLR_RX_UNDER);
priv->error = -EIO;
}
if (state & INTR_TX_EMPTY)
{
/* TX_EMPTY is automatically cleared by hardware
* when the buffer level goes above the threshold.
*/
i2c_reg_rmw(priv, CXD56_IC_INTR_MASK, 0, INTR_TX_EMPTY);
}
if (state & INTR_RX_FULL)
{
/* RX_FULL is automatically cleared by hardware
* when the buffer level goes below the threshold.
*/
i2c_reg_rmw(priv, CXD56_IC_INTR_MASK, 0, INTR_RX_FULL);
cxd56_i2c_drainrxfifo(priv);
}
if (state & INTR_STOP_DET)
{
i2c_reg_read(priv, CXD56_IC_CLR_STOP_DET);
}
if ((priv->error) || (state & INTR_TX_EMPTY) || (state & INTR_RX_FULL))
{
/* Failure of wd_cancel() means that the timer expired.
* In this case, nxsem_post() has already been called.
* Therefore, call nxsem_post() only when wd_cancel() succeeds.
*/
ret = wd_cancel(priv->timeout);
if (ret == OK)
{
nxsem_post(&priv->wait);
}
}
return OK;
}
/****************************************************************************
* Name: cxd56_i2c_receive
*
* Description:
* Receive data from I2C bus.
* Prohibit all interrupt because the STOP condition might happen
* if the interrupt occurs when the writing request.
* Actual receiving data is in RX_FULL interrupt handler.
*
* TODO : The argument "last" is not used.
****************************************************************************/
static int cxd56_i2c_receive(struct cxd56_i2cdev_s *priv, int last)
{
struct i2c_msg_s *msg = priv->msgs;
int i;
int en;
ssize_t msg_length;
irqstate_t flags;
priv->reg_buff_offset = 0;
DEBUGASSERT(msg != NULL);
for (msg_length = msg->length; msg_length > 0; msg_length -= priv->rw_size)
{
if (msg_length <= I2C_FIFO_MAX_SIZE)
{
priv->rw_size = msg_length;
en = 1;
}
else
{
priv->rw_size = I2C_FIFO_MAX_SIZE;
en = 0;
}
/* update threshold value of the receive buffer */
i2c_reg_write(priv, CXD56_IC_RX_TL, priv->rw_size - 1);
for (i = 0; i < priv->rw_size - 1; i++)
{
i2c_reg_write(priv, CXD56_IC_DATA_CMD, CMD_READ);
}
flags = enter_critical_section();
wd_start(priv->timeout, I2C_TIMEOUT, cxd56_i2c_timeout, 1,
(uint32_t)priv);
/* Set stop flag for indicate the last data */
i2c_reg_write(priv, CXD56_IC_DATA_CMD, CMD_READ | (en ? CMD_STOP : 0));
i2c_reg_rmw(priv, CXD56_IC_INTR_MASK, INTR_RX_FULL, INTR_RX_FULL);
leave_critical_section(flags);
nxsem_wait(&priv->wait);
if (priv->error != OK)
{
break;
}
}
return 0;
}
/****************************************************************************
* Name: cxd56_i2c_send
*
* Description:
* Send data to I2C bus.
*
****************************************************************************/
static int cxd56_i2c_send(struct cxd56_i2cdev_s *priv, int last)
{
struct i2c_msg_s *msg = priv->msgs;
ssize_t i;
irqstate_t flags;
DEBUGASSERT(msg != NULL);
for (i = 0; i < msg->length - 1; i++)
{
while (!(i2c_reg_read(priv, CXD56_IC_STATUS) & STATUS_TFNF));
i2c_reg_write(priv, CXD56_IC_DATA_CMD, (uint32_t)msg->buffer[i]);
}
while (!(i2c_reg_read(priv, CXD56_IC_STATUS) & STATUS_TFNF));
flags = enter_critical_section();
wd_start(priv->timeout, I2C_TIMEOUT, cxd56_i2c_timeout, 1, (uint32_t)priv);
i2c_reg_write(priv, CXD56_IC_DATA_CMD,
(uint32_t)msg->buffer[i] | (last ? CMD_STOP : 0));
/* Enable TX_EMPTY interrupt for determine transfer done. */
i2c_reg_rmw(priv, CXD56_IC_INTR_MASK, INTR_TX_EMPTY, INTR_TX_EMPTY);
leave_critical_section(flags);
nxsem_wait(&priv->wait);
return 0;
}
/****************************************************************************
* Name: cxd56_i2c_transfer
*
* Description:
* Perform a sequence of I2C transfers
*
* TODO: Multiple i2c_msg_s read operations with the same address are not
* currently guaranteed.
****************************************************************************/
static int cxd56_i2c_transfer(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count)
{
struct cxd56_i2cdev_s *priv = (struct cxd56_i2cdev_s *)dev;
int i;
int ret = 0;
int semval = 0;
int addr = -1;
static int wostop = 0;
DEBUGASSERT(dev != NULL);
/* Get exclusive access to the I2C bus */
nxsem_wait(&priv->mutex);
/* Check wait semaphore value. If the value is not 0, the transfer can not
* be performed normally.
*/
ret = nxsem_getvalue(&priv->wait, &semval);
DEBUGASSERT(ret == OK && semval == 0);
/* Disable clock gating (clock enable) */
cxd56_i2c_clock_gate_disable(priv->port);
for (i = 0; i < count; i++, msgs++)
{
/* Pass msg descriptor via device context */
priv->msgs = msgs;
priv->error = OK;
if ((addr != msgs->addr) && !wostop)
{
cxd56_i2c_disable(priv);
cxd56_i2c_setfrequency(priv, msgs->frequency);
i2c_reg_rmw(priv, CXD56_IC_CON, IC_RESTART_EN, IC_RESTART_EN);
i2c_reg_write(priv, CXD56_IC_TAR, msgs->addr & 0x7f);
cxd56_i2c_enable(priv);
addr = msgs->addr;
}
if (msgs->flags & I2C_M_NOSTOP)
{
/* Don't send stop condition even if the last data */
wostop = 1;
}
else
{
wostop = 0;
}
if (msgs->flags & I2C_M_READ)
{
ret = cxd56_i2c_receive(priv, (wostop) ? 0 : (i + 1 == count));
}
else
{
ret = cxd56_i2c_send(priv, (wostop) ? 0 : (i + 1 == count));
}
if (ret < 0)
{
break;
}
if (priv->error != OK)
{
ret = priv->error;
break;
}
/* Clear msg descriptor for prevent illegal access in interrupt */
priv->msgs = NULL;
}
if (!wostop)
{
cxd56_i2c_disable(priv);
}
/* Enable clock gating (clock disable) */
cxd56_i2c_clock_gate_enable(priv->port);
nxsem_post(&priv->mutex);
return ret;
}
/****************************************************************************
* Name: cxd56_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 cxd56_i2c_reset(FAR struct i2c_master_s *dev)
{
return OK;
}
#endif /* CONFIG_I2C_RESET */
/****************************************************************************
* Name: cxd56_i2c_transfer_scu
*
* Description:
* Perform a sequence of I2C transfers with scu oneshot sequencer.
*
****************************************************************************/
#if defined(CONFIG_CXD56_I2C0_SCUSEQ) || defined(CONFIG_CXD56_I2C1_SCUSEQ)
static int cxd56_i2c_scurecv(int port, int addr,
uint8_t *buf, ssize_t buflen)
{
uint16_t inst[2];
int instn;
int len0;
int len1;
ssize_t rem;
int ret = OK;
/* Ignore buffer is NULL */
if (buf == NULL)
{
return OK;
}
if (buflen > 16)
{
return -EINVAL;
}
rem = buflen;
len0 = rem > 8 ? 8 : rem;
rem -= len0;
len1 = rem > 8 ? 8 : rem;
rem -= len1;
inst[0] = SCU_INST_RECV(len0);
if (len1)
{
inst[1] = SCU_INST_RECV(len1);
instn = 2;
}
else
{
instn = 1;
}
inst[instn - 1] |= SCU_INST_LAST;
ret = scu_i2ctransfer(port, addr, inst, instn, buf, buflen);
if (ret < 0)
{
syslog(LOG_ERR, "I2C receive failed. port %d addr %d\n",
port, addr);
}
return ret;
}
static int cxd56_i2c_scusend(int port, int addr,
uint8_t *buf, ssize_t buflen)
{
uint16_t inst[12];
ssize_t rem;
int i;
int ret = OK;
rem = buflen;
while (rem)
{
for (i = 0; i < 12 && rem > 0; i++)
{
inst[i] = SCU_INST_SEND(*buf++);
rem--;
}
if (rem == 0)
{
inst[i - 1] |= SCU_INST_LAST;
}
if (i > 0)
{
ret = scu_i2ctransfer(port, addr, inst, i, NULL, i);
if (ret < 0)
{
syslog(LOG_ERR, "I2C send failed. port %d addr %d\n",
port, addr);
break;
}
}
}
return ret;
}
static int cxd56_i2c_transfer_scu(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count)
{
FAR struct cxd56_i2cdev_s *priv = (FAR struct cxd56_i2cdev_s *)dev;
ssize_t len = 0;
uint8_t *buf = NULL;
uint8_t addr = msgs->addr;
int i;
int ret = 0;
DEBUGASSERT(dev != NULL);
/* Get exclusive access to the I2C bus */
nxsem_wait(&priv->mutex);
/* Apply frequency for request msgs */
if (priv->frequency != msgs->frequency)
{
cxd56_i2c_clock_gate_disable(priv->port);
cxd56_i2c_disable(priv);
cxd56_i2c_setfrequency(priv, msgs->frequency);
i2c_reg_rmw(priv, CXD56_IC_CON, IC_RESTART_EN, IC_RESTART_EN);
i2c_reg_write(priv, CXD56_IC_TAR, msgs->addr & 0x7f);
cxd56_i2c_enable(priv);
cxd56_i2c_clock_gate_enable(priv->port);
priv->frequency = msgs->frequency;
}
for (i = 0; i < count; i++, msgs++)
{
len = msgs->length;
buf = msgs->buffer;
if (msgs->flags & I2C_M_READ)
{
ret = cxd56_i2c_scurecv(priv->port, addr, buf, len);
}
else
{
ret = cxd56_i2c_scusend(priv->port, addr, buf, len);
}
if (ret < 0)
{
break;
}
}
nxsem_post(&priv->mutex);
return ret;
}
#endif
static inline uint32_t i2c_reg_read(struct cxd56_i2cdev_s *priv,
uint32_t offset)
{
return getreg32(priv->base + offset);
}
static inline void i2c_reg_write(struct cxd56_i2cdev_s *priv,
uint32_t offset, uint32_t val)
{
putreg32(val, priv->base + offset);
}
static inline void i2c_reg_rmw(struct cxd56_i2cdev_s *priv, uint32_t offset,
uint32_t val, uint32_t mask)
{
uint32_t regval;
regval = getreg32(priv->base + offset);
putreg32((regval & ~mask) | val, priv->base + offset);
}
static int cxd56_i2c_disable(struct cxd56_i2cdev_s *priv)
{
int retry = 25000;
uint32_t stat;
/* disable all interrupt */
i2c_reg_write(priv, CXD56_IC_INTR_MASK, 0x0);
/* clear all interrupt status */
i2c_reg_read(priv, CXD56_IC_CLR_INTR);
i2c_reg_write(priv, CXD56_IC_ENABLE, 0);
do
{
stat = i2c_reg_read(priv, CXD56_IC_ENABLE_STATUS);
}
while (--retry && (stat & ESTATUS_IC_EN));
if (!retry)
{
i2cerr("i2c wait timeout.\n");
return -EBUSY;
}
/* clear all interrupt status again */
i2c_reg_read(priv, CXD56_IC_CLR_INTR);
return 0;
}
static void cxd56_i2c_enable(struct cxd56_i2cdev_s *priv)
{
i2c_reg_write(priv, CXD56_IC_INTR_MASK, I2C_INTR_ENABLE);
i2c_reg_write(priv, CXD56_IC_ENABLE, 1);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cxd56_i2cbus_initialize
*
* Description:
* Initialise an I2C device
*
****************************************************************************/
struct i2c_master_s *cxd56_i2cbus_initialize(int port)
{
struct cxd56_i2cdev_s *priv;
irqstate_t flags;
flags = enter_critical_section();
#ifdef CONFIG_CXD56_I2C0
if (port == 0)
{
priv = &g_i2c0dev;
# ifndef CONFIG_CXD56_I2C0_SCUSEQ
priv->dev.ops = &cxd56_i2c_ops;
# else
priv->dev.ops = &cxd56_i2c_scu_ops;
# endif
}
else
#endif
#ifdef CONFIG_CXD56_I2C1
if (port == 1)
{
priv = &g_i2c1dev;
# ifndef CONFIG_CXD56_I2C1_SCUSEQ
priv->dev.ops = &cxd56_i2c_ops;
# else
priv->dev.ops = &cxd56_i2c_scu_ops;
# endif
}
else
#endif
#ifdef CONFIG_CXD56_I2C2
if (port == 2)
{
priv = &g_i2c2dev;
priv->dev.ops = &cxd56_i2c_ops;
}
else
#endif
{
leave_critical_section(flags);
i2cerr("I2C Only support 0,1,2\n");
return NULL;
}
priv->refs++;
/* Test if already initialized or not */
if (1 < priv->refs)
{
leave_critical_section(flags);
return &priv->dev;
}
priv->port = port;
priv->frequency = 0;
cxd56_i2c_clock_enable(priv->port);
priv->base_freq = cxd56_get_i2c_baseclock(priv->port);
cxd56_i2c_disable(priv);
i2c_reg_write(priv, CXD56_IC_INTR_MASK, 0x00);
i2c_reg_read(priv, CXD56_IC_CLR_INTR);
/* set threshold level of the Rx/Tx FIFO */
i2c_reg_write(priv, CXD56_IC_RX_TL, 0xff);
i2c_reg_write(priv, CXD56_IC_TX_TL, 0);
/* set hold time for margin */
i2c_reg_write(priv, CXD56_IC_SDA_HOLD, 1);
i2c_reg_write(priv, CXD56_IC_CON,
(IC_SLAVE_DISABLE | IC_MASTER_MODE | IC_TX_EMPTY_CTRL));
cxd56_i2c_setfrequency(priv, I2C_DEFAULT_FREQUENCY);
leave_critical_section(flags);
/* Configure pin */
cxd56_i2c_pincontrol(port, true);
nxsem_init(&priv->mutex, 0, 1);
nxsem_init(&priv->wait, 0, 0);
priv->timeout = wd_create();
/* Attach Interrupt Handler */
irq_attach(priv->irqid, cxd56_i2c_interrupt, priv);
/* Enable Interrupt Handler */
up_enable_irq(priv->irqid);
/* Enable Interrupt in SCU */
if (port == 0 || port == 1)
{
putreg32(getreg32(CXD56_SCU_BASE + 0x400) | (1u << (port + 1)),
CXD56_SCU_BASE + 0x400);
}
/* Enable clock gating (clock disable) */
cxd56_i2c_clock_gate_enable(port);
return &priv->dev;
}
/****************************************************************************
* Name: cxd56_i2cbus_uninitialize
*
* Description:
* Uninitialise an I2C device
*
****************************************************************************/
int cxd56_i2cbus_uninitialize(FAR struct i2c_master_s *dev)
{
struct cxd56_i2cdev_s *priv = (struct cxd56_i2cdev_s *)dev;
/* Decrement reference count and check for underflow */
if (priv->refs == 0)
{
return ERROR;
}
if (--priv->refs)
{
return OK;
}
/* Configure pin */
cxd56_i2c_pincontrol(priv->port, false);
/* Disable clock gating (clock enable) */
cxd56_i2c_clock_gate_disable(priv->port);
cxd56_i2c_disable(priv);
cxd56_i2c_clock_disable(priv->port);
up_disable_irq(priv->irqid);
irq_detach(priv->irqid);
wd_delete(priv->timeout);
priv->timeout = NULL;
nxsem_destroy(&priv->mutex);
nxsem_destroy(&priv->wait);
return OK;
}
#endif