/* * Brontes PCI frame grabber driver * * Copyright (C) 2008 3M Company * Contact: Justin Bronder * Original Authors: Daniel Drake * Duane Griffin * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 "); 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) { u16 command; 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; } /* * Check we're a bus master. * TODO: I think we can remove this having added the pci_set_master call */ pci_read_config_word(fgdev->pdev, PCI_COMMAND, &command); if (!(command & PCI_COMMAND_MASTER)) { dev_err(dev, "not a bus master, force-enabling\n"); pci_write_config_word(fgdev->pdev, PCI_COMMAND, command | PCI_COMMAND_MASTER); } 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);