AD7879-I2C,touchscreen驱动分析

这段时间看嘞AD7879-I2C的touchscreen驱动,并在3.3内核中模拟在三星平台上做移植动作。下面首先来分析下3.3.3内核中这部分代码代码。
首先看下驱动函数的入口和出口,
static int __init ad7879_i2c_init(void)
{
	return i2c_add_driver(&ad7879_i2c_driver);
}
module_init(ad7879_i2c_init);
static void __exit ad7879_i2c_exit(void)
{
	i2c_del_driver(&ad7879_i2c_driver);
}
module_exit(ad7879_i2c_exit);
在函数的入口和出口,分别进行了I2C驱动的注册和注销。
static struct i2c_driver ad7879_i2c_driver = {
	.driver = {
		.name	= "ad7879",//
		.owner	= THIS_MODULE,
		.pm	= &ad7879_pm_ops,
	},
	.probe		= ad7879_i2c_probe,//探测函数
	.remove		= __devexit_p(ad7879_i2c_remove),
	.id_table	= ad7879_id,//描述了这个I2C驱动支持那些设备
};
在这个里面内嵌了一个driver的结构体,其中name成员,再匹配设备的时候有用到,.owner= THIS_MODULE表示这个模块在使用的时候,不能被remmod。 ad7879_pm_ops一个电源管理方面的操作函数集。
SIMPLE_DEV_PM_OPS(ad7879_pm_ops, ad7879_suspend, ad7879_resume);
#define SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn) \
const struct dev_pm_ops name = { \
	SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
}
#ifdef CONFIG_PM_SLEEP
#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
	.suspend = suspend_fn, \
	.resume = resume_fn, \
	.freeze = suspend_fn, \
	.thaw = resume_fn, \
	.poweroff = suspend_fn, \
	.restore = resume_fn,
