* arch/arm/src/mx8mp/mx8mp_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/signal.h>
#include <nuttx/mutex.h>
#include <nuttx/i2c/i2c_master.h>
#include "mx8mp_i2c.h"
#include "mx8mp_ccm.h"
#include "hardware/mx8mp_memorymap.h"
#include "hardware/mx8mp_i2c.h"
#include <debug.h>
#include <assert.h>
#include "arm_internal.h"
* Pre-processor Definitions
****************************************************************************/
#define I2C_STALL_TIMEOUT MSEC2TICK(10)
* Private Function Prototypes
****************************************************************************/
struct mx8mp_i2c_s;
static bool is_busy(struct mx8mp_i2c_s *priv);
static void mx8mp_i2c_enable(struct mx8mp_i2c_s *priv);
static void mx8mp_i2c_disable(struct mx8mp_i2c_s *priv);
static int mx8mp_i2c_interrupt(int irq, void *context, void *arg);
static void mx8mp_i2c_reset_bus(struct mx8mp_i2c_s *dev);
static int mx8mp_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs, int count);
static int mx8mp_i2c_reset(struct i2c_master_s *dev);
* Private Types
****************************************************************************/
struct mx8mp_i2c_s
{
struct i2c_master_s dev;
uint32_t base;
int clock;
uint16_t irq;
uint32_t frequency;
int refs;
mutex_t lock;
sem_t wait;
};
static const struct i2c_ops_s mx8mp_i2c_ops =
{
.transfer = mx8mp_i2c_transfer
#ifdef CONFIG_I2C_RESET
, .reset = mx8mp_i2c_reset
#endif
};
#ifdef CONFIG_MX8MP_I2C1
static struct mx8mp_i2c_s g_i2c1_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C1,
.clock = I2C1_CLK_ROOT,
.irq = MX8MP_IRQ_I2C1,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
#ifdef CONFIG_MX8MP_I2C2
static struct mx8mp_i2c_s g_i2c2_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C2,
.clock = I2C2_CLK_ROOT,
.irq = MX8MP_IRQ_I2C2,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
#ifdef CONFIG_MX8MP_I2C3
static struct mx8mp_i2c_s g_i2c3_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C3,
.clock = I2C3_CLK_ROOT,
.irq = MX8MP_IRQ_I2C3,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
#ifdef CONFIG_MX8MP_I2C4
static struct mx8mp_i2c_s g_i2c4_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C4,
.clock = I2C4_CLK_ROOT,
.irq = MX8MP_IRQ_I2C4,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
#ifdef CONFIG_MX8MP_I2C5
static struct mx8mp_i2c_s g_i2c5_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C5,
.clock = I2C5_CLK_ROOT,
.irq = MX8MP_IRQ_I2C5,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
#ifdef CONFIG_MX8MP_I2C6
static struct mx8mp_i2c_s g_i2c6_dev =
{
.dev.ops = &mx8mp_i2c_ops,
.base = MX8M_I2C6,
.clock = I2C6_CLK_ROOT,
.irq = MX8MP_IRQ_I2C6,
.refs = 0,
.lock = NXMUTEX_INITIALIZER,
.wait = SEM_INITIALIZER(0),
};
#endif
* Private Data
****************************************************************************/
static const uint16_t DIVIDERS_MAP[] =
{
30, 32, 36, 42, 48, 52, 60, 72,
80, 88, 104, 128, 144, 160, 192, 240,
288, 320, 384, 480, 576, 640, 768, 960,
1152, 1280, 1536, 1920, 2304, 2560, 3072, 3840,
22, 24, 26, 28, 32, 36, 40, 44,
48, 56, 64, 72, 80, 96, 112, 128,
160, 192, 224, 256, 320, 384, 448, 512,
640, 768, 896, 1024, 1280, 1536, 1792, 2048
};
* Private Functions
****************************************************************************/
* Name: mx8mp_i2c_init
*
* Description:
* Setup the I2C hardware, ready for operation with defaults
*
****************************************************************************/
static int mx8mp_i2c_init(struct mx8mp_i2c_s *priv)
{
mx8mp_i2c_enable(priv);
irq_attach(priv->irq, mx8mp_i2c_interrupt, priv);
up_enable_irq(priv->irq);
priv->frequency = 0;
return 0;
}
* Name: mx8mp_i2c_deinit
*
* Description:
* Shutdown the I2C hardware
*
****************************************************************************/
static int mx8mp_i2c_deinit(struct mx8mp_i2c_s *priv)
{
up_disable_irq(priv->irq);
irq_detach(priv->irq);
mx8mp_i2c_disable(priv);
return 0;
}
* Name: mx8mp_i2c_enable
*
* Description:
* Enable the I2C peripheral
*
****************************************************************************/
static void mx8mp_i2c_enable(struct mx8mp_i2c_s *priv)
{
putreg16(0, priv->base + I2SR_OFFSET);
modreg16(I2CR_IEN, I2CR_IEN, priv->base + I2CR_OFFSET);
modreg16(I2CR_IIEN, I2CR_IIEN, priv->base + I2CR_OFFSET);
}
* Name: mx8mp_i2c_disable
*
* Description:
* Disable the I2C peripheral
*
****************************************************************************/
static void mx8mp_i2c_disable(struct mx8mp_i2c_s *priv)
{
putreg16(0, priv->base + I2CR_OFFSET);
}
* Name: is_busy
*
* Description:
* Check if the bus is busy (true) or not (false)
*
****************************************************************************/
static bool is_busy(struct mx8mp_i2c_s *priv)
{
return (getreg16(priv->base + I2SR_OFFSET) & I2SR_IBB);
}
* Name: mx8mp_i2c_wait
*
* Description:
* Wait for the IRQ to be triggered.
*
****************************************************************************/
static inline int mx8mp_i2c_wait_irq(struct mx8mp_i2c_s *priv, int usec)
{
return nxsem_tickwait(&priv->wait, USEC2TICK(usec));
}
* Name: mx8mp_i2c_send_start
*
* Description:
* Emit start bit on the bus
*
****************************************************************************/
static int mx8mp_i2c_send_start(struct mx8mp_i2c_s *priv)
{
if (is_busy(priv))
{
return -EBUSY;
}
modreg16(I2CR_MSTA | I2CR_MTX | I2CR_TXAK,
I2CR_MSTA | I2CR_MTX | I2CR_TXAK,
priv->base + I2CR_OFFSET);
return 0;
}
* Name: mx8mp_i2c_send_repeat_start
*
* Description:
* Emit repeat start bit on the bus
*
****************************************************************************/
static int mx8mp_i2c_send_repeat_start(struct mx8mp_i2c_s *priv)
{
if (is_busy(priv) && !(getreg16(priv->base + I2SR_OFFSET) & I2CR_MSTA))
{
return -EBUSY;
}
modreg16(I2CR_RSTA, I2CR_RSTA, priv->base + I2CR_OFFSET);
return 0;
}
* Name: mx8mp_i2c_send_stop
*
* Description:
* Emit stop bit on the bus
*
****************************************************************************/
static void mx8mp_i2c_send_stop(struct mx8mp_i2c_s *priv)
{
modreg16(0, I2CR_MSTA | I2CR_MTX | I2CR_TXAK, priv->base + I2CR_OFFSET);
}
* Name: mx8mp_i2c_write_byte
*
* Description:
* Write a byte on the bus, checking for arbitration lost and RX ACK.
*
****************************************************************************/
static int mx8mp_i2c_write_byte(struct mx8mp_i2c_s *priv, uint8_t byte)
{
int ret;
uint16_t status;
modreg16(0, I2SR_IIF, priv->base + I2SR_OFFSET);
putreg16(byte, priv->base + I2DR_OFFSET);
ret = mx8mp_i2c_wait_irq(priv, 500);
if (ret < 0)
{
return ret;
}
status = getreg16(priv->base + I2SR_OFFSET);
if (status & I2SR_IAL)
{
modreg16(0, I2SR_IAL, priv->base + I2SR_OFFSET);
return -EACCES;
}
if (status & I2SR_RXAK)
{
return -EIO;
}
return 0;
}
* Name: mx8mp_i2c_write_bytes
*
* Description:
* Write multiple byte on the bus, handling the STOP logic.
*
****************************************************************************/
static int mx8mp_i2c_write_bytes(struct mx8mp_i2c_s *priv,
const uint8_t *data,
uint32_t size,
bool is_last)
{
uint32_t i;
int ret;
for (i = 0; i < size; ++i)
{
ret = mx8mp_i2c_write_byte(priv, data[i]);
if (ret < 0)
{
return ret;
}
}
if (is_last)
{
mx8mp_i2c_send_stop(priv);
}
return 0;
}
* Name: mx8mp_i2c_read_bytes
*
* Description:
* Read multiple bytes on the bus, handling the NACK and STOP logic.
*
****************************************************************************/
int mx8mp_i2c_read_bytes(struct mx8mp_i2c_s *priv,
uint8_t *data,
uint32_t size,
bool is_last)
{
int ret;
uint8_t temp;
uint32_t i;
temp = getreg16(priv->base + I2CR_OFFSET);
temp &= ~(I2CR_MTX | I2CR_TXAK);
if (size == 1)
{
temp |= I2CR_TXAK;
}
putreg16(temp, priv->base + I2CR_OFFSET);
modreg16(0, I2SR_IIF, priv->base + I2SR_OFFSET);
data[0] = getreg16(priv->base + I2DR_OFFSET);
for (i = 0; i < size; ++i)
{
ret = mx8mp_i2c_wait_irq(priv, 500);
if (ret < 0)
{
return ret;
}
if ((i == (size - 1)) && is_last)
{
mx8mp_i2c_send_stop(priv);
}
else if (i == (size - 2))
{
modreg16(0, I2CR_TXAK, priv->base + I2CR_OFFSET);
}
modreg16(0, I2SR_IIF, priv->base + I2SR_OFFSET);
data[i] = getreg16(priv->base + I2DR_OFFSET);
}
return 0;
}
* Name: mx8mp_i2c_set_frequency
*
* Description:
* Set the I2C peripheral clock frequency. Note: the real frequency may be
* lower than the targeted one (but never greater).
*
****************************************************************************/
static void mx8mp_i2c_set_frequency(struct mx8mp_i2c_s *priv,
uint32_t frequency)
{
uint8_t i;
uint8_t divider;
uint32_t old_frequency;
uint32_t clock;
i2cinfo("frequency=%lu\n", frequency);
clock = mx8mp_ccm_get_clock(priv->clock);
DEBUGASSERT(clock > frequency);
if (frequency == priv->frequency)
{
return;
}
divider = 0;
old_frequency = 0;
for (i = 0; i < 0x40; ++i)
{
uint32_t current_frequency = clock / DIVIDERS_MAP[i];
if ((current_frequency <= frequency) &&
(current_frequency > old_frequency))
{
divider = i;
old_frequency = current_frequency;
}
}
putreg16(divider, priv->base + IFDR_OFFSET);
priv->frequency = frequency;
i2cinfo(" real frequency=%lu (%lu / %d)\n",
old_frequency, clock, DIVIDERS_MAP[divider]);
}
* Name: mx8mp_i2c_reset_bus
*
* Description:
* Reset the bus if this one is stall (stuck I2C device(s))
*
****************************************************************************/
static void mx8mp_i2c_reset_bus(struct mx8mp_i2c_s *priv)
{
mx8mp_i2c_disable(priv);
nxsig_usleep(50);
mx8mp_i2c_enable(priv);
nxsig_usleep(50);
}
* Name: mx8mp_i2c_interrupt
*
* Description:
* The I2C common interrupt handler
*
****************************************************************************/
static int mx8mp_i2c_interrupt(int irq, void *context, void *arg)
{
struct mx8mp_i2c_s *priv = (struct mx8mp_i2c_s *)arg;
modreg16(0, I2SR_IIF, priv->base + I2SR_OFFSET);
nxsem_post(&priv->wait);
return 0;
}
* Name: mx8mp_i2c_transfer
*
* Description:
* Perform a sequence of I2C transfers
*
****************************************************************************/
static int mx8mp_i2c_transfer(struct i2c_master_s *dev,
struct i2c_msg_s *msgs, int count)
{
struct mx8mp_i2c_s *priv = (struct mx8mp_i2c_s *)dev;
struct i2c_msg_s *msg;
clock_t deadline;
bool last;
int ret;
int i;
i2cinfo("msgs=%p count=%d\n", msgs, count);
DEBUGASSERT(dev != NULL && msgs != NULL && (unsigned)count <= UINT16_MAX);
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
* only on the first message. This could be extended to support
* different transfer frequencies for each message segment.
*/
mx8mp_i2c_set_frequency(priv, msgs->frequency);
if ((I2C_M_NOSTART & msgs->flags) == 0)
{
ret = mx8mp_i2c_send_start(priv);
if (ret < 0)
{
goto error;
}
}
for (i = 0; i < count; ++i)
{
msg = msgs + i;
last = (i == (count - 1));
if ((I2C_M_NOSTART & msg->flags) == 0)
{
if (I2C_M_READ & msg->flags)
{
ret = mx8mp_i2c_write_byte(priv, I2C_READADDR8(msg->addr));
}
else
{
ret = mx8mp_i2c_write_byte(priv, I2C_WRITEADDR8(msg->addr));
}
}
if (ret == 0)
{
if (I2C_M_READ & msg->flags)
{
ret = mx8mp_i2c_read_bytes(priv,
msg->buffer, msg->length, last);
}
else
{
ret = mx8mp_i2c_write_bytes(priv,
msg->buffer, msg->length, last);
}
}
if (ret == -EACCES)
{
goto error;
}
else if (ret < 0)
{
mx8mp_i2c_send_stop(priv);
goto error;
}
if (!last && ((I2C_M_NOSTART & msg->flags) == 0))
{
ret = mx8mp_i2c_send_repeat_start(priv);
if (ret < 0)
{
goto error;
}
}
}
error:
deadline = clock_systime_ticks() + I2C_STALL_TIMEOUT;
while (is_busy(priv))
{
if (clock_systime_ticks() > deadline)
{
i2cwarn("Bus is stall: try to reset it\n");
mx8mp_i2c_reset_bus(priv);
break;
}
nxsig_usleep(10);
}
nxmutex_unlock(&priv->lock);
return ret;
}
static int mx8mp_i2c_reset(struct i2c_master_s *dev)
{
struct mx8mp_i2c_s *priv = (struct mx8mp_i2c_s *)dev;
int ret;
DEBUGASSERT(priv->refs > 0);
ret = nxmutex_lock(&priv->lock);
if (ret < 0)
{
return ret;
}
mx8mp_i2c_reset_bus(priv);
nxmutex_unlock(&priv->lock);
return 0;
}
* Public Functions
****************************************************************************/
* Name: mx8mp_i2cbus_initialize
*
* Description:
* Initialise an I2C device
*
****************************************************************************/
struct i2c_master_s *mx8mp_i2cbus_initialize(int port)
{
struct mx8mp_i2c_s *priv;
i2cinfo("port=%d\n", port);
switch (port)
{
#ifdef CONFIG_MX8MP_I2C1
case 1:
priv = &g_i2c1_dev;
break;
#endif
#ifdef CONFIG_MX8MP_I2C2
case 2:
priv = &g_i2c2_dev;
break;
#endif
#ifdef CONFIG_MX8MP_I2C3
case 3:
priv = &g_i2c3_dev;
break;
#endif
#ifdef CONFIG_MX8MP_I2C4
case 4:
priv = &g_i2c4_dev;
break;
#endif
#ifdef CONFIG_MX8MP_I2C5
case 5:
priv = &g_i2c5_dev;
break;
#endif
#ifdef CONFIG_MX8MP_I2C6
case 6:
priv = &g_i2c6_dev;
break;
#endif
default:
i2cerr("ERROR: Unsupported I2C port %d\n", port);
return NULL;
}
nxmutex_lock(&priv->lock);
if (priv->refs++ == 0)
{
mx8mp_i2c_init(priv);
}
nxmutex_unlock(&priv->lock);
return &priv->dev;
}
* Name: mx8mp_i2cbus_uninitialize
*
* Description:
* Uninitialise an I2C device
*
****************************************************************************/
int mx8mp_i2cbus_uninitialize(struct i2c_master_s *dev)
{
struct mx8mp_i2c_s *priv = (struct mx8mp_i2c_s *)dev;
DEBUGASSERT(priv != NULL);
if (priv->refs == 0)
{
return -1;
}
nxmutex_lock(&priv->lock);
if (--priv->refs)
{
nxmutex_unlock(&priv->lock);
return 0;
}
mx8mp_i2c_deinit(priv);
nxmutex_unlock(&priv->lock);
return 0;
}