Rockchip平台TP驱动详解

本文描述在RK3126平台上添加一个新的TP驱动(gslx680驱动)以及详细的驱动代码信息。如有不足之处,敬请指出。

1、修改dts,添加新的i2c设备。

arch/arm/boot/dts/rk312x-sdk-v2.2.dtsi中添加i2c设备的相关信息:

[email protected] {
        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>;
    };
&i2c2 {
    status = "okay";
    /*
    [email protected]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>;
    };*/

    [email protected]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>;
    };
    /* ... */

表示i2c2总线上下挂在了多个i2c设备。

其中[email protected]是表示此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

(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;
}

注册名字为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)、自定义的数据结构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
};

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);

可以通过设备节点np获取到它irq-gpio这一个属性的值存放在ts->irq_pin中,以及将其flag刚到变量irq_flags中。因此我们可以知道:

ts->irq_pin = GPIO_A2
irq_flag = IRQ_TYPE_LEVEL_HIGH

同理,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;
}

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);

注:如果申请资源出错的话一定要记得释放资源以及前面的资源。比如说这里为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;
}

一旦有中断到来,立马调用gsl_ts_irq(),在这个中断服务子程序中先判断ts->work是否挂起,如果没有挂起就启动工作队列ts->wq的工作ts->workts->workgslX680_ts_worker()对应,主要用来读取坐标信息。

5、休眠唤醒

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

时间: 2024-11-11 03:33:18

Rockchip平台TP驱动详解的相关文章

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设备的相关信息: [email protected] { compatible = "gslX680"; r

MTK平台tp驱动详解

MTK平台tp驱动详解 本博文将讲解基于goodix9157触控芯片的tp驱动程序.这里有对应的驱动程序. 初始化 static int __init tpd_driver_init(void) { GTP_INFO("MediaTek gt91xx touch panel driver init\n"); #if defined(TPD_I2C_NUMBER) i2c_register_board_info(TPD_I2C_NUMBER, &i2c_tpd, 1); #els

Linux的i2c驱动详解

目录(?)[-] 简介 架构 设备注册 I2C关键数据结构和详细注册流程 关键数据结构 详细注册流程 使用I2C子系统资源函数操作I2C设备 Gpio模拟i2c总线的通用传输算法 总结 理清i2c中的个结构体关系 i2c驱动的编写建议 1 简介 I2C 总线仅仅使用 SCL . SDA 两根信号线就实现了设备之间的数据交互,极大地简化对硬件资源和 PCB 板布线空间的占用.因此, I2C 总线被非常广泛地应用在 EEPROM .实时钟.小型 LCD 等设备与 CPU 的接口中. Linux I2

Linux USB 鼠标输入驱动详解

平台:mini2440 内核:linux 2.6.32.2 USB设备插入时,内核会读取设备信息,接着就把id_table里的信息与读取到的信息做比较,看是否匹配,如果匹配,就调用probe函数.USB设备拔出时会调用disconnect函数.URB在USB设备驱动程序中用来描述与USB设备通信时用到的基本载体和核心数据结构. URB(usb request block)处理流程: ①USB设备驱动程序创建并初始化一个访问特定USB设备特定端点的urb并提交给USB core. ②USB cor

16.Linux-LCD驱动(详解)

在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 4) 使能LCD,并注册fb_info: register_framebuffer() 本节需要用到的函数: void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

使用VS2010编译MongoDB C++驱动详解

最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.hbase等NoSQL中转了一圈,最后还是选择了MongoDB,应了那句话:没有最好的,只有最合适的. MongoDB由于使用了C++的新特性,官方建议使用VS2013来编译,最低要求VS2010. MongoDB C++驱动编译过程较为复杂,官方也没有提供编译好的驱动包,网上的资料编译版本都比较老了

微信公众平台认证步骤详解及服务号和订阅号区别

微信公众号对象: 企业.媒体.以及公益.社区等组织.机构. 微信公众号作用: 通过微信公众渠道将品牌推广给上亿或者更多的微信用户. 1.极快的传播速度.极少的宣传成本: 2.提高品牌知名度,打造更具影响力的品牌形象. 3.危机公关(越来越需要) 微信公众号怎样认证: 一般企业或个人通过认证除了看努力还得看天意,当然也可以联系一些认证机构去办理相对要简单些.虽然微信公众号的口号是"再小的个体,也有自己品牌",但个体的力量毕竟不如团队的力量-- 认证条件: 1.微信公众号认证需要在业内有一

基于DKHadoop的智慧政务服务平台开发案例详解

基于DKHadoop的智慧政务服务平台开发案例详解大数据技术的应用与发展正在让我们的生活经历一场深刻的"变革",而且这种变革几乎让所有人都感觉非常舒服,自然而然的就完成了这样的一个变化.最根本的原因其实是大数据技术的应用真正帮助我们解决了问题.我想也正是基于大数据技术的超强实用性吧,它才会被上升到国家战略层面的高度得以出现在政府工作报告中.大数据技术的应用于,对于建设智慧政务平台的可谓功不可没.智慧政务云平台的建设技术以及方案,可以说是比较成熟了,当然前提是必须与大的.开发团队强的大数

13.Linux键盘按键驱动 (详解)

版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一节分析输入子系统内的intput_handler软件处理部分后,接下来我们开始写input_dev驱动 本节目标: 实现键盘驱动,让开发板的4个按键代表键盘中的L.S.空格键.回车键 1.先来介绍以下几个结构体使用和函数,下面代码中会用到 1)input_dev驱动设备结构体中常用成员如下: struct input_dev { void *private; const char *name; //设备名字 const char *ph