自己写驱动程序之触摸屏
第一步:驱动程序大框
Input输入子系统驱动程序大框比较简单如下:
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <mach/irqs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <mach/fb.h>
#include <mach/regs-lcd.h>
#include <linux/dma-mapping.h>
//#include <linux/pm.h> //POWER Manager
#include <asm/gpio.h>
#include <linux/clk.h>
#include <asm/irq>
#include <plat/ts.h>
#include <plat/regs-adc.h>
#include <linux/serio.h>
static int s3cts_init(void)
{
return 0;
}
static void s3cts_exit(void)
{
}
module_init(s3cts_init);
module_exit(s3cts_exit);
MODULE_LICENSE("GPL");
第二步:补充初始化init()函数
大框如下:
/* 1. 分配一个input_dev结构体 */
如果不会写,搜一下s3c2410_ts.c,看看它是怎样分配的,之后照着自己写咯:
struct input_dev *s3cts_dev;
s3cts_dev= input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
s3cts_dev->evbit[0]=BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
/* 2.2 能产生这类操作里的哪些事件*/
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
上面可能不太好理解,所以用以下的写法比较简单易懂:
set_bit(BTN_TOUCH, s3cts_dev->keybit);
input_set_abs_params(s3cts_dev, ABS_X, 0, 0x3FF, 0, 0); //见注释1 这个是设置ad转换的x坐标
input_set_abs_params(s3cts_dev, ABS_Y, 0, 0x3FF, 0, 0); //这个是设置ad转换的y坐标
什么时候按下,什么时候抬起需要有标志来提醒:
input_set_abs_params(s3cts_dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 3. 注册 */
input_register_device(s3cts_dev);
/* 4. 硬件相关的操作 */
WOW,硬件操作来咯,在很多关于硬件操作部分的其实仔细看主芯片手册和硬件相关手册来操作没有那么难,只是麻烦:
1. 首先,如果要实现滑屏,长时间按住,拖拽等动作就要有时间控制,这个时候就需要时钟啦:
struct clk *clock;
clock = clk_get(NULL, "adc"); //见注释2
clk_enable(clock);
2. 其次就是设置寄存器咯,硬件嘛肯定要和寄存器相关了,之前已经相关寄存器截图,现在就是对其赋值啦!
与触摸相关的寄存器如上图所示,既然都是一起的就放在一个结构体中吧:
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
static volatile struct s3cts_regs *S3cts_regs;
S3cts_regs= ioremap(0x58000000, sizeof(struct s3cts_regs));
之后就是按部就班的设置啦:
S3cts_regs->adccon=(1<<14) | (49<<6); //49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz 0 1 49 000 0 0 0
查看S3C2440芯片手册如下图:
触摸屏的工作方式是循环工作,一直判断是否有中断产生,首先判断是否有按键按下(触摸按下),如果有按下,则发生INT_TC中断,此中断有什么作用呢?如下图:
它只有判断是否按下和抬起,要是想得到判断位置,判断滑屏等就需要另一个中断操作啦:INT_ADC,模数转换!
综上,就需要初始化两个中断啦:
一般申请irq都是request_irq()函数:request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev),但是具体该怎样写呢?参看S3C2410_ts.c中:irq_tc: The interrupt number for pen up/down interrupt,和Called when the IRQ_TC is fired for a pen up or down event.所以,搜索irq_tc或者IRQ_TC,WOW,内核中暂时没有可以利用的哦!这时候怎么办呢?这时候去内核文件夹中去看看,在drivers中有Input文件夹,触摸屏就是输入子系统的一个应用,所以猜测应该有相应触摸屏的,果不其然,在input中有touchscreen,之后挨个看看吧,瞅的像的就进去看看,最后在tsc2007.c中,我们发现了和我们驱动程序最相近的啦!所以好好利用一下啦!
err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq, IRQF_ONESHOT, client->dev.driver->name, ts);
照葫芦画瓢咯:
request_irq(IRQ_TC, pen_down_up_irq, IRQF_****,"ts_pen" , NULL); //注释3
request_irq(IRQ_ADC,adc_irq,IRQF_****,"adc_irq",NULL);
通过观察上面的IRQF_ONESHOT,所以它将两个中断声明在一个函数中,查看了注释3,对于我们的驱动程序来说,IRQF_SAMPLE_RANDOM比较好,所以:
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM,"ts_pen" , NULL);
request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,"adc_irq",NULL);
之后就进入第三步啦:
第三步:实现pen_down_up_irq()函数
查看tsc2007.c中,分析一下:
static irqreturn_t tsc2007_hard_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
if (!ts->get_pendown_state || likely(ts->get_pendown_state()))
return IRQ_WAKE_THREAD;
if (ts->clear_penirq)
ts->clear_penirq();
return IRQ_HANDLED;
}
可以知道,先是判断是否按下,在我们写的驱动程序中是直接和硬件打交道的,所以看看硬件手册可以得出:
adcdat0是判断x,adcdat1是判断y,其最高位是判断是否按下,所以就写一个就行:
static irqreturn_t pen_down_up_irq(int irq, void *handle)
{
if(S3cts_regs->adcdat0 & (1<<15)) //这里是指抬起
{
}
else //这里才是按下
{