2008年9月17日星期三

touch screen注册字符设备

这2天写了touch screen驱动,IC是tsc2007,用gpio口模拟i2c。不管IC和模拟。这篇就写linux2.6里添加的ts 字符设备,本来正规是在input device里添加的,但是方案商的keypad和ir也是用字符设备做的,所以就配合它这样了。

1>
模块初始化

注册字符设备
rc = register_chrdev(tsdev->major, tsdev->devName, &ts_fops);
if (rc < 0)
{
printk("touch screen driver could not get major number\n");
return rc;
}

IC初始化
cp2007_gpio_init(tsdev);

与某个中断源共享pen down中断,注册
/*PENIRQ connect to SAR, share interrupt, set ts register flag, then pendown irq can up the tsdev->sem*/
tsRegisterIrq(tsPenIrq);

起内核线程
kernel_thread(tsThread,tsdev,0);

2>
完成ts_fops,主要是read和poll,ts的open和release可以do nothing
static struct file_operations ts_fops = {
.owner = THIS_MODULE,
.read = ts_read,
.poll = ts_poll,
.open = ts_open,
.release = ts_close,
};

//虽然不是注册到input设备里,但是read上去的数据结构还是input。我们用的是nanox ads touchscreen,所以就用的这个结构
struct ts_event
{
short x;
short y;
short pressure;/*equal to Z*/
};

static ssize_t ts_read(struct file *filp, char __user *buf, size_t count, loff_t *ptr)
{
struct tsc2007* tsdev = filp->private_data;
int retval = -1;

/*nonblock*/
if ((tsdev->head == tsdev->tail) && (filp->f_flags & O_NONBLOCK))
return -EAGAIN;
/*block*/
//如果是阻塞方式读取,并且buffer里无数据,那么wait
retval = wait_event_interruptible(tsdev->wait, tsdev->head != tsdev->tail);
if (retval)
return retval;
/*if circle buf have event, copy to the buf, else return*/
while ((tsdev->head != tsdev->tail) && (retval + sizeof (struct ts_event) <= count))
{
if (copy_to_user ((void*)(buf + retval), (void*)&(tsdev->eventBuf[tsdev->tail]), sizeof (struct ts_event)))
return -EFAULT;
tsdev->tail = (tsdev->tail + 1) & (TS_EVENT_CNT- 1);
retval += sizeof (struct ts_event);
}
return retval;
}

//当应用用阻塞的方式来读取数据,也就是调用select,对应到内核就是poll,无数据返回0,有数据返回POLLIN POLLRDNORM
static unsigned int ts_poll(struct file * filp, poll_table *wait)
{
struct tsc2007* tsdev = filp->private_data;
poll_wait(filp, &tsdev->wait, wait);
return (tsdev->head == tsdev->tail) ? 0 : (POLLIN POLLRDNORM);
}

3>
实现ts线程,死循环,一直采样并把数据放到circle buffer里,并wakeup等待队列,让app read
当采样一直是pen down,就循环采,如果是pen up,那么被信号量阻塞,直到发生pen down中断,up信号量
一般应该用ostimer来做采样间隔,这里的ostimer给keypad单独用了,且ts又不需要手写,只是电屏而已,所以虽然schedule时间可能很长,不过能让pen down时cpu占用率不会提高很多
/*touch panel main loop*/
static int tsThread(struct tsc2007* tsdev)
{
//don't have ps/top command, no need to daemon it. name it
while(1)
{
schedule();
if(PEN_DOWN == tsProcess(tsdev))
continue;
sema_init(&(tsdev->sem),0);
down(&(tsdev->sem));
}
return 0;
}

irqreturn_t tsPenIrq(int irq, void *dev_id, struct pt_regs * regs)
{
up(&(ts2007_dev.sem));
return IRQ_NONE;
}

