程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-I2C总线设备驱动

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

通信协议-I2C小节,我们已经对I2C协议进行了详细的介绍,并在Mini2440裸机开发之I2C(AT24C08)小节中介绍了通过I2C协议去读写AT24C08 EEPROM。在这一节将会学习I2C总线设备驱动模型。

一、I2C驱动框架

1.1 I2C框架

I2C总线设备驱动模型由I2C核心、I2C总线驱动(或者说I2C适配器驱动、I2C控制器驱动)、I2C设备驱动组成。

对于linux系统来说,支持各式各样的SOC,并且还想要支持各种I2C硬件芯片,就必须将一些公共的部分抽离出来,这样就抽象出了:

  • i2c client:描述具体的I2C设备,每个i2c_client对应一个实际的I2C设备,比如AT24C08、MPU6050、SSD1306 OLED等;
    • /sys/devices/platform/s3c2440-i2c.0/i2c-0/0-<addr>:I2C设备注册时创建;0为I2C适配器编号,addr为4位16进制数字,比如具有0x50地址的设备连接到0号I2C总线,则设备名为0-0050;

  • i2c driver:描述一个I2C设备驱动,每个i2c_driver描述一种I2C设备的驱动;
  • i2c dev:I2C字符设备;
    • /dev/i2c-0:I2C字符设备文件;0为控制器的编号;
    • /sys/class/i2c-dev/i2c-0:I2C字符设备所属类名为i2c-dev;
  • i2c adapter:描述SOC的一个I2C控制器;
    • /sys/devices/platform/s3c2440-i2c.0/i2c-0:I2C适配器设备注册时创建;0为I2C适配器编号,如果有多个I2C控制器注册,/sys/devices/platform/s3c2440-i2c.0目录下将会有多个i2c-%d目录;

 注意:/sys/devices/platform/s3c2440-i2c.0的subsystem指向了/sys/bus/platform。而/sys/devices/platform/s3c2440-i2c.0/i2c.0的subsystem指向了/sys/bus/i2c。

    • /sys/class/i2c-adapter:I2C控制器设备类为i2c-adatper,下面存储的都是I2C控制器相关的符号链接;

    • /sys/bus/i2c/devices/i2c-0:I2C适配器设备注册时创建;0为I2C适配器编号,如果有多个I2C控制器注册,/sys/bus/devices目录下将会有多个i2c-%链接文件;其指向/sys/devices/platform/s3c2440-i2c.0/i2c-0;

  • i2c algorithm:I2C通信算法,用于操作实际的I2C控制器,产生 I2C硬件波形;

在一个SOC上可能有多条I2C总线,一条总线对应一个I2C总线驱动,每一条总线上又可以接多个I2C设备。

假如一个SOC上有3个I2C适配器,每个I2C适配器,外接3个I2C设备,想要3个适配器分别都能驱动3个I2C设备,我们只需写3个适配器代码,3个I2C设备代码即可。

注意:关于/sys目录下各个文件的意义可以参考:/sys目录下各个子目录的具体说明

1.1.1 I2C核心

I2C核心是linux内核用来维护和管理I2C的核心部分。I2C core提供接口函数,允许一个i2c_adapter、i2c_driver和i2c_client初始化时在I2C core中注册,以及退出时进行卸载,同时还提供了I2C总线读写访问的接口。

1.1.2 I2C总线驱动

I2C总线驱动包含了I2C适配器数据结构i2c_adapter、I2C通信算法i2c_algorithm和控制I2C适配器产生通信信号的函数。

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

1.1.3 I2C设备驱动

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

1.2 目录结构

linux内核将I2C驱动相关的代码放在drivers/i2c目录下,这下面的文件还是比较多的,我们大概了解一下即可。

1.2.1 algos

保存I2C通信方面的算法。

1.2.2 busses

保存SoC厂家提供的I2C控制器驱动相关的文件,比如i2c-stm32.c、i2c-stm32f4.c、i2c-stm32f7.c、 i2c-versatile.c、 i2c-s3c2410.c等。

1.2.3 muxes

实现I2C mux驱动相关的文件,比如i2c-mux-pca954x.c。

mux包含如下功能:

  • switch:在配置为 1:N 时,可以为每个子通道选择和设计。换句话说,它可以选择一个或多个;
  • muliplexor:在配置为 1:N 或 N:1 时,可以选择和设计一个子通道。选择时,一次只能有一个通道。
1.2.4 i2c-core-base.c

这个文件实现了I2C核心的功能,I2C总线的初始化、注册和适配器添加和注销等相关工作。

1.2.5  i2c-dev.c

实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备的主设备号都为89,次设备号为0~255。

应用程序通过“i2c-%d”(i2c-0、i2c-1、i2c-2...)文件名并使用文件操作结构open()、write()、read()、ioctl()和close()等来访问这个设备。

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

二、I2C总线注册

linux驱动移植-platform总线设备驱动中我们已经介绍了platfrom总线的注册流程,I2C总线类型注册和platfrom总线注册类似。

2.1 I2C总线类型定义

在linux 设备模型中,总线类型由bus_type 结构表示,我们所用的 I2C、SPI、USB 都是用这个结构体来定义的。该结构体定义在 include/linux/device.h文件中:

/**
 * struct bus_type - The bus type of the device
 *
 * @name:       The name of the bus.
 * @dev_name:   Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:   Default device to use as the parent.
 * @bus_groups: Default attributes of the bus.
 * @dev_groups: Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:      Called, perhaps multiple times, whenever a new device or driver
 *              is added for this bus. It should return a positive value if the
 *              given device can be handled by the given driver and zero
 *              otherwise. It may also return error code if determining that
 *              the driver supports the device is not possible. In case of
 *              -EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:     Called when a device is added, removed, or a few other things
 *              that generate uevents to add the environment variables.
 * @probe:      Called when a new device or driver add to this bus, and callback
 *              the specific driver's probe to initial the matched device.
 * @remove:     Called when a device removed from this bus.
 * @shutdown:   Called at shut-down time to quiesce the device.
 *
 * @online:     Called to put the device back online (after offlining it).
 * @offline:    Called to put the device offline for hot-removal. May fail.
 *
 * @suspend:    Called when a device on this bus wants to go to sleep mode.
 * @resume:     Called to bring a device on this bus out of sleep mode.
 * @num_vf:     Called to find out how many virtual functions a device on this
 *              bus supports.
 * @dma_configure:      Called to setup DMA configuration on a device on
 *                      this bus.
 * @pm:         Power management operations of this bus, callback the specific
 *              device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:          The private data of the driver core, only the driver core can
 *              touch this.
 * @lock_key:   Lock class key for use by the lock validator
 * @need_parent_lock:   When probing or removing a device on this bus, the
 *                      device core should lock the device's parent.
 *
 * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
        const char              *name;
        const char              *dev_name;
        struct device           *dev_root;
        const struct attribute_group **bus_groups;
        const struct attribute_group **dev_groups;
        const struct attribute_group **drv_groups;

        int (*match)(struct device *dev, struct device_driver *drv);
        int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
        int (*probe)(struct device *dev);
        int (*remove)(struct device *dev);
        void (*shutdown)(struct device *dev);

        int (*online)(struct device *dev);
        int (*offline)(struct device *dev);

        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);

        int (*num_vf)(struct device *dev);

        int (*dma_configure)(struct device *dev);

        const struct dev_pm_ops *pm;

        const struct iommu_ops *iommu_ops;

        struct subsys_private *p;
        struct lock_class_key lock_key;

        bool need_parent_lock;
};

其中部分字段的含义如下:

  • name:总线名称;
  • bus_groups:总线属性;
  • dev_groups:该总线上所有设备的默认属性;
  • drv_groups:该总线上所有驱动的默认属性;
  • match:当有新的设备或驱动添加到总线上时match函数被调用,如果设备和驱动可以匹配,返回0;
  • uevent:当一个设备添加、移除或添加环境变量时,函数调用;
  • probe:当有新设备或驱动添加时,probe函数调用,并且回调该驱动的probe函数来初始化相关联的设备;
  • remove:设备移除时调用remove函数;
  • shutdown:设备关机时调用shutdown函数;
  • suspend:设备进入睡眠时调用suspend函数;
  • resume:设备唤醒时调用resume函数;
  • pm:总线的电源管理选项,并回调设备驱动的电源管理模块;
  • p: 驱动核心的私有数据,只有驱动核心才可以访问。使用struct subsys_private可以将struct bus_type中的部分细节屏蔽掉,利于外界使用bus_type;struct  driver_private和struct device_private都有类似的功能。

i2c_bus_type是 bus_type 类型的全局变量,这个变量已经被 linux 内核赋值好了,其结构体成员对应的函数也已经在内核里面写好,定义在drivers/i2c/i2c-core-base.c:

struct bus_type i2c_bus_type = {
        .name           = "i2c",
        .match          = i2c_device_match,
        .probe          = i2c_device_probe,
        .remove         = i2c_device_remove,
        .shutdown       = i2c_device_shutdown,
};

这里我们重点关注I2C匹配函数i2c_device_match即可。

2.2 I2C设备和驱动匹配

i2c_bus_type 中的 i2c_device_match就是我们常说的做驱动和设备匹配的函数,不同的总线对应的match函数肯定不一样,这个我们不用管,内核都会写好。我们所用的 I2C 总线对应的 match 函数是 i2c_device_match函数,该函数定义在drivers/i2c/i2c-core-base.c:

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


        /* Attempt an OF style match */
        if (i2c_of_match_device(drv->of_match_table, client))
                return 1;

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

        driver = to_i2c_driver(drv);

        /* Finally an I2C match */
        if (i2c_match_id(driver->id_table, client))
                return 1;

        return 0;
}

该函数有两个参数:设备和设备驱动,该函数主要做了一下事情:

  • 将设备转为I2C从设备类型;
  • 将驱动转为I2C驱动类型;
  • 调用i2c_of_driver_match_device进行设备树OF类型匹配;
  • 调用acpi_driver_match_device进行ACPI类型匹配;
  • 如果设置值了pdrv->id_table,进行id_table匹配;

通过对上面匹配函数的一个简单分析,我们知道匹配函数做匹配的顺序是先匹配设备树,然后匹配id_table表:

const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                                                const struct i2c_client *client)
{
        if (!(id && client))
                return NULL;

        while (id->name[0]) {
                if (strcmp(client->name, id->name) == 0)
                        return id;
                id++;
        }
        return NULL;
}

对于支持设备树的 Linux 版本,我们一上来做设备树匹配就完事,不支持设备树时,我们就得定义I2C设备,再用id_tabale表匹配。

2.3 I2C总线注册

I2C子系统的初始化是由i2c_init函数完成的。I2C总线以模块的方式注册到内核。i2c_init定义在drivers/i2c/i2c-core-base.c文件中:

