/* This device driver is actually 3 drivers in one. * * 1) The first driver is responsible for moving image data from the card * via iomem on the PCI bus. * * 2) The second is the I2C "Adapter" driver. This driver handles * sending data onto the card's on-board I2C bus. You could think * about this similarly to the "USB host" component of the USB * subsystem. This driver is very simple, since most of the work is * already done in the kernel. We pretty much just need to let the * kernel know how to push bits onto the bus through PCI iomem calls. * * 3) The third is the I2C "Device" driver. This is the driver that * actually communicates with the card's Phillips SAA7110 * analog-to-digital converter chip. We don't actually read any image * data from the card (as mentioned above), but we do need to access * and modify the SAA7110's status registers. So this part uses the * I2C API (i2c_inb/i2c_outb etc.) to communicate with the chip. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HRT_DRIVER_NAME "hrt" #define HRT_DRIVER_VERSION 0x1 MODULE_DESCRIPTION("High Resolution Technology Video Framegrabber"); MODULE_AUTHOR("Peter Gavin, Eduardo Parisi, Evan Hollander"); MODULE_LICENSE("Dual BSD/GPL"); enum { HRT_IO_OFFSET_VIDEO_BUFFER = 0x0000, HRT_IO_VIDEO_BUFFER_SIZE_PS512_8 = 0x0200, HRT_IO_VIDEO_BUFFER_SIZE_VIDEO_GALA = 0x0680, HRT_IO_OFFSET_CONTROL = 0x2000, HRT_IO_OFFSET_I2C = 0x2001, HRT_IO_OFFSET_VIDEO_Y_LSB = 0x2002, HRT_IO_OFFSET_VIDEO_Y_MSB = 0x2003, HRT_IO_OFFSET_RCA_INPUT_POLARITY = 0x2004, HRT_IO_REGION_SIZE = 0x4000, }; enum { HRT_CONTROL_BIT_FIELD_ID = 1 << 0, HRT_CONTROL_BIT_EXTERNAL_TRIGGER_INPUT = 1 << 1, HRT_CONTROL_BIT_Y7 = 1 << 2, HRT_CONTROL_BIT_Y8 = 1 << 3, HRT_CONTROL_BIT_VERTICAL_DISPLAY_ENABLE = 1 << 4, HRT_CONTROL_BIT_VERTICAL_SYNC = 1 << 5, HRT_CONTROL_BIT_GRAB_OK = 1 << 6, HRT_CONTROL_BIT_I2C_DONE = 1 << 7, HRT_CONTROL_CMD_FREEZE_IMM = 0x5b, HRT_CONTROL_CMD_FREEZE_NEXT = 0x99, HRT_CONTROL_CMD_LIVE = 0x91, }; enum { HRT_VIDEO_BW_HEIGHT = 480, HRT_VIDEO_BW_WIDTH = 512, HRT_VIDEO_COLOR_HEIGHT = 480, HRT_VIDEO_COLOR_WIDTH = 640, HRT_VIDEO_COLOR_BREAK_PT = 0x200, }; enum { HRT_VIDEO_FRAMERATE_NUMERATOR_COLOR = 30000, HRT_VIDEO_FRAMERATE_DENOMINATOR_COLOR = 1001, HRT_VIDEO_FRAMERATE_NUMERATOR_BW = 30000, HRT_VIDEO_FRAMERATE_DENOMINATOR_BW = 1000, }; /* the address 0x9c is in the chip spec sheet. it needs to be shifted one bit right because 0x9c is the value actually transmitted on the bus. the actual address occupies the high 7 bits of the byte. */ #define HRT_I2C_SAA7110_ADDR (0x9c >> 1) // or 0x9e #define HRT_SAA7110_INIT_REG(reg, val) { .addr = HRT_I2C_SAA7110_ADDR, .len = 2, .buf = ((__u8[]) { (reg), (val) }) } static struct i2c_msg hrt_saa7110_init_regs[] = { HRT_SAA7110_INIT_REG ( 0x00, 0x4c ), /* increment delay (IDEL) */ HRT_SAA7110_INIT_REG ( 0x01, 0x3c ), /* HSY begin 50 Hz */ HRT_SAA7110_INIT_REG ( 0x02, 0x0d ), /*HSY stop 50 Hz */ HRT_SAA7110_INIT_REG ( 0x03, 0xef ), /*HCL begin 50 Hz */ HRT_SAA7110_INIT_REG ( 0x04, 0xbd ), /*HCL stop 50 Hz */ HRT_SAA7110_INIT_REG ( 0x05, 0xf0 ), /*HSY after PHI1 50 Hz */ HRT_SAA7110_INIT_REG ( 0x06, 0x00 ), /*luminance control */ HRT_SAA7110_INIT_REG ( 0x07, 0x00 ), /*hue control */ HRT_SAA7110_INIT_REG ( 0x08, 0xf8 ), /*colour killer threshold QUAM (PAL/NTSC) */ HRT_SAA7110_INIT_REG ( 0x09, 0xf8 ), /*colour killer threshold SECAM */ HRT_SAA7110_INIT_REG ( 0x0A, 0x60 ), /*PAL switch sensitivity */ HRT_SAA7110_INIT_REG ( 0x0B, 0x50 ), /*SECAM switch sensitivity */ HRT_SAA7110_INIT_REG ( 0x0C, 0x00 ), /*gain control chrominance */ HRT_SAA7110_INIT_REG ( 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) */ HRT_SAA7110_INIT_REG ( 0x0E, 0x18 ), /*I/O and clock control */ HRT_SAA7110_INIT_REG ( 0x0F, 0x90 ), /*control #1 */ HRT_SAA7110_INIT_REG ( 0x10, 0x00 ), /*control #2 */ HRT_SAA7110_INIT_REG ( 0x11, 0x2c ), /*chrominance gain reference */ HRT_SAA7110_INIT_REG ( 0x12, 0x7f ), /*chrominance saturation */ HRT_SAA7110_INIT_REG ( 0x13, 0x5e ), /*luminance contrast */ HRT_SAA7110_INIT_REG ( 0x14, 0x42 ), /*HSY begin 60 Hz */ HRT_SAA7110_INIT_REG ( 0x15, 0x1a ), /*HSY stop 60 Hz */ HRT_SAA7110_INIT_REG ( 0x16, 0xff ), /*HCL begin 60 Hz */ HRT_SAA7110_INIT_REG ( 0x17, 0xda ), /*HCL stop 60 Hz */ HRT_SAA7110_INIT_REG ( 0x18, 0xf0 ), /*HSY after PHI1 60 Hz */ HRT_SAA7110_INIT_REG ( 0x19, 0x9b ), /*luminance brightness */ /* 0x1A - not used 0x1B - not used 0x1C - not used 0x1D - not used 0x1E - not used 0x1F - not used */ HRT_SAA7110_INIT_REG ( 0x20, 0x7c ), /*analog control #1 */ HRT_SAA7110_INIT_REG ( 0x21, 0x03 ), /*analog control #2 */ HRT_SAA7110_INIT_REG ( 0x22, 0xd2 ), /*mixer control #1 */ HRT_SAA7110_INIT_REG ( 0x23, 0x41 ), /*clamping level control 21 */ HRT_SAA7110_INIT_REG ( 0x24, 0x80 ), /*clamping level control 22 */ HRT_SAA7110_INIT_REG ( 0x25, 0x41 ), /*clamping level control 31 */ HRT_SAA7110_INIT_REG ( 0x26, 0x80 ), /*clamping level control 32 */ HRT_SAA7110_INIT_REG ( 0x27, 0x4f ), /*gain control #1 */ HRT_SAA7110_INIT_REG ( 0x28, 0xfe ), /*white peak control */ HRT_SAA7110_INIT_REG ( 0x29, 0x01 ), /*sync bottom control */ HRT_SAA7110_INIT_REG ( 0x2A, 0xcf ), /*gain control analog #2 */ HRT_SAA7110_INIT_REG ( 0x2B, 0x0f ), /*gain control analog #3 */ HRT_SAA7110_INIT_REG ( 0x2C, 0x83 ), /*mixer control #2 */ HRT_SAA7110_INIT_REG ( 0x2D, 0x01 ), /*integration value gain */ HRT_SAA7110_INIT_REG ( 0x2E, 0x81 ), /*vertical blanking pulse set */ HRT_SAA7110_INIT_REG ( 0x2F, 0x03 ), /*vertical blanking pulse reset */ HRT_SAA7110_INIT_REG ( 0x30, 0x60 ), /*ADCs gain control */ HRT_SAA7110_INIT_REG ( 0x31, 0x71 ), /*mixer control #3 */ HRT_SAA7110_INIT_REG ( 0x32, 0x02 ), /*integration value white peak */ HRT_SAA7110_INIT_REG ( 0x33, 0x8c ), /*mixer control #4 */ HRT_SAA7110_INIT_REG ( 0x34, 0x03 ), /*gain update level */ }; enum hrt_thread_state { HRT_THREAD_STOPPED, HRT_THREAD_STOPPING, HRT_THREAD_RETURNED, HRT_THREAD_RUNNING, }; struct hrt_sg_to_addr { int pos; struct scatterlist *sg; }; struct hrt_buffer { struct videobuf_buffer vidbuf; struct hrt_sg_to_addr *to_addr; }; struct hrt_dev { struct list_head list_head; struct video_device video_device; struct pci_dev *pci_dev; struct list_head active_bufs; struct list_head queued_bufs; wait_queue_head_t waitq; struct task_struct *kthread; enum hrt_thread_state thread_state; struct i2c_adapter i2c_adapter; struct i2c_client i2c_saa7110_client; struct i2c_algo_bit_data i2c_algo_bit_data; __u8 i2c_signal_state; struct videobuf_queue vidbufq; struct videobuf_queue_ops vidbufq_ops; unsigned long users; unsigned long phys_address; unsigned long base_address; unsigned short video_height; unsigned short video_width; unsigned long video_memory_limit; struct semaphore thread_lock; // lock between a user context and the thread struct semaphore user_lock; // lock between user contexts atomic_t busy; int color : 1; int dual_ported : 1; }; #define HRT_BUSY_INIT ((atomic_t) ATOMIC_INIT (1)) /* hrt_dev_busy: if the device is not busy, (atomically) mark it busy and return zero. otherwise, return nonzero */ static inline int hrt_busy (struct hrt_dev *hrt_dev) { if (!atomic_dec_and_test (&hrt_dev->busy)) { atomic_inc (&hrt_dev->busy); return 1; } else { return 0; } } /* hrt_dev_unbusy: mark the device as not busy */ #define hrt_unbusy(hrt_dev) (atomic_inc (&(hrt_dev)->busy)) #define hrt_check_busy(hrt_dev) (atomic_read (&(hrt_dev)->busy)) LIST_HEAD (hrt_devs); DECLARE_MUTEX (hrt_devs_lock); static struct hrt_dev * hrt_dev_get (int minor) { struct list_head *pos; struct hrt_dev *hrt_dev = NULL; down (&hrt_devs_lock); list_for_each (pos, &hrt_devs) { hrt_dev = list_entry (pos, struct hrt_dev, list_head); if (hrt_dev->video_device.minor == minor) break; } up (&hrt_devs_lock); return hrt_dev; } static inline u8 hrt_get_control (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); mb (); return ioread8 ((void *) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL); } static inline int hrt_check_control (struct hrt_dev *hrt_dev, int flags) { BUG_ON (!hrt_dev); return (hrt_get_control (hrt_dev) & flags) == flags; } #define hrt_other_field(field) ((field) == V4L2_FIELD_TOP ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP) static inline enum v4l2_field hrt_get_current_field (struct hrt_dev *hrt_dev) { if (hrt_check_control (hrt_dev, HRT_CONTROL_BIT_FIELD_ID)) return V4L2_FIELD_TOP; else return V4L2_FIELD_BOTTOM; } static inline void hrt_video_live (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); mb (); iowrite8 (HRT_CONTROL_CMD_LIVE, (void*) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL); mb (); } static inline void hrt_video_freeze_immediate (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); iowrite8 (HRT_CONTROL_CMD_FREEZE_IMM, (void*) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL); wmb (); } static inline void hrt_video_freeze_next (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); iowrite8 (HRT_CONTROL_CMD_FREEZE_NEXT, (void*) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL); wmb (); } static inline void hrt_set_video_y (struct hrt_dev *hrt_dev, u16 y) { u8 y_lsb = y & 0xff; u8 y_msb = y >> 8; BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); iowrite8 (y_lsb, (void*) hrt_dev->base_address + HRT_IO_OFFSET_VIDEO_Y_LSB); iowrite8 (y_msb, (void*) hrt_dev->base_address + HRT_IO_OFFSET_VIDEO_Y_MSB); wmb (); } static inline u16 hrt_get_video_y (struct hrt_dev *hrt_dev) { u8 y_lsb; u8 y_msb; BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); y_lsb = ioread8 ((void*) hrt_dev->base_address + HRT_IO_OFFSET_VIDEO_Y_LSB); y_msb = ioread8 ((void*) hrt_dev->base_address + HRT_IO_OFFSET_VIDEO_Y_MSB); return ((u16) y_msb << 8) | (u16) y_lsb; } static int hrt_generic_init (unsigned long phys_address, struct hrt_dev **hrt_dev_out); static void hrt_generic_cleanup (struct hrt_dev *hrt_dev); /* I2C Bus initialization */ /******************************************************************************/ /* the I2C "Adapter" for our card DRIVER 2 */ /* this was chosen so as not to conflict with any other IDs in i2c-id.h */ #define I2C_HW_HRT_PIXELSMART 0x100000 static int hrt_i2c_client_register (struct i2c_client *i2c_client) { /* TODO */ printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return 0; } #define HRT_I2C_SCL_BIT (1 << 0) #define HRT_I2C_SDA_BIT (1 << 1) #define HRT_I2C_SEND_BIT (1 << 2) static inline void hrt_i2c_get_signal (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); hrt_dev->i2c_signal_state = ioread8((void *) hrt_dev->base_address + HRT_IO_OFFSET_I2C); #if 0 { const char *sda = hrt_dev->i2c_signal_state & HRT_I2C_SDA_BIT ? "HIGH" : "LOW "; const char *scl = hrt_dev->i2c_signal_state & HRT_I2C_SCL_BIT ? "HIGH" : "LOW "; const char *send = hrt_dev->i2c_signal_state & HRT_I2C_SEND_BIT ? "HIGH" : "LOW "; printk (KERN_ALERT "hrt: I2C got signal: sda=%s scl=%s send=%s\n", sda, scl, send); } #endif } static inline void hrt_i2c_set_signal (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); #if 0 { const char *sda = hrt_dev->i2c_signal_state & HRT_I2C_SDA_BIT ? "HIGH" : "LOW "; const char *scl = hrt_dev->i2c_signal_state & HRT_I2C_SCL_BIT ? "HIGH" : "LOW "; const char *send = hrt_dev->i2c_signal_state & HRT_I2C_SEND_BIT ? "HIGH" : "LOW "; printk (KERN_ALERT "hrt: I2C set signal: sda=%s scl=%s send=%s\n", sda, scl, send); } #endif iowrite8 (hrt_dev->i2c_signal_state, (void*) hrt_dev->base_address + HRT_IO_OFFSET_I2C); wmb (); /* hrt_i2c_get_signal (hrt_dev); */ } /* * sda = set data bit on I2C bus * to the value given by parameter high * taken from Dr. Baker */ static void hrt_i2c_setsda(struct hrt_dev *hrt_dev, int state) { BUG_ON (!hrt_dev); /* hrt_i2c_get_signal (hrt_dev); */ if (state) hrt_dev->i2c_signal_state |= HRT_I2C_SDA_BIT; else hrt_dev->i2c_signal_state &= ~HRT_I2C_SDA_BIT; hrt_i2c_set_signal (hrt_dev); } /* * scl = set clock bit on I2C bus * to the value given by parameter high * taken from Dr. Baker */ static void hrt_i2c_setscl(struct hrt_dev *hrt_dev, int state) { BUG_ON (!hrt_dev); /* hrt_i2c_get_signal (hrt_dev); */ if (state) hrt_dev->i2c_signal_state |= HRT_I2C_SCL_BIT; else hrt_dev->i2c_signal_state &= ~HRT_I2C_SCL_BIT; hrt_i2c_set_signal (hrt_dev); } /* * sda_read = read data bit from I2C control register * taken from Dr. Baker */ static int hrt_i2c_getsda (struct hrt_dev *hrt_dev) { int retval; BUG_ON (!hrt_dev); hrt_i2c_get_signal (hrt_dev); retval = !!(hrt_dev->i2c_signal_state & HRT_I2C_SDA_BIT); return retval; } /* * scl_read = read data bit from I2C control register * taken from Dr. Baker */ static int hrt_i2c_getscl(struct hrt_dev *hrt_dev) { int retval; BUG_ON (!hrt_dev); hrt_i2c_get_signal (hrt_dev); retval = !!(hrt_dev->i2c_signal_state & HRT_I2C_SCL_BIT); return retval; } #if 0 static void hrt_i2c_write_stop (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); printk (KERN_ALERT "hrt: I2C STOP\n"); /* clear both lines */ hrt_i2c_setscl (hrt_dev, 0); hrt_i2c_setsda (hrt_dev, 0); udelay (hrt_dev->i2c_udelay); hrt_i2c_setscl (hrt_dev, 1); udelay (hrt_dev->i2c_udelay); hrt_i2c_setsda (hrt_dev, 1); udelay (hrt_dev->i2c_udelay); hrt_i2c_setscl (hrt_dev, 0); udelay (hrt_dev->i2c_udelay); hrt_i2c_setsda (hrt_dev, 0); udelay (hrt_dev->i2c_udelay); } #endif static const struct i2c_algo_bit_data hrt_i2c_algo_bit_data_template = { .setsda = (void (*) (void *, int)) hrt_i2c_setsda, .setscl = (void (*) (void *, int))hrt_i2c_setscl, .getsda = (int (*) (void *)) hrt_i2c_getsda, .getscl = (int (*) (void *)) hrt_i2c_getscl, }; #define HRT_I2C_ADAPTER_NAME "hrt_pixelsmart" static const struct i2c_adapter hrt_i2c_adapter_template = { .owner = THIS_MODULE, .class = I2C_CLASS_TV_ANALOG, .name = HRT_I2C_ADAPTER_NAME, .id = I2C_HW_HRT_PIXELSMART, .client_register = hrt_i2c_client_register, }; static int hrt_i2c_adapter_init (struct hrt_dev *hrt_dev) { int result; struct i2c_adapter *i2c_adapter; struct i2c_algo_bit_data *i2c_algo_bit_data; BUG_ON (!hrt_dev); i2c_adapter = &hrt_dev->i2c_adapter; i2c_algo_bit_data = &hrt_dev->i2c_algo_bit_data; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); memcpy (i2c_adapter, &hrt_i2c_adapter_template, sizeof (*i2c_adapter)); i2c_set_adapdata (i2c_adapter, hrt_dev); memcpy (i2c_algo_bit_data, &hrt_i2c_algo_bit_data_template, sizeof (*i2c_algo_bit_data)); i2c_algo_bit_data->data = hrt_dev; i2c_adapter->algo_data = i2c_algo_bit_data; if (hrt_dev->pci_dev) i2c_adapter->dev.parent = &hrt_dev->pci_dev->dev; #if 0 /* clear the bus */ hrt_i2c_write_stop (hrt_dev); #endif hrt_dev->i2c_signal_state = 0; hrt_i2c_set_signal (hrt_dev); result = i2c_bit_add_bus (i2c_adapter); if (result < 0) { printk (KERN_ERR "hrt: i2c_add_adapter failed\n"); return result; } return 0; } static void hrt_i2c_adapter_cleanup (struct hrt_dev *hrt_dev) { BUG_ON (!hrt_dev); printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); /* TODO */ if (i2c_del_adapter (&hrt_dev->i2c_adapter) < 0) { printk (KERN_ERR "hrt: i2c_del_adapter failed\n"); } } /******************************************************************************/ /* the I2C SAA7110 "Driver" DRIVER 3 */ static int hrt_saa7110_attach_adapter (struct i2c_adapter *adapter); static int hrt_saa7110_detach_client (struct i2c_client *client); static int hrt_saa7110_found_proc (struct i2c_adapter *adapter, int addr, int kind); #define HRT_I2C_DRIVER_NAME "hrt-saa7110" #define HRT_SAA7110_CLIENT_NAME "hrt-saa7110" static struct i2c_driver hrt_saa7110_driver = { .driver = { .name = HRT_I2C_DRIVER_NAME }, .attach_adapter = hrt_saa7110_attach_adapter, .detach_client = hrt_saa7110_detach_client }; static unsigned short normal_i2c[] = { HRT_I2C_SAA7110_ADDR, HRT_I2C_SAA7110_ADDR + 1, I2C_CLIENT_END }; #if 0 static const unsigned short saa7110_force[] = { HRT_I2C_SAA7110_ADDR, HRT_I2C_SAA7110_ADDR + 1 }; I2C_CLIENT_INSMOD_1 (saa7110); #else I2C_CLIENT_INSMOD; #endif static int hrt_saa7110_attach_adapter (struct i2c_adapter *adapter) { int result; BUG_ON (!adapter); printk (KERN_ALERT "%s:%s: adapter = %p adapter->algo = %p\n", __FILE__, __FUNCTION__, adapter, adapter ? adapter->algo : 0); result = i2c_probe (adapter, &addr_data, hrt_saa7110_found_proc); if (result < 0) return result; return 0; } static int hrt_saa7110_detach_client (struct i2c_client *client) { BUG_ON (!client); printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return i2c_detach_client (client); } static int hrt_saa7110_found_proc (struct i2c_adapter *adapter, int addr, int kind) { int result, err; struct hrt_dev *hrt_dev; struct i2c_client *i2c_client; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); /* our chip must be attached to the PixelSmart's I2C adapter */ if (strncmp (adapter->name, HRT_I2C_ADAPTER_NAME, sizeof (adapter->name)) != 0) { printk (KERN_ALERT "hrt: trying to probe for SAA7110 on non-HRT adapter\n"); return -ENODEV; } if (!i2c_check_functionality (adapter, (I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))) return -ENODEV; /* TODO: probe this I2C device to make sure its really what we wanted */ /* for now, assume it's the one we want */ /* initialize the i2c_client structure in our device structure */ hrt_dev = i2c_get_adapdata (adapter); BUG_ON (!hrt_dev); printk (KERN_ALERT "hrt: trying to probe I2C bus on device at address 0x%08lx\n", hrt_dev->phys_address); BUG_ON (!hrt_dev->phys_address); i2c_client = &hrt_dev->i2c_saa7110_client; memset (i2c_client, 0, sizeof (*i2c_client)); i2c_set_clientdata (i2c_client, hrt_dev); i2c_client->addr = addr; i2c_client->adapter = adapter; i2c_client->driver = &hrt_saa7110_driver; strncpy (i2c_client->name, HRT_SAA7110_CLIENT_NAME, sizeof (i2c_client->name)); result = i2c_transfer (adapter, hrt_saa7110_init_regs, ARRAY_SIZE (hrt_saa7110_init_regs)); if (result < 0) { printk (KERN_ERR "hrt: failed to send SAA7110 register initialization sequence\n"); err = result; goto fail_transfer; } result = i2c_attach_client (i2c_client); if (result < 0) { printk (KERN_ERR "hrt: could not attach i2c_client for SAA7110 chip\n"); err = result; goto fail_attach; } return 0; fail_attach: fail_transfer: memset (i2c_client, 0, sizeof (*i2c_client)); return err; } static int hrt_i2c_init (void) { return i2c_add_driver (&hrt_saa7110_driver); } static void hrt_i2c_cleanup (void) { i2c_del_driver (&hrt_saa7110_driver); } /* end I2C Bus initialization */ /******************************************************************************/ /* PCI initialization */ #define PCI_VENDOR_ID_HRT 0x0004 #define PCI_DEVICE_ID_HRT_PS512_8 0x0404 #define PCI_DEVICE_ID_HRT_VIDEO_GALA 0x0408 static const struct pci_device_id hrt_pci_device_ids[] = { { PCI_DEVICE (PCI_VENDOR_ID_HRT, PCI_DEVICE_ID_HRT_PS512_8) }, { PCI_DEVICE (PCI_VENDOR_ID_HRT, PCI_DEVICE_ID_HRT_VIDEO_GALA) }, { 0 } }; MODULE_DEVICE_TABLE(pci, hrt_pci_device_ids); static int hrt_pci_probe (struct pci_dev *dev, const struct pci_device_id *id); static void hrt_pci_remove (struct pci_dev *dev); static struct pci_driver hrt_pci_driver = { .name = HRT_DRIVER_NAME, .id_table = hrt_pci_device_ids, .probe = hrt_pci_probe, .remove = __devexit_p (hrt_pci_remove), }; static int hrt_pci_probe (struct pci_dev *pci_dev, const struct pci_device_id *id) { int result; int err = 0; struct hrt_dev *hrt_dev = 0; unsigned long phys_address; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); if (id->vendor != PCI_VENDOR_ID_HRT) { printk (KERN_ERR "hrt: unknown PCI vendor 0x%04x\n", id->vendor); return -ENODEV; } else if (id->device != PCI_DEVICE_ID_HRT_PS512_8 && id->device != PCI_DEVICE_ID_HRT_VIDEO_GALA) { printk (KERN_ERR "hrt: unknown PCI device 0x%04x\n", id->device); return -ENODEV; } phys_address = pci_resource_start (pci_dev, 0); printk (KERN_ALERT "hrt: probing PCI device at address 0x%08lx with id %04x:%04x\n", phys_address, id->vendor, id->device); result = pci_enable_device (pci_dev); if (result < 0) { printk (KERN_ERR "hrt: failed to enable PCI device\n"); err = result; goto fail_pci_enable; } result = hrt_generic_init (phys_address, &hrt_dev); if (result < 0) { err = result; goto fail_generic_init; } BUG_ON (!hrt_dev); /* set the driver specific data embedded in the generic device structure in the pci_dev structure to point to our hrt_dev structure */ hrt_dev->pci_dev = pci_dev; dev_set_drvdata (&pci_dev->dev, hrt_dev); return 0; fail_generic_init: pci_disable_device (pci_dev); fail_pci_enable: return err; } static void hrt_pci_remove (struct pci_dev *pci_dev) { struct hrt_dev *hrt_dev; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); hrt_dev = dev_get_drvdata (&pci_dev->dev); hrt_generic_cleanup (hrt_dev); pci_disable_device (pci_dev); } static int hrt_pci_init (void) { int result; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); result = pci_register_driver (&hrt_pci_driver); if (result < 0) { printk (KERN_ERR "hrt: failed to register PCI driver\n"); return result; } return 0; } static void hrt_pci_cleanup (void) { pci_unregister_driver (&hrt_pci_driver); } /* end PCI initialization */ /******************************************************************************/ /* ISA initialization */ /* TODO: ISA stuff should use kernel's isa_driver stuff. Also need to detect PNP device */ #define HRT_MAX_ISA_DEVS 4 static const unsigned long hrt_isa_addresses[] = { 0x000d4000, 0x000dc000, }; static void hrt_isa_cleanup (void) { struct list_head *pos, *n; down (&hrt_devs_lock); list_for_each_safe (pos, n, &hrt_devs) { struct hrt_dev *hrt_dev = list_entry (pos, struct hrt_dev, list_head); if (NULL == hrt_dev->pci_dev) hrt_generic_cleanup (hrt_dev); } up (&hrt_devs_lock); } static int hrt_isa_init (void) { int i; int result, err; for (i = 0; i < ARRAY_SIZE (hrt_isa_addresses); i++) { struct hrt_dev *hrt_dev = 0; result = hrt_generic_init (hrt_isa_addresses[i], &hrt_dev); if (result < 0) { if (result == -ENODEV) continue; err = result; goto fail; } } return 0; fail: hrt_isa_cleanup (); return err; } /* end ISA initialization */ /******************************************************************************/ /* Video4Linux2 initialization */ #define HRT_VIDEO_MEMORY_LIMIT_DEFAULT (16 * 1024 * 1024) // 16 megabytes #define HRT_VIDEO_BUFFER_COUNT_DEFAULT 32 // buffer 32 frames by default #define HRT_VIDEO_MIN_WIDTH 32 #define HRT_VIDEO_MIN_HEIGHT 32 static const struct v4l2_queryctrl hrt_ctls[] = { { .id = V4L2_CID_BRIGHTNESS, .name = "Brightness", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0x9b, // (decimal: 155) .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_CONTRAST, .name = "Contrast", .minimum = 0, .maximum = 255, .step = 1, .default_value = 0x5e, // (decimal: 94) .type = V4L2_CTRL_TYPE_INTEGER, } }; /* for each entry in the scatterlist, figure out what the offset into the whole scatterlist the first address is */ static void hrt_prep_to_addr (struct hrt_sg_to_addr *to_addr, struct videobuf_buffer *vidbuf) { int i, pos = 0; for (i = 0; i < vidbuf->dma.nr_pages; i++) { to_addr[i].sg = &vidbuf->dma.sglist[i]; to_addr[i].pos = pos; pos += vidbuf->dma.sglist[i].length; } } /* returns the index into the scatterlist array for the entry that contains the address pos. copied from vivi.c */ static int hrt_get_addr_pos (int pos, int pages, struct hrt_sg_to_addr *to_addr) { int p1 = 0, p2 = pages - 1, p3 = pages / 2; BUG_ON (pos >= to_addr[p2].pos + to_addr[p2].sg->length); while (p1 + 1 < p2) { if (pos < to_addr[p3].pos) { p2 = p3; } else { p1 = p3; } p3 = (p1 + p2) / 2; } if (pos >= to_addr[p2].pos) p1 = p2; return p1; } static void hrt_get_line (struct hrt_dev *hrt_dev, struct videobuf_buffer *vidbuf, unsigned short line) { struct hrt_buffer *hrt_buffer = container_of (vidbuf, struct hrt_buffer, vidbuf); int old_page_pos; struct page *page; unsigned long bytes_per_line = hrt_dev->video_width * hrt_dev->video_height * (hrt_dev->color ? 2 : 1); char *base; unsigned int frame_index = line * bytes_per_line; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); old_page_pos = hrt_get_addr_pos (frame_index, vidbuf->dma.nr_pages, hrt_buffer->to_addr); page = pfn_to_page (sg_dma_address (hrt_buffer->to_addr[old_page_pos].sg) >> PAGE_SHIFT); base = kmap_atomic (page, KM_BOUNCE_READ) + hrt_buffer->to_addr[old_page_pos].sg->offset; hrt_set_video_y (hrt_dev, line); if (hrt_dev->color) { /* TODO */ } else { int w; int i = 0; for (w = 0; w < hrt_dev->video_width; w++) { int page_pos = hrt_get_addr_pos (frame_index + w, vidbuf->dma.nr_pages, hrt_buffer->to_addr); if (page_pos != old_page_pos) { /* if we're not writing into the same page, we need to map a the new one */ page = pfn_to_page (sg_dma_address (hrt_buffer->to_addr[page_pos].sg) >> PAGE_SHIFT); kunmap_atomic (base, KM_BOUNCE_READ); base = kmap_atomic (page, KM_BOUNCE_READ) + hrt_buffer->to_addr[page_pos].sg->offset; old_page_pos = page_pos; i = 0; } base[i] = ioread8 ((void*) hrt_dev->base_address + w); i++; } } } static void hrt_fill_buffer (struct hrt_dev *hrt_dev, struct videobuf_buffer *vidbuf) { enum v4l2_field field; unsigned long bytesperline; unsigned short line; struct hrt_buffer *hrt_buffer = container_of (vidbuf, struct hrt_buffer, vidbuf); printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); if (!waitqueue_active (&vidbuf->done)) return; bytesperline = hrt_dev->video_width * hrt_dev->video_height * (hrt_dev->color ? 2 : 1); if (!sg_dma_address (&vidbuf->dma.sglist[0])) return; BUG_ON (vidbuf->dma.nr_pages << PAGE_SHIFT < (bytesperline * vidbuf->height)); hrt_prep_to_addr (hrt_buffer->to_addr, vidbuf); field = hrt_get_current_field (hrt_dev); hrt_video_freeze_next (hrt_dev); while (hrt_get_current_field (hrt_dev) == field) schedule (); for (line = 0; (unsigned long) line * bytesperline < vidbuf->bsize && line < hrt_dev->video_height; line++) { hrt_get_line (hrt_dev, vidbuf, line); } hrt_video_live (hrt_dev); vidbuf->size = line * bytesperline; vidbuf->state = STATE_DONE; vidbuf->field_count++; do_gettimeofday (&vidbuf->ts); wake_up (&vidbuf->done); } static int hrt_thread (void *data) { int result, err = 0; struct hrt_dev *hrt_dev = data; hrt_dev->thread_state = HRT_THREAD_RUNNING; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); while (!kthread_should_stop ()) { printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); if (list_empty (&hrt_dev->active_bufs)) { result = wait_event_interruptible (hrt_dev->waitq, kthread_should_stop ()); if (result < 0) { err = result; break; } } if (kthread_should_stop ()) break; printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); result = down_interruptible (&hrt_dev->thread_lock); if (result < 0) { err = result; break; } printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); if (hrt_dev->thread_state == HRT_THREAD_STOPPING) { up (&hrt_dev->thread_lock); break; } up (&hrt_dev->thread_lock); if (kthread_should_stop ()) break; while (!list_empty (&hrt_dev->active_bufs)) { struct videobuf_buffer *vidbuf = list_entry (hrt_dev->active_bufs.next, struct videobuf_buffer, queue); hrt_fill_buffer (hrt_dev, vidbuf); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); } } hrt_dev->thread_state = HRT_THREAD_RETURNED; printk (KERN_ALERT "hrt: exiting %s\n", __FUNCTION__); return err; } static int hrt_start_thread (struct hrt_dev *hrt_dev) { struct task_struct *kthread; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); down (&hrt_dev->thread_lock); if (hrt_dev->kthread) return 0; init_waitqueue_head (&hrt_dev->waitq); kthread = kthread_run (hrt_thread, hrt_dev, "hrt/%d", hrt_dev->video_device.minor); if (IS_ERR (kthread)) { printk (KERN_ERR "hrt: could not start thread\n"); hrt_dev->kthread = NULL; return PTR_ERR (kthread); } hrt_dev->kthread = kthread; up (&hrt_dev->thread_lock); return 0; } static void hrt_stop_thread (struct hrt_dev *hrt_dev) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); down (&hrt_dev->thread_lock); if (hrt_dev->kthread) { printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); hrt_dev->thread_state = HRT_THREAD_STOPPING; wake_up_interruptible (&hrt_dev->waitq); up (&hrt_dev->thread_lock); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); kthread_stop (hrt_dev->kthread); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); hrt_dev->thread_state = HRT_THREAD_STOPPED; hrt_dev->kthread = NULL; } else { up (&hrt_dev->thread_lock); } printk (KERN_ALERT "hrt: thread stopped\n"); } static int hrt_videobuf_queue_buf_setup (struct videobuf_queue *vidbufq, unsigned int *count, unsigned int *size) { struct hrt_dev *hrt_dev = vidbufq->priv_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); *size = hrt_dev->video_width * hrt_dev->video_height; if (hrt_dev->color) *size *= 2; if (*count == 0) *count = HRT_VIDEO_BUFFER_COUNT_DEFAULT; if (*count * *size > hrt_dev->video_memory_limit) *count = hrt_dev->video_memory_limit / *size; return 0; } static void hrt_free_videobuf_buffer (struct videobuf_queue *vidbufq, struct videobuf_buffer *vidbuf) { struct hrt_buffer *hrt_buffer = container_of (vidbuf, struct hrt_buffer, vidbuf); printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); might_sleep (); kfree (hrt_buffer->to_addr); hrt_buffer->to_addr = NULL; videobuf_waiton (vidbuf, 0, 0); videobuf_dma_unmap (vidbufq, &vidbuf->dma); videobuf_dma_free (&vidbuf->dma); vidbuf->state = STATE_NEEDS_INIT; } static int hrt_videobuf_queue_buf_prepare (struct videobuf_queue *vidbufq, struct videobuf_buffer *vidbuf, enum v4l2_field field) { struct hrt_dev *hrt_dev = vidbufq->priv_data; struct hrt_buffer *hrt_buffer = container_of (vidbuf, struct hrt_buffer, vidbuf); int result; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); vidbuf->size = hrt_dev->video_width * hrt_dev->video_height; if (hrt_dev->color) vidbuf->size *= 2; /* make sure the buffer is big enough */ if (vidbuf->baddr != 0 && vidbuf->bsize < vidbuf->size) return -EINVAL; vidbuf->width = hrt_dev->video_width; vidbuf->height = hrt_dev->video_height; vidbuf->field = V4L2_FIELD_INTERLACED; if (vidbuf->state == STATE_NEEDS_INIT) { result = videobuf_iolock (vidbufq, vidbuf, NULL); if (result < 0) return result; } vidbuf->state = STATE_PREPARED; hrt_buffer->to_addr = kmalloc (sizeof (*hrt_buffer->to_addr) * vidbuf->dma.nr_pages, GFP_KERNEL); if (hrt_buffer->to_addr == NULL) { hrt_free_videobuf_buffer (vidbufq, vidbuf); return -ENOMEM; } return 0; } static void hrt_videobuf_queue_buf_queue(struct videobuf_queue *vidbufq, struct videobuf_buffer *vidbuf) { struct hrt_dev *hrt_dev = vidbufq->priv_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); down (&hrt_dev->thread_lock); if (!list_empty (&hrt_dev->queued_bufs)) { list_add_tail (&vidbuf->queue, &hrt_dev->queued_bufs); vidbuf->state = STATE_QUEUED; } else if (list_empty (&hrt_dev->active_bufs)) { list_add_tail (&vidbuf->queue, &hrt_dev->active_bufs); vidbuf->state = STATE_ACTIVE; } else { struct videobuf_buffer *prev = list_entry (hrt_dev->active_bufs.prev, struct videobuf_buffer, queue); if (prev->width == vidbuf->width && prev->height == vidbuf->height) { list_add_tail (&vidbuf->queue, &hrt_dev->active_bufs); vidbuf->state = STATE_ACTIVE; } else { list_add_tail (&vidbuf->queue, &hrt_dev->queued_bufs); vidbuf->state = STATE_QUEUED; } } up (&hrt_dev->thread_lock); wake_up_interruptible (&hrt_dev->waitq); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); } static void hrt_videobuf_queue_buf_release(struct videobuf_queue *vidbufq, struct videobuf_buffer *vidbuf) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); hrt_free_videobuf_buffer (vidbufq, vidbuf); } static int hrt_videobuf_queue_vb_map_sg(void *dev, struct scatterlist *sg, int nents, int direction) { int i; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); BUG_ON(direction == DMA_NONE); for (i = 0; i < nents; i++) { BUG_ON(!sg[i].page); sg_dma_address(&sg[i]) = page_to_phys(sg[i].page) + sg[i].offset; } printk (KERN_ALERT "hrt: %s: returning %d\n", __FUNCTION__, nents); return nents; } static int hrt_videobuf_queue_vb_unmap_sg(void *dev, struct scatterlist *sglist, int nr_pages, int direction) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); return 0; } static int hrt_videobuf_queue_vb_dma_sync_sg(void *dev, struct scatterlist *sglist, int nr_pages, int direction) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); return 0; } static const struct videobuf_queue_ops hrt_videobuf_queue_ops = { .buf_setup = hrt_videobuf_queue_buf_setup, .buf_prepare = hrt_videobuf_queue_buf_prepare, .buf_queue = hrt_videobuf_queue_buf_queue, .buf_release = hrt_videobuf_queue_buf_release, .vb_map_sg = hrt_videobuf_queue_vb_map_sg, .vb_unmap_sg = hrt_videobuf_queue_vb_unmap_sg, .vb_dma_sync_sg = hrt_videobuf_queue_vb_dma_sync_sg, }; static int hrt_open (struct inode *inode, struct file *filp) { int result, err; struct hrt_dev *hrt_dev = hrt_dev_get (iminor (inode)); filp->private_data = hrt_dev; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (down_interruptible (&hrt_dev->user_lock) < 0) return -ERESTARTSYS; if (hrt_dev->users == 0) { memcpy (&hrt_dev->vidbufq_ops, &hrt_videobuf_queue_ops, sizeof (hrt_dev->vidbufq_ops)); INIT_LIST_HEAD (&hrt_dev->active_bufs); INIT_LIST_HEAD (&hrt_dev->queued_bufs); videobuf_queue_init (&hrt_dev->vidbufq, &hrt_dev->vidbufq_ops, hrt_dev->pci_dev, /* I *think* this is right... */ NULL, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED, sizeof (struct hrt_buffer), hrt_dev); result = hrt_start_thread (hrt_dev); if (result < 0) { err = result; goto fail; } } hrt_dev->users++; up (&hrt_dev->user_lock); return 0; fail: up (&hrt_dev->user_lock); memset (&hrt_dev->vidbufq, 0, sizeof (hrt_dev->vidbufq)); return err; } static int hrt_release (struct inode *inode, struct file *filp) { struct hrt_dev *hrt_dev = filp->private_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (down_interruptible (&hrt_dev->user_lock) < 0) return -ERESTARTSYS; hrt_dev->users--; if (hrt_dev->users == 0) { hrt_stop_thread (hrt_dev); memset (&hrt_dev->vidbufq, 0, sizeof (hrt_dev->vidbufq)); } up (&hrt_dev->user_lock); return 0; } static ssize_t hrt_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos) { int result; struct hrt_dev *hrt_dev = filp->private_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); if (hrt_busy (hrt_dev)) return -EBUSY; result = videobuf_read_one (&hrt_dev->vidbufq, buf, count, fpos, filp->f_flags & O_NONBLOCK); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); hrt_unbusy (hrt_dev); printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); return result; } static unsigned int hrt_poll (struct file *filp, struct poll_table_struct *poll) { struct hrt_dev *hrt_dev = filp->private_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (hrt_busy (hrt_dev)) return 0; return POLLIN | POLLRDNORM; } static int hrt_mmap (struct file *filp, struct vm_area_struct *vma) { struct hrt_dev *hrt_dev = filp->private_data; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return videobuf_mmap_mapper (&hrt_dev->vidbufq, vma); } static void hrt_video_device_release (struct video_device *vfd) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); memset (vfd, 0, sizeof (*vfd)); } /* query capabilities */ static int hrt_vidioc_querycap (struct file *filp, void *priv, struct v4l2_capability *cap) { struct hrt_dev *hrt_dev = filp->private_data; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); strcpy (cap->driver, HRT_DRIVER_NAME); strcpy (cap->card, hrt_dev->color ? "HRT Video Gala" : "HRT PixelSmart 512-8"); if (hrt_dev->pci_dev) { sprintf (cap->bus_info, "PCI:%s", pci_name (hrt_dev->pci_dev)); } else { sprintf (cap->bus_info, "ISA:0x%05lx", hrt_dev->phys_address); } cap->version = HRT_DRIVER_VERSION; cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; return 0; } /* get the current video standard */ static int hrt_vidioc_querystd (struct file *filp, void *priv, v4l2_std_id *std) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); *std = V4L2_STD_NTSC; return 0; } static const struct v4l2_input hrt_v4l2_input_default = { .name = "Camera", .type = V4L2_INPUT_TYPE_CAMERA, .std = V4L2_STD_NTSC, }; /* enumerate the inputs */ static int hrt_vidioc_enum_input (struct file *filp, void *priv, struct v4l2_input *input) { struct hrt_dev *hrt_dev = filp->private_data; __u32 index = input->index; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); switch (index) { case 0: memcpy (input, &hrt_v4l2_input_default, sizeof (*input)); input->index = index; if (!hrt_dev->color) input->status |= V4L2_IN_ST_NO_COLOR; break; default: return -EINVAL; } return 0; } /* set the current input index */ static int hrt_vidioc_s_input (struct file *filp, void *priv, unsigned int index) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); switch (index) { case 0: return 0; default: return -EINVAL; } } /* get the current input index */ static int hrt_vidioc_g_input (struct file *filp, void *priv, unsigned int *index) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); *index = 0; return 0; } static const struct v4l2_fmtdesc hrt_v4l2_fmtdesc_color = { .description = "16-bit RGB 5-6-5", .pixelformat = V4L2_PIX_FMT_RGB565, }; static const struct v4l2_fmtdesc hrt_v4l2_fmtdesc_bw = { .description = "8-bit Greyscale", .pixelformat = V4L2_PIX_FMT_GREY, }; /* enumerate capture formats */ static int hrt_vidioc_enum_fmt_cap (struct file *filp, void *priv, struct v4l2_fmtdesc *fmtdesc) { struct hrt_dev *hrt_dev = priv; __u32 index = fmtdesc->index; enum v4l2_buf_type type = fmtdesc->type; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); switch (index) { case 0: if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; memcpy (fmtdesc, hrt_dev->color ? &hrt_v4l2_fmtdesc_color : &hrt_v4l2_fmtdesc_bw, sizeof (*fmtdesc)); fmtdesc->index = index; fmtdesc->type = type; return 0; default: return -EINVAL; } } const static struct v4l2_pix_format hrt_v4l2_pix_format_color = { .width = HRT_VIDEO_COLOR_WIDTH, .height = HRT_VIDEO_COLOR_HEIGHT, .pixelformat = V4L2_PIX_FMT_RGB565, .field = V4L2_FIELD_INTERLACED, .bytesperline = HRT_VIDEO_COLOR_WIDTH * 2, .sizeimage = HRT_VIDEO_COLOR_WIDTH * HRT_VIDEO_COLOR_HEIGHT * 2, }; const static struct v4l2_pix_format hrt_v4l2_pix_format_bw = { .width = HRT_VIDEO_BW_WIDTH, .height = HRT_VIDEO_BW_HEIGHT, .pixelformat = V4L2_PIX_FMT_GREY, .field = V4L2_FIELD_INTERLACED, .bytesperline = HRT_VIDEO_BW_WIDTH * 2, .sizeimage = HRT_VIDEO_BW_WIDTH * HRT_VIDEO_BW_HEIGHT * 2, }; /* get the format for capture */ static int hrt_vidioc_g_fmt_cap (struct file *filp, void *priv, struct v4l2_format *format) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; memcpy (&format->fmt.pix, hrt_dev->color ? &hrt_v4l2_pix_format_color : &hrt_v4l2_pix_format_bw, sizeof (format->fmt.pix)); return 0; } /* try setting the capture format */ /* we don't allow the user to change the format :( */ static int hrt_vidioc_try_fmt_cap (struct file *filp, void *priv, struct v4l2_format *format) { struct hrt_dev *hrt_dev = priv; const struct v4l2_pix_format *pix_format; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); pix_format = hrt_dev->color ? &hrt_v4l2_pix_format_color : &hrt_v4l2_pix_format_bw; printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); if (format->fmt.pix.field != V4L2_FIELD_ANY && format->fmt.pix.field != pix_format->field) return -EINVAL; printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); if (format->fmt.pix.pixelformat != pix_format->pixelformat) return -EINVAL; printk (KERN_ALERT "hrt: %s:%d\n", __FUNCTION__, __LINE__); format->fmt.pix.width = pix_format->width; format->fmt.pix.height = pix_format->height; format->fmt.pix.field = V4L2_FIELD_INTERLACED; return 0; } static int hrt_vidioc_s_fmt_cap (struct file *filp, void *priv, struct v4l2_format *format) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); return hrt_vidioc_try_fmt_cap (filp, priv, format); } static const struct v4l2_captureparm hrt_v4l2_captureparm_color = { .capability = 0, .capturemode = 0, .timeperframe.numerator = HRT_VIDEO_FRAMERATE_DENOMINATOR_COLOR, .timeperframe.denominator = HRT_VIDEO_FRAMERATE_NUMERATOR_COLOR, }; static const struct v4l2_captureparm hrt_v4l2_captureparm_bw = { .capability = 0, .capturemode = 0, .timeperframe.numerator = HRT_VIDEO_FRAMERATE_DENOMINATOR_BW, .timeperframe.denominator = HRT_VIDEO_FRAMERATE_NUMERATOR_BW, }; static int hrt_vidioc_g_parm (struct file *filp, void *priv, struct v4l2_streamparm *streamparm) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); if (streamparm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; memcpy (&streamparm->parm.capture, hrt_dev->color ? &hrt_v4l2_captureparm_color : &hrt_v4l2_captureparm_bw, sizeof (streamparm->parm.capture)); return 0; } static int hrt_vidioc_s_parm (struct file *filp, void *priv, struct v4l2_streamparm *streamparm) { printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); return hrt_vidioc_g_parm (filp, priv, streamparm); } static int hrt_vidioc_reqbufs (struct file *filp, void *priv, struct v4l2_requestbuffers *p) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return videobuf_reqbufs (&hrt_dev->vidbufq, p); } static int hrt_vidioc_querybuf (struct file *filp, void *priv, struct v4l2_buffer *p) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return videobuf_querybuf (&hrt_dev->vidbufq, p); } static int hrt_vidioc_qbuf (struct file *filp, void *priv, struct v4l2_buffer *p) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return videobuf_qbuf (&hrt_dev->vidbufq, p); } static int hrt_vidioc_dqbuf (struct file *filp, void *priv, struct v4l2_buffer *p) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); return videobuf_dqbuf (&hrt_dev->vidbufq, p, filp->f_flags & O_NONBLOCK); } static int hrt_vidioc_streamon (struct file *filp, void *priv, enum v4l2_buf_type i) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (hrt_busy (hrt_dev)) return -EBUSY; return videobuf_streamon (&hrt_dev->vidbufq); } static int hrt_vidioc_streamoff (struct file *filp, void *priv, enum v4l2_buf_type i) { struct hrt_dev *hrt_dev = priv; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; videobuf_streamoff (&hrt_dev->vidbufq); hrt_unbusy (hrt_dev); return 0; } static const struct file_operations hrt_fops = { .owner = THIS_MODULE, .open = hrt_open, .release = hrt_release, .ioctl = video_ioctl2, .read = hrt_read, .poll = hrt_poll, .llseek = no_llseek, .mmap = hrt_mmap, }; static const struct video_device hrt_video_device_template = { .name = HRT_DRIVER_NAME, .fops = &hrt_fops, .type = VFL_TYPE_GRABBER, .type2 = VID_TYPE_CAPTURE, .minor = -1, .release = hrt_video_device_release, .vidioc_querycap = hrt_vidioc_querycap, .vidioc_querystd = hrt_vidioc_querystd, .vidioc_enum_input = hrt_vidioc_enum_input, .vidioc_s_input = hrt_vidioc_s_input, .vidioc_g_input = hrt_vidioc_g_input, .vidioc_enum_fmt_cap = hrt_vidioc_enum_fmt_cap, .vidioc_g_fmt_cap = hrt_vidioc_g_fmt_cap, .vidioc_s_fmt_cap = hrt_vidioc_s_fmt_cap, .vidioc_g_parm = hrt_vidioc_g_parm, .vidioc_s_parm = hrt_vidioc_s_parm, .vidioc_reqbufs = hrt_vidioc_reqbufs, .vidioc_querybuf = hrt_vidioc_querybuf, .vidioc_qbuf = hrt_vidioc_qbuf, .vidioc_dqbuf = hrt_vidioc_dqbuf, .vidioc_streamon = hrt_vidioc_streamon, .vidioc_streamoff = hrt_vidioc_streamoff, }; /* end Video4Linux2 initialization */ /******************************************************************************/ /* generic initialization */ static int hrt_generic_probe (struct hrt_dev *hrt_dev) { u8 save_registers[5]; u8 pixel; int i; int result; printk (KERN_ALERT "hrt: %s\n", __FUNCTION__); /* save current register values */ for (i = 0; i < 5; i++) save_registers[i] = ioread8 ((void*) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL + i); hrt_dev->color = 0; hrt_video_freeze_immediate (hrt_dev); hrt_set_video_y (hrt_dev, 0); pixel = ioread8 ((void*) hrt_dev->base_address); iowrite8 (~pixel, (void*) hrt_dev->base_address); mb (); hrt_set_video_y (hrt_dev, 1); iowrite8 (pixel, (void*) hrt_dev->base_address); mb (); hrt_set_video_y (hrt_dev, 0); if (ioread8 ((void*) hrt_dev->base_address) == pixel) { printk (KERN_ERR "hrt: probe failed for device at address 0x%08lx\n", hrt_dev->phys_address); /* restore register values */ for (i = 0; i < 5; i++) iowrite8 (save_registers[i], (void*) hrt_dev->base_address + HRT_IO_OFFSET_CONTROL + i); iowrite8 (pixel, (void*) hrt_dev->base_address); return -ENODEV; } /* now we know we have an HRT card at least */ printk (KERN_ERR "hrt: probe succeeded for device at address 0x%08lx\n", hrt_dev->phys_address); /* find out if it's black&white or color */ hrt_video_freeze_immediate (hrt_dev); hrt_set_video_y (hrt_dev, 0); /* get the pixel at offset 0x400. this pixel is outside the region for the black&white card, but within the region for the color card */ pixel = ioread8 ((void*) hrt_dev->base_address + 0x400); iowrite8 (~pixel, (void*) hrt_dev->base_address + 0x400); /* in case greyscale addresses wrap around, refresh the value at offset zero */ iowrite8(pixel, (void *) hrt_dev->base_address); mb (); if (ioread8 ((void *) hrt_dev->base_address + 0x400) == ~pixel) { printk (KERN_ALERT "hrt: detected color device\n"); hrt_dev->color = 1; hrt_dev->video_height = HRT_VIDEO_COLOR_HEIGHT; hrt_dev->video_width = HRT_VIDEO_COLOR_WIDTH; } else { printk (KERN_ALERT "hrt: detected b/w device\n"); hrt_dev->color = 0; hrt_dev->video_height = HRT_VIDEO_BW_HEIGHT; hrt_dev->video_width = HRT_VIDEO_BW_WIDTH; } /* write zeros to horizontal raster line 500, a line that the A/D unit does not modify */ hrt_set_video_y (hrt_dev, 500); for (i = 0; i < 512; i++) iowrite8(0, (void *) hrt_dev->base_address + i); /* set capturing mode */ hrt_video_live(hrt_dev); /* try to overwrite the line with ones */ for (i = 0; i < 512; i++) iowrite8(0xff, (void *) hrt_dev->base_address + i); /* freeze the image */ hrt_video_freeze_immediate (hrt_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 *) hrt_dev->base_address + i)) result = 0; hrt_video_live(hrt_dev); if (result) { printk (KERN_ALERT "hrt: detected dual ported card\n"); hrt_dev->dual_ported = 1; } else { printk (KERN_ALERT "hrt: detected non-dual ported card\n"); hrt_dev->dual_ported = 0; } return 0; } static int hrt_generic_init (unsigned long phys_address, struct hrt_dev **hrt_dev_out) { int result, err; unsigned long base_address; struct hrt_dev *hrt_dev; printk (KERN_ALERT "hrt: probing physical address 0x%08lx\n", phys_address); if (!request_mem_region (phys_address, HRT_IO_REGION_SIZE, HRT_DRIVER_NAME)) { printk (KERN_ERR "hrt: could not get IO memory region\n"); err = -ENODEV; goto fail_pci_get_region; } base_address = (unsigned long) ioremap_nocache (phys_address, HRT_IO_REGION_SIZE); if (!base_address) { printk (KERN_ERR "hrt: could not remap IO memory region\n"); err = -ENODEV; goto fail_ioremap; } else { printk (KERN_ERR "hrt: IO memory region mapped to 0x%08lx\n", base_address); } hrt_dev = kzalloc (sizeof (*hrt_dev), GFP_KERNEL); if (!hrt_dev) { printk (KERN_ERR "hrt: could not allocate device structure\n"); err = -ENOMEM; goto fail_alloc; } init_MUTEX (&hrt_dev->thread_lock); init_MUTEX (&hrt_dev->user_lock); hrt_dev->busy = HRT_BUSY_INIT; hrt_dev->phys_address = phys_address; hrt_dev->base_address = base_address; hrt_dev->video_memory_limit = HRT_VIDEO_MEMORY_LIMIT_DEFAULT; INIT_LIST_HEAD (&hrt_dev->list_head); /* need to make sure whatever pci device this is behaves the way we expect the HRT card to behave */ result = hrt_generic_probe (hrt_dev); if (result < 0) { err = result; goto fail_probe; } hrt_video_live (hrt_dev); result = hrt_i2c_adapter_init (hrt_dev); if (result < 0) { err = result; goto fail_i2c_adapter; } memcpy (&hrt_dev->video_device, &hrt_video_device_template, sizeof (hrt_dev->video_device)); result = video_register_device (&hrt_dev->video_device, hrt_dev->video_device.type, hrt_dev->video_device.minor); if (result < 0) { err = result; goto fail_video_register; } down (&hrt_devs_lock); list_add_tail (&hrt_devs, &hrt_dev->list_head); up (&hrt_devs_lock); *hrt_dev_out = hrt_dev; return 0; fail_video_register: hrt_i2c_adapter_cleanup (hrt_dev); fail_i2c_adapter: fail_probe: BUG_ON (!hrt_dev); kfree (hrt_dev); fail_alloc: fail_ioremap: BUG_ON (!base_address); iounmap ((void*) base_address); fail_pci_get_region: BUG_ON (!phys_address); release_mem_region (phys_address, HRT_IO_REGION_SIZE); BUG_ON (err == 0); return err; } static void hrt_generic_cleanup (struct hrt_dev *hrt_dev) { struct list_head *pos, *n; BUG_ON (!hrt_dev); BUG_ON (!hrt_dev->base_address); BUG_ON (!hrt_dev->phys_address); BUG_ON (hrt_dev->kthread != NULL); list_for_each_safe (pos, n, &hrt_devs) { if (list_entry (pos, struct hrt_dev, list_head) == hrt_dev) { list_del (pos); break; } } video_unregister_device (&hrt_dev->video_device); hrt_i2c_adapter_cleanup (hrt_dev); iounmap ((void*) hrt_dev->base_address); release_mem_region (hrt_dev->phys_address, HRT_IO_REGION_SIZE); kfree (hrt_dev); } /******************************************************************************/ /* Module Initialization / Shutdown */ static int __init hrt_init(void) { int result; int err; printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); result = hrt_isa_init (); if (result < 0) { err = result; goto fail_isa; } result = hrt_pci_init (); if (result < 0) { err = result; goto fail_pci; } result = hrt_i2c_init (); if (result < 0) { err = result; goto fail_i2c; } return 0; fail_i2c: hrt_pci_cleanup (); fail_pci: hrt_isa_cleanup (); fail_isa: return err; } static void __exit hrt_exit(void) { printk (KERN_ALERT "%s:%s\n", __FILE__, __FUNCTION__); hrt_i2c_cleanup (); hrt_pci_cleanup (); hrt_isa_cleanup (); BUG_ON (!list_empty (&hrt_devs)); // punch_eduardo (); } module_init(hrt_init); module_exit(hrt_exit);