Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Lite high speed synchronous mode driver for USART in Atmel AT91SAM9260

124 views
Skip to first unread message

Wojciech M. Zabolotny

unread,
Nov 19, 2011, 8:03:32 AM11/19/11
to
Archive-name: wzusart
Submitted-by: wz...@ise.pw.edu.pl
Last-modified: 2011-11-19 +00:00
Copyright-Notice: GPL v2 or higher

I had to design a simple data acquisition system, based on AT91SAM9260,
in which the data stream from ADCs was transmitted in USART format
in synchronous mode at 6Mb/s.
It appeared, that the original USART driver is not able to handle such
high data rate.
Even optimization based on moving of USART data buffer to internal SRAM
didn't help.
Finally I was forced to write my own driver with significantly reduced
(comparing to original atmel_serial.c) functionality.
The attached driver is able only to receive the data in synchronous mode,
at rate defined by the frequency of externally provided SCK.
The driver handles only one USART (currently USART2, if you want to use
with another USART, you need to modify sources - see line 51 and below).

The driver does not provide "read" method. The data are transferred
to the buffer mmapped into the user space application memory.

buf=(unsigned char *)mmap(0,buffer_length,PROT_READ,MAP_PRIVATE,fd_of_wzusart,0);

The length of the buffer is defined in the driver sources:

static unsigned int rx_mbuf_len = (1<<19);
static unsigned int rx_mbuf_ptr_mask = (1<<19)-1;

It should be always power of 2.
The user application may find the length of the buffer using another ioctl:

buffer_length = ioctl(frs,0xa531,NULL);

The application should use ioctl to obtain current values of tail and head
(or read_pointer and write pointer) and number of available bytes of data:

unsigned int pointers[2];
[...]
len_of_data = ioctl(frs,0xa532,(long) pointers);
read_pointer = pointers[1];
write_pointer = pointers[0];

The above call checks also if the buffer got overflown. In this case
it returns the negative number, equal to -1-number_of_bytes_stored_in_buffer

After the application processes some amount of data, it should inform the
driver, that some data are used and may be overwritten:

ioctl(frs,0xa533,len_of_data);

This ioctl updates the read pointer and the write pointer accordingly.

To avoid active waiting for data, the driver supports "poll" method.
You can program the number of bytes which must be available before
the application is woken up:
ioctl(frs,0xa530,minimum_number_of_bytes_to_wake_up);

The driver may be improved:
1. By allowing it to handle multiple USARTs
2. By converting it into a platform driver
3. By allowing the PDC to store data directly into the mmapped buffer
(so no copying of data by CPU is required at all). This modification
however may introduce problems related to cache management
(DMA buffer synchronization) and with congestions in bus matrix.

Anyway I hope that this buffer may be useful for someone else

Wojciech M. Zabolotny
wzab<at>ise.pw.edu.pl

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.11).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `#!/bin/sh' line above, then type `sh FILE'.
#
lock_dir=_sh24692
# Made on 2011-11-19 13:46 CET by <wzab@wzab>.
# Source directory was `/home/wzab/biezace/olt_adc_new/usart_driver'.
#
# Existing files will *not* be overwritten, unless `-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 19792 -rw-r--r-- wzusart.c
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
if test "X$1" = "X-c"
then keep_file=''
else keep_file=true
fi
echo=echo
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=
locale_dir=
set_echo=false

for dir in $PATH
do
if test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir
set_echo=true
break ;;
esac
fi
done

if ${set_echo}
then
set_echo=false
for dir in $PATH
do
if test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
set_echo=true
break
fi
done