#else
#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)
#endif
可以看出,如果设备支持睡眠的,提供了  ad7879_suspend和ad7879_resume两个操作函数。
#ifdef CONFIG_PM_SLEEP
static int ad7879_suspend(struct device *dev)
{
	struct ad7879 *ts = dev_get_drvdata(dev);
	mutex_lock(&ts->input->mutex);
	if (!ts->suspended && !ts->disabled && ts->input->users)
		__ad7879_disable(ts);
	ts->suspended = true;
	mutex_unlock(&ts->input->mutex);
	return 0;
}
static int ad7879_resume(struct device *dev)
{
	struct ad7879 *ts = dev_get_drvdata(dev);
	mutex_lock(&ts->input->mutex);
	if (ts->suspended && !ts->disabled && ts->input->users)
		__ad7879_enable(ts);
	ts->suspended = false;
	mutex_unlock(&ts->input->mutex);
	return 0;
}
#endif
这两个函数主要完成touchscreen的挂起和恢复操作。
static const struct i2c_device_id ad7879_id[] = {
	{ "ad7879", 0 },
	{ "ad7889", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, ad7879_id);
这里是驱动所支持的设备数组,{}表示结束。
接下来主要看probe 探测函数,要明白探测函数试怎么被调用到的,这就是linux2.6以后最主要的设备模型,由于touchscreen并不是热插拔设备,我们会在对应平台上设置设备信息。以AD7879以I2C为例在三星的64系列板子上,假设AD7879的I2C接口接在I2C1上面的,我们会在/arch/arm/mach-s3c64XX/math-smdk6410.c中增加设备描述,
这里主要包含设备的名字和地址。这里AD7879的设备地址为0X59,因为再AD7879芯片手册上说到它的7位地址前5位固定为01011后两位可配置,我配置为00,最后一位我们配置为1,表示由主控器件向被控器件读数据所以地址为0X59.
static struct i2c_board_info i2c_devs1[] __initdata = {
	{ I2C_BOARD_INFO("24c128", 0x57), I2C_BOARD_INFO("ad7879", 0x59)},	/* Samsung S524AD0XD1 */
};
当I2C驱动注册的时候,如果 ad7879_id表中设备名字有和i2c_devs1表中支持设备的名字相同的化,调用match会成功,然后调用ad7879_i2c_probe函数
static int __devinit ad7879_i2c_probe(struct i2c_client *client,
				      const struct i2c_device_id *id)
{
	struct ad7879 *ts;
	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_WORD_DATA)) {
		dev_err(&client->dev, "SMBUS Word Data not Supported\n");
		return -EIO;
	}
	ts = ad7879_probe(&client->dev, AD7879_DEVID, client->irq,
			  &ad7879_i2c_bus_ops);
	if (IS_ERR(ts))
		return PTR_ERR(ts);
	i2c_set_clientdata(client, ts);
	return 0;
}
在探测函数里面首先完成了I2C功能的检测,然后主要由ad7879_probe完成具体的探测,注意其中有一个参数ad7879_i2c_bus_ops,表示提供给用户空间的操作函数集,最后由 i2c_set_clientdata把ts挂再I2C总线上。
static const struct ad7879_bus_ops ad7879_i2c_bus_ops = {
	.bustype	= BUS_I2C,
	.read		= ad7879_i2c_read,
	.multi_read	= ad7879_i2c_multi_read,
	.write		= ad7879_i2c_write,
};
bustype表示挂在什么总线上。
static int ad7879_i2c_read(struct device *dev, u8 reg)
{
	struct i2c_client *client = to_i2c_client(dev);
	return i2c_smbus_read_word_swapped(client, reg);
}
首先获得I2C上的设备,然后寄存器获得设备的信息,完成读。
static int ad7879_i2c_multi_read(struct device *dev,
				 u8 first_reg, u8 count, u16 *buf)
{
	struct i2c_client *client = to_i2c_client(dev);
	u8 idx;

	i2c_smbus_read_i2c_block_data(client, first_reg, count * 2, (u8 *)buf);

	for (idx = 0; idx < count; ++idx)
		buf[idx] = swab16(buf[idx]);
	return 0;
}
实现批量数据的读。
static int ad7879_i2c_write(struct device *dev, u8 reg, u16 val)
{
	struct i2c_client *client = to_i2c_client(dev);

	return i2c_smbus_write_word_swapped(client, reg, val);
}
首先获得I2C上的设备,然后设备获得寄存器信息,完成写。
struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq,
			    const struct ad7879_bus_ops *bops)
{
	struct ad7879_platform_data *pdata = dev->platform_data;
	struct ad7879 *ts;
	struct input_dev *input_dev;
	int err;
	u16 revid;
	if (!irq) {
		dev_err(dev, "no IRQ?\n");
		err = -EINVAL;
		goto err_out;
	}
	if (!pdata) {
		dev_err(dev, "no platform data?\n");
		err = -EINVAL;
		goto err_out;
	}
	ts = kzalloc(sizeof(*ts), GFP_KERNEL);//给TS分配空间
	input_dev = input_allocate_device();//动态分配input设备
	if (!ts || !input_dev) {
		err = -ENOMEM;
		goto err_free_mem;
	}
	/*初始化ts结构体*/
	ts->bops = bops;
	ts->dev = dev;
	ts->input = input_dev;
	ts->irq = irq;
	setup_timer(&ts->timer, ad7879_timer, (unsigned long) ts);//设置定时器
	ts->x_plate_ohms = pdata->x_plate_ohms ? : 400;
	ts->pressure_max = pdata->pressure_max ? : ~0;
	ts->first_conversion_delay = pdata->first_conversion_delay;
	ts->acquisition_time = pdata->acquisition_time;
	ts->averaging = pdata->averaging;
	ts->pen_down_acc_interval = pdata->pen_down_acc_interval;
	ts->median = pdata->median;
	snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev));//保存用户空间的设备节点
	/*初始化input_dev*/
	input_dev->name = "AD7879 Touchscreen";
	input_dev->phys = ts->phys;
	input_dev->dev.parent = dev;
	input_dev->id.bustype = bops->bustype;
	input_dev->open = ad7879_open;
	input_dev->close = ad7879_close;
	input_set_drvdata(input_dev, ts);
	/*设置事件*/
	__set_bit(EV_ABS, input_dev->evbit);
	__set_bit(ABS_X, input_dev->absbit);
	__set_bit(ABS_Y, input_dev->absbit);
	__set_bit(ABS_PRESSURE, input_dev->absbit);
	__set_bit(EV_KEY, input_dev->evbit);
	__set_bit(BTN_TOUCH, input_dev->keybit);
	/*设置事件参数*/
	input_set_abs_params(input_dev, ABS_X,
			pdata->x_min ? : 0,
			pdata->x_max ? : MAX_12BIT,
			0, 0);
	input_set_abs_params(input_dev, ABS_Y,
			pdata->y_min ? : 0,
			pdata->y_max ? : MAX_12BIT,
			0, 0);
	input_set_abs_params(input_dev, ABS_PRESSURE,
			pdata->pressure_min, pdata->pressure_max, 0, 0);
	/*配置AD7879控制寄存器2的第4位,完成复位*/
	err = ad7879_write(ts, AD7879_REG_CTRL2, AD7879_RESET);
	if (err < 0) {
		dev_err(dev, "Failed to write %s\n", input_dev->name);
		goto err_free_mem;
	}
	/*获得AD7879的产品号和厂商号*/
	revid = ad7879_read(ts, AD7879_REG_REVID);
	input_dev->id.product = (revid & 0xff);
	input_dev->id.version = revid >> 8;
	if (input_dev->id.product != devid) {
		dev_err(dev, "Failed to probe %s (%x vs %x)\n",
			input_dev->name, devid, revid);
		err = -ENODEV;
		goto err_free_mem;
	}
	/*配置AD7879的控制寄存器3,使其不予许温度/AUX/VBAT测量引起中断,屏蔽GPIO,测量X,Y,及压力*/
	ts->cmd_crtl3 = AD7879_YPLUS_BIT |
			AD7879_XPLUS_BIT |
			AD7879_Z2_BIT |
			AD7879_Z1_BIT |
			AD7879_TEMPMASK_BIT |
			AD7879_AUXVBATMASK_BIT |
			AD7879_GPIOALERTMASK_BIT;
	/*配置AD7879寄存器2,模拟模块掉电,平均值基础,中值滤波器,首次转换延迟时间*/
	ts->cmd_crtl2 = AD7879_PM(AD7879_PM_DYN) | AD7879_DFR |
			AD7879_AVG(ts->averaging) |
			AD7879_MFS(ts->median) |
			AD7879_FCD(ts->first_conversion_delay);
	/*配置AD7879控制寄存器3,禁用INT始能,设置为主机模式,设置ADC采集时间,设置转换间隔定时器*/
	ts->cmd_crtl1 = AD7879_MODE_INT | AD7879_MODE_SEQ1 |
			AD7879_ACQ(ts->acquisition_time) |
			AD7879_TMR(ts->pen_down_acc_interval);
	/*注册中断,这里和注册普通中断由什么区别还不是很清除*/
	err = request_threaded_irq(ts->irq, NULL, ad7879_irq,
				   IRQF_TRIGGER_FALLING,
				   dev_name(dev), ts);
	if (err) {
		dev_err(dev, "irq %d busy?\n", ts->irq);
		goto err_free_mem;
	}
	__ad7879_disable(ts);//让touchscreen不可用
	err = sysfs_create_group(&dev->kobj, &ad7879_attr_group);//生成系统属性文件
	if (err)
		goto err_free_irq;
	err = ad7879_gpio_add(ts, pdata);
	if (err)
		goto err_remove_attr;
	err = input_register_device(input_dev);
	if (err)
		goto err_remove_gpio;
	return ts;
