In-Depth Understanding of the Linux I2C Subsystem: From Hardware Protocol to Driver Practice

5. Composition of the I2C Subsystem

In-Depth Understanding of the Linux I2C Subsystem: From Hardware Protocol to Driver Practice

The kernel space can be divided into: i2c device drivers, i2c core, and i2c bus drivers.

  • i2c core: Implementation of the framework; provides methods for registering and unregistering i2c bus drivers and device drivers; the upper-level code for i2c communication methods (algorithm) that is independent of specific adapters, as well as the upper-level code for probing devices and detecting device addresses. This part of the work is completed by kernel developers.

  • i2c bus driver: Implementation of specific controllers; the i2c bus driver is the implementation of the adapter side in the i2c hardware architecture, detailing how to operate the i2c module. The adapter can be controlled by the CPU or even directly integrated into the CPU (algorithm driver, adapter driver).

  • i2c device: Implementation of the device side in the i2c hardware architecture, such as EEPROM devices on the board. Devices are generally connected to the i2c adapter controlled by the CPU, exchanging data with the CPU through the i2c adapter. (chip drivers, including various types such as RTC, EEPROM, I/O expander, hardware monitoring, sound, video, etc.).

Terminology Explanation:

  • i2c-adapter: Refers to the actual I2C controller of the CPU (e.g., I2C0, I2C1);

  • i2c-device: Refers to the slave devices on the I2C bus (e.g., a certain EEPROM, a certain touchscreen);

  • i2c-algorithm: Here it refers to a set of corresponding communication methods for i2c devices.

6. I2C Bus Driver

The I2C bus driver focuses on the I2C adapter (i.e., the I2C interface controller of the SoC) driver, which involves two important data structures: i2c_adapter and i2c_algorithm. The I2C subsystem abstracts the SoC’s I2C adapter (controller) into an i2c_adapter structure, which is defined in the include/linux/i2c.h file, as follows:

/* * i2c_adapter is the structure used to identify a physical i2c bus along * with the access algorithms necessary to access it. */struct i2c_adapter {struct module *owner;unsigned int class; /* classes to allow probing for */const struct i2c_algorithm *algo; /* the algorithm to access the bus */void *algo_data; /* data fields that are valid for all devices */const struct i2c_lock_operations *lock_ops;struct rt_mutex bus_lock;struct rt_mutex mux_lock;int timeout; /* in jiffies */int retries;struct device dev; /* the adapter device */unsigned long locked_flags; /* owned by the I2C core */#define I2C_ALF_IS_SUSPENDED 0#define I2C_ALF_SUSPEND_REPORTED 1int nr;char name[48];struct completion dev_released;struct mutex userspace_clients_lock;struct list_head userspace_clients;struct i2c_bus_recovery_info *bus_recovery_info;const struct i2c_adapter_quirks *quirks;struct irq_domain *host_notify_domain;struct regulator *bus_regulator;};

The pointer variable algo of type i2c_algorithm must provide read and write API functions for an I2C adapter, which device driver programs can use to perform read and write operations. i2c_algorithm is the method for communication between the I2C adapter and IIC devices.

struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);int (*master_xfer_atomic)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);/* To determine what the adapter supports */u32 (*functionality)(struct i2c_adapter *adap);#if IS_ENABLED(CONFIG_I2C_SLAVE)int (*reg_slave)(struct i2c_client *client);int (*unreg_slave)(struct i2c_client *client);#endif}

1. master_xfer is the transfer function of the I2C adapter, which can be used to complete communication with IIC devices.

2. 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 the i2c_algorithm. After completion, the configured i2c_adapter is registered with the I2C subsystem using either i2c_add_numbered_adapter or i2c_add_adapter, whose prototypes are as follows:

int i2c_add_adapter(struct i2c_adapter *adapter)int i2c_add_numbered_adapter(struct i2c_adapter *adap)

The difference between these two functions is that i2c_add_adapter dynamically allocates a bus number, while i2c_add_numbered_adapter specifies a static bus number.

To delete an I2C adapter, use the i2c_del_adapter function, whose prototype is as follows:

void i2c_del_adapter(struct i2c_adapter * adap)

7. I2C Bus Devices

The I2C device driver focuses on two data structures: i2c_client and i2c_driver. i2c_client is used to describe devices on the I2C bus, while i2c_driver describes the device drivers on the I2C bus.

7.1 i2c_client Structure

The i2c_client structure is defined in the include/linux/i2c.h file, as follows:

struct i2c_client {unsigned short flags; /* div., see below */#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address */#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol */unsigned short addr; /* chip address - NOTE: 7bit */char name[I2C_NAME_SIZE];struct i2c_adapter *adapter; /* the adapter we sit on */struct device dev; /* the device structure */int init_irq; /* irq set at initialization */int irq; /* irq issued by device */struct list_head detected;#if IS_ENABLED(CONFIG_I2C_SLAVE)i2c_slave_cb_t slave_cb; /* callback for slave mode */#endifvoid *devres_group_id; /* ID of probe devres group */};

Each I2C device corresponds to an i2c_client structure variable, and the system assigns an i2c_client to each detected I2C slave device.

7.2 i2c_driver Structure

The i2c_driver structure is defined in the include/linux/i2c.h file, as follows:

/**struct i2c_driver {unsigned int class;union { /* Standard driver model interfaces */int (*probe)(struct i2c_client *client);int (*probe_new)(struct i2c_client *client);};void (*remove)(struct i2c_client *client);void (*shutdown)(struct i2c_client *client);void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol, unsigned int data);int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);struct device_driver driver;const struct i2c_device_id *id_table;int (*detect)(struct i2c_client *client, struct i2c_board_info *info);const unsigned short *address_list;struct list_head clients;u32 flags;};

For the I2C device driver writer, the key task is to construct the i2c_driver. After construction, it needs to register this i2c_driver with the I2C subsystem. The registration function for i2c_driver is int i2c_register_driver, whose prototype is as follows:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

Additionally, i2c_add_driver is often used to register i2c_driver. i2c_add_driver is a macro defined as follows:

#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)

When unregistering the I2C device driver, the previously registered i2c_driver must be unregistered from the I2C subsystem using the i2c_del_driver function, whose prototype is as follows:

void i2c_del_driver(struct i2c_driver *driver)

8. Example of an I2C Driver

#include <linux/i2c.h>#include <linux/init.h>#include <linux/module.h>int i2c_dev_probe(struct i2c_client* client, const struct i2c_device_id* id){ printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); return 0;}int i2c_dev_remove(struct i2c_client* client){ printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); return 0;}const struct of_device_id oftable[] = { {.compatible = "xx,xx",}, {/*end*/}};MODULE_DEVICE_TABLE(of,oftable);struct i2c_driver i2c_dev = { .probe = i2c_dev_probe, .remove = i2c_dev_remove, .driver = { .name = "xx", .of_match_table = oftable, }};module_i2c_driver(i2c_dev);MODULE_LICENSE("GPL");

Please open in the WeChat client

Leave a Comment