The atmel tsadcc is a touchscreen/adc controller found on the AT91SAM9RL SoC. This driver provides basic support for this controller for use as a touchscreen. Some future improvements include suspend/resume capabilities and debugfs support. Signed-off-by: Justin Waters <justin.waters@xxxxxxxxxxx> --- drivers/input/touchscreen/Kconfig | 11 + drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/atmel_tsadcc.c | 375 ++++++++++++++++++++++++++++++ include/linux/atmel_tsadcc.h | 89 +++++++ 4 files changed, 476 insertions(+), 0 deletions(-) create mode 100644 drivers/input/touchscreen/atmel_tsadcc.c create mode 100644 include/linux/atmel_tsadcc.h diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 90e8e92..6e76eff 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -170,6 +170,17 @@ config TOUCHSCREEN_TOUCHWIN To compile this driver as a module, choose M here: the module will be called touchwin. +config TOUCHSCREEN_ATMEL_TSADCC + tristate "Atmel Touchscreen Interface" + help + Say Y here if you have a 4-wire touchscreen connected to the + ADC Controller on your Atmel SoC (such as the AT91SAM9RL). + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called atmel_tsadcc. + config TOUCHSCREEN_UCB1400 tristate "Philips UCB1400 touchscreen" select AC97_BUS diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 35d4097..3302e27 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -18,4 +18,5 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o +obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o diff --git a/drivers/input/touchscreen/atmel_tsadcc.c b/drivers/input/touchscreen/atmel_tsadcc.c new file mode 100644 index 0000000..b8a0984 --- /dev/null +++ b/drivers/input/touchscreen/atmel_tsadcc.c @@ -0,0 +1,375 @@ +/* + * Atmel TSADCC touchscreen sensor driver + * + * Copyright (c) 2008 TimeSys Corporation + * Copyright (c) 2008 Justin Waters + * + * Based on touchscreen code from Atmel Corporation + * and on the ads7846 driver by David Brownell. + * + * 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/hwmon.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <asm/irq.h> +#include <linux/atmel_tsadcc.h> + +/* + * This code has been heavily tested on the Atmel at91sam9rl-ek + */ + +/* These values are dependent upon the performance of the ADC. Ideally, + * These should be as close to the startup time and debounce time as + * possible (without going over). These assume an optimally setup ADC as + * per the electrical characteristics of the AT91SAM9RL tsadcc module + */ +#define TS_POLL_DELAY (1 * 1000000) /* ns delay before the first sample */ +#define TS_POLL_PERIOD (5 * 1000000) /* ns delay between samples */ + +/*--------------------------------------------------------------------------*/ + +static inline void atmel_tsadcc_getevent(struct atmel_tsadcc *ts) +{ + unsigned int regbuf; + + /* + * Calculate the X Coordinates + * x = (ATMEL_TSADCC_CDR3 * 1024) / ATMEL_TSADCC_CDR2 + */ + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_CDR2); + if (regbuf) + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_CDR3) * 1024 / regbuf; + ts->event.x = regbuf; + + /* + * Calculate the Y Coordinates + * y = (ATMEL_TSADCC_CDR1 * 1024) / ATMEL_TSADCC_CDR0 + */ + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_CDR0); + if (regbuf) + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_CDR1) * 1024 / regbuf; + ts->event.y = regbuf; + + dev_dbg (&ts->pdev->dev, "X=%08X Y=%08X\n", ts->event.x, ts->event.y); + + return; +} + +/*--------------------------------------------------------------------------*/ +/* + * Touchscreen Interrupts + * + * The TSADCC Touchscreen provides a number of hardware interrupts. For this + * driver, we enable the PENCNT and NOCNT interrupts, which signal an interrupt + * when the pen either makes or breaks contact with the screen. When the pen + * is detected, we enable a timer that triggers the ADC periodically and polls + * the results. The timer resets itself only as long as the pen is making + * contact. Once the pen is lifted, the timer is no longer refreshed and the + * button and pressure is reduced to zero. + */ +static enum hrtimer_restart atmel_tsadcc_timer(struct hrtimer *handle) +{ + struct atmel_tsadcc *ts = container_of(handle, struct atmel_tsadcc, timer); + unsigned long flags; + unsigned int status; + + spin_lock_irqsave(&ts->lock, flags); + + /* Read the status register */ + status = atmel_tsadcc_readreg(ATMEL_TSADCC_SR); + + /* If the data is ready, read it! */ + if(status & 0xF) + { + /* Read the LCDR Register to clear the DRDY bit */ + atmel_tsadcc_readreg(ATMEL_TSADCC_LCDR); + + dev_dbg (&ts->pdev->dev,"Status=0x%08X\n",status); + + /* Pen remains down */ + atmel_tsadcc_getevent(ts); + + input_report_abs(ts->input, ABS_X, ts->event.x); + input_report_abs(ts->input, ABS_Y, ts->event.y); + input_report_abs(ts->input, ABS_PRESSURE, 7500); + + input_sync(ts->input); + + if(ts->pendown) { + /* Make ADC perform another conversion */ + atmel_tsadcc_writereg(ATMEL_TSADCC_CR,2); + + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), + HRTIMER_MODE_REL); + } + } + + spin_unlock_irq(&ts->lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t atmel_tsadcc_irq(int irq, void *handle) +{ + struct atmel_tsadcc *ts = handle; + unsigned long flags; + unsigned int status; + + spin_lock_irqsave(&ts->lock, flags); + + /* Read the status register */ + status = atmel_tsadcc_readreg(ATMEL_TSADCC_SR); + + /* Read the LCDR Register to clear the DRDY bit */ + atmel_tsadcc_readreg(ATMEL_TSADCC_LCDR); + + dev_dbg (&ts->pdev->dev,"Status=0x%08X\n",status); + + if (ts->pendown) { + if (status & (1 << 21)) { /* Pen Up Event */ + dev_dbg (&ts->pdev->dev, "Pen up\n"); + + ts->pendown = 0; + + atmel_tsadcc_getevent(ts); + + input_report_key(ts->input, BTN_TOUCH, 0); + input_report_abs(ts->input, ABS_PRESSURE, 0); + input_sync(ts->input); + } + } else if (status & (1 << 20)) { /* Pen first down */ + dev_dbg (&ts->pdev->dev, "Pen down\n"); + + ts->pendown = 1; + + atmel_tsadcc_getevent(ts); + + input_report_key(ts->input, BTN_TOUCH, 1); + input_report_abs(ts->input, ABS_X, ts->event.x); + input_report_abs(ts->input, ABS_Y, ts->event.y); + input_report_abs(ts->input, ABS_PRESSURE, 1); + + input_sync(ts->input); + + /* Set up polling timer */ + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_DELAY), + HRTIMER_MODE_REL); + } + + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/*--------------------------------------------------------------------------*/ +/* TODO: Create power save features */ +static int atmel_tsadcc_suspend(struct platform_device *pdev, pm_message_t message) +{ + return 0; +} + +static int atmel_tsadcc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*--------------------------------------------------------------------------*/ + +static int __devinit atmel_tsadcc_probe(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts; + struct input_dev *input_dev; + struct atmel_tsadcc_platform_data *pdata = pdev->dev.platform_data; + int err; + struct resource *res; + unsigned int regbuf; + + dev_dbg(&pdev->dev,"Begin probe\n"); + + /* Check Resources */ + if (pdev->num_resources != 2) { + dev_err(&pdev->dev, "resources improperly configured\n"); + return -ENODEV; + } + + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + + /* Allocate memory for device */ + ts = kzalloc(sizeof(struct atmel_tsadcc), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + platform_set_drvdata(pdev, ts); + + /* Set up polling timer */ + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = atmel_tsadcc_timer; + + /* Touchscreen Information */ + ts->pdev = pdev; + ts->input = input_dev; + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", pdev->dev.bus_id); + ts->reg_name = "Atmel TSADCC Regs"; + ts->pendown = 0; + + /* Input Device Information */ + input_dev->name = "Atmel Touchscreen ADC Controller"; + input_dev->phys = ts->phys; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x3FF, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 15000, 0, 0); + + /* Remap Register Memory */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ts->reg_start = res->start; + ts->reg_length = res->end - res->start +1; + + if (!request_mem_region(ts->reg_start, ts->reg_length, ts->reg_name)) { + dev_err(&pdev->dev, "resources unavailable\n"); + err = -EBUSY; + goto err_free_mem; + } + + ts->reg = ioremap(ts->reg_start, ts->reg_length); + if (!ts->reg){ + dev_err(&pdev->dev, "unable to remap register memory\n"); + err = -ENOMEM; + goto err_release_mem; + } + + /* Setup Clock */ + ts->tsc_clk = clk_get(&pdev->dev,"tsc_clk"); + if (IS_ERR(ts->tsc_clk)) { + dev_err(&pdev->dev, "unable to get clock\n"); + err = PTR_ERR(ts->tsc_clk); + goto err_iounmap; + } + clk_enable(ts->tsc_clk); + + regbuf = clk_get_rate(ts->tsc_clk); + dev_dbg (&pdev->dev,"Master clock is set at: %d Hz\n",regbuf); + + /* Setup IRQ */ + ts->irq = platform_get_irq(pdev, 0); + if (ts->irq < 0) { + dev_err(&pdev->dev, "unable to get IRQ\n"); + err = -EBUSY; + goto err_clk_put; + } + + /* Fill in initial Register Data */ + ATMEL_TSADCC_RESET; + regbuf = (0x01 & 0x3) | /* TSAMOD */ + ((0x0 & 0x1) << 5) | /* SLEEP */ + ((0x1 & 0x1) << 6) | /* PENDET */ + ((pdata->prescaler & 0x3F) << 8) | /* PRESCAL */ + ((pdata->startup & 0x7F) << 16) | /* STARTUP */ + ((pdata->debounce & 0x0F) << 28); /* PENDBC */ + atmel_tsadcc_writereg(ATMEL_TSADCC_MR,regbuf); + + regbuf = (0x4 & 0x7) | /* TRGMOD */ + ((0xFFFF & 0xFFFF) << 16); /* TRGPER */ + atmel_tsadcc_writereg(ATMEL_TSADCC_TRGR,regbuf); + + regbuf = ((pdata->tsshtim & 0xF) << 24); /* TSSHTIM */ + atmel_tsadcc_writereg(ATMEL_TSADCC_TSR,regbuf); + + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_IMR); + dev_dbg (&pdev->dev, "Initial IMR = %X\n", regbuf); + + /* Register and enable IRQ */ + if (request_irq(ts->irq, atmel_tsadcc_irq, IRQF_TRIGGER_LOW, + pdev->dev.driver->name, ts)) { + dev_dbg(&pdev->dev, "IRQ %d busy!\n", ts->irq); + err = -EBUSY; + goto err_iounmap; + } + regbuf = ((0x1 & 0x1) << 20) | /* PENCNT */ + ((0x1 & 0x1) << 21); /* NOCNT */ + atmel_tsadcc_writereg(ATMEL_TSADCC_IER,regbuf); + + regbuf = atmel_tsadcc_readreg(ATMEL_TSADCC_IMR); + dev_dbg (&pdev->dev, "Post-Enable IMR = %X\n", regbuf); + + /* Register Input Device */ + err = input_register_device(input_dev); + if (err) + goto err_free_irq; + + return 0; + + err_free_irq: + free_irq(ts->irq, ts); + err_clk_put: + clk_put(ts->tsc_clk); + err_iounmap: + iounmap(ts->reg); + err_release_mem: + release_mem_region(ts->reg_start, ts->reg_length); + err_free_mem: + dev_dbg(&pdev->dev, "Error occurred: Freeing Memory!\n"); + input_free_device(input_dev); + kfree(ts); + return err; +} + +static int __devexit atmel_tsadcc_remove(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts = dev_get_drvdata(&pdev->dev); + + input_unregister_device(ts->input); + + free_irq(ts->irq, ts); + + /* Free Memory */ + iounmap(ts->reg); + release_mem_region(ts->reg_start, ts->reg_length); + kfree(ts); + + dev_dbg(&pdev->dev, "unregistered touchscreen\n"); + + return 0; +} + +static struct platform_driver atmel_tsadcc_driver = { + .driver = { + .name = "atmel_tsadcc", + .owner = THIS_MODULE, + }, + .probe = atmel_tsadcc_probe, + .remove = __devexit_p(atmel_tsadcc_remove), + .suspend = atmel_tsadcc_suspend, + .resume = atmel_tsadcc_resume, +}; + +static int __init atmel_tsadcc_init(void) +{ + return platform_driver_register(&atmel_tsadcc_driver); +} +module_init(atmel_tsadcc_init); + +static void __exit atmel_tsadcc_exit(void) +{ + platform_driver_unregister(&atmel_tsadcc_driver); +} +module_exit(atmel_tsadcc_exit); + +MODULE_DESCRIPTION("Atmel TSADCC Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/atmel_tsadcc.h b/include/linux/atmel_tsadcc.h new file mode 100644 index 0000000..3199622 --- /dev/null +++ b/include/linux/atmel_tsadcc.h @@ -0,0 +1,89 @@ +/* + * Atmel TSADCC touchscreen sensor driver + * + * Copyright (c) 2008 TimeSys Corporation + * Copyright (c) 2008 Justin Waters + * + * 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. + */ + +#ifndef _ATMEL_TSADCC_H +#define _ATMEL_TSADCC_H + +/*-------------------------------------------------------------------------- + * Register Locations + * + * From AT91SAM9RL64 User Guide, 15-Jan-08, Table 43-2 + *--------------------------------------------------------------------------*/ +#define ATMEL_TSADCC_CR 0x00 /* Control (wo) */ +#define ATMEL_TSADCC_MR 0x04 /* Mode (rw) */ +#define ATMEL_TSADCC_TRGR 0x08 /* Trigger (rw) */ +#define ATMEL_TSADCC_TSR 0x0C /* Touch Screen (rw) */ +#define ATMEL_TSADCC_CHER 0x10 /* Channel Enable (wo) */ +#define ATMEL_TSADCC_CHDR 0x14 /* Channel Disable (wo) */ +#define ATMEL_TSADCC_CHSR 0x18 /* Channel Status (ro) */ +#define ATMEL_TSADCC_SR 0x1C /* Status (ro) */ +#define ATMEL_TSADCC_LCDR 0x20 /* Last Converted Data (ro) */ +#define ATMEL_TSADCC_IER 0x24 /* Interrupt Enable (wo) */ +#define ATMEL_TSADCC_IDR 0x28 /* Interrupt Disable (wo) */ +#define ATMEL_TSADCC_IMR 0x2C /* Interrupt Mask (ro) */ +#define ATMEL_TSADCC_CDR0 0x30 /* Channel Data 0 (ro) */ +#define ATMEL_TSADCC_CDR1 0x34 /* Channel Data 1 (ro) */ +#define ATMEL_TSADCC_CDR2 0x38 /* Channel Data 2 (ro) */ +#define ATMEL_TSADCC_CDR3 0x3C /* Channel Data 3 (ro) */ +#define ATMEL_TSADCC_CDR4 0x40 /* Channel Data 4 (ro) */ +#define ATMEL_TSADCC_CDR5 0x44 /* Channel Data 5 (ro) */ + +/*-------------------------------------------------------------------------- + * I/O Macros + *--------------------------------------------------------------------------*/ +#define atmel_tsadcc_readreg(a) ioread32(ts->reg + a) +#define atmel_tsadcc_writereg(a,v) iowrite32(v,ts->reg + a) + + +/*-------------------------------------------------------------------------- + * Miscellaneous Macros + *--------------------------------------------------------------------------*/ +#define ATMEL_TSADCC_RESET atmel_tsadcc_writereg(ATMEL_TSADCC_CR,0x00000001) + +/*-------------------------------------------------------------------------- + * Touchscreen Structs + *--------------------------------------------------------------------------*/ + +struct ts_event { + unsigned int x; + unsigned int y; +}; + +struct atmel_tsadcc_platform_data { + unsigned int prescaler:6; /* ADC Clock Prescaler */ + unsigned int debounce:4; /* Pen Debounce Period */ + unsigned int tsshtim:4; /* Touchscreen Sample and Hold Time */ + unsigned int startup:7; /* Startup time */ +}; + +struct atmel_tsadcc { + struct input_dev *input; + char phys[32]; + struct platform_device *pdev; + int irq; + + void __iomem *reg; + const char *reg_name; + unsigned long reg_start; + unsigned long reg_length; + + spinlock_t lock; + + struct clk *tsc_clk; + struct hrtimer timer; + struct ts_event event; + + unsigned int pendown; +}; + +/*--------------------------------------------------------------------------*/ + +#endif /* _ATMEL_TSADCC_H */ -- 1.5.4.3 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html