标签:sig 释放 设置 ddr 调度 sla ++ 数组 mat
/************************************************************************************
*本文为个人学习记录,如有错误,欢迎指正。
* https://blog.csdn.net/li_wen01/article/details/51657040
* https://blog.csdn.net/jscese/article/details/44003393
* https://blog.csdn.net/mcgrady_tracy/article/details/51288138
* https://www.cnblogs.com/xiaojiang1025/p/6500540.html
* https://blog.csdn.net/xie0812/article/details/22984527
* https://blog.csdn.net/zqixiao_09/article/details/50916916
************************************************************************************/
(1)IIC物理总线的构成
IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。
(2)IIC通信的特点
同步、串行、电平信号、低速率、近距离。
(3)IIC通信时序
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;
数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期间,数据线SDA的数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线SDA的数据才允许改变。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
(4)IIC通信操作(s5pv210)
(1)i2c_client
Linux内核使用i2c_client来描述一个挂载在I2C总线上的I2C设备。
struct i2c_client { unsigned short flags; //标志位 unsigned short addr; //设备的地址,低7位为芯片地址 char name[I2C_NAME_SIZE]; //设备的名称,最大为20个字节 struct i2c_adapter *adapter; //依附的适配器i2c_adapter,适配器指明所属的总线 struct i2c_driver *driver; //指向设备对应的驱动程序 struct device dev; //设备结构体 int irq; //设备申请的中断号 struct list_head list; //连接到总线上的所有设备 struct list_head detected; //已经被发现的设备链表 struct completion released; //是否已经释放的完成量 };
(2)i2c_driver
Linux内核使用i2c_driver来描述一个IIC设备的驱动程序。每个i2c_client对应一个i2c_driver。
struct i2c_driver { int id; //驱动标识ID unsigned int class; //驱动的类型 int (*attach_adapter)(struct i2c_adapter *); //当检测到适配器时调用的函数 int (*detach_adapter)(struct i2c_adapter*); //卸载适配器时调用的函数 int (*detach_client)(struct i2c_client *) __deprecated;//卸载设备时调用的函数 /*以下是一种新类型驱动需要的函数,这些函数支持IIC设备动态插入和拔出。如果不想支持只实现上面3个。 要不实现上面3个。要么实现下面5个。不能同时定义*/ int (*probe)(struct i2c_client *,const struct i2c_device_id *); //新类型设备探测函数 int (*remove)(struct i2c_client *); //新类型设备的移除函数 void (*shutdown)(struct i2c_client *); //关闭IIC设备 int (*suspend)(struct i2c_client *,pm_messge_t mesg); //挂起IIC设备 int (*resume)(struct i2c_client *); //恢复IIC设备 int (*command)(struct i2c_client *client,unsigned int cmd,void *arg);//使用命令使设备完成特殊的功能。类似ioctl()函数 struct devcie_driver driver; //设备驱动结构体 const struct i2c_device_id *id_table; //设备ID表 int (*detect)(struct i2c_client *,int kind,struct i2c_board_info *);//自动探测设备的回调函数 const struct i2c_client_address_data *address_data; //设备所在的地址范围 struct list_head clients; //指向驱动支持的设备 };
(3)i2c_adapter
Linux内核使用i2c_adapter来描述一个IIC总线适配器。IIC总线适配器就是SoC内部的IIC总线控制器,在物理上连接若干个IIC设备。IIC总线适配器本质上是一个物理设备,其主要功能是完成IIC总线控制器相关的数据通信。
struct i2c_adapter { struct module *owner; //模块计数 unsigned int id; //alogorithm的类型,定义于i2c_id.h中 unsigned int class; //允许探测的驱动类型 const struct i2c_algorithm *algo; //指向适配器的驱动程序 void *algo_data; //指向适配器的私有数据,根据不同的情况使用方法不同 int (*client_register)(struct i2c_client *); //设备client注册时调用 int (*client_unregister(struct i2c_client *);//设备client注销时调用 u8 level; struct mutex bus_lock; //对总线进行操作时,将获得总线锁 struct mutex clist_lock ; //链表操作的互斥锁 int timeout; //超时 int retries; //重拨次数 struct device dev; //指向适配器的设备结构体 int nr ; struct list_head clients; //连接总线上的设备的链表 char name[48]; //适配器名称 struct completion dev_released; //用于同步的完成量 };
(4)i2c_algorithm
Linux内核使用i2c_algorithm来描述IIC适配器与IIC设备的通信方法。
struct i2c_algorithm { /*传输函数指针,指向实现IIC总线通信协议的函数,用来确定适配器支持那些传输类型 */ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msg, int num); /*smbus方式传输函数指针,指向实现SMBus总线通信协议的函数。SMBus和IIC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL*/ int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); /*返回适配器支持的功能*/ u32 (*functionality)(struct i2c_adapter *); };
(1)i2c_driver和i2c_client
i2c_client对应真实的IIC物理设备,每个IIC设备都需要一个i2c_client来描述;而i2c_driver对应一套驱动方法。i2c_driver与i2c_client的关系是一对多,即一个i2c_driver上可以支持多个同等类型的i2c_client。
(2)i2c_adapter与i2c_algorithm
i2c_adapter对应一个IIC总线适配器(SoC内部的IIC总线控制器),而i2c_algorithm对应一套通信方法。一个IIC适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
(3)i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与IIC硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
Linux内核中的IIC总线不同于SoC内部的物理IIC总线 ,内核中的IIC总线是虚拟出来的,目的是管理内核中的IIC从设备及其驱动。
Linux的I2C体系结构分为3个组成部分:
(1)IIC核心
IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法、IIC通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
(2)IIC总线驱动
IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器)的实现。IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。
(3)IIC设备驱动
IIC设备驱动是对IIC硬件体系结构中设备端的实现,与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同IIC总线适配器的差异,不考虑其实现细节地与硬件设备通讯。这部分代码一般由驱动工程师完成。
IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法、IIC通信方法(algorithm)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
IIC核心的实现代码:/kernel/drivers/i2c/i2c-core.c。
Linux内核初始化阶段,调用i2c_init() 函数来初始化IIC总线。
static int __init i2c_init(void) { int retval; retval = bus_register(&i2c_bus_type); //注册IIC总线 if (retval) return retval; ... ... retval = i2c_add_driver(&dummy_driver); //添加一个空驱动,不知为何要添加这个空驱动 if (retval) goto class_err; return 0; ... ... return retval; }
i2c_init() 函数中调用bus_register()函数注册IIC总线。IIC总线定义如下:
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match,//match方法用来进行 device 和driver 的匹配,在向总线注册设备或是驱动的的时候会调用此方法 .probe = i2c_device_probe,//probe方法在完成设备和驱动的配对之后调用执行 .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .suspend = i2c_device_suspend, .resume = i2c_device_resume, };
IIC总线提供的match方法:match方法用来进行 i2c_driver 和 i2c_client 的匹配,在向总线注册i2c_driver或i2c_client的的时候会调用此方法。匹配的方法是拿id_table 中的每一项与 i2c_client 的name 进行匹配,如果名字相同则匹配成功。其函数定义如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; if (!client) return 0; driver = to_i2c_driver(drv); /* match on an id table if there is one */ if (driver->id_table)//如果IIC驱动的id_table 存在的话,使用i2c_match_id 进行函数进行匹配。 return i2c_match_id(driver->id_table, client) != NULL; return 0; }
i2c_driver 和 i2c_client匹配成功后,IIC总线提供的probe方法将被调用执行,即执行i2c_device_probe()函数。实质上,最终调用执行的是IIC设备驱动中的probe函数,即i2c_driver->probe。
static int i2c_device_probe(struct device *dev) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; int status; if (!client) return 0; driver = to_i2c_driver(dev->driver); if (!driver->probe || !driver->id_table) return -ENODEV; client->driver = driver; ... ... status = driver->probe(client, i2c_match_id(driver->id_table, client));//调用IIC设备驱动中的probe函数 if (status) client->driver = NULL; return status; }
(1)增加/删除IIC总线适配器
/*增加一个IIC总线适配器*/ int i2c_add_adapter(struct i2c_adapter *adapter);
/*删除一个IIC总线适配器*/ int i2c_del_adapter(struct i2c_adapter *adap);
(2)增加/删除IIC从设备驱动
/*增加一个IIC从设备驱动*/ int i2c_add_driver(struct i2c_driver *driver);
/*删除一个IIC从设备驱动*/ void i2c_del_driver(struct i2c_driver *driver);
(3)IIC数据传输
/* *参数: struct i2c_adapter *adap:IIC总线适配器 * struct i2c_msg*msgs: * int num: */ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg*msgs, int num) ; /*从以下代码可知,IIC的数据传输是调用i2c_adapter->i2c_algorithm->master_xfer完成*/ int i2c_transfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num) { ... ... if (adap->algo->master_xfer) { for (ret = 0, try = 0; try <=adap->retries; try++) { ret = adap->algo->master_xfer(adap, msgs,num); } } ... ... }
IIC总线上的数据传输是以字节为单位的,有读和写两种通信模式。IIC子系统为了实现这种通信方法,提供了i2c_msg结构,对于每一个START信号,都对应一个i2c_msg对象,实际操作中我们会将所有的请求封装成一个struct i2c_msg[],一次性将所有的请求通过i2c_transfer()发送给匹配到的client的从属的adapter,由adapter根据相应的algo域以及master_xfer域通过主机驱动来将这些请求发送给硬件上的设备。
struct i2c_msg { __u16 addr; //IIC从设备地址 __u16 flags; //操作标志位,I2C_M_RD为读(1),写为0 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16 len; //传输的数据长度,字节为单位 __u8 *buf; //存放read或write的数据的buffer };
IIC总线驱动是对IIC硬件体系结构中适配器端(SoC内部的IIC总线控制器)的实现。IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter,IIC适配器的通信方法数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位,停止位,读写周期,以及以从设备方式被读写,产生ACK等。不同的CPU平台对应着不同的I2C总线驱动。
IIC总线驱动的核心代码:/kernel/drivers/i2c/busses/i2c-s3c2410.c(SAMSUNG 2410平台)。
i2c-s3c2410.c利用platform总线来实现IIC总线驱动,将IIC适配器的硬件信息挂载在platform device,将IIC总线适配器的驱动程序挂载在platform driver下。不同的CPU平台对应着不同的I2C总线驱动。
platform device的对象是IIC总线适配器,即SoC内部的IIC总线控制器。
构建IIC总线控制器的设备信息。
/*IIC总线控制器的硬件资源*/ static struct resource s3c_i2c_resource[] = { [0] = { .start = S3C_PA_IIC, //IIC总线控制器寄存器开始地址 .end = S3C_PA_IIC + SZ_4K - 1,//IIC总线控制器寄存器结束地址 .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_IIC, .end = IRQ_IIC, .flags = IORESOURCE_IRQ, }, }; /*IIC总线控制器的设备信息*/ struct platform_device s3c_device_i2c0 = { .name = "s3c2410-i2c",//名称,与platform driver匹配时使用 .id = 1, .num_resources = ARRAY_SIZE(s3c_i2c_resource), .resource = s3c_i2c_resource, }; /*IIC总线控制器的默认平台数据*/ static struct s3c2410_platform_i2c default_i2c_data0 __initdata = { .flags = 0, .slave_addr = 0x10, //从设备的设备地址 .frequency = 400*1000,//IIC的时钟频率 .sda_delay = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON, }; /*设置IIC总线控制器的平台数据*/ void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd) { struct s3c2410_platform_i2c *npd; if (!pd) pd = &default_i2c_data0; npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL); if (!npd) printk(KERN_ERR "%s: no memory for platform data\n", __func__); else if (!npd->cfg_gpio) npd->cfg_gpio = s3c_i2c0_cfg_gpio; s3c_device_i2c0.dev.platform_data = npd; }
向内核注册IIC总线控制器的platform device。
/*开发板的所有设备信息*/ static struct platform_device *smdkc110_devices[] __initdata = { ... ... &s3c_device_i2c0, ... ... } static void __init smdkc110_machine_init(void) { ... ... platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));//向内核注册开发板的所有设备信息 s3c_i2c0_set_platdata(NULL);//设置IIC总线控制器的平台数据,NULL表示使用默认的平台数据 ... ... }
platform driver的对象是IIC总线适配器的驱动程序。分析/kernel/driver/i2c/busees/i2c_s3c2410.c。
注册IIC总线适配器的驱动程序。
从如下代码可知,IIC总线适配器的驱动注册函数i2c_adap_s3c_init()被subsys_initcall修饰,则i2c_adap_s3c_init()函数将在内核初始化阶段被调度执行。
/*platform driver支持的设备表*/ static struct platform_device_id s3c24xx_driver_ids[] = { { .name = "s3c2410-i2c", .driver_data = TYPE_S3C2410, }, { .name = "s3c2440-i2c", .driver_data = TYPE_S3C2440, }, { }, }; /*使用MODULE_DEVICE_TABLE 宏声明,s3c24xx_driver_ids 是platform类型的一个设备表*/ MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids); /*IIC总线适配器的驱动信息*/ static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, .id_table = s3c24xx_driver_ids, //与IIC总线适配器的platform device匹配 .driver = { .owner = THIS_MODULE, .name = "s3c-i2c", .pm = S3C24XX_DEV_PM_OPS, }, }; /*注册IIC总线适配器的驱动*/ static int __init i2c_adap_s3c_init(void) { return platform_driver_register(&s3c24xx_i2c_driver); } subsys_initcall(i2c_adap_s3c_init);
在IIC总线适配器的platform device与platform driver匹配成功之后,platform driver中的probe函数将被调度运行,即s3c24xx_i2c_probe()函数。
s3c24xx_i2c_probe()函数的主要工作是对IIC总线适配器进行硬件初始化,并向内核注册一个i2c_adapter。
static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c; struct s3c2410_platform_i2c *pdata; struct resource *res; int ret; pdata = pdev->dev.platform_data;//获取IIC总线适配器的平台数据 i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); /*填充i2c变量*/ strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm;//初始化IIC总线适配器的控制算法 i2c->adap.retries = 2; //设置重播次数 i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; spin_lock_init(&i2c->lock); init_waitqueue_head(&i2c->wait);//初始化一个等待队列 /* find the clock and enable it */ /*初始化IIC总线适配器的时钟*/ i2c->dev = &pdev->dev; i2c->clk = clk_get(&pdev->dev, "i2c"); dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); clk_enable(i2c->clk); /* map the registers */ //获取IIC适配器的内存资源信息 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //申请内存资源 i2c->ioarea = request_mem_region(res->start, resource_size(res), pdev->name); //虚拟地址映射 i2c->regs = ioremap(res->start, resource_size(res)); /* setup info block for the i2c core */ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; /* initialise the i2c controller */ //初始化IIC总线适配器,主要针对相关寄存器进行操作 ret = s3c24xx_i2c_init(i2c); /* find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ //获取IIC总线适配器的中断资源信息 i2c->irq = ret = platform_get_irq(pdev, 0); //申请中断 ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, dev_name(&pdev->dev), i2c); ret = s3c24xx_i2c_register_cpufreq(i2c); /* Note, previous versions of the driver used i2c_add_adapter() * to add the bus at any number. We now pass the bus number via * the platform data, so if unset it will now default to always * being bus 0. */ i2c->adap.nr = pdata->bus_num; //向内核注册一个i2c adapter ret = i2c_add_numbered_adapter(&i2c->adap); ... ... return 0; ... ... }
在我们实际开发中,I2C 总线驱动一般芯片原厂会提供,我们开发一般是设计设备驱动。
IIC从设备驱动是对IIC硬件体系结构中设备端的实现,与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同IIC总线适配器的差异,不考虑其实现细节地与硬件设备通讯。这部分代码一般由驱动工程师完成。
IIC从设备驱动挂载在IIC总线下,IIC总线管理着IIC从设备的设备信息(i2c_client)与设备驱动(i2c_driver)。因此,IIC从设备驱动的编写分为两个部分:注册IIC从设备信息、编写IIC从设备驱动程序。
Linux内核提供了struct i2c_board_info与i2c_register_board_info()函数,方便驱动工程师构建IIC从设备的设备信息 。
(1)struct i2c_board_info
Linux内核封装了一个i2c_board_info结构体变量,描述一个IIC从设备的基本信息。内核使用i2c_board_info来构建i2c_client。
struct i2c_board_info { char type[I2C_NAME_SIZE];//设备名称,对应i2c_client.name unsigned short flags; //标志位,对应i2c_client.flags unsigned short addr; //从设备地址,对应i2c_client.addr void *platform_data; //平台数据,对应i2c_client.dev.platform_data struct dev_archdata *archdata; //对应i2c_client.dev.archdata #ifdef CONFIG_OF struct device_node *of_node; #endif int irq;//中断号,对应i2c_client.irq };
(2)i2c_register_board_info()函数
Linux内核维护了一个__i2c_board_list链表,用以管理内核中的所有IIC从设备信息。i2c_register_board_info()函数的工作是向__i2c_board_list链表添加一条IIC从设备信息。
/* * 参数: int busnum:IIC总线编号,表示IIC从设备所挂载的IIC适配器编号 * struct i2c_board_info const *info:IIC从设备信息 unsigned len:i2c_board_info数组的大小,即IIC从设备的个数 */ int __init i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) { int status; ... ... for (status = 0; len; len--, info++) { struct i2c_devinfo *devinfo; devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL); ... ... devinfo->busnum = busnum; devinfo->board_info = *info; list_add_tail(&devinfo->list, &__i2c_board_list);//向链表添加一条IIC从设备信息 } ... ... return status; }
在i2c_adapter注册的时候,会利用__i2c_board_list链表中的IIC从设备信息(i2c_board_info)来构建i2c_client。具体调用关系如下:
static int s3c24xx_i2c_probe()【IIC总线驱动的probe函数】
--i2c_add_numbered_adapter()【注册i2c_adapter】
--i2c_register_adapter()【实质注册i2c_adapter】
--i2c_scan_static_board_info()【扫描__i2c_board_list链表】
--i2c_new_device()【构建i2c_client,并向内核添加IIC从设备】
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { struct i2c_client *client; int status; //申请client内存空间 client = kzalloc(sizeof *client, GFP_KERNEL); ... ... /*****************填充i2c_client*******************/ client->adapter = adap; //与i2c_adapter关联 client->dev.platform_data = info->platform_data;//填充平台数据 client->flags = info->flags; //填充标志位 client->addr = info->addr; //填充从设备地址 client->irq = info->irq; //填充中断号 strlcpy(client->name, info->type, sizeof(client->name));//填充从设备名称 ... ... /*****************初始化client->dev*****************/ client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; ... ... status = device_register(&client->dev);//创建IIC从设备 return client; ... ... }
IIC从设备的驱动程序的实例,详见驱动程序实例(六):mpu6050(IIC + cdev)。
标签:sig 释放 设置 ddr 调度 sla ++ 数组 mat
原文地址:https://www.cnblogs.com/linfeng-learning/p/9523046.html