diff options
author | David S. Miller <davem@davemloft.net> | 2010-03-15 23:23:54 (GMT) |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-03-15 23:23:54 (GMT) |
commit | 4961e02f1999e1c3468c09b2669c94d7c3ae82a8 (patch) | |
tree | 44c15abb09d7ba5e17a9aba95ee246648b1c1a8a /drivers/staging/b3dfg/b3dfg.c | |
parent | d14a0ebda7d3daede1a99c01527affb9ceaa4c22 (diff) | |
parent | a3d3203e4bb40f253b1541e310dc0f9305be7c84 (diff) | |
download | linux-fsl-qoriq-4961e02f1999e1c3468c09b2669c94d7c3ae82a8.tar.xz |
Merge branch 'master' of /home/davem/src/GIT/linux-2.6/
Diffstat (limited to 'drivers/staging/b3dfg/b3dfg.c')
-rw-r--r-- | drivers/staging/b3dfg/b3dfg.c | 1100 |
1 files changed, 0 insertions, 1100 deletions
diff --git a/drivers/staging/b3dfg/b3dfg.c b/drivers/staging/b3dfg/b3dfg.c deleted file mode 100644 index 4a43c51..0000000 --- a/drivers/staging/b3dfg/b3dfg.c +++ /dev/null @@ -1,1100 +0,0 @@ - /* - * 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, int frame) -{ - unsigned char *frm_addr; - dma_addr_t frm_addr_dma; - unsigned int frm_size = fgdev->frame_size; - - frm_addr = buf->frame[frame]; - frm_addr_dma = pci_map_single(fgdev->pdev, frm_addr, - frm_size, PCI_DMA_FROMDEVICE); - if (pci_dma_mapping_error(fgdev->pdev, frm_addr_dma)) - return -ENOMEM; - - fgdev->cur_dma_frame_addr = frm_addr_dma; - fgdev->cur_dma_frame_idx = frame; - - b3dfg_write32(fgdev, B3D_REG_EC220_DMA_ADDR, - cpu_to_le32(frm_addr_dma)); - b3dfg_write32(fgdev, B3D_REG_EC220_TRF_SIZE, - cpu_to_le32(frm_size >> 2)); - b3dfg_write32(fgdev, B3D_REG_EC220_DMA_STS, 0xf); - - return 0; -} - -/* Caller should hold buffer lock */ -static void dequeue_all_buffers(struct b3dfg_dev *fgdev) -{ - int i; - for (i = 0; i < b3dfg_nbuf; i++) { - struct b3dfg_buffer *buf = &fgdev->buffers[i]; - buf->state = B3DFG_BUFFER_POLLED; - list_del_init(&buf->list); - } -} - -/* queue a buffer to receive data */ -static int queue_buffer(struct b3dfg_dev *fgdev, int bufidx) -{ - struct device *dev = &fgdev->pdev->dev; - struct b3dfg_buffer *buf; - unsigned long flags; - int r = 0; - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - if (bufidx < 0 || bufidx >= b3dfg_nbuf) { - dev_dbg(dev, "Invalid buffer index, %d\n", bufidx); - r = -ENOENT; - goto out; - } - buf = &fgdev->buffers[bufidx]; - - if (unlikely(buf->state == B3DFG_BUFFER_PENDING)) { - dev_dbg(dev, "buffer %d is already queued\n", bufidx); - r = -EINVAL; - goto out; - } - - buf->state = B3DFG_BUFFER_PENDING; - list_add_tail(&buf->list, &fgdev->buffer_queue); - - if (fgdev->transmission_enabled && fgdev->triplet_ready) { - dev_dbg(dev, "triplet is ready, pushing immediately\n"); - fgdev->triplet_ready = 0; - r = setup_frame_transfer(fgdev, buf, 0); - if (r) - dev_err(dev, "unable to map DMA buffer\n"); - } - -out: - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - return r; -} - -/* non-blocking buffer poll. returns 1 if data is present in the buffer, - * 0 otherwise */ -static int poll_buffer(struct b3dfg_dev *fgdev, void __user *arg) -{ - struct device *dev = &fgdev->pdev->dev; - struct b3dfg_poll p; - struct b3dfg_buffer *buf; - unsigned long flags; - int r = 1; - int arg_out = 0; - - if (copy_from_user(&p, arg, sizeof(p))) - return -EFAULT; - - if (unlikely(!fgdev->transmission_enabled)) { - dev_dbg(dev, "cannot poll, transmission disabled\n"); - return -EINVAL; - } - - if (p.buffer_idx < 0 || p.buffer_idx >= b3dfg_nbuf) - return -ENOENT; - - buf = &fgdev->buffers[p.buffer_idx]; - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - - if (likely(buf->state == B3DFG_BUFFER_POPULATED)) { - arg_out = 1; - buf->state = B3DFG_BUFFER_POLLED; - - /* IRQs already disabled by spin_lock_irqsave above. */ - spin_lock(&fgdev->triplets_dropped_lock); - p.triplets_dropped = fgdev->triplets_dropped; - fgdev->triplets_dropped = 0; - spin_unlock(&fgdev->triplets_dropped_lock); - } else { - r = 0; - } - - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - if (arg_out && copy_to_user(arg, &p, sizeof(p))) - r = -EFAULT; - - return r; -} - -static unsigned long get_cstate_change(struct b3dfg_dev *fgdev) -{ - unsigned long flags, when; - - spin_lock_irqsave(&fgdev->cstate_lock, flags); - when = fgdev->cstate_tstamp; - spin_unlock_irqrestore(&fgdev->cstate_lock, flags); - return when; -} - -static int is_event_ready(struct b3dfg_dev *fgdev, struct b3dfg_buffer *buf, - unsigned long when) -{ - int result; - unsigned long flags; - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - spin_lock(&fgdev->cstate_lock); - result = (!fgdev->transmission_enabled || - buf->state == B3DFG_BUFFER_POPULATED || - when != fgdev->cstate_tstamp); - spin_unlock(&fgdev->cstate_lock); - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - return result; -} - -/* sleep until a specific buffer becomes populated */ -static int wait_buffer(struct b3dfg_dev *fgdev, void __user *arg) -{ - struct device *dev = &fgdev->pdev->dev; - struct b3dfg_wait w; - struct b3dfg_buffer *buf; - unsigned long flags, when; - int r; - - if (copy_from_user(&w, arg, sizeof(w))) - return -EFAULT; - - if (!fgdev->transmission_enabled) { - dev_dbg(dev, "cannot wait, transmission disabled\n"); - return -EINVAL; - } - - if (w.buffer_idx < 0 || w.buffer_idx >= b3dfg_nbuf) - return -ENOENT; - - buf = &fgdev->buffers[w.buffer_idx]; - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - - if (buf->state == B3DFG_BUFFER_POPULATED) { - r = w.timeout; - goto out_triplets_dropped; - } - - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - when = get_cstate_change(fgdev); - if (w.timeout > 0) { - r = wait_event_interruptible_timeout(fgdev->buffer_waitqueue, - is_event_ready(fgdev, buf, when), - (w.timeout * HZ) / 1000); - - if (unlikely(r < 0)) - goto out; - - w.timeout = r * 1000 / HZ; - } else { - r = wait_event_interruptible(fgdev->buffer_waitqueue, - is_event_ready(fgdev, buf, when)); - - if (unlikely(r)) { - r = -ERESTARTSYS; - goto out; - } - } - - /* TODO: Inform the user via field(s) in w? */ - if (!fgdev->transmission_enabled || when != get_cstate_change(fgdev)) { - r = -EINVAL; - goto out; - } - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - - if (buf->state != B3DFG_BUFFER_POPULATED) { - r = -ETIMEDOUT; - goto out_unlock; - } - - buf->state = B3DFG_BUFFER_POLLED; - -out_triplets_dropped: - - /* IRQs already disabled by spin_lock_irqsave above. */ - spin_lock(&fgdev->triplets_dropped_lock); - w.triplets_dropped = fgdev->triplets_dropped; - fgdev->triplets_dropped = 0; - spin_unlock(&fgdev->triplets_dropped_lock); - -out_unlock: - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - if (copy_to_user(arg, &w, sizeof(w))) - r = -EFAULT; -out: - return r; -} - -/* mmap page fault handler */ -static int b3dfg_vma_fault(struct vm_area_struct *vma, - struct vm_fault *vmf) -{ - struct b3dfg_dev *fgdev = vma->vm_file->private_data; - unsigned long off = vmf->pgoff << PAGE_SHIFT; - unsigned int frame_size = fgdev->frame_size; - unsigned int buf_size = frame_size * B3DFG_FRAMES_PER_BUFFER; - unsigned char *addr; - - /* determine which buffer the offset lies within */ - unsigned int buf_idx = off / buf_size; - /* and the offset into the buffer */ - unsigned int buf_off = off % buf_size; - - /* determine which frame inside the buffer the offset lies in */ - unsigned int frm_idx = buf_off / frame_size; - /* and the offset into the frame */ - unsigned int frm_off = buf_off % frame_size; - - if (unlikely(buf_idx >= b3dfg_nbuf)) - return VM_FAULT_SIGBUS; - - addr = fgdev->buffers[buf_idx].frame[frm_idx] + frm_off; - vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, - virt_to_phys(addr) >> PAGE_SHIFT); - - return VM_FAULT_NOPAGE; -} - -static struct vm_operations_struct b3dfg_vm_ops = { - .fault = b3dfg_vma_fault, -}; - -static int get_wand_status(struct b3dfg_dev *fgdev, int __user *arg) -{ - u32 wndstat = b3dfg_read32(fgdev, B3D_REG_WAND_STS); - dev_dbg(&fgdev->pdev->dev, "wand status %x\n", wndstat); - return __put_user(wndstat & 0x1, arg); -} - -static int enable_transmission(struct b3dfg_dev *fgdev) -{ - unsigned long flags; - struct device *dev = &fgdev->pdev->dev; - - dev_dbg(dev, "enable transmission\n"); - - /* check the cable is plugged in. */ - if (!b3dfg_read32(fgdev, B3D_REG_WAND_STS)) { - dev_dbg(dev, "cannot start transmission without wand\n"); - return -EINVAL; - } - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - - /* Handle racing enable_transmission calls. */ - if (fgdev->transmission_enabled) { - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - goto out; - } - - spin_lock(&fgdev->triplets_dropped_lock); - fgdev->triplets_dropped = 0; - spin_unlock(&fgdev->triplets_dropped_lock); - - fgdev->triplet_ready = 0; - fgdev->cur_dma_frame_idx = -1; - fgdev->transmission_enabled = 1; - - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - /* Enable DMA and cable status interrupts. */ - b3dfg_write32(fgdev, B3D_REG_HW_CTRL, 0x03); - -out: - return 0; -} - -static void disable_transmission(struct b3dfg_dev *fgdev) -{ - struct device *dev = &fgdev->pdev->dev; - unsigned long flags; - u32 tmp; - - dev_dbg(dev, "disable transmission\n"); - - /* guarantee that no more interrupts will be serviced */ - spin_lock_irqsave(&fgdev->buffer_lock, flags); - fgdev->transmission_enabled = 0; - - b3dfg_write32(fgdev, B3D_REG_HW_CTRL, 0); - - /* FIXME: temporary debugging only. if the board stops transmitting, - * hitting ctrl+c and seeing this message is useful for determining - * the state of the board. */ - tmp = b3dfg_read32(fgdev, B3D_REG_DMA_STS); - dev_dbg(dev, "DMA_STS reads %x after TX stopped\n", tmp); - - dequeue_all_buffers(fgdev); - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - wake_up_interruptible(&fgdev->buffer_waitqueue); -} - -static int set_transmission(struct b3dfg_dev *fgdev, int enabled) -{ - int res = 0; - - if (enabled && !fgdev->transmission_enabled) - res = enable_transmission(fgdev); - else if (!enabled && fgdev->transmission_enabled) - disable_transmission(fgdev); - - return res; -} - -/* Called in interrupt context. */ -static void handle_cstate_unplug(struct b3dfg_dev *fgdev) -{ - /* Disable all interrupts. */ - b3dfg_write32(fgdev, B3D_REG_HW_CTRL, 0); - - /* Stop transmission. */ - spin_lock(&fgdev->buffer_lock); - fgdev->transmission_enabled = 0; - - fgdev->cur_dma_frame_idx = -1; - fgdev->triplet_ready = 0; - if (fgdev->cur_dma_frame_addr) { - pci_unmap_single(fgdev->pdev, fgdev->cur_dma_frame_addr, - fgdev->frame_size, PCI_DMA_FROMDEVICE); - fgdev->cur_dma_frame_addr = 0; - } - dequeue_all_buffers(fgdev); - spin_unlock(&fgdev->buffer_lock); -} - -/* Called in interrupt context. */ -static void handle_cstate_change(struct b3dfg_dev *fgdev) -{ - u32 cstate = b3dfg_read32(fgdev, B3D_REG_WAND_STS); - unsigned long when; - struct device *dev = &fgdev->pdev->dev; - - dev_dbg(dev, "cable state change: %u\n", cstate); - - /* - * When the wand is unplugged we reset our state. The hardware will - * have done the same internally. - * - * Note we should never see a cable *plugged* event, as interrupts - * should only be enabled when transmitting, which requires the cable - * to be plugged. If we do see one it probably means the cable has been - * unplugged and re-plugged very rapidly. Possibly because it has a - * broken wire and is momentarily losing contact. - * - * TODO: At the moment if you plug in the cable then enable transmission - * the hardware will raise a couple of spurious interrupts, so - * just ignore them for now. - * - * Once the hardware is fixed we should complain and treat it as an - * unplug. Or at least track how frequently it is happening and do - * so if too many come in. - */ - if (cstate) { - dev_warn(dev, "ignoring unexpected plug event\n"); - return; - } - handle_cstate_unplug(fgdev); - - /* - * Record cable state change timestamp & wake anyone waiting - * on a cable state change. Be paranoid about ensuring events - * are not missed if we somehow get two interrupts in a jiffy. - */ - spin_lock(&fgdev->cstate_lock); - when = jiffies_64; - if (when <= fgdev->cstate_tstamp) - when = fgdev->cstate_tstamp + 1; - fgdev->cstate_tstamp = when; - wake_up_interruptible(&fgdev->buffer_waitqueue); - spin_unlock(&fgdev->cstate_lock); -} - -/* Called with buffer_lock held. */ -static void transfer_complete(struct b3dfg_dev *fgdev) -{ - struct b3dfg_buffer *buf; - struct device *dev = &fgdev->pdev->dev; - - pci_unmap_single(fgdev->pdev, fgdev->cur_dma_frame_addr, - fgdev->frame_size, PCI_DMA_FROMDEVICE); - fgdev->cur_dma_frame_addr = 0; - - buf = list_entry(fgdev->buffer_queue.next, struct b3dfg_buffer, list); - - dev_dbg(dev, "handle frame completion\n"); - if (fgdev->cur_dma_frame_idx == B3DFG_FRAMES_PER_BUFFER - 1) { - - /* last frame of that triplet completed */ - dev_dbg(dev, "triplet completed\n"); - buf->state = B3DFG_BUFFER_POPULATED; - list_del_init(&buf->list); - wake_up_interruptible(&fgdev->buffer_waitqueue); - } -} - -/* - * Called with buffer_lock held. - * - * Note that idx is the (1-based) *next* frame to be transferred, while - * cur_dma_frame_idx is the (0-based) *last* frame to have been transferred (or - * -1 if none). Thus there should be a difference of 2 between them. - */ -static bool setup_next_frame_transfer(struct b3dfg_dev *fgdev, int idx) -{ - struct b3dfg_buffer *buf; - struct device *dev = &fgdev->pdev->dev; - bool need_ack = 1; - - dev_dbg(dev, "program DMA transfer for next frame: %d\n", idx); - - buf = list_entry(fgdev->buffer_queue.next, struct b3dfg_buffer, list); - if (idx == fgdev->cur_dma_frame_idx + 2) { - if (setup_frame_transfer(fgdev, buf, idx - 1)) - dev_err(dev, "unable to map DMA buffer\n"); - need_ack = 0; - } else { - dev_err(dev, "frame mismatch, got %d, expected %d\n", - idx, fgdev->cur_dma_frame_idx + 2); - - /* FIXME: handle dropped triplets here */ - } - - return need_ack; -} - -static irqreturn_t b3dfg_intr(int irq, void *dev_id) -{ - struct b3dfg_dev *fgdev = dev_id; - struct device *dev = &fgdev->pdev->dev; - u32 sts; - u8 dropped; - bool need_ack = 1; - irqreturn_t res = IRQ_HANDLED; - - sts = b3dfg_read32(fgdev, B3D_REG_DMA_STS); - if (unlikely(sts == 0)) { - dev_warn(dev, "ignore interrupt, DMA status is 0\n"); - res = IRQ_NONE; - goto out; - } - - if (unlikely(!fgdev->transmission_enabled)) { - dev_warn(dev, "ignore interrupt, TX disabled\n"); - res = IRQ_HANDLED; - goto out; - } - - /* Handle dropped frames, as reported by the hardware. */ - dropped = (sts >> 8) & 0xff; - dev_dbg(dev, "intr: DMA_STS=%08x (drop=%d comp=%d next=%d)\n", - sts, dropped, !!(sts & 0x4), sts & 0x3); - if (unlikely(dropped > 0)) { - spin_lock(&fgdev->triplets_dropped_lock); - fgdev->triplets_dropped += dropped; - spin_unlock(&fgdev->triplets_dropped_lock); - } - - /* Handle a cable state change (i.e. the wand being unplugged). */ - if (sts & 0x08) { - handle_cstate_change(fgdev); - goto out; - } - - spin_lock(&fgdev->buffer_lock); - if (unlikely(list_empty(&fgdev->buffer_queue))) { - - /* FIXME need more sanity checking here */ - dev_info(dev, "buffer not ready for next transfer\n"); - fgdev->triplet_ready = 1; - goto out_unlock; - } - - /* Has a frame transfer been completed? */ - if (sts & 0x4) { - u32 dma_status = b3dfg_read32(fgdev, B3D_REG_EC220_DMA_STS); - - /* Check for DMA errors reported by the hardware. */ - if (unlikely(dma_status & 0x1)) { - dev_err(dev, "EC220 error: %08x\n", dma_status); - - /* FIXME flesh out error handling */ - goto out_unlock; - } - - /* Sanity check, we should have a frame index at this point. */ - if (unlikely(fgdev->cur_dma_frame_idx == -1)) { - dev_err(dev, "completed but no last idx?\n"); - - /* FIXME flesh out error handling */ - goto out_unlock; - } - - transfer_complete(fgdev); - } - - /* Is there another frame transfer pending? */ - if (sts & 0x3) - need_ack = setup_next_frame_transfer(fgdev, sts & 0x3); - else - fgdev->cur_dma_frame_idx = -1; - -out_unlock: - spin_unlock(&fgdev->buffer_lock); -out: - if (need_ack) { - dev_dbg(dev, "acknowledging interrupt\n"); - b3dfg_write32(fgdev, B3D_REG_EC220_DMA_STS, 0x0b); - } - return res; -} - -static int b3dfg_open(struct inode *inode, struct file *filp) -{ - struct b3dfg_dev *fgdev = - container_of(inode->i_cdev, struct b3dfg_dev, chardev); - - dev_dbg(&fgdev->pdev->dev, "open\n"); - filp->private_data = fgdev; - return 0; -} - -static int b3dfg_release(struct inode *inode, struct file *filp) -{ - struct b3dfg_dev *fgdev = filp->private_data; - dev_dbg(&fgdev->pdev->dev, "release\n"); - disable_transmission(fgdev); - return 0; -} - -static long b3dfg_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - struct b3dfg_dev *fgdev = filp->private_data; - - switch (cmd) { - case B3DFG_IOCGFRMSZ: - return __put_user(fgdev->frame_size, (int __user *) arg); - case B3DFG_IOCGWANDSTAT: - return get_wand_status(fgdev, (int __user *) arg); - case B3DFG_IOCTTRANS: - return set_transmission(fgdev, (int) arg); - case B3DFG_IOCTQUEUEBUF: - return queue_buffer(fgdev, (int) arg); - case B3DFG_IOCTPOLLBUF: - return poll_buffer(fgdev, (void __user *) arg); - case B3DFG_IOCTWAITBUF: - return wait_buffer(fgdev, (void __user *) arg); - default: - dev_dbg(&fgdev->pdev->dev, "unrecognised ioctl %x\n", cmd); - return -EINVAL; - } -} - -static unsigned int b3dfg_poll(struct file *filp, poll_table *poll_table) -{ - struct b3dfg_dev *fgdev = filp->private_data; - unsigned long flags, when; - int i; - int r = 0; - - when = get_cstate_change(fgdev); - poll_wait(filp, &fgdev->buffer_waitqueue, poll_table); - - spin_lock_irqsave(&fgdev->buffer_lock, flags); - for (i = 0; i < b3dfg_nbuf; i++) { - if (fgdev->buffers[i].state == B3DFG_BUFFER_POPULATED) { - r = POLLIN | POLLRDNORM; - break; - } - } - spin_unlock_irqrestore(&fgdev->buffer_lock, flags); - - /* TODO: Confirm this is how we want to communicate the change. */ - if (!fgdev->transmission_enabled || when != get_cstate_change(fgdev)) - r = POLLERR; - - return r; -} - -static int b3dfg_mmap(struct file *filp, struct vm_area_struct *vma) -{ - struct b3dfg_dev *fgdev = filp->private_data; - unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; - unsigned long vsize = vma->vm_end - vma->vm_start; - unsigned long bufdatalen = b3dfg_nbuf * fgdev->frame_size * 3; - unsigned long psize = bufdatalen - offset; - int r = 0; - - if (vsize <= psize) { - vma->vm_flags |= VM_IO | VM_RESERVED | VM_CAN_NONLINEAR | - VM_PFNMAP; - vma->vm_ops = &b3dfg_vm_ops; - } else { - r = -EINVAL; - } - - return r; -} - -static struct file_operations b3dfg_fops = { - .owner = THIS_MODULE, - .open = b3dfg_open, - .release = b3dfg_release, - .unlocked_ioctl = b3dfg_ioctl, - .poll = b3dfg_poll, - .mmap = b3dfg_mmap, -}; - -static void free_all_frame_buffers(struct b3dfg_dev *fgdev) -{ - int i, j; - for (i = 0; i < b3dfg_nbuf; i++) - for (j = 0; j < B3DFG_FRAMES_PER_BUFFER; j++) - kfree(fgdev->buffers[i].frame[j]); - kfree(fgdev->buffers); -} - -/* initialize device and any data structures. called before any interrupts - * are enabled. */ -static int b3dfg_init_dev(struct b3dfg_dev *fgdev) -{ - int i, j; - u32 frm_size = b3dfg_read32(fgdev, B3D_REG_FRM_SIZE); - - /* Disable interrupts. In abnormal circumstances (e.g. after a crash) - * the board may still be transmitting from the previous session. If we - * ensure that interrupts are disabled before we later enable them, we - * are sure to capture a triplet from the start, rather than starting - * from frame 2 or 3. Disabling interrupts causes the FG to throw away - * all buffered data and stop buffering more until interrupts are - * enabled again. - */ - b3dfg_write32(fgdev, B3D_REG_HW_CTRL, 0); - - fgdev->frame_size = frm_size * 4096; - fgdev->buffers = kzalloc(sizeof(struct b3dfg_buffer) * b3dfg_nbuf, - GFP_KERNEL); - if (!fgdev->buffers) - goto err_no_buf; - for (i = 0; i < b3dfg_nbuf; i++) { - struct b3dfg_buffer *buf = &fgdev->buffers[i]; - for (j = 0; j < B3DFG_FRAMES_PER_BUFFER; j++) { - buf->frame[j] = kmalloc(fgdev->frame_size, GFP_KERNEL); - if (!buf->frame[j]) - goto err_no_mem; - } - INIT_LIST_HEAD(&buf->list); - } - - INIT_LIST_HEAD(&fgdev->buffer_queue); - init_waitqueue_head(&fgdev->buffer_waitqueue); - spin_lock_init(&fgdev->buffer_lock); - spin_lock_init(&fgdev->cstate_lock); - spin_lock_init(&fgdev->triplets_dropped_lock); - return 0; - -err_no_mem: - free_all_frame_buffers(fgdev); -err_no_buf: - return -ENOMEM; -} - -/* find next free minor number, returns -1 if none are availabile */ -static int get_free_minor(void) -{ - int i; - for (i = 0; i < B3DFG_MAX_DEVS; i++) { - if (b3dfg_devices[i] == 0) - return i; - } - return -1; -} - -static int __devinit b3dfg_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - struct b3dfg_dev *fgdev = kzalloc(sizeof(*fgdev), GFP_KERNEL); - int r = 0; - int minor = get_free_minor(); - dev_t devno = MKDEV(MAJOR(b3dfg_devt), minor); - unsigned long res_len; - resource_size_t res_base; - - if (fgdev == NULL) - return -ENOMEM; - - if (minor < 0) { - dev_err(&pdev->dev, "too many devices found!\n"); - r = -EIO; - goto err_free; - } - - b3dfg_devices[minor] = 1; - dev_info(&pdev->dev, "probe device with IRQ %d\n", pdev->irq); - - cdev_init(&fgdev->chardev, &b3dfg_fops); - fgdev->chardev.owner = THIS_MODULE; - - r = cdev_add(&fgdev->chardev, devno, 1); - if (r) { - dev_err(&pdev->dev, "cannot add char device\n"); - goto err_release_minor; - } - - fgdev->dev = device_create( - b3dfg_class, - &pdev->dev, - devno, - dev_get_drvdata(&pdev->dev), - DRIVER_NAME "%d", minor); - - if (IS_ERR(fgdev->dev)) { - dev_err(&pdev->dev, "cannot create device\n"); - r = PTR_ERR(fgdev->dev); - goto err_del_cdev; - } - - r = pci_enable_device(pdev); - if (r) { - dev_err(&pdev->dev, "cannot enable PCI device\n"); - goto err_dev_unreg; - } - - res_len = pci_resource_len(pdev, B3DFG_BAR_REGS); - if (res_len != B3DFG_REGS_LENGTH) { - dev_err(&pdev->dev, "invalid register resource size\n"); - r = -EIO; - goto err_disable; - } - - if (pci_resource_flags(pdev, B3DFG_BAR_REGS) - != (IORESOURCE_MEM | IORESOURCE_SIZEALIGN)) { - dev_err(&pdev->dev, "invalid resource flags\n"); - r = -EIO; - goto err_disable; - } - r = pci_request_regions(pdev, DRIVER_NAME); - if (r) { - dev_err(&pdev->dev, "cannot obtain PCI resources\n"); - goto err_disable; - } - - pci_set_master(pdev); - - r = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); - if (r) { - dev_err(&pdev->dev, "no usable DMA configuration\n"); - goto err_free_res; - } - - res_base = pci_resource_start(pdev, B3DFG_BAR_REGS); - fgdev->regs = ioremap_nocache(res_base, res_len); - if (!fgdev->regs) { - dev_err(&pdev->dev, "regs ioremap failed\n"); - r = -EIO; - goto err_free_res; - } - - fgdev->pdev = pdev; - pci_set_drvdata(pdev, fgdev); - r = b3dfg_init_dev(fgdev); - if (r < 0) { - dev_err(&pdev->dev, "failed to initalize device\n"); - goto err_unmap; - } - - r = request_irq(pdev->irq, b3dfg_intr, IRQF_SHARED, DRIVER_NAME, fgdev); - if (r) { - dev_err(&pdev->dev, "couldn't request irq %d\n", pdev->irq); - goto err_free_bufs; - } - - return 0; - -err_free_bufs: - free_all_frame_buffers(fgdev); -err_unmap: - iounmap(fgdev->regs); -err_free_res: - pci_release_regions(pdev); -err_disable: - pci_disable_device(pdev); -err_dev_unreg: - device_destroy(b3dfg_class, devno); -err_del_cdev: - cdev_del(&fgdev->chardev); -err_release_minor: - b3dfg_devices[minor] = 0; -err_free: - kfree(fgdev); - return r; -} - -static void __devexit b3dfg_remove(struct pci_dev *pdev) -{ - struct b3dfg_dev *fgdev = pci_get_drvdata(pdev); - unsigned int minor = MINOR(fgdev->chardev.dev); - - dev_dbg(&pdev->dev, "remove\n"); - - free_irq(pdev->irq, fgdev); - iounmap(fgdev->regs); - pci_release_regions(pdev); - pci_disable_device(pdev); - device_destroy(b3dfg_class, MKDEV(MAJOR(b3dfg_devt), minor)); - cdev_del(&fgdev->chardev); - free_all_frame_buffers(fgdev); - kfree(fgdev); - b3dfg_devices[minor] = 0; -} - -static struct pci_driver b3dfg_driver = { - .name = DRIVER_NAME, - .id_table = b3dfg_ids, - .probe = b3dfg_probe, - .remove = __devexit_p(b3dfg_remove), -}; - -static int __init b3dfg_module_init(void) -{ - int r; - - if (b3dfg_nbuf < 2) { - printk(KERN_ERR DRIVER_NAME - ": buffer_count is out of range (must be >= 2)"); - return -EINVAL; - } - - printk(KERN_INFO DRIVER_NAME ": loaded\n"); - - b3dfg_class = class_create(THIS_MODULE, DRIVER_NAME); - if (IS_ERR(b3dfg_class)) - return PTR_ERR(b3dfg_class); - - r = alloc_chrdev_region(&b3dfg_devt, 0, B3DFG_MAX_DEVS, DRIVER_NAME); - if (r) - goto err1; - - r = pci_register_driver(&b3dfg_driver); - if (r) - goto err2; - - return r; - -err2: - unregister_chrdev_region(b3dfg_devt, B3DFG_MAX_DEVS); -err1: - class_destroy(b3dfg_class); - return r; -} - -static void __exit b3dfg_module_exit(void) -{ - printk(KERN_INFO DRIVER_NAME ": unloaded\n"); - pci_unregister_driver(&b3dfg_driver); - unregister_chrdev_region(b3dfg_devt, B3DFG_MAX_DEVS); - class_destroy(b3dfg_class); -} - -module_init(b3dfg_module_init); -module_exit(b3dfg_module_exit); |