(一)init函数
i2c_add_driver 调用 i2c_register_driver驱动注册
i2c_register_driver 初始化驱动,将驱动添加到i2c驱动中,再调用driver_register
driver_register检查驱动在总线上是否注册过,若未注册,则调用bus_add_driver 在总线上添加驱动
bus_add_driver将驱动加到总线上(通过klist),调用driver_attach
driver_attach返回函数bus_for_each_dev
bus_for_each_dev遍历总线上每个设备,并调用__driver_attach函数
__driver_attach函数调用driver_match_device函数匹配设备与驱动
driver_match_device函数返回drv->bus->match ? drv->bus->match(dev, drv) 调用bus的match函数匹配,若匹配上(通过.compatible),则调用driver_probe_device进行设备初始化
driver_probe_device调用really_probe进行初始化
really_probe调用driver->probe()(如果bus->probe非空,则调用bus->probe)
(二)Probe过程
mxt_get_platform_data(client)获取平台数据
input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化.
再对输入设备及数据赋值
__set_bit(EV_ABS, input_dev->evbit);//绝对坐标事件,触摸屏每次发送的坐标都是绝对坐标,不同于鼠标的相对坐标
__set_bit(EV_KEY, input_dev->evbit);//按键事件,每次触摸都有一个BTN_TOUCH的按键事件
__set_bit(BTN_TOUCH, input_dev->keybit);//touch类型按键
__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
error = input_mt_init_slots(input_dev, MXT_MAX_FINGER, 0);//报告最大支持的点数
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
0, MXT_MAX_AREA, 0, 0);
//将触摸点看成一个椭圆,它的长轴长度
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
0, MXT_DISS_MAX_X, 0, 0);//x坐标取值范围
input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
0, MXT_DISS_MAX_Y, 0, 0); //y坐标取值范围
input_set_abs_params(input_dev, ABS_MT_PRESSURE,
0, 255, 0, 0);
input_set_drvdata(input_dev, data);//保存data结构到input_dev里面
i2c_set_clientdata(client, data);//保存数据
error = mxt_configure_gpio(data,true);
//配置gpio,具体如下:
/*
gpio_request(data->pdata->reset_gpio, "mxt_reset_gpio")
//申请一个GPIO为reset
gpio_direction_output(data->pdata->reset_gpio, 0)
//设置reset_GPIO为输出,初始值为0
gpio_request(data->pdata->pow_en_gpio,"mxt_pow_en_gpio")
//申请一个GPIO为pow_en;
gpio_direction_output(data->pdata->pow_en_gpio, 1)
//设置pow_en为输出,初始值为1
msleep(1)
//延时1ms,休眠函数,是将当前调用线程挂起一段时间,不占用cpu资源。
gpio_set_value(data->pdata->reset_gpio, 1)
//将reset_GPIO设置为1(拉高)
msleep(100)//延时100ms
*/
error = mxt_configure_irq_gpio(data);//配置中断,具体如下:
/*
gpio_request(data->pdata->irq_gpio,"mxt_irq_gpio")
//申请一个GPIO为irq
gpio_direction_input(data->pdata->irq_gpio)
//设置irq_GPIO为输入
*/
error = input_register_device(input_dev);
//注册一个input设备
/*
__set_bit(EV_SYN, dev->evbit);
/* 事件类型由input_dev的evbit成员来表示,
在这里将其EV_SYN置位,表示设备支持所有的事件。
注意,一个设备可以支持一种或者多种事件类型
#define EV_SYN 0x00 //表示设备支持所有的事件
#define EV_KEY 0x01 //键盘或者按键,表示一个键码
#define EV_REL 0x02 //鼠标设备,表示一个相对的光标位置结果
#define EV_ABS 0x03 //手写板产生的值,其是一个绝对整数值
#define EV_MSC 0x04 //其他类型
#define EV_LED 0x11 //LED灯设备
#define EV_SND 0x12 //蜂鸣器,输入声音
#define EV_REP 0x14 //允许重复按键类型
#define EV_PWR 0x16 //电源管理事件
*/
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
input_enable_softrepeat(dev, 250, 33);
/*
* rep主要是处理重复按键,如果没有定义dev->rep[REP_DELAY]和 dev->rep[REP_PERIOD],
* 则将其赋值为默认值。dev->rep[REP_DELAY]是指第一次按下多久算一次,这里是250ms,
* dev->rep[REP_PERIOD]指如果按键没有被抬起,每33ms算一次。
*/
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
/*
* 如果dev没有定义getkeycode和setkeycode,则赋默认值。
* 他们的作用一个是获得键的扫描码,一个是设置键的扫描码
*/
list_add_tail(&dev->node, &input_dev_list);
//将新分配的input设备连接到input_dev_list链表上
list_for_each_entry(handler, &input_handler_list, node)
//宏for循环
input_attach_handler(dev, handler);
/*
* input_attach_handler的主要功能就是调用了两个函数,一个input_match_device进行配对,一个connect处理配对成功后续工作
* input_match_device通过handler->id_table(.driver_info = 1匹配所有设备)
* connect调用input_register_handle将d_node挂到input_device的h_list,h_node挂到handler的h_list来连接input_dev、input_handler与input_handl)
*/
//遍历input_handler_list链表,配对 input_dev 和 input_handler
/*
* 这里先把input_dev挂载到input_dev_list 链表上,然后对每个挂载到input_handler_list 的handler
* 调用input_attach_handler(dev, handler); 去匹配。
* 所有的input_dev挂载到input_dev_list 链表上
* 所有的handler挂载到input_handler_list 上
*/
/*
注册input device的过程就是为input device设置默认值,并将其挂在 input_dev_list上与挂在input_handler_list中的handler相匹配。如果匹配成功就会调用handler的connnect函数
*/
*/
snprintf(data->phys,sizeof(data->phys),"i2c-%u-%04x/input0", client->adapter->nr, client->addr);
//设将可变参数client->adapter->nr, client->addr按照 i2c-%u-%04x/input0格式化成字符串,并将字符串复制到 data->phys中,sizeof(data->phys)为要写入的字符的最大数目,超过会被截断。
init_completion(&data->chg_completion);
init_completion(&data->reset_completion);
init_completion(&data->crc_completion);
init_completion(&data->test_completion);//初始化completion
mutex_init(&data->debug_msg_lock);//初始化互斥体
irq_of_parse_and_map(client->dev.of_node, 0); //在设备树里查找中断的描述项,然后返回中断号。第二个参数是0,表示使用设备树中的第一个中断。
mxt_acquire_irq(data);//获取中断
mxt_acquire_irq
mxt_process_messages_until_invalid
mxt_read_and_process_messages <<---
mxt_proc_message
mxt_input_button <--------
input_report_key
mxt_proc_t15_messages <--------
input_event
input_sync
input_sync <<---
disable_irq(data->irq);//屏蔽中断
mxt_probe_regulators(data);//初始化电源设备,具体如下:
/*
data->reg_vdd = regulator_get(dev, "vdd");
data->reg_avdd = regulator_get(dev, "avdd");
//获取设备regulator
mxt_regulator_enable(data);//使能电源输出。
*/
mxt_initialize(data);
//初始化设备:1.读取基本信息 2.检测bootloader基本状态 3.申请中断 4.检测是否需要更新固件
sysfs_create_group(&client->dev.kobj, &mxt_fw_attr_group); //创建sys文件节点
mxt_sysfs_init(data); //初始化
mxt_debug_msg_init(data);
msm_drm_register_client(&data->mxt_fb_notifier);
mxt_self_test(data);
create_workqueue("mxt_queue");
INIT_WORK(&mxt_init, mxt_initialize_work);
(二)事件上报流程
1. input.c
__init input_init(void)//input子系统的初始化函数
在input_init函数中,先注册了一个名为input_class的类,所有input device都属于这个类。
在sysfs中表现就是:所有input device所代表的目录都位于/dev/class/input下面
然后调用input_proc_init()在/proc下面建立相关的交互文件
再调用register_chrdev()注册了主设备号为INPUT_MAJOR(13),次设备号为0~255的字符设备
2. 事件处理层 evdev.c (与上层交互)
evdev_fops 成员函数为对上层提供的接口
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
(1)evdev_open, 上层打开设备文件时调用
1. 分配并初始化一个client结构体,并将它和evdev关联起来
关联的内容是,将client->evdev指向它所表示的evdev,调用evdev_attach_client()将client挂到evdev->client_list上
(驱动层上报的输入事件的键值,就是存放在evdev->buffer中的)
2. 调用evdev_open_device()函数,打开输入设备使设备准备好接收或者发送数据
最终是通过调用核心层input.c中的input_open_device函数实现打开设备
(2)evdev_read
1. 调用input_event_to_user,在函数中调用copy_to_user上报键值
input_event_to_user
copy_to_user
__chk_user_ptr//检查buffer内存
volatile_memcpy//将event事件保存在buffer里
2. wait_event_interruptible等待队列等待事件,没有事件就阻塞
阻塞事件(以TP为例):触摸按键事件
(事件处理层对上层的读操作,用一个等待队列实现阻塞,这样就能保证,我们只有在触摸按键事件发生,中断到来,我们才去上报按键事件,并唤醒阻塞,让事件处理层的evdev_read将键值最终通过copy_to_user送到用户空间)
evdev_read函数中多次调用input_event_size函数进行条件判断。它判断缓存区大小是否足够,在读取数据的情况下,可能当前缓存区内没有数据可读,在这里先睡眠等待缓存区中有数据,如果在睡眠的时候,条件满足,是不会进行睡眠状态而直接返回的,然后根据read()提供的缓存区大小,将client中的数据(即上报的键值)写入到用户空间的缓存区中
驱动中
cyttsp6_setup_irq_gpio
request_threaded_irq(cd->irq, cyttsp6_hard_irq, cyttsp6_irq, irq_flags, dev_name(dev), cd);
cyttsp6_irq
call_atten_cb
atten->func(atten->dev);(cyttsp6_mt_attention)
/*
* 通过最初cyttsp6_mt_open函数调用
* cyttsp6_subscribe_attention_函数设置触摸call back线程,
* 将 atten->func设置为cyttsp6_mt_attention
*/
cyttsp6_xy_worker
cyttsp6_get_mt_touches
cyttsp6_report_event
input_report_abs
cyttsp6_mt_lift_all
input_sync
1. input_event与events
驱动代码中,上报键值时调用
input_sync
input_report_key
两者都调用了input_event函数
input_event
input_handle_event
input_pass_values
input_to_handler
handler->event
//evdev_handler的成员函数evdev_event
evdev_events
handler->events
//evdev_handler的成员函数evdev_events
evdev_pass_values
wake_up_interruptible//唤醒睡眠
总结:在evdev_read函数中通过等待队列实现了阻塞,等待输入事件的随机发生
而在触摸按键事件随机发生后,设备驱动层代码通过input_report_key等函数上报数据,逐级调用,通过在事件处理层evdev.c里定义的evdev_handler->events处理事件,最终调用到evdev_pass_values并唤醒睡眠
一旦在evdev_read中的等待队列被唤醒,则进入下一轮循环上报新的键值给用户空间
(三)event匹配
在evdev_connect中,在input_register_handle连接dev、handle和handler之后,cdev_init连接cdev与fops接口函数,
(kernel/msm-4.14/fs/char_dev.c:651)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
cdev_device_add通过cdev_add将dev与cdev进行关联,
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
定义一个 cdev 结构,然后定义一个 file_operations 结构,然后把这两个结构关联到一起。当然,还要把这个cdev 关联到 dev 设备号上。