http://www.at91.com/samphpbb/viewtopic.php?f=12&t=4992 This ADC driver sets up the TCO module to generate interrupts ever X ms and take data into a small ring buffer. It is assume the client reads the ADC data every 1 sec. Both channels are supported - the device files /dev/at91adc0 and /dev/at91adc1 are created. There are 4 files here: 1. the driver 2. makefile 3. install script 4. test code in ...TCL. Yeah, odd choice, but since Atmel uses TCL for a bunch of things we use it for testing. It just opens /dev/at91adc0 and reads the data bytes Cheers Gertjan /****************************************************************************** | File name : at91adc.c | | Description : Source code for the EDAQ ADC driver. This driver supports two | | : adc channesl with minor numbers 0 and 1. When the module is | | : is inserted to kernel, TC0 module is setup to generate interr-| | : upt every 1 msec. Both ADC channels are sampled in the ISR | | : and the samples are copied into a ring buffer. Sampling rate | | : can be changed by changing SAMPLE_INTERVAL_MS. In response to | | : the read from user space, latest requested number of samples | | : are returned as an unsigned short array. Channel 0/1 data is | | : returned depending on the minor number of the device opened. | | History : | | ### Date Author Comments | | 001 30-Jul-08 S. Thalore Initial creation | | Copyright 2008 | | 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 3 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, see . | ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include // Set the period between reads (max allowed value 1000) #define SAMPLE_INTERVAL_MS 5 // Assume client always reads data at 1 second intervals; so the client will read: #define READ_SAMPLES 1000/SAMPLE_INTERVAL_MS // Allocate to be at least 5 x bigger so that ISR will never catch up with reading. #define MAX_ADCSAMPLES 5* READ_SAMPLES int at91adc_devno; struct cdev at91adc_cdev; struct file_operations at91adc_fops; unsigned short *at91adc_pbuf0, *at91adc_pbuf1, at91adc_appidx; void __iomem *at91tc0_base; void __iomem *at91adc_base; struct clk *at91adc_clk; struct clk *at91tc0_clk; /***************************************************************************************** | Timer counter 0 ISR: Sample both ADC channels and copy the data to module ring buffer. | *****************************************************************************************/ static irqreturn_t at91tc0_isr (int irq, void *dev_id) { int status; //struct timeval time; static int timecount = 0; // Read TC0 status register to reset RC compare status. status = ioread32(at91tc0_base + AT91_TC_SR); timecount++; if (timecount >= SAMPLE_INTERVAL_MS) { timecount = 0; //do_gettimeofday(&time); //printk(KERN_INFO "Time %u:%u\n",time.tv_sec, time.tv_usec); // Trigger the ADC (this will be done using TIOA automatically eventually). iowrite32(AT91_ADC_START, (at91adc_base + AT91_ADC_CR)); // Wait for conversion to be complete. while ((ioread32(at91adc_base + AT91_ADC_SR) & AT91_ADC_DRDY) == 0) cpu_relax(); // Copy converted data to module ring buffer. at91adc_pbuf0[at91adc_appidx] = ioread32(at91adc_base + AT91_ADC_CHR(0)); at91adc_pbuf1[at91adc_appidx] = ioread32(at91adc_base + AT91_ADC_CHR(1)); // Increment the ring buffer index and check for wrap around. at91adc_appidx += 1; if (at91adc_appidx >= MAX_ADCSAMPLES) at91adc_appidx = 0; } return IRQ_HANDLED; } /***************************************************************************************** | Module initialization: Allocate device numbers, register device, setup ADC and timer | | counter registers for 100 msec periodic sampling. | *****************************************************************************************/ static int __init at91adc_init (void) { int ret; // Dynamically allocate major number and minor number ret = alloc_chrdev_region(&at91adc_devno, // pointer to where the device number to be stored 0, // first minor number requested 2, // number of devices "at91adc"); // device name if (ret < 0) { printk(KERN_INFO "at91adc: Device number allocation failed\n"); ret = -ENODEV; goto exit_1; } // Initialize cdev structure. cdev_init(&at91adc_cdev, // pointer to the cdev structure &at91adc_fops); // pointer to the file operations structure. at91adc_cdev.owner = THIS_MODULE; at91adc_cdev.ops = &at91adc_fops; // Register the device with kernel ret = cdev_add(&at91adc_cdev, // pointer to the initialized cdev structure at91adc_devno, // device number allocated 2); // number of devices if (ret != 0) { printk(KERN_INFO "at91adc: Device registration failed\n"); ret = -ECANCELED; goto exit_2; } // Character device driver initialization complete. Do device specific initialization now. // Allocate ring buffer memory for storing ADC values for both channels. at91adc_pbuf0 = (unsigned short *)kmalloc((MAX_ADCSAMPLES * sizeof(unsigned short)), // Number of bytes GFP_KERNEL); // Flags at91adc_pbuf1 = (unsigned short *)kmalloc((MAX_ADCSAMPLES * sizeof(unsigned short)), // Number of bytes GFP_KERNEL); // Flags if ((at91adc_pbuf0 == NULL) || (at91adc_pbuf1 == NULL)) { printk(KERN_INFO "at91adc: Memory allocation failed\n"); ret = -ECANCELED; goto exit_3; } // Initialize the ring buffer and append index. at91adc_appidx = 0; for (ret = 0; ret < MAX_ADCSAMPLES; ret++) { at91adc_pbuf0[ret] = 0; at91adc_pbuf1[ret] = 0; } // Initialize ADC. The following two lines set the appropriate PMC bit // for the ADC. Easier than mapping PMC registers and then setting the bit. at91adc_clk = clk_get(NULL, // Device pointer - not required. "adc_clk"); // Clock name. clk_enable(at91adc_clk); // Map ADC registers to the current address space. at91adc_base = ioremap_nocache(AT91SAM9260_BASE_ADC, // Physical address 64); // Number of bytes to be mapped. if (at91adc_base == NULL) { printk(KERN_INFO "at91adc: ADC memory mapping failed\n"); ret = -EACCES; goto exit_4; } // MUX GPIO pins for ADC (peripheral A) operation at91_set_A_periph(AT91_PIN_PC0, 0); at91_set_A_periph(AT91_PIN_PC1, 0); // Reset the ADC iowrite32(AT91_ADC_SWRST, (at91adc_base + AT91_ADC_CR)); // Enable both ADC channels iowrite32((AT91_ADC_CH(1) | AT91_ADC_CH(0)), (at91adc_base + AT91_ADC_CHER)); // Configure ADC mode register. // From table 43-31 in page #775 and page#741 of AT91SAM9260 user manual: // Maximum ADC clock frequency = 5MHz = MCK / ((PRESCAL+1) * 2) // PRESCAL = ((MCK / 5MHz) / 2) -1 = ((100MHz / 5MHz)/2)-1) = 9 // Maximum startup time = 15uS = (STARTUP+1)*8/ADC_CLOCK // STARTUP = ((15uS*ADC_CLOK)/8)-1 = ((15uS*5MHz)/8)-1 = 9 // Minimum hold time = 1.2uS = (SHTIM+1)/ADC_CLOCK // SHTIM = (1.2uS*ADC_CLOCK)-1 = (1.2uS*5MHz)-1 = 5, Use 9 to ensure 2uS hold time. // Enable sleep mode and hardware trigger from TIOA output from TC0. iowrite32((AT91_ADC_SHTIM_(9) | AT91_ADC_STARTUP_(9) | AT91_ADC_PRESCAL_(9) | AT91_ADC_SLEEP | AT91_ADC_TRGEN), (at91adc_base + AT91_ADC_MR)); // Initialize Timer Counter module 0. The following two lines set the appropriate // PMC bit for TC0. Easier than mapping PMC registers and then setting the bit. at91tc0_clk = clk_get(NULL, // Device pointer - not required. "tc0_clk"); // Clock name. clk_enable(at91tc0_clk); // Map TC0 registers to the current address space. at91tc0_base = ioremap_nocache(AT91SAM9260_BASE_TC0, // Physical address 64); // Number of bytes to be mapped. if (at91tc0_base == NULL) { printk(KERN_INFO "at91adc: TC0 memory mapping failed\n"); ret = -EACCES; goto exit_5; } // Configure TC0 in waveform mode, TIMER_CLK1 and to generate interrupt on RC compare. // Load 50000 to RC so that with TIMER_CLK1 = MCK/2 = 50MHz, the interrupt will be // generated every 1/50MHz * 50000 = 20nS * 50000 = 1 milli second. // NOTE: Even though AT91_TC_RC is a 32-bit register, only 16-bits are programmble. iowrite32(50000, (at91tc0_base + AT91_TC_RC)); iowrite32((AT91_TC_WAVE | AT91_TC_WAVESEL_UP_AUTO), (at91tc0_base + AT91_TC_CMR)); iowrite32(AT91_TC_CPCS, (at91tc0_base + AT91_TC_IER)); iowrite32((AT91_TC_SWTRG | AT91_TC_CLKEN), (at91tc0_base + AT91_TC_CCR)); // Install interrupt for TC0. ret = request_irq(AT91SAM9260_ID_TC0, // Interrupt number at91tc0_isr, // Pointer to the interrupt sub-routine 0, // Flags - fast, shared or contributing to entropy pool "at91adc", // Device name to show as owner in /proc/interrupts NULL); // Private data for shared interrupts if (ret != 0) { printk(KERN_INFO "at91adc: Timer interrupt request failed\n"); ret = -EBUSY; goto exit_6; } printk(KERN_INFO "at91adc: Loaded module\n"); return 0; exit_6: iounmap(at91tc0_base); exit_5: clk_disable(at91tc0_clk); iounmap(at91adc_base); exit_4: clk_disable(at91adc_clk); exit_3: kfree(at91adc_pbuf0); kfree(at91adc_pbuf1); exit_2: // Free device number allocated. unregister_chrdev_region(at91adc_devno, // allocated device number 2); // number of devices exit_1: return ret; } static void __exit at91adc_exit (void) { // Reset PMC bit for ADC and TC0 clk_disable(at91adc_clk); clk_disable(at91tc0_clk); // Free TC0 IRQ. free_irq(AT91SAM9260_ID_TC0, // Interrupt number NULL); // Private data for shared interrupts // Unmap ADC and TC0 register map. iounmap(at91adc_base); iounmap(at91tc0_base); // Free kernel memory allocated kfree(at91adc_pbuf0); kfree(at91adc_pbuf1); // Free device number allocated. unregister_chrdev_region(at91adc_devno, // allocated device number 2); // number of devices printk(KERN_INFO "at91adc: Unloaded module\n"); } /***************************************************************************************** | Module open: | *****************************************************************************************/ static int at91adc_open (struct inode *inode, struct file *filp) { return 0; } /***************************************************************************************** | Module close: | *****************************************************************************************/ static int at91adc_release (struct inode *inode, struct file *filp) { return 0; } /***************************************************************************************** | Module read: Return last READ_SAMPLES samples from ADC chan 0 or 1 depending on the | | minor number. | *****************************************************************************************/ static ssize_t at91adc_read (struct file *filp, char __iomem *buf, size_t bufsize, loff_t *f_pos) { unsigned int minor; unsigned short idx, readidx, *psrc; // Latch the index of the latest data readidx = at91adc_appidx; readidx = (readidx >= READ_SAMPLES) ? readidx - READ_SAMPLES : MAX_ADCSAMPLES - (READ_SAMPLES - readidx); // Read from ADC channel 0 or 1 depending on minor number. minor = iminor(filp->f_dentry->d_inode); // Select the source buffer pointer based on the minor number. if (minor == 0) psrc = at91adc_pbuf0; else if (minor == 1) psrc = at91adc_pbuf1; else return 0; // Return up to READ_SAMPLES samples depending on the size of the buffer. for (idx = 0; idx < READ_SAMPLES; idx++) { if (bufsize < sizeof(unsigned short)) break; if (copy_to_user(buf, &psrc[readidx], sizeof(unsigned short)) != 0) return -EFAULT; buf = buf + sizeof(unsigned short); bufsize = bufsize - sizeof(unsigned short); readidx += 1; if (readidx >= MAX_ADCSAMPLES) readidx = 0; } // Return number of characters copied. return (idx * sizeof(unsigned short)); } struct file_operations at91adc_fops = { .owner = THIS_MODULE, .open = at91adc_open, .read = at91adc_read, .release = at91adc_release, }; module_init(at91adc_init); module_exit(at91adc_exit); MODULE_AUTHOR("Sudhir Thalore"); MODULE_DESCRIPTION("Initialize and read AT91SAM9260 ADC channels"); MODULE_LICENSE("GPL"); ================= CUT ================= #****************************************************************************** # File name : Makefile | # Description : GNU makefile to build the AT91 ADC driver module at91adc.ko. | # : This makefile must be invoked using the command line | # : "make ARCH=arm CROSS_COMPILE=path_to_compiler KERN_DIR= | # : path_to_kernel_source" | # History : | # ### Date Author Comments | # 001 31-Jul-08 S. Thalore Initial creation | #*****************************************************************************/ EXTRA_CFLAGS = -Wall -O2 ifneq ($(KERNELRELEASE),) # Call from kernel build system obj-m := at91adc.o else # Call from module source directory PWD := $(shell pwd) # Verify command line arguments if this is not make clean ifneq ($(MAKECMDGOALS),clean) ifeq ($(ARCH),) $(error Run using make ARCH=arm CROSS_COMPILE=path_to_compiler KERN_DIR=path_to_kernel_source. Missing ARCH) endif ifeq ($(CROSS_COMPILE),) $(error Run using make ARCH=arm CROSS_COMPILE=path_to_compiler KERN_DIR=path_to_kernel_source. Missing CROSS_COMPILE) endif ifeq ($(KERN_DIR),) $(error Run using make ARCH=arm CROSS_COMPILE=path_to_compiler KERN_DIR=path_to_kernel_source. Missing KERN_DIR) endif endif default: $(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c *.symvers .tmp_versions endif =========== CUT ============ #!/bin/sh major=-1 module="at91adc" device="at91adc" /sbin/rmmod $module if [ "$?" != "0" ] then echo ...error ignored fi /sbin/insmod ./$module.ko if [ "$?" = "0" ] then rm -rf /dev/${device}[0-1] major=`cat /proc/devices | awk "\\$2==\"$device\" {print \\$1}"` if [ "$?" = "0" ] then mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 echo "Created nodes /dev/${device}0 and /dev/${device}1" fi fi ============================== cut =================== #!/usr/bin/tclsh #****************************************************************************** # File name : adctst | # Description : TCL script to run stability test on on-chip ADC channels. | # : The script reads ten 100 msec samples from both ADC channels | # : every second and assigns average of these readings as 1-sec | # : value. Max and Min of 60 1-sec values is computed and 1-minute| # : error is calculated using (1-secMax - 1-secMin)*100/1-secMean.| # : The test is run for the specified number of minutes and the | # : results are written into the file ./adctst.dat. | # History : | # ### Date Author Comments | # 001 30-Jul-08 S. Thalore Initial creation | #*****************************************************************************/ lappend auto_path /usr/lib/tcllib1.8 package require math::statistics if {$argc < 1} { puts "Specify length of test in minutes" exit } set numsamples_per_s 200 ; # from ADC driver - see: SAMPLE_INTERVAL_MS set num_bytes [expr 2 *$numsamples_per_s] set tlen [lindex $argv 0] set fptr [open "adctst.dat" "w"] puts $fptr "ADC0AVE, ADC0MIN, ADC0MAX, ADC0ERR, ADC1AVE, ADC1MIN, ADC1MAX, ADC1ERR" close $fptr set fptr0 [open "/dev/at91adc0" "r"] fconfigure $fptr0 -translation binary set fptr1 [open "/dev/at91adc1" "r"] fconfigure $fptr1 -translation binary while {$tlen > 0} { #puts "$tlen minutes left..." incr tlen -1 set adc0onesecval "" set adc1onesecval "" set secsinmin 60 while {$secsinmin > 0} { incr secsinmin -1 set adcdata "" set data [read $fptr0 $num_bytes ] if {[string length $data] == $num_bytes } { while {[string length $data] >= 2} { binary scan $data "s" sample puts $sample lappend adcdata $sample set data [string range $data 2 end] } lappend adc0onesecval [::math::statistics::mean $adcdata] } else { puts "Invalid length [string length $data]; ignoring the read data" } set adcdata "" set data [read $fptr1 $num_bytes] if {[string length $data] == $num_bytes} { while {[string length $data] >= 2} { binary scan [string range $data 0 1] "s" sample lappend adcdata $sample set data [string range $data 2 end] } lappend adc1onesecval [::math::statistics::mean $adcdata] } else { puts "Invalid length [string length $data]; ignoring the read data" } after 1010 ; # waiting 1 sec should be long enough not to see repeated samples from the buffer } set adc0oneminmin [::math::statistics::min $adc0onesecval] set adc0oneminmax [::math::statistics::max $adc0onesecval] set adc0oneminval [::math::statistics::mean $adc0onesecval] set adc0oneminerr [expr {(($adc0oneminmax-$adc0oneminmin)*100)/$adc0oneminval}] set adc1oneminmin [::math::statistics::min $adc1onesecval] set adc1oneminmax [::math::statistics::max $adc1onesecval] set adc1oneminval [::math::statistics::mean $adc1onesecval] set adc1oneminerr [expr {(($adc1oneminmax-$adc1oneminmin)*100)/$adc1oneminval}] set fptr [open "adctst.dat" "a"] puts $fptr "$adc0oneminval, $adc0oneminmin, $adc0oneminmax, $adc0oneminerr \ $adc1oneminval, $adc1oneminmin, $adc1oneminmax, $adc1oneminerr" close $fptr } close $fptr0 close $fptr1 puts "Output saved in ./adctst.dat"