static int __init i2c_init(void)
{
        int retval;

        retval = of_alias_get_highest_id("i2c");

        down_write(&__i2c_board_lock);
        if (retval >= __i2c_first_dynamic_bus_num)
                __i2c_first_dynamic_bus_num = retval + 1;
        up_write(&__i2c_board_lock);

        retval = bus_register(&i2c_bus_type);
        if (retval)
                return retval;

        is_registered = true;

#ifdef CONFIG_I2C_COMPAT
        i2c_adapter_compat_class = class_compat_register("i2c-adapter");
        if (!i2c_adapter_compat_class) {
                retval = -ENOMEM;
                goto bus_err;
        }
#endif
        retval = i2c_add_driver(&dummy_driver);
        if (retval)
                goto class_err;

        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
        if (IS_ENABLED(CONFIG_ACPI))
                WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

        return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
        class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
        is_registered = false;
        bus_unregister(&i2c_bus_type);
        return retval;
}

我们重点关注bus_register总线注册函数,传入的参数就是我们上面介绍的i2c_bus_type。

bus_register函数调用后,就会在用户空间/sys/bus目录下生成具有总线名称的目录,同时在该目录下创建devices、drivers文件夹,创建uevent、drivers_autoprobe、drivers_probe等文件。

执行如下命令:

/sys/bus/i2c/devices里用来存放的是I2C设备链接,/sys/bus/i2c/drivers里用来存放的是I2C驱动链接。需要注意的是在i2c_bus_type总线链表里面存放的除了I2C从设备,还有I2C适配器。

从其他博文找到一张总线创建的说明图,下图是foo总线创建的过程

三、I2C核心数据结构

学习I2C驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

3.1 struct i2c_adapter

struct i2c_adapter抽象了控制器硬件,在SOC中的指的就是内部I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。它的定义在 include/linux/i2c.h 文件,如下:

/*
 * 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        1

        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;
        const struct i2c_adapter_quirks *quirks;

        struct irq_domain *host_notify_domain;
};

其中部分参数含义如下:

  • algo:适配器与从设备的通信算法,即访问I2C总线的算法;
  • class:适配器支持的从设备的类型;
    • I2C_CLASS_HWMON:硬件监控类;
    • I2C_CLASS_DDC:数字显示通道,通常用于显示设备;
    • I2C_CLASS_SPD:内存设备;
    • I2C_CLASS_DEPRECATED:已废弃;不在使用的class;如果设置为这个,表示该I2C控制将不支持自动检测I2C从设备,即不再支持i2c_detect(这个后面会介绍);
    • ...
  • bus_lock:控制并发访问的互斥锁;
  • timeout:超时时间;为I2C控制器发送一次消息所需的最长时间,包括重传次数所消耗的总时间,如果在timeout时间内没有完成发送,则表示这次消息发送失败;
  • reties:重试次数;比如I2C控制器在传输过程中由于仲裁丢失、总线忙等情况而发送失败,则会根据retries的值来规定最大的重传次数;
  • dev:设备驱动模型中的设备,可以把i2c_adapter看做是device的子类;
    • bus为i2c_bus_type;
    • type为i2c_adapter_type :指定了适配器的类型,该类型中也定义了专有的属性文件;
    • kobj.name(设备名称)为i2c-%d指定在/sys/devices/platform/s3c2440-i2c.0目录下生成的适配器目录名;
  • nr:适配器的ID号;即I2C控制器的ID号,用来区分是属于SOC的第几个I2C控制器;
  • name:适配器名称;即I2C控制器的名字;
  • dev_released:用来实现同步,实现的机制为complete,作用跟信号量一样,只不过该信号解决了信号量在多进程操作中的一些缺点,在I2C控制器销毁过程中执行;
  • userspace_client:client链表头;专门存储i2c_client类型的设备,这些设备也就是像EEPROM、MPU6050这类的I2C设备。在设备树中定义了很多 IIC 设备,将来都会被转换成 i2c_client 类型的结构体,但是userspace_clients并不链接这些 I2C 设备。userspace_clients链接的是我们在应用层通过访问 I2C 控制器的属性文件创建的 I2C 设备;
  • userspace_clients_lock:就是在向链表添加过程中使用的互斥锁;
3.1.1 struct i2c_algorithm 

algo是struct i2c_adatper中的核心成员,它包含了操作SOC  I2C 控制器的函数集,也就是直接对接到实际的 SOC 的 I2C 控制器的操作(寄存器配置)。

algo类型为struct i2c_algorithm,定义在 include/linux/i2c.h 文件,如下:

/**
 * struct i2c_algorithm - represent I2C transfer method
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter
 *   defined by the msgs array, with num messages available to transfer via
 *   the adapter specified by adap.
 * @master_xfer_atomic: same as @master_xfer. Yet, only using atomic context
 *   so e.g. PMICs can be accessed very late before shutdown. Optional.
 * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
 *   is not present, then the bus layer will try and convert the SMBus calls
 *   into I2C transfers instead.
 * @smbus_xfer_atomic: same as @smbus_xfer. Yet, only using atomic context
 *   so e.g. PMICs can be accessed very late before shutdown. Optional.
 * @functionality: Return the flags that this algorithm/adapter pair supports
 *   from the I2C_FUNC_* flags.
 * @reg_slave: Register given client to I2C slave mode of this adapter
 * @unreg_slave: Unregister given client from I2C slave mode of this adapter
 *
 * The following structs are for those who like to implement new bus drivers:
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 *
 * The return codes from the @master_xfer{_atomic} fields should indicate the
 * type of error code that occurred during the transfer, as documented in the
 * Kernel Documentation file Documentation/i2c/fault-codes.
 */
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 (*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
};

其中部分参数含义如下:

  • master_xfer:为I2C控制器的消息传递函数,最终驱动层调用的i2c_transfer函数,就是将master_xfer进行一层封装。该函数返回成功处理的消息数,返回负数表示错误;
  • functionality:获取I2C控制器支持的功能,比如是否支持 I2C 总线或者 SMBUS;返回一个能力集,高速调用者该I2C适配器支持的功能:
    • I2C_FUNC_I2C:支持传统的I2C功能;
    • I2C_FUNC_10BIT_ADDR:支持10位地址;
    • I2C_FUNC_PROTOCOL_MANGLING:支持非标准的协议行为;
    • I2C_FUNC_NOSTART:支持不需要发送START信号的I2C传输;
    • I2C_FUNC_SMBUS_xzxx:SMBUS相关的功能;
    • .......

这两个参数都为函数指针,可以根据不同的 SOC 的 I2C 控制器编写相应的通信方法。

3.1.2 struct device

struct deivce这个结构已经很熟悉了,linux 系统中每一个设备都用 device 结构的一个实例来表示。i2c_adapter.dev部分成员会初始化为:

struct device dev                                                    // 适配器设备
{
    struct bus_type    *bus = &i2c_bus_type;                              
    struct device_type *type = &i2c_adapter_type;
    struct kobject kobj.name = "i2c-%d";    
struct device parent = &pdev->dev ; // 一般设置为platform设备dev ... }

i2c_bus_type为I2C总线,上面已经介绍过了。

i2c_adapter_type 表示该设备属于 i2c_adapter 设备类型,该结构体中包含了 i2c_adapter 中专有的属性文件,当设备注册成功后,将会在设备文件中生成相应的属性文件,这样就可以通过sysfs接口访问属性文件从而操作 I2C 设备。

kobj.name为i2c-%d:指定在/sys/devices/platform/s3c2440-i2c.0目录下生成的文件夹名称

3.2  struct i2c_board_info

struct i2c_board_info描述的是具体的I2C从设备,定义在include/linux/i2c.h文件中,如下:

/**
 * struct i2c_board_info - template for device creation
 * @type: chip type, to initialize i2c_client.name
 * @flags: to initialize i2c_client.flags
 * @addr: stored in i2c_client.addr
 * @dev_name: Overrides the default <busnr>-<addr> dev_name if set
 * @platform_data: stored in i2c_client.dev.platform_data
 * @of_node: pointer to OpenFirmware device node
 * @fwnode: device node supplied by the platform firmware
 * @properties: additional device properties for the device
 * @resources: resources associated with the device
 * @num_resources: number of resources in the @resources array
 * @irq: stored in i2c_client.irq
 *
 * I2C doesn't actually support hardware probing, although controllers and
 * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
 * a device at a given address.  Drivers commonly need more information than
 * that, such as chip type, configuration, associated IRQ, and so on.
 *
 * i2c_board_info is used to build tables of information listing I2C devices
 * that are present.  This information is used to grow the driver model tree.
 * For mainboards this is done statically using i2c_register_board_info();
 * bus numbers identify adapters that aren't yet available.  For add-on boards,
 * i2c_new_device() does this dynamically with the adapter already known.
 */
struct i2c_board_info {
        char            type[I2C_NAME_SIZE];
        unsigned short  flags;
        unsigned short  addr;
        const char      *dev_name;
        void            *platform_data;
        struct device_node *of_node;
        struct fwnode_handle *fwnode;
        const struct property_entry *properties;
        const struct resource *resources;
        unsigned int    num_resources;
        int             irq;
};

其中部分参数含义如下:

  • type:I2C从设备名称,后面I2C从设备驱动和I2C设备匹配时会用到,直接决定了I2C设备驱动能否匹配上进而进入probe探测函数;编写I2C设备驱动需要配置的参数;
  • flags:标志;
    • I2C_CLIENT_TEN:表示设备使用十位芯片地址;
    • I2C_CLIENT_PEC:表示它使用SMBus数据包错误检查;
    • I2C_CLIENT_SLAVA:从设备;
    • I2C_CLIENT_WAK:需要唤醒以启用节能功能时使用;
    • I2C_CLIENT_SCCB;
    • I2C_CLIENT_HOST_NOTIFY;
  • addr:从设备地址,7位地址不包含读写位,比如AT24C08地址为0x50;编写I2C设备驱动需要配置的参数;
  • dev_name:设备名称;
  • platform_data:在开发过程中,可用来存储一些数据;
  • of_node:从设备设备节点;
  • resources:资源,该设备的资源描述,指向一个资源数组;编写I2C从设备驱动一般不需要配置这个;
  • num_resource:资源的数量;编写I2C从设备驱动一般不需要配置这个;
  • irq:中断编号;编写I2C从设备驱动一般不需要配置这个;

i2c_board_info 中的大部分成员都是通过解析设备树获得,而i2c_board_info 将来会被转换成 i2c_client 类型,i2c_client 和 i2c_bard_info 内部很多成员都是相似的,i2c_client 中的很多成员变量值都继承于i2c_board_info。

