1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2024-12-01 22:35:20 +02:00
openwrt-xburst/target/linux/xburst/files-2.6.27/drivers/input/touchscreen/jz_ts.c
Mirko Vogt dc3d3f1c49 yet another patchset - 2.6.27
it's basically also provided by ingenic and nativly based on 2.6.27,
adjusted to fit into the OpenWrt-environment
2009-10-28 03:13:11 +08:00

790 lines
18 KiB
C

/*
* JZ Touch Screen Driver
*
* Copyright (c) 2005 - 2009 Ingenic Semiconductor Inc.
*
* Author: Jason <xwang@ingenic.cn> 20090219
* Regen <lhhuang@ingenic.cn> 20090324 add adkey
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <asm/irq.h>
#include <asm/gpio.h>
#include <asm/jzsoc.h>
#define TS_NAME "jz-ts"
#define KEY_SCAN_INTERVAL 5
#define TS_SCAN_INTERVAL 0
/* from qwerty.kl of android */
#define DPAD_CENTER 232
#define DPAD_DOWN 108
#define DPAD_UP 103
#define DPAD_LEFT 105
#define DPAD_RIGHT 106
/* TS event status */
#define PENUP 0x00
#define PENDOWN 0x01
/* Sample times in one sample process */
#define SAMPLE_TIMES 3
/* Min pressure value. If less than it, filt the point.
* Mask it if it is not useful for you
*/
//#define MIN_PRESSURE 0x100
/* Max delta x distance between current point and last point. */
#define MAX_DELTA_X_OF_2_POINTS 200
/* Max delta x distance between current point and last point. */
#define MAX_DELTA_Y_OF_2_POINTS 120
/* Max delta between points in one sample process
* Verify method :
* (diff value / min value) * 100 <= MAX_DELTA_OF_SAMPLING
*/
#define MAX_DELTA_OF_SAMPLING 20
#define DIFF(a,b) (((a)>(b))?((a)-(b)):((b)-(a)))
#define MIN(a,b) (((a)<(b))?(a):(b))
/*
* TS deriver
*/
struct jz_ts_t {
int pendown_irq; // IRQ of pendown interrupt
int pen_is_down; // 1 = pen is down, 0 = pen is up
int irq_enabled;
struct timer_list acq_timer; // Timer for triggering acquisitions
#ifdef CONFIG_JZ_ADKEY
struct timer_list key_timer; // for adkey
int active_low; // for adkey's interrupt pin
#endif
wait_queue_head_t wait; // read wait queue
spinlock_t lock;
/* Following 4 members use to pass arguments from u-boot to tell us the ts data.
* But in Android we do not use them.
*/
/*
int minx, miny, maxx, maxy;
*/
int first_read;
char phys[32];
struct input_dev *input_dev;
};
/*
* TS Event type
*/
struct ts_event {
u16 status;
u16 x;
u16 y;
u16 pressure;
u16 pad;
};
#ifdef CONFIG_JZ_ADKEY
struct ad_keys_button {
int code; /* input event code */
int val; /* the ad value of the key */
int fuzz; /* the error(+-fuzz) allowed of the ad value of the key */
};
static struct ad_keys_button ad_buttons[] = {
{
.code = DPAD_LEFT,
.val = DPAD_LEFT_LEVEL,
.fuzz = 40,
},
{
.code = DPAD_DOWN,
.val = DPAD_DOWN_LEVEL,
.fuzz = 40,
},
{
.code = DPAD_UP,
.val = DPAD_UP_LEVEL,
.fuzz = 40,
},
{
.code = DPAD_CENTER,
.val = DPAD_CENTER_LEVEL,
.fuzz = 40,
},
{
.code = DPAD_RIGHT,
.val = DPAD_RIGHT_LEVEL,
.fuzz = 40,
},
};
#define KEY_NUM (sizeof(ad_buttons) / sizeof(struct ad_keys_button))
#endif
/************************************************************************/
/* SAR ADC OPS */
/************************************************************************/
typedef struct datasource {
u16 xbuf;
u16 ybuf;
u16 zbuf;
}datasource_t;
static datasource_t data_s;
static unsigned int p;
static DECLARE_WAIT_QUEUE_HEAD (sadc_wait_queue);
static int first_time = 0;
//static unsigned long last_x, last_y, last_p;
static unsigned int old_x, old_y;
extern unsigned int (*codec_read_battery)(void);
#if 0
static void reg_debug(void)
{
printk("\t####CTRL####################################################\n");
printk("\tPEND %s, ", REG_SADC_CTRL & SADC_CTRL_PENDM ? "masked" : "enabled");
printk("PENU %s, ", REG_SADC_CTRL & SADC_CTRL_PENUM ? "masked" : "enabled");
printk("TSRDY %s\n", REG_SADC_CTRL & SADC_CTRL_TSRDYM ? "masked" : "enabled");
printk("\t----STATE---------------------------------------------------\n");
printk("\tIRQ actived: %s, %s, %s\n",
REG_SADC_STATE & SADC_STATE_PEND ? "pen down" : " ",
REG_SADC_STATE & SADC_STATE_PENU ? "pen up " : " ",
REG_SADC_STATE & SADC_STATE_TSRDY ? "sample " : " ");
printk("\t############################################################\n");
}
#endif
/*
* set adc clock to 24MHz/div. A/D works at freq between 500KHz to 8MHz.
*/
static void sadc_init_clock(int div)
{
if (div < 2) div = 2;
if (div > 23) div = 23;
#if defined(CONFIG_SOC_JZ4740)
REG_SADC_CFG &= ~SADC_CFG_CLKDIV_MASK;
REG_SADC_CFG |= (div - 1) << SADC_CFG_CLKDIV_BIT;
#endif
#if defined(CONFIG_SOC_JZ4750) || defined(CONFIG_SOC_JZ4750D)
REG_SADC_ADCLK &= ~SADC_ADCLK_CLKDIV_MASK;
REG_SADC_ADCLK |= (div - 1) << SADC_ADCLK_CLKDIV_BIT;
REG_SADC_ADCLK &= ~SADC_ADCLK_CLKDIV_BIT;
REG_SADC_ADCLK |= 39 << SADC_ADCLK_CLKDIV_10_BIT; /* if div ==3,here is 39 */
#endif
}
static inline void sadc_start_sadcin(void)
{
REG_SADC_ENA |= SADC_ENA_SADCINEN;
}
static void sadc_start_pbat(void)
{
if (CFG_PBAT_DIV == 1)
REG_SADC_CFG |= SADC_CFG_PBAT_HIGH; /* full baterry voltage >= 2.5V */
else
REG_SADC_CFG |= SADC_CFG_PBAT_LOW; /* full baterry voltage < 2.5V */
REG_SADC_ENA |= SADC_ENA_PBATEN; /* Enable pbat adc */
}
static inline void ts_enable_pendown_irq(void)
{
REG_SADC_CTRL &= ~SADC_CTRL_PENDM;
}
static inline void ts_enable_penup_irq(void)
{
REG_SADC_CTRL &= ~SADC_CTRL_PENUM;
}
static inline void ts_disable_pendown_irq(void)
{
REG_SADC_CTRL |= SADC_CTRL_PENDM;
}
static inline void ts_disable_penup_irq(void)
{
REG_SADC_CTRL |= SADC_CTRL_PENUM;
}
static inline void sadc_enable_ts(void)
{
REG_SADC_ENA |= SADC_ENA_TSEN;
}
static inline void sadc_disable_ts(void)
{
REG_SADC_ENA &= ~SADC_ENA_TSEN;
}
static inline void sadc_start_ts(void)
{
REG_SADC_SAMETIME = 10; /* about 0.1 ms,you can change it */
REG_SADC_WAITTIME = 2; /* about 0.02 ms,you can change it */
REG_SADC_CFG &= ~(SADC_CFG_TS_DMA | SADC_CFG_XYZ_MASK | SADC_CFG_SNUM_MASK);
REG_SADC_CFG |= (SADC_CFG_EXIN | SADC_CFG_XYZ | SADC_CFG_SNUM_3);
REG_SADC_CTRL |= (SADC_CTRL_TSRDYM | SADC_CTRL_PBATRDYM | SADC_CTRL_PENUM |SADC_CTRL_SRDYM);
ts_enable_pendown_irq();
sadc_enable_ts();
}
/**
* Read the battery voltage
*/
unsigned int jz_read_battery(void)
{
unsigned int v;
unsigned int timeout = 0x3fff;
u16 pbat;
if(!(REG_SADC_STATE & SADC_STATE_PBATRDY) ==1)
sadc_start_pbat();
while(!(REG_SADC_STATE & SADC_STATE_PBATRDY) && --timeout)
;
pbat = REG_SADC_BATDAT;
v = pbat & 0x0fff;
REG_SADC_STATE = SADC_STATE_PBATRDY;
return v;
}
#define TSMAXX 3920
#define TSMAXY 3700
#define TSMINX 150
#define TSMINY 270
#define SCREEN_MAXX 479
#define SCREEN_MAXY 271
static unsigned long transform_to_screen_x(struct jz_ts_t *ts, unsigned long x )
{
/* Now we don't need u-boot to tell us the ts data. */
/*
if (ts->minx)
{
if (x < ts->minx) x = ts->minx;
if (x > ts->maxx) x = ts->maxx;
return (x - ts->minx) * SCREEN_MAXX / (ts->maxx - ts->minx);
}
else
{
*/
if (x < TSMINX) x = TSMINX;
if (x > TSMAXX) x = TSMAXX;
return (x - TSMINX) * SCREEN_MAXX / (TSMAXX - TSMINX);
/*
}
*/
}
static unsigned long transform_to_screen_y(struct jz_ts_t *ts, unsigned long y)
{
/* Now we don't need u-boot to tell us the ts data. */
/*
if (ts->miny)
{
if (y < ts->miny) y = ts->miny;
if (y > ts->maxy) y = ts->maxy;
return (ts->maxy - y) * SCREEN_MAXY / (ts->maxy - ts->miny);
}
else
{
*/
if (y < TSMINY) y = TSMINY;
if (y > TSMAXY) y = TSMAXY;
return (TSMAXY - y) * SCREEN_MAXY / (TSMAXY - TSMINY);
/*
}
*/
}
static inline void ts_data_ready(void)
{
REG_SADC_CTRL |= SADC_CTRL_TSRDYM;
}
static int adc_read(struct jz_ts_t *ts)
{
struct datasource *ds = &data_s;
u32 xybuf,z;
while (!(REG_SADC_STATE & SADC_STATE_TSRDY)) {
REG_SADC_CTRL &= ~SADC_CTRL_TSRDYM;
}
xybuf = REG_SADC_TSDAT;
ds->xbuf = (xybuf>>16) & 0x0fff;
ds->ybuf = (xybuf)& 0x0fff;
z = REG_SADC_TSDAT;
ds->zbuf = z& 0x0fff;
REG_SADC_STATE &= ~SADC_STATE_TSRDY;
return 0;
}
/*
* Acquire raw pen coodinate data and compute touch screen
* pressure resistance. Hold spinlock when calling.
*/
int adc_acquire_event(struct jz_ts_t *ts, struct ts_event *event)
{
unsigned int x_raw[SAMPLE_TIMES], y_raw[SAMPLE_TIMES], p_raw[SAMPLE_TIMES];
int i;
unsigned int avl_x, avl_y, avl_p, diff_x, diff_y;
struct datasource *ds = &data_s;
avl_x = avl_y = avl_p = 0;
for (i = 0; i < SAMPLE_TIMES; i++) {
if (adc_read(ts)) {
goto _INVALID_POINT;
}
x_raw[i] = ds->ybuf;
y_raw[i] = ds->xbuf;
p_raw[i] = ds->zbuf;
#ifdef MIN_PRESSURE
if (p_raw[i] < MIN_PRESSURE) {
goto _INVALID_POINT;
}
#endif
avl_x += x_raw[i];
avl_y += y_raw[i];
avl_p += p_raw[i];
#if 0
printk("x_raw = %u , y_raw = %u , z_raw = %u\n",x_raw[i],y_raw[i],p_raw[i]);
#endif
}
avl_x /= SAMPLE_TIMES;
avl_y /= SAMPLE_TIMES;
avl_p /= SAMPLE_TIMES;
/* Verify delta data. */
for (i = 1; i < SAMPLE_TIMES; i++)
{
if ( ((DIFF(x_raw[i],x_raw[i-1]) / MIN(x_raw[i],x_raw[i-1])) * 100) > MAX_DELTA_OF_SAMPLING )
goto _INVALID_POINT;
if ( ((DIFF(y_raw[i],y_raw[i-1]) / MIN(y_raw[i],y_raw[i-1])) * 100) > MAX_DELTA_OF_SAMPLING )
goto _INVALID_POINT;
if ( ((DIFF(p_raw[i],p_raw[i-1]) / MIN(p_raw[i],p_raw[i-1])) * 100) > MAX_DELTA_OF_SAMPLING )
goto _INVALID_POINT;
}
/* Compare with last point. */
if (ts->first_read) {
ts->first_read = 0;
old_x = avl_x;
old_y = avl_y;
}
diff_x = DIFF(old_x, avl_x);
diff_y = DIFF(old_y, avl_y);
if (diff_x >= MAX_DELTA_X_OF_2_POINTS || diff_y >= MAX_DELTA_Y_OF_2_POINTS)
goto _INVALID_POINT;
old_x = avl_x;
old_y = avl_y;
/* Android need it ... transform the raw value to screen coordinate. */
event->x = transform_to_screen_x(ts, avl_x);
event->y = transform_to_screen_y(ts, avl_y);
event->pressure = (u16)avl_p;
event->status = PENDOWN;
return 1;
_INVALID_POINT:
return 0;
}
/*
* Interrupt handler
*/
void ts_irq_callback(void)
{
u32 state;
state = REG_SADC_STATE;
if (!(REG_SADC_CTRL&SADC_CTRL_PENDM)&&(REG_SADC_STATE & SADC_STATE_PEND)) {
REG_SADC_STATE = SADC_STATE_PEND;
REG_SADC_STATE = SADC_STATE_PENU;
REG_SADC_CTRL |= SADC_CTRL_PENDM;
REG_SADC_CTRL &= ~SADC_CTRL_PENUM;
p = 1;
}
if (!(REG_SADC_CTRL&SADC_CTRL_PENUM)&&(REG_SADC_STATE & SADC_STATE_PENU)) {
REG_SADC_STATE = SADC_STATE_PENU;
REG_SADC_CTRL |= SADC_CTRL_PENUM;
REG_SADC_CTRL &= ~SADC_CTRL_PENDM;
p = 0;
}
first_time = 1; // first time to acquire sample
}
static inline int PenIsDown(void)
{
return p;
}
#ifdef CONFIG_JZ_ADKEY
/**
* Read the battery voltage
*/
static unsigned int read_sadcin(void)
{
unsigned int v;
unsigned int timeout = 0x3ff;
u16 val;
if(!(REG_SADC_STATE & SADC_STATE_SRDY))
sadc_start_sadcin();
while(!(REG_SADC_STATE & SADC_STATE_SRDY) && --timeout)
;
val = REG_SADC_SADDAT;
v = val & 0x0fff;
REG_SADC_STATE = SADC_STATE_SRDY;
return v;
}
static unsigned int key_scan(int ad_val)
{
int i;
for(i = 0; i<KEY_NUM; i++) {
if((ad_buttons[i].val + ad_buttons[i].fuzz >= ad_val) &&
(ad_val >=ad_buttons[i].val - ad_buttons[i].fuzz)) {
return ad_buttons[i].code;
}
}
return -1;
}
static void key_timer_callback(unsigned long data)
{
struct jz_ts_t *ts = (struct jz_ts_t *)data;
int state;
int active_low = ts->active_low;
int ad_val, code;
static int old_code;
spin_lock(&ts->lock);
state = __gpio_get_pin(GPIO_ADKEY_INT);
ad_val = read_sadcin();
if (active_low) {
if (state == 0) {
/* press down */
code = key_scan(ad_val);
old_code = code;
input_report_key(ts->input_dev, code, 1);
input_sync(ts->input_dev);
mod_timer(&ts->key_timer, jiffies + KEY_SCAN_INTERVAL);
} else {
/* up */
input_report_key(ts->input_dev, old_code, 0);
input_sync(ts->input_dev);
udelay(1000);
__gpio_as_irq_fall_edge(GPIO_ADKEY_INT);
}
} else {
if (state == 1) {
/* press down */
code = key_scan(ad_val);
old_code = code;
input_report_key(ts->input_dev, code, 1);
input_sync(ts->input_dev);
mod_timer(&ts->key_timer, jiffies + KEY_SCAN_INTERVAL);
} else {
/* up */
input_report_key(ts->input_dev, old_code, 0);
input_sync(ts->input_dev);
udelay(1000);
__gpio_as_irq_rise_edge(GPIO_ADKEY_INT);
}
}
spin_unlock(&ts->lock);
}
static irqreturn_t key_interrupt(int irq, void * dev_id)
{
struct jz_ts_t *ts = dev_id;
spin_lock(&ts->lock);
__gpio_ack_irq(GPIO_ADKEY_INT);
__gpio_as_input(GPIO_ADKEY_INT);
sadc_start_sadcin();
mod_timer(&ts->key_timer, jiffies + KEY_SCAN_INTERVAL);
spin_unlock(&ts->lock);
return IRQ_HANDLED;
}
#endif
/************************************************************************/
/* Touch Screen module */
/************************************************************************/
static int pen_is_down = 0;
static irqreturn_t pendown_interrupt(int irq, void * dev_id)
{
struct jz_ts_t *ts = dev_id;
spin_lock_irq(&ts->lock);
if (ts->irq_enabled)
ts->irq_enabled = 0;
else
ts->irq_enabled = 1;
if (pen_is_down)
pen_is_down = 0;
else
pen_is_down = 1;
/* callback routine to clear irq status */
ts_irq_callback();
if ( (pen_is_down == 0)){
del_timer(&ts->acq_timer);
ts->first_read = 0;
input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
/* Android need it ... */
input_report_key(ts->input_dev, BTN_TOUCH, 0);
input_sync(ts->input_dev);
} else { // pen_is_down == 1
ts->acq_timer.expires = jiffies + TS_SCAN_INTERVAL;
del_timer(&ts->acq_timer);
ts->first_read = 1;
add_timer(&ts->acq_timer);
}
spin_unlock_irq(&ts->lock);
return IRQ_HANDLED;
}
/*
* Raw X,Y,pressure acquisition timer function. It gets scheduled
* only while pen is down. Its duration between calls is the polling
* rate.
*/
static void
jz_acq_timer(unsigned long data)
{
struct jz_ts_t *ts = (struct jz_ts_t *)data;
struct ts_event event;
int pen_was_down = ts->pen_is_down;
spin_lock_irq(&ts->lock);
if (PenIsDown()) {
ts->pen_is_down = 1;
if (adc_acquire_event(ts, &event)) {// check event is valid or not?
input_report_abs(ts->input_dev, ABS_X, event.x);
input_report_abs(ts->input_dev, ABS_Y, event.y);
input_report_abs(ts->input_dev, ABS_PRESSURE, event.pressure);
/* Android need it ... */
input_report_key(ts->input_dev, BTN_TOUCH, 1);
input_sync(ts->input_dev);
}
// schedule next acquire
ts->acq_timer.expires = jiffies + TS_SCAN_INTERVAL;
del_timer(&ts->acq_timer);
add_timer(&ts->acq_timer);
} else {
if (!ts->irq_enabled) {
ts->irq_enabled = 1;
}
ts->pen_is_down = 0;
if (pen_was_down) {
input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
/* Android need it ... */
input_report_key(ts->input_dev, BTN_TOUCH, 0);
input_sync(ts->input_dev);
}
}
spin_unlock_irq(&ts->lock);
}
static struct jz_ts_t *jz_ts;
static int __init jz_ts_init(void)
{
struct input_dev *input_dev;
struct jz_ts_t *ts;
int error;
ts = jz_ts = kzalloc(sizeof(struct jz_ts_t), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev)
return -ENOMEM;
input_dev->name = "qwerty"; /* Set to 'qwerty' to load /system/usr/keychars/qwerty.kcm.bin by Android */
input_dev->phys = ts->phys;
/*
old:
input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
*/
/* For Android */
set_bit(EV_ABS, input_dev->evbit);
set_bit(ABS_X, input_dev->absbit);
set_bit(ABS_Y, input_dev->absbit);
set_bit(ABS_PRESSURE, input_dev->absbit);
set_bit(EV_KEY, input_dev->evbit);
set_bit(BTN_TOUCH, input_dev->keybit);
#ifdef CONFIG_JZ_ADKEY
set_bit(DPAD_CENTER, input_dev->keybit);
set_bit(DPAD_DOWN, input_dev->keybit);
set_bit(DPAD_UP, input_dev->keybit);
set_bit(DPAD_LEFT, input_dev->keybit);
set_bit(DPAD_RIGHT, input_dev->keybit);
#endif
input_set_abs_params(input_dev, ABS_X, 0, SCREEN_MAXX, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, SCREEN_MAXY, 0, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0);
input_set_drvdata(input_dev, ts);
error = input_register_device(input_dev);
strcpy(ts->phys, "input/ts0");
spin_lock_init(&ts->lock);
ts->input_dev = input_dev;
// Init ts acquisition timer function
init_timer(&ts->acq_timer);
ts->acq_timer.function = jz_acq_timer;
ts->acq_timer.data = (unsigned long)ts;
ts->irq_enabled = 1;
if (error) {
printk("Input device register failed !\n");
goto err_free_dev;
}
sadc_init_clock(6);
ts_disable_pendown_irq();
ts_disable_penup_irq();
error = request_irq(IRQ_SADC, pendown_interrupt, IRQF_DISABLED, TS_NAME, ts);
if (error) {
pr_err("unable to get PenDown IRQ %d", IRQ_SADC);
goto err_free_irq;
}
#ifdef CONFIG_JZ_ADKEY
// Init key acquisition timer function
init_timer(&ts->key_timer);
ts->key_timer.function = key_timer_callback;
ts->key_timer.data = (unsigned long)ts;
ts->active_low = ACTIVE_LOW_ADKEY;
error = request_irq(IRQ_GPIO_0 + GPIO_ADKEY_INT, key_interrupt, IRQF_DISABLED, TS_NAME, ts);
if (error) {
pr_err("unable to get AD KEY IRQ %d", IRQ_GPIO_0 + GPIO_ADKEY_INT);
goto err_free_irq;
}
__gpio_disable_pull(GPIO_ADKEY_INT);
if(ts->active_low)
__gpio_as_irq_fall_edge(GPIO_ADKEY_INT);
else
__gpio_as_irq_rise_edge(GPIO_ADKEY_INT);
#endif
sadc_start_ts();
printk("input: JZ Touch Screen registered.\n");
return 0;
err_free_irq:
free_irq(IRQ_SADC, ts);
#ifdef CONFIG_JZ_ADKEY
free_irq(IRQ_GPIO_0 + GPIO_ADKEY_INT, ts);
#endif
err_free_dev:
input_free_device(ts->input_dev);
kfree(ts);
return 0;
}
static void __exit jz_ts_exit(void)
{
free_irq(IRQ_SADC, jz_ts);
input_unregister_device(jz_ts->input_dev);
ts_disable_pendown_irq();
ts_disable_penup_irq();
sadc_disable_ts();
}
module_init(jz_ts_init);
module_exit(jz_ts_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("JZ TouchScreen Driver");
MODULE_AUTHOR("Jason <xwang@ingenic.com>");