第15章 Linux I2C核心、总线与设备驱动之Linux I2C体系结构

本章涉及知识

I2C总线仅仅使用SCL(时钟信号线)、SDA(双向数据线)这两根信号线实现设备之间的数据交互,简化对硬件资源和PCB板布线空间的占用。I2C总线广泛地应用在EEPROM(电可擦除可编程只读存储器)、实时时钟(RTC)、小型LCD等设备与CPU的接口中。

Linux系统中定义I2C驱动体系结构。在Linux系统中,I2C驱动由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。这3个部分相互协作,形成非常通用、可适应性很强的I2C框架。

1、Linux的I2C体系结构分析,3个组成部分各自的功能及相互联系。

2、Linux的I2C核心分析,i2c-core.c文件的功能和主要函数的实现。

3、I2C适配器驱动和I2C设备驱动的编写方法。

4、Tegra (图睿)ARM处理器的I2C总线驱动,以挂接在I2C总线上的AT24XX系列EEPROM为例分析I2C设备驱动。

15.1 Linux I2C体系结构

Linux的I2C体系结构分为3个组成部分。

(1)I2C核心

I2C核心提供I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等,如图15.1所示。


扫描二维码关注公众号,回复: 1428028 查看本文章

图15.1 Linux的I2C体系结构

(2)I2C总线驱动

I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可直接集成在CPU内部。I2C总线驱动主要包含I2C适配器数据结构i2c_adapter、I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数

经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK(应答)等。

(3)I2C设备驱动

I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。

I2C设备驱动主要包含数据结构i2c_driver和i2c_client,需要根据具体设备实现其中的成员函数。

在Linux 2.6内核中,所有的I2C设备都在sysfs文件系统中显示,存在于/sys/bus/i2c/目录下,以适配器地址和芯片地址的形式列出,例如:

test@ubuntu2018:/sys/bus/i2c$ tree
.
├── devices
│   └── i2c-0 -> ../../../devices/pci0000:00/0000:00:1c.7/0000:06:00.0/0000:07:00.0/i2c-0
├── drivers
│   ├── 88PM860x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── aat2870
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── ab3100
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── adp5520
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── as3711
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── axp20x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── da903x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── da9052
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── da9055-pmic
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── da9063
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── dummy
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── elants_i2c
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── htcpld-chip
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── intel_soc_pmic_i2c
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── ipmi_ssif
│   │   ├── bind
│   │   ├── module -> ../../../../module/ipmi_ssif
│   │   ├── uevent
│   │   └── unbind
│   ├── lp8788
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max14577
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max77693
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max77843
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max8925
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max8997
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── max8998
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── palmas
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── rc5t583
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── sec_pmic
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── smsc
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── sx150x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps65090
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps65217
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps6586x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps65910
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps65912
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── tps80031
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── twl
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── twl6040
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── wm831x
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   ├── wm8350
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   └── WM8400
│       ├── bind
│       ├── uevent
│       └── unbind
├── drivers_autoprobe
├── drivers_probe
└── uevent

42 directories, 117 files

在Linux内核源码树中的drivers目录下有一个i2c目录,而在i2c目录下又包含如下文件和文件夹。

(1)i2c-core.c

这个文件实现I2C核心的功能以及/proc/bus/i2c*接口。

(2)i2c-dev.c

实现I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0,i2c-1,…,i2c-10,…)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来访问这个设备。

i2c-dev.c并不是针对特定的设备而设计,只是提供通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

(3)busses文件夹

这个文件夹包含一些I2C主机控制器的驱动,例如:

i2c-tegra.c、i2c-omap.c、i2c-versatile.c、i2c-s3c2410.c等。

(4)algos文件夹

实现一些I2C总线适配器的通信方法

内核中的include/linux/i2c.h头文件对i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构进行定义。

代码清单15.1 i2c_adapter结构体

/*
 * 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   */
        struct rt_mutex bus_lock;

        int timeout;                    /* in jiffies */
        int retries;
        struct device dev;              /* the adapter device */

        int 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;

};

代码清单15.2 i2c_algorithm结构体

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 *);

};

分析:

master_xfer:I2C传输函数指针,I2C主机驱动的大部分工作聚集在此。

smbus_xfer:SMBus传输函数指针,SMBus不需要增加额外引脚,与I2C总线相比,在访问时序上也有一定的差

异。

代码清单15.3 i2c_driver结构体

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 *);
        int (*suspend)(struct i2c_client *, pm_message_t mesg);
        int (*resume)(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;

};

代码清单15.4 i2c_client结构体

