触摸屏驱动分析(eeti源码为例)

module_init(egalax_i2c_ts_init)–>表示驱动加载时首先执行的函数是egalax_i2c_ts_init,下面看egalax_i2c_ts_init函数源码:

static int egalax_i2c_ts_init(void)
{
    int result;

    result = misc_register(&egalax_misc_dev);
    if(result) 
    {
        EGALAX_DBG(DBG_MODULE, " misc device register failed\n");
        goto fail;
    }

    p_char_dev = setup_chardev(); // allocate the character device
    if(!p_char_dev) 
    {
        result = -ENOMEM;
        goto fail;
    }

    dbgProcFile = proc_create(PROC_FS_NAME, S_IRUGO|S_IWUGO, NULL, &egalax_proc_fops);
    if (dbgProcFile == NULL) 
    {
        remove_proc_entry(PROC_FS_NAME, NULL);
        EGALAX_DBG(DBG_MODULE, " Could not initialize /proc/%s\n", PROC_FS_NAME);
    }

    EGALAX_DBG(DBG_MODULE, " Driver init done!\n");

    return i2c_add_driver(&egalax_i2c_driver);
    //通过i2c_add_driver这个函数向内核添加驱动,从而调
    用register_driver向内核注册驱动,如果驱动和设备匹配上以后,
    会调用peobe函数(前面有文章专门分析这个匹配过程,这篇文章主要
    分probe函数做的工作);

fail:   
    egalax_i2c_ts_exit();
    return result;
}

这里的egalax_i2c_driver为:
static struct i2c_driver egalax_i2c_driver = {
    .driver = {
        .name   = "egalax_i2c",
        .owner  = THIS_MODULE,
    #ifdef CONFIG_OF
        .of_match_table = egalax_i2c_dt_ids,
    #endif
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
        .pm     = &egalax_i2c_pm_ops,
    #endif
    },
    .class = I2C_CLASS_HWMON,
    .id_table   = egalax_i2c_idtable,
    .probe      = egalax_i2c_probe,
    .remove     = __devexit_p(egalax_i2c_remove),
    .suspend    = egalax_i2c_pm_suspend,
    .resume     = egalax_i2c_pm_resume,
    .detect         = ctp_detect,
    .address_list   = normal_i2c,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0)  
    #ifndef CONFIG_HAS_EARLYSUSPEND
    .suspend    = egalax_i2c_pm_suspend,
    .resume     = egalax_i2c_pm_resume,
    #endif
#endif  

};

看probe函数的源码:

