Rockchip平台TP驱动详解【转】
2017-09-30 17:51
351 查看
本文转载自:http://blog.csdn.net/encourage2011/article/details/51679332
本文描述在RK3126平台上添加一个新的TP驱动(gslx680驱动)以及详细的驱动代码信息。如有不足之处,敬请指出。
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
表示i2c2总线上下挂在了多个i2c设备。
其中
1、
2、
3、
很奇怪,为什么这里没有上电的信息,以及在整个驱动程序中都没有给ic上电的操作。在前面的MTK平台上的tp驱动都有上电的动作,暂时还搞不懂在RK平台上为什么没有。
4、
在上述的信息中,可以通过
只要当配置了
注:如果不想要这么复杂,可以将该语句写成
1
2
3
4
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
注册名字为
至于设备与驱动是如何匹配的,可以参照Linux i2c子系统
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)、自定义的数据结构
一般在自己的驱动程序中,都会为该驱动程序封装一个数据结构,这里的
在该驱动程序中自定义了一个数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
可以通过设备节点
1
2
同理,
(5)、
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
在
初始化工作
1
2
3
注:如果申请资源出错的话一定要记得释放资源以及前面的资源。比如说这里为
上述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
一旦有中断到来,立马调用
本文描述在RK3126平台上添加一个新的TP驱动(gslx680驱动)以及详细的驱动代码信息。如有不足之处,敬请指出。
1、修改dts,添加新的i2c设备。
在arch/arm/boot/dts/rk312x-sdk-v2.2.dtsi中添加i2c设备的相关信息:
ts@40 { compatible = "gslX680"; reg = <0x40>; wake-gpio = <&gpio0 GPIO_D3 GPIO_ACTIVE_LOW>; irp-gpio = <&gpio0 GPIO_A2 IRQ_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 = <&gpio1 GPIO_B0 IRQ_TYPE_LEVEL_LOW>; reset-gpio = <&gpio2 GPIO_C1 GPIO_ACTIVE_LOW>; //power-gpio = <&gpio0 GPIO_C5 GPIO_ACTIVE_LOW>; max-x = <1280>; max-y = <800>; };*/ ts@40 { compatible = "gslX680"; reg = <0x40>; //wake-gpio = <&gpio0 GPIO_D3 GPIO_ACTIVE_LOW>; irp-gpio = <&gpio0 GPIO_A2 IRQ_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 = <&gpio0 GPIO_D3 GPIO_ACTIVE_LOW>;表示复位引脚使用的是GPIO0 中的GPIO_D3这个引脚,低电平有效。
irp-gpio = <&gpio0 GPIO_A2 IRQ_TYPE_LEVEL_HIGH>;表示中断引脚使用的是GPIO0中的GPIO_A2这个引脚,高电平触发。
很奇怪,为什么这里没有上电的信息,以及在整个驱动程序中都没有给ic上电的操作。在前面的MTK平台上的tp驱动都有上电的动作,暂时还搞不懂在RK平台上为什么没有。
4、
revert_x = <0>; revert_y = <0>;标记x和y是否需要翻转。
在上述的信息中,可以通过
of接口获取到属性对应的值。在后面的
probe()函数中就会使用到。
2、修改Makefile、Kconfig、defconfig
(1)、修改Makefile添加gslx680驱动
在drivers/input/touchscreen/Makefile中添加驱动:
obj-$(CONFIG_TOUCHSCREEN_GSLX680) += gslx680/。
只要当配置了
CONFIG_TOUCHSCREEN_GSLX680的选项才会去编译
gslx680目录下的内容。在配置内核的时候会通过
make menuconfig来配置对应的选项。或者是直接在defconfig文件中强制设置该选项。
注:如果不想要这么复杂,可以将该语句写成
obj-y += gslx680/来强制编译该驱动。
(2)、修改Kconfig添加驱动配置描述
在drivers/input/touchscreen/Kconfig中添加驱动配置描述:
config TOUCHSCREEN_GSLX680 tristate "gslX680 touchscreen driver" help gslX680 touchscreen driver
1
2
3
4
(3)、配置defconfig设置编译驱动
一般在内核中会有配置好的默认的config文件供参考,可以直接修改defconfig来选择编译某个驱动。此处在arch/arm/configs/rockchip_defconfig文件中添加
CONFIG_TOUCHSCREEN_GSLX680=y并将该文件拷贝到kernel目录下命名为.config即可。
2、添加i2c驱动
#define GSLX680_I2C_NAME "gslX680" #define GSLX680_I2C_ADDR 0x40 static const struct i2c_device_id gsl_ts_id[] = { {GSLX680_I2C_NAME, 0}, {} }; MODULE_DEVICE_TABLE(i2c, gsl_ts_id); 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, .remove = gsl_ts_remove, .id_table = gsl_ts_id, }; static int __init gsl_ts_init(void) { int ret; printk("==gsl_ts_init==\n"); ret = i2c_add_driver(&gsl_ts_driver); printk("ret=%d\n",ret); return ret; } static void __exit gsl_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()函数。
至于设备与驱动是如何匹配的,可以参照Linux i2c子系统
3、gsl_ts_probe()
static int gsl_ts_probe(struct i2c_client *client,const struct i2c_device_id *id) { struct gsl_ts *ts; int rc; struct device_node *np = client->dev.of_node; enum of_gpio_flags wake_flags; unsigned long irq_flags; // 检查i2c适配器的能力 printk("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申请内核空间 ts = kzalloc(sizeof(*ts), GFP_KERNEL); if(!ts) return -ENOMEM; printk("==kzalloc success=\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, (enum of_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, "gslX680 wake pin"); if(rc != 0) { dev_err(&client->dev, "gslX680 wake pin error\n"); return -EIO; } g_wake_pin = ts->wake_pin; //msleep(100); } else { dev_info(&client->dev, "wake pin invalid\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, "gslX680 irq pin"); if (rc != 0) { dev_err(&client->dev, "gslX680 irq pin error\n"); return -EIO; } } else { dev_info(&client->dev, "irq pin invalid\n"); } // 创建工作队列,申请input设备 rc = gslX680_ts_init(client, ts); if(rc < 0) { dev_err(&client->dev, "GSLX680 init failed\n"); goto error_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); //If not defined in client 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 "Cannot allocate ts INT!ERRNO:%d\n", rc); goto error_req_irq_fail; } //disable_irq(ts->irq); } else { printk("gsl x680 irq req fail\n"); goto error_req_irq_fail; } ts->tp.tp_resume = gsl_ts_late_resume; ts->tp.tp_suspend = gsl_ts_early_suspend; tp_register_fb(&ts->tp); #ifdef CONFIG_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 #ifdef GSL_MONITOR printk( "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); #endif printk("[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
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就充当这种角色。
在该驱动程序中自定义了一个数据结构:
struct gsl_ts { struct i2c_client *client; struct input_dev *input; struct work_struct work; struct workqueue_struct *wq; struct gsl_ts_data *dd; u8 *touch_data; u8 device_id; int irq; int irq_pin; int wake_pin; struct tp_device tp; #if defined(CONFIG_HAS_EARLYSUSPEND) struct early_suspend early_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, (enum of_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
static int gslX680_ts_init(struct i2c_client *client, struct gsl_ts *ts) { struct input_dev *input_device; 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_dev 设备 input_device = input_allocate_device(); if (!input_device) { rc = -ENOMEM; goto error_alloc_dev; } // 初始化input_device ts->input = input_device; input_device->name = GSLX680_I2C_NAME; input_device->id.bustype = BUS_I2C; input_device->dev.parent = &client->dev; input_set_drvdata(input_device, ts); // #ifdef REPORT_DATA_ANDROID_4_0 __set_bit(EV_ABS, input_device->evbit); __set_bit(EV_KEY, input_device->evbit); __set_bit(EV_REP, input_device->evbit); __set_bit(INPUT_PROP_DIRECT, input_device->propbit); input_mt_init_slots(input_device, (MAX_CONTACTS+1),0); #else input_set_abs_params(input_device,ABS_MT_TRACKING_ID, 0, (MAX_CONTACTS+1), 0, 0); set_bit(EV_ABS, input_device->evbit); set_bit(EV_KEY, input_device->evbit); __set_bit(INPUT_PROP_DIRECT, input_device->propbit); input_device->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); #endif #ifdef HAVE_TOUCH_KEY input_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); #endif set_bit(ABS_MT_POSITION_X, input_device->absbit); set_bit(ABS_MT_POSITION_Y, input_device->absbit); set_bit(ABS_MT_TOUCH_MAJOR, input_device->absbit); set_bit(ABS_MT_WIDTH_MAJOR, input_device->absbit); input_set_abs_params(input_device,ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0); input_set_abs_params(input_device,ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0); input_set_abs_params(input_device,ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0); input_set_abs_params(input_device,ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0); // 创建工作队列 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); // 初始化工作 ts->work,其操作为 gslX680_ts_worker() INIT_WORK(&ts->work, gslX680_ts_worker); // 向input子系统注册一个input_dev rc = input_register_device(input_device); 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; }
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
在
gslX680_ts_init()中主要做了如下工作:
配置获取坐标信息
每次当中断来了之后,就要求通过i2c去读取坐标的信息,至于从哪里读取以及读取多少个,都是通过ts->dd来决定的。
为存储坐标信息申请空间
坐标信息放在ts->touch_data中。
申请及初始化input_dev设备,向input子系统注册该设备
这里面的内容涉及到input子系统,我还没有做过深入的了解。初始化工作 ts->work
ts->work对应的操作为
gslX680_ts_worker(),在中断来了之后,会
queue_work(ts->wq, &ts->work);让
ts->work工作起来,就会去读取坐标等信息,然后通过input子系统上报给Android系统。
(6)、获取属性信息
通过of_接口获取
revert_x和revert_y的信息,以此来决定坐标是否要翻转。
(7)、初始化ic
初始化的内容会放到一个全局的数组之中,这项工作一般都要FAE来完成。(8)、申请中断号以及中断服务子程序
通过devm_request_threaded_irq接口为设备申请一个中断服务子程序
gsl_ts_irq(),触发方式为
irq_flags即
IRQ_TYPE_LEVEL_HIGH高电平触发。
(9)、配置休眠唤醒
在前面说过,tp的休眠唤醒是通过LCD亮暗屏来决定的,这个动作由tp_register_fb()来实现。
ts->tp.tp_resume = gsl_ts_late_resume; ts->tp.tp_suspend = gsl_ts_early_suspend; tp_register_fb(&ts->tp);
1
2
3
注:如果申请资源出错的话一定要记得释放资源以及前面的资源。比如说这里为
ts申请的内核空间、申请的中断号、申请的input设备、申请的工作队列。
上述
probe()配置完成之后就是等待中断,如果中断到来,关闭中断,启动工作去读取坐标等信息并通过input子系统上报,之后再使能中断。如此反复。
4、中断服务子程序
static irqreturn_t gsl_ts_irq(int irq, void *dev_id) { struct gsl_ts *ts = dev_id; print_info("========gslX680 Interrupt=========\n"); disable_irq_nosync(ts->irq); if (!work_pending(&ts->work)) { queue_work(ts->wq, &ts->work); } return IRQ_HANDLED; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
一旦有中断到来,立马调用
gsl_ts_irq(),在这个中断服务子程序中先判断
ts->work是否挂起,如果没有挂起就启动工作队列
ts->wq的工作
ts->work。
ts->work与
gslX680_ts_worker()对应,主要用来读取坐标信息。
5、休眠唤醒
关于休眠和唤醒的内容根据ic的特性设置。如休眠的时候需要关闭中断、配置进入休眠模式、拉低wake引脚。唤醒的时候唤醒ic,使能wake引脚、使能中断等。相关文章推荐
- Rockchip平台TP驱动详解
- Rockchip平台TP驱动详解
- MTK平台TP驱动详解
- MTK平台tp驱动详解
- linux平台设备驱动架构详解 Linux Platform Device and Driver——神文,非常详细
- FS_S5PC100平台上Linux Camera驱动开发详解(二)
- 平台总线设备驱动详解
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- FS_S5PC100平台上Linux Camera驱动开发详解(二)
- FS_S5PC100平台上Linux Camera驱动开发详解(二)
- FS_S5PC100平台上Linux Camera驱动开发详解(二)
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- MTK平台tp触摸屏驱动分析
- S5PC100平台上Linux Camera驱动开发详解(一)
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- linux驱动由浅入深系列:基于高通平台分析触摸屏(TP)、虚拟按键驱动
- S5PC100平台上Linux Camera驱动开发详解(二)
- FS_S5PC100平台上Linux Camera驱动开发详解(一)
- linux平台设备驱动架构详解 Linux Platform Device and Driver