#include #include #include #include #include #include #include #include "hrt.h" /** * hrt_addresses - a list of possible jumper-selected addresses * jumper A on = plug and play address (above one MB address) * jumper A off = hardwired to 0xdc000 or 0xd4000 * jumper B on = address 0xd4000 (ignored if jumper A is on) * jumper B off = address 0xdc000 (ignored if jumper A is on) */ const unsigned long hrt_addresses[] = { 0xd4000, 0xdc000 }; /** * I2C routines - * Consider providing this capability also for an application * that is using raw memory-mapping, maybe as a separate header * file and object library */ /** * saa7110_default_init_regs - the register values used to * initialize the SAA7110 A/D converter. * Because some registers are not set, this is given * as a list of pairs. The first element of each pair * is the register number, and the second number is the * value of the register. The array is terminated by * a double zero-byte. This generalization allows us to reuse * the initialization routine with different tables, * to allow an application to reset any set of device * registers. */ const unsigned char saa7110_default_init_regs[] = { 94, /* there are 94 bytes that follow */ 0x00, 0x4c, /* increment delay (IDEL) */ 0x01, 0x3c, /* HSY begin 50 Hz */ 0x02, 0x0d, /* HSY stop 50 Hz */ 0x03, 0xef, /* HCL begin 50 Hz */ 0x04, 0xbd, /* HCL stop 50 Hz */ 0x05, 0xf0, /* HSY after PHI1 50 Hz */ 0x06, 0x00, /* luminance control */ 0x07, 0x00, /* hue control */ 0x08, 0xf8, /* colour killer threshold QUAM (PAL/NTSC) */ 0x09, 0xf8, /* colour killer threshold SECAM */ 0x0A, 0x60, /* PAL switch sensitivity */ 0x0B, 0x50, /* SECAM switch sensitivity */ 0x0C, 0x00, /* gain control chrominance */ 0x0D, 0x86, /* standard/mode control */ /* 7 VTRC = 1 (VCR mode, not TV) 6 XXX 5 XXX 4 XXX 3 RTSE = 0 (PLIN switched to output) 2 HRMV = 1 (HREF normal position) 1 SSTB = 1 (status byte = 1) 0 SECS = 0 (other standards, not SECAM) */ 0x0E, 0x18, /* I/O and clock control */ 0x0F, 0x90, /* control #1 */ 0x10, 0x00, /* control #2 */ 0x11, 0x2c, /* chrominance gain reference */ 0x12, 0x7f, /* chrominance saturation */ 0x13, 0x5e, /* luminance contrast */ 0x14, 0x42, /* HSY begin 60 Hz */ 0x15, 0x1a, /* HSY stop 60 Hz */ 0x16, 0xff, /* HCL begin 60 Hz */ 0x17, 0xda, /* HCL stop 60 Hz */ 0x18, 0xf0, /* HSY after PHI1 60 Hz */ 0x19, 0x9b, /* luminance brightness */ /* 0x1A - not used 0x1B - not used 0x1C - not used 0x1D - not used 0x1E - not used 0x1F - not used */ 0x20, 0x7c, /* analog control #1 */ 0x21, 0x03, /* analog control #2 */ 0x22, 0xd2, /* mixer control #1 */ 0x23, 0x41, /* clamping level control 21 */ 0x24, 0x80, /* clamping level control 22 */ 0x25, 0x41, /* clamping level control 31 */ 0x26, 0x80, /* clamping level control 32 */ 0x27, 0x4f, /* gain control #1 */ 0x28, 0xfe, /* white peak control */ 0x29, 0x01, /* sync bottom control */ 0x2A, 0xcf, /* gain control analog #2 */ 0x2B, 0x0f, /* gain control analog #3 */ 0x2C, 0x83, /* mixer control #2 */ 0x2D, 0x01, /* integration value gain */ 0x2E, 0x81, /* vertical blanking pulse set */ 0x2F, 0x03, /* vertical blanking pulse reset */ 0x30, 0x60, /* ADCs gain control */ 0x31, 0x71, /* mixer control #3 */ 0x32, 0x02, /* integration value white peak */ 0x33, 0x8c, /* mixer control #4 */ 0x34, 0x03, /* gain update level */ }; unsigned int HRT_WIDTH = 0; unsigned int HRT_HEIGHT = 0; unsigned int HRT_BYTES_PER_PIXEL = 0; unsigned int HRT_BYTES_PER_LINE = 0; unsigned int HRT_FRAMESIZE = 0; /* Number of registers on the board- checked in i2c_init() */ #define HRT_NUMREGS 0x34 /* Small utility used in read()... Probably there's an official one somewhere in the kernel, huh? */ #ifndef min # define min(a,b) ((a)<(b) ? (a) : (b)) #endif /* Interrupt Line No*/ int HRT_IRQ = -1; /* Variable to Grab Full or Sub Frame */ /* 0 = GRAB FULL FRAME */ /* 1 = GRAB SUB FRAME */ int GRAB_SUB_FRAME = 0; /** * struct hrt_per_file - contains data to be stored per file handle */ struct hrt_per_file { struct subwindow win; struct hrt *hrtdev; }; /** * struct hrt - the main device struct. This represents the card. */ struct hrt { unsigned long physaddr; /* Physical address */ unsigned long virtaddr; /* Virtual address (was ioremapped) */ struct pci_dev *pcidev; /* PCI device */ struct semaphore sem; /* Protective mutex */ unsigned int field_bit; /* Which field (even or odd) is capturing */ wait_queue_head_t waitqueue; /* Wait queue for poll() */ unsigned char regvals[HRT_NUMREGS]; /* Current values of the regs */ unsigned char *framedata; /* The data for a frame for read() */ struct subwindow *win; /* Current region of interest */ unsigned int bytesperline; /* Bytes per raster line */ unsigned int framesize; /* Size of a frame */ unsigned int users; /* Number of processes that have open()'d hrtmem */ unsigned int type; /* Type of the care COLOR or GRAY */ }; /* Multiple card support */ #define HRT_MAX_DEVS 2 static struct hrt hrtdevs[HRT_MAX_DEVS]; static int num_hrtdevs = 0; /* Flag to tell whether the kernel timer is running */ volatile static int timer_running = 0; /* The kernel timer */ static struct timer_list timer; /* 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_AUTHOR("Hemant Banavar & Karthik Parsha"); MODULE_DESCRIPTION("Driver for HRT Pixelsmart 512-8-PCI/640-480-16-PCI/PC104"); MODULE_LICENSE("GPL"); /** * dprintk, hrt_printk - thin wrappers around printk() */ #define dprintk(fmt, arg...) if (debug) \ printk("hrt: " fmt, ## arg) #define hrt_printk(fmt, arg...) printk("hrt: " fmt, ## arg) /* Prototypes */ void inline grab_field(struct hrt *hrtdev, unsigned char *framedata, int parity); void inline grab_color(struct hrt *hrtdev, unsigned char *framedata); void inline grab_sub_field(struct hrt *hrtdev, unsigned char *framedata, int parity, int subFrame); void timer_func(unsigned long ptr); int i2c_set_reg(struct hrt *hrtdev, int reg, unsigned char val); struct hrt_per_file *per_file_init(struct hrt *hrtdev); /* Dr. Baker's I2C prototypes */ static int i2c_init(struct hrt *hrtdev, unsigned long addr, const unsigned char *init_regs); static int hrt_open(struct inode *inode, struct file *file); static int hrt_read(struct file *file, char *buf, size_t count, loff_t * ppos); static int hrt_write(struct inode *inode, struct file *filp, const char *buf,int count); 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); static int hrt_release(struct inode *inode, struct file *file); /** * hrt_fops - This is the fops for the device that mmap's the device * memory directly (the hrtmem device) */ static struct file_operations hrt_fops = { .mmap = hrt_mmap, .open = hrt_open, .release = hrt_release, .ioctl = hrt_ioctl, .read = hrt_read, .write = hrt_write, }; /** * hrt_probe - check that we have a device as the specified address. * Assume the memory region is already mapped. * The address has to be a virtual address mapped to the device I/O space. */ int hrt_probe(unsigned long addr) { unsigned char oldval1, oldval2, oldval3, newval2; unsigned int oldaddr; /* save the old values at the address */ oldval1 = readb(HRT_CONTROL_REG + addr); rmb(); oldaddr = readw(HRT_Y_LOW_REG + addr); rmb(); /* freeze the frame grabbing, immediately */ writeb(0x5B, HRT_CONTROL_REG + addr); wmb(); /* write a new value to the first byte in the first raster/row */ writew(0, HRT_Y_LOW_REG + addr); wmb(); oldval2 = readb(addr); rmb(); writeb(~oldval2, addr); wmb(); /* write oldval2 to the first byte of the next raster/row */ writew(1, HRT_Y_LOW_REG + addr); wmb(); oldval3 = readb(addr); rmb(); writeb(oldval2, addr); wmb(); /* read the value at the previous raster/row */ writew(0, HRT_Y_LOW_REG + addr); wmb(); newval2 = readb(addr); rmb(); /* restore the old values */ writeb(oldval2, addr); wmb(); writew(1, HRT_Y_LOW_REG + addr); wmb(); writeb(oldval3, addr); wmb(); writeb(oldaddr, HRT_Y_LOW_REG + addr); wmb(); writeb(oldval1, HRT_CONTROL_REG + addr); wmb(); return (newval2 == (unsigned char)~oldval2); } /** * 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; /* 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 */ if(i2c_init(hrtdev, hrtdev->virtaddr, saa7110_default_init_regs) < 0) goto error; } else { error: /* Release memory and unmap */ iounmap((void *)virtual_addr); release_mem_region(address, BYTES_NEEDED); return 0; } return 1; } /** * 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_readB 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; static unsigned int subFrame; unsigned long start = jiffies, end; hrtdev->win = &per_file->win; len = min((size_t) (hrtdev->win->height * hrtdev->bytesperline), count); down_interruptible(&hrtdev->sem); old_field = hrtdev->field_bit; if(hrtdev->type == 1) { /* Freez immediately */ writeb(0x5B, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); } /* Grab a field */ if(!GRAB_SUB_FRAME) { grab_field(hrtdev, hrtdev->framedata, old_field); if(hrtdev->type) goto done; } else grab_sub_field(hrtdev, hrtdev->framedata, old_field, subFrame); /* 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; } /* Grab the other field */ if(!GRAB_SUB_FRAME) grab_field(hrtdev, hrtdev->framedata, hrtdev->field_bit); else { grab_sub_field(hrtdev, hrtdev->framedata, hrtdev->field_bit, subFrame); if(subFrame == 3) { subFrame = 0; } else { subFrame++; } } done: if(hrtdev->type == 1) { /* Un Freez */ writeb(0x91, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); } if (copy_to_user(buf, hrtdev->framedata, len)) { up(&hrtdev->sem); return -EFAULT; } up(&hrtdev->sem); end = jiffies; dprintk("Diff = %lu\n", end-start); return len; } static int hrt_write(struct inode *inode, struct file *filp, const char *buf,int count) { struct hrt_per_file *per_file = filp->private_data; struct hrt *hrtdev = per_file->hrtdev; hrtdev->win = &per_file->win; down_interruptible(&hrtdev->sem); /* Freez immediately */ writeb(0x5B, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); if(count > hrtdev->framesize) { count = hrtdev->framesize; } if (copy_from_user(hrtdev->framedata, buf, count)) { up(&hrtdev->sem); return -EFAULT; } /* go live */ writeb(0x91, HRT_CONTROL_REG + hrtdev->virtaddr); wmb(); up(&hrtdev->sem); return count; } void hrt_IRQ_handler(int irq, void *dev_id, struct pt_regs *regs) { 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; /*dprintk("Interrupt handler called and field bit changed\n");*/ /*disable interrupts for now: Else module init dose not conclude*/ writeb(0x00, HRT_INTR_ENABLE_REG + hrtdevs[i].virtaddr); wmb(); wake_up(&hrtdevs[i].waitqueue); } } } /** * 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; wake_up(&hrtdevs[i].waitqueue); } } timer.expires = jiffies + (HZ/100); add_timer(&timer); } /** * 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; per_file->win.width = HRT_WIDTH; per_file->win.startx = 0; per_file->win.starty = 0; return per_file; } /** * 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; u8 tempIRQ; /* 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; } if(dev->device == HRT_DEVICE_ID_COLOR) { HRT_WIDTH=640; HRT_HEIGHT=480; HRT_BYTES_PER_PIXEL=2; HRT_BYTES_PER_LINE = HRT_WIDTH * HRT_BYTES_PER_PIXEL; HRT_FRAMESIZE = HRT_WIDTH * HRT_HEIGHT * HRT_BYTES_PER_PIXEL; } else { HRT_WIDTH=512; HRT_HEIGHT=480; HRT_BYTES_PER_PIXEL=1; HRT_BYTES_PER_LINE = HRT_WIDTH * HRT_BYTES_PER_PIXEL; HRT_FRAMESIZE = HRT_WIDTH * HRT_HEIGHT * HRT_BYTES_PER_PIXEL; } /* 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; } } 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))) { dprintk("Success! We found the card, so now initialize the data structures\n"); goto success; } for (i = 0; i < ARRAY_SIZE(hrt_addresses); i++) if (hrt_try_address(hrtdev, hrt_addresses[i])) goto success; dprintk("NO CARD FOUND\n"); /* Failure; couldn't find it */ return 0; success: /* Success! We found the card, so now initialize the data structures */ /* Set the constants */ if(dev->device == HRT_DEVICE_ID_COLOR) { /*HRT_BYTES_PER_PIXEL = 2 for color. hence 'bytesperline' and 'framesize' double*/ hrtdev->bytesperline = HRT_BYTES_PER_LINE; hrtdev->framesize = HRT_FRAMESIZE; hrtdev->type = 1; /* Color */ } else { hrtdev->bytesperline = HRT_BYTES_PER_LINE; hrtdev->framesize = HRT_FRAMESIZE; hrtdev->type = 0; /* 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 */ hrtdev->framedata = (unsigned char *) vmalloc(HRT_FRAMESIZE); if (hrtdev->framedata == NULL) { hrt_printk(KERN_ERR "Unable to allocate memory\n"); if (!disable_hrtmem) unregister_chrdev(hrtmem_major, "hrtmem"); return -ENOMEM; } pci_read_config_byte(dev,PCI_INTERRUPT_LINE,&tempIRQ); HRT_IRQ = tempIRQ; if(HRT_IRQ > -1) { int result = -1; dprintk("Interrupts found on device : %d; Enabling Interrupt Handler.\n", HRT_IRQ); result = request_irq(HRT_IRQ, hrt_IRQ_handler, SA_INTERRUPT, "hrt IRQ handler", NULL); /*disable interrupts for now*/ writeb(0x00, HRT_INTR_ENABLE_REG + hrtdev->virtaddr); wmb(); } else { /* Set up our timer routine. It should expire 100 times a second (every 1 jiffy on 2.4, every 10 jiffies on 2.6) */ dprintk("Unable to find Interrupts on device; Enabling Timer.\n"); init_timer(&timer); timer.function = timer_func; timer.expires = jiffies + (HZ/100); } return 1; } /** * hrt_dev_cleanup - deallocate per-card resources */ void hrt_dev_cleanup(struct hrt *hrtdev) { if (hrtdev->pcidev) { release_mem_region(hrtdev->physaddr, BYTES_NEEDED); iounmap((unsigned char *)hrtdev->virtaddr); vfree(hrtdev->framedata); if(HRT_IRQ) { free_irq(HRT_IRQ, NULL); } if (timer_running) del_timer_sync(&timer); } } /** * 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; } } 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 - handle ioctl's on hrtmem device */ static int hrt_do_ioctl(struct hrt *hrtdev, unsigned int cmd, unsigned long arg) { if (cmd == 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; } else if (cmd == 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; } else if (cmd == IOC_HRT_SET_WIDTH) { if (arg + hrtdev->win->startx > HRT_WIDTH || arg < 1) return -EINVAL; hrtdev->win->width = arg; hrtdev->bytesperline = arg * HRT_BYTES_PER_PIXEL; return 0; } else if (cmd == IOC_HRT_SET_HEIGHT) { if (arg + hrtdev->win->starty > HRT_HEIGHT || arg < 1) return -EINVAL; hrtdev->win->height = arg; return 0; } else if (cmd == IOC_HRT_SET_STARTX) { if (arg > hrtdev->win->width || arg < 0) return -EINVAL; hrtdev->win->startx = arg; return 0; } else if (cmd == IOC_HRT_SET_STARTY) { if (arg > hrtdev->win->height || arg < 0) return -EINVAL; hrtdev->win->starty = arg; return 0; } else if (cmd == 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 (win.width > HRT_WIDTH || 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; } else if (cmd == IOC_HRT_GET_ROI) { if (copy_to_user((void *) arg, hrtdev->win, sizeof(*hrtdev->win))) { return -EFAULT; } return 0; } else if (cmd == 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); vfree(buf); /* Start the timer again */ timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; return retval; } else if (cmd == IOC_HRT_SET_SUBFRAME) { GRAB_SUB_FRAME = 1; } else if (cmd == IOC_HRT_SET_FULLFRAME) { GRAB_SUB_FRAME = 0; } return -EINVAL; } /** * 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; } /** * 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++; if(HRT_IRQ != -1){ /* Get the interrupts going */ writeb(0x01, HRT_INTR_ENABLE_REG + hrtdevs[minor].virtaddr); } else if (!timer_running) { /* Get the timer going */ timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; } up(&hrtdevs[minor].sem); return 0; } /** * 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; down(&hrtdev->sem); /* Decrement users */ hrtdev->users--; /* Tell the timer to stop if necessary */ if (!hrtdev->users && HRT_IRQ == -1) { del_timer_sync(&timer); timer_running = 0; } /* Free our private data */ vfree(file->private_data); up(&hrtdev->sem); dprintk("hrt_release called\n"); return 0; } /** * hrt_mmap - provides direct access to the card's memory */ static int hrt_mmap(struct file *file, struct vm_area_struct *vma) { printk("<1>mmap called\n"); 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; } /** * cleanup_module - called when we're unloaded of course */ void cleanup_module(void) { int i; /* Release our resouces */ if (!disable_hrtmem) unregister_chrdev(hrtmem_major, "hrtmem"); for (i = 0; hrtdevs[i].pcidev; i++) { hrt_dev_cleanup(&hrtdevs[i]); } } /** * 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,linelen,bufpos=0; char buf[hrtdev->bytesperline]; /*disable interrupts before grabbing a filed*/ writeb(0x00, HRT_INTR_ENABLE_REG + hrtdev->virtaddr); wmb(); linelen = hrtdev->win->width - hrtdev->win->startx; if(hrtdev->type == 1) { for (i = hrtdev->win->starty; i < hrtdev->win->height; i++) { int offset = 0; /* 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 == 512) offset = 512; memcpy_fromio(buf + bufpos++ , hrtdev->virtaddr + hrtdev->win->startx + j + offset,1); memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + 512 + offset,1); } bufpos = 0; memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline , buf, hrtdev->bytesperline); } } else { for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height; i += 2) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->starty) * linelen, hrtdev->virtaddr + hrtdev->win->startx,hrtdev->bytesperline); } } /*enable interrupts after getting a frame*/ writeb(0x01, HRT_INTR_ENABLE_REG + hrtdev->virtaddr); wmb(); } void inline grab_sub_field(struct hrt *hrtdev, unsigned char *framedata, int parity, int subFrame) { int i, j, linelen,bufpos=0; char buf[hrtdev->bytesperline]; linelen = (hrtdev->win->width) - hrtdev->win->startx; /*disable interrupts before grabbing a filed*/ writeb(0x00, HRT_INTR_ENABLE_REG + hrtdev->virtaddr); wmb(); if(hrtdev->type == 1) { /*color*/ if(subFrame == 0) { for (i = hrtdev->win->starty; i < hrtdev->win->height/2; i++) { int offset = 0; /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); for(j = 0; j < hrtdev->win->width/2; j++) { if(j == 512) offset = 512; memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + offset,1); memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + 512 + offset,1); } bufpos = 0; memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline, buf, hrtdev->bytesperline/2); } } else if(subFrame == 1) { bufpos = hrtdev->win->width/2; for (i = hrtdev->win->starty; i < hrtdev->win->height/2; i++) { int offset = 0; /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); for(j = hrtdev->win->width/2; j < hrtdev->win->width; j++) { if(j == 512) offset = 512; memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + offset,1); memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + 512 + offset,1); } bufpos = 0; memcpy(framedata + hrtdev->bytesperline/2 + (i - hrtdev->win->starty) * hrtdev->bytesperline , buf, hrtdev->bytesperline/2); } } else if(subFrame == 2) { bufpos = hrtdev->win->width/2; for (i = hrtdev->win->height/2-1; i < hrtdev->win->height; i++) { int offset = 0; /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); for(j = hrtdev->win->width/2; j < hrtdev->win->width; j++) { if(j == 512) offset = 512; memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + offset,1); memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + 512 + offset,1); } bufpos = 0; memcpy(framedata + (i - hrtdev->win->height/2) * hrtdev->bytesperline + hrtdev->win->height/2 * hrtdev->bytesperline + hrtdev->win->width, buf, hrtdev->bytesperline/2); } } else if(subFrame == 3) { bufpos = 0; for (i = hrtdev->win->height/2; i < hrtdev->win->height; i++) { int offset = 0; /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); for(j = 0; j < hrtdev->win->width/2; j++) { if(j == 512) offset = 512; memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + offset,1); memcpy_fromio(buf + bufpos++, hrtdev->virtaddr + hrtdev->win->startx + j + 512 + offset,1); } bufpos = 0; memcpy(framedata + (hrtdev->win->height/2) * hrtdev->bytesperline + (i - hrtdev->win->height/2) * hrtdev->bytesperline , buf, hrtdev->bytesperline/2); } } } else { /* Gray scale */ if(subFrame == 0) { for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height/2; i += 2) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline, hrtdev->virtaddr + hrtdev->win->startx, hrtdev->bytesperline/2); } } else if(subFrame == 1) { for (i = (!parity) + hrtdev->win->starty; i < hrtdev->win->height/2; i += 2) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline + hrtdev->win->width/2, hrtdev->virtaddr + hrtdev->win->startx + hrtdev->win->width/2, hrtdev->bytesperline/2); } } else if(subFrame == 2) { for (i = (!parity) + hrtdev->win->height/2; i < hrtdev->win->height; i += 2) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->height/2) * hrtdev->bytesperline + hrtdev->win->height/2 * hrtdev->bytesperline+ hrtdev->win->width/2, hrtdev->virtaddr + hrtdev->win->startx + hrtdev->win->width/2, hrtdev->bytesperline/2); } } else if(subFrame == 3) { for (i = (!parity) + hrtdev->win->height/2; i < hrtdev->win->height; i += 2) { /* Move the y register (current line) */ writew(i, hrtdev->virtaddr + HRT_Y_LOW_REG); wmb(); memcpy_fromio(framedata + (i - hrtdev->win->height/2) * hrtdev->bytesperline + (hrtdev->win->height/2) * hrtdev->bytesperline, hrtdev->virtaddr + hrtdev->win->startx, hrtdev->bytesperline/2); } } } /*enable interrupts after getting a frame*/ writeb(0x01, HRT_INTR_ENABLE_REG + hrtdev->virtaddr); wmb(); } /*** * Dr. Baker's I2C routines ***/ /* HRT_CONTROL_OFFSET is the offset of the I2C bus control port 0x2000 = 8192 = 2^13 */ #define HRT_CONTROL_OFFSET 0x2000 #define HRT_CONTROL(addr) (addr + HRT_CONTROL_OFFSET) /* bit 7 at 0x2000 (the HRT512-8 control register) tells whether the CPU is sending data across the I2C bus */ #define I2C_BUSY(addr) (!(readb(HRT_CONTROL(addr)) & 0x80)) /* 0x2001 = 8193 = 2^13+1 */ #define I2C_CONTROL_OFFSET 0x2001 /* The word at 0x2001 provides access to the i2c bus of the HRT512-8 bit 0 = i2c clock signal ("scl" for short) bit 1 = i2c data/address signal ("sda" for short) bit 2 = "go" signal for hardware to generate a clock pulse, once data is ready. This is a write-only bit. It is cleared by hardware a few microseconds after the software sets it high. */ #define I2C_CONTROL(addr) (addr + I2C_CONTROL_OFFSET) #define I2C_POKE(addr,data) { writeb(data,I2C_CONTROL(addr)); wmb();udelay(10); } #define I2C_PEEK(addr) (readb(I2C_CONTROL(addr))) #define I2C_00(addr) { writeb(0,I2C_CONTROL(addr)); wmb(); udelay(10); } #define I2C_10(addr) { writeb(1,I2C_CONTROL(addr)); wmb(); udelay(10);} #define I2C_01(addr) { writeb(2,I2C_CONTROL(addr)); wmb(); udelay(10);} #define I2C_11(addr) { writeb(3,I2C_CONTROL(addr)); wmb(); udelay(10);} /** * i2c_start - sends 'start' command onto i2c bus to be recieved by all * devices on bus. (The HRT512-8 only has the on A/D on the i2c bus.) * This tells all devices on the i2c bus to prepare for an address * phase, i.e., that one of them will be addressed next. * The parameter is the mapped I/O base address of a device. */ static inline void i2c_start(unsigned long addr) { I2C_00(addr); I2C_01(addr); I2C_11(addr); I2C_10(addr); I2C_00(addr); } /** * i2c_stop - tells the selected device (A/D) that transmission has * completed and the bus is free. * The parameter is the mapped I/O base address of a device. */ static inline void i2c_stop(unsigned long addr) { I2C_00(addr); I2C_10(addr); I2C_11(addr); I2C_01(addr); I2C_11(addr); } /** * i2c_send_byte - sends a byte to the A/D. * The parameter addr is the mapped I/O base address of a device. */ static inline int i2c_send_byte(unsigned long addr, unsigned char data) { char bitpos; unsigned char sda, sda_slc; unsigned long timeout; for (bitpos = 7; bitpos >= 0; bitpos--) { /* send a bit to the A/D device; clock must be low at this time. first, get the next bit from data value */ sda = (data & (1 << bitpos)) >> bitpos; sda_slc = sda << 1; /* put data bit on bus and take clock low (b0=0) */ I2C_POKE(addr, sda_slc); /* send the go signal to initiate clock pulse */ I2C_POKE(addr, sda_slc + 4); if (I2C_BUSY(addr)) { timeout = jiffies + HZ / 10; while (I2C_BUSY(addr)) { /* spin */ if (jiffies > timeout) { hrt_printk("I2C bus timeout\n"); goto failure; } } } } I2C_01(addr); /* leave the sda line at high impedance (bit0=0 clock, bit1=1 data/high impedance) */ /* check for ack from A/D after each byte has been sent */ I2C_11(addr); if ((I2C_PEEK(addr) & 2) == 2) { /* error: no ACK after Data Byte transmision */ goto failure; } I2C_01(addr); /* return the bus to high impediance */ return 0; failure: /* This is only a gesture toward cleaning up; if there is a failure we probably have a broken device. */ I2C_01(addr); /* return the bus to high impedance */ i2c_stop(addr); return -1; } /** * i2c_init - set initial register values for the * SAA7110 A/D converter (digitizer), which is on an * I2C bus. If an array of values is given, use it; * otherwise, use the default set of values. See the * comment on the declaration of saa7110_default_init_regs * for the format of the argument. * The parameter addr is the mapped I/O base address of a device. * * NB that the first byte of the array should be the size of the array. */ static int i2c_init(struct hrt *hrtdev, unsigned long addr, const unsigned char *init_regs) { int i, lastreg, len; if (!init_regs) init_regs = saa7110_default_init_regs; /* The length of the array is contained in the first byte */ len = init_regs[0]; init_regs++; i2c_start(addr); /* Tell the digitizer that it has been selected for data transmission. */ if (i2c_send_byte(addr, HRT_AD_DEVICE_ID)) return -1; /* Set the selected device's internal address register pointer to zero. Each subsequent data write will autoincrement the device's address register pointer. */ if (i2c_send_byte(addr, 0)) return -1; lastreg = 0; for (i = 0; i < len; i += 2) { if (init_regs[i] > HRT_NUMREGS) { hrt_printk("Register number 0x%x out of range!\n", init_regs[i]); return -1; } if (init_regs[i] != lastreg) { /* there is a gap in the sequence */ i2c_stop(addr); i2c_start(addr); /* resend device address byte */ if (i2c_send_byte(addr, HRT_AD_DEVICE_ID)) return -1; lastreg = init_regs[i]; /* send new register address byte */ if (i2c_send_byte(addr, init_regs[i])) return -1; } lastreg++; /* send register value */ if (i2c_send_byte(addr, init_regs[i + 1])) return -1; /* Keep track of the values in the registers */ hrtdev->regvals[init_regs[i]] = init_regs[i + 1]; } i2c_stop(addr); return 0; } /** * i2c_set_reg - set an I2C register, reg, to value val */ int i2c_set_reg(struct hrt *hrtdev, int reg, unsigned char val) { unsigned long addr = hrtdev->virtaddr; i2c_start(addr); i2c_send_byte(addr, HRT_AD_DEVICE_ID); i2c_send_byte(addr, reg); i2c_send_byte(addr, val); i2c_stop(addr); hrtdev->regvals[reg] = val; return val; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0) EXPORT_NO_SYMBOLS; #endif