| /* |
| * 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) |
| { |
| 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); |