davinci平台的I2C驱动(非GPIO口模拟i2c)
-
- 一、概述
- 前边介绍过将普通GPIO口模拟成I2C,本文介绍davinci平台的硬件I2C驱动。Linux的I2C体系结构分为3个组成部分:
- 1.I2C核心
- I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
- 2.I2C总线驱动
- I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。
- I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。
- 经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
- 3.I2C设备驱动
- I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
- I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
- 4.I2C驱动框架:
- I2C驱动实在platform机制上实现的,因为linux内核存在I2c的总线i2c_bus_type,为什么还要注册在platform的虚拟总线上?这是因为使用platform 总线在驱动中大体有以下几个好处:
- a) 使得设备被挂接在一个总线上,使配套的sysfs节点、设备电源管理都成为可能。
- b) 隔离了BSP 和驱动。BSP 中定义platform 设备和设备使用的资源(可以使用platform_data的形式来包括platform 设备的设备),设备的具体配置信息。而在驱动中,只需要通过通用API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
- 但是真正达i2c设备和驱动还是要注册在i2c_bus_type总线上。
- 所以i2c驱动框架如下:
- (1).向内核注册i2c_bus_type总线。
- (2).定义i2c的板级资源到platform_device结构中的resource字段,注册i2c的platform_device结构。
- (3).注册i2c的platform_driver结构。注册时会调用platform_driver中的probe函数,即davinci_i2c_probe()函数。
- (4).在davinci_i2c_probe()函数中,进行i2c内存资源的申请、中断申请、寄存器初始化、时钟设置等等(platform机制的优势),添加到davinci_i2c_dev结构(这个结构封装davinci平台cpu的i2c模块的所有资源,包括adapter,对底层硬件的操作也是通过此结构)。并注册davinci_i2c_dev结构中的adapter设备到i2c_bus_type总线上。(下面会注册该设备的驱动)
- (5).接下来调用i2c_dev_init()函数中,注册adapter字符设备,创建与设备节点相关的sysfs文件系统文件和目录(为udev创建设备节点有关),注册adapter设备的驱动到i2c_bus_type总线上。驱动注册时,会扫描i2c_bus_type总线上的设备,将之前注册的adapter设备依附到此driver上。此时会将adapter设备与i2c_dev相关联起来,同时将此i2c_dev结构挂接到全局链表i2c_dev_list中,为用户空间使用时,打开设备方便找到该i2c_dev,再找到对应的adapter结构,再找到对应的davinci_i2c_dev结构,就可以对硬件进行操作了。
- 6.其中davinci_i2c_dev结构、i2c_dev结构和i2c_adapter结构的联系
- adapter设备注册时,davinci_i2c_dev结构中的i2c_adapter结构的字段adapter指向前边注册的adapter。(davinci_i2c_dev.adapter->adapter)
- adapter驱动注册时,i2c_dev结构中的i2c_adapter结构的字段adapter也指向前边注册的adapter。(i2c_dev.adapter->adapter)
- i2c_dev结构用于上层使用,davinci_i2c_dev结构用于底层硬件操作使用,而两者共同指向的adapter结构将两者联系起来。当用户打i2c设备时,会首先找到i2c_dev结构,通过adapter字段找到底层的davinci_i2c_dev结构,进而就可以进行底层硬件寄存器的操作了。
- 驱动框架如下图:
- 二、下面介绍i2c各核心数据结构的定义和它们之间的连接关系。
- 1. 一个i2c适配器由i2c_adapter数据结构描述
- /*
- i2c adapter是软件上抽象出来的i2c总线控制器接口,i2c总线适配器(adapter)就是一条 i2c总线的控制器,i2c_adapter对应于物理上的一个适配器
- 物理上一条i2c总线可以挂接多个硬件设备(slave),一个CPU可以挂接多条i2c总线(想象一下PCI总线),i2c总线控制器就是CPU访问I2C总线的硬件接口,也就是你说的那几个寄存器
.
- 简单点了, 你的开发板上有几个I2C接口,就有几个adapter , 也就是有几条I2C bus , I2C CLIENT 对应的就是你的外围I2C 设备,有几个就有几个CLIENT
, 把这些设备插入开发板, 对应其中的一条BUS, 那么相应的就对应了其中的一个ADAPTER
, 接下来的就是 CLIENT 与 ADAPTER 勾搭成对了, 后面就是做该做的事了.
- */
- struct i2c_adapter {
- struct module *owner;/*所属模块*/
- unsigned int id;
/*algorithm的类型,定义于i2c-id.h,以I2C_ALGO_开始*/
- unsigned int
class;
- struct i2c_algorithm *algo;/*总线通信方法结构体指针,一个i2c适配器上的i2c总线通信方法由其驱动程序提供的i2c_algorithm数据结构描述,由algo指针指向
*/
- void *algo_data;
/* algorithm数据
*/
- int (*client_register)(struct i2c_client
*);
/*client注册时调用*/
- int (*client_unregister)(struct i2c_client
*);
/*client注销时调用*/
- struct semaphore bus_lock;
/*控制并发访问的自旋锁*/
- struct semaphore clist_lock;
- int timeout;
- int retries;
/*重试次数*/
- struct device dev;
/* 适配器设备 */
- struct class_device class_dev;
/* 类设备 */
- int nr;
- struct list_head clients;
/* client链表头,总线上每个设备的 i2c_client数据结构挂载在这里*/
- struct list_head list;
- char name[I2C_NAME_SIZE];
/*适配器名称*/
- struct completion dev_released;
/*用于同步*/
- struct completion class_dev_released;
- };
- 2.具体i2c适配器的通信方法由i2c_algorithm数据结构进行描述:
- struct i2c_algorithm {
- int (*master_xfer)(struct i2c_adapter
*adap, struct i2c_msg
*msgs,int num);//I2C传输函数指针
- int (*smbus_xfer)
(struct i2c_adapter
*adap, u16 addr,unsigned short flags, char read_write,u8 command,
int size, union i2c_smbus_data
*data);//SMbus传输函数指针
- u32 (*functionality)
(struct i2c_adapter
*);//返回适配器支持的功能
- };
- 3. 一个i2c设备的驱动程序由i2c_driver数据结构描述,i2c_driver代表I2C从设备驱动,定义于include/linux/i2c.h:
- struct i2c_driver {
- unsigned int
class;
- /* 这两个接口已经被probe和remove取代
*/
- int (*attach_adapter)(struct i2c_adapter
*);//attach_adapter回调函数在安装i2c设备驱动程序模块时、或者在安装i2c适配器驱动程序模块时被调用,
- //用于检测、认领设备并为设备分配i2c_client数据结构。
- int (*detach_adapter)(struct i2c_adapter
*);//detach_client方法在卸载适配器或设备驱动程序模块时被调用,用于从总线上注销设备、并释放i2c_client及相应的私有数据结构。
- int (*probe)(struct i2c_client
*,
const struct i2c_device_id *);
- int (*remove)(struct i2c_client
*);
- void (*shutdown)(struct i2c_client
*);
- int (*suspend)(struct i2c_client
*, pm_message_t mesg);
- int (*resume)(struct i2c_client
*);
- void (*alert)(struct i2c_client
*, unsigned
int data);
- int (*command)(struct i2c_client
*client, unsigned
int cmd, void
*arg);
- struct device_driver driver;/*设备驱动结构体*/
- const struct i2c_device_id
*id_table;//该驱动所支持的设备ID表
- int (*detect)(struct i2c_client
*, struct i2c_board_info
*);
- const unsigned short
*address_list;
- struct list_head clients;
- };
- 4.一个i2c设备由i2c_client数据结构进行描述:
- struct i2c_client {
- unsigned int flags;
/* 标志
*/
- /*需要说明的是,i2c设备的7位地址是就当前i2c总线而言的,是“相对地址”。不同的i2c总线上的设备可以使用相同的7位地址,
- 但是它们所在的i2c总线不同。所以在系统中一个i2c设备的“绝对地址”由二元组(i2c适配器的ID和设备在该总线上的7位地址)表示。
- */
- unsigned short addr;
/* 低7位为芯片地址
*/
- struct i2c_adapter *adapter;
/*依附的i2c_adapter*/
- struct i2c_driver *driver;
/*依附的i2c_driver
*/
- int usage_count;
/* 访问计数
*/
- struct device dev;
/* 设备结构体 */
- struct list_head list;
/* 链表头 */
- char name[I2C_NAME_SIZE];
/* 设备名称
*/
- struct completion released;
/* 用于同步 */
- };
- 5.下面分析一下i2c_driver、i2c_client、i2c_adapter和i2c_algorithm这4个数据结构的作用及其盘根错节的关系。
- 5.1 i2c_adapter与i2c_algorithm
- i2c_adapter 对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的 i2c_algorithm的指针。
- i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。
- struct i2c_msg {//i2c_msg结构体:
- __u16 addr;
/* 设备地址*/
- __u16 flags;
/* 标志 */
- __u16 len;
/* 消息长度*/
- __u8 *buf;
/* 消息数据*/
- };
- 5.2 i2c_driver与i2c_client
- i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结构体中。同一类的i2c设备device对应一个驱动driver。driver与device的关系是一对多的关系。
- i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter,
driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。
- 5.3 i2c_adpater与i2c_client
- i2c_adpater 与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器上可以连 接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client 的链表。
- 三、I2C驱动的实现工作
- 一方面,适配器驱动可能是Linux内核本身还不包含的。另一方面,挂接在适配器上的具体设备驱动可能也是Linux不存在的。即便上述设备驱动都存在于Linux内核中,其基于的平台也可能与我们的电路板不一样。因此,工程师要实现的主要工作将包括:
- 6.1 提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。
- 6.2 提供I2C适配器的algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
- 6.3 实现I2C设备驱动与i2c_driver接口,用具体设备yyy的yyy_attach_adapter()函数指针、 yyy_detach_client()函数指针和yyy_command()函数指针的赋值给i2c_driver的attach_adapter、
detach_adapter和detach_client指针。
- 6.4 实现I2C设备驱动的文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等。
- 四、核心层提供的接口函数
- 1、增加/删除I2C适配器
- int i2c_add_adapter(struct i2c_adapter
*adapter)
- int i2c_del_adapter(struct i2c_adapter
*adap)
- 2、增加/删除I2C从设备驱动
- int i2c_register_driver(struct module
*owner, structi2c_driver
*driver)
- static inline int i2c_add_driver(struct i2c_driver
*driver)
- void i2c_del_driver(struct i2c_driver
*driver)
- //i2c_add_driver是对i2c_register_driver简单的封装
- 3、i2c传输,发送和接收
- int i2c_transfer(struct i2c_adapter
*adap, struct i2c_msg*msgs,
int num)
- int i2c_master_send(const struct i2c_client
*client, constchar
*buf,
int count)
- int i2c_master_recv(const struct i2c_client
*client, char*buf,
int count)
- //i2c_master_send和i2c_master_recv是i2c_transfer的封装
- //3.1.0的内核中已经没有i2c_attach_client和i2c_detach_client接口
- 4、I2C总线通信方法
- 我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm结构体中的两个函数:
- struct i2c_algorithm {
- int(*master_xfer)(struct i2c_adapter
*adap, struct i2c_msg
*msgs,
int num);
- u32(*functionality)
(struct i2c_adapter
*);
- };
- Functionality函数用于返回algorithm所支持的通信协议;
- Master_xfer函数在I2C适配器上完成数据的传输;
- //Master_xfer函数实现模板
- static int i2c_adapter_xxx_xfer(structi2c_adapter
*adap, struct i2c_msg
*msgs,
int num)
- {
- ......
- for (i
= 0; i
< num; i++)
{
- i2c_adapter_xxx_start();
/*产生起始位*/
- if (msgs[i]->flags
& I2C_M_RD)
{ /*读取*/
- i2c_adapter_xxx_setaddr((msg->addr
<< 1)
| 1);
/*发送从设备地址*/
- i2c_adapter_xxx_wait_ack();
/*获得从设备的ACK*/
- i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);
/*读取len长度的数据到buf中*/
- } else
{
- i2c_adapter_xxx_setaddr(msg->addr
<< 1);
- i2c_adapter_xxx_wait_ack();
- i2c_adapter_xxx_writebytes(msgs[i]->buf,
msgs[i]->len);
- }
- }
- i2c_adapter_xxx_stop();
/*产生停止位*/
- }
- 我们来大致分析一下匹配的过程:
- 当调用i2c_add_driver函数向I2C总线(i2c-core.c文件中注册的”i2c”总线)增加一个i2c_driver时,会遍历总线中的所有i2c_client,
- 调用总线注册的match函数I2C适配器上是否有与i2c_driver匹配的i2c_client,如果匹配会调用I2C注册的probe函数,然后再调用i2c_driver定义的probe来进行关联和初始化工作。
- 五、i2c的初始化
- i2c子系统的初始化函数的执行先后顺序,结合vmlinux.lds和Makefile,可确定i2c初始化函数的执行顺序如下:
- 1./dricer/i2c/i2c-core.c中的函数:i2c_init()---------->postcore_initcall级别
- 2./arch/arm/mach-davinci/board-da850-evm.c中的函数:da850_evm_init()---------->arch_initcall级别
- 3.driver/i2c/buses/i2c-davinci.c中的函数:davinci_i2c_init_driver()---------->subsys_initcall级别
- 4./driver/i2c/i2c-dev.c中的函数:i2c_dev_init()---------->module_init级别
- 1.i2c总线注册
- static int __init i2c_init(void)
- {
- int retval;
- //设备模型中,关心总线,设备,驱动这三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动。
- //相反,在系统每注册一个驱动的时候,寻找与之匹配的设备,匹配是由总线来完成的。 你还可以看一看链表的信息。它们都是关联的。
- retval = bus_register(&i2c_bus_type);//可以发现i2c_inti的函数主要功能就是注册i2c总线,并且在sysfs文件系统/bus目录下创建i2c目录,并在i2c目录下建立devices和drivers目录等
- if (retval)
- return retval;
-
- /*
- static struct i2c_driver dummy_driver =
{
- .driver.name
= "dummy",
- .probe
= dummy_probe,
- .remove
= dummy_remove,
- .id_table
= dummy_id,
- };
- */
- retval = i2c_add_driver(&dummy_driver);//注册一个虚拟驱动
- if (retval)
- goto class_err;
- return 0;
- class_err:
- bus_unregister(&i2c_bus_type);
- return retval;
- }
- /*
- struct bus_type i2c_bus_type =
{
- .name =
"i2c",
- .match = i2c_device_match,
- .probe = i2c_device_probe,
- .remove = i2c_device_remove,
- .shutdown
= i2c_device_shutdown,
- .pm =
&i2c_device_pm_ops,
- };
- match方法的用来进行client device和client driver的配对。在向总线i2c_bus_type注册设备或者驱动时会调用此方法。
- probe方法在完成设备和驱动的配对后调用执行,i2c_bus_type的probe方法是通过传递进来的drv找到包含此drv的i2c_driver驱动,然后再去调用i2c_driver的probe方法,此处就是at24_probe。
- 为什么要这样呢?因为driver_register后,注册的是i2_driver->drv,而drv中的probe未初始化,我们需要调用的是i2c-driver的probe方法。
- */
- 2.设置i2c的复用管脚配置,以及注册i2c的platform_device设备
- 调用完函数i2c_init后,系统将成功创建i2c总线。初始化完毕总线后还需要接着初始化i2c设备和i2c驱动(一般是先初始化device),linux内核中的device初始化一般是通过platform device来初始化的,platform device的初始化在da850_evm_init().
- static __init void da850_evm_init(void)
- {
- int ret;
- char mask = 0;
- //在archarmmach-davinciincludemachcommon.h中定义davinci_soc_info结构。
- //而archarmmach-davincicommon.c中davinci_common_init()对其初始化。
- //davinci_soc_info真正的定义在archarmmach-davincida850.c中,定义达芬奇架构的各类资源的地址等
- struct davinci_soc_info *soc_info
= &davinci_soc_info;
- //......
- /* 寄存器端口配置为I2C
- MUX_CFG(DA850, I2C0_SDA, 4, 12, 15, 2, false)
- MUX_CFG(DA850, I2C0_SCL, 4, 8, 15, 2, false)
- ......
- const short da850_i2c0_pins[] __initdata
= {
- DA850_I2C0_SDA, DA850_I2C0_SCL,//修改1
- -1
- };
- */
- ret = davinci_cfg_reg_list(da850_i2c0_pins);//i2c的复用管脚配置
- if (ret)
- pr_warning("da850_evm_init: i2c0 mux setup failed: %dn",ret);
- /*
- static struct resource i2c_davinci_resources[]
= {
- {
- .name
= "i2c_davinci",
- .start
= 0x01C22000,
- .end
= 0x01C2205C,
- .flags
= IORESOURCE_MEM,
- },
- {
- .start
= 15,
- .end
= 16,
- .flags
= IORESOURCE_IRQ,
- },
- };
-
- static struct davinci_i2c_platform_data da850_davinci_i2c_pdata
= {//wbl test 0514
- .bus_freq = 20,
- .bus_delay = 100,
- };
-
- static struct platform_device davinci_i2c_device
= {//wbl test 0514
- .name =
"i2c_davinci",
- .num_resources
= ARRAY_SIZE(i2c_davinci_resources),
- .resource
= i2c_davinci_resources,
- .id = 1,
- .dev =
{
- .platform_data =
&da850_davinci_i2c_pdata,
- },
- };
- */
- //注册i2c对应的platform设备,把它注册到总线platform_bus_type上,即在sys的platform目录下建立文件和目录,参见platform驱动
- platform_device_register(&davinci_i2c_device);
-
- //......
- }
- 3.在完成platform_device的添加之后,i2c子系统将进行platform_driver的注册过程。platform_driver的注册通过调用初始化函数davinci_i2c_init_driver()函数来完成。
- static struct platform_driver davinci_i2c_driver
= {
- .probe = davinci_i2c_probe,
- .remove = davinci_i2c_remove,
- .driver =
{
- .name =
"i2c_davinci",
- .owner = THIS_MODULE,
- .pm = davinci_i2c_pm_ops,
- },
- };
- //i2c通信方法结构
- static struct i2c_algorithm i2c_davinci_algo =
{
- .master_xfer = i2c_davinci_xfer,
- .functionality = i2c_davinci_func,
- };
- //davinci平台封装的i2c设备结构
- struct davinci_i2c_dev {
- struct device *dev;//内嵌device结构
- void __iomem *base;//寄存器起始地址
- struct completion cmd_complete;
- struct clk *clk;//时钟
- int cmd_err;
- u8 *buf;//读写数据的buf
- size_t buf_len;//读写数据长度
- int irq;//中断号
- int stop;
- u8 terminate;
- struct i2c_adapter adapter;//设备对应的adapter
- #ifdef CONFIG_CPU_FREQ//是否支持cpu变频技术
- struct completion xfr_complete;
- struct notifier_block freq_transition;
- #endif
- struct davinci_i2c_platform_data *pdata;
- };
- //注册驱动
- static int __init davinci_i2c_init_driver(void)
- {
- //platform_driver_register()同样也是进行其它的一些初始化后调用driver_register()将驱动注册到platform_bus_type总线上.
- return platform_driver_register(&davinci_i2c_driver);
- }
- //driver_register()函数会扫描platform_bus_type总线上的设备与驱动进行配对,配对成功就会调用i2c的probe函数
- //因为之前的platform_device的name为i2c_davinci与platform_driver的name一致,故会match成功
- static int davinci_i2c_probe(struct platform_device
*pdev)
- {
- struct davinci_i2c_dev *dev;
- struct i2c_adapter *adap;
- struct resource *mem,
*irq,
*ioarea;
- int r;
- //获得platform_device设备对应的寄存器内存资源
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem)
{
- dev_err(&pdev->dev,
"no mem resource?n");
- return -ENODEV;
- }
- //获得platform_device设备对应的中断资源
- irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!irq)
{
- dev_err(&pdev->dev,
"no irq resource?n");
- return -ENODEV;
- }
-
- //申请寄存器的内存空间
- ioarea = request_mem_region(mem->start, resource_size(mem),pdev->name);
- if (!ioarea)
{
- dev_err(&pdev->dev,
"I2C region already claimedn");
- return -EBUSY;
- }
-
- //分配davinci_i2c_dev空间
- dev = kzalloc(sizeof(struct davinci_i2c_dev), GFP_KERNEL);
- if (!dev)
{
- r = -ENOMEM;
- goto err_release_region;
- }
-
- //初始化davinci_i2c_dev结构
- init_completion(&dev->cmd_complete);
- #ifdef CONFIG_CPU_FREQ
- init_completion(&dev->xfr_complete);
- #endif
- dev->dev
= get_device(&pdev->dev);//将platform_device结构中的device字段赋值给davinci_i2c_dev结构中的device字段
- dev->irq
= irq->start;//设置中断号
- platform_set_drvdata(pdev, dev);//将davinci_i2c_dev结构的dev保存在platform_device结构中
- //获得时钟并使能时钟
- dev->clk
= clk_get(&pdev->dev,
NULL);
- if (IS_ERR(dev->clk))
{
- r = -ENODEV;
- goto err_free_mem;
- }
- clk_enable(dev->clk);
- //将寄存器的起始地址映射到内存
- dev->base
= ioremap(mem->start, resource_size(mem));
- if (!dev->base)
{
- r = -EBUSY;
- goto err_mem_ioremap;
- }
-
- //设置i2c器件的寄存器,设置时钟分频,设置中断,设置从地址等
- i2c_davinci_init(dev);
-
- //申请中断号,中断处理函数是i2c_davinci_isr()
- r = request_irq(dev->irq, i2c_davinci_isr,
0, pdev->name, dev);
- if (r)
{
- dev_err(&pdev->dev,
"failure requesting irq %in", dev->irq);
- goto err_unuse_clocks;
- }
-
- //与cpu变频技术有关
- r = i2c_davinci_cpufreq_register(dev);
- if (r)
{
- dev_err(&pdev->dev,
"failed to register cpufreqn");
- goto err_free_irq;
- }
-
- //初始化davinci_i2c_dev结构中对应的adapter字段
- adap = &dev->adapter;
- i2c_set_adapdata(adap, dev);//将davinci_i2c_dev结构的dev保存在adapter结构中
- adap->owner
= THIS_MODULE;
- adap->class
= I2C_CLASS_HWMON;
- strlcpy(adap->name,
"DaVinci I2C adapter", sizeof(adap->name));
- adap->algo
= &i2c_davinci_algo;//adapter的通信方法
- adap->dev.parent
= &pdev->dev;
- adap->timeout
= DAVINCI_I2C_TIMEOUT;
- adap->nr
= pdev->id;//1
-
- //注册davinci_i2c_dev结构中对应的adapter设备(之后还会注册adapter的驱动)
- r = i2c_add_numbered_adapter(adap);//将adapter设备注册到i2c_bus_type总线上
- if (r)
{
- dev_err(&pdev->dev,
"failure adding adaptern");
- goto err_free_irq;
- }
- return 0;
- err_free_irq:
- free_irq(dev->irq, dev);
- err_unuse_clocks:
- iounmap(dev->base);
- err_mem_ioremap:
- clk_disable(dev->clk);
- clk_put(dev->clk);
- dev->clk
= NULL;
- err_free_mem:
- platform_set_drvdata(pdev,
NULL);
- put_device(&pdev->dev);
- kfree(dev);
- err_release_region:
- release_mem_region(mem->start, resource_size(mem));
- return r;
- }
- static int i2c_davinci_init(struct davinci_i2c_dev
*dev)
- {
- struct davinci_i2c_platform_data *pdata
= dev->dev->platform_data;
- if (!pdata)
- pdata =
&davinci_i2c_platform_data_default;
- //reset I2C,即使i2c无效
- davinci_i2c_reset_ctrl(dev, 0);
- //计算时钟分频
- i2c_davinci_calc_clk_dividers(dev);
- //为I2C提供从地址为0x08
- davinci_i2c_write_reg(dev, DAVINCI_I2C_OAR_REG, 0x08);
- //调试
- dev_dbg(dev->dev,
"PSC = %dn",davinci_i2c_read_reg(dev, DAVINCI_I2C_PSC_REG));
- dev_dbg(dev->dev,
"CLKL = %dn",davinci_i2c_read_reg(dev, DAVINCI_I2C_CLKL_REG));
- dev_dbg(dev->dev,
"CLKH = %dn",davinci_i2c_read_reg(dev, DAVINCI_I2C_CLKH_REG));
- dev_dbg(dev->dev,
"bus_freq = %dkHz, bus_delay = %dn",pdata->bus_freq, pdata->bus_delay);
- //使能I2C器件
- davinci_i2c_reset_ctrl(dev, 1);
- //使能所有i2c中断
- davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, I2C_DAVINCI_INTR_ALL);
- return 0;
- }
- static inline void davinci_i2c_reset_ctrl(struct davinci_i2c_dev
*i2c_dev,int val)
- {
- u16 w;
- //读I2C Mode Register
(ICMDR)寄存器
- w = davinci_i2c_read_reg(i2c_dev, DAVINCI_I2C_MDR_REG);
-
- if (!val)//reset
- w &=
~DAVINCI_I2C_MDR_IRS;
//DAVINCI_I2C_MDR_IRS
= BIT(5),即1<<5,即ICMDR寄存器第五位清0,表示reset i2c
- else//enable
- w |= DAVINCI_I2C_MDR_IRS;//ICMDR寄存器第五位置1,表示使能i2c
- //重新写会ICMDR寄存器
- davinci_i2c_write_reg(i2c_dev, DAVINCI_I2C_MDR_REG, w);
- }
- static inline void davinci_i2c_write_reg(struct davinci_i2c_dev
*i2c_dev,int reg, u16 val)
- {
- __raw_writew(val, i2c_dev->base
+ reg);
- }
- static inline u16 davinci_i2c_read_reg(struct davinci_i2c_dev
*i2c_dev,
int reg)
- {
- return __raw_readw(i2c_dev->base
+ reg);
- }
- static void i2c_davinci_calc_clk_dividers(struct davinci_i2c_dev
*dev)
- {
- struct davinci_i2c_platform_data *pdata
= dev->dev->platform_data;
- u16 psc;
- u32 clk;
- u32 d;
- u32 clkh;
- u32 clkl;
- u32 input_clock = clk_get_rate(dev->clk);
- /* NOTE: I2C Clock divider programming info
- * As per I2C specs the following formulas provide prescaler
- * and low/high divider values
- * input clk
--> PSC Div
----------->
ICCL/H Div --> output clock
- * module clk
- *
- * output clk
= module clk /
(PSC + 1)
[ (ICCL
+ d) +
(ICCH + d)
]
- *
- * Thus,
- * (ICCL
+ ICCH)
= clk = (input clk
/ ((psc
+1)
* output clk))
- 2d;
- *
- * where if PSC
== 0, d
= 7,
- * if PSC
== 1, d
= 6
- * if PSC
> 1 , d
= 5
- */
- /*
get minimum of 7 MHz clock, but max of 12 MHz
*/
- psc = (input_clock
/ 7000000)
- 1;
- if ((input_clock
/ (psc
+ 1))
> 12000000)
- psc++; /* better
to run under spec than over
*/
- d = (psc
>= 2)
? 5 : 7
- psc;
- clk = ((input_clock
/ (psc
+ 1))
/ (pdata->bus_freq
* 1000))
- (d
<< 1);
- clkh = clk
>> 1;
- clkl = clk
- clkh;
- davinci_i2c_write_reg(dev, DAVINCI_I2C_PSC_REG, psc);
- davinci_i2c_write_reg(dev, DAVINCI_I2C_CLKH_REG, clkh);
- davinci_i2c_write_reg(dev, DAVINCI_I2C_CLKL_REG, clkl);
- dev_dbg(dev->dev,
"input_clock = %d, CLK = %dn", input_clock, clk);
- }
- static inline void i2c_set_adapdata(struct i2c_adapter
*dev, void
*data)
- {
- dev_set_drvdata(&dev->dev, data);
- }
- int dev_set_drvdata(struct device
*dev, void
*data)
- {
- int error;
-
- if (!dev->p)
{
- error
= device_private_init(dev);//为dev->p分配空间,并且初始化
- if (error)
- return error;
- }
- dev->p->driver_data
= data;
- return 0;
- }
- int device_private_init(struct device
*dev)
- {
- dev->p
= kzalloc(sizeof(*dev->p),
GFP_KERNEL);
- if (!dev->p)
- return -ENOMEM;
- dev->p->device
= dev;
- klist_init(&dev->p->klist_children,
klist_children_get,klist_children_put);
- INIT_LIST_HEAD(&dev->p->deferred_probe);
- return 0;
- }
- int i2c_add_numbered_adapter(struct i2c_adapter
*adap)
- {
- int id;
- int status;
- if (adap->nr
& ~MAX_ID_MASK)//adap->nr=1
- return -EINVAL;
- retry:
- /*
- 在这里涉及到一个idr结构。idr结构本来是为了配合page cache中的radix tree而设计的。在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存。避免在内存不够的时候出现问题。所以,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了。
- */
- if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL)
== 0)
- return -ENOMEM;
- mutex_lock(&core_lock);//上锁
-
- //它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于adap->nr,然后将对应的id号存放在adapter->nr中
- status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr,
&id);
- if (status
== 0
&& id != adap->nr)
{
- status =
-EBUSY;
- idr_remove(&i2c_adapter_idr, id);
- }
- mutex_unlock(&core_lock);//解锁
- if (status
==
-EAGAIN)
- goto retry;
- if (status
== 0)
- status = i2c_register_adapter(adap);//对这个adapter进行进一步注册。
- return status;
- }
- static int i2c_register_adapter(struct i2c_adapter
*adap)
- {
- int res = 0;
- if (unlikely(WARN_ON(!i2c_bus_type.p)))
{
- res =
-EAGAIN;
- goto out_list;
- }
- /* Sanity checks
*/
- if (unlikely(adap->name[0]
==
'0'))
{//adapter的name不为空
- pr_err("i2c-core: Attempt to register an adapter with ""no name!n");
- return -EINVAL;
- }
- if (unlikely(!adap->algo))
{//adapter的通信算法不能为空
- pr_err("i2c-core: Attempt to register adapter '%s' with ""no algo!n", adap->name);
- return -EINVAL;
- }
- rt_mutex_init(&adap->bus_lock);
- mutex_init(&adap->userspace_clients_lock);
- INIT_LIST_HEAD(&adap->userspace_clients);
- //若没设置超时时间,则缺省为HZ。实际已经设置
- if (adap->timeout
== 0)
- adap->timeout
= HZ;
-
- //adapter中内嵌的struct device结构进行必须的初始化
- dev_set_name(&adap->dev,
"i2c-%d", adap->nr);//设置adapter->dev的设备名:i2c-1
- adap->dev.bus
= &i2c_bus_type;//adapter中内嵌的struct device所在总线为i2c_bus_type
- adap->dev.type
= &i2c_adapter_type;//adapter设备类型设为i2c_adapter_type
-
- //adapter内嵌的struct device注册
- res = device_register(&adap->dev);//注册adapter设备,即在sysfs文件系统的bus/i2c目录下创建adapter相应的文件和目录
- if (res)
- goto out_list;
- dev_dbg(&adap->dev,
"adapter [%s] registeredn", adap->name);
- //调用此函数i2c_scan_static_board_info之前,必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册到__i2c_board_list链表中,
- //同时才会更改__i2c_first_dynamic_bus_num的值
- if (adap->nr
< __i2c_first_dynamic_bus_num)//__i2c_first_dynamic_bus_num=0,adap->nr=1,不会调用下边的函数
- i2c_scan_static_board_info(adap);//遍历__i2c_board_list中挂载的i2c_devinfo结构,每个都是一个i2c_client即i2c设备。
- //若i2c设备(i2c_client)与adapter位于同一i2c总线上,则调用i2c_new_device()进行i2c设备(i2c_client)注册
- /* Notify drivers
*/
- mutex_lock(&core_lock);
- //在新的适配器加入内核时调用函数 bus_for_each_drv时调用的函数。
- //函数bus_for_each_drv是在总线类型为i2c_bus_type的驱动中找到一个驱动与新加入的适配器匹配。
- bus_for_each_drv(&i2c_bus_type,
NULL, adap, __process_new_adapter);//遍历该总线上所有的driver,并设用attach_adapter,因为adapter的驱动还没注册,
- //attach_adapter为空,设置会调用失败。
- mutex_unlock(&core_lock);
- return 0;
- out_list:
- mutex_lock(&core_lock);
- idr_remove(&i2c_adapter_idr, adap->nr);
- mutex_unlock(&core_lock);
- return res;
- }
- 4.i2c-dev模块为系统中所有的i2c适配器创建相应的/dev/i2c/%d字符设备节点,并注册设备访问方法,从而使得用户进程可以访问该i2c总线上的设备。
- 运行i2c_dev_init函数,注册adapter设备驱动,以_init为头的函数,在运行过后系统将回收其内存
- //前边已经注册了adapter设备device_register(&adap->dev);下边要注册设备的驱动i2c_add_driver(&i2cdev_driver);
- //驱动注册成功会和前边才注册的adapter设备相匹配i2cdev_attach_adapter(),匹配成功则创建设备文件。
- static int __init i2c_dev_init(void)
- {
- int res;
- printk(KERN_INFO
"i2c /dev entries drivern");
- //register_chrdev函数最终会向系统注册主设备为I2C_MAJOR,此设备号为0~255的设备。这表示系统最多可以容纳256个i2c adapter,其中注册的结构体&i2cdev_fops,给用户空间提供了调用接口,就是个字符型驱动
- /*当read()、write()、open()、close()、ioctl()等系统调用发生时就会调用到这些函数。
- static const struct file_operations i2cdev_fops
= {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .read = i2cdev_read,
- .write = i2cdev_write,
- .unlocked_ioctl = i2cdev_ioctl,
- .open = i2cdev_open,
- .release = i2cdev_release,
- };
- */
- res = register_chrdev(I2C_MAJOR,
"i2c",
&i2cdev_fops);//注册i2C的字符设备
- if (res)
- goto out;
- //创建设备类,字符设备注册完毕后通过class_create()函数初始化一个类i2c_dev_class,为sysfs系统创建一个i2c-dev的设备类
- i2c_dev_class = class_create(THIS_MODULE,
"i2c-dev");
- if (IS_ERR(i2c_dev_class))
{
- res = PTR_ERR(i2c_dev_class);
- goto out_unreg_chrdev;
- }
- //调用函数i2c_add_driver函数注册i2c driver。这里所说的i2c其实对应的是系统中所有的i2c类设备(包括i2c_client 和 adapter)。
- /*
- static struct i2c_driver i2cdev_driver =
{
- .driver
= {
- .name
= "dev_driver",
- },
- .attach_adapter
= i2cdev_attach_adapter,
- .detach_adapter
= i2cdev_detach_adapter,
- };
- */
- //其作用在于为系统中所有已安装的i2c适配器调用i2cdev_driver的attach_adpter方法,
- //即i2cdev_attach_adapter函数,为所有已安装的适配器创建相应的/dev/i2c-%d字符设备结点并注册设备访问方法。
- res = i2c_add_driver(&i2cdev_driver);//注册adapter的驱动
- if (res)
- goto out_unreg_class;
- return 0;
- out_unreg_class:
- class_destroy(i2c_dev_class);
- out_unreg_chrdev:
- unregister_chrdev(I2C_MAJOR,
"i2c");
- out:
- printk(KERN_ERR
"%s: Driver Initialisation failedn", __FILE__);
- return res;
- }
- static inline int i2c_add_driver(struct i2c_driver
*driver)
- {
- return i2c_register_driver(THIS_MODULE, driver);
- }
- int i2c_register_driver(struct module
*owner, struct i2c_driver
*driver)
- {
- int res;
- /* Can't register
until after driver model init
*/
- if (unlikely(WARN_ON(!i2c_bus_type.p)))
- return -EAGAIN;
- //关联到i2c_bus_types
- driver->driver.owner
= owner;
- driver->driver.bus
= &i2c_bus_type;//设置i2c驱动的总线
- //注册i2c_driver结构中内嵌的device_driver,即创建相应的sysfs文件系统的文件或属性文件
- res = driver_register(&driver->driver);
- if (res)
- return res;
- pr_debug("i2c-core: driver [%s] registeredn", driver->driver.name);
- INIT_LIST_HEAD(&driver->clients);
- /* Walk the adapters that are already present
*/
- mutex_lock(&core_lock);
- bus_for_each_dev(&i2c_bus_type,
NULL, driver, __process_new_driver);//遍历i2c_bus_type总线上所有的设备,与新加入的驱动相匹配,并调用驱动的attach_adapter
- mutex_unlock(&core_lock);
- return 0;
- }
- int bus_for_each_dev(struct bus_type
*bus, struct device
*start,void
*data,
int (*fn)(struct device
*, void
*))
- {
- struct klist_iter i;
- struct device *dev;
- int error
= 0;
- if (!bus)
- return -EINVAL;
- klist_iter_init_node(&bus->p->klist_devices,
&i,(start
? &start->p->knode_bus
: NULL));
//将bus中的已注册的device列表放到迭代器中,方便索引
- while ((dev
= next_device(&i))
&&
!error)
//将驱动逐个地与列表中每一个的device匹配,可能一个驱动匹配好几个设备
- error
= fn(dev, data);
//这个fn就是上面传下来的__process_new_driver
- klist_iter_exit(&i);
- return error;
- }
- static int __process_new_driver(struct device
*dev, void
*data)
- {
- if (dev->type
!=
&i2c_adapter_type)//设备的类型是adapter,才能配对成功。由于之前已经注册过adapter设备了,所以在这里会找到adapter的设备
- return 0;
- //前边adapter的dev已经注册,这里会找到注册的i2c_adapter设备
- return i2c_do_add_adapter(data, to_i2c_adapter(dev));
- }
- static int i2c_do_add_adapter(struct i2c_driver
*driver,struct i2c_adapter
*adap)
- {
- i2c_detect(adap, driver);//空函数
- if (driver->attach_adapter)
{
- driver->attach_adapter(adap);//调用i2cdev_attach_adapter()
- }
- return 0;
- }
- static int i2cdev_attach_adapter(struct i2c_adapter
*adap)
- {
- struct i2c_dev *i2c_dev;
- int res;
- /*打开一个i2c设备时,会看到此结构的用处:
- struct i2c_dev {
- struct list_head list;
- struct i2c_adapter *adap;//指向对应的adapter
- struct device *dev;
- };
- */
- i2c_dev = get_free_i2c_dev(adap);//创建一个i2c_dev结构,添加到全局链表i2c_dev_list中,并且指向adap(
i2c_dev->adap
= adap;)
- if (IS_ERR(i2c_dev))
- return PTR_ERR(i2c_dev);
- /* 可见attach_adapter函数的作用就是调用device_create()函数 通过之前class_create的类信息在/dev下自动创建设备文件。
- 并且此设备的设备号是由固定的主设备号I2C_MAJOR 和 从设备号组成的,从设备号取的就是adapter的nr,此处为1。
- 并且可以推断出系统最多可以容纳0~255 总共256个i2c adapter。
- */
- //创建adapter设备在sysfs文件系统中的相应文件或目录,并在sysfs文件系统类目录下的i2c-dev目录下创建i2c-1目录,此目录下创建设备节点的dev文件
- //即/sys/class/i2c-dev/i2c-1/...(其中这里的i2c_dev->dev结构就是为创建目录i2c-1而使用的)
- //系统启动后udev程序会根据sysfs文件系统相关的设备文件,创建设备节点,即i2c-1的设备节点
- i2c_dev->dev
= device_create(i2c_dev_class,
&adap->dev,MKDEV(I2C_MAJOR, adap->nr),
NULL,"i2c-%d", adap->nr);
- if (IS_ERR(i2c_dev->dev))
{
- res = PTR_ERR(i2c_dev->dev);
- goto error;
- }
- //为sysfs文件系统创建相应的属性文件
- res = device_create_file(i2c_dev->dev,
&dev_attr_name);
- if (res)
- goto error_destroy;
- pr_debug("i2c-dev: adapter [%s] registered as minor %dn",adap->name,
adap->nr);
- return 0;
- error_destroy:
- device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
- error:
- return_i2c_dev(i2c_dev);
- return res;
- }
- 六、i2c的打开、读、写
- 6.1 i2c设备的打开
- static int i2cdev_open(struct inode
*inode, struct file
*file)
- {
- unsigned int minor
= iminor(inode);
- struct i2c_client *client;
- struct i2c_adapter *adap;
- struct i2c_dev *i2c_dev;
-
- //遍历i2c_dev_list链表,根据i2c_dev对应的adapter的索引值找到对应的i2c_dev结构
- i2c_dev = i2c_dev_get_by_minor(minor);
- if (!i2c_dev)
- return -ENODEV;
- //根据i2c_dev结构找到adapter
- adap = i2c_get_adapter(i2c_dev->adap->nr);
- if (!adap)
- return -ENODEV;
- //分配i2c_client结构空间
- client = kzalloc(sizeof(*client), GFP_KERNEL);
- if (!client)
{
- i2c_put_adapter(adap);
- return -ENOMEM;
- }
- snprintf(client->name, I2C_NAME_SIZE,
"i2c-dev %d", adap->nr);
- client->driver
= &i2cdev_driver;//设备驱动(adapter 也是这个设备驱动,共用)
- client->adapter
= adap;//依附的adapter
- file->private_data
= client;//保存到文件的private_data字段中
- return 0;
- }
- 6.2 i2c的读数据
- static ssize_t i2cdev_read(struct file
*file, char __user
*buf, size_t count,loff_t
*offset)
- {
- char *tmp;
- int ret;
- //找到相应的i2c_client
- struct i2c_client *client
= file->private_data;
- if (count
> 8192)
- count = 8192;
- tmp = kmalloc(count, GFP_KERNEL);
- if (tmp
==
NULL)
- return -ENOMEM;
- pr_debug("i2c-dev: i2c-%d reading %zu bytes.n",iminor(file->f_path.dentry->d_inode),
count);
- ret = i2c_master_recv(client, tmp, count);//读数据
- if (ret
>= 0)
- ret = copy_to_user(buf, tmp, count)
? -EFAULT
: ret;//读出数据拷贝到用户空间
- kfree(tmp);
- return ret;
- }
- int i2c_master_recv(struct i2c_client
*client, char
*buf,
int count)
- {
- struct i2c_adapter *adap
= client->adapter;
- struct i2c_msg msg;
- int ret;
- msg.addr = client->addr;//应用程序会通过ioctl(i2c_fd,I2C_SLAVE,slaveaddr)来设置client的地址
- 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);//传输1个msg
- return (ret
== 1)
? count : ret;
- }
- int i2c_transfer(struct i2c_adapter
*adap, struct i2c_msg
*msgs,
int num)
- {
- unsigned long orig_jiffies;
- int ret, try;
-
- if (adap->algo->master_xfer)
{//存在通信方法
- if (in_atomic()
|| irqs_disabled())
{
- ret = i2c_trylock_adapter(adap);
- if
(!ret)/* I2C activity
is ongoing.
*/
- return -EAGAIN;
- } else
{
- i2c_lock_adapter(adap);//给adapter上锁
- }
- orig_jiffies = jiffies;
- for (ret
= 0, try
= 0; try <= adap->retries; try++)
{
- ret = adap->algo->master_xfer(adap,
msgs, num);//最终转换为i2c_algorithm中的master_xfer传输,调用i2c_davinci_xfer()
- if
(ret !=
-EAGAIN)
- break;
- if
(time_after(jiffies, orig_jiffies
+ adap->timeout))//retry间隔时间
- break;
- }
- i2c_unlock_adapter(adap);//给adapter解锁
- return ret;
- } else
{
- dev_dbg(&adap->dev,
"I2C level transfers not supportedn");
- return -EOPNOTSUPP;
- }
- }
- static int i2c_davinci_xfer(struct i2c_adapter
*adap, struct i2c_msg msgs[],
int num)
- {
- struct davinci_i2c_dev *dev
= i2c_get_adapdata(adap);
- int i;
- int ret;
- dev_dbg(dev->dev,
"%s: msgs: %dn", __func__, num);
- ret = i2c_davinci_wait_bus_not_busy(dev, 1);//读ICSTR寄存器查看i2c是否忙,忙则等待到timeout
- if (ret
< 0)
{
- dev_warn(dev->dev,
"timeout waiting for bus readyn");
- return ret;
- }
- for (i
= 0; i
< num; i++)
{
- ret = i2c_davinci_xfer_msg(adap,
&msgs[i],
(i ==
(num - 1)));//发送一个msg
- dev_dbg(dev->dev,
"%s [%d/%d] ret: %dn", __func__, i
+ 1, num,ret);
- if (ret
< 0)
- return ret;
- }
- #ifdef CONFIG_CPU_FREQ //cpu变频支持
- complete(&dev->xfr_complete);
- #endif
- return num;
- }
- static int i2c_davinci_xfer_msg(struct i2c_adapter
*adap, struct i2c_msg
*msg,
int stop)
- {
- struct davinci_i2c_dev *dev
= i2c_get_adapdata(adap);
- struct davinci_i2c_platform_data *pdata
= dev->dev->platform_data;
- u32 flag;
- u16 w;
- int r;
- if (!pdata)
- pdata =
&davinci_i2c_platform_data_default;
- /* Introduce a delay, required
for some boards (e.g Davinci EVM)
*/
- if (pdata->bus_delay)//i2c等待时间为100us
- udelay(pdata->bus_delay);
- //设置从地址
- davinci_i2c_write_reg(dev, DAVINCI_I2C_SAR_REG, msg->addr);
- dev->buf
= msg->buf;
- dev->buf_len
= msg->len;
- dev->stop
= stop;
- //写入数据计数寄存器,即把要读多少个数据写入寄存器ICCNT
- davinci_i2c_write_reg(dev, DAVINCI_I2C_CNT_REG, dev->buf_len);
- INIT_COMPLETION(dev->cmd_complete);
- dev->cmd_err
= 0;
- //设置使能I2C和设置I2C为主设备
- flag = DAVINCI_I2C_MDR_IRS
| DAVINCI_I2C_MDR_MST;
- /*
if the slave address is ten bit address, enable XA bit
*/
- if (msg->flags
& I2C_M_TEN)//如果从设备是10位地址模式
- flag |= DAVINCI_I2C_MDR_XA;//设置主设备也为10位地址模式
- if (!(msg->flags
& I2C_M_RD))
- flag |= DAVINCI_I2C_MDR_TRX;//设置主设备为接收模式
- if (msg->len
== 0)
- flag |= DAVINCI_I2C_MDR_RM;//repeat模式
- //使能I2C的接收和发送中断
- w = davinci_i2c_read_reg(dev, DAVINCI_I2C_IMR_REG);
- if (msg->flags
& I2C_M_RD)
- w |= DAVINCI_I2C_IMR_RRDY;
- else
- w |= DAVINCI_I2C_IMR_XRDY;
- davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w);
- dev->terminate
= 0;
- //写入配置模式寄存器I2C Mode Register
(ICMDR)
- davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag);
- /*
- * First byte should be
set here, not after interrupt,
- * because transmit-data-ready interrupt can come before
- * NACK-interrupt during sending of previous message
and
- * ICDXR may have wrong data
- * It also saves us one interrupt, slightly faster
- */
- if ((!(msg->flags
& I2C_M_RD))
&& dev->buf_len)
{//如果不是读模式,则向发送寄存器写出一个数据,后续数据会通过发送中断自动发送完成,直到dev->buf_len为0
- davinci_i2c_write_reg(dev, DAVINCI_I2C_DXR_REG,
*dev->buf++);
- dev->buf_len--;
- }
- /*
Set STT to begin transmit
now DXR is loaded
*/
- flag |= DAVINCI_I2C_MDR_STT;//产生起始位
- if (stop
&& msg->len
!= 0)//stop=0,不需设置停止位
- flag |= DAVINCI_I2C_MDR_STP;
- davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag);//产生起始位,准备读数据
-
- //等待读指令完成,这是在I2C中断中i2c_davinci_isr()完成读数据后,会complete(&dev->cmd_complete);
- r = wait_for_completion_interruptible_timeout(&dev->cmd_complete,dev->adapter.timeout);
- if (r
== 0)
{//返回0表示timeout,返回错误
- dev_err(dev->dev,
"controller timed outn");
- i2c_recover_bus(dev);
- i2c_davinci_init(dev);
- dev->buf_len
= 0;
- return -ETIMEDOUT;
- }
-
- if (dev->buf_len)
{//读取到足够数据或者写完数据,dev->buf_len应该为0
- /* This should be 0
if all bytes were transferred
or dev->cmd_err denotes an
error.A signal may have aborted the transfer.*/
- if (r
>= 0)
{
- dev_err(dev->dev,
"abnormal termination buf_len=%in",dev->buf_len);
- r =
-EREMOTEIO;
- }
- dev->terminate
= 1;
- wmb();
- dev->buf_len
= 0;
- }
- if (r
< 0)
- return r;
- //没有错误,则返回读到的数据个数
- if (likely(!dev->cmd_err))
- return msg->len;
- /* We have an
error */
- if (dev->cmd_err
& DAVINCI_I2C_STR_AL)
{
- i2c_davinci_init(dev);
- return -EIO;
- }
- if (dev->cmd_err
& DAVINCI_I2C_STR_NACK)
{
- if (msg->flags
& I2C_M_IGNORE_NAK)
- return msg->len;
- if (stop)
{
- w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
- w |= DAVINCI_I2C_MDR_STP;
- davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);
- }
- return -EREMOTEIO;
- }
- return -EIO;
- }
- static irqreturn_t i2c_davinci_isr(int this_irq, void
*dev_id)
- {
- struct davinci_i2c_dev *dev
= dev_id;
- u32 stat;
- int count
= 0;
- u16 w;
- while ((stat
= davinci_i2c_read_reg(dev, DAVINCI_I2C_IVR_REG)))
{//读I2C Interrupt Vector Register
(ICIVR)判断中断类型
- dev_dbg(dev->dev,
"%s: stat=0x%xn", __func__, stat);
- if (count++
== 100)
{
- dev_warn(dev->dev,
"Too much work in one IRQn");
- break;
- }
- switch (stat)
{
- case DAVINCI_I2C_IVR_AL://Arbitration-lost interrupt
- /* Arbitration lost, must retry
*/
- dev->cmd_err
|= DAVINCI_I2C_STR_AL;
- dev->buf_len
= 0;
- complete(&dev->cmd_complete);
- break;
- case DAVINCI_I2C_IVR_NACK://No-acknowledgment interrupt
- dev->cmd_err
|= DAVINCI_I2C_STR_NACK;
- dev->buf_len
= 0;
- complete(&dev->cmd_complete);
- break;
- case DAVINCI_I2C_IVR_ARDY://Register-access-ready
interrupt
- davinci_i2c_write_reg(dev,DAVINCI_I2C_STR_REG, DAVINCI_I2C_STR_ARDY);
- if
(((dev->buf_len
== 0)
&&
(dev->stop
!= 0))
||(dev->cmd_err
& DAVINCI_I2C_STR_NACK))
{
- w = davinci_i2c_read_reg(dev,DAVINCI_I2C_MDR_REG);
- w |= DAVINCI_I2C_MDR_STP;
- davinci_i2c_write_reg(dev,DAVINCI_I2C_MDR_REG, w);
- }
- complete(&dev->cmd_complete);
- break;
- case DAVINCI_I2C_IVR_RDR://接受数据中断
- if
(dev->buf_len)
{
- *dev->buf++
=davinci_i2c_read_reg(dev,DAVINCI_I2C_DRR_REG);//从接受寄存器ICDRR中读取数据
- dev->buf_len--;
- if
(dev->buf_len)
- continue;
- davinci_i2c_write_reg(dev,DAVINCI_I2C_STR_REG,DAVINCI_I2C_IMR_RRDY);//表明接受寄存器ICDRR中的数据已经拷贝完毕
- }
else {
- /* signal can terminate transfer
*/
- terminate_read(dev);
- }
- break;
- case DAVINCI_I2C_IVR_XRDY://发送数据中断interrupt
- if
(dev->buf_len)
{
- davinci_i2c_write_reg(dev, DAVINCI_I2C_DXR_REG,*dev->buf++);//要发送的数据写入到发送寄存器中
- dev->buf_len--;
- if
(dev->buf_len)
- continue;
- w = davinci_i2c_read_reg(dev,DAVINCI_I2C_IMR_REG);
- w &=
~DAVINCI_I2C_IMR_XRDY;
- davinci_i2c_write_reg(dev,DAVINCI_I2C_IMR_REG,w);//禁止发送中断,在i2c_davinci_xfer()函数中,当数据要再次发送时,会再使能发送中断的
- }
else {
- /* signal can terminate transfer
*/
- terminate_write(dev);
- }
- break;
- case DAVINCI_I2C_IVR_SCD://Stop condition detected interrupt
- davinci_i2c_write_reg(dev,DAVINCI_I2C_STR_REG, DAVINCI_I2C_STR_SCD);
- complete(&dev->cmd_complete);
- break;
- case DAVINCI_I2C_IVR_AAS://Address-as-slave
interrupt
- dev_dbg(dev->dev,
"Address as slave interruptn");
- break;
- default:
- dev_warn(dev->dev,
"Unrecognized irq stat %dn", stat);
- break;
- }
- }
- return count ? IRQ_HANDLED
: IRQ_NONE;
- }
- 6.3 i2c的写数据
- 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;
- 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->f_path.dentry->d_inode),
count);
- ret = i2c_master_send(client, tmp, count);
- kfree(tmp);
- return ret;
- }
- int i2c_master_send(struct i2c_client
*client,
const char *buf,
int count)
- {
- int ret;
- struct i2c_adapter *adap
= client->adapter;
- struct i2c_msg msg;
- msg.addr = client->addr;
- msg.flags
= client->flags
& I2C_M_TEN;
- msg.len
= count;
- msg.buf =
(char *)buf;
- ret = i2c_transfer(adap,
&msg, 1);//同上
- return (ret
== 1)
? count : ret;
- }
- 一、概述
static int i2c_davinci_cpufreq_transition(struct notifier_block *nb,unsigned long val, void *data)
{
struct davinci_i2c_dev *dev;
dev = container_of(nb, struct davinci_i2c_dev, freq_transition);
if (val == CPUFREQ_PRECHANGE) {
//wait_for_completion(&dev->xfr_complete);//也要注销掉,否则程序一直等在这里
davinci_i2c_reset_ctrl(dev, 0);
} else if (val == CPUFREQ_POSTCHANGE) {
i2c_davinci_calc_clk_dividers(dev);
davinci_i2c_reset_ctrl(dev, 1);
}
return 0;
}
如果不把这一行wait_for_completion(&dev->xfr_complete);注销掉,linux启动时程序会一直在这里等待。
发现唤醒它的地方在:
static int i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct davinci_i2c_dev *dev = i2c_get_adapdata(adap);
int i;
int ret;
dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num);
ret = i2c_davinci_wait_bus_not_busy(dev, 1);
if (ret < 0) {
dev_warn(dev->dev, "timeout waiting for bus ready\n");
return ret;
}
for (i = 0; i < num; i++) {
ret = i2c_davinci_xfer_msg(adap, &msgs[i], (i == (num - 1)));
dev_dbg(dev->dev, "%s [%d/%d] ret: %d\n", __func__, i + 1, num,
ret);
if (ret < 0)
return ret;
}
#ifdef CONFIG_CPU_FREQ
complete(&dev->xfr_complete);//只有在这里唤醒
#endif
return num;
}
但是当cpu实现变频时,就会触发i2c_davinci_cpufreq_transition(),但是此时一定要等i2c发送或接受一次数据,才能唤醒吗?实在不能理解。
请高手们能不能帮忙解答一下,thanks!!!
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- [原创]网站性能优化利器之二"Yahoo Yslow"
[原创]网站性能优化利器之二"Yahoo Yslow"http://developer.yahoo.com/yslow/Yahoo! YSlow YSlow analyzes web pages and suggests ways to improve their performance based on a set of rules for high performance web pages. YSlow is a Firefox add-on integra…...
2024/4/28 11:06:07 - 网络工程师的5天修炼——第四天
网络工程师的5天修炼——第四天 第四天 呼~,终于把这本书给看的差不多了,,剩下的第五天就是习题部分了,所以也没有什么好写的了,基本上就是做题,这本书前前后后还是花了不少时间,这也是我第一次使用电子版的书籍来学习,刚开始还是会有一些不习惯,后来慢慢的就有一点渐…...
2024/4/28 4:18:47 - Python学习笔记(小甲鱼版)Day7:
今天已经是第七天啦~虽然进展很缓慢,但是看看每天的知识一点一点融入还是很爽的。今天学了列表,列表的表示形式为,member=[lbq,zzy,3,m,2.78,[5,6,7]]嗯没错,列表可以装各种形式的元素输入列表的名字可以打印整个列表。长度的求法 len(member)==6向列表里添加元素:1.member…...
2024/4/28 20:05:52 - layUI前端框架使用详解_layUI前端框架项目实战(完整)
课程目录:2为什么要用layUI框架4layer组件的使用方法详解6layDate组件的使用方法详解8layUI的基础文件引用10页面元素之动画、按钮12页面元素之选项卡、进度条、表格14内置模块之轮播图模块15内置模块之分页模块下载地址:百度网盘...
2024/4/12 5:28:45 - 小甲鱼零基础学python第四讲笔记+课后作业
课后作业: 0. 请问以下代码会打印多少次“我爱鱼C!” while ‘C’: print(‘我爱鱼C!’) 复制代码请问以下代码会打印多少次“我爱鱼C!” i = 10 while i: print(‘我爱鱼C!’) i = i - 1 复制代码 请写出与 10 < cost < 50 等价的表达式 Python3 中,一行可以…...
2024/4/28 11:59:49 - Yslow性能测试框架部署
框架简述用于前端的性能评测,基于雅虎的评分规则对页面进行评分的Firefox插件,从中可以看出我们页面上的很多不足,并且可以知道我们如何改进和优化,配合将测试报告发送到本地的ShowSlow平台以提供给开发人员随时查看。在Xvfb的辅助下,此框架最大的优点就是可以在无显示设备…...
2024/4/28 8:25:09 - 栈的括号匹配 C语言
//括号匹配 #define ElementType char struct StackNode {ElementType data;int Length;struct StackNode* pNext; }; typedef struct StackNode *Stack;Stack CreateStack() {Stack MyStack = (Stack)malloc(sizeof(struct StackNode));MyStack->pNext = NULL;MyStack->…...
2024/4/28 1:29:36 - 软考,网络工程师教程(第四版)2017最新视频教程
大涛老师版本的视频教程淘宝卖188,别的贴吧不是卖25就是卖20,我免费相赠,希望大家都过!链接:http://pan.baidu.com/s/1bp1sdNh 密码:9pp6另外的别人发给我的更完整的:链接:http://pan.baidu.com/s/1o7PQv5C 密码:38y8...
2024/4/28 1:40:16 - 零基础入门Python小甲鱼-笔记4
第3章 成为高手前必须知道的一些基础知识-下原文再续,书接上一回 3.6 条件分支改进一:当用户猜错的时候程序应该给点提示,比如告诉用户输入的值比答案是大了还是小了。程序修改后(假设答案是8):如果用户输入3,程序应该提示比答案小了 如果用户输入9,程序应该提示比答案大了那怎…...
2024/4/28 7:47:56 - layui flow流加载,(Bootstrap+jquery+layui)
最近在研究页面展示方式,以往千篇一律的表格展示,有些单调,偶尔机会了解到Layui flow,看看官方文档用着还是比较简单的,一下是范例,仅供参考:1.引入样式文件:<link href="../css/lib/bootstrap/bootstrap.css?v=3.3.7" rel="stylesheet"> &l…...
2024/4/27 23:06:52 - 圆括号匹配
问题描述:设计一个算法判断算数表达式的圆括号是否正确匹配算法思想: 使用栈来实现,当在输入的字符串中检测到‘(’字符时,该字符入栈。当在输入的字符串中检测到‘)’字符时,上一个字符出栈。 最后判断栈是否为空,若为空,则圆括号匹配正确,否则,圆括号匹配不正确。…...
2024/4/28 4:49:04 - 《网络工程师教程(第五版)》大纲及与第四版比较
...
2024/4/28 3:51:02 - 小甲鱼Python课后整理P7(2018.11.7)
Python 的 floor 除法现在使用 “//” 实现,那 3.0 // 2.0 会显示什么内容呢?如果回答是 1.5 那么很遗憾,您受到 C 语言的影响比较大,Python 这里会义无反顾地执行 floor 除法原则,答案是:1.0a < b < c 事实上是等于?(a < b) and (b < c)不使用 IDLE,你可以…...
2024/4/27 21:16:39 - layui二次封装
最近一直用layui进行页面的重构,这个框架十分适合我们后台人员开发。简单易用,但是layui本身不支持双向绑定,所以很多情况下,我们在支持动态的控件加载时,需要反复刷新。这里我自己封装了一个common模块。话不多说,直接上代码。//Author :chenzihao//Date :2018/8/2…...
2024/4/28 23:14:57 - 自己写的括号匹配算法
记得数据结构中有使用栈进行括号匹配的算法,但是忘记了. 昨天在做作业的时候自己设计了一个算法,测试结果是正确的. 基本思想是: 逐字扫描公式字符;并用一个list存贮已经匹配了的"("的索引 遇到")"(其索引为right0)时向前找公式开始到这个")"之间…...
2024/4/28 14:43:49 - 网络工程师考试历年真题汇总及教程第5版pdf
还有一个月就要进行网络工程师考试了,网上的各种视频讲的挺杂的,还不如好好看看教程,毕竟考的都是教程中的考点,给大家分享一本备考网络工程师的教程,最新版(第五版),毕竟自从2017年以来,考点变化挺大的。分享一些网络工程师资料包括:网络工程师教程第五版和历年真题…...
2024/4/28 22:58:27 - 小甲鱼零基础入门学习Python-010
--------------Class notes------------------1.数组有一个基本要求,就是你所放在一起的数据必须类型一致。由于Python的变量没有数据类型,也就是说,Python是没有数组的。--------------HOMEWORK------------------0.列表都可以存放一些什么东西?answer:python的列表是一个…...
2024/4/13 21:31:20 - springboot+thymeleaf+layui
1、IDEA+maven+thymeleaf+layui环境搭建 (1)创建工程勾选上面的选项之后,pom.xml文件会自动添加相关依赖,如图:(2)引入layui:在resource/static下创建文件js到官网https://www.layui.com/下载layui,放到resource/static/js下:(3)添加配置。application.properties文…...
2024/4/12 5:29:45 - Davinci用户体验 | 你离数据可视精美大屏只差一个Davinci!
作者: 李玲、王小燕 出处:敏捷大数据 来源:宜信技术学院技术沙龙001期|AI中台:一种敏捷的智能业务支持方案|宜信技术沙龙 3月28日晚8点线上直播,点击报名导读:同比和环比是衡量企业某个数据周期性增长速度变化的重要指标,但是一味的看数据,我们很难对增长率的大小做出比…...
2024/4/13 12:32:42 - 南阳oj 第15题 括号匹配(二)
括号匹配(二) 时间限制:1000 ms | 内存限制:65535 KB 难度:6描述给你一个字符串,里面只包含"(",")","[","]"四种符号,请问你需要至少添加多少个括号才能使这些括号匹配起来。 如: []是匹配的 ([])[]是匹配的 ((]是不匹配的 …...
2024/4/17 17:14:52
最新文章
- linux下安装deepspeed
安装步骤 一开始安装deepspeed不可以使用pip直接进行安装。 这时我们需要利用git进行clone下载到本地: git clone https://github.com/microsoft/DeepSpeed.git 进入到deepspeed的安装目录下 cd /home/bingxing2/ailab/group/ai4agr/wzf/Tools/DeepSpeed 激活…...
2024/4/28 23:49:50 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - dp小兰走迷宫
昨天学习了bfs的基本概念,今天来做一道经典习题练练手吧! bfs常用的两类题型 1.从A出发是否存在到达B的路径(dfs也可) 2.从A出发到B的最短路径(数小:<20才能用dfs) 遗留的那个问题的答案- 题目:走迷宫 #incl…...
2024/4/28 3:06:28 - C++ 【原型模式】
简单介绍 原型模式是一种创建型设计模式 | 它使你能够复制已有对象,客户端不需要知道要复制的对象是哪个类的实例,只需通过原型工厂获取该对象的副本。 以后需要更改具体的类或添加新的原型类,客户端代码无需改变,只需修改原型工…...
2024/4/28 20:55:13 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/4/28 13:52:11 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/28 3:28:32 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/26 23:05:52 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/28 13:51:37 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/28 15:57:13 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/28 1:22:35 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/25 18:39:14 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/4/26 23:04:58 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/26 19:46:12 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/27 11:43:08 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/27 8:32:30 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57