当我们按下触摸屏时:
1,首先进入触摸屏中断,RQ_TC
2,如果触摸屏是被按下的状态,则调用touch_timer_fire启动ADC中断adc_irq
3,如果1个时间滴答到来则进入定时器服务程序touch_timer_fire
4,判断触摸屏是否仍然处于按下状态
5,如果是,则上报事件和转换的数据,并重启ADC转换,重复第(2)步
6,如果不是(松开),则上报事件和转换的数据,本次转换完成
从module_init开始分析
-
static int __init s3c2410ts_init(void)
-
{
-
return platform_driver_register(&s3c_ts_driver);
-
}
-
-
static void __exit s3c2410ts_exit(void)
-
{
-
platform_driver_unregister(&s3c_ts_driver);
-
}
-
-
module_init(s3c2410ts_init);
-
module_exit(s3c2410ts_exit);
这里向平台总线注册一个驱动,
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),
-
}
先看id_table
-
static struct dev_pm_ops s3c_ts_pmops = {
-
.suspend = s3c2410ts_suspend,//挂起操作
-
.resume = s3c2410ts_resume,//重启操作
-
};
-
#endif
-
-
static struct platform_device_id s3cts_driver_ids[] = {
-
{ "s3c2410-ts", 0 },
-
{ "s3c2440-ts", 0 },
-
{ "s3c64xx-ts", FEAT_PEN_IRQ },
-
{ }
-
};
-
MODULE_DEVICE_TABLE(platform, s3cts_driver_ids)
接下来分析驱动函数
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清零
-
-
ts.dev = dev;
-
-
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");//获取时钟
-
if (IS_ERR(ts.clock)) {
-
dev_err(dev, "cannot get adc clock source\n");
-
return -ENOENT;
-
}
-
-
clk_enable(ts.clock);//使能时钟
-
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);//获得I/O资源
-
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);//注册申请获得ADC服务的客户, 设置好select和convert回调函数
-
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();//分配一个输入型设备
-
if (!input_dev) {
-
dev_err(dev, "Unable to allocate the input device !!\n");
-
ret = -ENOMEM;
-
goto err_iomap;
-
}
-
-
ts.input = input_dev;
-
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);//注册一个输入型设备
-
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;
-
}
第10行,将全局变量ts清零,我们来看ts数据类型的定义:
-
struct s3c2410ts {
-
struct s3c_adc_client *client;
-
struct device *dev;
-
struct input_dev *input;//通用输入设备结构体
-
struct clk *clock;
-
void __iomem *io;//映射后的寄存器基址
-
unsigned long xp;//x坐标
-
unsigned long yp;//y坐标
-
int irq_tc;//触摸屏中断号?
-
int count;//计采样次数
-
int shift;//1<
-
int features;
-
};
-
-
static struct s3c2410ts ts
第14行,获得了平台设备的私有数据,看info数据类型的定义,它定义在arch/arm/plat-samsung/include/plat/ts.h:
-
struct s3c2410_ts_mach_info {
-
int delay;//adc延迟
-
int presc;//预分频系数
-
int oversampling_shift;//通过它得到采用次数
-
void (*cfg_gpio)(struct platform_device *dev);
-
}
应该要在mach-my2440中定义一个值,类似
-
static struct s3c2410_ts_mach_info s3c_ts_platform __initdata = {
-
.delay = 10000,
-
.presc = 49,
-
.oversampling_shift = 2,
-
}
继续s3c2410ts_probe,22行获得时钟,需要时钟的是因为触摸屏要用到ADC转换,而完成ADC转换则需要时钟
第52行,初始化gpio管脚,这个cfg_gpio是设备提供的接口,参数是一个宏,返回的是struct platform_device结构体指针,这个宏用到了大名鼎鼎的container_of宏,container_of宏的作用是根据结构体的成员返回结构体的首地址。
ts.client = s3c_adc_register(pdev, s3c24xx_ts_select, s3c24xx_ts_conversion, 1);这里s3c24xx_ts_select、s3c24xx_ts_conversion就是回调函数,先看看看这两个回调函数再继续s3c2410ts_probe:
-
/**
-
* s3c24xx_ts_select - ADC selection callback.
-
* @client: The client that was registered with the ADC core.
-
* @select: The reason for select.
-
*
-
* Called when the ADC core selects (or deslects) us as a client.
-
*/
-
static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
-
{
-
if (select) {
-
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
-
ts.io + S3C2410_ADCTSC);
-
} else {
-
mod_timer(&touch_timer, jiffies+1);
-
writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
-
}
-
}
select为真,将ADCTSC的23467位置1,其他位为0,意味着禁止XP上拉,启用自动(连续)x/y轴坐标转换模式。 select为假,调用了一个mod_timer,然后设置ADCTSC为等待中断模式、探测到抬起发中断。
-
/**
-
* s3c24xx_ts_conversion - ADC conversion callback
-
* @client: The client that was registered with the ADC core.
-
* @data0: The reading from ADCDAT0.
-
* @data1: The reading from ADCDAT1.
-
* @left: The number of samples left.
-
*
-
* Called when a conversion has finished.
-
*/
-
static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
-
unsigned data0, unsigned data1,
-
unsigned *left)
-
{
-
dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
-
-
ts.xp += data0;
-
ts.yp += data1;
-
-
ts.count++;
-
-
/* From tests, it seems that it is unlikely to get a pen-up
-
* event during the conversion process which means we can
-
* ignore any pen-up events with less than the requisite
-
* count done.
-
*
-
* In several thousand conversions, no pen-ups where detected
-
* before count completed.
-
*/
-
}
看中断处理函数
-
/**
-
* stylus_irq - touchscreen stylus event interrupt
-
* @irq: The interrupt number
-
* @dev_id: The device ID.
-
*
-
* Called when the IRQ_TC is fired for a pen up or down event.
-
*/
-
static irqreturn_t stylus_irq(int irq, void *dev_id)
-
{
-
unsigned long data0;
-
unsigned long data1;
-
bool down;
-
-
data0 = readl(ts.io + S3C2410_ADCDAT0);//读ADCDAT0
-
data1 = readl(ts.io + S3C2410_ADCDAT1);//读ADCDAT1
-
-
down = get_down(data0, data1);//判断有没有按下
-
-
/* 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);//如果被按下
-
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;
-
}
s3c_adc_start函数分析
-
int s3c_adc_start(struct s3c_adc_client *client,
-
unsigned int channel, unsigned int nr_samples)
-
{
-
struct adc_device *adc = adc_dev;
-
unsigned long flags;
-
-
if (!adc) {
-
printk(KERN_ERR "%s: failed to find adc\n", __func__);
-
return -EINVAL;
-
}
-
-
spin_lock_irqsave(&adc->lock, flags);//获得锁
-
-
if (client->is_ts && adc->ts_pend) {
-
spin_unlock_irqrestore(&adc->lock, flags);
-
return -EAGAIN;
-
}
-
-
client->channel = channel;//设置通道
-
client->nr_samples = nr_samples;//设置采样次数
-
-
if (client->is_ts)
-
adc->ts_pend = client;//如果是触摸屏,将client保存在ts_pend中
-
else
-
list_add_tail(&client->pend, &adc_pending);//如果不是触摸屏,挂到链表
-
-
if (!adc->cur)//如果没有正在处理其他客户请求,则调用s3c_adc_try函数处理当前客户请求
-
s3c_adc_try(adc);
-
-
spin_unlock_irqrestore(&adc->lock, flags);//释放锁
-
-
return 0;
-
}
-
EXPORT_SYMBOL_GPL(s3c_adc_start)
-
static void s3c_adc_try(struct adc_device *adc)
-
{
-
struct s3c_adc_client *next = adc->ts_pend;
-
-
if (!next && !list_empty(&adc_pending))//如果没有触摸屏在等待,
-
{
-
next = list_first_entry(&adc_pending,
-
struct s3c_adc_client, pend);
-
list_del(&next->pend);
-
}
-
else//如果有
-
adc->ts_pend = NULL;
-
-
if (next) {
-
adc_dbg(adc, "new client is %p\n", next);
-
adc->cur = next;
-
s3c_adc_select(adc, next);//调用s3c_adc_select,该函数有client->select_cb回调函数的调用
-
s3c_adc_convert(adc);
-
s3c_adc_dbgshow(adc);
-
}
-
}
s3c_adc_select(adc, next);
也就是在这里调用了s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)函数,调用了mod_timer(&touch_timer, jiffies+1);开启了定时器
mod_timer的功能是修改定时器的超时参数并重启,第一个参数是要修改的时钟,第二个参数是超时时间。
-
int mod_timer(struct timer_list *timer, unsigned long expires)
-
{
-
expires = apply_slack(timer, expires);
-
-
/*
-
* This is a common optimization triggered by the
-
* networking code - if the timer is re-modified
-
* to be the same thing then just return:
-
*/
-
if (timer_pending(timer) && timer->expires == expires)
-
return 1;
-
-
return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
-
}
定时器touch_timer的初始化:
-
static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0)
DEFINE_TIMER宏会定义一个名叫 touch_timer内核定时器,并初始化其 function, expires, name 和 base 字段。当jiffies 到达expires时会执行function,并传递data作为参数。这里并没有给expires和data传值
看看touch_timer_fire,定时器超时会做什么
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);//初始化的时候,ts被清零,ts.count = 0,第一次进按键按下中断,启动ADC中断,ADC采集数据,再启动定时器
-
} 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);//进入等待中断模式
-
}
-
}
使用input子系统函数input_sync(ts.input)同步所有事件,将所有事件组成一个evdev包,并通过/dev/input/eventX发送出去。input子系统要求提交各种事件后必须执行同步。
代码都分析完了,结合ADC驱动,总结下触摸屏驱动工作的逻辑
-
s3c2410ts_probe
-
获得时钟、使能时钟
-
获得IO资源,映射到虚拟地址
-
初始化gpio、为采样数据分配空间
-
注册ADC服务,传递select和convert回调函数指针
-
设置工作模式为等待中断模式
-
获得、申请中断,中断处理函数stylus_irq
-
注册系统输入设备
-
等待INT_TC中断,中断到来,进入stylus_irq
-
判断是否被按下,被按下调用s3c_adc_start
-
在s3c_adc_start中根据传的参数设置通道和采样次数,调用s3c_adc_try处理请求
-
在s3c_adc_try中,先检查有没有触摸屏在等待,如果有
-
调用s3c_adc_select,执行触摸屏注册的select回调函数(s3c24xx_ts_select),禁止XP上拉,启用自动(连续)x/y轴坐标转换模式。
-
调用s3c_adc_convert,开启AD转换
-
调用s3c_adc_dbgshow,打印相关寄存器的值
-
转换完成,产生INT_ADC中断,进入中断处理函数,s3c_adc_irq
-
读ADCDAT0和ADCDAT1的值,将12位以上清零,这是x、y轴的坐标
-
执行触摸屏注册的convert回调函数(s3c24xx_ts_conversion),保存坐标到结构体数组apt
-
这里我们假设采样完成,再次调用select回调函数,取消对触摸屏的选择,启动定时器,超时后执行touch_timer_fire
-
定时器超时处理函数touch_timer_fire 中
-
采样完成,向input子系统提交处理后的坐标、触屏时间等,清空ts.x、ts.y、ts.count,等待下一次采样
-
采样未完成,调用s3c_adc_start继续采样
-
执行完touch_timer_fire,设置ADC_TSC寄存器,回到等待中断模式
阅读(2020) | 评论(0) | 转发(0) |