715 lines
17 KiB
C
715 lines
17 KiB
C
/*
|
|
* Copyright (c) 2021 BrainCo Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT gd_gd32_i2c
|
|
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/gd32.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/reset.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
|
|
#include <gd32_i2c.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(i2c_gd32, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
#include "i2c-priv.h"
|
|
|
|
/* Bus error */
|
|
#define I2C_GD32_ERR_BERR BIT(0)
|
|
/* Arbitration lost */
|
|
#define I2C_GD32_ERR_LARB BIT(1)
|
|
/* No ACK received */
|
|
#define I2C_GD32_ERR_AERR BIT(2)
|
|
/* I2C bus busy */
|
|
#define I2C_GD32_ERR_BUSY BIT(4)
|
|
|
|
struct i2c_gd32_config {
|
|
uint32_t reg;
|
|
uint32_t bitrate;
|
|
uint16_t clkid;
|
|
struct reset_dt_spec reset;
|
|
const struct pinctrl_dev_config *pcfg;
|
|
void (*irq_cfg_func)(void);
|
|
};
|
|
|
|
struct i2c_gd32_data {
|
|
struct k_sem bus_mutex;
|
|
struct k_sem sync_sem;
|
|
uint32_t dev_config;
|
|
uint16_t addr1;
|
|
uint16_t addr2;
|
|
uint32_t xfer_len;
|
|
struct i2c_msg *current;
|
|
uint8_t errs;
|
|
bool is_restart;
|
|
};
|
|
|
|
static inline void i2c_gd32_enable_interrupts(const struct i2c_gd32_config *cfg)
|
|
{
|
|
I2C_CTL1(cfg->reg) |= I2C_CTL1_ERRIE;
|
|
I2C_CTL1(cfg->reg) |= I2C_CTL1_EVIE;
|
|
I2C_CTL1(cfg->reg) |= I2C_CTL1_BUFIE;
|
|
}
|
|
|
|
static inline void i2c_gd32_disable_interrupts(const struct i2c_gd32_config *cfg)
|
|
{
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_ERRIE;
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_EVIE;
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
|
|
}
|
|
|
|
static inline void i2c_gd32_xfer_read(struct i2c_gd32_data *data,
|
|
const struct i2c_gd32_config *cfg)
|
|
{
|
|
data->current->len--;
|
|
*data->current->buf = I2C_DATA(cfg->reg);
|
|
data->current->buf++;
|
|
|
|
if ((data->xfer_len > 0U) &&
|
|
(data->current->len == 0U)) {
|
|
data->current++;
|
|
}
|
|
}
|
|
|
|
static inline void i2c_gd32_xfer_write(struct i2c_gd32_data *data,
|
|
const struct i2c_gd32_config *cfg)
|
|
{
|
|
data->current->len--;
|
|
I2C_DATA(cfg->reg) = *data->current->buf;
|
|
data->current->buf++;
|
|
|
|
if ((data->xfer_len > 0U) &&
|
|
(data->current->len == 0U)) {
|
|
data->current++;
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_handle_rbne(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
switch (data->xfer_len) {
|
|
case 0:
|
|
/* Unwanted data received, ignore it. */
|
|
k_sem_give(&data->sync_sem);
|
|
break;
|
|
case 1:
|
|
/* If total_read_length == 1, read the data directly. */
|
|
data->xfer_len--;
|
|
i2c_gd32_xfer_read(data, cfg);
|
|
|
|
k_sem_give(&data->sync_sem);
|
|
|
|
break;
|
|
case 2:
|
|
__fallthrough;
|
|
case 3:
|
|
/*
|
|
* If total_read_length == 2, or total_read_length > 3
|
|
* and remaining_read_length == 3, disable the RBNE
|
|
* interrupt.
|
|
* Remaining data will be read from BTC interrupt.
|
|
*/
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
|
|
break;
|
|
default:
|
|
/*
|
|
* If total_read_length > 3 and remaining_read_length > 3,
|
|
* read the data directly.
|
|
*/
|
|
data->xfer_len--;
|
|
i2c_gd32_xfer_read(data, cfg);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static void i2c_gd32_handle_tbe(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
if (data->xfer_len > 0U) {
|
|
data->xfer_len--;
|
|
if (data->xfer_len == 0U) {
|
|
/*
|
|
* This is the last data to transmit, disable the TBE interrupt.
|
|
* Use the BTC interrupt to indicate the write data complete state.
|
|
*/
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_BUFIE;
|
|
}
|
|
i2c_gd32_xfer_write(data, cfg);
|
|
|
|
} else {
|
|
/* Enter stop condition */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;
|
|
|
|
k_sem_give(&data->sync_sem);
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_handle_btc(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
if (data->current->flags & I2C_MSG_READ) {
|
|
uint32_t counter = 0U;
|
|
|
|
switch (data->xfer_len) {
|
|
case 2:
|
|
/* Stop condition must be generated before reading the last two bytes. */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;
|
|
|
|
for (counter = 2U; counter > 0; counter--) {
|
|
data->xfer_len--;
|
|
i2c_gd32_xfer_read(data, cfg);
|
|
}
|
|
|
|
k_sem_give(&data->sync_sem);
|
|
|
|
break;
|
|
case 3:
|
|
/* Clear ACKEN bit */
|
|
I2C_CTL0(cfg->reg) &= ~I2C_CTL0_ACKEN;
|
|
|
|
data->xfer_len--;
|
|
i2c_gd32_xfer_read(data, cfg);
|
|
|
|
break;
|
|
default:
|
|
i2c_gd32_handle_rbne(dev);
|
|
break;
|
|
}
|
|
} else {
|
|
i2c_gd32_handle_tbe(dev);
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_handle_addsend(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
if ((data->current->flags & I2C_MSG_READ) && (data->xfer_len <= 2U)) {
|
|
I2C_CTL0(cfg->reg) &= ~I2C_CTL0_ACKEN;
|
|
}
|
|
|
|
/* Clear ADDSEND bit */
|
|
I2C_STAT0(cfg->reg);
|
|
I2C_STAT1(cfg->reg);
|
|
|
|
if (data->is_restart) {
|
|
data->is_restart = false;
|
|
data->current->flags &= ~I2C_MSG_RW_MASK;
|
|
data->current->flags |= I2C_MSG_READ;
|
|
/* Enter repeated start condition */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_START;
|
|
|
|
return;
|
|
}
|
|
|
|
if ((data->current->flags & I2C_MSG_READ) && (data->xfer_len == 1U)) {
|
|
/* Enter stop condition */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_event_isr(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
uint32_t stat;
|
|
|
|
stat = I2C_STAT0(cfg->reg);
|
|
|
|
if (stat & I2C_STAT0_SBSEND) {
|
|
if (data->current->flags & I2C_MSG_READ) {
|
|
I2C_DATA(cfg->reg) = (data->addr1 << 1U) | 1U;
|
|
} else {
|
|
I2C_DATA(cfg->reg) = (data->addr1 << 1U) | 0U;
|
|
}
|
|
} else if (stat & I2C_STAT0_ADD10SEND) {
|
|
I2C_DATA(cfg->reg) = data->addr2;
|
|
} else if (stat & I2C_STAT0_ADDSEND) {
|
|
i2c_gd32_handle_addsend(dev);
|
|
/*
|
|
* Must handle BTC first.
|
|
* For I2C_STAT0, BTC is the superset of RBNE and TBE.
|
|
*/
|
|
} else if (stat & I2C_STAT0_BTC) {
|
|
i2c_gd32_handle_btc(dev);
|
|
} else if (stat & I2C_STAT0_RBNE) {
|
|
i2c_gd32_handle_rbne(dev);
|
|
} else if (stat & I2C_STAT0_TBE) {
|
|
i2c_gd32_handle_tbe(dev);
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_error_isr(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
uint32_t stat;
|
|
|
|
stat = I2C_STAT0(cfg->reg);
|
|
|
|
if (stat & I2C_STAT0_BERR) {
|
|
I2C_STAT0(cfg->reg) &= ~I2C_STAT0_BERR;
|
|
data->errs |= I2C_GD32_ERR_BERR;
|
|
}
|
|
|
|
if (stat & I2C_STAT0_LOSTARB) {
|
|
I2C_STAT0(cfg->reg) &= ~I2C_STAT0_LOSTARB;
|
|
data->errs |= I2C_GD32_ERR_LARB;
|
|
}
|
|
|
|
if (stat & I2C_STAT0_AERR) {
|
|
I2C_STAT0(cfg->reg) &= ~I2C_STAT0_AERR;
|
|
data->errs |= I2C_GD32_ERR_AERR;
|
|
}
|
|
|
|
if (data->errs != 0U) {
|
|
/* Enter stop condition */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_STOP;
|
|
|
|
k_sem_give(&data->sync_sem);
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_log_err(struct i2c_gd32_data *data)
|
|
{
|
|
if (data->errs & I2C_GD32_ERR_BERR) {
|
|
LOG_ERR("Bus error");
|
|
}
|
|
|
|
if (data->errs & I2C_GD32_ERR_LARB) {
|
|
LOG_ERR("Arbitration lost");
|
|
}
|
|
|
|
if (data->errs & I2C_GD32_ERR_AERR) {
|
|
LOG_ERR("No ACK received");
|
|
}
|
|
|
|
if (data->errs & I2C_GD32_ERR_BUSY) {
|
|
LOG_ERR("I2C bus busy");
|
|
}
|
|
}
|
|
|
|
static void i2c_gd32_xfer_begin(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
k_sem_reset(&data->sync_sem);
|
|
|
|
data->errs = 0U;
|
|
data->is_restart = false;
|
|
|
|
/* Default to set ACKEN bit. */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_ACKEN;
|
|
|
|
if (data->current->flags & I2C_MSG_READ) {
|
|
/* For 2 bytes read, use POAP bit to give NACK for the last data receiving. */
|
|
if (data->xfer_len == 2U) {
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_POAP;
|
|
}
|
|
|
|
/*
|
|
* For read on 10 bits address mode, start condition will happen twice.
|
|
* Transfer sequence as below:
|
|
* S addr1+W addr2 S addr1+R
|
|
* Use a is_restart flag to cover this case.
|
|
*/
|
|
if (data->dev_config & I2C_ADDR_10_BITS) {
|
|
data->is_restart = true;
|
|
data->current->flags &= ~I2C_MSG_RW_MASK;
|
|
}
|
|
}
|
|
|
|
i2c_gd32_enable_interrupts(cfg);
|
|
|
|
/* Enter repeated start condition */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_START;
|
|
}
|
|
|
|
static int i2c_gd32_xfer_end(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
i2c_gd32_disable_interrupts(cfg);
|
|
|
|
/* Wait for stop condition is done. */
|
|
while (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
|
|
/* NOP */
|
|
}
|
|
|
|
if (data->errs) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_gd32_msg_read(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
if (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
|
|
data->errs = I2C_GD32_ERR_BUSY;
|
|
return -EBUSY;
|
|
}
|
|
|
|
i2c_gd32_xfer_begin(dev);
|
|
|
|
k_sem_take(&data->sync_sem, K_FOREVER);
|
|
|
|
return i2c_gd32_xfer_end(dev);
|
|
}
|
|
|
|
static int i2c_gd32_msg_write(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
|
|
if (I2C_STAT1(cfg->reg) & I2C_STAT1_I2CBSY) {
|
|
data->errs = I2C_GD32_ERR_BUSY;
|
|
return -EBUSY;
|
|
}
|
|
|
|
i2c_gd32_xfer_begin(dev);
|
|
|
|
k_sem_take(&data->sync_sem, K_FOREVER);
|
|
|
|
return i2c_gd32_xfer_end(dev);
|
|
}
|
|
|
|
static int i2c_gd32_transfer(const struct device *dev,
|
|
struct i2c_msg *msgs,
|
|
uint8_t num_msgs,
|
|
uint16_t addr)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
struct i2c_msg *current, *next;
|
|
uint8_t itr;
|
|
int err = 0;
|
|
|
|
current = msgs;
|
|
|
|
/* First message flags implicitly contain I2C_MSG_RESTART flag. */
|
|
current->flags |= I2C_MSG_RESTART;
|
|
|
|
for (uint8_t i = 1; i <= num_msgs; i++) {
|
|
|
|
if (i < num_msgs) {
|
|
next = current + 1;
|
|
|
|
/*
|
|
* If there have a R/W transfer state change between messages,
|
|
* An explicit I2C_MSG_RESTART flag is needed for the second message.
|
|
*/
|
|
if ((current->flags & I2C_MSG_RW_MASK) !=
|
|
(next->flags & I2C_MSG_RW_MASK)) {
|
|
if ((next->flags & I2C_MSG_RESTART) == 0U) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Only the last message need I2C_MSG_STOP flag to free the Bus. */
|
|
if (current->flags & I2C_MSG_STOP) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if ((current->buf == NULL) ||
|
|
(current->len == 0U)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
current++;
|
|
}
|
|
|
|
k_sem_take(&data->bus_mutex, K_FOREVER);
|
|
|
|
/* Enable i2c device */
|
|
I2C_CTL0(cfg->reg) |= I2C_CTL0_I2CEN;
|
|
|
|
if (data->dev_config & I2C_ADDR_10_BITS) {
|
|
data->addr1 = 0xF0 | ((addr & BITS(8, 9)) >> 8U);
|
|
data->addr2 = addr & BITS(0, 7);
|
|
} else {
|
|
data->addr1 = addr & BITS(0, 6);
|
|
}
|
|
|
|
for (uint8_t i = 0; i < num_msgs; i = itr) {
|
|
data->current = &msgs[i];
|
|
data->xfer_len = msgs[i].len;
|
|
|
|
for (itr = i + 1; itr < num_msgs; itr++) {
|
|
if ((data->current->flags & I2C_MSG_RW_MASK) !=
|
|
(msgs[itr].flags & I2C_MSG_RW_MASK)) {
|
|
break;
|
|
}
|
|
data->xfer_len += msgs[itr].len;
|
|
}
|
|
|
|
if (data->current->flags & I2C_MSG_READ) {
|
|
err = i2c_gd32_msg_read(dev);
|
|
} else {
|
|
err = i2c_gd32_msg_write(dev);
|
|
}
|
|
|
|
if (err < 0) {
|
|
i2c_gd32_log_err(data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Disable I2C device */
|
|
I2C_CTL0(cfg->reg) &= ~I2C_CTL0_I2CEN;
|
|
|
|
k_sem_give(&data->bus_mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int i2c_gd32_configure(const struct device *dev,
|
|
uint32_t dev_config)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
uint32_t pclk1, freq, clkc;
|
|
int err = 0;
|
|
|
|
k_sem_take(&data->bus_mutex, K_FOREVER);
|
|
|
|
/* Disable I2C device */
|
|
I2C_CTL0(cfg->reg) &= ~I2C_CTL0_I2CEN;
|
|
|
|
(void)clock_control_get_rate(GD32_CLOCK_CONTROLLER,
|
|
(clock_control_subsys_t)&cfg->clkid,
|
|
&pclk1);
|
|
|
|
/* i2c clock frequency, us */
|
|
freq = pclk1 / 1000000U;
|
|
if (freq > I2CCLK_MAX) {
|
|
LOG_ERR("I2C max clock freq %u, current is %u\n",
|
|
I2CCLK_MAX, freq);
|
|
err = -ENOTSUP;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Refer from SoC user manual.
|
|
* In standard mode:
|
|
* T_high = CLKC * T_pclk1
|
|
* T_low = CLKC * T_pclk1
|
|
*
|
|
* In fast mode and fast mode plus with DTCY=1:
|
|
* T_high = 9 * CLKC * T_pclk1
|
|
* T_low = 16 * CLKC * T_pclk1
|
|
*
|
|
* T_pclk1 is reciprocal of pclk1:
|
|
* T_pclk1 = 1 / pclk1
|
|
*
|
|
* T_high and T_low construct the bit transfer:
|
|
* T_high + T_low = 1 / bitrate
|
|
*
|
|
* And then, we can get the CLKC equation.
|
|
* Standard mode:
|
|
* CLKC = pclk1 / (bitrate * 2)
|
|
* Fast mode and fast mode plus:
|
|
* CLKC = pclk1 / (bitrate * 25)
|
|
*
|
|
* Variable list:
|
|
* T_high: high period of the SCL clock
|
|
* T_low: low period of the SCL clock
|
|
* T_pclk1: duration of single pclk1 pulse
|
|
* pclk1: i2c device clock frequency
|
|
* bitrate: 100 Kbits for standard mode
|
|
*/
|
|
switch (I2C_SPEED_GET(dev_config)) {
|
|
case I2C_SPEED_STANDARD:
|
|
if (freq < I2CCLK_MIN) {
|
|
LOG_ERR("I2C standard-mode min clock freq %u, current is %u\n",
|
|
I2CCLK_MIN, freq);
|
|
err = -ENOTSUP;
|
|
goto error;
|
|
}
|
|
I2C_CTL1(cfg->reg) &= ~I2C_CTL1_I2CCLK;
|
|
I2C_CTL1(cfg->reg) |= freq;
|
|
|
|
/* Standard-mode risetime maximum value: 1000ns */
|
|
if (freq == I2CCLK_MAX) {
|
|
I2C_RT(cfg->reg) = I2CCLK_MAX;
|
|
} else {
|
|
I2C_RT(cfg->reg) = freq + 1U;
|
|
}
|
|
|
|
/* CLKC = pclk1 / (bitrate * 2) */
|
|
clkc = pclk1 / (I2C_BITRATE_STANDARD * 2U);
|
|
|
|
I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
|
|
I2C_CKCFG(cfg->reg) |= clkc;
|
|
/* standard-mode */
|
|
I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_FAST;
|
|
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
if (freq < I2CCLK_FM_MIN) {
|
|
LOG_ERR("I2C fast-mode min clock freq %u, current is %u\n",
|
|
I2CCLK_FM_MIN, freq);
|
|
err = -ENOTSUP;
|
|
goto error;
|
|
}
|
|
|
|
/* Fast-mode risetime maximum value: 300ns */
|
|
I2C_RT(cfg->reg) = freq * 300U / 1000U + 1U;
|
|
|
|
/* CLKC = pclk1 / (bitrate * 25) */
|
|
clkc = pclk1 / (I2C_BITRATE_FAST * 25U);
|
|
if (clkc == 0U) {
|
|
clkc = 1U;
|
|
}
|
|
|
|
/* Default DCTY to 1 */
|
|
I2C_CKCFG(cfg->reg) |= I2C_CKCFG_DTCY;
|
|
I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
|
|
I2C_CKCFG(cfg->reg) |= clkc;
|
|
/* Transfer mode: fast-mode */
|
|
I2C_CKCFG(cfg->reg) |= I2C_CKCFG_FAST;
|
|
|
|
#ifdef I2C_FMPCFG
|
|
/* Disable transfer mode: fast-mode plus */
|
|
I2C_FMPCFG(cfg->reg) &= ~I2C_FMPCFG_FMPEN;
|
|
#endif /* I2C_FMPCFG */
|
|
|
|
break;
|
|
#ifdef I2C_FMPCFG
|
|
case I2C_SPEED_FAST_PLUS:
|
|
if (freq < I2CCLK_FM_PLUS_MIN) {
|
|
LOG_ERR("I2C fast-mode plus min clock freq %u, current is %u\n",
|
|
I2CCLK_FM_PLUS_MIN, freq);
|
|
err = -ENOTSUP;
|
|
goto error;
|
|
}
|
|
|
|
/* Fast-mode plus risetime maximum value: 120ns */
|
|
I2C_RT(cfg->reg) = freq * 120U / 1000U + 1U;
|
|
|
|
/* CLKC = pclk1 / (bitrate * 25) */
|
|
clkc = pclk1 / (I2C_BITRATE_FAST_PLUS * 25U);
|
|
if (clkc == 0U) {
|
|
clkc = 1U;
|
|
}
|
|
|
|
/* Default DCTY to 1 */
|
|
I2C_CKCFG(cfg->reg) |= I2C_CKCFG_DTCY;
|
|
I2C_CKCFG(cfg->reg) &= ~I2C_CKCFG_CLKC;
|
|
I2C_CKCFG(cfg->reg) |= clkc;
|
|
/* Transfer mode: fast-mode */
|
|
I2C_CKCFG(cfg->reg) |= I2C_CKCFG_FAST;
|
|
|
|
/* Enable transfer mode: fast-mode plus */
|
|
I2C_FMPCFG(cfg->reg) |= I2C_FMPCFG_FMPEN;
|
|
|
|
break;
|
|
#endif /* I2C_FMPCFG */
|
|
default:
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
data->dev_config = dev_config;
|
|
error:
|
|
k_sem_give(&data->bus_mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static const struct i2c_driver_api i2c_gd32_driver_api = {
|
|
.configure = i2c_gd32_configure,
|
|
.transfer = i2c_gd32_transfer,
|
|
};
|
|
|
|
static int i2c_gd32_init(const struct device *dev)
|
|
{
|
|
struct i2c_gd32_data *data = dev->data;
|
|
const struct i2c_gd32_config *cfg = dev->config;
|
|
uint32_t bitrate_cfg;
|
|
int err;
|
|
|
|
err = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
/* Mutex semaphore to protect the i2c api in multi-thread env. */
|
|
k_sem_init(&data->bus_mutex, 1, 1);
|
|
|
|
/* Sync semaphore to sync i2c state between isr and transfer api. */
|
|
k_sem_init(&data->sync_sem, 0, K_SEM_MAX_LIMIT);
|
|
|
|
(void)clock_control_on(GD32_CLOCK_CONTROLLER,
|
|
(clock_control_subsys_t)&cfg->clkid);
|
|
|
|
(void)reset_line_toggle_dt(&cfg->reset);
|
|
|
|
cfg->irq_cfg_func();
|
|
|
|
bitrate_cfg = i2c_map_dt_bitrate(cfg->bitrate);
|
|
|
|
i2c_gd32_configure(dev, I2C_MODE_CONTROLLER | bitrate_cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define I2C_GD32_INIT(inst) \
|
|
PINCTRL_DT_INST_DEFINE(inst); \
|
|
static void i2c_gd32_irq_cfg_func_##inst(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, event, irq), \
|
|
DT_INST_IRQ_BY_NAME(inst, event, priority), \
|
|
i2c_gd32_event_isr, \
|
|
DEVICE_DT_INST_GET(inst), \
|
|
0); \
|
|
irq_enable(DT_INST_IRQ_BY_NAME(inst, event, irq)); \
|
|
\
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, error, irq), \
|
|
DT_INST_IRQ_BY_NAME(inst, error, priority), \
|
|
i2c_gd32_error_isr, \
|
|
DEVICE_DT_INST_GET(inst), \
|
|
0); \
|
|
irq_enable(DT_INST_IRQ_BY_NAME(inst, error, irq)); \
|
|
} \
|
|
static struct i2c_gd32_data i2c_gd32_data_##inst; \
|
|
const static struct i2c_gd32_config i2c_gd32_cfg_##inst = { \
|
|
.reg = DT_INST_REG_ADDR(inst), \
|
|
.bitrate = DT_INST_PROP(inst, clock_frequency), \
|
|
.clkid = DT_INST_CLOCKS_CELL(inst, id), \
|
|
.reset = RESET_DT_SPEC_INST_GET(inst), \
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
|
.irq_cfg_func = i2c_gd32_irq_cfg_func_##inst, \
|
|
}; \
|
|
I2C_DEVICE_DT_INST_DEFINE(inst, \
|
|
i2c_gd32_init, NULL, \
|
|
&i2c_gd32_data_##inst, &i2c_gd32_cfg_##inst, \
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \
|
|
&i2c_gd32_driver_api); \
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_GD32_INIT)
|