I2c controller data transceiver function example

1. Example of calling device driver data sending and receiving functions

Pay attention to I2C_M_RD. This flag must be assigned when reading. The data sending and receiving functions implemented by the controller must be used.
static int ap3216c_read_regs(struct ap3216c_dev *dev,u8 reg,
                                void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->private_data;
 
    /*msg[0] Send the first address of the register to be read*/
    msg[0].addr = client->addr;/*slave address, which is ap3216c*/
    msg[0].flags = 0;/* Mark to send data */
    msg[0].buf = & amp;reg;/*The data to be sent, that is, the register address*/
    msg[0].len = 1;/*The length of the register address to be sent is 1*/
    /*msg[1]Read data*/
    msg[1].addr = client->addr;/*slave address, which is ap3216c*/
    msg[1].flags = I2C_M_RD;/*Indicates reading data*/
    msg[1].buf = val;/*Received slave address*/
    msg[1].len = len;/*length of register data to be read*/
 
    ret = i2c_transfer(client->adapter,msg,2);
    if(ret==2){
        ret = 0;
    }else{
        printk("i2c rd failed=%d reg= x len=%d\\
",ret, reg, len);
        ret = -EREMOTEIO;
    }
    return ret;

2. i2c_transfer function description

i2c_transfer–>__i2c_transfer–>master_xfer (function registered when the i2c controller driver is initialized)

3. How master_xfer implements data sending and receiving

The implementation of each chip is different and can be roughly divided into two types
1. Write data. The master_xfer function directly writes the register to send data.
Read data, configure the register to enable interrupts, and receive data in the interrupt processing function
2. Writing data and reading data are done through master_xfer. This function sets the interrupt flag bit, enables interrupts, and then the registered interrupt processing function will read and write data.

The following is an example of the second method

static const struct i2c_algorithm bsp_i2c_algo = {
.master_xfer = bsp_i2c_xfer,
.functionality = bsp_i2c_func,
};
The function actually called by bsp_i2c_xfer
static int bsp_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct bsp_i2c_dev *i2c = i2c_get_adapdata(adap);
int status = -EINVAL;
unsigned long flags;

if (msgs == NULL || (num <= 0)) {
dev_err(i2c->dev, "msgs == NULL || num <= 0, Invalid argument!\\
");
return -EINVAL;
}

spin_lock_irqsave( & amp;i2c->lock, flags);

i2c->msg = msgs;//Assign msg, which will be used later. It is actually used in the i2c interrupt processing function
i2c->msg_num = num;
i2c->msg_idx = 0;

while (i2c->msg_idx < i2c->msg_num) {
#if defined(CONFIG_EDMAC)
if ((i2c->msg->len >= CONFIG_DMA_MSG_MIN_LEN) & amp; & amp;
(i2c->msg->len <= CONFIG_DMA_MSG_MAX_LEN)) {
status = bsp_i2c_dma_xfer_one_msg(i2c);
if(status)
break;
} else if (i2c->irq >= 0) {
#else
if (i2c->irq >= 0) {
#endif
spin_unlock_irqrestore( & amp;i2c->lock, flags);
status = bsp_i2c_interrupt_xfer_one_msg(i2c);//Mainly this function
spin_lock_irqsave( & amp;i2c->lock, flags);
if(status)
break;
} else {
status = bsp_i2c_polling_xfer_one_msg(i2c);
if(status)
break;
}
i2c->msg + + ;
i2c->msg_idx + + ;
}

if (!status || i2c->msg_idx > 0)
status = i2c->msg_idx;

spin_unlock_irqrestore( & amp;i2c->lock, flags);
return status;
}
static int bsp_i2c_interrupt_xfer_one_msg(struct bsp_i2c_dev *i2c)
{
int status;
struct i2c_msg *msg = i2c->msg;
unsigned long timeout;
unsigned long flags;

dev_dbg(i2c->dev, "[%s,%d]msg->flags=0x%x, len=0x%x\\
",
__func__, __LINE__, msg->flags, msg->len);

reinit_completion( & amp;i2c->msg_complete);
i2c->msg_buf_ptr = 0;
i2c->status = -EIO;

spin_lock_irqsave( & amp;i2c->lock, flags);
check_i2c_send_complete(i2c);
bsp_i2c_enable(i2c);
bsp_i2c_clr_irq(i2c);
if (msg->flags & amp; I2C_M_RD)//I2C_M_RD is used here to determine whether to set the read or write interrupt flag
bsp_i2c_cfg_irq(i2c, INTR_USE_MASK & amp; ~INTR_TX_MASK);
else
bsp_i2c_cfg_irq(i2c, INTR_USE_MASK & amp; ~INTR_RX_MASK);

bsp_i2c_set_addr(i2c);
bsp_i2c_cfg_cmd(i2c);
bsp_i2c_start_cmd(i2c);
spin_unlock_irqrestore( & amp;i2c->lock, flags);
//We are waiting for the completion amount here. Only when the data transmission and reception is completed or times out, it will continue to go down and return. The completion amount is released in the interrupt processing function.
timeout = wait_for_completion_timeout( & amp;i2c->msg_complete,
I2C_IRQ_TIMEOUT);

spin_lock_irqsave( & amp;i2c->lock, flags);
if (timeout == 0) {
bsp_i2c_disable_irq(i2c, INTR_ALL_MASK);
status = -EIO;
dev_err(i2c->dev, "%s timeout\\
",
msg->flags & amp; I2C_M_RD ? "rx" : "tx");
} else {
status = i2c->status;
}

bsp_i2c_disable(i2c);

spin_unlock_irqrestore( & amp;i2c->lock, flags);
return status;
}
//Interrupt processing function registered in the probe function
status = devm_request_irq( & amp;pdev->dev, i2c->irq, bsp_i2c_isr,
IRQF_SHARED, dev_name( & amp;pdev->dev), i2c);
static irqreturn_t bsp_i2c_isr(int irq, void *dev_id)
{
struct bsp_i2c_dev *i2c = dev_id;
unsigned int irq_status;
struct i2c_msg *msg = i2c->msg;

spin_lock( & amp;i2c->lock);

irq_status = bsp_i2c_clr_irq(i2c);
dev_dbg(i2c->dev, "%s RIS: 0x%x\\
", __func__, irq_status);

if (!irq_status) {
dev_dbg(i2c->dev, "no irq\\
");
goto end;
}

if (irq_status & INTR_ABORT_MASK) {
dev_err(i2c->dev, "irq handle abort, RIS: 0x%x\\
",
irq_status);
i2c->status = -EIO;
bsp_i2c_disable_irq(i2c, INTR_ALL_MASK);

complete( & amp;i2c->msg_complete);
goto end;
}
//After the interrupt is enabled, the interrupt processing function will be called, where the actual reading and writing data operations will be done.
if (msg->flags & amp; I2C_M_RD) {
while ((readl(i2c->base + BSP_I2C_STAT) & STAT_RXF_NOE_MASK)
& amp; & amp; (i2c->msg_buf_ptr < msg->len)) {
msg->buf[i2c->msg_buf_ptr] =
readl(i2c->base + BSP_I2C_RXF);
i2c->msg_buf_ptr + + ;
}
} else {
while ((readl(i2c->base + BSP_I2C_STAT) & STAT_TXF_NOF_MASK)
& amp; & amp; (i2c->msg_buf_ptr < msg->len)) {
writel(msg->buf[i2c->msg_buf_ptr],
i2c->base + BSP_I2C_TXF);
i2c->msg_buf_ptr + + ;
}
}

if (i2c->msg_buf_ptr >= msg->len)
bsp_i2c_disable_irq(i2c, INTR_TX_MASK | INTR_RX_MASK);

if (irq_status & INTR_CMD_DONE_MASK) {
dev_dbg(i2c->dev, "cmd done\\
");
i2c->status = 0;
bsp_i2c_disable_irq(i2c, INTR_ALL_MASK);
//The completion amount is released here. The bsp_i2c_interrupt_xfer_one_msg function then goes down and returns to complete the data sending and receiving.
complete( & amp;i2c->msg_complete);
}

end:
spin_unlock( & amp;i2c->lock);

return IRQ_HANDLED;
}