diff options
author | Andy Green <andy@openmoko.com> | 2008-11-19 17:11:06 +0000 |
---|---|---|
committer | Andy Green <agreen@pads.home.warmcat.com> | 2008-11-19 17:11:06 +0000 |
commit | d082ca932a9d03105fe26881ee860e7ff7025a9d (patch) | |
tree | 04253fc0920a760167d873d1c850bdbe0863bbb2 /drivers/input/touchscreen | |
parent | 4c1c1b07611390857b93f515a284dae2de3115d7 (diff) |
test-touchscreen-median.patch
Signed-off-by: Andy Green <andy@openmoko.com>
Diffstat (limited to 'drivers/input/touchscreen')
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 30 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 3 | ||||
-rw-r--r-- | drivers/input/touchscreen/s3c2410_ts.c | 254 | ||||
-rw-r--r-- | drivers/input/touchscreen/ts_filter.c | 57 | ||||
-rw-r--r-- | drivers/input/touchscreen/ts_filter_mean.c | 170 | ||||
-rw-r--r-- | drivers/input/touchscreen/ts_filter_median.c | 210 |
6 files changed, 560 insertions, 164 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 26dac07e38e..ec28494584b 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,34 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +menuconfig TOUCHSCREEN_FILTER + boolean "Touchscreen Filtering" + depends on INPUT_TOUCHSCREEN + help + Select this to include kernel touchscreen filter support. The filters + can be combined in any order in your machine init and the parameters + for them can also be set there. + +if TOUCHSCREEN_FILTER + +config TOUCHSCREEN_FILTER_MEDIAN + bool "Median Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Median touchscreen filter, it's + highly effective if you data is noisy with occasional excursions. + +config TOUCHSCREEN_FILTER_MEAN + bool "Mean Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Mean touchscreen filter, it + can further improve decent quality data by removing jitter + +endif + config TOUCHSCREEN_ADS7846 tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" depends on SPI_MASTER @@ -75,6 +103,7 @@ config TOUCHSCREEN_S3C2410 tristate "Samsung S3C2410 touchscreen input driver" depends on ARCH_S3C2410 && INPUT && INPUT_TOUCHSCREEN select SERIO + select TOUCHSCREEN_FILTER help Say Y here if you have the s3c2410 touchscreen. @@ -395,3 +424,4 @@ config TOUCHSCREEN_TOUCHIT213 module will be called touchit213. endif + diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 53e5a52f225..1bbe8f5812f 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -32,3 +32,6 @@ wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_FILTER) += ts_filter.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEDIAN) += ts_filter_median.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEAN) += ts_filter_mean.o diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c index ceca0886a80..774b71ba1dd 100644 --- a/drivers/input/touchscreen/s3c2410_ts.c +++ b/drivers/input/touchscreen/s3c2410_ts.c @@ -37,8 +37,9 @@ * 2007-05-23: Harald Welte <laforge@openmoko.org> * - Add proper support for S32440 * - * 2008-06-18: Andy Green <andy@openmoko.com> - * - Outlier removal + * 2008-06-23: Andy Green <andy@openmoko.com> + * - removed averaging system + * - added generic Touchscreen filter stuff */ #include <linux/errno.h> @@ -59,6 +60,8 @@ #include <plat/regs-adc.h> +#include <linux/ts_filter.h> + /* For ts.dev.id.version */ #define S3C2410TSVERSION 0x0101 @@ -93,46 +96,16 @@ static char *s3c2410ts_name = "s3c2410 TouchScreen"; * Per-touchscreen data. */ -struct s3c2410ts_sample { - int x; - int y; -}; - struct s3c2410ts { struct input_dev *dev; - long xp; - long yp; - int count; - int shift; - int extent; /* 1 << shift */ - - /* the raw sample fifo is a lightweight way to track a running average - * of all taken samples. "running average" here means that it gives - * correct average for each sample, not only at the end of block of - * samples - */ - int excursion_filter_len; - struct s3c2410ts_sample *raw_sample_fifo; - int head_raw_fifo; - int tail_raw_fifo; - struct s3c2410ts_sample raw_running_avg; - int reject_threshold_vs_avg; - int flag_previous_exceeded_threshold; int flag_first_touch_sent; + struct ts_filter *tsf[MAX_TS_FILTER_CHAIN]; + int coords[2]; /* just X and Y for us */ }; static struct s3c2410ts ts; -static void __iomem *base_addr; -static void clear_raw_fifo(void) -{ - ts.head_raw_fifo = 0; - ts.tail_raw_fifo = 0; - ts.raw_running_avg.x = 0; - ts.raw_running_avg.y = 0; - ts.flag_previous_exceeded_threshold = 0; - ts.flag_first_touch_sent = 0; -} +static void __iomem *base_addr; static inline void s3c2410_ts_connect(void) @@ -169,39 +142,35 @@ static void touch_timer_fire(unsigned long data) } if (updown) { - if (ts.count != 0) { - ts.xp >>= ts.shift; - ts.yp >>= ts.shift; + + if (ts.tsf[0]) + (ts.tsf[0]->api->scale)(ts.tsf[0], &ts.coords[0]); #ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG - { - struct timeval tv; + { + struct timeval tv; - do_gettimeofday(&tv); - printk(DEBUG_LVL "T:%06d, X:%03ld, Y:%03ld\n", - (int)tv.tv_usec, ts.xp, ts.yp); - } + do_gettimeofday(&tv); + printk(DEBUG_LVL "T:%06d, X:%03ld, Y:%03ld\n", + (int)tv.tv_usec, ts.coords[0], ts.coords[1]); + } #endif - input_report_abs(ts.dev, ABS_X, ts.xp); - input_report_abs(ts.dev, ABS_Y, ts.yp); + input_report_abs(ts.dev, ABS_X, ts.coords[0]); + input_report_abs(ts.dev, ABS_Y, ts.coords[1]); - input_report_key(ts.dev, BTN_TOUCH, 1); - input_report_abs(ts.dev, ABS_PRESSURE, 1); - input_sync(ts.dev); - ts.flag_first_touch_sent = 1; - } - - ts.xp = 0; - ts.yp = 0; - ts.count = 0; + input_report_key(ts.dev, BTN_TOUCH, 1); + input_report_abs(ts.dev, ABS_PRESSURE, 1); + input_sync(ts.dev); writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { - ts.count = 0; + + if (ts.tsf[0]) + (ts.tsf[0]->api->clear)(ts.tsf[0]); input_report_key(ts.dev, BTN_TOUCH, 0); input_report_abs(ts.dev, ABS_PRESSURE, 0); @@ -237,103 +206,35 @@ static irqreturn_t stylus_updown(int irq, void *dev_id) return IRQ_HANDLED; } - static irqreturn_t stylus_action(int irq, void *dev_id) { - unsigned long x; - unsigned long y; - int length = (ts.head_raw_fifo - ts.tail_raw_fifo) & (ts.extent - 1); - int scaled_avg_x; - int scaled_avg_y; - - x = readl(base_addr + S3C2410_ADCDAT0) & S3C2410_ADCDAT0_XPDATA_MASK; - y = readl(base_addr + S3C2410_ADCDAT1) & S3C2410_ADCDAT1_YPDATA_MASK; - - if (!length) - goto store_sample; - - scaled_avg_x = ts.raw_running_avg.x / length; - scaled_avg_y = ts.raw_running_avg.y / length; - - /* we appear to accept every sample into both the running average FIFO - * and the summing average. BUT, if the last sample crossed a - * machine-set threshold, each time we do a beauty contest - * on the new sample comparing if it is closer to the running - * average and the previous sample. If it is closer to the previous - * suspicious sample, we assume the change is real and accept both - * if the new sample has returned to being closer to the average than - * the previous sample, we take the previous sample as an excursion - * and overwrite it in both the running average and summing average. + /* grab the ADC results */ + ts.coords[0] = readl(base_addr + S3C2410_ADCDAT0) & + S3C2410_ADCDAT0_XPDATA_MASK; + ts.coords[1] = readl(base_addr + S3C2410_ADCDAT1) & + S3C2410_ADCDAT1_YPDATA_MASK; + + if (!ts.tsf[0]) /* filtering is disabled then use raw directly */ + goto real_sample; + + /* send it to the chain of filters */ + if ((ts.tsf[0]->api->process)(ts.tsf[0], &ts.coords[0])) + goto real_sample; + + /* + * no real sample came out of processing yet, + * get another raw result to feed it */ - - if (ts.flag_previous_exceeded_threshold) - /* new one closer to "nonconformist" previous, or average? - * Pythagoras? Who? Don't need it because large excursion - * will be accounted for correctly this way - */ - if ((abs(x - scaled_avg_x) + abs(y - scaled_avg_y)) < - (abs(x - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].x) + - abs(y - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].y))) { - /* it's closer to average, reject previous as a one- - * shot excursion, by overwriting it - */ - ts.xp += x - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].x; - ts.yp += y - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].y; - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].x = x; - ts.raw_sample_fifo[(ts.head_raw_fifo - 1) & - (ts.extent - 1)].y = y; - /* no new sample: replaced previous, so we are done */ - goto completed; - } - /* else it was closer to nonconformist previous: it's likely - * a genuine consistent move then. - * Keep previous and add new guy. - */ - - if ((x >= scaled_avg_x - ts.reject_threshold_vs_avg) && - (x <= scaled_avg_x + ts.reject_threshold_vs_avg) && - (y >= scaled_avg_y - ts.reject_threshold_vs_avg) && - (y <= scaled_avg_y + ts.reject_threshold_vs_avg)) - ts.flag_previous_exceeded_threshold = 0; - else - ts.flag_previous_exceeded_threshold = 1; - -store_sample: - ts.xp += x; - ts.yp += y; - ts.count++; - - /* remove oldest sample from avg when we have full pipeline */ - if (((ts.head_raw_fifo + 1) & (ts.extent - 1)) == ts.tail_raw_fifo) { - ts.raw_running_avg.x -= ts.raw_sample_fifo[ts.tail_raw_fifo].x; - ts.raw_running_avg.y -= ts.raw_sample_fifo[ts.tail_raw_fifo].y; - ts.tail_raw_fifo = (ts.tail_raw_fifo + 1) & (ts.extent - 1); - } - /* always add current sample to fifo and average */ - ts.raw_sample_fifo[ts.head_raw_fifo].x = x; - ts.raw_sample_fifo[ts.head_raw_fifo].y = y; - ts.raw_running_avg.x += x; - ts.raw_running_avg.y += y; - ts.head_raw_fifo = (ts.head_raw_fifo + 1) & (ts.extent - 1); - -completed: - if (ts.count >= (1 << ts.shift)) { - mod_timer(&touch_timer, jiffies + 1); - writel(WAIT4INT(1), base_addr+S3C2410_ADCTSC); - goto bail; - } - writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, - base_addr+S3C2410_ADCTSC); - writel(readl(base_addr+S3C2410_ADCCON) | - S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); + base_addr + S3C2410_ADCTSC); + writel(readl(base_addr + S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, + base_addr + S3C2410_ADCCON); + return IRQ_HANDLED; + +real_sample: + mod_timer(&touch_timer, jiffies + 1); + writel(WAIT4INT(1), base_addr + S3C2410_ADCTSC); -bail: return IRQ_HANDLED; } @@ -348,6 +249,9 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) int rc; struct s3c2410_ts_mach_info *info; struct input_dev *input_dev; + int ret = 0; + + dev_info(&pdev->dev, "Starting\n"); info = (struct s3c2410_ts_mach_info *)pdev->dev.platform_data; @@ -375,7 +279,8 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) base_addr = ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) { dev_err(&pdev->dev, "Failed to remap register block\n"); - return -ENOMEM; + ret = -ENOMEM; + goto bail0; } @@ -390,7 +295,6 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) else writel(0, base_addr+S3C2410_ADCCON); - /* Initialise registers */ if ((info->delay & 0xffff) > 0) writel(info->delay & 0xffff, base_addr + S3C2410_ADCDLY); @@ -403,7 +307,8 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) if (!input_dev) { dev_err(&pdev->dev, "Unable to allocate the input device\n"); - return -ENOMEM; + ret = -ENOMEM; + goto bail1; } ts.dev = input_dev; @@ -420,28 +325,33 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) ts.dev->id.product = 0xBEEF; ts.dev->id.version = S3C2410TSVERSION; - ts.shift = info->oversampling_shift; - ts.extent = 1 << info->oversampling_shift; - ts.reject_threshold_vs_avg = info->reject_threshold_vs_avg; - ts.excursion_filter_len = 1 << info->excursion_filter_len_bits; + /* create the filter chain set up for the 2 coordinates we produce */ + ret = ts_filter_create_chain( + (struct ts_filter_api **)&info->filter_sequence, + &info->filter_config, ts.tsf, ARRAY_SIZE(ts.coords)); + if (ret) + dev_info(&pdev->dev, "%d filter(s) initialized\n", ret); + else /* this is OK, just means there won't be any filtering */ + dev_info(&pdev->dev, "Unfiltered output selected\n"); - ts.raw_sample_fifo = kmalloc(sizeof(struct s3c2410ts_sample) * - ts.excursion_filter_len, GFP_KERNEL); - clear_raw_fifo(); + if (!ts.tsf[0]) + dev_info(&pdev->dev, "No filtering\n"); /* Get irqs */ if (request_irq(IRQ_ADC, stylus_action, IRQF_SAMPLE_RANDOM, "s3c2410_action", ts.dev)) { dev_err(&pdev->dev, "Could not allocate ts IRQ_ADC !\n"); iounmap(base_addr); - return -EIO; + ret = -EIO; + goto bail3; } if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c2410_action", ts.dev)) { dev_err(&pdev->dev, "Could not allocate ts IRQ_TC !\n"); free_irq(IRQ_ADC, ts.dev); iounmap(base_addr); - return -EIO; + ret = -EIO; + goto bail4; } dev_info(&pdev->dev, "successfully loaded\n"); @@ -453,10 +363,25 @@ static int __init s3c2410ts_probe(struct platform_device *pdev) free_irq(IRQ_ADC, ts.dev); clk_disable(adc_clock); iounmap(base_addr); - return -EIO; + ret = -EIO; + goto bail5; } return 0; + +bail5: + disable_irq(IRQ_TC); +bail4: + disable_irq(IRQ_ADC); +bail3: + ts_filter_destroy_chain(ts.tsf); + + input_unregister_device(ts.dev); +bail1: + iounmap(base_addr); +bail0: + + return ret; } static int s3c2410ts_remove(struct platform_device *pdev) @@ -472,11 +397,11 @@ static int s3c2410ts_remove(struct platform_device *pdev) adc_clock = NULL; } - kfree(ts.raw_sample_fifo); - input_unregister_device(ts.dev); iounmap(base_addr); + ts_filter_destroy_chain(ts.tsf); + return 0; } @@ -503,7 +428,8 @@ static int s3c2410ts_resume(struct platform_device *pdev) clk_enable(adc_clock); mdelay(1); - clear_raw_fifo(); + if (ts.tsf[0]) + (ts.tsf[0]->api->clear)(ts.tsf[0]); enable_irq(IRQ_ADC); enable_irq(IRQ_TC); diff --git a/drivers/input/touchscreen/ts_filter.c b/drivers/input/touchscreen/ts_filter.c new file mode 100644 index 00000000000..f8b2b2f5846 --- /dev/null +++ b/drivers/input/touchscreen/ts_filter.c @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008 Andy Green <andy@openmoko.com> + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/ts_filter.h> + +int ts_filter_create_chain(struct ts_filter_api **api, void **config, + struct ts_filter **list, int count_coords) +{ + int count = 0; + struct ts_filter *last = NULL; + + if (!api) + return 0; + + while (*api && (count < MAX_TS_FILTER_CHAIN)) { + *list = ((*api)->create)(*config++, count_coords); + if (!*list) { + printk(KERN_ERR "Filter %d failed init\n", count); + return count; + } + (*list)->api = (struct ts_filter_api *)*api++; + if (last) + last->next = *list; + last = *list; + list++; + count++; + } + + return count; +} +EXPORT_SYMBOL_GPL(ts_filter_create_chain); + +void ts_filter_destroy_chain(struct ts_filter **list) +{ + while (*list) { + ((*list)->api->destroy)(*list); + list++; + } +} +EXPORT_SYMBOL_GPL(ts_filter_destroy_chain); diff --git a/drivers/input/touchscreen/ts_filter_mean.c b/drivers/input/touchscreen/ts_filter_mean.c new file mode 100644 index 00000000000..b589bee826f --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_mean.c @@ -0,0 +1,170 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008 Andy Green <andy@openmoko.com> + * + * + * Mean has no effect if the samples are changing by more that the + * threshold set by averaging_threshold in the configuration. + * + * However while samples come in that don't go outside this threshold from + * the last reported sample, Mean replaces the samples with a simple mean + * of a configurable number of samples (set by bits_filter_length in config, + * which is 2^n, so 5 there makes 32 sample averaging). + * + * Mean works well if the input data is already good quality, reducing + / - 1 + * sample jitter when the stylus is still, or moving very slowly, without + * introducing abrupt transitions or reducing ability to follow larger + * movements. If you set the threshold higher than the dynamic range of the + * coordinates, you can just use it as a simple mean average. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/ts_filter_mean.h> + +static void ts_filter_mean_clear(struct ts_filter *tsf) +{ + struct ts_filter_mean *tsfs = (struct ts_filter_mean *)tsf; + int n; + + for (n = 0; n < tsfs->tsf.count_coords; n++) { + tsfs->fhead[n] = 0; + tsfs->ftail[n] = 0; + tsfs->lowpass[n] = 0; + } + + if (tsf->next) /* chain */ + (tsf->next->api->clear)(tsf->next); +} + +static struct ts_filter *ts_filter_mean_create(void *config, int count_coords) +{ + int *p; + int n; + struct ts_filter_mean *tsfs = kzalloc( + sizeof(struct ts_filter_mean), GFP_KERNEL); + + if (!tsfs) + return NULL; + + BUG_ON((count_coords < 1) || (count_coords > MAX_TS_FILTER_COORDS)); + tsfs->tsf.count_coords = count_coords; + + tsfs->config = (struct ts_filter_mean_configuration *)config; + + tsfs->config->extent = 1 << tsfs->config->bits_filter_length; + BUG_ON((tsfs->config->extent > 256) || (!tsfs->config->extent)); + + p = kmalloc(tsfs->config->extent * sizeof(int) * count_coords, + GFP_KERNEL); + if (!p) + return NULL; + + for (n = 0; n < count_coords; n++) { + tsfs->fifo[n] = p; + p += tsfs->config->extent; + } + + if (!tsfs->config->averaging_threshold) + tsfs->config->averaging_threshold = 0xffff; /* always active */ + + ts_filter_mean_clear(&tsfs->tsf); + + printk(KERN_INFO " Created Mean ts filter len %d thresh %d\n", + tsfs->config->extent, tsfs->config->averaging_threshold); + + return &tsfs->tsf; +} + +static void ts_filter_mean_destroy(struct ts_filter *tsf) +{ + struct ts_filter_mean *tsfs = (struct ts_filter_mean *)tsf; + + if (!tsf) + return; + + if (tsf->next) /* chain */ + (tsf->next->api->destroy)(tsf->next); + + kfree(tsfs->fifo[0]); /* first guy has pointer from kmalloc */ + kfree(tsf); +} + +static void ts_filter_mean_scale(struct ts_filter *tsf, int *coords) +{ + if (tsf->next) /* chain */ + (tsf->next->api->scale)(tsf->next, coords); +} + +/* give us the raw sample data in x and y, and if we return 1 then you can + * get a filtered coordinate from tsm->x and tsm->y: if we return 0 you didn't + * fill the filter with samples yet. + */ + +static int ts_filter_mean_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_mean *tsfs = (struct ts_filter_mean *)tsf; + int n; + int len; + + for (n = 0; n < tsf->count_coords; n++) { + + /* has he moved far enough away that we should abandon current + * low pass filtering state? + */ + if ((coords[n] < (tsfs->reported[n] - + tsfs->config->averaging_threshold)) || + (coords[n] > (tsfs->reported[n] + + tsfs->config->averaging_threshold))) { + tsfs->fhead[n] = 0; + tsfs->ftail[n] = 0; + tsfs->lowpass[n] = 0; + } + + /* capture this sample into fifo and sum */ + tsfs->fifo[n][tsfs->fhead[n]++] = coords[n]; + if (tsfs->fhead[n] == tsfs->config->extent) + tsfs->fhead[n] = 0; + tsfs->lowpass[n] += coords[n]; + + /* adjust the sum into an average and use that*/ + len = (tsfs->fhead[n] - tsfs->ftail[n]) & + (tsfs->config->extent - 1); + coords[n] = (tsfs->lowpass[n] + (len >> 1)) / len; + tsfs->reported[n] = coords[n]; + + /* remove oldest sample if we are full */ + if (len == (tsfs->config->extent - 1)) { + tsfs->lowpass[n] -= tsfs->fifo[n][tsfs->ftail[n]++]; + if (tsfs->ftail[n] == tsfs->config->extent) + tsfs->ftail[n] = 0; + } + } + + if (tsf->next) /* chain */ + return (tsf->next->api->process)(tsf->next, coords); +// else + return 1; +} + +struct ts_filter_api ts_filter_mean_api = { + .create = ts_filter_mean_create, + .destroy = ts_filter_mean_destroy, + .clear = ts_filter_mean_clear, + .process = ts_filter_mean_process, + .scale = ts_filter_mean_scale, +}; diff --git a/drivers/input/touchscreen/ts_filter_median.c b/drivers/input/touchscreen/ts_filter_median.c new file mode 100644 index 00000000000..fe1b35f110b --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_median.c @@ -0,0 +1,210 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2008 Andy Green <andy@openmoko.com> + * + * + * Median averaging stuff. We sort incoming raw samples into an array of + * MEDIAN_SIZE length, discarding the oldest sample each time once we are full. + * We then return the sum of the middle three samples for X and Y. It means + * the final result must be divided by (3 * scaling factor) to correct for + * avoiding the repeated /3. + * + * This strongly rejects brief excursions away from a central point that is + * sticky in time compared to the excursion duration. + * + * Thanks to Dale Schumacher (who wrote some example code) and Carl-Daniel + * Halifinger who pointed out this would be a good method. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/ts_filter_median.h> + +static void ts_filter_median_insert(int *p, int sample, int count) +{ + int n; + + /* search through what we got so far to find where to put sample */ + for (n = 0; n < count; n++) + /* we met somebody bigger than us? */ + if (sample < p[n]) { + /* starting from the end, push bigger guys down one */ + for (count--; count >= n; count--) + p[count + 1] = p[count]; + p[n] = sample; /* and put us in place of first bigger */ + return; + } + + p[count] = sample; /* nobody was bigger than us, add us on the end */ +} + +static void ts_filter_median_del(int *p, int value, int count) +{ + int index; + + for (index = 0; index < count; index++) + if (p[index] == value) { + for (; index < count; index++) + p[index] = p[index + 1]; + return; + } +} + + +static void ts_filter_median_clear(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = (struct ts_filter_median *)tsf; + + tsfm->pos = 0; + tsfm->valid = 0; + + if (tsf->next) /* chain */ + (tsf->next->api->clear)(tsf->next); +} + +static struct ts_filter *ts_filter_median_create(void * conf, int count_coords) +{ + int *p; + int n; + struct ts_filter_median *tsfm = kzalloc(sizeof(struct ts_filter_median), + GFP_KERNEL); + + if (!tsfm) + return NULL; + + tsfm->config = (struct ts_filter_median_configuration *)conf; + BUG_ON((count_coords < 1) || (count_coords > MAX_TS_FILTER_COORDS)); + tsfm->tsf.count_coords = count_coords; + + tsfm->config->midpoint = (tsfm->config->extent >> 1) + 1; + + printk(KERN_INFO" Creating Median ts filter len %d depth %d dec %d\n", + tsfm->config->extent, count_coords, + tsfm->config->decimation_threshold); + + p = kmalloc(2 * count_coords * sizeof(int) * (tsfm->config->extent + 1), + GFP_KERNEL); + if (!p) { + kfree(tsfm); + return NULL; + } + + for (n = 0; n < count_coords; n++) { + tsfm->sort[n] = p; + p += tsfm->config->extent + 1; + tsfm->fifo[n] = p; + p += tsfm->config->extent + 1; + } + + ts_filter_median_clear(&tsfm->tsf); + + return &tsfm->tsf; +} + +static void ts_filter_median_destroy(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = (struct ts_filter_median *)tsf; + + if (tsf->next) /* chain */ + (tsf->next->api->destroy)(tsf->next); + + kfree(tsfm->sort[0]); /* first guy has pointer from kmalloc */ + kfree(tsf); +} + +static void ts_filter_median_scale(struct ts_filter *tsf, int *coords) +{ + int n; + + for (n = 0; n < tsf->count_coords; n++) + coords[n] = (coords[n] + 2) / 3; + + if (tsf->next) /* chain */ + (tsf->next->api->scale)(tsf->next, coords); +} + +/* give us the raw sample data coords, and if we return 1 then you can + * get a filtered coordinate from coords: if we return 0 you didn't + * fill all the filters with samples yet. + */ + +static int ts_filter_median_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_median *tsfm = (struct ts_filter_median *)tsf; + int n; + int movement = 1; + + for (n = 0; n < tsf->count_coords; n++) { + /* grab copy in insertion order to remove when oldest */ + tsfm->fifo[n][tsfm->pos] = coords[n]; + /* insert these samples in sorted order in the median arrays */ + ts_filter_median_insert(tsfm->sort[n], coords[n], tsfm->valid); + } + /* move us on in the fifo */ + if (++tsfm->pos == (tsfm->config->extent + 1)) + tsfm->pos = 0; + + /* we have finished a median sampling? */ + if (++tsfm->valid != tsfm->config->extent) + return 0; /* no valid sample to use */ + + /* discard the oldest sample in median sorted array */ + tsfm->valid--; + + /* sum the middle 3 in the median sorted arrays. We don't divide back + * down which increases the sum resolution by a factor of 3 until the + * scale API is called + */ + for (n = 0; n < tsfm->tsf.count_coords; n++) + /* perform the deletion of the oldest sample */ + ts_filter_median_del(tsfm->sort[n], tsfm->fifo[n][tsfm->pos], + tsfm->valid); + + tsfm->decimation_count--; + if (tsfm->decimation_count >= 0) + return 0; + + for (n = 0; n < tsfm->tsf.count_coords; n++) { + /* give the coordinate result from summing median 3 */ + coords[n] = tsfm->sort[n][tsfm->config->midpoint - 1] + + tsfm->sort[n][tsfm->config->midpoint] + + tsfm->sort[n][tsfm->config->midpoint + 1] + ; + + movement += abs(tsfm->last_issued[n] - coords[n]); + } + + if (movement > tsfm->config->decimation_threshold) /* fast */ + tsfm->decimation_count = tsfm->config->decimation_above; + else + tsfm->decimation_count = tsfm->config->decimation_below; + + memcpy(&tsfm->last_issued, coords, tsfm->tsf.count_coords); + + if (tsf->next) /* chain */ + return (tsf->next->api->process)(tsf->next, coords); + else + return 1; +} + +struct ts_filter_api ts_filter_median_api = { + .create = ts_filter_median_create, + .destroy = ts_filter_median_destroy, + .clear = ts_filter_median_clear, + .process = ts_filter_median_process, + .scale = ts_filter_median_scale, +}; |