/* * Media Vision Pro Movie Studio * or * "all you need is an I2C bus some RAM and a prayer" * * This draws heavily on code * * (c) Wolfgang Koehler, wolf@first.gmd.de, Dec. 1994 * Kiefernring 15 * 14478 Potsdam, Germany * * Most of this code is directly derived from his userspace driver. * His driver works so send any reports to alan@lxorguk.ukuu.org.uk * unless the userspace driver also doesn't work for you... * * Changes: * 08/07/2003 Daniele Bellucci <bellucda@tiscali.it> * - pms_capture: report back -EFAULT */ #include <linux/module.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/ioport.h> #include <linux/init.h> #include <asm/io.h> #include <linux/videodev.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <linux/mutex.h> #include <asm/uaccess.h> #define MOTOROLA 1 #define PHILIPS2 2 #define PHILIPS1 3 #define MVVMEMORYWIDTH 0x40 /* 512 bytes */ struct pms_device { struct video_device v; struct video_picture picture; int height; int width; unsigned long in_use; struct mutex lock; }; struct i2c_info { u8 slave; u8 sub; u8 data; u8 hits; }; static int i2c_count; static struct i2c_info i2cinfo[64]; static int decoder = PHILIPS2; static int standard; /* 0 - auto 1 - ntsc 2 - pal 3 - secam */ /* * I/O ports and Shared Memory */ static int io_port = 0x250; static int data_port = 0x251; static int mem_base = 0xC8000; static void __iomem *mem; static int video_nr = -1; static inline void mvv_write(u8 index, u8 value) { outw(index|(value<<8), io_port); } static inline u8 mvv_read(u8 index) { outb(index, io_port); return inb(data_port); } static int pms_i2c_stat(u8 slave) { int counter; int i; outb(0x28, io_port); counter=0; while((inb(data_port)&0x01)==0) if(counter++==256) break; while((inb(data_port)&0x01)!=0) if(counter++==256) break; outb(slave, io_port); counter=0; while((inb(data_port)&0x01)==0) if(counter++==256) break; while((inb(data_port)&0x01)!=0) if(counter++==256) break; for(i=0;i<12;i++) { char st=inb(data_port); if((st&2)!=0) return -1; if((st&1)==0) break; } outb(0x29, io_port); return inb(data_port); } static int pms_i2c_write(u16 slave, u16 sub, u16 data) { int skip=0; int count; int i; for(i=0;i<i2c_count;i++) { if((i2cinfo[i].slave==slave) && (i2cinfo[i].sub == sub)) { if(i2cinfo[i].data==data) skip=1; i2cinfo[i].data=data; i=i2c_count+1; } } if(i==i2c_count && i2c_count<64) { i2cinfo[i2c_count].slave=slave; i2cinfo[i2c_count].sub=sub; i2cinfo[i2c_count].data=data; i2c_count++; } if(skip) return 0; mvv_write(0x29, sub); mvv_write(0x2A, data); mvv_write(0x28, slave); outb(0x28, io_port); count=0; while((inb(data_port)&1)==0) if(count>255) break; while((inb(data_port)&1)!=0) if(count>255) break; count=inb(data_port); if(count&2) return -1; return count; } static int pms_i2c_read(int slave, int sub) { int i=0; for(i=0;i<i2c_count;i++) { if(i2cinfo[i].slave==slave && i2cinfo[i].sub==sub) return i2cinfo[i].data; } return 0; } static void pms_i2c_andor(int slave, int sub, int and, int or) { u8 tmp; tmp=pms_i2c_read(slave, sub); tmp = (tmp&and)|or; pms_i2c_write(slave, sub, tmp); } /* * Control functions */ static void pms_videosource(short source) { mvv_write(0x2E, source?0x31:0x30); } static void pms_hue(short hue) { switch(decoder) { case MOTOROLA: pms_i2c_write(0x8A, 0x00, hue); break; case PHILIPS2: pms_i2c_write(0x8A, 0x07, hue); break; case PHILIPS1: pms_i2c_write(0x42, 0x07, hue); break; } } static void pms_colour(short colour) { switch(decoder) { case MOTOROLA: pms_i2c_write(0x8A, 0x00, colour); break; case PHILIPS1: pms_i2c_write(0x42, 0x12, colour); break; } } static void pms_contrast(short contrast) { switch(decoder) { case MOTOROLA: pms_i2c_write(0x8A, 0x00, contrast); break; case PHILIPS1: pms_i2c_write(0x42, 0x13, contrast); break; } } static void pms_brightness(short brightness) { switch(decoder) { case MOTOROLA: pms_i2c_write(0x8A, 0x00, brightness); pms_i2c_write(0x8A, 0x00, brightness); pms_i2c_write(0x8A, 0x00, brightness); break; case PHILIPS1: pms_i2c_write(0x42, 0x19, brightness); break; } } static void pms_format(short format) { int target; standard = format; if(decoder==PHILIPS1) target=0x42; else if(decoder==PHILIPS2) target=0x8A; else return; switch(format) { case 0: /* Auto */ pms_i2c_andor(target, 0x0D, 0xFE,0x00); pms_i2c_andor(target, 0x0F, 0x3F,0x80); break; case 1: /* NTSC */ pms_i2c_andor(target, 0x0D, 0xFE, 0x00); pms_i2c_andor(target, 0x0F, 0x3F, 0x40); break; case 2: /* PAL */ pms_i2c_andor(target, 0x0D, 0xFE, 0x00); pms_i2c_andor(target, 0x0F, 0x3F, 0x00); break; case 3: /* SECAM */ pms_i2c_andor(target, 0x0D, 0xFE, 0x01); pms_i2c_andor(target, 0x0F, 0x3F, 0x00); break; } } #ifdef FOR_FUTURE_EXPANSION /* * These features of the PMS card are not currently exposes. They * could become a private v4l ioctl for PMSCONFIG or somesuch if * people need it. We also don't yet use the PMS interrupt. */ static void pms_hstart(short start) { switch(decoder) { case PHILIPS1: pms_i2c_write(0x8A, 0x05, start); pms_i2c_write(0x8A, 0x18, start); break; case PHILIPS2: pms_i2c_write(0x42, 0x05, start); pms_i2c_write(0x42, 0x18, start); break; } } /* * Bandpass filters */ static void pms_bandpass(short pass) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x06, 0xCF, (pass&0x03)<<4); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x06, 0xCF, (pass&0x03)<<4); } static void pms_antisnow(short snow) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x06, 0xF3, (snow&0x03)<<2); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x06, 0xF3, (snow&0x03)<<2); } static void pms_sharpness(short sharp) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x06, 0xFC, sharp&0x03); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x06, 0xFC, sharp&0x03); } static void pms_chromaagc(short agc) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x0C, 0x9F, (agc&0x03)<<5); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x0C, 0x9F, (agc&0x03)<<5); } static void pms_vertnoise(short noise) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x10, 0xFC, noise&3); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x10, 0xFC, noise&3); } static void pms_forcecolour(short colour) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x0C, 0x7F, (colour&1)<<7); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x0C, 0x7, (colour&1)<<7); } static void pms_antigamma(short gamma) { if(decoder==PHILIPS2) pms_i2c_andor(0xB8, 0x00, 0x7F, (gamma&1)<<7); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x20, 0x7, (gamma&1)<<7); } static void pms_prefilter(short filter) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x06, 0xBF, (filter&1)<<6); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x06, 0xBF, (filter&1)<<6); } static void pms_hfilter(short filter) { if(decoder==PHILIPS2) pms_i2c_andor(0xB8, 0x04, 0x1F, (filter&7)<<5); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x24, 0x1F, (filter&7)<<5); } static void pms_vfilter(short filter) { if(decoder==PHILIPS2) pms_i2c_andor(0xB8, 0x08, 0x9F, (filter&3)<<5); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x28, 0x9F, (filter&3)<<5); } static void pms_killcolour(short colour) { if(decoder==PHILIPS2) { pms_i2c_andor(0x8A, 0x08, 0x07, (colour&0x1F)<<3); pms_i2c_andor(0x8A, 0x09, 0x07, (colour&0x1F)<<3); } else if(decoder==PHILIPS1) { pms_i2c_andor(0x42, 0x08, 0x07, (colour&0x1F)<<3); pms_i2c_andor(0x42, 0x09, 0x07, (colour&0x1F)<<3); } } static void pms_chromagain(short chroma) { if(decoder==PHILIPS2) { pms_i2c_write(0x8A, 0x11, chroma); } else if(decoder==PHILIPS1) { pms_i2c_write(0x42, 0x11, chroma); } } static void pms_spacialcompl(short data) { mvv_write(0x3B, data); } static void pms_spacialcomph(short data) { mvv_write(0x3A, data); } static void pms_vstart(short start) { mvv_write(0x16, start); mvv_write(0x17, (start>>8)&0x01); } #endif static void pms_secamcross(short cross) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A, 0x0F, 0xDF, (cross&1)<<5); else if(decoder==PHILIPS1) pms_i2c_andor(0x42, 0x0F, 0xDF, (cross&1)<<5); } static void pms_swsense(short sense) { if(decoder==PHILIPS2) { pms_i2c_write(0x8A, 0x0A, sense); pms_i2c_write(0x8A, 0x0B, sense); } else if(decoder==PHILIPS1) { pms_i2c_write(0x42, 0x0A, sense); pms_i2c_write(0x42, 0x0B, sense); } } static void pms_framerate(short frr) { int fps=(standard==1)?30:25; if(frr==0) return; fps=fps/frr; mvv_write(0x14,0x80|fps); mvv_write(0x15,1); } static void pms_vert(u8 deciden, u8 decinum) { mvv_write(0x1C, deciden); /* Denominator */ mvv_write(0x1D, decinum); /* Numerator */ } /* * Turn 16bit ratios into best small ratio the chipset can grok */ static void pms_vertdeci(unsigned short decinum, unsigned short deciden) { /* Knock it down by /5 once */ if(decinum%5==0) { deciden/=5; decinum/=5; } /* * 3's */ while(decinum%3==0 && deciden%3==0) { deciden/=3; decinum/=3; } /* * 2's */ while(decinum%2==0 && deciden%2==0) { decinum/=2; deciden/=2; } /* * Fudgyify */ while(deciden>32) { deciden/=2; decinum=(decinum+1)/2; } if(deciden==32) deciden--; pms_vert(deciden,decinum); } static void pms_horzdeci(short decinum, short deciden) { if(decinum<=512) { if(decinum%5==0) { decinum/=5; deciden/=5; } } else { decinum=512; deciden=640; /* 768 would be ideal */ } while(((decinum|deciden)&1)==0) { decinum>>=1; deciden>>=1; } while(deciden>32) { deciden>>=1; decinum=(decinum+1)>>1; } if(deciden==32) deciden--; mvv_write(0x24, 0x80|deciden); mvv_write(0x25, decinum); } static void pms_resolution(short width, short height) { int fg_height; fg_height=height; if(fg_height>280) fg_height=280; mvv_write(0x18, fg_height); mvv_write(0x19, fg_height>>8); if(standard==1) { mvv_write(0x1A, 0xFC); mvv_write(0x1B, 0x00); if(height>fg_height) pms_vertdeci(240,240); else pms_vertdeci(fg_height,240); } else { mvv_write(0x1A, 0x1A); mvv_write(0x1B, 0x01); if(fg_height>256) pms_vertdeci(270,270); else pms_vertdeci(fg_height, 270); } mvv_write(0x12,0); mvv_write(0x13, MVVMEMORYWIDTH); mvv_write(0x42, 0x00); mvv_write(0x43, 0x00); mvv_write(0x44, MVVMEMORYWIDTH); mvv_write(0x22, width+8); mvv_write(0x23, (width+8)>> 8); if(standard==1) pms_horzdeci(width,640); else pms_horzdeci(width+8, 768); mvv_write(0x30, mvv_read(0x30)&0xFE); mvv_write(0x08, mvv_read(0x08)|0x01); mvv_write(0x01, mvv_read(0x01)&0xFD); mvv_write(0x32, 0x00); mvv_write(0x33, MVVMEMORYWIDTH); } /* * Set Input */ static void pms_vcrinput(short input) { if(decoder==PHILIPS2) pms_i2c_andor(0x8A,0x0D,0x7F,(input&1)<<7); else if(decoder==PHILIPS1) pms_i2c_andor(0x42,0x0D,0x7F,(input&1)<<7); } static int pms_capture(struct pms_device *dev, char __user *buf, int rgb555, int count) { int y; int dw = 2*dev->width; char tmp[dw+32]; /* using a temp buffer is faster than direct */ int cnt = 0; int len=0; unsigned char r8 = 0x5; /* value for reg8 */ if (rgb555) r8 |= 0x20; /* else use untranslated rgb = 565 */ mvv_write(0x08,r8); /* capture rgb555/565, init DRAM, PC enable */ /* printf("%d %d %d %d %d %x %x\n",width,height,voff,nom,den,mvv_buf); */ for (y = 0; y < dev->height; y++ ) { writeb(0, mem); /* synchronisiert neue Zeile */ /* * This is in truth a fifo, be very careful as if you * forgot this odd things will occur 8) */ memcpy_fromio(tmp, mem, dw+32); /* discard 16 word */ cnt -= dev->height; while (cnt <= 0) { /* * Don't copy too far */ int dt=dw; if(dt+len>count) dt=count-len; cnt += dev->height; if (copy_to_user(buf, tmp+32, dt)) return len ? len : -EFAULT; buf += dt; len += dt; } } return len; } /* * Video4linux interfacing */ static long pms_do_ioctl(struct file *file, unsigned int cmd, void *arg) { struct video_device *dev = video_devdata(file); struct pms_device *pd=(struct pms_device *)dev; switch(cmd) { case VIDIOCGCAP: { struct video_capability *b = arg; strcpy(b->name, "Mediavision PMS"); b->type = VID_TYPE_CAPTURE|VID_TYPE_SCALES; b->channels = 4; b->audios = 0; b->maxwidth = 640; b->maxheight = 480; b->minwidth = 16; b->minheight = 16; return 0; } case VIDIOCGCHAN: { struct video_channel *v = arg; if(v->channel<0 || v->channel>3) return -EINVAL; v->flags=0; v->tuners=1; /* Good question.. its composite or SVHS so.. */ v->type = VIDEO_TYPE_CAMERA; switch(v->channel) { case 0: strcpy(v->name, "Composite");break; case 1: strcpy(v->name, "SVideo");break; case 2: strcpy(v->name, "Composite(VCR)");break; case 3: strcpy(v->name, "SVideo(VCR)");break; } return 0; } case VIDIOCSCHAN: { struct video_channel *v = arg; if(v->channel<0 || v->channel>3) return -EINVAL; mutex_lock(&pd->lock); pms_videosource(v->channel&1); pms_vcrinput(v->channel>>1); mutex_unlock(&pd->lock); return 0; } case VIDIOCGTUNER: { struct video_tuner *v = arg; if(v->tuner) return -EINVAL; strcpy(v->name, "Format"); v->rangelow=0; v->rangehigh=0; v->flags= VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; switch(standard) { case 0: v->mode = VIDEO_MODE_AUTO; break; case 1: v->mode = VIDEO_MODE_NTSC; break; case 2: v->mode = VIDEO_MODE_PAL; break; case 3: v->mode = VIDEO_MODE_SECAM; break; } return 0; } case VIDIOCSTUNER: { struct video_tuner *v = arg; if(v->tuner) return -EINVAL; mutex_lock(&pd->lock); switch(v->mode) { case VIDEO_MODE_AUTO: pms_framerate(25); pms_secamcross(0); pms_format(0); break; case VIDEO_MODE_NTSC: pms_framerate(30); pms_secamcross(0); pms_format(1); break; case VIDEO_MODE_PAL: pms_framerate(25); pms_secamcross(0); pms_format(2); break; case VIDEO_MODE_SECAM: pms_framerate(25); pms_secamcross(1); pms_format(2); break; default: mutex_unlock(&pd->lock); return -EINVAL; } mutex_unlock(&pd->lock); return 0; } case VIDIOCGPICT: { struct video_picture *p = arg; *p = pd->picture; return 0; } case VIDIOCSPICT: { struct video_picture *p = arg; if(!((p->palette==VIDEO_PALETTE_RGB565 && p->depth==16) ||(p->palette==VIDEO_PALETTE_RGB555 && p->depth==15))) return -EINVAL; pd->picture= *p; /* * Now load the card. */ mutex_lock(&pd->lock); pms_brightness(p->brightness>>8); pms_hue(p->hue>>8); pms_colour(p->colour>>8); pms_contrast(p->contrast>>8); mutex_unlock(&pd->lock); return 0; } case VIDIOCSWIN: { struct video_window *vw = arg; if(vw->flags) return -EINVAL; if(vw->clipcount) return -EINVAL; if(vw->height<16||vw->height>480) return -EINVAL; if(vw->width<16||vw->width>640) return -EINVAL; pd->width=vw->width; pd->height=vw->height; mutex_lock(&pd->lock); pms_resolution(pd->width, pd->height); mutex_unlock(&pd->lock); /* Ok we figured out what to use from our wide choice */ return 0; } case VIDIOCGWIN: { struct video_window *vw = arg; memset(vw,0,sizeof(*vw)); vw->width=pd->width; vw->height=pd->height; return 0; } case VIDIOCKEY: return 0; case VIDIOCCAPTURE: case VIDIOCGFBUF: case VIDIOCSFBUF: case VIDIOCGFREQ: case VIDIOCSFREQ: case VIDIOCGAUDIO: case VIDIOCSAUDIO: return -EINVAL; default: return -ENOIOCTLCMD; } return 0; } static long pms_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return video_usercopy(file, cmd, arg, pms_do_ioctl); } static ssize_t pms_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct video_device *v = video_devdata(file); struct pms_device *pd=(struct pms_device *)v; int len; mutex_lock(&pd->lock); len=pms_capture(pd, buf, (pd->picture.depth==16)?0:1,count); mutex_unlock(&pd->lock); return len; } static int pms_exclusive_open(struct file *file) { struct video_device *v = video_devdata(file); struct pms_device *pd = (struct pms_device *)v; return test_and_set_bit(0, &pd->in_use) ? -EBUSY : 0; } static int pms_exclusive_release(struct file *file) { struct video_device *v = video_devdata(file); struct pms_device *pd = (struct pms_device *)v; clear_bit(0, &pd->in_use); return 0; } static const struct v4l2_file_operations pms_fops = { .owner = THIS_MODULE, .open = pms_exclusive_open, .release = pms_exclusive_release, .ioctl = pms_ioctl, .read = pms_read, }; static struct video_device pms_template= { .name = "Mediavision PMS", .fops = &pms_fops, .release = video_device_release_empty, }; static struct pms_device pms_device; /* * Probe for and initialise the Mediavision PMS */ static int init_mediavision(void) { int id; int idec, decst; int i; unsigned char i2c_defs[]={ 0x4C,0x30,0x00,0xE8, 0xB6,0xE2,0x00,0x00, 0xFF,0xFF,0x00,0x00, 0x00,0x00,0x78,0x98, 0x00,0x00,0x00,0x00, 0x34,0x0A,0xF4,0xCE, 0xE4 }; mem = ioremap(mem_base, 0x800); if (!mem) return -ENOMEM; if (!request_region(0x9A01, 1, "Mediavision PMS config")) { printk(KERN_WARNING "mediavision: unable to detect: 0x9A01 in use.\n"); iounmap(mem); return -EBUSY; } if (!request_region(io_port, 3, "Mediavision PMS")) { printk(KERN_WARNING "mediavision: I/O port %d in use.\n", io_port); release_region(0x9A01, 1); iounmap(mem); return -EBUSY; } outb(0xB8, 0x9A01); /* Unlock */ outb(io_port>>4, 0x9A01); /* Set IO port */ id=mvv_read(3); decst=pms_i2c_stat(0x43); if(decst!=-1) idec=2; else if(pms_i2c_stat(0xb9)!=-1) idec=3; else if(pms_i2c_stat(0x8b)!=-1) idec=1; else idec=0; printk(KERN_INFO "PMS type is %d\n", idec); if(idec == 0) { release_region(io_port, 3); release_region(0x9A01, 1); iounmap(mem); return -ENODEV; } /* * Ok we have a PMS of some sort */ mvv_write(0x04, mem_base>>12); /* Set the memory area */ /* Ok now load the defaults */ for(i=0;i<0x19;i++) { if(i2c_defs[i]==0xFF) pms_i2c_andor(0x8A, i, 0x07,0x00); else pms_i2c_write(0x8A, i, i2c_defs[i]); } pms_i2c_write(0xB8,0x00,0x12); pms_i2c_write(0xB8,0x04,0x00); pms_i2c_write(0xB8,0x07,0x00); pms_i2c_write(0xB8,0x08,0x00); pms_i2c_write(0xB8,0x09,0xFF); pms_i2c_write(0xB8,0x0A,0x00); pms_i2c_write(0xB8,0x0B,0x10); pms_i2c_write(0xB8,0x10,0x03); mvv_write(0x01, 0x00); mvv_write(0x05, 0xA0); mvv_write(0x08, 0x25); mvv_write(0x09, 0x00); mvv_write(0x0A, 0x20|MVVMEMORYWIDTH); mvv_write(0x10, 0x02); mvv_write(0x1E, 0x0C); mvv_write(0x1F, 0x03); mvv_write(0x26, 0x06); mvv_write(0x2B, 0x00); mvv_write(0x2C, 0x20); mvv_write(0x2D, 0x00); mvv_write(0x2F, 0x70); mvv_write(0x32, 0x00); mvv_write(0x33, MVVMEMORYWIDTH); mvv_write(0x34, 0x00); mvv_write(0x35, 0x00); mvv_write(0x3A, 0x80); mvv_write(0x3B, 0x10); mvv_write(0x20, 0x00); mvv_write(0x21, 0x00); mvv_write(0x30, 0x22); return 0; } /* * Initialization and module stuff */ #ifndef MODULE static int enable; module_param(enable, int, 0); #endif static int __init init_pms_cards(void) { printk(KERN_INFO "Mediavision Pro Movie Studio driver 0.02\n"); #ifndef MODULE if (!enable) { printk(KERN_INFO "PMS: not enabled, use pms.enable=1 to " "probe\n"); return -ENODEV; } #endif data_port = io_port +1; if(init_mediavision()) { printk(KERN_INFO "Board not found.\n"); return -ENODEV; } memcpy(&pms_device, &pms_template, sizeof(pms_template)); mutex_init(&pms_device.lock); pms_device.height=240; pms_device.width=320; pms_swsense(75); pms_resolution(320,240); return video_register_device((struct video_device *)&pms_device, VFL_TYPE_GRABBER, video_nr); } module_param(io_port, int, 0); module_param(mem_base, int, 0); module_param(video_nr, int, 0); MODULE_LICENSE("GPL"); static void __exit shutdown_mediavision(void) { release_region(io_port,3); release_region(0x9A01, 1); } static void __exit cleanup_pms_module(void) { shutdown_mediavision(); video_unregister_device((struct video_device *)&pms_device); iounmap(mem); } module_init(init_pms_cards); module_exit(cleanup_pms_module);