Skip to content

I2C Platform

Tăng Thiên Vũ, Trung tâm nghiên cứu công nghệ mạng Viettel, Hà Nội, 2/2018

Introduction

Tài liệu mô tả các kiến thức tìm hiểu được về I2C Platform trên Linux được thực hiện thực tế trên phần cứng ZC706 và FMCJESDADC1. Tài liệu là kết quả của quá trình mở rộng tìm hiểu dựa trên yêu cầu ban đầu của “Bài tập 6: Viết ứng dụng đọc/ghi chip EEPROM thông qua bus I2C trên KIT hoặc RRU” trong chương trình Linux Training của phòng Firmware – VTBR-VTTEK.

I2C là một bus rất phổ biến được sử dụng cho giao tiếp giữa master (một hoặc nhiều) và các thiết bị slave. Hình dưới mô tả một topo cơ bản kết nối các thiết bị ngoại vi với Microprocessor thông qua bus I2C.

i2c-bus

Figure. I2C Bus

ZC706 board triển khai hai cổng I2C (Hình) [2]:

  • PL-Side: cổng IIC_SCL/SDA_MAIN trên Bank 10.
  • PS-Side: cổng IIC_SDA/SCL_MAIN trên Bank 501.

zc706_i2c

Figure. I2C Bus in ZC706 KIT

Hiện tại, trên project phần cứng ZC706 và FMCJESDADC1 (kết nối với nhau qua bus FMC-HPC), hệ thống bus I2C được minh họa trong sơ đồ Hình dưới. FPGA Project cung cấp bởi ADI sử dụng Xilinx AXI I2C Controller (implement trên FPGA) cho mục đích giao tiếp với các thiết bị I2C trên mạch thông qua bus trên PL Bank 10.

zc706_i2c_devs

Figure. Bus I2C trên phần cứng ZC706/FMCJESDDAC1

I2C Platform

Basic elements

Hình 2.1 mô tả các thành phần cơ bản của một i2c platform trên Linux. Ứng dụng i2ctool trên tầng User Space tương tác với các file /dev/i2c-n tương ứng với các bus i2c. Thông qua các module i2c-dev, i2c-corei2c-driver, ứng dụng i2ctool có thể truy nhập đến các thiết bị i2c trên phần cứng tương ứng với bus i2c được đại diện bằng file /dev/i2c-n.

i2cbus_system

I2C Subsystem

Trong phần tiếp theo sẽ tìm hiểu rõ hơn các thành phần trong hệ thống trên, cũng như cách hệ thống hoạt động.

Important Data Structures

Để có thể quản lí và truy cập đến các thiết bị i2c, i2c platform sử dụng các cấu trúc dữ liệu như: i2c_client, i2c_adapteri2c_algorithm. Trong đó:

  • i2c_client đại diện cho các thiết bị trên một bus i2c;
  • i2c_adapter và i2c algorithm cung cấp các phương thức (các hàm master_xfer(), smbus_xfer()) để có thể truy nhập đến các thiết bị i2c.

Mối quan hệ giữa các struct này thể hiện trong Hình 2.2.

spi_bus_signals

Figure. Các cấu trúc dữ liệu quan trọng liên quan đến i2c platform

Struct i2c_client

Trên một bus i2c có thể có một hoặc nhiều thiết bị, mỗi thiết bị trên bus sẽ được driver đăng kí với hệ điều hành thông qua một struct i2c_client (Hình 2.3). Đặc trưng nhất của struct i2c_client này là trường addr nơi chứa địa chỉ của thiết bị trên bus i2c để phân biệt với các thiết bị khác.

struct i2c_client {
    unsigned short flags;   /* div., see below */
    unsigned short addr;    /* chip address - NOTE: 7bit */
                            /* addresses are stored in the */
                            /* _LOWER_ 7 bits */
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;    /* the adapter we sit on */
    struct device dev;              /* the device structure */
    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 */
#endif
};

i2c_client chứa con trỏ để trỏ đến adapter của driver tương ứng với bus i2c.

Struct i2c_adapter và i2c_algorithm

