[PATCH 1/1] ARM: Developed device driver for Atmel TSADC controller

Petter Nordby pettno at gmail.com
Mon Jan 23 06:25:06 EST 2012


Some Atmel AT91 devices contains a touchscreen analog to digital
converter. This device driver use the ADC as a multi-channel raw
data input device.

Tested on AT91SAM9G45 boards.

Signed-off-by: Petter Nordby <pettno at gmail.com>
---
 CREDITS                                    |    4 +
 arch/arm/configs/at91sam9g45_defconfig     |    1 +
 arch/arm/mach-at91/include/mach/at91_adc.h |   16 ++
 drivers/misc/Kconfig                       |    7 +
 drivers/misc/Makefile                      |    1 +
 drivers/misc/atmel_tsadc.c                 |  361 ++++++++++++++++++++++++++++
 6 files changed, 390 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/atmel_tsadc.c

diff --git a/CREDITS b/CREDITS
index 370b4c7..8c615eb 100644
--- a/CREDITS
+++ b/CREDITS
@@ -2652,6 +2652,10 @@ S: 2364 Old Trail Drive
 S: Reston, Virginia 20191
 S: USA
 
+N: Petter Nordby
+E: pettno at gmail.com
+D: Atmel ADC device driver
+
 N: Fredrik Noring
 E: noring at nocrew.org
 W: http://www.lysator.liu.se/~noring/
diff --git a/arch/arm/configs/at91sam9g45_defconfig b/arch/arm/configs/at91sam9g45_defconfig
index 606d48f..748caca 100644
--- a/arch/arm/configs/at91sam9g45_defconfig
+++ b/arch/arm/configs/at91sam9g45_defconfig
@@ -15,6 +15,7 @@ CONFIG_MODULE_UNLOAD=y
 # CONFIG_BLK_DEV_BSG is not set
 # CONFIG_IOSCHED_DEADLINE is not set
 # CONFIG_IOSCHED_CFQ is not set
+CONFIG_CROSS_COMPILE="arm-linux-"
 CONFIG_ARCH_AT91=y
 CONFIG_ARCH_AT91SAM9G45=y
 CONFIG_MACH_AT91SAM9M10G45EK=y
diff --git a/arch/arm/mach-at91/include/mach/at91_adc.h b/arch/arm/mach-at91/include/mach/at91_adc.h
index 8e7ed5c..b665d61 100644
--- a/arch/arm/mach-at91/include/mach/at91_adc.h
+++ b/arch/arm/mach-at91/include/mach/at91_adc.h
@@ -29,12 +29,28 @@
 #define		AT91_ADC_LOWRES		(1 << 4)	/* Low Resolution */
 #define		AT91_ADC_SLEEP		(1 << 5)	/* Sleep Mode */
 #define		AT91_ADC_PRESCAL	(0x3f << 8)	/* Prescalar Rate Selection */
+#define			AT91_ADC_EPRESCAL	(0xff << 8)	/* Prescalar Rate Selection (Extended) */
 #define			AT91_ADC_PRESCAL_(x)	((x) << 8)
 #define		AT91_ADC_STARTUP	(0x1f << 16)	/* Startup Up Time */
 #define			AT91_ADC_STARTUP_(x)	((x) << 16)
 #define		AT91_ADC_SHTIM		(0xf  << 24)	/* Sample & Hold Time */
 #define			AT91_ADC_SHTIM_(x)	((x) << 24)
 
+#define AT91_ADC_TRGR		0x08		/* Trigger register */
+#define		AT91_ADC_TRGMOD	(7      <<  0)	/* Trigger mode */
+#define			AT91_ADC_TRGMOD_NONE		(0 << 0)
+#define			AT91_ADC_TRGMOD_EXT_RISING	(1 << 0)
+#define			AT91_ADC_TRGMOD_EXT_FALLING	(2 << 0)
+#define			AT91_ADC_TRGMOD_EXT_ANY		(3 << 0)
+#define			AT91_ADC_TRGMOD_PENDET		(4 << 0)
+#define			AT91_ADC_TRGMOD_PERIOD		(5 << 0)
+#define			AT91_ADC_TRGMOD_CONTINUOUS	(6 << 0)
+#define			AT91_ADC_TRGPER	(0xffff << 16)	/* Trigger period */
+
+#define AT91_ADC_TSR	0x0C	/* Touch Screen register */
+#define	  AT91_ADC_TSFREQ	(0xf <<  0)	/* TS Frequency in Interleaved mode */
+#define	  AT91_ADC_TSSHTIM	(0xf << 24)	/* Sample & Hold time */
+
 #define AT91_ADC_CHER		0x10		/* Channel Enable Register */
 #define AT91_ADC_CHDR		0x14		/* Channel Disable Register */
 #define AT91_ADC_CHSR		0x18		/* Channel Status Register */
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6a1a092..d105f42 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -112,6 +112,13 @@ config ATMEL_TCB_CLKSRC_BLOCK
 	  TC can be used for other purposes, such as PWM generation and
 	  interval timing.
 