  • type 将初始化 i2c_client.name,最后会与I2C从设备驱动中i2c_device_id的 name 做匹配,直接决定了I2C设备驱动能否匹配上进而进入probe探测函数;
  • addr 将初始化 i2c_client.addr;
  • flags 将初始化 i2c_client.flags;
  • platform_data 将初始化 i2c_client.dev.platform_data;
  • irq 将初始化 i2c_client.irq 。
3.2.1 i2c_register_board_info

在编写I2C设备驱动时,我们可以定义一个i2c_board_info 结构体,在结构体中包含有名字和设备地址,根据这些信息来构造一个i2c_client。

i2c_register_board_info函数定义如下:

/**
 * i2c_register_board_info - statically declare I2C devices
 * @busnum: identifies the bus to which these devices belong
 * @info: vector of i2c device descriptors
 * @len: how many descriptors in the vector; may be zero to reserve
 *      the specified bus number.
 *
 * Systems using the Linux I2C driver stack can declare tables of board info
 * while they initialize.  This should be done in board-specific init code
 * near arch_initcall() time, or equivalent, before any I2C adapter driver is
 * registered.  For example, mainboard init code could define several devices,
 * as could the init code for each daughtercard in a board stack.
 *
 * The I2C devices will be created later, after the adapter for the relevant
 * bus has been registered.  After that moment, standard driver model tools
 * are used to bind "new style" I2C drivers to the devices.  The bus number
 * for any device declared using this routine is not available for dynamic
 * allocation.
 *
 * The board info passed can safely be __initdata, but be careful of embedded
 * pointers (for platform_data, functions, etc) since that won't be copied.
 * Device properties are deep-copied though.
 */
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) // 第一个参数为I2C适配器编号
{
        int status;

        down_write(&__i2c_board_lock);

        /* dynamic bus numbers will be assigned after the last static one */
        if (busnum >= __i2c_first_dynamic_bus_num)
                __i2c_first_dynamic_bus_num = busnum + 1;

        for (status = 0; len; len--, info++) {
                struct i2c_devinfo      *devinfo;

                devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
                if (!devinfo) {
                        pr_debug("i2c-core: can't register boardinfo!\n");
                        status = -ENOMEM;
                        break;
                }

                devinfo->busnum = busnum;        // 设置I2C适配器编号
                devinfo->board_info = *info;     // 保存I2C从设备信息

                if (info->properties) {
                        devinfo->board_info.properties =
                                        property_entries_dup(info->properties);
                        if (IS_ERR(devinfo->board_info.properties)) {
                                status = PTR_ERR(devinfo->board_info.properties);
                                kfree(devinfo);
                                break;
                        }
                }

                if (info->resources) {
                        devinfo->board_info.resources =
                                kmemdup(info->resources,
                                        info->num_resources *
                                                sizeof(*info->resources),
                                        GFP_KERNEL);
                        if (!devinfo->board_info.resources) {
                                status = -ENOMEM;
                                kfree(devinfo);
                                break;
                        }
                }

                list_add_tail(&devinfo->list, &__i2c_board_list);   // 将当前元素添加到链表
        }

        up_write(&__i2c_board_lock);

        return status;
}

这里仅仅是将i2c_board_info追加到__i2c_board_list链表。当我们去注册一个I2C适配器时,会调用i2c_scan_static_board_info函数:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)  // 扫描i2c_board_info链表,
{
        struct i2c_devinfo      *devinfo;

        down_read(&__i2c_board_lock);
        list_for_each_entry(devinfo, &__i2c_board_list, list) {   
                if (devinfo->busnum == adapter->nr              // I2C从设备如果是连接在该I2C适配器上
                                && !i2c_new_device(adapter,     // I2C从设备注册
                                                &devinfo->board_info))
                        dev_err(&adapter->dev,
                                "Can't create device at 0x%02x\n",
                                devinfo->board_info.addr);
        }
        up_read(&__i2c_board_lock);
}

在该函数内部遍历__i2c_board_list链表,对链表的每一个成员都调用一个i2c_new_device,通过i2c_new_device构造一个i2c_client。

因此这种方法有个限制,使用前,必须在i2c_register_adapter之前调用 i2c_register_board_info注册i2c_board_info。

3.3 struct i2c_client

struct i2c_client抽象了连接到I2C总线上的I2C从设备,表示从设备的最终形态, 包含与从设备关联的完整信息。它内部成员的值很多来自于 i2c_board_info。它的定义在 include/linux/i2c.h 文件,如下:

/**
 * 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
 * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
 *      calls it to pass on slave events to the slave driver.
 *
 * 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 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      */
#endif
};

其中部分参数含义如下:

  • flags:标志;
    • I2C_CLIENT_TEN:表示设备使用10位设备地址;
    • I2C_CLIENT_PEC:表示它使用SMBus数据包错误检查;
    • I2C_CLIENT_SLAVA:从设备;
    • I2C_CLIENT_WAK:需要唤醒以启用节能功能时使用;
    • I2C_CLIENT_SCCB;
    • I2C_CLIENT_HOST_NOTIFY;
  • addr:I2C从设备地址,7位地址不包含读写位,比如AT24C08地址为0x50;
  • name:设备名称,后面I2C从设备驱动和I2C设备匹配时会用到,直接决定了I2C设备驱动能否匹配上进而进入probe探测函数;
  • adapter:I2C从设备连接到SOC的I2C控制器;
  • dev:设备驱动模型中的设备,可以把i2c_client看做是device的子类;
    • parent为&client->adapter->dev,建立从设备与I2C控制器的父子关系;这样设备注册时将会在/sys/devices/platform/s3c2440-i2c.0/i2c-0目录下生成从设备目录;
    • bus为i2c_bus_type;
    • type为i2c_client_type:指定了从设备的类型,该类型中也定义了从设备专有的属性文件;
    • kobj.name(设备名称)为%d-%04x,第一个数字为I2C适配器编号,第二个数字为从设备地址:指定在/sys/devices/platform/s3c2440-i2c.0/i2c-0目录下生成的从设备目录名;
  • init_irq:初始化时设置的中断编号;
  • irq:I2C从设备所使用的的中断编号;
  • detected:用于构建双向链表,在应用层创建的 I2C 从设备就是通过该成员链接到i2c_adapter中的userspace_clients;

3.4 struct i2c_devinfo

struct i2c_devinfo 和 struct i2c_board_info 类似,同样用于描述具体的 I2C 从设备,它的定义在drivers/i2c/i2c-core.h文件,如下:

struct i2c_devinfo {
        struct list_head        list;   // 用于构建双向链表
        int                     busnum;  // I2C控制器编号
        struct i2c_board_info   board_info;
};

i2c_devinfo 中包含了 i2c_board_info,那么这两者有什么联系呢?

  • 其实 i2c_devinfo 在如今采用设备树的内核驱动已经很少被用到,在还没有设备树时,都是开发人员自己去定义一个 i2c_devinfo ,然后通过 i2c_register_board_info 函数注册到 i2c_board_list 总链表上;
  • 最后在注册 I2C 控制器的时候,遍历 i2c_board_list 总链表,将上面挂接的每个 i2c_devinfo 转换为 i2c_client;
  • 然而在如今设备树的内核版本,都是解析设备树生成 i2c_board_info,然后把 i2c_board_info 转换为 i2c_client。

busnum表示该从设备是属于SOC哪一个 I2C控制器,list将被挂接到 i2c_board_list 总链表上。

3.5 struct i2c_msg

struct i2c_msg描述一个I2C传输的数据,定义在 include/uapi/linux/i2c.h文件,如下:

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 * @addr: Slave address, either seven or ten bits.  When this is a ten
 *      bit address, I2C_M_TEN must be set in @flags and the adapter
 *      must support I2C_FUNC_10BIT_ADDR.
 * @flags: I2C_M_RD is handled by all adapters.  No other flags may be
 *      provided unless the adapter exported the relevant I2C_FUNC_*
 *      flags through i2c_check_functionality().
 * @len: Number of data bytes in @buf being read from or written to the
 *      I2C slave address.  For read transactions where I2C_M_RECV_LEN
 *      is set, the caller guarantees that this buffer can hold up to
 *      32 bytes in addition to the initial length byte sent by the
 *      slave (plus, if used, the SMBus PEC); and this value will be
 *      incremented by the number of block data bytes received.
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they
 * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
 */
struct i2c_msg {
        __u16 addr;     /* slave address                        */
        __u16 flags;
#define I2C_M_RD                0x0001  /* read data, from slave to master */
                                        /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE          0x0200  /* the buffer of this message is DMA safe */
                                        /* makes only sense in kernelspace */
                                        /* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
        __u16 len;              /* msg length                           */
        __u8 *buf;              /* pointer to msg data                  */
};

I2C传输数据都是以i2c_msg类型为一个数据包,参数:

  • addr:从设备地址;7位地址,比如AT24C08地址为0x50;
  • flags:标志位;
    • I2C_M_RD:此次传输是读操作;
    • I2C_M_REV_DIR_ADDR:将读写flag翻转,即读的时候发W信号,写的时候发R信号;
    • I2C_M_IGNORE_NAK:写操作的时候,忽略从设备返回的NA;
    • I2C_M_NO_RD_ACK:读操作的时候,忽略所有的NACK/ACK信号;
    • I2C_M_NOSTART: 读写混合操作的情况下,假如要传输多个msg(以2个为例),如果第二个msg携带了该标志,则不再发送起始信号和从设备地址;
    • I2C_M_RECV_LEN: SMBUS的一个flag;
    • ....
  • buf:发送或接收的数据;需要注意,如果是发送数据,buf里存放需要发送的数据即可,不包含从设备地址;
  • len:发送或接收的数据长度;

3.6 struct i2c_driver

struct i2c_driver描述一个I2C设备驱动,定义在 include/linux/i2c.h文件,如下:

/**
 * struct i2c_driver - represent an I2C device driver
 * @class: What kind of i2c device we instantiate (for detect)
 * @probe: Callback for device binding - soon to be deprecated
 * @probe_new: New callback for device binding
 * @remove: Callback for device unbinding
 * @shutdown: Callback for device shutdown
 * @alert: Alert callback, for example for the SMBus alert protocol
 * @command: Callback for bus-wide signaling (optional)
 * @driver: Device driver model driver
 * @id_table: List of I2C devices supported by this driver
 * @detect: Callback for device detection
 * @address_list: The I2C addresses to probe (for detect)
 * @clients: List of detected clients we created (for i2c-core use only)
 * @disable_i2c_core_irq_mapping: Tell the i2c-core to not do irq-mapping
 *
 * The driver.owner field should be set to the module owner of this driver.
 * The driver.name field should be set to the name of this driver.
 *
 * For automatic device detection, both @detect and @address_list must
 * be defined. @class should also be set, otherwise only devices forced
 * with module parameters will be created. The detect function must
 * fill at least the name field of the i2c_board_info structure it is
 * handed upon successful detection, and possibly also the flags field.
 *
 * If @detect is missing, the driver will still work fine for enumerated
 * devices. Detected devices simply won't be supported. This is expected
 * for the many I2C/SMBus devices which can't be detected reliably, and
 * the ones which can always be enumerated in practice.
 *
 * The i2c_client structure which is handed to the @detect callback is
 * not a real i2c_client. It is initialized just enough so that you can
 * call i2c_smbus_read_byte_data and friends on it. Don't do anything
 * else with it. In particular, calling dev_dbg and friends on it is
 * not allowed.
 */