if ${set_echo}
then
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
fi
IFS="$save_IFS"
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and
installing GNU `touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir} ; then :
else ${echo} "lock directory ${lock_dir} exists"
exit 1
fi
if mkdir ${lock_dir}
then ${echo} "x - created lock directory ${lock_dir}."
else ${echo} "x - failed to create lock directory ${lock_dir}."
exit 1
fi
# ============= wzusart.c ==============
if test -n "${keep_file}" && test -f 'wzusart.c'
then
${echo} "x - SKIPPING wzusart.c (file already exists)"
else
${echo} "x - extracting wzusart.c (text)"
sed 's/^X//' << 'SHAR_EOF' > 'wzusart.c' &&
/*
X This is a driver allowing to use the USART in AT91SAM9260 to receive the
X data in synchronous mode, with external clock.
X The data are transferred with the PDC (via DMA) to the buffer located in the internal SRAM
X and then in the bottom half of interrupt routine to the buffer mmapped into the
X application memory.
X With this implementation data are copied with CPU only once - from the SRAM tu the final
X buffer.
X Theoretically it should be possible to implement a zero-copy solution, where the PDC
X writes data directly to the application buffer, but there are two possible problems:
X 1. Access of PDC to the SDRAM probably may cause congestions in the bus matrix, resulting
X in data loss, especially when Ethernet is used simultaneously
X 2. Cache related problems may arise when PDC writes data to the final buffer in SDRAM
X and when CPU reads data from that buffer. The problem how to cache-synchronize the part
X of a long buffer seems to be not well documented
X
X Written by Wojciech M. Zabolotny (wz...@ise.pw.edu.pl).
X The code was significantly based on the atmel_serial.c driver and also on many different
X examples of Linux device drivers...
X
X This program is free software; you can redistribute it and/or modify
X it under the terms of the GNU General Public License as published by
X the Free Software Foundation; either version 2 of the License, or
X (at your option) any later version.
X
X This program is distributed in the hope that it will be useful,
X but WITHOUT ANY WARRANTY; without even the implied warranty of
X MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
X GNU General Public License for more details.
X
X You should have received a copy of the GNU General Public License
X along with this program; if not, write to the Free Software
X Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
X
*/
X
X
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/vmalloc.h>
#include <linux/gpio.h>
#include <mach/board.h>
#include <linux/atmel_serial.h>
#include <linux/atmel_pdc.h>
//#define WZDAQ_DEBUG 1
#define DEVICE_NAME "wzusart"
X
/* The macro below should be customized for the particular USART */
#define WZ_USART_BASE AT91SAM9260_BASE_US2
X
#define WZ_USART_IRQ AT91SAM9260_ID_US2
X
#define WZ_USART_INIT() { \
X /* enable the clock */ \
X clk_enable(clk_get(NULL,"usart2_clk")); \
X /* Set pins for USART */ \
X at91_set_A_periph(AT91_PIN_PB8, 1); /* TXD2 */ \
X at91_set_A_periph(AT91_PIN_PB9, 0); /* RXD2 */ \
X at91_set_A_periph(AT91_PIN_PA30, 1); /* SCK2 */ \
X }
X
#define WZ_USART_RELEASE() { \
X clk_disable(clk_get(NULL,"usart2_clk")); \
X }
X
/*
X Macros below are copied from the atmel_serial.c driver with one
X modification - I have removed the "port" argument, as my driver
X works only with one serial port - defined above
*/
X
/* UART registers. CR is write-only, hence no GET macro */
#define UART_PUT_CR(v) __raw_writel(v, usart_regs + ATMEL_US_CR)
#define UART_GET_MR() __raw_readl(usart_regs + ATMEL_US_MR)
#define UART_PUT_MR(v) __raw_writel(v, usart_regs + ATMEL_US_MR)
#define UART_PUT_IER(v) __raw_writel(v, usart_regs + ATMEL_US_IER)
#define UART_PUT_IDR(v) __raw_writel(v, usart_regs + ATMEL_US_IDR)
#define UART_GET_IMR() __raw_readl(usart_regs + ATMEL_US_IMR)
#define UART_GET_CSR() __raw_readl(usart_regs + ATMEL_US_CSR)
#define UART_GET_CHAR() __raw_readl(usart_regs + ATMEL_US_RHR)
#define UART_PUT_CHAR(v) __raw_writel(v, usart_regs + ATMEL_US_THR)
#define UART_GET_BRGR() __raw_readl(usart_regs + ATMEL_US_BRGR)
#define UART_PUT_BRGR(v) __raw_writel(v, usart_regs + ATMEL_US_BRGR)
#define UART_PUT_RTOR(v) __raw_writel(v, usart_regs + ATMEL_US_RTOR)
#define UART_PUT_TTGR(v) __raw_writel(v, usart_regs + ATMEL_US_TTGR)
X
/* PDC registers */
#define UART_PUT_PTCR(v) __raw_writel(v, usart_regs + ATMEL_PDC_PTCR)
#define UART_GET_PTSR() __raw_readl(usart_regs + ATMEL_PDC_PTSR)
X
#define UART_PUT_RPR(v) __raw_writel(v, usart_regs + ATMEL_PDC_RPR)
#define UART_GET_RPR() __raw_readl(usart_regs + ATMEL_PDC_RPR)
#define UART_PUT_RCR(v) __raw_writel(v, usart_regs + ATMEL_PDC_RCR)
#define UART_PUT_RNPR(v) __raw_writel(v, usart_regs + ATMEL_PDC_RNPR)
#define UART_PUT_RNCR(v) __raw_writel(v, usart_regs + ATMEL_PDC_RNCR)
X
#define UART_PUT_TPR(v) __raw_writel(v, usart_regs + ATMEL_PDC_TPR)
#define UART_PUT_TCR(v) __raw_writel(v, usart_regs + ATMEL_PDC_TCR)
#define UART_GET_TCR() __raw_readl(usart_regs + ATMEL_PDC_TCR)
X
#define PDC_BUFFER_SIZE (1024)
#define ATMEL_ISR_PASS_LIMIT (256)
//Variables
X
static dev_t my_dev=0;
static uint32_t read_status_mask = 0;
static struct cdev * my_cdev = NULL;
static struct class *class_wzusart_priv = NULL;
static dma_addr_t rx_dma_buf = 0;
static void * rx_virt_buf = NULL;
static void * rx_mmap_buf = NULL;
atomic_t rx_mbuf_wptr = ATOMIC_INIT(0);
atomic_t rx_mbuf_rptr = ATOMIC_INIT(0);
static unsigned int rx_mbuf_len = (1<<19);
static unsigned int rx_mbuf_ptr_mask = (1<<19)-1;
static unsigned int rx_mbuf_err_flag = 0;
static unsigned int rx_mbuf_wakeup_thr = 0;
X
static unsigned int dma_size = 0;
static dma_addr_t dma_addr[2] = {0,0};
static void * dma_buf[2] = {NULL, NULL};
static unsigned int dma_ofs[2] = {0,0};
unsigned int rx_idx = 0;
X
static void * usart_regs = NULL;
X
static struct tasklet_struct tasklet_receive;
static void tasklet_receive_func(unsigned long data);
X
static unsigned char wzusart_opened = 0;
static unsigned char irq_reserved = 0;
static unsigned char regs_reserved = 0;
X
//Function prototypes
irqreturn_t wzusart_irq(int irq, void *dev_id);
int wzusart_open(struct inode *inode, struct file *filp);
int wzusart_release(struct inode *inode, struct file *filp);
long wzusart_ioctl (struct file *filp,
X unsigned int cmd, unsigned long arg);
X
//Wait queues
DECLARE_WAIT_QUEUE_HEAD (read_queue);
X
/* The ioctl commands should be assigned according to LDD3 chapter 6
X however for this "quick&dirty" example I have decided just
X tu use 3 arbitrary selected values */
X
long wzusart_ioctl (struct file *filp,
X unsigned int cmd, unsigned long arg)
{
X //printk("<1> wzdaq - ioctl command: %d %x \n", cmd, cmd);
X switch(cmd) {
X case 0xa530:
X /*
X * Set wakeup threshold - used to sleep user application
X * waiting for data
X */
X rx_mbuf_wakeup_thr = arg;
X return 0;
X case 0xa531:
X /* Inform the user application about the length of the buffer */
X return rx_mbuf_len;
X case 0xa532:
X /* Read the read and write pointers
X We assume, that arg is a pointer pointing to structure
X containing two unsigned integers rptr and wptr
X The returned value is number of bytes available for reading
X if everything is OK, or a negative number equal to
X -1-number_of_available_bytes if data reception was too slow
X and some data are dropped (-1 is added to allow detection of
X error even if number of bytes in buffer is 0).
X */
X {
X void * res = (void *) arg;
X unsigned int pointers[2];
X if(!access_ok(VERIFY_WRITE,res,sizeof(pointers))) {
X return -EFAULT;
X } else {
X pointers[0]=atomic_read(&rx_mbuf_wptr);
X pointers[1]=atomic_read(&rx_mbuf_rptr);
X __copy_to_user(res,pointers,sizeof(pointers));
X if(rx_mbuf_err_flag)
X return -1-((pointers[0]-pointers[1]) & rx_mbuf_ptr_mask);
X else
X return (pointers[0]-pointers[1]) & rx_mbuf_ptr_mask;
X }
X }
X case 0xa533: //Update the read pointer
X //The argument contains information about the number of bytes
X //consumed by the application
X {
X unsigned int rptr;
X unsigned int wptr;
X unsigned int available_data;
X //We need to check if the amount of consumed data is correct
X wptr = atomic_read(&rx_mbuf_wptr);
X rptr = atomic_read(&rx_mbuf_rptr);
X available_data = (wptr - rptr) & rx_mbuf_ptr_mask;
X if(arg>available_data) return -EINVAL;
X //If the number of consumed bytes is correct, update the number of bytes
X rptr = (rptr + arg) & rx_mbuf_ptr_mask;
X atomic_set(&rx_mbuf_rptr,rptr);
X }
X return 0;
X }
X return -EINVAL;
}
X
X
/* Memory mapping support */
void wzusart_vma_open (struct vm_area_struct * area)
{ }
X
void wzusart_vma_close (struct vm_area_struct * area)
{ }
X
static struct vm_operations_struct wzusart_vm_ops = {
X wzusart_vma_open,
X wzusart_vma_close,
};
X
/*
X MMap implementation
*/
int wzusart_mmap(struct file *filp,
X struct vm_area_struct *vma)
{
X unsigned long vsize = vma->vm_end - vma->vm_start;
X unsigned long psize = rx_mbuf_len;
X if(vsize>psize)
X return -EINVAL;
X remap_vmalloc_range(vma,rx_mmap_buf, 0);
X if (vma->vm_ops)
X return -EINVAL; //It should never happen...
X vma->vm_ops = &wzusart_vm_ops;
X wzusart_vma_open(vma); //No open(vma) was called, we have called it ourselves
X return 0;
}
X
X
/*
X Implementation of the poll method
*/
unsigned int wzusart_poll(struct file *filp,poll_table *wait)
{
X unsigned int mask =0;
X unsigned int rptr, wptr, data_available;
X poll_wait(filp,&read_queue,wait);
X rptr = atomic_read(&rx_mbuf_rptr);
X wptr = atomic_read(&rx_mbuf_wptr);
X data_available = (wptr - rptr) & rx_mbuf_ptr_mask;
X if(data_available>=rx_mbuf_wakeup_thr) mask |= POLLIN |POLLRDNORM;
X //Check if the error occured
X if(rx_mbuf_err_flag) mask |= POLLERR;
X return mask;
}
X
int wzusart_open(struct inode *inode, struct file *filp)
{
X //The device may be opened only once!
X if (wzusart_opened) {
X //Device is already opened
X return -EINVAL;
X }
X wzusart_opened = 1;
X //Initialize mmaped buffer for transmission
X atomic_set(&rx_mbuf_rptr,0);
X atomic_set(&rx_mbuf_wptr,0);
X rx_mbuf_err_flag = 0; //clear the error flag
X //Initialize data used to manage the DMA buffer
X dma_size = PDC_BUFFER_SIZE;
X dma_buf[0] = rx_virt_buf;
X dma_buf[1] = rx_virt_buf + dma_size;
X dma_addr[0] = rx_dma_buf;
X dma_addr[1] = rx_dma_buf + dma_size;
X dma_ofs[0] = dma_ofs[1] = 0;
X rx_idx = 0;
X
X //Reset both transmitter and receiver
X smp_mb();
X UART_PUT_CR(ATMEL_US_RXDIS | ATMEL_US_TXDIS | ATMEL_US_RSTRX | ATMEL_US_RSTTX);
X smp_mb();
X //Initialize PDC part of USART
X UART_PUT_PTCR(ATMEL_PDC_TXTDIS);
X smp_mb();
X UART_PUT_PTCR(ATMEL_PDC_RXTDIS);
X smp_mb();
X UART_PUT_RPR(rx_dma_buf);
X smp_mb();
X UART_PUT_RNPR(rx_dma_buf+PDC_BUFFER_SIZE);
X smp_mb();
X UART_PUT_RCR(PDC_BUFFER_SIZE);
X smp_mb();
X UART_PUT_RNCR(PDC_BUFFER_SIZE);
X smp_mb();
X //Initialize USART transmission parameters
X UART_PUT_MR(ATMEL_US_USMODE_NORMAL | ATMEL_US_USCLKS_SCK | ATMEL_US_CHRL_8 |
X ATMEL_US_SYNC | ATMEL_US_PAR_NONE | ATMEL_US_NBSTOP_1 |
X ATMEL_US_CHMODE_NORMAL );
X smp_mb();
X //Set the timeout
X UART_PUT_RTOR(60); //This value is suited to my protocol.
X //You may need to replace it with a different one!
X smp_mb();
X /* enable PDC controller */
X UART_PUT_IER( ATMEL_US_ENDRX | ATMEL_US_TIMEOUT | read_status_mask);
X smp_mb();
X UART_PUT_PTCR(ATMEL_PDC_RXTEN);
X smp_mb();
X //Finally start the reception
X UART_PUT_CR(ATMEL_US_RXEN | ATMEL_US_RSTSTA | ATMEL_US_STTTO );
X smp_mb();
X return 0;
}
X
/* Implementation of the release method */
int wzusart_release(struct inode *inode, struct file *filp)
{
X smp_mb();
X //Disable transmitter and receiver
X UART_PUT_CR(ATMEL_US_RXDIS | ATMEL_US_TXDIS | ATMEL_US_RSTRX | ATMEL_US_RSTTX);
X smp_mb();
X //Disable interrupts
X UART_PUT_IDR(ATMEL_US_ENDRX | ATMEL_US_TIMEOUT | read_status_mask);
X smp_mb();
X //Disable PDC - both transmitter and receiver
X UART_PUT_PTCR(ATMEL_PDC_TXTDIS);
X smp_mb();
X UART_PUT_PTCR(ATMEL_PDC_RXTDIS);
X smp_mb();
X wzusart_opened = 0;
X return 0;
}
X
static void put_data_to_buf(void * src, unsigned int count)
{
X //We calculate, if the amount of free space in the buffer is sufficient
X //If yes - copy the data
X //If not - drop the data and set error flag!
X //What is important - we don't need locking!
X
X //Calculate the amount of free space
X unsigned int free_space, rptr, wptr;
X rptr = atomic_read(&rx_mbuf_rptr);
X wptr = atomic_read(&rx_mbuf_wptr);
X free_space = (rptr - wptr - 1) & rx_mbuf_ptr_mask;
X if(unlikely(count>=free_space)) {
X //No space in the buffer
X //Set the error flag
X rx_mbuf_err_flag = 1;
X return;
X } else {
X //We can copy the data, but we must handle the case, when
X //part of the data goes to the end of the circular buffer,
X //and rest to the begining...
X while(count) {
X unsigned int to_copy = min(rx_mbuf_len-wptr,count);
X memcpy(rx_mmap_buf+wptr,src,to_copy);
X count -= to_copy;
X wptr = (wptr + to_copy) & rx_mbuf_ptr_mask;
X }
X //Update the write pointer, but make sure, that all other data
X //are written first...
X smp_mb();
X atomic_set(&rx_mbuf_wptr,wptr);
X }
}
X
/* The tasklet receive function has been almost fully copied from
X * the original atmel_serial.c driver
X */
X
static void tasklet_receive_func(unsigned long data)
{
X unsigned int head;
X unsigned int tail;
X unsigned int count;
X
X do {
X /* Reset the UART timeout early so that we don't miss one */
X UART_PUT_CR(ATMEL_US_STTTO);
X head = UART_GET_RPR() - dma_addr[rx_idx];
X tail = dma_ofs[rx_idx];
X
X /* If the PDC has switched buffers, RPR won't contain
X * any address within the current buffer. Since head
X * is unsigned, we just need a one-way comparison to
X * find out.
X *
X * In this case, we just need to consume the entire
X * buffer and resubmit it for DMA. This will clear the
X * ENDRX bit as well, so that we can safely re-enable
X * all interrupts below.
X */
X head = min(head, dma_size);
X
X if (likely(head != tail)) {
X /*
X * head will only wrap around when we recycle
X * the DMA buffer, and when that happens, we
X * explicitly set tail to 0. So head will
X * always be greater than tail.
X */
X count = head - tail;
X
X put_data_to_buf(dma_buf[rx_idx] + dma_ofs[rx_idx], count);
X
X dma_ofs[rx_idx] = head;
X }
X
X /*
X * If the current buffer is full, we need to check if
X * the next one contains any additional data.
X */
X if (head >= dma_size) {
X dma_ofs[rx_idx] = 0;
X UART_PUT_RNPR(dma_addr[rx_idx]);
X UART_PUT_RNCR(dma_size);
X
X rx_idx = !rx_idx;
X }
X } while (head >= dma_size);
X
X UART_PUT_IER(ATMEL_US_ENDRX | ATMEL_US_TIMEOUT);
X wake_up_interruptible(&read_queue);
}
X
X
/*
X * receive interrupt handler - also almost fully
X * copied from the atmel_serial.c driver - only the transmitter
X * part was removed.
X */
static inline void
atmel_handle_receive(unsigned int pending)
{
X /*
X * PDC receive. Just schedule the tasklet and let it
X * figure out the details.
X *
X * TODO: We're not handling error flags correctly at
X * the moment.
X */
X if (pending & (ATMEL_US_ENDRX | ATMEL_US_TIMEOUT)) {
X UART_PUT_IDR((ATMEL_US_ENDRX
X | ATMEL_US_TIMEOUT));
X tasklet_schedule(&tasklet_receive);
X }
X
X if (pending & (ATMEL_US_RXBRK | ATMEL_US_OVRE |
X ATMEL_US_FRAME | ATMEL_US_PARE))
X {
X //No statistixs are collected (yet?)
X UART_PUT_CR(ATMEL_US_RSTSTA);
X }
}
X
//USART irq is triggered when one of DMA buffers is filled or when transmission
//was stopped for significant time (end of data)
irqreturn_t wzusart_irq(int irq, void *dev_id)
{
X unsigned int status, pending, pass_counter = 0;
X
X do {
X status = UART_GET_CSR();
X pending = status & UART_GET_IMR();
X if (!pending)
X break;
X atmel_handle_receive(pending);
X } while (pass_counter++ < ATMEL_ISR_PASS_LIMIT);
X
X return pass_counter ? IRQ_HANDLED : IRQ_NONE; //Read the status of USART
}
X
X
struct file_operations wzusart_fops = {
X .owner=THIS_MODULE,
X .open=wzusart_open,
X .unlocked_ioctl=wzusart_ioctl,
X .poll=wzusart_poll,
X .mmap=wzusart_mmap,
X .release=wzusart_release,
};
X
void wzusart_clean_up(void)
{
X //Free the IRQ
X if (irq_reserved) {
X free_irq(WZ_USART_IRQ,THIS_MODULE);
X irq_reserved = 0;
X }
X //Unmap registers
X if (usart_regs) {
X iounmap(usart_regs);
X usart_regs = NULL;
X }
X if(regs_reserved) {
X release_mem_region(WZ_USART_BASE,0x4000);
X regs_reserved = 0;
X }
X //Unmap the buffer
X if (rx_virt_buf) {
X iounmap(rx_virt_buf);
X rx_virt_buf = NULL;
X }
X //Free the buffer
X if (rx_dma_buf) {
X release_mem_region(rx_dma_buf,2*PDC_BUFFER_SIZE);
X rx_dma_buf = 0;
X }
X if(my_dev && class_wzusart_priv) {
X device_destroy(class_wzusart_priv,my_dev);
X }
X if(my_cdev) {
X cdev_del(my_cdev);
X my_cdev=NULL;
X }
X if(my_dev) {
X unregister_chrdev_region(my_dev, 1);
X }
X if(class_wzusart_priv) {
X class_destroy(class_wzusart_priv);
X class_wzusart_priv=NULL;
X }
X WZ_USART_RELEASE();
}
X
static int __init wzusart_init(void)
{
X int res=0;
X WZ_USART_INIT();
X //Reserve the DMA buffer in internal SRAM
X if (request_mem_region(AT91SAM9260_SRAM0_BASE, 2*PDC_BUFFER_SIZE, "wzusart")) {
X rx_dma_buf = AT91SAM9260_SRAM0_BASE;
X } else if (request_mem_region(AT91SAM9260_SRAM1_BASE, 2*PDC_BUFFER_SIZE, "wzusart")) {
X rx_dma_buf = AT91SAM9260_SRAM1_BASE;
X } else {
X //We were not able to allocate the DMA buffer in SRAM
X return -ENOMEM;
X }
X //Create mapping for the allocated buffer
X rx_virt_buf = ioremap(rx_dma_buf,2*PDC_BUFFER_SIZE);
X if(!rx_virt_buf) {
X res = -ENOMEM;
X goto err1;
X }
X //Alocate the area for USART registers
X if(!request_mem_region(WZ_USART_BASE, 0x4000, "wzusart")) {
X printk(KERN_DEBUG"Can't get access to USART registers\n");
X regs_reserved = 0;
X goto err1;
X } else {
X regs_reserved = 1;
X }
X //Create mapping for registers
X usart_regs=ioremap(WZ_USART_BASE, 0x4000);
X if(!usart_regs) {
X res = -ENOMEM;
X goto err1;
X }
X //Allocate the buffer for received data, which will be mmapped to the application
X rx_mmap_buf=vmalloc_32_user(rx_mbuf_len);
X //This buffer will be finally mapped with remap_vmalloc_range
X //Lock the buffer in memory (as it will be used in the interrupt context!)
X //Is the memory returned by vmalloc already locked?
X //Register the interrupt service routine
X UART_PUT_IDR(-1); //Mask all interrupts
X res=request_irq(WZ_USART_IRQ, wzusart_irq, IRQF_SHARED,"WZ_USART_interrupt", THIS_MODULE);
X if (res) {
X printk(KERN_DEBUG"Can't register IRQ %d\n", WZ_USART_IRQ);
X irq_reserved = 0;
X goto err1;
X } else {
X irq_reserved = 1;
X }
X //Register the device
X res=alloc_chrdev_region(&my_dev, 0, 1, DEVICE_NAME);
X if(res) {
X printk ("<1>Allocation of the device number for %s failed\n",
X DEVICE_NAME);
X return res;
X };
X class_wzusart_priv = class_create(THIS_MODULE, "wzusart_class");
X if (IS_ERR(class_wzusart_priv)) {
X printk(KERN_ERR "Error creating wzusart_class.\n");
X res=PTR_ERR(class_wzusart_priv);
X goto err1;
X }
X my_cdev = cdev_alloc();
X my_cdev->ops = &wzusart_fops;
X my_cdev->owner = THIS_MODULE;
X res=cdev_add(my_cdev, my_dev, 1);
X if(res) {
X printk ("<1>Registration of the device %s failed\n",
X DEVICE_NAME);
X res=-EFAULT;
X goto err1;
X };
X device_create(class_wzusart_priv,NULL,my_dev,NULL,"wzusart_%d",MINOR(my_dev));
X printk ("<1>%s The major device number is %d.\n",
X "Registration is a success.",
X MAJOR(my_dev));
X tasklet_init(&tasklet_receive, tasklet_receive_func,0);
X return res;
X err1:
X wzusart_clean_up();
X return res;
}
module_init(wzusart_init);
X
static void __exit wzusart_exit(void)
{
X wzusart_clean_up();
}
module_exit(wzusart_exit);
X
/* Information about this module */
MODULE_DESCRIPTION("Lite high speed sync mode Atmel USART driver for DAQ system."
X "The driver is significantly based on original atmel_serial.c driver");
MODULE_AUTHOR("Wojciech M. Zabolotny, Institute of Electronic Systems");
MODULE_LICENSE("Dual BSD/GPL");
//MODULE_LICENSE("GPL");
SHAR_EOF
(set 20 11 11 19 13 43 36 'wzusart.c'
eval "${shar_touch}") && \
chmod 0644 'wzusart.c'
if test $? -ne 0
then ${echo} "restore of wzusart.c failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'wzusart.c': 'MD5 check failed'
) << \SHAR_EOF
f5ca6fe7c5fa22b0d157b73c2fdc8d41 wzusart.c
SHAR_EOF
else
test `LC_ALL=C wc -c < 'wzusart.c'` -ne 19792 && \
${echo} "restoration warning: size of 'wzusart.c' is not 19792"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0



0 new messages