static int __devinit egalax_i2c_probe(struct i2c_client *client, const struct i2c_device_id *idp)
{
    int ret;
#ifdef CONFIG_OF//这里定义的CONFIG_OF是指是否使用了设备树,
                  这里对使用和没使用两种,都做了兼容;
    struct device_node *devnode;
#endif //#ifdef CONFIG_OF   

    EGALAX_DBG(DBG_MODULE, " Start probe\n");

    p_egalax_i2c_dev = (struct _egalax_i2c *)kzalloc(sizeof(struct _egalax_i2c), GFP_KERNEL);
    //开辟结构体在和内核的内存空间
    if (!p_egalax_i2c_dev) 
    {
        EGALAX_DBG(DBG_MODULE, " Request memory failed\n");
        ret = -ENOMEM;
        goto fail1;
    }

#ifdef CONFIG_OF
    devnode = client->dev.of_node;
    if(devnode) //if use the device tree config
    {
        //通过of_get_named_gpio函数来获取设备树的节点信息(获取设备树中名称为int-gpios的gpio number)
        并将此gpio注册为中断,返回中断号
        p_egalax_i2c_dev->interrupt_gpio = of_get_named_gpio(devnode, "int-gpios", 0);
        client->irq = gpio_to_irq(p_egalax_i2c_dev->interrupt_gpio);
    }
#else
    //p_egalax_i2c_dev->interrupt_gpio = irq_to_gpio(client->irq);
    p_egalax_i2c_dev->interrupt_gpio = 356;//未使用设备树时,可直接使用该gpio对应的gpio number
    client->irq = gpio_to_irq(p_egalax_i2c_dev->interrupt_gpio);

#endif //#ifdef CONFIG_OF

    if( !gpio_is_valid(p_egalax_i2c_dev->interrupt_gpio) )
    //判断此gpio是否可用
    {
        ret = -ENODEV;
        goto fail1;
    }
    ret = gpio_request(p_egalax_i2c_dev->interrupt_gpio, "Touch IRQ");
    //如果可用,向内核说明该gpio此被占用
    if(ret<0 && ret!=-EBUSY)
    {
        EGALAX_DBG(DBG_MODULE, " gpio_request[%d] failed: %d\n", p_egalax_i2c_dev->interrupt_gpio, ret);
        goto fail1;
    }
    gpio_direction_input(p_egalax_i2c_dev->interrupt_gpio);
    //该gpio作为中断脚,将该gpio设为输入
    input_dev = allocate_Input_Dev();//这个函数比较重要,单独看源码讲解
    if(input_dev==NULL)
    {
        EGALAX_DBG(DBG_MODULE, " allocate_Input_Dev failed\n");
        ret = -EINVAL; 
        goto fail2;
    }
    EGALAX_DBG(DBG_MODULE, " Register input device done\n");

    p_egalax_i2c_dev->client = client;
    mutex_init(&p_egalax_i2c_dev->mutex_wq);

    p_egalax_i2c_dev->ktouch_wq = create_singlethread_workqueue("egalax_touch_wq");
    //建立一个工作队列
    INIT_WORK(&p_egalax_i2c_dev->work_irq, egalax_i2c_wq_irq);
    //将egalax_i2c_wq_irq函数用一个work_struct来描述(work_irq是一个work_struct结构体)
    i2c_set_clientdata(client, p_egalax_i2c_dev);

    if( gpio_get_value(p_egalax_i2c_dev->interrupt_gpio) )//驱动该gpio的引脚电平的高低
        p_egalax_i2c_dev->skip_packet = 0;
    else
        p_egalax_i2c_dev->skip_packet = 1;

    p_egalax_i2c_dev->work_state = MODE_WORKING;

    ret = request_irq(client->irq, egalax_i2c_interrupt, IRQF_TRIGGER_LOW, client->name, p_egalax_i2c_dev);//向内核注册中断,中断处理函数为egalax_i2c_interrupt
                    下面会分析中断处理函数源码
    if( ret ) 
    {
        EGALAX_DBG(DBG_MODULE, " Request irq(%d) failed\n", client->irq);
        goto fail3;
    }
    EGALAX_DBG(DBG_MODULE, " Request irq(%d) gpio(%d) with result:%d\n", client->irq, p_egalax_i2c_dev->interrupt_gpio, ret);

#ifdef CONFIG_HAS_EARLYSUSPEND
    egalax_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
    egalax_early_suspend.suspend = egalax_i2c_early_suspend;
    egalax_early_suspend.resume = egalax_i2c_early_resume;
    register_early_suspend(&egalax_early_suspend);
    EGALAX_DBG(DBG_MODULE, " Register early_suspend done\n");
#endif

    EGALAX_DBG(DBG_MODULE, " I2C probe done\n");
    return 0;

fail3:
    i2c_set_clientdata(client, NULL);
    destroy_workqueue(p_egalax_i2c_dev->ktouch_wq); 
    free_irq(client->irq, p_egalax_i2c_dev);
    input_unregister_device(input_dev);
    input_dev = NULL;
fail2:
    gpio_free(p_egalax_i2c_dev->interrupt_gpio);
fail1:
    kfree(p_egalax_i2c_dev);
    p_egalax_i2c_dev = NULL;

    EGALAX_DBG(DBG_MODULE, " I2C probe failed\n");
    return ret;
}

下面看函数:allocate_Input_Dev,此函数中设计的是input输入子系统中向内核注册input_dev的知识:

static struct input_dev * allocate_Input_Dev(void)
{
    int ret;
    //定义input_dev结构体,开辟内存空间
    struct input_dev *pInputDev=NULL;
    pInputDev = input_allocate_device();
    if(pInputDev == NULL)
    {
        EGALAX_DBG(DBG_MODULE, " Failed to allocate input device\n");
        return NULL;//-ENOMEM;
    }

    //设置input_dev的属性,包括名称,id_table,input事件等,
      详细内容可以搜索下input_dev属性的设置
    pInputDev->name = "eGalax_Touch_Screen"; 
    pInputDev->phys = "I2C";
    pInputDev->id.bustype = BUS_I2C;//id_table
    pInputDev->id.vendor = 0x0EEF;
    pInputDev->id.product = 0x0020;
    pInputDev->id.version = 0x0001;

