Commit aa337ef1 authored by Scott Smedley's avatar Scott Smedley Committed by Greg Kroah-Hartman
Browse files

Staging: add dt3155 driver



This is a driver for the DT3155 Digitizer

Signed-off-by: default avatarScott Smedley <ss@aao.gov.au>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent da94a755
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line

ifeq ($(shell [[ `uname -r | cut -f 1,2 -d\.` < 2.6 ]] && echo pre2.6),pre2.6)
# system with a pre 2.6 kernel _don't_ use kbuild.
all:
	$(MAKE) -f Makefile.pre-2.6

clean:
	rm -f *.o

else
# systems with a 2.6 or later kernel use kbuild.
ifneq ($(KERNELRELEASE),)
obj-m	:= dt3155.o
dt3155-objs := dt3155_drv.o dt3155_isr.o dt3155_io.o allocator.o

else
KDIR	:= /lib/modules/$(shell uname -r)/build
PWD		:= $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	rm -rf *.o *.mod *.mod.c *.ko  .dt3155* .allocator.o.cmd  .tmp_versions

endif
endif
+98 −0
Original line number Diff line number Diff line

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.


+296 −0
Original line number Diff line number Diff line
/*
 * 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(u_int *) 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 <asm/page.h>

#include "sysdep.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      = 0;  /* physical address */
static unsigned long allocator_buffer_size = 0;  /* 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 = NULL;


#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(u_long *allocator_max)
{
    /* check how much free memory is there */

    volatile 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;

    *allocator_max = allocator_buffer_size; /* Back to the user code, in KB */

    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;
}

+28 −0
Original line number Diff line number Diff line
/*
 * 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(u_long *);
void allocator_cleanup(void);
+175 −0
Original line number Diff line number Diff line
/*

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

  $Id: dt3155.h,v 1.11 2005/08/09 06:08:51 ssmedley Exp $

-- 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>	/* u_int etc. */
#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 {
  u_int acq_mode;
  u_int cols, rows;
  u_int continuous;
};


/* hold data for each frame */
typedef struct
{
  u_long addr;          /* address of the buffer with the frame */
  u_long 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 ];
  u_long ready_head;  /* The most recent buffer located here */
  u_long ready_len;   /* The number of ready buffers */

  int even_happened;
  int even_stopped;

  int stop_acquire;             /* Flag to stop interrupts */
  u_long 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 */
  u_long reg_addr;             /* Register address for a single card */
  u_long mem_addr;             /* Buffer start addr for this card */
  u_long mem_size;             /* This is the amount of mem available  */
  u_int  irq;                  /* this card's irq */
  struct dt3155_config_s config;  /* configuration struct */
  struct dt3155_fbuffer_s fbuffer;/* frame buffer state struct */
  u_long state;                /* this card's state */
  u_int 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 - SS */
#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 - SS */

/* User code will probably want to declare one of these for each card */
typedef struct dt3155_read_s
{
  u_long offset;
  u_long frame_seq;
  u_long state;

  frame_info_t frame_info;
} dt3155_read_t;

#endif /* _DT3155_inc */
Loading