触摸屏驱动 ad7879 代码简析

触摸屏驱动十分常见,这篇博文就以ad7879为例,简单分析一下触摸屏驱动的常用套路,本文注重重代码逻辑,轻寄存器hack,寄存器hack,请参考datasheet。

 

代码解析

源码路径drivers/input/touchscreen/ad7879-i2c.c.

首先简单看下驱动注册部分

static const struct i2c_device_id ad7879_id[] = {
    { "ad7879", 0 },
    { "ad7889", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, ad7879_id);

#ifdef CONFIG_OF
static const struct of_device_id ad7879_i2c_dt_ids[] = {
    { .compatible = "adi,ad7879-1", },
    { }
};
MODULE_DEVICE_TABLE(of, ad7879_i2c_dt_ids);
#endif

static struct i2c_driver ad7879_i2c_driver = {
    .driver = {
        .name   = "ad7879",
        .pm = &ad7879_pm_ops,
        .of_match_table = of_match_ptr(ad7879_i2c_dt_ids),
    },
    .probe      = ad7879_i2c_probe,
    .id_table   = ad7879_id,
};

module_i2c_driver(ad7879_i2c_driver); 

可以看出这是一个基于i2c的驱动,设备树的兼容关键字为adi,ad7879-1,并且此驱动兼容两块芯片,ad7879和ad7889。

接着分析驱动的probe函数

static int ad7879_i2c_probe(struct i2c_client *client,
                      const struct i2c_device_id *id)
{
    struct regmap *regmap;

    //判断i2c适配器能力,是否支持I2C_FUNC_SMBUS_WORD_DATA
    if (!i2c_check_functionality(client->adapter,
                     I2C_FUNC_SMBUS_WORD_DATA)) {
        dev_err(&client->dev, "SMBUS Word Data not Supported\n");
        return -EIO;
    }
    
    //regmap 初始化,用于i2c通信
    regmap = devm_regmap_init_i2c(client, &ad7879_i2c_regmap_config);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    return ad7879_probe(&client->dev, regmap, client->irq,
                BUS_I2C, AD7879_DEVID);
}

首先判断一下i2c适配器的能力,看其是否支持I2C_FUNC_SMBUS_WORD_DATA(以字(两个byte)为单位进行读写)。

regmap初始化,用于调用封装好的i2c底层通信驱动。

最后到了ad7879_probe函数,很显然,从名字猜测,从面向对象的角度来说,ad7879_i2c_probe是对ad7879_probe的一种继承。由此可以推测ad7879_probe是更加通用的probe代码,让我们来看看。

int ad7879_probe(struct device *dev, struct regmap *regmap,
         int irq, u16 bustype, u8 devid)
{
    struct ad7879 *ts;
    struct input_dev *input_dev;
    int err;
    u16 revid;

    if (irq <= 0) {//判断中断号是否为超出范围
        dev_err(dev, "No IRQ specified\n");
        return -EINVAL;
    }

    ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);//为ts结构体指针分配内存
    if (!ts)
        return -ENOMEM;

    err = ad7879_parse_dt(dev, ts);//解析设备数相关
    if (err)
        return err;

    input_dev = devm_input_allocate_device(dev);//为input_dev结构体分配指针
    if (!input_dev) {
        dev_err(dev, "Failed to allocate input device\n");
        return -ENOMEM;
    }

    ts->dev = dev;         //ts结构体赋值
    ts->input = input_dev;
    ts->irq = irq;
    ts->regmap = regmap;

    timer_setup(&ts->timer, ad7879_timer, 0);//定时器timer初始化,回调函数为ad7879_timer
    snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev));

    input_dev->name = "AD7879 Touchscreen";//input_dev结构体初始化
    input_dev->phys = ts->phys;
    input_dev->dev.parent = dev;
    input_dev->id.bustype = bustype;

    input_dev->open = ad7879_open; //设置open回调
    input_dev->close = ad7879_close; //设置close回调

    input_set_drvdata(input_dev, ts); //将input_dev与ts关联

    input_set_capability(input_dev, EV_KEY, BTN_TOUCH); //设置具有按钮功能

    input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); //设置X值范围

    input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); //设置Y值范围
    input_set_capability(input_dev, EV_ABS, ABS_PRESSURE);  //设置具有压力检测功能
    touchscreen_parse_properties(input_dev, false, NULL);   //解析触摸屏
    if (!input_abs_get_max(input_dev, ABS_PRESSURE)) { 
        dev_err(dev, "Touchscreen pressure is not specified\n");
        return -EINVAL;
    }

    err = ad7879_write(ts, AD7879_REG_CTRL2, AD7879_RESET);//reset触摸屏
    if (err < 0) {
        dev_err(dev, "Failed to write %s\n", input_dev->name);
        return err;
    }

    revid = ad7879_read(ts, AD7879_REG_REVID); //获取触摸屏id信息
    input_dev->id.product = (revid & 0xff);
    input_dev->id.version = revid >> 8;
    if (input_dev->id.product != devid) {//如果id不符合,则报错退出
        dev_err(dev, "Failed to probe %s (%x vs %x)\n",
            input_dev->name, devid, revid);
        return -ENODEV;
    }
    //设置三个cmd寄存器的初始值,具体请对照datasheet
    ts->cmd_crtl3 = AD7879_YPLUS_BIT |
            AD7879_XPLUS_BIT |
            AD7879_Z2_BIT |
            AD7879_Z1_BIT |
            AD7879_TEMPMASK_BIT |
            AD7879_AUXVBATMASK_BIT |
            AD7879_GPIOALERTMASK_BIT;

    ts->cmd_crtl2 = AD7879_PM(AD7879_PM_DYN) | AD7879_DFR |
            AD7879_AVG(ts->averaging) |
            AD7879_MFS(ts->median) | 
            AD7879_FCD(ts->first_conversion_delay);

    ts->cmd_crtl1 = AD7879_MODE_INT | AD7879_MODE_SEQ1 |
            AD7879_ACQ(ts->acquisition_time) |
            AD7879_TMR(ts->pen_down_acc_interval);

    //申请触摸屏中断的中断号,回调为ad7879_irq。
    err = devm_request_threaded_irq(dev, ts->irq, NULL, ad7879_irq,
                    IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                    dev_name(dev), ts);
    if (err) {
        dev_err(dev, "Failed to request IRQ: %d\n", err);
        return err;
    }

    __ad7879_disable(ts);//暂时禁止中断

    err = devm_device_add_group(dev, &ad7879_attr_group);//添加sys文件系统项用于使能/禁能ts
    if (err)
        return err;

    err = ad7879_gpio_add(ts);//有一个引脚可以作为gpio使用,将其注册进gpio子系统
    if (err)
        return err;

    err = input_register_device(input_dev);//向input子系统注册。
    if (err)
        return err;

    dev_set_drvdata(dev, ts);

    return 0;
}