struct i2c_driver {
        unsigned int class;

        /* Standard driver model interfaces */
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);

        /* New driver model interface to aid the seamless removal of the
         * current probe()'s, more commonly unused than used second parameter.
         */
        int (*probe_new)(struct i2c_client *);

        /* driver model interfaces that don't relate to enumeration  */
        void (*shutdown)(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").
         * For the SMBus Host Notify protocol, the data corresponds to the
         * 16-bit payload data reported by the slave device acting as master.
         */
        void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
                      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;

        bool disable_i2c_core_irq_mapping;
};

其中部分参数含义如下:

  • class:表示当前从设备驱动使用的适配器的类型,它将与上面 i2c_adapter的class进行匹配判断;
  • probe:当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
  • remove:设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中;
  • driver:驱动基类,内核维护的所有的驱动必须包含该成员;
    • bus为i2c_bus_type;
  • id_table:往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中;
  • address_list:指向从设备地址表,该表存放了需要创建的从设备地址,该表由开发人员自己定义;

如果是有新的适配器注册,将会遍历I2C 总线上的所有I2C驱动,将新的i2c_adapter中的class成员与遍历到的i2c_driver的class成员匹配:

  • 如果匹配成功,将会使用i2c_detect_address函数来确定能否找到address_list里的从设备,如果找到了,则会回调detect方法,并会自动注册新的i2c_client从设备。

其中clients就是专门链接此过程中创建的i2c_client从设备。

如果是有新的I2C设备驱动注册,将会遍历I2C总线上的所有适配器,查看有没有适配器与自身i2c_driver的class成员匹配,如果有,则与上面说的过程一样。

这里为什么要提供一个2C从设备自动探测的功能的目的是什么呢?比如在一些场景下,一个SOC可能存在多个I2C控制器,同时连接了多个I2C从设备,在不知道I2C从设备分别连接到哪个I2C控制器上的时候,我们就可以使用这种方法。当我们去注册一个I2C适配器或I2C设备驱动驱动时就会触发这个自动探测的流程,根据我们驱动中的地址,去判断I2C设备是否存在,如果存在,就自动将注册发现的I2C从设备,这样就可以省去我们手动注册I2C从设备的过程。

需要注意的是,当I2C适配器开启了自动探测时,将会调用i2c_detect_address去判断从设备地址的合法性(包括地址校验,通过I2C通信是否有响应等手段),这部分代码内核已经帮我们实现了,我们要做的只是在从设备探测成功后,在detect回调函数中进行一些初始化工作,比如必须初始化i2c_board_info的type成员。

3.7 数据结构关系图

下图展示了i2c_client、i2c_driver、i2c_adapter、i2c_bus_type以及相关注册函数之间的关系:

四、I2C框架API

I2C框架对外提供的API几乎都可以在include/linux/i2c.h中找,其实现代码一般都位于drivers/i2c/i2c-core-base.c文件,主要都是围着上面数据结构进行的。

4.1 I2C子系统初始化

I2C子系统的初始化函数i2c_init,定义在drivers/i2c/i2c-core-base.c文件中:

static int __init i2c_init(void)
{
        int retval;

        retval = of_alias_get_highest_id("i2c");        // 获取SOC中I2C控制器个数

        down_write(&__i2c_board_lock);
        if (retval >= __i2c_first_dynamic_bus_num)
                __i2c_first_dynamic_bus_num = retval + 1;
        up_write(&__i2c_board_lock);

        retval = bus_register(&i2c_bus_type);     // I2C总线注册,这个上面已经介绍过了,会sys文件系统创建/sys/bus/i2c目录以及相关文件
        if (retval)
                return retval;

        is_registered = true;    // 标志位

#ifdef CONFIG_I2C_COMPAT
        i2c_adapter_compat_class = class_compat_register("i2c-adapter");   // class注册 会在sys文件系统创建/sys/class/i2c-adapter目录
        if (!i2c_adapter_compat_class) {
                retval = -ENOMEM;
                goto bus_err;
        }
#endif
        retval = i2c_add_driver(&dummy_driver);         // 注册dummy_driver驱动 
        if (retval)
                goto class_err;

        if (IS_ENABLED(CONFIG_OF_DYNAMIC))  // 使能设备树 
                WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
        if (IS_ENABLED(CONFIG_ACPI))        // 使用CPI
                WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

        return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
        class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
        is_registered = false;
        bus_unregister(&i2c_bus_type);
        return retval;
}

4.2 I2C适配器注册

下图显示了注册I2C适配器设备和检测I2C总线上I2C设备的过程:

当有新的适配器注册,将会遍历I2C总线上的所有设备驱动,将新的i2c_adapter中的class成员与遍历到的i2c_driver的class成员匹配:如果匹配成功,将会调用i2c_detect_address函数(在该函数中使用当前i2c_adapter发送起始信号和从设备地址并得到ACK响应来判定从设备是否存在)来确定能否找到address_list里的从设备,如果找到了,则将自动注册新的i2c_client从设备。

i2c_add_adapter用于I2C适配器注册,定义在drivers/i2c/i2c-core-base.c文件中:

/**
 * i2c_add_adapter - declare i2c adapter, use dynamic bus number
 * @adapter: the adapter to add
 * Context: can sleep
 *
 * This routine is used to declare an I2C adapter when its bus number
 * doesn't matter or when its bus number is specified by an dt alias.
 * Examples of bases when the bus number doesn't matter: I2C adapters
 * dynamically added by USB links or PCI plugin cards.
 *
 * When this returns zero, a new bus number was allocated and stored
 * in adap->nr, and the specified adapter became available for clients.
 * Otherwise, a negative errno value is returned.
 */
int i2c_add_adapter(struct i2c_adapter *adapter)
{
        struct device *dev = &adapter->dev;  // 获取适配器device对象
        int id;

        if (dev->of_node) {     // 设备节点
                id = of_alias_get_id(dev->of_node, "i2c");   // 如果设备树中存在i2c节点,并且指定了id,则使用该编号注册适配器
                if (id >= 0) {
                        adapter->nr = id;
                        return __i2c_add_numbered_adapter(adapter);  //在i2c_adapter_idr树中新增节点,节点ID为id
                }
        }

        mutex_lock(&core_lock);      // 获取互斥锁
        id = idr_alloc(&i2c_adapter_idr, adapter,  // 为i2c_adapter_idr树新增一个节点,该节点在i2c_adapter_idr树中有唯一的节点ID,并且将这个节点和adapter关联,通过id就可以定位到这个adapter
                       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
        mutex_unlock(&core_lock);   // 释放互斥锁
        if (WARN(id < 0, "couldn't get idr"))
                return id;

        adapter->nr = id;

        return i2c_register_adapter(adapter);  // 注册I2C适配器
}

从上面我们可以看到i2c_add_adapter与i2c_register_adapter唯一的区别在于,i2c_add_adapter为I2C适配器分配了一个唯一的编号。

4.2.1 i2c_register_adapter

i2c_register_adapter用于I2C适配器注册,定义在drivers/i2c/i2c-core-base.c文件中:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
        int res = -EINVAL;

        /* Can't register until after driver model init */
        if (WARN_ON(!is_registered)) {  // i2c_init()执行前,都是未注册状态
                res = -EAGAIN;
                goto out_list;
        }

        /* Sanity checks */
        if (WARN(!adap->name[0], "i2c adapter has no name"))  // 未指定I2C适配器名称
                goto out_list;

        if (!adap->algo) {    // 未指定I2C算法
                pr_err("adapter '%s': no algo supplied!\n", adap->name);
                goto out_list;
        }

        if (!adap->lock_ops)    // 未初始化lock_ops
                adap->lock_ops = &i2c_adapter_lock_ops;

        adap->locked_flags = 0;
        rt_mutex_init(&adap->bus_lock);       // 初始化各种锁
        rt_mutex_init(&adap->mux_lock);
        mutex_init(&adap->userspace_clients_lock);    
        INIT_LIST_HEAD(&adap->userspace_clients);  // 初始化双向链表

        /* Set default timeout to 1 second if not already set */
        if (adap->timeout == 0)    // 设定超时时间,默认为1s
                adap->timeout = HZ;

        /* register soft irqs for Host Notify */
        res = i2c_setup_host_notify_irq_domain(adap);           // 注册软中断
        if (res) {
                pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",
                       adap->name, res);
                goto out_list;
        }
        // 初始化适配器device成员
        dev_set_name(&adap->dev, "i2c-%d", adap->nr); // 设定适配器dev.kobj.name为i2c-编号
        adap->dev.bus = &i2c_bus_type;
        adap->dev.type = &i2c_adapter_type;
        res = device_register(&adap->dev); // 注册设备 会在sys文件系统下创建/sys/devices/platform/s3c2440-i2c.0/i2c-0目录,并创建链接文件/sys/bus/i2c/devices/i2c-0指向该目录
        if (res) {
                pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
                goto out_list;
        }

        res = of_i2c_setup_smbus_alert(adap);
        if (res)
                goto out_reg;

        dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

        pm_runtime_no_callbacks(&adap->dev); 
        pm_suspend_ignore_children(&adap->dev, true);
        pm_runtime_enable(&adap->dev);

#ifdef CONFIG_I2C_COMPAT
        res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,   // 在sys文件系统创建链接 /sys/class/i2c-adapter/i2c-0指向/sys/devices/platform/s3c2440-i2c.0/i2c.0
                                       adap->dev.parent);
        if (res)
                dev_warn(&adap->dev,
                         "Failed to create compatibility class link\n");
#endif

        i2c_init_recovery(adap);

        /* create pre-declared device nodes */ 
        of_i2c_register_devices(adap);            // 通过设备树节点注册该控制器下的所有从设备
        i2c_acpi_register_devices(adap);         //通过mach文件注册该控制器下的所有从设备
        i2c_acpi_install_space_handler(adap);

        if (adap->nr < __i2c_first_dynamic_bus_num)
                i2c_scan_static_board_info(adap);   // 扫描i2c_board_info链表,注册I2C从设备

        /* Notify drivers */
        mutex_lock(&core_lock);
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);  //遍历i2c总线i2c_bus_type驱动链表上的每个驱动,对每个驱动调用__process_new_adapter
        mutex_unlock(&core_lock);

        return 0;

out_reg:
        init_completion(&adap->dev_released);
        device_unregister(&adap->dev);
        wait_for_completion(&adap->dev_released);
out_list:
        mutex_lock(&core_lock);
        idr_remove(&i2c_adapter_idr, adap->nr);
        mutex_unlock(&core_lock);
        return res;
}
4.2.2 device_register

device_register函数主要做了以下三件事情:

  • 在sys文件系统下创建/sys/devices/platform/s3c2440-i2c.0/i2c-0目录;创建链接文件/sys/bus/i2c/devices/i2c-0指向/sys/devices/platform/s3c2440-i2c.0/i2c-0;
  • 将I2C适配器设备挂载到I2C总线i2c_bus_type的设备链表上;
  • 遍历I2C总线i2c_bus_type的驱动链表中每个I2C驱动,如果匹配将会指向驱动的probe函数;
