Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

444 lines
10 KiB

/*
* Copyright (c) 2025 Andrei-Edward Popa
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT wch_i2c
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(i2c_wch);
#include <zephyr/drivers/clock_control.h>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/device.h>
#include "i2c-priv.h"
#include <ch32fun.h>
typedef void (*irq_config_func_t)(const struct device *port);
struct i2c_wch_config {
const struct pinctrl_dev_config *pcfg;
irq_config_func_t irq_config_func;
const struct device *clk_dev;
I2C_TypeDef *regs;
uint32_t bitrate;
uint8_t clk_id;
};
struct i2c_wch_data {
struct k_sem xfer_done;
struct {
struct i2c_msg *msg;
uint32_t idx;
union {
struct {
uint16_t addr : 10;
uint16_t err : 6;
};
uint16_t: 16;
};
} current;
};
static void wch_i2c_handle_start_bit(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
if ((data->current.msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
regs->DATAR = ((data->current.addr << 1) & 0xFF);
} else {
regs->DATAR = ((data->current.addr << 1) & 0xFF) | 1;
if (data->current.msg->len == 2U) {
regs->CTLR1 |= I2C_CTLR1_POS;
}
}
}
static void wch_i2c_handle_addr(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
if ((data->current.msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
if (data->current.msg->len <= 2U) {
regs->CTLR1 &= ~I2C_CTLR1_ACK;
if (data->current.msg->len == 2U) {
regs->CTLR1 |= I2C_CTLR1_POS;
}
}
}
regs->STAR1;
regs->STAR2;
}
static void wch_i2c_handle_txe(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
if (data->current.idx < data->current.msg->len) {
regs->DATAR = data->current.msg->buf[data->current.idx++];
if (data->current.idx == data->current.msg->len) {
regs->CTLR2 &= ~I2C_CTLR2_ITBUFEN;
}
} else {
if (data->current.msg->flags & I2C_MSG_STOP) {
regs->CTLR1 |= I2C_CTLR1_STOP;
}
if (regs->STAR1 & I2C_STAR1_BTF) {
regs->DATAR;
}
k_sem_give(&data->xfer_done);
}
}
static void wch_i2c_handle_rxne(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
if (data->current.idx < data->current.msg->len) {
switch (data->current.msg->len - data->current.idx) {
case 1:
if (data->current.msg->flags & I2C_MSG_STOP) {
regs->CTLR1 |= I2C_CTLR1_STOP;
}
regs->CTLR2 &= ~I2C_CTLR2_ITBUFEN;
data->current.msg->buf[data->current.idx++] = regs->DATAR;
k_sem_give(&data->xfer_done);
break;
case 2:
regs->CTLR1 &= ~I2C_CTLR1_ACK;
regs->CTLR1 |= I2C_CTLR1_POS;
__fallthrough;
default:
data->current.msg->buf[data->current.idx++] = regs->DATAR;
}
} else {
if (data->current.msg->flags & I2C_MSG_STOP) {
regs->CTLR1 |= I2C_CTLR1_STOP;
}
k_sem_give(&data->xfer_done);
}
}
static void i2c_wch_event_isr(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
uint16_t status = regs->STAR1;
bool write;
write = ((data->current.msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE);
if (status & I2C_STAR1_SB) {
wch_i2c_handle_start_bit(dev);
} else if (status & I2C_STAR1_ADDR) {
wch_i2c_handle_addr(dev);
} else if ((status & (I2C_STAR1_TXE | I2C_STAR1_BTF)) && write) {
wch_i2c_handle_txe(dev);
} else if ((status & (I2C_STAR1_RXNE | I2C_STAR1_BTF)) && (!write)) {
wch_i2c_handle_rxne(dev);
}
}
static void i2c_wch_error_isr(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
uint16_t status = regs->STAR1;
if (status & (I2C_STAR1_AF | I2C_STAR1_ARLO | I2C_STAR1_BERR)) {
if (status & I2C_STAR1_AF) {
regs->CTLR1 |= I2C_CTLR1_STOP;
}
data->current.err |= ((status >> 8) & 0x7);
regs->STAR1 &= ~(I2C_STAR1_AF | I2C_STAR1_ARLO | I2C_STAR1_BERR);
k_sem_give(&data->xfer_done);
}
}
static void wch_i2c_msg_init(const struct device *dev, struct i2c_msg *msg,
uint16_t addr, bool first_msg)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
k_sem_reset(&data->xfer_done);
data->current.msg = msg;
data->current.idx = 0U;
data->current.err = 0U;
data->current.addr = addr;
regs->CTLR1 |= I2C_CTLR1_PE;
regs->CTLR1 |= I2C_CTLR1_ACK;
if (first_msg || (msg->flags & I2C_MSG_RESTART)) {
if (regs->CTLR1 & I2C_CTLR1_STOP) {
regs->CTLR1 &= ~I2C_CTLR1_STOP;
}
regs->CTLR1 |= I2C_CTLR1_START;
}
}
static int32_t wch_i2c_msg_end(const struct device *dev)
{
struct i2c_wch_data *data = dev->data;
if (!data->current.err) {
return 0;
}
if (data->current.err & (I2C_STAR1_ARLO >> 8)) {
LOG_DBG("ARLO");
}
if (data->current.err & (I2C_STAR1_AF >> 8)) {
LOG_DBG("NACK");
}
if (data->current.err & (I2C_STAR1_BERR >> 8)) {
LOG_DBG("ERR");
}
data->current.err = 0;
return -EIO;
}
static void wch_i2c_config_interrupts(I2C_TypeDef *regs, bool enable)
{
if (enable) {
regs->CTLR2 |= (I2C_CTLR2_ITERREN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITBUFEN);
} else {
regs->CTLR2 &= ~(I2C_CTLR2_ITERREN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITBUFEN);
}
}
static int32_t wch_i2c_begin_transfer(const struct device *dev, struct i2c_msg *msg,
uint16_t addr, bool first_msg)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
I2C_TypeDef *regs = config->regs;
wch_i2c_msg_init(dev, msg, addr, first_msg);
wch_i2c_config_interrupts(regs, true);
if (k_sem_take(&data->xfer_done, K_MSEC(CONFIG_I2C_WCH_XFER_TIMEOUT_MS)) < 0) {
return -ETIMEDOUT;
}
return wch_i2c_msg_end(dev);
}
static void wch_i2c_finish_transfer(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
I2C_TypeDef *regs = config->regs;
wch_i2c_config_interrupts(regs, false);
while (regs->STAR2 & I2C_STAR2_BUSY) {
}
regs->CTLR1 &= ~I2C_CTLR1_PE;
}
static int wch_i2c_configure_timing(I2C_TypeDef *regs, uint32_t clock_rate,
uint32_t speed)
{
uint16_t freq_range = (uint16_t)(clock_rate / 1000000);
uint16_t clock_config;
#ifndef CONFIG_SOC_CH32V003
uint16_t trise;
switch (speed) {
case I2C_SPEED_STANDARD:
trise = freq_range + 1;
break;
case I2C_SPEED_FAST:
trise = (freq_range * 3 / 10) + 1;
break;
default:
return -EINVAL;
}
regs->RTR = trise;
#endif
switch (speed) {
case I2C_SPEED_STANDARD:
clock_config = MAX((uint16_t)(clock_rate / (100000 * 2)), 4);
break;
case I2C_SPEED_FAST:
clock_config = MAX((uint16_t)(clock_rate / (400000 * 3)), 1) | I2C_CKCFGR_FS;
break;
default:
return -EINVAL;
}
regs->CKCFGR = clock_config;
regs->CTLR2 = (regs->CTLR2 & ~I2C_CTLR2_FREQ) | freq_range;
return 0;
}
static int i2c_wch_configure(const struct device *dev, uint32_t dev_config)
{
const struct i2c_wch_config *config = dev->config;
I2C_TypeDef *regs = config->regs;
clock_control_subsys_t clk_sys;
uint32_t clock_rate;
int err;
if (!(dev_config & I2C_MODE_CONTROLLER)) {
return -ENOTSUP;
}
if (dev_config & I2C_ADDR_10_BITS) {
return -ENOTSUP;
}
clk_sys = (clock_control_subsys_t)(uintptr_t)config->clk_id;
err = clock_control_get_rate(config->clk_dev, clk_sys, &clock_rate);
if (err != 0) {
return err;
}
regs->CTLR1 &= ~I2C_CTLR1_PE;
err = wch_i2c_configure_timing(regs, clock_rate, I2C_SPEED_GET(dev_config));
if (err != 0) {
return err;
}
return 0;
}
static int i2c_wch_transfer(const struct device *dev, struct i2c_msg *msg,
uint8_t num_msgs, uint16_t addr)
{
int ret = 0;
for (uint8_t i = 1; i < num_msgs; i++) {
if ((msg[i - 1].flags & I2C_MSG_RW_MASK) != (msg[i].flags & I2C_MSG_RW_MASK)) {
if (!(msg[i].flags & I2C_MSG_RESTART)) {
return -EINVAL;
}
}
if (msg[i - 1].flags & I2C_MSG_STOP) {
return -EINVAL;
}
}
for (uint8_t i = 0; i < num_msgs && ret == 0; i++) {
ret = wch_i2c_begin_transfer(dev, &msg[i], addr, i == 0);
}
wch_i2c_finish_transfer(dev);
return ret;
}
static int i2c_wch_init(const struct device *dev)
{
const struct i2c_wch_config *config = dev->config;
struct i2c_wch_data *data = dev->data;
clock_control_subsys_t clk_sys;
int err;
k_sem_init(&data->xfer_done, 0, 1);
clk_sys = (clock_control_subsys_t)(uintptr_t)config->clk_id;
err = clock_control_on(config->clk_dev, clk_sys);
if (err < 0) {
return err;
}
err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (err < 0) {
return err;
}
err = i2c_wch_configure(dev, I2C_MODE_CONTROLLER | i2c_map_dt_bitrate(config->bitrate));
if (err < 0) {
return err;
}
config->irq_config_func(dev);
return 0;
}
static DEVICE_API(i2c, i2c_wch_api) = {
.configure = i2c_wch_configure,
.transfer = i2c_wch_transfer,
#ifdef CONFIG_I2C_RTIO
.iodev_submit = i2c_iodev_submit_fallback,
#endif
};
#define I2C_WCH_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
\
static void i2c_wch_config_func_##inst(const struct device *dev); \
\
static struct i2c_wch_config i2c_wch_cfg_##inst = { \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
.irq_config_func = i2c_wch_config_func_##inst, \
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
.regs = (I2C_TypeDef *)DT_INST_REG_ADDR(inst), \
.bitrate = DT_INST_PROP(inst, clock_frequency), \
.clk_id = DT_INST_CLOCKS_CELL(inst, id) \
}; \
\
static struct i2c_wch_data i2c_wch_data_##inst; \
\
I2C_DEVICE_DT_INST_DEFINE(inst, i2c_wch_init, NULL, &i2c_wch_data_##inst, \
&i2c_wch_cfg_##inst, PRE_KERNEL_1, \
CONFIG_I2C_INIT_PRIORITY, &i2c_wch_api); \
\
static void i2c_wch_config_func_##inst(const struct device *dev) \
{ \
ARG_UNUSED(dev); \
\
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 0, irq), \
DT_INST_IRQ_BY_IDX(inst, 0, priority), \
i2c_wch_event_isr, DEVICE_DT_INST_GET(inst), 0); \
irq_enable(DT_INST_IRQ_BY_IDX(inst, 0, irq)); \
\
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 1, irq), \
DT_INST_IRQ_BY_IDX(inst, 1, priority), \
i2c_wch_error_isr, DEVICE_DT_INST_GET(inst), 0); \
irq_enable(DT_INST_IRQ_BY_IDX(inst, 1, irq)); \
}
DT_INST_FOREACH_STATUS_OKAY(I2C_WCH_INIT)