简单说一下就是初始化了两个重要的结构体,ts用于存放驱动数据,input_dev用于向input子系统注册,以上传坐标值。

然后irq中断初始化,添加中断函数ad8789_irq。

 

irq中断函数也是整个触摸屏驱动的核心部分,当触摸屏的被按下时,就会触发此中断,进而调用此中断函数,来完成数据的处理,来我们来看看。

static irqreturn_t ad7879_irq(int irq, void *handle)
{
    struct ad7879 *ts = handle;
    int error;
    
    error = regmap_bulk_read(ts->regmap, AD7879_REG_XPLUS,//i2c读取检测到的坐标值
                 ts->conversion_data, AD7879_NR_SENSE);
    if (error)
        dev_err_ratelimited(ts->dev, "failed to read %#02x: %d\n",
                    AD7879_REG_XPLUS, error);
    else if (!ad7879_report(ts))//向input子系统上报坐标值
        mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);//重新设置定时器,在50ms后,触发定时器回调
    
    return IRQ_HANDLED;
}

所做的工作很简单,就是通过i2c读取触摸屏的坐标值。

然后通过ad7879_report函数将读取到的坐标上报给input子系统。

整个驱动最精妙的地方就在mod_timer这个函数的应用,也是触摸屏驱动的惯用手段,用于检查是否手是否已经离开了触摸屏。mod_timer的功能是重新设置定时器的回调函数的调用时间,将其设置为50ms之后。

这里需要注意的是,如果50ms还未到达,而此函数再次被调用的时候,定时器会再延后50ms。

想想什么时候,此函数会再次调用呢?那就是ad7879_irq中断再次被触发的时候,那么只要保证irq中断产生的时间间隔在50ms以内,那么定时器的回调函数就永远不会调用。