4.2.3 of_i2c_register_devices

of_i2c_register_devices定义在drivers/i2c/i2c-core-of.c,通过设备树节点注册该控制器下的所有从设备;

void of_i2c_register_devices(struct i2c_adapter *adap)
{
        struct device_node *bus, *node;
        struct i2c_client *client;

        /* Only register child devices if the adapter has a node pointer set */
        if (!adap->dev.of_node)   // 如果没有使用设备树,直接返回
                return;

        dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

        bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");  // 如果使用设备树,则获取设备节点i2c@54000000子节点i2c-bus
        if (!bus)         // i2c-bus节点不存在,则进入
                bus = of_node_get(adap->dev.of_node);       // 获取平台设备节点,即设备节点i2c@54000000

        for_each_available_child_of_node(bus, node) {   // 遍历总线节点下的子设备节点,从而达到注册每一个子设备的作用 
                if (of_node_test_and_set_flag(node, OF_POPULATED))
                        continue;

                client = of_i2c_register_device(adap, node);  // 注册I2C从设备
                if (IS_ERR(client)) {
                        dev_err(&adap->dev,
                                 "Failed to create I2C device for %pOF\n",
                                 node);
                        of_node_clear_flag(node, OF_POPULATED);
                }
        }

        of_node_put(bus);
}

如果使用了设备树,这里实际上就是解析I2C控制器设备节点的子设备节点,从而调用of_i2c_register_device完成I2C从设备的注册,比如 i2c@54000000设备节点:

i2c@54000000 {
    compatible = "samsung,s3c2440-i2c";
    clocks = <&clocks PCLK_I2C>;
    clock-names = "i2c";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c0_bus>;
    status = "okay";
    samsung,i2c-max-bus-freq = <200000>;

    eeprom@50 {
        compatible = "24c02";
        reg = <0x50>;
        pagesize = <32>;
        status = "okay";
    };
};

of_i2c_register_device函数定义在drivers/i2c/i2c-core-of.c:

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,   // I2C控制器
                                                 struct device_node *node)   // I2C从设备节点,比如eeprom@50
{
        struct i2c_client *client;
        struct i2c_board_info info;
        int ret;

        dev_dbg(&adap->dev, "of_i2c: register %pOF\n", node);

        ret = of_i2c_get_board_info(&adap->dev, node, &info);
        if (ret)
                return ERR_PTR(ret);

        client = i2c_new_device(adap, &info);   // 注册I2C从设备
        if (!client) {
                dev_err(&adap->dev, "of_i2c: Failure registering %pOF\n", node);
                return ERR_PTR(-EINVAL);
        }
        return client;
}

首先调用of_i2c_get_board_info解析I2C从设备节点,从而初始化info成员;

int of_i2c_get_board_info(struct device *dev, struct device_node *node,  // I2C从设备节点,比如eeprom@50
                          struct i2c_board_info *info)    // 保存I2C从设备信息
{
        u32 addr;
        int ret;

        memset(info, 0, sizeof(*info));

// 找到i2c控制器的子节点的compatible字段,如果有",",取","后面的字段,否则取compatible字段的值,因此这里最终把"24c02"返回,赋值在info.type变量中
if (of_modalias_node(node, info->type, sizeof(info->type)) < 0) { dev_err(dev, "of_i2c: modalias failure on %pOF\n", node); return -EINVAL; } ret = of_property_read_u32(node, "reg", &addr); // 获取reg属性值,比如0x50,即I2C从设备地址(位[0-6],不包含读写位) if (ret) { dev_err(dev, "of_i2c: invalid reg on %pOF\n", node); return ret; } if (addr & I2C_TEN_BIT_ADDRESS) { // 宏的值为1<<31 10位设备地址 addr &= ~I2C_TEN_BIT_ADDRESS; info->flags |= I2C_CLIENT_TEN; } if (addr & I2C_OWN_SLAVE_ADDRESS) { // 宏的值1<<30 addr &= ~I2C_OWN_SLAVE_ADDRESS; info->flags |= I2C_CLIENT_SLAVE; } info->addr = addr; // 设置I2C从设备地址 info->of_node = node; // I2C设备设备节点 if (of_property_read_bool(node, "host-notify")) info->flags |= I2C_CLIENT_HOST_NOTIFY; if (of_get_property(node, "wakeup-source", NULL)) info->flags |= I2C_CLIENT_WAKE; return 0; }

然后调用i2c_new_device注册I2C从设备,这个函数后面介绍。

4.2.4 bus_for_each_drv

调用bus_for_each_drv遍历I2C总线i2c_bus_type的驱动链表中的每个I2C驱动,对每个驱动调用__process_new_adapter,告诉每个驱动有新的I2C适配器注册,该函数定义在drivers/base/bus.c,代码如下:

/**
 * bus_for_each_drv - driver iterator
 * @bus: bus we're dealing with.
 * @start: driver to start iterating on.
 * @data: data to pass to the callback.
 * @fn: function to call for each driver.
 *
 * This is nearly identical to the device iterator above.
 * We iterate over each driver that belongs to @bus, and call
 * @fn for each. If @fn returns anything but 0, we break out
 * and return it. If @start is not NULL, we use it as the head
 * of the list.
 *
 * NOTE: we don't return the driver that returns a non-zero
 * value, nor do we leave the reference count incremented for that
 * driver. If the caller needs to know that info, it must set it
 * in the callback. It must also be sure to increment the refcount
 * so it doesn't disappear before returning to the caller.
 */
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
                     void *data, int (*fn)(struct device_driver *, void *))
{
        struct klist_iter i;
        struct device_driver *drv;
        int error = 0;

        if (!bus)
                return -EINVAL;

        klist_iter_init_node(&bus->p->klist_drivers, &i,   // 遍历bus的驱动链表
                             start ? &start->p->knode_bus : NULL);
        while ((drv = next_driver(&i)) && !error)
                error = fn(drv, data);
        klist_iter_exit(&i);
        return error;
}
4.2.5 __process_new_adapter

__process_new_adapter函数定义在drivers/i2c/i2c-core-base.c文件中:

static int __process_new_adapter(struct device_driver *d, void *data)
{
        return i2c_do_add_adapter(to_i2c_driver(d), data);
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
                              struct i2c_adapter *adap)
{
        /* Detect supported devices on that bus, and instantiate them */
        i2c_detect(adap, driver);   

        return 0;
}

这里调用i2c_detect在当前适配器对应的的I2C总线上,进行I2C设备的检测。

4.2.6 i2c_detect

i2c_detect函数定义在/i2c/i2c-core-base.c文件:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
        const unsigned short *address_list;
        struct i2c_client *temp_client;
        int i, err = 0;
        int adap_id = i2c_adapter_id(adapter);

        address_list = driver->address_list;   // 获取驱动中指定的地址列表
        if (!driver->detect || !address_list)   // 未指定detect、address_list 返回
                return 0;

        /* Warn that the adapter lost class based instantiation */
        if (adapter->class == I2C_CLASS_DEPRECATED) {  // I2C控制将不支持自动检测I2C从设备  输出调试消息,并退出 
                dev_dbg(&adapter->dev,
                        "This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
                        "If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",
                        driver->driver.name);
                return 0;
        }

        /* Stop here if the classes do not match */
        if (!(adapter->class & driver->class))     // 适配器和I2C驱动所属class不匹配
                return 0;

        /* Set up a temporary client to help detect callback */
        temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);   // 创建一个i2c_client
        if (!temp_client)
                return -ENOMEM;
        temp_client->adapter = adapter;      // 指定其所属适配器

        for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {  // 遍历地址列表
                dev_dbg(&adapter->dev,
                        "found normal entry for adapter %d, addr 0x%02x\n",
                        adap_id, address_list[i]);
                temp_client->addr = address_list[i];  // 为其分配从设备地址
                err = i2c_detect_address(temp_client, driver);  // 检测从设备地址是否可用
                if (unlikely(err))
                        break;
        }

        kfree(temp_client);
        return err;
}

将新的i2c_adapter中的class成员与遍历到的i2c_driver的class成员匹配:如果匹配成功,用i2c_detect_address函数来确定能否找到address_list里的从设备。

4.2.7 i2c_detect_address

i2c_detect_address函数定义在drivers/i2c/i2c-core-base.c文件:

static int i2c_detect_address(struct i2c_client *temp_client,
                              struct i2c_driver *driver)
{
        struct i2c_board_info info;
        struct i2c_adapter *adapter = temp_client->adapter;
        int addr = temp_client->addr;
        int err;

        /* Make sure the address is valid */
        err = i2c_check_7bit_addr_validity_strict(addr);  // 检测目标地址,不能超出0x08~0x77范围  成功返回0
        if (err) {
                dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
                         addr);
                return err;
        }

        /* Skip if already in use (7 bit, no need to encode flags) */
        if (i2c_check_addr_busy(adapter, addr))   // 目标地址检查 好像是和mux有关,忽略即可
                return 0;

        /* Make sure there is something at this address */
        if (!i2c_default_probe(adapter, addr))      // 进行目标地址探测,通过I2C通信检查这个地址是否有回应   探测成功范围1,失败返回0
                return 0;

        /* Finally call the custom detection function */
        memset(&info, 0, sizeof(struct i2c_board_info));   // 初始化info,全部填充0
        info.addr = addr;                                  // 设置从设备地址  
        err = driver->detect(temp_client, &info);          // 设备探测成功后的回调函数
        if (err) {
                /* -ENODEV is returned if the detection fails. We catch it
                   here as this isn't an error. */
                return err == -ENODEV ? 0 : err;
        }

        /* Consistency check */
        if (info.type[0] == '\0') {
                dev_err(&adapter->dev,
                        "%s detection function provided no name for 0x%x\n",
                        driver->driver.name, addr);
        } else {              // 探测到I2C从设备,注册I2C从设备
                struct i2c_client *client;

                /* Detection succeeded, instantiate the device */
                if (adapter->class & I2C_CLASS_DEPRECATED)
                        dev_warn(&adapter->dev,
                                "This adapter will soon drop class based instantiation of devices. "
                                "Please make sure client 0x%02x gets instantiated by other means. "
                                "Check 'Documentation/i2c/instantiating-devices' for details.\n",
                                info.addr);

                dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",
                        info.type, info.addr);
                client = i2c_new_device(adapter, &info);   // 注册I2C从设备
                if (client)
                        list_add_tail(&client->detected, &driver->clients);  // 将当前I2C设备添加到driver->clients链表
                else
                        dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
                                info.type, info.addr);
        }
        return 0;
}

