Linux驱动开发12之再论I2C 驱动模型之device

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangdapao12138/article/details/81838771

1.驱动工程师所关心的东西

首先作为驱动工程师移植I2C驱动的时候,除了I2C驱动框架之外,最关心的是什么?

  1. 使用的是哪个管脚,如果更改怎么办?
  2. I2C的时钟哪里来?I2C的速率是多大?怎样修改?时序怎样?
  3. I2C主从模式怎样设置的?
  4. 在控制台命令行,怎样验证I2C驱动的移植是正确的,有哪些方法?
  5.  怎样验证通过I2C去读写一个设备的寄存器?
  6. 一条总线上挂载多个设备,怎样移植?
  7. 如果I2C的管脚使用的是gpio模拟的方法,怎样去修改?

本篇主要来探讨这七个问题!

2.I2C总线的开机初始化之smdkc110_machine_init

答案:从上电就开始!在/arch/arm/mach-s5pv210/mach-x210.c中。

我们知道smdkc110_machine_inità定义了一个机器硬件初始化函数,是整个开发板的所有硬件的初始化函数!

  • 这个函数非常重要,这个函数中绑定了开发板linux内核启动过程中会初始化的各种硬件的信息。
  • 在此函数中添加的才会被初始化,否则不会被初始化。

那么这两个函数到底是干什么的呢?

1).s3c_i2c_set_platdata。构建platform_device型设备s3c_device_i2c0。

2).i2c_register_board_info。将板上相关的i2c设备信息统一注册到i2c设备链__i2c_board_list上。此处就为at24c02的相关参数。

3.s3c_i2c_set_platdata

我们来跟踪一下:首先传入的NULL,

//首先我们看下参数,struct s3c2410_platform_i2c *pd

30: struct s3c2410_platform_i2c {
31: int bus_num;         //总线号
32: unsigned int flags;      //标志
33: unsigned int slave_addr; //自己作为从设备的地址
34: unsigned long frequency; //I2C总线频率
35: unsigned int sda_delay;      //sda时间延迟
36:
37:
void (*cfg_gpio)(struct platform_device *dev);   //I2C的硬件配置
38: };

//下面进行实例化

51: static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
52: .flags = 0,
53: .slave_addr = 0x10,
54: .frequency = 400*1000,
55: .sda_delay = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON,
56: };

58: void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)

59: {
60: struct s3c2410_platform_i2c *npd;
61:
62:
if (!pd)    //传入的是pd = NULL,因此!pd为真。
63: pd = &default_i2c_data0;    //default_i2c_data0传给pd,此时pdNULL,属于空指针或者0指针。

//2.这个s3c2410_platform_i2c结构体中包含了频率,时钟速度,如果要修改可以修改这个地方,包含了从模式的地址
65: npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL); //给变量创建空间,并赋值,指针赋值
66: if (!npd)   //malloc返回值指针进行判断,判断是否malloc成功?
67: printk(KERN_ERR "%s: no memory for platform data\n", __func__);
68: else if (!npd->cfg_gpio)    //malloc成功,若npd->cfg_gpio没有定义,则在此处定义!
69: npd->cfg_gpio = s3c_i2c0_cfg_gpio//1.I2C的硬件配置管脚,包括所有参数

71: s3c_device_i2c0.dev.platform_data = npd;
72: }

这个s3c_i2c0_set_platdata函数干了什么:

  在将 s3c_device_i2c0 注册到平台设备总线上去之前,还提供了以上的其它信息,包括i2c控制器作为从机的默认slave_addr等,以及引脚的配置函数。注意,s3c_device_i2c0.name  = "s3c2410-i2c"; s3c_i2c0_set_platdata()函数将S3C2410上的I2C控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在s3c2410_platform_i2c结构体中。也就是构建platform_device型设备s3c_device_i2c0

24: void s3c_i2c0_cfg_gpio(struct platform_device *dev)
25: {
26: s3c_gpio_cfgpin(S5PV210_GPD1(0), S3C_GPIO_SFN(2));
27: s3c_gpio_setpull(S5PV210_GPD1(0), S3C_GPIO_PULL_NONE);
28: s3c_gpio_cfgpin(S5PV210_GPD1(1), S3C_GPIO_SFN(2));
29: s3c_gpio_setpull(S5PV210_GPD1(1), S3C_GPIO_PULL_NONE);
30: }

