S3C2440驱动简析——触摸屏驱动

    因困于杂事,博客荒废将近半个月,此时此刻重提笔墨,继续记录鄙人学习之历程。

    本文将简要分析2440的触摸屏驱动,其驱动程序内核自带。在浏览本文之前,如果对Linux 驱动的input 子系统没有认识的话,请先回头参考鄙人之前的博文《input子系统》http://blog.csdn.net/jarvis_xian/article/details/6552579

 

    事不宜迟,马上进入本文的正题。有经验的朋友都知道,看驱动,先找入口、出口!

static int __init s3c2410ts_init(void)
{
	return platform_driver_register(&s3c_ts_driver);//注册platform驱动,注意参数s3c_ts_driver
}

static void __exit s3c2410ts_exit(void)
{
	platform_driver_unregister(&s3c_ts_driver);   //注销platform驱动
}

platform_driver_register 的参数s3c_ts_driver 定义如下

static struct platform_driver s3c_ts_driver = {
	.driver         = {
		.name   = "samsung-ts",
		.owner  = THIS_MODULE,
#ifdef CONFIG_PM
		.pm	= &s3c_ts_pmops,
#endif
	},
	.id_table	= s3cts_driver_ids,
	.probe		= s3c2410ts_probe,
	.remove		= __devexit_p(s3c2410ts_remove),
};

在上述结构体中,我们应该把精力放在.probe、.remove和.pm上

探讨probe 函数前,我们先看一下结构体ts 的定义,其统筹了整个驱动所用到的资源

struct s3c2410ts {
	struct s3c_adc_client *client;
	struct device *dev;
	struct input_dev *input;
	struct clk *clock;
	void __iomem *io;
	unsigned long xp;
	unsigned long yp;
	int irq_tc;
	int count;
	int shift;
	int features;
};


s3c2410ts_probe代码如下:

static int __devinit s3c2410ts_probe(struct platform_device *pdev)
{
	struct s3c2410_ts_mach_info *info;
	struct device *dev = &pdev->dev;
	struct input_dev *input_dev;
	struct resource *res;
	int ret = -EINVAL;

	/* Initialise input stuff */
	memset(&ts, 0, sizeof(struct s3c2410ts));

	ts.dev = dev;   //把platform的设备挂到ts 结构体

	info = pdev->dev.platform_data;
	if (!info) {
		dev_err(dev, "no platform data, cannot attach\n");
		return -EINVAL;
	}

	dev_dbg(dev, "initialising touchscreen\n");

	ts.clock = clk_get(dev, "adc");   //得到adc 时钟资源
	if (IS_ERR(ts.clock)) {
		dev_err(dev, "cannot get adc clock source\n");
		return -ENOENT;
	}

	clk_enable(ts.clock);   //使能ts 时钟
	dev_dbg(dev, "got and enabled clocks\n");

	ts.irq_tc = ret = platform_get_irq(pdev, 0);   //获取中断资源
	if (ret < 0) {
		dev_err(dev, "no resource for interrupt\n");
		goto err_clk;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //获取占用端口的大小
	if (!res) {
		dev_err(dev, "no resource for registers\n");
		ret = -ENOENT;
		goto err_clk;
	}

	ts.io = ioremap(res->start, resource_size(res));   //物理地址映射到虚拟地址
	if (ts.io == NULL) {
		dev_err(dev, "cannot map registers\n");
		ret = -ENOMEM;
		goto err_clk;
	}

	/* inititalise the gpio */
	if (info->cfg_gpio)
		info->cfg_gpio(to_platform_device(ts.dev));

	ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
				     s3c24xx_ts_conversion, 1);
	if (IS_ERR(ts.client)) {
		dev_err(dev, "failed to register adc client\n");
		ret = PTR_ERR(ts.client);
		goto err_iomap;
	}

	/* Initialise registers */
	if ((info->delay & 0xffff) > 0)
		writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);   //初始化延时寄存器

	writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);

	input_dev = input_allocate_device();   //为input_dev 分配空间并初始化
	if (!input_dev) {
		dev_err(dev, "Unable to allocate the input device !!\n");
		ret = -ENOMEM;
		goto err_iomap;
	}

	ts.input = input_dev;   //把input_dev 挂接到ts 结构体
	ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); //设置事件类型
	ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);   //设置边界条件等
	input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);

	ts.input->name = "S3C24XX TouchScreen";
	ts.input->id.bustype = BUS_HOST;
	ts.input->id.vendor = 0xDEAD;
	ts.input->id.product = 0xBEEF;
	ts.input->id.version = 0x0102;

	ts.shift = info->oversampling_shift;
	ts.features = platform_get_device_id(pdev)->driver_data;

	ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
			  "s3c2410_ts_pen", ts.input);
	if (ret) {
		dev_err(dev, "cannot get TC interrupt\n");
		goto err_inputdev;
	}

	dev_info(dev, "driver attached, registering input device\n");

	/* All went ok, so register to the input system */
	ret = input_register_device(ts.input);   //初始化完毕后,注册input 子系统
	if (ret < 0) {
		dev_err(dev, "failed to register input device\n");
		ret = -EIO;
		goto err_tcirq;
	}

	return 0;

 err_tcirq:
	free_irq(ts.irq_tc, ts.input);
 err_inputdev:
	input_free_device(ts.input);
 err_iomap:
	iounmap(ts.io);
 err_clk:
	del_timer_sync(&touch_timer);
	clk_put(ts.clock);
	return ret;
}
 