在这个函数里面首先检查有效性、是否被使用、是否有设备回应,之后初始化了i2c_board_info结构,注意只初始化了地址(实例化设备必须还要名字),然后调用了i2c驱动中的detect函数,如果成功则调用 i2c_new_device函数真正实例化i2c设备,并且将i2c设备挂在i2c驱动的链表上。

仔细思考上面就能发现,I2C驱动中的detect函数必须要填写i2c_board_info结构体中type,i2c_new_device才能实例化i2c设备。

4.3 I2C从设备注册

i2c_new_device函数定义在drivers/i2c/i2c-core-base.c文件中:

/**
 * i2c_new_device - instantiate an i2c device
 * @adap: the adapter managing the device
 * @info: describes one I2C device; bus_num is ignored
 * Context: can sleep
 *
 * This deprecated function has the same functionality as
 * @i2c_new_client_device, it just returns NULL instead of an ERR_PTR in case of
 * an error for compatibility with current I2C API. It will be removed once all
 * users are converted.
 *
 * This returns the new i2c client, which may be saved for later use with
 * i2c_unregister_device(); or NULL to indicate an error.
 */
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client *ret;

        ret = i2c_new_client_device(adap, info);
        return IS_ERR(ret) ? NULL : ret;
}

/**
 * i2c_new_client_device - instantiate an i2c device
 * @adap: the adapter managing the device
 * @info: describes one I2C device; bus_num is ignored
 * Context: can sleep
 *
 * Create an i2c device. Binding is handled through driver model
 * probe()/remove() methods.  A driver may be bound to this device when we
 * return from this function, or any later moment (e.g. maybe hotplugging will
 * load the driver module).  This call is not appropriate for use by mainboard
 * initialization logic, which usually runs during an arch_initcall() long
 * before any i2c_adapter could exist.
 *
 * This returns the new i2c client, which may be saved for later use with
 * i2c_unregister_device(); or an ERR_PTR to describe the error.
 */
static struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client       *client;
        int                     status;

        client = kzalloc(sizeof *client, GFP_KERNEL);   // 申请i2c_client
        if (!client)
                return ERR_PTR(-ENOMEM);

        client->adapter = adap;   // 设置其I2C适配器

        client->dev.platform_data = info->platform_data;  // 使用info初始化client
        client->flags = info->flags;
        client->addr = info->addr;

        client->init_irq = info->irq;
        if (!client->init_irq)   // 如果未设定中断号,将从资源中查找中断
                client->init_irq = i2c_dev_irq_from_resources(info->resources,
                                                         info->num_resources);
        client->irq = client->init_irq;

        strlcpy(client->name, info->type, sizeof(client->name));  // 设置client->name名称

        status = i2c_check_addr_validity(client->addr, client->flags);  // 检查地址是否有效 如果是10位地址,则地址不能超过0x3ff;如果是7位地址,则地址不能超过0x01到0x7f
        if (status) {
                dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                        client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
                goto out_err_silent;
        }

        /* Check for address business */
        status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));  // 检查地址 
        if (status)
                goto out_err;

        client->dev.parent = &client->adapter->dev;   // 初始化设备基类dev,因此是从设备被是挂载适配器设备目录下 /sys/devices/platform/s3c2440-i2c.0/i2c-0/设备名称
        client->dev.bus = &i2c_bus_type;
        client->dev.type = &i2c_client_type;
        client->dev.of_node = of_node_get(info->of_node);
        client->dev.fwnode = info->fwnode;

        i2c_dev_set_name(adap, client, info);    // 设置client->dev.kobj.name为info->dev_name或者i2c-%d或者%d-%04x
        if (info->properties) {
                status = device_add_properties(&client->dev, info->properties);
                if (status) {
                        dev_err(&adap->dev,
                                "Failed to add properties to client %s: %d\n",
                                client->name, status);
                        goto out_err_put_of_node;
                }
        }

        status = device_register(&client->dev);   // 注册设备 会在sys文件系统下创建/sys/devices/platform/s3c2440-i2c.0/i2c-0/%d-%04x目录
if (status) goto out_free_props; dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); return client; out_free_props: if (info->properties) device_remove_properties(&client->dev); out_err_put_of_node: of_node_put(info->of_node); out_err: dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x (%d)\n", client->name, client->addr, status); out_err_silent: kfree(client); return ERR_PTR(status); }

4.4 I2C驱动注册

 i2c_register_driver用于I2C驱动注册,定义在drivers/i2c/i2c-core-base.c文件中:

/*
 * An i2c_driver is used with one or more i2c_client (device) nodes to access
 * i2c slave chips, on a bus instance associated with some i2c_adapter.
 */

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
        int res;

        /* Can't register until after driver model init */
        if (WARN_ON(!is_registered))     // i2c_init()执行前,都是未注册状态
                return -EAGAIN;

        /* add the driver to the list of i2c drivers in the driver core */
        driver->driver.owner = owner;
        driver->driver.bus = &i2c_bus_type;    // 驱动总线
        INIT_LIST_HEAD(&driver->clients);      // 初始化I2C从设备链表 

        /* When registration returns, the driver core
         * will have called probe() for all matching-but-unbound devices.
         */
        res = driver_register(&driver->driver);  // 驱动注册
        if (res)
                return res;

        pr_debug("driver [%s] registered\n", driver->driver.name);

        /* Walk the adapters that are already present */
        i2c_for_each_dev(driver, __process_new_driver);  // 遍历I2C总线上的所有设备

        return 0;
}

这个函数首先执行了I2C驱动的注册,然后调用i2c_for_each_dev遍历I2C总线i2c_bus_type上的所有设备,然后根据dev->type过滤出适配器。

对每个适配器调用__process_new_driver,查看有没有适配器与自身i2c_driver的class成员匹配。

如果匹配成功,将会使用i2c_detect_address函数来确定能否找到address_list里的从设备,如果找到了,则会回调detect方法,并会自动注册新的i2c_client从设备。

__process_new_driver函数定义在drivers/i2c/i2c-core-base.c文件中:

static int __process_new_driver(struct device *dev, void *data)
{
        if (dev->type != &i2c_adapter_type)
                return 0;
        return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

i2c_do_add_adatper之前已经介绍过了,不再重复了。

4.5 I2C从设备和驱动注册流程

可以看到无论是I2C从设备注册还是驱动注册,当I2C从设备和I2C驱动匹配成功,函数最终执行了i2c_device_probe,而在i2c_device_probe中调用了I2C驱动的probe/probe_new函数,一般我们会在probe函数中实现一套I2C从设备字符设备驱动逻辑。

如果单独看上面这张图,这张图是没有关于I2C适配器的任何信息的,如果我们仅仅注册I2C从设备、I2C驱动,不注册I2C适配器可以么,显然是不行的,我们回归一下i2c_new_device以及i2c_register_driver函数参数:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

其中I2C从设备i2c_client的注册是依赖于I2C适配器的,也就是说我们必须要指定I2C从设备所连接的I2C适配器,只有这样我们才能通过I2C适配器来对I2C从设备进行数据的读写操作。

五、I2C字符设备驱动的注册

在前面介绍了I2C适配器、从设备以及驱动的注册。那问题来了,应用层如何去访问从设备呢,在过去我们一般都是在应用层通过open函数访问设备节点文件。

同理内核为我们提供了应用层操作I2C从设备的方式,可以让我们在应用层操作该 I2C 总线上的任意从设备。其实现思路是为了每个I2C适配器注册一个字符设备驱动。

这样我们就可以通过该字符设备对I2C控制器进行读写操作,这里简单说下怎么在应用层操作从设备的方法,首先通过 open 函数打开一个设备,然后通过ioctl设置你要操作的从设备地址,当然ioctl函数不仅可以设置从设备地址,还可以设置你的重传次数,超时时间等,最后就可以通过write 和read 函数接收和发送。

我们定位到drivers/i2c/i2c-dev.c文件:

/*
 * module load/unload record keeping
 */

static int __init i2c_dev_init(void)
{
        int res;

        printk(KERN_INFO "i2c /dev entries driver\n");

        res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");  // 静态注册设备编号 起始设备编号为0,个数为256
        if (res)
                goto out;

        i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");  // 注册i2c-dev类 会在sys文件系统创建/sys/class/i2c-dev目录
        if (IS_ERR(i2c_dev_class)) {
                res = PTR_ERR(i2c_dev_class);
                goto out_unreg_chrdev;
        }
        i2c_dev_class->dev_groups = i2c_groups;

        /* Keep track of adapters which will be added or removed later */
        res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);  // 监听I2C总线,当有设备新增、删除事件,执行i2dev_attach_adapter、i2dev_detach_adapter
        if (res)
                goto out_unreg_class;

        /* Bind to already existing adapters right away */
        i2c_for_each_dev(NULL, i2cdev_attach_adapter);  // 遍历I2C总线上的所有设备,根据dev->type筛选出适配器,执行i2cdev_attach_adapter函数

        return 0;

out_unreg_class:
        class_destroy(i2c_dev_class);
out_unreg_chrdev:
        unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
        printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
        return res;
}

该函数主要流程如下:

  • ​register_chrdev_region函数注册了一个 I2C 字符设备,主设备号为 I2C_MAJOR;
  • 然后创建了一个"i2c-dev"类,这个就是/sys/class/i2c-dev目录的由来,而 i2c_groups 里面包含了该类中专有的属性文件;
  • 遍历I2C总线上的适配器,调用i2cdev_attach_adapter,为每个I2C适配器注册一个字符设备驱动;

这样就可以操作/dev/i2c-%d的设备节点,来完成对该设备下的所有从设备的操作。

5.1 i2cdev_attach_adapter

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
        struct i2c_adapter *adap;
        struct i2c_dev *i2c_dev;
        int res;

        if (dev->type != &i2c_adapter_type)   // 过滤I2C适配器,这里主要是把I2C从设备排除掉
                return 0;
        adap = to_i2c_adapter(dev);     // 转换为I2C适配器类型

        i2c_dev = get_free_i2c_dev(adap);        // 这里应该是动态分配i2c_dev
        if (IS_ERR(i2c_dev))
                return PTR_ERR(i2c_dev);

        cdev_init(&i2c_dev->cdev, &i2cdev_fops);  // 初始化字符设备结构体  i2cdev_fops为函数操作集
        i2c_dev->cdev.owner = THIS_MODULE;
        res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);  // 将字符设备添加到系统,次设备号为适配器编号
        if (res)
                goto error_cdev;

        /* register this i2c device with the driver core */
        i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,        // 注册设备 会在/sys/class/i2c-dev/下生成i2c-%d链接文件
                                     MKDEV(I2C_MAJOR, adap->nr), NULL,
                                     "i2c-%d", adap->nr);
        if (IS_ERR(i2c_dev->dev)) {
                res = PTR_ERR(i2c_dev->dev);
                goto error;
        }

        pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
                 adap->name, adap->nr);
        return 0;
error:
        cdev_del(&i2c_dev->cdev);
error_cdev:
        put_i2c_dev(i2c_dev);
        return res;
}

