/*
* Brontes PCI frame grabber driver
*
* Copyright (C) 2008 3M Company
* Contact: Justin Bronder <jsbronder@brontes3d.com>
* Original Authors: Daniel Drake <ddrake@brontes3d.com>
* Duane Griffin <duaneg@dghda.com>
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
static unsigned int b3dfg_nbuf = 2;
module_param_named(buffer_count, b3dfg_nbuf, uint, 0444);
MODULE_PARM_DESC(buffer_count, "Number of buffers (min 2, default 2)");
MODULE_AUTHOR("Daniel Drake <ddrake@brontes3d.com>");
MODULE_DESCRIPTION("Brontes frame grabber driver");
MODULE_LICENSE("GPL");
#define DRIVER_NAME "b3dfg"
#define B3DFG_MAX_DEVS 4
#define B3DFG_FRAMES_PER_BUFFER 3
#define B3DFG_BAR_REGS 0
#define B3DFG_REGS_LENGTH 0x10000
#define B3DFG_IOC_MAGIC 0xb3 /* dfg :-) */
#define B3DFG_IOCGFRMSZ _IOR(B3DFG_IOC_MAGIC, 1, int)
#define B3DFG_IOCTNUMBUFS _IO(B3DFG_IOC_MAGIC, 2)
#define B3DFG_IOCTTRANS _IO(B3DFG_IOC_MAGIC, 3)
#define B3DFG_IOCTQUEUEBUF _IO(B3DFG_IOC_MAGIC, 4)
#define B3DFG_IOCTPOLLBUF _IOWR(B3DFG_IOC_MAGIC, 5, struct b3dfg_poll)
#define B3DFG_IOCTWAITBUF _IOWR(B3DFG_IOC_MAGIC, 6, struct b3dfg_wait)
#define B3DFG_IOCGWANDSTAT _IOR(B3DFG_IOC_MAGIC, 7, int)
enum {
/* number of 4kb pages per frame */
B3D_REG_FRM_SIZE = 0x0,
/* bit 0: set to enable interrupts
* bit 1: set to enable cable status change interrupts */
B3D_REG_HW_CTRL = 0x4,
/* bit 0-1 - 1-based ID of next pending frame transfer (0 = none)
* bit 2 indicates the previous DMA transfer has completed
* bit 3 indicates wand cable status change
* bit 8:15 - counter of number of discarded triplets */
B3D_REG_DMA_STS = 0x8,
/* bit 0: wand status (1 = present, 0 = disconnected) */
B3D_REG_WAND_STS = 0xc,
/* bus address for DMA transfers. lower 2 bits must be zero because DMA
* works with 32 bit word size. */
B3D_REG_EC220_DMA_ADDR = 0x8000,
/* bit 20:0 - number of 32 bit words to be transferred
* bit 21:31 - reserved */
B3D_REG_EC220_TRF_SIZE = 0x8004,
/* bit 0 - error bit
* bit 1 - interrupt bit (set to generate interrupt at end of transfer)
* bit 2 - start bit (set to start transfer)
* bit 3 - direction (0 = DMA_TO_DEVICE, 1 = DMA_FROM_DEVICE
* bit 4:31 - reserved */
B3D_REG_EC220_DMA_STS = 0x8008,
};
enum b3dfg_buffer_state {
B3DFG_BUFFER_POLLED = 0,
B3DFG_BUFFER_PENDING,
B3DFG_BUFFER_POPULATED,
};
struct b3dfg_buffer {
unsigned char *frame[B3DFG_FRAMES_PER_BUFFER];
struct list_head list;
u8 state;
};
struct b3dfg_dev {
/* no protection needed: all finalized at initialization time */
struct pci_dev *pdev;
struct cdev chardev;
struct device *dev;
void __iomem *regs;
unsigned int frame_size;
/*
* Protects buffer state, including buffer_queue, triplet_ready,
* cur_dma_frame_idx & cur_dma_frame_addr.
*/
spinlock_t buffer_lock;
struct b3dfg_buffer *buffers;
struct list_head buffer_queue;
/* Last frame in triplet transferred (-1 if none). */
int cur_dma_frame_idx;
/* Current frame's address for DMA. */
dma_addr_t cur_dma_frame_addr;
/*
* Protects cstate_tstamp.
* Nests inside buffer_lock.
*/
spinlock_t cstate_lock;
unsigned long cstate_tstamp;
/*
* Protects triplets_dropped.
* Nests inside buffers_lock.
*/
spinlock_t triplets_dropped_lock;
unsigned int triplets_dropped;
wait_queue_head_t buffer_waitqueue;
unsigned int transmission_enabled:1;
unsigned int triplet_ready:1;
};
static u8 b3dfg_devices[B3DFG_MAX_DEVS];
static struct class *b3dfg_class;
static dev_t b3dfg_devt;
static const struct pci_device_id b3dfg_ids[] __devinitdata = {
{ PCI_DEVICE(0x0b3d, 0x0001) },
{ },
};
MODULE_DEVICE_TABLE(pci, b3dfg_ids);
/***** user-visible types *****/
struct b3dfg_poll {
int buffer_idx;
unsigned int triplets_dropped;
};
struct b3dfg_wait {
int buffer_idx;
unsigned int timeout;
unsigned int triplets_dropped;
};
/**** register I/O ****/
static u32 b3dfg_read32(struct b3dfg_dev *fgdev, u16 reg)
{
return ioread32(fgdev->regs + reg);
}
static void b3dfg_write32(struct b3dfg_dev *fgdev, u16 reg, u32 value)
{
iowrite32(value, fgdev->regs + reg);
}
/**** buffer management ****/
/*
* Program EC220 for transfer of a specific frame.
* Called with buffer_lock held.
*/
static int setup_frame_transfer(struct b3dfg_dev *fgdev,
struct b3dfg_buffer *buf,