GPIO的管脚号,pin脚的参数,上下拉,复用模式等等,驱动工程师可以修改这个地方。

--------------------------

下面可是研究这个平台总线的注册过程:也就是结构体实例化过程: s3c_device_i2c0.dev.platform_data = npd;

结构体部分实例化:

00044: struct platform_device s3c_device_i2c0 = {
00045: .name = "s3c2410-i2c",
00046: .id = 0,
00047: .num_resources = ARRAY_SIZE(s3c_i2c_resource),
00048: .resource = s3c_i2c_resource,
00049: };

17: struct platform_device {
18: const char * name;      //设备名称, 设备的名称,和struct device结构中的init_name"Linux设备模型(5)_devicedevice driver”)意义相同。实际上,该名称在设备注册时,会拷贝到dev.init_name中。
19: int id;         //设备实例, 用于标识该设备的ID 在“Linux设备模型(6)_Bus”中有提过,内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driverprobe接口。 因此,在driverprobe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。
20: struct device dev;      //基本设备结构体, 真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)

21: u32 num_resources;
22: struct resource * resource; //该设备的资源描述,由struct resourceinclude/linux/ioport.h)结构抽象。 Linux中,系统资源包括I/OMemoryRegisterIRQDMABus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。 当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,linux是怎么管理所有的这些外部"物理地址范围段",进而给用户和linux自身一个比较好的观察4G总线上挂接的一个个设备实体的简洁、统一级联视图的呢?

24: const struct platform_device_id *id_entry;  //id_entry,和内核模块相关的内容,暂不说明。
25:
26:
/* arch specific additions */
27: struct pdev_archdata archdata;  //archdata,一个奇葩的存在!!它的目的是为了保存一些architecture相关的数据,去看看arch/arm/include/asm/device.hstruct pdev_archdata结构的定义,就知道这种放纵的设计有多么垃圾了。不管它了!!
28: };

--------------------

18: struct resource { //linux采用struct resource结构体来描述一个挂接在cpu总线上的设备实体(32cpu的总线地址范围是0~4G):
19: resource_size_t start;  //描述设备实体在cpu总线上的资源起始物理地址;
20: resource_size_t end;    //描述设备实体在cpu总线上的资源结尾物理地址;
21: const char *name;       // 描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切;
22: unsigned long flags;    //描述这个设备实体的一些共性和特性的标志位,区分资源是什么类型的
23: struct resource *parent, *sibling, *child;
24: };

       flags 指资源类型,我们常用的是 IORESOURCE_MEMIORESOURCE_IRQ  这两种。start end 的含义会随着 flags而变更,如

a -- flagsIORESOURCE_MEM 时,start end 分别表示该platform_device占据的内存的开始地址和结束值; 

b -- flags IORESOURCE_IRQ   时,start end 分别表示该platform_device使用的中断号的开始地址和结束值;

下面看一个实例:

00031: static struct resource s3c_i2c_resource[] = {
00032: [0] = {
00033: .start = S3C_PA_IIC,
00034: .end = S3C_PA_IIC + SZ_4K - 1,
00035: .flags = IORESOURCE_MEM,
00036: },
00037: [1] = {
00038: .start = IRQ_IIC,
00039: .end = IRQ_IIC,
00040: .flags = IORESOURCE_IRQ,
00041: },
00042: };

