diff options
Diffstat (limited to 'drivers/staging/dt3155')
-rw-r--r-- | drivers/staging/dt3155/Kconfig | 4 | ||||
-rw-r--r-- | drivers/staging/dt3155/Makefile | 6 | ||||
-rw-r--r-- | drivers/staging/dt3155/TODO | 10 | ||||
-rw-r--r-- | drivers/staging/dt3155/allocator.README | 98 | ||||
-rw-r--r-- | drivers/staging/dt3155/allocator.c | 296 | ||||
-rw-r--r-- | drivers/staging/dt3155/allocator.h | 28 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155.h | 171 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155.sysvinit | 60 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_drv.c | 1087 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_drv.h | 45 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_io.c | 175 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_io.h | 358 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_isr.c | 516 | ||||
-rw-r--r-- | drivers/staging/dt3155/dt3155_isr.h | 77 |
14 files changed, 2931 insertions, 0 deletions
diff --git a/drivers/staging/dt3155/Kconfig b/drivers/staging/dt3155/Kconfig new file mode 100644 index 000000000000..4a3293c721b1 --- /dev/null +++ b/drivers/staging/dt3155/Kconfig @@ -0,0 +1,4 @@ +config DT3155 + tristate "DT3155 Digitizer support" + depends on PCI + diff --git a/drivers/staging/dt3155/Makefile b/drivers/staging/dt3155/Makefile new file mode 100644 index 000000000000..136f21fdbbee --- /dev/null +++ b/drivers/staging/dt3155/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_DT3155) += dt3155.o +dt3155-objs := \ + dt3155_drv.o \ + dt3155_isr.o \ + dt3155_io.o \ + allocator.o diff --git a/drivers/staging/dt3155/TODO b/drivers/staging/dt3155/TODO new file mode 100644 index 000000000000..3baa3b6294cc --- /dev/null +++ b/drivers/staging/dt3155/TODO @@ -0,0 +1,10 @@ +TODO: + - fix checkpatch.pl issues + - remove old kernel support, it is not needed + - convert to proper PCI device API + - fix sparse warnings + - audit for correct subsystem interaction + - review review review! + +Please send patches to Greg Kroah-Hartman <greg@kroah.com> +and Scott Smedley <ss@aao.gov.au> diff --git a/drivers/staging/dt3155/allocator.README b/drivers/staging/dt3155/allocator.README new file mode 100644 index 000000000000..05700b6c926c --- /dev/null +++ b/drivers/staging/dt3155/allocator.README @@ -0,0 +1,98 @@ + +The allocator shown here exploits high memory. This document explains +how a user can deal with drivers uses this allocator and how a +programmer can link in the module. + +The module is being used by my pxc and pxdrv device drivers (as well as +other ones), available from ftp.systemy.it/pub/develop and +ftp.linux.it/pub/People/Rubini + + User's manual + ============= + + +One of the most compelling problems with any DMA-capable device is the +allocation of a suitable memory buffer. The "allocator" module tries +to deal with the problem in a clean way. The module is able to use +high memory (above the one used in normal operation) for DMA +allocation. + +To prevent the kernel for using high memory, so that it remains +available for DMA, you should pass a command line argument to the +kernel. Command line arguments can be passed to Lilo, to Loadlin or +to whichever loader you are using (unless it's very poor in design). +For Lilo, either use "append=" in /etc/lilo.conf or add commandline +arguments to the interactive prompt. For example, I have a 32MB box +and reserve two megs for DMA: + +In lilo.conf: + image = /zImage + label = linux + append = "mem=30M" + +Or, interactively: + LILO: linux mem=30M + +Once the kernel is booted with the right command-line argument, any +driver linked with the allocator module will be able to get +DMA-capable memory without much trouble (unless the various drivers +need more memory than available). + +The module implements an alloc/free mechanism, so that it can serve +multiple drivers at the same time. Note however that the allocator +uses all of high memory and assumes to be the only piece of software +using such memory. + + + Programmer's manual + =================== + +The allocator, as released, is designed to be linked to a device +driver. In this case, the driver must call allocator_init() before +using the allocator and must call allocator_cleanup() before +unloading. This is usually done from within init_module() and +cleanup_module(). If the allocator is linked to a driver, it won't be +possible for several drivers to allocate high DMA memory, as explained +above. + +It is possible, on the other hand, to compile the module as a standalone +module, so that several modules can rely on the allocator for they DMA +buffers. To compile the allocator as a standalone module, do the +following in this directory (or provide a suitable Makefile, or edit +the source code): + + make allocator.o CC="gcc -Dallocator_init=init_module -Dallocator_cleanup=cleanup_module -include /usr/include/linux/module.h" + +The previous commandline tells to include <linux/module.h> in the +first place, and to rename the init and cleanup function to the ones +needed for module loading and unloading. Drivers using a standalone +allocator won't need to call allocator_init() nor allocator_cleanup(). + +The allocator exports the following functions (declared in allocator.h): + + unsigned long allocator_allocate_dma (unsigned long kilobytes, + int priority); + + This function returns a physical address, over high_memory, + which corresponds to an area of at least "kilobytes" kilobytes. + The area will be owned by the module calling the function. + The returned address can be passed to device boards, to instruct + their DMA controllers, via phys_to_bus(). The address can be used + by C code after vremap()/ioremap(). The "priority" argument should + be GFP_KERNEL or GFP_ATOMIC, according to the context of the + caller; it is used to call kmalloc(), as the allocator must keep + track of any region it gives away. In case of error the function + returns 0, and the caller is expected to issue a -ENOMEM error. + + + void allocator_free_dma (unsigned long address); + + This function is the reverse of the previous one. If a driver + doesn't free the DMA memory it allocated, the allocator will + consider such memory as busy. Note, however, that + allocator_cleanup() calls kfree() on every region it reclaimed, + so that a driver with the allocator linked in can avoid calling + allocator_free_dma() at unload time. + + + diff --git a/drivers/staging/dt3155/allocator.c b/drivers/staging/dt3155/allocator.c new file mode 100644 index 000000000000..db382ef90217 --- /dev/null +++ b/drivers/staging/dt3155/allocator.c @@ -0,0 +1,296 @@ +/* + * allocator.c -- allocate after high_memory, if available + * + * NOTE: this is different from my previous allocator, the one that + * assembles pages, which revealed itself both slow and unreliable. + * + * Copyright (C) 1998 rubini@linux.it (Alessandro Rubini) + * + * 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. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 02-Aug-2002 NJC allocator now steps in 1MB increments, rather + than doubling its size each time. + Also, allocator_init(u32 *) now returns + (in the first arg) the size of the free + space. This is no longer consistent with + using the allocator as a module, and some changes + may be necessary for that purpose. This was + designed to work with the DT3155 driver, in + stand alone mode only!!! + 26-Oct-2009 SS Port to 2.6.30 kernel. + */ + + +#ifndef __KERNEL__ +# define __KERNEL__ +#endif +#ifndef MODULE +# define MODULE +#endif + +#include <linux/version.h> + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mm.h> /* PAGE_ALIGN() */ +#include <linux/io.h> +#include <linux/slab.h> + +#include <asm/page.h> + +/*#define ALL_DEBUG*/ +#define ALL_MSG "allocator: " + +#undef PDEBUG /* undef it, just in case */ +#ifdef ALL_DEBUG +# define __static +# define DUMP_LIST() dump_list() +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk(KERN_DEBUG ALL_MSG fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +# define DUMP_LIST() +# define __static static +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) +/*#define PDEBUGG(fmt, args...) printk( KERN_DEBUG ALL_MSG fmt, ## args)*/ + + +int allocator_himem = 1; /* 0 = probe, pos. = megs, neg. = disable */ +int allocator_step = 1; /* This is the step size in MB */ +int allocator_probe = 1; /* This is a flag -- 1=probe, 0=don't probe */ + +static unsigned long allocator_buffer; /* physical address */ +static unsigned long allocator_buffer_size; /* kilobytes */ + +/* + * The allocator keeps a list of DMA areas, so multiple devices + * can coexist. The list is kept sorted by address + */ + +struct allocator_struct { + unsigned long address; + unsigned long size; + struct allocator_struct *next; +}; + +struct allocator_struct *allocator_list; + + +#ifdef ALL_DEBUG +static int dump_list(void) +{ + struct allocator_struct *ptr; + + PDEBUG("Current list:\n"); + for (ptr = allocator_list; ptr; ptr = ptr->next) + PDEBUG("0x%08lx (size %likB)\n", ptr->address, ptr->size>>10); + return 0; +} +#endif + +/* ======================================================================== + * This function is the actual allocator. + * + * If space is available in high memory (as detected at load time), that + * one is returned. The return value is a physical address (i.e., it can + * be used straight ahead for DMA, but needs remapping for program use). + */ + +unsigned long allocator_allocate_dma(unsigned long kilobytes, int prio) +{ + struct allocator_struct *ptr = allocator_list, *newptr; + unsigned long bytes = kilobytes << 10; + + /* check if high memory is available */ + if (!allocator_buffer) + return 0; + + /* Round it to a multiple of the pagesize */ + bytes = PAGE_ALIGN(bytes); + PDEBUG("request for %li bytes\n", bytes); + + while (ptr && ptr->next) { + if (ptr->next->address - (ptr->address + ptr->size) >= bytes) + break; /* enough space */ + ptr = ptr->next; + } + if (!ptr->next) { + DUMP_LIST(); + PDEBUG("alloc failed\n"); + return 0; /* end of list */ + } + newptr = kmalloc(sizeof(struct allocator_struct), prio); + if (!newptr) + return 0; + + /* ok, now stick it after ptr */ + newptr->address = ptr->address + ptr->size; + newptr->size = bytes; + newptr->next = ptr->next; + ptr->next = newptr; + + DUMP_LIST(); + PDEBUG("returning 0x%08lx\n", newptr->address); + return newptr->address; +} + +int allocator_free_dma(unsigned long address) +{ + struct allocator_struct *ptr = allocator_list, *prev; + + while (ptr && ptr->next) { + if (ptr->next->address == address) + break; + ptr = ptr->next; + } + /* the one being freed is ptr->next */ + prev = ptr; ptr = ptr->next; + + if (!ptr) { + printk(KERN_ERR ALL_MSG + "free_dma(0x%08lx) but add. not allocated\n", + ptr->address); + return -EINVAL; + } + PDEBUGG("freeing: %08lx (%li) next %08lx\n", ptr->address, ptr->size, + ptr->next->address); + prev->next = ptr->next; + kfree(ptr); + + /* dump_list(); */ + return 0; +} + +/* ======================================================================== + * Init and cleanup + * + * On cleanup everything is released. If the list is not empty, that a + * problem of our clients + */ +int allocator_init(u32 *allocator_max) +{ + /* check how much free memory is there */ + void *remapped; + unsigned long max; + unsigned long trial_size = allocator_himem<<20; + unsigned long last_trial = 0; + unsigned long step = allocator_step<<20; + unsigned long i = 0; + struct allocator_struct *head, *tail; + char test_string[] = "0123456789abcde"; /* 16 bytes */ + + PDEBUGG("himem = %i\n", allocator_himem); + if (allocator_himem < 0) /* don't even try */ + return -EINVAL; + + if (!trial_size) + trial_size = 1<<20; /* not specified: try one meg */ + + while (1) { + remapped = ioremap(__pa(high_memory), trial_size); + if (!remapped) { + PDEBUGG("%li megs failed!\n", trial_size>>20); + break; + } + PDEBUGG("Trying %li megs (at %p, %p)\n", trial_size>>20, + (void *)__pa(high_memory), remapped); + for (i = last_trial; i < trial_size; i += 16) { + strcpy((char *)(remapped)+i, test_string); + if (strcmp((char *)(remapped)+i, test_string)) + break; + } + iounmap((void *)remapped); + schedule(); + last_trial = trial_size; + if (i == trial_size) + trial_size += step; /* increment, if all went well */ + else { + PDEBUGG("%li megs copy test failed!\n", trial_size>>20); + break; + } + if (!allocator_probe) + break; + } + PDEBUG("%li megs (%li k, %li b)\n", i>>20, i>>10, i); + allocator_buffer_size = i>>10; /* kilobytes */ + allocator_buffer = __pa(high_memory); + if (!allocator_buffer_size) { + printk(KERN_WARNING ALL_MSG "no free high memory to use\n"); + return -ENOMEM; + } + + /* + * to simplify things, always have two cells in the list: + * the first and the last. This avoids some conditionals and + * extra code when allocating and deallocating: we only play + * in the middle of the list + */ + head = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL); + if (!head) + return -ENOMEM; + tail = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL); + if (!tail) { + kfree(head); + return -ENOMEM; + } + + max = allocator_buffer_size<<10; + + head->size = tail->size = 0; + head->address = allocator_buffer; + tail->address = allocator_buffer + max; + head->next = tail; + tail->next = NULL; + allocator_list = head; + + /* Back to the user code, in KB */ + *allocator_max = allocator_buffer_size; + + return 0; /* ok, ready */ +} + +void allocator_cleanup(void) +{ + struct allocator_struct *ptr, *next; + + for (ptr = allocator_list; ptr; ptr = next) { + next = ptr->next; + PDEBUG("freeing list: 0x%08lx\n", ptr->address); + kfree(ptr); + } + + allocator_buffer = 0; + allocator_buffer_size = 0; + allocator_list = NULL; +} + + diff --git a/drivers/staging/dt3155/allocator.h b/drivers/staging/dt3155/allocator.h new file mode 100644 index 000000000000..bdf3268ca52d --- /dev/null +++ b/drivers/staging/dt3155/allocator.h @@ -0,0 +1,28 @@ +/* + * allocator.h -- prototypes for allocating high memory + * + * NOTE: this is different from my previous allocator, the one that + * assembles pages, which revealed itself both slow and unreliable. + * + * Copyright (C) 1998 rubini@linux.it (Alessandro Rubini) + * + * 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. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +void allocator_free_dma(unsigned long address); +unsigned long allocator_allocate_dma(unsigned long kilobytes, int priority); +int allocator_init(u32 *); +void allocator_cleanup(void); diff --git a/drivers/staging/dt3155/dt3155.h b/drivers/staging/dt3155/dt3155.h new file mode 100644 index 000000000000..1bf786364eec --- /dev/null +++ b/drivers/staging/dt3155/dt3155.h @@ -0,0 +1,171 @@ +/* + +Copyright 1996,2002,2005 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Jason Lapenta, Scott Smedley + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 03-Jul-2000 JML n/a + 10-Oct-2001 SS port to 2.4 kernel. + 24-Jul-2002 SS remove unused code & added GPL licence. + 05-Aug-2005 SS port to 2.6 kernel; make CCIR mode default. + +*/ + +#ifndef _DT3155_INC +#define _DT3155_INC + +#ifdef __KERNEL__ +#include <linux/types.h> +#include <linux/time.h> /* struct timeval */ +#else +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/time.h> +#include <unistd.h> +#endif + + +#define TRUE 1 +#define FALSE 0 + +/* Uncomment this for 50Hz CCIR */ +#define CCIR 1 + +/* Can be 1 or 2 */ +#define MAXBOARDS 1 + +#define BOARD_MAX_BUFFS 3 +#define MAXBUFFERS (BOARD_MAX_BUFFS*MAXBOARDS) + +#define PCI_PAGE_SIZE (1 << 12) + +#ifdef CCIR +#define DT3155_MAX_ROWS 576 +#define DT3155_MAX_COLS 768 +#define FORMAT50HZ TRUE +#else +#define DT3155_MAX_ROWS 480 +#define DT3155_MAX_COLS 640 +#define FORMAT50HZ FALSE +#endif + +/* Configuration structure */ +struct dt3155_config_s { + u32 acq_mode; + u32 cols, rows; + u32 continuous; +}; + + +/* hold data for each frame */ +typedef struct { + u32 addr; /* address of the buffer with the frame */ + u32 tag; /* unique number for the frame */ + struct timeval time; /* time that capture took place */ +} frame_info_t; + +/* + * Structure for interrupt and buffer handling. + * This is the setup for 1 card + */ +struct dt3155_fbuffer_s { + int nbuffers; + + frame_info_t frame_info[BOARD_MAX_BUFFS]; + + int empty_buffers[BOARD_MAX_BUFFS]; /* indexes empty frames */ + int empty_len; /* Number of empty buffers */ + /* Zero means empty */ + + int active_buf; /* Where data is currently dma'ing */ + int locked_buf; /* Buffers used by user */ + + int ready_que[BOARD_MAX_BUFFS]; + u32 ready_head; /* The most recent buffer located here */ + u32 ready_len; /* The number of ready buffers */ + + int even_happened; + int even_stopped; + + int stop_acquire; /* Flag to stop interrupts */ + u32 frame_count; /* Counter for frames acquired by this card */ +}; + + + +#define DT3155_MODE_FRAME 1 +#define DT3155_MODE_FIELD 2 + +#define DT3155_SNAP 1 +#define DT3155_ACQ 2 + +/* There is one status structure for each card. */ +typedef struct dt3155_status_s { + int fixed_mode; /* if 1, we are in fixed frame mode */ + u32 reg_addr; /* Register address for a single card */ + u32 mem_addr; /* Buffer start addr for this card */ + u32 mem_size; /* This is the amount of mem available */ + u32 irq; /* this card's irq */ + struct dt3155_config_s config; /* configuration struct */ + struct dt3155_fbuffer_s fbuffer; /* frame buffer state struct */ + u32 state; /* this card's state */ + u32 device_installed; /* Flag if installed. 1=installed */ +} dt3155_status_t; + +/* Reference to global status structure */ +extern struct dt3155_status_s dt3155_status[MAXBOARDS]; + +#define DT3155_STATE_IDLE 0x00 +#define DT3155_STATE_FRAME 0x01 +#define DT3155_STATE_FLD 0x02 +#define DT3155_STATE_STOP 0x100 +#define DT3155_STATE_ERROR 0x200 +#define DT3155_STATE_MODE 0x0ff + +#define DT3155_IOC_MAGIC '!' + +#define DT3155_SET_CONFIG _IOW(DT3155_IOC_MAGIC, 1, struct dt3155_config_s) +#define DT3155_GET_CONFIG _IOR(DT3155_IOC_MAGIC, 2, struct dt3155_status_s) +#define DT3155_STOP _IO(DT3155_IOC_MAGIC, 3) +#define DT3155_START _IO(DT3155_IOC_MAGIC, 4) +#define DT3155_FLUSH _IO(DT3155_IOC_MAGIC, 5) +#define DT3155_IOC_MAXNR 5 + +/* Error codes */ + +#define DT_ERR_NO_BUFFERS 0x10000 /* not used but it might be one day */ +#define DT_ERR_CORRUPT 0x20000 +#define DT_ERR_OVERRUN 0x30000 +#define DT_ERR_I2C_TIMEOUT 0x40000 +#define DT_ERR_MASK 0xff0000/* not used but it might be one day */ + +/* User code will probably want to declare one of these for each card */ +typedef struct dt3155_read_s { + u32 offset; + u32 frame_seq; + u32 state; + + frame_info_t frame_info; +} dt3155_read_t; + +#endif /* _DT3155_inc */ diff --git a/drivers/staging/dt3155/dt3155.sysvinit b/drivers/staging/dt3155/dt3155.sysvinit new file mode 100644 index 000000000000..92ec0939cb7a --- /dev/null +++ b/drivers/staging/dt3155/dt3155.sysvinit @@ -0,0 +1,60 @@ +#! /bin/sh +# +# Module load/unload script for use with SysV-style /etc/init.d/ systems. +# On a Debian system, copy this to /etc/init.d/dt3155 and then run +# /usr/sbin/update-rc.d dt3155 defaults 55 +# to create the appropriate /etc/rc?.d/[SK]55dt3155 start/stop links. +# (The "55" is arbitrary but is what I use to load this rather late.) +# +# Andy Dougherty Feb 22 2000 doughera@lafayette.edu +# Dept. of Physics +# Lafayette College, Easton PA 18042 +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# Edit to point to your local copy. +FILE=/usr/local/lib/modules/dt3155/dt3155.o +NAME="dt3155" +DESC="dt3155 Frame Grabber module" +DEV="dt3155" + +if test ! -f $FILE; then + echo "Unable to locate $FILE" + exit 0 +fi + +set -e + +case "$1" in + start) + echo -n "Loading $DESC " + if /sbin/insmod -v -f $FILE; then + major=`grep $DEV /proc/devices | awk "{print \\$1}"` + rm -f /dev/dt3155? + mknod /dev/dt3155a c $major 0 + mknod /dev/dt3155b c $major 1 + chmod go+rw /dev/dt3155? + echo + else + echo "$FILE not loaded." + fi + ;; + stop) + echo -n "Unloading $DESC: " + if /sbin/rmmod $NAME ; then + echo + else + echo "$DEV not removed" + exit 0 + fi + rm -f /dev/dt3155? + ;; + *) + echo "Usage: /etc/init.d/$NAME {start|stop}" + exit 1 + ;; +esac + +exit 0 + diff --git a/drivers/staging/dt3155/dt3155_drv.c b/drivers/staging/dt3155/dt3155_drv.c new file mode 100644 index 000000000000..e2c44ec6fc45 --- /dev/null +++ b/drivers/staging/dt3155/dt3155_drv.c @@ -0,0 +1,1087 @@ +/* + +Copyright 1996,2002,2005 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Jason Lapenta, Scott Smedley, Greg Sharp + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 03-Jul-2000 JML n/a + 10-Oct-2001 SS port to 2.4 kernel + 02-Apr-2002 SS Mods to use allocator as a standalone module; + Merged John Roll's changes (john@cfa.harvard.edu) + to make work with multiple boards. + 02-Jul-2002 SS Merged James Rose's chages (rosejr@purdue.edu) to: + * fix successive interrupt-driven captures + * add select/poll support. + 10-Jul-2002 GCS Add error check when ndevices > MAXBOARDS. + 02-Aug-2002 GCS Fix field mode so that odd (lower) field is stored + in lower half of buffer. + 05-Aug-2005 SS port to 2.6 kernel. + 26-Oct-2009 SS port to 2.6.30 kernel. + +-- Notes -- + +** appended "mem=124" in lilo.conf to allow for 4megs free on my 128meg system. + * using allocator.c and allocator.h from o'reilly book (alessandro rubini) + ftp://ftp.systemy.it/pub/develop (see README.allocator) + + + might want to get rid of MAXboards for allocating initial buffer. + confusing and not necessary + + + in cleanup_module the MOD_IN_USE looks like it is check after it should + + * GFP_DMA should not be set with a PCI system (pg 291) + + - NJC why are only two buffers allowed? (see isr, approx line 358) + +*/ + +extern void printques(int); + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/poll.h> +#include <linux/sched.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include "dt3155.h" +#include "dt3155_drv.h" +#include "dt3155_isr.h" +#include "dt3155_io.h" +#include "allocator.h" + + +MODULE_LICENSE("GPL"); + +/* Error variable. Zero means no error. */ +int dt3155_errno = 0; + +#ifndef PCI_DEVICE_ID_INTEL_7116 +#define PCI_DEVICE_ID_INTEL_7116 0x1223 +#endif + +#define DT3155_VENDORID PCI_VENDOR_ID_INTEL +#define DT3155_DEVICEID PCI_DEVICE_ID_INTEL_7116 +#define MAXPCI 16 + +#ifdef DT_DEBUG +#define DT_3155_DEBUG_MSG(x,y) printk(x,y) +#else +#define DT_3155_DEBUG_MSG(x,y) +#endif + +/* wait queue for interrupts */ +wait_queue_head_t dt3155_read_wait_queue[ MAXBOARDS ]; + +#define DT_3155_SUCCESS 0 +#define DT_3155_FAILURE -EIO + +/* set to dynamicaly allocate, but it is tunable: */ +/* insmod DT_3155 dt3155 dt3155_major=XX */ +int dt3155_major = 0; + +/* The minor numbers are 0 and 1 ... they are not tunable. + * They are used as the indices for the structure vectors, + * and register address vectors + */ + +/* Global structures and variables */ + +/* Status of each device */ +struct dt3155_status_s dt3155_status[ MAXBOARDS ]; + +/* kernel logical address of the board */ +u8 *dt3155_lbase[ MAXBOARDS ] = { NULL +#if MAXBOARDS == 2 + , NULL +#endif +}; +/* DT3155 registers */ +u8 *dt3155_bbase = NULL; /* kernel logical address of the * + * buffer region */ +u32 dt3155_dev_open[ MAXBOARDS ] = {0 +#if MAXBOARDS == 2 + , 0 +#endif +}; + +u32 ndevices = 0; +u32 unique_tag = 0;; + + +/* + * Stops interrupt generation right away and resets the status + * to idle. I don't know why this works and the other way doesn't. + * (James Rose) + */ +static void quick_stop (int minor) +{ + // TODO: scott was here +#if 1 + ReadMReg((dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg); + /* disable interrupts */ + int_csr_r.fld.FLD_END_EVE_EN = 0; + int_csr_r.fld.FLD_END_ODD_EN = 0; + WriteMReg((dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg ); + + dt3155_status[ minor ].state &= ~(DT3155_STATE_STOP|0xff); + /* mark the system stopped: */ + dt3155_status[ minor ].state |= DT3155_STATE_IDLE; + dt3155_fbuffer[ minor ]->stop_acquire = 0; + dt3155_fbuffer[ minor ]->even_stopped = 0; +#else + dt3155_status[minor].state |= DT3155_STATE_STOP; + dt3155_status[minor].fbuffer.stop_acquire = 1; +#endif + +} + + +/***************************************************** + * dt3155_isr() Interrupt service routien + * + * - looks like this isr supports IRQ sharing (or could) JML + * - Assumes irq's are disabled, via SA_INTERRUPT flag + * being set in request_irq() call from init_module() + *****************************************************/ +static inline void dt3155_isr( int irq, void *dev_id, struct pt_regs *regs ) +{ + int minor = -1; + int index; + unsigned long flags; + u32 buffer_addr; + + /* find out who issued the interrupt */ + for ( index = 0; index < ndevices; index++ ) { + if( dev_id == (void*) &dt3155_status[ index ]) + { + minor = index; + break; + } + } + + /* hopefully we should not get here */ + if ( minor < 0 || minor >= MAXBOARDS ) { + printk(KERN_ERR "dt3155_isr called with invalid dev_id\n"); + return; + } + + /* Check for corruption and set a flag if so */ + ReadMReg( (dt3155_lbase[ minor ] + CSR1), csr1_r.reg ); + + if ( (csr1_r.fld.FLD_CRPT_EVE) || (csr1_r.fld.FLD_CRPT_ODD) ) + { + /* TODO: this should probably stop acquisition */ + /* and set some flags so that dt3155_read */ + /* returns an error next time it is called */ + dt3155_errno = DT_ERR_CORRUPT; + printk("dt3155: corrupt field\n"); + return; + } + + ReadMReg((dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg); + + /* Handle the even field ... */ + if (int_csr_r.fld.FLD_END_EVE) + { + if ( (dt3155_status[ minor ].state & DT3155_STATE_MODE) == + DT3155_STATE_FLD ) + { + dt3155_fbuffer[ minor ]->frame_count++; + } + + ReadI2C(dt3155_lbase[ minor ], EVEN_CSR, &i2c_even_csr.reg); + + /* Clear the interrupt? */ + int_csr_r.fld.FLD_END_EVE = 1; + + /* disable the interrupt if last field */ + if (dt3155_fbuffer[ minor ]->stop_acquire) + { + printk("dt3155: even stopped.\n"); + dt3155_fbuffer[ minor ]->even_stopped = 1; + if (i2c_even_csr.fld.SNGL_EVE) + { + int_csr_r.fld.FLD_END_EVE_EN = 0; + } + else + { + i2c_even_csr.fld.SNGL_EVE = 1; + } + } + + WriteMReg( (dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg ); + + /* Set up next DMA if we are doing FIELDS */ + if ( (dt3155_status[ minor ].state & DT3155_STATE_MODE ) == + DT3155_STATE_FLD) + { + /* GCS (Aug 2, 2002) -- In field mode, dma the odd field + into the lower half of the buffer */ + const u32 stride = dt3155_status[ minor ].config.cols; + buffer_addr = dt3155_fbuffer[ minor ]-> + frame_info[ dt3155_fbuffer[ minor ]->active_buf ].addr + + (DT3155_MAX_ROWS / 2) * stride; + local_save_flags(flags); + local_irq_disable(); + wake_up_interruptible( &dt3155_read_wait_queue[ minor ] ); + + /* Set up the DMA address for the next field */ + local_irq_restore(flags); + WriteMReg((dt3155_lbase[ minor ] + ODD_DMA_START), buffer_addr); + } + + /* Check for errors. */ + i2c_even_csr.fld.DONE_EVE = 1; + if ( i2c_even_csr.fld.ERROR_EVE ) + dt3155_errno = DT_ERR_OVERRUN; + + WriteI2C( dt3155_lbase[ minor ], EVEN_CSR, i2c_even_csr.reg ); + + /* Note that we actually saw an even field meaning */ + /* that subsequent odd field complete the frame */ + dt3155_fbuffer[ minor ]->even_happened = 1; + + /* recording the time that the even field finished, this should be */ + /* about time in the middle of the frame */ + do_gettimeofday( &(dt3155_fbuffer[ minor ]-> + frame_info[ dt3155_fbuffer[ minor ]-> + active_buf ].time) ); + return; + } + + /* ... now handle the odd field */ + if ( int_csr_r.fld.FLD_END_ODD ) + { + ReadI2C( dt3155_lbase[ minor ], ODD_CSR, &i2c_odd_csr.reg ); + + /* Clear the interrupt? */ + int_csr_r.fld.FLD_END_ODD = 1; + + if (dt3155_fbuffer[ minor ]->even_happened || + (dt3155_status[ minor ].state & DT3155_STATE_MODE) == + DT3155_STATE_FLD) + { + dt3155_fbuffer[ minor ]->frame_count++; + } + + if ( dt3155_fbuffer[ minor ]->stop_acquire && + dt3155_fbuffer[ minor ]->even_stopped ) + { + printk(KERN_DEBUG "dt3155: stopping odd..\n"); + if ( i2c_odd_csr.fld.SNGL_ODD ) + { + /* disable interrupts */ + int_csr_r.fld.FLD_END_ODD_EN = 0; + dt3155_status[ minor ].state &= ~(DT3155_STATE_STOP|0xff); + + /* mark the system stopped: */ + dt3155_status[ minor ].state |= DT3155_STATE_IDLE; + dt3155_fbuffer[ minor ]->stop_acquire = 0; + dt3155_fbuffer[ minor ]->even_stopped = 0; + + printk(KERN_DEBUG "dt3155: state is now %x\n", + dt3155_status[minor].state); + } + else + { + i2c_odd_csr.fld.SNGL_ODD = 1; + } + } + + WriteMReg( (dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg ); + + /* if the odd field has been acquired, then */ + /* change the next dma location for both fields */ + /* and wake up the process if sleeping */ + if ( dt3155_fbuffer[ minor ]->even_happened || + (dt3155_status[ minor ].state & DT3155_STATE_MODE) == + DT3155_STATE_FLD ) + { + + local_save_flags(flags); + local_irq_disable(); + +#ifdef DEBUG_QUES_B + printques( minor ); +#endif + if ( dt3155_fbuffer[ minor ]->nbuffers > 2 ) + { + if ( !are_empty_buffers( minor ) ) + { + /* The number of active + locked buffers is + * at most 2, and since there are none empty, there + * must be at least nbuffers-2 ready buffers. + * This is where we 'drop frames', oldest first. */ + push_empty( pop_ready( minor ), minor ); + } + + /* The ready_que can't be full, since we know + * there is one active buffer right now, so it's safe + * to push the active buf on the ready_que. */ + push_ready( minor, dt3155_fbuffer[ minor ]->active_buf ); + /* There's at least 1 empty -- make it active */ + dt3155_fbuffer[ minor ]->active_buf = pop_empty( minor ); + dt3155_fbuffer[ minor ]-> + frame_info[ dt3155_fbuffer[ minor ]-> + active_buf ].tag = ++unique_tag; + } + else /* nbuffers == 2, special case */ + { /* There is 1 active buffer. + * If there is a locked buffer, keep the active buffer + * the same -- that means we drop a frame. + */ + if ( dt3155_fbuffer[ minor ]->locked_buf < 0 ) + { + push_ready( minor, + dt3155_fbuffer[ minor ]->active_buf ); + if (are_empty_buffers( minor ) ) + { + dt3155_fbuffer[ minor ]->active_buf = + pop_empty( minor ); + } + else + { /* no empty or locked buffers, so use a readybuf */ + dt3155_fbuffer[ minor ]->active_buf = + pop_ready( minor ); + } + } + } + +#ifdef DEBUG_QUES_B + printques( minor ); +#endif + + dt3155_fbuffer[ minor ]->even_happened = 0; + + wake_up_interruptible( &dt3155_read_wait_queue[ minor ] ); + + local_irq_restore(flags); + } + + + /* Set up the DMA address for the next frame/field */ + buffer_addr = dt3155_fbuffer[ minor ]-> + frame_info[ dt3155_fbuffer[ minor ]->active_buf ].addr; + if ( (dt3155_status[ minor ].state & DT3155_STATE_MODE) == + DT3155_STATE_FLD ) + { + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_START), buffer_addr); + } + else + { + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_START), buffer_addr); + + WriteMReg((dt3155_lbase[ minor ] + ODD_DMA_START), buffer_addr + + dt3155_status[ minor ].config.cols); + } + + /* Do error checking */ + i2c_odd_csr.fld.DONE_ODD = 1; + if ( i2c_odd_csr.fld.ERROR_ODD ) + dt3155_errno = DT_ERR_OVERRUN; + + WriteI2C(dt3155_lbase[ minor ], ODD_CSR, i2c_odd_csr.reg ); + + return; + } + /* If we get here, the Odd Field wasn't it either... */ + printk( "neither even nor odd. shared perhaps?\n"); +} + +/***************************************************** + * init_isr(int minor) + * turns on interupt generation for the card + * designated by "minor". + * It is called *only* from inside ioctl(). + *****************************************************/ +static void dt3155_init_isr(int minor) +{ + const u32 stride = dt3155_status[ minor ].config.cols; + + switch (dt3155_status[ minor ].state & DT3155_STATE_MODE) + { + case DT3155_STATE_FLD: + { + even_dma_start_r = dt3155_status[ minor ]. + fbuffer.frame_info[ dt3155_status[ minor ].fbuffer.active_buf ].addr; + even_dma_stride_r = 0; + odd_dma_stride_r = 0; + + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_START), + even_dma_start_r); + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_STRIDE), + even_dma_stride_r); + WriteMReg((dt3155_lbase[ minor ] + ODD_DMA_STRIDE), + odd_dma_stride_r); + break; + } + + case DT3155_STATE_FRAME: + default: + { + even_dma_start_r = dt3155_status[ minor ]. + fbuffer.frame_info[ dt3155_status[ minor ].fbuffer.active_buf ].addr; + odd_dma_start_r = even_dma_start_r + stride; + even_dma_stride_r = stride; + odd_dma_stride_r = stride; + + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_START), + even_dma_start_r); + WriteMReg((dt3155_lbase[ minor ] + ODD_DMA_START), + odd_dma_start_r); + WriteMReg((dt3155_lbase[ minor ] + EVEN_DMA_STRIDE), + even_dma_stride_r); + WriteMReg((dt3155_lbase[ minor ] + ODD_DMA_STRIDE), + odd_dma_stride_r); + break; + } + } + + /* 50/60 Hz should be set before this point but let's make sure it is */ + /* right anyway */ + + ReadI2C(dt3155_lbase[ minor ], CONFIG, &i2c_csr2.reg); + i2c_csr2.fld.HZ50 = FORMAT50HZ; + WriteI2C(dt3155_lbase[ minor ], CONFIG, i2c_config.reg); + + /* enable busmaster chip, clear flags */ + + /* + * TODO: + * shouldn't we be concered with continuous values of + * DT3155_SNAP & DT3155_ACQ here? (SS) + */ + + csr1_r.reg = 0; + csr1_r.fld.CAP_CONT_EVE = 1; /* use continuous capture bits to */ + csr1_r.fld.CAP_CONT_ODD = 1; /* enable */ + csr1_r.fld.FLD_DN_EVE = 1; /* writing a 1 clears flags */ + csr1_r.fld.FLD_DN_ODD = 1; + csr1_r.fld.SRST = 1; /* reset - must be 1 */ + csr1_r.fld.FIFO_EN = 1; /* fifo control - must be 1 */ + csr1_r.fld.FLD_CRPT_EVE = 1; /* writing a 1 clears flags */ + csr1_r.fld.FLD_CRPT_ODD = 1; + + WriteMReg((dt3155_lbase[ minor ] + CSR1),csr1_r.reg); + + /* Enable interrupts at the end of each field */ + + int_csr_r.reg = 0; + int_csr_r.fld.FLD_END_EVE_EN = 1; + int_csr_r.fld.FLD_END_ODD_EN = 1; + int_csr_r.fld.FLD_START_EN = 0; + + WriteMReg((dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg); + + /* start internal BUSY bits */ + + ReadI2C(dt3155_lbase[ minor ], CSR2, &i2c_csr2.reg); + i2c_csr2.fld.BUSY_ODD = 1; + i2c_csr2.fld.BUSY_EVE = 1; + WriteI2C(dt3155_lbase[ minor ], CSR2, i2c_csr2.reg); + + /* Now its up to the interrupt routine!! */ + + return; +} + + +/***************************************************** + * ioctl() + * + *****************************************************/ +static int dt3155_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + int minor = MINOR(inode->i_rdev); /* What device are we ioctl()'ing? */ + + if ( minor >= MAXBOARDS || minor < 0 ) + return -ENODEV; + + /* make sure it is valid command */ + if (_IOC_NR(cmd) > DT3155_IOC_MAXNR) + { + printk("DT3155: invalid IOCTL(0x%x)\n",cmd); + printk("DT3155: Valid commands (0x%x), (0x%x), (0x%x), (0x%x), (0x%x)\n", + (unsigned int)DT3155_GET_CONFIG, + (unsigned int)DT3155_SET_CONFIG, + (unsigned int)DT3155_START, + (unsigned int)DT3155_STOP, + (unsigned int)DT3155_FLUSH); + return -EINVAL; + } + + switch (cmd) + { + case DT3155_SET_CONFIG: + { + if (dt3155_status[minor].state != DT3155_STATE_IDLE) + return -EBUSY; + + { + struct dt3155_config_s tmp; + if (copy_from_user((void *)&tmp, (void *) arg, sizeof(tmp))) + return -EFAULT; + /* check for valid settings */ + if (tmp.rows > DT3155_MAX_ROWS || + tmp.cols > DT3155_MAX_COLS || + (tmp.acq_mode != DT3155_MODE_FRAME && + tmp.acq_mode != DT3155_MODE_FIELD) || + (tmp.continuous != DT3155_SNAP && + tmp.continuous != DT3155_ACQ)) + { + return -EINVAL; + } + dt3155_status[minor].config = tmp; + } + return 0; + } + case DT3155_GET_CONFIG: + { + if (copy_to_user((void *) arg, (void *) &dt3155_status[minor], + sizeof(dt3155_status_t) )) + return -EFAULT; + return 0; + } + case DT3155_FLUSH: /* Flushes the buffers -- ensures fresh data */ + { + if (dt3155_status[minor].state != DT3155_STATE_IDLE) + return -EBUSY; + return dt3155_flush(minor); + } + case DT3155_STOP: + { + if (dt3155_status[minor].state & DT3155_STATE_STOP || + dt3155_status[minor].fbuffer.stop_acquire) + return -EBUSY; + + if (dt3155_status[minor].state == DT3155_STATE_IDLE) + return 0; + + quick_stop(minor); + if (copy_to_user((void *) arg, (void *) &dt3155_status[minor], + sizeof(dt3155_status_t))) + return -EFAULT; + return 0; + } + case DT3155_START: + { + if (dt3155_status[minor].state != DT3155_STATE_IDLE) + return -EBUSY; + + dt3155_status[minor].fbuffer.stop_acquire = 0; + dt3155_status[minor].fbuffer.frame_count = 0; + + /* Set the MODE in the status -- we default to FRAME */ + if (dt3155_status[minor].config.acq_mode == DT3155_MODE_FIELD) + { + dt3155_status[minor].state = DT3155_STATE_FLD; + } + else + { + dt3155_status[minor].state = DT3155_STATE_FRAME; + } + + dt3155_init_isr(minor); + if (copy_to_user( (void *) arg, (void *) &dt3155_status[minor], + sizeof(dt3155_status_t))) + return -EFAULT; + return 0; + } + default: + { + printk("DT3155: invalid IOCTL(0x%x)\n",cmd); + printk("DT3155: Valid commands (0x%x), (0x%x), (0x%x), (0x%x), (0x%x)\n", + (unsigned int)DT3155_GET_CONFIG, + (unsigned int)DT3155_SET_CONFIG, + DT3155_START, DT3155_STOP, DT3155_FLUSH); + return -ENOSYS; + } + } + return -ENOSYS; +} + +/***************************************************** + * mmap() + * + * only allow the user to mmap the registers and buffer + * It is quite possible that this is broken, since the + * addition of of the capacity for two cards!!!!!!!! + * It *looks* like it should work but since I'm not + * sure how to use it, I'm not actually sure. (NJC? ditto by SS) + *****************************************************/ +static int dt3155_mmap (struct file * file, struct vm_area_struct * vma) +{ + /* which device are we mmapping? */ + int minor = MINOR(file->f_dentry->d_inode->i_rdev); + unsigned long offset; + offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset >= __pa(high_memory) || (file->f_flags & O_SYNC)) + vma->vm_flags |= VM_IO; + + /* Don't try to swap out physical pages.. */ + vma->vm_flags |= VM_RESERVED; + + /* they are mapping the registers or the buffer */ + if ((offset == dt3155_status[minor].reg_addr && + vma->vm_end - vma->vm_start == PCI_PAGE_SIZE) || + (offset == dt3155_status[minor].mem_addr && + vma->vm_end - vma->vm_start == dt3155_status[minor].mem_size)) + { + if (remap_pfn_range(vma, + vma->vm_start, + offset >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + printk("DT3155: remap_page_range() failed.\n"); + return -EAGAIN; + } + } + else + { + printk("DT3155: dt3155_mmap() bad call.\n"); + return -ENXIO; + } + + return 0; +} + + +/***************************************************** + * open() + * + * Our special open code. + * MOD_INC_USE_COUNT make sure that the driver memory is not freed + * while the device is in use. + *****************************************************/ +static int dt3155_open( struct inode* inode, struct file* filep) +{ + int minor = MINOR(inode->i_rdev); /* what device are we opening? */ + if (dt3155_dev_open[ minor ]) { + printk ("DT3155: Already opened by another process.\n"); + return -EBUSY; + } + + if (dt3155_status[ minor ].device_installed==0) + { + printk("DT3155 Open Error: No such device dt3155 minor number %d\n", + minor); + return -EIO; + } + + if (dt3155_status[ minor ].state != DT3155_STATE_IDLE) { + printk ("DT3155: Not in idle state (state = %x)\n", + dt3155_status[ minor ].state); + return -EBUSY; + } + + printk("DT3155: Device opened.\n"); + + dt3155_dev_open[ minor ] = 1 ; + + dt3155_flush( minor ); + + /* Disable ALL interrupts */ + int_csr_r.reg = 0; + WriteMReg( (dt3155_lbase[ minor ] + INT_CSR), int_csr_r.reg ); + + init_waitqueue_head(&(dt3155_read_wait_queue[minor])); + + return 0; +} + + +/***************************************************** + * close() + * + * Now decrement the use count. + * + *****************************************************/ +static int dt3155_close( struct inode *inode, struct file *filep) +{ + int minor; + + minor = MINOR(inode->i_rdev); /* which device are we closing */ + if (!dt3155_dev_open[ minor ]) + { + printk("DT3155: attempt to CLOSE a not OPEN device\n"); + } + else + { + dt3155_dev_open[ minor ] = 0; + + if (dt3155_status[ minor ].state != DT3155_STATE_IDLE) + { + quick_stop(minor); + } + } + return 0; +} + +/***************************************************** + * read() + * + *****************************************************/ +static ssize_t dt3155_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + /* which device are we reading from? */ + int minor = MINOR(filep->f_dentry->d_inode->i_rdev); + u32 offset; + int frame_index; + frame_info_t *frame_info_p; + + /* TODO: this should check the error flag and */ + /* return an error on hardware failures */ + if (count != sizeof(dt3155_read_t)) + { + printk("DT3155 ERROR (NJC): count is not right\n"); + return -EINVAL; + } + + + /* Hack here -- I'm going to allow reading even when idle. + * this is so that the frames can be read after STOP has + * been called. Leaving it here, commented out, as a reminder + * for a short while to make sure there are no problems. + * Note that if the driver is not opened in non_blocking mode, + * and the device is idle, then it could sit here forever! */ + + /* if (dt3155_status[minor].state == DT3155_STATE_IDLE)*/ + /* return -EBUSY;*/ + + /* non-blocking reads should return if no data */ + if (filep->f_flags & O_NDELAY) + { + if ((frame_index = dt3155_get_ready_buffer(minor)) < 0) { + /*printk( "dt3155: no buffers available (?)\n");*/ + /* printques(minor); */ + return -EAGAIN; + } + } + else + { + /* + * sleep till data arrives , or we get interrupted. + * Note that wait_event_interruptible() does not actually + * sleep/wait if it's condition evaluates to true upon entry. + */ + wait_event_interruptible(dt3155_read_wait_queue[minor], + (frame_index = dt3155_get_ready_buffer(minor)) + >= 0); + + if (frame_index < 0) + { + printk ("DT3155: read: interrupted\n"); + quick_stop (minor); + printques(minor); + return -EINTR; + } + } + + frame_info_p = &dt3155_status[minor].fbuffer.frame_info[frame_index]; + + /* make this an offset */ + offset = frame_info_p->addr - dt3155_status[minor].mem_addr; + + put_user(offset, (unsigned int *) buf); + buf += sizeof(u32); + put_user( dt3155_status[minor].fbuffer.frame_count, (unsigned int *) buf); + buf += sizeof(u32); + put_user(dt3155_status[minor].state, (unsigned int *) buf); + buf += sizeof(u32); + if (copy_to_user(buf, frame_info_p, sizeof(frame_info_t))) + return -EFAULT; + + return sizeof(dt3155_read_t); +} + +static unsigned int dt3155_poll (struct file * filp, poll_table *wait) +{ + int minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + if (!is_ready_buf_empty(minor)) + return POLLIN | POLLRDNORM; + + poll_wait (filp, &dt3155_read_wait_queue[minor], wait); + + return 0; +} + + +/***************************************************** + * file operations supported by DT3155 driver + * needed by init_module + * register_chrdev + *****************************************************/ +static struct file_operations dt3155_fops = { + read: dt3155_read, + ioctl: dt3155_ioctl, + mmap: dt3155_mmap, + poll: dt3155_poll, + open: dt3155_open, + release: dt3155_close +}; + + +/***************************************************** + * find_PCI(); + * + * PCI has been totally reworked in 2.1.. + *****************************************************/ +static int find_PCI (void) +{ + struct pci_dev *pci_dev = NULL; + int error, pci_index = 0; + unsigned short rev_device; + unsigned long base; + unsigned char irq; + + while ((pci_dev = pci_get_device + (DT3155_VENDORID, DT3155_DEVICEID, pci_dev)) != NULL) + { + pci_index ++; + + /* Is it really there? */ + if ((error = + pci_read_config_word(pci_dev, PCI_CLASS_DEVICE, &rev_device))) + continue; + + /* Found a board */ + DT_3155_DEBUG_MSG("DT3155: Device number %d \n", pci_index); + + /* Make sure the driver was compiled with enough buffers to handle + this many boards */ + if (pci_index > MAXBOARDS) { + printk("DT3155: ERROR - found %d devices, but driver only configured " + "for %d devices\n" + "DT3155: Please change MAXBOARDS in dt3155.h\n", + pci_index, MAXBOARDS); + goto err; + } + + /* Now, just go out and make sure that this/these device(s) is/are + actually mapped into the kernel address space */ + if ((error = pci_read_config_dword( pci_dev, PCI_BASE_ADDRESS_0, + (u32 *) &base))) + { + printk("DT3155: Was not able to find device \n"); + goto err; + } + + DT_3155_DEBUG_MSG("DT3155: Base address 0 for device is %lx \n", base); + dt3155_status[pci_index-1].reg_addr = base; + + /* Remap the base address to a logical address through which we + * can access it. */ + dt3155_lbase[ pci_index - 1 ] = ioremap(base,PCI_PAGE_SIZE); + dt3155_status[ pci_index - 1 ].reg_addr = base; + DT_3155_DEBUG_MSG("DT3155: New logical address is %p \n", + dt3155_lbase[pci_index-1]); + if ( !dt3155_lbase[pci_index-1] ) + { + printk("DT3155: Unable to remap control registers\n"); + goto err; + } + + if ( (error = pci_read_config_byte( pci_dev, PCI_INTERRUPT_LINE, &irq)) ) + { + printk("DT3155: Was not able to find device \n"); + goto err; + } + + DT_3155_DEBUG_MSG("DT3155: IRQ is %d \n",irq); + dt3155_status[ pci_index-1 ].irq = irq; + /* Set flag: kth device found! */ + dt3155_status[ pci_index-1 ].device_installed = 1; + printk("DT3155: Installing device %d w/irq %d and address %p\n", + pci_index, + dt3155_status[pci_index-1].irq, + dt3155_lbase[pci_index-1]); + + } + ndevices = pci_index; + + return DT_3155_SUCCESS; + +err: + pci_dev_put(pci_dev); + return DT_3155_FAILURE; +} + +u32 allocatorAddr = 0; + +/***************************************************** + * init_module() + *****************************************************/ +int init_module(void) +{ + int index; + int rcode = 0; + char *devname[ MAXBOARDS ]; + + devname[ 0 ] = "dt3155a"; +#if MAXBOARDS == 2 + devname[ 1 ] = "dt3155b"; +#endif + + printk("DT3155: Loading module...\n"); + + /* Register the device driver */ + rcode = register_chrdev( dt3155_major, "dt3155", &dt3155_fops ); + if( rcode < 0 ) + { + printk( KERN_INFO "DT3155: register_chrdev failed \n"); + return rcode; + } + + if( dt3155_major == 0 ) + dt3155_major = rcode; /* dynamic */ + + + /* init the status variables. */ + /* DMA memory is taken care of in setup_buffers() */ + for ( index = 0; index < MAXBOARDS; index++ ) + { + dt3155_status[ index ].config.acq_mode = DT3155_MODE_FRAME; + dt3155_status[ index ].config.continuous = DT3155_ACQ; + dt3155_status[ index ].config.cols = DT3155_MAX_COLS; + dt3155_status[ index ].config.rows = DT3155_MAX_ROWS; + dt3155_status[ index ].state = DT3155_STATE_IDLE; + + /* find_PCI() will check if devices are installed; */ + /* first assume they're not: */ + dt3155_status[ index ].mem_addr = 0; + dt3155_status[ index ].mem_size = 0; + dt3155_status[ index ].state = DT3155_STATE_IDLE; + dt3155_status[ index ].device_installed = 0; + } + + /* Now let's find the hardware. find_PCI() will set ndevices to the + * number of cards found in this machine. */ + { + if ( (rcode = find_PCI()) != DT_3155_SUCCESS ) + { + printk("DT3155 error: find_PCI() failed to find dt3155 board(s)\n"); + unregister_chrdev( dt3155_major, "dt3155" ); + return rcode; + } + } + + /* Ok, time to setup the frame buffers */ + if( (rcode = dt3155_setup_buffers(&allocatorAddr)) < 0 ) + { + printk("DT3155: Error: setting up buffer not large enough."); + unregister_chrdev( dt3155_major, "dt3155" ); + return rcode; + } + + /* If we are this far, then there is enough RAM */ + /* for the buffers: Print the configuration. */ + for( index = 0; index < ndevices; index++ ) + { + printk("DT3155: Device = %d; acq_mode = %d; " + "continuous = %d; cols = %d; rows = %d;\n", + index , + dt3155_status[ index ].config.acq_mode, + dt3155_status[ index ].config.continuous, + dt3155_status[ index ].config.cols, + dt3155_status[ index ].config.rows); + printk("DT3155: m_addr = 0x%x; m_size = %ld; " + "state = %d; device_installed = %d\n", + dt3155_status[ index ].mem_addr, + (long int)dt3155_status[ index ].mem_size, + dt3155_status[ index ].state, + dt3155_status[ index ].device_installed); + } + + /* Disable ALL interrupts */ + int_csr_r.reg = 0; + for( index = 0; index < ndevices; index++ ) + { + WriteMReg( (dt3155_lbase[ index ] + INT_CSR), int_csr_r.reg ); + if( dt3155_status[ index ].device_installed ) + { + /* + * This driver *looks* like it can handle sharing interrupts, + * but I can't actually test myself. I've had reports that it + * DOES work so I'll enable it for now. This comment will remain + * as a reminder in case any problems arise. (SS) + */ + /* in older kernels flags are: SA_SHIRQ | SA_INTERRUPT */ + rcode = request_irq( dt3155_status[ index ].irq, (void *)dt3155_isr, + IRQF_SHARED | IRQF_DISABLED, devname[ index ], + (void*) &dt3155_status[index]); + if( rcode < 0 ) + { + printk("DT3155: minor %d request_irq failed for IRQ %d\n", + index, dt3155_status[index].irq); + unregister_chrdev( dt3155_major, "dt3155" ); + return rcode; + } + } + } + + printk("DT3155: finished loading\n"); + + return 0; +} + +/***************************************************** + * cleanup_module(void) + * + *****************************************************/ +void cleanup_module(void) +{ + int index; + + printk("DT3155: cleanup_module called\n"); + + /* removed DMA allocated with the allocator */ +#ifdef STANDALONE_ALLOCATOR + if (allocatorAddr != 0) + allocator_free_dma(allocatorAddr); +#else + allocator_cleanup(); +#endif + + unregister_chrdev( dt3155_major, "dt3155" ); + + for( index = 0; index < ndevices; index++ ) + { + if( dt3155_status[ index ].device_installed == 1 ) + { + printk( "DT3155: Freeing irq %d for device %d\n", + dt3155_status[ index ].irq, index ); + free_irq( dt3155_status[ index ].irq, (void*)&dt3155_status[index] ); + } + } +} + diff --git a/drivers/staging/dt3155/dt3155_drv.h b/drivers/staging/dt3155/dt3155_drv.h new file mode 100644 index 000000000000..95e68c3388a4 --- /dev/null +++ b/drivers/staging/dt3155/dt3155_drv.h @@ -0,0 +1,45 @@ +/* + +Copyright 1996,2002 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Scott Smedley + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA +*/ + +#ifndef DT3155_DRV_INC +#define DT3155_DRV_INC + +/* kernel logical address of the frame grabbers */ +extern u8 *dt3155_lbase[MAXBOARDS]; + +/* kernel logical address of ram buffer */ +extern u8 *dt3155_bbase; + +#ifdef __KERNEL__ +#include <linux/wait.h> + +/* wait queue for reads */ +extern wait_queue_head_t dt3155_read_wait_queue[MAXBOARDS]; +#endif + +/* number of devices */ +extern u32 ndevices; + +extern int dt3155_errno; + +#endif diff --git a/drivers/staging/dt3155/dt3155_io.c b/drivers/staging/dt3155/dt3155_io.c new file mode 100644 index 000000000000..6b9c68501a61 --- /dev/null +++ b/drivers/staging/dt3155/dt3155_io.c @@ -0,0 +1,175 @@ +/* + * Copyright 1996,2002,2005 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + * Jason Lapenta, Scott Smedley + * + * This file is part of the DT3155 Device Driver. + * + * The DT3155 Device Driver 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. + * + * The DT3155 Device Driver 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. + */ + +/* + * This file provides some basic register io routines. It is modified from + * demo code provided by Data Translations. + */ + +#include <linux/delay.h> +#include "dt3155.h" +#include "dt3155_io.h" +#include "dt3155_drv.h" + + +/****** local copies of board's 32 bit registers ******/ +u32 even_dma_start_r; /* bit 0 should always be 0 */ +u32 odd_dma_start_r; /* .. */ +u32 even_dma_stride_r; /* bits 0&1 should always be 0 */ +u32 odd_dma_stride_r; /* .. */ +u32 even_pixel_fmt_r; +u32 odd_pixel_fmt_r; + +FIFO_TRIGGER_R fifo_trigger_r; +XFER_MODE_R xfer_mode_r; +CSR1_R csr1_r; +RETRY_WAIT_CNT_R retry_wait_cnt_r; +INT_CSR_R int_csr_r; + +u32 even_fld_mask_r; +u32 odd_fld_mask_r; + +MASK_LENGTH_R mask_length_r; +FIFO_FLAG_CNT_R fifo_flag_cnt_r; +IIC_CLK_DUR_R iic_clk_dur_r; +IIC_CSR1_R iic_csr1_r; +IIC_CSR2_R iic_csr2_r; +DMA_UPPER_LMT_R even_dma_upper_lmt_r; +DMA_UPPER_LMT_R odd_dma_upper_lmt_r; + + + +/******** local copies of board's 8 bit I2C registers ******/ +I2C_CSR2 i2c_csr2; +I2C_EVEN_CSR i2c_even_csr; +I2C_ODD_CSR i2c_odd_csr; +I2C_CONFIG i2c_config; +u8 i2c_dt_id; +u8 i2c_x_clip_start; +u8 i2c_y_clip_start; +u8 i2c_x_clip_end; +u8 i2c_y_clip_end; +u8 i2c_ad_addr; +u8 i2c_ad_lut; +I2C_AD_CMD i2c_ad_cmd; +u8 i2c_dig_out; +u8 i2c_pm_lut_addr; +u8 i2c_pm_lut_data; + +/* + * wait_ibsyclr() + * + * This function handles read/write timing and r/w timeout error + * + * Returns TRUE if NEW_CYCLE clears + * Returns FALSE if NEW_CYCLE doesn't clear in roughly 3 msecs, otherwise + * returns 0 + */ +static int wait_ibsyclr(u8 *lpReg) +{ + /* wait 100 microseconds */ + udelay(100L); + /* __delay(loops_per_sec/10000); */ + if (iic_csr2_r.fld.NEW_CYCLE) { + /* if NEW_CYCLE didn't clear */ + /* TIMEOUT ERROR */ + dt3155_errno = DT_ERR_I2C_TIMEOUT; + return FALSE; + } else + return TRUE; /* no error */ +} + +/* + * WriteI2C() + * + * This function handles writing to 8-bit DT3155 registers + * + * 1st parameter is pointer to 32-bit register base address + * 2nd parameter is reg. index; + * 3rd is value to be written + * + * Returns TRUE - Successful completion + * FALSE - Timeout error - cycle did not complete! + */ +int WriteI2C(u8 *lpReg, u_short wIregIndex, u8 byVal) +{ + int writestat; /* status for return */ + + /* read 32 bit IIC_CSR2 register data into union */ + + ReadMReg((lpReg + IIC_CSR2), iic_csr2_r.reg); + + /* for write operation */ + iic_csr2_r.fld.DIR_RD = 0; + /* I2C address of I2C register: */ + iic_csr2_r.fld.DIR_ADDR = wIregIndex; + /* 8 bit data to be written to I2C reg */ + iic_csr2_r.fld.DIR_WR_DATA = byVal; + /* will start a direct I2C cycle: */ + iic_csr2_r.fld.NEW_CYCLE = 1; + + /* xfer union data into 32 bit IIC_CSR2 register */ + WriteMReg((lpReg + IIC_CSR2), iic_csr2_r.reg); + + /* wait for IIC cycle to finish */ + writestat = wait_ibsyclr(lpReg); + return writestat; +} + +/* + * ReadI2C() + * + * This function handles reading from 8-bit DT3155 registers + * + * 1st parameter is pointer to 32-bit register base address + * 2nd parameter is reg. index; + * 3rd is adrs of value to be read + * + * Returns TRUE - Successful completion + * FALSE - Timeout error - cycle did not complete! + */ +int ReadI2C(u8 *lpReg, u_short wIregIndex, u8 *byVal) +{ + int writestat; /* status for return */ + + /* read 32 bit IIC_CSR2 register data into union */ + ReadMReg((lpReg + IIC_CSR2), iic_csr2_r.reg); + + /* for read operation */ + iic_csr2_r.fld.DIR_RD = 1; + + /* I2C address of I2C register: */ + iic_csr2_r.fld.DIR_ADDR = wIregIndex; + + /* will start a direct I2C cycle: */ + iic_csr2_r.fld.NEW_CYCLE = 1; + + /* xfer union's data into 32 bit IIC_CSR2 register */ + WriteMReg((lpReg + IIC_CSR2), iic_csr2_r.reg); + + /* wait for IIC cycle to finish */ + writestat = wait_ibsyclr(lpReg); + + /* Next 2 commands read 32 bit IIC_CSR1 register's data into union */ + /* first read data is in IIC_CSR1 */ + ReadMReg((lpReg + IIC_CSR1), iic_csr1_r.reg); + + /* now get data u8 out of register */ + *byVal = (u8) iic_csr1_r.fld.RD_DATA; + + return writestat; +} diff --git a/drivers/staging/dt3155/dt3155_io.h b/drivers/staging/dt3155/dt3155_io.h new file mode 100644 index 000000000000..d1a25100169f --- /dev/null +++ b/drivers/staging/dt3155/dt3155_io.h @@ -0,0 +1,358 @@ +/* + +Copyright 1996,2002 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Jason Lapenta, Scott Smedley + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA + + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 24-Jul-2002 SS GPL licence. + +*/ + +/* This code is a modified version of examples provided by Data Translations.*/ + +#ifndef DT3155_IO_INC +#define DT3155_IO_INC + +/* macros to access registers */ + +#define WriteMReg(Address, Data) (*((u32 *)(Address)) = Data) +#define ReadMReg(Address, Data) (Data = *((u32 *)(Address))) + +/***************** 32 bit register globals **************/ + +/* offsets for 32-bit memory mapped registers */ + +#define EVEN_DMA_START 0x000 +#define ODD_DMA_START 0x00C +#define EVEN_DMA_STRIDE 0x018 +#define ODD_DMA_STRIDE 0x024 +#define EVEN_PIXEL_FMT 0x030 +#define ODD_PIXEL_FMT 0x034 +#define FIFO_TRIGGER 0x038 +#define XFER_MODE 0x03C +#define CSR1 0x040 +#define RETRY_WAIT_CNT 0x044 +#define INT_CSR 0x048 +#define EVEN_FLD_MASK 0x04C +#define ODD_FLD_MASK 0x050 +#define MASK_LENGTH 0x054 +#define FIFO_FLAG_CNT 0x058 +#define IIC_CLK_DUR 0x05C +#define IIC_CSR1 0x060 +#define IIC_CSR2 0x064 +#define EVEN_DMA_UPPR_LMT 0x08C +#define ODD_DMA_UPPR_LMT 0x090 + +#define CLK_DUR_VAL 0x01010101 + + + +/******** Assignments and Typedefs for 32 bit Memory Mapped Registers ********/ + +typedef union fifo_trigger_tag { + u32 reg; + struct { + u32 PACKED:6; + u32 :9; + u32 PLANER:7; + u32 :9; + } fld; +} FIFO_TRIGGER_R; + +typedef union xfer_mode_tag { + u32 reg; + struct { + u32 :2; + u32 FIELD_TOGGLE:1; + u32 :5; + u32 :2; + u32 :22; + } fld; +} XFER_MODE_R; + +typedef union csr1_tag { + u32 reg; + struct { + u32 CAP_CONT_EVE:1; + u32 CAP_CONT_ODD:1; + u32 CAP_SNGL_EVE:1; + u32 CAP_SNGL_ODD:1; + u32 FLD_DN_EVE :1; + u32 FLD_DN_ODD :1; + u32 SRST :1; + u32 FIFO_EN :1; + u32 FLD_CRPT_EVE:1; + u32 FLD_CRPT_ODD:1; + u32 ADDR_ERR_EVE:1; + u32 ADDR_ERR_ODD:1; + u32 CRPT_DIS :1; + u32 RANGE_EN :1; + u32 :16; + } fld; +} CSR1_R; + +typedef union retry_wait_cnt_tag { + u32 reg; + struct { + u32 RTRY_WAIT_CNT:8; + u32 :24; + } fld; +} RETRY_WAIT_CNT_R; + +typedef union int_csr_tag { + u32 reg; + struct { + u32 FLD_END_EVE :1; + u32 FLD_END_ODD :1; + u32 FLD_START :1; + u32 :5; + u32 FLD_END_EVE_EN:1; + u32 FLD_END_ODD_EN:1; + u32 FLD_START_EN :1; + u32 :21; + } fld; +} INT_CSR_R; + +typedef union mask_length_tag { + u32 reg; + struct { + u32 MASK_LEN_EVE:5; + u32 :11; + u32 MASK_LEN_ODD:5; + u32 :11; + } fld; +} MASK_LENGTH_R; + +typedef union fifo_flag_cnt_tag { + u32 reg; + struct { + u32 AF_COUNT:7; + u32 :9; + u32 AE_COUNT:7; + u32 :9; + } fld; +} FIFO_FLAG_CNT_R; + +typedef union iic_clk_dur { + u32 reg; + struct { + u32 PHASE_1:8; + u32 PHASE_2:8; + u32 PHASE_3:8; + u32 PHASE_4:8; + } fld; +} IIC_CLK_DUR_R; + +typedef union iic_csr1_tag { + u32 reg; + struct { + u32 AUTO_EN :1; + u32 BYPASS :1; + u32 SDA_OUT :1; + u32 SCL_OUT :1; + u32 :4; + u32 AUTO_ABORT :1; + u32 DIRECT_ABORT:1; + u32 SDA_IN :1; + u32 SCL_IN :1; + u32 :4; + u32 AUTO_ADDR :8; + u32 RD_DATA :8; + } fld; +} IIC_CSR1_R; + +/********************************** + * iic_csr2_tag + */ +typedef union iic_csr2_tag { + u32 reg; + struct { + u32 DIR_WR_DATA :8; + u32 DIR_SUB_ADDR:8; + u32 DIR_RD :1; + u32 DIR_ADDR :7; + u32 NEW_CYCLE :1; + u32 :7; + } fld; +} IIC_CSR2_R; + +/* use for both EVEN and ODD DMA UPPER LIMITS */ + +/* + * dma_upper_lmt_tag + */ +typedef union dma_upper_lmt_tag { + u32 reg; + struct { + u32 DMA_UPPER_LMT_VAL:24; + u32 :8; + } fld; +} DMA_UPPER_LMT_R; + + +/* + * Global declarations of local copies of boards' 32 bit registers + */ +extern u32 even_dma_start_r; /* bit 0 should always be 0 */ +extern u32 odd_dma_start_r; /* .. */ +extern u32 even_dma_stride_r; /* bits 0&1 should always be 0 */ +extern u32 odd_dma_stride_r; /* .. */ +extern u32 even_pixel_fmt_r; +extern u32 odd_pixel_fmt_r; + +extern FIFO_TRIGGER_R fifo_trigger_r; +extern XFER_MODE_R xfer_mode_r; +extern CSR1_R csr1_r; +extern RETRY_WAIT_CNT_R retry_wait_cnt_r; +extern INT_CSR_R int_csr_r; + +extern u32 even_fld_mask_r; +extern u32 odd_fld_mask_r; + +extern MASK_LENGTH_R mask_length_r; +extern FIFO_FLAG_CNT_R fifo_flag_cnt_r; +extern IIC_CLK_DUR_R iic_clk_dur_r; +extern IIC_CSR1_R iic_csr1_r; +extern IIC_CSR2_R iic_csr2_r; +extern DMA_UPPER_LMT_R even_dma_upper_lmt_r; +extern DMA_UPPER_LMT_R odd_dma_upper_lmt_r; + + + +/***************** 8 bit I2C register globals ***********/ +#define CSR2 0x010 /* indices of 8-bit I2C mapped reg's*/ +#define EVEN_CSR 0x011 +#define ODD_CSR 0x012 +#define CONFIG 0x013 +#define DT_ID 0x01F +#define X_CLIP_START 0x020 +#define Y_CLIP_START 0x022 +#define X_CLIP_END 0x024 +#define Y_CLIP_END 0x026 +#define AD_ADDR 0x030 +#define AD_LUT 0x031 +#define AD_CMD 0x032 +#define DIG_OUT 0x040 +#define PM_LUT_ADDR 0x050 +#define PM_LUT_DATA 0x051 + + +/******** Assignments and Typedefs for 8 bit I2C Registers********************/ + +typedef union i2c_csr2_tag { + u8 reg; + struct { + u8 CHROM_FIL:1; + u8 SYNC_SNTL:1; + u8 HZ50:1; + u8 SYNC_PRESENT:1; + u8 BUSY_EVE:1; + u8 BUSY_ODD:1; + u8 DISP_PASS:1; + } fld; +} I2C_CSR2; + +typedef union i2c_even_csr_tag { + u8 reg; + struct { + u8 DONE_EVE :1; + u8 SNGL_EVE :1; + u8 ERROR_EVE:1; + u8 :5; + } fld; +} I2C_EVEN_CSR; + +typedef union i2c_odd_csr_tag { + u8 reg; + struct { + u8 DONE_ODD:1; + u8 SNGL_ODD:1; + u8 ERROR_ODD:1; + u8 :5; + } fld; +} I2C_ODD_CSR; + +typedef union i2c_config_tag { + u8 reg; + struct { + u8 ACQ_MODE:2; + u8 EXT_TRIG_EN:1; + u8 EXT_TRIG_POL:1; + u8 H_SCALE:1; + u8 CLIP:1; + u8 PM_LUT_SEL:1; + u8 PM_LUT_PGM:1; + } fld; +} I2C_CONFIG; + + +typedef union i2c_ad_cmd_tag { + /* bits can have 3 different meanings depending on value of AD_ADDR */ + u8 reg; + /* Bt252 Command Register if AD_ADDR = 00h */ + struct { + u8 :2; + u8 SYNC_LVL_SEL:2; + u8 SYNC_CNL_SEL:2; + u8 DIGITIZE_CNL_SEL1:2; + } bt252_command; + + /* Bt252 IOUT0 register if AD_ADDR = 01h */ + struct { + u8 IOUT_DATA:8; + } bt252_iout0; + + /* BT252 IOUT1 register if AD_ADDR = 02h */ + struct { + u8 IOUT_DATA:8; + } bt252_iout1; +} I2C_AD_CMD; + + +/***** Global declarations of local copies of boards' 8 bit I2C registers ***/ + +extern I2C_CSR2 i2c_csr2; +extern I2C_EVEN_CSR i2c_even_csr; +extern I2C_ODD_CSR i2c_odd_csr; +extern I2C_CONFIG i2c_config; +extern u8 i2c_dt_id; +extern u8 i2c_x_clip_start; +extern u8 i2c_y_clip_start; +extern u8 i2c_x_clip_end; +extern u8 i2c_y_clip_end; +extern u8 i2c_ad_addr; +extern u8 i2c_ad_lut; +extern I2C_AD_CMD i2c_ad_cmd; +extern u8 i2c_dig_out; +extern u8 i2c_pm_lut_addr; +extern u8 i2c_pm_lut_data; + +/* Functions for Global use */ + +/* access 8-bit IIC registers */ + +extern int ReadI2C(u8 *lpReg, u_short wIregIndex, u8 *byVal); +extern int WriteI2C(u8 *lpReg, u_short wIregIndex, u8 byVal); + +#endif diff --git a/drivers/staging/dt3155/dt3155_isr.c b/drivers/staging/dt3155/dt3155_isr.c new file mode 100644 index 000000000000..09d7d9b8272d --- /dev/null +++ b/drivers/staging/dt3155/dt3155_isr.c @@ -0,0 +1,516 @@ +/* + +Copyright 1996,2002,2005 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Jason Lapenta, Scott Smedley, Greg Sharp + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA + + File: dt3155_isr.c +Purpose: Buffer management routines, and other routines for the ISR + (the actual isr is in dt3155_drv.c) + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 03-Jul-2000 JML n/a + 02-Apr-2002 SS Mods to make work with separate allocator + module; Merged John Roll's mods to make work with + multiple boards. + 10-Jul-2002 GCS Complete rewrite of setup_buffers to disallow + buffers which span a 4MB boundary. + 24-Jul-2002 SS GPL licence. + 30-Jul-2002 NJC Added support for buffer loop. + 31-Jul-2002 NJC Complete rewrite of buffer management + 02-Aug-2002 NJC Including slab.h instead of malloc.h (no warning). + Also, allocator_init() now returns allocator_max + so cleaned up allocate_buffers() accordingly. + 08-Aug-2005 SS port to 2.6 kernel. + +*/ + +#include <asm/system.h> +#include <linux/gfp.h> +#include <linux/sched.h> +#include <linux/types.h> + +#include "dt3155.h" +#include "dt3155_drv.h" +#include "dt3155_io.h" +#include "dt3155_isr.h" +#include "allocator.h" + +#define FOUR_MB (0x0400000) /* Can't DMA accross a 4MB boundary!*/ +#define UPPER_10_BITS (0x3FF<<22) /* Can't DMA accross a 4MB boundary!*/ + + +/* Pointer into global structure for handling buffers */ +struct dt3155_fbuffer_s *dt3155_fbuffer[MAXBOARDS] = {NULL +#if MAXBOARDS == 2 + , NULL +#endif +}; + +/****************************************************************************** + * Simple array based que struct + * + * Some handy functions using the buffering structure. + *****************************************************************************/ + + +/*************************** + * are_empty_buffers + * m is minor # of device + ***************************/ +inline bool are_empty_buffers( int m ) +{ + return ( dt3155_fbuffer[ m ]->empty_len ); +} + +/************************** + * push_empty + * m is minor # of device + * + * This is slightly confusing. The number empty_len is the literal # + * of empty buffers. After calling, empty_len-1 is the index into the + * empty buffer stack. So, if empty_len == 1, there is one empty buffer, + * given by dt3155_fbuffer[m]->empty_buffers[0]. + * empty_buffers should never fill up, though this is not checked. + **************************/ +inline void push_empty( int index, int m ) +{ + dt3155_fbuffer[m]->empty_buffers[ dt3155_fbuffer[m]->empty_len ] = index; + dt3155_fbuffer[m]->empty_len++; +} + +/************************** + * pop_empty( m ) + * m is minor # of device + **************************/ +inline int pop_empty( int m ) +{ + dt3155_fbuffer[m]->empty_len--; + return dt3155_fbuffer[m]->empty_buffers[ dt3155_fbuffer[m]->empty_len ]; +} + +/************************* + * is_ready_buf_empty( m ) + * m is minor # of device + *************************/ +inline bool is_ready_buf_empty( int m ) +{ + return ((dt3155_fbuffer[ m ]->ready_len) == 0); +} + +/************************* + * is_ready_buf_full( m ) + * m is minor # of device + * this should *never* be true if there are any active, locked or empty + * buffers, since it corresponds to nbuffers ready buffers!! + * 7/31/02: total rewrite. --NJC + *************************/ +inline bool is_ready_buf_full( int m ) +{ + return ( dt3155_fbuffer[ m ]->ready_len == dt3155_fbuffer[ m ]->nbuffers ); +} + +/***************************************************** + * push_ready( m, buffer ) + * m is minor # of device + * + *****************************************************/ +inline void push_ready( int m, int index ) +{ + int head = dt3155_fbuffer[m]->ready_head; + + dt3155_fbuffer[ m ]->ready_que[ head ] = index; + dt3155_fbuffer[ m ]->ready_head = ( (head + 1) % + (dt3155_fbuffer[ m ]->nbuffers) ); + dt3155_fbuffer[ m ]->ready_len++; + +} + +/***************************************************** + * get_tail() + * m is minor # of device + * + * Simply comptutes the tail given the head and the length. + *****************************************************/ +static inline int get_tail( int m ) +{ + return ((dt3155_fbuffer[ m ]->ready_head - + dt3155_fbuffer[ m ]->ready_len + + dt3155_fbuffer[ m ]->nbuffers)% + (dt3155_fbuffer[ m ]->nbuffers)); +} + + + +/***************************************************** + * pop_ready() + * m is minor # of device + * + * This assumes that there is a ready buffer ready... should + * be checked (e.g. with is_ready_buf_empty() prior to call. + *****************************************************/ +inline int pop_ready( int m ) +{ + int tail; + tail = get_tail(m); + dt3155_fbuffer[ m ]->ready_len--; + return dt3155_fbuffer[ m ]->ready_que[ tail ]; +} + + +/***************************************************** + * printques + * m is minor # of device + *****************************************************/ +inline void printques( int m ) +{ + int head = dt3155_fbuffer[ m ]->ready_head; + int tail; + int num = dt3155_fbuffer[ m ]->nbuffers; + int frame_index; + int index; + + tail = get_tail(m); + + printk("\n R:"); + for ( index = tail; index != head; index++, index = index % (num) ) + { + frame_index = dt3155_fbuffer[ m ]->ready_que[ index ]; + printk(" %d ", frame_index ); + } + + printk("\n E:"); + for ( index = 0; index < dt3155_fbuffer[ m ]->empty_len; index++ ) + { + frame_index = dt3155_fbuffer[ m ]->empty_buffers[ index ]; + printk(" %d ", frame_index ); + } + + frame_index = dt3155_fbuffer[ m ]->active_buf; + printk("\n A: %d", frame_index); + + frame_index = dt3155_fbuffer[ m ]->locked_buf; + printk("\n L: %d \n", frame_index ); + +} + +/***************************************************** + * adjust_4MB + * + * If a buffer intersects the 4MB boundary, push + * the start address up to the beginning of the + * next 4MB chunk (assuming bufsize < 4MB). + *****************************************************/ +u32 adjust_4MB (u32 buf_addr, u32 bufsize) { + if (((buf_addr+bufsize) & UPPER_10_BITS) != (buf_addr & UPPER_10_BITS)) + return (buf_addr+bufsize) & UPPER_10_BITS; + else + return buf_addr; +} + + +/***************************************************** + * allocate_buffers + * + * Try to allocate enough memory for all requested + * buffers. If there is not enough free space + * try for less memory. + *****************************************************/ +void allocate_buffers (u32 *buf_addr, u32* total_size_kbs, + u32 bufsize) +{ + /* Compute the minimum amount of memory guaranteed to hold all + MAXBUFFERS such that no buffer crosses the 4MB boundary. + Store this value in the variable "full_size" */ + + u32 allocator_max; + u32 bufs_per_chunk = (FOUR_MB / bufsize); + u32 filled_chunks = (MAXBUFFERS-1) / bufs_per_chunk; + u32 leftover_bufs = MAXBUFFERS - filled_chunks * bufs_per_chunk; + + u32 full_size = bufsize /* possibly unusable part of 1st chunk */ + + filled_chunks * FOUR_MB /* max # of completely filled 4mb chunks */ + + leftover_bufs * bufsize; /* these buffs will be in a partly filled + chunk at beginning or end */ + + u32 full_size_kbs = 1 + (full_size-1) / 1024; + u32 min_size_kbs = 2*ndevices*bufsize / 1024; + u32 size_kbs; + + /* Now, try to allocate full_size. If this fails, keep trying for + less & less memory until it succeeds. */ +#ifndef STANDALONE_ALLOCATOR + /* initialize the allocator */ + allocator_init(&allocator_max); +#endif + size_kbs = full_size_kbs; + *buf_addr = 0; + printk("DT3155: We would like to get: %d KB\n", full_size_kbs); + printk("DT3155: ...but need at least: %d KB\n", min_size_kbs); + printk("DT3155: ...the allocator has: %d KB\n", allocator_max); + size_kbs = (full_size_kbs <= allocator_max ? full_size_kbs : allocator_max); + if (size_kbs > min_size_kbs) { + if ((*buf_addr = allocator_allocate_dma (size_kbs, GFP_KERNEL)) != 0) { + printk("DT3155: Managed to allocate: %d KB\n", size_kbs); + *total_size_kbs = size_kbs; + return; + } + } + /* If we got here, the allocation failed */ + printk ("DT3155: Allocator failed!\n"); + *buf_addr = 0; + *total_size_kbs = 0; + return; + +} + + +/***************************************************** + * dt3155_setup_buffers + * + * setup_buffers just puts the buffering system into + * a consistent state before the start of interrupts + * + * JML : it looks like all the buffers need to be + * continuous. So I'm going to try and allocate one + * continuous buffer. + * + * GCS : Fix DMA problems when buffer spans + * 4MB boundary. Also, add error checking. This + * function will return -ENOMEM when not enough memory. + *****************************************************/ +u32 dt3155_setup_buffers(u32 *allocatorAddr) + +{ + u32 index; + u32 rambuff_addr; /* start of allocation */ + u32 rambuff_size; /* total size allocated to driver */ + u32 rambuff_acm; /* accumlator, keep track of how much + is left after being split up*/ + u32 rambuff_end; /* end of rambuff */ + u32 numbufs; /* number of useful buffers allocated (per device) */ + u32 bufsize = DT3155_MAX_ROWS * DT3155_MAX_COLS; + int m; /* minor # of device, looped for all devs */ + + /* zero the fbuffer status and address structure */ + for ( m = 0; m < ndevices; m++) + { + dt3155_fbuffer[ m ] = &(dt3155_status[ m ].fbuffer); + + /* Make sure the buffering variables are consistent */ + { + u8 *ptr = (u8 *) dt3155_fbuffer[ m ]; + for( index = 0; index < sizeof(struct dt3155_fbuffer_s); index++) + *(ptr++)=0; + } + } + + /* allocate a large contiguous chunk of RAM */ + allocate_buffers (&rambuff_addr, &rambuff_size, bufsize); + printk("DT3155: mem info\n"); + printk(" - rambuf_addr = 0x%x \n", rambuff_addr); + printk(" - length (kb) = %u \n", rambuff_size); + if( rambuff_addr == 0 ) + { + printk( KERN_INFO + "DT3155: Error setup_buffers() allocator dma failed \n" ); + return -ENOMEM; + } + *allocatorAddr = rambuff_addr; + rambuff_end = rambuff_addr + 1024 * rambuff_size; + + /* after allocation, we need to count how many useful buffers there + are so we can give an equal number to each device */ + rambuff_acm = rambuff_addr; + for ( index = 0; index < MAXBUFFERS; index++) { + rambuff_acm = adjust_4MB (rambuff_acm, bufsize);/*avoid spanning 4MB bdry*/ + if (rambuff_acm + bufsize > rambuff_end) + break; + rambuff_acm += bufsize; + } + /* Following line is OK, will waste buffers if index + * not evenly divisible by ndevices -NJC*/ + numbufs = index / ndevices; + printk(" - numbufs = %u\n", numbufs); + if (numbufs < 2) { + printk( KERN_INFO + "DT3155: Error setup_buffers() couldn't allocate 2 bufs/board\n" ); + return -ENOMEM; + } + + /* now that we have board memory we spit it up */ + /* between the boards and the buffers */ + rambuff_acm = rambuff_addr; + for ( m = 0; m < ndevices; m ++) + { + rambuff_acm = adjust_4MB (rambuff_acm, bufsize); + + /* Save the start of this boards buffer space (for mmap). */ + dt3155_status[ m ].mem_addr = rambuff_acm; + + for (index = 0; index < numbufs; index++) + { + rambuff_acm = adjust_4MB (rambuff_acm, bufsize); + if (rambuff_acm + bufsize > rambuff_end) { + /* Should never happen */ + printk ("DT3155 PROGRAM ERROR (GCS)\n" + "Error distributing allocated buffers\n"); + return -ENOMEM; + } + + dt3155_fbuffer[ m ]->frame_info[ index ].addr = rambuff_acm; + push_empty( index, m ); + /* printk(" - Buffer : %lx\n", + * dt3155_fbuffer[ m ]->frame_info[ index ].addr ); + */ + dt3155_fbuffer[ m ]->nbuffers += 1; + rambuff_acm += bufsize; + } + + /* Make sure there is an active buffer there. */ + dt3155_fbuffer[ m ]->active_buf = pop_empty( m ); + dt3155_fbuffer[ m ]->even_happened = 0; + dt3155_fbuffer[ m ]->even_stopped = 0; + + /* make sure there is no locked_buf JML 2/28/00 */ + dt3155_fbuffer[ m ]->locked_buf = -1; + + dt3155_status[ m ].mem_size = + rambuff_acm - dt3155_status[ m ].mem_addr; + + /* setup the ready queue */ + dt3155_fbuffer[ m ]->ready_head = 0; + dt3155_fbuffer[ m ]->ready_len = 0; + printk("Available buffers for device %d: %d\n", + m, dt3155_fbuffer[ m ]->nbuffers); + } + + return 1; +} + +/***************************************************** + * internal_release_locked_buffer + * + * The internal function for releasing a locked buffer. + * It assumes interrupts are turned off. + * + * m is minor number of device + *****************************************************/ +static inline void internal_release_locked_buffer( int m ) +{ + /* Pointer into global structure for handling buffers */ + if ( dt3155_fbuffer[ m ]->locked_buf >= 0 ) + { + push_empty( dt3155_fbuffer[ m ]->locked_buf, m ); + dt3155_fbuffer[ m ]->locked_buf = -1; + } +} + + +/***************************************************** + * dt3155_release_locked_buffer() + * m is minor # of device + * + * The user function of the above. + * + *****************************************************/ +inline void dt3155_release_locked_buffer( int m ) +{ + unsigned long int flags; + local_save_flags(flags); + local_irq_disable(); + internal_release_locked_buffer(m); + local_irq_restore(flags); +} + + +/***************************************************** + * dt3155_flush() + * m is minor # of device + * + *****************************************************/ +inline int dt3155_flush( int m ) +{ + int index; + unsigned long int flags; + local_save_flags(flags); + local_irq_disable(); + + internal_release_locked_buffer( m ); + dt3155_fbuffer[ m ]->empty_len = 0; + + for ( index = 0; index < dt3155_fbuffer[ m ]->nbuffers; index++ ) + push_empty( index, m ); + + /* Make sure there is an active buffer there. */ + dt3155_fbuffer[ m ]->active_buf = pop_empty( m ); + + dt3155_fbuffer[ m ]->even_happened = 0; + dt3155_fbuffer[ m ]->even_stopped = 0; + + /* setup the ready queue */ + dt3155_fbuffer[ m ]->ready_head = 0; + dt3155_fbuffer[ m ]->ready_len = 0; + + local_irq_restore(flags); + + return 0; +} + +/***************************************************** + * dt3155_get_ready_buffer() + * m is minor # of device + * + * get_ready_buffer will grab the next chunk of data + * if it is already there, otherwise it returns 0. + * If the user has a buffer locked it will unlock + * that buffer before returning the new one. + *****************************************************/ +inline int dt3155_get_ready_buffer( int m ) +{ + int frame_index; + unsigned long int flags; + local_save_flags(flags); + local_irq_disable(); + +#ifdef DEBUG_QUES_A + printques( m ); +#endif + + internal_release_locked_buffer( m ); + + if (is_ready_buf_empty( m )) + frame_index = -1; + else + { + frame_index = pop_ready( m ); + dt3155_fbuffer[ m ]->locked_buf = frame_index; + } + +#ifdef DEBUG_QUES_B + printques( m ); +#endif + + local_irq_restore(flags); + + return frame_index; +} diff --git a/drivers/staging/dt3155/dt3155_isr.h b/drivers/staging/dt3155/dt3155_isr.h new file mode 100644 index 000000000000..7595cb16c988 --- /dev/null +++ b/drivers/staging/dt3155/dt3155_isr.h @@ -0,0 +1,77 @@ +/* + +Copyright 1996,2002 Gregory D. Hager, Alfred A. Rizzi, Noah J. Cowan, + Jason Lapenta, Scott Smedley + +This file is part of the DT3155 Device Driver. + +The DT3155 Device Driver 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. + +The DT3155 Device Driver 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 the DT3155 Device Driver; if not, write to the Free +Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA + + +-- Changes -- + + Date Programmer Description of changes made + ------------------------------------------------------------------- + 03-Jul-2000 JML n/a + 24-Jul-2002 SS GPL licence. + 26-Oct-2009 SS Porting to 2.6.30 kernel. + +-- notes -- + +*/ + +#ifndef DT3155_ISR_H +#define DT3155_ISR_H + +extern struct dt3155_fbuffer_s *dt3155_fbuffer[MAXBOARDS]; + +/* User functions for buffering */ +/* Initialize the buffering system. This should */ +/* be called prior to enabling interrupts */ + +u32 dt3155_setup_buffers(u32 *allocatorAddr); + +/* Get the next frame of data if it is ready. Returns */ +/* zero if no data is ready. If there is data but */ +/* the user has a locked buffer, it will unlock that */ +/* buffer and return it to the free list. */ + +int dt3155_get_ready_buffer(int minor); + +/* Return a locked buffer to the free list */ + +void dt3155_release_locked_buffer(int minor); + +/* Flush the buffer system */ +int dt3155_flush(int minor); + +/********************************** + * Simple array based que struct + **********************************/ + +bool are_empty_buffers(int minor); +void push_empty(int index, int minor); + +int pop_empty(int minor); + +bool is_ready_buf_empty(int minor); +bool is_ready_buf_full(int minor); + +void push_ready(int minor, int index); +int pop_ready(int minor); + + +#endif |