本文转载自:http://blog.csdn.net/encourage2011/article/details/51679332
本文描述在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()
函数中就会使用到。
注:关于dts的详细信息可以查看ARM Linux 3.x的设备树(Device Tree)和Device Tree Usage
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引脚、使能中断等。