只需要了解一个设备实体的以上4,linux就能够知晓这个挂接在cpu总线的上的设备实体的基本使用情况,也就是[resource->start, resource->end]这段物理地址现在是空闲着呢,还是被什么设备占用着呢?linux会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段[resource->start, resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体,进而避免设备之间出现对同一总线物理地址段的重复引用,而造成对唯一物理地址的设备实体二义性.以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不是linux所想的,linux需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,这就需要链在一起,因此resource结构体提供了另外3个成员:指针parentsiblingchild:分别为指向父亲、兄弟和子资源的指针。以root source为例,root->child(*pchild)指向root所有孩子中地址空间最小的一个;pchild->sibling是兄弟链表的开头,指向比自己地址空间大的兄弟。

物理内存页面是重要的资源。从另一个角度看,地址空间本身,或者物理存储器在地址空间中的位置,也是一种资源,也要加以管理 -- resource管理地址空间资源。内核中有两棵resource树,一棵是iomem_resource 另一棵是ioport_resource分别代表着两类不同性质的地址资源。两棵树的根也都是resource数据结构,不过这两个数据结构描述的并不是用于具体操作对象的地址资源,而是概念上的整个地址空间。 将主板上的ROM空间纳入iomem_resource树中;系统固有的I/O类资源则纳入ioport_resource

-------------

405: struct device {      //基本设备结构体
406: struct device *parent;     //设备的父设备,设备之间是联系到一起的。大部分情况下,父设备会是一个总线或者主机控制器,如果父设备是空,此设备就是顶端设备,通常情况下这是你不想要的情况。
408: struct device_private *p;  //存储了关于设备的驱动核心部分的私有数据,详细内容可查device_private
410: struct kobject kobj;      //一种最高等级的抽象类,用于派生其他类别,一个kobject对象就对应sys目录中的一个设备。
411: const char *init_name; //设备初始化的名称
412: struct device_type *type;  //设备类型,它定义了设备类型并且传递了典型特有信息
413:
414:
struct mutex mutex;        //用于同步驱动用的自旋锁
418: struct bus_type *bus;      //设备所在总线类型
419: struct device_driver *driver;   //哪种设备分配给它了,用来匹配的驱动的!
421: void *platform_data;       //针对设备的平台数据
例如:对于定制板上面的设备,像典型的嵌入式和SOC硬件, linux惊颤使用platform_data去指定特定的扳级数据结构去描述设备和连线方式。那样可以包含什么端口可用,芯片的变量,哪个GPIO引脚工作于复用功能,等等。这样就减少了BSP并且最小化使用关于特定板级的驱动的
423: struct dev_pm_info power;  //用于设备电压管理,更多信息查看Documentation/power/devices.txt
425: #ifdef CONFIG_NUMA
426: int numa_node;         // NUMA node this device is close to.
427: #endif
428: u64 *dma_mask;         // Dma mask (if dma'ble device). DMA屏蔽码
429: u64 coherent_dma_mask;     //dma_mask,但是开辟像在64位地址连续分配不是所有硬件都支持这是利用它开辟连续内存

435: struct device_dma_parameters *dma_parms;   //一个低等级的驱动可以用它去告诉IO内存管理模块关于段的限制
437: struct list_head dma_pools;    // DMA
439: struct dma_coherent_mem *dma_mem; //内部连续内存覆盖
442: struct dev_archdata archdata;  //用于特定架构
443: #ifdef CONFIG_OF
444: struct device_node *of_node;   //连接设备树节点
445: #endif
447: dev_t devt;            //用于创建sysfsdev目录
449: spinlock_t devres_lock;    //用于保护设备资源的自旋锁
450: struct list_head devres_head;  //设备的资源链表
452: struct klist_node knode_class; //这个节点用于添加设备到类链表
453: struct class *class;       //设备类
454: const struct attribute_group **groups; //可选的属性群
455:
456:
void (*release)(struct device *dev);   //用于释放设备。它必须被相应的设备分配器调用(例如,发现设备的总线驱动) * LINUX 系统中的外设设备都被设备结构体实例化,设备结构体包换设备模块的信息,在子系统中,情况有所不同。一个设备被裸设备结构体代表的情况是非常少见的。由此,像kobject这种结构体,经常嵌入到更高等级的能代表设备的结构体。
457: } « end device » ;

----------------------
至此,platform_device平台总线参数填充完毕,也就是说bus测完成了,等待驱动注册,然后match

3. i2c_register_board_info

01614: /* I2C0 */
01615: static struct i2c_board_info i2c_devs0[] __initdata =
01615: {
01616: #ifdef CONFIG_SND_SOC_WM8580
01617: {
01618:    I2C_BOARD_INFO("wm8580", 0x1b),
01619: },
01620: #endif
01621: };

00229: /**
00230: * struct i2c_board_info - template for device creation
00231: * @type: chip type, to initialize i2c_client.name

00232: * @flags: to initialize i2c_client.flags
00233: * @addr: stored in i2c_client.addr
00234: * @platform_data: stored in i2c_client.dev.platform_data
00235: * @archdata: copied into i2c_client.dev.archdata
00236: * @irq: stored in i2c_client.irq
00237: *
00238: * 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. */
00249: struct i2c_board_info {
00250: char type[I2C_NAME_SIZE];
00251: unsigned short flags;
00252: unsigned short addr;
00253: void *platform_data;
00254: struct dev_archdata *archdata;
00255: #ifdef CONFIG_OF
00256: struct device_node *of_node;
00257: #endif
00258: int irq;
00259: };

00063: i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
00065: {
00066: int status;
00068: down_write(&__i2c_board_lock);
00070: /*
00070: dynamic bus numbers will be assigned after the last static one
00071: if (busnum >= __i2c_first_dynamic_bus_num) __i2c_first_dynamic_bus_num = busnum + 1;
00074: for (status = 0; len; len--, info++) {
00075:           struct i2c_devinfo *devinfo;
00077:           devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
00078:           if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");
00080:           status = -ENOMEM;
00081:           break;
00082: }
00084: devinfo->busnum = busnum;
00085: devinfo->board_info = *info;
00086: list_add_tail(&devinfo->list, & __i2c_board_list);
00087: }
00089: up_write(&__i2c_board_lock);
00091: return status;
00092: } ? end i2c_register_board_info ?

i2c_register_board_info()函数用于往__i2c_board_list这条链表添加一条i2c设备信息,在i2c adapter注册的时候,会扫描__i2c_board_list链表,然后调用i2c_new_device()函数来注册i2c设备,注意,要先于i2c adapter注册之前就添加好i2c设备信息,否则会出现调用了i2c_register_board_info()函数,而设备不能注册的情况。

至此kernel上电初始化这一过程,I2C平台部分已经完成。我们来看下干了什么东西:

  1. 使用 s3c_i2c0_set_platdata(NULL)函数,来完成硬件参数的配置和平台总线的参数填充:
    1. 硬件参数的配置:
      1. 控制机地址设定
      2. 频率设定
      3. GPIO管脚的配置复用方式
      4. 管脚的上下拉,输入输出等等
    2. 平台总线结构体的填充:
      1. 平台名称为:s3c2410-i2c.0
      2. 资源配置:内存或中断
      3. 调用struct platform_device,设置bus名称,然后调用 struct device结构体,用于创建/sys/下面的class,devices,dev,bus目录

2. i2c_register_board_info函数来在i2c下挂接一个设备,在i2c_register_board_info()函数用于往__i2c_board_list这条链表添加一条i2c设备信息,在i2c adapter注册的时候,会扫描__i2c_board_list链表,然后调用i2c_new_device()函数来注册i2c设备。

4.怎样查看一个设备挂载完成并完成注册?

4.1  I2C总线扫描

通过i2cdetect -l指令可以查看I2C总线,从返回的结果来看板子含有三个I2C总线。

4.2 i2C设备查询

    若总线上挂载I2C从设备,可通过i2cdetect扫描某个I2C总线上的所有设备。可通过控制台输入i2cdetect -y 1,结果如下所示。

说明1-y为一个可选参数,如果有-y参数的存在则会有一个用户交互过程,意思是希望用户停止使用该I2C总线。如果写入该参数,则没有这个交互过程,一般该参数在脚本中使用。

说明2:此处每个的I2C总线挂载的设备,表中是地址。如0x0F,0x3a,0x40,0x1a等,其中0x40是触摸屏的设备。

5.I2C的寄存器内容导出

 通过i2cdump指令可导出I2C设备中的所有寄存器内容,例如输入i2cdump -y 1 0x0f,可获得以下内容:

   i2cdump -y 1 0x0f指令中,

    -y        代表取消用户交互过程,直接执行指令;

    1         代表I2C总线编号;

    0x0f    代表I2C设备从机地址。

6.I2C寄存器内容写入

    如果向I2C设备中写入某字节,可输入指令i2cset -y 1 0x50 0x00 0x13

    -y        代表曲线用户交互过程,直接执行指令

    1         代表I2C总线编号

    0x50    代表I2C设备地址,此处选择AT24C04的低256字节内容

    0x00    代表存储器地址

    0x13    代表存储器地址中的具体内容

猜你喜欢

转载自blog.csdn.net/wangdapao12138/article/details/81838771