Rockchip平台TP驱动详解.docx
《Rockchip平台TP驱动详解.docx》由会员分享,可在线阅读,更多相关《Rockchip平台TP驱动详解.docx(25页珍藏版)》请在冰豆网上搜索。
Rockchip平台TP驱动详解
本文描述在RK3126平台上添加一个新的TP驱动(gslx680驱动)以及详细的驱动代码信息。
如有不足之处,敬请指出。
1、修改dts,添加新的i2c设备。
在 arch/arm/boot/dts/rk312x-sdk-v2.2.dtsi中添加i2c设备的相关信息:
ts@40{
compatible="gslX680";
reg=<0x40>;
wake-gpio=<&gpio0GPIO_D3GPIO_ACTIVE_LOW>;
irp-gpio=<&gpio0GPIO_A2IRQ_TYPE_LEVEL_HIGH>;
revert_x=<0>;
revert_y=<0>;
};
∙1
∙2
∙3
∙4
∙5
∙6
∙7
∙8
&i2c2{
status="okay";
/*
ts@55{
compatible="goodix,gt8xx";
reg=<0x55>;
touch-gpio=<&gpio1GPIO_B0IRQ_TYPE_LEVEL_LOW>;
reset-gpio=<&gpio2GPIO_C1GPIO_ACTIVE_LOW>;
//power-gpio=<&gpio0GPIO_C5GPIO_ACTIVE_LOW>;
max-x=<1280>;
max-y=<800>;
};*/
ts@40{
compatible="gslX680";
reg=<0x40>;
//wake-gpio=<&gpio0GPIO_D3GPIO_ACTIVE_LOW>;
irp-gpio=<&gpio0GPIO_A2IRQ_TYPE_LEVEL_HIGH>;
revert_x=<0>;
revert_y=<0>;
};
/*...*/
∙1
∙2
∙3
∙4
∙5
∙6
∙7
∙8
∙9
∙10
∙11
∙12
∙13
∙14
∙15
∙16
∙17
∙18
∙19
∙20
∙21
∙22
表示i2c2总线上下挂在了多个i2c设备。
其中ts@40是表示此i2c设备的设备类型为触摸屏,设备地址为0x40(7位地址,注意:
在i2c的传输函数中,会将此地址左移一位,因此实际上gslx680的i2c设备地址为0x80)。
该节点下有多个属性:
1、compatible="gslX680";属性用于驱动和设备的绑定。
表示特定的设备名称,此处为gslX680;
2、reg=<0x40>;属性表示此设备的i2c地址为0x40,等同于@40;
3、wake-gpio=<&gpio0GPIO_D3GPIO_ACTIVE_LOW>;表示复位引脚使用的是GPIO0中的GPIO_D3这个引脚,低电平有效。
irp-gpio=<&gpio0GPIO_A2IRQ_TYPE_LEVEL_HIGH>;表示中断引脚使用的是GPIO0中的GPIO_A2这个引脚,高电平触发。
很奇怪,为什么这里没有上电的信息,以及在整个驱动程序中都没有给ic上电的操作。
在前面的MTK平台上的tp驱动都有上电的动作,暂时还搞不懂在RK平台上为什么没有。
4、revert_x=<0>;revert_y=<0>;标记x和y是否需要翻转。
在上述的信息中,可以通过of接口获取到属性对应的值。
在后面的probe()函数中就会使用到。
注:
关于dts的详细信息可以查看ARMLinux3.x的设备树(DeviceTree)和DeviceTreeUsage
2、修改Makefile、Kconfig、defconfig
(1)、修改Makefile添加gslx680驱动
在 drivers/input/touchscreen/Makefile中添加驱动:
obj-$(CONFIG_TOUCHSCREEN_GSLX680)+=gslx680/。
只要当配置了CONFIG_TOUCHSCREEN_GSLX680的选项才会去编译gslx680目录下的内容。
在配置内核的时候会通过makemenuconfig来配置对应的选项。
或者是直接在defconfig文件中强制设置该选项。
注:
如果不想要这么复杂,可以将该语句写成obj-y+=gslx680/来强制编译该驱动。
(2)、修改Kconfig添加驱动配置描述
在 drivers/input/touchscreen/Kconfig中添加驱动配置描述:
configTOUCHSCREEN_GSLX680
tristate"gslX680touchscreendriver"
help
gslX680touchscreendriver
∙1
∙2
∙3
∙4
(3)、配置defconfig设置编译驱动
一般在内核中会有配置好的默认的config文件供参考,可以直接修改defconfig来选择编译某个驱动。
此处在arch/arm/configs/rockchip_defconfig文件中添加CONFIG_TOUCHSCREEN_GSLX680=y并将该文件拷贝到kernel目录下命名为.config即可。
2、添加i2c驱动
#defineGSLX680_I2C_NAME"gslX680"
#defineGSLX680_I2C_ADDR0x40
staticconststructi2c_device_idgsl_ts_id[]={
{GSLX680_I2C_NAME,0},
{}
};
MODULE_DEVICE_TABLE(i2c,gsl_ts_id);
staticstructi2c_drivergsl_ts_driver={
.driver={
.name=GSLX680_I2C_NAME,
.owner=THIS_MODULE,
},
#ifndefCONFIG_HAS_EARLYSUSPEND
//.suspend=gsl_ts_suspend,
//.resume=gsl_ts_resume,
#endif
.probe=gsl_ts_probe,
.remove=gsl_ts_remove,
.id_table=gsl_ts_id,
};
staticint__initgsl_ts_init(void)
{
intret;
printk("==gsl_ts_init==\n");
ret=i2c_add_driver(&gsl_ts_driver);
printk("ret=%d\n",ret);
returnret;
}
staticvoid__exitgsl_ts_exit(void)
{
printk("==gsl_ts_exit==\n");
i2c_del_driver(&gsl_ts_driver);
return;
}
∙1
∙2
∙3
∙4
∙5
∙6
∙7
∙8
∙9
∙10
∙11
∙12
∙13
∙14
∙15
∙16
∙17
∙18
∙19
∙20
∙21
∙22
∙23
∙24
∙25
∙26
∙27
∙28
∙29
∙30
∙31
∙32
∙33
∙34
∙35
∙36
∙37
∙38
注册名字为GSLX680_I2C_NAME的i2c驱动,即gslx680,该驱动支持的设备名为字gsl_ts_id[]里的设备名称。
因为我们在dts中已注册了一个名字为gslx680的i2c设备。
因此,设备与驱动可以匹配成功并正确执行probe()函数。
至于设备与驱动是如何匹配的,可以参照Linuxi2c子系统
3、gsl_ts_probe()
staticintgsl_ts_probe(structi2c_client*client,conststructi2c_device_id*id)
{
structgsl_ts*ts;
intrc;
structdevice_node*np=client->dev.of_node;
enumof_gpio_flagswake_flags;
unsignedlongirq_flags;
//检查i2c适配器的能力
printk("GSLX680Enter%s\n",__func__);
if(!
i2c_check_functionality(client->adapter,I2C_FUNC_I2C))
{
dev_err(&client->dev,"I2Cfunctionalitynotsupported\n");
return-ENODEV;
}
//为ts申请内核空间
ts=kzalloc(sizeof(*ts),GFP_KERNEL);
if(!
ts)
return-ENOMEM;
printk("==kzallocsuccess=\n");
ts->client=client;
i2c_set_clientdata(client,ts);
ts->device_id=id->driver_data;
//从设备节点np中获取到irq和wake的gpio的信息
ts->irq_pin=of_get_named_gpio_flags(np,"irp-gpio",0,(enumof_gpio_flags*)&irq_flags);
ts->wake_pin=of_get_named_gpio_flags(np,"wake-gpio",0,&wake_flags);
//为设备申请gpio,并设置默认电平
if(gpio_is_valid(ts->wake_pin))
{
rc=devm_gpio_request_one(&client->dev,ts->wake_pin,(wake_flags&OF_GPIO_ACTIVE_LOW)?
GPIOF_OUT_INIT_LOW:
GPIOF_OUT_INIT_HIGH,"gslX680wakepin");
if(rc!
=0)
{
dev_err(&client->dev,"gslX680wakepinerror\n");
return-EIO;
}
g_wake_pin=ts->wake_pin;
//msleep(100);
}
else
{
dev_info(&client->dev,"wakepininvalid\n");
}
if(gpio_is_valid(ts->irq_pin))
{
rc=devm_gpio_request_one(&client->dev,ts->irq_pin,(irq_flags&OF_GPIO_ACTIVE_LOW)?
GPIOF_OUT_INIT_LOW:
GPIOF_OUT_INIT_HIGH,"gslX680irqpin");
if(rc!
=0)
{
dev_err(&client->dev,"gslX680irqpinerror\n");
return-EIO;
}
}
else
{
dev_info(&client->dev,"irqpininvalid\n");
}
//创建工作队列,申请input设备
rc=gslX680_ts_init(client,ts);
if(rc<0)
{
dev_err(&client->dev,"GSLX680initfailed\n");
gotoerror_mutex_destroy;
}
gsl_client=client;
//从设备节点中获取属性信息
of_property_read_u32(np,"revert_x",&revert_x);//sss
of_property_read_u32(np,"revert_y",&revert_y);//sss
//初始化IC,包括复位,测试i2c以及加载ic配置信息
init_chip(ts->client);
check_mem_data(ts->client);
//申请中断号
ts->irq=gpio_to_irq(ts->irq_pin);//Ifnotdefinedinclient
if(ts->irq)
{
//为client->dev设备的中断号ts->irq申请irq_flags触发的中断,中断服务子程序为gsl_ts_irq
rc=devm_request_threaded_irq(&client->dev,ts->irq,NULL,gsl_ts_irq,irq_flags|IRQF_ONESHOT,client->name,ts);
if(rc!
=0)
{
printk(KERN_ALERT"CannotallocatetsINT!
ERRNO:
%d\n",rc);
gotoerror_req_irq_fail;
}
//disable_irq(ts->irq);
}
else
{
printk("gslx680irqreqfail\n");
gotoerror_req_irq_fail;
}
ts->tp.tp_resume=gsl_ts_late_resume;
ts->tp.tp_suspend=gsl_ts_early_suspend;
tp_register_fb(&ts->tp);
#ifdefCONFIG_HAS_EARLYSUSPEND
ts->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
#ifdefGSL_MONITOR
printk("gsl_ts_probe():
queuegsl_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);
#endif
printk("[GSLX680]End%s\n",__func__);
return0;
//exit_set_irq_mode:
error_req_irq_fail:
free_irq(ts->irq,ts);
error_mutex_destroy:
input_free_device(ts->input);
kfree(ts);
returnrc;
}
∙1
∙2
∙3
∙4
∙5
∙6
∙7
∙8
∙9
∙10
∙11
∙12
∙13
∙14
∙15
∙16
∙17
∙18
∙19
∙20
∙21
∙22
∙23
∙24
∙25
∙26
∙27
∙28
∙29
∙30
∙31
∙32
∙33
∙34
∙35
∙36
∙37
∙38
∙39
∙40
∙41
∙42
∙43
∙44
∙45
∙46
∙47
∙48
∙49
∙50
∙51
∙52
∙53
∙54
∙55
∙56
∙57
∙58
∙59
∙60
∙61
∙62
∙63
∙64
∙65
∙66
∙67
∙68
∙69
∙70
∙71
∙72
∙73
∙74
∙75
∙76
∙77
∙78
∙79
∙80
∙81
∙82
∙83
∙84
∙85
∙86
∙87
∙88
∙89
∙90
∙91
∙92
∙93
∙94
∙95
∙96
∙97
∙98
∙99
∙100
∙101
∙102
∙103
∙104
∙105
∙106
∙107
∙108
∙109
∙110
∙111
∙112
∙113
∙114
∙115
∙116
∙117
∙118
∙119
∙120
∙121
∙122
∙123
∙124
∙125
∙126
∙127
∙128
∙129
∙130
∙131
(1)、自定义的数据结构gsl_ts
一般在自己的驱动程序中,都会为该驱动程序封装一个数据结构,这里的gsl_ts就充当这种角色。
在该驱动程序中自定义了一个数据结构:
structgsl_ts{
structi2c_client*client;
structinput_dev*input;
structwork_structwork;
structworkqueue_struct*wq;
structgsl_ts_data*dd;
u8*touch_data;
u8device_id;
intirq;
intirq_pin;
intwake_pin;
structtp_devicetp;
#ifdefined(CONFIG_HAS_EARLYSUSPEND)
structearly_suspendearly_suspend;
#endif
};
∙1
∙2
∙3
∙4
∙5
∙6
∙7
∙8
∙9
∙10
∙11
∙12
∙13
∙14
∙15
∙16
client表示一个i2c的设备;
input表示一个输入设备;
work表示一个工作,用于处理中断到来之后获取坐标等信息;
wq表示一个工作队列,将上面的work加入到该工作队列中;
dd和touch_data用来存储坐标的相关信息;
device_id表示i2c设备的设备号;
irq申请的中断号;
irq_pin中断引脚;
wake_pin复位引脚;这两个引脚信息可以通过dts获取到.
tp创建一个为tp_device的数据结构,里面有个成员notifier_block用来接收LCD背光灯的亮暗的通知进而调用suspend()和resume()。
主要的实现在tp_suspend.h中。
至于这里面的详细机制还不是很明白。
(2)、检查i2c适配器的能力
在很多i2c设备驱动程序中,一进入probe()就要检查i2c适配器的能力。
现在还不清楚这么做的目的是什么。
后面会仔细的学习一下linux的i2c子系统。
(3)、获取wake和irq的引脚信息
在前面配置dts说过:
dts中设备节点的属性的值可以通过of_接口获取到。
//从设备节点np中获取到irq和wake的gpio的信息
ts->irq_pin=of_get_named_gpio_flags(np,"irp-gpio",0,(enumof_gpio_flags*)&irq_flags);
ts->wake_pin=of_get_named_gpio_flags(np,"wake-gpio",0,&wake_flags);
∙1
∙2
∙3
可以通过设备节点np获取到它irq-gpio这一个属性的值存放在ts->irq_pin中,以及将其flag刚到变量irq_flags中。
因此我们可以知道:
ts->irq_pin=GPIO_A2
irq_flag=IRQ_TYPE_LEVEL_HIGH
∙1
∙2
同理,wake_pin也是一样。
(4)、申请gpio并设置输出电平
将irq和wake引脚电平都设置输出低电平。
(5)、gslX680_ts_init
staticintgslX680_ts_init(structi2c_client*client,structgsl_ts*ts)
{
structinput_dev*input_device;
intrc=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:
Unabletoallocatememory\n",__func__);
return-ENOMEM;
}
//申请一个input_dev设备
input_device=input_allocate_device();
if(!
input_device){
rc=-ENOMEM;
gotoerror_alloc_dev;
}
//初始化input_device
ts->input=input_device;
input_device->name=GSLX680_I2C_NAME;
input_devi