openmv/ports/alif/omv_i2c.c
iabdalkader daf2bb30da misc: Restructure repo.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
2025-04-13 08:28:34 +02:00

507 lines
16 KiB
C

/*
* Copyright (C) 2023-2024 OpenMV, LLC.
*
* 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. Any redistribution, use, or modification in source or binary form
* is done solely for personal benefit and not for any commercial
* purpose or for monetary gain. For commercial licensing options,
* please contact openmv@openmv.io
*
* THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "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 LICENSOR OR COPYRIGHT
* OWNER 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.
*
* Alif I2C driver.
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "py/mphal.h"
#include "py/runtime.h"
#include "omv_portconfig.h"
#include "omv_boardconfig.h"
#include "alif_hal.h"
#include "omv_gpio.h"
#include "omv_common.h"
#include "omv_i2c.h"
#include "sys_ctrl_i3c.h"
#define I2C_SCAN_TIMEOUT (10)
#define I2C_XFER_TIMEOUT (1000)
#define I2C_DAT_INDEX (0)
#define I2C_IC_STATUS_RFNE I2C_IC_STATUS_RECEIVE_FIFO_NOT_EMPTY
#define I2C_IC_STATUS_TFNF I2C_IC_STATUS_TRANSMIT_FIFO_NOT_FULL
#define I2C_IC_CON_MASTER_TX_EMPTY_CTRL (1 << 8)
#define I2C_CON_SPEED(speed) \
((speed == I2C_SPEED_STANDARD) ? I2C_IC_CON_SPEED_STANDARD : I2C_IC_CON_SPEED_FAST)
#define I2C_TX_FIFO_LEN (I2C_FIFO_DEPTH / 2)
#define I2C_RX_FIFO_LEN (I2C_FIFO_DEPTH / 2)
#define I2C_STAT_ERRORS (I2C_IC_INTR_STAT_TX_ABRT | I2C_IC_INTR_STAT_TX_OVER | \
I2C_IC_INTR_STAT_RX_OVER | I2C_IC_INTR_STAT_RX_UNDER)
#ifdef NDEBUG
#define I2C_CHECK_ERRORS(base) \
if (base->I2C_RAW_INTR_STAT & I2C_STAT_ERRORS) { \
(void) base->I2C_RAW_INTR_STAT; \
(void) base->I2C_CLR_TX_ABRT; \
return -1; \
}
#else
#define I2C_CHECK_ERRORS(base) \
if (base->I2C_RAW_INTR_STAT & I2C_STAT_ERRORS) { \
uint32_t status = base->I2C_RAW_INTR_STAT; \
printf("status: 0x%lx raw_int: 0x%lx abort: 0x%lx line: %d\n", \
base->I2C_STATUS, status, base->I2C_TX_ABRT_SOURCE, __LINE__); \
(void) base->I2C_CLR_TX_ABRT; \
return -1; \
}
#endif
typedef struct {
uint8_t *data;
size_t size;
uint32_t flags;
uint32_t direction;
uint8_t address;
} i2c_transfer_t;
typedef enum {
I2C_TRANSFER_READ = (1 << 0),
I2C_TRANSFER_WRITE = (1 << 1),
} i2c_transfer_direction_t;
static int omv_ixc_transfer_timeout(omv_i2c_t *i2c, i2c_transfer_t *xfer, uint32_t timeout);
int omv_i2c_init(omv_i2c_t *i2c, uint32_t bus_id, uint32_t speed) {
i2c->id = bus_id;
i2c->initialized = false;
i2c->is_i3c = (bus_id == OMV_I3C0_ID);
i2c->cw_size = 0;
i2c->speed = speed;
switch (speed) {
case OMV_I2C_SPEED_STANDARD:
speed = i2c->is_i3c ? I3C_I2C_SPEED_MODE_SS_100_KBPS : I2C_SPEED_STANDARD; // 100 kbit/s
break;
case OMV_I2C_SPEED_FULL:
speed = i2c->is_i3c ? I3C_I2C_SPEED_MODE_FM_400_KBPS : I2C_SPEED_FAST; // 400 kbit/s
break;
case OMV_I2C_SPEED_FAST:
speed = i2c->is_i3c ? I3C_I2C_SPEED_MODE_FMP_1_MBPS : I2C_SPEED_FASTPLUS; // 1000 kbit/s
break;
default:
return -1;
}
switch (bus_id) {
#if defined(OMV_I3C0_ID)
case OMV_I3C0_ID: {
i2c->inst = (I3C_Type *) I3C_BASE;
i2c->scl_pin = OMV_I3C0_SCL_PIN;
i2c->sda_pin = OMV_I3C0_SDA_PIN;
break;
}
#endif
#if defined(OMV_I2C0_ID)
case OMV_I2C0_ID: {
i2c->inst = (I2C_Type *) I2C0_BASE;
i2c->scl_pin = OMV_I2C0_SCL_PIN;
i2c->sda_pin = OMV_I2C0_SDA_PIN;
break;
}
#endif
#if defined(OMV_I2C1_ID)
case OMV_I2C1_ID: {
i2c->inst = (I2C_Type *) I2C1_BASE;
i2c->scl_pin = OMV_I2C1_SCL_PIN;
i2c->sda_pin = OMV_I2C1_SDA_PIN;
break;
}
#endif
#if defined(OMV_I2C2_ID)
case OMV_I2C2_ID: {
i2c->inst = (I2C_Type *) I2C2_BASE;
i2c->scl_pin = OMV_I2C2_SCL_PIN;
i2c->sda_pin = OMV_I2C2_SDA_PIN;
break;
}
#endif
#if defined(OMV_I2C3_ID)
case OMV_I2C3_ID: {
i2c->inst = (I2C_Type *) I2C3_BASE;
i2c->scl_pin = OMV_I2C3_SCL_PIN;
i2c->sda_pin = OMV_I2C3_SDA_PIN;
break;
}
#endif
default:
return -1;
}
alif_hal_i2c_init(bus_id);
if (i2c->is_i3c) {
// I3C controller in I2C/legacy mode.
I3C_Type *inst = i2c->inst;
// Core soft-reset.
inst->I3C_RESET_CTRL = 0x1;
while (inst->I3C_RESET_CTRL & 0x1) {
}
// Initialize I2C controller
i3c_master_init(inst);
// Configure clock and speed.
i2c_clk_cfg(inst, SystemAPBClock, speed);
} else {
// I2C controller.
I2C_Type *inst = i2c->inst;
// Disable I2C.
i2c_disable(inst);
// Initialize I2C controller
inst->I2C_CON = I2C_IC_CON_ENABLE_MASTER_MODE |
I2C_IC_CON_MASTER_RESTART_EN |
I2C_IC_CON_MASTER_TX_EMPTY_CTRL |
I2C_CON_SPEED(speed);
// Set TX/RX FIFO threshold (i2c must be disabled)
inst->I2C_TX_TL = I2C_TX_FIFO_LEN;
inst->I2C_RX_TL = I2C_RX_FIFO_LEN;
// Configure clock.
i2c_master_set_clock(inst, SystemAPBClock / 1000, speed);
// Re-enable I2C.
i2c_enable(inst);
}
i2c->initialized = true;
return 0;
}
int omv_i2c_deinit(omv_i2c_t *i2c) {
if (i2c->initialized) {
// TODO
i2c->initialized = false;
}
return 0;
}
int omv_i2c_scan(omv_i2c_t *i2c, uint8_t *list, uint8_t size) {
uint32_t idx = 0;
for (uint8_t addr = 0x29, data = 0; addr < 0x78; addr++) {
i2c_transfer_t xfer = {
.data = &data,
.size = 1,
.flags = 0,
.direction = I2C_TRANSFER_READ,
.address = addr
};
if (omv_ixc_transfer_timeout(i2c, &xfer, I2C_SCAN_TIMEOUT) == 0) {
if (list == NULL || size == 0) {
idx = (addr << 1);
break;
} else if (idx < size) {
list[idx++] = (addr << 1);
} else {
break;
}
}
mp_event_handle_nowait();
}
return idx;
}
int omv_i2c_enable(omv_i2c_t *i2c, bool enable) {
if (i2c->is_i3c) {
//TODO: For I3C this causes a lockup.
I3C_Type *inst = i2c->inst;
if (enable) {
inst->I3C_DEVICE_CTRL = inst->I3C_DEVICE_CTRL & ~DEV_CTRL_ENABLE;
while (inst->I3C_DEVICE_CTRL & DEV_CTRL_ENABLE) {
}
} else {
inst->I3C_DEVICE_CTRL = inst->I3C_DEVICE_CTRL | DEV_CTRL_ENABLE;
while (!(inst->I3C_DEVICE_CTRL & DEV_CTRL_ENABLE)) {
}
i3c_resume(inst);
}
} else {
I2C_Type *inst = i2c->inst;
if (enable) {
i2c_enable(inst);
} else {
i2c_disable(inst);
}
}
return 0;
}
int omv_i2c_gencall(omv_i2c_t *i2c, uint8_t cmd) {
int ret = 0;
ret |= omv_i2c_write_bytes(i2c, 0, &cmd, 1, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_readb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t *reg_data) {
int ret = 0;
ret |= omv_i2c_write_bytes(i2c, slv_addr, &reg_addr, 1, OMV_I2C_XFER_NO_FLAGS);
ret |= omv_i2c_read_bytes(i2c, slv_addr, reg_data, 1, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_writeb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t reg_data) {
int ret = 0;
uint8_t buf[] = {reg_addr, reg_data};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_readb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t *reg_data) {
int ret = 0;
uint8_t buf[] = {(reg_addr >> 8), reg_addr};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP);
ret |= omv_i2c_read_bytes(i2c, slv_addr, reg_data, 1, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_writeb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t reg_data) {
int ret = 0;
uint8_t buf[] = {(reg_addr >> 8), reg_addr, reg_data};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 3, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_readw(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t *reg_data) {
int ret = 0;
ret |= omv_i2c_write_bytes(i2c, slv_addr, &reg_addr, 1, OMV_I2C_XFER_NO_STOP);
ret |= omv_i2c_read_bytes(i2c, slv_addr, (uint8_t *) reg_data, 2, OMV_I2C_XFER_NO_FLAGS);
*reg_data = (*reg_data << 8) | (*reg_data >> 8);
return ret;
}
int omv_i2c_writew(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t reg_data) {
int ret = 0;
uint8_t buf[] = {reg_addr, (reg_data >> 8), reg_data};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 3, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
int omv_i2c_readw2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t *reg_data) {
int ret = 0;
uint8_t buf[] = {(reg_addr >> 8), reg_addr};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP);
ret |= omv_i2c_read_bytes(i2c, slv_addr, (uint8_t *) reg_data, 2, OMV_I2C_XFER_NO_FLAGS);
*reg_data = (*reg_data << 8) | (*reg_data >> 8);
return ret;
}
int omv_i2c_writew2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t reg_data) {
int ret = 0;
uint8_t buf[] = {(reg_addr >> 8), reg_addr, (reg_data >> 8), reg_data};
ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 4, OMV_I2C_XFER_NO_FLAGS);
return ret;
}
static int i2c_poll_flags(I2C_Type *base, uint32_t flags, uint32_t timeout) {
mp_uint_t tick_start = mp_hal_ticks_ms();
while (!(base->I2C_STATUS & flags)) {
I2C_CHECK_ERRORS(base);
if ((mp_hal_ticks_ms() - tick_start) >= timeout) {
// Should not raise exception as we're not always in nlr context.
return -1;
}
mp_event_handle_nowait();
}
return 0;
}
static int omv_i2c_transfer_timeout(omv_i2c_t *i2c, i2c_transfer_t *xfer, uint32_t timeout) {
I2C_Type *base = i2c->inst;
i2c_clear_all_interrupt(base);
i2c_set_target_addr(base, xfer->address, I2C_7BIT_ADDRESS, 0);
// Write buffered transfer (if any) first.
for (size_t cw_idx = 0; cw_idx < i2c->cw_size;) {
// Write data to FIFO
if (base->I2C_STATUS & I2C_IC_STATUS_TFNF) {
base->I2C_DATA_CMD = (uint16_t) i2c->cw_buf[cw_idx++];
I2C_CHECK_ERRORS(base);
}
}
size_t tx_size = (xfer->direction == I2C_TRANSFER_WRITE) ? xfer->size : 0;
for (size_t tx_idx = 0; tx_idx < tx_size; ) {
// Write data to FIFO
if (base->I2C_STATUS & I2C_IC_STATUS_TFNF) {
base->I2C_DATA_CMD = (uint16_t) xfer->data[tx_idx++];
I2C_CHECK_ERRORS(base);
}
// Wait for TX FIFO empty
if (tx_idx == tx_size && i2c_poll_flags(base, I2C_IC_STATUS_TFE, 10) != 0) {
return -1;
}
}
size_t rx_size = (xfer->direction == I2C_TRANSFER_READ) ? xfer->size : 0;
for (size_t tx_idx = 0, rx_idx = 0; rx_idx < rx_size; ) {
// Write command to FIFO
if ((base->I2C_STATUS & I2C_IC_STATUS_TFNF) &&
!(base->I2C_STATUS & I2C_IC_STATUS_RFNE) && tx_idx++ < rx_size) {
base->I2C_DATA_CMD = I2C_IC_DATA_CMD_READ_REQ;
I2C_CHECK_ERRORS(base);
}
// Wait for RX FIFO not empty
if (i2c_poll_flags(base, I2C_IC_STATUS_RFNE, timeout) != 0) {
return -1;
}
// Read data from FIFO
while ((base->I2C_STATUS & I2C_IC_STATUS_RFNE) && rx_idx < rx_size) {
xfer->data[rx_idx++] = base->I2C_DATA_CMD & 0xFF;
}
}
return 0;
}
static int omv_i3c_transfer_timeout(omv_i2c_t *i2c, i2c_transfer_t *xfer, uint32_t timeout) {
int ret = 0;
I3C_XFER i3c_xfer = {0};
I3C_Type *base = i2c->inst;
i3c_add_slv_to_dat(base, I2C_DAT_INDEX, 0, xfer->address);
if (xfer->direction == I2C_TRANSFER_READ) {
i3c_xfer.rx_buf = xfer->data;
i3c_xfer.rx_len = xfer->size;
i3c_master_rx(base, &i3c_xfer, I2C_DAT_INDEX, i3c_xfer.rx_len);
} else {
i3c_xfer.tx_buf = xfer->data;
i3c_xfer.tx_len = xfer->size;
i3c_master_tx(base, &i3c_xfer, I2C_DAT_INDEX, i3c_xfer.tx_len);
}
// Wait for the transfer to finish.
mp_uint_t tick_start = mp_hal_ticks_ms();
while (base->I3C_INTR_STATUS == 0) {
if ((mp_hal_ticks_ms() - tick_start) >= timeout) {
// Should not raise exception as we're not always in nlr context.
ret = -1;
goto cleanup;
}
mp_event_handle_nowait();
}
uint32_t status = base->I3C_INTR_STATUS;
// See Table 15-81 Response Data Structure
uint32_t resp = base->I3C_RESPONSE_QUEUE_PORT;
if ((status & INTR_TRANSFER_ERR_STAT) || RESPONSE_PORT_ERR_STATUS(resp)) {
ret = -1;
goto cleanup;
}
if (xfer->direction == I2C_TRANSFER_READ && xfer->data) {
for (uint32_t i = 0, dr = 0; i < xfer->size; i++, dr >>= 8) {
if ((i % 4) == 0) {
dr = base->I3C_RX_DATA_PORT;
}
xfer->data[i] = dr & 0xFF;
}
}
cleanup:
i3c_clear_xfer_error(base);
i3c_resume(base);
return ret;
}
static int omv_ixc_transfer_timeout(omv_i2c_t *i2c, i2c_transfer_t *xfer, uint32_t timeout) {
int ret = 0;
if (i2c->is_i3c) {
ret = omv_i3c_transfer_timeout(i2c, xfer, timeout);
} else {
uint32_t xfer_cont = (OMV_I2C_XFER_NO_STOP | OMV_I2C_XFER_SUSPEND);
if (xfer->direction == I2C_TRANSFER_WRITE && (xfer->flags & xfer_cont)) {
if (xfer->size > sizeof(i2c->cw_buf)) {
return -1;
}
i2c->cw_size = xfer->size;
memcpy(i2c->cw_buf, xfer->data, xfer->size);
} else {
ret = omv_i2c_transfer_timeout(i2c, xfer, timeout);
i2c->cw_size = 0;
}
}
return ret;
}
int omv_i2c_read_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags) {
i2c_transfer_t xfer = {
.data = buf,
.size = len,
.flags = flags,
.direction = I2C_TRANSFER_READ,
.address = (slv_addr >> 1)
};
return omv_ixc_transfer_timeout(i2c, &xfer, I2C_XFER_TIMEOUT);
}
int omv_i2c_write_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags) {
i2c_transfer_t xfer = {
.data = buf,
.size = len,
.flags = flags,
.direction = I2C_TRANSFER_WRITE,
.address = (slv_addr >> 1)
};
return omv_ixc_transfer_timeout(i2c, &xfer, I2C_XFER_TIMEOUT);
}
int omv_i2c_pulse_scl(omv_i2c_t *i2c) {
if (i2c->initialized && i2c->scl_pin) {
omv_i2c_deinit(i2c);
omv_gpio_config(i2c->scl_pin, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1);
// Pulse SCL to recover stuck device.
for (int i = 0; i < 1000; i++) {
omv_gpio_write(i2c->scl_pin, 1);
mp_hal_delay_us(10);
omv_gpio_write(i2c->scl_pin, 0);
mp_hal_delay_us(10);
}
omv_i2c_init(i2c, i2c->id, i2c->speed);
}
return 0;
}