/* * hrt_dev.c * WARNING: ------- The current version of this code is incomplete. Only the device probing and initialization code is believed to be correct. This is a bare-bones device driver for the PixelSmart512-8 (greyscale) and Video Gala (color) framegrabbers. For more information on these devices, see http://www.pixelsmart.com. Unlike earlier drivers for these cards from FSU, this driver does *not* attempt to support the Video4Linux (V4L) API, nor does it use the generic Linux I2C support code. We have intentionally removed all dependencies of V4L, because: (1) This board is so basic that most of the V4L API (e.g., tuning, audio) is not applicable.. (2) At this time, the V4L API seems sufficiently unstable to cause us worries about backward and forward compatibility. That is, we don't want to have to worry about differences in V4L headers and kernel modules breaking this driver. (3) Users of this card for embedded applications do not want to waste kernel memory for the V4L code if they are not using it. (4) We don't know of any demand for using these cards with existing V4L applications. We have intentionally removed all dependecies on the Linux generic I2C core (i2c.h and i2c-dev.h) because: (1) The Linux generic i2c support code added both logical complexity and code size, and did not work reliably for this card. (2) The Linux I2C core has changed enough over time, and we expect it will change further. We don't want to have to worry about such differences between versions breking this driver. (3) this card has enough idiosyncracies that there is very little potential reuse of code between this I2C bus and other I2C applications. (4) Users of this card for embedded applications do not want to waste kernel memory for the extra code. ----------------------------------------- This version does only supports blocking reads. I deleted the support for streaming, because it needs to be done differently for single-ported and dual-ported cards, and I did not have time to work out the best way to allow for both cases within one framework. Blocking read operation: 1. Initiate digitization, and start the timer/handler if necessary. 2. Wait for a frame to become available and frozen. 3. Copy frame to user memory. Timer handler/Frame Interrupt Handler: Keep track of fields, and when a full frame is captured, freeze the card and unblock the reader at step (2) above. Locking rules for direct I/O access to the device: We cannot allow concurrent access to the device, lest it be confused by sequences of commands that are interleaved. Mutual exclusion is guaranteed as follows: (1) The holder of dev->spinlock can do I/O on the device. (2) The IRQ/timer handler uses spin_lock/unlock, and normally scheduled code uses spin_lock/unlock_irqsave/restore. (3) Since we don't want to keep interrupts disabled the whole time we copy data from the device, we use an additional variable, dev->is_locked, to keep the timer handler from touching the device while copying is going on from a process context. */ #ifndef __KERNEL__ #define __KERNEL__ #endif #include #include /* udelay */ #include /* error codes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hrt.h" #define HRT_DEBUG #define HRT_ERROR_MSG(args...) do{printk("<1>hrt: "); printk(args);\ printk("\n");}while(0) /********************* * Module Parameters * *********************/ #ifndef module_param #define hrt_parm(name, pstr, type, perm) MODULE_PARM(name, pstr) #else #define hrt_parm(name, pstr, type, perm) module_param(name, type, perm) #endif static int major_number = 82; hrt_parm(major_number, "i", int, 0); #ifndef HRT_MAX_DEVICES #define HRT_MAX_DEVICES 4 #endif #ifndef HRT_IO_SIZE #define HRT_IO_SIZE 0x4000 #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 }; /*************************** * Card-specific constants * ***************************/ #define HRT_VENDOR_ID 0x0004 #define HRT_DEVICE_ID_GREY 0x0404 #define HRT_DEVICE_ID_COLOR 0x0408 /* Number of registers on the board- checked in i2c_init() */ #define HRT_SAA7110_MAXREG 0x34 /** * HRT device descriptor */ typedef struct { int num; int state; /* minor device number, index in hrt_devices[] */ unsigned long virt_addr; unsigned long phys_addr; /* cannot be opened again until released */ /* spinlock and is_locked control access to the device */ spinlock_t spinlock; int is_locked; wait_queue_head_t wait_queue; struct timer_list timer; struct tasklet_struct tasklet; int timer_active; /* timer_active != 0 iff timer or irq is active */ int irq; /* irq != 0 if board supports interrupts = -irq if handler not installed = irq if handler is installed */ volatile int irq_count; volatile int i2c_bits; /* last values written to i2c */ char saa7110_registers[HRT_SAA7110_MAXREG+1]; /* video data format, and dependent values */ int mode; int rows, cols, bytes_per_pixel; int win_row, win_col, win_width, win_height; /* current field being digitized */ int field; /* how many field changes we are waiting for */ int field_changes; /* the device is either frozen or being frozen */ int is_frozen; #ifdef CONFIG_PCI struct pci_dev *pci_dev; #endif } hrt_t; /* values for field hrt_t->state */ #define HRT_UNINITIALIZED_STATE 1 #define HRT_INITIALIZING_STATE 2 #define HRT_CLOSED_STATE 4 #define HRT_OPEN_STATE 8 #define HRT_FINALIZING_STATE 16 /* values for field hrt_t->mode */ #define HRT_DUAL_PORTED_MODE 1 #define HRT_COLOR_MODE 2 #define HRT_STREAMING_MODE 4 #define HRT_IRQ_MODE 8 #define HRT_ISA_MODE 16 /* values for global hrt_module_state */ #define HRT_MODULE_UNINITIALIZED_STATE 1 #define HRT_MODULE_INITIALIZING_STATE 2 #define HRT_MODULE_INITIALIZED_STATE 4 #define HRT_MODULE_FINALIZING_STATE 8 int hrt_module_state = HRT_MODULE_UNINITIALIZED_STATE; int hrt_nonpci_devices = 0; /* number of non-pci devices detected */ /* non-pci devices have lower numbers */ int hrt_num_devices = 0; /* total number of devices detected */ #define HRT_IS_PCI(X) ((X) >= hrt_nonpci_devices) /* ??? consider dynamically allocating the device objects as they are probed and detectg, and using a linked list here instead of an array */ hrt_t hrt_devices[HRT_MAX_DEVICES]; #ifdef HRT_DEBUG /****************************** * optional debugging support * ******************************/ #define HRT_DEBUG_LEVEL 3 #define HRT_CHECK(dev,states,msg) \ {if (dev->state & ~(states))\ {HRT_DEBUG_MSG(1, "* unexpected state 0x%2x (%s)", dev->state, msg);}} #define HRT_MODULE_CHECK(states,msg) \ {if (hrt_module_state & ~(states))\ {HRT_DEBUG_MSG(1, "* unexpected state 0x%2x (%s)", hrt_module_state, msg);}} #define HRT_DEBUG_MSG(level,args...) {if (level <= HRT_DEBUG_LEVEL)\ {printk("<1>hrt * "); printk(args); printk("\n");}} struct proc_dir_entry *hrt_proc_read_entry = NULL; #define write_buf(args...) \ do { \ n = snprintf(buf, count, args); \ buf += n; \ count -= n; \ } while(0) int hrt_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int n, i; int ips, tps, rps; char *org_buf = buf; ips = 0; tps = 0; rps = 0; /* dont excede count bytes when writing to buf */ /* just write to buf as a normal ptr to a file */ write_buf("dev HZ/10 int timer read int/s timer/s read/s\n"); for (i=0; iis_frozen = 1;\ iowrite8(HRT_FREEZE_NEXT_CMD, (void *) (dev->virt_addr + HRT_CONTROL_REG)) #define hrt_freeze_immediate(dev) \ dev->is_frozen = 1;\ iowrite8(HRT_FREEZE_IMM_CMD, (void *) (dev->virt_addr + HRT_CONTROL_REG)) #define hrt_go_live(dev) \ dev->is_frozen = 0;\ iowrite8(HRT_LIVE_CMD, (void *) (dev->virt_addr + HRT_CONTROL_REG)) #define hrt_get_field(dev) \ ioread8((void *) (dev->virt_addr + HRT_CONTROL_REG)) & 0x1 #define hrt_i2c_delay() udelay(5) /* Unique I2C bus address of the SAA7110 (A/D) device */ #define HRT_AD_DEVICE_ID (128+16+8+4) 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 */ }; /* * sda = set data bit on I2C bus * to the value given by parameter high */ static inline void hrt_sda(hrt_t *dev, unsigned long addr, int high) { if (high) dev->i2c_bits |= HRT_I2C_SDA; else dev->i2c_bits &= ~HRT_I2C_SDA; iowrite8(dev->i2c_bits, (void *) I2C_CONTROL(addr)); wmb(); } /* * scl = set clock bit on I2C bus * to the value given by parameter high */ static inline void hrt_scl(hrt_t *dev, unsigned long addr, int high) { if (high) dev->i2c_bits |= HRT_I2C_SCL; else dev->i2c_bits &= ~HRT_I2C_SCL; iowrite8(dev->i2c_bits, (void *) I2C_CONTROL(addr)); wmb(); } /* * sda_scl = set data and clock bits on I2C bus * to the values given by parameters sda_high and scl_high */ static inline void hrt_sda_scl(hrt_t *dev, unsigned long addr, int sda_high, int scl_high) { if (sda_high) dev->i2c_bits |= HRT_I2C_SDA; else dev->i2c_bits &= ~HRT_I2C_SDA; if (scl_high) dev->i2c_bits |= HRT_I2C_SCL; else dev->i2c_bits &= ~HRT_I2C_SCL; iowrite8(dev->i2c_bits, (void *) I2C_CONTROL(addr)); wmb(); } /* * sda_read = read data bit from I2C control register */ static inline int hrt_sda_read(unsigned long addr) { char c = ioread8((void *) I2C_CONTROL(addr)); return (c & HRT_I2C_SDA); } /* * hrt_i2c_start = start I2C data transmission */ static inline void hrt_i2c_start(hrt_t *dev, unsigned long addr) { hrt_sda_scl(dev, addr, 0, 0); hrt_i2c_delay(); hrt_sda(dev, addr, 1); hrt_i2c_delay(); hrt_scl(dev, addr, 1); hrt_i2c_delay(); hrt_sda(dev, addr, 0); hrt_i2c_delay(); hrt_scl(dev, addr, 0); } /* * hrt_i2c_stop = end I2C data transmission */ static inline void hrt_i2c_stop(hrt_t *dev, unsigned long addr) { hrt_sda_scl(dev, addr, 0, 0); hrt_i2c_delay(); hrt_scl(dev, addr, 1); hrt_i2c_delay(); hrt_sda(dev, addr, 1); hrt_i2c_delay(); hrt_scl(dev, addr, 0); hrt_scl(dev, addr, 1); } /* * hrt_i2c_send_bit */ static int hrt_i2c_send_bit(hrt_t *dev, unsigned long addr, unsigned char bit) { unsigned long timeout; if (bit) hrt_sda(dev, addr, 1); else hrt_sda(dev, addr, 0); hrt_i2c_delay(); iowrite8(dev->i2c_bits | 0x04, (void *) I2C_CONTROL(addr)); wmb(); hrt_i2c_delay(); if (I2C_BUSY(addr)) { timeout = jiffies + HZ/10; while (I2C_BUSY(addr)) { if (jiffies > timeout) { HRT_ERROR_MSG("i2c bus timeout"); return -1; } } } return 0; } /* * hrt_i2c_send_byte */ static int hrt_i2c_send_byte(hrt_t *dev, unsigned long addr, unsigned char data) { char bitpos, bit; for(bitpos = 0; bitpos < 8; bitpos++) { bit = (data & 0x80) >> 7; data <<= 1; if(hrt_i2c_send_bit(dev, addr, bit)) goto failure; } hrt_i2c_delay(); hrt_sda_scl(dev, addr, 1, 0); hrt_scl(dev, addr, 1); /* leave clock high */ udelay(10); if (hrt_sda_read(addr)) { HRT_ERROR_MSG("no i2c ack"); goto failure; } hrt_sda_scl(dev, addr, 1, 0); return 0; failure: hrt_sda_scl(dev, addr, 1, 0); hrt_i2c_stop(dev, addr); return -1; } /* * hrt_i2c_init_registers * * sends a sequence of values to the A/D converter device over the I2C bus. * For an example of the format of "sequence" see the declaration of * saa7110_default_init_regs[] above. The first byte is the number of * data bytes that follow. The rest of the sequence is a series of pairs * of a register number followed by a value. It is better if the values * are sorted in increasing order of register number, but the sequence may * have gaps and need not be in order. */ int hrt_i2c_init_registers(hrt_t *dev, const char *sequence) { unsigned long addr; int i, len, cur_reg; len = (int) (*sequence++); addr = dev->virt_addr; if (len <= 2) { HRT_ERROR_MSG("invalid register initialization sequence"); return -1; } hrt_i2c_start(dev, addr); /* here we select the A/D Device on the i2c bus * that should pay attention to the following bytes */ if (hrt_i2c_send_byte(dev, dev->virt_addr, HRT_AD_DEVICE_ID)) { HRT_ERROR_MSG("send_byte failed"); return -1; } /* start at the first register and increment along the way */ if (hrt_i2c_send_byte(dev, dev->virt_addr, cur_reg = sequence[0])) { HRT_ERROR_MSG("send_byte failed(2)"); return -1; } for(i = 0; i < len; i += 2, sequence += 2) { char reg = sequence[0]; char data = sequence[1]; if (reg > HRT_SAA7110_MAXREG) { HRT_ERROR_MSG("register %02X out of range!", reg); return -1; } if (reg != cur_reg) { /* we're going to an entirely different register */ hrt_i2c_stop(dev, addr); hrt_i2c_start(dev, addr); /* select the chip/device on the bus */ if (hrt_i2c_send_byte(dev, dev->virt_addr, HRT_AD_DEVICE_ID)) { HRT_ERROR_MSG("send_byte failed(3)"); return -1; } /* select the register */ if (hrt_i2c_send_byte(dev, dev->virt_addr, cur_reg = reg)) { HRT_ERROR_MSG("send_byte failed(4)"); return -1; } } if (hrt_i2c_send_byte(dev, dev->virt_addr, data)) { HRT_ERROR_MSG("send_byte failed(5)"); return -1; } dev->saa7110_registers[cur_reg++] = data; } /* free the i2c bus */ hrt_i2c_stop(dev, addr); return 0; } int hrt_i2c_init_device(hrt_t *dev) { int result = 0; HRT_DEBUG_MSG(2, "hrt_i2c_init_device entered" " (virt_addr = %08X, addr = %08X)", (unsigned) dev->virt_addr, (unsigned) dev->phys_addr); result = hrt_i2c_init_registers(dev, saa7110_default_init_regs); if (result) { HRT_ERROR_MSG("hrt_i2c_init_registers failed %d", result); return result; } HRT_DEBUG_MSG(2, "hrt_i2c_init_device returning %d", result); return result; } /*************************** * buffering, data copying * ***************************/ /* * hrt_copy_window * * copy entire window from video device to buffer * assume frame is frozen */ int hrt_copy_window(hrt_t *dev, char * buf) { unsigned long y_addr; unsigned long in_addr; int win_width, win_height, win_end, win_col, win_row; int i, j, y; int dev_line_length; y_addr = dev->virt_addr + HRT_Y_LOW_REG; win_width = dev->win_width; win_height = dev->win_height; win_row = dev->win_row; win_col = dev->win_col; in_addr = dev->virt_addr + win_col; if (~(dev->mode & HRT_COLOR_MODE)) { dev_line_length = 512; win_end = dev->win_row + dev->win_height; for (y = win_row * dev_line_length; y < win_end; y++) { iowrite16(y, (void *) y_addr); wmb(); if (copy_to_user(buf, (void *) in_addr, win_width)) { HRT_ERROR_MSG("copy_to_user failed %lx %lx %d %d (1)", (unsigned long) in_addr, (unsigned long) buf, win_width, y); return -EFAULT; } buf += win_width; } } else if (dev->mode & HRT_COLOR_MODE) { dev_line_length = 2048; win_end = dev->win_row + dev->win_height; for (y = win_row * dev_line_length; y < win_end; y++) { iowrite16(y, (void *) y_addr); wmb(); if (copy_to_user(buf, (void *) in_addr, win_width)) { HRT_ERROR_MSG("copy_to_user failed (2)"); return -EFAULT; } /* rearrange pixels into logical order */ for (i = 0; i < win_width; i++) { j = i + win_col; if (j < 512) { buf[i] = buf[j]; buf[i+1] = buf[j+512]; } else { buf[i] = buf[j+512]; buf[i+1] = buf[j+1024]; } } buf += win_width; } } else { HRT_ERROR_MSG("unsupported format %x", dev->mode); } return 0; } /************************************* * device initialization and cleanup * *************************************/ static inline void hrt_irq_enable(hrt_t *dev) { int val; val = ioread8((void *)(dev->virt_addr + HRT_IRQ_ENABLE)); val |= 0x1; iowrite8(val, (void *) (dev->virt_addr + HRT_IRQ_ENABLE)); } static inline void hrt_irq_disable(hrt_t *dev) { int val; val = ioread8((void *)(dev->virt_addr + HRT_IRQ_ENABLE)); val &= ~0x1; iowrite8(val, (void *) (dev->virt_addr + HRT_IRQ_ENABLE)); } void hrt_tasklet(unsigned long); void hrt_timer_init(hrt_t* dev); void hrt_timer_cleanup(hrt_t* dev); int hrt_timer_activate(hrt_t* dev); void hrt_timer_deactivate(hrt_t* dev); void hrt_cleanup(hrt_t* dev) { HRT_DEBUG_MSG(1, "shutting down hrt device %d at 0x%lx", dev->num, dev->phys_addr); HRT_MODULE_CHECK (HRT_MODULE_FINALIZING_STATE | HRT_MODULE_INITIALIZING_STATE, "1"); HRT_CHECK (dev, HRT_INITIALIZING_STATE | HRT_CLOSED_STATE | HRT_FINALIZING_STATE, "2"); dev->state = HRT_FINALIZING_STATE; /* restore device to an inactive state */ hrt_freeze_next(dev); /* deactivate and remove interrupt handler or timer */ hrt_timer_cleanup (dev); if (dev->phys_addr) { HRT_DEBUG_MSG(2, "release_mem_region 0x%lx, 0x%lx", (unsigned long)dev, (unsigned long) dev->phys_addr); release_mem_region(dev->phys_addr, HRT_IO_SIZE); dev->phys_addr = 0; } if (dev->virt_addr) { HRT_DEBUG_MSG(2, "iounmap %lx", (unsigned long) dev->virt_addr); iounmap((void *)dev->virt_addr); dev->virt_addr = 0; } #ifdef CONFIG_PCI if (dev->pci_dev) { pci_disable_device(dev->pci_dev); dev->pci_dev = NULL; } #endif dev->state = HRT_UNINITIALIZED_STATE; } void hrt_lock_init(hrt_t * dev) { spin_lock_init(&dev->spinlock); dev->is_locked = 0; } int hrt_trylock(hrt_t * dev) { spin_lock(&dev->spinlock); if (dev->is_locked) { spin_unlock(&dev->spinlock); return 1; } dev->is_locked = 1; spin_unlock(&dev->spinlock); return 0; } void hrt_lock(hrt_t * dev) { unsigned long flags; spin_lock_irqsave(&dev->spinlock, flags); if (dev->is_locked) { HRT_ERROR_MSG("locking an already-locked device"); } dev->is_locked = 1; spin_unlock_irqrestore(&dev->spinlock, flags); } void hrt_unlock(hrt_t * dev) { spin_lock(&dev->spinlock); if (!dev->is_locked) { HRT_ERROR_MSG("unlocking unlocked device"); } dev->is_locked = 0; spin_unlock(&dev->spinlock); } int hrt_init(unsigned long phys_address, struct pci_dev * pci_dev) { hrt_t * dev = &hrt_devices[hrt_num_devices]; int i, result; char *bus, *color, *ported; unsigned int old_control, old_y_high, old_y_low; unsigned char val1, val2; unsigned long virt_address; HRT_DEBUG_MSG(1, "probing device at 0x%lx", phys_address); dev = &hrt_devices[hrt_num_devices]; memset(dev, 0, sizeof(hrt_t)); dev->state = HRT_INITIALIZING_STATE; dev->num = hrt_num_devices; hrt_lock_init (dev); tasklet_init(&dev->tasklet, hrt_tasklet, (unsigned long) dev); /* reserve the I/O address space for this device */ if (!request_mem_region(phys_address, HRT_IO_SIZE, "hrt")) { HRT_ERROR_MSG("I/O memory at %lx already in use", (unsigned long) phys_address); return -EBUSY; } dev->phys_addr = phys_address; /* map the device's I/O space into kernel memory */ virt_address = (unsigned long) ioremap_nocache(phys_address, HRT_IO_SIZE); if (!virt_address) { HRT_ERROR_MSG("couldn't remap io memory!!"); hrt_cleanup(dev); return -ENODEV; } dev->virt_addr = virt_address; #ifdef CONFIG_PCI /* pci-specific processing */ if (pci_dev) { pci_set_drvdata (pci_dev, dev); dev->pci_dev = pci_dev; dev->irq = -pci_dev->irq; dev->mode &= ~HRT_ISA_MODE; /* make certain IRQ is disabled before enabling device, or else we may get an IRQ we are not prepared to handle? */ hrt_irq_disable(dev); if (pci_enable_device(pci_dev)) { HRT_ERROR_MSG("pci_enable_device failed"); hrt_cleanup(dev); return -EIO; } } #endif /* find out whether there is an hrt device at this address, and which type of device it is */ /* save the values we are about to modify */ old_control = ioread8((void *)(HRT_CONTROL_REG + virt_address)); old_y_low = ioread8((void *)(HRT_Y_LOW_REG + virt_address)); old_y_high = ioread8((void *)(HRT_Y_HIGH_REG + virt_address)); /* freeze the frame grabbing, immediately */ iowrite8(0x5B, (void *) (HRT_CONTROL_REG + virt_address)); /* complement pixel (0,0) */ iowrite16(0, (void *) (HRT_Y_LOW_REG + virt_address)); iowrite16(0, (void *) (HRT_Y_HIGH_REG + virt_address)); val1 = ioread8((void *) virt_address); iowrite8(~val1, (void *) virt_address); /* write old value of pixel (0,0) to (1,0) */ iowrite16(1, (void *) (HRT_Y_LOW_REG + virt_address)); iowrite8(val1, (void *) virt_address); /* read the value at the previous raster/row */ iowrite16(0, (void *) (HRT_Y_LOW_REG + virt_address)); val2 = ioread8((void *) virt_address); if (val2 != (unsigned char)~val1) { HRT_DEBUG_MSG(1, "no hrt device at address 0x%lx", virt_address); /* restore the old values, and hope we did no damage to some other device at this address; this is pretty poor, since if there is another device at that address the effects of these writes could be harmful; ideally, there should be a way to identify the devices that only uses read operations */ iowrite8(val1, (void *) virt_address); iowrite8(old_y_low, (void *) (HRT_Y_LOW_REG + virt_address)); iowrite8(old_y_high, (void *) (HRT_Y_HIGH_REG + virt_address)); iowrite8(old_control, (void *) (HRT_CONTROL_REG + virt_address)); hrt_cleanup(dev); return -ENODEV; } /* test whether we have a color or greyscale card: the color frame buffer has line of 2048 = 0x400 pixels; greyscale has only 512 pixels/line, so the memory mapped row of frame buffer memory should wrap around at 0x200 and 0x400 on a greyscale card but should be good through 0x400 on a color card. */ iowrite8(HRT_FREEZE_IMM_CMD, (void *) (HRT_CONTROL_REG + virt_address)); iowrite16(0, (void *) (HRT_Y_LOW_REG + virt_address)); iowrite16(0, (void *) (HRT_Y_HIGH_REG + virt_address)); val1 = ioread8((void *)(0x400 + virt_address)); iowrite8(~val1, (void *) (0x400 + virt_address)); /* in case greyscale addresses wrap around, refresh the value at offset zero */ iowrite8(val1, (void *) (virt_address)); val2 = ioread8((void *)(0x400 + virt_address)); if (val2 == (unsigned char) ~val1) { dev->mode |= HRT_COLOR_MODE; /* infer the frame geometry from the device type */ HRT_DEBUG_MSG(1, "COLOR device detected"); dev->rows = 480; dev->cols = 640; dev->bytes_per_pixel = 2; } else { HRT_DEBUG_MSG(1, "GREYSCALE device detected"); dev->cols = 512; /* is for NTSC, 512 for PAL */ dev->rows = 480; dev->bytes_per_pixel = 1; } /* try to initialize the device */ result = hrt_i2c_init_device(dev); if (result) { HRT_ERROR_MSG("hrt_i2c_init_device %d failed",result); hrt_cleanup(dev); return -ENODEV; } hrt_go_live(dev); /* delete this once driver is debugged */ /* find out whether the device has dual-ported memory*/ /* freeze the image */ hrt_freeze_immediate(dev); /* write zeros to horizontal raster line 500, a line that the A/D unit does not modify */ iowrite16(0, (void *) (dev->virt_addr + HRT_Y_HIGH_REG)); iowrite16(500, (void *) (dev->virt_addr + HRT_Y_LOW_REG)); for (i = 0; i < 512; i++) { iowrite8(0, (void *)(dev->virt_addr + i)); } /* set capturing mode */ hrt_go_live(dev); /* try to overwrite the line with ones */ for (i = 0; i < 512; i++) { iowrite8(255, (void *) (dev->virt_addr + i)); } /* freeze the image */ hrt_freeze_immediate(dev); /* read back the line; if some of the pixels are still zero the memory is not dual ported */ result = 1; for (i = 0; i < 512; i++) { if (!ioread8((void *)(dev->virt_addr + i))) result = 0; } if (result) { HRT_DEBUG_MSG(1, "seems to be dual ported"); dev->mode |= HRT_DUAL_PORTED_MODE; } dev->mode |= HRT_ISA_MODE; #ifdef CONFIG_PCI if ((pci_dev->device == HRT_DEVICE_ID_COLOR) != ((dev->mode & HRT_COLOR_MODE) == HRT_COLOR_MODE)) HRT_ERROR_MSG("PCI and probed types don't match"); #endif /* discover whether this device supports interrupts */ hrt_timer_init(dev); if (dev->mode & HRT_IRQ_MODE) { dev->irq_count = 0; hrt_irq_enable(dev); hrt_go_live(dev); mdelay(20); hrt_freeze_immediate(dev); hrt_irq_disable(dev); if (dev->irq_count) { HRT_DEBUG_MSG(1, "counted %d irqs in 20 ms", dev->irq_count); HRT_ERROR_MSG("devices supports IRQs"); } else { HRT_DEBUG_MSG(1, "no irqs in 10 ms"); dev->irq = 0; dev->mode &= ~HRT_IRQ_MODE; } } /* describe this device for the log */ if (dev->mode & HRT_ISA_MODE) bus = "ISA"; else bus = "PCI"; if (dev->mode & HRT_COLOR_MODE) color = "COLOR"; else color = "GREYSCALE"; if (dev->mode & HRT_DUAL_PORTED_MODE) ported = "DUAL"; else ported = "SINGLE"; HRT_ERROR_MSG("found device %d at 0x%lx", dev->num, dev->phys_addr); HRT_ERROR_MSG("%s %s with %s-ported memory", bus, color, ported); if (dev->mode & HRT_IRQ_MODE) HRT_ERROR_MSG("using IRQ %d", dev->irq); init_waitqueue_head(&dev->wait_queue); /* set default window size to cover the entire frame buffer */ HRT_DEBUG_MSG(1, "setting width, cols = %d", dev->cols); dev->win_row = 0; dev->win_width = dev->cols; dev->win_col = 0; dev->win_height = dev->rows; dev->field = -1; hrt_num_devices++; dev->state = HRT_CLOSED_STATE; return 0; } int hrt_isa_init(void) { int i; for (i = 0; i < ARRAY_SIZE(hrt_addresses); i++) hrt_init(hrt_addresses[i], NULL); hrt_nonpci_devices = hrt_num_devices; return 0; } void hrt_isa_cleanup(void) { int i; for (i=0; ivirt_addr + HRT_CONTROL_REG)) & 0x4; rmb(); return r; } irqreturn_t hrt_irq_handler(int irq, void* dev_id, struct pt_regs* regs) { hrt_t* dev = dev_id; if (!hrt_irq_pending(dev)) goto none; /* acknowledge the interrupt */ hrt_irq_disable(dev); dev->irq_count++; /* do not try to do any I/O on the device here, or else risk race with last scheduled tasklet */ tasklet_schedule(&dev->tasklet); /* enable the next interrupt */ hrt_irq_enable(dev); #ifdef IRQ_HANDLED return IRQ_HANDLED; #endif none: #ifdef IRQ_NONE return IRQ_NONE; #endif } void hrt_timer_handler(unsigned long data) { hrt_t* dev = (hrt_t*)data; if (dev->timer_active) { tasklet_schedule(&dev->tasklet); dev->timer.expires = jiffies + HZ/100; add_timer(&dev->timer); } } void hrt_irq_init(hrt_t* dev) { HRT_DEBUG_MSG(2, "hrt_irq_init"); dev->irq_count = 0; if (dev->irq == 0) return; if ((dev->irq > 0) || (dev->mode & HRT_IRQ_MODE)) { HRT_ERROR_MSG("irq handler already installed"); return; } hrt_irq_disable(dev); /* insurance */ if (request_irq(-dev->irq, hrt_irq_handler, SA_SHIRQ, "hrt", (void*)dev)) { HRT_ERROR_MSG("unable to reserve IRQ"); dev->irq = 0; } else { dev->irq = -dev->irq; dev->mode |= HRT_IRQ_MODE; } } void hrt_irq_cleanup(hrt_t* dev) { HRT_DEBUG_MSG(2, "hrt_irq_cleanup"); if (dev->mode & HRT_IRQ_MODE) { free_irq(dev->irq, dev); dev->mode &= ~HRT_IRQ_MODE; } } void hrt_timer_init(hrt_t* dev) { HRT_DEBUG_MSG(1, "hrt_timer_init"); HRT_CHECK(dev, HRT_INITIALIZING_STATE, "4"); if (dev->timer_active) { HRT_ERROR_MSG("irq or timer already active"); } hrt_irq_init(dev); if (!(dev->mode & HRT_IRQ_MODE)) { if (dev->timer.function) { HRT_ERROR_MSG("timer already initialized"); return; } HRT_DEBUG_MSG(1, "installing timer"); init_timer(&dev->timer); dev->timer.function = hrt_timer_handler; dev->timer.data = (unsigned long)dev; } } void hrt_timer_cleanup(hrt_t* dev) { HRT_DEBUG_MSG(1, "hrt_timer_cleanup"); HRT_CHECK(dev, HRT_FINALIZING_STATE, "3"); hrt_timer_deactivate (dev); hrt_irq_cleanup(dev); dev->timer.function = NULL; } void hrt_timer_deactivate(hrt_t* dev) { HRT_DEBUG_MSG(1, "hrt_timer_deactivate"); if (dev->timer_active) { dev->timer_active = 0; if (dev->mode & HRT_IRQ_MODE) hrt_irq_disable(dev); else del_timer_sync(&dev->timer); tasklet_disable(&dev->tasklet); } } /* hrt_time_activate ----------------- starts a task to monitor the state of the device. This may be driven by a timer or by an interrupt from the device. */ int hrt_timer_activate(hrt_t* dev) { int result = 0; HRT_DEBUG_MSG(1, "hrt_timer_activate"); if (dev->timer_active) { HRT_ERROR_MSG("irq or timer already active"); return 0; } dev->timer_active = 1; if (dev->mode & HRT_IRQ_MODE) { HRT_DEBUG_MSG(1, "enabling irq"); hrt_irq_enable(dev); } else { if (!dev->timer.function) { HRT_ERROR_MSG("timer not initialized"); return -1; } dev->timer.expires = jiffies + HZ/100; add_timer(&dev->timer); } return result; } /* hrt_tasklet ----------- is called in response to either a timer interrupt or an interrupt generated by the device */ void hrt_tasklet(unsigned long data) { int field; hrt_t *dev = (hrt_t *) data; if (hrt_trylock (dev)) { /* device is busy */ /* could add a wakeup mechanism here */ return; } field = hrt_get_field (dev); if (dev->field == -1) { /* just started; don't know previous state */ dev->field = field; goto done; } if (field != dev->field) { /* field has changed */ if (dev->field_changes == 1) { HRT_DEBUG_MSG(1, "field changes going to 0 %d", dev->num); wake_up_interruptible (&dev->wait_queue); } if (dev->field_changes) dev->field_changes--; } /* .... this code is incomplete Eventually, there should be code here to keep track of how many fields have been digitized, and arrange for buffering the data */ done: hrt_unlock(dev); } /******************************** * basic file operations (fops) * ********************************/ int hrt_open (struct inode *inode, struct file *file); int hrt_release(struct inode *inode, struct file *file); int hrt_read (struct file *file, char *buf, size_t count, loff_t * ppos); int hrt_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); int hrt_mmap (struct file *file, struct vm_area_struct *vma); static struct file_operations hrt_fops = { .owner = THIS_MODULE, .open = hrt_open, .release = hrt_release, .read = hrt_read, .ioctl = hrt_ioctl, .mmap = hrt_mmap, }; /** * hrt_open * * checks minor number range, sets private_data, * starts timer if necessary, increments users */ int hrt_open(struct inode *inode, struct file *file) { hrt_t* dev; unsigned int minor; int result = 0; minor = MINOR(inode->i_rdev); if (minor >= HRT_MAX_DEVICES) return -ENODEV; dev = hrt_devices + minor; HRT_DEBUG_MSG(1, "opening device %d at addr %lX", minor, (unsigned long)dev); if (dev->state != HRT_CLOSED_STATE) { HRT_DEBUG_MSG(2, "already open"); return -EBUSY; } HRT_DEBUG_MSG(2, "setting private data"); file->private_data = (void*)dev; dev->state = HRT_OPEN_STATE; hrt_go_live(dev); /* FIXME: shift responsibility for buffer allocation to open (here) and deallocation to the release operation; only do this later, when everything else is working. */ HRT_DEBUG_MSG(2, "installing timer/irq"); result = hrt_timer_activate(dev); return result; } /* hrt_release should not need any lock, because it is only called by the system, on last close; at that point there should be no possibility of more calls that touch this device, unless they are from a timer or interrrupt handler */ int hrt_release(struct inode *inode, struct file *file) { hrt_t* dev = file->private_data; if (dev->state != HRT_OPEN_STATE) return -ENODEV; dev->state = HRT_CLOSED_STATE; hrt_timer_deactivate(dev); /* above should wait for active tasklet to finish */ return 0; } int hrt_read(struct file *file, char *buf, size_t count, loff_t * ppos) { hrt_t* dev = file->private_data; int max_count, result; HRT_DEBUG_MSG(3, "hrt_read_proc (1) count = %d", count); if (dev->state != HRT_OPEN_STATE) return -ENODEV; hrt_lock(dev); dev->field_changes = 2; if (dev->is_frozen) { hrt_go_live (dev); hrt_unlock(dev); if (wait_event_interruptible(dev->wait_queue, dev->field_changes == 0)) { return -ERESTARTSYS; } hrt_lock(dev); dev->field_changes = 2; } hrt_freeze_next(dev); hrt_unlock(dev); if (wait_event_interruptible(dev->wait_queue, dev->field_changes == 0)) { return -ERESTARTSYS; } HRT_DEBUG_MSG(3, "hrt_read_proc (3) bpp = %d width = %d height = %d", dev->bytes_per_pixel, dev->win_width, dev->win_height); max_count = dev->win_width * dev->win_height * dev->bytes_per_pixel; if (count > max_count) count = max_count; hrt_lock(dev); HRT_DEBUG_MSG(3, "hrt_read_proc (4) count = %d", count); if ((result = hrt_copy_window(dev, buf))) { HRT_DEBUG_MSG(3, "hrt_copy_window failed %d", result); hrt_unlock(dev); return -EFAULT; } hrt_unlock(dev); HRT_DEBUG_MSG(3, "hrt_read_proc (5)"); return count; } /* * ioctl operations */ int hrt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int result = 0, iarg = *((int*)arg); hrt_t* dev = file->private_data; switch (cmd) { case IOC_HRT_FREEZE_FRAME: HRT_DEBUG_MSG(2, "IOC_HRT_FREEZE_FRAME: called"); hrt_freeze_next(dev); break; case IOC_HRT_GO_LIVE: HRT_DEBUG_MSG(2, "IOC_HRT_GO_LIVE: called"); hrt_go_live(dev); break; case IOC_HRT_WIN_SET_WIDTH: HRT_DEBUG_MSG(2, "IOC_HRT_WIN_SET_WIDTH: called with arg %d", iarg); dev->win_width = iarg; break; case IOC_HRT_WIN_SET_HEIGHT: HRT_DEBUG_MSG(2, "IOC_HRT_WIN_SET_HEIGHT: called with arg %d", iarg); dev->win_height = iarg; break; case IOC_HRT_WIN_SET_X: HRT_DEBUG_MSG(2, "IOC_HRT_WIN_SET_X: called with arg %d", iarg); dev->win_col = iarg; break; case IOC_HRT_WIN_SET_Y: HRT_DEBUG_MSG(2, "IOC_HRT_WIN_SET_Y: called with arg %d", iarg); dev->win_row = iarg; break; default: result = -ENOSYS; break; } return result; } int hrt_mmap (struct file *file, struct vm_area_struct *vma) { return -ENOSYS; } #ifdef CONFIG_PCI /*************** * PCI support * ***************/ MODULE_DEVICE_TABLE(pci, hrt_pci_tbl); int hrt_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id); void hrt_pci_remove (struct pci_dev *pci_dev); static struct pci_device_id hrt_pci_tbl[] __devinitdata = { {PCI_DEVICE(HRT_VENDOR_ID, HRT_DEVICE_ID_GREY)}, {PCI_DEVICE(HRT_VENDOR_ID, HRT_DEVICE_ID_COLOR)}, {0,} }; static struct pci_driver hrt_pci_driver = { .name = "hrt", .id_table = hrt_pci_tbl, .probe = hrt_pci_probe, .remove = __devexit_p(hrt_pci_remove) }; int hrt_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { HRT_DEBUG_MSG(2, "probing pci device"); /* check that this is a type of device we support */ if ((pci_dev->device != HRT_DEVICE_ID_COLOR) && (pci_dev->device != HRT_DEVICE_ID_GREY)) { HRT_ERROR_MSG("unknown device type 0x%hx", pci_dev->device); return -ENODEV; } if (hrt_num_devices >= HRT_MAX_DEVICES) { HRT_ERROR_MSG("need to increase HRT_MAX_DEVICES"); return -ENOMEM; } return hrt_init(pci_resource_start(pci_dev, 0), pci_dev); } /* since these cards are not intended to be hot-pluggable, we do not try to reuse hrt_t objects; we just use the remove operation for removal of the device driver module */ void hrt_pci_remove (struct pci_dev *pci_dev) { hrt_t *dev; HRT_DEBUG_MSG(2, "hrt_pci_remove"); dev = (hrt_t *) pci_get_drvdata (pci_dev); hrt_cleanup(dev); pci_disable_device(pci_dev); } void hrt_pci_init(void) { HRT_DEBUG_MSG(2, "pci_register_driver"); pci_register_driver(&hrt_pci_driver); } void hrt_pci_cleanup(void) { HRT_DEBUG_MSG(2, "pci_unregister_driver"); pci_unregister_driver(&hrt_pci_driver); } #else /* no PCI support */ void hrt_pci_init(void) { } void hrt_pci_cleanup(void) { } #endif /************************************* * module initialization and cleanup * *************************************/ int hrt_major_number = 0; void hrt_cleanup_module(void) { HRT_MODULE_CHECK (HRT_MODULE_INITIALIZING_STATE | HRT_MODULE_INITIALIZED_STATE, "6"); hrt_module_state = HRT_MODULE_FINALIZING_STATE; HRT_DEBUG_MSG(2, "hrt_cleanup_module"); hrt_isa_cleanup(); hrt_pci_cleanup(); if (hrt_major_number) unregister_chrdev(hrt_major_number, "hrt"); hrt_debug_cleanup(); hrt_module_state = HRT_MODULE_UNINITIALIZED_STATE; } int hrt_init_module(void) { int result = 0; hrt_module_state = HRT_MODULE_INITIALIZING_STATE; hrt_debug_init(); result = register_chrdev(major_number, "hrt", &hrt_fops); if (result < 0) { HRT_ERROR_MSG("failed to register char device %d", major_number); goto failed; } /* use major number returned by system if no major number is specified by this module */ if (major_number == 0) hrt_major_number = result; else hrt_major_number = major_number; HRT_ERROR_MSG("module initializing with major number %d", hrt_major_number); memset(hrt_devices, 0, sizeof(hrt_devices)); /* detect ISA/PC104 devices, and PCI devices that are jumpered to use the ISA/PC104 I/O address space */ hrt_isa_init(); /* now detect regular PCI devices */ hrt_pci_init(); if (hrt_num_devices == 0) { HRT_ERROR_MSG("no hrt devices detected"); hrt_module_state = HRT_MODULE_UNINITIALIZED_STATE; return -ENODEV; } else { HRT_ERROR_MSG("found %d hrt devices", hrt_num_devices); } hrt_module_state = HRT_MODULE_INITIALIZED_STATE; return 0; failed: hrt_cleanup_module(); HRT_DEBUG_MSG(2, "returning from failed init_module() with result %d", result); hrt_module_state = HRT_MODULE_UNINITIALIZED_STATE; return result; } module_init(hrt_init_module); module_exit(hrt_cleanup_module); MODULE_LICENSE("GPL");