该函树主要作用:

  • 为每一个I2C适配器初始化一个字符设备;主设备编号为I2C_MAJOR,次设备编号为I2C适配器编号;
  • 将字符设备注册到系统;
  • 调用device_create注册设备,并创建/dev/i2c-%d设备节点;

当我们对设备节点进行读写操作时,会调用i2cdev_fops操作集的方法。

5.2 i2cdev_fops

文件操作集定义如下:

static const struct file_operations i2cdev_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .read           = i2cdev_read,
        .write          = i2cdev_write,
        .unlocked_ioctl = i2cdev_ioctl,
        .compat_ioctl   = compat_i2cdev_ioctl,
        .open           = i2cdev_open,
        .release        = i2cdev_release,
};

这里我们依次来看一下i2cdev_open、i2cdev_read、i2cdev_write、i2cdev_release方法。

5.2.1. i2cdev_open
static int i2cdev_open(struct inode *inode, struct file *file)
{
        unsigned int minor = iminor(inode);  // 获取次设备号
        struct i2c_client *client;
        struct i2c_adapter *adap;

        adap = i2c_get_adapter(minor);   // 由于字符设备的次设备号为I2C适配器的编号,因此根据次设备号查询i2c_adapter_idr树就能获取到I2C适配器, 同时对client->adapter.dev引用计数+1
        if (!adap)
                return -ENODEV;

        /* This creates an anonymous i2c_client, which may later be
         * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
         *
         * This client is ** NEVER REGISTERED ** with the driver model
         * or I2C core code!!  It just holds private copies of addressing
         * information and maybe a PEC flag.
         */
        client = kzalloc(sizeof(*client), GFP_KERNEL);   // 动态分配I2C从设备
        if (!client) {
                i2c_put_adapter(adap);
                return -ENOMEM;
        }
        snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

        client->adapter = adap;       // 初始化adapater
        file->private_data = client;  // 保存I2C从设备

        return 0;
}
5.2.2 i2cdev_read
/*
 * After opening an instance of this character special file, a file
 * descriptor starts out associated only with an i2c_adapter (and bus).
 *
 * Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg
 * traffic to any devices on the bus used by that adapter.  That's because
 * the i2c_msg vectors embed all the addressing information they need, and
 * are submitted directly to an i2c_adapter.  However, SMBus-only adapters
 * don't support that interface.
 *
 * To use read()/write() system calls on that file descriptor, or to use
 * SMBus interfaces (and work with SMBus-only hosts!), you must first issue
 * an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl.  That configures an anonymous
 * (never registered) i2c_client so it holds the addressing information
 * needed by those system calls and by this SMBus interface.
 */

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
                loff_t *offset)
{
        char *tmp;
        int ret;

        struct i2c_client *client = file->private_data;  // 获取I2C从设备

        if (count > 8192)
                count = 8192;

        tmp = kmalloc(count, GFP_KERNEL);   // 动态分配内存,长度为count
        if (tmp == NULL)
                return -ENOMEM;

        pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
                iminor(file_inode(file)), count);

        ret = i2c_master_recv(client, tmp, count);  // 实现从I2C设备读取数据
        if (ret >= 0)
                ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;   // 从内核空间拷贝数据到用户空间
        kfree(tmp);       // 释放内存
        return ret;
}
5.2.3 i2cdev_write
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
                size_t count, loff_t *offset)
{
        int ret;
        char *tmp;
        struct i2c_client *client = file->private_data;   // 获取I2C从设备

        if (count > 8192)
                count = 8192;

        tmp = memdup_user(buf, count);    // 从用户空间拷贝数据到内核空间
        if (IS_ERR(tmp))
                return PTR_ERR(tmp);

        pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
                iminor(file_inode(file)), count);

        ret = i2c_master_send(client, tmp, count);  // 实现I2C从设备的数据写入
        kfree(tmp);   // 释放内存
        return ret;
}
5.2.4 i2cdev_release
static int i2cdev_release(struct inode *inode, struct file *file)
{
        struct i2c_client *client = file->private_data;  // 获取I2C从设备

        i2c_put_adapter(client->adapter);   // 对client->adapter.dev引用计数-1
        kfree(client);   // 释放I2C从设备
        file->private_data = NULL;

        return 0;
}

5.3 i2c_master_send

这个函数就是在应用层 可以通过 read函数来操作 I2C控制器接收数据的原因,本质就是对 i2c_transfer 函数的封装。

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;  // 获取I2C从设备连接的I2C适配器
    struct i2c_msg msg;                          // 用于构建I2C数据包 

    msg.addr = client->addr;                     // 从设备地址
    msg.flags = client->flags & I2C_M_TEN;       // 10位地址?
    msg.len = count;                             // 待发送数据长度
    msg.buf = (char *)buf;                       // 待发送的数据

    ret = i2c_transfer(adap, &msg, 1);           // 实现数据的发送

    return (ret == 1) ? count : ret;
}

5.4 i2c_master_recv

这两个函数就是在应用层可以通过write函数来操作I2C控制器接发送数据的原因,本质就是对 i2c_transfer 函数的封装。

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
    struct i2c_adapter *adap = client->adapter;   // 获取I2C从设备连接的I2C适配器
    struct i2c_msg msg;                           // 用于构建I2C数据包 
    int ret;

    msg.addr = client->addr;                      // 从设备地址 
    msg.flags = client->flags & I2C_M_TEN;        // 10位地址  
    msg.flags |= I2C_M_RD;                        // 主设备接收模式 
    msg.len = count;                              // 读取数据长度  
    msg.buf = buf;                                // 读取到的数据
    ret = i2c_transfer(adap, &msg, 1);            // 实现数据的读取
    
    return (ret == 1) ? count : ret;
}

5.5 i2c_transfer

下图显示了I2C数据传输过程:

5.5.1 i2c_transfer

i2c_transfer定义在drivers/i2c/i2c-core-base.c文件:

/**
 * i2c_transfer - execute a single or combined I2C message
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 *      terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Note that there is no requirement that each message be sent to
 * the same slave address, although that is the most common model.
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)  // mgs为需要传输的数据包数组
{
        int ret;

        if (!adap->algo->master_xfer) {
                dev_dbg(&adap->dev, "I2C level transfers not supported\n");
                return -EOPNOTSUPP;
        }

        /* REVISIT the fault reporting model here is weak:
         *
         *  - When we get an error after receiving N bytes from a slave,
         *    there is no way to report "N".
         *
         *  - When we get a NAK after transmitting N bytes to a slave,
         *    there is no way to report "N" ... or to let the master
         *    continue executing the rest of this combined message, if
         *    that's the appropriate response.
         *
         *  - When for example "num" is two and we successfully complete
         *    the first message but get an error part way through the
         *    second, it's unclear whether that should be reported as
         *    one (discarding status on the second message) or errno
         *    (discarding status on the first one).
         */
        ret = __i2c_lock_bus_helper(adap);
        if (ret)
                return ret;

        ret = __i2c_transfer(adap, msgs, num);
        i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);

        return ret;
}
5.5.2 __i2c_transfer
/**
 * __i2c_transfer - unlocked flavor of i2c_transfer
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 *      terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Adapter lock must be held when calling this function. No debug logging
 * takes place. adap->algo->master_xfer existence isn't checked.
 */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) // num为1
{
        unsigned long orig_jiffies;
        int ret, try;

        if (WARN_ON(!msgs || num < 1))
                return -EINVAL;

        ret = __i2c_check_suspended(adap);
        if (ret)
                return ret;

        if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
                return -EOPNOTSUPP;

        /*
         * i2c_trace_msg_key gets enabled when tracepoint i2c_transfer gets
         * enabled.  This is an efficient way of keeping the for-loop from
         * being executed when not needed.
         */
        if (static_branch_unlikely(&i2c_trace_msg_key)) {
                int i;
                for (i = 0; i < num; i++)
                        if (msgs[i].flags & I2C_M_RD)
                                trace_i2c_read(adap, &msgs[i], i);
                        else
                                trace_i2c_write(adap, &msgs[i], i);
        }

        /* Retry automatically on arbitration loss */
        orig_jiffies = jiffies;
        for (ret = 0, try = 0; try <= adap->retries; try++) {   // 重试
                if (i2c_in_atomic_xfer_mode() && adap->algo->master_xfer_atomic)
                        ret = adap->algo->master_xfer_atomic(adap, msgs, num);
                else
                        ret = adap->algo->master_xfer(adap, msgs, num);  // 数据传输

                if (ret != -EAGAIN)
                        break;
                if (time_after(jiffies, orig_jiffies + adap->timeout))
                        break;
        }

        if (static_branch_unlikely(&i2c_trace_msg_key)) {
                int i;
                for (i = 0; i < ret; i++)
                        if (msgs[i].flags & I2C_M_RD)
                                trace_i2c_reply(adap, &msgs[i], i);
                trace_i2c_result(adap, num, ret);
        }

        return ret;
}

master_xfer(adap, msgs, num)函数就与SOC的I2C控制器寄存器设置相关了,基本是裸机相关的内容,无非就是产生 I2C 通信时序,如开始信号,停止信号、应答信号等。

这里我们以S3C2440为例,当注册了I2C适配器之后,adap->algo被设置为了&s3c24xx_i2c_algorithm:

/* i2c bus registration info */
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
        .master_xfer            = s3c24xx_i2c_xfer,
        .functionality          = s3c24xx_i2c_func,
};

s3c24xx_i2c_xfer函数执行流程如下:

  • s3c24xx_i2c_xfer中会调用s3c24xx_i2c_doxfer进行数据的传输;
  • s3c24xx_i2c_doxfer中向总线发送开始信号和I2C从设备地址后,便会调用wait_event_timeout函数进程进入睡眠状态;
  • 从设备地址发送完成时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq;
  • s3c24xx_i2c_irq调用下一个字节传输函数i2s_s3c_irq_nextbyte来传输数据;
  • 当I2C数据传输完成后,会调用 s3c24xx_i2c_stop;
  • 最后调用wake_up唤醒等待队列,完成数据的传输过程;
5.5.3 s3c24xx_i2c_xfer

s3c24xx_i2c_xfer函数定义在drivers/i2c/busses/i2c-s3c2410.c文件:

/*
 * first port of call from the i2c bus code when an message needs
 * transferring across the i2c bus.
 */
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
                        struct i2c_msg *msgs, int num)  // num为1
{
        struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;  // 获取私有数据
        int retry;
        int ret;

        ret = clk_enable(i2c->clk);   // 时钟使能
        if (ret)
                return ret;

        for (retry = 0; retry < adap->retries; retry++) {    // 失败重试

                ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

                if (ret != -EAGAIN) {           
                        clk_disable(i2c->clk);  // 禁止时钟
                        return ret;
                }

                dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

                udelay(100);
        }

        clk_disable(i2c->clk);  // 禁止时钟
        return -EREMOTEIO;
}

这里I2C数据传输又调用了s3c24xx_i2c_doxfer:

