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

----------------------------------------------------------------------------------------------------------------------------内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-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 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值