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"