/* * File Name : hrt.c * * This is a device driver for the High Resolution Technologies * Pixelsmart 512-8 frame grabber. * Copyright (C) 2003, Florida State University * * This is free software; you can redistribute it and/or modify it under * terms of the GNU General Public License as published by the Free Soft- * ware Foundation, Version 2. This software is distributed in the hope * that it will be useful, but WITH OUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have * received a copy of the GNU General Public License distributed with this * software; see file COPYING. If not, write to the Free Software Foundation, * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * This is based on two drivers, originally written by two * teams of students: * 1) Brett W. Thompson, Gilberto Morejon, and Alex Rudnick * 2) Veena Adityan and Arthi Gokarn * It was the final project for the course CIS 5930 Linux Kernel & * Device Driver Programming, at the Florida State University, * Summer 2003. * I2C initialization and probing were written by Dr. T. P. Baker. * Merging the two drivers was started by Dr. T. P. Baker and * finished by Brett W. Thompson and Gilberto Morejon. * Streaming portion adapted from Bill Dirks' v4l2cap.c by * Veena Adityan and Arthi Gokarn. */ #include #include #include #include #include #include #include #include "hrt.color.h" #include unsigned long my_virtual_address; volatile int frame_irq = -1; volatile int intr_count = 0; /* Controls the user can set via V4L ioctl's */ #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 /** * 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 */ }; /* 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 #ifdef HAVE_V4L2 /** * struct stream_buffer - represents a single buffer for streaming */ struct stream_buffer { struct v4l2_buffer vidbuf; /* For the V4L ioctl's */ int fields_grabbed; /* Two-bit pair of flags indicating which fields have been grabbed (i.e., 00 = no fields, 11 = both) */ struct list_head node; /* Connection into queues */ int requested; /* Set by REQBUF ioctl */ unsigned char *vaddress; /* This is the pointer to the vmalloc()'d memory into which the actual frame goes */ int vma_refcount; /* Reference count */ }; /** * struct stream_buffer_queue - FIFO of stream_buffer structs */ struct stream_buffer_queue { struct stream_buffer *streambuf; /* Pointer to node data */ struct list_head head; /* Start of the queue */ rwlock_t lock; /* Lock to protect the queue */ int length; /* How many nodes are on the queue */ }; #endif /** * 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 */ #ifdef HAVE_V4L2 struct video_device video_dev; int streaming; /* Flag for streaming mode */ int numbufs; /* Number of buffers for streaming */ struct stream_buffer *streambufs; /* Array of buffers */ struct stream_buffer_queue capture_list; /* List of queued buffers */ struct stream_buffer_queue done_list; /* List of filled buffers */ /* Veena and Arthi's streaming */ struct v4l2_format clientfmt; struct v4l2_captureparm capture; int stream_buffers_mapped; struct stream_buffer stream_buf[MAX_CAPTURE_BUFFERS]; int stream_buffers_requested; #endif }; /* variable to check whether the card is color or not */ unsigned short type_of_device; /* 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; #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)"); #ifdef HAVE_V4L2 MODULE_PARM(video_minor, "i"); MODULE_PARM_DESC(video_minor, "Device's minor number (default: dynamic)"); MODULE_PARM(exclusive, "i"); MODULE_PARM_DESC(exclusive, "Only one process can open the device (default: off)"); MODULE_PARM(disable_streaming, "i"); MODULE_PARM_DESC(disable_streaming, "Don't return V4L2_CAP_STREAMING; recommended for xawtv (default: off)"); #endif MODULE_AUTHOR("Brett W. Thompson, Gilberto Morejon, Veena Adityan, Arthi Gokarn, Alex Rudnick"); MODULE_DESCRIPTION("Driver for HRT Pixelsmart 512-8-PCI"); MODULE_LICENSE("GPL"); /*interupt handler function */ void frame_interrupt(int , void *, struct pt_regs *); /** * 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); /*for the color device */ void inline cgrab_field(struct hrt *hrtdev, unsigned char *framedata, int parity); 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); #ifdef HAVE_V4L2 /* Prototypes for file_operations */ static int hrt_open(struct inode *inode, struct file *file); static int hrt_release(struct inode *inode, struct file *file); static int hrt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static unsigned int hrt_poll(struct file *file, poll_table * wait); #endif /* hrt_read is used for both devices */ static int hrt_read(struct file *file, char *buf, size_t count, loff_t * ppos); /* Prototypes for direct mmap device */ static int hrt_direct_open(struct inode *inode, struct file *file); static int hrt_direct_release(struct inode *inode, struct file *file); static int hrt_direct_mmap(struct file *file, struct vm_area_struct *vma); static int hrt_direct_do_ioctl(struct hrt *hrtdev, unsigned int cmd, unsigned long arg); static int hrt_direct_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); /** * hrt_direct_fops - This is the fops for the device that mmap's the device * memory directly (the hrtmem device) */ static struct file_operations hrt_direct_fops = { .mmap = hrt_direct_mmap, .open = hrt_direct_open, .release = hrt_direct_release, .ioctl = hrt_direct_ioctl, .read = hrt_read, }; /** * hrt_fops - fops for /dev/videoN */ #ifdef HAVE_V4L2 static struct file_operations hrt_fops = { .owner = THIS_MODULE, .open = hrt_open, .release = hrt_release, .ioctl = hrt_ioctl, .read = hrt_read, .llseek = no_llseek, .poll = hrt_poll, }; /* 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 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 /** * 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. */ /** * hrt_try_address - return 0 if there is a device * at the specified address; else return an error code. * 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; dprintk("hrt_try_address: 0x%lx\n", address); /* Reserve our memory range */ if (!request_mem_region(address, BYTES_NEEDED, "hrt")) { hrt_printk("I/O memory already in use\n"); return -EBUSY; } 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; my_virtual_address = virtual_addr; /* Try to initialize the A/D converter */ result = i2c_init(hrtdev,hrtdev->virtaddr,saa7110_default_init_regs); /* made a change i the upper line ****** init_hrt_i2c(hrtdev)*/ if (result < 0) goto failure; else return result; } else { goto failure; } failure: /* Release memory and unmap */ iounmap((void *)virtual_addr); release_mem_region(address, BYTES_NEEDED); return -ENODEV; } /** * 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; } /** * v4l1_ioctls - used for printing out ioctl codes in hrt_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" }; /** * hrt_do_ioctl - handles ioctl's on /dev/videoN */ static int hrt_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) { /*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; 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; if (f->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; 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; } } /** * hrt_ioctl - handler for ioctl's on /dev/videoN. Tries the private * ioctl's first, via hrt_direct_do_ioctl(). */ static int hrt_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_direct_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_do_ioctl); up(&hrtdev->sem); return ret; } #endif /** * 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; if(type_of_device == HRT_DEVICE_ID_COLOR ) { writeb(HRT_FREEZE_NEXT_CMD, hrtdev->virtaddr + HRT_CONTROL_REG); /* Grab a field */ cgrab_field(hrtdev, hrtdev->framedata, old_field); } else grab_field(hrtdev, hrtdev->framedata, old_field); /* 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(type_of_device == HRT_DEVICE_ID_COLOR ) { cgrab_field(hrtdev, hrtdev->framedata, hrtdev->field_bit); writeb(HRT_LIVE_CMD, hrtdev->virtaddr + HRT_CONTROL_REG); } else grab_field(hrtdev, hrtdev->framedata, hrtdev->field_bit); if (copy_to_user(buf, hrtdev->framedata, len)) { up(&hrtdev->sem); return -EFAULT; } up(&hrtdev->sem); return len; } #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) { 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 /** * 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); } /** * 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; } #ifdef HAVE_V4L2 /** * hrt_open - called upon an open() on /dev/videoN */ int hrt_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; } /** * hrt_release - basically just stops the timer because it isn't necessary * if no process has /dev/videoN open */ static int hrt_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; } /** * hrt_poll - handles poll() and select() on /dev/videoN */ unsigned int hrt_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 /** * 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, interrupt_result; struct hrt *hrtdev = NULL; u8 myirq; type_of_device = dev->device; /* 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; } } 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 writeb(HRT_LIVE_CMD, HRT_CONTROL_REG + hrtdev->virtaddr); pci_read_config_byte(dev,PCI_INTERRUPT_LINE,&myirq); frame_irq = myirq; if (frame_irq >= 0) { interrupt_result = request_irq((unsigned int)frame_irq, frame_interrupt,SA_INTERRUPT, "hrtmem",NULL); if (interrupt_result) { printk(KERN_INFO "hrt: can't get assigned irq %i\n", frame_irq); frame_irq = -1; } else { /* actually enable the interrupt */ if (my_virtual_address != hrtdev->virtaddr) printk(KERN_INFO "hrt: addresses don't match\n"); wmb(); writeb(0x01, 0x2005 + hrtdev->virtaddr); } } else printk("dint get the required line\n"); /* Set the constants */ hrtdev->bytesperline = HRT_BYTES_PER_LINE; hrtdev->framesize = HRT_FRAMESIZE; /* 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"); #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 - 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); } } /** ** This is the interrupt handler function **/ void frame_interrupt(int irq, void *dev_id, struct pt_regs *regs) { intr_count++; /*disable the interrupt */ writeb(0x00,0x2005 + my_virtual_address); } /** * 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_direct_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_direct_do_ioctl - handle ioctl's on hrtmem device */ static int hrt_direct_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; } return -EINVAL; } /** * hrt_direct_ioctl - wrapper to pass ioctl's on hrtmem to * hrt_direct_do_ioctl() */ static int hrt_direct_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_direct_do_ioctl(hrtdev, cmd, arg); up(&hrtdev->sem); return ret; } /** * hrt_direct_open - checks minor number range, sets private_data, * starts timer if necessary, increments users */ static int hrt_direct_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++; /* Get the timer going */ if (!timer_running) { timer.expires = jiffies + (HZ/100); add_timer(&timer); timer_running = 1; } up(&hrtdevs[minor].sem); dprintk("hrt_direct_open called\n"); return 0; } /** * hrt_direct_release - free our per-fd data, stop the timer if users is 0 */ static int hrt_direct_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--; /* Tell the timer to stop if necessary */ #ifdef HAVE_V4L2 if (!vfl->users && !hrtdev->users) { #else if (!hrtdev->users) { #endif del_timer_sync(&timer); timer_running = 0; } /* Free our private data */ vfree(file->private_data); up(&hrtdev->sem); dprintk("hrt_direct_release called\n"); return 0; } /** * hrt_direct_mmap - provides direct access to the card's memory */ static int hrt_direct_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; } /** * cleanup_module - called when we're unloaded of course */ void cleanup_module(void) { int i; writeb(0x0,my_virtual_address + 0x2005);/*disable the interrupt*/ free_irq(frame_irq,NULL); printk("<1>intr_count == %d\n",intr_count); /* Release our resouces */ if (!disable_hrtmem) unregister_chrdev(hrtmem_major, "hrtmem"); for (i = 0; hrtdevs[i].pcidev; i++) { hrt_dev_cleanup(&hrtdevs[i]); } } /** "This is for gray device" * 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; /* 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; #if 0 if (linelen % 4) linelen = linelen + (4 - linelen % 4); dprintk("linelength = %d starty = %d startx = %d bytesperline = %d\n", linelen, hrtdev->win->starty, hrtdev->win->startx, hrtdev->bytesperline); #endif 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, linelen); } } /** " This is for the color device" * 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 cgrab_field(struct hrt *hrtdev, unsigned char *framedata, int parity) { int i; int colcount,bufcount=0; int linelen; char buffer[hrtdev->bytesperline]; /* XXX: not sure why this is needed. Without it the image is skewed on values for startx that are not multiples of 4. */ linelen = hrtdev->win->width - hrtdev->win->startx; #if 0 if (linelen % 4) linelen = linelen + (4 - linelen % 4); dprintk("linelength = %d starty = %d startx = %d bytesperline = %d\n", linelen, hrtdev->win->starty, hrtdev->win->startx, hrtdev->bytesperline); #endif 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(); for (colcount = 0 ; colcount < 512; colcount++) { memcpy_fromio(buffer + bufcount++ , hrtdev->virtaddr + hrtdev->win->startx + colcount,1); memcpy_fromio(buffer + bufcount++, hrtdev->virtaddr + hrtdev->win->startx + colcount + 512,1); } bufcount = 0; memcpy(framedata + (i - hrtdev->win->starty) * hrtdev->bytesperline , buffer,hrtdev->bytesperline); } } /*** * 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 */ hrt_printk("No I2C ACK\n"); 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