#include #include #include #include #include #include #include #include #include "hrt.h" #include "hrt_probe.h" #include "hrt_i2c.h" /* Controls what the user can set via V4L ioctls */ #ifdef HAVE_V4L2 static const struct v4l2_queryctrl hrt_ctls[] = { { .id = V4L2_CID_BRIGHTNESS, .name = "Brightness", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0x9b, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_CONTRAST, .name = "Contrast", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0x5e, .type = V4L2_CTRL_TYPE_INTEGER, } }; #endif /* ************************************************************** */ /* ************************************************************** */ /* SUB FRAME VARIABLES */ /* ************************************************************** */ /* ************************************************************** */ int SUB_FRAME_TOGGLE = 0; #define SUB_FRAME_UPPER_LEFT 1 #define SUB_FRAME_UPPER_RIGHT 2 #define SUB_FRAME_LOWER_LEFT 3 #define SUB_FRAME_LOWER_RIGHT 4 int SUB_FRAME_USER_CHOICE = 1; /* ************************************************************** */ /* ************************************************************** */ /* MINIMUM FUNCTION */ /* ************************************************************** */ /* ************************************************************** */ #ifndef min # define min(a,b) ((a)<(b) ? (a) : (b)) #endif /* ************************************************************** */ /* ************************************************************** */ /* struct hrt_per_file */ /* contains data to be stored per file handle */ /* ************************************************************** */ /* ************************************************************** */ struct hrt_per_file { struct subwindow win; struct hrt *hrtdev; }; /* ************************************************************** */ /* ************************************************************** */ /* MULTI-CARD SUPPORT */ /* ************************************************************** */ /* ************************************************************** */ #define HRT_MAX_DEVS 2 static struct hrt hrtdevs[HRT_MAX_DEVS]; static int num_hrtdevs = 0; /* ************************************************************** */ /* ************************************************************** */ /* PARAMETERS */ /* ************************************************************** */ /* ************************************************************** */ /* Flag to tell whether the kernel timer is running */ volatile static int timer_running = 0; /* The kernel timer */ static struct timer_list timer; #ifdef HAVE_V4L2 /* Minor number, or -1 for first free */ static int video_minor = -1; /* Don't put V4L2_CAP_STREAMING in capabilities; recommended for xawtv */ static int disable_streaming = 0; /* Only one user can open /dev/videoN at at time */ static unsigned int exclusive = 0; #endif /* Debugging output */ static unsigned int debug = 1; /* Major number for hrtmem device */ static unsigned int hrtmem_major = 0; /* Disable the hrtmem device */ static unsigned int disable_hrtmem = 0; MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Enable debugging output"); MODULE_PARM(hrtmem_major, "i"); MODULE_PARM_DESC(hrtmem_major, "Major number for hrtmem device (default: dynamic)"); MODULE_PARM(disable_hrtmem, "i"); MODULE_PARM_DESC(disable_hrtmem, "Disable hrtmem device (default: off)"); MODULE_DESCRIPTION("Driver for HRT Pixelsmart 512-8-PCI"); MODULE_LICENSE("GPL"); /* ************************************************************** */ /* ************************************************************** */ /* FUNCTIONS PROTOTYPES */ /* ************************************************************** */ /* ************************************************************** */ void inline grab_field(struct hrt *hrtdev, unsigned char *framedata, int parity); void inline grab_subfield(struct hrt *hrtdev, unsigned char *framedata, int parity, int which_sub_frame); void timer_func(unsigned long ptr); struct hrt_per_file *per_file_init(struct hrt *hrtdev); #ifdef HAVE_V4L2 int mmap_request_buffers(struct hrt *dev, struct v4l2_requestbuffers *req); int hrt_streamon(struct hrt *hrtdev, __u32 type); void hrt_streamoff(struct hrt *hrtdev, __u32 type); int hrt_dequeuebuffer(struct hrt *hrtdev, struct v4l2_buffer *buf); int hrt_queuebuffer(struct hrt *hrtdev, struct v4l2_buffer *buf); /* ************************************************************** */ /* ************************************************************** */ /* V4L2 FILE OPERATION PROTOTYPES */ /* ************************************************************** */ /* ************************************************************** */ /* Prototypes for file_operations */ static int hrt_v4l2_open(struct inode *inode, struct file *file); static int hrt_v4l2_release(struct inode *inode, struct file *file); static int hrt_v4l2_mmap(struct file *file, struct vm_area_struct *vma); static int hrt_v4l2_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static unsigned int hrt_v4l2_poll(struct file *file, poll_table * wait); static inline void streaming_fieldchange(struct hrt *hrtdev); #endif #ifndef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* FILE OPERATION PROTOTYPES */ /* ************************************************************** */ /* ************************************************************** */ /* Prototypes for direct mmap device */ static int hrt_open(struct inode *inode, struct file *file); static int hrt_release(struct inode *inode, struct file *file); static int hrt_mmap(struct file *file, struct vm_area_struct *vma); static int hrt_do_ioctl(struct hrt *hrtdev, unsigned int cmd, unsigned long arg); static int hrt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); #endif /* hrt_read is used for both devices */ static int hrt_read(struct file *file, char *buf, size_t count, loff_t * ppos); /* ************************************************************** */ /* ************************************************************** */ /* FILE OPERATIONS */ /* ************************************************************** */ /* ************************************************************** */ static struct file_operations hrt_fops = { #ifdef HAVE_V4L2 .owner = THIS_MODULE, .open = hrt_v4l2_open, .mmap = hrt_v4l2_mmap, .release = hrt_v4l2_release, .ioctl = hrt_v4l2_ioctl, .read = hrt_read, .poll = hrt_v4l2_poll, #else .mmap = hrt_mmap, .open = hrt_open, .release = hrt_release, .ioctl = hrt_ioctl, .read = hrt_read, #endif }; #ifdef HAVE_V4L2 /* XXX: Get an actual official number from kraxel@bytesex.org (see /usr/src/linux/include/linux/videodev.h) */ #define VID_HARDWARE_HRT 36 static struct video_device videodev_template = { .owner = THIS_MODULE, .name = "HRT Pixelsmart (PS512-8-PCI)", .type = HRT_VID_TYPE, .hardware = VID_HARDWARE_HRT, .fops = &hrt_fops, }; /* ************************************************************** */ /* ************************************************************** */ /* QUEUE MANIPULATION */ /* ************************************************************** */ /* ************************************************************** */ /** * Queue manipulation functions (basically wrappers for the Linux * linked list functions) */ void queue_add_tail(struct stream_buffer *streambuf, struct stream_buffer_queue *q) { unsigned long flags; if (streambuf == NULL || q == NULL) return; write_lock_irqsave(&q->lock, flags); list_add_tail(&streambuf->node, &q->head); q->length++; write_unlock_irqrestore(&q->lock, flags); } void queue_del(struct stream_buffer *streambuf, struct stream_buffer_queue *q) { unsigned long flags; if (q == NULL) return; write_lock_irqsave(&q->lock, flags); list_del(&streambuf->node); q->length--; write_unlock_irqrestore(&q->lock, flags); } struct stream_buffer *queue_peek_head(struct stream_buffer_queue *q) { unsigned long flags; struct stream_buffer *streambuf; if (q == NULL) return NULL; read_lock_irqsave(&q->lock, flags); streambuf = list_entry(q->head.next, struct stream_buffer, node); read_unlock_irqrestore(&q->lock, flags); return streambuf; } struct stream_buffer *queue_del_head(struct stream_buffer_queue *q) { unsigned long flags; struct stream_buffer *streambuf; if (q == NULL) return NULL; if (!q->length) return NULL; read_lock_irqsave(&q->lock, flags); streambuf = list_entry(q->head.next, struct stream_buffer, node); list_del(&streambuf->node); q->length--; read_unlock_irqrestore(&q->lock, flags); return streambuf; } #endif /* ************************************************************** */ /* ************************************************************** */ /* IRQ OF THE DEVICE */ /* ************************************************************** */ /* ************************************************************** */ int hrt_irq; /* ************************************************************** */ /* ************************************************************** */ /* START FUNCTIONS */ /* ************************************************************** */ /* ************************************************************** */ /* ************************************************************** */ /* ************************************************************** */ /* HRT TRY ADDRESS */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_try_address - return 1 if there is a device * at the specified address; else return 0; * If there is a device, map the device memory into the kernel * virtual memory space. */ int hrt_try_address(struct hrt *hrtdev, unsigned long address) { unsigned long virtual_addr; int result = 0; /* Reserve our memory range */ if (!request_mem_region(address, BYTES_NEEDED, "hrt")) { hrt_printk("I/O memory already in use\n"); return 0; } virtual_addr = (unsigned long) ioremap_nocache(address, BYTES_NEEDED); /* Check that there is really a device at this address */ if (hrt_probe(virtual_addr)) { hrtdev->physaddr = address; hrtdev->virtaddr = virtual_addr; /* Try to initialize the A/D converter */ result = i2c_init(hrtdev, saa7110_default_init_regs); if (result < 0){ iounmap((void *)virtual_addr); release_mem_region(address, BYTES_NEEDED); return 0; } } else { /* Release memory and unmap */ iounmap((void *)virtual_addr); release_mem_region(address, BYTES_NEEDED); return 0; } return 1; } /** * kvirt_to_pa - converts an address from vmalloc() to a struct page * From v4l2cap.c. */ #ifdef HAVE_V4L2 struct page *kvirt_to_pa(unsigned long adr) { struct page *ret = NULL; pmd_t *pmd; pte_t *pte; pgd_t *pgd; pgd = pgd_offset_k(adr); if (!pgd_none(*pgd)) { pmd = pmd_offset(pgd, adr); if (!pmd_none(*pmd)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) pte = pte_offset(pmd, adr); #else pte = pte_offset_kernel(pmd, adr); #endif if (pte_present(*pte)) { ret = pte_page(*pte); } } } return ret; } #endif /* ************************************************************** */ /* ************************************************************** */ /* HRT READ */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_read - depends on timer_func to change the field_bit. * Reads at most about 23 FPS on this 400MHz machine without displaying, * and it reads at about 13 FPS with displaying (using hrtdemo). * NB that hrt_read is used on both hrtmem and /dev/videoN. */ static int hrt_read(struct file *file, char *buf, size_t count, loff_t *ppos) { struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; int len; int old_field; hrtdev->win = &per_file->win; len = min((size_t) (hrtdev->win->height * hrtdev->bytesperline), count); down_interruptible(&hrtdev->sem); old_field = hrtdev->field_bit; /* Freeze Frame! */ writeb(HRT_FREEZE_IMM_CMD, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); /* Grab a field */ if (SUB_FRAME_TOGGLE == 1) { grab_subfield(hrtdev, hrtdev->framedata, old_field, SUB_FRAME_USER_CHOICE); } else { grab_field(hrtdev, hrtdev->framedata, old_field); } if(hrtdev->typeofcard == TYPE_OF_CARD_IS_GRAY) { /* UnFreeze Frame! */ writeb(HRT_LIVE_CMD, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); } if (copy_to_user(buf, hrtdev->framedata, len)) { up(&hrtdev->sem); return -EFAULT; } if(hrtdev->typeofcard == TYPE_OF_CARD_IS_GRAY) { /* Go to sleep until the field bit changes */ while (old_field == hrtdev->field_bit) { up(&hrtdev->sem); if (file->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible (hrtdev->waitqueue, (old_field != hrtdev->field_bit))) return -ERESTARTSYS; if (down_interruptible(&hrtdev->sem)) return -ERESTARTSYS; } /* Freeze Frame! */ writeb(HRT_FREEZE_IMM_CMD, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); } /* Grab the other field */ if (SUB_FRAME_TOGGLE == 1) { grab_subfield(hrtdev, hrtdev->framedata, old_field, SUB_FRAME_USER_CHOICE); } else { grab_field(hrtdev, hrtdev->framedata, hrtdev->field_bit); } /* UnFreeze Frame! */ writeb(HRT_LIVE_CMD, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); if (copy_to_user(buf, hrtdev->framedata, len)) { up(&hrtdev->sem); return -EFAULT; } up(&hrtdev->sem); return len; } /* ************************************************************** */ /* ************************************************************** */ /* PER FILE INIT */ /* ************************************************************** */ /* ************************************************************** */ /** * per_file_init - allocates and initializes per-file data, which * is currently just the ROI (region-of-interest) subwindow * and a pointer back to this hrtdev */ struct hrt_per_file *per_file_init(struct hrt *hrtdev) { struct hrt_per_file *per_file = vmalloc(sizeof(*per_file)); per_file->hrtdev = hrtdev; per_file->win.height = HRT_HEIGHT; if(hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) per_file->win.width = HRT_WIDTH_COLOR; else per_file->win.width = HRT_WIDTH_GRAY; per_file->win.startx = 0; per_file->win.starty = 0; return per_file; } /* ************************************************************** */ /* ************************************************************** */ /* HRT DEV INIT */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_dev_init - try to initialize one PCI device, specified * by the parameter dev */ static int hrt_dev_init(struct pci_dev *dev) { int i; struct hrt *hrtdev = NULL; /* check that the device is one of those we recognize */ if ((dev->device != HRT_DEVICE_ID_COLOR) && (dev->device != HRT_DEVICE_ID_GRAY)) { hrt_printk("Unknown HRT device: %d\n", dev->device); return 0; } /* try to find a free device descriptor */ for (i = 0; i < HRT_MAX_DEVS; i++) { if (hrtdevs[i].pcidev == NULL) { hrtdev = &hrtdevs[i]; num_hrtdevs++; break; } } hrt_printk("Found %d HRT device(s)\n", num_hrtdevs); if (!hrtdev) { hrt_printk("More than %d HRT devices!\n", HRT_MAX_DEVS); return 0; } /* probe the device to get the base address */ pci_enable_device(dev); if (hrt_try_address(hrtdev, pci_resource_start(dev, 0))) goto success; for (i = 0; i < ARRAY_SIZE(hrt_addresses); i++) if (hrt_try_address(hrtdev, hrt_addresses[i])) goto success; /* Failure; couldn't find it */ return 0; success: /* Success! We found the card, so now initialize the data structures */ #ifdef HAVE_V4L2 /* We don't start in streaming mode */ hrtdev->streaming = 0; /* Make the lists empty */ INIT_LIST_HEAD(&hrtdev->capture_list.head); INIT_LIST_HEAD(&hrtdev->done_list.head); hrtdev->capture_list.length = hrtdev->done_list.length = 0; /* Initialize the spinlocks to protect the lists */ hrtdev->capture_list.lock = RW_LOCK_UNLOCKED; hrtdev->done_list.lock = RW_LOCK_UNLOCKED; /* Register with V4L */ hrtdev->video_dev = videodev_template; if (video_register_device(&hrtdev->video_dev, VFL_TYPE_GRABBER, video_minor) < 0) { hrt_printk(KERN_ERR "Unable to register video device\n"); return -ENODEV; } /* Set private data (to access through struct file) */ hrtdev->video_dev.priv = hrtdev; #endif /* Set the constants */ if(dev->device == HRT_DEVICE_ID_COLOR) { hrtdev->bytesperline = HRT_BYTES_PER_LINE_COLOR; hrtdev->framesize = HRT_FRAMESIZE_COLOR; hrtdev->typeofcard = TYPE_OF_CARD_IS_COLOR; } else { hrtdev->bytesperline = HRT_BYTES_PER_LINE_GRAY; hrtdev->framesize = HRT_FRAMESIZE_GRAY; hrtdev->typeofcard = TYPE_OF_CARD_IS_GRAY; } /* Copy the PCI device struct */ hrtdev->pcidev = dev; /* Initialize the wait queue (for poll) */ init_waitqueue_head(&hrtdev->waitqueue); /* Initialize semaphore */ sema_init(&hrtdev->sem, 1); /* Read the field id bit (should be 0 since we're shouldn't be in live mode yet) */ hrtdev->field_bit = readb(hrtdev->virtaddr + HRT_CONTROL_REG) & 1; /* Put the card into Live mode */ writeb(HRT_LIVE_CMD, hrtdev->virtaddr + HRT_CONTROL_REG); /* Allocate frame buffer memory */ if(dev->device == HRT_DEVICE_ID_COLOR) hrtdev->framedata = (unsigned char *) vmalloc(HRT_FRAMESIZE_COLOR); else hrtdev->framedata = (unsigned char *) vmalloc(HRT_FRAMESIZE_GRAY); if (hrtdev->framedata == NULL) { hrt_printk(KERN_ERR "Unable to allocate memory\n"); #ifdef HAVE_V4L2 video_unregister_device(&hrtdev->video_dev); #endif if (!disable_hrtmem) unregister_chrdev(hrtmem_major, "hrtmem"); return -ENOMEM; } /* Set up our timer routine. It should expire 100 times a second (every 1 jiffy on 2.4, every 10 jiffies on 2.6) */ init_timer(&timer); timer.function = timer_func; timer.expires = jiffies + (HZ/100); return 1; } /* ************************************************************** */ /* ************************************************************** */ /* HRT DEV CLEANUP */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_dev_cleanup - deallocate per-card resources */ void hrt_dev_cleanup(struct hrt *hrtdev) { if (hrtdev->pcidev) { #ifdef HAVE_V4L2 video_unregister_device(&hrtdev->video_dev); #endif release_mem_region(hrtdev->physaddr, BYTES_NEEDED); iounmap((unsigned char *)hrtdev->virtaddr); vfree(hrtdev->framedata); if (timer_running) del_timer_sync(&timer); } } /* ************************************************************** */ /* ************************************************************** */ /* INIT MODULE */ /* ************************************************************** */ /* ************************************************************** */ /** * init_module - where it all starts. Mainly performs probing. */ int init_module(void) { int result; struct pci_dev *dev = NULL; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) /* This is a PCI card, after all */ if (!pci_present()) return -ENODEV; #endif /* Look for the card */ dev = pci_find_device(HRT_VENDOR_ID, PCI_ANY_ID, dev); if (dev) { if (!hrt_dev_init(dev)) { hrt_printk("Failed to initialize card\n"); return -ENODEV; } #ifdef HRT_HAS_INTERRUPTS hrt_irq = dev->irq; /* store the IRQ of the device */ #endif } else { /* It should always show up, regardless of jumper settings */ hrt_printk("PCI device not found\n"); return -ENODEV; } /* We found at least one card, so register our direct-to-device-memory character device once for all cards */ if (!disable_hrtmem) { result = register_chrdev(hrtmem_major, "hrtmem", &hrt_fops); if (result < 0) { int i; hrt_printk("Unable to register hrtmem device\n"); /* Free up all the devices */ for (i = 0; hrtdevs[i].pcidev; i++) { hrt_dev_cleanup(&hrtdevs[i]); } return result; } if (hrtmem_major == 0) hrtmem_major = result; /* XXX: Put in support for devfs! And sysfs too possibly? */ hrt_printk("Registered hrtmem with major number = %d\n", hrtmem_major); } return 0; } /* ************************************************************** */ /* ************************************************************** */ /* HRT DO IOCTL */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_do_ioctl - handle ioctl's on hrtmem device */ static int hrt_do_ioctl(struct hrt *hrtdev, unsigned int cmd, unsigned long arg) { switch (cmd) { case IOC_HRT_SET_I2CREG: { struct i2c_regval r; if (copy_from_user(&r, (void *) arg, sizeof(r))) { return -EFAULT; } i2c_set_reg(hrtdev, r.reg, r.val); return 0; } case IOC_HRT_GET_I2CREG: { struct i2c_regval r; r.val = hrtdev->regvals[r.reg]; if (copy_to_user((void *) arg, &r, sizeof(r))) { return -EFAULT; } return 0; } case IOC_HRT_SET_WIDTH: { if(hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { if (arg + hrtdev->win->startx > HRT_WIDTH_COLOR || arg < 1) return -EINVAL; } else { if (arg + hrtdev->win->startx > HRT_WIDTH_GRAY || arg < 1) return -EINVAL; } hrtdev->win->width = arg; if (hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) hrtdev->bytesperline = arg * HRT_BYTES_PER_PIXEL_COLOR; else hrtdev->bytesperline = arg * HRT_BYTES_PER_PIXEL_GRAY; return 0; } case IOC_HRT_SET_HEIGHT: { if (arg + hrtdev->win->starty > HRT_HEIGHT || arg < 1) return -EINVAL; hrtdev->win->height = arg; return 0; } case IOC_HRT_SET_STARTX: { if (arg > hrtdev->win->width || arg < 0) return -EINVAL; hrtdev->win->startx = arg; return 0; } case IOC_HRT_SET_STARTY: { if (arg > hrtdev->win->height || arg < 0) return -EINVAL; hrtdev->win->starty = arg; return 0; } case IOC_HRT_SET_ROI: { struct subwindow win; if (copy_from_user(&win, (void *) arg, sizeof(win))) { return -EFAULT; } if (win.height > HRT_HEIGHT || win.height < 0) return -EINVAL; if (hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { if (win.width > HRT_WIDTH_COLOR || win.width < 0) return -EINVAL; } else { if (win.width > HRT_WIDTH_GRAY || win.width < 0) return -EINVAL; } if (win.startx > win.width || win.startx < 0) return -EINVAL; if (win.starty > win.height || win.starty < 0) return -EINVAL; *hrtdev->win = win; return 0; } case IOC_HRT_GET_ROI: { if (copy_to_user((void *) arg, hrtdev->win, sizeof(*hrtdev->win))) { return -EFAULT; } return 0; } case IOC_HRT_SET_I2CREGS: { unsigned char len; int retval; unsigned char *buf; /* Stop the timer first */ del_timer_sync(&timer); timer_running = 0; /* First byte is the length of the array */ if (copy_from_user(&len, (void *) arg, 1)) return -EFAULT; dprintk("size of array = %d\n", len); buf = vmalloc(len + 1); if (copy_from_user(buf, (void *) arg, len)) { vfree(buf); return -EFAULT; } /*retval = i2c_init(hrtdev, hrtdev->virtaddr, buf); */ retval = i2c_init(hrtdev, buf); vfree(buf); /* Start the timer again */ timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; return retval; } case IOC_HRT_ENABLE_SUBFRAME: { SUB_FRAME_TOGGLE = 1; SUB_FRAME_USER_CHOICE = arg; return 0; } case IOC_HRT_DISABLE_SUBFRAME: { SUB_FRAME_TOGGLE = 0; return 0; } case IOC_HRT_GET_MAGIC_MMAP_OFFSET: { return HRT_MAGIC_MMAP_OFFSET; } default: return -EINVAL; } return -EINVAL; } #ifndef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* HRT IOCTL */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_ioctl - wrapper to pass ioctl's on hrtmem to * hrt_do_ioctl() */ static int hrt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) int minor = MINOR(inode->i_rdev); #else int minor = MINOR(kdev_val(inode->i_rdev)); #endif struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; int ret; down(&hrtdev->sem); hrtdev->win = &per_file->win; dprintk("ioctl: %x on minor %d\n", cmd, minor); if (minor > num_hrtdevs) { hrt_printk("Minor num %d out of range\n", minor); up(&hrtdev->sem); return -1; } ret = hrt_do_ioctl(hrtdev, cmd, arg); up(&hrtdev->sem); return ret; } #endif /* ************************************************************** */ /* ************************************************************** */ /* TIMER FUNCTION */ /* ************************************************************** */ /* ************************************************************** */ /** * timer_func - the crux of the driver. This function is called every timer * tick and checks to see if the field bit has changed. If it has, we * either wake up sleeping readers or call streaming_fieldchange(). */ void timer_func(unsigned long ptr) { int i; for (i = 0; hrtdevs[i].pcidev; i++) { int new_field; new_field = readb(hrtdevs[i].virtaddr + HRT_CONTROL_REG) & 1; if (new_field != hrtdevs[i].field_bit) { hrtdevs[i].field_bit = new_field; #ifdef HAVE_V4L2 if (hrtdevs[i].streaming) { streaming_fieldchange(&hrtdevs[i]); } else { wake_up(&hrtdevs[i].waitqueue); } #else wake_up(&hrtdevs[i].waitqueue); #endif } } timer.expires = jiffies + (HZ/100); add_timer(&timer); } /* ************************************************************** */ /* ************************************************************** */ /* HRT INTERRUPT */ /* ************************************************************** */ /* ************************************************************** */ /* Updates the field_bit much like the timer function would have. * * However this function works with more precision than the timer * * function. */ void hrt_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct hrt *hrtdev = dev_id; int new_field; writeb(HRT_INTERRUPT_OFF, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); new_field = readb(hrtdev->virtaddr + HRT_CONTROL_REG) & 1; if (new_field != hrtdev->field_bit) { hrtdev->field_bit = new_field; #ifdef HAVE_V4L2 if (hrtdev->streaming) { streaming_fieldchange(hrtdev); } else { wake_up(&hrtdev->waitqueue); } #else wake_up(&hrtdev->waitqueue); #endif } else { writeb(HRT_INTERRUPT_ON, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); } } #ifndef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* HRT OPEN */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_open - checks minor number range, sets private_data, * starts timer if necessary, increments users */ static int hrt_open(struct inode *inode, struct file *file) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) int minor = MINOR(inode->i_rdev); #else int minor = MINOR(kdev_val(inode->i_rdev)); #endif if (minor > num_hrtdevs) { hrt_printk("Minor num %d out of range\n", minor); return -1; } down(&hrtdevs[minor].sem); /* Initialize per-file data */ file->private_data = per_file_init(&hrtdevs[minor]); /* Increment users */ hrtdevs[minor].users++; #ifdef HRT_HAS_INTERRUPTS /* register the interrupt handler */ if(hrt_irq >= 0) { if(request_irq(hrt_irq, hrt_interrupt, SA_INTERRUPT, "hrt", &hrtdevs[minor])) { printk(KERN_INFO "hrt: can't get assigned irq %i\n", hrt_irq); hrt_irq = -1; } writeb(HRT_INTERRUPT_ON, (HRT_IRQ_REG + hrtdevs[minor].virtaddr)); wmb(); } #else /* Get the timer going */ if (!timer_running) { timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; } #endif up(&hrtdevs[minor].sem); dprintk("hrt_open called\n"); return 0; } #endif #ifndef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* HRT RELEASE */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_release - free our per-fd data, stop the timer if users is 0 */ static int hrt_release(struct inode *inode, struct file *file) { struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; #ifdef HAVE_V4L2 struct video_device *vfl = video_devdata(file); #endif down(&hrtdev->sem); /* Decrement users */ hrtdev->users--; #ifdef HRT_HAS_INTERRUPTS if(hrtdev->users <= 0) { free_irq(hrt_irq, hrtdev); } #endif /* Tell the timer to stop if necessary */ #ifdef HAVE_V4L2 if (!vfl->users && !hrtdev->users) { del_timer_sync(&timer); timer_running = 0; } #else if (!hrtdev->users) { del_timer_sync(&timer); timer_running = 0; } #endif /* Free our private data */ vfree(file->private_data); up(&hrtdev->sem); dprintk("hrt_release called\n"); return 0; } #endif #ifndef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* HRT MMAP */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_mmap - provides direct access to the card's memory */ static int hrt_mmap(struct file *file, struct vm_area_struct *vma) { struct hrt *hrtdev = file->private_data; vma->vm_flags |= (VM_IO | VM_RESERVED); /* Just map the pages directly */ if (remap_page_range(vma->vm_start, hrtdev->physaddr, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0; } #endif /* ************************************************************** */ /* ************************************************************** */ /* CLEANUP MODULE */ /* ************************************************************** */ /* ************************************************************** */ /** * cleanup_module - called when we're unloaded of course */ void cleanup_module(void) { int i; for (i = 0; hrtdevs[i].pcidev; i++) { hrt_dev_cleanup(&hrtdevs[i]); } /* Release our resouces */ if (!disable_hrtmem) unregister_chrdev(hrtmem_major, "hrtmem"); } /* ************************************************************** */ /* ************************************************************** */ /* GRAB FIELD */ /* ************************************************************** */ /* ************************************************************** */ /** * grab_field - reads a field (half a frame) from the card's memory. * Which field (0 or 1) is determined by the parity parameter. * It stores the data into the framedata parameter, which is usually * the same as hrtdev->framedata, except when we're doing streaming, * in which case it's a stream buffer's vaddress field. */ void inline grab_field(struct hrt *hrtdev, unsigned char *framedata, int parity) { int i, j; /* XXX: not sure why this is needed. Without it the image is skewed on values for startx that are not multiples of 4. */ int linelen; linelen = hrtdev->win->width - hrtdev->win->startx; char finalbuffer[hrtdev->bytesperline]; #ifdef HRT_HAS_INTERRUPTS writeb(HRT_INTERRUPT_OFF, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); #endif if (hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { int bufpos = 0; for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height; i++) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); for (j = 0; j < hrtdev->win->width; j++) { if (j < HRT_WIDTH_GRAY) { /* getting pixels 0 thru 511 */ memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j, 1); memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + (j + HRT_WIDTH_GRAY), 1); } else { /* otherwize get the LSB and MSB for the last 128 pixels of the row */ /* getting pixels 512 thru 640 */ memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + (j + HRT_WIDTH_GRAY), 1); memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + (j + (HRT_WIDTH_GRAY * 2)), 1); } } memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline, finalbuffer, hrtdev->bytesperline); bufpos = 0; } } else { /* else it is a grayscale card */ for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height; i += 2) { writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->starty) * linelen, hrtdev->virtaddr + hrtdev->win->startx, linelen); } } #ifdef HRT_HAS_INTERRUPTS writeb(HRT_INTERRUPT_ON, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); #endif } /* ************************************************************** */ /* ************************************************************** */ /* GRAB SUBFIELD */ /* ************************************************************** */ /* ************************************************************** */ void inline grab_subfield(struct hrt *hrtdev, unsigned char *framedata, int parity, int which_sub_frame) { int i, j; int linelen, halflinelen; int x_value = 0; int y_value = 0; linelen = hrtdev->win->width; halflinelen = hrtdev->win->width/2; char finalbuffer[hrtdev->bytesperline]; //char *buf[2] = {0, 0}; #ifdef HRT_HAS_INTERRUPTS writeb(HRT_INTERRUPT_OFF, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); #endif switch (which_sub_frame) { case SUB_FRAME_UPPER_LEFT: { x_value = 0; y_value = 0; break; } case SUB_FRAME_UPPER_RIGHT: { x_value = 320; y_value = 0; break; } case SUB_FRAME_LOWER_LEFT: { x_value = 0; y_value = 240; break; } case SUB_FRAME_LOWER_RIGHT: { x_value = 320; y_value = 240; break; } default: printk("<1>hrt: in function grab_subfield: invalid subframe choice\n"); printk("<1>hrt: in function grab_subfield: using default\n"); } if (hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { int bufpos = 0; for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height; i++) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); if ((i >= y_value) && (i <= y_value + hrtdev->win->height/2)) { for (j = 0; j < hrtdev->win->width; j++) { if (j < HRT_WIDTH_GRAY) { /* getting pixels 0 thru 511 */ if ((j >= x_value) && (j <= x_value + halflinelen)) { memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->starty + j, 1); memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->starty + (j + HRT_WIDTH_GRAY), 1); } // memcpy_fromio(finalbuffer + bufpos++, buf, 1); // memcpy_fromio(finalbuffer + bufpos++, buf, 1); } else { /* otherwize get the LSB and MSB for the last 128 pixels of the row */ /* getting pixels 512 thru 640 */ if ((j >= x_value) && (j <= x_value + halflinelen)) { memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->starty + (j + HRT_WIDTH_GRAY), 1); memcpy_fromio(finalbuffer + bufpos++, hrtdev->virtaddr + hrtdev->win->starty + (j + (HRT_WIDTH_GRAY * 2)), 1); } // memcpy_fromio(finalbuffer + bufpos++, buf, 1); // memcpy_fromio(finalbuffer + bufpos++, buf, 1); } } memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline, finalbuffer, hrtdev->bytesperline); bufpos = 0; } else { // "i" is pointing to a point in the y axis that we want blank for (j = 0; j < hrtdev->win->width; j++) { // memcpy_fromio(finalbuffer + bufpos++, buf, 1); } memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline, finalbuffer, hrtdev->bytesperline); } } /* end for loop that moves i pointer through y axis */ } else { /* else it is a grayscale card */ for (i = y_value; i < hrtdev->win->height/2; i += 2) { writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->starty) * linelen, hrtdev->virtaddr + x_value, linelen); } } #ifdef HRT_HAS_INTERRUPTS writeb(HRT_INTERRUPT_ON, (HRT_IRQ_REG + hrtdev->virtaddr)); wmb(); #endif } #ifdef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* THE V4L2 STUFF */ /* ************************************************************** */ /* ************************************************************** */ /* ************************************************************** */ /* ************************************************************** */ /* V4L1 STUFF */ /* ************************************************************** */ /* ************************************************************** */ /** * v4l1_ioctls - used for printing out ioctl codes in hrt_v4l2_do_ioctl */ static const char *v4l1_ioctls[] = { "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", "GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF", "SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO", "SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE", "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", "GVBIFMT", "SVBIFMT" }; /* ************************************************************** */ /* ************************************************************** */ /* V4L2 DO IOCTL */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_v4l2_do_ioctl - handles ioctl's on /dev/videoN */ static int hrt_v4l2_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg) { struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; hrtdev->win = &per_file->win; /* This nice code copied from xawtv's libng. It prints out the ioctl's that are done. */ switch (_IOC_TYPE(cmd)) { case 'v': dprintk("ioctl 0x%x (v4l1, VIDIOC%s)\n", cmd, (_IOC_NR(cmd) < ARRAY_SIZE(v4l1_ioctls)) ? v4l1_ioctls[_IOC_NR(cmd)] : "???"); break; case 'V': dprintk("ioctl 0x%x (v4l2, %s)\n", cmd, v4l2_ioctl_names[_IOC_NR(cmd)]); break; default: dprintk("ioctl 0x%x (?)\n", cmd); } switch (cmd) { /* Streaming ioctl's */ case VIDIOC_REQBUFS: { struct v4l2_requestbuffers *req = arg; if (hrtdev->stream_buffers_mapped) { hrt_printk("REQBUFS - Can't request buffers if " " buffers are already mapped\n"); return -EPERM; } if (!mmap_request_buffers(hrtdev, req)) { hrt_printk("REQBUFS - Request of buffers failed\n"); return -EINVAL; } return 0; } case VIDIOC_QUERYBUF: { struct v4l2_buffer *buf = (struct v4l2_buffer *) arg; int i; i = buf->index; if (i < 0 || i >= MAX_CAPTURE_BUFFERS || !hrtdev->stream_buf[i].requested || (buf->type & V4L2_BUF_TYPE_VIDEO_CAPTURE) != (hrtdev->stream_buf[i].vidbuf.type & V4L2_BUF_TYPE_VIDEO_CAPTURE)) { hrt_printk("QUERYBUF - bad parameter\n"); return -EINVAL; } *buf = hrtdev->stream_buf[i].vidbuf; return 0; } case VIDIOC_QBUF: { struct v4l2_buffer *buf = (struct v4l2_buffer *) arg; if (!hrtdev->stream_buffers_mapped) { hrt_printk("QBUF - No buffers are mapped\n"); return -EINVAL; } if (stream_buffer_use == BUFFER_QUEUED) { hrt_printk("QBUF - buffer was already queued\n"); return 0; } if (!hrt_queuebuffer(hrtdev, buf)) return -EINVAL; return 0; } case VIDIOC_DQBUF: { struct v4l2_buffer *buf = (struct v4l2_buffer *) arg; if (stream_buffer_use == BUFFER_NOT_QUEUED) { hrt_printk("DQBUF - buffer was already dequeued\n"); return 0; } if (!hrt_dequeuebuffer(hrtdev, buf)) { hrt_printk("DQBUF - failure to dequeue buffer\n"); return -EINVAL; } return 0; } case VIDIOC_STREAMON: { __u32 *type = (__u32 *) arg; if (!hrt_streamon(hrtdev, *type)) return -EINVAL; return 0; } case VIDIOC_STREAMOFF: { __u32 *type = (__u32 *) arg; hrt_streamoff(hrtdev, *type); return 0; } /* Non-streaming ioctl's */ case VIDIOC_QUERYCAP: { struct v4l2_capability *b = (struct v4l2_capability *) arg; strcpy(b->driver, "hrt"); strncpy(b->card, "PS 512-8-PCI", sizeof(b->card)); sprintf(b->bus_info,"PCI:%s", hrtdev->pcidev->slot_name); b->version = KERNEL_VERSION(0, 0, 2); if (disable_streaming) { b->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE; } else { b->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; } return 0; } case VIDIOC_ENUM_FMT: { struct v4l2_fmtdesc *f = arg; enum v4l2_buf_type type = f->type; int index = f->index; /* There's only one format */ if (index > 0) return -EINVAL; if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { memset(f, 0, sizeof(*f)); f->index = index; f->type = type; if(hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { f->pixelformat = V4L2_PIX_FMT_RGB565; } else { f->pixelformat = V4L2_PIX_FMT_GREY; } strcpy(f->description, "Grey"); return 0; } else { return -EINVAL; } } case VIDIOC_TRY_FMT: case VIDIOC_S_FMT: case VIDIOC_G_FMT: { struct v4l2_format *f = arg; enum v4l2_buf_type type = f->type; if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format)); f->fmt.pix.width = hrtdev->win->width; f->fmt.pix.height = hrtdev->win->height; if(hrtdev->typeofcard == TYPE_OF_CARD_IS_COLOR) { f->fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; } else { f->fmt.pix.pixelformat = V4L2_PIX_FMT_GREY; } f->fmt.pix.bytesperline = hrtdev->bytesperline; f->fmt.pix.sizeimage = hrtdev->framesize; return 0; } else { return -EINVAL; } } case VIDIOC_G_STD: { v4l2_std_id *id = arg; *id = V4L2_STD_NTSC; return 0; } case VIDIOC_S_STD: { /* There's only one standard that we support, NTSC */ return 0; } case VIDIOC_QUERYCTRL: { struct v4l2_queryctrl *c = arg; int i; for (i = 0; i < ARRAY_SIZE(hrt_ctls); i++) if (hrt_ctls[i].id == c->id) break; /* Didn't find the id */ if (i == ARRAY_SIZE(hrt_ctls)) { return -EINVAL; } *c = hrt_ctls[i]; return 0; } case VIDIOC_G_CTRL: { struct v4l2_control *c = arg; int i; for (i = 0; i < ARRAY_SIZE(hrt_ctls); i++) if (hrt_ctls[i].id == c->id) break; if (i == sizeof(hrt_ctls)) return -EINVAL; if (c->id == V4L2_CID_BRIGHTNESS) { c->value = hrtdev->regvals[HRT_BRIGHTNESS_REG]; } else if (c->id == V4L2_CID_CONTRAST) { c->value = hrtdev->regvals[HRT_CONTRAST_REG]; } else { return -EINVAL; } return 0; } case VIDIOC_S_CTRL: { struct v4l2_control *c = arg; int i; for (i = 0; i < sizeof(hrt_ctls); i++) if (hrt_ctls[i].id == c->id) break; if (i == sizeof(hrt_ctls)) return -EINVAL; if (c->id == V4L2_CID_BRIGHTNESS) { if (c->value > 255) return -EINVAL; c->value = i2c_set_reg(hrtdev, HRT_BRIGHTNESS_REG, c->value); } else if (c->id == V4L2_CID_CONTRAST) { if (c->value > 255) return -EINVAL; c->value = i2c_set_reg(hrtdev, HRT_CONTRAST_REG, c->value); } else { return -EINVAL; } return 0; } case VIDIOC_ENUMINPUT: { struct v4l2_input *i = arg; unsigned int n; /* Only one input */ if (i->index > 0) return -EINVAL; n = i->index; memset(i, 0, sizeof(*i)); i->index = n; i->type = V4L2_INPUT_TYPE_CAMERA; sprintf(i->name, "NTSC Camera"); return 0; } case VIDIOC_ENUMSTD: { struct v4l2_standard *e = arg; struct v4l2_fract fract = { 1001, 30000 }; unsigned int index = e->index; /* One standard (NTSC) */ if (index > 0) return -EINVAL; e->id = V4L2_STD_NTSC_M; e->index = index; strcpy(e->name, "NTSC"); e->frameperiod = fract; e->framelines = hrtdev->win->height; return 0; } case VIDIOC_G_PARM: { struct v4l2_streamparm *parm = arg; struct v4l2_standard s; if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; memset(parm, 0, sizeof(*parm)); v4l2_video_std_construct(&s, V4L2_STD_NTSC, "NTSC"); parm->parm.capture.timeperframe = s.frameperiod; return 0; } case VIDIOC_G_INPUT: { return 0; } case VIDIOC_S_INPUT: { return 0; } default: return -ENOIOCTLCMD; } } /* ************************************************************** */ /* ************************************************************** */ /* V4L2 IOCTLs */ /* ************************************************************** */ /* ************************************************************** */ /** * hrt_v4l2_ioctl - handler for ioctl's on /dev/videoN. Tries the private * ioctl's first, via hrt_do_ioctl(). */ static int hrt_v4l2_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; int ret; hrtdev->win = &per_file->win; dprintk("ioctl arg = %lx\n", arg); down(&hrtdev->sem); ret = hrt_do_ioctl(hrtdev, cmd, arg); if (ret != -EINVAL) { dprintk("Releasing sem\n"); up(&hrtdev->sem); return ret; } ret = video_usercopy(inode, file, cmd, arg, hrt_v4l2_do_ioctl); up(&hrtdev->sem); return ret; } #endif /* ************************************************************** */ /* ************************************************************** */ /* V4L2 STREAMING_FIELDCHANGE */ /* ************************************************************** */ /* ************************************************************** */ #ifdef HAVE_V4L2 /** * streaming_fieldchange - if streaming is on, this function is called * from timer_func when the field_bit changes */ static inline void streaming_fieldchange(struct hrt *hrtdev) { printk("<1>newton: im streaming\n"); struct stream_buffer *streambuf; /* Capture list is empty */ if (!hrtdev->capture_list.length) { return; } streambuf = queue_peek_head(&hrtdev->capture_list); /* Sanity checks- don't want to do anything bad in interrupt mode */ if (streambuf == NULL) { hrt_printk("streambuf is NULL!\n"); return; } if (streambuf->vaddress == NULL) { hrt_printk("vaddress is NULL!\n"); return; } if (hrtdev->framedata == NULL) { hrt_printk("framedata is NULL!\n"); return; } grab_field(hrtdevs, streambuf->vaddress, !hrtdev->field_bit); streambuf->fields_grabbed |= (!hrtdev->field_bit) + 1; /* We have a complete frame */ if (streambuf->fields_grabbed == 3) { streambuf->fields_grabbed = 0; streambuf->vidbuf.flags |= V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_KEYFRAME; /* Move the buffer to the done queue */ queue_del(streambuf, &hrtdev->capture_list); queue_add_tail(streambuf, &hrtdev->done_list); hrtdev->field_bit = !hrtdev->field_bit; wake_up(&hrtdev->waitqueue); } } #endif /* ************************************************************** */ /* ************************************************************** */ /* V4L2 FUNCTIONS */ /* ************************************************************** */ /* ************************************************************** */ #ifdef HAVE_V4L2 /* ************************************************************** */ /* ************************************************************** */ /* V4L2 OPEN */ /* ************************************************************** */ /* ************************************************************** */ /* int hrt_v4l2_open(struct inode *inode, struct file *file) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) int minor = MINOR(inode->i_rdev); #else int minor = MINOR(kdev_val(inode->i_rdev)); #endif if (minor > num_hrtdevs) { hrt_printk("Minor num %d out of range\n", minor); return -1; } struct video_device *vfl = video_devdata(file); int retval = 0; down(&hrtdevs[minor].sem); vfl->users++; if (exclusive && vfl->users) { retval = -EBUSY; } else { file->private_data = per_file_init(&hrtdevs[minor]); hrtdevs[minor].users++; #ifdef HRT_HAS_INTERRUPTS if(hrt_irq >= 0) { if(request_irq(hrt_irq, hrt_interrupt, SA_INTERRUPT, "hrt", &hrtdevs[minor])) { printk(KERN_INFO "hrt: can't get assigned irq %i\n", hrt_irq); hrt_irq = -1; } writeb(HRT_INTERRUPT_ON, (HRT_IRQ_REG + hrtdevs[minor].virtaddr)); wmb(); } #else if (!timer_running) { timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; } #endif hrtdevs[minor].streaming = 0; } up(&hrtdevs[minor].sem); return retval; } */ int hrt_v4l2_open(struct inode *inode, struct file *file) { struct video_device *vfl = video_devdata(file); struct hrt *hrtdev = vfl->priv; int retval = 0; down(&hrtdev->sem); vfl->users++; if (exclusive && vfl->users) { retval = -EBUSY; } else { /* Initialize per-file data */ file->private_data = per_file_init(hrtdev); /* Get the timer going */ if (!timer_running) { timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; } /* Start off not in streaming mode */ hrtdev->streaming = 0; } up(&hrtdev->sem); return retval; } /* ************************************************************** */ /* ************************************************************** */ /* V4L2 RELEASE */ /* ************************************************************** */ /* ************************************************************** */ static int hrt_v4l2_release(struct inode *inode, struct file *file) { struct video_device *vfl = video_devdata(file); struct hrt *hrtdev = vfl->priv; down(&hrtdev->sem); vfl->users--; /* Tell the timer to stop if necessary */ if (!vfl->users && !hrtdev->users) { del_timer_sync(&timer); timer_running = 0; } /* Free our private data */ vfree(file->private_data); up(&hrtdev->sem); return 0; } /* ************************************************************** */ /* ************************************************************** */ /* V4L2 POLL */ /* ************************************************************** */ /* ************************************************************** */ unsigned int hrt_v4l2_poll(struct file *file, poll_table *wait) { struct hrt_per_file *per_file = file->private_data; struct hrt *hrtdev = per_file->hrtdev; if (!hrtdev->streaming) { poll_wait(file, &hrtdev->waitqueue, wait); } else { /* Go to sleep if the done list is empty */ while (!hrtdev->done_list.length) { up(&hrtdev->sem); if (wait_event_interruptible (hrtdev->waitqueue, (hrtdev->done_list.length))) return -ERESTARTSYS; if (down_interruptible(&hrtdev->sem)) return -ERESTARTSYS; } } return POLLIN | POLLRDNORM; } #endif /* ************************************************************** */ /* ************************************************************** */ /* V4L2 QUEUE BUFFER MANIPULATION */ /* ************************************************************** */ /* ************************************************************** */ #ifdef HAVE_V4L2 /** * hrt_queuebuffer - called by the QBUF ioctl. * Adds a buffer to the capture queue. */ /* ************************************************************** */ /* ************************************************************** */ /* HRT QUEUEBUFFER */ /* ************************************************************** */ /* ************************************************************** */ int hrt_queuebuffer(struct hrt *hrtdev, struct v4l2_buffer *vidbuf) { int i = vidbuf->index; struct stream_buffer *buf = NULL; if (!hrtdev->stream_buffers_mapped) { hrt_printk("QBUF - no buffers mapped\n"); return 0; } if (vidbuf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { hrt_printk("QBUF - wrong type (type = %x)\n", vidbuf->type); return 0; } if (i < 0 || i >= MAX_CAPTURE_BUFFERS || !hrtdev->stream_buf[i].requested) { hrt_printk("QBUF - buffer index %d is out of range\n", i); return 0; } buf = &hrtdev->stream_buf[i]; if (stream_buffer_use == BUFFER_QUEUED) { hrt_printk("hrt_queuebuffer - buffer already queued\n"); return 1; } if (!(buf->vidbuf.flags & V4L2_BUF_FLAG_MAPPED)) { hrt_printk("QBUF - buffer %d is not mapped\n", i); return 0; } if ((buf->vidbuf.flags & V4L2_BUF_FLAG_QUEUED)) { hrt_printk("QBUF - buffer %d is already queued\n", i); return 0; } buf->vidbuf.flags &= ~V4L2_BUF_FLAG_DONE; queue_add_tail(buf, &hrtdev->capture_list); buf->vidbuf.flags |= V4L2_BUF_FLAG_QUEUED; stream_buffer_use = BUFFER_QUEUED; return 1; } /** * hrt_dequeuebuffer - called by the DQBUF ioctl. * Returns a buffer from the done queue. */ /* ************************************************************** */ /* ************************************************************** */ /* HRT DEQUEUEBUFFER */ /* ************************************************************** */ /* ************************************************************** */ int hrt_dequeuebuffer(struct hrt *hrtdev, struct v4l2_buffer *buf) { struct stream_buffer *newbuf; if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { hrt_printk("DQBUF - Wrong buffer type\n"); return 0; } newbuf = queue_del_head(&hrtdev->done_list); if (stream_buffer_use == BUFFER_NOT_QUEUED) { hrt_printk("hrt_dequeuebuffer - buffer already dequeued\n"); return 1; } if (newbuf == NULL) { hrt_printk("DQBUF - No buffers on done queue\n"); return 0; } newbuf->vidbuf.flags &= ~V4L2_BUF_FLAG_QUEUED; *buf = newbuf->vidbuf; stream_buffer_use = BUFFER_NOT_QUEUED; return 1; } /** * mmap_stream_buffer_from_offset - get a buffer via the specified offset */ static struct stream_buffer *mmap_stream_buffer_from_offset(struct hrt *dev, unsigned long offset) { int i; offset *= PAGE_SIZE; for (i = 0; i < MAX_CAPTURE_BUFFERS; ++i) if (offset == dev->stream_buf[i].vidbuf.m.offset) return &dev->stream_buf[i]; return NULL; } /** * hrt_vma_open - increments reference count */ static void hrt_vma_open(struct vm_area_struct *vma) { struct video_device *vdev = video_devdata(vma->vm_file); struct hrt *hrtdev = vdev->priv; struct stream_buffer *buf; if (hrtdev == NULL) return; buf = mmap_stream_buffer_from_offset(hrtdev, vma->vm_pgoff); if (buf == NULL) return; buf->vma_refcount++; } /** * hrt_vma_close - decrements reference count of a stream buffer */ static void hrt_vma_close(struct vm_area_struct *vma) { struct video_device *vdev = video_devdata(vma->vm_file); struct hrt *hrtdev = vdev->priv; struct stream_buffer *buf; if (hrtdev == NULL) return; buf = mmap_stream_buffer_from_offset(hrtdev, vma->vm_pgoff); buf->vma_refcount--; if (buf->vma_refcount > 0) { return; } if (hrtdev->streaming) { hrt_printk(KERN_WARNING "munmap() called while streaming\n"); hrt_streamoff(hrtdev, buf->vidbuf.type); } if (buf->vaddress != NULL) { vfree(buf->vaddress); buf->vaddress = NULL; } buf->vidbuf.flags = 0; if (hrtdev->stream_buffers_mapped > 0) hrtdev->stream_buffers_mapped--; } /** * hrt_vma_nopage - calls kvirt_to_pa to convert the vaddress in the * stream buffer (indexed via the offset parameter to mmap()) to * a page number for access from userspace */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) struct page *hrt_vma_nopage(struct vm_area_struct *vma, unsigned long address, int write_access) #else struct page *hrt_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type) #endif { /* Streaming data buffer */ struct video_device *vdev = video_devdata(vma->vm_file); struct hrt *hrtdev = vdev->priv; struct stream_buffer *buf; unsigned long offset_into_buf; struct page *page; buf = mmap_stream_buffer_from_offset(hrtdev, vma->vm_pgoff); if (buf == NULL) return 0; offset_into_buf = address - vma->vm_start; if (offset_into_buf >= buf->vidbuf.length) { hrt_printk("Attempt to read past end of mmap() buffer\n"); return 0; } page = kvirt_to_pa((unsigned long) (buf->vaddress + offset_into_buf)); if (page != NULL) atomic_inc(&page->count); return page; } /** * hrt_vm_ops - vm operations on /dev/videoN */ struct vm_operations_struct hrt_vm_ops = { .open = hrt_vma_open, .close = hrt_vma_close, .nopage = hrt_vma_nopage, }; /** * mmap_request_buffers - called by the ioctl REQBUF to request buffers */ int mmap_request_buffers(struct hrt *dev, struct v4l2_requestbuffers *req) { int i; u32 buflen; u32 type; if (req->count < 1) req->count = 1; if (req->count > MAX_CAPTURE_BUFFERS) req->count = MAX_CAPTURE_BUFFERS; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* The buffer length needs to be a multiple of the page size */ buflen = (dev->framesize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); dprintk("Granting %d buffers\n", req->count); /* Now initialize the buffer structures. Don't allocate the buffers until they're mapped. */ for (i = 0; i < req->count; i++) { dev->stream_buf[i].requested = 1; dev->stream_buf[i].vidbuf.index = i; dev->stream_buf[i].vidbuf.type = type; /* offset must be unique for each buffer, and a multiple of PAGE_SIZE on 2.4.x */ dev->stream_buf[i].vidbuf.m.offset = PAGE_SIZE * (i + 1); dev->stream_buf[i].vidbuf.length = buflen; dev->stream_buf[i].vidbuf.bytesused = 0; dev->stream_buf[i].vidbuf.flags = 0; /* XXX: should set timestamp? */ dev->stream_buf[i].vidbuf.sequence = 0; dev->stream_buf[i].vma_refcount = 0; dev->stream_buf[i].vaddress = NULL; memset(&dev->stream_buf[i].vidbuf.timecode, 0, sizeof(struct v4l2_timecode)); } for (i = req->count; i < MAX_CAPTURE_BUFFERS; i++) dev->stream_buf[i].requested = 0; dev->stream_buffers_requested = req->count; return 1; } /** * hrt_mmap - called by the mmap() system call on /dev/videoN */ int hrt_v4l2_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *dev = video_devdata(file); struct hrt *hrtdev = dev->priv; struct stream_buffer *buf = NULL; buf = mmap_stream_buffer_from_offset(hrtdev, vma->vm_pgoff); if (buf == NULL) { hrt_printk("mmap() - Invalid offset parameter\n"); return -EINVAL; } if (buf->vidbuf.length != vma->vm_end - vma->vm_start) { hrt_printk("mmap() - Bad length parameter\n"); return -EINVAL; } if (!buf->requested) { hrt_printk("mmap() - Buffer is not available for mapping\n"); return -EINVAL; } if (buf->vidbuf.flags & V4L2_BUF_FLAG_MAPPED) { hrt_printk("mmap() - Buffer is already mapped\n"); return -EINVAL; } if (buf->vaddress != NULL) vfree(buf->vaddress); // Allocate virtual memory. This memory is mapped to the // buffer in user space. buf->vaddress = vmalloc(buf->vidbuf.length); if (buf->vaddress == NULL) { hrt_printk("Could not allocate mmap() buffer\n"); return -ENODEV; } buf->vidbuf.flags |= V4L2_BUF_FLAG_MAPPED; hrtdev->stream_buffers_mapped++; vma->vm_ops = &hrt_vm_ops; if (vma->vm_ops->open) vma->vm_ops->open(vma); return 0; } /** * hrt_streamon - called by the STREAMON ioctl. */ int hrt_streamon(struct hrt *hrtdev, __u32 type) { struct stream_buffer *buf; if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { printk("STREAMON - Wrong buffer type\n"); return 0; } /* Move any leftover done buffers to the free pool */ while ((buf = queue_del_head(&hrtdev->done_list))) buf->vidbuf.flags &= ~V4L2_BUF_FLAG_QUEUED; hrtdev->streaming = 1; return 1; } /** * hrt_streamoff - called by the STREAMOFF ioctl. */ void hrt_streamoff(struct hrt *hrtdev, __u32 type) { int i = 0; struct stream_buffer *buf; if (!hrtdev->streaming) { hrt_printk("STREAMOFF - Not in streaming mode\n"); return; } if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { hrt_printk("STREAMOFF - Wrong buffer type\n"); return; } hrtdev->streaming = 0; while ((buf = queue_del_head(&hrtdev->done_list))) { buf->vidbuf.flags &= ~V4L2_BUF_FLAG_QUEUED; i++; } } #endif /* ************************************************************** */ /* ************************************************************** */ /* HRT.C FOOTER */ /* ************************************************************** */ /* ************************************************************** */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) EXPORT_NO_SYMBOLS; #endif