I3C

Overview

The Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient bidirectional 2-wire synchronous serial bus protocol developed by the Mobile Industry Processor Interface (MIPI) Alliance.

I3C is backward compatible with legacy Inter-Integrated Circuit (I2C). Moreover, it provides the in-band interrupt (IBI) function and supports hot-join of I3C devices. This eliminates the need for adding an extra interrupt line to implement interrupts in I2C.

The I2C device, I3C slave device, and I3C secondary master device can coexist on the I3C bus.

The I3C APIs provide a set of common functions for I3C transfer, including:

  • Opening and closing an I3C controller
  • Obtaining and setting I3C controller parameters
  • Performing custom I3C message transfer by using a message array
  • Requesting and releasing an IBI

Figure 1 shows the I3C physical connection. Figure 1 I3C physical connection

Available APIs

Table 1 I3C driver APIs

Category

API

Description

I3C controller management

I3cOpen

Opens an I3C controller.

I3cClose

Closes an I3C controller.

I3C transfer

I3cTransfer

Customizes an I3C transfer.

I3C controller configuration

I3cSetConfig

Sets an I3C controller.

I3cGetConfig

Obtains the I3C controller configuration.

I3C IBI

I3cRequestIbi

Requests an IBI.

I3cFreeIbi

Releases an IBI.

NOTE

All functions described in this document can be called only in the kernel space.

Usage Guidelines

How to Use

Figure 2 shows how I3C works.

Figure 2 How I3C works

Opening an I3C Controller

Before I3C communication, call I3cOpen to open an I3C controller.

DevHandle I3cOpen(int16_t number);

Table 2 Description of I3cOpen

Parameter

Description

number

I3C controller ID.

Return Value

Description

NULL

Failed to open the I3C controller.

Controller handle

Handle of the I3C controller opened.

The following example opens I3C controller 1 of the eight I3C controllers (numbered from 0 to 7) in the system.

DevHandle i3cHandle = NULL; /* I3C controller handle. /

/* Open I3C controller 1. */
i3cHandle = I3cOpen(1);
if (i3cHandle == NULL) {
    HDF_LOGE("I3cOpen: failed\n");
    return;
}

Performing I3C Communication

Call I3cTransfer() to transfer messages.

int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode);

Table 3 Description of I3cTransfer

Parameter

Description

handle

I3C controller handle.

msgs

Pointer to the message structure array of the data to be transmitted.

count

Length of the message array.

mode

Transmission mode, where the value 0 indicates the I2C mode, 1 indicates the I3C mode, and 2 indicates transmission of the Common Command Code (CCC).

Return Value

Description

Positive integer

Number of message structures successfully transferred.

Negative number

The operation failed.

The I3C messages are of the I3cMsg type. Each message structure indicates a read or write operation. A message array is used to perform multiple read or write operations.

int32_t ret;
uint8_t wbuff[2] = { 0x12, 0x13 };
uint8_t rbuff[2] = { 0 };
struct I3cMsg msgs[2]; /* Custom message array for transfer. */
msgs[0].buf = wbuff;    /* Data to write. */
msgs[0].len = 2;        /* Length of the data to write. */
msgs[0].addr = 0x3F; /* Address of the device to which the data is written. */
msgs[0].flags = 0;      /* Transfer flag. A write operation is performed by default. */
msgs[1].buf = rbuff;    /* Data to read. */
msgs[1].len = 2;        /* Length of the data to read. */
msgs[1].addr = 0x3F;    /* Address of the device from which the data is read. */
msgs[1].flags = I3C_FLAG_READ /* I3C_FLAG_READ is set. */
/* Transfer two messages in I2C mode. */
ret = I3cTransfer(i3cHandle, msgs, 2, I2C_MODE);
if (ret != 2) {
    HDF_LOGE("I3cTransfer: failed, ret %d\n", ret);
    return;
}

Caution

  • The device address in the I3cMsg structure does not contain the read/write flag bit. The read/write information is passed by the read/write control bit in the member variable flags.
  • The I3cTransfer() function does not limit the number of message structures or the length of data in each message structure. The I3C controller determines these two limits.
  • Using I3cTransfer() may cause the system to sleep. Do not call it in the interrupt context.

Obtaining the I3C Controller Configuration

int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config);

Table 4 Description of I3cGetConfig

Parameter

Description

handle

I3C controller handle.

config

Pointer to the I3C controller configuration.

Return Value

Description

0

The operation is successful.

Negative number

Failed to obtain the I3C controller configuration.

Configuring an I3C Controller

int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config);

Table 5 Description of I3cSetConfig

Parameter

Description

handle

I3C controller handle.

config

Pointer to the I3C controller configuration.

Return Value

Description

0

The operation is successful.

Negative number

Failed to configure the I3C controller.

Requesting an IBI

int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload);

Table 6 Description of I3cRequestIbi

Parameter

Description

handle

I3C controller handle.

addr

I3C device address.

func

Callback used to return the IBI.

payload

IBI payload.

Return Value

Description

0

The operation is successful.

Negative number

Failed to request the IBI.

static int32_t TestI3cIbiFunc(DevHandle handle, uint16_t addr, struct I3cIbiData data)
{
    (void)handle;
    (void)addr;
    HDF_LOGD("%s: %.16s", __func__, (char *)data.buf);

    return 0;
}

int32_t I3cTestRequestIbi(void)
{
    DevHandle i3cHandle = NULL;
    int32_t ret;

    /* Open an I3C controller. */
    i3cHandle = I3cOpen(1);
    if (i3cHandle == NULL) {
        HDF_LOGE("I3cOpen: failed\n");
    return;
}
    ret = I3cRequestIbi(i3cHandle, 0x3F, TestI3cIbiFunc, 16);
    if (ret != 0) {
        HDF_LOGE("%s: Requset IBI failed!", __func__);
        return -1;
    }

    I3cClose(i3cHandle);
    HDF_LOGD("%s: Done", __func__);

    return 0;
}

