以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、前言
由I2C总线设备的驱动框架可知,I2C总线设备的驱动框架涉及的文件如下:
(1)I2C设备驱动层相关的文件
x210开发板的电容触摸屏gslX680采用I2C接口,因此以这个I2C设备为例进行分析。其对应的驱动源代码文件是x210_kernel\drivers\input\touchscreen\gslX680.c文件。
它由触摸屏IC原厂工程师提供,代码中涉及到很多触摸屏专业方面的知识,但无需理会。
gslX680.c文件进行了I2C设备驱动的注册,相应的I2C设备注册在mach-x210.c文件。
(2)I2C核心层相关的文件
I2C核心层相关的文件是i2c-core.c文件,它由内核开发者提供,和具体的硬件操作无关。(3)I2C总线驱动层相关的文件
algos目录:存放的是I2C通信的算法(主要是时序等内容)。busses目录:存放的是各种已经编写好的、将来要向i2c核心层注册的适配器文件。
我们主要分析drivers\i2c\busses\i2c-s3c2410.c文件。
下面将对I2C设备驱动层相关文件进行分析,主要分析gslX680.c文件。
二、I2C设备驱动层代码分析
同平台总线类似,I2C总线分为I2C总线设备层(struct i2c_client)和 I2C总线驱动层(struct i2c_driver)。
gslX680.c文件提供的是I2C总线驱动的注册,相应的I2C总线设备注册在mach-x210.c文件中。
1、I2C总线驱动的注册
static int __init gsl_ts_init(void) {int ret;print_info("==gsl_ts_init==\n");ret = i2c_add_driver(&gsl_ts_driver);//注册I2C总线驱动print_info("ret=%d\n",ret);return ret; } module_init(gsl_ts_init);
static struct i2c_driver gsl_ts_driver = {.driver = {.name = GSLX680_I2C_NAME,//驱动的名字,并不用于设备和设备驱动的匹配.owner = THIS_MODULE,}, #ifndef CONFIG_HAS_EARLYSUSPEND.suspend = gsl_ts_suspend,.resume = gsl_ts_resume, #endif.probe = gsl_ts_probe,//被I2C总线层的probe函数调用.remove = __devexit_p(gsl_ts_remove),.id_table = gsl_ts_id,//用来匹配本驱动支持的设备 };
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_clienti2c_set_clientdata(client, ts); // 将gsl_ts作为i2c次设备的私有数据区中的设备驱动私有数据ts->device_id = id->driver_data;rc = gslX680_ts_init(client, ts); // 初始化操作if (rc < 0) {dev_err(&client->dev, "GSLX680 init failed\n");goto error_mutex_destroy;}gsl_client = client; // 通过一个全局的i2c_client指针gsl_client去指向传进来的i2c次设备i2c_clientgslX680_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 < 0) { // 当发生中断的时候表示有数据可以进行读取了,那么就会通知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_EARLYSUSPENDts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;//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_MONITORprint_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, 1000); #endifprint_info("[GSLX680] End %s\n", __func__);return 0;//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; }
(1)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 = 0;printk("[GSLX680] Enter %s\n", __func__);ts->dd = &devices[ts->device_id];if (ts->device_id == 0) {ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data;ts->dd->touch_index = 0;}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, 0, SCREEN_MAX_X, 0, 0);input_set_abs_params(input_device, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);input_set_abs_params(input_device, ABS_PRESSURE, 0, 1, 0, 0); #ifdef HAVE_TOUCH_KEYinput_device->evbit[0] = BIT_MASK(EV_KEY);//input_device->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);for (i = 0; i < MAX_KEY_NUM; i++)set_bit(key_array[i], input_device->keybit); #endifclient->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 0;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_workergsl_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子系统总线驱动层提供的函数)
3、工作流程总结
当触摸屏有人按下并且在触摸屏IC中已经完成了AD转换之后就会产生一个中断信号给I2C控制器,就会触发I2C设备驱动层的中断函数。在设备驱动层的中断函数中会调用核心层提供的读写函数来读取触摸屏的数据,然后核心层最终调用的是I2C总线驱动层(适配器)中注册的通信算法函数来发起一个起始信号来开启一个接收数据的通信周期,之后的事情就都交给I2C总线驱动层的中断函数来读取数据了,当数据读取完成之后就会将数据存放在缓冲区中。所以我们最终在设备驱动层的中断函数中将读取到的数据进行一些处理,然后上报给input核心层。
4、i2c_client的来源
mach-x210.c文件,smdkc110_machine_init 函数调用 i2c_register_board_info函数,如下所示:
static void __init smdkc110_machine_init(void) {//省略部分内容 //i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));//省略部分内容 }
其中i2c_devs*的定义如下。/* I2C0 */ static struct i2c_board_info i2c_devs0[] __initdata = { #ifdef CONFIG_SND_SOC_WM8580{I2C_BOARD_INFO("wm8580", 0x1b),}, #endif };/* I2C1 */ static struct i2c_board_info i2c_devs1[] __initdata = { #ifdef CONFIG_VIDEO_TV20{I2C_BOARD_INFO("s5p_ddc", (0x74>>1)),}, #endif #ifdef CONFIG_TOUCHSCREEN_GSLX680{I2C_BOARD_INFO("gslX680", 0x40),}, #endif };/* I2C2 */ static struct i2c_board_info i2c_devs2[] __initdata = {{ #if defined(CONFIG_SMDKC110_REV03) || defined(CONFIG_SMDKV210_REV02)/* The address is 0xCC used since SRAD = 0 */I2C_BOARD_INFO("max8998", (0xCC >> 1)),.platform_data = &max8998_pdata, #else/* The address is 0xCC used since SRAD = 0 */I2C_BOARD_INFO("max8698", (0xCC >> 1)),.platform_data = &max8698_pdata, #endif}, };
struct i2c_board_info {char type[I2C_NAME_SIZE];//设备名字unsigned short flags;//属性unsigned short addr;//设备从地址void *platform_data;struct dev_archdata *archdata; #ifdef CONFIG_OFstruct device_node *of_node; #endifint irq;//设备使用的IRQ号,对应cpu的EINT };
(1)实现原理分析
内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。即这个链表维护的是一个struct i2c_board_info结构体链表。真正的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len) {int status;down_write(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);//这里}up_write(&__i2c_board_lock);return status; }
(2)函数调用层次
|……i2c_add_adapter/i2c_add_numbered_adapter |…………i2c_register_adapter |………………i2c_scan_static_board_info |……………………i2c_new_device |…………………………device_register
(3)总结
I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的:
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));