想想看什么时候,irq产生的中断会在50ms以内呢,答案很简单,那就是手一直在触摸屏上,没有离开。所以我们可以猜测定时器的回调函数会在手离开触摸屏的时候调用,来看看:

static void ad7879_ts_event_release(struct ad7879 *ts)
{
    struct input_dev *input_dev = ts->input;

    input_report_abs(input_dev, ABS_PRESSURE, 0); //上报压力值=0
    input_report_key(input_dev, BTN_TOUCH, 0); //上报按键已经松开
    input_sync(input_dev); //同步至input子系统
}

static void ad7879_timer(struct timer_list *t)                                                                                               
{
    struct ad7879 *ts = from_timer(ts, t, timer);

    ad7879_ts_event_release(ts);
}

果不其然,定时器回调函数ad7879_timer只是调用了ad7879_ts_event_release,从名字也可以看出,我们猜对了。完成在手离开触摸屏之后,上报input子系统,”你好,input子系统,我们现在的压力为0,而且按键已经松开了哈。“

再回过头看看ad7879_report干了什么:

static int ad7879_report(struct ad7879 *ts)
{
    struct input_dev *input_dev = ts->input;
    unsigned Rt;
    u16 x, y, z1, z2;

    x = ts->conversion_data[AD7879_SEQ_XPOS] & MAX_12BIT; //数据x
    y = ts->conversion_data[AD7879_SEQ_YPOS] & MAX_12BIT;  //数据y
    z1 = ts->conversion_data[AD7879_SEQ_Z1] & MAX_12BIT;   //数据z1
    z2 = ts->conversion_data[AD7879_SEQ_Z2] & MAX_12BIT;   ///数据z2

    if (ts->swap_xy) //如果此flag存在
        swap(x, y);  //交换x,y

    /*
     * The samples processed here are already preprocessed by the AD7879.
     * The preprocessing function consists of a median and an averaging
     * filter.  The combination of these two techniques provides a robust
     * solution, discarding the spurious noise in the signal and keeping                                                                                                                                                                                                                                                                                                        
     * only the data of interest.  The size of both filters is
     * programmable. (dev.platform_data, see linux/platform_data/ad7879.h)
     * Other user-programmable conversion controls include variable
     * acquisition time, and first conversion delay. Up to 16 averages can
     * be taken per conversion.
     */

    if (likely(x && z1)) { //如果x和z1都不为0(数据有效)
        /* compute touch pressure resistance using equation #1 */
        Rt = (z2 - z1) * x * ts->x_plate_ohms; //压力计算公式,请参考datasheet
        Rt /= z1;
        Rt = (Rt + 2047) >> 12;

        /*
         * Sample found inconsistent, pressure is beyond
         * the maximum. Don't report it to user space.
         */
        if (Rt > input_abs_get_max(input_dev, ABS_PRESSURE))//rt大于我们限制的最大压力值
            return -EINVAL;//不上报

        /*
         * Note that we delay reporting events by one sample.
         * This is done to avoid reporting last sample of the
         * touch sequence, which may be incomplete if finger
         * leaves the surface before last reading is taken.
         */
        if (timer_pending(&ts->timer)) {//如果timer在pending状态
            /* Touch continues */        //做上报工作
            input_report_key(input_dev, BTN_TOUCH, 1); //上报按键按下
            input_report_abs(input_dev, ABS_X, ts->x); //上报x值
            input_report_abs(input_dev, ABS_Y, ts->y); //上报y值
            input_report_abs(input_dev, ABS_PRESSURE, ts->Rt); //上报压力值Rt
            input_sync(input_dev); //以上数据同步至input子系统
        }

        ts->x = x;
        ts->y = y;
        ts->Rt = Rt;

        return 0;
    }

    return -EINVAL;
}

ad7879_report主要做了触摸屏坐标值的上报工作,当然是有效的坐标值。有意思的地方是timer_pending的使用,timer_pending用于判断当前的定时器是否在pending状态(即定时器已经加入定时器队列,开始计时,但是时间还没有到的状态),联系之前内容,我们可以知道,timer处于pending状态,就是说,还在定时器计时中(延后中),也就是我们的手还没有离开触摸屏的时候,我们才会上报数据,如果timer不在pending状态,说明手已经离开了触摸屏了,很显然这时的数据应该作废。

 

至此,ad7879触摸屏的关键部分已经分析完毕,触摸屏的常用套路,你掌握了吗?

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值