Understanding Linux I2C Framework: A Layered Kernel Subsystem Walkthrough
This article provides a comprehensive analysis of the Linux I2C framework, detailing its physical bus characteristics, protocol specifics, comparison with SMBus, and the multi‑layer kernel architecture—including hardware, adapter, core, and driver layers—culminating in a step‑by‑step MPU6050 driver implementation and testing guide.
1. Introduction to the I2C Framework
The Linux I2C framework acts as a communication bridge between devices, similar to a traffic hub, enabling orderly data exchange among sensors, EEPROMs, and other peripherals via the I2C bus.
1.1 I2C Bus Physical Topology and Characteristics
The bus consists of SDA (data) and SCL (clock) lines with pull‑up resistors. When idle, both lines are pulled high. Each device can act as master or slave and has a unique address defined in its datasheet. Typically, the CPU module serves as the master, while attached devices are slaves.
The bus can support up to 400 pF total capacitance, limiting the number of devices. Standard‑mode speed is 100 kbit/s, fast mode 400 kbit/s, and high‑speed mode up to 3.4 Mbit/s. Transfer speed can be adjusted via the programmable clock, which is also influenced by the pull‑up resistor value.
1.2 I2C Bus Protocol
Communication starts with a start condition (SCL high, SDA falling) generated by the master and ends with a stop condition (SCL high, SDA rising). While a transaction is active, the bus is busy and exclusive to that master‑slave pair.
Data is transferred byte‑wise. For each clock pulse on SCL, a bit is placed on SDA. After a byte is sent, the receiver acknowledges by pulling SDA low. Not all bytes require an ACK; a NACK indicates the slave cannot accept more data.
Each device has a 7‑bit address; the least‑significant bit of the address byte indicates read (1) or write (0) direction.
1.3 I2C vs. SMBus Comparison
SMBus is derived from I2C and shares the two‑wire interface, master‑slave model, and 7‑bit addressing, but differs in voltage range (1.8 V–5 V for SMBus), minimum clock frequency (10 kHz for SMBus, no minimum for I2C), clock‑stretching limits, mandatory address response, and defined data formats. Linux prefers SMBus when available, and software can emulate SMBus over I2C if hardware support is absent.
2. Linux I2C Kernel Subsystem Layered Structure
2.1 Hardware Layer: I2C Controllers and Slave Devices
The hardware layer comprises I2C controllers (integrated in SoCs or standalone chips) that generate clock and control signals, and slave devices such as temperature sensors, accelerometers, and EEPROMs that respond to controller commands.
2.2 Adapter Driver Layer: i2c_adapter and i2c_algorithm
The adapter driver bridges hardware and core layers. Key structures:
struct i2c_adapter {
const struct i2c_algorithm *algo; // communication algorithm
struct device dev; // device attributes
int nr; // bus number
unsigned int timeout; // transfer timeout
unsigned int retries; // retry count
};
struct i2c_algorithm {
int (*master_xfer)(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);
u32 (*functionality)(struct i2c_adapter *adap);
};i2c_adapter abstracts the controller, managing device type, timeout, and retries. i2c_algorithm implements the low‑level transfer functions, with master_xfer handling master‑mode data exchange, smbus_xfer for SMBus (may be NULL), and functionality reporting supported features such as 10‑bit addressing.
2.3 Core Layer: I2C Core Functions
The I2C core provides unified APIs for bus, device, and driver registration, matching, and binding. Drivers can invoke core APIs to send or receive data without dealing with hardware specifics.
2.4 Device Driver Layer: i2c_client and i2c_driver
i2c_clientrepresents a slave device on the bus, holding its address, name, and a pointer to the associated adapter. Registration can be performed via device tree parsing ( i2c_new_device()) or sysfs. i2c_driver contains match rules, probe, and remove callbacks. The probe function is called after a successful match to initialize the device; remove cleans up resources.
3. Data Flow Across Layers
Application layer issues read/write requests through a device node (e.g., /dev/i2c‑0).
Device driver translates the request into i2c_msg structures and calls core APIs such as i2c_transfer().
I2C core forwards messages to the appropriate adapter driver based on the target address.
Adapter driver uses the algorithm’s master_xfer to drive the controller hardware, toggling SCL and SDA lines.
Hardware layer performs the actual bit‑level transmission and reception, handling errors like timeouts or CRC failures.
4. MPU6050 Driver Practical Walkthrough
4.1 Hardware Connection and Environment Setup
Connect MPU6050 SCL to the board’s I2C SCL, SDA to SDA, VCC to 3.3 V, and GND to ground. Verify the connection with i2c‑tools by running i2cdetect -y <bus>; a response of 0x68 confirms the sensor is reachable.
4.2 Device Tree Configuration
Add a node for the sensor under the appropriate I2C bus in the device tree:
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
interrupt-parent = <&gpioX>;
interrupts = <Y IRQ_TYPE_LEVEL_HIGH>;
};The compatible string must match the driver’s of_match_table, and reg specifies the I2C address.
4.3 Kernel Driver Implementation
Define the driver structure:
static struct i2c_driver mpu6050_driver = {
.driver = {
.name = "mpu6050",
.of_match_table = of_match_ptr(mpu6050_of_match),
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
};The probe function performs resource allocation and register initialization; remove releases them.
Implement file operations to expose the device to user space:
static const struct file_operations mpu6050_fops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.write = mpu6050_write,
.release = mpu6050_release,
};4.4 Driver Loading and Testing
Register the driver with module_init(mpu6050_driver_init) and module_exit(mpu6050_driver_exit). After loading, a simple user‑space program can open /dev/mpu6050, read sensor data, and print it:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define MPU6050_DEV "/dev/mpu6050"
int main() {
int fd = open(MPU6050_DEV, O_RDONLY);
if (fd < 0) { perror("open"); return 1; }
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) perror("read"); else printf("Read data from MPU6050: %.*s
", (int)n, buf);
close(fd);
return 0;
}Successful execution confirms the driver correctly communicates with the MPU6050 sensor.
Overall, the Linux I2C framework’s layered architecture—hardware, adapter, core, and driver layers—collaborates tightly to provide efficient, modular device management and communication.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