4>
ts采样后的处理
/*filter & average 3 point*/
unsigned char tsProcess(struct tsc2007* tsdev)
{
struct ts_event event;
unsigned char cnt;
unsigned char penstate;

memset((void*)&event, 0, sizeof(struct ts_event));
memset((void*)&(tsdev->pointBuf), 0, sizeof(tsdev->pointBuf));

//连续采样3个点
for(cnt = 0; cnt < TS_SAMP_P_CNT; cnt++)
penstate = tsGetPoint(&tsdev->pointBuf[cnt], tsdev);

//判断3个点之间的间距,如果超过规定值(认为是飞笔),或者是pen up就认为是发送pen up消息
if(TS_ERR == tsCheckGap(tsdev))
{
event.x = 0;
event.y = 0;
event.pressure = 0;
tsdev->upTimes++;
if(tsdev->upTimes > 2)
return PEN_UP;

//插入事件到circle buffer
tsInsertEvent(&event,tsdev);
}

//first pen down point, discard it
if(2 == tsdev->upTimes)
{
tsdev->upTimes = 0;
return PEN_DOWN;
}
else
tsdev->upTimes = 0;

//3个点取重心
for(cnt = 0; cnt < TS_SAMP_P_CNT; cnt++)
{
event.x += tsdev->pointBuf[cnt].x;
event.y += tsdev->pointBuf[cnt].y;
}

event.x /= TS_SAMP_P_CNT;
event.y /= TS_SAMP_P_CNT;
event.pressure = 100;

tsInsertEvent(&event,tsdev);

//唤醒block read里的等待队列
wake_up_interruptible(&tsdev->wait);
return PEN_DOWN;
}

void tsInsertEvent(struct ts_event* event, struct tsc2007* tsdev)
{
tsdev->head = (tsdev->head + 1) & (TS_EVENT_CNT- 1);
memcpy((void*)&(tsdev->eventBuf[tsdev->head]), (void*)event, sizeof(struct ts_event));
return;
}

顺便贴的函数
#define CHECK_GAP_XY(point1, point2) \
do{ \
if((!point1.pressure) (!point2.pressure)) \
{ \
tsdev->upTimes++; \
return TS_ERR; \
} \
if((abs(point1.x - point2.x) > MAX_GAP_X)(abs(point1.y - point2.y) > MAX_GAP_Y)) \
return TS_ERR; \
}while(0)

unsigned char tsCheckGap(struct tsc2007* tsdev)
{
CHECK_GAP_XY(tsdev->pointBuf[0], tsdev->pointBuf[1]);
CHECK_GAP_XY(tsdev->pointBuf[0], tsdev->pointBuf[2]);
CHECK_GAP_XY(tsdev->pointBuf[1], tsdev->pointBuf[2]);
return TS_OK;
}

模块数据结构
struct tsc2007
{
int major;/*ts dev major*/
char devName[DEVNAME_LENGTH];/*dev name to register*/
unsigned char devI2cAddr;/*cp2007 i2c addr*/
union cp2007Cmd cmd;
int i2cDelay;/*iic delay u seconds*/
int adcDelay;/*wait for adc complete u seconds*/
wait_queue_head_t wait;/*ts event wakeup for ts poll*/
unsigned char head;/*for eventBuf cycle buf*/
unsigned char tail;/*for eventBuf* cycle buf*/
struct ts_event eventBuf[TS_EVENT_CNT];/*ts event buf to be read for tsdev*/
unsigned char upTimes;/*pen up times*/
struct ts_event pointBuf[TS_SAMP_P_CNT];/*point sample,3 point choose 1*/
unsigned char PressThresh;/*pen down pressure threshold*/
struct semaphore sem;/*pen down semaphore*/
unsigned char sclgpio;
unsigned char sdagpio;
char fileName[DEVNAME_LENGTH];/*老板说要从文件里读出来模拟I2C的GPIO口是多少,说这样以后别的产品换IO口了不用重新编译内核, 多土啊*/
};

没有评论: