I2C总线驱动框架详解
一、I2C子系统总体架构
1、三大组成部分
(1)I2C核心(i2c-core):I2C核心提供了I2C总线驱动(适配器)和设备驱动的注册、注销方法,I2C通信方法(”algorithm”)上层的,与具体硬件无关的代码以及探测设备
检测设备地址的上层代码等。。
(2)I2C总线驱动(I2Cadapter):I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力。I2C总线驱动由i2c_adapter和i2c_algorithm来描述
I2C适配器是SoC中内置i2c控制器的软件抽象,可以理解为他所代表的是一个I2C主机。
(3)I2C设备驱动(I2Cclient driver):包括两部分:设备的注册和设备驱动的注册
2、I2C子系统的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,让内核统一管理I2C设备,从而可以更容易的在linux下驱动自己的I2C接口硬件。
3、I2C子系统提供的两种驱动实现方法(源码中I2C相关的驱动均位于:drivers/i2c目录下)
(1)第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2Cmaster,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作
接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实
现对硬件的操作,因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做“应用层驱动”,这种方式并不主流,它的优势是
把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。
(2)第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1
的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。
4、相关的结构体
(1)struct i2c_adapter(I2C适配器)
struct i2c_adapter是用来描述一个I2C适配器,在SoC中的指的就是内部外设I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。
struct i2c_adapter {
struct module *owner; // 所有者
unsigned int id;
unsigned int class; // 该适配器支持的从设备的类型
const struct i2c_algorithm *algo; // 该适配器与从设备的通信算法
void *algo_data; /* data fields that are valid for all devices */
struct rt_mutex bus_lock; int timeout; // 超时时间
int retries;
struct device dev; // 该适配器设备对应的device int nr; // 适配器的编号
char name[]; // 适配器的名字
struct completion dev_released; struct list_head userspace_clients; // 用来挂接与适配器匹配成功的从设备i2c_client的一个链表头
};
(2)struct i2c_algorithm(I2C算法)
struct i2c_algorithm结构体代表的是适配器的通信算法,在构建i2c_adapter结构体变量的时候会去填充这个元素。
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data); /* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
注意:smbus协议是从I2C协议的基础上发展而来的,他们之间有很大的相似度,SMBus与I2C总线之间在时序特性上存在一些差别,应用于移动PC和桌面PC系统中的低速率通讯。
(3)struct i2c_client
struct i2c_client { // 用来描述一个i2c次设备
unsigned short flags; // 描述i2c次设备特性的标志位
unsigned short addr; // i2c 次设备的地址 char name[I2C_NAME_SIZE]; // i2c次设备的名字
struct i2c_adapter *adapter; // 指向与次设备匹配成功的适配器
struct i2c_driver *driver; // 指向与次设备匹配成功的设备驱动
struct device dev; // 该次设备对应的device
int irq; // 次设备的中断引脚
struct list_head detected; // 作为一个链表节点挂接到与他匹配成功的i2c_driver 相应的链表头上
};
(4)struct device_driver
struct i2c_driver { // 代表一个i2c设备驱动
unsigned int class; // i2c设备驱动所支持的i2c设备的类型 /* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this if you can, it will probably
* be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *); // 用来匹配适配器的函数 adapter
int (*detach_adapter)(struct i2c_adapter *); /* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *); // 设备驱动层的probe函数
int (*remove)(struct i2c_client *); // 设备驱动层卸载函数 /* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *); /* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; // 该i2c设备驱动所对应的device_driver
const struct i2c_device_id *id_table; // 设备驱动层用来匹配设备的id_table /* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list; // 该设备驱动支持的所有次设备的地址数组
struct list_head clients; // 用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
};
struct i2c_board_info { // 这个结构体是用来描述板子上的一个i2c设备的信息
char type[I2C_NAME_SIZE]; // i2c 设备的名字,用来初始化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
}; struct i2c_devinfo {
struct list_head list; // 作为一个链表节点挂接到__i2c_board_list 链表上去
int busnum; // 适配器的编号
struct i2c_board_info board_info; // 内置的i2c_board_info 结构体
};
5、关键文件(drivers\i2c)
(1)i2c-core.c: i2c核心层
(2)busses目录:这个文件中是已经编写好的各种向i2c核心层注册的适配器
(3)algos目录:这个目录里面是一些I2C通信算法
二、I2C核心层源码分析
跟以前的分析一样,i2c子系统源代码同样是实现为一个模块,在内核配置的时候可以进行动态的加载和卸载。
1、I2C子系统注册函数:i2c_init
static int __init i2c_init(void)
{
int retval; retval = bus_register(&i2c_bus_type); // 注册i2c总线 /sys/bus/i2c
if (retval)
return retval;
#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); // 注册一个空设备驱动 /sys/bus/i2c/driver/dummy
if (retval)
goto class_err;
return ; class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
bus_unregister(&i2c_bus_type);
return retval;
}
struct bus_type i2c_bus_type = {
.name = "i2c", // 总线的名字
.match = i2c_device_match, // 总线下设备与设备驱动的匹配函数
.probe = i2c_device_probe, // 总线层的probr函数
.remove = i2c_device_remove, // 总线卸载时执行的函数
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops, // 电源管理
};
2、i2c_device_match函数分析:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev); // 通过device指针获取到对应的i2c_client指针
struct i2c_driver *driver; // 定义一个i2c_driver 指针 if (!client)
return ; driver = to_i2c_driver(drv); // 通过device_driver指针获取到对应的i2c_driver指针
/* match on an id table if there is one */
if (driver->id_table) // 如果设备驱动中存在id_table表,则通过这个来进行与设备的匹配
return i2c_match_id(driver->id_table, client) != NULL; // 匹配的方式就是比较 id_table指向的i2c_device_id数组中各个元素的名字
// 如果匹配成功就会返回这个 i2c_device_id 元素项地址
return ; // 匹配失败返回 0
}
由i2c_match_id函数分析可以知道,在i2c总线下的设备与设备驱动的匹配是通过设备的名字和设备驱动的i2c_device_id表中的各个表项中的依次进行匹配的,只要有一个匹配成功
则设备和设备驱动就匹配成功了,如果都没有匹配成功则表明匹配失败;由此可以对比platform平台总线的匹配原则,他们基本是一致的,但是platform总线下的设备与设备驱动
匹配过程中还会将设备的名字和设备驱动的名字进行一个匹配,如果这个也没有成功才说明设备与设备驱动匹配过程失败。
3、i2c_device_probe函数分析:
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev); // 通过device指针获取到对应的i2c_client指针
struct i2c_driver *driver;
int status; if (!client)
return ; driver = to_i2c_driver(dev->driver); // 通过device->driver指针获取到对应的i2c_driver指针
if (!driver->probe || !driver->id_table)
return -ENODEV;
client->driver = driver; // i2c设备通过i2c_client->driver指针去指向与他匹配成功的设备驱动i2c_driver
if (!device_can_wakeup(&client->dev))
device_init_wakeup(&client->dev,
client->flags & I2C_CLIENT_WAKE);
dev_dbg(dev, "probe\n"); status = driver->probe(client, i2c_match_id(driver->id_table, client)); // 调用设备驱动层的probe函数
if (status) {
client->driver = NULL;
i2c_set_clientdata(client, NULL);
}
return status;
}
从上面的分析可以知道,总线层的probe函数最终还是会去调用设备驱动层的probe函数。
4、核心层开放给其他部分的注册接口
(1)i2c_add_adapter/i2c_add_numbered_adapter(注册adapter)
/******************************************************************/
i2c_add_adapter
i2c_register_adapter
i2c_add_numbered_adapter
i2c_register_adapter
/******************************************************************/
i2c_register_adapter函数是I2C子系统核心层提供给I2C总线驱动层的用来向核心层注册一个adapter(适配器)的接口函数。
从上可以知道这两个函数最终都是调用i2c_register_adapter函数去注册adapter,他们的区别在于:i2c_add_adapter函数是自动分配适配器编号,而i2c_add_numbered_adapter
是需要自己手动指定一个适配器编号。这个编号的作用第一是为了i2c子系统方便管理系统中的adapter。
i2c_register_adapter函数分析:
static int i2c_register_adapter(struct i2c_adapter *adap) // 向i2c总线注册适配器adapter
{
int res = , dummy; /* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
} rt_mutex_init(&adap->bus_lock);
INIT_LIST_HEAD(&adap->userspace_clients); // 初始化i2c_adapter->userspace_clients链表 /* Set default timeout to 1 second if not already set */
if (adap->timeout == )
adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); // 设置适配器设备的名字 i2c-%d(nr)
adap->dev.bus = &i2c_bus_type; // 设置设备的总线类型
adap->dev.type = &i2c_adapter_type; // 设置设备的设备类型
res = device_register(&adap->dev); // 注册设备 如果前面没有指定父设备那么创建的设备文件是: /sys/devices/i2c-%d
if (res) // samsung在注册适配器的时候是指定了父设备的,所以他创建的设备是: /sys/devices/platform/s3c2410-i2cn/i2c-%d
goto out_list; // 为什么是这个会在后面说到 dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif /* create pre-declared device nodes */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap); // 扫描__i2c_board_list链表上挂接的所有的i2c次设备信息并与适配器进行匹配,匹配成功创建i2c次设备 /* Notify drivers */
mutex_lock(&core_lock);
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
__process_new_adapter);
mutex_unlock(&core_lock); return ; out_list:
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
return res;
}
i2c_scan_static_board_info函数分析:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo; // 定义一个i2c_devinfo 结构体指针 down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) { // 遍历 __i2c_board_list 链表上的所有i2c_devinfo 结构体
if (devinfo->busnum == adapter->nr // 比较 i2c_devinfo->busnum 与 适配器的编号是否匹配
&& !i2c_new_device(adapter, // 如果匹配就会调用 i2c_new_device 函数进行注册添加新的次设备 i2c_client
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
i2c_new_device函数分析:
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client; // 定义一个 i2c_client 指针
int status; client = kzalloc(sizeof *client, GFP_KERNEL); // 申请分配
if (!client)
return NULL; // 对i2c_client结构体变量进行填充
client->adapter = adap; // i2c次设备通过i2c_client->adapter指针去指向与它匹配成功的适配器i2c_adapter
client->dev.platform_data = info->platform_data; // 将传进来的i2c_board_info结构体作为i2c次设备的platform平台数据 if (info->archdata)
client->dev.archdata = *info->archdata; client->flags = info->flags; // 标志位
client->addr = info->addr; // i2c次设备的地址
client->irq = info->irq; // 中断号 strlcpy(client->name, info->type, sizeof(client->name)); // 名字 /* Check for address validity */
status = i2c_check_client_addr_validity(client); // 次设备地址校验
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? : , client->addr);
goto out_err_silent;
} /* Check for address business */
status = i2c_check_addr_busy(adap, client->addr);
if (status)
goto out_err; client->dev.parent = &client->adapter->dev; // 指定i2c 次设备的父设备是与它匹配成功的适配器对应的设备
client->dev.bus = &i2c_bus_type; // 指定次设备的总线类型
client->dev.type = &i2c_client_type; // 指定次设备的设备类型
#ifdef CONFIG_OF
client->dev.of_node = info->of_node;
#endif dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), // 设置次设备的名字 %d-%04x
client->addr);
status = device_register(&client->dev); // 注册次设备: /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04x
if (status)
goto out_err; dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev)); return client; out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}
i2c_new_device是I2C核心层提供给设备层注册i2c设备时使用的一个接口函数,我们可以在注册i2c设备时直接调用这个函数进行注册,在这里i2c核心层还提供了另一种
机制去实现制动注册,他的原理就是: 我们在系统启动的时候在他的硬件机器初始化函数中(例如: smdkc110_machine_init)去注册板子上的i2c次设备
(实际上就是构建i2c_devinfo结构体变量挂接到__i2c_board_list链表上),当我们去向i2c总线核心层注册适配器的时候就会去扫描该链表,然后根据
相关的信息去注册i2c次设备,也就是上面的那个函数的意义了。
/***********************************************************************************************************/
smdkc110_machine_init
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));
s3c_i2c1_set_platdata(NULL); /* 这个是设置的是SoC中的i2c控制器(适配器)作为平台设备的私有数据 */
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs0)); // 通过这个函数注册板子上的i2c次设备的信息
/*********************************************************************************************************/
i2c_add_driver函数分析:
i2c_add_driver函数是定义在 include\linux\i2c.h 头文件中的一个静态内敛函数,但是函数内部是直接调用了I2C核心层的i2c_register_driver函数来进行正真的工作。
该函数是提供用来注册一个I2C总线下的设备驱动的接口。
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; /* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type; // 指定该设备驱动的总线类型 i2c /* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver); // 注册设备驱动 /sys/bus/i2c/drivers/dummy dummy就是一个设备驱动文件
if (res)
return res; pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); INIT_LIST_HEAD(&driver->clients); // 初始化i2c_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); // 可以忽略这条语句的执行效果
mutex_unlock(&core_lock); return ;
}
总结:从上面的分析其实可以知道:i2c子系统内部存在着2个匹配过程:
(1)i2c总线下的设备与设备驱动之间的匹配(通过设备驱动的id_table)
(2)adapter适配器与设备之间的匹配(通过适配器编号)
三、I2C总线驱动层代码分析(drivers\i2c\busses\i2c-s3c2410.c)
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,I2C总线驱动由i2c_adapter和i2c_algorithm来描述。
1、入口函数:
从上面可以知道,adapter的注册实现为模块的方式,可以在内核配置的时候进行动态的加载和卸载,并且是基于platform平台总线,本文件提供的是platform平台设备驱动
的注册,那么他的platform平台设备注册在mach文件中,我这里是mach-x210.c文件。
2、probe函数分析:
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c; // 次结构体是三星对本SoC中的i2c控制器的一个描述,是一个用来在多文件中进行数据传递的全局结构体
struct s3c2410_platform_i2c *pdata; // 此结构体用来表示平台设备的私有数据
struct resource *res;
int ret; pdata = pdev->dev.platform_data; // 获取到平台设备层中的平台数据
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
} i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); // 给s3c24xx_i2c类型指针申请分配内存空间
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
} // 以下主要是对s3c24xx_i2c 结构体中的i2c_adapter变量的一个填充
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); // 设置适配器的名字 s3c2410-i2c
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; // 通信算法
i2c->adap.retries = ;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 该适配器所支持的次设备类有哪些
i2c->tx_setup = ; spin_lock_init(&i2c->lock); // 初始化互斥锁
init_waitqueue_head(&i2c->wait); // 初始化工作队列 /* find the clock and enable it */ i2c->dev = &pdev->dev; // 通过s3c24xx_i2c->dev 指针指向平台设备的device结构体
i2c->clk = clk_get(&pdev->dev, "i2c"); if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
} dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); clk_enable(i2c->clk); // 使能时钟 /* map the registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, ); // 获取平台设备资源
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
} i2c->ioarea = request_mem_region(res->start, resource_size(res), // 物理地址到虚拟地址的映射请求
pdev->name); if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
} i2c->regs = ioremap(res->start, resource_size(res)); // 地址映射 if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
} dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",
i2c->regs, i2c->ioarea, res); /* setup info block for the i2c core */ i2c->adap.algo_data = i2c; // 将s3c24xx_i2c 结构体变量作为s3c24xx_i2c中内置的i2c_adapter适配器中的私有数据
i2c->adap.dev.parent = &pdev->dev; // 指定适配器设备的父设备是平台设备device : /sys/devices/platform/s3c2410-i2cn这个目录下 /* initialise the i2c controller */ ret = s3c24xx_i2c_init(i2c); // i2c控制器(适配器) 寄存器相关的配置
if (ret != )
goto err_iomap; /* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/ i2c->irq = ret = platform_get_irq(pdev, ); // 获取平台设备中的i2c中断号(这个中断是I2C控制器产生的中断)
if (ret <= ) {
dev_err(&pdev->dev, "cannot find IRQ\n");
goto err_iomap;
} ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, // 申请中断
dev_name(&pdev->dev), i2c); if (ret != ) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
goto err_iomap;
} ret = s3c24xx_i2c_register_cpufreq(i2c); // 这个不清楚
if (ret < ) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
goto err_irq;
} /* 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主机(适配器)的编号 ret = i2c_add_numbered_adapter(&i2c->adap); // 向i2c核心注册i2c适配器 /sys/devices/platform/s3c2410-i2cn/s3c2410-i2c 因为在函数内会将 i2c-%d作为适配器的名字
if (ret < ) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
} platform_set_drvdata(pdev, i2c); // 将s3c24xx_i2c变量作为平台设备私有数据中的设备驱动私有数据 dev->p->driver_data
// 因为这个变量还会在本文件中其他函数中会用到了
clk_disable(i2c->clk); dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return ; err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c); err_irq:
free_irq(i2c->irq, i2c); err_iomap:
iounmap(i2c->regs); err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea); err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk); err_noclk:
kfree(i2c);
return ret;
}
struct i2c_msg:
struct i2c_msg {
__u16 addr; /* slave address 设备地址 */
__u16 flags; /* 本次消息的标志位,就是下面的这些 */
#define I2C_M_TEN 0x0010 /* 设置了这个标志位表示从设备的地址是10bit */
#define I2C_M_RD 0x0001 /* 设置了这个标志位表示本次通信i2c控制器是处于接收方,否则就是发送方 */
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000 /* 设置这个标志位表示需要将读写标志位反转过来 */
#define I2C_M_IGNORE_NAK 0x1000 /* 设置这个标志意味当前i2c_msg忽略I2C器件的ack和nack信号 */
#define I2C_M_NO_RD_ACK 0x0800 /* 设置这个标志位表示在读操作中主机不用ACK */
#define I2C_M_RECV_LEN 0x0400
__u16 len; /* 数据长度 */
__u8 *buf; /* 数据缓冲区指针 */
};
这个结构体就是用来表示一个通信周期的数据相关的结构体,包括通信从设备的信息,通信数据长度等等。函数中填充的通信算法会在i2c主设备与从设备
通信的时候调用到。
平台设备的注册会随着smdkc110_machine_init函数中的platform_add_devices函数的执行被注册(s3c_device_i2c0、s3c_device_i2c1...)
四、I2C设备驱动层代码分析(drivers\input\touchscreen\gslX680.c)
同platform平台总线一样,I2C总线下也是分为i2c总线设备层(struct i2c_client)和i2c总线设备驱动层(struct i2c_driver),这两个结构体已经在上面分析过了
因为我的板子上使用的是一款I2C接口的电容触摸屏:gslx680
所以就以这个I2C设备为例进行分析(源代码文件: gslX680.c),代码是由触摸品IC原厂工程师提供的,代码中涉及到很多的触摸屏专业方面的知识,这个就不用去管了。
同样的gslX680.c文件提供的是I2C设备驱动的注册,相应的I2C设备注册是在mach文件中,我这里同样还是:mach-x210.c
1、I2C总线设备驱动的注册
2、gsl_ts_probe函数分析
static int __devinit gsl_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct gsl_ts *ts; // 设备驱动层封装的一个全局结构体
int rc; print_info("GSLX680 Enter %s\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "I2C functionality not supported\n");
return -ENODEV;
} ts = kzalloc(sizeof(*ts), GFP_KERNEL); // 给 gsl_ts类型的指针申请分配内存
if (!ts)
return -ENOMEM;
print_info("==kzalloc success=\n"); ts->client = client; // 通过gsl_ts->client指针去指向传进来的i2c次设备i2c_client
i2c_set_clientdata(client, ts); // 将gsl_ts作为i2c次设备的私有数据区中的设备驱动私有数据
ts->device_id = id->driver_data; rc = gslX680_ts_init(client, ts); // 初始化操作
if (rc < ) {
dev_err(&client->dev, "GSLX680 init failed\n");
goto error_mutex_destroy;
} gsl_client = client; // 通过一个全局的i2c_client指针gsl_client去指向传进来的i2c次设备i2c_client gslX680_init(); // gslX680 触摸屏相关的gpio初始化操作
init_chip(ts->client); // gslX680触摸屏芯片相关的初始化操作
check_mem_data(ts->client); rc= request_irq(client->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts); // 申请中断,这个中断是接在SoC的一个外部中断引脚上的
if (rc < ) { // 当发生中断的时候表示有数据可以进行读取了,那么就会通知
print_info( "gsl_probe: request irq failed\n"); // I2C主机去去读数据
goto error_req_irq_fail;
} /* create debug attribute */
//rc = device_create_file(&ts->input->dev, &dev_attr_debug_enable); #ifdef CONFIG_HAS_EARLYSUSPEND
ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + ;
//ts->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1;
ts->early_suspend.suspend = gsl_ts_early_suspend;
ts->early_suspend.resume = gsl_ts_late_resume;
register_early_suspend(&ts->early_suspend);
#endif #ifdef GSL_MONITOR
print_info( "gsl_ts_probe () : queue gsl_monitor_workqueue\n"); INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker);
gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue");
queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, );
#endif print_info("[GSLX680] End %s\n", __func__); return ; //exit_set_irq_mode:
error_req_irq_fail:
free_irq(ts->irq, ts); error_mutex_destroy:
input_free_device(ts->input);
kfree(ts);
return rc;
}
gslX680_ts_init函数分析:
static int gslX680_ts_init(struct i2c_client *client, struct gsl_ts *ts)
{
struct input_dev *input_device; // 定义一个 input_dev 结构体指针
int rc = ; printk("[GSLX680] Enter %s\n", __func__); ts->dd = &devices[ts->device_id]; if (ts->device_id == ) {
ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data;
ts->dd->touch_index = ;
} ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
if (!ts->touch_data) {
pr_err("%s: Unable to allocate memory\n", __func__);
return -ENOMEM;
} input_device = input_allocate_device(); // 给input_device指针申请分配内存
if (!input_device) {
rc = -ENOMEM;
goto error_alloc_dev;
} ts->input = input_device; // 通过gsl_ts->input指针去指向input输入设备
input_device->name = GSLX680_I2C_NAME; // 设置input设备的名字
input_device->id.bustype = BUS_I2C; // 设置input设备的总线类型
input_device->dev.parent = &client->dev; // 设置input设备的父设备: /sys/devices/platform/s3c2410-i2cn/i2c-%d/%d-%04x
// 但是通过后面的分析可知,最终不是这个父设备
input_set_drvdata(input_device, ts); // 将gsl_ts结构体作为input设备的私有数据区中的设备驱动数据 // 以下是对input_dev 输入设备的一些设置 设置input设备可以上报的事件类型
set_bit(EV_ABS, input_device->evbit);
set_bit(BTN_TOUCH, input_device->keybit);
set_bit(EV_ABS, input_device->evbit);
set_bit(EV_KEY, input_device->evbit);
input_set_abs_params(input_device, ABS_X, , SCREEN_MAX_X, , );
input_set_abs_params(input_device, ABS_Y, , SCREEN_MAX_Y, , );
input_set_abs_params(input_device, ABS_PRESSURE, , , , );
#ifdef HAVE_TOUCH_KEY
input_device->evbit[] = BIT_MASK(EV_KEY);
//input_device->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
for (i = ; i < MAX_KEY_NUM; i++)
set_bit(key_array[i], input_device->keybit);
#endif client->irq = IRQ_PORT; // 触摸屏使用到的中断号
ts->irq = client->irq; // ts->wq = create_singlethread_workqueue("kworkqueue_ts");
if (!ts->wq) {
dev_err(&client->dev, "Could not create workqueue\n");
goto error_wq_create;
}
flush_workqueue(ts->wq); INIT_WORK(&ts->work, gslX680_ts_worker); // 初始化工作队列 当发生中断的时候在在中断处理函数中就会调用这个工作队列
// 作为中断的下半部
rc = input_register_device(input_device); // 注册input设备
if (rc)
goto error_unreg_device; return ; error_unreg_device:
destroy_workqueue(ts->wq);
error_wq_create:
input_free_device(input_device);
error_alloc_dev:
kfree(ts->touch_data);
return rc;
}
/*******************************************************************************************/
因为在中断中需要做的事情很多,所以在这里采用了中断上下半部的方式来处理,上半部就是probe函数中的申请中断时绑定的函数gsl_ts_irq
在这个函数中做了一些最必要做的事情,然后开启下半部,在下半部中继续执行未处理完的事情gslX680_ts_worker。
需要注意的是这里的中断指的是触摸屏方的中断信号,他的思想是这样的:当有人按下电容触摸屏的时候,在触摸屏IC就会将产生的模拟量转化为数字量,当转换完成之后
由于I2C协议本身的限制,所有的通信周期都是由主机方发起,从机只能被动的相应。I2c控制器这边如何知道数据已经可以读取了呢?这就得通过触摸屏接口这边引出一个
中断信号引脚来告知I2C主机控制器读取数据,然后I2C主机就会发起一次通信来读取数据。
所以由此可以推知在gslX680_ts_worker(中断下半部)函数中需要做两件事: 读取触摸屏数据、向input核心层上报数据
/*****************************************************************************/
gslX680_ts_worker
gsl_ts_read(I2C总线设备驱动层提供的函数)
i2c_master_recv (I2C子系统核心层提供的函数)
i2c_transfer(I2C子系统核心层提供的函数)
adap->algo->master_xfer(adap, msgs, num)(I2C子系统总线驱动层提供的函数)
/****************************************************************************/
gsl_ts_write(I2C总线设备驱动层提供的函数)
i2c_master_send(I2C子系统核心层提供的函数)
i2c_transfer(I2C子系统核心层提供的函数)
adap->algo->master_xfer(adap, msgs, num)(I2C子系统总线驱动层提供的函数)
/******************************************************/
现在来分析一下整个的工作流程:
当触摸屏有人按下并且在触摸屏IC中已经完成了AD转换之后就会产生一个中断信号给I2C控制器,就会触发I2C设备驱动层的中断函数。在设备驱动层的中断函数中会调用
核心层提供的读写函数来读取触摸屏的数据,然后核心层最终调用的是I2C总线驱动层(适配器)中注册的通信算法函数来发起一个起始信号来开启一个接收数据的通信周期,
之后的事情就都交给I2C总线驱动层的中断函数来读取数据了,当数据读取完成之后就会将数据存放在缓冲区中。所以我们最终在设备驱动层的中断函数中将读取到的数据进
行一些处理,然后上报给input核心层。
************************************************end******************************************************
I2C总线驱动框架详解的更多相关文章
- i2c总线驱动,总线设备(适配器),从设备,从设备驱动的注册以及匹配
常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 随笔分类 ARM裸机(13) C(8) C++(8) GNU-ARM汇编 Linux驱动(24) Linux应用编程(5) Makefile ...
- Spark2.1.0——内置Web框架详解
Spark2.1.0——内置Web框架详解 任何系统都需要提供监控功能,否则在运行期间发生一些异常时,我们将会束手无策.也许有人说,可以增加日志来解决这个问题.日志只能解决你的程序逻辑在运行期的监控, ...
- I2C子系统驱动框架及应用 (转)
I2C子系统驱动框架: 应用程序层(app层) ——————————————————————————————————– i2c driver层: 从设备驱动层(TS Sensor等) 1. ...
- Solon 框架详解(十一)- Solon Cloud 的配置说明
Solon 详解系列文章: Solon 框架详解(一)- 快速入门 Solon 框架详解(二)- Solon的核心 Solon 框架详解(三)- Solon的web开发 Solon 框架详解(四)- ...
- jQuery Validate验证框架详解
转自:http://www.cnblogs.com/linjiqin/p/3431835.html jQuery校验官网地址:http://bassistance.de/jquery-plugins/ ...
- mina框架详解
转:http://blog.csdn.net/w13770269691/article/details/8614584 mina框架详解 分类: web2013-02-26 17:13 12651人 ...
- lombok+slf4j+logback SLF4J和Logback日志框架详解
maven 包依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ...
- [Cocoa]深入浅出 Cocoa 之 Core Data(1)- 框架详解
Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂.Core Data 相关的类比较多,初学者往往不太容易弄懂.计划用三个教程来讲解这一部分: 框架详解 ...
- iOS 开发之照片框架详解(2)
一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...
随机推荐
- Git----分支管理之分支管理策略04
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息. 如果要强制禁用Fast forward模式,Git就会在merge时生产一个新的comm ...
- delphi webbrowser 执行 js ---转
EmbeddedWB1.OleObject.document.parentWindow.execScript(memo1.Text, 'javascript');
- 系统批量运维管理器pexpect的使用
# pip install pexpect 或 # easy_install pexpect 1 #!/usr/bin/env python 2 import pexpect 3 child = pe ...
- Mysql count+if 函数结合使用
Mysql count+if 函数结合使用 果林椰子 关注 2017.05.18 13:48* 字数 508 阅读 148评论 0喜欢 1 涉及函数 count函数 mysql中count函数用于统计 ...
- jsp+Servlet+JavaBean+JDBC+MySQL项目增删改查
1简单的Mvc,分层建包. java resources src/mian/java (1)dao 包 JDBC连接类,连接数据库.增删改查方法,其他的方法. (2)model包 实体类,数据库字段, ...
- 吴裕雄 python神经网络 水果图片识别(2)
import osimport numpy as npimport matplotlib.pyplot as pltfrom skimage import color,data,transform,i ...
- 吴裕雄 数据挖掘与分析案例实战(5)——python数据可视化
# 饼图的绘制# 导入第三方模块import matplotlibimport matplotlib.pyplot as plt plt.rcParams['font.sans-serif']=['S ...
- JavaScript实现AOP(面向切面编程,装饰者模式)
什么是AOP? AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计.安全控制.异常处理等.把这些功能抽离出来之后, 再通过“动态织入 ...
- chrome 调试跨域iframe
相关链接:https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome https://stacko ...
- IIS中的MIME类型设置
https://www.cnblogs.com/David-Young/p/5323949.html