URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [media/] [video/] [vino.c] - Rev 1765
Compare with Previous | Blame | View Log
/* * Driver for the VINO (Video In No Out) system found in SGI Indys. * * This file is subject to the terms and conditions of the GNU General Public * License version 2 as published by the Free Software Foundation. * * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> */ #include <linux/module.h> #include <linux/kmod.h> #include <linux/init.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/wrapper.h> #include <linux/irq.h> #include <linux/delay.h> #include <linux/pci.h> #include <linux/videodev.h> #include <linux/video_decoder.h> #include <linux/i2c.h> #include <linux/i2c-algo-sgi.h> #include <asm/paccess.h> #include <asm/io.h> #include <asm/sgi/ip22.h> #include <asm/sgi/hpc3.h> #include <asm/sgi/mc.h> #include "vino.h" /* debugging? */ #if 1 #define DEBUG(x...) printk(x); #else #define DEBUG(x...) #endif /* VINO video size */ #define VINO_PAL_WIDTH 768 #define VINO_PAL_HEIGHT 576 #define VINO_NTSC_WIDTH 646 #define VINO_NTSC_HEIGHT 486 /* set this to some sensible values. note: VINO_MIN_WIDTH has to be 8*x */ #define VINO_MIN_WIDTH 32 #define VINO_MIN_HEIGHT 32 /* channel selection */ #define VINO_INPUT_COMP 0 #define VINO_INPUT_SVIDEO 1 #define VINO_INPUT_CAMERA 2 #define VINO_INPUT_CHANNELS 3 #define PAGE_RATIO (PAGE_SIZE / VINO_PAGE_SIZE) /* VINO ASIC registers */ struct sgi_vino *vino; static const char *vinostr = "VINO IndyCam/TV"; static int threshold_a = 512; static int threshold_b = 512; struct vino_device { struct video_device vdev; #define VINO_CHAN_A 1 #define VINO_CHAN_B 2 int chan; int alpha; /* clipping... */ unsigned int left, right, top, bottom; /* decimation used */ unsigned int decimation; /* palette used, picture hue, etc */ struct video_picture picture; /* VINO_INPUT_COMP, VINO_INPUT_SVIDEO or VINO_INPUT_CAMERA */ unsigned int input; /* bytes per line */ unsigned int line_size; /* descriptor table (virtual addresses) */ unsigned long *desc; /* # of allocated pages */ int page_count; /* descriptor table (dma addresses) */ struct { dma_addr_t *cpu; dma_addr_t dma; } dma_desc; /* add some more space to let VINO trigger End Of Field interrupt * before reaching end of buffer */ #define VINO_FBUFSIZE (VINO_PAL_WIDTH * VINO_PAL_HEIGHT * 4 + 2 * PAGE_SIZE) unsigned int frame_size; #define VINO_BUF_UNUSED 0 #define VINO_BUF_GRABBING 1 #define VINO_BUF_DONE 2 int buffer_state; wait_queue_head_t dma_wait; spinlock_t state_lock; struct semaphore sem; /* Make sure we only have one user at the time */ int users; }; struct vino_client { struct i2c_client *driver; int owner; }; struct vino_video { struct vino_device chA; struct vino_device chB; struct vino_client decoder; struct vino_client camera; spinlock_t vino_lock; spinlock_t input_lock; /* Loaded into VINO descriptors to clear End Of Descriptors table * interupt condition */ unsigned long dummy_desc; struct { dma_addr_t *cpu; dma_addr_t dma; } dummy_dma; }; static struct vino_video *Vino; /* --- */ unsigned i2c_vino_getctrl(void *data) { return vino->i2c_control; } void i2c_vino_setctrl(void *data, unsigned val) { vino->i2c_control = val; } unsigned i2c_vino_rdata(void *data) { return vino->i2c_data; } void i2c_vino_wdata(void *data, unsigned val) { vino->i2c_data = val; } static struct i2c_algo_sgi_data i2c_sgi_vino_data = { .getctrl = &i2c_vino_getctrl, .setctrl = &i2c_vino_setctrl, .rdata = &i2c_vino_rdata, .wdata = &i2c_vino_wdata, .xfer_timeout = 200, .ack_timeout = 1000, }; /* * There are two possible clients on VINO I2C bus, so we limit usage only * to them. */ static int i2c_vino_client_reg(struct i2c_client *client) { int res = 0; spin_lock(&Vino->input_lock); switch (client->driver->id) { case I2C_DRIVERID_SAA7191: if (Vino->decoder.driver) res = -EBUSY; else Vino->decoder.driver = client; break; case I2C_DRIVERID_INDYCAM: if (Vino->camera.driver) res = -EBUSY; else Vino->camera.driver = client; break; default: res = -ENODEV; } spin_unlock(&Vino->input_lock); return res; } static int i2c_vino_client_unreg(struct i2c_client *client) { int res = 0; spin_lock(&Vino->input_lock); if (client == Vino->decoder.driver) { if (Vino->decoder.owner) res = -EBUSY; else Vino->decoder.driver = NULL; } else if (client == Vino->camera.driver) { if (Vino->camera.owner) res = -EBUSY; else Vino->camera.driver = NULL; } spin_unlock(&Vino->input_lock); return res; } static struct i2c_adapter vino_i2c_adapter = { .name = "VINO I2C bus", .id = I2C_HW_SGI_VINO, .algo_data = &i2c_sgi_vino_data, .client_register = &i2c_vino_client_reg, .client_unregister = &i2c_vino_client_unreg, }; static int vino_i2c_add_bus(void) { return i2c_sgi_add_bus(&vino_i2c_adapter); } static int vino_i2c_del_bus(void) { return i2c_sgi_del_bus(&vino_i2c_adapter); } static int i2c_camera_command(unsigned int cmd, void *arg) { return Vino->camera.driver->driver->command(Vino->camera.driver, cmd, arg); } static int i2c_decoder_command(unsigned int cmd, void *arg) { return Vino->decoder.driver->driver->command(Vino->decoder.driver, cmd, arg); } /* --- */ static int bytes_per_pixel(struct vino_device *v) { switch (v->picture.palette) { case VIDEO_PALETTE_GREY: return 1; case VIDEO_PALETTE_YUV422: return 2; default: /* VIDEO_PALETTE_RGB32 */ return 4; } } static int get_capture_norm(struct vino_device *v) { if (v->input == VINO_INPUT_CAMERA) return VIDEO_MODE_NTSC; else { /* TODO */ return VIDEO_MODE_NTSC; } } /* * Set clipping. Try new values to fit, if they don't return -EINVAL */ static int set_clipping(struct vino_device *v, int x, int y, int w, int h, int d) { int maxwidth, maxheight, lsize; if (d < 1) d = 1; if (d > 8) d = 8; if (w / d < VINO_MIN_WIDTH || h / d < VINO_MIN_HEIGHT) return -EINVAL; if (get_capture_norm(v) == VIDEO_MODE_NTSC) { maxwidth = VINO_NTSC_WIDTH; maxheight = VINO_NTSC_HEIGHT; } else { maxwidth = VINO_PAL_WIDTH; maxheight = VINO_PAL_HEIGHT; } if (x < 0) x = 0; if (y < 0) y = 0; y &= ~1; /* odd/even fields */ if (x + w > maxwidth) { w = maxwidth - x; if (w / d < VINO_MIN_WIDTH) x = maxwidth - VINO_MIN_WIDTH * d; } if (y + h > maxheight) { h = maxheight - y; if (h / d < VINO_MIN_HEIGHT) y = maxheight - VINO_MIN_HEIGHT * d; } /* line size must be multiple of 8 bytes */ lsize = (bytes_per_pixel(v) * w / d) & ~7; w = lsize * d / bytes_per_pixel(v); v->left = x; v->top = y; v->right = x + w; v->bottom = y + h; v->decimation = d; v->line_size = lsize; DEBUG("VINO: clipping %d, %d, %d, %d / %d - %d\n", v->left, v->top, v->right, v->bottom, v->decimation, v->line_size); return 0; } static int set_scaling(struct vino_device *v, int w, int h) { int maxwidth, maxheight, lsize, d; if (w < VINO_MIN_WIDTH || h < VINO_MIN_HEIGHT) return -EINVAL; if (get_capture_norm(v) == VIDEO_MODE_NTSC) { maxwidth = VINO_NTSC_WIDTH; maxheight = VINO_NTSC_HEIGHT; } else { maxwidth = VINO_PAL_WIDTH; maxheight = VINO_PAL_HEIGHT; } if (w > maxwidth) w = maxwidth; if (h > maxheight) h = maxheight; d = max(maxwidth / w, maxheight / h); if (d > 8) d = 8; /* line size must be multiple of 8 bytes */ lsize = (bytes_per_pixel(v) * w) & ~7; w = lsize * d / bytes_per_pixel(v); h *= d; if (v->left + w > maxwidth) v->left = maxwidth - w; if (v->top + h > maxheight) v->top = (maxheight - h) & ~1; /* odd/even fields */ /* FIXME: -1 bug... Verify clipping with video signal generator */ v->right = v->left + w; v->bottom = v->top + h; v->decimation = d; v->line_size = lsize; DEBUG("VINO: scaling %d, %d, %d, %d / %d - %d\n", v->left, v->top, v->right, v->bottom, v->decimation, v->line_size); return 0; } /* * Prepare vino for DMA transfer... (execute only with vino_lock locked) */ static int dma_setup(struct vino_device *v) { u32 ctrl, intr; struct sgi_vino_channel *ch; ch = (v->chan == VINO_CHAN_A) ? &vino->a : &vino->b; ch->page_index = 0; ch->line_count = 0; /* let VINO know where to transfer data */ ch->start_desc_tbl = v->dma_desc.dma; ch->next_4_desc = v->dma_desc.dma; /* give vino time to fetch the first four descriptors, 5 usec * should be more than enough time */ udelay(5); /* VINO line size register is set 8 bytes less than actual */ ch->line_size = v->line_size - 8; /* set the alpha register */ ch->alpha = v->alpha; /* set cliping registers */ ch->clip_start = VINO_CLIP_ODD(v->top) | VINO_CLIP_EVEN(v->top+1) | VINO_CLIP_X(v->left); ch->clip_end = VINO_CLIP_ODD(v->bottom) | VINO_CLIP_EVEN(v->bottom+1) | VINO_CLIP_X(v->right); /* FIXME: end-of-field bug workaround VINO_CLIP_X(VINO_PAL_WIDTH); */ /* init the frame rate and norm (full frame rate only for now...) */ ch->frame_rate = VINO_FRAMERT_RT(0x1fff) | (get_capture_norm(v) == VIDEO_MODE_PAL ? VINO_FRAMERT_PAL : 0); ctrl = vino->control; intr = vino->intr_status; if (v->chan == VINO_CHAN_A) { /* All interrupt conditions for this channel was cleared * so clear the interrupt status register and enable * interrupts */ intr &= ~VINO_INTSTAT_A; ctrl |= VINO_CTRL_A_INT; /* enable synchronization */ ctrl |= VINO_CTRL_A_SYNC_ENBL; /* enable frame assembly */ ctrl |= VINO_CTRL_A_INTERLEAVE_ENBL; /* set decimation used */ if (v->decimation < 2) ctrl &= ~VINO_CTRL_A_DEC_ENBL; else { ctrl |= VINO_CTRL_A_DEC_ENBL; ctrl &= ~VINO_CTRL_A_DEC_SCALE_MASK; ctrl |= (v->decimation - 1) << VINO_CTRL_A_DEC_SCALE_SHIFT; } /* select input interface */ if (v->input == VINO_INPUT_CAMERA) ctrl |= VINO_CTRL_A_SELECT; else ctrl &= ~VINO_CTRL_A_SELECT; /* palette */ ctrl &= ~(VINO_CTRL_A_LUMA_ONLY | VINO_CTRL_A_RGB | VINO_CTRL_A_DITHER); } else { intr &= ~VINO_INTSTAT_B; ctrl |= VINO_CTRL_B_INT; ctrl |= VINO_CTRL_B_SYNC_ENBL; ctrl |= VINO_CTRL_B_INTERLEAVE_ENBL; if (v->decimation < 2) ctrl &= ~VINO_CTRL_B_DEC_ENBL; else { ctrl |= VINO_CTRL_B_DEC_ENBL; ctrl &= ~VINO_CTRL_B_DEC_SCALE_MASK; ctrl |= (v->decimation - 1) << VINO_CTRL_B_DEC_SCALE_SHIFT; } if (v->input == VINO_INPUT_CAMERA) ctrl |= VINO_CTRL_B_SELECT; else ctrl &= ~VINO_CTRL_B_SELECT; ctrl &= ~(VINO_CTRL_B_LUMA_ONLY | VINO_CTRL_B_RGB | VINO_CTRL_B_DITHER); } /* set palette */ switch (v->picture.palette) { case VIDEO_PALETTE_GREY: ctrl |= (v->chan == VINO_CHAN_A) ? VINO_CTRL_A_LUMA_ONLY : VINO_CTRL_B_LUMA_ONLY; break; case VIDEO_PALETTE_RGB32: ctrl |= (v->chan == VINO_CHAN_A) ? VINO_CTRL_A_RGB : VINO_CTRL_B_RGB; break; #if 0 /* FIXME: this is NOT in v4l API :-( */ case VIDEO_PALETTE_RGB332: ctrl |= (v->chan == VINO_CHAN_A) ? VINO_CTRL_A_RGB | VINO_CTRL_A_DITHER : VINO_CTRL_B_RGB | VINO_CTRL_B_DITHER; break; #endif } vino->control = ctrl; vino->intr_status = intr; return 0; } /* (execute only with vino_lock locked) */ static void dma_stop(struct vino_device *v) { u32 ctrl = vino->control; ctrl &= (v->chan == VINO_CHAN_A) ? ~VINO_CTRL_A_DMA_ENBL : ~VINO_CTRL_B_DMA_ENBL; vino->control = ctrl; } /* (execute only with vino_lock locked) */ static void dma_go(struct vino_device *v) { u32 ctrl = vino->control; ctrl |= (v->chan == VINO_CHAN_A) ? VINO_CTRL_A_DMA_ENBL : VINO_CTRL_B_DMA_ENBL; vino->control = ctrl; } /* * Load dummy page to descriptor registers. This prevents generating of * spurious interrupts. (execute only with vino_lock locked) */ static void clear_eod(struct vino_device *v) { struct sgi_vino_channel *ch; DEBUG("VINO: chnl %c clear EOD\n", (v->chan == VINO_CHAN_A) ? 'A':'B'); ch = (v->chan == VINO_CHAN_A) ? &vino->a : &vino->b; ch->page_index = 0; ch->line_count = 0; ch->start_desc_tbl = Vino->dummy_dma.dma; ch->next_4_desc = Vino->dummy_dma.dma; udelay(5); } static void field_done(struct vino_device *v) { spin_lock(&v->state_lock); if (v->buffer_state == VINO_BUF_GRABBING) v->buffer_state = VINO_BUF_DONE; spin_unlock(&v->state_lock); wake_up(&v->dma_wait); } static void vino_interrupt(int irq, void *dev_id, struct pt_regs *regs) { u32 intr, ctrl; spin_lock(&Vino->vino_lock); ctrl = vino->control; intr = vino->intr_status; DEBUG("VINO: intr status %04x\n", intr); if (intr & (VINO_INTSTAT_A_FIFO | VINO_INTSTAT_A_EOD)) { ctrl &= ~VINO_CTRL_A_DMA_ENBL; vino->control = ctrl; clear_eod(&Vino->chA); } if (intr & (VINO_INTSTAT_B_FIFO | VINO_INTSTAT_B_EOD)) { ctrl &= ~VINO_CTRL_B_DMA_ENBL; vino->control = ctrl; clear_eod(&Vino->chB); } vino->intr_status = ~intr; spin_unlock(&Vino->vino_lock); /* FIXME: For now we are assuming that interrupt means that frame is * done. That's not true, but we can live with such brokeness for * a while ;-) */ field_done(&Vino->chA); } static int vino_grab(struct vino_device *v, int frame) { int err = 0; spin_lock_irq(&v->state_lock); if (v->buffer_state == VINO_BUF_GRABBING) err = -EBUSY; v->buffer_state = VINO_BUF_GRABBING; spin_unlock_irq(&v->state_lock); if (err) return err; spin_lock_irq(&Vino->vino_lock); dma_setup(v); dma_go(v); spin_unlock_irq(&Vino->vino_lock); return 0; } static int vino_waitfor(struct vino_device *v, int frame) { wait_queue_t wait; int i, err = 0; if (frame != 0) return -EINVAL; spin_lock_irq(&v->state_lock); switch (v->buffer_state) { case VINO_BUF_GRABBING: init_waitqueue_entry(&wait, current); /* add ourselves into wait queue */ add_wait_queue(&v->dma_wait, &wait); /* and set current state */ set_current_state(TASK_INTERRUPTIBLE); /* before releasing spinlock */ spin_unlock_irq(&v->state_lock); /* to ensure that schedule_timeout will return imediately * if VINO interrupt was triggred meanwhile */ schedule_timeout(HZ / 10); if (signal_pending(current)) err = -EINTR; spin_lock_irq(&v->state_lock); remove_wait_queue(&v->dma_wait, &wait); /* don't rely on schedule_timeout return value and check what * really happened */ if (!err && v->buffer_state == VINO_BUF_GRABBING) err = -EIO; /* fall through */ case VINO_BUF_DONE: for (i = 0; i < v->page_count; i++) pci_dma_sync_single(NULL, v->dma_desc.cpu[PAGE_RATIO*i], PAGE_SIZE, PCI_DMA_FROMDEVICE); v->buffer_state = VINO_BUF_UNUSED; break; default: err = -EINVAL; } spin_unlock_irq(&v->state_lock); if (err && err != -EINVAL) { DEBUG("VINO: waiting for frame failed\n"); spin_lock_irq(&Vino->vino_lock); dma_stop(v); clear_eod(v); spin_unlock_irq(&Vino->vino_lock); } return err; } static int alloc_buffer(struct vino_device *v, int size) { int count, i, j, err; err = i = 0; count = (size / PAGE_SIZE + 4) & ~3; v->desc = (unsigned long *) kmalloc(count * sizeof(unsigned long), GFP_KERNEL); if (!v->desc) return -ENOMEM; v->dma_desc.cpu = pci_alloc_consistent(NULL, PAGE_RATIO * (count+4) * sizeof(dma_addr_t), &v->dma_desc.dma); if (!v->dma_desc.cpu) { err = -ENOMEM; goto out_free_desc; } while (i < count) { dma_addr_t dma; v->desc[i] = get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!v->desc[i]) break; dma = pci_map_single(NULL, (void *)v->desc[i], PAGE_SIZE, PCI_DMA_FROMDEVICE); for (j = 0; j < PAGE_RATIO; j++) v->dma_desc.cpu[PAGE_RATIO * i + j ] = dma + VINO_PAGE_SIZE * j; mem_map_reserve(virt_to_page(v->desc[i])); i++; } v->dma_desc.cpu[PAGE_RATIO * count] = VINO_DESC_STOP; if (i-- < count) { while (i >= 0) { mem_map_unreserve(virt_to_page(v->desc[i])); pci_unmap_single(NULL, v->dma_desc.cpu[PAGE_RATIO * i], PAGE_SIZE, PCI_DMA_FROMDEVICE); free_page(v->desc[i]); i--; } pci_free_consistent(NULL, PAGE_RATIO * (count+4) * sizeof(dma_addr_t), (void *)v->dma_desc.cpu, v->dma_desc.dma); err = -ENOBUFS; goto out_free_desc; } v->page_count = count; return 0; out_free_desc: kfree(v->desc); return err; } static void free_buffer(struct vino_device *v) { int i; for (i = 0; i < v->page_count; i++) { mem_map_unreserve(virt_to_page(v->desc[i])); pci_unmap_single(NULL, v->dma_desc.cpu[PAGE_RATIO * i], PAGE_SIZE, PCI_DMA_FROMDEVICE); free_page(v->desc[i]); } pci_free_consistent(NULL, PAGE_RATIO * (v->page_count+4) * sizeof(dma_addr_t), (void *)v->dma_desc.cpu, v->dma_desc.dma); kfree(v->desc); } static int vino_open(struct inode *inode, struct file *file) { struct video_device *dev = video_devdata(file); struct vino_device *v = dev->priv; int err = 0; down(&v->sem); if (v->users) { err = -EBUSY; goto out; } /* Check for input device (IndyCam, saa7191) availability. * Both DMA channels can run from the same source, but only * source owner is allowed to change its parameters */ spin_lock(&Vino->input_lock); if (Vino->camera.driver) { v->input = VINO_INPUT_CAMERA; if (!Vino->camera.owner) Vino->camera.owner = v->chan; } if (Vino->decoder.driver && Vino->camera.owner != v->chan) { /* There are two inputs (Composite and SVideo) but only * one output available to VINO DMA engine */ if (!Vino->decoder.owner) { Vino->decoder.owner = v->chan; v->input = VINO_INPUT_COMP; i2c_decoder_command(DECODER_SET_INPUT, &v->input); } else v->input = (v->chan == VINO_CHAN_A) ? Vino->chB.input : Vino->chA.input; } if (v->input == -1) err = -ENODEV; spin_unlock(&Vino->input_lock); if (err) goto out; if (alloc_buffer(v, VINO_FBUFSIZE)) { err = -ENOBUFS; goto out; } v->users++; out: up(&v->sem); return err; } static int vino_close(struct inode *inode, struct file *file) { struct video_device *dev = video_devdata(file); struct vino_device *v = dev->priv; down(&v->sem); v->users--; if (!v->users) { struct vino_device *w = (v->chan == VINO_CHAN_A) ? &Vino->chB : &Vino->chA; /* Eventually make other channel owner of input device */ spin_lock(&Vino->input_lock); if (Vino->camera.owner == v->chan) Vino->camera.owner = (w->input == VINO_INPUT_CAMERA) ? w->chan : 0; else if (Vino->decoder.owner == v->chan) Vino->decoder.owner = (w->input == VINO_INPUT_COMP || w->input == VINO_INPUT_SVIDEO) ? w->chan : 0; v->input = -1; spin_unlock(&Vino->input_lock); vino_waitfor(v, 0); free_buffer(v); } up(&v->sem); return 0; } static int vino_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *dev = video_devdata(file); struct vino_device *v = dev->priv; unsigned long start = vma->vm_start; unsigned long size = vma->vm_end - vma->vm_start; int i, err = 0; if (down_interruptible(&v->sem)) return -EINTR; if (size > v->page_count * PAGE_SIZE) { err = -EINVAL; goto out; } for (i = 0; i < v->page_count; i++) { unsigned long page = virt_to_phys((void *)v->desc[i]); if (remap_page_range(start, page, PAGE_SIZE, PAGE_READONLY)) { err = -EAGAIN; goto out; } start += PAGE_SIZE; if (size <= PAGE_SIZE) break; size -= PAGE_SIZE; } out: up(&v->sem); return err; } static int vino_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg) { struct video_device *dev = video_devdata(file); struct vino_device *v = dev->priv; switch (cmd) { case VIDIOCGCAP: { struct video_capability *cap = arg; strcpy(cap->name, vinostr); cap->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE; cap->channels = VINO_INPUT_CHANNELS; cap->audios = 0; cap->maxwidth = VINO_PAL_WIDTH; cap->maxheight = VINO_PAL_HEIGHT; cap->minwidth = VINO_MIN_WIDTH; cap->minheight = VINO_MIN_HEIGHT; break; } case VIDIOCGCHAN: { struct video_channel *ch = arg; ch->flags = 0; ch->tuners = 0; switch (ch->channel) { case VINO_INPUT_COMP: ch->norm = VIDEO_MODE_PAL | VIDEO_MODE_NTSC; ch->type = VIDEO_TYPE_TV; strcpy(ch->name, "Composite"); break; case VINO_INPUT_SVIDEO: ch->norm = VIDEO_MODE_PAL | VIDEO_MODE_NTSC; ch->type = VIDEO_TYPE_TV; strcpy(ch->name, "S-Video"); break; case VINO_INPUT_CAMERA: ch->norm = VIDEO_MODE_NTSC; ch->type = VIDEO_TYPE_CAMERA; strcpy(ch->name, "IndyCam"); break; default: return -EINVAL; } break; } case VIDIOCSCHAN: { struct video_channel *ch = arg; int err = 0; struct vino_device *w = (v->chan == VINO_CHAN_A) ? &Vino->chB : &Vino->chA; spin_lock(&Vino->input_lock); switch (ch->channel) { case VINO_INPUT_COMP: case VINO_INPUT_SVIDEO: if (!Vino->decoder.driver) { err = -ENODEV; break; } if (!Vino->decoder.owner) Vino->decoder.owner = v->chan; if (Vino->decoder.owner == v->chan) i2c_decoder_command(DECODER_SET_INPUT, &ch->channel); else if (ch->channel != w->input) { err = -EBUSY; break; } if (Vino->camera.owner == v->chan) Vino->camera.owner = (w->input == VINO_INPUT_CAMERA) ? w->chan : 0; break; case VINO_INPUT_CAMERA: if (!Vino->camera.driver) { err = -ENODEV; break; } if (!Vino->camera.owner) Vino->camera.owner = v->chan; if (Vino->decoder.owner == v->chan) Vino->decoder.owner = (w->input == VINO_INPUT_COMP || w->input == VINO_INPUT_SVIDEO) ? w->chan : 0; break; default: err = -EINVAL; } if (!err) v->input = ch->channel; spin_unlock(&Vino->input_lock); return err; } case VIDIOCGPICT: { struct video_picture *pic = arg; memcpy(pic, &v->picture, sizeof(struct video_picture)); break; } case VIDIOCSPICT: { struct video_picture *pic = arg; switch (pic->palette) { case VIDEO_PALETTE_GREY: pic->depth = 8; break; case VIDEO_PALETTE_YUV422: pic->depth = 16; break; case VIDEO_PALETTE_RGB32: pic->depth = 24; break; default: return -EINVAL; } if (v->picture.palette != pic->palette) { v->picture.palette = pic->palette; v->picture.depth = pic->depth; /* TODO: we need to change line size */ } DEBUG("XXX %d, %d\n", v->input, Vino->camera.owner); spin_lock(&Vino->input_lock); if (v->input == VINO_INPUT_CAMERA) { if (Vino->camera.owner == v->chan) { spin_unlock(&Vino->input_lock); memcpy(&v->picture, pic, sizeof(struct video_picture)); i2c_camera_command(DECODER_SET_PICTURE, pic); goto out_unlocked; } } else { if (Vino->decoder.owner == v->chan) { spin_unlock(&Vino->input_lock); memcpy(&v->picture, pic, sizeof(struct video_picture)); i2c_decoder_command(DECODER_SET_PICTURE, pic); goto out_unlocked; } } spin_unlock(&Vino->input_lock); out_unlocked: break; } /* get cropping */ case VIDIOCGCAPTURE: { struct video_capture *capture = arg; capture->x = v->left; capture->y = v->top; capture->width = v->right - v->left; capture->height = v->bottom - v->top; capture->decimation = v->decimation; capture->flags = 0; break; } /* set cropping */ case VIDIOCSCAPTURE: { struct video_capture *capture = arg; return set_clipping(v, capture->x, capture->y, capture->width, capture->height, capture->decimation); } /* get scaling */ case VIDIOCGWIN: { struct video_window *win = arg; memset(win, 0, sizeof(*win)); win->width = (v->right - v->left) / v->decimation; win->height = (v->bottom - v->top) / v->decimation; break; } /* set scaling */ case VIDIOCSWIN: { struct video_window *win = arg; if (win->x || win->y || win->clipcount || win->clips) return -EINVAL; return set_scaling(v, win->width, win->height); } case VIDIOCGMBUF: { struct video_mbuf *buf = arg; buf->frames = 1; buf->offsets[0] = 0; buf->size = v->page_count * PAGE_SIZE; break; } case VIDIOCMCAPTURE: { struct video_mmap *mmap = arg; if (mmap->width != v->right - v->left || mmap->height != v->bottom - v->top || mmap->format != v->picture.palette || mmap->frame != 0) return -EINVAL; return vino_grab(v, mmap->frame); } case VIDIOCSYNC: return vino_waitfor(v, *((int*)arg)); default: return -ENOIOCTLCMD; } return 0; } static int vino_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct video_device *dev = video_devdata(file); struct vino_device *v = dev->priv; int err; if (down_interruptible(&v->sem)) return -EINTR; err = video_usercopy(inode, file, cmd, arg, vino_do_ioctl); up(&v->sem); return err; } static struct file_operations vino_fops = { .owner = THIS_MODULE, .open = vino_open, .release = vino_close, .ioctl = vino_ioctl, .mmap = vino_mmap, .llseek = no_llseek, }; static const struct video_device vino_template = { .owner = THIS_MODULE, .type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE, .hardware = VID_HARDWARE_VINO, .name = "VINO", .fops = &vino_fops, .minor = -1, }; static void init_channel_data(struct vino_device *v, int channel) { init_waitqueue_head(&v->dma_wait); init_MUTEX(&v->sem); spin_lock_init(&v->state_lock); memcpy(&v->vdev, &vino_template, sizeof(vino_template)); v->vdev.priv = v; v->chan = channel; v->input = -1; v->picture.palette = VIDEO_PALETTE_GREY; v->picture.depth = 8; v->buffer_state = VINO_BUF_UNUSED; v->users = 0; set_clipping(v, 0, 0, VINO_NTSC_WIDTH, VINO_NTSC_HEIGHT, 1); } static int __init vino_init(void) { unsigned long rev; dma_addr_t dma; int i, ret = 0; /* VINO is Indy specific beast */ if (ip22_is_fullhouse()) return -ENODEV; /* * VINO is in the EISA address space, so the sysid register will tell * us if the EISA_PRESENT pin on MC has been pulled low. * * If EISA_PRESENT is not set we definitely don't have a VINO equiped * system. */ if (!(sgimc->systemid & SGIMC_SYSID_EPRESENT)) { printk(KERN_ERR "VINO not found\n"); return -ENODEV; } vino = (struct sgi_vino *)ioremap(VINO_BASE, sizeof(struct sgi_vino)); if (!vino) return -EIO; /* Okay, once we know that VINO is present we'll read its revision * safe way. One never knows... */ if (get_dbe(rev, &(vino->rev_id))) { printk(KERN_ERR "VINO: failed to read revision register\n"); ret = -ENODEV; goto out_unmap; } if (VINO_ID_VALUE(rev) != VINO_CHIP_ID) { printk(KERN_ERR "VINO is not VINO (Rev/ID: 0x%04lx)\n", rev); ret = -ENODEV; goto out_unmap; } printk(KERN_INFO "VINO Rev: 0x%02lx\n", VINO_REV_NUM(rev)); Vino = (struct vino_video *) kmalloc(sizeof(struct vino_video), GFP_KERNEL); if (!Vino) { ret = -ENOMEM; goto out_unmap; } memset(Vino, 0, sizeof(struct vino_video)); Vino->dummy_desc = get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!Vino->dummy_desc) { ret = -ENOMEM; goto out_free_vino; } Vino->dummy_dma.cpu = pci_alloc_consistent(NULL, 4 * sizeof(dma_addr_t), &Vino->dummy_dma.dma); if (!Vino->dummy_dma.cpu) { ret = -ENOMEM; goto out_free_dummy_desc; } dma = pci_map_single(NULL, (void *)Vino->dummy_desc, PAGE_SIZE, PCI_DMA_FROMDEVICE); for (i = 0; i < 4; i++) Vino->dummy_dma.cpu[i] = dma; vino->control = 0; /* prevent VINO from throwing spurious interrupts */ vino->a.next_4_desc = Vino->dummy_dma.dma; vino->b.next_4_desc = Vino->dummy_dma.dma; udelay(5); vino->intr_status = 0; /* set threshold level */ vino->a.fifo_thres = threshold_a; vino->b.fifo_thres = threshold_b; spin_lock_init(&Vino->vino_lock); spin_lock_init(&Vino->input_lock); init_channel_data(&Vino->chA, VINO_CHAN_A); init_channel_data(&Vino->chB, VINO_CHAN_B); if (request_irq(SGI_VINO_IRQ, vino_interrupt, 0, vinostr, NULL)) { printk(KERN_ERR "VINO: request irq%02d failed\n", SGI_VINO_IRQ); ret = -EAGAIN; goto out_unmap_dummy_desc; } ret = vino_i2c_add_bus(); if (ret) { printk(KERN_ERR "VINO: I2C bus registration failed\n"); goto out_free_irq; } if (video_register_device(&Vino->chA.vdev, VFL_TYPE_GRABBER, -1) < 0) { printk("%s, chnl %d: device registration failed.\n", Vino->chA.vdev.name, Vino->chA.chan); ret = -EINVAL; goto out_i2c_del_bus; } if (video_register_device(&Vino->chB.vdev, VFL_TYPE_GRABBER, -1) < 0) { printk("%s, chnl %d: device registration failed.\n", Vino->chB.vdev.name, Vino->chB.chan); ret = -EINVAL; goto out_unregister_vdev; } #if defined(CONFIG_KMOD) && defined (MODULE) request_module("saa7191"); request_module("indycam"); #endif return 0; out_unregister_vdev: video_unregister_device(&Vino->chA.vdev); out_i2c_del_bus: vino_i2c_del_bus(); out_free_irq: free_irq(SGI_VINO_IRQ, NULL); out_unmap_dummy_desc: pci_unmap_single(NULL, Vino->dummy_dma.dma, PAGE_SIZE, PCI_DMA_FROMDEVICE); pci_free_consistent(NULL, 4 * sizeof(dma_addr_t), (void *)Vino->dummy_dma.cpu, Vino->dummy_dma.dma); out_free_dummy_desc: free_page(Vino->dummy_desc); out_free_vino: kfree(Vino); out_unmap: iounmap(vino); return ret; } static void __exit vino_exit(void) { video_unregister_device(&Vino->chA.vdev); video_unregister_device(&Vino->chB.vdev); vino_i2c_del_bus(); free_irq(SGI_VINO_IRQ, NULL); pci_unmap_single(NULL, Vino->dummy_dma.dma, PAGE_SIZE, PCI_DMA_FROMDEVICE); pci_free_consistent(NULL, 4 * sizeof(dma_addr_t), (void *)Vino->dummy_dma.cpu, Vino->dummy_dma.dma); free_page(Vino->dummy_desc); kfree(Vino); iounmap(vino); } module_init(vino_init); module_exit(vino_exit); MODULE_AUTHOR("Ladislav Michl <ladis@linux-mips.org>"); MODULE_DESCRIPTION("Video4Linux driver for SGI Indy VINO (IndyCam)"); MODULE_LICENSE("GPL");