    set_bit(EV_ABS, pInputDev->evbit);//绝对坐标
#ifndef CONFIG_HAS_EARLYSUSPEND //We use this config to distinguish Linux and Android
    set_bit(EV_KEY, pInputDev->evbit);//按键事件
    __set_bit(BTN_TOUCH, pInputDev->keybit);
    input_set_abs_params(pInputDev, ABS_X, 0, MAX_RESOLUTION, 0, 0);
                参数分别是(input_dev结构体,表示X轴,最小分辨率,最大分辨率,误差数据,平滑数据)
    //注意这里的最小分辨率和最大分辨率表示该input_dev所能支持的屏的分辨率
    input_set_abs_params(pInputDev, ABS_Y, 0, MAX_RESOLUTION, 0, 0);
#endif

//下面很好的说明了 不同内核版本下对多点触摸的支持的改动
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
    __set_bit(INPUT_PROP_DIRECT, pInputDev->propbit);
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)
        input_mt_init_slots(pInputDev, MAX_SUPPORT_POINT, 0);
    #else
        input_mt_init_slots(pInputDev, MAX_SUPPORT_POINT);
    #endif
    input_set_abs_params(pInputDev, ABS_MT_POSITION_X, 0, MAX_RESOLUTION, 0, 0);
    input_set_abs_params(pInputDev, ABS_MT_POSITION_Y, 0, MAX_RESOLUTION, 0, 0);
    input_set_abs_params(pInputDev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
#else
    input_set_abs_params(pInputDev, ABS_MT_POSITION_X, 0, MAX_RESOLUTION, 0, 0);
    input_set_abs_params(pInputDev, ABS_MT_POSITION_Y, 0, MAX_RESOLUTION, 0, 0);
    input_set_abs_params(pInputDev, ABS_MT_WIDTH_MAJOR, 0, MAX_RESOLUTION, 0, 0); //Size
    input_set_abs_params(pInputDev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); //Pressure
    input_set_abs_params(pInputDev, ABS_MT_TRACKING_ID, 0, MAX_SUPPORT_POINT, 0, 0);
#endif // #if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,0)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
    input_set_events_per_packet(pInputDev, MAX_EVENTS);
#endif

    ret = input_register_device(pInputDev);//向input_core注册input_dev,这里还有
                                            dev和handler的绑定问题,在下一篇中在分析
    if(ret) 
    {
        EGALAX_DBG(DBG_MODULE, " Unable to register input device.\n");
        input_free_device(pInputDev);
        pInputDev = NULL;
    }

    return pInputDev;
}

下面看probe函数中的中断注册部分:

ret = request_irq(client->irq, egalax_i2c_interrupt, IRQF_TRIGGER_LOW, client->name, p_egalax_i2c_dev);
这里的egalax_i2c_interrupt是中断处理函数,下面看egalax_i2c_interrupt的源码:
static irqreturn_t egalax_i2c_interrupt(int irq, void *dev_id)
{
    struct _egalax_i2c *egalax_i2c = (struct _egalax_i2c *)dev_id;

    EGALAX_DBG(DBG_INT, " INT with irq:%d\n", irq);

    disable_irq_nosync(irq);
    //这个函数只有下面的关键一句:将work_irq描述的函数加入到,probe函数中创建的工作队列ktouch_wq中
    //所以这里的中断处理函数实际为work_irq这个结构体描述的函数,再回头看probe函数中被work_irq函数描述的是哪一个

    **probe中关于工作队列的源码开始:**
    //创建一个动作队列
    p_egalax_i2c_dev->ktouch_wq = create_singlethread_workqueue("egalax_touch_wq");
    //将函数egalax_i2c_wq_irq用work_struct work_irq描述
    INIT_WORK(&p_egalax_i2c_dev->work_irq, egalax_i2c_wq_irq);
    **probe中关于工作队列的源码结束:**

    //将work_struct work_irq描述的函数加入到队列中(这部分设备工作队列的知识)
    queue_work(egalax_i2c->ktouch_wq, &egalax_i2c->work_irq);
    //既然实际的中断处理函数是egalax_i2c_wq_irq,看看egalax_i2c_wq_irq的源码
    return IRQ_HANDLED;
}

egalax_i2c_wq_irq的源码:

