mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-03 09:44:47 +00:00
Add initial support for i2c on Renesas RX MCU This driver is controlling the RIIC HW of RX MCU for i2c bus interface on Zephyr Only master mode is supported Signed-off-by: Duy Nguyen <duy.nguyen.xa@renesas.com>
393 lines
13 KiB
C
393 lines
13 KiB
C
/*
|
|
* Copyright (c) 2025 Renesas Electronics Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT renesas_rx_i2c
|
|
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <errno.h>
|
|
#include <r_riic_rx_if.h>
|
|
#include <r_riic_rx_private.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <soc.h>
|
|
|
|
LOG_MODULE_REGISTER(i2c_renesas_rx, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
struct i2c_rx_config {
|
|
const struct pinctrl_dev_config *pcfg;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
void (*riic_eei_sub)(void);
|
|
void (*riic_txi_sub)(void);
|
|
void (*riic_rxi_sub)(void);
|
|
void (*riic_tei_sub)(void);
|
|
};
|
|
|
|
struct i2c_rx_data {
|
|
riic_info_t rdp_info;
|
|
struct k_sem bus_lock;
|
|
struct k_sem bus_sync;
|
|
uint32_t dev_config;
|
|
i2c_callback_t user_callback;
|
|
void *user_data;
|
|
bool skip_callback;
|
|
};
|
|
|
|
static void riic_eei_isr(const struct device *dev)
|
|
{
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
const struct i2c_rx_config *config = dev->config;
|
|
|
|
riic_return_t rdp_ret;
|
|
riic_info_t iic_info_m;
|
|
riic_mcu_status_t iic_status;
|
|
|
|
iic_info_m.ch_no = data->rdp_info.ch_no;
|
|
rdp_ret = R_RIIC_GetStatus(&iic_info_m, &iic_status);
|
|
if (rdp_ret == RIIC_SUCCESS) {
|
|
if (iic_status.BIT.SP) {
|
|
k_sem_give(&data->bus_sync);
|
|
if ((data->user_callback != NULL) && !data->skip_callback) {
|
|
data->user_callback(dev, 0, data->user_data);
|
|
}
|
|
}
|
|
if (iic_status.BIT.TMO) {
|
|
k_sem_give(&data->bus_sync);
|
|
if ((data->user_callback != NULL) && !data->skip_callback) {
|
|
data->user_callback(dev, -ETIME, data->user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
config->riic_eei_sub();
|
|
}
|
|
static void riic_rxi_isr(const struct device *dev)
|
|
{
|
|
const struct i2c_rx_config *config = dev->config;
|
|
|
|
config->riic_rxi_sub();
|
|
}
|
|
|
|
static void riic_txi_isr(const struct device *dev)
|
|
{
|
|
const struct i2c_rx_config *config = dev->config;
|
|
|
|
config->riic_txi_sub();
|
|
}
|
|
|
|
static void riic_tei_isr(const struct device *dev)
|
|
{
|
|
const struct i2c_rx_config *config = dev->config;
|
|
|
|
config->riic_tei_sub();
|
|
}
|
|
|
|
static void rdp_callback(void)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
|
|
static void setup_rdp_info(struct i2c_rx_data *data, uint8_t *buf1, size_t len1, uint8_t *buf2,
|
|
size_t len2, uint16_t *addr)
|
|
{
|
|
data->rdp_info.cnt1st = len1;
|
|
data->rdp_info.cnt2nd = len2;
|
|
data->rdp_info.p_data1st = buf1 ? buf1 : FIT_NO_PTR;
|
|
data->rdp_info.p_data2nd = buf2 ? buf2 : FIT_NO_PTR;
|
|
data->rdp_info.p_slv_adr = (uint8_t *)addr;
|
|
}
|
|
|
|
static int run_rx_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
|
|
uint16_t addr, bool async)
|
|
{
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
riic_return_t rdp_ret;
|
|
int ret;
|
|
|
|
ret = k_sem_take(&data->bus_lock, async ? K_NO_WAIT : K_FOREVER);
|
|
if (ret != 0) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
k_sem_reset(&data->bus_sync);
|
|
|
|
if (addr == 0x00) {
|
|
/* Enter transmission pattern 4 */
|
|
LOG_DBG("RDP RX I2C master transmit pattern 4\n");
|
|
setup_rdp_info(data, NULL, 0, NULL, 0, NULL);
|
|
rdp_ret = R_RIIC_MasterSend(&data->rdp_info);
|
|
goto transfer_blocking;
|
|
}
|
|
|
|
if (num_msgs == 1) {
|
|
if (msgs[0].flags & I2C_MSG_READ) {
|
|
/* Enter master reception pattern 1 */
|
|
LOG_DBG("RDP RX I2C master reception pattern 1\n");
|
|
setup_rdp_info(data, NULL, 0, msgs[0].buf, msgs[0].len, &addr);
|
|
rdp_ret = R_RIIC_MasterReceive(&data->rdp_info);
|
|
goto transfer_blocking;
|
|
} else {
|
|
/* Enter master transmission pattern 2/3 */
|
|
LOG_DBG("RDP RX I2C master transmit pattern 2/3\n");
|
|
setup_rdp_info(data, NULL, 0, msgs[0].len ? msgs[0].buf : NULL, msgs[0].len,
|
|
&addr);
|
|
rdp_ret = R_RIIC_MasterSend(&data->rdp_info);
|
|
goto transfer_blocking;
|
|
}
|
|
} else if (num_msgs == 2) {
|
|
if (msgs[0].flags & I2C_MSG_READ) {
|
|
goto unsupport_pattern;
|
|
}
|
|
|
|
if (msgs[1].flags & I2C_MSG_READ) {
|
|
/* Enter master reception pattern 2 */
|
|
LOG_DBG("RDP RX I2C master reception pattern 2\n");
|
|
setup_rdp_info(data, msgs[0].buf, msgs[0].len, msgs[1].buf, msgs[1].len,
|
|
&addr);
|
|
rdp_ret = R_RIIC_MasterReceive(&data->rdp_info);
|
|
goto transfer_blocking;
|
|
} else {
|
|
/* Enter master transmission pattern 1 */
|
|
LOG_DBG("RDP RX I2C master transmit pattern 1\n");
|
|
setup_rdp_info(data, msgs[0].buf, msgs[0].len, msgs[1].buf, msgs[1].len,
|
|
&addr);
|
|
rdp_ret = R_RIIC_MasterSend(&data->rdp_info);
|
|
goto transfer_blocking;
|
|
}
|
|
}
|
|
|
|
unsupport_pattern:
|
|
/* Unsupport pattern, emit each fragment as a distinct transaction */
|
|
LOG_DBG("%s: \"Not a generic pattern ...\" !\n", __func__);
|
|
for (uint8_t i = 0; i < num_msgs; i++) {
|
|
if (msgs[i].flags & I2C_MSG_READ) {
|
|
/* Enter master reception pattern 1 */
|
|
LOG_DBG("RDP RX I2C master reception pattern 1\n");
|
|
setup_rdp_info(data, NULL, 0, msgs[i].buf, msgs[i].len, &addr);
|
|
rdp_ret = R_RIIC_MasterReceive(&data->rdp_info);
|
|
} else {
|
|
/* Enter master transmission pattern 2 */
|
|
LOG_DBG("RDP RX I2C master transmit pattern 2/3\n");
|
|
setup_rdp_info(data, NULL, 0, (msgs[i].len) ? msgs[i].buf : NULL,
|
|
msgs[i].len, &addr);
|
|
rdp_ret = R_RIIC_MasterSend(&data->rdp_info);
|
|
}
|
|
if (i == num_msgs - 1) {
|
|
data->skip_callback = false; /* Invoke callback in the last message */
|
|
}
|
|
if (!rdp_ret) {
|
|
k_sem_take(&data->bus_sync, K_FOREVER);
|
|
k_sem_reset(&data->bus_sync);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
goto transfer_exit;
|
|
|
|
transfer_blocking:
|
|
if (!rdp_ret && !async) {
|
|
data->skip_callback = false; /* Invoke callback */
|
|
k_sem_take(&data->bus_sync, K_FOREVER);
|
|
}
|
|
transfer_exit:
|
|
k_sem_give(&data->bus_lock);
|
|
|
|
if (!rdp_ret) {
|
|
return 0;
|
|
}
|
|
return -EIO;
|
|
}
|
|
|
|
static int i2c_rx_init(const struct device *dev)
|
|
{
|
|
const struct i2c_rx_config *config = dev->config;
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
int ret = 0;
|
|
|
|
/* Setup pin control */
|
|
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret) {
|
|
LOG_ERR("Pin control config failed.");
|
|
return ret;
|
|
}
|
|
|
|
/* Init kernal object */
|
|
k_sem_init(&data->bus_lock, 1, 1);
|
|
k_sem_init(&data->bus_sync, 0, 1);
|
|
|
|
/* Open Device */
|
|
ret = R_RIIC_Open(&data->rdp_info);
|
|
|
|
if (ret) {
|
|
LOG_ERR("Open i2c master failed.");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Connect and enable Interrupts in zephyr */
|
|
config->irq_config_func(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_rx_configure(const struct device *dev, uint32_t dev_config)
|
|
{
|
|
/* Setup bitrate */
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
uint16_t speed; /* bitrate in kbps */
|
|
int ret;
|
|
|
|
/* Validate input */
|
|
if (!(dev_config & I2C_MODE_CONTROLLER)) {
|
|
LOG_ERR("Only I2C Master mode supported.");
|
|
return -ENOTSUP;
|
|
}
|
|
if (dev_config & I2C_ADDR_10_BITS) {
|
|
LOG_ERR("Only address 7 bits supported.");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (I2C_SPEED_GET(dev_config)) {
|
|
case I2C_SPEED_STANDARD:
|
|
speed = 100;
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
speed = 400;
|
|
break;
|
|
default:
|
|
LOG_ERR("Unsupported speed: %d", I2C_SPEED_GET(dev_config));
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
k_sem_take(&data->bus_lock, K_FOREVER);
|
|
|
|
ret = riic_bps_calc(&data->rdp_info, speed);
|
|
|
|
k_sem_give(&data->bus_lock);
|
|
|
|
/* Validate result */
|
|
if (ret) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->dev_config = dev_config;
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_rx_get_config(const struct device *dev, uint32_t *dev_config)
|
|
{
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
|
|
*dev_config = data->dev_config;
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_rx_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
|
|
uint16_t addr)
|
|
{
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
|
|
data->user_callback = NULL;
|
|
data->user_data = NULL;
|
|
data->skip_callback = true;
|
|
|
|
/* Transfer */
|
|
return run_rx_transfer(dev, msgs, num_msgs, addr, false);
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_RENESAS_RX_CALLBACK
|
|
static int i2c_rx_transfer_cb(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
|
|
uint16_t addr, i2c_callback_t cb, void *userdata)
|
|
{
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
|
|
data->user_callback = cb;
|
|
data->user_data = userdata;
|
|
data->skip_callback = true;
|
|
|
|
/* Transfer */
|
|
return run_rx_transfer(dev, msgs, num_msgs, addr, true);
|
|
}
|
|
#endif
|
|
|
|
static int i2c_rx_recover_bus(const struct device *dev)
|
|
{
|
|
LOG_DBG("Recover I2C Bus\n");
|
|
struct i2c_rx_data *data = (struct i2c_rx_data *const)dev->data;
|
|
riic_return_t rdp_ret = RIIC_SUCCESS;
|
|
|
|
k_sem_take(&data->bus_lock, K_FOREVER);
|
|
rdp_ret |= R_RIIC_Control(&data->rdp_info, RIIC_GEN_START_CON);
|
|
rdp_ret |= R_RIIC_Control(&data->rdp_info, RIIC_GEN_STOP_CON);
|
|
k_sem_give(&data->bus_lock);
|
|
|
|
if (rdp_ret != 0) {
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static DEVICE_API(i2c, i2c_rx_driver_api) = {
|
|
.configure = (i2c_api_configure_t)i2c_rx_configure,
|
|
.get_config = (i2c_api_get_config_t)i2c_rx_get_config,
|
|
.transfer = (i2c_api_full_io_t)i2c_rx_transfer,
|
|
#ifdef CONFIG_I2C_RENESAS_RX_CALLBACK
|
|
.transfer_cb = (i2c_api_transfer_cb_t)i2c_rx_transfer_cb,
|
|
#endif
|
|
.recover_bus = (i2c_api_recover_bus_t)i2c_rx_recover_bus,
|
|
#ifdef CONFIG_I2C_RTIO
|
|
.iodev_submit = i2c_iodev_submit_fallback,
|
|
#endif
|
|
};
|
|
|
|
#define I2C_RX_RIIC_INIT(index) \
|
|
\
|
|
PINCTRL_DT_INST_DEFINE(index); \
|
|
\
|
|
static void i2c_rx_irq_config_func##index(const struct device *dev) \
|
|
{ \
|
|
\
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, eei, irq), \
|
|
DT_INST_IRQ_BY_NAME(index, eei, priority), riic_eei_isr, \
|
|
DEVICE_DT_INST_GET(index), 0); \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, rxi, irq), \
|
|
DT_INST_IRQ_BY_NAME(index, rxi, priority), riic_rxi_isr, \
|
|
DEVICE_DT_INST_GET(index), 0); \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, txi, irq), \
|
|
DT_INST_IRQ_BY_NAME(index, txi, priority), riic_txi_isr, \
|
|
DEVICE_DT_INST_GET(index), 0); \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, tei, irq), \
|
|
DT_INST_IRQ_BY_NAME(index, tei, priority), riic_tei_isr, \
|
|
DEVICE_DT_INST_GET(index), 0); \
|
|
\
|
|
irq_enable(DT_INST_IRQ_BY_NAME(index, eei, irq)); \
|
|
irq_enable(DT_INST_IRQ_BY_NAME(index, rxi, irq)); \
|
|
irq_enable(DT_INST_IRQ_BY_NAME(index, txi, irq)); \
|
|
irq_enable(DT_INST_IRQ_BY_NAME(index, tei, irq)); \
|
|
}; \
|
|
\
|
|
static const struct i2c_rx_config i2c_rx_config_##index = { \
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \
|
|
.irq_config_func = i2c_rx_irq_config_func##index, \
|
|
.riic_eei_sub = riic##index##_eei_sub, \
|
|
.riic_rxi_sub = riic##index##_rxi_sub, \
|
|
.riic_txi_sub = riic##index##_txi_sub, \
|
|
.riic_tei_sub = riic##index##_tei_sub, \
|
|
}; \
|
|
\
|
|
static struct i2c_rx_data i2c_rx_data_##index = { \
|
|
.rdp_info = \
|
|
{ \
|
|
.dev_sts = RIIC_NO_INIT, \
|
|
.ch_no = DT_INST_PROP(index, channel), \
|
|
.callbackfunc = rdp_callback, \
|
|
}, \
|
|
}; \
|
|
\
|
|
I2C_DEVICE_DT_INST_DEFINE(index, i2c_rx_init, NULL, &i2c_rx_data_##index, \
|
|
&i2c_rx_config_##index, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \
|
|
&i2c_rx_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_RX_RIIC_INIT)
|