Driver quản lí các thiết bị thông qua struct i2c_client và cung cấp phương pháp để truy cập đến các thiết bị này thông qua struct i2c_adapter.

struct i2c_adapter {
    struct module *owner;
    unsigned int class; /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* the algo 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 */
    int nr;
    char name[48];
     …
};

I2c_adapter chứa các thông tin cơ bản của bus như name, nr (number) để định danh bus i2c mà driver đang quản lí. Ngoài ra, i2c_adapter chứa i2c_algorithm chứa các con trỏ để trỏ đến hàm truy nhập.

struct i2c_algorithm {
    /* If an adapter algorithm can't do I2C-level access, set master_xfer
     to NULL. If an adapter algorithm can do SMBus access, set
     smbus_xfer. If set to NULL, the SMBus protocol is simulated
     using common I2C messages */
    /* master_xfer should return the number of messages successfully
     processed, or a negative value on error */
    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);
    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
    int (*reg_slave)(struct i2c_client *client);
    int (*unreg_slave)(struct i2c_client *client);
#endif
};

Probing a device

spi_bus_signals

Mỗi I2C Bus Driver đều chứa struct i2c_algorithm quản lí các hàm truy nhập đến thiết bị i2c. Ví dụ như Xilinx I2C Controller Driver:

static const struct i2c_algorithm xiic_algorithm = {
    .master_xfer = xiic_xfer,
    .functionality = xiic_func,
};

Hoặc như Cadence I2C Controller Drivers:

static const struct i2c_algorithm cdns_i2c_algo = {
    .master_xfer = cdns_i2c_master_xfer,
    .functionality = cdns_i2c_func,
};

Quá trình probing một thiết bị về bản chất là quá trình khởi tạo i2c_adapter (Hình 2.6-1) và các i2c_client (Hình 2.6-2). Ví dụ đối với Xilinx I2C Controller driver, i2c_adapter được khởi tạo như sau:

static struct i2c_adapter xiic_adapter = {
    .owner = THIS_MODULE,
    .name = DRIVER_NAME,
    .class = I2C_CLASS_DEPRECATED,
    .algo = &xiic_algorithm,
};

Hàm probe của driver sẽ gọi đến i2c_add_adapteri2c_new_divice() để đăng kí i2c_adapteri2c_client vào hệ thống. Như vậy, sau quá trình driver probing, với mỗi thiết bị trên bus i2c đã được đăng kí và cấp phát một i2c_client tương ứng, các i2c_client này sử dụng i2c_adapter để truy nhập vào thiết bị trên phần cứng.

Access device from User space

Lấy i2c_adapter

Sau khi i2c driver thực hiện probing thiết bị, một i2c_adapter đã được cấp phát và khởi tạo. Để tiến hành truy nhập được thiết bị thông qua các phương thức cung cấp bởi adapter, i2c_tool cần lấy được i2c_adapter này. Quá trình này được thực hiện trong khi thực hiện hàm open() trên user space.

sprintf(filename, "/dev/i2c-%d", i2cbus);
file = open(filename, O_RDWR);

Hàm open() trên User Space sẽ gọi đến phương thức open()trong Kernel Space của file /dev/i2c-%d. Trong phương thức open, hàm i2c_get_adapter được gọi để tìm kiếm i2c_adapter.

i2c_dev = i2c_dev_get_by_minor(minor);
if (!i2c_dev)
    return -ENODEV;
adap = i2c_get_adapter(i2c_dev->adap->nr);
if (!adap)
    return -ENODEV;

Quá trình này được mô tả trong Hình 2.6-4.

Khởi tạo một i2c_client ảo

Bản chất mỗi thiết bị trên bus i2c được quản lí bởi i2c-core thông qua các cấu trúc i2c_client. Nhưng trong quá trình truy nhập đến thiết bị, i2c-dev không sử dụng các i2c_client này mà cấp phát một i2c_client mới để trỏ đến i2c_adapter vừa được tìm thấy ở quá trình được nhắc đến ở mục 2.3.1. Quá trình khởi tạo i2c_client này được mô tả trong Hình 2.6-5.