/**
 * struct i2c_client - represent an I2C slave device
 * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
 *      I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
 * @addr: Address used on the I2C bus connected to the parent adapter.
 * @name: Indicates the type of the device, usually a chip name that's
 *      generic enough to hide second-sourcing and compatible revisions.
 * @adapter: manages the bus segment hosting this I2C device
 * @dev: Driver model device node for the slave.
 * @irq: indicates the IRQ generated by this device (if any)
 * @detected: member of an i2c_driver.clients list or i2c-core's
 *      userspace_devices list
 *
 * An i2c_client identifies a single device (i.e. chip) connected to an
 * i2c bus. The behaviour exposed to Linux is defined by the driver
 * managing the device.
 */
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;

};

下面分析i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构的作用及其之间的关系。

(1)i2c_adapter与i2c_algorithm

i2c_adapter对应物理上的一个适配器,i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm提供的通信函数控制适配器产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此在i2c_adapter中绑定一个i2c_algorithm的指针。

i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。i2c_msg结构体定义在include/uapi/linux/i2c.h(在uapi目录下,证明用户空间的应用也可能使用这个结构体)中,代码清单15.5给出定义,其中的成员表明了I2C的传输地址、方向、缓冲区、缓冲区长度等信息。

代码清单15.5 i2c_msg结构体

struct i2c_msg {
        __u16 addr;     /* slave address                        */
        __u16 flags;
#define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
#define I2C_M_RD                0x0001  /* read data, from slave to master */
#define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
        __u16 len;              /* msg length                           */
        __u8 *buf;              /* pointer to msg data                  */

};

(2)i2c_driver与i2c_client

i2c_driver对应于一套驱动方法,其主要成员函数是probe()、remove()、suspend()、resume()等,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_driver可以支持多个同类型的i2c_client。

备注:

i2c_client的物理设备信息通常在BSP的板文件中通过i2c_board_info填充,例如:定义一个I2C设备的ID为“ad7142_joystick”、地址为0x2C、中断号为IRQ_PF5的i2c_client:

linux/i2c.h
/**
 * I2C_BOARD_INFO - macro used to list an i2c device and its address
 * @dev_type: identifies the device type
 * @dev_addr: the device's address on the bus.
 *
 * This macro initializes essential fields of a struct i2c_board_info,
 * declaring what has been provided on a particular board.  Optional
 * fields (such as associated irq, or device-specific platform_data)
 * are provided using conventional syntax.
 */
#define I2C_BOARD_INFO(dev_type, dev_addr) \
        .type = dev_type, .addr = (dev_addr)

static struct i2c_board_info __initdata xxx_i2c_board_info[] = {
#if defined(CONfiG_JOYSTICK_AD7142) || defined(CONfiG_JOYSTICK_AD7142_MODULE)
       {
               I2C_BOARD_INFO("ad7142_joystick", 0x2C), /* dev_type, dev_addr */
               .irq = IRQ_PF5,
       },
        ...
}

在I2C总线驱动i2c_bus_type的match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配在板文件中定义的ID和i2c_driver所支持的ID表。

drivers/i2c/i2c-core.c

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;

if (!client)
return 0;

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv)) // of类型匹配
return 1;

/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv)) // ACPI类型匹配
return 1;

driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table) // id table匹配
return i2c_match_id(driver->id_table, client) != NULL;

return 0;
}

struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops, // 电源管理操作
};

EXPORT_SYMBOL_GPL(i2c_bus_type);

(3)i2c_adpater与i2c_client

i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和物理设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I2C物理设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于i2c_adpater的i2c_client的链表。

假设I2C总线适配器xxx上有两个使用相同驱动程序的yyy I2C设备,在打开该I2C总线的设备节点后,相关数据结构之间的逻辑组织关系如图15.2所示。


图15.2 I2C驱动的各种数据结构的关系

从上面的分析,虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现相当复杂。当拿到实际的电路板时,面对复杂的Linux I2C子系统,应该如何下手写驱动?哪些是需要亲自做的,哪些是内核已经提供的?理清这个问题非常有意义,可以使我们在面对具体问题时迅速抓住重点。

一、适配器驱动可能是Linux内核本身还不包含的。

二、挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的。

因此,要实现的主要工作如下。

1、提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。

2、提供I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。

3、实现I2C设备驱动中的i2c_driver接口,用具体设备yyy的yyy_probe()、yyy_remove()、
yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、

suspend、resume和id_table指针。

4、实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;如果是声卡,就实现ALSA/OSS驱动。

总结:

在上述工作中,前两个属于I2C总线驱动,后两个属于I2C设备驱动。



猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80535510