err_remove_gpio:
	ad7879_gpio_remove(ts);
err_remove_attr:
	sysfs_remove_group(&dev->kobj, &ad7879_attr_group);
err_free_irq:
	free_irq(ts->irq, ts);
err_free_mem:
	input_free_device(input_dev);
	kfree(ts);
err_out:
	return ERR_PTR(err);
}
上面由几个地方没有说清除,比如设置定时器
setup_timer(&ts->timer, ad7879_timer, (unsigned long) ts);//设置定时器
当时间溢出后,进入中断处理例程
static void ad7879_timer(unsigned long handle)
{
	struct ad7879 *ts = (void *)handle;
	ad7879_ts_event_release(ts);
}
static void ad7879_ts_event_release(struct ad7879 *ts)
{
	struct input_dev *input_dev = ts->input;

	input_report_abs(input_dev, ABS_PRESSURE, 0);
	input_report_key(input_dev, BTN_TOUCH, 0);
	input_sync(input_dev);
}
由这里可以看出,定时溢出以后,主要完成,touchscreen按键的释放
让touchscreen不可用的函数需要说明一下
static void __ad7879_disable(struct ad7879 *ts)
{
	u16 reg = (ts->cmd_crtl2 & ~AD7879_PM(-1)) |
		AD7879_PM(AD7879_PM_SHUTDOWN);//实现ADC的完全关断
	disable_irq(ts->irq);
	if (del_timer_sync(&ts->timer))
		ad7879_ts_event_release(ts);
	ad7879_write(ts, AD7879_REG_CTRL2, reg);
}
#define AD7879_PM(x)			((x & 0x3) << 14)
#define AD7879_PM_SHUTDOWN		(0)
可以看出这个函数主要屏蔽中断,完全关闭AD7879
和它对应的一个函数,始能AD7879
static void __ad7879_enable(struct ad7879 *ts)
{
	ad7879_write(ts, AD7879_REG_CTRL2, ts->cmd_crtl2);
	ad7879_write(ts, AD7879_REG_CTRL3, ts->cmd_crtl3);
	ad7879_write(ts, AD7879_REG_CTRL1, ts->cmd_crtl1);
	enable_irq(ts->irq);
}
static int ad7879_write(struct ad7879 *ts, u8 reg, u16 val)
{
	return ts->bops->write(ts->dev, reg, val);
}
首先这里说明一下吧,在配置内核的时候AD7879-I2C这个驱动,它依赖AD7879驱动,从/drivers/input/touchscreen/kconfig也可以看到
config TOUCHSCREEN_AD7879
	tristate "Analog Devices AD7879-1/AD7889-1 touchscreen interface"
	help
	  Say Y here if you want to support a touchscreen interface using
	  the AD7879-1/AD7889-1 controller.

	  You should select a bus connection too.

	  To compile this driver as a module, choose M here: the
	  module will be called ad7879.