client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {
    i2c_put_adapter(adap);
    return -ENOMEM;
}
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
client->adapter = adap;
file->private_data = client;

Như vậy, sau quá trình open() file, i2c-dev đã có một struct i2c_client (trỏ đến i2c_adapter được khởi tạo lúc driver probe). I2c-dev sẽ sử dụng i2c_client này để tương tác với thiết bị.

Access via IOCTL (/dev/i2c-%d)

Set Slave Addr

Trước khi tiến hành truy nhập vào một device trên bus i2c, quá trình đầu tiên phải tiến hành setup địa chỉ mà device cần truy cập đến. Trong phương thức IOCTL của i2c file cung cấp command I2C_SLAVE_FORCE để gán địa chỉ của device cho i2c_client struct được khởi tạo từ quá trình open file.

case I2C_SLAVE_FORCE:
    if ((arg > 0x3ff) ||
        (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
        return -EINVAL;
    if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
        return -EBUSY;
    /* REVISIT: address could become busy later */
    client->addr = arg;
    return 0;

Biến “client” là i2c_client ảo đã được cấp phát trong phương thức open().

Truy nhập đến device

Phương thức IOCTL của i2c file cung cấp command I2C_SMBUS và gọi đến hàm i2cdev_ioctl_smbus() để truy nhập đến thiết bị. Bản chất của hàm i2cdev_ioctl_smbus() là gọi đến hàm xfer của adapter.

Trên User Space gọi đến phương thức ioctl của /dev/i2c-%d.

static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, int size, union i2c_smbus_data *data)
{
    struct i2c_smbus_ioctl_data args;
    args.read_write = read_write;
    args.command = command;
    args.size = size;
    args.data = data;
    return ioctl(file,I2C_SMBUS,&args);
}

Quá trình này sẽ gọi đến hàm i2cdev_ioctl_smbus( ) trong i2c-dev trong Kernel Space mà bản chất là gọi đến hàm smbus_xfer() của i2c_adapter:

res = adapter->algo->smbus_xfer(adapter, addr, flags, read_write, command, protocol, data);

I2C MUX

spi_bus_signals

I2C MUX được sử dụng để mở rộng bus I2C để có thể hỗ trợ kết nối đến nhiều thiết bị hơn. Board ZC706 sử dụng IC I2C MUX PCA 9548 (Hình 1.2). Quá trình thực hiện probing các thiết bị trên các kênh của PCA 9548 Driver được mô tả trong Hình 2.7. Bản chất của quá trình này là khởi tạo các i2c_adapter tương ứng với các kênh bằng hàm i2c_add_mux_adapter() trong i2c-mux module.

Các i2c_adapter của các kênh tương ứng bản chất sẽ sử dụng các hàm smbus_xfer và master_xfer của “parent adapter” (chính là adapter đã được khởi tạo trọng Xilinx I2C Driver Controller). i2c-mux module sử dụng struct i2c_mux_priv để quản lí chung các channel.

/* multiplexer per channel data */
struct i2c_mux_priv {
    struct i2c_adapter adap;
    struct i2c_algorithm algo;
    struct i2c_adapter *parent;
    struct device *mux_dev;
    void *mux_priv;
    u32 chan_id;
    int (*select)(struct i2c_adapter *, void *mux_priv, u32 chan_id);
    int (*deselect)(struct i2c_adapter *, void *mux_priv, u32 chan_id);
};

Ngoài ra, PCA 9548 cung cấp thêm hàm select và deselect để chọn kênh trước khi tiến hành truy nhập thiết bị.

static int pca954x_select_chan(struct i2c_adapter *adap, void *client,
u32 chan);
static int pca954x_deselect_mux(struct i2c_adapter *adap, void *client,
u32 chan);

I2C Bus

I2C Protocol

*TBD

I2C Pull-up

*TBD

Refereneces

[1] TI SL VA704 – Application Report – Understanding the I2C Bus.

[2] Xilinx UG954 - ZC706 Evaluation Board for the Zynq-7000 XC7Z045 All Programmable SoC User Guide.

Back to top