605 lines
15 KiB
C
605 lines
15 KiB
C
/*
|
|
* Copyright (c) 2006-2024, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2024-02-14 ShichengChu first version
|
|
*/
|
|
#include "drv_hw_i2c.h"
|
|
#include <rtdevice.h>
|
|
#include <board.h>
|
|
#include "drv_pinmux.h"
|
|
|
|
#define DBG_TAG "drv.i2c"
|
|
#define DBG_LVL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
struct dw_iic_bus
|
|
{
|
|
struct rt_i2c_bus_device parent;
|
|
dw_iic_regs_t *iic_base;
|
|
rt_uint32_t irq;
|
|
char *device_name;
|
|
};
|
|
|
|
static struct dw_iic_bus _i2c_obj[] =
|
|
{
|
|
#ifdef BSP_USING_I2C0
|
|
{
|
|
.iic_base = (dw_iic_regs_t *)I2C0_BASE,
|
|
.device_name = "i2c0",
|
|
.irq = I2C0_IRQ,
|
|
},
|
|
#endif /* BSP_USING_I2C0 */
|
|
#ifdef BSP_USING_I2C1
|
|
{
|
|
.iic_base = (dw_iic_regs_t *)I2C1_BASE,
|
|
.device_name = "i2c1",
|
|
.irq = I2C1_IRQ,
|
|
},
|
|
#endif /* BSP_USING_I2C1 */
|
|
#ifdef BSP_USING_I2C2
|
|
{
|
|
.iic_base = (dw_iic_regs_t *)I2C2_BASE,
|
|
.device_name = "i2c2",
|
|
.irq = I2C2_IRQ,
|
|
},
|
|
#endif /* BSP_USING_I2C2 */
|
|
#ifdef BSP_USING_I2C3
|
|
{
|
|
.iic_base = (dw_iic_regs_t *)I2C3_BASE,
|
|
.device_name = "i2c3",
|
|
.irq = I2C3_IRQ,
|
|
},
|
|
#endif /* BSP_USING_I2C3 */
|
|
#ifdef BSP_USING_I2C4
|
|
{
|
|
.iic_base = (dw_iic_regs_t *)I2C4_BASE,
|
|
.device_name = "i2c4",
|
|
.irq = I2C4_IRQ,
|
|
},
|
|
#endif /* BSP_USING_I2C4 */
|
|
};
|
|
|
|
static rt_uint32_t dw_iic_wait_for_bb(dw_iic_regs_t *iic_base)
|
|
{
|
|
uint16_t timeout = 0;
|
|
while ((iic_base->IC_STATUS & DW_IIC_MST_ACTIVITY_STATE) || !(iic_base->IC_STATUS & DW_IIC_TXFIFO_EMPTY_STATE))
|
|
{
|
|
/* Evaluate timeout */
|
|
rt_hw_us_delay(5);
|
|
timeout ++;
|
|
if (timeout > 200)
|
|
{
|
|
/* exceed 1 ms */
|
|
LOG_E("Timed out waiting for bus busy");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dw_iic_set_reg_address(dw_iic_regs_t *iic_base, rt_uint32_t addr, uint8_t addr_len)
|
|
{
|
|
while (addr_len)
|
|
{
|
|
addr_len --;
|
|
/* high byte address going out first */
|
|
dw_iic_transmit_data(iic_base, (addr >> (addr_len * 8)) & 0xff);
|
|
}
|
|
}
|
|
|
|
static void dw_iic_set_target_address(dw_iic_regs_t *iic_base, rt_uint32_t address)
|
|
{
|
|
rt_uint32_t i2c_status;
|
|
i2c_status = dw_iic_get_iic_status(iic_base);
|
|
dw_iic_disable(iic_base);
|
|
iic_base->IC_TAR = (iic_base->IC_TAR & ~0x3ff) | address; /* this register can be written only when the I2C is disabled*/
|
|
|
|
if (i2c_status == DW_IIC_EN)
|
|
{
|
|
dw_iic_enable(iic_base);
|
|
}
|
|
}
|
|
|
|
static int dw_iic_xfer_init(dw_iic_regs_t *iic_base, rt_uint32_t dev_addr)
|
|
{
|
|
if (dw_iic_wait_for_bb(iic_base))
|
|
return -RT_ERROR;
|
|
|
|
dw_iic_set_target_address(iic_base, dev_addr);
|
|
dw_iic_enable(iic_base);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static int dw_iic_xfer_finish(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t timeout = 0;
|
|
|
|
while (1)
|
|
{
|
|
if (iic_base->IC_RAW_INTR_STAT & DW_IIC_RAW_STOP_DET)
|
|
{
|
|
iic_base->IC_CLR_STOP_DET;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
timeout ++;
|
|
rt_hw_us_delay(5);
|
|
if (timeout > 10000)
|
|
{
|
|
LOG_E("xfer finish tiemout");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dw_iic_wait_for_bb(iic_base))
|
|
{
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
dw_iic_flush_rxfifo(iic_base);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static void dw_iic_set_slave_mode(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t i2c_status;
|
|
i2c_status = dw_iic_get_iic_status(iic_base);
|
|
dw_iic_disable(iic_base);
|
|
rt_uint32_t val = DW_IIC_CON_MASTER_EN | DW_IIC_CON_SLAVE_EN;
|
|
iic_base->IC_CON &= ~val; ///< set 0 to disabled master mode; set 0 to enabled slave mode
|
|
|
|
if (i2c_status == DW_IIC_EN)
|
|
{
|
|
dw_iic_enable(iic_base);
|
|
}
|
|
}
|
|
|
|
static void dw_iic_set_master_mode(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t i2c_status;
|
|
i2c_status = dw_iic_get_iic_status(iic_base);
|
|
dw_iic_disable(iic_base);
|
|
rt_uint32_t val = DW_IIC_CON_MASTER_EN | DW_IIC_CON_SLAVE_EN; ///< set 1 to enabled master mode; set 1 to disabled slave mode
|
|
iic_base->IC_CON |= val;
|
|
|
|
if (i2c_status == DW_IIC_EN)
|
|
{
|
|
dw_iic_enable(iic_base);
|
|
}
|
|
}
|
|
|
|
static rt_err_t dw_iic_recv(dw_iic_regs_t *iic_base, rt_uint32_t devaddr, rt_uint8_t *data, rt_uint32_t size, rt_uint32_t timeout)
|
|
{
|
|
rt_err_t ret = RT_EOK;
|
|
rt_uint32_t timecount = 0;
|
|
|
|
RT_ASSERT(data != RT_NULL);
|
|
|
|
if (dw_iic_xfer_init(iic_base, devaddr))
|
|
{
|
|
ret = -RT_EIO;
|
|
goto ERR_EXIT;
|
|
}
|
|
|
|
timecount = timeout + rt_tick_get_millisecond();
|
|
|
|
for (int i = 0 ; i < size; i ++)
|
|
{
|
|
if(i != (size - 1))
|
|
{
|
|
dw_iic_transmit_data(iic_base, DW_IIC_DATA_CMD);
|
|
}
|
|
else
|
|
{
|
|
dw_iic_transmit_data(iic_base, DW_IIC_DATA_CMD | DW_IIC_DATA_STOP);
|
|
}
|
|
}
|
|
|
|
while (size > 0)
|
|
{
|
|
if (iic_base->IC_STATUS & DW_IIC_RXFIFO_NOT_EMPTY_STATE)
|
|
{
|
|
*data ++ = dw_iic_receive_data(iic_base);
|
|
-- size;
|
|
}
|
|
else if (rt_tick_get_millisecond() >= timecount)
|
|
{
|
|
LOG_E("Timed out read ic_cmd_data");
|
|
ret = -RT_ETIMEOUT;
|
|
goto ERR_EXIT;
|
|
}
|
|
}
|
|
|
|
if (dw_iic_xfer_finish(iic_base))
|
|
{
|
|
ret = -RT_EIO;
|
|
goto ERR_EXIT;
|
|
}
|
|
|
|
ERR_EXIT:
|
|
dw_iic_disable(iic_base);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static rt_err_t dw_iic_send(dw_iic_regs_t *iic_base, rt_uint32_t devaddr, const uint8_t *data, rt_uint32_t size, rt_uint32_t timeout)
|
|
{
|
|
rt_err_t ret = RT_EOK;
|
|
rt_uint32_t timecount;
|
|
|
|
RT_ASSERT(data != RT_NULL);
|
|
|
|
if (dw_iic_xfer_init(iic_base, devaddr))
|
|
{
|
|
ret = -RT_EIO;
|
|
goto ERR_EXIT;
|
|
}
|
|
|
|
timecount = timeout + rt_tick_get_millisecond();
|
|
|
|
while (size > 0)
|
|
{
|
|
if (iic_base->IC_STATUS & DW_IIC_TXFIFO_NOT_FULL_STATE)
|
|
{
|
|
if (-- size == 0)
|
|
{
|
|
dw_iic_transmit_data(iic_base, *data ++ | DW_IIC_DATA_STOP);
|
|
}
|
|
else
|
|
{
|
|
dw_iic_transmit_data(iic_base, *data ++);
|
|
}
|
|
}
|
|
else if (rt_tick_get_millisecond() >= timecount)
|
|
{
|
|
LOG_D("ic status is not TFNF\n");
|
|
ret = -RT_ETIMEOUT;
|
|
goto ERR_EXIT;
|
|
}
|
|
}
|
|
|
|
LOG_D("dw_iic_xfer_finish");
|
|
|
|
if (dw_iic_xfer_finish(iic_base))
|
|
{
|
|
ret = -RT_EIO;
|
|
goto ERR_EXIT;
|
|
}
|
|
|
|
ERR_EXIT:
|
|
dw_iic_disable(iic_base);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static rt_ssize_t dw_iic_master_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num)
|
|
{
|
|
struct rt_i2c_msg *msg;
|
|
rt_uint32_t i;
|
|
rt_ssize_t ret = -RT_ERROR;
|
|
rt_uint32_t timeout;
|
|
|
|
struct dw_iic_bus *i2c_bus = (struct dw_iic_bus *)bus;
|
|
dw_iic_regs_t *iic_base = i2c_bus->iic_base;
|
|
|
|
for (i = 0; i < num; i++)
|
|
{
|
|
msg = &msgs[i];
|
|
|
|
if (msg->flags & RT_I2C_ADDR_10BIT)
|
|
{
|
|
dw_iic_set_master_10bit_addr_mode(iic_base);
|
|
dw_iic_set_slave_10bit_addr_mode(iic_base);
|
|
}
|
|
else
|
|
{
|
|
dw_iic_set_master_7bit_addr_mode(iic_base);
|
|
dw_iic_set_slave_7bit_addr_mode(iic_base);
|
|
}
|
|
|
|
if (msg->flags & RT_I2C_RD)
|
|
{
|
|
timeout = 1000;
|
|
ret = dw_iic_recv(iic_base, msg->addr, msg->buf, msg->len, timeout);
|
|
if (ret != RT_EOK)
|
|
LOG_E("dw_iic_recv error: %d", ret);
|
|
}
|
|
else
|
|
{
|
|
timeout = 100;
|
|
ret = dw_iic_send(iic_base, msg->addr, msg->buf, msg->len, timeout);
|
|
if (ret != RT_EOK)
|
|
LOG_E("dw_iic_send error: %d", ret);
|
|
}
|
|
}
|
|
|
|
return ret == RT_EOK ? num : ret;
|
|
}
|
|
|
|
static void dw_iic_set_transfer_speed_high(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t speed_config = iic_base->IC_CON;
|
|
speed_config &= ~(DW_IIC_CON_SPEEDL_EN | DW_IIC_CON_SPEEDH_EN);
|
|
speed_config |= DW_IIC_CON_SPEEDL_EN | DW_IIC_CON_SPEEDH_EN;
|
|
iic_base->IC_CON = speed_config;
|
|
}
|
|
|
|
static void dw_iic_set_transfer_speed_fast(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t speed_config = iic_base->IC_CON;
|
|
speed_config &= ~(DW_IIC_CON_SPEEDL_EN | DW_IIC_CON_SPEEDH_EN);
|
|
speed_config |= DW_IIC_CON_SPEEDH_EN;
|
|
iic_base->IC_CON = speed_config;
|
|
}
|
|
|
|
static void dw_iic_set_transfer_speed_standard(dw_iic_regs_t *iic_base)
|
|
{
|
|
rt_uint32_t speed_config = iic_base->IC_CON;
|
|
speed_config &= ~(DW_IIC_CON_SPEEDL_EN | DW_IIC_CON_SPEEDH_EN);
|
|
speed_config |= DW_IIC_CON_SPEEDL_EN;
|
|
iic_base->IC_CON = speed_config;
|
|
}
|
|
|
|
static rt_err_t dw_iic_bus_control(struct rt_i2c_bus_device *bus, int cmd, void *args)
|
|
{
|
|
struct dw_iic_bus *i2c_bus = (struct dw_iic_bus *)bus;
|
|
|
|
RT_ASSERT(bus != RT_NULL);
|
|
|
|
dw_iic_regs_t *iic_base = i2c_bus->iic_base;
|
|
|
|
switch (cmd)
|
|
{
|
|
case RT_I2C_DEV_CTRL_CLK:
|
|
{
|
|
rt_uint32_t speed = *(rt_uint32_t *)args;
|
|
if (speed == 100 * 1000)
|
|
{
|
|
dw_iic_set_transfer_speed_standard(iic_base);
|
|
dw_iic_set_standard_scl_hcnt(iic_base, (((IC_CLK * 4000U) / 1000U) - 7U));
|
|
dw_iic_set_standard_scl_lcnt(iic_base, (((IC_CLK * 4700) / 1000U) - 1U));
|
|
}
|
|
else if (speed == 400 * 1000)
|
|
{
|
|
dw_iic_set_transfer_speed_fast(iic_base);
|
|
dw_iic_set_fast_scl_hcnt(iic_base, (((IC_CLK * 600U) / 1000U) - 7U));
|
|
dw_iic_set_fast_scl_lcnt(iic_base, (((IC_CLK * 1300U) / 1000U) - 1U));
|
|
}
|
|
else if (speed == 4 * 1000 * 1000)
|
|
{
|
|
dw_iic_set_transfer_speed_high(iic_base);
|
|
dw_iic_set_high_scl_hcnt(iic_base, 6U);
|
|
dw_iic_set_high_scl_lcnt(iic_base, 8U);
|
|
}
|
|
else
|
|
{
|
|
return -RT_EIO;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RT_I2C_DEV_CTRL_10BIT:
|
|
dw_iic_set_master_10bit_addr_mode(iic_base);
|
|
dw_iic_set_slave_10bit_addr_mode(iic_base);
|
|
break;
|
|
|
|
default:
|
|
return -RT_EIO;
|
|
break;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static const struct rt_i2c_bus_device_ops i2c_ops =
|
|
{
|
|
.master_xfer = dw_iic_master_xfer,
|
|
.slave_xfer = RT_NULL,
|
|
.i2c_bus_control = dw_iic_bus_control,
|
|
};
|
|
|
|
static void dw_iic_init(dw_iic_regs_t *iic_base)
|
|
{
|
|
dw_iic_disable(iic_base);
|
|
dw_iic_clear_all_irq(iic_base);
|
|
dw_iic_disable_all_irq(iic_base);
|
|
|
|
iic_base->IC_SAR = 0;
|
|
|
|
dw_iic_set_receive_fifo_threshold(iic_base, 0x1);
|
|
dw_iic_set_transmit_fifo_threshold(iic_base, 0x0);
|
|
dw_iic_set_sda_hold_time(iic_base, 0x1e);
|
|
|
|
dw_iic_set_master_mode(iic_base);
|
|
dw_iic_enable_restart(iic_base);
|
|
|
|
dw_iic_set_transfer_speed_standard(iic_base);
|
|
dw_iic_set_standard_scl_hcnt(iic_base, (((IC_CLK * 4000U) / 1000U) - 7U));
|
|
dw_iic_set_standard_scl_lcnt(iic_base, (((IC_CLK * 4700) / 1000U) - 1U));
|
|
}
|
|
|
|
#if defined(BOARD_TYPE_MILKV_DUO) || defined(BOARD_TYPE_MILKV_DUO_SPINOR)
|
|
|
|
#ifdef BSP_USING_I2C0
|
|
static const char *pinname_whitelist_i2c0_scl[] = {
|
|
"IIC0_SCL",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c0_sda[] = {
|
|
"IIC0_SDA",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C1
|
|
static const char *pinname_whitelist_i2c1_scl[] = {
|
|
"SD1_D2",
|
|
"SD1_D3",
|
|
"PAD_MIPIRX0N",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c1_sda[] = {
|
|
"SD1_D1",
|
|
"SD1_D0",
|
|
"PAD_MIPIRX1P",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C2
|
|
// I2C2 is not ALLOWED for Duo
|
|
static const char *pinname_whitelist_i2c2_scl[] = {
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c2_sda[] = {
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C3
|
|
static const char *pinname_whitelist_i2c3_scl[] = {
|
|
"SD1_CMD",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c3_sda[] = {
|
|
"SD1_CLK",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C4
|
|
// I2C4 is not ALLOWED for Duo
|
|
static const char *pinname_whitelist_i2c4_scl[] = {
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c4_sda[] = {
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#elif defined(BOARD_TYPE_MILKV_DUO256M) || defined(BOARD_TYPE_MILKV_DUO256M_SPINOR)
|
|
|
|
#ifdef BSP_USING_I2C0
|
|
// I2C0 is not ALLOWED for Duo256
|
|
static const char *pinname_whitelist_i2c0_scl[] = {
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c0_sda[] = {
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C1
|
|
static const char *pinname_whitelist_i2c1_scl[] = {
|
|
"SD1_D2",
|
|
"SD1_D3",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c1_sda[] = {
|
|
"SD1_D1",
|
|
"SD1_D0",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C2
|
|
static const char *pinname_whitelist_i2c2_scl[] = {
|
|
"PAD_MIPI_TXP1",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c2_sda[] = {
|
|
"PAD_MIPI_TXM1",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C3
|
|
static const char *pinname_whitelist_i2c3_scl[] = {
|
|
"SD1_CMD",
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c3_sda[] = {
|
|
"SD1_CLK",
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#ifdef BSP_USING_I2C4
|
|
// I2C4 is not ALLOWED for Duo256
|
|
static const char *pinname_whitelist_i2c4_scl[] = {
|
|
NULL,
|
|
};
|
|
static const char *pinname_whitelist_i2c4_sda[] = {
|
|
NULL,
|
|
};
|
|
#endif
|
|
|
|
#else
|
|
#error "Unsupported board type!"
|
|
#endif
|
|
|
|
static void rt_hw_i2c_pinmux_config()
|
|
{
|
|
#ifdef BSP_USING_I2C0
|
|
pinmux_config(BSP_I2C0_SCL_PINNAME, IIC0_SCL, pinname_whitelist_i2c0_scl);
|
|
pinmux_config(BSP_I2C0_SDA_PINNAME, IIC0_SDA, pinname_whitelist_i2c0_sda);
|
|
#endif /* BSP_USING_I2C0 */
|
|
|
|
#ifdef BSP_USING_I2C1
|
|
pinmux_config(BSP_I2C1_SCL_PINNAME, IIC1_SCL, pinname_whitelist_i2c1_scl);
|
|
pinmux_config(BSP_I2C1_SDA_PINNAME, IIC1_SDA, pinname_whitelist_i2c1_sda);
|
|
#endif /* BSP_USING_I2C1 */
|
|
|
|
#ifdef BSP_USING_I2C2
|
|
pinmux_config(BSP_I2C2_SCL_PINNAME, IIC2_SCL, pinname_whitelist_i2c2_scl);
|
|
pinmux_config(BSP_I2C2_SDA_PINNAME, IIC2_SDA, pinname_whitelist_i2c2_sda);
|
|
#endif /* BSP_USING_I2C2 */
|
|
|
|
#ifdef BSP_USING_I2C3
|
|
pinmux_config(BSP_I2C3_SCL_PINNAME, IIC3_SCL, pinname_whitelist_i2c3_scl);
|
|
pinmux_config(BSP_I2C3_SDA_PINNAME, IIC3_SDA, pinname_whitelist_i2c3_sda);
|
|
#endif /* BSP_USING_I2C3 */
|
|
|
|
#ifdef BSP_USING_I2C4
|
|
pinmux_config(BSP_I2C4_SCL_PINNAME, IIC4_SCL, pinname_whitelist_i2c4_scl);
|
|
pinmux_config(BSP_I2C4_SDA_PINNAME, IIC4_SDA, pinname_whitelist_i2c4_sda);
|
|
#endif /* BSP_USING_I2C4 */
|
|
}
|
|
|
|
int rt_hw_i2c_init(void)
|
|
{
|
|
int result = RT_EOK;
|
|
|
|
rt_hw_i2c_pinmux_config();
|
|
|
|
for (rt_size_t i = 0; i < sizeof(_i2c_obj) / sizeof(struct dw_iic_bus); i++)
|
|
{
|
|
dw_iic_init(_i2c_obj->iic_base);
|
|
|
|
_i2c_obj[i].parent.ops = &i2c_ops;
|
|
|
|
/* register i2c device */
|
|
if (rt_i2c_bus_device_register(&_i2c_obj[i].parent, _i2c_obj[i].device_name) == RT_EOK)
|
|
{
|
|
LOG_D("%s init success", _i2c_obj[i].device_name);
|
|
}
|
|
else
|
|
{
|
|
LOG_E("%s register failed", _i2c_obj[i].device_name);
|
|
result = -RT_ERROR;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
INIT_DEVICE_EXPORT(rt_hw_i2c_init);
|