Releasing an IBI

int32_t I3cFreeIbi(DevHandle handle, uint16_t addr);

Table 7 Description of I3cFreeIbi

Parameter

Description

handle

I3C controller handle.

addr

I3C device address.

Return Value

Description

0

The operation is successful.

Negative number

Failed to release the IBI.

I3cFreeIbi(i3cHandle, 0x3F); /* Release an IBI. */

Closing an I3C controller

Call I3cClose() to close the I3C controller after the communication is complete.

void I3cClose(DevHandle handle); 

Table 8 Description of I3cClose

Parameter

Description

handle

I3C controller handle.

I3cClose(i3cHandle); /* Close an I3C controller. */

Example<a name="section12"">

This following example shows how to use I3C APIs to manage an I3C device on a Hi3516D V300 development board.

Because the Hi3516D V300 SoC has no I3C controller, this example describes how to perform simple transfer operations on a virtual driver on a Hi3516D V300. The basic information is as follows:

  • SoC: Hi3516D V300

  • Virtual: The I3C address is 0x3f, and the register bit width is 1 byte.

  • The virtual I3C devices are connected to virtual I3C controllers 18 and 19.

Perform simple I3C transfer to test whether the I3C channels are normal.

The sample code is as follows:

#include "i3c_if.h"          /* Header file for I3C standard APIs */
#include "i3c_ccc.h"         /* Header file for I3C CCC */
#include "hdf_log.h"         /* Header file for log APIs */
##include "osal_io.h"         /* Header file for I/O read and write APIs */
##include "osal_time.h"       /* Header file for delay and sleep APIs */

/* Define a device structure to hold information. */
struct TestI3cDevice {
    uint16_t busNum;              /* I3C bus number */ 
    uint16_t addr;                /* I3C device address */ 
    uint16_t regLen;              /* Register bit width */
    DevHandle i3cHandle;          /* I3C controller handle */ 
};

/* Use I3cTransfer to encapsulate a register read/write helper function. Use flag to indicate a read or write operation. */
static int TestI3cReadWrite(struct TestI3cDevice *testDevice, unsigned int regAddr,
    unsigned char *regData, unsigned int dataLen, uint8_t flag)
{
    int index = 0;
    unsigned char regBuf[4] = {0};
    struct I3cMsg msgs[2] = {0};

    /* Perform length adaptation for the single- or dual-byte register. */
    if (testDevice->regLen == 1) { 
        regBuf[index++] = regAddr & 0xFF;
    } else {
        regBuf[index++] = (regAddr >> 8) & 0xFF;
        regBuf[index++] = regAddr & 0xFF;
    }

    /* Fill in the I3cMsg message structure. */
    msgs[0].addr = testDevice->addr;
    msgs[0].flags = 0; /* The flag 0 indicates a write operation. */
    msgs[0].len = testDevice->regLen;
    msgs[0].buf = regBuf;

    msgs[1].addr = testDevice->addr;
    msgs[1].flags = (flag == 1) ? I3C_FLAG_READ: 0; /* Add a read flag bit to read data. */
    msgs[1].len = dataLen;
    msgs[1].buf = regData;

    if (I3cTransfer(testDevice->i3cHandle, msgs, 2, I2C_MODE) != 2) {
        HDF_LOGE("%s: i3c read err", __func__);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

/* Read data from the register. */
static inline int TestI3cReadReg(struct TestI3cDevice *testDevice, unsigned int regAddr,
    unsigned char *regData, unsigned int dataLen)
{
    return TestI3cReadWrite(testDevice, regAddr, regData, dataLen, 1);
}

/* Write data to the register. */
static inline int TestI3cWriteReg(struct TestI3cDevice *testDevice, unsigned int regAddr,
    unsigned char *regData, unsigned int dataLen)
{
    return TestI3cReadWrite(testDevice, regAddr, regData, dataLen, 0);
}

/* Main entry of I3C routines. */
static int32_t TestCaseI3c(void)
{
    int32_t i;
    int32_t ret;
    unsigned char bufWrite[7] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xA, 0xB, 0xC };
    unsigned char bufRead[7] = {0};
    static struct TestI3cDevice testDevice;

    /* Initialize device information. */
    testDevice.busNum = 18;
    testDevice.addr = 0x3F;
    testDevice.regLen = 1;
    testDevice.i3cHandle = NULL;

    /* Open an I3C controller. */
    testDevice.i3cHandle = I3cOpen(testDevice.busNum);
    if (testDevice.i3cHandle == NULL) {
        HDF_LOGE("%s: Open I3c:%u fail!", __func__, testDevice.busNum);
        return -1;
    }

    /* Write 7-byte data continuously to the device whose address is 0x3F. */
    ret = TestI3cWriteReg(&testDevice, 0x3F, bufWrite, 7);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: test i3c write reg fail!:%d", __func__, ret);
        I3cClose(testDevice.i3cHandle);
        return -1;
    }
    OsalMSleep(10);

    /* Read 7-byte data continuously from the device whose address is 0x3F. */
    ret = TestI3cReadReg(&testDevice, 0x3F, bufRead, 7);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: test i3c read reg fail!:%d", __func__, ret);
        I3cClose(testDevice.i3cHandle);
        return -1;
    }
    HDF_LOGI("%s: test i3c write&read reg success!", __func__);

    /* Close the I3C controller. */
    I3cClose(testDevice.i3cHandle);

    return 0;
}