1. I2C Bus Driver / I2C Adapter Driver
-
This section focuses on the structures: i2c_adapter and i2c_algorithm, which are defined in
include/linux/i2c.h. -
The Linux kernel abstracts the SOC’s I2C controller (adapter) as i2c_adapter; i2c_algorithm defines the methods for communication between the I2C controller and IIC devices.
-
The main task of the I2C bus driver, or the I2C adapter driver, is to initialize the i2c_adapter structure variable and then set the master_xfer function in i2c_algorithm. After completion, it registers the configured i2c_adapter with the system using either
i2c_add_numbered_adapterori2c_add_adapter. -
To remove an I2C adapter, the
i2c_del_adapterfunction is used.
In the i2c_adapter structure, there is a very important member: i2c_algorithm. This variable contains the API functions for the I2C controller to access I2C devices, which need to be implemented by the I2C controller developers.
The i2c_algorithm structure contains a master_xfer function. This function is the one that ultimately performs data transmission and reception for the I2C controller.
1.1 Analysis of the i2c-imx.c File
-
The I2C adapter driver file for I.MX6U is
drivers/i2c/busses/i2c-imx.c.
For device tree nodes, the focus is generally on the compatible property value, as it allows you to find the corresponding driver file in the Linux source code.
-
From the
i2c-imx.cfile, it can be seen that although the I2C bus provides a bus driver framework for other devices, the I2C adapter is a platform driver.
This is not a contradiction!
The I2C bus is responsible for managing the I2C devices mounted on the I2C controller, but the I2C bus framework (
i2c-core) must rely on an already initialized I2C controller (adapter) to function, and the platform is used to complete the initialization of the I2C adapter (controller).
The I2C controller and the I2C adapter are the same thing!
In hardware, it is called the I2C controller.
In the Linux kernel, the software instance representing this hardware is called the I2C adapter (
i2c_adapter).
1.2 Analysis of the i2c_imx_probe Function (in i2c-imx.c)
Once the device and driver match successfully, the i2c_imx_probe function will execute to complete the initialization of the I2C adapter.
-
Call
platform_get_irqto obtain the interrupt number. -
Call
platform_get_resourceto get the physical base address of the I2C1 controller registers from the device tree. -
Then, use
devm_ioremap_resourceto perform memory mapping and obtain the virtual address. -
Initialize i2c_adapter. Here, set the algo member variable in i2c_adapter to i2c_imx_algo, which sets the i2c_algorithm.
NXP uses the imx_i2c_struct structure to represent the I2C controller of the I.MX series SOC, which contains a member variable adapter.
i2c_imx_algo contains the communication function master_xfer between the I2C1 adapter and the I2C devices.
-
Register the I2C controller interrupt, with the interrupt service function being i2c_imx_isr.
-
Set the default I2C frequency to IMX_I2C_BIT_RATE=100KHz. If the device tree node sets the clock-frequency property, the I2C frequency will use the value of the clock-frequency property.
-
Set the I2CR and I2SR registers controlled by I2C1.
-
Call
i2c_add_numbered_adapterto register the i2c_adapter with the Linux kernel. -
Request DMA.
It seems that the I.MX I2C adapter driver uses DMA.
-
Summary: The main tasks of the i2c_imx_probe function are
-
① Initialize i2c_adapter, set i2_algorithm to i2c_imx_algo, and finally register i2c_adapter with the Linux kernel.
-
② Initialize the relevant registers of the I2C1 controller.
1.3 Analysis of the i2c_imx_xfer Function
static struct i2c_algorithm i2c_imx_algo = { .master_xfer = i2c_imx_xfer, .functionality = i2c_imx_func, };
Ultimately, communication with the I2C device is completed through the i2c_imx_xfer function.
-
Call i2c_imx_start to initiate I2C communication.
-
If reading data from the I2C device, call i2c_imx_read.
-
Otherwise, call i2c_imx_dma_write to write data to the I2C device. If not using DMA, use i2c_imx_write to complete the write operation.
-
After I2C communication is complete, call i2c_imx_stop to stop I2C communication.
.
2. I2C Device Driver
-
The I2C device driver focuses on two data structures: i2c_client and i2c_driver, both defined in
include/linux/i2c.h.
i2c_client describes device information; i2c_driver describes driver content.
2.1 i2c_client Structure
-
We do not need to create i2c_client. The kernel will allocate an i2c_client for each I2C device it reads from the device tree. One device corresponds to one i2c_client.
struct i2c_client {
unsigned short flags; /* Flags */
unsigned short addr; /* Chip address, 7 bits, address exists in the low 7 bits */
char name[I2C_NAME_SIZE]; /* Name */
struct i2c_adapter *adapter; /* Corresponding I2C adapter */
struct device dev; /* Device structure */
int irq; /* Interrupt */
struct list_head detected;
......
};
2.2 i2c_driver Structure
-
This structure needs to be created by us. Key work!!!
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
-
When the I2C device and driver match successfully, the probe function will execute.
-
If using the device tree, the device_driver structure needs to set the of_match_table member variable, which is the compatible property of the driver.
-
The id_table is the traditional device matching ID table that does not use the device tree.
-
After building, use the
i2c_register_driverori2c_add_driverfunction to register this i2c_driver with the Linux kernel.
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
-
i2c_add_driver is actually a macro definition, and the actual function to register the I2C device is
i2c_register_driver. -
i2c_register_driver registers the I2C device driver with the driver core and initializes the relevant data structures, where the data structure type passed in is i2c_driver.
-
To unregister, use the
i2c_del_driverfunction.
2.3 Detailed Writing Process of the I2C Device Driver
2.3.1 Description of I2C Device Information
1. When not using the device tree
-
Use the i2c_board_info structure to describe the information of a specific I2C device.
Among them, the type and addr member variables must be set. One is the name of the I2C device, and the other is the device address of the I2C device.
2. When using the device tree
-
Directly create the corresponding node to describe the I2C device information.
The I2C device is mounted under the I2C controller, and the corresponding device node is added under that controller.
2.3.2 I2C Device Data Transmission and Reception Process
-
Use the
i2c_transferfunction to perform read and write operations on the I2C device registers. Thei2c_transferfunction ultimately calls the master_xfer function in the i2c_algorithm of the I2C adapter, which for I.MX6U is the i2c_imx_xfer function. -
The Linux kernel uses the i2c_msg structure to describe a message.
Data transmission and reception in I2C is essentially the transmission of messages.
-
Before sending data using the
i2c_transferfunction, the i2c_msg must be constructed.
2.4 Analysis of the i2c_transfer Function
Located in
/driver/i2c/i2c-core.c.
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret; // Save transmission result
/*
* There are some weaknesses in the fault reporting model:
* 1. When we encounter an error after receiving N bytes from the slave device, there is no way to report "N".
* 2. When we receive NAK after transmitting N bytes to the slave device, there is no way to report "N",
* nor can we allow the master device to continue executing the remaining messages (if that is an appropriate response).
* 3. When "num" is two, we successfully complete the first message but encounter an error midway through the second message,
* it is unclear whether to report it as one (discarding the status of the second message) or errno (discarding the status of the first message).
*/
// If the adapter supports master_xfer operation, use that operation to transfer messages
if (adap->algo->master_xfer) {
#ifdef DEBUG
// Print information for each message for debugging
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
// If the current context does not allow sleeping, try to lock the I2C adapter
// If the adapter is already locked, return -EAGAIN
if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_adapter(adap);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
// Otherwise, lock the I2C adapter
i2c_lock_adapter(adap);
}
// Call __i2c_transfer to perform the actual message transfer
ret = __i2c_transfer(adap, msgs, num);
// Unlock the I2C adapter
i2c_unlock_adapter(adap);
return ret;
} else {
// If the adapter does not support master_xfer operation, return unsupported error
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
-
The i2c_transfer function itself does not have the ability to control hardware; the master_xfer is the function that actually drives the hardware.
The master_xfer is written by the original factory engineers, and generally, it can be indirectly called using the i2c_transfer function.
-
The i2c_transfer function uses a locking mechanism to manage access to the I2C bus, preventing race conditions between multiple operations.
-
in_atomic() and irqs_disabled() are used to check whether the current context is atomic or whether interrupts are disabled. In these two cases, the kernel generally does not allow operations that may cause context switches.
.
3. I2C Device and Driver Matching Process
-
The matching process between I2C devices and drivers is completed by the I2C core,
drivers/i2c/i2c-core.cis the core part of I2C, providing some hardware-independent API functions. -
The matching process between devices and drivers is also completed by the I2C bus, with the data structure of the I2C bus being i2c_bus_type, defined in
drivers/i2c/i2c-core.c.
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
-
.match is the device and driver matching function of the I2C bus.
3.1 Analysis of the i2c_device_match Function
This function contains three types of matching:
-
The of_driver_match_device function is used to complete the matching of device tree devices and drivers. It compares the compatible property of the I2C device node with the compatible property in of_device_id. If they are equal, it indicates that the I2C device and driver match.
of_driver_match_device->of_match_device
-
The acpi_driver_match_device function is used for ACPI-based matching.
-
The i2c_match_id function is used for the traditional, non-device tree I2C device and driver matching process. It compares the I2C device name with the name field of i2c_device_id. If they are equal, it indicates that the I2C device and driver match.