文章目录
触摸屏驱动
触摸屏驱动分析
触摸屏驱动的主要函数如下:
tscadc_readl()
tscadc_writel()
tsc_setp_config()
tsc_idle_config()
tscadc_interrupt()
tscadc_probe()
tscadc_remove()
tscadc_suspend()
tscadc_resume()
ti_tsc_init()
ti_tsc_exit()
本次分析将对每个函数进行分析,了解每个函数的作用,以及函数在何时被调用。
ti_tsc_init函数和ti_tsc_exit函数
这两个函数较为简单,在platform总线驱动的框架下,进行驱动的注册和注销,将驱动的接口函数注册到内核当中。
tscadc_resume函数
这个函数是一个复位函数,对设备进行一系列的初始化。使设备在休眠状态被唤醒。
static int tscadc_resume(struct platform_device *pdev)
{
struct tscadc *ts_dev = platform_get_drvdata(pdev);
unsigned int restore;
pm_runtime_get_sync(&pdev->dev);
if (device_may_wakeup(&pdev->dev)) {
tscadc_writel(ts_dev, TSCADC_REG_IRQWAKEUP,
TSCADC_IRQWKUP_DISABLE);
tscadc_writel(ts_dev, TSCADC_REG_IRQCLR, TSCADC_IRQENB_HW_PEN);
}
/* context restore */
tscadc_writel(ts_dev, TSCADC_REG_CTRL, ts_dev->ctrl);
tsc_idle_config(ts_dev);
tsc_step_config(ts_dev);
tscadc_writel(ts_dev, TSCADC_REG_FIFO1THR, 6);
restore = tscadc_readl(ts_dev, TSCADC_REG_CTRL);
tscadc_writel(ts_dev, TSCADC_REG_CTRL,
(restore | TSCADC_CNTRLREG_TSCSSENB));
return 0;
}
驱动在此处进行了一系列的写寄存器操作,目的是为了触摸屏正常工作进行准备。
tscadc_resume函数寄存器使用情况
首先了解该函数的基本功能就是对设备的初始化,下一步进一步分析是如何对该设备进行的初始化。
用到的寄存器有TSCADC_REG_IRQWAKEUP、TSCADC_REG_IRQCLR、TSCADC_REG_CTRL、TSCADC_REG_FIFO1THR。
下面对每个寄存器进行分析:
TSCADC_REG_IRQWAKEUP
该寄存器十分简单,仅在第0位表示wakeup是否使能。
TSCADC_REG_IRQCLR
中断控制寄存器,该寄存器控制很多的中断使能。
该寄存器中的所有中断使能在中断状态寄存器中都有表示。
TSCADC_REG_CTRL
在代码中可以看到,向该寄存器中写入了设备结构体中的ctrl,经过查找,找到了ctrl的值。
位数 | 作用 |
---|---|
9 | 中断是否抢占软件step |
8 | 中断映射 |
7 | 触摸屏使能 |
6 | 设置5线模式 |
5 | 设置4线模式 |
4 | 电源控制 |
3 | adc偏置选择 |
2 | 寄存器写保护 |
1 | ADC ID tag |
0 | 使能 |
TSCADC_REG_FIFO1THR
6位,设置多少字节产生一次fifo中断,供CPU获取FIFO数据。
tscadc_suspend函数
static int tscadc_suspend(struct platform_device *pdev, pm_message_t state)
{
struct tscadc *ts_dev = platform_get_drvdata(pdev);
unsigned int idle;
if (device_may_wakeup(&pdev->dev)) {
idle = tscadc_readl(ts_dev, TSCADC_REG_IRQENABLE);
tscadc_writel(ts_dev, TSCADC_REG_IRQENABLE,
(idle | TSCADC_IRQENB_HW_PEN));
tscadc_writel(ts_dev, TSCADC_REG_IRQWAKEUP, TSCADC_IRQWKUP_ENB);
}
/* module disable */
idle = 0;
idle = tscadc_readl(ts_dev, TSCADC_REG_CTRL);
idle &= ~(TSCADC_CNTRLREG_TSCSSENB);
tscadc_writel(ts_dev, TSCADC_REG_CTRL, idle);
pm_runtime_put_sync(&pdev->dev);
return 0;
}
这个函数是一个暂停函数,使控制器进入暂停状态,继续执行一系列的写寄存器操作。
对寄存器就是停止使能的操作,进入休眠。
tscadc_remove函数
static int __devexit tscadc_remove(struct platform_device *pdev)
{
struct tscadc *ts_dev = platform_get_drvdata(pdev);
struct resource *res;
free_irq(ts_dev->irq, ts_dev);
input_unregister_device(ts_dev->input);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
iounmap(ts_dev->tsc_base);
release_mem_region(res->start, resource_size(res));
pm_runtime_disable(&pdev->dev);
kfree(ts_dev);
device_init_wakeup(&pdev->dev, 0);
platform_set_drvdata(pdev, NULL);
return 0;
}
卸载函数,释放中断,释放设备,释放内存。
tscadc_probe函数
tscadc_probe函数是驱动函数中非常重要的一个函数,该函数比较长,主要实现的功能为将设备和驱动连接起来,当驱动向内核注册时,就会从设备链表中寻找相匹配的设备,当匹配成功后,就会调用probe函数,将设备与驱动连接起来。
下面我们分析probe函数是如何将设备和驱动连接起来的。
static int __devinit tscadc_probe(struct platform_device *pdev)
{
struct tscadc *ts_dev;
struct input_dev *input_dev;
int err;
int clk_value;
int clock_rate, irqenable, ctrl;
struct tsc_data *pdata = pdev->dev.platform_data;
struct resource *res;
struct clk *clk;
//为设备申请资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resource defined.\n");
return -EINVAL;
}
//为设备申请内存
/* Allocate memory for device */
ts_dev = kzalloc(sizeof(struct tscadc), GFP_KERNEL);
if (!ts_dev) {
dev_err(&pdev->dev, "failed to allocate memory.\n");
return -ENOMEM;
}
//申请中断号
ts_dev->irq = platform_get_irq(pdev, 0);
if (ts_dev->irq < 0) {
dev_err(&pdev->dev, "no irq ID is specified.\n");
return -ENODEV;
}
//为输入设备申请内存
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(&pdev->dev, "failed to allocate input device.\n");
err = -ENOMEM;
goto err_free_mem;
}
ts_dev->input = input_dev;
//请求内存区域
res = request_mem_region(res->start, resource_size(res), pdev->name);
if (!res) {
dev_err(&pdev->dev, "failed to reserve registers.\n");
err = -EBUSY;
goto err_free_mem;
}
ts_dev->tsc_base = ioremap(res->start, resource_size(res));
if (!ts_dev->tsc_base) {
dev_err(&pdev->dev, "failed to map registers.\n");
err = -ENOMEM;
goto err_release_mem;
}
share_tsc_base = ts_dev->tsc_base;
//为中断申请内存
err = request_irq(ts_dev->irq, tscadc_interrupt, IRQF_DISABLED,
pdev->dev.driver->name, ts_dev);
if (err) {
dev_err(&pdev->dev, "failed to allocate irq.\n");
goto err_unmap_regs;
}
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
//设定ADC时钟
clk = clk_get(&pdev->dev, "adc_tsc_fck");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get TSC fck\n");
err = PTR_ERR(clk);
goto err_free_irq;
}
clock_rate = clk_get_rate(clk);
clk_value = clock_rate / ADC_CLK;
if (clk_value < 7) {
dev_err(&pdev->dev, "clock input less than min clock requirement\n");
err = -EINVAL;
goto err_fail;
}
/* TSCADC_CLKDIV needs to be configured to the value minus 1 */
clk_value = clk_value - 1;
tscadc_writel(ts_dev, TSCADC_REG_CLKDIV, clk_value);
//将设备信息写入到申请到的内存当中
ts_dev->wires = pdata->wires;
ts_dev->x_max = pdata->x_max;
ts_dev->y_max = pdata->y_max;
ts_dev->analog_input = pdata->analog_input;
ts_dev->x_plate_resistance = pdata->x_plate_resistance;
/* Set the control register bits */
ctrl = TSCADC_CNTRLREG_STEPCONFIGWRT |
TSCADC_CNTRLREG_TSCENB |
TSCADC_CNTRLREG_STEPID;
switch (ts_dev->wires) {
case 4:
ctrl |= TSCADC_CNTRLREG_4WIRE;
break;
case 5:
ctrl |= TSCADC_CNTRLREG_5WIRE;
break;
case 8:
ctrl |= TSCADC_CNTRLREG_8WIRE;
break;
}
tscadc_writel(ts_dev, TSCADC_REG_CTRL, ctrl);
ts_dev->ctrl = ctrl;
/* Set register bits for Idel Config Mode */
tsc_idle_config(ts_dev);
/* IRQ Enable */
irqenable = TSCADC_IRQENB_FIFO1THRES;
tscadc_writel(ts_dev, TSCADC_REG_IRQENABLE, irqenable);
tsc_step_config(ts_dev);
tscadc_writel(ts_dev, TSCADC_REG_FIFO1THR, 6);
ctrl |= TSCADC_CNTRLREG_TSCSSENB;
tscadc_writel(ts_dev, TSCADC_REG_CTRL, ctrl);
input_dev->name = "Resistance_ts";
input_dev->dev.parent = &pdev->dev;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X,
ts_dev->x_min ? : 0,
ts_dev->x_max ? : MAX_12BIT,
0, 0);
input_set_abs_params(input_dev, ABS_Y,
ts_dev->y_min ? : 0,
ts_dev->y_max ? : MAX_12BIT,
0, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0);
/* register to the input system */
err = input_register_device(input_dev);
if (err)
goto err_fail;
device_init_wakeup(&pdev->dev, true);
platform_set_drvdata(pdev, ts_dev);
return 0;
err_fail:
pm_runtime_disable(&pdev->dev);
err_free_irq:
free_irq(ts_dev->irq, ts_dev);
err_unmap_regs:
iounmap(ts_dev->tsc_base);
err_release_mem:
release_mem_region(res->start, resource_size(res));
input_free_device(ts_dev->input);
err_free_mem:
kfree(ts_dev);
return err;
}
tscadc_interrupt中断处理函数
static irqreturn_t tscadc_interrupt(int irq, void *dev)
{
struct tscadc *ts_dev = (struct tscadc *)dev;
struct input_dev *input_dev = ts_dev->input;
unsigned int status, irqclr = 0;
int i;
int fsm = 0, fifo0count = 0, fifo1count = 0;
unsigned int readx1 = 0, ready1 = 0;
unsigned int prev_val_x = ~0, prev_val_y = ~0;
unsigned int prev_diff_x = ~0, prev_diff_y = ~0;
unsigned int cur_diff_x = 0, cur_diff_y = 0;
unsigned int val_x = 0, val_y = 0, diffx = 0, diffy = 0;
unsigned int z1 = 0, z2 = 0, z = 0;
读取中断状态,通过中断状态判断当前是ADC产生的数据转换完成中断,还是触摸屏触摸中断。当ADC产生中断时,读取寄存器中的x方向上的AD值与y方向的AD值,还可以读取z方向上的压力值,最终通过input_report_abs上报数据。
status = tscadc_readl(ts_dev, TSCADC_REG_IRQSTATUS);
if (status & TSCADC_IRQENB_FIFO1THRES) {
fifo0count = tscadc_readl(ts_dev, TSCADC_REG_FIFO0CNT);
fifo1count = tscadc_readl(ts_dev, TSCADC_REG_FIFO1CNT);
for (i = 0; i < (fifo0count-1); i++) {
readx1 = tscadc_readl(ts_dev, TSCADC_REG_FIFO0);
readx1 = readx1 & 0xfff;
if (readx1 > prev_val_x)
cur_diff_x = readx1 - prev_val_x;
else
cur_diff_x = prev_val_x - readx1;
if (cur_diff_x < prev_diff_x) {
prev_diff_x = cur_diff_x;
val_x = readx1;
}
prev_val_x = readx1;
// pr_notice("prev_val_x :0x%x\n",prev_val_x);
ready1 = tscadc_readl(ts_dev, TSCADC_REG_FIFO1);
ready1 &= 0xfff;
if (ready1 > prev_val_y)
cur_diff_y = ready1 - prev_val_y;
else
cur_diff_y = prev_val_y - ready1;
if (cur_diff_y < prev_diff_y) {
prev_diff_y = cur_diff_y;
val_y = ready1;
}
prev_val_y = ready1;
// pr_notice("prev_val_y :0x%x\n",prev_val_y);
}
/* Calibrate absolute value of x and y co-ordinate */
#ifdef USER_TN92
val_x = ts_dev->x_max -
((ts_dev->x_max * (val_x - AM335X_TS_XMIN)) /
(AM335X_TS_XMAX - AM335X_TS_XMIN));
#if 0
val_y = ts_dev->y_max -
((ts_dev->y_max * (val_y - AM335X_TS_YMIN)) /
(AM335X_TS_YMAX - AM335X_TS_YMIN));
#endif
#endif
if (val_x > bckup_x) {
diffx = val_x - bckup_x;
diffy = val_y - bckup_y;
} else {
diffx = bckup_x - val_x;
diffy = bckup_y - val_y;
}
bckup_x = val_x;
bckup_y = val_y;
z1 = ((tscadc_readl(ts_dev, TSCADC_REG_FIFO0)) & 0xfff);
z2 = ((tscadc_readl(ts_dev, TSCADC_REG_FIFO1)) & 0xfff);
if ((z1 != 0) && (z2 != 0)) {
/*
* cal pressure using formula
* Resistance(touch) = x plate resistance *
* x postion/4096 * ((z2 / z1) - 1)
*/
z = z2 - z1;
z *= val_x;
z *= ts_dev->x_plate_resistance;
z /= z1;
z = (z + 2047) >> 12;
// pr_notice("x_val:0x%x,y_val:0x%x,z:0x%x\n",val_x,val_y,z);
/*
* Sample found inconsistent by debouncing
* or pressure is beyond the maximum.
* Don't report it to user space.
*/
if (pen == 0) {
if ((diffx < 15) && (diffy < 15)
&& (z <= MAX_12BIT)) {
#ifdef USER_A70
input_report_abs(input_dev, ABS_X,
val_y);
input_report_abs(input_dev, ABS_Y,
val_x);
#else
input_report_abs(input_dev, ABS_X,
val_x);
input_report_abs(input_dev, ABS_Y,
val_y);
#endif
input_report_abs(input_dev, ABS_PRESSURE,
z);
input_report_key(input_dev, BTN_TOUCH,
1);
input_sync(input_dev);
}
}
}
irqclr |= TSCADC_IRQENB_FIFO1THRES;
}
udelay(315);
由此处分开,下边是关于触摸屏有没有被触摸而产生的中断,具体对于触摸屏中断的处理,需要对寄存器进行详细的分析。
status = tscadc_readl(ts_dev, TSCADC_REG_RAWIRQSTATUS);
if (status & TSCADC_IRQENB_PENUP) {
/* Pen up event */
fsm = tscadc_readl(ts_dev, TSCADC_REG_ADCFSM);
if (fsm == 0x10) {
pen = 1;
bckup_x = 0;
bckup_y = 0;
input_report_key(input_dev, BTN_TOUCH, 0);
input_report_abs(input_dev, ABS_PRESSURE, 0);
input_sync(input_dev);
} else {
pen = 0;
}
irqclr |= TSCADC_IRQENB_PENUP;
}
以下内容是处理完中断之后,执行的中断结束的处理情况。
irqclr |= TSCADC_IRQENB_HW_PEN;
tscadc_writel(ts_dev, TSCADC_REG_IRQSTATUS, irqclr);
/* check pending interrupts */
tscadc_writel(ts_dev, TSCADC_REG_IRQEOI, 0x0);
tscadc_writel(ts_dev, TSCADC_REG_SE, TSCADC_STPENB_STEPENB);
return IRQ_HANDLED;
}
中断函数中的寄存器使用情况
可以看到中断处理中用到了TSCADC_REG_IRQSTATUS、TSCADC_REG_FIFO0CNT、TSCADC_REG_FIFO1CNT、TSCADC_REG_FIFO0、TSCADC_REG_FIFO1、TSCADC_REG_RAWIRQSTATUS、TSCADC_REG_ADCFSM、TSCADC_REG_IRQEOI、TSCADC_REG_SE,要搞清楚整个中断函数的情况,需要将寄存器的作用以及使用情况进行梳理。
首先分析TSCADC_REG_IRQSTATUS
修改驱动代码,将中断状态寄存器数据打印出来。
以上图中数据是%x打印的数据,
其中ADC中断时读取的处理过的寄存器TSCADC_REG_IRQSTATUS的数据,而对于检测是否按下等寄存器是使用TSCADC_REG_RAWIRQSTATUS寄存器,这两个寄存器的区别需要进一步分析。 简单来说,raw版本的IRQ状态寄存器是未经处理的,至于为什么这么做,还不清楚。
以上是该寄存器的全部内容,可以看到关于每一位的描述都是一样的。所以关于每一位的具体作用已经在下表中给出。
在打印的数据中转化为二进制表现为以下情况,当事件处理完成之后,Pen_Up_event变为了0;
位数 | 名称 | 0X406 | 0X627 | 0X626(抬起时偶尔存在) | 作用 |
---|---|---|---|---|---|
10 | HW_Pen_Event_synchronous | 1 | 1 | 1 | 事件同步 |
9 | Pen_Up_event | 0 | 1 | 1 | 笔举事件(具体作用不详) |
8 | Out_of_Range | 0 | 0 | 0 | 超出范围 |
7 | FIFO1_Underflow | 0 | 0 | 0 | FIFO1读空 |
6 | FIFO1_Overrun | 0 | 0 | 0 | FIFO1写满 |
5 | FIFO1_Threshold | 0 | 1 | 1 | FIFO1保持 |
4 | FIFO0_Underflow | 0 | 0 | 0 | FIFO0读空 |
3 | FIFO0_Overrun | 0 | 0 | 0 | FIFO0写满 |
2 | FIFO0_Threshold | 1 | 1 | 1 | FIFO0保持 |
1 | End_of_Sequence | 1 | 1 | 1 | 序列结束 |
0 | HW_Pen_Event_asynchronous | 0 | 1 | 0 | 事件不同步 |
以上是关于中断的寄存器情况,
下面继续分析TSCADC_REG_FIFO0CNT、TSCADC_REG_FIFO1CNT两个寄存器。
status = tscadc_readl(ts_dev, TSCADC_REG_IRQSTATUS);
if (status & TSCADC_IRQENB_FIFO1THRES) {
fifo0count = tscadc_readl(ts_dev, TSCADC_REG_FIFO0CNT);
fifo1count = tscadc_readl(ts_dev, TSCADC_REG_FIFO1CNT);
for (i = 0; i < (fifo0count-1); i++) {
readx1 = tscadc_readl(ts_dev, TSCADC_REG_FIFO0);
readx1 = readx1 & 0xfff;
在此处代码可以看到关于CNT的用法,获取当前FIFO当中的字节数,然后按照这些字节数去获取数据。
这两个寄存器属于同一类型的就寄存器,保存了当前FIFO当中的字节数。
TSCADC_REG_FIFO0、TSCADC_REG_FIFO1为同一类型的寄存器,一起进行分析。
其中包括通道ID和当前转化的ADC数据。
至此基本已经分析完了关于FIFO的相关寄存器,FIFO的作用就是作为一个缓存,当触摸按键的时候,ADC是不断的采集信息的,但是CPU不可能总是及时的去读取AD值,CPU还有许多事情要做,当AD采集到的数值得不到处理,如果没有缓存机制的话,数据就会丢失,加上FIFO之后,数据就会源源不断的写入到FIFO当中,当CPU获取中断时,再进入FIFO进行数据的读取,这一次读取可能就会获取到大量的数据,然后FIFO被清空,ADC继续向其写入数据。可以有效的提高CPU效率,防止CPU频繁的读取数据。
TSCADC_REG_ADCFSM寄存器
status = tscadc_readl(ts_dev, TSCADC_REG_RAWIRQSTATUS);
if (status & TSCADC_IRQENB_PENUP) {
/* Pen up event */
fsm = tscadc_readl(ts_dev, TSCADC_REG_ADCFSM);
if (fsm == 0x10) {
pen = 1;
bckup_x = 0;
bckup_y = 0;
input_report_key(input_dev, BTN_TOUCH, 0);
input_report_abs(input_dev, ABS_PRESSURE, 0);
input_sync(input_dev);
} else {
pen = 0;
}
irqclr |= TSCADC_IRQENB_PENUP;
}
此处可以看到是作为一个步骤统计,查看当前执行到的步骤,具体每个步骤做的什么工作,需要进一步研究。
TSCADC_REG_IRQEOI
寄存器偏移地址为0x20,未在数据手册中找到该寄存器。代码中向该地址写入了0x00;具体作用不详,猜测是为下一次进入中断做准备。
TSCADC_REG_SE
处理完成后向该寄存器写入0x7FFF,就是将0-14位置1。
为下一次采集触摸屏信息进行准备。
以上完成关于中断函数中的寄存器处理分析。