触摸屏驱动 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);
#endifstatic 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_DATAif (!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_timersnprintf(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寄存器的初始值,具体请对照datasheetts->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文件系统项用于使能/禁能tsif (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); //上报压力值=0input_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; //数据xy = ts->conversion_data[AD7879_SEQ_YPOS] & MAX_12BIT;  //数据yz1 = ts->conversion_data[AD7879_SEQ_Z1] & MAX_12BIT;   //数据z1z2 = ts->conversion_data[AD7879_SEQ_Z2] & MAX_12BIT;   ///数据z2if (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; //压力计算公式,请参考datasheetRt /= 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); //上报压力值Rtinput_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触摸屏的关键部分已经分析完毕,触摸屏的常用套路,你掌握了吗?

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部