static void egalax_i2c_wq_irq(struct work_struct *work)
{
    struct _egalax_i2c *egalax_i2c = container_of(work, struct _egalax_i2c, work_irq);
    struct i2c_client *client = egalax_i2c->client;

    EGALAX_DBG(DBG_INT, " egalax_i2c_wq run\n");

    /*continue recv data*/
    while( !gpio_get_value(egalax_i2c->interrupt_gpio) ) 
    {
        egalax_i2c_measure(egalax_i2c);//当中断脚由高变低时,执行此函数,看面看这个函数的源码
        schedule();
    }

    if( egalax_i2c->skip_packet > 0 )
        egalax_i2c->skip_packet = 0;

    enable_irq(client->irq);

    EGALAX_DBG(DBG_INT, " egalax_i2c_wq leave\n");
}

egalax_i2c_measure源码:

static int egalax_i2c_measure(struct _egalax_i2c *egalax_i2c)
{
    int ret=0, frameLen=0, loop=3, i;

    EGALAX_DBG(DBG_INT, " egalax_i2c_measure\n");
//调用下面这个函数会调用i2c_tranfer读触摸ic里面的数据,存在input_report_buf数组里
    if( egalax_I2C_read(input_report_buf, MAX_I2C_LEN+2) < 0)
    {
        EGALAX_DBG(DBG_I2C, " I2C read input report fail!\n");
        return -1;
    }

    if( DbgLevel&DBG_I2C )//调试信息,打印input_report_buf数组信息
    {
        char dbgmsg[(MAX_I2C_LEN+2)*4];
        for(i=0; i<MAX_I2C_LEN+2; i++)
            sprintf(dbgmsg+i*4, "[%02X]", input_report_buf[i]);
        EGALAX_DBG(DBG_I2C, " Buf=%s\n", dbgmsg);
    }
    //从这里看input_report_buf[0]和input_report_buf[1]存放的是长度,

    frameLen = input_report_buf[0] + (input_report_buf[1]<<8);
    EGALAX_DBG(DBG_I2C, " I2C read data with Len=%d\n", frameLen);

    if(frameLen==0)
    {
        EGALAX_DBG(DBG_MODULE, " Device reset\n");
        return -1;
    }

    switch(input_report_buf[2])
    {
    //把input_report_buf[2]作为case的判断依据,input_report_buf[2]应该数据类型
        case REPORTID_MTOUCH://触摸数据
            if( !egalax_i2c->skip_packet && egalax_i2c->work_state==MODE_WORKING )
                ProcessReport(input_report_buf+2, egalax_i2c);//看ProcessReport源码是怎么
                                                                对读来的触摸数据进行处理的
            ret = 0;
            break;
        case REPORTID_VENDOR://i2c芯片的版本信息
            atomic_set(&wait_command_ack, 1);
            EGALAX_DBG(DBG_I2C, " I2C get vendor command packet\n");

            if( p_char_dev->OpenCnts>0 ) // If someone reading now! put the data into the buffer!
            {
                loop=3;
                do {
                #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
                    ret = wait_event_timeout(p_char_dev->fifo_inq, (FIFO_SIZE-kfifo_len(p_char_dev->pDataKFiFo))>=MAX_I2C_LEN, HZ);
                #else
                    ret = wait_event_timeout(p_char_dev->fifo_inq, kfifo_avail(&p_char_dev->DataKFiFo)>=MAX_I2C_LEN, HZ);
                #endif
                }while(ret<=0 && --loop);

                if(ret>0) // fifo size is ready
                {
                #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
                    ret = kfifo_put(p_char_dev->pDataKFiFo, input_report_buf+2, MAX_I2C_LEN);
                #else
                    ret = kfifo_in_locked(&p_char_dev->DataKFiFo, input_report_buf+2, MAX_I2C_LEN, &p_char_dev->FiFoLock);
                #endif

                    wake_up_interruptible( &p_char_dev->fifo_inq );
                }
                else
                    EGALAX_DBG(DBG_CDEV, " [Warning] Can't write data because fifo size is overflow.\n");
            }

            break;
        default:
            EGALAX_DBG(DBG_I2C, " I2C read error data with hedaer=%d\n", input_report_buf[2]);
            ret = -1;
            break;
    }

    return ret;
}

ProcessReport源码:
对触摸数据的上报就是这个函数处理的

