Mini2440 触摸屏驱动分析

Mini2440 触摸屏驱动分析

前提知识:

  • 内核定时器

    内核定时器用于控制某个函数(定时器处理函数)在未来某个时间特定执行。内核定时器注册的函数只会执行一次
      内核定时器被组织成双向链表,使用struct_timer_list结构来描述

    struct timer_list
    {
       struct list_head entry;/*链表*/
       unsigned long expires;/*超时的jiffies值*/
       void (*function)(unsigned long);/*超时处理函数*/
       unsigned long data;/*超时函数参数*/
       struct tvec_base *base;/*内核使用*/
    }
    
     
    初始化定时器队列结构:
    void init_timer(struct timer_list *timer);
    
     
    添加定时器:
    void add_timer(struct timer_list *timer);
    
     
    删除定时器:
    int del_timer(struct timer_list *timer);
    在定时器超时前将它删除。(超时后会自动删除)
    
     
    修改定时器:
    int mod_timer(struct timer_list *timer,unsigned long expires);
    
     
    使用示例:
    struct timer_list timer;
    void timer_fuction(int para);

    init_timer(&timer);
    timer.data = 5;
    timer.expires = jiffies +(20*HZ);
    timer.function = timer_function;
    add_timer(&timer);

    还可以使用TIMER_INITIALIZER(_function, _expires, _data)宏迅速创建timer

    static struct timer_list touch_timer = TIMER_INITIALIZER(timer_function, 0, 0);
    
     
     
  • Input System (输入子系统)

    在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
    在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
    将Input设备注册到input子系统中;
    在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
     
    设置设备所支持的事件类型和取值范围:

    dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
    /* 设置按键类型为触摸 */
    dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
    /* 设置abs事件参数取值范围 */
    input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
    
     
    Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
    EV_SYN     0x00     同步事件
    EV_KEY     0x01     按键事件
    EV_REL     0x02     相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
    EV_ABS     0x03     绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
    EV_MSC     0x04     其它
    EV_LED     0x11     LED
    EV_SND     0x12     声音
    EV_REP     0x14     Repeat
    EV_FF      0x15     力反馈
    
     
    用于提交较常用的事件类型给输入子系统的函数有:
    void input_report_key(struct input_dev *dev, unsigned int code, int value); //提交按键事件的函数
    void input_report_rel(struct input_dev *dev, unsigned int code, int value); //提交相对坐标事件的函数
    void input_report_abs(struct input_dev *dev, unsigned int code, int value); //提交绝对坐标事件的函数
    
     
    注意,在提交输入设备的事件后必须用下列方法使事件同步,让它告知input系统,设备驱动已经发出了一个完整的报告,对于触摸屏,如果不报告同步,可能导致xy坐标分离:
    void input_sync(struct input_dev *dev)
    
     
      触摸屏驱动工作流程图: Mini2440 触摸屏驱动分析_第1张图片

    需要注意的是ADC工作模式的切换:先使用waiting for interrupt mode,检测触摸中断。然后进入Auto X/Y Position conversion mode,产生ADC中断后获得坐标值。 驱动源代码:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 

    #include #include

    /* For ts.dev.id.version */
    #define S3C2410TSVERSION 0x0101

    #define WAIT4INT(x) (((x)<<8) | 
    S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | 
    S3C2410_ADCTSC_XY_PST(3))

    #define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | 
    S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)) //S3C2410_ADCTSC_XY_PST(0) 设置无操作模式

    static char *s3c2410ts_name = “s3c2410 TouchScreen”;

    static struct input_dev *dev;
    static long xp;
    static long yp;
    static int count;

    extern struct semaphore ADC_LOCK;
    static int OwnADC = 0;

    static void __iomem *base_addr;

    static inline void s3c2410_ts_connect(void)
    { s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON);
    s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON);
    s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON);
    s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON);
    }

    static void touch_timer_fire(unsigned long data)
    { unsigned long data0;
    unsigned long data1;
    int updown;

    data0 = ioread32(base_addr + S3C2410_ADCDAT0);
    data1 = ioread32(base_addr + S3C2410_ADCDAT1);

    updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN))
    && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

    if (updown) { //如果触摸屏按下
    if (count != 0) {
    long tmp;

    tmp = xp; //我猜测MINI2440 的触摸屏以横屏为正方向
    xp = yp; //所以这里需要交换xy
    yp = tmp;

    xp >>= 2; //取算术平均值
    yp >>= 2;

    /* 调试信息 */
    #ifdef CONFIG_TOUCHSCREEN_MY2440_DEBUG
    struct timeval tv;
    do_gettimeofday(&tv);
    printk(KERN_DEBUG “T: %06d, X: %03ld, Y: %03ldn”,
    (int) tv.tv_usec, xp, yp);
    #endif

    /* 上报坐标值 */
    input_report_abs(dev, ABS_X, xp);
    input_report_abs(dev, ABS_Y, yp);

    /* 上报触摸状态为按下,压力为1 */
    input_report_key(dev, BTN_TOUCH, 1);
    input_report_abs(dev, ABS_PRESSURE, 1);
    input_sync(dev);
    }

    xp = 0;
    yp = 0;
    count = 0;

    iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
    base_addr + S3C2410_ADCTSC); //关闭XP上拉,开启xy自动转换
    iowrite32(ioread32(base_addr + S3C2410_ADCCON) |
    S3C2410_ADCCON_ENABLE_START,
    base_addr + S3C2410_ADCCON); //启动ADC
    } else {
    count = 0;

    /* 上报触摸状态为抬起,压力为0 */
    input_report_key(dev, BTN_TOUCH, 0);
    input_report_abs(dev, ABS_PRESSURE, 0);
    input_sync(dev);

    iowrite32(WAIT4INT(0), base_addr + S3C2410_ADCTSC);
    if (OwnADC) {
    OwnADC = 0;
    up(&ADC_LOCK);
    }
    }
    }

    static struct timer_list touch_timer =
    TIMER_INITIALIZER(touch_timer_fire, 0, 0);

    /* 触摸中断处理函数 */
    static irqreturn_t stylus_updown(int irq, void *dev_id)
    { unsigned long data0;
    unsigned long data1;
    int updown;

    if (down_trylock(&ADC_LOCK) == 0) { //获取信号量
    OwnADC = 1; //标记获得ADC
    data0 = ioread32(base_addr + S3C2410_ADCDAT0);
    data1 = ioread32(base_addr + S3C2410_ADCDAT1);

    updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); //通过DAT寄存器15位,判断是否真的按下了,按下时为1

    if (updown) { //如果按下
    touch_timer_fire(0);
    } else {
    OwnADC = 0;
    up(&ADC_LOCK); //释放信号量
    }
    }

    return IRQ_HANDLED;
    }

    /* ADC中断处理函数 */
    static irqreturn_t stylus_action(int irq, void *dev_id)
    { unsigned long data0;
    unsigned long data1;

    if (OwnADC) {
    data0 = ioread32(base_addr + S3C2410_ADCDAT0);
    data1 = ioread32(base_addr + S3C2410_ADCDAT1);

    xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; //data0 & 0x3ff 取出x坐标值
    yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
    count++;

    if (count < (1 << 2)) { //count < 4 采样次数少于4次,再次启动ADC采样
    iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
    base_addr + S3C2410_ADCTSC);
    iowrite32(ioread32(base_addr + S3C2410_ADCCON) |
    S3C2410_ADCCON_ENABLE_START,
    base_addr + S3C2410_ADCCON);
    } else {
    mod_timer(&touch_timer, jiffies + 1);
    iowrite32(WAIT4INT(1), base_addr + S3C2410_ADCTSC); //进入等待中断模式,设置为等待Pen up
    }
    }

    return IRQ_HANDLED;
    }

    static struct clk *adc_clock;

    static int __init s3c2410ts_init(void)
    { struct input_dev *input_dev;

    adc_clock = clk_get(NULL, “adc”);
    if (!adc_clock) {
    printk(KERN_ERR “failed to get adc clock sourcen”);
    return -ENOENT;
    }
    clk_enable(adc_clock);

    base_addr = ioremap(S3C2410_PA_ADC, 0x20);
    if (base_addr == NULL) {
    printk(KERN_ERR “Failed to remap register blockn”);
    return -ENOMEM;
    }

    /* Configure GPIOs */
    s3c2410_ts_connect();

    iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),
    base_addr + S3C2410_ADCCON);
    iowrite32(0xffff, base_addr + S3C2410_ADCDLY); //设置等待中断模式下长生INT_TC等待时间间隔
    iowrite32(WAIT4INT(0), base_addr + S3C2410_ADCTSC); //设置等待中断模式,Pen Down 中断

    /* Initialise input stuff */
    input_dev = input_allocate_device();

    if (!input_dev) {
    printk(KERN_ERR
    “Unable to allocate the input device !!n”);
    return -ENOMEM;
    }

    dev = input_dev;
    /* 设置input设备支持事件类型:同步事件,按键事件,绝对坐标变化 */
    dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
    /* 设置按键类型为触摸 */
    dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
    /* 设置abs事件参数取值范围 */
    input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);

    /* 设置dev信息 */
    dev->name = s3c2410ts_name;
    dev->id.bustype = BUS_RS232;
    dev->id.vendor = 0xDEAD;
    dev->id.product = 0xBEEF;
    dev->id.version = S3C2410TSVERSION;

    /* 注册irq */
    if (request_irq
    (IRQ_ADC, stylus_action, IRQF_SHARED | IRQF_SAMPLE_RANDOM,
    “s3c2410_action”, dev)) {
    printk(KERN_ERR
    “s3c2410_ts.c: Could not allocate ts IRQ_ADC !n”);
    iounmap(base_addr);
    return -EIO;
    }
    if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,
    “s3c2410_action”, dev)) {
    printk(KERN_ERR
    “s3c2410_ts.c: Could not allocate ts IRQ_TC !n”);
    iounmap(base_addr);
    return -EIO;
    }

    printk(KERN_INFO “%s successfully loadedn”, s3c2410ts_name);

    /* 注册input设备 */
    input_register_device(dev);

    return 0;
    }

    static void __exit s3c2410ts_exit(void)
    { disable_irq(IRQ_ADC);
    disable_irq(IRQ_TC);
    free_irq(IRQ_TC, dev);
    free_irq(IRQ_ADC, dev);

    if (adc_clock) {
    clk_disable(adc_clock);
    clk_put(adc_clock);
    adc_clock = NULL;
    }

    input_unregister_device(dev);
    iounmap(base_addr);
    }

    module_init(s3c2410ts_init);
    module_exit(s3c2410ts_exit);

  • 你可能感兴趣的:(c,timer,struct,list,report,input)