788 lines
25 KiB
C
788 lines
25 KiB
C
/*
|
|
* Copyright (c) 2023 Nuvoton Technology Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nuvoton_numaker_i2c
|
|
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
|
|
#include <zephyr/drivers/reset.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(i2c_numaker, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
#include "i2c-priv.h"
|
|
#include <soc.h>
|
|
#include <NuMicro.h>
|
|
|
|
/* i2c Master Mode Status */
|
|
#define M_START 0x08 /* Start */
|
|
#define M_REPEAT_START 0x10 /* Master Repeat Start */
|
|
#define M_TRAN_ADDR_ACK 0x18 /* Master Transmit Address ACK */
|
|
#define M_TRAN_ADDR_NACK 0x20 /* Master Transmit Address NACK */
|
|
#define M_TRAN_DATA_ACK 0x28 /* Master Transmit Data ACK */
|
|
#define M_TRAN_DATA_NACK 0x30 /* Master Transmit Data NACK */
|
|
#define M_ARB_LOST 0x38 /* Master Arbitration Los */
|
|
#define M_RECE_ADDR_ACK 0x40 /* Master Receive Address ACK */
|
|
#define M_RECE_ADDR_NACK 0x48 /* Master Receive Address NACK */
|
|
#define M_RECE_DATA_ACK 0x50 /* Master Receive Data ACK */
|
|
#define M_RECE_DATA_NACK 0x58 /* Master Receive Data NACK */
|
|
#define BUS_ERROR 0x00 /* Bus error */
|
|
|
|
/* i2c Slave Mode Status */
|
|
#define S_REPEAT_START_STOP 0xA0 /* Slave Transmit Repeat Start or Stop */
|
|
#define S_TRAN_ADDR_ACK 0xA8 /* Slave Transmit Address ACK */
|
|
#define S_TRAN_DATA_ACK 0xB8 /* Slave Transmit Data ACK */
|
|
#define S_TRAN_DATA_NACK 0xC0 /* Slave Transmit Data NACK */
|
|
#define S_TRAN_LAST_DATA_ACK 0xC8 /* Slave Transmit Last Data ACK */
|
|
#define S_RECE_ADDR_ACK 0x60 /* Slave Receive Address ACK */
|
|
#define S_RECE_ARB_LOST 0x68 /* Slave Receive Arbitration Lost */
|
|
#define S_RECE_DATA_ACK 0x80 /* Slave Receive Data ACK */
|
|
#define S_RECE_DATA_NACK 0x88 /* Slave Receive Data NACK */
|
|
|
|
/* i2c GC Mode Status */
|
|
#define GC_ADDR_ACK 0x70 /* GC mode Address ACK */
|
|
#define GC_ARB_LOST 0x78 /* GC mode Arbitration Lost */
|
|
#define GC_DATA_ACK 0x90 /* GC mode Data ACK */
|
|
#define GC_DATA_NACK 0x98 /* GC mode Data NACK */
|
|
|
|
/* i2c Other Status */
|
|
#define ADDR_TRAN_ARB_LOST 0xB0 /* Address Transmit Arbitration Lost */
|
|
#define BUS_RELEASED 0xF8 /* Bus Released */
|
|
|
|
struct i2c_numaker_config {
|
|
I2C_T *i2c_base;
|
|
const struct reset_dt_spec reset;
|
|
uint32_t clk_modidx;
|
|
uint32_t clk_src;
|
|
uint32_t clk_div;
|
|
const struct device *clkctrl_dev;
|
|
uint32_t irq_n;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
const struct pinctrl_dev_config *pincfg;
|
|
uint32_t bitrate;
|
|
};
|
|
|
|
struct i2c_numaker_data {
|
|
struct k_sem lock;
|
|
uint32_t dev_config;
|
|
/* Master transfer context */
|
|
struct {
|
|
struct k_sem xfer_sync;
|
|
uint16_t addr;
|
|
struct i2c_msg *msgs_beg;
|
|
struct i2c_msg *msgs_pos;
|
|
struct i2c_msg *msgs_end;
|
|
uint8_t *buf_beg;
|
|
uint8_t *buf_pos;
|
|
uint8_t *buf_end;
|
|
} master_xfer;
|
|
#ifdef CONFIG_I2C_TARGET
|
|
/* Slave transfer context */
|
|
struct {
|
|
struct i2c_target_config *slave_config;
|
|
bool slave_addressed;
|
|
} slave_xfer;
|
|
#endif
|
|
};
|
|
|
|
/* ACK/NACK last data byte, dependent on whether or not message merge is allowed */
|
|
static void m_numaker_i2c_master_xfer_msg_read_last_byte(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
|
|
/* Shouldn't invoke with message pointer OOB */
|
|
__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
|
|
/* Should invoke with exactly one data byte remaining for read */
|
|
__ASSERT_NO_MSG((data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ);
|
|
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);
|
|
|
|
/* Flags of previous message */
|
|
bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;
|
|
|
|
/* Advance to next messages temporarily */
|
|
data->master_xfer.msgs_pos++;
|
|
|
|
/* Has next message? */
|
|
if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
|
|
/* Flags of next message */
|
|
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
|
bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
|
bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;
|
|
|
|
/*
|
|
* Different R/W bit so message merge is disallowed.
|
|
* Force I2C Repeat Start on I2C Stop/Repeat Start missing
|
|
*/
|
|
if (!is_read_next) {
|
|
if (!do_stop_prev && !do_restart_next) {
|
|
do_restart_next = true;
|
|
}
|
|
}
|
|
|
|
if (do_stop_prev || do_restart_next) {
|
|
/* NACK last data byte (required for Master Receiver) */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
} else {
|
|
/* ACK last data byte, so to merge adjacent messages into one transaction */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
}
|
|
} else {
|
|
/* NACK last data byte (required for Master Receiver) */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
}
|
|
|
|
/* Roll back message pointer */
|
|
data->master_xfer.msgs_pos--;
|
|
}
|
|
|
|
/* End the transfer, involving I2C Stop and signal to thread */
|
|
static void m_numaker_i2c_master_xfer_end(const struct device *dev, bool do_stop)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
|
|
if (do_stop) {
|
|
/* Do I2C Stop */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
|
}
|
|
|
|
/* Signal master transfer end */
|
|
k_sem_give(&data->master_xfer.xfer_sync);
|
|
}
|
|
|
|
static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev);
|
|
/* Read next data byte, involving ACK/NACK last data byte and message merge */
|
|
static void m_numaker_i2c_master_xfer_msg_read_next_byte(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
|
|
switch (data->master_xfer.buf_end - data->master_xfer.buf_pos) {
|
|
case 0:
|
|
/* Last data byte ACKed, we'll do message merge */
|
|
m_numaker_i2c_master_xfer_msg_end(dev);
|
|
break;
|
|
case 1:
|
|
/* Read last data byte for this message */
|
|
m_numaker_i2c_master_xfer_msg_read_last_byte(dev);
|
|
break;
|
|
default:
|
|
/* ACK non-last data byte */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
}
|
|
}
|
|
|
|
/* End one message transfer, involving message merge and transfer end */
|
|
static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
|
|
/* Shouldn't invoke with message pointer OOB */
|
|
__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
|
|
/* Should have transferred up */
|
|
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 0);
|
|
|
|
/* Flags of previous message */
|
|
bool is_read_prev = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
|
bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;
|
|
|
|
/* Advance to next messages */
|
|
data->master_xfer.msgs_pos++;
|
|
|
|
/* Has next message? */
|
|
if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
|
|
/* Flags of next message */
|
|
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
|
bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
|
bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;
|
|
|
|
/*
|
|
* Different R/W bit so message merge is disallowed.
|
|
* Force I2C Repeat Start on I2C Stop/Repeat Start missing
|
|
*/
|
|
if (!is_read_prev != !is_read_next) { /* Logical XOR idiom */
|
|
if (!do_stop_prev && !do_restart_next) {
|
|
LOG_WRN("Cannot merge adjacent messages, force I2C Repeat Start");
|
|
do_restart_next = true;
|
|
}
|
|
}
|
|
|
|
if (do_stop_prev) {
|
|
/* Do I2C Stop and then Start */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk |
|
|
I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
|
} else if (do_restart_next) {
|
|
/* Do I2C Repeat Start */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);
|
|
} else {
|
|
/* Merge into the same transaction */
|
|
|
|
/* Prepare buffer for current message */
|
|
data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
|
|
data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
|
|
data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
|
|
data->master_xfer.msgs_pos->len;
|
|
|
|
if (is_read_prev) {
|
|
m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
|
|
} else {
|
|
/*
|
|
* Interrupt flag not cleared, expect to re-enter ISR with
|
|
* context unchanged, except buffer changed for message change.
|
|
*/
|
|
}
|
|
}
|
|
} else {
|
|
if (!do_stop_prev) {
|
|
LOG_WRN("Last message not marked I2C Stop");
|
|
}
|
|
|
|
m_numaker_i2c_master_xfer_end(dev, do_stop_prev);
|
|
}
|
|
}
|
|
|
|
static int i2c_numaker_configure(const struct device *dev, uint32_t dev_config)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
uint32_t bitrate;
|
|
|
|
/* Check address size */
|
|
if (dev_config & I2C_ADDR_10_BITS) {
|
|
LOG_ERR("10-bits address not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (I2C_SPEED_GET(dev_config)) {
|
|
case I2C_SPEED_STANDARD:
|
|
bitrate = KHZ(100);
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
bitrate = KHZ(400);
|
|
break;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
bitrate = MHZ(1);
|
|
break;
|
|
default:
|
|
LOG_ERR("Speed code %d not supported", I2C_SPEED_GET(dev_config));
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
int err = 0;
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
irq_disable(config->irq_n);
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
if (data->slave_xfer.slave_addressed) {
|
|
LOG_ERR("Reconfigure with slave being busy");
|
|
err = -EBUSY;
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
I2C_Open(i2c_base, bitrate);
|
|
/* INTEN bit and FSM control bits (STA, STO, SI, AA) are packed in one register CTL0. */
|
|
i2c_base->CTL0 |= (I2C_CTL0_INTEN_Msk | I2C_CTL0_I2CEN_Msk);
|
|
data->dev_config = dev_config;
|
|
|
|
done:
|
|
|
|
irq_enable(config->irq_n);
|
|
k_sem_give(&data->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int i2c_numaker_get_config(const struct device *dev, uint32_t *dev_config)
|
|
{
|
|
struct i2c_numaker_data *data = dev->data;
|
|
|
|
if (!dev_config) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
*dev_config = data->dev_config;
|
|
k_sem_give(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Master active transfer:
|
|
* 1. Do I2C Start to start the transfer (thread)
|
|
* 2. I2C FSM (ISR)
|
|
* 3. Force I2C Stop to end the transfer (thread)
|
|
* Slave passive transfer:
|
|
* 1. Prepare callback (thread)
|
|
* 2. Do data transfer via above callback (ISR)
|
|
*/
|
|
static int i2c_numaker_transfer(const struct device *dev, struct i2c_msg *msgs,
|
|
uint8_t num_msgs, uint16_t addr)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
int err = 0;
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
irq_disable(config->irq_n);
|
|
|
|
if (data->slave_xfer.slave_addressed) {
|
|
LOG_ERR("Master transfer with slave being busy");
|
|
err = -EBUSY;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (num_msgs == 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Prepare to start transfer */
|
|
data->master_xfer.addr = addr;
|
|
data->master_xfer.msgs_beg = msgs;
|
|
data->master_xfer.msgs_pos = msgs;
|
|
data->master_xfer.msgs_end = msgs + num_msgs;
|
|
|
|
/* Do I2C Start to start the transfer */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);
|
|
|
|
irq_enable(config->irq_n);
|
|
k_sem_take(&data->master_xfer.xfer_sync, K_FOREVER);
|
|
irq_disable(config->irq_n);
|
|
|
|
/* Check transfer result */
|
|
if (data->master_xfer.msgs_pos != data->master_xfer.msgs_end) {
|
|
bool is_read;
|
|
bool is_10bit;
|
|
|
|
is_read = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
|
is_10bit = data->master_xfer.msgs_pos->flags & I2C_MSG_ADDR_10_BITS;
|
|
LOG_ERR("Failed message:");
|
|
LOG_ERR("MSG IDX: %d", data->master_xfer.msgs_pos - data->master_xfer.msgs_beg);
|
|
LOG_ERR("ADDR (%d-bit): 0x%04X", is_10bit ? 10 : 7, addr);
|
|
LOG_ERR("DIR: %s", is_read ? "R" : "W");
|
|
LOG_ERR("Expected %d bytes transferred, but actual %d",
|
|
data->master_xfer.msgs_pos->len,
|
|
data->master_xfer.buf_pos - data->master_xfer.buf_beg);
|
|
err = -EIO;
|
|
goto i2c_stop;
|
|
}
|
|
|
|
i2c_stop:
|
|
|
|
/* Do I2C Stop to release bus ownership */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
/* Enable slave mode if one slave is registered */
|
|
if (data->slave_xfer.slave_config) {
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
}
|
|
#endif
|
|
|
|
cleanup:
|
|
|
|
irq_enable(config->irq_n);
|
|
k_sem_give(&data->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
static int i2c_numaker_slave_register(const struct device *dev,
|
|
struct i2c_target_config *slave_config)
|
|
{
|
|
if (!slave_config || !slave_config->callbacks) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (slave_config->flags & I2C_ADDR_10_BITS) {
|
|
LOG_ERR("10-bits address not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
int err = 0;
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
irq_disable(config->irq_n);
|
|
|
|
if (data->slave_xfer.slave_config) {
|
|
err = -EBUSY;
|
|
goto cleanup;
|
|
}
|
|
|
|
data->slave_xfer.slave_config = slave_config;
|
|
/* Slave address */
|
|
I2C_SetSlaveAddr(i2c_base,
|
|
0,
|
|
slave_config->address,
|
|
I2C_GCMODE_DISABLE);
|
|
|
|
/* Slave address state */
|
|
data->slave_xfer.slave_addressed = false;
|
|
|
|
/* Enable slave mode */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
|
|
cleanup:
|
|
|
|
irq_enable(config->irq_n);
|
|
k_sem_give(&data->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int i2c_numaker_slave_unregister(const struct device *dev,
|
|
struct i2c_target_config *slave_config)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
int err = 0;
|
|
|
|
if (!slave_config) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
irq_disable(config->irq_n);
|
|
|
|
if (data->slave_xfer.slave_config != slave_config) {
|
|
err = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (data->slave_xfer.slave_addressed) {
|
|
LOG_ERR("Unregister slave driver with slave being busy");
|
|
err = -EBUSY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Slave address: Zero */
|
|
I2C_SetSlaveAddr(i2c_base,
|
|
0,
|
|
0,
|
|
I2C_GCMODE_DISABLE);
|
|
|
|
/* Slave address state */
|
|
data->slave_xfer.slave_addressed = false;
|
|
|
|
/* Disable slave mode */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
data->slave_xfer.slave_config = NULL;
|
|
|
|
cleanup:
|
|
|
|
irq_enable(config->irq_n);
|
|
k_sem_give(&data->lock);
|
|
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
static int i2c_numaker_recover_bus(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
|
|
k_sem_take(&data->lock, K_FOREVER);
|
|
/* Do I2C Stop to release bus ownership */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
|
k_sem_give(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_numaker_isr(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
I2C_T *i2c_base = config->i2c_base;
|
|
#ifdef CONFIG_I2C_TARGET
|
|
struct i2c_target_config *slave_config = data->slave_xfer.slave_config;
|
|
const struct i2c_target_callbacks *slave_callbacks =
|
|
slave_config ? slave_config->callbacks : NULL;
|
|
uint8_t data_byte;
|
|
#endif
|
|
uint32_t status;
|
|
|
|
if (I2C_GET_TIMEOUT_FLAG(i2c_base)) {
|
|
I2C_ClearTimeoutFlag(i2c_base);
|
|
return;
|
|
}
|
|
|
|
status = I2C_GET_STATUS(i2c_base);
|
|
|
|
switch (status) {
|
|
case M_START: /* Start */
|
|
case M_REPEAT_START: /* Master Repeat Start */
|
|
/* Prepare buffer for current message */
|
|
data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
|
|
data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
|
|
data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
|
|
data->master_xfer.msgs_pos->len;
|
|
|
|
/* Write I2C address */
|
|
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
|
bool is_read = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
|
uint16_t addr = data->master_xfer.addr;
|
|
int addr_rw = is_read ? ((addr << 1) | 1) : (addr << 1);
|
|
|
|
I2C_SET_DATA(i2c_base, (uint8_t) (addr_rw & 0xFF));
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
break;
|
|
case M_TRAN_ADDR_ACK: /* Master Transmit Address ACK */
|
|
case M_TRAN_DATA_ACK: /* Master Transmit Data ACK */
|
|
__ASSERT_NO_MSG(data->master_xfer.buf_pos);
|
|
if (data->master_xfer.buf_pos < data->master_xfer.buf_end) {
|
|
I2C_SET_DATA(i2c_base, *data->master_xfer.buf_pos++);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
} else {
|
|
/* End this message */
|
|
m_numaker_i2c_master_xfer_msg_end(dev);
|
|
}
|
|
break;
|
|
case M_TRAN_ADDR_NACK: /* Master Transmit Address NACK */
|
|
case M_TRAN_DATA_NACK: /* Master Transmit Data NACK */
|
|
case M_RECE_ADDR_NACK: /* Master Receive Address NACK */
|
|
case M_ARB_LOST: /* Master Arbitration Lost */
|
|
m_numaker_i2c_master_xfer_end(dev, true);
|
|
break;
|
|
case M_RECE_ADDR_ACK: /* Master Receive Address ACK */
|
|
case M_RECE_DATA_ACK: /* Master Receive Data ACK */
|
|
__ASSERT_NO_MSG(data->master_xfer.buf_pos);
|
|
|
|
if (status == M_RECE_ADDR_ACK) {
|
|
__ASSERT_NO_MSG(data->master_xfer.buf_pos < data->master_xfer.buf_end);
|
|
} else if (status == M_RECE_DATA_ACK) {
|
|
__ASSERT_NO_MSG((data->master_xfer.buf_end -
|
|
data->master_xfer.buf_pos) >= 1);
|
|
*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
|
|
}
|
|
|
|
m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
|
|
break;
|
|
case M_RECE_DATA_NACK: /* Master Receive Data NACK */
|
|
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);
|
|
*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
|
|
/* End this message */
|
|
m_numaker_i2c_master_xfer_msg_end(dev);
|
|
break;
|
|
case BUS_ERROR: /* Bus error */
|
|
m_numaker_i2c_master_xfer_end(dev, true);
|
|
break;
|
|
#ifdef CONFIG_I2C_TARGET
|
|
/* NOTE: Don't disable interrupt here because slave mode relies on */
|
|
/* for passive transfer in ISR. */
|
|
|
|
/* Slave Transmit */
|
|
case S_TRAN_ADDR_ACK: /* Slave Transmit Address ACK */
|
|
case ADDR_TRAN_ARB_LOST: /* Slave Transmit Arbitration Lost */
|
|
data->slave_xfer.slave_addressed = true;
|
|
if (slave_callbacks->read_requested(slave_config, &data_byte) == 0) {
|
|
/* Non-last data byte */
|
|
I2C_SET_DATA(i2c_base, data_byte);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
} else {
|
|
/* Go S_TRAN_LAST_DATA_ACK on error */
|
|
I2C_SET_DATA(i2c_base, 0xFF);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
}
|
|
break;
|
|
case S_TRAN_DATA_ACK: /* Slave Transmit Data ACK */
|
|
if (slave_callbacks->read_processed(slave_config, &data_byte) == 0) {
|
|
/* Non-last data byte */
|
|
I2C_SET_DATA(i2c_base, data_byte);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
} else {
|
|
/* Go S_TRAN_LAST_DATA_ACK on error */
|
|
I2C_SET_DATA(i2c_base, 0xFF);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
}
|
|
break;
|
|
case S_TRAN_DATA_NACK: /* Slave Transmit Data NACK */
|
|
case S_TRAN_LAST_DATA_ACK: /* Slave Transmit Last Data ACK */
|
|
/* Go slave end */
|
|
data->slave_xfer.slave_addressed = false;
|
|
slave_callbacks->stop(slave_config);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
break;
|
|
/* Slave Receive */
|
|
case S_RECE_DATA_ACK: /* Slave Receive Data ACK */
|
|
data_byte = I2C_GET_DATA(i2c_base);
|
|
if (slave_callbacks->write_received(slave_config, data_byte) == 0) {
|
|
/* Write OK, ACK next data byte */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
} else {
|
|
/* Write FAILED, NACK next data byte */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
}
|
|
break;
|
|
case S_RECE_DATA_NACK: /* Slave Receive Data NACK */
|
|
/* Go slave end */
|
|
data->slave_xfer.slave_addressed = false;
|
|
slave_callbacks->stop(slave_config);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
break;
|
|
case S_RECE_ADDR_ACK: /* Slave Receive Address ACK */
|
|
case S_RECE_ARB_LOST: /* Slave Receive Arbitration Lost */
|
|
data->slave_xfer.slave_addressed = true;
|
|
if (slave_callbacks->write_requested(slave_config) == 0) {
|
|
/* Write ready, ACK next byte */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
} else {
|
|
/* Write not ready, NACK next byte */
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
|
}
|
|
break;
|
|
case S_REPEAT_START_STOP: /* Slave Transmit/Receive Repeat Start or Stop */
|
|
/* Go slave end */
|
|
data->slave_xfer.slave_addressed = false;
|
|
slave_callbacks->stop(slave_config);
|
|
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
|
break;
|
|
#endif /* CONFIG_I2C_TARGET */
|
|
|
|
case BUS_RELEASED: /* Bus Released */
|
|
/* Ignore the interrupt raised by BUS_RELEASED. */
|
|
break;
|
|
default:
|
|
__ASSERT(false, "Uncaught I2C FSM state");
|
|
m_numaker_i2c_master_xfer_end(dev, true);
|
|
}
|
|
}
|
|
|
|
static int i2c_numaker_init(const struct device *dev)
|
|
{
|
|
const struct i2c_numaker_config *config = dev->config;
|
|
struct i2c_numaker_data *data = dev->data;
|
|
int err = 0;
|
|
struct numaker_scc_subsys scc_subsys;
|
|
|
|
/* Validate this module's reset object */
|
|
if (!device_is_ready(config->reset.dev)) {
|
|
LOG_ERR("reset controller not ready");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Clean mutable context */
|
|
memset(data, 0x00, sizeof(*data));
|
|
|
|
k_sem_init(&data->lock, 1, 1);
|
|
k_sem_init(&data->master_xfer.xfer_sync, 0, 1);
|
|
|
|
SYS_UnlockReg();
|
|
|
|
memset(&scc_subsys, 0x00, sizeof(scc_subsys));
|
|
scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
|
|
scc_subsys.pcc.clk_modidx = config->clk_modidx;
|
|
scc_subsys.pcc.clk_src = config->clk_src;
|
|
scc_subsys.pcc.clk_div = config->clk_div;
|
|
|
|
/* Equivalent to CLK_EnableModuleClock() */
|
|
err = clock_control_on(config->clkctrl_dev, (clock_control_subsys_t) &scc_subsys);
|
|
if (err != 0) {
|
|
goto cleanup;
|
|
}
|
|
/* Equivalent to CLK_SetModuleClock() */
|
|
err = clock_control_configure(config->clkctrl_dev,
|
|
(clock_control_subsys_t) &scc_subsys,
|
|
NULL);
|
|
if (err != 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Configure pinmux (NuMaker's SYS MFP) */
|
|
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
|
|
if (err != 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Reset I2C to default state, same as BSP's SYS_ResetModule(id_rst) */
|
|
reset_line_toggle_dt(&config->reset);
|
|
|
|
err = i2c_numaker_configure(dev, I2C_MODE_CONTROLLER | i2c_map_dt_bitrate(config->bitrate));
|
|
if (err != 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
config->irq_config_func(dev);
|
|
|
|
cleanup:
|
|
|
|
SYS_LockReg();
|
|
return err;
|
|
}
|
|
|
|
static const struct i2c_driver_api i2c_numaker_driver_api = {
|
|
.configure = i2c_numaker_configure,
|
|
.get_config = i2c_numaker_get_config,
|
|
.transfer = i2c_numaker_transfer,
|
|
#ifdef CONFIG_I2C_TARGET
|
|
.target_register = i2c_numaker_slave_register,
|
|
.target_unregister = i2c_numaker_slave_unregister,
|
|
#endif
|
|
#ifdef CONFIG_I2C_RTIO
|
|
.iodev_submit = i2c_iodev_submit_fallback,
|
|
#endif
|
|
.recover_bus = i2c_numaker_recover_bus,
|
|
};
|
|
|
|
#define I2C_NUMAKER_INIT(inst) \
|
|
PINCTRL_DT_INST_DEFINE(inst); \
|
|
\
|
|
static void i2c_numaker_irq_config_func_##inst(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(inst), \
|
|
DT_INST_IRQ(inst, priority), \
|
|
i2c_numaker_isr, \
|
|
DEVICE_DT_INST_GET(inst), \
|
|
0); \
|
|
\
|
|
irq_enable(DT_INST_IRQN(inst)); \
|
|
} \
|
|
\
|
|
static const struct i2c_numaker_config i2c_numaker_config_##inst = { \
|
|
.i2c_base = (I2C_T *) DT_INST_REG_ADDR(inst), \
|
|
.reset = RESET_DT_SPEC_INST_GET(inst), \
|
|
.clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \
|
|
.clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \
|
|
.clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \
|
|
.clkctrl_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))),\
|
|
.irq_n = DT_INST_IRQN(inst), \
|
|
.irq_config_func = i2c_numaker_irq_config_func_##inst, \
|
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
|
.bitrate = DT_INST_PROP(inst, clock_frequency), \
|
|
}; \
|
|
\
|
|
static struct i2c_numaker_data i2c_numaker_data_##inst; \
|
|
\
|
|
I2C_DEVICE_DT_INST_DEFINE(inst, \
|
|
i2c_numaker_init, \
|
|
NULL, \
|
|
&i2c_numaker_data_##inst, \
|
|
&i2c_numaker_config_##inst, \
|
|
POST_KERNEL, \
|
|
CONFIG_I2C_INIT_PRIORITY, \
|
|
&i2c_numaker_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_NUMAKER_INIT);
|