zoukankan      html  css  js  c++  java
  • Rockchip平台TP驱动详解【转】

    本文转载自: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==
    ");
        ret = i2c_add_driver(&gsl_ts_driver);
        printk("ret=%d
    ",ret);
        return ret;
    }
    static void __exit gsl_ts_exit(void)
    {
        printk("==gsl_ts_exit==
    ");
        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_NAMEi2c驱动,即gslx680,该驱动支持的设备名为字gsl_ts_id[]里的设备名称。因为我们在dts中已注册了一个名字为gslx680i2c设备。因此,设备与驱动可以匹配成功并正确执行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
    ", __func__);
        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
        {
            dev_err(&client->dev, "I2C functionality not supported
    ");
            return -ENODEV;
        }
    
        // 为ts申请内核空间
        ts = kzalloc(sizeof(*ts), GFP_KERNEL);
        if(!ts)
            return -ENOMEM;
        printk("==kzalloc success=
    ");
    
        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
    ");
                return -EIO;
            }
            g_wake_pin = ts->wake_pin;
            //msleep(100);
        }
        else
        {
            dev_info(&client->dev, "wake pin invalid
    ");
        }
        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
    ");
                return -EIO;
            }
        }
        else
        {
            dev_info(&client->dev, "irq pin invalid
    ");
        }
    
        // 创建工作队列,申请input设备
        rc = gslX680_ts_init(client, ts);
        if(rc < 0)
        {
            dev_err(&client->dev, "GSLX680 init failed
    ");
            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
    ", rc);
                goto error_req_irq_fail;
            }
            //disable_irq(ts->irq);
        }
        else
        {
            printk("gsl x680 irq req fail
    ");
            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
    ");
    
        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
    ", __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加入到该工作队列中; 
    ddtouch_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
    ", __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
    ", __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
    ");
            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_flagsIRQ_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=========
    ");              
    
        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->workts->workgslX680_ts_worker()对应,主要用来读取坐标信息。

    5、休眠唤醒

    关于休眠和唤醒的内容根据ic的特性设置。如休眠的时候需要关闭中断、配置进入休眠模式、拉低wake引脚。唤醒的时候唤醒ic,使能wake引脚、使能中断等。

     
     
  • 相关阅读:
    django 自定义用户身份验证
    登录验证算法
    Scrapy
    爬虫性能相关
    Beautifulsoup模块
    selenium模块
    Cookie&Session
    Django Admin 本质
    JavaScript自执行函数和jquery扩展方法
    JS作用域与词法分析
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/7615770.html
Copyright © 2011-2022 走看看