static void ProcessReport(unsigned char *buf, struct _egalax_i2c *p_egalax_i2c)
{
    unsigned char i, index=0, cnt_down=0, cnt_up=0, shift=0;
    unsigned char status=0;
    unsigned short contactID=0, x=0, y=0;

    if(TotalPtsCnt<=0)
    {
        //这里的buf=input_report_buf+2(调用ProcessReport函数出入的形参为input_report_buf+2)
        //从下面的判断条件来看,input_report_buf[4]存放的触摸点数
        if(buf[1]==0 || buf[1]>MAX_SUPPORT_POINT)
        {
            EGALAX_DBG(DBG_POINT, " NumsofContacts mismatch, skip packet\n");
            return;
        }

        TotalPtsCnt = buf[1];//触摸点数
        RecvPtsCnt = 0;
    }
    else if(buf[1]>0)
    {
        TotalPtsCnt = RecvPtsCnt = 0;
        EGALAX_DBG(DBG_POINT, " NumsofContacts mismatch, skip packet\n");
        return;
    }

    while(index<MAX_POINT_PER_PACKET)
    {
        shift = index * POINT_STRUCT_SIZE + 2;
        status = buf[shift] & 0x01;//对buf中的数据进行处理
                                    得到status 、contactID 以及x、y
        contactID = buf[shift+1];
        x = ((buf[shift+3]<<8) + buf[shift+2]);
        y = ((buf[shift+5]<<8) + buf[shift+4]);

        if( contactID>=MAX_SUPPORT_POINT )
        {
            TotalPtsCnt = RecvPtsCnt = 0;
            EGALAX_DBG(DBG_POINT, " Get error ContactID.\n");
            return;
        }

        EGALAX_DBG(DBG_POINT, " Get Point[%d] Update: Status=%d X=%d Y=%d\n", contactID, status, x, y);

    //这里是对触摸屏上报数据的x、y轴进行反向
    #ifdef _SWITCH_XY
        short tmp = x;
        x = y;
        y = tmp;
    #endif
    #ifdef _CONVERT_X
        x = MAX_RESOLUTION-x;
    #endif

    #ifdef _CONVERT_Y
        y = MAX_RESOLUTION-y;
    #endif
        //将获得的每一点数据(contactID,status以及x、y存在结构体数组pContactBuf中)
        pContactBuf[RecvPtsCnt].ID = contactID;
        pContactBuf[RecvPtsCnt].Status = status;
        pContactBuf[RecvPtsCnt].X = x;
        pContactBuf[RecvPtsCnt].Y = y;

        RecvPtsCnt++;
        index++;

        // Recv all points, send input report
        if(RecvPtsCnt==TotalPtsCnt)//当读取了所有触摸芯片中的坐标点的时候,开始上报
        {
            for(i=0; i<RecvPtsCnt; i++)//一个点一个点循环上报
            {
            //下面也给出了不同的内核下上报点的不同,这里对各种内核版本都做了兼容
            #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0)
                input_mt_slot(input_dev, pContactBuf[i].ID);
                input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pContactBuf[i].Status);
                if(pContactBuf[i].Status)
                {
                    input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, pContactBuf[i].Status);
                    input_report_abs(input_dev, ABS_MT_POSITION_X, pContactBuf[i].X);
                    input_report_abs(input_dev, ABS_MT_POSITION_Y, pContactBuf[i].Y);
                }
            #else
                input_report_abs(input_dev, ABS_MT_TRACKING_ID, pContactBuf[i].ID);
                input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, pContactBuf[i].Status);
                input_report_abs(input_dev, ABS_MT_POSITION_X, pContactBuf[i].X);
                input_report_abs(input_dev, ABS_MT_POSITION_Y, pContactBuf[i].Y);
                input_report_abs(input_dev, ABS_MT_WIDTH_MAJOR, 0);
                input_mt_sync(input_dev);  //进行同步,表示一个点的数据以及上报完了
            #endif

                if(pContactBuf[i].Status)
                    cnt_down++;
                else
                    cnt_up++;
            }
        #ifndef CONFIG_HAS_EARLYSUSPEND //We use this config to distinguish Linux and Android
            #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,38)
            input_mt_report_pointer_emulation(input_dev, true);
            #endif
        #endif
            input_sync(input_dev);
            EGALAX_DBG(DBG_POINT, " Input sync point data done! (Down:%d Up:%d)\n", cnt_down, cnt_up);

            TotalPtsCnt = RecvPtsCnt = 0;
            return;
        }
    }
}

到这里这一个触摸屏驱动大概的执行流程就讲解完了,下一篇将会讲解input_dev和handler的匹配过程。
也就是input_register_device函数执行是匹配过程

猜你喜欢

转载自blog.csdn.net/chihunqi5879/article/details/80792777