diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-08-28 22:00:09 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-08-28 22:00:09 +0000 |
commit | 1a1d77dd589de5a567fa95e36aa6999c704ceca4 (patch) | |
tree | 141e31f89f18b9fe0831f31852e0435ceaccafc5 /drivers/media | |
parent | fb9c690a18b3d66925a65b17441c37fa14d4370b (diff) |
Merge with 2.4.0-test7.
Diffstat (limited to 'drivers/media')
71 files changed, 43800 insertions, 0 deletions
diff --git a/drivers/media/Config.in b/drivers/media/Config.in new file mode 100644 index 000000000..ad64c65d4 --- /dev/null +++ b/drivers/media/Config.in @@ -0,0 +1,13 @@ +# +# Multimedia device configuration +# +mainmenu_option next_comment +comment 'Multimedia devices' + +tristate 'Video For Linux' CONFIG_VIDEO_DEV +if [ "$CONFIG_VIDEO_DEV" != "n" ]; then + source drivers/media/video/Config.in + source drivers/media/radio/Config.in +fi + +endmenu diff --git a/drivers/media/Makefile b/drivers/media/Makefile new file mode 100644 index 000000000..348687b5f --- /dev/null +++ b/drivers/media/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the kernel multimedia device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# + +MEDIAS = $(join $(SUB_DIRS),$(SUB_DIRS:%=/%.o)) + +SUB_DIRS := video radio +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +O_TARGET := media.o +O_OBJS := happy.o $(MEDIAS) + +include $(TOPDIR)/Rules.make diff --git a/drivers/media/happy.c b/drivers/media/happy.c new file mode 100644 index 000000000..4954935c1 --- /dev/null +++ b/drivers/media/happy.c @@ -0,0 +1 @@ +/* This is here to keep the linker happy. */ diff --git a/drivers/media/radio/Config.in b/drivers/media/radio/Config.in new file mode 100644 index 000000000..34b396acf --- /dev/null +++ b/drivers/media/radio/Config.in @@ -0,0 +1,52 @@ +# +# Multimedia Video device configuration +# +mainmenu_option next_comment +comment 'Radio Adapters' + +dep_tristate ' ADS Cadet AM/FM Tuner' CONFIG_RADIO_CADET $CONFIG_VIDEO_DEV +dep_tristate ' AIMSlab RadioTrack (aka RadioReveal) support' CONFIG_RADIO_RTRACK $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_RTRACK" = "y" ]; then + hex ' RadioTrack i/o port (0x20f or 0x30f)' CONFIG_RADIO_RTRACK_PORT 20f +fi +dep_tristate ' AIMSlab RadioTrack II support' CONFIG_RADIO_RTRACK2 $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_RTRACK2" = "y" ]; then + hex ' RadioTrack II i/o port (0x20c or 0x30c)' CONFIG_RADIO_RTRACK2_PORT 30c +fi +dep_tristate ' Aztech/Packard Bell Radio' CONFIG_RADIO_AZTECH $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_AZTECH" = "y" ]; then + hex ' Aztech/Packard Bell I/O port (0x350 or 0x358)' CONFIG_RADIO_AZTECH_PORT 350 +fi +dep_tristate ' GemTek Radio Card support' CONFIG_RADIO_GEMTEK $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_GEMTEK" = "y" ]; then + hex ' GemTek i/o port (0x20c, 0x30c, 0x24c or 0x34c)' CONFIG_RADIO_GEMTEK_PORT 34c +fi +dep_tristate ' Miro PCM20 Radio' CONFIG_RADIO_MIROPCM20 $CONFIG_VIDEO_DEV +dep_tristate ' SF16FMI Radio' CONFIG_RADIO_SF16FMI $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_SF16FMI" = "y" ]; then + hex ' SF16FMI I/O port (0x284 or 0x384)' CONFIG_RADIO_SF16FMI_PORT 284 +fi +dep_tristate ' TerraTec ActiveRadio ISA Standalone' CONFIG_RADIO_TERRATEC $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_TERRATEC" = "y" ]; then + hex ' Terratec i/o port (normally 0x590)' CONFIG_RADIO_TERRATEC_PORT 590 +fi +dep_tristate ' Trust FM radio card' CONFIG_RADIO_TRUST $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_TRUST" = "y" ]; then + hex ' Trust i/o port (usually 0x350 or 0x358)' CONFIG_RADIO_TRUST_PORT 350 +fi +dep_tristate ' Typhoon Radio (a.k.a. EcoRadio)' CONFIG_RADIO_TYPHOON $CONFIG_VIDEO_DEV +if [ "$CONFIG_PROC_FS" = "y" ]; then + if [ "$CONFIG_RADIO_TYPHOON" != "n" ]; then + bool ' Support for /proc/radio-typhoon' CONFIG_RADIO_TYPHOON_PROC_FS + fi +fi +if [ "$CONFIG_RADIO_TYPHOON" = "y" ]; then + hex ' Typhoon I/O port (0x316 or 0x336)' CONFIG_RADIO_TYPHOON_PORT 316 + int ' Typhoon frequency set when muting the device (kHz)' CONFIG_RADIO_TYPHOON_MUTEFREQ 87500 +fi +dep_tristate ' Zoltrix Radio' CONFIG_RADIO_ZOLTRIX $CONFIG_VIDEO_DEV +if [ "$CONFIG_RADIO_ZOLTRIX" = "y" ]; then + hex ' ZOLTRIX I/O port (0x20c or 0x30c)' CONFIG_RADIO_ZOLTRIX_PORT 20c +fi + +endmenu diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile new file mode 100644 index 000000000..1b74d6b88 --- /dev/null +++ b/drivers/media/radio/Makefile @@ -0,0 +1,75 @@ +# +# Makefile for the kernel character device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# + +O_OBJS := +OX_OBJS := +M_OBJS := +MX_OBJS := + +# Object file lists. + +obj-y := +obj-m := +obj-n := +obj- := + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +O_TARGET := radio.o + +# All of the (potential) objects that export symbols. +# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'. + +export-objs := + +list-multi := + +obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o +obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o +obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o +obj-$(CONFIG_RADIO_CADET) += radio-cadet.o +obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o +obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o +obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o +obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o +obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o +obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o +obj-$(CONFIG_RADIO_TRUST) += radio-trust.o + +# Extract lists of the multi-part drivers. +# The 'int-*' lists are the intermediate files used to build the multi's. + +multi-y := $(filter $(list-multi), $(obj-y)) +multi-m := $(filter $(list-multi), $(obj-m)) +int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs))) +int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs))) + +# Files that are both resident and modular: remove from modular. + +obj-m := $(filter-out $(obj-y), $(obj-m)) +int-m := $(filter-out $(int-y), $(int-m)) + +# Take multi-part drivers out of obj-y and put components in. + +obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y) + +# Translate to Rules.make lists. + +O_OBJS := $(filter-out $(export-objs), $(obj-y)) +OX_OBJS := $(filter $(export-objs), $(obj-y)) +M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m))) +MX_OBJS := $(sort $(filter $(export-objs), $(obj-m))) +MI_OBJS := $(sort $(filter-out $(export-objs), $(int-m))) +MIX_OBJS := $(sort $(filter $(export-objs), $(int-m))) + +include $(TOPDIR)/Rules.make diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c new file mode 100644 index 000000000..d716c54bd --- /dev/null +++ b/drivers/media/radio/radio-aimslab.c @@ -0,0 +1,390 @@ +/* radiotrack (radioreveal) driver for Linux radio support + * (c) 1997 M. Kirkwood + * Coverted to new API by Alan Cox <Alan.Cox@linux.org> + * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> + * + * History: + * 1999-02-24 Russell Kroll <rkroll@exploits.org> + * Fine tuning/VIDEO_TUNER_LOW + * Frequency range expanded to start at 87 MHz + * + * TODO: Allow for more than one of these foolish entities :-) + * + * Notes on the hardware (reverse engineered from other peoples' + * reverse engineering of AIMS' code :-) + * + * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); + * + * The signal strength query is unsurprisingly inaccurate. And it seems + * to indicate that (on my card, at least) the frequency setting isn't + * too great. (I have to tune up .025MHz from what the freq should be + * to get a report that the thing is tuned.) + * + * Volume control is (ugh) analogue: + * out(port, start_increasing_volume); + * wait(a_wee_while); + * out(port, stop_changing_the_volume); + * + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_RTRACK_PORT */ +#include <asm/semaphore.h> /* Lock for the I/O */ + +#ifndef CONFIG_RADIO_RTRACK_PORT +#define CONFIG_RADIO_RTRACK_PORT -1 +#endif + +static int io = CONFIG_RADIO_RTRACK_PORT; +static int users = 0; +static struct semaphore lock; + +struct rt_device +{ + int port; + int curvol; + unsigned long curfreq; + int muted; +}; + + +/* local things */ + +static void sleep_delay(long n) +{ + /* Sleep nicely for 'n' uS */ + int d=n/(1000000/HZ); + if(!d) + udelay(n); + else + { + /* Yield CPU time */ + unsigned long x=jiffies; + while((jiffies-x)<=d) + schedule(); + } +} + +static void rt_decvol(void) +{ + outb(0x58, io); /* volume down + sigstr + on */ + sleep_delay(100000); + outb(0xd8, io); /* volume steady + sigstr + on */ +} + +static void rt_incvol(void) +{ + outb(0x98, io); /* volume up + sigstr + on */ + sleep_delay(100000); + outb(0xd8, io); /* volume steady + sigstr + on */ +} + +static void rt_mute(struct rt_device *dev) +{ + dev->muted = 1; + down(&lock); + outb(0xd0, io); /* volume steady, off */ + up(&lock); +} + +static int rt_setvol(struct rt_device *dev, int vol) +{ + int i; + + down(&lock); + + if(vol == dev->curvol) { /* requested volume = current */ + if (dev->muted) { /* user is unmuting the card */ + dev->muted = 0; + outb (0xd8, io); /* enable card */ + } + up(&lock); + return 0; + } + + if(vol == 0) { /* volume = 0 means mute the card */ + outb(0x48, io); /* volume down but still "on" */ + sleep_delay(2000000); /* make sure it's totally down */ + outb(0xd0, io); /* volume steady, off */ + dev->curvol = 0; /* track the volume state! */ + up(&lock); + return 0; + } + + dev->muted = 0; + if(vol > dev->curvol) + for(i = dev->curvol; i < vol; i++) + rt_incvol(); + else + for(i = dev->curvol; i > vol; i--) + rt_decvol(); + + dev->curvol = vol; + up(&lock); + return 0; +} + +/* the 128+64 on these outb's is to keep the volume stable while tuning + * without them, the volume _will_ creep up with each frequency change + * and bit 4 (+16) is to keep the signal strength meter enabled + */ + +void send_0_byte(int port, struct rt_device *dev) +{ + if ((dev->curvol == 0) || (dev->muted)) { + outb_p(128+64+16+ 1, port); /* wr-enable + data low */ + outb_p(128+64+16+2+1, port); /* clock */ + } + else { + outb_p(128+64+16+8+ 1, port); /* on + wr-enable + data low */ + outb_p(128+64+16+8+2+1, port); /* clock */ + } + sleep_delay(1000); +} + +void send_1_byte(int port, struct rt_device *dev) +{ + if ((dev->curvol == 0) || (dev->muted)) { + outb_p(128+64+16+4 +1, port); /* wr-enable+data high */ + outb_p(128+64+16+4+2+1, port); /* clock */ + } + else { + outb_p(128+64+16+8+4 +1, port); /* on+wr-enable+data high */ + outb_p(128+64+16+8+4+2+1, port); /* clock */ + } + + sleep_delay(1000); +} + +static int rt_setfreq(struct rt_device *dev, unsigned long freq) +{ + int i; + + /* adapted from radio-aztech.c */ + + /* now uses VIDEO_TUNER_LOW for fine tuning */ + + freq += 171200; /* Add 10.7 MHz IF */ + freq /= 800; /* Convert to 50 kHz units */ + + down(&lock); /* Stop other ops interfering */ + + send_0_byte (io, dev); /* 0: LSB of frequency */ + + for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ + if (freq & (1 << i)) + send_1_byte (io, dev); + else + send_0_byte (io, dev); + + send_0_byte (io, dev); /* 14: test bit - always 0 */ + send_0_byte (io, dev); /* 15: test bit - always 0 */ + + send_0_byte (io, dev); /* 16: band data 0 - always 0 */ + send_0_byte (io, dev); /* 17: band data 1 - always 0 */ + send_0_byte (io, dev); /* 18: band data 2 - always 0 */ + send_0_byte (io, dev); /* 19: time base - always 0 */ + + send_0_byte (io, dev); /* 20: spacing (0 = 25 kHz) */ + send_1_byte (io, dev); /* 21: spacing (1 = 25 kHz) */ + send_0_byte (io, dev); /* 22: spacing (0 = 25 kHz) */ + send_1_byte (io, dev); /* 23: AM/FM (FM = 1, always) */ + + if ((dev->curvol == 0) || (dev->muted)) + outb (0xd0, io); /* volume steady + sigstr */ + else + outb (0xd8, io); /* volume steady + sigstr + on */ + + up(&lock); + + return 0; +} + +static int rt_getsigstr(struct rt_device *dev) +{ + if (inb(io) & 2) /* bit set = no signal present */ + return 0; + return 1; /* signal present */ +} + +static int rt_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct rt_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "RadioTrack"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=(87*16000); + v.rangehigh=(108*16000); + v.flags=VIDEO_TUNER_LOW; + v.mode=VIDEO_MODE_AUTO; + strcpy(v.name, "FM"); + v.signal=0xFFFF*rt_getsigstr(rt); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&rt->curfreq, arg,sizeof(rt->curfreq))) + return -EFAULT; + rt_setfreq(rt, rt->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; + v.volume=rt->curvol * 6554; + v.step=6554; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + if(v.flags&VIDEO_AUDIO_MUTE) + rt_mute(rt); + else + rt_setvol(rt,v.volume/6554); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int rt_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void rt_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct rt_device rtrack_unit; + +static struct video_device rtrack_radio= +{ + "RadioTrack radio", + VID_TYPE_TUNER, + VID_HARDWARE_RTRACK, + rt_open, + rt_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* No poll */ + rt_ioctl, + NULL, + NULL +}; + +static int __init rtrack_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + + if (check_region(io, 2)) + { + printk(KERN_ERR "rtrack: port 0x%x already in use\n", io); + return -EBUSY; + } + + rtrack_radio.priv=&rtrack_unit; + + if(video_register_device(&rtrack_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 2, "rtrack"); + printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n"); + + /* Set up the I/O locking */ + + init_MUTEX(&lock); + + /* mute card - prevents noisy bootups */ + + /* this ensures that the volume is all the way down */ + outb(0x48, io); /* volume down but still "on" */ + sleep_delay(2000000); /* make sure it's totally down */ + outb(0xc0, io); /* steady volume, mute card */ + rtrack_unit.curvol = 0; + + return 0; +} + +MODULE_AUTHOR("M.Kirkwood"); +MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); + +EXPORT_NO_SYMBOLS; + +static void __exit cleanup_rtrack_module(void) +{ + video_unregister_device(&rtrack_radio); + release_region(io,2); +} + +module_init(rtrack_init); +module_exit(cleanup_rtrack_module); + diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c new file mode 100644 index 000000000..2fb8714ad --- /dev/null +++ b/drivers/media/radio/radio-aztech.c @@ -0,0 +1,330 @@ +/* radio-aztech.c - Aztech radio card driver for Linux 2.2 + * + * Adapted to support the Video for Linux API by + * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: + * + * Quay Ly + * Donald Song + * Jason Lewis (jlewis@twilight.vtc.vsc.edu) + * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) + * William McGrath (wmcgrath@twilight.vtc.vsc.edu) + * + * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ + * along with more information on the card itself. + * + * History: + * 1999-02-24 Russell Kroll <rkroll@exploits.org> + * Fine tuning/VIDEO_TUNER_LOW + * Range expanded to 87-108 MHz (from 87.9-107.8) + * + * Notable changes from the original source: + * - includes stripped down to the essentials + * - for loops used as delays replaced with udelay() + * - #defines removed, changed to static values + * - tuning structure changed - no more character arrays, other changes +*/ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */ + +/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ + +#ifndef CONFIG_RADIO_AZTECH_PORT +#define CONFIG_RADIO_AZTECH_PORT -1 +#endif + +static int io = CONFIG_RADIO_AZTECH_PORT; +static int radio_wait_time = 1000; +static int users = 0; +static struct semaphore lock; + +struct az_device +{ + int curvol; + unsigned long curfreq; + int stereo; +}; + +static int volconvert(int level) +{ + level>>=14; /* Map 16bits down to 2 bit */ + level&=3; + + /* convert to card-friendly values */ + switch (level) + { + case 0: + return 0; + case 1: + return 1; + case 2: + return 4; + case 3: + return 5; + } + return 0; /* Quieten gcc */ +} + +static void send_0_byte (struct az_device *dev) +{ + udelay(radio_wait_time); + outb_p(2+volconvert(dev->curvol), io); + outb_p(64+2+volconvert(dev->curvol), io); +} + +static void send_1_byte (struct az_device *dev) +{ + udelay (radio_wait_time); + outb_p(128+2+volconvert(dev->curvol), io); + outb_p(128+64+2+volconvert(dev->curvol), io); +} + +static int az_setvol(struct az_device *dev, int vol) +{ + down(&lock); + outb (volconvert(vol), io); + up(&lock); + return 0; +} + +/* thanks to Michael Dwyer for giving me a dose of clues in + * the signal strength department.. + * + * This card has a stereo bit - bit 0 set = mono, not set = stereo + * It also has a "signal" bit - bit 1 set = bad signal, not set = good + * + */ + +static int az_getsigstr(struct az_device *dev) +{ + if (inb(io) & 2) /* bit set = no signal present */ + return 0; + return 1; /* signal present */ +} + +static int az_getstereo(struct az_device *dev) +{ + if (inb(io) & 1) /* bit set = mono */ + return 0; + return 1; /* stereo */ +} + +static int az_setfreq(struct az_device *dev, unsigned long frequency) +{ + int i; + + frequency += 171200; /* Add 10.7 MHz IF */ + frequency /= 800; /* Convert to 50 kHz units */ + + down(&lock); + + send_0_byte (dev); /* 0: LSB of frequency */ + + for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ + if (frequency & (1 << i)) + send_1_byte (dev); + else + send_0_byte (dev); + + send_0_byte (dev); /* 14: test bit - always 0 */ + send_0_byte (dev); /* 15: test bit - always 0 */ + send_0_byte (dev); /* 16: band data 0 - always 0 */ + if (dev->stereo) /* 17: stereo (1 to enable) */ + send_1_byte (dev); + else + send_0_byte (dev); + + send_1_byte (dev); /* 18: band data 1 - unknown */ + send_0_byte (dev); /* 19: time base - always 0 */ + send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */ + send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */ + send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */ + send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */ + + /* latch frequency */ + + udelay (radio_wait_time); + outb_p(128+64+volconvert(dev->curvol), io); + + up(&lock); + + return 0; +} + +static int az_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct az_device *az=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "Aztech Radio"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=(87*16000); + v.rangehigh=(108*16000); + v.flags=VIDEO_TUNER_LOW; + v.mode=VIDEO_MODE_AUTO; + v.signal=0xFFFF*az_getsigstr(az); + if(az_getstereo(az)) + v.flags|=VIDEO_TUNER_STEREO_ON; + strcpy(v.name, "FM"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &az->curfreq, sizeof(az->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&az->curfreq, arg,sizeof(az->curfreq))) + return -EFAULT; + az_setfreq(az, az->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; + if(az->stereo) + v.mode=VIDEO_SOUND_STEREO; + else + v.mode=VIDEO_SOUND_MONO; + v.volume=az->curvol; + v.step=16384; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + az->curvol=v.volume; + + az->stereo=(v.mode&VIDEO_SOUND_STEREO)?1:0; + if(v.flags&VIDEO_AUDIO_MUTE) + az_setvol(az,0); + else + az_setvol(az,az->curvol); + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int az_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void az_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct az_device aztech_unit; + +static struct video_device aztech_radio= +{ + "Aztech radio", + VID_TYPE_TUNER, + VID_HARDWARE_AZTECH, + az_open, + az_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* No poll */ + az_ioctl, + NULL, + NULL +}; + +static int __init aztech_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + + if (check_region(io, 2)) + { + printk(KERN_ERR "aztech: port 0x%x already in use\n", io); + return -EBUSY; + } + + init_MUTEX(&lock); + aztech_radio.priv=&aztech_unit; + + if(video_register_device(&aztech_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 2, "aztech"); + printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); + /* mute card - prevents noisy bootups */ + outb (0, io); + return 0; +} + +MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); +MODULE_DESCRIPTION("A driver for the Aztech radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); + +EXPORT_NO_SYMBOLS; + +static void __exit aztech_cleanup(void) +{ + video_unregister_device(&aztech_radio); + release_region(io,2); +} + +module_init(aztech_init); +module_exit(aztech_cleanup); diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c new file mode 100644 index 000000000..ed2056dea --- /dev/null +++ b/drivers/media/radio/radio-cadet.c @@ -0,0 +1,603 @@ +/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card + * + * by Fred Gleason <fredg@wava.com> + * Version 0.3.3 + * + * (Loosely) based on code for the Aztech radio card by + * + * Russell Kroll (rkroll@exploits.org) + * Quay Ly + * Donald Song + * Jason Lewis (jlewis@twilight.vtc.vsc.edu) + * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) + * William McGrath (wmcgrath@twilight.vtc.vsc.edu) + * +*/ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_CADET_PORT */ +#include <linux/param.h> + +#ifndef CONFIG_RADIO_CADET_PORT +#define CONFIG_RADIO_CADET_PORT 0x330 +#endif +#define RDS_BUFFER 256 + +static int io=CONFIG_RADIO_CADET_PORT; +static int users=0; +static int curtuner=0; +static int tunestat=0; +static int sigstrength=0; +static wait_queue_head_t tunerq,rdsq,readq; +struct timer_list tunertimer,rdstimer,readtimer; +static __u8 rdsin=0,rdsout=0,rdsstat=0; +static unsigned char rdsbuf[RDS_BUFFER]; +static int cadet_lock=0; + +/* + * Signal Strength Threshold Values + * The V4L API spec does not define any particular unit for the signal + * strength value. These values are in microvolts of RF at the tuner's input. + */ +static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}}; + + + +void cadet_wake(unsigned long qnum) +{ + switch(qnum) { + case 0: /* cadet_setfreq */ + wake_up(&tunerq); + break; + case 1: /* cadet_getrds */ + wake_up(&rdsq); + break; + } +} + + + +static int cadet_getrds(void) +{ + int rdsstat=0; + + cadet_lock++; + outb(3,io); /* Select Decoder Control/Status */ + outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */ + cadet_lock--; + init_timer(&rdstimer); + rdstimer.function=cadet_wake; + rdstimer.data=(unsigned long)1; + rdstimer.expires=jiffies+(HZ/10); + init_waitqueue_head(&rdsq); + add_timer(&rdstimer); + sleep_on(&rdsq); + + cadet_lock++; + outb(3,io); /* Select Decoder Control/Status */ + if((inb(io+1)&0x80)!=0) { + rdsstat|=VIDEO_TUNER_RDS_ON; + } + if((inb(io+1)&0x10)!=0) { + rdsstat|=VIDEO_TUNER_MBS_ON; + } + cadet_lock--; + return rdsstat; +} + + + + +static int cadet_getstereo(void) +{ + if(curtuner!=0) { /* Only FM has stereo capability! */ + return 0; + } + cadet_lock++; + outb(7,io); /* Select tuner control */ + if((inb(io+1)&0x40)==0) { + cadet_lock--; + return 1; /* Stereo pilot detected */ + } + else { + cadet_lock--; + return 0; /* Mono */ + } +} + + + +static unsigned cadet_gettune(void) +{ + int curvol,i; + unsigned fifo=0; + + /* + * Prepare for read + */ + cadet_lock++; + outb(7,io); /* Select tuner control */ + curvol=inb(io+1); /* Save current volume/mute setting */ + outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */ + tunestat=0xffff; + + /* + * Read the shift register + */ + for(i=0;i<25;i++) { + fifo=(fifo<<1)|((inb(io+1)>>7)&0x01); + if(i<24) { + outb(0x01,io+1); + tunestat&=inb(io+1); + outb(0x00,io+1); + } + } + + /* + * Restore volume/mute setting + */ + outb(curvol,io+1); + cadet_lock--; + + return fifo; +} + + + +static unsigned cadet_getfreq(void) +{ + int i; + unsigned freq=0,test,fifo=0; + + /* + * Read current tuning + */ + fifo=cadet_gettune(); + + /* + * Convert to actual frequency + */ + if(curtuner==0) { /* FM */ + test=12500; + for(i=0;i<14;i++) { + if((fifo&0x01)!=0) { + freq+=test; + } + test=test<<1; + fifo=fifo>>1; + } + freq-=10700000; /* IF frequency is 10.7 MHz */ + freq=(freq*16)/1000000; /* Make it 1/16 MHz */ + } + if(curtuner==1) { /* AM */ + freq=((fifo&0x7fff)-2010)*16; + } + + return freq; +} + + + +static void cadet_settune(unsigned fifo) +{ + int i; + unsigned test; + + cadet_lock++; + outb(7,io); /* Select tuner control */ + /* + * Write the shift register + */ + test=0; + test=(fifo>>23)&0x02; /* Align data for SDO */ + test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */ + outb(7,io); /* Select tuner control */ + outb(test,io+1); /* Initialize for write */ + for(i=0;i<25;i++) { + test|=0x01; /* Toggle SCK High */ + outb(test,io+1); + test&=0xfe; /* Toggle SCK Low */ + outb(test,io+1); + fifo=fifo<<1; /* Prepare the next bit */ + test=0x1c|((fifo>>23)&0x02); + outb(test,io+1); + } + cadet_lock--; +} + + + +static void cadet_setfreq(unsigned freq) +{ + unsigned fifo; + int i,j,test; + int curvol; + + /* + * Formulate a fifo command + */ + fifo=0; + if(curtuner==0) { /* FM */ + test=102400; + freq=(freq*1000)/16; /* Make it kHz */ + freq+=10700; /* IF is 10700 kHz */ + for(i=0;i<14;i++) { + fifo=fifo<<1; + if(freq>=test) { + fifo|=0x01; + freq-=test; + } + test=test>>1; + } + } + if(curtuner==1) { /* AM */ + fifo=(freq/16)+2010; /* Make it kHz */ + fifo|=0x100000; /* Select AM Band */ + } + + /* + * Save current volume/mute setting + */ + cadet_lock++; + outb(7,io); /* Select tuner control */ + curvol=inb(io+1); + + /* + * Tune the card + */ + for(j=3;j>-1;j--) { + cadet_settune(fifo|(j<<16)); + outb(7,io); /* Select tuner control */ + outb(curvol,io+1); + cadet_lock--; + init_timer(&tunertimer); + tunertimer.function=cadet_wake; + tunertimer.data=(unsigned long)0; + tunertimer.expires=jiffies+(HZ/10); + init_waitqueue_head(&tunerq); + add_timer(&tunertimer); + sleep_on(&tunerq); + cadet_gettune(); + if((tunestat&0x40)==0) { /* Tuned */ + sigstrength=sigtable[curtuner][j]; + return; + } + cadet_lock++; + } + cadet_lock--; + sigstrength=0; +} + + +static int cadet_getvol(void) +{ + cadet_lock++; + outb(7,io); /* Select tuner control */ + if((inb(io+1)&0x20)!=0) { + cadet_lock--; + return 0xffff; + } + else { + cadet_lock--; + return 0; + } +} + + +static void cadet_setvol(int vol) +{ + cadet_lock++; + outb(7,io); /* Select tuner control */ + if(vol>0) { + outb(0x20,io+1); + } + else { + outb(0x00,io+1); + } + cadet_lock--; +} + + + +void cadet_handler(unsigned long data) +{ + /* + * Service the RDS fifo + */ + if(cadet_lock==0) { + outb(0x3,io); /* Select RDS Decoder Control */ + if((inb(io+1)&0x20)!=0) { + printk(KERN_CRIT "cadet: RDS fifo overflow\n"); + } + outb(0x80,io); /* Select RDS fifo */ + while((inb(io)&0x80)!=0) { + rdsbuf[rdsin++]=inb(io+1); + if(rdsin==rdsout) { + printk(KERN_CRIT "cadet: RDS buffer overflow\n"); + } + } + } + + /* + * Service pending read + */ + if( rdsin!=rdsout) { + wake_up_interruptible(&readq); + } + + /* + * Clean up and exit + */ + init_timer(&readtimer); + readtimer.function=cadet_handler; + readtimer.data=(unsigned long)0; + readtimer.expires=jiffies+(HZ/20); + add_timer(&readtimer); +} + + + +static long cadet_read(struct video_device *v,char *buf,unsigned long count, + int nonblock) +{ + int i=0; + unsigned char readbuf[RDS_BUFFER]; + + if(rdsstat==0) { + cadet_lock++; + rdsstat=1; + outb(0x80,io); /* Select RDS fifo */ + cadet_lock--; + init_timer(&readtimer); + readtimer.function=cadet_handler; + readtimer.data=(unsigned long)0; + readtimer.expires=jiffies+(HZ/20); + add_timer(&readtimer); + } + if(rdsin==rdsout) { + if(nonblock) { + return -EWOULDBLOCK; + } + interruptible_sleep_on(&readq); + } + while((i<count)&&(rdsin!=rdsout)) { + readbuf[i++]=rdsbuf[rdsout++]; + } + if(copy_to_user(buf,readbuf,i)) { + return -EFAULT; + } + return i; +} + + + +static int cadet_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + unsigned freq; + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=2; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "ADS Cadet"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) { + return -EFAULT; + } + if((v.tuner<0)||(v.tuner>1)) { + return -EINVAL; + } + switch(v.tuner) { + case 0: + strcpy(v.name,"FM"); + v.rangelow=1400; /* 87.5 MHz */ + v.rangehigh=1728; /* 108.0 MHz */ + v.flags=0; + v.mode=0; + v.mode|=VIDEO_MODE_AUTO; + v.signal=sigstrength; + if(cadet_getstereo()==1) { + v.flags|=VIDEO_TUNER_STEREO_ON; + } + v.flags|=cadet_getrds(); + if(copy_to_user(arg,&v, sizeof(v))) { + return -EFAULT; + } + break; + case 1: + strcpy(v.name,"AM"); + v.rangelow=8320; /* 520 kHz */ + v.rangehigh=26400; /* 1650 kHz */ + v.flags=0; + v.flags|=VIDEO_TUNER_LOW; + v.mode=0; + v.mode|=VIDEO_MODE_AUTO; + v.signal=sigstrength; + if(copy_to_user(arg,&v, sizeof(v))) { + return -EFAULT; + } + break; + } + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) { + return -EFAULT; + } + if((v.tuner<0)||(v.tuner>1)) { + return -EINVAL; + } + curtuner=v.tuner; + return 0; + } + case VIDIOCGFREQ: + freq=cadet_getfreq(); + if(copy_to_user(arg, &freq, sizeof(freq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&freq, arg,sizeof(freq))) + return -EFAULT; + if((curtuner==0)&&((freq<1400)||(freq>1728))) { + return -EINVAL; + } + if((curtuner==1)&&((freq<8320)||(freq>26400))) { + return -EINVAL; + } + cadet_setfreq(freq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; + if(cadet_getstereo()==0) { + v.mode=VIDEO_SOUND_MONO; + } + else { + v.mode=VIDEO_SOUND_STEREO; + } + v.volume=cadet_getvol(); + v.step=0xffff; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + cadet_setvol(v.volume); + if(v.flags&VIDEO_AUDIO_MUTE) + cadet_setvol(0); + else + cadet_setvol(0xffff); + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + + +static int cadet_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + init_waitqueue_head(&readq); + return 0; +} + +static void cadet_close(struct video_device *dev) +{ + if(rdsstat==1) { + del_timer(&readtimer); + rdsstat=0; + } + users--; + MOD_DEC_USE_COUNT; +} + + +static struct video_device cadet_radio= +{ + "Cadet radio", + VID_TYPE_TUNER, + VID_HARDWARE_CADET, + cadet_open, + cadet_close, + cadet_read, + NULL, /* Can't write */ + NULL, /* No poll */ + cadet_ioctl, + NULL, + NULL +}; + +#ifndef MODULE +static int cadet_probe(void) +{ + static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e}; + int i; + + for(i=0;i<8;i++) { + io=iovals[i]; + if(check_region(io,2)>=0) { + cadet_setfreq(1410); + if(cadet_getfreq()==1410) { + return io; + } + } + } + return -1; +} +#endif + +static int __init cadet_init(void) +{ +#ifndef MODULE + io = cadet_probe (); +#endif + + if(io < 0) { +#ifdef MODULE + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); +#endif + return -EINVAL; + } + if (!request_region(io,2,"cadet")) + return -EBUSY; + if(video_register_device(&cadet_radio,VFL_TYPE_RADIO)==-1) { + release_region(io,2); + return -EINVAL; + } + printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); + return 0; +} + + + +MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); +MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); + +EXPORT_NO_SYMBOLS; + +static void __exit cadet_cleanup_module(void) +{ + video_unregister_device(&cadet_radio); + release_region(io,2); +} + +module_init(cadet_init); +module_exit(cadet_cleanup_module); + diff --git a/drivers/media/radio/radio-gemtek.c b/drivers/media/radio/radio-gemtek.c new file mode 100644 index 000000000..8b53dbd0d --- /dev/null +++ b/drivers/media/radio/radio-gemtek.c @@ -0,0 +1,319 @@ +/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi> + * + * GemTek hasn't released any specs on the card, so the protocol had to + * be reverse engineered with dosemu. + * + * Besides the protocol changes, this is mostly a copy of: + * + * RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff + * + * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood + * Coverted to new API by Alan Cox <Alan.Cox@linux.org> + * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> + * + * TODO: Allow for more than one of these foolish entities :-) + * + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_GEMTEK_PORT */ +#include <linux/spinlock.h> + +#ifndef CONFIG_RADIO_GEMTEK_PORT +#define CONFIG_RADIO_GEMTEK_PORT -1 +#endif + +static int io = CONFIG_RADIO_GEMTEK_PORT; +static int users = 0; +static spinlock_t lock; + +struct gemtek_device +{ + int port; + unsigned long curfreq; + int muted; +}; + + +/* local things */ + +/* the correct way to mute the gemtek may be to write the last written + * frequency || 0x10, but just writing 0x10 once seems to do it as well + */ +static void gemtek_mute(struct gemtek_device *dev) +{ + if(dev->muted) + return; + spin_lock(&lock); + outb(0x10, io); + spin_unlock(&lock); + dev->muted = 1; +} + +static void gemtek_unmute(struct gemtek_device *dev) +{ + if(dev->muted == 0) + return; + spin_lock(&lock); + outb(0x20, io); + spin_unlock(&lock); + dev->muted = 0; +} + +static void zero(void) +{ + outb_p(0x04, io); + udelay(5); + outb_p(0x05, io); + udelay(5); +} + +static void one(void) +{ + outb_p(0x06, io); + udelay(5); + outb_p(0x07, io); + udelay(5); +} + +static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq) +{ + int i; + +/* freq = 78.25*((float)freq/16000.0 + 10.52); */ + + freq /= 16; + freq += 10520; + freq *= 7825; + freq /= 100000; + + spin_lock(&lock); + + /* 2 start bits */ + outb_p(0x03, io); + udelay(5); + outb_p(0x07, io); + udelay(5); + + /* 28 frequency bits (lsb first) */ + for (i = 0; i < 14; i++) + if (freq & (1 << i)) + one(); + else + zero(); + /* 36 unknown bits */ + for (i = 0; i < 11; i++) + zero(); + one(); + for (i = 0; i < 4; i++) + zero(); + one(); + zero(); + + /* 2 end bits */ + outb_p(0x03, io); + udelay(5); + outb_p(0x07, io); + udelay(5); + + spin_unlock(&lock); + + return 0; +} + +int gemtek_getsigstr(struct gemtek_device *dev) +{ + spin_lock(&lock); + inb(io); + udelay(5); + spin_unlock(&lock); + if (inb(io) & 8) /* bit set = no signal present */ + return 0; + return 1; /* signal present */ +} + +static int gemtek_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct gemtek_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "GemTek"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=87*16000; + v.rangehigh=108*16000; + v.flags=VIDEO_TUNER_LOW; + v.mode=VIDEO_MODE_AUTO; + v.signal=0xFFFF*gemtek_getsigstr(rt); + strcpy(v.name, "FM"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&rt->curfreq, arg,sizeof(rt->curfreq))) + return -EFAULT; + /* needs to be called twice in order for getsigstr to work */ + gemtek_setfreq(rt, rt->curfreq); + gemtek_setfreq(rt, rt->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE; + v.volume=1; + v.step=65535; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + if(v.flags&VIDEO_AUDIO_MUTE) + gemtek_mute(rt); + else + gemtek_unmute(rt); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int gemtek_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void gemtek_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct gemtek_device gemtek_unit; + +static struct video_device gemtek_radio= +{ + "GemTek radio", + VID_TYPE_TUNER, + VID_HARDWARE_GEMTEK, + gemtek_open, + gemtek_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* Can't poll */ + gemtek_ioctl, + NULL, + NULL +}; + +static int __init gemtek_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x20c, io=0x30c, io=0x24c or io=0x34c (io=0x020c or io=0x248 for the combined sound/radiocard)\n"); + return -EINVAL; + } + + if (check_region(io, 4)) + { + printk(KERN_ERR "gemtek: port 0x%x already in use\n", io); + return -EBUSY; + } + + gemtek_radio.priv=&gemtek_unit; + + if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 4, "gemtek"); + printk(KERN_INFO "GemTek Radio Card driver.\n"); + + spin_lock_init(&lock); + /* mute card - prevents noisy bootups */ + outb(0x10, io); + udelay(5); + gemtek_unit.muted = 1; + + /* this is _maybe_ unnecessary */ + outb(0x01, io); + + return 0; +} + +MODULE_AUTHOR("Jonas Munsin"); +MODULE_DESCRIPTION("A driver for the GemTek Radio Card"); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the GemTek card (0x20c, 0x30c, 0x24c or 0x34c (0x20c or 0x248 have been reported to work for the combined sound/radiocard))."); + +EXPORT_NO_SYMBOLS; + +static void __exit gemtek_cleanup(void) +{ + video_unregister_device(&gemtek_radio); + release_region(io,4); +} + +module_init(gemtek_init); +module_exit(gemtek_cleanup); + +/* + Local variables: + compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c" + End: +*/ diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c new file mode 100644 index 000000000..1068467c8 --- /dev/null +++ b/drivers/media/radio/radio-miropcm20.c @@ -0,0 +1,239 @@ +/* Miro PCM20 radio driver for Linux radio support + * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> + * Thanks to Norberto Pellici for the ACI device interface specification + * The API part is based on the radiotrack driver by M. Kirkwood + * This driver relies on the aci mixer (drivers/sound/lowlevel/aci.c) + * Look there for further info... + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include "../../sound/miroaci.h" /* ACI Control by acimixer */ + +static int users = 0; + +struct pcm20_device +{ + int port; + int curvol; + unsigned long curfreq; + int muted; +}; + + +/* local things */ + + +static void pcm20_mute(struct pcm20_device *dev) +{ + + dev->muted = 1; + aci_write_cmd(0xa3,0x01); + +} + +static int pcm20_setvol(struct pcm20_device *dev, int vol) +{ + + if(vol == dev->curvol) { /* requested volume = current */ + if (dev->muted) { /* user is unmuting the card */ + dev->muted = 0; + aci_write_cmd(0xa3,0x00); /* enable card */ + } + + return 0; + } + + if(vol == 0) { /* volume = 0 means mute the card */ + aci_write_cmd(0x3d, 0x20); + aci_write_cmd(0x35, 0x20); + return 0; + } + + dev->muted = 0; + aci_write_cmd(0x3d, 32-vol); /* Right Channel */ + aci_write_cmd(0x35, 32-vol); /* Left Channel */ + dev->curvol = vol; + + return 0; +} + +static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq) +{ + unsigned char freql; + unsigned char freqh; + + freq = (freq * 10) / 16; + freql = freq & 0xff; + freqh = freq >> 8; + + + aci_write_cmd_d(0xa7, freql, freqh); /* Tune to frequency */ + + return 0; +} + +int pcm20_getsigstr(struct pcm20_device *dev) +{ + unsigned char buf; + aci_indexed_cmd(0xf0, 0x32, &buf); + if ((buf & 0x80) == 0x80) + return 0; + return 1; /* signal present */ +} + +static int pcm20_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct pcm20_device *pcm20=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + strcpy(v.name, "Miro PCM20"); + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=(int)(87.5*16); + v.rangehigh=(int)(108.0*16); + v.flags=0; + v.mode=VIDEO_MODE_AUTO; + v.signal=0xFFFF*pcm20_getsigstr(pcm20); + strcpy(v.name, "FM"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &pcm20->curfreq, sizeof(pcm20->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&pcm20->curfreq, arg,sizeof(pcm20->curfreq))) + return -EFAULT; + pcm20_setfreq(pcm20, pcm20->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; + v.volume=pcm20->curvol * 2048; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + if(v.flags&VIDEO_AUDIO_MUTE) + pcm20_mute(pcm20); + else + pcm20_setvol(pcm20,v.volume/2048); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int pcm20_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void pcm20_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct pcm20_device pcm20_unit; + +static struct video_device pcm20_radio= +{ + "Miro PCM 20 radio", + VID_TYPE_TUNER, + VID_HARDWARE_RTRACK, + pcm20_open, + pcm20_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* Can't poll */ + pcm20_ioctl, + NULL, + NULL +}; + +static int __init pcm20_init(void) +{ + + pcm20_radio.priv=&pcm20_unit; + + if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + printk(KERN_INFO "Miro PCM20 radio card driver.\n"); + + /* mute card - prevents noisy bootups */ + + /* this ensures that the volume is all the way down */ + + pcm20_unit.curvol = 0; + + return 0; +} + +MODULE_AUTHOR("Ruurd Reitsma"); +MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); + +EXPORT_NO_SYMBOLS; + +static void __exit pcm20_cleanup(void) +{ + video_unregister_device(&pcm20_radio); +} + +module_init(pcm20_init); +module_exit(pcm20_cleanup); + diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c new file mode 100644 index 000000000..c060ded4a --- /dev/null +++ b/drivers/media/radio/radio-rtrack2.c @@ -0,0 +1,280 @@ +/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff + * + * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood + * Coverted to new API by Alan Cox <Alan.Cox@linux.org> + * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> + * + * TODO: Allow for more than one of these foolish entities :-) + * + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_RTRACK2_PORT */ +#include <linux/spinlock.h> + +#ifndef CONFIG_RADIO_RTRACK2_PORT +#define CONFIG_RADIO_RTRACK2_PORT -1 +#endif + +static int io = CONFIG_RADIO_RTRACK2_PORT; +static int users = 0; +static spinlock_t lock; + +struct rt_device +{ + int port; + unsigned long curfreq; + int muted; +}; + + +/* local things */ + +static void rt_mute(struct rt_device *dev) +{ + if(dev->muted) + return; + spin_lock(&lock); + outb(1, io); + spin_unlock(&lock); + dev->muted = 1; +} + +static void rt_unmute(struct rt_device *dev) +{ + if(dev->muted == 0) + return; + spin_lock(&lock); + outb(0, io); + spin_unlock(&lock); + dev->muted = 0; +} + +static void zero(void) +{ + outb_p(1, io); + outb_p(3, io); + outb_p(1, io); +} + +static void one(void) +{ + outb_p(5, io); + outb_p(7, io); + outb_p(5, io); +} + +static int rt_setfreq(struct rt_device *dev, unsigned long freq) +{ + int i; + + freq = freq / 200 + 856; + + spin_lock(&lock); + + outb_p(0xc8, io); + outb_p(0xc9, io); + outb_p(0xc9, io); + + for (i = 0; i < 10; i++) + zero (); + + for (i = 14; i >= 0; i--) + if (freq & (1 << i)) + one (); + else + zero (); + + outb_p(0xc8, io); + if (!dev->muted) + outb_p(0, io); + + spin_unlock(&lock); + return 0; +} + +static int rt_getsigstr(struct rt_device *dev) +{ + if (inb(io) & 2) /* bit set = no signal present */ + return 0; + return 1; /* signal present */ +} + +static int rt_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct rt_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "RadioTrack II"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=88*16000; + v.rangehigh=108*16000; + v.flags=VIDEO_TUNER_LOW; + v.mode=VIDEO_MODE_AUTO; + v.signal=0xFFFF*rt_getsigstr(rt); + strcpy(v.name, "FM"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &rt->curfreq, sizeof(rt->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&rt->curfreq, arg,sizeof(rt->curfreq))) + return -EFAULT; + rt_setfreq(rt, rt->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE; + v.volume=1; + v.step=65535; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + if(v.flags&VIDEO_AUDIO_MUTE) + rt_mute(rt); + else + rt_unmute(rt); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int rt_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void rt_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct rt_device rtrack2_unit; + +static struct video_device rtrack2_radio= +{ + "RadioTrack II radio", + VID_TYPE_TUNER, + VID_HARDWARE_RTRACK2, + rt_open, + rt_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* Can't poll */ + rt_ioctl, + NULL, + NULL +}; + +static int __init rtrack2_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x20c or io=0x30c\n"); + return -EINVAL; + } + if (check_region(io, 4)) + { + printk(KERN_ERR "rtrack2: port 0x%x already in use\n", io); + return -EBUSY; + } + + rtrack2_radio.priv=&rtrack2_unit; + + spin_lock_init(&lock); + if(video_register_device(&rtrack2_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 4, "rtrack2"); + printk(KERN_INFO "AIMSlab Radiotrack II card driver.\n"); + + /* mute card - prevents noisy bootups */ + outb(1, io); + rtrack2_unit.muted = 1; + + return 0; +} + +MODULE_AUTHOR("Ben Pfaff"); +MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); + +EXPORT_NO_SYMBOLS; + +static void __exit rtrack2_cleanup_module(void) +{ + video_unregister_device(&rtrack2_radio); + release_region(io,4); +} + +module_init(rtrack2_init); +module_exit(rtrack2_cleanup_module); + +/* + Local variables: + compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c" + End: +*/ diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c new file mode 100644 index 000000000..55328a96f --- /dev/null +++ b/drivers/media/radio/radio-sf16fmi.c @@ -0,0 +1,339 @@ +/* SF16FMI radio driver for Linux radio support + * heavily based on rtrack driver... + * (c) 1997 M. Kirkwood + * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz + * + * Fitted to new interface by Alan Cox <alan.cox@linux.org> + * Made working and cleaned up functions <mikael.hedin@irf.se> + * + * Notes on the hardware + * + * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); + * No volume control - only mute/unmute - you have to use line volume + * control on SB-part of SF16FMI + * + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_SF16MI_PORT */ +#include <asm/semaphore.h> + +struct fmi_device +{ + int port; + int curvol; /* 1 or 0 */ + unsigned long curfreq; /* freq in kHz */ + __u32 flags; +}; + +#ifndef CONFIG_RADIO_SF16FMI_PORT +#define CONFIG_RADIO_SF16FMI_PORT -1 +#endif + +static int io = CONFIG_RADIO_SF16FMI_PORT; +static int users = 0; +static struct semaphore lock; + +/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ +/* It is only usefull to give freq in intervall of 800 (=0.05Mhz), + * other bits will be truncated, e.g 92.7400016 -> 92.7, but + * 92.7400017 -> 92.75 + */ +#define RSF16_ENCODE(x) ((x)/800+214) +#define RSF16_MINFREQ 87*16000 +#define RSF16_MAXFREQ 108*16000 + +static void outbits(int bits, unsigned int data, int port) +{ + while(bits--) { + if(data & 1) { + outb(5, port); + udelay(6); + outb(7, port); + udelay(6); + } else { + outb(1, port); + udelay(6); + outb(3, port); + udelay(6); + } + data>>=1; + } +} + +static inline void fmi_mute(int port) +{ + down(&lock); + outb(0x00, port); + up(&lock); +} + +static inline void fmi_unmute(int port) +{ + down(&lock); + outb(0x08, port); + up(&lock); +} + +static inline int fmi_setfreq(struct fmi_device *dev) +{ + int myport = dev->port; + unsigned long freq = dev->curfreq; + int i; + + down(&lock); + + outbits(16, RSF16_ENCODE(freq), myport); + outbits(8, 0xC0, myport); + for(i=0; i< 100; i++) + { + udelay(1400); + if(current->need_resched) + schedule(); + } +/* If this becomes allowed use it ... + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ/7); +*/ + + up(&lock); + if (dev->curvol) fmi_unmute(myport); + return 0; +} + +static inline int fmi_getsigstr(struct fmi_device *dev) +{ + int val; + int res; + int myport = dev->port; + int i; + + down(&lock); + val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ + outb(val, myport); + outb(val | 0x10, myport); + for(i=0; i< 100; i++) + { + udelay(1400); + if(current->need_resched) + schedule(); + } +/* If this becomes allowed use it ... + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ/7); +*/ + res = (int)inb(myport+1); + outb(val, myport); + + up(&lock); + return (res & 2) ? 0 : 0xFFFF; +} + +static int fmi_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct fmi_device *fmi=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + strcpy(v.name, "SF16-FMx radio"); + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + int mult; + + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + strcpy(v.name, "FM"); + mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000; + v.rangelow = RSF16_MINFREQ/mult; + v.rangehigh = RSF16_MAXFREQ/mult; + v.flags=fmi->flags; + v.mode=VIDEO_MODE_AUTO; + v.signal = fmi_getsigstr(fmi); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + fmi->flags = v.flags & VIDEO_TUNER_LOW; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long tmp = fmi->curfreq; + if (!(fmi->flags & VIDEO_TUNER_LOW)) + tmp /= 1000; + if(copy_to_user(arg, &tmp, sizeof(tmp))) + return -EFAULT; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long tmp; + if(copy_from_user(&tmp, arg, sizeof(tmp))) + return -EFAULT; + if (!(fmi->flags & VIDEO_TUNER_LOW)) + tmp *= 1000; + if ( tmp<RSF16_MINFREQ || tmp>RSF16_MAXFREQ ) + return -EINVAL; + /*rounding in steps of 800 to match th freq + that will be used */ + fmi->curfreq = (tmp/800)*800; + fmi_setfreq(fmi); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio v; + v.audio=0; + v.volume=0; + v.bass=0; + v.treble=0; + v.flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); + strcpy(v.name, "Radio"); + v.mode=VIDEO_SOUND_STEREO; + v.balance=0; + v.step=0; /* No volume, just (un)mute */ + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + fmi->curvol= v.flags&VIDEO_AUDIO_MUTE ? 0 : 1; + fmi->curvol ? + fmi_unmute(fmi->port) : fmi_mute(fmi->port); + return 0; + } + case VIDIOCGUNIT: + { + struct video_unit v; + v.video=VIDEO_NO_UNIT; + v.vbi=VIDEO_NO_UNIT; + v.radio=dev->minor; + v.audio=0; /* How do we find out this??? */ + v.teletext=VIDEO_NO_UNIT; + if(copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int fmi_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void fmi_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct fmi_device fmi_unit; + +static struct video_device fmi_radio= +{ + "SF16FMx radio", + VID_TYPE_TUNER, + VID_HARDWARE_SF16MI, + fmi_open, + fmi_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* Can't poll */ + fmi_ioctl, + NULL, + NULL +}; + +static int __init fmi_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + if (check_region(io, 2)) + { + printk(KERN_ERR "fmi: port 0x%x already in use\n", io); + return -EBUSY; + } + + fmi_unit.port = io; + fmi_unit.curvol = 0; + fmi_unit.curfreq = 0; + fmi_unit.flags = VIDEO_TUNER_LOW; + fmi_radio.priv = &fmi_unit; + + init_MUTEX(&lock); + + if(video_register_device(&fmi_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 2, "fmi"); + printk(KERN_INFO "SF16FMx radio card driver at 0x%x.\n", io); + printk(KERN_INFO "(c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz.\n"); + /* mute card - prevents noisy bootups */ + fmi_mute(io); + return 0; +} + +MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); +MODULE_DESCRIPTION("A driver for the SF16MI radio."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); + +EXPORT_NO_SYMBOLS; + +static void __exit fmi_cleanup_module(void) +{ + video_unregister_device(&fmi_radio); + release_region(io,2); +} + +module_init(fmi_init); +module_exit(fmi_cleanup_module); + diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c new file mode 100644 index 000000000..1dda1618e --- /dev/null +++ b/drivers/media/radio/radio-terratec.c @@ -0,0 +1,358 @@ +/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support + * (c) 1999 R. Offermanns (rolf@offermanns.de) + * based on the aimslab radio driver from M. Kirkwood + * many thanks to Michael Becker and Friedhelm Birth (from TerraTec) + * + * + * History: + * 1999-05-21 First preview release + * + * Notes on the hardware: + * There are two "main" chips on the card: + * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf) + * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf) + * (you can get the datasheet at the above links) + * + * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); + * Volume Control is done digitally + * + * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday + * (as soon i have understand how to get started :) + * If you can help me out with that, please contact me!! + * + * + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_TERRATEC_PORT */ +#include <linux/spinlock.h> + +#ifndef CONFIG_RADIO_TERRATEC_PORT +#define CONFIG_RADIO_TERRATEC_PORT 0x590 +#endif + +/**************** this ones are for the terratec *******************/ +#define BASEPORT 0x590 +#define VOLPORT 0x591 +#define WRT_DIS 0x00 +#define CLK_OFF 0x00 +#define IIC_DATA 0x01 +#define IIC_CLK 0x02 +#define DATA 0x04 +#define CLK_ON 0x08 +#define WRT_EN 0x10 +/*******************************************************************/ + +static int io = CONFIG_RADIO_TERRATEC_PORT; +static int users = 0; +static spinlock_t lock; + +struct tt_device +{ + int port; + int curvol; + unsigned long curfreq; + int muted; +}; + + +/* local things */ + +static void cardWriteVol(int volume) +{ + int i; + volume = volume+(volume * 32); // change both channels + spin_lock(&lock); + for (i=0;i<8;i++) + { + if (volume & (0x80>>i)) + outb(0x80, VOLPORT); + else outb(0x00, VOLPORT); + } + spin_unlock(&lock); +} + + + +static void tt_mute(struct tt_device *dev) +{ + dev->muted = 1; + cardWriteVol(0); +} + +static int tt_setvol(struct tt_device *dev, int vol) +{ + +// printk(KERN_ERR "setvol called, vol = %d\n", vol); + + if(vol == dev->curvol) { /* requested volume = current */ + if (dev->muted) { /* user is unmuting the card */ + dev->muted = 0; + cardWriteVol(vol); /* enable card */ + } + + return 0; + } + + if(vol == 0) { /* volume = 0 means mute the card */ + cardWriteVol(0); /* "turn off card" by setting vol to 0 */ + dev->curvol = vol; /* track the volume state! */ + return 0; + } + + dev->muted = 0; + + cardWriteVol(vol); + + dev->curvol = vol; + + return 0; + +} + + +/* this is the worst part in this driver */ +/* many more or less strange things are going on here, but hey, it works :) */ + +static int tt_setfreq(struct tt_device *dev, unsigned long freq1) +{ + int freq; + int i; + int p; + int temp; + long rest; + + unsigned char buffer[25]; /* we have to bit shift 25 registers */ + freq = freq1/160; /* convert the freq. to a nice to handel value */ + for(i=24;i>-1;i--) + buffer[i]=0; + + rest = freq*10+10700; /* i once had understood what is going on here */ + /* maybe some wise guy (friedhelm?) can comment this stuff */ + i=13; + p=10; + temp=102400; + while (rest!=0) + { + if (rest%temp == rest) + buffer[i] = 0; + else + { + buffer[i] = 1; + rest = rest-temp; + } + i--; + p--; + temp = temp/2; + } + + spin_lock(&lock); + + for (i=24;i>-1;i--) /* bit shift the values to the radiocard */ + { + if (buffer[i]==1) + { + outb(WRT_EN|DATA, BASEPORT); + outb(WRT_EN|DATA|CLK_ON , BASEPORT); + outb(WRT_EN|DATA, BASEPORT); + } + else + { + outb(WRT_EN|0x00, BASEPORT); + outb(WRT_EN|0x00|CLK_ON , BASEPORT); + } + } + outb(0x00, BASEPORT); + + spin_unlock(&lock); + + return 0; +} + +int tt_getsigstr(struct tt_device *dev) /* TODO */ +{ + if (inb(io) & 2) /* bit set = no signal present */ + return 0; + return 1; /* signal present */ +} + + +/* implement the video4linux api */ + +static int tt_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct tt_device *tt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + strcpy(v.name, "ActiveRadio"); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow=(87*16000); + v.rangehigh=(108*16000); + v.flags=VIDEO_TUNER_LOW; + v.mode=VIDEO_MODE_AUTO; + strcpy(v.name, "FM"); + v.signal=0xFFFF*tt_getsigstr(tt); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &tt->curfreq, sizeof(tt->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if(copy_from_user(&tt->curfreq, arg,sizeof(tt->curfreq))) + return -EFAULT; + tt_setfreq(tt, tt->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v,0, sizeof(v)); + v.flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME; + v.volume=tt->curvol * 6554; + v.step=6554; + strcpy(v.name, "Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + if(v.flags&VIDEO_AUDIO_MUTE) + tt_mute(tt); + else + tt_setvol(tt,v.volume/6554); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int tt_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void tt_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct tt_device terratec_unit; + +static struct video_device terratec_radio= +{ + "TerraTec ActiveRadio", + VID_TYPE_TUNER, + VID_HARDWARE_TERRATEC, + tt_open, + tt_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* No poll */ + tt_ioctl, + NULL, + NULL +}; + +static int __init terratec_init(void) +{ + if(io==-1) + { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + if (check_region(io, 2)) + { + printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io); + return -EBUSY; + } + + terratec_radio.priv=&terratec_unit; + + spin_lock_init(&lock); + + if(video_register_device(&terratec_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 2, "terratec"); + printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n"); + + /* mute card - prevents noisy bootups */ + + /* this ensures that the volume is all the way down */ + cardWriteVol(0); + terratec_unit.curvol = 0; + + return 0; +} + +MODULE_AUTHOR("R.OFFERMANNS & others"); +MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); + +EXPORT_NO_SYMBOLS; + +static void __exit terratec_cleanup_module(void) +{ + video_unregister_device(&terratec_radio); + release_region(io,2); + printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n"); +} + +module_init(terratec_init); +module_exit(terratec_cleanup_module); + diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c new file mode 100644 index 000000000..4e86bda90 --- /dev/null +++ b/drivers/media/radio/radio-trust.c @@ -0,0 +1,350 @@ +/* radio-trust.c - Trust FM Radio card driver for Linux 2.2 + * by Eric Lammerts <eric@scintilla.utwente.nl> + * + * Based on radio-aztech.c. Original notes: + * + * Adapted to support the Video for Linux API by + * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: + * + * Quay Ly + * Donald Song + * Jason Lewis (jlewis@twilight.vtc.vsc.edu) + * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) + * William McGrath (wmcgrath@twilight.vtc.vsc.edu) + * + * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ + */ + +#include <stdarg.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/videodev.h> +#include <linux/config.h> /* CONFIG_RADIO_TRUST_PORT */ + +/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ + +#ifndef CONFIG_RADIO_TRUST_PORT +#define CONFIG_RADIO_TRUST_PORT -1 +#endif + +static int io = CONFIG_RADIO_TRUST_PORT; +static int ioval = 0xf; +static int users = 0; +static __u16 curvol; +static __u16 curbass; +static __u16 curtreble; +static unsigned long curfreq; +static int curstereo; +static int curmute; + +/* i2c addresses */ +#define TDA7318_ADDR 0x88 +#define TSA6060T_ADDR 0xc4 + +#define TR_DELAY do { inb(io); inb(io); inb(io); } while(0) +#define TR_SET_SCL outb(ioval |= 2, io) +#define TR_CLR_SCL outb(ioval &= 0xfd, io) +#define TR_SET_SDA outb(ioval |= 1, io) +#define TR_CLR_SDA outb(ioval &= 0xfe, io) + +static void write_i2c(int n, ...) +{ + unsigned char val, mask; + va_list args; + + va_start(args, n); + + /* start condition */ + TR_SET_SDA; + TR_SET_SCL; + TR_DELAY; + TR_CLR_SDA; + TR_CLR_SCL; + TR_DELAY; + + for(; n; n--) { + val = va_arg(args, unsigned); + for(mask = 0x80; mask; mask >>= 1) { + if(val & mask) + TR_SET_SDA; + else + TR_CLR_SDA; + TR_SET_SCL; + TR_DELAY; + TR_CLR_SCL; + TR_DELAY; + } + /* acknowledge bit */ + TR_SET_SDA; + TR_SET_SCL; + TR_DELAY; + TR_CLR_SCL; + TR_DELAY; + } + + /* stop condition */ + TR_CLR_SDA; + TR_DELAY; + TR_SET_SCL; + TR_DELAY; + TR_SET_SDA; + TR_DELAY; + + va_end(args); +} + +static void tr_setvol(__u16 vol) +{ + curvol = vol / 2048; + write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f); +} + +static int basstreble2chip[15] = { + 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 +}; + +static void tr_setbass(__u16 bass) +{ + curbass = bass / 4370; + write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]); +} + +static void tr_settreble(__u16 treble) +{ + curtreble = treble / 4370; + write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]); +} + +static void tr_setstereo(int stereo) +{ + curstereo = !!stereo; + ioval = (ioval & 0xfb) | (!curstereo << 2); + outb(ioval, io); +} + +static void tr_setmute(int mute) +{ + curmute = !!mute; + ioval = (ioval & 0xf7) | (curmute << 3); + outb(ioval, io); +} + +static int tr_getsigstr(void) +{ + int i, v; + + for(i = 0, v = 0; i < 100; i++) v |= inb(io); + return (v & 1)? 0 : 0xffff; +} + +static int tr_getstereo(void) +{ + /* don't know how to determine it, just return the setting */ + return curstereo; +} + +static void tr_setfreq(unsigned long f) +{ + f /= 160; /* Convert to 10 kHz units */ + f += 1070; /* Add 10.7 MHz IF */ + + write_i2c(5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0); +} + +static int tr_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability v; + + v.type=VID_TYPE_TUNER; + v.channels=1; + v.audios=1; + + /* No we don't do pictures */ + v.maxwidth=0; + v.maxheight=0; + v.minwidth=0; + v.minheight=0; + + strcpy(v.name, "Trust FM Radio"); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + + if(copy_from_user(&v, arg,sizeof(v))!=0) + return -EFAULT; + + if(v.tuner) /* Only 1 tuner */ + return -EINVAL; + + v.rangelow = 87500 * 16; + v.rangehigh = 108000 * 16; + v.flags = VIDEO_TUNER_LOW; + v.mode = VIDEO_MODE_AUTO; + + v.signal = tr_getsigstr(); + if(tr_getstereo()) + v.flags |= VIDEO_TUNER_STEREO_ON; + + strcpy(v.name, "FM"); + + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.tuner != 0) + return -EINVAL; + + return 0; + } + case VIDIOCGFREQ: + if(copy_to_user(arg, &curfreq, sizeof(curfreq))) + return -EFAULT; + return 0; + + case VIDIOCSFREQ: + { + unsigned long f; + + if(copy_from_user(&f, arg, sizeof(curfreq))) + return -EFAULT; + tr_setfreq(f); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio v; + + memset(&v,0, sizeof(v)); + v.flags = VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE; + v.mode = curstereo? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO; + v.volume = curvol * 2048; + v.step = 2048; + v.bass = curbass * 4370; + v.treble = curtreble * 4370; + + strcpy(v.name, "Trust FM Radio"); + if(copy_to_user(arg,&v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.audio) + return -EINVAL; + + tr_setvol(v.volume); + tr_setbass(v.bass); + tr_settreble(v.treble); + tr_setstereo(v.mode & VIDEO_SOUND_STEREO); + tr_setmute(v.flags & VIDEO_AUDIO_MUTE); + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int tr_open(struct video_device *dev, int flags) +{ + if(users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void tr_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct video_device trust_radio= +{ + "Trust FM Radio", + VID_TYPE_TUNER, + VID_HARDWARE_TRUST, + tr_open, + tr_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* No poll */ + tr_ioctl, + NULL, + NULL +}; + +static int __init trust_init(void) +{ + if(io == -1) { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + if(check_region(io, 2)) { + printk(KERN_ERR "trust: port 0x%x already in use\n", io); + return -EBUSY; + } + if(video_register_device(&trust_radio, VFL_TYPE_RADIO)==-1) + return -EINVAL; + + request_region(io, 2, "Trust FM Radio"); + + printk(KERN_INFO "Trust FM Radio card driver v1.0.\n"); + + write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ + write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ + write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ + write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ + write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ + + tr_setvol(0x8000); + tr_setbass(0x8000); + tr_settreble(0x8000); + tr_setstereo(1); + + /* mute card - prevents noisy bootups */ + tr_setmute(1); + + return 0; +} + +MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); +MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); + +EXPORT_NO_SYMBOLS; + +static void __exit cleanup_trust_module(void) +{ + video_unregister_device(&trust_radio); + release_region(io, 2); +} + +module_init(trust_init); +module_exit(cleanup_trust_module); diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c new file mode 100644 index 000000000..a0bbf343f --- /dev/null +++ b/drivers/media/radio/radio-typhoon.c @@ -0,0 +1,402 @@ +/* Typhoon Radio Card driver for radio support + * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de> + * + * Card manufacturer: + * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e + * + * Notes on the hardware + * + * This card has two output sockets, one for speakers and one for line. + * The speaker output has volume control, but only in four discrete + * steps. The line output has neither volume control nor mute. + * + * The card has auto-stereo according to its manual, although it all + * sounds mono to me (even with the Win/DOS drivers). Maybe it's my + * antenna - I really don't know for sure. + * + * Frequency control is done digitally. + * + * Volume control is done digitally, but there are only four different + * possible values. So you should better always turn the volume up and + * use line control. I got the best results by connecting line output + * to the sound card microphone input. For such a configuration the + * volume control has no effect, since volume control only influences + * the speaker output. + * + * There is no explicit mute/unmute. So I set the radio frequency to a + * value where I do expect just noise and turn the speaker volume down. + * The frequency change is necessary since the card never seems to be + * completely silent. + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/proc_fs.h> /* radio card status report */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_TYPHOON_* */ + +#define BANNER "Typhoon Radio Card driver v0.1\n" + +#ifndef CONFIG_RADIO_TYPHOON_PORT +#define CONFIG_RADIO_TYPHOON_PORT -1 +#endif + +#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ +#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 +#endif + +#ifndef CONFIG_PROC_FS +#undef CONFIG_RADIO_TYPHOON_PROC_FS +#endif + +struct typhoon_device { + int users; + int iobase; + int curvol; + int muted; + unsigned long curfreq; + unsigned long mutefreq; +}; + +static void typhoon_setvol_generic(struct typhoon_device *dev, int vol); +static int typhoon_setfreq_generic(struct typhoon_device *dev, + unsigned long frequency); +static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency); +static void typhoon_mute(struct typhoon_device *dev); +static void typhoon_unmute(struct typhoon_device *dev); +static int typhoon_setvol(struct typhoon_device *dev, int vol); +static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg); +static int typhoon_open(struct video_device *dev, int flags); +static void typhoon_close(struct video_device *dev); +#ifdef CONFIG_RADIO_TYPHOON_PROC_FS +static int typhoon_get_info(char *buf, char **start, off_t offset, int len); +#endif + +static void typhoon_setvol_generic(struct typhoon_device *dev, int vol) +{ + vol >>= 14; /* Map 16 bit to 2 bit */ + vol &= 3; + outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */ + outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */ +} + +static int typhoon_setfreq_generic(struct typhoon_device *dev, + unsigned long frequency) +{ + unsigned long outval; + unsigned long x; + + /* + * The frequency transfer curve is not linear. The best fit I could + * get is + * + * outval = -155 + exp((f + 15.55) * 0.057)) + * + * where frequency f is in MHz. Since we don't have exp in the kernel, + * I approximate this function by a third order polynomial. + * + */ + + x = frequency / 160; + outval = (x * x + 2500) / 5000; + outval = (outval * x + 5000) / 10000; + outval -= (10 * x * x + 10433) / 20866; + outval += 4 * x - 11505; + + outb_p((outval >> 8) & 0x01, dev->iobase + 4); + outb_p(outval >> 9, dev->iobase + 6); + outb_p(outval & 0xff, dev->iobase + 8); + + return 0; +} + +static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency) +{ + typhoon_setfreq_generic(dev, frequency); + dev->curfreq = frequency; + return 0; +} + +static void typhoon_mute(struct typhoon_device *dev) +{ + if (dev->muted == 1) + return; + typhoon_setvol_generic(dev, 0); + typhoon_setfreq_generic(dev, dev->mutefreq); + dev->muted = 1; +} + +static void typhoon_unmute(struct typhoon_device *dev) +{ + if (dev->muted == 0) + return; + typhoon_setfreq_generic(dev, dev->curfreq); + typhoon_setvol_generic(dev, dev->curvol); + dev->muted = 0; +} + +static int typhoon_setvol(struct typhoon_device *dev, int vol) +{ + if (dev->muted && vol != 0) { /* user is unmuting the card */ + dev->curvol = vol; + typhoon_unmute(dev); + return 0; + } + if (vol == dev->curvol) /* requested volume == current */ + return 0; + + if (vol == 0) { /* volume == 0 means mute the card */ + typhoon_mute(dev); + dev->curvol = vol; + return 0; + } + typhoon_setvol_generic(dev, vol); + dev->curvol = vol; + return 0; +} + + +static int typhoon_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct typhoon_device *typhoon = dev->priv; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability v; + v.type = VID_TYPE_TUNER; + v.channels = 1; + v.audios = 1; + /* No we don't do pictures */ + v.maxwidth = 0; + v.maxheight = 0; + v.minwidth = 0; + v.minheight = 0; + strcpy(v.name, "Typhoon Radio"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v)) != 0) + return -EFAULT; + if (v.tuner) /* Only 1 tuner */ + return -EINVAL; + v.rangelow = 875 * 1600; + v.rangehigh = 1080 * 1600; + v.flags = VIDEO_TUNER_LOW; + v.mode = VIDEO_MODE_AUTO; + v.signal = 0xFFFF; /* We can't get the signal strength */ + strcpy(v.name, "FM"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.tuner != 0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if (copy_to_user(arg, &typhoon->curfreq, + sizeof(typhoon->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if (copy_from_user(&typhoon->curfreq, arg, + sizeof(typhoon->curfreq))) + return -EFAULT; + typhoon_setfreq(typhoon, typhoon->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v, 0, sizeof(v)); + v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; + v.mode |= VIDEO_SOUND_MONO; + v.volume = typhoon->curvol; + v.step = 1 << 14; + strcpy(v.name, "Typhoon Radio"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.audio) + return -EINVAL; + + if (v.flags & VIDEO_AUDIO_MUTE) + typhoon_mute(typhoon); + else + typhoon_unmute(typhoon); + + if (v.flags & VIDEO_AUDIO_VOLUME) + typhoon_setvol(typhoon, v.volume); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int typhoon_open(struct video_device *dev, int flags) +{ + struct typhoon_device *typhoon = dev->priv; + if (typhoon->users) + return -EBUSY; + typhoon->users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void typhoon_close(struct video_device *dev) +{ + struct typhoon_device *typhoon = dev->priv; + typhoon->users--; + MOD_DEC_USE_COUNT; +} + +static struct typhoon_device typhoon_unit = +{ + 0, /* users */ + CONFIG_RADIO_TYPHOON_PORT, /* iobase */ + 0, /* curvol */ + 0, /* muted */ + CONFIG_RADIO_TYPHOON_MUTEFREQ, /* curfreq */ + CONFIG_RADIO_TYPHOON_MUTEFREQ /* mutefreq */ +}; + +static struct video_device typhoon_radio = +{ + "Typhoon Radio", + VID_TYPE_TUNER, + VID_HARDWARE_TYPHOON, + typhoon_open, + typhoon_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, /* Can't poll */ + typhoon_ioctl, + NULL, + NULL +}; + +#ifdef CONFIG_RADIO_TYPHOON_PROC_FS + +static int typhoon_get_info(char *buf, char **start, off_t offset, int len) +{ + char *out = buf; + + #ifdef MODULE + #define MODULEPROCSTRING "Driver loaded as a module" + #else + #define MODULEPROCSTRING "Driver compiled into kernel" + #endif + + /* output must be kept under PAGE_SIZE */ + out += sprintf(out, BANNER); + out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n"); + out += sprintf(out, "frequency = %lu kHz\n", + typhoon_unit.curfreq >> 4); + out += sprintf(out, "volume = %d\n", typhoon_unit.curvol); + out += sprintf(out, "mute = %s\n", typhoon_unit.muted ? + "on" : "off"); + out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase); + out += sprintf(out, "mute frequency = %lu kHz\n", + typhoon_unit.mutefreq >> 4); + return out - buf; +} + +#endif /* CONFIG_RADIO_TYPHOON_PROC_FS */ + +MODULE_AUTHOR("Dr. Henrik Seidel"); +MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); +MODULE_PARM(mutefreq, "i"); +MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); + +EXPORT_NO_SYMBOLS; + +static int io = -1; + +#ifdef MODULE +static unsigned long mutefreq = 0; +#endif + +static int __init typhoon_init(void) +{ +#ifdef MODULE + if (io == -1) { + printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n"); + return -EINVAL; + } + typhoon_unit.iobase = io; + + if (mutefreq < 87000 || mutefreq > 108500) { + printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n"); + printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); + return -EINVAL; + } + typhoon_unit.mutefreq = mutefreq; +#endif /* MODULE */ + + printk(KERN_INFO BANNER); + io = typhoon_unit.iobase; + if (check_region(io, 8)) { + printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n", + typhoon_unit.iobase); + return -EBUSY; + } + + typhoon_radio.priv = &typhoon_unit; + if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO) == -1) + return -EINVAL; + + request_region(typhoon_unit.iobase, 8, "typhoon"); + printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase); + printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n", + typhoon_unit.mutefreq); + typhoon_unit.mutefreq <<= 4; + + /* mute card - prevents noisy bootups */ + typhoon_mute(&typhoon_unit); + +#ifdef CONFIG_RADIO_TYPHOON_PROC_FS + if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL, + typhoon_get_info)) + printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n"); +#endif + + return 0; +} + +static void __exit typhoon_cleanup_module(void) +{ + +#ifdef CONFIG_RADIO_TYPHOON_PROC_FS + remove_proc_entry("driver/radio-typhoon", NULL); +#endif + + video_unregister_device(&typhoon_radio); + release_region(io, 8); +} + +module_init(typhoon_init); +module_exit(typhoon_cleanup_module); + diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c new file mode 100644 index 000000000..dd688935a --- /dev/null +++ b/drivers/media/radio/radio-zoltrix.c @@ -0,0 +1,412 @@ +/* zoltrix radio plus driver for Linux radio support + * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> + * + * BUGS + * Due to the inconsistancy in reading from the signal flags + * it is difficult to get an accurate tuned signal. + * + * It seems that the card is not linear to 0 volume. It cuts off + * at a low volume, and it is not possible (at least I have not found) + * to get fine volume control over the low volume range. + * + * Some code derived from code by Romolo Manfredini + * romolo@bicnet.it + * + * 1999-05-06 - (C. van Schaik) + * - Make signal strength and stereo scans + * kinder to cpu while in delay + * 1999-01-05 - (C. van Schaik) + * - Changed tuning to 1/160Mhz accuracy + * - Added stereo support + * (card defaults to stereo) + * (can explicitly force mono on the card) + * (can detect if station is in stereo) + * - Added unmute function + * - Reworked ioctl functions + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* check_region, request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/config.h> /* CONFIG_RADIO_ZOLTRIX_PORT */ + +#ifndef CONFIG_RADIO_ZOLTRIX_PORT +#define CONFIG_RADIO_ZOLTRIX_PORT -1 +#endif + +static int io = CONFIG_RADIO_ZOLTRIX_PORT; +static int users = 0; + +struct zol_device { + int port; + int curvol; + unsigned long curfreq; + int muted; + unsigned int stereo; + struct semaphore lock; +}; + + +/* local things */ + +static void sleep_delay(void) +{ + /* Sleep nicely for +/- 10 mS */ + schedule(); +} + +static int zol_setvol(struct zol_device *dev, int vol) +{ + dev->curvol = vol; + if (dev->muted) + return 0; + + down(&dev->lock); + if (vol == 0) { + outb(0, io); + outb(0, io); + inb(io + 3); /* Zoltrix needs to be read to confirm */ + up(&dev->lock); + return 0; + } + + outb(dev->curvol-1, io); + sleep_delay(); + inb(io + 2); + up(&dev->lock); + return 0; +} + +static void zol_mute(struct zol_device *dev) +{ + dev->muted = 1; + down(&dev->lock); + outb(0, io); + outb(0, io); + inb(io + 3); /* Zoltrix needs to be read to confirm */ + up(&dev->lock); +} + +static void zol_unmute(struct zol_device *dev) +{ + dev->muted = 0; + zol_setvol(dev, dev->curvol); +} + +static int zol_setfreq(struct zol_device *dev, unsigned long freq) +{ + /* tunes the radio to the desired frequency */ + unsigned long long bitmask, f, m; + unsigned int stereo = dev->stereo; + int i; + + if (freq == 0) + return 1; + m = (freq / 160 - 8800) * 2; + f = (unsigned long long) m + 0x4d1c; + + bitmask = 0xc480402c10080000ull; + i = 45; + + down(&dev->lock); + + outb(0, io); + outb(0, io); + inb(io + 3); /* Zoltrix needs to be read to confirm */ + + outb(0x40, io); + outb(0xc0, io); + + bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31)); + while (i--) { + if ((bitmask & 0x8000000000000000ull) != 0) { + outb(0x80, io); + udelay(50); + outb(0x00, io); + udelay(50); + outb(0x80, io); + udelay(50); + } else { + outb(0xc0, io); + udelay(50); + outb(0x40, io); + udelay(50); + outb(0xc0, io); + udelay(50); + } + bitmask *= 2; + } + /* termination sequence */ + outb(0x80, io); + outb(0xc0, io); + outb(0x40, io); + udelay(1000); + inb(io+2); + + udelay(1000); + + if (dev->muted) + { + outb(0, io); + outb(0, io); + inb(io + 3); + udelay(1000); + } + + up(&dev->lock); + + if(!dev->muted) + { + zol_setvol(dev, dev->curvol); + } + return 0; +} + +/* Get signal strength */ + +int zol_getsigstr(struct zol_device *dev) +{ + int a, b; + + down(&dev->lock); + outb(0x00, io); /* This stuff I found to do nothing */ + outb(dev->curvol, io); + sleep_delay(); + sleep_delay(); + + a = inb(io); + sleep_delay(); + b = inb(io); + + up(&dev->lock); + + if (a != b) + return (0); + + if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */ + || (a == 0xef)) /* with a binary scanner on the card io */ + return (1); + return (0); +} + +int zol_is_stereo (struct zol_device *dev) +{ + int x1, x2; + + down(&dev->lock); + + outb(0x00, io); + outb(dev->curvol, io); + sleep_delay(); + sleep_delay(); + + x1 = inb(io); + sleep_delay(); + x2 = inb(io); + + up(&dev->lock); + + if ((x1 == x2) && (x1 == 0xcf)) + return 1; + return 0; +} + +static int zol_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct zol_device *zol = dev->priv; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability v; + v.type = VID_TYPE_TUNER; + v.channels = 1 + zol->stereo; + v.audios = 1; + /* No we don't do pictures */ + v.maxwidth = 0; + v.maxheight = 0; + v.minwidth = 0; + v.minheight = 0; + strcpy(v.name, "Zoltrix Radio"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.tuner) + return -EINVAL; + strcpy(v.name, "FM"); + v.rangelow = (int) (88.0 * 16000); + v.rangehigh = (int) (108.0 * 16000); + v.flags = zol_is_stereo(zol) + ? VIDEO_TUNER_STEREO_ON : 0; + v.flags |= VIDEO_TUNER_LOW; + v.mode = VIDEO_MODE_AUTO; + v.signal = 0xFFFF * zol_getsigstr(zol); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.tuner != 0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + if (copy_to_user(arg, &zol->curfreq, sizeof(zol->curfreq))) + return -EFAULT; + return 0; + case VIDIOCSFREQ: + if (copy_from_user(&zol->curfreq, arg, sizeof(zol->curfreq))) + return -EFAULT; + zol_setfreq(zol, zol->curfreq); + return 0; + case VIDIOCGAUDIO: + { + struct video_audio v; + memset(&v, 0, sizeof(v)); + v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; + v.mode != zol_is_stereo(zol) + ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO; + v.volume = zol->curvol * 4096; + v.step = 4096; + strcpy(v.name, "Zoltrix Radio"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.audio) + return -EINVAL; + + if (v.flags & VIDEO_AUDIO_MUTE) + zol_mute(zol); + else + { + zol_unmute(zol); + zol_setvol(zol, v.volume / 4096); + } + + if (v.mode & VIDEO_SOUND_STEREO) + { + zol->stereo = 1; + zol_setfreq(zol, zol->curfreq); + } + if (v.mode & VIDEO_SOUND_MONO) + { + zol->stereo = 0; + zol_setfreq(zol, zol->curfreq); + } + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int zol_open(struct video_device *dev, int flags) +{ + if (users) + return -EBUSY; + users++; + MOD_INC_USE_COUNT; + return 0; +} + +static void zol_close(struct video_device *dev) +{ + users--; + MOD_DEC_USE_COUNT; +} + +static struct zol_device zoltrix_unit; + +static struct video_device zoltrix_radio = +{ + "Zoltrix Radio Plus", + VID_TYPE_TUNER, + VID_HARDWARE_ZOLTRIX, + zol_open, + zol_close, + NULL, /* Can't read (no capture ability) */ + NULL, /* Can't write */ + NULL, + zol_ioctl, + NULL, + NULL +}; + +static int __init zoltrix_init(void) +{ + if (io == -1) { + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); + return -EINVAL; + } + if (check_region(io, 2)) { + printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io); + return -EBUSY; + } + if ((io != 0x20c) && (io != 0x30c)) { + printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n"); + return -ENXIO; + } + zoltrix_radio.priv = &zoltrix_unit; + + if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO) == -1) + return -EINVAL; + + request_region(io, 2, "zoltrix"); + printk(KERN_INFO "Zoltrix Radio Plus card driver.\n"); + + init_MUTEX(&zoltrix_unit.lock); + + /* mute card - prevents noisy bootups */ + + /* this ensures that the volume is all the way down */ + + outb(0, io); + outb(0, io); + sleep_delay(); + sleep_delay(); + inb(io + 3); + + zoltrix_unit.curvol = 0; + zoltrix_unit.stereo = 1; + + return 0; +} + +MODULE_AUTHOR("C.van Schaik"); +MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); +MODULE_PARM(io, "i"); +MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); + +EXPORT_NO_SYMBOLS; + +static void __exit zoltrix_cleanup_module(void) +{ + video_unregister_device(&zoltrix_radio); + release_region(io, 2); +} + +module_init(zoltrix_init); +module_exit(zoltrix_cleanup_module); + diff --git a/drivers/media/video/Config.in b/drivers/media/video/Config.in new file mode 100644 index 000000000..617dcda93 --- /dev/null +++ b/drivers/media/video/Config.in @@ -0,0 +1,45 @@ +# +# Multimedia Video device configuration +# +mainmenu_option next_comment +comment 'Video For Linux' + +bool ' V4L information in proc filesystem' CONFIG_VIDEO_PROC_FS +dep_tristate ' I2C on parallel port' CONFIG_I2C_PARPORT $CONFIG_PARPORT $CONFIG_I2C + +comment 'Video Adapters' +if [ "$CONFIG_I2C_ALGOBIT" = "y" -o "$CONFIG_I2C_ALGOBIT" = "m" ]; then + dep_tristate ' BT848 Video For Linux' CONFIG_VIDEO_BT848 $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C_ALGOBIT +fi +dep_tristate ' Mediavision Pro Movie Studio Video For Linux' CONFIG_VIDEO_PMS $CONFIG_VIDEO_DEV +if [ "$CONFIG_ALL_PPC" = "y" ]; then + dep_tristate ' PlanB Video-In on PowerMac' CONFIG_VIDEO_PLANB $CONFIG_VIDEO_DEV +fi +if [ "$CONFIG_PARPORT" != "n" ]; then + dep_tristate ' Quickcam BW Video For Linux' CONFIG_VIDEO_BWQCAM $CONFIG_VIDEO_DEV $CONFIG_PARPORT + if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + dep_tristate ' QuickCam Colour Video For Linux (EXPERIMENTAL)' CONFIG_VIDEO_CQCAM $CONFIG_VIDEO_DEV $CONFIG_PARPORT + fi +fi +dep_tristate ' CPiA Video For Linux' CONFIG_VIDEO_CPIA $CONFIG_VIDEO_DEV +if [ "$CONFIG_VIDEO_CPIA" != "n" ]; then + if [ "CONFIG_PARPORT_1284" != "n" ]; then + dep_tristate ' CPiA Parallel Port Lowlevel Support' CONFIG_VIDEO_CPIA_PP $CONFIG_VIDEO_CPIA $CONFIG_PARPORT + fi + if [ "$CONFIG_USB" != "n" ]; then + dep_tristate ' CPiA USB Lowlevel Support' CONFIG_VIDEO_CPIA_USB $CONFIG_VIDEO_CPIA $CONFIG_USB + fi +fi +dep_tristate ' SAA5249 Teletext processor' CONFIG_VIDEO_SAA5249 $CONFIG_VIDEO_DEV $CONFIG_I2C +dep_tristate ' SAB3036 tuner' CONFIG_TUNER_3036 $CONFIG_VIDEO_DEV $CONFIG_I2C +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + if [ "$CONFIG_SGI" = "y" ]; then + dep_tristate ' SGI Vino Video For Linux (EXPERIMENTAL)' CONFIG_VIDEO_VINO $CONFIG_VIDEO_DEV $CONFIG_SGI + fi + dep_tristate ' Stradis 4:2:2 MPEG-2 video driver (EXPERIMENTAL)' CONFIG_VIDEO_STRADIS $CONFIG_VIDEO_DEV $CONFIG_PCI +fi +dep_tristate ' Zoran ZR36057/36060 Video For Linux' CONFIG_VIDEO_ZORAN $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C +dep_tristate ' Include support for Iomega Buz' CONFIG_VIDEO_BUZ $CONFIG_VIDEO_ZORAN +dep_tristate ' Zoran ZR36120/36125 Video For Linux' CONFIG_VIDEO_ZR36120 $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C + +endmenu diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile new file mode 100644 index 000000000..fac6990c4 --- /dev/null +++ b/drivers/media/video/Makefile @@ -0,0 +1,96 @@ +# +# Makefile for the kernel character device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# + +O_OBJS := +OX_OBJS := +M_OBJS := +MX_OBJS := + +# Object file lists. + +obj-y := +obj-m := +obj-n := +obj- := + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +O_TARGET := video.o + +# All of the (potential) objects that export symbols. +# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'. + +export-objs := i2c-old.o videodev.o bttv-if.o cpia.o + +list-multi := bttv.o zoran.o +bttv-objs := bttv-driver.o bttv-cards.o bttv-if.o +zoran-objs := zr36120.o zr36120_i2c.o zr36120_mem.o + +obj-$(CONFIG_VIDEO_DEV) += videodev.o + +obj-$(CONFIG_BUS_I2C) += i2c-old.o +obj-$(CONFIG_VIDEO_BT848) += bttv.o msp3400.o \ + tda7432.o tda8425.o tda985x.o tda9875.o tea6300.o tea6420.o tuner.o +obj-$(CONFIG_SOUND_TVMIXER) += tvmixer.o + +obj-$(CONFIG_VIDEO_ZR36120) += zoran.o i2c-old.o tuner.o saa7110.o saa7111.o saa7185.o +obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o i2c-old.o +obj-$(CONFIG_VIDEO_SAA5249) += saa5249.o i2c-old.o +obj-$(CONFIG_VIDEO_CQCAM) += c-qcam.o +obj-$(CONFIG_VIDEO_BWQCAM) += bw-qcam.o +obj-$(CONFIG_VIDEO_ZORAN) += buz.o i2c-old.o saa7110.o saa7111.o saa7185.o +obj-$(CONFIG_VIDEO_LML33) += bt856.o bt819.o +obj-$(CONFIG_VIDEO_PMS) += pms.o +obj-$(CONFIG_VIDEO_PLANB) += planb.o +obj-$(CONFIG_VIDEO_VINO) += vino.o +obj-$(CONFIG_VIDEO_STRADIS) += stradis.o +obj-$(CONFIG_VIDEO_CPIA) += cpia.o +obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o +obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o +obj-$(CONFIG_TUNER_3036) += tuner-3036.o + +# Extract lists of the multi-part drivers. +# The 'int-*' lists are the intermediate files used to build the multi's. + +multi-y := $(filter $(list-multi), $(obj-y)) +multi-m := $(filter $(list-multi), $(obj-m)) +int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs))) +int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs))) + +# Files that are both resident and modular: remove from modular. + +obj-m := $(filter-out $(obj-y), $(obj-m)) +int-m := $(filter-out $(int-y), $(int-m)) + +# Take multi-part drivers out of obj-y and put components in. + +obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y) + +# Translate to Rules.make lists. + +O_OBJS := $(filter-out $(export-objs), $(obj-y)) +OX_OBJS := $(filter $(export-objs), $(obj-y)) +M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m))) +MX_OBJS := $(sort $(filter $(export-objs), $(obj-m))) +MI_OBJS := $(sort $(filter-out $(export-objs), $(int-m))) +MIX_OBJS := $(sort $(filter $(export-objs), $(int-m))) + +include $(TOPDIR)/Rules.make + +fastdep: + +zoran.o: zr36120.o zr36120_i2c.o zr36120_mem.o + $(LD) $(LD_RFLAG) -r -o $@ zr36120.o zr36120_i2c.o zr36120_mem.o + +bttv.o: $(bttv-objs) + $(LD) $(LD_RFLAG) -r -o $@ $(bttv-objs) diff --git a/drivers/media/video/audiochip.h b/drivers/media/video/audiochip.h new file mode 100644 index 000000000..23d1b1259 --- /dev/null +++ b/drivers/media/video/audiochip.h @@ -0,0 +1,60 @@ +#ifndef AUDIOCHIP_H +#define AUDIOCHIP_H + +/* ---------------------------------------------------------------------- */ + +#define MIN(a,b) (((a)>(b))?(b):(a)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +/* v4l device was opened in Radio mode */ +#define AUDC_SET_RADIO _IO('m',2) +/* select from TV,radio,extern,MUTE */ +#define AUDC_SET_INPUT _IOW('m',17,int) + +/* all the stuff below is obsolete and just here for reference. I'll + * remove it once the driver is tested and works fine. + * + * Instead creating alot of tiny API's for all kinds of different + * chips, we'll just pass throuth the v4l ioctl structs (v4l2 not + * yet...). It is a bit less flexible, but most/all used i2c chips + * make sense in v4l context only. So I think that's acceptable... + */ + +#if 0 + +/* TODO (if it is ever [to be] accessible in the V4L[2] spec): + * maybe fade? (back/front) + * notes: + * NEWCHANNEL and SWITCH_MUTE are here because the MSP3400 has a special + * routine to go through when it tunes in to a new channel before turning + * back on the sound. + * Either SET_RADIO, NEWCHANNEL, and SWITCH_MUTE or SET_INPUT need to be + * implemented (MSP3400 uses SET_RADIO to select inputs, and SWITCH_MUTE for + * channel-change mute -- TEA6300 et al use SET_AUDIO to select input [TV, + * radio, external, or MUTE]). If both methods are implemented, you get a + * cookie for doing such a good job! :) + */ + +#define AUDC_SET_TVNORM _IOW('m',1,int) /* TV mode + PAL/SECAM/NTSC */ +#define AUDC_NEWCHANNEL _IO('m',3) /* indicate new chan - off mute */ + +#define AUDC_GET_VOLUME_LEFT _IOR('m',4,__u16) +#define AUDC_GET_VOLUME_RIGHT _IOR('m',5,__u16) +#define AUDC_SET_VOLUME_LEFT _IOW('m',6,__u16) +#define AUDC_SET_VOLUME_RIGHT _IOW('m',7,__u16) + +#define AUDC_GET_STEREO _IOR('m',8,__u16) +#define AUDC_SET_STEREO _IOW('m',9,__u16) + +#define AUDC_GET_DC _IOR('m',10,__u16)/* ??? */ + +#define AUDC_GET_BASS _IOR('m',11,__u16) +#define AUDC_SET_BASS _IOW('m',12,__u16) +#define AUDC_GET_TREBLE _IOR('m',13,__u16) +#define AUDC_SET_TREBLE _IOW('m',14,__u16) + +#define AUDC_GET_UNIT _IOR('m',15,int) /* ??? - unimplemented in MSP3400 */ +#define AUDC_SWITCH_MUTE _IO('m',16) /* turn on mute */ +#endif + +#endif /* AUDIOCHIP_H */ diff --git a/drivers/media/video/bt848.h b/drivers/media/video/bt848.h new file mode 100644 index 000000000..340accf31 --- /dev/null +++ b/drivers/media/video/bt848.h @@ -0,0 +1,355 @@ +/* + bt848.h - Bt848 register offsets + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BT848_H_ +#define _BT848_H_ + +#ifndef PCI_VENDOR_ID_BROOKTREE +#define PCI_VENDOR_ID_BROOKTREE 0x109e +#endif +#ifndef PCI_DEVICE_ID_BT848 +#define PCI_DEVICE_ID_BT848 0x350 +#endif +#ifndef PCI_DEVICE_ID_BT849 +#define PCI_DEVICE_ID_BT849 0x351 +#endif +#ifndef PCI_DEVICE_ID_BT878 +#define PCI_DEVICE_ID_BT878 0x36e +#endif +#ifndef PCI_DEVICE_ID_BT879 +#define PCI_DEVICE_ID_BT879 0x36f +#endif + + +/* Brooktree 848 registers */ + +#define BT848_DSTATUS 0x000 +#define BT848_DSTATUS_PRES (1<<7) +#define BT848_DSTATUS_HLOC (1<<6) +#define BT848_DSTATUS_FIELD (1<<5) +#define BT848_DSTATUS_NUML (1<<4) +#define BT848_DSTATUS_CSEL (1<<3) +#define BT848_DSTATUS_PLOCK (1<<2) +#define BT848_DSTATUS_LOF (1<<1) +#define BT848_DSTATUS_COF (1<<0) + +#define BT848_IFORM 0x004 +#define BT848_IFORM_HACTIVE (1<<7) +#define BT848_IFORM_MUXSEL (3<<5) +#define BT848_IFORM_MUX0 (2<<5) +#define BT848_IFORM_MUX1 (3<<5) +#define BT848_IFORM_MUX2 (1<<5) +#define BT848_IFORM_XTSEL (3<<3) +#define BT848_IFORM_XT0 (1<<3) +#define BT848_IFORM_XT1 (2<<3) +#define BT848_IFORM_XTAUTO (3<<3) +#define BT848_IFORM_XTBOTH (3<<3) +#define BT848_IFORM_NTSC 1 +#define BT848_IFORM_NTSC_J 2 +#define BT848_IFORM_PAL_BDGHI 3 +#define BT848_IFORM_PAL_M 4 +#define BT848_IFORM_PAL_N 5 +#define BT848_IFORM_SECAM 6 +#define BT848_IFORM_PAL_NC 7 +#define BT848_IFORM_AUTO 0 +#define BT848_IFORM_NORM 7 + +#define BT848_TDEC 0x008 +#define BT848_TDEC_DEC_FIELD (1<<7) +#define BT848_TDEC_FLDALIGN (1<<6) +#define BT848_TDEC_DEC_RAT (0x1f) + +#define BT848_E_CROP 0x00C +#define BT848_O_CROP 0x08C + +#define BT848_E_VDELAY_LO 0x010 +#define BT848_O_VDELAY_LO 0x090 + +#define BT848_E_VACTIVE_LO 0x014 +#define BT848_O_VACTIVE_LO 0x094 + +#define BT848_E_HDELAY_LO 0x018 +#define BT848_O_HDELAY_LO 0x098 + +#define BT848_E_HACTIVE_LO 0x01C +#define BT848_O_HACTIVE_LO 0x09C + +#define BT848_E_HSCALE_HI 0x020 +#define BT848_O_HSCALE_HI 0x0A0 + +#define BT848_E_HSCALE_LO 0x024 +#define BT848_O_HSCALE_LO 0x0A4 + +#define BT848_BRIGHT 0x028 + +#define BT848_E_CONTROL 0x02C +#define BT848_O_CONTROL 0x0AC +#define BT848_CONTROL_LNOTCH (1<<7) +#define BT848_CONTROL_COMP (1<<6) +#define BT848_CONTROL_LDEC (1<<5) +#define BT848_CONTROL_CBSENSE (1<<4) +#define BT848_CONTROL_CON_MSB (1<<2) +#define BT848_CONTROL_SAT_U_MSB (1<<1) +#define BT848_CONTROL_SAT_V_MSB (1<<0) + +#define BT848_CONTRAST_LO 0x030 +#define BT848_SAT_U_LO 0x034 +#define BT848_SAT_V_LO 0x038 +#define BT848_HUE 0x03C + +#define BT848_E_SCLOOP 0x040 +#define BT848_O_SCLOOP 0x0C0 +#define BT848_SCLOOP_CAGC (1<<6) +#define BT848_SCLOOP_CKILL (1<<5) +#define BT848_SCLOOP_HFILT_AUTO (0<<3) +#define BT848_SCLOOP_HFILT_CIF (1<<3) +#define BT848_SCLOOP_HFILT_QCIF (2<<3) +#define BT848_SCLOOP_HFILT_ICON (3<<3) + +#define BT848_SCLOOP_PEAK (1<<7) +#define BT848_SCLOOP_HFILT_MINP (1<<3) +#define BT848_SCLOOP_HFILT_MEDP (2<<3) +#define BT848_SCLOOP_HFILT_MAXP (3<<3) + + +#define BT848_OFORM 0x048 +#define BT848_OFORM_RANGE (1<<7) +#define BT848_OFORM_CORE0 (0<<5) +#define BT848_OFORM_CORE8 (1<<5) +#define BT848_OFORM_CORE16 (2<<5) +#define BT848_OFORM_CORE32 (3<<5) + +#define BT848_E_VSCALE_HI 0x04C +#define BT848_O_VSCALE_HI 0x0CC +#define BT848_VSCALE_YCOMB (1<<7) +#define BT848_VSCALE_COMB (1<<6) +#define BT848_VSCALE_INT (1<<5) +#define BT848_VSCALE_HI 15 + +#define BT848_E_VSCALE_LO 0x050 +#define BT848_O_VSCALE_LO 0x0D0 +#define BT848_TEST 0x054 +#define BT848_ADELAY 0x060 +#define BT848_BDELAY 0x064 + +#define BT848_ADC 0x068 +#define BT848_ADC_RESERVED (2<<6) +#define BT848_ADC_SYNC_T (1<<5) +#define BT848_ADC_AGC_EN (1<<4) +#define BT848_ADC_CLK_SLEEP (1<<3) +#define BT848_ADC_Y_SLEEP (1<<2) +#define BT848_ADC_C_SLEEP (1<<1) +#define BT848_ADC_CRUSH (1<<0) + +#define BT848_E_VTC 0x06C +#define BT848_O_VTC 0x0EC +#define BT848_VTC_HSFMT (1<<7) +#define BT848_VTC_VFILT_2TAP 0 +#define BT848_VTC_VFILT_3TAP 1 +#define BT848_VTC_VFILT_4TAP 2 +#define BT848_VTC_VFILT_5TAP 3 + +#define BT848_SRESET 0x07C + +#define BT848_COLOR_FMT 0x0D4 +#define BT848_COLOR_FMT_O_RGB32 (0<<4) +#define BT848_COLOR_FMT_O_RGB24 (1<<4) +#define BT848_COLOR_FMT_O_RGB16 (2<<4) +#define BT848_COLOR_FMT_O_RGB15 (3<<4) +#define BT848_COLOR_FMT_O_YUY2 (4<<4) +#define BT848_COLOR_FMT_O_BtYUV (5<<4) +#define BT848_COLOR_FMT_O_Y8 (6<<4) +#define BT848_COLOR_FMT_O_RGB8 (7<<4) +#define BT848_COLOR_FMT_O_YCrCb422 (8<<4) +#define BT848_COLOR_FMT_O_YCrCb411 (9<<4) +#define BT848_COLOR_FMT_O_RAW (14<<4) +#define BT848_COLOR_FMT_E_RGB32 0 +#define BT848_COLOR_FMT_E_RGB24 1 +#define BT848_COLOR_FMT_E_RGB16 2 +#define BT848_COLOR_FMT_E_RGB15 3 +#define BT848_COLOR_FMT_E_YUY2 4 +#define BT848_COLOR_FMT_E_BtYUV 5 +#define BT848_COLOR_FMT_E_Y8 6 +#define BT848_COLOR_FMT_E_RGB8 7 +#define BT848_COLOR_FMT_E_YCrCb422 8 +#define BT848_COLOR_FMT_E_YCrCb411 9 +#define BT848_COLOR_FMT_E_RAW 14 + +#define BT848_COLOR_FMT_RGB32 0x00 +#define BT848_COLOR_FMT_RGB24 0x11 +#define BT848_COLOR_FMT_RGB16 0x22 +#define BT848_COLOR_FMT_RGB15 0x33 +#define BT848_COLOR_FMT_YUY2 0x44 +#define BT848_COLOR_FMT_BtYUV 0x55 +#define BT848_COLOR_FMT_Y8 0x66 +#define BT848_COLOR_FMT_RGB8 0x77 +#define BT848_COLOR_FMT_YCrCb422 0x88 +#define BT848_COLOR_FMT_YCrCb411 0x99 +#define BT848_COLOR_FMT_RAW 0xee + +#define BT848_COLOR_CTL 0x0D8 +#define BT848_COLOR_CTL_EXT_FRMRATE (1<<7) +#define BT848_COLOR_CTL_COLOR_BARS (1<<6) +#define BT848_COLOR_CTL_RGB_DED (1<<5) +#define BT848_COLOR_CTL_GAMMA (1<<4) +#define BT848_COLOR_CTL_WSWAP_ODD (1<<3) +#define BT848_COLOR_CTL_WSWAP_EVEN (1<<2) +#define BT848_COLOR_CTL_BSWAP_ODD (1<<1) +#define BT848_COLOR_CTL_BSWAP_EVEN (1<<0) + +#define BT848_CAP_CTL 0x0DC +#define BT848_CAP_CTL_DITH_FRAME (1<<4) +#define BT848_CAP_CTL_CAPTURE_VBI_ODD (1<<3) +#define BT848_CAP_CTL_CAPTURE_VBI_EVEN (1<<2) +#define BT848_CAP_CTL_CAPTURE_ODD (1<<1) +#define BT848_CAP_CTL_CAPTURE_EVEN (1<<0) + +#define BT848_VBI_PACK_SIZE 0x0E0 + +#define BT848_VBI_PACK_DEL 0x0E4 +#define BT848_VBI_PACK_DEL_VBI_HDELAY 0xfc +#define BT848_VBI_PACK_DEL_EXT_FRAME 2 +#define BT848_VBI_PACK_DEL_VBI_PKT_HI 1 + + +#define BT848_INT_STAT 0x100 +#define BT848_INT_MASK 0x104 + +#define BT848_INT_ETBF (1<<23) + +#define BT848_INT_RISCS (0xf<<28) +#define BT848_INT_RISC_EN (1<<27) +#define BT848_INT_RACK (1<<25) +#define BT848_INT_FIELD (1<<24) +#define BT848_INT_SCERR (1<<19) +#define BT848_INT_OCERR (1<<18) +#define BT848_INT_PABORT (1<<17) +#define BT848_INT_RIPERR (1<<16) +#define BT848_INT_PPERR (1<<15) +#define BT848_INT_FDSR (1<<14) +#define BT848_INT_FTRGT (1<<13) +#define BT848_INT_FBUS (1<<12) +#define BT848_INT_RISCI (1<<11) +#define BT848_INT_GPINT (1<<9) +#define BT848_INT_I2CDONE (1<<8) +#define BT848_INT_VPRES (1<<5) +#define BT848_INT_HLOCK (1<<4) +#define BT848_INT_OFLOW (1<<3) +#define BT848_INT_HSYNC (1<<2) +#define BT848_INT_VSYNC (1<<1) +#define BT848_INT_FMTCHG (1<<0) + + +#define BT848_GPIO_DMA_CTL 0x10C +#define BT848_GPIO_DMA_CTL_GPINTC (1<<15) +#define BT848_GPIO_DMA_CTL_GPINTI (1<<14) +#define BT848_GPIO_DMA_CTL_GPWEC (1<<13) +#define BT848_GPIO_DMA_CTL_GPIOMODE (3<<11) +#define BT848_GPIO_DMA_CTL_GPCLKMODE (1<<10) +#define BT848_GPIO_DMA_CTL_PLTP23_4 (0<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_8 (1<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_16 (2<<6) +#define BT848_GPIO_DMA_CTL_PLTP23_32 (3<<6) +#define BT848_GPIO_DMA_CTL_PLTP1_4 (0<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_8 (1<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_16 (2<<4) +#define BT848_GPIO_DMA_CTL_PLTP1_32 (3<<4) +#define BT848_GPIO_DMA_CTL_PKTP_4 (0<<2) +#define BT848_GPIO_DMA_CTL_PKTP_8 (1<<2) +#define BT848_GPIO_DMA_CTL_PKTP_16 (2<<2) +#define BT848_GPIO_DMA_CTL_PKTP_32 (3<<2) +#define BT848_GPIO_DMA_CTL_RISC_ENABLE (1<<1) +#define BT848_GPIO_DMA_CTL_FIFO_ENABLE (1<<0) + +#define BT848_I2C 0x110 +#define BT848_I2C_DIV (0xf<<4) +#define BT848_I2C_SYNC (1<<3) +#define BT848_I2C_W3B (1<<2) +#define BT848_I2C_SCL (1<<1) +#define BT848_I2C_SDA (1<<0) + + +#define BT848_RISC_STRT_ADD 0x114 +#define BT848_GPIO_OUT_EN 0x118 +#define BT848_GPIO_REG_INP 0x11C +#define BT848_RISC_COUNT 0x120 +#define BT848_GPIO_DATA 0x200 + + +/* Bt848 RISC commands */ + +/* only for the SYNC RISC command */ +#define BT848_FIFO_STATUS_FM1 0x06 +#define BT848_FIFO_STATUS_FM3 0x0e +#define BT848_FIFO_STATUS_SOL 0x02 +#define BT848_FIFO_STATUS_EOL4 0x01 +#define BT848_FIFO_STATUS_EOL3 0x0d +#define BT848_FIFO_STATUS_EOL2 0x09 +#define BT848_FIFO_STATUS_EOL1 0x05 +#define BT848_FIFO_STATUS_VRE 0x04 +#define BT848_FIFO_STATUS_VRO 0x0c +#define BT848_FIFO_STATUS_PXV 0x00 + +#define BT848_RISC_RESYNC (1<<15) + +/* WRITE and SKIP */ +/* disable which bytes of each DWORD */ +#define BT848_RISC_BYTE0 (1<<12) +#define BT848_RISC_BYTE1 (1<<13) +#define BT848_RISC_BYTE2 (1<<14) +#define BT848_RISC_BYTE3 (1<<15) +#define BT848_RISC_BYTE_ALL (0x0f<<12) +#define BT848_RISC_BYTE_NONE 0 +/* cause RISCI */ +#define BT848_RISC_IRQ (1<<24) +/* RISC command is last one in this line */ +#define BT848_RISC_EOL (1<<26) +/* RISC command is first one in this line */ +#define BT848_RISC_SOL (1<<27) + +#define BT848_RISC_WRITE (0x01<<28) +#define BT848_RISC_SKIP (0x02<<28) +#define BT848_RISC_WRITEC (0x05<<28) +#define BT848_RISC_JUMP (0x07<<28) +#define BT848_RISC_SYNC (0x08<<28) + +#define BT848_RISC_WRITE123 (0x09<<28) +#define BT848_RISC_SKIP123 (0x0a<<28) +#define BT848_RISC_WRITE1S23 (0x0b<<28) + + + +/* Bt848A and higher only !! */ +#define BT848_TGLB 0x080 +#define BT848_TGCTRL 0x084 +#define BT848_FCAP 0x0E8 +#define BT848_PLL_F_LO 0x0F0 +#define BT848_PLL_F_HI 0x0F4 + +#define BT848_PLL_XCI 0x0F8 +#define BT848_PLL_X (1<<7) +#define BT848_PLL_C (1<<6) + +/* Bt878 register */ + +#define BT878_DEVCTRL 0x40 +#define BT878_EN_TBFX 0x02 + +#endif diff --git a/drivers/media/video/bttv-cards.c b/drivers/media/video/bttv-cards.c new file mode 100644 index 000000000..b2c26e35b --- /dev/null +++ b/drivers/media/video/bttv-cards.c @@ -0,0 +1,835 @@ +/* + bttv-cards.c -- this file has card-specific stuff + + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ 1 + +#include <linux/version.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "bttv.h" +#include "tuner.h" + +/* fwd decl */ +static void hauppauge_eeprom(struct bttv *btv); +static void hauppauge_boot_msp34xx(struct bttv *btv); +static void init_PXC200(struct bttv *btv); +static void init_tea5757(struct bttv *btv); + +MODULE_PARM(card,"1-4i"); +MODULE_PARM(pll,"1-4i"); +MODULE_PARM(autoload,"i"); + +static unsigned int card[4] = { -1, -1, -1, -1 }; +static unsigned int pll[4] = { -1, -1, -1, -1 }; +#ifdef MODULE +static unsigned int autoload = 1; +#else +static unsigned int autoload = 0; +#endif + +/* ----------------------------------------------------------------------- */ +/* list of card IDs for bt878+ cards */ + +static struct CARD { + unsigned id; + int cardnr; + char *name; +} cards[] __devinitdata = { + { 0x00011002, BTTV_HAUPPAUGE878, "ATI TV Wonder" }, + { 0x00011461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00021461, BTTV_AVERMEDIA98, "Avermedia TVCapture 98" }, + { 0x00031461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x00041461, BTTV_AVPHONE98, "AVerMedia TVPhone98" }, + { 0x10b42636, BTTV_HAUPPAUGE878, "STB ???" }, + { 0x1118153b, BTTV_TERRATVALUE, "Terratec TV Value" }, + { 0x1123153b, BTTV_TERRATVRADIO, "Terratec TV/Radio+" }, + { 0x1200bd11, BTTV_PINNACLERAVE, "Pinnacle PCTV Rave" }, + { 0x13eb0070, BTTV_HAUPPAUGE878, "Hauppauge WinTV" }, + { 0x18501851, BTTV_CHRONOS_VS2, "Chronos Video Shuttle II" }, + { 0x18521852, BTTV_TYPHOON_TVIEW, "Typhoon TView TV/FM Tuner" }, + { 0x217d6606, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, + { 0x263610b4, BTTV_STB2, "STB TV PCI FM, P/N 6000704" }, + { 0x3000144f, BTTV_MAGICTVIEW063, "TView 99 (CPH063)" }, + { 0x300014ff, BTTV_MAGICTVIEW061, "TView 99 (CPH061)" }, + { 0x3002144f, BTTV_MAGICTVIEW061, "Askey Magic TView" }, + { 0x300214ff, BTTV_PHOEBE_TVMAS, "Phoebe TV Master" }, + { 0x400a15b0, BTTV_ZOLTRIX_GENIE, "Zoltrix Genie TV" }, + { 0x402010fc, 0 /* no tvcards entry yet */, "I-O Data Co. GV-BCV3/PCI" }, +#if 0 /* probably wrong */ + { 0x14610002, BTTV_AVERMEDIA98, "Avermedia TVCapture 98" }, + { 0x6606217d, BTTV_WINFAST2000, "Leadtek WinFast TV 2000" }, +#endif + { 0, -1, NULL } +}; + +/* ----------------------------------------------------------------------- */ +/* array with description for bt848 / bt878 tv/grabber cards */ + +struct tvcard bttv_tvcards[] = +{ + /* 0x00 */ + { " *** UNKNOWN *** ", + 3, 1, 0, 2, 0, { 2, 3, 1, 1}, { 0, 0, 0, 0, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "MIRO PCTV", + 4, 1, 0, 2,15, { 2, 3, 1, 1}, { 2, 0, 0, 0,10},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Hauppauge old", + 4, 1, 0, 2, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0, + 1,1,0,1,0,0,0,1, PLL_NONE, -1 }, + { "STB", + 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 0, 2, 3, 1},0, + 0,1,1,1,1,0,0,1, PLL_NONE, -1 }, + + { "Intel", + 3, 1, 0, -1, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Diamond DTV2000", + 3, 1, 0, 2, 3, { 2, 3, 1, 1}, { 0, 1, 0, 1, 3},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "AVerMedia TVPhone", + 3, 1, 0, 3,15, { 2, 3, 1, 1}, {12, 4,11,11, 0},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "MATRIX-Vision MV-Delta", + 5, 1, -1, 3, 0, { 2, 3, 1, 0, 0},{0 }, 0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + + /* 0x08 */ + { "Fly Video II", + 3, 1, 0, 2, 0xc00, { 2, 3, 1, 1}, + { 0, 0xc00, 0x800, 0x400, 0xc00, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "TurboTV", + 3, 1, 0, 2, 3, { 2, 3, 1, 1}, { 1, 1, 2, 3, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Hauppauge new (bt878)", + 4, 1, 0, 2, 7, { 2, 0, 1, 1}, { 0, 1, 2, 3, 4},0, + 1,1,0,1,0,0,0,1, PLL_28, -1 }, + { "MIRO PCTV pro", + 3, 1, 0, 2, 65551, { 2, 3, 1, 1}, {1,65537, 0, 0,10},0, + /* 3, 1, 0, 2, 0x3004F, { 2, 3, 1, 1}, {1, 0x10011, 5, 0,10}, 0x3004F, */ + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + + { "ADS Technologies Channel Surfer TV", + 3, 1, 2, 2, 15, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "AVerMedia TVCapture 98", + 3, 4, 0, 2, 15, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "Aimslab VHX", + 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 0, 1, 2, 3, 4},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Zoltrix TV-Max", + 3, 1, 0, 2,15, { 2, 3, 1, 1}, {0 , 0, 1 , 0, 10},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + + /* 0x10 */ + { "Pixelview PlayTV (bt878)", + 3, 1, 0, 2, 0x01fe00, { 2, 3, 1, 1}, + { 0x01c000, 0, 0x018000, 0x014000, 0x002000, 0 },0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "Leadtek WinView 601", + 3, 1, 0, 2, 0x8300f8, { 2, 3, 1, 1,0}, + { 0x4fa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007,0xcfa007},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "AVEC Intercapture", + 3, 2, 0, 2, 0, {2, 3, 1, 1}, {1, 0, 0, 0, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "LifeView FlyKit w/o Tuner", + 3, 1, -1, -1, 0x8dff00, { 2, 3, 1, 1}, { 0 },0, + 0,0,0,0,0,0,0,1, PLL_NONE, -1 }, + + { "CEI Raffles Card", + 3, 3, 0, 2, 0, {2, 3, 1, 1}, {0, 0, 0, 0 ,0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Lucky Star Image World ConferenceTV", + 3, 1, 0, 2, 0x00fffe07, { 2, 3, 1, 1}, { 131072, 1, 1638400, 3, 4},0, + 1,1,1,1,0,0,0,1, PLL_28, TUNER_PHILIPS_PAL_I }, + { "Phoebe Tv Master + FM", + 3, 1, 0, 2, 0xc00, { 2, 3, 1, 1},{0, 1, 0x800, 0x400, 0xc00, 0},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Modular Technology MM205 PCTV, bt878", + 2, 1, 0, -1, 7, { 2, 3 }, { 0, 0, 0, 0, 0 },0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + + /* 0x18 */ + { "Askey/Typhoon/Anubis Magic TView CPH051/061 (bt878)", + 3, 1, 0, 2, 0xe00, { 2, 3, 1, 1}, {0x400, 0x400, 0x400, 0x400, 0},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "Terratec/Vobis TV-Boostar", + 3, 1, 0, 2, 16777215 , { 2, 3, 1, 1}, { 131072, 1, 1638400, 3,4},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Newer Hauppauge WinCam (bt878)", + 4, 1, 0, 3, 7, { 2, 0, 1, 1}, { 0, 1, 2, 3, 4},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "MAXI TV Video PCI2", + 3, 1, 0, 2, 0xffff, { 2, 3, 1, 1}, { 0, 1, 2, 3, 0xc00},0, + 1,1,1,1,0,0,0,1, PLL_NONE, TUNER_PHILIPS_SECAM }, + + { "Terratec TerraTV+", + 3, 1, 0, 2, 0x70000, { 2, 3, 1, 1}, + { 0x20000, 0x30000, 0x00000, 0x10000, 0x40000},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Imagenation PXC200", + 5, 1, -1, 4, 0, { 2, 3, 1, 0, 0}, { 0 }, 0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "FlyVideo 98", + 3, 1, 0, 2, 0x8dff00, {2, 3, 1, 1}, + { 0, 0x8dff00, 0x8df700, 0x8de700, 0x8dff00, 0 },0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "iProTV", + 3, 1, 0, 2, 1, { 2, 3, 1, 1}, { 1, 0, 0, 0, 0 },0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + + /* 0x20 */ + { "Intel Create and Share PCI", + 4, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 4, 4, 4, 4},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Terratec TerraTValue", + 3, 1, 0, 2, 0xffff00, { 2, 3, 1, 1}, + { 0x500, 0, 0x300, 0x900, 0x900},0, + 1,1,1,1,0,0,0,1, PLL_NONE, -1 }, + { "Leadtek WinFast 2000", + 3, 1, 0, 2, 0xfff000, { 2, 3, 1, 1,0}, + { 0x621000,0x6ddf07,0x621100,0x620000,0xE210000,0x620000},0, + 1,1,1,1,1,0,0,1, PLL_28, -1 }, + { "Chronos Video Shuttle II", + 3, 3, 0, 2, 0x1800, { 2, 3, 1, 1}, { 0, 0, 0x1000, 0x1000, 0x0800},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + + { "Typhoon TView TV/FM Tuner", + 3, 3, 0, 2, 0x1800, { 2, 3, 1, 1}, { 0, 0x800, 0, 0, 0x1800, 0 },0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "PixelView PlayTV pro", + 3, 1, 0, 2, 0xff, { 2, 3, 1, 1 }, + { 0x21, 0x20, 0x24, 0x2c, 0x29, 0x29 }, 0, + 0,0,0,0,0,0,0,1, PLL_28, -1 }, + { "TView99 CPH063", + 3, 1, 0, 2, 0x551e00, { 2, 3, 1, 1}, + { 0x551400, 0x551200, 0, 0, 0, 0x551200 }, 0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + { "Pinnacle PCTV Rave", + 3, 1, 0, 2, 0x03000F, { 2, 3, 1, 1}, { 2, 0, 0, 0, 1},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, + + /* 0x28 */ + { "STB2", + 3, 1, 0, 2, 7, { 2, 3, 1, 1}, { 4, 0, 2, 3, 1},0, + 0,1,1,1,0,1,1,1, PLL_NONE, -1 }, + { "AVerMedia TVPhone 98", + 3, 4, 0, 2, 4, { 2, 3, 1, 1}, { 13, 14, 11, 7, 0, 0},0, + 1,1,1,1,0,0,0,1, PLL_28, 5 }, + { "ProVideo PV951", /* pic16c54 */ + 3, 1, 0, 2, 0, { 2, 3, 1, 1}, { 0, 0, 0, 0, 0},0, + 0,0,0,0,0,0,0,0, PLL_28, 1 }, + { "Little OnAir TV", + 3, 1, 0, 2, 0xe00b, {2, 3, 1, 1}, + {0xff9ff6, 0xff9ff6, 0xff1ff7, 0, 0xff3ffc},0, + 0,0,0,0,0,0,0,0, PLL_NONE, -1 }, + + { "Sigma TVII-FM", + 2, 1, 0, -1, 3, {2, 3, 1, 1}, {1, 1, 0, 2, 3},0, + 0,0,0,0,0,0,0,0, PLL_NONE, -1 }, + { "MATRIX-Vision MV-Delta 2", + 5, 1, -1, 3, 0, { 2, 3, 1, 0, 0},{0 }, 0, + 0,0,0,0,0,0,0,0, PLL_28, -1 }, + { "Zoltrix Genie TV", + 3, 1, 0, 2, 0xbcf03f, { 2, 3, 1, 1}, + { 0xbc803f, 0, 0xbcb03f, 0, 0xbcb03f}, 0, + 0,0,0,0,0,0,0,0, PLL_28, 5 }, + { "Terratec TV/Radio+", /* Radio ?? */ + 3, 1, 0, 2, 0x1f0000, { 2, 3, 1, 1}, + { 0xe2ffff, 0xebffff, 0, 0, 0xe0ffff, 0xe2ffff },0, + 0,0,0,0,0,0,0,0, PLL_35, 1 }, + + /* 0x30 */ + { "Dynalink Magic TView ", + 3, 1, 0, 2, 15, { 2, 3, 1, 1}, {2,0,0,0,1},0, + 1,1,1,1,0,0,0,1, PLL_28, -1 }, +}; +const int bttv_num_tvcards = (sizeof(bttv_tvcards)/sizeof(struct tvcard)); + +/* ----------------------------------------------------------------------- */ + +static unsigned char eeprom_data[256]; + +static void __devinit bttv_dump_eeprom(struct bttv *btv,int addr) +{ + int i; + + if (bttv_verbose < 2) + return; + /* for debugging: dump eeprom to syslog */ + printk(KERN_DEBUG "bttv%d: dump eeprom @ 0x%02x\n",btv->nr,addr); + for (i = 0; i < 256;) { + printk(KERN_DEBUG " %02x:",i); + do { + printk(" %02x",eeprom_data[i++]); + } while (i % 16); + printk("\n"); + } +} + +static int __devinit bttv_idcard_eeprom(struct bttv *btv) +{ + unsigned id; + int i,n; + + id = (eeprom_data[252] << 24) | + (eeprom_data[253] << 16) | + (eeprom_data[254] << 8) | + (eeprom_data[255]); + if (id == 0 || id == 0xffffffff) + return -1; + + /* look for the card */ + btv->cardid = id; + for (n = -1, i = 0; cards[i].id != 0; i++) + if (cards[i].id == id) + n = i; + + if (n != -1) { + /* found it */ + printk(KERN_INFO "bttv%d: card id: %s (0x%08x) => card=%d\n", + btv->nr,cards[n].name,id,cards[n].cardnr); + return cards[n].cardnr; + } else { + /* 404 */ + printk(KERN_INFO "bttv%d: id: unknown (0x%08x)\n", + btv->nr, id); + printk(KERN_INFO "please mail id, board name and " + "the correct card= insmod option to " + "kraxel@goldbach.in-berlin.de\n"); + return -1; + } +} + +#ifndef HAVE_TVAUDIO +/* can tda9855.c handle this too maybe? */ +static void __devinit init_tda9840(struct bttv *btv) +{ + /* Horrible Hack */ + bttv_I2CWrite(btv, I2C_TDA9840, TDA9840_SW, 0x2a, 1); /* sound mode switching */ + /* 00 - mute + 10 - mono / averaged stereo + 2a - stereo + 12 - dual A + 1a - dual AB + 16 - dual BA + 1e - dual B + 7a - external */ +} +#endif + +void __devinit bttv_idcard(struct bttv *btv) +{ + int type,eeprom = 0; + + btwrite(0, BT848_GPIO_OUT_EN); + + /* try to autodetect the card */ + /* many bt878 cards have a eeprom @ 0xa0 => read ID + and try to identify it */ + if (bttv_I2CRead(btv, I2C_HAUPEE, "eeprom") >= 0) { + eeprom = 0xa0; + bttv_readee(btv,eeprom_data,0xa0); + bttv_dump_eeprom(btv,0xa0); /* DEBUG */ + type = bttv_idcard_eeprom(btv); + if (-1 != type) { + btv->type = type; + } else if (btv->id <= 849) { + /* for unknown bt848, assume old Hauppauge */ + btv->type=BTTV_HAUPPAUGE; + } + + /* STB cards have a eeprom @ 0xae (old bt848) */ + } else if (bttv_I2CRead(btv, I2C_STBEE, "eeprom")>=0) { + btv->type=BTTV_STB; + } + + /* let the user override the autodetected type */ + if (card[btv->nr] >= 0 && card[btv->nr] < bttv_num_tvcards) + btv->type=card[btv->nr]; + + /* print which card config we are using */ + sprintf(btv->video_dev.name,"BT%d%s(%.23s)", + btv->id, + (btv->id==848 && btv->revision==0x12) ? "A" : "", + bttv_tvcards[btv->type].name); + printk(KERN_INFO "bttv%d: model: %s [%s]\n",btv->nr,btv->video_dev.name, + (card[btv->nr] >= 0 && card[btv->nr] < bttv_num_tvcards) ? + "insmod option" : "autodetected"); + + /* board specific initialisations */ + if (btv->type == BTTV_MIRO || btv->type == BTTV_MIROPRO) { + /* auto detect tuner for MIRO cards */ + btv->tuner_type=((btread(BT848_GPIO_DATA)>>10)-1)&7; +#if 0 + if (btv->type == BTTV_MIROPRO) { + if (bttv_verbose) + printk(KERN_INFO "Initializing TEA5757...\n"); + init_tea5757(btv); + } +#endif + } + if (btv->type == BTTV_HAUPPAUGE || btv->type == BTTV_HAUPPAUGE878) { + /* pick up some config infos from the eeprom */ + if (0xa0 != eeprom) { + eeprom = 0xa0; + bttv_readee(btv,eeprom_data,0xa0); + } + hauppauge_eeprom(btv); + hauppauge_boot_msp34xx(btv); + } + if (btv->type == BTTV_PXC200) + init_PXC200(btv); + + /* pll configuration */ + if (!(btv->id==848 && btv->revision==0x11)) { + /* defaults from card list */ + if (PLL_28 == bttv_tvcards[btv->type].pll) { + btv->pll.pll_ifreq=28636363; + btv->pll.pll_crystal=BT848_IFORM_XT0; + } + /* insmod options can override */ + switch (pll[btv->nr]) { + case 0: /* none */ + btv->pll.pll_crystal = 0; + btv->pll.pll_ifreq = 0; + btv->pll.pll_ofreq = 0; + break; + case 1: /* 28 MHz */ + btv->pll.pll_ifreq = 28636363; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal=BT848_IFORM_XT0; + break; + case 2: /* 35 MHz */ + btv->pll.pll_ifreq = 35468950; + btv->pll.pll_ofreq = 0; + btv->pll.pll_crystal=BT848_IFORM_XT1; + break; + } + } + + + /* tuner configuration */ + if (-1 != bttv_tvcards[btv->type].tuner_type) + btv->tuner_type = bttv_tvcards[btv->type].tuner_type; + if (btv->tuner_type != -1) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + + /* try to detect audio/fader chips */ + if (bttv_tvcards[btv->type].msp34xx && + bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx") >=0) { + if (autoload) + request_module("msp3400"); + } + +#ifndef HAVE_TVAUDIO + if (bttv_tvcards[btv->type].tda8425 && + bttv_I2CRead(btv, I2C_TDA8425, "TDA8425") >=0) { + if (autoload) + request_module("tda8425"); + } + + if (bttv_tvcards[btv->type].tda9840 && + bttv_I2CRead(btv, I2C_TDA9840, "TDA9840") >=0) { + init_tda9840(btv); + btv->audio_chip = TDA9840; + /* move this to a module too? */ + init_tda9840(btv); + } + + if (bttv_tvcards[btv->type].tda985x && + bttv_I2CRead(btv, I2C_TDA9850, "TDA985x") >=0) { + if (autoload) + request_module("tda985x"); + } + if (bttv_tvcards[btv->type].tea63xx) { + if (autoload) + request_module("tea6300"); + } +#else + if (bttv_tvcards[btv->type].tda8425 || + bttv_tvcards[btv->type].tda9840 || + bttv_tvcards[btv->type].tda985x || + bttv_tvcards[btv->type].tea63xx) { + if (autoload) + request_module("tvaudio"); + } +#endif + + if (bttv_tvcards[btv->type].tda9875 && + bttv_I2CRead(btv, I2C_TDA9875, "TDA9875") >=0) { + if (autoload) + request_module("tda9875"); + } + + if (bttv_tvcards[btv->type].tda7432 && + bttv_I2CRead(btv, I2C_TDA7432, "TDA7432") >=0) { + if (autoload) + request_module("tda7432"); + } + + + if (bttv_tvcards[btv->type].tea64xx) { + if (autoload) + request_module("tea6420"); + } + + if (bttv_tvcards[btv->type].tuner != -1) { + if (autoload) + request_module("tuner"); + } +} + + +/* ----------------------------------------------------------------------- */ +/* some hauppauge specific stuff */ + +static struct HAUPPAUGE_TUNER +{ + int id; + char *name; +} +hauppauge_tuner[] __devinitdata = +{ + { TUNER_ABSENT, "" }, + { TUNER_ABSENT, "External" }, + { TUNER_ABSENT, "Unspecified" }, + { TUNER_ABSENT, "Philips FI1216" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236" }, + { TUNER_ABSENT, "Philips FI1246" }, + { TUNER_ABSENT, "Philips FI1256" }, + { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" }, + { TUNER_ABSENT, "Philips FI1256 MK2" }, + { TUNER_ABSENT, "Temic 4032FY5" }, + { TUNER_TEMIC_PAL, "Temic 4002FH5" }, + { TUNER_TEMIC_PAL_I, "Temic 4062FY5" }, + { TUNER_ABSENT, "Philips FR1216 MK2" }, + { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" }, + { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" }, + { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" }, + { TUNER_ABSENT, "Philips FR1256 MK2" }, + { TUNER_PHILIPS_PAL, "Philips FM1216" }, + { TUNER_ABSENT, "Philips FM1216MF" }, + { TUNER_PHILIPS_NTSC, "Philips FM1236" }, + { TUNER_PHILIPS_PAL_I, "Philips FM1246" }, + { TUNER_ABSENT, "Philips FM1256" }, + { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" }, + { TUNER_ABSENT, "Samsung TCPN9082D" }, + { TUNER_ABSENT, "Samsung TCPM9092P" }, + { TUNER_TEMIC_PAL, "Temic 4006FH5" }, + { TUNER_ABSENT, "Samsung TCPN9085D" }, + { TUNER_ABSENT, "Samsung TCPB9085P" }, + { TUNER_ABSENT, "Samsung TCPL9091P" }, + { TUNER_ABSENT, "Temic 4039FR5" }, + { TUNER_ABSENT, "Philips FQ1216 ME" }, + { TUNER_TEMIC_PAL_I, "Temic 4066FY5" }, + { TUNER_ABSENT, "Philips TD1536" }, + { TUNER_ABSENT, "Philips TD1536D" }, + { TUNER_ABSENT, "Philips FMR1236" }, + { TUNER_ABSENT, "Philips FI1256MP" }, + { TUNER_ABSENT, "Samsung TCPQ9091P" }, + { TUNER_ABSENT, "Temic 4006FN5" }, + { TUNER_ABSENT, "Temic 4009FR5" }, + { TUNER_ABSENT, "Temic 4046FM5" }, +}; + +static void __devinit hauppauge_eeprom(struct bttv *btv) +{ + if (eeprom_data[9] < sizeof(hauppauge_tuner)/sizeof(struct HAUPPAUGE_TUNER)) + { + btv->tuner_type = hauppauge_tuner[eeprom_data[9]].id; + if (bttv_verbose) + printk("bttv%d: Hauppauge eeprom: tuner=%s (%d)\n",btv->nr, + hauppauge_tuner[eeprom_data[9]].name,btv->tuner_type); + } +} + +static void __devinit hauppauge_boot_msp34xx(struct bttv *btv) +{ + int i; + + /* reset/enable the MSP on some Hauppauge cards */ + /* Thanks to Kyösti Mälkki (kmalkki@cc.hut.fi)! */ + btaor(32, ~32, BT848_GPIO_OUT_EN); + btaor(0, ~32, BT848_GPIO_DATA); + udelay(2500); + btaor(32, ~32, BT848_GPIO_DATA); + + if (bttv_verbose) + printk("bttv%d: Hauppauge msp34xx: reset line init\n",btv->nr); + + /* look if the msp3400 driver is already registered */ + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (btv->i2c_clients[i] != NULL && + btv->i2c_clients[i]->driver->id == I2C_DRIVERID_MSP3400) { + return; + } + } + + /* if not: look for the chip ... */ + if (bttv_I2CRead(btv, I2C_MSP3400, "MSP34xx")) { + /* ... if found re-register to trigger a i2c bus rescan, */ + /* this time with the msp34xx chip activated */ + i2c_bit_del_bus(&btv->i2c_adap); + i2c_bit_add_bus(&btv->i2c_adap); + } +} + + +/* ----------------------------------------------------------------------- */ +/* Imagenation L-Model PXC200 Framegrabber */ +/* This is basically the same procedure as + * used by Alessandro Rubini in his pxc200 + * driver, but using BTTV functions */ + +static void __devinit init_PXC200(struct bttv *btv) +{ + static const int vals[] = { 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0d, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x00 }; + int i,tmp; + + /* Initialise GPIO-connevted stuff */ + btwrite(1<<13,BT848_GPIO_OUT_EN); /* Reset pin only */ + btwrite(0,BT848_GPIO_DATA); + udelay(3); + btwrite(1<<13,BT848_GPIO_DATA); + /* GPIO inputs are pulled up, so no need to drive + * reset pin any longer */ + btwrite(0,BT848_GPIO_OUT_EN); + + /* we could/should try and reset/control the AD pots? but + right now we simply turned off the crushing. Without + this the AGC drifts drifts + remember the EN is reverse logic --> + setting BT848_ADC_AGC_EN disable the AGC + tboult@eecs.lehigh.edu + */ + btwrite(BT848_ADC_RESERVED|BT848_ADC_AGC_EN, BT848_ADC); + + /* Initialise MAX517 DAC */ + printk(KERN_INFO "Setting DAC reference voltage level ...\n"); + bttv_I2CWrite(btv,0x5E,0,0x80,1); + + /* Initialise 12C508 PIC */ + /* The I2CWrite and I2CRead commmands are actually to the + * same chips - but the R/W bit is included in the address + * argument so the numbers are different */ + + printk(KERN_INFO "Initialising 12C508 PIC chip ...\n"); + + for (i = 0; i < sizeof(vals)/sizeof(int); i++) { + tmp=bttv_I2CWrite(btv,0x1E,vals[i],0,1); + printk(KERN_INFO "I2C Write(0x08) = %i\nI2C Read () = %x\n\n", + tmp,bttv_I2CRead(btv,0x1F,NULL)); + } + printk(KERN_INFO "PXC200 Initialised.\n"); +} + +/* ----------------------------------------------------------------------- */ +/* Miro Pro radio stuff -- the tea5757 is connected to some GPIO ports */ +/* + * Copyright (c) 1999 Csaba Halasz <qgehali@uni-miskolc.hu> + * This code is placed under the terms of the GNU General Public License + * + * Brutally hacked by Dan Sheridan <dan.sheridan@contact.org.uk> djs52 8/3/00 + */ + +/* bus bits on the GPIO port */ +#define TEA_WE 6 +#define TEA_DATA 9 +#define TEA_CLK 8 +#define TEA_MOST 7 + +#define BUS_LOW(bit) btand(~(1<<TEA_##bit), BT848_GPIO_DATA) +#define BUS_HIGH(bit) btor((1<<TEA_##bit), BT848_GPIO_DATA) +#define BUS_IN(bit) ((btread(BT848_GPIO_DATA) >> TEA_##bit) & 1) + +/* TEA5757 register bits */ +#define TEA_FREQ 0:14 +#define TEA_BUFFER 15:15 + +#define TEA_SIGNAL_STRENGTH 16:17 + +#define TEA_PORT1 18:18 +#define TEA_PORT0 19:19 + +#define TEA_BAND 20:21 +#define TEA_BAND_FM 0 +#define TEA_BAND_MW 1 +#define TEA_BAND_LW 2 +#define TEA_BAND_SW 3 + +#define TEA_MONO 22:22 +#define TEA_ALLOW_STEREO 0 +#define TEA_FORCE_MONO 1 + +#define TEA_SEARCH_DIRECTION 23:23 +#define TEA_SEARCH_DOWN 0 +#define TEA_SEARCH_UP 1 + +#define TEA_STATUS 24:24 +#define TEA_STATUS_TUNED 0 +#define TEA_STATUS_SEARCHING 1 + +/* Low-level stuff */ +static int tea_read(struct bttv *btv) +{ + int value = 0; + long timeout; + int i; + + /* better safe than sorry */ + btaor((1<<TEA_CLK) | (1<<TEA_WE), ~((1<<TEA_CLK) | (1<<TEA_DATA) | (1<<TEA_WE) | (1<<TEA_MOST)), BT848_GPIO_OUT_EN); + + BUS_LOW(WE); + BUS_LOW(CLK); + + udelay(10); + for(timeout = jiffies + 10 * HZ; + BUS_IN(DATA) == 1 && time_before(jiffies, timeout); + schedule()); /* 10 s */ + if (BUS_IN(DATA) == 1) { + printk("tea5757: read timeout\n"); + return -1; + } + for(timeout = jiffies + HZ/5; + BUS_IN(MOST) == 1 && time_before(jiffies, timeout); + schedule()); /* 0.2 s */ + if (bttv_debug) printk("tea5757:"); + for(i = 0; i < 24; i++) + { + udelay(10); + BUS_HIGH(CLK); + udelay(10); + if (bttv_debug) printk("%c", (BUS_IN(MOST) == 0)?'T':'-'); + BUS_LOW(CLK); + value <<= 1; + value |= (BUS_IN(DATA) == 0)?0:1; /* MSB first */ + if (bttv_debug) printk("%c", (BUS_IN(MOST) == 0)?'S':'M'); + } + if (bttv_debug) printk("\ntea5757: read 0x%X\n", value); + return value; +} + +static int tea_write(struct bttv *btv, int value) +{ + int i; + int reg = value; + + btaor((1<<TEA_CLK) | (1<<TEA_WE) | (1<<TEA_DATA), ~((1<<TEA_CLK) | (1<<TEA_DATA) | (1<<TEA_WE) | (1<<TEA_MOST)), BT848_GPIO_OUT_EN); + if (bttv_debug) printk("tea5757: write 0x%X\n", value); + BUS_LOW(CLK); + BUS_HIGH(WE); + for(i = 0; i < 25; i++) + { + if (reg & 0x1000000) + BUS_HIGH(DATA); + else + BUS_LOW(DATA); + reg <<= 1; + BUS_HIGH(CLK); + udelay(10); + BUS_LOW(CLK); + udelay(10); + } + BUS_LOW(WE); /* unmute !!! */ + return 0; +} + +void tea5757_set_freq(struct bttv *btv, unsigned short freq) +{ + tea_write(btv, 5 * freq + 0x358); /* add 10.7MHz (see docs) */ + if (bttv_debug) tea_read(btv); +} + +void init_tea5757(struct bttv *btv) +{ + BUS_LOW(CLK); + BUS_LOW(WE); /* just to be on the safe side... */ + + /* software CLK (unused) */ + btaor(0, BT848_GPIO_DMA_CTL_GPCLKMODE, BT848_GPIO_DMA_CTL); + /* normal mode for GPIO */ + btaor(0, BT848_GPIO_DMA_CTL_GPIOMODE, BT848_GPIO_DMA_CTL); +} + +/* ----------------------------------------------------------------------- */ +/* winview */ + +void winview_setvol(struct bttv *btv, struct video_audio *v) +{ + /* PT2254A programming Jon Tombs, jon@gte.esi.us.es */ + int bits_out, loops, vol, data; + + /* 32 levels logarithmic */ + vol = 32 - ((v->volume>>11)); + /* units */ + bits_out = (PT2254_DBS_IN_2>>(vol%5)); + /* tens */ + bits_out |= (PT2254_DBS_IN_10>>(vol/5)); + bits_out |= PT2254_L_CHANEL | PT2254_R_CHANEL; + data = btread(BT848_GPIO_DATA); + data &= ~(WINVIEW_PT2254_CLK| WINVIEW_PT2254_DATA| + WINVIEW_PT2254_STROBE); + for (loops = 17; loops >= 0 ; loops--) { + if (bits_out & (1<<loops)) + data |= WINVIEW_PT2254_DATA; + else + data &= ~WINVIEW_PT2254_DATA; + btwrite(data, BT848_GPIO_DATA); + udelay(5); + data |= WINVIEW_PT2254_CLK; + btwrite(data, BT848_GPIO_DATA); + udelay(5); + data &= ~WINVIEW_PT2254_CLK; + btwrite(data, BT848_GPIO_DATA); + } + data |= WINVIEW_PT2254_STROBE; + data &= ~WINVIEW_PT2254_DATA; + btwrite(data, BT848_GPIO_DATA); + udelay(10); + data &= ~WINVIEW_PT2254_STROBE; + btwrite(data, BT848_GPIO_DATA); +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-driver.c b/drivers/media/video/bttv-driver.c new file mode 100644 index 000000000..6c88a757e --- /dev/null +++ b/drivers/media/video/bttv-driver.c @@ -0,0 +1,3272 @@ +/* + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> +#include <linux/wrapper.h> +#include <linux/interrupt.h> +#include <linux/kmod.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#ifdef HACKING +# include <linux/iobuf.h> +#endif + +#include "bttv.h" +#include "tuner.h" + +#define DEBUG(x) /* Debug driver */ +#define IDEBUG(x) /* Debug interrupt handler */ +#define MIN(a,b) (((a)>(b))?(b):(a)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + + +static void bt848_set_risc_jmps(struct bttv *btv, int state); + +int bttv_num; /* number of Bt848s in use */ +struct bttv bttvs[BTTV_MAX]; + + +/* insmod args */ +MODULE_PARM(triton1,"i"); +MODULE_PARM(radio,"1-4i"); +MODULE_PARM(bigendian,"i"); +MODULE_PARM(fieldnr,"i"); +MODULE_PARM(bttv_verbose,"i"); +MODULE_PARM(bttv_debug,"i"); +MODULE_PARM(gbuffers,"i"); +MODULE_PARM(gbufsize,"i"); + +MODULE_DESCRIPTION("bttv - v4l driver module for bt848/878 based cards"); +MODULE_AUTHOR("Ralph Metzler & Marcus Metzler & Gerd Knorr"); + +#if defined(__sparc__) || defined(__powerpc__) +static unsigned int bigendian=1; +#else +static unsigned int bigendian=0; +#endif +static int triton1=0; +static unsigned int radio[BTTV_MAX]; +static unsigned int fieldnr = 0; +static unsigned int gbuffers = 2; +static unsigned int gbufsize = BTTV_MAX_FBUF; +unsigned int bttv_debug = 0; +unsigned int bttv_verbose = 1; + +#define I2C_TIMING (0x7<<4) +#define I2C_DELAY 10 + +#define I2C_SET(CTRL,DATA) \ + { btwrite((CTRL<<1)|(DATA), BT848_I2C); udelay(I2C_DELAY); } +#define I2C_GET() (btread(BT848_I2C)&1) + +#define BURSTOFFSET 76 +#define BTTV_ERRORS 5 + + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +#define MDEBUG(x) do { } while(0) /* Debug memory management */ + +/* [DaveM] I've recoded most of this so that: + * 1) It's easier to tell what is happening + * 2) It's more portable, especially for translating things + * out of vmalloc mapped areas in the kernel. + * 3) Less unnecessary translations happen. + * + * The code used to assume that the kernel vmalloc mappings + * existed in the page tables of every process, this is simply + * not guarenteed. We now use pgd_offset_k which is the + * defined way to get at the kernel page tables. + */ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none(*pgd)) { + pmd = pmd_offset(pgd, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset(pmd, adr); + pte = *ptep; + if(pte_present(pte)) { + ret = (unsigned long) page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + } + } + } + MDEBUG(printk("uv2kva(%lx-->%lx)", adr, ret)); + return ret; +} + +static inline unsigned long uvirt_to_bus(unsigned long adr) +{ + unsigned long kva, ret; + + kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr); + ret = virt_to_bus((void *)kva); + MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret)); + return ret; +} + +static inline unsigned long kvirt_to_bus(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = virt_to_bus((void *)kva); + MDEBUG(printk("kv2b(%lx-->%lx)", adr, ret)); + return ret; +} + +/* Here we want the physical address of the memory. + * This is used when initializing the contents of the + * area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = __pa(kva); + MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret)); + return ret; +} + +static void * rvmalloc(signed long size) +{ + void * mem; + unsigned long adr, page; + + mem=vmalloc_32(size); + if (mem) + { + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + mem_map_reserve(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + } + return mem; +} + +static void rvfree(void * mem, signed long size) +{ + unsigned long adr, page; + + if (mem) + { + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_pa(adr); + mem_map_unreserve(virt_to_page(__va(page))); + adr+=PAGE_SIZE; + size-=PAGE_SIZE; + } + vfree(mem); + } +} + + + +/* + * Create the giant waste of buffer space we need for now + * until we get DMA to user space sorted out (probably 2.3.x) + * + * We only create this as and when someone uses mmap + */ + +static int fbuffer_alloc(struct bttv *btv) +{ + if(!btv->fbuffer) + btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize); + else + printk(KERN_ERR "bttv%d: Double alloc of fbuffer!\n", + btv->nr); + if(!btv->fbuffer) + return -ENOBUFS; + return 0; +} + + +static int __devinit init_bttv_i2c(struct bttv *btv) +{ + /* i2c bit_adapter */ + memcpy(&btv->i2c_adap, &bttv_i2c_adap_template, sizeof(struct i2c_adapter)); + memcpy(&btv->i2c_algo, &bttv_i2c_algo_template, sizeof(struct i2c_algo_bit_data)); + memcpy(&btv->i2c_client, &bttv_i2c_client_template, sizeof(struct i2c_client)); + + sprintf(btv->i2c_adap.name+strlen(btv->i2c_adap.name), + " #%d", btv->nr); + btv->i2c_algo.data = btv; + btv->i2c_adap.data = btv; + btv->i2c_adap.algo_data = &btv->i2c_algo; + btv->i2c_client.adapter = &btv->i2c_adap; + + bttv_bit_setscl(btv,1); + bttv_bit_setsda(btv,1); + + btv->i2c_ok = i2c_bit_add_bus(&btv->i2c_adap); + return btv->i2c_ok; +} + + +/* ----------------------------------------------------------------------- */ + +static void audio(struct bttv *btv, int mode, int no_irq_context) +{ + btaor(bttv_tvcards[btv->type].gpiomask, ~bttv_tvcards[btv->type].gpiomask, + BT848_GPIO_OUT_EN); + + switch (mode) + { + case AUDIO_MUTE: + btv->audio|=AUDIO_MUTE; + break; + case AUDIO_UNMUTE: + btv->audio&=~AUDIO_MUTE; + mode=btv->audio; + break; + case AUDIO_OFF: + mode=AUDIO_OFF; + break; + case AUDIO_ON: + mode=btv->audio; + break; + default: + btv->audio&=AUDIO_MUTE; + btv->audio|=mode; + break; + } + /* if audio mute or not in H-lock, turn audio off */ + if ((btv->audio&AUDIO_MUTE)) + mode=AUDIO_OFF; + if ((mode == AUDIO_TUNER) && (btv->radio)) + mode = AUDIO_RADIO; + btaor(bttv_tvcards[btv->type].audiomux[mode], + ~bttv_tvcards[btv->type].gpiomask, BT848_GPIO_DATA); + if (no_irq_context) + bttv_call_i2c_clients(btv,AUDC_SET_INPUT,&(mode)); +} + + +extern inline void bt848_dma(struct bttv *btv, uint state) +{ + if (state) + btor(3, BT848_GPIO_DMA_CTL); + else + btand(~3, BT848_GPIO_DMA_CTL); +} + + +/* If Bt848a or Bt849, use PLL for PAL/SECAM and crystal for NTSC*/ + +/* Frequency = (F_input / PLL_X) * PLL_I.PLL_F/PLL_C + PLL_X = Reference pre-divider (0=1, 1=2) + PLL_C = Post divider (0=6, 1=4) + PLL_I = Integer input + PLL_F = Fractional input + + F_input = 28.636363 MHz: + PAL (CLKx2 = 35.46895 MHz): PLL_X = 1, PLL_I = 0x0E, PLL_F = 0xDCF9, PLL_C = 0 +*/ + +static void set_pll_freq(struct bttv *btv, unsigned int fin, unsigned int fout) +{ + unsigned char fl, fh, fi; + + /* prevent overflows */ + fin/=4; + fout/=4; + + fout*=12; + fi=fout/fin; + + fout=(fout%fin)*256; + fh=fout/fin; + + fout=(fout%fin)*256; + fl=fout/fin; + + /*printk("0x%02x 0x%02x 0x%02x\n", fi, fh, fl);*/ + btwrite(fl, BT848_PLL_F_LO); + btwrite(fh, BT848_PLL_F_HI); + btwrite(fi|BT848_PLL_X, BT848_PLL_XCI); +} + +static int set_pll(struct bttv *btv) +{ + int i; + unsigned long tv; + + if (!btv->pll.pll_crystal) + return 0; + + if (btv->pll.pll_ifreq == btv->pll.pll_ofreq) { + /* no PLL needed */ + if (btv->pll.pll_current == 0) { + /* printk ("bttv%d: PLL: is off\n",btv->nr); */ + return 0; + } + printk ("bttv%d: PLL: switching off\n",btv->nr); + btwrite(0x00,BT848_TGCTRL); + btwrite(0x00,BT848_PLL_XCI); + btv->pll.pll_current = 0; + return 0; + } + + if (btv->pll.pll_ofreq == btv->pll.pll_current) { + /* printk("bttv%d: PLL: no change required\n",btv->nr); */ + return 1; + } + + if (bttv_verbose) + printk("bttv%d: PLL: %d => %d ... ",btv->nr, + btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + set_pll_freq(btv, btv->pll.pll_ifreq, btv->pll.pll_ofreq); + + /* Let other people run while the PLL stabilizes */ + tv=jiffies+HZ/10; /* .1 seconds */ + do + { + schedule(); + } + while(time_before(jiffies,tv)); + + for (i=0; i<100; i++) + { + if ((btread(BT848_DSTATUS)&BT848_DSTATUS_PLOCK)) + btwrite(0,BT848_DSTATUS); + else + { + btwrite(0x08,BT848_TGCTRL); + btv->pll.pll_current = btv->pll.pll_ofreq; + if (bttv_verbose) + printk("ok\n"); + return 1; + } + mdelay(10); + } + btv->pll.pll_current = 0; + if (bttv_verbose) + printk("oops\n"); + return -1; +} + +static void bt848_muxsel(struct bttv *btv, unsigned int input) +{ + btaor(bttv_tvcards[btv->type].gpiomask2,~bttv_tvcards[btv->type].gpiomask2, + BT848_GPIO_OUT_EN); + + /* This seems to get rid of some synchronization problems */ + btand(~(3<<5), BT848_IFORM); + mdelay(10); + + + input %= bttv_tvcards[btv->type].video_inputs; + if (input==bttv_tvcards[btv->type].svhs) + { + btor(BT848_CONTROL_COMP, BT848_E_CONTROL); + btor(BT848_CONTROL_COMP, BT848_O_CONTROL); + } + else + { + btand(~BT848_CONTROL_COMP, BT848_E_CONTROL); + btand(~BT848_CONTROL_COMP, BT848_O_CONTROL); + } + btaor((bttv_tvcards[btv->type].muxsel[input&7]&3)<<5, ~(3<<5), BT848_IFORM); + audio(btv, (input!=bttv_tvcards[btv->type].tuner) ? + AUDIO_EXTERN : AUDIO_TUNER, 1); + btaor(bttv_tvcards[btv->type].muxsel[input]>>4, + ~bttv_tvcards[btv->type].gpiomask2, BT848_GPIO_DATA); +} + + +struct tvnorm +{ + u32 Fsc; + u16 swidth, sheight; /* scaled standard width, height */ + u16 totalwidth; + u8 adelay, bdelay, iform; + u32 scaledtwidth; + u16 hdelayx1, hactivex1; + u16 vdelay; + u8 vbipack; +}; + +static struct tvnorm tvnorms[] = { + /* PAL-BDGHI */ + /* max. active video is actually 922, but 924 is divisible by 4 and 3! */ + /* actually, max active PAL with HSCALE=0 is 948, NTSC is 768 - nil */ + { 35468950, + 924, 576, 1135, 0x7f, 0x72, (BT848_IFORM_PAL_BDGHI|BT848_IFORM_XT1), + 1135, 186, 924, 0x20, 255}, + + /* NTSC */ + { 28636363, + 768, 480, 910, 0x68, 0x5d, (BT848_IFORM_NTSC|BT848_IFORM_XT0), + 910, 128, 910, 0x1a, 144}, +#if 0 + /* SECAM EAST */ + { 35468950, + 768, 576, 1135, 0x7f, 0xb0, (BT848_IFORM_SECAM|BT848_IFORM_XT1), + 944, 186, 922, 0x20, 255}, +#else + /* SECAM L */ + { 35468950, + 924, 576, 1135, 0x7f, 0xb0, (BT848_IFORM_SECAM|BT848_IFORM_XT1), + 1135, 186, 922, 0x20, 255}, +#endif + /* PAL-NC */ + { 28636363, + 640, 576, 910, 0x68, 0x5d, (BT848_IFORM_PAL_NC|BT848_IFORM_XT0), + 780, 130, 734, 0x1a, 144}, + /* PAL-M */ + { 28636363, + 640, 480, 910, 0x68, 0x5d, (BT848_IFORM_PAL_M|BT848_IFORM_XT0), + 780, 135, 754, 0x1a, 144}, + /* PAL-N */ + { 35468950, + 768, 576, 1135, 0x7f, 0x72, (BT848_IFORM_PAL_N|BT848_IFORM_XT1), + 944, 186, 922, 0x20, 144}, + /* NTSC-Japan */ + { 28636363, + 640, 480, 910, 0x68, 0x5d, (BT848_IFORM_NTSC_J|BT848_IFORM_XT0), + 780, 135, 754, 0x16, 144}, +}; +#define TVNORMS (sizeof(tvnorms)/sizeof(tvnorm)) +#define VBI_SPL 2044 + +/* RISC command to write one VBI data line */ +#define VBI_RISC BT848_RISC_WRITE|VBI_SPL|BT848_RISC_EOL|BT848_RISC_SOL + +static void make_vbitab(struct bttv *btv) +{ + int i; + unsigned int *po=(unsigned int *) btv->vbi_odd; + unsigned int *pe=(unsigned int *) btv->vbi_even; + + if (bttv_debug > 1) + printk("bttv%d: vbi1: po=%08lx pe=%08lx\n", + btv->nr,virt_to_bus(po), virt_to_bus(pe)); + + *(po++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); *(po++)=0; + for (i=0; i<16; i++) + { + *(po++)=cpu_to_le32(VBI_RISC); + *(po++)=cpu_to_le32(kvirt_to_bus((unsigned long)btv->vbibuf+i*2048)); + } + *(po++)=cpu_to_le32(BT848_RISC_JUMP); + *(po++)=cpu_to_le32(virt_to_bus(btv->risc_jmp+4)); + + *(pe++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); *(pe++)=0; + for (i=16; i<32; i++) + { + *(pe++)=cpu_to_le32(VBI_RISC); + *(pe++)=cpu_to_le32(kvirt_to_bus((unsigned long)btv->vbibuf+i*2048)); + } + *(pe++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(0x01<<16)); + *(pe++)=cpu_to_le32(virt_to_bus(btv->risc_jmp+10)); + + if (bttv_debug > 1) + printk("bttv%d: vbi2: po=%08lx pe=%08lx\n", + btv->nr,virt_to_bus(po), virt_to_bus(pe)); +} + +static int fmtbppx2[16] = { + 8, 6, 4, 4, 4, 3, 2, 2, 4, 3, 0, 0, 0, 0, 2, 0 +}; + +static int palette2fmt[] = { + 0, + BT848_COLOR_FMT_Y8, + BT848_COLOR_FMT_RGB8, + BT848_COLOR_FMT_RGB16, + BT848_COLOR_FMT_RGB24, + BT848_COLOR_FMT_RGB32, + BT848_COLOR_FMT_RGB15, + BT848_COLOR_FMT_YUY2, + BT848_COLOR_FMT_BtYUV, + -1, + -1, + -1, + BT848_COLOR_FMT_RAW, + BT848_COLOR_FMT_YCrCb422, + BT848_COLOR_FMT_YCrCb411, + BT848_COLOR_FMT_YCrCb422, + BT848_COLOR_FMT_YCrCb411, +}; +#define PALETTEFMT_MAX (sizeof(palette2fmt)/sizeof(int)) + +static int make_rawrisctab(struct bttv *btv, unsigned int *ro, + unsigned int *re, unsigned int *vbuf) +{ + unsigned long line; + unsigned long bpl=1024; /* bytes per line */ + unsigned long vadr=(unsigned long) vbuf; + + *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(ro++)=cpu_to_le32(0); + *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(re++)=cpu_to_le32(0); + + /* In PAL 650 blocks of 256 DWORDs are sampled, but only if VDELAY + is 2 and without separate VBI grabbing. + We'll have to handle this inside the IRQ handler ... */ + + for (line=0; line < 640; line++) + { + *(ro++)=cpu_to_le32(BT848_RISC_WRITE|bpl|BT848_RISC_SOL|BT848_RISC_EOL); + *(ro++)=cpu_to_le32(kvirt_to_bus(vadr)); + *(re++)=cpu_to_le32(BT848_RISC_WRITE|bpl|BT848_RISC_SOL|BT848_RISC_EOL); + *(re++)=cpu_to_le32(kvirt_to_bus(vadr+gbufsize/2)); + vadr+=bpl; + } + + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16)); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + + return 0; +} + +static int make_prisctab(struct bttv *btv, unsigned int *ro, + unsigned int *re, + unsigned int *vbuf, unsigned short width, + unsigned short height, unsigned short fmt) +{ + unsigned long line, lmask; + unsigned long bl, blcr, blcb, rcmd; + unsigned long todo; + unsigned int **rp; + int inter; + unsigned long cbadr, cradr; + unsigned long vadr=(unsigned long) vbuf; + int shift, csize; + + if (bttv_debug > 1) + printk("bttv%d: prisc1: ro=%08lx re=%08lx\n", + btv->nr,virt_to_bus(ro), virt_to_bus(re)); + + switch(fmt) + { + case VIDEO_PALETTE_YUV422P: + csize=(width*height)>>1; + shift=1; + lmask=0; + break; + + case VIDEO_PALETTE_YUV411P: + csize=(width*height)>>2; + shift=2; + lmask=0; + break; + + case VIDEO_PALETTE_YUV420P: + csize=(width*height)>>2; + shift=1; + lmask=1; + break; + + case VIDEO_PALETTE_YUV410P: + csize=(width*height)>>4; + shift=2; + lmask=3; + break; + + default: + return -1; + } + cbadr=vadr+(width*height); + cradr=cbadr+csize; + inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0; + + *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3); + *(ro++)=0; + *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM3); + *(re++)=0; + + for (line=0; line < (height<<(1^inter)); line++) + { + if(line==height) + { + vadr+=csize<<1; + cbadr=vadr+(width*height); + cradr=cbadr+csize; + } + if (inter) + rp= (line&1) ? &re : &ro; + else + rp= (line>=height) ? &ro : &re; + + + if(line&lmask) + rcmd=BT848_RISC_WRITE1S23|BT848_RISC_SOL; + else + rcmd=BT848_RISC_WRITE123|BT848_RISC_SOL; + + todo=width; + while(todo) + { + bl=PAGE_SIZE-((PAGE_SIZE-1)&vadr); + blcr=(PAGE_SIZE-((PAGE_SIZE-1)&cradr))<<shift; + blcb=(PAGE_SIZE-((PAGE_SIZE-1)&cbadr))<<shift; + bl=(blcr<bl) ? blcr : bl; + bl=(blcb<bl) ? blcb : bl; + bl=(bl>todo) ? todo : bl; + blcr=bl>>shift; + blcb=blcr; + /* bl now containts the longest row that can be written */ + todo-=bl; + if(!todo) rcmd|=BT848_RISC_EOL; /* if this is the last EOL */ + + *((*rp)++)=cpu_to_le32(rcmd|bl); + *((*rp)++)=cpu_to_le32(blcb|(blcr<<16)); + *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr)); + vadr+=bl; + if((rcmd&(15<<28))==BT848_RISC_WRITE123) + { + *((*rp)++)=(kvirt_to_bus(cbadr)); + cbadr+=blcb; + *((*rp)++)=cpu_to_le32(kvirt_to_bus(cradr)); + cradr+=blcr; + } + + rcmd&=~BT848_RISC_SOL; /* only the first has SOL */ + } + } + + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16)); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + + if (bttv_debug > 1) + printk("bttv%d: prisc2: ro=%08lx re=%08lx\n", + btv->nr,virt_to_bus(ro), virt_to_bus(re)); + + return 0; +} + +static int make_vrisctab(struct bttv *btv, unsigned int *ro, + unsigned int *re, + unsigned int *vbuf, unsigned short width, + unsigned short height, unsigned short palette) +{ + unsigned long line; + unsigned long bpl; /* bytes per line */ + unsigned long bl; + unsigned long todo; + unsigned int **rp; + int inter; + unsigned long vadr=(unsigned long) vbuf; + + if (palette==VIDEO_PALETTE_RAW) + return make_rawrisctab(btv, ro, re, vbuf); + if (palette>=VIDEO_PALETTE_PLANAR) + return make_prisctab(btv, ro, re, vbuf, width, height, palette); + + if (bttv_debug > 1) + printk("bttv%d: vrisc1: ro=%08lx re=%08lx\n", + btv->nr,virt_to_bus(ro), virt_to_bus(re)); + + inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0; + bpl=width*fmtbppx2[palette2fmt[palette]&0xf]/2; + + *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(ro++)=cpu_to_le32(0); + *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(re++)=cpu_to_le32(0); + + for (line=0; line < (height<<(1^inter)); line++) + { + if (inter) + rp= (line&1) ? &re : &ro; + else + rp= (line>=height) ? &ro : &re; + + bl=PAGE_SIZE-((PAGE_SIZE-1)&vadr); + if (bpl<=bl) + { + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + BT848_RISC_EOL|bpl); + *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr)); + vadr+=bpl; + } + else + { + todo=bpl; + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|bl); + *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr)); + vadr+=bl; + todo-=bl; + while (todo>PAGE_SIZE) + { + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|PAGE_SIZE); + *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr)); + vadr+=PAGE_SIZE; + todo-=PAGE_SIZE; + } + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL|todo); + *((*rp)++)=cpu_to_le32(kvirt_to_bus(vadr)); + vadr+=todo; + } + } + + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16)); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + + if (bttv_debug > 1) + printk("bttv%d: vrisc2: ro=%08lx re=%08lx\n", + btv->nr,virt_to_bus(ro), virt_to_bus(re)); + + return 0; +} + +static unsigned char lmaskt[8] = +{ 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80}; +static unsigned char rmaskt[8] = +{ 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + +static void clip_draw_rectangle(unsigned char *clipmap, int x, int y, int w, int h) +{ + unsigned char lmask, rmask, *p; + int W, l, r; + int i; + + if (bttv_debug > 1) + printk("bttv clip: %dx%d+%d+%d\n",w,h,x,y); + + /* bitmap is fixed width, 128 bytes (1024 pixels represented) */ + if (x<0) + { + w+=x; + x=0; + } + if (y<0) + { + h+=y; + y=0; + } + if (w < 0 || h < 0) /* catch bad clips */ + return; + /* out of range data should just fall through */ + if (y+h>=625) + h=625-y; + if (x+w>=1024) + w=1024-x; + + l=x>>3; + r=(x+w-1)>>3; + W=r-l-1; + lmask=lmaskt[x&7]; + rmask=rmaskt[(x+w-1)&7]; + p=clipmap+128*y+l; + + if (W>0) + { + for (i=0; i<h; i++, p+=128) + { + *p|=lmask; + memset(p+1, 0xff, W); + p[W+1]|=rmask; + } + } else if (!W) { + for (i=0; i<h; i++, p+=128) + { + p[0]|=lmask; + p[1]|=rmask; + } + } else { + for (i=0; i<h; i++, p+=128) + p[0]|=lmask&rmask; + } + + +} + +static void make_clip_tab(struct bttv *btv, struct video_clip *cr, int ncr) +{ + int i, line, x, y, bpl, width, height, inter, maxw; + unsigned int bpp, dx, sx, **rp, *ro, *re, flags, len; + unsigned long adr; + unsigned char *clipmap, *clipline, cbit, lastbit, outofmem; + + /* take care: bpp != btv->win.bpp is allowed here */ + bpp = fmtbppx2[btv->win.color_fmt&0xf]/2; + bpl=btv->win.bpl; + adr=btv->win.vidadr + btv->win.x * btv->win.bpp + btv->win.y * bpl; + inter=(btv->win.interlace&1)^1; + width=btv->win.width; + height=btv->win.height; + if (bttv_debug > 1) + printk("bttv%d: clip1: pal=%d size=%dx%d, bpl=%d bpp=%d\n", + btv->nr,btv->picture.palette,width,height,bpl,bpp); + if(width > 1023) + width = 1023; /* sanity check */ + if(height > 625) + height = 625; /* sanity check */ + ro=btv->risc_scr_odd; + re=btv->risc_scr_even; + + if (bttv_debug) + printk("bttv%d: clip: ro=%08lx re=%08lx\n", + btv->nr,virt_to_bus(ro), virt_to_bus(re)); + + if ((clipmap=vmalloc(VIDEO_CLIPMAP_SIZE))==NULL) { + /* can't clip, don't generate any risc code */ + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + } + if (ncr < 0) { /* bitmap was pased */ + memcpy(clipmap, (unsigned char *)cr, VIDEO_CLIPMAP_SIZE); + } else { /* convert rectangular clips to a bitmap */ + memset(clipmap, 0, VIDEO_CLIPMAP_SIZE); /* clear map */ + for (i=0; i<ncr; i++) + clip_draw_rectangle(clipmap, cr[i].x, cr[i].y, + cr[i].width, cr[i].height); + } + /* clip against viewing window AND screen + so we do not have to rely on the user program + */ + maxw = (bpl - btv->win.x * btv->win.bpp) / bpp; + clip_draw_rectangle(clipmap, (width > maxw) ? maxw : width, + 0, 1024, 768); + clip_draw_rectangle(clipmap,0,(btv->win.y+height>btv->win.sheight) ? + (btv->win.sheight-btv->win.y) : height,1024,768); + if (btv->win.x<0) + clip_draw_rectangle(clipmap, 0, 0, -(btv->win.x), 768); + if (btv->win.y<0) + clip_draw_rectangle(clipmap, 0, 0, 1024, -(btv->win.y)); + + *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(ro++)=cpu_to_le32(0); + *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(re++)=cpu_to_le32(0); + + /* translate bitmap to risc code */ + for (line=outofmem=0; line < (height<<inter) && !outofmem; line++) + { + y = line>>inter; + rp= (line&1) ? &re : &ro; + clipline = clipmap + (y<<7); /* running pointers ... */ + lastbit = *clipline & 1; + for(x=dx=0,sx=0; x<=width && !outofmem;) { + if (0 == (x&7)) { + /* check bytes not bits if we can ... */ + if (lastbit) { + while (0xff==*clipline && x<width-8) { + x += 8; + dx += 8; + clipline++; + } + } else { + while (0x00==*clipline && x<width-8) { + x += 8; + dx += 8; + clipline++; + } + } + } + cbit = *clipline & (1<<(x&7)); + if (x < width && !lastbit == !cbit) { + dx++; + } else { + /* generate the dma controller code */ + len = dx * bpp; + flags = ((bpp==4) ? BT848_RISC_BYTE3 : 0); + flags |= ((!sx) ? BT848_RISC_SOL : 0); + flags |= ((sx + dx == width) ? BT848_RISC_EOL : 0); + if (!lastbit) { + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|flags|len); + *((*rp)++)=cpu_to_le32(adr + bpp * sx); + } else { + *((*rp)++)=cpu_to_le32(BT848_RISC_SKIP|flags|len); + } + lastbit=cbit; + sx += dx; + dx = 1; + if (ro - btv->risc_scr_odd>RISCMEM_LEN/2 - 16) + outofmem++; + if (re - btv->risc_scr_even>RISCMEM_LEN/2 - 16) + outofmem++; + } + x++; + if (0 == (x&7)) + clipline++; + } + if ((!inter)||(line&1)) + adr+=bpl; + } + + vfree(clipmap); + /* outofmem flag relies on the following code to discard extra data */ + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + + if (bttv_debug > 1) + printk("bttv%d: clip2: pal=%d size=%dx%d, bpl=%d bpp=%d\n", + btv->nr,btv->picture.palette,width,height,bpl,bpp); +} + +/* + * Set the registers for the size we have specified. Don't bother + * trying to understand this without the BT848 manual in front of + * you [AC]. + * + * PS: The manual is free for download in .pdf format from + * www.brooktree.com - nicely done those folks. + */ + +static inline void bt848_set_eogeo(struct bttv *btv, struct tvnorm *tvn, + int odd, int width, int height) +{ + u16 vscale, hscale; + u32 xsf, sr; + u16 hdelay; + u8 crop, vtc; + int inter = (height>tvn->sheight/2) ? 0 : 1; + int off = odd ? 0x80 : 0x00; + + xsf = (width*tvn->scaledtwidth)/tvn->swidth; + hscale = ((tvn->totalwidth*4096UL)/xsf-4096); + hdelay = tvn->hdelayx1; + hdelay = (hdelay*width)/tvn->swidth; + hdelay &= 0x3fe; + sr=((tvn->sheight>>inter)*512)/height-512; + vscale=(0x10000UL-sr)&0x1fff; + crop=((width>>8)&0x03)|((hdelay>>6)&0x0c)| + ((tvn->sheight>>4)&0x30)|((tvn->vdelay>>2)&0xc0); + vscale |= inter ? (BT848_VSCALE_INT<<8) : 0; + +#if 0 + /* Some people say interpolation looks bad ... */ + vtc = (width < 193) ? 2 : ((width < 385) ? 1 : 0); + if (width < 767) + btor(BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); + else + btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); +#else + vtc = 0; + btand(~BT848_VSCALE_COMB, BT848_E_VSCALE_HI+off); +#endif + + btwrite(vtc, BT848_E_VTC+off); + btwrite(hscale>>8, BT848_E_HSCALE_HI+off); + btwrite(hscale&0xff, BT848_E_HSCALE_LO+off); + btaor((vscale>>8), 0xe0, BT848_E_VSCALE_HI+off); + btwrite(vscale&0xff, BT848_E_VSCALE_LO+off); + btwrite(width&0xff, BT848_E_HACTIVE_LO+off); + btwrite(hdelay&0xff, BT848_E_HDELAY_LO+off); + btwrite(tvn->sheight&0xff, BT848_E_VACTIVE_LO+off); + btwrite(tvn->vdelay&0xff, BT848_E_VDELAY_LO+off); + btwrite(crop, BT848_E_CROP+off); +} + + +static void bt848_set_geo(struct bttv *btv, + int no_irq_context) +{ + u16 ewidth, eheight, owidth, oheight; + u16 format, bswap; + struct tvnorm *tvn; + + tvn=&tvnorms[btv->win.norm]; + + btwrite(tvn->adelay, BT848_ADELAY); + btwrite(tvn->bdelay, BT848_BDELAY); + btaor(tvn->iform,~(BT848_IFORM_NORM|BT848_IFORM_XTBOTH), BT848_IFORM); + btwrite(tvn->vbipack, BT848_VBI_PACK_SIZE); + btwrite(1, BT848_VBI_PACK_DEL); + + btv->pll.pll_ofreq = tvn->Fsc; + if (no_irq_context) + set_pll(btv); + + btv->win.interlace = (btv->win.height>tvn->sheight/2) ? 1 : 0; + + if (0 == btv->risc_cap_odd && + 0 == btv->risc_cap_even) { + /* overlay only */ + owidth = btv->win.width; + oheight = btv->win.height; + ewidth = btv->win.width; + eheight = btv->win.height; + format = btv->win.color_fmt; + bswap = btv->fb_color_ctl; + } else if (-1 != btv->gq_grab && + 0 == btv->risc_cap_odd && + !btv->win.interlace && + btv->scr_on) { + /* odd field -> overlay, even field -> capture */ + owidth = btv->win.width; + oheight = btv->win.height; + ewidth = btv->gbuf[btv->gq_grab].width; + eheight = btv->gbuf[btv->gq_grab].height; + format = (btv->win.color_fmt & 0xf0) | + (btv->gbuf[btv->gq_grab].fmt & 0x0f); + bswap = btv->fb_color_ctl & 0x0a; + } else { + /* capture only */ + owidth = btv->gbuf[btv->gq_grab].width; + oheight = btv->gbuf[btv->gq_grab].height; + ewidth = btv->gbuf[btv->gq_grab].width; + eheight = btv->gbuf[btv->gq_grab].height; + format = btv->gbuf[btv->gq_grab].fmt; + bswap = 0; + } + + /* program odd + even fields */ + bt848_set_eogeo(btv, tvn, 1, owidth, oheight); + bt848_set_eogeo(btv, tvn, 0, ewidth, eheight); + + btwrite(format, BT848_COLOR_FMT); + btwrite(bswap | BT848_COLOR_CTL_GAMMA, BT848_COLOR_CTL); +} + + +static int bpp2fmt[4] = { + BT848_COLOR_FMT_RGB8, BT848_COLOR_FMT_RGB16, + BT848_COLOR_FMT_RGB24, BT848_COLOR_FMT_RGB32 +}; + +static void bt848_set_winsize(struct bttv *btv) +{ + unsigned short format; + + if (btv->picture.palette > 0 && btv->picture.palette <= VIDEO_PALETTE_YUV422) { + /* format set by VIDIOCSPICT */ + format = palette2fmt[btv->picture.palette]; + } else { + /* use default for the given color depth */ + format = (btv->win.depth==15) ? BT848_COLOR_FMT_RGB15 : + bpp2fmt[(btv->win.bpp-1)&3]; + } + btv->win.color_fmt = format; + if (bigendian && + format == BT848_COLOR_FMT_RGB32) { + btv->fb_color_ctl = + BT848_COLOR_CTL_WSWAP_ODD | + BT848_COLOR_CTL_WSWAP_EVEN | + BT848_COLOR_CTL_BSWAP_ODD | + BT848_COLOR_CTL_BSWAP_EVEN; + } else if (bigendian && + (format == BT848_COLOR_FMT_RGB16 || + format == BT848_COLOR_FMT_RGB15)) { + btv->fb_color_ctl = + BT848_COLOR_CTL_BSWAP_ODD | + BT848_COLOR_CTL_BSWAP_EVEN; + } else { + btv->fb_color_ctl = 0; + } + + /* RGB8 seems to be a 9x5x5 GRB color cube starting at + * color 16. Why the h... can't they even mention this in the + * data sheet? [AC - because it's a standard format so I guess + * it never occurred to them] + * Enable dithering in this mode. + */ + + if (format==BT848_COLOR_FMT_RGB8) + btand(~BT848_CAP_CTL_DITH_FRAME, BT848_CAP_CTL); + else + btor(BT848_CAP_CTL_DITH_FRAME, BT848_CAP_CTL); + + bt848_set_geo(btv,1); +} + +#ifdef HACKING +/* playing with kiobufs and dma-to-userspace. 2.4.x only + Yes, I know: cut+paste programming is ugly. + will fix later, this is proof-of-concept right now. */ +static int make_vrisctab_kiobuf(struct bttv *btv, unsigned int *ro, + unsigned int *re, struct kiobuf *iobuf, + unsigned short width, unsigned short height, + unsigned short palette) +{ + unsigned long bpl; /* bytes per line */ + unsigned long bl; + unsigned long todo; + unsigned long pageaddr; + unsigned int **rp; + unsigned long line,inter,offset,page; + + inter = (height>tvnorms[btv->win.norm].sheight/2) ? 1 : 0; + bpl=width*fmtbppx2[palette2fmt[palette]&0xf]/2; + + *(ro++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(ro++)=cpu_to_le32(0); + *(re++)=cpu_to_le32(BT848_RISC_SYNC|BT848_FIFO_STATUS_FM1); + *(re++)=cpu_to_le32(0); + + offset = iobuf->offset; + page = 0; + pageaddr = virt_to_bus(page_address(iobuf->maplist[page])); + for (line=0; line < (height<<(1^inter)); line++) + { + if (inter) + rp= (line&1) ? &re : &ro; + else + rp= (line>=height) ? &ro : &re; + + bl = PAGE_SIZE - offset; + if (bpl <= bl) { + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL| + BT848_RISC_EOL|bpl); + *((*rp)++)=cpu_to_le32(pageaddr+offset); + offset+=bpl; + } else { + todo = bpl; + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_SOL|bl); + *((*rp)++)=cpu_to_le32(pageaddr+offset); + todo -= bl; + offset = 0; + page++; + pageaddr = virt_to_bus(page_address(iobuf->maplist[page])); + while (todo>PAGE_SIZE) + { + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|PAGE_SIZE); + *((*rp)++)=cpu_to_le32(pageaddr); + page++; + pageaddr = virt_to_bus(page_address(iobuf->maplist[page])); + } + *((*rp)++)=cpu_to_le32(BT848_RISC_WRITE|BT848_RISC_EOL|todo); + *((*rp)++)=cpu_to_le32(pageaddr); + offset += todo; + } + } + + *(ro++)=cpu_to_le32(BT848_RISC_JUMP); + *(ro++)=cpu_to_le32(btv->bus_vbi_even); + *(re++)=cpu_to_le32(BT848_RISC_JUMP|BT848_RISC_IRQ|(2<<16)); + *(re++)=cpu_to_le32(btv->bus_vbi_odd); + + return 0; +} + +static int vgrab_kiobuf(struct bttv *btv, struct bttv_just_hacking *mp, + struct kiobuf *iobuf) +{ + unsigned int *ro, *re; + unsigned long flags; + + if(btv->gbuf[0].stat != GBUFFER_UNUSED) + return -EBUSY; + + if(mp->height < 32 || mp->width < 32) + return -EINVAL; + if (mp->format >= 12 /* more is'nt yet ... PALETTEFMT_MAX */) + return -EINVAL; + + if(-1 == palette2fmt[mp->format]) + return -EINVAL; + + /* + * Ok load up the BT848 + */ + + ro=btv->gbuf[0].risc; + re=ro+2048; + make_vrisctab_kiobuf(btv, ro, re, iobuf, mp->width, mp->height, mp->format); + + if (bttv_debug) + printk("bttv%d: cap vgrab_kiobuf: queue %d (%d:%dx%d)\n", + btv->nr,0,mp->format,mp->width,mp->height); + spin_lock_irqsave(&btv->s_lock, flags); + btv->gbuf[0].stat = GBUFFER_GRABBING; + btv->gbuf[0].fmt = palette2fmt[mp->format]; + btv->gbuf[0].width = mp->width; + btv->gbuf[0].height = mp->height; + btv->gbuf[0].ro = virt_to_bus(ro); + btv->gbuf[0].re = virt_to_bus(re); + +#if 1 + if (mp->height <= tvnorms[btv->win.norm].sheight/2 && + mp->format != VIDEO_PALETTE_RAW) + btv->gbuf[0].ro = 0; +#endif + + if (-1 == btv->gq_grab && btv->gq_in == btv->gq_out) { + btv->gq_start = 1; + btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ); + } + btv->gqueue[btv->gq_in++] = 0; + btv->gq_in = btv->gq_in % MAX_GBUFFERS; + + btor(3, BT848_CAP_CTL); + btor(3, BT848_GPIO_DMA_CTL); + spin_unlock_irqrestore(&btv->s_lock, flags); + return 0; +} +#endif + +/* + * Grab into virtual memory. + */ + +static int vgrab(struct bttv *btv, struct video_mmap *mp) +{ + unsigned int *ro, *re; + unsigned int *vbuf; + unsigned long flags; + + if(btv->fbuffer==NULL) + { + if(fbuffer_alloc(btv)) + return -ENOBUFS; + } + + if(mp->frame >= gbuffers || mp->frame < 0) + return -EINVAL; + if(btv->gbuf[mp->frame].stat != GBUFFER_UNUSED) + return -EBUSY; + + if(mp->height < 32 || mp->width < 32) + return -EINVAL; + if (mp->format >= PALETTEFMT_MAX) + return -EINVAL; + + if (mp->height*mp->width*fmtbppx2[palette2fmt[mp->format]&0x0f]/2 + > gbufsize) + return -EINVAL; + if(-1 == palette2fmt[mp->format]) + return -EINVAL; + + /* + * Ok load up the BT848 + */ + + vbuf=(unsigned int *)(btv->fbuffer+gbufsize*mp->frame); + ro=btv->gbuf[mp->frame].risc; + re=ro+2048; + make_vrisctab(btv, ro, re, vbuf, mp->width, mp->height, mp->format); + + if (bttv_debug) + printk("bttv%d: cap vgrab: queue %d (%d:%dx%d)\n", + btv->nr,mp->frame,mp->format,mp->width,mp->height); + spin_lock_irqsave(&btv->s_lock, flags); + btv->gbuf[mp->frame].stat = GBUFFER_GRABBING; + btv->gbuf[mp->frame].fmt = palette2fmt[mp->format]; + btv->gbuf[mp->frame].width = mp->width; + btv->gbuf[mp->frame].height = mp->height; + btv->gbuf[mp->frame].ro = virt_to_bus(ro); + btv->gbuf[mp->frame].re = virt_to_bus(re); + +#if 1 + if (mp->height <= tvnorms[btv->win.norm].sheight/2 && + mp->format != VIDEO_PALETTE_RAW) + btv->gbuf[mp->frame].ro = 0; +#endif + + if (-1 == btv->gq_grab && btv->gq_in == btv->gq_out) { + btv->gq_start = 1; + btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ); + } + btv->gqueue[btv->gq_in++] = mp->frame; + btv->gq_in = btv->gq_in % MAX_GBUFFERS; + + btor(3, BT848_CAP_CTL); + btor(3, BT848_GPIO_DMA_CTL); + spin_unlock_irqrestore(&btv->s_lock, flags); + return 0; +} + +static long bttv_write(struct video_device *v, const char *buf, unsigned long count, int nonblock) +{ + return -EINVAL; +} + +static long bttv_read(struct video_device *v, char *buf, unsigned long count, int nonblock) +{ + struct bttv *btv= (struct bttv *)v; + int q,todo; + DECLARE_WAITQUEUE(wait, current); + + /* BROKEN: RETURNS VBI WHEN IT SHOULD RETURN GRABBED VIDEO FRAME */ + todo=count; + while (todo && todo>(q=VBIBUF_SIZE-btv->vbip)) + { + if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, q)) + return -EFAULT; + todo-=q; + buf+=q; + + add_wait_queue(&btv->vbiq, &wait); + current->state = TASK_INTERRUPTIBLE; + if (todo && q==VBIBUF_SIZE-btv->vbip) + { + if(nonblock) + { + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + if(count==todo) + return -EWOULDBLOCK; + return count-todo; + } + schedule(); + if(signal_pending(current)) + { + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + + if(todo==count) + return -EINTR; + else + return count-todo; + } + } + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + } + if (todo) + { + if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, todo)) + return -EFAULT; + btv->vbip+=todo; + } + return count; +} + +static inline void burst(int on) +{ + tvnorms[0].scaledtwidth = 1135 - (on?BURSTOFFSET-2:0); + tvnorms[0].hdelayx1 = 186 - (on?BURSTOFFSET :0); + tvnorms[2].scaledtwidth = 1135 - (on?BURSTOFFSET-2:0); + tvnorms[2].hdelayx1 = 186 - (on?BURSTOFFSET :0); +} + +static void bt848_restart(struct bttv *btv) +{ + unsigned long irq_flags; + + if (bttv_verbose) + printk("bttv%d: resetting chip\n",btv->nr); + btwrite(0xfffffUL, BT848_INT_STAT); + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(0, BT848_SRESET); + btwrite(virt_to_bus(btv->risc_jmp+2), + BT848_RISC_STRT_ADD); + + /* enforce pll reprogramming */ + btv->pll.pll_current = 0; + set_pll(btv); + + spin_lock_irqsave(&btv->s_lock, irq_flags); + btv->errors = 0; + btv->needs_restart = 0; + bt848_set_geo(btv,0); + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); +} + +/* + * Open a bttv card. Right now the flags stuff is just playing + */ + +static int bttv_open(struct video_device *dev, int flags) +{ + struct bttv *btv = (struct bttv *)dev; + int i,ret; + + ret = -EBUSY; + + MOD_INC_USE_COUNT; + down(&btv->lock); + if (btv->user) + goto out_unlock; + + btv->fbuffer=(unsigned char *) rvmalloc(gbuffers*gbufsize); + ret = -ENOMEM; + if (!btv->fbuffer) + goto out_unlock; + + btv->gq_in = 0; + btv->gq_out = 0; + btv->gq_grab = -1; + for (i = 0; i < gbuffers; i++) + btv->gbuf[i].stat = GBUFFER_UNUSED; + + if (btv->needs_restart) + bt848_restart(btv); + burst(0); + set_pll(btv); + btv->user++; + up(&btv->lock); + return 0; + + out_unlock: + up(&btv->lock); + MOD_DEC_USE_COUNT; + return ret; +} + +static void bttv_close(struct video_device *dev) +{ + struct bttv *btv=(struct bttv *)dev; + unsigned long irq_flags; + + down(&btv->lock); + btv->user--; + spin_lock_irqsave(&btv->s_lock, irq_flags); + btv->scr_on = 0; + btv->risc_cap_odd = 0; + btv->risc_cap_even = 0; + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + + /* + * A word of warning. At this point the chip + * is still capturing because its FIFO hasn't emptied + * and the DMA control operations are posted PCI + * operations. + */ + + btread(BT848_I2C); /* This fixes the PCI posting delay */ + + if (-1 != btv->gq_grab) { + /* + * This is sucky but right now I can't find a good way to + * be sure its safe to free the buffer. We wait 5-6 fields + * which is more than sufficient to be sure. + */ + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ/10); /* Wait 1/10th of a second */ + } + + /* + * We have allowed it to drain. + */ + + if(btv->fbuffer) + rvfree((void *) btv->fbuffer, gbuffers*gbufsize); + btv->fbuffer=0; + up(&btv->lock); + MOD_DEC_USE_COUNT; +} + + +/***********************************/ +/* ioctls and supporting functions */ +/***********************************/ + +extern inline void bt848_bright(struct bttv *btv, uint bright) +{ + btwrite(bright&0xff, BT848_BRIGHT); +} + +extern inline void bt848_hue(struct bttv *btv, uint hue) +{ + btwrite(hue&0xff, BT848_HUE); +} + +extern inline void bt848_contrast(struct bttv *btv, uint cont) +{ + unsigned int conthi; + + conthi=(cont>>6)&4; + btwrite(cont&0xff, BT848_CONTRAST_LO); + btaor(conthi, ~4, BT848_E_CONTROL); + btaor(conthi, ~4, BT848_O_CONTROL); +} + +extern inline void bt848_sat_u(struct bttv *btv, unsigned long data) +{ + u32 datahi; + + datahi=(data>>7)&2; + btwrite(data&0xff, BT848_SAT_U_LO); + btaor(datahi, ~2, BT848_E_CONTROL); + btaor(datahi, ~2, BT848_O_CONTROL); +} + +static inline void bt848_sat_v(struct bttv *btv, unsigned long data) +{ + u32 datahi; + + datahi=(data>>8)&1; + btwrite(data&0xff, BT848_SAT_V_LO); + btaor(datahi, ~1, BT848_E_CONTROL); + btaor(datahi, ~1, BT848_O_CONTROL); +} + +/* + * ioctl routine + */ + + +static int bttv_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct bttv *btv=(struct bttv *)dev; + unsigned long irq_flags; + int i,ret = 0; + + if (bttv_debug > 1) + printk("bttv%d: ioctl 0x%x\n",btv->nr,cmd); + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name,btv->video_dev.name); + b.type = VID_TYPE_CAPTURE| + ((bttv_tvcards[btv->type].tuner != -1) ? VID_TYPE_TUNER : 0) | + VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING| + VID_TYPE_FRAMERAM| + VID_TYPE_SCALES; + b.channels = bttv_tvcards[btv->type].video_inputs; + b.audios = bttv_tvcards[btv->type].audio_inputs; + b.maxwidth = tvnorms[btv->win.norm].swidth; + b.maxheight = tvnorms[btv->win.norm].sheight; + b.minwidth = 32; + b.minheight = 32; + if(copy_to_user(arg,&b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + v.flags=VIDEO_VC_AUDIO; + v.tuners=0; + v.type=VIDEO_TYPE_CAMERA; + v.norm = btv->win.norm; + if (v.channel>=bttv_tvcards[btv->type].video_inputs) + return -EINVAL; + if(v.channel==bttv_tvcards[btv->type].tuner) + { + strcpy(v.name,"Television"); + v.flags|=VIDEO_VC_TUNER; + v.type=VIDEO_TYPE_TV; + v.tuners=1; + } + else if(v.channel==bttv_tvcards[btv->type].svhs) + strcpy(v.name,"S-Video"); + else + sprintf(v.name,"Composite%d",v.channel); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + /* + * Each channel has 1 tuner + */ + case VIDIOCSCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + + if (v.channel>bttv_tvcards[btv->type].video_inputs) + return -EINVAL; + if (v.norm > (sizeof(tvnorms)/sizeof(*tvnorms))) + return -EOPNOTSUPP; + + bttv_call_i2c_clients(btv,cmd,&v); + down(&btv->lock); + bt848_muxsel(btv, v.channel); + btv->channel=v.channel; + if (btv->win.norm != v.norm) { + btv->win.norm = v.norm; + make_vbitab(btv); + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_winsize(btv); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + } + up(&btv->lock); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v,arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner||btv->channel) /* Only tuner 0 */ + return -EINVAL; + strcpy(v.name, "Television"); + v.rangelow=0; + v.rangehigh=0xFFFFFFFF; + v.flags=VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + v.mode = btv->win.norm; + v.signal = (btread(BT848_DSTATUS)&BT848_DSTATUS_HLOC) ? 0xFFFF : 0; + bttv_call_i2c_clients(btv,cmd,&v); + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + /* We have but one tuner */ + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + /* Only one channel has a tuner */ + if(v.tuner!=bttv_tvcards[btv->type].tuner) + return -EINVAL; + + if(v.mode!=VIDEO_MODE_PAL&&v.mode!=VIDEO_MODE_NTSC + &&v.mode!=VIDEO_MODE_SECAM) + return -EOPNOTSUPP; + bttv_call_i2c_clients(btv,cmd,&v); + if (btv->win.norm != v.mode) { + btv->win.norm = v.mode; + down(&btv->lock); + set_pll(btv); + make_vbitab(btv); + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_winsize(btv); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + up(&btv->lock); + } + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p=btv->picture; + if(copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + if(copy_from_user(&p, arg,sizeof(p))) + return -EFAULT; + if (p.palette > PALETTEFMT_MAX) + return -EINVAL; + down(&btv->lock); + /* We want -128 to 127 we get 0-65535 */ + bt848_bright(btv, (p.brightness>>8)-128); + /* 0-511 for the colour */ + bt848_sat_u(btv, p.colour>>7); + bt848_sat_v(btv, ((p.colour>>7)*201L)/237); + /* -128 to 127 */ + bt848_hue(btv, (p.hue>>8)-128); + /* 0-511 */ + bt848_contrast(btv, p.contrast>>7); + btv->picture = p; + up(&btv->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip *vcp = NULL; + + if(copy_from_user(&vw,arg,sizeof(vw))) + return -EFAULT; + + down(&btv->lock); + if(vw.flags || vw.width < 16 || vw.height < 16) + { + spin_lock_irqsave(&btv->s_lock, irq_flags); + btv->scr_on = 0; + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + return -EINVAL; + } + if (btv->win.bpp < 4) + { /* adjust and align writes */ + vw.x = (vw.x + 3) & ~3; + vw.width &= ~3; + } + if (btv->needs_restart) + bt848_restart(btv); + btv->win.x=vw.x; + btv->win.y=vw.y; + btv->win.width=vw.width; + btv->win.height=vw.height; + + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_risc_jmps(btv,0); + bt848_set_winsize(btv); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + + /* + * Do any clips. + */ + if(vw.clipcount<0) { + if((vcp=vmalloc(VIDEO_CLIPMAP_SIZE))==NULL) + return -ENOMEM; + if(copy_from_user(vcp, vw.clips, + VIDEO_CLIPMAP_SIZE)) { + vfree(vcp); + return -EFAULT; + } + } else if (vw.clipcount) { + if((vcp=vmalloc(sizeof(struct video_clip)* + (vw.clipcount))) == NULL) + return -ENOMEM; + if(copy_from_user(vcp,vw.clips, + sizeof(struct video_clip)* + vw.clipcount)) { + vfree(vcp); + return -EFAULT; + } + } + make_clip_tab(btv, vcp, vw.clipcount); + if (vw.clipcount != 0) + vfree(vcp); + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + up(&btv->lock); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + /* Oh for a COBOL move corresponding .. */ + vw.x=btv->win.x; + vw.y=btv->win.y; + vw.width=btv->win.width; + vw.height=btv->win.height; + vw.chromakey=0; + vw.flags=0; + if(btv->win.interlace) + vw.flags|=VIDEO_WINDOW_INTERLACE; + if(copy_to_user(arg,&vw,sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + { + int v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + if(btv->win.vidadr == 0) + return -EINVAL; + if (btv->win.width==0 || btv->win.height==0) + return -EINVAL; + spin_lock_irqsave(&btv->s_lock, irq_flags); + if (v == 1 && btv->win.vidadr != 0) + btv->scr_on = 1; + if (v == 0) + btv->scr_on = 0; + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer v; + v.base=(void *)btv->win.vidadr; + v.height=btv->win.sheight; + v.width=btv->win.swidth; + v.depth=btv->win.depth; + v.bytesperline=btv->win.bpl; + if(copy_to_user(arg, &v,sizeof(v))) + return -EFAULT; + return 0; + + } + case VIDIOCSFBUF: + { + struct video_buffer v; + if(!capable(CAP_SYS_ADMIN) && + !capable(CAP_SYS_RAWIO)) + return -EPERM; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + if(v.depth!=8 && v.depth!=15 && v.depth!=16 && + v.depth!=24 && v.depth!=32 && v.width > 16 && + v.height > 16 && v.bytesperline > 16) + return -EINVAL; + down(&btv->lock); + if (v.base) + btv->win.vidadr=(unsigned long)v.base; + btv->win.sheight=v.height; + btv->win.swidth=v.width; + btv->win.bpp=((v.depth+7)&0x38)/8; + btv->win.depth=v.depth; + btv->win.bpl=v.bytesperline; + + /* set sefault color format */ + switch (btv->win.bpp) { + case 8: btv->picture.palette = VIDEO_PALETTE_HI240; break; + case 15: btv->picture.palette = VIDEO_PALETTE_RGB555; break; + case 16: btv->picture.palette = VIDEO_PALETTE_RGB565; break; + case 24: btv->picture.palette = VIDEO_PALETTE_RGB24; break; + case 32: btv->picture.palette = VIDEO_PALETTE_RGB32; break; + } + + if (bttv_debug) + printk("Display at %p is %d by %d, bytedepth %d, bpl %d\n", + v.base, v.width,v.height, btv->win.bpp, btv->win.bpl); + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_winsize(btv); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + up(&btv->lock); + return 0; + } + case VIDIOCKEY: + { + /* Will be handled higher up .. */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long v=btv->win.freq; + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + btv->win.freq=v; + bttv_call_i2c_clients(btv,cmd,&v); +#if 0 + if (btv->type == BTTV_MIROPRO && btv->radio) + tea5757_set_freq(btv,v); +#endif + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio v; + + v=btv->audio_dev; + v.flags&=~(VIDEO_AUDIO_MUTE|VIDEO_AUDIO_MUTABLE); + v.flags|=VIDEO_AUDIO_MUTABLE; + strcpy(v.name,"TV"); + + v.mode = VIDEO_SOUND_MONO; + bttv_call_i2c_clients(btv,cmd,&v); + + if (btv->type == BTTV_TERRATV) { + v.mode = VIDEO_SOUND_MONO | VIDEO_SOUND_STEREO | + VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } +#ifndef HAVE_TVAUDIO + else if (btv->audio_chip == TDA9840) { + /* begin of Horrible Hack <grin@tolna.net> */ + v.flags|=VIDEO_AUDIO_VOLUME; + v.mode = VIDEO_SOUND_MONO; + v.mode |= VIDEO_SOUND_STEREO; + v.mode |= VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2; + v.volume = 32768; /* fixme */ + v.step = 4096; + } +#endif + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + + if(copy_from_user(&v,arg, sizeof(v))) + return -EFAULT; + down(&btv->lock); + if(v.flags&VIDEO_AUDIO_MUTE) + audio(btv, AUDIO_MUTE, 1); + /* One audio source per tuner -- huh? <GA> */ + if(v.audio<0 || v.audio >= bttv_tvcards[btv->type].audio_inputs) { + up(&btv->lock); + return -EINVAL; + } + /* bt848_muxsel(btv,v.audio); */ + if(!(v.flags&VIDEO_AUDIO_MUTE)) + audio(btv, AUDIO_UNMUTE, 1); + + bttv_call_i2c_clients(btv,cmd,&v); + + if (btv->type == BTTV_TERRATV) { + unsigned int con = 0; + btor(0x180000, BT848_GPIO_OUT_EN); + if (v.mode & VIDEO_SOUND_LANG2) + con = 0x080000; + if (v.mode & VIDEO_SOUND_STEREO) + con = 0x180000; + btaor(con, ~0x180000, BT848_GPIO_DATA); + + } else if (btv->type == BTTV_WINVIEW_601) + winview_setvol(btv,&v); + + btv->audio_dev=v; + up(&btv->lock); + return 0; + } + + case VIDIOCSYNC: + { + DECLARE_WAITQUEUE(wait, current); + + if(copy_from_user((void *)&i,arg,sizeof(int))) + return -EFAULT; + if (i < 0 || i >= gbuffers) + return -EINVAL; + switch (btv->gbuf[i].stat) { + case GBUFFER_UNUSED: + ret = -EINVAL; + break; + case GBUFFER_GRABBING: + add_wait_queue(&btv->capq, &wait); + current->state = TASK_INTERRUPTIBLE; + while(btv->gbuf[i].stat==GBUFFER_GRABBING) { + if (bttv_debug) + printk("bttv%d: cap sync: sleep on %d\n",btv->nr,i); + schedule(); + if(signal_pending(current)) { + remove_wait_queue(&btv->capq, &wait); + current->state = TASK_RUNNING; + return -EINTR; + } + } + remove_wait_queue(&btv->capq, &wait); + current->state = TASK_RUNNING; + /* fall throuth */ + case GBUFFER_DONE: + case GBUFFER_ERROR: + ret = (btv->gbuf[i].stat == GBUFFER_ERROR) ? -EIO : 0; + if (bttv_debug) + printk("bttv%d: cap sync: buffer %d, retval %d\n",btv->nr,i,ret); + btv->gbuf[i].stat = GBUFFER_UNUSED; + } + if (btv->needs_restart) { + down(&btv->lock); + bt848_restart(btv); + up(&btv->lock); + } + return ret; + } + + case BTTV_FIELDNR: + if(copy_to_user((void *) arg, (void *) &btv->last_field, + sizeof(btv->last_field))) + return -EFAULT; + break; + + case BTTV_PLLSET: { + struct bttv_pll_info p; + if(!capable(CAP_SYS_ADMIN)) + return -EPERM; + if(copy_from_user(&p , (void *) arg, sizeof(btv->pll))) + return -EFAULT; + down(&btv->lock); + btv->pll.pll_ifreq = p.pll_ifreq; + btv->pll.pll_ofreq = p.pll_ofreq; + btv->pll.pll_crystal = p.pll_crystal; + up(&btv->lock); + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + int ret; + if(copy_from_user((void *) &vm, (void *) arg, sizeof(vm))) + return -EFAULT; + down(&btv->lock); + ret = vgrab(btv, &vm); + up(&btv->lock); + return ret; + } + + case VIDIOCGMBUF: + { + struct video_mbuf vm; + memset(&vm, 0 , sizeof(vm)); + vm.size=gbufsize*gbuffers; + vm.frames=gbuffers; + for (i = 0; i < gbuffers; i++) + vm.offsets[i]=i*gbufsize; + if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + return -EFAULT; + return 0; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + vu.video=btv->video_dev.minor; + vu.vbi=btv->vbi_dev.minor; + if(btv->radio_dev.minor!=-1) + vu.radio=btv->radio_dev.minor; + else + vu.radio=VIDEO_NO_UNIT; + vu.audio=VIDEO_NO_UNIT; + vu.teletext=VIDEO_NO_UNIT; + if(copy_to_user((void *)arg, (void *)&vu, sizeof(vu))) + return -EFAULT; + return 0; + } + + case BTTV_BURST_ON: + { + burst(1); + return 0; + } + + case BTTV_BURST_OFF: + { + burst(0); + return 0; + } + + case BTTV_VERSION: + { + return BTTV_VERSION_CODE; + } + + case BTTV_PICNR: + { + /* return picture;*/ + return 0; + } + +#ifdef HACKING + /* playing with kiobufs and dma-to-userspace */ + case BTTV_JUST_HACKING: + { + DECLARE_WAITQUEUE(wait, current); + struct bttv_just_hacking hack; + struct kiobuf *iobuf; + int err; + + if(copy_from_user((void *) &hack, (void *) arg, sizeof(hack))) + return -EFAULT; + printk("bttv%d: hack: userland args: %dx%d, fmt=%d, buf=%lx, len=%d\n", + btv->nr,hack.width,hack.height,hack.format, + hack.buf,hack.len); + + /* pin down */ + err = alloc_kiovec(1,&iobuf); + if (err) + goto hack_oops; + err = map_user_kiobuf(READ, iobuf, hack.buf, hack.len); + if (err) + goto hack_oops; + err = lock_kiovec(1,&iobuf,1); + if (err) + goto hack_oops; + + /* have a look */ + printk("bttv%d: hack: kiobuf: nr_pages=%d, offset=%d, length=%d, locked=%d\n", + btv->nr,iobuf->nr_pages,iobuf->offset,iobuf->length, + iobuf->locked); + printk("bttv%d: hack: pages (bus addr):",btv->nr); + for (i = 0; i < iobuf->nr_pages; i++) { + printk(" %lx", virt_to_bus(page_address(iobuf->maplist[i]))); + } + printk("\n"); + + /* start capture */ + err = -EINVAL; + if (hack.height * hack.width * 2 * /* fixme: *2 */ + fmtbppx2[palette2fmt[hack.format]&0x0f]/2 > hack.len) + goto hack_oops; + err = vgrab_kiobuf(btv,&hack,iobuf); + if (err) + goto hack_oops; + printk("bttv%d: hack: capture started\n",btv->nr); + + /* wait */ + add_wait_queue(&btv->capq, &wait); + current->state = TASK_INTERRUPTIBLE; + while(btv->gbuf[0].stat==GBUFFER_GRABBING) { + if (bttv_debug) + printk("bttv%d: hack: cap sync: sleep on %d\n",btv->nr,0); + schedule(); +#if 0 + if(signal_pending(current)) { + remove_wait_queue(&btv->capq, &wait); + current->state = TASK_RUNNING; + return -EINTR; + } +#endif + } + remove_wait_queue(&btv->capq, &wait); + current->state = TASK_RUNNING; + printk("bttv%d: hack: capture done\n",btv->nr); + + /* release */ + err = 0; + hack_oops: + free_kiovec(1,&iobuf); + return 0; + } +#endif + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int bttv_init_done(struct video_device *dev) +{ + return 0; +} + +/* + * This maps the vmalloced and reserved fbuffer to user space. + * + * FIXME: + * - PAGE_READONLY should suffice!? + * - remap_page_range is kind of inefficient for page by page remapping. + * But e.g. pte_alloc() does not work in modules ... :-( + */ + +static int do_bttv_mmap(struct bttv *btv, const char *adr, unsigned long size) +{ + unsigned long start=(unsigned long) adr; + unsigned long page,pos; + + if (size>gbuffers*gbufsize) + return -EINVAL; + if (!btv->fbuffer) { + if(fbuffer_alloc(btv)) + return -EINVAL; + } + pos=(unsigned long) btv->fbuffer; + while (size > 0) { + page = kvirt_to_pa(pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start+=PAGE_SIZE; + pos+=PAGE_SIZE; + size-=PAGE_SIZE; + } + return 0; +} + +static int bttv_mmap(struct video_device *dev, const char *adr, unsigned long size) +{ + struct bttv *btv=(struct bttv *)dev; + int r; + + down(&btv->lock); + r=do_bttv_mmap(btv, adr, size); + up(&btv->lock); + return r; +} + + +static struct video_device bttv_template= +{ + "UNSET", + VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY|VID_TYPE_TELETEXT, + VID_HARDWARE_BT848, + bttv_open, + bttv_close, + bttv_read, + bttv_write, + NULL, + bttv_ioctl, + bttv_mmap, + bttv_init_done, + NULL, + 0, + -1 +}; + + +static long vbi_read(struct video_device *v, char *buf, unsigned long count, + int nonblock) +{ + struct bttv *btv=(struct bttv *)(v-2); + int q,todo; + DECLARE_WAITQUEUE(wait, current); + + todo=count; + while (todo && todo>(q=VBIBUF_SIZE-btv->vbip)) + { + if (btv->needs_restart) { + down(&btv->lock); + bt848_restart(btv); + up(&btv->lock); + } + if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, q)) + return -EFAULT; + todo-=q; + buf+=q; + + add_wait_queue(&btv->vbiq, &wait); + current->state = TASK_INTERRUPTIBLE; + if (todo && q==VBIBUF_SIZE-btv->vbip) + { + if(nonblock) + { + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + if(count==todo) + return -EWOULDBLOCK; + return count-todo; + } + schedule(); + if(signal_pending(current)) + { + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + if(todo==count) + return -EINTR; + else + return count-todo; + } + } + remove_wait_queue(&btv->vbiq, &wait); + current->state = TASK_RUNNING; + } + if (todo) + { + if(copy_to_user((void *) buf, (void *) btv->vbibuf+btv->vbip, todo)) + return -EFAULT; + btv->vbip+=todo; + } + return count; +} + +static unsigned int vbi_poll(struct video_device *dev, struct file *file, + poll_table *wait) +{ + struct bttv *btv=(struct bttv *)(dev-2); + unsigned int mask = 0; + + poll_wait(file, &btv->vbiq, wait); + + if (btv->vbip < VBIBUF_SIZE) + mask |= (POLLIN | POLLRDNORM); + + return mask; +} + +static int vbi_open(struct video_device *dev, int flags) +{ + struct bttv *btv=(struct bttv *)(dev-2); + unsigned long irq_flags; + + MOD_INC_USE_COUNT; + down(&btv->lock); + if (btv->needs_restart) + bt848_restart(btv); + set_pll(btv); + btv->vbip=VBIBUF_SIZE; + spin_lock_irqsave(&btv->s_lock, irq_flags); + btv->vbi_on = 1; + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + up(&btv->lock); + + return 0; +} + +static void vbi_close(struct video_device *dev) +{ + struct bttv *btv=(struct bttv *)(dev-2); + unsigned long irq_flags; + + spin_lock_irqsave(&btv->s_lock, irq_flags); + btv->vbi_on = 0; + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + MOD_DEC_USE_COUNT; +} + +static int vbi_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct bttv *btv=(struct bttv *)dev; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name,btv->vbi_dev.name); + b.type = ((bttv_tvcards[btv->type].tuner != -1) ? VID_TYPE_TUNER : 0) | + VID_TYPE_TELETEXT; + b.channels = 0; + b.audios = 0; + b.maxwidth = 0; + b.maxheight = 0; + b.minwidth = 0; + b.minheight = 0; + if(copy_to_user(arg,&b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGFREQ: + case VIDIOCSFREQ: + return bttv_ioctl((struct video_device *)btv,cmd,arg); + case BTTV_VBISIZE: + /* make alevt happy :-) */ + return VBIBUF_SIZE; + default: + return -EINVAL; + } +} + +static struct video_device vbi_template= +{ + "bttv vbi", + VID_TYPE_CAPTURE|VID_TYPE_TELETEXT, + VID_HARDWARE_BT848, + vbi_open, + vbi_close, + vbi_read, + bttv_write, + vbi_poll, + vbi_ioctl, + NULL, /* no mmap yet */ + bttv_init_done, + NULL, + 0, + -1 +}; + + +static int radio_open(struct video_device *dev, int flags) +{ + struct bttv *btv = (struct bttv *)(dev-1); + unsigned long v; + + MOD_INC_USE_COUNT; + down(&btv->lock); + if (btv->user) + goto busy_unlock; + btv->user++; + + btv->radio = 1; + v = 400*16; + bttv_call_i2c_clients(btv,VIDIOCSFREQ,&v); + bttv_call_i2c_clients(btv,AUDC_SET_RADIO,&btv->tuner_type); + bt848_muxsel(btv,0); + up(&btv->lock); + + return 0; + + busy_unlock: + up(&btv->lock); + MOD_DEC_USE_COUNT; + return -EBUSY; +} + +static void radio_close(struct video_device *dev) +{ + struct bttv *btv=(struct bttv *)(dev-1); + + down(&btv->lock); + btv->user--; + btv->radio = 0; + up(&btv->lock); + MOD_DEC_USE_COUNT; +} + +static long radio_read(struct video_device *v, char *buf, unsigned long count, int nonblock) +{ + return -EINVAL; +} + +static int radio_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct bttv *btv=(struct bttv *)(dev-1); + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability v; + strcpy(v.name,btv->video_dev.name); + v.type = VID_TYPE_TUNER; + v.channels = 1; + v.audios = 1; + /* No we don't do pictures */ + v.maxwidth = 0; + v.maxheight = 0; + v.minwidth = 0; + v.minheight = 0; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + break; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v,arg,sizeof(v))!=0) + return -EFAULT; + if(v.tuner||btv->channel) /* Only tuner 0 */ + return -EINVAL; + strcpy(v.name, "Radio"); + v.rangelow=(int)(76*16); /* jp: 76.0MHz - 89.9MHz */ + v.rangehigh=(int)(108*16); /* eu: 87.5MHz - 108.0MHz */ + v.flags= 0; /* XXX */ + v.mode = 0; /* XXX */ + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + /* Only channel 0 has a tuner */ + if(v.tuner!=0 || btv->channel) + return -EINVAL; + /* XXX anything to do ??? */ + return 0; + } + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + bttv_ioctl((struct video_device *)btv,cmd,arg); + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct video_device radio_template= +{ + "bttv radio", + VID_TYPE_TUNER, + VID_HARDWARE_BT848, + radio_open, + radio_close, + radio_read, /* just returns -EINVAL */ + bttv_write, /* just returns -EINVAL */ + NULL, /* no poll */ + radio_ioctl, + NULL, /* no mmap */ + bttv_init_done, /* just returns 0 */ + NULL, + 0, + -1 +}; + + +#define TRITON_PCON 0x50 +#define TRITON_BUS_CONCURRENCY (1<<0) +#define TRITON_STREAMING (1<<1) +#define TRITON_WRITE_BURST (1<<2) +#define TRITON_PEER_CONCURRENCY (1<<3) + + +static void __devinit handle_chipset(void) +{ + struct pci_dev *dev = NULL; + + /* Just in case some nut set this to something dangerous */ + if (triton1) + triton1=BT848_INT_ETBF; + + while ((dev = pci_find_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_496, dev))) + { + /* Beware the SiS 85C496 my friend - rev 49 don't work with a bttv */ + printk(KERN_WARNING "BT848 and SIS 85C496 chipset don't always work together.\n"); + } + + while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82441, dev))) + { + unsigned char b; + pci_read_config_byte(dev, 0x53, &b); + DEBUG(printk(KERN_INFO "bttv: Host bridge: 82441FX Natoma, ")); + DEBUG(printk("bufcon=0x%02x\n",b)); + } + + while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437, dev))) + { + printk(KERN_INFO "bttv: Host bridge 82437FX Triton PIIX\n"); + triton1=BT848_INT_ETBF; + } +} + + +static void bt848_set_risc_jmps(struct bttv *btv, int flags) +{ + if (-1 == flags) { + /* defaults */ + flags = 0; + if (btv->scr_on) + flags |= 0x03; + if (btv->vbi_on) + flags |= 0x0c; + } + + if (bttv_debug > 1) + printk("bttv%d: set_risc_jmp %08lx:", + btv->nr,virt_to_bus(btv->risc_jmp)); + + /* Sync to start of odd field */ + btv->risc_jmp[0]=cpu_to_le32(BT848_RISC_SYNC|BT848_RISC_RESYNC + |BT848_FIFO_STATUS_VRE); + btv->risc_jmp[1]=cpu_to_le32(0); + + /* Jump to odd vbi sub */ + btv->risc_jmp[2]=cpu_to_le32(BT848_RISC_JUMP|(0xd<<20)); + if (flags&8) { + if (bttv_debug > 1) + printk(" ev=%08lx",virt_to_bus(btv->vbi_odd)); + btv->risc_jmp[3]=cpu_to_le32(virt_to_bus(btv->vbi_odd)); + } else { + if (bttv_debug > 1) + printk(" -----------"); + btv->risc_jmp[3]=cpu_to_le32(virt_to_bus(btv->risc_jmp+4)); + } + + /* Jump to odd sub */ + btv->risc_jmp[4]=cpu_to_le32(BT848_RISC_JUMP|(0xe<<20)); + if (0 != btv->risc_cap_odd) { + if (bttv_debug > 1) + printk(" e%d=%08x",btv->gq_grab,btv->risc_cap_odd); + flags |= 3; + btv->risc_jmp[5]=cpu_to_le32(btv->risc_cap_odd); + } else if (flags&2) { + if (bttv_debug > 1) + printk(" eo=%08lx",virt_to_bus(btv->risc_scr_odd)); + btv->risc_jmp[5]=cpu_to_le32(virt_to_bus(btv->risc_scr_odd)); + } else { + if (bttv_debug > 1) + printk(" -----------"); + btv->risc_jmp[5]=cpu_to_le32(virt_to_bus(btv->risc_jmp+6)); + } + + + /* Sync to start of even field */ + btv->risc_jmp[6]=cpu_to_le32(BT848_RISC_SYNC|BT848_RISC_RESYNC + |BT848_FIFO_STATUS_VRO); + btv->risc_jmp[7]=cpu_to_le32(0); + + /* Jump to even vbi sub */ + btv->risc_jmp[8]=cpu_to_le32(BT848_RISC_JUMP); + if (flags&4) { + if (bttv_debug > 1) + printk(" ov=%08lx",virt_to_bus(btv->vbi_even)); + btv->risc_jmp[9]=cpu_to_le32(virt_to_bus(btv->vbi_even)); + } else { + if (bttv_debug > 1) + printk(" -----------"); + btv->risc_jmp[9]=cpu_to_le32(virt_to_bus(btv->risc_jmp+10)); + } + + /* Jump to even sub */ + btv->risc_jmp[10]=cpu_to_le32(BT848_RISC_JUMP|(8<<20)); + if (0 != btv->risc_cap_even) { + if (bttv_debug > 1) + printk(" o%d=%08x",btv->gq_grab,btv->risc_cap_even); + flags |= 3; + btv->risc_jmp[11]=cpu_to_le32(btv->risc_cap_even); + } else if (flags&1) { + if (bttv_debug > 1) + printk(" oo=%08lx",virt_to_bus(btv->risc_scr_even)); + btv->risc_jmp[11]=cpu_to_le32(virt_to_bus(btv->risc_scr_even)); + } else { + if (bttv_debug > 1) + printk(" -----------"); + btv->risc_jmp[11]=cpu_to_le32(virt_to_bus(btv->risc_jmp+12)); + } + + if (btv->gq_start) { + btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP|(0x8<<16)|BT848_RISC_IRQ); + } else { + btv->risc_jmp[12]=cpu_to_le32(BT848_RISC_JUMP); + } + btv->risc_jmp[13]=cpu_to_le32(virt_to_bus(btv->risc_jmp)); + + /* enable cpaturing and DMA */ + if (bttv_debug > 1) + printk(" flags=0x%x dma=%s\n", + flags,(flags&0x0f) ? "on" : "off"); + btaor(flags, ~0x0f, BT848_CAP_CTL); + if (flags&0x0f) + bt848_dma(btv, 3); + else + bt848_dma(btv, 0); +} + +static int __devinit init_video_dev(struct bttv *btv) +{ + memcpy(&btv->video_dev,&bttv_template, sizeof(bttv_template)); + memcpy(&btv->vbi_dev,&vbi_template, sizeof(vbi_template)); + memcpy(&btv->radio_dev,&radio_template,sizeof(radio_template)); + + bttv_idcard(btv); + audio(btv, AUDIO_MUTE, 1); + + if(video_register_device(&btv->video_dev,VFL_TYPE_GRABBER)<0) + return -1; + if(video_register_device(&btv->vbi_dev,VFL_TYPE_VBI)<0) + { + video_unregister_device(&btv->video_dev); + return -1; + } + if (radio[btv->nr]) + { + if(video_register_device(&btv->radio_dev, VFL_TYPE_RADIO)<0) + { + video_unregister_device(&btv->vbi_dev); + video_unregister_device(&btv->video_dev); + return -1; + } + } + return 1; +} + +static int __devinit init_bt848(struct bttv *btv) +{ + int j; + unsigned long irq_flags; + + btv->user=0; + init_MUTEX(&btv->lock); + + /* dump current state of the gpio registers before changing them, + * might help to make a new card work */ + if (bttv_verbose >= 2) + printk("bttv%d: gpio: out_enable=0x%x, data=0x%x, in=0x%x\n", + btv->nr, + btread(BT848_GPIO_OUT_EN), + btread(BT848_GPIO_DATA), + btread(BT848_GPIO_REG_INP)); + + /* reset the bt848 */ + btwrite(0, BT848_SRESET); + DEBUG(printk(KERN_DEBUG "bttv%d: bt848_mem: 0x%lx\n", btv->nr, (unsigned long) btv->bt848_mem)); + + /* not registered yet */ + btv->video_dev.minor = -1; + btv->radio_dev.minor = -1; + btv->vbi_dev.minor = -1; + + /* default setup for max. PAL size in a 1024xXXX hicolor framebuffer */ + btv->win.norm=0; /* change this to 1 for NTSC, 2 for SECAM */ + btv->win.interlace=1; + btv->win.x=0; + btv->win.y=0; + btv->win.width=768; /* 640 */ + btv->win.height=576; /* 480 */ + btv->win.bpp=2; + btv->win.depth=16; + btv->win.color_fmt=BT848_COLOR_FMT_RGB16; + btv->win.bpl=1024*btv->win.bpp; + btv->win.swidth=1024; + btv->win.sheight=768; + btv->win.vidadr=0; + btv->vbi_on=0; + btv->scr_on=0; + + btv->risc_scr_odd=0; + btv->risc_scr_even=0; + btv->risc_cap_odd=0; + btv->risc_cap_even=0; + btv->risc_jmp=0; + btv->vbibuf=0; + btv->field=btv->last_field=0; + + btv->errors=0; + btv->needs_restart=0; + + /* i2c */ + btv->tuner_type=-1; + init_bttv_i2c(btv); + + if (!(btv->risc_scr_odd=(unsigned int *) kmalloc(RISCMEM_LEN/2, GFP_KERNEL))) + return -1; + if (!(btv->risc_scr_even=(unsigned int *) kmalloc(RISCMEM_LEN/2, GFP_KERNEL))) + return -1; + if (!(btv->risc_jmp =(unsigned int *) kmalloc(2048, GFP_KERNEL))) + return -1; + btv->vbi_odd=btv->risc_jmp+16; + btv->vbi_even=btv->vbi_odd+256; + btv->bus_vbi_odd=virt_to_bus(btv->risc_jmp+12); + btv->bus_vbi_even=virt_to_bus(btv->risc_jmp+6); + + btwrite(virt_to_bus(btv->risc_jmp+2), BT848_RISC_STRT_ADD); + btv->vbibuf=(unsigned char *) vmalloc_32(VBIBUF_SIZE); + if (!btv->vbibuf) + return -1; + if (!(btv->gbuf = kmalloc(sizeof(struct bttv_gbuf)*gbuffers,GFP_KERNEL))) + return -1; + for (j = 0; j < gbuffers; j++) { + if (!(btv->gbuf[j].risc = kmalloc(16384,GFP_KERNEL))) + return -1; + } + + memset(btv->vbibuf, 0, VBIBUF_SIZE); /* We don't want to return random + memory to the user */ + + btv->fbuffer=NULL; + + bt848_muxsel(btv, 1); + bt848_set_winsize(btv); + +/* btwrite(0, BT848_TDEC); */ + btwrite(0x10, BT848_COLOR_CTL); + btwrite(0x00, BT848_CAP_CTL); + /* set planar and packed mode trigger points and */ + /* set rising edge of inverted GPINTR pin as irq trigger */ + btwrite(BT848_GPIO_DMA_CTL_PKTP_32| + BT848_GPIO_DMA_CTL_PLTP1_16| + BT848_GPIO_DMA_CTL_PLTP23_16| + BT848_GPIO_DMA_CTL_GPINTC| + BT848_GPIO_DMA_CTL_GPINTI, + BT848_GPIO_DMA_CTL); + + /* select direct input */ + btwrite(0x00, BT848_GPIO_REG_INP); + + btwrite(BT848_IFORM_MUX1 | BT848_IFORM_XTAUTO | BT848_IFORM_AUTO, + BT848_IFORM); + + btwrite(0xd8, BT848_CONTRAST_LO); + bt848_bright(btv, 0x10); + + btwrite(0x20, BT848_E_VSCALE_HI); + btwrite(0x20, BT848_O_VSCALE_HI); + btwrite(/*BT848_ADC_SYNC_T|*/ + BT848_ADC_RESERVED|BT848_ADC_CRUSH, BT848_ADC); + + btwrite(BT848_CONTROL_LDEC, BT848_E_CONTROL); + btwrite(BT848_CONTROL_LDEC, BT848_O_CONTROL); + + btv->picture.colour=254<<7; + btv->picture.brightness=128<<8; + btv->picture.hue=128<<8; + btv->picture.contrast=0xd8<<7; + + btwrite(0x00, BT848_E_SCLOOP); + btwrite(0x00, BT848_O_SCLOOP); + + /* clear interrupt status */ + btwrite(0xfffffUL, BT848_INT_STAT); + + /* set interrupt mask */ + btwrite(btv->triton1| + /*BT848_INT_PABORT|BT848_INT_RIPERR|BT848_INT_PPERR| + BT848_INT_FDSR|BT848_INT_FTRGT|BT848_INT_FBUS|*/ + (fieldnr ? BT848_INT_VSYNC : 0)| + BT848_INT_GPINT| + BT848_INT_SCERR| + BT848_INT_RISCI|BT848_INT_OCERR|BT848_INT_VPRES| + BT848_INT_FMTCHG|BT848_INT_HLOCK, + BT848_INT_MASK); + + make_vbitab(btv); + spin_lock_irqsave(&btv->s_lock, irq_flags); + bt848_set_risc_jmps(btv,-1); + spin_unlock_irqrestore(&btv->s_lock, irq_flags); + + /* + * Now add the template and register the device unit. + */ + init_video_dev(btv); + + return 0; +} + +static void bttv_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + u32 stat,astat; + u32 dstat; + int count,i; + struct bttv *btv; + + btv=(struct bttv *)dev_id; + count=0; + while (1) + { + /* get/clear interrupt status bits */ + stat=btread(BT848_INT_STAT); + astat=stat&btread(BT848_INT_MASK); + if (!astat) + return; + btwrite(astat,BT848_INT_STAT); + IDEBUG(printk ("bttv%d: astat=%08x\n", btv->nr, astat)); + IDEBUG(printk ("bttv%d: stat=%08x\n", btv->nr, stat)); + + /* get device status bits */ + dstat=btread(BT848_DSTATUS); + + if (astat&BT848_INT_GPINT) { + IDEBUG(printk ("bttv%d: IRQ_GPINT\n", btv->nr)); + wake_up_interruptible(&btv->gpioq); + } + + if (astat&BT848_INT_FMTCHG) + { + IDEBUG(printk ("bttv%d: IRQ_FMTCHG\n", btv->nr)); + /*btv->win.norm&= + (dstat&BT848_DSTATUS_NUML) ? (~1) : (~0); */ + } + if (astat&BT848_INT_VPRES) + { + IDEBUG(printk ("bttv%d: IRQ_VPRES\n", btv->nr)); + } + if (astat&BT848_INT_VSYNC) + { + IDEBUG(printk ("bttv%d: IRQ_VSYNC\n", btv->nr)); + btv->field++; + } + if (astat&(BT848_INT_SCERR|BT848_INT_OCERR)) { + if (bttv_verbose) + printk("bttv%d: irq:%s%s risc_count=%08x\n", + btv->nr, + (astat&BT848_INT_SCERR) ? " SCERR" : "", + (astat&BT848_INT_OCERR) ? " OCERR" : "", + btread(BT848_RISC_COUNT)); + btv->errors++; + if (btv->errors < BTTV_ERRORS) { + spin_lock(&btv->s_lock); + btand(~15, BT848_GPIO_DMA_CTL); + btwrite(virt_to_bus(btv->risc_jmp+2), + BT848_RISC_STRT_ADD); + bt848_set_geo(btv,0); + bt848_set_risc_jmps(btv,-1); + spin_unlock(&btv->s_lock); + } else { + if (bttv_verbose) + printk("bttv%d: aiee: error loops\n",btv->nr); + /* cancel all outstanding grab requests */ + spin_lock(&btv->s_lock); + btv->gq_in = 0; + btv->gq_out = 0; + btv->gq_grab = -1; + for (i = 0; i < gbuffers; i++) + if (btv->gbuf[i].stat == GBUFFER_GRABBING) + btv->gbuf[i].stat = GBUFFER_ERROR; + /* disable DMA */ + btv->risc_cap_odd = 0; + btv->risc_cap_even = 0; + bt848_set_risc_jmps(btv,0); + btv->needs_restart = 1; + spin_unlock(&btv->s_lock); + + wake_up_interruptible(&btv->vbiq); + wake_up_interruptible(&btv->capq); + } + } + if (astat&BT848_INT_RISCI) + { + if (bttv_debug > 1) + printk("bttv%d: IRQ_RISCI\n",btv->nr); + + /* captured VBI frame */ + if (stat&(1<<28)) + { + btv->vbip=0; + /* inc vbi frame count for detecting drops */ + (*(u32 *)&(btv->vbibuf[VBIBUF_SIZE - 4]))++; + wake_up_interruptible(&btv->vbiq); + } + + /* captured full frame */ + if (stat&(2<<28) && btv->gq_grab != -1) + { + btv->last_field=btv->field; + if (bttv_debug) + printk("bttv%d: cap irq: done %d\n",btv->nr,btv->gq_grab); + do_gettimeofday(&btv->gbuf[btv->gq_grab].tv); + spin_lock(&btv->s_lock); + btv->gbuf[btv->gq_grab].stat = GBUFFER_DONE; + btv->gq_grab = -1; + if (btv->gq_in != btv->gq_out) + { + btv->gq_grab = btv->gqueue[btv->gq_out++]; + btv->gq_out = btv->gq_out % MAX_GBUFFERS; + if (bttv_debug) + printk("bttv%d: cap irq: capture %d\n",btv->nr,btv->gq_grab); + btv->risc_cap_odd = btv->gbuf[btv->gq_grab].ro; + btv->risc_cap_even = btv->gbuf[btv->gq_grab].re; + bt848_set_risc_jmps(btv,-1); + bt848_set_geo(btv,0); + btwrite(BT848_COLOR_CTL_GAMMA, + BT848_COLOR_CTL); + } else { + btv->risc_cap_odd = 0; + btv->risc_cap_even = 0; + bt848_set_risc_jmps(btv,-1); + bt848_set_geo(btv,0); + btwrite(btv->fb_color_ctl | BT848_COLOR_CTL_GAMMA, + BT848_COLOR_CTL); + } + spin_unlock(&btv->s_lock); + wake_up_interruptible(&btv->capq); + break; + } + if (stat&(8<<28)) + { + spin_lock(&btv->s_lock); + btv->gq_start = 0; + btv->gq_grab = btv->gqueue[btv->gq_out++]; + btv->gq_out = btv->gq_out % MAX_GBUFFERS; + if (bttv_debug) + printk("bttv%d: cap irq: capture %d [start]\n",btv->nr,btv->gq_grab); + btv->risc_cap_odd = btv->gbuf[btv->gq_grab].ro; + btv->risc_cap_even = btv->gbuf[btv->gq_grab].re; + bt848_set_risc_jmps(btv,-1); + bt848_set_geo(btv,0); + btwrite(BT848_COLOR_CTL_GAMMA, + BT848_COLOR_CTL); + spin_unlock(&btv->s_lock); + } + } + if (astat&BT848_INT_OCERR) + { + IDEBUG(printk ("bttv%d: IRQ_OCERR\n", btv->nr)); + } + if (astat&BT848_INT_PABORT) + { + IDEBUG(printk ("bttv%d: IRQ_PABORT\n", btv->nr)); + } + if (astat&BT848_INT_RIPERR) + { + IDEBUG(printk ("bttv%d: IRQ_RIPERR\n", btv->nr)); + } + if (astat&BT848_INT_PPERR) + { + IDEBUG(printk ("bttv%d: IRQ_PPERR\n", btv->nr)); + } + if (astat&BT848_INT_FDSR) + { + IDEBUG(printk ("bttv%d: IRQ_FDSR\n", btv->nr)); + } + if (astat&BT848_INT_FTRGT) + { + IDEBUG(printk ("bttv%d: IRQ_FTRGT\n", btv->nr)); + } + if (astat&BT848_INT_FBUS) + { + IDEBUG(printk ("bttv%d: IRQ_FBUS\n", btv->nr)); + } + if (astat&BT848_INT_HLOCK) + { + if ((dstat&BT848_DSTATUS_HLOC) || (btv->radio)) + audio(btv, AUDIO_ON,0); + else + audio(btv, AUDIO_OFF,0); + } + + if (astat&BT848_INT_I2CDONE) + { + IDEBUG(printk ("bttv%d: IRQ_I2CDONE\n", btv->nr)); + } + count++; + if (count > 10) + printk (KERN_WARNING "bttv%d: irq loop %d\n", + btv->nr,count); + if (count > 20) + { + btwrite(0, BT848_INT_MASK); + printk(KERN_ERR + "bttv%d: IRQ lockup, cleared int mask\n", btv->nr); + } + } +} + + + +/* + * Scan for a Bt848 card, request the irq and map the io memory + */ + +static void __devinit bttv_remove(struct pci_dev *pci_dev) +{ + u8 command; + int j; + struct bttv *btv = PCI_GET_DRIVER_DATA(pci_dev); + + /* unregister i2c_bus */ + if (0 == btv->i2c_ok) + i2c_bit_del_bus(&btv->i2c_adap); + + /* turn off all capturing, DMA and IRQs */ + btand(~15, BT848_GPIO_DMA_CTL); + + /* first disable interrupts before unmapping the memory! */ + btwrite(0, BT848_INT_MASK); + btwrite(~0x0UL,BT848_INT_STAT); + btwrite(0x0, BT848_GPIO_OUT_EN); + + /* disable PCI bus-mastering */ + pci_read_config_byte(btv->dev, PCI_COMMAND, &command); + /* Should this be &=~ ?? */ + command&=~PCI_COMMAND_MASTER; + pci_write_config_byte(btv->dev, PCI_COMMAND, command); + + /* unmap and free memory */ + for (j = 0; j < gbuffers; j++) + if (btv->gbuf[j].risc) + kfree(btv->gbuf[j].risc); + if (btv->gbuf) + kfree((void *) btv->gbuf); + + if (btv->risc_scr_odd) + kfree((void *) btv->risc_scr_odd); + + if (btv->risc_scr_even) + kfree((void *) btv->risc_scr_even); + + DEBUG(printk(KERN_DEBUG "free: risc_jmp: 0x%p.\n", btv->risc_jmp)); + if (btv->risc_jmp) + kfree((void *) btv->risc_jmp); + + DEBUG(printk(KERN_DEBUG "bt848_vbibuf: 0x%p.\n", btv->vbibuf)); + if (btv->vbibuf) + vfree((void *) btv->vbibuf); + + free_irq(btv->irq,btv); + DEBUG(printk(KERN_DEBUG "bt848_mem: 0x%p.\n", btv->bt848_mem)); + if (btv->bt848_mem) + iounmap(btv->bt848_mem); + + if(btv->video_dev.minor!=-1) + video_unregister_device(&btv->video_dev); + if(btv->vbi_dev.minor!=-1) + video_unregister_device(&btv->vbi_dev); + if (radio[btv->nr] && btv->radio_dev.minor != -1) + video_unregister_device(&btv->radio_dev); + + release_mem_region(btv->bt848_adr, + pci_resource_len(btv->dev,0)); + /* wake up any waiting processes + because shutdown flag is set, no new processes (in this queue) + are expected + */ + btv->shutdown=1; + wake_up(&btv->gpioq); + + return; +} + + +static int __devinit bttv_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) +{ + int result; + unsigned char command; + struct bttv *btv; +#if defined(__powerpc__) + unsigned int cmd; +#endif + + printk(KERN_INFO "bttv: Bt8xx card found (%d).\n", bttv_num); + + btv=&bttvs[bttv_num]; + btv->dev=dev; + btv->nr = bttv_num; + btv->bt848_mem=NULL; + btv->vbibuf=NULL; + btv->risc_jmp=NULL; + btv->vbi_odd=NULL; + btv->vbi_even=NULL; + init_waitqueue_head(&btv->vbiq); + init_waitqueue_head(&btv->capq); + btv->vbip=VBIBUF_SIZE; + btv->s_lock = SPIN_LOCK_UNLOCKED; + init_waitqueue_head(&btv->gpioq); + btv->shutdown=0; + + btv->id=dev->device; + btv->irq=dev->irq; + btv->bt848_adr=pci_resource_start(dev,0); + if (pci_enable_device(dev)) + return -EIO; + if (!request_mem_region(pci_resource_start(dev,0), + pci_resource_len(dev,0), + "bttv")) { + return -EBUSY; + } + if (btv->id >= 878) + btv->i2c_command = 0x83; + else + btv->i2c_command=(I2C_TIMING | BT848_I2C_SCL | BT848_I2C_SDA); + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &btv->revision); + printk(KERN_INFO "bttv%d: Brooktree Bt%d (rev %d) ", + bttv_num,btv->id, btv->revision); + printk("bus: %d, devfn: %d, ",dev->bus->number, dev->devfn); + printk("irq: %d, ",btv->irq); + printk("memory: 0x%lx.\n", btv->bt848_adr); + +#if defined(__powerpc__) + /* on OpenFirmware machines (PowerMac at least), PCI memory cycle */ + /* response on cards with no firmware is not enabled by OF */ + pci_read_config_dword(dev, PCI_COMMAND, &cmd); + cmd = (cmd | PCI_COMMAND_MEMORY ); + pci_write_config_dword(dev, PCI_COMMAND, cmd); +#endif + +#ifdef __sparc__ + btv->bt848_mem=(unsigned char *)btv->bt848_adr; +#else + btv->bt848_mem=ioremap(btv->bt848_adr, 0x1000); +#endif + + /* clear interrupt mask */ + btwrite(0, BT848_INT_MASK); + + result = request_irq(btv->irq, bttv_irq, + SA_SHIRQ | SA_INTERRUPT,"bttv",(void *)btv); + if (result==-EINVAL) + { + printk(KERN_ERR "bttv%d: Bad irq number or handler\n", + bttv_num); + goto fail; + } + if (result==-EBUSY) + { + printk(KERN_ERR "bttv%d: IRQ %d busy, change your PnP config in BIOS\n",bttv_num,btv->irq); + goto fail; + } + if (result < 0) + goto fail; + + pci_set_master(dev); + + btv->triton1=triton1 ? BT848_INT_ETBF : 0; + if (triton1 && btv->id >= 878) + { + btv->triton1 = 0; + printk("bttv: Enabling 430FX compatibilty for bt878\n"); + pci_read_config_byte(dev, BT878_DEVCTRL, &command); + command|=BT878_EN_TBFX; + pci_write_config_byte(dev, BT878_DEVCTRL, command); + pci_read_config_byte(dev, BT878_DEVCTRL, &command); + if (!(command&BT878_EN_TBFX)) + { + printk("bttv: 430FX compatibility could not be enabled\n"); + free_irq(btv->irq,btv); + result = -1; + goto fail; + } + } + + PCI_SET_DRIVER_DATA(dev,btv); + + if(init_bt848(btv) < 0) { + bttv_remove(dev); + return -EIO; + } + bttv_num++; + + return 0; + + fail: + release_mem_region(pci_resource_start(btv->dev,0), + pci_resource_len(btv->dev,0)); + return result; +} + +static struct pci_device_id bttv_pci_tbl[] __devinitdata = { + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT848, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT849, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT878, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_BROOKTREE, PCI_DEVICE_ID_BT879, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, bttv_pci_tbl); + +static struct pci_driver bttv_pci_driver = { + name: "bttv", + id_table: bttv_pci_tbl, + probe: bttv_probe, + remove: bttv_remove, +}; + +int bttv_init_module(void) +{ + bttv_num = 0; + + printk(KERN_INFO "bttv: driver version %d.%d.%d loaded\n", + (BTTV_VERSION_CODE >> 16) & 0xff, + (BTTV_VERSION_CODE >> 8) & 0xff, + BTTV_VERSION_CODE & 0xff); + if (gbuffers < 2 || gbuffers > MAX_GBUFFERS) + gbuffers = 2; + if (gbufsize < 0 || gbufsize > BTTV_MAX_FBUF) + gbufsize = BTTV_MAX_FBUF; + if (bttv_verbose) + printk(KERN_INFO "bttv: using %d buffers with %dk (%dk total) for capture\n", + gbuffers,gbufsize/1024,gbuffers*gbufsize/1024); + + handle_chipset(); + + return pci_module_init(&bttv_pci_driver); +} + +void bttv_cleanup_module(void) +{ + pci_unregister_driver(&bttv_pci_driver); + return; +} + +module_init(bttv_init_module); +module_exit(bttv_cleanup_module); + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv-if.c b/drivers/media/video/bttv-if.c new file mode 100644 index 000000000..a58e441db --- /dev/null +++ b/drivers/media/video/bttv-if.c @@ -0,0 +1,337 @@ +/* + bttv-if.c -- interfaces to other kernel modules + all the i2c code is here + also the gpio interface exported by bttv (used by lirc) + + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + (c) 1999,2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ 1 + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include "bttv.h" +#include "tuner.h" + + +EXPORT_SYMBOL(bttv_get_cardinfo); +EXPORT_SYMBOL(bttv_get_id); +EXPORT_SYMBOL(bttv_gpio_enable); +EXPORT_SYMBOL(bttv_read_gpio); +EXPORT_SYMBOL(bttv_write_gpio); +EXPORT_SYMBOL(bttv_get_gpio_queue); + +/* ----------------------------------------------------------------------- */ +/* Exported functions - for other modules which want to access the */ +/* gpio ports (IR for example) */ +/* see bttv.h for comments */ + +int bttv_get_cardinfo(unsigned int card, int *type, int *cardid) +{ + if (card >= bttv_num) { + return -1; + } + *type = bttvs[card].type; + *cardid = bttvs[card].cardid; + return 0; +} + +int bttv_get_id(unsigned int card) +{ + printk("bttv_get_id is obsolete, use bttv_get_cardinfo instead\n"); + if (card >= bttv_num) { + return -1; + } + return bttvs[card].type; +} + +int bttv_gpio_enable(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + btaor(data, ~mask, BT848_GPIO_OUT_EN); + return 0; +} + +int bttv_read_gpio(unsigned int card, unsigned long *data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + + if(btv->shutdown) { + return -ENODEV; + } + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because we set direct input on init */ + *data = btread(BT848_GPIO_DATA); + return 0; +} + +int bttv_write_gpio(unsigned int card, unsigned long mask, unsigned long data) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return -EINVAL; + } + + btv = &bttvs[card]; + +/* prior setting BT848_GPIO_REG_INP is (probably) not needed + because direct input is set on init */ + btaor(data & mask, ~mask, BT848_GPIO_DATA); + return 0; +} + +wait_queue_head_t* bttv_get_gpio_queue(unsigned int card) +{ + struct bttv *btv; + + if (card >= bttv_num) { + return NULL; + } + + btv = &bttvs[card]; + if (bttvs[card].shutdown) { + return NULL; + } + return &btv->gpioq; +} + + +/* ----------------------------------------------------------------------- */ +/* I2C functions */ + +void bttv_bit_setscl(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x02; + else + btv->i2c_state &= ~0x02; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +void bttv_bit_setsda(void *data, int state) +{ + struct bttv *btv = (struct bttv*)data; + + if (state) + btv->i2c_state |= 0x01; + else + btv->i2c_state &= ~0x01; + btwrite(btv->i2c_state, BT848_I2C); + btread(BT848_I2C); +} + +static int bttv_bit_getscl(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x02 ? 1 : 0; + return state; +} + +static int bttv_bit_getsda(void *data) +{ + struct bttv *btv = (struct bttv*)data; + int state; + + state = btread(BT848_I2C) & 0x01; + return state; +} + +static void bttv_inc_use(struct i2c_adapter *adap) +{ + MOD_INC_USE_COUNT; +} + +static void bttv_dec_use(struct i2c_adapter *adap) +{ + MOD_DEC_USE_COUNT; +} + +static int attach_inform(struct i2c_client *client) +{ + struct bttv *btv = (struct bttv*)client->adapter->data; + int i; + + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (btv->i2c_clients[i] == NULL || + btv->i2c_clients[i]->driver->id == client->driver->id) { + btv->i2c_clients[i] = client; + break; + } + } + if (btv->tuner_type != -1) + bttv_call_i2c_clients(btv,TUNER_SET_TYPE,&btv->tuner_type); + if (bttv_verbose) + printk("bttv%d: i2c attach [%s]\n",btv->nr,client->name); + return 0; +} + +static int detach_inform(struct i2c_client *client) +{ + struct bttv *btv = (struct bttv*)client->adapter->data; + int i; + + if (bttv_verbose) + printk("bttv%d: i2c detach [%s]\n",btv->nr,client->name); + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (NULL != btv->i2c_clients[i] && + btv->i2c_clients[i]->driver->id == client->driver->id) { + btv->i2c_clients[i] = NULL; + break; + } + } + return 0; +} + +void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg) +{ + int i; + + for (i = 0; i < I2C_CLIENTS_MAX; i++) { + if (NULL == btv->i2c_clients[i]) + continue; + if (NULL == btv->i2c_clients[i]->driver->command) + continue; + btv->i2c_clients[i]->driver->command( + btv->i2c_clients[i],cmd,arg); + } +} + +struct i2c_algo_bit_data bttv_i2c_algo_template = { + NULL, + bttv_bit_setsda, + bttv_bit_setscl, + bttv_bit_getsda, + bttv_bit_getscl, + 10, 10, 100, +}; + +struct i2c_adapter bttv_i2c_adap_template = { + "bt848", + I2C_HW_B_BT848, + NULL, + NULL, + bttv_inc_use, + bttv_dec_use, + attach_inform, + detach_inform, + NULL, +}; + +struct i2c_client bttv_i2c_client_template = { + "bttv internal", + -1, + 0, + 0, + NULL, + NULL +}; + + +/* read I2C */ +int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for) +{ + unsigned char buffer = 0; + + if (0 != btv->i2c_ok) + return -1; + if (bttv_verbose && NULL != probe_for) + printk(KERN_INFO "bttv%d: i2c: checking for %s @ 0x%02x... ", + btv->nr,probe_for,addr); + btv->i2c_client.addr = addr >> 1; + if (1 != i2c_master_recv(&btv->i2c_client, &buffer, 1)) { + if (NULL != probe_for) { + if (bttv_verbose) + printk("not found\n"); + } else + printk(KERN_WARNING "bttv%d: i2c read 0x%x: error\n", + btv->nr,addr); + return -1; + } + if (bttv_verbose && NULL != probe_for) + printk("found\n"); + return buffer; +} + +/* write I2C */ +int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + unsigned char buffer[2]; + int bytes = both ? 2 : 1; + + if (0 != btv->i2c_ok) + return -1; + btv->i2c_client.addr = addr >> 1; + buffer[0] = b1; + buffer[1] = b2; + if (bytes != i2c_master_send(&btv->i2c_client, buffer, bytes)) + return -1; + return 0; +} + +/* read EEPROM content */ +void __devinit bttv_readee(struct bttv *btv, unsigned char *eedata, int addr) +{ + int i; + + if (bttv_I2CWrite(btv, addr, 0, -1, 0)<0) { + printk(KERN_WARNING "bttv: readee error\n"); + return; + } + btv->i2c_client.addr = addr >> 1; + for (i=0; i<256; i+=16) { + if (16 != i2c_master_recv(&btv->i2c_client,eedata+i,16)) { + printk(KERN_WARNING "bttv: readee error\n"); + break; + } + } +} + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/bttv.h b/drivers/media/video/bttv.h new file mode 100644 index 000000000..6b35d23e5 --- /dev/null +++ b/drivers/media/video/bttv.h @@ -0,0 +1,425 @@ +/* + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _BTTV_H_ +#define _BTTV_H_ + +#define BTTV_VERSION_CODE KERNEL_VERSION(0,7,38) + +#ifndef PCI_GET_DRIVER_DATA +# define PCI_GET_DRIVER_DATA(pdev) ((pdev)->driver_data) +# define PCI_SET_DRIVER_DATA(pdev,data) (((pdev)->driver_data) = (data)) +#endif /* PCI_GET_DRIVER_DATA */ + +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "audiochip.h" +#include "bt848.h" + +#ifdef __KERNEL__ + +/* fwd decl */ +struct bttv; + + +/* ---------------------------------------------------------- */ +/* exported by bttv-cards.c */ + +#define BTTV_UNKNOWN 0x00 +#define BTTV_MIRO 0x01 +#define BTTV_HAUPPAUGE 0x02 +#define BTTV_STB 0x03 +#define BTTV_INTEL 0x04 +#define BTTV_DIAMOND 0x05 +#define BTTV_AVERMEDIA 0x06 +#define BTTV_MATRIX_VISION 0x07 +#define BTTV_FLYVIDEO 0x08 +#define BTTV_TURBOTV 0x09 +#define BTTV_HAUPPAUGE878 0x0a +#define BTTV_MIROPRO 0x0b +#define BTTV_ADSTECH_TV 0x0c +#define BTTV_AVERMEDIA98 0x0d +#define BTTV_VHX 0x0e +#define BTTV_ZOLTRIX 0x0f +#define BTTV_PIXVIEWPLAYTV 0x10 +#define BTTV_WINVIEW_601 0x11 +#define BTTV_AVEC_INTERCAP 0x12 +#define BTTV_LIFE_FLYKIT 0x13 +#define BTTV_CEI_RAFFLES 0x14 +#define BTTV_CONFERENCETV 0x15 +#define BTTV_PHOEBE_TVMAS 0x16 +#define BTTV_MODTEC_205 0x17 +#define BTTV_MAGICTVIEW061 0x18 +#define BTTV_VOBIS_BOOSTAR 0x19 +#define BTTV_HAUPPAUG_WCAM 0x1a +#define BTTV_MAXI 0x1b +#define BTTV_TERRATV 0x1c +#define BTTV_PXC200 0x1d +#define BTTV_FLYVIDEO_98 0x1e +#define BTTV_IPROTV 0x1f +#define BTTV_INTEL_C_S_PCI 0x20 +#define BTTV_TERRATVALUE 0x21 +#define BTTV_WINFAST2000 0x22 +#define BTTV_CHRONOS_VS2 0x23 +#define BTTV_TYPHOON_TVIEW 0x24 +#define BTTV_PXELVWPLTVPRO 0x25 +#define BTTV_MAGICTVIEW063 0x26 +#define BTTV_PINNACLERAVE 0x27 +#define BTTV_STB2 0x28 +#define BTTV_AVPHONE98 0x29 +#define BTTV_PV951 0x2a +#define BTTV_ONAIR_TV 0x2b +#define BTTV_SIGMA_TVII_FM 0x2c +#define BTTV_MATRIX_VISION2 0x2d +#define BTTV_ZOLTRIX_GENIE 0x2e +#define BTTV_TERRATVRADIO 0x2f +#define BTTV_DYNALINK 0x30 + +struct tvcard +{ + char *name; + int video_inputs; + int audio_inputs; + int tuner; + int svhs; + u32 gpiomask; + u32 muxsel[8]; + u32 audiomux[6]; /* Tuner, Radio, external, internal, mute, stereo */ + u32 gpiomask2; /* GPIO MUX mask */ + + /* look for these i2c audio chips */ + int msp34xx:1; + int tda8425:1; + int tda9840:1; + int tda985x:1; + int tea63xx:1; + int tea64xx:1; + int tda7432:1; + int tda9875:1; + + /* other settings */ + int pll; +#define PLL_NONE 0 +#define PLL_28 1 +#define PLL_35 2 + + int tuner_type; +}; + +extern struct tvcard bttv_tvcards[]; +extern const int bttv_num_tvcards; + +/* identification / initialization of the card */ +extern void bttv_idcard(struct bttv *btv); + +/* card-specific funtions */ +extern void tea5757_set_freq(struct bttv *btv, unsigned short freq); +extern void winview_setvol(struct bttv *btv, struct video_audio *v); + +/* ---------------------------------------------------------- */ +/* exported by bttv-if.c */ +/* interface for gpio access by other modules */ + +/* returns card type + card ID (for bt878-based ones) + for possible values see lines below beginning with #define BTTV_UNKNOWN + returns negative value if error ocurred +*/ +extern int bttv_get_cardinfo(unsigned int card, int *type, int *cardid); + +/* obsolete, use bttv_get_cardinfo instead */ +extern int bttv_get_id(unsigned int card); + +/* sets GPOE register (BT848_GPIO_OUT_EN) to new value: + data | (current_GPOE_value & ~mask) + returns negative value if error ocurred +*/ +extern int bttv_gpio_enable(unsigned int card, + unsigned long mask, unsigned long data); + +/* fills data with GPDATA register contents + returns negative value if error ocurred +*/ +extern int bttv_read_gpio(unsigned int card, unsigned long *data); + +/* sets GPDATA register to new value: + (data & mask) | (current_GPDATA_value & ~mask) + returns negative value if error ocurred +*/ +extern int bttv_write_gpio(unsigned int card, + unsigned long mask, unsigned long data); + +/* returns pointer to task queue which can be used as parameter to + interruptible_sleep_on + in interrupt handler if BT848_INT_GPINT bit is set - this queue is activated + (wake_up_interruptible) and following call to the function bttv_read_gpio + should return new value of GPDATA, + returns NULL value if error ocurred or queue is not available + WARNING: because there is no buffer for GPIO data, one MUST + process data ASAP +*/ +extern wait_queue_head_t* bttv_get_gpio_queue(unsigned int card); + +/* i2c */ +struct i2c_algo_bit_data bttv_i2c_algo_template; +struct i2c_adapter bttv_i2c_adap_template; +struct i2c_client bttv_i2c_client_template; +void bttv_bit_setscl(void *data, int state); +void bttv_bit_setsda(void *data, int state); +void bttv_call_i2c_clients(struct bttv *btv, unsigned int cmd, void *arg); +int bttv_I2CRead(struct bttv *btv, unsigned char addr, char *probe_for); +int bttv_I2CWrite(struct bttv *btv, unsigned char addr, unsigned char b1, + unsigned char b2, int both); +void bttv_readee(struct bttv *btv, unsigned char *eedata, int addr); + + +/* ---------------------------------------------------------- */ +/* bttv-driver.c */ + +/* insmod options */ +extern unsigned int bttv_verbose; +extern unsigned int bttv_debug; + +/* Anybody who uses more than four? */ +#define BTTV_MAX 4 +extern int bttv_num; /* number of Bt848s in use */ +extern struct bttv bttvs[BTTV_MAX]; + + +#ifndef O_NONCAP +#define O_NONCAP O_TRUNC +#endif + +#define MAX_GBUFFERS 64 +#define RISCMEM_LEN (32744*2) +#define VBI_MAXLINES 16 +#define VBIBUF_SIZE (2048*VBI_MAXLINES*2) + +#define BTTV_MAX_FBUF 0x208000 +#define I2C_CLIENTS_MAX 8 + +struct bttv_window +{ + int x, y; + ushort width, height; + ushort bpp, bpl; + ushort swidth, sheight; + unsigned long vidadr; + ushort freq; + int norm; + int interlace; + int color_fmt; + ushort depth; +}; + +struct bttv_pll_info { + unsigned int pll_ifreq; /* PLL input frequency */ + unsigned int pll_ofreq; /* PLL output frequency */ + unsigned int pll_crystal; /* Crystal used for input */ + unsigned int pll_current; /* Currently programmed ofreq */ +}; + +struct bttv_gbuf { + int stat; +#define GBUFFER_UNUSED 0 +#define GBUFFER_GRABBING 1 +#define GBUFFER_DONE 2 +#define GBUFFER_ERROR 3 + struct timeval tv; + + u16 width; + u16 height; + u16 fmt; + + u32 *risc; + unsigned long ro; + unsigned long re; +}; + +struct bttv { + struct video_device video_dev; + struct video_device radio_dev; + struct video_device vbi_dev; + struct video_picture picture; /* Current picture params */ + struct video_audio audio_dev; /* Current audio params */ + + spinlock_t s_lock; + struct semaphore lock; + int user; + int capuser; + + /* i2c */ + struct i2c_adapter i2c_adap; + struct i2c_algo_bit_data i2c_algo; + struct i2c_client i2c_client; + int i2c_state, i2c_ok; + struct i2c_client *i2c_clients[I2C_CLIENTS_MAX]; + + int tuner_type; + int channel; + + unsigned int nr; + unsigned short id; + struct pci_dev *dev; + unsigned int irq; /* IRQ used by Bt848 card */ + unsigned char revision; + unsigned long bt848_adr; /* bus address of IO mem returned by PCI BIOS */ + unsigned char *bt848_mem; /* pointer to mapped IO memory */ + unsigned long busriscmem; + u32 *riscmem; + + unsigned char *vbibuf; + struct bttv_window win; + int fb_color_ctl; + int type; /* card type */ + int cardid; + int audio; /* audio mode */ + int audio_chip; /* set to one of the chips supported by bttv.c */ + int radio; + + u32 *risc_jmp; + u32 *vbi_odd; + u32 *vbi_even; + u32 bus_vbi_even; + u32 bus_vbi_odd; + wait_queue_head_t vbiq; + wait_queue_head_t capq; + int vbip; + + u32 *risc_scr_odd; + u32 *risc_scr_even; + u32 risc_cap_odd; + u32 risc_cap_even; + int scr_on; + int vbi_on; + struct video_clip *cliprecs; + + struct bttv_gbuf *gbuf; + int gqueue[MAX_GBUFFERS]; + int gq_in,gq_out,gq_grab,gq_start; + char *fbuffer; + + struct bttv_pll_info pll; + unsigned int Fsc; + unsigned int field; + unsigned int last_field; /* number of last grabbed field */ + int i2c_command; + int triton1; + + int errors; + int needs_restart; + + wait_queue_head_t gpioq; + int shutdown; +}; +#endif + +#if defined(__powerpc__) /* big-endian */ +extern __inline__ void io_st_le32(volatile unsigned *addr, unsigned val) +{ + __asm__ __volatile__ ("stwbrx %1,0,%2" : \ + "=m" (*addr) : "r" (val), "r" (addr)); + __asm__ __volatile__ ("eieio" : : : "memory"); +} + +#define btwrite(dat,adr) io_st_le32((unsigned *)(btv->bt848_mem+(adr)),(dat)) +#define btread(adr) ld_le32((unsigned *)(btv->bt848_mem+(adr))) +#else +#define btwrite(dat,adr) writel((dat), (char *) (btv->bt848_mem+(adr))) +#define btread(adr) readl(btv->bt848_mem+(adr)) +#endif + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +/* bttv ioctls */ + +#define BTTV_READEE _IOW('v', BASE_VIDIOCPRIVATE+0, char [256]) +#define BTTV_WRITEE _IOR('v', BASE_VIDIOCPRIVATE+1, char [256]) +#define BTTV_FIELDNR _IOR('v' , BASE_VIDIOCPRIVATE+2, unsigned int) +#define BTTV_PLLSET _IOW('v' , BASE_VIDIOCPRIVATE+3, struct bttv_pll_info) +#define BTTV_BURST_ON _IOR('v' , BASE_VIDIOCPRIVATE+4, int) +#define BTTV_BURST_OFF _IOR('v' , BASE_VIDIOCPRIVATE+5, int) +#define BTTV_VERSION _IOR('v' , BASE_VIDIOCPRIVATE+6, int) +#define BTTV_PICNR _IOR('v' , BASE_VIDIOCPRIVATE+7, int) +#define BTTV_VBISIZE _IOR('v' , BASE_VIDIOCPRIVATE+8, int) + +#define AUDIO_TUNER 0x00 +#define AUDIO_RADIO 0x01 +#define AUDIO_EXTERN 0x02 +#define AUDIO_INTERN 0x03 +#define AUDIO_OFF 0x04 +#define AUDIO_ON 0x05 +#define AUDIO_MUTE 0x80 +#define AUDIO_UNMUTE 0x81 + +#define TDA9850 0x01 +#define TDA9840 0x02 +#define TDA8425 0x03 +#define TEA6300 0x04 + +#define I2C_TSA5522 0xc2 +#define I2C_TDA7432 0x8a +#define I2C_TDA8425 0x82 +#define I2C_TDA9840 0x84 +#define I2C_TDA9850 0xb6 /* also used by 9855,9873 */ +#define I2C_TDA9875 0xb0 +#define I2C_HAUPEE 0xa0 +#define I2C_STBEE 0xae +#define I2C_VHX 0xc0 +#define I2C_MSP3400 0x80 +#define I2C_TEA6300 0x80 +#define I2C_DPL3518 0x84 + +#ifndef HAVE_TVAUDIO +#define TDA9840_SW 0x00 +#define TDA9840_LVADJ 0x02 +#define TDA9840_STADJ 0x03 +#define TDA9840_TEST 0x04 +#endif + +#define PT2254_L_CHANEL 0x10 +#define PT2254_R_CHANEL 0x08 +#define PT2254_DBS_IN_2 0x400 +#define PT2254_DBS_IN_10 0x20000 +#define WINVIEW_PT2254_CLK 0x40 +#define WINVIEW_PT2254_DATA 0x20 +#define WINVIEW_PT2254_STROBE 0x80 + +struct bttv_just_hacking { + int height,width; /* size */ + unsigned int format; /* should be VIDEO_PALETTE_* */ + long buf; + int len; +}; + +#define BTTV_JUST_HACKING _IOR('v' , BASE_VIDIOCPRIVATE+31,struct bttv_just_hacking) + +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/buz.c b/drivers/media/video/buz.c new file mode 100644 index 000000000..ca3cb4f47 --- /dev/null +++ b/drivers/media/video/buz.c @@ -0,0 +1,3479 @@ +#define MAX_KMALLOC_MEM (512*1024) +/* + buz - Iomega Buz driver version 1.0 + + Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + + based on + + buz.0.0.3 Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + and + + bttv - Bt848 frame grabber driver + + Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) + & Marcus Metzler (mocm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> +#include <linux/wrapper.h> +#include <linux/spinlock.h> +#include <linux/vmalloc.h> + +#include <linux/videodev.h> + +#include <linux/version.h> +#include <asm/uaccess.h> + +#include <linux/i2c-old.h> +#include "buz.h" +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> + +#define IRQ_MASK ( ZR36057_ISR_GIRQ0 | /* ZR36057_ISR_GIRQ1 | ZR36057_ISR_CodRepIRQ | */ ZR36057_ISR_JPEGRepIRQ ) +#define GPIO_MASK 0xdf + +/* + + BUZ + + GPIO0 = 1, take board out of reset + GPIO1 = 1, take JPEG codec out of sleep mode + GPIO3 = 1, deassert FRAME# to 36060 + + + GIRQ0 signals a vertical sync of the video signal + GIRQ1 signals that ZR36060's DATERR# line is asserted. + + SAA7111A + + In their infinite wisdom, the Iomega engineers decided to + use the same input line for composite and S-Video Color, + although there are two entries not connected at all! + Through this ingenious strike, it is not possible to + keep two running video sources connected at the same time + to Composite and S-VHS input! + + mode 0 - N/C + mode 1 - S-Video Y + mode 2 - noise or something I don't know + mode 3 - Composite and S-Video C + mode 4 - N/C + mode 5 - S-Video (gain C independently selectable of gain Y) + mode 6 - N/C + mode 7 - S-Video (gain C adapted to gain Y) + */ + +#define MAJOR_VERSION 1 /* driver major version */ +#define MINOR_VERSION 0 /* driver minor version */ + +#define BUZ_NAME "Iomega BUZ V-1.0" /* name of the driver */ + +#define DEBUG(x) /* Debug driver */ +#define IDEBUG(x) /* Debug interrupt handler */ +#define IOCTL_DEBUG(x) + + +/* The parameters for this driver */ + +/* + The video mem address of the video card. + The driver has a little database for some videocards + to determine it from there. If your video card is not in there + you have either to give it to the driver as a parameter + or set in in a VIDIOCSFBUF ioctl + */ + +static unsigned long vidmem = 0; /* Video memory base address */ + +/* Special purposes only: */ + +static int triton = 0; /* 0=no, 1=yes */ +static int natoma = 0; /* 0=no, 1=yes */ + +/* + Number and size of grab buffers for Video 4 Linux + The vast majority of applications should not need more than 2, + the very popular BTTV driver actually does ONLY have 2. + Time sensitive applications might need more, the maximum + is VIDEO_MAX_FRAME (defined in <linux/videodev.h>). + + The size is set so that the maximum possible request + can be satisfied. Decrease it, if bigphys_area alloc'd + memory is low. If you don't have the bigphys_area patch, + set it to 128 KB. Will you allow only to grab small + images with V4L, but that's better than nothing. + + v4l_bufsize has to be given in KB ! + + */ + +static int v4l_nbufs = 2; +static int v4l_bufsize = 128; /* Everybody should be able to work with this setting */ + +/* + Default input and video norm at startup of the driver. + */ + +static int default_input = 0; /* 0=Composite, 1=S-VHS */ +static int default_norm = 0; /* 0=PAL, 1=NTSC */ + +MODULE_PARM(vidmem, "i"); +MODULE_PARM(triton, "i"); +MODULE_PARM(natoma, "i"); +MODULE_PARM(v4l_nbufs, "i"); +MODULE_PARM(v4l_bufsize, "i"); +MODULE_PARM(default_input, "i"); +MODULE_PARM(default_norm, "i"); + +/* Anybody who uses more than four? */ +#define BUZ_MAX 4 + +static int zoran_num; /* number of Buzs in use */ +static struct zoran zoran[BUZ_MAX]; + +/* forward references */ + +static void v4l_fbuffer_free(struct zoran *zr); +static void jpg_fbuffer_free(struct zoran *zr); +static void zoran_feed_stat_com(struct zoran *zr); + + + +/* + * Allocate the V4L grab buffers + * + * These have to be pysically contiguous. + * If v4l_bufsize <= MAX_KMALLOC_MEM we use kmalloc + */ + +static int v4l_fbuffer_alloc(struct zoran *zr) +{ + int i, off; + unsigned char *mem; + + for (i = 0; i < v4l_nbufs; i++) { + if (zr->v4l_gbuf[i].fbuffer) + printk(KERN_WARNING "%s: v4l_fbuffer_alloc: buffer %d allready allocated ?\n", zr->name, i); + + if (v4l_bufsize <= MAX_KMALLOC_MEM) { + /* Use kmalloc */ + + mem = (unsigned char *) kmalloc(v4l_bufsize, GFP_KERNEL); + if (mem == 0) { + printk(KERN_ERR "%s: kmalloc for V4L bufs failed\n", zr->name); + v4l_fbuffer_free(zr); + return -ENOBUFS; + } + zr->v4l_gbuf[i].fbuffer = mem; + zr->v4l_gbuf[i].fbuffer_phys = virt_to_phys(mem); + zr->v4l_gbuf[i].fbuffer_bus = virt_to_bus(mem); + for (off = 0; off < v4l_bufsize; off += PAGE_SIZE) + mem_map_reserve(virt_to_page(mem + off)); + DEBUG(printk(BUZ_INFO ": V4L frame %d mem 0x%x (bus: 0x%x=%d)\n", i, mem, virt_to_bus(mem), virt_to_bus(mem))); + } else { + return -ENOBUFS; + } + } + + return 0; +} + +/* free the V4L grab buffers */ +static void v4l_fbuffer_free(struct zoran *zr) +{ + int i, off; + unsigned char *mem; + + for (i = 0; i < v4l_nbufs; i++) { + if (!zr->v4l_gbuf[i].fbuffer) + continue; + + mem = zr->v4l_gbuf[i].fbuffer; + for (off = 0; off < v4l_bufsize; off += PAGE_SIZE) + mem_map_unreserve(virt_to_page(mem + off)); + kfree((void *) zr->v4l_gbuf[i].fbuffer); + zr->v4l_gbuf[i].fbuffer = NULL; + } +} + +/* + * Allocate the MJPEG grab buffers. + * + * If the requested buffer size is smaller than MAX_KMALLOC_MEM, + * kmalloc is used to request a physically contiguous area, + * else we allocate the memory in framgents with get_free_page. + * + * If a Natoma chipset is present and this is a revision 1 zr36057, + * each MJPEG buffer needs to be physically contiguous. + * (RJ: This statement is from Dave Perks' original driver, + * I could never check it because I have a zr36067) + * The driver cares about this because it reduces the buffer + * size to MAX_KMALLOC_MEM in that case (which forces contiguous allocation). + * + * RJ: The contents grab buffers needs never be accessed in the driver. + * Therefore there is no need to allocate them with vmalloc in order + * to get a contiguous virtual memory space. + * I don't understand why many other drivers first allocate them with + * vmalloc (which uses internally also get_free_page, but delivers you + * virtual addresses) and then again have to make a lot of efforts + * to get the physical address. + * + */ + +static int jpg_fbuffer_alloc(struct zoran *zr) +{ + int i, j, off, alloc_contig; + unsigned long mem; + + /* Decide if we should alloc contiguous or fragmented memory */ + /* This has to be identical in jpg_fbuffer_alloc and jpg_fbuffer_free */ + + alloc_contig = (zr->jpg_bufsize < MAX_KMALLOC_MEM); + + for (i = 0; i < zr->jpg_nbufs; i++) { + if (zr->jpg_gbuf[i].frag_tab) + printk(KERN_WARNING "%s: jpg_fbuffer_alloc: buffer %d allready allocated ???\n", zr->name, i); + + /* Allocate fragment table for this buffer */ + + mem = get_free_page(GFP_KERNEL); + if (mem == 0) { + printk(KERN_ERR "%s: jpg_fbuffer_alloc: get_free_page (frag_tab) failed for buffer %d\n", zr->name, i); + jpg_fbuffer_free(zr); + return -ENOBUFS; + } + memset((void *) mem, 0, PAGE_SIZE); + zr->jpg_gbuf[i].frag_tab = (u32 *) mem; + zr->jpg_gbuf[i].frag_tab_bus = virt_to_bus((void *) mem); + + if (alloc_contig) { + mem = (unsigned long) kmalloc(zr->jpg_bufsize, GFP_KERNEL); + if (mem == 0) { + jpg_fbuffer_free(zr); + return -ENOBUFS; + } + zr->jpg_gbuf[i].frag_tab[0] = virt_to_bus((void *) mem); + zr->jpg_gbuf[i].frag_tab[1] = ((zr->jpg_bufsize / 4) << 1) | 1; + for (off = 0; off < zr->jpg_bufsize; off += PAGE_SIZE) + mem_map_reserve(virt_to_page(mem + off)); + } else { + /* jpg_bufsize is alreay page aligned */ + for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) { + mem = get_free_page(GFP_KERNEL); + if (mem == 0) { + jpg_fbuffer_free(zr); + return -ENOBUFS; + } + zr->jpg_gbuf[i].frag_tab[2 * j] = virt_to_bus((void *) mem); + zr->jpg_gbuf[i].frag_tab[2 * j + 1] = (PAGE_SIZE / 4) << 1; + mem_map_reserve(virt_to_page(mem)); + } + + zr->jpg_gbuf[i].frag_tab[2 * j - 1] |= 1; + } + } + + DEBUG(printk("jpg_fbuffer_alloc: %d KB allocated\n", + (zr->jpg_nbufs * zr->jpg_bufsize) >> 10)); + zr->jpg_buffers_allocated = 1; + return 0; +} + +/* free the MJPEG grab buffers */ +static void jpg_fbuffer_free(struct zoran *zr) +{ + int i, j, off, alloc_contig; + unsigned char *mem; + + /* Decide if we should alloc contiguous or fragmented memory */ + /* This has to be identical in jpg_fbuffer_alloc and jpg_fbuffer_free */ + + alloc_contig = (zr->jpg_bufsize < MAX_KMALLOC_MEM); + + for (i = 0; i < zr->jpg_nbufs; i++) { + if (!zr->jpg_gbuf[i].frag_tab) + continue; + + if (alloc_contig) { + if (zr->jpg_gbuf[i].frag_tab[0]) { + mem = (unsigned char *) bus_to_virt(zr->jpg_gbuf[i].frag_tab[0]); + for (off = 0; off < zr->jpg_bufsize; off += PAGE_SIZE) + mem_map_unreserve(virt_to_page(mem + off)); + kfree((void *) mem); + zr->jpg_gbuf[i].frag_tab[0] = 0; + zr->jpg_gbuf[i].frag_tab[1] = 0; + } + } else { + for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) { + if (!zr->jpg_gbuf[i].frag_tab[2 * j]) + break; + mem_map_unreserve(virt_to_page(bus_to_virt(zr->jpg_gbuf[i].frag_tab[2 * j]))); + free_page((unsigned long) bus_to_virt(zr->jpg_gbuf[i].frag_tab[2 * j])); + zr->jpg_gbuf[i].frag_tab[2 * j] = 0; + zr->jpg_gbuf[i].frag_tab[2 * j + 1] = 0; + } + } + + free_page((unsigned long) zr->jpg_gbuf[i].frag_tab); + zr->jpg_gbuf[i].frag_tab = NULL; + } + zr->jpg_buffers_allocated = 0; +} + + +/* ----------------------------------------------------------------------- */ + +/* I2C functions */ + +#define I2C_DELAY 10 + + +/* software I2C functions */ + +static void i2c_setlines(struct i2c_bus *bus, int ctrl, int data) +{ + struct zoran *zr = (struct zoran *) bus->data; + btwrite((data << 1) | ctrl, ZR36057_I2CBR); + btread(ZR36057_I2CBR); + udelay(I2C_DELAY); +} + +static int i2c_getdataline(struct i2c_bus *bus) +{ + struct zoran *zr = (struct zoran *) bus->data; + return (btread(ZR36057_I2CBR) >> 1) & 1; +} + +static void attach_inform(struct i2c_bus *bus, int id) +{ + DEBUG(struct zoran *zr = (struct zoran *) bus->data); + DEBUG(printk(BUZ_DEBUG "-%u: i2c attach %02x\n", zr->id, id)); +} + +static void detach_inform(struct i2c_bus *bus, int id) +{ + DEBUG(struct zoran *zr = (struct zoran *) bus->data); + DEBUG(printk(BUZ_DEBUG "-%u: i2c detach %02x\n", zr->id, id)); +} + +static struct i2c_bus zoran_i2c_bus_template = +{ + "zr36057", + I2C_BUSID_BT848, + NULL, + + SPIN_LOCK_UNLOCKED, + + attach_inform, + detach_inform, + + i2c_setlines, + i2c_getdataline, + NULL, + NULL, +}; + + +/* ----------------------------------------------------------------------- */ + +static void GPIO(struct zoran *zr, unsigned bit, unsigned value) +{ + u32 reg; + u32 mask; + + mask = 1 << (24 + bit); + reg = btread(ZR36057_GPPGCR1) & ~mask; + if (value) { + reg |= mask; + } + btwrite(reg, ZR36057_GPPGCR1); + /* Stop any PCI posting on the GPIO bus */ + btread(ZR36057_I2CBR); +} + + +/* + * Set the registers for the size we have specified. Don't bother + * trying to understand this without the ZR36057 manual in front of + * you [AC]. + * + * PS: The manual is free for download in .pdf format from + * www.zoran.com - nicely done those folks. + */ + +struct tvnorm { + u16 Wt, Wa, Ht, Ha, HStart, VStart; +}; + +static struct tvnorm tvnorms[] = +{ + /* PAL-BDGHI */ + {864, 720, 625, 576, 31, 16}, + /* NTSC */ + {858, 720, 525, 480, 21, 8}, +}; +#define TVNORMS (sizeof(tvnorms) / sizeof(tvnorm)) + +static int format2bpp(int format) +{ + int bpp; + + /* Determine the number of bytes per pixel for the video format requested */ + + switch (format) { + + case VIDEO_PALETTE_YUV422: + bpp = 2; + break; + + case VIDEO_PALETTE_RGB555: + bpp = 2; + break; + + case VIDEO_PALETTE_RGB565: + bpp = 2; + break; + + case VIDEO_PALETTE_RGB24: + bpp = 3; + break; + + case VIDEO_PALETTE_RGB32: + bpp = 4; + break; + + default: + bpp = 0; + } + + return bpp; +} + +/* + * set geometry + */ +static void zr36057_set_vfe(struct zoran *zr, int video_width, int video_height, + unsigned int video_format) +{ + struct tvnorm *tvn; + unsigned HStart, HEnd, VStart, VEnd; + unsigned DispMode; + unsigned VidWinWid, VidWinHt; + unsigned hcrop1, hcrop2, vcrop1, vcrop2; + unsigned Wa, We, Ha, He; + unsigned X, Y, HorDcm, VerDcm; + u32 reg; + unsigned mask_line_size; + + if (zr->params.norm < 0 || zr->params.norm > 1) { + printk(KERN_ERR "%s: set_vfe: video_norm = %d not valid\n", zr->name, zr->params.norm); + return; + } + if (video_width < BUZ_MIN_WIDTH || video_height < BUZ_MIN_HEIGHT) { + printk(KERN_ERR "%s: set_vfe: w=%d h=%d not valid\n", zr->name, video_width, video_height); + return; + } + tvn = &tvnorms[zr->params.norm]; + + Wa = tvn->Wa; + Ha = tvn->Ha; + + /* if window has more than half of active height, + switch on interlacing - we want the full information */ + + zr->video_interlace = (video_height > Ha / 2); + +/**** zr36057 ****/ + + /* horizontal */ + VidWinWid = video_width; + X = (VidWinWid * 64 + tvn->Wa - 1) / tvn->Wa; + We = (VidWinWid * 64) / X; + HorDcm = 64 - X; + hcrop1 = 2 * ((tvn->Wa - We) / 4); + hcrop2 = tvn->Wa - We - hcrop1; + HStart = tvn->HStart | 1; + HEnd = HStart + tvn->Wa - 1; + HStart += hcrop1; + HEnd -= hcrop2; + reg = ((HStart & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HStart) + | ((HEnd & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HEnd); + reg |= ZR36057_VFEHCR_HSPol; + btwrite(reg, ZR36057_VFEHCR); + + /* Vertical */ + DispMode = !zr->video_interlace; + VidWinHt = DispMode ? video_height : video_height / 2; + Y = (VidWinHt * 64 * 2 + tvn->Ha - 1) / tvn->Ha; + He = (VidWinHt * 64) / Y; + VerDcm = 64 - Y; + vcrop1 = (tvn->Ha / 2 - He) / 2; + vcrop2 = tvn->Ha / 2 - He - vcrop1; + VStart = tvn->VStart; + VEnd = VStart + tvn->Ha / 2 - 1; + VStart += vcrop1; + VEnd -= vcrop2; + reg = ((VStart & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VStart) + | ((VEnd & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VEnd); + reg |= ZR36057_VFEVCR_VSPol; + btwrite(reg, ZR36057_VFEVCR); + + /* scaler and pixel format */ + reg = 0 // ZR36057_VFESPFR_ExtFl /* Trying to live without ExtFl */ + | (HorDcm << ZR36057_VFESPFR_HorDcm) + | (VerDcm << ZR36057_VFESPFR_VerDcm) + | (DispMode << ZR36057_VFESPFR_DispMode) + | ZR36057_VFESPFR_LittleEndian; + /* RJ: I don't know, why the following has to be the opposite + of the corresponding ZR36060 setting, but only this way + we get the correct colors when uncompressing to the screen */ + reg |= ZR36057_VFESPFR_VCLKPol; + /* RJ: Don't know if that is needed for NTSC also */ + reg |= ZR36057_VFESPFR_TopField; + switch (video_format) { + + case VIDEO_PALETTE_YUV422: + reg |= ZR36057_VFESPFR_YUV422; + break; + + case VIDEO_PALETTE_RGB555: + reg |= ZR36057_VFESPFR_RGB555 | ZR36057_VFESPFR_ErrDif; + break; + + case VIDEO_PALETTE_RGB565: + reg |= ZR36057_VFESPFR_RGB565 | ZR36057_VFESPFR_ErrDif; + break; + + case VIDEO_PALETTE_RGB24: + reg |= ZR36057_VFESPFR_RGB888 | ZR36057_VFESPFR_Pack24; + break; + + case VIDEO_PALETTE_RGB32: + reg |= ZR36057_VFESPFR_RGB888; + break; + + default: + printk(KERN_INFO "%s: Unknown color_fmt=%x\n", zr->name, video_format); + return; + + } + if (HorDcm >= 48) { + reg |= 3 << ZR36057_VFESPFR_HFilter; /* 5 tap filter */ + } else if (HorDcm >= 32) { + reg |= 2 << ZR36057_VFESPFR_HFilter; /* 4 tap filter */ + } else if (HorDcm >= 16) { + reg |= 1 << ZR36057_VFESPFR_HFilter; /* 3 tap filter */ + } + btwrite(reg, ZR36057_VFESPFR); + + /* display configuration */ + + reg = (16 << ZR36057_VDCR_MinPix) + | (VidWinHt << ZR36057_VDCR_VidWinHt) + | (VidWinWid << ZR36057_VDCR_VidWinWid); + if (triton) + reg &= ~ZR36057_VDCR_Triton; + else + reg |= ZR36057_VDCR_Triton; + btwrite(reg, ZR36057_VDCR); + + /* Write overlay clipping mask data, but don't enable overlay clipping */ + /* RJ: since this makes only sense on the screen, we use + zr->window.width instead of video_width */ + + mask_line_size = (BUZ_MAX_WIDTH + 31) / 32; + reg = virt_to_bus(zr->overlay_mask); + btwrite(reg, ZR36057_MMTR); + reg = virt_to_bus(zr->overlay_mask + mask_line_size); + btwrite(reg, ZR36057_MMBR); + reg = mask_line_size - (zr->window.width + 31) / 32; + if (DispMode == 0) + reg += mask_line_size; + reg <<= ZR36057_OCR_MaskStride; + btwrite(reg, ZR36057_OCR); + +} + +/* + * Switch overlay on or off + */ + +static void zr36057_overlay(struct zoran *zr, int on) +{ + int fmt, bpp; + u32 reg; + + if (on) { + /* do the necessary settings ... */ + + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); /* switch it off first */ + + switch (zr->buffer.depth) { + case 15: + fmt = VIDEO_PALETTE_RGB555; + bpp = 2; + break; + case 16: + fmt = VIDEO_PALETTE_RGB565; + bpp = 2; + break; + case 24: + fmt = VIDEO_PALETTE_RGB24; + bpp = 3; + break; + case 32: + fmt = VIDEO_PALETTE_RGB32; + bpp = 4; + break; + default: + fmt = 0; + bpp = 0; + } + + zr36057_set_vfe(zr, zr->window.width, zr->window.height, fmt); + + /* Start and length of each line MUST be 4-byte aligned. + This should be allready checked before the call to this routine. + All error messages are internal driver checking only! */ + + /* video display top and bottom registers */ + + reg = (u32) zr->buffer.base + + zr->window.x * bpp + + zr->window.y * zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDTR); + if (reg & 3) + printk(KERN_ERR "%s: zr36057_overlay: video_address not aligned\n", zr->name); + if (zr->video_interlace) + reg += zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + + reg = zr->buffer.bytesperline - zr->window.width * bpp; + if (zr->video_interlace) + reg += zr->buffer.bytesperline; + if (reg & 3) + printk(KERN_ERR "%s: zr36057_overlay: video_stride not aligned\n", zr->name); + reg = (reg << ZR36057_VSSFGR_DispStride); + reg |= ZR36057_VSSFGR_VidOvf; /* clear overflow status */ + btwrite(reg, ZR36057_VSSFGR); + + /* Set overlay clipping */ + + if (zr->window.clipcount) + btor(ZR36057_OCR_OvlEnable, ZR36057_OCR); + + /* ... and switch it on */ + + btor(ZR36057_VDCR_VidEn, ZR36057_VDCR); + } else { + /* Switch it off */ + + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); + } +} + +/* + * The overlay mask has one bit for each pixel on a scan line, + * and the maximum window size is BUZ_MAX_WIDTH * BUZ_MAX_HEIGHT pixels. + */ +static void write_overlay_mask(struct zoran *zr, struct video_clip *vp, int count) +{ + unsigned mask_line_size = (BUZ_MAX_WIDTH + 31) / 32; + u32 *mask; + int x, y, width, height; + unsigned i, j, k; + u32 reg; + + /* fill mask with one bits */ + memset(zr->overlay_mask, ~0, mask_line_size * 4 * BUZ_MAX_HEIGHT); + reg = 0; + + for (i = 0; i < count; ++i) { + /* pick up local copy of clip */ + x = vp[i].x; + y = vp[i].y; + width = vp[i].width; + height = vp[i].height; + + /* trim clips that extend beyond the window */ + if (x < 0) { + width += x; + x = 0; + } + if (y < 0) { + height += y; + y = 0; + } + if (x + width > zr->window.width) { + width = zr->window.width - x; + } + if (y + height > zr->window.height) { + height = zr->window.height - y; + } + /* ignore degenerate clips */ + if (height <= 0) { + continue; + } + if (width <= 0) { + continue; + } + /* apply clip for each scan line */ + for (j = 0; j < height; ++j) { + /* reset bit for each pixel */ + /* this can be optimized later if need be */ + mask = zr->overlay_mask + (y + j) * mask_line_size; + for (k = 0; k < width; ++k) { + mask[(x + k) / 32] &= ~((u32) 1 << (x + k) % 32); + } + } + } +} + +/* Enable/Disable uncompressed memory grabbing of the 36057 */ + +static void zr36057_set_memgrab(struct zoran *zr, int mode) +{ + if (mode) { + if (btread(ZR36057_VSSFGR) & (ZR36057_VSSFGR_SnapShot | ZR36057_VSSFGR_FrameGrab)) + printk(KERN_WARNING "%s: zr36057_set_memgrab_on with SnapShot or FrameGrab on ???\n", zr->name); + + /* switch on VSync interrupts */ + + btwrite(IRQ_MASK, ZR36057_ISR); // Clear Interrupts + + btor(ZR36057_ICR_GIRQ0, ZR36057_ICR); + + /* enable SnapShot */ + + btor(ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR); + + /* Set zr36057 video front end and enable video */ + +#ifdef XAWTV_HACK + zr36057_set_vfe(zr, zr->gwidth > 720 ? 720 : zr->gwidth, zr->gheight, zr->gformat); +#else + zr36057_set_vfe(zr, zr->gwidth, zr->gheight, zr->gformat); +#endif + + zr->v4l_memgrab_active = 1; + } else { + zr->v4l_memgrab_active = 0; + + /* switch off VSync interrupts */ + + btand(~ZR36057_ICR_GIRQ0, ZR36057_ICR); + + /* reenable grabbing to screen if it was running */ + + if (zr->v4l_overlay_active) { + zr36057_overlay(zr, 1); + } else { + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); + btand(~ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR); + } + } +} + +static int wait_grab_pending(struct zoran *zr) +{ + unsigned long flags; + + /* wait until all pending grabs are finished */ + + if (!zr->v4l_memgrab_active) + return 0; + + while (zr->v4l_pend_tail != zr->v4l_pend_head) { + interruptible_sleep_on(&zr->v4l_capq); + if (signal_pending(current)) + return -ERESTARTSYS; + } + + spin_lock_irqsave(&zr->lock, flags); + zr36057_set_memgrab(zr, 0); + spin_unlock_irqrestore(&zr->lock, flags); + + return 0; +} + +/* + * V4L Buffer grabbing + */ + +static int v4l_grab(struct zoran *zr, struct video_mmap *mp) +{ + unsigned long flags; + int res, bpp; + + /* + * There is a long list of limitations to what is allowed to be grabbed + * We don't output error messages her, since some programs (e.g. xawtv) + * just try several settings to find out what is valid or not. + */ + + /* No grabbing outside the buffer range! */ + + if (mp->frame >= v4l_nbufs || mp->frame < 0) + return -EINVAL; + + /* Check size and format of the grab wanted */ + + if (mp->height < BUZ_MIN_HEIGHT || mp->width < BUZ_MIN_WIDTH) + return -EINVAL; + if (mp->height > BUZ_MAX_HEIGHT || mp->width > BUZ_MAX_WIDTH) + return -EINVAL; + + bpp = format2bpp(mp->format); + if (bpp == 0) + return -EINVAL; + + /* Check against available buffer size */ + + if (mp->height * mp->width * bpp > v4l_bufsize) + return -EINVAL; + + /* The video front end needs 4-byte alinged line sizes */ + + if ((bpp == 2 && (mp->width & 1)) || (bpp == 3 && (mp->width & 3))) + return -EINVAL; + + /* + * To minimize the time spent in the IRQ routine, we avoid setting up + * the video front end there. + * If this grab has different parameters from a running streaming capture + * we stop the streaming capture and start it over again. + */ + + if (zr->v4l_memgrab_active && + (zr->gwidth != mp->width || zr->gheight != mp->height || zr->gformat != mp->format)) { + res = wait_grab_pending(zr); + if (res) + return res; + } + zr->gwidth = mp->width; + zr->gheight = mp->height; + zr->gformat = mp->format; + zr->gbpl = bpp * zr->gwidth; + + + spin_lock_irqsave(&zr->lock, flags); + + /* make sure a grab isn't going on currently with this buffer */ + + switch (zr->v4l_gbuf[mp->frame].state) { + + default: + case BUZ_STATE_PEND: + res = -EBUSY; /* what are you doing? */ + break; + + case BUZ_STATE_USER: + case BUZ_STATE_DONE: + /* since there is at least one unused buffer there's room for at least one more pend[] entry */ + zr->v4l_pend[zr->v4l_pend_head++ & V4L_MASK_FRAME] = mp->frame; + zr->v4l_gbuf[mp->frame].state = BUZ_STATE_PEND; + res = 0; + break; + + } + + /* put the 36057 into frame grabbing mode */ + + if (!res && !zr->v4l_memgrab_active) + zr36057_set_memgrab(zr, 1); + + spin_unlock_irqrestore(&zr->lock, flags); + + return res; +} + +/* + * Sync on a V4L buffer + */ + +static int v4l_sync(struct zoran *zr, int frame) +{ + unsigned long flags; + + + /* check passed-in frame number */ + if (frame >= v4l_nbufs || frame < 0) { + printk(KERN_ERR "%s: v4l_sync: frame %d is invalid\n", zr->name, frame); + return -EINVAL; + } + /* Check if is buffer was queued at all */ + + if (zr->v4l_gbuf[frame].state == BUZ_STATE_USER) { +// printk(KERN_ERR "%s: v4l_sync: Trying to sync on a buffer which was not queued?\n", zr->name); + return -EINVAL; + } + /* wait on this buffer to get ready */ + + while (zr->v4l_gbuf[frame].state == BUZ_STATE_PEND) { + interruptible_sleep_on(&zr->v4l_capq); + if (signal_pending(current)) + return -ERESTARTSYS; + } + + /* buffer should now be in BUZ_STATE_DONE */ + + if (zr->v4l_gbuf[frame].state != BUZ_STATE_DONE) + printk(KERN_ERR "%s: v4l_sync - internal error\n", zr->name); + + /* Check if streaming capture has finished */ + + spin_lock_irqsave(&zr->lock, flags); + + if (zr->v4l_pend_tail == zr->v4l_pend_head) + zr36057_set_memgrab(zr, 0); + + spin_unlock_irqrestore(&zr->lock, flags); + + return 0; +} +/***************************************************************************** + * * + * Set up the Buz-specific MJPEG part * + * * + *****************************************************************************/ + +/* + * Wait til post office is no longer busy + */ + +static int post_office_wait(struct zoran *zr) +{ + u32 por; + u32 ct=0; + + while (((por = btread(ZR36057_POR)) & (ZR36057_POR_POPen | ZR36057_POR_POTime)) == ZR36057_POR_POPen) { + ct++; + if(ct>100000) + { + printk(KERN_ERR "%s: timeout on post office.\n", zr->name); + return -1; + } + /* wait for something to happen */ + } + if ((por & ZR36057_POR_POPen) != 0) { + printk(KERN_WARNING "%s: pop pending %08x\n", zr->name, por); + return -1; + } + if ((por & (ZR36057_POR_POTime | ZR36057_POR_POPen)) != 0) { + printk(KERN_WARNING "%s: pop timeout %08x\n", zr->name, por); + return -1; + } + return 0; +} + +static int post_office_write(struct zoran *zr, unsigned guest, unsigned reg, unsigned value) +{ + u32 por; + + post_office_wait(zr); + por = ZR36057_POR_PODir | ZR36057_POR_POTime | ((guest & 7) << 20) | ((reg & 7) << 16) | (value & 0xFF); + btwrite(por, ZR36057_POR); + return post_office_wait(zr); +} + +static int post_office_read(struct zoran *zr, unsigned guest, unsigned reg) +{ + u32 por; + + post_office_wait(zr); + por = ZR36057_POR_POTime | ((guest & 7) << 20) | ((reg & 7) << 16); + btwrite(por, ZR36057_POR); + if (post_office_wait(zr) < 0) { + return -1; + } + return btread(ZR36057_POR) & 0xFF; +} + +static int zr36060_write_8(struct zoran *zr, unsigned reg, unsigned val) +{ + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg)) { + return -1; + } + return post_office_write(zr, 0, 3, val); +} + +static int zr36060_write_16(struct zoran *zr, unsigned reg, unsigned val) +{ + if (zr36060_write_8(zr, reg + 0, val >> 8)) { + return -1; + } + return zr36060_write_8(zr, reg + 1, val >> 0); +} + +static int zr36060_write_24(struct zoran *zr, unsigned reg, unsigned val) +{ + if (zr36060_write_8(zr, reg + 0, val >> 16)) { + return -1; + } + return zr36060_write_16(zr, reg + 1, val >> 0); +} + +static int zr36060_write_32(struct zoran *zr, unsigned reg, unsigned val) +{ + if (zr36060_write_16(zr, reg + 0, val >> 16)) { + return -1; + } + return zr36060_write_16(zr, reg + 2, val >> 0); +} + +static u32 zr36060_read_8(struct zoran *zr, unsigned reg) +{ + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg)) { + return -1; + } + return post_office_read(zr, 0, 3) & 0xFF; +} + +static int zr36060_reset(struct zoran *zr) +{ + return post_office_write(zr, 3, 0, 0); +} + +static void zr36060_sleep(struct zoran *zr, int sleep) +{ + GPIO(zr, 1, !sleep); +} + + +static void zr36060_set_jpg(struct zoran *zr, enum zoran_codec_mode mode) +{ + struct tvnorm *tvn; + u32 reg; + int size; + + reg = (1 << 0) /* CodeMstr */ + |(0 << 2) /* CFIS=0 */ + |(0 << 6) /* Endian=0 */ + |(0 << 7); /* Code16=0 */ + zr36060_write_8(zr, 0x002, reg); + + switch (mode) { + + case BUZ_MODE_MOTION_DECOMPRESS: + case BUZ_MODE_STILL_DECOMPRESS: + reg = 0x00; /* Codec mode = decompression */ + break; + + case BUZ_MODE_MOTION_COMPRESS: + case BUZ_MODE_STILL_COMPRESS: + default: + reg = 0xa4; /* Codec mode = compression with variable scale factor */ + break; + + } + zr36060_write_8(zr, 0x003, reg); + + reg = 0x00; /* reserved, mbz */ + zr36060_write_8(zr, 0x004, reg); + + reg = 0xff; /* 510 bits/block */ + zr36060_write_8(zr, 0x005, reg); + + /* JPEG markers */ + reg = (zr->params.jpeg_markers) & 0x38; /* DRI, DQT, DHT */ + if (zr->params.COM_len) + reg |= JPEG_MARKER_COM; + if (zr->params.APP_len) + reg |= JPEG_MARKER_APP; + zr36060_write_8(zr, 0x006, reg); + + reg = (0 << 3) /* DATERR=0 */ + |(0 << 2) /* END=0 */ + |(0 << 1) /* EOI=0 */ + |(0 << 0); /* EOAV=0 */ + zr36060_write_8(zr, 0x007, reg); + + /* code volume */ + + /* Target field size in pixels: */ + tvn = &tvnorms[zr->params.norm]; + size = (tvn->Ha / 2) * (tvn->Wa) / (zr->params.HorDcm) / (zr->params.VerDcm); + + /* Target compressed field size in bits: */ + size = size * 16; /* uncompressed size in bits */ + size = size * zr->params.quality / 400; /* quality = 100 is a compression ratio 1:4 */ + + /* Lower limit (arbitrary, 1 KB) */ + if (size < 8192) + size = 8192; + + /* Upper limit: 7/8 of the code buffers */ + if (size * zr->params.field_per_buff > zr->jpg_bufsize * 7) + size = zr->jpg_bufsize * 7 / zr->params.field_per_buff; + + reg = size; + zr36060_write_32(zr, 0x009, reg); + + /* how do we set initial SF as a function of quality parameter? */ + reg = 0x0100; /* SF=1.0 */ + zr36060_write_16(zr, 0x011, reg); + + reg = 0x00ffffff; /* AF=max */ + zr36060_write_24(zr, 0x013, reg); + + reg = 0x0000; /* test */ + zr36060_write_16(zr, 0x024, reg); +} + +static void zr36060_set_video(struct zoran *zr, enum zoran_codec_mode mode) +{ + struct tvnorm *tvn; + u32 reg; + + reg = (0 << 7) /* Video8=0 */ + |(0 << 6) /* Range=0 */ + |(0 << 3) /* FlDet=0 */ + |(1 << 2) /* FlVedge=1 */ + |(0 << 1) /* FlExt=0 */ + |(0 << 0); /* SyncMstr=0 */ + + /* According to ZR36067 documentation, FlDet should correspond + to the odd_even flag of the ZR36067 */ + if (zr->params.odd_even) + reg |= (1 << 3); + + if (mode != BUZ_MODE_STILL_DECOMPRESS) { + /* limit pixels to range 16..235 as per CCIR-601 */ + reg |= (1 << 6); /* Range=1 */ + } + zr36060_write_8(zr, 0x030, reg); + + reg = (0 << 7) /* VCLKPol=0 */ + |(0 << 6) /* PValPol=0 */ + |(1 << 5) /* PoePol=1 */ + |(0 << 4) /* SImgPol=0 */ + |(0 << 3) /* BLPol=0 */ + |(0 << 2) /* FlPol=0 */ + |(0 << 1) /* HSPol=0, sync on falling edge */ + |(1 << 0); /* VSPol=1 */ + zr36060_write_8(zr, 0x031, reg); + + switch (zr->params.HorDcm) { + default: + case 1: + reg = (0 << 0); + break; /* HScale = 0 */ + + case 2: + reg = (1 << 0); + break; /* HScale = 1 */ + + case 4: + reg = (2 << 0); + break; /* HScale = 2 */ + } + if (zr->params.VerDcm == 2) + reg |= (1 << 2); + zr36060_write_8(zr, 0x032, reg); + + reg = 0x80; /* BackY */ + zr36060_write_8(zr, 0x033, reg); + + reg = 0xe0; /* BackU */ + zr36060_write_8(zr, 0x034, reg); + + reg = 0xe0; /* BackV */ + zr36060_write_8(zr, 0x035, reg); + + /* sync generator */ + + tvn = &tvnorms[zr->params.norm]; + + reg = tvn->Ht - 1; /* Vtotal */ + zr36060_write_16(zr, 0x036, reg); + + reg = tvn->Wt - 1; /* Htotal */ + zr36060_write_16(zr, 0x038, reg); + + reg = 6 - 1; /* VsyncSize */ + zr36060_write_8(zr, 0x03a, reg); + + reg = 100 - 1; /* HsyncSize */ + zr36060_write_8(zr, 0x03b, reg); + + reg = tvn->VStart - 1; /* BVstart */ + zr36060_write_8(zr, 0x03c, reg); + + reg += tvn->Ha / 2; /* BVend */ + zr36060_write_16(zr, 0x03e, reg); + + reg = tvn->HStart - 1; /* BHstart */ + zr36060_write_8(zr, 0x03d, reg); + + reg += tvn->Wa; /* BHend */ + zr36060_write_16(zr, 0x040, reg); + + /* active area */ + reg = zr->params.img_y + tvn->VStart; /* Vstart */ + zr36060_write_16(zr, 0x042, reg); + + reg += zr->params.img_height; /* Vend */ + zr36060_write_16(zr, 0x044, reg); + + reg = zr->params.img_x + tvn->HStart; /* Hstart */ + zr36060_write_16(zr, 0x046, reg); + + reg += zr->params.img_width; /* Hend */ + zr36060_write_16(zr, 0x048, reg); + + /* subimage area */ + reg = zr->params.img_y + tvn->VStart; /* SVstart */ + zr36060_write_16(zr, 0x04a, reg); + + reg += zr->params.img_height; /* SVend */ + zr36060_write_16(zr, 0x04c, reg); + + reg = zr->params.img_x + tvn->HStart; /* SHstart */ + zr36060_write_16(zr, 0x04e, reg); + + reg += zr->params.img_width; /* SHend */ + zr36060_write_16(zr, 0x050, reg); +} + +static void zr36060_set_jpg_SOF(struct zoran *zr) +{ + u32 reg; + + + reg = 0xffc0; /* SOF marker */ + zr36060_write_16(zr, 0x060, reg); + + reg = 17; /* SOF length */ + zr36060_write_16(zr, 0x062, reg); + + reg = 8; /* precision 8 bits */ + zr36060_write_8(zr, 0x064, reg); + + reg = zr->params.img_height / zr->params.VerDcm; /* image height */ + zr36060_write_16(zr, 0x065, reg); + + reg = zr->params.img_width / zr->params.HorDcm; /* image width */ + zr36060_write_16(zr, 0x067, reg); + + reg = 3; /* 3 color components */ + zr36060_write_8(zr, 0x069, reg); + + reg = 0x002100; /* Y component */ + zr36060_write_24(zr, 0x06a, reg); + + reg = 0x011101; /* U component */ + zr36060_write_24(zr, 0x06d, reg); + + reg = 0x021101; /* V component */ + zr36060_write_24(zr, 0x070, reg); +} + +static void zr36060_set_jpg_SOS(struct zoran *zr) +{ + u32 reg; + + + reg = 0xffda; /* SOS marker */ + zr36060_write_16(zr, 0x07a, reg); + + reg = 12; /* SOS length */ + zr36060_write_16(zr, 0x07c, reg); + + reg = 3; /* 3 color components */ + zr36060_write_8(zr, 0x07e, reg); + + reg = 0x0000; /* Y component */ + zr36060_write_16(zr, 0x07f, reg); + + reg = 0x0111; /* U component */ + zr36060_write_16(zr, 0x081, reg); + + reg = 0x0211; /* V component */ + zr36060_write_16(zr, 0x083, reg); + + reg = 0x003f00; /* Start, end spectral scans */ + zr36060_write_24(zr, 0x085, reg); +} + +static void zr36060_set_jpg_DRI(struct zoran *zr) +{ + u32 reg; + + + reg = 0xffdd; /* DRI marker */ + zr36060_write_16(zr, 0x0c0, reg); + + reg = 4; /* DRI length */ + zr36060_write_16(zr, 0x0c2, reg); + + reg = 8; /* length in MCUs */ + zr36060_write_16(zr, 0x0c4, reg); +} + +static void zr36060_set_jpg_DQT(struct zoran *zr) +{ + unsigned i; + unsigned adr; + static const u8 dqt[] = + { + 0xff, 0xdb, /* DHT marker */ + 0x00, 0x84, /* DHT length */ + 0x00, /* table ID 0 */ + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, + 0x01, /* table ID 1 */ + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 + }; + + /* write fixed quantitization tables */ + adr = 0x0cc; + for (i = 0; i < sizeof(dqt); ++i) { + zr36060_write_8(zr, adr++, dqt[i]); + } +} + +static void zr36060_set_jpg_DHT(struct zoran *zr) +{ + unsigned i; + unsigned adr; + static const u8 dht[] = + { + 0xff, 0xc4, /* DHT marker */ + 0x01, 0xa2, /* DHT length */ + 0x00, /* table class 0, ID 0 */ + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, /* # codes of length 1..8 */ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* # codes of length 8..16 */ + 0x00, /* values for codes of length 2 */ + 0x01, 0x02, 0x03, 0x04, 0x05, /* values for codes of length 3 */ + 0x06, /* values for codes of length 4 */ + 0x07, /* values for codes of length 5 */ + 0x08, /* values for codes of length 6 */ + 0x09, /* values for codes of length 7 */ + 0x0a, /* values for codes of length 8 */ + 0x0b, /* values for codes of length 9 */ + 0x01, /* table class 0, ID 1 */ + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* # codes of length 1..8 */ + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, /* # codes of length 9..16 */ + 0x00, 0x01, 0x02, /* values for codes of length 2 */ + 0x03, /* values for codes of length 3 */ + 0x04, /* values for codes of length 4 */ + 0x05, /* values for codes of length 5 */ + 0x06, /* values for codes of length 6 */ + 0x07, /* values for codes of length 7 */ + 0x08, /* values for codes of length 8 */ + 0x09, /* values for codes of length 9 */ + 0x0a, /* values for codes of length 10 */ + 0x0b, /* values for codes of length 11 */ + 0x10, + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, + 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, + 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, + 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, + 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, + 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }; + + /* write fixed Huffman tables */ + adr = 0x1d4; + for (i = 0; i < sizeof(dht); ++i) { + zr36060_write_8(zr, adr++, dht[i]); + } +} + +static void zr36060_set_jpg_APP(struct zoran *zr) +{ + unsigned adr; + int len, i; + u32 reg; + + + len = zr->params.APP_len; + if (len < 0) + len = 0; + if (len > 60) + len = 60; + + i = zr->params.APPn; + if (i < 0) + i = 0; + if (i > 15) + i = 15; + + reg = 0xffe0 + i; /* APPn marker */ + zr36060_write_16(zr, 0x380, reg); + + reg = len + 2; /* APPn len */ + zr36060_write_16(zr, 0x382, reg); + + /* write APPn data */ + adr = 0x384; + for (i = 0; i < 60; i++) { + zr36060_write_8(zr, adr++, (i < len ? zr->params.APP_data[i] : 0)); + } +} + +static void zr36060_set_jpg_COM(struct zoran *zr) +{ + unsigned adr; + int len, i; + u32 reg; + + + len = zr->params.COM_len; + if (len < 0) + len = 0; + if (len > 60) + len = 60; + + reg = 0xfffe; /* COM marker */ + zr36060_write_16(zr, 0x3c0, reg); + + reg = len + 2; /* COM len */ + zr36060_write_16(zr, 0x3c2, reg); + + /* write COM data */ + adr = 0x3c4; + for (i = 0; i < 60; i++) { + zr36060_write_8(zr, adr++, (i < len ? zr->params.COM_data[i] : 0)); + } +} + +static void zr36060_set_cap(struct zoran *zr, enum zoran_codec_mode mode) +{ + unsigned i; + u32 reg; + + zr36060_reset(zr); + mdelay(10); + + reg = (0 << 7) /* Load=0 */ + |(1 << 0); /* SynRst=1 */ + zr36060_write_8(zr, 0x000, reg); + + zr36060_set_jpg(zr, mode); + zr36060_set_video(zr, mode); + zr36060_set_jpg_SOF(zr); + zr36060_set_jpg_SOS(zr); + zr36060_set_jpg_DRI(zr); + zr36060_set_jpg_DQT(zr); + zr36060_set_jpg_DHT(zr); + zr36060_set_jpg_APP(zr); + zr36060_set_jpg_COM(zr); + + reg = (1 << 7) /* Load=1 */ + |(0 << 0); /* SynRst=0 */ + zr36060_write_8(zr, 0x000, reg); + + /* wait for codec to unbusy */ + for (i = 0; i < 1000; ++i) { + reg = zr36060_read_8(zr, 0x001); + if ((reg & (1 << 7)) == 0) { + DEBUG(printk(KERN_DEBUG "060: loaded, loops=%u\n", i)); + return; + } + udelay(1000); + } + printk(KERN_INFO "060: stuck busy, statux=%02x\n", reg); +} + +static void zr36057_set_jpg(struct zoran *zr, enum zoran_codec_mode mode) +{ + struct tvnorm *tvn; + u32 reg; + int i; + + tvn = &tvnorms[zr->params.norm]; + + /* assert P_Reset */ + btwrite(0, ZR36057_JPC); + + /* re-initialize DMA ring stuff */ + zr->jpg_que_head = 0; + zr->jpg_dma_head = 0; + zr->jpg_dma_tail = 0; + zr->jpg_que_tail = 0; + zr->jpg_seq_num = 0; + for (i = 0; i < BUZ_NUM_STAT_COM; ++i) { + zr->stat_com[i] = 1; /* mark as unavailable to zr36057 */ + } + for (i = 0; i < zr->jpg_nbufs; i++) { + zr->jpg_gbuf[i].state = BUZ_STATE_USER; /* nothing going on */ + } + + /* MJPEG compression mode */ + switch (mode) { + + case BUZ_MODE_MOTION_COMPRESS: + default: + reg = ZR36057_JMC_MJPGCmpMode; + break; + + case BUZ_MODE_MOTION_DECOMPRESS: + reg = ZR36057_JMC_MJPGExpMode; + reg |= ZR36057_JMC_SyncMstr; + /* RJ: The following is experimental - improves the output to screen */ + if (zr->params.VFIFO_FB) + reg |= ZR36057_JMC_VFIFO_FB; + break; + + case BUZ_MODE_STILL_COMPRESS: + reg = ZR36057_JMC_JPGCmpMode; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + reg = ZR36057_JMC_JPGExpMode; + break; + + } + reg |= ZR36057_JMC_JPG; + if (zr->params.field_per_buff == 1) + reg |= ZR36057_JMC_Fld_per_buff; + btwrite(reg, ZR36057_JMC); + + /* vertical */ + btor(ZR36057_VFEVCR_VSPol, ZR36057_VFEVCR); + reg = (6 << ZR36057_VSP_VsyncSize) | (tvn->Ht << ZR36057_VSP_FrmTot); + btwrite(reg, ZR36057_VSP); + reg = ((zr->params.img_y + tvn->VStart) << ZR36057_FVAP_NAY) + | (zr->params.img_height << ZR36057_FVAP_PAY); + btwrite(reg, ZR36057_FVAP); + + /* horizontal */ + btor(ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR); + reg = ((tvn->Wt - 100) << ZR36057_HSP_HsyncStart) | (tvn->Wt << ZR36057_HSP_LineTot); + btwrite(reg, ZR36057_HSP); + reg = ((zr->params.img_x + tvn->HStart) << ZR36057_FHAP_NAX) + | (zr->params.img_width << ZR36057_FHAP_PAX); + btwrite(reg, ZR36057_FHAP); + + /* field process parameters */ + if (zr->params.odd_even) + reg = ZR36057_FPP_Odd_Even; + else + reg = 0; + btwrite(reg, ZR36057_FPP); + + /* Set proper VCLK Polarity, else colors will be wrong during playback */ + btor(ZR36057_VFESPFR_VCLKPol, ZR36057_VFESPFR); + + /* code base address and FIFO threshold */ + reg = virt_to_bus(zr->stat_com); + btwrite(reg, ZR36057_JCBA); + reg = 0x50; + btwrite(reg, ZR36057_JCFT); + + /* JPEG codec guest ID */ + reg = (1 << ZR36057_JCGI_JPEGuestID) | (0 << ZR36057_JCGI_JPEGuestReg); + btwrite(reg, ZR36057_JCGI); + + /* Code transfer guest ID */ + reg = (0 << ZR36057_MCTCR_CodGuestID) | (3 << ZR36057_MCTCR_CodGuestReg); + reg |= ZR36057_MCTCR_CFlush; + btwrite(reg, ZR36057_MCTCR); + + /* deassert P_Reset */ + btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC); +} + +static void zr36057_enable_jpg(struct zoran *zr, enum zoran_codec_mode mode) +{ + static int zero = 0; + static int one = 1; + + switch (mode) { + + case BUZ_MODE_MOTION_COMPRESS: + zr36060_set_cap(zr, mode); + zr36057_set_jpg(zr, mode); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &one); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &zero); + + /* deassert P_Reset, assert Code transfer enable */ + btwrite(IRQ_MASK, ZR36057_ISR); + btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + break; + + case BUZ_MODE_MOTION_DECOMPRESS: + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &zero); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &one); + zr36060_set_cap(zr, mode); + zr36057_set_jpg(zr, mode); + + /* deassert P_Reset, assert Code transfer enable */ + btwrite(IRQ_MASK, ZR36057_ISR); + btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + break; + + case BUZ_MODE_IDLE: + default: + /* shut down processing */ + btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC); + btand(~ZR36057_JMC_VFIFO_FB, ZR36057_JMC); + btand(~ZR36057_JMC_SyncMstr, ZR36057_JMC); + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + btwrite(0, ZR36057_ISR); + zr36060_reset(zr); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_ENABLE_OUTPUT, &one); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_INPUT, &zero); + break; + + } + zr->codec_mode = mode; +} + +/* + * Queue a MJPEG buffer for capture/playback + */ + +static int jpg_qbuf(struct zoran *zr, int frame, enum zoran_codec_mode mode) +{ + unsigned long flags; + int res; + + /* Check if buffers are allocated */ + + if (!zr->jpg_buffers_allocated) { + printk(KERN_ERR "%s: jpg_qbuf: buffers not yet allocated\n", zr->name); + return -ENOMEM; + } + /* Does the user want to stop streaming? */ + + if (frame < 0) { + if (zr->codec_mode == mode) { + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + return 0; + } else { + printk(KERN_ERR "%s: jpg_qbuf - stop streaming but not in streaming mode\n", zr->name); + return -EINVAL; + } + } + /* No grabbing outside the buffer range! */ + + if (frame >= zr->jpg_nbufs) { + printk(KERN_ERR "%s: jpg_qbuf: buffer %d out of range\n", zr->name, frame); + return -EINVAL; + } + /* what is the codec mode right now? */ + + if (zr->codec_mode == BUZ_MODE_IDLE) { + /* Ok load up the zr36060 and go */ + zr36057_enable_jpg(zr, mode); + } else if (zr->codec_mode != mode) { + /* wrong codec mode active - invalid */ + printk(KERN_ERR "%s: jpg_qbuf - codec in wrong mode\n", zr->name); + return -EINVAL; + } + spin_lock_irqsave(&zr->lock, flags); + + /* make sure a grab isn't going on currently with this buffer */ + + switch (zr->jpg_gbuf[frame].state) { + + default: + case BUZ_STATE_DMA: + case BUZ_STATE_PEND: + case BUZ_STATE_DONE: + res = -EBUSY; /* what are you doing? */ + break; + + case BUZ_STATE_USER: + /* since there is at least one unused buffer there's room for at least one more pend[] entry */ + zr->jpg_pend[zr->jpg_que_head++ & BUZ_MASK_FRAME] = frame; + zr->jpg_gbuf[frame].state = BUZ_STATE_PEND; + zoran_feed_stat_com(zr); + res = 0; + break; + + } + + spin_unlock_irqrestore(&zr->lock, flags); + + /* Start the zr36060 when the first frame is queued */ + if (zr->jpg_que_head == 1) { + btor(ZR36057_JMC_Go_en, ZR36057_JMC); + btwrite(ZR36057_JPC_P_Reset | ZR36057_JPC_CodTrnsEn | ZR36057_JPC_Active, ZR36057_JPC); + } + return res; +} + +/* + * Sync on a MJPEG buffer + */ + +static int jpg_sync(struct zoran *zr, struct zoran_sync *bs) +{ + unsigned long flags; + int frame; + + if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS && + zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) { + return -EINVAL; + } + while (zr->jpg_que_tail == zr->jpg_dma_tail) { + interruptible_sleep_on(&zr->jpg_capq); + if (signal_pending(current)) + return -ERESTARTSYS; + } + + spin_lock_irqsave(&zr->lock, flags); + + frame = zr->jpg_pend[zr->jpg_que_tail++ & BUZ_MASK_FRAME]; + + /* buffer should now be in BUZ_STATE_DONE */ + + if (zr->jpg_gbuf[frame].state != BUZ_STATE_DONE) + printk(KERN_ERR "%s: jpg_sync - internal error\n", zr->name); + + *bs = zr->jpg_gbuf[frame].bs; + zr->jpg_gbuf[frame].state = BUZ_STATE_USER; + + spin_unlock_irqrestore(&zr->lock, flags); + + return 0; +} + +/* when this is called the spinlock must be held */ +static void zoran_feed_stat_com(struct zoran *zr) +{ + /* move frames from pending queue to DMA */ + + int frame, i, max_stat_com; + + max_stat_com = (zr->params.TmpDcm == 1) ? BUZ_NUM_STAT_COM : (BUZ_NUM_STAT_COM >> 1); + + while ((zr->jpg_dma_head - zr->jpg_dma_tail) < max_stat_com + && zr->jpg_dma_head != zr->jpg_que_head) { + + frame = zr->jpg_pend[zr->jpg_dma_head & BUZ_MASK_FRAME]; + if (zr->params.TmpDcm == 1) { + /* fill 1 stat_com entry */ + i = zr->jpg_dma_head & BUZ_MASK_STAT_COM; + zr->stat_com[i] = zr->jpg_gbuf[frame].frag_tab_bus; + } else { + /* fill 2 stat_com entries */ + i = (zr->jpg_dma_head & 1) * 2; + zr->stat_com[i] = zr->jpg_gbuf[frame].frag_tab_bus; + zr->stat_com[i + 1] = zr->jpg_gbuf[frame].frag_tab_bus; + } + zr->jpg_gbuf[frame].state = BUZ_STATE_DMA; + zr->jpg_dma_head++; + + } +} + +/* when this is called the spinlock must be held */ +static void zoran_reap_stat_com(struct zoran *zr) +{ + /* move frames from DMA queue to done queue */ + + int i; + u32 stat_com; + unsigned int seq; + unsigned int dif; + int frame; + struct zoran_gbuffer *gbuf; + + /* In motion decompress we don't have a hardware frame counter, + we just count the interrupts here */ + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) + zr->jpg_seq_num++; + + while (zr->jpg_dma_tail != zr->jpg_dma_head) { + if (zr->params.TmpDcm == 1) + i = zr->jpg_dma_tail & BUZ_MASK_STAT_COM; + else + i = (zr->jpg_dma_tail & 1) * 2 + 1; + + stat_com = zr->stat_com[i]; + + if ((stat_com & 1) == 0) { + return; + } + frame = zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME]; + gbuf = &zr->jpg_gbuf[frame]; + get_fast_time(&gbuf->bs.timestamp); + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + gbuf->bs.length = (stat_com & 0x7fffff) >> 1; + + /* update sequence number with the help of the counter in stat_com */ + + seq = stat_com >> 24; + dif = (seq - zr->jpg_seq_num) & 0xff; + zr->jpg_seq_num += dif; + } else { + gbuf->bs.length = 0; + } + gbuf->bs.seq = zr->params.TmpDcm == 2 ? (zr->jpg_seq_num >> 1) : zr->jpg_seq_num; + gbuf->state = BUZ_STATE_DONE; + + zr->jpg_dma_tail++; + } +} + +static void zoran_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 stat, astat; + int count; + struct zoran *zr; + unsigned long flags; + + zr = (struct zoran *) dev_id; + count = 0; + + spin_lock_irqsave(&zr->lock, flags); + while (1) { + /* get/clear interrupt status bits */ + stat = btread(ZR36057_ISR); + astat = stat & IRQ_MASK; + if (!astat) { + break; + } + btwrite(astat, ZR36057_ISR); + IDEBUG(printk(BUZ_DEBUG "-%u: astat %08x stat %08x\n", zr->id, astat, stat)); + +#if (IRQ_MASK & ZR36057_ISR_GIRQ0) + if (astat & ZR36057_ISR_GIRQ0) { + + /* Interrupts may still happen when zr->v4l_memgrab_active is switched off. + We simply ignore them */ + + if (zr->v4l_memgrab_active) { + +/* A lot more checks should be here ... */ + if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_SnapShot) == 0) + printk(KERN_WARNING "%s: BuzIRQ with SnapShot off ???\n", zr->name); + + if (zr->v4l_grab_frame != NO_GRAB_ACTIVE) { + /* There is a grab on a frame going on, check if it has finished */ + + if ((btread(ZR36057_VSSFGR) & ZR36057_VSSFGR_FrameGrab) == 0) { + /* it is finished, notify the user */ + + zr->v4l_gbuf[zr->v4l_grab_frame].state = BUZ_STATE_DONE; + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_grab_seq++; + zr->v4l_pend_tail++; + } + } + if (zr->v4l_grab_frame == NO_GRAB_ACTIVE) + wake_up_interruptible(&zr->v4l_capq); + + /* Check if there is another grab queued */ + + if (zr->v4l_grab_frame == NO_GRAB_ACTIVE && + zr->v4l_pend_tail != zr->v4l_pend_head) { + + int frame = zr->v4l_pend[zr->v4l_pend_tail & V4L_MASK_FRAME]; + u32 reg; + + zr->v4l_grab_frame = frame; + + /* Set zr36057 video front end and enable video */ + + /* Buffer address */ + + reg = zr->v4l_gbuf[frame].fbuffer_bus; + btwrite(reg, ZR36057_VDTR); + if (zr->video_interlace) + reg += zr->gbpl; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + +#ifdef XAWTV_HACK + reg = (zr->gwidth > 720) ? ((zr->gwidth & ~3) - 720) * zr->gbpl / zr->gwidth : 0; +#else + reg = 0; +#endif + if (zr->video_interlace) + reg += zr->gbpl; + reg = (reg << ZR36057_VSSFGR_DispStride); + reg |= ZR36057_VSSFGR_VidOvf; + reg |= ZR36057_VSSFGR_SnapShot; + reg |= ZR36057_VSSFGR_FrameGrab; + btwrite(reg, ZR36057_VSSFGR); + + btor(ZR36057_VDCR_VidEn, ZR36057_VDCR); + } + } + } +#endif /* (IRQ_MASK & ZR36057_ISR_GIRQ0) */ + +#if (IRQ_MASK & ZR36057_ISR_GIRQ1) + if (astat & ZR36057_ISR_GIRQ1) { + unsigned csr = zr36060_read_8(zr, 0x001); + unsigned isr = zr36060_read_8(zr, 0x008); + + IDEBUG(printk(KERN_DEBUG "%s: ZR36057_ISR_GIRQ1 60_code=%02x 60_intr=%02x\n", + zr->name, csr, isr)); + + btand(~ZR36057_ICR_GIRQ1, ZR36057_ICR); + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + } +#endif /* (IRQ_MASK & ZR36057_ISR_GIRQ1) */ + +#if (IRQ_MASK & ZR36057_ISR_CodRepIRQ) + if (astat & ZR36057_ISR_CodRepIRQ) { + IDEBUG(printk(KERN_DEBUG "%s: ZR36057_ISR_CodRepIRQ\n", zr->name)); + btand(~ZR36057_ICR_CodRepIRQ, ZR36057_ICR); + } +#endif /* (IRQ_MASK & ZR36057_ISR_CodRepIRQ) */ + +#if (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) + if ((astat & ZR36057_ISR_JPEGRepIRQ) && + (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS)) { + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + } +#endif /* (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) */ + + count++; + if (count > 10) { + printk(KERN_WARNING "%s: irq loop %d\n", zr->name, count); + if (count > 20) { + btwrite(0, ZR36057_ICR); + printk(KERN_ERR "%s: IRQ lockup, cleared int mask\n", zr->name); + break; + } + } + } + spin_unlock_irqrestore(&zr->lock, flags); +} + +/* Check a zoran_params struct for correctness, insert default params */ + +static int zoran_check_params(struct zoran *zr, struct zoran_params *params) +{ + int err = 0, err0 = 0; + + /* insert constant params */ + + params->major_version = MAJOR_VERSION; + params->minor_version = MINOR_VERSION; + + /* Check input and norm */ + + if (params->input != 0 && params->input != 1) { + err++; + } + if (params->norm != VIDEO_MODE_PAL && params->norm != VIDEO_MODE_NTSC) { + err++; + } + /* Check decimation, set default values for decimation = 1, 2, 4 */ + + switch (params->decimation) { + case 1: + + params->HorDcm = 1; + params->VerDcm = 1; + params->TmpDcm = 1; + params->field_per_buff = 2; + + params->img_x = 0; + params->img_y = 0; + params->img_width = 720; + params->img_height = tvnorms[params->norm].Ha / 2; + break; + + case 2: + + params->HorDcm = 2; + params->VerDcm = 1; + params->TmpDcm = 2; + params->field_per_buff = 1; + + params->img_x = 8; + params->img_y = 0; + params->img_width = 704; + params->img_height = tvnorms[params->norm].Ha / 2; + break; + + case 4: + + params->HorDcm = 4; + params->VerDcm = 2; + params->TmpDcm = 2; + params->field_per_buff = 1; + + params->img_x = 8; + params->img_y = 0; + params->img_width = 704; + params->img_height = tvnorms[params->norm].Ha / 2; + break; + + case 0: + + /* We have to check the data the user has set */ + + if (params->HorDcm != 1 && params->HorDcm != 2 && params->HorDcm != 4) + err0++; + if (params->VerDcm != 1 && params->VerDcm != 2) + err0++; + if (params->TmpDcm != 1 && params->TmpDcm != 2) + err0++; + if (params->field_per_buff != 1 && params->field_per_buff != 2) + err0++; + + if (params->img_x < 0) + err0++; + if (params->img_y < 0) + err0++; + if (params->img_width < 0) + err0++; + if (params->img_height < 0) + err0++; + if (params->img_x + params->img_width > 720) + err0++; + if (params->img_y + params->img_height > tvnorms[params->norm].Ha / 2) + err0++; + if (params->img_width % (16 * params->HorDcm) != 0) + err0++; + if (params->img_height % (8 * params->VerDcm) != 0) + err0++; + + if (err0) { + err++; + } + break; + + default: + err++; + break; + } + + if (params->quality > 100) + params->quality = 100; + if (params->quality < 5) + params->quality = 5; + + if (params->APPn < 0) + params->APPn = 0; + if (params->APPn > 15) + params->APPn = 15; + if (params->APP_len < 0) + params->APP_len = 0; + if (params->APP_len > 60) + params->APP_len = 60; + if (params->COM_len < 0) + params->COM_len = 0; + if (params->COM_len > 60) + params->COM_len = 60; + + if (err) + return -EINVAL; + + return 0; + +} +static void zoran_open_init_params(struct zoran *zr) +{ + int i; + + /* Per default, map the V4L Buffers */ + + zr->map_mjpeg_buffers = 0; + + /* User must explicitly set a window */ + + zr->window_set = 0; + + zr->window.x = 0; + zr->window.y = 0; + zr->window.width = 0; + zr->window.height = 0; + zr->window.chromakey = 0; + zr->window.flags = 0; + zr->window.clips = NULL; + zr->window.clipcount = 0; + + zr->video_interlace = 0; + + zr->v4l_memgrab_active = 0; + zr->v4l_overlay_active = 0; + + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_grab_seq = 0; + + zr->gwidth = 0; + zr->gheight = 0; + zr->gformat = 0; + zr->gbpl = 0; + + /* DMA ring stuff for V4L */ + + zr->v4l_pend_tail = 0; + zr->v4l_pend_head = 0; + for (i = 0; i < v4l_nbufs; i++) { + zr->v4l_gbuf[i].state = BUZ_STATE_USER; /* nothing going on */ + } + + /* Set necessary params and call zoran_check_params to set the defaults */ + + zr->params.decimation = 1; + + zr->params.quality = 50; /* default compression factor 8 */ + zr->params.odd_even = 1; + + zr->params.APPn = 0; + zr->params.APP_len = 0; /* No APPn marker */ + for (i = 0; i < 60; i++) + zr->params.APP_data[i] = 0; + + zr->params.COM_len = 0; /* No COM marker */ + for (i = 0; i < 60; i++) + zr->params.COM_data[i] = 0; + + zr->params.VFIFO_FB = 0; + + memset(zr->params.reserved, 0, sizeof(zr->params.reserved)); + + zr->params.jpeg_markers = JPEG_MARKER_DHT | JPEG_MARKER_DQT; + + i = zoran_check_params(zr, &zr->params); + if (i) + printk(KERN_ERR "%s: zoran_open_init_params internal error\n", zr->name); +} + +/* + * Open a buz card. Right now the flags stuff is just playing + */ + +static int zoran_open(struct video_device *dev, int flags) +{ + struct zoran *zr = (struct zoran *) dev; + + DEBUG(printk(KERN_INFO ": zoran_open\n")); + + switch (flags) { + + case 0: + if (zr->user) + return -EBUSY; + zr->user++; + + if (v4l_fbuffer_alloc(zr) < 0) { + zr->user--; + return -ENOMEM; + } + /* default setup */ + + zoran_open_init_params(zr); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + + btwrite(IRQ_MASK, ZR36057_ISR); // Clears interrupts + + btor(ZR36057_ICR_IntPinEn, ZR36057_ICR); + + break; + + default: + return -EBUSY; + + } + MOD_INC_USE_COUNT; + return 0; +} + +static void zoran_close(struct video_device *dev) +{ + struct zoran *zr = (struct zoran *) dev; + + DEBUG(printk(KERN_INFO ": zoran_close\n")); + + /* disable interrupts */ + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + + /* wake up sleeping beauties */ + wake_up_interruptible(&zr->v4l_capq); + wake_up_interruptible(&zr->jpg_capq); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr36057_set_memgrab(zr, 0); + if (zr->v4l_overlay_active) + zr36057_overlay(zr, 0); + + zr->user--; + + v4l_fbuffer_free(zr); + jpg_fbuffer_free(zr); + zr->jpg_nbufs = 0; + + MOD_DEC_USE_COUNT; + DEBUG(printk(KERN_INFO ": zoran_close done\n")); +} + + +static long zoran_read(struct video_device *dev, char *buf, unsigned long count, int nonblock) +{ + return -EINVAL; +} + +static long zoran_write(struct video_device *dev, const char *buf, unsigned long count, int nonblock) +{ + return -EINVAL; +} + +/* + * ioctl routine + */ + + +static int zoran_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct zoran *zr = (struct zoran *) dev; + + switch (cmd) { + + case VIDIOCGCAP: + { + struct video_capability b; + IOCTL_DEBUG(printk("buz ioctl VIDIOCGCAP\n")); + strncpy(b.name, zr->video_dev.name, sizeof(b.name)); + b.type = VID_TYPE_CAPTURE | + VID_TYPE_OVERLAY | + VID_TYPE_CLIPPING | + VID_TYPE_FRAMERAM | + VID_TYPE_SCALES; + /* theoretically we could also flag VID_TYPE_SUBCAPTURE + but this is not even implemented in the BTTV driver */ + + b.channels = 2; /* composite, svhs */ + b.audios = 0; + b.maxwidth = BUZ_MAX_WIDTH; + b.maxheight = BUZ_MAX_HEIGHT; + b.minwidth = BUZ_MIN_WIDTH; + b.minheight = BUZ_MIN_HEIGHT; + if (copy_to_user(arg, &b, sizeof(b))) { + return -EFAULT; + } + return 0; + } + + case VIDIOCGCHAN: + { + struct video_channel v; + + if (copy_from_user(&v, arg, sizeof(v))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCGCHAN for channel %d\n", v.channel)); + switch (v.channel) { + case 0: + strcpy(v.name, "Composite"); + break; + case 1: + strcpy(v.name, "SVHS"); + break; + default: + return -EINVAL; + } + v.tuners = 0; + v.flags = 0; + v.type = VIDEO_TYPE_CAMERA; + v.norm = zr->params.norm; + if (copy_to_user(arg, &v, sizeof(v))) { + return -EFAULT; + } + return 0; + } + + /* RJ: the documentation at http://roadrunner.swansea.linux.org.uk/v4lapi.shtml says: + + * "The VIDIOCSCHAN ioctl takes an integer argument and switches the capture to this input." + * ^^^^^^^ + * The famos BTTV driver has it implemented with a struct video_channel argument + * and we follow it for compatibility reasons + * + * BTW: this is the only way the user can set the norm! + */ + + case VIDIOCSCHAN: + { + struct video_channel v; + int input; + int on, res; + + if (copy_from_user(&v, arg, sizeof(v))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCSCHAN: channel=%d, norm=%d\n", v.channel, v.norm)); + switch (v.channel) { + case 0: + input = 3; + break; + case 1: + input = 7; + break; + default: + return -EINVAL; + } + + if (v.norm != VIDEO_MODE_PAL + && v.norm != VIDEO_MODE_NTSC) { + return -EINVAL; + } + zr->params.norm = v.norm; + zr->params.input = v.channel; + + /* We switch overlay off and on since a change in the norm + needs different VFE settings */ + + on = zr->v4l_overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm); + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + if (res) + return res; + + return 0; + } + + case VIDIOCGTUNER: + case VIDIOCSTUNER: + return -EINVAL; + + case VIDIOCGPICT: + { + struct video_picture p = zr->picture; + + IOCTL_DEBUG(printk("buz ioctl VIDIOCGPICT\n")); + p.depth = zr->buffer.depth; + switch (zr->buffer.depth) { + case 15: + p.palette = VIDEO_PALETTE_RGB555; + break; + + case 16: + p.palette = VIDEO_PALETTE_RGB565; + break; + + case 24: + p.palette = VIDEO_PALETTE_RGB24; + break; + + case 32: + p.palette = VIDEO_PALETTE_RGB32; + break; + } + + if (copy_to_user(arg, &p, sizeof(p))) { + return -EFAULT; + } + return 0; + } + + case VIDIOCSPICT: + { + struct video_picture p; + + if (copy_from_user(&p, arg, sizeof(p))) { + return -EFAULT; + } + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &p); + IOCTL_DEBUG(printk("buz ioctl VIDIOCSPICT bri=%d hue=%d col=%d con=%d dep=%d pal=%d\n", + p.brightness, p.hue, p.colour, p.contrast, p.depth, p.palette)); + /* The depth and palette values have no meaning to us, + should we return -EINVAL if they don't fit ? */ + zr->picture = p; + return 0; + } + + case VIDIOCCAPTURE: + { + int v, res; + + if (copy_from_user(&v, arg, sizeof(v))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCCAPTURE: %d\n", v)); + /* If there is nothing to do, return immediatly */ + + if ((v && zr->v4l_overlay_active) || (!v && !zr->v4l_overlay_active)) + return 0; + + if (v == 0) { + zr->v4l_overlay_active = 0; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + /* When a grab is running, the video simply won't be switched on any more */ + } else { + if (!zr->buffer_set || !zr->window_set) { + return -EINVAL; + } + zr->v4l_overlay_active = 1; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 1); + /* When a grab is running, the video will be switched on when grab is finished */ + } + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + if (res) + return res; + return 0; + } + + case VIDIOCGWIN: + { + IOCTL_DEBUG(printk("buz ioctl VIDIOCGWIN\n")); + if (copy_to_user(arg, &zr->window, sizeof(zr->window))) { + return -EFAULT; + } + return 0; + } + + case VIDIOCSWIN: + { + struct video_clip *vcp; + struct video_window vw; + int on, end, res; + + if (copy_from_user(&vw, arg, sizeof(vw))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCSWIN: x=%d y=%d w=%d h=%d clipcount=%d\n", vw.x, vw.y, vw.width, vw.height, vw.clipcount)); + if (!zr->buffer_set) { + return -EINVAL; + } + /* + * The video front end needs 4-byte alinged line sizes, we correct that + * silently here if necessary + */ + + if (zr->buffer.depth == 15 || zr->buffer.depth == 16) { + end = (vw.x + vw.width) & ~1; /* round down */ + vw.x = (vw.x + 1) & ~1; /* round up */ + vw.width = end - vw.x; + } + if (zr->buffer.depth == 24) { + end = (vw.x + vw.width) & ~3; /* round down */ + vw.x = (vw.x + 3) & ~3; /* round up */ + vw.width = end - vw.x; + } +#if 0 + // At least xawtv seems to care about the following - just leave it away + /* + * Also corrected silently (as long as window fits at all): + * video not fitting the screen + */ +#if 0 + if (vw.x < 0 || vw.y < 0 || vw.x + vw.width > zr->buffer.width || + vw.y + vw.height > zr->buffer.height) { + printk(BUZ_ERR ": VIDIOCSWIN: window does not fit frame buffer: %dx%d+%d*%d\n", + vw.width, vw.height, vw.x, vw.y); + return -EINVAL; + } +#else + if (vw.x < 0) + vw.x = 0; + if (vw.y < 0) + vw.y = 0; + if (vw.x + vw.width > zr->buffer.width) + vw.width = zr->buffer.width - vw.x; + if (vw.y + vw.height > zr->buffer.height) + vw.height = zr->buffer.height - vw.y; +#endif +#endif + + /* Check for vaild parameters */ + if (vw.width < BUZ_MIN_WIDTH || vw.height < BUZ_MIN_HEIGHT || + vw.width > BUZ_MAX_WIDTH || vw.height > BUZ_MAX_HEIGHT) { + return -EINVAL; + } +#ifdef XAWTV_HACK + if (vw.width > 720) + vw.width = 720; +#endif + + zr->window.x = vw.x; + zr->window.y = vw.y; + zr->window.width = vw.width; + zr->window.height = vw.height; + zr->window.chromakey = 0; + zr->window.flags = 0; // RJ: Is this intended for interlace on/off ? + + zr->window.clips = NULL; + zr->window.clipcount = vw.clipcount; + + /* + * If an overlay is running, we have to switch it off + * and switch it on again in order to get the new settings in effect. + * + * We also want to avoid that the overlay mask is written + * when an overlay is running. + */ + + on = zr->v4l_overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + /* + * Write the overlay mask if clips are wanted. + */ + if (vw.clipcount) { + vcp = vmalloc(sizeof(struct video_clip) * (vw.clipcount + 4)); + if (vcp == NULL) { + return -ENOMEM; + } + if (copy_from_user(vcp, vw.clips, sizeof(struct video_clip) * vw.clipcount)) { + vfree(vcp); + return -EFAULT; + } + write_overlay_mask(zr, vcp, vw.clipcount); + vfree(vcp); + } + if (on) + zr36057_overlay(zr, 1); + zr->window_set = 1; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + if (res) + return res; + + return 0; + } + + case VIDIOCGFBUF: + { + IOCTL_DEBUG(printk("buz ioctl VIDIOCGFBUF\n")); + if (copy_to_user(arg, &zr->buffer, sizeof(zr->buffer))) { + return -EFAULT; + } + return 0; + } + + case VIDIOCSFBUF: + { + struct video_buffer v; + + if (!capable(CAP_SYS_ADMIN) + || !capable(CAP_SYS_RAWIO)) + return -EPERM; + + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + IOCTL_DEBUG(printk("buz ioctl VIDIOCSFBUF: base=0x%x w=%d h=%d depth=%d bpl=%d\n", (u32) v.base, v.width, v.height, v.depth, v.bytesperline)); + if (zr->v4l_overlay_active) { + /* Has the user gotten crazy ... ? */ + return -EINVAL; + } + if (v.depth != 15 + && v.depth != 16 + && v.depth != 24 + && v.depth != 32) { + return -EINVAL; + } + if (v.height <= 0 || v.width <= 0 || v.bytesperline <= 0) { + return -EINVAL; + } + if (v.bytesperline & 3) { + return -EINVAL; + } + if (v.base) { + zr->buffer.base = (void *) ((unsigned long) v.base & ~3); + } + zr->buffer.height = v.height; + zr->buffer.width = v.width; + zr->buffer.depth = v.depth; + zr->buffer.bytesperline = v.bytesperline; + + if (zr->buffer.base) + zr->buffer_set = 1; + zr->window_set = 0; /* The user should set new window parameters */ + return 0; + } + + /* RJ: what is VIDIOCKEY intended to do ??? */ + + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + + case VIDIOCSYNC: + { + int v; + + if (copy_from_user(&v, arg, sizeof(v))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCSYNC %d\n", v)); + return v4l_sync(zr, v); + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + + if (copy_from_user((void *) &vm, (void *) arg, sizeof(vm))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl VIDIOCMCAPTURE frame=%d geom=%dx%d fmt=%d\n", + vm.frame, vm.height, vm.width, vm.format)); + return v4l_grab(zr, &vm); + } + + case VIDIOCGMBUF: + { + struct video_mbuf vm; + int i; + + IOCTL_DEBUG(printk("buz ioctl VIDIOCGMBUF\n")); + + vm.size = v4l_nbufs * v4l_bufsize; + vm.frames = v4l_nbufs; + for (i = 0; i < v4l_nbufs; i++) { + vm.offsets[i] = i * v4l_bufsize; + } + + /* The next mmap will map the V4L buffers */ + zr->map_mjpeg_buffers = 0; + + if (copy_to_user(arg, &vm, sizeof(vm))) { + return -EFAULT; + } + return 0; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + + IOCTL_DEBUG(printk("buz ioctl VIDIOCGUNIT\n")); + vu.video = zr->video_dev.minor; + vu.vbi = VIDEO_NO_UNIT; + vu.radio = VIDEO_NO_UNIT; + vu.audio = VIDEO_NO_UNIT; + vu.teletext = VIDEO_NO_UNIT; + if (copy_to_user(arg, &vu, sizeof(vu))) + return -EFAULT; + return 0; + } + + /* + * RJ: In principal we could support subcaptures for V4L grabbing. + * Not even the famous BTTV driver has them, however. + * If there should be a strong demand, one could consider + * to implement them. + */ + case VIDIOCGCAPTURE: + case VIDIOCSCAPTURE: + return -EINVAL; + + case BUZIOC_G_PARAMS: + { + IOCTL_DEBUG(printk("buz ioctl BUZIOC_G_PARAMS\n")); + if (copy_to_user(arg, &(zr->params), sizeof(zr->params))) + return -EFAULT; + return 0; + } + + case BUZIOC_S_PARAMS: + { + struct zoran_params bp; + int input, on; + + if (zr->codec_mode != BUZ_MODE_IDLE) { + return -EINVAL; + } + if (copy_from_user(&bp, arg, sizeof(bp))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl BUZIOC_S_PARAMS\n")); + + /* Check the params first before overwriting our internal values */ + + if (zoran_check_params(zr, &bp)) + return -EINVAL; + + zr->params = bp; + + /* Make changes of input and norm go into effect immediatly */ + + /* We switch overlay off and on since a change in the norm + needs different VFE settings */ + + on = zr->v4l_overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + input = zr->params.input == 0 ? 3 : 7; + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm); + + if (on) + zr36057_overlay(zr, 1); + + if (copy_to_user(arg, &bp, sizeof(bp))) { + return -EFAULT; + } + return 0; + } + + case BUZIOC_REQBUFS: + { + struct zoran_requestbuffers br; + + if (zr->jpg_buffers_allocated) { + return -EINVAL; + } + if (copy_from_user(&br, arg, sizeof(br))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl BUZIOC_REQBUFS count = %lu size=%lu\n", + br.count, br.size)); + /* Enforce reasonable lower and upper limits */ + if (br.count < 4) + br.count = 4; /* Could be choosen smaller */ + if (br.count > BUZ_MAX_FRAME) + br.count = BUZ_MAX_FRAME; + br.size = PAGE_ALIGN(br.size); + if (br.size < 8192) + br.size = 8192; /* Arbitrary */ + /* br.size is limited by 1 page for the stat_com tables to a Maximum of 2 MB */ + if (br.size > (512 * 1024)) + br.size = (512 * 1024); /* 512 K should be enough */ + if (zr->need_contiguous && br.size > MAX_KMALLOC_MEM) + br.size = MAX_KMALLOC_MEM; + + zr->jpg_nbufs = br.count; + zr->jpg_bufsize = br.size; + + if (jpg_fbuffer_alloc(zr)) + return -ENOMEM; + + /* The next mmap will map the MJPEG buffers */ + zr->map_mjpeg_buffers = 1; + + if (copy_to_user(arg, &br, sizeof(br))) { + return -EFAULT; + } + return 0; + } + + case BUZIOC_QBUF_CAPT: + { + int nb; + + if (copy_from_user((void *) &nb, (void *) arg, sizeof(int))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl BUZIOC_QBUF_CAPT %d\n", nb)); + return jpg_qbuf(zr, nb, BUZ_MODE_MOTION_COMPRESS); + } + + case BUZIOC_QBUF_PLAY: + { + int nb; + + if (copy_from_user((void *) &nb, (void *) arg, sizeof(int))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl BUZIOC_QBUF_PLAY %d\n", nb)); + return jpg_qbuf(zr, nb, BUZ_MODE_MOTION_DECOMPRESS); + } + + case BUZIOC_SYNC: + { + struct zoran_sync bs; + int res; + + IOCTL_DEBUG(printk("buz ioctl BUZIOC_SYNC\n")); + res = jpg_sync(zr, &bs); + if (copy_to_user(arg, &bs, sizeof(bs))) { + return -EFAULT; + } + return res; + } + + case BUZIOC_G_STATUS: + { + struct zoran_status bs; + int norm, input, status; + + if (zr->codec_mode != BUZ_MODE_IDLE) { + return -EINVAL; + } + if (copy_from_user(&bs, arg, sizeof(bs))) { + return -EFAULT; + } + IOCTL_DEBUG(printk("buz ioctl BUZIOC_G_STATUS\n")); + switch (bs.input) { + case 0: + input = 3; + break; + case 1: + input = 7; + break; + default: + return -EINVAL; + } + + /* Set video norm to VIDEO_MODE_AUTO */ + + norm = VIDEO_MODE_AUTO; + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &norm); + + /* sleep 1 second */ + + schedule_timeout(HZ); + + /* Get status of video decoder */ + + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_GET_STATUS, &status); + bs.signal = (status & DECODER_STATUS_GOOD) ? 1 : 0; + bs.norm = (status & DECODER_STATUS_NTSC) ? VIDEO_MODE_NTSC : VIDEO_MODE_PAL; + bs.color = (status & DECODER_STATUS_COLOR) ? 1 : 0; + + /* restore previous input and norm */ + input = zr->params.input == 0 ? 3 : 7; + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &input); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm); + + if (copy_to_user(arg, &bs, sizeof(bs))) { + return -EFAULT; + } + return 0; + } + + default: + return -ENOIOCTLCMD; + + } + return 0; +} + + +/* + * This maps the buffers to user space. + * + * Depending on the state of zr->map_mjpeg_buffers + * the V4L or the MJPEG buffers are mapped + * + */ + +static int zoran_mmap(struct video_device *dev, const char *adr, unsigned long size) +{ + struct zoran *zr = (struct zoran *) dev; + unsigned long start = (unsigned long) adr; + unsigned long page, pos, todo, fraglen; + int i, j; + + if (zr->map_mjpeg_buffers) { + /* Map the MJPEG buffers */ + + if (!zr->jpg_buffers_allocated) { + return -ENOMEM; + } + if (size > zr->jpg_nbufs * zr->jpg_bufsize) { + return -EINVAL; + } + + for (i = 0; i < zr->jpg_nbufs; i++) { + for (j = 0; j < zr->jpg_bufsize / PAGE_SIZE; j++) { + fraglen = (zr->jpg_gbuf[i].frag_tab[2 * j + 1] & ~1) << 1; + todo = size; + if (todo > fraglen) + todo = fraglen; + pos = (unsigned long) zr->jpg_gbuf[i].frag_tab[2 * j]; + page = virt_to_phys(bus_to_virt(pos)); /* should just be pos on i386 */ + if (remap_page_range(start, page, todo, PAGE_SHARED)) { + printk(KERN_ERR "%s: zoran_mmap(V4L): remap_page_range failed\n", zr->name); + return -EAGAIN; + } + size -= todo; + start += todo; + if (size == 0) + break; + if (zr->jpg_gbuf[i].frag_tab[2 * j + 1] & 1) + break; /* was last fragment */ + } + if (size == 0) + break; + } + } else { + /* Map the V4L buffers */ + + if (size > v4l_nbufs * v4l_bufsize) { + return -EINVAL; + } + + for (i = 0; i < v4l_nbufs; i++) { + todo = size; + if (todo > v4l_bufsize) + todo = v4l_bufsize; + page = zr->v4l_gbuf[i].fbuffer_phys; + DEBUG(printk("V4L remap page range %d 0x%x %d to 0x%x\n", i, page, todo, start)); + if (remap_page_range(start, page, todo, PAGE_SHARED)) { + printk(KERN_ERR "%s: zoran_mmap(V4L): remap_page_range failed\n", zr->name); + return -EAGAIN; + } + size -= todo; + start += todo; + if (size == 0) + break; + } + } + return 0; +} + +static int zoran_init_done(struct video_device *dev) +{ + return 0; +} + +static struct video_device zoran_template = +{ + BUZ_NAME, + VID_TYPE_CAPTURE | VID_TYPE_OVERLAY | VID_TYPE_CLIPPING | VID_TYPE_FRAMERAM | + VID_TYPE_SCALES | VID_TYPE_SUBCAPTURE, + VID_HARDWARE_ZR36067, + zoran_open, + zoran_close, + zoran_read, + zoran_write, + NULL, + zoran_ioctl, + zoran_mmap, + zoran_init_done, + NULL, + 0, + 0 +}; + +static int zr36057_init(int i) +{ + struct zoran *zr = &zoran[i]; + unsigned long mem; + unsigned mem_needed; + int j; + int rev; + + /* reset zr36057 */ + btwrite(0, ZR36057_SPGPPCR); + mdelay(10); + + /* default setup of all parameters which will persist beetween opens */ + + zr->user = 0; + + init_waitqueue_head(&zr->v4l_capq); + init_waitqueue_head(&zr->jpg_capq); + + zr->map_mjpeg_buffers = 0; /* Map V4L buffers by default */ + + zr->jpg_nbufs = 0; + zr->jpg_bufsize = 0; + zr->jpg_buffers_allocated = 0; + + zr->buffer_set = 0; /* Flag if frame buffer has been set */ + zr->buffer.base = (void *) vidmem; + zr->buffer.width = 0; + zr->buffer.height = 0; + zr->buffer.depth = 0; + zr->buffer.bytesperline = 0; + + zr->params.norm = default_norm ? 1 : 0; /* Avoid nonsense settings from user */ + zr->params.input = default_input ? 1 : 0; /* Avoid nonsense settings from user */ + zr->video_interlace = 0; + + /* Should the following be reset at every open ? */ + + zr->picture.colour = 32768; + zr->picture.brightness = 32768; + zr->picture.hue = 32768; + zr->picture.contrast = 32768; + zr->picture.whiteness = 0; + zr->picture.depth = 0; + zr->picture.palette = 0; + + for (j = 0; j < VIDEO_MAX_FRAME; j++) { + zr->v4l_gbuf[i].fbuffer = 0; + zr->v4l_gbuf[i].fbuffer_phys = 0; + zr->v4l_gbuf[i].fbuffer_bus = 0; + } + + zr->stat_com = 0; + + /* default setup (will be repeated at every open) */ + + zoran_open_init_params(zr); + + /* allocate memory *before* doing anything to the hardware in case allocation fails */ + + /* STAT_COM table and overlay mask */ + + mem_needed = (BUZ_NUM_STAT_COM + ((BUZ_MAX_WIDTH + 31) / 32) * BUZ_MAX_HEIGHT) * 4; + mem = (unsigned long) kmalloc(mem_needed, GFP_KERNEL); + if (!mem) { + return -ENOMEM; + } + memset((void *) mem, 0, mem_needed); + + zr->stat_com = (u32 *) mem; + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = 1; /* mark as unavailable to zr36057 */ + } + zr->overlay_mask = (u32 *) (mem + BUZ_NUM_STAT_COM * 4); + + /* Initialize zr->jpg_gbuf */ + + for (j = 0; j < BUZ_MAX_FRAME; j++) { + zr->jpg_gbuf[j].frag_tab = 0; + zr->jpg_gbuf[j].frag_tab_bus = 0; + zr->jpg_gbuf[j].state = BUZ_STATE_USER; + zr->jpg_gbuf[j].bs.frame = j; + } + + /* take zr36057 out of reset now */ + btwrite(ZR36057_SPGPPCR_SoftReset, ZR36057_SPGPPCR); + mdelay(10); + + /* stop all DMA processes */ + btwrite(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); + /* assert P_Reset */ + btwrite(0, ZR36057_JPC); + + switch(zr->board) + { + case BOARD_BUZ: + + /* set up GPIO direction */ + btwrite(ZR36057_SPGPPCR_SoftReset | 0, ZR36057_SPGPPCR); + + /* Set up guest bus timing - Guests 0..3 Tdur=12, Trec=3 */ + btwrite((GPIO_MASK << 24) | 0x8888, ZR36057_GPPGCR1); + mdelay(10); + + /* reset video decoder */ + + GPIO(zr, 0, 0); + mdelay(10); + GPIO(zr, 0, 1); + mdelay(10); + + /* reset JPEG codec */ + zr36060_sleep(zr, 0); + mdelay(10); + zr36060_reset(zr); + mdelay(10); + + /* display codec revision */ + if ((rev=zr36060_read_8(zr, 0x022)) == 0x33) { + printk(KERN_INFO "%s: Zoran ZR36060 (rev %d)\n", + zr->name, zr36060_read_8(zr, 0x023)); + } else { + printk(KERN_ERR "%s: Zoran ZR36060 not found (Rev=%d)\n", zr->name, rev); + kfree((void *) zr->stat_com); + return -1; + } + break; + + case BOARD_LML33: +// btwrite(btread(ZR36057_SPGPPCR)&~ZR36057_SPGPPCR_SoftReset , ZR36057_SPGPPCR); +// udelay(100); +// btwrite(btread(ZR36057_SPGPPCR)|ZR36057_SPGPPCR_SoftReset , ZR36057_SPGPPCR); +// udelay(1000); + + /* + * Set up the GPIO direction + */ + btwrite(btread(ZR36057_SPGPPCR_SoftReset)|0 , ZR36057_SPGPPCR); + /* Set up guest bus timing - Guests 0..2 Tdur=12, Trec=3 */ + btwrite(0xFF00F888, ZR36057_GPPGCR1); + mdelay(10); + GPIO(zr, 5, 0); /* Analog video bypass */ + udelay(3000); + GPIO(zr, 0, 0); /* Reset 819 */ + udelay(3000); + GPIO(zr, 0, 1); /* 819 back */ + udelay(3000); + /* reset JPEG codec */ + zr36060_sleep(zr, 0); + udelay(3000); + zr36060_reset(zr); + udelay(3000); + + /* display codec revision */ + if ((rev=zr36060_read_8(zr, 0x022)) == 0x33) { + printk(KERN_INFO "%s: Zoran ZR36060 (rev %d)\n", + zr->name, zr36060_read_8(zr, 0x023)); + } else { + printk(KERN_ERR "%s: Zoran ZR36060 not found (rev=%d)\n", zr->name, rev); + kfree((void *) zr->stat_com); + return -1; + } + break; + } + /* i2c */ + memcpy(&zr->i2c, &zoran_i2c_bus_template, sizeof(struct i2c_bus)); + sprintf(zr->i2c.name, "zoran%u", zr->id); + zr->i2c.data = zr; + if (i2c_register_bus(&zr->i2c) < 0) { + kfree((void *) zr->stat_com); + return -1; + } + /* + * Now add the template and register the device unit. + */ + memcpy(&zr->video_dev, &zoran_template, sizeof(zoran_template)); + sprintf(zr->video_dev.name, "zoran%u", zr->id); + if (video_register_device(&zr->video_dev, VFL_TYPE_GRABBER) < 0) { + i2c_unregister_bus(&zr->i2c); + kfree((void *) zr->stat_com); + return -1; + } + /* toggle JPEG codec sleep to sync PLL */ + zr36060_sleep(zr, 1); + mdelay(10); + zr36060_sleep(zr, 0); + mdelay(10); + + /* Enable bus-mastering */ + pci_set_master(zr->pci_dev); + + j = zr->params.input == 0 ? 3 : 7; + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &j); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &zr->params.norm); + i2c_control_device(&zr->i2c, I2C_DRIVERID_VIDEOENCODER, ENCODER_SET_NORM, &zr->params.norm); + + /* set individual interrupt enables (without GIRQ0) + but don't global enable until zoran_open() */ + + btwrite(IRQ_MASK & ~ZR36057_ISR_GIRQ0, ZR36057_ICR); + + if(request_irq(zr->pci_dev->irq, zoran_irq, + SA_SHIRQ | SA_INTERRUPT, zr->name, (void *) zr)<0) + { + printk(KERN_ERR "%s: Can't assign irq.\n", zr->name); + video_unregister_device(&zr->video_dev); + i2c_unregister_bus(&zr->i2c); + kfree((void *) zr->stat_com); + return -1; + } + zr->initialized = 1; + return 0; +} + + + +static void release_zoran(void) +{ + u8 command; + int i; + struct zoran *zr; + + for (i = 0; i < zoran_num; i++) { + zr = &zoran[i]; + + if (!zr->initialized) + continue; + + /* unregister i2c_bus */ + i2c_unregister_bus((&zr->i2c)); + + /* disable PCI bus-mastering */ + pci_read_config_byte(zr->pci_dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(zr->pci_dev, PCI_COMMAND, command); + + /* put chip into reset */ + btwrite(0, ZR36057_SPGPPCR); + + free_irq(zr->pci_dev->irq, zr); + + /* unmap and free memory */ + + kfree((void *) zr->stat_com); + + iounmap(zr->zr36057_mem); + + video_unregister_device(&zr->video_dev); + } +} + +/* + * Scan for a Buz card (actually for the PCI controller ZR36057), + * request the irq and map the io memory + */ + +static int find_zr36057(void) +{ + unsigned char latency; + struct zoran *zr; + struct pci_dev *dev = NULL; + + zoran_num = 0; + + while (zoran_num < BUZ_MAX + && (dev = pci_find_device(PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36057, dev)) != NULL) { + zr = &zoran[zoran_num]; + zr->pci_dev = dev; + zr->zr36057_mem = NULL; + zr->id = zoran_num; + sprintf(zr->name, "zoran%u", zr->id); + + spin_lock_init(&zr->lock); + + if (pci_enable_device(dev)) + continue; + + zr->zr36057_adr = pci_resource_start(zr->pci_dev, 0); + pci_read_config_byte(zr->pci_dev, PCI_CLASS_REVISION, &zr->revision); + if (zr->revision < 2) { + printk(KERN_INFO "%s: Zoran ZR36057 (rev %d) irq: %d, memory: 0x%08x.\n", + zr->name, zr->revision, zr->pci_dev->irq, zr->zr36057_adr); + } else { + unsigned short ss_vendor_id, ss_id; + + ss_vendor_id = zr->pci_dev->subsystem_vendor; + ss_id = zr->pci_dev->subsystem_device; + printk(KERN_INFO "%s: Zoran ZR36067 (rev %d) irq: %d, memory: 0x%08x\n", + zr->name, zr->revision, zr->pci_dev->irq, zr->zr36057_adr); + printk(KERN_INFO "%s: subsystem vendor=0x%04x id=0x%04x\n", + zr->name, ss_vendor_id, ss_id); + if(ss_vendor_id==0xFF10 && ss_id == 0xDE41) + { + zr->board = BOARD_LML33; + printk(KERN_INFO "%s: LML33 detected.\n", zr->name); + } + } + + zr->zr36057_mem = ioremap(zr->zr36057_adr, 0x1000); + if (!zr->zr36057_mem) { + printk(KERN_ERR "%s: ioremap failed\n", zr->name); + /* XXX handle error */ + } + + /* set PCI latency timer */ + pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, &latency); + if (latency != 48) { + printk(KERN_INFO "%s: Changing PCI latency from %d to 48.\n", zr->name, latency); + latency = 48; + pci_write_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, latency); + } + zoran_num++; + } + if (zoran_num == 0) + printk(KERN_INFO "zoran: no cards found.\n"); + + return zoran_num; +} + +static void handle_chipset(void) +{ + if(pci_pci_problems&PCIPCI_FAIL) + { + printk(KERN_WARNING "buz: This configuration is known to have PCI to PCI DMA problems\n"); + printk(KERN_WARNING "buz: You may not be able to use overlay mode.\n"); + } + + + if(pci_pci_problems&PCIPCI_TRITON) + { + printk("buz: Enabling Triton support.\n"); + triton = 1; + } + + if(pci_pci_problems&PCIPCI_NATOMA) + { + printk("buz: Enabling Natoma workaround.\n"); + natoma = 1; + } +} + +#ifdef MODULE +int init_module(void) +#else +int init_zoran_cards(struct video_init *unused) +#endif +{ + int i; + + + printk(KERN_INFO "Zoran driver 1.00 (c) 1999 Rainer Johanni, Dave Perks.\n"); + + /* Look for Buz cards */ + + if (find_zr36057() <= 0) { + return -EIO; + } + printk(KERN_INFO"zoran: %d zoran card(s) found\n", zoran_num); + + if (zoran_num == 0) + return -ENXIO; + + + /* check the parameters we have been given, adjust if necessary */ + + if (v4l_nbufs < 0) + v4l_nbufs = 0; + if (v4l_nbufs > VIDEO_MAX_FRAME) + v4l_nbufs = VIDEO_MAX_FRAME; + /* The user specfies the in KB, we want them in byte (and page aligned) */ + v4l_bufsize = PAGE_ALIGN(v4l_bufsize * 1024); + if (v4l_bufsize < 32768) + v4l_bufsize = 32768; + /* 2 MB is arbitrary but sufficient for the maximum possible images */ + if (v4l_bufsize > 2048 * 1024) + v4l_bufsize = 2048 * 1024; + + printk(KERN_INFO "zoran: using %d V4L buffers of size %d KB\n", v4l_nbufs, v4l_bufsize >> 10); + + /* Use parameter for vidmem or try to find a video card */ + + if (vidmem) { + printk(KERN_INFO "zoran: Using supplied video memory base address @ 0x%lx\n", vidmem); + } + + /* check if we have a Triton or Natome chipset */ + + handle_chipset(); + + /* take care of Natoma chipset and a revision 1 zr36057 */ + + for (i = 0; i < zoran_num; i++) { + if (natoma && zoran[i].revision <= 1) { + zoran[i].need_contiguous = 1; + printk(KERN_INFO "%s: ZR36057/Natome bug, max. buffer size is 128K\n", zoran[i].name); + } else { + zoran[i].need_contiguous = 0; + } + } + + /* initialize the Buzs */ + + /* We have to know which ones must be released if an error occurs */ + for (i = 0; i < zoran_num; i++) + zoran[i].initialized = 0; + + for (i = 0; i < zoran_num; i++) { + if (zr36057_init(i) < 0) { + release_zoran(); + return -EIO; + } + } + + return 0; +} + + + +#ifdef MODULE + +void cleanup_module(void) +{ + release_zoran(); +} + +#endif diff --git a/drivers/media/video/buz.h b/drivers/media/video/buz.h new file mode 100644 index 000000000..06b794a09 --- /dev/null +++ b/drivers/media/video/buz.h @@ -0,0 +1,319 @@ +/* + buz - Iomega Buz driver + + Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + + based on + + buz.0.0.3 Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + and + + bttv - Bt848 frame grabber driver + Copyright (C) 1996,97 Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _BUZ_H_ +#define _BUZ_H_ + +/* The Buz only supports a maximum width of 720, but some V4L + applications (e.g. xawtv are more happy with 768). + If XAWTV_HACK is defined, we try to fake a device with bigger width */ + +#define XAWTV_HACK + +#ifdef XAWTV_HACK +#define BUZ_MAX_WIDTH 768 /* never display more than 768 pixels */ +#else +#define BUZ_MAX_WIDTH 720 /* never display more than 720 pixels */ +#endif +#define BUZ_MAX_HEIGHT 576 /* never display more than 576 rows */ +#define BUZ_MIN_WIDTH 32 /* never display less than 32 pixels */ +#define BUZ_MIN_HEIGHT 24 /* never display less than 24 rows */ + +struct zoran_requestbuffers { + unsigned long count; /* Number of buffers for MJPEG grabbing */ + unsigned long size; /* Size PER BUFFER in bytes */ +}; + +struct zoran_sync { + unsigned long frame; /* number of buffer that has been free'd */ + unsigned long length; /* number of code bytes in buffer (capture only) */ + unsigned long seq; /* frame sequence number */ + struct timeval timestamp; /* timestamp */ +}; + +struct zoran_status { + int input; /* Input channel, has to be set prior to BUZIOC_G_STATUS */ + int signal; /* Returned: 1 if valid video signal detected */ + int norm; /* Returned: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */ + int color; /* Returned: 1 if color signal detected */ +}; + +struct zoran_params { + + /* The following parameters can only be queried */ + + int major_version; /* Major version number of driver */ + int minor_version; /* Minor version number of driver */ + + /* Main control parameters */ + + int input; /* Input channel: 0 = Composite, 1 = S-VHS */ + int norm; /* Norm: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */ + int decimation; /* decimation of captured video, + enlargement of video played back. + Valid values are 1, 2, 4 or 0. + 0 is a special value where the user + has full control over video scaling */ + + /* The following parameters only have to be set if decimation==0, + for other values of decimation they provide the data how the image is captured */ + + int HorDcm; /* Horizontal decimation: 1, 2 or 4 */ + int VerDcm; /* Vertical decimation: 1 or 2 */ + int TmpDcm; /* Temporal decimation: 1 or 2, + if TmpDcm==2 in capture every second frame is dropped, + in playback every frame is played twice */ + int field_per_buff; /* Number of fields per buffer: 1 or 2 */ + int img_x; /* start of image in x direction */ + int img_y; /* start of image in y direction */ + int img_width; /* image width BEFORE decimation, + must be a multiple of HorDcm*16 */ + int img_height; /* image height BEFORE decimation, + must be a multiple of VerDcm*8 */ + + /* --- End of parameters for decimation==0 only --- */ + + /* JPEG control parameters */ + + int quality; /* Measure for quality of compressed images. + Scales linearly with the size of the compressed images. + Must be beetween 0 and 100, 100 is a compression + ratio of 1:4 */ + + int odd_even; /* Which field should come first ??? */ + + int APPn; /* Number of APP segment to be written, must be 0..15 */ + int APP_len; /* Length of data in JPEG APPn segment */ + char APP_data[60]; /* Data in the JPEG APPn segment. */ + + int COM_len; /* Length of data in JPEG COM segment */ + char COM_data[60]; /* Data in JPEG COM segment */ + + unsigned long jpeg_markers; /* Which markers should go into the JPEG output. + Unless you exactly know what you do, leave them untouched. + Inluding less markers will make the resulting code + smaller, but there will be fewer aplications + which can read it. + The presence of the APP and COM marker is + influenced by APP0_len and COM_len ONLY! */ +#define JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */ +#define JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */ +#define JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */ +#define JPEG_MARKER_COM (1<<6) /* Comment segment */ +#define JPEG_MARKER_APP (1<<7) /* App segment, driver will allways use APP0 */ + + int VFIFO_FB; /* Flag for enabling Video Fifo Feedback. + If this flag is turned on and JPEG decompressing + is going to the screen, the decompress process + is stopped every time the Video Fifo is full. + This enables a smooth decompress to the screen + but the video output signal will get scrambled */ + + /* Misc */ + + char reserved[312]; /* Makes 512 bytes for this structure */ +}; + +/* + Private IOCTL to set up for displaying MJPEG + */ +#define BUZIOC_G_PARAMS _IOR ('v', BASE_VIDIOCPRIVATE+0, struct zoran_params) +#define BUZIOC_S_PARAMS _IOWR('v', BASE_VIDIOCPRIVATE+1, struct zoran_params) +#define BUZIOC_REQBUFS _IOWR('v', BASE_VIDIOCPRIVATE+2, struct zoran_requestbuffers) +#define BUZIOC_QBUF_CAPT _IOW ('v', BASE_VIDIOCPRIVATE+3, int) +#define BUZIOC_QBUF_PLAY _IOW ('v', BASE_VIDIOCPRIVATE+4, int) +#define BUZIOC_SYNC _IOR ('v', BASE_VIDIOCPRIVATE+5, struct zoran_sync) +#define BUZIOC_G_STATUS _IOWR('v', BASE_VIDIOCPRIVATE+6, struct zoran_status) + + +#ifdef __KERNEL__ + +#define BUZ_NUM_STAT_COM 4 +#define BUZ_MASK_STAT_COM 3 + +#define BUZ_MAX_FRAME 256 /* Must be a power of 2 */ +#define BUZ_MASK_FRAME 255 /* Must be BUZ_MAX_FRAME-1 */ + +#if VIDEO_MAX_FRAME <= 32 +#define V4L_MAX_FRAME 32 +#elif VIDEO_MAX_FRAME <= 64 +#define V4L_MAX_FRAME 64 +#else +#error "Too many video frame buffers to handle" +#endif +#define V4L_MASK_FRAME (V4L_MAX_FRAME - 1) + + +#include "zr36057.h" + +enum zoran_codec_mode { + BUZ_MODE_IDLE, /* nothing going on */ + BUZ_MODE_MOTION_COMPRESS, /* grabbing frames */ + BUZ_MODE_MOTION_DECOMPRESS, /* playing frames */ + BUZ_MODE_STILL_COMPRESS, /* still frame conversion */ + BUZ_MODE_STILL_DECOMPRESS /* still frame conversion */ +}; + +enum zoran_buffer_state { + BUZ_STATE_USER, /* buffer is owned by application */ + BUZ_STATE_PEND, /* buffer is queued in pend[] ready to feed to I/O */ + BUZ_STATE_DMA, /* buffer is queued in dma[] for I/O */ + BUZ_STATE_DONE /* buffer is ready to return to application */ +}; + +struct zoran_gbuffer { + u32 *frag_tab; /* addresses of frag table */ + u32 frag_tab_bus; /* same value cached to save time in ISR */ + enum zoran_buffer_state state; /* non-zero if corresponding buffer is in use in grab queue */ + struct zoran_sync bs; /* DONE: info to return to application */ +}; + +struct v4l_gbuffer { + char *fbuffer; /* virtual address of frame buffer */ + unsigned long fbuffer_phys; /* physical address of frame buffer */ + unsigned long fbuffer_bus; /* bus address of frame buffer */ + enum zoran_buffer_state state; /* state: unused/pending/done */ +}; + +struct zoran { + struct video_device video_dev; + struct i2c_bus i2c; + + int initialized; /* flag if zoran has been correctly initalized */ + int user; /* number of current users (0 or 1) */ + + unsigned short id; /* number of this device */ + char name[32]; /* name of this device */ + struct pci_dev *pci_dev; /* PCI device */ + unsigned char revision; /* revision of zr36057 */ + int board; /* Board type */ +#define BOARD_BUZ 0 +#define BOARD_LML33 1 + unsigned int zr36057_adr; /* bus address of IO mem returned by PCI BIOS */ + unsigned char *zr36057_mem; /* pointer to mapped IO memory */ + + int map_mjpeg_buffers; /* Flag which bufferset will map by next mmap() */ + + spinlock_t lock; /* Spinlock */ + + /* Video for Linux parameters */ + + struct video_picture picture; /* Current picture params */ + struct video_buffer buffer; /* Current buffer params */ + struct video_window window; /* Current window params */ + int buffer_set, window_set; /* Flags if the above structures are set */ + int video_interlace; /* Image on screen is interlaced */ + + u32 *overlay_mask; + + wait_queue_head_t v4l_capq; /* wait here for grab to finish */ + + int v4l_overlay_active; /* Overlay grab is activated */ + int v4l_memgrab_active; /* Memory grab is activated */ + + int v4l_grab_frame; /* Frame number being currently grabbed */ +#define NO_GRAB_ACTIVE (-1) + int v4l_grab_seq; /* Number of frames grabbed */ + int gwidth; /* Width of current memory capture */ + int gheight; /* Height of current memory capture */ + int gformat; /* Format of ... */ + int gbpl; /* byte per line of ... */ + + /* V4L grab queue of frames pending */ + + unsigned v4l_pend_head; + unsigned v4l_pend_tail; + int v4l_pend[V4L_MAX_FRAME]; + + struct v4l_gbuffer v4l_gbuf[VIDEO_MAX_FRAME]; /* V4L buffers' info */ + + /* Buz MJPEG parameters */ + + unsigned long jpg_nbufs; /* Number of buffers */ + unsigned long jpg_bufsize; /* Size of mjpeg buffers in bytes */ + int jpg_buffers_allocated; /* Flag if buffers are allocated */ + int need_contiguous; /* Flag if contiguous buffers are needed */ + + enum zoran_codec_mode codec_mode; /* status of codec */ + struct zoran_params params; /* structure with a lot of things to play with */ + + wait_queue_head_t jpg_capq; /* wait here for grab to finish */ + + /* grab queue counts/indices, mask with BUZ_MASK_STAT_COM before using as index */ + /* (dma_head - dma_tail) is number active in DMA, must be <= BUZ_NUM_STAT_COM */ + /* (value & BUZ_MASK_STAT_COM) corresponds to index in stat_com table */ + unsigned long jpg_que_head; /* Index where to put next buffer which is queued */ + unsigned long jpg_dma_head; /* Index of next buffer which goes into stat_com */ + unsigned long jpg_dma_tail; /* Index of last buffer in stat_com */ + unsigned long jpg_que_tail; /* Index of last buffer in queue */ + unsigned long jpg_seq_num; /* count of frames since grab/play started */ + + /* zr36057's code buffer table */ + u32 *stat_com; /* stat_com[i] is indexed by dma_head/tail & BUZ_MASK_STAT_COM */ + + /* (value & BUZ_MASK_FRAME) corresponds to index in pend[] queue */ + int jpg_pend[BUZ_MAX_FRAME]; + + /* array indexed by frame number */ + struct zoran_gbuffer jpg_gbuf[BUZ_MAX_FRAME]; /* MJPEG buffers' info */ +}; + +#endif + +/*The following should be done in more portable way. It depends on define + of _ALPHA_BUZ in the Makefile. */ + +#ifdef _ALPHA_BUZ +#define btwrite(dat,adr) writel((dat),(char *) (zr->zr36057_adr+(adr))) +#define btread(adr) readl(zr->zr36057_adr+(adr)) +#else +#define btwrite(dat,adr) writel((dat), (char *) (zr->zr36057_mem+(adr))) +#define btread(adr) readl(zr->zr36057_mem+(adr)) +#endif + +#define btand(dat,adr) btwrite((dat) & btread(adr), adr) +#define btor(dat,adr) btwrite((dat) | btread(adr), adr) +#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr) + +#define I2C_TSA5522 0xc2 +#define I2C_TDA9850 0xb6 +#define I2C_HAUPEE 0xa0 +#define I2C_STBEE 0xae +#define I2C_SAA7111 0x48 +#define I2C_SAA7185 0x88 + +#define TDA9850_CON1 0x04 +#define TDA9850_CON2 0x05 +#define TDA9850_CON3 0x06 +#define TDA9850_CON4 0x07 +#define TDA9850_ALI1 0x08 +#define TDA9850_ALI2 0x09 +#define TDA9850_ALI3 0x0a + +#endif diff --git a/drivers/media/video/bw-qcam.c b/drivers/media/video/bw-qcam.c new file mode 100644 index 000000000..17f7d25dc --- /dev/null +++ b/drivers/media/video/bw-qcam.c @@ -0,0 +1,1066 @@ +/* + * QuickCam Driver For Video4Linux. + * + * This version only works as a module. + * + * Video4Linux conversion work by Alan Cox. + * Parport compatibility by Phil Blundell. + * Busy loop avoidance by Mark Cooke. + * + * Module parameters: + * + * maxpoll=<1 - 5000> + * + * When polling the QuickCam for a response, busy-wait for a + * maximum of this many loops. The default of 250 gives little + * impact on interactive response. + * + * NOTE: If this parameter is set too high, the processor + * will busy wait until this loop times out, and then + * slowly poll for a further 5 seconds before failing + * the transaction. You have been warned. + * + * yieldlines=<1 - 250> + * + * When acquiring a frame from the camera, the data gathering + * loop will yield back to the scheduler after completing + * this many lines. The default of 4 provides a trade-off + * between increased frame acquisition time and impact on + * interactive response. + */ + +/* qcam-lib.c -- Library for programming with the Connectix QuickCam. + * See the included documentation for usage instructions and details + * of the protocol involved. */ + + +/* Version 0.5, August 4, 1996 */ +/* Version 0.7, August 27, 1996 */ +/* Version 0.9, November 17, 1996 */ + + +/****************************************************************** + +Copyright (C) 1996 by Scott Laird + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include <linux/version.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +#include "bw-qcam.h" + +static unsigned int maxpoll=250; /* Maximum busy-loop count for qcam I/O */ +static unsigned int yieldlines=4; /* Yield after this many during capture */ + +#if LINUX_VERSION_CODE >= 0x020117 +MODULE_PARM(maxpoll,"i"); +MODULE_PARM(yieldlines,"i"); +#endif + +extern __inline__ int read_lpstatus(struct qcam_device *q) +{ + return parport_read_status(q->pport); +} + +extern __inline__ int read_lpcontrol(struct qcam_device *q) +{ + return parport_read_control(q->pport); +} + +extern __inline__ int read_lpdata(struct qcam_device *q) +{ + return parport_read_data(q->pport); +} + +extern __inline__ void write_lpdata(struct qcam_device *q, int d) +{ + parport_write_data(q->pport, d); +} + +extern __inline__ void write_lpcontrol(struct qcam_device *q, int d) +{ + parport_write_control(q->pport, d); +} + +static int qc_waithand(struct qcam_device *q, int val); +static int qc_command(struct qcam_device *q, int command); +static int qc_readparam(struct qcam_device *q); +static int qc_setscanmode(struct qcam_device *q); +static int qc_readbytes(struct qcam_device *q, char buffer[]); + +static struct video_device qcam_template; + +static int qc_calibrate(struct qcam_device *q) +{ + /* + * Bugfix by Hanno Mueller hmueller@kabel.de, Mai 21 96 + * The white balance is an individiual value for each + * quickcam. + */ + + int value; + int count = 0; + + qc_command(q, 27); /* AutoAdjustOffset */ + qc_command(q, 0); /* Dummy Parameter, ignored by the camera */ + + /* GetOffset (33) will read 255 until autocalibration */ + /* is finished. After that, a value of 1-254 will be */ + /* returned. */ + + do { + qc_command(q, 33); + value = qc_readparam(q); + mdelay(1); + schedule(); + count++; + } while (value == 0xff && count<2048); + + q->whitebal = value; + return value; +} + +/* Initialize the QuickCam driver control structure. This is where + * defaults are set for people who don't have a config file.*/ + +static struct qcam_device *qcam_init(struct parport *port) +{ + struct qcam_device *q; + + q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL); + if(q==NULL) + return NULL; + + q->pport = port; + q->pdev = parport_register_device(port, "bw-qcam", NULL, NULL, + NULL, 0, NULL); + if (q->pdev == NULL) + { + printk(KERN_ERR "bw-qcam: couldn't register for %s.\n", + port->name); + kfree(q); + return NULL; + } + + memcpy(&q->vdev, &qcam_template, sizeof(qcam_template)); + + init_MUTEX(&q->lock); + + q->port_mode = (QC_ANY | QC_NOTSET); + q->width = 320; + q->height = 240; + q->bpp = 4; + q->transfer_scale = 2; + q->contrast = 192; + q->brightness = 180; + q->whitebal = 105; + q->top = 1; + q->left = 14; + q->mode = -1; + q->status = QC_PARAM_CHANGE; + return q; +} + + +/* qc_command is probably a bit of a misnomer -- it's used to send + * bytes *to* the camera. Generally, these bytes are either commands + * or arguments to commands, so the name fits, but it still bugs me a + * bit. See the documentation for a list of commands. */ + +static int qc_command(struct qcam_device *q, int command) +{ + int n1, n2; + int cmd; + + write_lpdata(q, command); + write_lpcontrol(q, 6); + + n1 = qc_waithand(q, 1); + + write_lpcontrol(q, 0xe); + n2 = qc_waithand(q, 0); + + cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); + return cmd; +} + +static int qc_readparam(struct qcam_device *q) +{ + int n1, n2; + int cmd; + + write_lpcontrol(q, 6); + n1 = qc_waithand(q, 1); + + write_lpcontrol(q, 0xe); + n2 = qc_waithand(q, 0); + + cmd = (n1 & 0xf0) | ((n2 & 0xf0) >> 4); + return cmd; +} + +/* qc_waithand busy-waits for a handshake signal from the QuickCam. + * Almost all communication with the camera requires handshaking. */ + +static int qc_waithand(struct qcam_device *q, int val) +{ + int status; + int runs=0; + + if (val) + { + while (!((status = read_lpstatus(q)) & 8)) + { + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/200); + } + if(runs>(maxpoll+1000)) /* 5 seconds */ + return -1; + } + } + else + { + while (((status = read_lpstatus(q)) & 8)) + { + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/200); + } + if(runs++>(maxpoll+1000)) /* 5 seconds */ + return -1; + } + } + + return status; +} + +/* Waithand2 is used when the qcam is in bidirectional mode, and the + * handshaking signal is CamRdy2 (bit 0 of data reg) instead of CamRdy1 + * (bit 3 of status register). It also returns the last value read, + * since this data is useful. */ + +static unsigned int qc_waithand2(struct qcam_device *q, int val) +{ + unsigned int status; + int runs=0; + + do + { + status = read_lpdata(q); + /* 1000 is enough spins on the I/O for all normal + cases, at that point we start to poll slowly + until the camera wakes up. However, we are + busy blocked until the camera responds, so + setting it lower is much better for interactive + response. */ + + if(runs++>maxpoll) + { + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/200); + } + if(runs++>(maxpoll+1000)) /* 5 seconds */ + return 0; + } + while ((status & 1) != val); + + return status; +} + + +/* Try to detect a QuickCam. It appears to flash the upper 4 bits of + the status register at 5-10 Hz. This is only used in the autoprobe + code. Be aware that this isn't the way Connectix detects the + camera (they send a reset and try to handshake), but this should be + almost completely safe, while their method screws up my printer if + I plug it in before the camera. */ + +static int qc_detect(struct qcam_device *q) +{ + int reg, lastreg; + int count = 0; + int i; + + lastreg = reg = read_lpstatus(q) & 0xf0; + + for (i = 0; i < 500; i++) + { + reg = read_lpstatus(q) & 0xf0; + if (reg != lastreg) + count++; + lastreg = reg; + mdelay(2); + } + + +#if 0 + /* Force camera detection during testing. Sometimes the camera + won't be flashing these bits. Possibly unloading the module + in the middle of a grab? Or some timeout condition? + I've seen this parameter as low as 19 on my 450Mhz box - mpc */ + printk("Debugging: QCam detection counter <30-200 counts as detected>: %d\n", count); + return 1; +#endif + + /* Be (even more) liberal in what you accept... */ + +/* if (count > 30 && count < 200) */ + if (count > 20 && count < 300) + return 1; /* found */ + else + return 0; /* not found */ +} + + +/* Reset the QuickCam. This uses the same sequence the Windows + * QuickPic program uses. Someone with a bi-directional port should + * check that bi-directional mode is detected right, and then + * implement bi-directional mode in qc_readbyte(). */ + +static void qc_reset(struct qcam_device *q) +{ + switch (q->port_mode & QC_FORCE_MASK) + { + case QC_FORCE_UNIDIR: + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; + break; + + case QC_FORCE_BIDIR: + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; + break; + + case QC_ANY: + write_lpcontrol(q, 0x20); + write_lpdata(q, 0x75); + + if (read_lpdata(q) != 0x75) { + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_BIDIR; + } else { + q->port_mode = (q->port_mode & ~QC_MODE_MASK) | QC_UNIDIR; + } + break; + } + + write_lpcontrol(q, 0xb); + udelay(250); + write_lpcontrol(q, 0xe); + qc_setscanmode(q); /* in case port_mode changed */ +} + + +/* Decide which scan mode to use. There's no real requirement that + * the scanmode match the resolution in q->height and q-> width -- the + * camera takes the picture at the resolution specified in the + * "scanmode" and then returns the image at the resolution specified + * with the resolution commands. If the scan is bigger than the + * requested resolution, the upper-left hand corner of the scan is + * returned. If the scan is smaller, then the rest of the image + * returned contains garbage. */ + +static int qc_setscanmode(struct qcam_device *q) +{ + int old_mode = q->mode; + + switch (q->transfer_scale) + { + case 1: + q->mode = 0; + break; + case 2: + q->mode = 4; + break; + case 4: + q->mode = 8; + break; + } + + switch (q->bpp) + { + case 4: + break; + case 6: + q->mode += 2; + break; + } + + switch (q->port_mode & QC_MODE_MASK) + { + case QC_BIDIR: + q->mode += 1; + break; + case QC_NOTSET: + case QC_UNIDIR: + break; + } + + if (q->mode != old_mode) + q->status |= QC_PARAM_CHANGE; + + return 0; +} + + +/* Reset the QuickCam and program for brightness, contrast, + * white-balance, and resolution. */ + +void qc_set(struct qcam_device *q) +{ + int val; + int val2; + + qc_reset(q); + + /* Set the brightness. Yes, this is repetitive, but it works. + * Shorter versions seem to fail subtly. Feel free to try :-). */ + /* I think the problem was in qc_command, not here -- bls */ + + qc_command(q, 0xb); + qc_command(q, q->brightness); + + val = q->height / q->transfer_scale; + qc_command(q, 0x11); + qc_command(q, val); + if ((q->port_mode & QC_MODE_MASK) == QC_UNIDIR && q->bpp == 6) { + /* The normal "transfers per line" calculation doesn't seem to work + as expected here (and yet it works fine in qc_scan). No idea + why this case is the odd man out. Fortunately, Laird's original + working version gives me a good way to guess at working values. + -- bls */ + val = q->width; + val2 = q->transfer_scale * 4; + } else { + val = q->width * q->bpp; + val2 = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * + q->transfer_scale; + } + val = (val + val2 - 1) / val2; + qc_command(q, 0x13); + qc_command(q, val); + + /* Setting top and left -- bls */ + qc_command(q, 0xd); + qc_command(q, q->top); + qc_command(q, 0xf); + qc_command(q, q->left / 2); + + qc_command(q, 0x19); + qc_command(q, q->contrast); + qc_command(q, 0x1f); + qc_command(q, q->whitebal); + + /* Clear flag that we must update the grabbing parameters on the camera + before we grab the next frame */ + q->status &= (~QC_PARAM_CHANGE); +} + +/* Qc_readbytes reads some bytes from the QC and puts them in + the supplied buffer. It returns the number of bytes read, + or -1 on error. */ + +extern __inline__ int qc_readbytes(struct qcam_device *q, char buffer[]) +{ + int ret=1; + unsigned int hi, lo; + unsigned int hi2, lo2; + static int state = 0; + + if (buffer == NULL) + { + state = 0; + return 0; + } + + switch (q->port_mode & QC_MODE_MASK) + { + case QC_BIDIR: /* Bi-directional Port */ + write_lpcontrol(q, 0x26); + lo = (qc_waithand2(q, 1) >> 1); + hi = (read_lpstatus(q) >> 3) & 0x1f; + write_lpcontrol(q, 0x2e); + lo2 = (qc_waithand2(q, 0) >> 1); + hi2 = (read_lpstatus(q) >> 3) & 0x1f; + switch (q->bpp) + { + case 4: + buffer[0] = lo & 0xf; + buffer[1] = ((lo & 0x70) >> 4) | ((hi & 1) << 3); + buffer[2] = (hi & 0x1e) >> 1; + buffer[3] = lo2 & 0xf; + buffer[4] = ((lo2 & 0x70) >> 4) | ((hi2 & 1) << 3); + buffer[5] = (hi2 & 0x1e) >> 1; + ret = 6; + break; + case 6: + buffer[0] = lo & 0x3f; + buffer[1] = ((lo & 0x40) >> 6) | (hi << 1); + buffer[2] = lo2 & 0x3f; + buffer[3] = ((lo2 & 0x40) >> 6) | (hi2 << 1); + ret = 4; + break; + } + break; + + case QC_UNIDIR: /* Unidirectional Port */ + write_lpcontrol(q, 6); + lo = (qc_waithand(q, 1) & 0xf0) >> 4; + write_lpcontrol(q, 0xe); + hi = (qc_waithand(q, 0) & 0xf0) >> 4; + + switch (q->bpp) + { + case 4: + buffer[0] = lo; + buffer[1] = hi; + ret = 2; + break; + case 6: + switch (state) + { + case 0: + buffer[0] = (lo << 2) | ((hi & 0xc) >> 2); + q->saved_bits = (hi & 3) << 4; + state = 1; + ret = 1; + break; + case 1: + buffer[0] = lo | q->saved_bits; + q->saved_bits = hi << 2; + state = 2; + ret = 1; + break; + case 2: + buffer[0] = ((lo & 0xc) >> 2) | q->saved_bits; + buffer[1] = ((lo & 3) << 4) | hi; + state = 0; + ret = 2; + break; + } + break; + } + break; + } + return ret; +} + +/* requests a scan from the camera. It sends the correct instructions + * to the camera and then reads back the correct number of bytes. In + * previous versions of this routine the return structure contained + * the raw output from the camera, and there was a 'qc_convertscan' + * function that converted that to a useful format. In version 0.3 I + * rolled qc_convertscan into qc_scan and now I only return the + * converted scan. The format is just an one-dimensional array of + * characters, one for each pixel, with 0=black up to n=white, where + * n=2^(bit depth)-1. Ask me for more details if you don't understand + * this. */ + +long qc_capture(struct qcam_device * q, char *buf, unsigned long len) +{ + int i, j, k, yield; + int bytes; + int linestotrans, transperline; + int divisor; + int pixels_per_line; + int pixels_read = 0; + int got=0; + char buffer[6]; + int shift=8-q->bpp; + char invert; + + if (q->mode == -1) + return -ENXIO; + + qc_command(q, 0x7); + qc_command(q, q->mode); + + if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) + { + write_lpcontrol(q, 0x2e); /* turn port around */ + write_lpcontrol(q, 0x26); + (void) qc_waithand(q, 1); + write_lpcontrol(q, 0x2e); + (void) qc_waithand(q, 0); + } + + /* strange -- should be 15:63 below, but 4bpp is odd */ + invert = (q->bpp == 4) ? 16 : 63; + + linestotrans = q->height / q->transfer_scale; + pixels_per_line = q->width / q->transfer_scale; + transperline = q->width * q->bpp; + divisor = (((q->port_mode & QC_MODE_MASK) == QC_BIDIR) ? 24 : 8) * + q->transfer_scale; + transperline = (transperline + divisor - 1) / divisor; + + for (i = 0, yield = yieldlines; i < linestotrans; i++) + { + for (pixels_read = j = 0; j < transperline; j++) + { + bytes = qc_readbytes(q, buffer); + for (k = 0; k < bytes && (pixels_read + k) < pixels_per_line; k++) + { + int o; + if (buffer[k] == 0 && invert == 16) + { + /* 4bpp is odd (again) -- inverter is 16, not 15, but output + must be 0-15 -- bls */ + buffer[k] = 16; + } + o=i*pixels_per_line + pixels_read + k; + if(o<len) + { + got++; + put_user((invert - buffer[k])<<shift, buf+o); + } + } + pixels_read += bytes; + } + (void) qc_readbytes(q, 0); /* reset state machine */ + + /* Grabbing an entire frame from the quickcam is a lengthy + process. We don't (usually) want to busy-block the + processor for the entire frame. yieldlines is a module + parameter. If we yield every line, the minimum frame + time will be 240 / 200 = 1.2 seconds. The compile-time + default is to yield every 4 lines. */ + if (i >= yield) { + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/200); + yield = i + yieldlines; + } + } + + if ((q->port_mode & QC_MODE_MASK) == QC_BIDIR) + { + write_lpcontrol(q, 2); + write_lpcontrol(q, 6); + udelay(3); + write_lpcontrol(q, 0xe); + } + if(got<len) + return got; + return len; +} + +/* + * Video4linux interfacing + */ + +static int qcam_open(struct video_device *dev, int flags) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static void qcam_close(struct video_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +static int qcam_init_done(struct video_device *dev) +{ + return 0; +} + +static long qcam_write(struct video_device *v, const char *buf, unsigned long count, int noblock) +{ + return -EINVAL; +} + +static int qcam_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name, "Quickcam"); + b.type = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_MONOCHROME; + b.channels = 1; + b.audios = 0; + b.maxwidth = 320; + b.maxheight = 240; + b.minwidth = 80; + b.minheight = 60; + if(copy_to_user(arg, &b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.channel!=0) + return -EINVAL; + v.flags=0; + v.tuners=0; + /* Good question.. its composite or SVHS so.. */ + v.type = VIDEO_TYPE_CAMERA; + strcpy(v.name, "Camera"); + if(copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: + { + int v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + if(v!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + strcpy(v.name, "Format"); + v.rangelow=0; + v.rangehigh=0; + v.flags= 0; + v.mode = VIDEO_MODE_AUTO; + if(copy_to_user(arg,&v,sizeof(v))!=0) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + if(v.mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p; + p.colour=0x8000; + p.hue=0x8000; + p.brightness=qcam->brightness<<8; + p.contrast=qcam->contrast<<8; + p.whiteness=qcam->whitebal<<8; + p.depth=qcam->bpp; + p.palette=VIDEO_PALETTE_GREY; + if(copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + if(copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + if(p.palette!=VIDEO_PALETTE_GREY) + return -EINVAL; + if(p.depth!=4 && p.depth!=6) + return -EINVAL; + + /* + * Now load the camera. + */ + + qcam->brightness = p.brightness>>8; + qcam->contrast = p.contrast>>8; + qcam->whitebal = p.whiteness>>8; + qcam->bpp = p.depth; + + down(&qcam->lock); + qc_setscanmode(qcam); + up(&qcam->lock); + qcam->status |= QC_PARAM_CHANGE; + + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + if(copy_from_user(&vw, arg,sizeof(vw))) + return -EFAULT; + if(vw.flags) + return -EINVAL; + if(vw.clipcount) + return -EINVAL; + if(vw.height<60||vw.height>240) + return -EINVAL; + if(vw.width<80||vw.width>320) + return -EINVAL; + + qcam->width = 320; + qcam->height = 240; + qcam->transfer_scale = 4; + + if(vw.width>=160 && vw.height>=120) + { + qcam->transfer_scale = 2; + } + if(vw.width>=320 && vw.height>=240) + { + qcam->width = 320; + qcam->height = 240; + qcam->transfer_scale = 1; + } + down(&qcam->lock); + qc_setscanmode(qcam); + up(&qcam->lock); + + /* We must update the camera before we grab. We could + just have changed the grab size */ + qcam->status |= QC_PARAM_CHANGE; + + /* Ok we figured out what to use from our wide choice */ + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x=0; + vw.y=0; + vw.width=qcam->width/qcam->transfer_scale; + vw.height=qcam->height/qcam->transfer_scale; + vw.chromakey=0; + vw.flags=0; + if(copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + return -EINVAL; + case VIDIOCGFBUF: + return -EINVAL; + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCKEY: + return 0; + case VIDIOCGFREQ: + return -EINVAL; + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + return -EINVAL; + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static long qcam_read(struct video_device *v, char *buf, unsigned long count, int noblock) +{ + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + parport_claim_or_block(qcam->pdev); + + down(&qcam->lock); + + qc_reset(qcam); + + /* Update the camera parameters if we need to */ + if (qcam->status & QC_PARAM_CHANGE) + qc_set(qcam); + + len=qc_capture(qcam, buf,count); + + up(&qcam->lock); + + parport_release(qcam->pdev); + return len; +} + +static struct video_device qcam_template= +{ + "Connectix Quickcam", + VID_TYPE_CAPTURE, + VID_HARDWARE_QCAM_BW, + qcam_open, + qcam_close, + qcam_read, + qcam_write, + NULL, + qcam_ioctl, + NULL, + qcam_init_done, + NULL, + 0, + 0 +}; + +#define MAX_CAMS 4 +static struct qcam_device *qcams[MAX_CAMS]; +static unsigned int num_cams = 0; + +int init_bwqcam(struct parport *port) +{ + struct qcam_device *qcam; + + if (num_cams == MAX_CAMS) + { + printk(KERN_ERR "Too many Quickcams (max %d)\n", MAX_CAMS); + return -ENOSPC; + } + + qcam=qcam_init(port); + if(qcam==NULL) + return -ENODEV; + + parport_claim_or_block(qcam->pdev); + + qc_reset(qcam); + + if(qc_detect(qcam)==0) + { + parport_release(qcam->pdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + qc_calibrate(qcam); + + parport_release(qcam->pdev); + + printk(KERN_INFO "Connectix Quickcam on %s\n", qcam->pport->name); + + if(video_register_device(&qcam->vdev, VFL_TYPE_GRABBER)==-1) + { + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + qcams[num_cams++] = qcam; + + return 0; +} + +void close_bwqcam(struct qcam_device *qcam) +{ + video_unregister_device(&qcam->vdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); +} + +/* The parport parameter controls which parports will be scanned. + * Scanning all parports causes some printers to print a garbage page. + * -- March 14, 1999 Billy Donahue <billy@escape.com> */ +#ifdef MODULE +static char *parport[MAX_CAMS] = { NULL, }; +MODULE_PARM(parport, "1-" __MODULE_STRING(MAX_CAMS) "s"); +#endif + +#ifdef MODULE +int init_module(void) +{ + struct parport *port; + int n; + if(parport[0] && strncmp(parport[0], "auto", 4)){ + /* user gave parport parameters */ + for(n=0; parport[n] && n<MAX_CAMS; n++){ + char *ep; + unsigned long r; + r = simple_strtoul(parport[n], &ep, 0); + if(ep == parport[n]){ + printk(KERN_ERR + "bw-qcam: bad port specifier \"%s\"\n", + parport[n]); + continue; + } + for (port=parport_enumerate(); port; port=port->next){ + if(r!=port->number) + continue; + init_bwqcam(port); + break; + } + } + return (num_cams)?0:-ENODEV; + } + /* no parameter or "auto" */ + for (port = parport_enumerate(); port; port=port->next) + init_bwqcam(port); + + /* Do some sanity checks on the module parameters. */ + if (maxpoll > 5000) { + printk("Connectix Quickcam max-poll was above 5000. Using 5000.\n"); + maxpoll = 5000; + } + + if (yieldlines < 1) { + printk("Connectix Quickcam yieldlines was less than 1. Using 1.\n"); + yieldlines = 1; + } + + return (num_cams)?0:-ENODEV; +} + +void cleanup_module(void) +{ + unsigned int i; + for (i = 0; i < num_cams; i++) + close_bwqcam(qcams[i]); +} +#else +int __init init_bw_qcams(struct video_init *unused) +{ + struct parport *port; + + for (port = parport_enumerate(); port; port=port->next) + init_bwqcam(port); + return 0; +} +#endif diff --git a/drivers/media/video/bw-qcam.h b/drivers/media/video/bw-qcam.h new file mode 100644 index 000000000..723e8ad9e --- /dev/null +++ b/drivers/media/video/bw-qcam.h @@ -0,0 +1,68 @@ +/* + * Video4Linux bw-qcam driver + * + * Derived from code.. + */ + +/****************************************************************** + +Copyright (C) 1996 by Scott Laird + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL SCOTT LAIRD BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + +/* One from column A... */ +#define QC_NOTSET 0 +#define QC_UNIDIR 1 +#define QC_BIDIR 2 +#define QC_SERIAL 3 + +/* ... and one from column B */ +#define QC_ANY 0x00 +#define QC_FORCE_UNIDIR 0x10 +#define QC_FORCE_BIDIR 0x20 +#define QC_FORCE_SERIAL 0x30 +/* in the port_mode member */ + +#define QC_MODE_MASK 0x07 +#define QC_FORCE_MASK 0x70 + +#define MAX_HEIGHT 243 +#define MAX_WIDTH 336 + +/* Bit fields for status flags */ +#define QC_PARAM_CHANGE 0x01 /* Camera status change has occurred */ + +struct qcam_device { + struct video_device vdev; + struct pardevice *pdev; + struct parport *pport; + struct semaphore lock; + int width, height; + int bpp; + int mode; + int contrast, brightness, whitebal; + int port_mode; + int transfer_scale; + int top, left; + int status; + unsigned int saved_bits; +}; diff --git a/drivers/media/video/c-qcam.c b/drivers/media/video/c-qcam.c new file mode 100644 index 000000000..fa96cc925 --- /dev/null +++ b/drivers/media/video/c-qcam.c @@ -0,0 +1,901 @@ +/* + * Video4Linux Colour QuickCam driver + * Copyright 1997-2000 Philip Blundell <philb@gnu.org> + * + * Module parameters: + * + * parport=auto -- probe all parports (default) + * parport=0 -- parport0 becomes qcam1 + * parport=2,0,1 -- parports 2,0,1 are tried in that order + * + * probe=0 -- do no probing, assume camera is present + * probe=1 -- use IEEE-1284 autoprobe data only (default) + * probe=2 -- probe aggressively for cameras + * + * force_rgb=1 -- force data format to RGB (default is BGR) + * + * The parport parameter controls which parports will be scanned. + * Scanning all parports causes some printers to print a garbage page. + * -- March 14, 1999 Billy Donahue <billy@escape.com> + * + * Fixed data format to BGR, added force_rgb parameter. Added missing + * parport_unregister_driver() on module removal. + * -- May 28, 2000 Claudio Matsuoka <claudio@conectiva.com> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include <linux/version.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <asm/uaccess.h> + +struct qcam_device { + struct video_device vdev; + struct pardevice *pdev; + struct parport *pport; + int width, height; + int ccd_width, ccd_height; + int mode; + int contrast, brightness, whitebal; + int top, left; + unsigned int bidirectional; + struct semaphore lock; +}; + +/* cameras maximum */ +#define MAX_CAMS 4 + +/* The three possible QuickCam modes */ +#define QC_MILLIONS 0x18 +#define QC_BILLIONS 0x10 +#define QC_THOUSANDS 0x08 /* with VIDEC compression (not supported) */ + +/* The three possible decimations */ +#define QC_DECIMATION_1 0 +#define QC_DECIMATION_2 2 +#define QC_DECIMATION_4 4 + +#define BANNER "Colour QuickCam for Video4Linux v0.05" + +static int parport[MAX_CAMS] = { [1 ... MAX_CAMS-1] = -1 }; +static int probe = 2; +static int force_rgb = 0; + +static inline void qcam_set_ack(struct qcam_device *qcam, unsigned int i) +{ + /* note: the QC specs refer to the PCAck pin by voltage, not + software level. PC ports have builtin inverters. */ + parport_frob_control(qcam->pport, 8, i?8:0); +} + +static inline unsigned int qcam_ready1(struct qcam_device *qcam) +{ + return (parport_read_status(qcam->pport) & 0x8)?1:0; +} + +static inline unsigned int qcam_ready2(struct qcam_device *qcam) +{ + return (parport_read_data(qcam->pport) & 0x1)?1:0; +} + +static unsigned int qcam_await_ready1(struct qcam_device *qcam, + int value) +{ + unsigned long oldjiffies = jiffies; + unsigned int i; + + for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); ) + if (qcam_ready1(qcam) == value) + return 0; + + /* If the camera didn't respond within 1/25 second, poll slowly + for a while. */ + for (i = 0; i < 50; i++) + { + if (qcam_ready1(qcam) == value) + return 0; + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + } + + /* Probably somebody pulled the plug out. Not much we can do. */ + printk(KERN_ERR "c-qcam: ready1 timeout (%d) %x %x\n", value, + parport_read_status(qcam->pport), + parport_read_control(qcam->pport)); + return 1; +} + +static unsigned int qcam_await_ready2(struct qcam_device *qcam, int value) +{ + unsigned long oldjiffies = jiffies; + unsigned int i; + + for (oldjiffies = jiffies; (jiffies - oldjiffies) < (HZ/25); ) + if (qcam_ready2(qcam) == value) + return 0; + + /* If the camera didn't respond within 1/25 second, poll slowly + for a while. */ + for (i = 0; i < 50; i++) + { + if (qcam_ready2(qcam) == value) + return 0; + current->state=TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + } + + /* Probably somebody pulled the plug out. Not much we can do. */ + printk(KERN_ERR "c-qcam: ready2 timeout (%d) %x %x %x\n", value, + parport_read_status(qcam->pport), + parport_read_control(qcam->pport), + parport_read_data(qcam->pport)); + return 1; +} + +static int qcam_read_data(struct qcam_device *qcam) +{ + unsigned int idata; + qcam_set_ack(qcam, 0); + if (qcam_await_ready1(qcam, 1)) return -1; + idata = parport_read_status(qcam->pport) & 0xf0; + qcam_set_ack(qcam, 1); + if (qcam_await_ready1(qcam, 0)) return -1; + idata |= (parport_read_status(qcam->pport) >> 4); + return idata; +} + +static int qcam_write_data(struct qcam_device *qcam, unsigned int data) +{ + unsigned int idata; + parport_write_data(qcam->pport, data); + idata = qcam_read_data(qcam); + if (data != idata) + { + printk(KERN_WARNING "cqcam: sent %x but received %x\n", data, + idata); + return 1; + } + return 0; +} + +static inline int qcam_set(struct qcam_device *qcam, unsigned int cmd, unsigned int data) +{ + if (qcam_write_data(qcam, cmd)) + return -1; + if (qcam_write_data(qcam, data)) + return -1; + return 0; +} + +static inline int qcam_get(struct qcam_device *qcam, unsigned int cmd) +{ + if (qcam_write_data(qcam, cmd)) + return -1; + return qcam_read_data(qcam); +} + +static int qc_detect(struct qcam_device *qcam) +{ + unsigned int stat, ostat, i, count = 0; + + /* The probe routine below is not very reliable. The IEEE-1284 + probe takes precedence. */ + /* XXX Currently parport provides no way to distinguish between + "the IEEE probe was not done" and "the probe was done, but + no device was found". Fix this one day. */ + if (qcam->pport->probe_info[0].class == PARPORT_CLASS_MEDIA + && qcam->pport->probe_info[0].model + && !strcmp(qcam->pdev->port->probe_info[0].model, + "Color QuickCam 2.0")) { + printk(KERN_DEBUG "QuickCam: Found by IEEE1284 probe.\n"); + return 1; + } + + if (probe < 2) + return 0; + + parport_write_control(qcam->pport, 0xc); + + /* look for a heartbeat */ + ostat = stat = parport_read_status(qcam->pport); + for (i=0; i<250; i++) + { + mdelay(1); + stat = parport_read_status(qcam->pport); + if (ostat != stat) + { + if (++count >= 3) return 1; + ostat = stat; + } + } + + /* Reset the camera and try again */ + parport_write_control(qcam->pport, 0xc); + parport_write_control(qcam->pport, 0x8); + mdelay(1); + parport_write_control(qcam->pport, 0xc); + mdelay(1); + count = 0; + + ostat = stat = parport_read_status(qcam->pport); + for (i=0; i<250; i++) + { + mdelay(1); + stat = parport_read_status(qcam->pport); + if (ostat != stat) + { + if (++count >= 3) return 1; + ostat = stat; + } + } + + /* no (or flatline) camera, give up */ + return 0; +} + +static void qc_reset(struct qcam_device *qcam) +{ + parport_write_control(qcam->pport, 0xc); + parport_write_control(qcam->pport, 0x8); + mdelay(1); + parport_write_control(qcam->pport, 0xc); + mdelay(1); +} + +/* Reset the QuickCam and program for brightness, contrast, + * white-balance, and resolution. */ + +static void qc_setup(struct qcam_device *q) +{ + qc_reset(q); + + /* Set the brightness. */ + qcam_set(q, 11, q->brightness); + + /* Set the height and width. These refer to the actual + CCD area *before* applying the selected decimation. */ + qcam_set(q, 17, q->ccd_height); + qcam_set(q, 19, q->ccd_width / 2); + + /* Set top and left. */ + qcam_set(q, 0xd, q->top); + qcam_set(q, 0xf, q->left); + + /* Set contrast and white balance. */ + qcam_set(q, 0x19, q->contrast); + qcam_set(q, 0x1f, q->whitebal); + + /* Set the speed. */ + qcam_set(q, 45, 2); +} + +/* Read some bytes from the camera and put them in the buffer. + nbytes should be a multiple of 3, because bidirectional mode gives + us three bytes at a time. */ + +static unsigned int qcam_read_bytes(struct qcam_device *q, unsigned char *buf, unsigned int nbytes) +{ + unsigned int bytes = 0; + + qcam_set_ack(q, 0); + if (q->bidirectional) + { + /* It's a bidirectional port */ + while (bytes < nbytes) + { + unsigned int lo1, hi1, lo2, hi2; + unsigned char r, g, b; + + if (qcam_await_ready2(q, 1)) return bytes; + lo1 = parport_read_data(q->pport) >> 1; + hi1 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10; + qcam_set_ack(q, 1); + if (qcam_await_ready2(q, 0)) return bytes; + lo2 = parport_read_data(q->pport) >> 1; + hi2 = ((parport_read_status(q->pport) >> 3) & 0x1f) ^ 0x10; + qcam_set_ack(q, 0); + r = (lo1 | ((hi1 & 1)<<7)); + g = ((hi1 & 0x1e)<<3) | ((hi2 & 0x1e)>>1); + b = (lo2 | ((hi2 & 1)<<7)); + if (force_rgb) { + buf[bytes++] = r; + buf[bytes++] = g; + buf[bytes++] = b; + } else { + buf[bytes++] = b; + buf[bytes++] = g; + buf[bytes++] = r; + } + } + } + else + { + /* It's a unidirectional port */ + int i = 0, n = bytes; + unsigned char rgb[3]; + + while (bytes < nbytes) + { + unsigned int hi, lo; + + if (qcam_await_ready1(q, 1)) return bytes; + hi = (parport_read_status(q->pport) & 0xf0); + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) return bytes; + lo = (parport_read_status(q->pport) & 0xf0); + qcam_set_ack(q, 0); + /* flip some bits */ + rgb[(i = bytes++ % 3)] = (hi | (lo >> 4)) ^ 0x88; + if (i >= 2) { +get_fragment: + if (force_rgb) { + buf[n++] = rgb[0]; + buf[n++] = rgb[1]; + buf[n++] = rgb[2]; + } else { + buf[n++] = rgb[2]; + buf[n++] = rgb[1]; + buf[n++] = rgb[0]; + } + } + } + if (i) { + i = 0; + goto get_fragment; + } + } + return bytes; +} + +#define BUFSZ 150 + +static long qc_capture(struct qcam_device *q, char *buf, unsigned long len) +{ + unsigned lines, pixelsperline, bitsperxfer; + unsigned int is_bi_dir = q->bidirectional; + size_t wantlen, outptr = 0; + char tmpbuf[BUFSZ]; + + if (verify_area(VERIFY_WRITE, buf, len)) + return -EFAULT; + + /* Wait for camera to become ready */ + for (;;) + { + int i = qcam_get(q, 41); + if (i == -1) { + qc_setup(q); + return -EIO; + } + if ((i & 0x80) == 0) + break; + else + schedule(); + } + + if (qcam_set(q, 7, (q->mode | (is_bi_dir?1:0)) + 1)) + return -EIO; + + lines = q->height; + pixelsperline = q->width; + bitsperxfer = (is_bi_dir) ? 24 : 8; + + if (is_bi_dir) + { + /* Turn the port around */ + parport_data_reverse(q->pport); + mdelay(3); + qcam_set_ack(q, 0); + if (qcam_await_ready1(q, 1)) { + qc_setup(q); + return -EIO; + } + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) { + qc_setup(q); + return -EIO; + } + } + + wantlen = lines * pixelsperline * 24 / 8; + + while (wantlen) + { + size_t t, s; + s = (wantlen > BUFSZ)?BUFSZ:wantlen; + t = qcam_read_bytes(q, tmpbuf, s); + if (outptr < len) + { + size_t sz = len - outptr; + if (sz > t) sz = t; + if (__copy_to_user(buf+outptr, tmpbuf, sz)) + break; + outptr += sz; + } + wantlen -= t; + if (t < s) + break; + if (current->need_resched) + schedule(); + } + + len = outptr; + + if (wantlen) + { + printk("qcam: short read.\n"); + if (is_bi_dir) + parport_data_forward(q->pport); + qc_setup(q); + return len; + } + + if (is_bi_dir) + { + int l; + do { + l = qcam_read_bytes(q, tmpbuf, 3); + if (current->need_resched) + schedule(); + } while (l && (tmpbuf[0] == 0x7e || tmpbuf[1] == 0x7e || tmpbuf[2] == 0x7e)); + if (force_rgb) { + if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) + printk("qcam: bad EOF\n"); + } else { + if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) + printk("qcam: bad EOF\n"); + } + qcam_set_ack(q, 0); + if (qcam_await_ready1(q, 1)) + { + printk("qcam: no ack after EOF\n"); + parport_data_forward(q->pport); + qc_setup(q); + return len; + } + parport_data_forward(q->pport); + mdelay(3); + qcam_set_ack(q, 1); + if (qcam_await_ready1(q, 0)) + { + printk("qcam: no ack to port turnaround\n"); + qc_setup(q); + return len; + } + } + else + { + int l; + do { + l = qcam_read_bytes(q, tmpbuf, 1); + if (current->need_resched) + schedule(); + } while (l && tmpbuf[0] == 0x7e); + l = qcam_read_bytes(q, tmpbuf+1, 2); + if (force_rgb) { + if (tmpbuf[0] != 0xe || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xf) + printk("qcam: bad EOF\n"); + } else { + if (tmpbuf[0] != 0xf || tmpbuf[1] != 0x0 || tmpbuf[2] != 0xe) + printk("qcam: bad EOF\n"); + } + } + + qcam_write_data(q, 0); + return len; +} + +/* + * Video4linux interfacing + */ + +static int qcam_open(struct video_device *dev, int flags) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static void qcam_close(struct video_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +static int qcam_init_done(struct video_device *dev) +{ + return 0; +} + +static long qcam_write(struct video_device *v, const char *buf, unsigned long count, int noblock) +{ + return -EINVAL; +} + +static int qcam_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name, "Quickcam"); + b.type = VID_TYPE_CAPTURE|VID_TYPE_SCALES; + b.channels = 1; + b.audios = 0; + b.maxwidth = 320; + b.maxheight = 240; + b.minwidth = 80; + b.minheight = 60; + if(copy_to_user(arg, &b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.channel!=0) + return -EINVAL; + v.flags=0; + v.tuners=0; + /* Good question.. its composite or SVHS so.. */ + v.type = VIDEO_TYPE_CAMERA; + strcpy(v.name, "Camera"); + if(copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: + { + int v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + if(v!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + strcpy(v.name, "Format"); + v.rangelow=0; + v.rangehigh=0; + v.flags= 0; + v.mode = VIDEO_MODE_AUTO; + if(copy_to_user(arg,&v,sizeof(v))!=0) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + if(v.mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p; + p.colour=0x8000; + p.hue=0x8000; + p.brightness=qcam->brightness<<8; + p.contrast=qcam->contrast<<8; + p.whiteness=qcam->whitebal<<8; + p.depth=24; + p.palette=VIDEO_PALETTE_RGB24; + if(copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + if(copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + + /* + * Sanity check args + */ + if (p.depth != 24 || p.palette != VIDEO_PALETTE_RGB24) + return -EINVAL; + + /* + * Now load the camera. + */ + qcam->brightness = p.brightness>>8; + qcam->contrast = p.contrast>>8; + qcam->whitebal = p.whiteness>>8; + + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + up(&qcam->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + + if(copy_from_user(&vw, arg,sizeof(vw))) + return -EFAULT; + if(vw.flags) + return -EINVAL; + if(vw.clipcount) + return -EINVAL; + if(vw.height<60||vw.height>240) + return -EINVAL; + if(vw.width<80||vw.width>320) + return -EINVAL; + + qcam->width = 80; + qcam->height = 60; + qcam->mode = QC_DECIMATION_4; + + if(vw.width>=160 && vw.height>=120) + { + qcam->width = 160; + qcam->height = 120; + qcam->mode = QC_DECIMATION_2; + } + if(vw.width>=320 && vw.height>=240) + { + qcam->width = 320; + qcam->height = 240; + qcam->mode = QC_DECIMATION_1; + } + qcam->mode |= QC_MILLIONS; +#if 0 + if(vw.width>=640 && vw.height>=480) + { + qcam->width = 640; + qcam->height = 480; + qcam->mode = QC_BILLIONS | QC_DECIMATION_1; + } +#endif + /* Ok we figured out what to use from our + wide choice */ + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + up(&qcam->lock); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x=0; + vw.y=0; + vw.width=qcam->width; + vw.height=qcam->height; + vw.chromakey=0; + vw.flags=0; + if(copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + return -EINVAL; + case VIDIOCGFBUF: + return -EINVAL; + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCKEY: + return 0; + case VIDIOCGFREQ: + return -EINVAL; + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + return -EINVAL; + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static long qcam_read(struct video_device *v, char *buf, unsigned long count, int noblock) +{ + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + + down(&qcam->lock); + parport_claim_or_block(qcam->pdev); + /* Probably should have a semaphore against multiple users */ + len = qc_capture(qcam, buf,count); + parport_release(qcam->pdev); + up(&qcam->lock); + return len; +} + +/* video device template */ +static struct video_device qcam_template= +{ + "Colour QuickCam", + VID_TYPE_CAPTURE, + VID_HARDWARE_QCAM_C, + qcam_open, + qcam_close, + qcam_read, + qcam_write, + NULL, + qcam_ioctl, + NULL, + qcam_init_done, + NULL, + 0, + 0 +}; + +/* Initialize the QuickCam driver control structure. */ + +static struct qcam_device *qcam_init(struct parport *port) +{ + struct qcam_device *q; + + q = kmalloc(sizeof(struct qcam_device), GFP_KERNEL); + if(q==NULL) + return NULL; + + q->pport = port; + q->pdev = parport_register_device(port, "c-qcam", NULL, NULL, + NULL, 0, NULL); + + q->bidirectional = (q->pport->modes & PARPORT_MODE_TRISTATE)?1:0; + + if (q->pdev == NULL) + { + printk(KERN_ERR "c-qcam: couldn't register for %s.\n", + port->name); + kfree(q); + return NULL; + } + + memcpy(&q->vdev, &qcam_template, sizeof(qcam_template)); + + init_MUTEX(&q->lock); + q->width = q->ccd_width = 320; + q->height = q->ccd_height = 240; + q->mode = QC_MILLIONS | QC_DECIMATION_1; + q->contrast = 192; + q->brightness = 240; + q->whitebal = 128; + q->top = 1; + q->left = 14; + return q; +} + +static struct qcam_device *qcams[MAX_CAMS]; +static unsigned int num_cams = 0; + +int init_cqcam(struct parport *port) +{ + struct qcam_device *qcam; + + if (parport[0] != -1) + { + /* The user gave specific instructions */ + int i, found = 0; + for (i = 0; i < MAX_CAMS && parport[i] != -1; i++) + { + if (parport[0] == port->number) + found = 1; + } + if (!found) + return -ENODEV; + } + + if (num_cams == MAX_CAMS) + return -ENOSPC; + + qcam = qcam_init(port); + if (qcam==NULL) + return -ENODEV; + + parport_claim_or_block(qcam->pdev); + + qc_reset(qcam); + + if (probe && qc_detect(qcam)==0) + { + parport_release(qcam->pdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + qc_setup(qcam); + + parport_release(qcam->pdev); + + if (video_register_device(&qcam->vdev, VFL_TYPE_GRABBER)==-1) + { + printk(KERN_ERR "Unable to register Colour QuickCam on %s\n", + qcam->pport->name); + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + printk(KERN_INFO "video%d: Colour QuickCam found on %s\n", + qcam->vdev.minor, qcam->pport->name); + + qcams[num_cams++] = qcam; + + return 0; +} + +void close_cqcam(struct qcam_device *qcam) +{ + video_unregister_device(&qcam->vdev); + parport_unregister_device(qcam->pdev); + kfree(qcam); +} + +static void cq_attach(struct parport *port) +{ + init_cqcam(port); +} + +static void cq_detach(struct parport *port) +{ + /* Write this some day. */ +} + +static struct parport_driver cqcam_driver = { + "cqcam", + cq_attach, + cq_detach, + NULL +}; + +static int __init cqcam_init (void) +{ + printk(BANNER "\n"); + + return parport_register_driver(&cqcam_driver); +} + +static void __exit cqcam_cleanup (void) +{ + unsigned int i; + + for (i = 0; i < num_cams; i++) + close_cqcam(qcams[i]); + + parport_unregister_driver(&cqcam_driver); +} + +MODULE_AUTHOR("Philip Blundell <philb@gnu.org>"); +MODULE_DESCRIPTION(BANNER); +MODULE_PARM_DESC(parport ,"parport=<auto|n[,n]...> for port detection method\n\ +probe=<0|1|2> for camera detection method\n\ +force_rgb=<0|1> for RGB data format (default BGR)"); +MODULE_PARM(parport, "1-" __MODULE_STRING(MAX_CAMS) "i"); +MODULE_PARM(probe, "i"); +MODULE_PARM(force_rgb, "i"); + +module_init(cqcam_init); +module_exit(cqcam_cleanup); diff --git a/drivers/media/video/cpia.c b/drivers/media/video/cpia.c new file mode 100644 index 000000000..d7d007f01 --- /dev/null +++ b/drivers/media/video/cpia.c @@ -0,0 +1,3324 @@ +/* + * cpia CPiA driver + * + * Supports CPiA based Video Camera's. + * + * (C) Copyright 1999-2000 Peter Pregler, + * (C) Copyright 1999-2000 Scott J. Bertin, + * (C) Copyright 1999-2000 Johannes Erdfelt, jerdfelt@valinux.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* #define _CPIA_DEBUG_ define for verbose debug output */ +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <asm/io.h> +#include <asm/semaphore.h> +#include <linux/wrapper.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +#include "cpia.h" + +#ifdef CONFIG_VIDEO_CPIA_PP +extern int cpia_pp_init(void); +#endif +#ifdef CONFIG_VIDEO_CPIA_USB +extern int cpia_usb_init(void); +#endif + +#ifdef MODULE +MODULE_AUTHOR("Scott J. Bertin <sbertin@mindspring.com> & Peter Pregler <Peter_Pregler@email.com> & Johannes Erdfelt <jerdfelt@valinux.com>"); +MODULE_DESCRIPTION("V4L-driver for Vision CPiA based cameras"); +MODULE_SUPPORTED_DEVICE("video"); +#endif + +#define ABOUT "V4L-Driver for Vision CPiA based cameras" + +#ifndef VID_HARDWARE_CPIA +#define VID_HARDWARE_CPIA 24 /* FIXME -> from linux/videodev.h */ +#endif + +#define CPIA_MODULE_CPIA (0<<5) +#define CPIA_MODULE_SYSTEM (1<<5) +#define CPIA_MODULE_VP_CTRL (5<<5) +#define CPIA_MODULE_CAPTURE (6<<5) +#define CPIA_MODULE_DEBUG (7<<5) + +#define INPUT (DATA_IN << 8) +#define OUTPUT (DATA_OUT << 8) + +#define CPIA_COMMAND_GetCPIAVersion (INPUT | CPIA_MODULE_CPIA | 1) +#define CPIA_COMMAND_GetPnPID (INPUT | CPIA_MODULE_CPIA | 2) +#define CPIA_COMMAND_GetCameraStatus (INPUT | CPIA_MODULE_CPIA | 3) +#define CPIA_COMMAND_GotoHiPower (OUTPUT | CPIA_MODULE_CPIA | 4) +#define CPIA_COMMAND_GotoLoPower (OUTPUT | CPIA_MODULE_CPIA | 5) +#define CPIA_COMMAND_GotoSuspend (OUTPUT | CPIA_MODULE_CPIA | 7) +#define CPIA_COMMAND_GotoPassThrough (OUTPUT | CPIA_MODULE_CPIA | 8) +#define CPIA_COMMAND_ModifyCameraStatus (OUTPUT | CPIA_MODULE_CPIA | 10) + +#define CPIA_COMMAND_ReadVCRegs (INPUT | CPIA_MODULE_SYSTEM | 1) +#define CPIA_COMMAND_WriteVCReg (OUTPUT | CPIA_MODULE_SYSTEM | 2) +#define CPIA_COMMAND_ReadMCPorts (INPUT | CPIA_MODULE_SYSTEM | 3) +#define CPIA_COMMAND_WriteMCPort (OUTPUT | CPIA_MODULE_SYSTEM | 4) +#define CPIA_COMMAND_SetBaudRate (OUTPUT | CPIA_MODULE_SYSTEM | 5) +#define CPIA_COMMAND_SetECPTiming (OUTPUT | CPIA_MODULE_SYSTEM | 6) +#define CPIA_COMMAND_ReadIDATA (INPUT | CPIA_MODULE_SYSTEM | 7) +#define CPIA_COMMAND_WriteIDATA (OUTPUT | CPIA_MODULE_SYSTEM | 8) +#define CPIA_COMMAND_GenericCall (OUTPUT | CPIA_MODULE_SYSTEM | 9) +#define CPIA_COMMAND_I2CStart (OUTPUT | CPIA_MODULE_SYSTEM | 10) +#define CPIA_COMMAND_I2CStop (OUTPUT | CPIA_MODULE_SYSTEM | 11) +#define CPIA_COMMAND_I2CWrite (OUTPUT | CPIA_MODULE_SYSTEM | 12) +#define CPIA_COMMAND_I2CRead (INPUT | CPIA_MODULE_SYSTEM | 13) + +#define CPIA_COMMAND_GetVPVersion (INPUT | CPIA_MODULE_VP_CTRL | 1) +#define CPIA_COMMAND_SetColourParams (OUTPUT | CPIA_MODULE_VP_CTRL | 3) +#define CPIA_COMMAND_SetExposure (OUTPUT | CPIA_MODULE_VP_CTRL | 4) +#define CPIA_COMMAND_SetColourBalance (OUTPUT | CPIA_MODULE_VP_CTRL | 6) +#define CPIA_COMMAND_SetSensorFPS (OUTPUT | CPIA_MODULE_VP_CTRL | 7) +#define CPIA_COMMAND_SetVPDefaults (OUTPUT | CPIA_MODULE_VP_CTRL | 8) +#define CPIA_COMMAND_SetApcor (OUTPUT | CPIA_MODULE_VP_CTRL | 9) +#define CPIA_COMMAND_SetFlickerCtrl (OUTPUT | CPIA_MODULE_VP_CTRL | 10) +#define CPIA_COMMAND_SetVLOffset (OUTPUT | CPIA_MODULE_VP_CTRL | 11) +#define CPIA_COMMAND_GetColourParams (INPUT | CPIA_MODULE_VP_CTRL | 16) +#define CPIA_COMMAND_GetColourBalance (INPUT | CPIA_MODULE_VP_CTRL | 17) +#define CPIA_COMMAND_GetExposure (INPUT | CPIA_MODULE_VP_CTRL | 18) +#define CPIA_COMMAND_SetSensorMatrix (OUTPUT | CPIA_MODULE_VP_CTRL | 19) +#define CPIA_COMMAND_ColourBars (OUTPUT | CPIA_MODULE_VP_CTRL | 25) +#define CPIA_COMMAND_ReadVPRegs (INPUT | CPIA_MODULE_VP_CTRL | 30) +#define CPIA_COMMAND_WriteVPReg (OUTPUT | CPIA_MODULE_VP_CTRL | 31) + +#define CPIA_COMMAND_GrabFrame (OUTPUT | CPIA_MODULE_CAPTURE | 1) +#define CPIA_COMMAND_UploadFrame (OUTPUT | CPIA_MODULE_CAPTURE | 2) +#define CPIA_COMMAND_SetGrabMode (OUTPUT | CPIA_MODULE_CAPTURE | 3) +#define CPIA_COMMAND_InitStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 4) +#define CPIA_COMMAND_FiniStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 5) +#define CPIA_COMMAND_StartStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 6) +#define CPIA_COMMAND_EndStreamCap (OUTPUT | CPIA_MODULE_CAPTURE | 7) +#define CPIA_COMMAND_SetFormat (OUTPUT | CPIA_MODULE_CAPTURE | 8) +#define CPIA_COMMAND_SetROI (OUTPUT | CPIA_MODULE_CAPTURE | 9) +#define CPIA_COMMAND_SetCompression (OUTPUT | CPIA_MODULE_CAPTURE | 10) +#define CPIA_COMMAND_SetCompressionTarget (OUTPUT | CPIA_MODULE_CAPTURE | 11) +#define CPIA_COMMAND_SetYUVThresh (OUTPUT | CPIA_MODULE_CAPTURE | 12) +#define CPIA_COMMAND_SetCompressionParams (OUTPUT | CPIA_MODULE_CAPTURE | 13) +#define CPIA_COMMAND_DiscardFrame (OUTPUT | CPIA_MODULE_CAPTURE | 14) + +#define CPIA_COMMAND_OutputRS232 (OUTPUT | CPIA_MODULE_DEBUG | 1) +#define CPIA_COMMAND_AbortProcess (OUTPUT | CPIA_MODULE_DEBUG | 4) +#define CPIA_COMMAND_SetDramPage (OUTPUT | CPIA_MODULE_DEBUG | 5) +#define CPIA_COMMAND_StartDramUpload (OUTPUT | CPIA_MODULE_DEBUG | 6) +#define CPIA_COMMAND_StartDummyDtream (OUTPUT | CPIA_MODULE_DEBUG | 8) +#define CPIA_COMMAND_AbortStream (OUTPUT | CPIA_MODULE_DEBUG | 9) +#define CPIA_COMMAND_DownloadDRAM (OUTPUT | CPIA_MODULE_DEBUG | 10) + +enum { + FRAME_READY, /* Ready to grab into */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_UNUSED, /* Unused (no MCAPTURE) */ +}; + +#define COMMAND_NONE 0x0000 +#define COMMAND_SETCOMPRESSION 0x0001 +#define COMMAND_SETCOMPRESSIONTARGET 0x0002 +#define COMMAND_SETCOLOURPARAMS 0x0004 +#define COMMAND_SETFORMAT 0x0008 +#define COMMAND_PAUSE 0x0010 +#define COMMAND_RESUME 0x0020 +#define COMMAND_SETYUVTHRESH 0x0040 +#define COMMAND_SETECPTIMING 0x0080 +#define COMMAND_SETCOMPRESSIONPARAMS 0x0100 +#define COMMAND_SETEXPOSURE 0x0200 +#define COMMAND_SETCOLOURBALANCE 0x0400 +#define COMMAND_SETSENSORFPS 0x0800 +#define COMMAND_SETAPCOR 0x1000 +#define COMMAND_SETFLICKERCTRL 0x2000 +#define COMMAND_SETVLOFFSET 0x4000 + +/* Developer's Guide Table 5 p 3-34 + * indexed by [mains][sensorFps.baserate][sensorFps.divisor]*/ +static u8 flicker_jumps[2][2][4] = +{ { { 76, 38, 19, 9 }, { 92, 46, 23, 11 } }, + { { 64, 32, 16, 8 }, { 76, 38, 19, 9} } +}; + +/* forward declaration of local function */ +static void reset_camera_struct(struct cam_data *cam); + +/********************************************************************** + * + * Memory management + * + * This is a shameless copy from the USB-cpia driver (linux kernel + * version 2.3.29 or so, I have no idea what this code actually does ;). + * Actually it seems to be a copy of a shameless copy of the bttv-driver. + * Or that is a copy of a shameless copy of ... (To the powers: is there + * no generic kernel-function to do this sort of stuff?) + * + * Yes, it was a shameless copy from the bttv-driver. IIRC, Alan says + * there will be one, but apparentely not yet - jerdfelt + * + **********************************************************************/ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none(*pgd)) { + pmd = pmd_offset(pgd, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset(pmd, adr); + pte = *ptep; + if (pte_present(pte)) { + ret = (unsigned long) page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE-1)); + } + } + } + return ret; +} + +/* Here we want the physical address of the memory. + * This is used when initializing the contents of the + * area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = __pa(kva); + return ret; +} + +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr, page; + + /* Round it off to PAGE_SIZE */ + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa(adr); + mem_map_reserve(virt_to_page(__va(page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr, page; + + if (!mem) + return; + + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + adr = (unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa(adr); + mem_map_unreserve(virt_to_page(__va(page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + vfree(mem); +} + +/********************************************************************** + * + * /proc interface + * + **********************************************************************/ +#ifdef CONFIG_PROC_FS +static struct proc_dir_entry *cpia_proc_root=NULL; + +static int cpia_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + int len, tmp; + struct cam_data *cam = data; + char tmpstr[20]; + + /* IMPORTANT: This output MUST be kept under PAGE_SIZE + * or we need to get more sophisticated. */ + + out += sprintf(out, "read-only\n-----------------------\n"); + out += sprintf(out, "V4L Driver version: %d.%d.%d\n", + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); + out += sprintf(out, "CPIA Version: %d.%02d (%d.%d)\n", + cam->params.version.firmwareVersion, + cam->params.version.firmwareRevision, + cam->params.version.vcVersion, + cam->params.version.vcRevision); + out += sprintf(out, "CPIA PnP-ID: %04x:%04x:%04x\n", + cam->params.pnpID.vendor, cam->params.pnpID.product, + cam->params.pnpID.deviceRevision); + out += sprintf(out, "VP-Version: %d.%d %04x\n", + cam->params.vpVersion.vpVersion, + cam->params.vpVersion.vpRevision, + cam->params.vpVersion.cameraHeadID); + + out += sprintf(out, "system_state: %#04x\n", + cam->params.status.systemState); + out += sprintf(out, "grab_state: %#04x\n", + cam->params.status.grabState); + out += sprintf(out, "stream_state: %#04x\n", + cam->params.status.streamState); + out += sprintf(out, "fatal_error: %#04x\n", + cam->params.status.fatalError); + out += sprintf(out, "cmd_error: %#04x\n", + cam->params.status.cmdError); + out += sprintf(out, "debug_flags: %#04x\n", + cam->params.status.debugFlags); + out += sprintf(out, "vp_status: %#04x\n", + cam->params.status.vpStatus); + out += sprintf(out, "error_code: %#04x\n", + cam->params.status.errorCode); + out += sprintf(out, "video_size: %s\n", + cam->params.format.videoSize == VIDEOSIZE_CIF ? + "CIF " : "QCIF"); + out += sprintf(out, "sub_sample: %s\n", + cam->params.format.subSample == SUBSAMPLE_420 ? + "420" : "422"); + out += sprintf(out, "yuv_order: %s\n", + cam->params.format.yuvOrder == YUVORDER_YUYV ? + "YUYV" : "UYVY"); + out += sprintf(out, "roi: (%3d, %3d) to (%3d, %3d)\n", + cam->params.roi.colStart*8, + cam->params.roi.rowStart*4, + cam->params.roi.colEnd*8, + cam->params.roi.rowEnd*4); + out += sprintf(out, "actual_fps: %3d\n", cam->fps); + out += sprintf(out, "transfer_rate: %4dkB/s\n", + cam->transfer_rate); + + out += sprintf(out, "\nread-write\n"); + out += sprintf(out, "----------------------- current min" + " max default comment\n"); + out += sprintf(out, "brightness: %8d %8d %8d %8d\n", + cam->params.colourParams.brightness, 0, 100, 50); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits contrast to 80 */ + tmp = 80; + else + tmp = 96; + + out += sprintf(out, "contrast: %8d %8d %8d %8d" + " steps of 8\n", + cam->params.colourParams.contrast, 0, tmp, 48); + out += sprintf(out, "saturation: %8d %8d %8d %8d\n", + cam->params.colourParams.saturation, 0, 100, 50); + tmp = (25000+5000*cam->params.sensorFps.baserate)/ + (1<<cam->params.sensorFps.divisor); + out += sprintf(out, "sensor_fps: %4d.%03d %8d %8d %8d\n", + tmp/1000, tmp%1000, 3, 30, 15); + out += sprintf(out, "stream_start_line: %8d %8d %8d %8d\n", + 2*cam->params.streamStartLine, 0, + cam->params.format.videoSize == VIDEOSIZE_CIF ? 288:144, + cam->params.format.videoSize == VIDEOSIZE_CIF ? 240:120); + out += sprintf(out, "ecp_timing: %8s %8s %8s %8s\n", + cam->params.ecpTiming ? "slow" : "normal", "slow", + "normal", "normal"); + + if (cam->params.colourBalance.balanceModeIsAuto) { + sprintf(tmpstr, "auto"); + } else { + sprintf(tmpstr, "manual"); + } + out += sprintf(out, "color_balance_mode: %8s %8s %8s" + " %8s\n", tmpstr, "manual", "auto", "auto"); + out += sprintf(out, "red_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.redGain, 0, 212, 32); + out += sprintf(out, "green_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.greenGain, 0, 212, 6); + out += sprintf(out, "blue_gain: %8d %8d %8d %8d\n", + cam->params.colourBalance.blueGain, 0, 212, 92); + + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits gain to 2 */ + sprintf(tmpstr, "%8d %8d", 1, 2); + else + sprintf(tmpstr, "1,2,4,8"); + + if (cam->params.exposure.gainMode == 0) + out += sprintf(out, "max_gain: unknown %18s" + " %8d\n", tmpstr, 2); + else + out += sprintf(out, "max_gain: %8d %18s %8d\n", + 1<<(cam->params.exposure.gainMode-1), tmpstr, 2); + + switch(cam->params.exposure.expMode) { + case 1: + case 3: + sprintf(tmpstr, "manual"); + break; + case 2: + sprintf(tmpstr, "auto"); + break; + default: + sprintf(tmpstr, "unknown"); + break; + } + out += sprintf(out, "exposure_mode: %8s %8s %8s" + " %8s\n", tmpstr, "manual", "auto", "auto"); + out += sprintf(out, "centre_weight: %8s %8s %8s %8s\n", + (2-cam->params.exposure.centreWeight) ? "on" : "off", + "off", "on", "on"); + out += sprintf(out, "gain: %8d %8d max_gain %8d 1,2,4,8 possible\n", + 1<<cam->params.exposure.gain, 1, 1); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits fineExp to 127 */ + tmp = 255; + else + tmp = 511; + + out += sprintf(out, "fine_exp: %8d %8d %8d %8d\n", + cam->params.exposure.fineExp*2, 0, tmp, 0); + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2) + /* 1-02 firmware limits coarseExpHi to 0 */ + tmp = 255; + else + tmp = 65535; + + out += sprintf(out, "coarse_exp: %8d %8d %8d" + " %8d\n", cam->params.exposure.coarseExpLo+ + 256*cam->params.exposure.coarseExpHi, 0, tmp, 185); + out += sprintf(out, "red_comp: %8d %8d %8d %8d\n", + cam->params.exposure.redComp, 220, 255, 220); + out += sprintf(out, "green1_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green1Comp, 214, 255, 214); + out += sprintf(out, "green2_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green2Comp, 214, 255, 214); + out += sprintf(out, "blue_comp: %8d %8d %8d %8d\n", + cam->params.exposure.blueComp, 230, 255, 230); + + out += sprintf(out, "apcor_gain1: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain1, 0, 0xff, 0x1c); + out += sprintf(out, "apcor_gain2: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain2, 0, 0xff, 0x1a); + out += sprintf(out, "apcor_gain4: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain4, 0, 0xff, 0x2d); + out += sprintf(out, "apcor_gain8: %#8x %#8x %#8x %#8x\n", + cam->params.apcor.gain8, 0, 0xff, 0x2a); + out += sprintf(out, "vl_offset_gain1: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain1, 0, 255, 24); + out += sprintf(out, "vl_offset_gain2: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain2, 0, 255, 28); + out += sprintf(out, "vl_offset_gain4: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain4, 0, 255, 30); + out += sprintf(out, "vl_offset_gain8: %8d %8d %8d %8d\n", + cam->params.vlOffset.gain8, 0, 255, 30); + out += sprintf(out, "flicker_control: %8s %8s %8s %8s\n", + cam->params.flickerControl.flickerMode ? "on" : "off", + "off", "on", "off"); + out += sprintf(out, "mains_frequency: %8d %8d %8d %8d" + " only 50/60\n", + cam->mainsFreq ? 60 : 50, 50, 60, 50); + out += sprintf(out, "allowable_overexposure: %8d %8d %8d %8d\n", + cam->params.flickerControl.allowableOverExposure, 0, + 255, 0); + out += sprintf(out, "compression_mode: "); + switch(cam->params.compression.mode) { + case CPIA_COMPRESSION_NONE: + out += sprintf(out, "%8s", "none"); + break; + case CPIA_COMPRESSION_AUTO: + out += sprintf(out, "%8s", "auto"); + break; + case CPIA_COMPRESSION_MANUAL: + out += sprintf(out, "%8s", "manual"); + break; + default: + out += sprintf(out, "%8s", "unknown"); + break; + } + out += sprintf(out, " none,auto,manual auto\n"); + out += sprintf(out, "decimation_enable: %8s %8s %8s %8s\n", + cam->params.compression.decimation == + DECIMATION_ENAB ? "on":"off", "off", "off", + "off"); + out += sprintf(out, "compression_target: %9s %9s %9s %9s\n", + cam->params.compressionTarget.frTargeting == + CPIA_COMPRESSION_TARGET_FRAMERATE ? + "framerate":"quality", + "framerate", "quality", "quality"); + out += sprintf(out, "target_framerate: %8d %8d %8d %8d\n", + cam->params.compressionTarget.targetFR, 0, 30, 7); + out += sprintf(out, "target_quality: %8d %8d %8d %8d\n", + cam->params.compressionTarget.targetQ, 0, 255, 10); + out += sprintf(out, "y_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.yThreshold, 0, 31, 15); + out += sprintf(out, "uv_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.uvThreshold, 0, 31, 15); + out += sprintf(out, "hysteresis: %8d %8d %8d %8d\n", + cam->params.compressionParams.hysteresis, 0, 255, 3); + out += sprintf(out, "threshold_max: %8d %8d %8d %8d\n", + cam->params.compressionParams.threshMax, 0, 255, 11); + out += sprintf(out, "small_step: %8d %8d %8d %8d\n", + cam->params.compressionParams.smallStep, 0, 255, 1); + out += sprintf(out, "large_step: %8d %8d %8d %8d\n", + cam->params.compressionParams.largeStep, 0, 255, 3); + out += sprintf(out, "decimation_hysteresis: %8d %8d %8d %8d\n", + cam->params.compressionParams.decimationHysteresis, + 0, 255, 2); + out += sprintf(out, "fr_diff_step_thresh: %8d %8d %8d %8d\n", + cam->params.compressionParams.frDiffStepThresh, + 0, 255, 5); + out += sprintf(out, "q_diff_step_thresh: %8d %8d %8d %8d\n", + cam->params.compressionParams.qDiffStepThresh, + 0, 255, 3); + out += sprintf(out, "decimation_thresh_mod: %8d %8d %8d %8d\n", + cam->params.compressionParams.decimationThreshMod, + 0, 255, 2); + + len = out - page; + len -= off; + if (len < count) { + *eof = 1; + if (len <= 0) return 0; + } else + len = count; + + *start = page + off; + return len; +} + +static int cpia_write_proc(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + struct cam_data *cam = data; + struct cam_params new_params; + int retval, find_colon; + int size = count; + unsigned long val; + u32 command_flags = 0; + u8 new_mains; + + if (down_interruptible(&cam->param_lock)) + return -ERESTARTSYS; + + /* + * Skip over leading whitespace + */ + while (count && isspace(*buffer)) { + --count; + ++buffer; + } + + memcpy(&new_params, &cam->params, sizeof(struct cam_params)); + new_mains = cam->mainsFreq; + +#define MATCH(x) \ + ({ \ + int _len = strlen(x), _ret, _colon_found; \ + _ret = (_len <= count && strncmp(buffer, x, _len) == 0); \ + if (_ret) { \ + buffer += _len; \ + count -= _len; \ + if (find_colon) { \ + _colon_found = 0; \ + while (count && (*buffer == ' ' || *buffer == '\t' || \ + (!_colon_found && *buffer == ':'))) { \ + if (*buffer == ':') \ + _colon_found = 1; \ + --count; \ + ++buffer; \ + } \ + if (!count || !_colon_found) \ + retval = -EINVAL; \ + find_colon = 0; \ + } \ + } \ + _ret; \ + }) +#define FIRMWARE_VERSION(x,y) (new_params.version.firmwareVersion == (x) && \ + new_params.version.firmwareRevision == (y)) +#define VALUE \ + ({ \ + char *_p; \ + unsigned long int _ret; \ + _ret = simple_strtoul(buffer, &_p, 0); \ + if (_p == buffer) \ + retval = -EINVAL; \ + else { \ + count -= _p - buffer; \ + buffer = _p; \ + } \ + _ret; \ + }) + + retval = 0; + while (count && !retval) { + find_colon = 1; + if (MATCH("brightness")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) + new_params.colourParams.brightness = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + } else if (MATCH("contrast")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) { + /* contrast is in steps of 8, so round*/ + val = ((val + 3) / 8) * 8; + /* 1-02 firmware limits contrast to 80*/ + if (FIRMWARE_VERSION(1,2) && val > 80) + val = 80; + + new_params.colourParams.contrast = val; + } else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + } else if (MATCH("saturation")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 100) + new_params.colourParams.saturation = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURPARAMS; + } else if (MATCH("sensor_fps")) { + if (!retval) + val = VALUE; + + if (!retval) { + /* find values so that sensorFPS is minimized, + * but >= val */ + if (val > 30) + retval = -EINVAL; + else if (val > 25) { + new_params.sensorFps.divisor = 0; + new_params.sensorFps.baserate = 1; + } else if (val > 15) { + new_params.sensorFps.divisor = 0; + new_params.sensorFps.baserate = 0; + } else if (val > 12) { + new_params.sensorFps.divisor = 1; + new_params.sensorFps.baserate = 1; + } else if (val > 7) { + new_params.sensorFps.divisor = 1; + new_params.sensorFps.baserate = 0; + } else if (val > 6) { + new_params.sensorFps.divisor = 2; + new_params.sensorFps.baserate = 1; + } else if (val > 3) { + new_params.sensorFps.divisor = 2; + new_params.sensorFps.baserate = 0; + } else { + new_params.sensorFps.divisor = 3; + /* Either base rate would work here */ + new_params.sensorFps.baserate = 1; + } + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } + command_flags |= COMMAND_SETSENSORFPS; + } else if (MATCH("stream_start_line")) { + if (!retval) + val = VALUE; + + if (!retval) { + int max_line = 288; + + if (new_params.format.videoSize == VIDEOSIZE_QCIF) + max_line = 144; + if (val <= max_line) + new_params.streamStartLine = val/2; + else + retval = -EINVAL; + } + } else if (MATCH("ecp_timing")) { + if (!retval && MATCH("normal")) + new_params.ecpTiming = 0; + else if (!retval && MATCH("slow")) + new_params.ecpTiming = 1; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETECPTIMING; + } else if (MATCH("color_balance_mode")) { + if (!retval && MATCH("manual")) + new_params.colourBalance.balanceModeIsAuto = 0; + else if (!retval && MATCH("auto")) + new_params.colourBalance.balanceModeIsAuto = 1; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("red_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) + new_params.colourBalance.redGain = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("green_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) + new_params.colourBalance.greenGain = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("blue_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 212) + new_params.colourBalance.blueGain = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOLOURBALANCE; + } else if (MATCH("max_gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + /* 1-02 firmware limits gain to 2 */ + if (FIRMWARE_VERSION(1,2) && val > 2) + val = 2; + switch(val) { + case 1: + new_params.exposure.gainMode = 1; + break; + case 2: + new_params.exposure.gainMode = 2; + break; + case 4: + new_params.exposure.gainMode = 3; + break; + case 8: + new_params.exposure.gainMode = 4; + break; + default: + retval = -EINVAL; + break; + } + } + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("exposure_mode")) { + if (!retval && MATCH("auto")) + new_params.exposure.expMode = 2; + else if (!retval && MATCH("manual")) { + if (new_params.exposure.expMode == 2) + new_params.exposure.expMode = 3; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("centre_weight")) { + if (!retval && MATCH("on")) + new_params.exposure.centreWeight = 1; + else if (!retval && MATCH("off")) + new_params.exposure.centreWeight = 2; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETEXPOSURE; + } else if (MATCH("gain")) { + if (!retval) + val = VALUE; + + if (!retval) { + switch(val) { + case 1: + new_params.exposure.gain = 0; + new_params.exposure.expMode = 1; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + break; + case 2: + new_params.exposure.gain = 1; + new_params.exposure.expMode = 1; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + break; + case 4: + new_params.exposure.gain = 2; + new_params.exposure.expMode = 1; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + break; + case 8: + new_params.exposure.gain = 3; + new_params.exposure.expMode = 1; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + break; + default: + retval = -EINVAL; + break; + } + command_flags |= COMMAND_SETEXPOSURE; + if (new_params.exposure.gain > + new_params.exposure.gainMode-1) + retval = -EINVAL; + } + } else if (MATCH("fine_exp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 256) { + /* 1-02 firmware limits fineExp to 127*/ + if (FIRMWARE_VERSION(1,2) && val > 127) + val = 127; + new_params.exposure.fineExp = val; + new_params.exposure.expMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } else if (MATCH("coarse_exp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 65536) { + /* 1-02 firmware limits + * coarseExp to 255 */ + if (FIRMWARE_VERSION(1,2) && val > 255) + val = 255; + new_params.exposure.coarseExpLo = + val & 0xff; + new_params.exposure.coarseExpHi = + val >> 8; + new_params.exposure.expMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + new_params.flickerControl.flickerMode = 0; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } else if (MATCH("red_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= 220 && val <= 255) { + new_params.exposure.redComp = val; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green1_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= 214 && val <= 255) { + new_params.exposure.green1Comp = val; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green2_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= 214 && val <= 255) { + new_params.exposure.green2Comp = val; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("blue_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= 230 && val <= 255) { + new_params.exposure.blueComp = val; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain1")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain1 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain2")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain2 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain4")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain4 = val; + else + retval = -EINVAL; + } + } else if (MATCH("apcor_gain8")) { + if (!retval) + val = VALUE; + + if (!retval) { + command_flags |= COMMAND_SETAPCOR; + if (val <= 0xff) + new_params.apcor.gain8 = val; + else + retval = -EINVAL; + } + } else if (MATCH("vl_offset_gain1")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain1 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain2")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain2 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain4")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain4 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("vl_offset_gain8")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.vlOffset.gain8 = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETVLOFFSET; + } else if (MATCH("flicker_control")) { + if (!retval && MATCH("on")) { + new_params.flickerControl.flickerMode = 1; + new_params.exposure.expMode = 2; + command_flags |= COMMAND_SETEXPOSURE; + } else if (!retval && MATCH("off")) + new_params.flickerControl.flickerMode = 0; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETFLICKERCTRL; + } else if (MATCH("mains_frequency")) { + if (!retval && MATCH("50")) { + new_mains = 0; + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } else if (!retval && MATCH("60")) { + new_mains = 1; + new_params.flickerControl.coarseJump = + flicker_jumps[new_mains] + [new_params.sensorFps.baserate] + [new_params.sensorFps.divisor]; + if (new_params.flickerControl.flickerMode) + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } else if (MATCH("allowable_overexposure")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) { + new_params.flickerControl. + allowableOverExposure = val; + command_flags |= COMMAND_SETFLICKERCTRL; + } else + retval = -EINVAL; + } + } else if (MATCH("compression_mode")) { + if (!retval && MATCH("none")) + new_params.compression.mode = + CPIA_COMPRESSION_NONE; + else if (!retval && MATCH("auto")) + new_params.compression.mode = + CPIA_COMPRESSION_AUTO; + else if (!retval && MATCH("manual")) + new_params.compression.mode = + CPIA_COMPRESSION_MANUAL; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSION; + } else if (MATCH("decimation_enable")) { + if (!retval && MATCH("off")) + new_params.compression.decimation = 0; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSION; + } else if (MATCH("compression_target")) { + if (!retval && MATCH("quality")) + new_params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_QUALITY; + else if (!retval && MATCH("framerate")) + new_params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_FRAMERATE; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("target_framerate")) { + if (!retval) + val = VALUE; + + if (!retval) + new_params.compressionTarget.targetFR = val; + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("target_quality")) { + if (!retval) + val = VALUE; + + if (!retval) + new_params.compressionTarget.targetQ = val; + + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("y_threshold")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 32) + new_params.yuvThreshold.yThreshold = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETYUVTHRESH; + } else if (MATCH("uv_threshold")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val < 32) + new_params.yuvThreshold.uvThreshold = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETYUVTHRESH; + } else if (MATCH("hysteresis")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.hysteresis = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("threshold_max")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.threshMax = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("small_step")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.smallStep = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("large_step")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.largeStep = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("decimation_hysteresis")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.decimationHysteresis = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("fr_diff_step_thresh")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.frDiffStepThresh = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("q_diff_step_thresh")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.qDiffStepThresh = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else if (MATCH("decimation_thresh_mod")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) + new_params.compressionParams.decimationThreshMod = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONPARAMS; + } else { + DBG("No match found\n"); + retval = -EINVAL; + } + + if (!retval) { + while (count && isspace(*buffer) && *buffer != '\n') { + --count; + ++buffer; + } + if (count) { + if (*buffer != '\n' && *buffer != ';') + retval = -EINVAL; + else { + --count; + ++buffer; + } + } + } + } +#undef MATCH +#undef FIRMWARE_VERSION +#undef VALUE +#undef FIND_VALUE +#undef FIND_END + if (!retval) { + if (command_flags & COMMAND_SETCOLOURPARAMS) { + /* Adjust cam->vp to reflect these changes */ + cam->vp.brightness = + new_params.colourParams.brightness*65535/100; + cam->vp.contrast = + new_params.colourParams.contrast*65535/100; + cam->vp.colour = + new_params.colourParams.saturation*65535/100; + } + + memcpy(&cam->params, &new_params, sizeof(struct cam_params)); + cam->mainsFreq = new_mains; + cam->cmd_queue |= command_flags; + retval = size; + } else + DBG("error: %d\n", retval); + + up(&cam->param_lock); + + return retval; +} + +static void create_proc_cpia_cam(struct cam_data *cam) +{ + char name[7]; + struct proc_dir_entry *ent; + + if (!cpia_proc_root || !cam) + return; + + sprintf(name, "video%d", cam->vdev.minor); + + ent = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, cpia_proc_root); + if (!ent) + return; + + ent->data = cam; + ent->read_proc = cpia_read_proc; + ent->write_proc = cpia_write_proc; + ent->size = 3626; + cam->proc_entry = ent; +} + +static void destroy_proc_cpia_cam(struct cam_data *cam) +{ + char name[7]; + + if (!cam || !cam->proc_entry) + return; + + sprintf(name, "video%d", cam->vdev.minor); + remove_proc_entry(name, cpia_proc_root); + cam->proc_entry = NULL; +} + +static void proc_cpia_create(void) +{ + cpia_proc_root = create_proc_entry("cpia", S_IFDIR, 0); + + if (cpia_proc_root) + cpia_proc_root->owner = THIS_MODULE; + else + LOG("Unable to initialise /proc/cpia\n"); +} + +static void proc_cpia_destroy(void) +{ + remove_proc_entry("cpia", 0); +} +#endif /* CONFIG_PROC_FS */ + +/* ----------------------- debug functions ---------------------- */ + +#define printstatus(cam) \ + DBG("%02x %02x %02x %02x %02x %02x %02x %02x\n",\ + cam->params.status.systemState, cam->params.status.grabState, \ + cam->params.status.streamState, cam->params.status.fatalError, \ + cam->params.status.cmdError, cam->params.status.debugFlags, \ + cam->params.status.vpStatus, cam->params.status.errorCode); + +/* ----------------------- v4l helpers -------------------------- */ + +/* supported frame palettes and depths */ +static inline int valid_mode(u16 palette, u16 depth) +{ + return (palette == VIDEO_PALETTE_GREY && depth == 8) || + (palette == VIDEO_PALETTE_RGB555 && depth == 16) || + (palette == VIDEO_PALETTE_RGB565 && depth == 16) || + (palette == VIDEO_PALETTE_RGB24 && depth == 24) || + (palette == VIDEO_PALETTE_RGB32 && depth == 32) || + (palette == VIDEO_PALETTE_YUV422 && depth == 16) || + (palette == VIDEO_PALETTE_YUYV && depth == 16) || + (palette == VIDEO_PALETTE_UYVY && depth == 16); +} + +static int match_videosize( int width, int height ) +{ + /* return the best match, where 'best' is as always + * the largest that is not bigger than what is requested. */ + if (width>=352 && height>=288) + return VIDEOSIZE_352_288; /* CIF */ + + if (width>=320 && height>=240) + return VIDEOSIZE_320_240; /* SIF */ + + if (width>=288 && height>=216) + return VIDEOSIZE_288_216; + + if (width>=256 && height>=192) + return VIDEOSIZE_256_192; + + if (width>=224 && height>=168) + return VIDEOSIZE_224_168; + + if (width>=192 && height>=144) + return VIDEOSIZE_192_144; + + if (width>=176 && height>=144) + return VIDEOSIZE_176_144; /* QCIF */ + + if (width>=160 && height>=120) + return VIDEOSIZE_160_120; /* QSIF */ + + if (width>=128 && height>=96) + return VIDEOSIZE_128_96; + + if (width>=88 && height>=72) + return VIDEOSIZE_88_72; + + if (width>=64 && height>=48) + return VIDEOSIZE_64_48; + + if (width>=48 && height>=48) + return VIDEOSIZE_48_48; + + return -1; +} + +/* these are the capture sizes we support */ +static void set_vw_size(struct cam_data *cam) +{ + /* the col/row/start/end values are the result of simple math */ + /* study the SetROI-command in cpia developers guide p 2-22 */ + /* streamStartLine is set to the recommended value in the cpia */ + /* developers guide p 3-37 */ + switch(cam->video_size) { + case VIDEOSIZE_CIF: + cam->vw.width = 352; + cam->vw.height = 288; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=0; + cam->params.roi.colEnd=44; + cam->params.roi.rowStart=0; + cam->params.roi.rowEnd=72; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_SIF: + cam->vw.width = 320; + cam->vw.height = 240; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=2; + cam->params.roi.colEnd=42; + cam->params.roi.rowStart=6; + cam->params.roi.rowEnd=66; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_288_216: + cam->vw.width = 288; + cam->vw.height = 216; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=4; + cam->params.roi.colEnd=40; + cam->params.roi.rowStart=9; + cam->params.roi.rowEnd=63; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_256_192: + cam->vw.width = 256; + cam->vw.height = 192; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=6; + cam->params.roi.colEnd=38; + cam->params.roi.rowStart=12; + cam->params.roi.rowEnd=60; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_224_168: + cam->vw.width = 224; + cam->vw.height = 168; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=8; + cam->params.roi.colEnd=36; + cam->params.roi.rowStart=15; + cam->params.roi.rowEnd=57; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_192_144: + cam->vw.width = 192; + cam->vw.height = 144; + cam->params.format.videoSize=VIDEOSIZE_CIF; + cam->params.roi.colStart=10; + cam->params.roi.colEnd=34; + cam->params.roi.rowStart=18; + cam->params.roi.rowEnd=54; + cam->params.streamStartLine = 120; + break; + case VIDEOSIZE_QCIF: + cam->vw.width = 176; + cam->vw.height = 144; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=0; + cam->params.roi.colEnd=22; + cam->params.roi.rowStart=0; + cam->params.roi.rowEnd=36; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_QSIF: + cam->vw.width = 160; + cam->vw.height = 120; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=1; + cam->params.roi.colEnd=21; + cam->params.roi.rowStart=3; + cam->params.roi.rowEnd=33; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_128_96: + cam->vw.width = 128; + cam->vw.height = 96; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=3; + cam->params.roi.colEnd=19; + cam->params.roi.rowStart=6; + cam->params.roi.rowEnd=30; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_88_72: + cam->vw.width = 88; + cam->vw.height = 72; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=5; + cam->params.roi.colEnd=16; + cam->params.roi.rowStart=9; + cam->params.roi.rowEnd=27; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_64_48: + cam->vw.width = 64; + cam->vw.height = 48; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=7; + cam->params.roi.colEnd=15; + cam->params.roi.rowStart=12; + cam->params.roi.rowEnd=24; + cam->params.streamStartLine = 60; + break; + case VIDEOSIZE_48_48: + cam->vw.width = 48; + cam->vw.height = 48; + cam->params.format.videoSize=VIDEOSIZE_QCIF; + cam->params.roi.colStart=8; + cam->params.roi.colEnd=14; + cam->params.roi.rowStart=6; + cam->params.roi.rowEnd=30; + cam->params.streamStartLine = 60; + break; + default: + LOG("bad videosize value: %d\n", cam->video_size); + } + + return; +} + +static int allocate_frame_buf(struct cam_data *cam) +{ + int i; + + cam->frame_buf = rvmalloc(FRAME_NUM * CPIA_MAX_FRAME_SIZE); + if (!cam->frame_buf) + return -ENOBUFS; + + for (i = 0; i < FRAME_NUM; i++) + cam->frame[i].data = cam->frame_buf + i * CPIA_MAX_FRAME_SIZE; + + return 0; +} + +static int free_frame_buf(struct cam_data *cam) +{ + int i; + + rvfree(cam->frame_buf, FRAME_NUM*CPIA_MAX_FRAME_SIZE); + cam->frame_buf = 0; + for (i=0; i < FRAME_NUM; i++) + cam->frame[i].data = NULL; + + return 0; +} + + +static void inline free_frames(struct cpia_frame frame[FRAME_NUM]) +{ + int i; + + for (i=0; i < FRAME_NUM; i++) + frame[i].state = FRAME_UNUSED; + return; +} + +/********************************************************************** + * + * General functions + * + **********************************************************************/ +/* send an arbitrary command to the camera */ +static int do_command(struct cam_data *cam, u16 command, u8 a, u8 b, u8 c, u8 d) +{ + int retval, datasize; + u8 cmd[8], data[8]; + + switch(command) { + case CPIA_COMMAND_GetCPIAVersion: + case CPIA_COMMAND_GetPnPID: + case CPIA_COMMAND_GetCameraStatus: + case CPIA_COMMAND_GetVPVersion: + datasize=8; + break; + case CPIA_COMMAND_GetColourParams: + case CPIA_COMMAND_GetColourBalance: + case CPIA_COMMAND_GetExposure: + down(&cam->param_lock); + datasize=8; + break; + default: + datasize=0; + break; + } + + cmd[0] = command>>8; + cmd[1] = command&0xff; + cmd[2] = a; + cmd[3] = b; + cmd[4] = c; + cmd[5] = d; + cmd[6] = datasize; + cmd[7] = 0; + + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) { + DBG("%x - failed, retval=%d\n", command, retval); + if (command == CPIA_COMMAND_GetColourParams || + command == CPIA_COMMAND_GetColourBalance || + command == CPIA_COMMAND_GetExposure) + up(&cam->param_lock); + } else { + switch(command) { + case CPIA_COMMAND_GetCPIAVersion: + cam->params.version.firmwareVersion = data[0]; + cam->params.version.firmwareRevision = data[1]; + cam->params.version.vcVersion = data[2]; + cam->params.version.vcRevision = data[3]; + break; + case CPIA_COMMAND_GetPnPID: + cam->params.pnpID.vendor = data[0]+(((u16)data[1])<<8); + cam->params.pnpID.product = data[2]+(((u16)data[3])<<8); + cam->params.pnpID.deviceRevision = + data[4]+(((u16)data[5])<<8); + break; + case CPIA_COMMAND_GetCameraStatus: + cam->params.status.systemState = data[0]; + cam->params.status.grabState = data[1]; + cam->params.status.streamState = data[2]; + cam->params.status.fatalError = data[3]; + cam->params.status.cmdError = data[4]; + cam->params.status.debugFlags = data[5]; + cam->params.status.vpStatus = data[6]; + cam->params.status.errorCode = data[7]; + break; + case CPIA_COMMAND_GetVPVersion: + cam->params.vpVersion.vpVersion = data[0]; + cam->params.vpVersion.vpRevision = data[1]; + cam->params.vpVersion.cameraHeadID = + data[2]+(((u16)data[3])<<8); + break; + case CPIA_COMMAND_GetColourParams: + cam->params.colourParams.brightness = data[0]; + cam->params.colourParams.contrast = data[1]; + cam->params.colourParams.saturation = data[2]; + up(&cam->param_lock); + break; + case CPIA_COMMAND_GetColourBalance: + cam->params.colourBalance.redGain = data[0]; + cam->params.colourBalance.greenGain = data[1]; + cam->params.colourBalance.blueGain = data[2]; + up(&cam->param_lock); + break; + case CPIA_COMMAND_GetExposure: + cam->params.exposure.gain = data[0]; + cam->params.exposure.fineExp = data[1]; + cam->params.exposure.coarseExpLo = data[2]; + cam->params.exposure.coarseExpHi = data[3]; + cam->params.exposure.redComp = data[4]; + cam->params.exposure.green1Comp = data[5]; + cam->params.exposure.green2Comp = data[6]; + cam->params.exposure.blueComp = data[7]; + /* If the *Comp parameters are wacko, generate + * a warning, and reset them back to default + * values. - rich@annexia.org + */ + if (cam->params.exposure.redComp < 220 || + cam->params.exposure.redComp > 255 || + cam->params.exposure.green1Comp < 214 || + cam->params.exposure.green1Comp > 255 || + cam->params.exposure.green2Comp < 214 || + cam->params.exposure.green2Comp > 255 || + cam->params.exposure.blueComp < 230 || + cam->params.exposure.blueComp > 255) + { + printk (KERN_WARNING "*_comp parameters have gone AWOL (%d/%d/%d/%d) - reseting them\n", + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + cam->params.exposure.redComp = 220; + cam->params.exposure.green1Comp = 214; + cam->params.exposure.green2Comp = 214; + cam->params.exposure.blueComp = 230; + } + up(&cam->param_lock); + break; + default: + break; + } + } + return retval; +} + +/* send a command to the camera with an additional data transaction */ +static int do_command_extended(struct cam_data *cam, u16 command, + u8 a, u8 b, u8 c, u8 d, + u8 e, u8 f, u8 g, u8 h, + u8 i, u8 j, u8 k, u8 l) +{ + int retval; + u8 cmd[8], data[8]; + + cmd[0] = command>>8; + cmd[1] = command&0xff; + cmd[2] = a; + cmd[3] = b; + cmd[4] = c; + cmd[5] = d; + cmd[6] = 8; + cmd[7] = 0; + data[0] = e; + data[1] = f; + data[2] = g; + data[3] = h; + data[4] = i; + data[5] = j; + data[6] = k; + data[7] = l; + + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) + LOG("%x - failed\n", command); + + return retval; +} + +/********************************************************************** + * + * Colorspace conversion + * + **********************************************************************/ +#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16) + +static int yuvconvert(unsigned char *yuv, unsigned char *rgb, int out_fmt, + int in_uyvy, int mmap_kludge) +{ + int y, u, v, r, g, b, y1; + + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + if (in_uyvy) { + u = *yuv++ - 128; + y = (*yuv++ - 16) * 76310; + v = *yuv++ - 128; + y1 = (*yuv - 16) * 76310; + } else { + y = (*yuv++ - 16) * 76310; + u = *yuv++ - 128; + y1 = (*yuv++ - 16) * 76310; + v = *yuv - 128; + } + r = 104635 * v; + g = -25690 * u + -53294 * v; + b = 132278 * u; + break; + default: + y = *yuv++; + u = *yuv++; + y1 = *yuv++; + v = *yuv; + /* Just to avoid compiler warnings */ + r = 0; + g = 0; + b = 0; + break; + } + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + *rgb++ = ((LIMIT(g+y) & 0xf8) << 2) | (LIMIT(b+y) >> 3); + *rgb++ = ((LIMIT(r+y) & 0xf8) >> 1) | (LIMIT(g+y) >> 6); + *rgb++ = ((LIMIT(g+y1) & 0xf8) << 2) | (LIMIT(b+y1) >> 3); + *rgb = ((LIMIT(r+y1) & 0xf8) >> 1) | (LIMIT(g+y1) >> 6); + return 4; + case VIDEO_PALETTE_RGB565: + *rgb++ = ((LIMIT(g+y) & 0xfc) << 3) | (LIMIT(b+y) >> 3); + *rgb++ = (LIMIT(r+y) & 0xf8) | (LIMIT(g+y) >> 5); + *rgb++ = ((LIMIT(g+y1) & 0xfc) << 3) | (LIMIT(b+y1) >> 3); + *rgb = (LIMIT(r+y1) & 0xf8) | (LIMIT(g+y1) >> 5); + return 4; + case VIDEO_PALETTE_RGB24: + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(b+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(r+y1); + } else { + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + return 6; + case VIDEO_PALETTE_RGB32: + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + rgb++; + *rgb++ = LIMIT(b+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(r+y1); + } else { + *rgb++ = LIMIT(r+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(b+y); + rgb++; + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + return 8; + case VIDEO_PALETTE_GREY: + *rgb++ = y; + *rgb = y1; + return 2; + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + *rgb++ = y; + *rgb++ = u; + *rgb++ = y1; + *rgb = v; + return 4; + case VIDEO_PALETTE_UYVY: + *rgb++ = u; + *rgb++ = y; + *rgb++ = v; + *rgb = y1; + return 4; + default: + DBG("Empty: %d\n", out_fmt); + return 0; + } +} + +static int skipcount(int count, int fmt) +{ + switch(fmt) { + case VIDEO_PALETTE_GREY: + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_UYVY: + return 2*count; + case VIDEO_PALETTE_RGB24: + return 3*count; + case VIDEO_PALETTE_RGB32: + return 4*count; + default: + return 0; + } +} + +static int parse_picture(struct cam_data *cam, int size) +{ + u8 *obuf, *ibuf, *end_obuf; + int ll, in_uyvy, compressed, origsize, out_fmt; + + /* make sure params don't change while we are decoding */ + down(&cam->param_lock); + + obuf = cam->decompressed_frame.data; + end_obuf = obuf+CPIA_MAX_FRAME_SIZE; + ibuf = cam->raw_image; + origsize = size; + out_fmt = cam->vp.palette; + + if ((ibuf[0] != MAGIC_0) || (ibuf[1] != MAGIC_1)) { + LOG("header not found\n"); + up(&cam->param_lock); + return -1; + } + + if ((ibuf[16] != VIDEOSIZE_QCIF) && (ibuf[16] != VIDEOSIZE_CIF)) { + LOG("wrong video size\n"); + up(&cam->param_lock); + return -1; + } + + if (ibuf[17] != SUBSAMPLE_422) { + LOG("illegal subtype %d\n",ibuf[17]); + up(&cam->param_lock); + return -1; + } + + if (ibuf[18] != YUVORDER_YUYV && ibuf[18] != YUVORDER_UYVY) { + LOG("illegal yuvorder %d\n",ibuf[18]); + up(&cam->param_lock); + return -1; + } + in_uyvy = ibuf[18] == YUVORDER_UYVY; + +#if 0 + /* FIXME: ROI mismatch occurs when switching capture sizes */ + if ((ibuf[24] != cam->params.roi.colStart) || + (ibuf[25] != cam->params.roi.colEnd) || + (ibuf[26] != cam->params.roi.rowStart) || + (ibuf[27] != cam->params.roi.rowEnd)) { + LOG("ROI mismatch\n"); + up(&cam->param_lock); + return -1; + } +#endif + + if ((ibuf[28] != NOT_COMPRESSED) && (ibuf[28] != COMPRESSED)) { + LOG("illegal compression %d\n",ibuf[28]); + up(&cam->param_lock); + return -1; + } + compressed = (ibuf[28] == COMPRESSED); + + if (ibuf[29] != NO_DECIMATION) { + LOG("decimation not supported\n"); + up(&cam->param_lock); + return -1; + } + + cam->params.yuvThreshold.yThreshold = ibuf[30]; + cam->params.yuvThreshold.uvThreshold = ibuf[31]; + cam->params.status.systemState = ibuf[32]; + cam->params.status.grabState = ibuf[33]; + cam->params.status.streamState = ibuf[34]; + cam->params.status.fatalError = ibuf[35]; + cam->params.status.cmdError = ibuf[36]; + cam->params.status.debugFlags = ibuf[37]; + cam->params.status.vpStatus = ibuf[38]; + cam->params.status.errorCode = ibuf[39]; + cam->fps = ibuf[41]; + up(&cam->param_lock); + + ibuf += FRAME_HEADER_SIZE; + size -= FRAME_HEADER_SIZE; + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; + + while (size > 0) { + size -= (ll+2); + if (size < 0) { + LOG("Insufficient data in buffer\n"); + return -1; + } + + while (ll > 1) { + if (!compressed || (compressed && !(*ibuf & 1))) { + obuf += yuvconvert(ibuf, obuf, out_fmt, + in_uyvy, cam->mmap_kludge); + ibuf += 4; + ll -= 4; + } else { + /*skip compressed interval from previous frame*/ + int skipsize = skipcount(*ibuf >> 1, out_fmt); + obuf += skipsize; + if (obuf > end_obuf) { + LOG("Insufficient data in buffer\n"); + return -1; + } + ++ibuf; + ll--; + } + } + if (ll == 1) { + if (*ibuf != EOL) { + LOG("EOL not found giving up after %d/%d" + " bytes\n", origsize-size, origsize); + return -1; + } + + ibuf++; /* skip over EOL */ + + if ((size > 3) && (ibuf[0] == EOI) && (ibuf[1] == EOI) && + (ibuf[2] == EOI) && (ibuf[3] == EOI)) { + size -= 4; + break; + } + + if (size > 1) { + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; /* skip over line length */ + } + } else { + LOG("line length was not 1 but %d after %d/%d bytes\n", + ll, origsize-size, origsize); + return -1; + } + } + + cam->decompressed_frame.count = obuf-cam->decompressed_frame.data; + + return cam->decompressed_frame.count; +} + +/* InitStreamCap wrapper to select correct start line */ +static inline int init_stream_cap(struct cam_data *cam) +{ + return do_command(cam, CPIA_COMMAND_InitStreamCap, + 0, cam->params.streamStartLine, 0, 0); +} + +/* update various camera modes and settings */ +static void dispatch_commands(struct cam_data *cam) +{ + down(&cam->param_lock); + if (cam->cmd_queue==COMMAND_NONE) { + up(&cam->param_lock); + return; + } + DEB_BYTE(cam->cmd_queue); + DEB_BYTE(cam->cmd_queue>>8); + if (cam->cmd_queue & COMMAND_SETCOLOURPARAMS) + do_command(cam, CPIA_COMMAND_SetColourParams, + cam->params.colourParams.brightness, + cam->params.colourParams.contrast, + cam->params.colourParams.saturation, 0); + + if (cam->cmd_queue & COMMAND_SETCOMPRESSION) + do_command(cam, CPIA_COMMAND_SetCompression, + cam->params.compression.mode, + cam->params.compression.decimation, 0, 0); + + if (cam->cmd_queue & COMMAND_SETFORMAT) { + do_command(cam, CPIA_COMMAND_SetFormat, + cam->params.format.videoSize, + cam->params.format.subSample, + cam->params.format.yuvOrder, 0); + do_command(cam, CPIA_COMMAND_SetROI, + cam->params.roi.colStart, cam->params.roi.colEnd, + cam->params.roi.rowStart, cam->params.roi.rowEnd); + cam->first_frame = 1; + } + + if (cam->cmd_queue & COMMAND_SETCOMPRESSIONTARGET) + do_command(cam, CPIA_COMMAND_SetCompressionTarget, + cam->params.compressionTarget.frTargeting, + cam->params.compressionTarget.targetFR, + cam->params.compressionTarget.targetQ, 0); + + if (cam->cmd_queue & COMMAND_SETYUVTHRESH) + do_command(cam, CPIA_COMMAND_SetYUVThresh, + cam->params.yuvThreshold.yThreshold, + cam->params.yuvThreshold.uvThreshold, 0, 0); + + if (cam->cmd_queue & COMMAND_SETECPTIMING) + do_command(cam, CPIA_COMMAND_SetECPTiming, + cam->params.ecpTiming, 0, 0, 0); + + if (cam->cmd_queue & COMMAND_SETCOMPRESSIONPARAMS) + do_command_extended(cam, CPIA_COMMAND_SetCompressionParams, + 0, 0, 0, 0, + cam->params.compressionParams.hysteresis, + cam->params.compressionParams.threshMax, + cam->params.compressionParams.smallStep, + cam->params.compressionParams.largeStep, + cam->params.compressionParams.decimationHysteresis, + cam->params.compressionParams.frDiffStepThresh, + cam->params.compressionParams.qDiffStepThresh, + cam->params.compressionParams.decimationThreshMod); + + if (cam->cmd_queue & COMMAND_SETEXPOSURE) + do_command_extended(cam, CPIA_COMMAND_SetExposure, + cam->params.exposure.gainMode, + cam->params.exposure.expMode, + cam->params.exposure.compMode, + cam->params.exposure.centreWeight, + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + + if (cam->cmd_queue & COMMAND_SETCOLOURBALANCE) { + if (cam->params.colourBalance.balanceModeIsAuto) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 2, 0, 0, 0); + } else { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 1, + cam->params.colourBalance.redGain, + cam->params.colourBalance.greenGain, + cam->params.colourBalance.blueGain); + do_command(cam, CPIA_COMMAND_SetColourBalance, + 3, 0, 0, 0); + } + } + + if (cam->cmd_queue & COMMAND_SETSENSORFPS) + do_command(cam, CPIA_COMMAND_SetSensorFPS, + cam->params.sensorFps.divisor, + cam->params.sensorFps.baserate, 0, 0); + + if (cam->cmd_queue & COMMAND_SETAPCOR) + do_command(cam, CPIA_COMMAND_SetApcor, + cam->params.apcor.gain1, + cam->params.apcor.gain2, + cam->params.apcor.gain4, + cam->params.apcor.gain8); + + if (cam->cmd_queue & COMMAND_SETFLICKERCTRL) + do_command(cam, CPIA_COMMAND_SetFlickerCtrl, + cam->params.flickerControl.flickerMode, + cam->params.flickerControl.coarseJump, + cam->params.flickerControl.allowableOverExposure, 0); + + if (cam->cmd_queue & COMMAND_SETVLOFFSET) + do_command(cam, CPIA_COMMAND_SetVLOffset, + cam->params.vlOffset.gain1, + cam->params.vlOffset.gain2, + cam->params.vlOffset.gain4, + cam->params.vlOffset.gain8); + + if (cam->cmd_queue & COMMAND_PAUSE) + do_command(cam, CPIA_COMMAND_EndStreamCap, 0, 0, 0, 0); + + if (cam->cmd_queue & COMMAND_RESUME) + init_stream_cap(cam); + + up(&cam->param_lock); + cam->cmd_queue = COMMAND_NONE; + return; +} + +/* kernel thread function to read image from camera */ +static void fetch_frame(void *data) +{ + int image_size, retry; + struct cam_data *cam = (struct cam_data *)data; + unsigned long oldjif, rate, diff; + + /* Allow up to two bad images in a row to be read and + * ignored before an error is reported */ + for (retry = 0; retry < 3; ++retry) { + if (retry) + DBG("retry=%d\n", retry); + + if (!cam->ops) + continue; + + /* load first frame always uncompressed */ + if (cam->first_frame && + cam->params.compression.mode != CPIA_COMPRESSION_NONE) + do_command(cam, CPIA_COMMAND_SetCompression, + CPIA_COMPRESSION_NONE, + NO_DECIMATION, 0, 0); + + /* init camera upload */ + if (do_command(cam, CPIA_COMMAND_SetGrabMode, + CPIA_GRAB_CONTINUOUS, 0, 0, 0)) + continue; + + if (do_command(cam, CPIA_COMMAND_GrabFrame, 0, + cam->params.streamStartLine, 0, 0)) + continue; + + if (cam->ops->wait_for_stream_ready) { + /* loop until image ready */ + do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0); + while (cam->params.status.streamState != STREAM_READY) { + if (current->need_resched) + schedule(); + + current->state = TASK_INTERRUPTIBLE; + + /* sleep for 10 ms, hopefully ;) */ + schedule_timeout(10*HZ/1000); + if (signal_pending(current)) + return; + + do_command(cam, CPIA_COMMAND_GetCameraStatus, + 0, 0, 0, 0); + } + } + + /* grab image from camera */ + if (current->need_resched) + schedule(); + + oldjif = jiffies; + image_size = cam->ops->streamRead(cam->lowlevel_data, + cam->raw_image, 0); + if (image_size <= 0) { + DBG("streamRead failed: %d\n", image_size); + continue; + } + + rate = image_size * HZ / 1024; + diff = jiffies-oldjif; + cam->transfer_rate = diff==0 ? rate : rate/diff; + /* diff==0 ? unlikely but possible */ + + /* camera idle now so dispatch queued commands */ + dispatch_commands(cam); + + /* Update our knowledge of the camera state - FIXME: necessary? */ + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0); + + /* decompress and convert image to by copying it from + * raw_image to decompressed_frame + */ + if (current->need_resched) + schedule(); + + cam->image_size = parse_picture(cam, image_size); + if (cam->image_size <= 0) + DBG("parse_picture failed %d\n", cam->image_size); + else + break; + } + + if (retry < 3) { + /* FIXME: this only works for double buffering */ + if (cam->frame[cam->curframe].state == FRAME_READY) { + memcpy(cam->frame[cam->curframe].data, + cam->decompressed_frame.data, + cam->decompressed_frame.count); + cam->frame[cam->curframe].state = FRAME_DONE; + } else + cam->decompressed_frame.state = FRAME_DONE; + +#if 0 + if (cam->first_frame && + cam->params.compression.mode != CPIA_COMPRESSION_NONE) { + cam->first_frame = 0; + cam->cmd_queue |= COMMAND_SETCOMPRESSION; + } +#else + if (cam->first_frame) { + cam->first_frame = 0; + cam->cmd_queue |= COMMAND_SETCOMPRESSION; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + } +#endif + } +} + +static int capture_frame(struct cam_data *cam, struct video_mmap *vm) +{ + int retval = 0; + + if (!cam->frame_buf) { + /* we do lazy allocation */ + if ((retval = allocate_frame_buf(cam))) + return retval; + } + + /* FIXME: the first frame seems to be captured by the camera + without regards to any initial settings, so we throw away + that one, the next one is generated with our settings + (exposure, color balance, ...) + */ + if (cam->first_frame) { + cam->curframe = vm->frame; + cam->frame[cam->curframe].state = FRAME_READY; + fetch_frame(cam); + if (cam->frame[cam->curframe].state != FRAME_DONE) + retval = -EIO; + } + cam->curframe = vm->frame; + cam->frame[cam->curframe].state = FRAME_READY; + fetch_frame(cam); + if (cam->frame[cam->curframe].state != FRAME_DONE) + retval=-EIO; + + return retval; +} + +static int goto_high_power(struct cam_data *cam) +{ + if (do_command(cam, CPIA_COMMAND_GotoHiPower, 0, 0, 0, 0)) + return -1; + mdelay(100); /* windows driver does it too */ + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -1; + if (cam->params.status.systemState == HI_POWER_STATE) { + DBG("camera now in HIGH power state\n"); + return 0; + } + printstatus(cam); + return -1; +} + +static int goto_low_power(struct cam_data *cam) +{ + if (do_command(cam, CPIA_COMMAND_GotoLoPower, 0, 0, 0, 0)) + return -1; + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -1; + if (cam->params.status.systemState == LO_POWER_STATE) { + DBG("camera now in LOW power state\n"); + return 0; + } + printstatus(cam); + return -1; +} + +static void save_camera_state(struct cam_data *cam) +{ + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0); + + DBG("%d/%d/%d/%d/%d/%d/%d/%d\n", + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + DBG("%d/%d/%d\n", + cam->params.colourBalance.redGain, + cam->params.colourBalance.greenGain, + cam->params.colourBalance.blueGain); +} + +static void set_camera_state(struct cam_data *cam) +{ + if(cam->params.colourBalance.balanceModeIsAuto) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 2, 0, 0, 0); + } else { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 1, + cam->params.colourBalance.redGain, + cam->params.colourBalance.greenGain, + cam->params.colourBalance.blueGain); + do_command(cam, CPIA_COMMAND_SetColourBalance, + 3, 0, 0, 0); + } + + + do_command_extended(cam, CPIA_COMMAND_SetExposure, + cam->params.exposure.gainMode, 1, 1, + cam->params.exposure.centreWeight, + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + cam->params.exposure.redComp, + cam->params.exposure.green1Comp, + cam->params.exposure.green2Comp, + cam->params.exposure.blueComp); + do_command_extended(cam, CPIA_COMMAND_SetExposure, + 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + + if (!cam->params.exposure.gainMode) + cam->params.exposure.gainMode = 2; + if (!cam->params.exposure.expMode) + cam->params.exposure.expMode = 2; + if (!cam->params.exposure.centreWeight) + cam->params.exposure.centreWeight = 1; + + cam->cmd_queue = COMMAND_SETCOMPRESSION | + COMMAND_SETCOMPRESSIONTARGET | + COMMAND_SETCOLOURPARAMS | + COMMAND_SETFORMAT | + COMMAND_SETYUVTHRESH | + COMMAND_SETECPTIMING | + COMMAND_SETCOMPRESSIONPARAMS | +#if 0 + COMMAND_SETEXPOSURE | +#endif + COMMAND_SETCOLOURBALANCE | + COMMAND_SETSENSORFPS | + COMMAND_SETAPCOR | + COMMAND_SETFLICKERCTRL | + COMMAND_SETVLOFFSET; + dispatch_commands(cam); + save_camera_state(cam); + + return; +} + +static void get_version_information(struct cam_data *cam) +{ + /* GetCPIAVersion */ + do_command(cam, CPIA_COMMAND_GetCPIAVersion, 0, 0, 0, 0); + + /* GetPnPID */ + do_command(cam, CPIA_COMMAND_GetPnPID, 0, 0, 0, 0); +} + +/* initialize camera */ +static int reset_camera(struct cam_data *cam) +{ + /* Start the camera in low power mode */ + if (goto_low_power(cam)) { + if (cam->params.status.systemState != WARM_BOOT_STATE) + return -ENODEV; + + /* FIXME: this is just dirty trial and error */ + reset_camera_struct(cam); + goto_high_power(cam); + do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0); + if (goto_low_power(cam)) + return -NODEV; + } + + /* procedure described in developer's guide p3-28 */ + + /* Check the firmware version FIXME: should we check PNPID? */ + cam->params.version.firmwareVersion = 0; + get_version_information(cam); + if (cam->params.version.firmwareVersion != 1) + return -ENODEV; + + /* The fatal error checking should be done after + * the camera powers up (developer's guide p 3-38) */ + + /* Set streamState before transition to high power to avoid bug + * in firmware 1-02 */ + do_command(cam, CPIA_COMMAND_ModifyCameraStatus, STREAMSTATE, 0, + STREAM_NOT_READY, 0); + + /* GotoHiPower */ + if (goto_high_power(cam)) + return -ENODEV; + + /* Check the camera status */ + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -EIO; + + if (cam->params.status.fatalError) { + DBG("fatal_error: %#04x\n", + cam->params.status.fatalError); + DBG("vp_status: %#04x\n", + cam->params.status.vpStatus); + if (cam->params.status.fatalError & ~(COM_FLAG|CPIA_FLAG)) { + /* Fatal error in camera */ + return -EIO; + } else if (cam->params.status.fatalError & (COM_FLAG|CPIA_FLAG)) { + /* Firmware 1-02 may do this for parallel port cameras, + * just clear the flags (developer's guide p 3-38) */ + do_command(cam, CPIA_COMMAND_ModifyCameraStatus, + FATALERROR, ~(COM_FLAG|CPIA_FLAG), 0, 0); + } + } + + /* Check the camera status again */ + if (cam->params.status.fatalError) { + if (cam->params.status.fatalError) + return -EIO; + } + + /* VPVersion can't be retrieved before the camera is in HiPower, + * so get it here instead of in get_version_information. */ + do_command(cam, CPIA_COMMAND_GetVPVersion, 0, 0, 0, 0); + + /* set camera to a known state */ + set_camera_state(cam); + + return 0; +} + +/* ------------------------- V4L interface --------------------- */ +static int cpia_open(struct video_device *dev, int flags) +{ + int i; + struct cam_data *cam = dev->priv; + + if (!cam) { + DBG("Internal error, cam_data not found!\n"); + return -EBUSY; + } + + if (cam->open_count > 0) { + DBG("Camera already open\n"); + return -EBUSY; + } + + if (!cam->raw_image) { + cam->raw_image = rvmalloc(CPIA_MAX_IMAGE_SIZE); + if (!cam->raw_image) + return -ENOMEM; + } + + if (!cam->decompressed_frame.data) { + cam->decompressed_frame.data = rvmalloc(CPIA_MAX_FRAME_SIZE); + if (!cam->decompressed_frame.data) { + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + return -ENOMEM; + } + } + + /* open cpia */ + if (cam->ops->open(cam->lowlevel_data)) { + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + return -ENODEV; + } + + /* reset the camera */ + if ((i = reset_camera(cam)) != 0) { + cam->ops->close(cam->lowlevel_data); + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + return i; + } + + /* Set ownership of /proc/cpia/videoX to current user */ + if(cam->proc_entry) + cam->proc_entry->uid = current->uid; + + /* set mark for loading first frame uncompressed */ + cam->first_frame = 1; + + /* init it to something */ + cam->mmap_kludge = 0; + + ++cam->open_count; +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif + return 0; +} + +static void cpia_close(struct video_device *dev) +{ + struct cam_data *cam; + + cam = dev->priv; + + if (cam->ops) { + /* Return ownership of /proc/cpia/videoX to root */ + if(cam->proc_entry) + cam->proc_entry->uid = 0; + + /* save camera state for later open (developers guide ch 3.5.3) */ + save_camera_state(cam); + + /* GotoLoPower */ + goto_low_power(cam); + + /* Update the camera ststus */ + do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0); + + /* cleanup internal state stuff */ + free_frames(cam->frame); + + /* close cpia */ + cam->ops->close(cam->lowlevel_data); + } + + if (--cam->open_count == 0) { + /* clean up capture-buffers */ + if (cam->raw_image) { + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + } + + if (cam->decompressed_frame.data) { + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + } + + if (cam->frame_buf) + free_frame_buf(cam); + + if (!cam->ops) { + video_unregister_device(dev); + kfree(cam); + } + } + + +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif + return; +} + +static long cpia_read(struct video_device *dev, char *buf, + unsigned long count, int noblock) +{ + struct cam_data *cam = dev->priv; + + /* make this _really_ smp and multithredi-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!buf) { + DBG("buf NULL\n"); + up(&cam->busy_lock); + return -EINVAL; + } + + if (!count) { + DBG("count 0\n"); + up(&cam->busy_lock); + return 0; + } + + if (!cam->ops) { + DBG("ops NULL\n"); + up(&cam->busy_lock); + return -ENODEV; + } + + /* upload frame */ + cam->decompressed_frame.state = FRAME_READY; + cam->mmap_kludge=0; + fetch_frame(cam); + if (cam->decompressed_frame.state != FRAME_DONE) { + DBG("upload failed %d/%d\n", cam->decompressed_frame.count, + cam->decompressed_frame.state); + up(&cam->busy_lock); + return -EIO; + } + cam->decompressed_frame.state = FRAME_UNUSED; + + /* copy data to user space */ + if (cam->decompressed_frame.count > count) { + DBG("count wrong: %d, %lu\n", cam->decompressed_frame.count, + count); + up(&cam->busy_lock); + return -EFAULT; + } + if (copy_to_user(buf, cam->decompressed_frame.data, + cam->decompressed_frame.count)) { + DBG("copy_to_user failed\n"); + up(&cam->busy_lock); + return -EFAULT; + } + + up(&cam->busy_lock); + return cam->decompressed_frame.count; +} + +static int cpia_ioctl(struct video_device *dev, unsigned int ioctlnr, void *arg) +{ + struct cam_data *cam = dev->priv; + int retval = 0; + + if (!cam || !cam->ops) + return -ENODEV; + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + //DBG("cpia_ioctl: %u\n", ioctlnr); + + switch (ioctlnr) { + /* query capabilites */ + case VIDIOCGCAP: + { + struct video_capability b; + + DBG("VIDIOCGCAP\n"); + strcpy(b.name, "CPiA Camera"); + b.type = VID_TYPE_CAPTURE; + b.channels = 1; + b.audios = 0; + b.maxwidth = 352; /* VIDEOSIZE_CIF */ + b.maxheight = 288; + b.minwidth = 48; /* VIDEOSIZE_48_48 */ + b.minheight = 48; + + if (copy_to_user(arg, &b, sizeof(b))) + retval = -EFAULT; + + break; + } + + /* get/set video source - we are a camera and nothing else */ + case VIDIOCGCHAN: + { + struct video_channel v; + + DBG("VIDIOCGCHAN\n"); + if (copy_from_user(&v, arg, sizeof(v))) { + retval = -EFAULT; + break; + } + if (v.channel != 0) { + retval = -EINVAL; + break; + } + + v.channel = 0; + strcpy(v.name, "Camera"); + v.tuners = 0; + v.flags = 0; + v.type = VIDEO_TYPE_CAMERA; + v.norm = 0; + + if (copy_to_user(arg, &v, sizeof(v))) + retval = -EFAULT; + break; + } + + case VIDIOCSCHAN: + { + int v; + + DBG("VIDIOCSCHAN\n"); + if (copy_from_user(&v, arg, sizeof(v))) + retval = -EFAULT; + + if (retval == 0 && v != 0) + retval = -EINVAL; + + break; + } + + /* image properties */ + case VIDIOCGPICT: + DBG("VIDIOCGPICT\n"); + if (copy_to_user(arg, &cam->vp, sizeof(struct video_picture))) + retval = -EFAULT; + break; + + case VIDIOCSPICT: + { + struct video_picture vp; + + DBG("VIDIOCSPICT\n"); + + /* copy_from_user */ + if (copy_from_user(&vp, arg, sizeof(vp))) { + retval = -EFAULT; + break; + } + + /* check validity */ + DBG("palette: %d\n", vp.palette); + DBG("depth: %d\n", vp.depth); + if (!valid_mode(vp.palette, vp.depth)) { + retval = -EINVAL; + break; + } + + down(&cam->param_lock); + /* brightness, colour, contrast need no check 0-65535 */ + memcpy( &cam->vp, &vp, sizeof(vp) ); + /* update cam->params.colourParams */ + cam->params.colourParams.brightness = vp.brightness*100/65535; + cam->params.colourParams.contrast = vp.contrast*100/65535; + cam->params.colourParams.saturation = vp.colour*100/65535; + /* contrast is in steps of 8, so round */ + cam->params.colourParams.contrast = + ((cam->params.colourParams.contrast + 3) / 8) * 8; + if (cam->params.version.firmwareVersion == 1 && + cam->params.version.firmwareRevision == 2 && + cam->params.colourParams.contrast > 80) { + /* 1-02 firmware limits contrast to 80 */ + cam->params.colourParams.contrast = 80; + } + + /* queue command to update camera */ + cam->cmd_queue |= COMMAND_SETCOLOURPARAMS; + up(&cam->param_lock); + DBG("VIDIOCSPICT: %d / %d // %d / %d / %d / %d\n", + vp.depth, vp.palette, vp.brightness, vp.hue, vp.colour, + vp.contrast); + break; + } + + /* get/set capture window */ + case VIDIOCGWIN: + DBG("VIDIOCGWIN\n"); + + if (copy_to_user(arg, &cam->vw, sizeof(struct video_window))) + retval = -EFAULT; + break; + + case VIDIOCSWIN: + { + /* copy_from_user, check validity, copy to internal structure */ + struct video_window vw; + DBG("VIDIOCSWIN\n"); + if (copy_from_user(&vw, arg, sizeof(vw))) { + retval = -EFAULT; + break; + } + + if (vw.clipcount != 0) { /* clipping not supported */ + retval = -EINVAL; + break; + } + if (vw.clips != NULL) { /* clipping not supported */ + retval = -EINVAL; + break; + } + + /* we set the video window to something smaller or equal to what + * is requested by the user??? + */ + down(&cam->param_lock); + if (vw.width != cam->vw.width || vw.height != cam->vw.height) { + int video_size = match_videosize(vw.width, vw.height); + + if (video_size < 0) { + retval = -EINVAL; + up(&cam->param_lock); + break; + } + cam->video_size = video_size; + set_vw_size(cam); + DBG("%d / %d\n", cam->vw.width, cam->vw.height); + cam->cmd_queue |= COMMAND_SETFORMAT; + } + + // FIXME needed??? memcpy(&cam->vw, &vw, sizeof(vw)); + up(&cam->param_lock); + + /* setformat ignored by camera during streaming, + * so stop/dispatch/start */ + if (cam->cmd_queue & COMMAND_SETFORMAT) { + DBG("\n"); + dispatch_commands(cam); + } + DBG("%d/%d:%d\n", cam->video_size, + cam->vw.width, cam->vw.height); + break; + } + + /* mmap interface */ + case VIDIOCGMBUF: + { + struct video_mbuf vm; + int i; + + DBG("VIDIOCGMBUF\n"); + memset(&vm, 0, sizeof(vm)); + vm.size = CPIA_MAX_FRAME_SIZE*FRAME_NUM; + vm.frames = FRAME_NUM; + for (i = 0; i < FRAME_NUM; i++) + vm.offsets[i] = CPIA_MAX_FRAME_SIZE * i; + + if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + retval = -EFAULT; + + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + int video_size; + + if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm))) { + retval = -EFAULT; + break; + } +#if 1 + DBG("VIDIOCMCAPTURE: %d / %d / %dx%d\n", vm.format, vm.frame, + vm.width, vm.height); +#endif + if (vm.frame<0||vm.frame>FRAME_NUM) { + retval = -EINVAL; + break; + } + + /* set video format */ + cam->vp.palette = vm.format; + switch(vm.format) { + case VIDEO_PALETTE_GREY: + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_UYVY: + cam->vp.depth = 16; + break; + case VIDEO_PALETTE_RGB24: + cam->vp.depth = 24; + break; + case VIDEO_PALETTE_RGB32: + cam->vp.depth = 32; + break; + default: + retval = -EINVAL; + break; + } + if (retval) + break; + + /* set video size */ + video_size = match_videosize(vm.width, vm.height); + if (cam->video_size < 0) { + retval = -EINVAL; + break; + } + if (video_size != cam->video_size) { + cam->video_size = video_size; + set_vw_size(cam); + cam->cmd_queue |= COMMAND_SETFORMAT; + dispatch_commands(cam); + } +#if 0 + DBG("VIDIOCMCAPTURE: %d / %d/%d\n", cam->video_size, + cam->vw.width, cam->vw.height); +#endif + /* according to v4l-spec we must start streaming here */ + cam->mmap_kludge = 1; + retval = capture_frame(cam, &vm); + + break; + } + + case VIDIOCSYNC: + { + int frame; + + if (copy_from_user((void *)&frame, arg, sizeof(int))) { + retval = -EFAULT; + break; + } + //DBG("VIDIOCSYNC: %d\n", frame); + + if (frame<0 || frame >= FRAME_NUM) { + retval = -EINVAL; + break; + } + + switch (cam->frame[frame].state) { + case FRAME_UNUSED: + case FRAME_READY: + case FRAME_GRABBING: + DBG("sync to unused frame %d\n", frame); + retval = -EINVAL; + break; + + case FRAME_DONE: + cam->frame[frame].state = FRAME_UNUSED; + //DBG("VIDIOCSYNC: %d synced\n", frame); + break; + } + if (retval == -EINTR) { + /* FIXME - xawtv does not handle this nice */ + retval = 0; + } + break; + } + + /* pointless to implement overlay with this camera */ + case VIDIOCCAPTURE: + retval = -EINVAL; + break; + case VIDIOCGFBUF: + retval = -EINVAL; + break; + case VIDIOCSFBUF: + retval = -EINVAL; + break; + case VIDIOCKEY: + retval = -EINVAL; + break; + + /* tuner interface - we have none */ + case VIDIOCGTUNER: + retval = -EINVAL; + break; + case VIDIOCSTUNER: + retval = -EINVAL; + break; + case VIDIOCGFREQ: + retval = -EINVAL; + break; + case VIDIOCSFREQ: + retval = -EINVAL; + break; + + /* audio interface - we have none */ + case VIDIOCGAUDIO: + retval = -EINVAL; + break; + case VIDIOCSAUDIO: + retval = -EINVAL; + break; + default: + retval = -ENOIOCTLCMD; + break; + } + + up(&cam->param_lock); + up(&cam->busy_lock); + return retval; +} + +/* FIXME */ +static int cpia_mmap(struct video_device *dev, const char *adr, + unsigned long size) +{ + unsigned long start = (unsigned long)adr; + unsigned long page, pos; + struct cam_data *cam = dev->priv; + int retval; + + if (!cam || !cam->ops) + return -ENODEV; + + DBG("cpia_mmap: %ld\n", size); + + if (size > FRAME_NUM*CPIA_MAX_FRAME_SIZE) + return -EINVAL; + + if (!cam || !cam->ops) + return -ENODEV; + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!cam->frame_buf) { /* we do lazy allocation */ + if ((retval = allocate_frame_buf(cam))) { + up(&cam->busy_lock); + return retval; + } + } + + pos = (unsigned long)(cam->frame_buf); + while (size > 0) { + page = kvirt_to_pa(pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) { + up(&cam->busy_lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + DBG("cpia_mmap: %ld\n", size); + up(&cam->busy_lock); + + return 0; +} + +int cpia_video_init(struct video_device *vdev) +{ +#ifdef CONFIG_PROC_FS + create_proc_cpia_cam(vdev->priv); +#endif + return 0; +} + +static struct video_device cpia_template = { + "CPiA Camera", + VID_TYPE_CAPTURE, + VID_HARDWARE_CPIA, /* FIXME */ + cpia_open, /* open */ + cpia_close, /* close */ + cpia_read, /* read */ + NULL, /* no write */ + NULL, /* no poll */ + cpia_ioctl, /* ioctl */ + cpia_mmap, /* mmap */ + cpia_video_init, /* initialize */ + NULL, /* priv */ + 0, /* busy */ + -1 /* minor - unset */ +}; + +/* initialise cam_data structure */ +static void reset_camera_struct(struct cam_data *cam) +{ + /* The following parameter values are the defaults from + * "Software Developer's Guide for CPiA Cameras". Any changes + * to the defaults are noted in comments. */ + cam->params.colourParams.brightness = 50; + cam->params.colourParams.contrast = 48; + cam->params.colourParams.saturation = 50; + cam->params.exposure.gainMode = 2; + cam->params.exposure.expMode = 2; /* AEC */ + cam->params.exposure.compMode = 1; + cam->params.exposure.centreWeight = 1; + cam->params.exposure.gain = 0; + cam->params.exposure.fineExp = 0; + cam->params.exposure.coarseExpLo = 185; + cam->params.exposure.coarseExpHi = 0; + cam->params.exposure.redComp = 220; + cam->params.exposure.green1Comp = 214; + cam->params.exposure.green2Comp = 214; + cam->params.exposure.blueComp = 230; + cam->params.colourBalance.balanceModeIsAuto = 1; + cam->params.colourBalance.redGain = 32; + cam->params.colourBalance.greenGain = 6; + cam->params.colourBalance.blueGain = 92; + cam->params.apcor.gain1 = 0x1c; + cam->params.apcor.gain2 = 0x1a; + cam->params.apcor.gain4 = 0x2d; + cam->params.apcor.gain8 = 0x2a; + cam->params.flickerControl.flickerMode = 0; + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->params.vlOffset.gain1 = 24; + cam->params.vlOffset.gain2 = 28; + cam->params.vlOffset.gain4 = 30; + cam->params.vlOffset.gain8 = 30; + cam->params.compressionParams.hysteresis = 3; + cam->params.compressionParams.threshMax = 11; + cam->params.compressionParams.smallStep = 1; + cam->params.compressionParams.largeStep = 3; + cam->params.compressionParams.decimationHysteresis = 2; + cam->params.compressionParams.frDiffStepThresh = 5; + cam->params.compressionParams.qDiffStepThresh = 3; + cam->params.compressionParams.decimationThreshMod = 2; + /* End of default values from Software Developer's Guide */ + + cam->transfer_rate = 0; + + /* Set Sensor FPS to 15fps. This seems better than 30fps + * for indoor lighting. */ + cam->params.sensorFps.divisor = 1; + cam->params.sensorFps.baserate = 1; + + cam->params.yuvThreshold.yThreshold = 15; /* FIXME? */ + cam->params.yuvThreshold.uvThreshold = 15; /* FIXME? */ + + cam->params.format.subSample = SUBSAMPLE_422; + cam->params.format.yuvOrder = YUVORDER_YUYV; + + cam->params.compression.mode = CPIA_COMPRESSION_AUTO; + cam->params.compressionTarget.frTargeting = + CPIA_COMPRESSION_TARGET_QUALITY; + cam->params.compressionTarget.targetFR = 7; /* FIXME? */ + cam->params.compressionTarget.targetQ = 10; /* FIXME? */ + + cam->video_size = VIDEOSIZE_CIF; + + cam->vp.colour = 32768; /* 50% */ + cam->vp.hue = 32768; /* 50% */ + cam->vp.brightness = 32768; /* 50% */ + cam->vp.contrast = 32768; /* 50% */ + cam->vp.whiteness = 0; /* not used -> grayscale only */ + cam->vp.depth = 0; /* FIXME: to be set by user? */ + cam->vp.palette = VIDEO_PALETTE_RGB24; /* FIXME: to be set by user? */ + + cam->vw.x = 0; + cam->vw.y = 0; + set_vw_size(cam); + cam->vw.chromakey = 0; + /* PP NOTE: my extension to use vw.flags for this, bear it! */ + cam->vw.flags = 0; + cam->vw.clipcount = 0; + cam->vw.clips = NULL; + + cam->cmd_queue = COMMAND_NONE; + cam->first_frame = 0; + + return; +} + +/* initialize cam_data structure */ +static void init_camera_struct(struct cam_data *cam, + struct cpia_camera_ops *ops ) +{ + int i; + + /* Default everything to 0 */ + memset(cam, 0, sizeof(struct cam_data)); + + cam->ops = ops; + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + reset_camera_struct(cam); + + cam->proc_entry = NULL; + + memcpy(&cam->vdev, &cpia_template, sizeof(cpia_template)); + cam->vdev.priv = cam; + + cam->curframe = 0; + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].width = 0; + cam->frame[i].height = 0; + cam->frame[i].state = FRAME_UNUSED; + cam->frame[i].data = NULL; + } + cam->decompressed_frame.width = 0; + cam->decompressed_frame.height = 0; + cam->decompressed_frame.state = FRAME_UNUSED; + cam->decompressed_frame.data = NULL; +} + +struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel) +{ + struct cam_data *camera; + + /* Need a lock when adding/removing cameras. This doesn't happen + * often and doesn't take very long, so grabbing the kernel lock + * should be OK. */ + + if ((camera = kmalloc(sizeof(struct cam_data), GFP_KERNEL)) == NULL) { + unlock_kernel(); + return NULL; + } + + init_camera_struct( camera, ops ); + camera->lowlevel_data = lowlevel; + + /* register v4l device */ + if (video_register_device(&camera->vdev, VFL_TYPE_GRABBER) == -1) { + kfree(camera); + unlock_kernel(); + printk(KERN_DEBUG "video_register_device failed\n"); + return NULL; + } + + /* get version information from camera: open/reset/close */ + + /* open cpia */ + if (camera->ops->open(camera->lowlevel_data)) + return camera; + + /* reset the camera */ + if (reset_camera(camera) != 0) { + camera->ops->close(camera->lowlevel_data); + return camera; + } + + /* close cpia */ + camera->ops->close(camera->lowlevel_data); + +/* Eh? Feeling happy? - jerdfelt */ +/* + camera->ops->open(camera->lowlevel_data); + camera->ops->close(camera->lowlevel_data); +*/ + + printk(KERN_INFO " CPiA Version: %d.%02d (%d.%d)\n", + camera->params.version.firmwareVersion, + camera->params.version.firmwareRevision, + camera->params.version.vcVersion, + camera->params.version.vcRevision); + printk(KERN_INFO " CPiA PnP-ID: %04x:%04x:%04x\n", + camera->params.pnpID.vendor, + camera->params.pnpID.product, + camera->params.pnpID.deviceRevision); + printk(KERN_INFO " VP-Version: %d.%d %04x\n", + camera->params.vpVersion.vpVersion, + camera->params.vpVersion.vpRevision, + camera->params.vpVersion.cameraHeadID); + + return camera; +} + +void cpia_unregister_camera(struct cam_data *cam) +{ + if (!cam->open_count) { + DBG("unregistering video\n"); + video_unregister_device(&cam->vdev); + } else { + LOG("/dev/video%d removed while open, " + "deferring video_unregister_device\n", cam->vdev.minor); + DBG("camera open -- setting ops to NULL\n"); + cam->ops = NULL; + } + +#ifdef CONFIG_PROC_FS + DBG("destroying /proc/cpia/video%d\n", cam->vdev.minor); + destroy_proc_cpia_cam(cam); +#endif + if (!cam->open_count) { + DBG("freeing camera\n"); + kfree(cam); + } +} + +/**************************************************************************** + * + * Module routines + * + ***************************************************************************/ + +#ifdef MODULE +int init_module(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT, + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); +#ifdef CONFIG_PROC_FS + proc_cpia_create(); +#endif +#ifdef CONFIG_KMOD +#ifdef CONFIG_VIDEO_CPIA_PP_MODULE + request_module("cpia_pp"); +#endif +#ifdef CONFIG_VIDEO_CPIA_USB_MODULE + request_module("cpia_usb"); +#endif +#endif +return 0; +} + +void cleanup_module(void) +{ +#ifdef CONFIG_PROC_FS + proc_cpia_destroy(); +#endif +} + +#else + +int cpia_init(struct video_init *unused) +{ + printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT, + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); +#ifdef CONFIG_PROC_FS + proc_cpia_create(); +#endif + +#ifdef CONFIG_VIDEO_CPIA_PP + cpia_pp_init(); +#endif +#ifdef CONFIG_KMOD +#ifdef CONFIG_VIDEO_CPIA_PP_MODULE + request_module("cpia_pp"); +#endif + +#ifdef CONFIG_VIDEO_CPIA_USB_MODULE + request_module("cpia_usb"); +#endif +#endif /* CONFIG_KMOD */ +#ifdef CONFIG_VIDEO_CPIA_USB + cpia_usb_init(); +#endif + return 0; +} + +/* Exported symbols for modules. */ + +EXPORT_SYMBOL(cpia_register_camera); +EXPORT_SYMBOL(cpia_unregister_camera); + +#endif diff --git a/drivers/media/video/cpia.h b/drivers/media/video/cpia.h new file mode 100644 index 000000000..579b5e153 --- /dev/null +++ b/drivers/media/video/cpia.h @@ -0,0 +1,421 @@ +#ifndef cpia_h +#define cpia_h + +/* + * CPiA Parallel Port Video4Linux driver + * + * Supports CPiA based parallel port Video Camera's. + * + * (C) Copyright 1999 Bas Huisman, + * Peter Pregler, + * Scott J. Bertin, + * VLSI Vision Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define CPIA_MAJ_VER 0 +#define CPIA_MIN_VER 7 +#define CPIA_PATCH_VER 4 + +#define CPIA_PP_MAJ_VER 0 +#define CPIA_PP_MIN_VER 7 +#define CPIA_PP_PATCH_VER 4 + +#define CPIA_MAX_FRAME_SIZE_UNALIGNED (352 * 288 * 4) /* CIF at RGB32 */ +#define CPIA_MAX_FRAME_SIZE ((CPIA_MAX_FRAME_SIZE_UNALIGNED + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) /* align above to PAGE_SIZE */ + +#ifdef __KERNEL__ + +#include <asm/uaccess.h> +#include <linux/videodev.h> +#include <linux/smp_lock.h> + +struct cpia_camera_ops +{ + /* open sets privdata to point to structure for this camera. + * Returns negative value on error, otherwise 0. + */ + int (*open)(void *privdata); + + /* Registers callback function cb to be called with cbdata + * when an image is ready. If cb is NULL, only single image grabs + * should be used. cb should immediately call streamRead to read + * the data or data may be lost. Returns negative value on error, + * otherwise 0. + */ + int (*registerCallback)(void *privdata, void (*cb)(void *cbdata), + void *cbdata); + + /* transferCmd sends commands to the camera. command MUST point to + * an 8 byte buffer in kernel space. data can be NULL if no extra + * data is needed. The size of the data is given by the last 2 + * bytes of comand. data must also point to memory in kernel space. + * Returns negative value on error, otherwise 0. + */ + int (*transferCmd)(void *privdata, u8 *command, u8 *data); + + /* streamStart initiates stream capture mode. + * Returns negative value on error, otherwise 0. + */ + int (*streamStart)(void *privdata); + + /* streamStop terminates stream capture mode. + * Returns negative value on error, otherwise 0. + */ + int (*streamStop)(void *privdata); + + /* streamRead reads a frame from the camera. buffer points to a + * buffer large enough to hold a complete frame in kernel space. + * noblock indicates if this should be a non blocking read. + * Returns the number of bytes read, or negative value on error. + */ + int (*streamRead)(void *privdata, u8 *buffer, int noblock); + + /* close disables the device until open() is called again. + * Returns negative value on error, otherwise 0. + */ + int (*close)(void *privdata); + + /* If wait_for_stream_ready is non-zero, wait until the streamState + * is STREAM_READY before calling streamRead. + */ + int wait_for_stream_ready; +}; + +struct cpia_frame { + u8 *data; + int count; + int width; + int height; + volatile int state; +}; + +struct cam_params { + struct { + u8 firmwareVersion; + u8 firmwareRevision; + u8 vcVersion; + u8 vcRevision; + } version; + struct { + u16 vendor; + u16 product; + u16 deviceRevision; + } pnpID; + struct { + u8 vpVersion; + u8 vpRevision; + u16 cameraHeadID; + } vpVersion; + struct { + u8 systemState; + u8 grabState; + u8 streamState; + u8 fatalError; + u8 cmdError; + u8 debugFlags; + u8 vpStatus; + u8 errorCode; + } status; + struct { + u8 brightness; + u8 contrast; + u8 saturation; + } colourParams; + struct { + u8 gainMode; + u8 expMode; + u8 compMode; + u8 centreWeight; + u8 gain; + u8 fineExp; + u8 coarseExpLo; + u8 coarseExpHi; + u8 redComp; + u8 green1Comp; + u8 green2Comp; + u8 blueComp; + } exposure; + struct { + u8 balanceModeIsAuto; + u8 redGain; + u8 greenGain; + u8 blueGain; + } colourBalance; + struct { + u8 divisor; + u8 baserate; + } sensorFps; + struct { + u8 gain1; + u8 gain2; + u8 gain4; + u8 gain8; + } apcor; + struct { + u8 flickerMode; + u8 coarseJump; + u8 allowableOverExposure; + } flickerControl; + struct { + u8 gain1; + u8 gain2; + u8 gain4; + u8 gain8; + } vlOffset; + struct { + u8 mode; + u8 decimation; + } compression; + struct { + u8 frTargeting; + u8 targetFR; + u8 targetQ; + } compressionTarget; + struct { + u8 yThreshold; + u8 uvThreshold; + } yuvThreshold; + struct { + u8 hysteresis; + u8 threshMax; + u8 smallStep; + u8 largeStep; + u8 decimationHysteresis; + u8 frDiffStepThresh; + u8 qDiffStepThresh; + u8 decimationThreshMod; + } compressionParams; + struct { + u8 videoSize; /* CIF/QCIF */ + u8 subSample; + u8 yuvOrder; + } format; + struct { + u8 colStart; /* skip first 8*colStart pixels */ + u8 colEnd; /* finish at 8*colEnd pixels */ + u8 rowStart; /* skip first 4*rowStart lines */ + u8 rowEnd; /* finish at 4*rowEnd lines */ + } roi; + u8 ecpTiming; + u8 streamStartLine; +}; + +enum v4l_camstates { + CPIA_V4L_IDLE = 0, + CPIA_V4L_ERROR, + CPIA_V4L_COMMAND, + CPIA_V4L_GRABBING, + CPIA_V4L_STREAMING, + CPIA_V4L_STREAMING_PAUSED, +}; + +#define FRAME_NUM 2 /* double buffering for now */ + +struct cam_data { + struct cam_data **previous; + struct cam_data *next; + + struct semaphore busy_lock; /* guard against SMP multithreading */ + struct cpia_camera_ops *ops; /* lowlevel driver operations */ + void *lowlevel_data; /* private data for lowlevel driver */ + u8 *raw_image; /* buffer for raw image data */ + struct cpia_frame decompressed_frame; + /* buffer to hold decompressed frame */ + int image_size; /* sizeof last decompressed image */ + int open_count; /* # of process that have camera open */ + + /* camera status */ + int fps; /* actual fps reported by the camera */ + int transfer_rate; /* transfer rate from camera in kB/s */ + u8 mainsFreq; /* for flicker control */ + + /* proc interface */ + struct semaphore param_lock; /* params lock for this camera */ + struct cam_params params; /* camera settings */ + struct proc_dir_entry *proc_entry; /* /proc/cpia/videoX */ + + /* v4l */ + int video_size; /* VIDEO_SIZE_ */ + volatile enum v4l_camstates camstate; /* v4l layer status */ + struct video_device vdev; /* v4l videodev */ + struct video_picture vp; /* v4l camera settings */ + struct video_window vw; /* v4l capture area */ + + /* mmap interface */ + int curframe; /* the current frame to grab into */ + u8 *frame_buf; /* frame buffer data */ + struct cpia_frame frame[FRAME_NUM]; + /* FRAME_NUM-buffering, so we need a array */ + + int first_frame; + int mmap_kludge; /* 'wrong' byte order for mmap */ + volatile u32 cmd_queue; /* queued commands */ +}; + +/* cpia_register_camera is called by low level driver for each camera. + * A unique camera number is returned, or a negative value on error */ +struct cam_data *cpia_register_camera(struct cpia_camera_ops *ops, void *lowlevel); + +/* cpia_unregister_camera is called by low level driver when a camera + * is removed. This must not fail. */ +void cpia_unregister_camera(struct cam_data *cam); + +/* raw CIF + 64 byte header + (2 bytes line_length + EOL) per line + 4*EOI + + * one byte 16bit DMA alignment + */ +#define CPIA_MAX_IMAGE_SIZE ((352*288*2)+64+(288*3)+5) + +/* constant value's */ +#define MAGIC_0 0x19 +#define MAGIC_1 0x68 +#define DATA_IN 0xC0 +#define DATA_OUT 0x40 +#define VIDEOSIZE_QCIF 0 /* 176x144 */ +#define VIDEOSIZE_CIF 1 /* 352x288 */ +#define VIDEOSIZE_SIF 2 /* 320x240 */ +#define VIDEOSIZE_QSIF 3 /* 160x120 */ +#define VIDEOSIZE_48_48 4 /* where no one has gone before, iconsize! */ +#define VIDEOSIZE_64_48 5 +#define VIDEOSIZE_128_96 6 +#define VIDEOSIZE_160_120 VIDEOSIZE_QSIF +#define VIDEOSIZE_176_144 VIDEOSIZE_QCIF +#define VIDEOSIZE_192_144 7 +#define VIDEOSIZE_224_168 8 +#define VIDEOSIZE_256_192 9 +#define VIDEOSIZE_288_216 10 +#define VIDEOSIZE_320_240 VIDEOSIZE_SIF +#define VIDEOSIZE_352_288 VIDEOSIZE_CIF +#define VIDEOSIZE_88_72 11 /* quarter CIF */ +#define SUBSAMPLE_420 0 +#define SUBSAMPLE_422 1 +#define YUVORDER_YUYV 0 +#define YUVORDER_UYVY 1 +#define NOT_COMPRESSED 0 +#define COMPRESSED 1 +#define NO_DECIMATION 0 +#define DECIMATION_ENAB 1 +#define EOI 0xff /* End Of Image */ +#define EOL 0xfd /* End Of Line */ +#define FRAME_HEADER_SIZE 64 + +/* Image grab modes */ +#define CPIA_GRAB_SINGLE 0 +#define CPIA_GRAB_CONTINUOUS 1 + +/* Compression parameters */ +#define CPIA_COMPRESSION_NONE 0 +#define CPIA_COMPRESSION_AUTO 1 +#define CPIA_COMPRESSION_MANUAL 2 +#define CPIA_COMPRESSION_TARGET_QUALITY 0 +#define CPIA_COMPRESSION_TARGET_FRAMERATE 1 + +/* Return offsets for GetCameraState */ +#define SYSTEMSTATE 0 +#define GRABSTATE 1 +#define STREAMSTATE 2 +#define FATALERROR 3 +#define CMDERROR 4 +#define DEBUGFLAGS 5 +#define VPSTATUS 6 +#define ERRORCODE 7 + +/* SystemState */ +#define UNINITIALISED_STATE 0 +#define PASS_THROUGH_STATE 1 +#define LO_POWER_STATE 2 +#define HI_POWER_STATE 3 +#define WARM_BOOT_STATE 4 + +/* GrabState */ +#define GRAB_IDLE 0 +#define GRAB_ACTIVE 1 +#define GRAB_DONE 2 + +/* StreamState */ +#define STREAM_NOT_READY 0 +#define STREAM_READY 1 +#define STREAM_OPEN 2 +#define STREAM_PAUSED 3 +#define STREAM_FINISHED 4 + +/* Fatal Error, CmdError, and DebugFlags */ +#define CPIA_FLAG 1 +#define SYSTEM_FLAG 2 +#define INT_CTRL_FLAG 4 +#define PROCESS_FLAG 8 +#define COM_FLAG 16 +#define VP_CTRL_FLAG 32 +#define CAPTURE_FLAG 64 +#define DEBUG_FLAG 128 + +/* VPStatus */ +#define VP_STATE_OK 0x00 + +#define VP_STATE_FAILED_VIDEOINIT 0x01 +#define VP_STATE_FAILED_AECACBINIT 0x02 +#define VP_STATE_AEC_MAX 0x04 +#define VP_STATE_ACB_BMAX 0x08 + +#define VP_STATE_ACB_RMIN 0x10 +#define VP_STATE_ACB_GMIN 0x20 +#define VP_STATE_ACB_RMAX 0x40 +#define VP_STATE_ACB_GMAX 0x80 + +/* ErrorCode */ +#define ERROR_FLICKER_BELOW_MIN_EXP 0x01 /*flicker exposure got below minimum exposure */ + +#define ALOG(lineno,fmt,args...) printk(fmt,lineno,##args) +#define LOG(fmt,args...) ALOG((__LINE__),KERN_INFO __FILE__":"__FUNCTION__"(%d):"fmt,##args) + +#ifdef _CPIA_DEBUG_ +#define ADBG(lineno,fmt,args...) printk(fmt, jiffies, lineno, ##args) +#define DBG(fmt,args...) ADBG((__LINE__),KERN_DEBUG __FILE__"(%ld):"__FUNCTION__"(%d):"fmt,##args) +#else +#define DBG(fmn,args...) do {} while(0) +#endif + +#define DEB_BYTE(p)\ + DBG("%1d %1d %1d %1d %1d %1d %1d %1d \n",\ + (p)&0x80?1:0, (p)&0x40?1:0, (p)&0x20?1:0, (p)&0x10?1:0,\ + (p)&0x08?1:0, (p)&0x04?1:0, (p)&0x02?1:0, (p)&0x01?1:0); + +#define ADD_TO_LIST(l, drv) \ + {\ + lock_kernel();\ + (drv)->next = l;\ + (drv)->previous = &(l);\ + (l) = drv;\ + unlock_kernel();\ + } while(0) + +#define REMOVE_FROM_LIST(drv) \ + {\ + if ((drv)->previous != NULL) {\ + lock_kernel();\ + if ((drv)->next != NULL)\ + (drv)->next->previous = (drv)->previous;\ + *((drv)->previous) = (drv)->next;\ + (drv)->previous = NULL;\ + (drv)->next = NULL;\ + unlock_kernel();\ + }\ + } while (0) + + +#endif /* __KERNEL__ */ + +#endif /* cpia_h */ diff --git a/drivers/media/video/cpia_pp.c b/drivers/media/video/cpia_pp.c new file mode 100644 index 000000000..7d4be2744 --- /dev/null +++ b/drivers/media/video/cpia_pp.c @@ -0,0 +1,745 @@ +/* + * cpia_pp CPiA Parallel Port driver + * + * Supports CPiA based parallel port Video Camera's. + * + * (C) Copyright 1999 Bas Huisman <bhuism@cs.utwente.nl> + * (C) Copyright 1999-2000 Scott J. Bertin <sbertin@mindspring.com>, + * (C) Copyright 1999-2000 Peter Pregler <Peter_Pregler@email.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/config.h> +#include <linux/version.h> + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/kernel.h> +#include <linux/parport.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/smp_lock.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +/* #define _CPIA_DEBUG_ define for verbose debug output */ +#include "cpia.h" + +static int cpia_pp_open(void *privdata); +static int cpia_pp_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata); +static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data); +static int cpia_pp_streamStart(void *privdata); +static int cpia_pp_streamStop(void *privdata); +static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock); +static int cpia_pp_close(void *privdata); + +#define ABOUT "Parallel port driver for Vision CPiA based cameras" + +/* IEEE 1284 Compatiblity Mode signal names */ +#define nStrobe PARPORT_CONTROL_STROBE /* inverted */ +#define nAutoFd PARPORT_CONTROL_AUTOFD /* inverted */ +#define nInit PARPORT_CONTROL_INIT +#define nSelectIn PARPORT_CONTROL_SELECT +#define IntrEnable PARPORT_CONTROL_INTEN /* normally zero for no IRQ */ +#define DirBit PARPORT_CONTROL_DIRECTION /* 0 = Forward, 1 = Reverse */ + +#define nFault PARPORT_STATUS_ERROR +#define Select PARPORT_STATUS_SELECT +#define PError PARPORT_STATUS_PAPEROUT +#define nAck PARPORT_STATUS_ACK +#define Busy PARPORT_STATUS_BUSY /* inverted */ + +/* some more */ +#define HostClk nStrobe +#define HostAck nAutoFd +#define nReverseRequest nInit +#define Active_1284 nSelectIn +#define nPeriphRequest nFault +#define XFlag Select +#define nAckReverse PError +#define PeriphClk nAck +#define PeriphAck Busy + +/* these can be used to correct for the inversion on some bits */ +#define STATUS_INVERSION_MASK (Busy) +#define CONTROL_INVERSION_MASK (nStrobe|nAutoFd|nSelectIn) + +#define ECR_empty 0x01 +#define ECR_full 0x02 +#define ECR_serviceIntr 0x04 +#define ECR_dmaEn 0x08 +#define ECR_nErrIntrEn 0x10 + +#define ECR_mode_mask 0xE0 +#define ECR_SPP_mode 0x00 +#define ECR_PS2_mode 0x20 +#define ECR_FIFO_mode 0x40 +#define ECR_ECP_mode 0x60 + +#define ECP_FIFO_SIZE 16 +#define DMA_BUFFER_SIZE PAGE_SIZE + /* for 16bit DMA make sure DMA_BUFFER_SIZE is 16 bit aligned */ +#define PARPORT_CHUNK_SIZE PAGE_SIZE/* >=2.3.x */ + /* we read this many bytes at once */ + +#define GetECRMasked(port,mask) (parport_read_econtrol(port) & (mask)) +#define GetStatus(port) ((parport_read_status(port)^STATUS_INVERSION_MASK)&(0xf8)) +#define SetStatus(port,val) parport_write_status(port,(val)^STATUS_INVERSION_MASK) +#define GetControl(port) ((parport_read_control(port)^CONTROL_INVERSION_MASK)&(0x3f)) +#define SetControl(port,val) parport_write_control(port,(val)^CONTROL_INVERSION_MASK) + +#define GetStatusMasked(port,mask) (GetStatus(port) & (mask)) +#define GetControlMasked(port,mask) (GetControl(port) & (mask)) +#define SetControlMasked(port,mask) SetControl(port,GetControl(port) | (mask)); +#define ClearControlMasked(port,mask) SetControl(port,GetControl(port)&~(mask)); +#define FrobControlBit(port,mask,value) SetControl(port,(GetControl(port)&~(mask))|((value)&(mask))); + +#define PACKET_LENGTH 8 + +/* Magic numbers for defining port-device mappings */ +#define PPCPIA_PARPORT_UNSPEC -4 +#define PPCPIA_PARPORT_AUTO -3 +#define PPCPIA_PARPORT_OFF -2 +#define PPCPIA_PARPORT_NONE -1 + +#ifdef MODULE +static int parport_nr[PARPORT_MAX] = {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC}; +static char *parport[PARPORT_MAX] = {NULL,}; + +MODULE_AUTHOR("B. Huisman <bhuism@cs.utwente.nl> & Peter Pregler <Peter_Pregler@email.com>"); +MODULE_DESCRIPTION("Parallel port driver for Vision CPiA based cameras"); +MODULE_PARM(parport, "1-" __MODULE_STRING(PARPORT_MAX) "s"); +MODULE_PARM_DESC(parport, "'auto' or a list of parallel port numbers. Just like lp."); +#else +static int parport_nr[PARPORT_MAX] __initdata = + {[0 ... PARPORT_MAX - 1] = PPCPIA_PARPORT_UNSPEC}; +static int parport_ptr = 0; +#endif + +struct pp_cam_entry { + struct pardevice *pdev; + struct parport *port; + struct tq_struct cb_task; + int open_count; + wait_queue_head_t wq_stream; + /* image state flags */ + int image_ready; /* we got an interrupt */ + int image_complete; /* we have seen 4 EOI */ + + int streaming; /* we are in streaming mode */ + int stream_irq; +}; + +static struct cpia_camera_ops cpia_pp_ops = +{ + cpia_pp_open, + cpia_pp_registerCallback, + cpia_pp_transferCmd, + cpia_pp_streamStart, + cpia_pp_streamStop, + cpia_pp_streamRead, + cpia_pp_close, + 1 +}; + +static struct cam_data *cam_list; + +#ifdef _CPIA_DEBUG_ +#define DEB_PORT(port) { \ +u8 controll = GetControl(port); \ +u8 statusss = GetStatus(port); \ +DBG("nsel %c per %c naut %c nstrob %c nak %c busy %c nfaul %c sel %c init %c dir %c\n",\ +((controll & nSelectIn) ? 'U' : 'D'), \ +((statusss & PError) ? 'U' : 'D'), \ +((controll & nAutoFd) ? 'U' : 'D'), \ +((controll & nStrobe) ? 'U' : 'D'), \ +((statusss & nAck) ? 'U' : 'D'), \ +((statusss & Busy) ? 'U' : 'D'), \ +((statusss & nFault) ? 'U' : 'D'), \ +((statusss & Select) ? 'U' : 'D'), \ +((controll & nInit) ? 'U' : 'D'), \ +((controll & DirBit) ? 'R' : 'F') \ +); } +#else +#define DEB_PORT(port) {} +#endif + +#define WHILE_OUT_TIMEOUT (HZ/10) +#define DMA_TIMEOUT 10*HZ + +/* FIXME */ +static void cpia_parport_enable_irq( struct parport *port ) { + parport_enable_irq(port); + mdelay(10); + return; +} + +static void cpia_parport_disable_irq( struct parport *port ) { + parport_disable_irq(port); + mdelay(10); + return; +} + +/**************************************************************************** + * + * EndTransferMode + * + ***************************************************************************/ +static void EndTransferMode(struct pp_cam_entry *cam) +{ + parport_negotiate(cam->port, IEEE1284_MODE_COMPAT); +} + +/**************************************************************************** + * + * ForwardSetup + * + ***************************************************************************/ +static int ForwardSetup(struct pp_cam_entry *cam) +{ + int retry; + + /* After some commands the camera needs extra time before + * it will respond again, so we try up to 3 times */ + for(retry=0; retry<3; ++retry) { + if(!parport_negotiate(cam->port, IEEE1284_MODE_ECP)) { + break; + } + } + if(retry == 3) { + DBG("Unable to negotiate ECP mode\n"); + return -1; + } + return 0; +} + +/**************************************************************************** + * + * ReverseSetup + * + ***************************************************************************/ +static int ReverseSetup(struct pp_cam_entry *cam, int extensibility) +{ + int retry; + int mode = IEEE1284_MODE_ECP; + if(extensibility) mode = 8|3|IEEE1284_EXT_LINK; + + /* After some commands the camera needs extra time before + * it will respond again, so we try up to 3 times */ + for(retry=0; retry<3; ++retry) { + if(!parport_negotiate(cam->port, mode)) { + break; + } + } + if(retry == 3) { + if(extensibility) + DBG("Unable to negotiate extensibility mode\n"); + else + DBG("Unable to negotiate ECP mode\n"); + return -1; + } + if(extensibility) cam->port->ieee1284.mode = IEEE1284_MODE_ECP; + return 0; +} + +/**************************************************************************** + * + * WritePacket + * + ***************************************************************************/ +static int WritePacket(struct pp_cam_entry *cam, const u8 *packet, size_t size) +{ + int retval=0; + int size_written; + + if (packet == NULL) { + return -EINVAL; + } + if (ForwardSetup(cam)) { + DBG("Write failed in setup\n"); + return -EIO; + } + size_written = parport_write(cam->port, packet, size); + if(size_written != size) { + DBG("Write failed, wrote %d/%d\n", size_written, size); + retval = -EIO; + } + EndTransferMode(cam); + return retval; +} + +/**************************************************************************** + * + * ReadPacket + * + ***************************************************************************/ +static int ReadPacket(struct pp_cam_entry *cam, u8 *packet, size_t size) +{ + int retval=0; + if (packet == NULL) { + return -EINVAL; + } + if (ReverseSetup(cam, 0)) { + return -EIO; + } + if(parport_read(cam->port, packet, size) != size) { + retval = -EIO; + } + EndTransferMode(cam); + return retval; +} + +/**************************************************************************** + * + * cpia_pp_streamStart + * + ***************************************************************************/ +static int cpia_pp_streamStart(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + DBG("\n"); + cam->streaming=1; + cam->image_ready=0; + //if (ReverseSetup(cam,1)) return -EIO; + if(cam->stream_irq) cpia_parport_enable_irq(cam->port); + return 0; +} + +/**************************************************************************** + * + * cpia_pp_streamStop + * + ***************************************************************************/ +static int cpia_pp_streamStop(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + + DBG("\n"); + cam->streaming=0; + cpia_parport_disable_irq(cam->port); + //EndTransferMode(cam); + + return 0; +} + +static int cpia_pp_read(struct parport *port, u8 *buffer, int len) +{ + int bytes_read, new_bytes; + for(bytes_read=0; bytes_read<len; bytes_read += new_bytes) { + new_bytes = parport_read(port, buffer+bytes_read, + len-bytes_read); + if(new_bytes < 0) break; + } + return bytes_read; +} + +/**************************************************************************** + * + * cpia_pp_streamRead + * + ***************************************************************************/ +static int cpia_pp_streamRead(void *privdata, u8 *buffer, int noblock) +{ + struct pp_cam_entry *cam = privdata; + int read_bytes = 0; + int i, endseen, block_size, new_bytes; + + if(cam == NULL) { + DBG("Internal driver error: cam is NULL\n"); + return -EINVAL; + } + if(buffer == NULL) { + DBG("Internal driver error: buffer is NULL\n"); + return -EINVAL; + } + //if(cam->streaming) DBG("%d / %d\n", cam->image_ready, noblock); + if( cam->stream_irq ) { + DBG("%d\n", cam->image_ready); + cam->image_ready--; + } + cam->image_complete=0; + if (0/*cam->streaming*/) { + if(!cam->image_ready) { + if(noblock) return -EWOULDBLOCK; + interruptible_sleep_on(&cam->wq_stream); + if( signal_pending(current) ) return -EINTR; + DBG("%d\n", cam->image_ready); + } + } else { + if (ReverseSetup(cam, 1)) { + DBG("unable to ReverseSetup\n"); + return -EIO; + } + } + endseen = 0; + block_size = PARPORT_CHUNK_SIZE; + while( !cam->image_complete ) { + if(current->need_resched) schedule(); + + new_bytes = cpia_pp_read(cam->port, buffer, block_size ); + if( new_bytes <= 0 ) { + break; + } + i=-1; + while(++i<new_bytes && endseen<4) { + if(*buffer==EOI) { + endseen++; + } else { + endseen=0; + } + buffer++; + } + read_bytes += i; + if( endseen==4 ) { + cam->image_complete=1; + break; + } + if( CPIA_MAX_IMAGE_SIZE-read_bytes <= PARPORT_CHUNK_SIZE ) { + block_size=CPIA_MAX_IMAGE_SIZE-read_bytes; + } + } + EndTransferMode(cam); + return cam->image_complete ? read_bytes : -EIO; +} + +/**************************************************************************** + * + * cpia_pp_transferCmd + * + ***************************************************************************/ +static int cpia_pp_transferCmd(void *privdata, u8 *command, u8 *data) +{ + int err; + int retval=0; + int databytes; + struct pp_cam_entry *cam = privdata; + + if(cam == NULL) { + DBG("Internal driver error: cam is NULL\n"); + return -EINVAL; + } + if(command == NULL) { + DBG("Internal driver error: command is NULL\n"); + return -EINVAL; + } + databytes = (((int)command[7])<<8) | command[6]; + if ((err = WritePacket(cam, command, PACKET_LENGTH)) < 0) { + DBG("Error writing command\n"); + return err; + } + if(command[0] == DATA_IN) { + u8 buffer[8]; + if(data == NULL) { + DBG("Internal driver error: data is NULL\n"); + return -EINVAL; + } + if((err = ReadPacket(cam, buffer, 8)) < 0) { + return err; + DBG("Error reading command result\n"); + } + memcpy(data, buffer, databytes); + } else if(command[0] == DATA_OUT) { + if(databytes > 0) { + if(data == NULL) { + DBG("Internal driver error: data is NULL\n"); + retval = -EINVAL; + } else { + if((err=WritePacket(cam, data, databytes)) < 0){ + DBG("Error writing command data\n"); + return err; + } + } + } + } else { + DBG("Unexpected first byte of command: %x\n", command[0]); + retval = -EINVAL; + } + return retval; +} + +/**************************************************************************** + * + * cpia_pp_open + * + ***************************************************************************/ +static int cpia_pp_open(void *privdata) +{ + struct pp_cam_entry *cam = (struct pp_cam_entry *)privdata; + + if (cam == NULL) + return -EINVAL; + + if(cam->open_count == 0) { + if (parport_claim(cam->pdev)) { + DBG("failed to claim the port\n"); + return -EBUSY; + } + parport_negotiate(cam->port, IEEE1284_MODE_COMPAT); + parport_data_forward(cam->port); + parport_write_control(cam->port, PARPORT_CONTROL_SELECT); + udelay(50); + parport_write_control(cam->port, + PARPORT_CONTROL_SELECT + | PARPORT_CONTROL_INIT); + } + + ++cam->open_count; + +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif + return 0; +} + +/**************************************************************************** + * + * cpia_pp_registerCallback + * + ***************************************************************************/ +static int cpia_pp_registerCallback(void *privdata, void (*cb)(void *cbdata), void *cbdata) +{ + struct pp_cam_entry *cam = privdata; + int retval = 0; + + if(cam->port->irq != PARPORT_IRQ_NONE) { + cam->cb_task.routine = cb; + cam->cb_task.data = cbdata; + } else { + retval = -1; + } + return retval; +} + +/**************************************************************************** + * + * cpia_pp_close + * + ***************************************************************************/ +static int cpia_pp_close(void *privdata) +{ + struct pp_cam_entry *cam = privdata; +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif + if (--cam->open_count == 0) { + parport_release(cam->pdev); + } + return 0; +} + +/**************************************************************************** + * + * cpia_pp_register + * + ***************************************************************************/ +static int cpia_pp_register(struct parport *port) +{ + struct pardevice *pdev = NULL; + struct pp_cam_entry *cam; + struct cam_data *cpia; + + if (!(port->modes & PARPORT_MODE_ECP) && + !(port->modes & PARPORT_MODE_TRISTATE)) { + LOG("port is not ECP capable\n"); + return -ENXIO; + } + + cam = kmalloc(sizeof(struct pp_cam_entry), GFP_KERNEL); + if (cam == NULL) { + LOG("failed to allocate camera structure\n"); + return -ENOMEM; + } + memset(cam,0,sizeof(struct pp_cam_entry)); + + pdev = parport_register_device(port, "cpia_pp", NULL, NULL, + NULL, 0, cam); + + if (!pdev) { + LOG("failed to parport_register_device\n"); + kfree(cam); + return -ENXIO; + } + + cam->pdev = pdev; + cam->port = port; + init_waitqueue_head(&cam->wq_stream); + + cam->streaming = 0; + cam->stream_irq = 0; + + if((cpia = cpia_register_camera(&cpia_pp_ops, cam)) == NULL) { + LOG("failed to cpia_register_camera\n"); + parport_unregister_device(pdev); + kfree(cam); + return -ENXIO; + } + ADD_TO_LIST(cam_list, cpia); + + return 0; +} + +static void cpia_pp_detach (struct parport *port) +{ + struct cam_data *cpia; + + for(cpia = cam_list; cpia != NULL; cpia = cpia->next) { + struct pp_cam_entry *cam = cpia->lowlevel_data; + if (cam && cam->port->number == port->number) { + REMOVE_FROM_LIST(cpia); + + cpia_unregister_camera(cpia); + + if(cam->open_count > 0) { + cpia_pp_close(cam); + } + + parport_unregister_device(cam->pdev); + + kfree(cam); + cpia->lowlevel_data = NULL; + break; + } + } +} + +static void cpia_pp_attach (struct parport *port) +{ + unsigned int i; + + switch (parport_nr[0]) + { + case PPCPIA_PARPORT_UNSPEC: + case PPCPIA_PARPORT_AUTO: + if (port->probe_info[0].class != PARPORT_CLASS_MEDIA || + port->probe_info[0].cmdset == NULL || + strncmp(port->probe_info[0].cmdset, "CPIA_1", 6) != 0) + return; + + cpia_pp_register(port); + + break; + + default: + for (i = 0; i < PARPORT_MAX; ++i) { + if (port->number == parport_nr[i]) { + cpia_pp_register(port); + break; + } + } + break; + } +} + +static struct parport_driver cpia_pp_driver = { + "cpia_pp", + cpia_pp_attach, + cpia_pp_detach, + NULL +}; + +int cpia_pp_init(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT, + CPIA_PP_MAJ_VER,CPIA_PP_MIN_VER,CPIA_PP_PATCH_VER); + + if(parport_nr[0] == PPCPIA_PARPORT_OFF) { + printk(" disabled\n"); + return 0; + } + + if (parport_register_driver (&cpia_pp_driver)) { + LOG ("unable to register with parport\n"); + return -EIO; + } + + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + if (parport[0]) { + /* The user gave some parameters. Let's see what they were. */ + if (!strncmp(parport[0], "auto", 4)) { + parport_nr[0] = PPCPIA_PARPORT_AUTO; + } else { + int n; + for (n = 0; n < PARPORT_MAX && parport[n]; n++) { + if (!strncmp(parport[n], "none", 4)) { + parport_nr[n] = PPCPIA_PARPORT_NONE; + } else { + char *ep; + unsigned long r = simple_strtoul(parport[n], &ep, 0); + if (ep != parport[n]) { + parport_nr[n] = r; + } else { + LOG("bad port specifier `%s'\n", parport[n]); + return -ENODEV; + } + } + } + } + } +#if defined(CONFIG_KMOD) && defined(CONFIG_PNP_PARPORT_MODULE) + if(parport_enumerate() && !parport_enumerate()->probe_info.model) { + request_module("parport_probe"); + } +#endif + return cpia_pp_init(); +} + +void cleanup_module(void) +{ + parport_unregister_driver (&cpia_pp_driver); + return; +} + +#else /* !MODULE */ + +static int __init cpia_pp_setup(char *str) +{ +#if 0 + /* Is this only a 2.2ism? -jerdfelt */ + if (!str) { + if (ints[0] == 0 || ints[1] == 0) { + /* disable driver on "cpia_pp=" or "cpia_pp=0" */ + parport_nr[0] = PPCPIA_PARPORT_OFF; + } + } else +#endif + if (!strncmp(str, "parport", 7)) { + int n = simple_strtoul(str + 7, NULL, 10); + if (parport_ptr < PARPORT_MAX) { + parport_nr[parport_ptr++] = n; + } else { + LOG("too many ports, %s ignored.\n", str); + } + } else if (!strcmp(str, "auto")) { + parport_nr[0] = PPCPIA_PARPORT_AUTO; + } else if (!strcmp(str, "none")) { + parport_nr[parport_ptr++] = PPCPIA_PARPORT_NONE; + } + + return 0; +} + +__setup("cpia_pp=", cpia_pp_setup); + +#endif /* !MODULE */ diff --git a/drivers/media/video/cpia_usb.c b/drivers/media/video/cpia_usb.c new file mode 100644 index 000000000..6b67fbc81 --- /dev/null +++ b/drivers/media/video/cpia_usb.c @@ -0,0 +1,626 @@ +/* + * cpia_usb CPiA USB driver + * + * Supports CPiA based parallel port Video Camera's. + * + * Copyright (C) 1999 Jochen Scharrlach <Jochen.Scharrlach@schwaben.de> + * Copyright (C) 1999, 2000 Johannes Erdfelt <jerdfelt@valinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/usb.h> + +#include "cpia.h" + +#define USB_REQ_CPIA_GRAB_FRAME 0xC1 +#define USB_REQ_CPIA_UPLOAD_FRAME 0xC2 +#define WAIT_FOR_NEXT_FRAME 0 +#define FORCE_FRAME_UPLOAD 1 + +#define FRAMES_PER_DESC 10 +#define FRAME_SIZE_PER_DESC 960 /* Shouldn't be hardcoded */ +#define CPIA_NUMSBUF 2 +#define STREAM_BUF_SIZE (PAGE_SIZE * 4) +#define SCRATCH_BUF_SIZE (STREAM_BUF_SIZE * 2) + +struct cpia_sbuf { + char *data; + urb_t *urb; +}; + +#define FRAMEBUF_LEN (CPIA_MAX_FRAME_SIZE+100) +enum framebuf_status { + FRAME_EMPTY, + FRAME_READING, + FRAME_READY, + FRAME_ERROR, +}; + +struct framebuf { + int length; + enum framebuf_status status; + u8 data[FRAMEBUF_LEN]; + struct framebuf *next; +}; + +struct usb_cpia { + /* Device structure */ + struct usb_device *dev; + + unsigned char iface; + wait_queue_head_t wq_stream; + + int cursbuf; /* Current receiving sbuf */ + struct cpia_sbuf sbuf[CPIA_NUMSBUF]; /* Double buffering */ + + int streaming; + int open; + int present; + struct framebuf *buffers[3]; + struct framebuf *curbuff, *workbuff; +}; + +static int cpia_usb_open(void *privdata); +static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata); +static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data); +static int cpia_usb_streamStart(void *privdata); +static int cpia_usb_streamStop(void *privdata); +static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock); +static int cpia_usb_close(void *privdata); + +#define ABOUT "USB driver for Vision CPiA based cameras" + +static struct cpia_camera_ops cpia_usb_ops = { + cpia_usb_open, + cpia_usb_registerCallback, + cpia_usb_transferCmd, + cpia_usb_streamStart, + cpia_usb_streamStop, + cpia_usb_streamRead, + cpia_usb_close, + 0 +}; + +static struct cam_data *cam_list; + +static void cpia_usb_complete(struct urb *urb) +{ + int i; + char *cdata; + struct usb_cpia *ucpia; + + if (!urb || !urb->context) + return; + + ucpia = (struct usb_cpia *) urb->context; + + if (!ucpia->dev || !ucpia->streaming || !ucpia->present || !ucpia->open) + return; + + if (ucpia->workbuff->status == FRAME_EMPTY) { + ucpia->workbuff->status = FRAME_READING; + ucpia->workbuff->length = 0; + } + + for (i = 0; i < urb->number_of_packets; i++) { + int n = urb->iso_frame_desc[i].actual_length; + int st = urb->iso_frame_desc[i].status; + + cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset; + + if (st) + printk(KERN_DEBUG "cpia data error: [%d] len=%d, status=%X\n", i, n, st); + + if (FRAMEBUF_LEN < ucpia->workbuff->length + n) { + printk(KERN_DEBUG "cpia: scratch buf overflow!scr_len: %d, n: %d\n", ucpia->workbuff->length, n); + return; + } + + if (n) { + if ((ucpia->workbuff->length > 0) || + (0x19 == cdata[0] && 0x68 == cdata[1])) { + memcpy(ucpia->workbuff->data + ucpia->workbuff->length, cdata, n); + ucpia->workbuff->length += n; + } else + DBG("Ignoring packet!\n"); + } else { + if (ucpia->workbuff->length > 4 && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-1] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-2] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-3] && + 0xff == ucpia->workbuff->data[ucpia->workbuff->length-4]) { + ucpia->workbuff->status = FRAME_READY; + ucpia->curbuff = ucpia->workbuff; + ucpia->workbuff = ucpia->workbuff->next; + ucpia->workbuff->status = FRAME_EMPTY; + ucpia->workbuff->length = 0; + + if (waitqueue_active(&ucpia->wq_stream)) + wake_up_interruptible(&ucpia->wq_stream); + } + } + } +} + +static int cpia_usb_open(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + urb_t *urb; + int ret, retval = 0, fx, err; + + if (!ucpia) + return -EINVAL; + + ucpia->sbuf[0].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL); + if (!ucpia->sbuf[0].data) + return -EINVAL; + + ucpia->sbuf[1].data = kmalloc(FRAMES_PER_DESC * FRAME_SIZE_PER_DESC, GFP_KERNEL); + if (!ucpia->sbuf[1].data) { + retval = -EINVAL; + goto error_0; + } + + ret = usb_set_interface(ucpia->dev, ucpia->iface, 3); + if (ret < 0) { + printk(KERN_ERR "cpia_usb_open: usb_set_interface error (ret = %d)\n", ret); + retval = -EBUSY; + goto error_all; + } + + ucpia->buffers[0]->status = FRAME_EMPTY; + ucpia->buffers[0]->length = 0; + ucpia->buffers[1]->status = FRAME_EMPTY; + ucpia->buffers[1]->length = 0; + ucpia->buffers[2]->status = FRAME_EMPTY; + ucpia->buffers[2]->length = 0; + ucpia->curbuff = ucpia->buffers[0]; + ucpia->workbuff = ucpia->buffers[1]; + + /* We double buffer the Iso lists */ + urb = usb_alloc_urb(FRAMES_PER_DESC); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n"); + retval = -ENOMEM; + goto error_all; + } + + ucpia->sbuf[0].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = USB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[0].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC; + for (fx = 0; fx < FRAMES_PER_DESC; fx++) { + urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx; + urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC; + } + + urb = usb_alloc_urb(FRAMES_PER_DESC); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n"); + retval = -ENOMEM; + goto error_all; + } + + ucpia->sbuf[1].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = USB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[1].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = FRAME_SIZE_PER_DESC * FRAMES_PER_DESC; + for (fx = 0; fx < FRAMES_PER_DESC; fx++) { + urb->iso_frame_desc[fx].offset = FRAME_SIZE_PER_DESC * fx; + urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC; + } + + ucpia->sbuf[1].urb->next = ucpia->sbuf[0].urb; + ucpia->sbuf[0].urb->next = ucpia->sbuf[1].urb; + + err = usb_submit_urb(ucpia->sbuf[0].urb); + if (err) + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 0 ret %d\n", + err); + err = usb_submit_urb(ucpia->sbuf[1].urb); + if (err) + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 1 ret %d\n", + err); + + ucpia->streaming = 1; + ucpia->open = 1; + + return 0; + +error_all: + kfree (ucpia->sbuf[1].data); +error_0: + kfree (ucpia->sbuf[0].data); + + return retval; +} + +// +// convenience functions +// + +/**************************************************************************** + * + * WritePacket + * + ***************************************************************************/ +static int WritePacket(struct usb_device *udev, const u8 *packet, u8 *buf, size_t size) +{ + if (!packet) + return -EINVAL; + + return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + packet[1] + (packet[0] << 8), + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + packet[2] + (packet[3] << 8), + packet[4] + (packet[5] << 8), buf, size, HZ); +} + +/**************************************************************************** + * + * ReadPacket + * + ***************************************************************************/ +static int ReadPacket(struct usb_device *udev, u8 *packet, u8 *buf, size_t size) +{ + if (!packet || size <= 0) + return -EINVAL; + + return usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + packet[1] + (packet[0] << 8), + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + packet[2] + (packet[3] << 8), + packet[4] + (packet[5] << 8), buf, size, HZ); +} + +static int cpia_usb_transferCmd(void *privdata, u8 *command, u8 *data) +{ + int err = 0; + int databytes; + struct usb_cpia *ucpia = (struct usb_cpia *)privdata; + struct usb_device *udev = ucpia->dev; + + if (!udev) { + DBG("Internal driver error: udev is NULL\n"); + return -EINVAL; + } + + if (!command) { + DBG("Internal driver error: command is NULL\n"); + return -EINVAL; + } + + databytes = (((int)command[7])<<8) | command[6]; + + if (command[0] == DATA_IN) { + u8 buffer[8]; + + if (!data) { + DBG("Internal driver error: data is NULL\n"); + return -EINVAL; + } + + err = ReadPacket(udev, command, buffer, 8); + if (err < 0) + return err; + + memcpy(data, buffer, databytes); + } else if(command[0] == DATA_OUT) + WritePacket(udev, command, data, databytes); + else { + DBG("Unexpected first byte of command: %x\n", command[0]); + err = -EINVAL; + } + + return 0; +} + +static int cpia_usb_registerCallback(void *privdata, void (*cb) (void *cbdata), + void *cbdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamStart(void *privdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamStop(void *privdata) +{ + return -ENODEV; +} + +static int cpia_usb_streamRead(void *privdata, u8 *frame, int noblock) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + struct framebuf *mybuff; + + if (!ucpia || !ucpia->present) + return -1; + + if (ucpia->curbuff->status != FRAME_READY) + interruptible_sleep_on(&ucpia->wq_stream); + else + DBG("Frame already waiting!\n"); + + mybuff = ucpia->curbuff; + + if (!mybuff) + return -1; + + if (mybuff->status != FRAME_READY || mybuff->length < 4) { + DBG("Something went wrong!\n"); + return -1; + } + + memcpy(frame, mybuff->data, mybuff->length); + mybuff->status = FRAME_EMPTY; + +/* DBG("read done, %d bytes, Header: %x/%x, Footer: %x%x%x%x\n", */ +/* mybuff->length, frame[0], frame[1], */ +/* frame[mybuff->length-4], frame[mybuff->length-3], */ +/* frame[mybuff->length-2], frame[mybuff->length-1]); */ + + return mybuff->length; +} + +static void cpia_usb_free_resources(struct usb_cpia *ucpia, int try) +{ + if (!ucpia->streaming) + return; + + ucpia->streaming = 0; + + /* Set packet size to 0 */ + if (try) { + int ret; + + ret = usb_set_interface(ucpia->dev, ucpia->iface, 0); + if (ret < 0) { + printk(KERN_ERR "usb_set_interface error (ret = %d)\n", ret); + return; + } + } + + /* Unschedule all of the iso td's */ + if (ucpia->sbuf[1].urb) { + usb_unlink_urb(ucpia->sbuf[1].urb); + usb_free_urb(ucpia->sbuf[1].urb); + ucpia->sbuf[1].urb = NULL; + } + + if (ucpia->sbuf[0].urb) { + usb_unlink_urb(ucpia->sbuf[0].urb); + usb_free_urb(ucpia->sbuf[0].urb); + ucpia->sbuf[0].urb = NULL; + } +} + +static int cpia_usb_close(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + + ucpia->open = 0; + + cpia_usb_free_resources(ucpia, 1); + + if (!ucpia->present) + kfree(ucpia); + + return 0; +} + +int cpia_usb_init(void) +{ + /* return -ENODEV; */ + return 0; +} + +/* Probing and initializing */ + +static void *cpia_probe(struct usb_device *udev, unsigned int ifnum) +{ + struct usb_interface_descriptor *interface; + struct usb_cpia *ucpia; + struct cam_data *cam; + int ret; + + /* A multi-config CPiA camera? */ + if (udev->descriptor.bNumConfigurations != 1) + return NULL; + + interface = &udev->actconfig->interface[ifnum].altsetting[0]; + + /* Is it a CPiA? */ + if (udev->descriptor.idVendor != 0x0553) + return NULL; + if (udev->descriptor.idProduct != 0x0002) + return NULL; + + /* We found a CPiA */ + printk(KERN_INFO "USB CPiA camera found\n"); + + ucpia = kmalloc(sizeof(*ucpia), GFP_KERNEL); + if (!ucpia) { + printk(KERN_ERR "couldn't kmalloc cpia struct\n"); + return NULL; + } + + memset(ucpia, 0, sizeof(*ucpia)); + + ucpia->dev = udev; + ucpia->iface = interface->bInterfaceNumber; + init_waitqueue_head(&ucpia->wq_stream); + + ucpia->buffers[0] = vmalloc(sizeof(*ucpia->buffers[0])); + if (!ucpia->buffers[0]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 0\n"); + goto fail_alloc_0; + } + + ucpia->buffers[1] = vmalloc(sizeof(*ucpia->buffers[1])); + if (!ucpia->buffers[1]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 1\n"); + goto fail_alloc_1; + } + + ucpia->buffers[2] = vmalloc(sizeof(*ucpia->buffers[2])); + if (!ucpia->buffers[2]) { + printk(KERN_ERR "couldn't vmalloc frame buffer 2\n"); + goto fail_alloc_2; + } + + ucpia->buffers[0]->next = ucpia->buffers[1]; + ucpia->buffers[1]->next = ucpia->buffers[2]; + ucpia->buffers[2]->next = ucpia->buffers[0]; + + ret = usb_set_interface(udev, ucpia->iface, 0); + if (ret < 0) { + printk(KERN_ERR "cpia_probe: usb_set_interface error (ret = %d)\n", ret); + /* goto fail_all; */ + } + + /* Before register_camera, important */ + ucpia->present = 1; + + cam = cpia_register_camera(&cpia_usb_ops, ucpia); + if (!cam) { + LOG("failed to cpia_register_camera\n"); + goto fail_all; + } + + ADD_TO_LIST(cam_list, cam); + + return cam; + +fail_all: + vfree(ucpia->buffers[2]); + ucpia->buffers[2] = NULL; +fail_alloc_2: + vfree(ucpia->buffers[1]); + ucpia->buffers[1] = NULL; +fail_alloc_1: + vfree(ucpia->buffers[0]); + ucpia->buffers[0] = NULL; +fail_alloc_0: + + return NULL; +} + +static void cpia_disconnect(struct usb_device *dev, void *ptr); + +static struct usb_driver cpia_driver = { + "cpia", + cpia_probe, + cpia_disconnect, + { NULL, NULL } +}; + +/* don't use dev, it may be NULL! (see usb_cpia_cleanup) */ +/* _disconnect from usb_cpia_cleanup is not necessary since usb_deregister */ +/* will do it for us as well as passing a udev structure - jerdfelt */ +static void cpia_disconnect(struct usb_device *udev, void *ptr) +{ + struct cam_data *cam = (struct cam_data *) ptr; + struct usb_cpia *ucpia = (struct usb_cpia *) cam->lowlevel_data; + + REMOVE_FROM_LIST(cam); + + /* Don't even try to reset the altsetting if we're disconnected */ + cpia_usb_free_resources(ucpia, 0); + + ucpia->present = 0; + + cpia_unregister_camera(cam); + + ucpia->curbuff->status = FRAME_ERROR; + + if (waitqueue_active(&ucpia->wq_stream)) + wake_up_interruptible(&ucpia->wq_stream); + + usb_driver_release_interface(&cpia_driver, + &udev->actconfig->interface[0]); + + ucpia->curbuff = ucpia->workbuff = NULL; + + if (ucpia->buffers[2]) { + vfree(ucpia->buffers[2]); + ucpia->buffers[2] = NULL; + } + + if (ucpia->buffers[1]) { + vfree(ucpia->buffers[1]); + ucpia->buffers[1] = NULL; + } + + if (ucpia->buffers[0]) { + vfree(ucpia->buffers[0]); + ucpia->buffers[0] = NULL; + } + + if (!ucpia->open) + kfree(ucpia); +} + +int usb_cpia_init(void) +{ + cam_list = NULL; + + return usb_register(&cpia_driver); +} + +void usb_cpia_cleanup(void) +{ +/* + struct cam_data *cam; + + while ((cam = cam_list) != NULL) + cpia_disconnect(NULL, cam); +*/ + + usb_deregister(&cpia_driver); +} + +#ifdef MODULE +int init_module(void) +{ + return usb_cpia_init(); +} + +void cleanup_module(void) +{ + usb_cpia_cleanup(); +} +#endif /* !MODULE */ diff --git a/drivers/media/video/cs8420.h b/drivers/media/video/cs8420.h new file mode 100644 index 000000000..2b22f3a38 --- /dev/null +++ b/drivers/media/video/cs8420.h @@ -0,0 +1,50 @@ +/* cs8420.h - cs8420 initializations + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +#ifndef __CS8420_H__ +#define __CS8420_H__ + +/* Initialization Sequence */ + +static __u8 init8420[] = { + 1, 0x01, 2, 0x02, 3, 0x00, 4, 0x46, + 5, 0x24, 6, 0x84, 18, 0x18, 19, 0x13, +}; + +#define INIT8420LEN (sizeof(init8420)/2) + +static __u8 mode8420pro[] = { /* professional output mode */ + 32, 0xa1, 33, 0x00, 34, 0x00, 35, 0x00, + 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00, + 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00, + 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00, + 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00, + 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00, +}; +#define MODE8420LEN (sizeof(mode8420pro)/2) + +static __u8 mode8420con[] = { /* consumer output mode */ + 32, 0x20, 33, 0x00, 34, 0x00, 35, 0x48, + 36, 0x00, 37, 0x00, 38, 0x00, 39, 0x00, + 40, 0x00, 41, 0x00, 42, 0x00, 43, 0x00, + 44, 0x00, 45, 0x00, 46, 0x00, 47, 0x00, + 48, 0x00, 49, 0x00, 50, 0x00, 51, 0x00, + 52, 0x00, 53, 0x00, 54, 0x00, 55, 0x00, +}; + +#endif diff --git a/drivers/media/video/i2c-old.c b/drivers/media/video/i2c-old.c new file mode 100644 index 000000000..c896057cd --- /dev/null +++ b/drivers/media/video/i2c-old.c @@ -0,0 +1,450 @@ +/* + * Generic i2c interface for linux + * + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/locks.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/i2c-old.h> + +#define REGPRINT(x) if (verbose) (x) +#define I2C_DEBUG(x) if (i2c_debug) (x) + +static int scan = 0; +static int verbose = 0; +static int i2c_debug = 0; + +#if LINUX_VERSION_CODE >= 0x020117 +MODULE_PARM(scan,"i"); +MODULE_PARM(verbose,"i"); +MODULE_PARM(i2c_debug,"i"); +#endif + +/* ----------------------------------------------------------------------- */ + +static struct i2c_bus *busses[I2C_BUS_MAX]; +static struct i2c_driver *drivers[I2C_DRIVER_MAX]; +static int bus_count = 0, driver_count = 0; + +#ifdef CONFIG_VIDEO_BUZ +extern int saa7111_init(void); +extern int saa7185_init(void); +#endif +#ifdef CONFIG_VIDEO_LML33 +extern int bt819_init(void); +extern int bt856_init(void); +#endif + +int i2c_init(void) +{ + printk(KERN_INFO "i2c: initialized%s\n", + scan ? " (i2c bus scan enabled)" : ""); + /* anything to do here ? */ +#ifdef CONFIG_VIDEO_BUZ + saa7111_init(); + saa7185_init(); +#endif +#ifdef CONFIG_VIDEO_LML33 + bt819_init(); + bt856_init(); +#endif + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static void i2c_attach_device(struct i2c_bus *bus, struct i2c_driver *driver) +{ + struct i2c_device *device; + int i,j,ack=1; + unsigned char addr; + LOCK_FLAGS; + + /* probe for device */ + LOCK_I2C_BUS(bus); + for (addr = driver->addr_l; addr <= driver->addr_h; addr += 2) + { + i2c_start(bus); + ack = i2c_sendbyte(bus,addr,0); + i2c_stop(bus); + if (!ack) + break; + } + UNLOCK_I2C_BUS(bus); + if (ack) + return; + + /* got answer */ + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (NULL == driver->devices[i]) + break; + if (I2C_DEVICE_MAX == i) + return; + + for (j = 0; j < I2C_DEVICE_MAX; j++) + if (NULL == bus->devices[j]) + break; + if (I2C_DEVICE_MAX == j) + return; + + if (NULL == (device = kmalloc(sizeof(struct i2c_device),GFP_KERNEL))) + return; + device->bus = bus; + device->driver = driver; + device->addr = addr; + + /* Attach */ + + if (driver->attach(device)!=0) + { + kfree(device); + return; + } + driver->devices[i] = device; + driver->devcount++; + bus->devices[j] = device; + bus->devcount++; + + if (bus->attach_inform) + bus->attach_inform(bus,driver->id); + REGPRINT(printk("i2c: device attached: %s (addr=0x%02x, bus=%s, driver=%s)\n",device->name,addr,bus->name,driver->name)); +} + +static void i2c_detach_device(struct i2c_device *device) +{ + int i; + + if (device->bus->detach_inform) + device->bus->detach_inform(device->bus,device->driver->id); + device->driver->detach(device); + + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (device == device->driver->devices[i]) + break; + if (I2C_DEVICE_MAX == i) + { + printk(KERN_WARNING "i2c: detach_device #1: device not found: %s\n", + device->name); + return; + } + device->driver->devices[i] = NULL; + device->driver->devcount--; + + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (device == device->bus->devices[i]) + break; + if (I2C_DEVICE_MAX == i) + { + printk(KERN_WARNING "i2c: detach_device #2: device not found: %s\n", + device->name); + return; + } + device->bus->devices[i] = NULL; + device->bus->devcount--; + + REGPRINT(printk("i2c: device detached: %s (addr=0x%02x, bus=%s, driver=%s)\n",device->name,device->addr,device->bus->name,device->driver->name)); + kfree(device); +} + +/* ----------------------------------------------------------------------- */ + +int i2c_register_bus(struct i2c_bus *bus) +{ + int i,ack; + LOCK_FLAGS; + + memset(bus->devices,0,sizeof(bus->devices)); + bus->devcount = 0; + + for (i = 0; i < I2C_BUS_MAX; i++) + if (NULL == busses[i]) + break; + if (I2C_BUS_MAX == i) + return -ENOMEM; + + busses[i] = bus; + bus_count++; + REGPRINT(printk("i2c: bus registered: %s\n",bus->name)); + + MOD_INC_USE_COUNT; + + if (scan) + { + /* scan whole i2c bus */ + LOCK_I2C_BUS(bus); + for (i = 0; i < 256; i+=2) + { + i2c_start(bus); + ack = i2c_sendbyte(bus,i,0); + i2c_stop(bus); + if (!ack) + { + printk(KERN_INFO "i2c: scanning bus %s: found device at addr=0x%02x\n", + bus->name,i); + } + } + UNLOCK_I2C_BUS(bus); + } + + /* probe available drivers */ + for (i = 0; i < I2C_DRIVER_MAX; i++) + if (drivers[i]) + i2c_attach_device(bus,drivers[i]); + return 0; +} + +int i2c_unregister_bus(struct i2c_bus *bus) +{ + int i; + + /* detach devices */ + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (bus->devices[i]) + i2c_detach_device(bus->devices[i]); + + for (i = 0; i < I2C_BUS_MAX; i++) + if (bus == busses[i]) + break; + if (I2C_BUS_MAX == i) + { + printk(KERN_WARNING "i2c: unregister_bus #1: bus not found: %s\n", + bus->name); + return -ENODEV; + } + + MOD_DEC_USE_COUNT; + + busses[i] = NULL; + bus_count--; + REGPRINT(printk("i2c: bus unregistered: %s\n",bus->name)); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +int i2c_register_driver(struct i2c_driver *driver) +{ + int i; + + memset(driver->devices,0,sizeof(driver->devices)); + driver->devcount = 0; + + for (i = 0; i < I2C_DRIVER_MAX; i++) + if (NULL == drivers[i]) + break; + if (I2C_DRIVER_MAX == i) + return -ENOMEM; + + drivers[i] = driver; + driver_count++; + + MOD_INC_USE_COUNT; + + REGPRINT(printk("i2c: driver registered: %s\n",driver->name)); + + /* Probe available busses */ + for (i = 0; i < I2C_BUS_MAX; i++) + if (busses[i]) + i2c_attach_device(busses[i],driver); + + return 0; +} + +int i2c_unregister_driver(struct i2c_driver *driver) +{ + int i; + + /* detach devices */ + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (driver->devices[i]) + i2c_detach_device(driver->devices[i]); + + for (i = 0; i < I2C_DRIVER_MAX; i++) + if (driver == drivers[i]) + break; + if (I2C_DRIVER_MAX == i) + { + printk(KERN_WARNING "i2c: unregister_driver: driver not found: %s\n", + driver->name); + return -ENODEV; + } + + MOD_DEC_USE_COUNT; + + drivers[i] = NULL; + driver_count--; + REGPRINT(printk("i2c: driver unregistered: %s\n",driver->name)); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +int i2c_control_device(struct i2c_bus *bus, int id, + unsigned int cmd, void *arg) +{ + int i; + + for (i = 0; i < I2C_DEVICE_MAX; i++) + if (bus->devices[i] && bus->devices[i]->driver->id == id) + break; + if (i == I2C_DEVICE_MAX) + return -ENODEV; + if (NULL == bus->devices[i]->driver->command) + return -ENODEV; + return bus->devices[i]->driver->command(bus->devices[i],cmd,arg); +} + +/* ----------------------------------------------------------------------- */ + +#define I2C_SET(bus,ctrl,data) (bus->i2c_setlines(bus,ctrl,data)) +#define I2C_GET(bus) (bus->i2c_getdataline(bus)) + +void i2c_start(struct i2c_bus *bus) +{ + I2C_SET(bus,0,1); + I2C_SET(bus,1,1); + I2C_SET(bus,1,0); + I2C_SET(bus,0,0); + I2C_DEBUG(printk("%s: < ",bus->name)); +} + +void i2c_stop(struct i2c_bus *bus) +{ + I2C_SET(bus,0,0); + I2C_SET(bus,1,0); + I2C_SET(bus,1,1); + I2C_DEBUG(printk(">\n")); +} + +void i2c_one(struct i2c_bus *bus) +{ + I2C_SET(bus,0,1); + I2C_SET(bus,1,1); + I2C_SET(bus,0,1); +} + +void i2c_zero(struct i2c_bus *bus) +{ + I2C_SET(bus,0,0); + I2C_SET(bus,1,0); + I2C_SET(bus,0,0); +} + +int i2c_ack(struct i2c_bus *bus) +{ + int ack; + + I2C_SET(bus,0,1); + I2C_SET(bus,1,1); + ack = I2C_GET(bus); + I2C_SET(bus,0,1); + return ack; +} + +int i2c_sendbyte(struct i2c_bus *bus,unsigned char data,int wait_for_ack) +{ + int i, ack; + + I2C_SET(bus,0,0); + for (i=7; i>=0; i--) + (data&(1<<i)) ? i2c_one(bus) : i2c_zero(bus); + if (wait_for_ack) + udelay(wait_for_ack); + ack=i2c_ack(bus); + I2C_DEBUG(printk("%02x%c ",(int)data,ack?'-':'+')); + return ack; +} + +unsigned char i2c_readbyte(struct i2c_bus *bus,int last) +{ + int i; + unsigned char data=0; + + I2C_SET(bus,0,1); + for (i=7; i>=0; i--) + { + I2C_SET(bus,1,1); + if (I2C_GET(bus)) + data |= (1<<i); + I2C_SET(bus,0,1); + } + last ? i2c_one(bus) : i2c_zero(bus); + I2C_DEBUG(printk("=%02x%c ",(int)data,last?'-':'+')); + return data; +} + +/* ----------------------------------------------------------------------- */ + +int i2c_read(struct i2c_bus *bus, unsigned char addr) +{ + int ret; + + if (bus->i2c_read) + return bus->i2c_read(bus, addr); + + i2c_start(bus); + i2c_sendbyte(bus,addr,0); + ret = i2c_readbyte(bus,1); + i2c_stop(bus); + return ret; +} + +int i2c_write(struct i2c_bus *bus, unsigned char addr, + unsigned char data1, unsigned char data2, int both) +{ + int ack; + + if (bus->i2c_write) + return bus->i2c_write(bus, addr, data1, data2, both); + + i2c_start(bus); + i2c_sendbyte(bus,addr,0); + ack = i2c_sendbyte(bus,data1,0); + if (both) + ack = i2c_sendbyte(bus,data2,0); + i2c_stop(bus); + return ack ? -1 : 0 ; +} + +/* ----------------------------------------------------------------------- */ + +#ifdef MODULE + +#if LINUX_VERSION_CODE >= 0x020100 +EXPORT_SYMBOL(i2c_register_bus); +EXPORT_SYMBOL(i2c_unregister_bus); +EXPORT_SYMBOL(i2c_register_driver); +EXPORT_SYMBOL(i2c_unregister_driver); +EXPORT_SYMBOL(i2c_control_device); +EXPORT_SYMBOL(i2c_start); +EXPORT_SYMBOL(i2c_stop); +EXPORT_SYMBOL(i2c_one); +EXPORT_SYMBOL(i2c_zero); +EXPORT_SYMBOL(i2c_ack); +EXPORT_SYMBOL(i2c_sendbyte); +EXPORT_SYMBOL(i2c_readbyte); +EXPORT_SYMBOL(i2c_read); +EXPORT_SYMBOL(i2c_write); +#endif + +int init_module(void) +{ + return i2c_init(); +} + +void cleanup_module(void) +{ +} +#endif diff --git a/drivers/media/video/i2c-parport.c b/drivers/media/video/i2c-parport.c new file mode 100644 index 000000000..00b574f60 --- /dev/null +++ b/drivers/media/video/i2c-parport.c @@ -0,0 +1,147 @@ +/* + * I2C driver for parallel port + * + * Author: Phil Blundell <philb@gnu.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This driver implements a simple I2C protocol by bit-twiddling some + * signals on the parallel port. Since the outputs on the parallel port + * aren't open collector, three lines rather than two are used: + * + * D0 clock out + * D1 data out + * BUSY data in + */ + +#include <linux/parport.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c-old.h> +#include <linux/init.h> +#include <linux/spinlock.h> + +#define I2C_DELAY 10 + +static int debug = 0; + +struct parport_i2c_bus +{ + struct i2c_bus i2c; + struct parport_i2c_bus *next; +}; + +static struct parport_i2c_bus *bus_list; + +static spinlock_t bus_list_lock = SPIN_LOCK_UNLOCKED; + +/* software I2C functions */ + +static void i2c_setlines(struct i2c_bus *bus, int clk, int data) +{ + struct parport *p = bus->data; + parport_write_data(p, (clk?1:0) | (data?2:0)); + udelay(I2C_DELAY); +} + +static int i2c_getdataline(struct i2c_bus *bus) +{ + struct parport *p = bus->data; + return (parport_read_status(p) & PARPORT_STATUS_BUSY) ? 0 : 1; +} + +static struct i2c_bus parport_i2c_bus_template = +{ + "...", + I2C_BUSID_PARPORT, + NULL, + + SPIN_LOCK_UNLOCKED, + + NULL, + NULL, + + i2c_setlines, + i2c_getdataline, + NULL, + NULL, +}; + +static void i2c_parport_attach(struct parport *port) +{ + struct parport_i2c_bus *b = kmalloc(sizeof(struct parport_i2c_bus), + GFP_KERNEL); + b->i2c = parport_i2c_bus_template; + b->i2c.data = parport_get_port (port); + strncpy(b->i2c.name, port->name, 32); + spin_lock(&bus_list_lock); + b->next = bus_list; + bus_list = b; + spin_unlock(&bus_list_lock); + i2c_register_bus(&b->i2c); + if (debug) + printk(KERN_DEBUG "i2c: attached to %s\n", port->name); +} + +static void i2c_parport_detach(struct parport *port) +{ + struct parport_i2c_bus *b, *old_b = NULL; + spin_lock(&bus_list_lock); + b = bus_list; + while (b) + { + if (b->i2c.data == port) + { + if (old_b) + old_b->next = b->next; + else + bus_list = b->next; + i2c_unregister_bus(&b->i2c); + kfree(b); + break; + } + old_b = b; + b = b->next; + } + spin_unlock(&bus_list_lock); + if (debug) + printk(KERN_DEBUG "i2c: detached from %s\n", port->name); +} + +static struct parport_driver parport_i2c_driver = +{ + "i2c", + i2c_parport_attach, + i2c_parport_detach +}; + +#ifdef MODULE +int init_module(void) +#else +int __init i2c_parport_init(void) +#endif +{ + printk("I2C: driver for parallel port v0.1 philb@gnu.org\n"); + parport_register_driver(&parport_i2c_driver); + return 0; +} + +#ifdef MODULE +MODULE_PARM(debug, "i"); + +void cleanup_module(void) +{ + struct parport_i2c_bus *b = bus_list; + while (b) + { + struct parport_i2c_bus *next = b->next; + i2c_unregister_bus(&b->i2c); + kfree(b); + b = next; + } + parport_unregister_driver(&parport_i2c_driver); +} +#endif diff --git a/drivers/media/video/ibmmpeg2.h b/drivers/media/video/ibmmpeg2.h new file mode 100644 index 000000000..68e10387c --- /dev/null +++ b/drivers/media/video/ibmmpeg2.h @@ -0,0 +1,94 @@ +/* ibmmpeg2.h - IBM MPEGCD21 definitions */ + +#ifndef __IBM_MPEG2__ +#define __IBM_MPEG2__ + +/* Define all MPEG Decoder registers */ +/* Chip Control and Status */ +#define IBM_MP2_CHIP_CONTROL 0x200*2 +#define IBM_MP2_CHIP_MODE 0x201*2 +/* Timer Control and Status */ +#define IBM_MP2_SYNC_STC2 0x202*2 +#define IBM_MP2_SYNC_STC1 0x203*2 +#define IBM_MP2_SYNC_STC0 0x204*2 +#define IBM_MP2_SYNC_PTS2 0x205*2 +#define IBM_MP2_SYNC_PTS1 0x206*2 +#define IBM_MP2_SYNC_PTS0 0x207*2 +/* Video FIFO Control */ +#define IBM_MP2_FIFO 0x208*2 +#define IBM_MP2_FIFOW 0x100*2 +#define IBM_MP2_FIFO_STAT 0x209*2 +#define IBM_MP2_RB_THRESHOLD 0x22b*2 +/* Command buffer */ +#define IBM_MP2_COMMAND 0x20a*2 +#define IBM_MP2_CMD_DATA 0x20b*2 +#define IBM_MP2_CMD_STAT 0x20c*2 +#define IBM_MP2_CMD_ADDR 0x20d*2 +/* Internal Processor Control and Status */ +#define IBM_MP2_PROC_IADDR 0x20e*2 +#define IBM_MP2_PROC_IDATA 0x20f*2 +#define IBM_MP2_WR_PROT 0x235*2 +/* DRAM Access */ +#define IBM_MP2_DRAM_ADDR 0x210*2 +#define IBM_MP2_DRAM_DATA 0x212*2 +#define IBM_MP2_DRAM_CMD_STAT 0x213*2 +#define IBM_MP2_BLOCK_SIZE 0x23b*2 +#define IBM_MP2_SRC_ADDR 0x23c*2 +/* Onscreen Display */ +#define IBM_MP2_OSD_ADDR 0x214*2 +#define IBM_MP2_OSD_DATA 0x215*2 +#define IBM_MP2_OSD_MODE 0x217*2 +#define IBM_MP2_OSD_LINK_ADDR 0x229*2 +#define IBM_MP2_OSD_SIZE 0x22a*2 +/* Interrupt Control */ +#define IBM_MP2_HOST_INT 0x218*2 +#define IBM_MP2_MASK0 0x219*2 +#define IBM_MP2_HOST_INT1 0x23e*2 +#define IBM_MP2_MASK1 0x23f*2 +/* Audio Control */ +#define IBM_MP2_AUD_IADDR 0x21a*2 +#define IBM_MP2_AUD_IDATA 0x21b*2 +#define IBM_MP2_AUD_FIFO 0x21c*2 +#define IBM_MP2_AUD_FIFOW 0x101*2 +#define IBM_MP2_AUD_CTL 0x21d*2 +#define IBM_MP2_BEEP_CTL 0x21e*2 +#define IBM_MP2_FRNT_ATTEN 0x22d*2 +/* Display Control */ +#define IBM_MP2_DISP_MODE 0x220*2 +#define IBM_MP2_DISP_DLY 0x221*2 +#define IBM_MP2_VBI_CTL 0x222*2 +#define IBM_MP2_DISP_LBOR 0x223*2 +#define IBM_MP2_DISP_TBOR 0x224*2 +/* Polarity Control */ +#define IBM_MP2_INFC_CTL 0x22c*2 + +/* control commands */ +#define IBM_MP2_PLAY 0 +#define IBM_MP2_PAUSE 1 +#define IBM_MP2_SINGLE_FRAME 2 +#define IBM_MP2_FAST_FORWARD 3 +#define IBM_MP2_SLOW_MOTION 4 +#define IBM_MP2_IMED_NORM_PLAY 5 +#define IBM_MP2_RESET_WINDOW 6 +#define IBM_MP2_FREEZE_FRAME 7 +#define IBM_MP2_RESET_VID_RATE 8 +#define IBM_MP2_CONFIG_DECODER 9 +#define IBM_MP2_CHANNEL_SWITCH 10 +#define IBM_MP2_RESET_AUD_RATE 11 +#define IBM_MP2_PRE_OP_CHN_SW 12 +#define IBM_MP2_SET_STILL_MODE 14 + +/* Define Xilinx FPGA Internal Registers */ + +/* general control register 0 */ +#define XILINX_CTL0 0x600 +/* genlock delay resister 1 */ +#define XILINX_GLDELAY 0x602 +/* send 16 bits to CS3310 port */ +#define XILINX_CS3310 0x604 +/* send 16 bits to CS3310 and complete */ +#define XILINX_CS3310_CMPLT 0x60c +/* pulse width modulator control */ +#define XILINX_PWM 0x606 + +#endif diff --git a/drivers/media/video/msp3400.c b/drivers/media/video/msp3400.c new file mode 100644 index 000000000..2d2b1e89c --- /dev/null +++ b/drivers/media/video/msp3400.c @@ -0,0 +1,1454 @@ +/* + * programming the msp34* sound processor family + * + * (c) 1997-2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + * what works and what doesn't: + * + * AM-Mono + * Support for Hauppauge cards added (decoding handled by tuner) added by + * Frederic Crozat <fcrozat@mail.dotcom.fr> + * + * FM-Mono + * should work. The stereo modes are backward compatible to FM-mono, + * therefore FM-Mono should be allways available. + * + * FM-Stereo (B/G, used in germany) + * should work, with autodetect + * + * FM-Stereo (satellite) + * should work, no autodetect (i.e. default is mono, but you can + * switch to stereo -- untested) + * + * NICAM (B/G, L , used in UK, Scandinavia, Spain and France) + * should work, with autodetect. Support for NICAM was added by + * Pekka Pietikainen <pp@netppl.fi> + * + * + * TODO: + * - better SAT support + * + * + * 980623 Thomas Sailer (sailer@ife.ee.ethz.ch) + * using soundcore instead of OSS + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <linux/init.h> + +#ifdef CONFIG_SMP +#include <asm/pgtable.h> +#include <linux/smp_lock.h> +#endif +/* kernel_thread */ +#define __KERNEL_SYSCALLS__ +#include <linux/unistd.h> + +#include "audiochip.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {0x40,0x40,I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* insmod parameters */ +static int debug = 0; /* debug output */ +static int once = 0; /* no continous stereo monitoring */ +static int amsound = 0; /* hard-wire AM sound at 6.5 Hz (france), + the autoscan seems work well only with FM... */ +static int simple = -1; /* use short programming (>= msp3410 only) */ +static int dolby = 0; + +struct msp3400c { + int simple; + int nicam; + int mode; + int norm; + int stereo; + int nicam_on; + int main, second; /* sound carrier */ + + int left, right; /* volume */ + int bass, treble; + + /* thread */ + struct task_struct *thread; + wait_queue_head_t wq; + + struct semaphore *notify; + int active,restart,rmmod; + + int watch_stereo; + struct timer_list wake_stereo; +}; + +#define MSP3400_MAX 4 +static struct i2c_client *msps[MSP3400_MAX]; + +#define VIDEO_MODE_RADIO 16 /* norm magic for radio mode */ + +/* ---------------------------------------------------------------------- */ + +#define dprintk if (debug) printk + +MODULE_PARM(once,"i"); +MODULE_PARM(debug,"i"); +MODULE_PARM(simple,"i"); +MODULE_PARM(amsound,"i"); +MODULE_PARM(dolby,"i"); + +/* ---------------------------------------------------------------------- */ + +#define I2C_MSP3400C 0x80 +#define I2C_MSP3400C_DEM 0x10 +#define I2C_MSP3400C_DFP 0x12 + +/* ----------------------------------------------------------------------- */ +/* functions for talking to the MSP3400C Sound processor */ + +static int msp3400c_reset(struct i2c_client *client) +{ + static char reset_off[3] = { 0x00, 0x80, 0x00 }; + static char reset_on[3] = { 0x00, 0x00, 0x00 }; + + i2c_master_send(client,reset_off,3); /* XXX ignore errors here */ + if (3 != i2c_master_send(client,reset_on, 3)) { + printk(KERN_ERR "msp3400: chip reset failed, penguin on i2c bus?\n"); + return -1; + } + return 0; +} + +static int +msp3400c_read(struct i2c_client *client, int dev, int addr) +{ + int err; + + unsigned char write[3]; + unsigned char read[2]; + struct i2c_msg msgs[2] = { + { client->addr, 0, 3, write }, + { client->addr, I2C_M_RD, 2, read } + }; + write[0] = dev+1; + write[1] = addr >> 8; + write[2] = addr & 0xff; + + for (err = 0; err < 3;) { + if (2 == i2c_transfer(client->adapter,msgs,2)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (read 0x%02x/0x%02x)\n", + err, dev, addr); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return read[0] << 8 | read[1]; +} + +static int +msp3400c_write(struct i2c_client *client, int dev, int addr, int val) +{ + int err; + unsigned char buffer[5]; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + + for (err = 0; err < 3;) { + if (5 == i2c_master_send(client, buffer, 5)) + break; + err++; + printk(KERN_WARNING "msp34xx: I/O error #%d (write 0x%02x/0x%02x)\n", + err, dev, addr); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + } + if (3 == err) { + printk(KERN_WARNING "msp34xx: giving up, reseting chip. Sound will go off, sorry folks :-|\n"); + msp3400c_reset(client); + return -1; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ + +/* This macro is allowed for *constants* only, gcc must calculate it + at compile time. Remember -- no floats in kernel mode */ +#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24))) + +#define MSP_MODE_AM_DETECT 0 +#define MSP_MODE_FM_RADIO 2 +#define MSP_MODE_FM_TERRA 3 +#define MSP_MODE_FM_SAT 4 +#define MSP_MODE_FM_NICAM1 5 +#define MSP_MODE_FM_NICAM2 6 +#define MSP_MODE_AM_NICAM 7 +#define MSP_MODE_BTSC 8 + +static struct MSP_INIT_DATA_DEM { + int fir1[6]; + int fir2[6]; + int cdo1; + int cdo2; + int ad_cv; + int mode_reg; + int dfp_src; + int dfp_matrix; +} msp_init_data[] = { + /* AM (for carrier detect / msp3400) */ + { { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0500, 0x0020, 0x3000}, + + /* AM (for carrier detect / msp3410) */ + { { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0100, 0x0020, 0x3000}, + + /* FM Radio */ + { { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 }, + MSP_CARRIER(10.7), MSP_CARRIER(10.7), + 0x00d0, 0x0480, 0x0020, 0x3000 }, + + /* Terrestial FM-mono + FM-stereo */ + { { 3, 18, 27, 48, 66, 72 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0480, 0x0030, 0x3000}, + + /* Sat FM-mono */ + { { 1, 9, 14, 24, 33, 37 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0480, 0x0000, 0x3000}, + + /* NICAM/FM -- B/G (5.5/5.85), D/K (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(5.5), MSP_CARRIER(5.5), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/FM -- I (6.0/6.552) */ + { { 2, 4, -6, -4, 40, 94 }, { 3, 18, 27, 48, 66, 72 }, + MSP_CARRIER(6.0), MSP_CARRIER(6.0), + 0x00d0, 0x0040, 0x0120, 0x3000}, + + /* NICAM/AM -- L (6.5/5.85) */ + { { -2, -8, -10, 10, 50, 86 }, { -4, -12, -9, 23, 79, 126 }, + MSP_CARRIER(6.5), MSP_CARRIER(6.5), + 0x00c6, 0x0140, 0x0120, 0x7c03}, +}; + +struct CARRIER_DETECT { + int cdo; + char *name; +}; + +static struct CARRIER_DETECT carrier_detect_main[] = { + /* main carrier */ + { MSP_CARRIER(4.5), "4.5 NTSC" }, + { MSP_CARRIER(5.5), "5.5 PAL B/G" }, + { MSP_CARRIER(6.0), "6.0 PAL I" }, + { MSP_CARRIER(6.5), "6.5 PAL D/K + SAT + SECAM" } +}; + +static struct CARRIER_DETECT carrier_detect_55[] = { + /* PAL B/G */ + { MSP_CARRIER(5.7421875), "5.742 PAL B/G FM-stereo" }, + { MSP_CARRIER(5.85), "5.85 PAL B/G NICAM" } +}; + +static struct CARRIER_DETECT carrier_detect_65[] = { + /* PAL SAT / SECAM */ + { MSP_CARRIER(5.85), "5.85 PAL D/K + SECAM NICAM" }, + { MSP_CARRIER(6.2578125), "6.25 PAL D/K1 FM-stereo" }, + { MSP_CARRIER(6.7421875), "6.74 PAL D/K2 FM-stereo" }, + { MSP_CARRIER(7.02), "7.02 PAL SAT FM-stereo s/b" }, + { MSP_CARRIER(7.20), "7.20 PAL SAT FM-stereo s" }, + { MSP_CARRIER(7.38), "7.38 PAL SAT FM-stereo b" }, +}; + +#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT)) + +/* ------------------------------------------------------------------------ */ + +static void msp3400c_setcarrier(struct i2c_client *client, int cdo1, int cdo2) +{ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ +} + +static void msp3400c_setvolume(struct i2c_client *client, int left, int right) +{ + int vol,val,balance; + + vol = (left > right) ? left : right; + val = (vol * 0x73 / 65535) << 8; + balance = 0; + if (vol > 0) + balance = ((right-left) * 127) / vol; + + dprintk("msp34xx: setvolume: %d:%d 0x%02x 0x%02x\n", + left,right,val>>8,balance); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones */ + /* scart - on/off only */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, val ? 0x4000 : 0); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, balance << 8); +} + +static void msp3400c_setbass(struct i2c_client *client, int bass) +{ + int val = ((bass-32768) * 0x60 / 65535) << 8; + + dprintk("msp34xx: setbass: %d 0x%02x\n",bass, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */ +} + +static void msp3400c_settreble(struct i2c_client *client, int treble) +{ + int val = ((treble-32768) * 0x60 / 65535) << 8; + + dprintk("msp34xx: settreble: %d 0x%02x\n",treble, val>>8); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */ +} + +static void msp3400c_setmode(struct i2c_client *client, int type) +{ + struct msp3400c *msp = client->data; + int i; + + dprintk("msp3400: setmode: %d\n",type); + msp->mode = type; + msp->stereo = VIDEO_SOUND_MONO; + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb, /* ad_cv */ + msp_init_data[type].ad_cv); + + for (i = 5; i >= 0; i--) /* fir 1 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001, + msp_init_data[type].fir1[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */ + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040); + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000); + for (i = 5; i >= 0; i--) + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, + msp_init_data[type].fir2[i]); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083, /* MODE_REG */ + msp_init_data[type].mode_reg); + + msp3400c_setcarrier(client, msp_init_data[type].cdo1, + msp_init_data[type].cdo2); + + msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/ + + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + 0x0520); /* I2S1 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + 0x0620); /* I2S2 */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b, + msp_init_data[type].dfp_src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009, + msp_init_data[type].dfp_src); + } + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a, + msp_init_data[type].dfp_src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, + msp_init_data[type].dfp_matrix); + + if (msp->nicam) { + /* nicam prescale */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */ + } +} + +/* turn on/off nicam + stereo */ +static void msp3400c_setstereo(struct i2c_client *client, int mode) +{ + struct msp3400c *msp = client->data; + int nicam=0; /* channel source: FM/AM or nicam */ + int src=0; + + /* switch demodulator */ + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + dprintk("msp3400: FM setstereo: %d\n",mode); + msp3400c_setcarrier(client,msp->second,msp->main); + switch (mode) { + case VIDEO_SOUND_STEREO: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001); + break; + case VIDEO_SOUND_MONO: + case VIDEO_SOUND_LANG1: + case VIDEO_SOUND_LANG2: + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000); + break; + } + break; + case MSP_MODE_FM_SAT: + dprintk("msp3400: SAT setstereo: %d\n",mode); + switch (mode) { + case VIDEO_SOUND_MONO: + msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5)); + break; + case VIDEO_SOUND_STEREO: + msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG1: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + case VIDEO_SOUND_LANG2: + msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02)); + break; + } + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + dprintk("msp3400: NICAM setstereo: %d\n",mode); + msp3400c_setcarrier(client,msp->second,msp->main); + if (msp->nicam_on) + nicam=0x0100; + break; + case MSP_MODE_BTSC: + dprintk("msp3400: BTSC setstereo: %d\n",mode); + nicam=0x0300; + break; + default: + dprintk("msp3400: mono setstereo\n"); + return; + } + + /* switch audio */ + switch (mode) { + case VIDEO_SOUND_STEREO: + src = 0x0020 | nicam; +#if 0 + /* spatial effect */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000); +#endif + break; + case VIDEO_SOUND_MONO: + if (msp->mode == MSP_MODE_AM_NICAM) { + dprintk("msp3400: switching to AM mono\n"); + /* AM mono decoding is handled by tuner, not MSP chip */ + /* so let's redirect sound from tuner via SCART */ + /* volume prescale for SCART */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900); + /* SCART switching control register*/ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, 0xe900); + src = 0x0200; + break; + } + case VIDEO_SOUND_LANG1: + src = 0x0000 | nicam; + break; + case VIDEO_SOUND_LANG2: + src = 0x0010 | nicam; + break; + } + if (dolby) { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src); + } else { + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src); + msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src); + } +} + +static void +msp3400c_print_mode(struct msp3400c *msp) +{ + if (msp->main == msp->second) { + printk("msp3400: mono sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } else { + printk("msp3400: main sound carrier: %d.%03d MHz\n", + msp->main/910000,(msp->main/910)%1000); + } + if (msp->mode == MSP_MODE_FM_NICAM1 || + msp->mode == MSP_MODE_FM_NICAM2) + printk("msp3400: NICAM/FM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_AM_NICAM) + printk("msp3400: NICAM/AM carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + if (msp->mode == MSP_MODE_FM_TERRA && + msp->main != msp->second) { + printk("msp3400: FM-stereo carrier : %d.%03d MHz\n", + msp->second/910000,(msp->second/910)%1000); + } +} + +/* ----------------------------------------------------------------------- */ + +struct REGISTER_DUMP { + int addr; + char *name; +}; + +struct REGISTER_DUMP d1[] = { + { 0x007e, "autodetect" }, + { 0x0023, "C_AD_BITS " }, + { 0x0038, "ADD_BITS " }, + { 0x003e, "CIB_BITS " }, + { 0x0057, "ERROR_RATE" }, +}; + +static int +autodetect_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = client->data; + int val; + int newstereo = msp->stereo; + int newnicam = msp->nicam_on; + int update = 0; + + switch (msp->mode) { + case MSP_MODE_FM_TERRA: + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18); + if (val > 32768) + val -= 65536; + dprintk("msp34xx: stereo detect register: %d\n",val); + + if (val > 4096) { + newstereo = VIDEO_SOUND_STEREO | VIDEO_SOUND_MONO; + } else if (val < -4096) { + newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2; + } else { + newstereo = VIDEO_SOUND_MONO; + } + newnicam = 0; + break; + case MSP_MODE_FM_NICAM1: + case MSP_MODE_FM_NICAM2: + case MSP_MODE_AM_NICAM: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23); + dprintk("msp34xx: nicam sync=%d, mode=%d\n",val & 1, (val & 0x1e) >> 1); + + if (val & 1) { + /* nicam synced */ + switch ((val & 0x1e) >> 1) { + case 0: + case 8: + newstereo = VIDEO_SOUND_STEREO; + break; + case 1: + case 9: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1; + break; + case 2: + case 10: + newstereo = VIDEO_SOUND_MONO + | VIDEO_SOUND_LANG1 + | VIDEO_SOUND_LANG2; + break; + default: + newstereo = VIDEO_SOUND_MONO; + break; + } + newnicam=1; + } else { + newnicam = 0; + newstereo = VIDEO_SOUND_MONO; + } + break; + case MSP_MODE_BTSC: + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200); + dprintk("msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n", + val, + (val & 0x0002) ? "no" : "yes", + (val & 0x0004) ? "no" : "yes", + (val & 0x0040) ? "stereo" : "mono", + (val & 0x0080) ? ", nicam 2nd mono" : "", + (val & 0x0100) ? ", bilingual/SAP" : ""); + newstereo = VIDEO_SOUND_MONO; + if (val & 0x0040) newstereo |= VIDEO_SOUND_STEREO; + if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1; + break; + } + if (newstereo != msp->stereo) { + update = 1; + dprintk("msp34xx: watch: stereo %d => %d\n", + msp->stereo,newstereo); + msp->stereo = newstereo; + } + if (newnicam != msp->nicam_on) { + update = 1; + dprintk("msp34xx: watch: nicam %d => %d\n", + msp->nicam_on,newnicam); + msp->nicam_on = newnicam; + } + return update; +} + +/* + * A kernel thread for msp3400 control -- we don't want to block the + * in the ioctl while doing the sound carrier & stereo detect + */ + +static void msp3400c_stereo_wake(unsigned long data) +{ + struct msp3400c *msp = (struct msp3400c*)data; /* XXX alpha ??? */ + + wake_up_interruptible(&msp->wq); +} + +/* stereo/multilang monitoring */ +static void watch_stereo(struct i2c_client *client) +{ + struct msp3400c *msp = client->data; + + if (autodetect_stereo(client)) { + if (msp->stereo & VIDEO_SOUND_STEREO) + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + else if (msp->stereo & VIDEO_SOUND_LANG1) + msp3400c_setstereo(client,VIDEO_SOUND_LANG1); + else + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + } + if (once) + msp->watch_stereo = 0; + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); +} + +static int msp3400c_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = client->data; + + struct CARRIER_DETECT *cd; + int count, max1,max2,val1,val2, val,this; + +#ifdef CONFIG_SMP + lock_kernel(); +#endif + + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3400"); + + msp->thread = current; + +#ifdef CONFIG_SMP + unlock_kernel(); +#endif + + printk("msp3400: daemon started\n"); + if(msp->notify != NULL) + up(msp->notify); + + for (;;) { + if (msp->rmmod) + goto done; + if (debug > 1) + printk("msp3400: thread: sleep\n"); + interruptible_sleep_on(&msp->wq); + if (debug > 1) + printk("msp3400: thread: wakeup\n"); + if (msp->rmmod || signal_pending(current)) + goto done; + + if (VIDEO_MODE_RADIO == msp->norm) + continue; /* nothing to do */ + + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/5); + if (signal_pending(current)) + goto done; + + restart: + msp->restart = 0; + msp3400c_setvolume(client, 0, 0); + msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ ); + val1 = val2 = 0; + max1 = max2 = -1; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* carrier detect pass #1 -- main carrier */ + cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main); + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + max1 = 3; + count = 0; + dprintk("msp3400: AM sound override\n"); + } + + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + if (signal_pending(current)) + goto done; + if (msp->restart) + msp->restart = 0; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32768) + val -= 65536; + if (val1 < val) + val1 = val, max1 = this; + dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name); + } + + /* carrier detect pass #2 -- second (stereo) carrier */ + switch (max1) { + case 1: /* 5.5 */ + cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55); + break; + case 3: /* 6.5 */ + cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65); + break; + case 0: /* 4.5 */ + case 2: /* 6.0 */ + default: + cd = NULL; count = 0; + break; + } + + if (amsound && (msp->norm == VIDEO_MODE_SECAM)) { + /* autodetect doesn't work well with AM ... */ + cd = NULL; count = 0; max2 = 0; + } + for (this = 0; this < count; this++) { + msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo); + + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + if (signal_pending(current)) + goto done; + if (msp->restart) + goto restart; + + val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b); + if (val > 32768) + val -= 65536; + if (val2 < val) + val2 = val, max2 = this; + dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name); + } + + /* programm the msp3400 according to the results */ + msp->main = carrier_detect_main[max1].cdo; + switch (max1) { + case 1: /* 5.5 */ + if (max2 == 0) { + /* B/G FM-stereo */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 1 && msp->nicam) { + /* B/G NICAM */ + msp->second = carrier_detect_55[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 2: /* 6.0 */ + /* PAL I NICAM */ + msp->second = MSP_CARRIER(6.552); + msp3400c_setmode(client, MSP_MODE_FM_NICAM2); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + break; + case 3: /* 6.5 */ + if (max2 == 1 || max2 == 2) { + /* D/K FM-stereo */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + } else if (max2 == 0 && + msp->norm == VIDEO_MODE_SECAM) { + /* L NICAM or AM-mono */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_AM_NICAM); + msp->nicam_on = 0; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else if (max2 == 0 && msp->nicam) { + /* D/K NICAM */ + msp->second = carrier_detect_65[max2].cdo; + msp3400c_setmode(client, MSP_MODE_FM_NICAM1); + msp->nicam_on = 1; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->watch_stereo = 1; + } else { + goto no_second; + } + break; + case 0: /* 4.5 */ + default: + no_second: + msp->second = carrier_detect_main[max1].cdo; + msp3400c_setmode(client, MSP_MODE_FM_TERRA); + msp->nicam_on = 0; + msp3400c_setcarrier(client, msp->second, msp->main); + msp->stereo = VIDEO_SOUND_MONO; + msp3400c_setstereo(client, VIDEO_SOUND_MONO); + break; + } + + /* unmute */ + msp3400c_setvolume(client, msp->left, msp->right); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+5*HZ); + + if (debug) + msp3400c_print_mode(msp); + + msp->active = 0; + } + +done: + dprintk("msp3400: thread: exit\n"); + msp->active = 0; + msp->thread = NULL; + + if(msp->notify != NULL) + up(msp->notify); + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* this one uses the automatic sound standard detection of newer */ +/* msp34xx chip versions */ + +static struct MODES { + int retval; + int main, second; + char *name; +} modelist[] = { + { 0x0000, 0, 0, "ERROR" }, + { 0x0001, 0, 0, "autodetect start" }, + { 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72 M Dual FM-Stereo" }, + { 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74 B/G Dual FM-Stereo" }, + { 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25 D/K1 Dual FM-Stereo" }, + { 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74 D/K2 Dual FM-Stereo" }, + { 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 D/K FM-Mono (HDEV3)" }, + { 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85 B/G NICAM FM" }, + { 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 L NICAM AM" }, + { 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55 I NICAM FM" }, + { 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM" }, + { 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85 D/K NICAM FM (HDEV2)" }, + { 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Stereo" }, + { 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M BTSC-Mono + SAP" }, + { 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5 M EIA-J Japan Stereo" }, + { 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7 FM-Stereo Radio" }, + { 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5 SAT-Mono" }, + { 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20 SAT-Stereo" }, + { 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2 SAT ADR" }, + { -1, 0, 0, NULL }, /* EOF */ +}; + +static int msp3410d_thread(void *data) +{ + struct i2c_client *client = data; + struct msp3400c *msp = client->data; + int mode,val,i,std; + +#ifdef CONFIG_SMP + lock_kernel(); +#endif + + daemonize(); + sigfillset(¤t->blocked); + strcpy(current->comm,"msp3410 [auto]"); + + msp->thread = current; + +#ifdef CONFIG_SMP + unlock_kernel(); +#endif + + printk("msp3410: daemon started\n"); + if(msp->notify != NULL) + up(msp->notify); + + for (;;) { + if (msp->rmmod) + goto done; + if (debug > 1) + printk("msp3410: thread: sleep\n"); + interruptible_sleep_on(&msp->wq); + if (debug > 1) + printk("msp3410: thread: wakeup\n"); + if (msp->rmmod || signal_pending(current)) + goto done; + + if (VIDEO_MODE_RADIO == msp->norm) + continue; /* nothing to do */ + + msp->active = 1; + + if (msp->watch_stereo) { + watch_stereo(client); + msp->active = 0; + continue; + } + + /* some time for the tuner to sync */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/5); + if (signal_pending(current)) + goto done; + + restart: + msp->restart = 0; + del_timer(&msp->wake_stereo); + msp->watch_stereo = 0; + + /* put into sane state (and mute) */ + msp3400c_reset(client); + + /* start autodetect */ + switch (msp->norm) { + case VIDEO_MODE_PAL: + mode = 0x1003; + std = 1; + break; + case VIDEO_MODE_NTSC: /* BTSC */ + mode = 0x2003; + std = 0x0020; + break; + case VIDEO_MODE_SECAM: + mode = 0x0003; + std = 1; + break; + default: + mode = 0x0003; + std = 1; + break; + } + msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std); + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); + msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); + if (debug) { + int i; + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == std) + break; + printk("msp3410: setting mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown",std); + } + + if (std != 1) { + /* programmed some specific mode */ + val = std; + } else { + /* triggered autodetect */ + for (;;) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + if (signal_pending(current)) + goto done; + if (msp->restart) + goto restart; + + /* check results */ + val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e); + if (val < 0x07ff) + break; + dprintk("msp3410: detection still in progress\n"); + } + } + for (i = 0; modelist[i].name != NULL; i++) + if (modelist[i].retval == val) + break; + dprintk("msp3410: current mode: %s (0x%04x)\n", + modelist[i].name ? modelist[i].name : "unknown", + val); + msp->main = modelist[i].main; + msp->second = modelist[i].second; + + if (amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) { + /* autodetection has failed, let backup */ + dprintk("msp3410: autodetection failed, switching to backup mode: %s (0x%04x)\n", + modelist[8].name ? modelist[8].name : "unknown",val); + val = 0x0009; + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val); + } + + /* set prescale / stereo */ + switch (val) { + case 0x0009: + msp->mode = MSP_MODE_AM_NICAM; + msp->stereo = VIDEO_SOUND_MONO; + msp3400c_setstereo(client,VIDEO_SOUND_MONO); + msp->watch_stereo = 1; + break; + case 0x0020: /* BTSC */ + /* just turn on stereo */ + msp->mode = MSP_MODE_BTSC; + msp->stereo = VIDEO_SOUND_STEREO; + msp->watch_stereo = 1; + msp3400c_setstereo(client,VIDEO_SOUND_STEREO); + /* set prescale */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */ + break; + case 0x0003: + msp->mode = MSP_MODE_FM_TERRA; + msp->stereo = VIDEO_SOUND_MONO; + msp->watch_stereo = 1; + /* fall */ + default: + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* NICAM */ + break; + } + + /* unmute */ + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->left, msp->right); + + if (msp->watch_stereo) + mod_timer(&msp->wake_stereo, jiffies+HZ); + + msp->active = 0; + } + +done: + dprintk("msp3410: thread: exit\n"); + msp->active = 0; + msp->thread = NULL; + + if(msp->notify != NULL) + up(msp->notify); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind); +static int msp_detach(struct i2c_client *client); +static int msp_probe(struct i2c_adapter *adap); +static int msp_command(struct i2c_client *client, unsigned int cmd, void *arg); + +static struct i2c_driver driver = { + "i2c msp3400 driver", + I2C_DRIVERID_MSP3400, + I2C_DF_NOTIFY, + msp_probe, + msp_detach, + msp_command, +}; + +static struct i2c_client client_template = +{ + "unset", + -1, + 0, + 0, + NULL, + &driver +}; + +static int msp_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + DECLARE_MUTEX_LOCKED(sem); + struct msp3400c *msp; + struct i2c_client *c; + int rev1,rev2,i; + + client_template.adapter = adap; + client_template.addr = addr; + + if (-1 == msp3400c_reset(&client_template)) { + dprintk("msp3400: no chip found\n"); + return -1; + } + + if (NULL == (c = kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) + return -ENOMEM; + memcpy(c,&client_template,sizeof(struct i2c_client)); + if (NULL == (msp = kmalloc(sizeof(struct msp3400c),GFP_KERNEL))) { + kfree(c); + return -ENOMEM; + } + + memset(msp,0,sizeof(struct msp3400c)); + msp->left = 65535; + msp->right = 65535; + msp->bass = 32768; + msp->treble = 32768; + c->data = msp; + init_waitqueue_head(&msp->wq); + + if (-1 == msp3400c_reset(c)) { + kfree(msp); + dprintk("msp3400: no chip found\n"); + return -1; + } + + rev1 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1e); + rev2 = msp3400c_read(c, I2C_MSP3400C_DFP, 0x1f); + if (0 == rev1 && 0 == rev2) { + kfree(msp); + printk("msp3400: error while reading chip version\n"); + return -1; + } + +#if 0 + /* this will turn on a 1kHz beep - might be useful for debugging... */ + msp3400c_write(client,I2C_MSP3400C_DFP, 0x0014, 0x1040); +#endif + + sprintf(c->name,"MSP34%02d%c-%c%d", + (rev2>>8)&0xff, (rev1&0xff)+'@', ((rev1>>8)&0xff)+'@', rev2&0x1f); + msp->nicam = (((rev2>>8)&0xff) != 00) ? 1 : 0; + + if (simple == -1) { + /* default mode */ + /* msp->simple = (((rev2>>8)&0xff) == 0) ? 0 : 1; */ + msp->simple = ((rev1&0xff)+'@' > 'C'); + } else { + /* use insmod option */ + msp->simple = simple; + } + + /* timer for stereo checking */ + msp->wake_stereo.function = msp3400c_stereo_wake; + msp->wake_stereo.data = (unsigned long)msp; + + /* hello world :-) */ + printk(KERN_INFO "msp3400: init: chip=%s",c->name); + if (msp->nicam) + printk(", has NICAM support"); + printk("\n"); + + /* startup control thread */ + MOD_INC_USE_COUNT; + msp->notify = &sem; + kernel_thread(msp->simple ? msp3410d_thread : msp3400c_thread, + (void *)c, 0); + down(&sem); + msp->notify = NULL; + wake_up_interruptible(&msp->wq); + +#ifdef REGISTER_MIXER + if ((msp->mixer_num = register_sound_mixer(&msp3400c_mixer_fops,mixer)) < 0) + printk(KERN_ERR "msp3400c: cannot allocate mixer device\n"); +#endif + + /* update our own array */ + for (i = 0; i < MSP3400_MAX; i++) { + if (NULL == msps[i]) { + msps[i] = c; + break; + } + } + + /* done */ + i2c_attach_client(c); + return 0; +} + +static int msp_detach(struct i2c_client *client) +{ + DECLARE_MUTEX_LOCKED(sem); + struct msp3400c *msp = (struct msp3400c*)client->data; + int i; + +#ifdef REGISTER_MIXER + if (msp->mixer_num >= 0) + unregister_sound_mixer(msp->mixer_num); +#endif + + /* shutdown control thread */ + del_timer(&msp->wake_stereo); + if (msp->thread) + { + msp->notify = &sem; + msp->rmmod = 1; + wake_up_interruptible(&msp->wq); + down(&sem); + msp->notify = NULL; + } + msp3400c_reset(client); + + /* update our own array */ + for (i = 0; i < MSP3400_MAX; i++) { + if (client == msps[i]) { + msps[i] = NULL; + break; + } + } + + i2c_detach_client(client); + kfree(msp); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int msp_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, msp_attach); + return 0; +} + +static int msp_command(struct i2c_client *client,unsigned int cmd, void *arg) +{ + struct msp3400c *msp = (struct msp3400c*)client->data; +#if 0 + int *iarg = (int*)arg; + __u16 *sarg = arg; +#endif + + switch (cmd) { + + case AUDC_SET_RADIO: + msp->norm = VIDEO_MODE_RADIO; + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + if (msp->simple) { + msp3400c_reset(client); + msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, 0x0003); /* automatic */ + msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, 0x0040); /* FM Radio */ + msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM prescale */ + msp3400c_setbass(client, msp->bass); + msp3400c_settreble(client, msp->treble); + msp3400c_setvolume(client, msp->left, msp->right); + } else { + msp3400c_setmode(client,MSP_MODE_FM_RADIO); + msp3400c_setcarrier(client, MSP_CARRIER(10.7),MSP_CARRIER(10.7)); + msp3400c_setvolume(client,msp->left, msp->right); + } + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + va->volume=MAX(msp->left,msp->right); + va->balance=(32768*MIN(msp->left,msp->right))/ + (va->volume ? va->volume : 1); + va->balance=(msp->left<msp->right)? + (65535-va->balance) : va->balance; + va->bass = msp->bass; + va->treble = msp->treble; + + autodetect_stereo(client); + va->mode = msp->stereo; + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + msp->left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + msp->right = (MIN(va->balance,32768) * + va->volume) / 32768; + msp->bass = va->bass; + msp->treble = va->treble; + msp3400c_setvolume(client,msp->left, msp->right); + msp3400c_setbass(client,msp->bass); + msp3400c_settreble(client,msp->treble); + + if (va->mode != 0) { + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + msp->stereo = va->mode; + msp3400c_setstereo(client,va->mode); + } + break; + } + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + msp->norm = vc->norm; + break; + } + case VIDIOCSFREQ: + { + /* new channel -- kick audio carrier scan */ + msp3400c_setvolume(client,0,0); + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + if (msp->active) + msp->restart = 1; + wake_up_interruptible(&msp->wq); + break; + } + + /* --- v4l2 ioctls --- */ + /* NOT YET */ + +#if 0 + /* --- old, obsolete interface --- */ + case AUDC_SET_TVNORM: + msp->norm = *iarg; + break; + case AUDC_SWITCH_MUTE: + /* channels switching step one -- mute */ + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + msp3400c_setvolume(client,0,0); + break; + case AUDC_NEWCHANNEL: + /* channels switching step two -- trigger sound carrier scan */ + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + if (msp->active) + msp->restart = 1; + wake_up_interruptible(&msp->wq); + break; + + case AUDC_GET_VOLUME_LEFT: + *sarg = msp->left; + break; + case AUDC_GET_VOLUME_RIGHT: + *sarg = msp->right; + break; + case AUDC_SET_VOLUME_LEFT: + msp->left = *sarg; + msp3400c_setvolume(client,msp->left, msp->right); + break; + case AUDC_SET_VOLUME_RIGHT: + msp->right = *sarg; + msp3400c_setvolume(client,msp->left, msp->right); + break; + + case AUDC_GET_BASS: + *sarg = msp->bass; + break; + case AUDC_SET_BASS: + msp->bass = *sarg; + msp3400c_setbass(client,msp->bass); + break; + + case AUDC_GET_TREBLE: + *sarg = msp->treble; + break; + case AUDC_SET_TREBLE: + msp->treble = *sarg; + msp3400c_settreble(client,msp->treble); + break; + + case AUDC_GET_STEREO: + autodetect_stereo(client); + *sarg = msp->stereo; + break; + case AUDC_SET_STEREO: + if (*sarg) { + msp->watch_stereo=0; + del_timer(&msp->wake_stereo); + msp->stereo = *sarg; + msp3400c_setstereo(client,*sarg); + } + break; + + case AUDC_GET_DC: + if (msp->simple) + break; /* fixme */ + *sarg = ((int)msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b) + + (int)msp3400c_read(client, I2C_MSP3400C_DFP, 0x1c)); + break; +#endif + default: + /* nothing */ + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +int msp3400_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +void msp3400_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(msp3400_init_module); +module_exit(msp3400_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/planb.c b/drivers/media/video/planb.c new file mode 100644 index 000000000..94707619d --- /dev/null +++ b/drivers/media/video/planb.c @@ -0,0 +1,2341 @@ +/* + planb - PlanB frame grabber driver + + PlanB is used in the 7x00/8x00 series of PowerMacintosh + Computers as video input DMA controller. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de) + + Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* $Id: planb.c,v 1.18 1999/05/02 17:36:34 mlan Exp $ */ + +#include <linux/version.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/wrapper.h> +#include <linux/tqueue.h> +#include <linux/videodev.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/prom.h> +#include <asm/dbdma.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/irq.h> + +#include "planb.h" +#include "saa7196.h" + + +/* Would you mind for some ugly debugging? */ +//#define DEBUG(x...) printk(KERN_DEBUG ## x) /* Debug driver */ +#define DEBUG(x...) /* Don't debug driver */ +//#define IDEBUG(x...) printk(KERN_DEBUG ## x) /* Debug interrupt part */ +#define IDEBUG(x...) /* Don't debug interrupt part */ + +/* Ever seen a Mac with more than 1 of these? */ +#define PLANB_MAX 1 + +static int planb_num; +static struct planb planbs[PLANB_MAX]; +static volatile struct planb_registers *planb_regs; + +static int def_norm = PLANB_DEF_NORM; /* default norm */ + +MODULE_PARM(def_norm, "i"); +MODULE_PARM_DESC(def_norm, "Default startup norm (0=PAL, 1=NTSC, 2=SECAM)"); + +/* ------------------ PlanB Exported Functions ------------------ */ +static long planb_write(struct video_device *, const char *, unsigned long, int); +static long planb_read(struct video_device *, char *, unsigned long, int); +static int planb_open(struct video_device *, int); +static void planb_close(struct video_device *); +static int planb_ioctl(struct video_device *, unsigned int, void *); +static int planb_init_done(struct video_device *); +static int planb_mmap(struct video_device *, const char *, unsigned long); +static void planb_irq(int, void *, struct pt_regs *); +static void release_planb(void); +int init_planbs(struct video_init *); + +/* ------------------ PlanB Internal Functions ------------------ */ +static int planb_prepare_open(struct planb *); +static void planb_prepare_close(struct planb *); +static void saa_write_reg(unsigned char, unsigned char); +static unsigned char saa_status(int, struct planb *); +static void saa_set(unsigned char, unsigned char, struct planb *); +static void saa_init_regs(struct planb *); +static int grabbuf_alloc(struct planb *); +static int vgrab(struct planb *, struct video_mmap *); +static void add_clip(struct planb *, struct video_clip *); +static void fill_cmd_buff(struct planb *); +static void cmd_buff(struct planb *); +static volatile struct dbdma_cmd *setup_grab_cmd(int, struct planb *); +static void overlay_start(struct planb *); +static void overlay_stop(struct planb *); +static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *, unsigned short, + unsigned int); +static inline void tab_cmd_store(volatile struct dbdma_cmd *, unsigned int, + unsigned int); +static inline void tab_cmd_gen(volatile struct dbdma_cmd *, unsigned short, + unsigned short, unsigned int, unsigned int); +static int init_planb(struct planb *); +static int find_planb(void); +static void planb_pre_capture(int, int, struct planb *); +static volatile struct dbdma_cmd *cmd_geo_setup(volatile struct dbdma_cmd *, + int, int, int, int, int, struct planb *); +static inline void planb_dbdma_stop(volatile struct dbdma_regs *); +static unsigned int saa_geo_setup(int, int, int, int, struct planb *); +static inline int overlay_is_active(struct planb *); + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +static int grabbuf_alloc(struct planb *pb) +{ + int i, npage; + + npage = MAX_GBUFFERS * ((PLANB_MAX_FBUF / PAGE_SIZE + 1) +#ifndef PLANB_GSCANLINE + + MAX_LNUM +#endif /* PLANB_GSCANLINE */ + ); + if ((pb->rawbuf = (unsigned char**) kmalloc (npage + * sizeof(unsigned long), GFP_KERNEL)) == 0) + return -ENOMEM; + for (i = 0; i < npage; i++) { + pb->rawbuf[i] = (unsigned char *)__get_free_pages(GFP_KERNEL + |GFP_DMA, 0); + if (!pb->rawbuf[i]) + break; + mem_map_reserve(virt_to_page(pb->rawbuf[i])); + } + if (i-- < npage) { + printk(KERN_DEBUG "PlanB: init_grab: grab buffer not allocated\n"); + for (; i > 0; i--) { + mem_map_unreserve(virt_to_page(pb->rawbuf[i])); + free_pages((unsigned long)pb->rawbuf[i], 0); + } + kfree(pb->rawbuf); + return -ENOBUFS; + } + pb->rawbuf_size = npage; + return 0; +} + +/*****************************/ +/* Hardware access functions */ +/*****************************/ + +static void saa_write_reg(unsigned char addr, unsigned char val) +{ + planb_regs->saa_addr = addr; eieio(); + planb_regs->saa_regval = val; eieio(); + return; +} + +/* return status byte 0 or 1: */ +static unsigned char saa_status(int byte, struct planb *pb) +{ + saa_regs[pb->win.norm][SAA7196_STDC] = + (saa_regs[pb->win.norm][SAA7196_STDC] & ~2) | ((byte & 1) << 1); + saa_write_reg (SAA7196_STDC, saa_regs[pb->win.norm][SAA7196_STDC]); + + /* Let's wait 30msec for this one */ + current->state = TASK_INTERRUPTIBLE; +#if LINUX_VERSION_CODE >= 0x02017F + schedule_timeout(30 * HZ / 1000); +#else + current->timeout = jiffies + 30 * HZ / 1000; /* 30 ms */; + schedule(); +#endif + + return (unsigned char)in_8 (&planb_regs->saa_status); +} + +static void saa_set(unsigned char addr, unsigned char val, struct planb *pb) +{ + if(saa_regs[pb->win.norm][addr] != val) { + saa_regs[pb->win.norm][addr] = val; + saa_write_reg (addr, val); + } + return; +} + +static void saa_init_regs(struct planb *pb) +{ + int i; + + for (i = 0; i < SAA7196_NUMREGS; i++) + saa_write_reg (i, saa_regs[pb->win.norm][i]); +} + +static unsigned int saa_geo_setup(int width, int height, int interlace, int bpp, + struct planb *pb) +{ + int ht, norm = pb->win.norm; + + switch(bpp) { + case 2: + /* RGB555+a 1x16-bit + 16-bit transparent */ + saa_regs[norm][SAA7196_FMTS] &= ~0x3; + break; + case 1: + case 4: + /* RGB888 1x24-bit + 8-bit transparent */ + saa_regs[norm][SAA7196_FMTS] &= ~0x1; + saa_regs[norm][SAA7196_FMTS] |= 0x2; + break; + default: + return -EINVAL; + } + ht = (interlace ? height / 2 : height); + saa_regs[norm][SAA7196_OUTPIX] = (unsigned char) (width & 0x00ff); + saa_regs[norm][SAA7196_HFILT] = (saa_regs[norm][SAA7196_HFILT] & ~0x3) + | (width >> 8 & 0x3); + saa_regs[norm][SAA7196_OUTLINE] = (unsigned char) (ht & 0xff); + saa_regs[norm][SAA7196_VYP] = (saa_regs[norm][SAA7196_VYP] & ~0x3) + | (ht >> 8 & 0x3); + /* feed both fields if interlaced, or else feed only even fields */ + saa_regs[norm][SAA7196_FMTS] = (interlace) ? + (saa_regs[norm][SAA7196_FMTS] & ~0x60) + : (saa_regs[norm][SAA7196_FMTS] | 0x60); + /* transparent mode; extended format enabled */ + saa_regs[norm][SAA7196_DPATH] |= 0x3; + + return 0; +} + +/***************************/ +/* DBDMA support functions */ +/***************************/ + +static inline void planb_dbdma_restart(volatile struct dbdma_regs *ch) +{ + out_le32(&ch->control, PLANB_CLR(RUN)); + out_le32(&ch->control, PLANB_SET(RUN|WAKE) | PLANB_CLR(PAUSE)); +} + +static inline void planb_dbdma_stop(volatile struct dbdma_regs *ch) +{ + int i = 0; + + out_le32(&ch->control, PLANB_CLR(RUN) | PLANB_SET(FLUSH)); + while((in_le32(&ch->status) == (ACTIVE | FLUSH)) && (i < 999)) { + IDEBUG("PlanB: waiting for DMA to stop\n"); + i++; + } +} + +static inline void tab_cmd_dbdma(volatile struct dbdma_cmd *ch, + unsigned short command, unsigned int cmd_dep) +{ + st_le16(&ch->command, command); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static inline void tab_cmd_store(volatile struct dbdma_cmd *ch, + unsigned int phy_addr, unsigned int cmd_dep) +{ + st_le16(&ch->command, STORE_WORD | KEY_SYSTEM); + st_le16(&ch->req_count, 4); + st_le32(&ch->phy_addr, phy_addr); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static inline void tab_cmd_gen(volatile struct dbdma_cmd *ch, + unsigned short command, unsigned short req_count, + unsigned int phy_addr, unsigned int cmd_dep) +{ + st_le16(&ch->command, command); + st_le16(&ch->req_count, req_count); + st_le32(&ch->phy_addr, phy_addr); + st_le32(&ch->cmd_dep, cmd_dep); +} + +static volatile struct dbdma_cmd *cmd_geo_setup( + volatile struct dbdma_cmd *c1, int width, int height, int interlace, + int bpp, int clip, struct planb *pb) +{ + int norm = pb->win.norm; + + if((saa_geo_setup(width, height, interlace, bpp, pb)) != 0) + return (volatile struct dbdma_cmd *)NULL; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_FMTS); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_FMTS]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_DPATH); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_DPATH]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->even), + bpp | ((clip)? PLANB_CLIPMASK: 0)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->odd), + bpp | ((clip)? PLANB_CLIPMASK: 0)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_OUTPIX); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_OUTPIX]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_HFILT); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_HFILT]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_OUTLINE); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_OUTLINE]); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_addr), + SAA7196_VYP); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->saa_regval), + saa_regs[norm][SAA7196_VYP]); + return c1; +} + +/******************************/ +/* misc. supporting functions */ +/******************************/ + +static void __planb_wait(struct planb *pb) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&pb->lockq, &wait); +repeat: + set_current_state(TASK_UNINTERRUPTIBLE); + if (pb->lock) { + schedule(); + goto repeat; + } + remove_wait_queue(&pb->lockq, &wait); + current->state = TASK_RUNNING; +} + +static inline void planb_wait(struct planb *pb) +{ + DEBUG("PlanB: planb_wait\n"); + if(pb->lock) + __planb_wait(pb); +} + +static inline void planb_lock(struct planb *pb) +{ + DEBUG("PlanB: planb_lock\n"); + if(pb->lock) + __planb_wait(pb); + pb->lock = 1; +} + +static inline void planb_unlock(struct planb *pb) +{ + DEBUG("PlanB: planb_unlock\n"); + pb->lock = 0; + wake_up(&pb->lockq); +} + +/***************/ +/* Driver Core */ +/***************/ + +static int planb_prepare_open(struct planb *pb) +{ + int i, size; + + /* allocate memory for two plus alpha command buffers (size: max lines, + plus 40 commands handling, plus 1 alignment), plus dummy command buf, + plus clipmask buffer, plus frame grabbing status */ + size = (pb->tab_size*(2+MAX_GBUFFERS*TAB_FACTOR)+1+MAX_GBUFFERS + * PLANB_DUMMY)*sizeof(struct dbdma_cmd) + +(PLANB_MAXLINES*((PLANB_MAXPIXELS+7)& ~7))/8 + +MAX_GBUFFERS*sizeof(unsigned int); + if ((pb->priv_space = kmalloc (size, GFP_KERNEL)) == 0) + return -ENOMEM; + memset ((void *) pb->priv_space, 0, size); + pb->overlay_last1 = pb->ch1_cmd = (volatile struct dbdma_cmd *) + DBDMA_ALIGN (pb->priv_space); + pb->overlay_last2 = pb->ch2_cmd = pb->ch1_cmd + pb->tab_size; + pb->ch1_cmd_phys = virt_to_bus(pb->ch1_cmd); + pb->cap_cmd[0] = pb->ch2_cmd + pb->tab_size; + pb->pre_cmd[0] = pb->cap_cmd[0] + pb->tab_size * TAB_FACTOR; + for (i = 1; i < MAX_GBUFFERS; i++) { + pb->cap_cmd[i] = pb->pre_cmd[i-1] + PLANB_DUMMY; + pb->pre_cmd[i] = pb->cap_cmd[i] + pb->tab_size * TAB_FACTOR; + } + pb->frame_stat=(volatile unsigned int *)(pb->pre_cmd[MAX_GBUFFERS-1] + + PLANB_DUMMY); + pb->mask = (unsigned char *)(pb->frame_stat+MAX_GBUFFERS); + + pb->rawbuf = NULL; + pb->rawbuf_size = 0; + pb->grabbing = 0; + for (i = 0; i < MAX_GBUFFERS; i++) { + pb->frame_stat[i] = GBUFFER_UNUSED; + pb->gwidth[i] = 0; + pb->gheight[i] = 0; + pb->gfmt[i] = 0; + pb->gnorm_switch[i] = 0; +#ifndef PLANB_GSCANLINE + pb->lsize[i] = 0; + pb->lnum[i] = 0; +#endif /* PLANB_GSCANLINE */ + } + pb->gcount = 0; + pb->suspend = 0; + pb->last_fr = -999; + pb->prev_last_fr = -999; + + /* Reset DMA controllers */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + return 0; +} + +static void planb_prepare_close(struct planb *pb) +{ + int i; + + /* make sure the dma's are idle */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + /* free kernel memory of command buffers */ + if(pb->priv_space != 0) { + kfree (pb->priv_space); + pb->priv_space = 0; + pb->cmd_buff_inited = 0; + } + if(pb->rawbuf) { + for (i = 0; i < pb->rawbuf_size; i++) { + mem_map_unreserve(virt_to_page(pb->rawbuf[i])); + free_pages((unsigned long)pb->rawbuf[i], 0); + } + kfree(pb->rawbuf); + } + pb->rawbuf = NULL; +} + +/*****************************/ +/* overlay support functions */ +/*****************************/ + +static void overlay_start(struct planb *pb) +{ + + DEBUG("PlanB: overlay_start()\n"); + + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + + DEBUG("PlanB: presumably, grabbing is in progress...\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + out_le32 (&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->ch2_cmd)); + planb_dbdma_restart(&pb->planb_base->ch2); + st_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + eieio(); + pb->prev_last_fr = pb->last_fr; + pb->last_fr = -2; + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + IDEBUG("PlanB: became inactive " + "in the mean time... reactivating\n"); + planb_dbdma_stop(&pb->planb_base->ch1); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->ch1_cmd)); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } else { + + DEBUG("PlanB: currently idle, so can do whatever\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + st_le32 (&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->ch2_cmd)); + st_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->ch1_cmd)); + out_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + planb_dbdma_restart(&pb->planb_base->ch2); + planb_dbdma_restart(&pb->planb_base->ch1); + pb->last_fr = -1; + } + return; +} + +static void overlay_stop(struct planb *pb) +{ + DEBUG("PlanB: overlay_stop()\n"); + + if(pb->last_fr == -1) { + + DEBUG("PlanB: no grabbing, it seems...\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->last_fr = -999; + } else if(pb->last_fr == -2) { + unsigned int cmd_dep; + tab_cmd_dbdma(pb->cap_cmd[pb->prev_last_fr], DBDMA_STOP, 0); + eieio(); + cmd_dep = (unsigned int)in_le32(&pb->overlay_last1->cmd_dep); + if(overlay_is_active(pb)) { + + DEBUG("PlanB: overlay is currently active\n"); + + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + if(cmd_dep != pb->ch1_cmd_phys) { + out_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->overlay_last1)); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } + pb->last_fr = pb->prev_last_fr; + pb->prev_last_fr = -999; + } + return; +} + +static void suspend_overlay(struct planb *pb) +{ + int fr = -1; + struct dbdma_cmd last; + + DEBUG("PlanB: suspend_overlay: %d\n", pb->suspend); + + if(pb->suspend++) + return; + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + if(pb->last_fr == -2) { + fr = pb->prev_last_fr; + memcpy(&last, (void*)pb->last_cmd[fr], sizeof(last)); + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + } + if(overlay_is_active(pb)) { + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->suspended.overlay = 1; + pb->suspended.frame = fr; + memcpy(&pb->suspended.cmd, &last, sizeof(last)); + return; + } + } + pb->suspended.overlay = 0; + pb->suspended.frame = fr; + memcpy(&pb->suspended.cmd, &last, sizeof(last)); + return; +} + +static void resume_overlay(struct planb *pb) +{ + + DEBUG("PlanB: resume_overlay: %d\n", pb->suspend); + + if(pb->suspend > 1) + return; + if(pb->suspended.frame != -1) { + memcpy((void*)pb->last_cmd[pb->suspended.frame], + &pb->suspended.cmd, sizeof(pb->suspended.cmd)); + } + if(ACTIVE & in_le32(&pb->planb_base->ch1.status)) { + goto finish; + } + if(pb->suspended.overlay) { + + DEBUG("PlanB: overlay being resumed\n"); + + st_le16 (&pb->ch1_cmd->command, DBDMA_NOP); + st_le16 (&pb->ch2_cmd->command, DBDMA_NOP); + /* Set command buffer addresses */ + st_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->overlay_last1)); + out_le32(&pb->planb_base->ch2.cmdptr, + virt_to_bus(pb->overlay_last2)); + /* Start the DMA controller */ + out_le32 (&pb->planb_base->ch2.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + out_le32 (&pb->planb_base->ch1.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + } else if(pb->suspended.frame != -1) { + out_le32(&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->last_cmd[pb->suspended.frame])); + out_le32 (&pb->planb_base->ch1.control, + PLANB_CLR(PAUSE) | PLANB_SET(RUN|WAKE)); + } + +finish: + pb->suspend--; + wake_up_interruptible(&pb->suspendq); +} + +static void add_clip(struct planb *pb, struct video_clip *clip) +{ + volatile unsigned char *base; + int xc = clip->x, yc = clip->y; + int wc = clip->width, hc = clip->height; + int ww = pb->win.width, hw = pb->win.height; + int x, y, xtmp1, xtmp2; + + DEBUG("PlanB: clip %dx%d+%d+%d\n", wc, hc, xc, yc); + + if(xc < 0) { + wc += xc; + xc = 0; + } + if(yc < 0) { + hc += yc; + yc = 0; + } + if(xc + wc > ww) + wc = ww - xc; + if(wc <= 0) /* Nothing to do */ + return; + if(yc + hc > hw) + hc = hw - yc; + + for (y = yc; y < yc+hc; y++) { + xtmp1=xc>>3; + xtmp2=(xc+wc)>>3; + base = pb->mask + y*96; + if(xc != 0 || wc >= 8) + *(base + xtmp1) &= (unsigned char)(0x00ff & + (0xff00 >> (xc&7))); + for (x = xtmp1 + 1; x < xtmp2; x++) { + *(base + x) = 0; + } + if(xc < (ww & ~0x7)) + *(base + xtmp2) &= (unsigned char)(0x00ff >> + ((xc+wc) & 7)); + } + + return; +} + +static void fill_cmd_buff(struct planb *pb) +{ + int restore = 0; + volatile struct dbdma_cmd last; + + DEBUG("PlanB: fill_cmd_buff()\n"); + + if(pb->overlay_last1 != pb->ch1_cmd) { + restore = 1; + last = *(pb->overlay_last1); + } + memset ((void *) pb->ch1_cmd, 0, 2 * pb->tab_size + * sizeof(struct dbdma_cmd)); + cmd_buff (pb); + if(restore) + *(pb->overlay_last1) = last; + if(pb->suspended.overlay) { + unsigned long jump_addr = in_le32(&pb->overlay_last1->cmd_dep); + if(jump_addr != pb->ch1_cmd_phys) { + int i; + + DEBUG("PlanB: adjusting ch1's jump address\n"); + + for(i = 0; i < MAX_GBUFFERS; i++) { + if(pb->need_pre_capture[i]) { + if(jump_addr == virt_to_bus(pb->pre_cmd[i])) + goto found; + } else { + if(jump_addr == virt_to_bus(pb->cap_cmd[i])) + goto found; + } + } + + DEBUG("PlanB: not found...\n"); + + goto out; +found: + if(pb->need_pre_capture[i]) + out_le32(&pb->pre_cmd[i]->phy_addr, + virt_to_bus(pb->overlay_last1)); + else + out_le32(&pb->cap_cmd[i]->phy_addr, + virt_to_bus(pb->overlay_last1)); + } + } +out: + pb->cmd_buff_inited = 1; + + return; +} + +static void cmd_buff(struct planb *pb) +{ + int i, bpp, count, nlines, stepsize, interlace; + unsigned long base, jump, addr_com, addr_dep; + volatile struct dbdma_cmd *c1 = pb->ch1_cmd; + volatile struct dbdma_cmd *c2 = pb->ch2_cmd; + + interlace = pb->win.interlace; + bpp = pb->win.bpp; + count = (bpp * ((pb->win.x + pb->win.width > pb->win.swidth) ? + (pb->win.swidth - pb->win.x) : pb->win.width)); + nlines = ((pb->win.y + pb->win.height > pb->win.sheight) ? + (pb->win.sheight - pb->win.y) : pb->win.height); + + /* Do video in: */ + + /* Preamble commands: */ + addr_com = virt_to_bus(c1); + addr_dep = virt_to_bus(&c1->cmd_dep); + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + jump = virt_to_bus(c1+16); /* 14 by cmd_geo_setup() and 2 for padding */ + if((c1 = cmd_geo_setup(c1, pb->win.width, pb->win.height, interlace, + bpp, 1, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered serious problems\n"); + tab_cmd_dbdma(pb->ch1_cmd + 1, DBDMA_STOP, 0); + tab_cmd_dbdma(pb->ch2_cmd + 1, DBDMA_STOP, 0); + return; + } + tab_cmd_store(c1++, addr_com, (unsigned)(DBDMA_NOP | BR_ALWAYS) << 16); + tab_cmd_store(c1++, addr_dep, jump); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + /* (1) wait for field sync to be set */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + /* wait for field sync to be cleared */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + /* if not odd field, wait until field sync is set again */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + /* assert ch_sync to ch2 */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_SET(CH_SYNC)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + base = (pb->frame_buffer_phys + pb->offset + pb->win.y * (pb->win.bpl + + pb->win.pad) + pb->win.x * bpp); + + if (interlace) { + stepsize = 2; + jump = virt_to_bus(c1 + (nlines + 1) / 2); + } else { + stepsize = 1; + jump = virt_to_bus(c1 + nlines); + } + + /* even field data: */ + for (i=0; i < nlines; i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, + count, base + i * (pb->win.bpl + pb->win.pad), jump); + + /* For non-interlaced, we use even fields only */ + if (!interlace) + goto cmd_tab_data_end; + + /* Resync to odd field */ + /* (2) wait for field sync to be set */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + /* wait for field sync to be cleared */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + /* if not odd field, wait until field sync is set again */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + /* assert ch_sync to ch2 */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_SET(CH_SYNC)); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + /* odd field data: */ + jump = virt_to_bus(c1 + nlines / 2); + for (i=1; i < nlines; i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + i * (pb->win.bpl + pb->win.pad), jump); + + /* And jump back to the start */ +cmd_tab_data_end: + pb->overlay_last1 = c1; /* keep a pointer to the last command */ + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch1_cmd)); + + /* Clipmask command buffer */ + + /* Preamble commands: */ + tab_cmd_dbdma(c2++, DBDMA_NOP, 0); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel), + PLANB_SET(CH_SYNC)); + /* wait until ch1 asserts ch_sync */ + tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0); + /* clear ch_sync asserted by ch1 */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.control), + PLANB_CLR(CH_SYNC)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(ODD_FIELD)); + + /* jump to end of even field if appropriate */ + /* this points to (interlace)? pos. C: pos. B */ + jump = (interlace) ? virt_to_bus(c2 + (nlines + 1) / 2 + 2): + virt_to_bus(c2 + nlines + 2); + /* if odd field, skip over to odd field clipmasking */ + tab_cmd_dbdma(c2++, DBDMA_NOP | BR_IFSET, jump); + + /* even field mask: */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(DMA_ABORT)); + /* this points to pos. B */ + jump = (interlace) ? virt_to_bus(c2 + nlines + 1): + virt_to_bus(c2 + nlines); + base = virt_to_bus(pb->mask); + for (i=0; i < nlines; i += stepsize, c2++) + tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96, + base + i * 96, jump); + + /* For non-interlaced, we use only even fields */ + if(!interlace) + goto cmd_tab_mask_end; + + /* odd field mask: */ +/* C */ tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch2.br_sel), + PLANB_SET(DMA_ABORT)); + /* this points to pos. B */ + jump = virt_to_bus(c2 + nlines / 2); + base = virt_to_bus(pb->mask); + for (i=1; i < nlines; i += 2, c2++) /* abort if set */ + tab_cmd_gen(c2, OUTPUT_MORE | KEY_STREAM0 | BR_IFSET, 96, + base + i * 96, jump); + + /* Inform channel 1 and jump back to start */ +cmd_tab_mask_end: + /* ok, I just realized this is kind of flawed. */ + /* this part is reached only after odd field clipmasking. */ + /* wanna clean up? */ + /* wait for field sync to be set */ + /* corresponds to fsync (1) of ch1 */ +/* B */ tab_cmd_dbdma(c2++, DBDMA_NOP | WAIT_IFCLR, 0); + /* restart ch1, meant to clear any dead bit or something */ + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control), + PLANB_CLR(RUN)); + tab_cmd_store(c2++, (unsigned)(&pb->planb_base_phys->ch1.control), + PLANB_SET(RUN)); + pb->overlay_last2 = c2; /* keep a pointer to the last command */ + /* start over even field clipmasking */ + tab_cmd_dbdma(c2, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->ch2_cmd)); + + eieio(); + return; +} + +/*********************************/ +/* grabdisplay support functions */ +/*********************************/ + +static int palette2fmt[] = { + 0, + PLANB_GRAY, + 0, + 0, + 0, + PLANB_COLOUR32, + PLANB_COLOUR15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; + +#define PLANB_PALETTE_MAX 15 + +static inline int overlay_is_active(struct planb *pb) +{ + unsigned int size = pb->tab_size * sizeof(struct dbdma_cmd); + unsigned int caddr = (unsigned)in_le32(&pb->planb_base->ch1.cmdptr); + + return (in_le32(&pb->overlay_last1->cmd_dep) == pb->ch1_cmd_phys) + && (caddr < (pb->ch1_cmd_phys + size)) + && (caddr >= (unsigned)pb->ch1_cmd_phys); +} + +static int vgrab(struct planb *pb, struct video_mmap *mp) +{ + unsigned int fr = mp->frame; + unsigned int format; + + if(pb->rawbuf==NULL) { + int err; + if((err=grabbuf_alloc(pb))) + return err; + } + + IDEBUG("PlanB: grab %d: %dx%d(%u)\n", pb->grabbing, + mp->width, mp->height, fr); + + if(pb->grabbing >= MAX_GBUFFERS) + return -ENOBUFS; + if(fr > (MAX_GBUFFERS - 1) || fr < 0) + return -EINVAL; + if(mp->height <= 0 || mp->width <= 0) + return -EINVAL; + if(mp->format < 0 || mp->format >= PLANB_PALETTE_MAX) + return -EINVAL; + if((format = palette2fmt[mp->format]) == 0) + return -EINVAL; + if (mp->height * mp->width * format > PLANB_MAX_FBUF) /* format = bpp */ + return -EINVAL; + + planb_lock(pb); + if(mp->width != pb->gwidth[fr] || mp->height != pb->gheight[fr] || + format != pb->gfmt[fr] || (pb->gnorm_switch[fr])) { + int i; +#ifndef PLANB_GSCANLINE + unsigned int osize = pb->gwidth[fr] * pb->gheight[fr] + * pb->gfmt[fr]; + unsigned int nsize = mp->width * mp->height * format; +#endif + + IDEBUG("PlanB: gwidth = %d, gheight = %d, mp->format = %u\n", + mp->width, mp->height, mp->format); + +#ifndef PLANB_GSCANLINE + if(pb->gnorm_switch[fr]) + nsize = 0; + if (nsize < osize) { + for(i = pb->gbuf_idx[fr]; osize > 0; i++) { + memset((void *)pb->rawbuf[i], 0, PAGE_SIZE); + osize -= PAGE_SIZE; + } + } + for(i = pb->l_fr_addr_idx[fr]; i < pb->l_fr_addr_idx[fr] + + pb->lnum[fr]; i++) + memset((void *)pb->rawbuf[i], 0, PAGE_SIZE); +#else +/* XXX TODO */ +/* + if(pb->gnorm_switch[fr]) + memset((void *)pb->gbuffer[fr], 0, + pb->gbytes_per_line * pb->gheight[fr]); + else { + if(mp-> + for(i = 0; i < pb->gheight[fr]; i++) { + memset((void *)(pb->gbuffer[fr] + + pb->gbytes_per_line * i + } + } +*/ +#endif + pb->gwidth[fr] = mp->width; + pb->gheight[fr] = mp->height; + pb->gfmt[fr] = format; + pb->last_cmd[fr] = setup_grab_cmd(fr, pb); + planb_pre_capture(fr, pb->gfmt[fr], pb); /* gfmt = bpp */ + pb->need_pre_capture[fr] = 1; + pb->gnorm_switch[fr] = 0; + } else + pb->need_pre_capture[fr] = 0; + pb->frame_stat[fr] = GBUFFER_GRABBING; + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + + IDEBUG("PlanB: ch1 inactive, initiating grabbing\n"); + + planb_dbdma_stop(&pb->planb_base->ch1); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->pre_cmd[fr])); + } else { + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + /* let's be on the safe side. here is not timing critical. */ + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), DBDMA_NOP, 0); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->cap_cmd[fr])); + } + planb_dbdma_restart(&pb->planb_base->ch1); + pb->last_fr = fr; + } else { + int i; + + IDEBUG("PlanB: ch1 active, grabbing being queued\n"); + + if((pb->last_fr == -1) || ((pb->last_fr == -2) && + overlay_is_active(pb))) { + + IDEBUG("PlanB: overlay is active, grabbing defered\n"); + + tab_cmd_dbdma(pb->last_cmd[fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_store(pb->pre_cmd[fr], + virt_to_bus(&pb->overlay_last1->cmd_dep), + virt_to_bus(pb->ch1_cmd)); + eieio(); + out_le32 (&pb->overlay_last1->cmd_dep, + virt_to_bus(pb->pre_cmd[fr])); + } else { + tab_cmd_store(pb->cap_cmd[fr], + virt_to_bus(&pb->overlay_last1->cmd_dep), + virt_to_bus(pb->ch1_cmd)); + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + eieio(); + out_le32 (&pb->overlay_last1->cmd_dep, + virt_to_bus(pb->cap_cmd[fr])); + } + for(i = 0; overlay_is_active(pb) && i < 999; i++) + IDEBUG("PlanB: waiting for overlay done\n"); + tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0); + pb->prev_last_fr = fr; + pb->last_fr = -2; + } else if(pb->last_fr == -2) { + + IDEBUG("PlanB: mixed mode detected, grabbing" + " will be done before activating overlay\n"); + + tab_cmd_dbdma(pb->ch1_cmd, DBDMA_NOP, 0); + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->pre_cmd[fr])); + eieio(); + } else { + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + if(pb->gwidth[pb->prev_last_fr] != + pb->gwidth[fr] + || pb->gheight[pb->prev_last_fr] != + pb->gheight[fr] + || pb->gfmt[pb->prev_last_fr] != + pb->gfmt[fr]) + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + else + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr] + 16)); + tab_cmd_dbdma(pb->last_cmd[pb->prev_last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr])); + eieio(); + } + tab_cmd_dbdma(pb->last_cmd[fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->ch1_cmd)); + eieio(); + pb->prev_last_fr = fr; + pb->last_fr = -2; + } else { + + IDEBUG("PlanB: active grabbing session detected\n"); + + if(pb->need_pre_capture[fr]) { + + IDEBUG("PlanB: padding pre-capture sequence\n"); + + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->pre_cmd[fr])); + eieio(); + } else { + tab_cmd_dbdma(pb->last_cmd[fr], DBDMA_STOP, 0); + tab_cmd_dbdma(pb->cap_cmd[fr], DBDMA_NOP, 0); + if(pb->gwidth[pb->last_fr] != pb->gwidth[fr] + || pb->gheight[pb->last_fr] != + pb->gheight[fr] + || pb->gfmt[pb->last_fr] != + pb->gfmt[fr]) + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP, 0); + else + tab_cmd_dbdma((pb->cap_cmd[fr] + 1), + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr] + 16)); + tab_cmd_dbdma(pb->last_cmd[pb->last_fr], + DBDMA_NOP | BR_ALWAYS, + virt_to_bus(pb->cap_cmd[fr])); + eieio(); + } + pb->last_fr = fr; + } + if(!(ACTIVE & in_le32(&pb->planb_base->ch1.status))) { + + IDEBUG("PlanB: became inactive in the mean time..." + "reactivating\n"); + + planb_dbdma_stop(&pb->planb_base->ch1); + out_le32 (&pb->planb_base->ch1.cmdptr, + virt_to_bus(pb->cap_cmd[fr])); + planb_dbdma_restart(&pb->planb_base->ch1); + } + } + pb->grabbing++; + planb_unlock(pb); + + return 0; +} + +static void planb_pre_capture(int fr, int bpp, struct planb *pb) +{ + volatile struct dbdma_cmd *c1 = pb->pre_cmd[fr]; + int interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0; + + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace, + bpp, 0, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered some problems\n"); + tab_cmd_dbdma(pb->pre_cmd[fr] + 1, DBDMA_STOP, 0); + return; + } + /* Sync to even field */ + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + /* For non-interlaced, we use even fields only */ + if (pb->gheight[fr] <= pb->maxlines/2) + goto cmd_tab_data_end; + /* Sync to odd field */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); +cmd_tab_data_end: + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(pb->cap_cmd[fr])); + + eieio(); +} + +static volatile struct dbdma_cmd *setup_grab_cmd(int fr, struct planb *pb) +{ + int i, bpp, count, nlines, stepsize, interlace; +#ifdef PLANB_GSCANLINE + int scanline; +#else + int nlpp, leftover1; + unsigned long base; +#endif + unsigned long jump; + int pagei; + volatile struct dbdma_cmd *c1; + volatile struct dbdma_cmd *jump_addr; + + c1 = pb->cap_cmd[fr]; + interlace = (pb->gheight[fr] > pb->maxlines/2)? 1: 0; + bpp = pb->gfmt[fr]; /* gfmt = bpp */ + count = bpp * pb->gwidth[fr]; + nlines = pb->gheight[fr]; +#ifdef PLANB_GSCANLINE + scanline = pb->gbytes_per_line; +#else + pb->lsize[fr] = count; + pb->lnum[fr] = 0; +#endif + + /* Do video in: */ + + /* Preamble commands: */ + tab_cmd_dbdma(c1++, DBDMA_NOP, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, virt_to_bus(c1 + 16)); c1++; + if((c1 = cmd_geo_setup(c1, pb->gwidth[fr], pb->gheight[fr], interlace, + bpp, 0, pb)) == NULL) { + printk(KERN_WARNING "PlanB: encountered serious problems\n"); + tab_cmd_dbdma(pb->cap_cmd[fr] + 1, DBDMA_STOP, 0); + return (pb->cap_cmd[fr] + 2); + } + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.wait_sel), + PLANB_SET(FIELD_SYNC)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFSET, virt_to_bus(c1-3)); c1++; + tab_cmd_dbdma(c1++, DBDMA_NOP | INTR_ALWAYS, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + if (interlace) { + stepsize = 2; + jump_addr = c1 + TAB_FACTOR * (nlines + 1) / 2; + } else { + stepsize = 1; + jump_addr = c1 + TAB_FACTOR * nlines; + } + jump = virt_to_bus(jump_addr); + + /* even field data: */ + + pagei = pb->gbuf_idx[fr]; +#ifdef PLANB_GSCANLINE + for (i = 0; i < nlines; i += stepsize) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pagei + + i * scanline / PAGE_SIZE]), jump); + } +#else + i = 0; + leftover1 = 0; + do { + int j; + + base = virt_to_bus(pb->rawbuf[pagei]); + nlpp = (PAGE_SIZE - leftover1) / count / stepsize; + for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, + count, base + count * j * stepsize + leftover1, jump); + if(i < nlines) { + int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1; + + if(lov0 == 0) + leftover1 = 0; + else { + if(lov0 >= count) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, base + + count * nlpp * stepsize + leftover1, jump); + } else { + pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei] + + count * nlpp * stepsize + leftover1; + pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1; + pb->l_to_next_size[fr][pb->lnum[fr]] = count - lov0; + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr] + + pb->lnum[fr]]), jump); + if(++pb->lnum[fr] > MAX_LNUM) + pb->lnum[fr]--; + } + leftover1 = count * stepsize - lov0; + i += stepsize; + } + } + pagei++; + } while(i < nlines); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump); + c1 = jump_addr; +#endif /* PLANB_GSCANLINE */ + + /* For non-interlaced, we use even fields only */ + if (!interlace) + goto cmd_tab_data_end; + + /* Sync to odd field */ + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFCLR, 0); + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(ODD_FIELD)); + tab_cmd_dbdma(c1++, DBDMA_NOP | WAIT_IFSET, 0); + tab_cmd_dbdma(c1, DBDMA_NOP | BR_IFCLR, virt_to_bus(c1-3)); c1++; + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->ch1.br_sel), + PLANB_SET(DMA_ABORT)); + + /* odd field data: */ + jump_addr = c1 + TAB_FACTOR * nlines / 2; + jump = virt_to_bus(jump_addr); +#ifdef PLANB_GSCANLINE + for (i = 1; i < nlines; i += stepsize) { + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pagei + + i * scanline / PAGE_SIZE]), jump); + } +#else + i = 1; + leftover1 = 0; + pagei = pb->gbuf_idx[fr]; + if(nlines <= 1) + goto skip; + do { + int j; + + base = virt_to_bus(pb->rawbuf[pagei]); + nlpp = (PAGE_SIZE - leftover1) / count / stepsize; + if(leftover1 >= count) { + tab_cmd_gen(c1++, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + leftover1 - count, jump); + i += stepsize; + } + for(j = 0; j < nlpp && i < nlines; j++, i += stepsize, c1++) + tab_cmd_gen(c1, INPUT_MORE | KEY_STREAM0 | BR_IFSET, count, + base + count * (j * stepsize + 1) + leftover1, jump); + if(i < nlines) { + int lov0 = PAGE_SIZE - count * nlpp * stepsize - leftover1; + + if(lov0 == 0) + leftover1 = 0; + else { + if(lov0 > count) { + pb->l_to_addr[fr][pb->lnum[fr]] = pb->rawbuf[pagei] + + count * (nlpp * stepsize + 1) + leftover1; + pb->l_to_next_idx[fr][pb->lnum[fr]] = pagei + 1; + pb->l_to_next_size[fr][pb->lnum[fr]] = count * stepsize + - lov0; + tab_cmd_gen(c1++, INPUT_MORE | BR_IFSET, count, + virt_to_bus(pb->rawbuf[pb->l_fr_addr_idx[fr] + + pb->lnum[fr]]), jump); + if(++pb->lnum[fr] > MAX_LNUM) + pb->lnum[fr]--; + i += stepsize; + } + leftover1 = count * stepsize - lov0; + } + } + pagei++; + } while(i < nlines); +skip: + tab_cmd_dbdma(c1, DBDMA_NOP | BR_ALWAYS, jump); + c1 = jump_addr; +#endif /* PLANB_GSCANLINE */ + +cmd_tab_data_end: + tab_cmd_store(c1++, (unsigned)(&pb->planb_base_phys->intr_stat), + (fr << 9) | PLANB_FRM_IRQ | PLANB_GEN_IRQ); + /* stop it */ + tab_cmd_dbdma(c1, DBDMA_STOP, 0); + + eieio(); + return c1; +} + +static void planb_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + unsigned int stat, astat; + struct planb *pb = (struct planb *)dev_id; + + IDEBUG("PlanB: planb_irq()\n"); + + /* get/clear interrupt status bits */ + eieio(); + stat = in_le32(&pb->planb_base->intr_stat); + astat = stat & pb->intr_mask; + out_le32(&pb->planb_base->intr_stat, PLANB_FRM_IRQ + & ~astat & stat & ~PLANB_GEN_IRQ); + IDEBUG("PlanB: stat = %X, astat = %X\n", stat, astat); + + if(astat & PLANB_FRM_IRQ) { + unsigned int fr = stat >> 9; +#ifndef PLANB_GSCANLINE + int i; +#endif + IDEBUG("PlanB: PLANB_FRM_IRQ\n"); + + pb->gcount++; + + IDEBUG("PlanB: grab %d: fr = %d, gcount = %d\n", + pb->grabbing, fr, pb->gcount); +#ifndef PLANB_GSCANLINE + IDEBUG("PlanB: %d * %d bytes are being copied over\n", + pb->lnum[fr], pb->lsize[fr]); + for(i = 0; i < pb->lnum[fr]; i++) { + int first = pb->lsize[fr] - pb->l_to_next_size[fr][i]; + + memcpy(pb->l_to_addr[fr][i], + pb->rawbuf[pb->l_fr_addr_idx[fr] + i], + first); + memcpy(pb->rawbuf[pb->l_to_next_idx[fr][i]], + pb->rawbuf[pb->l_fr_addr_idx[fr] + i] + first, + pb->l_to_next_size[fr][i]); + } +#endif + pb->frame_stat[fr] = GBUFFER_DONE; + pb->grabbing--; + wake_up_interruptible(&pb->capq); + return; + } + /* incorrect interrupts? */ + pb->intr_mask = PLANB_CLR_IRQ; + out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + printk(KERN_ERR "PlanB: IRQ lockup, cleared intrrupts" + " unconditionally\n"); +} + +/******************************* + * Device Operations functions * + *******************************/ + +static int planb_open(struct video_device *dev, int mode) +{ + struct planb *pb = (struct planb *)dev; + + if (pb->user == 0) { + int err; + if((err = planb_prepare_open(pb)) != 0) + return err; + } + pb->user++; + + DEBUG("PlanB: device opened\n"); + + MOD_INC_USE_COUNT; + return 0; +} + +static void planb_close(struct video_device *dev) +{ + struct planb *pb = (struct planb *)dev; + + if(pb->user < 1) /* ??? */ + return; + planb_lock(pb); + if (pb->user == 1) { + if (pb->overlay) { + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + pb->overlay = 0; + } + planb_prepare_close(pb); + } + pb->user--; + planb_unlock(pb); + + DEBUG("PlanB: device closed\n"); + + MOD_DEC_USE_COUNT; +} + +static long planb_read(struct video_device *v, char *buf, unsigned long count, + int nonblock) +{ + DEBUG("planb: read request\n"); + return -EINVAL; +} + +static long planb_write(struct video_device *v, const char *buf, + unsigned long count, int nonblock) +{ + DEBUG("planb: write request\n"); + return -EINVAL; +} + +static int planb_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct planb *pb=(struct planb *)dev; + + switch (cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + + DEBUG("PlanB: IOCTL VIDIOCGCAP\n"); + + strcpy (b.name, pb->video_dev.name); + b.type = VID_TYPE_OVERLAY | VID_TYPE_CLIPPING | + VID_TYPE_FRAMERAM | VID_TYPE_SCALES | + VID_TYPE_CAPTURE; + b.channels = 2; /* composite & svhs */ + b.audios = 0; + b.maxwidth = PLANB_MAXPIXELS; + b.maxheight = PLANB_MAXLINES; + b.minwidth = 32; /* wild guess */ + b.minheight = 32; + if (copy_to_user(arg,&b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCSFBUF: + { + struct video_buffer v; + unsigned short bpp; + unsigned int fmt; + + DEBUG("PlanB: IOCTL VIDIOCSFBUF\n"); + + if (!capable(CAP_SYS_ADMIN) + || !capable(CAP_SYS_RAWIO)) + return -EPERM; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + planb_lock(pb); + switch(v.depth) { + case 8: + bpp = 1; + fmt = PLANB_GRAY; + break; + case 15: + case 16: + bpp = 2; + fmt = PLANB_COLOUR15; + break; + case 24: + case 32: + bpp = 4; + fmt = PLANB_COLOUR32; + break; + default: + planb_unlock(pb); + return -EINVAL; + } + if (bpp * v.width > v.bytesperline) { + planb_unlock(pb); + return -EINVAL; + } + pb->win.bpp = bpp; + pb->win.color_fmt = fmt; + pb->frame_buffer_phys = (unsigned long) v.base; + pb->win.sheight = v.height; + pb->win.swidth = v.width; + pb->picture.depth = pb->win.depth = v.depth; + pb->win.bpl = pb->win.bpp * pb->win.swidth; + pb->win.pad = v.bytesperline - pb->win.bpl; + + DEBUG("PlanB: Display at %p is %d by %d, bytedepth %d," + " bpl %d (+ %d)\n", v.base, v.width,v.height, + pb->win.bpp, pb->win.bpl, pb->win.pad); + + pb->cmd_buff_inited = 0; + if(pb->overlay) { + suspend_overlay(pb); + fill_cmd_buff(pb); + resume_overlay(pb); + } + planb_unlock(pb); + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer v; + + DEBUG("PlanB: IOCTL VIDIOCGFBUF\n"); + + v.base = (void *)pb->frame_buffer_phys; + v.height = pb->win.sheight; + v.width = pb->win.swidth; + v.depth = pb->win.depth; + v.bytesperline = pb->win.bpl + pb->win.pad; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + { + int i; + + if(copy_from_user(&i, arg, sizeof(i))) + return -EFAULT; + if(i==0) { + DEBUG("PlanB: IOCTL VIDIOCCAPTURE Stop\n"); + + if (!(pb->overlay)) + return 0; + planb_lock(pb); + pb->overlay = 0; + overlay_stop(pb); + planb_unlock(pb); + } else { + DEBUG("PlanB: IOCTL VIDIOCCAPTURE Start\n"); + + if (pb->frame_buffer_phys == 0 || + pb->win.width == 0 || + pb->win.height == 0) + return -EINVAL; + if (pb->overlay) + return 0; + planb_lock(pb); + pb->overlay = 1; + if(!(pb->cmd_buff_inited)) + fill_cmd_buff(pb); + overlay_start(pb); + planb_unlock(pb); + } + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + + DEBUG("PlanB: IOCTL VIDIOCGCHAN\n"); + + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + v.flags = 0; + v.tuners = 0; + v.type = VIDEO_TYPE_CAMERA; + v.norm = pb->win.norm; + switch(v.channel) + { + case 0: + strcpy(v.name,"Composite"); + break; + case 1: + strcpy(v.name,"SVHS"); + break; + default: + return -EINVAL; + break; + } + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel v; + + DEBUG("PlanB: IOCTL VIDIOCSCHAN\n"); + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + if (v.norm != pb->win.norm) { + int i, maxlines; + + switch (v.norm) + { + case VIDEO_MODE_PAL: + case VIDEO_MODE_SECAM: + maxlines = PLANB_MAXLINES; + break; + case VIDEO_MODE_NTSC: + maxlines = PLANB_NTSC_MAXLINES; + break; + default: + return -EINVAL; + break; + } + planb_lock(pb); + /* empty the grabbing queue */ + while(pb->grabbing) + interruptible_sleep_on(&pb->capq); + pb->maxlines = maxlines; + pb->win.norm = v.norm; + /* Stop overlay if running */ + suspend_overlay(pb); + for(i = 0; i < MAX_GBUFFERS; i++) + pb->gnorm_switch[i] = 1; + /* I know it's an overkill, but.... */ + fill_cmd_buff(pb); + /* ok, now init it accordingly */ + saa_init_regs (pb); + /* restart overlay if it was running */ + resume_overlay(pb); + planb_unlock(pb); + } + + switch(v.channel) + { + case 0: /* Composite */ + saa_set (SAA7196_IOCC, + ((saa_regs[pb->win.norm][SAA7196_IOCC] & + ~7) | 3), pb); + break; + case 1: /* SVHS */ + saa_set (SAA7196_IOCC, + ((saa_regs[pb->win.norm][SAA7196_IOCC] & + ~7) | 4), pb); + break; + default: + return -EINVAL; + break; + } + + return 0; + } + case VIDIOCGPICT: + { + struct video_picture vp = pb->picture; + + DEBUG("PlanB: IOCTL VIDIOCGPICT\n"); + + switch(pb->win.color_fmt) { + case PLANB_GRAY: + vp.palette = VIDEO_PALETTE_GREY; + case PLANB_COLOUR15: + vp.palette = VIDEO_PALETTE_RGB555; + break; + case PLANB_COLOUR32: + vp.palette = VIDEO_PALETTE_RGB32; + break; + default: + vp.palette = 0; + break; + } + + if(copy_to_user(arg,&vp,sizeof(vp))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture vp; + + DEBUG("PlanB: IOCTL VIDIOCSPICT\n"); + + if(copy_from_user(&vp,arg,sizeof(vp))) + return -EFAULT; + pb->picture = vp; + /* Should we do sanity checks here? */ + saa_set (SAA7196_BRIG, (unsigned char) + ((pb->picture.brightness) >> 8), pb); + saa_set (SAA7196_HUEC, (unsigned char) + ((pb->picture.hue) >> 8) ^ 0x80, pb); + saa_set (SAA7196_CSAT, (unsigned char) + ((pb->picture.colour) >> 9), pb); + saa_set (SAA7196_CONT, (unsigned char) + ((pb->picture.contrast) >> 9), pb); + + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip clip; + int i; + + DEBUG("PlanB: IOCTL VIDIOCSWIN\n"); + + if(copy_from_user(&vw,arg,sizeof(vw))) + return -EFAULT; + + planb_lock(pb); + /* Stop overlay if running */ + suspend_overlay(pb); + pb->win.interlace = (vw.height > pb->maxlines/2)? 1: 0; + if (pb->win.x != vw.x || + pb->win.y != vw.y || + pb->win.width != vw.width || + pb->win.height != vw.height || + !pb->cmd_buff_inited) { + pb->win.x = vw.x; + pb->win.y = vw.y; + pb->win.width = vw.width; + pb->win.height = vw.height; + fill_cmd_buff(pb); + } + /* Reset clip mask */ + memset ((void *) pb->mask, 0xff, (pb->maxlines + * ((PLANB_MAXPIXELS + 7) & ~7)) / 8); + /* Add any clip rects */ + for (i = 0; i < vw.clipcount; i++) { + if (copy_from_user(&clip, vw.clips + i, + sizeof(struct video_clip))) + return -EFAULT; + add_clip(pb, &clip); + } + /* restart overlay if it was running */ + resume_overlay(pb); + planb_unlock(pb); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + + DEBUG("PlanB: IOCTL VIDIOCGWIN\n"); + + vw.x=pb->win.x; + vw.y=pb->win.y; + vw.width=pb->win.width; + vw.height=pb->win.height; + vw.chromakey=0; + vw.flags=0; + if(pb->win.interlace) + vw.flags|=VIDEO_WINDOW_INTERLACE; + if(copy_to_user(arg,&vw,sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCSYNC: { + int i; + + IDEBUG("PlanB: IOCTL VIDIOCSYNC\n"); + + if(copy_from_user((void *)&i,arg,sizeof(int))) + return -EFAULT; + + IDEBUG("PlanB: sync to frame %d\n", i); + + if(i > (MAX_GBUFFERS - 1) || i < 0) + return -EINVAL; +chk_grab: + switch (pb->frame_stat[i]) { + case GBUFFER_UNUSED: + return -EINVAL; + case GBUFFER_GRABBING: + IDEBUG("PlanB: waiting for grab" + " done (%d)\n", i); + interruptible_sleep_on(&pb->capq); + if(signal_pending(current)) + return -EINTR; + goto chk_grab; + case GBUFFER_DONE: + pb->frame_stat[i] = GBUFFER_UNUSED; + break; + } + return 0; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + volatile unsigned int status; + + IDEBUG("PlanB: IOCTL VIDIOCMCAPTURE\n"); + + if(copy_from_user((void *) &vm,(void *)arg,sizeof(vm))) + return -EFAULT; + status = pb->frame_stat[vm.frame]; + if (status != GBUFFER_UNUSED) + return -EBUSY; + + return vgrab(pb, &vm); + } + + case VIDIOCGMBUF: + { + int i; + struct video_mbuf vm; + + DEBUG("PlanB: IOCTL VIDIOCGMBUF\n"); + + memset(&vm, 0 , sizeof(vm)); + vm.size = PLANB_MAX_FBUF * MAX_GBUFFERS; + vm.frames = MAX_GBUFFERS; + for(i = 0; i<MAX_GBUFFERS; i++) + vm.offsets[i] = PLANB_MAX_FBUF * i; + if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + return -EFAULT; + return 0; + } + + case PLANBIOCGSAAREGS: + { + struct planb_saa_regs preg; + + DEBUG("PlanB: IOCTL PLANBIOCGSAAREGS\n"); + + if(copy_from_user(&preg, arg, sizeof(preg))) + return -EFAULT; + if(preg.addr >= SAA7196_NUMREGS) + return -EINVAL; + preg.val = saa_regs[pb->win.norm][preg.addr]; + if(copy_to_user((void *)arg, (void *)&preg, + sizeof(preg))) + return -EFAULT; + return 0; + } + + case PLANBIOCSSAAREGS: + { + struct planb_saa_regs preg; + + DEBUG("PlanB: IOCTL PLANBIOCSSAAREGS\n"); + + if(copy_from_user(&preg, arg, sizeof(preg))) + return -EFAULT; + if(preg.addr >= SAA7196_NUMREGS) + return -EINVAL; + saa_set (preg.addr, preg.val, pb); + return 0; + } + + case PLANBIOCGSTAT: + { + struct planb_stat_regs pstat; + + DEBUG("PlanB: IOCTL PLANBIOCGSTAT\n"); + + pstat.ch1_stat = in_le32(&pb->planb_base->ch1.status); + pstat.ch2_stat = in_le32(&pb->planb_base->ch2.status); + pstat.saa_stat0 = saa_status(0, pb); + pstat.saa_stat1 = saa_status(1, pb); + + if(copy_to_user((void *)arg, (void *)&pstat, + sizeof(pstat))) + return -EFAULT; + return 0; + } + + case PLANBIOCSMODE: { + int v; + + DEBUG("PlanB: IOCTL PLANBIOCSMODE\n"); + + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + switch(v) + { + case PLANB_TV_MODE: + saa_set (SAA7196_STDC, + (saa_regs[pb->win.norm][SAA7196_STDC] & + 0x7f), pb); + break; + case PLANB_VTR_MODE: + saa_set (SAA7196_STDC, + (saa_regs[pb->win.norm][SAA7196_STDC] | + 0x80), pb); + break; + default: + return -EINVAL; + break; + } + pb->win.mode = v; + return 0; + } + case PLANBIOCGMODE: { + int v=pb->win.mode; + + DEBUG("PlanB: IOCTL PLANBIOCGMODE\n"); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } +#ifdef PLANB_GSCANLINE + case PLANBG_GRAB_BPL: { + int v=pb->gbytes_per_line; + + DEBUG("PlanB: IOCTL PLANBG_GRAB_BPL\n"); + + if(copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + return 0; + } +#endif /* PLANB_GSCANLINE */ + case PLANB_INTR_DEBUG: { + int i; + + DEBUG("PlanB: IOCTL PLANB_INTR_DEBUG\n"); + + if(copy_from_user(&i, arg, sizeof(i))) + return -EFAULT; + + /* avoid hang ups all together */ + for (i = 0; i < MAX_GBUFFERS; i++) { + if(pb->frame_stat[i] == GBUFFER_GRABBING) { + pb->frame_stat[i] = GBUFFER_DONE; + } + } + if(pb->grabbing) + pb->grabbing--; + wake_up_interruptible(&pb->capq); + return 0; + } + case PLANB_INV_REGS: { + int i; + struct planb_any_regs any; + + DEBUG("PlanB: IOCTL PLANB_INV_REGS\n"); + + if(copy_from_user(&any, arg, sizeof(any))) + return -EFAULT; + if(any.offset < 0 || any.offset + any.bytes > 0x400) + return -EINVAL; + if(any.bytes > 128) + return -EINVAL; + for (i = 0; i < any.bytes; i++) { + any.data[i] = + in_8((unsigned char *)pb->planb_base + + any.offset + i); + } + if(copy_to_user(arg,&any,sizeof(any))) + return -EFAULT; + return 0; + } + default: + { + DEBUG("PlanB: Unimplemented IOCTL\n"); + return -ENOIOCTLCMD; + } + /* Some IOCTLs are currently unsupported on PlanB */ + case VIDIOCGTUNER: { + DEBUG("PlanB: IOCTL VIDIOCGTUNER\n"); + goto unimplemented; } + case VIDIOCSTUNER: { + DEBUG("PlanB: IOCTL VIDIOCSTUNER\n"); + goto unimplemented; } + case VIDIOCSFREQ: { + DEBUG("PlanB: IOCTL VIDIOCSFREQ\n"); + goto unimplemented; } + case VIDIOCGFREQ: { + DEBUG("PlanB: IOCTL VIDIOCGFREQ\n"); + goto unimplemented; } + case VIDIOCKEY: { + DEBUG("PlanB: IOCTL VIDIOCKEY\n"); + goto unimplemented; } + case VIDIOCSAUDIO: { + DEBUG("PlanB: IOCTL VIDIOCSAUDIO\n"); + goto unimplemented; } + case VIDIOCGAUDIO: { + DEBUG("PlanB: IOCTL VIDIOCGAUDIO\n"); + goto unimplemented; } +unimplemented: + DEBUG(" Unimplemented\n"); + return -ENOIOCTLCMD; + } + return 0; +} + +static int planb_mmap(struct video_device *dev, const char *adr, unsigned long size) +{ + int i; + struct planb *pb = (struct planb *)dev; + unsigned long start = (unsigned long)adr; + + if (size > MAX_GBUFFERS * PLANB_MAX_FBUF) + return -EINVAL; + if (!pb->rawbuf) { + int err; + if((err=grabbuf_alloc(pb))) + return err; + } + for (i = 0; i < pb->rawbuf_size; i++) { + if (remap_page_range(start, virt_to_phys((void *)pb->rawbuf[i]), + PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + if (size <= PAGE_SIZE) + break; + size -= PAGE_SIZE; + } + return 0; +} + +/* This gets called upon device registration */ +/* we could do some init here */ +static int planb_init_done(struct video_device *dev) +{ + return 0; +} + +static struct video_device planb_template= +{ + PLANB_DEVICE_NAME, + VID_TYPE_OVERLAY, + VID_HARDWARE_PLANB, + planb_open, + planb_close, + planb_read, + planb_write, +#if LINUX_VERSION_CODE >= 0x020100 + NULL, /* poll */ +#endif + planb_ioctl, + planb_mmap, /* mmap? */ + planb_init_done, + NULL, /* pointer to private data */ + 0, + 0 +}; + +static int init_planb(struct planb *pb) +{ + unsigned char saa_rev; + int i, result; + unsigned long flags; + + memset ((void *) &pb->win, 0, sizeof (struct planb_window)); + /* Simple sanity check */ + if(def_norm >= NUM_SUPPORTED_NORM || def_norm < 0) { + printk(KERN_ERR "PlanB: Option(s) invalid\n"); + return -2; + } + pb->win.norm = def_norm; + pb->win.mode = PLANB_TV_MODE; /* TV mode */ + pb->win.interlace=1; + pb->win.x=0; + pb->win.y=0; + pb->win.width=768; /* 640 */ + pb->win.height=576; /* 480 */ + pb->maxlines=576; +#if 0 + btv->win.cropwidth=768; /* 640 */ + btv->win.cropheight=576; /* 480 */ + btv->win.cropx=0; + btv->win.cropy=0; +#endif + pb->win.pad=0; + pb->win.bpp=4; + pb->win.depth=32; + pb->win.color_fmt=PLANB_COLOUR32; + pb->win.bpl=1024*pb->win.bpp; + pb->win.swidth=1024; + pb->win.sheight=768; +#ifdef PLANB_GSCANLINE + if((pb->gbytes_per_line = PLANB_MAXPIXELS * 4) > PAGE_SIZE + || (pb->gbytes_per_line <= 0)) + return -3; + else { + /* page align pb->gbytes_per_line for DMA purpose */ + for(i = PAGE_SIZE; pb->gbytes_per_line < (i>>1);) + i>>=1; + pb->gbytes_per_line = i; + } +#endif + pb->tab_size = PLANB_MAXLINES + 40; + pb->suspend = 0; + pb->lock = 0; + init_waitqueue_head(&pb->lockq); + pb->ch1_cmd = 0; + pb->ch2_cmd = 0; + pb->mask = 0; + pb->priv_space = 0; + pb->offset = 0; + pb->user = 0; + pb->overlay = 0; + init_waitqueue_head(&pb->suspendq); + pb->cmd_buff_inited = 0; + pb->frame_buffer_phys = 0; + + /* Reset DMA controllers */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + saa_rev = (saa_status(0, pb) & 0xf0) >> 4; + printk(KERN_INFO "PlanB: SAA7196 video processor rev. %d\n", saa_rev); + /* Initialize the SAA registers in memory and on chip */ + saa_init_regs (pb); + + /* clear interrupt mask */ + pb->intr_mask = PLANB_CLR_IRQ; + + save_flags(flags); cli(); + result = request_irq(pb->irq, planb_irq, 0, "PlanB", (void *)pb); + if (result < 0) { + if (result==-EINVAL) + printk(KERN_ERR "PlanB: Bad irq number (%d) " + "or handler\n", (int)pb->irq); + else if (result==-EBUSY) + printk(KERN_ERR "PlanB: I don't know why, " + "but IRQ %d is busy\n", (int)pb->irq); + restore_flags(flags); + return result; + } + disable_irq(pb->irq); + restore_flags(flags); + + /* Now add the template and register the device unit. */ + memcpy(&pb->video_dev,&planb_template,sizeof(planb_template)); + + pb->picture.brightness=0x90<<8; + pb->picture.contrast = 0x70 << 8; + pb->picture.colour = 0x70<<8; + pb->picture.hue = 0x8000; + pb->picture.whiteness = 0; + pb->picture.depth = pb->win.depth; + + pb->frame_stat=NULL; + init_waitqueue_head(&pb->capq); + for(i=0; i<MAX_GBUFFERS; i++) { + pb->gbuf_idx[i] = PLANB_MAX_FBUF * i / PAGE_SIZE; + pb->gwidth[i]=0; + pb->gheight[i]=0; + pb->gfmt[i]=0; + pb->cap_cmd[i]=NULL; +#ifndef PLANB_GSCANLINE + pb->l_fr_addr_idx[i] = MAX_GBUFFERS * (PLANB_MAX_FBUF + / PAGE_SIZE + 1) + MAX_LNUM * i; + pb->lsize[i] = 0; + pb->lnum[i] = 0; +#endif + } + pb->rawbuf=NULL; + pb->grabbing=0; + + /* enable interrupts */ + out_le32(&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + pb->intr_mask = PLANB_FRM_IRQ; + enable_irq(pb->irq); + + if(video_register_device(&pb->video_dev, VFL_TYPE_GRABBER)<0) + return -1; + + return 0; +} + +/* + * Scan for a PlanB controller, request the irq and map the io memory + */ + +static int find_planb(void) +{ + struct planb *pb; + struct device_node *planb_devices; + unsigned char dev_fn, confreg, bus; + unsigned int old_base, new_base; + unsigned int irq; + struct pci_dev *pdev; + + if (_machine != _MACH_Pmac) + return 0; + + planb_devices = find_devices("planb"); + if (planb_devices == 0) { + planb_num=0; + printk(KERN_WARNING "PlanB: no device found!\n"); + return planb_num; + } + + if (planb_devices->next != NULL) + printk(KERN_ERR "Warning: only using first PlanB device!\n"); + pb = &planbs[0]; + planb_num = 1; + + if (planb_devices->n_addrs != 1) { + printk (KERN_WARNING "PlanB: expecting 1 address for planb " + "(got %d)", planb_devices->n_addrs); + return 0; + } + + if (planb_devices->n_intrs == 0) { + printk(KERN_WARNING "PlanB: no intrs for device %s\n", + planb_devices->full_name); + return 0; + } else { + irq = planb_devices->intrs[0].line; + } + + /* Initialize PlanB's PCI registers */ + + /* There is a bug with the way OF assigns addresses + to the devices behind the chaos bridge. + control needs only 0x1000 of space, but decodes only + the upper 16 bits. It therefore occupies a full 64K. + OF assigns the planb controller memory within this space; + so we need to change that here in order to access planb. */ + + /* We remap to 0xf1000000 in hope that nobody uses it ! */ + + bus = (planb_devices->addrs[0].space >> 16) & 0xff; + dev_fn = (planb_devices->addrs[0].space >> 8) & 0xff; + confreg = planb_devices->addrs[0].space & 0xff; + old_base = planb_devices->addrs[0].address; + new_base = 0xf1000000; + + DEBUG("PlanB: Found on bus %d, dev %d, func %d, " + "membase 0x%x (base reg. 0x%x)\n", + bus, PCI_SLOT(dev_fn), PCI_FUNC(dev_fn), old_base, confreg); + + pdev = pci_find_slot (bus, dev_fn); + if (!pdev) { + printk(KERN_ERR "cannot find slot\n"); + /* XXX handle error */ + } + + /* Enable response in memory space, bus mastering, + use memory write and invalidate */ + pci_write_config_word (pdev, PCI_COMMAND, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + PCI_COMMAND_INVALIDATE); + /* Set PCI Cache line size & latency timer */ + pci_write_config_byte (pdev, PCI_CACHE_LINE_SIZE, 0x8); + pci_write_config_byte (pdev, PCI_LATENCY_TIMER, 0x40); + + /* Set the new base address */ + pci_write_config_dword (pdev, confreg, new_base); + + planb_regs = (volatile struct planb_registers *) + ioremap (new_base, 0x400); + pb->planb_base = planb_regs; + pb->planb_base_phys = (struct planb_registers *)new_base; + pb->irq = irq; + + return planb_num; +} + +static void release_planb(void) +{ + int i; + struct planb *pb; + + for (i=0;i<planb_num; i++) + { + pb=&planbs[i]; + + /* stop and flash DMAs unconditionally */ + planb_dbdma_stop(&pb->planb_base->ch2); + planb_dbdma_stop(&pb->planb_base->ch1); + + /* clear and free interrupts */ + pb->intr_mask = PLANB_CLR_IRQ; + out_le32 (&pb->planb_base->intr_stat, PLANB_CLR_IRQ); + free_irq(pb->irq, pb); + + /* make sure all allocated memory are freed */ + planb_prepare_close(pb); + + printk(KERN_INFO "PlanB: unregistering with v4l\n"); + video_unregister_device(&pb->video_dev); + + /* note that iounmap() does nothing on the PPC right now */ + iounmap ((void *)pb->planb_base); + } +} + +#ifdef MODULE + +int init_module(void) +{ +#else +int __init init_planbs(struct video_init *unused) +{ +#endif + int i; + + if (find_planb()<=0) + return -EIO; + + for (i=0; i<planb_num; i++) { + if (init_planb(&planbs[i])<0) { + printk(KERN_ERR "PlanB: error registering device %d" + " with v4l\n", i); + release_planb(); + return -EIO; + } + printk(KERN_INFO "PlanB: registered device %d with v4l\n", i); + } + return 0; +} + +#ifdef MODULE + +void cleanup_module(void) +{ + release_planb(); +} + +#endif diff --git a/drivers/media/video/planb.h b/drivers/media/video/planb.h new file mode 100644 index 000000000..98d5697c9 --- /dev/null +++ b/drivers/media/video/planb.h @@ -0,0 +1,232 @@ +/* + planb - PlanB frame grabber driver + + PlanB is used in the 7x00/8x00 series of PowerMacintosh + Computers as video input DMA controller. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + Based largely on the bttv driver by Ralph Metzler (rjkm@thp.uni-koeln.de) + + Additional debugging and coding by Takashi Oe (toe@unlserve.unl.edu) + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* $Id: planb.h,v 1.13 1999/05/03 19:28:56 mlan Exp $ */ + +#ifndef _PLANB_H_ +#define _PLANB_H_ + +#ifdef __KERNEL__ +#include <asm/dbdma.h> +#include "saa7196.h" +#endif /* __KERNEL__ */ + +#define PLANB_DEVICE_NAME "Apple PlanB Video-In" +#define PLANB_REV "1.0" + +#ifdef __KERNEL__ +//#define PLANB_GSCANLINE /* use this if apps have the notion of */ + /* grab buffer scanline */ +/* This should be safe for both PAL and NTSC */ +#define PLANB_MAXPIXELS 768 +#define PLANB_MAXLINES 576 +#define PLANB_NTSC_MAXLINES 480 + +/* Uncomment your preferred norm ;-) */ +#define PLANB_DEF_NORM VIDEO_MODE_PAL +//#define PLANB_DEF_NORM VIDEO_MODE_NTSC +//#define PLANB_DEF_NORM VIDEO_MODE_SECAM + +/* fields settings */ +#define PLANB_GRAY 0x1 /* 8-bit mono? */ +#define PLANB_COLOUR15 0x2 /* 16-bit mode */ +#define PLANB_COLOUR32 0x4 /* 32-bit mode */ +#define PLANB_CLIPMASK 0x8 /* hardware clipmasking */ + +/* misc. flags for PlanB DMA operation */ +#define CH_SYNC 0x1 /* synchronize channels (set by ch1; + cleared by ch2) */ +#define FIELD_SYNC 0x2 /* used for the start of each field + (0 -> 1 -> 0 for ch1; 0 -> 1 for ch2) */ +#define EVEN_FIELD 0x0 /* even field is detected if unset */ +#define DMA_ABORT 0x2 /* error or just out of sync if set */ +#define ODD_FIELD 0x4 /* odd field is detected if set */ + +/* for capture operations */ +#define MAX_GBUFFERS 2 +/* note PLANB_MAX_FBUF must be divisible by PAGE_SIZE */ +#ifdef PLANB_GSCANLINE +#define PLANB_MAX_FBUF 0x240000 /* 576 * 1024 * 4 */ +#define TAB_FACTOR (1) +#else +#define PLANB_MAX_FBUF 0x1b0000 /* 576 * 768 * 4 */ +#define TAB_FACTOR (2) +#endif +#endif /* __KERNEL__ */ + +struct planb_saa_regs { + unsigned char addr; + unsigned char val; +}; + +struct planb_stat_regs { + unsigned int ch1_stat; + unsigned int ch2_stat; + unsigned char saa_stat0; + unsigned char saa_stat1; +}; + +struct planb_any_regs { + unsigned int offset; + unsigned int bytes; + unsigned char data[128]; +}; + +/* planb private ioctls */ +#define PLANBIOCGSAAREGS _IOWR('v', BASE_VIDIOCPRIVATE, struct planb_saa_regs) /* Read a saa7196 reg value */ +#define PLANBIOCSSAAREGS _IOW('v', BASE_VIDIOCPRIVATE + 1, struct planb_saa_regs) /* Set a saa7196 reg value */ +#define PLANBIOCGSTAT _IOR('v', BASE_VIDIOCPRIVATE + 2, struct planb_stat_regs) /* Read planb status */ +#define PLANB_TV_MODE 1 +#define PLANB_VTR_MODE 2 +#define PLANBIOCGMODE _IOR('v', BASE_VIDIOCPRIVATE + 3, int) /* Get TV/VTR mode */ +#define PLANBIOCSMODE _IOW('v', BASE_VIDIOCPRIVATE + 4, int) /* Set TV/VTR mode */ + +#ifdef PLANB_GSCANLINE +#define PLANBG_GRAB_BPL _IOR('v', BASE_VIDIOCPRIVATE + 5, int) /* # of bytes per scanline in grab buffer */ +#endif + +/* call wake_up_interruptible() with appropriate actions */ +#define PLANB_INTR_DEBUG _IOW('v', BASE_VIDIOCPRIVATE + 20, int) +/* investigate which reg does what */ +#define PLANB_INV_REGS _IOWR('v', BASE_VIDIOCPRIVATE + 21, struct planb_any_regs) + +#ifdef __KERNEL__ + +/* Potentially useful macros */ +#define PLANB_SET(x) ((x) << 16 | (x)) +#define PLANB_CLR(x) ((x) << 16) + +/* This represents the physical register layout */ +struct planb_registers { + volatile struct dbdma_regs ch1; /* 0x00: video in */ + volatile unsigned int even; /* 0x40: even field setting */ + volatile unsigned int odd; /* 0x44; odd field setting */ + unsigned int pad1[14]; /* empty? */ + volatile struct dbdma_regs ch2; /* 0x80: clipmask out */ + unsigned int pad2[16]; /* 0xc0: empty? */ + volatile unsigned int reg3; /* 0x100: ???? */ + volatile unsigned int intr_stat; /* 0x104: irq status */ +#define PLANB_CLR_IRQ 0x00 /* clear Plan B interrupt */ +#define PLANB_GEN_IRQ 0x01 /* assert Plan B interrupt */ +#define PLANB_FRM_IRQ 0x0100 /* end of frame */ + unsigned int pad3[1]; /* empty? */ + volatile unsigned int reg5; /* 0x10c: ??? */ + unsigned int pad4[60]; /* empty? */ + volatile unsigned char saa_addr; /* 0x200: SAA subadr */ + char pad5[3]; + volatile unsigned char saa_regval; /* SAA7196 write reg. val */ + char pad6[3]; + volatile unsigned char saa_status; /* SAA7196 status byte */ + /* There is more unused stuff here */ +}; + +struct planb_window { + int x, y; + ushort width, height; + ushort bpp, bpl, depth, pad; + ushort swidth, sheight; + int norm; + int interlace; + u32 color_fmt; + int chromakey; + int mode; /* used to switch between TV/VTR modes */ +}; + +struct planb_suspend { + int overlay; + int frame; + struct dbdma_cmd cmd; +}; + +struct planb { + struct video_device video_dev; + struct video_picture picture; /* Current picture params */ + struct video_audio audio_dev; /* Current audio params */ + + volatile struct planb_registers *planb_base; /* virt base of planb */ + struct planb_registers *planb_base_phys; /* phys base of planb */ + void *priv_space; /* Org. alloc. mem for kfree */ + int user; + unsigned int tab_size; + int maxlines; + int lock; + wait_queue_head_t lockq; + unsigned int irq; /* interrupt number */ + volatile unsigned int intr_mask; + + int overlay; /* overlay running? */ + struct planb_window win; + unsigned long frame_buffer_phys; /* We need phys for DMA */ + int offset; /* offset of pixel 1 */ + volatile struct dbdma_cmd *ch1_cmd; /* Video In DMA cmd buffer */ + volatile struct dbdma_cmd *ch2_cmd; /* Clip Out DMA cmd buffer */ + volatile struct dbdma_cmd *overlay_last1; + volatile struct dbdma_cmd *overlay_last2; + unsigned long ch1_cmd_phys; + volatile unsigned char *mask; /* Clipmask buffer */ + int suspend; + wait_queue_head_t suspendq; + struct planb_suspend suspended; + int cmd_buff_inited; /* cmd buffer inited? */ + + int grabbing; + unsigned int gcount; + wait_queue_head_t capq; + int last_fr; + int prev_last_fr; + unsigned char **rawbuf; + int rawbuf_size; + int gbuf_idx[MAX_GBUFFERS]; + volatile struct dbdma_cmd *cap_cmd[MAX_GBUFFERS]; + volatile struct dbdma_cmd *last_cmd[MAX_GBUFFERS]; + volatile struct dbdma_cmd *pre_cmd[MAX_GBUFFERS]; + int need_pre_capture[MAX_GBUFFERS]; +#define PLANB_DUMMY 40 /* # of command buf's allocated for pre-capture seq. */ + int gwidth[MAX_GBUFFERS], gheight[MAX_GBUFFERS]; + unsigned int gfmt[MAX_GBUFFERS]; + int gnorm_switch[MAX_GBUFFERS]; + volatile unsigned int *frame_stat; +#define GBUFFER_UNUSED 0x00U +#define GBUFFER_GRABBING 0x01U +#define GBUFFER_DONE 0x02U +#ifdef PLANB_GSCANLINE + int gbytes_per_line; +#else +#define MAX_LNUM 431 /* change this if PLANB_MAXLINES or */ + /* PLANB_MAXPIXELS changes */ + int l_fr_addr_idx[MAX_GBUFFERS]; + unsigned char *l_to_addr[MAX_GBUFFERS][MAX_LNUM]; + int l_to_next_idx[MAX_GBUFFERS][MAX_LNUM]; + int l_to_next_size[MAX_GBUFFERS][MAX_LNUM]; + int lsize[MAX_GBUFFERS], lnum[MAX_GBUFFERS]; +#endif +}; + +#endif /* __KERNEL__ */ + +#endif /* _PLANB_H_ */ diff --git a/drivers/media/video/pms.c b/drivers/media/video/pms.c new file mode 100644 index 000000000..1e50880a0 --- /dev/null +++ b/drivers/media/video/pms.c @@ -0,0 +1,1074 @@ +/* + * 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@redhat.com unless the + * userspace driver also doesnt work for you... + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <asm/io.h> +#include <linux/sched.h> +#include <linux/videodev.h> +#include <linux/version.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; +}; + +struct i2c_info +{ + u8 slave; + u8 sub; + u8 data; + u8 hits; +}; + +static int i2c_count = 0; +static struct i2c_info i2cinfo[64]; + +static int decoder = PHILIPS2; +static int standard = 0; /* 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; + + + +extern __inline__ void mvv_write(u8 index, u8 value) +{ + outw(index|(value<<8), io_port); +} + +extern __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 *buf, int rgb555, int count) +{ + int y; + int dw = 2*dev->width; + u32 src = mem_base; + + 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++ ) + { + isa_writeb(0, src); /* synchronisiert neue Zeile */ + + /* + * This is in truth a fifo, be very careful as if you + * forgot this odd things will occur 8) + */ + + isa_memcpy_fromio(tmp, src, 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; + copy_to_user(buf, tmp+32, dt); + buf += dt; + len += dt; + } + } + return len; +} + + +/* + * Video4linux interfacing + */ + +static int pms_open(struct video_device *dev, int flags) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static void pms_close(struct video_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +static int pms_init_done(struct video_device *dev) +{ + return 0; +} + +static long pms_write(struct video_device *v, const char *buf, unsigned long count, int noblock) +{ + return -EINVAL; +} + +static int pms_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct pms_device *pd=(struct pms_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + 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; + if(copy_to_user(arg, &b,sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + 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; + } + if(copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: + { + int v; + if(copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + if(v<0 || v>3) + return -EINVAL; + pms_videosource(v&1); + pms_vcrinput(v>>1); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + 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; + } + if(copy_to_user(arg,&v,sizeof(v))!=0) + return -EFAULT; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + 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: + return -EINVAL; + } + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p=pd->picture; + if(copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + if(copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + 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. + */ + + pms_brightness(p.brightness>>8); + pms_hue(p.hue>>8); + pms_colour(p.colour>>8); + pms_contrast(p.contrast>>8); + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + if(copy_from_user(&vw, arg,sizeof(vw))) + return -EFAULT; + 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; + pms_resolution(pd->width, pd->height); + /* Ok we figured out what to use from our wide choice */ + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x=0; + vw.y=0; + vw.width=pd->width; + vw.height=pd->height; + vw.chromakey=0; + vw.flags=0; + if(copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + return -EINVAL; + case VIDIOCGFBUF: + return -EINVAL; + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCKEY: + return 0; + case VIDIOCGFREQ: + return -EINVAL; + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + return -EINVAL; + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static long pms_read(struct video_device *v, char *buf, unsigned long count, int noblock) +{ + struct pms_device *pd=(struct pms_device *)v; + int len; + + /* FIXME: semaphore this */ + len=pms_capture(pd, buf, (pd->picture.depth==16)?0:1,count); + return len; +} + + +struct video_device pms_template= +{ + "Mediavision PMS", + VID_TYPE_CAPTURE, + VID_HARDWARE_PMS, + pms_open, + pms_close, + pms_read, + pms_write, + NULL, /* FIXME - we can use POLL on this board with the irq */ + pms_ioctl, + NULL, + pms_init_done, + NULL, + 0, + 0 +}; + +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 + }; + + if(check_region(0x9A01,1)) + { + printk(KERN_WARNING "mediavision: unable to detect: 0x9A01 in use.\n"); + return -EBUSY; + } + if(check_region(io_port,3)) + { + printk(KERN_WARNING "mediavision: I/O port %d in use.\n", io_port); + 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) + return -ENODEV; + + /* + * Ok we have a PMS of some sort + */ + + request_region(io_port,3, "Mediavision PMS"); + request_region(0x9A01, 1, "Mediavision PMS config"); + + 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 + */ + +static int __init init_pms_cards(void) +{ + printk(KERN_INFO "Mediavision Pro Movie Studio driver 0.02\n"); + + 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)); + 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); +} + +MODULE_PARM(io_port,"i"); +MODULE_PARM(mem_base,"i"); + +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); +} + +module_init(init_pms_cards); +module_exit(cleanup_pms_module); + diff --git a/drivers/media/video/saa5249.c b/drivers/media/video/saa5249.c new file mode 100644 index 000000000..1213e2ee5 --- /dev/null +++ b/drivers/media/video/saa5249.c @@ -0,0 +1,683 @@ +/* + * Cleaned up to use existing videodev interface and allow the idea + * of multiple teletext decoders on the video4linux iface. Changed i2c + * to cover addressing clashes on device busses. It's also rebuilt so + * you can add arbitary multiple teletext devices to Linux video4linux + * now (well 32 anyway). + * + * Alan Cox <Alan.Cox@linux.org> + * + * The original driver was heavily modified to match the i2c interface + * It was truncated to use the WinTV boards, too. + * + * Copyright (c) 1998 Richard Guenther <richard.guenther@student.uni-tuebingen.de> + * + * $Id: saa5249.c,v 1.1 1998/03/30 22:23:23 alan Exp $ + * + * Derived From + * + * vtx.c: + * This is a loadable character-device-driver for videotext-interfaces + * (aka teletext). Please check the Makefile/README for a list of supported + * interfaces. + * + * Copyright (c) 1994-97 Martin Buck <martin-2.buck@student.uni-ulm.de> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <stdarg.h> +#include <linux/i2c.h> +#include <linux/videotext.h> +#include <linux/videodev.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#define VTX_VER_MAJ 1 +#define VTX_VER_MIN 7 + + + +#define NUM_DAUS 4 +#define NUM_BUFS 8 +#define IF_NAME "SAA5249" + +static const int disp_modes[8][3] = +{ + { 0x46, 0x03, 0x03 }, /* DISPOFF */ + { 0x46, 0xcc, 0xcc }, /* DISPNORM */ + { 0x44, 0x0f, 0x0f }, /* DISPTRANS */ + { 0x46, 0xcc, 0x46 }, /* DISPINS */ + { 0x44, 0x03, 0x03 }, /* DISPOFF, interlaced */ + { 0x44, 0xcc, 0xcc }, /* DISPNORM, interlaced */ + { 0x44, 0x0f, 0x0f }, /* DISPTRANS, interlaced */ + { 0x44, 0xcc, 0x46 } /* DISPINS, interlaced */ +}; + + + +#define PAGE_WAIT (300*HZ/1000) /* Time between requesting page and */ + /* checking status bits */ +#define PGBUF_EXPIRE (15*HZ) /* Time to wait before retransmitting */ + /* page regardless of infobits */ +typedef struct { + u8 pgbuf[VTX_VIRTUALSIZE]; /* Page-buffer */ + u8 laststat[10]; /* Last value of infobits for DAU */ + u8 sregs[7]; /* Page-request registers */ + unsigned long expire; /* Time when page will be expired */ + unsigned clrfound : 1; /* VTXIOCCLRFOUND has been called */ + unsigned stopped : 1; /* VTXIOCSTOPDAU has been called */ +} vdau_t; + +struct saa5249_device +{ + vdau_t vdau[NUM_DAUS]; /* Data for virtual DAUs (the 5249 only has one */ + /* real DAU, so we have to simulate some more) */ + int vtx_use_count; + int is_searching[NUM_DAUS]; + int disp_mode; + int virtual_mode; + struct i2c_client *client; +}; + + +#define CCTWR 34 /* I²C write/read-address of vtx-chip */ +#define CCTRD 35 +#define NOACK_REPEAT 10 /* Retry access this many times on failure */ +#define CLEAR_DELAY (HZ/20) /* Time required to clear a page */ +#define READY_TIMEOUT (30*HZ/1000) /* Time to wait for ready signal of I²C-bus interface */ +#define INIT_DELAY 500 /* Time in usec to wait at initialization of CEA interface */ +#define START_DELAY 10 /* Time in usec to wait before starting write-cycle (CEA) */ + +#define VTX_DEV_MINOR 0 + +/* General defines and debugging support */ + +#ifndef FALSE +#define FALSE 0 +#define TRUE 1 +#endif +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define RESCHED \ + do { \ + if (current->need_resched) \ + schedule(); \ + } while (0) + +static struct video_device saa_template; /* Declared near bottom */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {34>>1,I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +static struct i2c_client client_template; + +static int saa5249_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind) +{ + int pgbuf; + int err; + struct i2c_client *client; + struct video_device *vd; + struct saa5249_device *t; + + printk(KERN_INFO "saa5249: teletext chip found.\n"); + client=kmalloc(sizeof(*client), GFP_KERNEL); + if(client==NULL) + return -ENOMEM; + client_template.adapter = adap; + client_template.addr = addr; + memcpy(client, &client_template, sizeof(*client)); + t = kmalloc(sizeof(*t), GFP_KERNEL); + if(t==NULL) + { + kfree(client); + return -ENOMEM; + } + memset(t, 0, sizeof(*t)); + strcpy(client->name, IF_NAME); + + /* + * Now create a video4linux device + */ + + client->data = vd=(struct video_device *)kmalloc(sizeof(struct video_device), GFP_KERNEL); + if(vd==NULL) + { + kfree(t); + kfree(client); + return -ENOMEM; + } + memcpy(vd, &saa_template, sizeof(*vd)); + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs)); + memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat)); + t->vdau[pgbuf].expire = 0; + t->vdau[pgbuf].clrfound = TRUE; + t->vdau[pgbuf].stopped = TRUE; + t->is_searching[pgbuf] = FALSE; + } + vd->priv=t; + + /* + * Register it + */ + + if((err=video_register_device(vd, VFL_TYPE_VTX))<0) + { + kfree(t); + kfree(vd); + kfree(client); + return err; + } + t->client = client; + i2c_attach_client(client); + MOD_INC_USE_COUNT; + return 0; +} + +/* + * We do most of the hard work when we become a device on the i2c. + */ + +static int saa5249_probe(struct i2c_adapter *adap) +{ + /* Only attach these chips to the BT848 bus for now */ + + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + { + return i2c_probe(adap, &addr_data, saa5249_attach); + } + return 0; +} + +static int saa5249_detach(struct i2c_client *client) +{ + struct video_device *vd=client->data; + i2c_detach_client(client); + video_unregister_device(vd); + kfree(vd->priv); + kfree(vd); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int saa5249_command(struct i2c_client *device, + unsigned int cmd, void *arg) +{ + return -EINVAL; +} + +/* new I2C driver support */ + +static struct i2c_driver i2c_driver_videotext = +{ + IF_NAME, /* name */ + I2C_DRIVERID_SAA5249, /* in i2c.h */ + I2C_DF_NOTIFY, + saa5249_probe, + saa5249_detach, + saa5249_command +}; + +static struct i2c_client client_template = { + "(unset)", + -1, + 0, + 0, + NULL, + &i2c_driver_videotext +}; + +/* + * Wait the given number of jiffies (10ms). This calls the scheduler, so the actual + * delay may be longer. + */ + +static void jdelay(unsigned long delay) +{ + sigset_t oldblocked = current->blocked; + + spin_lock_irq(¤t->sigmask_lock); + sigfillset(¤t->blocked); + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(delay); + + spin_lock_irq(¤t->sigmask_lock); + current->blocked = oldblocked; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); +} + + +/* + * I2C interfaces + */ + +static int i2c_sendbuf(struct saa5249_device *t, int reg, int count, u8 *data) +{ + char buf[64]; + + buf[0] = reg; + memcpy(buf+1, data, count); + + if(i2c_master_send(t->client, buf, count+1)==count+1) + return 0; + return -1; +} + +static int i2c_senddata(struct saa5249_device *t, ...) +{ + unsigned char buf[64]; + int v; + int ct=0; + va_list argp; + va_start(argp,t); + + while((v=va_arg(argp,int))!=-1) + buf[ct++]=v; + return i2c_sendbuf(t, buf[0], ct-1, buf+1); +} + +/* Get count number of bytes from I²C-device at address adr, store them in buf. Start & stop + * handshaking is done by this routine, ack will be sent after the last byte to inhibit further + * sending of data. If uaccess is TRUE, data is written to user-space with put_user. + * Returns -1 if I²C-device didn't send acknowledge, 0 otherwise + */ + +static int i2c_getdata(struct saa5249_device *t, int count, u8 *buf) +{ + if(i2c_master_recv(t->client, buf, count)!=count) + return -1; + return 0; +} + + +/* + * Standard character-device-driver functions + */ + +static int saa5249_ioctl(struct video_device *vd, unsigned int cmd, void *arg) +{ + struct saa5249_device *t=vd->priv; + static int virtual_mode = FALSE; + + switch(cmd) + { + case VTXIOCGETINFO: + { + vtx_info_t info; + info.version_major = VTX_VER_MAJ; + info.version_minor = VTX_VER_MIN; + info.numpages = NUM_DAUS; + /*info.cct_type = CCT_TYPE;*/ + if(copy_to_user((void*)arg, &info, sizeof(vtx_info_t))) + return -EFAULT; + return 0; + } + + case VTXIOCCLRPAGE: + { + vtx_pagereq_t req; + + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS) + return -EINVAL; + memset(t->vdau[req.pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + t->vdau[req.pgbuf].clrfound = TRUE; + return 0; + } + + case VTXIOCCLRFOUND: + { + vtx_pagereq_t req; + + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req.pgbuf].clrfound = TRUE; + return 0; + } + + case VTXIOCPAGEREQ: + { + vtx_pagereq_t req; + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (!(req.pagemask & PGMASK_PAGE)) + req.page = 0; + if (!(req.pagemask & PGMASK_HOUR)) + req.hour = 0; + if (!(req.pagemask & PGMASK_MINUTE)) + req.minute = 0; + if (req.page < 0 || req.page > 0x8ff) /* 7FF ?? */ + return -EINVAL; + req.page &= 0x7ff; + if (req.hour < 0 || req.hour > 0x3f || req.minute < 0 || req.minute > 0x7f || + req.pagemask < 0 || req.pagemask >= PGMASK_MAX || req.pgbuf < 0 || req.pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req.pgbuf].sregs[0] = (req.pagemask & PG_HUND ? 0x10 : 0) | (req.page / 0x100); + t->vdau[req.pgbuf].sregs[1] = (req.pagemask & PG_TEN ? 0x10 : 0) | ((req.page / 0x10) & 0xf); + t->vdau[req.pgbuf].sregs[2] = (req.pagemask & PG_UNIT ? 0x10 : 0) | (req.page & 0xf); + t->vdau[req.pgbuf].sregs[3] = (req.pagemask & HR_TEN ? 0x10 : 0) | (req.hour / 0x10); + t->vdau[req.pgbuf].sregs[4] = (req.pagemask & HR_UNIT ? 0x10 : 0) | (req.hour & 0xf); + t->vdau[req.pgbuf].sregs[5] = (req.pagemask & MIN_TEN ? 0x10 : 0) | (req.minute / 0x10); + t->vdau[req.pgbuf].sregs[6] = (req.pagemask & MIN_UNIT ? 0x10 : 0) | (req.minute & 0xf); + t->vdau[req.pgbuf].stopped = FALSE; + t->vdau[req.pgbuf].clrfound = TRUE; + t->is_searching[req.pgbuf] = TRUE; + return 0; + } + + case VTXIOCGETSTAT: + { + vtx_pagereq_t req; + u8 infobits[10]; + vtx_pageinfo_t info; + int a; + + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS) + return -EINVAL; + if (!t->vdau[req.pgbuf].stopped) + { + if (i2c_senddata(t, 2, 0, -1) || + i2c_sendbuf(t, 3, sizeof(t->vdau[0].sregs), t->vdau[req.pgbuf].sregs) || + i2c_senddata(t, 8, 0, 25, 0, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', -1) || + i2c_senddata(t, 2, 0, t->vdau[req.pgbuf].sregs[0] | 8, -1) || + i2c_senddata(t, 8, 0, 25, 0, -1)) + return -EIO; + jdelay(PAGE_WAIT); + if (i2c_getdata(t, 10, infobits)) + return -EIO; + + if (!(infobits[8] & 0x10) && !(infobits[7] & 0xf0) && /* check FOUND-bit */ + (memcmp(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits)) || + time_after_eq(jiffies, t->vdau[req.pgbuf].expire))) + { /* check if new page arrived */ + if (i2c_senddata(t, 8, 0, 0, 0, -1) || + i2c_getdata(t, VTX_PAGESIZE, t->vdau[req.pgbuf].pgbuf)) + return -EIO; + t->vdau[req.pgbuf].expire = jiffies + PGBUF_EXPIRE; + memset(t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE, ' ', VTX_VIRTUALSIZE - VTX_PAGESIZE); + if (t->virtual_mode) + { + /* Packet X/24 */ + if (i2c_senddata(t, 8, 0, 0x20, 0, -1) || + i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 20 * 40)) + return -EIO; + /* Packet X/27/0 */ + if (i2c_senddata(t, 8, 0, 0x21, 0, -1) || + i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 16 * 40)) + return -EIO; + /* Packet 8/30/0...8/30/15 + * FIXME: AFAIK, the 5249 does hamming-decoding for some bytes in packet 8/30, + * so we should undo this here. + */ + if (i2c_senddata(t, 8, 0, 0x22, 0, -1) || + i2c_getdata(t, 40, t->vdau[req.pgbuf].pgbuf + VTX_PAGESIZE + 23 * 40)) + return -EIO; + } + t->vdau[req.pgbuf].clrfound = FALSE; + memcpy(t->vdau[req.pgbuf].laststat, infobits, sizeof(infobits)); + } + else + { + memcpy(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits)); + } + } + else + { + memcpy(infobits, t->vdau[req.pgbuf].laststat, sizeof(infobits)); + } + + info.pagenum = ((infobits[8] << 8) & 0x700) | ((infobits[1] << 4) & 0xf0) | (infobits[0] & 0x0f); + if (info.pagenum < 0x100) + info.pagenum += 0x800; + info.hour = ((infobits[5] << 4) & 0x30) | (infobits[4] & 0x0f); + info.minute = ((infobits[3] << 4) & 0x70) | (infobits[2] & 0x0f); + info.charset = ((infobits[7] >> 1) & 7); + info.delete = !!(infobits[3] & 8); + info.headline = !!(infobits[5] & 4); + info.subtitle = !!(infobits[5] & 8); + info.supp_header = !!(infobits[6] & 1); + info.update = !!(infobits[6] & 2); + info.inter_seq = !!(infobits[6] & 4); + info.dis_disp = !!(infobits[6] & 8); + info.serial = !!(infobits[7] & 1); + info.notfound = !!(infobits[8] & 0x10); + info.pblf = !!(infobits[9] & 0x20); + info.hamming = 0; + for (a = 0; a <= 7; a++) + { + if (infobits[a] & 0xf0) + { + info.hamming = 1; + break; + } + } + if (t->vdau[req.pgbuf].clrfound) + info.notfound = 1; + if(copy_to_user(req.buffer, &info, sizeof(vtx_pageinfo_t))) + return -EFAULT; + if (!info.hamming && !info.notfound) + { + t->is_searching[req.pgbuf] = FALSE; + } + return 0; + } + + case VTXIOCGETPAGE: + { + vtx_pagereq_t req; + int start, end; + + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS || req.start < 0 || + req.start > req.end || req.end >= (virtual_mode ? VTX_VIRTUALSIZE : VTX_PAGESIZE)) + return -EINVAL; + if(copy_to_user(req.buffer, &t->vdau[req.pgbuf].pgbuf[req.start], req.end - req.start + 1)) + return -EFAULT; + + /* + * Always read the time directly from SAA5249 + */ + + if (req.start <= 39 && req.end >= 32) + { + int len; + char buf[16]; + start = MAX(req.start, 32); + end = MIN(req.end, 39); + len=end-start+1; + if (i2c_senddata(t, 8, 0, 0, start, -1) || + i2c_getdata(t, len, buf)) + return -EIO; + if(copy_to_user(req.buffer+start-req.start, buf, len)) + return -EFAULT; + } + /* Insert the current header if DAU is still searching for a page */ + if (req.start <= 31 && req.end >= 7 && t->is_searching[req.pgbuf]) + { + char buf[32]; + int len; + start = MAX(req.start, 7); + end = MIN(req.end, 31); + len=end-start+1; + if (i2c_senddata(t, 8, 0, 0, start, -1) || + i2c_getdata(t, len, buf)) + return -EIO; + if(copy_to_user(req.buffer+start-req.start, buf, len)) + return -EFAULT; + } + return 0; + } + + case VTXIOCSTOPDAU: + { + vtx_pagereq_t req; + + if(copy_from_user(&req, (void*)arg, sizeof(vtx_pagereq_t))) + return -EFAULT; + if (req.pgbuf < 0 || req.pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req.pgbuf].stopped = TRUE; + t->is_searching[req.pgbuf] = FALSE; + return 0; + } + + case VTXIOCPUTPAGE: + case VTXIOCSETDISP: + case VTXIOCPUTSTAT: + return 0; + + case VTXIOCCLRCACHE: + { + if (i2c_senddata(t, 0, NUM_DAUS, 0, 8, -1) || i2c_senddata(t, 11, + ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', + ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', -1)) + return -EIO; + if (i2c_senddata(t, 3, 0x20, -1)) + return -EIO; + jdelay(10 * CLEAR_DELAY); /* I have no idea how long we have to wait here */ + return 0; + } + + case VTXIOCSETVIRT: + { + /* The SAA5249 has virtual-row reception turned on always */ + t->virtual_mode = (int)arg; + return 0; + } + } + return -EINVAL; +} + + +static int saa5249_open(struct video_device *vd, int nb) +{ + struct saa5249_device *t=vd->priv; + int pgbuf; + + if (t->client==NULL) + return -ENODEV; + + if (i2c_senddata(t, 0, 0, -1) || /* Select R11 */ + /* Turn off parity checks (we do this ourselves) */ + i2c_senddata(t, 1, disp_modes[t->disp_mode][0], 0, -1) || + /* Display TV-picture, no virtual rows */ + i2c_senddata(t, 4, NUM_DAUS, disp_modes[t->disp_mode][1], disp_modes[t->disp_mode][2], 7, -1)) /* Set display to page 4 */ + + { + return -EIO; + } + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->vdau[pgbuf].pgbuf, ' ', sizeof(t->vdau[0].pgbuf)); + memset(t->vdau[pgbuf].sregs, 0, sizeof(t->vdau[0].sregs)); + memset(t->vdau[pgbuf].laststat, 0, sizeof(t->vdau[0].laststat)); + t->vdau[pgbuf].expire = 0; + t->vdau[pgbuf].clrfound = TRUE; + t->vdau[pgbuf].stopped = TRUE; + t->is_searching[pgbuf] = FALSE; + } + t->virtual_mode=FALSE; + MOD_INC_USE_COUNT; + return 0; +} + + + +static void saa5249_release(struct video_device *vd) +{ + struct saa5249_device *t=vd->priv; + i2c_senddata(t, 1, 0x20, -1); /* Turn off CCT */ + i2c_senddata(t, 5, 3, 3, -1); /* Turn off TV-display */ + MOD_DEC_USE_COUNT; + return; +} + +static long saa5249_write(struct video_device *v, const char *buf, unsigned long l, int nb) +{ + return -EINVAL; +} + +static int __init init_saa_5249 (void) +{ + printk(KERN_INFO "SAA5249 driver (" IF_NAME " interface) for VideoText version %d.%d\n", + VTX_VER_MAJ, VTX_VER_MIN); + return i2c_add_driver(&i2c_driver_videotext); +} + +static void __exit cleanup_saa_5249 (void) +{ + i2c_del_driver(&i2c_driver_videotext); +} + +module_init(init_saa_5249); +module_exit(cleanup_saa_5249); + +static struct video_device saa_template = +{ + IF_NAME, + VID_TYPE_TELETEXT, /*| VID_TYPE_TUNER ?? */ + VID_HARDWARE_SAA5249, + saa5249_open, + saa5249_release, + NULL, /* read */ + saa5249_write, + NULL, /* poll */ + saa5249_ioctl, + /* the rest are null */ +}; + diff --git a/drivers/media/video/saa7110.c b/drivers/media/video/saa7110.c new file mode 100644 index 000000000..6146b84b9 --- /dev/null +++ b/drivers/media/video/saa7110.c @@ -0,0 +1,429 @@ +/* + saa7110 - Philips SAA7110(A) video decoder driver + + Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/i2c-old.h> +#include <linux/videodev.h> +#include "linux/video_decoder.h" + +#define DEBUG(x...) /* remove when no long debugging */ + +#define SAA7110_MAX_INPUT 9 /* 6 CVBS, 3 SVHS */ +#define SAA7110_MAX_OUTPUT 0 /* its a decoder only */ + +#define I2C_SAA7110 0x9C /* or 0x9E */ + +#define I2C_DELAY 10 /* 10 us or 100khz */ + +struct saa7110 { + struct i2c_bus *bus; + int addr; + unsigned char reg[36]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +/* ----------------------------------------------------------------------- */ +/* I2C support functions */ +/* ----------------------------------------------------------------------- */ +static +int saa7110_write(struct saa7110 *decoder, unsigned char subaddr, unsigned char data) +{ + int ack; + + LOCK_I2C_BUS(decoder->bus); + i2c_start(decoder->bus); + i2c_sendbyte(decoder->bus, decoder->addr, I2C_DELAY); + i2c_sendbyte(decoder->bus, subaddr, I2C_DELAY); + ack = i2c_sendbyte(decoder->bus, data, I2C_DELAY); + i2c_stop(decoder->bus); + decoder->reg[subaddr] = data; + UNLOCK_I2C_BUS(decoder->bus); + return ack; +} + +static +int saa7110_write_block(struct saa7110* decoder, unsigned const char *data, unsigned int len) +{ + unsigned subaddr = *data; + + LOCK_I2C_BUS(decoder->bus); + i2c_start(decoder->bus); + i2c_sendbyte(decoder->bus,decoder->addr,I2C_DELAY); + while (len-- > 0) { + if (i2c_sendbyte(decoder->bus,*data,0)) { + i2c_stop(decoder->bus); + return -EAGAIN; + } + decoder->reg[subaddr++] = *data++; + } + i2c_stop(decoder->bus); + UNLOCK_I2C_BUS(decoder->bus); + + return 0; +} + +static +int saa7110_read(struct saa7110* decoder) +{ + int data; + + LOCK_I2C_BUS(decoder->bus); + i2c_start(decoder->bus); + i2c_sendbyte(decoder->bus, decoder->addr, I2C_DELAY); + i2c_start(decoder->bus); + i2c_sendbyte(decoder->bus, decoder->addr | 1, I2C_DELAY); + data = i2c_readbyte(decoder->bus, 1); + i2c_stop(decoder->bus); + UNLOCK_I2C_BUS(decoder->bus); + return data; +} + +/* ----------------------------------------------------------------------- */ +/* SAA7110 functions */ +/* ----------------------------------------------------------------------- */ +static +int saa7110_selmux(struct i2c_device *device, int chan) +{ +static const unsigned char modes[9][8] = { +/* mode 0 */ { 0x00, 0xD9, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 }, +/* mode 1 */ { 0x00, 0xD8, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 }, +/* mode 2 */ { 0x00, 0xBA, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 }, +/* mode 3 */ { 0x00, 0xB8, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 }, +/* mode 4 */ { 0x00, 0x7C, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 }, +/* mode 5 */ { 0x00, 0x78, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 }, +/* mode 6 */ { 0x80, 0x59, 0x17, 0x42, 0xA3, 0x44, 0x75, 0x12 }, +/* mode 7 */ { 0x80, 0x9A, 0x17, 0xB1, 0x13, 0x60, 0xB5, 0x14 }, +/* mode 8 */ { 0x80, 0x3C, 0x27, 0xC1, 0x23, 0x44, 0x75, 0x21 } }; + struct saa7110* decoder = device->data; + const unsigned char* ptr = modes[chan]; + + saa7110_write(decoder,0x06,ptr[0]); /* Luminance control */ + saa7110_write(decoder,0x20,ptr[1]); /* Analog Control #1 */ + saa7110_write(decoder,0x21,ptr[2]); /* Analog Control #2 */ + saa7110_write(decoder,0x22,ptr[3]); /* Mixer Control #1 */ + saa7110_write(decoder,0x2C,ptr[4]); /* Mixer Control #2 */ + saa7110_write(decoder,0x30,ptr[5]); /* ADCs gain control */ + saa7110_write(decoder,0x31,ptr[6]); /* Mixer Control #3 */ + saa7110_write(decoder,0x21,ptr[7]); /* Analog Control #2 */ + + return 0; +} + +static +int determine_norm(struct i2c_device* dev) +{ + struct saa7110* decoder = dev->data; + int status; + + /* mode changed, start automatic detection */ + status = saa7110_read(decoder); + if ((status & 3) == 0) { + saa7110_write(decoder,0x06,0x80); + if (status & 0x20) { + DEBUG(printk(KERN_INFO "%s: norm=bw60\n",dev->name)); + saa7110_write(decoder,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + DEBUG(printk(KERN_INFO "%s: norm=bw50\n",dev->name)); + saa7110_write(decoder,0x2E,0x9A); + return VIDEO_MODE_PAL; + } + + saa7110_write(decoder,0x06,0x00); + if (status & 0x20) { /* 60Hz */ + DEBUG(printk(KERN_INFO "%s: norm=ntsc\n",dev->name)); + saa7110_write(decoder,0x0D,0x06); + saa7110_write(decoder,0x11,0x2C); + saa7110_write(decoder,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + + /* 50Hz -> PAL/SECAM */ + saa7110_write(decoder,0x0D,0x06); + saa7110_write(decoder,0x11,0x59); + saa7110_write(decoder,0x2E,0x9A); + + mdelay(150); /* pause 150 ms */ + + status = saa7110_read(decoder); + if ((status & 0x03) == 0x01) { + DEBUG(printk(KERN_INFO "%s: norm=secam\n",dev->name)); + saa7110_write(decoder,0x0D,0x07); + return VIDEO_MODE_SECAM; + } + DEBUG(printk(KERN_INFO "%s: norm=pal\n",dev->name)); + return VIDEO_MODE_PAL; +} + +static +int saa7110_attach(struct i2c_device *device) +{ +static const unsigned char initseq[] = { + 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF0, 0x00, 0x00, + 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x06, 0x18, 0x90, + 0x00, 0x2C, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA, + 0xF0, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xD9, 0x17, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F, + 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x81, 0x03, + 0x40, 0x75, 0x01, 0x8C, 0x03}; + struct saa7110* decoder; + int rv; + + device->data = decoder = kmalloc(sizeof(struct saa7110), GFP_KERNEL); + if (device->data == 0) + return -ENOMEM; + + MOD_INC_USE_COUNT; + + /* clear our private data */ + memset(decoder, 0, sizeof(struct saa7110)); + strcpy(device->name, "saa7110"); + decoder->bus = device->bus; + decoder->addr = device->addr; + decoder->norm = VIDEO_MODE_PAL; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + + rv = saa7110_write_block(decoder, initseq, sizeof(initseq)); + if (rv < 0) + printk(KERN_ERR "%s_attach: init status %d\n", device->name, rv); + else { + saa7110_write(decoder,0x21,0x16); + saa7110_write(decoder,0x0D,0x04); + DEBUG(printk(KERN_INFO "%s_attach: chip version %x\n", device->name, saa7110_read(decoder))); + saa7110_write(decoder,0x0D,0x06); + } + + /* setup and implicit mode 0 select has been performed */ + return 0; +} + +static +int saa7110_detach(struct i2c_device *device) +{ + struct saa7110* decoder = device->data; + + DEBUG(printk(KERN_INFO "%s_detach\n",device->name)); + + /* stop further output */ + saa7110_write(decoder,0x0E,0x00); + + kfree(device->data); + + MOD_DEC_USE_COUNT; + return 0; +} + +static +int saa7110_command(struct i2c_device *device, unsigned int cmd, void *arg) +{ + struct saa7110* decoder = device->data; + int v; + + switch (cmd) { + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *dc = arg; + dc->flags = VIDEO_DECODER_PAL + | VIDEO_DECODER_NTSC + | VIDEO_DECODER_SECAM + | VIDEO_DECODER_AUTO + | VIDEO_DECODER_CCIR; + dc->inputs = SAA7110_MAX_INPUT; + dc->outputs = SAA7110_MAX_OUTPUT; + } + break; + + case DECODER_GET_STATUS: + { + struct saa7110* decoder = device->data; + int status; + int res = 0; + + status = i2c_read(device->bus,device->addr|1); + if (status & 0x40) + res |= DECODER_STATUS_GOOD; + if (status & 0x03) + res |= DECODER_STATUS_COLOR; + + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + break; + } + *(int*)arg = res; + } + break; + + case DECODER_SET_NORM: + v = *(int*)arg; + if (decoder->norm != v) { + decoder->norm = v; + saa7110_write(decoder, 0x06, 0x00); + switch (v) { + case VIDEO_MODE_NTSC: + saa7110_write(decoder, 0x0D, 0x06); + saa7110_write(decoder, 0x11, 0x2C); + saa7110_write(decoder, 0x30, 0x81); +saa7110_write(decoder, 0x2A, 0xDF); + break; + case VIDEO_MODE_PAL: + saa7110_write(decoder, 0x0D, 0x06); + saa7110_write(decoder, 0x11, 0x59); + saa7110_write(decoder, 0x2E, 0x9A); + break; + case VIDEO_MODE_SECAM: + saa7110_write(decoder, 0x0D, 0x07); + saa7110_write(decoder, 0x11, 0x59); + saa7110_write(decoder, 0x2E, 0x9A); + break; + case VIDEO_MODE_AUTO: + *(int*)arg = determine_norm(device); + break; + default: + return -EPERM; + } + } + break; + + case DECODER_SET_INPUT: + v = *(int*)arg; + if (v<0 || v>SAA7110_MAX_INPUT) + return -EINVAL; + if (decoder->input != v) { + decoder->input = v; + saa7110_selmux(device, v); + } + break; + + case DECODER_SET_OUTPUT: + v = *(int*)arg; + /* not much choice of outputs */ + if (v != 0) + return -EINVAL; + break; + + case DECODER_ENABLE_OUTPUT: + v = *(int*)arg; + if (decoder->enable != v) { + decoder->enable = v; + saa7110_write(decoder,0x0E, v ? 0x18 : 0x00); + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7110_write(decoder, 0x19, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7110_write(decoder, 0x13, decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7110_write(decoder, 0x12, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7110_write(decoder, 0x07, (decoder->hue>>8)-128); + } + } + break; + + case DECODER_DUMP: + for (v=0; v<34; v+=16) { + int j; + DEBUG(printk(KERN_INFO "%s: %03x\n",device->name,v)); + for (j=0; j<16; j++) { + DEBUG(printk(KERN_INFO " %02x",decoder->reg[v+j])); + } + DEBUG(printk(KERN_INFO "\n")); + } + break; + + default: + DEBUG(printk(KERN_INFO "unknown saa7110_command??(%d)\n",cmd)); + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7110 = +{ + "saa7110", /* name */ + + I2C_DRIVERID_VIDEODECODER, /* in i2c.h */ + I2C_SAA7110, I2C_SAA7110+1, /* Addr range */ + + saa7110_attach, + saa7110_detach, + saa7110_command +}; + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE +int init_module(void) +#else +int saa7110_init(void) +#endif +{ + return i2c_register_driver(&i2c_driver_saa7110); +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_unregister_driver(&i2c_driver_saa7110); +} +#endif diff --git a/drivers/media/video/saa7111.c b/drivers/media/video/saa7111.c new file mode 100644 index 000000000..1eeeca352 --- /dev/null +++ b/drivers/media/video/saa7111.c @@ -0,0 +1,418 @@ +/* + saa7111 - Philips SAA7111A video decoder driver version 0.0.3 + + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> +#include <linux/wrapper.h> + +#include <linux/videodev.h> +#include <linux/version.h> +#include <asm/uaccess.h> + +#include <linux/i2c-old.h> +#include <linux/video_decoder.h> + +#define DEBUG(x) /* Debug driver */ + +/* ----------------------------------------------------------------------- */ + +struct saa7111 { + struct i2c_bus *bus; + int addr; + unsigned char reg[32]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7111 0x48 + +#define I2C_DELAY 10 + +/* ----------------------------------------------------------------------- */ + +static int saa7111_write(struct saa7111 *dev, unsigned char subaddr, unsigned char data) +{ + int ack; + + LOCK_I2C_BUS(dev->bus); + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY); + i2c_sendbyte(dev->bus, subaddr, I2C_DELAY); + ack = i2c_sendbyte(dev->bus, data, I2C_DELAY); + dev->reg[subaddr] = data; + i2c_stop(dev->bus); + UNLOCK_I2C_BUS(dev->bus); + return ack; +} + +static int saa7111_write_block(struct saa7111 *dev, unsigned const char *data, unsigned int len) +{ + int ack = 0; + unsigned subaddr; + + while (len > 1) { + LOCK_I2C_BUS(dev->bus); + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY); + ack = i2c_sendbyte(dev->bus, (subaddr = *data++), I2C_DELAY); + ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY); + len -= 2; + while (len > 1 && *data == ++subaddr) { + data++; + ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY); + len -= 2; + } + i2c_stop(dev->bus); + UNLOCK_I2C_BUS(dev->bus); + } + return ack; +} + +static int saa7111_read(struct saa7111 *dev, unsigned char subaddr) +{ + int data; + + LOCK_I2C_BUS(dev->bus); + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY); + i2c_sendbyte(dev->bus, subaddr, I2C_DELAY); + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr | 1, I2C_DELAY); + data = i2c_readbyte(dev->bus, 1); + i2c_stop(dev->bus); + UNLOCK_I2C_BUS(dev->bus); + return data; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7111_attach(struct i2c_device *device) +{ + int i; + struct saa7111 *decoder; + + static const unsigned char init[] = + { + 0x00, 0x00, /* 00 - ID byte */ + 0x01, 0x00, /* 01 - reserved */ + + /*front end */ + 0x02, 0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */ + 0x03, 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x00, /* 04 - GAI1=256 */ + 0x05, 0x00, /* 05 - GAI2=256 */ + + /* decoder */ + 0x06, 0xf6, /* 06 - HSB at 13(50Hz) / 17(60Hz) pixels after end of last line */ + 0x07, 0xdd, /* 07 - HSS at 113(50Hz) / 117(60Hz) pixels after end of last line */ + 0x08, 0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, HPLL=0, VNOI=0 */ + 0x09, 0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, UPTCV=0, APER=1 */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x47, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ + 0x0f, 0x00, /* 0f - reserved */ + 0x10, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ + 0x11, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x12, 0x00, /* 12 - output control 2 */ + 0x13, 0x00, /* 13 - output control 3 */ + 0x14, 0x00, /* 14 - reserved */ + 0x15, 0x00, /* 15 - VBI */ + 0x16, 0x00, /* 16 - VBI */ + 0x17, 0x00, /* 17 - VBI */ + }; + + device->data = decoder = kmalloc(sizeof(struct saa7111), GFP_KERNEL); + if (decoder == NULL) { + return -ENOMEM; + } + MOD_INC_USE_COUNT; + + memset(decoder, 0, sizeof(struct saa7111)); + strcpy(device->name, "saa7111"); + decoder->bus = device->bus; + decoder->addr = device->addr; + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + + i = saa7111_write_block(decoder, init, sizeof(init)); + if (i < 0) { + printk(KERN_ERR "%s_attach: init status %d\n", device->name, i); + } else { + printk(KERN_INFO "%s_attach: chip version %x\n", device->name, saa7111_read(decoder, 0x00)); + } + return 0; +} + + +static int saa7111_detach(struct i2c_device *device) +{ + kfree(device->data); + MOD_DEC_USE_COUNT; + return 0; +} + +static int saa7111_command(struct i2c_device *device, unsigned int cmd, void *arg) +{ + struct saa7111 *decoder = device->data; + + switch (cmd) { + +#if defined(DECODER_DUMP) + case DECODER_DUMP: + { + int i; + + for (i = 0; i < 32; i += 16) { + int j; + + printk("KERN_DEBUG %s: %03x", device->name, i); + for (j = 0; j < 16; ++j) { + printk(" %02x", saa7111_read(decoder, i + j)); + } + printk("\n"); + } + } + break; +#endif /* defined(DECODER_DUMP) */ + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + cap->flags + = VIDEO_DECODER_PAL + | VIDEO_DECODER_NTSC + | VIDEO_DECODER_AUTO + | VIDEO_DECODER_CCIR; + cap->inputs = 8; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int *iarg = arg; + int status; + int res; + + status = saa7111_read(decoder, 0x1f); + res = 0; + if ((status & (1 << 6)) == 0) { + res |= DECODER_STATUS_GOOD; + } + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + default: + case VIDEO_MODE_AUTO: + if ((status & (1 << 5)) != 0) { + res |= DECODER_STATUS_NTSC; + } else { + res |= DECODER_STATUS_PAL; + } + break; + } + if ((status & (1 << 0)) != 0) { + res |= DECODER_STATUS_COLOR; + } + *iarg = res; + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg; + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x40); + break; + + case VIDEO_MODE_PAL: + saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x00); + break; + + case VIDEO_MODE_AUTO: + saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0x3f) | 0x80); + break; + + default: + return -EINVAL; + + } + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + if (decoder->input != *iarg) { + decoder->input = *iarg; + /* select mode */ + saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8) | decoder->input); + /* bypass chrominance trap for modes 4..7 */ + saa7111_write(decoder, 0x09, (decoder->reg[0x09] & 0x7f) | ((decoder->input > 3) ? 0x80 : 0)); + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + if (decoder->enable != enable) { + decoder->enable = enable; + +// RJ: If output should be disabled (for playing videos), we also need a open PLL. + // The input is set to 0 (where no input source is connected), although this + // is not necessary. + // + // If output should be enabled, we have to reverse the above. + + if (decoder->enable) { + saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8) | decoder->input); + saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0xfb)); + saa7111_write(decoder, 0x11, (decoder->reg[0x11] & 0xf3) | 0x0c); + } else { + saa7111_write(decoder, 0x02, (decoder->reg[0x02] & 0xf8)); + saa7111_write(decoder, 0x08, (decoder->reg[0x08] & 0xfb) | 0x04); + saa7111_write(decoder, 0x11, (decoder->reg[0x11] & 0xf3)); + } + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7111_write(decoder, 0x0a, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7111_write(decoder, 0x0b, decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7111_write(decoder, 0x0c, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7111_write(decoder, 0x0d, (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7111 = +{ + "saa7111", /* name */ + I2C_DRIVERID_VIDEODECODER, /* ID */ + I2C_SAA7111, I2C_SAA7111 + 1, + + saa7111_attach, + saa7111_detach, + saa7111_command +}; + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE +int init_module(void) +#else +int saa7111_init(void) +#endif +{ + return i2c_register_driver(&i2c_driver_saa7111); +} + + + +#ifdef MODULE + +void cleanup_module(void) +{ + i2c_unregister_driver(&i2c_driver_saa7111); +} + +#endif diff --git a/drivers/media/video/saa7121.h b/drivers/media/video/saa7121.h new file mode 100644 index 000000000..74e37d405 --- /dev/null +++ b/drivers/media/video/saa7121.h @@ -0,0 +1,132 @@ +/* saa7121.h - saa7121 initializations + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +#ifndef __SAA7121_H__ +#define __SAA7121_H__ + +#define NTSC_BURST_START 0x19 /* 28 */ +#define NTSC_BURST_END 0x1d /* 29 */ +#define NTSC_CHROMA_PHASE 0x67 /* 5a */ +#define NTSC_GAINU 0x76 /* 5b */ +#define NTSC_GAINV 0xa5 /* 5c */ +#define NTSC_BLACK_LEVEL 0x2a /* 5d */ +#define NTSC_BLANKING_LEVEL 0x2e /* 5e */ +#define NTSC_VBI_BLANKING 0x2e /* 5f */ +#define NTSC_DAC_CONTROL 0x11 /* 61 */ +#define NTSC_BURST_AMP 0x3f /* 62 */ +#define NTSC_SUBC3 0x1f /* 63 */ +#define NTSC_SUBC2 0x7c /* 64 */ +#define NTSC_SUBC1 0xf0 /* 65 */ +#define NTSC_SUBC0 0x21 /* 66 */ +#define NTSC_HTRIG 0x72 /* 6c */ +#define NTSC_VTRIG 0x00 /* 6c */ +#define NTSC_MULTI 0x30 /* 6e */ +#define NTSC_CCTTX 0x11 /* 6f */ +#define NTSC_FIRST_ACTIVE 0x12 /* 7a */ +#define NTSC_LAST_ACTIVE 0x02 /* 7b */ +#define NTSC_MSB_VERTICAL 0x40 /* 7c */ + +#define PAL_BURST_START 0x21 /* 28 */ +#define PAL_BURST_END 0x1d /* 29 */ +#define PAL_CHROMA_PHASE 0x3f /* 5a */ +#define PAL_GAINU 0x7d /* 5b */ +#define PAL_GAINV 0xaf /* 5c */ +#define PAL_BLACK_LEVEL 0x23 /* 5d */ +#define PAL_BLANKING_LEVEL 0x35 /* 5e */ +#define PAL_VBI_BLANKING 0x35 /* 5f */ +#define PAL_DAC_CONTROL 0x02 /* 61 */ +#define PAL_BURST_AMP 0x2f /* 62 */ +#define PAL_SUBC3 0xcb /* 63 */ +#define PAL_SUBC2 0x8a /* 64 */ +#define PAL_SUBC1 0x09 /* 65 */ +#define PAL_SUBC0 0x2a /* 66 */ +#define PAL_HTRIG 0x86 /* 6c */ +#define PAL_VTRIG 0x04 /* 6d */ +#define PAL_MULTI 0x20 /* 6e */ +#define PAL_CCTTX 0x15 /* 6f */ +#define PAL_FIRST_ACTIVE 0x16 /* 7a */ +#define PAL_LAST_ACTIVE 0x36 /* 7b */ +#define PAL_MSB_VERTICAL 0x40 /* 7c */ + +/* Initialization Sequence */ + +static __u8 init7121ntsc[] = { + 0x26, 0x0, 0x27, 0x0, + 0x28, NTSC_BURST_START, 0x29, NTSC_BURST_END, + 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0, + 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0, + 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0, + 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0, + 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0, + 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, + 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0, + 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0, + 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0, + 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0, + 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0, + 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0, + 0x5a, NTSC_CHROMA_PHASE, 0x5b, NTSC_GAINU, + 0x5c, NTSC_GAINV, 0x5d, NTSC_BLACK_LEVEL, + 0x5e, NTSC_BLANKING_LEVEL, 0x5f, NTSC_VBI_BLANKING, + 0x60, 0x0, 0x61, NTSC_DAC_CONTROL, + 0x62, NTSC_BURST_AMP, 0x63, NTSC_SUBC3, + 0x64, NTSC_SUBC2, 0x65, NTSC_SUBC1, + 0x66, NTSC_SUBC0, 0x67, 0x80, 0x68, 0x80, + 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29, + 0x6c, NTSC_HTRIG, 0x6d, NTSC_VTRIG, + 0x6e, NTSC_MULTI, 0x6f, NTSC_CCTTX, + 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0, + 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0, + 0x78, 0x0, 0x79, 0x0, 0x7a, NTSC_FIRST_ACTIVE, + 0x7b, NTSC_LAST_ACTIVE, 0x7c, NTSC_MSB_VERTICAL, + 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0 +}; +#define INIT7121LEN (sizeof(init7121ntsc)/2) + +static __u8 init7121pal[] = { + 0x26, 0x0, 0x27, 0x0, + 0x28, PAL_BURST_START, 0x29, PAL_BURST_END, + 0x2a, 0x0, 0x2b, 0x0, 0x2c, 0x0, 0x2d, 0x0, + 0x2e, 0x0, 0x2f, 0x0, 0x30, 0x0, 0x31, 0x0, + 0x32, 0x0, 0x33, 0x0, 0x34, 0x0, 0x35, 0x0, + 0x36, 0x0, 0x37, 0x0, 0x38, 0x0, 0x39, 0x0, + 0x3a, 0x03, 0x3b, 0x0, 0x3c, 0x0, 0x3d, 0x0, + 0x3e, 0x0, 0x3f, 0x0, 0x40, 0x0, 0x41, 0x0, + 0x42, 0x0, 0x43, 0x0, 0x44, 0x0, 0x45, 0x0, + 0x46, 0x0, 0x47, 0x0, 0x48, 0x0, 0x49, 0x0, + 0x4a, 0x0, 0x4b, 0x0, 0x4c, 0x0, 0x4d, 0x0, + 0x4e, 0x0, 0x4f, 0x0, 0x50, 0x0, 0x51, 0x0, + 0x52, 0x0, 0x53, 0x0, 0x54, 0x0, 0x55, 0x0, + 0x56, 0x0, 0x57, 0x0, 0x58, 0x0, 0x59, 0x0, + 0x5a, PAL_CHROMA_PHASE, 0x5b, PAL_GAINU, + 0x5c, PAL_GAINV, 0x5d, PAL_BLACK_LEVEL, + 0x5e, PAL_BLANKING_LEVEL, 0x5f, PAL_VBI_BLANKING, + 0x60, 0x0, 0x61, PAL_DAC_CONTROL, + 0x62, PAL_BURST_AMP, 0x63, PAL_SUBC3, + 0x64, PAL_SUBC2, 0x65, PAL_SUBC1, + 0x66, PAL_SUBC0, 0x67, 0x80, 0x68, 0x80, + 0x69, 0x80, 0x6a, 0x80, 0x6b, 0x29, + 0x6c, PAL_HTRIG, 0x6d, PAL_VTRIG, + 0x6e, PAL_MULTI, 0x6f, PAL_CCTTX, + 0x70, 0xc9, 0x71, 0x68, 0x72, 0x60, 0x73, 0x0, + 0x74, 0x0, 0x75, 0x0, 0x76, 0x0, 0x77, 0x0, + 0x78, 0x0, 0x79, 0x0, 0x7a, PAL_FIRST_ACTIVE, + 0x7b, PAL_LAST_ACTIVE, 0x7c, PAL_MSB_VERTICAL, + 0x7d, 0x0, 0x7e, 0x0, 0x7f, 0x0 +}; +#endif diff --git a/drivers/media/video/saa7146.h b/drivers/media/video/saa7146.h new file mode 100644 index 000000000..481308e6c --- /dev/null +++ b/drivers/media/video/saa7146.h @@ -0,0 +1,117 @@ +/* + saa7146.h - definitions philips saa7146 based cards + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __SAA7146__ +#define __SAA7146__ + +#define SAA7146_VERSION_CODE 0x000101 + +#include <linux/types.h> +#include <linux/wait.h> + +#include <linux/i2c.h> +#include <linux/videodev.h> + +#ifndef O_NONCAP +#define O_NONCAP O_TRUNC +#endif + +#define MAX_GBUFFERS 2 +#define FBUF_SIZE 0x190000 + +#ifdef __KERNEL__ + +struct saa7146_window +{ + int x, y; + ushort width, height; + ushort bpp, bpl; + ushort swidth, sheight; + short cropx, cropy; + ushort cropwidth, cropheight; + unsigned long vidadr; + int color_fmt; + ushort depth; +}; + +/* Per-open data for handling multiple opens on one device */ +struct device_open +{ + int isopen; + int noncapturing; + struct saa7146 *dev; +}; +#define MAX_OPENS 3 + +struct saa7146 +{ + struct video_device video_dev; + struct video_picture picture; + struct video_audio audio_dev; + struct video_info vidinfo; + int user; + int cap; + int capuser; + int irqstate; /* irq routine is state driven */ + int writemode; + int playmode; + unsigned int nr; + unsigned long irq; /* IRQ used by SAA7146 card */ + unsigned short id; + struct i2c_bus i2c; + struct pci_dev *dev; + unsigned char revision; + unsigned char boardcfg[64]; /* 64 bytes of config from eeprom */ + unsigned long saa7146_adr; /* bus address of IO mem from PCI BIOS */ + struct saa7146_window win; + unsigned char *saa7146_mem; /* pointer to mapped IO memory */ + struct device_open open_data[MAX_OPENS]; +#define MAX_MARKS 16 + /* for a/v sync */ + int endmark[MAX_MARKS], endmarkhead, endmarktail; + u32 *dmaRPS1, *pageRPS1, *dmaRPS2, *pageRPS2, *dmavid1, *dmavid2, + *dmavid3, *dmaa1in, *dmaa1out, *dmaa2in, *dmaa2out, + *pagedebi, *pagevid1, *pagevid2, *pagevid3, *pagea1in, + *pagea1out, *pagea2in, *pagea2out; + wait_queue_head_t i2cq, debiq, audq, vidq; + u8 *vidbuf, *audbuf, *osdbuf, *dmadebi; + int audhead, vidhead, osdhead, audtail, vidtail, osdtail; + spinlock_t lock; /* the device lock */ +}; +#endif + +#ifdef _ALPHA_SAA7146 +#define saawrite(dat,adr) writel((dat),(char *) (saa->saa7146_adr+(adr))) +#define saaread(adr) readl(saa->saa7146_adr+(adr)) +#else +#define saawrite(dat,adr) writel((dat), (char *) (saa->saa7146_mem+(adr))) +#define saaread(adr) readl(saa->saa7146_mem+(adr)) +#endif + +#define saaand(dat,adr) saawrite((dat) & saaread(adr), adr) +#define saaor(dat,adr) saawrite((dat) | saaread(adr), adr) +#define saaaor(dat,mask,adr) saawrite((dat) | ((mask) & saaread(adr)), adr) + +/* bitmask of attached hardware found */ +#define SAA7146_UNKNOWN 0x00000000 +#define SAA7146_SAA7111 0x00000001 +#define SAA7146_SAA7121 0x00000002 +#define SAA7146_IBMMPEG 0x00000004 + +#endif diff --git a/drivers/media/video/saa7146reg.h b/drivers/media/video/saa7146reg.h new file mode 100644 index 000000000..6cc910f50 --- /dev/null +++ b/drivers/media/video/saa7146reg.h @@ -0,0 +1,283 @@ +/* + saa7146.h - definitions philips saa7146 based cards + Copyright (C) 1999 Nathan Laredo (laredo@gnu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __SAA7146_REG__ +#define __SAA7146_REG__ +#define SAA7146_BASE_ODD1 0x00 +#define SAA7146_BASE_EVEN1 0x04 +#define SAA7146_PROT_ADDR1 0x08 +#define SAA7146_PITCH1 0x0c +#define SAA7146_PAGE1 0x10 +#define SAA7146_NUM_LINE_BYTE1 0x14 +#define SAA7146_BASE_ODD2 0x18 +#define SAA7146_BASE_EVEN2 0x1c +#define SAA7146_PROT_ADDR2 0x20 +#define SAA7146_PITCH2 0x24 +#define SAA7146_PAGE2 0x28 +#define SAA7146_NUM_LINE_BYTE2 0x2c +#define SAA7146_BASE_ODD3 0x30 +#define SAA7146_BASE_EVEN3 0x34 +#define SAA7146_PROT_ADDR3 0x38 +#define SAA7146_PITCH3 0x3c +#define SAA7146_PAGE3 0x40 +#define SAA7146_NUM_LINE_BYTE3 0x44 +#define SAA7146_PCI_BT_V1 0x48 +#define SAA7146_PCI_BT_V2 0x49 +#define SAA7146_PCI_BT_V3 0x4a +#define SAA7146_PCI_BT_DEBI 0x4b +#define SAA7146_PCI_BT_A 0x4c +#define SAA7146_DD1_INIT 0x50 +#define SAA7146_DD1_STREAM_B 0x54 +#define SAA7146_DD1_STREAM_A 0x56 +#define SAA7146_BRS_CTRL 0x58 +#define SAA7146_HPS_CTRL 0x5c +#define SAA7146_HPS_V_SCALE 0x60 +#define SAA7146_HPS_V_GAIN 0x64 +#define SAA7146_HPS_H_PRESCALE 0x68 +#define SAA7146_HPS_H_SCALE 0x6c +#define SAA7146_BCS_CTRL 0x70 +#define SAA7146_CHROMA_KEY_RANGE 0x74 +#define SAA7146_CLIP_FORMAT_CTRL 0x78 +#define SAA7146_DEBI_CONFIG 0x7c +#define SAA7146_DEBI_COMMAND 0x80 +#define SAA7146_DEBI_PAGE 0x84 +#define SAA7146_DEBI_AD 0x88 +#define SAA7146_I2C_TRANSFER 0x8c +#define SAA7146_I2C_STATUS 0x90 +#define SAA7146_BASE_A1_IN 0x94 +#define SAA7146_PROT_A1_IN 0x98 +#define SAA7146_PAGE_A1_IN 0x9C +#define SAA7146_BASE_A1_OUT 0xa0 +#define SAA7146_PROT_A1_OUT 0xa4 +#define SAA7146_PAGE_A1_OUT 0xa8 +#define SAA7146_BASE_A2_IN 0xac +#define SAA7146_PROT_A2_IN 0xb0 +#define SAA7146_PAGE_A2_IN 0xb4 +#define SAA7146_BASE_A2_OUT 0xb8 +#define SAA7146_PROT_A2_OUT 0xbc +#define SAA7146_PAGE_A2_OUT 0xc0 +#define SAA7146_RPS_PAGE0 0xc4 +#define SAA7146_RPS_PAGE1 0xc8 +#define SAA7146_RPS_THRESH0 0xcc +#define SAA7146_RPS_THRESH1 0xd0 +#define SAA7146_RPS_TOV0 0xd4 +#define SAA7146_RPS_TOV1 0xd8 +#define SAA7146_IER 0xdc +#define SAA7146_GPIO_CTRL 0xe0 +#define SAA7146_EC1SSR 0xe4 +#define SAA7146_EC2SSR 0xe8 +#define SAA7146_ECT1R 0xec +#define SAA7146_ECT2R 0xf0 +#define SAA7146_ACON1 0xf4 +#define SAA7146_ACON2 0xf8 +#define SAA7146_MC1 0xfc +#define SAA7146_MC2 0x100 +#define SAA7146_RPS_ADDR0 0x104 +#define SAA7146_RPS_ADDR1 0x108 +#define SAA7146_ISR 0x10c +#define SAA7146_PSR 0x110 +#define SAA7146_SSR 0x114 +#define SAA7146_EC1R 0x118 +#define SAA7146_EC2R 0x11c +#define SAA7146_VDP1 0x120 +#define SAA7146_VDP2 0x124 +#define SAA7146_VDP3 0x128 +#define SAA7146_ADP1 0x12c +#define SAA7146_ADP2 0x130 +#define SAA7146_ADP3 0x134 +#define SAA7146_ADP4 0x138 +#define SAA7146_DDP 0x13c +#define SAA7146_LEVEL_REP 0x140 +#define SAA7146_FB_BUFFER1 0x144 +#define SAA7146_FB_BUFFER2 0x148 +#define SAA7146_A_TIME_SLOT1 0x180 +#define SAA7146_A_TIME_SLOT2 0x1C0 + +/* bitfield defines */ +#define MASK_31 0x80000000 +#define MASK_30 0x40000000 +#define MASK_29 0x20000000 +#define MASK_28 0x10000000 +#define MASK_27 0x08000000 +#define MASK_26 0x04000000 +#define MASK_25 0x02000000 +#define MASK_24 0x01000000 +#define MASK_23 0x00800000 +#define MASK_22 0x00400000 +#define MASK_21 0x00200000 +#define MASK_20 0x00100000 +#define MASK_19 0x00080000 +#define MASK_18 0x00040000 +#define MASK_17 0x00020000 +#define MASK_16 0x00010000 +#define MASK_15 0x00008000 +#define MASK_14 0x00004000 +#define MASK_13 0x00002000 +#define MASK_12 0x00001000 +#define MASK_11 0x00000800 +#define MASK_10 0x00000400 +#define MASK_09 0x00000200 +#define MASK_08 0x00000100 +#define MASK_07 0x00000080 +#define MASK_06 0x00000040 +#define MASK_05 0x00000020 +#define MASK_04 0x00000010 +#define MASK_03 0x00000008 +#define MASK_02 0x00000004 +#define MASK_01 0x00000002 +#define MASK_00 0x00000001 +#define MASK_B0 0x000000ff +#define MASK_B1 0x0000ff00 +#define MASK_B2 0x00ff0000 +#define MASK_B3 0xff000000 +#define MASK_W0 0x0000ffff +#define MASK_W1 0xffff0000 +#define MASK_PA 0xfffffffc +#define MASK_PR 0xfffffffe +#define MASK_ER 0xffffffff +#define MASK_NONE 0x00000000 + +#define SAA7146_PAGE_MAP_EN MASK_11 +/* main control register 1 */ +#define SAA7146_MC1_MRST_N MASK_15 +#define SAA7146_MC1_ERPS1 MASK_13 +#define SAA7146_MC1_ERPS0 MASK_12 +#define SAA7146_MC1_EDP MASK_11 +#define SAA7146_MC1_EVP MASK_10 +#define SAA7146_MC1_EAP MASK_09 +#define SAA7146_MC1_EI2C MASK_08 +#define SAA7146_MC1_TR_E_DEBI MASK_07 +#define SAA7146_MC1_TR_E_1 MASK_06 +#define SAA7146_MC1_TR_E_2 MASK_05 +#define SAA7146_MC1_TR_E_3 MASK_04 +#define SAA7146_MC1_TR_E_A2_OUT MASK_03 +#define SAA7146_MC1_TR_E_A2_IN MASK_02 +#define SAA7146_MC1_TR_E_A1_OUT MASK_01 +#define SAA7146_MC1_TR_E_A1_IN MASK_00 +/* main control register 2 */ +#define SAA7146_MC2_RPS_SIG4 MASK_15 +#define SAA7146_MC2_RPS_SIG3 MASK_14 +#define SAA7146_MC2_RPS_SIG2 MASK_13 +#define SAA7146_MC2_RPS_SIG1 MASK_12 +#define SAA7146_MC2_RPS_SIG0 MASK_11 +#define SAA7146_MC2_UPLD_D1_B MASK_10 +#define SAA7146_MC2_UPLD_D1_A MASK_09 +#define SAA7146_MC2_UPLD_BRS MASK_08 +#define SAA7146_MC2_UPLD_HPS_H MASK_06 +#define SAA7146_MC2_UPLD_HPS_V MASK_05 +#define SAA7146_MC2_UPLD_DMA3 MASK_04 +#define SAA7146_MC2_UPLD_DMA2 MASK_03 +#define SAA7146_MC2_UPLD_DMA1 MASK_02 +#define SAA7146_MC2_UPLD_DEBI MASK_01 +#define SAA7146_MC2_UPLD_I2C MASK_00 +/* Primary Status Register and Interrupt Enable/Status Registers */ +#define SAA7146_PSR_PPEF MASK_31 +#define SAA7146_PSR_PABO MASK_30 +#define SAA7146_PSR_PPED MASK_29 +#define SAA7146_PSR_RPS_I1 MASK_28 +#define SAA7146_PSR_RPS_I0 MASK_27 +#define SAA7146_PSR_RPS_LATE1 MASK_26 +#define SAA7146_PSR_RPS_LATE0 MASK_25 +#define SAA7146_PSR_RPS_E1 MASK_24 +#define SAA7146_PSR_RPS_E0 MASK_23 +#define SAA7146_PSR_RPS_TO1 MASK_22 +#define SAA7146_PSR_RPS_TO0 MASK_21 +#define SAA7146_PSR_UPLD MASK_20 +#define SAA7146_PSR_DEBI_S MASK_19 +#define SAA7146_PSR_DEBI_E MASK_18 +#define SAA7146_PSR_I2C_S MASK_17 +#define SAA7146_PSR_I2C_E MASK_16 +#define SAA7146_PSR_A2_IN MASK_15 +#define SAA7146_PSR_A2_OUT MASK_14 +#define SAA7146_PSR_A1_IN MASK_13 +#define SAA7146_PSR_A1_OUT MASK_12 +#define SAA7146_PSR_AFOU MASK_11 +#define SAA7146_PSR_V_PE MASK_10 +#define SAA7146_PSR_VFOU MASK_09 +#define SAA7146_PSR_FIDA MASK_08 +#define SAA7146_PSR_FIDB MASK_07 +#define SAA7146_PSR_PIN3 MASK_06 +#define SAA7146_PSR_PIN2 MASK_05 +#define SAA7146_PSR_PIN1 MASK_04 +#define SAA7146_PSR_PIN0 MASK_03 +#define SAA7146_PSR_ECS MASK_02 +#define SAA7146_PSR_EC3S MASK_01 +#define SAA7146_PSR_EC0S MASK_00 +/* Secondary Status Register */ +#define SAA7146_SSR_PRQ MASK_31 +#define SAA7146_SSR_PMA MASK_30 +#define SAA7146_SSR_RPS_RE1 MASK_29 +#define SAA7146_SSR_RPS_PE1 MASK_28 +#define SAA7146_SSR_RPS_A1 MASK_27 +#define SAA7146_SSR_RPS_RE0 MASK_26 +#define SAA7146_SSR_RPS_PE0 MASK_25 +#define SAA7146_SSR_RPS_A0 MASK_24 +#define SAA7146_SSR_DEBI_TO MASK_23 +#define SAA7146_SSR_DEBI_EF MASK_22 +#define SAA7146_SSR_I2C_EA MASK_21 +#define SAA7146_SSR_I2C_EW MASK_20 +#define SAA7146_SSR_I2C_ER MASK_19 +#define SAA7146_SSR_I2C_EL MASK_18 +#define SAA7146_SSR_I2C_EF MASK_17 +#define SAA7146_SSR_V3P MASK_16 +#define SAA7146_SSR_V2P MASK_15 +#define SAA7146_SSR_V1P MASK_14 +#define SAA7146_SSR_VF3 MASK_13 +#define SAA7146_SSR_VF2 MASK_12 +#define SAA7146_SSR_VF1 MASK_11 +#define SAA7146_SSR_AF2_IN MASK_10 +#define SAA7146_SSR_AF2_OUT MASK_09 +#define SAA7146_SSR_AF1_IN MASK_08 +#define SAA7146_SSR_AF1_OUT MASK_07 +#define SAA7146_SSR_VGT MASK_05 +#define SAA7146_SSR_LNQG MASK_04 +#define SAA7146_SSR_EC5S MASK_03 +#define SAA7146_SSR_EC4S MASK_02 +#define SAA7146_SSR_EC2S MASK_01 +#define SAA7146_SSR_EC1S MASK_00 +/* I2C status register */ +#define SAA7146_I2C_ABORT MASK_07 +#define SAA7146_I2C_SPERR MASK_06 +#define SAA7146_I2C_APERR MASK_05 +#define SAA7146_I2C_DTERR MASK_04 +#define SAA7146_I2C_DRERR MASK_03 +#define SAA7146_I2C_AL MASK_02 +#define SAA7146_I2C_ERR MASK_01 +#define SAA7146_I2C_BUSY MASK_00 +/* output formats */ +#define SAA7146_YUV422 0 +#define SAA7146_RGB16 0 +#define SAA7146_YUV444 1 +#define SAA7146_RGB24 1 +#define SAA7146_ARGB32 2 +#define SAA7146_YUV411 3 +#define SAA7146_ARGB15 3 +#define SAA7146_YUV2 4 +#define SAA7146_RGAB15 4 +#define SAA7146_Y8 6 +#define SAA7146_YUV8 7 +#define SAA7146_RGB8 7 +#define SAA7146_YUV444p 8 +#define SAA7146_YUV422p 9 +#define SAA7146_YUV420p 10 +#define SAA7146_YUV1620 11 +#define SAA7146_Y1 13 +#define SAA7146_Y2 14 +#define SAA7146_YUV1 15 +#endif diff --git a/drivers/media/video/saa7185.c b/drivers/media/video/saa7185.c new file mode 100644 index 000000000..c30e6353b --- /dev/null +++ b/drivers/media/video/saa7185.c @@ -0,0 +1,377 @@ +/* + saa7185 - Philips SAA7185B video encoder driver version 0.0.3 + + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <linux/types.h> +#include <linux/wrapper.h> + +#include <linux/videodev.h> +#include <linux/version.h> +#include <asm/uaccess.h> + +#include <linux/i2c-old.h> +#include <linux/video_encoder.h> + +#define DEBUG(x) x /* Debug driver */ + +/* ----------------------------------------------------------------------- */ + +struct saa7185 { + struct i2c_bus *bus; + int addr; + unsigned char reg[128]; + + int norm; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7185 0x88 + +#define I2C_DELAY 10 + +/* ----------------------------------------------------------------------- */ + +static int saa7185_write(struct saa7185 *dev, unsigned char subaddr, unsigned char data) +{ + int ack; + + LOCK_I2C_BUS(dev->bus); + + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY); + i2c_sendbyte(dev->bus, subaddr, I2C_DELAY); + ack = i2c_sendbyte(dev->bus, data, I2C_DELAY); + dev->reg[subaddr] = data; + i2c_stop(dev->bus); + UNLOCK_I2C_BUS(dev->bus); + return ack; +} + +static int saa7185_write_block(struct saa7185 *dev, unsigned const char *data, unsigned int len) +{ + int ack = 0; + unsigned subaddr; + + while (len > 1) { + LOCK_I2C_BUS(dev->bus); + i2c_start(dev->bus); + i2c_sendbyte(dev->bus, dev->addr, I2C_DELAY); + ack = i2c_sendbyte(dev->bus, (subaddr = *data++), I2C_DELAY); + ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY); + len -= 2; + while (len > 1 && *data == ++subaddr) { + data++; + ack = i2c_sendbyte(dev->bus, (dev->reg[subaddr] = *data++), I2C_DELAY); + len -= 2; + } + i2c_stop(dev->bus); + UNLOCK_I2C_BUS(dev->bus); + } + return ack; +} + +/* ----------------------------------------------------------------------- */ + +static const unsigned char init_common[] = +{ + 0x3a, 0x0f, /* CBENB=0, V656=0, VY2C=1, YUV2C=1, MY2C=1, MUV2C=1 */ + + 0x42, 0x6b, /* OVLY0=107 */ + 0x43, 0x00, /* OVLU0=0 white */ + 0x44, 0x00, /* OVLV0=0 */ + 0x45, 0x22, /* OVLY1=34 */ + 0x46, 0xac, /* OVLU1=172 yellow */ + 0x47, 0x0e, /* OVLV1=14 */ + 0x48, 0x03, /* OVLY2=3 */ + 0x49, 0x1d, /* OVLU2=29 cyan */ + 0x4a, 0xac, /* OVLV2=172 */ + 0x4b, 0xf0, /* OVLY3=240 */ + 0x4c, 0xc8, /* OVLU3=200 green */ + 0x4d, 0xb9, /* OVLV3=185 */ + 0x4e, 0xd4, /* OVLY4=212 */ + 0x4f, 0x38, /* OVLU4=56 magenta */ + 0x50, 0x47, /* OVLV4=71 */ + 0x51, 0xc1, /* OVLY5=193 */ + 0x52, 0xe3, /* OVLU5=227 red */ + 0x53, 0x54, /* OVLV5=84 */ + 0x54, 0xa3, /* OVLY6=163 */ + 0x55, 0x54, /* OVLU6=84 blue */ + 0x56, 0xf2, /* OVLV6=242 */ + 0x57, 0x90, /* OVLY7=144 */ + 0x58, 0x00, /* OVLU7=0 black */ + 0x59, 0x00, /* OVLV7=0 */ + + 0x5a, 0x00, /* CHPS=0 */ + 0x5b, 0x76, /* GAINU=118 */ + 0x5c, 0xa5, /* GAINV=165 */ + 0x5d, 0x3c, /* BLCKL=60 */ + 0x5e, 0x3a, /* BLNNL=58 */ + 0x5f, 0x3a, /* CCRS=0, BLNVB=58 */ + 0x60, 0x00, /* NULL */ + +/* 0x61 - 0x66 set according to norm */ + + 0x67, 0x00, /* 0 : caption 1st byte odd field */ + 0x68, 0x00, /* 0 : caption 2nd byte odd field */ + 0x69, 0x00, /* 0 : caption 1st byte even field */ + 0x6a, 0x00, /* 0 : caption 2nd byte even field */ + + 0x6b, 0x91, /* MODIN=2, PCREF=0, SCCLN=17 */ + 0x6c, 0x20, /* SRCV1=0, TRCV2=1, ORCV1=0, PRCV1=0, CBLF=0, ORCV2=0, PRCV2=0 */ + 0x6d, 0x00, /* SRCM1=0, CCEN=0 */ + + 0x6e, 0x0e, /* HTRIG=0x00e, approx. centered, at least for PAL */ + 0x6f, 0x00, /* HTRIG upper bits */ + 0x70, 0x20, /* PHRES=0, SBLN=1, VTRIG=0 */ + +/* The following should not be needed */ + + 0x71, 0x15, /* BMRQ=0x115 */ + 0x72, 0x90, /* EMRQ=0x690 */ + 0x73, 0x61, /* EMRQ=0x690, BMRQ=0x115 */ + 0x74, 0x00, /* NULL */ + 0x75, 0x00, /* NULL */ + 0x76, 0x00, /* NULL */ + 0x77, 0x15, /* BRCV=0x115 */ + 0x78, 0x90, /* ERCV=0x690 */ + 0x79, 0x61, /* ERCV=0x690, BRCV=0x115 */ + +/* Field length controls */ + + 0x7a, 0x70, /* FLC=0 */ + +/* The following should not be needed if SBLN = 1 */ + + 0x7b, 0x16, /* FAL=22 */ + 0x7c, 0x35, /* LAL=244 */ + 0x7d, 0x20, /* LAL=244, FAL=22 */ +}; + +static const unsigned char init_pal[] = +{ + 0x61, 0x1e, /* FISE=0, PAL=1, SCBW=1, RTCE=1, YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xc8, /* DECTYP=1, BSTA=72 */ + 0x63, 0xcb, /* FSC0 */ + 0x64, 0x8a, /* FSC1 */ + 0x65, 0x09, /* FSC2 */ + 0x66, 0x2a, /* FSC3 */ +}; + +static const unsigned char init_ntsc[] = +{ + 0x61, 0x1d, /* FISE=1, PAL=0, SCBW=1, RTCE=1, YGS=1, INPI=0, DOWN=0 */ + 0x62, 0xe6, /* DECTYP=1, BSTA=102 */ + 0x63, 0x1f, /* FSC0 */ + 0x64, 0x7c, /* FSC1 */ + 0x65, 0xf0, /* FSC2 */ + 0x66, 0x21, /* FSC3 */ +}; + +static int saa7185_attach(struct i2c_device *device) +{ + int i; + struct saa7185 *encoder; + + device->data = encoder = kmalloc(sizeof(struct saa7185), GFP_KERNEL); + if (encoder == NULL) { + return -ENOMEM; + } + MOD_INC_USE_COUNT; + + memset(encoder, 0, sizeof(struct saa7185)); + strcpy(device->name, "saa7185"); + encoder->bus = device->bus; + encoder->addr = device->addr; + encoder->norm = VIDEO_MODE_NTSC; + encoder->enable = 1; + + i = saa7185_write_block(encoder, init_common, sizeof(init_common)); + if (i >= 0) { + i = saa7185_write_block(encoder, init_ntsc, sizeof(init_ntsc)); + } + if (i < 0) { + printk(KERN_ERR "%s_attach: init error %d\n", device->name, i); + } + return 0; +} + + +static int saa7185_detach(struct i2c_device *device) +{ + kfree(device->data); + MOD_DEC_USE_COUNT; + return 0; +} + +static int saa7185_command(struct i2c_device *device, unsigned int cmd, void *arg) +{ + struct saa7185 *encoder = device->data; + + switch (cmd) { + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags + = VIDEO_ENCODER_PAL + | VIDEO_ENCODER_NTSC + | VIDEO_ENCODER_SECAM + | VIDEO_ENCODER_CCIR; + cap->inputs = 1; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int *iarg = arg; + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7185_write_block(encoder, init_ntsc, sizeof(init_ntsc)); + break; + + case VIDEO_MODE_PAL: + saa7185_write_block(encoder, init_pal, sizeof(init_pal)); + break; + + case VIDEO_MODE_SECAM: + default: + return -EINVAL; + + } + encoder->norm = *iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int *iarg = arg; + +#if 0 + /* not much choice of inputs */ + if (*iarg != 0) { + return -EINVAL; + } +#else + /* RJ: *iarg = 0: input is from SA7111 + *iarg = 1: input is from ZR36060 */ + + switch (*iarg) { + + case 0: + /* Switch RTCE to 1 */ + saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xf7) | 0x08); + break; + + case 1: + /* Switch RTCE to 0 */ + saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xf7) | 0x00); + break; + + default: + return -EINVAL; + + } +#endif + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + saa7185_write(encoder, 0x61, (encoder->reg[0x61] & 0xbf) | (encoder->enable ? 0x00 : 0x40)); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7185 = +{ + "saa7185", /* name */ + I2C_DRIVERID_VIDEOENCODER, /* ID */ + I2C_SAA7185, I2C_SAA7185 + 1, + + saa7185_attach, + saa7185_detach, + saa7185_command +}; + +EXPORT_NO_SYMBOLS; + +#ifdef MODULE +int init_module(void) +#else +int saa7185_init(void) +#endif +{ + return i2c_register_driver(&i2c_driver_saa7185); +} + + + +#ifdef MODULE + +void cleanup_module(void) +{ + i2c_unregister_driver(&i2c_driver_saa7185); +} + +#endif diff --git a/drivers/media/video/saa7196.h b/drivers/media/video/saa7196.h new file mode 100644 index 000000000..f92f21cfb --- /dev/null +++ b/drivers/media/video/saa7196.h @@ -0,0 +1,117 @@ +/* + Definitions for the Philips SAA7196 digital video decoder, + scaler, and clock generator circuit (DESCpro), as used in + the PlanB video input of the Powermac 7x00/8x00 series. + + Copyright (C) 1998 Michel Lanners (mlan@cpu.lu) + + The register defines are shamelessly copied from the meteor + driver out of NetBSD (with permission), + and are copyrighted (c) 1995 Mark Tinguely and Jim Lowe + (Thanks !) + + Additional debugging and coding by Takashi Oe (toe@unlinfo.unl.edu) + + The default values used for PlanB are my mistakes. +*/ + +/* $Id: saa7196.h,v 1.5 1999/03/26 23:28:47 mlan Exp $ */ + +#ifndef _SAA7196_H_ +#define _SAA7196_H_ + +#define SAA7196_NUMREGS 0x31 /* Number of registers (used)*/ +#define NUM_SUPPORTED_NORM 3 /* Number of supported norms by PlanB */ + +/* Decoder part: */ +#define SAA7196_IDEL 0x00 /* Increment delay */ +#define SAA7196_HSB5 0x01 /* H-sync begin; 50 hz */ +#define SAA7196_HSS5 0x02 /* H-sync stop; 50 hz */ +#define SAA7196_HCB5 0x03 /* H-clamp begin; 50 hz */ +#define SAA7196_HCS5 0x04 /* H-clamp stop; 50 hz */ +#define SAA7196_HSP5 0x05 /* H-sync after PHI1; 50 hz */ +#define SAA7196_LUMC 0x06 /* Luminance control */ +#define SAA7196_HUEC 0x07 /* Hue control */ +#define SAA7196_CKTQ 0x08 /* Colour Killer Threshold QAM (PAL, NTSC) */ +#define SAA7196_CKTS 0x09 /* Colour Killer Threshold SECAM */ +#define SAA7196_PALS 0x0a /* PAL switch sensitivity */ +#define SAA7196_SECAMS 0x0b /* SECAM switch sensitivity */ +#define SAA7196_CGAINC 0x0c /* Chroma gain control */ +#define SAA7196_STDC 0x0d /* Standard/Mode control */ +#define SAA7196_IOCC 0x0e /* I/O and Clock Control */ +#define SAA7196_CTRL1 0x0f /* Control #1 */ +#define SAA7196_CTRL2 0x10 /* Control #2 */ +#define SAA7196_CGAINR 0x11 /* Chroma Gain Reference */ +#define SAA7196_CSAT 0x12 /* Chroma Saturation */ +#define SAA7196_CONT 0x13 /* Luminance Contrast */ +#define SAA7196_HSB6 0x14 /* H-sync begin; 60 hz */ +#define SAA7196_HSS6 0x15 /* H-sync stop; 60 hz */ +#define SAA7196_HCB6 0x16 /* H-clamp begin; 60 hz */ +#define SAA7196_HCS6 0x17 /* H-clamp stop; 60 hz */ +#define SAA7196_HSP6 0x18 /* H-sync after PHI1; 60 hz */ +#define SAA7196_BRIG 0x19 /* Luminance Brightness */ + +/* Scaler part: */ +#define SAA7196_FMTS 0x20 /* Formats and sequence */ +#define SAA7196_OUTPIX 0x21 /* Output data pixel/line */ +#define SAA7196_INPIX 0x22 /* Input data pixel/line */ +#define SAA7196_HWS 0x23 /* Horiz. window start */ +#define SAA7196_HFILT 0x24 /* Horiz. filter */ +#define SAA7196_OUTLINE 0x25 /* Output data lines/field */ +#define SAA7196_INLINE 0x26 /* Input data lines/field */ +#define SAA7196_VWS 0x27 /* Vertical window start */ +#define SAA7196_VYP 0x28 /* AFS/vertical Y processing */ +#define SAA7196_VBS 0x29 /* Vertical Bypass start */ +#define SAA7196_VBCNT 0x2a /* Vertical Bypass count */ +#define SAA7196_VBP 0x2b /* veritcal Bypass Polarity */ +#define SAA7196_VLOW 0x2c /* Colour-keying lower V limit */ +#define SAA7196_VHIGH 0x2d /* Colour-keying upper V limit */ +#define SAA7196_ULOW 0x2e /* Colour-keying lower U limit */ +#define SAA7196_UHIGH 0x2f /* Colour-keying upper U limit */ +#define SAA7196_DPATH 0x30 /* Data path setting */ + +/* Initialization default values: */ + +unsigned char saa_regs[NUM_SUPPORTED_NORM][SAA7196_NUMREGS] = { + +/* PAL, 768x576 (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff, + 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x06, 0x3b, 0x98, + 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0xa2, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12, + 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 }, + +/* NTSC, 640x480? (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x50, 0x00, + 0xf8, 0xf0, 0xfe, 0xe0, 0x00, 0x06, 0x3b, 0x98, + 0x00, 0x2c, 0x3d, 0x40, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0x98, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x80, 0x03, 0x89, 0xf0, 0xf0, 0x0d, + 0xa0, 0x0d, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 }, + +/* SECAM, 768x576 (no scaling), composite video-in */ +/* Decoder: */ + { 0x50, 0x30, 0x00, 0xe8, 0xb6, 0xe5, 0x63, 0xff, + 0xfe, 0xf0, 0xfe, 0xe0, 0x20, 0x07, 0x3b, 0x98, + 0x00, 0x59, 0x41, 0x45, 0x34, 0x0a, 0xf4, 0xd2, + 0xe9, 0xa2, +/* Padding */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +/* Scaler: */ + 0x72, 0x80, 0x00, 0x03, 0x8d, 0x20, 0x20, 0x12, + 0xa5, 0x12, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x87 } + }; + +#endif /* _SAA7196_H_ */ diff --git a/drivers/media/video/stallion.c b/drivers/media/video/stallion.c new file mode 100644 index 000000000..a0faeada4 --- /dev/null +++ b/drivers/media/video/stallion.c @@ -0,0 +1,5329 @@ +/*****************************************************************************/ + +/* + * stallion.c -- stallion multiport serial driver. + * + * Copyright (C) 1996-1999 Stallion Technologies (support@stallion.oz.au). + * Copyright (C) 1994-1996 Greg Ungerer. + * + * This code is loosely based on the Linux serial driver, written by + * Linus Torvalds, Theodore T'so and others. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/*****************************************************************************/ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/version.h> /* for linux/stallion.h */ +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/cd1400.h> +#include <linux/sc26198.h> +#include <linux/comstats.h> +#include <linux/stallion.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/smp_lock.h> +#include <linux/devfs_fs_kernel.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif + +/*****************************************************************************/ + +/* + * Define different board types. Use the standard Stallion "assigned" + * board numbers. Boards supported in this driver are abbreviated as + * EIO = EasyIO and ECH = EasyConnection 8/32. + */ +#define BRD_EASYIO 20 +#define BRD_ECH 21 +#define BRD_ECHMC 22 +#define BRD_ECHPCI 26 +#define BRD_ECH64PCI 27 +#define BRD_EASYIOPCI 28 + +/* + * Define a configuration structure to hold the board configuration. + * Need to set this up in the code (for now) with the boards that are + * to be configured into the system. This is what needs to be modified + * when adding/removing/modifying boards. Each line entry in the + * stl_brdconf[] array is a board. Each line contains io/irq/memory + * ranges for that board (as well as what type of board it is). + * Some examples: + * { BRD_EASYIO, 0x2a0, 0, 0, 10, 0 }, + * This line would configure an EasyIO board (4 or 8, no difference), + * at io address 2a0 and irq 10. + * Another example: + * { BRD_ECH, 0x2a8, 0x280, 0, 12, 0 }, + * This line will configure an EasyConnection 8/32 board at primary io + * address 2a8, secondary io address 280 and irq 12. + * Enter as many lines into this array as you want (only the first 4 + * will actually be used!). Any combination of EasyIO and EasyConnection + * boards can be specified. EasyConnection 8/32 boards can share their + * secondary io addresses between each other. + * + * NOTE: there is no need to put any entries in this table for PCI + * boards. They will be found automatically by the driver - provided + * PCI BIOS32 support is compiled into the kernel. + */ + +typedef struct { + int brdtype; + int ioaddr1; + int ioaddr2; + unsigned long memaddr; + int irq; + int irqtype; +} stlconf_t; + +static stlconf_t stl_brdconf[] = { + /*{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },*/ +}; + +static int stl_nrbrds = sizeof(stl_brdconf) / sizeof(stlconf_t); + +/*****************************************************************************/ + +/* + * Define some important driver characteristics. Device major numbers + * allocated as per Linux Device Registry. + */ +#ifndef STL_SIOMEMMAJOR +#define STL_SIOMEMMAJOR 28 +#endif +#ifndef STL_SERIALMAJOR +#define STL_SERIALMAJOR 24 +#endif +#ifndef STL_CALLOUTMAJOR +#define STL_CALLOUTMAJOR 25 +#endif + +#define STL_DRVTYPSERIAL 1 +#define STL_DRVTYPCALLOUT 2 + +/* + * Set the TX buffer size. Bigger is better, but we don't want + * to chew too much memory with buffers! + */ +#define STL_TXBUFLOW 512 +#define STL_TXBUFSIZE 4096 + +/*****************************************************************************/ + +/* + * Define our local driver identity first. Set up stuff to deal with + * all the local structures required by a serial tty driver. + */ +static char *stl_drvtitle = "Stallion Multiport Serial Driver"; +static char *stl_drvname = "stallion"; +static char *stl_drvversion = "5.6.0"; +static char *stl_serialname = "ttyE"; +static char *stl_calloutname = "cue"; + +static struct tty_driver stl_serial; +static struct tty_driver stl_callout; +static struct tty_struct *stl_ttys[STL_MAXDEVS]; +static struct termios *stl_termios[STL_MAXDEVS]; +static struct termios *stl_termioslocked[STL_MAXDEVS]; +static int stl_refcount = 0; + +/* + * We will need to allocate a temporary write buffer for chars that + * come direct from user space. The problem is that a copy from user + * space might cause a page fault (typically on a system that is + * swapping!). All ports will share one buffer - since if the system + * is already swapping a shared buffer won't make things any worse. + */ +static char *stl_tmpwritebuf; +static DECLARE_MUTEX(stl_tmpwritesem); + +/* + * Define a local default termios struct. All ports will be created + * with this termios initially. Basically all it defines is a raw port + * at 9600, 8 data bits, 1 stop bit. + */ +static struct termios stl_deftermios = { + 0, + 0, + (B9600 | CS8 | CREAD | HUPCL | CLOCAL), + 0, + 0, + INIT_C_CC +}; + +/* + * Define global stats structures. Not used often, and can be + * re-used for each stats call. + */ +static comstats_t stl_comstats; +static combrd_t stl_brdstats; +static stlbrd_t stl_dummybrd; +static stlport_t stl_dummyport; + +/* + * Define global place to put buffer overflow characters. + */ +static char stl_unwanted[SC26198_RXFIFOSIZE]; + +/* + * Keep track of what interrupts we have requested for us. + * We don't need to request an interrupt twice if it is being + * shared with another Stallion board. + */ +static int stl_gotintrs[STL_MAXBRDS]; +static int stl_numintrs = 0; + +/*****************************************************************************/ + +static stlbrd_t *stl_brds[STL_MAXBRDS]; + +/* + * Per board state flags. Used with the state field of the board struct. + * Not really much here! + */ +#define BRD_FOUND 0x1 + +/* + * Define the port structure istate flags. These set of flags are + * modified at interrupt time - so setting and reseting them needs + * to be atomic. Use the bit clear/setting routines for this. + */ +#define ASYI_TXBUSY 1 +#define ASYI_TXLOW 2 +#define ASYI_DCDCHANGE 3 +#define ASYI_TXFLOWED 4 + +/* + * Define an array of board names as printable strings. Handy for + * referencing boards when printing trace and stuff. + */ +static char *stl_brdnames[] = { + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + (char *) NULL, + "EasyIO", + "EC8/32-AT", + "EC8/32-MC", + (char *) NULL, + (char *) NULL, + (char *) NULL, + "EC8/32-PCI", + "EC8/64-PCI", + "EasyIO-PCI", +}; + +/*****************************************************************************/ + +#ifdef MODULE +/* + * Define some string labels for arguments passed from the module + * load line. These allow for easy board definitions, and easy + * modification of the io, memory and irq resoucres. + */ + +static char *board0[4]; +static char *board1[4]; +static char *board2[4]; +static char *board3[4]; + +static char **stl_brdsp[] = { + (char **) &board0, + (char **) &board1, + (char **) &board2, + (char **) &board3 +}; + +/* + * Define a set of common board names, and types. This is used to + * parse any module arguments. + */ + +typedef struct stlbrdtype { + char *name; + int type; +} stlbrdtype_t; + +static stlbrdtype_t stl_brdstr[] = { + { "easyio", BRD_EASYIO }, + { "eio", BRD_EASYIO }, + { "20", BRD_EASYIO }, + { "ec8/32", BRD_ECH }, + { "ec8/32-at", BRD_ECH }, + { "ec8/32-isa", BRD_ECH }, + { "ech", BRD_ECH }, + { "echat", BRD_ECH }, + { "21", BRD_ECH }, + { "ec8/32-mc", BRD_ECHMC }, + { "ec8/32-mca", BRD_ECHMC }, + { "echmc", BRD_ECHMC }, + { "echmca", BRD_ECHMC }, + { "22", BRD_ECHMC }, + { "ec8/32-pc", BRD_ECHPCI }, + { "ec8/32-pci", BRD_ECHPCI }, + { "26", BRD_ECHPCI }, + { "ec8/64-pc", BRD_ECH64PCI }, + { "ec8/64-pci", BRD_ECH64PCI }, + { "ech-pci", BRD_ECH64PCI }, + { "echpci", BRD_ECH64PCI }, + { "echpc", BRD_ECH64PCI }, + { "27", BRD_ECH64PCI }, + { "easyio-pc", BRD_EASYIOPCI }, + { "easyio-pci", BRD_EASYIOPCI }, + { "eio-pci", BRD_EASYIOPCI }, + { "eiopci", BRD_EASYIOPCI }, + { "28", BRD_EASYIOPCI }, +}; + +/* + * Define the module agruments. + */ +MODULE_AUTHOR("Greg Ungerer"); +MODULE_DESCRIPTION("Stallion Multiport Serial Driver"); + +MODULE_PARM(board0, "1-4s"); +MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,ioaddr2][,irq]]"); +MODULE_PARM(board1, "1-4s"); +MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,ioaddr2][,irq]]"); +MODULE_PARM(board2, "1-4s"); +MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,ioaddr2][,irq]]"); +MODULE_PARM(board3, "1-4s"); +MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,ioaddr2][,irq]]"); + +#endif + +/*****************************************************************************/ + +/* + * Hardware ID bits for the EasyIO and ECH boards. These defines apply + * to the directly accessible io ports of these boards (not the uarts - + * they are in cd1400.h and sc26198.h). + */ +#define EIO_8PORTRS 0x04 +#define EIO_4PORTRS 0x05 +#define EIO_8PORTDI 0x00 +#define EIO_8PORTM 0x06 +#define EIO_MK3 0x03 +#define EIO_IDBITMASK 0x07 + +#define EIO_BRDMASK 0xf0 +#define ID_BRD4 0x10 +#define ID_BRD8 0x20 +#define ID_BRD16 0x30 + +#define EIO_INTRPEND 0x08 +#define EIO_INTEDGE 0x00 +#define EIO_INTLEVEL 0x08 +#define EIO_0WS 0x10 + +#define ECH_ID 0xa0 +#define ECH_IDBITMASK 0xe0 +#define ECH_BRDENABLE 0x08 +#define ECH_BRDDISABLE 0x00 +#define ECH_INTENABLE 0x01 +#define ECH_INTDISABLE 0x00 +#define ECH_INTLEVEL 0x02 +#define ECH_INTEDGE 0x00 +#define ECH_INTRPEND 0x01 +#define ECH_BRDRESET 0x01 + +#define ECHMC_INTENABLE 0x01 +#define ECHMC_BRDRESET 0x02 + +#define ECH_PNLSTATUS 2 +#define ECH_PNL16PORT 0x20 +#define ECH_PNLIDMASK 0x07 +#define ECH_PNLXPID 0x40 +#define ECH_PNLINTRPEND 0x80 + +#define ECH_ADDR2MASK 0x1e0 + +/* + * Define the vector mapping bits for the programmable interrupt board + * hardware. These bits encode the interrupt for the board to use - it + * is software selectable (except the EIO-8M). + */ +static unsigned char stl_vecmap[] = { + 0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07, + 0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03 +}; + +/* + * Set up enable and disable macros for the ECH boards. They require + * the secondary io address space to be activated and deactivated. + * This way all ECH boards can share their secondary io region. + * If this is an ECH-PCI board then also need to set the page pointer + * to point to the correct page. + */ +#define BRDENABLE(brdnr,pagenr) \ + if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \ + outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE), \ + stl_brds[(brdnr)]->ioctrl); \ + else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI) \ + outb((pagenr), stl_brds[(brdnr)]->ioctrl); + +#define BRDDISABLE(brdnr) \ + if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \ + outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE), \ + stl_brds[(brdnr)]->ioctrl); + +#define STL_CD1400MAXBAUD 230400 +#define STL_SC26198MAXBAUD 460800 + +#define STL_BAUDBASE 115200 +#define STL_CLOSEDELAY (5 * HZ / 10) + +/*****************************************************************************/ + +#ifdef CONFIG_PCI + +/* + * Define the Stallion PCI vendor and device IDs. + */ +#ifndef PCI_VENDOR_ID_STALLION +#define PCI_VENDOR_ID_STALLION 0x124d +#endif +#ifndef PCI_DEVICE_ID_ECHPCI832 +#define PCI_DEVICE_ID_ECHPCI832 0x0000 +#endif +#ifndef PCI_DEVICE_ID_ECHPCI864 +#define PCI_DEVICE_ID_ECHPCI864 0x0002 +#endif +#ifndef PCI_DEVICE_ID_EIOPCI +#define PCI_DEVICE_ID_EIOPCI 0x0003 +#endif + +/* + * Define structure to hold all Stallion PCI boards. + */ +typedef struct stlpcibrd { + unsigned short vendid; + unsigned short devid; + int brdtype; +} stlpcibrd_t; + +static stlpcibrd_t stl_pcibrds[] = { + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI864, BRD_ECH64PCI }, + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_EIOPCI, BRD_EASYIOPCI }, + { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI832, BRD_ECHPCI }, + { PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87410, BRD_ECHPCI }, +}; + +static int stl_nrpcibrds = sizeof(stl_pcibrds) / sizeof(stlpcibrd_t); + +#endif + +/*****************************************************************************/ + +/* + * Define macros to extract a brd/port number from a minor number. + */ +#define MINOR2BRD(min) (((min) & 0xc0) >> 6) +#define MINOR2PORT(min) ((min) & 0x3f) + +/* + * Define a baud rate table that converts termios baud rate selector + * into the actual baud rate value. All baud rate calculations are + * based on the actual baud rate required. + */ +static unsigned int stl_baudrates[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 +}; + +/* + * Define some handy local macros... + */ +#undef MIN +#define MIN(a,b) (((a) <= (b)) ? (a) : (b)) + +#undef TOLOWER +#define TOLOWER(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x)) + +/*****************************************************************************/ + +/* + * Declare all those functions in this driver! + */ + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +static void stl_argbrds(void); +static int stl_parsebrd(stlconf_t *confp, char **argp); + +static unsigned long stl_atol(char *str); +#endif + +int stl_init(void); +static int stl_open(struct tty_struct *tty, struct file *filp); +static void stl_close(struct tty_struct *tty, struct file *filp); +static int stl_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count); +static void stl_putchar(struct tty_struct *tty, unsigned char ch); +static void stl_flushchars(struct tty_struct *tty); +static int stl_writeroom(struct tty_struct *tty); +static int stl_charsinbuffer(struct tty_struct *tty); +static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg); +static void stl_settermios(struct tty_struct *tty, struct termios *old); +static void stl_throttle(struct tty_struct *tty); +static void stl_unthrottle(struct tty_struct *tty); +static void stl_stop(struct tty_struct *tty); +static void stl_start(struct tty_struct *tty); +static void stl_flushbuffer(struct tty_struct *tty); +static void stl_breakctl(struct tty_struct *tty, int state); +static void stl_waituntilsent(struct tty_struct *tty, int timeout); +static void stl_sendxchar(struct tty_struct *tty, char ch); +static void stl_hangup(struct tty_struct *tty); +static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg); +static int stl_portinfo(stlport_t *portp, int portnr, char *pos); +static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data); + +static int stl_brdinit(stlbrd_t *brdp); +static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp); +static int stl_mapirq(int irq, char *name); +static void stl_getserial(stlport_t *portp, struct serial_struct *sp); +static int stl_setserial(stlport_t *portp, struct serial_struct *sp); +static int stl_getbrdstats(combrd_t *bp); +static int stl_getportstats(stlport_t *portp, comstats_t *cp); +static int stl_clrportstats(stlport_t *portp, comstats_t *cp); +static int stl_getportstruct(unsigned long arg); +static int stl_getbrdstruct(unsigned long arg); +static int stl_waitcarrier(stlport_t *portp, struct file *filp); +static void stl_delay(int len); +static void stl_intr(int irq, void *dev_id, struct pt_regs *regs); +static void stl_eiointr(stlbrd_t *brdp); +static void stl_echatintr(stlbrd_t *brdp); +static void stl_echmcaintr(stlbrd_t *brdp); +static void stl_echpciintr(stlbrd_t *brdp); +static void stl_echpci64intr(stlbrd_t *brdp); +static void stl_offintr(void *private); +static void *stl_memalloc(int len); +static stlbrd_t *stl_allocbrd(void); +static stlport_t *stl_getport(int brdnr, int panelnr, int portnr); + +static inline int stl_initbrds(void); +static inline int stl_initeio(stlbrd_t *brdp); +static inline int stl_initech(stlbrd_t *brdp); +static inline int stl_getbrdnr(void); + +#ifdef CONFIG_PCI +static inline int stl_findpcibrds(void); +static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp); +#endif + +/* + * CD1400 uart specific handling functions. + */ +static void stl_cd1400setreg(stlport_t *portp, int regnr, int value); +static int stl_cd1400getreg(stlport_t *portp, int regnr); +static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value); +static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp); +static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); +static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp); +static int stl_cd1400getsignals(stlport_t *portp); +static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts); +static void stl_cd1400ccrwait(stlport_t *portp); +static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx); +static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx); +static void stl_cd1400disableintrs(stlport_t *portp); +static void stl_cd1400sendbreak(stlport_t *portp, int len); +static void stl_cd1400flowctrl(stlport_t *portp, int state); +static void stl_cd1400sendflow(stlport_t *portp, int state); +static void stl_cd1400flush(stlport_t *portp); +static int stl_cd1400datastate(stlport_t *portp); +static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase); +static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase); +static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr); +static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr); +static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr); + +static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr); + +/* + * SC26198 uart specific handling functions. + */ +static void stl_sc26198setreg(stlport_t *portp, int regnr, int value); +static int stl_sc26198getreg(stlport_t *portp, int regnr); +static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value); +static int stl_sc26198getglobreg(stlport_t *portp, int regnr); +static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp); +static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); +static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp); +static int stl_sc26198getsignals(stlport_t *portp); +static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts); +static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx); +static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx); +static void stl_sc26198disableintrs(stlport_t *portp); +static void stl_sc26198sendbreak(stlport_t *portp, int len); +static void stl_sc26198flowctrl(stlport_t *portp, int state); +static void stl_sc26198sendflow(stlport_t *portp, int state); +static void stl_sc26198flush(stlport_t *portp); +static int stl_sc26198datastate(stlport_t *portp); +static void stl_sc26198wait(stlport_t *portp); +static void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty); +static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase); +static void stl_sc26198txisr(stlport_t *port); +static void stl_sc26198rxisr(stlport_t *port, unsigned int iack); +static void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch); +static void stl_sc26198rxbadchars(stlport_t *portp); +static void stl_sc26198otherisr(stlport_t *port, unsigned int iack); + +/*****************************************************************************/ + +/* + * Generic UART support structure. + */ +typedef struct uart { + int (*panelinit)(stlbrd_t *brdp, stlpanel_t *panelp); + void (*portinit)(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp); + void (*setport)(stlport_t *portp, struct termios *tiosp); + int (*getsignals)(stlport_t *portp); + void (*setsignals)(stlport_t *portp, int dtr, int rts); + void (*enablerxtx)(stlport_t *portp, int rx, int tx); + void (*startrxtx)(stlport_t *portp, int rx, int tx); + void (*disableintrs)(stlport_t *portp); + void (*sendbreak)(stlport_t *portp, int len); + void (*flowctrl)(stlport_t *portp, int state); + void (*sendflow)(stlport_t *portp, int state); + void (*flush)(stlport_t *portp); + int (*datastate)(stlport_t *portp); + void (*intr)(stlpanel_t *panelp, unsigned int iobase); +} uart_t; + +/* + * Define some macros to make calling these functions nice and clean. + */ +#define stl_panelinit (* ((uart_t *) panelp->uartp)->panelinit) +#define stl_portinit (* ((uart_t *) portp->uartp)->portinit) +#define stl_setport (* ((uart_t *) portp->uartp)->setport) +#define stl_getsignals (* ((uart_t *) portp->uartp)->getsignals) +#define stl_setsignals (* ((uart_t *) portp->uartp)->setsignals) +#define stl_enablerxtx (* ((uart_t *) portp->uartp)->enablerxtx) +#define stl_startrxtx (* ((uart_t *) portp->uartp)->startrxtx) +#define stl_disableintrs (* ((uart_t *) portp->uartp)->disableintrs) +#define stl_sendbreak (* ((uart_t *) portp->uartp)->sendbreak) +#define stl_flowctrl (* ((uart_t *) portp->uartp)->flowctrl) +#define stl_sendflow (* ((uart_t *) portp->uartp)->sendflow) +#define stl_flush (* ((uart_t *) portp->uartp)->flush) +#define stl_datastate (* ((uart_t *) portp->uartp)->datastate) + +/*****************************************************************************/ + +/* + * CD1400 UART specific data initialization. + */ +static uart_t stl_cd1400uart = { + stl_cd1400panelinit, + stl_cd1400portinit, + stl_cd1400setport, + stl_cd1400getsignals, + stl_cd1400setsignals, + stl_cd1400enablerxtx, + stl_cd1400startrxtx, + stl_cd1400disableintrs, + stl_cd1400sendbreak, + stl_cd1400flowctrl, + stl_cd1400sendflow, + stl_cd1400flush, + stl_cd1400datastate, + stl_cd1400eiointr +}; + +/* + * Define the offsets within the register bank of a cd1400 based panel. + * These io address offsets are common to the EasyIO board as well. + */ +#define EREG_ADDR 0 +#define EREG_DATA 4 +#define EREG_RXACK 5 +#define EREG_TXACK 6 +#define EREG_MDACK 7 + +#define EREG_BANKSIZE 8 + +#define CD1400_CLK 25000000 +#define CD1400_CLK8M 20000000 + +/* + * Define the cd1400 baud rate clocks. These are used when calculating + * what clock and divisor to use for the required baud rate. Also + * define the maximum baud rate allowed, and the default base baud. + */ +static int stl_cd1400clkdivs[] = { + CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4 +}; + +/*****************************************************************************/ + +/* + * SC26198 UART specific data initization. + */ +static uart_t stl_sc26198uart = { + stl_sc26198panelinit, + stl_sc26198portinit, + stl_sc26198setport, + stl_sc26198getsignals, + stl_sc26198setsignals, + stl_sc26198enablerxtx, + stl_sc26198startrxtx, + stl_sc26198disableintrs, + stl_sc26198sendbreak, + stl_sc26198flowctrl, + stl_sc26198sendflow, + stl_sc26198flush, + stl_sc26198datastate, + stl_sc26198intr +}; + +/* + * Define the offsets within the register bank of a sc26198 based panel. + */ +#define XP_DATA 0 +#define XP_ADDR 1 +#define XP_MODID 2 +#define XP_STATUS 2 +#define XP_IACK 3 + +#define XP_BANKSIZE 4 + +/* + * Define the sc26198 baud rate table. Offsets within the table + * represent the actual baud rate selector of sc26198 registers. + */ +static unsigned int sc26198_baudtable[] = { + 50, 75, 150, 200, 300, 450, 600, 900, 1200, 1800, 2400, 3600, + 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200, + 230400, 460800, 921600 +}; + +#define SC26198_NRBAUDS (sizeof(sc26198_baudtable) / sizeof(unsigned int)) + +/*****************************************************************************/ + +/* + * Define the driver info for a user level control device. Used mainly + * to get at port stats - only not using the port device itself. + */ +static struct file_operations stl_fsiomem = { + owner: THIS_MODULE, + ioctl: stl_memioctl, +}; + +/*****************************************************************************/ + +static devfs_handle_t devfs_handle = NULL; + +#ifdef MODULE + +/* + * Loadable module initialization stuff. + */ + +int init_module() +{ + unsigned long flags; + +#if DEBUG + printk("init_module()\n"); +#endif + + save_flags(flags); + cli(); + stl_init(); + restore_flags(flags); + + return(0); +} + +/*****************************************************************************/ + +void cleanup_module() +{ + stlbrd_t *brdp; + stlpanel_t *panelp; + stlport_t *portp; + unsigned long flags; + int i, j, k; + +#if DEBUG + printk("cleanup_module()\n"); +#endif + + printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle, + stl_drvversion); + + save_flags(flags); + cli(); + +/* + * Free up all allocated resources used by the ports. This includes + * memory and interrupts. As part of this process we will also do + * a hangup on every open port - to try to flush out any processes + * hanging onto ports. + */ + i = tty_unregister_driver(&stl_serial); + j = tty_unregister_driver(&stl_callout); + if (i || j) { + printk("STALLION: failed to un-register tty driver, " + "errno=%d,%d\n", -i, -j); + restore_flags(flags); + return; + } + devfs_unregister (devfs_handle); + if ((i = devfs_unregister_chrdev(STL_SIOMEMMAJOR, "staliomem"))) + printk("STALLION: failed to un-register serial memory device, " + "errno=%d\n", -i); + + if (stl_tmpwritebuf != (char *) NULL) + kfree(stl_tmpwritebuf); + + for (i = 0; (i < stl_nrbrds); i++) { + if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) + continue; + for (j = 0; (j < STL_MAXPANELS); j++) { + panelp = brdp->panels[j]; + if (panelp == (stlpanel_t *) NULL) + continue; + for (k = 0; (k < STL_PORTSPERPANEL); k++) { + portp = panelp->ports[k]; + if (portp == (stlport_t *) NULL) + continue; + if (portp->tty != (struct tty_struct *) NULL) + stl_hangup(portp->tty); + if (portp->tx.buf != (char *) NULL) + kfree(portp->tx.buf); + kfree(portp); + } + kfree(panelp); + } + + release_region(brdp->ioaddr1, brdp->iosize1); + if (brdp->iosize2 > 0) + release_region(brdp->ioaddr2, brdp->iosize2); + + kfree(brdp); + stl_brds[i] = (stlbrd_t *) NULL; + } + + for (i = 0; (i < stl_numintrs); i++) + free_irq(stl_gotintrs[i], NULL); + + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Check for any arguments passed in on the module load command line. + */ + +static void stl_argbrds() +{ + stlconf_t conf; + stlbrd_t *brdp; + int nrargs, i; + +#if DEBUG + printk("stl_argbrds()\n"); +#endif + + nrargs = sizeof(stl_brdsp) / sizeof(char **); + + for (i = stl_nrbrds; (i < nrargs); i++) { + memset(&conf, 0, sizeof(conf)); + if (stl_parsebrd(&conf, stl_brdsp[i]) == 0) + continue; + if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL) + continue; + stl_nrbrds = i + 1; + brdp->brdnr = i; + brdp->brdtype = conf.brdtype; + brdp->ioaddr1 = conf.ioaddr1; + brdp->ioaddr2 = conf.ioaddr2; + brdp->irq = conf.irq; + brdp->irqtype = conf.irqtype; + stl_brdinit(brdp); + } +} + +/*****************************************************************************/ + +/* + * Convert an ascii string number into an unsigned long. + */ + +static unsigned long stl_atol(char *str) +{ + unsigned long val; + int base, c; + char *sp; + + val = 0; + sp = str; + if ((*sp == '0') && (*(sp+1) == 'x')) { + base = 16; + sp += 2; + } else if (*sp == '0') { + base = 8; + sp++; + } else { + base = 10; + } + + for (; (*sp != 0); sp++) { + c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0'); + if ((c < 0) || (c >= base)) { + printk("STALLION: invalid argument %s\n", str); + val = 0; + break; + } + val = (val * base) + c; + } + return(val); +} + +/*****************************************************************************/ + +/* + * Parse the supplied argument string, into the board conf struct. + */ + +static int stl_parsebrd(stlconf_t *confp, char **argp) +{ + char *sp; + int nrbrdnames, i; + +#if DEBUG + printk("stl_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp); +#endif + + if ((argp[0] == (char *) NULL) || (*argp[0] == 0)) + return(0); + + for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++) + *sp = TOLOWER(*sp); + + nrbrdnames = sizeof(stl_brdstr) / sizeof(stlbrdtype_t); + for (i = 0; (i < nrbrdnames); i++) { + if (strcmp(stl_brdstr[i].name, argp[0]) == 0) + break; + } + if (i >= nrbrdnames) { + printk("STALLION: unknown board name, %s?\n", argp[0]); + return(0); + } + + confp->brdtype = stl_brdstr[i].type; + + i = 1; + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->ioaddr1 = stl_atol(argp[i]); + i++; + if (confp->brdtype == BRD_ECH) { + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->ioaddr2 = stl_atol(argp[i]); + i++; + } + if ((argp[i] != (char *) NULL) && (*argp[i] != 0)) + confp->irq = stl_atol(argp[i]); + return(1); +} + +#endif + +/*****************************************************************************/ + +/* + * Local driver kernel memory allocation routine. + */ + +static void *stl_memalloc(int len) +{ + return((void *) kmalloc(len, GFP_KERNEL)); +} + +/*****************************************************************************/ + +/* + * Allocate a new board structure. Fill out the basic info in it. + */ + +static stlbrd_t *stl_allocbrd() +{ + stlbrd_t *brdp; + + brdp = (stlbrd_t *) stl_memalloc(sizeof(stlbrd_t)); + if (brdp == (stlbrd_t *) NULL) { + printk("STALLION: failed to allocate memory (size=%d)\n", + sizeof(stlbrd_t)); + return((stlbrd_t *) NULL); + } + + memset(brdp, 0, sizeof(stlbrd_t)); + brdp->magic = STL_BOARDMAGIC; + return(brdp); +} + +/*****************************************************************************/ + +static int stl_open(struct tty_struct *tty, struct file *filp) +{ + stlport_t *portp; + stlbrd_t *brdp; + unsigned int minordev; + int brdnr, panelnr, portnr, rc; + +#if DEBUG + printk("stl_open(tty=%x,filp=%x): device=%x\n", (int) tty, + (int) filp, tty->device); +#endif + + minordev = MINOR(tty->device); + brdnr = MINOR2BRD(minordev); + if (brdnr >= stl_nrbrds) + return(-ENODEV); + brdp = stl_brds[brdnr]; + if (brdp == (stlbrd_t *) NULL) + return(-ENODEV); + minordev = MINOR2PORT(minordev); + for (portnr = -1, panelnr = 0; (panelnr < STL_MAXPANELS); panelnr++) { + if (brdp->panels[panelnr] == (stlpanel_t *) NULL) + break; + if (minordev < brdp->panels[panelnr]->nrports) { + portnr = minordev; + break; + } + minordev -= brdp->panels[panelnr]->nrports; + } + if (portnr < 0) + return(-ENODEV); + + portp = brdp->panels[panelnr]->ports[portnr]; + if (portp == (stlport_t *) NULL) + return(-ENODEV); + + MOD_INC_USE_COUNT; + +/* + * On the first open of the device setup the port hardware, and + * initialize the per port data structure. + */ + portp->tty = tty; + tty->driver_data = portp; + portp->refcount++; + + if ((portp->flags & ASYNC_INITIALIZED) == 0) { + if (portp->tx.buf == (char *) NULL) { + portp->tx.buf = (char *) stl_memalloc(STL_TXBUFSIZE); + if (portp->tx.buf == (char *) NULL) + return(-ENOMEM); + portp->tx.head = portp->tx.buf; + portp->tx.tail = portp->tx.buf; + } + stl_setport(portp, tty->termios); + portp->sigs = stl_getsignals(portp); + stl_setsignals(portp, 1, 1); + stl_enablerxtx(portp, 1, 1); + stl_startrxtx(portp, 1, 0); + clear_bit(TTY_IO_ERROR, &tty->flags); + portp->flags |= ASYNC_INITIALIZED; + } + +/* + * Check if this port is in the middle of closing. If so then wait + * until it is closed then return error status, based on flag settings. + * The sleep here does not need interrupt protection since the wakeup + * for it is done with the same context. + */ + if (portp->flags & ASYNC_CLOSING) { + interruptible_sleep_on(&portp->close_wait); + if (portp->flags & ASYNC_HUP_NOTIFY) + return(-EAGAIN); + return(-ERESTARTSYS); + } + +/* + * Based on type of open being done check if it can overlap with any + * previous opens still in effect. If we are a normal serial device + * then also we might have to wait for carrier. + */ + if (tty->driver.subtype == STL_DRVTYPCALLOUT) { + if (portp->flags & ASYNC_NORMAL_ACTIVE) + return(-EBUSY); + if (portp->flags & ASYNC_CALLOUT_ACTIVE) { + if ((portp->flags & ASYNC_SESSION_LOCKOUT) && + (portp->session != current->session)) + return(-EBUSY); + if ((portp->flags & ASYNC_PGRP_LOCKOUT) && + (portp->pgrp != current->pgrp)) + return(-EBUSY); + } + portp->flags |= ASYNC_CALLOUT_ACTIVE; + } else { + if (filp->f_flags & O_NONBLOCK) { + if (portp->flags & ASYNC_CALLOUT_ACTIVE) + return(-EBUSY); + } else { + if ((rc = stl_waitcarrier(portp, filp)) != 0) + return(rc); + } + portp->flags |= ASYNC_NORMAL_ACTIVE; + } + + if ((portp->refcount == 1) && (portp->flags & ASYNC_SPLIT_TERMIOS)) { + if (tty->driver.subtype == STL_DRVTYPSERIAL) + *tty->termios = portp->normaltermios; + else + *tty->termios = portp->callouttermios; + stl_setport(portp, tty->termios); + } + + portp->session = current->session; + portp->pgrp = current->pgrp; + return(0); +} + +/*****************************************************************************/ + +/* + * Possibly need to wait for carrier (DCD signal) to come high. Say + * maybe because if we are clocal then we don't need to wait... + */ + +static int stl_waitcarrier(stlport_t *portp, struct file *filp) +{ + unsigned long flags; + int rc, doclocal; + +#if DEBUG + printk("stl_waitcarrier(portp=%x,filp=%x)\n", (int) portp, (int) filp); +#endif + + rc = 0; + doclocal = 0; + + if (portp->flags & ASYNC_CALLOUT_ACTIVE) { + if (portp->normaltermios.c_cflag & CLOCAL) + doclocal++; + } else { + if (portp->tty->termios->c_cflag & CLOCAL) + doclocal++; + } + + save_flags(flags); + cli(); + portp->openwaitcnt++; + if (! tty_hung_up_p(filp)) + portp->refcount--; + + for (;;) { + if ((portp->flags & ASYNC_CALLOUT_ACTIVE) == 0) + stl_setsignals(portp, 1, 1); + if (tty_hung_up_p(filp) || + ((portp->flags & ASYNC_INITIALIZED) == 0)) { + if (portp->flags & ASYNC_HUP_NOTIFY) + rc = -EBUSY; + else + rc = -ERESTARTSYS; + break; + } + if (((portp->flags & ASYNC_CALLOUT_ACTIVE) == 0) && + ((portp->flags & ASYNC_CLOSING) == 0) && + (doclocal || (portp->sigs & TIOCM_CD))) { + break; + } + if (signal_pending(current)) { + rc = -ERESTARTSYS; + break; + } + interruptible_sleep_on(&portp->open_wait); + } + + if (! tty_hung_up_p(filp)) + portp->refcount++; + portp->openwaitcnt--; + restore_flags(flags); + + return(rc); +} + +/*****************************************************************************/ + +static void stl_close(struct tty_struct *tty, struct file *filp) +{ + stlport_t *portp; + unsigned long flags; + +#if DEBUG + printk("stl_close(tty=%x,filp=%x)\n", (int) tty, (int) filp); +#endif + + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + save_flags(flags); + cli(); + if (tty_hung_up_p(filp)) { + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } + if ((tty->count == 1) && (portp->refcount != 1)) + portp->refcount = 1; + if (portp->refcount-- > 1) { + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } + + portp->refcount = 0; + portp->flags |= ASYNC_CLOSING; + + if (portp->flags & ASYNC_NORMAL_ACTIVE) + portp->normaltermios = *tty->termios; + if (portp->flags & ASYNC_CALLOUT_ACTIVE) + portp->callouttermios = *tty->termios; + +/* + * May want to wait for any data to drain before closing. The BUSY + * flag keeps track of whether we are still sending or not - it is + * very accurate for the cd1400, not quite so for the sc26198. + * (The sc26198 has no "end-of-data" interrupt only empty FIFO) + */ + tty->closing = 1; + if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, portp->closing_wait); + stl_waituntilsent(tty, (HZ / 2)); + + portp->flags &= ~ASYNC_INITIALIZED; + stl_disableintrs(portp); + if (tty->termios->c_cflag & HUPCL) + stl_setsignals(portp, 0, 0); + stl_enablerxtx(portp, 0, 0); + stl_flushbuffer(tty); + portp->istate = 0; + if (portp->tx.buf != (char *) NULL) { + kfree(portp->tx.buf); + portp->tx.buf = (char *) NULL; + portp->tx.head = (char *) NULL; + portp->tx.tail = (char *) NULL; + } + set_bit(TTY_IO_ERROR, &tty->flags); + if (tty->ldisc.flush_buffer) + (tty->ldisc.flush_buffer)(tty); + + tty->closing = 0; + portp->tty = (struct tty_struct *) NULL; + + if (portp->openwaitcnt) { + if (portp->close_delay) + stl_delay(portp->close_delay); + wake_up_interruptible(&portp->open_wait); + } + + portp->flags &= ~(ASYNC_CALLOUT_ACTIVE | ASYNC_NORMAL_ACTIVE | + ASYNC_CLOSING); + wake_up_interruptible(&portp->close_wait); + MOD_DEC_USE_COUNT; + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Wait for a specified delay period, this is not a busy-loop. It will + * give up the processor while waiting. Unfortunately this has some + * rather intimate knowledge of the process management stuff. + */ + +static void stl_delay(int len) +{ +#if DEBUG + printk("stl_delay(len=%d)\n", len); +#endif + if (len > 0) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(len); + current->state = TASK_RUNNING; + } +} + +/*****************************************************************************/ + +/* + * Write routine. Take data and stuff it in to the TX ring queue. + * If transmit interrupts are not running then start them. + */ + +static int stl_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) +{ + stlport_t *portp; + unsigned int len, stlen; + unsigned char *chbuf; + char *head, *tail; + +#if DEBUG + printk("stl_write(tty=%x,from_user=%d,buf=%x,count=%d)\n", + (int) tty, from_user, (int) buf, count); +#endif + + if ((tty == (struct tty_struct *) NULL) || + (stl_tmpwritebuf == (char *) NULL)) + return(0); + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return(0); + if (portp->tx.buf == (char *) NULL) + return(0); + +/* + * If copying direct from user space we must cater for page faults, + * causing us to "sleep" here for a while. To handle this copy in all + * the data we need now, into a local buffer. Then when we got it all + * copy it into the TX buffer. + */ + chbuf = (unsigned char *) buf; + if (from_user) { + head = portp->tx.head; + tail = portp->tx.tail; + len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) : + (tail - head - 1); + count = MIN(len, count); + + down(&stl_tmpwritesem); + copy_from_user(stl_tmpwritebuf, chbuf, count); + chbuf = &stl_tmpwritebuf[0]; + } + + head = portp->tx.head; + tail = portp->tx.tail; + if (head >= tail) { + len = STL_TXBUFSIZE - (head - tail) - 1; + stlen = STL_TXBUFSIZE - (head - portp->tx.buf); + } else { + len = tail - head - 1; + stlen = len; + } + + len = MIN(len, count); + count = 0; + while (len > 0) { + stlen = MIN(len, stlen); + memcpy(head, chbuf, stlen); + len -= stlen; + chbuf += stlen; + count += stlen; + head += stlen; + if (head >= (portp->tx.buf + STL_TXBUFSIZE)) { + head = portp->tx.buf; + stlen = tail - head; + } + } + portp->tx.head = head; + + clear_bit(ASYI_TXLOW, &portp->istate); + stl_startrxtx(portp, -1, 1); + + if (from_user) + up(&stl_tmpwritesem); + + return(count); +} + +/*****************************************************************************/ + +static void stl_putchar(struct tty_struct *tty, unsigned char ch) +{ + stlport_t *portp; + unsigned int len; + char *head, *tail; + +#if DEBUG + printk("stl_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + if (portp->tx.buf == (char *) NULL) + return; + + head = portp->tx.head; + tail = portp->tx.tail; + + len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail)) : (tail - head); + len--; + + if (len > 0) { + *head++ = ch; + if (head >= (portp->tx.buf + STL_TXBUFSIZE)) + head = portp->tx.buf; + } + portp->tx.head = head; +} + +/*****************************************************************************/ + +/* + * If there are any characters in the buffer then make sure that TX + * interrupts are on and get'em out. Normally used after the putchar + * routine has been called. + */ + +static void stl_flushchars(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_flushchars(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + if (portp->tx.buf == (char *) NULL) + return; + +#if 0 + if (tty->stopped || tty->hw_stopped || + (portp->tx.head == portp->tx.tail)) + return; +#endif + stl_startrxtx(portp, -1, 1); +} + +/*****************************************************************************/ + +static int stl_writeroom(struct tty_struct *tty) +{ + stlport_t *portp; + char *head, *tail; + +#if DEBUG + printk("stl_writeroom(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return(0); + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return(0); + if (portp->tx.buf == (char *) NULL) + return(0); + + head = portp->tx.head; + tail = portp->tx.tail; + return((head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) : (tail - head - 1)); +} + +/*****************************************************************************/ + +/* + * Return number of chars in the TX buffer. Normally we would just + * calculate the number of chars in the buffer and return that, but if + * the buffer is empty and TX interrupts are still on then we return + * that the buffer still has 1 char in it. This way whoever called us + * will not think that ALL chars have drained - since the UART still + * must have some chars in it (we are busy after all). + */ + +static int stl_charsinbuffer(struct tty_struct *tty) +{ + stlport_t *portp; + unsigned int size; + char *head, *tail; + +#if DEBUG + printk("stl_charsinbuffer(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return(0); + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return(0); + if (portp->tx.buf == (char *) NULL) + return(0); + + head = portp->tx.head; + tail = portp->tx.tail; + size = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head)); + if ((size == 0) && test_bit(ASYI_TXBUSY, &portp->istate)) + size = 1; + return(size); +} + +/*****************************************************************************/ + +/* + * Generate the serial struct info. + */ + +static void stl_getserial(stlport_t *portp, struct serial_struct *sp) +{ + struct serial_struct sio; + stlbrd_t *brdp; + +#if DEBUG + printk("stl_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp); +#endif + + memset(&sio, 0, sizeof(struct serial_struct)); + sio.line = portp->portnr; + sio.port = portp->ioaddr; + sio.flags = portp->flags; + sio.baud_base = portp->baud_base; + sio.close_delay = portp->close_delay; + sio.closing_wait = portp->closing_wait; + sio.custom_divisor = portp->custom_divisor; + sio.hub6 = 0; + if (portp->uartp == &stl_cd1400uart) { + sio.type = PORT_CIRRUS; + sio.xmit_fifo_size = CD1400_TXFIFOSIZE; + } else { + sio.type = PORT_UNKNOWN; + sio.xmit_fifo_size = SC26198_TXFIFOSIZE; + } + + brdp = stl_brds[portp->brdnr]; + if (brdp != (stlbrd_t *) NULL) + sio.irq = brdp->irq; + + copy_to_user(sp, &sio, sizeof(struct serial_struct)); +} + +/*****************************************************************************/ + +/* + * Set port according to the serial struct info. + * At this point we do not do any auto-configure stuff, so we will + * just quietly ignore any requests to change irq, etc. + */ + +static int stl_setserial(stlport_t *portp, struct serial_struct *sp) +{ + struct serial_struct sio; + +#if DEBUG + printk("stl_setserial(portp=%x,sp=%x)\n", (int) portp, (int) sp); +#endif + + copy_from_user(&sio, sp, sizeof(struct serial_struct)); + if (!capable(CAP_SYS_ADMIN)) { + if ((sio.baud_base != portp->baud_base) || + (sio.close_delay != portp->close_delay) || + ((sio.flags & ~ASYNC_USR_MASK) != + (portp->flags & ~ASYNC_USR_MASK))) + return(-EPERM); + } + + portp->flags = (portp->flags & ~ASYNC_USR_MASK) | + (sio.flags & ASYNC_USR_MASK); + portp->baud_base = sio.baud_base; + portp->close_delay = sio.close_delay; + portp->closing_wait = sio.closing_wait; + portp->custom_divisor = sio.custom_divisor; + stl_setport(portp, portp->tty->termios); + return(0); +} + +/*****************************************************************************/ + +static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +{ + stlport_t *portp; + unsigned int ival; + int rc; + +#if DEBUG + printk("stl_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n", + (int) tty, (int) file, cmd, (int) arg); +#endif + + if (tty == (struct tty_struct *) NULL) + return(-ENODEV); + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return(-ENODEV); + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return(-EIO); + } + + rc = 0; + + switch (cmd) { + case TIOCGSOFTCAR: + rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0), + (unsigned int *) arg); + break; + case TIOCSSOFTCAR: + if ((rc = verify_area(VERIFY_READ, (void *) arg, + sizeof(int))) == 0) { + get_user(ival, (unsigned int *) arg); + tty->termios->c_cflag = + (tty->termios->c_cflag & ~CLOCAL) | + (ival ? CLOCAL : 0); + } + break; + case TIOCMGET: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(unsigned int))) == 0) { + ival = stl_getsignals(portp); + put_user(ival, (unsigned int *) arg); + } + break; + case TIOCMBIS: + if ((rc = verify_area(VERIFY_READ, (void *) arg, + sizeof(unsigned int))) == 0) { + get_user(ival, (unsigned int *) arg); + stl_setsignals(portp, ((ival & TIOCM_DTR) ? 1 : -1), + ((ival & TIOCM_RTS) ? 1 : -1)); + } + break; + case TIOCMBIC: + if ((rc = verify_area(VERIFY_READ, (void *) arg, + sizeof(unsigned int))) == 0) { + get_user(ival, (unsigned int *) arg); + stl_setsignals(portp, ((ival & TIOCM_DTR) ? 0 : -1), + ((ival & TIOCM_RTS) ? 0 : -1)); + } + break; + case TIOCMSET: + if ((rc = verify_area(VERIFY_READ, (void *) arg, + sizeof(unsigned int))) == 0) { + get_user(ival, (unsigned int *) arg); + stl_setsignals(portp, ((ival & TIOCM_DTR) ? 1 : 0), + ((ival & TIOCM_RTS) ? 1 : 0)); + } + break; + case TIOCGSERIAL: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(struct serial_struct))) == 0) + stl_getserial(portp, (struct serial_struct *) arg); + break; + case TIOCSSERIAL: + if ((rc = verify_area(VERIFY_READ, (void *) arg, + sizeof(struct serial_struct))) == 0) + rc = stl_setserial(portp, (struct serial_struct *) arg); + break; + case COM_GETPORTSTATS: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(comstats_t))) == 0) + rc = stl_getportstats(portp, (comstats_t *) arg); + break; + case COM_CLRPORTSTATS: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(comstats_t))) == 0) + rc = stl_clrportstats(portp, (comstats_t *) arg); + break; + case TIOCSERCONFIG: + case TIOCSERGWILD: + case TIOCSERSWILD: + case TIOCSERGETLSR: + case TIOCSERGSTRUCT: + case TIOCSERGETMULTI: + case TIOCSERSETMULTI: + default: + rc = -ENOIOCTLCMD; + break; + } + + return(rc); +} + +/*****************************************************************************/ + +static void stl_settermios(struct tty_struct *tty, struct termios *old) +{ + stlport_t *portp; + struct termios *tiosp; + +#if DEBUG + printk("stl_settermios(tty=%x,old=%x)\n", (int) tty, (int) old); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + tiosp = tty->termios; + if ((tiosp->c_cflag == old->c_cflag) && + (tiosp->c_iflag == old->c_iflag)) + return; + + stl_setport(portp, tiosp); + stl_setsignals(portp, ((tiosp->c_cflag & (CBAUD & ~CBAUDEX)) ? 1 : 0), + -1); + if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0)) { + tty->hw_stopped = 0; + stl_start(tty); + } + if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL)) + wake_up_interruptible(&portp->open_wait); +} + +/*****************************************************************************/ + +/* + * Attempt to flow control who ever is sending us data. Based on termios + * settings use software or/and hardware flow control. + */ + +static void stl_throttle(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_throttle(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + stl_flowctrl(portp, 0); +} + +/*****************************************************************************/ + +/* + * Unflow control the device sending us data... + */ + +static void stl_unthrottle(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_unthrottle(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + stl_flowctrl(portp, 1); +} + +/*****************************************************************************/ + +/* + * Stop the transmitter. Basically to do this we will just turn TX + * interrupts off. + */ + +static void stl_stop(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_stop(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + stl_startrxtx(portp, -1, 0); +} + +/*****************************************************************************/ + +/* + * Start the transmitter again. Just turn TX interrupts back on. + */ + +static void stl_start(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_start(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + stl_startrxtx(portp, -1, 1); +} + +/*****************************************************************************/ + +/* + * Hangup this port. This is pretty much like closing the port, only + * a little more brutal. No waiting for data to drain. Shutdown the + * port and maybe drop signals. + */ + +static void stl_hangup(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_hangup(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + portp->flags &= ~ASYNC_INITIALIZED; + stl_disableintrs(portp); + if (tty->termios->c_cflag & HUPCL) + stl_setsignals(portp, 0, 0); + stl_enablerxtx(portp, 0, 0); + stl_flushbuffer(tty); + portp->istate = 0; + set_bit(TTY_IO_ERROR, &tty->flags); + if (portp->tx.buf != (char *) NULL) { + kfree(portp->tx.buf); + portp->tx.buf = (char *) NULL; + portp->tx.head = (char *) NULL; + portp->tx.tail = (char *) NULL; + } + portp->tty = (struct tty_struct *) NULL; + portp->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE); + portp->refcount = 0; + wake_up_interruptible(&portp->open_wait); +} + +/*****************************************************************************/ + +static void stl_flushbuffer(struct tty_struct *tty) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_flushbuffer(tty=%x)\n", (int) tty); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + stl_flush(portp); + wake_up_interruptible(&tty->write_wait); + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +} + +/*****************************************************************************/ + +static void stl_breakctl(struct tty_struct *tty, int state) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_breakctl(tty=%x,state=%d)\n", (int) tty, state); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + stl_sendbreak(portp, ((state == -1) ? 1 : 2)); +} + +/*****************************************************************************/ + +static void stl_waituntilsent(struct tty_struct *tty, int timeout) +{ + stlport_t *portp; + unsigned long tend; + +#if DEBUG + printk("stl_waituntilsent(tty=%x,timeout=%d)\n", (int) tty, timeout); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + if (timeout == 0) + timeout = HZ; + tend = jiffies + timeout; + + while (stl_datastate(portp)) { + if (signal_pending(current)) + break; + stl_delay(2); + if (time_after_eq(jiffies, tend)) + break; + } +} + +/*****************************************************************************/ + +static void stl_sendxchar(struct tty_struct *tty, char ch) +{ + stlport_t *portp; + +#if DEBUG + printk("stl_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch); +#endif + + if (tty == (struct tty_struct *) NULL) + return; + portp = tty->driver_data; + if (portp == (stlport_t *) NULL) + return; + + if (ch == STOP_CHAR(tty)) + stl_sendflow(portp, 0); + else if (ch == START_CHAR(tty)) + stl_sendflow(portp, 1); + else + stl_putchar(tty, ch); +} + +/*****************************************************************************/ + +#define MAXLINE 80 + +/* + * Format info for a specified port. The line is deliberately limited + * to 80 characters. (If it is too long it will be truncated, if too + * short then padded with spaces). + */ + +static int stl_portinfo(stlport_t *portp, int portnr, char *pos) +{ + char *sp; + int sigs, cnt; + + sp = pos; + sp += sprintf(sp, "%d: uart:%s tx:%d rx:%d", + portnr, (portp->hwid == 1) ? "SC26198" : "CD1400", + (int) portp->stats.txtotal, (int) portp->stats.rxtotal); + + if (portp->stats.rxframing) + sp += sprintf(sp, " fe:%d", (int) portp->stats.rxframing); + if (portp->stats.rxparity) + sp += sprintf(sp, " pe:%d", (int) portp->stats.rxparity); + if (portp->stats.rxbreaks) + sp += sprintf(sp, " brk:%d", (int) portp->stats.rxbreaks); + if (portp->stats.rxoverrun) + sp += sprintf(sp, " oe:%d", (int) portp->stats.rxoverrun); + + sigs = stl_getsignals(portp); + cnt = sprintf(sp, "%s%s%s%s%s ", + (sigs & TIOCM_RTS) ? "|RTS" : "", + (sigs & TIOCM_CTS) ? "|CTS" : "", + (sigs & TIOCM_DTR) ? "|DTR" : "", + (sigs & TIOCM_CD) ? "|DCD" : "", + (sigs & TIOCM_DSR) ? "|DSR" : ""); + *sp = ' '; + sp += cnt; + + for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++) + *sp++ = ' '; + if (cnt >= MAXLINE) + pos[(MAXLINE - 2)] = '+'; + pos[(MAXLINE - 1)] = '\n'; + + return(MAXLINE); +} + +/*****************************************************************************/ + +/* + * Port info, read from the /proc file system. + */ + +static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + stlbrd_t *brdp; + stlpanel_t *panelp; + stlport_t *portp; + int brdnr, panelnr, portnr, totalport; + int curoff, maxoff; + char *pos; + +#if DEBUG + printk("stl_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x," + "data=%x\n", (int) page, (int) start, (int) off, count, + (int) eof, (int) data); +#endif + + pos = page; + totalport = 0; + curoff = 0; + + if (off == 0) { + pos += sprintf(pos, "%s: version %s", stl_drvtitle, + stl_drvversion); + while (pos < (page + MAXLINE - 1)) + *pos++ = ' '; + *pos++ = '\n'; + } + curoff = MAXLINE; + +/* + * We scan through for each board, panel and port. The offset is + * calculated on the fly, and irrelevant ports are skipped. + */ + for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) { + brdp = stl_brds[brdnr]; + if (brdp == (stlbrd_t *) NULL) + continue; + if (brdp->state == 0) + continue; + + maxoff = curoff + (brdp->nrports * MAXLINE); + if (off >= maxoff) { + curoff = maxoff; + continue; + } + + totalport = brdnr * STL_MAXPORTS; + for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) { + panelp = brdp->panels[panelnr]; + if (panelp == (stlpanel_t *) NULL) + continue; + + maxoff = curoff + (panelp->nrports * MAXLINE); + if (off >= maxoff) { + curoff = maxoff; + totalport += panelp->nrports; + continue; + } + + for (portnr = 0; (portnr < panelp->nrports); portnr++, + totalport++) { + portp = panelp->ports[portnr]; + if (portp == (stlport_t *) NULL) + continue; + if (off >= (curoff += MAXLINE)) + continue; + if ((pos - page + MAXLINE) > count) + goto stl_readdone; + pos += stl_portinfo(portp, totalport, pos); + } + } + } + + *eof = 1; + +stl_readdone: + *start = page; + return(pos - page); +} + +/*****************************************************************************/ + +/* + * All board interrupts are vectored through here first. This code then + * calls off to the approrpriate board interrupt handlers. + */ + +static void stl_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + stlbrd_t *brdp; + int i; + +#if DEBUG + printk("stl_intr(irq=%d,regs=%x)\n", irq, (int) regs); +#endif + + for (i = 0; (i < stl_nrbrds); i++) { + if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) + continue; + if (brdp->state == 0) + continue; + (* brdp->isr)(brdp); + } +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for EasyIO board types. + */ + +static void stl_eiointr(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int iobase; + + panelp = brdp->panels[0]; + iobase = panelp->iobase; + while (inb(brdp->iostatus) & EIO_INTRPEND) + (* panelp->isr)(panelp, iobase); +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for ECH-AT board types. + */ + +static void stl_echatintr(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int ioaddr; + int bnknr; + + outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl); + + while (inb(brdp->iostatus) & ECH_INTRPEND) { + for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) { + ioaddr = brdp->bnkstataddr[bnknr]; + if (inb(ioaddr) & ECH_PNLINTRPEND) { + panelp = brdp->bnk2panel[bnknr]; + (* panelp->isr)(panelp, (ioaddr & 0xfffc)); + } + } + } + + outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl); +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for ECH-MCA board types. + */ + +static void stl_echmcaintr(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int ioaddr; + int bnknr; + + while (inb(brdp->iostatus) & ECH_INTRPEND) { + for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) { + ioaddr = brdp->bnkstataddr[bnknr]; + if (inb(ioaddr) & ECH_PNLINTRPEND) { + panelp = brdp->bnk2panel[bnknr]; + (* panelp->isr)(panelp, (ioaddr & 0xfffc)); + } + } + } +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for ECH-PCI board types. + */ + +static void stl_echpciintr(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int ioaddr; + int bnknr, recheck; + + while (1) { + recheck = 0; + for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) { + outb(brdp->bnkpageaddr[bnknr], brdp->ioctrl); + ioaddr = brdp->bnkstataddr[bnknr]; + if (inb(ioaddr) & ECH_PNLINTRPEND) { + panelp = brdp->bnk2panel[bnknr]; + (* panelp->isr)(panelp, (ioaddr & 0xfffc)); + recheck++; + } + } + if (! recheck) + break; + } +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for ECH-8/64-PCI board types. + */ + +static void stl_echpci64intr(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int ioaddr; + int bnknr; + + while (inb(brdp->ioctrl) & 0x1) { + for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) { + ioaddr = brdp->bnkstataddr[bnknr]; + if (inb(ioaddr) & ECH_PNLINTRPEND) { + panelp = brdp->bnk2panel[bnknr]; + (* panelp->isr)(panelp, (ioaddr & 0xfffc)); + } + } + } +} + +/*****************************************************************************/ + +/* + * Service an off-level request for some channel. + */ +static void stl_offintr(void *private) +{ + stlport_t *portp; + struct tty_struct *tty; + unsigned int oldsigs; + + portp = private; + +#if DEBUG + printk("stl_offintr(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return; + + tty = portp->tty; + if (tty == (struct tty_struct *) NULL) + return; + + lock_kernel(); + if (test_bit(ASYI_TXLOW, &portp->istate)) { + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + wake_up_interruptible(&tty->write_wait); + } + if (test_bit(ASYI_DCDCHANGE, &portp->istate)) { + clear_bit(ASYI_DCDCHANGE, &portp->istate); + oldsigs = portp->sigs; + portp->sigs = stl_getsignals(portp); + if ((portp->sigs & TIOCM_CD) && ((oldsigs & TIOCM_CD) == 0)) + wake_up_interruptible(&portp->open_wait); + if ((oldsigs & TIOCM_CD) && ((portp->sigs & TIOCM_CD) == 0)) { + if (portp->flags & ASYNC_CHECK_CD) { + if (! ((portp->flags & ASYNC_CALLOUT_ACTIVE) && + (portp->flags & ASYNC_CALLOUT_NOHUP))) { + tty_hangup(tty); + } + } + } + } + unlock_kernel(); +} + +/*****************************************************************************/ + +/* + * Map in interrupt vector to this driver. Check that we don't + * already have this vector mapped, we might be sharing this + * interrupt across multiple boards. + */ + +static int __init stl_mapirq(int irq, char *name) +{ + int rc, i; + +#if DEBUG + printk("stl_mapirq(irq=%d,name=%s)\n", irq, name); +#endif + + rc = 0; + for (i = 0; (i < stl_numintrs); i++) { + if (stl_gotintrs[i] == irq) + break; + } + if (i >= stl_numintrs) { + if (request_irq(irq, stl_intr, SA_SHIRQ, name, NULL) != 0) { + printk("STALLION: failed to register interrupt " + "routine for %s irq=%d\n", name, irq); + rc = -ENODEV; + } else { + stl_gotintrs[stl_numintrs++] = irq; + } + } + return(rc); +} + +/*****************************************************************************/ + +/* + * Initialize all the ports on a panel. + */ + +static int __init stl_initports(stlbrd_t *brdp, stlpanel_t *panelp) +{ + stlport_t *portp; + int chipmask, i; + +#if DEBUG + printk("stl_initports(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp); +#endif + + chipmask = stl_panelinit(brdp, panelp); + +/* + * All UART's are initialized (if found!). Now go through and setup + * each ports data structures. + */ + for (i = 0; (i < panelp->nrports); i++) { + portp = (stlport_t *) stl_memalloc(sizeof(stlport_t)); + if (portp == (stlport_t *) NULL) { + printk("STALLION: failed to allocate memory " + "(size=%d)\n", sizeof(stlport_t)); + break; + } + memset(portp, 0, sizeof(stlport_t)); + + portp->magic = STL_PORTMAGIC; + portp->portnr = i; + portp->brdnr = panelp->brdnr; + portp->panelnr = panelp->panelnr; + portp->uartp = panelp->uartp; + portp->clk = brdp->clk; + portp->baud_base = STL_BAUDBASE; + portp->close_delay = STL_CLOSEDELAY; + portp->closing_wait = 30 * HZ; + portp->normaltermios = stl_deftermios; + portp->callouttermios = stl_deftermios; + portp->tqueue.routine = stl_offintr; + portp->tqueue.data = portp; + init_waitqueue_head(&portp->open_wait); + init_waitqueue_head(&portp->close_wait); + portp->stats.brd = portp->brdnr; + portp->stats.panel = portp->panelnr; + portp->stats.port = portp->portnr; + panelp->ports[i] = portp; + stl_portinit(brdp, panelp, portp); + } + + return(0); +} + +/*****************************************************************************/ + +/* + * Try to find and initialize an EasyIO board. + */ + +static inline int stl_initeio(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int status; + char *name; + int rc; + +#if DEBUG + printk("stl_initeio(brdp=%x)\n", (int) brdp); +#endif + + brdp->ioctrl = brdp->ioaddr1 + 1; + brdp->iostatus = brdp->ioaddr1 + 2; + + status = inb(brdp->iostatus); + if ((status & EIO_IDBITMASK) == EIO_MK3) + brdp->ioctrl++; + +/* + * Handle board specific stuff now. The real difference is PCI + * or not PCI. + */ + if (brdp->brdtype == BRD_EASYIOPCI) { + brdp->iosize1 = 0x80; + brdp->iosize2 = 0x80; + name = "serial(EIO-PCI)"; + outb(0x41, (brdp->ioaddr2 + 0x4c)); + } else { + brdp->iosize1 = 8; + name = "serial(EIO)"; + if ((brdp->irq < 0) || (brdp->irq > 15) || + (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) { + printk("STALLION: invalid irq=%d for brd=%d\n", + brdp->irq, brdp->brdnr); + return(-EINVAL); + } + outb((stl_vecmap[brdp->irq] | EIO_0WS | + ((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)), + brdp->ioctrl); + } + + if (check_region(brdp->ioaddr1, brdp->iosize1)) { + printk("STALLION: Warning, board %d I/O address %x conflicts " + "with another device\n", brdp->brdnr, brdp->ioaddr1); + } + if (brdp->iosize2 > 0) { + if (check_region(brdp->ioaddr2, brdp->iosize2)) { + printk("STALLION: Warning, board %d I/O address %x " + "conflicts with another device\n", + brdp->brdnr, brdp->ioaddr2); + } + } + +/* + * Everything looks OK, so let's go ahead and probe for the hardware. + */ + brdp->clk = CD1400_CLK; + brdp->isr = stl_eiointr; + + switch (status & EIO_IDBITMASK) { + case EIO_8PORTM: + brdp->clk = CD1400_CLK8M; + /* fall thru */ + case EIO_8PORTRS: + case EIO_8PORTDI: + brdp->nrports = 8; + break; + case EIO_4PORTRS: + brdp->nrports = 4; + break; + case EIO_MK3: + switch (status & EIO_BRDMASK) { + case ID_BRD4: + brdp->nrports = 4; + break; + case ID_BRD8: + brdp->nrports = 8; + break; + case ID_BRD16: + brdp->nrports = 16; + break; + default: + return(-ENODEV); + } + break; + default: + return(-ENODEV); + } + +/* + * We have verfied that the board is actually present, so now we + * can complete the setup. + */ + request_region(brdp->ioaddr1, brdp->iosize1, name); + if (brdp->iosize2 > 0) + request_region(brdp->ioaddr2, brdp->iosize2, name); + + panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t)); + if (panelp == (stlpanel_t *) NULL) { + printk("STALLION: failed to allocate memory (size=%d)\n", + sizeof(stlpanel_t)); + return(-ENOMEM); + } + memset(panelp, 0, sizeof(stlpanel_t)); + + panelp->magic = STL_PANELMAGIC; + panelp->brdnr = brdp->brdnr; + panelp->panelnr = 0; + panelp->nrports = brdp->nrports; + panelp->iobase = brdp->ioaddr1; + panelp->hwid = status; + if ((status & EIO_IDBITMASK) == EIO_MK3) { + panelp->uartp = (void *) &stl_sc26198uart; + panelp->isr = stl_sc26198intr; + } else { + panelp->uartp = (void *) &stl_cd1400uart; + panelp->isr = stl_cd1400eiointr; + } + + brdp->panels[0] = panelp; + brdp->nrpanels = 1; + brdp->state |= BRD_FOUND; + brdp->hwid = status; + rc = stl_mapirq(brdp->irq, name); + return(rc); +} + +/*****************************************************************************/ + +/* + * Try to find an ECH board and initialize it. This code is capable of + * dealing with all types of ECH board. + */ + +static int inline stl_initech(stlbrd_t *brdp) +{ + stlpanel_t *panelp; + unsigned int status, nxtid, ioaddr, conflict; + int panelnr, banknr, i; + char *name; + +#if DEBUG + printk("stl_initech(brdp=%x)\n", (int) brdp); +#endif + + status = 0; + conflict = 0; + +/* + * Set up the initial board register contents for boards. This varies a + * bit between the different board types. So we need to handle each + * separately. Also do a check that the supplied IRQ is good. + */ + switch (brdp->brdtype) { + + case BRD_ECH: + brdp->isr = stl_echatintr; + brdp->ioctrl = brdp->ioaddr1 + 1; + brdp->iostatus = brdp->ioaddr1 + 1; + status = inb(brdp->iostatus); + if ((status & ECH_IDBITMASK) != ECH_ID) + return(-ENODEV); + if ((brdp->irq < 0) || (brdp->irq > 15) || + (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) { + printk("STALLION: invalid irq=%d for brd=%d\n", + brdp->irq, brdp->brdnr); + return(-EINVAL); + } + status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1); + status |= (stl_vecmap[brdp->irq] << 1); + outb((status | ECH_BRDRESET), brdp->ioaddr1); + brdp->ioctrlval = ECH_INTENABLE | + ((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE); + for (i = 0; (i < 10); i++) + outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl); + brdp->iosize1 = 2; + brdp->iosize2 = 32; + name = "serial(EC8/32)"; + outb(status, brdp->ioaddr1); + break; + + case BRD_ECHMC: + brdp->isr = stl_echmcaintr; + brdp->ioctrl = brdp->ioaddr1 + 0x20; + brdp->iostatus = brdp->ioctrl; + status = inb(brdp->iostatus); + if ((status & ECH_IDBITMASK) != ECH_ID) + return(-ENODEV); + if ((brdp->irq < 0) || (brdp->irq > 15) || + (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) { + printk("STALLION: invalid irq=%d for brd=%d\n", + brdp->irq, brdp->brdnr); + return(-EINVAL); + } + outb(ECHMC_BRDRESET, brdp->ioctrl); + outb(ECHMC_INTENABLE, brdp->ioctrl); + brdp->iosize1 = 64; + name = "serial(EC8/32-MC)"; + break; + + case BRD_ECHPCI: + brdp->isr = stl_echpciintr; + brdp->ioctrl = brdp->ioaddr1 + 2; + brdp->iosize1 = 4; + brdp->iosize2 = 8; + name = "serial(EC8/32-PCI)"; + break; + + case BRD_ECH64PCI: + brdp->isr = stl_echpci64intr; + brdp->ioctrl = brdp->ioaddr2 + 0x40; + outb(0x43, (brdp->ioaddr1 + 0x4c)); + brdp->iosize1 = 0x80; + brdp->iosize2 = 0x80; + name = "serial(EC8/64-PCI)"; + break; + + default: + printk("STALLION: unknown board type=%d\n", brdp->brdtype); + return(-EINVAL); + break; + } + +/* + * Check boards for possible IO address conflicts. We won't actually + * do anything about it here, just issue a warning... + */ + conflict = check_region(brdp->ioaddr1, brdp->iosize1) ? + brdp->ioaddr1 : 0; + if ((conflict == 0) && (brdp->iosize2 > 0)) + conflict = check_region(brdp->ioaddr2, brdp->iosize2) ? + brdp->ioaddr2 : 0; + if (conflict) { + printk("STALLION: Warning, board %d I/O address %x conflicts " + "with another device\n", brdp->brdnr, conflict); + } + + request_region(brdp->ioaddr1, brdp->iosize1, name); + if (brdp->iosize2 > 0) + request_region(brdp->ioaddr2, brdp->iosize2, name); + +/* + * Scan through the secondary io address space looking for panels. + * As we find'em allocate and initialize panel structures for each. + */ + brdp->clk = CD1400_CLK; + brdp->hwid = status; + + ioaddr = brdp->ioaddr2; + banknr = 0; + panelnr = 0; + nxtid = 0; + + for (i = 0; (i < STL_MAXPANELS); i++) { + if (brdp->brdtype == BRD_ECHPCI) { + outb(nxtid, brdp->ioctrl); + ioaddr = brdp->ioaddr2; + } + status = inb(ioaddr + ECH_PNLSTATUS); + if ((status & ECH_PNLIDMASK) != nxtid) + break; + panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t)); + if (panelp == (stlpanel_t *) NULL) { + printk("STALLION: failed to allocate memory " + "(size=%d)\n", sizeof(stlpanel_t)); + break; + } + memset(panelp, 0, sizeof(stlpanel_t)); + panelp->magic = STL_PANELMAGIC; + panelp->brdnr = brdp->brdnr; + panelp->panelnr = panelnr; + panelp->iobase = ioaddr; + panelp->pagenr = nxtid; + panelp->hwid = status; + brdp->bnk2panel[banknr] = panelp; + brdp->bnkpageaddr[banknr] = nxtid; + brdp->bnkstataddr[banknr++] = ioaddr + ECH_PNLSTATUS; + + if (status & ECH_PNLXPID) { + panelp->uartp = (void *) &stl_sc26198uart; + panelp->isr = stl_sc26198intr; + if (status & ECH_PNL16PORT) { + panelp->nrports = 16; + brdp->bnk2panel[banknr] = panelp; + brdp->bnkpageaddr[banknr] = nxtid; + brdp->bnkstataddr[banknr++] = ioaddr + 4 + + ECH_PNLSTATUS; + } else { + panelp->nrports = 8; + } + } else { + panelp->uartp = (void *) &stl_cd1400uart; + panelp->isr = stl_cd1400echintr; + if (status & ECH_PNL16PORT) { + panelp->nrports = 16; + panelp->ackmask = 0x80; + if (brdp->brdtype != BRD_ECHPCI) + ioaddr += EREG_BANKSIZE; + brdp->bnk2panel[banknr] = panelp; + brdp->bnkpageaddr[banknr] = ++nxtid; + brdp->bnkstataddr[banknr++] = ioaddr + + ECH_PNLSTATUS; + } else { + panelp->nrports = 8; + panelp->ackmask = 0xc0; + } + } + + nxtid++; + ioaddr += EREG_BANKSIZE; + brdp->nrports += panelp->nrports; + brdp->panels[panelnr++] = panelp; + if ((brdp->brdtype != BRD_ECHPCI) && + (ioaddr >= (brdp->ioaddr2 + brdp->iosize2))) + break; + } + + brdp->nrpanels = panelnr; + brdp->nrbnks = banknr; + if (brdp->brdtype == BRD_ECH) + outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl); + + brdp->state |= BRD_FOUND; + i = stl_mapirq(brdp->irq, name); + return(i); +} + +/*****************************************************************************/ + +/* + * Initialize and configure the specified board. + * Scan through all the boards in the configuration and see what we + * can find. Handle EIO and the ECH boards a little differently here + * since the initial search and setup is very different. + */ + +static int __init stl_brdinit(stlbrd_t *brdp) +{ + int i; + +#if DEBUG + printk("stl_brdinit(brdp=%x)\n", (int) brdp); +#endif + + switch (brdp->brdtype) { + case BRD_EASYIO: + case BRD_EASYIOPCI: + stl_initeio(brdp); + break; + case BRD_ECH: + case BRD_ECHMC: + case BRD_ECHPCI: + case BRD_ECH64PCI: + stl_initech(brdp); + break; + default: + printk("STALLION: board=%d is unknown board type=%d\n", + brdp->brdnr, brdp->brdtype); + return(ENODEV); + } + + stl_brds[brdp->brdnr] = brdp; + if ((brdp->state & BRD_FOUND) == 0) { + printk("STALLION: %s board not found, board=%d io=%x irq=%d\n", + stl_brdnames[brdp->brdtype], brdp->brdnr, + brdp->ioaddr1, brdp->irq); + return(ENODEV); + } + + for (i = 0; (i < STL_MAXPANELS); i++) + if (brdp->panels[i] != (stlpanel_t *) NULL) + stl_initports(brdp, brdp->panels[i]); + + printk("STALLION: %s found, board=%d io=%x irq=%d " + "nrpanels=%d nrports=%d\n", stl_brdnames[brdp->brdtype], + brdp->brdnr, brdp->ioaddr1, brdp->irq, brdp->nrpanels, + brdp->nrports); + return(0); +} + +/*****************************************************************************/ + +/* + * Find the next available board number that is free. + */ + +static inline int stl_getbrdnr() +{ + int i; + + for (i = 0; (i < STL_MAXBRDS); i++) { + if (stl_brds[i] == (stlbrd_t *) NULL) { + if (i >= stl_nrbrds) + stl_nrbrds = i + 1; + return(i); + } + } + return(-1); +} + +/*****************************************************************************/ + +#ifdef CONFIG_PCI + +/* + * We have a Stallion board. Allocate a board structure and + * initialize it. Read its IO and IRQ resources from PCI + * configuration space. + */ + +static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp) +{ + stlbrd_t *brdp; + +#if DEBUG + printk("stl_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n", brdtype, + devp->bus->number, devp->devfn); +#endif + + if (pci_enable_device(devp)) + return(-EIO); + if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL) + return(-ENOMEM); + if ((brdp->brdnr = stl_getbrdnr()) < 0) { + printk("STALLION: too many boards found, " + "maximum supported %d\n", STL_MAXBRDS); + return(0); + } + brdp->brdtype = brdtype; + +/* + * Different Stallion boards use the BAR registers in different ways, + * so set up io addresses based on board type. + */ +#if DEBUG + printk("%s(%d): BAR[]=%x,%x,%x,%x IRQ=%x\n", __FILE__, __LINE__, + pci_resource_start(devp, 0), pci_resource_start(devp, 1), + pci_resource_start(devp, 2), pci_resource_start(devp, 3), devp->irq); +#endif + +/* + * We have all resources from the board, so let's setup the actual + * board structure now. + */ + switch (brdtype) { + case BRD_ECHPCI: + brdp->ioaddr2 = pci_resource_start(devp, 0); + brdp->ioaddr1 = pci_resource_start(devp, 1); + break; + case BRD_ECH64PCI: + brdp->ioaddr2 = pci_resource_start(devp, 2); + brdp->ioaddr1 = pci_resource_start(devp, 1); + break; + case BRD_EASYIOPCI: + brdp->ioaddr1 = pci_resource_start(devp, 2); + brdp->ioaddr2 = pci_resource_start(devp, 1); + break; + default: + printk("STALLION: unknown PCI board type=%d\n", brdtype); + break; + } + + brdp->irq = devp->irq; + stl_brdinit(brdp); + + return(0); +} + +/*****************************************************************************/ + +/* + * Find all Stallion PCI boards that might be installed. Initialize each + * one as it is found. + */ + + +static inline int stl_findpcibrds() +{ + struct pci_dev *dev = NULL; + int i, rc; + +#if DEBUG + printk("stl_findpcibrds()\n"); +#endif + + if (! pci_present()) + return(0); + + for (i = 0; (i < stl_nrpcibrds); i++) + while ((dev = pci_find_device(stl_pcibrds[i].vendid, + stl_pcibrds[i].devid, dev))) { + +/* + * Found a device on the PCI bus that has our vendor and + * device ID. Need to check now that it is really us. + */ + if ((dev->class >> 8) == PCI_CLASS_STORAGE_IDE) + continue; + + rc = stl_initpcibrd(stl_pcibrds[i].brdtype, dev); + if (rc) + return(rc); + } + + return(0); +} + +#endif + +/*****************************************************************************/ + +/* + * Scan through all the boards in the configuration and see what we + * can find. Handle EIO and the ECH boards a little differently here + * since the initial search and setup is too different. + */ + +static inline int stl_initbrds() +{ + stlbrd_t *brdp; + stlconf_t *confp; + int i; + +#if DEBUG + printk("stl_initbrds()\n"); +#endif + + if (stl_nrbrds > STL_MAXBRDS) { + printk("STALLION: too many boards in configuration table, " + "truncating to %d\n", STL_MAXBRDS); + stl_nrbrds = STL_MAXBRDS; + } + +/* + * Firstly scan the list of static boards configured. Allocate + * resources and initialize the boards as found. + */ + for (i = 0; (i < stl_nrbrds); i++) { + confp = &stl_brdconf[i]; +#ifdef MODULE + stl_parsebrd(confp, stl_brdsp[i]); +#endif + if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL) + return(-ENOMEM); + brdp->brdnr = i; + brdp->brdtype = confp->brdtype; + brdp->ioaddr1 = confp->ioaddr1; + brdp->ioaddr2 = confp->ioaddr2; + brdp->irq = confp->irq; + brdp->irqtype = confp->irqtype; + stl_brdinit(brdp); + } + +/* + * Find any dynamically supported boards. That is via module load + * line options or auto-detected on the PCI bus. + */ +#ifdef MODULE + stl_argbrds(); +#endif +#ifdef CONFIG_PCI + stl_findpcibrds(); +#endif + + return(0); +} + +/*****************************************************************************/ + +/* + * Return the board stats structure to user app. + */ + +static int stl_getbrdstats(combrd_t *bp) +{ + stlbrd_t *brdp; + stlpanel_t *panelp; + int i; + + copy_from_user(&stl_brdstats, bp, sizeof(combrd_t)); + if (stl_brdstats.brd >= STL_MAXBRDS) + return(-ENODEV); + brdp = stl_brds[stl_brdstats.brd]; + if (brdp == (stlbrd_t *) NULL) + return(-ENODEV); + + memset(&stl_brdstats, 0, sizeof(combrd_t)); + stl_brdstats.brd = brdp->brdnr; + stl_brdstats.type = brdp->brdtype; + stl_brdstats.hwid = brdp->hwid; + stl_brdstats.state = brdp->state; + stl_brdstats.ioaddr = brdp->ioaddr1; + stl_brdstats.ioaddr2 = brdp->ioaddr2; + stl_brdstats.irq = brdp->irq; + stl_brdstats.nrpanels = brdp->nrpanels; + stl_brdstats.nrports = brdp->nrports; + for (i = 0; (i < brdp->nrpanels); i++) { + panelp = brdp->panels[i]; + stl_brdstats.panels[i].panel = i; + stl_brdstats.panels[i].hwid = panelp->hwid; + stl_brdstats.panels[i].nrports = panelp->nrports; + } + + copy_to_user(bp, &stl_brdstats, sizeof(combrd_t)); + return(0); +} + +/*****************************************************************************/ + +/* + * Resolve the referenced port number into a port struct pointer. + */ + +static stlport_t *stl_getport(int brdnr, int panelnr, int portnr) +{ + stlbrd_t *brdp; + stlpanel_t *panelp; + + if ((brdnr < 0) || (brdnr >= STL_MAXBRDS)) + return((stlport_t *) NULL); + brdp = stl_brds[brdnr]; + if (brdp == (stlbrd_t *) NULL) + return((stlport_t *) NULL); + if ((panelnr < 0) || (panelnr >= brdp->nrpanels)) + return((stlport_t *) NULL); + panelp = brdp->panels[panelnr]; + if (panelp == (stlpanel_t *) NULL) + return((stlport_t *) NULL); + if ((portnr < 0) || (portnr >= panelp->nrports)) + return((stlport_t *) NULL); + return(panelp->ports[portnr]); +} + +/*****************************************************************************/ + +/* + * Return the port stats structure to user app. A NULL port struct + * pointer passed in means that we need to find out from the app + * what port to get stats for (used through board control device). + */ + +static int stl_getportstats(stlport_t *portp, comstats_t *cp) +{ + unsigned char *head, *tail; + unsigned long flags; + + if (portp == (stlport_t *) NULL) { + copy_from_user(&stl_comstats, cp, sizeof(comstats_t)); + portp = stl_getport(stl_comstats.brd, stl_comstats.panel, + stl_comstats.port); + if (portp == (stlport_t *) NULL) + return(-ENODEV); + } + + portp->stats.state = portp->istate; + portp->stats.flags = portp->flags; + portp->stats.hwid = portp->hwid; + + portp->stats.ttystate = 0; + portp->stats.cflags = 0; + portp->stats.iflags = 0; + portp->stats.oflags = 0; + portp->stats.lflags = 0; + portp->stats.rxbuffered = 0; + + save_flags(flags); + cli(); + if (portp->tty != (struct tty_struct *) NULL) { + if (portp->tty->driver_data == portp) { + portp->stats.ttystate = portp->tty->flags; + portp->stats.rxbuffered = portp->tty->flip.count; + if (portp->tty->termios != (struct termios *) NULL) { + portp->stats.cflags = portp->tty->termios->c_cflag; + portp->stats.iflags = portp->tty->termios->c_iflag; + portp->stats.oflags = portp->tty->termios->c_oflag; + portp->stats.lflags = portp->tty->termios->c_lflag; + } + } + } + restore_flags(flags); + + head = portp->tx.head; + tail = portp->tx.tail; + portp->stats.txbuffered = ((head >= tail) ? (head - tail) : + (STL_TXBUFSIZE - (tail - head))); + + portp->stats.signals = (unsigned long) stl_getsignals(portp); + + copy_to_user(cp, &portp->stats, sizeof(comstats_t)); + return(0); +} + +/*****************************************************************************/ + +/* + * Clear the port stats structure. We also return it zeroed out... + */ + +static int stl_clrportstats(stlport_t *portp, comstats_t *cp) +{ + if (portp == (stlport_t *) NULL) { + copy_from_user(&stl_comstats, cp, sizeof(comstats_t)); + portp = stl_getport(stl_comstats.brd, stl_comstats.panel, + stl_comstats.port); + if (portp == (stlport_t *) NULL) + return(-ENODEV); + } + + memset(&portp->stats, 0, sizeof(comstats_t)); + portp->stats.brd = portp->brdnr; + portp->stats.panel = portp->panelnr; + portp->stats.port = portp->portnr; + copy_to_user(cp, &portp->stats, sizeof(comstats_t)); + return(0); +} + +/*****************************************************************************/ + +/* + * Return the entire driver ports structure to a user app. + */ + +static int stl_getportstruct(unsigned long arg) +{ + stlport_t *portp; + + copy_from_user(&stl_dummyport, (void *) arg, sizeof(stlport_t)); + portp = stl_getport(stl_dummyport.brdnr, stl_dummyport.panelnr, + stl_dummyport.portnr); + if (portp == (stlport_t *) NULL) + return(-ENODEV); + copy_to_user((void *) arg, portp, sizeof(stlport_t)); + return(0); +} + +/*****************************************************************************/ + +/* + * Return the entire driver board structure to a user app. + */ + +static int stl_getbrdstruct(unsigned long arg) +{ + stlbrd_t *brdp; + + copy_from_user(&stl_dummybrd, (void *) arg, sizeof(stlbrd_t)); + if ((stl_dummybrd.brdnr < 0) || (stl_dummybrd.brdnr >= STL_MAXBRDS)) + return(-ENODEV); + brdp = stl_brds[stl_dummybrd.brdnr]; + if (brdp == (stlbrd_t *) NULL) + return(-ENODEV); + copy_to_user((void *) arg, brdp, sizeof(stlbrd_t)); + return(0); +} + +/*****************************************************************************/ + +/* + * The "staliomem" device is also required to do some special operations + * on the board and/or ports. In this driver it is mostly used for stats + * collection. + */ + +static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg) +{ + int brdnr, rc; + +#if DEBUG + printk("stl_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n", (int) ip, + (int) fp, cmd, (int) arg); +#endif + + brdnr = MINOR(ip->i_rdev); + if (brdnr >= STL_MAXBRDS) + return(-ENODEV); + rc = 0; + + switch (cmd) { + case COM_GETPORTSTATS: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(comstats_t))) == 0) + rc = stl_getportstats((stlport_t *) NULL, + (comstats_t *) arg); + break; + case COM_CLRPORTSTATS: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(comstats_t))) == 0) + rc = stl_clrportstats((stlport_t *) NULL, + (comstats_t *) arg); + break; + case COM_GETBRDSTATS: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(combrd_t))) == 0) + rc = stl_getbrdstats((combrd_t *) arg); + break; + case COM_READPORT: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(stlport_t))) == 0) + rc = stl_getportstruct(arg); + break; + case COM_READBOARD: + if ((rc = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(stlbrd_t))) == 0) + rc = stl_getbrdstruct(arg); + break; + default: + rc = -ENOIOCTLCMD; + break; + } + + return(rc); +} + +/*****************************************************************************/ + +int __init stl_init(void) +{ + printk(KERN_INFO "%s: version %s\n", stl_drvtitle, stl_drvversion); + + stl_initbrds(); + +/* + * Allocate a temporary write buffer. + */ + stl_tmpwritebuf = (char *) stl_memalloc(STL_TXBUFSIZE); + if (stl_tmpwritebuf == (char *) NULL) + printk("STALLION: failed to allocate memory (size=%d)\n", + STL_TXBUFSIZE); + +/* + * Set up a character driver for per board stuff. This is mainly used + * to do stats ioctls on the ports. + */ + if (devfs_register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stl_fsiomem)) + printk("STALLION: failed to register serial board device\n"); + devfs_handle = devfs_mk_dir (NULL, "staliomem", NULL); + devfs_register_series (devfs_handle, "%u", 4, DEVFS_FL_DEFAULT, + STL_SIOMEMMAJOR, 0, + S_IFCHR | S_IRUSR | S_IWUSR, + &stl_fsiomem, NULL); + +/* + * Set up the tty driver structure and register us as a driver. + * Also setup the callout tty device. + */ + memset(&stl_serial, 0, sizeof(struct tty_driver)); + stl_serial.magic = TTY_DRIVER_MAGIC; + stl_serial.driver_name = stl_drvname; + stl_serial.name = stl_serialname; + stl_serial.major = STL_SERIALMAJOR; + stl_serial.minor_start = 0; + stl_serial.num = STL_MAXBRDS * STL_MAXPORTS; + stl_serial.type = TTY_DRIVER_TYPE_SERIAL; + stl_serial.subtype = STL_DRVTYPSERIAL; + stl_serial.init_termios = stl_deftermios; + stl_serial.flags = TTY_DRIVER_REAL_RAW; + stl_serial.refcount = &stl_refcount; + stl_serial.table = stl_ttys; + stl_serial.termios = stl_termios; + stl_serial.termios_locked = stl_termioslocked; + + stl_serial.open = stl_open; + stl_serial.close = stl_close; + stl_serial.write = stl_write; + stl_serial.put_char = stl_putchar; + stl_serial.flush_chars = stl_flushchars; + stl_serial.write_room = stl_writeroom; + stl_serial.chars_in_buffer = stl_charsinbuffer; + stl_serial.ioctl = stl_ioctl; + stl_serial.set_termios = stl_settermios; + stl_serial.throttle = stl_throttle; + stl_serial.unthrottle = stl_unthrottle; + stl_serial.stop = stl_stop; + stl_serial.start = stl_start; + stl_serial.hangup = stl_hangup; + stl_serial.flush_buffer = stl_flushbuffer; + stl_serial.break_ctl = stl_breakctl; + stl_serial.wait_until_sent = stl_waituntilsent; + stl_serial.send_xchar = stl_sendxchar; + stl_serial.read_proc = stl_readproc; + + stl_callout = stl_serial; + stl_callout.name = stl_calloutname; + stl_callout.major = STL_CALLOUTMAJOR; + stl_callout.subtype = STL_DRVTYPCALLOUT; + stl_callout.read_proc = 0; + + if (tty_register_driver(&stl_serial)) + printk("STALLION: failed to register serial driver\n"); + if (tty_register_driver(&stl_callout)) + printk("STALLION: failed to register callout driver\n"); + + return(0); +} + +/*****************************************************************************/ +/* CD1400 HARDWARE FUNCTIONS */ +/*****************************************************************************/ + +/* + * These functions get/set/update the registers of the cd1400 UARTs. + * Access to the cd1400 registers is via an address/data io port pair. + * (Maybe should make this inline...) + */ + +static int stl_cd1400getreg(stlport_t *portp, int regnr) +{ + outb((regnr + portp->uartaddr), portp->ioaddr); + return(inb(portp->ioaddr + EREG_DATA)); +} + +static void stl_cd1400setreg(stlport_t *portp, int regnr, int value) +{ + outb((regnr + portp->uartaddr), portp->ioaddr); + outb(value, portp->ioaddr + EREG_DATA); +} + +static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value) +{ + outb((regnr + portp->uartaddr), portp->ioaddr); + if (inb(portp->ioaddr + EREG_DATA) != value) { + outb(value, portp->ioaddr + EREG_DATA); + return(1); + } + return(0); +} + +/*****************************************************************************/ + +/* + * Inbitialize the UARTs in a panel. We don't care what sort of board + * these ports are on - since the port io registers are almost + * identical when dealing with ports. + */ + +static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp) +{ + unsigned int gfrcr; + int chipmask, i, j; + int nrchips, uartaddr, ioaddr; + +#if DEBUG + printk("stl_panelinit(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp); +#endif + + BRDENABLE(panelp->brdnr, panelp->pagenr); + +/* + * Check that each chip is present and started up OK. + */ + chipmask = 0; + nrchips = panelp->nrports / CD1400_PORTS; + for (i = 0; (i < nrchips); i++) { + if (brdp->brdtype == BRD_ECHPCI) { + outb((panelp->pagenr + (i >> 1)), brdp->ioctrl); + ioaddr = panelp->iobase; + } else { + ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1)); + } + uartaddr = (i & 0x01) ? 0x080 : 0; + outb((GFRCR + uartaddr), ioaddr); + outb(0, (ioaddr + EREG_DATA)); + outb((CCR + uartaddr), ioaddr); + outb(CCR_RESETFULL, (ioaddr + EREG_DATA)); + outb(CCR_RESETFULL, (ioaddr + EREG_DATA)); + outb((GFRCR + uartaddr), ioaddr); + for (j = 0; (j < CCR_MAXWAIT); j++) { + if ((gfrcr = inb(ioaddr + EREG_DATA)) != 0) + break; + } + if ((j >= CCR_MAXWAIT) || (gfrcr < 0x40) || (gfrcr > 0x60)) { + printk("STALLION: cd1400 not responding, " + "brd=%d panel=%d chip=%d\n", + panelp->brdnr, panelp->panelnr, i); + continue; + } + chipmask |= (0x1 << i); + outb((PPR + uartaddr), ioaddr); + outb(PPR_SCALAR, (ioaddr + EREG_DATA)); + } + + BRDDISABLE(panelp->brdnr); + return(chipmask); +} + +/*****************************************************************************/ + +/* + * Initialize hardware specific port registers. + */ + +static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp) +{ +#if DEBUG + printk("stl_cd1400portinit(brdp=%x,panelp=%x,portp=%x)\n", + (int) brdp, (int) panelp, (int) portp); +#endif + + if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) || + (portp == (stlport_t *) NULL)) + return; + + portp->ioaddr = panelp->iobase + (((brdp->brdtype == BRD_ECHPCI) || + (portp->portnr < 8)) ? 0 : EREG_BANKSIZE); + portp->uartaddr = (portp->portnr & 0x04) << 5; + portp->pagenr = panelp->pagenr + (portp->portnr >> 3); + + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400setreg(portp, LIVR, (portp->portnr << 3)); + portp->hwid = stl_cd1400getreg(portp, GFRCR); + BRDDISABLE(portp->brdnr); +} + +/*****************************************************************************/ + +/* + * Wait for the command register to be ready. We will poll this, + * since it won't usually take too long to be ready. + */ + +static void stl_cd1400ccrwait(stlport_t *portp) +{ + int i; + + for (i = 0; (i < CCR_MAXWAIT); i++) { + if (stl_cd1400getreg(portp, CCR) == 0) { + return; + } + } + + printk("STALLION: cd1400 not responding, port=%d panel=%d brd=%d\n", + portp->portnr, portp->panelnr, portp->brdnr); +} + +/*****************************************************************************/ + +/* + * Set up the cd1400 registers for a port based on the termios port + * settings. + */ + +static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp) +{ + stlbrd_t *brdp; + unsigned long flags; + unsigned int clkdiv, baudrate; + unsigned char cor1, cor2, cor3; + unsigned char cor4, cor5, ccr; + unsigned char srer, sreron, sreroff; + unsigned char mcor1, mcor2, rtpr; + unsigned char clk, div; + + cor1 = 0; + cor2 = 0; + cor3 = 0; + cor4 = 0; + cor5 = 0; + ccr = 0; + rtpr = 0; + clk = 0; + div = 0; + mcor1 = 0; + mcor2 = 0; + sreron = 0; + sreroff = 0; + + brdp = stl_brds[portp->brdnr]; + if (brdp == (stlbrd_t *) NULL) + return; + +/* + * Set up the RX char ignore mask with those RX error types we + * can ignore. We can get the cd1400 to help us out a little here, + * it will ignore parity errors and breaks for us. + */ + portp->rxignoremsk = 0; + if (tiosp->c_iflag & IGNPAR) { + portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN); + cor1 |= COR1_PARIGNORE; + } + if (tiosp->c_iflag & IGNBRK) { + portp->rxignoremsk |= ST_BREAK; + cor4 |= COR4_IGNBRK; + } + + portp->rxmarkmsk = ST_OVERRUN; + if (tiosp->c_iflag & (INPCK | PARMRK)) + portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING); + if (tiosp->c_iflag & BRKINT) + portp->rxmarkmsk |= ST_BREAK; + +/* + * Go through the char size, parity and stop bits and set all the + * option register appropriately. + */ + switch (tiosp->c_cflag & CSIZE) { + case CS5: + cor1 |= COR1_CHL5; + break; + case CS6: + cor1 |= COR1_CHL6; + break; + case CS7: + cor1 |= COR1_CHL7; + break; + default: + cor1 |= COR1_CHL8; + break; + } + + if (tiosp->c_cflag & CSTOPB) + cor1 |= COR1_STOP2; + else + cor1 |= COR1_STOP1; + + if (tiosp->c_cflag & PARENB) { + if (tiosp->c_cflag & PARODD) + cor1 |= (COR1_PARENB | COR1_PARODD); + else + cor1 |= (COR1_PARENB | COR1_PAREVEN); + } else { + cor1 |= COR1_PARNONE; + } + +/* + * Set the RX FIFO threshold at 6 chars. This gives a bit of breathing + * space for hardware flow control and the like. This should be set to + * VMIN. Also here we will set the RX data timeout to 10ms - this should + * really be based on VTIME. + */ + cor3 |= FIFO_RXTHRESHOLD; + rtpr = 2; + +/* + * Calculate the baud rate timers. For now we will just assume that + * the input and output baud are the same. Could have used a baud + * table here, but this way we can generate virtually any baud rate + * we like! + */ + baudrate = tiosp->c_cflag & CBAUD; + if (baudrate & CBAUDEX) { + baudrate &= ~CBAUDEX; + if ((baudrate < 1) || (baudrate > 4)) + tiosp->c_cflag &= ~CBAUDEX; + else + baudrate += 15; + } + baudrate = stl_baudrates[baudrate]; + if ((tiosp->c_cflag & CBAUD) == B38400) { + if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + baudrate = 57600; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + baudrate = 115200; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + baudrate = 230400; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + baudrate = 460800; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) + baudrate = (portp->baud_base / portp->custom_divisor); + } + if (baudrate > STL_CD1400MAXBAUD) + baudrate = STL_CD1400MAXBAUD; + + if (baudrate > 0) { + for (clk = 0; (clk < CD1400_NUMCLKS); clk++) { + clkdiv = ((portp->clk / stl_cd1400clkdivs[clk]) / baudrate); + if (clkdiv < 0x100) + break; + } + div = (unsigned char) clkdiv; + } + +/* + * Check what form of modem signaling is required and set it up. + */ + if ((tiosp->c_cflag & CLOCAL) == 0) { + mcor1 |= MCOR1_DCD; + mcor2 |= MCOR2_DCD; + sreron |= SRER_MODEM; + portp->flags |= ASYNC_CHECK_CD; + } else { + portp->flags &= ~ASYNC_CHECK_CD; + } + +/* + * Setup cd1400 enhanced modes if we can. In particular we want to + * handle as much of the flow control as possible automatically. As + * well as saving a few CPU cycles it will also greatly improve flow + * control reliability. + */ + if (tiosp->c_iflag & IXON) { + cor2 |= COR2_TXIBE; + cor3 |= COR3_SCD12; + if (tiosp->c_iflag & IXANY) + cor2 |= COR2_IXM; + } + + if (tiosp->c_cflag & CRTSCTS) { + cor2 |= COR2_CTSAE; + mcor1 |= FIFO_RTSTHRESHOLD; + } + +/* + * All cd1400 register values calculated so go through and set + * them all up. + */ + +#if DEBUG + printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n", + portp->portnr, portp->panelnr, portp->brdnr); + printk(" cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n", + cor1, cor2, cor3, cor4, cor5); + printk(" mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n", + mcor1, mcor2, rtpr, sreron, sreroff); + printk(" tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div); + printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n", + tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP], + tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x3)); + srer = stl_cd1400getreg(portp, SRER); + stl_cd1400setreg(portp, SRER, 0); + if (stl_cd1400updatereg(portp, COR1, cor1)) + ccr = 1; + if (stl_cd1400updatereg(portp, COR2, cor2)) + ccr = 1; + if (stl_cd1400updatereg(portp, COR3, cor3)) + ccr = 1; + if (ccr) { + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_CORCHANGE); + } + stl_cd1400setreg(portp, COR4, cor4); + stl_cd1400setreg(portp, COR5, cor5); + stl_cd1400setreg(portp, MCOR1, mcor1); + stl_cd1400setreg(portp, MCOR2, mcor2); + if (baudrate > 0) { + stl_cd1400setreg(portp, TCOR, clk); + stl_cd1400setreg(portp, TBPR, div); + stl_cd1400setreg(portp, RCOR, clk); + stl_cd1400setreg(portp, RBPR, div); + } + stl_cd1400setreg(portp, SCHR1, tiosp->c_cc[VSTART]); + stl_cd1400setreg(portp, SCHR2, tiosp->c_cc[VSTOP]); + stl_cd1400setreg(portp, SCHR3, tiosp->c_cc[VSTART]); + stl_cd1400setreg(portp, SCHR4, tiosp->c_cc[VSTOP]); + stl_cd1400setreg(portp, RTPR, rtpr); + mcor1 = stl_cd1400getreg(portp, MSVR1); + if (mcor1 & MSVR1_DCD) + portp->sigs |= TIOCM_CD; + else + portp->sigs &= ~TIOCM_CD; + stl_cd1400setreg(portp, SRER, ((srer & ~sreroff) | sreron)); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Set the state of the DTR and RTS signals. + */ + +static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts) +{ + unsigned char msvr1, msvr2; + unsigned long flags; + +#if DEBUG + printk("stl_cd1400setsignals(portp=%x,dtr=%d,rts=%d)\n", + (int) portp, dtr, rts); +#endif + + msvr1 = 0; + msvr2 = 0; + if (dtr > 0) + msvr1 = MSVR1_DTR; + if (rts > 0) + msvr2 = MSVR2_RTS; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + if (rts >= 0) + stl_cd1400setreg(portp, MSVR2, msvr2); + if (dtr >= 0) + stl_cd1400setreg(portp, MSVR1, msvr1); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Return the state of the signals. + */ + +static int stl_cd1400getsignals(stlport_t *portp) +{ + unsigned char msvr1, msvr2; + unsigned long flags; + int sigs; + +#if DEBUG + printk("stl_cd1400getsignals(portp=%x)\n", (int) portp); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + msvr1 = stl_cd1400getreg(portp, MSVR1); + msvr2 = stl_cd1400getreg(portp, MSVR2); + BRDDISABLE(portp->brdnr); + restore_flags(flags); + + sigs = 0; + sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0; + sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0; + sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0; + sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0; +#if 0 + sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0; + sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0; +#else + sigs |= TIOCM_DSR; +#endif + return(sigs); +} + +/*****************************************************************************/ + +/* + * Enable/Disable the Transmitter and/or Receiver. + */ + +static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx) +{ + unsigned char ccr; + unsigned long flags; + +#if DEBUG + printk("stl_cd1400enablerxtx(portp=%x,rx=%d,tx=%d)\n", + (int) portp, rx, tx); +#endif + ccr = 0; + + if (tx == 0) + ccr |= CCR_TXDISABLE; + else if (tx > 0) + ccr |= CCR_TXENABLE; + if (rx == 0) + ccr |= CCR_RXDISABLE; + else if (rx > 0) + ccr |= CCR_RXENABLE; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, ccr); + stl_cd1400ccrwait(portp); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Start/stop the Transmitter and/or Receiver. + */ + +static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx) +{ + unsigned char sreron, sreroff; + unsigned long flags; + +#if DEBUG + printk("stl_cd1400startrxtx(portp=%x,rx=%d,tx=%d)\n", + (int) portp, rx, tx); +#endif + + sreron = 0; + sreroff = 0; + if (tx == 0) + sreroff |= (SRER_TXDATA | SRER_TXEMPTY); + else if (tx == 1) + sreron |= SRER_TXDATA; + else if (tx >= 2) + sreron |= SRER_TXEMPTY; + if (rx == 0) + sreroff |= SRER_RXDATA; + else if (rx > 0) + sreron |= SRER_RXDATA; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400setreg(portp, SRER, + ((stl_cd1400getreg(portp, SRER) & ~sreroff) | sreron)); + BRDDISABLE(portp->brdnr); + if (tx > 0) + set_bit(ASYI_TXBUSY, &portp->istate); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Disable all interrupts from this port. + */ + +static void stl_cd1400disableintrs(stlport_t *portp) +{ + unsigned long flags; + +#if DEBUG + printk("stl_cd1400disableintrs(portp=%x)\n", (int) portp); +#endif + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400setreg(portp, SRER, 0); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +static void stl_cd1400sendbreak(stlport_t *portp, int len) +{ + unsigned long flags; + +#if DEBUG + printk("stl_cd1400sendbreak(portp=%x,len=%d)\n", (int) portp, len); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400setreg(portp, SRER, + ((stl_cd1400getreg(portp, SRER) & ~SRER_TXDATA) | + SRER_TXEMPTY)); + BRDDISABLE(portp->brdnr); + portp->brklen = len; + if (len == 1) + portp->stats.txbreaks++; + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Take flow control actions... + */ + +static void stl_cd1400flowctrl(stlport_t *portp, int state) +{ + struct tty_struct *tty; + unsigned long flags; + +#if DEBUG + printk("stl_cd1400flowctrl(portp=%x,state=%x)\n", (int) portp, state); +#endif + + if (portp == (stlport_t *) NULL) + return; + tty = portp->tty; + if (tty == (struct tty_struct *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + + if (state) { + if (tty->termios->c_iflag & IXOFF) { + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1); + portp->stats.rxxon++; + stl_cd1400ccrwait(portp); + } +/* + * Question: should we return RTS to what it was before? It may + * have been set by an ioctl... Suppose not, since if you have + * hardware flow control set then it is pretty silly to go and + * set the RTS line by hand. + */ + if (tty->termios->c_cflag & CRTSCTS) { + stl_cd1400setreg(portp, MCOR1, + (stl_cd1400getreg(portp, MCOR1) | + FIFO_RTSTHRESHOLD)); + stl_cd1400setreg(portp, MSVR2, MSVR2_RTS); + portp->stats.rxrtson++; + } + } else { + if (tty->termios->c_iflag & IXOFF) { + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2); + portp->stats.rxxoff++; + stl_cd1400ccrwait(portp); + } + if (tty->termios->c_cflag & CRTSCTS) { + stl_cd1400setreg(portp, MCOR1, + (stl_cd1400getreg(portp, MCOR1) & 0xf0)); + stl_cd1400setreg(portp, MSVR2, 0); + portp->stats.rxrtsoff++; + } + } + + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Send a flow control character... + */ + +static void stl_cd1400sendflow(stlport_t *portp, int state) +{ + struct tty_struct *tty; + unsigned long flags; + +#if DEBUG + printk("stl_cd1400sendflow(portp=%x,state=%x)\n", (int) portp, state); +#endif + + if (portp == (stlport_t *) NULL) + return; + tty = portp->tty; + if (tty == (struct tty_struct *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + if (state) { + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1); + portp->stats.rxxon++; + stl_cd1400ccrwait(portp); + } else { + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2); + portp->stats.rxxoff++; + stl_cd1400ccrwait(portp); + } + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +static void stl_cd1400flush(stlport_t *portp) +{ + unsigned long flags; + +#if DEBUG + printk("stl_cd1400flush(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); + stl_cd1400ccrwait(portp); + stl_cd1400setreg(portp, CCR, CCR_TXFLUSHFIFO); + stl_cd1400ccrwait(portp); + portp->tx.tail = portp->tx.head; + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Return the current state of data flow on this port. This is only + * really interresting when determining if data has fully completed + * transmission or not... This is easy for the cd1400, it accurately + * maintains the busy port flag. + */ + +static int stl_cd1400datastate(stlport_t *portp) +{ +#if DEBUG + printk("stl_cd1400datastate(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return(0); + + return(test_bit(ASYI_TXBUSY, &portp->istate) ? 1 : 0); +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for cd1400 EasyIO boards. + */ + +static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase) +{ + unsigned char svrtype; + +#if DEBUG + printk("stl_cd1400eiointr(panelp=%x,iobase=%x)\n", + (int) panelp, iobase); +#endif + + outb(SVRR, iobase); + svrtype = inb(iobase + EREG_DATA); + if (panelp->nrports > 4) { + outb((SVRR + 0x80), iobase); + svrtype |= inb(iobase + EREG_DATA); + } + + if (svrtype & SVRR_RX) + stl_cd1400rxisr(panelp, iobase); + else if (svrtype & SVRR_TX) + stl_cd1400txisr(panelp, iobase); + else if (svrtype & SVRR_MDM) + stl_cd1400mdmisr(panelp, iobase); +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for cd1400 panels. + */ + +static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase) +{ + unsigned char svrtype; + +#if DEBUG + printk("stl_cd1400echintr(panelp=%x,iobase=%x)\n", (int) panelp, + iobase); +#endif + + outb(SVRR, iobase); + svrtype = inb(iobase + EREG_DATA); + outb((SVRR + 0x80), iobase); + svrtype |= inb(iobase + EREG_DATA); + if (svrtype & SVRR_RX) + stl_cd1400rxisr(panelp, iobase); + else if (svrtype & SVRR_TX) + stl_cd1400txisr(panelp, iobase); + else if (svrtype & SVRR_MDM) + stl_cd1400mdmisr(panelp, iobase); +} + + +/*****************************************************************************/ + +/* + * Unfortunately we need to handle breaks in the TX data stream, since + * this is the only way to generate them on the cd1400. + */ + +static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr) +{ + if (portp->brklen == 1) { + outb((COR2 + portp->uartaddr), ioaddr); + outb((inb(ioaddr + EREG_DATA) | COR2_ETC), + (ioaddr + EREG_DATA)); + outb((TDR + portp->uartaddr), ioaddr); + outb(ETC_CMD, (ioaddr + EREG_DATA)); + outb(ETC_STARTBREAK, (ioaddr + EREG_DATA)); + outb((SRER + portp->uartaddr), ioaddr); + outb((inb(ioaddr + EREG_DATA) & ~(SRER_TXDATA | SRER_TXEMPTY)), + (ioaddr + EREG_DATA)); + return(1); + } else if (portp->brklen > 1) { + outb((TDR + portp->uartaddr), ioaddr); + outb(ETC_CMD, (ioaddr + EREG_DATA)); + outb(ETC_STOPBREAK, (ioaddr + EREG_DATA)); + portp->brklen = -1; + return(1); + } else { + outb((COR2 + portp->uartaddr), ioaddr); + outb((inb(ioaddr + EREG_DATA) & ~COR2_ETC), + (ioaddr + EREG_DATA)); + portp->brklen = 0; + } + return(0); +} + +/*****************************************************************************/ + +/* + * Transmit interrupt handler. This has gotta be fast! Handling TX + * chars is pretty simple, stuff as many as possible from the TX buffer + * into the cd1400 FIFO. Must also handle TX breaks here, since they + * are embedded as commands in the data stream. Oh no, had to use a goto! + * This could be optimized more, will do when I get time... + * In practice it is possible that interrupts are enabled but that the + * port has been hung up. Need to handle not having any TX buffer here, + * this is done by using the side effect that head and tail will also + * be NULL if the buffer has been freed. + */ + +static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr) +{ + stlport_t *portp; + int len, stlen; + char *head, *tail; + unsigned char ioack, srer; + +#if DEBUG + printk("stl_cd1400txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr); +#endif + + ioack = inb(ioaddr + EREG_TXACK); + if (((ioack & panelp->ackmask) != 0) || + ((ioack & ACK_TYPMASK) != ACK_TYPTX)) { + printk("STALLION: bad TX interrupt ack value=%x\n", ioack); + return; + } + portp = panelp->ports[(ioack >> 3)]; + +/* + * Unfortunately we need to handle breaks in the data stream, since + * this is the only way to generate them on the cd1400. Do it now if + * a break is to be sent. + */ + if (portp->brklen != 0) + if (stl_cd1400breakisr(portp, ioaddr)) + goto stl_txalldone; + + head = portp->tx.head; + tail = portp->tx.tail; + len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head)); + if ((len == 0) || ((len < STL_TXBUFLOW) && + (test_bit(ASYI_TXLOW, &portp->istate) == 0))) { + set_bit(ASYI_TXLOW, &portp->istate); + queue_task(&portp->tqueue, &tq_scheduler); + } + + if (len == 0) { + outb((SRER + portp->uartaddr), ioaddr); + srer = inb(ioaddr + EREG_DATA); + if (srer & SRER_TXDATA) { + srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY; + } else { + srer &= ~(SRER_TXDATA | SRER_TXEMPTY); + clear_bit(ASYI_TXBUSY, &portp->istate); + } + outb(srer, (ioaddr + EREG_DATA)); + } else { + len = MIN(len, CD1400_TXFIFOSIZE); + portp->stats.txtotal += len; + stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail)); + outb((TDR + portp->uartaddr), ioaddr); + outsb((ioaddr + EREG_DATA), tail, stlen); + len -= stlen; + tail += stlen; + if (tail >= (portp->tx.buf + STL_TXBUFSIZE)) + tail = portp->tx.buf; + if (len > 0) { + outsb((ioaddr + EREG_DATA), tail, len); + tail += len; + } + portp->tx.tail = tail; + } + +stl_txalldone: + outb((EOSRR + portp->uartaddr), ioaddr); + outb(0, (ioaddr + EREG_DATA)); +} + +/*****************************************************************************/ + +/* + * Receive character interrupt handler. Determine if we have good chars + * or bad chars and then process appropriately. Good chars are easy + * just shove the lot into the RX buffer and set all status byte to 0. + * If a bad RX char then process as required. This routine needs to be + * fast! In practice it is possible that we get an interrupt on a port + * that is closed. This can happen on hangups - since they completely + * shutdown a port not in user context. Need to handle this case. + */ + +static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr) +{ + stlport_t *portp; + struct tty_struct *tty; + unsigned int ioack, len, buflen; + unsigned char status; + char ch; + +#if DEBUG + printk("stl_cd1400rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr); +#endif + + ioack = inb(ioaddr + EREG_RXACK); + if ((ioack & panelp->ackmask) != 0) { + printk("STALLION: bad RX interrupt ack value=%x\n", ioack); + return; + } + portp = panelp->ports[(ioack >> 3)]; + tty = portp->tty; + + if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) { + outb((RDCR + portp->uartaddr), ioaddr); + len = inb(ioaddr + EREG_DATA); + if ((tty == (struct tty_struct *) NULL) || + (tty->flip.char_buf_ptr == (char *) NULL) || + ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) { + len = MIN(len, sizeof(stl_unwanted)); + outb((RDSR + portp->uartaddr), ioaddr); + insb((ioaddr + EREG_DATA), &stl_unwanted[0], len); + portp->stats.rxlost += len; + portp->stats.rxtotal += len; + } else { + len = MIN(len, buflen); + if (len > 0) { + outb((RDSR + portp->uartaddr), ioaddr); + insb((ioaddr + EREG_DATA), tty->flip.char_buf_ptr, len); + memset(tty->flip.flag_buf_ptr, 0, len); + tty->flip.flag_buf_ptr += len; + tty->flip.char_buf_ptr += len; + tty->flip.count += len; + tty_schedule_flip(tty); + portp->stats.rxtotal += len; + } + } + } else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) { + outb((RDSR + portp->uartaddr), ioaddr); + status = inb(ioaddr + EREG_DATA); + ch = inb(ioaddr + EREG_DATA); + if (status & ST_PARITY) + portp->stats.rxparity++; + if (status & ST_FRAMING) + portp->stats.rxframing++; + if (status & ST_OVERRUN) + portp->stats.rxoverrun++; + if (status & ST_BREAK) + portp->stats.rxbreaks++; + if (status & ST_SCHARMASK) { + if ((status & ST_SCHARMASK) == ST_SCHAR1) + portp->stats.txxon++; + if ((status & ST_SCHARMASK) == ST_SCHAR2) + portp->stats.txxoff++; + goto stl_rxalldone; + } + if ((tty != (struct tty_struct *) NULL) && + ((portp->rxignoremsk & status) == 0)) { + if (portp->rxmarkmsk & status) { + if (status & ST_BREAK) { + status = TTY_BREAK; + if (portp->flags & ASYNC_SAK) { + do_SAK(tty); + BRDENABLE(portp->brdnr, portp->pagenr); + } + } else if (status & ST_PARITY) { + status = TTY_PARITY; + } else if (status & ST_FRAMING) { + status = TTY_FRAME; + } else if(status & ST_OVERRUN) { + status = TTY_OVERRUN; + } else { + status = 0; + } + } else { + status = 0; + } + if (tty->flip.char_buf_ptr != (char *) NULL) { + if (tty->flip.count < TTY_FLIPBUF_SIZE) { + *tty->flip.flag_buf_ptr++ = status; + *tty->flip.char_buf_ptr++ = ch; + tty->flip.count++; + } + tty_schedule_flip(tty); + } + } + } else { + printk("STALLION: bad RX interrupt ack value=%x\n", ioack); + return; + } + +stl_rxalldone: + outb((EOSRR + portp->uartaddr), ioaddr); + outb(0, (ioaddr + EREG_DATA)); +} + +/*****************************************************************************/ + +/* + * Modem interrupt handler. The is called when the modem signal line + * (DCD) has changed state. Leave most of the work to the off-level + * processing routine. + */ + +static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr) +{ + stlport_t *portp; + unsigned int ioack; + unsigned char misr; + +#if DEBUG + printk("stl_cd1400mdmisr(panelp=%x)\n", (int) panelp); +#endif + + ioack = inb(ioaddr + EREG_MDACK); + if (((ioack & panelp->ackmask) != 0) || + ((ioack & ACK_TYPMASK) != ACK_TYPMDM)) { + printk("STALLION: bad MODEM interrupt ack value=%x\n", ioack); + return; + } + portp = panelp->ports[(ioack >> 3)]; + + outb((MISR + portp->uartaddr), ioaddr); + misr = inb(ioaddr + EREG_DATA); + if (misr & MISR_DCD) { + set_bit(ASYI_DCDCHANGE, &portp->istate); + queue_task(&portp->tqueue, &tq_scheduler); + portp->stats.modem++; + } + + outb((EOSRR + portp->uartaddr), ioaddr); + outb(0, (ioaddr + EREG_DATA)); +} + +/*****************************************************************************/ +/* SC26198 HARDWARE FUNCTIONS */ +/*****************************************************************************/ + +/* + * These functions get/set/update the registers of the sc26198 UARTs. + * Access to the sc26198 registers is via an address/data io port pair. + * (Maybe should make this inline...) + */ + +static int stl_sc26198getreg(stlport_t *portp, int regnr) +{ + outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR)); + return(inb(portp->ioaddr + XP_DATA)); +} + +static void stl_sc26198setreg(stlport_t *portp, int regnr, int value) +{ + outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR)); + outb(value, (portp->ioaddr + XP_DATA)); +} + +static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value) +{ + outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR)); + if (inb(portp->ioaddr + XP_DATA) != value) { + outb(value, (portp->ioaddr + XP_DATA)); + return(1); + } + return(0); +} + +/*****************************************************************************/ + +/* + * Functions to get and set the sc26198 global registers. + */ + +static int stl_sc26198getglobreg(stlport_t *portp, int regnr) +{ + outb(regnr, (portp->ioaddr + XP_ADDR)); + return(inb(portp->ioaddr + XP_DATA)); +} + +#if 0 +static void stl_sc26198setglobreg(stlport_t *portp, int regnr, int value) +{ + outb(regnr, (portp->ioaddr + XP_ADDR)); + outb(value, (portp->ioaddr + XP_DATA)); +} +#endif + +/*****************************************************************************/ + +/* + * Inbitialize the UARTs in a panel. We don't care what sort of board + * these ports are on - since the port io registers are almost + * identical when dealing with ports. + */ + +static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp) +{ + int chipmask, i; + int nrchips, ioaddr; + +#if DEBUG + printk("stl_sc26198panelinit(brdp=%x,panelp=%x)\n", + (int) brdp, (int) panelp); +#endif + + BRDENABLE(panelp->brdnr, panelp->pagenr); + +/* + * Check that each chip is present and started up OK. + */ + chipmask = 0; + nrchips = (panelp->nrports + 4) / SC26198_PORTS; + if (brdp->brdtype == BRD_ECHPCI) + outb(panelp->pagenr, brdp->ioctrl); + + for (i = 0; (i < nrchips); i++) { + ioaddr = panelp->iobase + (i * 4); + outb(SCCR, (ioaddr + XP_ADDR)); + outb(CR_RESETALL, (ioaddr + XP_DATA)); + outb(TSTR, (ioaddr + XP_ADDR)); + if (inb(ioaddr + XP_DATA) != 0) { + printk("STALLION: sc26198 not responding, " + "brd=%d panel=%d chip=%d\n", + panelp->brdnr, panelp->panelnr, i); + continue; + } + chipmask |= (0x1 << i); + outb(GCCR, (ioaddr + XP_ADDR)); + outb(GCCR_IVRTYPCHANACK, (ioaddr + XP_DATA)); + outb(WDTRCR, (ioaddr + XP_ADDR)); + outb(0xff, (ioaddr + XP_DATA)); + } + + BRDDISABLE(panelp->brdnr); + return(chipmask); +} + +/*****************************************************************************/ + +/* + * Initialize hardware specific port registers. + */ + +static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp) +{ +#if DEBUG + printk("stl_sc26198portinit(brdp=%x,panelp=%x,portp=%x)\n", + (int) brdp, (int) panelp, (int) portp); +#endif + + if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) || + (portp == (stlport_t *) NULL)) + return; + + portp->ioaddr = panelp->iobase + ((portp->portnr < 8) ? 0 : 4); + portp->uartaddr = (portp->portnr & 0x07) << 4; + portp->pagenr = panelp->pagenr; + portp->hwid = 0x1; + + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, IOPCR, IOPCR_SETSIGS); + BRDDISABLE(portp->brdnr); +} + +/*****************************************************************************/ + +/* + * Set up the sc26198 registers for a port based on the termios port + * settings. + */ + +static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp) +{ + stlbrd_t *brdp; + unsigned long flags; + unsigned int baudrate; + unsigned char mr0, mr1, mr2, clk; + unsigned char imron, imroff, iopr, ipr; + + mr0 = 0; + mr1 = 0; + mr2 = 0; + clk = 0; + iopr = 0; + imron = 0; + imroff = 0; + + brdp = stl_brds[portp->brdnr]; + if (brdp == (stlbrd_t *) NULL) + return; + +/* + * Set up the RX char ignore mask with those RX error types we + * can ignore. + */ + portp->rxignoremsk = 0; + if (tiosp->c_iflag & IGNPAR) + portp->rxignoremsk |= (SR_RXPARITY | SR_RXFRAMING | + SR_RXOVERRUN); + if (tiosp->c_iflag & IGNBRK) + portp->rxignoremsk |= SR_RXBREAK; + + portp->rxmarkmsk = SR_RXOVERRUN; + if (tiosp->c_iflag & (INPCK | PARMRK)) + portp->rxmarkmsk |= (SR_RXPARITY | SR_RXFRAMING); + if (tiosp->c_iflag & BRKINT) + portp->rxmarkmsk |= SR_RXBREAK; + +/* + * Go through the char size, parity and stop bits and set all the + * option register appropriately. + */ + switch (tiosp->c_cflag & CSIZE) { + case CS5: + mr1 |= MR1_CS5; + break; + case CS6: + mr1 |= MR1_CS6; + break; + case CS7: + mr1 |= MR1_CS7; + break; + default: + mr1 |= MR1_CS8; + break; + } + + if (tiosp->c_cflag & CSTOPB) + mr2 |= MR2_STOP2; + else + mr2 |= MR2_STOP1; + + if (tiosp->c_cflag & PARENB) { + if (tiosp->c_cflag & PARODD) + mr1 |= (MR1_PARENB | MR1_PARODD); + else + mr1 |= (MR1_PARENB | MR1_PAREVEN); + } else { + mr1 |= MR1_PARNONE; + } + + mr1 |= MR1_ERRBLOCK; + +/* + * Set the RX FIFO threshold at 8 chars. This gives a bit of breathing + * space for hardware flow control and the like. This should be set to + * VMIN. + */ + mr2 |= MR2_RXFIFOHALF; + +/* + * Calculate the baud rate timers. For now we will just assume that + * the input and output baud are the same. The sc26198 has a fixed + * baud rate table, so only discrete baud rates possible. + */ + baudrate = tiosp->c_cflag & CBAUD; + if (baudrate & CBAUDEX) { + baudrate &= ~CBAUDEX; + if ((baudrate < 1) || (baudrate > 4)) + tiosp->c_cflag &= ~CBAUDEX; + else + baudrate += 15; + } + baudrate = stl_baudrates[baudrate]; + if ((tiosp->c_cflag & CBAUD) == B38400) { + if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + baudrate = 57600; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + baudrate = 115200; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + baudrate = 230400; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + baudrate = 460800; + else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) + baudrate = (portp->baud_base / portp->custom_divisor); + } + if (baudrate > STL_SC26198MAXBAUD) + baudrate = STL_SC26198MAXBAUD; + + if (baudrate > 0) { + for (clk = 0; (clk < SC26198_NRBAUDS); clk++) { + if (baudrate <= sc26198_baudtable[clk]) + break; + } + } + +/* + * Check what form of modem signaling is required and set it up. + */ + if (tiosp->c_cflag & CLOCAL) { + portp->flags &= ~ASYNC_CHECK_CD; + } else { + iopr |= IOPR_DCDCOS; + imron |= IR_IOPORT; + portp->flags |= ASYNC_CHECK_CD; + } + +/* + * Setup sc26198 enhanced modes if we can. In particular we want to + * handle as much of the flow control as possible automatically. As + * well as saving a few CPU cycles it will also greatly improve flow + * control reliability. + */ + if (tiosp->c_iflag & IXON) { + mr0 |= MR0_SWFTX | MR0_SWFT; + imron |= IR_XONXOFF; + } else { + imroff |= IR_XONXOFF; + } + if (tiosp->c_iflag & IXOFF) + mr0 |= MR0_SWFRX; + + if (tiosp->c_cflag & CRTSCTS) { + mr2 |= MR2_AUTOCTS; + mr1 |= MR1_AUTORTS; + } + +/* + * All sc26198 register values calculated so go through and set + * them all up. + */ + +#if DEBUG + printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n", + portp->portnr, portp->panelnr, portp->brdnr); + printk(" mr0=%x mr1=%x mr2=%x clk=%x\n", mr0, mr1, mr2, clk); + printk(" iopr=%x imron=%x imroff=%x\n", iopr, imron, imroff); + printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n", + tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP], + tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, IMR, 0); + stl_sc26198updatereg(portp, MR0, mr0); + stl_sc26198updatereg(portp, MR1, mr1); + stl_sc26198setreg(portp, SCCR, CR_RXERRBLOCK); + stl_sc26198updatereg(portp, MR2, mr2); + stl_sc26198updatereg(portp, IOPIOR, + ((stl_sc26198getreg(portp, IOPIOR) & ~IPR_CHANGEMASK) | iopr)); + + if (baudrate > 0) { + stl_sc26198setreg(portp, TXCSR, clk); + stl_sc26198setreg(portp, RXCSR, clk); + } + + stl_sc26198setreg(portp, XONCR, tiosp->c_cc[VSTART]); + stl_sc26198setreg(portp, XOFFCR, tiosp->c_cc[VSTOP]); + + ipr = stl_sc26198getreg(portp, IPR); + if (ipr & IPR_DCD) + portp->sigs &= ~TIOCM_CD; + else + portp->sigs |= TIOCM_CD; + + portp->imr = (portp->imr & ~imroff) | imron; + stl_sc26198setreg(portp, IMR, portp->imr); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Set the state of the DTR and RTS signals. + */ + +static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts) +{ + unsigned char iopioron, iopioroff; + unsigned long flags; + +#if DEBUG + printk("stl_sc26198setsignals(portp=%x,dtr=%d,rts=%d)\n", + (int) portp, dtr, rts); +#endif + + iopioron = 0; + iopioroff = 0; + if (dtr == 0) + iopioroff |= IPR_DTR; + else if (dtr > 0) + iopioron |= IPR_DTR; + if (rts == 0) + iopioroff |= IPR_RTS; + else if (rts > 0) + iopioron |= IPR_RTS; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, IOPIOR, + ((stl_sc26198getreg(portp, IOPIOR) & ~iopioroff) | iopioron)); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Return the state of the signals. + */ + +static int stl_sc26198getsignals(stlport_t *portp) +{ + unsigned char ipr; + unsigned long flags; + int sigs; + +#if DEBUG + printk("stl_sc26198getsignals(portp=%x)\n", (int) portp); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + ipr = stl_sc26198getreg(portp, IPR); + BRDDISABLE(portp->brdnr); + restore_flags(flags); + + sigs = 0; + sigs |= (ipr & IPR_DCD) ? 0 : TIOCM_CD; + sigs |= (ipr & IPR_CTS) ? 0 : TIOCM_CTS; + sigs |= (ipr & IPR_DTR) ? 0: TIOCM_DTR; + sigs |= (ipr & IPR_RTS) ? 0: TIOCM_RTS; + sigs |= TIOCM_DSR; + return(sigs); +} + +/*****************************************************************************/ + +/* + * Enable/Disable the Transmitter and/or Receiver. + */ + +static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx) +{ + unsigned char ccr; + unsigned long flags; + +#if DEBUG + printk("stl_sc26198enablerxtx(portp=%x,rx=%d,tx=%d)\n", + (int) portp, rx, tx); +#endif + + ccr = portp->crenable; + if (tx == 0) + ccr &= ~CR_TXENABLE; + else if (tx > 0) + ccr |= CR_TXENABLE; + if (rx == 0) + ccr &= ~CR_RXENABLE; + else if (rx > 0) + ccr |= CR_RXENABLE; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, SCCR, ccr); + BRDDISABLE(portp->brdnr); + portp->crenable = ccr; + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Start/stop the Transmitter and/or Receiver. + */ + +static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx) +{ + unsigned char imr; + unsigned long flags; + +#if DEBUG + printk("stl_sc26198startrxtx(portp=%x,rx=%d,tx=%d)\n", + (int) portp, rx, tx); +#endif + + imr = portp->imr; + if (tx == 0) + imr &= ~IR_TXRDY; + else if (tx == 1) + imr |= IR_TXRDY; + if (rx == 0) + imr &= ~(IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG); + else if (rx > 0) + imr |= IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, IMR, imr); + BRDDISABLE(portp->brdnr); + portp->imr = imr; + if (tx > 0) + set_bit(ASYI_TXBUSY, &portp->istate); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Disable all interrupts from this port. + */ + +static void stl_sc26198disableintrs(stlport_t *portp) +{ + unsigned long flags; + +#if DEBUG + printk("stl_sc26198disableintrs(portp=%x)\n", (int) portp); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + portp->imr = 0; + stl_sc26198setreg(portp, IMR, 0); + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +static void stl_sc26198sendbreak(stlport_t *portp, int len) +{ + unsigned long flags; + +#if DEBUG + printk("stl_sc26198sendbreak(portp=%x,len=%d)\n", (int) portp, len); +#endif + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + if (len == 1) { + stl_sc26198setreg(portp, SCCR, CR_TXSTARTBREAK); + portp->stats.txbreaks++; + } else { + stl_sc26198setreg(portp, SCCR, CR_TXSTOPBREAK); + } + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Take flow control actions... + */ + +static void stl_sc26198flowctrl(stlport_t *portp, int state) +{ + struct tty_struct *tty; + unsigned long flags; + unsigned char mr0; + +#if DEBUG + printk("stl_sc26198flowctrl(portp=%x,state=%x)\n", (int) portp, state); +#endif + + if (portp == (stlport_t *) NULL) + return; + tty = portp->tty; + if (tty == (struct tty_struct *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + + if (state) { + if (tty->termios->c_iflag & IXOFF) { + mr0 = stl_sc26198getreg(portp, MR0); + stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX)); + stl_sc26198setreg(portp, SCCR, CR_TXSENDXON); + mr0 |= MR0_SWFRX; + portp->stats.rxxon++; + stl_sc26198wait(portp); + stl_sc26198setreg(portp, MR0, mr0); + } +/* + * Question: should we return RTS to what it was before? It may + * have been set by an ioctl... Suppose not, since if you have + * hardware flow control set then it is pretty silly to go and + * set the RTS line by hand. + */ + if (tty->termios->c_cflag & CRTSCTS) { + stl_sc26198setreg(portp, MR1, + (stl_sc26198getreg(portp, MR1) | MR1_AUTORTS)); + stl_sc26198setreg(portp, IOPIOR, + (stl_sc26198getreg(portp, IOPIOR) | IOPR_RTS)); + portp->stats.rxrtson++; + } + } else { + if (tty->termios->c_iflag & IXOFF) { + mr0 = stl_sc26198getreg(portp, MR0); + stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX)); + stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF); + mr0 &= ~MR0_SWFRX; + portp->stats.rxxoff++; + stl_sc26198wait(portp); + stl_sc26198setreg(portp, MR0, mr0); + } + if (tty->termios->c_cflag & CRTSCTS) { + stl_sc26198setreg(portp, MR1, + (stl_sc26198getreg(portp, MR1) & ~MR1_AUTORTS)); + stl_sc26198setreg(portp, IOPIOR, + (stl_sc26198getreg(portp, IOPIOR) & ~IOPR_RTS)); + portp->stats.rxrtsoff++; + } + } + + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Send a flow control character. + */ + +static void stl_sc26198sendflow(stlport_t *portp, int state) +{ + struct tty_struct *tty; + unsigned long flags; + unsigned char mr0; + +#if DEBUG + printk("stl_sc26198sendflow(portp=%x,state=%x)\n", (int) portp, state); +#endif + + if (portp == (stlport_t *) NULL) + return; + tty = portp->tty; + if (tty == (struct tty_struct *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + if (state) { + mr0 = stl_sc26198getreg(portp, MR0); + stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX)); + stl_sc26198setreg(portp, SCCR, CR_TXSENDXON); + mr0 |= MR0_SWFRX; + portp->stats.rxxon++; + stl_sc26198wait(portp); + stl_sc26198setreg(portp, MR0, mr0); + } else { + mr0 = stl_sc26198getreg(portp, MR0); + stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX)); + stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF); + mr0 &= ~MR0_SWFRX; + portp->stats.rxxoff++; + stl_sc26198wait(portp); + stl_sc26198setreg(portp, MR0, mr0); + } + BRDDISABLE(portp->brdnr); + restore_flags(flags); +} + +/*****************************************************************************/ + +static void stl_sc26198flush(stlport_t *portp) +{ + unsigned long flags; + +#if DEBUG + printk("stl_sc26198flush(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return; + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + stl_sc26198setreg(portp, SCCR, CR_TXRESET); + stl_sc26198setreg(portp, SCCR, portp->crenable); + BRDDISABLE(portp->brdnr); + portp->tx.tail = portp->tx.head; + restore_flags(flags); +} + +/*****************************************************************************/ + +/* + * Return the current state of data flow on this port. This is only + * really interresting when determining if data has fully completed + * transmission or not... The sc26198 interrupt scheme cannot + * determine when all data has actually drained, so we need to + * check the port statusy register to be sure. + */ + +static int stl_sc26198datastate(stlport_t *portp) +{ + unsigned long flags; + unsigned char sr; + +#if DEBUG + printk("stl_sc26198datastate(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return(0); + if (test_bit(ASYI_TXBUSY, &portp->istate)) + return(1); + + save_flags(flags); + cli(); + BRDENABLE(portp->brdnr, portp->pagenr); + sr = stl_sc26198getreg(portp, SR); + BRDDISABLE(portp->brdnr); + restore_flags(flags); + + return((sr & SR_TXEMPTY) ? 0 : 1); +} + +/*****************************************************************************/ + +/* + * Delay for a small amount of time, to give the sc26198 a chance + * to process a command... + */ + +static void stl_sc26198wait(stlport_t *portp) +{ + int i; + +#if DEBUG + printk("stl_sc26198wait(portp=%x)\n", (int) portp); +#endif + + if (portp == (stlport_t *) NULL) + return; + + for (i = 0; (i < 20); i++) + stl_sc26198getglobreg(portp, TSTR); +} + +/*****************************************************************************/ + +/* + * If we are TX flow controlled and in IXANY mode then we may + * need to unflow control here. We gotta do this because of the + * automatic flow control modes of the sc26198. + */ + +static inline void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty) +{ + unsigned char mr0; + + mr0 = stl_sc26198getreg(portp, MR0); + stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX)); + stl_sc26198setreg(portp, SCCR, CR_HOSTXON); + stl_sc26198wait(portp); + stl_sc26198setreg(portp, MR0, mr0); + clear_bit(ASYI_TXFLOWED, &portp->istate); +} + +/*****************************************************************************/ + +/* + * Interrupt service routine for sc26198 panels. + */ + +static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase) +{ + stlport_t *portp; + unsigned int iack; + +/* + * Work around bug in sc26198 chip... Cannot have A6 address + * line of UART high, else iack will be returned as 0. + */ + outb(0, (iobase + 1)); + + iack = inb(iobase + XP_IACK); + portp = panelp->ports[(iack & IVR_CHANMASK) + ((iobase & 0x4) << 1)]; + + if (iack & IVR_RXDATA) + stl_sc26198rxisr(portp, iack); + else if (iack & IVR_TXDATA) + stl_sc26198txisr(portp); + else + stl_sc26198otherisr(portp, iack); +} + +/*****************************************************************************/ + +/* + * Transmit interrupt handler. This has gotta be fast! Handling TX + * chars is pretty simple, stuff as many as possible from the TX buffer + * into the sc26198 FIFO. + * In practice it is possible that interrupts are enabled but that the + * port has been hung up. Need to handle not having any TX buffer here, + * this is done by using the side effect that head and tail will also + * be NULL if the buffer has been freed. + */ + +static void stl_sc26198txisr(stlport_t *portp) +{ + unsigned int ioaddr; + unsigned char mr0; + int len, stlen; + char *head, *tail; + +#if DEBUG + printk("stl_sc26198txisr(portp=%x)\n", (int) portp); +#endif + + ioaddr = portp->ioaddr; + head = portp->tx.head; + tail = portp->tx.tail; + len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head)); + if ((len == 0) || ((len < STL_TXBUFLOW) && + (test_bit(ASYI_TXLOW, &portp->istate) == 0))) { + set_bit(ASYI_TXLOW, &portp->istate); + queue_task(&portp->tqueue, &tq_scheduler); + } + + if (len == 0) { + outb((MR0 | portp->uartaddr), (ioaddr + XP_ADDR)); + mr0 = inb(ioaddr + XP_DATA); + if ((mr0 & MR0_TXMASK) == MR0_TXEMPTY) { + portp->imr &= ~IR_TXRDY; + outb((IMR | portp->uartaddr), (ioaddr + XP_ADDR)); + outb(portp->imr, (ioaddr + XP_DATA)); + clear_bit(ASYI_TXBUSY, &portp->istate); + } else { + mr0 |= ((mr0 & ~MR0_TXMASK) | MR0_TXEMPTY); + outb(mr0, (ioaddr + XP_DATA)); + } + } else { + len = MIN(len, SC26198_TXFIFOSIZE); + portp->stats.txtotal += len; + stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail)); + outb(GTXFIFO, (ioaddr + XP_ADDR)); + outsb((ioaddr + XP_DATA), tail, stlen); + len -= stlen; + tail += stlen; + if (tail >= (portp->tx.buf + STL_TXBUFSIZE)) + tail = portp->tx.buf; + if (len > 0) { + outsb((ioaddr + XP_DATA), tail, len); + tail += len; + } + portp->tx.tail = tail; + } +} + +/*****************************************************************************/ + +/* + * Receive character interrupt handler. Determine if we have good chars + * or bad chars and then process appropriately. Good chars are easy + * just shove the lot into the RX buffer and set all status byte to 0. + * If a bad RX char then process as required. This routine needs to be + * fast! In practice it is possible that we get an interrupt on a port + * that is closed. This can happen on hangups - since they completely + * shutdown a port not in user context. Need to handle this case. + */ + +static void stl_sc26198rxisr(stlport_t *portp, unsigned int iack) +{ + struct tty_struct *tty; + unsigned int len, buflen, ioaddr; + +#if DEBUG + printk("stl_sc26198rxisr(portp=%x,iack=%x)\n", (int) portp, iack); +#endif + + tty = portp->tty; + ioaddr = portp->ioaddr; + outb(GIBCR, (ioaddr + XP_ADDR)); + len = inb(ioaddr + XP_DATA) + 1; + + if ((iack & IVR_TYPEMASK) == IVR_RXDATA) { + if ((tty == (struct tty_struct *) NULL) || + (tty->flip.char_buf_ptr == (char *) NULL) || + ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) { + len = MIN(len, sizeof(stl_unwanted)); + outb(GRXFIFO, (ioaddr + XP_ADDR)); + insb((ioaddr + XP_DATA), &stl_unwanted[0], len); + portp->stats.rxlost += len; + portp->stats.rxtotal += len; + } else { + len = MIN(len, buflen); + if (len > 0) { + outb(GRXFIFO, (ioaddr + XP_ADDR)); + insb((ioaddr + XP_DATA), tty->flip.char_buf_ptr, len); + memset(tty->flip.flag_buf_ptr, 0, len); + tty->flip.flag_buf_ptr += len; + tty->flip.char_buf_ptr += len; + tty->flip.count += len; + tty_schedule_flip(tty); + portp->stats.rxtotal += len; + } + } + } else { + stl_sc26198rxbadchars(portp); + } + +/* + * If we are TX flow controlled and in IXANY mode then we may need + * to unflow control here. We gotta do this because of the automatic + * flow control modes of the sc26198. + */ + if (test_bit(ASYI_TXFLOWED, &portp->istate)) { + if ((tty != (struct tty_struct *) NULL) && + (tty->termios != (struct termios *) NULL) && + (tty->termios->c_iflag & IXANY)) { + stl_sc26198txunflow(portp, tty); + } + } +} + +/*****************************************************************************/ + +/* + * Process an RX bad character. + */ + +static void inline stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch) +{ + struct tty_struct *tty; + unsigned int ioaddr; + + tty = portp->tty; + ioaddr = portp->ioaddr; + + if (status & SR_RXPARITY) + portp->stats.rxparity++; + if (status & SR_RXFRAMING) + portp->stats.rxframing++; + if (status & SR_RXOVERRUN) + portp->stats.rxoverrun++; + if (status & SR_RXBREAK) + portp->stats.rxbreaks++; + + if ((tty != (struct tty_struct *) NULL) && + ((portp->rxignoremsk & status) == 0)) { + if (portp->rxmarkmsk & status) { + if (status & SR_RXBREAK) { + status = TTY_BREAK; + if (portp->flags & ASYNC_SAK) { + do_SAK(tty); + BRDENABLE(portp->brdnr, portp->pagenr); + } + } else if (status & SR_RXPARITY) { + status = TTY_PARITY; + } else if (status & SR_RXFRAMING) { + status = TTY_FRAME; + } else if(status & SR_RXOVERRUN) { + status = TTY_OVERRUN; + } else { + status = 0; + } + } else { + status = 0; + } + + if (tty->flip.char_buf_ptr != (char *) NULL) { + if (tty->flip.count < TTY_FLIPBUF_SIZE) { + *tty->flip.flag_buf_ptr++ = status; + *tty->flip.char_buf_ptr++ = ch; + tty->flip.count++; + } + tty_schedule_flip(tty); + } + + if (status == 0) + portp->stats.rxtotal++; + } +} + +/*****************************************************************************/ + +/* + * Process all characters in the RX FIFO of the UART. Check all char + * status bytes as well, and process as required. We need to check + * all bytes in the FIFO, in case some more enter the FIFO while we + * are here. To get the exact character error type we need to switch + * into CHAR error mode (that is why we need to make sure we empty + * the FIFO). + */ + +static void stl_sc26198rxbadchars(stlport_t *portp) +{ + unsigned char status, mr1; + char ch; + +/* + * To get the precise error type for each character we must switch + * back into CHAR error mode. + */ + mr1 = stl_sc26198getreg(portp, MR1); + stl_sc26198setreg(portp, MR1, (mr1 & ~MR1_ERRBLOCK)); + + while ((status = stl_sc26198getreg(portp, SR)) & SR_RXRDY) { + stl_sc26198setreg(portp, SCCR, CR_CLEARRXERR); + ch = stl_sc26198getreg(portp, RXFIFO); + stl_sc26198rxbadch(portp, status, ch); + } + +/* + * To get correct interrupt class we must switch back into BLOCK + * error mode. + */ + stl_sc26198setreg(portp, MR1, mr1); +} + +/*****************************************************************************/ + +/* + * Other interrupt handler. This includes modem signals, flow + * control actions, etc. Most stuff is left to off-level interrupt + * processing time. + */ + +static void stl_sc26198otherisr(stlport_t *portp, unsigned int iack) +{ + unsigned char cir, ipr, xisr; + +#if DEBUG + printk("stl_sc26198otherisr(portp=%x,iack=%x)\n", (int) portp, iack); +#endif + + cir = stl_sc26198getglobreg(portp, CIR); + + switch (cir & CIR_SUBTYPEMASK) { + case CIR_SUBCOS: + ipr = stl_sc26198getreg(portp, IPR); + if (ipr & IPR_DCDCHANGE) { + set_bit(ASYI_DCDCHANGE, &portp->istate); + queue_task(&portp->tqueue, &tq_scheduler); + portp->stats.modem++; + } + break; + case CIR_SUBXONXOFF: + xisr = stl_sc26198getreg(portp, XISR); + if (xisr & XISR_RXXONGOT) { + set_bit(ASYI_TXFLOWED, &portp->istate); + portp->stats.txxoff++; + } + if (xisr & XISR_RXXOFFGOT) { + clear_bit(ASYI_TXFLOWED, &portp->istate); + portp->stats.txxon++; + } + break; + case CIR_SUBBREAK: + stl_sc26198setreg(portp, SCCR, CR_BREAKRESET); + stl_sc26198rxbadchars(portp); + break; + default: + break; + } +} + +/*****************************************************************************/ diff --git a/drivers/media/video/stradis.c b/drivers/media/video/stradis.c new file mode 100644 index 000000000..03ca27a25 --- /dev/null +++ b/drivers/media/video/stradis.c @@ -0,0 +1,2287 @@ +/* + * stradis.c - stradis 4:2:2 mpeg decoder driver + * + * Stradis 4:2:2 MPEG-2 Decoder Driver + * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <asm/segment.h> +#include <asm/types.h> +#include <linux/types.h> +#include <linux/wrapper.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> +#include <linux/vmalloc.h> +#include <linux/videodev.h> +#include <linux/i2c-old.h> + +#include "saa7146.h" +#include "saa7146reg.h" +#include "ibmmpeg2.h" +#include "saa7121.h" +#include "cs8420.h" + +#define DEBUG(x) /* debug driver */ +#undef IDEBUG(x) /* debug irq handler */ +#undef MDEBUG(x) /* debug memory management */ + +#define SAA7146_MAX 6 + +static struct saa7146 saa7146s[SAA7146_MAX]; + +static int saa_num = 0; /* number of SAA7146s in use */ + +#define nDebNormal 0x00480000 +#define nDebNoInc 0x00480000 +#define nDebVideo 0xd0480000 +#define nDebAudio 0xd0400000 +#define nDebDMA 0x02c80000 + +#define oDebNormal 0x13c80000 +#define oDebNoInc 0x13c80000 +#define oDebVideo 0xd1080000 +#define oDebAudio 0xd1080000 +#define oDebDMA 0x03080000 + +#define NewCard (saa->boardcfg[3]) +#define ChipControl (saa->boardcfg[1]) +#define NTSCFirstActive (saa->boardcfg[4]) +#define PALFirstActive (saa->boardcfg[5]) +#define NTSCLastActive (saa->boardcfg[54]) +#define PALLastActive (saa->boardcfg[55]) +#define Have2MB (saa->boardcfg[18] & 0x40) +#define HaveCS8420 (saa->boardcfg[18] & 0x04) +#define IBMMPEGCD20 (saa->boardcfg[18] & 0x20) +#define HaveCS3310 (saa->boardcfg[18] & 0x01) +#define CS3310MaxLvl ((saa->boardcfg[30] << 8) | saa->boardcfg[31]) +#define HaveCS4341 (saa->boardcfg[40] == 2) +#define SDIType (saa->boardcfg[27]) +#define CurrentMode (saa->boardcfg[2]) + +#define debNormal (NewCard ? nDebNormal : oDebNormal) +#define debNoInc (NewCard ? nDebNoInc : oDebNoInc) +#define debVideo (NewCard ? nDebVideo : oDebVideo) +#define debAudio (NewCard ? nDebAudio : oDebAudio) +#define debDMA (NewCard ? nDebDMA : oDebDMA) + +#ifdef DEBUG +int stradis_driver(void) /* for the benefit of ksymoops */ +{ + return 1; +} +#endif + +#ifdef USE_RESCUE_EEPROM_SDM275 +static unsigned char rescue_eeprom[64] = { +0x00,0x01,0x04,0x13,0x26,0x0f,0x10,0x00,0x00,0x00,0x43,0x63,0x22,0x01,0x29,0x15,0x73,0x00,0x1f, 'd', 'e', 'c', 'x', 'l', 'd', 'v', 'a',0x02,0x00,0x01,0x00,0xcc,0xa4,0x63,0x09,0xe2,0x10,0x00,0x0a,0x00,0x02,0x02, 'd', 'e', 'c', 'x', 'l', 'a',0x00,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +#endif + +/* ----------------------------------------------------------------------- */ +/* Hardware I2C functions */ +static void I2CWipe(struct saa7146 *saa) +{ + int i; + /* set i2c to ~=100kHz, abort transfer, clear busy */ + saawrite(0x600 | SAA7146_I2C_ABORT, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + saawrite(0x600, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + saawrite(0x600, SAA7146_I2C_STATUS); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); +} +/* read I2C */ +static int I2CRead(struct i2c_bus *bus, unsigned char addr, + unsigned char subaddr, int dosub) +{ + struct saa7146 *saa = (struct saa7146 *) bus->data; + int i; + + + if (saaread(SAA7146_I2C_STATUS) & 0x3c) + I2CWipe(saa); + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (i == 1000) + I2CWipe(saa); + if (dosub) + saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 8) | + ((subaddr & 0xff) << 16) | 0xed, SAA7146_I2C_TRANSFER); + else + saawrite(((addr & 0xfe) << 24) | (((addr | 1) & 0xff) << 16) | + 0xf1, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + /* wait for valid data */ + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_ERR) + return -1; + if (i == 1000) + printk("i2c setup read timeout\n"); + saawrite(0x41, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | + SAA7146_MC2_UPLD_I2C, SAA7146_MC2); + /* wait for i2c registers to be programmed */ + for (i = 0; i < 1000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_I2C); i++) + schedule(); + /* wait for valid data */ + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_BUSY); i++) + schedule(); + if (saaread(SAA7146_I2C_TRANSFER) & SAA7146_I2C_ERR) + return -1; + if (i == 1000) + printk("i2c read timeout\n"); + return ((saaread(SAA7146_I2C_TRANSFER) >> 24) & 0xff); +} +static int I2CReadOld(struct i2c_bus *bus, unsigned char addr) +{ + return I2CRead(bus, addr, 0, 0); +} + +/* set both to write both bytes, reset it to write only b1 */ + +static int I2CWrite(struct i2c_bus *bus, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + struct saa7146 *saa = (struct saa7146 *) bus->data; + int i; + u32 data; + + if (saaread(SAA7146_I2C_STATUS) & 0x3c) + I2CWipe(saa); + for (i = 0; i < 1000 && + (saaread(SAA7146_I2C_STATUS) & SAA7146_I2C_BUSY); i++) + schedule(); + if (i == 1000) + I2CWipe(saa); + data = ((addr & 0xfe) << 24) | ((b1 & 0xff) << 16); + if (both) + data |= ((b2 & 0xff) << 8) | 0xe5; + else + data |= 0xd1; + saawrite(data, SAA7146_I2C_TRANSFER); + saawrite((SAA7146_MC2_UPLD_I2C << 16) | SAA7146_MC2_UPLD_I2C, + SAA7146_MC2); + return 0; +} + +static void attach_inform(struct i2c_bus *bus, int id) +{ + struct saa7146 *saa = (struct saa7146 *) bus->data; + int i; + + DEBUG(printk(KERN_DEBUG "stradis%d: i2c: device found=%02x\n", saa->nr, id)); + if (id == 0xa0) { /* we have rev2 or later board, fill in info */ + for (i = 0; i < 64; i++) + saa->boardcfg[i] = I2CRead(bus, 0xa0, i, 1); +#ifdef USE_RESCUE_EEPROM_SDM275 + if (saa->boardcfg[0] != 0) { + printk("stradis%d: WARNING: EEPROM STORED VALUES HAVE BEEN IGNORED\n", saa->nr); + for (i = 0; i < 64; i++) + saa->boardcfg[i] = rescue_eeprom[i]; + } +#endif + printk("stradis%d: config =", saa->nr); + for (i = 0; i < 51; i++) { + printk(" %02x",saa->boardcfg[i]); + } + printk("\n"); + } +} + +static void detach_inform(struct i2c_bus *bus, int id) +{ + struct saa7146 *saa = (struct saa7146 *) bus->data; + int i; + i = saa->nr; +} + +static void I2CBusScan(struct i2c_bus *bus) +{ + int i; + for (i = 0; i < 0xff; i += 2) + if ((I2CRead(bus, i, 0, 0)) >= 0) + attach_inform(bus, i); +} + +static struct i2c_bus saa7146_i2c_bus_template = +{ + "saa7146", + I2C_BUSID_BT848, + NULL, + SPIN_LOCK_UNLOCKED, + attach_inform, + detach_inform, + NULL, + NULL, + I2CReadOld, + I2CWrite, +}; + +static int debiwait_maxwait = 0; + +static int wait_for_debi_done(struct saa7146 *saa) +{ + int i; + + /* wait for registers to be programmed */ + for (i = 0; i < 100000 && + !(saaread(SAA7146_MC2) & SAA7146_MC2_UPLD_DEBI); i++) + saaread(SAA7146_MC2); + /* wait for transfer to complete */ + for (i = 0; i < 500000 && + (saaread(SAA7146_PSR) & SAA7146_PSR_DEBI_S); i++) + saaread(SAA7146_MC2); + if (i > debiwait_maxwait) + printk("wait-for-debi-done maxwait: %d\n", + debiwait_maxwait = i); + + if (i == 500000) + return -1; + return 0; +} + +static int debiwrite(struct saa7146 *saa, u32 config, int addr, + u32 val, int count) +{ + u32 cmd; + if (count <= 0 || count > 32764) + return -1; + if (wait_for_debi_done(saa) < 0) + return -1; + saawrite(config, SAA7146_DEBI_CONFIG); + if (count <= 4) /* immediate transfer */ + saawrite(val, SAA7146_DEBI_AD); + else /* block transfer */ + saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD); + saawrite((cmd = (count << 17) | (addr & 0xffff)), SAA7146_DEBI_COMMAND); + saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI, + SAA7146_MC2); + return 0; +} + +static u32 debiread(struct saa7146 *saa, u32 config, int addr, int count) +{ + u32 result = 0; + + if (count > 32764 || count <= 0) + return 0; + if (wait_for_debi_done(saa) < 0) + return 0; + saawrite(virt_to_bus(saa->dmadebi), SAA7146_DEBI_AD); + saawrite((count << 17) | 0x10000 | (addr & 0xffff), + SAA7146_DEBI_COMMAND); + saawrite(config, SAA7146_DEBI_CONFIG); + saawrite((SAA7146_MC2_UPLD_DEBI << 16) | SAA7146_MC2_UPLD_DEBI, + SAA7146_MC2); + if (count > 4) /* not an immediate transfer */ + return count; + wait_for_debi_done(saa); + result = saaread(SAA7146_DEBI_AD); + if (count == 1) + result &= 0xff; + if (count == 2) + result &= 0xffff; + if (count == 3) + result &= 0xffffff; + return result; +} + +#if 0 /* unused */ +/* MUST be a multiple of 8 bytes and 8-byte aligned and < 32768 bytes */ +/* data copied into saa->dmadebi buffer, caller must re-enable interrupts */ +static void ibm_block_dram_read(struct saa7146 *saa, int address, int bytes) +{ + int i, j; + u32 *buf; + buf = (u32 *) saa->dmadebi; + if (bytes > 0x7000) + bytes = 0x7000; + saawrite(0, SAA7146_IER); /* disable interrupts */ + for (i=0; i < 10000 && + (debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2) + & 0x8000); i++) + saaread(SAA7146_MC2); + if (i == 10000) + printk(KERN_ERR "stradis%d: dram_busy never cleared\n", + saa->nr); + debiwrite(saa, debNormal, IBM_MP2_SRC_ADDR, (address<<16) | + (address>>16), 4); + debiwrite(saa, debNormal, IBM_MP2_BLOCK_SIZE, bytes, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 0x8a10, 2); + for (j = 0; j < bytes/4; j++) { + for (i = 0; i < 10000 && + (!(debiread(saa, debNormal, IBM_MP2_DRAM_CMD_STAT, 2) + & 0x4000)); i++) + saaread(SAA7146_MC2); + if (i == 10000) + printk(KERN_ERR "stradis%d: dram_ready never set\n", + saa->nr); + buf[j] = debiread(saa, debNormal, IBM_MP2_DRAM_DATA, 4); + } +} +#endif /* unused */ + +static void do_irq_send_data(struct saa7146 *saa) +{ + int split, audbytes, vidbytes; + + saawrite(SAA7146_PSR_PIN1, SAA7146_IER); + /* if special feature mode in effect, disable audio sending */ + if (saa->playmode != VID_PLAY_NORMAL) + saa->audtail = saa->audhead = 0; + if (saa->audhead <= saa->audtail) + audbytes = saa->audtail - saa->audhead; + else + audbytes = 65536 - (saa->audhead - saa->audtail); + if (saa->vidhead <= saa->vidtail) + vidbytes = saa->vidtail - saa->vidhead; + else + vidbytes = 524288 - (saa->vidhead - saa->vidtail); + if (audbytes == 0 && vidbytes == 0 && saa->osdtail == saa->osdhead) { + saawrite(0, SAA7146_IER); + return; + } + /* if at least 1 block audio waiting and audio fifo isn't full */ + if (audbytes >= 2048 && (debiread(saa, debNormal, + IBM_MP2_AUD_FIFO, 2) & 0xff) < 60) { + if (saa->audhead > saa->audtail) + split = 65536 - saa->audhead; + else + split = 0; + audbytes = 2048; + if (split > 0 && split < 2048) { + memcpy(saa->dmadebi, saa->audbuf + saa->audhead, + split); + saa->audhead = 0; + audbytes -= split; + } else + split = 0; + memcpy(saa->dmadebi + split, saa->audbuf + saa->audhead, + audbytes); + saa->audhead += audbytes; + saa->audhead &= 0xffff; + debiwrite(saa, debAudio, (NewCard? IBM_MP2_AUD_FIFO : + IBM_MP2_AUD_FIFOW), 0, 2048); + wake_up_interruptible(&saa->audq); + /* if at least 1 block video waiting and video fifo isn't full */ + } else if (vidbytes >= 30720 && (debiread(saa, debNormal, + IBM_MP2_FIFO, 2)) < 16384) { + if (saa->vidhead > saa->vidtail) + split = 524288 - saa->vidhead; + else + split = 0; + vidbytes = 30720; + if (split > 0 && split < 30720) { + memcpy(saa->dmadebi, saa->vidbuf + saa->vidhead, + split); + saa->vidhead = 0; + vidbytes -= split; + } else + split = 0; + memcpy(saa->dmadebi + split, saa->vidbuf + saa->vidhead, + vidbytes); + saa->vidhead += vidbytes; + saa->vidhead &= 0x7ffff; + debiwrite(saa, debVideo, (NewCard ? IBM_MP2_FIFO : + IBM_MP2_FIFOW), 0, 30720); + wake_up_interruptible(&saa->vidq); + } + saawrite(SAA7146_PSR_DEBI_S | SAA7146_PSR_PIN1, SAA7146_IER); +} + +static void send_osd_data(struct saa7146 *saa) +{ + int size = saa->osdtail - saa->osdhead; + if (size > 30720) + size = 30720; + /* ensure some multiple of 8 bytes is transferred */ + size = 8 * ((size + 8)>>3); + if (size) { + debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR, + (saa->osdhead>>3), 2); + memcpy(saa->dmadebi, &saa->osdbuf[saa->osdhead], size); + saa->osdhead += size; + /* block transfer of next 8 bytes to ~32k bytes */ + debiwrite(saa, debNormal, IBM_MP2_OSD_DATA, 0, size); + } + if (saa->osdhead >= saa->osdtail) { + saa->osdhead = saa->osdtail = 0; + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + } +} + +static void saa7146_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7146 *saa = (struct saa7146 *) dev_id; + u32 stat, astat; + int count; + + count = 0; + while (1) { + /* get/clear interrupt status bits */ + stat = saaread(SAA7146_ISR); + astat = stat & saaread(SAA7146_IER); + if (!astat) + return; + saawrite(astat, SAA7146_ISR); + if (astat & SAA7146_PSR_DEBI_S) { + do_irq_send_data(saa); + } + if (astat & SAA7146_PSR_PIN1) { + int istat; + /* the following read will trigger DEBI_S */ + istat = debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + if (istat & 1) { + saawrite(0, SAA7146_IER); + send_osd_data(saa); + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + } + if (istat & 0x20) { /* Video Start */ + saa->vidinfo.frame_count++; + } + if (istat & 0x400) { /* Picture Start */ + /* update temporal reference */ + } + if (istat & 0x200) { /* Picture Resolution Change */ + /* read new resolution */ + } + if (istat & 0x100) { /* New User Data found */ + /* read new user data */ + } + if (istat & 0x1000) { /* new GOP/SMPTE */ + /* read new SMPTE */ + } + if (istat & 0x8000) { /* Sequence Start Code */ + /* reset frame counter, load sizes */ + saa->vidinfo.frame_count = 0; + saa->vidinfo.h_size = 704; + saa->vidinfo.v_size = 480; +#if 0 + if (saa->endmarkhead != saa->endmarktail) { + saa->audhead = + saa->endmark[saa->endmarkhead]; + saa->endmarkhead++; + if (saa->endmarkhead >= MAX_MARKS) + saa->endmarkhead = 0; + } +#endif + } + if (istat & 0x4000) { /* Sequence Error Code */ + if (saa->endmarkhead != saa->endmarktail) { + saa->audhead = + saa->endmark[saa->endmarkhead]; + saa->endmarkhead++; + if (saa->endmarkhead >= MAX_MARKS) + saa->endmarkhead = 0; + } + } + } +#ifdef IDEBUG + if (astat & SAA7146_PSR_PPEF) { + IDEBUG(printk("stradis%d irq: PPEF\n", saa->nr)); + } + if (astat & SAA7146_PSR_PABO) { + IDEBUG(printk("stradis%d irq: PABO\n", saa->nr)); + } + if (astat & SAA7146_PSR_PPED) { + IDEBUG(printk("stradis%d irq: PPED\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_I1) { + IDEBUG(printk("stradis%d irq: RPS_I1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_I0) { + IDEBUG(printk("stradis%d irq: RPS_I0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_LATE1) { + IDEBUG(printk("stradis%d irq: RPS_LATE1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_LATE0) { + IDEBUG(printk("stradis%d irq: RPS_LATE0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_E1) { + IDEBUG(printk("stradis%d irq: RPS_E1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_E0) { + IDEBUG(printk("stradis%d irq: RPS_E0\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_TO1) { + IDEBUG(printk("stradis%d irq: RPS_TO1\n", saa->nr)); + } + if (astat & SAA7146_PSR_RPS_TO0) { + IDEBUG(printk("stradis%d irq: RPS_TO0\n", saa->nr)); + } + if (astat & SAA7146_PSR_UPLD) { + IDEBUG(printk("stradis%d irq: UPLD\n", saa->nr)); + } + if (astat & SAA7146_PSR_DEBI_E) { + IDEBUG(printk("stradis%d irq: DEBI_E\n", saa->nr)); + } + if (astat & SAA7146_PSR_I2C_S) { + IDEBUG(printk("stradis%d irq: I2C_S\n", saa->nr)); + } + if (astat & SAA7146_PSR_I2C_E) { + IDEBUG(printk("stradis%d irq: I2C_E\n", saa->nr)); + } + if (astat & SAA7146_PSR_A2_IN) { + IDEBUG(printk("stradis%d irq: A2_IN\n", saa->nr)); + } + if (astat & SAA7146_PSR_A2_OUT) { + IDEBUG(printk("stradis%d irq: A2_OUT\n", saa->nr)); + } + if (astat & SAA7146_PSR_A1_IN) { + IDEBUG(printk("stradis%d irq: A1_IN\n", saa->nr)); + } + if (astat & SAA7146_PSR_A1_OUT) { + IDEBUG(printk("stradis%d irq: A1_OUT\n", saa->nr)); + } + if (astat & SAA7146_PSR_AFOU) { + IDEBUG(printk("stradis%d irq: AFOU\n", saa->nr)); + } + if (astat & SAA7146_PSR_V_PE) { + IDEBUG(printk("stradis%d irq: V_PE\n", saa->nr)); + } + if (astat & SAA7146_PSR_VFOU) { + IDEBUG(printk("stradis%d irq: VFOU\n", saa->nr)); + } + if (astat & SAA7146_PSR_FIDA) { + IDEBUG(printk("stradis%d irq: FIDA\n", saa->nr)); + } + if (astat & SAA7146_PSR_FIDB) { + IDEBUG(printk("stradis%d irq: FIDB\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN3) { + IDEBUG(printk("stradis%d irq: PIN3\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN2) { + IDEBUG(printk("stradis%d irq: PIN2\n", saa->nr)); + } + if (astat & SAA7146_PSR_PIN0) { + IDEBUG(printk("stradis%d irq: PIN0\n", saa->nr)); + } + if (astat & SAA7146_PSR_ECS) { + IDEBUG(printk("stradis%d irq: ECS\n", saa->nr)); + } + if (astat & SAA7146_PSR_EC3S) { + IDEBUG(printk("stradis%d irq: EC3S\n", saa->nr)); + } + if (astat & SAA7146_PSR_EC0S) { + IDEBUG(printk("stradis%d irq: EC0S\n", saa->nr)); + } +#endif + count++; + if (count > 15) + printk(KERN_WARNING "stradis%d: irq loop %d\n", + saa->nr, count); + if (count > 20) { + saawrite(0, SAA7146_IER); + printk(KERN_ERR + "stradis%d: IRQ loop cleared\n", saa->nr); + } + } +} + +static int ibm_send_command(struct saa7146 *saa, + int command, int data, int chain) +{ + int i; + + if (chain) + debiwrite(saa, debNormal, IBM_MP2_COMMAND, (command << 1) | 1, 2); + else + debiwrite(saa, debNormal, IBM_MP2_COMMAND, command << 1, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_DATA, data, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_STAT, 1, 2); + for (i = 0; i < 100 && + (debiread(saa, debNormal, IBM_MP2_CMD_STAT, 2) & 1); i++) + schedule(); + if (i == 100) + return -1; + return 0; +} + +static void cs4341_setlevel(struct saa7146 *saa, int left, int right) +{ + I2CWrite(&(saa->i2c), 0x22, 0x03, + left > 94 ? 94 : left, 2); + I2CWrite(&(saa->i2c), 0x22, 0x04, + right > 94 ? 94 : right, 2); +} + +static void initialize_cs4341(struct saa7146 *saa) +{ + int i; + for (i = 0; i < 200; i++) { + /* auto mute off, power on, no de-emphasis */ + /* I2S data up to 24-bit 64xFs internal SCLK */ + I2CWrite(&(saa->i2c), 0x22, 0x01, 0x11, 2); + /* ATAPI mixer setings */ + I2CWrite(&(saa->i2c), 0x22, 0x02, 0x49, 2); + /* attenuation left 3db */ + I2CWrite(&(saa->i2c), 0x22, 0x03, 0x00, 2); + /* attenuation right 3db */ + I2CWrite(&(saa->i2c), 0x22, 0x04, 0x00, 2); + I2CWrite(&(saa->i2c), 0x22, 0x01, 0x10, 2); + if (I2CRead(&(saa->i2c), 0x22, 0x02, 1) == 0x49) + break; + schedule(); + } + printk("stradis%d: CS4341 initialized (%d)\n", saa->nr, i); + return; +} + +static void initialize_cs8420(struct saa7146 *saa, int pro) +{ + int i; + u8 *sequence; + if (pro) + sequence = mode8420pro; + else + sequence = mode8420con; + for (i = 0; i < INIT8420LEN; i++) + I2CWrite(&(saa->i2c), 0x20, init8420[i * 2], + init8420[i * 2 + 1], 2); + for (i = 0; i < MODE8420LEN; i++) + I2CWrite(&(saa->i2c), 0x20, sequence[i * 2], + sequence[i * 2 + 1], 2); + printk("stradis%d: CS8420 initialized\n", saa->nr); +} + +static void initialize_saa7121(struct saa7146 *saa, int dopal) +{ + int i, mod; + u8 *sequence; + if (dopal) + sequence = init7121pal; + else + sequence = init7121ntsc; + mod = saaread(SAA7146_PSR) & 0x08; + /* initialize PAL/NTSC video encoder */ + for (i = 0; i < INIT7121LEN; i++) { + if (NewCard) { /* handle new card encoder differences */ + if (sequence[i*2] == 0x3a) + I2CWrite(&(saa->i2c), 0x88, 0x3a, 0x13, 2); + else if (sequence[i*2] == 0x6b) + I2CWrite(&(saa->i2c), 0x88, 0x6b, 0x20, 2); + else if (sequence[i*2] == 0x6c) + I2CWrite(&(saa->i2c), 0x88, 0x6c, + dopal ? 0x09 : 0xf5, 2); + else if (sequence[i*2] == 0x6d) + I2CWrite(&(saa->i2c), 0x88, 0x6d, + dopal ? 0x20 : 0x00, 2); + else if (sequence[i*2] == 0x7a) + I2CWrite(&(saa->i2c), 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i*2] == 0x7b) + I2CWrite(&(saa->i2c), 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else I2CWrite(&(saa->i2c), 0x88, sequence[i * 2], + sequence[i * 2 + 1], 2); + } else { + if (sequence[i*2] == 0x6b && mod) + I2CWrite(&(saa->i2c), 0x88, 0x6b, + (sequence[i * 2 + 1] ^ 0x09), 2); + else if (sequence[i*2] == 0x7a) + I2CWrite(&(saa->i2c), 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i*2] == 0x7b) + I2CWrite(&(saa->i2c), 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else + I2CWrite(&(saa->i2c), 0x88, sequence[i * 2], + sequence[i * 2 + 1], 2); + } + } +} + +static void set_genlock_offset(struct saa7146 *saa, int noffset) +{ + int nCode; + int PixelsPerLine = 858; + if (CurrentMode == VIDEO_MODE_PAL) + PixelsPerLine = 864; + if (noffset > 500) + noffset = 500; + else if (noffset < -500) + noffset = -500; + nCode = noffset + 0x100; + if (nCode == 1) + nCode = 0x401; + else if (nCode < 1) nCode = 0x400 + PixelsPerLine + nCode; + debiwrite(saa, debNormal, XILINX_GLDELAY, nCode, 2); +} + +static void set_out_format(struct saa7146 *saa, int mode) +{ + initialize_saa7121(saa, (mode == VIDEO_MODE_NTSC ? 0 : 1)); + saa->boardcfg[2] = mode; + /* do not adjust analog video parameters here, use saa7121 init */ + /* you will affect the SDI output on the new card */ + if (mode == VIDEO_MODE_PAL) { /* PAL */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x0808, 2); + mdelay(50); + saawrite(0x012002c0, SAA7146_NUM_LINE_BYTE1); + if (NewCard) { + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + 0xe100, 2); + mdelay(50); + } + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + NewCard ? 0xe500: 0x6500, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_DLY, + (1 << 8) | + (NewCard ? PALFirstActive : PALFirstActive-6), 2); + } else { /* NTSC */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x0800, 2); + mdelay(50); + saawrite(0x00f002c0, SAA7146_NUM_LINE_BYTE1); + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + NewCard ? 0xe100: 0x6100, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_DLY, + (1 << 8) | + (NewCard ? NTSCFirstActive : NTSCFirstActive-6), 2); + } +} + + +/* Intialize bitmangler to map from a byte value to the mangled word that + * must be output to program the Xilinx part through the DEBI port. + * Xilinx Data Bit->DEBI Bit: 0->15 1->7 2->6 3->12 4->11 5->2 6->1 7->0 + * transfer FPGA code, init IBM chip, transfer IBM microcode + * rev2 card mangles: 0->7 1->6 2->5 3->4 4->3 5->2 6->1 7->0 + */ +static u16 bitmangler[256]; + +static int initialize_fpga(struct video_code *bitdata) +{ + int i, num, startindex, failure = 0, loadtwo, loadfile = 0; + u16 *dmabuf; + u8 *newdma; + struct saa7146 *saa; + + /* verify fpga code */ + for (startindex = 0; startindex < bitdata->datasize; startindex++) + if (bitdata->data[startindex] == 255) + break; + if (startindex == bitdata->datasize) { + printk(KERN_INFO "stradis: bad fpga code\n"); + return -1; + } + /* initialize all detected cards */ + for (num = 0; num < saa_num; num++) { + saa = &saa7146s[num]; + if (saa->boardcfg[0] > 20) + continue; /* card was programmed */ + loadtwo = (saa->boardcfg[18] & 0x10); + if (!NewCard) /* we have an old board */ + for (i = 0; i < 256; i++) + bitmangler[i] = ((i & 0x01) << 15) | + ((i & 0x02) << 6) | ((i & 0x04) << 4) | + ((i & 0x08) << 9) | ((i & 0x10) << 7) | + ((i & 0x20) >> 3) | ((i & 0x40) >> 5) | + ((i & 0x80) >> 7); + else /* else we have a new board */ + for (i = 0; i < 256; i++) + bitmangler[i] = ((i & 0x01) << 7) | + ((i & 0x02) << 5) | ((i & 0x04) << 3) | + ((i & 0x08) << 1) | ((i & 0x10) >> 1) | + ((i & 0x20) >> 3) | ((i & 0x40) >> 5) | + ((i & 0x80) >> 7); + + dmabuf = (u16 *) saa->dmadebi; + newdma = (u8 *) saa->dmadebi; + if (NewCard) { /* SDM2xxx */ + if (!strncmp(bitdata->loadwhat, "decoder2", 8)) + continue; /* fpga not for this card */ + if (!strncmp(&saa->boardcfg[42], + bitdata->loadwhat, 8)) { + loadfile = 1; + } else if (loadtwo && !strncmp(&saa->boardcfg[19], + bitdata->loadwhat, 8)) { + loadfile = 2; + } else if (!saa->boardcfg[42] && /* special */ + !strncmp("decxl", bitdata->loadwhat, 8)) { + loadfile = 1; + } else + continue; /* fpga not for this card */ + if (loadfile != 1 && loadfile != 2) { + continue; /* skip to next card */ + } + if (saa->boardcfg[0] && loadfile == 1 ) + continue; /* skip to next card */ + if (saa->boardcfg[0] != 1 && loadfile == 2) + continue; /* skip to next card */ + saa->boardcfg[0]++; /* mark fpga handled */ + printk("stradis%d: loading %s\n", saa->nr, + bitdata->loadwhat); + if (loadtwo && loadfile == 2) + goto send_fpga_stuff; + /* turn on the Audio interface to set PROG low */ + saawrite(0x00400040, SAA7146_GPIO_CTRL); + saaread(SAA7146_PSR); /* ensure posted write */ + /* wait for everyone to reset */ + mdelay(10); + saawrite(0x00400000, SAA7146_GPIO_CTRL); + } else { /* original card */ + if (strncmp(bitdata->loadwhat, "decoder2", 8)) + continue; /* fpga not for this card */ + /* Pull the Xilinx PROG signal WS3 low */ + saawrite(0x02000200, SAA7146_MC1); + /* Turn on the Audio interface so can set PROG low */ + saawrite(0x000000c0, SAA7146_ACON1); + /* Pull the Xilinx INIT signal (GPIO2) low */ + saawrite(0x00400000, SAA7146_GPIO_CTRL); + /* Make sure everybody resets */ + saaread(SAA7146_PSR); /* ensure posted write */ + mdelay(10); + /* Release the Xilinx PROG signal */ + saawrite(0x00000000, SAA7146_ACON1); + /* Turn off the Audio interface */ + saawrite(0x02000000, SAA7146_MC1); + } + /* Release Xilinx INIT signal (WS2) */ + saawrite(0x00000000, SAA7146_GPIO_CTRL); + /* Wait for the INIT to go High */ + for (i = 0; i < 10000 && + !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++) + schedule(); + if (i == 1000) { + printk(KERN_INFO "stradis%d: no fpga INIT\n", saa->nr); + return -1; + } +send_fpga_stuff: + if (NewCard) { + for (i = startindex; i < bitdata->datasize; i++) + newdma[i - startindex] = + bitmangler[bitdata->data[i]]; + debiwrite(saa, 0x01420000, 0, 0, + ((bitdata->datasize - startindex) + 5)); + if (loadtwo) { + if (loadfile == 1) { + printk("stradis%d: " + "awaiting 2nd FPGA bitfile\n", + saa->nr); + continue; /* skip to next card */ + } + + } + } else { + for (i = startindex; i < bitdata->datasize; i++) + dmabuf[i - startindex] = + bitmangler[bitdata->data[i]]; + debiwrite(saa, 0x014a0000, 0, 0, + ((bitdata->datasize - startindex) + 5) * 2); + } + for (i = 0; i < 1000 && + !(saaread(SAA7146_PSR) & SAA7146_PSR_PIN2); i++) + schedule(); + if (i == 1000) { + printk(KERN_INFO "stradis%d: FPGA load failed\n", + saa->nr); + failure++; + continue; + } + if (!NewCard) { + /* Pull the Xilinx INIT signal (GPIO2) low */ + saawrite(0x00400000, SAA7146_GPIO_CTRL); + saaread(SAA7146_PSR); /* ensure posted write */ + mdelay(2); + saawrite(0x00000000, SAA7146_GPIO_CTRL); + mdelay(2); + } + printk(KERN_INFO "stradis%d: FPGA Loaded\n", saa->nr); + saa->boardcfg[0] = 26; /* mark fpga programmed */ + /* set VXCO to its lowest frequency */ + debiwrite(saa, debNormal, XILINX_PWM, 0, 2); + if (NewCard) { + /* mute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, + 0, 2); + /* set VXCO to PWM mode, release reset, blank on */ + debiwrite(saa, debNormal, XILINX_CTL0, 0xffc4, 2); + mdelay(10); + /* unmute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CTL0, + 0x2020, 2); + } + /* set source Black */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2); + saa->boardcfg[4] = 22; /* set NTSC First Active Line */ + saa->boardcfg[5] = 23; /* set PAL First Active Line */ + saa->boardcfg[54] = 2; /* set NTSC Last Active Line - 256 */ + saa->boardcfg[55] = 54; /* set PAL Last Active Line - 256 */ + set_out_format(saa, VIDEO_MODE_NTSC); + mdelay(50); + /* begin IBM chip init */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2); + saaread(SAA7146_PSR); /* wait for reset */ + mdelay(5); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2); + debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0x10, 2); + debiwrite(saa, debNormal, IBM_MP2_CMD_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2); + if (NewCard) { + mdelay(5); + /* set i2s rate converter to 48KHz */ + debiwrite(saa, debNormal, 0x80c0, 6, 2); + /* we must init CS8420 first since rev b pulls i2s */ + /* master clock low and CS4341 needs i2s master to */ + /* run the i2c port. */ + if (HaveCS8420) { + /* 0=consumer, 1=pro */ + initialize_cs8420(saa, 0); + } + mdelay(5); + if (HaveCS4341) + initialize_cs4341(saa); + } + debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2); + debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2); + if (NewCard) + set_genlock_offset(saa, 0); + debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2); +#if 0 + /* enable genlock */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x8000, 2); +#else + /* disable genlock */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x8080, 2); +#endif + } + return failure; +} + +static int do_ibm_reset(struct saa7146 *saa) +{ + /* failure if decoder not previously programmed */ + if (saa->boardcfg[0] < 37) + return -EIO; + /* mute CS3310 */ + if (HaveCS3310) + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, 0, 2); + /* disable interrupts */ + saawrite(0, SAA7146_IER); + saa->audhead = saa->audtail = 0; + saa->vidhead = saa->vidtail = 0; + /* tristate debi bus, disable debi transfers */ + saawrite(0x00880000, SAA7146_MC1); + /* ensure posted write */ + saaread(SAA7146_MC1); + mdelay(50); + /* re-enable debi transfers */ + saawrite(0x00880088, SAA7146_MC1); + /* set source Black */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1707, 2); + /* begin IBM chip init */ + set_out_format(saa, CurrentMode); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 4, 2); + saaread(SAA7146_PSR); /* wait for reset */ + mdelay(5); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, 0, 2); + debiread(saa, debNormal, IBM_MP2_CHIP_CONTROL, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_MODE, 0x2e, 2); + if (NewCard) { + mdelay(5); + /* set i2s rate converter to 48KHz */ + debiwrite(saa, debNormal, 0x80c0, 6, 2); + /* we must init CS8420 first since rev b pulls i2s */ + /* master clock low and CS4341 needs i2s master to */ + /* run the i2c port. */ + if (HaveCS8420) { + /* 0=consumer, 1=pro */ + initialize_cs8420(saa, 1); + } + mdelay(5); + if (HaveCS4341) + initialize_cs4341(saa); + } + debiwrite(saa, debNormal, IBM_MP2_INFC_CTL, 0x48, 2); + debiwrite(saa, debNormal, IBM_MP2_BEEP_CTL, 0xa000, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_LBOR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_TBOR, 0, 2); + if (NewCard) + set_genlock_offset(saa, 0); + debiwrite(saa, debNormal, IBM_MP2_FRNT_ATTEN, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2); + if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER, + (ChipControl == 0x43 ? 0xe800 : 0xe000), 1)) { + printk(KERN_ERR "stradis%d: IBM config failed\n", saa->nr); + } + if (HaveCS3310) { + int i = CS3310MaxLvl; + debiwrite(saa, debNormal, XILINX_CS3310_CMPLT, ((i<<8)|i), 2); + } + /* start video decoder */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, ChipControl, 2); + /* 256k vid, 3520 bytes aud */ + debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD, 0x4037, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2); + ibm_send_command(saa, IBM_MP2_PLAY, 0, 0); + /* enable buffer threshold irq */ + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + /* clear pending interrupts */ + debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2); + return 0; +} + +/* load the decoder microcode */ +static int initialize_ibmmpeg2(struct video_code *microcode) +{ + int i, num; + struct saa7146 *saa; + + for (num = 0; num < saa_num; num++) { + saa = &saa7146s[num]; + /* check that FPGA is loaded */ + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0xa55a, 2); + if ((i = debiread(saa, debNormal, IBM_MP2_OSD_SIZE, 2)) != + 0xa55a) { + printk(KERN_INFO "stradis%d: %04x != 0xa55a\n", + saa->nr, i); +#if 0 + return -1; +#endif + } + if (!strncmp(microcode->loadwhat, "decoder.vid", 11)) { + if (saa->boardcfg[0] > 27) + continue; /* skip to next card */ + /* load video control store */ + saa->boardcfg[1] = 0x13; /* no-sync default */ + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2); + debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2); + for (i = 0; i < microcode->datasize / 2; i++) + debiwrite(saa, debNormal, IBM_MP2_PROC_IDATA, + (microcode->data[i * 2] << 8) | + microcode->data[i * 2 + 1], 2); + debiwrite(saa, debNormal, IBM_MP2_PROC_IADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + saa->boardcfg[0] = 28; + } + if (!strncmp(microcode->loadwhat, "decoder.aud", 11)) { + if (saa->boardcfg[0] > 35) + continue; /* skip to next card */ + /* load audio control store */ + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 1, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2); + for (i = 0; i < microcode->datasize; i++) + debiwrite(saa, debNormal, IBM_MP2_AUD_IDATA, + microcode->data[i], 1); + debiwrite(saa, debNormal, IBM_MP2_AUD_IADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_WR_PROT, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_SIZE, 0x2000, 2); + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4552, 2); + if (ibm_send_command(saa, IBM_MP2_CONFIG_DECODER, + 0xe000, 1)) { + printk(KERN_ERR + "stradis%d: IBM config failed\n", + saa->nr); + return -1; + } + /* set PWM to center value */ + if (NewCard) { + debiwrite(saa, debNormal, XILINX_PWM, + saa->boardcfg[14] + + (saa->boardcfg[13]<<8), 2); + } else + debiwrite(saa, debNormal, XILINX_PWM, + 0x46, 2); + if (HaveCS3310) { + i = CS3310MaxLvl; + debiwrite(saa, debNormal, + XILINX_CS3310_CMPLT, ((i<<8)|i), 2); + } + printk(KERN_INFO + "stradis%d: IBM MPEGCD%d Initialized\n", + saa->nr, 18 + (debiread(saa, debNormal, + IBM_MP2_CHIP_CONTROL, 2) >> 12)); + /* start video decoder */ + debiwrite(saa, debNormal, IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + debiwrite(saa, debNormal, IBM_MP2_RB_THRESHOLD, + 0x4037, 2); /* 256k vid, 3520 bytes aud */ + debiwrite(saa, debNormal, IBM_MP2_AUD_CTL, 0x4573, 2); + ibm_send_command(saa, IBM_MP2_PLAY, 0, 0); + /* enable buffer threshold irq */ + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00c, 2); + debiread(saa, debNormal, IBM_MP2_HOST_INT, 2); + /* enable gpio irq */ + saawrite(0x00002000, SAA7146_GPIO_CTRL); + /* enable decoder output to HPS */ + debiwrite(saa, debNormal, XILINX_CTL0, 0x1711, 2); + saa->boardcfg[0] = 37; + } + } + return 0; +} + +static u32 palette2fmt[] = +{ /* some of these YUV translations are wrong */ + 0xffffffff, 0x86000000, 0x87000000, 0x80000000, 0x8100000, 0x82000000, + 0x83000000, 0x00000000, 0x03000000, 0x03000000, 0x0a00000, 0x03000000, + 0x06000000, 0x00000000, 0x03000000, 0x0a000000, 0x0300000 +}; +static int bpp2fmt[4] = +{ + VIDEO_PALETTE_HI240, VIDEO_PALETTE_RGB565, VIDEO_PALETTE_RGB24, + VIDEO_PALETTE_RGB32 +}; + +/* I wish I could find a formula to calculate these... */ +static u32 h_prescale[64] = +{ + 0x10000000, 0x18040202, 0x18080000, 0x380c0606, 0x38100204, 0x38140808, + 0x38180000, 0x381c0000, 0x3820161c, 0x38242a3b, 0x38281230, 0x382c4460, + 0x38301040, 0x38340080, 0x38380000, 0x383c0000, 0x3840fefe, 0x3844ee9f, + 0x3848ee9f, 0x384cee9f, 0x3850ee9f, 0x38542a3b, 0x38581230, 0x385c0000, + 0x38600000, 0x38640000, 0x38680000, 0x386c0000, 0x38700000, 0x38740000, + 0x38780000, 0x387c0000, 0x30800000, 0x38840000, 0x38880000, 0x388c0000, + 0x38900000, 0x38940000, 0x38980000, 0x389c0000, 0x38a00000, 0x38a40000, + 0x38a80000, 0x38ac0000, 0x38b00000, 0x38b40000, 0x38b80000, 0x38bc0000, + 0x38c00000, 0x38c40000, 0x38c80000, 0x38cc0000, 0x38d00000, 0x38d40000, + 0x38d80000, 0x38dc0000, 0x38e00000, 0x38e40000, 0x38e80000, 0x38ec0000, + 0x38f00000, 0x38f40000, 0x38f80000, 0x38fc0000, +}; +static u32 v_gain[64] = +{ + 0x016000ff, 0x016100ff, 0x016100ff, 0x016200ff, 0x016200ff, 0x016200ff, + 0x016200ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff, 0x016300ff, + 0x016300ff, 0x016300ff, 0x016300ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, + 0x016400ff, 0x016400ff, 0x016400ff, 0x016400ff, +}; + + +static void saa7146_set_winsize(struct saa7146 *saa) +{ + u32 format; + int offset, yacl, ysci; + saa->win.color_fmt = format = + (saa->win.depth == 15) ? palette2fmt[VIDEO_PALETTE_RGB555] : + palette2fmt[bpp2fmt[(saa->win.bpp - 1) & 3]]; + offset = saa->win.x * saa->win.bpp + saa->win.y * saa->win.bpl; + saawrite(saa->win.vidadr + offset, SAA7146_BASE_EVEN1); + saawrite(saa->win.vidadr + offset + saa->win.bpl, SAA7146_BASE_ODD1); + saawrite(saa->win.bpl * 2, SAA7146_PITCH1); + saawrite(saa->win.vidadr + saa->win.bpl * saa->win.sheight, + SAA7146_PROT_ADDR1); + saawrite(0, SAA7146_PAGE1); + saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL); + offset = (704 / (saa->win.width - 1)) & 0x3f; + saawrite(h_prescale[offset], SAA7146_HPS_H_PRESCALE); + offset = (720896 / saa->win.width) / (offset + 1); + saawrite((offset<<12)|0x0c, SAA7146_HPS_H_SCALE); + if (CurrentMode == VIDEO_MODE_NTSC) { + yacl = /*(480 / saa->win.height - 1) & 0x3f*/ 0; + ysci = 1024 - (saa->win.height * 1024 / 480); + } else { + yacl = /*(576 / saa->win.height - 1) & 0x3f*/ 0; + ysci = 1024 - (saa->win.height * 1024 / 576); + } + saawrite((1<<31)|(ysci<<21)|(yacl<<15), SAA7146_HPS_V_SCALE); + saawrite(v_gain[yacl], SAA7146_HPS_V_GAIN); + saawrite(((SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_HPS_V | + SAA7146_MC2_UPLD_HPS_H) << 16) | (SAA7146_MC2_UPLD_DMA1 | + SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_HPS_H), + SAA7146_MC2); +} + +/* clip_draw_rectangle(cm,x,y,w,h) -- handle clipping an area + * bitmap is fixed width, 128 bytes (1024 pixels represented) + * arranged most-sigificant-bit-left in 32-bit words + * based on saa7146 clipping hardware, it swaps bytes if LE + * much of this makes up for egcs brain damage -- so if you + * are wondering "why did he do this?" it is because the C + * was adjusted to generate the optimal asm output without + * writing non-portable __asm__ directives. + */ + +static void clip_draw_rectangle(u32 *clipmap, int x, int y, int w, int h) +{ + register int startword, endword; + register u32 bitsleft, bitsright; + u32 *temp; + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + if (w <= 0 || h <= 0 || x > 1023 || y > 639) + return; /* throw away bad clips */ + if (x + w > 1024) + w = 1024 - x; + if (y + h > 640) + h = 640 - y; + startword = (x >> 5); + endword = ((x + w) >> 5); + bitsleft = (0xffffffff >> (x & 31)); + bitsright = (0xffffffff << (~((x + w) - (endword<<5)))); + temp = &clipmap[(y<<5) + startword]; + w = endword - startword; + if (!w) { + bitsleft |= bitsright; + for (y = 0; y < h; y++) { + *temp |= bitsleft; + temp += 32; + } + } else { + for (y = 0; y < h; y++) { + *temp++ |= bitsleft; + for (x = 1; x < w; x++) + *temp++ = 0xffffffff; + *temp |= bitsright; + temp += (32 - w); + } + } +} + +static void make_clip_tab(struct saa7146 *saa, struct video_clip *cr, int ncr) +{ + int i, width, height; + u32 *clipmap; + + clipmap = saa->dmavid2; + if((width=saa->win.width)>1023) + width = 1023; /* sanity check */ + if((height=saa->win.height)>640) + height = 639; /* sanity check */ + if (ncr > 0) { /* rectangles pased */ + /* convert rectangular clips to a bitmap */ + memset(clipmap, 0, VIDEO_CLIPMAP_SIZE); /* clear map */ + for (i = 0; i < ncr; i++) + clip_draw_rectangle(clipmap, cr[i].x, cr[i].y, + cr[i].width, cr[i].height); + } + /* clip against viewing window AND screen + so we do not have to rely on the user program + */ + clip_draw_rectangle(clipmap,(saa->win.x+width>saa->win.swidth) ? + (saa->win.swidth-saa->win.x) : width, 0, 1024, 768); + clip_draw_rectangle(clipmap,0,(saa->win.y+height>saa->win.sheight) ? + (saa->win.sheight-saa->win.y) : height,1024,768); + if (saa->win.x<0) + clip_draw_rectangle(clipmap, 0, 0, -(saa->win.x), 768); + if (saa->win.y<0) + clip_draw_rectangle(clipmap, 0, 0, 1024, -(saa->win.y)); +} + +static int saa_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct saa7146 *saa = (struct saa7146 *) dev; + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + strcpy(b.name, saa->video_dev.name); + b.type = VID_TYPE_CAPTURE | + VID_TYPE_OVERLAY | + VID_TYPE_CLIPPING | + VID_TYPE_FRAMERAM | + VID_TYPE_SCALES; + b.channels = 1; + b.audios = 1; + b.maxwidth = 768; + b.maxheight = 576; + b.minwidth = 32; + b.minheight = 32; + if (copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p = saa->picture; + if (saa->win.depth == 8) + p.palette = VIDEO_PALETTE_HI240; + if (saa->win.depth == 15) + p.palette = VIDEO_PALETTE_RGB555; + if (saa->win.depth == 16) + p.palette = VIDEO_PALETTE_RGB565; + if (saa->win.depth == 24) + p.palette = VIDEO_PALETTE_RGB24; + if (saa->win.depth == 32) + p.palette = VIDEO_PALETTE_RGB32; + if (copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + u32 format; + if (copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + if (p.palette < sizeof(palette2fmt) / sizeof(u32)) { + format = palette2fmt[p.palette]; + saa->win.color_fmt = format; + saawrite(format|0x60, SAA7146_CLIP_FORMAT_CTRL); + } + saawrite(((p.brightness & 0xff00) << 16) | + ((p.contrast & 0xfe00) << 7) | + ((p.colour & 0xfe00) >> 9), SAA7146_BCS_CTRL); + saa->picture = p; + /* upload changed registers */ + saawrite(((SAA7146_MC2_UPLD_HPS_H | + SAA7146_MC2_UPLD_HPS_V) << 16) | + SAA7146_MC2_UPLD_HPS_H | SAA7146_MC2_UPLD_HPS_V, + SAA7146_MC2); + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip *vcp = NULL; + + if (copy_from_user(&vw, arg, sizeof(vw))) + return -EFAULT; + + if (vw.flags || vw.width < 16 || vw.height < 16) { /* stop capture */ + saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1); + return -EINVAL; + } + if (saa->win.bpp < 4) { /* 32-bit align start and adjust width */ + int i = vw.x; + vw.x = (vw.x + 3) & ~3; + i = vw.x - i; + vw.width -= i; + } + saa->win.x = vw.x; + saa->win.y = vw.y; + saa->win.width = vw.width; + if (saa->win.width > 768) + saa->win.width = 768; + saa->win.height = vw.height; + if (CurrentMode == VIDEO_MODE_NTSC) { + if (saa->win.height > 480) + saa->win.height = 480; + } else { + if (saa->win.height > 576) + saa->win.height = 576; + } + + /* stop capture */ + saawrite((SAA7146_MC1_TR_E_1 << 16), SAA7146_MC1); + saa7146_set_winsize(saa); + + /* + * Do any clips. + */ + if (vw.clipcount < 0) { + if (copy_from_user(saa->dmavid2, vw.clips, + VIDEO_CLIPMAP_SIZE)) + return -EFAULT; + } else if (vw.clipcount > 0) { + if ((vcp = vmalloc(sizeof(struct video_clip) * + (vw.clipcount))) == NULL) + return -ENOMEM; + if (copy_from_user(vcp, vw.clips, + sizeof(struct video_clip) * + vw.clipcount)) { + vfree(vcp); + return -EFAULT; + } + } else /* nothing clipped */ + memset(saa->dmavid2, 0, VIDEO_CLIPMAP_SIZE); + make_clip_tab(saa, vcp, vw.clipcount); + if (vw.clipcount > 0) + vfree(vcp); + + /* start capture & clip dma if we have an address */ + if ((saa->cap & 3) && saa->win.vidadr != 0) + saawrite(((SAA7146_MC1_TR_E_1 | + SAA7146_MC1_TR_E_2) << 16) | 0xffff, + SAA7146_MC1); + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x = saa->win.x; + vw.y = saa->win.y; + vw.width = saa->win.width; + vw.height = saa->win.height; + vw.chromakey = 0; + vw.flags = 0; + if (copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + { + int v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v == 0) { + saa->cap &= ~1; + saawrite((SAA7146_MC1_TR_E_1 << 16), + SAA7146_MC1); + } else { + if (saa->win.vidadr == 0 || saa->win.width == 0 + || saa->win.height == 0) + return -EINVAL; + saa->cap |= 1; + saawrite((SAA7146_MC1_TR_E_1 << 16) | 0xffff, + SAA7146_MC1); + } + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer v; + v.base = (void *) saa->win.vidadr; + v.height = saa->win.sheight; + v.width = saa->win.swidth; + v.depth = saa->win.depth; + v.bytesperline = saa->win.bpl; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + + } + case VIDIOCSFBUF: + { + struct video_buffer v; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.depth != 8 && v.depth != 15 && v.depth != 16 && + v.depth != 24 && v.depth != 32 && v.width > 16 && + v.height > 16 && v.bytesperline > 16) + return -EINVAL; + if (v.base) + saa->win.vidadr = (unsigned long) v.base; + saa->win.sheight = v.height; + saa->win.swidth = v.width; + saa->win.bpp = ((v.depth + 7) & 0x38) / 8; + saa->win.depth = v.depth; + saa->win.bpl = v.bytesperline; + + DEBUG(printk("Display at %p is %d by %d, bytedepth %d, bpl %d\n", + v.base, v.width, v.height, saa->win.bpp, saa->win.bpl)); + saa7146_set_winsize(saa); + return 0; + } + case VIDIOCKEY: + { + /* Will be handled higher up .. */ + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio v; + v = saa->audio_dev; + v.flags &= ~(VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); + v.flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME; + strcpy(v.name, "MPEG"); + v.mode = VIDEO_SOUND_STEREO; + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio v; + int i; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + i = (~(v.volume>>8))&0xff; + if (!HaveCS4341) { + if (v.flags & VIDEO_AUDIO_MUTE) { + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + 0xffff, 2); + } + if (!(v.flags & VIDEO_AUDIO_MUTE)) + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + 0x0000, 2); + if (v.flags & VIDEO_AUDIO_VOLUME) + debiwrite(saa, debNormal, + IBM_MP2_FRNT_ATTEN, + (i<<8)|i, 2); + } else { + if (v.flags & VIDEO_AUDIO_MUTE) + cs4341_setlevel(saa, 0xff, 0xff); + if (!(v.flags & VIDEO_AUDIO_MUTE)) + cs4341_setlevel(saa, 0, 0); + if (v.flags & VIDEO_AUDIO_VOLUME) + cs4341_setlevel(saa, i, i); + } + saa->audio_dev = v; + return 0; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + vu.video = saa->video_dev.minor; + vu.vbi = VIDEO_NO_UNIT; + vu.radio = VIDEO_NO_UNIT; + vu.audio = VIDEO_NO_UNIT; + vu.teletext = VIDEO_NO_UNIT; + if (copy_to_user((void *) arg, (void *) &vu, sizeof(vu))) + return -EFAULT; + return 0; + } + case VIDIOCSPLAYMODE: + { + struct video_play_mode pmode; + if (copy_from_user((void *) &pmode, arg, + sizeof(struct video_play_mode))) + return -EFAULT; + switch (pmode.mode) { + case VID_PLAY_VID_OUT_MODE: + if (pmode.p1 != VIDEO_MODE_NTSC && + pmode.p1 != VIDEO_MODE_PAL) + return -EINVAL; + set_out_format(saa, pmode.p1); + return 0; + case VID_PLAY_GENLOCK: + debiwrite(saa, debNormal, + XILINX_CTL0, + (pmode.p1 ? 0x8000 : 0x8080), + 2); + if (NewCard) + set_genlock_offset(saa, + pmode.p2); + return 0; + case VID_PLAY_NORMAL: + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_PLAY, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_PAUSE: + /* IBM removed the PAUSE command */ + /* they say use SINGLE_FRAME now */ + case VID_PLAY_SINGLE_FRAME: + ibm_send_command(saa, + IBM_MP2_SINGLE_FRAME, + 0, 0); + if (saa->playmode == pmode.mode) { + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + } + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_FAST_FORWARD: + ibm_send_command(saa, + IBM_MP2_FAST_FORWARD, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_SLOW_MOTION: + ibm_send_command(saa, + IBM_MP2_SLOW_MOTION, + pmode.p1, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_IMMEDIATE_NORMAL: + /* ensure transfers resume */ + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_IMED_NORM_PLAY, 0, 0); + saa->playmode = VID_PLAY_NORMAL; + return 0; + case VID_PLAY_SWITCH_CHANNELS: + saa->audhead = saa->audtail = 0; + saa->vidhead = saa->vidtail = 0; + ibm_send_command(saa, + IBM_MP2_FREEZE_FRAME, 0, 1); + ibm_send_command(saa, + IBM_MP2_RESET_AUD_RATE, 0, 1); + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, 0, 2); + ibm_send_command(saa, + IBM_MP2_CHANNEL_SWITCH, 0, 1); + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + ibm_send_command(saa, + IBM_MP2_PLAY, 0, 0); + saa->playmode = VID_PLAY_NORMAL; + return 0; + case VID_PLAY_FREEZE_FRAME: + ibm_send_command(saa, + IBM_MP2_FREEZE_FRAME, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_STILL_MODE: + ibm_send_command(saa, + IBM_MP2_SET_STILL_MODE, 0, 0); + saa->playmode = pmode.mode; + return 0; + case VID_PLAY_MASTER_MODE: + if (pmode.p1 == VID_PLAY_MASTER_NONE) + saa->boardcfg[1] = 0x13; + else if (pmode.p1 == + VID_PLAY_MASTER_VIDEO) + saa->boardcfg[1] = 0x23; + else if (pmode.p1 == + VID_PLAY_MASTER_AUDIO) + saa->boardcfg[1] = 0x43; + else + return -EINVAL; + debiwrite(saa, debNormal, + IBM_MP2_CHIP_CONTROL, + ChipControl, 2); + return 0; + case VID_PLAY_ACTIVE_SCANLINES: + if (CurrentMode == VIDEO_MODE_PAL) { + if (pmode.p1 < 1 || + pmode.p2 > 625) + return -EINVAL; + saa->boardcfg[5] = pmode.p1; + saa->boardcfg[55] = (pmode.p1 + + (pmode.p2/2) - 1) & + 0xff; + } else { + if (pmode.p1 < 4 || + pmode.p2 > 525) + return -EINVAL; + saa->boardcfg[4] = pmode.p1; + saa->boardcfg[54] = (pmode.p1 + + (pmode.p2/2) - 4) & + 0xff; + } + set_out_format(saa, CurrentMode); + case VID_PLAY_RESET: + return do_ibm_reset(saa); + case VID_PLAY_END_MARK: + if (saa->endmarktail < + saa->endmarkhead) { + if (saa->endmarkhead - + saa->endmarktail < 2) + return -ENOSPC; + } else if (saa->endmarkhead <= + saa->endmarktail) { + if (saa->endmarktail - + saa->endmarkhead > + (MAX_MARKS - 2)) + return -ENOSPC; + } else + return -ENOSPC; + saa->endmark[saa->endmarktail] = + saa->audtail; + saa->endmarktail++; + if (saa->endmarktail >= MAX_MARKS) + saa->endmarktail = 0; + } + return -EINVAL; + } + case VIDIOCSWRITEMODE: + { + int mode; + if (copy_from_user((void *) &mode, arg, sizeof(int))) + return -EFAULT; + if (mode == VID_WRITE_MPEG_AUD || + mode == VID_WRITE_MPEG_VID || + mode == VID_WRITE_CC || + mode == VID_WRITE_TTX || + mode == VID_WRITE_OSD) { + saa->writemode = mode; + return 0; + } + return -EINVAL; + } + case VIDIOCSMICROCODE: + { + struct video_code ucode; + __u8 *udata; + int i; + if (copy_from_user((void *) &ucode, arg, + sizeof(ucode))) + return -EFAULT; + if (ucode.datasize > 65536 || ucode.datasize < 1024 || + strncmp(ucode.loadwhat, "dec", 3)) + return -EINVAL; + if ((udata = vmalloc(ucode.datasize)) == NULL) + return -ENOMEM; + if (copy_from_user((void *) udata, ucode.data, + ucode.datasize)) { + vfree(udata); + return -EFAULT; + } + ucode.data = udata; + if (!strncmp(ucode.loadwhat, "decoder.aud", 11) + || !strncmp(ucode.loadwhat, "decoder.vid", 11)) + i = initialize_ibmmpeg2(&ucode); + else + i = initialize_fpga(&ucode); + vfree(udata); + if (i) + return -EINVAL; + return 0; + + } + case VIDIOCGCHAN: /* this makes xawtv happy */ + { + struct video_channel v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + v.flags = VIDEO_VC_AUDIO; + v.tuners = 0; + v.type = VID_TYPE_MPEG_DECODER; + v.norm = CurrentMode; + strcpy(v.name, "MPEG2"); + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: /* this makes xawtv happy */ + { + struct video_channel v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + /* do nothing */ + return 0; + } + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int saa_init_done(struct video_device *dev) +{ + return 0; +} + +static int saa_mmap(struct video_device *dev, const char *adr, + unsigned long size) +{ + struct saa7146 *saa = (struct saa7146 *) dev; + printk(KERN_DEBUG "stradis%d: saa_mmap called\n", saa->nr); + return -EINVAL; +} + +static long saa_read(struct video_device *dev, char *buf, + unsigned long count, int nonblock) +{ + return -EINVAL; +} + +static long saa_write(struct video_device *dev, const char *buf, + unsigned long count, int nonblock) +{ + struct saa7146 *saa = (struct saa7146 *) dev; + unsigned long todo = count; + int blocksize, split; + unsigned long flags; + + while (todo > 0) { + if (saa->writemode == VID_WRITE_MPEG_AUD) { + spin_lock_irqsave(&saa->lock, flags); + if (saa->audhead <= saa->audtail) + blocksize = 65536-(saa->audtail - saa->audhead); + else + blocksize = saa->audhead - saa->audtail; + spin_unlock_irqrestore(&saa->lock, flags); + if (blocksize < 16384) { + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + /* wait for buffer space to open */ + interruptible_sleep_on(&saa->audq); + } + spin_lock_irqsave(&saa->lock, flags); + if (saa->audhead <= saa->audtail) { + blocksize = 65536-(saa->audtail - saa->audhead); + split = 65536 - saa->audtail; + } else { + blocksize = saa->audhead - saa->audtail; + split = 65536; + } + spin_unlock_irqrestore(&saa->lock, flags); + blocksize--; + if (blocksize > todo) + blocksize = todo; + /* double check that we really have space */ + if (!blocksize) + return -ENOSPC; + if (split < blocksize) { + if (copy_from_user(saa->audbuf + + saa->audtail, buf, split)) + return -EFAULT; + buf += split; + todo -= split; + blocksize -= split; + saa->audtail = 0; + } + if (copy_from_user(saa->audbuf + saa->audtail, buf, + blocksize)) + return -EFAULT; + saa->audtail += blocksize; + todo -= blocksize; + buf += blocksize; + saa->audtail &= 0xffff; + } else if (saa->writemode == VID_WRITE_MPEG_VID) { + spin_lock_irqsave(&saa->lock, flags); + if (saa->vidhead <= saa->vidtail) + blocksize=524288-(saa->vidtail - saa->vidhead); + else + blocksize = saa->vidhead - saa->vidtail; + spin_unlock_irqrestore(&saa->lock, flags); + if (blocksize < 65536) { + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + /* wait for buffer space to open */ + interruptible_sleep_on(&saa->vidq); + } + spin_lock_irqsave(&saa->lock, flags); + if (saa->vidhead <= saa->vidtail) { + blocksize=524288-(saa->vidtail - saa->vidhead); + split = 524288 - saa->vidtail; + } else { + blocksize = saa->vidhead - saa->vidtail; + split = 524288; + } + spin_unlock_irqrestore(&saa->lock, flags); + blocksize--; + if (blocksize > todo) + blocksize = todo; + /* double check that we really have space */ + if (!blocksize) + return -ENOSPC; + if (split < blocksize) { + if (copy_from_user(saa->vidbuf + + saa->vidtail, buf, split)) + return -EFAULT; + buf += split; + todo -= split; + blocksize -= split; + saa->vidtail = 0; + } + if (copy_from_user(saa->vidbuf + saa->vidtail, buf, + blocksize)) + return -EFAULT; + saa->vidtail += blocksize; + todo -= blocksize; + buf += blocksize; + saa->vidtail &= 0x7ffff; + } else if (saa->writemode == VID_WRITE_OSD) { + if (count > 131072) + return -ENOSPC; + if (copy_from_user(saa->osdbuf, buf, count)) + return -EFAULT; + buf += count; + saa->osdhead = 0; + saa->osdtail = count; + debiwrite(saa, debNormal, IBM_MP2_OSD_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_OSD_LINK_ADDR, 0, 2); + debiwrite(saa, debNormal, IBM_MP2_MASK0, 0xc00d, 2); + debiwrite(saa, debNormal, IBM_MP2_DISP_MODE, + debiread(saa, debNormal, + IBM_MP2_DISP_MODE, 2) | 1, 2); + /* trigger osd data transfer */ + saawrite(SAA7146_PSR_DEBI_S | + SAA7146_PSR_PIN1, SAA7146_IER); + saawrite(SAA7146_PSR_PIN1, SAA7146_PSR); + } + } + return count; +} + +static int saa_open(struct video_device *dev, int flags) +{ + struct saa7146 *saa = (struct saa7146 *) dev; + + saa->video_dev.busy = 0; + saa->user++; + if (saa->user > 1) + return 0; /* device open already, don't reset */ + saa->writemode = VID_WRITE_MPEG_VID; /* default to video */ + return 0; +} + +static void saa_close(struct video_device *dev) +{ + struct saa7146 *saa = (struct saa7146 *) dev; + saa->user--; + saa->video_dev.busy = 0; + if (saa->user > 0) /* still someone using device */ + return; + saawrite(0x007f0000, SAA7146_MC1); /* stop all overlay dma */ +} + +/* template for video_device-structure */ +static struct video_device saa_template = +{ + "SAA7146A", + VID_TYPE_CAPTURE | VID_TYPE_OVERLAY, + VID_HARDWARE_SAA7146, + saa_open, + saa_close, + saa_read, + saa_write, + NULL, /* poll */ + saa_ioctl, + saa_mmap, + saa_init_done, + NULL, + 0, + 0 +}; + +static int configure_saa7146(struct pci_dev *dev, int num) +{ + int result; + struct saa7146 *saa; + + saa = &saa7146s[num]; + + saa->endmarkhead = saa->endmarktail = 0; + saa->win.x = saa->win.y = 0; + saa->win.width = saa->win.cropwidth = 720; + saa->win.height = saa->win.cropheight = 480; + saa->win.cropx = saa->win.cropy = 0; + saa->win.bpp = 2; + saa->win.depth = 16; + saa->win.color_fmt = palette2fmt[VIDEO_PALETTE_RGB565]; + saa->win.bpl = 1024 * saa->win.bpp; + saa->win.swidth = 1024; + saa->win.sheight = 768; + saa->picture.brightness = 32768; + saa->picture.contrast = 38768; + saa->picture.colour = 32768; + saa->cap = 0; + saa->dev = dev; + saa->nr = num; + saa->playmode = VID_PLAY_NORMAL; + memset(saa->boardcfg, 0, 64); /* clear board config area */ + saa->saa7146_mem = NULL; + saa->dmavid1 = saa->dmavid2 = saa->dmavid3 = saa->dmaa1in = + saa->dmaa1out = saa->dmaa2in = saa->dmaa2out = + saa->pagevid1 = saa->pagevid2 = saa->pagevid3 = saa->pagea1in = + saa->pagea1out = saa->pagea2in = saa->pagea2out = + saa->pagedebi = saa->dmaRPS1 = saa->dmaRPS2 = saa->pageRPS1 = + saa->pageRPS2 = NULL; + saa->audbuf = saa->vidbuf = saa->osdbuf = saa->dmadebi = NULL; + saa->audhead = saa->vidtail = 0; + + init_waitqueue_head(&saa->i2cq); + init_waitqueue_head(&saa->audq); + init_waitqueue_head(&saa->debiq); + init_waitqueue_head(&saa->vidq); + spin_lock_init(&saa->lock); + + if (pci_enable_device(dev)) + return -EIO; + + saa->id = dev->device; + saa->irq = dev->irq; + saa->video_dev.minor = -1; + saa->saa7146_adr = pci_resource_start(dev, 0); + pci_read_config_byte(dev, PCI_CLASS_REVISION, &saa->revision); + + saa->saa7146_mem = ioremap(saa->saa7146_adr, 0x200); + if (!saa->saa7146_mem) + return -EIO; + + memcpy(&(saa->i2c), &saa7146_i2c_bus_template, sizeof(struct i2c_bus)); + memcpy(&saa->video_dev, &saa_template, sizeof(saa_template)); + sprintf(saa->i2c.name, "stradis%d", num); + saa->i2c.data = saa; + saawrite(0, SAA7146_IER); /* turn off all interrupts */ + result = request_irq(saa->irq, saa7146_irq, + SA_SHIRQ | SA_INTERRUPT, "stradis", (void *) saa); + if (result == -EINVAL) + printk(KERN_ERR "stradis%d: Bad irq number or handler\n", + num); + if (result == -EBUSY) + printk(KERN_ERR "stradis%d: IRQ %ld busy, change your PnP" + " config in BIOS\n", num, saa->irq); + if (result < 0) + return result; + pci_set_master(dev); + if (video_register_device(&saa->video_dev, VFL_TYPE_GRABBER) < 0) + return -1; +#if 0 + /* i2c generic interface is currently BROKEN */ + i2c_register_bus(&saa->i2c); +#endif + return 0; +} + +static int init_saa7146(int i) +{ + struct saa7146 *saa = &saa7146s[i]; + + saa->user = 0; + /* reset the saa7146 */ + saawrite(0xffff0000, SAA7146_MC1); + mdelay(5); + /* enable debi and i2c transfers and pins */ + saawrite(((SAA7146_MC1_EDP | SAA7146_MC1_EI2C | + SAA7146_MC1_TR_E_DEBI) << 16) | 0xffff, SAA7146_MC1); + /* ensure proper state of chip */ + saawrite(0x00000000, SAA7146_PAGE1); + saawrite(0x00f302c0, SAA7146_NUM_LINE_BYTE1); + saawrite(0x00000000, SAA7146_PAGE2); + saawrite(0x01400080, SAA7146_NUM_LINE_BYTE2); + saawrite(0x00000000, SAA7146_DD1_INIT); + saawrite(0x00000000, SAA7146_DD1_STREAM_B); + saawrite(0x00000000, SAA7146_DD1_STREAM_A); + saawrite(0x00000000, SAA7146_BRS_CTRL); + saawrite(0x80400040, SAA7146_BCS_CTRL); + saawrite(0x0000e000 /*| (1<<29)*/, SAA7146_HPS_CTRL); + saawrite(0x00000060, SAA7146_CLIP_FORMAT_CTRL); + saawrite(0x00000000, SAA7146_ACON1); + saawrite(0x00000000, SAA7146_ACON2); + saawrite(0x00000600, SAA7146_I2C_STATUS); + saawrite(((SAA7146_MC2_UPLD_D1_B | SAA7146_MC2_UPLD_D1_A | + SAA7146_MC2_UPLD_BRS | SAA7146_MC2_UPLD_HPS_H | + SAA7146_MC2_UPLD_HPS_V | SAA7146_MC2_UPLD_DMA2 | + SAA7146_MC2_UPLD_DMA1 | SAA7146_MC2_UPLD_I2C) << 16) | 0xffff, + SAA7146_MC2); + /* setup arbitration control registers */ + saawrite(0x1412121a, SAA7146_PCI_BT_V1); + + /* allocate 32k dma buffer + 4k for page table */ + if ((saa->dmadebi = kmalloc(32768 + 4096, GFP_KERNEL)) == NULL) { + printk(KERN_ERR "stradis%d: debi kmalloc failed\n", i); + return -1; + } +#if 0 + saa->pagedebi = saa->dmadebi + 32768; /* top 4k is for mmu */ + saawrite(virt_to_bus(saa->pagedebi) /*|0x800 */ , SAA7146_DEBI_PAGE); + for (i = 0; i < 12; i++) /* setup mmu page table */ + saa->pagedebi[i] = virt_to_bus((saa->dmadebi + i * 4096)); +#endif + saa->audhead = saa->vidhead = saa->osdhead = 0; + saa->audtail = saa->vidtail = saa->osdtail = 0; + if (saa->vidbuf == NULL) + if ((saa->vidbuf = vmalloc(524288)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + return -ENOMEM; + } + if (saa->audbuf == NULL) + if ((saa->audbuf = vmalloc(65536)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + vfree(saa->vidbuf); + saa->vidbuf = NULL; + return -ENOMEM; + } + if (saa->osdbuf == NULL) + if ((saa->osdbuf = vmalloc(131072)) == NULL) { + printk(KERN_ERR "stradis%d: malloc failed\n", saa->nr); + vfree(saa->vidbuf); + vfree(saa->audbuf); + saa->vidbuf = saa->audbuf = NULL; + return -ENOMEM; + } + /* allocate 81920 byte buffer for clipping */ + if ((saa->dmavid2 = kmalloc(VIDEO_CLIPMAP_SIZE, GFP_KERNEL)) == NULL) { + printk(KERN_ERR "stradis%d: clip kmalloc failed\n", saa->nr); + vfree(saa->vidbuf); + vfree(saa->audbuf); + vfree(saa->osdbuf); + saa->vidbuf = saa->audbuf = saa->osdbuf = NULL; + saa->dmavid2 = NULL; + return -1; + } + memset(saa->dmavid2, 0x00, VIDEO_CLIPMAP_SIZE); /* clip everything */ + /* setup clipping registers */ + saawrite(virt_to_bus(saa->dmavid2), SAA7146_BASE_EVEN2); + saawrite(virt_to_bus(saa->dmavid2) + 128, SAA7146_BASE_ODD2); + saawrite(virt_to_bus(saa->dmavid2) + VIDEO_CLIPMAP_SIZE, + SAA7146_PROT_ADDR2); + saawrite(256, SAA7146_PITCH2); + saawrite(4, SAA7146_PAGE2); /* dma direction: read, no byteswap */ + saawrite(((SAA7146_MC2_UPLD_DMA2) << 16) | SAA7146_MC2_UPLD_DMA2, + SAA7146_MC2); + I2CBusScan(&(saa->i2c)); + return 0; +} + +static void release_saa(void) +{ + u8 command; + int i; + struct saa7146 *saa; + + for (i = 0; i < saa_num; i++) { + saa = &saa7146s[i]; + + /* turn off all capturing, DMA and IRQs */ + saawrite(0xffff0000, SAA7146_MC1); /* reset chip */ + saawrite(0, SAA7146_MC2); + saawrite(0, SAA7146_IER); + saawrite(0xffffffffUL, SAA7146_ISR); +#if 0 + /* unregister i2c_bus */ + i2c_unregister_bus((&saa->i2c)); +#endif + + /* disable PCI bus-mastering */ + pci_read_config_byte(saa->dev, PCI_COMMAND, &command); + /* Should this be &=~ ?? */ + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(saa->dev, PCI_COMMAND, command); + /* unmap and free memory */ + saa->audhead = saa->audtail = saa->osdhead = 0; + saa->vidhead = saa->vidtail = saa->osdtail = 0; + if (saa->vidbuf) + vfree(saa->vidbuf); + if (saa->audbuf) + vfree(saa->audbuf); + if (saa->osdbuf) + vfree(saa->osdbuf); + if (saa->dmavid2) + kfree((void *) saa->dmavid2); + saa->audbuf = saa->vidbuf = saa->osdbuf = NULL; + saa->dmavid2 = NULL; + if (saa->dmadebi) + kfree((void *) saa->dmadebi); + if (saa->dmavid1) + kfree((void *) saa->dmavid1); + if (saa->dmavid2) + kfree((void *) saa->dmavid2); + if (saa->dmavid3) + kfree((void *) saa->dmavid3); + if (saa->dmaa1in) + kfree((void *) saa->dmaa1in); + if (saa->dmaa1out) + kfree((void *) saa->dmaa1out); + if (saa->dmaa2in) + kfree((void *) saa->dmaa2in); + if (saa->dmaa2out) + kfree((void *) saa->dmaa2out); + if (saa->dmaRPS1) + kfree((void *) saa->dmaRPS1); + if (saa->dmaRPS2) + kfree((void *) saa->dmaRPS2); + free_irq(saa->irq, saa); + if (saa->saa7146_mem) + iounmap(saa->saa7146_mem); + if (saa->video_dev.minor != -1) + video_unregister_device(&saa->video_dev); + } +} + + +static int __init stradis_init (void) +{ + struct pci_dev *dev = NULL; + int result = 0, i; + + saa_num = 0; + + while ((dev = pci_find_device(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, dev))) { + if (!dev->subsystem_vendor) + printk(KERN_INFO "stradis%d: rev1 decoder\n", saa_num); + else + printk(KERN_INFO "stradis%d: SDM2xx found\n", saa_num); + result = configure_saa7146(dev, saa_num++); + if (result) + return result; + } + if (saa_num) + printk(KERN_INFO "stradis: %d card(s) found.\n", saa_num); + else + return -EINVAL; + for (i = 0; i < saa_num; i++) + if (init_saa7146(i) < 0) { + release_saa(); + return -EIO; + } + return 0; +} + + +static void __exit stradis_exit (void) +{ + release_saa(); + printk(KERN_INFO "stradis: module cleanup complete\n"); +} + + +module_init(stradis_init); +module_exit(stradis_exit); + diff --git a/drivers/media/video/tda7432.c b/drivers/media/video/tda7432.c new file mode 100644 index 000000000..488091310 --- /dev/null +++ b/drivers/media/video/tda7432.c @@ -0,0 +1,505 @@ +/* + * For the STS-Thompson TDA7432 audio processor chip + * + * Handles audio functions: volume, balance, tone, loudness + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Copyright (c) 2000 Eric Sandeen <eric_sandeen@bigfoot.com> + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * set to 2 if you'd like to be inundated with debug messages + * + * loudness - set between 0 and 15 for varying degrees of loudness effect + * + * TODO: + * Implement tone controls + * + * Revision: 0.3 - Fixed silly reversed volume controls. :) + * Revision: 0.2 - Cleaned up #defines + * fixed volume control + * Added I2C_DRIVERID_TDA7432 + * added loudness insmod control + * Revision: 0.1 - initial version + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + +/* This driver ID is brand new, so define it if it's not in i2c-id.h yet */ +#ifndef I2C_DRIVERID_TDA7432 + #define I2C_DRIVERID_TDA7432 27 +#endif + + +MODULE_AUTHOR("Eric Sandeen <eric_sandeen@bigfoot.com>"); +MODULE_DESCRIPTION("bttv driver for the tda7432 audio processor chip"); + +MODULE_PARM(debug,"i"); +MODULE_PARM(loudness,"i"); +static int loudness = 0; /* disable loudness by default */ +static int debug = 0; /* insmod parameter */ + + +/* Address to scan (I2C address of this chip) */ +static unsigned short normal_i2c[] = { + I2C_TDA7432 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* Structure of address and subaddresses for the tda7432 */ + +struct tda7432 { + int addr; + int input; + int volume; + int tone; + int lf, lr, rf, rr; + int loud; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk +#define d2printk if (debug > 1) printk + +/* The TDA7432 is made by STS-Thompson + * http://www.st.com + * http://us.st.com/stonline/books/pdf/docs/4056.pdf + * + * TDA7432: I2C-bus controlled basic audio processor + * + * The TDA7432 controls basic audio functions like volume, balance, + * and tone control (including loudness). It also has four channel + * output (for front and rear). Since most vidcap cards probably + * don't have 4 channel output, this driver will set front & rear + * together (no independent control). + */ + + /* Subaddresses for TDA7432 */ + +#define TDA7432_IN 0x00 /* Input select */ +#define TDA7432_VL 0x01 /* Volume */ +#define TDA7432_TN 0x02 /* Bass, Treble (Tone) */ +#define TDA7432_LF 0x03 /* Attenuation LF (Left Front) */ +#define TDA7432_LR 0x04 /* Attenuation LR (Left Rear) */ +#define TDA7432_RF 0x05 /* Attenuation RF (Right Front) */ +#define TDA7432_RR 0x06 /* Attenuation RR (Right Rear) */ +#define TDA7432_LD 0x07 /* Loudness */ + + + /* Masks for bits in TDA7432 subaddresses */ + +/* Many of these not used - just for documentation */ + +/* Subaddress 0x00 - Input selection and bass control */ + +/* Bits 0,1,2 control input: + * 0x00 - Stereo input + * 0x02 - Mono input + * 0x03 - Mute + * Mono probably isn't used - I'm guessing only the stereo + * input is connected on most cards, so we'll set it to stereo. + * + * Bit 3 controls bass cut: 0/1 is non-symmetric/symmetric bass cut + * Bit 4 controls bass range: 0/1 is extended/standard bass range + * + * Highest 3 bits not used + */ + +#define TDA7432_STEREO_IN 0 +#define TDA7432_MONO_IN 2 /* Probably won't be used */ +#define TDA7432_MUTE 3 /* Probably won't be used */ +#define TDA7432_BASS_SYM 1 << 3 +#define TDA7432_BASS_NORM 1 << 4 + +/* Subaddress 0x01 - Volume */ + +/* Lower 7 bits control volume from -79dB to +32dB in 1dB steps + * Recommended maximum is +20 dB + * + * +32dB: 0x00 + * +20dB: 0x0c + * 0dB: 0x20 + * -79dB: 0x6f + * + * MSB (bit 7) controls loudness: 1/0 is loudness on/off + */ + +#define TDA7432_VOL_0DB 0x20 +#define TDA7432_LD_ON 1 << 7 + + +/* Subaddress 0x02 - Tone control */ + +/* Bits 0,1,2 control absolute treble gain from 0dB to 14dB + * 0x0 is 14dB, 0x7 is 0dB + * + * Bit 3 controls treble attenuation/gain (sign) + * 1 = gain (+) + * 0 = attenuation (-) + * + * Bits 4,5,6 control absolute bass gain from 0dB to 14dB + * (This is only true for normal base range, set in 0x00) + * 0x0 << 4 is 14dB, 0x7 is 0dB + * + * Bit 7 controls bass attenuation/gain (sign) + * 1 << 7 = gain (+) + * 0 << 7 = attenuation (-) + * + * Example: + * 1 1 0 1 0 1 0 1 is +4dB bass, -4dB treble + */ + +#define TDA7432_TREBLE_0DB 0xf +#define TDA7432_TREBLE 7 +#define TDA7432_TREBLE_GAIN 1 << 3 +#define TDA7432_BASS_0DB 0xf << 4 +#define TDA7432_BASS 7 << 4 +#define TDA7432_BASS_GAIN 1 << 7 + + +/* Subaddress 0x03 - Left Front attenuation */ +/* Subaddress 0x04 - Left Rear attenuation */ +/* Subaddress 0x05 - Right Front attenuation */ +/* Subaddress 0x06 - Right Rear attenuation */ + +/* Bits 0,1,2,3,4 control attenuation from 0dB to -37.5dB + * in 1.5dB steps. + * + * 0x00 is 0dB + * 0x1f is -37.5dB + * + * Bit 5 mutes that channel when set (1 = mute, 0 = unmute) + * We'll use the mute on the input, though (above) + * Bits 6,7 unused + */ + +#define TDA7432_ATTEN_0DB 0x00 + + +/* Subaddress 0x07 - Loudness Control */ + +/* Bits 0,1,2,3 control loudness from 0dB to -15dB in 1dB steps + * when bit 4 is NOT set + * + * 0x0 is 0dB + * 0xf is -15dB + * + * If bit 4 is set, then there is a flat attenuation according to + * the lower 4 bits, as above. + * + * Bits 5,6,7 unused + */ + + + +/* Begin code */ + +static int tda7432_write(struct i2c_client *client, int subaddr, int val) +{ + unsigned char buffer[2]; + d2printk("tda7432: In tda7432_write\n"); + dprintk("tda7432: Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda7432: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +/* I don't think we ever actually _read_ the chip... */ +#if 0 +static int tda7432_read(struct i2c_client *client) +{ + unsigned char buffer; + d2printk("tda7432: In tda7432_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda7432: I/O error, trying (read)\n"); + return -1; + } + dprintk("tda7432: Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static int tda7432_set(struct i2c_client *client) +{ + struct tda7432 *t = client->data; + unsigned char buf[16]; + d2printk("tda7432: In tda7432_set\n"); + + dprintk(KERN_INFO + "tda7432: 7432_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n", + t->input,t->volume,t->tone,t->lf,t->lr,t->rf,t->rr,t->loud); + buf[0] = TDA7432_IN; + buf[1] = t->input; + buf[2] = t->volume; + buf[3] = t->tone; + buf[4] = t->lf; + buf[5] = t->lr; + buf[6] = t->rf; + buf[7] = t->rr; + buf[8] = t->loud; + if (9 != i2c_master_send(client,buf,9)) { + printk(KERN_WARNING "tda7432: I/O error, trying tda7432_set\n"); + return -1; + } + + return 0; +} + +static void do_tda7432_init(struct i2c_client *client) +{ + struct tda7432 *t = client->data; + d2printk("tda7432: In tda7432_init\n"); + + t->input = TDA7432_STEREO_IN | /* Main (stereo) input */ + TDA7432_BASS_SYM | /* Symmetric bass cut */ + TDA7432_BASS_NORM; /* Normal bass range */ + t->volume = TDA7432_VOL_0DB; /* 0dB Volume */ + if (loudness) /* Turn loudness on? */ + t->volume |= TDA7432_LD_ON; + t->tone = TDA7432_TREBLE_0DB | /* 0dB Treble */ + TDA7432_BASS_0DB; /* 0dB Bass */ + t->lf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->lr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rf = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->rr = TDA7432_ATTEN_0DB; /* 0dB attenuation */ + t->loud = loudness; /* insmod parameter */ + + tda7432_set(client); +} + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda7432_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tda7432 *t; + struct i2c_client *client; + d2printk("tda7432: In tda7432_attach\n"); + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + do_tda7432_init(client); + MOD_INC_USE_COUNT; + strcpy(client->name,"TDA7432"); + printk(KERN_INFO "tda7432: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda7432_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda7432_attach); + return 0; +} + +static int tda7432_detach(struct i2c_client *client) +{ + struct tda7432 *t = client->data; + + do_tda7432_init(client); + i2c_detach_client(client); + + kfree(t); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int tda7432_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda7432 *t = client->data; + d2printk("tda7432: In tda7432_command\n"); +#if 0 + __u16 *sarg = arg; +#endif + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + + /* Query card - scale from TDA7432 settings to V4L settings */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + + /* Master volume control + * V4L volume is min 0, max 65535 + * TDA7432 Volume: + * Min (-79dB) is 0x6f + * Max (+20dB) is 0x07 + * (Mask out bit 7 of vol - it's for the loudness setting) + */ + + va->volume = ( 0x6f - (t->volume & 0x7F) ) * 630; + + /* Balance depends on L,R attenuation + * V4L balance is 0 to 65535, middle is 32768 + * TDA7432 attenuation: min (0dB) is 0, max (-37.5dB) is 0x1f + * to scale up to V4L numbers, mult by 1057 + * attenuation exists for lf, lr, rf, rr + * we use only lf and rf (front channels) + */ + + if ( (t->lf) < (t->rf) ) + /* right is attenuated, balance shifted left */ + va->balance = (32768 - 1057*(t->rf)); + else + /* left is attenuated, balance shifted right */ + va->balance = (32768 + 1057*(t->lf)); + + /* Bass/treble */ + va->bass = 32768; /* brain hurts... set to middle for now */ + va->treble = 32768; /* brain hurts... set to middle for now */ + + break; /* VIDIOCGAUDIO case */ + } + + /* Set card - scale from V4L settings to TDA7432 settings */ + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + dprintk("tda7432: VIDEOCSAUDIO\n"); + + t->volume = 0x6f - ( (va->volume)/630 ); + + if (loudness) /* Turn on the loudness bit */ + t->volume |= TDA7432_LD_ON; + + if (va->balance < 32768) { + /* shifted to left, attenuate right */ + t->rr = (32768 - va->balance)/1057; + t->rf = t->rr; + } + else { + /* shifted to right, attenuate left */ + t->lf = (va->balance - 32768)/1057; + t->lr = t->lf; + } + + /* t->tone = 0xff; */ /* Brain hurts - no tone control for now... */ + + tda7432_write(client,TDA7432_VL, t->volume); + /* tda7432_write(client,TDA7432_TN, t->tone); */ + tda7432_write(client,TDA7432_LF, t->lf); + tda7432_write(client,TDA7432_LR, t->lr); + tda7432_write(client,TDA7432_RF, t->rf); + tda7432_write(client,TDA7432_RR, t->rr); + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + d2printk("tda7432: Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + + +static struct i2c_driver driver = { + "i2c tda7432 driver", + I2C_DRIVERID_TDA7432, + I2C_DF_NOTIFY, + tda7432_probe, + tda7432_detach, + tda7432_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tda7432_init(void) +#endif +{ + + if ( (loudness < 0) || (loudness > 15) ) + { + printk(KERN_ERR "tda7432: loudness parameter must be between 0 and 15\n"); + return -EINVAL; + } + + + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tda8425.c b/drivers/media/video/tda8425.c new file mode 100644 index 000000000..a1dec22eb --- /dev/null +++ b/drivers/media/video/tda8425.c @@ -0,0 +1,324 @@ +/* + * for the TDA8425 chip (I don't know which cards have this) + * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF A DIFFERENT + * CHIP IS AT ADDRESS 0x82 (it relies on i2c to make sure that there is a + * device acknowledging that address) + * + * Copyright (c) 1998 Greg Alexander <galexand@acm.org> + * This code is placed under the terms of the GNU General Public License + * Code liberally copied from msp3400.c, which is by Gerd Knorr + * + * All of this should work, though it would be nice to eventually support + * balance (different left,right values). Also, the chip seems (?) to have + * two stereo inputs, so if someone has this card, could they tell me if the + * second one can be used for anything (i.e., does it have an external input + * that you can't hear even if you set input to composite?) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_TDA8425 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +MODULE_PARM(debug,"i"); +static int debug = 0; /* insmod parameter */ +#define dprintk if (debug) printk + + +struct tda8425 { + int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */ + int stereo; + __u16 left,right; + __u16 bass,treble; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + + +#define TDA8425_VL 0x00 /* volume left */ +#define TDA8425_VR 0x01 /* volume right */ +#define TDA8425_BA 0x02 /* bass */ +#define TDA8425_TR 0x03 /* treble */ +#define TDA8425_S1 0x08 /* switch functions */ + /* values for those registers: */ +#define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ +#define TDA8425_S1_ON 0xCE /* audio on (mute off) - "linear stereo" mode */ + + +/* ******************************** * + * functions for talking to TDA8425 * + * ******************************** */ + +static int tda8425_write(struct i2c_client *client, int addr, int val) +{ + unsigned char buffer[2]; + + buffer[0] = addr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda8425: I/O error, trying (write %d 0x%x)\n", + addr, val); + return -1; + } + return 0; +} + +static void tda8425_set(struct i2c_client *client) +{ + struct tda8425 *tda = client->data; + + /* mode is ignored today */ + dprintk(KERN_DEBUG "tda8425_set(%04x,%04x,%04x,%04x)\n",tda->left>>10,tda->right>>10,tda->bass>>12,tda->treble>>12); + tda8425_write(client, TDA8425_VL, tda->left>>10 |0xC0); + tda8425_write(client, TDA8425_VR, tda->right>>10 |0xC0); + tda8425_write(client, TDA8425_BA, tda->bass>>12 |0xF0); + tda8425_write(client, TDA8425_TR, tda->treble>>12|0xF0); +} + +static void do_tda8425_init(struct i2c_client *client) +{ + struct tda8425 *tda = client->data; + + tda->left=tda->right =61440; /* 0dB */ + tda->bass=tda->treble=24576; /* 0dB */ + tda->mode=AUDIO_OFF; + tda->stereo=1; + /* left=right=0x27<<10, bass=treble=0x07<<12 */ + tda8425_write(client, TDA8425_S1, TDA8425_S1_OFF); /* mute */ + tda8425_set(client); +} + +static void tda8425_audio(struct i2c_client *client, int mode) +{ + struct tda8425 *tda = client->data; + + /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */ + dprintk(KERN_DEBUG "tda8425_audio:%d (T,R,E,I,O)\n",mode); + tda->mode=mode; + tda8425_write(client, TDA8425_S1, + (mode==AUDIO_OFF)?TDA8425_S1_OFF:TDA8425_S1_ON); + /* this is the function we'll need to change if it turns out the + * input-selecting capabilities should be used. */ +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda8425_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tda8425 *tda; + struct i2c_client *client; + + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = tda = kmalloc(sizeof *tda,GFP_KERNEL); + if (!tda) + return -ENOMEM; + memset(tda,0,sizeof *tda); + do_tda8425_init(client); + MOD_INC_USE_COUNT; + strcpy(client->name,"TDA8425"); + printk(KERN_INFO "tda8425: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda8425_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda8425_attach); + return 0; +} + + +static int tda8425_detach(struct i2c_client *client) +{ + struct tda8425 *tda = client->data; + + do_tda8425_init(client); + i2c_detach_client(client); + + kfree(tda); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int tda8425_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda8425 *tda = client->data; + __u16 *sarg = arg; + + switch (cmd) { + case AUDC_SET_RADIO: + tda8425_audio(client,AUDIO_RADIO); + break; + case AUDC_SET_INPUT: + tda8425_audio(client,*sarg); + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + va->volume=MAX(tda->left,tda->right); + va->balance=(32768*MIN(tda->left,tda->right))/ + (va->volume ? va->volume : 1); + va->balance=(tda->left<tda->right)? + (65535-va->balance) : va->balance; + va->bass = tda->bass; + va->treble = tda->treble; + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + tda->left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + tda->right = (MIN(va->balance,32768) * + va->volume) / 32768; + tda->bass = va->bass; + tda->treble = va->treble; + tda8425_set(client); + break; + } + +#if 0 + /* --- old, obsolete interface --- */ + case AUDC_GET_VOLUME_LEFT: + *sarg = tda->left; + break; + case AUDC_GET_VOLUME_RIGHT: + *sarg = tda->right; + break; + case AUDC_SET_VOLUME_LEFT: + tda->left = *sarg; + tda8425_set(client); + break; + case AUDC_SET_VOLUME_RIGHT: + tda->right = *sarg; + tda8425_set(client); + break; + + case AUDC_GET_BASS: + *sarg = tda->bass; + break; + case AUDC_SET_BASS: + tda->bass = *sarg; + tda8425_set(client); + break; + + case AUDC_GET_TREBLE: + *sarg = tda->treble; + break; + case AUDC_SET_TREBLE: + tda->treble = *sarg; + tda8425_set(client); + break; + + case AUDC_GET_STEREO: + *sarg = tda->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO; + break; + case AUDC_SET_STEREO: + tda->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1; + /* TODO: make this write to the TDA9850? */ + break; + +/* case AUDC_SWITCH_MUTE: someday, maybe -- not a lot of point to + case AUDC_NEWCHANNEL: it and it would require preserving state + case AUDC_GET_DC: huh?? (not used by bttv.c) +*/ +#endif + default: + /* nothing */ + } + return 0; +} + + +static struct i2c_driver driver = { + "i2c tda8424 driver", + I2C_DRIVERID_TDA8425, + I2C_DF_NOTIFY, + tda8425_probe, + tda8425_detach, + tda8425_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tda8425_init(void) +#endif +{ + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tda985x.c b/drivers/media/video/tda985x.c new file mode 100644 index 000000000..73fb9bd52 --- /dev/null +++ b/drivers/media/video/tda985x.c @@ -0,0 +1,536 @@ +/* + * For the TDA9850 and TDA9855 chips + * (The TDA9855 is used on the Diamond DTV2000 and the TDA9850 is used + * on STB cards. Other cards probably use these chips as well.) + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Copyright (c) 1999 Gerd Knorr + * TDA9850 code and TDA9855.c merger by Eric Sandeen (eric_sandeen@bigfoot.com) + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * - set to 2 if you'd like to be flooded with debug messages + * chip - set to 9850 or 9855 to select your chip (default 9855) + * + * TODO: + * Fix channel change bug - sound goes out when changeing channels, mute + * and unmote to fix. - Is this still here? + * Fine tune sound + * Get rest of capabilities into video_audio struct... + * + * Revision 0.5 - cleaned up debugging messages, added debug level=2 + * Revision: 0.4 - check for correct chip= insmod value + * also cleaned up comments a bit + * Revision: 0.3 - took out extraneous tda985x_write in tda985x_command + * Revision: 0.2 - added insmod option chip= + * Revision: 0.1 - original version + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + +MODULE_PARM(debug,"i"); +MODULE_PARM(chip,"i"); +MODULE_PARM_DESC(chip, "Type of chip to handle: 9850 or 9855"); + +static int debug = 0; /* insmod parameter */ +static int chip = 9855; /* insmod parameter */ + +/* Addresses to scan */ +#define I2C_TDA985x_L 0xb4 +#define I2C_TDA985x_H 0xb6 +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = { + I2C_TDA985x_L >> 1, + I2C_TDA985x_H >> 1, + I2C_CLIENT_END +}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* This is a superset of the TDA9850 and TDA9855 members */ + +struct tda985x { + int addr; + int rvol, lvol; + int bass, treble, sub; + int c4, c5, c6, c7; + int a1, a2, a3; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk +#define d2printk if (debug == 2) printk + +/* The TDA9850 and TDA9855 are both made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9850: I2C-bus controlled BTSC stereo/SAP decoder + * TDA9855: I2C-bus controlled BTSC stereo/SAP decoder and audio processor + * + * The TDA9850 has more or less a subset of the functions that the TDA9855 + * has. As a result, we can re-use many of these defines. Anything with + * TDA9855 is specific to that chip, anything with TDA9850 is specific + * to that chip, and anything with TDA985x is valid for either. + * + * To complicate things further, the TDA9850 uses labels C1 through C4 + * for subaddresses 0x04 through 0x07, while the TDA9855 uses + * C1 through C3 for subadresses 0x05 through 0x07 - quite confusing. + * To help keep things straight, I have renamed the various C[1,4] labels + * to C[4,7] so that the numerical label matches the hex value of the + * subaddress for both chips. At least the A[1,3] labels line up. :) + */ + + /* subaddresses for TDA9855 */ +#define TDA9855_VR 0x00 /* Volume, right */ +#define TDA9855_VL 0x01 /* Volume, left */ +#define TDA9855_BA 0x02 /* Bass */ +#define TDA9855_TR 0x03 /* Treble */ +#define TDA9855_SW 0x04 /* Subwoofer - not connected on DTV2000 */ + + /* subaddresses for TDA9850 */ +#define TDA9850_C4 0x04 /* Control 1 for TDA9850 */ + + /* subaddesses for both chips */ +#define TDA985x_C5 0x05 /* Control 2 for TDA9850, Control 1 for TDA9855 */ +#define TDA985x_C6 0x06 /* Control 3 for TDA9850, Control 2 for TDA9855 */ +#define TDA985x_C7 0x07 /* Control 4 for TDA9850, Control 3 for TDA9855 */ +#define TDA985x_A1 0x08 /* Alignment 1 for both chips */ +#define TDA985x_A2 0x09 /* Alignment 2 for both chips */ +#define TDA985x_A3 0x0a /* Alignment 3 for both chips */ + + /* Masks for bits in TDA9855 subaddresses */ +/* 0x00 - VR in TDA9855 */ +/* 0x01 - VL in TDA9855 */ +/* lower 7 bits control gain from -71dB (0x28) to 16dB (0x7f) + * in 1dB steps - mute is 0x27 */ + + +/* 0x02 - BA in TDA9855 */ +/* lower 5 bits control bass gain from -12dB (0x06) to 16.5dB (0x19) + * in .5dB steps - 0 is 0x0E */ + + +/* 0x03 - TR in TDA9855 */ +/* 4 bits << 1 control treble gain from -12dB (0x3) to 12dB (0xb) + * in 3dB steps - 0 is 0x7 */ + + /* Masks for bits in both chips' subaddresses */ +/* 0x04 - SW in TDA9855, C4/Control 1 in TDA9850 */ +/* Unique to TDA9855: */ +/* 4 bits << 2 control subwoofer/surround gain from -14db (0x1) to 14db (0xf) + * in 3dB steps - mute is 0x0 */ + +/* Unique to TDA9850: */ +/* lower 4 bits control stereo noise threshold, over which stereo turns off + * set to values of 0x00 through 0x0f for Ster1 through Ster16 */ + + +/* 0x05 - C5 - Control 1 in TDA9855 , Control 2 in TDA9850*/ +/* Unique to TDA9855: */ +#define TDA9855_MUTE 1<<7 /* GMU, Mute at outputs */ +#define TDA9855_AVL 1<<6 /* AVL, Automatic Volume Level */ +#define TDA9855_LOUD 1<<5 /* Loudness, 1==off */ +#define TDA9855_SUR 1<<3 /* Surround / Subwoofer 1==.5(L-R) 0==.5(L+R) */ + /* Bits 0 to 3 select various combinations + * of line in and line out, only the + * interesting ones are defined */ +#define TDA9855_EXT 1<<2 /* Selects inputs LIR and LIL. Pins 41 & 12 */ +#define TDA9855_INT 0 /* Selects inputs LOR and LOL. (internal) */ + +/* Unique to TDA9850: */ +/* lower 4 bits contol SAP noise threshold, over which SAP turns off + * set to values of 0x00 through 0x0f for SAP1 through SAP16 */ + + +/* 0x06 - C6 - Control 2 in TDA9855, Control 3 in TDA9850 */ +/* Common to TDA9855 and TDA9850: */ +#define TDA985x_SAP 3<<6 /* Selects SAP output, mute if not received */ +#define TDA985x_STEREO 1<<6 /* Selects Stereo ouput, mono if not received */ +#define TDA985x_MONO 0 /* Forces Mono output */ +#define TDA985x_LMU 1<<3 /* Mute (LOR/LOL for 9855, OUTL/OUTR for 9850) */ + +/* Unique to TDA9855: */ +#define TDA9855_TZCM 1<<5 /* If set, don't mute till zero crossing */ +#define TDA9855_VZCM 1<<4 /* If set, don't change volume till zero crossing*/ +#define TDA9855_LINEAR 0 /* Linear Stereo */ +#define TDA9855_PSEUDO 1 /* Pseudo Stereo */ +#define TDA9855_SPAT_30 2 /* Spatial Stereo, 30% anti-phase crosstalk */ +#define TDA9855_SPAT_50 3 /* Spatial Stereo, 52% anti-phase crosstalk */ +#define TDA9855_E_MONO 7 /* Forced mono - mono select elseware, so useless*/ + + +/* 0x07 - C7 - Control 3 in TDA9855, Control 4 in TDA9850 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 4 bits control input gain from -3.5dB (0x0) to 4dB (0xF) + * in .5dB steps - 0dB is 0x7 */ + + +/* 0x08, 0x09 - A1 and A2 (read/write) */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 5 bites are wideband and spectral expander alignment + * from 0x00 to 0x1f - nominal at 0x0f and 0x10 (read/write) */ +#define TDA985x_STP 1<<5 /* Stereo Pilot/detect (read-only) */ +#define TDA985x_SAPP 1<<6 /* SAP Pilot/detect (read-only) */ +#define TDA985x_STS 1<<7 /* Stereo trigger 1= <35mV 0= <30mV (write-only)*/ + + +/* 0x0a - A3 */ +/* Common to both TDA9855 and TDA9850: */ +/* lower 3 bits control timing current for alignment: -30% (0x0), -20% (0x1), + * -10% (0x2), nominal (0x3), +10% (0x6), +20% (0x5), +30% (0x4) */ +#define TDA985x_ADJ 1<<7 /* Stereo adjust on/off (wideband and spectral */ + +/* Unique to TDA9855: */ +/* 2 bits << 5 control AVL attack time: 420ohm (0x0), 730ohm (0x2), + * 1200ohm (0x1), 2100ohm (0x3) */ + + +/* Begin code */ + +static int tda985x_write(struct i2c_client *client, int subaddr, int val) +{ + unsigned char buffer[2]; + d2printk("tda985x: In tda985x_write\n"); + dprintk("tda985x: Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda985x: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +static int tda985x_read(struct i2c_client *client) +{ + unsigned char buffer; + d2printk("tda985x: In tda985x_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda985x: I/O error, trying (read)\n"); + return -1; + } + dprintk("tda985x: Read 0x%02x\n", buffer); + return buffer; +} + +static int tda985x_set(struct i2c_client *client) +{ + struct tda985x *t = client->data; + unsigned char buf[16]; + d2printk("tda985x: In tda985x_set\n"); + + if (chip == 9855) + { + dprintk(KERN_INFO + "tda985x: tda985x_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n", + t->rvol,t->lvol,t->bass,t->treble,t->sub, + t->c5,t->c6,t->c7,t->a1,t->a2,t->a3); + buf[0] = TDA9855_VR; + buf[1] = t->rvol; + buf[2] = t->lvol; + buf[3] = t->bass; + buf[4] = t->treble; + buf[5] = t->sub; + buf[6] = t->c5; + buf[7] = t->c6; + buf[8] = t->c7; + buf[9] = t->a1; + buf[10] = t->a2; + buf[11] = t->a3; + if (12 != i2c_master_send(client,buf,12)) { + printk(KERN_WARNING "tda985x: I/O error, trying tda985x_set\n"); + return -1; + } + } + + else if (chip == 9850) + { + dprintk(KERN_INFO + "tda986x: tda985x_set(0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)\n", + t->c4,t->c5,t->c6,t->c7,t->a1,t->a2,t->a3); + buf[0] = TDA9850_C4; + buf[1] = t->c4; + buf[2] = t->c5; + buf[3] = t->c6; + buf[4] = t->c7; + buf[5] = t->a1; + buf[6] = t->a2; + buf[7] = t->a3; + if (8 != i2c_master_send(client,buf,8)) { + printk(KERN_WARNING "tda985x: I/O error, trying tda985x_set\n"); + return -1; + } + } + + return 0; +} + +static void do_tda985x_init(struct i2c_client *client) +{ + struct tda985x *t = client->data; + d2printk("tda985x: In tda985x_init\n"); + + if (chip == 9855) + { + printk("tda985x: Using tda9855 options\n"); + t->rvol = 0x6f; /* 0dB */ + t->lvol = 0x6f; /* 0dB */ + t->bass = 0x0e; /* 0dB */ + t->treble = (0x07 << 1); /* 0dB */ + t->sub = 0x8 << 2; /* 0dB */ + t->c5 = TDA9855_MUTE | TDA9855_AVL | + TDA9855_LOUD | TDA9855_INT; + /* Set Mute, AVL, Loudness off, Internal sound */ + t->c6 = TDA985x_STEREO | TDA9855_LINEAR | + TDA9855_TZCM | TDA9855_VZCM; + /* Stereo linear mode, also wait til zero crossings */ + t->c7 = 0x07; /* 0dB input gain */ + } + + else if (chip == 9850) + { + printk("tda985x: Using tda9850 options\n"); + t->c4 = 0x08; /* Set stereo noise thresh to nominal */ + t->c5 = 0x08; /* Set SAP noise threshold to nominal */ + t->c6 = TDA985x_STEREO; /* Select Stereo mode for decoder */ + t->c7 = 0x07; /* 0dB input gain */ + } + + /* The following is valid for both chip types */ + t->a1 = 0x10; /* Select nominal wideband expander */ + t->a2 = 0x10; /* Select nominal spectral expander and 30mV trigger */ + t->a3 = 0x3; /* Set: nominal timing current, 420ohm AVL attack */ + + tda985x_set(client); +} + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda985x_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tda985x *t; + struct i2c_client *client; + d2printk("tda985x: In tda985x_attach\n"); + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + do_tda985x_init(client); + MOD_INC_USE_COUNT; + strcpy(client->name,"TDA985x"); + printk(KERN_INFO "tda985x: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda985x_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda985x_attach); + return 0; +} + +static int tda985x_detach(struct i2c_client *client) +{ + struct tda985x *t = client->data; + + do_tda985x_init(client); + i2c_detach_client(client); + + kfree(t); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int tda985x_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda985x *t = client->data; + d2printk("tda985x: In tda985x_command\n"); +#if 0 + __u16 *sarg = arg; +#endif + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + dprintk("tda985x: VIDIOCGAUDIO\n"); + if (chip == 9855) + { + int left,right; + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + + /* min is 0x27 max is 0x7f, vstep is 2e8 */ + left = (t->lvol-0x27)*0x2e8; + right = (t->rvol-0x27)*0x2e8; + va->volume=MAX(left,right); + va->balance=(32768*MIN(left,right))/ + (va->volume ? va->volume : 1); + va->balance=(left<right)? + (65535-va->balance) : va->balance; + va->bass = (t->bass-0x6)*0xccc; /* min 0x6 max 0x19 */ + va->treble = ((t->treble>>1)-0x3)*0x1c71; + } + + /* Valid for both chips: */ + { + va->mode = ((TDA985x_STP | TDA985x_SAPP) & + tda985x_read(client)) >> 4; + /* Add mono mode regardless of SAP and stereo */ + /* Allows forced mono */ + va->mode |= VIDEO_SOUND_MONO; + } + + break; /* VIDIOCGAUDIO case */ + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + dprintk("tda985x: VIDEOCSAUDIO\n"); + if (chip == 9855) + { + int left,right; + + left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + right = (MIN(va->balance,32768) * + va->volume) / 32768; + t->lvol = left/0x2e8+0x27; + t->rvol = right/0x2e8+0x27; + t->bass = va->bass/0xccc+0x6; + t->treble = (va->treble/0x1c71+0x3)<<1; + tda985x_write(client,TDA9855_VL,t->lvol); + tda985x_write(client,TDA9855_VR,t->rvol); + tda985x_write(client,TDA9855_BA, t->bass); + tda985x_write(client,TDA9855_TR,t->treble); + } + + /* The following is valid for both chips */ + + switch (va->mode) { + case VIDEO_SOUND_MONO: + dprintk("tda985x: VIDEO_SOUND_MONO\n"); + t->c6= TDA985x_MONO | (t->c6 & 0x3f); + tda985x_write(client,TDA985x_C6,t->c6); + break; + case VIDEO_SOUND_STEREO: + dprintk("tda985x: VIDEO_SOUND_STEREO\n"); + t->c6= TDA985x_STEREO | (t->c6 & 0x3f); + tda985x_write(client,TDA985x_C6,t->c6); + break; + case VIDEO_SOUND_LANG1: + dprintk("tda985x: VIDEO_SOUND_LANG1\n"); + t->c6= TDA985x_SAP | (t->c6 & 0x3f); + tda985x_write(client,TDA985x_C6,t->c6); + break; + } /* End of (va->mode) switch */ + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + d2printk("tda985x: Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + + +static struct i2c_driver driver = { + "i2c tda985x driver", + I2C_DRIVERID_TDA9855, /* Get new one for TDA985x? */ + I2C_DF_NOTIFY, + tda985x_probe, + tda985x_detach, + tda985x_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tda985x_init(void) +#endif +{ + if ( (chip != 9850) && (chip != 9855) ) + { + printk(KERN_ERR "tda985x: chip parameter must be 9850 or 9855\n"); + return -EINVAL; + } + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tda9875.c b/drivers/media/video/tda9875.c new file mode 100644 index 000000000..9f6ecd332 --- /dev/null +++ b/drivers/media/video/tda9875.c @@ -0,0 +1,403 @@ +/* + * For the TDA9875 chip + * (The TDA9875 is used on the Diamond DTV2000 french version + * Other cards probably use these chips as well.) + * This driver will not complain if used with any + * other i2c device with the same address. + * + * Copyright (c) 2000 Guillaume Delvit based on Gerd Knorr source and + * Eric Sandeen + * This code is placed under the terms of the GNU General Public License + * Based on tda9855.c by Steve VanDeBogart (vandebo@uclink.berkeley.edu) + * Which was based on tda8425.c by Greg Alexander (c) 1998 + * + * OPTIONS: + * debug - set to 1 if you'd like to see debug messages + * + * Revision: 0.1 - original version + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + +/* This driver ID is brand new, so define it if it's not in i2c-id.h yet */ +#ifndef I2C_DRIVERID_TDA9875 + #define I2C_DRIVERID_TDA9875 28 +#endif + + +MODULE_PARM(debug,"i"); + +static int debug = 0; /* insmod parameter */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + I2C_TDA9875 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* This is a superset of the TDA9875 */ +struct tda9875 { + int mode; + int rvol, lvol; + int bass, treble; +}; + + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define dprintk if (debug) printk + +/* The TDA9875 is made by Philips Semiconductor + * http://www.semiconductors.philips.com + * TDA9875: I2C-bus controlled DSP audio processor, FM demodulator + * + */ + + /* subaddresses for TDA9875 */ +#define TDA9875_MUT 0x12 /*General mute (value --> 0b11001100*/ +#define TDA9875_CFG 0x01 /* Config register (value --> 0b00000000 */ +#define TDA9875_DACOS 0x13 /*DAC i/o select (ADC) 0b0000100*/ +#define TDA9875_LOSR 0x16 /*Line output select regirter 0b0100 0001*/ + +#define TDA9875_CH1V 0x0c /*Chanel 1 volume (mute)*/ +#define TDA9875_CH2V 0x0d /*Chanel 2 volume (mute)*/ +#define TDA9875_SC1 0x14 /*SCART 1 in (mono)*/ +#define TDA9875_SC2 0x15 /*SCART 2 in (mono)*/ + +#define TDA9875_ADCIS 0x17 /*ADC input select (mono) 0b0110 000*/ +#define TDA9875_AER 0x19 /*Audio effect (AVL+Pseudo) 0b0000 0110*/ +#define TDA9875_MCS 0x18 /*Main channel select (DAC) 0b0000100*/ +#define TDA9875_MVL 0x1a /* Main volume gauche */ +#define TDA9875_MVR 0x1b /* Main volume droite */ +#define TDA9875_MBA 0x1d /* Main Basse */ +#define TDA9875_MTR 0x1e /* Main treble */ +#define TDA9875_ACS 0x1f /* Auxilary channel select (FM) 0b0000000*/ +#define TDA9875_AVL 0x20 /* Auxilary volume gauche */ +#define TDA9875_AVR 0x21 /* Auxilary volume droite */ +#define TDA9875_ABA 0x22 /* Auxilary Basse */ +#define TDA9875_ATR 0x23 /* Auxilary treble */ + +#define TDA9875_MSR 0x02 /* Monitor select register */ +#define TDA9875_C1MSB 0x03 /* Carrier 1 (FM) frequency register MSB */ +#define TDA9875_C1MIB 0x04 /* Carrier 1 (FM) frequency register (16-8]b */ +#define TDA9875_C1LSB 0x05 /* Carrier 1 (FM) frequency register LSB */ +#define TDA9875_C2MSB 0x06 /* Carrier 2 (nicam) frequency register MSB */ +#define TDA9875_C2MIB 0x07 /* Carrier 2 (nicam) frequency register (16-8]b */ +#define TDA9875_C2LSB 0x08 /* Carrier 2 (nicam) frequency register LSB */ +#define TDA9875_DCR 0x09 /* Demodulateur configuration regirter*/ +#define TDA9875_DEEM 0x0a /* FM de-emphasis regirter*/ +#define TDA9875_FMAT 0x0b /* FM Matrix regirter*/ + +/* values */ +#define TDA9875_MUTE_ON 0xff /* general mute */ +#define TDA9875_MUTE_OFF 0xcc /* general no mute */ + + + +/* Begin code */ + +static int tda9875_write(struct i2c_client *client, int subaddr, unsigned char val) +{ + unsigned char buffer[2]; + dprintk("In tda9875_write\n"); + dprintk("Writing %d 0x%x\n", subaddr, val); + buffer[0] = subaddr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tda9875: I/O error, trying (write %d 0x%x)\n", + subaddr, val); + return -1; + } + return 0; +} + +#if 0 +static int tda9875_read(struct i2c_client *client) +{ + unsigned char buffer; + dprintk("In tda9875_read\n"); + if (1 != i2c_master_recv(client,&buffer,1)) { + printk(KERN_WARNING "tda9875: I/O error, trying (read)\n"); + return -1; + } + dprintk("Read 0x%02x\n", buffer); + return buffer; +} +#endif + +static void tda9875_set(struct i2c_client *client) +{ + struct tda9875 *tda = client->data; + unsigned char a; + + dprintk(KERN_DEBUG "tda9875_set(%04x,%04x,%04x,%04x)\n",tda->lvol,tda->rvol,tda->bass,tda->treble); + + + a = tda->lvol & 0xff; + tda9875_write(client, TDA9875_MVL, a); + a =tda->rvol & 0xff; + tda9875_write(client, TDA9875_MVR, a); + a =tda->bass & 0xff; + tda9875_write(client, TDA9875_MBA, a); + a =tda->treble & 0xff; + tda9875_write(client, TDA9875_MTR, a); +} + +static void do_tda9875_init(struct i2c_client *client) +{ + struct tda9875 *t = client->data; + dprintk("In tda9875_init\n"); + tda9875_write(client, TDA9875_CFG, 0xd0 ); /*reg de config 0 (reset)*/ + tda9875_write(client, TDA9875_MSR, 0x03 ); /* Monitor 0b00000XXX*/ + tda9875_write(client, TDA9875_C1MSB, 0x00 ); /*Car1(FM) MSB XMHz*/ + tda9875_write(client, TDA9875_C1MIB, 0x00 ); /*Car1(FM) MIB XMHz*/ + tda9875_write(client, TDA9875_C1LSB, 0x00 ); /*Car1(FM) LSB XMHz*/ + tda9875_write(client, TDA9875_C2MSB, 0x00 ); /*Car2(NICAM) MSB XMHz*/ + tda9875_write(client, TDA9875_C2MIB, 0x00 ); /*Car2(NICAM) MIB XMHz*/ + tda9875_write(client, TDA9875_C2LSB, 0x00 ); /*Car2(NICAM) LSB XMHz*/ + tda9875_write(client, TDA9875_DCR, 0x00 ); /*Demod config 0x00*/ + tda9875_write(client, TDA9875_DEEM, 0x44 ); /*DE-Emph 0b0100 0100*/ + tda9875_write(client, TDA9875_FMAT, 0x00 ); /*FM Matrix reg 0x00*/ + tda9875_write(client, TDA9875_SC1, 0x00 ); /* SCART 1 (SC1)*/ + tda9875_write(client, TDA9875_SC2, 0x01 ); /* SCART 2 (sc2)*/ + + tda9875_write(client, TDA9875_CH1V, 0x10 ); /* Chanel volume 1 mute*/ + tda9875_write(client, TDA9875_CH2V, 0x10 ); /* Chanel volume 2 mute */ + tda9875_write(client, TDA9875_DACOS, 0x02 ); /* sig DAC i/o(in:nicam)*/ + tda9875_write(client, TDA9875_ADCIS, 0x6f ); /* sig ADC input(in:mono)*/ + tda9875_write(client, TDA9875_LOSR, 0x00 ); /* line out (in:mono)*/ + tda9875_write(client, TDA9875_AER, 0x00 ); /*06 Effect (AVL+PSEUDO) */ + tda9875_write(client, TDA9875_MCS, 0x44 ); /* Main ch select (DAC) */ + tda9875_write(client, TDA9875_MVL, 0x03 ); /* Vol Main left 10dB */ + tda9875_write(client, TDA9875_MVR, 0x03 ); /* Vol Main right 10dB*/ + tda9875_write(client, TDA9875_MBA, 0x00 ); /* Main Bass Main 0dB*/ + tda9875_write(client, TDA9875_MTR, 0x00 ); /* Main Treble Main 0dB*/ + tda9875_write(client, TDA9875_ACS, 0x44 ); /* Aux chan select (dac)*/ + tda9875_write(client, TDA9875_AVL, 0x00 ); /* Vol Aux left 0dB*/ + tda9875_write(client, TDA9875_AVR, 0x00 ); /* Vol Aux right 0dB*/ + tda9875_write(client, TDA9875_ABA, 0x00 ); /* Aux Bass Main 0dB*/ + tda9875_write(client, TDA9875_ATR, 0x00 ); /* Aux Aigus Main 0dB*/ + + tda9875_write(client, TDA9875_MUT, 0xcc ); /* General mute */ + + t->mode=AUDIO_UNMUTE; + t->lvol=t->rvol =0; /* 0dB */ + t->bass=0; /* 0dB */ + t->treble=0; /* 0dB */ + tda9875_set(client); + +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tda9875_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tda9875 *t; + struct i2c_client *client; + dprintk("In tda9875_attach\n"); + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = t = kmalloc(sizeof *t,GFP_KERNEL); + if (!t) + return -ENOMEM; + memset(t,0,sizeof *t); + do_tda9875_init(client); + MOD_INC_USE_COUNT; + strcpy(client->name,"TDA9875"); + printk(KERN_INFO "tda9875: init\n"); + + i2c_attach_client(client); + return 0; +} + +static int tda9875_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tda9875_attach); + return 0; +} + +static int tda9875_detach(struct i2c_client *client) +{ + struct tda9875 *t = client->data; + + do_tda9875_init(client); + i2c_detach_client(client); + + kfree(t); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int tda9875_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct tda9875 *t = client->data; + + dprintk("In tda9875_command...\n"); + + switch (cmd) { + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDIOCGAUDIO\n"); + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + + /* min is -84 max is 24 */ + left = (t->lvol+84)*606; + right = (t->rvol+84)*606; + va->volume=MAX(left,right); + va->balance=(32768*MIN(left,right))/ + (va->volume ? va->volume : 1); + va->balance=(left<right)? + (65535-va->balance) : va->balance; + va->bass = (t->bass+12)*2427; /* min -12 max +15 */ + va->treble = (t->treble+12)*2730;/* min -12 max +12 */ + + va->mode |= VIDEO_SOUND_MONO; + + + break; /* VIDIOCGAUDIO case */ + } + + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + int left,right; + + dprintk("VIDEOCSAUDIO...\n"); + left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + right = (MIN(va->balance,32768) * + va->volume) / 32768; + t->lvol = ((left/606)-84) & 0xff; + if (t->lvol > 24) + t->lvol = 24; + if (t->lvol < -84) + t->lvol = -84 & 0xff; + + t->rvol = ((right/606)-84) & 0xff; + if (t->rvol > 24) + t->rvol = 24; + if (t->rvol < -84) + t->rvol = -84 & 0xff; + + t->bass = ((va->bass/2400)-12) & 0xff; + if (t->bass > 15) + t->bass = 15; + if (t->bass < -12) + t->bass = -12 & 0xff; + + t->treble = ((va->treble/2700)-12) & 0xff; + if (t->treble > 12) + t->treble = 12; + if (t->treble < -12) + t->treble = -12 & 0xff; + + + +//printk("tda9875 bal:%04x vol:%04x bass:%04x treble:%04x\n",va->balance,va->volume,va->bass,va->treble); + + + tda9875_set(client); + + break; + + } /* end of VIDEOCSAUDIO case */ + + default: /* Not VIDEOCGAUDIO or VIDEOCSAUDIO */ + + /* nothing */ + dprintk("Default\n"); + + } /* end of (cmd) switch */ + + return 0; +} + + +static struct i2c_driver driver = { + "i2c tda9875 driver", + I2C_DRIVERID_TDA9875, /* Get new one for TDA9875 */ + I2C_DF_NOTIFY, + tda9875_probe, + tda9875_detach, + tda9875_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tda9875_init(void) +#endif +{ + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/media/video/tea6300.c b/drivers/media/video/tea6300.c new file mode 100644 index 000000000..f5949c94f --- /dev/null +++ b/drivers/media/video/tea6300.c @@ -0,0 +1,344 @@ +/* + * for the TEA6300 chip (only found on Gateway STB TV/FM cards tho the best + * of my knowledge) + * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF THE WRONG + * CHIP (i.e., an MSP3400) IS ON I2C ADDRESS 0x80 (it relies on i2c to + * make sure that there is a device acknowledging that address). This + * is a potential problem because the MSP3400 is very popular and does + * use this address! You have been warned! + * + * Copyright (c) 1998 Greg Alexander <galexand@acm.org> + * This code is placed under the terms of the GNU General Public License + * Code liberally copied from msp3400.c, which is by Gerd Knorr + * + * All of this should work, though it would be nice to eventually support + * balance (different left,right values) and, if someone ever finds a card + * with the support (or if you're careful with a soldering iron), fade + * (front/back). + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + + +/* Addresses to scan */ +#define I2C_TEA6300 0x80 +static unsigned short normal_i2c[] = { + I2C_TEA6300 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + + +MODULE_PARM(debug,"i"); +static int debug = 0; /* insmod parameter */ + +#define dprintk if (debug) printk + + +struct tea6300 { + int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */ + int stereo; + __u16 left,right; + __u16 bass,treble; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define TEA6300_VL 0x00 /* volume left */ +#define TEA6300_VR 0x01 /* volume right */ +#define TEA6300_BA 0x02 /* bass */ +#define TEA6300_TR 0x03 /* treble */ +#define TEA6300_FA 0x04 /* fader control */ +#define TEA6300_S 0x05 /* switch register */ + /* values for those registers: */ +#define TEA6300_S_SA 0x01 /* stereo A input */ +#define TEA6300_S_SB 0x02 /* stereo B */ +#define TEA6300_S_SC 0x04 /* stereo C */ +#define TEA6300_S_GMU 0x80 /* general mute */ + + +/* ******************************** * + * functions for talking to TEA6300 * + * ******************************** */ + +static int tea6300_write(struct i2c_client *client, int addr, int val) +{ + unsigned char buffer[2]; + + buffer[0] = addr; + buffer[1] = val; + if (2 != i2c_master_send(client,buffer,2)) { + printk(KERN_WARNING "tea6300: I/O error, trying (write %d 0x%x)\n", + addr, val); + return -1; + } + return 0; +} + +static void tea6300_set(struct i2c_client *client) +{ + struct tea6300 *tea = client->data; + + /* mode is ignored today */ + dprintk(KERN_DEBUG "tea6300_set(%04x,%04x,%04x,%04x)\n",tea->left>>10,tea->right>>10,tea->bass>>12,tea->treble>>12); + tea6300_write(client, TEA6300_VL, tea->left>>10 ); + tea6300_write(client, TEA6300_VR, tea->right>>10 ); + tea6300_write(client, TEA6300_BA, tea->bass>>12 ); + tea6300_write(client, TEA6300_TR, tea->treble>>12); +} + +static void do_tea6300_init(struct i2c_client *client) +{ + struct tea6300 *tea = client->data; + + tea->left=tea->right =49152; /* -10dB (loud enough, but not beyond + normal line levels - so as to avoid + clipping */ + tea->bass=tea->treble=28672; /* 0dB */ + tea->mode=AUDIO_OFF; + tea->stereo=1; + /* left=right=0x27<<10, bass=treble=0x07<<12 */ + tea6300_write(client, TEA6300_FA, 0x3f ); /* fader off */ + tea6300_write(client, TEA6300_S , TEA6300_S_GMU); /* mute */ + tea6300_set(client); +} + +static void tea6300_audio(struct i2c_client *client, int mode) +{ + struct tea6300 *tea = client->data; + + /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */ + dprintk(KERN_DEBUG "tea6300_audio:%d (T,R,E,I,O)\n",mode); + tea->mode=mode; + if (mode==AUDIO_OFF) { /* just mute it */ + tea6300_write(client, TEA6300_S, TEA6300_S_GMU); + return; + } + switch(mode) { + case AUDIO_TUNER: + tea6300_write(client, TEA6300_S, TEA6300_S_SA); + break; + case AUDIO_RADIO: + tea6300_write(client, TEA6300_S, TEA6300_S_SB); + break; + case AUDIO_EXTERN: + tea6300_write(client, TEA6300_S, TEA6300_S_SC); + break; + } +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tea6300_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tea6300 *tea; + struct i2c_client *client; + + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = tea = kmalloc(sizeof *tea,GFP_KERNEL); + if (!tea) + return -ENOMEM; + memset(tea,0,sizeof *tea); + do_tea6300_init(client); + + MOD_INC_USE_COUNT; + strcpy(client->name,"TEA6300T"); + printk(KERN_INFO "tea6300: initialized\n"); + + i2c_attach_client(client); + return 0; +} + +static int tea6300_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tea6300_attach); + return 0; +} + +static int tea6300_detach(struct i2c_client *client) +{ + struct tea6300 *tea = client->data; + + do_tea6300_init(client); + i2c_detach_client(client); + + kfree(tea); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int +tea6300_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tea6300 *tea = client->data; + __u16 *sarg = arg; + + switch (cmd) { + case AUDC_SET_RADIO: + tea6300_audio(client,AUDIO_RADIO); + break; + case AUDC_SET_INPUT: + tea6300_audio(client,*sarg); + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; + va->volume=MAX(tea->left,tea->right); + va->balance=(32768*MIN(tea->left,tea->right))/ + (va->volume ? va->volume : 1); + va->balance=(tea->left<tea->right)? + (65535-va->balance) : va->balance; + va->bass = tea->bass; + va->treble = tea->treble; + break; + } + case VIDIOCSAUDIO: + { + struct video_audio *va = arg; + + tea->left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + tea->right = (MIN(va->balance,32768) * + va->volume) / 32768; + tea->bass = va->bass; + tea->treble = va->treble; + tea6300_set(client); + break; + } +#if 0 + /* --- old, obsolete interface --- */ + case AUDC_GET_VOLUME_LEFT: + *sarg = tea->left; + break; + case AUDC_GET_VOLUME_RIGHT: + *sarg = tea->right; + break; + case AUDC_SET_VOLUME_LEFT: + tea->left = *sarg; + tea6300_set(client); + break; + case AUDC_SET_VOLUME_RIGHT: + tea->right = *sarg; + tea6300_set(client); + break; + + case AUDC_GET_BASS: + *sarg = tea->bass; + break; + case AUDC_SET_BASS: + tea->bass = *sarg; + tea6300_set(client); + break; + + case AUDC_GET_TREBLE: + *sarg = tea->treble; + break; + case AUDC_SET_TREBLE: + tea->treble = *sarg; + tea6300_set(client); + break; + + case AUDC_GET_STEREO: + *sarg = tea->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO; + break; + case AUDC_SET_STEREO: + tea->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1; + /* TODO: make this write to the TDA9850? */ + break; + +/* case AUDC_SWITCH_MUTE: someday, maybe -- not a lot of point to + case AUDC_NEWCHANNEL: it and it would require preserving state + case AUDC_GET_DC: huh?? (not used by bttv.c) +*/ +#endif + default: + /* nothing */ + } + return 0; +} + +static struct i2c_driver driver = { + "i2c tea6300 driver", + I2C_DRIVERID_TEA6300, + I2C_DF_NOTIFY, + tea6300_probe, + tea6300_detach, + tea6300_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tea6300_init(void) +#endif +{ + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tea6420.c b/drivers/media/video/tea6420.c new file mode 100644 index 000000000..3a1d63517 --- /dev/null +++ b/drivers/media/video/tea6420.c @@ -0,0 +1,273 @@ +/* + * for the TEA6420 chip (only found on 3DFX (STB) TV/FM cards to the best + * of my knowledge) + * Copyright (C) 2000 Dave Stuart <justdave@ynn.com> + * This code is placed under the terms of the GNU General Public License + * Code liberally copied from tea6300 by . . . + * + * Copyright (c) 1998 Greg Alexander <galexand@acm.org> + * This code is placed under the terms of the GNU General Public License + * Code liberally copied from msp3400.c, which is by Gerd Knorr + * + * Changes: + * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/14/2000 + * - resource allocation fixes in tea6300_attach + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/videodev.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include "bttv.h" +#include "audiochip.h" + + +/* Addresses to scan */ +#define I2C_TEA6420 0x98 +static unsigned short normal_i2c[] = { + I2C_TEA6420 >> 1, + I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + + +MODULE_PARM(debug,"i"); +static int debug = 0; /* insmod parameter */ + +#define dprintk if (debug) printk + + +struct tea6420 { + int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */ + int stereo; +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +#define TEA6420_S_SA 0x00 /* stereo A input */ +#define TEA6420_S_SB 0x01 /* stereo B */ +#define TEA6420_S_SC 0x02 /* stereo C */ +#define TEA6420_S_SD 0x03 /* stereo D */ +#define TEA6420_S_SE 0x04 /* stereo E */ +#define TEA6420_S_GMU 0x05 /* general mute */ + + +/* ******************************** * + * functions for talking to TEA6420 * + * ******************************** */ + +static int tea6420_write(struct i2c_client *client, int val) +{ + unsigned char buffer[2]; + int result; + +/* buffer[0] = addr; */ + buffer[0] = val; + result = i2c_master_send(client,buffer,1); + if (1 != result) { + printk(KERN_WARNING "tea6420: I/O error, trying (write +0x%x) result = %d\n", val, result); + return -1; + } + return 0; +} + + +static void do_tea6420_init(struct i2c_client *client) +{ + struct tea6420 *tea = client->data; + + tea->mode=AUDIO_OFF; + tea->stereo=1; + tea6420_write(client, TEA6420_S_GMU); /* mute */ +} + +static void tea6420_audio(struct i2c_client *client, int mode) +{ + struct tea6420 *tea = client->data; + + /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */ + dprintk(KERN_DEBUG "tea6420_audio:%d (T,R,E,I,O)\n",mode); + tea->mode=mode; + if (mode==AUDIO_OFF) { /* just mute it */ + tea6420_write(client, TEA6420_S_GMU); + return; + } + switch(mode) { + case AUDIO_TUNER: + tea6420_write(client, TEA6420_S_SA); + break; + case AUDIO_RADIO: + tea6420_write(client, TEA6420_S_SB); + break; + case AUDIO_EXTERN: + tea6420_write(client, TEA6420_S_SC); + break; + } +} + + +/* *********************** * + * i2c interface functions * + * *********************** */ + +static int tea6420_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tea6420 *tea; + struct i2c_client *client; + + client = kmalloc(sizeof *client,GFP_KERNEL); + if (!client) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->adapter = adap; + client->addr = addr; + + client->data = tea = kmalloc(sizeof *tea,GFP_KERNEL); + if (!tea) { + kfree(client); + return -ENOMEM; + } + memset(tea,0,sizeof *tea); + do_tea6420_init(client); + + MOD_INC_USE_COUNT; + strcpy(client->name,"TEA6420"); + printk(KERN_INFO "tea6420: initialized\n"); + + i2c_attach_client(client); + return 0; +} + +static int tea6420_probe(struct i2c_adapter *adap) +{ + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tea6420_attach); + return 0; +} + +static int tea6420_detach(struct i2c_client *client) +{ + struct tea6420 *tea = client->data; + + do_tea6420_init(client); + i2c_detach_client(client); + + kfree(tea); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int +tea6420_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + __u16 *sarg = arg; + + switch (cmd) { + case AUDC_SET_RADIO: + tea6420_audio(client,AUDIO_RADIO); + break; + case AUDC_SET_INPUT: + tea6420_audio(client,*sarg); + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCGAUDIO: + { + struct video_audio *va = arg; + + va->flags |= VIDEO_AUDIO_VOLUME | + VIDEO_AUDIO_BASS | + VIDEO_AUDIO_TREBLE; +/* va->volume=MAX(tea->left,tea->right); + va->balance=(32768*MIN(tea->left,tea->right))/ + (va->volume ? va->volume : 1); + va->balance=(tea->left<tea->right)? + (65535-va->balance) : va->balance; + va->bass = tea->bass; + va->treble = tea->treble; +*/ break; + } + case VIDIOCSAUDIO: + { + +/* tea->left = (MIN(65536 - va->balance,32768) * + va->volume) / 32768; + tea->right = (MIN(va->balance,32768) * + va->volume) / 32768; + tea->bass = va->bass; + tea->treble = va->treble; + tea6420_set(client); +*/ break; + } + +default: + /* nothing */ + } + return 0; +} + +static struct i2c_driver driver = { + "i2c tea6420 driver", + I2C_DRIVERID_TEA6420, + I2C_DF_NOTIFY, + tea6420_probe, + tea6420_detach, + tea6420_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +#ifdef MODULE +int init_module(void) +#else +int tea6420_init(void) +#endif +{ + i2c_add_driver(&driver); + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + i2c_del_driver(&driver); +} +#endif + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tuner-3036.c b/drivers/media/video/tuner-3036.c new file mode 100644 index 000000000..807ae339b --- /dev/null +++ b/drivers/media/video/tuner-3036.c @@ -0,0 +1,227 @@ +/* + * Driver for Philips SAB3036 "CITAC" tuner control chip. + * + * Author: Phil Blundell <philb@gnu.org> + * + * The SAB3036 is just about different enough from the chips that + * tuner.c copes with to make it not worth the effort to crowbar + * the support into that file. So instead we have a separate driver. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/version.h> +#include <linux/init.h> + +#include <linux/i2c.h> +#include <linux/videodev.h> + +#include "tuner.h" + +static int debug; /* insmod parameter */ +static int this_adap; + +static struct i2c_client client_template; + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {0x60, 0x61, I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +/* ---------------------------------------------------------------------- */ + +static unsigned char +tuner_getstatus (struct i2c_client *c) +{ + unsigned char byte; + if (i2c_master_recv(c, &byte, 1) != 1) + printk(KERN_ERR "tuner-3036: I/O error.\n"); + return byte; +} + +#define TUNER_FL 0x80 + +static int +tuner_islocked (struct i2c_client *c) +{ + return (tuner_getstatus(c) & TUNER_FL); +} + +/* ---------------------------------------------------------------------- */ + +static void +set_tv_freq(struct i2c_client *c, int freq) +{ + u16 div = ((freq * 20) / 16); + unsigned long give_up = jiffies + HZ; + unsigned char buffer[2]; + + if (debug) + printk(KERN_DEBUG "tuner: setting frequency %dMHz, divisor %x\n", freq / 16, div); + + /* Select high tuning current */ + buffer[0] = 0x29; + buffer[1] = 0x3e; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 1\n"); + + buffer[0] = 0x80 | ((div>>8) & 0x7f); + buffer[1] = div & 0xff; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 2\n"); + + while (!tuner_islocked(c) && time_before(jiffies, give_up)) + schedule(); + + if (!tuner_islocked(c)) + printk(KERN_WARNING "tuner: failed to achieve PLL lock\n"); + + /* Select low tuning current and engage AFC */ + buffer[0] = 0x29; + buffer[1] = 0xb2; + + if (i2c_master_send(c, buffer, 2) != 2) + printk("tuner: i2c i/o error 3\n"); + + if (debug) + printk(KERN_DEBUG "tuner: status %02x\n", tuner_getstatus(c)); +} + +/* ---------------------------------------------------------------------- */ + +static int +tuner_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + static unsigned char buffer[] = { 0x29, 0x32, 0x2a, 0, 0x2b, 0 }; + + struct i2c_client *client; + + if (this_adap > 0) + return -1; + this_adap++; + + client_template.adapter = adap; + client_template.addr = addr; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == NULL) + return -ENOMEM; + memcpy(client, &client_template, sizeof(struct i2c_client)); + + printk("tuner: SAB3036 found, status %02x\n", tuner_getstatus(client)); + + i2c_attach_client(client); + MOD_INC_USE_COUNT; + + if (i2c_master_send(client, buffer, 2) != 2) + printk("tuner: i2c i/o error 1\n"); + if (i2c_master_send(client, buffer+2, 2) != 2) + printk("tuner: i2c i/o error 2\n"); + if (i2c_master_send(client, buffer+4, 2) != 2) + printk("tuner: i2c i/o error 3\n"); + return 0; +} + +static int +tuner_detach(struct i2c_client *c) +{ + MOD_DEC_USE_COUNT; + return 0; +} + +static int +tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int *iarg = (int*)arg; + + switch (cmd) + { + case TUNER_SET_TVFREQ: + set_tv_freq(client, *iarg); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int +tuner_probe(struct i2c_adapter *adap) +{ + this_adap = 0; + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_LP)) + return i2c_probe(adap, &addr_data, tuner_attach); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver +i2c_driver_tuner = +{ + "sab3036", /* name */ + I2C_DRIVERID_SAB3036, /* ID */ + I2C_DF_NOTIFY, + tuner_probe, + tuner_detach, + tuner_command +}; + +static struct i2c_client client_template = +{ + "SAB3036", /* name */ + -1, + 0, + 0, + NULL, + &i2c_driver_tuner +}; + +EXPORT_NO_SYMBOLS; + +int __init +tuner3036_init(void) +{ + i2c_add_driver(&i2c_driver_tuner); + return 0; +} + +void __exit +tuner3036_exit(void) +{ + i2c_del_driver(&i2c_driver_tuner); +} + +MODULE_DESCRIPTION("SAB3036 tuner driver"); +MODULE_AUTHOR("Philip Blundell <philb@gnu.org>"); +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug,"Enable debugging output"); + +module_init(tuner3036_init); +module_exit(tuner3036_exit); diff --git a/drivers/media/video/tuner.c b/drivers/media/video/tuner.c new file mode 100644 index 000000000..dfbf0f6af --- /dev/null +++ b/drivers/media/video/tuner.c @@ -0,0 +1,451 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/poll.h> +#include <linux/i2c.h> +#include <linux/types.h> +#include <linux/videodev.h> +#include <linux/init.h> + +#include "tuner.h" +#include "audiochip.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {I2C_CLIENT_END}; +static unsigned short normal_i2c_range[] = {0x60,0x6f,I2C_CLIENT_END}; +static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END }; +static struct i2c_client_address_data addr_data = { + normal_i2c, normal_i2c_range, + probe, probe_range, + ignore, ignore_range, + force +}; + +static int debug = 0; /* insmod parameter */ +static int type = -1; /* insmod parameter */ + +static int addr = 0; +static int this_adap; + +#define dprintk if (debug) printk + +MODULE_PARM(debug,"i"); +MODULE_PARM(type,"i"); +MODULE_PARM(addr,"i"); + +struct tuner +{ + int type; /* chip type */ + int freq; /* keep track of the current settings */ + int std; + + int radio; + int mode; /* PAL(0)/SECAM(1) mode (PHILIPS_SECAM only) */ +}; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* ---------------------------------------------------------------------- */ + +struct tunertype +{ + char *name; + unsigned char Vendor; + unsigned char Type; + + unsigned short thresh1; /* frequency Range for UHF,VHF-L, VHF_H */ + unsigned short thresh2; + unsigned char VHF_L; + unsigned char VHF_H; + unsigned char UHF; + unsigned char config; + unsigned short IFPCoff; + unsigned char mode; /* mode change value (tested PHILIPS_SECAM only) */ + /* 0x01 -> ??? no change ??? */ + /* 0x02 -> PAL BDGHI / SECAM L */ + /* 0x04 -> ??? PAL others / SECAM others ??? */ + int capability; +}; + +/* + * The floats in the tuner struct are computed at compile time + * by gcc and cast back to integers. Thus we don't violate the + * "no float in kernel" rule. + */ +static struct tunertype tuners[] = { + { "Temic PAL", TEMIC, PAL, + 16*140.25,16*463.25,0x02,0x04,0x01,0x8e,623}, + { "Philips PAL_I", Philips, PAL_I, + 16*140.25,16*463.25,0xa0,0x90,0x30,0x8e,623}, + { "Philips NTSC", Philips, NTSC, + 16*157.25,16*451.25,0xA0,0x90,0x30,0x8e,732}, + { "Philips SECAM", Philips, SECAM, + 16*168.25,16*447.25,0xA7,0x97,0x37,0x8e,623,0x02}, + { "NoTuner", NoTuner, NOTUNER, + 0,0,0x00,0x00,0x00,0x00,0x00,000}, + { "Philips PAL", Philips, PAL, + 16*168.25,16*447.25,0xA0,0x90,0x30,0x8e,623}, + { "Temic NTSC", TEMIC, NTSC, + 16*157.25,16*463.25,0x02,0x04,0x01,0x8e,732}, + { "Temic PAL_I", TEMIC, PAL_I, + 16*170.00,16*450.00,0x02,0x04,0x01,0x8e,623}, + { "Temic 4036 FY5 NTSC", TEMIC, NTSC, + 16*157.25,16*463.25,0xa0,0x90,0x30,0x8e,732}, + { "Alps HSBH1", TEMIC, NTSC, + 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, + { "Alps TSBE1",TEMIC,PAL, + 16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, + { "Alps TSBB5", Alps, PAL_I, /* tested (UK UHF) with Modtec MM205 */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,632}, + { "Alps TSBE5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,622}, + { "Alps TSBC5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ + 16*133.25,16*351.25,0x01,0x02,0x08,0x8e,608}, + { "Temic 4006FH5", TEMIC, PAL_I, + 16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, +}; +#define TUNERS (sizeof(tuners)/sizeof(struct tunertype)) + +/* ---------------------------------------------------------------------- */ + +static int tuner_getstatus(struct i2c_client *c) +{ + unsigned char byte; + + if (1 != i2c_master_recv(c,&byte,1)) + return 0; + return byte; +} + +#define TUNER_POR 0x80 +#define TUNER_FL 0x40 +#define TUNER_MODE 0x38 +#define TUNER_AFC 0x07 + +static int tuner_islocked (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_FL); +} + +static int tuner_afcstatus (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_AFC) - 2; +} + +#if 0 /* unused */ +static int tuner_mode (struct i2c_client *c) +{ + return (tuner_getstatus (c) & TUNER_MODE) >> 3; +} +#endif + +static void set_tv_freq(struct i2c_client *c, int freq) +{ + u8 config; + u16 div; + struct tunertype *tun; + struct tuner *t = c->data; + unsigned char buffer[4]; + int rc; + + if (t->type == -1) { + printk("tuner: tuner type not set\n"); + return; + } + + tun=&tuners[t->type]; + if (freq < tun->thresh1) + config = tun->VHF_L; + else if (freq < tun->thresh2) + config = tun->VHF_H; + else + config = tun->UHF; + +#if 1 // Fix colorstandard mode change + if (t->type == TUNER_PHILIPS_SECAM && t->mode) + config |= tun->mode; + else + config &= ~tun->mode; +#else + config &= ~tun->mode; +#endif + + div=freq + tun->IFPCoff; + + /* + * Philips FI1216MK2 remark from specification : + * for channel selection involving band switching, and to ensure + * smooth tuning to the desired channel without causing + * unnecessary charge pump action, it is recommended to consider + * the difference between wanted channel frequency and the + * current channel frequency. Unnecessary charge pump action + * will result in very low tuning voltage which may drive the + * oscillator to extreme conditions. + */ + /* + * Progfou: specification says to send config data before + * frequency in case (wanted frequency < current frequency). + */ + + if (t->type == TUNER_PHILIPS_SECAM && freq < t->freq) { + buffer[0] = tun->config; + buffer[1] = config; + buffer[2] = (div>>8) & 0x7f; + buffer[3] = div & 0xff; + } else { + buffer[0] = (div>>8) & 0x7f; + buffer[1] = div & 0xff; + buffer[2] = tun->config; + buffer[3] = config; + } + + if (4 != (rc = i2c_master_send(c,buffer,4))) + printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc); + +} + +static void set_radio_freq(struct i2c_client *c, int freq) +{ + u8 config; + u16 div; + struct tunertype *tun; + struct tuner *t = (struct tuner*)c->data; + unsigned char buffer[4]; + int rc; + + if (t->type == -1) { + printk("tuner: tuner type not set\n"); + return; + } + + tun=&tuners[t->type]; + config = 0xa5; + div=freq + (int)(16*10.7); + div&=0x7fff; + + buffer[0] = (div>>8) & 0x7f; + buffer[1] = div & 0xff; + buffer[2] = tun->config; + buffer[3] = config; + if (4 != (rc = i2c_master_send(c,buffer,4))) + printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc); + + if (debug) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/10); + + if (tuner_islocked (c)) + printk ("tuner: PLL locked\n"); + else + printk ("tuner: PLL not locked\n"); + + printk ("tuner: AFC: %d\n", tuner_afcstatus (c)); + } +} +/* ---------------------------------------------------------------------- */ + + +static int tuner_attach(struct i2c_adapter *adap, int addr, + unsigned short flags, int kind) +{ + struct tuner *t; + struct i2c_client *client; + + if (this_adap > 0) + return -1; + this_adap++; + + client_template.adapter = adap; + client_template.addr = addr; + + printk("tuner: chip found @ 0x%x\n",addr); + + if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL))) + return -ENOMEM; + memcpy(client,&client_template,sizeof(struct i2c_client)); + client->data = t = kmalloc(sizeof(struct tuner),GFP_KERNEL); + if (NULL == t) { + kfree(client); + return -ENOMEM; + } + memset(t,0,sizeof(struct tuner)); + if (type >= 0 && type < TUNERS) { + t->type = type; + strncpy(client->name, tuners[t->type].name, sizeof(client->name)); + } else { + t->type = -1; + } + i2c_attach_client(client); + MOD_INC_USE_COUNT; + + return 0; +} + +static int tuner_probe(struct i2c_adapter *adap) +{ + if (0 != addr) { + normal_i2c_range[0] = addr; + normal_i2c_range[1] = addr; + } + this_adap = 0; + if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) + return i2c_probe(adap, &addr_data, tuner_attach); + return 0; +} + +static int tuner_detach(struct i2c_client *client) +{ + struct tuner *t = (struct tuner*)client->data; + + i2c_detach_client(client); + kfree(t); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int +tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tuner *t = (struct tuner*)client->data; + int *iarg = (int*)arg; +#if 0 + __u16 *sarg = (__u16*)arg; +#endif + + switch (cmd) { + + /* --- configuration --- */ + case TUNER_SET_TYPE: + if (t->type != -1) + return 0; + if (*iarg < 0 || *iarg >= TUNERS) + return 0; + t->type = *iarg; + dprintk("tuner: type set to %d (%s)\n", + t->type,tuners[t->type].name); + strncpy(client->name, tuners[t->type].name, sizeof(client->name)); + break; + case AUDC_SET_RADIO: + t->radio = 1; + break; + + /* --- v4l ioctls --- */ + /* take care: bttv does userspace copying, we'll get a + kernel pointer here... */ + case VIDIOCSCHAN: + { + struct video_channel *vc = arg; + + t->radio = 0; + if (t->type == TUNER_PHILIPS_SECAM) { + t->mode = (vc->norm == VIDEO_MODE_SECAM) ? 1 : 0; + set_tv_freq(client,t->freq); + } + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *v = arg; + + t->freq = *v; + if (t->radio) { + dprintk("tuner: radio freq set to %d.%02d\n", + (*iarg)/16,(*iarg)%16*100/16); + set_radio_freq(client,t->freq); + } else { + dprintk("tuner: tv freq set to %d.%02d\n", + (*iarg)/16,(*iarg)%16*100/16); + set_tv_freq(client,t->freq); + } + return 0; + } +#if 0 + /* --- old, obsolete interface --- */ + case TUNER_SET_TVFREQ: + dprintk("tuner: tv freq set to %d.%02d\n", + (*iarg)/16,(*iarg)%16*100/16); + set_tv_freq(client,*iarg); + t->radio = 0; + t->freq = *iarg; + break; + + case TUNER_SET_RADIOFREQ: + dprintk("tuner: radio freq set to %d.%02d\n", + (*iarg)/16,(*iarg)%16*100/16); + set_radio_freq(client,*iarg); + t->radio = 1; + t->freq = *iarg; + break; + case TUNER_SET_MODE: + if (t->type != TUNER_PHILIPS_SECAM) { + dprintk("tuner: trying to change mode for other than TUNER_PHILIPS_SECAM\n"); + } else { + int mode=(*sarg==VIDEO_MODE_SECAM)?1:0; + dprintk("tuner: mode set to %d\n", *sarg); + t->mode = mode; + set_tv_freq(client,t->freq); + } + break; +#endif + default: + /* nothing */ + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver driver = { + "i2c TV tuner driver", + I2C_DRIVERID_TUNER, + I2C_DF_NOTIFY, + tuner_probe, + tuner_detach, + tuner_command, +}; + +static struct i2c_client client_template = +{ + "(unset)", /* name */ + -1, + 0, + 0, + NULL, + &driver +}; + +EXPORT_NO_SYMBOLS; + +int tuner_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +void tuner_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tuner_init_module); +module_exit(tuner_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/tuner.h b/drivers/media/video/tuner.h new file mode 100644 index 000000000..96fcd3021 --- /dev/null +++ b/drivers/media/video/tuner.h @@ -0,0 +1,57 @@ +/* + tuner.h - definition for different tuners + + Copyright (C) 1997 Markus Schroeder (schroedm@uni-duesseldorf.de) + minor modifications by Ralph Metzler (rjkm@thp.uni-koeln.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _TUNER_H +#define _TUNER_H + +#define TUNER_TEMIC_PAL 0 /* Miro Gpio Coding -1 */ +#define TUNER_PHILIPS_PAL_I 1 +#define TUNER_PHILIPS_NTSC 2 +#define TUNER_PHILIPS_SECAM 3 +#define TUNER_ABSENT 4 +#define TUNER_PHILIPS_PAL 5 +#define TUNER_TEMIC_NTSC 6 +#define TUNER_TEMIC_PAL_I 7 +#define TUNER_TEMIC_4036FY5_NTSC 8 +#define TUNER_ALPS_TSBH1_NTSC 9 +#define TUNER_ALPS_TSBE1_PAL 10 +#define TUNER_ALPS_TSBB5_PAL_I 11 +#define TUNER_ALPS_TSBE5_PAL 12 +#define TUNER_ALPS_TSBC5_PAL 13 + +#define NOTUNER 0 +#define PAL 1 +#define PAL_I 2 +#define NTSC 3 +#define SECAM 4 + +#define NoTuner 0 +#define Philips 1 +#define TEMIC 2 +#define Sony 3 +#define Alps 4 + +#define TUNER_SET_TYPE _IOW('t',1,int) /* set tuner type */ +#define TUNER_SET_TVFREQ _IOW('t',2,int) /* set tv freq */ +#define TUNER_SET_RADIOFREQ _IOW('t',3,int) /* set radio freq */ +#define TUNER_SET_MODE _IOW('t',4,int) /* set tuner mode */ + +#endif diff --git a/drivers/media/video/tvmixer.c b/drivers/media/video/tvmixer.c new file mode 100644 index 000000000..e1034a152 --- /dev/null +++ b/drivers/media/video/tvmixer.c @@ -0,0 +1,353 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/i2c.h> +#include <linux/videodev.h> +#include <asm/semaphore.h> +#include <linux/init.h> + +#include <linux/sound.h> +#include <linux/soundcard.h> +#include <asm/uaccess.h> + +#include "audiochip.h" + +#define DEV_MAX 4 + +static int debug = 0; +static int devnr = -1; + +MODULE_PARM(debug,"i"); +MODULE_PARM(devnr,"i"); + +/* ----------------------------------------------------------------------- */ + +struct TVMIXER { + struct i2c_client *dev; + int minor; + int count; +}; + +static struct TVMIXER devices[DEV_MAX]; + +static int tvmixer_adapters(struct i2c_adapter *adap); +static int tvmixer_clients(struct i2c_client *client); + +static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +static int tvmixer_open(struct inode *inode, struct file *file); +static int tvmixer_release(struct inode *inode, struct file *file); +static loff_t tvmixer_llseek(struct file *file, loff_t offset, int origin); + + +static struct i2c_driver driver = { + "tv card mixer driver", + 42 /* I2C_DRIVERID_FIXME */, + I2C_DF_DUMMY, + tvmixer_adapters, + tvmixer_clients, +}; + +static struct file_operations tvmixer_fops = { + owner: THIS_MODULE, + llseek: tvmixer_llseek, + ioctl: tvmixer_ioctl, + open: tvmixer_open, + release: tvmixer_release, +}; + +/* ----------------------------------------------------------------------- */ + +static int mix_to_v4l(int i) +{ + int r; + + r = ((i & 0xff) * 65536 + 50) / 100; + if (r > 65535) r = 65535; + if (r < 0) r = 0; + return r; +} + +static int v4l_to_mix(int i) +{ + int r; + + r = (i * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + return r | (r << 8); +} + +static int v4l_to_mix2(int l, int r) +{ + r = (r * 100 + 32768) / 65536; + if (r > 100) r = 100; + if (r < 0) r = 0; + l = (l * 100 + 32768) / 65536; + if (l > 100) l = 100; + if (l < 0) l = 0; + return (r << 8) | l; +} + +static int tvmixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video_audio va; + int left,right,ret,val = 0; + struct TVMIXER *mix = file->private_data; + struct i2c_client *client = mix->dev; + + if (NULL == client) + return -ENODEV; + + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strncpy(info.id, "tv card", sizeof(info.id)); + strncpy(info.name, client->name, sizeof(info.name)); + info.modify_counter = 42 /* FIXME */; + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strncpy(info.id, "tv card", sizeof(info.id)); + strncpy(info.name, client->name, sizeof(info.name)); + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int *)arg); + + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + if (get_user(val, (int *)arg)) + return -EFAULT; + + /* read state */ + memset(&va,0,sizeof(va)); + client->driver->command(client,VIDIOCGAUDIO,&va); + + switch (cmd) { + case MIXER_READ(SOUND_MIXER_RECMASK): + case MIXER_READ(SOUND_MIXER_CAPS): + case MIXER_READ(SOUND_MIXER_RECSRC): + case MIXER_WRITE(SOUND_MIXER_RECSRC): + ret = 0; + break; + + case MIXER_READ(SOUND_MIXER_STEREODEVS): + ret = SOUND_MASK_VOLUME; + break; + case MIXER_READ(SOUND_MIXER_DEVMASK): + ret = SOUND_MASK_VOLUME; + if (va.flags & VIDEO_AUDIO_BASS) + ret |= SOUND_MASK_BASS; + if (va.flags & VIDEO_AUDIO_TREBLE) + ret |= SOUND_MASK_TREBLE; + break; + + case MIXER_WRITE(SOUND_MIXER_VOLUME): + left = mix_to_v4l(val); + right = mix_to_v4l(val >> 8); + va.volume = MAX(left,right); + va.balance = (32768*MIN(left,right)) / (va.volume ? va.volume : 1); + va.balance = (left<right) ? (65535-va.balance) : va.balance; + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_VOLUME): + left = (MIN(65536 - va.balance,32768) * + va.volume) / 32768; + right = (MIN(va.balance,32768) * + va.volume) / 32768; + ret = v4l_to_mix2(left,right); + break; + + case MIXER_WRITE(SOUND_MIXER_BASS): + va.bass = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_BASS): + ret = v4l_to_mix(va.bass); + break; + + case MIXER_WRITE(SOUND_MIXER_TREBLE): + va.treble = mix_to_v4l(val); + client->driver->command(client,VIDIOCSAUDIO,&va); + client->driver->command(client,VIDIOCGAUDIO,&va); + /* fall throuth */ + case MIXER_READ(SOUND_MIXER_TREBLE): + ret = v4l_to_mix(va.treble); + break; + + default: + return -EINVAL; + } + if (put_user(ret, (int *)arg)) + return -EFAULT; + return 0; +} + +static int tvmixer_open(struct inode *inode, struct file *file) +{ + int i, minor = MINOR(inode->i_rdev); + struct TVMIXER *mix = NULL; + struct i2c_client *client = NULL; + + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor == minor) { + mix = devices+i; + client = mix->dev; + break; + } + } + + if (NULL == client) + return -ENODEV; + + /* lock bttv in memory while the mixer is in use */ + file->private_data = mix; + if (client->adapter->inc_use) + client->adapter->inc_use(client->adapter); + return 0; +} + +static int tvmixer_release(struct inode *inode, struct file *file) +{ + struct TVMIXER *mix = file->private_data; + struct i2c_client *client; + + client = mix->dev; + if (NULL == client) { + return -ENODEV; + } + + if (client->adapter->dec_use) + client->adapter->dec_use(client->adapter); + return 0; +} + +static loff_t tvmixer_llseek(struct file *file, loff_t offset, int origin) +{ + return -ESPIPE; +} + +/* ----------------------------------------------------------------------- */ + +static int tvmixer_adapters(struct i2c_adapter *adap) +{ + return 0; +} + +static int tvmixer_clients(struct i2c_client *client) +{ + struct video_audio va; + int i,minor; + + /* TV card ??? */ + if (client->adapter->id != (I2C_ALGO_BIT | I2C_HW_B_BT848)) { + if (debug) + printk("tvmixer: %s is not a tv card\n", + client->adapter->name); + return -1; + } + printk("tvmixer: debug: %s\n",client->name); + + /* unregister ?? */ + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].dev == client) { + /* unregister */ + unregister_sound_mixer(devices[i].minor); + devices[i].dev = NULL; + devices[i].minor = -1; + printk("tvmixer: %s unregistered (#1)\n",client->name); + return 0; + } + } + + /* look for a free slot */ + for (i = 0; i < DEV_MAX; i++) + if (NULL == devices[i].dev) + break; + if (i == DEV_MAX) { + printk(KERN_WARNING "tvmixer: DEV_MAX too small\n"); + return -1; + } + + /* audio chip with mixer ??? */ + if (NULL == client->driver->command) { + if (debug) + printk("tvmixer: %s: driver->command is NULL\n", + client->driver->name); + return -1; + } + memset(&va,0,sizeof(va)); + if (0 != client->driver->command(client,VIDIOCGAUDIO,&va)) { + if (debug) + printk("tvmixer: %s: VIDIOCGAUDIO failed\n", + client->name); + return -1; + } + if (0 == (va.flags & VIDEO_AUDIO_VOLUME)) { + if (debug) + printk("tvmixer: %s: has no volume control\n", + client->name); + return -1; + } + + /* everything is fine, register */ + if ((minor = register_sound_mixer(&tvmixer_fops,devnr)) < 0) { + printk(KERN_ERR "tvmixer: cannot allocate mixer device\n"); + return -1; + } + + devices[i].minor = minor; + devices[i].count = 0; + devices[i].dev = client; + printk("tvmixer: %s (%s) registered with minor %d\n", + client->name,client->adapter->name,minor); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +int tvmixer_init_module(void) +{ + int i; + + for (i = 0; i < DEV_MAX; i++) + devices[i].minor = -1; + i2c_add_driver(&driver); + return 0; +} + +void tvmixer_cleanup_module(void) +{ + int i; + + i2c_del_driver(&driver); + for (i = 0; i < DEV_MAX; i++) { + if (devices[i].minor != -1) { + unregister_sound_mixer(devices[i].minor); + printk("tvmixer: %s unregistered (#2)\n", + devices[i].dev->name); + } + } +} + +module_init(tvmixer_init_module); +module_exit(tvmixer_cleanup_module); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ diff --git a/drivers/media/video/videodev.c b/drivers/media/video/videodev.c new file mode 100644 index 000000000..c806ff264 --- /dev/null +++ b/drivers/media/video/videodev.c @@ -0,0 +1,581 @@ +/* + * Video capture interface for Linux + * + * A generic video device interface for the LINUX operating system + * using a set of device structures/vectors for low level operations. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Author: Alan Cox, <alan@redhat.com> + * + * Fixes: 20000516 Claudio Matsuoka <claudio@conectiva.com> + * - Added procfs support + */ + +#include <linux/config.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/videodev.h> +#include <linux/init.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +#include <linux/kmod.h> + + +#define VIDEO_NUM_DEVICES 256 + +/* + * Active devices + */ + +static struct video_device *video_device[VIDEO_NUM_DEVICES]; + + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + +#include <linux/proc_fs.h> + +struct videodev_proc_data { + struct list_head proc_list; + char name[16]; + struct video_device *vdev; + struct proc_dir_entry *proc_entry; +}; + +static struct proc_dir_entry *video_dev_proc_entry = NULL; +struct proc_dir_entry *video_proc_entry = NULL; +EXPORT_SYMBOL(video_proc_entry); +LIST_HEAD(videodev_proc_list); + +#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */ + + +#ifdef CONFIG_VIDEO_BWQCAM +extern int init_bw_qcams(struct video_init *); +#endif +#ifdef CONFIG_VIDEO_CPIA +extern int cpia_init(struct video_init *); +#endif +#ifdef CONFIG_VIDEO_PLANB +extern int init_planbs(struct video_init *); +#endif +#ifdef CONFIG_VIDEO_ZORAN +extern int init_zoran_cards(struct video_init *); +#endif + +static struct video_init video_init_list[]={ +#ifdef CONFIG_VIDEO_BWQCAM + {"bw-qcam", init_bw_qcams}, +#endif +#ifdef CONFIG_VIDEO_CPIA + {"cpia", cpia_init}, +#endif +#ifdef CONFIG_VIDEO_PLANB + {"planb", init_planbs}, +#endif +#ifdef CONFIG_VIDEO_ZORAN + {"zoran", init_zoran_cards}, +#endif + {"end", NULL} +}; + +/* + * Read will do some smarts later on. Buffer pin etc. + */ + +static ssize_t video_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)]; + if(vfl->read) + return vfl->read(vfl, buf, count, file->f_flags&O_NONBLOCK); + else + return -EINVAL; +} + + +/* + * Write for now does nothing. No reason it shouldnt do overlay setting + * for some boards I guess.. + */ + +static ssize_t video_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)]; + if(vfl->write) + return vfl->write(vfl, buf, count, file->f_flags&O_NONBLOCK); + else + return 0; +} + +/* + * Poll to see if we're readable, can probably be used for timing on incoming + * frames, etc.. + */ + +static unsigned int video_poll(struct file *file, poll_table * wait) +{ + struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)]; + if(vfl->poll) + return vfl->poll(vfl, file, wait); + else + return 0; +} + + +/* + * Open a video device. + */ + +static int video_open(struct inode *inode, struct file *file) +{ + unsigned int minor = MINOR(inode->i_rdev); + int err; + struct video_device *vfl; + + if(minor>=VIDEO_NUM_DEVICES) + return -ENODEV; + + vfl=video_device[minor]; + if(vfl==NULL) { + char modname[20]; + + sprintf (modname, "char-major-%d-%d", VIDEO_MAJOR, minor); + request_module(modname); + vfl=video_device[minor]; + if (vfl==NULL) + return -ENODEV; + } + if(vfl->busy) + return -EBUSY; + vfl->busy=1; /* In case vfl->open sleeps */ + + if(vfl->open) + { + err=vfl->open(vfl,0); /* Tell the device it is open */ + if(err) + { + vfl->busy=0; + return err; + } + } + return 0; +} + +/* + * Last close of a video for Linux device + */ + +static int video_release(struct inode *inode, struct file *file) +{ + struct video_device *vfl; + lock_kernel(); + vfl=video_device[MINOR(inode->i_rdev)]; + if(vfl->close) + vfl->close(vfl); + vfl->busy=0; + unlock_kernel(); + return 0; +} + +/* + * Question: Should we be able to capture and then seek around the + * image ? + */ + +static long long video_lseek(struct file * file, + long long offset, int origin) +{ + return -ESPIPE; +} + +static int video_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vfl=video_device[MINOR(inode->i_rdev)]; + int err=vfl->ioctl(vfl, cmd, (void *)arg); + + if(err!=-ENOIOCTLCMD) + return err; + + switch(cmd) + { + default: + return -EINVAL; + } +} + +/* + * We need to do MMAP support + */ + + +int video_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret = -EINVAL; + struct video_device *vfl=video_device[MINOR(file->f_dentry->d_inode->i_rdev)]; + if(vfl->mmap) { + lock_kernel(); + ret = vfl->mmap(vfl, (char *)vma->vm_start, + (unsigned long)(vma->vm_end-vma->vm_start)); + unlock_kernel(); + } + return ret; +} + +/* + * /proc support + */ + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + +/* Hmm... i'd like to see video_capability information here, but + * how can I access it (without changing the other drivers? -claudio + */ +static int videodev_proc_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + struct video_device *vfd = data; + struct videodev_proc_data *d; + struct list_head *tmp; + int len; + char c = ' '; + + list_for_each (tmp, &videodev_proc_list) { + d = list_entry(tmp, struct videodev_proc_data, proc_list); + if (vfd == d->vdev) + break; + } + + /* Sanity check */ + if (tmp == &videodev_proc_list) + goto skip; + +#define PRINT_VID_TYPE(x) do { if (vfd->type & x) \ + out += sprintf (out, "%c%s", c, #x); c='|';} while (0) + + out += sprintf (out, "name : %s\n", vfd->name); + out += sprintf (out, "type :"); + PRINT_VID_TYPE(VID_TYPE_CAPTURE); + PRINT_VID_TYPE(VID_TYPE_TUNER); + PRINT_VID_TYPE(VID_TYPE_TELETEXT); + PRINT_VID_TYPE(VID_TYPE_OVERLAY); + PRINT_VID_TYPE(VID_TYPE_CHROMAKEY); + PRINT_VID_TYPE(VID_TYPE_CLIPPING); + PRINT_VID_TYPE(VID_TYPE_FRAMERAM); + PRINT_VID_TYPE(VID_TYPE_SCALES); + PRINT_VID_TYPE(VID_TYPE_MONOCHROME); + PRINT_VID_TYPE(VID_TYPE_SUBCAPTURE); + PRINT_VID_TYPE(VID_TYPE_MPEG_DECODER); + PRINT_VID_TYPE(VID_TYPE_MPEG_ENCODER); + PRINT_VID_TYPE(VID_TYPE_MJPEG_DECODER); + PRINT_VID_TYPE(VID_TYPE_MJPEG_ENCODER); + out += sprintf (out, "\n"); + out += sprintf (out, "hardware : 0x%x\n", vfd->hardware); +#if 0 + out += sprintf (out, "channels : %d\n", d->vcap.channels); + out += sprintf (out, "audios : %d\n", d->vcap.audios); + out += sprintf (out, "maxwidth : %d\n", d->vcap.maxwidth); + out += sprintf (out, "maxheight : %d\n", d->vcap.maxheight); + out += sprintf (out, "minwidth : %d\n", d->vcap.minwidth); + out += sprintf (out, "minheight : %d\n", d->vcap.minheight); +#endif + +skip: + len = out - page; + len -= off; + if (len < count) { + *eof = 1; + if (len <= 0) + return 0; + } else + len = count; + + *start = page + off; + + return len; +} + +static void videodev_proc_create(void) +{ + video_proc_entry = create_proc_entry("video", S_IFDIR, &proc_root); + + if (video_proc_entry == NULL) { + printk("video_dev: unable to initialise /proc/video\n"); + return; + } + + video_proc_entry->owner = THIS_MODULE; + video_dev_proc_entry = create_proc_entry("dev", S_IFDIR, video_proc_entry); + + if (video_dev_proc_entry == NULL) { + printk("video_dev: unable to initialise /proc/video/dev\n"); + return; + } + + video_dev_proc_entry->owner = THIS_MODULE; +} + +#ifdef MODULE +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) +static void videodev_proc_destroy(void) +{ + if (video_dev_proc_entry != NULL) + remove_proc_entry("dev", video_proc_entry); + + if (video_proc_entry != NULL) + remove_proc_entry("video", &proc_root); +} +#endif +#endif + +static void videodev_proc_create_dev (struct video_device *vfd, char *name) +{ + struct videodev_proc_data *d; + struct proc_dir_entry *p; + + if (video_dev_proc_entry == NULL) + return; + + d = kmalloc (sizeof (struct videodev_proc_data), GFP_KERNEL); + if (!d) + return; + + p = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, video_dev_proc_entry); + p->data = vfd; + p->read_proc = videodev_proc_read; + + d->proc_entry = p; + d->vdev = vfd; + strcpy (d->name, name); + + /* How can I get capability information ? */ + + list_add (&d->proc_list, &videodev_proc_list); +} + +static void videodev_proc_destroy_dev (struct video_device *vfd) +{ + struct list_head *tmp; + struct videodev_proc_data *d; + + list_for_each (tmp, &videodev_proc_list) { + d = list_entry(tmp, struct videodev_proc_data, proc_list); + if (vfd == d->vdev) { + remove_proc_entry(d->name, video_dev_proc_entry); + list_del (&d->proc_list); + kfree (d); + break; + } + } +} + +#endif /* CONFIG_VIDEO_PROC_FS */ + +extern struct file_operations video_fops; + +/** + * video_register_device - register video4linux devices + * @vfd: video device structure we want to register + * @type: type of device to register + * FIXME: needs a semaphore on 2.3.x + * + * The registration code assigns minor numbers based on the type + * requested. -ENFILE is returned in all the device slots for this + * category are full. If not then the minor field is set and the + * driver initialize function is called (if non %NULL). + * + * Zero is returned on success. + * + * Valid types are + * + * %VFL_TYPE_GRABBER - A frame grabber + * + * %VFL_TYPE_VTX - A teletext device + * + * %VFL_TYPE_VBI - Vertical blank data (undecoded) + * + * %VFL_TYPE_RADIO - A radio card + */ + +int video_register_device(struct video_device *vfd, int type) +{ + int i=0; + int base; + int err; + int end; + char *name_base; + + switch(type) + { + case VFL_TYPE_GRABBER: + base=0; + end=64; + name_base = "video"; + break; + case VFL_TYPE_VTX: + base=192; + end=224; + name_base = "vtx"; + break; + case VFL_TYPE_VBI: + base=224; + end=240; + name_base = "vbi"; + break; + case VFL_TYPE_RADIO: + base=64; + end=128; + name_base = "radio"; + break; + default: + return -1; + } + + for(i=base;i<end;i++) + { + if(video_device[i]==NULL) + { + char name[16]; + + video_device[i]=vfd; + vfd->minor=i; + /* The init call may sleep so we book the slot out + then call */ + MOD_INC_USE_COUNT; + if(vfd->initialize) + { + err=vfd->initialize(vfd); + if(err<0) + { + video_device[i]=NULL; + MOD_DEC_USE_COUNT; + return err; + } + } + sprintf (name, "v4l/%s%d", name_base, i - base); + /* + * Start the device root only. Anything else + * has serious privacy issues. + */ + vfd->devfs_handle = + devfs_register (NULL, name, DEVFS_FL_DEFAULT, + VIDEO_MAJOR, vfd->minor, + S_IFCHR | S_IRUSR | S_IWUSR, + &video_fops, NULL); + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + sprintf (name, "%s%d", name_base, i - base); + videodev_proc_create_dev (vfd, name); +#endif + + + return 0; + } + } + return -ENFILE; +} + +/** + * video_unregister_device - unregister a video4linux device + * @vfd: the device to unregister + * + * This unregisters the passed device and deassigns the minor + * number. Future open calls will be met with errors. + */ + +void video_unregister_device(struct video_device *vfd) +{ + if(video_device[vfd->minor]!=vfd) + panic("vfd: bad unregister"); + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + videodev_proc_destroy_dev (vfd); +#endif + + devfs_unregister (vfd->devfs_handle); + video_device[vfd->minor]=NULL; + MOD_DEC_USE_COUNT; +} + + +static struct file_operations video_fops= +{ + owner: THIS_MODULE, + llseek: video_lseek, + read: video_read, + write: video_write, + ioctl: video_ioctl, + mmap: video_mmap, + open: video_open, + release: video_release, + poll: video_poll, +}; + +/* + * Initialise video for linux + */ + +int __init videodev_init(void) +{ + struct video_init *vfli = video_init_list; + + printk(KERN_INFO "Linux video capture interface: v1.00\n"); + if(devfs_register_chrdev(VIDEO_MAJOR,"video_capture", &video_fops)) + { + printk("video_dev: unable to get major %d\n", VIDEO_MAJOR); + return -EIO; + } + + /* + * Init kernel installed video drivers + */ + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + videodev_proc_create (); +#endif + + while(vfli->init!=NULL) + { + vfli->init(vfli); + vfli++; + } + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + return videodev_init(); +} + +void cleanup_module(void) +{ +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + videodev_proc_destroy (); +#endif + + devfs_unregister_chrdev(VIDEO_MAJOR, "video_capture"); +} + +#endif + +EXPORT_SYMBOL(video_register_device); +EXPORT_SYMBOL(video_unregister_device); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("Device registrar for Video4Linux drivers"); diff --git a/drivers/media/video/vino.c b/drivers/media/video/vino.c new file mode 100644 index 000000000..1ef64ae81 --- /dev/null +++ b/drivers/media/video/vino.c @@ -0,0 +1,275 @@ +/* $Id: vino.c,v 1.5 1999/10/09 00:01:14 ralf Exp $ + * drivers/char/vino.c + * + * (incomplete) Driver for the Vino Video input system found in SGI Indys. + * + * Copyright (C) 1999 Ulf Carlsson (ulfc@bun.falkenberg.se) + * + * This isn't complete yet, please don't expect any video until I've written + * some more code. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/videodev.h> + +#include <asm/addrspace.h> +#include <asm/system.h> + +#include "vino.h" + +struct vino_device { + struct video_device vdev; + + unsigned long chan; +#define VINO_CHAN_A 0 +#define VINO_CHAN_B 1 + + unsigned long flags; +#define VINO_DMA_ACTIVE (1<<0) +}; + +/* We can actually receive TV and IndyCam input at the same time. Believe it or + * not.. + */ +static struct vino_device vino[2]; + +/* Those registers have to be accessed by either *one* 64 bit write or *one* 64 + * bit read. We need some asm to fix this. We can't use mips3 as standard + * because we just save 32 bits at context switch. + */ + +static __inline__ unsigned long long vino_reg_read(unsigned long addr) +{ + unsigned long long ret __attribute__ ((aligned (64))); + unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE); + unsigned long flags; + + save_and_cli(flags); + __asm__ __volatile__( + ".set\tmips3\n\t" + ".set\tnoat\n\t" + "ld\t$1,(%0)\n\t" + "sd\t$1,(%1)\n\t" + ".set\tat\n\t" + ".set\tmips0" + : + :"r" (virt_addr), + "r" (&ret) + :"$1"); + restore_flags(flags); + + return ret; +} + +static __inline__ void vino_reg_write(unsigned long long value, + unsigned long addr) +{ + unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE); + unsigned long flags; + + /* we might lose the upper parts of the registers which are not saved + * if there comes an interrupt in our way, play safe */ + + save_and_cli(flags); + __asm__ __volatile__( + ".set\tmips3\n\t" + ".set\tnoat\n\t" + "ld\t$1,(%0)\n\t" + "sd\t$1,(%1)\n\t" + ".set\tat\n\t" + ".set\tmips0" + : + :"r" (&value), + "r" (virt_addr) + :"$1"); + restore_flags(flags); +} + +static __inline__ void vino_reg_and(unsigned long long value, + unsigned long addr) +{ + unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE); + unsigned long flags; + + save_and_cli(flags); + __asm__ __volatile__( + ".set\tmips3\n\t" + ".set\tnoat\n\t" + "ld\t$1,(%0)\n\t" + "ld\t$2,(%1)\n\t" + "and\t$1,$1,$2\n\t" + "sd\t$1,(%0)\n\t" + ".set\tat\n\t" + ".set\tmips0" + : + :"r" (virt_addr), + "r" (&value) + :"$1","$2"); + restore_flags(flags); +} + +static __inline__ void vino_reg_or(unsigned long long value, + unsigned long addr) +{ + unsigned long virt_addr = KSEG1ADDR(addr + VINO_BASE); + unsigned long flags; + + save_and_cli(flags); + __asm__ __volatile__( + ".set\tmips3\n\t" + ".set\tnoat\n\t" + "ld\t$1,(%0)\n\t" + "ld\t$2,(%1)\n\t" + "or\t$1,$1,$2\n\t" + "sd\t$1,(%0)\n\t" + ".set\tat\n\t" + ".set\tmips0" + : + :"r" (virt_addr), + "r" (&value) + :"$1","$2"); + restore_flags(flags); +} + +static int vino_dma_setup(void) +{ + return 0; +} + +static void vino_dma_stop(void) +{ + +} + +static int vino_init(void) +{ + unsigned long ret; + unsigned short rev, id; + unsigned long long foo; + unsigned long *bar; + + bar = (unsigned long *) &foo; + + ret = vino_reg_read(VINO_REVID); + + rev = (ret & VINO_REVID_REV_MASK); + id = (ret & VINO_REVID_ID_MASK) >> 4; + + printk("Vino: ID:%02hx Rev:%02hx\n", id, rev); + + foo = vino_reg_read(VINO_A_DESC_DATA0); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_A_DESC_DATA1); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_A_DESC_DATA2); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_A_DESC_DATA3); + printk("0x%lx", bar[0]); + printk("%lx\n", bar[1]); + foo = vino_reg_read(VINO_B_DESC_DATA0); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_B_DESC_DATA1); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_B_DESC_DATA2); + printk("0x%lx", bar[0]); + printk("%lx ", bar[1]); + foo = vino_reg_read(VINO_B_DESC_DATA3); + printk("0x%lx", bar[0]); + printk("%lx\n", bar[1]); + + return 0; +} + +static void vino_dma_go(struct vino_device *v) +{ + +} + +/* Reset the vino back to default state */ + +static void vino_setup(struct vino_device *v) +{ + +} + +static int vino_open(struct video_device *dev, int flags) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static void vino_close(struct video_device *dev) +{ + MOD_DEC_USE_COUNT; +} + +static int vino_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + return 0; +} + +static int vino_mmap(struct video_device *dev, const char *adr, + unsigned long size) +{ + return 0; +} + +static struct video_device vino_dev = { + "Vino IndyCam/TV", + VID_TYPE_CAPTURE, + VID_HARDWARE_VINO, + vino_open, + vino_close, + NULL, /* vino_read */ + NULL, /* vino_write */ + NULL, /* vino_poll */ + vino_ioctl, + vino_mmap, + NULL, /* vino_init */ + NULL, + 0, + 0 +}; + +int __init init_vino(struct video_device *dev) +{ + int err; + + err = vino_init(); + if (err) + return err; + +#if 0 + if (video_register_device(&vinodev, VFL_TYPE_GRABBER) == -1) { + return -ENODEV; + } +#endif + + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + int err; + + err = vino_init(); + if (err) + return err; + + return 0; +} + +void cleanup_module(void) +{ +} +#endif diff --git a/drivers/media/video/vino.h b/drivers/media/video/vino.h new file mode 100644 index 000000000..8caadc9aa --- /dev/null +++ b/drivers/media/video/vino.h @@ -0,0 +1,118 @@ +/* $Id: vino.h,v 1.1 1999/02/08 18:29:32 ulfc Exp $ + * drivers/sgi/vino.h + * + * Copyright (C) 1999 Ulf Carlsson (ulfc@bun.falkenberg.se) + */ + +#define VINO_BASE 0x0008000 + +#define VINO_REVID 0x0000 +#define VINO_CTRL 0x0008 +#define VINO_INTSTAT 0x0010 /* Interrupt status */ +#define VINO_I2C_CTRL 0x0018 +#define VINO_I2C_DATA 0x0020 +#define VINO_A_ALPHA 0x0028 /* Channel A ... */ +#define VINO_A_CLIPS 0x0030 /* Clipping start */ +#define VINO_A_CLIPE 0x0038 /* Clipping end */ +#define VINO_A_FRAMERT 0x0040 /* Framerate */ +#define VINO_A_FLDCNT 0x0048 /* Field counter */ +#define VINO_A_LNSZ 0x0050 +#define VINO_A_LNCNT 0x0058 +#define VINO_A_PGIX 0x0060 /* Page index */ +#define VINO_A_DESC_PTR 0x0068 /* Ptr to next four descriptors */ +#define VINO_A_DESC_TLB_PTR 0x0070 /* Ptr to start of descriptor table */ +#define VINO_A_DESC_DATA0 0x0078 /* Descriptor data 0 */ +#define VINO_A_DESC_DATA1 0x0080 /* ... */ +#define VINO_A_DESC_DATA2 0x0088 +#define VINO_A_DESC_DATA3 0x0090 +#define VINO_A_FIFO_THRESHOLD 0x0098 /* FIFO threshold */ +#define VINO_A_FIFO_RP 0x00a0 +#define VINO_A_FIFO_WP 0x00a8 +#define VINO_B_ALPHA 0x00b0 /* Channel B ... */ +#define VINO_B_CLIPS 0x00b8 +#define VINO_B_CLIPE 0x00c0 +#define VINO_B_FRAMERT 0x00c8 +#define VINO_B_FLDCNT 0x00d0 +#define VINO_B_LNSZ 0x00d8 +#define VINO_B_LNCNT 0x00e0 +#define VINO_B_PGIX 0x00e8 +#define VINO_B_DESC_PTR 0x00f0 +#define VINO_B_DESC_TLB_PTR 0x00f8 +#define VINO_B_DESC_DATA0 0x0100 +#define VINO_B_DESC_DATA1 0x0108 +#define VINO_B_DESC_DATA2 0x0110 +#define VINO_B_DESC_DATA3 0x0118 +#define VINO_B_FIFO_THRESHOLD 0x0120 +#define VINO_B_FIFO_RP 0x0128 +#define VINO_B_FIFO_WP 0x0130 + +/* Bits in the VINO_REVID register */ + +#define VINO_REVID_REV_MASK 0x000f /* bits 0:3 */ +#define VINO_REVID_ID_MASK 0x00f0 /* bits 4:7 */ + +/* Bits in the VINO_CTRL register */ + +#define VINO_CTRL_LITTLE_ENDIAN (1<<0) +#define VINO_CTRL_A_FIELD_TRANS_INT (1<<1) /* Field transferred int */ +#define VINO_CTRL_A_FIFO_OF_INT (1<<2) /* FIFO overflow int */ +#define VINO_CTRL_A_END_DESC_TBL_INT (1<<3) /* End of desc table int */ +#define VINO_CTRL_B_FIELD_TRANS_INT (1<<4) /* Field transferred int */ +#define VINO_CTRL_B_FIFO_OF_INT (1<<5) /* FIFO overflow int */ +#define VINO_CTRL_B_END_DESC_TLB_INT (1<<6) /* End of desc table int */ +#define VINO_CTRL_A_DMA_ENBL (1<<7) +#define VINO_CTRL_A_INTERLEAVE_ENBL (1<<8) +#define VINO_CTRL_A_SYNC_ENBL (1<<9) +#define VINO_CTRL_A_SELECT (1<<10) /* 1=D1 0=Philips */ +#define VINO_CTRL_A_RGB (1<<11) /* 1=RGB 0=YUV */ +#define VINO_CTRL_A_LUMA_ONLY (1<<12) +#define VINO_CTRL_A_DEC_ENBL (1<<13) /* Decimation */ +#define VINO_CTRL_A_DEC_SCALE_MASK 0x1c000 /* bits 14:17 */ +#define VINO_CTRL_A_DEC_HOR_ONLY (1<<17) /* Horizontal only */ +#define VINO_CTRL_A_DITHER (1<<18) /* 24 -> 8 bit dither */ +#define VINO_CTRL_B_DMA_ENBL (1<<19) +#define VINO_CTRL_B_INTERLEAVE_ENBL (1<<20) +#define VINO_CTRL_B_SYNC_ENBL (1<<21) +#define VINO_CTRL_B_SELECT (1<<22) /* 1=D1 0=Philips */ +#define VINO_CTRL_B_RGB (1<<22) /* 1=RGB 0=YUV */ +#define VINO_CTRL_B_LUMA_ONLY (1<<23) +#define VINO_CTRL_B_DEC_ENBL (1<<24) /* Decimation */ +#define VINO_CTRL_B_DEC_SCALE_MASK 0x1c000000 /* bits 25:28 */ +#define VINO_CTRL_B_DEC_HOR_ONLY (1<<29) /* Decimation horizontal only */ +#define VINO_CTRL_B_DITHER (1<<30) /* ChanB 24 -> 8 bit dither */ + +/* Bits in the Interrupt and Status register */ + +#define VINO_INTSTAT_A_FIELD_TRANS (1<<0) /* Field transferred int */ +#define VINO_INTSTAT_A_FIFO_OF (1<<1) /* FIFO overflow int */ +#define VINO_INTSTAT_A_END_DESC_TBL (1<<2) /* End of desc table int */ +#define VINO_INTSTAT_B_FIELD_TRANS (1<<3) /* Field transferred int */ +#define VINO_INTSTAT_B_FIFO_OF (1<<4) /* FIFO overflow int */ +#define VINO_INTSTAT_B_END_DESC_TBL (1<<5) /* End of desc table int */ + +/* Bits in the Clipping Start register */ + +#define VINO_CLIPS_START 0x3ff /* bits 0:9 */ +#define VINO_CLIPS_ODD_MASK 0x7fc00 /* bits 10:18 */ +#define VINO_CLIPS_EVEN_MASK 0xff80000 /* bits 19:27 */ + +/* Bits in the Clipping End register */ + +#define VINO_CLIPE_END 0x3ff /* bits 0:9 */ +#define VINO_CLIPE_ODD_MASK 0x7fc00 /* bits 10:18 */ +#define VINO_CLIPE_EVEN_MASK 0xff80000 /* bits 19:27 */ + +/* Bits in the Frame Rate register */ + +#define VINO_FRAMERT_PAL (1<<0) /* 0=NTSC 1=PAL */ +#define VINO_FRAMERT_RT_MASK 0x1ffe /* bits 1:12 */ + +/* Bits in the VINO_I2C_CTRL */ + +#define VINO_CTRL_I2C_IDLE (1<<0) /* write: 0=force idle + * read: 0=idle 1=not idle */ +#define VINO_CTRL_I2C_DIR (1<<1) /* 0=read 1=write */ +#define VINO_CTRL_I2C_MORE_BYTES (1<<2) /* 0=last byte 1=more bytes */ +#define VINO_CTRL_I2C_TRANS_BUSY (1<<4) /* 0=trans done 1=trans busy */ +#define VINO_CTRL_I2C_ACK (1<<5) /* 0=ack received 1=ack not */ +#define VINO_CTRL_I2C_BUS_ERROR (1<<7) /* 0=no bus err 1=bus err */ diff --git a/drivers/media/video/zr36057.h b/drivers/media/video/zr36057.h new file mode 100644 index 000000000..b672357bc --- /dev/null +++ b/drivers/media/video/zr36057.h @@ -0,0 +1,168 @@ +/* + zr36057.h - zr36057 register offsets + + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _ZR36057_H_ +#define _ZR36057_H_ + + +/* Zoran ZR36057 registers */ + +#define ZR36057_VFEHCR 0x000 /* Video Front End, Horizontal Configuration Register */ +#define ZR36057_VFEHCR_HSPol (1<<30) +#define ZR36057_VFEHCR_HStart 10 +#define ZR36057_VFEHCR_HEnd 0 +#define ZR36057_VFEHCR_Hmask 0x3ff + +#define ZR36057_VFEVCR 0x004 /* Video Front End, Vertical Configuration Register */ +#define ZR36057_VFEVCR_VSPol (1<<30) +#define ZR36057_VFEVCR_VStart 10 +#define ZR36057_VFEVCR_VEnd 0 +#define ZR36057_VFEVCR_Vmask 0x3ff + +#define ZR36057_VFESPFR 0x008 /* Video Front End, Scaler and Pixel Format Register */ +#define ZR36057_VFESPFR_ExtFl (1<<26) +#define ZR36057_VFESPFR_TopField (1<<25) +#define ZR36057_VFESPFR_VCLKPol (1<<24) +#define ZR36057_VFESPFR_HFilter 21 +#define ZR36057_VFESPFR_HorDcm 14 +#define ZR36057_VFESPFR_VerDcm 8 +#define ZR36057_VFESPFR_DispMode 6 +#define ZR36057_VFESPFR_YUV422 (0<<3) +#define ZR36057_VFESPFR_RGB888 (1<<3) +#define ZR36057_VFESPFR_RGB565 (2<<3) +#define ZR36057_VFESPFR_RGB555 (3<<3) +#define ZR36057_VFESPFR_ErrDif (1<<2) +#define ZR36057_VFESPFR_Pack24 (1<<1) +#define ZR36057_VFESPFR_LittleEndian (1<<0) + +#define ZR36057_VDTR 0x00c /* Video Display "Top" Register */ + +#define ZR36057_VDBR 0x010 /* Video Display "Bottom" Register */ + +#define ZR36057_VSSFGR 0x014 /* Video Stride, Status, and Frame Grab Register */ +#define ZR36057_VSSFGR_DispStride 16 +#define ZR36057_VSSFGR_VidOvf (1<<8) +#define ZR36057_VSSFGR_SnapShot (1<<1) +#define ZR36057_VSSFGR_FrameGrab (1<<0) + +#define ZR36057_VDCR 0x018 /* Video Display Configuration Register */ +#define ZR36057_VDCR_VidEn (1<<31) +#define ZR36057_VDCR_MinPix 24 +#define ZR36057_VDCR_Triton (1<<24) +#define ZR36057_VDCR_VidWinHt 12 +#define ZR36057_VDCR_VidWinWid 0 + +#define ZR36057_MMTR 0x01c /* Masking Map "Top" Register */ + +#define ZR36057_MMBR 0x020 /* Masking Map "Bottom" Register */ + +#define ZR36057_OCR 0x024 /* Overlay Control Register */ +#define ZR36057_OCR_OvlEnable (1 << 15) +#define ZR36057_OCR_MaskStride 0 + +#define ZR36057_SPGPPCR 0x028 /* System, PCI, and General Purpose Pins Control Register */ +#define ZR36057_SPGPPCR_SoftReset (1<<24) + +#define ZR36057_GPPGCR1 0x02c /* General Purpose Pins and GuestBus Control Register (1) */ + +#define ZR36057_MCSAR 0x030 /* MPEG Code Source Address Register */ + +#define ZR36057_MCTCR 0x034 /* MPEG Code Transfer Control Register */ +#define ZR36057_MCTCR_CodTime (1 << 30) +#define ZR36057_MCTCR_CEmpty (1 << 29) +#define ZR36057_MCTCR_CFlush (1 << 28) +#define ZR36057_MCTCR_CodGuestID 20 +#define ZR36057_MCTCR_CodGuestReg 16 + +#define ZR36057_MCMPR 0x038 /* MPEG Code Memory Pointer Register */ + +#define ZR36057_ISR 0x03c /* Interrupt Status Register */ +#define ZR36057_ISR_GIRQ1 (1<<30) +#define ZR36057_ISR_GIRQ0 (1<<29) +#define ZR36057_ISR_CodRepIRQ (1<<28) +#define ZR36057_ISR_JPEGRepIRQ (1<<27) + +#define ZR36057_ICR 0x040 /* Interrupt Control Register */ +#define ZR36057_ICR_GIRQ1 (1<<30) +#define ZR36057_ICR_GIRQ0 (1<<29) +#define ZR36057_ICR_CodRepIRQ (1<<28) +#define ZR36057_ICR_JPEGRepIRQ (1<<27) +#define ZR36057_ICR_IntPinEn (1<<24) + +#define ZR36057_I2CBR 0x044 /* I2C Bus Register */ +#define ZR36057_I2CBR_SDA (1<<1) +#define ZR36057_I2CBR_SCL (1<<0) + +#define ZR36057_JMC 0x100 /* JPEG Mode and Control */ +#define ZR36057_JMC_JPG (1 << 31) +#define ZR36057_JMC_JPGExpMode (0 << 29) +#define ZR36057_JMC_JPGCmpMode (1 << 29) +#define ZR36057_JMC_MJPGExpMode (2 << 29) +#define ZR36057_JMC_MJPGCmpMode (3 << 29) +#define ZR36057_JMC_RTBUSY_FB (1 << 6) +#define ZR36057_JMC_Go_en (1 << 5) +#define ZR36057_JMC_SyncMstr (1 << 4) +#define ZR36057_JMC_Fld_per_buff (1 << 3) +#define ZR36057_JMC_VFIFO_FB (1 << 2) +#define ZR36057_JMC_CFIFO_FB (1 << 1) +#define ZR36057_JMC_Stll_LitEndian (1 << 0) + +#define ZR36057_JPC 0x104 /* JPEG Process Control */ +#define ZR36057_JPC_P_Reset (1 << 7) +#define ZR36057_JPC_CodTrnsEn (1 << 5) +#define ZR36057_JPC_Active (1 << 0) + +#define ZR36057_VSP 0x108 /* Vertical Sync Parameters */ +#define ZR36057_VSP_VsyncSize 16 +#define ZR36057_VSP_FrmTot 0 + +#define ZR36057_HSP 0x10c /* Horizontal Sync Parameters */ +#define ZR36057_HSP_HsyncStart 16 +#define ZR36057_HSP_LineTot 0 + +#define ZR36057_FHAP 0x110 /* Field Horizontal Active Portion */ +#define ZR36057_FHAP_NAX 16 +#define ZR36057_FHAP_PAX 0 + +#define ZR36057_FVAP 0x114 /* Field Vertical Active Portion */ +#define ZR36057_FVAP_NAY 16 +#define ZR36057_FVAP_PAY 0 + +#define ZR36057_FPP 0x118 /* Field Process Parameters */ +#define ZR36057_FPP_Odd_Even (1 << 0) + +#define ZR36057_JCBA 0x11c /* JPEG Code Base Address */ + +#define ZR36057_JCFT 0x120 /* JPEG Code FIFO Threshold */ + +#define ZR36057_JCGI 0x124 /* JPEG Codec Guest ID */ +#define ZR36057_JCGI_JPEGuestID 4 +#define ZR36057_JCGI_JPEGuestReg 0 + +#define ZR36057_GCR2 0x12c /* GuestBus Control Register (2) */ + +#define ZR36057_POR 0x200 /* Post Office Register */ +#define ZR36057_POR_POPen (1<<25) +#define ZR36057_POR_POTime (1<<24) +#define ZR36057_POR_PODir (1<<23) + +#define ZR36057_STR 0x300 /* "Still" Transfer Register */ + +#endif diff --git a/drivers/media/video/zr36060.h b/drivers/media/video/zr36060.h new file mode 100644 index 000000000..d94f56b0b --- /dev/null +++ b/drivers/media/video/zr36060.h @@ -0,0 +1,35 @@ +/* + zr36060.h - zr36060 register offsets + + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _ZR36060_H_ +#define _ZR36060_H_ + + +/* Zoran ZR36060 registers */ + +#define ZR36060_LoadParameters 0x000 +#define ZR36060_Load (1<<7) +#define ZR36060_SyncRst (1<<0) + +#define ZR36060_CodeFifoStatus 0x001 +#define ZR36060_Load (1<<7) +#define ZR36060_SyncRst (1<<0) + +#endif diff --git a/drivers/media/video/zr36120.c b/drivers/media/video/zr36120.c new file mode 100644 index 000000000..6e860021e --- /dev/null +++ b/drivers/media/video/zr36120.c @@ -0,0 +1,2086 @@ +/* + zr36120.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/malloc.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/signal.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <linux/video_decoder.h> +#include <asm/segment.h> + +#include <linux/version.h> +#include <asm/uaccess.h> + +#include "tuner.h" +#include "zr36120.h" +#include "zr36120_mem.h" + +/* mark an required function argument unused - lintism */ +#define UNUSED(x) (void)(x) + +/* sensible default */ +#ifndef CARDTYPE +#define CARDTYPE 0 +#endif + +/* Anybody who uses more than four? */ +#define ZORAN_MAX 4 + +static unsigned int triton1=0; /* triton1 chipset? */ +static unsigned int cardtype[ZORAN_MAX]={ [ 0 ... ZORAN_MAX-1 ] = CARDTYPE }; + +MODULE_AUTHOR("Pauline Middelink <middelin@polyware.nl>"); +MODULE_DESCRIPTION("Zoran ZR36120 based framegrabber"); +MODULE_PARM(triton1,"i"); +MODULE_PARM(cardtype,"1-" __MODULE_STRING(ZORAN_MAX) "i"); + +static int zoran_cards; +static struct zoran zorans[ZORAN_MAX]; + +/* + * the meaning of each element can be found in zr36120.h + * Determining the value of gpdir/gpval can be tricky. The + * best way is to run the card under the original software + * and read the values from the general purpose registers + * 0x28 and 0x2C. How you do that is left as an exercise + * to the impatient reader :) + */ +#define T 1 /* to seperate the bools from the ints */ +#define F 0 +static struct tvcard tvcards[] = { + /* reported working by <middelin@polyware.nl> */ +/*0*/ { "Trust Victor II", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by <Michael.Paxton@aihw.gov.au> */ +/*1*/ { "Aitech WaveWatcher TV-PCI", + 3, 0, T, F, T, T, 0x7F, 0x80, { 1, TUNER(3), SVHS(6) }, { 0 } }, + /* reported working by ? */ +/*2*/ { "Genius Video Wonder PCI Video Capture Card", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by <Pascal.Gabriel@wanadoo.fr> */ +/*3*/ { "Guillemot Maxi-TV PCI", + 2, 0, T, T, T, T, 0x7F, 0x80, { 1, SVHS(6) }, { 0 } }, + /* reported working by "Craig Whitmore <lennon@igrin.co.nz> */ +/*4*/ { "Quadrant Buster", + 3, 3, T, F, T, T, 0x7F, 0x80, { SVHS(1), TUNER(2), 3 }, { 1, 2, 3 } }, + /* a debug entry which has all inputs mapped */ +/*5*/ { "ZR36120 based framegrabber (all inputs enabled)", + 6, 0, T, T, T, T, 0x7F, 0x80, { 1, 2, 3, 4, 5, 6 }, { 0 } } +}; +#undef T +#undef F +#define NRTVCARDS (sizeof(tvcards)/sizeof(tvcards[0])) + +#ifdef __sparc__ +#define ENDIANESS 0 +#else +#define ENDIANESS ZORAN_VFEC_LE +#endif + +static struct { const char name[8]; uint mode; uint bpp; } palette2fmt[] = { +/* n/a */ { "n/a", 0, 0 }, +/* GREY */ { "GRAY", 0, 0 }, +/* HI240 */ { "HI240", 0, 0 }, +/* RGB565 */ { "RGB565", ZORAN_VFEC_RGB_RGB565|ENDIANESS, 2 }, +/* RGB24 */ { "RGB24", ZORAN_VFEC_RGB_RGB888|ENDIANESS|ZORAN_VFEC_PACK24, 3 }, +/* RGB32 */ { "RGB32", ZORAN_VFEC_RGB_RGB888|ENDIANESS, 4 }, +/* RGB555 */ { "RGB555", ZORAN_VFEC_RGB_RGB555|ENDIANESS, 2 }, +/* YUV422 */ { "YUV422", ZORAN_VFEC_RGB_YUV422|ENDIANESS, 2 }, +/* YUYV */ { "YUYV", 0, 0 }, +/* UYVY */ { "UYVY", 0, 0 }, +/* YUV420 */ { "YUV420", 0, 0 }, +/* YUV411 */ { "YUV411", 0, 0 }, +/* RAW */ { "RAW", 0, 0 }, +/* YUV422P */ { "YUV422P", 0, 0 }, +/* YUV411P */ { "YUV411P", 0, 0 }}; +#define NRPALETTES (sizeof(palette2fmt)/sizeof(palette2fmt[0])) +#undef ENDIANESS + +/* ----------------------------------------------------------------------- */ +/* ZORAN chipset detector */ +/* shamelessly stolen from bttv.c */ +/* Reason for beeing here: we need to detect if we are running on a */ +/* Triton based chipset, and if so, enable a certain bit */ +/* ----------------------------------------------------------------------- */ +static +void __init handle_chipset(void) +{ + struct pci_dev *dev = NULL; + + /* Just in case some nut set this to something dangerous */ + if (triton1) + triton1 = ZORAN_VDC_TRICOM; + + while ((dev = pci_find_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82437, dev))) + { + printk(KERN_INFO "zoran: Host bridge 82437FX Triton PIIX\n"); + triton1 = ZORAN_VDC_TRICOM; + } +} + +/* ----------------------------------------------------------------------- */ +/* ZORAN functions */ +/* ----------------------------------------------------------------------- */ + +static void zoran_set_geo(struct zoran* ztv, struct vidinfo* i); + +#if 0 /* unused */ +static +void zoran_dump(struct zoran *ztv) +{ + char str[256]; + char *p=str; /* shut up, gcc! */ + int i; + + for (i=0; i<0x60; i+=4) { + if ((i % 16) == 0) { + if (i) printk("%s\n",str); + p = str; + p+= sprintf(str, KERN_DEBUG " %04x: ",i); + } + p += sprintf(p, "%08x ",zrread(i)); + } +} +#endif /* unused */ + +static +void reap_states(struct zoran* ztv) +{ + /* count frames */ + ztv->fieldnr++; + + /* + * Are we busy at all? + * This depends on if there is a workqueue AND the + * videotransfer is enabled on the chip... + */ + if (ztv->workqueue && (zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN)) + { + struct vidinfo* newitem; + + /* did we get a complete frame? */ + if (zrread(ZORAN_VSTR) & ZORAN_VSTR_GRAB) + return; + +DEBUG(printk(CARD_DEBUG "completed %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue)); + + /* we are done with this buffer, tell everyone */ + ztv->workqueue->status = FBUFFER_DONE; + ztv->workqueue->fieldnr = ztv->fieldnr; + /* not good, here for BTTV_FIELDNR reasons */ + ztv->lastfieldnr = ztv->fieldnr; + + switch (ztv->workqueue->kindof) { + case FBUFFER_GRAB: + wake_up_interruptible(&ztv->grabq); + break; + case FBUFFER_VBI: + wake_up_interruptible(&ztv->vbiq); + break; + default: + printk(CARD_INFO "somebody killed the workqueue (kindof=%d)!\n",CARD,ztv->workqueue->kindof); + } + + /* item completed, skip to next item in queue */ + write_lock(&ztv->lock); + newitem = ztv->workqueue->next; + ztv->workqueue->next = 0; /* mark completed */ + ztv->workqueue = newitem; + write_unlock(&ztv->lock); + } + + /* + * ok, so it seems we have nothing in progress right now. + * Lets see if we can find some work. + */ + if (ztv->workqueue) + { + struct vidinfo* newitem; +again: + +DEBUG(printk(CARD_DEBUG "starting %s at %p\n",CARD,ztv->workqueue->kindof==FBUFFER_GRAB?"grab":"read",ztv->workqueue)); + + /* loadup the frame settings */ + read_lock(&ztv->lock); + zoran_set_geo(ztv,ztv->workqueue); + read_unlock(&ztv->lock); + + switch (ztv->workqueue->kindof) { + case FBUFFER_GRAB: + case FBUFFER_VBI: + zrand(~ZORAN_OCR_OVLEN, ZORAN_OCR); + zror(ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR); + zror(ZORAN_VDC_VIDEN,ZORAN_VDC); + + /* start single-shot grab */ + zror(ZORAN_VSTR_GRAB, ZORAN_VSTR); + break; + default: + printk(CARD_INFO "what is this doing on the queue? (kindof=%d)\n",CARD,ztv->workqueue->kindof); + write_lock(&ztv->lock); + newitem = ztv->workqueue->next; + ztv->workqueue->next = 0; + ztv->workqueue = newitem; + write_unlock(&ztv->lock); + if (newitem) + goto again; /* yeah, sure.. */ + } + /* bye for now */ + return; + } +DEBUG(printk(CARD_DEBUG "nothing in queue\n",CARD)); + + /* + * What? Even the workqueue is empty? Am i really here + * for nothing? Did i come all that way to... do nothing? + */ + + /* do we need to overlay? */ + if (test_bit(STATE_OVERLAY, &ztv->state)) + { + /* are we already overlaying? */ + if (!(zrread(ZORAN_OCR) & ZORAN_OCR_OVLEN) || + !(zrread(ZORAN_VDC) & ZORAN_VDC_VIDEN)) + { +DEBUG(printk(CARD_DEBUG "starting overlay\n",CARD)); + + read_lock(&ztv->lock); + zoran_set_geo(ztv,&ztv->overinfo); + read_unlock(&ztv->lock); + + zror(ZORAN_OCR_OVLEN, ZORAN_OCR); + zrand(~ZORAN_VSTR_SNAPSHOT,ZORAN_VSTR); + zror(ZORAN_VDC_VIDEN,ZORAN_VDC); + } + + /* + * leave overlaying on, but turn interrupts off. + */ + zrand(~ZORAN_ICR_EN,ZORAN_ICR); + return; + } + + /* do we have any VBI idle time processing? */ + if (test_bit(STATE_VBI, &ztv->state)) + { + struct vidinfo* item; + struct vidinfo* lastitem; + + /* protect the workqueue */ + write_lock(&ztv->lock); + lastitem = ztv->workqueue; + if (lastitem) + while (lastitem->next) lastitem = lastitem->next; + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + if (item->next == 0 && item->status == FBUFFER_FREE) + { +DEBUG(printk(CARD_DEBUG "%p added to queue\n",CARD,item)); + item->status = FBUFFER_BUSY; + if (!lastitem) + ztv->workqueue = item; + else + lastitem->next = item; + lastitem = item; + } + write_unlock(&ztv->lock); + if (ztv->workqueue) + goto again; /* hey, _i_ graduated :) */ + } + + /* + * Then we must be realy IDLE + */ +DEBUG(printk(CARD_DEBUG "turning off\n",CARD)); + /* nothing further to do, disable DMA and further IRQs */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + zrand(~ZORAN_ICR_EN,ZORAN_ICR); +} + +static +void zoran_irq(int irq, void *dev_id, struct pt_regs * regs) +{ + u32 stat,estat; + int count = 0; + struct zoran *ztv = (struct zoran *)dev_id; + + UNUSED(irq); UNUSED(regs); + for (;;) { + /* get/clear interrupt status bits */ + stat=zrread(ZORAN_ISR); + estat=stat & zrread(ZORAN_ICR); + if (!estat) + return; + zrwrite(estat,ZORAN_ISR); + IDEBUG(printk(CARD_DEBUG "estat %08x\n",CARD,estat)); + IDEBUG(printk(CARD_DEBUG " stat %08x\n",CARD,stat)); + + if (estat & ZORAN_ISR_CODE) + { + IDEBUG(printk(CARD_DEBUG "CodReplIRQ\n",CARD)); + } + if (estat & ZORAN_ISR_GIRQ0) + { + IDEBUG(printk(CARD_DEBUG "GIRQ0\n",CARD)); + if (!ztv->card->usegirq1) + reap_states(ztv); + } + if (estat & ZORAN_ISR_GIRQ1) + { + IDEBUG(printk(CARD_DEBUG "GIRQ1\n",CARD)); + if (ztv->card->usegirq1) + reap_states(ztv); + } + + count++; + if (count > 10) + printk(CARD_ERR "irq loop %d (%x)\n",CARD,count,estat); + if (count > 20) + { + zrwrite(0, ZORAN_ICR); + printk(CARD_ERR "IRQ lockup, cleared int mask\n",CARD); + } + } +} + +static +int zoran_muxsel(struct zoran* ztv, int channel, int norm) +{ + int rv; + + /* set the new video norm */ + rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_NORM, &norm); + if (rv) + return rv; + ztv->norm = norm; + + /* map the given channel to the cards decoder's channel */ + channel = ztv->card->video_mux[channel] & CHANNEL_MASK; + + /* set the new channel */ + rv = i2c_control_device(&(ztv->i2c), I2C_DRIVERID_VIDEODECODER, DECODER_SET_INPUT, &channel); + return rv; +} + +/* Tell the interrupt handler what to to. */ +static +void zoran_cap(struct zoran* ztv, int on) +{ +DEBUG(printk(CARD_DEBUG "zoran_cap(%d) state=%x\n",CARD,on,ztv->state)); + + if (on) { + ztv->running = 1; + + /* + * turn interrupts (back) on. The DMA will be enabled + * inside the irq handler when it detects a restart. + */ + zror(ZORAN_ICR_EN,ZORAN_ICR); + } + else { + /* + * turn both interrupts and DMA off + */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + zrand(~ZORAN_ICR_EN,ZORAN_ICR); + + ztv->running = 0; + } +} + +static ulong dmask[] = { + 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8, + 0xFFFFFFF0, 0xFFFFFFE0, 0xFFFFFFC0, 0xFFFFFF80, + 0xFFFFFF00, 0xFFFFFE00, 0xFFFFFC00, 0xFFFFF800, + 0xFFFFF000, 0xFFFFE000, 0xFFFFC000, 0xFFFF8000, + 0xFFFF0000, 0xFFFE0000, 0xFFFC0000, 0xFFF80000, + 0xFFF00000, 0xFFE00000, 0xFFC00000, 0xFF800000, + 0xFF000000, 0xFE000000, 0xFC000000, 0xF8000000, + 0xF0000000, 0xE0000000, 0xC0000000, 0x80000000 +}; + +static +void zoran_built_overlay(struct zoran* ztv, int count, struct video_clip *vcp) +{ + ulong* mtop; + int ystep = (ztv->vidXshift + ztv->vidWidth+31)/32; /* next DWORD */ + int i; + +DEBUG(printk(KERN_DEBUG " overlay at %p, ystep=%d, clips=%d\n",ztv->overinfo.overlay,ystep,count)); + + for (i=0; i<count; i++) { + struct video_clip *vp = vcp+i; + UNUSED(vp); +DEBUG(printk(KERN_DEBUG " %d: clip(%d,%d,%d,%d)\n", i,vp->x,vp->y,vp->width,vp->height)); + } + + /* + * activate the visible portion of the screen + * Note we take some shortcuts here, because we + * know the width can never be < 32. (I.e. a DWORD) + * We also assume the overlay starts somewhere in + * the FIRST dword. + */ + { + int start = ztv->vidXshift; + ulong firstd = dmask[start]; + ulong lastd = ~dmask[(start + ztv->overinfo.w) & 31]; + mtop = ztv->overinfo.overlay; + for (i=0; i<ztv->overinfo.h; i++) { + int w = ztv->vidWidth; + ulong* line = mtop; + if (start & 31) { + *line++ = firstd; + w -= 32-(start&31); + } + memset(line, ~0, w/8); + if (w & 31) + line[w/32] = lastd; + mtop += ystep; + } + } + + /* process clipping regions */ + for (i=0; i<count; i++) { + int h; + if (vcp->x < 0 || (uint)vcp->x > ztv->overinfo.w || + vcp->y < 0 || vcp->y > ztv->overinfo.h || + vcp->width < 0 || (uint)(vcp->x+vcp->width) > ztv->overinfo.w || + vcp->height < 0 || (vcp->y+vcp->height) > ztv->overinfo.h) + { + DEBUG(printk(CARD_DEBUG "illegal clipzone (%d,%d,%d,%d) not in (0,0,%d,%d), adapting\n",CARD,vcp->x,vcp->y,vcp->width,vcp->height,ztv->overinfo.w,ztv->overinfo.h)); + if (vcp->x < 0) vcp->x = 0; + if ((uint)vcp->x > ztv->overinfo.w) vcp->x = ztv->overinfo.w; + if (vcp->y < 0) vcp->y = 0; + if (vcp->y > ztv->overinfo.h) vcp->y = ztv->overinfo.h; + if (vcp->width < 0) vcp->width = 0; + if ((uint)(vcp->x+vcp->width) > ztv->overinfo.w) vcp->width = ztv->overinfo.w - vcp->x; + if (vcp->height < 0) vcp->height = 0; + if (vcp->y+vcp->height > ztv->overinfo.h) vcp->height = ztv->overinfo.h - vcp->y; +// continue; + } + + mtop = &ztv->overinfo.overlay[vcp->y*ystep]; + for (h=0; h<=vcp->height; h++) { + int w; + int x = ztv->vidXshift + vcp->x; + for (w=0; w<=vcp->width; w++) { + clear_bit(x&31, &mtop[x/32]); + x++; + } + mtop += ystep; + } + ++vcp; + } + + mtop = ztv->overinfo.overlay; + zrwrite(virt_to_bus(mtop), ZORAN_MTOP); + zrwrite(virt_to_bus(mtop+ystep), ZORAN_MBOT); + zraor((ztv->vidInterlace*ystep)<<0,~ZORAN_OCR_MASKSTRIDE,ZORAN_OCR); +} + +struct tvnorm +{ + u16 Wt, Wa, Ht, Ha, HStart, VStart; +}; + +static struct tvnorm tvnorms[] = { + /* PAL-BDGHI */ +/* { 864, 720, 625, 576, 131, 21 },*/ +/*00*/ { 864, 768, 625, 576, 81, 17 }, + /* NTSC */ +/*01*/ { 858, 720, 525, 480, 121, 10 }, + /* SECAM */ +/*02*/ { 864, 720, 625, 576, 131, 21 }, + /* BW50 */ +/*03*/ { 864, 720, 625, 576, 131, 21 }, + /* BW60 */ +/*04*/ { 858, 720, 525, 480, 121, 10 } +}; +#define TVNORMS (sizeof(tvnorms)/sizeof(tvnorm)) + +/* + * Program the chip for a setup as described in the vidinfo struct. + * + * Side-effects: calculates vidXshift, vidInterlace, + * vidHeight, vidWidth which are used in a later stage + * to calculate the overlay mask + * + * This is an internal function, as such it does not check the + * validity of the struct members... Spectaculair crashes will + * follow /very/ quick when you're wrong and the chip right :) + */ +static +void zoran_set_geo(struct zoran* ztv, struct vidinfo* i) +{ + ulong top, bot; + int stride; + int winWidth, winHeight; + int maxWidth, maxHeight, maxXOffset, maxYOffset; + long vfec; + +DEBUG(printk(CARD_DEBUG "set_geo(rect=(%d,%d,%d,%d), norm=%d, format=%d, bpp=%d, bpl=%d, busadr=%lx, overlay=%p)\n",CARD,i->x,i->y,i->w,i->h,ztv->norm,i->format,i->bpp,i->bpl,i->busadr,i->overlay)); + + /* + * make sure the DMA transfers are inhibited during our + * reprogramming of the chip + */ + zrand(~ZORAN_VDC_VIDEN,ZORAN_VDC); + + maxWidth = tvnorms[ztv->norm].Wa; + maxHeight = tvnorms[ztv->norm].Ha/2; + maxXOffset = tvnorms[ztv->norm].HStart; + maxYOffset = tvnorms[ztv->norm].VStart; + + /* setup vfec register (keep ExtFl,TopField and VCLKPol settings) */ + vfec = (zrread(ZORAN_VFEC) & (ZORAN_VFEC_EXTFL|ZORAN_VFEC_TOPFIELD|ZORAN_VFEC_VCLKPOL)) | + (palette2fmt[i->format].mode & (ZORAN_VFEC_RGB|ZORAN_VFEC_ERRDIF|ZORAN_VFEC_LE|ZORAN_VFEC_PACK24)); + + /* + * Set top, bottom ptrs. Since these must be DWORD aligned, + * possible adjust the x and the width of the window. + * so the endposition stay the same. The vidXshift will make + * sure we are not writing pixels before the requested x. + */ + ztv->vidXshift = 0; + winWidth = i->w; + if (winWidth < 0) + winWidth = -winWidth; + top = i->busadr + i->x*i->bpp + i->y*i->bpl; + if (top & 3) { + ztv->vidXshift = (top & 3) / i->bpp; + winWidth += ztv->vidXshift; + DEBUG(printk(KERN_DEBUG " window-x shifted %d pixels left\n",ztv->vidXshift)); + top &= ~3; + } + + /* + * bottom points to next frame but in interleaved mode we want + * to 'mix' the 2 frames to one capture, so 'bot' points to one + * (physical) line below the top line. + */ + bot = top + i->bpl; + zrwrite(top,ZORAN_VTOP); + zrwrite(bot,ZORAN_VBOT); + + /* + * Make sure the winWidth is DWORD aligned too, + * thereby automaticly making sure the stride to the + * next line is DWORD aligned too (as required by spec). + */ + if ((winWidth*i->bpp) & 3) { +DEBUG(printk(KERN_DEBUG " window-width enlarged by %d pixels\n",(winWidth*i->bpp) & 3)); + winWidth += (winWidth*i->bpp) & 3; + } + + /* determine the DispMode and stride */ + if (i->h >= 0 && i->h <= maxHeight) { + /* single frame grab suffices for this height. */ + vfec |= ZORAN_VFEC_DISPMOD; + ztv->vidInterlace = 0; + stride = i->bpl - (winWidth*i->bpp); + winHeight = i->h; + } + else { + /* interleaving needed for this height */ + ztv->vidInterlace = 1; + stride = i->bpl*2 - (winWidth*i->bpp); + winHeight = i->h/2; + } + if (winHeight < 0) /* can happen for VBI! */ + winHeight = -winHeight; + + /* safety net, sometimes bpl is too short??? */ + if (stride<0) { +DEBUG(printk(CARD_DEBUG "WARNING stride = %d\n",CARD,stride)); + stride = 0; + } + + zraor((winHeight<<12)|(winWidth<<0),~(ZORAN_VDC_VIDWINHT|ZORAN_VDC_VIDWINWID), ZORAN_VDC); + zraor(stride<<16,~ZORAN_VSTR_DISPSTRIDE,ZORAN_VSTR); + + /* remember vidWidth, vidHeight for overlay calculations */ + ztv->vidWidth = winWidth; + ztv->vidHeight = winHeight; +DEBUG(printk(KERN_DEBUG " top=%08lx, bottom=%08lx\n",top,bot)); +DEBUG(printk(KERN_DEBUG " winWidth=%d, winHeight=%d\n",winWidth,winHeight)); +DEBUG(printk(KERN_DEBUG " maxWidth=%d, maxHeight=%d\n",maxWidth,maxHeight)); +DEBUG(printk(KERN_DEBUG " stride=%d\n",stride)); + + /* + * determine horizontal scales and crops + */ + if (i->w < 0) { + int Hstart = 1; + int Hend = Hstart + winWidth; +DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Hstart, Hend)); + zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH); + } + else { + int Wa = maxWidth; + int X = (winWidth*64+Wa-1)/Wa; + int We = winWidth*64/X; + int HorDcm = 64-X; + int hcrop1 = 2*(Wa-We)/4; + /* + * BUGFIX: Juha Nurmela <junki@qn-lpr2-165.quicknet.inet.fi> + * found the solution to the color phase shift. + * See ChangeLog for the full explanation) + */ + int Hstart = (maxXOffset + hcrop1) | 1; + int Hend = Hstart + We - 1; + +DEBUG(printk(KERN_DEBUG " X: scale=%d, start=%d, end=%d\n", HorDcm, Hstart, Hend)); + + zraor((Hstart<<10)|(Hend<<0),~(ZORAN_VFEH_HSTART|ZORAN_VFEH_HEND),ZORAN_VFEH); + vfec |= HorDcm<<14; + + if (HorDcm<16) + vfec |= ZORAN_VFEC_HFILTER_1; /* no filter */ + else if (HorDcm<32) + vfec |= ZORAN_VFEC_HFILTER_3; /* 3 tap filter */ + else if (HorDcm<48) + vfec |= ZORAN_VFEC_HFILTER_4; /* 4 tap filter */ + else vfec |= ZORAN_VFEC_HFILTER_5; /* 5 tap filter */ + } + + /* + * Determine vertical scales and crops + * + * when height is negative, we want to read starting at line 0 + * One day someone might need access to these lines... + */ + if (i->h < 0) { + int Vstart = 0; + int Vend = Vstart + winHeight; +DEBUG(printk(KERN_DEBUG " Y: scale=0, start=%d, end=%d\n", Vstart, Vend)); + zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV); + } + else { + int Ha = maxHeight; + int Y = (winHeight*64+Ha-1)/Ha; + int He = winHeight*64/Y; + int VerDcm = 64-Y; + int vcrop1 = 2*(Ha-He)/4; + int Vstart = maxYOffset + vcrop1; + int Vend = Vstart + He - 1; + +DEBUG(printk(KERN_DEBUG " Y: scale=%d, start=%d, end=%d\n", VerDcm, Vstart, Vend)); + zraor((Vstart<<10)|(Vend<<0),~(ZORAN_VFEV_VSTART|ZORAN_VFEV_VEND),ZORAN_VFEV); + vfec |= VerDcm<<8; + } + +DEBUG(printk(KERN_DEBUG " F: format=%d(=%s)\n",i->format,palette2fmt[i->format].name)); + + /* setup the requested format */ + zrwrite(vfec, ZORAN_VFEC); +} + +static +void zoran_common_open(struct zoran* ztv, int flags) +{ + UNUSED(flags); + + /* already opened? */ + if (ztv->users++ != 0) + return; + + /* unmute audio */ + /* /what/ audio? */ + + ztv->state = 0; + + /* setup the encoder to the initial values */ + ztv->picture.colour=254<<7; + ztv->picture.brightness=128<<8; + ztv->picture.hue=128<<8; + ztv->picture.contrast=216<<7; + i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &ztv->picture); + + /* default to the composite input since my camera is there */ + zoran_muxsel(ztv, 0, VIDEO_MODE_PAL); +} + +static +void zoran_common_close(struct zoran* ztv) +{ + if (--ztv->users != 0) + return; + + /* mute audio */ + /* /what/ audio? */ + + /* stop the chip */ + zoran_cap(ztv, 0); +} + +/* + * Open a zoran card. Right now the flags are just a hack + */ +static int zoran_open(struct video_device *dev, int flags) +{ + struct zoran *ztv = (struct zoran*)dev; + struct vidinfo* item; + char* pos; + + DEBUG(printk(CARD_DEBUG "open(dev,%d)\n",CARD,flags)); + + /********************************************* + * We really should be doing lazy allocing... + *********************************************/ + /* allocate a frame buffer */ + if (!ztv->fbuffer) + ztv->fbuffer = bmalloc(ZORAN_MAX_FBUFSIZE); + if (!ztv->fbuffer) { + /* could not get a buffer, bail out */ + return -ENOBUFS; + } + /* at this time we _always_ have a framebuffer */ + memset(ztv->fbuffer,0,ZORAN_MAX_FBUFSIZE); + + if (!ztv->overinfo.overlay) + ztv->overinfo.overlay = (void*)kmalloc(1024*1024/8, GFP_KERNEL); + if (!ztv->overinfo.overlay) { + /* could not get an overlay buffer, bail out */ + bfree(ztv->fbuffer, ZORAN_MAX_FBUFSIZE); + return -ENOBUFS; + } + /* at this time we _always_ have a overlay */ + + /* clear buffer status, and give them a DMAable address */ + pos = ztv->fbuffer; + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + { + item->status = FBUFFER_FREE; + item->memadr = pos; + item->busadr = virt_to_bus(pos); + pos += ZORAN_MAX_FBUFFER; + } + + /* do the common part of all open's */ + zoran_common_open(ztv, flags); + + MOD_INC_USE_COUNT; + return 0; +} + +static +void zoran_close(struct video_device* dev) +{ + struct zoran *ztv = (struct zoran*)dev; + + DEBUG(printk(CARD_DEBUG "close(dev)\n",CARD)); + + /* driver specific closure */ + clear_bit(STATE_OVERLAY, &ztv->state); + + zoran_common_close(ztv); + + /* + * This is sucky but right now I can't find a good way to + * be sure its safe to free the buffer. We wait 5-6 fields + * which is more than sufficient to be sure. + */ + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ/10); /* Wait 1/10th of a second */ + + /* free the allocated framebuffer */ + if (ztv->fbuffer) + bfree( ztv->fbuffer, ZORAN_MAX_FBUFSIZE ); + ztv->fbuffer = 0; + if (ztv->overinfo.overlay) + kfree( ztv->overinfo.overlay ); + ztv->overinfo.overlay = 0; + + MOD_DEC_USE_COUNT; +} + +/* + * This read function could be used reentrant in a SMP situation. + * + * This is made possible by the spinlock which is kept till we + * found and marked a buffer for our own use. The lock must + * be released as soon as possible to prevent lock contention. + */ +static +long zoran_read(struct video_device* dev, char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = (struct zoran*)dev; + unsigned long max; + struct vidinfo* unused = 0; + struct vidinfo* done = 0; + + DEBUG(printk(CARD_DEBUG "zoran_read(%p,%ld,%d)\n",CARD,buf,count,nonblock)); + + /* find ourself a free or completed buffer */ + for (;;) { + struct vidinfo* item; + + write_lock_irq(&ztv->lock); + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + { + if (!unused && item->status == FBUFFER_FREE) + unused = item; + if (!done && item->status == FBUFFER_DONE) + done = item; + } + if (done || unused) + break; + + /* no more free buffers, wait for them. */ + write_unlock_irq(&ztv->lock); + if (nonblock) + return -EWOULDBLOCK; + interruptible_sleep_on(&ztv->grabq); + if (signal_pending(current)) + return -EINTR; + } + + /* Do we have 'ready' data? */ + if (!done) { + /* no? than this will take a while... */ + if (nonblock) { + write_unlock_irq(&ztv->lock); + return -EWOULDBLOCK; + } + + /* mark the unused buffer as wanted */ + unused->status = FBUFFER_BUSY; + unused->w = 320; + unused->h = 240; + unused->format = VIDEO_PALETTE_RGB24; + unused->bpp = palette2fmt[unused->format].bpp; + unused->bpl = unused->w * unused->bpp; + unused->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = unused; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = unused; + } + } + write_unlock_irq(&ztv->lock); + + /* tell the state machine we want it filled /NOW/ */ + zoran_cap(ztv, 1); + + /* wait till this buffer gets grabbed */ + while (unused->status == FBUFFER_BUSY) { + interruptible_sleep_on(&ztv->grabq); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + } + done = unused; + } + else + write_unlock_irq(&ztv->lock); + + /* Yes! we got data! */ + max = done->bpl * done->h; + if (count > max) + count = max; + if (copy_to_user((void*)buf, done->memadr, count)) + count = -EFAULT; + + /* keep the engine running */ + done->status = FBUFFER_FREE; +// zoran_cap(ztv,1); + + /* tell listeners this buffer became free */ + wake_up_interruptible(&ztv->grabq); + + /* goodbye */ + DEBUG(printk(CARD_DEBUG "zoran_read() returns %lu\n",CARD,count)); + return count; +} + +static +long zoran_write(struct video_device* dev, const char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = (struct zoran *)dev; + UNUSED(ztv); UNUSED(dev); UNUSED(buf); UNUSED(count); UNUSED(nonblock); + DEBUG(printk(CARD_DEBUG "zoran_write\n",CARD)); + return -EINVAL; +} + +#if LINUX_VERSION_CODE >= 0x020100 +static +unsigned int zoran_poll(struct video_device *dev, struct file *file, poll_table *wait) +{ + struct zoran *ztv = (struct zoran *)dev; + struct vidinfo* item; + unsigned int mask = 0; + + poll_wait(file, &ztv->grabq, wait); + + for (item=ztv->grabinfo; item!=ztv->grabinfo+ZORAN_MAX_FBUFFERS; item++) + if (item->status == FBUFFER_DONE) + { + mask |= (POLLIN | POLLRDNORM); + break; + } + + DEBUG(printk(CARD_DEBUG "zoran_poll()=%x\n",CARD,mask)); + + return mask; +} +#endif + +/* append a new clipregion to the vector of video_clips */ +static +void new_clip(struct video_window* vw, struct video_clip* vcp, int x, int y, int w, int h) +{ + vcp[vw->clipcount].x = x; + vcp[vw->clipcount].y = y; + vcp[vw->clipcount].width = w; + vcp[vw->clipcount].height = h; + vw->clipcount++; +} + +static +int zoran_ioctl(struct video_device* dev, unsigned int cmd, void *arg) +{ + struct zoran* ztv = (struct zoran*)dev; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability c; + DEBUG(printk(CARD_DEBUG "VIDIOCGCAP\n",CARD)); + + strcpy(c.name,ztv->video_dev.name); + c.type = VID_TYPE_CAPTURE| + VID_TYPE_OVERLAY| + VID_TYPE_CLIPPING| + VID_TYPE_FRAMERAM| + VID_TYPE_SCALES; + if (ztv->have_tuner) + c.type |= VID_TYPE_TUNER; + if (ztv->have_decoder) { + c.channels = ztv->card->video_inputs; + c.audios = ztv->card->audio_inputs; + } else + /* no decoder -> no channels */ + c.channels = c.audios = 0; + c.maxwidth = 768; + c.maxheight = 576; + c.minwidth = 32; + c.minheight = 32; + if (copy_to_user(arg,&c,sizeof(c))) + return -EFAULT; + break; + } + + case VIDIOCGCHAN: + { + struct video_channel v; + int mux; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGCHAN(%d)\n",CARD,v.channel)); + v.flags=VIDEO_VC_AUDIO +#ifdef VIDEO_VC_NORM + |VIDEO_VC_NORM +#endif + ; + v.tuners=0; + v.type=VIDEO_TYPE_CAMERA; +#ifdef I_EXPECT_POSSIBLE_NORMS_IN_THE_API + v.norm=VIDEO_MODE_PAL| + VIDEO_MODE_NTSC| + VIDEO_MODE_SECAM; +#else + v.norm=VIDEO_MODE_PAL; +#endif + /* too many inputs? no decoder -> no channels */ + if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs) + return -EINVAL; + + /* now determine the name of the channel */ + mux = ztv->card->video_mux[v.channel]; + if (mux & IS_TUNER) { + /* lets assume only one tuner, yes? */ + strcpy(v.name,"Television"); + v.type = VIDEO_TYPE_TV; + if (ztv->have_tuner) { + v.flags |= VIDEO_VC_TUNER; + v.tuners = 1; + } + } + else if (mux & IS_SVHS) + sprintf(v.name,"S-Video-%d",v.channel); + else + sprintf(v.name,"CVBS-%d",v.channel); + + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSCHAN: + { /* set video channel */ + struct video_channel v; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSCHAN(%d,%d)\n",CARD,v.channel,v.norm)); + + /* too many inputs? no decoder -> no channels */ + if (!ztv->have_decoder || v.channel >= ztv->card->video_inputs) + return -EINVAL; + + if (v.norm != VIDEO_MODE_PAL && + v.norm != VIDEO_MODE_NTSC && + v.norm != VIDEO_MODE_SECAM && + v.norm != VIDEO_MODE_AUTO) + return -EOPNOTSUPP; + + /* make it happen, nr1! */ + return zoran_muxsel(ztv,v.channel,v.norm); + } + + case VIDIOCGTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGTUNER(%d)\n",CARD,v.tuner)); + + /* Only no or one tuner for now */ + if (!ztv->have_tuner || v.tuner) + return -EINVAL; + + strcpy(v.name,"Television"); + v.rangelow = 0; + v.rangehigh = ~0; + v.flags = VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + v.mode = ztv->norm; + v.signal = 0xFFFF; /* unknown */ + + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSTUNER(%d,%d)\n",CARD,v.tuner,v.mode)); + + /* Only no or one tuner for now */ + if (!ztv->have_tuner || v.tuner) + return -EINVAL; + + /* and it only has certain valid modes */ + if( v.mode != VIDEO_MODE_PAL && + v.mode != VIDEO_MODE_NTSC && + v.mode != VIDEO_MODE_SECAM) + return -EOPNOTSUPP; + + /* engage! */ + return zoran_muxsel(ztv,v.tuner,v.mode); + } + + case VIDIOCGPICT: + { + struct video_picture p = ztv->picture; + DEBUG(printk(CARD_DEBUG "VIDIOCGPICT\n",CARD)); + p.depth = ztv->depth; + switch (p.depth) { + case 8: p.palette=VIDEO_PALETTE_YUV422; + break; + case 15: p.palette=VIDEO_PALETTE_RGB555; + break; + case 16: p.palette=VIDEO_PALETTE_RGB565; + break; + case 24: p.palette=VIDEO_PALETTE_RGB24; + break; + case 32: p.palette=VIDEO_PALETTE_RGB32; + break; + } + if (copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + break; + } + case VIDIOCSPICT: + { + struct video_picture p; + if (copy_from_user(&p, arg,sizeof(p))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSPICT(%d,%d,%d,%d,%d,%d,%d)\n",CARD,p.brightness,p.hue,p.colour,p.contrast,p.whiteness,p.depth,p.palette)); + + /* depth must match with framebuffer */ + if (p.depth != ztv->depth) + return -EINVAL; + + /* check if palette matches this bpp */ + if (p.palette>NRPALETTES || + palette2fmt[p.palette].bpp != ztv->overinfo.bpp) + return -EINVAL; + + write_lock_irq(&ztv->lock); + ztv->overinfo.format = p.palette; + ztv->picture = p; + write_unlock_irq(&ztv->lock); + + /* tell the decoder */ + i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_SET_PICTURE, &p); + break; + } + + case VIDIOCGWIN: + { + struct video_window vw; + DEBUG(printk(CARD_DEBUG "VIDIOCGWIN\n",CARD)); + read_lock(&ztv->lock); + vw.x = ztv->overinfo.x; + vw.y = ztv->overinfo.y; + vw.width = ztv->overinfo.w; + vw.height = ztv->overinfo.h; + vw.chromakey= 0; + vw.flags = 0; + if (ztv->vidInterlace) + vw.flags|=VIDEO_WINDOW_INTERLACE; + read_unlock(&ztv->lock); + if (copy_to_user(arg,&vw,sizeof(vw))) + return -EFAULT; + break; + } + case VIDIOCSWIN: + { + struct video_window vw; + struct video_clip *vcp; + int on; + if (copy_from_user(&vw,arg,sizeof(vw))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSWIN(%d,%d,%d,%d,%x,%d)\n",CARD,vw.x,vw.y,vw.width,vw.height,vw.flags,vw.clipcount)); + + if (vw.flags) + return -EINVAL; + + if (vw.clipcount>256) + return -EDOM; /* Too many! */ + + /* + * Do any clips. + */ + vcp = vmalloc(sizeof(struct video_clip)*(vw.clipcount+4)); + if (vcp==NULL) + return -ENOMEM; + if (vw.clipcount && copy_from_user(vcp,vw.clips,sizeof(struct video_clip)*vw.clipcount)) + return -EFAULT; + + on = ztv->running; + if (on) + zoran_cap(ztv, 0); + + /* + * strange, it seems xawtv sometimes calls us with 0 + * width and/or height. Ignore these values + */ + if (vw.x == 0) + vw.x = ztv->overinfo.x; + if (vw.y == 0) + vw.y = ztv->overinfo.y; + + /* by now we are committed to the new data... */ + write_lock_irq(&ztv->lock); + ztv->overinfo.x = vw.x; + ztv->overinfo.y = vw.y; + ztv->overinfo.w = vw.width; + ztv->overinfo.h = vw.height; + write_unlock_irq(&ztv->lock); + + /* + * Impose display clips + */ + if (vw.x+vw.width > ztv->swidth) + new_clip(&vw, vcp, ztv->swidth-vw.x, 0, vw.width-1, vw.height-1); + if (vw.y+vw.height > ztv->sheight) + new_clip(&vw, vcp, 0, ztv->sheight-vw.y, vw.width-1, vw.height-1); + + /* built the requested clipping zones */ + zoran_set_geo(ztv, &ztv->overinfo); + zoran_built_overlay(ztv, vw.clipcount, vcp); + vfree(vcp); + + /* if we were on, restart the video engine */ + if (on) + zoran_cap(ztv, 1); + break; + } + + case VIDIOCCAPTURE: + { + int v; + get_user_ret(v,(int*)arg, -EFAULT); + DEBUG(printk(CARD_DEBUG "VIDIOCCAPTURE(%d)\n",CARD,v)); + + if (v==0) { + clear_bit(STATE_OVERLAY, &ztv->state); + zoran_cap(ztv, 1); + } + else { + /* is VIDIOCSFBUF, VIDIOCSWIN done? */ + if (ztv->overinfo.busadr==0 || ztv->overinfo.w==0 || ztv->overinfo.h==0) + return -EINVAL; + + set_bit(STATE_OVERLAY, &ztv->state); + zoran_cap(ztv, 1); + } + break; + } + + case VIDIOCGFBUF: + { + struct video_buffer v; + DEBUG(printk(CARD_DEBUG "VIDIOCGFBUF\n",CARD)); + read_lock(&ztv->lock); + v.base = (void *)ztv->overinfo.busadr; + v.height = ztv->sheight; + v.width = ztv->swidth; + v.depth = ztv->depth; + v.bytesperline = ztv->overinfo.bpl; + read_unlock(&ztv->lock); + if(copy_to_user(arg, &v,sizeof(v))) + return -EFAULT; + break; + } + case VIDIOCSFBUF: + { + struct video_buffer v; +#if LINUX_VERSION_CODE >= 0x020100 + if(!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_ADMIN)) +#else + if(!suser()) +#endif + return -EPERM; + if (copy_from_user(&v, arg,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSFBUF(%p,%d,%d,%d,%d)\n",CARD,v.base, v.width,v.height,v.depth,v.bytesperline)); + + if (v.depth!=15 && v.depth!=16 && v.depth!=24 && v.depth!=32) + return -EINVAL; + if (v.bytesperline<1) + return -EINVAL; + if (ztv->running) + return -EBUSY; + write_lock_irq(&ztv->lock); + ztv->overinfo.busadr = (ulong)v.base; + ztv->sheight = v.height; + ztv->swidth = v.width; + ztv->depth = v.depth; /* bits per pixel */ + ztv->overinfo.bpp = ((v.depth+1)&0x38)/8;/* bytes per pixel */ + ztv->overinfo.bpl = v.bytesperline; /* bytes per line */ + write_unlock_irq(&ztv->lock); + break; + } + + case VIDIOCKEY: + { + /* Will be handled higher up .. */ + break; + } + + case VIDIOCSYNC: + { + int i; + get_user_ret(i,(int*)arg, -EFAULT); + DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d)\n",CARD,i)); + if (i<0 || i>ZORAN_MAX_FBUFFERS) + return -EINVAL; + switch (ztv->grabinfo[i].status) { + case FBUFFER_FREE: + return -EINVAL; + case FBUFFER_BUSY: + /* wait till this buffer gets grabbed */ + while (ztv->grabinfo[i].status == FBUFFER_BUSY) { + interruptible_sleep_on(&ztv->grabq); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + } + /* don't fall through; a DONE buffer is not UNUSED */ + break; + case FBUFFER_DONE: + ztv->grabinfo[i].status = FBUFFER_FREE; + /* tell ppl we have a spare buffer */ + wake_up_interruptible(&ztv->grabq); + break; + } + DEBUG(printk(CARD_DEBUG "VIDEOCSYNC(%d) returns\n",CARD,i)); + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + struct vidinfo* frame; + if (copy_from_user(&vm,arg,sizeof(vm))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCMCAPTURE(%d,(%d,%d),%d)\n",CARD,vm.frame,vm.width,vm.height,vm.format)); + if (vm.frame<0 || vm.frame>ZORAN_MAX_FBUFFERS || + vm.width<32 || vm.width>768 || + vm.height<32 || vm.height>576 || + vm.format>NRPALETTES || + palette2fmt[vm.format].mode == 0) + return -EINVAL; + + /* we are allowed to take over UNUSED and DONE buffers */ + frame = &ztv->grabinfo[vm.frame]; + if (frame->status == FBUFFER_BUSY) + return -EBUSY; + + /* setup the other parameters if they are given */ + write_lock_irq(&ztv->lock); + frame->w = vm.width; + frame->h = vm.height; + frame->format = vm.format; + frame->bpp = palette2fmt[frame->format].bpp; + frame->bpl = frame->w*frame->bpp; + frame->status = FBUFFER_BUSY; + frame->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = frame; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = frame; + } + } + write_unlock_irq(&ztv->lock); + zoran_cap(ztv, 1); + break; + } + + case VIDIOCGMBUF: + { + struct video_mbuf mb; + int i; + DEBUG(printk(CARD_DEBUG "VIDIOCGMBUF\n",CARD)); + mb.size = ZORAN_MAX_FBUFSIZE; + mb.frames = ZORAN_MAX_FBUFFERS; + for (i=0; i<ZORAN_MAX_FBUFFERS; i++) + mb.offsets[i] = i*ZORAN_MAX_FBUFFER; + if(copy_to_user(arg, &mb,sizeof(mb))) + return -EFAULT; + break; + } + + case VIDIOCGUNIT: + { + struct video_unit vu; + DEBUG(printk(CARD_DEBUG "VIDIOCGUNIT\n",CARD)); + vu.video = ztv->video_dev.minor; + vu.vbi = ztv->vbi_dev.minor; + vu.radio = VIDEO_NO_UNIT; + vu.audio = VIDEO_NO_UNIT; + vu.teletext = VIDEO_NO_UNIT; + if(copy_to_user(arg, &vu,sizeof(vu))) + return -EFAULT; + break; + } + + case VIDIOCGFREQ: + { + unsigned long v = ztv->tuner_freq; + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCGFREQ\n",CARD)); + break; + } + case VIDIOCSFREQ: + { + unsigned long v; + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSFREQ\n",CARD)); + + if (ztv->have_tuner) { + int fixme = v; + if (i2c_control_device(&(ztv->i2c), I2C_DRIVERID_TUNER, TUNER_SET_TVFREQ, &fixme) < 0) + return -EAGAIN; + } + ztv->tuner_freq = v; + break; + } + + /* Why isn't this in the API? + * And why doesn't it take a buffer number? + case BTTV_FIELDNR: + { + unsigned long v = ztv->lastfieldnr; + if (copy_to_user(arg,&v,sizeof(v))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "BTTV_FIELDNR\n",CARD)); + break; + } + */ + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static +int zoran_mmap(struct video_device* dev, const char* adr, unsigned long size) +{ + struct zoran* ztv = (struct zoran*)dev; + unsigned long start = (unsigned long)adr; + unsigned long pos; + + DEBUG(printk(CARD_DEBUG "zoran_mmap(0x%p,%ld)\n",CARD,adr,size)); + + /* sanity checks */ + if (size > ZORAN_MAX_FBUFSIZE || !ztv->fbuffer) + return -EINVAL; + + /* start mapping the whole shabang to user memory */ + pos = (unsigned long)ztv->fbuffer; + while (size>0) { + unsigned long page = virt_to_phys((void*)pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + size -= PAGE_SIZE; + } + return 0; +} + +static struct video_device zr36120_template= +{ + "UNSET", + VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY, + VID_HARDWARE_ZR36120, + + zoran_open, + zoran_close, + zoran_read, + zoran_write, +#if LINUX_VERSION_CODE >= 0x020100 + zoran_poll, /* poll */ +#endif + zoran_ioctl, + zoran_mmap, + NULL, /* initialize */ + NULL, + 0, + -1 +}; + +static +int vbi_open(struct video_device *dev, int flags) +{ + struct zoran *ztv = (struct zoran*)dev->priv; + struct vidinfo* item; + + DEBUG(printk(CARD_DEBUG "vbi_open(dev,%d)\n",CARD,flags)); + + /* + * During VBI device open, we continiously grab VBI-like + * data in the vbi buffer when we have nothing to do. + * Only when there is an explicit request for VBI data + * (read call) we /force/ a read. + */ + + /* allocate buffers */ + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + { + item->status = FBUFFER_FREE; + + /* alloc */ + if (!item->memadr) { + item->memadr = bmalloc(ZORAN_VBI_BUFSIZE); + if (!item->memadr) { + /* could not get a buffer, bail out */ + while (item != ztv->readinfo) { + item--; + bfree(item->memadr, ZORAN_VBI_BUFSIZE); + item->memadr = 0; + item->busadr = 0; + } + return -ENOBUFS; + } + } + + /* determine the DMAable address */ + item->busadr = virt_to_bus(item->memadr); + } + + /* do the common part of all open's */ + zoran_common_open(ztv, flags); + + set_bit(STATE_VBI, &ztv->state); + /* start read-ahead */ + zoran_cap(ztv, 1); + + MOD_INC_USE_COUNT; + return 0; +} + +static +void vbi_close(struct video_device *dev) +{ + struct zoran *ztv = (struct zoran*)dev->priv; + struct vidinfo* item; + + DEBUG(printk(CARD_DEBUG "vbi_close(dev)\n",CARD)); + + /* driver specific closure */ + clear_bit(STATE_VBI, &ztv->state); + + zoran_common_close(ztv); + + /* + * This is sucky but right now I can't find a good way to + * be sure its safe to free the buffer. We wait 5-6 fields + * which is more than sufficient to be sure. + */ + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(HZ/10); /* Wait 1/10th of a second */ + + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + { + if (item->memadr) + bfree(item->memadr, ZORAN_VBI_BUFSIZE); + item->memadr = 0; + } + + MOD_DEC_USE_COUNT; +} + +/* + * This read function could be used reentrant in a SMP situation. + * + * This is made possible by the spinlock which is kept till we + * found and marked a buffer for our own use. The lock must + * be released as soon as possible to prevent lock contention. + */ +static +long vbi_read(struct video_device* dev, char* buf, unsigned long count, int nonblock) +{ + struct zoran *ztv = (struct zoran*)dev->priv; + unsigned long max; + struct vidinfo* unused = 0; + struct vidinfo* done = 0; + + DEBUG(printk(CARD_DEBUG "vbi_read(0x%p,%ld,%d)\n",CARD,buf,count,nonblock)); + + /* find ourself a free or completed buffer */ + for (;;) { + struct vidinfo* item; + + write_lock_irq(&ztv->lock); + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) { + if (!unused && item->status == FBUFFER_FREE) + unused = item; + if (!done && item->status == FBUFFER_DONE) + done = item; + } + if (done || unused) + break; + + /* no more free buffers, wait for them. */ + write_unlock_irq(&ztv->lock); + if (nonblock) + return -EWOULDBLOCK; + interruptible_sleep_on(&ztv->vbiq); + if (signal_pending(current)) + return -EINTR; + } + + /* Do we have 'ready' data? */ + if (!done) { + /* no? than this will take a while... */ + if (nonblock) { + write_unlock_irq(&ztv->lock); + return -EWOULDBLOCK; + } + + /* mark the unused buffer as wanted */ + unused->status = FBUFFER_BUSY; + unused->next = 0; + { /* add to tail of queue */ + struct vidinfo* oldframe = ztv->workqueue; + if (!oldframe) ztv->workqueue = unused; + else { + while (oldframe->next) oldframe = oldframe->next; + oldframe->next = unused; + } + } + write_unlock_irq(&ztv->lock); + + /* tell the state machine we want it filled /NOW/ */ + zoran_cap(ztv, 1); + + /* wait till this buffer gets grabbed */ + while (unused->status == FBUFFER_BUSY) { + interruptible_sleep_on(&ztv->vbiq); + /* see if a signal did it */ + if (signal_pending(current)) + return -EINTR; + } + done = unused; + } + else + write_unlock_irq(&ztv->lock); + + /* Yes! we got data! */ + max = done->bpl * -done->h; + if (count > max) + count = max; + + /* check if the user gave us enough room to write the data */ + if (!access_ok(VERIFY_WRITE, buf, count)) { + count = -EFAULT; + goto out; + } + + /* + * Now transform/strip the data from YUV to Y-only + * NB. Assume the Y is in the LSB of the YUV data. + */ + { + unsigned char* optr = buf; + unsigned char* eptr = buf+count; + + /* are we beeing accessed from an old driver? */ + if (count == 2*19*2048) { + /* + * Extreme HACK, old VBI programs expect 2048 points + * of data, and we only got 864 orso. Double each + * datapoint and clear the rest of the line. + * This way we have appear to have a + * sample_frequency of 29.5 Mc. + */ + int x,y; + unsigned char* iptr = done->memadr+1; + for (y=done->h; optr<eptr && y<0; y++) + { + /* copy to doubled data to userland */ + for (x=0; optr+1<eptr && x<-done->w; x++) + { + unsigned char a = iptr[x*2]; + *optr++ = a; + *optr++ = a; + } + /* and clear the rest of the line */ + for (x*=2; optr<eptr && x<done->bpl; x++) + *optr++ = 0; + /* next line */ + iptr += done->bpl; + } + } + else { + /* + * Other (probably newer) programs asked + * us what geometry we are using, and are + * reading the correct size. + */ + int x,y; + unsigned char* iptr = done->memadr+1; + for (y=done->h; optr<eptr && y<0; y++) + { + /* copy to doubled data to userland */ + for (x=0; optr<eptr && x<-done->w; x++) + *optr++ = iptr[x*2]; + /* and clear the rest of the line */ + for (;optr<eptr && x<done->bpl; x++) + *optr++ = 0; + /* next line */ + iptr += done->bpl; + } + } + + /* API compliance: + * place the framenumber (half fieldnr) in the last long + */ + ((ulong*)eptr)[-1] = done->fieldnr/2; + } + + /* keep the engine running */ + done->status = FBUFFER_FREE; + zoran_cap(ztv, 1); + + /* tell listeners this buffer just became free */ + wake_up_interruptible(&ztv->vbiq); + + /* goodbye */ +out: + DEBUG(printk(CARD_DEBUG "vbi_read() returns %lu\n",CARD,count)); + return count; +} + +#if LINUX_VERSION_CODE >= 0x020100 +static +unsigned int vbi_poll(struct video_device *dev, struct file *file, poll_table *wait) +{ + struct zoran *ztv = (struct zoran*)dev->priv; + struct vidinfo* item; + unsigned int mask = 0; + + poll_wait(file, &ztv->vbiq, wait); + + for (item=ztv->readinfo; item!=ztv->readinfo+ZORAN_VBI_BUFFERS; item++) + if (item->status == FBUFFER_DONE) + { + mask |= (POLLIN | POLLRDNORM); + break; + } + + DEBUG(printk(CARD_DEBUG "vbi_poll()=%x\n",CARD,mask)); + + return mask; +} +#endif + +static +int vbi_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct zoran* ztv = (struct zoran*)dev->priv; + + switch (cmd) { + case VIDIOCGVBIFMT: + { + struct vbi_format f; + DEBUG(printk(CARD_DEBUG "VIDIOCGVBIINFO\n",CARD)); + f.sampling_rate = 14750000UL; + f.samples_per_line = -ztv->readinfo[0].w; + f.sample_format = VIDEO_PALETTE_RAW; + f.start[0] = f.start[1] = ztv->readinfo[0].y; + f.start[1] += 312; + f.count[0] = f.count[1] = -ztv->readinfo[0].h; + f.flags = VBI_INTERLACED; + if (copy_to_user(arg,&f,sizeof(f))) + return -EFAULT; + break; + } + case VIDIOCSVBIFMT: + { + struct vbi_format f; + int i; + if (copy_from_user(&f, arg,sizeof(f))) + return -EFAULT; + DEBUG(printk(CARD_DEBUG "VIDIOCSVBIINFO(%d,%d,%d,%d,%d,%d,%d,%x)\n",CARD,f.sampling_rate,f.samples_per_line,f.sample_format,f.start[0],f.start[1],f.count[0],f.count[1],f.flags)); + + /* lots of parameters are fixed... (PAL) */ + if (f.sampling_rate != 14750000UL || + f.samples_per_line > 864 || + f.sample_format != VIDEO_PALETTE_RAW || + f.start[0] < 0 || + f.start[0] != f.start[1]-312 || + f.count[0] != f.count[1] || + f.start[0]+f.count[0] >= 288 || + f.flags != VBI_INTERLACED) + return -EINVAL; + + write_lock_irq(&ztv->lock); + ztv->readinfo[0].y = f.start[0]; + ztv->readinfo[0].w = -f.samples_per_line; + ztv->readinfo[0].h = -f.count[0]; + ztv->readinfo[0].bpl = f.samples_per_line*ztv->readinfo[0].bpp; + for (i=1; i<ZORAN_VBI_BUFFERS; i++) + ztv->readinfo[i] = ztv->readinfo[i]; + write_unlock_irq(&ztv->lock); + break; + } + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct video_device vbi_template= +{ + "UNSET", + VID_TYPE_CAPTURE|VID_TYPE_TELETEXT, + VID_HARDWARE_ZR36120, + + vbi_open, + vbi_close, + vbi_read, + zoran_write, +#if LINUX_VERSION_CODE >= 0x020100 + vbi_poll, /* poll */ +#endif + vbi_ioctl, + NULL, /* no mmap */ + NULL, /* no initialize */ + NULL, /* priv */ + 0, /* busy */ + -1 /* minor */ +}; + +/* + * Scan for a Zoran chip, request the irq and map the io memory + */ +static +int __init find_zoran(void) +{ + int result; + struct zoran *ztv; + struct pci_dev *dev = NULL; + unsigned char revision; + int zoran_num=0; + + while ((dev = pci_find_device(PCI_VENDOR_ID_ZORAN,PCI_DEVICE_ID_ZORAN_36120, dev))) + { + /* Ok, a ZR36120/ZR36125 found! */ + ztv = &zorans[zoran_num]; + ztv->dev = dev; + + if (pci_enable_device(dev)) + return -EIO; + + pci_read_config_byte(dev, PCI_CLASS_REVISION, &revision); + printk(KERN_INFO "zoran: Zoran %x (rev %d) ", + dev->device, revision); + printk("bus: %d, devfn: %d, irq: %d, ", + dev->bus->number, dev->devfn, dev->irq); + printk("memory: 0x%08lx.\n", ztv->zoran_adr); + + ztv->zoran_mem = ioremap(ztv->zoran_adr, 0x1000); + DEBUG(printk(KERN_DEBUG "zoran: mapped-memory at 0x%p\n",ztv->zoran_mem)); + + result = request_irq(dev->irq, zoran_irq, + SA_SHIRQ|SA_INTERRUPT,"zoran",(void *)ztv); + if (result==-EINVAL) + { + printk(KERN_ERR "zoran: Bad irq number or handler\n"); + return -EINVAL; + } + if (result==-EBUSY) + printk(KERN_ERR "zoran: IRQ %d busy, change your PnP config in BIOS\n",dev->irq); + if (result < 0) + return result; + + /* Enable bus-mastering */ + pci_set_master(dev); + + zoran_num++; + } + if(zoran_num) + printk(KERN_INFO "zoran: %d Zoran card(s) found.\n",zoran_num); + return zoran_num; +} + +static +int __init init_zoran(int card) +{ + struct zoran *ztv = &zorans[card]; + int i; + + /* if the given cardtype valid? */ + if (cardtype[card]>=NRTVCARDS) { + printk(KERN_INFO "invalid cardtype(%d) detected\n",cardtype[card]); + return -1; + } + + /* reset the zoran */ + zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + + /* zoran chip specific details */ + ztv->card = tvcards+cardtype[card]; /* point to the selected card */ + ztv->norm = 0; /* PAL */ + ztv->tuner_freq = 0; + + /* videocard details */ + ztv->swidth = 800; + ztv->sheight = 600; + ztv->depth = 16; + + /* State details */ + ztv->fbuffer = 0; + ztv->overinfo.kindof = FBUFFER_OVERLAY; + ztv->overinfo.status = FBUFFER_FREE; + ztv->overinfo.x = 0; + ztv->overinfo.y = 0; + ztv->overinfo.w = 768; /* 640 */ + ztv->overinfo.h = 576; /* 480 */ + ztv->overinfo.format = VIDEO_PALETTE_RGB565; + ztv->overinfo.bpp = palette2fmt[ztv->overinfo.format].bpp; + ztv->overinfo.bpl = ztv->overinfo.bpp*ztv->swidth; + ztv->overinfo.busadr = 0; + ztv->overinfo.memadr = 0; + ztv->overinfo.overlay = 0; + for (i=0; i<ZORAN_MAX_FBUFFERS; i++) { + ztv->grabinfo[i] = ztv->overinfo; + ztv->grabinfo[i].kindof = FBUFFER_GRAB; + } + init_waitqueue_head(&ztv->grabq); + + /* VBI details */ + ztv->readinfo[0] = ztv->overinfo; + ztv->readinfo[0].kindof = FBUFFER_VBI; + ztv->readinfo[0].w = -864; + ztv->readinfo[0].h = -38; + ztv->readinfo[0].format = VIDEO_PALETTE_YUV422; + ztv->readinfo[0].bpp = palette2fmt[ztv->readinfo[0].format].bpp; + ztv->readinfo[0].bpl = 1024*ztv->readinfo[0].bpp; + for (i=1; i<ZORAN_VBI_BUFFERS; i++) + ztv->readinfo[i] = ztv->readinfo[0]; + init_waitqueue_head(&ztv->vbiq); + + /* maintenance data */ + ztv->have_decoder = 0; + ztv->have_tuner = 0; + ztv->tuner_type = 0; + ztv->running = 0; + ztv->users = 0; + ztv->lock = RW_LOCK_UNLOCKED; + ztv->workqueue = 0; + ztv->fieldnr = 0; + ztv->lastfieldnr = 0; + + if (triton1) + zrand(~ZORAN_VDC_TRICOM, ZORAN_VDC); + + /* external FL determines TOP frame */ + zror(ZORAN_VFEC_EXTFL, ZORAN_VFEC); + + /* set HSpol */ + if (ztv->card->hsync_pos) + zrwrite(ZORAN_VFEH_HSPOL, ZORAN_VFEH); + /* set VSpol */ + if (ztv->card->vsync_pos) + zrwrite(ZORAN_VFEV_VSPOL, ZORAN_VFEV); + + /* Set the proper General Purpuse register bits */ + /* implicit: no softreset, 0 waitstates */ + zrwrite(ZORAN_PCI_SOFTRESET|(ztv->card->gpdir<<0),ZORAN_PCI); + /* implicit: 3 duration and recovery PCI clocks on guest 0-3 */ + zrwrite(ztv->card->gpval<<24,ZORAN_GUEST); + + /* clear interrupt status */ + zrwrite(~0, ZORAN_ISR); + + /* + * i2c template + */ + ztv->i2c = zoran_i2c_bus_template; + sprintf(ztv->i2c.name,"zoran-%d",card); + ztv->i2c.data = ztv; + + /* + * Now add the template and register the device unit + */ + ztv->video_dev = zr36120_template; + strcpy(ztv->video_dev.name, ztv->i2c.name); + ztv->video_dev.priv = ztv; + if (video_register_device(&ztv->video_dev, VFL_TYPE_GRABBER) < 0) + return -1; + + ztv->vbi_dev = vbi_template; + strcpy(ztv->vbi_dev.name, ztv->i2c.name); + ztv->vbi_dev.priv = ztv; + if (video_register_device(&ztv->vbi_dev, VFL_TYPE_VBI) < 0) { + video_unregister_device(&ztv->video_dev); + return -1; + } + i2c_register_bus(&ztv->i2c); + + /* set interrupt mask - the PIN enable will be set later */ + zrwrite(ZORAN_ICR_GIRQ0|ZORAN_ICR_GIRQ1|ZORAN_ICR_CODE, ZORAN_ICR); + + printk(KERN_INFO "%s: installed %s\n",ztv->i2c.name,ztv->card->name); + return 0; +} + +static +void __exit release_zoran(int max) +{ + struct zoran *ztv; + int i; + + for (i=0;i<max; i++) + { + ztv = &zorans[i]; + + /* turn off all capturing, DMA and IRQs */ + /* reset the zoran */ + zrand(~ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + zror(ZORAN_PCI_SOFTRESET,ZORAN_PCI); + udelay(10); + + /* first disable interrupts before unmapping the memory! */ + zrwrite(0, ZORAN_ICR); + zrwrite(0xffffffffUL,ZORAN_ISR); + + /* free it */ + free_irq(ztv->dev->irq,ztv); + + /* unregister i2c_bus */ + i2c_unregister_bus((&ztv->i2c)); + + /* unmap and free memory */ + if (ztv->zoran_mem) + iounmap(ztv->zoran_mem); + + video_unregister_device(&ztv->video_dev); + video_unregister_device(&ztv->vbi_dev); + } +} + +void __exit zr36120_exit(void) +{ + release_zoran(zoran_cards); +} + +int __init zr36120_init(void) +{ + int card; + + handle_chipset(); + zoran_cards = find_zoran(); + if (zoran_cards<0) + /* no cards found, no need for a driver */ + return -EIO; + + /* initialize Zorans */ + for (card=0; card<zoran_cards; card++) { + if (init_zoran(card)<0) { + /* only release the zorans we have registered */ + release_zoran(card); + return -EIO; + } + } + return 0; +} + +module_init(zr36120_init); +module_exit(zr36120_exit); diff --git a/drivers/media/video/zr36120.h b/drivers/media/video/zr36120.h new file mode 100644 index 000000000..e500de10d --- /dev/null +++ b/drivers/media/video/zr36120.h @@ -0,0 +1,279 @@ +/* + zr36120.h - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink (middelin@polyware.nl) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef _ZR36120_H +#define _ZR36120_H + +#ifdef __KERNEL__ + +#include <linux/types.h> +#include <linux/wait.h> + +#include <linux/i2c-old.h> +#include <linux/videodev.h> + +#include <asm/io.h> + +/* + * Debug macro's, place an x behind the ) for actual debug-compilation + * E.g. #define DEBUG(x...) x + */ +#define DEBUG(x...) /* Debug driver */ +#define IDEBUG(x...) /* Debug interrupt handler */ +#define PDEBUG 0 /* Debug PCI writes */ + +/* defined in zr36120_i2c */ +extern struct i2c_bus zoran_i2c_bus_template; + +#define ZORAN_MAX_FBUFFERS 2 +#define ZORAN_MAX_FBUFFER (768*576*2) +#define ZORAN_MAX_FBUFSIZE (ZORAN_MAX_FBUFFERS*ZORAN_MAX_FBUFFER) + +#define ZORAN_VBI_BUFFERS 2 +#define ZORAN_VBI_BUFSIZE (22*1024*2) + +struct tvcard { + char* name; /* name of the cardtype */ + int video_inputs; /* number of channels defined in video_mux */ + int audio_inputs; /* number of channels defined in audio_mux */ + __u32 swapi2c:1, /* need to swap i2c wires SDA/SCL? */ + usegirq1:1, /* VSYNC at GIRQ1 instead of GIRQ0? */ + vsync_pos:1, /* positive VSYNC signal? */ + hsync_pos:1, /* positive HSYNC signal? */ + gpdir:8, /* General Purpose Direction register */ + gpval:8; /* General Purpose Value register */ + int video_mux[6]; /* mapping channel number to physical input */ +#define IS_TUNER 0x80 +#define IS_SVHS 0x40 +#define CHANNEL_MASK 0x3F + int audio_mux[6]; /* mapping channel number to physical input */ +}; +#define TUNER(x) ((x)|IS_TUNER) +#define SVHS(x) ((x)|IS_SVHS) + +struct vidinfo { + struct vidinfo* next; /* next active buffer */ + uint kindof; +#define FBUFFER_OVERLAY 0 +#define FBUFFER_GRAB 1 +#define FBUFFER_VBI 2 + uint status; +#define FBUFFER_FREE 0 +#define FBUFFER_BUSY 1 +#define FBUFFER_DONE 2 + ulong fieldnr; /* # of field, not framer! */ + uint x,y; + int w,h; /* w,h can be negative! */ + uint format; /* index in palette2fmt[] */ + uint bpp; /* lookup from palette2fmt[] */ + uint bpl; /* calc: width * bpp */ + ulong busadr; /* bus addr for DMA engine */ + char* memadr; /* kernel addr for making copies */ + ulong* overlay; /* kernel addr of overlay mask */ +}; + +struct zoran +{ + struct video_device video_dev; +#define CARD_DEBUG KERN_DEBUG "%s(%lu): " +#define CARD_INFO KERN_INFO "%s(%lu): " +#define CARD_ERR KERN_ERR "%s(%lu): " +#define CARD ztv->video_dev.name,ztv->fieldnr + + /* zoran chip specific details */ + struct i2c_bus i2c; /* i2c registration data */ + struct pci_dev* dev; /* ptr to PCI device */ + ulong zoran_adr; /* bus address of IO memory */ + char* zoran_mem; /* kernel address of IO memory */ + struct tvcard* card; /* the cardtype */ + uint norm; /* 0=PAL, 1=NTSC, 2=SECAM */ + uint tuner_freq; /* Current freq in kHz */ + struct video_picture picture; /* Current picture params */ + + /* videocard details */ + uint swidth; /* screen width */ + uint sheight; /* screen height */ + uint depth; /* depth in bits */ + + /* State details */ + char* fbuffer; /* framebuffers for mmap */ + struct vidinfo overinfo; /* overlay data */ + struct vidinfo grabinfo[ZORAN_MAX_FBUFFERS]; /* grabbing data*/ + wait_queue_head_t grabq; /* grabbers queue */ + + /* VBI details */ + struct video_device vbi_dev; + struct vidinfo readinfo[2]; /* VBI data - flip buffers */ + wait_queue_head_t vbiq; /* vbi queue */ + + /* maintenance data */ + int have_decoder; /* did we detect a mux? */ + int have_tuner; /* did we detect a tuner? */ + int users; /* howmany video/vbi open? */ + int tuner_type; /* tuner type, when found */ + int running; /* are we rolling? */ + rwlock_t lock; + int state; /* what is requested of us? */ +#define STATE_OVERLAY 0 +#define STATE_VBI 1 + struct vidinfo* workqueue; /* buffers to grab, head is active */ + ulong fieldnr; /* #field, ticked every VSYNC */ + ulong lastfieldnr; /* #field, ticked every GRAB */ + + int vidInterlace; /* calculated */ + int vidXshift; /* calculated */ + uint vidWidth; /* calculated */ + uint vidHeight; /* calculated */ +}; + +#define zrwrite(dat,adr) writel((dat),(char *) (ztv->zoran_mem+(adr))) +#define zrread(adr) readl(ztv->zoran_mem+(adr)) + +#if PDEBUG == 0 +#define zrand(dat,adr) zrwrite((dat) & zrread(adr), adr) +#define zror(dat,adr) zrwrite((dat) | zrread(adr), adr) +#define zraor(dat,mask,adr) zrwrite( ((dat)&~(mask)) | ((mask)&zrread(adr)), adr) +#else +#define zrand(dat, adr) \ +do { \ + ulong data = (dat) & zrread((adr)); \ + zrwrite(data, (adr)); \ + if (0 != (~(dat) & zrread((adr)))) \ + printk(KERN_DEBUG "zoran: zrand at %d(%d) detected set bits(%x)\n", __LINE__, (adr), (dat)); \ +} while(0) + +#define zror(dat, adr) \ +do { \ + ulong data = (dat) | zrread((adr)); \ + zrwrite(data, (adr)); \ + if ((dat) != ((dat) & zrread(adr))) \ + printk(KERN_DEBUG "zoran: zror at %d(%d) detected unset bits(%x)\n", __LINE__, (adr), (dat)); \ +} while(0) + +#define zraor(dat, mask, adr) \ +do { \ + ulong data; \ + if ((dat) & (mask)) \ + printk(KERN_DEBUG "zoran: zraor at %d(%d) detected bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \ + data = ((dat)&~(mask)) | ((mask) & zrread((adr))); \ + zrwrite(data,(adr)); \ + if ( (dat) != (~(mask) & zrread((adr))) ) \ + printk(KERN_DEBUG "zoran: zraor at %d(%d) could not set all bits(%x:%x)\n", __LINE__, (adr), (dat), (mask)); \ +} while(0) +#endif + +#endif + +/* zoran PCI address space */ +#define ZORAN_VFEH 0x000 /* Video Front End Horizontal Conf. */ +#define ZORAN_VFEH_HSPOL (1<<30) +#define ZORAN_VFEH_HSTART (0x3FF<<10) +#define ZORAN_VFEH_HEND (0x3FF<<0) + +#define ZORAN_VFEV 0x004 /* Video Front End Vertical Conf. */ +#define ZORAN_VFEV_VSPOL (1<<30) +#define ZORAN_VFEV_VSTART (0x3FF<<10) +#define ZORAN_VFEV_VEND (0x3FF<<0) + +#define ZORAN_VFEC 0x008 /* Video Front End Scaler and Pixel */ +#define ZORAN_VFEC_EXTFL (1<<26) +#define ZORAN_VFEC_TOPFIELD (1<<25) +#define ZORAN_VFEC_VCLKPOL (1<<24) +#define ZORAN_VFEC_HFILTER (7<<21) +#define ZORAN_VFEC_HFILTER_1 (0<<21) /* no lumi, 3-tap chromo */ +#define ZORAN_VFEC_HFILTER_2 (1<<21) /* 3-tap lumi, 3-tap chromo */ +#define ZORAN_VFEC_HFILTER_3 (2<<21) /* 4-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_HFILTER_4 (3<<21) /* 5-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_HFILTER_5 (4<<21) /* 4-tap lumi, 4-tap chromo */ +#define ZORAN_VFEC_DUPFLD (1<<20) +#define ZORAN_VFEC_HORDCM (63<<14) +#define ZORAN_VFEC_VERDCM (63<<8) +#define ZORAN_VFEC_DISPMOD (1<<6) +#define ZORAN_VFEC_RGB (3<<3) +#define ZORAN_VFEC_RGB_YUV422 (0<<3) +#define ZORAN_VFEC_RGB_RGB888 (1<<3) +#define ZORAN_VFEC_RGB_RGB565 (2<<3) +#define ZORAN_VFEC_RGB_RGB555 (3<<3) +#define ZORAN_VFEC_ERRDIF (1<<2) +#define ZORAN_VFEC_PACK24 (1<<1) +#define ZORAN_VFEC_LE (1<<0) + +#define ZORAN_VTOP 0x00C /* Video Display "Top" */ + +#define ZORAN_VBOT 0x010 /* Video Display "Bottom" */ + +#define ZORAN_VSTR 0x014 /* Video Display Stride */ +#define ZORAN_VSTR_DISPSTRIDE (0xFFFF<<16) +#define ZORAN_VSTR_VIDOVF (1<<8) +#define ZORAN_VSTR_SNAPSHOT (1<<1) +#define ZORAN_VSTR_GRAB (1<<0) + +#define ZORAN_VDC 0x018 /* Video Display Conf. */ +#define ZORAN_VDC_VIDEN (1<<31) +#define ZORAN_VDC_MINPIX (0x1F<<25) +#define ZORAN_VDC_TRICOM (1<<24) +#define ZORAN_VDC_VIDWINHT (0x3FF<<12) +#define ZORAN_VDC_VIDWINWID (0x3FF<<0) + +#define ZORAN_MTOP 0x01C /* Masking Map "Top" */ + +#define ZORAN_MBOT 0x020 /* Masking Map "Bottom" */ + +#define ZORAN_OCR 0x024 /* Overlay Control */ +#define ZORAN_OCR_OVLEN (1<<15) +#define ZORAN_OCR_MASKSTRIDE (0xFF<<0) + +#define ZORAN_PCI 0x028 /* System, PCI and GPP Control */ +#define ZORAN_PCI_SOFTRESET (1<<24) +#define ZORAN_PCI_WAITSTATE (3<<16) +#define ZORAN_PCI_GENPURDIR (0xFF<<0) + +#define ZORAN_GUEST 0x02C /* GuestBus Control */ + +#define ZORAN_CSOURCE 0x030 /* Code Source Address */ + +#define ZORAN_CTRANS 0x034 /* Code Transfer Control */ + +#define ZORAN_CMEM 0x038 /* Code Memory Pointer */ + +#define ZORAN_ISR 0x03C /* Interrupt Status Register */ +#define ZORAN_ISR_CODE (1<<28) +#define ZORAN_ISR_GIRQ0 (1<<29) +#define ZORAN_ISR_GIRQ1 (1<<30) + +#define ZORAN_ICR 0x040 /* Interrupt Control Register */ +#define ZORAN_ICR_EN (1<<24) +#define ZORAN_ICR_CODE (1<<28) +#define ZORAN_ICR_GIRQ0 (1<<29) +#define ZORAN_ICR_GIRQ1 (1<<30) + +#define ZORAN_I2C 0x044 /* I2C-Bus */ +#define ZORAN_I2C_SCL (1<<1) +#define ZORAN_I2C_SDA (1<<0) + +#define ZORAN_POST 0x48 /* PostOffice */ +#define ZORAN_POST_PEN (1<<25) +#define ZORAN_POST_TIME (1<<24) +#define ZORAN_POST_DIR (1<<23) +#define ZORAN_POST_GUESTID (3<<20) +#define ZORAN_POST_GUEST (7<<16) +#define ZORAN_POST_DATA (0xFF<<0) + +#endif diff --git a/drivers/media/video/zr36120_i2c.c b/drivers/media/video/zr36120_i2c.c new file mode 100644 index 000000000..0de59b9f1 --- /dev/null +++ b/drivers/media/video/zr36120_i2c.c @@ -0,0 +1,133 @@ +/* + zr36120_i2c.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/types.h> +#include <linux/delay.h> +#include <asm/io.h> + +#include <linux/version.h> +#include <linux/video_decoder.h> +#include <asm/uaccess.h> + +#include "tuner.h" +#include "zr36120.h" + +/* ----------------------------------------------------------------------- */ +/* I2C functions */ +/* ----------------------------------------------------------------------- */ + +/* software I2C functions */ + +#define I2C_DELAY 10 + +static void i2c_setlines(struct i2c_bus *bus,int ctrl,int data) +{ + struct zoran *ztv = (struct zoran*)bus->data; + unsigned int b = 0; + if (data) b |= ztv->card->swapi2c ? ZORAN_I2C_SCL : ZORAN_I2C_SDA; + if (ctrl) b |= ztv->card->swapi2c ? ZORAN_I2C_SDA : ZORAN_I2C_SCL; + zrwrite(b, ZORAN_I2C); + udelay(I2C_DELAY); +} + +static int i2c_getdataline(struct i2c_bus *bus) +{ + struct zoran *ztv = (struct zoran*)bus->data; + if (ztv->card->swapi2c) + return zrread(ZORAN_I2C) & ZORAN_I2C_SCL; + return zrread(ZORAN_I2C) & ZORAN_I2C_SDA; +} + +static +void attach_inform(struct i2c_bus *bus, int id) +{ + struct zoran *ztv = (struct zoran*)bus->data; + struct video_decoder_capability dc; + int rv; + + switch (id) { + case I2C_DRIVERID_VIDEODECODER: + DEBUG(printk(CARD_INFO "decoder attached\n",CARD)); + + /* fetch the capabilites of the decoder */ + rv = i2c_control_device(&ztv->i2c, I2C_DRIVERID_VIDEODECODER, DECODER_GET_CAPABILITIES, &dc); + if (rv) { + DEBUG(printk(CARD_DEBUG "decoder is not V4L aware!\n",CARD)); + break; + } + DEBUG(printk(CARD_DEBUG "capabilities %d %d %d\n",CARD,dc.flags,dc.inputs,dc.outputs)); + + /* Test if the decoder can de VBI transfers */ + if (dc.flags & 16 /*VIDEO_DECODER_VBI*/) + ztv->have_decoder = 2; + else + ztv->have_decoder = 1; + break; + case I2C_DRIVERID_TUNER: + ztv->have_tuner = 1; + DEBUG(printk(CARD_INFO "tuner attached\n",CARD)); + if (ztv->tuner_type >= 0) + { + if (i2c_control_device(&ztv->i2c,I2C_DRIVERID_TUNER,TUNER_SET_TYPE,&ztv->tuner_type)<0) + DEBUG(printk(CARD_INFO "attach_inform; tuner wont be set to type %d\n",CARD,ztv->tuner_type)); + } + break; + default: + DEBUG(printk(CARD_INFO "attach_inform; unknown device id=%d\n",CARD,id)); + break; + } +} + +static +void detach_inform(struct i2c_bus *bus, int id) +{ + struct zoran *ztv = (struct zoran*)bus->data; + + switch (id) { + case I2C_DRIVERID_VIDEODECODER: + ztv->have_decoder = 0; + DEBUG(printk(CARD_INFO "decoder detached\n",CARD)); + break; + case I2C_DRIVERID_TUNER: + ztv->have_tuner = 0; + DEBUG(printk(CARD_INFO "tuner detached\n",CARD)); + break; + default: + DEBUG(printk(CARD_INFO "detach_inform; unknown device id=%d\n",CARD,id)); + break; + } +} + +struct i2c_bus zoran_i2c_bus_template = +{ + "ZR36120", + I2C_BUSID_ZORAN, + NULL, + + SPIN_LOCK_UNLOCKED, + + attach_inform, + detach_inform, + + i2c_setlines, + i2c_getdataline, + NULL, + NULL +}; diff --git a/drivers/media/video/zr36120_mem.c b/drivers/media/video/zr36120_mem.c new file mode 100644 index 000000000..b4c6078d3 --- /dev/null +++ b/drivers/media/video/zr36120_mem.c @@ -0,0 +1,77 @@ +/* + zr36120_mem.c - Zoran 36120/36125 based framegrabbers + + Copyright (C) 1998-1999 Pauline Middelink <middelin@polyware.nl> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/config.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/wrapper.h> +#include <linux/slab.h> +#include <asm/io.h> +#ifdef CONFIG_BIGPHYS_AREA +#include <linux/bigphysarea.h> +#endif + +#include "zr36120.h" +#include "zr36120_mem.h" + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +void* bmalloc(unsigned long size) +{ + void* mem; +#ifdef CONFIG_BIGPHYS_AREA + mem = bigphysarea_alloc_pages(size/PAGE_SIZE, 1, GFP_KERNEL); +#else + /* + * The following function got a lot of memory at boottime, + * so we know its always there... + */ + mem = (void*)__get_free_pages(GFP_USER|GFP_DMA,get_order(size)); +#endif + if (mem) { + unsigned long adr = (unsigned long)mem; + while (size > 0) { + mem_map_reserve(virt_to_page(phys_to_virt(adr))); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } + return mem; +} + +void bfree(void* mem, unsigned long size) +{ + if (mem) { + unsigned long adr = (unsigned long)mem; + unsigned long siz = size; + while (siz > 0) { + mem_map_unreserve(virt_to_page(phys_to_virt(adr))); + adr += PAGE_SIZE; + siz -= PAGE_SIZE; + } +#ifdef CONFIG_BIGPHYS_AREA + bigphysarea_free_pages(mem); +#else + free_pages((unsigned long)mem,get_order(size)); +#endif + } +} diff --git a/drivers/media/video/zr36120_mem.h b/drivers/media/video/zr36120_mem.h new file mode 100644 index 000000000..aad117acc --- /dev/null +++ b/drivers/media/video/zr36120_mem.h @@ -0,0 +1,3 @@ +/* either kmalloc() or bigphysarea() alloced memory - continuous */ +void* bmalloc(unsigned long size); +void bfree(void* mem, unsigned long size); |