#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/device.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-adc.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
/* debug macros */
#undef DEBUG
#ifdef DEBUG
#define DPRINTK( x... ) printk("s3c2410-ts: " ##x)
#else
#define DPRINTK( x... )
#endif
typedef struct {
unsigned short pressure;
unsigned short x;
unsigned short y;
unsigned short pad;
} TS_RET; //用于返回应用程序
typedef struct {
int xscale;
int xtrans;
int yscale;
int ytrans;
int xyswap;
} TS_CAL;
#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
#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; //环形缓冲0-15 head用于保存获取的转换数据 tail用于供函数读取若两者不相等则可读出
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() __raw_writel(DOWN_INT | XP_PULL_UP_EN | /
XP_AIN | XM_HIZ | YP_AIN | YM_GND | /
XP_PST(WAIT_INT_MODE), S3C2410_ADCTSC)
//保留位为0 上拉使能 XP连接连接AIN7 XM为高阻态 YP连接AIN5 YM接地 设置等待中断模式
#define wait_up_int() __raw_writel(UP_INT | XP_PULL_UP_EN | XP_AIN | XM_HIZ | /
YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE), S3C2410_ADCTSC)
//保留位为1 上拉使能 XP连接连接AIN7 XM为高阻态 YP连接AIN5 YM接地 设置等待中断模式
#define mode_x_axis() __raw_writel(XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | /
XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE), S3C2410_ADCTSC)
//XP外部电压 XM为0 YP连接AIN5 YM为高阻态 上拉禁止 对X轴坐标进行测量 普通ADC转换
#define mode_x_axis_n() __raw_writel(XP_EXTVLT | XM_GND | YP_AIN | YM_HIZ | /
XP_PULL_UP_DIS | XP_PST(NOP_MODE), S3C2410_ADCTSC)
#define mode_y_axis() __raw_writel(XP_AIN | XM_HIZ | YP_EXTVLT | YM_GND | /
XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE), S3C2410_ADCTSC)
#define start_adc_x() do {__raw_writel(PRESCALE_EN | PRSCVL(49) | /
ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | /
ADC_NORMAL_MODE, S3C2410_ADCCON); /
__raw_readl(S3C2410_ADCDAT0); } while(0)
//预分频器使能 预分频器数值=49 选择7通道 通过读取来启动A/D转换
#define start_adc_y() do {__raw_writel(PRESCALE_EN | PRSCVL(49) | /
ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | /
ADC_NORMAL_MODE, S3C2410_ADCCON); /
__raw_readl(S3C2410_ADCDAT1); } while(0)
#define disable_ts_adc() __raw_writel(__raw_readl(S3C2410_ADCCON)&~ADCCON_READ_START, S3C2410_ADCCON)
static int adc_state = 0;
static int x, y; /* touch screen coorinates */
static void tsEvent_raw(void)//保存转换数值
{
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);//缓冲头加1
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)//将结构体中的值读入ts_ret
{
spin_lock_irq(&(tsdev.lock));
ts_ret->x = BUF_TAIL.x;
ts_ret->y = BUF_TAIL.y;
ts_ret->pressure = BUF_TAIL.pressure;
tsdev.tail = INCBUF(tsdev.tail, MAX_TS_BUF);//尾指针加1
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);
return count;
} else {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
interruptible_sleep_on(&(tsdev.wq));//为阻塞时将当前进程加入等待队列并设置为可中断睡眠
if (signal_pending(current))//为信号唤醒时无效
return -ERESTARTSYS;
goto 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_wait(filp, &(tsdev.wq), wait);
return (tsdev.head == tsdev.tail) ? 0 : (POLLIN | POLLRDNORM); //处理fasync变更函数
}
static inline void start_ts_adc(void)
{
adc_state = 0;
mode_x_axis();//设置为x转换
start_adc_x();//开始x转换
}
static inline void s3c2410_get_XY(void)
{
if (adc_state == 0) { //表示进行x转化
adc_state = 1;
disable_ts_adc();
y = __raw_readl(S3C2410_ADCDAT0) & 0x3ff;//由于竖屏关系ADCDAT0,y实际为x轴
mode_y_axis();
start_adc_y(); //开始y转换
} else if (adc_state == 1) { //表示y转换
adc_state = 0;
disable_ts_adc();
x = __raw_readl(S3C2410_ADCDAT1) & 0x3ff;//由于竖屏关系ADCDAT1,x实际为y轴
DPRINTK("PEN DOWN: x: %08d, y: %08d/n", x, y);
wait_up_int(); //设为笔弹起产生中断
tsdev.penStatus = PEN_DOWN;//当XY都转换完后才为按下状态
tsEvent();
}
}
static irqreturn_t s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg)
{
#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();//x y转化完成后要获取坐标值
#ifdef HOOK_FOR_DRAG
else
s3c2410_get_XY();//拖动时按下状态继续读取值
#endif
spin_unlock_irq(&(tsdev.lock));
return IRQ_HANDLED;
}
static irqreturn_t s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg)
{
#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) {//因为笔按下产生
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));
return IRQ_HANDLED;
}
#ifdef HOOK_FOR_DRAG
static void ts_timer_handler(unsigned long data)
{
spin_lock_irq(&(tsdev.lock));
if (tsdev.penStatus == PEN_DOWN) {//在拖动过程中则继续以10ms进行采样周期获取坐标
start_ts_adc();
}
spin_unlock_irq(&(tsdev.lock));
}
#endif
static int s3c2410_ts_open(struct inode *inode, struct file *filp)
{
tsdev.head = tsdev.tail = 0;
tsdev.penStatus = PEN_UP;
#ifdef HOOK_FOR_DRAG
init_timer(&ts_timer);//初始化软件定时器
ts_timer.function = ts_timer_handler;//定时器处理函数完成10毫秒到时触摸笔还处于拖动状态中则开始adc转换
#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)
{
#ifdef HOOK_FOR_DRAG
del_timer(&ts_timer);
#endif
//MOD_DEC_USE_COUNT;
return 0;
}
static struct file_operations s3c2410_fops = {
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
static int s3c2410_ts_pm_callback(struct pm_dev *pm_dev, pm_request_t req,
void *data)
{
switch (req) {
case PM_SUSPEND:
tsEvent = tsEvent_dummy;
break;
case PM_RESUME:
tsEvent = tsEvent_raw;
wait_down_int();
break;
}
return 0;
}
#endif
static int __init s3c2410ts_probe(struct device *dev)
{
int ret;
tsEvent = tsEvent_dummy;
ret = register_chrdev(0, DEVICE_NAME, &s3c2410_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't get major number/n");
return ret;
}
tsMajor = ret;
printk("%s device driver MAJOR:%d/n", DEVICE_NAME, tsMajor);
/* 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);
__raw_writel(__raw_readl(S3C2410_GPGCON)|(0xff<<24), S3C2410_GPGCON);
/* Enable touch interrupt */
ret = request_irq(IRQ_ADC, 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_mk_dir("touchscreen");
devfs_mk_cdev(MKDEV(tsMajor, TSRAW_MINOR), S_IFCHR|S_IRUGO|S_IWUSR, "touchscreen/%d", 0);
#endif
#ifdef CONFIG_PM
#if 0
tsdev.pm_dev = pm_register(PM_GP_DEV, PM_USER_INPUT,
s3c2410_ts_pm_callback);
#endif
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, s3c2410_isr_adc);
adc_failed:
return ret;
}
static struct device_driver s3c2410ts_driver = {
.name = DEVICE_NAME,
.bus = &platform_bus_type,
.probe = s3c2410ts_probe,
#ifdef CONFIG_PM
.suspend = s3c2410ts_suspend,
.resume = s3c2410ts_resume,
#endif
};
static int __init s3c2410ts_init(void)
{
int ret;
printk("s3c2410ts init/n");
//return s3c2410ts_probe(NULL);
ret = driver_register(&s3c2410ts_driver);
if(ret)
printk("register %s driver failed, return code is %d/n", DEVICE_NAME, ret);
return ret;
}
static void __exit s3c2410ts_exit(void)
{
#ifdef CONFIG_DEVFS_FS
devfs_remove("touchscreen/%d", 0);
devfs_remove("touchscreen");
#endif
unregister_chrdev(tsMajor, DEVICE_NAME);
#ifdef CONFIG_PM
pm_unregister(tsdev.pm_dev);
#endif
free_irq(IRQ_ADC, s3c2410_isr_adc);
free_irq(IRQ_TC, s3c2410_isr_tc);
driver_unregister(&s3c2410ts_driver);
}
module_init(s3c2410ts_init);
module_exit(s3c2410ts_exit);
平台设备touchscreen
触摸屏驱动有两种方式编写,一是作为普通的字符设备,二是以平台设备为基础嵌套字符设备比如看门狗平台设备嵌套杂项设备,有关区别可以参考有关驱动程序。
触摸屏驱动设计流程:
在初始化函数中注册触摸屏字符设备,将相应的IO口设置为触摸屏控制功能,再为ADC和触摸中断申请中断,后设置触摸中断为按下中断功能并等待按下,由于配置了devfs所以要使用devfs_mk_dir和devfs_mk_cdev分别创建设备文件夹和字符设备文件
devfs_mk_dir("touchscreen"); //在/dev目录中创建一个 touchscreen目录
devfs_mk_cdev(MKDEV(tsMajor, TSRAW_MINOR), S_IFCHR|S_IRUGO|S_IWUSR, "touchscreen/%d", 0);
//在 touchscreen目录中创建名称为0的字符设备文件,其原型为#fmt,,,,,, ;即可以格式化多参数输入
"touchscreen/%d %s %c", 0,touchscreen,l,且设置笔为弹起状态即tsdev.penStatus = PEN_UP。
若笔按下则将触发等待中断调用s3c2410_isr_tc中断函数,如果是笔按下触发中断则设置为x坐标转换功能并启动x通道ADC转换;X坐标转化完成后将触发ADC中断调用s3c2410_isr_adc函数,此时由于笔仍然为按下状态所以在保存x坐标值后设置为Y坐标转换功能并启动Y通道ADC转换;Y坐标转化完成后将再次触发ADC中断调用s3c2410_isr_adc函数,此时由于笔仍然为按下状态所以在保存Y坐标值后设置笔为按下状态即tsdev.penStatus = PEN_DOWN和笔弹起触发中断功能。
在人的反应时间过后,如果笔弹起则触发等待中断调用s3c2410_isr_tc中断函数后设置笔为弹起状态,再设置触摸中断为按下中断功能并等待笔下一次按下;如果笔未弹起或处于拖曳过程中则等到定时器到时间后触发定时中断开始新的XY转换,此后周期性的进行XY转化直到笔弹起为止。
此坐标值保存在一个环形缓冲区中,这很巧妙。head用于保存转换数据,tail用于供读取。
在调试过程中经常出现编译错误:
1.在添加的全局函数前面加static,经常会出现未定义参数错误,经分析后得知static不仅仅只是表明其为静态函数或变量;static的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。注意此时, 对于外部(全局)变量, 不论是否有static限制, 它的存储区域都是在静态存储区, 生存期都是全局的. 此时的static只是起作用域限制作用, 限定作用域在本模块(文件)内部. 即对于全局函数或变量不可以加上static否则其不具有全局性。
2.gcc和g++编译器默认是不开启内联的,如果一个函数标为inline将会提示找不到函数。