花了三天时间,终于把这个驱动程序看得懂了个六七分,读到的心得都在文件里注释上。贴上来免得忘记。
/*
个人感觉驱动的工作过程是这样的,设置s3c2410的touchscreen和ADC的工作方式--当笔在触摸屏上按下的时候触发中断--接收--触发ADc的中断,转换后的数据被读到一个循环链表的结构体中--放入buffer缓冲区--然后驱动的read函数用copy_to_usercopy到用户空间,这样在main函数里打开文件,读取,就可以显示了。
驱动的参考书是周立功的ARM嵌入式linux系统构建与驱动开发范例
#include "config.h"
//laputa append for get bus clk freq
#define ADC_FREQ 2000000 // 2MHz AD convert freq 030918
static int PreScale_n; // PCLK / (PreScale_n+1) = ADConversion freq.
//laputa end 030918
#ifdef CONFIG_PM
#include
#endif
/* debug macros */
#undef DEBUG
#ifdef DEBUG
#define DPRINTK( x... ) printk("s3c2410-ts: " ##x)
#else
#define DPRINTK( x... )
#endif
#define PEN_UP 0
#define PEN_DOWN 1
#define PEN_FLEETING 2
#define MAX_TS_BUF 16 /* how many do we want to buffer */
#undef USE_ASYNC 1
#define DEVICE_NAME "s3c2410-ts"
#define TSRAW_MINOR 1
typedef struct { // 定义液晶屏设备结构体,成员有,笔的状态,缓冲区队列,头尾指针,循环队列,
//自旋锁
unsigned int penStatus; /* PEN_UP, PEN_DOWN, PEN_SAMPLE */
TS_RET buf[MAX_TS_BUF]; /* protect against overrun */
unsigned int head, tail; /* head and tail for queued events */
wait_queue_head_t wq;
spinlock_t lock;
#ifdef USE_ASYNC
struct fasync_struct *aq;
#endif
#ifdef CONFIG_PM
struct pm_dev *pm_dev;
#endif
} TS_DEV;
static TS_DEV tsdev;
#define BUF_HEAD (tsdev.buf[tsdev.head])
#define BUF_TAIL (tsdev.buf[tsdev.tail])
#define INCBUF(x,mod) ((++(x)) & ((mod) - 1))
static int tsMajor = 0;
static void (*tsEvent)(void);
#define HOOK_FOR_DRAG
#ifdef HOOK_FOR_DRAG
#define TS_TIMER_DELAY (HZ/100) /* 10 ms */
static struct timer_list ts_timer;
#endif
//设置控制寄存器的相应位
#define wait_down_int() { ADCTSC = DOWN_INT | XP_PULL_UP_EN | \
XP_AIN | XM_HIZ | YP_AIN | YM_GND | \
XP_PST(WAIT_INT_MODE); } ///*设置触摸屏处于等待中断模式,笔处于抬起状态,等待笔按下,,理解了,但为什么这样写呢,我觉得
这个应该在哪个头文件里有定义的*/
/*找到了这些头文件,在linux的内核中,
#define DOWN_INT (UD_SEN*0)
#define UP_INT (UD_SEN*1)
#define YM_SEN (1 << 7)
#define YM_HIZ (YM_SEN*0)
#define YM_GND (YM_SEN*1)
#define YP_SEN (1 << 6)
#define YP_EXTVLT (YP_SEN*0)
#define YP_AIN (YP_SEN*1)
#define XM_SEN (1 << 5)
#define XM_HIZ (XM_SEN*0)
#define XM_GND (XM_SEN*1)
#define XP_SEN (1 << 4)
#define XP_EXTVLT (XP_SEN*0)
#define XP_AIN (XP_SEN*1)
#define XP_PULL_UP (1 << 3)
#define XP_PULL_UP_EN (XP_PULL_UP*0)
#define XP_PULL_UP_DIS (XP_PULL_UP*1)
#define AUTO_PST (1 << 2)
#define CONVERT_MAN (AUTO_PST*0)
#define CONVERT_AUTO (AUTO_PST*1)
#define XP_PST(x) (x << 0)
*/
/*
/* ADC and Touch Screen Interface */
#define ADC_CTL_BASE 0x58000000
#define bADC_CTL(Nb) __REG(ADC_CTL_BASE + (Nb))
/* Offset */
#define oADCCON 0x00 /* R/W, ADC control register */
#define oADCTSC 0x04 /* R/W, ADC touch screen ctl reg */
#define oADCDLY 0x08 /* R/W, ADC start or interval delay reg */
#define oADCDAT0 0x0c /* R , ADC conversion data reg */
#define oADCDAT1 0x10 /* R , ADC conversion data reg */
/* Registers */
#define ADCCON bADC_CTL(oADCCON)
#define ADCTSC bADC_CTL(oADCTSC)
#define ADCDLY bADC_CTL(oADCDLY)
#define ADCDAT0 bADC_CTL(oADCDAT0)
#define ADCDAT1 bADC_CTL(oADCDAT1)
/* Field */
#define fADCCON_PRSCVL Fld(8, 6)
#define fADCCON_INPUT Fld(3, 3)
#define fTSC_XY_PST Fld(2, 0)
#define fADC_DELAY Fld(6, 0)
#define fDAT_UPDOWN Fld(1, 15)
#define fDAT_AUTO_PST Fld(1, 14)
#define fDAT_XY_PST Fld(2, 12)
#define fDAT_XPDATA Fld(10, 0)
#define fDAT_YPDATA Fld(10, 0)
/* ... */
#define ADC_IN0 0
#define ADC_IN1 1
#define ADC_IN2 2
#define ADC_IN3 3
#define ADC_IN4 4
#define ADC_IN5 5
#define ADC_IN6 6
#define ADC_IN7 7
#define ADC_BUSY 1
#define ADC_READY 0
#define NOP_MODE 0
#define X_AXIS_MODE 1
#define Y_AXIS_MODE 2
#define WAIT_INT_MODE 3
/* ... */
#define ADCCON_ECFLG (1 << 15)
#define PRESCALE_ENDIS (1 << 14)
#define PRESCALE_DIS (PRESCALE_ENDIS*0)
#define PRESCALE_EN (PRESCALE_ENDIS*1)
#if 0
#define PRSCVL(x) ({ FClrFld(ADCCON, fADCCON_PRSCVL); \
FInsrt((x), fADCCON_PRSCVL); })
#define ADC_INPUT(x) ({ FClrFld(ADCCON, fADCCON_INPUT); \
FInsrt((x), fADCCON_INPUT); })
#endif
#define PRSCVL(x) (x << 6)
#define ADC_INPUT(x) (x << 3)
#define ADCCON_STDBM (1 << 2) /* 1: standby mode, 0: normal mode */
#define ADC_NORMAL_MODE FClrBit(ADCCON, ADCCON_STDBM)
#define ADC_STANDBY_MODE (ADCCON_STDBM*1)
#define ADCCON_READ_START (1 << 1)
#define ADC_START_BY_RD_DIS FClrBit(ADCCON, ADCCON_READ_START)
#define ADC_START_BY_RD_EN (ADCCON_READ_START*1)
#define ADC_START (1 << 0)
*/
#define wait_up_int() { ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN | XM_HIZ | \
YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); } ///*设置触摸屏处于等待中断模式,笔处于按下状态,等待笔抬起*/
#define mode_x_axis() { ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | \
XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE); } /*对X坐标进行测量*/
#define mode_x_axis_n() { ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | \
XP_PULL_UP_DIS | XP_PST(NOP_MODE); } /*基本配置和上面相同,就是不进行x 坐标的转换*/
#define mode_y_axis() { ADCTSC = XP_AIN | XM_HIZ | YP_EXTVLT | YM_GND | \
XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE); }/*对Y 坐标开始进行测量*/
// laputa modi for 66MHz/(32+1) = 2MHz
// 1/(2MHz/5cycle) = 2.5uS
#define start_adc_x(n) { ADCCON = PRESCALE_EN | PRSCVL(n) | \
ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | \
ADC_NORMAL_MODE; \
ADCDAT0; }
#define start_adc_y(n) { ADCCON = PRESCALE_EN | PRSCVL(n) | \
ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | \
ADC_NORMAL_MODE; \
ADCDAT1; }
/*首先设置ADCCON 寄存器,开始转换x 坐标,因为在ADCCON 寄存器中,已经设置通
//过读取数据寄存器来启动转换,所以在执行ADCDAT0 宏时(该宏从ADCDATA0 寄存器中
//读取数据),A/D 转换器即开始转换数据。
宏ADCDAT0 定义为:
#define ADCDAT0 bADC_CTL(oADCDAT0)
。。。。。
#define bADC_CTL(Nb) __REG(ADC_CTL_BASE + (Nb))
*/
//laputa end 030917
#define disable_ts_adc() { ADCCON &= ~(ADCCON_READ_START); }
static int adc_state = 0;
static int x, y; /* touch screen coorinates */
static void tsEvent_raw(void) //这个函数好像也没头绪,判断出触摸笔的状态,落笔这从缓冲区队
//伍的头部读出坐标,起笔则设置xy坐标为0?
/*当触摸笔按下的时候记录状态,并将其xy坐标值放入缓冲队列,若笔为抬起状态,则记录状态,值xy坐标值
//为0,然后等待循环队列的中断*/
{
if (tsdev.penStatus == PEN_DOWN) {
BUF_HEAD.x = x;
BUF_HEAD.y = y;
BUF_HEAD.pressure = PEN_DOWN;
#ifdef HOOK_FOR_DRAG
ts_timer.expires = jiffies + TS_TIMER_DELAY;
add_timer(&ts_timer);
#endif
} else {
#ifdef HOOK_FOR_DRAG
del_timer(&ts_timer);
#endif
BUF_HEAD.x = 0;
BUF_HEAD.y = 0;
BUF_HEAD.pressure = PEN_UP;
}
tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF);
wake_up_interruptible(&(tsdev.wq));
#ifdef USE_ASYNC
if (tsdev.aq)
kill_fasync(&(tsdev.aq), SIGIO, POLL_IN);
#endif
#ifdef CONFIG_PM
pm_access(tsdev.pm_dev);
#endif
}
static int tsRead(TS_RET * ts_ret) //液晶屏读函数,这个是从循环队列里读出来
{
spin_lock_irq(&(tsdev.lock));//先锁上,防止其他的函数再使用此资源
ts_ret->x = BUF_TAIL.x;//然后读取缓冲队列的坐标值,赋值给ts_ret
ts_ret->y = BUF_TAIL.y;
ts_ret->pressure = BUF_TAIL.pressure;
tsdev.tail = INCBUF(tsdev.tail, MAX_TS_BUF);//队列尾向前移动一个,这个是循环队列的操作
spin_unlock_irq(&(tsdev.lock));//释放自旋锁资源
return sizeof(TS_RET);
}
static ssize_t s3c2410_ts_read(struct file *filp, char *buffer, size_t count, loff_t *ppos) //这个和上个有什么不同?这个是从系统空间读到用户空间
{
TS_RET ts_ret;
retry:
if (tsdev.head != tsdev.tail) {//当循环队列中的数据不为空的时候
int count;
count = tsRead(&ts_ret);
if (count) copy_to_user(buffer, (char *)&ts_ret, count);//copy to user这个函数是整
//个大函数的关键句子,是主要的函数
return count;
} else {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
interruptible_sleep_on(&(tsdev.wq));
if (signal_pending(current))
return -ERESTARTSYS;
goto retry;//若为空,则反复读取,程序在retry这里循环
}
return sizeof(TS_RET);
}
#ifdef USE_ASYNC
static int s3c2410_ts_fasync(int fd, struct file *filp, int mode) //这个是什么?同
//步???
{
return fasync_helper(fd, filp, mode, &(tsdev.aq));
}
#endif
static unsigned int s3c2410_ts_poll(struct file *filp, struct poll_table_struct *wait)
//这个poll也是文件操作的一个函数,具体什么判断缓冲队列是否为空?
{
poll_wait(filp, &(tsdev.wq), wait);
return (tsdev.head == tsdev.tail) ? 0 : (POLLIN | POLLRDNORM);
}
static inline void start_ts_adc(void) //开始adc转换?
{
adc_state = 0;
mode_x_axis();
start_adc_x(PreScale_n); //+- modify for ADC freq 030918
}
static inline void s3c2410_get_XY(void) //从转换好的ADCdata中读取数据
{
if (adc_state == 0) {
adc_state = 1;
disable_ts_adc();
y = (ADCDAT0 & 0x3ff);
mode_y_axis();
start_adc_y(PreScale_n); //+- modify for ADC freq 030918
} else if (adc_state == 1) {
adc_state = 0;
disable_ts_adc();
x = (ADCDAT1 & 0x3ff);
tsdev.penStatus = PEN_DOWN;
DPRINTK("PEN DOWN: x: %08d, y: %08d\n", x, y);
wait_up_int();
tsEvent();
}
}
static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg) //adc 中断服
//务函数,读取xy坐标值,也是先锁再读解锁
{
#if 0
DPRINTK("Occured Touch Screen Interrupt\n");
DPRINTK("SUBSRCPND = 0x%08lx\n", SUBSRCPND);
#endif
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_UP)
s3c2410_get_XY();
#ifdef HOOK_FOR_DRAG
else
s3c2410_get_XY();
#endif
spin_unlock_irq(&(tsdev.lock));
}
static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg) //液晶屏幕中
//断服务函数,先锁再读再解锁
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_UP) {
start_ts_adc();
} else {
tsdev.penStatus = PEN_UP;
DPRINTK("PEN UP: x: %08d, y: %08d\n", x, y);
wait_down_int();
tsEvent();
}
spin_unlock_irq(&(tsdev.lock));
}
#ifdef HOOK_FOR_DRAG
static void ts_timer_handler(unsigned long data)
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_DOWN) {
start_ts_adc();
}
spin_unlock_irq(&(tsdev.lock));
}
#endif
static int s3c2410_ts_open(struct inode *inode, struct file *filp) //open()函数,查
//看设备的就绪状态
{
tsdev.head = tsdev.tail = 0;
tsdev.penStatus = PEN_UP;
#ifdef HOOK_FOR_DRAG
init_timer(&ts_timer);
ts_timer.function = ts_timer_handler;
#endif
tsEvent = tsEvent_raw;
init_waitqueue_head(&(tsdev.wq));
MOD_INC_USE_COUNT;
return 0;
}
static int s3c2410_ts_release(struct inode *inode, struct file *filp)//release()函
//数,卸载设备的时候执行
{
#ifdef HOOK_FOR_DRAG
del_timer(&ts_timer);
#endif
MOD_DEC_USE_COUNT; //设备驱动程序的使用次数计数。卸载设备的时候减一个
return 0;
}
static struct file_operations s3c2410_fops = {//file oprerations struct
owner: THIS_MODULE,
open: s3c2410_ts_open,
read: s3c2410_ts_read,
release: s3c2410_ts_release,
#ifdef USE_ASYNC
fasync: s3c2410_ts_fasync,
#endif
poll: s3c2410_ts_poll,
};
void tsEvent_dummy(void) {}
#ifdef CONFIG_PM //pm是电源管理模块
static int s3c2410_ts_pm_callback(struct pm_dev *pm_dev, pm_request_t req,void *data) //这个Pm是什么东东,还不知道
{
switch (req) {
case PM_SUSPEND:
tsEvent = tsEvent_dummy;//dummy是什么都不干,void()
break;
case PM_RESUME:
tsEvent = tsEvent_raw;//raw是读取坐标值
wait_down_int();
break;
}
return 0;
}
#endif
#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_ts_dir, devfs_tsraw;
#endif
static int __init s3c2410_ts_init(void) //初始化驱动程序模块,linux内核在初始化的时候加
//驱动程序初始化模块
{
int ret;
tsEvent = tsEvent_dummy;
// laputa for touchscreen adc delay time setting
ADCDLY = 20000; // it is got from test
//laputa append for AD converting freq calculate
ret = s3c2410_get_bus_clk(GET_PCLK);
PreScale_n = (int)( ret / ADC_FREQ ) - 1 ;
//laputa end 030918
ret = register_chrdev(0, DEVICE_NAME, &s3c2410_fops);
//这是这个函数最重要的一句,向内核注册驱动程序。第一个参数是主设备号,第二个是设备名称,第三个
//是文件操作的结构提地址
if (ret < 0) {
printk(DEVICE_NAME " can't get major number\n");
return ret;
}
tsMajor = ret;
/* set gpio to XP, YM, YP and YM */
set_gpio_ctrl(GPIO_YPON);
set_gpio_ctrl(GPIO_YMON);
set_gpio_ctrl(GPIO_XPON);
set_gpio_ctrl(GPIO_XMON);
/* Enable touch interrupt */
ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, SA_INTERRUPT,
DEVICE_NAME, s3c2410_isr_adc);
if (ret) goto adc_failed;
ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT,
DEVICE_NAME, s3c2410_isr_tc);
if (ret) goto tc_failed;
/* Wait for touch screen interrupts */
wait_down_int();
#ifdef CONFIG_DEVFS_FS
devfs_ts_dir = devfs_mk_dir(NULL, "touchscreen", NULL);
devfs_tsraw = devfs_register(devfs_ts_dir, "0", DEVFS_FL_DEFAULT, // +- 031127 dev/touchscreen/0 for standard interface
tsMajor, TSRAW_MINOR, S_IFCHR | S_IRUSR | S_IWUSR,
&s3c2410_fops, NULL);
#endif
#ifdef CONFIG_PM
tsdev.pm_dev = pm_register(PM_DEBUG_DEV, PM_USER_INPUT,
s3c2410_ts_pm_callback);
#endif
printk(DEVICE_NAME " initialized\n");
return 0;
tc_failed:
free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);
adc_failed:
return ret;
}
static void __exit s3c2410_ts_exit(void) //从内核中卸载驱动程序,
{
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_tsraw);
devfs_unregister(devfs_ts_dir);
#endif
unregister_chrdev(tsMajor, DEVICE_NAME); //与向内核中注册驱动程序类似,这个是这个函数
//最重要的一句
#ifdef CONFIG_PM
pm_unregister(tsdev.pm_dev);
#endif
free_irq(IRQ_ADC_DONE, s3c2410_isr_adc);
free_irq(IRQ_TC, s3c2410_isr_tc);
}
module_init(s3c2410_ts_init); /×这两句告诉内核在卸载驱动程序的时候执行哪些函数,这两个函数就是我们上面所实现的。×/
module_exit(s3c2410_ts_exit);