相对于probe,remove的代码如下
static int __devexit s3c2410ts_remove(struct platform_device *pdev)
{
	free_irq(ts.irq_tc, ts.input);   //释放中断资源
	del_timer_sync(&touch_timer);   //卸载软件时钟

	clk_disable(ts.clock);   //禁能
	clk_put(ts.clock);

	input_unregister_device(ts.input);   //注销输入子系统
	iounmap(ts.io);   //解除地址映射

	return 0;
}
 
至于上文提及的.pm,实则也是一个结构体,如下
static struct dev_pm_ops s3c_ts_pmops = {
	.suspend	= s3c2410ts_suspend,
	.resume		= s3c2410ts_resume,
};
包含了suspend 和resume 两个操作。
 
s3c2410ts_suspend函数代码:
static int s3c2410ts_suspend(struct device *dev)
{
	writel(TSC_SLEEP, ts.io + S3C2410_ADCTSC);   //把挂起状态写入硬件寄存器
	disable_irq(ts.irq_tc);   
	clk_disable(ts.clock);

	return 0;
}
 
s3c2410ts_resume函数代码:
static int s3c2410ts_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct s3c2410_ts_mach_info *info = pdev->dev.platform_data;

	clk_enable(ts.clock);
	enable_irq(ts.irq_tc);

	/* Initialise registers */
	if ((info->delay & 0xffff) > 0)   //写入延时初值
		writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);

	writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);   //写入工作状态

	return 0;
}
 
经过上述代码的铺垫,我们已经有了一个舒适的环境,剩下的工作,就在与中断相关的函数里完成了。
static irqreturn_t stylus_irq(int irq, void *dev_id)
{
	unsigned long data0;
	unsigned long data1;
	bool down;

	data0 = readl(ts.io + S3C2410_ADCDAT0);   //读取X、Y轴坐标
	data1 = readl(ts.io + S3C2410_ADCDAT1);

	down = get_down(data0, data1);   //关于get_down函数查看下文

	/* TODO we should never get an interrupt with down set while
	 * the timer is running, but maybe we ought to verify that the
	 * timer isn't running anyways. */

	if (down)
		s3c_adc_start(ts.client, 0, 1 << ts.shift);   //开始ADC转换
	else
		dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);

	if (ts.features & FEAT_PEN_IRQ) {   //清除标志位(等待下一次中断的到来)
		/* Clear pen down/up interrupt */
		writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
	}

	return IRQ_HANDLED;
}
static inline bool get_down(unsigned long data0, unsigned long data1)
{   //检测触摸屏有没有被按下
	/* returns true if both data values show stylus down */
	return (!(data0 & S3C2410_ADCDAT0_UPDOWN) &&
		!(data1 & S3C2410_ADCDAT0_UPDOWN));
}
 
touch_timer_fire函数将触摸屏的数据报告给内核
static void touch_timer_fire(unsigned long data)
{
	unsigned long data0;
	unsigned long data1;
	bool down;

	data0 = readl(ts.io + S3C2410_ADCDAT0);
	data1 = readl(ts.io + S3C2410_ADCDAT1);

	down = get_down(data0, data1);

	if (down) {
		if (ts.count == (1 << ts.shift)) {
			ts.xp >>= ts.shift;
			ts.yp >>= ts.shift;

			dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
				__func__, ts.xp, ts.yp, ts.count);

			input_report_abs(ts.input, ABS_X, ts.xp);   //熟悉的操作~~~
			input_report_abs(ts.input, ABS_Y, ts.yp);

			input_report_key(ts.input, BTN_TOUCH, 1);
			input_sync(ts.input);   //结束同步

			ts.xp = 0;
			ts.yp = 0;
			ts.count = 0;
		}

		s3c_adc_start(ts.client, 0, 1 << ts.shift);
	} else {
		ts.xp = 0;
		ts.yp = 0;
		ts.count = 0;

		input_report_key(ts.input, BTN_TOUCH, 0);
		input_sync(ts.input);

		writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
	}
}
 
    
    最后,我们可以看到触摸屏驱动是基于platform + input子系统 搭建起来的,正如搭积木一样,只要理解了每一个系统、部件的特点和接口等等,再稍加努力消化,就可把各式简单驱动程序的架构辨清,继而为自己编写驱动或者修改驱动打下了一个良好的基础。以上仅是菜鸟之愚见,还请前辈多多指教!

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