/*
 * this starts an i2c transfer
 */
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
                              struct i2c_msg *msgs, int num)
{
        unsigned long timeout;
        int ret;

        ret = s3c24xx_i2c_set_master(i2c);
        if (ret != 0) {
                dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
                ret = -EAGAIN;
                goto out;
        }

        i2c->msg     = msgs;     // I2C数据包
        i2c->msg_num = num;      // 一般只有一个数据包
        i2c->msg_ptr = 0;
        i2c->msg_idx = 0;       // 当前数据包索引 
        i2c->state   = STATE_START;  // 开始标志

        s3c24xx_i2c_enable_irq(i2c);           // i2c中断使能  IICCON位[5]设置位1
        s3c24xx_i2c_message_start(i2c, msgs);  // 实际进行数据传输的函数

        if (i2c->quirks & QUIRK_POLL) {   // 轮询方式,不会进入
                ret = i2c->msg_idx;

                if (ret != num)
                        dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

                goto out;
        }

        timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); // 等待传输完成或超时,进程进入睡眠

        ret = i2c->msg_idx;

        /*
         * Having these next two as dev_err() makes life very
         * noisy when doing an i2cdetect
         */
        if (timeout == 0)
                dev_dbg(i2c->dev, "timeout\n");
        else if (ret != num)
                dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

        /* For QUIRK_HDMIPHY, bus is already disabled */
        if (i2c->quirks & QUIRK_HDMIPHY)
                goto out;

        s3c24xx_i2c_wait_idle(i2c);   // 等待I2C总线空闲

        s3c24xx_i2c_disable_bus(i2c);

 out:
        i2c->state = STATE_IDLE;  // 空闲

        return ret;
}

到了这一步,我们终于看到I2C数据传输的函数了s3c24xx_i2c_message_start。

5.5.4 s3c24xx_i2c_message_start

s3c24xx_i2c_message_start函数用于I2C数据包的传输:

/*
 * put the start of a message onto the bus
 */
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
                                      struct i2c_msg *msg)  // I2C数据包数组
{
        unsigned int addr = (msg->addr & 0x7f) << 1;
        unsigned long stat;
        unsigned long iiccon;

        stat = 0;
        stat |=  S3C2410_IICSTAT_TXRXEN;

        if (msg->flags & I2C_M_RD) {   // 主接口模式
                stat |= S3C2410_IICSTAT_MASTER_RX;
                addr |= 1;
        } else                          // 主发送发送
                stat |= S3C2410_IICSTAT_MASTER_TX;

        if (msg->flags & I2C_M_REV_DIR_ADDR)  // 位0 设置发送/接收
                addr ^= 1;

        /* todo - check for whether ack wanted or not */
        s3c24xx_i2c_enable_ack(i2c);                // 1. 使能应答  IICCON位[7]

        iiccon = readl(i2c->regs + S3C2410_IICCON);
        writel(stat, i2c->regs + S3C2410_IICSTAT);  // 2/ 设置工作模式 

        dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
        writeb(addr, i2c->regs + S3C2410_IICDS);    // 3. 发送从设备地址

        /*
         * delay here to ensure the data byte has gotten onto the bus
         * before the transaction is started
         */
        ndelay(i2c->tx_setup);  // 延时
                    
        dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
        writel(iiccon, i2c->regs + S3C2410_IICCON);

        stat |= S3C2410_IICSTAT_START;              // 位[4]设置为1
        writel(stat, i2c->regs + S3C2410_IICSTAT);  // 4. 使能串行输出,主设备发送START信号 

        if (i2c->quirks & QUIRK_POLL) {   // I2C数据传输采用轮询方式,才会进来  这里我们用的中断传输方式,不会进入该方法
                while ((i2c->msg_num != 0) && is_ack(i2c)) {  // 数据包未传输完&&收到应答
                        i2c_s3c_irq_nextbyte(i2c, stat);      // 发送下一个字节
                        stat = readl(i2c->regs + S3C2410_IICSTAT);

                        if (stat & S3C2410_IICSTAT_ARBITR)    // 仲裁失败
                                dev_err(i2c->dev, "deal with arbitration loss\n");
                }
        }
}

这个函数主要就是在I2C数据传输之前的初始化工作:

  • 配置IICCON位[7]使能应答;
  • 配置IICSTAT位[7:6],设置模式;
  • 设置IICDS:发送从设备地址,数据发送完成,触发中断;
  • 进入中断处理函数,s3c24xx_i2c_irq调用下一个字节传输函数i2s_s3c_irq_nextbyte来传输I2C数据包中的数据;
5.5.5 i2c_s3c_irq_nextbyte

这段代码实际上就是进行I2C数据包的发送了,这里实现了多个I2C数据包的发送,包括改变I2C数据传输方向的逻辑、I2C数据传输停止、清除I2C中断挂起标志等:

/*
 * process an interrupt and work out what to do
 */
static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
        unsigned long tmp;
        unsigned char byte;
        int ret = 0;

        switch (i2c->state) {

        case STATE_IDLE:
                dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
                goto out;

        case STATE_STOP:
                dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
                s3c24xx_i2c_disable_irq(i2c);
                goto out_ack;

        case STATE_START:
                /*
                 * last thing we did was send a start condition on the
                 * bus, or started a new i2c message
                 */
                if (iicstat & S3C2410_IICSTAT_LASTBIT &&
                    !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
                        /* ack was not received... */
                        dev_dbg(i2c->dev, "ack was not received\n");
                        s3c24xx_i2c_stop(i2c, -ENXIO);
                        goto out_ack;
                }

                if (i2c->msg->flags & I2C_M_RD)   // 主接收模式
                        i2c->state = STATE_READ;
                else                              // 主发送模式
                        i2c->state = STATE_WRITE;

                /*
                 * Terminate the transfer if there is nothing to do
                 * as this is used by the i2c probe to find devices.
                 */
                if (is_lastmsg(i2c) && i2c->msg->len == 0) {  // 无数据需要传输 发送停止信号
                        s3c24xx_i2c_stop(i2c, 0);
                        goto out_ack;
                }

                if (i2c->state == STATE_READ)
                        goto prepare_read;

                /*
                 * fall through to the write state, as we will need to
                 * send a byte as well
                 */
        case STATE_WRITE:
                /*
                 * we are writing data to the device... check for the
                 * end of the message, and if so, work out what to do
                 */
                if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {      // 没有设置NAK标志进入,也就是要主设备发送一个字节数据后要收到应答
                        if (iicstat & S3C2410_IICSTAT_LASTBIT) {  // 主设备没有收到应答
                                dev_dbg(i2c->dev, "WRITE: No Ack\n");

                                s3c24xx_i2c_stop(i2c, -ECONNREFUSED);    // 发送停止信号
                                goto out_ack;
                        }
                }

 retry_write:

                if (!is_msgend(i2c)) {     //  不是当前I2C数据包的最后一个字节,则传输下一个字节
                        byte = i2c->msg->buf[i2c->msg_ptr++];     // 获取当前需要发送的字节
                        writeb(byte, i2c->regs + S3C2410_IICDS);  // 发送数据  

                        /*
                         * delay after writing the byte to allow the
                         * data setup time on the bus, as writing the
                         * data to the register causes the first bit
                         * to appear on SDA, and SCL will change as
                         * soon as the interrupt is acknowledged
                         */
                        ndelay(i2c->tx_setup);   // 等待

                } else if (!is_lastmsg(i2c)) {   // 是当前I2C数据包的最后一个字节,但不是最后一个I2C数据包,则传输下一个数据包
                        /* we need to go to the next i2c message */

                        dev_dbg(i2c->dev, "WRITE: Next Message\n");

                        i2c->msg_ptr = 0;                          
                        i2c->msg_idx++;
                        i2c->msg++;

                        /* check to see if we need to do another message */
                        if (i2c->msg->flags & I2C_M_NOSTART) {         // 如果不需要发送I2C START信号

                                if (i2c->msg->flags & I2C_M_RD) {      // 主接收模式  即进行I2C读操作   之前是写、现在变成读,改变了I2C传输方向
                                        /*
                                         * cannot do this, the controller
                                         * forces us to send a new START
                                         * when we change direction
                                         */
                                        s3c24xx_i2c_stop(i2c, -EINVAL);
                                }

                                goto retry_write;
                        } else {                                     // 新的I2C数据包开始传输,需要进行新的数据传输流程
                                /* send the new start */
                                s3c24xx_i2c_message_start(i2c, i2c->msg);
                                i2c->state = STATE_START;
                        }

                } else {
                        /* send stop */
                        s3c24xx_i2c_stop(i2c, 0);   // 发送停止信号
                }
                break;
        case STATE_READ:
                /*
                 * we have a byte of data in the data register, do
                 * something with it, and then work out whether we are
                 * going to do any more read/write
                 */
                byte = readb(i2c->regs + S3C2410_IICDS);
                i2c->msg->buf[i2c->msg_ptr++] = byte;

                /* Add actual length to read for smbus block read */
                if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1)
                        i2c->msg->len += byte;
 prepare_read:
                if (is_msglast(i2c)) {
                        /* last byte of buffer */

                        if (is_lastmsg(i2c))
                                s3c24xx_i2c_disable_ack(i2c);

                } else if (is_msgend(i2c)) {
                        /*
                         * ok, we've read the entire buffer, see if there
                         * is anything else we need to do
                         */
                        if (is_lastmsg(i2c)) {
                                /* last message, send stop and complete */
                                dev_dbg(i2c->dev, "READ: Send Stop\n");

                                s3c24xx_i2c_stop(i2c, 0);
                        } else {
                                /* go to the next transfer */
                                dev_dbg(i2c->dev, "READ: Next Transfer\n");

                                i2c->msg_ptr = 0;
                                i2c->msg_idx++;
                                i2c->msg++;
                        }
                }

                break;
        }

        /* acknowlegde the IRQ and get back on with the work */

 out_ack:
        tmp = readl(i2c->regs + S3C2410_IICCON);
        tmp &= ~S3C2410_IICCON_IRQPEND;
        writel(tmp, i2c->regs + S3C2410_IICCON);   // 清除I2C中断挂起标志
 out:
        return ret;
}

参考文章

[1]二十二、Linux驱动之IIC驱动(基于linux2.6.22.6内核)

[2]IIC接口下24C02驱动分析

[3]基于Linux的IIC驱动框架分析

[4]fs4412 I2C驱动基于Cortex-A9,mpu6050裸机程序,驱动,I2C架构,有这一篇够了

[5]I2C and SMBus Subsystem

[6]I2C Subsystem -1- (Basic)

[7]I2C Subsystem -2- (Core)

[8]I2C Subsystem -3- (Transfer)

[9]I2C Subsystem -4- (I2C-Mux)

[10]Device & Driver -2- (Bus & Class)

[11] s3c2440.dtsi

[12] s3c2440-tq2440-dt.dts

posted @ 2023-02-17 23:32  大奥特曼打小怪兽  阅读(931)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步