Mastering I2C for Linux Driver Development

Mastering I2C for Linux Driver Development

A Comprehensive Guide from Protocol Basics to Production Drivers

1. Introduction to I2C

The?Inter-Integrated Circuit (I2C)?protocol is a synchronous, two-wire serial communication standard designed for short-distance communication between integrated circuits. Developed by Philips (now NXP Semiconductors), I2C is widely used in embedded systems to connect sensors, EEPROMs, displays, and other peripherals to microcontrollers or System-on-Chips (SoCs).

Key Advantages:

  • Minimal Wiring: Uses only two lines (SDA and SCL).
  • Multi-Master Support: Multiple controllers can share the bus.
  • Flexible Addressing: Supports up to?1008 devices?with 10-bit addressing.

2. I2C Protocol Fundamentals

2.1 Signal Lines

SignalDescriptionSDASerial Data Line (bidirectional, open-drain).SCLSerial Clock Line (generated by the master).

2.2 Speed Modes

2.3 Frame Structure

An I2C transaction consists of four phases:

  • Start Condition
  • Address Frame
  • Data Frame(s)
  • Stop Condition

3. Addressing I2C Devices

3.1 7-Bit vs. 10-Bit Addressing

3.2 Scanning the Bus

Use the?i2c-tools?package to detect devices:

i2cdetect -y 1  # Scan bus 1 (Raspberry Pi)          

Output Example:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f  
00:                                -- -- -- -- -- -- -- --   
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --   
70: -- -- -- -- -- -- 76 --   
        

4. Linux I2C Subsystem

4.1 Key Components

  • I2C Core: Manages buses, devices, and algorithms.
  • Adapter Drivers: Interface with hardware controllers (e.g.,?i2c-bcm2835?for Raspberry Pi).
  • Device Drivers: Handle specific peripherals (e.g.,?lm75?for temperature sensors).

4.2 Key Data Structures

4.3 Common API Functions

5.1 Device Tree Configuration

Define the I2C controller and devices in the Device Tree (DTS):

&i2c1 {  
    status = "okay";  
    clock-frequency = <100000>; // 100 kHz  

    // BMP280 temperature sensor  
    temp_sensor: bmp280@76 {  
        compatible = "bosch,bmp280";  
        reg = <0x76>;  
    };  

    // AT24C32 EEPROM  
    eeprom: at24c32@50 {  
        compatible = "atmel,at24";  
        reg = <0x50>;  
        pagesize = <32>;  
    };  
};  
        

5.2 Driver Registration

#include <linux/i2c.h>  

static const struct i2c_device_id bmp280_id[] = {  
    { "bmp280", 0 },  
    { }  
};  
MODULE_DEVICE_TABLE(i2c, bmp280_id);  

static const struct of_device_id bmp280_of_match[] = {  
    { .compatible = "bosch,bmp280" },  
    { }  
};  
MODULE_DEVICE_TABLE(of, bmp280_of_match);  

static struct i2c_driver bmp280_driver = {  
    .driver = {  
        .name = "bmp280",  
        .of_match_table = bmp280_of_match,  
    },  
    .probe = bmp280_probe,  
    .remove = bmp280_remove,  
    .id_table = bmp280_id,  
};  
module_i2c_driver(bmp280_driver);  
        

5.3 Reading Data from a Sensor

static int bmp280_read_temp(struct i2c_client *client, int *temp) {  
    u8 reg = BMP280_REG_TEMP;  
    u8 buf[3];  
    struct i2c_msg msgs[] = {  
        {  
            .addr = client->addr,  
            .flags = 0,          // Write  
            .len = 1,  
            .buf = &reg,  
        },  
        {  
            .addr = client->addr,  
            .flags = I2C_M_RD,   // Read  
            .len = 3,  
            .buf = buf,  
        },  
    };  

    // Execute transaction  
    if (i2c_transfer(client->adapter, msgs, 2) != 2) {  
        dev_err(&client->dev, "I2C read failed");  
        return -EIO;  
    }  

    // Convert raw data to temperature  
    *temp = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);  
    return 0;  
}  
        

6. SMBus Compatibility

  • SMBus: A subset of I2C with stricter timing rules.
  • Compatibility: Most I2C devices work with SMBus, but avoid mixing protocols.

7. Debugging I2C Drivers

7.1 Tools

  • i2c-tools:

i2cdetect -y 1      # Scan bus 1  
i2cget -y 1 0x76    # Read from address 0x76  
i2cset -y 1 0x50 0x00 0xFF  # Write 0xFF to EEPROM at 0x00          

  • Logic Analyzer: Capture waveforms to inspect start/stop conditions and data.

7.2 Common Issues

8. Best Practices

  1. Error Handling: Check return values of all I2C functions.
  2. Power Management: Use?pm_runtime_get_sync()?to manage device states.
  3. Concurrency: Protect shared resources with mutexes.
  4. Device Tree: Use?of_match_ptr()?for compatibility.

9. Real-World Example: EEPROM Driver

9.1 Writing to AT24C32

static int at24_write(struct i2c_client *client, u16 addr, u8 *data, u8 len) {  
    u8 buf[len + 2];  
    buf[0] = addr >> 8;   // High byte  
    buf[1] = addr & 0xFF; // Low byte  
    memcpy(&buf[2], data, len);  
    return i2c_master_send(client, buf, len + 2);  
}  
        

9.2 Reading from AT24C32

static int at24_read(struct i2c_client *client, u16 addr, u8 *data, u8 len) {  
    u8 addr_buf[2] = { addr >> 8, addr & 0xFF };  
    struct i2c_msg msgs[] = {  
        { .addr = client->addr, .flags = 0, .len = 2, .buf = addr_buf },  
        { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = data },  
    };  
    return i2c_transfer(client->adapter, msgs, 2);  
}  
        

10. Conclusion

Mastering I2C driver development in Linux empowers you to interface with a vast ecosystem of sensors, memory chips, and peripherals. By understanding the protocol’s fundamentals, leveraging the kernel’s I2C subsystem, and adhering to best practices, you can create robust, production-ready drivers.

Next Steps:

  • Experiment with an I2C sensor (e.g., MPU6050 accelerometer).
  • Contribute your driver to the Linux kernel community.

Resources:

Happy coding!???

Also sometimes i2c-detect takes long time to detect the devices on the bus.. can you suggest, what is the issue here ?

回复

you can use i2c_transfer for MASTER write to SLAVE too, instead of using flag I2C_M_RD in MASTER read from SLAVE, make .flags= 0 during Write and use sizeof(arr) for .len instead of using hard coded lengths in bytes..

回复
Devendra Kumar Mishra

Automotive | Embedded Software Developer | AUTOSAR | Bare metal Driver Development | Embedded Linux | Linux Device Driver Developer | Embedded C |TDA4VM SOC | FREE-RTOS | UDS | YOCTO | DDS |

2 天前

Very informative! I am following each post of yours.it does have in depth explanation of each Subsystem of Linux. Thanks for Sharing!

要查看或添加评论,请登录

David Zhu的更多文章