+config ATMEL_TSADC
+	tristate "Atmel touchscreen ADC char device driver"
+	depends on ARCH_AT91SAM9RL || ARCH_AT91SAM9G45
+	help
+	  Say Y here if you want to use the touchscreen ADC controller
+	  as a simple ADC input device.
+
 config IBM_ASM
 	tristate "Device driver for IBM RSA service processor"
 	depends on X86 && PCI && INPUT && EXPERIMENTAL
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3e1d801..b6a13a1 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_INTEL_MID_PTI)	+= pti.o
 obj-$(CONFIG_ATMEL_PWM)		+= atmel_pwm.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
 obj-$(CONFIG_ATMEL_TCLIB)	+= atmel_tclib.o
+obj-$(CONFIG_ATMEL_TSADC)	+= atmel_tsadc.o
 obj-$(CONFIG_BMP085)		+= bmp085.o
 obj-$(CONFIG_ICS932S401)	+= ics932s401.o
 obj-$(CONFIG_LKDTM)		+= lkdtm.o
diff --git a/drivers/misc/atmel_tsadc.c b/drivers/misc/atmel_tsadc.c
new file mode 100644
index 0000000..778bd4f
--- /dev/null
+++ b/drivers/misc/atmel_tsadc.c
@@ -0,0 +1,361 @@
+/*
+ *  Atmel Touch Screen Driver in ADC only mode
+ *
+ *  Copyright (c) 2012 Petter Nordby
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+
+#include <asm/system.h>
+
+#include <mach/cpu.h>
+#include <mach/gpio.h>
+#include <mach/at91sam9g45.h>
+#include <mach/at91_adc.h>
+
+#define DEVICE_NAME "at-tsadc"
+#define NO_OF_CHANNELS 4
+#define NO_OF_SAMPLES  4
+
+static int major;
+static int minor;
+
+static void __iomem *tsadc_base;
+
+#define tsadc_readl(reg)		__raw_readl(tsadc_base + (reg))
+#define tsadc_writel(reg, val)	__raw_writel((val), tsadc_base + (reg))
+
+#define PRESCALER_VAL(x)	((x) >> 8)
+#define ADC_DEFAULT_CLOCK	100000
+
+
+struct tsadc_dev {
+	int id;
+	unsigned long tsadc_base;
+	unsigned long tsadc_size;
+	struct cdev cdev;
+
+	struct clk *clk;
+	unsigned int value[NO_OF_CHANNELS][NO_OF_SAMPLES];
+	unsigned int sample_no[NO_OF_CHANNELS];
+	unsigned int adc_clock;
+	u8 ts_sample_hold_time;
+};
+
+static struct tsadc_dev dev = {
+	.id = AT91SAM9G45_ID_TSC,
+	.tsadc_base = AT91SAM9G45_BASE_TSC,
+	.tsadc_size = SZ_16K,
+	.cdev = {
+		.kobj = { .name = DEVICE_NAME, },
+		.owner = THIS_MODULE,
+	},
+	.adc_clock = 300000,
+	.ts_sample_hold_time = 0x0a,
+};
+
+static dev_t devt;
+
+
+static irqreturn_t tsadc_interrupt(int irq, void *pdev)
+{
+	unsigned int ch, status, sample_no, sample;
+	status = tsadc_readl(AT91_ADC_SR);
+	status &= tsadc_readl(AT91_ADC_IMR);
+
+	for (ch = 0; ch < NO_OF_CHANNELS; ch++) {
+
+		/* Conversion finished? */
+		if (status & AT91_ADC_EOC(ch)) {
+
+			/* Store new measurement */
+			sample = tsadc_readl(AT91_ADC_CHR(ch)) & 0x3FF;
+			sample_no = dev.sample_no[ch];
+			dev.value[ch][sample_no] = sample;
+			dev.sample_no[ch] = (sample_no+1) % NO_OF_SAMPLES;
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+
+/***************************************************************************
+************************************ READ **********************************
+***************************************************************************/
+
+static ssize_t tsadc_read(struct file *filp, char *buff,
+	size_t length, loff_t *offp)
+{
+	unsigned long flags;
+	unsigned int sample_no, ch, i, bytes_read = 0;
+	u32 value = 0;
+	u8 *pv = (u8 *)&value;
+
+	/* Range check channel number */
+	ch = iminor(filp->f_dentry->d_inode);
+	if ((ch < 0) || (ch >= NO_OF_CHANNELS))
+		return -EFAULT;
+
+	/********** Start of critical section **********/
+	local_irq_save(flags);
+
+	for (sample_no = 0; sample_no < NO_OF_SAMPLES; sample_no++)
+		value += dev.value[ch][sample_no];
+	value = value / NO_OF_SAMPLES;
+
+	for (i = 0; length && (i < 4); i++) {
+		put_user(pv[i], buff++);
+		length--;
+		bytes_read++;
+	}
+
+	/********** End of critical section **********/
+	local_irq_restore(flags);
+
+	return bytes_read;
+}
+
+
+/***************************************************************************
+******************************* CONFIGURATION ******************************
+***************************************************************************/
+
+static int tsadc_init_hw(void)
+{
+	unsigned int ch, prsc, mode_reg;
+
+	clk_enable(dev.clk);
+	prsc = clk_get_rate(dev.clk);
+	printk(KERN_INFO "Master clock is set at: %d Hz\n", prsc);
+
+	if (!dev.adc_clock)
+		dev.adc_clock = ADC_DEFAULT_CLOCK;
+	for (ch = 0; ch < NO_OF_CHANNELS; ch++)
+		dev.sample_no[ch] = 0;
+
+	prsc = (prsc / (2 * dev.adc_clock)) - 1;
+
+	/* saturate if this value is too high */
+	if (cpu_is_at91sam9rl()) {
+		if (prsc > PRESCALER_VAL(AT91_ADC_PRESCAL))
+			prsc = PRESCALER_VAL(AT91_ADC_PRESCAL);
+	} else {
+		if (prsc > PRESCALER_VAL(AT91_ADC_EPRESCAL))
+			prsc = PRESCALER_VAL(AT91_ADC_EPRESCAL);
+	}
+
+	printk(KERN_INFO "Prescaler is set at: %d\n", prsc);
+
+	tsadc_writel(AT91_ADC_CR, AT91_ADC_SWRST);
+	mode_reg = ((0x00 << 5) & AT91_ADC_SLEEP) | /* ADC only mode */
+		(prsc << 8) |
+		((0x26 << 16) & AT91_ADC_STARTUP);
+	tsadc_writel(AT91_ADC_MR, mode_reg);
+
+	for (ch = 0; ch < NO_OF_CHANNELS; ch++)
+		tsadc_writel(AT91_ADC_CHER, AT91_ADC_CH(ch));
+	tsadc_writel(AT91_ADC_TRGR, AT91_ADC_TRGMOD_PERIOD | (0xFFFF << 16));
+
+	/* ADC clock = Master clock / ((prsc+1)*2) */
+	/* Trigger period = (0xFFFF+1) / ADC clock */
+
+	tsadc_writel(AT91_ADC_TSR,
+		(dev.ts_sample_hold_time << 24) & AT91_ADC_TSSHTIM);
+
+	tsadc_readl(AT91_ADC_SR);
+	for (ch = 0; ch < NO_OF_CHANNELS; ch++)
+		tsadc_writel(AT91_ADC_IER, AT91_ADC_EOC(ch));
+
+	return 0;
+}
+
+
+#define TSADC_IOC_MAGIC 0xA2
+#define TSADC_IOC_SET_CLK _IO(TSADC_IOC_MAGIC, 0x80)
+#define TSADC_IOC_GET_CLK _IO(TSADC_IOC_MAGIC, 0x81)
+
+static long tsadc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	int ch = iminor(filp->f_dentry->d_inode);
+
+	/* Range check channel number */
+	if ((ch < 0) || (ch >= NO_OF_CHANNELS))
+		return -ENODEV;
+
+	switch (cmd) {
+	case TSADC_IOC_SET_CLK:
+		if (copy_from_user(&dev.adc_clock, (unsigned int *) arg,
+				sizeof(unsigned int)))
+			return -EFAULT;
+		return tsadc_init_hw();
+		break;
+
+	case TSADC_IOC_GET_CLK:
+		if (copy_to_user((unsigned int *) arg, &dev.adc_clock,
+				sizeof(unsigned int)))
+			return -EFAULT;
+		break;
+
+	default:
+		printk(KERN_INFO "Unsupported ioctl command (%u)\n", cmd);
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+
+/***************************************************************************
+******************************* OPEN - CLOSE *******************************
+***************************************************************************/
+
+static int tsadc_open(struct inode *inode, struct file *filp)
+{
+	int ch = iminor(inode);
+
+	/* Range check channel number */
+	if ((ch < 0) || (ch >= NO_OF_CHANNELS))
+		return -ENODEV;
+
+	try_module_get(THIS_MODULE);
+	return 0;
+}
+
+
+static int tsadc_release(struct inode *inode, struct file *filp)
+{
+	int ch = iminor(inode);
+
+	/* Range check channel number */
+	if ((ch < 0) || (ch >= NO_OF_CHANNELS))
+		return -ENODEV;
+
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+
+const struct file_operations tsadc_fops = {
+	.owner = THIS_MODULE,
+	.read = tsadc_read,
+	.unlocked_ioctl = tsadc_ioctl,  /* supported if kernel >= 2.6.11 */
+	.open = tsadc_open,
+	.release = tsadc_release,
+};
+
+
+/***************************************************************************
+*************************** CONSTRUCT - DESTRUCT ***************************
+***************************************************************************/
+
+static int __init tsadc_init(void)
+{
+	int result;
+	printk(KERN_INFO "Initialize char device driver for touchscreen ADC\n");
+
+	if (!request_mem_region(dev.tsadc_base, dev.tsadc_size, DEVICE_NAME)) {
+		printk(KERN_ALERT "Can't get mem region 0x%lx\n", dev.tsadc_base);
+		result = -ENODEV;
+		goto err_request_mem_region;
+	}
+
+	tsadc_base = ioremap(dev.tsadc_base, dev.tsadc_size);
+	if (!tsadc_base) {
+		printk(KERN_ALERT "Failed to map registers\n");
+		result = -ENOMEM;
+		goto err_ioremap;
+	}
+
+	result = alloc_chrdev_region(&devt, minor, NO_OF_CHANNELS, DEVICE_NAME);
+	if (result < 0) {
+		printk(KERN_ALERT "Allocate device region failed (%d)\n", result);
+		goto err_alloc_chrdev_region;
+	}
+	major = MAJOR(devt);
+	printk(KERN_INFO "Device number: Major=%d Minor=%d\n", major, minor);
+
+	cdev_init(&dev.cdev, &tsadc_fops);
+	dev.cdev.owner = THIS_MODULE;
+	dev.cdev.ops = &tsadc_fops;
+	result = cdev_add(&dev.cdev, devt, NO_OF_CHANNELS);
+	if (result) {
+		printk(KERN_ALERT "Error %d adding cdev\n", result);
+		goto err_cdev_add;
+	}
+
+	result = request_irq(dev.id, tsadc_interrupt, IRQF_DISABLED,
+		DEVICE_NAME, &dev);
+	if (result) {
+		printk(KERN_ALERT "Error %d on request irq %d\n",
+			result, dev.id);
+		goto err_request_irq;
+	}
+
+	dev.clk = clk_get(NULL, "tsc_clk");
+	if (IS_ERR(dev.clk)) {
+		printk(KERN_ALERT "Failed to get ts_clk\n");
+		result = PTR_ERR(dev.clk);
+		goto err_clk_get;
+	}
+
+	at91_set_gpio_input(AT91_PIN_PD20, 0);	/* AD0_XR */
+	at91_set_gpio_input(AT91_PIN_PD21, 0);	/* AD1_XL */
+	at91_set_gpio_input(AT91_PIN_PD22, 0);	/* AD2_YT */
+	at91_set_gpio_input(AT91_PIN_PD23, 0);	/* AD3_TB */
+
+	return tsadc_init_hw();
+
+	clk_disable(dev.clk);
+	clk_put(dev.clk);
+err_clk_get:
+	disable_irq(dev.id);
+	free_irq(dev.id, &dev);
+err_request_irq:
+	cdev_del(&dev.cdev);
+err_cdev_add:
+	unregister_chrdev_region(devt, NO_OF_CHANNELS);
+err_alloc_chrdev_region:
+	iounmap(tsadc_base);
+err_ioremap:
+	release_mem_region(dev.tsadc_base, dev.tsadc_size);
+err_request_mem_region:
+	return result;
+}
+
+
+static void __exit tsadc_exit(void)
+{
+	printk(KERN_INFO "Clean up after char device driver for touchscreen ADC\n");
+
+	/* Stop tsadc hardware */
+	tsadc_writel(AT91_ADC_TRGR, 0);
+
+	disable_irq(dev.id);
+	free_irq(dev.id, &dev);
+	cdev_del(&dev.cdev);
+	unregister_chrdev_region(devt, NO_OF_CHANNELS);
+	iounmap(tsadc_base);
+	release_mem_region(dev.tsadc_base, dev.tsadc_size);
+	clk_disable(dev.clk);
+	clk_put(dev.clk);
+}
+
+module_init(tsadc_init);
+module_exit(tsadc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Simple char device driver for touchscreen ADC in AT91");
+MODULE_AUTHOR("Petter Nordby <pettno at gmail.com>");
-- 
1.7.7




More information about the linux-arm-kernel mailing list