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
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)
|
|
|