diff options
-rw-r--r-- | arch/arm/mach-s3c2410/include/mach/ts.h | 15 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/mach-gta01.c | 34 | ||||
-rw-r--r-- | arch/arm/mach-s3c2440/mach-gta02.c | 37 | ||||
-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 | ||||
-rw-r--r-- | include/linux/ts_filter.h | 53 | ||||
-rw-r--r-- | include/linux/ts_filter_mean.h | 34 | ||||
-rw-r--r-- | include/linux/ts_filter_median.h | 36 |
12 files changed, 745 insertions, 188 deletions
diff --git a/arch/arm/mach-s3c2410/include/mach/ts.h b/arch/arm/mach-s3c2410/include/mach/ts.h index 6fa75f6c0ab..ab4433f10bd 100644 --- a/arch/arm/mach-s3c2410/include/mach/ts.h +++ b/arch/arm/mach-s3c2410/include/mach/ts.h @@ -16,12 +16,17 @@ #ifndef __ASM_ARM_TS_H #define __ASM_ARM_TS_H +#include <linux/ts_filter.h> + struct s3c2410_ts_mach_info { - int delay; - int presc; - int oversampling_shift; - int excursion_filter_len_bits; - int reject_threshold_vs_avg; + int delay; + int presc; + /* array of pointers to filter APIs we want to use, in order + * ends on first NULL, all NULL is OK + */ + struct ts_filter_api *filter_sequence[MAX_TS_FILTER_CHAIN]; + /* array of configuration ints, one for each filter above */ + void *filter_config[MAX_TS_FILTER_CHAIN]; }; void set_s3c2410ts_info(struct s3c2410_ts_mach_info *hard_s3c2410ts_info); diff --git a/arch/arm/mach-s3c2410/mach-gta01.c b/arch/arm/mach-s3c2410/mach-gta01.c index 47377bd3da0..e718a810f42 100644 --- a/arch/arm/mach-s3c2410/mach-gta01.c +++ b/arch/arm/mach-s3c2410/mach-gta01.c @@ -505,19 +505,35 @@ static struct s3c2410_udc_mach_info gta01_udc_cfg = { .vbus_draw = gta01_udc_vbus_draw, }; + +/* touchscreen configuration */ + +static struct ts_filter_median_configuration gta01_ts_median_config = { + .extent = 31, + .decimation_below = 24, + .decimation_threshold = 8 * 3, + .decimation_above = 12, +}; + +static struct ts_filter_mean_configuration gta01_ts_mean_config = { + .bits_filter_length = 5, + .averaging_threshold = 12 +}; + static struct s3c2410_ts_mach_info gta01_ts_cfg = { .delay = 10000, - .presc = 50000000 / 1000000, /* 50 MHz PCLK / 1MHz */ - /* simple averaging, 2^n samples */ - .oversampling_shift = 5, - /* averaging filter length, 2^n */ - .excursion_filter_len_bits = 5, - /* flagged for beauty contest on next sample if differs from - * average more than this - */ - .reject_threshold_vs_avg = 2, + .presc = 0xff, /* slow as we can go */ + .filter_sequence = { + [0] = &ts_filter_median_api, + [1] = &ts_filter_mean_api, + }, + .filter_config = { + [0] = >a01_ts_median_config, + [1] = >a01_ts_mean_config, + }, }; + /* SPI */ static void gta01_jbt6k74_reset(int devidx, int level) diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c index 0ccb7fba850..6b44f1731b6 100644 --- a/arch/arm/mach-s3c2440/mach-gta02.c +++ b/arch/arm/mach-s3c2440/mach-gta02.c @@ -92,6 +92,9 @@ #include "../plat-s3c24xx/neo1973_pm_gps.h" +#include <linux/ts_filter_mean.h> +#include <linux/ts_filter_median.h> + /* arbitrates which sensor IRQ owns the shared SPI bus */ static spinlock_t motion_irq_lock; @@ -939,20 +942,34 @@ static struct s3c2410_udc_mach_info gta02_udc_cfg = { }; + +/* touchscreen configuration */ + +static struct ts_filter_median_configuration gta02_ts_median_config = { + .extent = 31, + .decimation_below = 28, + .decimation_threshold = 8 * 3, + .decimation_above = 12, +}; + +static struct ts_filter_mean_configuration gta02_ts_mean_config = { + .bits_filter_length = 3, + .averaging_threshold = 6 * 3, +}; + static struct s3c2410_ts_mach_info gta02_ts_cfg = { .delay = 10000, - .presc = 50000000 / 1000000, /* 50 MHz PCLK / 1MHz */ - /* simple averaging, 2^n samples */ - .oversampling_shift = 5, - /* averaging filter length, 2^n */ - .excursion_filter_len_bits = 5, - /* flagged for beauty contest on next sample if differs from - * average more than this - */ - .reject_threshold_vs_avg = 2, + .presc = 0xff, /* slow as we can go */ + .filter_sequence = { + [0] = &ts_filter_median_api, + [1] = &ts_filter_mean_api, + }, + .filter_config = { + [0] = >a02_ts_median_config, + [1] = >a02_ts_mean_config, + }, }; - /* SPI: LCM control interface attached to Glamo3362 */ static void gta02_jbt6k74_reset(int devidx, int level) 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, +}; diff --git a/include/linux/ts_filter.h b/include/linux/ts_filter.h new file mode 100644 index 00000000000..7262bbaf3dd --- /dev/null +++ b/include/linux/ts_filter.h @@ -0,0 +1,53 @@ +#ifndef __TS_FILTER_H__ +#define __TS_FILTER_H__ + +/* + * touchscreen filter + * + * median + * + * (c) 2008 Andy Green <andy@openmoko.com> + */ + +/* max filters you can chain up */ +#define MAX_TS_FILTER_CHAIN 4 +#define MAX_TS_FILTER_COORDS 6 + +struct ts_filter; + +/* operations that a filter can perform + */ +struct ts_filter_api { + struct ts_filter * (*create)(void *config, int count_coords); + void (*destroy)(struct ts_filter *filter); + void (*clear)(struct ts_filter *filter); + int (*process)(struct ts_filter *filter, int *coords); + void (*scale)(struct ts_filter *filter, int *coords); +}; + +/* this is the common part of all filters, the result + * we use this type as an otherwise opaque handle on to + * the actual filter. Therefore you need one of these + * at the start of your actual filter struct + */ + +struct ts_filter { + struct ts_filter *next; /* next in chain */ + struct ts_filter_api *api; /* operations to use for this object */ + int count_coords; + int coords[MAX_TS_FILTER_COORDS]; +}; + +/* + * helper to create a filter chain from array of API pointers and + * array of config ints... leaves pointers to created filters in list + * array and fills in ->next pointers to create the chain + */ + +extern int ts_filter_create_chain(struct ts_filter_api **api, void **config, + struct ts_filter **list, int count_coords); + +/* helper to destroy a whole chain from the list of filter pointers */ + +extern void ts_filter_destroy_chain(struct ts_filter **list); +#endif diff --git a/include/linux/ts_filter_mean.h b/include/linux/ts_filter_mean.h new file mode 100644 index 00000000000..46ff01a4c82 --- /dev/null +++ b/include/linux/ts_filter_mean.h @@ -0,0 +1,34 @@ +#ifndef __TS_FILTER_MEAN_H__ +#define __TS_FILTER_MEAN_H__ + +#include <linux/ts_filter.h> + +/* + * touchscreen filter + * + * mean + * + * (c) 2008 Andy Green <andy@openmoko.com> + */ + +struct ts_filter_mean_configuration { + int bits_filter_length; + int averaging_threshold; + + int extent; +}; + +struct ts_filter_mean { + struct ts_filter tsf; + struct ts_filter_mean_configuration *config; + + int reported[MAX_TS_FILTER_COORDS]; + int lowpass[MAX_TS_FILTER_COORDS]; + int *fifo[MAX_TS_FILTER_COORDS]; + int fhead[MAX_TS_FILTER_COORDS]; + int ftail[MAX_TS_FILTER_COORDS]; +}; + +extern struct ts_filter_api ts_filter_mean_api; + +#endif diff --git a/include/linux/ts_filter_median.h b/include/linux/ts_filter_median.h new file mode 100644 index 00000000000..d81642883f7 --- /dev/null +++ b/include/linux/ts_filter_median.h @@ -0,0 +1,36 @@ +#ifndef __TS_FILTER_MEDIAN_H__ +#define __TS_FILTER_MEDIAN_H__ + +#include <linux/ts_filter.h> + +/* + * touchscreen filter + * + * median + * + * (c) 2008 Andy Green <andy@openmoko.com> + */ + +struct ts_filter_median_configuration { + int extent; + int midpoint; + int decimation_threshold; + int decimation_above; + int decimation_below; +}; + +struct ts_filter_median { + struct ts_filter tsf; + struct ts_filter_median_configuration *config; + + int decimation_count; + int last_issued[MAX_TS_FILTER_COORDS]; + int valid; /* how many samples in the sort buffer are valid */ + int *sort[MAX_TS_FILTER_COORDS]; /* samples taken for median */ + int *fifo[MAX_TS_FILTER_COORDS]; /* samples taken for median */ + int pos; /* where we are in the fifo sample memory */ +}; + +extern struct ts_filter_api ts_filter_median_api; + +#endif |