config TOUCHSCREEN_AD7879_I2C
	tristate "support I2C bus connection"
	depends on TOUCHSCREEN_AD7879 && I2C
	help
	  Say Y here if you have AD7879-1/AD7889-1 hooked to an I2C bus.

	  To compile this driver as a module, choose M here: the
	  module will be called ad7879-i2c.
所以AD7879实现了这块ADC的完全封装,具体使用SPI,还是I2C总线完成数据传输的驱动,只是实现数据传输的读写,而具体与AD7879的信息则不用关心,所以在ad7879_probe中会传入bops的操作函数集接口,用于AD7879使用。

现在来看一下在初始化input_dev的时候,输入子系统的打开和关闭函数的实现。
static int ad7879_open(struct input_dev *input)
{
	struct ad7879 *ts = input_get_drvdata(input);
	/* protected by input->mutex */
	if (!ts->disabled && !ts->suspended)
		__ad7879_enable(ts);
	return 0;
}
static void ad7879_close(struct input_dev* input)
{
	struct ad7879 *ts = input_get_drvdata(input);
	/* protected by input->mutex */
	if (!ts->disabled && !ts->suspended)
		__ad7879_disable(ts);
}
其实在input_dev打开和关闭中主要完成的开启和关闭AD7879,在开启和关闭之前有判断ts是否是不可用,是否是挂起的,这里主要防止竞态。
static irqreturn_t ad7879_irq(int irq, void *handle)
{
	struct ad7879 *ts = handle;
	ad7879_multi_read(ts, AD7879_REG_XPLUS, AD7879_NR_SENSE, ts->conversion_data);
	if (!ad7879_report(ts))
		mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);
	return IRQ_HANDLED;
}
中断处理例程中,主要完成事件的上报,如果上报成功,设置定时器。
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;
	y = ts->conversion_data[AD7879_SEQ_YPOS] & MAX_12BIT;
	z1 = ts->conversion_data[AD7879_SEQ_Z1] & MAX_12BIT;
	z2 = ts->conversion_data[AD7879_SEQ_Z2] & MAX_12BIT;
	/*
	 * 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/spi/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)) {
		/* compute touch pressure resistance using equation #1 */
		/*更具AD7879提供的公式,求出按下的压力*/
		Rt = (z2 - z1) * x * ts->x_plate_ohms;
		Rt /= z1;
		Rt = (Rt + 2047) >> 12;
		/*
		 * Sample found inconsistent, pressure is beyond
		 * the maximum. Don't report it to user space.
		 */
		if (Rt > ts->pressure_max)
			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)) {
			/* Touch continues */
			input_report_key(input_dev, BTN_TOUCH, 1);
			input_report_abs(input_dev, ABS_X, ts->x);
			input_report_abs(input_dev, ABS_Y, ts->y);
			input_report_abs(input_dev, ABS_PRESSURE, ts->Rt);
			input_sync(input_dev);
		}
		ts->x = x;
		ts->y = y;
		ts->Rt = Rt;
		return 0;
	}
	return -EINVAL;
}
再touchscreen中关于input的将在后面分析,现在总结一下AD7879-I2C touchscreen在驱动中的流程吧。
1.驱动的入口注册I2C
2.实现ad7879_i2c_bus_ops操作函数集中的操作,基于不同总线有不同的读写操作。
3.初始化AD7879 touchsreen
4.分配input_dev,初始化input_dev
5.设置事件及对应事件的参数
6.配置AD7879的控制寄存器
7.在中断处理例程里面完成AD转换,上报数据
8.实现定时器溢出函数来释放touchscreen

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值