您的位置:首页 > 编程语言 > PHP开发

Rockchip平台TP驱动详解【转】

2017-09-30 17:51 351 查看
本文转载自: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引脚、使能中断等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: