diff options
Diffstat (limited to 'linux')
91 files changed, 69325 insertions, 0 deletions
diff --git a/linux/drivers/media/radio/Kconfig b/linux/drivers/media/radio/Kconfig new file mode 100644 index 000000000..d318be383 --- /dev/null +++ b/linux/drivers/media/radio/Kconfig @@ -0,0 +1,354 @@ +# +# Multimedia Video device configuration +# + +menu "Radio Adapters" + depends on VIDEO_DEV!=n + +config RADIO_CADET + tristate "ADS Cadet AM/FM Tuner" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these AM/FM radio cards, and then + fill in the port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + Further documentation on this driver can be found on the WWW at + <http://linux.blackhawke.net/cadet/>. + + To compile this driver as a module, choose M here: the + module will be called radio-cadet. + +config RADIO_RTRACK + tristate "AIMSlab RadioTrack (aka RadioReveal) support" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards, and then fill + in the port address below. + + Note that newer AIMSlab RadioTrack cards have a different chipset + and are not supported by this driver. For these cards, use the + RadioTrack II driver below. + + If you have a GemTeks combined (PnP) sound- and radio card you must + use this driver as a module and setup the card with isapnptools. + You must also pass the module a suitable io parameter, 0x248 has + been reported to be used by these cards. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. More information is + contained in the file + <file:Documentation/video4linux/radiotrack.txt>. + + To compile this driver as a module, choose M here: the + module will be called radio-aimslab. + +config RADIO_RTRACK_PORT + hex "RadioTrack i/o port (0x20f or 0x30f)" + depends on RADIO_RTRACK=y + default "20f" + help + Enter either 0x30f or 0x20f here. The card default is 0x30f, if you + haven't changed the jumper setting on the card. + +config RADIO_RTRACK2 + tristate "AIMSlab RadioTrack II support" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have this FM radio card, and then fill in the + port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-rtrack2. + +config RADIO_RTRACK2_PORT + hex "RadioTrack II i/o port (0x20c or 0x30c)" + depends on RADIO_RTRACK2=y + default "30c" + help + Enter either 0x30c or 0x20c here. The card default is 0x30c, if you + haven't changed the jumper setting on the card. + +config RADIO_AZTECH + tristate "Aztech/Packard Bell Radio" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards, and then fill + in the port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-aztech. + +config RADIO_AZTECH_PORT + hex "Aztech/Packard Bell I/O port (0x350 or 0x358)" + depends on RADIO_AZTECH=y + default "350" + help + Enter either 0x350 or 0x358 here. The card default is 0x350, if you + haven't changed the setting of jumper JP3 on the card. Removing the + jumper sets the card to 0x358. + +config RADIO_GEMTEK + tristate "GemTek Radio Card support" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have this FM radio card, and then fill in the + port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-gemtek. + +config RADIO_GEMTEK_PORT + hex "GemTek i/o port (0x20c, 0x30c, 0x24c or 0x34c)" + depends on RADIO_GEMTEK=y + default "34c" + help + Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is + 0x34c, if you haven't changed the jumper setting on the card. On + Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O + port is 0x28c. + +config RADIO_GEMTEK_PCI + tristate "GemTek PCI Radio Card support" + depends on VIDEO_DEV && PCI + ---help--- + Choose Y here if you have this PCI FM radio card. + + In order to control your radio card, you will need to use programs + that are compatible with the Video for Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-gemtek-pci. + +config RADIO_MAXIRADIO + tristate "Guillemot MAXI Radio FM 2000 radio" + depends on VIDEO_DEV && PCI + ---help--- + Choose Y here if you have this radio card. This card may also be + found as Gemtek PCI FM. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-maxiradio. + +config RADIO_MAESTRO + tristate "Maestro on board radio" + depends on VIDEO_DEV + ---help--- + Say Y here to directly support the on-board radio tuner on the + Maestro 2 or 2E sound card. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-maestro. + +config RADIO_MIROPCM20 + tristate "miroSOUND PCM20 radio" + depends on ISA && VIDEO_DEV && SOUND_ACI_MIXER + ---help--- + Choose Y here if you have this FM radio card. You also need to say Y + to "ACI mixer (miroSOUND PCM1-pro/PCM12/PCM20 radio)" (in "Sound") + for this to work. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called miropcm20. + +config RADIO_MIROPCM20_RDS + tristate "miroSOUND PCM20 radio RDS user interface (EXPERIMENTAL)" + depends on RADIO_MIROPCM20 && EXPERIMENTAL + ---help--- + Choose Y here if you want to see RDS/RBDS information like + RadioText, Programme Service name, Clock Time and date, Programme + TYpe and Traffic Announcement/Programme identification. You also + need to say Y to "miroSOUND PCM20 radio" and devfs! + + It's not possible to read the raw RDS packets from the device, so + the driver cant provide an V4L interface for this. But the + availability of RDS is reported over V4L by the basic driver + already. Here RDS can be read from files in /dev/v4l/rds. + + To compile this driver as a module, choose M here: the + module will be called miropcm20-rds. + +config RADIO_SF16FMI + tristate "SF16FMI Radio" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards. If you + compile the driver into the kernel and your card is not PnP one, you + have to add "sf16fm=<io>" to the kernel command line (I/O address is + 0x284 or 0x384). + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-sf16fmi. + +config RADIO_SF16FMR2 + tristate "SF16FMR2 Radio" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found on the WWW at + <http://roadrunner.swansea.uk.linux.org/v4l.shtml>. + + To compile this driver as a module, choose M here: the + module will be called radio-sf16fmr2. + +config RADIO_TERRATEC + tristate "TerraTec ActiveRadio ISA Standalone" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have this FM radio card, and then fill in the + port address below. (TODO) + + Note: This driver is in its early stages. Right now volume and + frequency control and muting works at least for me, but + unfortunately I have not found anybody who wants to use this card + with Linux. So if it is this what YOU are trying to do right now, + PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-terratec. + +config RADIO_TERRATEC_PORT + hex "Terratec i/o port (normally 0x590)" + depends on RADIO_TERRATEC=y + default "590" + help + Fill in the I/O port of your TerraTec FM radio card. If unsure, go + with the default. + +config RADIO_TRUST + tristate "Trust FM radio card" + depends on ISA && VIDEO_DEV + help + This is a driver for the Trust FM radio cards. Say Y if you have + such a card and want to use it under Linux. + + To compile this driver as a module, choose M here: the + module will be called radio-trust. + +config RADIO_TRUST_PORT + hex "Trust i/o port (usually 0x350 or 0x358)" + depends on RADIO_TRUST=y + default "350" + help + Enter the I/O port of your Trust FM radio card. If unsure, try the + values "0x350" or "0x358". + +config RADIO_TYPHOON + tristate "Typhoon Radio (a.k.a. EcoRadio)" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards, and then fill + in the port address and the frequency used for muting below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-typhoon. + +config RADIO_TYPHOON_PROC_FS + bool "Support for /proc/radio-typhoon" + depends on PROC_FS && RADIO_TYPHOON + help + Say Y here if you want the typhoon radio card driver to write + status information (frequency, volume, muted, mute frequency, + base address) to /proc/radio-typhoon. The file can be viewed with + your favorite pager (i.e. use "more /proc/radio-typhoon" or "less + /proc/radio-typhoon" or simply "cat /proc/radio-typhoon"). + +config RADIO_TYPHOON_PORT + hex "Typhoon I/O port (0x316 or 0x336)" + depends on RADIO_TYPHOON=y + default "316" + help + Enter the I/O port of your Typhoon or EcoRadio radio card. + +config RADIO_TYPHOON_MUTEFREQ + int "Typhoon frequency set when muting the device (kHz)" + depends on RADIO_TYPHOON=y + default "87500" + help + Enter the frequency used for muting the radio. The device is never + completely silent. If the volume is just turned down, you can still + hear silent voices and music. For that reason, the frequency of the + radio device is set to the frequency you can enter here whenever + the device is muted. There should be no local radio station at that + frequency. + +config RADIO_ZOLTRIX + tristate "Zoltrix Radio" + depends on ISA && VIDEO_DEV + ---help--- + Choose Y here if you have one of these FM radio cards, and then fill + in the port address below. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-zoltrix. + +config RADIO_ZOLTRIX_PORT + hex "ZOLTRIX I/O port (0x20c or 0x30c)" + depends on RADIO_ZOLTRIX=y + default "20c" + help + Enter the I/O port of your Zoltrix radio card. + +endmenu + diff --git a/linux/drivers/media/radio/Makefile b/linux/drivers/media/radio/Makefile new file mode 100644 index 000000000..8b351945d --- /dev/null +++ b/linux/drivers/media/radio/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for the kernel character device drivers. +# + +miropcm20-objs := miropcm20-rds-core.o miropcm20-radio.o + +obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o +obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o +obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o +obj-$(CONFIG_RADIO_SF16FMR2) += radio-sf16fmr2.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_MAXIRADIO) += radio-maxiradio.o +obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o +obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o +obj-$(CONFIG_RADIO_MIROPCM20) += miropcm20.o +obj-$(CONFIG_RADIO_MIROPCM20_RDS) += miropcm20-rds.o +obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o +obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o +obj-$(CONFIG_RADIO_TRUST) += radio-trust.o +obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o diff --git a/linux/drivers/media/radio/miropcm20-radio.c b/linux/drivers/media/radio/miropcm20-radio.c new file mode 100644 index 000000000..c305ecbc0 --- /dev/null +++ b/linux/drivers/media/radio/miropcm20-radio.c @@ -0,0 +1,266 @@ +/* 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/aci.c) + * Look there for further info... + */ + +/* Revision history: + * + * 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> + * 2000-09-05 Robert Siemer <Robert.Siemer@gmx.de> + * removed unfinished volume control (maybe adding it later again) + * use OSS-mixer; added stereo control + */ + +/* What ever you think about the ACI, version 0x07 is not very well! + * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono + * conditions... Robert + */ + +#include <linux/module.h> +#include <linux/init.h> +#include "compat.h" +#include <linux/videodev.h> +#include "../../../sound/oss/aci.h" +#include "miropcm20-rds-core.h" + +static int radio_nr = -1; +module_param(radio_nr, int, 0); + +struct pcm20_device { + unsigned long freq; + int muted; + int stereo; +}; + + +static int pcm20_mute(struct pcm20_device *dev, unsigned char mute) +{ + dev->muted = mute; + return aci_write_cmd(ACI_SET_TUNERMUTE, mute); +} + +static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo) +{ + dev->stereo = stereo; + return aci_write_cmd(ACI_SET_TUNERMONO, !stereo); +} + +static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq) +{ + unsigned char freql; + unsigned char freqh; + + dev->freq=freq; + + freq /= 160; + if (!(aci_version==0x07 || aci_version>=0xb0)) + freq /= 10; /* I don't know exactly which version + * needs this hack */ + freql = freq & 0xff; + freqh = freq >> 8; + + aci_rds_cmd(RDS_RESET, NULL, 0); + pcm20_stereo(dev, 1); + + return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh); +} + +static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal) +{ + /* okay, check for signal, stereo and rds here... */ + int i; + unsigned char buf; + + if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0) + return i; + pr_debug("check_sig: 0x%x\n", i); + if (i & 0x80) { + /* no signal from tuner */ + *flags=0; + *signal=0; + return 0; + } else + *signal=0xffff; + + if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0) + return i; + if (i & 0x40) { + *flags=0; + } else { + /* stereo */ + *flags=VIDEO_TUNER_STEREO_ON; + /* I can't see stereo, when forced to mono */ + dev->stereo=1; + } + + if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0) + return i; + if (buf & 1) + /* RDS available */ + *flags|=VIDEO_TUNER_RDS_ON; + else + return 0; + + if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0) + return i; + pr_debug("rds-signal: %d\n", buf); + if (buf > 15) { + printk("miropcm20-radio: RX strengths unexpected high...\n"); + buf=15; + } + /* refine signal */ + if ((*signal=SCALE(15, 0xffff, buf))==0) + *signal = 1; + + return 0; +} + +static int pcm20_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct pcm20_device *pcm20 = dev->priv; + int i; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + strcpy(v->name, "Miro PCM20"); + v->channels=1; + v->audios=1; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) /* Only 1 tuner */ + return -EINVAL; + v->rangelow=87*16000; + v->rangehigh=108*16000; + pcm20_getflags(pcm20, &v->flags, &v->signal); + v->flags|=VIDEO_TUNER_LOW; + v->mode=VIDEO_MODE_AUTO; + strcpy(v->name, "FM"); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = pcm20->freq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + pcm20->freq = *freq; + i=pcm20_setfreq(pcm20, pcm20->freq); + pr_debug("First view (setfreq): 0x%x\n", i); + return i; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + memset(v,0, sizeof(*v)); + v->flags=VIDEO_AUDIO_MUTABLE; + if (pcm20->muted) + v->flags|=VIDEO_AUDIO_MUTE; + v->mode=VIDEO_SOUND_STEREO; + if (pcm20->stereo) + v->mode|=VIDEO_SOUND_MONO; + /* v->step=2048; */ + strcpy(v->name, "Radio"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + if(v->audio) + return -EINVAL; + + pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE)); + if(v->flags&VIDEO_SOUND_MONO) + pcm20_stereo(pcm20, 0); + if(v->flags&VIDEO_SOUND_STEREO) + pcm20_stereo(pcm20, 1); + + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int pcm20_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl); +} + +static struct pcm20_device pcm20_unit = { + .freq = 87*16000, + .muted = 1, +}; + +static struct file_operations pcm20_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = pcm20_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device pcm20_radio = { + .owner = THIS_MODULE, + .name = "Miro PCM 20 radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_RTRACK, + .fops = &pcm20_fops, + .priv = &pcm20_unit +}; + +static int __init pcm20_init(void) +{ + if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1) + goto video_register_device; + + if(attach_aci_rds()<0) + goto attach_aci_rds; + + printk(KERN_INFO "Miro PCM20 radio card driver.\n"); + + return 0; + + attach_aci_rds: + video_unregister_device(&pcm20_radio); + video_register_device: + return -EINVAL; +} + +MODULE_AUTHOR("Ruurd Reitsma"); +MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); +MODULE_LICENSE("GPL"); + +static void __exit pcm20_cleanup(void) +{ + unload_aci_rds(); + video_unregister_device(&pcm20_radio); +} + +module_init(pcm20_init); +module_exit(pcm20_cleanup); diff --git a/linux/drivers/media/radio/miropcm20-rds-core.c b/linux/drivers/media/radio/miropcm20-rds-core.c new file mode 100644 index 000000000..b602c73e2 --- /dev/null +++ b/linux/drivers/media/radio/miropcm20-rds-core.c @@ -0,0 +1,211 @@ +/* + * Many thanks to Fred Seidel <seidel@metabox.de>, the + * designer of the RDS decoder hardware. With his help + * I was able to code this driver. + * Thanks also to Norberto Pellicci, Dominic Mounteney + * <DMounteney@pinnaclesys.com> and www.teleauskunft.de + * for good hints on finding Fred. It was somewhat hard + * to locate him here in Germany... [: + * + * Revision history: + * + * 2000-08-09 Robert Siemer <Robert.Siemer@gmx.de> + * RDS support for MiroSound PCM20 radio + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +#include <asm/io.h> +#include "../../../sound/oss/aci.h" +#include "miropcm20-rds-core.h" + +#define DEBUG 0 + +static struct mutex aci_rds_mutex; + +#define RDS_DATASHIFT 2 /* Bit 2 */ +#define RDS_DATAMASK (1 << RDS_DATASHIFT) +#define RDS_BUSYMASK 0x10 /* Bit 4 */ +#define RDS_CLOCKMASK 0x08 /* Bit 3 */ + +#define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) + + +#if DEBUG +static void print_matrix(char array[], unsigned int length) +{ + int i, j; + + for (i=0; i<length; i++) { + printk(KERN_DEBUG "aci-rds: "); + for (j=7; j>=0; j--) { + printk("%d", (array[i] >> j) & 0x1); + } + if (i%8 == 0) + printk(" byte-border\n"); + else + printk("\n"); + } +} +#endif /* DEBUG */ + +static int byte2trans(unsigned char byte, unsigned char sendbuffer[], int size) +{ + int i; + + if (size != 8) + return -1; + for (i = 7; i >= 0; i--) + sendbuffer[7-i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; + sendbuffer[0] |= RDS_CLOCKMASK; + + return 0; +} + +static int rds_waitread(void) +{ + unsigned char byte; + int i=2000; + + do { + byte=inb(RDS_REGISTER); + i--; + } + while ((byte & RDS_BUSYMASK) && i); + + if (i) { + #if DEBUG + printk(KERN_DEBUG "rds_waitread()"); + print_matrix(&byte, 1); + #endif + return (byte); + } else { + printk(KERN_WARNING "aci-rds: rds_waitread() timeout...\n"); + return -1; + } +} + +/* don't use any ..._nowait() function if you are not sure what you do... */ + +static inline void rds_rawwrite_nowait(unsigned char byte) +{ + #if DEBUG + printk(KERN_DEBUG "rds_rawwrite()"); + print_matrix(&byte, 1); + #endif + outb(byte, RDS_REGISTER); +} + +static int rds_rawwrite(unsigned char byte) +{ + if (rds_waitread() >= 0) { + rds_rawwrite_nowait(byte); + return 0; + } else + return -1; +} + +static int rds_write(unsigned char cmd) +{ + unsigned char sendbuffer[8]; + int i; + + if (byte2trans(cmd, sendbuffer, 8) != 0){ + return -1; + } else { + for (i=0; i<8; i++) { + rds_rawwrite(sendbuffer[i]); + } + } + return 0; +} + +static int rds_readcycle_nowait(void) +{ + rds_rawwrite_nowait(0); + return rds_waitread(); +} + +static int rds_readcycle(void) +{ + if (rds_rawwrite(0) < 0) + return -1; + return rds_waitread(); +} + +static int rds_read(unsigned char databuffer[], int datasize) +{ + #define READSIZE (8*datasize) + + int i,j; + + if (datasize < 1) /* nothing to read */ + return 0; + + /* to be able to use rds_readcycle_nowait() + I have to waitread() here */ + if (rds_waitread() < 0) + return -1; + + memset(databuffer, 0, datasize); + + for (i=0; i< READSIZE; i++) + if((j=rds_readcycle_nowait()) < 0) { + return -1; + } else { + databuffer[i/8]|=(RDS_DATA(j) << (7-(i%8))); + } + + return 0; +} + +static int rds_ack(void) +{ + int i=rds_readcycle(); + + if (i < 0) + return -1; + if (i & RDS_DATAMASK) { + return 0; /* ACK */ + } else { + printk(KERN_DEBUG "aci-rds: NACK\n"); + return 1; /* NACK */ + } +} + +int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize) +{ + int ret; + + if (mutex_lock_interruptible(&aci_rds_mutex)) + return -EINTR; + + rds_write(cmd); + + /* RDS_RESET doesn't need further processing */ + if (cmd!=RDS_RESET && (rds_ack() || rds_read(databuffer, datasize))) + ret = -1; + else + ret = 0; + + mutex_unlock(&aci_rds_mutex); + + return ret; +} +EXPORT_SYMBOL(aci_rds_cmd); + +int __init attach_aci_rds(void) +{ + mutex_init(&aci_rds_mutex); + return 0; +} + +void __exit unload_aci_rds(void) +{ +} +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/radio/miropcm20-rds-core.h b/linux/drivers/media/radio/miropcm20-rds-core.h new file mode 100644 index 000000000..aeb5761f0 --- /dev/null +++ b/linux/drivers/media/radio/miropcm20-rds-core.h @@ -0,0 +1,19 @@ +#ifndef _MIROPCM20_RDS_CORE_H_ +#define _MIROPCM20_RDS_CORE_H_ + +extern int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize); + +#define RDS_STATUS 0x01 +#define RDS_STATIONNAME 0x02 +#define RDS_TEXT 0x03 +#define RDS_ALTFREQ 0x04 +#define RDS_TIMEDATE 0x05 +#define RDS_PI_CODE 0x06 +#define RDS_PTYTATP 0x07 +#define RDS_RESET 0x08 +#define RDS_RXVALUE 0x09 + +extern void __exit unload_aci_rds(void); +extern int __init attach_aci_rds(void); + +#endif /* _MIROPCM20_RDS_CORE_H_ */ diff --git a/linux/drivers/media/radio/miropcm20-rds.c b/linux/drivers/media/radio/miropcm20-rds.c new file mode 100644 index 000000000..e09214082 --- /dev/null +++ b/linux/drivers/media/radio/miropcm20-rds.c @@ -0,0 +1,134 @@ +/* MiroSOUND PCM20 radio rds interface driver + * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de> + * Thanks to Fred Seidel. See miropcm20-rds-core.c for further information. + */ + +/* Revision history: + * + * 2001-04-18 Robert Siemer <Robert.Siemer@gmx.de> + * separate file for user interface driver + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/sched.h> /* current, TASK_*, schedule_timeout() */ +#include <linux/delay.h> +#include <asm/uaccess.h> +#include "miropcm20-rds-core.h" + +static char * text_buffer; +static int rds_users = 0; + + +static int rds_f_open(struct inode *in, struct file *fi) +{ + if (rds_users) + return -EBUSY; + + rds_users++; + if ((text_buffer=kmalloc(66, GFP_KERNEL)) == 0) { + rds_users--; + printk(KERN_NOTICE "aci-rds: Out of memory by open()...\n"); + return -ENOMEM; + } + + return 0; +} + +static int rds_f_release(struct inode *in, struct file *fi) +{ + kfree(text_buffer); + + rds_users--; + return 0; +} + +static void print_matrix(char *ch, char out[]) +{ + int j; + + for (j=7; j>=0; j--) { + out[7-j] = ((*ch >> j) & 0x1) + '0'; + } +} + +static ssize_t rds_f_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) +{ +// i = sprintf(text_buffer, "length: %d, offset: %d\n", length, *offset); + + char c; + char bits[8]; + + msleep(2000); + aci_rds_cmd(RDS_STATUS, &c, 1); + print_matrix(&c, bits); + if (copy_to_user(buffer, bits, 8)) + return -EFAULT; + +/* if ((c >> 3) & 1) { + aci_rds_cmd(RDS_STATIONNAME, text_buffer+1, 8); + text_buffer[0] = ' ' ; + text_buffer[9] = '\n'; + return copy_to_user(buffer+8, text_buffer, 10) ? -EFAULT: 18; + } +*/ +/* if ((c >> 6) & 1) { + aci_rds_cmd(RDS_PTYTATP, &c, 1); + if ( c & 1) + sprintf(text_buffer, " M"); + else + sprintf(text_buffer, " S"); + if ((c >> 1) & 1) + sprintf(text_buffer+2, " TA"); + else + sprintf(text_buffer+2, " --"); + if ((c >> 7) & 1) + sprintf(text_buffer+5, " TP"); + else + sprintf(text_buffer+5, " --"); + sprintf(text_buffer+8, " %2d\n", (c >> 2) & 0x1f); + return copy_to_user(buffer+8, text_buffer, 12) ? -EFAULT: 20; + } +*/ + + if ((c >> 4) & 1) { + aci_rds_cmd(RDS_TEXT, text_buffer, 65); + text_buffer[0] = ' ' ; + text_buffer[65] = '\n'; + return copy_to_user(buffer+8, text_buffer,66) ? -EFAULT : 66+8; + } else { + put_user('\n', buffer+8); + return 9; + } +} + +static struct file_operations rds_fops = { + .owner = THIS_MODULE, + .read = rds_f_read, + .open = rds_f_open, + .release = rds_f_release +}; + +static struct miscdevice rds_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "radiotext", + .devfs_name = "v4l/rds/radiotext", + .fops = &rds_fops, +}; + +static int __init miropcm20_rds_init(void) +{ + return misc_register(&rds_miscdev); +} + +static void __exit miropcm20_rds_cleanup(void) +{ + misc_deregister(&rds_miscdev); +} + +module_init(miropcm20_rds_init); +module_exit(miropcm20_rds_cleanup); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/radio/radio-aimslab.c b/linux/drivers/media/radio/radio-aimslab.c new file mode 100644 index 000000000..0fa385ef8 --- /dev/null +++ b/linux/drivers/media/radio/radio-aimslab.c @@ -0,0 +1,370 @@ +/* radiotrack (radioreveal) driver for Linux radio support + * (c) 1997 M. Kirkwood + * Converted 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> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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 radio_nr = -1; +static struct mutex 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 + msleep(jiffies_to_msecs(d)); +} + +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; + mutex_lock(&lock); + outb(0xd0, io); /* volume steady, off */ + mutex_unlock(&lock); +} + +static int rt_setvol(struct rt_device *dev, int vol) +{ + int i; + + mutex_lock(&lock); + + if(vol == dev->curvol) { /* requested volume = current */ + if (dev->muted) { /* user is unmuting the card */ + dev->muted = 0; + outb (0xd8, io); /* enable card */ + } + mutex_unlock(&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! */ + mutex_unlock(&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; + mutex_unlock(&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 + */ + +static 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); +} + +static 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 */ + + mutex_lock(&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 */ + + mutex_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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct rt_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "RadioTrack"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = rt->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + rt->curfreq = *freq; + rt_setfreq(rt, rt->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, rt_do_ioctl); +} + +static struct rt_device rtrack_unit; + +static struct file_operations rtrack_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = rt_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device rtrack_radio= +{ + .owner = THIS_MODULE, + .name = "RadioTrack radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_RTRACK, + .fops = &rtrack_fops, +}; + +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 (!request_region(io, 2, "rtrack")) + { + 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, radio_nr)==-1) + { + release_region(io, 2); + return -EINVAL; + } + printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n"); + + /* Set up the I/O locking */ + + mutex_init(&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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); +module_param(radio_nr, int, 0); + +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/linux/drivers/media/radio/radio-aztech.c b/linux/drivers/media/radio/radio-aztech.c new file mode 100644 index 000000000..97f88c907 --- /dev/null +++ b/linux/drivers/media/radio/radio-aztech.c @@ -0,0 +1,317 @@ +/* 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> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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_nr = -1; +static int radio_wait_time = 1000; +static struct mutex 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) +{ + mutex_lock(&lock); + outb (volconvert(vol), io); + mutex_unlock(&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 */ + + mutex_lock(&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); + + mutex_unlock(&lock); + + return 0; +} + +static int az_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct az_device *az = dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "Aztech Radio"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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"); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = az->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + az->curfreq = *freq; + az_setfreq(az, az->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, az_do_ioctl); +} + +static struct az_device aztech_unit; + +static struct file_operations aztech_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = az_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device aztech_radio= +{ + .owner = THIS_MODULE, + .name = "Aztech radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_AZTECH, + .fops = &aztech_fops, +}; + +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 (!request_region(io, 2, "aztech")) + { + printk(KERN_ERR "aztech: port 0x%x already in use\n", io); + return -EBUSY; + } + + mutex_init(&lock); + aztech_radio.priv=&aztech_unit; + + if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1) + { + release_region(io,2); + return -EINVAL; + } + + 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_LICENSE("GPL"); + +module_param(io, int, 0); +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); + +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/linux/drivers/media/radio/radio-cadet.c b/linux/drivers/media/radio/radio-cadet.c new file mode 100644 index 000000000..559be5d00 --- /dev/null +++ b/linux/drivers/media/radio/radio-cadet.c @@ -0,0 +1,622 @@ +/* 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) + * + * History: + * 2000-04-29 Russell Kroll <rkroll@exploits.org> + * Added ISAPnP detection for Linux 2.3/2.4 + * + * 2001-01-10 Russell Kroll <rkroll@exploits.org> + * Removed dead CONFIG_RADIO_CADET_PORT code + * PnP detection on load is now default (no args necessary) + * + * 2002-01-17 Adam Belay <ambx1@neo.rr.com> + * Updated to latest pnp code + * + * 2003-01-31 Alan Cox <alan@redhat.com> + * Cleaned up locking, delay code, general odds and ends + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/param.h> +#include <linux/pnp.h> + +#define RDS_BUFFER 256 + +static int io=-1; /* default to isapnp activation */ +static int radio_nr = -1; +static int users=0; +static int curtuner=0; +static int tunestat=0; +static int sigstrength=0; +static wait_queue_head_t read_queue; +static struct timer_list readtimer; +static __u8 rdsin=0,rdsout=0,rdsstat=0; +static unsigned char rdsbuf[RDS_BUFFER]; +static spinlock_t cadet_io_lock; + +static int cadet_probe(void); + +/* + * 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}}; + +static int cadet_getrds(void) +{ + int rdsstat=0; + + spin_lock(&cadet_io_lock); + outb(3,io); /* Select Decoder Control/Status */ + outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */ + spin_unlock(&cadet_io_lock); + + msleep(100); + + spin_lock(&cadet_io_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; + } + spin_unlock(&cadet_io_lock); + return rdsstat; +} + +static int cadet_getstereo(void) +{ + int ret = 0; + if(curtuner != 0) /* Only FM has stereo capability! */ + return 0; + + spin_lock(&cadet_io_lock); + outb(7,io); /* Select tuner control */ + if( (inb(io+1) & 0x40) == 0) + ret = 1; + spin_unlock(&cadet_io_lock); + return ret; +} + +static unsigned cadet_gettune(void) +{ + int curvol,i; + unsigned fifo=0; + + /* + * Prepare for read + */ + + spin_lock(&cadet_io_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); + spin_unlock(&cadet_io_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; + + spin_lock(&cadet_io_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); + } + spin_unlock(&cadet_io_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 + */ + + spin_lock(&cadet_io_lock); + outb(7,io); /* Select tuner control */ + curvol=inb(io+1); + spin_unlock(&cadet_io_lock); + + /* + * Tune the card + */ + for(j=3;j>-1;j--) { + cadet_settune(fifo|(j<<16)); + + spin_lock(&cadet_io_lock); + outb(7,io); /* Select tuner control */ + outb(curvol,io+1); + spin_unlock(&cadet_io_lock); + + msleep(100); + + cadet_gettune(); + if((tunestat & 0x40) == 0) { /* Tuned */ + sigstrength=sigtable[curtuner][j]; + return; + } + } + sigstrength=0; +} + + +static int cadet_getvol(void) +{ + int ret = 0; + + spin_lock(&cadet_io_lock); + + outb(7,io); /* Select tuner control */ + if((inb(io + 1) & 0x20) != 0) + ret = 0xffff; + + spin_unlock(&cadet_io_lock); + return ret; +} + + +static void cadet_setvol(int vol) +{ + spin_lock(&cadet_io_lock); + outb(7,io); /* Select tuner control */ + if(vol>0) + outb(0x20,io+1); + else + outb(0x00,io+1); + spin_unlock(&cadet_io_lock); +} + +static void cadet_handler(unsigned long data) +{ + /* + * Service the RDS fifo + */ + + if(spin_trylock(&cadet_io_lock)) + { + 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_WARNING "cadet: RDS buffer overflow\n"); + else + rdsin++; + } + spin_unlock(&cadet_io_lock); + } + + /* + * Service pending read + */ + if( rdsin!=rdsout) + wake_up_interruptible(&read_queue); + + /* + * 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 ssize_t cadet_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + int i=0; + unsigned char readbuf[RDS_BUFFER]; + + if(rdsstat==0) { + spin_lock(&cadet_io_lock); + rdsstat=1; + outb(0x80,io); /* Select RDS fifo */ + spin_unlock(&cadet_io_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 (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + interruptible_sleep_on(&read_queue); + } + while( i<count && rdsin!=rdsout) + readbuf[i++]=rdsbuf[rdsout++]; + + if (copy_to_user(data,readbuf,i)) + return -EFAULT; + return i; +} + + + +static int cadet_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=2; + v->audios=1; + strcpy(v->name, "ADS Cadet"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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(); + 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; + break; + } + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if((v->tuner<0)||(v->tuner>1)) { + return -EINVAL; + } + curtuner=v->tuner; + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = cadet_getfreq(); + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + 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 = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl); +} + +static int cadet_open(struct inode *inode, struct file *file) +{ + if(users) + return -EBUSY; + users++; + init_waitqueue_head(&read_queue); + return 0; +} + +static int cadet_release(struct inode *inode, struct file *file) +{ + del_timer_sync(&readtimer); + rdsstat=0; + users--; + return 0; +} + + +static struct file_operations cadet_fops = { + .owner = THIS_MODULE, + .open = cadet_open, + .release = cadet_release, + .read = cadet_read, + .ioctl = cadet_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device cadet_radio= +{ + .owner = THIS_MODULE, + .name = "Cadet radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_CADET, + .fops = &cadet_fops, +}; + +static struct pnp_device_id cadet_pnp_devices[] = { + /* ADS Cadet AM/FM Radio Card */ + {.id = "MSM0c24", .driver_data = 0}, + {.id = ""} +}; + +MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices); + +static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) +{ + if (!dev) + return -ENODEV; + /* only support one device */ + if (io > 0) + return -EBUSY; + + if (!pnp_port_valid(dev, 0)) { + return -ENODEV; + } + + io = pnp_port_start(dev, 0); + + printk ("radio-cadet: PnP reports device at %#x\n", io); + + return io; +} + +static struct pnp_driver cadet_pnp_driver = { + .name = "radio-cadet", + .id_table = cadet_pnp_devices, + .probe = cadet_pnp_probe, + .remove = NULL, +}; + +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 (request_region(io, 2, "cadet-probe")) { + cadet_setfreq(1410); + if(cadet_getfreq()==1410) { + release_region(io, 2); + return io; + } + release_region(io, 2); + } + } + return -1; +} + +/* + * io should only be set if the user has used something like + * isapnp (the userspace program) to initialize this card for us + */ + +static int __init cadet_init(void) +{ + spin_lock_init(&cadet_io_lock); + + /* + * If a probe was requested then probe ISAPnP first (safest) + */ + if (io < 0) + pnp_register_driver(&cadet_pnp_driver); + /* + * If that fails then probe unsafely if probe is requested + */ + if(io < 0) + io = cadet_probe (); + + /* + * Else we bail out + */ + + if(io < 0) { +#ifdef MODULE + printk(KERN_ERR "You must set an I/O address with io=0x???\n"); +#endif + goto fail; + } + if (!request_region(io,2,"cadet")) + goto fail; + if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) { + release_region(io,2); + goto fail; + } + printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io); + return 0; +fail: + pnp_unregister_driver(&cadet_pnp_driver); + return -1; +} + + + +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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)"); +module_param(radio_nr, int, 0); + +static void __exit cadet_cleanup_module(void) +{ + video_unregister_device(&cadet_radio); + release_region(io,2); + pnp_unregister_driver(&cadet_pnp_driver); +} + +module_init(cadet_init); +module_exit(cadet_cleanup_module); + diff --git a/linux/drivers/media/radio/radio-gemtek-pci.c b/linux/drivers/media/radio/radio-gemtek-pci.c new file mode 100644 index 000000000..c8777e953 --- /dev/null +++ b/linux/drivers/media/radio/radio-gemtek-pci.c @@ -0,0 +1,417 @@ +/* + *************************************************************************** + * + * radio-gemtek-pci.c - Gemtek PCI Radio driver + * (C) 2001 Vladimir Shebordaev <vshebordaev@mail.ru> + * + *************************************************************************** + * + * 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. + * + *************************************************************************** + * + * Gemtek Corp still silently refuses to release any specifications + * of their multimedia devices, so the protocol still has to be + * reverse engineered. + * + * The v4l code was inspired by Jonas Munsin's Gemtek serial line + * radio device driver. + * + * Please, let me know if this piece of code was useful :) + * + * TODO: multiple device support and portability were not tested + * + *************************************************************************** + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/errno.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#ifndef PCI_VENDOR_ID_GEMTEK +#define PCI_VENDOR_ID_GEMTEK 0x5046 +#endif + +#ifndef PCI_DEVICE_ID_GEMTEK_PR103 +#define PCI_DEVICE_ID_GEMTEK_PR103 0x1001 +#endif + +#ifndef GEMTEK_PCI_RANGE_LOW +#define GEMTEK_PCI_RANGE_LOW (87*16000) +#endif + +#ifndef GEMTEK_PCI_RANGE_HIGH +#define GEMTEK_PCI_RANGE_HIGH (108*16000) +#endif + +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +struct gemtek_pci_card { + struct video_device *videodev; + + u32 iobase; + u32 length; + u8 chiprev; + u16 model; + + u32 current_frequency; + u8 mute; +}; + +static const char rcsid[] = "$Id: radio-gemtek-pci.c,v 1.1 2001/07/23 08:08:16 ted Exp ted $"; + +static int nr_radio = -1; + +static inline u8 gemtek_pci_out( u16 value, u32 port ) +{ + outw( value, port ); + + return (u8)value; +} + +#define _b0( v ) *((u8 *)&v) +static void __gemtek_pci_cmd( u16 value, u32 port, u8 *last_byte, int keep ) +{ + register u8 byte = *last_byte; + + if ( !value ) { + if ( !keep ) + value = (u16)port; + byte &= 0xfd; + } else + byte |= 2; + + _b0( value ) = byte; + outw( value, port ); + byte |= 1; + _b0( value ) = byte; + outw( value, port ); + byte &= 0xfe; + _b0( value ) = byte; + outw( value, port ); + + *last_byte = byte; +} + +static inline void gemtek_pci_nil( u32 port, u8 *last_byte ) +{ + __gemtek_pci_cmd( 0x00, port, last_byte, FALSE ); +} + +static inline void gemtek_pci_cmd( u16 cmd, u32 port, u8 *last_byte ) +{ + __gemtek_pci_cmd( cmd, port, last_byte, TRUE ); +} + +static void gemtek_pci_setfrequency( struct gemtek_pci_card *card, unsigned long frequency ) +{ + register int i; + register u32 value = frequency / 200 + 856; + register u16 mask = 0x8000; + u8 last_byte; + u32 port = card->iobase; + + last_byte = gemtek_pci_out( 0x06, port ); + + i = 0; + do { + gemtek_pci_nil( port, &last_byte ); + i++; + } while ( i < 9 ); + + i = 0; + do { + gemtek_pci_cmd( value & mask, port, &last_byte ); + mask >>= 1; + i++; + } while ( i < 16 ); + + outw( 0x10, port ); +} + + +static inline void gemtek_pci_mute( struct gemtek_pci_card *card ) +{ + outb( 0x1f, card->iobase ); + card->mute = TRUE; +} + +static inline void gemtek_pci_unmute( struct gemtek_pci_card *card ) +{ + if ( card->mute ) { + gemtek_pci_setfrequency( card, card->current_frequency ); + card->mute = FALSE; + } +} + +static inline unsigned int gemtek_pci_getsignal( struct gemtek_pci_card *card ) +{ + return ( inb( card->iobase ) & 0x08 ) ? 0 : 1; +} + +static int gemtek_pci_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct gemtek_pci_card *card = dev->priv; + + switch ( cmd ) { + case VIDIOCGCAP: + { + struct video_capability *c = arg; + + memset(c,0,sizeof(*c)); + c->type = VID_TYPE_TUNER; + c->channels = 1; + c->audios = 1; + strcpy( c->name, "Gemtek PCI Radio" ); + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner *t = arg; + + if ( t->tuner ) + return -EINVAL; + + t->rangelow = GEMTEK_PCI_RANGE_LOW; + t->rangehigh = GEMTEK_PCI_RANGE_HIGH; + t->flags = VIDEO_TUNER_LOW; + t->mode = VIDEO_MODE_AUTO; + t->signal = 0xFFFF * gemtek_pci_getsignal( card ); + strcpy( t->name, "FM" ); + return 0; + } + + case VIDIOCSTUNER: + { + struct video_tuner *t = arg; + if ( t->tuner ) + return -EINVAL; + return 0; + } + + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = card->current_frequency; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + + if ( (*freq < GEMTEK_PCI_RANGE_LOW) || + (*freq > GEMTEK_PCI_RANGE_HIGH) ) + return -EINVAL; + + gemtek_pci_setfrequency( card, *freq ); + card->current_frequency = *freq; + card->mute = FALSE; + + return 0; + } + + case VIDIOCGAUDIO: + { + struct video_audio *a = arg; + + memset( a, 0, sizeof( *a ) ); + a->flags |= VIDEO_AUDIO_MUTABLE; + a->volume = 1; + a->step = 65535; + strcpy( a->name, "Radio" ); + return 0; + } + + case VIDIOCSAUDIO: + { + struct video_audio *a = arg; + + if ( a->audio ) + return -EINVAL; + + if ( a->flags & VIDEO_AUDIO_MUTE ) + gemtek_pci_mute( card ); + else + gemtek_pci_unmute( card ); + return 0; + } + + default: + return -ENOIOCTLCMD; + } +} + +static int gemtek_pci_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, gemtek_pci_do_ioctl); +} + +enum { + GEMTEK_PR103 +}; + +static char *card_names[] __devinitdata = { + "GEMTEK_PR103" +}; + +static struct pci_device_id gemtek_pci_id[] = +{ + { PCI_VENDOR_ID_GEMTEK, PCI_DEVICE_ID_GEMTEK_PR103, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, GEMTEK_PR103 }, + { 0 } +}; + +MODULE_DEVICE_TABLE( pci, gemtek_pci_id ); + +static int mx = 1; + +static struct file_operations gemtek_pci_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = gemtek_pci_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device vdev_template = { + .owner = THIS_MODULE, + .name = "Gemtek PCI Radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_GEMTEK, + .fops = &gemtek_pci_fops, +}; + +static int __devinit gemtek_pci_probe( struct pci_dev *pci_dev, const struct pci_device_id *pci_id ) +{ + struct gemtek_pci_card *card; + struct video_device *devradio; + + if ( (card = kzalloc( sizeof( struct gemtek_pci_card ), GFP_KERNEL )) == NULL ) { + printk( KERN_ERR "gemtek_pci: out of memory\n" ); + return -ENOMEM; + } + + if ( pci_enable_device( pci_dev ) ) + goto err_pci; + + card->iobase = pci_resource_start( pci_dev, 0 ); + card->length = pci_resource_len( pci_dev, 0 ); + + if ( request_region( card->iobase, card->length, card_names[pci_id->driver_data] ) == NULL ) { + printk( KERN_ERR "gemtek_pci: i/o port already in use\n" ); + goto err_pci; + } + + pci_read_config_byte( pci_dev, PCI_REVISION_ID, &card->chiprev ); + pci_read_config_word( pci_dev, PCI_SUBSYSTEM_ID, &card->model ); + + pci_set_drvdata( pci_dev, card ); + + if ( (devradio = kmalloc( sizeof( struct video_device ), GFP_KERNEL )) == NULL ) { + printk( KERN_ERR "gemtek_pci: out of memory\n" ); + goto err_video; + } + *devradio = vdev_template; + + if ( video_register_device( devradio, VFL_TYPE_RADIO , nr_radio) == -1 ) { + kfree( devradio ); + goto err_video; + } + + card->videodev = devradio; + devradio->priv = card; + gemtek_pci_mute( card ); + + printk( KERN_INFO "Gemtek PCI Radio (rev. %d) found at 0x%04x-0x%04x.\n", + card->chiprev, card->iobase, card->iobase + card->length - 1 ); + + return 0; + +err_video: + release_region( card->iobase, card->length ); + +err_pci: + kfree( card ); + return -ENODEV; +} + +static void __devexit gemtek_pci_remove( struct pci_dev *pci_dev ) +{ + struct gemtek_pci_card *card = pci_get_drvdata( pci_dev ); + + video_unregister_device( card->videodev ); + kfree( card->videodev ); + + release_region( card->iobase, card->length ); + + if ( mx ) + gemtek_pci_mute( card ); + + kfree( card ); + + pci_set_drvdata( pci_dev, NULL ); +} + +static struct pci_driver gemtek_pci_driver = +{ + .name = "gemtek_pci", + .id_table = gemtek_pci_id, + .probe = gemtek_pci_probe, + .remove = __devexit_p(gemtek_pci_remove), +}; + +static int __init gemtek_pci_init_module( void ) +{ + return pci_register_driver( &gemtek_pci_driver ); +} + +static void __exit gemtek_pci_cleanup_module( void ) +{ + return pci_unregister_driver( &gemtek_pci_driver ); +} + +MODULE_AUTHOR( "Vladimir Shebordaev <vshebordaev@mail.ru>" ); +MODULE_DESCRIPTION( "The video4linux driver for the Gemtek PCI Radio Card" ); +MODULE_LICENSE("GPL"); + +module_param(mx, bool, 0); +MODULE_PARM_DESC( mx, "single digit: 1 - turn off the turner upon module exit (default), 0 - do not" ); +module_param(nr_radio, int, 0); +MODULE_PARM_DESC( nr_radio, "video4linux device number to use"); + +module_init( gemtek_pci_init_module ); +module_exit( gemtek_pci_cleanup_module ); + diff --git a/linux/drivers/media/radio/radio-gemtek.c b/linux/drivers/media/radio/radio-gemtek.c new file mode 100644 index 000000000..199f2df7a --- /dev/null +++ b/linux/drivers/media/radio/radio-gemtek.c @@ -0,0 +1,306 @@ +/* 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 + * Converted 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> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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 radio_nr = -1; +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; +} + +static 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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct gemtek_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "GemTek"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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"); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = rt->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + rt->curfreq = *freq; + /* 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 = arg; + memset(v,0, sizeof(*v)); + v->flags|=VIDEO_AUDIO_MUTABLE; + v->volume=1; + v->step=65535; + strcpy(v->name, "Radio"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, gemtek_do_ioctl); +} + +static struct gemtek_device gemtek_unit; + +static struct file_operations gemtek_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = gemtek_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device gemtek_radio= +{ + .owner = THIS_MODULE, + .name = "GemTek radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_GEMTEK, + .fops = &gemtek_fops, +}; + +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 (!request_region(io, 4, "gemtek")) + { + 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, radio_nr)==-1) + { + release_region(io, 4); + return -EINVAL; + } + printk(KERN_INFO "GemTek Radio Card driver.\n"); + + spin_lock_init(&lock); + + /* this is _maybe_ unnecessary */ + outb(0x01, io); + + /* mute card - prevents noisy bootups */ + gemtek_unit.muted = 0; + gemtek_mute(&gemtek_unit); + + return 0; +} + +MODULE_AUTHOR("Jonas Munsin"); +MODULE_DESCRIPTION("A driver for the GemTek Radio Card"); +MODULE_LICENSE("GPL"); + +module_param(io, int, 0); +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))."); +module_param(radio_nr, int, 0); + +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/linux/drivers/media/radio/radio-maestro.c b/linux/drivers/media/radio/radio-maestro.c new file mode 100644 index 000000000..613a84142 --- /dev/null +++ b/linux/drivers/media/radio/radio-maestro.c @@ -0,0 +1,383 @@ +/* Maestro PCI sound card radio driver for Linux support + * (c) 2000 A. Tlalka, atlka@pg.gda.pl + * Notes on the hardware + * + * + Frequency control is done digitally + * + No volume control - only mute/unmute - you have to use Aux line volume + * control on Maestro card to set the volume + * + Radio status (tuned/not_tuned and stereo/mono) is valid some time after + * frequency setting (>100ms) and only when the radio is unmuted. + * version 0.02 + * + io port is automatically detected - only the first radio is used + * version 0.03 + * + thread access locking additions + * version 0.04 + * + code improvements + * + VIDEO_TUNER_LOW is permanent + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include "compat.h" +#include <linux/videodev.h> + + +#define DRIVER_VERSION "0.05" + +#define GPIO_DATA 0x60 /* port offset from ESS_IO_BASE */ + +#define IO_MASK 4 /* mask register offset from GPIO_DATA + bits 1=unmask write to given bit */ +#define IO_DIR 8 /* direction register offset from GPIO_DATA + bits 0/1=read/write direction */ + +#define GPIO6 0x0040 /* mask bits for GPIO lines */ +#define GPIO7 0x0080 +#define GPIO8 0x0100 +#define GPIO9 0x0200 + +#define STR_DATA GPIO6 /* radio TEA5757 pins and GPIO bits */ +#define STR_CLK GPIO7 +#define STR_WREN GPIO8 +#define STR_MOST GPIO9 + +#define FREQ_LO 50*16000 +#define FREQ_HI 150*16000 + +#define FREQ_IF 171200 /* 10.7*16000 */ +#define FREQ_STEP 200 /* 12.5*16 */ + +#define FREQ2BITS(x) ((((unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\ + /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */ + +#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) + +static int radio_nr = -1; +module_param(radio_nr, int, 0); + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static int maestro_probe(struct pci_dev *pdev, const struct pci_device_id *ent); +static void maestro_remove(struct pci_dev *pdev); + +static struct pci_device_id maestro_r_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ESS1968), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00 }, + { PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ESS1978), + .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8, + .class_mask = 0xffff00 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, maestro_r_pci_tbl); + +static struct pci_driver maestro_r_driver = { + .name = "maestro_radio", + .id_table = maestro_r_pci_tbl, + .probe = maestro_probe, + .remove = __devexit_p(maestro_remove), +}; + +static struct file_operations maestro_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = radio_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device maestro_radio = { + .name = "Maestro radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_SF16MI, + .fops = &maestro_fops, +}; + +struct radio_device { + u16 io, /* base of Maestro card radio io (GPIO_DATA)*/ + muted, /* VIDEO_AUDIO_MUTE */ + stereo, /* VIDEO_TUNER_STEREO_ON */ + tuned; /* signal strength (0 or 0xffff) */ + struct mutex lock; +}; + +static u32 radio_bits_get(struct radio_device *dev) +{ + register u16 io=dev->io, l, rdata; + register u32 data=0; + u16 omask; + + omask = inw(io + IO_MASK); + outw(~(STR_CLK | STR_WREN), io + IO_MASK); + outw(0, io); + udelay(16); + + for (l=24;l--;) { + outw(STR_CLK, io); /* HI state */ + udelay(2); + if(!l) + dev->tuned = inw(io) & STR_MOST ? 0 : 0xffff; + outw(0, io); /* LO state */ + udelay(2); + data <<= 1; /* shift data */ + rdata = inw(io); + if(!l) + dev->stereo = rdata & STR_MOST ? + 0 : VIDEO_TUNER_STEREO_ON; + else + if(rdata & STR_DATA) + data++; + udelay(2); + } + + if(dev->muted) + outw(STR_WREN, io); + + udelay(4); + outw(omask, io + IO_MASK); + + return data & 0x3ffe; +} + +static void radio_bits_set(struct radio_device *dev, u32 data) +{ + register u16 io=dev->io, l, bits; + u16 omask, odir; + + omask = inw(io + IO_MASK); + odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN); + outw(odir | STR_DATA, io + IO_DIR); + outw(~(STR_DATA | STR_CLK | STR_WREN), io + IO_MASK); + udelay(16); + for (l=25;l;l--) { + bits = ((data >> 18) & STR_DATA) | STR_WREN ; + data <<= 1; /* shift data */ + outw(bits, io); /* start strobe */ + udelay(2); + outw(bits | STR_CLK, io); /* HI level */ + udelay(2); + outw(bits, io); /* LO level */ + udelay(4); + } + + if(!dev->muted) + outw(0, io); + + udelay(4); + outw(omask, io + IO_MASK); + outw(odir, io + IO_DIR); + msleep(125); +} + +static inline int radio_function(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct radio_device *card = video_get_drvdata(dev); + + switch (cmd) { + case VIDIOCGCAP: { + struct video_capability *v = arg; + memset(v, 0, sizeof(*v)); + strcpy(v->name, "Maestro radio"); + v->type = VID_TYPE_TUNER; + v->channels = v->audios = 1; + return 0; + } case VIDIOCGTUNER: { + struct video_tuner *v = arg; + if (v->tuner) + return -EINVAL; + (void)radio_bits_get(card); + v->flags = VIDEO_TUNER_LOW | card->stereo; + v->signal = card->tuned; + strcpy(v->name, "FM"); + v->rangelow = FREQ_LO; + v->rangehigh = FREQ_HI; + v->mode = VIDEO_MODE_AUTO; + return 0; + } case VIDIOCSTUNER: { + struct video_tuner *v = arg; + if (v->tuner != 0) + return -EINVAL; + return 0; + } case VIDIOCGFREQ: { + unsigned long *freq = arg; + *freq = BITS2FREQ(radio_bits_get(card)); + return 0; + } case VIDIOCSFREQ: { + unsigned long *freq = arg; + if (*freq < FREQ_LO || *freq > FREQ_HI) + return -EINVAL; + radio_bits_set(card, FREQ2BITS(*freq)); + return 0; + } case VIDIOCGAUDIO: { + struct video_audio *v = arg; + memset(v, 0, sizeof(*v)); + strcpy(v->name, "Radio"); + v->flags = VIDEO_AUDIO_MUTABLE | card->muted; + v->mode = VIDEO_SOUND_STEREO; + return 0; + } case VIDIOCSAUDIO: { + struct video_audio *v = arg; + if (v->audio) + return -EINVAL; + { + register u16 io = card->io; + register u16 omask = inw(io + IO_MASK); + outw(~STR_WREN, io + IO_MASK); + outw((card->muted = v->flags & VIDEO_AUDIO_MUTE) ? + STR_WREN : 0, io); + udelay(4); + outw(omask, io + IO_MASK); + msleep(125); + return 0; + } + } case VIDIOCGUNIT: { + struct video_unit *v = arg; + v->video = VIDEO_NO_UNIT; + v->vbi = VIDEO_NO_UNIT; + v->radio = dev->minor; + v->audio = 0; + v->teletext = VIDEO_NO_UNIT; + return 0; + } default: + return -ENOIOCTLCMD; + } +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *dev = video_devdata(file); + struct radio_device *card = video_get_drvdata(dev); + int ret; + + mutex_lock(&card->lock); + ret = video_usercopy(inode, file, cmd, arg, radio_function); + mutex_unlock(&card->lock); + + return ret; +} + +static u16 __devinit radio_power_on(struct radio_device *dev) +{ + register u16 io = dev->io; + register u32 ofreq; + u16 omask, odir; + + omask = inw(io + IO_MASK); + odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN); + outw(odir & ~STR_WREN, io + IO_DIR); + dev->muted = inw(io) & STR_WREN ? 0 : VIDEO_AUDIO_MUTE; + outw(odir, io + IO_DIR); + outw(~(STR_WREN | STR_CLK), io + IO_MASK); + outw(dev->muted ? 0 : STR_WREN, io); + udelay(16); + outw(omask, io + IO_MASK); + ofreq = radio_bits_get(dev); + + if ((ofreq < FREQ2BITS(FREQ_LO)) || (ofreq > FREQ2BITS(FREQ_HI))) + ofreq = FREQ2BITS(FREQ_LO); + radio_bits_set(dev, ofreq); + + return (ofreq == radio_bits_get(dev)); +} + +static int __devinit maestro_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct radio_device *radio_unit; + struct video_device *maestro_radio_inst; + int retval; + + retval = pci_enable_device(pdev); + if (retval) { + dev_err(&pdev->dev, "enabling pci device failed!\n"); + goto err; + } + + retval = -ENOMEM; + + radio_unit = kzalloc(sizeof(*radio_unit), GFP_KERNEL); + if (radio_unit == NULL) { + dev_err(&pdev->dev, "not enough memory\n"); + goto err; + } + + radio_unit->io = pci_resource_start(pdev, 0) + GPIO_DATA; + mutex_init(&radio_unit->lock); + + maestro_radio_inst = video_device_alloc(); + if (maestro_radio_inst == NULL) { + dev_err(&pdev->dev, "not enough memory\n"); + goto errfr; + } + + memcpy(maestro_radio_inst, &maestro_radio, sizeof(maestro_radio)); + video_set_drvdata(maestro_radio_inst, radio_unit); + pci_set_drvdata(pdev, maestro_radio_inst); + + retval = video_register_device(maestro_radio_inst, VFL_TYPE_RADIO, + radio_nr); + if (retval) { + printk(KERN_ERR "can't register video device!\n"); + goto errfr1; + } + + if (!radio_power_on(radio_unit)) { + retval = -EIO; + goto errunr; + } + + dev_info(&pdev->dev, "version " DRIVER_VERSION " time " __TIME__ " " + __DATE__ "\n"); + dev_info(&pdev->dev, "radio chip initialized\n"); + + return 0; +errunr: + video_unregister_device(maestro_radio_inst); +errfr1: + kfree(maestro_radio_inst); +errfr: + kfree(radio_unit); +err: + return retval; + +} + +static void __devexit maestro_remove(struct pci_dev *pdev) +{ + struct video_device *vdev = pci_get_drvdata(pdev); + + video_unregister_device(vdev); +} + +static int __init maestro_radio_init(void) +{ + int retval = pci_register_driver(&maestro_r_driver); + + if (retval) + printk(KERN_ERR "error during registration pci driver\n"); + + return retval; +} + +static void __exit maestro_radio_exit(void) +{ + pci_unregister_driver(&maestro_r_driver); +} + +module_init(maestro_radio_init); +module_exit(maestro_radio_exit); + +MODULE_AUTHOR("Adam Tlalka, atlka@pg.gda.pl"); +MODULE_DESCRIPTION("Radio driver for the Maestro PCI sound card radio."); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/radio/radio-maxiradio.c b/linux/drivers/media/radio/radio-maxiradio.c new file mode 100644 index 000000000..da6b6148e --- /dev/null +++ b/linux/drivers/media/radio/radio-maxiradio.c @@ -0,0 +1,352 @@ +/* + * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux + * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net> + * + * Based in the radio Maestro PCI driver. Actually it uses the same chip + * for radio but different pci controller. + * + * I didn't have any specs I reversed engineered the protocol from + * the windows driver (radio.dll). + * + * The card uses the TEA5757 chip that includes a search function but it + * is useless as I haven't found any way to read back the frequency. If + * anybody does please mail me. + * + * For the pdf file see: + * http://www.semiconductors.philips.com/pip/TEA5757H/V1 + * + * + * CHANGES: + * 0.75b + * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr> + * + * 0.75 + * - tiding up + * - removed support for multiple devices as it didn't work anyway + * + * BUGS: + * - card unmutes if you change frequency + * + */ + + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/mutex.h> + +#include <linux/pci.h> +#include "compat.h" +#include <linux/videodev.h> + +/* version 0.75 Sun Feb 4 22:51:27 EET 2001 */ +#define DRIVER_VERSION "0.75" + +#ifndef PCI_VENDOR_ID_GUILLEMOT +#define PCI_VENDOR_ID_GUILLEMOT 0x5046 +#endif + +#ifndef PCI_DEVICE_ID_GUILLEMOT +#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 +#endif + + +/* TEA5757 pin mappings */ +static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ; + +static int radio_nr = -1; +module_param(radio_nr, int, 0); + + +#define FREQ_LO 50*16000 +#define FREQ_HI 150*16000 + +#define FREQ_IF 171200 /* 10.7*16000 */ +#define FREQ_STEP 200 /* 12.5*16 */ + +#define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\ + /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */ + +#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) + + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +static struct file_operations maxiradio_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = radio_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +static struct video_device maxiradio_radio = +{ + .owner = THIS_MODULE, + .name = "Maxi Radio FM2000 radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_SF16MI, + .fops = &maxiradio_fops, +}; + +static struct radio_device +{ + __u16 io, /* base of radio io */ + muted, /* VIDEO_AUDIO_MUTE */ + stereo, /* VIDEO_TUNER_STEREO_ON */ + tuned; /* signal strength (0 or 0xffff) */ + + unsigned long freq; + + struct mutex lock; +} radio_unit = {0, 0, 0, 0, }; + + +static void outbit(unsigned long bit, __u16 io) +{ + if(bit != 0) + { + outb( power|wren|data ,io); udelay(4); + outb( power|wren|data|clk ,io); udelay(4); + outb( power|wren|data ,io); udelay(4); + } + else + { + outb( power|wren ,io); udelay(4); + outb( power|wren|clk ,io); udelay(4); + outb( power|wren ,io); udelay(4); + } +} + +static void turn_power(__u16 io, int p) +{ + if(p != 0) outb(power, io); else outb(0,io); +} + + +static void set_freq(__u16 io, __u32 data) +{ + unsigned long int si; + int bl; + + /* TEA5757 shift register bits (see pdf) */ + + outbit(0,io); // 24 search + outbit(1,io); // 23 search up/down + + outbit(0,io); // 22 stereo/mono + + outbit(0,io); // 21 band + outbit(0,io); // 20 band (only 00=FM works I think) + + outbit(0,io); // 19 port ? + outbit(0,io); // 18 port ? + + outbit(0,io); // 17 search level + outbit(0,io); // 16 search level + + si = 0x8000; + for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; } + + outb(power,io); +} + +static int get_stereo(__u16 io) +{ + outb(power,io); udelay(4); + return !(inb(io) & mo_st); +} + +static int get_tune(__u16 io) +{ + outb(power+clk,io); udelay(4); + return !(inb(io) & mo_st); +} + + +static inline int radio_function(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct radio_device *card=dev->priv; + + switch(cmd) { + case VIDIOCGCAP: { + struct video_capability *v = arg; + + memset(v,0,sizeof(*v)); + strcpy(v->name, "Maxi Radio FM2000 radio"); + v->type=VID_TYPE_TUNER; + v->channels=v->audios=1; + return 0; + } + case VIDIOCGTUNER: { + struct video_tuner *v = arg; + + if(v->tuner) + return -EINVAL; + + card->stereo = 0xffff * get_stereo(card->io); + card->tuned = 0xffff * get_tune(card->io); + + v->flags = VIDEO_TUNER_LOW | card->stereo; + v->signal = card->tuned; + + strcpy(v->name, "FM"); + + v->rangelow = FREQ_LO; + v->rangehigh = FREQ_HI; + v->mode = VIDEO_MODE_AUTO; + + return 0; + } + case VIDIOCSTUNER: { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + return 0; + } + case VIDIOCGFREQ: { + unsigned long *freq = arg; + + *freq = card->freq; + return 0; + } + case VIDIOCSFREQ: { + unsigned long *freq = arg; + + if (*freq < FREQ_LO || *freq > FREQ_HI) + return -EINVAL; + card->freq = *freq; + set_freq(card->io, FREQ2BITS(card->freq)); + msleep(125); + return 0; + } + case VIDIOCGAUDIO: { + struct video_audio *v = arg; + memset(v,0,sizeof(*v)); + strcpy(v->name, "Radio"); + v->flags=VIDEO_AUDIO_MUTABLE | card->muted; + v->mode=VIDEO_SOUND_STEREO; + return 0; + } + + case VIDIOCSAUDIO: { + struct video_audio *v = arg; + + if(v->audio) + return -EINVAL; + card->muted = v->flags & VIDEO_AUDIO_MUTE; + if(card->muted) + turn_power(card->io, 0); + else + set_freq(card->io, FREQ2BITS(card->freq)); + return 0; + } + case VIDIOCGUNIT: { + struct video_unit *v = arg; + + v->video=VIDEO_NO_UNIT; + v->vbi=VIDEO_NO_UNIT; + v->radio=dev->minor; + v->audio=0; + v->teletext=VIDEO_NO_UNIT; + return 0; + } + default: return -ENOIOCTLCMD; + } +} + +static int radio_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *dev = video_devdata(file); + struct radio_device *card=dev->priv; + int ret; + + mutex_lock(&card->lock); + ret = video_usercopy(inode, file, cmd, arg, radio_function); + mutex_unlock(&card->lock); + return ret; +} + +MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); +MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); +MODULE_LICENSE("GPL"); + + +static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + if(!request_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { + printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n"); + goto err_out; + } + + if (pci_enable_device(pdev)) + goto err_out_free_region; + + radio_unit.io = pci_resource_start(pdev, 0); + mutex_init(&radio_unit.lock); + maxiradio_radio.priv = &radio_unit; + + if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) { + printk("radio-maxiradio: can't register device!"); + goto err_out_free_region; + } + + printk(KERN_INFO "radio-maxiradio: version " + DRIVER_VERSION + " time " + __TIME__ " " + __DATE__ + "\n"); + + printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n", + radio_unit.io); + return 0; + +err_out_free_region: + release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); +err_out: + return -ENODEV; +} + +static void __devexit maxiradio_remove_one(struct pci_dev *pdev) +{ + video_unregister_device(&maxiradio_radio); + release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); +} + +static struct pci_device_id maxiradio_pci_tbl[] = { + { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); + +static struct pci_driver maxiradio_driver = { + .name = "radio-maxiradio", + .id_table = maxiradio_pci_tbl, + .probe = maxiradio_init_one, + .remove = __devexit_p(maxiradio_remove_one), +}; + +static int __init maxiradio_radio_init(void) +{ + return pci_register_driver(&maxiradio_driver); +} + +static void __exit maxiradio_radio_exit(void) +{ + pci_unregister_driver(&maxiradio_driver); +} + +module_init(maxiradio_radio_init); +module_exit(maxiradio_radio_exit); diff --git a/linux/drivers/media/radio/radio-rtrack2.c b/linux/drivers/media/radio/radio-rtrack2.c new file mode 100644 index 000000000..defdc83e4 --- /dev/null +++ b/linux/drivers/media/radio/radio-rtrack2.c @@ -0,0 +1,268 @@ +/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff + * + * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood + * Converted 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> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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 radio_nr = -1; +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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct rt_device *rt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "RadioTrack II"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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"); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = rt->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + rt->curfreq = *freq; + rt_setfreq(rt, rt->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + memset(v,0, sizeof(*v)); + v->flags|=VIDEO_AUDIO_MUTABLE; + v->volume=1; + v->step=65535; + strcpy(v->name, "Radio"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, rt_do_ioctl); +} + +static struct rt_device rtrack2_unit; + +static struct file_operations rtrack2_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = rt_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device rtrack2_radio= +{ + .owner = THIS_MODULE, + .name = "RadioTrack II radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_RTRACK2, + .fops = &rtrack2_fops, +}; + +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 (!request_region(io, 4, "rtrack2")) + { + 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, radio_nr)==-1) + { + release_region(io, 4); + return -EINVAL; + } + + 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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); +module_param(radio_nr, int, 0); + +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: "mmake" + End: +*/ diff --git a/linux/drivers/media/radio/radio-sf16fmi.c b/linux/drivers/media/radio/radio-sf16fmi.c new file mode 100644 index 000000000..c1d3b5c33 --- /dev/null +++ b/linux/drivers/media/radio/radio-sf16fmi.c @@ -0,0 +1,330 @@ +/* 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> + * Support for ISAPnP by Ladislav Michl <ladis@psi.cz> + * + * 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/kernel.h> /* __setup */ +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include "compat.h" +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/isapnp.h> +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include <linux/mutex.h> + +struct fmi_device +{ + int port; + int curvol; /* 1 or 0 */ + unsigned long curfreq; /* freq in kHz */ + __u32 flags; +}; + +static int io = -1; +static int radio_nr = -1; +static struct pnp_dev *dev = NULL; +static struct mutex lock; + +/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ +/* It is only useful 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) +{ + mutex_lock(&lock); + outb(0x00, port); + mutex_unlock(&lock); +} + +static inline void fmi_unmute(int port) +{ + mutex_lock(&lock); + outb(0x08, port); + mutex_unlock(&lock); +} + +static inline int fmi_setfreq(struct fmi_device *dev) +{ + int myport = dev->port; + unsigned long freq = dev->curfreq; + + mutex_lock(&lock); + + outbits(16, RSF16_ENCODE(freq), myport); + outbits(8, 0xC0, myport); + msleep(143); /* was schedule_timeout(HZ/7) */ + mutex_unlock(&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; + + + mutex_lock(&lock); + val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */ + outb(val, myport); + outb(val | 0x10, myport); + msleep(143); /* was schedule_timeout(HZ/7) */ + res = (int)inb(myport+1); + outb(val, myport); + + mutex_unlock(&lock); + return (res & 2) ? 0 : 0xFFFF; +} + +static int fmi_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct fmi_device *fmi=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + strcpy(v->name, "SF16-FMx radio"); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + int mult; + + 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); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + 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 *freq = arg; + *freq = fmi->curfreq; + if (!(fmi->flags & VIDEO_TUNER_LOW)) + *freq /= 1000; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + if (!(fmi->flags & VIDEO_TUNER_LOW)) + *freq *= 1000; + if (*freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) + return -EINVAL; + /*rounding in steps of 800 to match th freq + that will be used */ + fmi->curfreq = (*freq/800)*800; + fmi_setfreq(fmi); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + memset(v,0,sizeof(*v)); + v->flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE); + strcpy(v->name, "Radio"); + v->mode=VIDEO_SOUND_STEREO; + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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 = arg; + 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; + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int fmi_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl); +} + +static struct fmi_device fmi_unit; + +static struct file_operations fmi_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = fmi_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device fmi_radio= +{ + .owner = THIS_MODULE, + .name = "SF16FMx radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_SF16MI, + .fops = &fmi_fops, +}; + +/* ladis: this is my card. does any other types exist? */ +static struct isapnp_device_id id_table[] __devinitdata = { + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, + ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, + { ISAPNP_CARD_END, }, +}; + +MODULE_DEVICE_TABLE(isapnp, id_table); + +static int isapnp_fmi_probe(void) +{ + int i = 0; + + while (id_table[i].card_vendor != 0 && dev == NULL) { + dev = pnp_find_dev(NULL, id_table[i].vendor, + id_table[i].function, NULL); + i++; + } + + if (!dev) + return -ENODEV; + if (pnp_device_attach(dev) < 0) + return -EAGAIN; + if (pnp_activate_dev(dev) < 0) { + printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n"); + pnp_device_detach(dev); + return -ENOMEM; + } + if (!pnp_port_valid(dev, 0)) { + pnp_device_detach(dev); + return -ENODEV; + } + + i = pnp_port_start(dev, 0); + printk ("radio-sf16fmi: PnP reports card at %#x\n", i); + + return i; +} + +static int __init fmi_init(void) +{ + if (io < 0) + io = isapnp_fmi_probe(); + if (io < 0) { + printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n"); + return io; + } + if (!request_region(io, 2, "radio-sf16fmi")) { + printk(KERN_ERR "radio-sf16fmi: 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; + + mutex_init(&lock); + + if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) { + release_region(io, 2); + return -EINVAL; + } + + printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io); + /* 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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)"); +module_param(radio_nr, int, 0); + +static void __exit fmi_cleanup_module(void) +{ + video_unregister_device(&fmi_radio); + release_region(io, 2); + if (dev) + pnp_device_detach(dev); +} + +module_init(fmi_init); +module_exit(fmi_cleanup_module); diff --git a/linux/drivers/media/radio/radio-sf16fmr2.c b/linux/drivers/media/radio/radio-sf16fmr2.c new file mode 100644 index 000000000..d90d87840 --- /dev/null +++ b/linux/drivers/media/radio/radio-sf16fmr2.c @@ -0,0 +1,436 @@ +/* SF16FMR2 radio driver for Linux radio support + * heavily based on fmi driver... + * (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com + * + * 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 + * + * For read stereo/mono you must wait 0.1 sec after set frequency and + * card unmuted so I set frequency on unmute + * Signal handling seem to work only on autoscanning (not implemented) + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#include <linux/videodev.h> /* kernel radio structs */ +#include <linux/mutex.h> + +static struct mutex lock; + +#undef DEBUG +//#define DEBUG 1 + +#ifdef DEBUG +# define debug_print(s) printk s +#else +# define debug_print(s) +#endif + +/* this should be static vars for module size */ +struct fmr2_device +{ + int port; + int curvol; /* 0-65535, if not volume 0 or 65535 */ + int mute; + int stereo; /* card is producing stereo audio */ + unsigned long curfreq; /* freq in kHz */ + int card_type; + __u32 flags; +}; + +static int io = 0x384; +static int radio_nr = -1; + +/* hw precision is 12.5 kHz + * It is only useful to give freq in intervall of 200 (=0.0125Mhz), + * other bits will be truncated + */ +#define RSF16_ENCODE(x) ((x)/200+856) +#define RSF16_MINFREQ 87*16000 +#define RSF16_MAXFREQ 108*16000 + +static inline void wait(int n,int port) +{ + for (;n;--n) inb(port); +} + +static void outbits(int bits, unsigned int data, int nWait, int port) +{ + int bit; + for(;--bits>=0;) { + bit = (data>>bits) & 1; + outb(bit,port); + wait(nWait,port); + outb(bit|2,port); + wait(nWait,port); + outb(bit,port); + wait(nWait,port); + } +} + +static inline void fmr2_mute(int port) +{ + outb(0x00, port); + wait(4,port); +} + +static inline void fmr2_unmute(int port) +{ + outb(0x04, port); + wait(4,port); +} + +static inline int fmr2_stereo_mode(int port) +{ + int n = inb(port); + outb(6,port); + inb(port); + n = ((n>>3)&1)^1; + debug_print((KERN_DEBUG "stereo: %d\n", n)); + return n; +} + +static int fmr2_product_info(struct fmr2_device *dev) +{ + int n = inb(dev->port); + n &= 0xC1; + if (n == 0) + { + /* this should support volume set */ + dev->card_type = 12; + return 0; + } + /* not volume (mine is 11) */ + dev->card_type = (n==128)?11:0; + return n; +} + +static inline int fmr2_getsigstr(struct fmr2_device *dev) +{ + /* !!! work only if scanning freq */ + int port = dev->port, res = 0xffff; + outb(5,port); + wait(4,port); + if (!(inb(port)&1)) res = 0; + debug_print((KERN_DEBUG "signal: %d\n", res)); + return res; +} + +/* set frequency and unmute card */ +static int fmr2_setfreq(struct fmr2_device *dev) +{ + int port = dev->port; + unsigned long freq = dev->curfreq; + + fmr2_mute(port); + + /* 0x42 for mono output + * 0x102 forward scanning + * 0x182 scansione avanti + */ + outbits(9,0x2,3,port); + outbits(16,RSF16_ENCODE(freq),2,port); + + fmr2_unmute(port); + + /* wait 0.11 sec */ + msleep(110); + + /* NOTE if mute this stop radio + you must set freq on unmute */ + dev->stereo = fmr2_stereo_mode(port); + return 0; +} + +/* !!! not tested, in my card this does't work !!! */ +static int fmr2_setvolume(struct fmr2_device *dev) +{ + int i,a,n, port = dev->port; + + if (dev->card_type != 11) return 1; + + switch( (dev->curvol+(1<<11)) >> 12 ) + { + case 0: case 1: n = 0x21; break; + case 2: n = 0x84; break; + case 3: n = 0x90; break; + case 4: n = 0x104; break; + case 5: n = 0x110; break; + case 6: n = 0x204; break; + case 7: n = 0x210; break; + case 8: n = 0x402; break; + case 9: n = 0x404; break; + default: + case 10: n = 0x408; break; + case 11: n = 0x410; break; + case 12: n = 0x801; break; + case 13: n = 0x802; break; + case 14: n = 0x804; break; + case 15: n = 0x808; break; + case 16: n = 0x810; break; + } + for(i=12;--i>=0;) + { + a = ((n >> i) & 1) << 6; /* if (a=0) a= 0; else a= 0x40; */ + outb(a|4, port); + wait(4,port); + outb(a|0x24, port); + wait(4,port); + outb(a|4, port); + wait(4,port); + } + for(i=6;--i>=0;) + { + a = ((0x18 >> i) & 1) << 6; + outb(a|4, port); + wait(4,port); + outb(a|0x24, port); + wait(4,port); + outb(a|4, port); + wait(4,port); + } + wait(4,port); + outb(0x14, port); + + return 0; +} + +static int fmr2_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct fmr2_device *fmr2 = dev->priv; + debug_print((KERN_DEBUG "freq %ld flags %d vol %d mute %d " + "stereo %d type %d\n", + fmr2->curfreq, fmr2->flags, fmr2->curvol, fmr2->mute, + fmr2->stereo, fmr2->card_type)); + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + strcpy(v->name, "SF16-FMR2 radio"); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + int mult; + + if(v->tuner) /* Only 1 tuner */ + return -EINVAL; + strcpy(v->name, "FM"); + mult = (fmr2->flags & VIDEO_TUNER_LOW) ? 1 : 1000; + v->rangelow = RSF16_MINFREQ/mult; + v->rangehigh = RSF16_MAXFREQ/mult; + v->flags = fmr2->flags | VIDEO_AUDIO_MUTABLE; + if (fmr2->mute) + v->flags |= VIDEO_AUDIO_MUTE; + v->mode=VIDEO_MODE_AUTO; + mutex_lock(&lock); + v->signal = fmr2_getsigstr(fmr2); + mutex_unlock(&lock); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if (v->tuner!=0) + return -EINVAL; + fmr2->flags = v->flags & VIDEO_TUNER_LOW; + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = fmr2->curfreq; + if (!(fmr2->flags & VIDEO_TUNER_LOW)) + *freq /= 1000; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + if (!(fmr2->flags & VIDEO_TUNER_LOW)) + *freq *= 1000; + if ( *freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ ) + return -EINVAL; + /* rounding in steps of 200 to match th freq + * that will be used + */ + fmr2->curfreq = (*freq/200)*200; + + /* set card freq (if not muted) */ + if (fmr2->curvol && !fmr2->mute) + { + mutex_lock(&lock); + fmr2_setfreq(fmr2); + mutex_unlock(&lock); + } + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + memset(v,0,sizeof(*v)); + /* !!! do not return VIDEO_AUDIO_MUTE */ + v->flags = VIDEO_AUDIO_MUTABLE; + strcpy(v->name, "Radio"); + /* get current stereo mode */ + v->mode = fmr2->stereo ? VIDEO_SOUND_STEREO: VIDEO_SOUND_MONO; + /* volume supported ? */ + if (fmr2->card_type == 11) + { + v->flags |= VIDEO_AUDIO_VOLUME; + v->step = 1 << 12; + v->volume = fmr2->curvol; + } + debug_print((KERN_DEBUG "Get flags %d vol %d\n", v->flags, v->volume)); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + if(v->audio) + return -EINVAL; + debug_print((KERN_DEBUG "Set flags %d vol %d\n", v->flags, v->volume)); + /* set volume */ + if (v->flags & VIDEO_AUDIO_VOLUME) + fmr2->curvol = v->volume; /* !!! set with precision */ + if (fmr2->card_type != 11) fmr2->curvol = 65535; + fmr2->mute = 0; + if (v->flags & VIDEO_AUDIO_MUTE) + fmr2->mute = 1; +#ifdef DEBUG + if (fmr2->curvol && !fmr2->mute) + printk(KERN_DEBUG "unmute\n"); + else + printk(KERN_DEBUG "mute\n"); +#endif + mutex_lock(&lock); + if (fmr2->curvol && !fmr2->mute) + { + fmr2_setvolume(fmr2); + fmr2_setfreq(fmr2); + } + else fmr2_mute(fmr2->port); + mutex_unlock(&lock); + return 0; + } + case VIDIOCGUNIT: + { + struct video_unit *v = arg; + 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; + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int fmr2_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) + { + return video_usercopy(inode, file, cmd, arg, fmr2_do_ioctl); +} + +static struct fmr2_device fmr2_unit; + +static struct file_operations fmr2_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = fmr2_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device fmr2_radio= +{ + .owner = THIS_MODULE, + .name = "SF16FMR2 radio", + . type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_SF16FMR2, + .fops = &fmr2_fops, +}; + +static int __init fmr2_init(void) +{ + fmr2_unit.port = io; + fmr2_unit.curvol = 0; + fmr2_unit.mute = 0; + fmr2_unit.curfreq = 0; + fmr2_unit.stereo = 1; + fmr2_unit.flags = VIDEO_TUNER_LOW; + fmr2_unit.card_type = 0; + fmr2_radio.priv = &fmr2_unit; + + mutex_init(&lock); + + if (request_region(io, 2, "sf16fmr2")) + { + printk(KERN_ERR "fmr2: port 0x%x already in use\n", io); + return -EBUSY; + } + + if(video_register_device(&fmr2_radio, VFL_TYPE_RADIO, radio_nr)==-1) + { + release_region(io, 2); + return -EINVAL; + } + + printk(KERN_INFO "SF16FMR2 radio card driver at 0x%x.\n", io); + debug_print((KERN_DEBUG "Mute %d Low %d\n",VIDEO_AUDIO_MUTE,VIDEO_TUNER_LOW)); + /* mute card - prevents noisy bootups */ + mutex_lock(&lock); + fmr2_mute(io); + fmr2_product_info(&fmr2_unit); + mutex_unlock(&lock); + debug_print((KERN_DEBUG "card_type %d\n", fmr2_unit.card_type)); + return 0; +} + +MODULE_AUTHOR("Ziglio Frediano, freddy77@angelfire.com"); +MODULE_DESCRIPTION("A driver for the SF16FMR2 radio."); +MODULE_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the SF16FMR2 card (should be 0x384, if do not work try 0x284)"); +module_param(radio_nr, int, 0); + +static void __exit fmr2_cleanup_module(void) +{ + video_unregister_device(&fmr2_radio); + release_region(io,2); +} + +module_init(fmr2_init); +module_exit(fmr2_cleanup_module); + +#ifndef MODULE + +static int __init fmr2_setup_io(char *str) +{ + get_option(&str, &io); + return 1; +} + +__setup("sf16fmr2=", fmr2_setup_io); + +#endif diff --git a/linux/drivers/media/radio/radio-terratec.c b/linux/drivers/media/radio/radio-terratec.c new file mode 100644 index 000000000..ee8cc71a0 --- /dev/null +++ b/linux/drivers/media/radio/radio-terratec.c @@ -0,0 +1,343 @@ +/* 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> /* request_region */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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 radio_nr = -1; +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 handle 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; +} + +static 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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct tt_device *tt=dev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "ActiveRadio"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = tt->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + tt->curfreq = *freq; + tt_setfreq(tt, tt->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, tt_do_ioctl); +} + +static struct tt_device terratec_unit; + +static struct file_operations terratec_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = tt_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device terratec_radio= +{ + .owner = THIS_MODULE, + .name = "TerraTec ActiveRadio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_TERRATEC, + .fops = &terratec_fops, +}; + +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 (!request_region(io, 2, "terratec")) + { + 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, radio_nr)==-1) + { + release_region(io,2); + return -EINVAL; + } + + 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_LICENSE("GPL"); +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); +module_param(radio_nr, int, 0); + +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/linux/drivers/media/radio/radio-trust.c b/linux/drivers/media/radio/radio-trust.c new file mode 100644 index 000000000..448725ca9 --- /dev/null +++ b/linux/drivers/media/radio/radio-trust.c @@ -0,0 +1,322 @@ +/* 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 "compat.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 radio_nr = -1; +static int ioval = 0xf; +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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + + memset(v,0,sizeof(*v)); + v->type=VID_TYPE_TUNER; + v->channels=1; + v->audios=1; + strcpy(v->name, "Trust FM Radio"); + + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + + 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"); + + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner != 0) + return -EINVAL; + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + tr_setfreq(*freq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, tr_do_ioctl); +} + +static struct file_operations trust_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = tr_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device trust_radio= +{ + .owner = THIS_MODULE, + .name = "Trust FM Radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_TRUST, + .fops = &trust_fops, +}; + +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(!request_region(io, 2, "Trust FM Radio")) { + printk(KERN_ERR "trust: port 0x%x already in use\n", io); + return -EBUSY; + } + if(video_register_device(&trust_radio, VFL_TYPE_RADIO, radio_nr)==-1) + { + release_region(io, 2); + return -EINVAL; + } + + 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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); +module_param(radio_nr, int, 0); + +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/linux/drivers/media/radio/radio-typhoon.c b/linux/drivers/media/radio/radio-typhoon.c new file mode 100644 index 000000000..8e87d0b42 --- /dev/null +++ b/linux/drivers/media/radio/radio-typhoon.c @@ -0,0 +1,385 @@ +/* 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> /* 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 "compat.h" +#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; + struct mutex lock; +}; + +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 inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +#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) +{ + mutex_lock(&dev->lock); + 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. */ + mutex_unlock(&dev->lock); +} + +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. + * + */ + + mutex_lock(&dev->lock); + 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); + mutex_unlock(&dev->lock); + + 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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct typhoon_device *typhoon = dev->priv; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + memset(v,0,sizeof(*v)); + v->type = VID_TYPE_TUNER; + v->channels = 1; + v->audios = 1; + strcpy(v->name, "Typhoon Radio"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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"); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if (v->tuner != 0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = typhoon->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + typhoon->curfreq = *freq; + typhoon_setfreq(typhoon, typhoon->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, typhoon_do_ioctl); +} + +static struct typhoon_device typhoon_unit = +{ + .iobase = CONFIG_RADIO_TYPHOON_PORT, + .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, + .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ, +}; + +static struct file_operations typhoon_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = typhoon_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device typhoon_radio = +{ + .owner = THIS_MODULE, + .name = "Typhoon Radio", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_TYPHOON, + .fops = &typhoon_fops, +}; + +#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_LICENSE("GPL"); + +static int io = -1; +static int radio_nr = -1; + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); +module_param(radio_nr, int, 0); + +#ifdef MODULE +static unsigned long mutefreq = 0; +module_param(mutefreq, ulong, 0); +MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); +#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); + mutex_init(&typhoon_unit.lock); + io = typhoon_unit.iobase; + if (!request_region(io, 8, "typhoon")) { + 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, radio_nr) == -1) + { + release_region(io, 8); + return -EINVAL; + } + 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/linux/drivers/media/radio/radio-zoltrix.c b/linux/drivers/media/radio/radio-zoltrix.c new file mode 100644 index 000000000..7c7760e26 --- /dev/null +++ b/linux/drivers/media/radio/radio-zoltrix.c @@ -0,0 +1,387 @@ +/* zoltrix radio plus driver for Linux radio support + * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> + * + * BUGS + * Due to the inconsistency 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 + * 2002-07-15 - Fix Stereo typo + */ + +#include <linux/module.h> /* Modules */ +#include <linux/init.h> /* Initdata */ +#include <linux/ioport.h> /* request_region */ +#include <linux/delay.h> /* udelay, msleep */ +#include <asm/io.h> /* outb, outb_p */ +#include <asm/uaccess.h> /* copy to/from user */ +#include "compat.h" +#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 radio_nr = -1; + +struct zol_device { + int port; + int curvol; + unsigned long curfreq; + int muted; + unsigned int stereo; + struct mutex lock; +}; + +static int zol_setvol(struct zol_device *dev, int vol) +{ + dev->curvol = vol; + if (dev->muted) + return 0; + + mutex_lock(&dev->lock); + if (vol == 0) { + outb(0, io); + outb(0, io); + inb(io + 3); /* Zoltrix needs to be read to confirm */ + mutex_unlock(&dev->lock); + return 0; + } + + outb(dev->curvol-1, io); + msleep(10); + inb(io + 2); + mutex_unlock(&dev->lock); + return 0; +} + +static void zol_mute(struct zol_device *dev) +{ + dev->muted = 1; + mutex_lock(&dev->lock); + outb(0, io); + outb(0, io); + inb(io + 3); /* Zoltrix needs to be read to confirm */ + mutex_unlock(&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; + + mutex_lock(&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); + } + + mutex_unlock(&dev->lock); + + if(!dev->muted) + { + zol_setvol(dev, dev->curvol); + } + return 0; +} + +/* Get signal strength */ + +static int zol_getsigstr(struct zol_device *dev) +{ + int a, b; + + mutex_lock(&dev->lock); + outb(0x00, io); /* This stuff I found to do nothing */ + outb(dev->curvol, io); + msleep(20); + + a = inb(io); + msleep(10); + b = inb(io); + + mutex_unlock(&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); +} + +static int zol_is_stereo (struct zol_device *dev) +{ + int x1, x2; + + mutex_lock(&dev->lock); + + outb(0x00, io); + outb(dev->curvol, io); + msleep(20); + + x1 = inb(io); + msleep(10); + x2 = inb(io); + + mutex_unlock(&dev->lock); + + if ((x1 == x2) && (x1 == 0xcf)) + return 1; + return 0; +} + +static int zol_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct zol_device *zol = dev->priv; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *v = arg; + + memset(v,0,sizeof(*v)); + v->type = VID_TYPE_TUNER; + v->channels = 1 + zol->stereo; + v->audios = 1; + strcpy(v->name, "Zoltrix Radio"); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + 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); + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if (v->tuner != 0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: + { + unsigned long *freq = arg; + *freq = zol->curfreq; + return 0; + } + case VIDIOCSFREQ: + { + unsigned long *freq = arg; + zol->curfreq = *freq; + zol_setfreq(zol, zol->curfreq); + return 0; + } + case VIDIOCGAUDIO: + { + struct video_audio *v = arg; + 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"); + return 0; + } + case VIDIOCSAUDIO: + { + struct video_audio *v = arg; + 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_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, zol_do_ioctl); +} + +static struct zol_device zoltrix_unit; + +static struct file_operations zoltrix_fops = +{ + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = zol_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device zoltrix_radio = +{ + .owner = THIS_MODULE, + .name = "Zoltrix Radio Plus", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_ZOLTRIX, + .fops = &zoltrix_fops, +}; + +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 ((io != 0x20c) && (io != 0x30c)) { + printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n"); + return -ENXIO; + } + + zoltrix_radio.priv = &zoltrix_unit; + if (!request_region(io, 2, "zoltrix")) { + printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io); + return -EBUSY; + } + + if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1) + { + release_region(io, 2); + return -EINVAL; + } + printk(KERN_INFO "Zoltrix Radio Plus card driver.\n"); + + mutex_init(&zoltrix_unit.lock); + + /* mute card - prevents noisy bootups */ + + /* this ensures that the volume is all the way down */ + + outb(0, io); + outb(0, io); + msleep(20); + 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_LICENSE("GPL"); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); +module_param(radio_nr, int, 0); + +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/linux/drivers/media/video/adv7170.c b/linux/drivers/media/video/adv7170.c new file mode 100644 index 000000000..48fe2344b --- /dev/null +++ b/linux/drivers/media/video/adv7170.c @@ -0,0 +1,519 @@ +/* + * adv7170 - adv7170, adv7171 video encoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Based on adv7176 driver by: + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Analog Devices ADV7170 video encoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(x) (x)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct adv7170 { + unsigned char reg[128]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_ADV7170 0xd4 +#define I2C_ADV7171 0x54 + +static char adv7170_name[] = "adv7170"; +static char adv7171_name[] = "adv7171"; + +static char *inputs[] = { "pass_through", "play_back" }; +static char *norms[] = { "PAL", "NTSC" }; + +/* ----------------------------------------------------------------------- */ + +static inline int +adv7170_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +adv7170_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +adv7170_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7170 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct adv7170 *encoder = i2c_get_clientdata(client); + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = adv7170_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------- */ +// Output filter: S-Video Composite + +#define MR050 0x11 //0x09 +#define MR060 0x14 //0x0c + +//--------------------------------------------------------------------------- + +#define TR0MODE 0x4c +#define TR0RST 0x80 + +#define TR1CAPT 0x00 +#define TR1PLAY 0x00 + + +static const unsigned char init_NTSC[] = { + 0x00, 0x10, // MR0 + 0x01, 0x20, // MR1 + 0x02, 0x0e, // MR2 RTC control: bits 2 and 1 + 0x03, 0x80, // MR3 + 0x04, 0x30, // MR4 + 0x05, 0x00, // Reserved + 0x06, 0x00, // Reserved + 0x07, TR0MODE, // TM0 + 0x08, TR1CAPT, // TM1 + 0x09, 0x16, // Fsc0 + 0x0a, 0x7c, // Fsc1 + 0x0b, 0xf0, // Fsc2 + 0x0c, 0x21, // Fsc3 + 0x0d, 0x00, // Subcarrier Phase + 0x0e, 0x00, // Closed Capt. Ext 0 + 0x0f, 0x00, // Closed Capt. Ext 1 + 0x10, 0x00, // Closed Capt. 0 + 0x11, 0x00, // Closed Capt. 1 + 0x12, 0x00, // Pedestal Ctl 0 + 0x13, 0x00, // Pedestal Ctl 1 + 0x14, 0x00, // Pedestal Ctl 2 + 0x15, 0x00, // Pedestal Ctl 3 + 0x16, 0x00, // CGMS_WSS_0 + 0x17, 0x00, // CGMS_WSS_1 + 0x18, 0x00, // CGMS_WSS_2 + 0x19, 0x00, // Teletext Ctl +}; + +static const unsigned char init_PAL[] = { + 0x00, 0x71, // MR0 + 0x01, 0x20, // MR1 + 0x02, 0x0e, // MR2 RTC control: bits 2 and 1 + 0x03, 0x80, // MR3 + 0x04, 0x30, // MR4 + 0x05, 0x00, // Reserved + 0x06, 0x00, // Reserved + 0x07, TR0MODE, // TM0 + 0x08, TR1CAPT, // TM1 + 0x09, 0xcb, // Fsc0 + 0x0a, 0x8a, // Fsc1 + 0x0b, 0x09, // Fsc2 + 0x0c, 0x2a, // Fsc3 + 0x0d, 0x00, // Subcarrier Phase + 0x0e, 0x00, // Closed Capt. Ext 0 + 0x0f, 0x00, // Closed Capt. Ext 1 + 0x10, 0x00, // Closed Capt. 0 + 0x11, 0x00, // Closed Capt. 1 + 0x12, 0x00, // Pedestal Ctl 0 + 0x13, 0x00, // Pedestal Ctl 1 + 0x14, 0x00, // Pedestal Ctl 2 + 0x15, 0x00, // Pedestal Ctl 3 + 0x16, 0x00, // CGMS_WSS_0 + 0x17, 0x00, // CGMS_WSS_1 + 0x18, 0x00, // CGMS_WSS_2 + 0x19, 0x00, // Teletext Ctl +}; + + +static int +adv7170_command (struct i2c_client *client, + unsigned int cmd, + void * arg) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: +#if 0 + /* This is just for testing!!! */ + adv7170_write_block(client, init_common, + sizeof(init_common)); + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); +#endif + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC; + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int iarg = *(int *) arg; + + dprintk(1, KERN_DEBUG "%s_command: set norm %d", + I2C_NAME(client), iarg); + + switch (iarg) { + + case VIDEO_MODE_NTSC: + adv7170_write_block(client, init_NTSC, + sizeof(init_NTSC)); + if (encoder->input == 0) + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_PAL: + adv7170_write_block(client, init_PAL, + sizeof(init_PAL)); + if (encoder->input == 0) + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal norm: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_DEBUG "%s: switched to %s\n", I2C_NAME(client), + norms[iarg]); + encoder->norm = iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int iarg = *(int *) arg; + + /* RJ: *iarg = 0: input is from decoder + *iarg = 1: input is from ZR36060 + *iarg = 2: color bar */ + + dprintk(1, KERN_DEBUG "%s_command: set input from %s\n", + I2C_NAME(client), + iarg == 0 ? "decoder" : "ZR36060"); + + switch (iarg) { + + case 0: + adv7170_write(client, 0x01, 0x20); + adv7170_write(client, 0x08, TR1CAPT); /* TR1 */ + adv7170_write(client, 0x02, 0x0e); // Enable genlock + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 1: + adv7170_write(client, 0x01, 0x00); + adv7170_write(client, 0x08, TR1PLAY); /* TR1 */ + adv7170_write(client, 0x02, 0x08); + adv7170_write(client, 0x07, TR0MODE | TR0RST); + adv7170_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal input: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_DEBUG "%s: switched to %s\n", I2C_NAME(client), + inputs[iarg]); + encoder->input = iarg; + } + 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; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_ADV7170 >> 1, (I2C_ADV7170 >> 1) + 1, + I2C_ADV7171 >> 1, (I2C_ADV7171 >> 1) + 1, + I2C_CLIENT_END +}; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_adv7170; + +static int +adv7170_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct adv7170 *encoder; + char *dname; + + dprintk(1, + KERN_INFO + "adv7170.c: detecting adv7170 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_adv7170; + if ((client->addr == I2C_ADV7170 >> 1) || + (client->addr == (I2C_ADV7170 >> 1) + 1)) { + dname = adv7170_name; + } else if ((client->addr == I2C_ADV7171 >> 1) || + (client->addr == (I2C_ADV7171 >> 1) + 1)) { + dname = adv7171_name; + } else { + /* We should never get here!!! */ + kfree(client); + return 0; + } + strlcpy(I2C_NAME(client), dname, sizeof(I2C_NAME(client))); + + encoder = kzalloc(sizeof(struct adv7170), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + encoder->norm = VIDEO_MODE_NTSC; + encoder->input = 0; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = adv7170_write_block(client, init_NTSC, sizeof(init_NTSC)); + if (i >= 0) { + i = adv7170_write(client, 0x07, TR0MODE | TR0RST); + i = adv7170_write(client, 0x07, TR0MODE); + i = adv7170_read(client, 0x12); + dprintk(1, KERN_INFO "%s_attach: rev. %d at 0x%02x\n", + I2C_NAME(client), i & 1, client->addr << 1); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error 0x%x\n", + I2C_NAME(client), i); + } + + return 0; +} + +static int +adv7170_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "adv7170.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &adv7170_detect_client); +} + +static int +adv7170_detach_client (struct i2c_client *client) +{ + struct adv7170 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_adv7170 = { + .driver = { + .name = "adv7170", /* name */ + }, + + .id = I2C_DRIVERID_ADV7170, + + .attach_adapter = adv7170_attach_adapter, + .detach_client = adv7170_detach_client, + .command = adv7170_command, +}; + +static int __init +adv7170_init (void) +{ + return i2c_add_driver(&i2c_driver_adv7170); +} + +static void __exit +adv7170_exit (void) +{ + i2c_del_driver(&i2c_driver_adv7170); +} + +module_init(adv7170_init); +module_exit(adv7170_exit); diff --git a/linux/drivers/media/video/adv7175.c b/linux/drivers/media/video/adv7175.c new file mode 100644 index 000000000..052b54f83 --- /dev/null +++ b/linux/drivers/media/video/adv7175.c @@ -0,0 +1,537 @@ +/* + * adv7175 - adv7175a video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Analog Devices ADV7175 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct adv7175 { + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_ADV7175 0xd4 +#define I2C_ADV7176 0x54 + +static char adv7175_name[] = "adv7175"; +static char adv7176_name[] = "adv7176"; + +static char *inputs[] = { "pass_through", "play_back", "color_bar" }; +static char *norms[] = { "PAL", "NTSC", "SECAM->PAL (may not work!)" }; + +/* ----------------------------------------------------------------------- */ + +static inline int +adv7175_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +adv7175_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +adv7175_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = data[1]; + reg++; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = adv7175_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static void +set_subcarrier_freq (struct i2c_client *client, + int pass_through) +{ + /* for some reason pass_through NTSC needs + * a different sub-carrier freq to remain stable. */ + if(pass_through) + adv7175_write(client, 0x02, 0x00); + else + adv7175_write(client, 0x02, 0x55); + + adv7175_write(client, 0x03, 0x55); + adv7175_write(client, 0x04, 0x55); + adv7175_write(client, 0x05, 0x25); +} + +/* ----------------------------------------------------------------------- */ +// Output filter: S-Video Composite + +#define MR050 0x11 //0x09 +#define MR060 0x14 //0x0c + +//--------------------------------------------------------------------------- + +#define TR0MODE 0x46 +#define TR0RST 0x80 + +#define TR1CAPT 0x80 +#define TR1PLAY 0x00 + +static const unsigned char init_common[] = { + + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ + + 0x07, TR0MODE, /* TR0, 16bit */ + 0x08, 0x21, /* */ + 0x09, 0x00, /* */ + 0x0a, 0x00, /* */ + 0x0b, 0x00, /* */ + 0x0c, TR1CAPT, /* TR1 */ + 0x0d, 0x4f, /* MR2 */ + 0x0e, 0x00, /* */ + 0x0f, 0x00, /* */ + 0x10, 0x00, /* */ + 0x11, 0x00, /* */ +}; + +static const unsigned char init_pal[] = { + 0x00, MR050, /* MR0, PAL enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x0c, /* subc. freq. */ + 0x03, 0x8c, /* subc. freq. */ + 0x04, 0x79, /* subc. freq. */ + 0x05, 0x26, /* subc. freq. */ + 0x06, 0x40, /* subc. phase */ +}; + +static const unsigned char init_ntsc[] = { + 0x00, MR060, /* MR0, NTSC enabled */ + 0x01, 0x00, /* MR1 */ + 0x02, 0x55, /* subc. freq. */ + 0x03, 0x55, /* subc. freq. */ + 0x04, 0x55, /* subc. freq. */ + 0x05, 0x25, /* subc. freq. */ + 0x06, 0x1a, /* subc. phase */ +}; + +static int +adv7175_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + adv7175_write_block(client, init_common, + sizeof(init_common)); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC | + VIDEO_ENCODER_SECAM; /* well, hacky */ + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int iarg = *(int *) arg; + + switch (iarg) { + + case VIDEO_MODE_NTSC: + adv7175_write_block(client, init_ntsc, + sizeof(init_ntsc)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_PAL: + adv7175_write_block(client, init_pal, + sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + + case VIDEO_MODE_SECAM: // WARNING! ADV7176 does not support SECAM. + /* This is an attempt to convert + * SECAM->PAL (typically it does not work + * due to genlock: when decoder is in SECAM + * and encoder in in PAL the subcarrier can + * not be syncronized with horizontal + * quency) */ + adv7175_write_block(client, init_pal, + sizeof(init_pal)); + if (encoder->input == 0) + adv7175_write(client, 0x0d, 0x49); // Disable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + break; + default: + dprintk(1, KERN_ERR "%s: illegal norm: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_INFO "%s: switched to %s\n", I2C_NAME(client), + norms[iarg]); + encoder->norm = iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int iarg = *(int *) arg; + + /* RJ: *iarg = 0: input is from SAA7110 + *iarg = 1: input is from ZR36060 + *iarg = 2: color bar */ + + switch (iarg) { + + case 0: + adv7175_write(client, 0x01, 0x00); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 1); + + adv7175_write(client, 0x0c, TR1CAPT); /* TR1 */ + if (encoder->norm == VIDEO_MODE_SECAM) + adv7175_write(client, 0x0d, 0x49); // Disable genlock + else + adv7175_write(client, 0x0d, 0x4f); // Enable genlock + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 1: + adv7175_write(client, 0x01, 0x00); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 0); + + adv7175_write(client, 0x0c, TR1PLAY); /* TR1 */ + adv7175_write(client, 0x0d, 0x49); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + case 2: + adv7175_write(client, 0x01, 0x80); + + if (encoder->norm == VIDEO_MODE_NTSC) + set_subcarrier_freq(client, 0); + + adv7175_write(client, 0x0d, 0x49); + adv7175_write(client, 0x07, TR0MODE | TR0RST); + adv7175_write(client, 0x07, TR0MODE); + //udelay(10); + break; + + default: + dprintk(1, KERN_ERR "%s: illegal input: %d\n", + I2C_NAME(client), iarg); + return -EINVAL; + + } + dprintk(1, KERN_INFO "%s: switched to %s\n", I2C_NAME(client), + inputs[iarg]); + encoder->input = iarg; + } + 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; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_ADV7175 >> 1, (I2C_ADV7175 >> 1) + 1, + I2C_ADV7176 >> 1, (I2C_ADV7176 >> 1) + 1, + I2C_CLIENT_END +}; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_adv7175; + +static int +adv7175_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct adv7175 *encoder; + char *dname; + + dprintk(1, + KERN_INFO + "adv7175.c: detecting adv7175 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_adv7175; + if ((client->addr == I2C_ADV7175 >> 1) || + (client->addr == (I2C_ADV7175 >> 1) + 1)) { + dname = adv7175_name; + } else if ((client->addr == I2C_ADV7176 >> 1) || + (client->addr == (I2C_ADV7176 >> 1) + 1)) { + dname = adv7176_name; + } else { + /* We should never get here!!! */ + kfree(client); + return 0; + } + strlcpy(I2C_NAME(client), dname, sizeof(I2C_NAME(client))); + + encoder = kzalloc(sizeof(struct adv7175), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + encoder->norm = VIDEO_MODE_PAL; + encoder->input = 0; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = adv7175_write_block(client, init_common, sizeof(init_common)); + if (i >= 0) { + i = adv7175_write(client, 0x07, TR0MODE | TR0RST); + i = adv7175_write(client, 0x07, TR0MODE); + i = adv7175_read(client, 0x12); + dprintk(1, KERN_INFO "%s_attach: rev. %d at 0x%x\n", + I2C_NAME(client), i & 1, client->addr << 1); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error 0x%x\n", + I2C_NAME(client), i); + } + + return 0; +} + +static int +adv7175_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "adv7175.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &adv7175_detect_client); +} + +static int +adv7175_detach_client (struct i2c_client *client) +{ + struct adv7175 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_adv7175 = { + .driver = { + .name = "adv7175", /* name */ + }, + + .id = I2C_DRIVERID_ADV7175, + + .attach_adapter = adv7175_attach_adapter, + .detach_client = adv7175_detach_client, + .command = adv7175_command, +}; + +static int __init +adv7175_init (void) +{ + return i2c_add_driver(&i2c_driver_adv7175); +} + +static void __exit +adv7175_exit (void) +{ + i2c_del_driver(&i2c_driver_adv7175); +} + +module_init(adv7175_init); +module_exit(adv7175_exit); diff --git a/linux/drivers/media/video/arv.c b/linux/drivers/media/video/arv.c new file mode 100644 index 000000000..aec57f485 --- /dev/null +++ b/linux/drivers/media/video/arv.c @@ -0,0 +1,913 @@ +/* + * Colour AR M64278(VGA) driver for Video4Linux + * + * Copyright (C) 2003 Takeo Takahashi <takahashi.takeo@renesas.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. + * + * Some code is taken from AR driver sample program for M3T-M32700UT. + * + * AR driver sample (M32R SDK): + * Copyright (c) 2003 RENESAS TECHNOROGY CORPORATION + * AND RENESAS SOLUTIONS CORPORATION + * All Rights Reserved. + * + * 2003-09-01: Support w3cam by Takeo Takahashi + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/devfs_fs_kernel.h> +#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/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.h> + +#include <asm/uaccess.h> +#include <asm/m32r.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#if 0 +#define DEBUG(n, args...) printk(args) +#define CHECK_LOST 1 +#else +#define DEBUG(n, args...) +#define CHECK_LOST 0 +#endif + +/* + * USE_INT is always 0, interrupt mode is not available + * on linux due to lack of speed + */ +#define USE_INT 0 /* Don't modify */ + +#define VERSION "0.03" + +#define ar_inl(addr) inl((unsigned long)(addr)) +#define ar_outl(val, addr) outl((unsigned long)(val),(unsigned long)(addr)) + +extern struct cpuinfo_m32r boot_cpu_data; + +/* + * CCD pixel size + * Note that M32700UT does not support CIF mode, but QVGA is + * supported by M32700UT hardware using VGA mode of AR LSI. + * + * Supported: VGA (Normal mode, Interlace mode) + * QVGA (Always Interlace mode of VGA) + * + */ +#define AR_WIDTH_VGA 640 +#define AR_HEIGHT_VGA 480 +#define AR_WIDTH_QVGA 320 +#define AR_HEIGHT_QVGA 240 +#define MIN_AR_WIDTH AR_WIDTH_QVGA +#define MIN_AR_HEIGHT AR_HEIGHT_QVGA +#define MAX_AR_WIDTH AR_WIDTH_VGA +#define MAX_AR_HEIGHT AR_HEIGHT_VGA + +/* bits & bytes per pixel */ +#define AR_BITS_PER_PIXEL 16 +#define AR_BYTES_PER_PIXEL (AR_BITS_PER_PIXEL/8) + +/* line buffer size */ +#define AR_LINE_BYTES_VGA (AR_WIDTH_VGA * AR_BYTES_PER_PIXEL) +#define AR_LINE_BYTES_QVGA (AR_WIDTH_QVGA * AR_BYTES_PER_PIXEL) +#define MAX_AR_LINE_BYTES AR_LINE_BYTES_VGA + +/* frame size & type */ +#define AR_FRAME_BYTES_VGA \ + (AR_WIDTH_VGA * AR_HEIGHT_VGA * AR_BYTES_PER_PIXEL) +#define AR_FRAME_BYTES_QVGA \ + (AR_WIDTH_QVGA * AR_HEIGHT_QVGA * AR_BYTES_PER_PIXEL) +#define MAX_AR_FRAME_BYTES \ + (MAX_AR_WIDTH * MAX_AR_HEIGHT * AR_BYTES_PER_PIXEL) + +#define AR_MAX_FRAME 15 + +/* capture size */ +#define AR_SIZE_VGA 0 +#define AR_SIZE_QVGA 1 + +/* capture mode */ +#define AR_MODE_INTERLACE 0 +#define AR_MODE_NORMAL 1 + +struct ar_device { + struct video_device *vdev; + unsigned int start_capture; /* duaring capture in INT. mode. */ +#if USE_INT + unsigned char *line_buff; /* DMA line buffer */ +#endif + unsigned char *frame[MAX_AR_HEIGHT]; /* frame data */ + short size; /* capture size */ + short mode; /* capture mode */ + int width, height; + int frame_bytes, line_bytes; + wait_queue_head_t wait; + struct mutex lock; +}; + +static int video_nr = -1; /* video device number (first free) */ +static unsigned char yuv[MAX_AR_FRAME_BYTES]; + +/* module parameters */ +/* default frequency */ +#define DEFAULT_FREQ 50 /* 50 or 75 (MHz) is available as BCLK */ +static int freq = DEFAULT_FREQ; /* BCLK: available 50 or 70 (MHz) */ +static int vga = 0; /* default mode(0:QVGA mode, other:VGA mode) */ +static int vga_interlace = 0; /* 0 is normal mode for, else interlace mode */ +module_param(freq, int, 0); +module_param(vga, int, 0); +module_param(vga_interlace, int, 0); + +static int ar_initialize(struct video_device *dev); + +static inline void wait_for_vsync(void) +{ + while (ar_inl(ARVCR0) & ARVCR0_VDS) /* wait for VSYNC */ + cpu_relax(); + while (!(ar_inl(ARVCR0) & ARVCR0_VDS)) /* wait for VSYNC */ + cpu_relax(); +} + +static inline void wait_acknowledge(void) +{ + int i; + + for (i = 0; i < 1000; i++) + cpu_relax(); + while (ar_inl(PLDI2CSTS) & PLDI2CSTS_NOACK) + cpu_relax(); +} + +/******************************************************************* + * I2C functions + *******************************************************************/ +void iic(int n, unsigned long addr, unsigned long data1, unsigned long data2, + unsigned long data3) +{ + int i; + + /* Slave Address */ + ar_outl(addr, PLDI2CDATA); + wait_for_vsync(); + + /* Start */ + ar_outl(1, PLDI2CCND); + wait_acknowledge(); + + /* Transfer data 1 */ + ar_outl(data1, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + + /* Transfer data 2 */ + ar_outl(data2, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + + if (n == 3) { + /* Transfer data 3 */ + ar_outl(data3, PLDI2CDATA); + wait_for_vsync(); + ar_outl(PLDI2CSTEN_STEN, PLDI2CSTEN); + wait_acknowledge(); + } + + /* Stop */ + for (i = 0; i < 100; i++) + cpu_relax(); + ar_outl(2, PLDI2CCND); + ar_outl(2, PLDI2CCND); + + while (ar_inl(PLDI2CSTS) & PLDI2CSTS_BB) + cpu_relax(); +} + + +void init_iic(void) +{ + DEBUG(1, "init_iic:\n"); + + /* + * ICU Setting (iic) + */ + /* I2C Setting */ + ar_outl(0x0, PLDI2CCR); /* I2CCR Disable */ + ar_outl(0x0300, PLDI2CMOD); /* I2CMOD ACK/8b-data/7b-addr/auto */ + ar_outl(0x1, PLDI2CACK); /* I2CACK ACK */ + + /* I2C CLK */ + /* 50MH-100k */ + if (freq == 75) { + ar_outl(369, PLDI2CFREQ); /* BCLK = 75MHz */ + } else if (freq == 50) { + ar_outl(244, PLDI2CFREQ); /* BCLK = 50MHz */ + } else { + ar_outl(244, PLDI2CFREQ); /* default: BCLK = 50MHz */ + } + ar_outl(0x1, PLDI2CCR); /* I2CCR Enable */ +} + +/************************************************************************** + * + * Video4Linux Interface functions + * + **************************************************************************/ + +static inline void disable_dma(void) +{ + ar_outl(0x8000, M32R_DMAEN_PORTL); /* disable DMA0 */ +} + +static inline void enable_dma(void) +{ + ar_outl(0x8080, M32R_DMAEN_PORTL); /* enable DMA0 */ +} + +static inline void clear_dma_status(void) +{ + ar_outl(0x8000, M32R_DMAEDET_PORTL); /* clear status */ +} + +static inline void wait_for_vertical_sync(int exp_line) +{ +#if CHECK_LOST + int tmout = 10000; /* FIXME */ + int l; + + /* + * check HCOUNT because we cannot check vertical sync. + */ + for (; tmout >= 0; tmout--) { + l = ar_inl(ARVHCOUNT); + if (l == exp_line) + break; + } + if (tmout < 0) + printk("arv: lost %d -> %d\n", exp_line, l); +#else + while (ar_inl(ARVHCOUNT) != exp_line) + cpu_relax(); +#endif +} + +static ssize_t ar_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct ar_device *ar = v->priv; + long ret = ar->frame_bytes; /* return read bytes */ + unsigned long arvcr1 = 0; + unsigned long flags; + unsigned char *p; + int h, w; + unsigned char *py, *pu, *pv; +#if ! USE_INT + int l; +#endif + + DEBUG(1, "ar_read()\n"); + + if (ar->size == AR_SIZE_QVGA) + arvcr1 |= ARVCR1_QVGA; + if (ar->mode == AR_MODE_NORMAL) + arvcr1 |= ARVCR1_NORMAL; + + mutex_lock(&ar->lock); + +#if USE_INT + local_irq_save(flags); + disable_dma(); + ar_outl(0xa1871300, M32R_DMA0CR0_PORTL); + ar_outl(0x01000000, M32R_DMA0CR1_PORTL); + + /* set AR FIFO address as source(BSEL5) */ + ar_outl(ARDATA32, M32R_DMA0CSA_PORTL); + ar_outl(ARDATA32, M32R_DMA0RSA_PORTL); + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); /* destination addr. */ + ar_outl(ar->line_buff, M32R_DMA0RDA_PORTL); /* reload address */ + ar_outl(ar->line_bytes, M32R_DMA0CBCUT_PORTL); /* byte count (bytes) */ + ar_outl(ar->line_bytes, M32R_DMA0RBCUT_PORTL); /* reload count (bytes) */ + + /* + * Okey , kicks AR LSI to invoke an interrupt + */ + ar->start_capture = 0; + ar_outl(arvcr1 | ARVCR1_HIEN, ARVCR1); + local_irq_restore(flags); + /* .... AR interrupts .... */ + interruptible_sleep_on(&ar->wait); + if (signal_pending(current)) { + printk("arv: interrupted while get frame data.\n"); + ret = -EINTR; + goto out_up; + } +#else /* ! USE_INT */ + /* polling */ + ar_outl(arvcr1, ARVCR1); + disable_dma(); + ar_outl(0x8000, M32R_DMAEDET_PORTL); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + ar_outl(0x01000000, M32R_DMA0CR1_PORTL); + ar_outl(ARDATA32, M32R_DMA0CSA_PORTL); + ar_outl(ARDATA32, M32R_DMA0RSA_PORTL); + ar_outl(ar->line_bytes, M32R_DMA0CBCUT_PORTL); + ar_outl(ar->line_bytes, M32R_DMA0RBCUT_PORTL); + + local_irq_save(flags); + while (ar_inl(ARVHCOUNT) != 0) /* wait for 0 */ + cpu_relax(); + if (ar->mode == AR_MODE_INTERLACE && ar->size == AR_SIZE_VGA) { + for (h = 0; h < ar->height; h++) { + wait_for_vertical_sync(h); + if (h < (AR_HEIGHT_VGA/2)) + l = h << 1; + else + l = (((h - (AR_HEIGHT_VGA/2)) << 1) + 1); + ar_outl(virt_to_phys(ar->frame[l]), M32R_DMA0CDA_PORTL); + enable_dma(); + while (!(ar_inl(M32R_DMAEDET_PORTL) & 0x8000)) + cpu_relax(); + disable_dma(); + clear_dma_status(); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + } + } else { + for (h = 0; h < ar->height; h++) { + wait_for_vertical_sync(h); + ar_outl(virt_to_phys(ar->frame[h]), M32R_DMA0CDA_PORTL); + enable_dma(); + while (!(ar_inl(M32R_DMAEDET_PORTL) & 0x8000)) + cpu_relax(); + disable_dma(); + clear_dma_status(); + ar_outl(0xa0861300, M32R_DMA0CR0_PORTL); + } + } + local_irq_restore(flags); +#endif /* ! USE_INT */ + + /* + * convert YUV422 to YUV422P + * +--------------------+ + * | Y0,Y1,... | + * | ..............Yn | + * +--------------------+ + * | U0,U1,........Un | + * +--------------------+ + * | V0,V1,........Vn | + * +--------------------+ + */ + py = yuv; + pu = py + (ar->frame_bytes / 2); + pv = pu + (ar->frame_bytes / 4); + for (h = 0; h < ar->height; h++) { + p = ar->frame[h]; + for (w = 0; w < ar->line_bytes; w += 4) { + *py++ = *p++; + *pu++ = *p++; + *py++ = *p++; + *pv++ = *p++; + } + } + if (copy_to_user(buf, yuv, ar->frame_bytes)) { + printk("arv: failed while copy_to_user yuv.\n"); + ret = -EFAULT; + goto out_up; + } + DEBUG(1, "ret = %d\n", ret); +out_up: + mutex_unlock(&ar->lock); + return ret; +} + +static int ar_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct ar_device *ar = dev->priv; + + DEBUG(1, "ar_ioctl()\n"); + switch(cmd) { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + DEBUG(1, "VIDIOCGCAP:\n"); + strcpy(b->name, ar->vdev->name); + b->type = VID_TYPE_CAPTURE; + b->channels = 0; + b->audios = 0; + b->maxwidth = MAX_AR_WIDTH; + b->maxheight = MAX_AR_HEIGHT; + b->minwidth = MIN_AR_WIDTH; + b->minheight = MIN_AR_HEIGHT; + return 0; + } + case VIDIOCGCHAN: + DEBUG(1, "VIDIOCGCHAN:\n"); + return 0; + case VIDIOCSCHAN: + DEBUG(1, "VIDIOCSCHAN:\n"); + return 0; + case VIDIOCGTUNER: + DEBUG(1, "VIDIOCGTUNER:\n"); + return 0; + case VIDIOCSTUNER: + DEBUG(1, "VIDIOCSTUNER:\n"); + return 0; + case VIDIOCGPICT: + DEBUG(1, "VIDIOCGPICT:\n"); + return 0; + case VIDIOCSPICT: + DEBUG(1, "VIDIOCSPICT:\n"); + return 0; + case VIDIOCCAPTURE: + DEBUG(1, "VIDIOCCAPTURE:\n"); + return -EINVAL; + case VIDIOCGWIN: + { + struct video_window *w = arg; + DEBUG(1, "VIDIOCGWIN:\n"); + memset(w, 0, sizeof(w)); + w->width = ar->width; + w->height = ar->height; + return 0; + } + case VIDIOCSWIN: + { + struct video_window *w = arg; + DEBUG(1, "VIDIOCSWIN:\n"); + if ((w->width != AR_WIDTH_VGA || w->height != AR_HEIGHT_VGA) && + (w->width != AR_WIDTH_QVGA || w->height != AR_HEIGHT_QVGA)) + return -EINVAL; + + mutex_lock(&ar->lock); + ar->width = w->width; + ar->height = w->height; + if (ar->width == AR_WIDTH_VGA) { + ar->size = AR_SIZE_VGA; + ar->frame_bytes = AR_FRAME_BYTES_VGA; + ar->line_bytes = AR_LINE_BYTES_VGA; + if (vga_interlace) + ar->mode = AR_MODE_INTERLACE; + else + ar->mode = AR_MODE_NORMAL; + } else { + ar->size = AR_SIZE_QVGA; + ar->frame_bytes = AR_FRAME_BYTES_QVGA; + ar->line_bytes = AR_LINE_BYTES_QVGA; + ar->mode = AR_MODE_INTERLACE; + } + mutex_unlock(&ar->lock); + return 0; + } + case VIDIOCGFBUF: + DEBUG(1, "VIDIOCGFBUF:\n"); + return -EINVAL; + case VIDIOCSFBUF: + DEBUG(1, "VIDIOCSFBUF:\n"); + return -EINVAL; + case VIDIOCKEY: + DEBUG(1, "VIDIOCKEY:\n"); + return 0; + case VIDIOCGFREQ: + DEBUG(1, "VIDIOCGFREQ:\n"); + return -EINVAL; + case VIDIOCSFREQ: + DEBUG(1, "VIDIOCSFREQ:\n"); + return -EINVAL; + case VIDIOCGAUDIO: + DEBUG(1, "VIDIOCGAUDIO:\n"); + return -EINVAL; + case VIDIOCSAUDIO: + DEBUG(1, "VIDIOCSAUDIO:\n"); + return -EINVAL; + case VIDIOCSYNC: + DEBUG(1, "VIDIOCSYNC:\n"); + return -EINVAL; + case VIDIOCMCAPTURE: + DEBUG(1, "VIDIOCMCAPTURE:\n"); + return -EINVAL; + case VIDIOCGMBUF: + DEBUG(1, "VIDIOCGMBUF:\n"); + return -EINVAL; + case VIDIOCGUNIT: + DEBUG(1, "VIDIOCGUNIT:\n"); + return -EINVAL; + case VIDIOCGCAPTURE: + DEBUG(1, "VIDIOCGCAPTURE:\n"); + return -EINVAL; + case VIDIOCSCAPTURE: + DEBUG(1, "VIDIOCSCAPTURE:\n"); + return -EINVAL; + case VIDIOCSPLAYMODE: + DEBUG(1, "VIDIOCSPLAYMODE:\n"); + return -EINVAL; + case VIDIOCSWRITEMODE: + DEBUG(1, "VIDIOCSWRITEMODE:\n"); + return -EINVAL; + case VIDIOCGPLAYINFO: + DEBUG(1, "VIDIOCGPLAYINFO:\n"); + return -EINVAL; + case VIDIOCSMICROCODE: + DEBUG(1, "VIDIOCSMICROCODE:\n"); + return -EINVAL; + case VIDIOCGVBIFMT: + DEBUG(1, "VIDIOCGVBIFMT:\n"); + return -EINVAL; + case VIDIOCSVBIFMT: + DEBUG(1, "VIDIOCSVBIFMT:\n"); + return -EINVAL; + default: + DEBUG(1, "Unknown ioctl(0x%08x)\n", cmd); + return -ENOIOCTLCMD; + } + return 0; +} + +static int ar_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, ar_do_ioctl); +} + +#if USE_INT +/* + * Interrupt handler + */ +static void ar_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct ar_device *ar = dev; + unsigned int line_count; + unsigned int line_number; + unsigned int arvcr1; + + line_count = ar_inl(ARVHCOUNT); /* line number */ + if (ar->mode == AR_MODE_INTERLACE && ar->size == AR_SIZE_VGA) { + /* operations for interlace mode */ + if ( line_count < (AR_HEIGHT_VGA/2) ) /* even line */ + line_number = (line_count << 1); + else /* odd line */ + line_number = + (((line_count - (AR_HEIGHT_VGA/2)) << 1) + 1); + } else { + line_number = line_count; + } + + if (line_number == 0) { + /* + * It is an interrupt for line 0. + * we have to start capture. + */ + disable_dma(); +#if 0 + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); /* needless? */ +#endif + memcpy(ar->frame[0], ar->line_buff, ar->line_bytes); +#if 0 + ar_outl(0xa1861300, M32R_DMA0CR0_PORTL); +#endif + enable_dma(); + ar->start_capture = 1; /* during capture */ + return; + } + + if (ar->start_capture == 1 && line_number <= (ar->height - 1)) { + disable_dma(); + memcpy(ar->frame[line_number], ar->line_buff, ar->line_bytes); + + /* + * if captured all line of a frame, disable AR interrupt + * and wake a process up. + */ + if (line_number == (ar->height - 1)) { /* end of line */ + + ar->start_capture = 0; + + /* disable AR interrupt request */ + arvcr1 = ar_inl(ARVCR1); + arvcr1 &= ~ARVCR1_HIEN; /* clear int. flag */ + ar_outl(arvcr1, ARVCR1); /* disable */ + wake_up_interruptible(&ar->wait); + } else { +#if 0 + ar_outl(ar->line_buff, M32R_DMA0CDA_PORTL); + ar_outl(0xa1861300, M32R_DMA0CR0_PORTL); +#endif + enable_dma(); + } + } +} +#endif + +/* + * ar_initialize() + * ar_initialize() is called by video_register_device() and + * initializes AR LSI and peripherals. + * + * -1 is returned in all failures. + * 0 is returned in success. + * + */ +static int ar_initialize(struct video_device *dev) +{ + struct ar_device *ar = dev->priv; + unsigned long cr = 0; + int i,found=0; + + DEBUG(1, "ar_initialize:\n"); + + /* + * initialize AR LSI + */ + ar_outl(0, ARVCR0); /* assert reset of AR LSI */ + for (i = 0; i < 0x18; i++) /* wait for over 10 cycles @ 27MHz */ + cpu_relax(); + ar_outl(ARVCR0_RST, ARVCR0); /* negate reset of AR LSI (enable) */ + for (i = 0; i < 0x40d; i++) /* wait for over 420 cycles @ 27MHz */ + cpu_relax(); + + /* AR uses INT3 of CPU as interrupt pin. */ + ar_outl(ARINTSEL_INT3, ARINTSEL); + + if (ar->size == AR_SIZE_QVGA) + cr |= ARVCR1_QVGA; + if (ar->mode == AR_MODE_NORMAL) + cr |= ARVCR1_NORMAL; + ar_outl(cr, ARVCR1); + + /* + * Initialize IIC so that CPU can communicate with AR LSI, + * and send boot commands to AR LSI. + */ + init_iic(); + + for (i = 0; i < 0x100000; i++) { /* > 0xa1d10, 56ms */ + if ((ar_inl(ARVCR0) & ARVCR0_VDS)) { /* VSYNC */ + found = 1; + break; + } + } + + if (found == 0) + return -ENODEV; + + printk("arv: Initializing "); + + iic(2,0x78,0x11,0x01,0x00); /* start */ + iic(3,0x78,0x12,0x00,0x06); + iic(3,0x78,0x12,0x12,0x30); + iic(3,0x78,0x12,0x15,0x58); + iic(3,0x78,0x12,0x17,0x30); + printk("."); + iic(3,0x78,0x12,0x1a,0x97); + iic(3,0x78,0x12,0x1b,0xff); + iic(3,0x78,0x12,0x1c,0xff); + iic(3,0x78,0x12,0x26,0x10); + iic(3,0x78,0x12,0x27,0x00); + printk("."); + iic(2,0x78,0x34,0x02,0x00); + iic(2,0x78,0x7a,0x10,0x00); + iic(2,0x78,0x80,0x39,0x00); + iic(2,0x78,0x81,0xe6,0x00); + iic(2,0x78,0x8d,0x00,0x00); + printk("."); + iic(2,0x78,0x8e,0x0c,0x00); + iic(2,0x78,0x8f,0x00,0x00); +#if 0 + iic(2,0x78,0x90,0x00,0x00); /* AWB on=1 off=0 */ +#endif + iic(2,0x78,0x93,0x01,0x00); + iic(2,0x78,0x94,0xcd,0x00); + iic(2,0x78,0x95,0x00,0x00); + printk("."); + iic(2,0x78,0x96,0xa0,0x00); + iic(2,0x78,0x97,0x00,0x00); + iic(2,0x78,0x98,0x60,0x00); + iic(2,0x78,0x99,0x01,0x00); + iic(2,0x78,0x9a,0x19,0x00); + printk("."); + iic(2,0x78,0x9b,0x02,0x00); + iic(2,0x78,0x9c,0xe8,0x00); + iic(2,0x78,0x9d,0x02,0x00); + iic(2,0x78,0x9e,0x2e,0x00); + iic(2,0x78,0xb8,0x78,0x00); + iic(2,0x78,0xba,0x05,0x00); +#if 0 + iic(2,0x78,0x83,0x8c,0x00); /* brightness */ +#endif + printk("."); + + /* color correction */ + iic(3,0x78,0x49,0x00,0x95); /* a */ + iic(3,0x78,0x49,0x01,0x96); /* b */ + iic(3,0x78,0x49,0x03,0x85); /* c */ + iic(3,0x78,0x49,0x04,0x97); /* d */ + iic(3,0x78,0x49,0x02,0x7e); /* e(Lo) */ + iic(3,0x78,0x49,0x05,0xa4); /* f(Lo) */ + iic(3,0x78,0x49,0x06,0x04); /* e(Hi) */ + iic(3,0x78,0x49,0x07,0x04); /* e(Hi) */ + iic(2,0x78,0x48,0x01,0x00); /* on=1 off=0 */ + + printk("."); + iic(2,0x78,0x11,0x00,0x00); /* end */ + printk(" done\n"); + return 0; +} + + +void ar_release(struct video_device *vfd) +{ + struct ar_device *ar = vfd->priv; + mutex_lock(&ar->lock); + video_device_release(vfd); +} + +/**************************************************************************** + * + * Video4Linux Module functions + * + ****************************************************************************/ +static struct file_operations ar_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .read = ar_read, + .ioctl = ar_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device ar_template = { + .owner = THIS_MODULE, + .name = "Colour AR VGA", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_ARV, + .fops = &ar_fops, + .release = ar_release, + .minor = -1, +}; + +#define ALIGN4(x) ((((int)(x)) & 0x3) == 0) +static struct ar_device ardev; + +static int __init ar_init(void) +{ + struct ar_device *ar; + int ret; + int i; + + DEBUG(1, "ar_init:\n"); + ret = -EIO; + printk(KERN_INFO "arv: Colour AR VGA driver %s\n", VERSION); + + ar = &ardev; + memset(ar, 0, sizeof(struct ar_device)); + +#if USE_INT + /* allocate a DMA buffer for 1 line. */ + ar->line_buff = kmalloc(MAX_AR_LINE_BYTES, GFP_KERNEL | GFP_DMA); + if (ar->line_buff == NULL || ! ALIGN4(ar->line_buff)) { + printk("arv: buffer allocation failed for DMA.\n"); + ret = -ENOMEM; + goto out_end; + } +#endif + /* allocate buffers for a frame */ + for (i = 0; i < MAX_AR_HEIGHT; i++) { + ar->frame[i] = kmalloc(MAX_AR_LINE_BYTES, GFP_KERNEL); + if (ar->frame[i] == NULL || ! ALIGN4(ar->frame[i])) { + printk("arv: buffer allocation failed for frame.\n"); + ret = -ENOMEM; + goto out_line_buff; + } + } + + ar->vdev = video_device_alloc(); + if (!ar->vdev) { + printk(KERN_ERR "arv: video_device_alloc() failed\n"); + return -ENOMEM; + } + memcpy(ar->vdev, &ar_template, sizeof(ar_template)); + ar->vdev->priv = ar; + + if (vga) { + ar->width = AR_WIDTH_VGA; + ar->height = AR_HEIGHT_VGA; + ar->size = AR_SIZE_VGA; + ar->frame_bytes = AR_FRAME_BYTES_VGA; + ar->line_bytes = AR_LINE_BYTES_VGA; + if (vga_interlace) + ar->mode = AR_MODE_INTERLACE; + else + ar->mode = AR_MODE_NORMAL; + } else { + ar->width = AR_WIDTH_QVGA; + ar->height = AR_HEIGHT_QVGA; + ar->size = AR_SIZE_QVGA; + ar->frame_bytes = AR_FRAME_BYTES_QVGA; + ar->line_bytes = AR_LINE_BYTES_QVGA; + ar->mode = AR_MODE_INTERLACE; + } + mutex_init(&ar->lock); + init_waitqueue_head(&ar->wait); + +#if USE_INT + if (request_irq(M32R_IRQ_INT3, ar_interrupt, 0, "arv", ar)) { + printk("arv: request_irq(%d) failed.\n", M32R_IRQ_INT3); + ret = -EIO; + goto out_irq; + } +#endif + + if (ar_initialize(ar->vdev) != 0) { + printk("arv: M64278 not found.\n"); + ret = -ENODEV; + goto out_dev; + } + + /* + * ok, we can initialize h/w according to parameters, + * so register video device as a frame grabber type. + * device is named "video[0-64]". + * video_register_device() initializes h/w using ar_initialize(). + */ + if (video_register_device(ar->vdev, VFL_TYPE_GRABBER, video_nr) != 0) { + /* return -1, -ENFILE(full) or others */ + printk("arv: register video (Colour AR) failed.\n"); + ret = -ENODEV; + goto out_dev; + } + + printk("video%d: Found M64278 VGA (IRQ %d, Freq %dMHz).\n", + ar->vdev->minor, M32R_IRQ_INT3, freq); + + return 0; + +out_dev: +#if USE_INT + free_irq(M32R_IRQ_INT3, ar); + +out_irq: +#endif + for (i = 0; i < MAX_AR_HEIGHT; i++) + kfree(ar->frame[i]); + +out_line_buff: +#if USE_INT + kfree(ar->line_buff); + +out_end: +#endif + return ret; +} + + +static int __init ar_init_module(void) +{ + freq = (boot_cpu_data.bus_clock / 1000000); + printk("arv: Bus clock %d\n", freq); + if (freq != 50 && freq != 75) + freq = DEFAULT_FREQ; + return ar_init(); +} + +static void __exit ar_cleanup_module(void) +{ + struct ar_device *ar; + int i; + + ar = &ardev; + video_unregister_device(ar->vdev); +#if USE_INT + free_irq(M32R_IRQ_INT3, ar); +#endif + for (i = 0; i < MAX_AR_HEIGHT; i++) + kfree(ar->frame[i]); +#if USE_INT + kfree(ar->line_buff); +#endif +} + +module_init(ar_init_module); +module_exit(ar_cleanup_module); + +MODULE_AUTHOR("Takeo Takahashi <takahashi.takeo@renesas.com>"); +MODULE_DESCRIPTION("Colour AR M64278(VGA) for Video4Linux"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/bt819.c b/linux/drivers/media/video/bt819.c new file mode 100644 index 000000000..570d5997c --- /dev/null +++ b/linux/drivers/media/video/bt819.c @@ -0,0 +1,643 @@ +/* + * bt819 - BT819A VideoStream Decoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Brooktree-819 video decoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct bt819 { + unsigned char reg[32]; + + int initialized; + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +struct timing { + int hactive; + int hdelay; + int vactive; + int vdelay; + int hscale; + int vscale; +}; + +/* for values, see the bt819 datasheet */ +static struct timing timing_data[] = { + {864 - 24, 20, 625 - 2, 1, 0x0504, 0x0000}, + {858 - 24, 20, 525 - 2, 1, 0x00f8, 0x0000}, +}; + +#define I2C_BT819 0x8a + +/* ----------------------------------------------------------------------- */ + +static inline int +bt819_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +bt819_setbit (struct i2c_client *client, + u8 reg, + u8 bit, + u8 value) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + return bt819_write(client, reg, + (decoder-> + reg[reg] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static int +bt819_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the bt819 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct bt819 *decoder = i2c_get_clientdata(client); + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = bt819_write(client, reg, *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int +bt819_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +bt819_init (struct i2c_client *client) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + + static unsigned char init[] = { + //0x1f, 0x00, /* Reset */ + 0x01, 0x59, /* 0x01 input format */ + 0x02, 0x00, /* 0x02 temporal decimation */ + 0x03, 0x12, /* 0x03 Cropping msb */ + 0x04, 0x16, /* 0x04 Vertical Delay, lsb */ + 0x05, 0xe0, /* 0x05 Vertical Active lsb */ + 0x06, 0x80, /* 0x06 Horizontal Delay lsb */ + 0x07, 0xd0, /* 0x07 Horizontal Active lsb */ + 0x08, 0x00, /* 0x08 Horizontal Scaling msb */ + 0x09, 0xf8, /* 0x09 Horizontal Scaling lsb */ + 0x0a, 0x00, /* 0x0a Brightness control */ + 0x0b, 0x30, /* 0x0b Miscellaneous control */ + 0x0c, 0xd8, /* 0x0c Luma Gain lsb */ + 0x0d, 0xfe, /* 0x0d Chroma Gain (U) lsb */ + 0x0e, 0xb4, /* 0x0e Chroma Gain (V) msb */ + 0x0f, 0x00, /* 0x0f Hue control */ + 0x12, 0x04, /* 0x12 Output Format */ + 0x13, 0x20, /* 0x13 Vertial Scaling msb 0x00 + chroma comb OFF, line drop scaling, interlace scaling + BUG? Why does turning the chroma comb on fuck up color? + Bug in the bt819 stepping on my board? + */ + 0x14, 0x00, /* 0x14 Vertial Scaling lsb */ + 0x16, 0x07, /* 0x16 Video Timing Polarity + ACTIVE=active low + FIELD: high=odd, + vreset=active high, + hreset=active high */ + 0x18, 0x68, /* 0x18 AGC Delay */ + 0x19, 0x5d, /* 0x19 Burst Gate Delay */ + 0x1a, 0x80, /* 0x1a ADC Interface */ + }; + + struct timing *timing = &timing_data[decoder->norm]; + + init[0x03 * 2 - 1] = + (((timing->vdelay >> 8) & 0x03) << 6) | (((timing-> + vactive >> 8) & + 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | ((timing-> + hactive >> 8) & + 0x03); + init[0x04 * 2 - 1] = timing->vdelay & 0xff; + init[0x05 * 2 - 1] = timing->vactive & 0xff; + init[0x06 * 2 - 1] = timing->hdelay & 0xff; + init[0x07 * 2 - 1] = timing->hactive & 0xff; + init[0x08 * 2 - 1] = timing->hscale >> 8; + init[0x09 * 2 - 1] = timing->hscale & 0xff; + /* 0x15 in array is address 0x19 */ + init[0x15 * 2 - 1] = (decoder->norm == 0) ? 115 : 93; /* Chroma burst delay */ + /* reset */ + bt819_write(client, 0x1f, 0x00); + mdelay(1); + + /* init */ + return bt819_write_block(client, init, sizeof(init)); + +} + +/* ----------------------------------------------------------------------- */ + +static int +bt819_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + int temp; + + struct bt819 *decoder = i2c_get_clientdata(client); + + if (!decoder->initialized) { // First call to bt819_init could be + bt819_init(client); // without #FRST = 0 + decoder->initialized = 1; + } + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + bt819_init(client); + break; + + 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 = bt819_read(client, 0x00); + res = 0; + if ((status & 0x80)) { + 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 & 0x10)) { + res |= DECODER_STATUS_PAL; + } else { + res |= DECODER_STATUS_NTSC; + } + break; + } + res |= DECODER_STATUS_COLOR; + *iarg = res; + + dprintk(1, KERN_INFO "%s: get status %x\n", I2C_NAME(client), + *iarg); + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg; + struct timing *timing = NULL; + + dprintk(1, KERN_INFO "%s: set norm %x\n", I2C_NAME(client), + *iarg); + + switch (*iarg) { + case VIDEO_MODE_NTSC: + bt819_setbit(client, 0x01, 0, 1); + bt819_setbit(client, 0x01, 1, 0); + bt819_setbit(client, 0x01, 5, 0); + bt819_write(client, 0x18, 0x68); + bt819_write(client, 0x19, 0x5d); + //bt819_setbit(client, 0x1a, 5, 1); + timing = &timing_data[VIDEO_MODE_NTSC]; + break; + case VIDEO_MODE_PAL: + bt819_setbit(client, 0x01, 0, 1); + bt819_setbit(client, 0x01, 1, 1); + bt819_setbit(client, 0x01, 5, 1); + bt819_write(client, 0x18, 0x7f); + bt819_write(client, 0x19, 0x72); + //bt819_setbit(client, 0x1a, 5, 0); + timing = &timing_data[VIDEO_MODE_PAL]; + break; + case VIDEO_MODE_AUTO: + bt819_setbit(client, 0x01, 0, 0); + bt819_setbit(client, 0x01, 1, 0); + break; + default: + dprintk(1, + KERN_ERR + "%s: unsupported norm %d\n", + I2C_NAME(client), *iarg); + return -EINVAL; + } + + if (timing) { + bt819_write(client, 0x03, + (((timing->vdelay >> 8) & 0x03) << 6) | + (((timing->vactive >> 8) & 0x03) << 4) | + (((timing->hdelay >> 8) & 0x03) << 2) | + ((timing->hactive >> 8) & 0x03) ); + bt819_write(client, 0x04, timing->vdelay & 0xff); + bt819_write(client, 0x05, timing->vactive & 0xff); + bt819_write(client, 0x06, timing->hdelay & 0xff); + bt819_write(client, 0x07, timing->hactive & 0xff); + bt819_write(client, 0x08, (timing->hscale >> 8) & 0xff); + bt819_write(client, 0x09, timing->hscale & 0xff); + } + + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set input %x\n", I2C_NAME(client), + *iarg); + + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + decoder->input = *iarg; + /* select mode */ + if (decoder->input == 0) { + bt819_setbit(client, 0x0b, 6, 0); + bt819_setbit(client, 0x1a, 1, 1); + } else { + bt819_setbit(client, 0x0b, 6, 1); + bt819_setbit(client, 0x1a, 1, 0); + } + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set output %x\n", I2C_NAME(client), + *iarg); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + dprintk(1, KERN_INFO "%s: enable output %x\n", + I2C_NAME(client), *iarg); + + if (decoder->enable != enable) { + decoder->enable = enable; + + if (decoder->enable) { + bt819_setbit(client, 0x16, 7, 0); + } else { + bt819_setbit(client, 0x16, 7, 1); + } + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + dprintk(1, + KERN_INFO + "%s: set picture brightness %d contrast %d colour %d\n", + I2C_NAME(client), pic->brightness, pic->contrast, + pic->colour); + + + if (decoder->bright != pic->brightness) { + /* We want -128 to 127 we get 0-65535 */ + decoder->bright = pic->brightness; + bt819_write(client, 0x0a, + (decoder->bright >> 8) - 128); + } + + if (decoder->contrast != pic->contrast) { + /* We want 0 to 511 we get 0-65535 */ + decoder->contrast = pic->contrast; + bt819_write(client, 0x0c, + (decoder->contrast >> 7) & 0xff); + bt819_setbit(client, 0x0b, 2, + ((decoder->contrast >> 15) & 0x01)); + } + + if (decoder->sat != pic->colour) { + /* We want 0 to 511 we get 0-65535 */ + decoder->sat = pic->colour; + bt819_write(client, 0x0d, + (decoder->sat >> 7) & 0xff); + bt819_setbit(client, 0x0b, 1, + ((decoder->sat >> 15) & 0x01)); + + temp = (decoder->sat * 201) / 237; + bt819_write(client, 0x0e, (temp >> 7) & 0xff); + bt819_setbit(client, 0x0b, 0, (temp >> 15) & 0x01); + } + + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + bt819_write(client, 0x0f, + 128 - (decoder->hue >> 8)); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { + I2C_BT819 >> 1, + I2C_CLIENT_END, +}; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_bt819; + +static int +bt819_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i, id; + struct bt819 *decoder; + struct i2c_client *client; + + dprintk(1, + KERN_INFO + "saa7111.c: detecting bt819 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_bt819; + + decoder = kzalloc(sizeof(struct bt819), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + decoder->initialized = 0; + i2c_set_clientdata(client, decoder); + + id = bt819_read(client, 0x17); + switch (id & 0xf0) { + case 0x70: + strlcpy(I2C_NAME(client), "bt819a", sizeof(I2C_NAME(client))); + break; + case 0x60: + strlcpy(I2C_NAME(client), "bt817a", sizeof(I2C_NAME(client))); + break; + case 0x20: + strlcpy(I2C_NAME(client), "bt815a", sizeof(I2C_NAME(client))); + break; + default: + dprintk(1, + KERN_ERR + "bt819: unknown chip version 0x%x (ver 0x%x)\n", + id & 0xf0, id & 0x0f); + kfree(decoder); + kfree(client); + return 0; + } + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + i = bt819_init(client); + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version 0x%x at address 0x%x\n", + I2C_NAME(client), id & 0x0f, + client->addr << 1); + } + + return 0; +} + +static int +bt819_attach_adapter (struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, &bt819_detect_client); +} + +static int +bt819_detach_client (struct i2c_client *client) +{ + struct bt819 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_bt819 = { + .driver = { + .name = "bt819", + }, + + .id = I2C_DRIVERID_BT819, + + .attach_adapter = bt819_attach_adapter, + .detach_client = bt819_detach_client, + .command = bt819_command, +}; + +static int __init +bt819_init_module (void) +{ + return i2c_add_driver(&i2c_driver_bt819); +} + +static void __exit +bt819_exit (void) +{ + i2c_del_driver(&i2c_driver_bt819); +} + +module_init(bt819_init_module); +module_exit(bt819_exit); diff --git a/linux/drivers/media/video/bt856.c b/linux/drivers/media/video/bt856.c new file mode 100644 index 000000000..cb5c2160f --- /dev/null +++ b/linux/drivers/media/video/bt856.c @@ -0,0 +1,426 @@ +/* + * bt856 - BT856A Digital Video Encoder (Rockwell Part) + * + * Copyright (C) 1999 Mike Bernson <mike@mlb.org> + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Modifications for LML33/DC10plus unified driver + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * This code was modify/ported from the saa7111 driver written + * by Dave Perks. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (9/9/2002) + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Brooktree-856A video encoder driver"); +MODULE_AUTHOR("Mike Bernson & Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +#define REG_OFFSET 0xDA +#define BT856_NR_REG 6 + +struct bt856 { + unsigned char reg[BT856_NR_REG]; + + int norm; + int enable; +}; + +#define I2C_BT856 0x88 + +/* ----------------------------------------------------------------------- */ + +static inline int +bt856_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + encoder->reg[reg - REG_OFFSET] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +bt856_setbit (struct i2c_client *client, + u8 reg, + u8 bit, + u8 value) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + return bt856_write(client, reg, + (encoder-> + reg[reg - REG_OFFSET] & ~(1 << bit)) | + (value ? (1 << bit) : 0)); +} + +static void +bt856_dump (struct i2c_client *client) +{ + int i; + struct bt856 *encoder = i2c_get_clientdata(client); + + printk(KERN_INFO "%s: register dump:", I2C_NAME(client)); + for (i = 0; i < BT856_NR_REG; i += 2) + printk(" %02x", encoder->reg[i]); + printk("\n"); +} + +/* ----------------------------------------------------------------------- */ + +static int +bt856_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + /* This is just for testing!!! */ + dprintk(1, KERN_INFO "bt856: init\n"); + bt856_write(client, 0xdc, 0x18); + bt856_write(client, 0xda, 0); + bt856_write(client, 0xde, 0); + + bt856_setbit(client, 0xdc, 3, 1); + //bt856_setbit(client, 0xdc, 6, 0); + bt856_setbit(client, 0xdc, 4, 1); + + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + break; + } + + bt856_setbit(client, 0xdc, 1, 1); + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + if (debug != 0) + bt856_dump(client); + break; + + case ENCODER_GET_CAPABILITIES: + { + struct video_encoder_capability *cap = arg; + + dprintk(1, KERN_INFO "%s: get capabilities\n", + I2C_NAME(client)); + + cap->flags = VIDEO_ENCODER_PAL | + VIDEO_ENCODER_NTSC | + VIDEO_ENCODER_CCIR; + cap->inputs = 2; + cap->outputs = 1; + } + break; + + case ENCODER_SET_NORM: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set norm %d\n", I2C_NAME(client), + *iarg); + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + bt856_setbit(client, 0xda, 0, 0); + //bt856_setbit(client, 0xda, 0, 1); + break; + + default: + return -EINVAL; + + } + encoder->norm = *iarg; + if (debug != 0) + bt856_dump(client); + } + break; + + case ENCODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set input %d\n", I2C_NAME(client), + *iarg); + + /* We only have video bus. + * iarg = 0: input is from bt819 + * iarg = 1: input is from ZR36060 */ + + switch (*iarg) { + + case 0: + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + bt856_setbit(client, 0xdc, 3, 1); + bt856_setbit(client, 0xdc, 6, 0); + break; + case 1: + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + bt856_setbit(client, 0xdc, 3, 1); + bt856_setbit(client, 0xdc, 6, 1); + break; + case 2: // Color bar + bt856_setbit(client, 0xdc, 3, 0); + bt856_setbit(client, 0xde, 4, 1); + break; + default: + return -EINVAL; + + } + + if (debug != 0) + bt856_dump(client); + } + break; + + case ENCODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_INFO "%s: set output %d\n", I2C_NAME(client), + *iarg); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case ENCODER_ENABLE_OUTPUT: + { + int *iarg = arg; + + encoder->enable = !!*iarg; + + dprintk(1, KERN_INFO "%s: enable output %d\n", + I2C_NAME(client), encoder->enable); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_BT856 >> 1, I2C_CLIENT_END }; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_bt856; + +static int +bt856_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct bt856 *encoder; + + dprintk(1, + KERN_INFO + "bt856.c: detecting bt856 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_bt856; + strlcpy(I2C_NAME(client), "bt856", sizeof(I2C_NAME(client))); + + encoder = kzalloc(sizeof(struct bt856), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + encoder->norm = VIDEO_MODE_NTSC; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + bt856_write(client, 0xdc, 0x18); + bt856_write(client, 0xda, 0); + bt856_write(client, 0xde, 0); + + bt856_setbit(client, 0xdc, 3, 1); + //bt856_setbit(client, 0xdc, 6, 0); + bt856_setbit(client, 0xdc, 4, 1); + + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + bt856_setbit(client, 0xdc, 2, 0); + break; + + case VIDEO_MODE_PAL: + bt856_setbit(client, 0xdc, 2, 1); + break; + } + + bt856_setbit(client, 0xdc, 1, 1); + bt856_setbit(client, 0xde, 4, 0); + bt856_setbit(client, 0xde, 3, 1); + + if (debug != 0) + bt856_dump(client); + + dprintk(1, KERN_INFO "%s_attach: at address 0x%x\n", I2C_NAME(client), + client->addr << 1); + + return 0; +} + +static int +bt856_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "bt856.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &bt856_detect_client); +} + +static int +bt856_detach_client (struct i2c_client *client) +{ + struct bt856 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_bt856 = { + .driver = { + .name = "bt856", + }, + + .id = I2C_DRIVERID_BT856, + + .attach_adapter = bt856_attach_adapter, + .detach_client = bt856_detach_client, + .command = bt856_command, +}; + +static int __init +bt856_init (void) +{ + return i2c_add_driver(&i2c_driver_bt856); +} + +static void __exit +bt856_exit (void) +{ + i2c_del_driver(&i2c_driver_bt856); +} + +module_init(bt856_init); +module_exit(bt856_exit); diff --git a/linux/drivers/media/video/bw-qcam.c b/linux/drivers/media/video/bw-qcam.c new file mode 100644 index 000000000..0a276d967 --- /dev/null +++ b/linux/drivers/media/video/bw-qcam.c @@ -0,0 +1,1029 @@ +/* + * QuickCam Driver For Video4Linux. + * + * 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/slab.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.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 */ +static int video_nr = -1; + +module_param(maxpoll, int, 0); +module_param(yieldlines, int, 0); +module_param(video_nr, int, 0); + +static inline int read_lpstatus(struct qcam_device *q) +{ + return parport_read_status(q->pport); +} + +static inline int read_lpdata(struct qcam_device *q) +{ + return parport_read_data(q->pport); +} + +static inline void write_lpdata(struct qcam_device *q, int d) +{ + parport_write_data(q->pport, d); +} + +static 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)); + + mutex_init(&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) + { + msleep_interruptible(5); + } + 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) + { + msleep_interruptible(5); + } + 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) + { + msleep_interruptible(5); + } + 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. */ + +static 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. */ + +static 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. */ + +static long qc_capture(struct qcam_device * q, char __user *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, NULL); /* 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) { + msleep_interruptible(5); + 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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + 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; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + 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"); + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + strcpy(v->name, "Format"); + v->rangelow=0; + v->rangehigh=0; + v->flags= 0; + v->mode = VIDEO_MODE_AUTO; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + if(v->mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + 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; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + 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; + + mutex_lock(&qcam->lock); + qc_setscanmode(qcam); + mutex_unlock(&qcam->lock); + qcam->status |= QC_PARAM_CHANGE; + + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + 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; + } + mutex_lock(&qcam->lock); + qc_setscanmode(qcam); + mutex_unlock(&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 = arg; + memset(vw, 0, sizeof(*vw)); + vw->width=qcam->width/qcam->transfer_scale; + vw->height=qcam->height/qcam->transfer_scale; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int qcam_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, qcam_do_ioctl); +} + +static ssize_t qcam_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + parport_claim_or_block(qcam->pdev); + + mutex_lock(&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); + + mutex_unlock(&qcam->lock); + + parport_release(qcam->pdev); + return len; +} + +static struct file_operations qcam_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = qcam_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .read = qcam_read, + .llseek = no_llseek, +}; +static struct video_device qcam_template= +{ + .owner = THIS_MODULE, + .name = "Connectix Quickcam", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_QCAM_BW, + .fops = &qcam_fops, +}; + +#define MAX_CAMS 4 +static struct qcam_device *qcams[MAX_CAMS]; +static unsigned int num_cams = 0; + +static 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, video_nr)==-1) + { + parport_unregister_device(qcam->pdev); + kfree(qcam); + return -ENODEV; + } + + qcams[num_cams++] = qcam; + + return 0; +} + +static 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_param_array(parport, charp, NULL, 0); +#endif + +static int accept_bwqcam(struct parport *port) +{ +#ifdef MODULE + int n; + + if (parport[0] && strncmp(parport[0], "auto", 4) != 0) { + /* 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; + } + if (r == port->number) + return 1; + } + return 0; + } +#endif + return 1; +} + +static void bwqcam_attach(struct parport *port) +{ + if (accept_bwqcam(port)) + init_bwqcam(port); +} + +static void bwqcam_detach(struct parport *port) +{ + int i; + for (i = 0; i < num_cams; i++) { + struct qcam_device *qcam = qcams[i]; + if (qcam && qcam->pdev->port == port) { + qcams[i] = NULL; + close_bwqcam(qcam); + } + } +} + +static struct parport_driver bwqcam_driver = { + .name = "bw-qcam", + .attach = bwqcam_attach, + .detach = bwqcam_detach, +}; + +static void __exit exit_bw_qcams(void) +{ + parport_unregister_driver(&bwqcam_driver); +} + +static int __init init_bw_qcams(void) +{ +#ifdef MODULE + /* 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; + } +#endif + return parport_register_driver(&bwqcam_driver); +} + +module_init(init_bw_qcams); +module_exit(exit_bw_qcams); + +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/bw-qcam.h b/linux/drivers/media/video/bw-qcam.h new file mode 100644 index 000000000..6701dafbc --- /dev/null +++ b/linux/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 mutex 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/linux/drivers/media/video/c-qcam.c b/linux/drivers/media/video/c-qcam.c new file mode 100644 index 000000000..c1957a143 --- /dev/null +++ b/linux/drivers/media/video/c-qcam.c @@ -0,0 +1,858 @@ +/* + * 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/slab.h> +#include <linux/mm.h> +#include <linux/parport.h> +#include <linux/sched.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.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 mutex 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 int video_nr = -1; + +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; + msleep_interruptible(100); + } + + /* 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; + msleep_interruptible(100); + } + + /* 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 __user *buf, unsigned long len) +{ + unsigned lines, pixelsperline, bitsperxfer; + unsigned int is_bi_dir = q->bidirectional; + size_t wantlen, outptr = 0; + char tmpbuf[BUFSZ]; + + if (!access_ok(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; + cond_resched(); + } + + 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); + cond_resched(); + } 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); + cond_resched(); + } 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_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + 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; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + 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"); + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + memset(v,0,sizeof(*v)); + strcpy(v->name, "Format"); + v->mode = VIDEO_MODE_AUTO; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + if(v->mode!=VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + 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; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + + /* + * 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; + + mutex_lock(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + mutex_unlock(&qcam->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + + 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 */ + mutex_lock(&qcam->lock); + parport_claim_or_block(qcam->pdev); + qc_setup(qcam); + parport_release(qcam->pdev); + mutex_unlock(&qcam->lock); + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + memset(vw, 0, sizeof(*vw)); + vw->width=qcam->width; + vw->height=qcam->height; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int qcam_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, qcam_do_ioctl); +} + +static ssize_t qcam_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct qcam_device *qcam=(struct qcam_device *)v; + int len; + + mutex_lock(&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); + mutex_unlock(&qcam->lock); + return len; +} + +/* video device template */ +static struct file_operations qcam_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = qcam_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .read = qcam_read, + .llseek = no_llseek, +}; + +static struct video_device qcam_template= +{ + .owner = THIS_MODULE, + .name = "Colour QuickCam", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_QCAM_C, + .fops = &qcam_fops, +}; + +/* 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)); + + mutex_init(&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; + +static 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, video_nr)==-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; +} + +static 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 = { + .name = "cqcam", + .attach = cq_attach, + .detach = cq_detach, +}; + +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_LICENSE("GPL"); + +/* FIXME: parport=auto would never have worked, surely? --RR */ +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_param_array(parport, int, NULL, 0); +module_param(probe, int, 0); +module_param(force_rgb, bool, 0); +module_param(video_nr, int, 0); + +module_init(cqcam_init); +module_exit(cqcam_cleanup); diff --git a/linux/drivers/media/video/cpia.c b/linux/drivers/media/video/cpia.c new file mode 100644 index 000000000..85d84e89d --- /dev/null +++ b/linux/drivers/media/video/cpia.c @@ -0,0 +1,4073 @@ +/* + * 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 <johannes@erdfelt.com> + * (C) Copyright 2000 STMicroelectronics + * + * 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_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/mutex.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 + +static int video_nr = -1; + +#ifdef MODULE +module_param(video_nr, int, 0); +MODULE_AUTHOR("Scott J. Bertin <sbertin@securenym.net> & Peter Pregler <Peter_Pregler@email.com> & Johannes Erdfelt <johannes@erdfelt.com>"); +MODULE_DESCRIPTION("V4L-driver for Vision CPiA based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); +#endif + +static unsigned short colorspace_conv; +module_param(colorspace_conv, ushort, 0444); +MODULE_PARM_DESC(colorspace_conv, + " Colorspace conversion:" + "\n 0 = disable, 1 = enable" + "\n Default value is 0" + ); + +#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_ResetFrameCounter (INPUT | CPIA_MODULE_VP_CTRL | 2) +#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_GrabReset (OUTPUT | CPIA_MODULE_CAPTURE | 15) + +#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) +#define CPIA_COMMAND_Null (OUTPUT | CPIA_MODULE_DEBUG | 11) + +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 +#define COMMAND_SETLIGHTS 0x8000 + +#define ROUND_UP_EXP_FOR_FLICKER 15 + +/* Constants for automatic frame rate adjustment */ +#define MAX_EXP 302 +#define MAX_EXP_102 255 +#define LOW_EXP 140 +#define VERY_LOW_EXP 70 +#define TC 94 +#define EXP_ACC_DARK 50 +#define EXP_ACC_LIGHT 90 +#define HIGH_COMP_102 160 +#define MAX_COMP 239 +#define DARK_TIME 3 +#define LIGHT_TIME 3 + +/* Maximum number of 10ms loops to wait for the stream to become ready */ +#define READY_TIMEOUT 100 + +/* 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); +static int find_over_exposure(int brightness); +static void set_flicker(struct cam_params *params, volatile u32 *command_flags, + int on); + + +/********************************************************************** + * + * Memory management + * + **********************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + 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) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + 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[29]; + + /* 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); + /* QX3 specific entries */ + if (cam->params.qx3.qx3_detected) { + out += sprintf(out, "button: %4d\n", + cam->params.qx3.button); + out += sprintf(out, "cradled: %4d\n", + cam->params.qx3.cradled); + } + out += sprintf(out, "video_size: %s\n", + cam->params.format.videoSize == VIDEOSIZE_CIF ? + "CIF " : "QCIF"); + 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, "sub_sample: %8s %8s %8s %8s\n", + cam->params.format.subSample == SUBSAMPLE_420 ? + "420" : "422", "420", "422", "422"); + out += sprintf(out, "yuv_order: %8s %8s %8s %8s\n", + cam->params.format.yuvOrder == YUVORDER_YUYV ? + "YUYV" : "UYVY", "YUYV" , "UYVY", "YUYV"); + out += sprintf(out, "ecp_timing: %8s %8s %8s %8s\n", + cam->params.ecpTiming ? "slow" : "normal", "slow", + "normal", "normal"); + + if (cam->params.colourBalance.balanceMode == 2) { + 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 %8d", 1, 2, 2); + else + sprintf(tmpstr, "%8d %8d %8d", 1, 8, 2); + + if (cam->params.exposure.gainMode == 0) + out += sprintf(out, "max_gain: unknown %28s" + " powers of 2\n", tmpstr); + else + out += sprintf(out, "max_gain: %8d %28s" + " 1,2,4 or 8 \n", + 1<<(cam->params.exposure.gainMode-1), tmpstr); + + 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/2 to 127 */ + tmp = 254; + else + tmp = 510; + + 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 = MAX_EXP_102; + else + tmp = MAX_EXP; + + 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, COMP_RED, 255, COMP_RED); + out += sprintf(out, "green1_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green1Comp, COMP_GREEN1, 255, + COMP_GREEN1); + out += sprintf(out, "green2_comp: %8d %8d %8d %8d\n", + cam->params.exposure.green2Comp, COMP_GREEN2, 255, + COMP_GREEN2); + out += sprintf(out, "blue_comp: %8d %8d %8d %8d\n", + cam->params.exposure.blueComp, COMP_BLUE, 255, COMP_BLUE); + + 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); + if(cam->params.flickerControl.allowableOverExposure < 0) + out += sprintf(out, "allowable_overexposure: %4dauto auto %8d auto\n", + -cam->params.flickerControl.allowableOverExposure, + 255); + else + out += sprintf(out, "allowable_overexposure: %8d auto %8d auto\n", + cam->params.flickerControl.allowableOverExposure, + 255); + 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", "on", + "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, 1, 30, 15); + out += sprintf(out, "target_quality: %8d %8d %8d %8d\n", + cam->params.compressionTarget.targetQ, 1, 64, 5); + out += sprintf(out, "y_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.yThreshold, 0, 31, 6); + out += sprintf(out, "uv_threshold: %8d %8d %8d %8d\n", + cam->params.yuvThreshold.uvThreshold, 0, 31, 6); + 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); + /* QX3 specific entries */ + if (cam->params.qx3.qx3_detected) { + out += sprintf(out, "toplight: %8s %8s %8s %8s\n", + cam->params.qx3.toplight ? "on" : "off", + "off", "on", "off"); + out += sprintf(out, "bottomlight: %8s %8s %8s %8s\n", + cam->params.qx3.bottomlight ? "on" : "off", + "off", "on", "off"); + } + + 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 match(char *checkstr, char **buffer, unsigned long *count, + int *find_colon, int *err) +{ + int ret, colon_found = 1; + int len = strlen(checkstr); + ret = (len <= *count && strncmp(*buffer, checkstr, 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) + *err = -EINVAL; + *find_colon = 0; + } + } + return ret; +} + +static unsigned long int value(char **buffer, unsigned long *count, int *err) +{ + char *p; + unsigned long int ret; + ret = simple_strtoul(*buffer, &p, 0); + if (p == *buffer) + *err = -EINVAL; + else { + *count -= p - *buffer; + *buffer = p; + } + return ret; +} + +static int cpia_write_proc(struct file *file, const char __user *buf, + unsigned long count, void *data) +{ + struct cam_data *cam = data; + struct cam_params new_params; + char *page, *buffer; + int retval, find_colon; + int size = count; + unsigned long val = 0; + u32 command_flags = 0; + u8 new_mains; + + /* + * This code to copy from buf to page is shamelessly copied + * from the comx driver + */ + if (count > PAGE_SIZE) { + printk(KERN_ERR "count is %lu > %d!!!\n", count, (int)PAGE_SIZE); + return -ENOSPC; + } + + if (!(page = (char *)__get_free_page(GFP_KERNEL))) return -ENOMEM; + + if(copy_from_user(page, buf, count)) + { + retval = -EFAULT; + goto out; + } + + if (page[count-1] == '\n') + page[count-1] = '\0'; + else if (count < PAGE_SIZE) + page[count] = '\0'; + else if (page[count]) { + retval = -EINVAL; + goto out; + } + + buffer = page; + + if (mutex_lock_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) (match(x, &buffer, &count, &find_colon, &retval)) +#define VALUE (value(&buffer,&count, &retval)) +#define FIRMWARE_VERSION(x,y) (new_params.version.firmwareVersion == (x) && \ + new_params.version.firmwareRevision == (y)) + + 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; + if(new_params.flickerControl.allowableOverExposure < 0) + new_params.flickerControl.allowableOverExposure = + -find_over_exposure(new_params.colourParams.brightness); + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + + } 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; + cam->exposure_status = EXPOSURE_NORMAL; + } 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("sub_sample")) { + if (!retval && MATCH("420")) + new_params.format.subSample = SUBSAMPLE_420; + else if (!retval && MATCH("422")) + new_params.format.subSample = SUBSAMPLE_422; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETFORMAT; + } else if (MATCH("yuv_order")) { + if (!retval && MATCH("YUYV")) + new_params.format.yuvOrder = YUVORDER_YUYV; + else if (!retval && MATCH("UYVY")) + new_params.format.yuvOrder = YUVORDER_UYVY; + else + retval = -EINVAL; + + command_flags |= COMMAND_SETFORMAT; + } 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.balanceMode = 3; + else if (!retval && MATCH("auto")) + new_params.colourBalance.balanceMode = 2; + 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; + new_params.colourBalance.balanceMode = 1; + } 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; + new_params.colourBalance.balanceMode = 1; + } 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; + new_params.colourBalance.balanceMode = 1; + } 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; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + } 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; + break; + case 2: + new_params.exposure.gain = 1; + break; + case 4: + new_params.exposure.gain = 2; + break; + case 8: + new_params.exposure.gain = 3; + break; + default: + retval = -EINVAL; + break; + } + new_params.exposure.expMode = 1; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + new_params.flickerControl.flickerMode = 0; + 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/2; + + if (!retval) { + if (val < 256) { + /* 1-02 firmware limits fineExp/2 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; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + 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 <= MAX_EXP) { + if (FIRMWARE_VERSION(1,2) && + val > MAX_EXP_102) + val = MAX_EXP_102; + new_params.exposure.coarseExpLo = + val & 0xff; + new_params.exposure.coarseExpHi = + val >> 8; + new_params.exposure.expMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + 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 >= COMP_RED && val <= 255) { + new_params.exposure.redComp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green1_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_GREEN1 && val <= 255) { + new_params.exposure.green1Comp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("green2_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_GREEN2 && val <= 255) { + new_params.exposure.green2Comp = val; + new_params.exposure.compMode = 1; + command_flags |= COMMAND_SETEXPOSURE; + } else + retval = -EINVAL; + } + } else if (MATCH("blue_comp")) { + if (!retval) + val = VALUE; + + if (!retval) { + if (val >= COMP_BLUE && val <= 255) { + new_params.exposure.blueComp = val; + new_params.exposure.compMode = 1; + 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")) { + set_flicker(&new_params, &command_flags, 1); + } else if (!retval && MATCH("off")) { + set_flicker(&new_params, &command_flags, 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 && MATCH("auto")) { + new_params.flickerControl.allowableOverExposure = + -find_over_exposure(new_params.colourParams.brightness); + if(new_params.flickerControl.flickerMode != 0) + command_flags |= COMMAND_SETFLICKERCTRL; + } else { + if (!retval) + val = VALUE; + + if (!retval) { + if (val <= 0xff) { + new_params.flickerControl. + allowableOverExposure = val; + if(new_params.flickerControl.flickerMode != 0) + 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 if (!retval && MATCH("on")) + new_params.compression.decimation = 1; + 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) { + if(val > 0 && val <= 30) + new_params.compressionTarget.targetFR = val; + else + retval = -EINVAL; + } + command_flags |= COMMAND_SETCOMPRESSIONTARGET; + } else if (MATCH("target_quality")) { + if (!retval) + val = VALUE; + + if (!retval) { + if(val > 0 && val <= 64) + new_params.compressionTarget.targetQ = val; + else + retval = -EINVAL; + } + 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 if (MATCH("toplight")) { + if (!retval && MATCH("on")) + new_params.qx3.toplight = 1; + else if (!retval && MATCH("off")) + new_params.qx3.toplight = 0; + else + retval = -EINVAL; + command_flags |= COMMAND_SETLIGHTS; + } else if (MATCH("bottomlight")) { + if (!retval && MATCH("on")) + new_params.qx3.bottomlight = 1; + else if (!retval && MATCH("off")) + new_params.qx3.bottomlight = 0; + else + retval = -EINVAL; + command_flags |= COMMAND_SETLIGHTS; + } else { + DBG("No match found\n"); + retval = -EINVAL; + } + + if (!retval) { + while (count && isspace(*buffer) && *buffer != '\n') { + --count; + ++buffer; + } + if (count) { + if (*buffer == '\0' && count != 1) + retval = -EINVAL; + else if (*buffer != '\n' && *buffer != ';' && + *buffer != '\0') + retval = -EINVAL; + else { + --count; + ++buffer; + } + } + } + } +#undef MATCH +#undef VALUE +#undef FIRMWARE_VERSION + 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; + } + if((command_flags & COMMAND_SETEXPOSURE) && + new_params.exposure.expMode == 2) + cam->exposure_status = EXPOSURE_NORMAL; + + 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); + + mutex_unlock(&cam->param_lock); + +out: + free_page((unsigned long)page); + 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; + /* + size of the proc entry is 3736 bytes for the standard webcam; + the extra features of the QX3 microscope add 189 bytes. + (we have not yet probed the camera to see which type it is). + */ + ent->size = 3736 + 189; + 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 = proc_mkdir("cpia", NULL); + + if (cpia_proc_root) + cpia_proc_root->owner = THIS_MODULE; + else + LOG("Unable to initialise /proc/cpia\n"); +} + +static void __exit proc_cpia_destroy(void) +{ + remove_proc_entry("cpia", NULL); +} +#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) +{ + if ((palette == VIDEO_PALETTE_YUV422 && depth == 16) || + (palette == VIDEO_PALETTE_YUYV && depth == 16)) + return 1; + + if (colorspace_conv) + 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_UYVY && depth == 16); + + return 0; +} + +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.rowStart=0; + 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.rowStart=6; + 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.rowStart=9; + 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.rowStart=12; + 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.rowStart=15; + 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.rowStart=18; + 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.rowStart=0; + 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.rowStart=3; + 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.rowStart=6; + 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.rowStart=9; + 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.rowStart=12; + 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.rowStart=6; + cam->params.streamStartLine = 60; + break; + default: + LOG("bad videosize value: %d\n", cam->video_size); + return; + } + + if(cam->vc.width == 0) + cam->vc.width = cam->vw.width; + if(cam->vc.height == 0) + cam->vc.height = cam->vw.height; + + cam->params.roi.colStart += cam->vc.x >> 3; + cam->params.roi.colEnd = cam->params.roi.colStart + + (cam->vc.width >> 3); + cam->params.roi.rowStart += cam->vc.y >> 2; + cam->params.roi.rowEnd = cam->params.roi.rowStart + + (cam->vc.height >> 2); + + 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 = NULL; + for (i=0; i < FRAME_NUM; i++) + cam->frame[i].data = NULL; + + return 0; +} + + +static inline void 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: + mutex_lock(&cam->param_lock); + datasize=8; + break; + case CPIA_COMMAND_ReadMCPorts: + case CPIA_COMMAND_ReadVCRegs: + datasize = 4; + 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) + mutex_unlock(&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]; + mutex_unlock(&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]; + mutex_unlock(&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]; + mutex_unlock(&cam->param_lock); + break; + + case CPIA_COMMAND_ReadMCPorts: + if (!cam->params.qx3.qx3_detected) + break; + /* test button press */ + cam->params.qx3.button = ((data[1] & 0x02) == 0); + if (cam->params.qx3.button) { + /* button pressed - unlock the latch */ + do_command(cam,CPIA_COMMAND_WriteMCPort,3,0xDF,0xDF,0); + do_command(cam,CPIA_COMMAND_WriteMCPort,3,0xFF,0xFF,0); + } + + /* test whether microscope is cradled */ + cam->params.qx3.cradled = ((data[2] & 0x40) == 0); + 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) + DBG("%x - failed\n", command); + + return retval; +} + +/********************************************************************** + * + * Colorspace conversion + * + **********************************************************************/ +#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16) + +static int convert420(unsigned char *yuv, unsigned char *rgb, int out_fmt, + int linesize, int mmap_kludge) +{ + int y, u, v, r, g, b, y1; + + /* Odd lines use the same u and v as the previous line. + * Because of compression, it is necessary to get this + * information from the decoded image. */ + switch(out_fmt) { + case VIDEO_PALETTE_RGB555: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + r = ((*(rgb+1-linesize)) & 0x7c) << 1; + g = ((*(rgb-linesize)) & 0xe0) >> 4 | + ((*(rgb+1-linesize)) & 0x03) << 6; + b = ((*(rgb-linesize)) & 0x1f) << 3; + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u - 53294 * v; + b = 132278 * u; + *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: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + r = (*(rgb+1-linesize)) & 0xf8; + g = ((*(rgb-linesize)) & 0xe0) >> 3 | + ((*(rgb+1-linesize)) & 0x07) << 5; + b = ((*(rgb-linesize)) & 0x1f) << 3; + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u - 53294 * v; + b = 132278 * u; + *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; + break; + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + y = (*yuv++ - 16) * 76310; + y1 = (*yuv - 16) * 76310; + if (mmap_kludge) { + r = *(rgb+2-linesize); + g = *(rgb+1-linesize); + b = *(rgb-linesize); + } else { + r = *(rgb-linesize); + g = *(rgb+1-linesize); + b = *(rgb+2-linesize); + } + u = (-53294 * r - 104635 * g + 157929 * b) / 5756495; + v = (157968 * r - 132278 * g - 25690 * b) / 5366159; + r = 104635 * v; + g = -25690 * u + -53294 * v; + b = 132278 * u; + if (mmap_kludge) { + *rgb++ = LIMIT(b+y); + *rgb++ = LIMIT(g+y); + *rgb++ = LIMIT(r+y); + if(out_fmt == VIDEO_PALETTE_RGB32) + 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); + if(out_fmt == VIDEO_PALETTE_RGB32) + rgb++; + *rgb++ = LIMIT(r+y1); + *rgb++ = LIMIT(g+y1); + *rgb = LIMIT(b+y1); + } + if(out_fmt == VIDEO_PALETTE_RGB32) + return 8; + return 6; + case VIDEO_PALETTE_YUV422: + case VIDEO_PALETTE_YUYV: + y = *yuv++; + u = *(rgb+1-linesize); + y1 = *yuv; + v = *(rgb+3-linesize); + *rgb++ = y; + *rgb++ = u; + *rgb++ = y1; + *rgb = v; + return 4; + case VIDEO_PALETTE_UYVY: + u = *(rgb-linesize); + y = *yuv++; + v = *(rgb+2-linesize); + y1 = *yuv; + *rgb++ = u; + *rgb++ = y; + *rgb++ = v; + *rgb = y1; + return 4; + case VIDEO_PALETTE_GREY: + *rgb++ = *yuv++; + *rgb = *yuv; + return 2; + default: + DBG("Empty: %d\n", out_fmt); + return 0; + } +} + + +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: + return count; + 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, decimation, even_line, origsize, out_fmt; + int rows, cols, linesize, subsample_422; + + /* make sure params don't change while we are decoding */ + mutex_lock(&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"); + mutex_unlock(&cam->param_lock); + return -1; + } + + if ((ibuf[16] != VIDEOSIZE_QCIF) && (ibuf[16] != VIDEOSIZE_CIF)) { + LOG("wrong video size\n"); + mutex_unlock(&cam->param_lock); + return -1; + } + + if (ibuf[17] != SUBSAMPLE_420 && ibuf[17] != SUBSAMPLE_422) { + LOG("illegal subtype %d\n",ibuf[17]); + mutex_unlock(&cam->param_lock); + return -1; + } + subsample_422 = ibuf[17] == SUBSAMPLE_422; + + if (ibuf[18] != YUVORDER_YUYV && ibuf[18] != YUVORDER_UYVY) { + LOG("illegal yuvorder %d\n",ibuf[18]); + mutex_unlock(&cam->param_lock); + return -1; + } + in_uyvy = ibuf[18] == YUVORDER_UYVY; + + 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"); + mutex_unlock(&cam->param_lock); + return -1; + } + cols = 8*(ibuf[25] - ibuf[24]); + rows = 4*(ibuf[27] - ibuf[26]); + + + if ((ibuf[28] != NOT_COMPRESSED) && (ibuf[28] != COMPRESSED)) { + LOG("illegal compression %d\n",ibuf[28]); + mutex_unlock(&cam->param_lock); + return -1; + } + compressed = (ibuf[28] == COMPRESSED); + + if (ibuf[29] != NO_DECIMATION && ibuf[29] != DECIMATION_ENAB) { + LOG("illegal decimation %d\n",ibuf[29]); + mutex_unlock(&cam->param_lock); + return -1; + } + decimation = (ibuf[29] == DECIMATION_ENAB); + + 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]; + mutex_unlock(&cam->param_lock); + + linesize = skipcount(cols, out_fmt); + ibuf += FRAME_HEADER_SIZE; + size -= FRAME_HEADER_SIZE; + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; + even_line = 1; + + 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))) { + if(subsample_422 || even_line) { + obuf += yuvconvert(ibuf, obuf, out_fmt, + in_uyvy, cam->mmap_kludge); + ibuf += 4; + ll -= 4; + } else { + /* SUBSAMPLE_420 on an odd line */ + obuf += convert420(ibuf, obuf, + out_fmt, linesize, + cam->mmap_kludge); + ibuf += 2; + ll -= 2; + } + } else { + /*skip compressed interval from previous frame*/ + obuf += skipcount(*ibuf >> 1, out_fmt); + if (obuf > end_obuf) { + LOG("Insufficient buffer size\n"); + return -1; + } + ++ibuf; + ll--; + } + } + if (ll == 1) { + if (*ibuf != EOL) { + DBG("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(decimation) { + /* skip the odd lines for now */ + obuf += linesize; + } + + if (size > 1) { + ll = ibuf[0] | (ibuf[1] << 8); + ibuf += 2; /* skip over line length */ + } + if(!decimation) + even_line = !even_line; + } else { + LOG("line length was not 1 but %d after %d/%d bytes\n", + ll, origsize-size, origsize); + return -1; + } + } + + if(decimation) { + /* interpolate odd rows */ + int i, j; + u8 *prev, *next; + prev = cam->decompressed_frame.data; + obuf = prev+linesize; + next = obuf+linesize; + for(i=1; i<rows-1; i+=2) { + for(j=0; j<linesize; ++j) { + *obuf++ = ((int)*prev++ + *next++) / 2; + } + prev += linesize; + obuf += linesize; + next += linesize; + } + /* last row is odd, just copy previous row */ + memcpy(obuf, prev, linesize); + } + + 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); +} + + +/* find_over_exposure + * Finds a suitable value of OverExposure for use with SetFlickerCtrl + * Some calculation is required because this value changes with the brightness + * set with SetColourParameters + * + * Parameters: Brightness - last brightness value set with SetColourParameters + * + * Returns: OverExposure value to use with SetFlickerCtrl + */ +#define FLICKER_MAX_EXPOSURE 250 +#define FLICKER_ALLOWABLE_OVER_EXPOSURE 146 +#define FLICKER_BRIGHTNESS_CONSTANT 59 +static int find_over_exposure(int brightness) +{ + int MaxAllowableOverExposure, OverExposure; + + MaxAllowableOverExposure = FLICKER_MAX_EXPOSURE - brightness - + FLICKER_BRIGHTNESS_CONSTANT; + + if (MaxAllowableOverExposure < FLICKER_ALLOWABLE_OVER_EXPOSURE) { + OverExposure = MaxAllowableOverExposure; + } else { + OverExposure = FLICKER_ALLOWABLE_OVER_EXPOSURE; + } + + return OverExposure; +} +#undef FLICKER_MAX_EXPOSURE +#undef FLICKER_ALLOWABLE_OVER_EXPOSURE +#undef FLICKER_BRIGHTNESS_CONSTANT + +/* update various camera modes and settings */ +static void dispatch_commands(struct cam_data *cam) +{ + mutex_lock(&cam->param_lock); + if (cam->cmd_queue==COMMAND_NONE) { + mutex_unlock(&cam->param_lock); + return; + } + DEB_BYTE(cam->cmd_queue); + DEB_BYTE(cam->cmd_queue>>8); + 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_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_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_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_SETEXPOSURE) { + do_command_extended(cam, CPIA_COMMAND_SetExposure, + cam->params.exposure.gainMode, + 1, + 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->params.exposure.expMode != 1) { + do_command_extended(cam, CPIA_COMMAND_SetExposure, + 0, + cam->params.exposure.expMode, + 0, 0, + cam->params.exposure.gain, + cam->params.exposure.fineExp, + cam->params.exposure.coarseExpLo, + cam->params.exposure.coarseExpHi, + 0, 0, 0, 0); + } + } + + if (cam->cmd_queue & COMMAND_SETCOLOURBALANCE) { + if (cam->params.colourBalance.balanceMode == 1) { + 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->params.colourBalance.balanceMode == 2) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 2, 0, 0, 0); + } + if (cam->params.colourBalance.balanceMode == 3) { + do_command(cam, CPIA_COMMAND_SetColourBalance, + 3, 0, 0, 0); + } + } + + 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_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_SETCOMPRESSION) + do_command(cam, CPIA_COMMAND_SetCompression, + cam->params.compression.mode, + cam->params.compression.decimation, 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_SETFLICKERCTRL) + do_command(cam, CPIA_COMMAND_SetFlickerCtrl, + cam->params.flickerControl.flickerMode, + cam->params.flickerControl.coarseJump, + abs(cam->params.flickerControl.allowableOverExposure), + 0); + + if (cam->cmd_queue & COMMAND_SETECPTIMING) + do_command(cam, CPIA_COMMAND_SetECPTiming, + cam->params.ecpTiming, 0, 0, 0); + + 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); + + if (cam->cmd_queue & COMMAND_SETLIGHTS && cam->params.qx3.qx3_detected) + { + int p1 = (cam->params.qx3.bottomlight == 0) << 1; + int p2 = (cam->params.qx3.toplight == 0) << 3; + do_command(cam, CPIA_COMMAND_WriteVCReg, 0x90, 0x8F, 0x50, 0); + do_command(cam, CPIA_COMMAND_WriteMCPort, 2, 0, (p1|p2|0xE0), 0); + } + + cam->cmd_queue = COMMAND_NONE; + mutex_unlock(&cam->param_lock); + return; +} + + + +static void set_flicker(struct cam_params *params, volatile u32 *command_flags, + int on) +{ + /* Everything in here is from the Windows driver */ +#define FIRMWARE_VERSION(x,y) (params->version.firmwareVersion == (x) && \ + params->version.firmwareRevision == (y)) +/* define for compgain calculation */ +#if 0 +#define COMPGAIN(base, curexp, newexp) \ + (u8) ((((float) base - 128.0) * ((float) curexp / (float) newexp)) + 128.5) +#define EXP_FROM_COMP(basecomp, curcomp, curexp) \ + (u16)((float)curexp * (float)(u8)(curcomp + 128) / (float)(u8)(basecomp - 128)) +#else + /* equivalent functions without floating point math */ +#define COMPGAIN(base, curexp, newexp) \ + (u8)(128 + (((u32)(2*(base-128)*curexp + newexp)) / (2* newexp)) ) +#define EXP_FROM_COMP(basecomp, curcomp, curexp) \ + (u16)(((u32)(curexp * (u8)(curcomp + 128)) / (u8)(basecomp - 128))) +#endif + + + int currentexp = params->exposure.coarseExpLo + + params->exposure.coarseExpHi*256; + int startexp; + if (on) { + int cj = params->flickerControl.coarseJump; + params->flickerControl.flickerMode = 1; + params->flickerControl.disabled = 0; + if(params->exposure.expMode != 2) + *command_flags |= COMMAND_SETEXPOSURE; + params->exposure.expMode = 2; + currentexp = currentexp << params->exposure.gain; + params->exposure.gain = 0; + /* round down current exposure to nearest value */ + startexp = (currentexp + ROUND_UP_EXP_FOR_FLICKER) / cj; + if(startexp < 1) + startexp = 1; + startexp = (startexp * cj) - 1; + if(FIRMWARE_VERSION(1,2)) + while(startexp > MAX_EXP_102) + startexp -= cj; + else + while(startexp > MAX_EXP) + startexp -= cj; + params->exposure.coarseExpLo = startexp & 0xff; + params->exposure.coarseExpHi = startexp >> 8; + if (currentexp > startexp) { + if (currentexp > (2 * startexp)) + currentexp = 2 * startexp; + params->exposure.redComp = COMPGAIN (COMP_RED, currentexp, startexp); + params->exposure.green1Comp = COMPGAIN (COMP_GREEN1, currentexp, startexp); + params->exposure.green2Comp = COMPGAIN (COMP_GREEN2, currentexp, startexp); + params->exposure.blueComp = COMPGAIN (COMP_BLUE, currentexp, startexp); + } else { + params->exposure.redComp = COMP_RED; + params->exposure.green1Comp = COMP_GREEN1; + params->exposure.green2Comp = COMP_GREEN2; + params->exposure.blueComp = COMP_BLUE; + } + if(FIRMWARE_VERSION(1,2)) + params->exposure.compMode = 0; + else + params->exposure.compMode = 1; + + params->apcor.gain1 = 0x18; + params->apcor.gain2 = 0x18; + params->apcor.gain4 = 0x16; + params->apcor.gain8 = 0x14; + *command_flags |= COMMAND_SETAPCOR; + } else { + params->flickerControl.flickerMode = 0; + params->flickerControl.disabled = 1; + /* Coarse = average of equivalent coarse for each comp channel */ + startexp = EXP_FROM_COMP(COMP_RED, params->exposure.redComp, currentexp); + startexp += EXP_FROM_COMP(COMP_GREEN1, params->exposure.green1Comp, currentexp); + startexp += EXP_FROM_COMP(COMP_GREEN2, params->exposure.green2Comp, currentexp); + startexp += EXP_FROM_COMP(COMP_BLUE, params->exposure.blueComp, currentexp); + startexp = startexp >> 2; + while(startexp > MAX_EXP && + params->exposure.gain < params->exposure.gainMode-1) { + startexp = startexp >> 1; + ++params->exposure.gain; + } + if(FIRMWARE_VERSION(1,2) && startexp > MAX_EXP_102) + startexp = MAX_EXP_102; + if(startexp > MAX_EXP) + startexp = MAX_EXP; + params->exposure.coarseExpLo = startexp&0xff; + params->exposure.coarseExpHi = startexp >> 8; + params->exposure.redComp = COMP_RED; + params->exposure.green1Comp = COMP_GREEN1; + params->exposure.green2Comp = COMP_GREEN2; + params->exposure.blueComp = COMP_BLUE; + params->exposure.compMode = 1; + *command_flags |= COMMAND_SETEXPOSURE; + params->apcor.gain1 = 0x18; + params->apcor.gain2 = 0x16; + params->apcor.gain4 = 0x24; + params->apcor.gain8 = 0x34; + *command_flags |= COMMAND_SETAPCOR; + } + params->vlOffset.gain1 = 20; + params->vlOffset.gain2 = 24; + params->vlOffset.gain4 = 26; + params->vlOffset.gain8 = 26; + *command_flags |= COMMAND_SETVLOFFSET; +#undef FIRMWARE_VERSION +#undef EXP_FROM_COMP +#undef COMPGAIN +} + +#define FIRMWARE_VERSION(x,y) (cam->params.version.firmwareVersion == (x) && \ + cam->params.version.firmwareRevision == (y)) +/* monitor the exposure and adjust the sensor frame rate if needed */ +static void monitor_exposure(struct cam_data *cam) +{ + u8 exp_acc, bcomp, gain, coarseL, cmd[8], data[8]; + int retval, light_exp, dark_exp, very_dark_exp; + int old_exposure, new_exposure, framerate; + + /* get necessary stats and register settings from camera */ + /* do_command can't handle this, so do it ourselves */ + cmd[0] = CPIA_COMMAND_ReadVPRegs>>8; + cmd[1] = CPIA_COMMAND_ReadVPRegs&0xff; + cmd[2] = 30; + cmd[3] = 4; + cmd[4] = 9; + cmd[5] = 8; + cmd[6] = 8; + cmd[7] = 0; + retval = cam->ops->transferCmd(cam->lowlevel_data, cmd, data); + if (retval) { + LOG("ReadVPRegs(30,4,9,8) - failed, retval=%d\n", + retval); + return; + } + exp_acc = data[0]; + bcomp = data[1]; + gain = data[2]; + coarseL = data[3]; + + mutex_lock(&cam->param_lock); + light_exp = cam->params.colourParams.brightness + + TC - 50 + EXP_ACC_LIGHT; + if(light_exp > 255) + light_exp = 255; + dark_exp = cam->params.colourParams.brightness + + TC - 50 - EXP_ACC_DARK; + if(dark_exp < 0) + dark_exp = 0; + very_dark_exp = dark_exp/2; + + old_exposure = cam->params.exposure.coarseExpHi * 256 + + cam->params.exposure.coarseExpLo; + + if(!cam->params.flickerControl.disabled) { + /* Flicker control on */ + int max_comp = FIRMWARE_VERSION(1,2) ? MAX_COMP : HIGH_COMP_102; + bcomp += 128; /* decode */ + if(bcomp >= max_comp && exp_acc < dark_exp) { + /* dark */ + if(exp_acc < very_dark_exp) { + /* very dark */ + if(cam->exposure_status == EXPOSURE_VERY_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_DARK; + cam->exposure_count = 1; + } + } else { + /* just dark */ + if(cam->exposure_status == EXPOSURE_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_DARK; + cam->exposure_count = 1; + } + } + } else if(old_exposure <= LOW_EXP || exp_acc > light_exp) { + /* light */ + if(old_exposure <= VERY_LOW_EXP) { + /* very light */ + if(cam->exposure_status == EXPOSURE_VERY_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_LIGHT; + cam->exposure_count = 1; + } + } else { + /* just light */ + if(cam->exposure_status == EXPOSURE_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_LIGHT; + cam->exposure_count = 1; + } + } + } else { + /* not dark or light */ + cam->exposure_status = EXPOSURE_NORMAL; + } + } else { + /* Flicker control off */ + if(old_exposure >= MAX_EXP && exp_acc < dark_exp) { + /* dark */ + if(exp_acc < very_dark_exp) { + /* very dark */ + if(cam->exposure_status == EXPOSURE_VERY_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_DARK; + cam->exposure_count = 1; + } + } else { + /* just dark */ + if(cam->exposure_status == EXPOSURE_DARK) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_DARK; + cam->exposure_count = 1; + } + } + } else if(old_exposure <= LOW_EXP || exp_acc > light_exp) { + /* light */ + if(old_exposure <= VERY_LOW_EXP) { + /* very light */ + if(cam->exposure_status == EXPOSURE_VERY_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_VERY_LIGHT; + cam->exposure_count = 1; + } + } else { + /* just light */ + if(cam->exposure_status == EXPOSURE_LIGHT) + ++cam->exposure_count; + else { + cam->exposure_status = EXPOSURE_LIGHT; + cam->exposure_count = 1; + } + } + } else { + /* not dark or light */ + cam->exposure_status = EXPOSURE_NORMAL; + } + } + + framerate = cam->fps; + if(framerate > 30 || framerate < 1) + framerate = 1; + + if(!cam->params.flickerControl.disabled) { + /* Flicker control on */ + if((cam->exposure_status == EXPOSURE_VERY_DARK || + cam->exposure_status == EXPOSURE_DARK) && + cam->exposure_count >= DARK_TIME*framerate && + cam->params.sensorFps.divisor < 3) { + + /* dark for too long */ + ++cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + new_exposure = cam->params.flickerControl.coarseJump-1; + while(new_exposure < old_exposure/2) + new_exposure += cam->params.flickerControl.coarseJump; + cam->params.exposure.coarseExpLo = new_exposure & 0xff; + cam->params.exposure.coarseExpHi = new_exposure >> 8; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically decreasing sensor_fps\n"); + + } else if((cam->exposure_status == EXPOSURE_VERY_LIGHT || + cam->exposure_status == EXPOSURE_LIGHT) && + cam->exposure_count >= LIGHT_TIME*framerate && + cam->params.sensorFps.divisor > 0) { + + /* light for too long */ + int max_exp = FIRMWARE_VERSION(1,2) ? MAX_EXP_102 : MAX_EXP ; + + --cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + new_exposure = cam->params.flickerControl.coarseJump-1; + while(new_exposure < 2*old_exposure && + new_exposure+ + cam->params.flickerControl.coarseJump < max_exp) + new_exposure += cam->params.flickerControl.coarseJump; + cam->params.exposure.coarseExpLo = new_exposure & 0xff; + cam->params.exposure.coarseExpHi = new_exposure >> 8; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically increasing sensor_fps\n"); + } + } else { + /* Flicker control off */ + if((cam->exposure_status == EXPOSURE_VERY_DARK || + cam->exposure_status == EXPOSURE_DARK) && + cam->exposure_count >= DARK_TIME*framerate && + cam->params.sensorFps.divisor < 3) { + + /* dark for too long */ + ++cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + if(cam->params.exposure.gain > 0) { + --cam->params.exposure.gain; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + } + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically decreasing sensor_fps\n"); + + } else if((cam->exposure_status == EXPOSURE_VERY_LIGHT || + cam->exposure_status == EXPOSURE_LIGHT) && + cam->exposure_count >= LIGHT_TIME*framerate && + cam->params.sensorFps.divisor > 0) { + + /* light for too long */ + --cam->params.sensorFps.divisor; + cam->cmd_queue |= COMMAND_SETSENSORFPS; + + if(cam->params.exposure.gain < + cam->params.exposure.gainMode-1) { + ++cam->params.exposure.gain; + cam->cmd_queue |= COMMAND_SETEXPOSURE; + } + cam->exposure_status = EXPOSURE_NORMAL; + LOG("Automatically increasing sensor_fps\n"); + } + } + mutex_unlock(&cam->param_lock); +} + +/*-----------------------------------------------------------------*/ +/* if flicker is switched off, this function switches it back on.It checks, + however, that conditions are suitable before restarting it. + This should only be called for firmware version 1.2. + + It also adjust the colour balance when an exposure step is detected - as + long as flicker is running +*/ +static void restart_flicker(struct cam_data *cam) +{ + int cam_exposure, old_exp; + if(!FIRMWARE_VERSION(1,2)) + return; + mutex_lock(&cam->param_lock); + if(cam->params.flickerControl.flickerMode == 0 || + cam->raw_image[39] == 0) { + mutex_unlock(&cam->param_lock); + return; + } + cam_exposure = cam->raw_image[39]*2; + old_exp = cam->params.exposure.coarseExpLo + + cam->params.exposure.coarseExpHi*256; + /* + see how far away camera exposure is from a valid + flicker exposure value + */ + cam_exposure %= cam->params.flickerControl.coarseJump; + if(!cam->params.flickerControl.disabled && + cam_exposure <= cam->params.flickerControl.coarseJump - 3) { + /* Flicker control auto-disabled */ + cam->params.flickerControl.disabled = 1; + } + + if(cam->params.flickerControl.disabled && + cam->params.flickerControl.flickerMode && + old_exp > cam->params.flickerControl.coarseJump + + ROUND_UP_EXP_FOR_FLICKER) { + /* exposure is now high enough to switch + flicker control back on */ + set_flicker(&cam->params, &cam->cmd_queue, 1); + if((cam->cmd_queue & COMMAND_SETEXPOSURE) && + cam->params.exposure.expMode == 2) + cam->exposure_status = EXPOSURE_NORMAL; + + } + mutex_unlock(&cam->param_lock); +} +#undef FIRMWARE_VERSION + +static int clear_stall(struct cam_data *cam) +{ + /* FIXME: Does this actually work? */ + LOG("Clearing stall\n"); + + cam->ops->streamRead(cam->lowlevel_data, cam->raw_image, 0); + do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0); + return cam->params.status.streamState != STREAM_PAUSED; +} + +/* kernel thread function to read image from camera */ +static int 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); + /* Trial & error - Discarding a frame prevents the + first frame from having an error in the data. */ + do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0); + } + + /* init camera upload */ + 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 */ + int count = 0; + do_command(cam, CPIA_COMMAND_GetCameraStatus,0,0,0,0); + while (cam->params.status.streamState != STREAM_READY) { + if(++count > READY_TIMEOUT) + break; + if(cam->params.status.streamState == + STREAM_PAUSED) { + /* Bad news */ + if(!clear_stall(cam)) + return -EIO; + } + + cond_resched(); + + /* sleep for 10 ms, hopefully ;) */ + msleep_interruptible(10); + if (signal_pending(current)) + return -EINTR; + + do_command(cam, CPIA_COMMAND_GetCameraStatus, + 0, 0, 0, 0); + } + if(cam->params.status.streamState != STREAM_READY) { + continue; + } + } + + cond_resched(); + + /* grab image from camera */ + 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 */ + + /* Switch flicker control back on if it got turned off */ + restart_flicker(cam); + + /* If AEC is enabled, monitor the exposure and + adjust the sensor frame rate if needed */ + if(cam->params.exposure.expMode == 2) + monitor_exposure(cam); + + /* camera idle now so dispatch queued commands */ + dispatch_commands(cam); + + /* Update our knowledge of the camera state */ + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_GetExposure, 0, 0, 0, 0); + do_command(cam, CPIA_COMMAND_ReadMCPorts, 0, 0, 0, 0); + + /* decompress and convert image to by copying it from + * raw_image to decompressed_frame + */ + + cond_resched(); + + cam->image_size = parse_picture(cam, image_size); + if (cam->image_size <= 0) { + DBG("parse_picture failed %d\n", cam->image_size); + if(cam->params.compression.mode != + CPIA_COMPRESSION_NONE) { + /* Compression may not work right if we + had a bad frame, get the next one + uncompressed. */ + cam->first_frame = 1; + do_command(cam, CPIA_COMMAND_SetGrabMode, + CPIA_GRAB_SINGLE, 0, 0, 0); + /* FIXME: Trial & error - need up to 70ms for + the grab mode change to complete ? */ + msleep_interruptible(70); + if (signal_pending(current)) + return -EINTR; + } + } 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 (cam->first_frame) { + cam->first_frame = 0; + do_command(cam, CPIA_COMMAND_SetCompression, + cam->params.compression.mode, + cam->params.compression.decimation, 0, 0); + + /* Switch from single-grab to continuous grab */ + do_command(cam, CPIA_COMMAND_SetGrabMode, + CPIA_GRAB_CONTINUOUS, 0, 0, 0); + } + return 0; + } + return -EIO; +} + +static int capture_frame(struct cam_data *cam, struct video_mmap *vm) +{ + if (!cam->frame_buf) { + /* we do lazy allocation */ + int err; + if ((err = allocate_frame_buf(cam))) + return err; + } + + cam->curframe = vm->frame; + cam->frame[cam->curframe].state = FRAME_READY; + return fetch_frame(cam); +} + +static int goto_high_power(struct cam_data *cam) +{ + if (do_command(cam, CPIA_COMMAND_GotoHiPower, 0, 0, 0, 0)) + return -EIO; + msleep_interruptible(40); /* windows driver does it too */ + if(signal_pending(current)) + return -EINTR; + if (do_command(cam, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0)) + return -EIO; + if (cam->params.status.systemState == HI_POWER_STATE) { + DBG("camera now in HIGH power state\n"); + return 0; + } + printstatus(cam); + return -EIO; +} + +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) +{ + if(!(cam->cmd_queue & COMMAND_SETCOLOURBALANCE)) + do_command(cam, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0); + if(!(cam->cmd_queue & COMMAND_SETEXPOSURE)) + 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 int set_camera_state(struct cam_data *cam) +{ + cam->cmd_queue = COMMAND_SETCOMPRESSION | + COMMAND_SETCOMPRESSIONTARGET | + COMMAND_SETCOLOURPARAMS | + COMMAND_SETFORMAT | + COMMAND_SETYUVTHRESH | + COMMAND_SETECPTIMING | + COMMAND_SETCOMPRESSIONPARAMS | + COMMAND_SETEXPOSURE | + COMMAND_SETCOLOURBALANCE | + COMMAND_SETSENSORFPS | + COMMAND_SETAPCOR | + COMMAND_SETFLICKERCTRL | + COMMAND_SETVLOFFSET; + + do_command(cam, CPIA_COMMAND_SetGrabMode, CPIA_GRAB_SINGLE,0,0,0); + dispatch_commands(cam); + + /* Wait 6 frames for the sensor to get all settings and + AEC/ACB to settle */ + msleep_interruptible(6*(cam->params.sensorFps.baserate ? 33 : 40) * + (1 << cam->params.sensorFps.divisor) + 10); + + if(signal_pending(current)) + return -EINTR; + + save_camera_state(cam); + + return 0; +} + +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) +{ + int err; + /* 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 */ + err = goto_high_power(cam); + if(err) + return err; + do_command(cam, CPIA_COMMAND_DiscardFrame, 0, 0, 0, 0); + if (goto_low_power(cam)) + return -ENODEV; + } + + /* procedure described in developer's guide p3-28 */ + + /* Check the firmware version. */ + cam->params.version.firmwareVersion = 0; + get_version_information(cam); + if (cam->params.version.firmwareVersion != 1) + return -ENODEV; + + /* A bug in firmware 1-02 limits gainMode to 2 */ + if(cam->params.version.firmwareRevision <= 2 && + cam->params.exposure.gainMode > 2) { + cam->params.exposure.gainMode = 2; + } + + /* set QX3 detected flag */ + cam->params.qx3.qx3_detected = (cam->params.pnpID.vendor == 0x0813 && + cam->params.pnpID.product == 0x0001); + + /* 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 */ + err = goto_high_power(cam); + if (err) + return err; + + /* 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 */ + return set_camera_state(cam); +} + +static void put_cam(struct cpia_camera_ops* ops) +{ + if (ops->owner) + module_put(ops->owner); +} + +/* ------------------------- V4L interface --------------------- */ +static int cpia_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct cam_data *cam = dev->priv; + int err; + + if (!cam) { + DBG("Internal error, cam_data not found!\n"); + return -ENODEV; + } + + if (cam->open_count > 0) { + DBG("Camera already open\n"); + return -EBUSY; + } + + if (!try_module_get(cam->ops->owner)) + return -ENODEV; + + mutex_lock(&cam->busy_lock); + err = -ENOMEM; + if (!cam->raw_image) { + cam->raw_image = rvmalloc(CPIA_MAX_IMAGE_SIZE); + if (!cam->raw_image) + goto oops; + } + + if (!cam->decompressed_frame.data) { + cam->decompressed_frame.data = rvmalloc(CPIA_MAX_FRAME_SIZE); + if (!cam->decompressed_frame.data) + goto oops; + } + + /* open cpia */ + err = -ENODEV; + if (cam->ops->open(cam->lowlevel_data)) + goto oops; + + /* reset the camera */ + if ((err = reset_camera(cam)) != 0) { + cam->ops->close(cam->lowlevel_data); + goto oops; + } + + err = -EINTR; + if(signal_pending(current)) + goto oops; + + /* 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; + file->private_data = dev; + mutex_unlock(&cam->busy_lock); + return 0; + + oops: + if (cam->decompressed_frame.data) { + rvfree(cam->decompressed_frame.data, CPIA_MAX_FRAME_SIZE); + cam->decompressed_frame.data = NULL; + } + if (cam->raw_image) { + rvfree(cam->raw_image, CPIA_MAX_IMAGE_SIZE); + cam->raw_image = NULL; + } + mutex_unlock(&cam->busy_lock); + put_cam(cam->ops); + return err; +} + +static int cpia_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + struct cam_data *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 status */ + 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); + + put_cam(cam->ops); + } + + 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) + kfree(cam); + } + file->private_data = NULL; + + return 0; +} + +static ssize_t cpia_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *dev = file->private_data; + struct cam_data *cam = dev->priv; + int err; + + /* make this _really_ smp and multithread-safe */ + if (mutex_lock_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!buf) { + DBG("buf NULL\n"); + mutex_unlock(&cam->busy_lock); + return -EINVAL; + } + + if (!count) { + DBG("count 0\n"); + mutex_unlock(&cam->busy_lock); + return 0; + } + + if (!cam->ops) { + DBG("ops NULL\n"); + mutex_unlock(&cam->busy_lock); + return -ENODEV; + } + + /* upload frame */ + cam->decompressed_frame.state = FRAME_READY; + cam->mmap_kludge=0; + if((err = fetch_frame(cam)) != 0) { + DBG("ERROR from fetch_frame: %d\n", err); + mutex_unlock(&cam->busy_lock); + return err; + } + 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, + (unsigned long) count); + mutex_unlock(&cam->busy_lock); + return -EFAULT; + } + if (copy_to_user(buf, cam->decompressed_frame.data, + cam->decompressed_frame.count)) { + DBG("copy_to_user failed\n"); + mutex_unlock(&cam->busy_lock); + return -EFAULT; + } + + mutex_unlock(&cam->busy_lock); + return cam->decompressed_frame.count; +} + +static int cpia_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + struct cam_data *cam = dev->priv; + int retval = 0; + + if (!cam || !cam->ops) + return -ENODEV; + + /* make this _really_ smp-safe */ + if (mutex_lock_interruptible(&cam->busy_lock)) + return -EINTR; + + //DBG("cpia_ioctl: %u\n", ioctlnr); + + switch (ioctlnr) { + /* query capabilities */ + case VIDIOCGCAP: + { + struct video_capability *b = arg; + + DBG("VIDIOCGCAP\n"); + strcpy(b->name, "CPiA Camera"); + b->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = 352; /* VIDEOSIZE_CIF */ + b->maxheight = 288; + b->minwidth = 48; /* VIDEOSIZE_48_48 */ + b->minheight = 48; + break; + } + + /* get/set video source - we are a camera and nothing else */ + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + + DBG("VIDIOCGCHAN\n"); + 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; + break; + } + + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + + DBG("VIDIOCSCHAN\n"); + if (v->channel != 0) + retval = -EINVAL; + break; + } + + /* image properties */ + case VIDIOCGPICT: + { + struct video_picture *pic = arg; + DBG("VIDIOCGPICT\n"); + *pic = cam->vp; + break; + } + + case VIDIOCSPICT: + { + struct video_picture *vp = arg; + + DBG("VIDIOCSPICT\n"); + + /* check validity */ + DBG("palette: %d\n", vp->palette); + DBG("depth: %d\n", vp->depth); + if (!valid_mode(vp->palette, vp->depth)) { + retval = -EINVAL; + break; + } + + mutex_lock(&cam->param_lock); + /* brightness, colour, contrast need no check 0-65535 */ + cam->vp = *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; + } + + /* Adjust flicker control if necessary */ + if(cam->params.flickerControl.allowableOverExposure < 0) + cam->params.flickerControl.allowableOverExposure = + -find_over_exposure(cam->params.colourParams.brightness); + if(cam->params.flickerControl.flickerMode != 0) + cam->cmd_queue |= COMMAND_SETFLICKERCTRL; + + + /* queue command to update camera */ + cam->cmd_queue |= COMMAND_SETCOLOURPARAMS; + mutex_unlock(&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: + { + struct video_window *vw = arg; + DBG("VIDIOCGWIN\n"); + + *vw = cam->vw; + break; + } + + case VIDIOCSWIN: + { + /* copy_from_user, check validity, copy to internal structure */ + struct video_window *vw = arg; + DBG("VIDIOCSWIN\n"); + + 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??? + */ + mutex_lock(&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; + mutex_unlock(&cam->param_lock); + break; + } + cam->video_size = video_size; + + /* video size is changing, reset the subcapture area */ + memset(&cam->vc, 0, sizeof(cam->vc)); + + set_vw_size(cam); + DBG("%d / %d\n", cam->vw.width, cam->vw.height); + cam->cmd_queue |= COMMAND_SETFORMAT; + } + + mutex_unlock(&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 = arg; + 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; + break; + } + + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + int video_size; + + DBG("VIDIOCMCAPTURE: %d / %d / %dx%d\n", vm->format, vm->frame, + vm->width, vm->height); + 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: + cam->vp.depth=8; + break; + 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 (video_size < 0) { + retval = -EINVAL; + break; + } + if (video_size != cam->video_size) { + cam->video_size = video_size; + + /* video size is changing, reset the subcapture area */ + memset(&cam->vc, 0, sizeof(cam->vc)); + + set_vw_size(cam); + cam->cmd_queue |= COMMAND_SETFORMAT; + dispatch_commands(cam); + } + /* according to v4l-spec we must start streaming here */ + cam->mmap_kludge = 1; + retval = capture_frame(cam, vm); + + break; + } + + case VIDIOCSYNC: + { + int *frame = arg; + + //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; + } + + case VIDIOCGCAPTURE: + { + struct video_capture *vc = arg; + + DBG("VIDIOCGCAPTURE\n"); + + *vc = cam->vc; + + break; + } + + case VIDIOCSCAPTURE: + { + struct video_capture *vc = arg; + + DBG("VIDIOCSCAPTURE\n"); + + if (vc->decimation != 0) { /* How should this be used? */ + retval = -EINVAL; + break; + } + if (vc->flags != 0) { /* Even/odd grab not supported */ + retval = -EINVAL; + break; + } + + /* Clip to the resolution we can set for the ROI + (every 8 columns and 4 rows) */ + vc->x = vc->x & ~(__u32)7; + vc->y = vc->y & ~(__u32)3; + vc->width = vc->width & ~(__u32)7; + vc->height = vc->height & ~(__u32)3; + + if(vc->width == 0 || vc->height == 0 || + vc->x + vc->width > cam->vw.width || + vc->y + vc->height > cam->vw.height) { + retval = -EINVAL; + break; + } + + DBG("%d,%d/%dx%d\n", vc->x,vc->y,vc->width, vc->height); + + mutex_lock(&cam->param_lock); + + cam->vc.x = vc->x; + cam->vc.y = vc->y; + cam->vc.width = vc->width; + cam->vc.height = vc->height; + + set_vw_size(cam); + cam->cmd_queue |= COMMAND_SETFORMAT; + + mutex_unlock(&cam->param_lock); + + /* setformat ignored by camera during streaming, + * so stop/dispatch/start */ + dispatch_commands(cam); + break; + } + + case VIDIOCGUNIT: + { + struct video_unit *vu = arg; + + DBG("VIDIOCGUNIT\n"); + + vu->video = cam->vdev.minor; + vu->vbi = VIDEO_NO_UNIT; + vu->radio = VIDEO_NO_UNIT; + vu->audio = VIDEO_NO_UNIT; + vu->teletext = VIDEO_NO_UNIT; + + break; + } + + + /* pointless to implement overlay with this camera */ + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCKEY: + /* tuner interface - we have none */ + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + /* audio interface - we have none */ + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + retval = -EINVAL; + break; + default: + retval = -ENOIOCTLCMD; + break; + } + + mutex_unlock(&cam->busy_lock); + return retval; +} + +static int cpia_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, cpia_do_ioctl); +} + + +/* FIXME */ +static int cpia_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + 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 (mutex_lock_interruptible(&cam->busy_lock)) + return -EINTR; + + if (!cam->frame_buf) { /* we do lazy allocation */ + if ((retval = allocate_frame_buf(cam))) { + mutex_unlock(&cam->busy_lock); + return retval; + } + } + + pos = (unsigned long)(cam->frame_buf); + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + mutex_unlock(&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); + mutex_unlock(&cam->busy_lock); + + return 0; +} + +static struct file_operations cpia_fops = { + .owner = THIS_MODULE, + .open = cpia_open, + .release = cpia_close, + .read = cpia_read, + .mmap = cpia_mmap, + .ioctl = cpia_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device cpia_template = { + .owner = THIS_MODULE, + .name = "CPiA Camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_CPIA, + .fops = &cpia_fops, +}; + +/* 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 = 4; + 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 = COMP_RED; + cam->params.exposure.green1Comp = COMP_GREEN1; + cam->params.exposure.green2Comp = COMP_GREEN2; + cam->params.exposure.blueComp = COMP_BLUE; + cam->params.colourBalance.balanceMode = 2; /* ACB */ + cam->params.colourBalance.redGain = 32; + cam->params.colourBalance.greenGain = 6; + cam->params.colourBalance.blueGain = 92; + cam->params.apcor.gain1 = 0x18; + cam->params.apcor.gain2 = 0x16; + cam->params.apcor.gain4 = 0x24; + cam->params.apcor.gain8 = 0x34; + cam->params.flickerControl.flickerMode = 0; + cam->params.flickerControl.disabled = 1; + + cam->params.flickerControl.coarseJump = + flicker_jumps[cam->mainsFreq] + [cam->params.sensorFps.baserate] + [cam->params.sensorFps.divisor]; + cam->params.flickerControl.allowableOverExposure = + -find_over_exposure(cam->params.colourParams.brightness); + cam->params.vlOffset.gain1 = 20; + cam->params.vlOffset.gain2 = 24; + cam->params.vlOffset.gain4 = 26; + cam->params.vlOffset.gain8 = 26; + 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; + cam->exposure_status = EXPOSURE_NORMAL; + + /* 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 = 6; /* From windows driver */ + cam->params.yuvThreshold.uvThreshold = 6; /* From windows driver */ + + 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 = 15; /* From windows driver */ + cam->params.compressionTarget.targetQ = 5; /* From windows driver */ + + cam->params.qx3.qx3_detected = 0; + cam->params.qx3.toplight = 0; + cam->params.qx3.bottomlight = 0; + cam->params.qx3.button = 0; + cam->params.qx3.cradled = 0; + + 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 = 24; /* to be set by user */ + cam->vp.palette = VIDEO_PALETTE_RGB24; /* to be set by user */ + + cam->vc.x = 0; + cam->vc.y = 0; + cam->vc.width = 0; + cam->vc.height = 0; + + cam->vw.x = 0; + cam->vw.y = 0; + set_vw_size(cam); + cam->vw.chromakey = 0; + cam->vw.flags = 0; + cam->vw.clipcount = 0; + cam->vw.clips = NULL; + + cam->cmd_queue = COMMAND_NONE; + cam->first_frame = 1; + + 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; + mutex_init(&cam->param_lock); + mutex_init(&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; + + if ((camera = kmalloc(sizeof(struct cam_data), GFP_KERNEL)) == NULL) + return NULL; + + + init_camera_struct( camera, ops ); + camera->lowlevel_data = lowlevel; + + /* register v4l device */ + if (video_register_device(&camera->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + kfree(camera); + 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); + +#ifdef CONFIG_PROC_FS + create_proc_cpia_cam(camera); +#endif + + 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) +{ + DBG("unregistering video\n"); + video_unregister_device(&cam->vdev); + if (cam->open_count) { + put_cam(cam->ops); + 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); + } +} + +static int __init cpia_init(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n", ABOUT, + CPIA_MAJ_VER, CPIA_MIN_VER, CPIA_PATCH_VER); + + printk(KERN_WARNING "Since in-kernel colorspace conversion is not " + "allowed, it is disabled by default now. Users should fix the " + "applications in case they don't work without conversion " + "reenabled by setting the 'colorspace_conv' module " + "parameter to 1\n"); + +#ifdef CONFIG_PROC_FS + proc_cpia_create(); +#endif + +#ifdef CONFIG_VIDEO_CPIA_PP + cpia_pp_init(); +#endif +#ifdef CONFIG_VIDEO_CPIA_USB + cpia_usb_init(); +#endif + + return 0; +} + +static void __exit cpia_exit(void) +{ +#ifdef CONFIG_PROC_FS + proc_cpia_destroy(); +#endif +} + +module_init(cpia_init); +module_exit(cpia_exit); + +/* Exported symbols for modules. */ + +EXPORT_SYMBOL(cpia_register_camera); +EXPORT_SYMBOL(cpia_unregister_camera); diff --git a/linux/drivers/media/video/cpia.h b/linux/drivers/media/video/cpia.h new file mode 100644 index 000000000..120b66388 --- /dev/null +++ b/linux/drivers/media/video/cpia.h @@ -0,0 +1,432 @@ +#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 1 +#define CPIA_MIN_VER 2 +#define CPIA_PATCH_VER 3 + +#define CPIA_PP_MAJ_VER CPIA_MAJ_VER +#define CPIA_PP_MIN_VER CPIA_MIN_VER +#define CPIA_PP_PATCH_VER CPIA_PATCH_VER + +#define CPIA_USB_MAJ_VER CPIA_MAJ_VER +#define CPIA_USB_MIN_VER CPIA_MIN_VER +#define CPIA_USB_PATCH_VER CPIA_PATCH_VER + +#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 "compat.h" +#include <linux/videodev.h> +#include <linux/list.h> +#include <linux/smp_lock.h> +#include <linux/mutex.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 command. 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; + + /* + * Used to maintain lowlevel module usage counts + */ + struct module *owner; +}; + +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 balanceMode; + 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 disabled; + u8 flickerMode; + u8 coarseJump; + int 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 { /* Intel QX3 specific data */ + u8 qx3_detected; /* a QX3 is present */ + u8 toplight; /* top light lit , R/W */ + u8 bottomlight; /* bottom light lit, R/W */ + u8 button; /* snapshot button pressed (R/O) */ + u8 cradled; /* microscope is in cradle (R/O) */ + } qx3; + 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 list_head cam_data_list; + + struct mutex 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 mutex 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 */ + struct video_capture vc; /* v4l subcapture 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 */ + int exposure_status; /* EXPOSURE_* */ + int exposure_count; /* number of frames at this status */ +}; + +/* 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 + +/* default (minimum) compensation values */ +#define COMP_RED 220 +#define COMP_GREEN1 214 +#define COMP_GREEN2 COMP_GREEN1 +#define COMP_BLUE 230 + +/* exposure status */ +#define EXPOSURE_VERY_LIGHT 0 +#define EXPOSURE_LIGHT 1 +#define EXPOSURE_NORMAL 2 +#define EXPOSURE_DARK 3 +#define EXPOSURE_VERY_DARK 4 + +/* ErrorCode */ +#define ERROR_FLICKER_BELOW_MIN_EXP 0x01 /*flicker exposure got below minimum exposure */ +#define ALOG(fmt,args...) printk(fmt, ##args) +#define LOG(fmt,args...) ALOG(KERN_INFO __FILE__ ":%s(%d):" fmt, __FUNCTION__ , __LINE__ , ##args) + +#ifdef _CPIA_DEBUG_ +#define ADBG(fmt,args...) printk(fmt, jiffies, ##args) +#define DBG(fmt,args...) ADBG(KERN_DEBUG __FILE__" (%ld):%s(%d):" fmt, __FUNCTION__, __LINE__ , ##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); + +#endif /* __KERNEL__ */ + +#endif /* cpia_h */ diff --git a/linux/drivers/media/video/cpia_pp.c b/linux/drivers/media/video/cpia_pp.c new file mode 100644 index 000000000..0b00e6027 --- /dev/null +++ b/linux/drivers/media/video/cpia_pp.c @@ -0,0 +1,881 @@ +/* + * 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@securenym.net>, + * (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. + */ + +/* define _CPIA_DEBUG_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/config.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/workqueue.h> +#include <linux/smp_lock.h> +#include <linux/sched.h> + +#include <linux/kmod.h> + +/* #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" + +#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_LICENSE("GPL"); + +module_param_array(parport, charp, NULL, 0); +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 work_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, + THIS_MODULE +}; + +static LIST_HEAD(cam_list); +static spinlock_t cam_list_lock_pp; + +/* 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; +} + +/* Special CPiA PPC modes: These are invoked by using the 1284 Extensibility + * Link Flag during negotiation */ +#define UPLOAD_FLAG 0x08 +#define NIBBLE_TRANSFER 0x01 +#define ECP_TRANSFER 0x03 + +#define PARPORT_CHUNK_SIZE PAGE_SIZE + + +/**************************************************************************** + * + * CPiA-specific low-level parport functions for nibble uploads + * + ***************************************************************************/ +/* CPiA nonstandard "Nibble" mode (no nDataAvail signal after each byte). */ +/* The standard kernel parport_ieee1284_read_nibble() fails with the CPiA... */ + +static size_t cpia_read_nibble (struct parport *port, + void *buffer, size_t len, + int flags) +{ + /* adapted verbatim, with one change, from + parport_ieee1284_read_nibble() in drivers/parport/ieee1284-ops.c */ + + unsigned char *buf = buffer; + int i; + unsigned char byte = 0; + + len *= 2; /* in nibbles */ + for (i=0; i < len; i++) { + unsigned char nibble; + + /* The CPiA firmware suppresses the use of nDataAvail (nFault LO) + * after every second nibble to signal that more + * data is available. (the total number of Bytes that + * should be sent is known; if too few are received, an error + * will be recorded after a timeout). + * This is incompatible with parport_ieee1284_read_nibble(), + * which expects to find nFault LO after every second nibble. + */ + + /* Solution: modify cpia_read_nibble to only check for + * nDataAvail before the first nibble is sent. + */ + + /* Does the error line indicate end of data? */ + if (((i /*& 1*/) == 0) && + (parport_read_status(port) & PARPORT_STATUS_ERROR)) { + DBG("%s: No more nibble data (%d bytes)\n", + port->name, i/2); + goto end_of_data; + } + + /* Event 7: Set nAutoFd low. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + + /* Event 9: nAck goes low. */ + port->ieee1284.phase = IEEE1284_PH_REV_DATA; + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, 0)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 9 (%d bytes)\n", + port->name, i/2); + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + break; + } + + + /* Read a nibble. */ + nibble = parport_read_status (port) >> 3; + nibble &= ~8; + if ((nibble & 0x10) == 0) + nibble |= 8; + nibble &= 0xf; + + /* Event 10: Set nAutoFd high. */ + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + + /* Event 11: nAck goes high. */ + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, + PARPORT_STATUS_ACK)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 11\n", + port->name); + break; + } + + if (i & 1) { + /* Second nibble */ + byte |= nibble << 4; + *buf++ = byte; + } else + byte = nibble; + } + + if (i == len) { + /* Read the last nibble without checking data avail. */ + if (parport_read_status (port) & PARPORT_STATUS_ERROR) { + end_of_data: + /* Go to reverse idle phase. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + port->physport->ieee1284.phase = IEEE1284_PH_REV_IDLE; + } + else + port->physport->ieee1284.phase = IEEE1284_PH_HBUSY_DAVAIL; + } + + return i/2; +} + +/* CPiA nonstandard "Nibble Stream" mode (2 nibbles per cycle, instead of 1) + * (See CPiA Data sheet p. 31) + * + * "Nibble Stream" mode used by CPiA for uploads to non-ECP ports is a + * nonstandard variant of nibble mode which allows the same (mediocre) + * data flow of 8 bits per cycle as software-enabled ECP by TRISTATE-capable + * parallel ports, but works also for non-TRISTATE-capable ports. + * (Standard nibble mode only send 4 bits per cycle) + * + */ + +static size_t cpia_read_nibble_stream(struct parport *port, + void *buffer, size_t len, + int flags) +{ + int i; + unsigned char *buf = buffer; + int endseen = 0; + + for (i=0; i < len; i++) { + unsigned char nibble[2], byte = 0; + int j; + + /* Image Data is complete when 4 consecutive EOI bytes (0xff) are seen */ + if (endseen > 3 ) + break; + + /* Event 7: Set nAutoFd low. */ + parport_frob_control (port, + PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + + /* Event 9: nAck goes low. */ + port->ieee1284.phase = IEEE1284_PH_REV_DATA; + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, 0)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 9 (%d bytes)\n", + port->name, i/2); + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + break; + } + + /* Read lower nibble */ + nibble[0] = parport_read_status (port) >>3; + + /* Event 10: Set nAutoFd high. */ + parport_frob_control (port, PARPORT_CONTROL_AUTOFD, 0); + + /* Event 11: nAck goes high. */ + if (parport_wait_peripheral (port, + PARPORT_STATUS_ACK, + PARPORT_STATUS_ACK)) { + /* Timeout -- no more data? */ + DBG("%s: Nibble timeout at event 11\n", + port->name); + break; + } + + /* Read upper nibble */ + nibble[1] = parport_read_status (port) >>3; + + /* reassemble the byte */ + for (j = 0; j < 2 ; j++ ) { + nibble[j] &= ~8; + if ((nibble[j] & 0x10) == 0) + nibble[j] |= 8; + nibble[j] &= 0xf; + } + byte = (nibble[0] |(nibble[1] << 4)); + *buf++ = byte; + + if(byte == EOI) + endseen++; + else + endseen = 0; + } + return i; +} + +/**************************************************************************** + * + * 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; + + /* The CPiA uses ECP protocol for Downloads from the Host to the camera. + * This will be software-emulated if ECP hardware is not present + */ + + /* the usual camera maximum response time is 10ms, but after receiving + * some commands, it needs up to 40ms. (Data Sheet p. 32)*/ + + for(retry = 0; retry < 4; ++retry) { + if(!parport_negotiate(cam->port, IEEE1284_MODE_ECP)) { + break; + } + mdelay(10); + } + if(retry == 4) { + DBG("Unable to negotiate IEEE1284 ECP Download mode\n"); + return -1; + } + return 0; +} +/**************************************************************************** + * + * ReverseSetup + * + ***************************************************************************/ +static int ReverseSetup(struct pp_cam_entry *cam, int extensibility) +{ + int retry; + int upload_mode, mode = IEEE1284_MODE_ECP; + int transfer_mode = ECP_TRANSFER; + + if (!(cam->port->modes & PARPORT_MODE_ECP) && + !(cam->port->modes & PARPORT_MODE_TRISTATE)) { + mode = IEEE1284_MODE_NIBBLE; + transfer_mode = NIBBLE_TRANSFER; + } + + upload_mode = mode; + if(extensibility) mode = UPLOAD_FLAG|transfer_mode|IEEE1284_EXT_LINK; + + /* the usual camera maximum response time is 10ms, but after + * receiving some commands, it needs up to 40ms. */ + + for(retry = 0; retry < 4; ++retry) { + if(!parport_negotiate(cam->port, mode)) { + break; + } + mdelay(10); + } + if(retry == 4) { + if(extensibility) + DBG("Unable to negotiate upload extensibility mode\n"); + else + DBG("Unable to negotiate upload mode\n"); + return -1; + } + if(extensibility) cam->port->ieee1284.mode = upload_mode; + 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; + } + + /* support for CPiA variant nibble reads */ + if(cam->port->ieee1284.mode == IEEE1284_MODE_NIBBLE) { + if(cpia_read_nibble(cam->port, packet, size, 0) != size) + retval = -EIO; + } else { + 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; +} + +/**************************************************************************** + * + * cpia_pp_streamRead + * + ***************************************************************************/ +static int cpia_pp_read(struct parport *port, u8 *buffer, int len) +{ + int bytes_read; + + /* support for CPiA variant "nibble stream" reads */ + if(port->ieee1284.mode == IEEE1284_MODE_NIBBLE) + bytes_read = cpia_read_nibble_stream(port,buffer,len,0); + else { + int 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; +} + +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 ) { + cond_resched(); + + 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) { + DBG("Error reading command result\n"); + return err; + } + 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; + + 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) { + INIT_WORK(&cam->cb_task, cb, cbdata); + } else { + retval = -1; + } + return retval; +} + +/**************************************************************************** + * + * cpia_pp_close + * + ***************************************************************************/ +static int cpia_pp_close(void *privdata) +{ + struct pp_cam_entry *cam = privdata; + 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_PCSPP)) { + LOG("port is not supported by CPiA driver\n"); + return -ENXIO; + } + + cam = kzalloc(sizeof(struct pp_cam_entry), GFP_KERNEL); + if (cam == NULL) { + LOG("failed to allocate camera structure\n"); + return -ENOMEM; + } + + 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; + } + spin_lock( &cam_list_lock_pp ); + list_add( &cpia->cam_data_list, &cam_list ); + spin_unlock( &cam_list_lock_pp ); + + return 0; +} + +static void cpia_pp_detach (struct parport *port) +{ + struct list_head *tmp; + struct cam_data *cpia = NULL; + struct pp_cam_entry *cam; + + spin_lock( &cam_list_lock_pp ); + list_for_each (tmp, &cam_list) { + cpia = list_entry(tmp, struct cam_data, cam_data_list); + cam = (struct pp_cam_entry *) cpia->lowlevel_data; + if (cam && cam->port->number == port->number) { + list_del(&cpia->cam_data_list); + break; + } + cpia = NULL; + } + spin_unlock( &cam_list_lock_pp ); + + if (!cpia) { + DBG("cpia_pp_detach failed to find cam_data in cam_list\n"); + return; + } + + cam = (struct pp_cam_entry *) cpia->lowlevel_data; + cpia_unregister_camera(cpia); + if(cam->open_count > 0) + cpia_pp_close(cam); + parport_unregister_device(cam->pdev); + cpia->lowlevel_data = NULL; + kfree(cam); +} + +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 = { + .name = "cpia_pp", + .attach = cpia_pp_attach, + .detach = cpia_pp_detach, +}; + +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; + } + + spin_lock_init( &cam_list_lock_pp ); + + 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; + } + } + } + } + } + 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 (!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 1; +} + +__setup("cpia_pp=", cpia_pp_setup); + +#endif /* !MODULE */ diff --git a/linux/drivers/media/video/cpia_usb.c b/linux/drivers/media/video/cpia_usb.c new file mode 100644 index 000000000..9c49a4b00 --- /dev/null +++ b/linux/drivers/media/video/cpia_usb.c @@ -0,0 +1,649 @@ +/* + * 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 <johannes@erdfelt.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_ for verbose debug output (see cpia.h) */ +/* #define _CPIA_DEBUG_ 1 */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/wait.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; + struct urb *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, + THIS_MODULE +}; + +static LIST_HEAD(cam_list); +static spinlock_t cam_list_lock_usb; + +static void cpia_usb_complete(struct urb *urb, struct pt_regs *regs) +{ + 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); + } + } + } + + /* resubmit */ + urb->dev = ucpia->dev; + if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0) + printk(KERN_ERR "%s: usb_submit_urb ret %d\n", __FUNCTION__, i); +} + +static int cpia_usb_open(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + struct urb *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_1; + } + + 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, and also know the polling + * interval is every frame (1 == (1 << (bInterval -1))). + */ + urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 0\n"); + retval = -ENOMEM; + goto error_1; + } + + ucpia->sbuf[0].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[0].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->interval = 1; + 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, GFP_KERNEL); + if (!urb) { + printk(KERN_ERR "cpia_init_isoc: usb_alloc_urb 1\n"); + retval = -ENOMEM; + goto error_urb0; + } + + ucpia->sbuf[1].urb = urb; + urb->dev = ucpia->dev; + urb->context = ucpia; + urb->pipe = usb_rcvisocpipe(ucpia->dev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ucpia->sbuf[1].data; + urb->complete = cpia_usb_complete; + urb->number_of_packets = FRAMES_PER_DESC; + urb->interval = 1; + 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; + } + + /* queue the ISO urbs, and resubmit in the completion handler */ + err = usb_submit_urb(ucpia->sbuf[0].urb, GFP_KERNEL); + if (err) { + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 0 ret %d\n", + err); + goto error_urb1; + } + err = usb_submit_urb(ucpia->sbuf[1].urb, GFP_KERNEL); + if (err) { + printk(KERN_ERR "cpia_init_isoc: usb_submit_urb 1 ret %d\n", + err); + goto error_urb1; + } + + ucpia->streaming = 1; + ucpia->open = 1; + + return 0; + +error_urb1: /* free urb 1 */ + usb_free_urb(ucpia->sbuf[1].urb); + ucpia->sbuf[1].urb = NULL; +error_urb0: /* free urb 0 */ + usb_free_urb(ucpia->sbuf[0].urb); + ucpia->sbuf[0].urb = NULL; +error_1: + kfree (ucpia->sbuf[1].data); + ucpia->sbuf[1].data = NULL; +error_0: + kfree (ucpia->sbuf[0].data); + ucpia->sbuf[0].data = NULL; + + 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, 1000); +} + +/**************************************************************************** + * + * 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, 1000); +} + +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_kill_urb(ucpia->sbuf[1].urb); + usb_free_urb(ucpia->sbuf[1].urb); + ucpia->sbuf[1].urb = NULL; + } + + kfree(ucpia->sbuf[1].data); + ucpia->sbuf[1].data = NULL; + + if (ucpia->sbuf[0].urb) { + usb_kill_urb(ucpia->sbuf[0].urb); + usb_free_urb(ucpia->sbuf[0].urb); + ucpia->sbuf[0].urb = NULL; + } + + kfree(ucpia->sbuf[0].data); + ucpia->sbuf[0].data = NULL; +} + +static int cpia_usb_close(void *privdata) +{ + struct usb_cpia *ucpia = (struct usb_cpia *) privdata; + + if(!ucpia) + return -ENODEV; + + ucpia->open = 0; + + /* ucpia->present = 0 protects against trying to reset the + * alt setting if camera is physically disconnected while open */ + cpia_usb_free_resources(ucpia, ucpia->present); + + return 0; +} + +int cpia_usb_init(void) +{ + /* return -ENODEV; */ + return 0; +} + +/* Probing and initializing */ + +static int cpia_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_cpia *ucpia; + struct cam_data *cam; + int ret; + + /* A multi-config CPiA camera? */ + if (udev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + interface = intf->cur_altsetting; + + printk(KERN_INFO "USB CPiA camera found\n"); + + ucpia = kzalloc(sizeof(*ucpia), GFP_KERNEL); + if (!ucpia) { + printk(KERN_ERR "couldn't kmalloc cpia struct\n"); + return -ENOMEM; + } + + ucpia->dev = udev; + ucpia->iface = interface->desc.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; + } + + spin_lock( &cam_list_lock_usb ); + list_add( &cam->cam_data_list, &cam_list ); + spin_unlock( &cam_list_lock_usb ); + + usb_set_intfdata(intf, cam); + return 0; + +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: + kfree(ucpia); + return -EIO; +} + +static void cpia_disconnect(struct usb_interface *intf); + +static struct usb_device_id cpia_id_table [] = { + { USB_DEVICE(0x0553, 0x0002) }, + { USB_DEVICE(0x0813, 0x0001) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, cpia_id_table); +MODULE_LICENSE("GPL"); + + +static struct usb_driver cpia_driver = { + .name = "cpia", + .probe = cpia_probe, + .disconnect = cpia_disconnect, + .id_table = cpia_id_table, +}; + +static void cpia_disconnect(struct usb_interface *intf) +{ + struct cam_data *cam = usb_get_intfdata(intf); + struct usb_cpia *ucpia; + struct usb_device *udev; + + usb_set_intfdata(intf, NULL); + if (!cam) + return; + + ucpia = (struct usb_cpia *) cam->lowlevel_data; + spin_lock( &cam_list_lock_usb ); + list_del(&cam->cam_data_list); + spin_unlock( &cam_list_lock_usb ); + + ucpia->present = 0; + + cpia_unregister_camera(cam); + if(ucpia->open) + cpia_usb_close(cam->lowlevel_data); + + ucpia->curbuff->status = FRAME_ERROR; + + if (waitqueue_active(&ucpia->wq_stream)) + wake_up_interruptible(&ucpia->wq_stream); + + udev = interface_to_usbdev(intf); + + ucpia->curbuff = ucpia->workbuff = NULL; + + vfree(ucpia->buffers[2]); + ucpia->buffers[2] = NULL; + + vfree(ucpia->buffers[1]); + ucpia->buffers[1] = NULL; + + vfree(ucpia->buffers[0]); + ucpia->buffers[0] = NULL; + + cam->lowlevel_data = NULL; + kfree(ucpia); +} + +static int __init usb_cpia_init(void) +{ + printk(KERN_INFO "%s v%d.%d.%d\n",ABOUT, + CPIA_USB_MAJ_VER,CPIA_USB_MIN_VER,CPIA_USB_PATCH_VER); + + spin_lock_init(&cam_list_lock_usb); + return usb_register(&cpia_driver); +} + +static void __exit usb_cpia_cleanup(void) +{ + usb_deregister(&cpia_driver); +} + + +module_init (usb_cpia_init); +module_exit (usb_cpia_cleanup); + diff --git a/linux/drivers/media/video/cs8420.h b/linux/drivers/media/video/cs8420.h new file mode 100644 index 000000000..621c0c667 --- /dev/null +++ b/linux/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/linux/drivers/media/video/dabfirmware.h b/linux/drivers/media/video/dabfirmware.h new file mode 100644 index 000000000..d14d80356 --- /dev/null +++ b/linux/drivers/media/video/dabfirmware.h @@ -0,0 +1,1408 @@ +/* + * dabdata.h - dab usb firmware and bitstream data + */ + +static INTEL_HEX_RECORD firmware[] = { + +{ 2, 0x0000, 0, {0x21,0x57} }, +{ 3, 0x0003, 0, {0x02,0x01,0x66} }, +{ 3, 0x000b, 0, {0x02,0x01,0x66} }, +{ 3, 0x0013, 0, {0x02,0x01,0x66} }, +{ 3, 0x001b, 0, {0x02,0x01,0x66} }, +{ 3, 0x0023, 0, {0x02,0x01,0x66} }, +{ 3, 0x002b, 0, {0x02,0x01,0x66} }, +{ 3, 0x0033, 0, {0x02,0x03,0x0f} }, +{ 3, 0x003b, 0, {0x02,0x01,0x66} }, +{ 3, 0x0043, 0, {0x02,0x01,0x00} }, +{ 3, 0x004b, 0, {0x02,0x01,0x66} }, +{ 3, 0x0053, 0, {0x02,0x01,0x66} }, +{ 3, 0x005b, 0, {0x02,0x04,0xbd} }, +{ 3, 0x0063, 0, {0x02,0x01,0x67} }, +{ 3, 0x0100, 0, {0x02,0x0c,0x5a} }, +{ 3, 0x0104, 0, {0x02,0x01,0xed} }, +{ 3, 0x0108, 0, {0x02,0x02,0x51} }, +{ 3, 0x010c, 0, {0x02,0x02,0x7c} }, +{ 3, 0x0110, 0, {0x02,0x02,0xe4} }, +{ 1, 0x0114, 0, {0x32} }, +{ 1, 0x0118, 0, {0x32} }, +{ 3, 0x011c, 0, {0x02,0x05,0xfd} }, +{ 3, 0x0120, 0, {0x02,0x00,0x00} }, +{ 3, 0x0124, 0, {0x02,0x00,0x00} }, +{ 3, 0x0128, 0, {0x02,0x04,0x3c} }, +{ 3, 0x012c, 0, {0x02,0x04,0x6a} }, +{ 3, 0x0130, 0, {0x02,0x00,0x00} }, +{ 3, 0x0134, 0, {0x02,0x00,0x00} }, +{ 3, 0x0138, 0, {0x02,0x00,0x00} }, +{ 3, 0x013c, 0, {0x02,0x00,0x00} }, +{ 3, 0x0140, 0, {0x02,0x00,0x00} }, +{ 3, 0x0144, 0, {0x02,0x00,0x00} }, +{ 3, 0x0148, 0, {0x02,0x00,0x00} }, +{ 3, 0x014c, 0, {0x02,0x00,0x00} }, +{ 3, 0x0150, 0, {0x02,0x00,0x00} }, +{ 3, 0x0154, 0, {0x02,0x00,0x00} }, +{ 10, 0x0157, 0, {0x75,0x81,0x7f,0xe5,0x82,0x60,0x03,0x02,0x01,0x61} }, +{ 5, 0x0161, 0, {0x12,0x07,0x6f,0x21,0x64} }, +{ 1, 0x0166, 0, {0x32} }, +{ 14, 0x0167, 0, {0xc0,0xd0,0xc0,0x86,0xc0,0x82,0xc0,0x83,0xc0,0xe0,0x90,0x7f,0x97,0xe0} }, +{ 14, 0x0175, 0, {0x44,0x80,0xf0,0x90,0x7f,0x69,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0} }, +{ 14, 0x0183, 0, {0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0} }, +{ 14, 0x0191, 0, {0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0x90,0x7f,0x97,0xe0} }, +{ 3, 0x019f, 0, {0x55,0x7f,0xf0} }, +{ 14, 0x01a2, 0, {0x90,0x7f,0x9a,0xe0,0x30,0xe4,0x23,0x90,0x7f,0x68,0xf0,0xf0,0xf0,0xf0} }, +{ 14, 0x01b0, 0, {0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0} }, +{ 14, 0x01be, 0, {0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0} }, +{ 14, 0x01cc, 0, {0xe5,0xd8,0xc2,0xe3,0xf5,0xd8,0xd0,0xe0,0xd0,0x83,0xd0,0x82,0xd0,0x86} }, +{ 3, 0x01da, 0, {0xd0,0xd0,0x32} }, +{ 8, 0x01dd, 0, {0x75,0x86,0x00,0x90,0xff,0xc3,0x7c,0x05} }, +{ 7, 0x01e5, 0, {0xa3,0xe5,0x82,0x45,0x83,0x70,0xf9} }, +{ 1, 0x01ec, 0, {0x22} }, +{ 14, 0x01ed, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0x02,0xc0,0x03,0xc0,0xd0} }, +{ 14, 0x01fb, 0, {0x75,0xd0,0x00,0xc0,0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91} }, +{ 13, 0x0209, 0, {0x90,0x88,0x00,0xe0,0xf5,0x41,0x90,0x7f,0xab,0x74,0x02,0xf0,0x90} }, +{ 9, 0x0216, 0, {0x7f,0xab,0x74,0x02,0xf0,0xe5,0x32,0x60,0x21} }, +{ 4, 0x021f, 0, {0x7a,0x00,0x7b,0x00} }, +{ 11, 0x0223, 0, {0xc3,0xea,0x94,0x18,0xeb,0x64,0x80,0x94,0x80,0x50,0x12} }, +{ 14, 0x022e, 0, {0x90,0x7f,0x69,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0x0a,0xba,0x00} }, +{ 2, 0x023c, 0, {0x01,0x0b} }, +{ 2, 0x023e, 0, {0x80,0xe3} }, +{ 2, 0x0240, 0, {0xd0,0x86} }, +{ 14, 0x0242, 0, {0xd0,0xd0,0xd0,0x03,0xd0,0x02,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0} }, +{ 1, 0x0250, 0, {0x32} }, +{ 14, 0x0251, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 14, 0x025f, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90,0x7f,0xab,0x74} }, +{ 4, 0x026d, 0, {0x04,0xf0,0xd0,0x86} }, +{ 11, 0x0271, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x027c, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0x02,0xc0,0x03,0xc0,0x04} }, +{ 14, 0x028a, 0, {0xc0,0x05,0xc0,0x06,0xc0,0x07,0xc0,0x00,0xc0,0x01,0xc0,0xd0,0x75,0xd0} }, +{ 13, 0x0298, 0, {0x00,0xc0,0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90} }, +{ 12, 0x02a5, 0, {0x7f,0xab,0x74,0x08,0xf0,0x75,0x6e,0x00,0x75,0x6f,0x02,0x12} }, +{ 6, 0x02b1, 0, {0x11,0x44,0x75,0x70,0x39,0x75} }, +{ 6, 0x02b7, 0, {0x71,0x0c,0x75,0x72,0x02,0x12} }, +{ 12, 0x02bd, 0, {0x11,0x75,0x90,0x7f,0xd6,0xe4,0xf0,0x75,0xd8,0x20,0xd0,0x86} }, +{ 14, 0x02c9, 0, {0xd0,0xd0,0xd0,0x01,0xd0,0x00,0xd0,0x07,0xd0,0x06,0xd0,0x05,0xd0,0x04} }, +{ 13, 0x02d7, 0, {0xd0,0x03,0xd0,0x02,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x02e4, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 14, 0x02f2, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90,0x7f,0xab,0x74} }, +{ 4, 0x0300, 0, {0x10,0xf0,0xd0,0x86} }, +{ 11, 0x0304, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x030f, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0x02,0xc0,0x03,0xc0,0x04} }, +{ 14, 0x031d, 0, {0xc0,0x05,0xc0,0x06,0xc0,0x07,0xc0,0x00,0xc0,0x01,0xc0,0xd0,0x75,0xd0} }, +{ 12, 0x032b, 0, {0x00,0xc0,0x86,0x75,0x86,0x00,0x75,0x6e,0x00,0x75,0x6f,0x02} }, +{ 7, 0x0337, 0, {0x12,0x11,0x44,0x75,0x70,0x40,0x75} }, +{ 6, 0x033e, 0, {0x71,0x0c,0x75,0x72,0x02,0x12} }, +{ 14, 0x0344, 0, {0x11,0x75,0x90,0x7f,0xd6,0x74,0x02,0xf0,0x90,0x7f,0xd6,0x74,0x06,0xf0} }, +{ 5, 0x0352, 0, {0x75,0xd8,0x10,0xd0,0x86} }, +{ 14, 0x0357, 0, {0xd0,0xd0,0xd0,0x01,0xd0,0x00,0xd0,0x07,0xd0,0x06,0xd0,0x05,0xd0,0x04} }, +{ 13, 0x0365, 0, {0xd0,0x03,0xd0,0x02,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 13, 0x0372, 0, {0x90,0x7f,0xa5,0x74,0x80,0xf0,0x90,0x7f,0xa6,0x74,0x9a,0xf0,0x12} }, +{ 12, 0x037f, 0, {0x10,0x1b,0x90,0x7f,0xa6,0xe5,0x42,0xf0,0x12,0x10,0x1b,0x90} }, +{ 13, 0x038b, 0, {0x7f,0xa6,0xe5,0x43,0xf0,0x12,0x10,0x1b,0x90,0x7f,0xa5,0x74,0x40} }, +{ 1, 0x0398, 0, {0xf0} }, +{ 1, 0x0399, 0, {0x22} }, +{ 13, 0x039a, 0, {0x90,0x7f,0xa5,0x74,0x80,0xf0,0x90,0x7f,0xa6,0x74,0x9a,0xf0,0x12} }, +{ 12, 0x03a7, 0, {0x10,0x1b,0x90,0x7f,0xa6,0xe5,0x44,0xf0,0x12,0x10,0x1b,0x90} }, +{ 12, 0x03b3, 0, {0x7f,0xa6,0xe5,0x45,0xf0,0x12,0x10,0x1b,0x90,0x7f,0xa6,0xe5} }, +{ 11, 0x03bf, 0, {0x46,0xf0,0x12,0x10,0x1b,0x90,0x7f,0xa5,0x74,0x40,0xf0} }, +{ 1, 0x03ca, 0, {0x22} }, +{ 10, 0x03cb, 0, {0x75,0x44,0x02,0x75,0x45,0x00,0x75,0x46,0x00,0x12} }, +{ 9, 0x03d5, 0, {0x03,0x9a,0x75,0x42,0x03,0x75,0x43,0x00,0x12} }, +{ 2, 0x03de, 0, {0x03,0x72} }, +{ 1, 0x03e0, 0, {0x22} }, +{ 12, 0x03e1, 0, {0x90,0x88,0x00,0xe5,0x36,0xf0,0x90,0x88,0x00,0x74,0x10,0x25} }, +{ 9, 0x03ed, 0, {0x36,0xf0,0x12,0x01,0xdd,0x75,0x42,0x01,0x75} }, +{ 9, 0x03f6, 0, {0x43,0x18,0x12,0x03,0x72,0x75,0x44,0x02,0x75} }, +{ 9, 0x03ff, 0,{0x45,0x00,0x75,0x46,0x00,0x12,0x03,0x9a,0x75} }, +{ 8, 0x0408, 0,{0x42,0x03,0x75,0x43,0x44,0x12,0x03,0x72} }, +{ 1, 0x0410, 0,{0x22} }, +{ 14, 0x0411, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 14, 0x041f, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90,0x7f,0xaa,0x74} }, +{ 4, 0x042d, 0, {0x02,0xf0,0xd0,0x86} }, +{ 11, 0x0431, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x043c, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 14, 0x044a, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90,0x7f,0xa9,0x74} }, +{ 7, 0x0458, 0, {0x04,0xf0,0x75,0x30,0x01,0xd0,0x86} }, +{ 11, 0x045f, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x046a, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 14, 0x0478, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90,0x7f,0xaa,0x74} }, +{ 7, 0x0486, 0, {0x04,0xf0,0x75,0x31,0x01,0xd0,0x86} }, +{ 11, 0x048d, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x0498, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 12, 0x04a6, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe5,0xf5,0x91,0xd0,0x86} }, +{ 11, 0x04b2, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x04bd, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0xd0,0x75,0xd0,0x00,0xc0} }, +{ 12, 0x04cb, 0, {0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe7,0xf5,0x91,0xd0,0x86} }, +{ 11, 0x04d7, 0, {0xd0,0xd0,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 12, 0x04e2, 0, {0x90,0x7f,0xea,0xe0,0xfa,0x8a,0x20,0x90,0x7f,0x96,0xe4,0xf0} }, +{ 1, 0x04ee, 0, {0x22} }, +{ 7, 0x04ef, 0, {0x90,0x7f,0xea,0xe0,0xfa,0x8a,0x21} }, +{ 1, 0x04f6, 0, {0x22} }, +{ 14, 0x04f7, 0, {0x90,0x17,0x13,0xe0,0xfa,0x90,0x17,0x15,0xe0,0xfb,0x74,0x80,0x2a,0xfa} }, +{ 14, 0x0505, 0, {0x74,0x80,0x2b,0xfb,0xea,0x03,0x03,0x54,0x3f,0xfc,0xea,0xc4,0x23,0x54} }, +{ 14, 0x0513, 0, {0x1f,0xfa,0x2c,0xfa,0xeb,0x03,0x03,0x54,0x3f,0xfc,0xeb,0xc4,0x23,0x54} }, +{ 11, 0x0521, 0, {0x1f,0xfb,0x2c,0xfb,0x90,0x17,0x0a,0xe0,0xfc,0x60,0x02} }, +{ 2, 0x052c, 0, {0x7a,0x00} }, +{ 7, 0x052e, 0, {0x90,0x17,0x0c,0xe0,0xfc,0x60,0x02} }, +{ 2, 0x0535, 0, {0x7b,0x00} }, +{ 11, 0x0537, 0, {0xea,0x2b,0xfc,0xc3,0x13,0xf5,0x3a,0x75,0x44,0x02,0x8b} }, +{ 7, 0x0542, 0, {0x45,0x8a,0x46,0x12,0x03,0x9a,0x75} }, +{ 9, 0x0549, 0, {0x6e,0x08,0x75,0x6f,0x00,0x12,0x11,0x44,0x75} }, +{ 4, 0x0552, 0, {0x70,0x47,0x75,0x71} }, +{ 8, 0x0556, 0, {0x0c,0x75,0x72,0x02,0x12,0x11,0x75,0x85} }, +{ 5, 0x055e, 0, {0x3a,0x73,0x12,0x11,0xa0} }, +{ 1, 0x0563, 0, {0x22} }, +{ 14, 0x0564, 0, {0x90,0x7f,0x96,0xe0,0xfa,0x90,0x7f,0x96,0x74,0x80,0x65,0x02,0xf0,0x90} }, +{ 14, 0x0572, 0, {0x7f,0xeb,0xe0,0xfa,0x90,0x7f,0xea,0xe0,0xfb,0x90,0x7f,0xef,0xe0,0xfc} }, +{ 14, 0x0580, 0, {0x33,0x95,0xe0,0xfd,0x8c,0x05,0x7c,0x00,0x90,0x7f,0xee,0xe0,0xfe,0x33} }, +{ 14, 0x058e, 0, {0x95,0xe0,0xff,0xec,0x2e,0xfc,0xed,0x3f,0xfd,0x90,0x7f,0xe9,0xe0,0xfe} }, +{ 5, 0x059c, 0, {0xbe,0x01,0x02,0x80,0x03} }, +{ 3, 0x05a1, 0, {0x02,0x05,0xf9} }, +{ 6, 0x05a4, 0, {0xbc,0x01,0x21,0xbd,0x00,0x1e} }, +{ 14, 0x05aa, 0, {0xea,0xc4,0x03,0x54,0xf8,0xfc,0xeb,0x25,0xe0,0xfd,0x2c,0x24,0x00,0xfc} }, +{ 14, 0x05b8, 0, {0xe4,0x34,0x17,0xfd,0x90,0x7e,0xc0,0xe0,0xfe,0x8c,0x82,0x8d,0x83,0xf0} }, +{ 2, 0x05c6, 0, {0x80,0x31} }, +{ 14, 0x05c8, 0, {0xea,0xc4,0x03,0x54,0xf8,0xfa,0xeb,0x25,0xe0,0xfb,0x2a,0xfa,0x24,0x00} }, +{ 14, 0x05d6, 0, {0xfb,0xe4,0x34,0x17,0xfc,0x90,0x7e,0xc0,0xe0,0xfd,0x8b,0x82,0x8c,0x83} }, +{ 14, 0x05e4, 0, {0xf0,0x74,0x01,0x2a,0x24,0x00,0xfa,0xe4,0x34,0x17,0xfb,0x90,0x7e,0xc1} }, +{ 7, 0x05f2, 0, {0xe0,0xfc,0x8a,0x82,0x8b,0x83,0xf0} }, +{ 3, 0x05f9, 0, {0x75,0x38,0x01} }, +{ 1, 0x05fc, 0, {0x22} }, +{ 14, 0x05fd, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0x02,0xc0,0x03,0xc0,0x04} }, +{ 14, 0x060b, 0, {0xc0,0x05,0xc0,0x06,0xc0,0x07,0xc0,0x00,0xc0,0x01,0xc0,0xd0,0x75,0xd0} }, +{ 13, 0x0619, 0, {0x00,0xc0,0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90} }, +{ 13, 0x0626, 0, {0x7f,0xaa,0x74,0x01,0xf0,0x12,0x05,0x64,0x75,0x37,0x00,0xd0,0x86} }, +{ 14, 0x0633, 0, {0xd0,0xd0,0xd0,0x01,0xd0,0x00,0xd0,0x07,0xd0,0x06,0xd0,0x05,0xd0,0x04} }, +{ 13, 0x0641, 0, {0xd0,0x03,0xd0,0x02,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 14, 0x064e, 0, {0x90,0x7f,0xeb,0xe0,0xfa,0x90,0x7f,0xea,0xe0,0xfb,0x90,0x7f,0xee,0xe0} }, +{ 14, 0x065c, 0, {0xfc,0x33,0x95,0xe0,0xfd,0x90,0x7f,0x96,0xe0,0xfe,0x90,0x7f,0x96,0x74} }, +{ 14, 0x066a, 0, {0x80,0x65,0x06,0xf0,0x90,0x7f,0x00,0x74,0x01,0xf0,0xea,0xc4,0x03,0x54} }, +{ 14, 0x0678, 0, {0xf8,0xfe,0xeb,0x25,0xe0,0xfb,0x2e,0xfe,0x24,0x00,0xfb,0xe4,0x34,0x17} }, +{ 14, 0x0686, 0, {0xff,0x8b,0x82,0x8f,0x83,0xe0,0xfb,0x74,0x01,0x2e,0x24,0x00,0xfe,0xe4} }, +{ 14, 0x0694, 0, {0x34,0x17,0xff,0x8e,0x82,0x8f,0x83,0xe0,0xfe,0x90,0x7f,0xe9,0xe0,0xff} }, +{ 3, 0x06a2, 0, {0xbf,0x81,0x0a} }, +{ 10, 0x06a5, 0, {0x90,0x7f,0x00,0xeb,0xf0,0x90,0x7f,0x01,0xee,0xf0} }, +{ 8, 0x06af, 0, {0x90,0x7f,0xe9,0xe0,0xfb,0xbb,0x82,0x1a} }, +{ 3, 0x06b7, 0, {0xba,0x01,0x0c} }, +{ 12, 0x06ba, 0, {0x90,0x7f,0x00,0xe4,0xf0,0x90,0x7f,0x01,0xe4,0xf0,0x80,0x0b} }, +{ 11, 0x06c6, 0, {0x90,0x7f,0x00,0xe4,0xf0,0x90,0x7f,0x01,0x74,0xb5,0xf0} }, +{ 8, 0x06d1, 0, {0x90,0x7f,0xe9,0xe0,0xfb,0xbb,0x83,0x1b} }, +{ 3, 0x06d9, 0, {0xba,0x01,0x0d} }, +{ 13, 0x06dc, 0, {0x90,0x7f,0x00,0x74,0x01,0xf0,0x90,0x7f,0x01,0xe4,0xf0,0x80,0x0b} }, +{ 11, 0x06e9, 0, {0x90,0x7f,0x00,0xe4,0xf0,0x90,0x7f,0x01,0x74,0x12,0xf0} }, +{ 8, 0x06f4, 0, {0x90,0x7f,0xe9,0xe0,0xfb,0xbb,0x84,0x1c} }, +{ 3, 0x06fc, 0, {0xba,0x01,0x0d} }, +{ 13, 0x06ff, 0, {0x90,0x7f,0x00,0x74,0x01,0xf0,0x90,0x7f,0x01,0xe4,0xf0,0x80,0x0c} }, +{ 12, 0x070c, 0, {0x90,0x7f,0x00,0x74,0x80,0xf0,0x90,0x7f,0x01,0x74,0x01,0xf0} }, +{ 5, 0x0718, 0, {0x90,0x7f,0xb5,0xec,0xf0} }, +{ 1, 0x071d, 0, {0x22} }, +{ 12, 0x071e, 0, {0x75,0x36,0x0d,0x90,0x88,0x00,0x74,0x1d,0xf0,0x75,0x6b,0x80} }, +{ 10, 0x072a, 0, {0x75,0x6c,0x3c,0x12,0x10,0xe2,0x75,0x6b,0x80,0x75} }, +{ 9, 0x0734, 0, {0x6c,0x0f,0x12,0x10,0xe2,0x75,0x6b,0x80,0x75} }, +{ 9, 0x073d, 0, {0x6c,0x06,0x12,0x10,0xe2,0x75,0x6b,0x80,0x75} }, +{ 7, 0x0746, 0, {0x6c,0x01,0x12,0x10,0xe2,0x7a,0x00} }, +{ 3, 0x074d, 0, {0xba,0xff,0x00} }, +{ 2, 0x0750, 0, {0x50,0x0a} }, +{ 10, 0x0752, 0, {0xc0,0x02,0x12,0x01,0xdd,0xd0,0x02,0x0a,0x80,0xf1} }, +{ 10, 0x075c, 0, {0x75,0x6b,0x80,0x75,0x6c,0x3c,0x12,0x10,0xe2,0x75} }, +{ 8, 0x0766, 0, {0x6b,0x80,0x75,0x6c,0x0f,0x12,0x10,0xe2} }, +{ 1, 0x076e, 0, {0x22} }, +{ 14, 0x076f, 0, {0x90,0x7f,0xa1,0xe4,0xf0,0x90,0x7f,0xaf,0x74,0x01,0xf0,0x90,0x7f,0x92} }, +{ 14, 0x077d, 0, {0x74,0x02,0xf0,0x75,0x8e,0x31,0x75,0x89,0x21,0x75,0x88,0x00,0x75,0xc8} }, +{ 14, 0x078b, 0, {0x00,0x75,0x8d,0x40,0x75,0x98,0x40,0x75,0xc0,0x40,0x75,0x87,0x00,0x75} }, +{ 9, 0x0799, 0, {0x20,0x00,0x75,0x21,0x00,0x75,0x22,0x00,0x75} }, +{ 5, 0x07a2, 0, {0x23,0x00,0x75,0x47,0x00} }, +{ 7, 0x07a7, 0, {0xc3,0xe5,0x47,0x94,0x20,0x50,0x11} }, +{ 13, 0x07ae, 0, {0xe5,0x47,0x24,0x00,0xf5,0x82,0xe4,0x34,0x17,0xf5,0x83,0xe4,0xf0} }, +{ 4, 0x07bb, 0, {0x05,0x47,0x80,0xe8} }, +{ 9, 0x07bf, 0, {0xe4,0xf5,0x40,0xf5,0x3f,0xe4,0xf5,0x3c,0xf5} }, +{ 7, 0x07c8, 0, {0x3b,0xe4,0xf5,0x3e,0xf5,0x3d,0x75} }, +{ 11, 0x07cf, 0, {0x32,0x00,0x75,0x37,0x00,0x75,0x39,0x00,0x90,0x7f,0x93} }, +{ 14, 0x07da, 0, {0x74,0x3c,0xf0,0x90,0x7f,0x9c,0x74,0xff,0xf0,0x90,0x7f,0x96,0x74,0x80} }, +{ 14, 0x07e8, 0, {0xf0,0x90,0x7f,0x94,0x74,0x70,0xf0,0x90,0x7f,0x9d,0x74,0x8f,0xf0,0x90} }, +{ 14, 0x07f6, 0, {0x7f,0x97,0xe4,0xf0,0x90,0x7f,0x95,0x74,0xc2,0xf0,0x90,0x7f,0x98,0x74} }, +{ 14, 0x0804, 0, {0x28,0xf0,0x90,0x7f,0x9e,0x74,0x28,0xf0,0x90,0x7f,0xf0,0xe4,0xf0,0x90} }, +{ 14, 0x0812, 0, {0x7f,0xf1,0xe4,0xf0,0x90,0x7f,0xf2,0xe4,0xf0,0x90,0x7f,0xf3,0xe4,0xf0} }, +{ 14, 0x0820, 0, {0x90,0x7f,0xf4,0xe4,0xf0,0x90,0x7f,0xf5,0xe4,0xf0,0x90,0x7f,0xf6,0xe4} }, +{ 14, 0x082e, 0, {0xf0,0x90,0x7f,0xf7,0xe4,0xf0,0x90,0x7f,0xf8,0xe4,0xf0,0x90,0x7f,0xf9} }, +{ 14, 0x083c, 0, {0x74,0x38,0xf0,0x90,0x7f,0xfa,0x74,0xa0,0xf0,0x90,0x7f,0xfb,0x74,0xa0} }, +{ 14, 0x084a, 0, {0xf0,0x90,0x7f,0xfc,0x74,0xa0,0xf0,0x90,0x7f,0xfd,0x74,0xa0,0xf0,0x90} }, +{ 14, 0x0858, 0, {0x7f,0xfe,0x74,0xa0,0xf0,0x90,0x7f,0xff,0x74,0xa0,0xf0,0x90,0x7f,0xe0} }, +{ 14, 0x0866, 0, {0x74,0x03,0xf0,0x90,0x7f,0xe1,0x74,0x01,0xf0,0x90,0x7f,0xdd,0x74,0x80} }, +{ 11, 0x0874, 0, {0xf0,0x12,0x12,0x43,0x12,0x07,0x1e,0x7a,0x00,0x7b,0x00} }, +{ 9, 0x087f, 0, {0xc3,0xea,0x94,0x1e,0xeb,0x94,0x00,0x50,0x17} }, +{ 12, 0x0888, 0, {0x90,0x88,0x00,0xe0,0xf5,0x47,0x90,0x88,0x0b,0xe0,0xf5,0x47} }, +{ 9, 0x0894, 0, {0x90,0x7f,0x68,0xf0,0x0a,0xba,0x00,0x01,0x0b} }, +{ 2, 0x089d, 0, {0x80,0xe0} }, +{ 12, 0x089f, 0, {0x12,0x03,0xe1,0x90,0x7f,0xd6,0xe4,0xf0,0x7a,0x00,0x7b,0x00} }, +{ 13, 0x08ab, 0, {0x8a,0x04,0x8b,0x05,0xc3,0xea,0x94,0xe0,0xeb,0x94,0x2e,0x50,0x1a} }, +{ 14, 0x08b8, 0, {0xc0,0x02,0xc0,0x03,0xc0,0x04,0xc0,0x05,0x12,0x01,0xdd,0xd0,0x05,0xd0} }, +{ 10, 0x08c6, 0, {0x04,0xd0,0x03,0xd0,0x02,0x0a,0xba,0x00,0x01,0x0b} }, +{ 2, 0x08d0, 0, {0x80,0xd9} }, +{ 13, 0x08d2, 0, {0x90,0x7f,0xd6,0x74,0x02,0xf0,0x90,0x7f,0xd6,0x74,0x06,0xf0,0x90} }, +{ 14, 0x08df, 0, {0x7f,0xde,0x74,0x05,0xf0,0x90,0x7f,0xdf,0x74,0x05,0xf0,0x90,0x7f,0xac} }, +{ 14, 0x08ed, 0, {0xe4,0xf0,0x90,0x7f,0xad,0x74,0x05,0xf0,0x75,0xa8,0x80,0x75,0xf8,0x10} }, +{ 13, 0x08fb, 0, {0x90,0x7f,0xae,0x74,0x0b,0xf0,0x90,0x7f,0xe2,0x74,0x88,0xf0,0x90} }, +{ 12, 0x0908, 0, {0x7f,0xab,0x74,0x08,0xf0,0x75,0xe8,0x11,0x75,0x32,0x01,0x75} }, +{ 12, 0x0914, 0, {0x31,0x00,0x75,0x30,0x00,0xc0,0x04,0xc0,0x05,0x12,0x04,0xf7} }, +{ 10, 0x0920, 0, {0xd0,0x05,0xd0,0x04,0x75,0x34,0x00,0x75,0x35,0x01} }, +{ 13, 0x092a, 0, {0x90,0x7f,0xae,0x74,0x03,0xf0,0x8c,0x02,0xba,0x00,0x02,0x80,0x03} }, +{ 3, 0x0937, 0, {0x02,0x0a,0x3f} }, +{ 12, 0x093a, 0, {0x85,0x33,0x34,0x90,0x7f,0x9d,0x74,0x8f,0xf0,0x90,0x7f,0x97} }, +{ 14, 0x0946, 0, {0x74,0x08,0xf0,0x90,0x7f,0x9d,0x74,0x88,0xf0,0x90,0x7f,0x9a,0xe0,0xfa} }, +{ 12, 0x0954, 0, {0x74,0x05,0x5a,0xf5,0x33,0x90,0x7f,0x9d,0x74,0x8f,0xf0,0x90} }, +{ 13, 0x0960, 0, {0x7f,0x97,0x74,0x02,0xf0,0x90,0x7f,0x9d,0x74,0x82,0xf0,0xe5,0x33} }, +{ 13, 0x096d, 0, {0x25,0xe0,0xfa,0x90,0x7f,0x9a,0xe0,0x54,0x05,0xfb,0x4a,0xf5,0x33} }, +{ 2, 0x097a, 0, {0x60,0x0c} }, +{ 12, 0x097c, 0, {0x90,0x7f,0x96,0xe0,0xfa,0x90,0x7f,0x96,0x74,0x80,0x4a,0xf0} }, +{ 11, 0x0988, 0, {0x75,0x6e,0x00,0x75,0x6f,0x00,0xc0,0x04,0xc0,0x05,0x12} }, +{ 14, 0x0993, 0, {0x11,0x44,0xd0,0x05,0xd0,0x04,0x90,0x17,0x13,0xe0,0xfa,0x74,0x80,0x2a} }, +{ 6, 0x09a1, 0, {0xfa,0xe5,0x33,0xb4,0x04,0x29} }, +{ 3, 0x09a7, 0, {0xba,0xa0,0x00} }, +{ 2, 0x09aa, 0, {0x50,0x24} }, +{ 13, 0x09ac, 0, {0x90,0x17,0x13,0xe0,0x04,0xfb,0x0b,0x90,0x17,0x13,0xeb,0xf0,0x90} }, +{ 14, 0x09b9, 0, {0x17,0x13,0xe0,0xfb,0x90,0x17,0x15,0xf0,0xc0,0x02,0xc0,0x04,0xc0,0x05} }, +{ 9, 0x09c7, 0, {0x12,0x04,0xf7,0xd0,0x05,0xd0,0x04,0xd0,0x02} }, +{ 5, 0x09d0, 0, {0xe5,0x33,0xb4,0x02,0x26} }, +{ 6, 0x09d5, 0, {0xc3,0x74,0x04,0x9a,0x50,0x20} }, +{ 13, 0x09db, 0, {0x90,0x17,0x13,0xe0,0xfa,0x1a,0x1a,0x90,0x17,0x13,0xea,0xf0,0x90} }, +{ 13, 0x09e8, 0, {0x17,0x13,0xe0,0xfa,0x90,0x17,0x15,0xf0,0xc0,0x04,0xc0,0x05,0x12} }, +{ 6, 0x09f5, 0, {0x04,0xf7,0xd0,0x05,0xd0,0x04} }, +{ 5, 0x09fb, 0, {0xe5,0x33,0xb4,0x08,0x1d} }, +{ 4, 0x0a00, 0, {0xe5,0x34,0x70,0x19} }, +{ 10, 0x0a04, 0, {0x74,0x01,0x25,0x35,0x54,0x0f,0xf5,0x35,0x85,0x35} }, +{ 12, 0x0a0e, 0, {0x75,0x75,0x76,0x00,0xc0,0x04,0xc0,0x05,0x12,0x13,0xfe,0xd0} }, +{ 3, 0x0a1a, 0, {0x05,0xd0,0x04} }, +{ 5, 0x0a1d, 0, {0xe5,0x33,0xb4,0x01,0x1d} }, +{ 4, 0x0a22, 0, {0xe5,0x34,0x70,0x19} }, +{ 10, 0x0a26, 0, {0xe5,0x35,0x24,0xff,0x54,0x0f,0xf5,0x35,0x85,0x35} }, +{ 12, 0x0a30, 0, {0x75,0x75,0x76,0x00,0xc0,0x04,0xc0,0x05,0x12,0x13,0xfe,0xd0} }, +{ 3, 0x0a3c, 0, {0x05,0xd0,0x04} }, +{ 14, 0x0a3f, 0, {0xc0,0x04,0xc0,0x05,0x12,0x01,0xdd,0xd0,0x05,0xd0,0x04,0x90,0x7f,0x96} }, +{ 14, 0x0a4d, 0, {0xe0,0xfa,0x90,0x7f,0x96,0x74,0x7f,0x5a,0xf0,0x90,0x7f,0x97,0x74,0x08} }, +{ 10, 0x0a5b, 0, {0xf0,0xc3,0xec,0x94,0x00,0xed,0x94,0x02,0x40,0x08} }, +{ 8, 0x0a65, 0, {0x90,0x7f,0x96,0xe0,0xfa,0x20,0xe6,0x08} }, +{ 8, 0x0a6d, 0, {0xc3,0xe4,0x9c,0x74,0x08,0x9d,0x50,0x13} }, +{ 14, 0x0a75, 0, {0x90,0x7f,0x96,0xe0,0xfa,0x90,0x7f,0x96,0x74,0x40,0x65,0x02,0xf0,0x7c} }, +{ 5, 0x0a83, 0, {0x00,0x7d,0x00,0x80,0x05} }, +{ 5, 0x0a88, 0, {0x0c,0xbc,0x00,0x01,0x0d} }, +{ 5, 0x0a8d, 0, {0xe5,0x38,0xb4,0x01,0x0e} }, +{ 13, 0x0a92, 0, {0xc0,0x04,0xc0,0x05,0x12,0x04,0xf7,0xd0,0x05,0xd0,0x04,0x75,0x38} }, +{ 1, 0x0a9f, 0, {0x00} }, +{ 7, 0x0aa0, 0, {0xe5,0x31,0x70,0x03,0x02,0x09,0x2a} }, +{ 10, 0x0aa7, 0, {0x90,0x7f,0xc9,0xe0,0xfa,0x70,0x03,0x02,0x0c,0x2d} }, +{ 14, 0x0ab1, 0, {0x90,0x7f,0x96,0xe0,0xfa,0x90,0x7f,0x96,0x74,0x80,0x65,0x02,0xf0,0x90} }, +{ 9, 0x0abf, 0, {0x7d,0xc0,0xe0,0xfa,0xba,0x2c,0x02,0x80,0x03} }, +{ 3, 0x0ac8, 0, {0x02,0x0b,0x36} }, +{ 5, 0x0acb, 0, {0x75,0x32,0x00,0x7b,0x00} }, +{ 3, 0x0ad0, 0, {0xbb,0x64,0x00} }, +{ 2, 0x0ad3, 0, {0x50,0x1c} }, +{ 14, 0x0ad5, 0, {0xc0,0x02,0xc0,0x03,0xc0,0x04,0xc0,0x05,0x12,0x01,0xdd,0xd0,0x05,0xd0} }, +{ 13, 0x0ae3, 0, {0x04,0xd0,0x03,0xd0,0x02,0x90,0x88,0x0f,0xe0,0xf5,0x47,0x0b,0x80} }, +{ 1, 0x0af0, 0, {0xdf} }, +{ 13, 0x0af1, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x07,0x1e,0x12,0x03,0xe1,0x12} }, +{ 12, 0x0afe, 0, {0x04,0xf7,0xd0,0x05,0xd0,0x04,0xd0,0x02,0x75,0x6e,0x00,0x75} }, +{ 13, 0x0b0a, 0, {0x6f,0x01,0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x11,0x44,0xd0,0x05} }, +{ 9, 0x0b17, 0, {0xd0,0x04,0xd0,0x02,0x75,0x70,0x4d,0x75,0x71} }, +{ 11, 0x0b20, 0, {0x0c,0x75,0x72,0x02,0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12} }, +{ 11, 0x0b2b, 0, {0x11,0x75,0xd0,0x05,0xd0,0x04,0xd0,0x02,0x02,0x0c,0x2d} }, +{ 3, 0x0b36, 0, {0xba,0x2a,0x3b} }, +{ 13, 0x0b39, 0, {0x90,0x7f,0x98,0x74,0x20,0xf0,0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12} }, +{ 14, 0x0b46, 0, {0x01,0xdd,0xd0,0x05,0xd0,0x04,0xd0,0x02,0x90,0x7f,0x98,0x74,0x28,0xf0} }, +{ 2, 0x0b54, 0, {0x7b,0x00} }, +{ 3, 0x0b56, 0, {0xbb,0x0a,0x00} }, +{ 5, 0x0b59, 0, {0x40,0x03,0x02,0x0c,0x2d} }, +{ 14, 0x0b5e, 0, {0xc0,0x02,0xc0,0x03,0xc0,0x04,0xc0,0x05,0x12,0x01,0xdd,0xd0,0x05,0xd0} }, +{ 8, 0x0b6c, 0, {0x04,0xd0,0x03,0xd0,0x02,0x0b,0x80,0xe2} }, +{ 3, 0x0b74, 0, {0xba,0x2b,0x1a} }, +{ 8, 0x0b77, 0, {0x90,0x7f,0xc9,0xe0,0xfb,0xbb,0x40,0x12} }, +{ 14, 0x0b7f, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x12,0x05,0xd0,0x05,0xd0,0x04,0xd0} }, +{ 4, 0x0b8d, 0, {0x02,0x02,0x0c,0x2d} }, +{ 3, 0x0b91, 0, {0xba,0x10,0x1f} }, +{ 14, 0x0b94, 0, {0x90,0x7f,0x96,0xe0,0xfb,0x90,0x7f,0x96,0x74,0x80,0x65,0x03,0xf0,0xc0} }, +{ 14, 0x0ba2, 0, {0x02,0xc0,0x04,0xc0,0x05,0x12,0x10,0x3d,0xd0,0x05,0xd0,0x04,0xd0,0x02} }, +{ 3, 0x0bb0, 0, {0x02,0x0c,0x2d} }, +{ 3, 0x0bb3, 0, {0xba,0x11,0x12} }, +{ 14, 0x0bb6, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x10,0x6a,0xd0,0x05,0xd0,0x04,0xd0} }, +{ 4, 0x0bc4, 0, {0x02,0x02,0x0c,0x2d} }, +{ 3, 0x0bc8, 0, {0xba,0x12,0x12} }, +{ 14, 0x0bcb, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x10,0x8f,0xd0,0x05,0xd0,0x04,0xd0} }, +{ 4, 0x0bd9, 0, {0x02,0x02,0x0c,0x2d} }, +{ 3, 0x0bdd, 0, {0xba,0x13,0x0b} }, +{ 11, 0x0be0, 0, {0x90,0x7d,0xc1,0xe0,0xfb,0x90,0x88,0x00,0xf0,0x80,0x42} }, +{ 3, 0x0beb, 0, {0xba,0x14,0x11} }, +{ 14, 0x0bee, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x11,0xdd,0xd0,0x05,0xd0,0x04,0xd0} }, +{ 3, 0x0bfc, 0, {0x02,0x80,0x2e} }, +{ 3, 0x0bff, 0, {0xba,0x15,0x1d} }, +{ 12, 0x0c02, 0, {0x90,0x7d,0xc1,0xe0,0xf5,0x75,0x90,0x7d,0xc2,0xe0,0xf5,0x76} }, +{ 14, 0x0c0e, 0, {0xc0,0x02,0xc0,0x04,0xc0,0x05,0x12,0x13,0xfe,0xd0,0x05,0xd0,0x04,0xd0} }, +{ 3, 0x0c1c, 0, {0x02,0x80,0x0e} }, +{ 3, 0x0c1f, 0, {0xba,0x16,0x0b} }, +{ 11, 0x0c22, 0, {0xc0,0x04,0xc0,0x05,0x12,0x13,0xa3,0xd0,0x05,0xd0,0x04} }, +{ 11, 0x0c2d, 0, {0x90,0x7f,0xc9,0xe4,0xf0,0x75,0x31,0x00,0x02,0x09,0x2a} }, +{ 1, 0x0c38, 0, {0x22} }, +{ 7, 0x0c39, 0, {0x53,0x55,0x50,0x45,0x4e,0x44,0x00} }, +{ 7, 0x0c40, 0, {0x52,0x45,0x53,0x55,0x4d,0x45,0x00} }, +{ 6, 0x0c47, 0, {0x20,0x56,0x6f,0x6c,0x20,0x00} }, +{ 13, 0x0c4d, 0, {0x44,0x41,0x42,0x55,0x53,0x42,0x20,0x76,0x31,0x2e,0x30,0x30,0x00} }, +{ 14, 0x0c5a, 0, {0xc0,0xe0,0xc0,0xf0,0xc0,0x82,0xc0,0x83,0xc0,0x02,0xc0,0x03,0xc0,0x04} }, +{ 14, 0x0c68, 0, {0xc0,0x05,0xc0,0x06,0xc0,0x07,0xc0,0x00,0xc0,0x01,0xc0,0xd0,0x75,0xd0} }, +{ 13, 0x0c76, 0, {0x00,0xc0,0x86,0x75,0x86,0x00,0xe5,0x91,0xc2,0xe4,0xf5,0x91,0x90} }, +{ 14, 0x0c83, 0, {0x7f,0xab,0x74,0x01,0xf0,0x90,0x7f,0xe8,0xe0,0xfa,0x90,0x7f,0xe9,0xe0} }, +{ 6, 0x0c91, 0, {0xfb,0xbb,0x00,0x02,0x80,0x03} }, +{ 3, 0x0c97, 0, {0x02,0x0d,0x38} }, +{ 3, 0x0c9a, 0, {0xba,0x80,0x14} }, +{ 14, 0x0c9d, 0, {0x90,0x7f,0x00,0x74,0x01,0xf0,0x90,0x7f,0x01,0xe4,0xf0,0x90,0x7f,0xb5} }, +{ 6, 0x0cab, 0, {0x74,0x02,0xf0,0x02,0x0e,0xcd} }, +{ 5, 0x0cb1, 0, {0xba,0x82,0x02,0x80,0x03} }, +{ 3, 0x0cb6, 0, {0x02,0x0d,0x1d} }, +{ 8, 0x0cb9, 0, {0x90,0x7f,0xec,0xe0,0xfc,0xbc,0x01,0x00} }, +{ 2, 0x0cc1, 0, {0x40,0x21} }, +{ 6, 0x0cc3, 0, {0xc3,0x74,0x07,0x9c,0x40,0x1b} }, +{ 14, 0x0cc9, 0, {0xec,0x24,0xff,0x25,0xe0,0xfd,0x24,0xc6,0xf5,0x82,0xe4,0x34,0x7f,0xf5} }, +{ 13, 0x0cd7, 0, {0x83,0xe0,0xfd,0x53,0x05,0x01,0x90,0x7f,0x00,0xed,0xf0,0x80,0x2b} }, +{ 3, 0x0ce4, 0, {0xbc,0x81,0x00} }, +{ 2, 0x0ce7, 0, {0x40,0x21} }, +{ 6, 0x0ce9, 0, {0xc3,0x74,0x87,0x9c,0x40,0x1b} }, +{ 14, 0x0cef, 0, {0xec,0x24,0x7f,0x25,0xe0,0xfc,0x24,0xb6,0xf5,0x82,0xe4,0x34,0x7f,0xf5} }, +{ 13, 0x0cfd, 0, {0x83,0xe0,0xfc,0x53,0x04,0x01,0x90,0x7f,0x00,0xec,0xf0,0x80,0x05} }, +{ 5, 0x0d0a, 0, {0x90,0x7f,0x00,0xe4,0xf0} }, +{ 14, 0x0d0f, 0, {0x90,0x7f,0x01,0xe4,0xf0,0x90,0x7f,0xb5,0x74,0x02,0xf0,0x02,0x0e,0xcd} }, +{ 5, 0x0d1d, 0, {0xba,0x81,0x02,0x80,0x03} }, +{ 3, 0x0d22, 0, {0x02,0x0e,0xc5} }, +{ 14, 0x0d25, 0, {0x90,0x7f,0x00,0xe4,0xf0,0x90,0x7f,0x01,0xe4,0xf0,0x90,0x7f,0xb5,0x74} }, +{ 5, 0x0d33, 0, {0x02,0xf0,0x02,0x0e,0xcd} }, +{ 3, 0x0d38, 0, {0xbb,0x01,0x2d} }, +{ 6, 0x0d3b, 0, {0xba,0x00,0x03,0x02,0x0e,0xcd} }, +{ 3, 0x0d41, 0, {0xba,0x02,0x11} }, +{ 13, 0x0d44, 0, {0x75,0x59,0x00,0xc0,0x02,0xc0,0x03,0x12,0x0e,0xf0,0xd0,0x03,0xd0} }, +{ 4, 0x0d51, 0, {0x02,0x02,0x0e,0xcd} }, +{ 5, 0x0d55, 0, {0xba,0x21,0x02,0x80,0x03} }, +{ 3, 0x0d5a, 0, {0x02,0x0e,0xcd} }, +{ 11, 0x0d5d, 0, {0x75,0x37,0x01,0x90,0x7f,0xc5,0xe4,0xf0,0x02,0x0e,0xcd} }, +{ 3, 0x0d68, 0, {0xbb,0x03,0x1f} }, +{ 6, 0x0d6b, 0, {0xba,0x00,0x03,0x02,0x0e,0xcd} }, +{ 5, 0x0d71, 0, {0xba,0x02,0x02,0x80,0x03} }, +{ 3, 0x0d76, 0, {0x02,0x0e,0xcd} }, +{ 13, 0x0d79, 0, {0x75,0x59,0x01,0xc0,0x02,0xc0,0x03,0x12,0x0e,0xf0,0xd0,0x03,0xd0} }, +{ 4, 0x0d86, 0, {0x02,0x02,0x0e,0xcd} }, +{ 3, 0x0d8a, 0, {0xbb,0x06,0x54} }, +{ 5, 0x0d8d, 0, {0xba,0x80,0x02,0x80,0x03} }, +{ 3, 0x0d92, 0, {0x02,0x0e,0xc5} }, +{ 8, 0x0d95, 0, {0x90,0x7f,0xeb,0xe0,0xfc,0xbc,0x01,0x15} }, +{ 12, 0x0d9d, 0, {0x7c,0xfb,0x7d,0x0f,0x8d,0x06,0x7f,0x00,0x90,0x7f,0xd4,0xee} }, +{ 9, 0x0da9, 0, {0xf0,0x90,0x7f,0xd5,0xec,0xf0,0x02,0x0e,0xcd} }, +{ 10, 0x0db2, 0, {0x90,0x7f,0xeb,0xe0,0xfc,0xbc,0x02,0x02,0x80,0x03} }, +{ 3, 0x0dbc, 0, {0x02,0x0e,0xc5} }, +{ 10, 0x0dbf, 0, {0x90,0x7f,0xea,0xe0,0xfc,0xbc,0x00,0x02,0x80,0x03} }, +{ 3, 0x0dc9, 0, {0x02,0x0e,0xc5} }, +{ 12, 0x0dcc, 0, {0x7c,0x3b,0x7d,0x0f,0x8d,0x06,0x7f,0x00,0x90,0x7f,0xd4,0xee} }, +{ 9, 0x0dd8, 0, {0xf0,0x90,0x7f,0xd5,0xec,0xf0,0x02,0x0e,0xcd} }, +{ 6, 0x0de1, 0, {0xbb,0x07,0x03,0x02,0x0e,0xc5} }, +{ 3, 0x0de7, 0, {0xbb,0x08,0x10} }, +{ 13, 0x0dea, 0, {0xac,0x48,0x90,0x7f,0x00,0xec,0xf0,0x90,0x7f,0xb5,0x74,0x01,0xf0} }, +{ 3, 0x0df7, 0, {0x02,0x0e,0xcd} }, +{ 3, 0x0dfa, 0, {0xbb,0x09,0x31} }, +{ 5, 0x0dfd, 0, {0xba,0x00,0x02,0x80,0x03} }, +{ 3, 0x0e02, 0, {0x02,0x0e,0xc5} }, +{ 14, 0x0e05, 0, {0x90,0x7f,0xea,0xe0,0xfc,0xc3,0x74,0x01,0x9c,0x50,0x03,0x02,0x0e,0xc5} }, +{ 8, 0x0e13, 0, {0x90,0x7f,0xea,0xe0,0xfc,0xbc,0x00,0x0a} }, +{ 10, 0x0e1b, 0, {0x90,0x17,0x21,0xe4,0xf0,0x90,0x17,0x22,0xe4,0xf0} }, +{ 9, 0x0e25, 0, {0x90,0x7f,0xea,0xe0,0xf5,0x48,0x02,0x0e,0xcd} }, +{ 3, 0x0e2e, 0, {0xbb,0x0a,0x27} }, +{ 5, 0x0e31, 0, {0xba,0x81,0x02,0x80,0x03} }, +{ 3, 0x0e36, 0, {0x02,0x0e,0xc5} }, +{ 14, 0x0e39, 0, {0x90,0x7f,0xec,0xe0,0xfa,0x24,0x20,0xfa,0xe4,0x34,0x17,0xfc,0x8a,0x82} }, +{ 14, 0x0e47, 0, {0x8c,0x83,0xe0,0xfa,0x90,0x7f,0x00,0xf0,0x90,0x7f,0xb5,0x74,0x01,0xf0} }, +{ 3, 0x0e55, 0, {0x02,0x0e,0xcd} }, +{ 5, 0x0e58, 0, {0xbb,0x0b,0x02,0x80,0x03} }, +{ 3, 0x0e5d, 0, {0x02,0x0e,0xa9} }, +{ 13, 0x0e60, 0, {0x90,0x17,0x20,0xe4,0xf0,0x90,0x7f,0xec,0xe0,0xfa,0xba,0x01,0x1a} }, +{ 8, 0x0e6d, 0, {0x90,0x7f,0xed,0xe0,0xfa,0xba,0x00,0x12} }, +{ 14, 0x0e75, 0, {0x90,0x7f,0xea,0xe0,0xfa,0x90,0x17,0x21,0xf0,0xc0,0x03,0x12,0x04,0xe2} }, +{ 4, 0x0e83, 0, {0xd0,0x03,0x80,0x46} }, +{ 8, 0x0e87, 0, {0x90,0x7f,0xec,0xe0,0xfa,0xba,0x02,0x3e} }, +{ 8, 0x0e8f, 0, {0x90,0x7f,0xed,0xe0,0xfa,0xba,0x00,0x36} }, +{ 13, 0x0e97, 0, {0xc0,0x03,0x12,0x04,0xef,0xd0,0x03,0x90,0x7f,0xea,0xe0,0xfa,0x90} }, +{ 5, 0x0ea4, 0, {0x17,0x22,0xf0,0x80,0x24} }, +{ 5, 0x0ea9, 0, {0xbb,0x12,0x02,0x80,0x17} }, +{ 5, 0x0eae, 0, {0xbb,0x81,0x02,0x80,0x0d} }, +{ 5, 0x0eb3, 0, {0xbb,0x83,0x02,0x80,0x08} }, +{ 5, 0x0eb8, 0, {0xbb,0x82,0x02,0x80,0x03} }, +{ 3, 0x0ebd, 0, {0xbb,0x84,0x05} }, +{ 5, 0x0ec0, 0, {0x12,0x06,0x4e,0x80,0x08} }, +{ 8, 0x0ec5, 0, {0x90,0x7f,0xb4,0x74,0x03,0xf0,0x80,0x06} }, +{ 6, 0x0ecd, 0, {0x90,0x7f,0xb4,0x74,0x02,0xf0} }, +{ 2, 0x0ed3, 0, {0xd0,0x86} }, +{ 14, 0x0ed5, 0, {0xd0,0xd0,0xd0,0x01,0xd0,0x00,0xd0,0x07,0xd0,0x06,0xd0,0x05,0xd0,0x04} }, +{ 13, 0x0ee3, 0, {0xd0,0x03,0xd0,0x02,0xd0,0x83,0xd0,0x82,0xd0,0xf0,0xd0,0xe0,0x32} }, +{ 11, 0x0ef0, 0, {0x90,0x7f,0xec,0xe0,0xf5,0x5a,0xc3,0x94,0x01,0x40,0x1d} }, +{ 7, 0x0efb, 0, {0xc3,0x74,0x07,0x95,0x5a,0x40,0x16} }, +{ 13, 0x0f02, 0, {0xe5,0x5a,0x24,0xff,0x25,0xe0,0xfa,0x24,0xc6,0xf5,0x82,0xe4,0x34} }, +{ 9, 0x0f0f, 0, {0x7f,0xf5,0x83,0xaa,0x59,0xea,0xf0,0x80,0x22} }, +{ 7, 0x0f18, 0, {0xc3,0xe5,0x5a,0x94,0x81,0x40,0x1b} }, +{ 7, 0x0f1f, 0, {0xc3,0x74,0x87,0x95,0x5a,0x40,0x14} }, +{ 13, 0x0f26, 0, {0xe5,0x5a,0x24,0xff,0x25,0xe0,0xfa,0x24,0xb6,0xf5,0x82,0xe4,0x34} }, +{ 7, 0x0f33, 0, {0x7f,0xf5,0x83,0xaa,0x59,0xea,0xf0} }, +{ 1, 0x0f3a, 0, {0x22} }, +{ 14, 0x0f3b, 0, {0x09,0x02,0xba,0x00,0x03,0x01,0x00,0x40,0x00,0x09,0x04,0x00,0x00,0x00} }, +{ 14, 0x0f49, 0, {0x01,0x01,0x00,0x00,0x09,0x24,0x01,0x00,0x01,0x3d,0x00,0x01,0x01,0x0c} }, +{ 14, 0x0f57, 0, {0x24,0x02,0x01,0x10,0x07,0x00,0x02,0x03,0x00,0x00,0x00,0x0d,0x24,0x06} }, +{ 14, 0x0f65, 0, {0x03,0x01,0x02,0x15,0x00,0x03,0x00,0x03,0x00,0x00,0x09,0x24,0x03,0x02} }, +{ 14, 0x0f73, 0, {0x01,0x01,0x00,0x01,0x00,0x09,0x24,0x03,0x04,0x02,0x03,0x00,0x03,0x00} }, +{ 14, 0x0f81, 0, {0x09,0x24,0x03,0x05,0x03,0x06,0x00,0x01,0x00,0x09,0x04,0x01,0x00,0x00} }, +{ 14, 0x0f8f, 0, {0x01,0x02,0x00,0x00,0x09,0x04,0x01,0x01,0x01,0x01,0x02,0x00,0x00,0x07} }, +{ 14, 0x0f9d, 0, {0x24,0x01,0x02,0x01,0x01,0x00,0x0b,0x24,0x02,0x01,0x02,0x02,0x10,0x01} }, +{ 14, 0x0fab, 0, {0x80,0xbb,0x00,0x09,0x05,0x88,0x05,0x00,0x01,0x01,0x00,0x00,0x07,0x25} }, +{ 14, 0x0fb9, 0, {0x01,0x00,0x00,0x00,0x00,0x09,0x04,0x02,0x00,0x02,0x00,0x00,0x00,0x00} }, +{ 14, 0x0fc7, 0, {0x07,0x05,0x82,0x02,0x40,0x00,0x00,0x07,0x05,0x02,0x02,0x40,0x00,0x00} }, +{ 14, 0x0fd5, 0, {0x09,0x04,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x07,0x05,0x82,0x02,0x40} }, +{ 14, 0x0fe3, 0, {0x00,0x00,0x07,0x05,0x02,0x02,0x40,0x00,0x00,0x09,0x05,0x89,0x05,0xa0} }, +{ 10, 0x0ff1, 0, {0x01,0x01,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00} }, +{ 14, 0x0ffb, 0, {0x12,0x01,0x00,0x01,0x00,0x00,0x00,0x40,0x47,0x05,0x99,0x99,0x00,0x01} }, +{ 14, 0x1009, 0, {0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x02,0xba} }, +{ 4, 0x1017, 0, {0x00,0x03,0x01,0x00} }, +{ 2, 0x101b, 0, {0x7a,0x00} }, +{ 3, 0x101d, 0, {0xba,0x05,0x00} }, +{ 2, 0x1020, 0, {0x50,0x17} }, +{ 8, 0x1022, 0, {0x90,0x7f,0xa5,0xe0,0xfb,0x30,0xe0,0x05} }, +{ 5, 0x102a, 0, {0x90,0x00,0x01,0x80,0x0d} }, +{ 10, 0x102f, 0, {0xc0,0x02,0x12,0x01,0xdd,0xd0,0x02,0x0a,0x80,0xe4} }, +{ 3, 0x1039, 0, {0x90,0x00,0x01} }, +{ 1, 0x103c, 0, {0x22} }, +{ 14, 0x103d, 0, {0x90,0x7d,0xc1,0xe0,0xf9,0xa3,0xe0,0xfa,0xa3,0xe0,0xfb,0x7c,0x00,0x7d} }, +{ 4, 0x104b, 0, {0x7e,0xeb,0x60,0x12} }, +{ 14, 0x104f, 0, {0x89,0x82,0x8a,0x83,0xe0,0xa3,0xa9,0x82,0xaa,0x83,0x8c,0x82,0x8d,0x83} }, +{ 4, 0x105d, 0, {0xf0,0x0c,0xdb,0xee} }, +{ 8, 0x1061, 0, {0x90,0x7d,0xc3,0xe0,0x90,0x7f,0xb9,0xf0} }, +{ 1, 0x1069, 0, {0x22} }, +{ 14, 0x106a, 0, {0x90,0x7d,0xc1,0xe0,0xf9,0xa3,0xe0,0xfa,0xa3,0xe0,0xfb,0x7c,0xc4,0x7d} }, +{ 4, 0x1078, 0, {0x7d,0xeb,0x60,0xe5} }, +{ 14, 0x107c, 0, {0x8c,0x82,0x8d,0x83,0xe0,0x0c,0x89,0x82,0x8a,0x83,0xf0,0xa3,0xa9,0x82} }, +{ 4, 0x108a, 0, {0xaa,0x83,0xdb,0xee} }, +{ 1, 0x108e, 0, {0x22} }, +{ 14, 0x108f, 0, {0x90,0x7f,0xa5,0x74,0x80,0xf0,0x05,0x86,0x90,0x7d,0xc1,0xe0,0x05,0x86} }, +{ 14, 0x109d, 0, {0xa3,0xf0,0x12,0x10,0x1b,0x90,0x7f,0xa6,0x05,0x86,0xa3,0xa3,0xe0,0xf9} }, +{ 5, 0x10ab, 0, {0x60,0x16,0xa3,0x05,0x86} }, +{ 13, 0x10b0, 0, {0x90,0x7f,0xa6,0x05,0x86,0xe0,0xa3,0x05,0x86,0xf0,0xc0,0x01,0x12} }, +{ 6, 0x10bd, 0, {0x10,0x1b,0xd0,0x01,0xd9,0xed} }, +{ 6, 0x10c3, 0, {0x90,0x7f,0xa5,0x74,0x40,0xf0} }, +{ 1, 0x10c9, 0, {0x22} }, +{ 8, 0x10ca, 0, {0x90,0x88,0x02,0x74,0x01,0xf0,0x7a,0x00} }, +{ 3, 0x10d2, 0, {0xba,0xff,0x00} }, +{ 2, 0x10d5, 0, {0x50,0x0a} }, +{ 10, 0x10d7, 0, {0xc0,0x02,0x12,0x01,0xdd,0xd0,0x02,0x0a,0x80,0xf1} }, +{ 1, 0x10e1, 0, {0x22} }, +{ 5, 0x10e2, 0, {0xe5,0x6b,0xb4,0xc0,0x08} }, +{ 8, 0x10e7, 0, {0x90,0x88,0x03,0xe5,0x6c,0xf0,0x80,0x06} }, +{ 6, 0x10ef, 0, {0x90,0x88,0x02,0xe5,0x6c,0xf0} }, +{ 4, 0x10f5, 0, {0x7a,0x00,0x7b,0x00} }, +{ 11, 0x10f9, 0, {0xc3,0xea,0x94,0x32,0xeb,0x64,0x80,0x94,0x80,0x50,0x07} }, +{ 5, 0x1104, 0, {0x0a,0xba,0x00,0x01,0x0b} }, +{ 2, 0x1109, 0, {0x80,0xee} }, +{ 1, 0x110b, 0, {0x22} }, +{ 10, 0x110c, 0, {0x90,0x88,0x03,0xe5,0x6d,0xf0,0x05,0x39,0x7a,0x00} }, +{ 3, 0x1116, 0, {0xba,0x28,0x00} }, +{ 2, 0x1119, 0, {0x50,0x03} }, +{ 3, 0x111b, 0, {0x0a,0x80,0xf8} }, +{ 5, 0x111e, 0, {0xe5,0x39,0xb4,0x10,0x08} }, +{ 8, 0x1123, 0, {0x90,0x88,0x02,0x74,0xc0,0xf0,0x80,0x0e} }, +{ 5, 0x112b, 0, {0xe5,0x39,0xb4,0x20,0x09} }, +{ 9, 0x1130, 0, {0x90,0x88,0x02,0x74,0x80,0xf0,0x75,0x39,0x00} }, +{ 2, 0x1139, 0, {0x7a,0x00} }, +{ 3, 0x113b, 0, {0xba,0x28,0x00} }, +{ 2, 0x113e, 0, {0x50,0x03} }, +{ 3, 0x1140, 0, {0x0a,0x80,0xf8} }, +{ 1, 0x1143, 0, {0x22} }, +{ 4, 0x1144, 0, {0xe5,0x6f,0x60,0x02} }, +{ 2, 0x1148, 0, {0x80,0x07} }, +{ 7, 0x114a, 0, {0x7a,0x00,0x75,0x39,0x00,0x80,0x05} }, +{ 5, 0x1151, 0, {0x7a,0x40,0x75,0x39,0x10} }, +{ 9, 0x1156, 0, {0xe5,0x6e,0x2a,0xfa,0xe5,0x6e,0x25,0x39,0xf5} }, +{ 10, 0x115f, 0, {0x39,0x90,0x88,0x02,0x74,0x80,0x2a,0xf0,0x7a,0x00} }, +{ 8, 0x1169, 0, {0xc3,0xea,0x64,0x80,0x94,0xa8,0x50,0x03} }, +{ 3, 0x1171, 0, {0x0a,0x80,0xf5} }, +{ 1, 0x1174, 0, {0x22} }, +{ 6, 0x1175, 0, {0xaa,0x70,0xab,0x71,0xac,0x72} }, +{ 12, 0x117b, 0, {0x8a,0x82,0x8b,0x83,0x8c,0xf0,0x12,0x14,0xee,0xfd,0x60,0x18} }, +{ 13, 0x1187, 0, {0x8d,0x6d,0xc0,0x02,0xc0,0x03,0xc0,0x04,0x12,0x11,0x0c,0xd0,0x04} }, +{ 9, 0x1194, 0, {0xd0,0x03,0xd0,0x02,0x0a,0xba,0x00,0x01,0x0b} }, +{ 2, 0x119d, 0, {0x80,0xdc} }, +{ 1, 0x119f, 0, {0x22} }, +{ 13, 0x11a0, 0, {0xe5,0x73,0xc4,0x54,0x0f,0xfa,0x53,0x02,0x0f,0xc3,0x74,0x09,0x9a} }, +{ 2, 0x11ad, 0, {0x50,0x06} }, +{ 6, 0x11af, 0, {0x74,0x37,0x2a,0xfb,0x80,0x04} }, +{ 4, 0x11b5, 0, {0x74,0x30,0x2a,0xfb} }, +{ 12, 0x11b9, 0, {0x8b,0x6d,0xc0,0x03,0x12,0x11,0x0c,0xd0,0x03,0xaa,0x73,0x53} }, +{ 8, 0x11c5, 0, {0x02,0x0f,0xc3,0x74,0x09,0x9a,0x50,0x06} }, +{ 6, 0x11cd, 0, {0x74,0x37,0x2a,0xfb,0x80,0x04} }, +{ 4, 0x11d3, 0, {0x74,0x30,0x2a,0xfb} }, +{ 5, 0x11d7, 0, {0x8b,0x6d,0x12,0x11,0x0c} }, +{ 1, 0x11dc, 0, {0x22} }, +{ 7, 0x11dd, 0, {0x90,0x7d,0xc3,0xe0,0xfa,0x60,0x0f} }, +{ 12, 0x11e4, 0, {0x90,0x7d,0xc1,0xe0,0xf5,0x6e,0x90,0x7d,0xc2,0xe0,0xf5,0x6f} }, +{ 3, 0x11f0, 0, {0x12,0x11,0x44} }, +{ 12, 0x11f3, 0, {0x90,0x7d,0xff,0xe4,0xf0,0x75,0x70,0xc4,0x75,0x71,0x7d,0x75} }, +{ 5, 0x11ff, 0, {0x72,0x01,0x12,0x11,0x75} }, +{ 1, 0x1204, 0, {0x22} }, +{ 2, 0x1205, 0, {0x7a,0x04} }, +{ 3, 0x1207, 0, {0xba,0x40,0x00} }, +{ 2, 0x120a, 0, {0x50,0x36} }, +{ 14, 0x120c, 0, {0xea,0x24,0xc0,0xf5,0x82,0xe4,0x34,0x7d,0xf5,0x83,0xe0,0xfb,0x7c,0x00} }, +{ 3, 0x121a, 0, {0xbc,0x08,0x00} }, +{ 2, 0x121d, 0, {0x50,0x20} }, +{ 6, 0x121f, 0, {0x8b,0x05,0xed,0x30,0xe7,0x0b} }, +{ 11, 0x1225, 0, {0x90,0x7f,0x96,0x74,0x42,0xf0,0x74,0xc3,0xf0,0x80,0x08} }, +{ 8, 0x1230, 0, {0x90,0x7f,0x96,0xe4,0xf0,0x74,0x81,0xf0} }, +{ 7, 0x1238, 0, {0xeb,0x25,0xe0,0xfb,0x0c,0x80,0xdb} }, +{ 3, 0x123f, 0, {0x0a,0x80,0xc5} }, +{ 1, 0x1242, 0, {0x22} }, +{ 4, 0x1243, 0, {0x7a,0x00,0x7b,0xef} }, +{ 3, 0x1247, 0, {0xba,0x10,0x00} }, +{ 2, 0x124a, 0, {0x50,0x20} }, +{ 14, 0x124c, 0, {0x74,0x11,0x2b,0xfb,0x24,0x00,0xfc,0xe4,0x34,0x18,0xfd,0x8c,0x82,0x8d} }, +{ 14, 0x125a, 0, {0x83,0xe4,0xf0,0xea,0x24,0x00,0xf5,0x82,0xe4,0x34,0x19,0xf5,0x83,0xe4} }, +{ 4, 0x1268, 0, {0xf0,0x0a,0x80,0xdb} }, +{ 1, 0x126c, 0, {0x22} }, +{ 14, 0x126d, 0, {0x74,0xf8,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0xe4,0xf0} }, +{ 14, 0x127b, 0, {0x74,0xf9,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0xe4,0xf0} }, +{ 14, 0x1289, 0, {0x74,0xfa,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0xe4,0xf0} }, +{ 14, 0x1297, 0, {0x74,0xfb,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0xe4,0xf0} }, +{ 14, 0x12a5, 0, {0x74,0xff,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0xe4,0xf0} }, +{ 1, 0x12b3, 0, {0x22} }, +{ 14, 0x12b4, 0, {0x12,0x03,0xcb,0x12,0x12,0x6d,0x7a,0xc0,0x7b,0x87,0x7c,0x01,0x74,0x01} }, +{ 14, 0x12c2, 0, {0x2a,0xfd,0xe4,0x3b,0xfe,0x8c,0x07,0x8a,0x82,0x8b,0x83,0x8c,0xf0,0x74} }, +{ 14, 0x12d0, 0, {0x01,0x12,0x14,0xbf,0x2d,0xfa,0xe4,0x3e,0xfb,0x8f,0x04,0x8d,0x82,0x8e} }, +{ 14, 0x12de, 0, {0x83,0x8f,0xf0,0x74,0x06,0x12,0x14,0xbf,0x74,0x01,0x2a,0xfd,0xe4,0x3b} }, +{ 14, 0x12ec, 0, {0xfe,0x8c,0x07,0x8a,0x82,0x8b,0x83,0x8c,0xf0,0xe4,0x12,0x14,0xbf,0x74} }, +{ 14, 0x12fa, 0, {0x01,0x2d,0xfa,0xe4,0x3e,0xfb,0x8f,0x04,0x8d,0x82,0x8e,0x83,0x8f,0xf0} }, +{ 14, 0x1308, 0, {0x74,0x0b,0x12,0x14,0xbf,0x74,0x01,0x2a,0xfd,0xe4,0x3b,0xfe,0x8c,0x07} }, +{ 14, 0x1316, 0, {0x8a,0x82,0x8b,0x83,0x8c,0xf0,0x74,0x08,0x12,0x14,0xbf,0x74,0x01,0x2d} }, +{ 14, 0x1324, 0, {0xfa,0xe4,0x3e,0xfb,0x8f,0x04,0x8d,0x82,0x8e,0x83,0x8f,0xf0,0x74,0x01} }, +{ 14, 0x1332, 0, {0x12,0x14,0xbf,0x2a,0xfd,0xe4,0x3b,0xfe,0x8c,0x07,0x8a,0x82,0x8b,0x83} }, +{ 14, 0x1340, 0, {0x8c,0xf0,0xe4,0x12,0x14,0xbf,0x74,0x01,0x2d,0xfa,0xe4,0x3e,0xfb,0x8f} }, +{ 14, 0x134e, 0, {0x04,0x8d,0x82,0x8e,0x83,0x8f,0xf0,0x74,0x03,0x12,0x14,0xbf,0x7d,0x00} }, +{ 3, 0x135c, 0, {0xbd,0x06,0x00} }, +{ 2, 0x135f, 0, {0x50,0x12} }, +{ 11, 0x1361, 0, {0x8a,0x82,0x8b,0x83,0x8c,0xf0,0x0a,0xba,0x00,0x01,0x0b} }, +{ 7, 0x136c, 0, {0xe4,0x12,0x14,0xbf,0x0d,0x80,0xe9} }, +{ 13, 0x1373, 0, {0x8a,0x82,0x8b,0x83,0x8c,0xf0,0xe5,0x74,0x12,0x14,0xbf,0x74,0xf9} }, +{ 14, 0x1380, 0, {0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0x74,0x0f,0xf0,0x74} }, +{ 14, 0x138e, 0, {0xfe,0x24,0x00,0xf5,0x82,0x74,0x03,0x34,0x84,0xf5,0x83,0x74,0x01,0xf0} }, +{ 6, 0x139c, 0, {0x12,0x03,0xe1,0x12,0x04,0xf7} }, +{ 1, 0x13a2, 0, {0x22} }, +{ 13, 0x13a3, 0, {0x90,0x7d,0xc1,0xe0,0xfa,0x24,0x00,0xfb,0xe4,0x34,0x19,0xfc,0x90} }, +{ 14, 0x13b0, 0, {0x7d,0xc2,0xe0,0xfd,0x8b,0x82,0x8c,0x83,0xf0,0x75,0xf0,0x11,0xea,0xa4} }, +{ 3, 0x13be, 0, {0xfa,0x7b,0x00} }, +{ 3, 0x13c1, 0, {0xbb,0x10,0x00} }, +{ 2, 0x13c4, 0, {0x50,0x24} }, +{ 14, 0x13c6, 0, {0xea,0x24,0x00,0xfc,0xe4,0x34,0x18,0xfd,0xeb,0x2c,0xfc,0xe4,0x3d,0xfd} }, +{ 14, 0x13d4, 0, {0x74,0x04,0x2b,0x24,0xc0,0xf5,0x82,0xe4,0x34,0x7d,0xf5,0x83,0xe0,0xfe} }, +{ 8, 0x13e2, 0, {0x8c,0x82,0x8d,0x83,0xf0,0x0b,0x80,0xd7} }, +{ 14, 0x13ea, 0, {0xea,0x24,0x00,0xfa,0xe4,0x34,0x18,0xfb,0x74,0x10,0x2a,0xf5,0x82,0xe4} }, +{ 5, 0x13f8, 0, {0x3b,0xf5,0x83,0xe4,0xf0} }, +{ 1, 0x13fd, 0, {0x22} }, +{ 4, 0x13fe, 0, {0xe5,0x76,0x60,0x02} }, +{ 2, 0x1402, 0, {0x80,0x16} }, +{ 12, 0x1404, 0, {0x74,0x0f,0x55,0x75,0xfa,0x8a,0x75,0x24,0x00,0xf5,0x82,0xe4} }, +{ 10, 0x1410, 0, {0x34,0x19,0xf5,0x83,0xe0,0xf5,0x74,0x12,0x12,0xb4} }, +{ 10, 0x141a, 0, {0x12,0x10,0xca,0x75,0x6e,0x00,0x75,0x6f,0x00,0x12} }, +{ 6, 0x1424, 0, {0x11,0x44,0x75,0x70,0xb9,0x75} }, +{ 6, 0x142a, 0, {0x71,0x14,0x75,0x72,0x02,0x12} }, +{ 11, 0x1430, 0, {0x11,0x75,0xe5,0x76,0xb4,0x02,0x04,0x74,0x01,0x80,0x01} }, +{ 1, 0x143b, 0, {0xe4} }, +{ 3, 0x143c, 0, {0xfa,0x70,0x0f} }, +{ 12, 0x143f, 0, {0x74,0x01,0x25,0x75,0xf5,0x73,0xc0,0x02,0x12,0x11,0xa0,0xd0} }, +{ 3, 0x144b, 0, {0x02,0x80,0x0a} }, +{ 10, 0x144e, 0, {0x85,0x75,0x73,0xc0,0x02,0x12,0x11,0xa0,0xd0,0x02} }, +{ 12, 0x1458, 0, {0x75,0x6e,0x00,0x75,0x6f,0x01,0xc0,0x02,0x12,0x11,0x44,0xd0} }, +{ 4, 0x1464, 0, {0x02,0xea,0x70,0x1a} }, +{ 13, 0x1468, 0, {0x75,0xf0,0x11,0xe5,0x75,0xa4,0xfa,0x24,0x00,0xfa,0xe4,0x34,0x18} }, +{ 9, 0x1475, 0, {0xfb,0x8a,0x70,0x8b,0x71,0x75,0x72,0x01,0x12} }, +{ 4, 0x147e, 0, {0x11,0x75,0x80,0x36} }, +{ 2, 0x1482, 0, {0x7a,0x00} }, +{ 3, 0x1484, 0, {0xba,0x10,0x00} }, +{ 2, 0x1487, 0, {0x50,0x2f} }, +{ 13, 0x1489, 0, {0xea,0x24,0x00,0xf5,0x82,0xe4,0x34,0x19,0xf5,0x83,0xe0,0xfb,0xe5} }, +{ 4, 0x1496, 0, {0x75,0xb5,0x03,0x1b} }, +{ 14, 0x149a, 0, {0x75,0xf0,0x11,0xea,0xa4,0xfb,0x24,0x00,0xfb,0xe4,0x34,0x18,0xfc,0x8b} }, +{ 9, 0x14a8, 0, {0x70,0x8c,0x71,0x75,0x72,0x01,0xc0,0x02,0x12} }, +{ 4, 0x14b1, 0, {0x11,0x75,0xd0,0x02} }, +{ 3, 0x14b5, 0, {0x0a,0x80,0xcc} }, +{ 1, 0x14b8, 0, {0x22} }, +{ 6, 0x14b9, 0, {0x50,0x72,0x6f,0x67,0x20,0x00} }, +{ 14, 0x14bf, 0, {0xc8,0xc0,0xe0,0xc8,0xc0,0xe0,0xe5,0xf0,0x60,0x0b,0x14,0x60,0x0f,0x14} }, +{ 7, 0x14cd, 0, {0x60,0x11,0x14,0x60,0x12,0x80,0x15} }, +{ 7, 0x14d4, 0, {0xd0,0xe0,0xa8,0x82,0xf6,0x80,0x0e} }, +{ 5, 0x14db, 0, {0xd0,0xe0,0xf0,0x80,0x09} }, +{ 4, 0x14e0, 0, {0xd0,0xe0,0x80,0x05} }, +{ 5, 0x14e4, 0, {0xd0,0xe0,0xa8,0x82,0xf2} }, +{ 4, 0x14e9, 0, {0xc8,0xd0,0xe0,0xc8} }, +{ 1, 0x14ed, 0, {0x22} }, +{ 14, 0x14ee, 0, {0xc8,0xc0,0xe0,0xe5,0xf0,0x60,0x0d,0x14,0x60,0x0f,0x14,0x60,0x0f,0x14} }, +{ 6, 0x14fc, 0, {0x60,0x10,0x74,0xff,0x80,0x0f} }, +{ 5, 0x1502, 0, {0xa8,0x82,0xe6,0x80,0x0a} }, +{ 3, 0x1507, 0, {0xe0,0x80,0x07} }, +{ 4, 0x150a, 0, {0xe4,0x93,0x80,0x03} }, +{ 3, 0x150e, 0, {0xa8,0x82,0xe2} }, +{ 4, 0x1511, 0, {0xf8,0xd0,0xe0,0xc8} }, +{ 1, 0x1515, 0, {0x22} }, +{ 0, 0x0000, 1, {0} } + +}; + +static unsigned char bitstream[] = { + +0x00,0x09,0x0F,0xF0,0x0F,0xF0,0x0F,0xF0, 0x0F,0xF0,0x00,0x00,0x01,0x61,0x00,0x0D, +0x64,0x61,0x62,0x75,0x73,0x62,0x74,0x72, 0x2E,0x6E,0x63,0x64,0x00,0x62,0x00,0x0B, +0x73,0x31,0x30,0x78,0x6C,0x76,0x71,0x31, 0x30,0x30,0x00,0x63,0x00,0x0B,0x31,0x39, +0x39,0x39,0x2F,0x30,0x39,0x2F,0x32,0x34, 0x00,0x64,0x00,0x09,0x31,0x30,0x3A,0x34, +0x32,0x3A,0x34,0x36,0x00,0x65,0x00,0x00, 0x2E,0xC0,0xFF,0x20,0x17,0x5F,0x9F,0x5B, +0xFE,0xFB,0xBB,0xB7,0xBB,0xBB,0xFB,0xBF, 0xAF,0xEF,0xFB,0xDF,0xB7,0xFB,0xFB,0x7F, +0xBF,0xB7,0xEF,0xF2,0xFF,0xFB,0xFE,0xFF, 0xFF,0xEF,0xFF,0xFE,0xFF,0xBF,0xFF,0xFF, +0xFF,0xFF,0xAF,0xFF,0xFA,0xFF,0xFF,0xFF, 0xC9,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFB,0xFF,0xA3,0xFF,0xFB, +0xFE,0xFF,0xBF,0xEF,0xE3,0xFE,0xFF,0xBF, 0xE3,0xFE,0xFF,0xBF,0x6F,0xFB,0xF6,0xFF, +0xBF,0xFF,0x47,0xFF,0xFF,0x9F,0xEE,0xF9, 0xFE,0xCF,0x9F,0xEF,0xFB,0xCF,0x9B,0xEE, +0xF8,0xFE,0xEF,0x8F,0xEE,0xFB,0xFE,0x0B, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xBF,0xFF,0xFF,0xFB,0xFF,0xFF, 0xBF,0xFF,0xFF,0xFC,0x17,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFF,0x7F, 0xFF,0xFF,0xFB,0xFF,0xFF,0x7F,0xFF,0xFF, +0xFC,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFB,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0x5F,0xFF, 0xFF,0xFD,0xFF,0xFF,0xDB,0xFF,0xFD,0xFF, +0x77,0xFF,0xFD,0xFF,0xFF,0xDF,0xFE,0xFD, 0xFF,0xFF,0xF2,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFD,0xFF,0xFF,0xFF,0xFD,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE1, +0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xE3,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF, +0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x67,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0x7F,0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFF, 0xFF,0xFF,0xDF,0xFF,0xFF,0xFF,0x2F,0xFF, +0xF3,0xFD,0xFF,0x7F,0xDE,0xF7,0xFD,0xFF, 0x7F,0xF7,0x7D,0xFF,0x7F,0xDF,0xF7,0xBD, +0xFF,0x7F,0xFF,0x1F,0xFF,0xEF,0xFB,0xFE, 0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xEF,0xFB, +0xFE,0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xFF, 0x3F,0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x7F, +0x9F,0xE7,0xFA,0x7F,0x9F,0xE7,0xF9,0xFE, 0x7F,0x9F,0xE7,0xFF,0xFC,0x7F,0xBF,0xBF, +0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB,0xB7, 0xBF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB, +0xFF,0xE0,0xFD,0xF9,0xFE,0x7F,0x9F,0xE7, 0xF9,0xFE,0x7F,0x9D,0xF9,0xFE,0x7D,0x9D, +0xE7,0xF9,0xFE,0x7F,0x9F,0xED,0xED,0xFF, 0xFD,0xFF,0x7F,0xDF,0xF7,0xFD,0xFF,0x7F, +0xDF,0xFD,0xFF,0x7F,0xDF,0xF7,0xFD,0xFF, 0x7F,0xDF,0xFF,0x9B,0xFF,0xEF,0xFB,0xFE, +0xFB,0xBF,0xEF,0xBB,0xFE,0xFF,0xAF,0xBB, 0xBE,0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xFF, +0xB7,0xBF,0xDB,0xF6,0xBD,0xBF,0x6B,0xDB, 0xF6,0xF9,0xBF,0x5B,0xD6,0xF9,0xBF,0x6F, +0xDB,0xF6,0xFD,0xBF,0xFF,0x0E,0xFF,0xFF, 0xFF,0xFF,0x5F,0xFF,0xF7,0xFF,0xFF,0x7F, +0xF7,0xBD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xDF,0x9F,0xFF,0xFF,0xFF,0xFE,0xFF, +0xFF,0xEF,0xFE,0xFE,0xFF,0xFF,0x77,0xFF, 0xFB,0xFB,0xFF,0xFF,0xFF,0xFF,0xF8,0x3F, +0xFF,0xFD,0xFF,0xFF,0xFF,0xFD,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xF4,0x7F,0xFF,0xFE,0xFD, 0xBE,0xFF,0xDF,0xFE,0xFF,0xFF,0xEF,0x7F, +0xFF,0xCF,0xFF,0xCF,0xFF,0xFF,0xFF,0xDF, 0xE6,0xFF,0xFF,0x7F,0xDF,0xF7,0xDD,0x7F, +0x7F,0xDF,0xF7,0xFF,0x7F,0xDF,0xD7,0xFD, 0xFF,0x7F,0xDF,0xF7,0xFF,0xCD,0xFF,0xF2, +0xFF,0xFF,0x4F,0x7F,0xF4,0xFF,0xFF,0xFF, 0xE7,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xBB,0xFF,0xEF,0xFF,0xFE,0xFF, 0xFF,0xFF,0xEF,0xFF,0xFF,0xEF,0xFF,0xFB, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x65, 0xEF,0xFF,0xFF,0x7F,0xFF,0xFD,0xEF,0xFF, +0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFE,0xCF,0xDF,0xFE,0xFF, +0xFF,0xFB,0xFF,0xFF,0xFF,0xFF,0xF3,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFE,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xBF,0xFF, 0xFF,0xFF,0xE3,0x7F,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xEF,0xEB,0xFF,0xFE,0xBF,0xFF, 0xEB,0xFF,0xFC,0x7F,0xFF,0xFF,0xFF,0xEE, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDD,0xFF, 0xD6,0xFF,0xFD,0xBF,0xFF,0xFB,0xFF,0xFE, +0xFD,0xFF,0xFF,0xFD,0xEF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xDE,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xBF,0xFF,0xFD,0xFF,0x7F,0xBF, 0xFF,0x5F,0xDF,0xFF,0xFF,0xBF,0x77,0xFF, +0xFF,0xFF,0x7F,0xD7,0xFF,0xFF,0xFF,0xFF, 0xFF,0xC3,0xFF,0xFF,0xFF,0xFF,0xDF,0xEF, +0xFF,0xFF,0xFE,0xFB,0xFF,0xFF,0xDF,0xBF, 0xFF,0xFF,0xFF,0xFF,0xED,0xFF,0xB7,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xAF,0x7F,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xDF,0xBF,0xDF,0xF3,0xFD,0xFB,0xFF,0x5B, +0xFD,0xFF,0xBF,0xEF,0xF7,0xFF,0xFF,0x7D, 0xFF,0xFF,0xFF,0xFF,0xF8,0x3B,0xFF,0xBF, +0x6F,0xFF,0xFE,0xFF,0xBF,0xFF,0xEB,0x7D, 0xFF,0xEF,0xFB,0xFE,0xFF,0xFF,0xFF,0xFF, +0xFF,0xF2,0x7F,0xFC,0xFF,0x3F,0xDF,0xED, 0xFE,0xFF,0xFF,0xFF,0xFF,0xEF,0x5F,0xF7, +0xB5,0xFF,0xEF,0xFF,0xFF,0xFF,0xE0,0x3F, 0x9F,0x9E,0xFF,0xFF,0xEF,0xFF,0xDF,0xFF, +0xBF,0x5F,0xBF,0xCF,0xF3,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0x69,0xAF,0x33,0xFD,0xFF, +0xFB,0xFF,0xFF,0xFF,0xFF,0xFC,0xFF,0x7F, 0xD9,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xF5, +0xA3,0xDF,0x6E,0xDE,0xFF,0xFF,0xBD,0xFF, 0xFF,0xFE,0xFF,0xFF,0xFF,0xFE,0xE7,0xFD, +0xFF,0xFF,0xFF,0xF9,0xEF,0xC6,0xFE,0xB7, 0xAD,0xE5,0xF9,0xFF,0xFF,0xFF,0xCF,0xFF, +0xFF,0xFF,0xCD,0xFB,0x7F,0xFF,0xFF,0xFF, 0xF9,0xF6,0x0F,0xDF,0xEC,0xCF,0x7F,0xFF, +0xFB,0x7F,0xFF,0xFF,0xFF,0xFD,0xFF,0xFE, 0xF9,0xFD,0x7F,0xFF,0x7F,0xFF,0xF9,0x5B, +0xFF,0x73,0xDC,0xFD,0x7B,0xDF,0xFF,0xFF, 0xFF,0x7B,0xFF,0xFF,0xF7,0x53,0xD6,0xFF, +0xFF,0xFF,0xFF,0xD8,0x9F,0xFE,0xFF,0xEF, 0x7F,0xEE,0xFF,0xFF,0xFF,0xFB,0xED,0xED, +0xFD,0xFF,0xFE,0xFF,0xFF,0xFB,0x7F,0xFF, 0xE2,0x7F,0xFF,0x6F,0xD8,0x57,0xF7,0xFF, +0xFF,0xFF,0xDF,0xFF,0xE8,0xFF,0xFF,0xFD, 0xFF,0xFF,0xFC,0x7F,0xFF,0xE4,0xFF,0xFB, +0xEF,0xFB,0xFE,0xDF,0xB7,0xED,0xFF,0xFE, 0xDF,0x7F,0xFF,0xFE,0x7F,0xB7,0xFF,0xFF, +0xFF,0xFF,0x89,0xFF,0xFF,0xCF,0xF3,0xFE, 0x7F,0xFF,0xEF,0xFF,0xFE,0x7E,0x7F,0xFB, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF1, 0xFF,0xEB,0x7A,0xD5,0xBF,0x6F,0xDB,0xBE, +0xFD,0xB7,0xD8,0xF6,0xE5,0xBF,0x6F,0xFB, 0xFE,0xF5,0xBD,0x7E,0x06,0xFF,0xDF,0xF7, +0xFB,0xF6,0xFF,0x3F,0xFF,0xDB,0xFF,0xFF, 0x6F,0xFB,0xF7,0xFF,0xFF,0xFF,0xFB,0xFE, +0xF7,0xAF,0xFF,0xB7,0xED,0xEF,0xF7,0xFE, 0xFF,0xFF,0xDF,0xFF,0xFE,0xFF,0xEF,0xFF, +0xFF,0xFF,0xFF,0xBF,0xF7,0xFC,0x1F,0xEE, 0xFB,0xFE,0xBD,0xFF,0x7F,0x5F,0xD7,0xFD, +0xFB,0x43,0xFF,0xFF,0xFD,0xFF,0x5F,0xFF, 0xF7,0xFF,0xF9,0x3F,0xFF,0xCF,0xF3,0xFD, +0xF7,0x7E,0xEF,0xA7,0xF9,0xFE,0x8F,0xA7, 0xE9,0xF3,0x7E,0x9F,0xFB,0xF8,0xFF,0xFF, +0x3F,0xFD,0x7F,0x5F,0xDF,0xFD,0xFF,0xFF, 0x5F,0xFF,0xFD,0x5F,0xFF,0xFF,0x7F,0xFD, +0x7F,0xFD,0x9F,0xFF,0xE0,0xFF,0xFA,0xF8, 0xBE,0x6F,0x9F,0xE6,0xF8,0xBE,0x3F,0x9A, +0xF9,0xBE,0x6F,0x9F,0xE2,0xF9,0xFE,0x6F, 0x9F,0xF9,0xFF,0xF5,0xFD,0x7F,0xCF,0xDF, +0xFD,0xFD,0x7F,0xFF,0xF5,0xFF,0xFF,0xFF, 0xF7,0xF5,0xFD,0x0F,0xDB,0xFF,0xD3,0xFF, +0xEB,0xFA,0xFF,0xFF,0xBF,0xFF,0xFA,0xFF, 0xFF,0xCB,0xFB,0xFE,0xFF,0xFF,0xEB,0xFA, +0xFE,0xFF,0xFF,0xB7,0xFF,0xFF,0xFF,0xFF, 0xBF,0xFF,0xDF,0xF5,0xFF,0xFF,0xD7,0xFF, +0xFF,0xFF,0xDF,0xD7,0xF5,0xFF,0x7F,0xFE, 0x4F,0xFF,0xFD,0xFF,0x7F,0x7F,0xFF,0xAD, +0xEB,0xFB,0xFF,0xAD,0xFF,0xFF,0xFF,0xFF, 0xAF,0xEB,0xFB,0xFF,0xFC,0x0D,0xFF,0xFF, +0xDF,0xD2,0xFD,0xFF,0xFF,0xFD,0xF6,0xFF, 0xFF,0x7F,0xFF,0xFF,0x1F,0xFF,0xFF,0xFF, +0xFF,0xFB,0x3F,0x7D,0xEB,0x32,0xFE,0xBF, 0x2F,0xEB,0xFA,0xAE,0xBD,0xE0,0xFA,0x7E, +0xBF,0xAD,0xEB,0xFA,0xFE,0xBF,0xF5,0x7F, 0xFF,0xDE,0xFE,0xE3,0xFB,0xFF,0xFF,0xFF, +0xDF,0xEF,0x4F,0xDF,0xFF,0x7F,0xDF,0xFF, 0xF7,0xFF,0xFF,0xF8,0x7F,0xFF,0xFF,0xEF, +0xFB,0xFF,0xFF,0xFF,0xEF,0xFF,0xFF,0xDF, 0xED,0xFB,0xDF,0xFF,0xBF,0xFF,0xFF,0xFF, +0x81,0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF, 0xFF,0xFF,0xFE,0xDD,0xFE,0xEF,0xFD,0xFF, +0xFF,0xFB,0xFE,0xF7,0xFF,0x93,0xFD,0xFB, 0x7E,0xFF,0xFE,0x87,0xE9,0xFF,0x7F,0xB3, +0x9F,0xFE,0xFE,0xFF,0xAF,0xFD,0xFE,0x7E, 0x3F,0xFE,0x67,0xFF,0xFF,0xF7,0xFF,0xFF, +0xFC,0xF7,0xDF,0xFD,0xFF,0x7F,0xFF,0xFF, 0x7F,0x6D,0xFF,0xFF,0xFE,0xFF,0xFF,0x2F, +0xFF,0xBF,0xFF,0xFF,0xEE,0xFF,0xBE,0xFF, 0xFF,0xFE,0xFF,0xEF,0xFF,0xFF,0xFE,0xFF, +0xEF,0xFF,0xFF,0xFA,0x5F,0xFF,0xFF,0xFB, 0xFF,0xFF,0xEF,0xFF,0xFB,0xFE,0xFD,0xFF, +0xFE,0xFF,0xFB,0xFF,0xFF,0xFF,0x7F,0xFF, 0xFE,0xBF,0xDF,0xFF,0xFB,0xFF,0xFF,0xF7, +0xFC,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F, 0xFF,0xFF,0xFF,0xFF,0xFF,0xF2,0x7F,0xFF, +0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFF,0xFF, 0xF3,0xFF,0xFF,0xFF,0xEF,0xFB,0xFF,0xFF, +0xFF,0xDF,0xE2,0xFF,0xFF,0xFB,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFB,0xE7,0xFF,0xFD, +0xFF,0xFF,0xFF,0xBF,0xFF,0xFF,0xFF,0xED, 0xEF,0xFD,0xFF,0xFF,0xDF,0xD7,0xF5,0xFD, +0x7F,0x5D,0xFD,0xFF,0x7F,0xDF,0x97,0xF4, 0xFD,0x7B,0x5F,0xFF,0xC9,0xFF,0xFB,0xFE, +0xFF,0xBF,0xFF,0x5F,0xFF,0xFF,0xF7,0xFF, 0xEF,0xFD,0xFF,0xEF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xF7,0xFF,0xD7,0xFD,0x7D,0x7F,0xFF, 0xFF,0xFF,0xFF,0xEF,0xDF,0xF7,0xFD,0xFF, +0xBB,0xFF,0xFF,0x7F,0xFF,0xFE,0xE3,0xFF, 0xF9,0xFE,0x7F,0xBF,0xEF,0xFB,0xFE,0xFF, +0xBF,0xF9,0xFE,0xFF,0x9F,0xEF,0xF9,0xFE, 0xFF,0xBF,0xF3,0xDA,0xFF,0x37,0xCD,0xF3, +0x7C,0xDF,0x37,0xCD,0xF3,0x7F,0x37,0xCD, 0xF3,0x7C,0xDF,0x37,0xCC,0xF3,0x7F,0x5A, +0xBD,0xF6,0xFD,0xBF,0x6F,0xDB,0xF6,0xFD, 0xBF,0x6F,0xDE,0xFD,0xBF,0x6F,0xDB,0xF6, +0xFD,0xBF,0x6F,0xFE,0xF1,0x6F,0xEB,0x7A, 0xDE,0xB7,0xAD,0xEB,0x7A,0xDE,0xB7,0xAF, +0x7A,0xDE,0xB7,0xAD,0xEB,0x7A,0xDE,0xB7, 0xFF,0x7E,0xFF,0xFE,0xCD,0xB3,0x6C,0xDB, +0x36,0xCD,0xB3,0x6C,0xDE,0xCD,0xB3,0x6C, 0xDB,0x36,0xCD,0xB3,0x6C,0xDF,0xC9,0xBF, +0xF7,0xBD,0xEF,0x7A,0x9E,0xA7,0xA9,0xEA, 0x7A,0xB7,0xBD,0xEA,0x7B,0xDE,0xA7,0xBD, +0xCA,0x72,0x8D,0x91,0xFF,0xEF,0xFB,0xFE, 0xFF,0xBF,0xEF,0xFB,0xFE,0xF7,0xEF,0xFB, +0xFE,0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xFE, 0x87,0xFF,0xF6,0xFD,0xBF,0x6F,0xDB,0xF6, +0xFD,0xBF,0x6F,0xF6,0xFD,0xBF,0x6F,0xDB, 0xF6,0xFD,0xBF,0x6F,0xFE,0x4F,0xFF,0xBF, +0xEF,0xBB,0xEE,0xFB,0xBE,0xEF,0xBB,0xEF, 0xBE,0xEF,0xBB,0xEE,0xFB,0xBE,0xEF,0xBB, +0xEF,0xFC,0x5F,0xFF,0xFF,0xFF,0x3F,0xCF, 0xF3,0xFC,0xFF,0x3F,0xCF,0xFC,0xFF,0x3F, +0xCF,0xF3,0xFC,0xFF,0x3F,0xCF,0xFD,0x9F, 0xFE,0xBF,0xAF,0xEB,0xFA,0xFE,0xBF,0xAF, +0xEB,0xFE,0xBF,0xAF,0xEB,0xFA,0xFE,0xBF, 0xAF,0xEB,0xFF,0xE1,0x6F,0xFD,0xFF,0x7F, +0xDF,0xF7,0xFD,0xFF,0x7F,0xDF,0xFD,0xFF, 0x7F,0xDF,0xF7,0xFD,0xFF,0x7F,0xDF,0xFF, +0x7A,0xBF,0xFB,0xFE,0xDF,0xB7,0xED,0xFB, 0x7E,0xDF,0xB7,0xFB,0x7E,0xDF,0xB7,0xED, +0xFB,0x7E,0xDF,0xB7,0xFF,0xC9,0xFF,0xFF, 0xBF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB, +0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEE, 0xFB,0xFE,0xBB,0xFF,0xFE,0xFF,0xBF,0xEF, +0xFB,0xFE,0xFF,0xBF,0xEF,0xFE,0xFF,0xBF, 0xEF,0xFB,0xFE,0xFF,0x3F,0xCF,0xFF,0xE7, +0xFE,0xFF,0xF5,0xFD,0x77,0x5D,0xD7,0x35, 0xDD,0x77,0xD7,0xF5,0xCD,0x7B,0x5D,0xD7, +0xF5,0xDD,0x77,0xFE,0x27,0xFF,0xFF,0x8B, 0xE2,0xF8,0xBE,0x2F,0x8B,0xE2,0xF9,0xAF, +0x8B,0xE2,0xF8,0xBE,0x2F,0x8B,0xE2,0xF9, 0xFE,0x1F,0xFF,0x5F,0xD7,0xF5,0xFD,0x7F, +0x5F,0xD7,0xF5,0xFF,0x5F,0xD7,0xF5,0xFD, 0x7F,0x5F,0xD7,0xF5,0xFF,0xFA,0x3F,0xFE, +0xBF,0xAF,0xEB,0xFA,0xFE,0xBF,0xAF,0xEB, 0xEC,0xBF,0xAF,0xEB,0xFA,0xFE,0xBF,0xAF, +0xEB,0xFF,0xFE,0x7F,0xFD,0x7F,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6, 0xFF,0xFA,0xDF,0xF7,0xFD,0xFF,0x7F,0xDF, +0xF7,0xFC,0xFF,0xDF,0xF7,0xFD,0xFF,0x7F, 0xDF,0xF7,0xFD,0xFF,0xF5,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0x02,0xFF,0xFE,0xBF,0xAB,0xEB,0xFA, 0xBE,0xBF,0x23,0xEB,0xDE,0x1F,0xAF,0xEA, +0xFA,0xFE,0xAF,0xAF,0xEB,0xFD,0x97,0xFF, 0xF3,0xFC,0x7B,0x1F,0xCF,0xF1,0xFC,0x7F, +0x1F,0xF1,0xFC,0x77,0x1F,0xCD,0xF1,0xFC, 0xFF,0x1F,0xFE,0x87,0xFF,0xAF,0xEF,0xFA, +0xFE,0xFF,0xAF,0xEF,0xFA,0xFD,0xBF,0x2B, 0xFB,0x7E,0xBF,0xBF,0xEB,0xFB,0xFB,0xFB, +0xDF,0xFF,0xFB,0xF7,0xFF,0xFF,0x7F,0xF7, 0xF7,0xFF,0xFD,0xDF,0xFE,0xFC,0xDF,0xFF, +0xDF,0xFF,0xFD,0xFF,0xDA,0xBF,0xFF,0xBB, 0xEF,0xFB,0xF9,0xFF,0xBE,0xEF,0xFB,0xFB, +0xBF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB, 0xFF,0xF7,0x7F,0xFD,0xD7,0xFF,0xFF,0x7F, +0xFF,0xFF,0xFF,0xFE,0xF7,0xFF,0xFE,0xFF, 0xF7,0xFF,0xFF,0x7F,0xFF,0xFF,0xEC,0xFF, +0xFF,0xFE,0xDF,0xBF,0xFF,0xFB,0xFE,0xFF, 0xBB,0x68,0xAE,0x1F,0xAE,0xFB,0xFB,0xFF, +0xFF,0xBF,0xFF,0xD5,0xFF,0x7F,0xFF,0xFF, 0xF7,0xFE,0xFE,0xFF,0xBF,0xEF,0x9F,0xFD, +0x7F,0xFF,0xCB,0xFF,0xFF,0xDF,0xFF,0xFF, 0xBB,0xF7,0xBF,0xFF,0xFF,0xFF,0xFF,0xDF, +0xFF,0xBF,0xFB,0xFF,0xFF,0xFF,0xDE,0x3F, 0xFF,0xFF,0xFF,0xFF,0xFF,0xA7,0xFF,0xFF, +0xFF,0xFF,0xEF,0xFF,0x7F,0xFB,0xFD,0xFB, 0x7F,0xFF,0xFF,0xFF,0xFF,0xCF,0xF3,0x7C, +0xFF,0x7F,0x8D,0x7F,0xFF,0xFF,0xFF,0xFF, 0xFB,0xFF,0xF7,0xFB,0xFE,0xFD,0xFF,0xFF, +0xFF,0xFF,0xF7,0xFD,0xFF,0x7F,0xFD,0x1F, 0xFD,0xFF,0xFF,0xFF,0xFF,0xBF,0xDF,0xFF, +0xFF,0xFE,0x5C,0xFF,0x6D,0xFF,0x7F,0xAB, 0xE7,0xF1,0xFF,0xFD,0x9F,0xFF,0xFF,0xAD, +0xEB,0x7A,0x3F,0x1F,0xFF,0xFF,0xFE,0xBF, 0xAF,0xF3,0xDE,0xF5,0xFF,0x8F,0xFB,0xDF, +0xE6,0x7F,0xFF,0xDF,0xF3,0xFD,0xFF,0x7E, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xF7,0xF3, +0x7F,0xDF,0xF7,0xEF,0xFF,0xF6,0x3F,0x9F, 0xDF,0xFF,0xFF,0xEE,0xFF,0xFF,0xEF,0xFB, +0xFF,0xFF,0xF9,0xFB,0xFE,0x4F,0xBF,0xEF, 0xBB,0xFF,0x69,0xAF,0xAF,0xFC,0xFF,0x3F, +0xDD,0xFF,0xFC,0xBF,0x8F,0xFF,0xFD,0xF3, 0xBF,0xED,0x9E,0xFC,0xBF,0x6F,0xF5,0xD3, +0xDF,0xFF,0xDB,0xD6,0xF5,0xEF,0xFD,0xFE, 0xFF,0xB9,0xFF,0x1F,0xD2,0xA9,0xAF,0xFF, +0xDB,0xF7,0xBF,0xEF,0x46,0xFF,0xFF,0xAD, 0xEB,0x7A,0xDF,0xEF,0xF7,0xFF,0x7F,0xF7, +0x9F,0xED,0xFF,0x7F,0xFF,0xAD,0xEB,0x7F, 0xF5,0x6F,0xFF,0xFD,0xFB,0xD6,0xF4,0xF7, +0xFB,0xF9,0x7E,0x7F,0xFF,0x5F,0xC2,0xFE, 0xBF,0xFD,0xFB,0x33,0xDF,0xF9,0x5B,0xFF, +0xFF,0xDD,0x67,0x7D,0xCF,0xEF,0xDB,0xEC, 0xFF,0x77,0xDD,0xF7,0xFD,0xFF,0xFF,0xDE, +0xA7,0xBF,0xD4,0x9F,0xFF,0xFF,0xBF,0xEF, 0xFE,0xFF,0xDF,0xEF,0xBB,0xFF,0xFF,0xEF, +0xEB,0xFA,0xFF,0xEF,0xBD,0xFB,0xFF,0xE2, 0x7F,0xFF,0xDF,0xDF,0xF7,0xFD,0xBF,0xBB, +0x73,0xF7,0xFD,0x7F,0xDF,0xDE,0xF7,0xBF, 0xEA,0xDB,0xF6,0xFF,0xD6,0xFF,0xFF,0x66, +0xFF,0xBE,0xFF,0xBF,0x6B,0xD9,0xF6,0xDF, 0xFF,0xFB,0x7E,0x7F,0xB7,0x7E,0xFF,0xFE, +0xFF,0xCD,0xFF,0xFE,0x7F,0xFF,0xFC,0xFD, 0x3F,0xFB,0xFB,0xF7,0xFF,0xFF,0xFB,0xF6, +0x7D,0xFE,0x7F,0xFF,0xFC,0xFF,0xB9,0xFF, 0xF9,0xFA,0xFE,0xBF,0xAF,0x5B,0xD6,0xED, +0xAD,0x7B,0xF6,0xF9,0xBF,0xEF,0xF8,0xFA, 0xFE,0xBF,0xFE,0xE6,0xFF,0xFF,0xF7,0xFD, +0xFF,0x7F,0xBF,0xEF,0xF3,0xFF,0xFF,0x6F, 0xF7,0xFE,0xFF,0xFF,0xF7,0xFD,0xFE,0xF7, +0xEF,0xFF,0xFB,0xEF,0xFB,0x7E,0xDE,0xFE, 0xFF,0xBF,0xFF,0xFE,0xFF,0xFF,0xFB,0xFF, +0xFF,0xEF,0xFB,0x6F,0xFC,0x1F,0xFE,0xE7, 0xFF,0xFF,0xFF,0xEF,0xFF,0xD3,0xB4,0xBB, +0xFF,0xFF,0xFD,0xBF,0x6F,0xE3,0xFE,0xFF, 0xBF,0xFC,0xBF,0xF7,0xCF,0xF7,0xFD,0xFF, +0x2F,0xDF,0xAB,0xEA,0xFF,0xDF,0xE7,0xEA, 0x9A,0xAF,0xEF,0xFB,0xFE,0xFF,0xF5,0x3F, +0xFD,0x7E,0xFF,0xD7,0xF5,0xFB,0xFF,0xFD, 0xF7,0xFF,0x7F,0xFE,0xF7,0xFD,0xFF,0xD7, +0xFF,0xD7,0x7F,0xEE,0x7F,0xFA,0x79,0xFE, 0x2F,0x8B,0xE6,0xF9,0xFE,0x3F,0x9E,0xF9, +0xBE,0x2F,0x0B,0xE7,0xF9,0xFE,0x2F,0x9F, 0xFD,0xFF,0xFE,0x7D,0x7F,0x5F,0xD7,0xFF, +0xFF,0x7F,0xFF,0xFD,0xFF,0x7F,0x5F,0x97, 0xFF,0xFD,0x7F,0x5F,0xFF,0xE3,0xFF,0xFF, +0xFA,0xFE,0xBF,0xAF,0xFB,0xFB,0xFF,0xFF, 0xCF,0xEB,0xFE,0xBF,0xAF,0xFF,0xFA,0xFE, +0xBF,0xFF,0x87,0xFF,0xFF,0xF5,0xFF,0xFF, 0xFF,0xFF,0xFD,0xFF,0x7F,0xFF,0xFF,0xFF, +0xFB,0xFF,0xFF,0xF5,0xFF,0xFF,0xFE,0x0F, 0xFF,0xFD,0xEB,0xFF,0xFF,0xF7,0xFF,0xEF, +0x7B,0xDF,0xFE,0xFF,0xFF,0xDF,0xF7,0xFD, 0xEB,0x7F,0xDF,0xFF,0x5F,0xFF,0xFF,0xFF, +0xFF,0xFD,0xBF,0xFF,0x7E,0xFA,0xBF,0xC7, 0xDB,0xF7,0xBD,0x3F,0xFB,0xFF,0xF6,0xFF, +0xFA,0xAF,0xFF,0xEB,0xFA,0xFE,0x3F,0x2F, 0xEA,0xFA,0x3E,0xAD,0xC9,0xBA,0xF6,0xAD, +0xAF,0xEB,0xFA,0xF6,0xBF,0xFE,0x7F,0xFF, 0xFF,0xFD,0xFF,0xF1,0x7F,0x3F,0xCF,0xF1, +0xEF,0xFF,0x7F,0xFF,0xBC,0xDF,0xDF,0xF7, 0xDD,0xFF,0xE0,0x7F,0xFF,0xFF,0xFE,0xFF, +0xFA,0xEC,0xBB,0x7F,0x5F,0xFF,0xFB,0xEC, 0xFF,0xEF,0xB7,0xFF,0xF7,0xFF,0xFF,0xB5, +0xFF,0xFF,0x7F,0xFF,0xFF,0xFF,0xEE,0xDF, 0x5F,0xDF,0xDE,0xFF,0xAE,0xE7,0x77,0xFF, +0xFF,0xDF,0xF7,0xFF,0xE3,0xFF,0xFA,0xBB, 0xFE,0xFF,0xAF,0xFD,0xFB,0xFE,0xBF,0xAB, +0xF9,0xFE,0xFF,0xBF,0x7F,0xBF,0xFE,0xBD, 0xFE,0xD7,0xFF,0x9F,0xFD,0xFF,0xBE,0xEF, +0xFF,0xEE,0xFD,0xBB,0x5B,0xEF,0xFF,0x7F, 0xEF,0xFF,0xEF,0xFF,0x7F,0xFF,0x4F,0xFF, +0xEF,0xFB,0xBC,0xFC,0xFF,0xFF,0xFF,0xFE, 0xFE,0xFD,0xFA,0xFE,0xFB,0xFF,0xFD,0xF3, +0xFB,0xFF,0xF8,0x5F,0xFF,0xFF,0xD7,0xF5, 0xFD,0xDF,0xEF,0xFF,0xF3,0xDC,0x5F,0xCE, +0xF5,0xBD,0xFF,0xFF,0xD7,0xFF,0xFF,0xF9, 0x3F,0xFF,0xDF,0xF7,0xFF,0xFE,0xFF,0xFD, +0xFF,0xFB,0xFF,0xF7,0xB9,0x7D,0xFE,0xDF, 0xFF,0xFF,0xFF,0xFF,0xF9,0x7F,0xFF,0xFE, +0xFF,0xFF,0x7F,0xFF,0xFE,0xFF,0xFF,0xF7, 0xF6,0xFF,0xBF,0xF1,0xF8,0xFF,0xFF,0xFF, +0xFF,0xE0,0xFF,0xFF,0xFF,0xFF,0xF9,0xFF, 0xFF,0xFF,0xFF,0xFF,0xEF,0xEF,0xFF,0xFF, +0x9B,0xFB,0x7F,0xFF,0xFF,0xFF,0xC1,0xFF, 0xDF,0xFF,0x3F,0x5F,0xD7,0xBF,0xEF,0xBB, +0xDE,0xEE,0xFF,0x7F,0xDF,0xFF,0xFE,0xF5, 0x7F,0xDF,0xFF,0x99,0xFF,0xFF,0xFA,0xFF, +0xBF,0xFD,0xEB,0x7A,0xFF,0xB7,0xFE,0xFE, 0xFF,0xFF,0xEF,0xFF,0xFF,0xFD,0xBF,0xFF, +0x97,0xFF,0xFD,0xF7,0xFF,0x7F,0xF7,0xFF, 0xFF,0xFD,0x5F,0xFE,0xF3,0xF9,0xDF,0xDF, +0xFF,0xFF,0xFC,0xFF,0xFF,0x83,0xFF,0xFF, 0xFE,0xFF,0x9E,0xEC,0xFB,0xEE,0xFF,0x9F, +0xBF,0xEF,0xFF,0xFE,0xED,0x7B,0xFF,0xFF, 0xFF,0xF1,0x5A,0xFF,0xFF,0xFD,0xFF,0x7C, +0x69,0x3B,0xDF,0xFF,0x7F,0x1F,0xDF,0xFF, 0xFD,0xBA,0xFF,0xFF,0xFB,0xFF,0x5B,0xBD, +0xFF,0xFF,0xFF,0xFF,0xD7,0xB6,0xED,0xE9, 0xFF,0xD6,0xBD,0x6F,0x5F,0xFB,0xFF,0xEF, +0xFF,0x5F,0xFE,0xF6,0x6F,0xFF,0xFF,0xFF, 0xFF,0xF7,0xEB,0x7A,0xDF,0xFF,0x9F,0x7F, +0x7F,0xFF,0xB7,0xFF,0xFF,0xFE,0xDF,0xFF, 0x6C,0xFF,0xFB,0xFF,0xBB,0x6F,0xEB,0xFE, +0xCC,0xF7,0xA5,0xFA,0x5C,0xF5,0x75,0xBB, 0xB7,0xDF,0xFE,0x6F,0x5F,0xC5,0xBF,0xFD, +0x7B,0xFE,0xFF,0x95,0xE7,0x29,0xCF,0x4F, 0xF5,0x91,0xEE,0x6B,0xDF,0xEF,0xFD,0x54, +0xF5,0xBD,0xB1,0xFF,0xEF,0xEE,0xFB,0xBE, 0xBF,0xAF,0xFE,0xDE,0xBD,0x6F,0xDA,0xF2, +0xFF,0xAF,0xBE,0xFF,0xFF,0xFD,0x7E,0xA7, 0xFF,0xF7,0xFF,0xBF,0xEF,0x7B,0xF6,0xFD, +0xBD,0x4A,0xF2,0x85,0x85,0xBF,0x5B,0xFE, 0xB5,0xFD,0xFA,0xFF,0x4F,0xFF,0xFE,0xDF, +0xFF,0xED,0xFF,0xBF,0xFF,0xBF,0x7F,0xFE, 0xFF,0xB7,0x6D,0xFF,0xF7,0xBF,0xBF,0xEF, +0xFD,0x1F,0xFF,0xFE,0x7D,0xFF,0x67,0xFF, 0xFF,0xFF,0x3F,0x7F,0xFE,0xBF,0xFF,0xE7, +0xDF,0xE7,0xFF,0xEF,0x6B,0xFC,0x1F,0xFF, 0xBF,0xEF,0xFB,0xFE,0xDE,0xBF,0xAF,0xFA, +0xFF,0xB6,0xEF,0xF9,0xFE,0xFF,0x8F,0xEF, 0xDB,0xEF,0xAB,0x6F,0xFB,0xFE,0xFF,0xFF, +0xEF,0xFD,0xFF,0x7F,0xFF,0xFF,0xDE,0xFF, 0xFF,0xEF,0xFF,0xFF,0xFF,0x3F,0xFF,0x6C, +0xFF,0xBF,0xFB,0xFF,0xFE,0xFF,0xFB,0xFE, 0xDF,0xFF,0xFF,0xEF,0xFF,0xFF,0xBF,0xFF, +0xFF,0xFE,0xFB,0xFF,0xD5,0x7F,0xFF,0xFF, 0xEF,0xFB,0xFF,0xFF,0xBF,0xEF,0x43,0xB5, +0xFD,0x6F,0xCF,0xD6,0xBE,0x3F,0x7F,0xDB, 0xFE,0xC3,0xFF,0xFD,0xFF,0xAF,0xEB,0xFB, +0xFC,0xFF,0x3E,0xEF,0xE8,0xFA,0xBD,0xCD, 0xAA,0xFE,0xFE,0x7D,0xCF,0xFF,0xB7,0xFF, +0xF7,0xFF,0xFF,0xFF,0xFD,0xFF,0x75,0xCD, 0x52,0xD7,0xFD,0xFB,0xF7,0xDD,0xFB,0xEF, +0xEB,0xFF,0xFF,0x4F,0xFF,0xBF,0x9F,0xE7, 0xF9,0xFC,0x7F,0x8B,0xC3,0xF9,0xAF,0x8F, +0xE7,0xE9,0xBE,0x7F,0x9F,0xE6,0xF9,0xFC, 0x5F,0xFF,0xFF,0xF7,0xFD,0xFF,0x7A,0x5F, +0xD7,0xED,0xFF,0xFF,0xD7,0xFF,0xDD,0x7F, 0xE7,0xFF,0xFC,0xFF,0xFC,0x3F,0xFF,0xFF, +0xFF,0xFB,0xFF,0xFE,0xBF,0xAF,0xFF,0xFD, 0xFF,0xEF,0xFF,0xEB,0xFF,0xFF,0xFF,0xFF, +0xFF,0xF7,0x7F,0xFF,0x7F,0xDF,0xFF,0xFD, 0xFD,0x7F,0xFE,0xF7,0xFD,0x7F,0xDF,0xFF, +0xFD,0xFF,0xFF,0xDF,0xFB,0xFF,0xEE,0xFF, 0xFB,0xFF,0xF7,0xFD,0xFF,0x7A,0xDF,0xF5, +0xFD,0xFA,0xDF,0xF7,0xFC,0xFF,0x7F,0xDF, 0xBF,0xED,0xFF,0xC9,0xFF,0xDF,0xFF,0xBF, +0x2F,0xFB,0xFF,0xBC,0xAD,0xFF,0xF7,0xFF, 0xFF,0xEF,0xD3,0xFF,0x7D,0xBF,0x6F,0xFF, +0xFA,0xFF,0xFE,0xBF,0xAE,0xEA,0xFA,0xBE, 0xAD,0xA5,0xEB,0xCE,0xBF,0xA7,0xEB,0x5A, +0xDE,0xBD,0xAF,0x6B,0xFD,0x57,0xFF,0xFF, 0xF4,0x7F,0x1F,0x7F,0xFD,0xFF,0x7F,0x36, +0xF0,0xDF,0x79,0xFF,0xFF,0xFF,0xF7,0xFD, 0xBF,0xFF,0x87,0xFF,0xFB,0xF3,0xFC,0xFF, +0xFF,0xFF,0xFF,0x7E,0xFF,0xBF,0xDF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFD,0xBF,0xF8,0x9F, +0xFF,0xFF,0xFF,0xFF,0xBF,0xFF,0xFF,0xFD, 0xF7,0xFC,0xBD,0xFF,0xFE,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFB,0xF9,0xBF,0xFF,0xFF,0xEB, 0xE2,0xFE,0xFF,0xBF,0xEF,0xA9,0xBA,0x2F, +0xEB,0xF9,0xFE,0x77,0xDF,0xF7,0xFF,0xFF, 0xF9,0x7F,0xFF,0xFF,0x7F,0xEF,0xD7,0xFF, +0xFD,0xFF,0xFB,0xF5,0xFF,0xBF,0x6F,0xDF, 0xFF,0xFF,0xFD,0xFF,0xFF,0xF0,0xFF,0xFF, +0xFF,0x3F,0xCF,0xFF,0xBA,0xEE,0x9B,0xBF, 0xEE,0xD7,0xFE,0xCD,0xEF,0xFF,0xDF,0xBF, +0xFF,0xFF,0xC5,0xFF,0xFF,0xFD,0x7F,0x4F, 0xFD,0xF6,0xD9,0xFF,0x4F,0xD6,0xFD,0xBF, +0x6E,0xFF,0xFF,0xF4,0x7F,0xFF,0x7F,0x8B, 0xFF,0xFF,0xFF,0xFF,0xF7,0xFF,0xF9,0xFE, +0x37,0xFF,0xD9,0xFB,0xF5,0xAF,0xFD,0xFF, 0xFF,0xFB,0xFF,0xFF,0x07,0xFF,0xFF,0xFF, +0xFB,0xF7,0xFF,0xFD,0xFF,0x7C,0xFA,0x7E, 0x4F,0xFC,0xDF,0x1D,0xC7,0xFF,0xFF,0xFF, +0xFF,0xAE,0xFF,0xFF,0xFF,0xFF,0xFD,0xFB, 0xFF,0xFF,0xFE,0xFE,0xFC,0xFF,0x7F,0x7F, +0xBF,0xEF,0xFE,0xFF,0xFF,0xFF,0x5F,0xFD, 0xFF,0xFF,0xFF,0xFD,0x6F,0x5A,0xD7,0x7B, +0xBE,0x5F,0xFE,0x39,0xFF,0xF7,0xFF,0xF7, 0xFD,0xFE,0xAA,0x1F,0xFF,0xFF,0xFF,0xFF, +0xFE,0xFE,0xAB,0xAF,0xFD,0xFE,0xBF,0xFF, 0xF7,0xFF,0x7F,0xFE,0x8F,0xE3,0xFB,0xEE, +0x7F,0xFF,0xFF,0xFF,0xFF,0xEB,0xFB,0xFF, 0xFD,0xBF,0xEF,0xDF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFB,0xE4,0x3F,0xFF,0xDF, 0xFF,0xFF,0xFF,0xFF,0xF3,0xEF,0xBB,0xFB, +0xBF,0xEF,0xBB,0xFF,0xD7,0xBF,0xFF,0xFF, 0xFF,0x29,0xAF,0xF7,0xFF,0xFF,0xFB,0xFF, +0xFB,0xE6,0xFF,0x0F,0xFB,0x3F,0xDF,0x0F, 0xFF,0xAF,0xFF,0xFF,0xFF,0xF5,0xC3,0xDF, +0x5F,0xFF,0xFF,0xFF,0xFE,0x6B,0xCA,0xBE, 0xBC,0xFF,0x9F,0xF2,0xBF,0xFF,0xFE,0xFA, +0xFF,0xFF,0xEF,0x16,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFC,0xDF,0x97,0xFD,0x79,0xFF,0x37, +0xE7,0x7F,0xFF,0xFF,0xB5,0xFF,0xFF,0xF6, 0x2F,0xFF,0xFD,0xFB,0xFE,0xFF,0xFF,0xFD, +0x5F,0x57,0x5F,0xFF,0xDB,0x52,0xDF,0xFF, 0xFD,0xBF,0xFF,0xFF,0xFC,0xDB,0xFF,0x7B, +0xB5,0xFD,0x7F,0xFF,0x71,0x9C,0x6E,0xFF, 0xF6,0x35,0xA5,0x9B,0xFF,0xFF,0xFD,0xFF, +0xFF,0xDB,0x9E,0x7F,0xFE,0xEF,0xFB,0xFF, 0xFF,0xBD,0xEF,0xFF,0xDE,0xB7,0xF9,0x4B, +0xFF,0xF5,0xEF,0xFF,0xFF,0xFF,0xE8,0x7E, 0xFF,0xEA,0xDF,0xF7,0xFF,0xFD,0x69,0x5B, +0xFC,0x9F,0xEF,0x78,0xD6,0xFF,0xEB,0xEF, 0xFF,0xFF,0xFF,0xE8,0xFF,0xFF,0xED,0xFF, +0xFF,0xFF,0xFF,0xE3,0xF9,0xF6,0xBF,0xFF, 0xFF,0xFE,0xDF,0xFF,0x7F,0xFF,0xFF,0xFF, +0xD1,0xFF,0xFF,0xE7,0xFF,0xFF,0xFF,0xFF, 0xE7,0xF9,0xFF,0xBF,0x7F,0xD9,0xFF,0xFD, +0xFE,0x7F,0xFF,0xFE,0xFF,0xF9,0xFF,0xFB, 0xD6,0xDF,0xBF,0xEF,0x5B,0xD6,0xFF,0xBF, +0xFB,0xF6,0xFF,0xBF,0xEF,0xF8,0xF6,0xDD, 0xBE,0xFE,0x16,0xFF,0xBF,0xEF,0xFF,0xFE, +0xFF,0xBF,0xEF,0xFF,0xFF,0xFF,0x6F,0xFB, 0xFF,0xFF,0xFF,0x6F,0xF3,0xFF,0xF7,0xEF, +0xFB,0xFF,0xBF,0xFF,0xEF,0xFE,0xFF,0xBF, 0xFF,0xFF,0xFF,0xBE,0xBF,0xFF,0xEF,0xFF, +0x7F,0xEF,0xFF,0xFD,0x17,0xFB,0x7B,0xFF, 0xFF,0xFD,0x7F,0xDB,0xF6,0xF4,0x7F,0xFA, +0xFE,0xF5,0xBF,0xEB,0xE3,0xF7,0xFF,0xFF, 0xE9,0xBF,0xFF,0xAF,0xF7,0xFD,0xF3,0x7E, +0x8F,0xA3,0xEA,0xFF,0xCB,0xF3,0xEE,0xFF, 0xBF,0xEF,0xF7,0xF9,0xFF,0xFE,0x7F,0xFF, +0xFF,0xFF,0xFF,0xF5,0xFB,0xF6,0xFF,0xF5, 0x2F,0xFE,0xFB,0xD7,0xBF,0xFF,0xBE,0xDF, +0x9F,0xFF,0xF0,0xFF,0xFF,0xF9,0xFE,0x7F, 0x8F,0xA3,0xF8,0xFE,0x6F,0x9F,0xF9,0xF6, +0x2F,0x9F,0xE7,0xF9,0xFE,0x2F,0x9F,0xE1, 0xFF,0xFF,0xFF,0x7F,0xDF,0xF7,0xF5,0xFD, +0x7F,0x7F,0xF5,0xFF,0x9F,0x5F,0xFB,0xFE, 0xFF,0x7F,0xFF,0xFF,0xCB,0xFF,0xFF,0xFB, +0xFE,0xFF,0xBF,0xAF,0xFB,0xFE,0xFF,0xDF, 0xFE,0xFE,0xBF,0xF7,0xFF,0xFF,0xFF,0xFF, +0xFF,0xC7,0xFF,0xFF,0xFD,0xFF,0x7F,0xDD, 0xF7,0xFD,0xFF,0xFF,0xD7,0xFF,0xFD,0x7F, +0xFF,0xFB,0xFD,0xFF,0xFF,0xFE,0xEF,0x7F, 0xFD,0xEF,0xFB,0xFE,0xFB,0xFD,0xFF,0x7F, +0xDF,0xFD,0xFF,0x7A,0xDF,0xF7,0xFD,0xFF, 0xFF,0xFF,0xFF,0x1F,0xFF,0xFF,0xD3,0xF7, +0xFF,0xFF,0x6F,0xDB,0xFF,0xFF,0xEF,0xCB, 0xF4,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, +0x29,0xFF,0xE8,0xDA,0x76,0x9F,0xAF,0x6A, 0xDA,0xFE,0x35,0xEB,0xDA,0xD6,0xBF,0xAB, +0xEB,0x7A,0xDE,0xBF,0xD7,0x7F,0xFF,0xFE, 0xFF,0xBF,0xEF,0xFD,0xDF,0x77,0xBF,0xFD, +0x37,0xEF,0xFF,0xEF,0xFF,0x3F,0xFF,0xFF, 0xFF,0xFE,0x7F,0xFF,0xFF,0xFF,0xF7,0x7E, +0xDF,0xFF,0xFF,0xFF,0xFA,0xB7,0x7F,0xFF, 0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0x89,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x9F,0xFB,0xFF,0xFF,0xFF,0xE7,0xFF, +0xFF,0xFF,0xFF,0xAA,0xFF,0xAB,0xFB,0xFA, 0xEF,0xBF,0xFF,0xDF,0xFA,0x7B,0xB9,0xFE, +0xFE,0xFF,0xFD,0xFF,0xF7,0xFE,0x3F,0xFF, 0xB7,0xFF,0xF7,0xEE,0xFF,0x7F,0xEF,0xFF, +0xFF,0x7F,0xFF,0x1F,0xFB,0xFF,0xBF,0xFB, 0xFE,0xFF,0xBD,0xFF,0xFF,0x2F,0xFF,0xBF, +0xFF,0x7F,0xDF,0xFA,0xFF,0xFF,0xFC,0xEE, 0xF5,0xF3,0xBE,0xFB,0x0F,0xEF,0xF3,0xBE, +0xEF,0xFC,0x5F,0xFF,0x5A,0xFF,0xF7,0xDF, 0xFF,0xFF,0xFE,0xD5,0xFC,0x5F,0xFB,0xF2, +0xFF,0xFF,0x2F,0xBB,0xF3,0xFF,0xFF,0xBF, 0xFF,0xEF,0xFF,0xEF,0xFF,0xFF,0xFF,0xFF, +0xBF,0xFF,0xFF,0xFD,0x7B,0xFF,0xDF,0xB9, 0xFF,0xFB,0xFF,0xD8,0x7F,0xFF,0xFF,0xFF, +0xFB,0xFF,0xFC,0x7F,0x1F,0xBF,0xE0,0xDF, 0xF7,0xEF,0xFF,0xFD,0x7F,0xFE,0xDF,0xFF, +0xE0,0xFF,0xFF,0xFD,0xEF,0xFB,0xFF,0xFE, 0xF7,0xDF,0xFF,0xEB,0x5F,0xFF,0xF7,0xFF, +0xFF,0xFF,0xFF,0xBF,0xFF,0xFD,0xFF,0xFD, 0xFF,0xFF,0xFF,0xF7,0xFD,0xFF,0x3B,0xDC, +0xFD,0x6D,0x7B,0x5F,0x57,0xF5,0xFD,0x7F, 0x5F,0xFF,0xB1,0xFF,0xEB,0xFF,0xFF,0xFF, +0xFB,0xFB,0xFE,0xFF,0xBF,0xFB,0xBE,0xFF, 0xBF,0xEF,0xFB,0xFE,0xFF,0xAF,0xFE,0xF7, +0xDF,0xDF,0xFF,0xFF,0xFF,0x7F,0xCF,0xF3, 0xF8,0xFF,0xD7,0xFB,0xFF,0x5F,0xBF,0xF7, +0xFB,0xFF,0x7F,0xFE,0x23,0xFF,0xFF,0xFE, 0x7F,0xF3,0xFF,0xFB,0xFE,0xFF,0xFF,0xF3, +0xFF,0xFF,0xF5,0xF9,0xFF,0x3F,0xFF,0xFF, 0xF0,0x9A,0xFF,0xBE,0x7F,0xFF,0xFC,0xF9, +0xFF,0xFD,0xAF,0xEB,0xFE,0xBF,0xFF,0xCF, 0xF3,0xFE,0x7F,0xFF,0xFF,0x5B,0xBD,0xFF, +0xBC,0xEB,0xFF,0xD7,0xD4,0xAF,0xAF,0xFD, 0xFF,0xCF,0xF7,0xFD,0xFF,0x7F,0xDF,0xF7, +0xFD,0xFE,0xFF,0x6F,0xFF,0xFB,0xFF,0xFF, 0xFF,0xFD,0x7F,0x5E,0xFD,0xBF,0xDB,0xF6, +0xFD,0xBF,0x6F,0xFB,0xEE,0xFD,0xFF,0x7A, 0xFF,0xFA,0xFB,0xFF,0x3F,0xFB,0xB7,0x5F, +0xD6,0xF7,0x1F,0x71,0xDC,0x77,0x1D,0xC7, 0x31,0xDC,0x77,0xDF,0xF9,0xBF,0xF5,0x5B, +0xF4,0xD7,0x9D,0xAE,0xFF,0xBF,0xFD,0xBF, 0xDB,0xF6,0xFD,0xBF,0x6F,0xDB,0xF6,0xFE, +0x3D,0x81,0xFF,0xEB,0xFE,0xFE,0xFE,0xFF, 0xEB,0x7A,0xDF,0x7D,0x77,0x7D,0xF5,0x79, +0xDF,0x57,0xDD,0xF5,0x7D,0x7E,0xE6,0xFF, 0xD6,0x3F,0xBF,0x7F,0xFF,0xD4,0xF5,0x3F, +0xBF,0xFB,0xBE,0xEF,0xB3,0xEE,0xFB,0x9E, 0xEF,0xBB,0xFE,0x8B,0xFF,0xFE,0xDF,0xB7, +0xED,0xFF,0xF7,0xFD,0xFE,0xFF,0xEF,0xBB, 0xEE,0xFF,0xBE,0xEF,0xBB,0xEE,0xEB,0xFC, +0x1F,0xFF,0xFF,0xFD,0xFF,0xE7,0xFF,0xF7, 0xFD,0xFF,0xEF,0xFE,0xFF,0xBF,0xEF,0xFB, +0xFE,0xFF,0xBF,0xEB,0xFA,0x1F,0xFF,0xB7, 0xEF,0x5B,0xFE,0xFF,0xAF,0xEB,0xDD,0xE7, +0xDE,0x77,0x9D,0xE7,0x79,0xDE,0x77,0x9D, 0xBF,0xE6,0x6F,0xFF,0xFE,0xFF,0xBF,0xEF, +0xFB,0xFE,0xFD,0xBF,0x6F,0xF6,0xFD,0xBF, 0x6F,0xDB,0xF6,0xFD,0xBF,0xFF,0x7E,0xFF, +0xFF,0xFB,0xFE,0xFE,0xFF,0xEF,0xFB,0xFD, 0xEF,0x7E,0xF7,0xBD,0xEF,0x7B,0xDE,0xF7, +0xBD,0xEF,0xFF,0xD5,0xFF,0xBF,0xFF,0xEF, 0xFE,0xFF,0xFC,0x3F,0x0F,0xE7,0xFE,0x7F, +0x9F,0xE7,0xF9,0xFE,0x7F,0x9F,0xE7,0xFE, 0xF3,0xFF,0xFE,0xDF,0xAD,0xDF,0x67,0xEE, +0xFB,0xBF,0xEF,0xFE,0xFF,0xBF,0xEF,0xFB, 0xFE,0xFF,0xBF,0xEF,0xFF,0x23,0xFF,0xFF, +0xFF,0xFF,0x7F,0xFF,0xF3,0xBC,0xDB,0xFE, 0xFB,0xFF,0xFB,0xBE,0xF7,0xFB,0xFF,0x7F, +0xDF,0xFF,0xCF,0xFB,0xFF,0x9F,0xE3,0xF9, 0xBE,0x3F,0x8F,0xE7,0x79,0xFF,0x9D,0xE7, +0xF9,0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x5F, 0xFF,0xCF,0xF7,0xFF,0xFF,0xFF,0xDF,0xF7, +0xFE,0x7F,0xE7,0xF9,0xFE,0x7F,0xFF,0xFF, 0xFB,0xFE,0xFF,0xFF,0xBF,0xFF,0xBF,0xBF, +0xFF,0xFE,0xFF,0xBF,0xEF,0xFF,0xFD,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xFD,0xFF, +0xFF,0x3F,0xFF,0xBF,0xFF,0xF7,0xFF,0xFF, 0x7F,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xE8,0xEF,0xFF, 0x5F,0xF7,0xBF,0xF9,0xFE,0xDF,0xB7,0xFD, +0xFF,0xDF,0xF7,0xFD,0xFF,0x7F,0xDF,0xF7, 0xFD,0xFF,0xDD,0xFF,0xF2,0xFF,0xBF,0xFF, +0xFF,0xBF,0xFF,0xFF,0x2F,0xF2,0xFF,0xBF, 0x2F,0x7B,0xD2,0xF7,0xBF,0x2F,0xFF,0xBB, +0xFF,0xEE,0x8F,0xAF,0xEB,0xFA,0xFE,0x3F, 0xA7,0x69,0xCE,0x8F,0xA4,0xEA,0xFA,0xEE, +0xB7,0xAE,0xEB,0xFD,0xC7,0xFF,0xF7,0xF7, 0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0x3E,0xF3, +0x74,0xFF,0x3F,0x4F,0xFF,0xE7,0xFF,0x3F, 0xFE,0xA7,0xFF,0xFF,0xDF,0xF7,0xB7,0xFF, +0xF7,0xFF,0xBA,0xEF,0x37,0xEB,0xFB,0xFE, 0xBF,0xFB,0xFE,0xF3,0xFF,0xF9,0xDF,0xFF, +0xBF,0xFF,0xFF,0xFF,0xBF,0xFF,0xFF,0xFF, 0xFD,0xDF,0xFF,0xFD,0xFF,0xFF,0xFB,0xFE, +0xFD,0xFF,0xFB,0xBF,0xFE,0x3F,0xED,0xFF, 0xDF,0xBE,0x3D,0xA7,0xFB,0xFA,0x3F,0xE6, +0xE1,0xFE,0xFE,0x3F,0xEF,0xE3,0xDF,0xF5, 0x7F,0xFE,0xFF,0x7E,0xFF,0xFF,0xFF,0xFF, +0xEF,0x6F,0xF6,0xFF,0x7D,0xEF,0xD7,0xDE, 0xFF,0x7D,0xEF,0xFF,0xF2,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0x7B,0xDE,0xFB,0xE6,0xEE, 0xEF,0x37,0x6E,0xF3,0x7E,0xEB,0x37,0xEF, +0xFF,0xC1,0xFF,0xFE,0xFF,0xF7,0xEF,0xFF, 0xFF,0xFF,0xBF,0x3F,0xD2,0xDF,0xBF,0x2F, +0x7B,0xE2,0xFF,0xFE,0x3B,0xBD,0xDB,0xFF, 0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0xFE, +0xFF,0xFB,0xFF,0xFF,0xBF,0xFF,0xFB,0xDF, 0xFF,0xBF,0xFF,0xB7,0xFF,0xFF,0xBF,0xEF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0xFF, 0x7F,0xFF,0x1F,0xEF,0xF1,0xFD,0xFF,0xF6, +0xAF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0xFF, 0xFF,0xFF,0xFE,0x9F,0xFF,0xFF,0xFF,0x77, +0xEF,0xF7,0xFB,0xFF,0xFE,0x5F,0xFF,0xFF, 0xBF,0xCF,0xFB,0xF7,0xDD,0xF7,0xF5,0xFF, +0x5F,0xD5,0xF5,0xFD,0x7F,0x5F,0xD7,0xF5, 0xFF,0xFB,0x0F,0xFF,0xFF,0xA9,0xEA,0x7A, +0xFF,0xAF,0x8F,0xFE,0xDF,0xAF,0xEF,0xFB, 0xFE,0xFF,0xBF,0xEF,0xFB,0xDF,0xE5,0x5F, +0xFF,0xFF,0xFF,0xFF,0xFF,0xBD,0x57,0xFF, 0xFF,0x6F,0x77,0xBF,0xF7,0xFB,0xFF,0x7F, +0xBF,0xF7,0xFF,0xFC,0xBF,0xFF,0x9F,0xFF, 0xFF,0xEF,0xFF,0xFE,0xFF,0xFF,0xFF,0x1F, +0xCF,0xFF,0xFC,0xFF,0xFF,0xFF,0xFF,0xFB, 0x65,0xAF,0xF3,0x7C,0xFF,0x3F,0xDF,0xFF, +0xFD,0xE9,0xFE,0x7F,0xE7,0xFF,0xFE,0x7F, 0xFF,0xFF,0xFF,0xFF,0xFD,0xE3,0xDF,0xFB, +0xDB,0xF6,0xFD,0xEF,0x5B,0xFB,0xFF,0xDF, 0xFC,0xFF,0x3F,0xDF,0xF3,0xFD,0xFF,0x7F, +0xDF,0xEF,0x66,0xFF,0xDF,0xAD,0xEB,0x7A, 0xDE,0xF7,0xF7,0xE7,0xD9,0xFD,0x9F,0x67, +0xD9,0xF6,0x7D,0x9F,0xE7,0xDF,0xF5,0x47, 0xFD,0x65,0x5B,0xD6,0xF4,0xFE,0xFF,0xEF, +0xFF,0x6D,0xF6,0xDD,0xB7,0x6D,0xDB,0x76, 0xDC,0xB7,0x7D,0xFA,0x9B,0xF6,0x6D,0x9D, +0x67,0x59,0xDF,0xF7,0xDD,0xFF,0xEB,0xFE, 0xBF,0xAF,0xEB,0xFA,0xFE,0xBF,0xAF,0xE3, +0xD1,0x9F,0xFF,0xBD,0xBF,0xEF,0xFE,0xF7, 0xBF,0xBF,0xF7,0xD7,0x7F,0xDD,0xF7,0x9D, +0xDF,0x7F,0xDF,0xF7,0xFF,0xE0,0x7F,0xFD, 0xC1,0xDF,0xF7,0xFD,0xC7,0x7F,0x7F,0xFB, +0xFF,0xBB,0xEC,0xFB,0x3E,0xFF,0xBF,0xEC, 0xFB,0xFF,0xD8,0x7F,0xBF,0x6C,0xFF,0xBE, +0xFF,0xBF,0xED,0xFF,0xEF,0xFE,0xFB,0xBF, 0xEF,0xFB,0xFE,0xFF,0xBF,0xEE,0xFF,0xC5, +0xFF,0xAF,0x6F,0xFF,0xFC,0xFD,0x3F,0xE7, 0xFF,0xFE,0xFF,0xEF,0xFB,0xFE,0xFF,0xBF, +0xEF,0xFB,0xFE,0xBF,0x89,0xFE,0xFA,0xBA, 0xFE,0xBF,0xAF,0xFB,0xF6,0xF5,0xD9,0x7D, +0x97,0x65,0xD9,0x74,0x5D,0x97,0x65,0xD3, 0xFE,0xD6,0xFF,0xBF,0xF7,0xFD,0xFF,0x7F, +0xBF,0xCF,0xFB,0xFE,0xFF,0xEF,0xFB,0xFE, 0xFF,0xBF,0xEF,0xFB,0xFF,0xF6,0x8F,0xFB, +0xFF,0xEF,0xFB,0x7E,0xDB,0xFE,0xFF,0xBE, 0xEF,0xEE,0xFB,0xBE,0xEF,0xBB,0xEE,0xFB, +0xBE,0xFF,0xFF,0xDF,0xFF,0x43,0xFF,0xFF, 0xFB,0xEF,0x5F,0xB7,0xFE,0x7F,0xE7,0xF9, +0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x7F,0xF9, 0xBF,0xFE,0xAF,0x77,0xFD,0xFF,0x2F,0xAF, +0xA7,0xFE,0xFF,0xEF,0xFB,0xFE,0xFF,0xBF, 0xEF,0xFB,0xFE,0xFF,0xF1,0x7F,0xEF,0xDF, +0xFF,0x97,0xF5,0xEF,0xFF,0xDF,0xFF,0xFF, 0xBF,0xFF,0xBF,0xFF,0xFF,0xFE,0xFF,0xFF, +0xFF,0xE0,0xFF,0xFF,0xF9,0xFE,0x2F,0x8B, 0xE3,0xF8,0xBE,0x77,0x9F,0xF9,0xDA,0x77, +0x9D,0xE7,0x79,0xDE,0x77,0x9F,0xDD,0xFF, 0xFD,0xFD,0x7F,0x5F,0xD7,0xFD,0xFF,0x7F, +0xE7,0xFE,0x7F,0x97,0xE7,0xFB,0xFE,0xFF, 0xBF,0xEF,0xFF,0xAB,0xFF,0xEF,0xFA,0xFE, +0xBF,0xAF,0xFF,0xFA,0xFF,0xFF,0xDF,0xFF, 0xFB,0xFF,0xF7,0xFD,0xFF,0x7F,0xDF,0xFF, +0x67,0xFF,0xF7,0xF5,0xFF,0xFF,0xFF,0xDF, 0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xEF,0xFF,0xBD, 0xEB,0xFF,0xFF,0xF7,0xAD,0xEB,0xFF,0xDF, +0xFD,0xFF,0x3F,0xDF,0xF7,0xFD,0xFF,0x7F, 0xDF,0xFF,0x5F,0xFF,0xF7,0xFF,0xFF,0xFD, +0xBF,0xFF,0xCB,0xF4,0xFF,0x7F,0xD3,0xF7, 0xFD,0x3F,0x7F,0xD3,0xF7,0xFF,0xFC,0x3F, +0xFF,0xEA,0xFA,0xBE,0xAF,0xAB,0xEB,0xBA, 0xF4,0x95,0x6B,0x52,0xD4,0xAD,0x2F,0x4A, +0xD2,0xF6,0xBF,0xD2,0x7F,0xF7,0x3F,0xFF, 0xFF,0xF3,0x7F,0xFF,0xFF,0xF7,0xFF,0xBA, +0xDF,0xFB,0xFD,0xFF,0xBF,0xFF,0xFB,0xFF, 0xF8,0x7F,0xEA,0xFF,0xFE,0xFE,0xDF,0xFF, +0xF7,0xFF,0x7F,0xBB,0xFF,0xFF,0xBF,0xDF, 0xFB,0xFF,0xFF,0xBF,0xFF,0xB1,0x7F,0xFF, +0xFB,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF, 0xCF,0xFE,0xFF,0xFF,0xEF,0xFF,0xF7,0xFF, +0xFF,0xFF,0xF1,0xFF,0x69,0xBE,0xFA,0xBF, 0xAF,0xE2,0xFF,0xFE,0xFD,0xAF,0xF3,0xFE, +0xFF,0xBF,0xEF,0xFB,0xFC,0xFF,0xFF,0x07, 0xFD,0x95,0xDB,0xDF,0x7F,0xDF,0xAF,0xFF, +0xF7,0xAF,0x36,0xFE,0xBF,0x65,0xEB,0xF6, 0xFE,0x9F,0x6F,0xFE,0x07,0xFF,0xCF,0xFF, +0xF8,0xFE,0xFF,0xCF,0xFF,0xF6,0xFA,0xE7, 0xFB,0xFE,0xFF,0xBB,0xED,0xF9,0xFF,0xFF, +0xFF,0x5F,0xFF,0xFF,0xFF,0x75,0xFF,0xEF, 0x7E,0xFD,0xE0,0xE8,0x5E,0xD3,0xE5,0xF9, +0x3E,0x5F,0xD7,0xF7,0xFF,0xFA,0x2F,0xFB, 0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0x7F, +0x7F,0xD7,0xF5,0x7D,0x5F,0x57,0xD5,0xF5, 0xEF,0xFF,0xF3,0x7F,0xFC,0x7F,0xFF,0xC7, +0xF1,0xFF,0xFF,0x1F,0xCF,0xB0,0xFF,0x3F, 0xCF,0xF3,0xFC,0xFF,0x3F,0xCE,0xFF,0xE4, +0xFF,0xDF,0x7F,0xFE,0xF7,0xBB,0xFF,0xFF, 0xDF,0xEF,0xEE,0xFF,0xBF,0xEF,0xFB,0xFE, +0xBF,0xBF,0xEF,0xFF,0xD1,0xFF,0xFF,0xFF, 0xFD,0xFB,0xFF,0xFD,0xFF,0xFB,0x9F,0xE9, +0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x7F,0xBF, 0xFF,0xB3,0xFF,0xFF,0xF7,0xFF,0xFF,0xAF, +0xF7,0xFF,0xB6,0x3F,0xEB,0xFA,0xFE,0xBF, 0xAF,0xEB,0xFA,0xFE,0xBF,0xFE,0xA7,0xFF, +0xFF,0xFF,0xFF,0xFF,0xF7,0xFF,0xFF,0xFF, 0xFE,0x9F,0xF7,0xF9,0xFF,0x7F,0x9F,0xE7, +0xFF,0xFF,0xFE,0xAF,0x6F,0xFF,0xFF,0xFF, 0x9F,0xFF,0xDF,0xFF,0x7D,0x5F,0xDD,0xFF, +0xFB,0xBF,0xE7,0xBB,0xFF,0xFB,0xDF,0x6D, 0x5F,0x7E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xEB,0xF7,0xFF,0xE7,0xEF,0xF7,0xFF,0xFF, 0x7F,0xFF,0xF7,0xFF,0xFC,0x8F,0xFF,0xEF, +0xFD,0xFE,0xFF,0xBE,0xF4,0xF2,0x7D,0xD7, 0xCF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xCF,0x6B,0xFF,0xBF,0x3F,0xFB,0xF2, 0xFC,0x7F,0xEB,0xFF,0x9F,0xFA,0xFF,0xFF, +0x3F,0xFF,0xF3,0xFF,0xFF,0xFD,0x70,0xF7, 0xFF,0xFF,0xBF,0xFF,0xFB,0xD7,0xFE,0xF5, +0x77,0xFF,0x15,0xDD,0x77,0xFD,0xFF,0x7F, 0xDF,0xF7,0xFB,0xCD,0xBF,0xFF,0xFD,0xFF, +0xFF,0xDF,0x37,0xCD,0xF9,0xEC,0xFE,0xEF, 0xBB,0xF4,0xFB,0x3F,0x4F,0xB3,0xFF,0xFD, +0xCB,0xFF,0xE9,0x7E,0x54,0x9F,0xE5,0x4B, 0xB7,0xFF,0xDD,0x7D,0xC7,0x71,0xDD,0x77, +0x5D,0xD7,0x75,0xCD,0x7F,0xD6,0xFF,0xD3, 0xF6,0xF9,0x3F,0x6D,0x95,0xAF,0x7F,0xFE, +0xFF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB, 0xFE,0xF6,0xC7,0xFF,0xAD,0x7B,0xCA,0xFF, +0xBF,0xBF,0xEF,0xFD,0xE3,0xDF,0xB7,0xED, 0xFB,0x7E,0xDF,0x37,0xED,0xE3,0xFB,0xDF, +0xFF,0x52,0x5C,0x15,0xFD,0xCF,0x7F,0xDF, 0xFE,0xEF,0xEF,0xFB,0xFE,0xFF,0xBF,0xEC, +0x7B,0xFE,0xFF,0xFE,0x3E,0x7F,0xDA,0xF7, 0xFD,0xFF,0x7F,0xFF,0xFF,0xFB,0xEF,0xBB, +0x6F,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB,0xFF, 0xF7,0x7D,0xFF,0xD8,0xFF,0xFD,0xBF,0x7F, +0xFB,0xFF,0xFF,0x9F,0xFB,0xFE,0x7F,0x9F, 0xE7,0xF9,0xFE,0x7F,0x9F,0xEA,0x7F,0xF6, +0xBF,0xBD,0x6A,0x5A,0xF6,0xE5,0xBF,0x77, 0x5F,0x6D,0xDD,0x77,0x5D,0xD7,0x75,0xDD, +0x77,0xFF,0xA5,0xBF,0xCF,0xFB,0xFF,0xFF, 0xBF,0xCF,0xFB,0xFD,0xFF,0xBF,0xF3,0xFE, +0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xFD,0xAB, 0xFF,0xBF,0xBF,0xFF,0xFB,0xFF,0x7F,0xEF, +0xFF,0xBE,0xFB,0xEE,0xFB,0xBE,0xEF,0xBB, 0xEE,0xFB,0xBF,0xFF,0xB5,0xFF,0xD0,0xBC, +0xFD,0x2F,0x4B,0xF7,0xFF,0xFF,0x9F,0xF9, 0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x7F,0x9F, +0xFA,0x8F,0xFD,0xAB,0xFA,0xDA,0xBF,0xAF, 0xB3,0xFD,0xFF,0xBF,0xFB,0xFE,0xFF,0xBF, +0xEF,0xFB,0xFE,0xF7,0xBF,0xFF,0x9F,0xFF, 0x77,0xF7,0xBD,0xFD,0x77,0xDF,0xFF,0x7E, +0xDF,0xED,0xBB,0xFE,0xFF,0xBE,0xEF,0xFB, 0xFE,0xFF,0xFA,0x3F,0xFF,0xBE,0x6F,0x8F, +0xE6,0xF9,0xFE,0x7F,0x9F,0xC7,0xFE,0x7F, 0x9F,0xE7,0xF9,0xFE,0x7F,0x9F,0xE7,0xFB, +0x7F,0xFF,0x7F,0xCF,0xFF,0xFD,0xFF,0xFF, 0xDF,0xFB,0xAF,0xBF,0xEF,0xFF,0xFE,0xFF, +0x9F,0xEF,0xFB,0xFF,0xFC,0xFF,0xFB,0xFE, 0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xF7, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xF5,0xFF,0xFF,0xFF,0x3F,0xDF,0xF7, +0xFF,0xFF,0x7F,0xEF,0xFE,0xFF,0xBF,0xFF, 0xFB,0xFF,0xFF,0xBF,0xEF,0xFF,0xB3,0x7F, +0xFF,0x7B,0x5E,0xF7,0xFD,0xFF,0x7B,0x7F, 0xF7,0xFF,0x7F,0xDF,0xF7,0xFD,0xFF,0x7F, +0xDF,0xF7,0xFF,0x17,0xFF,0xFF,0xFF,0x7F, 0xFF,0xFF,0xDD,0xF6,0xFC,0xBF,0xCB,0xF2, +0xBC,0xBF,0x2F,0xCB,0xF2,0xFC,0xBF,0xFE, 0x8F,0xFF,0xFA,0x7E,0xBF,0xA7,0xEB,0xDA, +0xFC,0xBF,0xAF,0x7A,0xFE,0xBF,0xAF,0xEA, 0xFA,0xFE,0xBF,0xAF,0xF4,0xDF,0xFE,0xFF, +0xF3,0x3C,0x7F,0x3E,0xFF,0xCF,0xF8,0xBF, 0x8F,0xE3,0xF8,0xFE,0x3F,0x8F,0xE7,0xE8, +0xFF,0xFC,0x9F,0xFF,0xFF,0xCF,0xEB,0xB3, 0xE7,0xFB,0x7B,0xF3,0xFE,0xFF,0xCF,0xDB, +0xFB,0xFB,0xBF,0x6F,0x6F,0xDF,0xEC,0x7F, 0xFF,0xFF,0xF7,0xFD,0xFD,0xFF,0xFF,0xFF, +0xFF,0xB2,0xBF,0xFF,0xDE,0xFD,0xBD,0xEF, 0xFB,0xF6,0xDF,0xEA,0xE7,0xDB,0xFE,0xBB, +0xFF,0xEB,0xFB,0xBF,0x9F,0x8F,0xE8,0xFE, 0x3F,0x8F,0xA3,0xF8,0xFE,0x3F,0x8F,0xFF, +0xF8,0x7E,0xFD,0xFD,0x7F,0xFF,0xFB,0xCD, 0xFF,0xFD,0xFF,0x5F,0xEF,0xFD,0xFF,0xFF, +0xDF,0xF7,0xFD,0xFF,0xBE,0x90,0xFF,0xFF, 0xEE,0xFF,0x3F,0xBF,0xF3,0xBB,0xFE,0xB7, +0xAB,0xFA,0xFE,0xAF,0xAD,0xEA,0xFA,0xDE, 0xAB,0xFF,0x63,0xFF,0xFE,0xF2,0xFF,0xB3, +0xFF,0xDF,0xEE,0x7D,0xFF,0x03,0xF1,0xF4, 0x3F,0x1F,0xC3,0xF1,0xEC,0x7F,0xFE,0x6F, +0xFF,0xFB,0xFB,0xFF,0x9F,0xFF,0xBF,0xFF, 0x7B,0x5F,0xFD,0xFF,0xDF,0xF7,0xFD,0xFD, +0x7F,0x7F,0xDF,0xFE,0xCF,0xFB,0xFF,0xFF, 0xAF,0xFB,0xFF,0x1F,0xEF,0xA5,0xFD,0xBF, +0xDF,0xFB,0x7D,0xFF,0xBF,0xDF,0xFB,0xFF, 0xFD,0x3B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD, +0xAF,0xF3,0xFF,0xFB,0x7F,0xBF,0xD7,0xFB, 0xBF,0x7F,0xBB,0xF7,0xFF,0xF8,0x7F,0xFF, +0xFA,0x5F,0xD7,0xFF,0xDF,0x7F,0xEF,0xFF, 0xFF,0x7F,0xDB,0xF7,0xFD,0xFF,0x7F,0xDF, +0xB7,0xFB,0xEC,0xFF,0xFF,0xF7,0xBF,0xEF, 0xFD,0xFC,0xFB,0xFF,0xEF,0xF0,0xFE,0x3F, +0x8F,0xE3,0xF8,0xFE,0x3F,0x8F,0xEF,0x8D, 0xFF,0xFF,0xEF,0x7F,0xBF,0xFF,0xFB,0xFF, +0xDB,0xBF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xEF,0xD8,0xFF,0x2E,0x7F, +0xBE,0xEF,0xFE,0x6E,0xFF,0xBF,0xF9,0xFF, 0xFF,0xF3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFC,0x66,0xBE,0x47,0xF3,0x7F,0xDF,0xFE, 0x87,0x9F,0xFF,0xFF,0xFF,0xFF,0xE7,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xD6,0x6F,0x7C, 0xFB,0x4F,0xD2,0xFF,0xFD,0x2B,0xFE,0xFF, +0xFF,0xFD,0x5F,0xD7,0xD5,0xF5,0x7D,0xFF, 0xFF,0xFF,0xBF,0x9B,0xFF,0xFF,0xDF,0xB7, +0xFF,0xFF,0xDF,0xFF,0x3F,0xCF,0xFE,0x7F, 0xBF,0xEF,0xFB,0xFC,0xFF,0x3F,0xFF,0xD9, +0xBF,0xFE,0x97,0xEC,0x8F,0xB7,0xFE,0x9B, 0x7D,0xFD,0xB7,0xDD,0x77,0x1D,0xC7,0x71, +0xDD,0x77,0x5D,0xD7,0xF3,0x6F,0xFD,0x3F, 0x73,0xDD,0xAF,0xFD,0x7A,0xFF,0xFF,0xAF, +0xFE,0xFD,0xBF,0xEF,0xFB,0xFE,0xFF,0xBF, 0xEF,0x66,0x7F,0xFF,0xFF,0xBF,0xBF,0xFF, +0xFB,0xFF,0xF7,0xDF,0xFD,0xFB,0x7D,0xDF, 0xB7,0xCD,0xF3,0x7C,0x5F,0x3F,0x91,0x3F, +0xFF,0x3D,0xEF,0x7B,0xFF,0xFC,0xFF,0xCA, 0xEF,0xFE,0xFF,0xBD,0xEF,0xFB,0x1E,0xE7, +0xBB,0xEC,0x7F,0xB3,0xFF,0xFD,0x9F,0xFF, 0xFF,0xFE,0xFF,0xFF,0x7F,0xBF,0xFB,0xFE, +0xFF,0xBF,0xEF,0xFB,0xEE,0xFB,0xBF,0xDF, 0x67,0xFF,0xFF,0xBF,0xEF,0xDB,0xFF,0xBC, +0xFE,0x7F,0xFB,0xFF,0x9F,0xEF,0xF9,0xFE, 0x7F,0x9F,0xE7,0xF9,0xFE,0x87,0xFF,0xEE, +0xFB,0xBE,0xE5,0xBF,0xEF,0xF9,0xD7,0x65, 0xF7,0xDD,0xE7,0x7D,0xDF,0x77,0x5D,0xD7, +0x7F,0xF8,0x9B,0xFE,0xFF,0xBF,0xEF,0xFB, 0xFF,0xFF,0xBF,0xEF,0xFB,0xFF,0x7F,0xCF, +0xF3,0xFC,0xFF,0xBF,0xEF,0xFF,0xDB,0x3F, 0xEF,0xFB,0xFE,0xFF,0xDF,0xFF,0xFE,0xFB, +0xBB,0xEF,0xBF,0xEF,0xBB,0xEE,0xFB,0xBE, 0xEF,0xBB,0xFF,0xFC,0x7F,0xFD,0x3B,0x5B, +0xD6,0xE5,0xFD,0x4F,0xC3,0xFB,0xFF,0xBF, 0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB,0xFF, +0xB4,0xFF,0xFA,0xBC,0x8F,0xB2,0xE9,0xD2, 0x2E,0xCF,0xFB,0xFF,0xBF,0xEF,0xFB,0xFE, +0xFF,0xBF,0xEF,0xFB,0xFF,0xEC,0xFF,0xFD, 0xFD,0x7F,0xDF,0xF7,0xE4,0xDF,0x5F,0xFF, +0xFF,0xFB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xC3,0xFF,0xEF,0xE6,0xF8,0xFE, +0x3F,0x8B,0x83,0xF9,0xFE,0x7F,0xE7,0xF9, 0xFE,0x7F,0x9F,0xE7,0xF9,0xFE,0x7F,0x17, +0xFD,0xFF,0xFF,0xFF,0x7F,0x5F,0xF7,0x2C, 0xFF,0xFF,0xFF,0xFE,0x7F,0xFF,0xE7,0xF9, +0xFE,0x7F,0x9F,0xFE,0x2F,0xFF,0xFF,0xEF, 0xFF,0xFE,0xBF,0xEF,0xAD,0xFF,0xFF,0x7F, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFE,0xDF,0xFF,0xDF,0xFF,0xFD,0xFD,0x7F, +0xDF,0xF7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0x3F,0xFE, +0xF7,0xFD,0xEF,0x7A,0xFF,0xB1,0xBD,0xFF, 0x7F,0xF7,0xFD,0xFF,0x7F,0xDF,0xF7,0xFD, +0xFF,0x7F,0xF3,0x27,0xFF,0xDF,0xFF,0xDD, 0xFF,0xFC,0x9B,0xFF,0xCB,0xFC,0xBF,0x2F, +0xCB,0xF2,0xFC,0xBF,0x2F,0xC9,0xFF,0xDE, 0xFF,0xDF,0xAF,0xEB,0xDA,0xFE,0xBB,0xAF, +0xEB,0xF8,0xF7,0xAF,0xE8,0xFA,0xFE,0xBF, 0xAF,0xEB,0xF2,0xFF,0xFD,0xFF,0xFF,0xEF, +0xBD,0xD7,0xBF,0xFF,0xFF,0xDE,0x8F,0xB8, 0xDE,0x37,0x8D,0xA3,0x78,0xDA,0x3F,0x8F, +0xFF,0xA1,0xFF,0xFF,0xFB,0xFB,0xFF,0xFF, 0xFF,0xFF,0xA7,0xBD,0xFB,0x76,0xFD,0xBF, +0xEF,0xDB,0xFE,0xBB,0xBF,0xFE,0x27,0x7F, 0xFF,0xFE,0xFE,0xFD,0xF5,0xFF,0xEF,0xF5, +0xDF,0x1F,0xE7,0xFD,0xFF,0x7F,0xDF,0xF7, 0xFD,0xFF,0xFF,0xCD,0xFD,0xAE,0xFF,0xFA, +0x3E,0x3F,0xAB,0xFD,0xF8,0x7E,0x8F,0xE3, 0xF8,0xFE,0x3E,0x8F,0xE3,0xF8,0xFF,0xFE, +0x1F,0xEF,0xDF,0xBF,0xFE,0xDE,0xDF,0xD9, 0xFF,0xDF,0xBC,0xFF,0xFF,0x7F,0xFF,0xEF, +0xFD,0x7F,0xDF,0xF7,0xF9,0x3F,0xFE,0xFF, 0xFF,0x6F,0xFE,0xDE,0xBF,0xF7,0xED,0xEA, +0xFD,0x8F,0x83,0xF8,0xEA,0x3F,0x8F,0xEF, 0xFF,0xF4,0x7F,0xFF,0xEF,0xEF,0x7B,0xF3, +0xF1,0x5F,0xFF,0xFF,0xF1,0x3B,0x7F,0xDF, 0xF7,0xFD,0xFF,0xFF,0xFF,0xFF,0xE0,0xFF, +0xFF,0xFF,0xF7,0xFF,0x6F,0xFF,0x7F,0xFF, 0xFF,0xF7,0xDE,0xF7,0xBF,0xEF,0xFB,0xF7, +0xFD,0xFF,0xFF,0xF5,0xFA,0xFF,0xFF,0xFB, 0xE7,0xFF,0xF3,0xF8,0x7F,0xF3,0xDF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1F,0xEF, 0xBB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD, +0xFF,0x7F,0xFF,0x9F,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xCF,0xFF,0x37,0xFF,0xFF, +0x7F,0xDF,0x77,0x5D,0xE7,0xFC,0xFF,0xBF, 0xF7,0xF5,0xFB,0xFF,0xFF,0xD7,0xF5,0xFB, +0xFF,0xFF,0x45,0xFD,0x7F,0xEA,0xFD,0xBE, 0xBF,0xDF,0xF7,0xFF,0xFF,0xDB,0xFB,0xFE, +0xFF,0xBF,0xEF,0xFF,0xFF,0xFF,0xFB,0x5F, 0x7F,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFE,0xFF,0xEF,0xFD,0xFF,0x7F,0xDF, 0xFF,0xEF,0xFB,0xF8,0x0F,0xF3,0xFF,0xF9, +0x2E,0xFB,0xFE,0xFC,0xF3,0xEF,0xFF,0xFF, 0xBF,0xFF,0xFB,0xE7,0xFF,0xFE,0x7E,0xFF, +0xC0,0x6B,0xCF,0xFF,0x34,0xDF,0xF1,0xFD, 0xFF,0xEF,0xFF,0xFF,0xFF,0xDF,0xF7,0xFD, +0xCF,0x7F,0x9C,0xFD,0xFD,0x6C,0xF7,0xFF, 0xF6,0xFD,0xEB,0x2B,0x9F,0xFF,0xFC,0xFE, +0x7E,0xFF,0xFF,0xFF,0xFF,0xD7,0xF3,0xF7, 0xFF,0xFB,0xE1,0xBF,0xFF,0xEB,0x7A,0xDE, +0xD7,0xFB,0xFF,0xF9,0xFE,0xFF,0xFF,0xF3, 0xDE,0x7F,0xFD,0xE7,0x7F,0xFF,0xFD,0xBB, +0xFF,0xFF,0x7E,0xCC,0xF6,0xAF,0x5F,0x7F, 0xFE,0xF4,0x7D,0xF7,0xFD,0xBB,0x6E,0xDB, +0xB7,0xFF,0xF7,0xDF,0x66,0xFF,0xFF,0xF7, 0x3D,0xCF,0xDE,0xBD,0xFF,0xFF,0xDE,0xDB, +0x8D,0xF7,0x7E,0xDF,0xB7,0xEF,0x7F,0xFF, 0xF6,0x87,0xFF,0xFF,0xEF,0xFE,0xDE,0xBF, +0xFF,0xFF,0xFF,0xBB,0xEF,0xFD,0xFF,0x7B, 0xDE,0xF7,0x3F,0xFF,0xBF,0xFB,0xDB,0xFF, +0xF2,0xB6,0xFD,0xBD,0x7F,0xE7,0xFF,0xFF, 0xFF,0x6F,0xF7,0xFF,0xFF,0xFF,0xFE,0x77, +0xFF,0xBF,0xF8,0xAF,0xFF,0xDF,0xBF,0xFF, 0xBF,0x7F,0xFB,0xFF,0xFF,0xFF,0xDB,0xFE, +0xFF,0xBF,0xFF,0xFA,0xFF,0xFD,0xFF,0xF6, 0x7F,0xFF,0x9F,0xFF,0xFF,0x3F,0xEF,0xF8, +0xEE,0x7E,0x9F,0xBA,0xFE,0xBF,0x8F,0xEF, 0xFE,0xFE,0xF9,0xFF,0xFA,0x7F,0xFE,0x7E, +0xBF,0xAF,0xFB,0x96,0xFD,0x9F,0xEF,0x5E, 0x65,0xBE,0xEF,0x5B,0xB6,0xFF,0xBE,0xE3, +0xFF,0xB5,0xBF,0xFF,0xFD,0xFF,0x7F,0xFF, 0xEF,0xDF,0xFE,0xFF,0xBF,0xFB,0xFE,0xFF, +0xBF,0xCF,0xFF,0xFF,0xFF,0xFD,0x9B,0xFF, 0xFE,0xFB,0xFE,0xDF,0xFF,0x7F,0xFF,0xF7, +0xFE,0xFF,0xDF,0xFB,0xFB,0xFE,0xFF,0xFF, 0xFF,0xFF,0xFF,0xB7,0xFE,0xFA,0xFF,0xAB, +0xEF,0xFF,0xFD,0xB5,0x7B,0x7F,0xFB,0xF7, 0xFD,0xFF,0xFF,0xDD,0xFF,0xEF,0x8F,0xFF, +0x2F,0xFF,0xFB,0x7C,0xFF,0x3F,0xDF,0x73, 0xEB,0xFE,0x3F,0xFF,0xEF,0xFB,0xFE,0xFF, +0xEF,0xFD,0xFF,0xBF,0xFD,0x0F,0xFF,0xFF, 0xFF,0xF5,0xF9,0xFF,0x7F,0xD7,0xFD,0xFF, +0xDF,0xFF,0xF7,0xFB,0xFF,0x7F,0xBF,0xFF, 0xFF,0xF0,0x9F,0xFF,0xFE,0x7F,0x8B,0xE3, +0xF9,0xDE,0x27,0x9B,0xE6,0xBE,0x7F,0x9B, 0xC3,0xF8,0xDE,0x7F,0x9D,0xE7,0xFE,0x7F, +0xFF,0xFF,0x5F,0xD7,0xFF,0xFF,0xFF,0x4F, 0xFB,0xFF,0xFF,0x7F,0xFF,0xAF,0xFF,0x9F, +0x7F,0xFB,0xFF,0xE8,0xFF,0xFF,0xFE,0xBF, 0xAF,0xFF,0xFF,0xFE,0xBF,0xEF,0xF7,0xFF, +0xBF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xFF, 0xFC,0xFF,0xFF,0xFD,0x7F,0xFF,0xFF,0xFF, +0xFD,0x3F,0xCF,0xFF,0xFF,0xFF,0xFF,0xF7, 0xFF,0xFD,0x7F,0xFF,0xFF,0x93,0xFF,0xFF, +0x7A,0xDF,0xF7,0xFF,0xFF,0x7B,0x7F,0xB7, 0xEF,0xFF,0xFF,0xFD,0xBF,0xFD,0xFB,0xFF, +0xF7,0xFF,0xD7,0xFF,0xFF,0xFF,0xFC,0x9F, 0x6F,0xCB,0xFF,0xF4,0xBB,0xDF,0xD6,0xFD, +0xBF,0x2F,0xD3,0xF7,0xFF,0xDF,0xFF,0xCF, 0xFF,0xFA,0xBE,0xBD,0xAF,0x6A,0xDA,0xBE, +0xBB,0xAB,0x3A,0xBE,0x2D,0xAE,0xEB,0xDA, 0xF6,0x3F,0xAD,0xF5,0xDD,0xFF,0xCF,0xF1, +0xFF,0xF9,0x7F,0xFF,0x73,0xFE,0xFF,0xCF, 0xC3,0xF4,0xF7,0x2F,0xF3,0xFF,0xFC,0xFF, +0x7C,0x1F,0xFF,0x3F,0x4F,0xFF,0x7E,0xFF, 0xEF,0xBD,0xF6,0xFE,0xFF,0x2B,0xEF,0xDC, +0xFB,0xFD,0xFF,0xFB,0xFF,0xEA,0x7B,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFB,0xF7,0xDF,0xFF, +0xE3,0x7D,0xFF,0xB7,0xFF,0xBF,0xFF,0xFF, 0xDF,0xFF,0xF8,0xFF,0xBF,0xFF,0xBF,0xEB, +0xE7,0xFA,0xFE,0x3D,0xBF,0xE9,0xFC,0xBF, 0xFF,0xFA,0xFB,0xFE,0xFF,0xFF,0xFF,0xD9, +0xFF,0xFF,0xFF,0xF6,0x7F,0xFF,0xF6,0x7D, 0xFF,0xDF,0xCF,0xFD,0xBF,0xFB,0xEF,0x7E, +0xFF,0x7F,0xFF,0xFF,0xD3,0xFF,0xFD,0xFB, 0xFF,0xFB,0xFF,0xFF,0xFF,0xEF,0xFF,0xBF, +0xFE,0xFF,0xF7,0xEF,0xFF,0xFF,0xFF,0xFB, 0xFF,0x87,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF, +0x7B,0xFE,0xFF,0xFE,0x3B,0xF7,0xF7,0xFF, 0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0xFF, +0xFF,0xFF,0xFF,0xFB,0xFF,0xFF,0xFF,0xF7, 0xFF,0xFF,0xAD,0xFF,0xFE,0xF7,0xFF,0xFF, +0x5F,0xFF,0xFF,0xDF,0xFF,0xFD,0xFF,0xF5, 0xFF,0xDF,0xFF,0xBD,0xFF,0xE9,0xFF,0xC7, +0xF3,0xFF,0xFF,0xF7,0xFF,0xF3,0xFF,0xF8, 0x3B,0xFF,0xFF,0x7B,0xDF,0xBF,0xFB,0xEF, +0xFB,0xFF,0xFB,0xF7,0xF7,0xBB,0xFF,0xFF, 0xFF,0xFF,0xFB,0xFF,0xFE,0x7F,0xF3,0x7F, +0x5E,0xB7,0xBF,0xFD,0x7F,0xFF,0xF9,0x7F, 0xFB,0xFF,0xEB,0xFD,0x7F,0x7F,0xFF,0xEF, +0xFB,0xE0,0x3F,0xFE,0xBF,0xBF,0xDF,0xFF, 0x7E,0xFF,0xF7,0xFF,0xFF,0xFE,0xBF,0xFF, +0xDB,0x78,0xFF,0xFF,0xFF,0xEE,0xA1,0xBF, 0xF5,0xDE,0xFB,0xF7,0xFF,0xFB,0xFF,0xFF, +0xFF,0xFF,0xFB,0xFF,0xFF,0xD7,0xFF,0xFF, 0xFF,0xFF,0xEF,0xF0,0xFF,0xFF,0xFF,0xF3, +0xF7,0xFF,0xEF,0xFF,0xE7,0xCF,0xFF,0xFB, 0xFF,0xEF,0xFF,0xFF,0x9F,0x9F,0xEF,0xFC, +0x16,0xBF,0xFE,0xF3,0xE4,0xFF,0xFF,0xC6, 0xFF,0xE7,0xFF,0xFF,0xFD,0xFF,0xBF,0xFF, +0xFF,0x3F,0xFF,0xBF,0xD6,0xAF,0x7F,0xFE, 0x6B,0x7E,0x7F,0xFF,0xAF,0xFF,0xFF,0xBF, +0xFF,0x5F,0xFF,0xFE,0xFF,0xFF,0xFE,0xFF, 0xFF,0xBD,0xDB,0xFF,0xFE,0x5F,0xF2,0xFF, +0xFF,0x5F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xEF,0x7F,0xFF,0xFF,0xFF,0xFF,0xDE,0xBF, +0xFF,0xFF,0xEF,0xFB,0x77,0xFE,0xBD,0x7F, 0x5F,0xFF,0xFF,0xFF,0xDF,0x6F,0xED,0xFF, +0xFD,0xFF,0x7F,0xFD,0x6F,0xFF,0xFF,0x77, 0xDA,0xCF,0xFD,0x5F,0xFF,0xBF,0xFF,0xFF, +0xDF,0x7F,0xFF,0xFB,0xFF,0xFF,0xFF,0xFF, 0x66,0x7F,0xFF,0xFE,0xBF,0xE7,0xBF,0xFA, +0xFF,0xFE,0xFF,0xFF,0xFF,0xDF,0xFF,0x59, 0xEF,0xFF,0xEF,0xFB,0x7F,0x89,0xFF,0xFF, +0xE9,0xFF,0x6F,0xFF,0xF5,0xFF,0xFF,0xFF, 0xFF,0xFF,0x7F,0xF2,0xF7,0xFF,0xFF,0xEF, +0xF8,0x7F,0xFB,0xFF,0xFD,0xFF,0xFF,0xD9, 0xFF,0xEF,0xBB,0xFF,0xFF,0xFF,0xBF,0xEF, +0xDE,0xFF,0xFF,0x9F,0x7F,0xDF,0xFF,0xF7, 0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF,0xAF, +0xFF,0xFF,0xF7,0x3F,0xEB,0x9F,0xFE,0x7F, 0x9E,0x7F,0x9F,0xFE,0x87,0xFF,0xED,0xDB, +0x56,0xFF,0xBF,0xAF,0x0B,0xD2,0xFF,0xEF, 0xDB,0x6E,0x7D,0xBD,0x6F,0xF8,0xFE,0x3F, +0xFA,0x5B,0xFF,0xFD,0xBF,0xEF,0xFF,0xBF, 0x6F,0xDB,0xE6,0xFF,0xFF,0x3F,0xFF,0xDF, +0xFE,0xFF,0xFF,0xFF,0xFF,0xDA,0x3F,0xFF, 0xFB,0xFE,0xFE,0xFF,0xFF,0xDF,0xF7,0xBD, +0xFF,0xFD,0xFF,0xFE,0xFF,0xFB,0xFF,0xFF, 0xFF,0xFF,0xF1,0x5F,0xFD,0x9F,0xDF,0xFD, +0xFF,0xFD,0x7F,0xFF,0xFF,0xFF,0xFF,0x76, 0xFA,0xFF,0xFF,0x7F,0xE3,0xF8,0xFF,0xAE, +0xFF,0xFB,0x7E,0x9D,0x73,0xFF,0xFA,0x7F, 0xDF,0xFF,0xFF,0x7F,0xFF,0xFB,0xCD,0xFF, +0x7F,0xEF,0xFB,0xFF,0xFD,0xFF,0xF7,0x7F, 0x7F,0xEF,0xFF,0xED,0xFF,0xFF,0xFF,0xB5, +0xFF,0xBF,0xFF,0xBF,0xFD,0xEF,0xDB,0xF7, 0xFF,0x93,0xFF,0xEF,0xE2,0xF9,0xBE,0x7F, +0x8B,0xE7,0xF9,0xFE,0x6B,0xE7,0xF9,0xFE, 0x7F,0x9F,0xE7,0xF9,0xFE,0x7F,0x47,0xFF, +0xFF,0xFD,0xFF,0x9F,0xFF,0xD7,0xFF,0xFF, 0xFF,0xFF,0xF5,0xFF,0x9F,0xFF,0xF7,0xFE, +0xFF,0xBF,0xFE,0x6F,0xFF,0xFF,0xFB,0xFF, 0xFF,0xFF,0xAF,0xFF,0xFF,0xFF,0x7F,0xFB, +0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD, 0xDF,0xFF,0xFF,0xF7,0xFF,0xFF,0xFF,0xDF, +0xFF,0xFF,0xFF,0x5F,0xFF,0xFF,0xFF,0xFF, 0x5F,0xFB,0xFE,0xFF,0xF8,0x37,0xFF,0xFF, +0xEF,0xFF,0x7F,0xFE,0xBF,0xFF,0xFF,0xFE, 0xBF,0xFF,0xFF,0x7F,0xFF,0xBF,0xFD,0xFF, +0x7F,0xFA,0x7F,0xFF,0xFF,0x6F,0xFF,0xFF, 0x7D,0xFF,0xCF,0xFF,0xFF,0xFF,0x4F,0xFF, +0xF2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xBF, 0xFF,0xAE,0xEB,0xFA,0xFE,0xBB,0xAD,0xEB, +0xFA,0xF7,0xAF,0x6B,0xFA,0xF6,0xBF,0x25, 0xE9,0xF2,0x7F,0x45,0xFF,0xFF,0xFD,0xF7, +0xF7,0xBF,0xFF,0xDF,0xFF,0xFF,0xBF,0xFB, 0xFF,0xDF,0xF3,0xFF,0xF7,0x3F,0xCF,0xFF, +0xA1,0xFF,0xFF,0xBF,0xE7,0xFF,0xFF,0x7F, 0xFF,0x3D,0xFF,0xFF,0xFF,0xF7,0xFF,0x2F, +0xFF,0xFB,0xF5,0x7F,0xFE,0x57,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7, +0x3F,0xFF,0xFE,0xFF,0xFF,0xFF,0xFD,0xFE, 0xF7,0xEE,0xAF,0xFE,0xEE,0xE7,0xFA,0xFF, +0xFE,0x9D,0xF9,0x5E,0xFE,0xFF,0xEB,0xFF, 0xFF,0xDF,0xA7,0xFF,0xFF,0xFF,0xFC,0xDB, +0xFF,0xFF,0xFF,0x7E,0xFB,0xFF,0xFF,0xEF, 0xFB,0xFD,0xFF,0xDB,0xFF,0xFF,0xFF,0xEF, +0xFF,0xFF,0xFF,0xFD,0xBF,0xFE,0xBF,0xFF, 0x6F,0x7F,0xFF,0xF7,0xFF,0xFF,0xF9,0xFF, +0xF7,0xFF,0xBF,0xDE,0xF7,0xFF,0xFF,0xFF, 0xFA,0x7F,0xFD,0xBF,0x5F,0xFF,0xFF,0xBF, +0xFF,0xED,0xFF,0xF7,0xBF,0xFF,0xFF,0xEF, 0xFF,0xDF,0xFF,0xFF,0xFF,0xE6,0xFF,0xFB, +0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEB,0xFF, +0xFD,0xFF,0xF5,0xFF,0xF6,0x7F,0xDF,0xBD, 0xCF,0xFF,0xFF,0xFF,0xFF,0xDF,0xFF,0xFF, +0xFF,0xF9,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3, 0xFF,0xEE,0xBF,0xFF,0x7D,0xEF,0xFE,0xFF, +0xFF,0xFF,0xBF,0xFF,0xFF,0xFF,0xFF,0xFE, 0xFF,0xFF,0xFF,0xFF,0xE7,0xFF,0xB5,0xAE, +0xFF,0xFF,0xB6,0xFE,0xBF,0xFF,0xFF,0xBF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0x27,0xFF,0xEF,0xFE,0x7F,0xDF,0xFF, 0x7E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFD,0xFF,0xF7,0xF9,0x9F,0xFF, 0x5F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F, +0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0x0F,0xFF,0xE7,0xBF,0xFE, +0xFF,0xBF,0xFF,0xFF,0xFF,0xFF,0xFC,0xBF, 0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xC4, +0x6B,0xFF,0x29,0x1F,0xFB,0xAF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xEF,0x1B,0xFE,0xFF,0xFC, +0x6F,0xFF,0xFF,0xFD,0x6A,0xF7,0xD7,0xF5, 0xBF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFE,0xBF,0xFF,0xFF,0xFA,0xFF,0xFF,0xF7, 0xFB,0xDD,0xBF,0xFF,0xE7,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0x7F,0xFF, 0xFF,0xF5,0xFF,0xFF,0xF7,0xFD,0xB3,0xEF, +0xFD,0x7E,0x5D,0xFF,0xFD,0xFF,0xFF,0xFF, 0xFD,0x7F,0xD2,0xF5,0xFB,0x7E,0xCB,0xB7, +0xFF,0xFF,0xFF,0xC6,0xFF,0xFD,0xEE,0x63, 0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0xFD,0x65, +0x5B,0xDF,0xFF,0xD5,0xFF,0xFF,0xFF,0xF6, 0xE7,0xBF,0xF7,0xA9,0xFF,0xFF,0xED,0xFF, +0xFF,0xFF,0xFF,0xFF,0xEB,0xFF,0xFF,0xFF, 0xAF,0xFF,0xFF,0xFF,0xF8,0x1B,0xFF,0xE3, +0xD0,0xBF,0xFF,0xE1,0xFF,0xFF,0xFF,0xFF, 0xFF,0xD7,0xFF,0xFF,0xFF,0x5F,0xFF,0xFF, +0xFF,0xFF,0xAF,0xFF,0xDB,0x76,0xBF,0xFF, 0x7F,0xFF,0xBF,0xEF,0xFE,0xFF,0xBF,0xEF, +0xFB,0xFE,0xFF,0xFF,0xFF,0xBF,0xF2,0x7F, 0xFF,0x9F,0xFE,0xBD,0xFE,0x7F,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xF7,0x3F,0xEC,0x7F,0xF6,0x95,0xBB, +0xEF,0xF8,0xFE,0xFC,0xBF,0x2F,0xDA,0xFC, 0xBF,0x2F,0xCB,0xF2,0xFC,0xBF,0xEF,0xFF, +0xA9,0xBF,0xCF,0xFB,0xFF,0xFF,0xFF,0xFE, 0xDD,0xB7,0x6D,0xF6,0xD9,0xB6,0x6D,0x9B, +0x76,0xD9,0xBF,0xFB,0xFD,0xA3,0xFF,0xBF, 0xEF,0xFF,0xEF,0xFF,0xFF,0xFF,0x7F,0xDF, +0xFD,0xEF,0x7B,0xDE,0xF7,0xFD,0xEF,0x7F, 0xFF,0xFF,0x05,0xFF,0xFA,0xFE,0x7F,0xEF, +0xE3,0xFF,0xFF,0xFD,0x7F,0xFF,0xFF,0xFF, 0xFF,0x5F,0xFF,0xFF,0xFD,0x7F,0xFB,0xAF, +0xFF,0x63,0xC8,0xFF,0xBF,0xEF,0xFF,0xFF, 0xFA,0x7F,0xFF,0xFF,0xFF,0xFE,0x9F,0xF7, +0xFF,0xFA,0xBF,0xFE,0x9F,0xFB,0x7F,0xFF, 0xFF,0xEF,0xD7,0xFF,0xFF,0xF5,0xFF,0xFF, +0xFF,0xFF,0xFD,0x7F,0xFF,0xFF,0xBF,0xFF, 0xF9,0xBF,0xFF,0xBE,0x27,0x9F,0xE7,0xF9, +0xFE,0x7F,0x8B,0xE7,0xFE,0x7F,0x9F,0xE2, 0xF9,0xFE,0x7F,0x9F,0xE7,0xF1,0x7F,0xFF, +0xFF,0xFF,0xFB,0xFE,0xFF,0xFF,0xFF,0xD7, 0xFF,0xFF,0xFF,0xFF,0xF5,0xFF,0xFF,0xFF, +0xD7,0xFF,0xFA,0xFF,0xFE,0xFF,0xFF,0xFF, 0xFD,0xFF,0xFF,0xFF,0xAF,0xF7,0xFF,0xFF, +0xFF,0xEB,0xFF,0xFF,0xFF,0xAF,0xFF,0xC4, 0xFF,0xF7,0xFF,0xFF,0xEF,0xFF,0xFF,0xFF, +0xFF,0x5F,0xFF,0xFF,0xFF,0xFF,0xD7,0xFF, 0xFF,0xFF,0xFF,0xFF,0xEB,0xFF,0xFB,0x7A, +0xDF,0xF7,0xFD,0xFF,0xFF,0xFE,0xBF,0xFF, 0xFF,0x7F,0xFF,0xAF,0xFF,0xFF,0xFF,0xF7, +0xEF,0xE3,0xFF,0xDD,0xD2,0xFF,0xDF,0xFF, 0xFF,0xF2,0xFC,0xBF,0xCB,0xF6,0xFD,0xBF, +0x2F,0xCB,0xFF,0x7F,0xDF,0xDE,0xAF,0xFF, 0xDA,0xEE,0xBF,0xAF,0xE9,0xFA,0xF4,0xBD, +0xAF,0x5A,0xAE,0xBB,0xAB,0x6B,0xDA,0xDE, 0xBF,0xAD,0xD7,0x5E,0xFF,0xFF,0xBF,0xFC, +0xFF,0xDF,0xFD,0xFF,0xFF,0xFF,0xFF,0xDF, 0xF7,0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0xFA, +0x1F,0xFF,0xFE,0xFB,0xEF,0xBF,0xFD,0xFF, 0xFD,0xBD,0x77,0xFF,0xFF,0xFF,0xFF,0x9D, +0xEF,0xFF,0xFF,0xFF,0xEF,0x7D,0xFF,0xFB, 0xFE,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF7, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE, 0xBF,0xE4,0xFB,0xFF,0xFE,0x3F,0xFE,0xFF, +0xFF,0xFF,0xFF,0xAF,0xEA,0xFE,0xBF,0xAF, 0xEB,0xFA,0xFE,0xFF,0xFF,0xFF,0x55,0xF6, +0xFF,0xFE,0xF7,0xFF,0x7F,0xFF,0xEB,0xF7, 0x5F,0xC5,0xFD,0x7F,0x5F,0xD7,0xF5,0xFF, +0x6F,0xFB,0xFF,0x8A,0xFF,0xFF,0xFF,0xFF, 0xEB,0xFF,0xFF,0xFF,0xFF,0xFB,0xBF,0xBF, +0xEF,0xFB,0xFF,0xFF,0xFF,0xFF,0xFB,0xFF, 0x77,0xDF,0xFB,0xFF,0xFD,0x7F,0xEF,0xFF, +0xFF,0xFF,0xBF,0x7F,0xFF,0xDF,0xBF,0xFF, 0xFB,0xFF,0xFF,0xFF,0xFE,0xEF,0xDF,0xFF, +0xFE,0xFF,0x9F,0xEF,0x7D,0xFF,0xF7,0xFF, 0x7F,0xFF,0xFF,0xDF,0xF7,0xFD,0xFF,0xEF, +0xDF,0xFF,0xDF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFD,0xFF,0xFF,0xFB, +0xFD,0xFF,0xBF,0xDF,0xD1,0xFF,0xF8,0x3B, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0x7E,0xDB,0xFD,0xFF,0x77,0xDB,0xB7,0x7D, 0xBF,0xFB,0xFF,0xF8,0x7F,0xED,0x7B,0x5E, +0xFF,0xFE,0xFF,0xFF,0x4F,0xD7,0xFD,0x7F, 0xDF,0xD7,0xF5,0xFF,0x7F,0xFF,0xFF,0xFF, +0xF2,0x3F,0xFE,0xFF,0xBF,0xFF,0xFF,0xFF, 0xFF,0xBF,0xEF,0xFE,0xFF,0x3B,0xEE,0xFF, +0xFC,0xEF,0xFF,0xFF,0xFF,0x85,0xFF,0xFD, 0xFE,0xFF,0xF5,0xFF,0xFF,0xFE,0xFF,0xDF, +0xFB,0xFF,0x5F,0xBF,0xFF,0xFD,0xFF,0xFF, 0xFF,0xFF,0xA8,0xFF,0xFF,0x9F,0x9E,0xFF, +0xFF,0xFF,0x7F,0xF3,0xFF,0xFF,0xCF,0xFF, 0xF7,0xFD,0xFF,0x7F,0xFF,0xFF,0xFC,0x16, +0xBF,0xCF,0xA3,0xE5,0xEF,0x7F,0xFF,0xF3, 0xE4,0xFF,0xCF,0x93,0xFC,0xFF,0x3F,0xCF, +0xFF,0xFF,0xFF,0xD6,0x0F,0x7D,0xBF,0x6E, 0xFB,0xF4,0xFC,0xAF,0x6D,0xDB,0x77,0xB7, +0x6D,0xDB,0xF6,0xFD,0xBF,0xFF,0xFF,0xFF, 0xBF,0x9B,0xFA,0xDE,0xB7,0xB7,0xED,0xF9, +0x7E,0xB7,0xAC,0xEB,0xD6,0xB3,0xAD,0xEB, 0x7A,0xDF,0xFF,0xFF,0xFF,0xD8,0xBF,0xFF, +0xB7,0xED,0x9F,0x6F,0xDD,0xF7,0x68,0xDB, 0x37,0xB3,0x6C,0xDB,0x36,0xCD,0xB3,0x7F, +0xFF,0x7F,0xF5,0x6F,0xFD,0xEF,0x79,0x3D, 0xF7,0x93,0xE4,0x7A,0x9E,0xAD,0xEA,0x7A, +0x9E,0xF7,0xBD,0xEF,0xFF,0xFF,0xFF,0x76, 0x7F,0xFB,0xC6,0xFF,0xBB,0xEF,0xDA,0xFE, +0xFD,0xBF,0xFB,0xFE,0xFF,0xBF,0xEF,0xFB, 0xFF,0xFF,0xFB,0xFF,0xA5,0xFF,0xFD,0xAB, +0x6F,0x78,0xDE,0x17,0x8F,0x79,0xDF,0xFD, 0xFF,0x7F,0xDF,0xF7,0xFD,0xFF,0xFF,0xFB, +0xFF,0xFB,0xFF,0xEF,0xFB,0xEF,0xFB,0xFE, 0xFF,0xBB,0xDA,0xF3,0xEF,0x3B,0xCE,0xF3, +0xBC,0xEF,0x3F,0xCF,0xDF,0xFF,0xB7,0xFF, 0xFF,0xFF,0xCF,0x73,0xFF,0xBF,0xEF,0xFF, +0xF3,0xFF,0x3F,0xCF,0xF3,0xFC,0xFF,0x3D, 0xCF,0x9F,0xFE,0x07,0xFF,0xAF,0xEB,0xFE, +0xFD,0xBF,0xEF,0xEB,0xFA,0xFF,0xAF,0xEB, 0xFA,0xFE,0xBF,0xAF,0xFB,0xFE,0x3F,0xFB, +0x9B,0xFF,0x7F,0xDF,0xFF,0xF3,0xFE,0xFF, 0xDE,0xF7,0xBF,0x7B,0xDE,0xF7,0xBD,0xEF, +0x7B,0xFE,0xFF,0xFF,0xDF,0x3F,0xFE,0xFF, 0xB7,0xFF,0xEF,0xF7,0xFF,0xBF,0xED,0xFE, +0xDF,0xB7,0xED,0xFB,0x7E,0xDF,0xFF,0xFF, 0xFF,0xFD,0x5F,0xEF,0xEB,0xFA,0xFE,0xF5, +0xBF,0x6F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFE,0xF8,0xFF,0xA8,0xFF, +0xFF,0xBF,0xEF,0xFB,0x6A,0xFB,0xB7,0xEF, 0xFB,0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xBF, +0xEF,0xFB,0xFF,0xE0,0xFF,0xFF,0xFD,0x7F, 0x5C,0xD7,0x7D,0xDF,0xF3,0x5C,0xF5,0xCD, +0x73,0x5E,0xD7,0xB5,0xFD,0x7F,0xEF,0xFF, 0xDB,0xFF,0xFF,0xE2,0xF8,0xBE,0x2F,0x8F, +0xE7,0xF8,0xBE,0x6B,0xE2,0xF8,0xBE,0x2F, 0x8B,0xE2,0xF9,0xFE,0x7F,0xE7,0xFF,0xD7, +0xF5,0xFD,0x7F,0xFF,0xF7,0xF5,0xFD,0x7F, 0xD7,0xF5,0xFD,0x7F,0x5F,0xD7,0xF5,0xFF, +0xFF,0xFF,0x8F,0xFF,0xAF,0xEB,0xFA,0xFF, 0xFF,0xBF,0xEB,0xFA,0xFF,0x2F,0xEB,0xFA, +0xFE,0xBF,0xAF,0xEB,0xFF,0xFF,0xFE,0x5F, 0xFF,0x5F,0xFF,0xFF,0xFD,0xFF,0xFF,0xD7, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xBF,0xFE,0xB7,0xFD, +0xFF,0x7E,0xDF,0xF7,0xAD,0xFF,0x7F,0xF7, 0xFD,0xFF,0x7F,0xDF,0xF7,0xFD,0xFF,0x7F, +0xF6,0x7F,0xFF,0xFF,0xFF,0xDB,0xF6,0xFC, 0xAF,0xFF,0xFF,0xFF,0xFF,0xF7,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0xBF,0xFF, 0xAF,0xEB,0xFA,0xF6,0xAB,0x8F,0xEB,0xFA, +0xF7,0xA5,0xEB,0xFA,0xBE,0xBF,0xAF,0xEB, 0xFA,0xFF,0x6D,0xFF,0xFF,0x7F,0xDF,0x33, +0xDD,0xFF,0x7F,0xFE,0xF7,0xFC,0x7F,0xFB, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xA9, +0xFF,0xFD,0xFF,0xFF,0xFE,0xFF,0xFF,0xDF, 0xFF,0xFF,0xEF,0xEF,0xFD,0xFF,0x7F,0xFF, +0xFF,0xFF,0xFF,0xFE,0xA7,0xFF,0xFF,0xFF, 0x77,0xDF,0xF7,0xFD,0x9F,0x7F,0xFE,0x77, +0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xAF,0xBF,0xAF,0xFF,0xF9,0xBE,0xBF, +0x8F,0xFB,0xFE,0xFE,0xEF,0xFB,0xFE,0xFF, 0xBF,0xEF,0xFB,0xFF,0xFF,0xFD,0xDF,0x6F, +0xEF,0xFF,0x7F,0xFF,0xBF,0xBF,0xDF,0xFF, 0xFC,0xFF,0xDF,0xF7,0xFD,0xEF,0x7F,0xDF, +0xFF,0xFF,0xFF,0x3F,0xF6,0xFF,0xCF,0xFF, 0xDB,0xFB,0xF7,0xFF,0xEB,0x7A,0xFF,0xFF, +0xFF,0xBF,0xEF,0xFB,0xFF,0xFF,0xFF,0xFE, 0x6D,0xFD,0xFF,0x5F,0xFB,0xFF,0xFF,0xF7, +0xFF,0x5F,0xF5,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xF8,0xFF,0xFB,0xFF, +0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xE7,0xF6, 0xBF,0xFF,0xFF,0xFF,0xFF,0xFB,0xFF,0xFF, +0xFF,0xC9,0xFF,0xFF,0xFF,0xBD,0xFF,0xBF, 0xAF,0xEF,0xEF,0x3F,0xD1,0xFC,0x7F,0xFB, +0xC7,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0xFF, 0xFF,0xFF,0xFF,0xFD,0xFF,0xFF,0x77,0xFF, +0xDF,0xB7,0xFD,0xF7,0xFD,0xF7,0xFF,0xFF, 0xFF,0xFF,0xFF,0x57,0xFF,0xF7,0xA5,0xFD, +0x3F,0xDF,0xBF,0xBF,0xFE,0x7F,0xFF,0xFF, 0xFF,0xDF,0xFA,0xFD,0xFF,0xFF,0xFF,0xFE, +0x87,0xFF,0xE9,0xFF,0xFE,0xEF,0xBF,0xEF, 0xFE,0xFE,0xFF,0xEF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFA,0x9F,0xFF,0x3F, 0xFF,0xFD,0xFD,0x57,0xDF,0xFD,0xF3,0xFF, +0xDF,0xFD,0xFF,0x5F,0xDF,0xF5,0xFD,0xFF, 0xFF,0xF9,0x8F,0xFF,0xFF,0xFF,0xEE,0x7F, +0xFF,0xFF,0xBF,0x5E,0xFE,0xEC,0xFB,0x3F, 0x7F,0x9F,0xEF,0xF9,0xFF,0xFF,0xCD,0x6B, +0xFF,0xFF,0xFF,0xC5,0xF3,0xFC,0xFA,0x38, 0xFF,0xAF,0x3F,0xEE,0x7F,0x9F,0xFF,0xD9, +0xFF,0xFF,0xFD,0x7A,0xF7,0xFF,0xF3,0xFF, 0xAF,0x6F,0xDB,0xF2,0xB9,0xE9,0xFB,0xFF, +0xFF,0xFF,0xFE,0xFF,0xFF,0xEF,0xFF,0xFB, 0xC5,0xBF,0xFF,0xEF,0xFF,0x5E,0xB7,0xAD, +0xCD,0x79,0x7C,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFD,0x93,0xFF,0xEF, +0xEA,0xFE,0xBF,0xEF,0x5B,0xD2,0xCD,0xF5, 0x6D,0x77,0xDF,0xF7,0xFD,0xFF,0x7F,0xDF, +0xFF,0xFF,0x66,0xFF,0xD5,0x65,0x7D,0x5F, 0x75,0x9D,0x65,0x7F,0xD6,0xFB,0x4F,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,0xC7, 0xFF,0xBF,0xEF,0xFA,0xFE,0xFF,0xBF,0xEB, +0xFF,0xDF,0xFF,0x7E,0xFF,0xFF,0xEF,0xFD, 0x7E,0xD7,0xFF,0x78,0xDF,0xFF,0x5F,0xDF, +0xF5,0xBF,0x7F,0xDF,0xC5,0xFF,0x3F,0xF6, 0x7E,0xFF,0x0F,0xEF,0xF2,0x3E,0xBF,0xFF, +0xFB,0x3F,0xFF,0xFB,0x7F,0xFF,0xB3,0xFE, 0xFB,0xF6,0xFD,0xFF,0xDA,0xF7,0xFD,0xFF, +0x7F,0xDF,0xF7,0xBF,0xFF,0xFA,0x7F,0xFF, 0xFF,0xFF,0xFF,0x9F,0xFF,0xF3,0xDC,0xF9, +0xBF,0xCE,0xE7,0xF9,0xFE,0x7F,0x9F,0xE7, 0xFF,0xFF,0xE2,0x7F,0xFE,0xFF,0xBF,0xEF, +0xEB,0xFA,0xFF,0x9F,0x67,0x1E,0xFF,0x8F, 0xE7,0xF8,0xFE,0x7F,0x8F,0xEF,0xFF,0xBD, +0xBF,0xFF,0xFB,0xFF,0xFF,0xDF,0xF7,0xFF, 0xFC,0xFF,0xBF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFD,0xB3,0xFF,0xFF,0xEF, 0xFF,0xFF,0xBF,0xED,0xFF,0xFB,0xEE,0xFE, +0xFF,0xFF,0xEF,0xFF,0xFE,0xFF,0xFF,0xFF, 0xFF,0xB5,0xFF,0xB7,0xFD,0xFD,0x6E,0xFF, +0xFF,0xFE,0xFD,0x2F,0xD8,0xFE,0xBF,0x8F, 0xEB,0xF9,0xFE,0x3F,0xFF,0xFA,0xCF,0xFF, +0xE7,0xD9,0xFA,0xBF,0xDF,0x77,0xFC,0xFB, 0x3F,0xAB,0xFE,0xFF,0xBF,0xEF,0xFB,0xFE, +0xFF,0xFF,0xEE,0x1F,0xFF,0xDF,0xF7,0xFF, 0xFF,0xFF,0x5F,0x97,0x35,0xBF,0x5E,0xFE, +0xBF,0xEF,0xFF,0xF7,0xFD,0xFF,0xFF,0xFA, 0xBF,0xFF,0xBE,0x6F,0x9F,0xE7,0xF8,0xBE, +0x2F,0x8B,0x66,0x94,0x7D,0x9D,0xE7,0xF9, 0xFE,0x7F,0x9F,0xE7,0xF1,0x7F,0xFF,0xFF, +0xFF,0xF7,0xF5,0xFD,0x7F,0x5F,0xFB,0xFD, 0x9E,0xFF,0xFB,0xFE,0xFF,0xFF,0xEF,0xFF, +0xFF,0xA0,0xFF,0xFF,0xFF,0xBF,0xEF,0xEB, 0xFA,0xFE,0xBF,0xB7,0xF7,0xF7,0xFF,0xFF, +0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xDD,0xFF, 0xFD,0xFF,0xFF,0xFF,0xD7,0xFF,0xFF,0xFF, +0x7F,0xF5,0xFF,0xFF,0xEF,0xFF,0xFF,0xFF, 0xBF,0xFF,0xFF,0xAB,0xFE,0xFB,0xFE,0xFF, +0xF7,0xAF,0xFF,0xFF,0xDE,0xF7,0xEB,0x5F, 0xDF,0xF7,0xFD,0xFF,0x7F,0xDF,0xFF,0xFF, +0xB3,0xFF,0xC9,0xFE,0xFF,0xFF,0xFF,0xFF, 0xD6,0xFF,0xFF,0xCB,0xFF,0xFF,0xDF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFC,0x8F,0xFF,0xBA, 0xBE,0xBF,0xAF,0xEB,0x78,0xFE,0xB7,0xAD, +0x3A,0xFE,0xB7,0xAF,0xEB,0x7A,0xFE,0xBF, 0xAF,0xFF,0x9F,0xFF,0xFF,0xDF,0xFC,0xFF, +0xFF,0xFE,0xC3,0xFE,0xFF,0xFF,0x33,0xFC, 0xFF,0xBF,0xDF,0xF3,0xFF,0xFF,0xBB,0x9F, +0xFF,0xFF,0xFF,0xEB,0xDF,0xFF,0xFF,0xAF, 0xF7,0x6F,0xF9,0xBF,0xEF,0xFD,0xFF,0xFF, +0xFF,0xFF,0xFF,0xE3,0x7F,0xFF,0xFF,0xFF, 0xFB,0xFF,0xFF,0xBF,0xFD,0xFB,0xF7,0xFF, +0xDF,0xF7,0xFF,0xFE,0xEF,0x5F,0xBD,0xFF, 0xFA,0xFF,0xF8,0xFF,0xBF,0xAF,0xFB,0xFE, +0xFE,0x3F,0xEF,0xE8,0xFF,0xDF,0xF3,0xFD, 0xFF,0xFF,0xFF,0xFF,0xFF,0xED,0xFF,0xFB, +0xFD,0xFF,0xAF,0xFF,0xFF,0xFE,0xFE,0xBF, 0xDB,0xFF,0xFF,0xFF,0xBF,0xFF,0xDF,0xFF, +0xFD,0xFF,0xCB,0xFF,0xFF,0xFF,0xFF,0xFF, 0xBF,0x6F,0xFF,0x7F,0xB7,0xB3,0xFF,0xFF, +0xDF,0xFF,0xFB,0xEF,0xFF,0xFF,0xFF,0x07, 0xFF,0xFB,0xFF,0xFF,0xFF,0xED,0xFF,0xF5, +0x7C,0xFF,0x7F,0xFE,0xFF,0xFF,0xEF,0xCF, 0xFF,0xFB,0xFF,0xFF,0x2F,0xFF,0xFF,0xFF, +0xFF,0xF3,0xFF,0xFB,0xFF,0xFE,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xBF,0xFF,0xFF,0xFF, +0xFD,0x1B,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFE,0x7C,0xFF,0xFF,0xFF,0xFF, +0xEF,0xFF,0xFF,0xFF,0xFF,0xFB,0xBF,0x7F, 0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xDB,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFD, 0xFF,0xFF,0xF0,0x7F,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xFF,0xDF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFD,0xBF,0xFE, +0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xEF,0xFE,0xFF,0xBF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xEF,0xFA,0xB5,0xFF,0xFF,0xFF, 0xF7,0xF7,0xFF,0xFF,0xFF,0xFF,0xDF,0xFB, +0xFC,0xFF,0xFF,0xFE,0xFF,0x7F,0xDF,0xBF, 0xFF,0xCB,0xBF,0xF9,0xFE,0x7F,0x9F,0xE7, +0xF9,0xFE,0x7F,0x97,0xE1,0xFE,0x79,0x9F, 0xE7,0xFD,0xFE,0x7F,0xDF,0xFE,0x37,0xFF, +0xFB,0xDE,0xDE,0xBD,0xEF,0xF3,0xFE,0xFB, 0xAF,0xEB,0xFE,0xFF,0xFF,0xCF,0xFF,0xFE, +0xFF,0xBF,0xFF,0x8F,0xFF,0xEF,0xFB,0xFE, 0xFF,0xBF,0xE7,0xF9,0x5E,0x7F,0xEF,0xFB, +0xDA,0xFF,0xBF,0xEF,0xFB,0xFE,0xFF,0xFD, 0x1F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDF, +0xFF,0xFF,0x7F,0xFF,0xFF,0xF7,0xFB,0x7F, 0xFF,0xFF,0xFF,0xFF,0xFC,0x3F,0xFF,0xBF, +0xEF,0xFB,0xFE,0xFF,0xBF,0xEF,0x7B,0x7F, 0xBF,0xEF,0xFB,0xFE,0xFF,0xB5,0xEF,0xFB, +0xBF,0xFA,0x7F,0xFC,0xFF,0x3F,0xCF,0xF3, 0xFC,0xFF,0x3F,0xCF,0xBC,0xFF,0x3F,0xEF, +0xF3,0xFC,0xFE,0x3F,0xCF,0xFF,0xEE,0xEF, 0xFB,0xFE,0xFF,0xBF,0xEF,0xFB,0x6A,0xD7, +0xB7,0xFB,0xF8,0xFF,0xB7,0xEF,0xBA,0xFE, 0xFF,0xBF,0x7F,0xE9,0xFF,0xF9,0x7E,0x5F, +0x97,0xE5,0xF9,0xFE,0x7F,0xBF,0xF9,0x7E, 0x5F,0x9F,0xE5,0xFB,0xFE,0x5F,0xB7,0xFF, +0xA3,0xFF,0xF7,0xFD,0xFF,0x7F,0xDF,0xF7, 0xFD,0xFF,0x5E,0xF7,0x7D,0xFF,0x77,0xDF, +0xF7,0xFD,0xFF,0x7F,0xFF,0xD7,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFD,0xDF,0xFB,0x7F, +0xFF,0xFF,0xEF,0xFF,0xFE,0xFB,0xFF,0xFF, 0xBF,0xFE,0x8F,0xFF,0xDF,0xF7,0xFD,0xFD, +0x7F,0xDF,0xF7,0xFD,0x3E,0xDF,0xF5,0xBD, 0xFF,0x7F,0xDF,0xF7,0xFD,0xF7,0xFF,0x9F, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFD,0xFF,0xBE,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFD,0x3F,0xFF,0xDF,0xF7, 0xFD,0xFF,0x7F,0xDF,0xF7,0xFD,0xFF,0xCF, +0x77,0xFC,0xFF,0x5F,0xDF,0xF7,0xFD,0xFF, 0xF4,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFD,0xFF,0xFF,0xFF,0xEE,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xED,0xFB,0xFF,0xFF,0xBF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xE9,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFB,0xFF,0xFF,0xFF,0xD3,0xFF,0xFF, +0xBF,0x3F,0xFB,0xFF,0xFF,0xFF,0xFB,0xF3, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xF7, 0xFF,0xFF,0xFF,0xFF,0x17,0xFF,0xFF,0xFF, +0xDF,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF, 0xDF,0xDF,0xFF,0xFD,0xFF,0xFF,0xDF,0xF7, +0xFF,0x4F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFD, +0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x9F,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xFF, 0xFF,0xFF,0x7A,0x3F,0xFF,0xFF,0xFF,0xFF, +0xFF,0xFF,0xFF,0x7F,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF2, +0x7F,0xFF,0xFB,0xFE,0xFF,0xBF,0xEF,0xF8, 0xFE,0xFF,0xBF,0xFB,0xFE,0xFF,0x8F,0xEC, +0xFB,0xFE,0xFF,0xBF,0xF8,0xF7,0xFE,0xFF, 0xBF,0xEF,0xFB,0xFE,0xFD,0xBF,0xCF,0xEC, +0xFF,0x3F,0xEF,0xDB,0xF8,0xFF,0xBF,0xCF, 0xFF,0xF9,0xFF,0xFF,0xBF,0xFF,0xFB,0xFF, +0xFF,0xFF,0xEF,0xFB,0xDF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xBF,0xFF,0xFF,0xFF,0xBB,0xFF, +0xEF,0xFB,0xFE,0xEF,0xBF,0xEE,0xEB,0xFB, 0xFE,0xFF,0xEF,0xFE,0xEE,0xBF,0xFE,0xEB, +0xFF,0xEF,0xFF,0x17,0xFF,0x7E,0xEB,0xBB, 0xFE,0xBF,0xBE,0xFB,0xEF,0x5B,0xF7,0xBD, +0xFB,0xCF,0xBF,0xBF,0xBB,0xFB,0x7E,0xCC, 0xEF,0xFF + +}; diff --git a/linux/drivers/media/video/dabusb.c b/linux/drivers/media/video/dabusb.c new file mode 100644 index 000000000..b9ba95f5e --- /dev/null +++ b/linux/drivers/media/video/dabusb.c @@ -0,0 +1,874 @@ +/*****************************************************************************/ + +/* + * dabusb.c -- dab usb driver. + * + * Copyright (C) 1999 Deti Fliegl (deti@fliegl.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. + * + * + * + * $Id: dabusb.c,v 1.54 2000/07/24 21:39:39 deti Exp $ + * + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/socket.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <asm/atomic.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/smp_lock.h> +#include <linux/mutex.h> + +#include "dabusb.h" +#include "dabfirmware.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.54" +#define DRIVER_AUTHOR "Deti Fliegl, deti@fliegl.de" +#define DRIVER_DESC "DAB-USB Interface Driver for Linux (c)1999" + +/* --------------------------------------------------------------------- */ + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define NRDABUSB 256 +#else +#define NRDABUSB 4 +#endif + +/*-------------------------------------------------------------------*/ + +static dabusb_t dabusb[NRDABUSB]; +static int buffers = 256; +static struct usb_driver dabusb_driver; + +/*-------------------------------------------------------------------*/ + +static int dabusb_add_buf_tail (pdabusb_t s, struct list_head *dst, struct list_head *src) +{ + unsigned long flags; + struct list_head *tmp; + int ret = 0; + + spin_lock_irqsave (&s->lock, flags); + + if (list_empty (src)) { + // no elements in source buffer + ret = -1; + goto err; + } + tmp = src->next; + list_move_tail (tmp, dst); + + err: spin_unlock_irqrestore (&s->lock, flags); + return ret; +} +/*-------------------------------------------------------------------*/ +#ifdef DEBUG +static void dump_urb (struct urb *urb) +{ + dbg("urb :%p", urb); + dbg("dev :%p", urb->dev); + dbg("pipe :%08X", urb->pipe); + dbg("status :%d", urb->status); + dbg("transfer_flags :%08X", urb->transfer_flags); + dbg("transfer_buffer :%p", urb->transfer_buffer); + dbg("transfer_buffer_length:%d", urb->transfer_buffer_length); + dbg("actual_length :%d", urb->actual_length); + dbg("setup_packet :%p", urb->setup_packet); + dbg("start_frame :%d", urb->start_frame); + dbg("number_of_packets :%d", urb->number_of_packets); + dbg("interval :%d", urb->interval); + dbg("error_count :%d", urb->error_count); + dbg("context :%p", urb->context); + dbg("complete :%p", urb->complete); +} +#endif +/*-------------------------------------------------------------------*/ +static int dabusb_cancel_queue (pdabusb_t s, struct list_head *q) +{ + unsigned long flags; + pbuff_t b; + + dbg("dabusb_cancel_queue"); + + spin_lock_irqsave (&s->lock, flags); + + list_for_each_entry(b, q, buff_list) { +#ifdef DEBUG + dump_urb(b->purb); +#endif + usb_unlink_urb (b->purb); + } + spin_unlock_irqrestore (&s->lock, flags); + return 0; +} +/*-------------------------------------------------------------------*/ +static int dabusb_free_queue (struct list_head *q) +{ + struct list_head *tmp; + struct list_head *p; + pbuff_t b; + + dbg("dabusb_free_queue"); + for (p = q->next; p != q;) { + b = list_entry (p, buff_t, buff_list); + +#ifdef DEBUG + dump_urb(b->purb); +#endif + kfree(b->purb->transfer_buffer); + usb_free_urb(b->purb); + tmp = p->next; + list_del (p); + kfree (b); + p = tmp; + } + + return 0; +} +/*-------------------------------------------------------------------*/ +static int dabusb_free_buffers (pdabusb_t s) +{ + unsigned long flags; + dbg("dabusb_free_buffers"); + + spin_lock_irqsave(&s->lock, flags); + + dabusb_free_queue (&s->free_buff_list); + dabusb_free_queue (&s->rec_buff_list); + + spin_unlock_irqrestore(&s->lock, flags); + + s->got_mem = 0; + return 0; +} +/*-------------------------------------------------------------------*/ +static void dabusb_iso_complete (struct urb *purb, struct pt_regs *regs) +{ + pbuff_t b = purb->context; + pdabusb_t s = b->s; + int i; + int len; + int dst = 0; + void *buf = purb->transfer_buffer; + + dbg("dabusb_iso_complete"); + + // process if URB was not killed + if (purb->status != -ENOENT) { + unsigned int pipe = usb_rcvisocpipe (purb->dev, _DABUSB_ISOPIPE); + int pipesize = usb_maxpacket (purb->dev, pipe, usb_pipeout (pipe)); + for (i = 0; i < purb->number_of_packets; i++) + if (!purb->iso_frame_desc[i].status) { + len = purb->iso_frame_desc[i].actual_length; + if (len <= pipesize) { + memcpy (buf + dst, buf + purb->iso_frame_desc[i].offset, len); + dst += len; + } + else + err("dabusb_iso_complete: invalid len %d", len); + } + else + warn("dabusb_iso_complete: corrupted packet status: %d", purb->iso_frame_desc[i].status); + if (dst != purb->actual_length) + err("dst!=purb->actual_length:%d!=%d", dst, purb->actual_length); + } + + if (atomic_dec_and_test (&s->pending_io) && !s->remove_pending && s->state != _stopped) { + s->overruns++; + err("overrun (%d)", s->overruns); + } + wake_up (&s->wait); +} +/*-------------------------------------------------------------------*/ +static int dabusb_alloc_buffers (pdabusb_t s) +{ + int buffers = 0; + pbuff_t b; + unsigned int pipe = usb_rcvisocpipe (s->usbdev, _DABUSB_ISOPIPE); + int pipesize = usb_maxpacket (s->usbdev, pipe, usb_pipeout (pipe)); + int packets = _ISOPIPESIZE / pipesize; + int transfer_buffer_length = packets * pipesize; + int i; + + dbg("dabusb_alloc_buffers pipesize:%d packets:%d transfer_buffer_len:%d", + pipesize, packets, transfer_buffer_length); + + while (buffers < (s->total_buffer_size << 10)) { + b = (pbuff_t) kzalloc (sizeof (buff_t), GFP_KERNEL); + if (!b) { + err("kzalloc(sizeof(buff_t))==NULL"); + goto err; + } + b->s = s; + b->purb = usb_alloc_urb(packets, GFP_KERNEL); + if (!b->purb) { + err("usb_alloc_urb == NULL"); + kfree (b); + goto err; + } + + b->purb->transfer_buffer = kmalloc (transfer_buffer_length, GFP_KERNEL); + if (!b->purb->transfer_buffer) { + kfree (b->purb); + kfree (b); + err("kmalloc(%d)==NULL", transfer_buffer_length); + goto err; + } + + b->purb->transfer_buffer_length = transfer_buffer_length; + b->purb->number_of_packets = packets; + b->purb->complete = dabusb_iso_complete; + b->purb->context = b; + b->purb->dev = s->usbdev; + b->purb->pipe = pipe; + b->purb->transfer_flags = URB_ISO_ASAP; + + for (i = 0; i < packets; i++) { + b->purb->iso_frame_desc[i].offset = i * pipesize; + b->purb->iso_frame_desc[i].length = pipesize; + } + + buffers += transfer_buffer_length; + list_add_tail (&b->buff_list, &s->free_buff_list); + } + s->got_mem = buffers; + + return 0; + + err: + dabusb_free_buffers (s); + return -ENOMEM; +} +/*-------------------------------------------------------------------*/ +static int dabusb_bulk (pdabusb_t s, pbulk_transfer_t pb) +{ + int ret; + unsigned int pipe; + int actual_length; + + dbg("dabusb_bulk"); + + if (!pb->pipe) + pipe = usb_rcvbulkpipe (s->usbdev, 2); + else + pipe = usb_sndbulkpipe (s->usbdev, 2); + + ret=usb_bulk_msg(s->usbdev, pipe, pb->data, pb->size, &actual_length, 100); + if(ret<0) { + err("dabusb: usb_bulk_msg failed(%d)",ret); + + if (usb_set_interface (s->usbdev, _DABUSB_IF, 1) < 0) { + err("set_interface failed"); + return -EINVAL; + } + + } + + if( ret == -EPIPE ) { + warn("CLEAR_FEATURE request to remove STALL condition."); + if(usb_clear_halt(s->usbdev, usb_pipeendpoint(pipe))) + err("request failed"); + } + + pb->size = actual_length; + return ret; +} +/* --------------------------------------------------------------------- */ +static int dabusb_writemem (pdabusb_t s, int pos, unsigned char *data, int len) +{ + int ret; + unsigned char *transfer_buffer = kmalloc (len, GFP_KERNEL); + + if (!transfer_buffer) { + err("dabusb_writemem: kmalloc(%d) failed.", len); + return -ENOMEM; + } + + memcpy (transfer_buffer, data, len); + + ret=usb_control_msg(s->usbdev, usb_sndctrlpipe( s->usbdev, 0 ), 0xa0, 0x40, pos, 0, transfer_buffer, len, 300); + + kfree (transfer_buffer); + return ret; +} +/* --------------------------------------------------------------------- */ +static int dabusb_8051_reset (pdabusb_t s, unsigned char reset_bit) +{ + dbg("dabusb_8051_reset: %d",reset_bit); + return dabusb_writemem (s, CPUCS_REG, &reset_bit, 1); +} +/* --------------------------------------------------------------------- */ +static int dabusb_loadmem (pdabusb_t s, const char *fname) +{ + int ret; + PINTEL_HEX_RECORD ptr = firmware; + + dbg("Enter dabusb_loadmem (internal)"); + + ret = dabusb_8051_reset (s, 1); + while (ptr->Type == 0) { + + dbg("dabusb_writemem: %04X %p %d)", ptr->Address, ptr->Data, ptr->Length); + + ret = dabusb_writemem (s, ptr->Address, ptr->Data, ptr->Length); + if (ret < 0) { + err("dabusb_writemem failed (%d %04X %p %d)", ret, ptr->Address, ptr->Data, ptr->Length); + break; + } + ptr++; + } + ret = dabusb_8051_reset (s, 0); + + dbg("dabusb_loadmem: exit"); + + return ret; +} +/* --------------------------------------------------------------------- */ +static int dabusb_fpga_clear (pdabusb_t s, pbulk_transfer_t b) +{ + b->size = 4; + b->data[0] = 0x2a; + b->data[1] = 0; + b->data[2] = 0; + b->data[3] = 0; + + dbg("dabusb_fpga_clear"); + + return dabusb_bulk (s, b); +} +/* --------------------------------------------------------------------- */ +static int dabusb_fpga_init (pdabusb_t s, pbulk_transfer_t b) +{ + b->size = 4; + b->data[0] = 0x2c; + b->data[1] = 0; + b->data[2] = 0; + b->data[3] = 0; + + dbg("dabusb_fpga_init"); + + return dabusb_bulk (s, b); +} +/* --------------------------------------------------------------------- */ +static int dabusb_fpga_download (pdabusb_t s, const char *fname) +{ + pbulk_transfer_t b = kmalloc (sizeof (bulk_transfer_t), GFP_KERNEL); + unsigned int blen, n; + int ret; + unsigned char *buf = bitstream; + + dbg("Enter dabusb_fpga_download (internal)"); + + if (!b) { + err("kmalloc(sizeof(bulk_transfer_t))==NULL"); + return -ENOMEM; + } + + b->pipe = 1; + ret = dabusb_fpga_clear (s, b); + mdelay (10); + blen = buf[73] + (buf[72] << 8); + + dbg("Bitstream len: %i", blen); + + b->data[0] = 0x2b; + b->data[1] = 0; + b->data[2] = 0; + b->data[3] = 60; + + for (n = 0; n <= blen + 60; n += 60) { + // some cclks for startup + b->size = 64; + memcpy (b->data + 4, buf + 74 + n, 60); + ret = dabusb_bulk (s, b); + if (ret < 0) { + err("dabusb_bulk failed."); + break; + } + mdelay (1); + } + + ret = dabusb_fpga_init (s, b); + kfree (b); + + dbg("exit dabusb_fpga_download"); + + return ret; +} + +static int dabusb_stop (pdabusb_t s) +{ + dbg("dabusb_stop"); + + s->state = _stopped; + dabusb_cancel_queue (s, &s->rec_buff_list); + + dbg("pending_io: %d", s->pending_io.counter); + + s->pending_io.counter = 0; + return 0; +} + +static int dabusb_startrek (pdabusb_t s) +{ + if (!s->got_mem && s->state != _started) { + + dbg("dabusb_startrek"); + + if (dabusb_alloc_buffers (s) < 0) + return -ENOMEM; + dabusb_stop (s); + s->state = _started; + s->readptr = 0; + } + + if (!list_empty (&s->free_buff_list)) { + pbuff_t end; + int ret; + + while (!dabusb_add_buf_tail (s, &s->rec_buff_list, &s->free_buff_list)) { + + dbg("submitting: end:%p s->rec_buff_list:%p", s->rec_buff_list.prev, &s->rec_buff_list); + + end = list_entry (s->rec_buff_list.prev, buff_t, buff_list); + + ret = usb_submit_urb (end->purb, GFP_KERNEL); + if (ret) { + err("usb_submit_urb returned:%d", ret); + if (dabusb_add_buf_tail (s, &s->free_buff_list, &s->rec_buff_list)) + err("startrek: dabusb_add_buf_tail failed"); + break; + } + else + atomic_inc (&s->pending_io); + } + dbg("pending_io: %d",s->pending_io.counter); + } + + return 0; +} + +static ssize_t dabusb_read (struct file *file, char __user *buf, size_t count, loff_t * ppos) +{ + pdabusb_t s = (pdabusb_t) file->private_data; + unsigned long flags; + unsigned ret = 0; + int rem; + int cnt; + pbuff_t b; + struct urb *purb = NULL; + + dbg("dabusb_read"); + + if (*ppos) + return -ESPIPE; + + if (s->remove_pending) + return -EIO; + + + if (!s->usbdev) + return -EIO; + + while (count > 0) { + dabusb_startrek (s); + + spin_lock_irqsave (&s->lock, flags); + + if (list_empty (&s->rec_buff_list)) { + + spin_unlock_irqrestore(&s->lock, flags); + + err("error: rec_buf_list is empty"); + goto err; + } + + b = list_entry (s->rec_buff_list.next, buff_t, buff_list); + purb = b->purb; + + spin_unlock_irqrestore(&s->lock, flags); + + if (purb->status == -EINPROGRESS) { + if (file->f_flags & O_NONBLOCK) // return nonblocking + { + if (!ret) + ret = -EAGAIN; + goto err; + } + + interruptible_sleep_on (&s->wait); + + if (signal_pending (current)) { + if (!ret) + ret = -ERESTARTSYS; + goto err; + } + + spin_lock_irqsave (&s->lock, flags); + + if (list_empty (&s->rec_buff_list)) { + spin_unlock_irqrestore(&s->lock, flags); + err("error: still no buffer available."); + goto err; + } + spin_unlock_irqrestore(&s->lock, flags); + s->readptr = 0; + } + if (s->remove_pending) { + ret = -EIO; + goto err; + } + + rem = purb->actual_length - s->readptr; // set remaining bytes to copy + + if (count >= rem) + cnt = rem; + else + cnt = count; + + dbg("copy_to_user:%p %p %d",buf, purb->transfer_buffer + s->readptr, cnt); + + if (copy_to_user (buf, purb->transfer_buffer + s->readptr, cnt)) { + err("read: copy_to_user failed"); + if (!ret) + ret = -EFAULT; + goto err; + } + + s->readptr += cnt; + count -= cnt; + buf += cnt; + ret += cnt; + + if (s->readptr == purb->actual_length) { + // finished, take next buffer + if (dabusb_add_buf_tail (s, &s->free_buff_list, &s->rec_buff_list)) + err("read: dabusb_add_buf_tail failed"); + s->readptr = 0; + } + } + err: //mutex_unlock(&s->mutex); + return ret; +} + +static int dabusb_open (struct inode *inode, struct file *file) +{ + int devnum = iminor(inode); + pdabusb_t s; + + if (devnum < DABUSB_MINOR || devnum >= (DABUSB_MINOR + NRDABUSB)) + return -EIO; + + s = &dabusb[devnum - DABUSB_MINOR]; + + dbg("dabusb_open"); + mutex_lock(&s->mutex); + + while (!s->usbdev || s->opened) { + mutex_unlock(&s->mutex); + + if (file->f_flags & O_NONBLOCK) { + return -EBUSY; + } + msleep_interruptible(500); + + if (signal_pending (current)) { + return -EAGAIN; + } + mutex_lock(&s->mutex); + } + if (usb_set_interface (s->usbdev, _DABUSB_IF, 1) < 0) { + mutex_unlock(&s->mutex); + err("set_interface failed"); + return -EINVAL; + } + s->opened = 1; + mutex_unlock(&s->mutex); + + file->f_pos = 0; + file->private_data = s; + + return nonseekable_open(inode, file); +} + +static int dabusb_release (struct inode *inode, struct file *file) +{ + pdabusb_t s = (pdabusb_t) file->private_data; + + dbg("dabusb_release"); + + mutex_lock(&s->mutex); + dabusb_stop (s); + dabusb_free_buffers (s); + mutex_unlock(&s->mutex); + + if (!s->remove_pending) { + if (usb_set_interface (s->usbdev, _DABUSB_IF, 0) < 0) + err("set_interface failed"); + } + else + wake_up (&s->remove_ok); + + s->opened = 0; + return 0; +} + +static int dabusb_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + pdabusb_t s = (pdabusb_t) file->private_data; + pbulk_transfer_t pbulk; + int ret = 0; + int version = DABUSB_VERSION; + + dbg("dabusb_ioctl"); + + if (s->remove_pending) + return -EIO; + + mutex_lock(&s->mutex); + + if (!s->usbdev) { + mutex_unlock(&s->mutex); + return -EIO; + } + + switch (cmd) { + + case IOCTL_DAB_BULK: + pbulk = (pbulk_transfer_t) kmalloc (sizeof (bulk_transfer_t), GFP_KERNEL); + + if (!pbulk) { + ret = -ENOMEM; + break; + } + + if (copy_from_user (pbulk, (void __user *) arg, sizeof (bulk_transfer_t))) { + ret = -EFAULT; + kfree (pbulk); + break; + } + + ret=dabusb_bulk (s, pbulk); + if(ret==0) + if (copy_to_user((void __user *)arg, pbulk, + sizeof(bulk_transfer_t))) + ret = -EFAULT; + kfree (pbulk); + break; + + case IOCTL_DAB_OVERRUNS: + ret = put_user (s->overruns, (unsigned int __user *) arg); + break; + + case IOCTL_DAB_VERSION: + ret = put_user (version, (unsigned int __user *) arg); + break; + + default: + ret = -ENOIOCTLCMD; + break; + } + mutex_unlock(&s->mutex); + return ret; +} + +static struct file_operations dabusb_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = dabusb_read, + .ioctl = dabusb_ioctl, + .open = dabusb_open, + .release = dabusb_release, +}; + +static struct usb_class_driver dabusb_class = { + .name = "dabusb%d", + .fops = &dabusb_fops, + .minor_base = DABUSB_MINOR, +}; + + +/* --------------------------------------------------------------------- */ +static int dabusb_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usbdev = interface_to_usbdev(intf); + int retval; + pdabusb_t s; + + dbg("dabusb: probe: vendor id 0x%x, device id 0x%x ifnum:%d", + le16_to_cpu(usbdev->descriptor.idVendor), + le16_to_cpu(usbdev->descriptor.idProduct), + intf->altsetting->desc.bInterfaceNumber); + + /* We don't handle multiple configurations */ + if (usbdev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + if (intf->altsetting->desc.bInterfaceNumber != _DABUSB_IF && + le16_to_cpu(usbdev->descriptor.idProduct) == 0x9999) + return -ENODEV; + + + + s = &dabusb[intf->minor]; + + mutex_lock(&s->mutex); + s->remove_pending = 0; + s->usbdev = usbdev; + s->devnum = intf->minor; + + if (usb_reset_configuration (usbdev) < 0) { + err("reset_configuration failed"); + goto reject; + } + if (le16_to_cpu(usbdev->descriptor.idProduct) == 0x2131) { + dabusb_loadmem (s, NULL); + goto reject; + } + else { + dabusb_fpga_download (s, NULL); + + if (usb_set_interface (s->usbdev, _DABUSB_IF, 0) < 0) { + err("set_interface failed"); + goto reject; + } + } + dbg("bound to interface: %d", intf->altsetting->desc.bInterfaceNumber); + usb_set_intfdata (intf, s); + mutex_unlock(&s->mutex); + + retval = usb_register_dev(intf, &dabusb_class); + if (retval) { + usb_set_intfdata (intf, NULL); + return -ENOMEM; + } + + return 0; + + reject: + mutex_unlock(&s->mutex); + s->usbdev = NULL; + return -ENODEV; +} + +static void dabusb_disconnect (struct usb_interface *intf) +{ + wait_queue_t __wait; + pdabusb_t s = usb_get_intfdata (intf); + + dbg("dabusb_disconnect"); + + init_waitqueue_entry(&__wait, current); + + usb_set_intfdata (intf, NULL); + if (s) { + usb_deregister_dev (intf, &dabusb_class); + s->remove_pending = 1; + wake_up (&s->wait); + add_wait_queue(&s->remove_ok, &__wait); + set_current_state(TASK_UNINTERRUPTIBLE); + if (s->state == _started) + schedule(); + current->state = TASK_RUNNING; + remove_wait_queue(&s->remove_ok, &__wait); + + s->usbdev = NULL; + s->overruns = 0; + } +} + +static struct usb_device_id dabusb_ids [] = { + // { USB_DEVICE(0x0547, 0x2131) }, /* An2131 chip, no boot ROM */ + { USB_DEVICE(0x0547, 0x9999) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, dabusb_ids); + +static struct usb_driver dabusb_driver = { + .name = "dabusb", + .probe = dabusb_probe, + .disconnect = dabusb_disconnect, + .id_table = dabusb_ids, +}; + +/* --------------------------------------------------------------------- */ + +static int __init dabusb_init (void) +{ + int retval; + unsigned u; + + /* initialize struct */ + for (u = 0; u < NRDABUSB; u++) { + pdabusb_t s = &dabusb[u]; + memset (s, 0, sizeof (dabusb_t)); + mutex_init (&s->mutex); + s->usbdev = NULL; + s->total_buffer_size = buffers; + init_waitqueue_head (&s->wait); + init_waitqueue_head (&s->remove_ok); + spin_lock_init (&s->lock); + INIT_LIST_HEAD (&s->free_buff_list); + INIT_LIST_HEAD (&s->rec_buff_list); + } + + /* register misc device */ + retval = usb_register(&dabusb_driver); + if (retval) + goto out; + + dbg("dabusb_init: driver registered"); + + info(DRIVER_VERSION ":" DRIVER_DESC); + +out: + return retval; +} + +static void __exit dabusb_cleanup (void) +{ + dbg("dabusb_cleanup"); + + usb_deregister (&dabusb_driver); +} + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); + +module_param(buffers, int, 0); +MODULE_PARM_DESC (buffers, "Number of buffers (default=256)"); + +module_init (dabusb_init); +module_exit (dabusb_cleanup); + +/* --------------------------------------------------------------------- */ diff --git a/linux/drivers/media/video/dabusb.h b/linux/drivers/media/video/dabusb.h new file mode 100644 index 000000000..00eb34c86 --- /dev/null +++ b/linux/drivers/media/video/dabusb.h @@ -0,0 +1,85 @@ +#define _BULK_DATA_LEN 64 +typedef struct +{ + unsigned char data[_BULK_DATA_LEN]; + unsigned int size; + unsigned int pipe; +}bulk_transfer_t,*pbulk_transfer_t; + +#define DABUSB_MINOR 240 /* some unassigned USB minor */ +#define DABUSB_VERSION 0x1000 +#define IOCTL_DAB_BULK _IOWR('d', 0x30, bulk_transfer_t) +#define IOCTL_DAB_OVERRUNS _IOR('d', 0x15, int) +#define IOCTL_DAB_VERSION _IOR('d', 0x3f, int) + +#ifdef __KERNEL__ + +typedef enum { _stopped=0, _started } driver_state_t; + +typedef struct +{ + struct mutex mutex; + struct usb_device *usbdev; + wait_queue_head_t wait; + wait_queue_head_t remove_ok; + spinlock_t lock; + atomic_t pending_io; + driver_state_t state; + int remove_pending; + int got_mem; + int total_buffer_size; + unsigned int overruns; + int readptr; + int opened; + int devnum; + struct list_head free_buff_list; + struct list_head rec_buff_list; +} dabusb_t,*pdabusb_t; + +typedef struct +{ + pdabusb_t s; + struct urb *purb; + struct list_head buff_list; +} buff_t,*pbuff_t; + +typedef struct +{ + wait_queue_head_t wait; +} bulk_completion_context_t, *pbulk_completion_context_t; + + +#define _DABUSB_IF 2 +#define _DABUSB_ISOPIPE 0x09 +#define _ISOPIPESIZE 16384 + +#define _BULK_DATA_LEN 64 +// Vendor specific request code for Anchor Upload/Download +// This one is implemented in the core +#define ANCHOR_LOAD_INTERNAL 0xA0 + +// EZ-USB Control and Status Register. Bit 0 controls 8051 reset +#define CPUCS_REG 0x7F92 +#define _TOTAL_BUFFERS 384 + +#define MAX_INTEL_HEX_RECORD_LENGTH 16 + +#ifndef _BYTE_DEFINED +#define _BYTE_DEFINED +typedef unsigned char BYTE; +#endif // !_BYTE_DEFINED + +#ifndef _WORD_DEFINED +#define _WORD_DEFINED +typedef unsigned short WORD; +#endif // !_WORD_DEFINED + +typedef struct _INTEL_HEX_RECORD +{ + BYTE Length; + WORD Address; + BYTE Type; + BYTE Data[MAX_INTEL_HEX_RECORD_LENGTH]; +} INTEL_HEX_RECORD, *PINTEL_HEX_RECORD; + +#endif diff --git a/linux/drivers/media/video/dsbr100.c b/linux/drivers/media/video/dsbr100.c new file mode 100644 index 000000000..a4cee9886 --- /dev/null +++ b/linux/drivers/media/video/dsbr100.c @@ -0,0 +1,430 @@ +/* A driver for the D-Link DSB-R100 USB radio. The R100 plugs + into both the USB and an analog audio input, so this thing + only deals with initialisation and frequency setting, the + audio data has to be handled by a sound driver. + + Major issue: I can't find out where the device reports the signal + strength, and indeed the windows software appearantly just looks + at the stereo indicator as well. So, scanning will only find + stereo stations. Sad, but I can't help it. + + Also, the windows program sends oodles of messages over to the + device, and I couldn't figure out their meaning. My suspicion + is that they don't have any:-) + + You might find some interesting stuff about this module at + http://unimut.fsk.uni-heidelberg.de/unimut/demi/dsbr + + Copyright (c) 2000 Markus Demleitner <msdemlei@cl.uni-heidelberg.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 + + History: + + Version 0.40: + Markus: Updates for 2.6.x kernels, code layout changes, name sanitizing + + Version 0.30: + Markus: Updates for 2.5.x kernel and more ISO compliant source + + Version 0.25: + PSL and Markus: Cleanup, radio now doesn't stop on device close + + Version 0.24: + Markus: Hope I got these silly VIDEO_TUNER_LOW issues finally + right. Some minor cleanup, improved standalone compilation + + Version 0.23: + Markus: Sign extension bug fixed by declaring transfer_buffer unsigned + + Version 0.22: + Markus: Some (brown bag) cleanup in what VIDIOCSTUNER returns, + thanks to Mike Cox for pointing the problem out. + + Version 0.21: + Markus: Minor cleanup, warnings if something goes wrong, lame attempt + to adhere to Documentation/CodingStyle + + Version 0.2: + Brad Hards <bradh@dynamite.com.au>: Fixes to make it work as non-module + Markus: Copyright clarification + + Version 0.01: Markus: initial release + +*/ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/usb.h> +#include <linux/smp_lock.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.40" +#define DRIVER_AUTHOR "Markus Demleitner <msdemlei@tucana.harvard.edu>" +#define DRIVER_DESC "D-Link DSB-R100 USB FM radio driver" + +#define DSB100_VENDOR 0x04b4 +#define DSB100_PRODUCT 0x1002 + +/* Commands the device appears to understand */ +#define DSB100_TUNE 1 +#define DSB100_ONOFF 2 + +#define TB_LEN 16 + +/* Frequency limits in MHz -- these are European values. For Japanese +devices, that would be 76 and 91. */ +#define FREQ_MIN 87.5 +#define FREQ_MAX 108.0 +#define FREQ_MUL 16000 + + +static int usb_dsbr100_probe(struct usb_interface *intf, + const struct usb_device_id *id); +static void usb_dsbr100_disconnect(struct usb_interface *intf); +static int usb_dsbr100_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static int usb_dsbr100_open(struct inode *inode, struct file *file); +static int usb_dsbr100_close(struct inode *inode, struct file *file); + +static int radio_nr = -1; +module_param(radio_nr, int, 0); + +/* Data for one (physical) device */ +typedef struct { + struct usb_device *usbdev; + struct video_device *videodev; + unsigned char transfer_buffer[TB_LEN]; + int curfreq; + int stereo; + int users; + int removed; +} dsbr100_device; + + +/* File system interface */ +static struct file_operations usb_dsbr100_fops = { + .owner = THIS_MODULE, + .open = usb_dsbr100_open, + .release = usb_dsbr100_close, + .ioctl = usb_dsbr100_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +/* V4L interface */ +static struct video_device dsbr100_videodev_template= +{ + .owner = THIS_MODULE, + .name = "D-Link DSB-R 100", + .type = VID_TYPE_TUNER, + .hardware = VID_HARDWARE_AZTECH, + .fops = &usb_dsbr100_fops, + .release = video_device_release, +}; + +static struct usb_device_id usb_dsbr100_device_table [] = { + { USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, usb_dsbr100_device_table); + +/* USB subsystem interface */ +static struct usb_driver usb_dsbr100_driver = { + .name = "dsbr100", + .probe = usb_dsbr100_probe, + .disconnect = usb_dsbr100_disconnect, + .id_table = usb_dsbr100_device_table, +}; + +/* Low-level device interface begins here */ + +/* switch on radio */ +static int dsbr100_start(dsbr100_device *radio) +{ + if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0xC7, radio->transfer_buffer, 8, 300)<0 || + usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_ONOFF, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x01, 0x00, radio->transfer_buffer, 8, 300)<0) + return -1; + return (radio->transfer_buffer)[0]; +} + + +/* switch off radio */ +static int dsbr100_stop(dsbr100_device *radio) +{ + if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x16, 0x1C, radio->transfer_buffer, 8, 300)<0 || + usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_ONOFF, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0x00, radio->transfer_buffer, 8, 300)<0) + return -1; + return (radio->transfer_buffer)[0]; +} + +/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ +static int dsbr100_setfreq(dsbr100_device *radio, int freq) +{ + freq = (freq/16*80)/1000+856; + if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_TUNE, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + (freq>>8)&0x00ff, freq&0xff, + radio->transfer_buffer, 8, 300)<0 || + usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x96, 0xB7, radio->transfer_buffer, 8, 300)<0 || + usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0x24, radio->transfer_buffer, 8, 300)<0) { + radio->stereo = -1; + return -1; + } + radio->stereo = ! ((radio->transfer_buffer)[0]&0x01); + return (radio->transfer_buffer)[0]; +} + +/* return the device status. This is, in effect, just whether it +sees a stereo signal or not. Pity. */ +static void dsbr100_getstat(dsbr100_device *radio) +{ + if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00 , 0x24, radio->transfer_buffer, 8, 300)<0) + radio->stereo = -1; + else + radio->stereo = ! (radio->transfer_buffer[0]&0x01); +} + + +/* USB subsystem interface begins here */ + +/* check if the device is present and register with v4l and +usb if it is */ +static int usb_dsbr100_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + dsbr100_device *radio; + + if (!(radio = kmalloc(sizeof(dsbr100_device), GFP_KERNEL))) + return -ENOMEM; + if (!(radio->videodev = video_device_alloc())) { + kfree(radio); + return -ENOMEM; + } + memcpy(radio->videodev, &dsbr100_videodev_template, + sizeof(dsbr100_videodev_template)); + radio->removed = 0; + radio->users = 0; + radio->usbdev = interface_to_usbdev(intf); + radio->curfreq = FREQ_MIN*FREQ_MUL; + video_set_drvdata(radio->videodev, radio); + if (video_register_device(radio->videodev, VFL_TYPE_RADIO, + radio_nr)) { + warn("Could not register video device"); + video_device_release(radio->videodev); + kfree(radio); + return -EIO; + } + usb_set_intfdata(intf, radio); + return 0; +} + +/* handle unplugging of the device, release data structures +if nothing keeps us from doing it. If something is still +keeping us busy, the release callback of v4l will take care +of releasing it. stv680.c does not relase its private +data, so I don't do this here either. Checking out the +code I'd expect I better did that, but if there's a memory +leak here it's tiny (~50 bytes per disconnect) */ +static void usb_dsbr100_disconnect(struct usb_interface *intf) +{ + dsbr100_device *radio = usb_get_intfdata(intf); + + usb_set_intfdata (intf, NULL); + if (radio) { + video_unregister_device(radio->videodev); + radio->videodev = NULL; + if (radio->users) { + kfree(radio); + } else { + radio->removed = 1; + } + } +} + + +/* Video for Linux interface */ + +static int usb_dsbr100_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + dsbr100_device *radio=video_get_drvdata(video_devdata(file)); + + if (!radio) + return -EIO; + + switch(cmd) { + case VIDIOCGCAP: { + struct video_capability *v = arg; + + memset(v, 0, sizeof(*v)); + v->type = VID_TYPE_TUNER; + v->channels = 1; + v->audios = 1; + strcpy(v->name, "D-Link R-100 USB FM Radio"); + return 0; + } + case VIDIOCGTUNER: { + struct video_tuner *v = arg; + + dsbr100_getstat(radio); + if(v->tuner) /* Only 1 tuner */ + return -EINVAL; + v->rangelow = FREQ_MIN*FREQ_MUL; + v->rangehigh = FREQ_MAX*FREQ_MUL; + v->flags = VIDEO_TUNER_LOW; + v->mode = VIDEO_MODE_AUTO; + v->signal = radio->stereo*0x7000; + /* Don't know how to get signal strength */ + v->flags |= VIDEO_TUNER_STEREO_ON*radio->stereo; + strcpy(v->name, "DSB R-100"); + return 0; + } + case VIDIOCSTUNER: { + struct video_tuner *v = arg; + + if(v->tuner!=0) + return -EINVAL; + /* Only 1 tuner so no setting needed ! */ + return 0; + } + case VIDIOCGFREQ: { + int *freq = arg; + + if (radio->curfreq==-1) + return -EINVAL; + *freq = radio->curfreq; + return 0; + } + case VIDIOCSFREQ: { + int *freq = arg; + + radio->curfreq = *freq; + if (dsbr100_setfreq(radio, radio->curfreq)==-1) + warn("Set frequency failed"); + return 0; + } + case VIDIOCGAUDIO: { + struct video_audio *v = arg; + + memset(v, 0, sizeof(*v)); + v->flags |= VIDEO_AUDIO_MUTABLE; + v->mode = VIDEO_SOUND_STEREO; + v->volume = 1; + v->step = 1; + strcpy(v->name, "Radio"); + return 0; + } + case VIDIOCSAUDIO: { + struct video_audio *v = arg; + + if (v->audio) + return -EINVAL; + if (v->flags&VIDEO_AUDIO_MUTE) { + if (dsbr100_stop(radio)==-1) + warn("Radio did not respond properly"); + } + else + if (dsbr100_start(radio)==-1) + warn("Radio did not respond properly"); + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +static int usb_dsbr100_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, usb_dsbr100_do_ioctl); +} + +static int usb_dsbr100_open(struct inode *inode, struct file *file) +{ + dsbr100_device *radio=video_get_drvdata(video_devdata(file)); + + radio->users = 1; + if (dsbr100_start(radio)<0) { + warn("Radio did not start up properly"); + radio->users = 0; + return -EIO; + } + dsbr100_setfreq(radio, radio->curfreq); + return 0; +} + +static int usb_dsbr100_close(struct inode *inode, struct file *file) +{ + dsbr100_device *radio=video_get_drvdata(video_devdata(file)); + + if (!radio) + return -ENODEV; + radio->users = 0; + if (radio->removed) { + kfree(radio); + } + return 0; +} + +static int __init dsbr100_init(void) +{ + int retval = usb_register(&usb_dsbr100_driver); + info(DRIVER_VERSION ":" DRIVER_DESC); + return retval; +} + +static void __exit dsbr100_exit(void) +{ + usb_deregister(&usb_dsbr100_driver); +} + +module_init (dsbr100_init); +module_exit (dsbr100_exit); + +MODULE_AUTHOR( DRIVER_AUTHOR ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/ibmmpeg2.h b/linux/drivers/media/video/ibmmpeg2.h new file mode 100644 index 000000000..68e10387c --- /dev/null +++ b/linux/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/linux/drivers/media/video/indycam.c b/linux/drivers/media/video/indycam.c new file mode 100644 index 000000000..7420b79e9 --- /dev/null +++ b/linux/drivers/media/video/indycam.c @@ -0,0 +1,471 @@ +/* + * indycam.c - Silicon Graphics IndyCam digital camera driver + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <linux/videodev.h> +/* IndyCam decodes stream of photons into digital image representation ;-) */ +#include <linux/video_decoder.h> +#include <linux/i2c.h> + +#include "indycam.h" + +#define INDYCAM_MODULE_VERSION "0.0.5" + +MODULE_DESCRIPTION("SGI IndyCam driver"); +MODULE_VERSION(INDYCAM_MODULE_VERSION); +MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>"); +MODULE_LICENSE("GPL"); + +// #define INDYCAM_DEBUG + +#ifdef INDYCAM_DEBUG +#define dprintk(x...) printk("IndyCam: " x); +#define indycam_regdump(client) indycam_regdump_debug(client) +#else +#define dprintk(x...) +#define indycam_regdump(client) +#endif + +struct indycam { + struct i2c_client *client; + u8 version; +}; + +static struct i2c_driver i2c_driver_indycam; + +static const u8 initseq[] = { + INDYCAM_CONTROL_AGCENA, /* INDYCAM_CONTROL */ + INDYCAM_SHUTTER_60, /* INDYCAM_SHUTTER */ + INDYCAM_GAIN_DEFAULT, /* INDYCAM_GAIN */ + 0x00, /* INDYCAM_BRIGHTNESS (read-only) */ + INDYCAM_RED_BALANCE_DEFAULT, /* INDYCAM_RED_BALANCE */ + INDYCAM_BLUE_BALANCE_DEFAULT, /* INDYCAM_BLUE_BALANCE */ + INDYCAM_RED_SATURATION_DEFAULT, /* INDYCAM_RED_SATURATION */ + INDYCAM_BLUE_SATURATION_DEFAULT,/* INDYCAM_BLUE_SATURATION */ +}; + +/* IndyCam register handling */ + +static int indycam_read_reg(struct i2c_client *client, u8 reg, u8 *value) +{ + int ret; + + if (reg == INDYCAM_REG_RESET) { + dprintk("indycam_read_reg(): " + "skipping write-only register %d\n", reg); + *value = 0; + return 0; + } + + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) { + printk(KERN_ERR "IndyCam: indycam_read_reg(): read failed, " + "register = 0x%02x\n", reg); + return ret; + } + + *value = (u8)ret; + + return 0; +} + +static int indycam_write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + int err; + + if ((reg == INDYCAM_REG_BRIGHTNESS) + || (reg == INDYCAM_REG_VERSION)) { + dprintk("indycam_write_reg(): " + "skipping read-only register %d\n", reg); + return 0; + } + + dprintk("Writing Reg %d = 0x%02x\n", reg, value); + err = i2c_smbus_write_byte_data(client, reg, value); + + if (err) { + printk(KERN_ERR "IndyCam: indycam_write_reg(): write failed, " + "register = 0x%02x, value = 0x%02x\n", reg, value); + } + return err; +} + +static int indycam_write_block(struct i2c_client *client, u8 reg, + u8 length, u8 *data) +{ + int i, err; + + for (i = 0; i < length; i++) { + err = indycam_write_reg(client, reg + i, data[i]); + if (err) + return err; + } + + return 0; +} + +/* Helper functions */ + +#ifdef INDYCAM_DEBUG +static void indycam_regdump_debug(struct i2c_client *client) +{ + int i; + u8 val; + + for (i = 0; i < 9; i++) { + indycam_read_reg(client, i, &val); + dprintk("Reg %d = 0x%02x\n", i, val); + } +} +#endif + +static int indycam_get_control(struct i2c_client *client, + struct indycam_control *ctrl) +{ + struct indycam *camera = i2c_get_clientdata(client); + u8 reg; + int ret = 0; + + switch (ctrl->type) { + case INDYCAM_CONTROL_AGC: + case INDYCAM_CONTROL_AWB: + ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®); + if (ret) + return -EIO; + if (ctrl->type == INDYCAM_CONTROL_AGC) + ctrl->value = (reg & INDYCAM_CONTROL_AGCENA) + ? 1 : 0; + else + ctrl->value = (reg & INDYCAM_CONTROL_AWBCTL) + ? 1 : 0; + break; + case INDYCAM_CONTROL_SHUTTER: + ret = indycam_read_reg(client, INDYCAM_REG_SHUTTER, ®); + if (ret) + return -EIO; + ctrl->value = ((s32)reg == 0x00) ? 0xff : ((s32)reg - 1); + break; + case INDYCAM_CONTROL_GAIN: + ret = indycam_read_reg(client, INDYCAM_REG_GAIN, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + break; + case INDYCAM_CONTROL_RED_BALANCE: + ret = indycam_read_reg(client, INDYCAM_REG_RED_BALANCE, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + break; + case INDYCAM_CONTROL_BLUE_BALANCE: + ret = indycam_read_reg(client, INDYCAM_REG_BLUE_BALANCE, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + break; + case INDYCAM_CONTROL_RED_SATURATION: + ret = indycam_read_reg(client, + INDYCAM_REG_RED_SATURATION, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + break; + case INDYCAM_CONTROL_BLUE_SATURATION: + ret = indycam_read_reg(client, + INDYCAM_REG_BLUE_SATURATION, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + break; + case INDYCAM_CONTROL_GAMMA: + if (camera->version == CAMERA_VERSION_MOOSE) { + ret = indycam_read_reg(client, + INDYCAM_REG_GAMMA, ®); + if (ret) + return -EIO; + ctrl->value = (s32)reg; + } else { + ctrl->value = INDYCAM_GAMMA_DEFAULT; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int indycam_set_control(struct i2c_client *client, + struct indycam_control *ctrl) +{ + struct indycam *camera = i2c_get_clientdata(client); + u8 reg; + int ret = 0; + + switch (ctrl->type) { + case INDYCAM_CONTROL_AGC: + case INDYCAM_CONTROL_AWB: + ret = indycam_read_reg(client, INDYCAM_REG_CONTROL, ®); + if (ret) + break; + + if (ctrl->type == INDYCAM_CONTROL_AGC) { + if (ctrl->value) + reg |= INDYCAM_CONTROL_AGCENA; + else + reg &= ~INDYCAM_CONTROL_AGCENA; + } else { + if (ctrl->value) + reg |= INDYCAM_CONTROL_AWBCTL; + else + reg &= ~INDYCAM_CONTROL_AWBCTL; + } + + ret = indycam_write_reg(client, INDYCAM_REG_CONTROL, reg); + break; + case INDYCAM_CONTROL_SHUTTER: + reg = (ctrl->value == 0xff) ? 0x00 : (ctrl->value + 1); + ret = indycam_write_reg(client, INDYCAM_REG_SHUTTER, reg); + break; + case INDYCAM_CONTROL_GAIN: + ret = indycam_write_reg(client, INDYCAM_REG_GAIN, ctrl->value); + break; + case INDYCAM_CONTROL_RED_BALANCE: + ret = indycam_write_reg(client, INDYCAM_REG_RED_BALANCE, + ctrl->value); + break; + case INDYCAM_CONTROL_BLUE_BALANCE: + ret = indycam_write_reg(client, INDYCAM_REG_BLUE_BALANCE, + ctrl->value); + break; + case INDYCAM_CONTROL_RED_SATURATION: + ret = indycam_write_reg(client, INDYCAM_REG_RED_SATURATION, + ctrl->value); + break; + case INDYCAM_CONTROL_BLUE_SATURATION: + ret = indycam_write_reg(client, INDYCAM_REG_BLUE_SATURATION, + ctrl->value); + break; + case INDYCAM_CONTROL_GAMMA: + if (camera->version == CAMERA_VERSION_MOOSE) { + ret = indycam_write_reg(client, INDYCAM_REG_GAMMA, + ctrl->value); + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* I2C-interface */ + +static int indycam_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int err = 0; + struct indycam *camera; + struct i2c_client *client; + + printk(KERN_INFO "SGI IndyCam driver version %s\n", + INDYCAM_MODULE_VERSION); + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (!client) + return -ENOMEM; + camera = kzalloc(sizeof(struct indycam), GFP_KERNEL); + if (!camera) { + err = -ENOMEM; + goto out_free_client; + } + + client->addr = addr; + client->adapter = adap; + client->driver = &i2c_driver_indycam; + client->flags = 0; + strcpy(client->name, "IndyCam client"); + i2c_set_clientdata(client, camera); + + camera->client = client; + + err = i2c_attach_client(client); + if (err) + goto out_free_camera; + + camera->version = i2c_smbus_read_byte_data(client, + INDYCAM_REG_VERSION); + if (camera->version != CAMERA_VERSION_INDY && + camera->version != CAMERA_VERSION_MOOSE) { + err = -ENODEV; + goto out_detach_client; + } + printk(KERN_INFO "IndyCam v%d.%d detected\n", + INDYCAM_VERSION_MAJOR(camera->version), + INDYCAM_VERSION_MINOR(camera->version)); + + indycam_regdump(client); + + // initialize + err = indycam_write_block(client, 0, sizeof(initseq), (u8 *)&initseq); + if (err) { + printk(KERN_ERR "IndyCam initalization failed\n"); + err = -EIO; + goto out_detach_client; + } + + indycam_regdump(client); + + // white balance + err = indycam_write_reg(client, INDYCAM_REG_CONTROL, + INDYCAM_CONTROL_AGCENA | INDYCAM_CONTROL_AWBCTL); + if (err) { + printk(KERN_ERR "IndyCam: White balancing camera failed\n"); + err = -EIO; + goto out_detach_client; + } + + indycam_regdump(client); + + printk(KERN_INFO "IndyCam initialized\n"); + + return 0; + +out_detach_client: + i2c_detach_client(client); +out_free_camera: + kfree(camera); +out_free_client: + kfree(client); + return err; +} + +static int indycam_probe(struct i2c_adapter *adap) +{ + /* Indy specific crap */ + if (adap->id == I2C_HW_SGI_VINO) + return indycam_attach(adap, INDYCAM_ADDR, 0); + /* Feel free to add probe here :-) */ + return -ENODEV; +} + +static int indycam_detach(struct i2c_client *client) +{ + struct indycam *camera = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(camera); + kfree(client); + return 0; +} + +static int indycam_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + // struct indycam *camera = i2c_get_clientdata(client); + + /* The old video_decoder interface just isn't enough, + * so we'll use some custom commands. */ + switch (cmd) { + case DECODER_GET_CAPABILITIES: { + struct video_decoder_capability *cap = arg; + + cap->flags = VIDEO_DECODER_NTSC; + cap->inputs = 1; + cap->outputs = 1; + break; + } + case DECODER_GET_STATUS: { + int *iarg = arg; + + *iarg = DECODER_STATUS_GOOD | DECODER_STATUS_NTSC | + DECODER_STATUS_COLOR; + break; + } + case DECODER_SET_NORM: { + int *iarg = arg; + + switch (*iarg) { + case VIDEO_MODE_NTSC: + break; + default: + return -EINVAL; + } + break; + } + case DECODER_SET_INPUT: { + int *iarg = arg; + + if (*iarg != 0) + return -EINVAL; + break; + } + case DECODER_SET_OUTPUT: { + int *iarg = arg; + + if (*iarg != 0) + return -EINVAL; + break; + } + case DECODER_ENABLE_OUTPUT: { + /* Always enabled */ + break; + } + case DECODER_SET_PICTURE: { + // struct video_picture *pic = arg; + /* TODO: convert values for indycam_set_controls() */ + break; + } + case DECODER_INDYCAM_GET_CONTROL: { + return indycam_get_control(client, arg); + } + case DECODER_INDYCAM_SET_CONTROL: { + return indycam_set_control(client, arg); + } + default: + return -EINVAL; + } + + return 0; +} + +static struct i2c_driver i2c_driver_indycam = { + .driver = { + .name = "indycam", + }, + .id = I2C_DRIVERID_INDYCAM, + .attach_adapter = indycam_probe, + .detach_client = indycam_detach, + .command = indycam_command, +}; + +static int __init indycam_init(void) +{ + return i2c_add_driver(&i2c_driver_indycam); +} + +static void __exit indycam_exit(void) +{ + i2c_del_driver(&i2c_driver_indycam); +} + +module_init(indycam_init); +module_exit(indycam_exit); diff --git a/linux/drivers/media/video/indycam.h b/linux/drivers/media/video/indycam.h new file mode 100644 index 000000000..e6ee82063 --- /dev/null +++ b/linux/drivers/media/video/indycam.h @@ -0,0 +1,108 @@ +/* + * indycam.h - Silicon Graphics IndyCam digital camera driver + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _INDYCAM_H_ +#define _INDYCAM_H_ + +/* I2C address for the Guinness Camera */ +#define INDYCAM_ADDR 0x56 + +/* Camera version */ +#define CAMERA_VERSION_INDY 0x10 /* v1.0 */ +#define CAMERA_VERSION_MOOSE 0x12 /* v1.2 */ +#define INDYCAM_VERSION_MAJOR(x) (((x) & 0xf0) >> 4) +#define INDYCAM_VERSION_MINOR(x) ((x) & 0x0f) + +/* Register bus addresses */ +#define INDYCAM_REG_CONTROL 0x00 +#define INDYCAM_REG_SHUTTER 0x01 +#define INDYCAM_REG_GAIN 0x02 +#define INDYCAM_REG_BRIGHTNESS 0x03 /* read-only */ +#define INDYCAM_REG_RED_BALANCE 0x04 +#define INDYCAM_REG_BLUE_BALANCE 0x05 +#define INDYCAM_REG_RED_SATURATION 0x06 +#define INDYCAM_REG_BLUE_SATURATION 0x07 +#define INDYCAM_REG_GAMMA 0x08 +#define INDYCAM_REG_VERSION 0x0e /* read-only */ +#define INDYCAM_REG_RESET 0x0f /* write-only */ + +#define INDYCAM_REG_LED 0x46 +#define INDYCAM_REG_ORIENTATION 0x47 +#define INDYCAM_REG_BUTTON 0x48 + +/* Field definitions of registers */ +#define INDYCAM_CONTROL_AGCENA (1<<0) /* automatic gain control */ +#define INDYCAM_CONTROL_AWBCTL (1<<1) /* automatic white balance */ + /* 2-3 are reserved */ +#define INDYCAM_CONTROL_EVNFLD (1<<4) /* read-only */ + +#define INDYCAM_SHUTTER_10000 0x02 /* 1/10000 second */ +#define INDYCAM_SHUTTER_4000 0x04 /* 1/4000 second */ +#define INDYCAM_SHUTTER_2000 0x08 /* 1/2000 second */ +#define INDYCAM_SHUTTER_1000 0x10 /* 1/1000 second */ +#define INDYCAM_SHUTTER_500 0x20 /* 1/500 second */ +#define INDYCAM_SHUTTER_250 0x3f /* 1/250 second */ +#define INDYCAM_SHUTTER_125 0x7e /* 1/125 second */ +#define INDYCAM_SHUTTER_100 0x9e /* 1/100 second */ +#define INDYCAM_SHUTTER_60 0x00 /* 1/60 second */ + +#define INDYCAM_LED_ACTIVE 0x10 +#define INDYCAM_LED_INACTIVE 0x30 +#define INDYCAM_ORIENTATION_BOTTOM_TO_TOP 0x40 +#define INDYCAM_BUTTON_RELEASED 0x10 + +/* Values for controls */ +#define INDYCAM_SHUTTER_MIN 0x00 +#define INDYCAM_SHUTTER_MAX 0xff +#define INDYCAM_GAIN_MIN 0x00 +#define INDYCAM_GAIN_MAX 0xff +#define INDYCAM_RED_BALANCE_MIN 0x00 +#define INDYCAM_RED_BALANCE_MAX 0xff +#define INDYCAM_BLUE_BALANCE_MIN 0x00 +#define INDYCAM_BLUE_BALANCE_MAX 0xff +#define INDYCAM_RED_SATURATION_MIN 0x00 +#define INDYCAM_RED_SATURATION_MAX 0xff +#define INDYCAM_BLUE_SATURATION_MIN 0x00 +#define INDYCAM_BLUE_SATURATION_MAX 0xff +#define INDYCAM_GAMMA_MIN 0x00 +#define INDYCAM_GAMMA_MAX 0xff + +#define INDYCAM_AGC_DEFAULT 1 +#define INDYCAM_AWB_DEFAULT 0 +#define INDYCAM_SHUTTER_DEFAULT 0xff +#define INDYCAM_GAIN_DEFAULT 0x80 +#define INDYCAM_RED_BALANCE_DEFAULT 0x18 +#define INDYCAM_BLUE_BALANCE_DEFAULT 0xa4 +#define INDYCAM_RED_SATURATION_DEFAULT 0x80 +#define INDYCAM_BLUE_SATURATION_DEFAULT 0xc0 +#define INDYCAM_GAMMA_DEFAULT 0x80 + +/* Driver interface definitions */ + +#define INDYCAM_CONTROL_AGC 0 /* boolean */ +#define INDYCAM_CONTROL_AWB 1 /* boolean */ +#define INDYCAM_CONTROL_SHUTTER 2 +#define INDYCAM_CONTROL_GAIN 3 +#define INDYCAM_CONTROL_RED_BALANCE 4 +#define INDYCAM_CONTROL_BLUE_BALANCE 5 +#define INDYCAM_CONTROL_RED_SATURATION 6 +#define INDYCAM_CONTROL_BLUE_SATURATION 7 +#define INDYCAM_CONTROL_GAMMA 8 + +struct indycam_control { + u8 type; + s32 value; +}; + +#define DECODER_INDYCAM_GET_CONTROL _IOR('d', 193, struct indycam_control) +#define DECODER_INDYCAM_SET_CONTROL _IOW('d', 194, struct indycam_control) + +#endif diff --git a/linux/drivers/media/video/meye.c b/linux/drivers/media/video/meye.c new file mode 100644 index 000000000..dcd90f56b --- /dev/null +++ b/linux/drivers/media/video/meye.c @@ -0,0 +1,2044 @@ +/* + * Motion Eye video4linux driver for Sony Vaio PictureBook + * + * Copyright (C) 2001-2004 Stelian Pop <stelian@popies.net> + * + * Copyright (C) 2001-2002 Alcôve <www.alcove.com> + * + * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> + * + * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. + * + * Some parts borrowed from various video4linux drivers, especially + * bttv-driver.c and zoran.c, see original files for credits. + * + * 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/pci.h> +#include <linux/sched.h> +#include <linux/init.h> +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <linux/dma-mapping.h> + +#include "meye.h" +#include <linux/meye.h> + +MODULE_AUTHOR("Stelian Pop <stelian@popies.net>"); +MODULE_DESCRIPTION("v4l/v4l2 driver for the MotionEye camera"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(MEYE_DRIVER_VERSION); + +/* force usage of V4L1 API */ +static int forcev4l1; /* = 0 */ +module_param(forcev4l1, int, 0644); +MODULE_PARM_DESC(forcev4l1, "force use of V4L1 instead of V4L2"); + +/* number of grab buffers */ +static unsigned int gbuffers = 2; +module_param(gbuffers, int, 0444); +MODULE_PARM_DESC(gbuffers, "number of capture buffers, default is 2 (32 max)"); + +/* size of a grab buffer */ +static unsigned int gbufsize = MEYE_MAX_BUFSIZE; +module_param(gbufsize, int, 0444); +MODULE_PARM_DESC(gbufsize, "size of the capture buffers, default is 614400" + " (will be rounded up to a page multiple)"); + +/* /dev/videoX registration number */ +static int video_nr = -1; +module_param(video_nr, int, 0444); +MODULE_PARM_DESC(video_nr, "video device to register (0=/dev/video0, etc)"); + +/* driver structure - only one possible */ +static struct meye meye; + +/****************************************************************************/ +/* Memory allocation routines (stolen from bttv-driver.c) */ +/****************************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + mem = vmalloc_32(size); + if (mem) { + memset(mem, 0, size); + adr = (unsigned long) mem; + while (size > 0) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + } + return mem; +} + +static void rvfree(void * mem, unsigned long size) +{ + unsigned long adr; + + if (mem) { + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); + } +} + +/* + * return a page table pointing to N pages of locked memory + * + * NOTE: The meye device expects DMA addresses on 32 bits, we build + * a table of 1024 entries = 4 bytes * 1024 = 4096 bytes. + */ +static int ptable_alloc(void) +{ + u32 *pt; + int i; + + memset(meye.mchip_ptable, 0, sizeof(meye.mchip_ptable)); + + /* give only 32 bit DMA addresses */ + if (dma_set_mask(&meye.mchip_dev->dev, DMA_32BIT_MASK)) + return -1; + + meye.mchip_ptable_toc = dma_alloc_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + &meye.mchip_dmahandle, + GFP_KERNEL); + if (!meye.mchip_ptable_toc) { + meye.mchip_dmahandle = 0; + return -1; + } + + pt = meye.mchip_ptable_toc; + for (i = 0; i < MCHIP_NB_PAGES; i++) { + dma_addr_t dma; + meye.mchip_ptable[i] = dma_alloc_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + &dma, + GFP_KERNEL); + if (!meye.mchip_ptable[i]) { + int j; + pt = meye.mchip_ptable_toc; + for (j = 0; j < i; ++j) { + dma = (dma_addr_t) *pt; + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable[j], dma); + pt++; + } + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable_toc, + meye.mchip_dmahandle); + meye.mchip_ptable_toc = NULL; + meye.mchip_dmahandle = 0; + return -1; + } + *pt = (u32) dma; + pt++; + } + return 0; +} + +static void ptable_free(void) +{ + u32 *pt; + int i; + + pt = meye.mchip_ptable_toc; + for (i = 0; i < MCHIP_NB_PAGES; i++) { + dma_addr_t dma = (dma_addr_t) *pt; + if (meye.mchip_ptable[i]) + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable[i], dma); + pt++; + } + + if (meye.mchip_ptable_toc) + dma_free_coherent(&meye.mchip_dev->dev, + PAGE_SIZE, + meye.mchip_ptable_toc, + meye.mchip_dmahandle); + + memset(meye.mchip_ptable, 0, sizeof(meye.mchip_ptable)); + meye.mchip_ptable_toc = NULL; + meye.mchip_dmahandle = 0; +} + +/* copy data from ptable into buf */ +static void ptable_copy(u8 *buf, int start, int size, int pt_pages) +{ + int i; + + for (i = 0; i < (size / PAGE_SIZE) * PAGE_SIZE; i += PAGE_SIZE) { + memcpy(buf + i, meye.mchip_ptable[start++], PAGE_SIZE); + if (start >= pt_pages) + start = 0; + } + memcpy(buf + i, meye.mchip_ptable[start], size % PAGE_SIZE); +} + +/****************************************************************************/ +/* JPEG tables at different qualities to load into the VRJ chip */ +/****************************************************************************/ + +/* return a set of quantisation tables based on a quality from 1 to 10 */ +static u16 *jpeg_quantisation_tables(int *length, int quality) +{ + static u16 jpeg_tables[][70] = { { + 0xdbff, 0x4300, 0xff00, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + 0xdbff, 0x4300, 0xff01, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + }, + { + 0xdbff, 0x4300, 0x5000, 0x3c37, 0x3c46, 0x5032, 0x4146, 0x5a46, + 0x5055, 0x785f, 0x82c8, 0x6e78, 0x786e, 0xaff5, 0x91b9, 0xffc8, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + 0xdbff, 0x4300, 0x5501, 0x5a5a, 0x6978, 0xeb78, 0x8282, 0xffeb, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + 0xffff, 0xffff, 0xffff, + }, + { + 0xdbff, 0x4300, 0x2800, 0x1e1c, 0x1e23, 0x2819, 0x2123, 0x2d23, + 0x282b, 0x3c30, 0x4164, 0x373c, 0x3c37, 0x587b, 0x495d, 0x9164, + 0x9980, 0x8f96, 0x8c80, 0xa08a, 0xe6b4, 0xa0c3, 0xdaaa, 0x8aad, + 0xc88c, 0xcbff, 0xeeda, 0xfff5, 0xffff, 0xc19b, 0xffff, 0xfaff, + 0xe6ff, 0xfffd, 0xfff8, + 0xdbff, 0x4300, 0x2b01, 0x2d2d, 0x353c, 0x763c, 0x4141, 0xf876, + 0x8ca5, 0xf8a5, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, 0xf8f8, + 0xf8f8, 0xf8f8, 0xfff8, + }, + { + 0xdbff, 0x4300, 0x1b00, 0x1412, 0x1417, 0x1b11, 0x1617, 0x1e17, + 0x1b1c, 0x2820, 0x2b42, 0x2528, 0x2825, 0x3a51, 0x303d, 0x6042, + 0x6555, 0x5f64, 0x5d55, 0x6a5b, 0x9978, 0x6a81, 0x9071, 0x5b73, + 0x855d, 0x86b5, 0x9e90, 0xaba3, 0xabad, 0x8067, 0xc9bc, 0xa6ba, + 0x99c7, 0xaba8, 0xffa4, + 0xdbff, 0x4300, 0x1c01, 0x1e1e, 0x2328, 0x4e28, 0x2b2b, 0xa44e, + 0x5d6e, 0xa46e, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, 0xa4a4, + 0xa4a4, 0xa4a4, 0xffa4, + }, + { + 0xdbff, 0x4300, 0x1400, 0x0f0e, 0x0f12, 0x140d, 0x1012, 0x1712, + 0x1415, 0x1e18, 0x2132, 0x1c1e, 0x1e1c, 0x2c3d, 0x242e, 0x4932, + 0x4c40, 0x474b, 0x4640, 0x5045, 0x735a, 0x5062, 0x6d55, 0x4556, + 0x6446, 0x6588, 0x776d, 0x817b, 0x8182, 0x604e, 0x978d, 0x7d8c, + 0x7396, 0x817e, 0xff7c, + 0xdbff, 0x4300, 0x1501, 0x1717, 0x1a1e, 0x3b1e, 0x2121, 0x7c3b, + 0x4653, 0x7c53, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, 0x7c7c, + 0x7c7c, 0x7c7c, 0xff7c, + }, + { + 0xdbff, 0x4300, 0x1000, 0x0c0b, 0x0c0e, 0x100a, 0x0d0e, 0x120e, + 0x1011, 0x1813, 0x1a28, 0x1618, 0x1816, 0x2331, 0x1d25, 0x3a28, + 0x3d33, 0x393c, 0x3833, 0x4037, 0x5c48, 0x404e, 0x5744, 0x3745, + 0x5038, 0x516d, 0x5f57, 0x6762, 0x6768, 0x4d3e, 0x7971, 0x6470, + 0x5c78, 0x6765, 0xff63, + 0xdbff, 0x4300, 0x1101, 0x1212, 0x1518, 0x2f18, 0x1a1a, 0x632f, + 0x3842, 0x6342, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, 0x6363, + 0x6363, 0x6363, 0xff63, + }, + { + 0xdbff, 0x4300, 0x0d00, 0x0a09, 0x0a0b, 0x0d08, 0x0a0b, 0x0e0b, + 0x0d0e, 0x130f, 0x1520, 0x1213, 0x1312, 0x1c27, 0x171e, 0x2e20, + 0x3129, 0x2e30, 0x2d29, 0x332c, 0x4a3a, 0x333e, 0x4636, 0x2c37, + 0x402d, 0x4157, 0x4c46, 0x524e, 0x5253, 0x3e32, 0x615a, 0x505a, + 0x4a60, 0x5251, 0xff4f, + 0xdbff, 0x4300, 0x0e01, 0x0e0e, 0x1113, 0x2613, 0x1515, 0x4f26, + 0x2d35, 0x4f35, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, 0x4f4f, + 0x4f4f, 0x4f4f, 0xff4f, + }, + { + 0xdbff, 0x4300, 0x0a00, 0x0707, 0x0708, 0x0a06, 0x0808, 0x0b08, + 0x0a0a, 0x0e0b, 0x1018, 0x0d0e, 0x0e0d, 0x151d, 0x1116, 0x2318, + 0x251f, 0x2224, 0x221f, 0x2621, 0x372b, 0x262f, 0x3429, 0x2129, + 0x3022, 0x3141, 0x3934, 0x3e3b, 0x3e3e, 0x2e25, 0x4944, 0x3c43, + 0x3748, 0x3e3d, 0xff3b, + 0xdbff, 0x4300, 0x0a01, 0x0b0b, 0x0d0e, 0x1c0e, 0x1010, 0x3b1c, + 0x2228, 0x3b28, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, 0x3b3b, + 0x3b3b, 0x3b3b, 0xff3b, + }, + { + 0xdbff, 0x4300, 0x0600, 0x0504, 0x0506, 0x0604, 0x0506, 0x0706, + 0x0607, 0x0a08, 0x0a10, 0x090a, 0x0a09, 0x0e14, 0x0c0f, 0x1710, + 0x1814, 0x1718, 0x1614, 0x1a16, 0x251d, 0x1a1f, 0x231b, 0x161c, + 0x2016, 0x202c, 0x2623, 0x2927, 0x292a, 0x1f19, 0x302d, 0x282d, + 0x2530, 0x2928, 0xff28, + 0xdbff, 0x4300, 0x0701, 0x0707, 0x080a, 0x130a, 0x0a0a, 0x2813, + 0x161a, 0x281a, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, 0x2828, + 0x2828, 0x2828, 0xff28, + }, + { + 0xdbff, 0x4300, 0x0300, 0x0202, 0x0203, 0x0302, 0x0303, 0x0403, + 0x0303, 0x0504, 0x0508, 0x0405, 0x0504, 0x070a, 0x0607, 0x0c08, + 0x0c0a, 0x0b0c, 0x0b0a, 0x0d0b, 0x120e, 0x0d10, 0x110e, 0x0b0e, + 0x100b, 0x1016, 0x1311, 0x1514, 0x1515, 0x0f0c, 0x1817, 0x1416, + 0x1218, 0x1514, 0xff14, + 0xdbff, 0x4300, 0x0301, 0x0404, 0x0405, 0x0905, 0x0505, 0x1409, + 0x0b0d, 0x140d, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, 0x1414, + 0x1414, 0x1414, 0xff14, + }, + { + 0xdbff, 0x4300, 0x0100, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0xff01, + 0xdbff, 0x4300, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0101, 0x0101, 0xff01, + } }; + + if (quality < 0 || quality > 10) { + printk(KERN_WARNING + "meye: invalid quality level %d - using 8\n", quality); + quality = 8; + } + + *length = ARRAY_SIZE(jpeg_tables[quality]); + return jpeg_tables[quality]; +} + +/* return a generic set of huffman tables */ +static u16 *jpeg_huffman_tables(int *length) +{ + static u16 tables[] = { + 0xC4FF, 0xB500, 0x0010, 0x0102, 0x0303, 0x0402, 0x0503, 0x0405, + 0x0004, 0x0100, 0x017D, 0x0302, 0x0400, 0x0511, 0x2112, 0x4131, + 0x1306, 0x6151, 0x2207, 0x1471, 0x8132, 0xA191, 0x2308, 0xB142, + 0x15C1, 0xD152, 0x24F0, 0x6233, 0x8272, 0x0A09, 0x1716, 0x1918, + 0x251A, 0x2726, 0x2928, 0x342A, 0x3635, 0x3837, 0x3A39, 0x4443, + 0x4645, 0x4847, 0x4A49, 0x5453, 0x5655, 0x5857, 0x5A59, 0x6463, + 0x6665, 0x6867, 0x6A69, 0x7473, 0x7675, 0x7877, 0x7A79, 0x8483, + 0x8685, 0x8887, 0x8A89, 0x9392, 0x9594, 0x9796, 0x9998, 0xA29A, + 0xA4A3, 0xA6A5, 0xA8A7, 0xAAA9, 0xB3B2, 0xB5B4, 0xB7B6, 0xB9B8, + 0xC2BA, 0xC4C3, 0xC6C5, 0xC8C7, 0xCAC9, 0xD3D2, 0xD5D4, 0xD7D6, + 0xD9D8, 0xE1DA, 0xE3E2, 0xE5E4, 0xE7E6, 0xE9E8, 0xF1EA, 0xF3F2, + 0xF5F4, 0xF7F6, 0xF9F8, 0xFFFA, + 0xC4FF, 0xB500, 0x0011, 0x0102, 0x0402, 0x0304, 0x0704, 0x0405, + 0x0004, 0x0201, 0x0077, 0x0201, 0x1103, 0x0504, 0x3121, 0x1206, + 0x5141, 0x6107, 0x1371, 0x3222, 0x0881, 0x4214, 0xA191, 0xC1B1, + 0x2309, 0x5233, 0x15F0, 0x7262, 0x0AD1, 0x2416, 0xE134, 0xF125, + 0x1817, 0x1A19, 0x2726, 0x2928, 0x352A, 0x3736, 0x3938, 0x433A, + 0x4544, 0x4746, 0x4948, 0x534A, 0x5554, 0x5756, 0x5958, 0x635A, + 0x6564, 0x6766, 0x6968, 0x736A, 0x7574, 0x7776, 0x7978, 0x827A, + 0x8483, 0x8685, 0x8887, 0x8A89, 0x9392, 0x9594, 0x9796, 0x9998, + 0xA29A, 0xA4A3, 0xA6A5, 0xA8A7, 0xAAA9, 0xB3B2, 0xB5B4, 0xB7B6, + 0xB9B8, 0xC2BA, 0xC4C3, 0xC6C5, 0xC8C7, 0xCAC9, 0xD3D2, 0xD5D4, + 0xD7D6, 0xD9D8, 0xE2DA, 0xE4E3, 0xE6E5, 0xE8E7, 0xEAE9, 0xF3F2, + 0xF5F4, 0xF7F6, 0xF9F8, 0xFFFA, + 0xC4FF, 0x1F00, 0x0000, 0x0501, 0x0101, 0x0101, 0x0101, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0201, 0x0403, 0x0605, 0x0807, 0x0A09, + 0xFF0B, + 0xC4FF, 0x1F00, 0x0001, 0x0103, 0x0101, 0x0101, 0x0101, 0x0101, + 0x0000, 0x0000, 0x0000, 0x0201, 0x0403, 0x0605, 0x0807, 0x0A09, + 0xFF0B + }; + + *length = ARRAY_SIZE(tables); + return tables; +} + +/****************************************************************************/ +/* MCHIP low-level functions */ +/****************************************************************************/ + +/* returns the horizontal capture size */ +static inline int mchip_hsize(void) +{ + return meye.params.subsample ? 320 : 640; +} + +/* returns the vertical capture size */ +static inline int mchip_vsize(void) +{ + return meye.params.subsample ? 240 : 480; +} + +/* waits for a register to be available */ +static void mchip_sync(int reg) +{ + u32 status; + int i; + + if (reg == MCHIP_MM_FIFO_DATA) { + for (i = 0; i < MCHIP_REG_TIMEOUT; i++) { + status = readl(meye.mchip_mmregs + + MCHIP_MM_FIFO_STATUS); + if (!(status & MCHIP_MM_FIFO_WAIT)) { + printk(KERN_WARNING "meye: fifo not ready\n"); + return; + } + if (status & MCHIP_MM_FIFO_READY) + return; + udelay(1); + } + } else if (reg > 0x80) { + u32 mask = (reg < 0x100) ? MCHIP_HIC_STATUS_MCC_RDY + : MCHIP_HIC_STATUS_VRJ_RDY; + for (i = 0; i < MCHIP_REG_TIMEOUT; i++) { + status = readl(meye.mchip_mmregs + MCHIP_HIC_STATUS); + if (status & mask) + return; + udelay(1); + } + } else + return; + printk(KERN_WARNING + "meye: mchip_sync() timeout on reg 0x%x status=0x%x\n", + reg, status); +} + +/* sets a value into the register */ +static inline void mchip_set(int reg, u32 v) +{ + mchip_sync(reg); + writel(v, meye.mchip_mmregs + reg); +} + +/* get the register value */ +static inline u32 mchip_read(int reg) +{ + mchip_sync(reg); + return readl(meye.mchip_mmregs + reg); +} + +/* wait for a register to become a particular value */ +static inline int mchip_delay(u32 reg, u32 v) +{ + int n = 10; + while (--n && mchip_read(reg) != v) + udelay(1); + return n; +} + +/* setup subsampling */ +static void mchip_subsample(void) +{ + mchip_set(MCHIP_MCC_R_SAMPLING, meye.params.subsample); + mchip_set(MCHIP_MCC_R_XRANGE, mchip_hsize()); + mchip_set(MCHIP_MCC_R_YRANGE, mchip_vsize()); + mchip_set(MCHIP_MCC_B_XRANGE, mchip_hsize()); + mchip_set(MCHIP_MCC_B_YRANGE, mchip_vsize()); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); +} + +/* set the framerate into the mchip */ +static void mchip_set_framerate(void) +{ + mchip_set(MCHIP_HIC_S_RATE, meye.params.framerate); +} + +/* load some huffman and quantisation tables into the VRJ chip ready + for JPEG compression */ +static void mchip_load_tables(void) +{ + int i; + int length; + u16 *tables; + + tables = jpeg_huffman_tables(&length); + for (i = 0; i < length; i++) + writel(tables[i], meye.mchip_mmregs + MCHIP_VRJ_TABLE_DATA); + + tables = jpeg_quantisation_tables(&length, meye.params.quality); + for (i = 0; i < length; i++) + writel(tables[i], meye.mchip_mmregs + MCHIP_VRJ_TABLE_DATA); +} + +/* setup the VRJ parameters in the chip */ +static void mchip_vrj_setup(u8 mode) +{ + mchip_set(MCHIP_VRJ_BUS_MODE, 5); + mchip_set(MCHIP_VRJ_SIGNAL_ACTIVE_LEVEL, 0x1f); + mchip_set(MCHIP_VRJ_PDAT_USE, 1); + mchip_set(MCHIP_VRJ_IRQ_FLAG, 0xa0); + mchip_set(MCHIP_VRJ_MODE_SPECIFY, mode); + mchip_set(MCHIP_VRJ_NUM_LINES, mchip_vsize()); + mchip_set(MCHIP_VRJ_NUM_PIXELS, mchip_hsize()); + mchip_set(MCHIP_VRJ_NUM_COMPONENTS, 0x1b); + mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_LO, 0xFFFF); + mchip_set(MCHIP_VRJ_LIMIT_COMPRESSED_HI, 0xFFFF); + mchip_set(MCHIP_VRJ_COMP_DATA_FORMAT, 0xC); + mchip_set(MCHIP_VRJ_RESTART_INTERVAL, 0); + mchip_set(MCHIP_VRJ_SOF1, 0x601); + mchip_set(MCHIP_VRJ_SOF2, 0x1502); + mchip_set(MCHIP_VRJ_SOF3, 0x1503); + mchip_set(MCHIP_VRJ_SOF4, 0x1596); + mchip_set(MCHIP_VRJ_SOS, 0x0ed0); + + mchip_load_tables(); +} + +/* sets the DMA parameters into the chip */ +static void mchip_dma_setup(dma_addr_t dma_addr) +{ + int i; + + mchip_set(MCHIP_MM_PT_ADDR, (u32)dma_addr); + for (i = 0; i < 4; i++) + mchip_set(MCHIP_MM_FIR(i), 0); + meye.mchip_fnum = 0; +} + +/* setup for DMA transfers - also zeros the framebuffer */ +static int mchip_dma_alloc(void) +{ + if (!meye.mchip_dmahandle) + if (ptable_alloc()) + return -1; + return 0; +} + +/* frees the DMA buffer */ +static void mchip_dma_free(void) +{ + if (meye.mchip_dmahandle) { + mchip_dma_setup(0); + ptable_free(); + } +} + +/* stop any existing HIC action and wait for any dma to complete then + reset the dma engine */ +static void mchip_hic_stop(void) +{ + int i, j; + + meye.mchip_mode = MCHIP_HIC_MODE_NOOP; + if (!(mchip_read(MCHIP_HIC_STATUS) & MCHIP_HIC_STATUS_BUSY)) + return; + for (i = 0; i < 20; ++i) { + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_STOP); + mchip_delay(MCHIP_HIC_CMD, 0); + for (j = 0; j < 100; ++j) { + if (mchip_delay(MCHIP_HIC_STATUS, + MCHIP_HIC_STATUS_IDLE)) + return; + msleep(1); + } + printk(KERN_ERR "meye: need to reset HIC!\n"); + + mchip_set(MCHIP_HIC_CTL, MCHIP_HIC_CTL_SOFT_RESET); + msleep(250); + } + printk(KERN_ERR "meye: resetting HIC hanged!\n"); +} + +/****************************************************************************/ +/* MCHIP frame processing functions */ +/****************************************************************************/ + +/* get the next ready frame from the dma engine */ +static u32 mchip_get_frame(void) +{ + u32 v; + + v = mchip_read(MCHIP_MM_FIR(meye.mchip_fnum)); + return v; +} + +/* frees the current frame from the dma engine */ +static void mchip_free_frame(void) +{ + mchip_set(MCHIP_MM_FIR(meye.mchip_fnum), 0); + meye.mchip_fnum++; + meye.mchip_fnum %= 4; +} + +/* read one frame from the framebuffer assuming it was captured using + a uncompressed transfer */ +static void mchip_cont_read_frame(u32 v, u8 *buf, int size) +{ + int pt_id; + + pt_id = (v >> 17) & 0x3FF; + + ptable_copy(buf, pt_id, size, MCHIP_NB_PAGES); +} + +/* read a compressed frame from the framebuffer */ +static int mchip_comp_read_frame(u32 v, u8 *buf, int size) +{ + int pt_start, pt_end, trailer; + int fsize; + int i; + + pt_start = (v >> 19) & 0xFF; + pt_end = (v >> 11) & 0xFF; + trailer = (v >> 1) & 0x3FF; + + if (pt_end < pt_start) + fsize = (MCHIP_NB_PAGES_MJPEG - pt_start) * PAGE_SIZE + + pt_end * PAGE_SIZE + trailer * 4; + else + fsize = (pt_end - pt_start) * PAGE_SIZE + trailer * 4; + + if (fsize > size) { + printk(KERN_WARNING "meye: oversized compressed frame %d\n", + fsize); + return -1; + } + + ptable_copy(buf, pt_start, fsize, MCHIP_NB_PAGES_MJPEG); + +#ifdef MEYE_JPEG_CORRECTION + + /* Some mchip generated jpeg frames are incorrect. In most + * (all ?) of those cases, the final EOI (0xff 0xd9) marker + * is not present at the end of the frame. + * + * Since adding the final marker is not enough to restore + * the jpeg integrity, we drop the frame. + */ + + for (i = fsize - 1; i > 0 && buf[i] == 0xff; i--) ; + + if (i < 2 || buf[i - 1] != 0xff || buf[i] != 0xd9) + return -1; + +#endif + + return fsize; +} + +/* take a picture into SDRAM */ +static void mchip_take_picture(void) +{ + int i; + + mchip_hic_stop(); + mchip_subsample(); + mchip_dma_setup(meye.mchip_dmahandle); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_CAP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } +} + +/* dma a previously taken picture into a buffer */ +static void mchip_get_picture(u8 *buf, int bufsize) +{ + u32 v; + int i; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_OUT); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } + for (i = 0; i < 4; ++i) { + v = mchip_get_frame(); + if (v & MCHIP_MM_FIR_RDY) { + mchip_cont_read_frame(v, buf, bufsize); + break; + } + mchip_free_frame(); + } +} + +/* start continuous dma capture */ +static void mchip_continuous_start(void) +{ + mchip_hic_stop(); + mchip_subsample(); + mchip_set_framerate(); + mchip_dma_setup(meye.mchip_dmahandle); + + meye.mchip_mode = MCHIP_HIC_MODE_CONT_OUT; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_OUT); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); +} + +/* compress one frame into a buffer */ +static int mchip_compress_frame(u8 *buf, int bufsize) +{ + u32 v; + int len = -1, i; + + mchip_vrj_setup(0x3f); + udelay(50); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_COMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + for (i = 0; i < 100; ++i) { + if (mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE)) + break; + msleep(1); + } + + for (i = 0; i < 4; ++i) { + v = mchip_get_frame(); + if (v & MCHIP_MM_FIR_RDY) { + len = mchip_comp_read_frame(v, buf, bufsize); + break; + } + mchip_free_frame(); + } + return len; +} + +#if 0 +/* uncompress one image into a buffer */ +static int mchip_uncompress_frame(u8 *img, int imgsize, u8 *buf, int bufsize) +{ + mchip_vrj_setup(0x3f); + udelay(50); + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_STILL_DECOMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); + + return mchip_comp_read_frame(buf, bufsize); +} +#endif + +/* start continuous compressed capture */ +static void mchip_cont_compression_start(void) +{ + mchip_hic_stop(); + mchip_vrj_setup(0x3f); + mchip_subsample(); + mchip_set_framerate(); + mchip_dma_setup(meye.mchip_dmahandle); + + meye.mchip_mode = MCHIP_HIC_MODE_CONT_COMP; + + mchip_set(MCHIP_HIC_MODE, MCHIP_HIC_MODE_CONT_COMP); + mchip_set(MCHIP_HIC_CMD, MCHIP_HIC_CMD_START); + + mchip_delay(MCHIP_HIC_CMD, 0); +} + +/****************************************************************************/ +/* Interrupt handling */ +/****************************************************************************/ + +static irqreturn_t meye_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 v; + int reqnr; + static int sequence = 0; + + v = mchip_read(MCHIP_MM_INTA); + + if (meye.mchip_mode != MCHIP_HIC_MODE_CONT_OUT && + meye.mchip_mode != MCHIP_HIC_MODE_CONT_COMP) + return IRQ_NONE; + +again: + v = mchip_get_frame(); + if (!(v & MCHIP_MM_FIR_RDY)) + return IRQ_HANDLED; + + if (meye.mchip_mode == MCHIP_HIC_MODE_CONT_OUT) { + if (kfifo_get(meye.grabq, (unsigned char *)&reqnr, + sizeof(int)) != sizeof(int)) { + mchip_free_frame(); + return IRQ_HANDLED; + } + mchip_cont_read_frame(v, meye.grab_fbuffer + gbufsize * reqnr, + mchip_hsize() * mchip_vsize() * 2); + meye.grab_buffer[reqnr].size = mchip_hsize() * mchip_vsize() * 2; + meye.grab_buffer[reqnr].state = MEYE_BUF_DONE; + do_gettimeofday(&meye.grab_buffer[reqnr].timestamp); + meye.grab_buffer[reqnr].sequence = sequence++; + kfifo_put(meye.doneq, (unsigned char *)&reqnr, sizeof(int)); + wake_up_interruptible(&meye.proc_list); + } else { + int size; + size = mchip_comp_read_frame(v, meye.grab_temp, gbufsize); + if (size == -1) { + mchip_free_frame(); + goto again; + } + if (kfifo_get(meye.grabq, (unsigned char *)&reqnr, + sizeof(int)) != sizeof(int)) { + mchip_free_frame(); + goto again; + } + memcpy(meye.grab_fbuffer + gbufsize * reqnr, meye.grab_temp, + size); + meye.grab_buffer[reqnr].size = size; + meye.grab_buffer[reqnr].state = MEYE_BUF_DONE; + do_gettimeofday(&meye.grab_buffer[reqnr].timestamp); + meye.grab_buffer[reqnr].sequence = sequence++; + kfifo_put(meye.doneq, (unsigned char *)&reqnr, sizeof(int)); + wake_up_interruptible(&meye.proc_list); + } + mchip_free_frame(); + goto again; +} + +/****************************************************************************/ +/* video4linux integration */ +/****************************************************************************/ + +static int meye_open(struct inode *inode, struct file *file) +{ + int i, err; + + err = video_exclusive_open(inode, file); + if (err < 0) + return err; + + mchip_hic_stop(); + + if (mchip_dma_alloc()) { + printk(KERN_ERR "meye: mchip framebuffer allocation failed\n"); + video_exclusive_release(inode, file); + return -ENOBUFS; + } + + for (i = 0; i < MEYE_MAX_BUFNBRS; i++) + meye.grab_buffer[i].state = MEYE_BUF_UNUSED; + kfifo_reset(meye.grabq); + kfifo_reset(meye.doneq); + return 0; +} + +static int meye_release(struct inode *inode, struct file *file) +{ + mchip_hic_stop(); + mchip_dma_free(); + video_exclusive_release(inode, file); + return 0; +} + +static int meye_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + switch (cmd) { + + case VIDIOCGCAP: { + struct video_capability *b = arg; + strcpy(b->name,meye.video_dev->name); + b->type = VID_TYPE_CAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = 640; + b->maxheight = 480; + b->minwidth = 320; + b->minheight = 240; + break; + } + + case VIDIOCGCHAN: { + struct video_channel *v = arg; + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + if (v->channel != 0) + return -EINVAL; + strcpy(v->name,"Camera"); + break; + } + + case VIDIOCSCHAN: { + struct video_channel *v = arg; + if (v->channel != 0) + return -EINVAL; + break; + } + + case VIDIOCGPICT: { + struct video_picture *p = arg; + *p = meye.picture; + break; + } + + case VIDIOCSPICT: { + struct video_picture *p = arg; + if (p->depth != 16) + return -EINVAL; + if (p->palette != VIDEO_PALETTE_YUV422) + return -EINVAL; + mutex_lock(&meye.lock); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, + p->brightness >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, + p->hue >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, + p->colour >> 10); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, + p->contrast >> 10); + meye.picture = *p; + mutex_unlock(&meye.lock); + break; + } + + case VIDIOCSYNC: { + int *i = arg; + int unused; + + if (*i < 0 || *i >= gbuffers) + return -EINVAL; + + mutex_lock(&meye.lock); + + switch (meye.grab_buffer[*i].state) { + + case MEYE_BUF_UNUSED: + mutex_unlock(&meye.lock); + return -EINVAL; + case MEYE_BUF_USING: + if (file->f_flags & O_NONBLOCK) { + mutex_unlock(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + (meye.grab_buffer[*i].state != MEYE_BUF_USING))) { + mutex_unlock(&meye.lock); + return -EINTR; + } + /* fall through */ + case MEYE_BUF_DONE: + meye.grab_buffer[*i].state = MEYE_BUF_UNUSED; + kfifo_get(meye.doneq, (unsigned char *)&unused, sizeof(int)); + } + mutex_unlock(&meye.lock); + break; + } + + case VIDIOCMCAPTURE: { + struct video_mmap *vm = arg; + int restart = 0; + + if (vm->frame >= gbuffers || vm->frame < 0) + return -EINVAL; + if (vm->format != VIDEO_PALETTE_YUV422) + return -EINVAL; + if (vm->height * vm->width * 2 > gbufsize) + return -EINVAL; + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[vm->frame].state != MEYE_BUF_UNUSED) + return -EBUSY; + + mutex_lock(&meye.lock); + if (vm->width == 640 && vm->height == 480) { + if (meye.params.subsample) { + meye.params.subsample = 0; + restart = 1; + } + } else if (vm->width == 320 && vm->height == 240) { + if (!meye.params.subsample) { + meye.params.subsample = 1; + restart = 1; + } + } else { + mutex_unlock(&meye.lock); + return -EINVAL; + } + + if (restart || meye.mchip_mode != MCHIP_HIC_MODE_CONT_OUT) + mchip_continuous_start(); + meye.grab_buffer[vm->frame].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)&vm->frame, sizeof(int)); + mutex_unlock(&meye.lock); + break; + } + + case VIDIOCGMBUF: { + struct video_mbuf *vm = arg; + int i; + + memset(vm, 0 , sizeof(*vm)); + vm->size = gbufsize * gbuffers; + vm->frames = gbuffers; + for (i = 0; i < gbuffers; i++) + vm->offsets[i] = i * gbufsize; + break; + } + + case MEYEIOC_G_PARAMS: { + struct meye_params *p = arg; + *p = meye.params; + break; + } + + case MEYEIOC_S_PARAMS: { + struct meye_params *jp = arg; + if (jp->subsample > 1) + return -EINVAL; + if (jp->quality > 10) + return -EINVAL; + if (jp->sharpness > 63 || jp->agc > 63 || jp->picture > 63) + return -EINVAL; + if (jp->framerate > 31) + return -EINVAL; + mutex_lock(&meye.lock); + if (meye.params.subsample != jp->subsample || + meye.params.quality != jp->quality) + mchip_hic_stop(); /* need restart */ + meye.params = *jp; + sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, + meye.params.sharpness); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, + meye.params.agc); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, + meye.params.picture); + mutex_unlock(&meye.lock); + break; + } + + case MEYEIOC_QBUF_CAPT: { + int *nb = arg; + + if (!meye.grab_fbuffer) + return -EINVAL; + if (*nb >= gbuffers) + return -EINVAL; + if (*nb < 0) { + /* stop capture */ + mchip_hic_stop(); + return 0; + } + if (meye.grab_buffer[*nb].state != MEYE_BUF_UNUSED) + return -EBUSY; + mutex_lock(&meye.lock); + if (meye.mchip_mode != MCHIP_HIC_MODE_CONT_COMP) + mchip_cont_compression_start(); + meye.grab_buffer[*nb].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)nb, sizeof(int)); + mutex_unlock(&meye.lock); + break; + } + + case MEYEIOC_SYNC: { + int *i = arg; + int unused; + + if (*i < 0 || *i >= gbuffers) + return -EINVAL; + + mutex_lock(&meye.lock); + switch (meye.grab_buffer[*i].state) { + + case MEYE_BUF_UNUSED: + mutex_unlock(&meye.lock); + return -EINVAL; + case MEYE_BUF_USING: + if (file->f_flags & O_NONBLOCK) { + mutex_unlock(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + (meye.grab_buffer[*i].state != MEYE_BUF_USING))) { + mutex_unlock(&meye.lock); + return -EINTR; + } + /* fall through */ + case MEYE_BUF_DONE: + meye.grab_buffer[*i].state = MEYE_BUF_UNUSED; + kfifo_get(meye.doneq, (unsigned char *)&unused, sizeof(int)); + } + *i = meye.grab_buffer[*i].size; + mutex_unlock(&meye.lock); + break; + } + + case MEYEIOC_STILLCAPT: { + + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[0].state != MEYE_BUF_UNUSED) + return -EBUSY; + mutex_lock(&meye.lock); + meye.grab_buffer[0].state = MEYE_BUF_USING; + mchip_take_picture(); + mchip_get_picture( + meye.grab_fbuffer, + mchip_hsize() * mchip_vsize() * 2); + meye.grab_buffer[0].state = MEYE_BUF_DONE; + mutex_unlock(&meye.lock); + break; + } + + case MEYEIOC_STILLJCAPT: { + int *len = arg; + + if (!meye.grab_fbuffer) + return -EINVAL; + if (meye.grab_buffer[0].state != MEYE_BUF_UNUSED) + return -EBUSY; + mutex_lock(&meye.lock); + meye.grab_buffer[0].state = MEYE_BUF_USING; + *len = -1; + while (*len == -1) { + mchip_take_picture(); + *len = mchip_compress_frame(meye.grab_fbuffer, gbufsize); + } + meye.grab_buffer[0].state = MEYE_BUF_DONE; + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = arg; + + if (forcev4l1) + return -EINVAL; + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "meye"); + strcpy(cap->card, "meye"); + sprintf(cap->bus_info, "PCI:%s", pci_name(meye.mchip_dev)); + cap->version = (MEYE_DRIVER_MAJORVERSION << 8) + + MEYE_DRIVER_MINORVERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING; + break; + } + + case VIDIOC_ENUMINPUT: { + struct v4l2_input *i = arg; + + if (i->index != 0) + return -EINVAL; + memset(i, 0, sizeof(*i)); + i->index = 0; + strcpy(i->name, "Camera"); + i->type = V4L2_INPUT_TYPE_CAMERA; + break; + } + + case VIDIOC_G_INPUT: { + int *i = arg; + + *i = 0; + break; + } + + case VIDIOC_S_INPUT: { + int *i = arg; + + if (*i != 0) + return -EINVAL; + break; + } + + case VIDIOC_QUERYCTRL: { + struct v4l2_queryctrl *c = arg; + + switch (c->id) { + + case V4L2_CID_BRIGHTNESS: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Brightness"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_HUE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Hue"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_CONTRAST: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Contrast"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_SATURATION: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Saturation"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_AGC: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Agc"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 48; + c->flags = 0; + break; + case V4L2_CID_SHARPNESS: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Sharpness"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 32; + c->flags = 0; + break; + case V4L2_CID_PICTURE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Picture"); + c->minimum = 0; + c->maximum = 63; + c->step = 1; + c->default_value = 0; + c->flags = 0; + break; + case V4L2_CID_JPEGQUAL: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "JPEG quality"); + c->minimum = 0; + c->maximum = 10; + c->step = 1; + c->default_value = 8; + c->flags = 0; + break; + case V4L2_CID_FRAMERATE: + c->type = V4L2_CTRL_TYPE_INTEGER; + strcpy(c->name, "Framerate"); + c->minimum = 0; + c->maximum = 31; + c->step = 1; + c->default_value = 0; + c->flags = 0; + break; + default: + return -EINVAL; + } + break; + } + + case VIDIOC_S_CTRL: { + struct v4l2_control *c = arg; + + mutex_lock(&meye.lock); + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERABRIGHTNESS, c->value); + meye.picture.brightness = c->value << 10; + break; + case V4L2_CID_HUE: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAHUE, c->value); + meye.picture.hue = c->value << 10; + break; + case V4L2_CID_CONTRAST: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERACONTRAST, c->value); + meye.picture.contrast = c->value << 10; + break; + case V4L2_CID_SATURATION: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERACOLOR, c->value); + meye.picture.colour = c->value << 10; + break; + case V4L2_CID_AGC: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAAGC, c->value); + meye.params.agc = c->value; + break; + case V4L2_CID_SHARPNESS: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERASHARPNESS, c->value); + meye.params.sharpness = c->value; + break; + case V4L2_CID_PICTURE: + sonypi_camera_command( + SONYPI_COMMAND_SETCAMERAPICTURE, c->value); + meye.params.picture = c->value; + break; + case V4L2_CID_JPEGQUAL: + meye.params.quality = c->value; + break; + case V4L2_CID_FRAMERATE: + meye.params.framerate = c->value; + break; + default: + mutex_unlock(&meye.lock); + return -EINVAL; + } + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_G_CTRL: { + struct v4l2_control *c = arg; + + mutex_lock(&meye.lock); + switch (c->id) { + case V4L2_CID_BRIGHTNESS: + c->value = meye.picture.brightness >> 10; + break; + case V4L2_CID_HUE: + c->value = meye.picture.hue >> 10; + break; + case V4L2_CID_CONTRAST: + c->value = meye.picture.contrast >> 10; + break; + case V4L2_CID_SATURATION: + c->value = meye.picture.colour >> 10; + break; + case V4L2_CID_AGC: + c->value = meye.params.agc; + break; + case V4L2_CID_SHARPNESS: + c->value = meye.params.sharpness; + break; + case V4L2_CID_PICTURE: + c->value = meye.params.picture; + break; + case V4L2_CID_JPEGQUAL: + c->value = meye.params.quality; + break; + case V4L2_CID_FRAMERATE: + c->value = meye.params.framerate; + break; + default: + mutex_unlock(&meye.lock); + return -EINVAL; + } + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_ENUM_FMT: { + struct v4l2_fmtdesc *f = arg; + + if (f->index > 1) + return -EINVAL; + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->index == 0) { + /* standard YUV 422 capture */ + memset(f, 0, sizeof(*f)); + f->index = 0; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = 0; + strcpy(f->description, "YUV422"); + f->pixelformat = V4L2_PIX_FMT_YUYV; + } else { + /* compressed MJPEG capture */ + memset(f, 0, sizeof(*f)); + f->index = 1; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + strcpy(f->description, "MJPEG"); + f->pixelformat = V4L2_PIX_FMT_MJPEG; + } + break; + } + + case VIDIOC_TRY_FMT: { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV && + f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + if (f->fmt.pix.field != V4L2_FIELD_ANY && + f->fmt.pix.field != V4L2_FIELD_NONE) + return -EINVAL; + f->fmt.pix.field = V4L2_FIELD_NONE; + if (f->fmt.pix.width <= 320) { + f->fmt.pix.width = 320; + f->fmt.pix.height = 240; + } else { + f->fmt.pix.width = 640; + f->fmt.pix.height = 480; + } + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * + f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + break; + } + + case VIDIOC_G_FMT: { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + switch (meye.mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + default: + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + break; + case MCHIP_HIC_MODE_CONT_COMP: + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + break; + } + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = mchip_hsize(); + f->fmt.pix.height = mchip_vsize(); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * + f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + break; + } + + case VIDIOC_S_FMT: { + struct v4l2_format *f = arg; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV && + f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + if (f->fmt.pix.field != V4L2_FIELD_ANY && + f->fmt.pix.field != V4L2_FIELD_NONE) + return -EINVAL; + f->fmt.pix.field = V4L2_FIELD_NONE; + mutex_lock(&meye.lock); + if (f->fmt.pix.width <= 320) { + f->fmt.pix.width = 320; + f->fmt.pix.height = 240; + meye.params.subsample = 1; + } else { + f->fmt.pix.width = 640; + f->fmt.pix.height = 480; + meye.params.subsample = 0; + } + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + meye.mchip_mode = MCHIP_HIC_MODE_CONT_OUT; + break; + case V4L2_PIX_FMT_MJPEG: + meye.mchip_mode = MCHIP_HIC_MODE_CONT_COMP; + break; + } + mutex_unlock(&meye.lock); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + f->fmt.pix.sizeimage = f->fmt.pix.height * + f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = 0; + f->fmt.pix.priv = 0; + + break; + } + + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + int i; + + if (req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (req->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + if (meye.grab_fbuffer && req->count == gbuffers) { + /* already allocated, no modifications */ + break; + } + mutex_lock(&meye.lock); + if (meye.grab_fbuffer) { + for (i = 0; i < gbuffers; i++) + if (meye.vma_use_count[i]) { + mutex_unlock(&meye.lock); + return -EINVAL; + } + rvfree(meye.grab_fbuffer, gbuffers * gbufsize); + meye.grab_fbuffer = NULL; + } + gbuffers = max(2, min((int)req->count, MEYE_MAX_BUFNBRS)); + req->count = gbuffers; + meye.grab_fbuffer = rvmalloc(gbuffers * gbufsize); + if (!meye.grab_fbuffer) { + printk(KERN_ERR "meye: v4l framebuffer allocation" + " failed\n"); + mutex_unlock(&meye.lock); + return -ENOMEM; + } + for (i = 0; i < gbuffers; i++) + meye.vma_use_count[i] = 0; + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (index < 0 || index >= gbuffers) + return -EINVAL; + memset(buf, 0, sizeof(*buf)); + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->index = index; + buf->bytesused = meye.grab_buffer[index].size; + buf->flags = V4L2_BUF_FLAG_MAPPED; + if (meye.grab_buffer[index].state == MEYE_BUF_USING) + buf->flags |= V4L2_BUF_FLAG_QUEUED; + if (meye.grab_buffer[index].state == MEYE_BUF_DONE) + buf->flags |= V4L2_BUF_FLAG_DONE; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = meye.grab_buffer[index].timestamp; + buf->sequence = meye.grab_buffer[index].sequence; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = index * gbufsize; + buf->length = gbufsize; + break; + } + + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (buf->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + if (buf->index < 0 || buf->index >= gbuffers) + return -EINVAL; + if (meye.grab_buffer[buf->index].state != MEYE_BUF_UNUSED) + return -EINVAL; + mutex_lock(&meye.lock); + buf->flags |= V4L2_BUF_FLAG_QUEUED; + buf->flags &= ~V4L2_BUF_FLAG_DONE; + meye.grab_buffer[buf->index].state = MEYE_BUF_USING; + kfifo_put(meye.grabq, (unsigned char *)&buf->index, sizeof(int)); + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + int reqnr; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (buf->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + mutex_lock(&meye.lock); + if (kfifo_len(meye.doneq) == 0 && file->f_flags & O_NONBLOCK) { + mutex_unlock(&meye.lock); + return -EAGAIN; + } + if (wait_event_interruptible(meye.proc_list, + kfifo_len(meye.doneq) != 0) < 0) { + mutex_unlock(&meye.lock); + return -EINTR; + } + if (!kfifo_get(meye.doneq, (unsigned char *)&reqnr, + sizeof(int))) { + mutex_unlock(&meye.lock); + return -EBUSY; + } + if (meye.grab_buffer[reqnr].state != MEYE_BUF_DONE) { + mutex_unlock(&meye.lock); + return -EINVAL; + } + buf->index = reqnr; + buf->bytesused = meye.grab_buffer[reqnr].size; + buf->flags = V4L2_BUF_FLAG_MAPPED; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = meye.grab_buffer[reqnr].timestamp; + buf->sequence = meye.grab_buffer[reqnr].sequence; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = reqnr * gbufsize; + buf->length = gbufsize; + meye.grab_buffer[reqnr].state = MEYE_BUF_UNUSED; + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_STREAMON: { + mutex_lock(&meye.lock); + switch (meye.mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + mchip_continuous_start(); + break; + case MCHIP_HIC_MODE_CONT_COMP: + mchip_cont_compression_start(); + break; + default: + mutex_unlock(&meye.lock); + return -EINVAL; + } + mutex_unlock(&meye.lock); + break; + } + + case VIDIOC_STREAMOFF: { + int i; + + mutex_lock(&meye.lock); + mchip_hic_stop(); + kfifo_reset(meye.grabq); + kfifo_reset(meye.doneq); + for (i = 0; i < MEYE_MAX_BUFNBRS; i++) + meye.grab_buffer[i].state = MEYE_BUF_UNUSED; + mutex_unlock(&meye.lock); + break; + } + + /* + * XXX what about private snapshot ioctls ? + * Do they need to be converted to V4L2 ? + */ + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int meye_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, meye_do_ioctl); +} + +static unsigned int meye_poll(struct file *file, poll_table *wait) +{ + unsigned int res = 0; + + mutex_lock(&meye.lock); + poll_wait(file, &meye.proc_list, wait); + if (kfifo_len(meye.doneq)) + res = POLLIN | POLLRDNORM; + mutex_unlock(&meye.lock); + return res; +} + +static void meye_vm_open(struct vm_area_struct *vma) +{ + int idx = (int)vma->vm_private_data; + meye.vma_use_count[idx]++; +} + +static void meye_vm_close(struct vm_area_struct *vma) +{ + int idx = (int)vma->vm_private_data; + meye.vma_use_count[idx]--; +} + +static struct vm_operations_struct meye_vm_ops = { + .open = meye_vm_open, + .close = meye_vm_close, +}; + +static int meye_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long page, pos; + + mutex_lock(&meye.lock); + if (size > gbuffers * gbufsize) { + mutex_unlock(&meye.lock); + return -EINVAL; + } + if (!meye.grab_fbuffer) { + int i; + + /* lazy allocation */ + meye.grab_fbuffer = rvmalloc(gbuffers*gbufsize); + if (!meye.grab_fbuffer) { + printk(KERN_ERR "meye: v4l framebuffer allocation failed\n"); + mutex_unlock(&meye.lock); + return -ENOMEM; + } + for (i = 0; i < gbuffers; i++) + meye.vma_use_count[i] = 0; + } + pos = (unsigned long)meye.grab_fbuffer + offset; + + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + mutex_unlock(&meye.lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + vma->vm_ops = &meye_vm_ops; + vma->vm_flags &= ~VM_IO; /* not I/O memory */ + vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ + vma->vm_private_data = (void *) (offset / gbufsize); + meye_vm_open(vma); + + mutex_unlock(&meye.lock); + return 0; +} + +static struct file_operations meye_fops = { + .owner = THIS_MODULE, + .open = meye_open, + .release = meye_release, + .mmap = meye_mmap, + .ioctl = meye_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .poll = meye_poll, + .llseek = no_llseek, +}; + +static struct video_device meye_template = { + .owner = THIS_MODULE, + .name = "meye", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_MEYE, + .fops = &meye_fops, + .release = video_device_release, + .minor = -1, +}; + +#ifdef CONFIG_PM +static int meye_suspend(struct pci_dev *pdev, pm_message_t state) +{ + pci_save_state(pdev); + meye.pm_mchip_mode = meye.mchip_mode; + mchip_hic_stop(); + mchip_set(MCHIP_MM_INTA, 0x0); + return 0; +} + +static int meye_resume(struct pci_dev *pdev) +{ + pci_restore_state(pdev); + pci_write_config_word(meye.mchip_dev, MCHIP_PCI_SOFTRESET_SET, 1); + + mchip_delay(MCHIP_HIC_CMD, 0); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); + msleep(1); + mchip_set(MCHIP_VRJ_SOFT_RESET, 1); + msleep(1); + mchip_set(MCHIP_MM_PCI_MODE, 5); + msleep(1); + mchip_set(MCHIP_MM_INTA, MCHIP_MM_INTA_HIC_1_MASK); + + switch (meye.pm_mchip_mode) { + case MCHIP_HIC_MODE_CONT_OUT: + mchip_continuous_start(); + break; + case MCHIP_HIC_MODE_CONT_COMP: + mchip_cont_compression_start(); + break; + } + return 0; +} +#endif + +static int __devinit meye_probe(struct pci_dev *pcidev, + const struct pci_device_id *ent) +{ + int ret = -EBUSY; + unsigned long mchip_adr; + u8 revision; + + if (meye.mchip_dev != NULL) { + printk(KERN_ERR "meye: only one device allowed!\n"); + goto outnotdev; + } + + meye.mchip_dev = pcidev; + meye.video_dev = video_device_alloc(); + if (!meye.video_dev) { + printk(KERN_ERR "meye: video_device_alloc() failed!\n"); + goto outnotdev; + } + + ret = -ENOMEM; + meye.grab_temp = vmalloc(MCHIP_NB_PAGES_MJPEG * PAGE_SIZE); + if (!meye.grab_temp) { + printk(KERN_ERR "meye: grab buffer allocation failed\n"); + goto outvmalloc; + } + + spin_lock_init(&meye.grabq_lock); + meye.grabq = kfifo_alloc(sizeof(int) * MEYE_MAX_BUFNBRS, GFP_KERNEL, + &meye.grabq_lock); + if (IS_ERR(meye.grabq)) { + printk(KERN_ERR "meye: fifo allocation failed\n"); + goto outkfifoalloc1; + } + spin_lock_init(&meye.doneq_lock); + meye.doneq = kfifo_alloc(sizeof(int) * MEYE_MAX_BUFNBRS, GFP_KERNEL, + &meye.doneq_lock); + if (IS_ERR(meye.doneq)) { + printk(KERN_ERR "meye: fifo allocation failed\n"); + goto outkfifoalloc2; + } + + memcpy(meye.video_dev, &meye_template, sizeof(meye_template)); + meye.video_dev->dev = &meye.mchip_dev->dev; + + if ((ret = sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 1))) { + printk(KERN_ERR "meye: unable to power on the camera\n"); + printk(KERN_ERR "meye: did you enable the camera in " + "sonypi using the module options ?\n"); + goto outsonypienable; + } + + ret = -EIO; + if ((ret = pci_enable_device(meye.mchip_dev))) { + printk(KERN_ERR "meye: pci_enable_device failed\n"); + goto outenabledev; + } + + mchip_adr = pci_resource_start(meye.mchip_dev,0); + if (!mchip_adr) { + printk(KERN_ERR "meye: mchip has no device base address\n"); + goto outregions; + } + if (!request_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0), + "meye")) { + printk(KERN_ERR "meye: request_mem_region failed\n"); + goto outregions; + } + meye.mchip_mmregs = ioremap(mchip_adr, MCHIP_MM_REGS); + if (!meye.mchip_mmregs) { + printk(KERN_ERR "meye: ioremap failed\n"); + goto outremap; + } + + meye.mchip_irq = pcidev->irq; + if (request_irq(meye.mchip_irq, meye_irq, + SA_INTERRUPT | SA_SHIRQ, "meye", meye_irq)) { + printk(KERN_ERR "meye: request_irq failed\n"); + goto outreqirq; + } + + pci_read_config_byte(meye.mchip_dev, PCI_REVISION_ID, &revision); + pci_write_config_byte(meye.mchip_dev, PCI_CACHE_LINE_SIZE, 8); + pci_write_config_byte(meye.mchip_dev, PCI_LATENCY_TIMER, 64); + + pci_set_master(meye.mchip_dev); + + /* Ask the camera to perform a soft reset. */ + pci_write_config_word(meye.mchip_dev, MCHIP_PCI_SOFTRESET_SET, 1); + + mchip_delay(MCHIP_HIC_CMD, 0); + mchip_delay(MCHIP_HIC_STATUS, MCHIP_HIC_STATUS_IDLE); + + msleep(1); + mchip_set(MCHIP_VRJ_SOFT_RESET, 1); + + msleep(1); + mchip_set(MCHIP_MM_PCI_MODE, 5); + + msleep(1); + mchip_set(MCHIP_MM_INTA, MCHIP_MM_INTA_HIC_1_MASK); + + if (video_register_device(meye.video_dev, VFL_TYPE_GRABBER, + video_nr) < 0) { + printk(KERN_ERR "meye: video_register_device failed\n"); + goto outvideoreg; + } + + mutex_init(&meye.lock); + init_waitqueue_head(&meye.proc_list); + meye.picture.depth = 16; + meye.picture.palette = VIDEO_PALETTE_YUV422; + meye.picture.brightness = 32 << 10; + meye.picture.hue = 32 << 10; + meye.picture.colour = 32 << 10; + meye.picture.contrast = 32 << 10; + meye.picture.whiteness = 0; + meye.params.subsample = 0; + meye.params.quality = 8; + meye.params.sharpness = 32; + meye.params.agc = 48; + meye.params.picture = 0; + meye.params.framerate = 0; + + sonypi_camera_command(SONYPI_COMMAND_SETCAMERABRIGHTNESS, 32); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAHUE, 32); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACOLOR, 32); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERACONTRAST, 32); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERASHARPNESS, 32); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAPICTURE, 0); + sonypi_camera_command(SONYPI_COMMAND_SETCAMERAAGC, 48); + + printk(KERN_INFO "meye: Motion Eye Camera Driver v%s.\n", + MEYE_DRIVER_VERSION); + printk(KERN_INFO "meye: mchip KL5A72002 rev. %d, base %lx, irq %d\n", + revision, mchip_adr, meye.mchip_irq); + + return 0; + +outvideoreg: + free_irq(meye.mchip_irq, meye_irq); +outreqirq: + iounmap(meye.mchip_mmregs); +outremap: + release_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0)); +outregions: + pci_disable_device(meye.mchip_dev); +outenabledev: + sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); +outsonypienable: + kfifo_free(meye.doneq); +outkfifoalloc2: + kfifo_free(meye.grabq); +outkfifoalloc1: + vfree(meye.grab_temp); +outvmalloc: + video_device_release(meye.video_dev); +outnotdev: + return ret; +} + +static void __devexit meye_remove(struct pci_dev *pcidev) +{ + video_unregister_device(meye.video_dev); + + mchip_hic_stop(); + + mchip_dma_free(); + + /* disable interrupts */ + mchip_set(MCHIP_MM_INTA, 0x0); + + free_irq(meye.mchip_irq, meye_irq); + + iounmap(meye.mchip_mmregs); + + release_mem_region(pci_resource_start(meye.mchip_dev, 0), + pci_resource_len(meye.mchip_dev, 0)); + + pci_disable_device(meye.mchip_dev); + + sonypi_camera_command(SONYPI_COMMAND_SETCAMERA, 0); + + kfifo_free(meye.doneq); + kfifo_free(meye.grabq); + + vfree(meye.grab_temp); + + if (meye.grab_fbuffer) { + rvfree(meye.grab_fbuffer, gbuffers*gbufsize); + meye.grab_fbuffer = NULL; + } + + printk(KERN_INFO "meye: removed\n"); +} + +static struct pci_device_id meye_pci_tbl[] = { + { PCI_VENDOR_ID_KAWASAKI, PCI_DEVICE_ID_MCHIP_KL5A72002, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(pci, meye_pci_tbl); + +static struct pci_driver meye_driver = { + .name = "meye", + .id_table = meye_pci_tbl, + .probe = meye_probe, + .remove = __devexit_p(meye_remove), +#ifdef CONFIG_PM + .suspend = meye_suspend, + .resume = meye_resume, +#endif +}; + +static int __init meye_init(void) +{ + gbuffers = max(2, min((int)gbuffers, MEYE_MAX_BUFNBRS)); + if (gbufsize < 0 || gbufsize > MEYE_MAX_BUFSIZE) + gbufsize = MEYE_MAX_BUFSIZE; + gbufsize = PAGE_ALIGN(gbufsize); + printk(KERN_INFO "meye: using %d buffers with %dk (%dk total)" + "for capture\n", + gbuffers, + gbufsize / 1024, gbuffers * gbufsize / 1024); + return pci_register_driver(&meye_driver); +} + +static void __exit meye_exit(void) +{ + pci_unregister_driver(&meye_driver); +} + +module_init(meye_init); +module_exit(meye_exit); diff --git a/linux/drivers/media/video/meye.h b/linux/drivers/media/video/meye.h new file mode 100644 index 000000000..0d09a0e38 --- /dev/null +++ b/linux/drivers/media/video/meye.h @@ -0,0 +1,320 @@ +/* + * Motion Eye video4linux driver for Sony Vaio PictureBook + * + * Copyright (C) 2001-2004 Stelian Pop <stelian@popies.net> + * + * Copyright (C) 2001-2002 Alcôve <www.alcove.com> + * + * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> + * + * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. + * + * Some parts borrowed from various video4linux drivers, especially + * bttv-driver.c and zoran.c, see original files for credits. + * + * 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 _MEYE_PRIV_H_ +#define _MEYE_PRIV_H_ + +#define MEYE_DRIVER_MAJORVERSION 1 +#define MEYE_DRIVER_MINORVERSION 13 + +#define MEYE_DRIVER_VERSION __stringify(MEYE_DRIVER_MAJORVERSION) "." \ + __stringify(MEYE_DRIVER_MINORVERSION) + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/kfifo.h> + +/****************************************************************************/ +/* Motion JPEG chip registers */ +/****************************************************************************/ + +/* Motion JPEG chip PCI configuration registers */ +#define MCHIP_PCI_POWER_CSR 0x54 +#define MCHIP_PCI_MCORE_STATUS 0x60 /* see HIC_STATUS */ +#define MCHIP_PCI_HOSTUSEREQ_SET 0x64 +#define MCHIP_PCI_HOSTUSEREQ_CLR 0x68 +#define MCHIP_PCI_LOWPOWER_SET 0x6c +#define MCHIP_PCI_LOWPOWER_CLR 0x70 +#define MCHIP_PCI_SOFTRESET_SET 0x74 + +/* Motion JPEG chip memory mapped registers */ +#define MCHIP_MM_REGS 0x200 /* 512 bytes */ +#define MCHIP_REG_TIMEOUT 1000 /* reg access, ~us */ +#define MCHIP_MCC_VRJ_TIMEOUT 1000 /* MCC & VRJ access */ + +#define MCHIP_MM_PCI_MODE 0x00 /* PCI access mode */ +#define MCHIP_MM_PCI_MODE_RETRY 0x00000001 /* retry mode */ +#define MCHIP_MM_PCI_MODE_MASTER 0x00000002 /* master access */ +#define MCHIP_MM_PCI_MODE_READ_LINE 0x00000004 /* read line */ + +#define MCHIP_MM_INTA 0x04 /* Int status/mask */ +#define MCHIP_MM_INTA_MCC 0x00000001 /* MCC interrupt */ +#define MCHIP_MM_INTA_VRJ 0x00000002 /* VRJ interrupt */ +#define MCHIP_MM_INTA_HIC_1 0x00000004 /* one frame done */ +#define MCHIP_MM_INTA_HIC_1_MASK 0x00000400 /* 1: enable */ +#define MCHIP_MM_INTA_HIC_END 0x00000008 /* all frames done */ +#define MCHIP_MM_INTA_HIC_END_MASK 0x00000800 +#define MCHIP_MM_INTA_JPEG 0x00000010 /* decompress. error */ +#define MCHIP_MM_INTA_JPEG_MASK 0x00001000 +#define MCHIP_MM_INTA_CAPTURE 0x00000020 /* capture end */ +#define MCHIP_MM_INTA_PCI_ERR 0x00000040 /* PCI error */ +#define MCHIP_MM_INTA_PCI_ERR_MASK 0x00004000 + +#define MCHIP_MM_PT_ADDR 0x08 /* page table address*/ + /* n*4kB */ +#define MCHIP_NB_PAGES 1024 /* pages for display */ +#define MCHIP_NB_PAGES_MJPEG 256 /* pages for mjpeg */ + +#define MCHIP_MM_FIR(n) (0x0c+(n)*4) /* Frame info 0-3 */ +#define MCHIP_MM_FIR_RDY 0x00000001 /* frame ready */ +#define MCHIP_MM_FIR_FAILFR_MASK 0xf8000000 /* # of failed frames */ +#define MCHIP_MM_FIR_FAILFR_SHIFT 27 + + /* continuous comp/decomp mode */ +#define MCHIP_MM_FIR_C_ENDL_MASK 0x000007fe /* end DW [10] */ +#define MCHIP_MM_FIR_C_ENDL_SHIFT 1 +#define MCHIP_MM_FIR_C_ENDP_MASK 0x0007f800 /* end page [8] */ +#define MCHIP_MM_FIR_C_ENDP_SHIFT 11 +#define MCHIP_MM_FIR_C_STARTP_MASK 0x07f80000 /* start page [8] */ +#define MCHIP_MM_FIR_C_STARTP_SHIFT 19 + + /* continuous picture output mode */ +#define MCHIP_MM_FIR_O_STARTP_MASK 0x7ffe0000 /* start page [10] */ +#define MCHIP_MM_FIR_O_STARTP_SHIFT 17 + +#define MCHIP_MM_FIFO_DATA 0x1c /* PCI TGT FIFO data */ +#define MCHIP_MM_FIFO_STATUS 0x20 /* PCI TGT FIFO stat */ +#define MCHIP_MM_FIFO_MASK 0x00000003 +#define MCHIP_MM_FIFO_WAIT_OR_READY 0x00000002 /* Bits common to WAIT & READY*/ +#define MCHIP_MM_FIFO_IDLE 0x0 /* HIC idle */ +#define MCHIP_MM_FIFO_IDLE1 0x1 /* idem ??? */ +#define MCHIP_MM_FIFO_WAIT 0x2 /* wait request */ +#define MCHIP_MM_FIFO_READY 0x3 /* data ready */ + +#define MCHIP_HIC_HOST_USEREQ 0x40 /* host uses MCORE */ + +#define MCHIP_HIC_TP_BUSY 0x44 /* taking picture */ + +#define MCHIP_HIC_PIC_SAVED 0x48 /* pic in SDRAM */ + +#define MCHIP_HIC_LOWPOWER 0x4c /* clock stopped */ + +#define MCHIP_HIC_CTL 0x50 /* HIC control */ +#define MCHIP_HIC_CTL_SOFT_RESET 0x00000001 /* MCORE reset */ +#define MCHIP_HIC_CTL_MCORE_RDY 0x00000002 /* MCORE ready */ + +#define MCHIP_HIC_CMD 0x54 /* HIC command */ +#define MCHIP_HIC_CMD_BITS 0x00000003 /* cmd width=[1:0]*/ +#define MCHIP_HIC_CMD_NOOP 0x0 +#define MCHIP_HIC_CMD_START 0x1 +#define MCHIP_HIC_CMD_STOP 0x2 + +#define MCHIP_HIC_MODE 0x58 +#define MCHIP_HIC_MODE_NOOP 0x0 +#define MCHIP_HIC_MODE_STILL_CAP 0x1 /* still pic capt */ +#define MCHIP_HIC_MODE_DISPLAY 0x2 /* display */ +#define MCHIP_HIC_MODE_STILL_COMP 0x3 /* still pic comp. */ +#define MCHIP_HIC_MODE_STILL_DECOMP 0x4 /* still pic decomp. */ +#define MCHIP_HIC_MODE_CONT_COMP 0x5 /* cont capt+comp */ +#define MCHIP_HIC_MODE_CONT_DECOMP 0x6 /* cont decomp+disp */ +#define MCHIP_HIC_MODE_STILL_OUT 0x7 /* still pic output */ +#define MCHIP_HIC_MODE_CONT_OUT 0x8 /* cont output */ + +#define MCHIP_HIC_STATUS 0x5c +#define MCHIP_HIC_STATUS_MCC_RDY 0x00000001 /* MCC reg acc ok */ +#define MCHIP_HIC_STATUS_VRJ_RDY 0x00000002 /* VRJ reg acc ok */ +#define MCHIP_HIC_STATUS_IDLE 0x00000003 +#define MCHIP_HIC_STATUS_CAPDIS 0x00000004 /* cap/disp in prog */ +#define MCHIP_HIC_STATUS_COMPDEC 0x00000008 /* (de)comp in prog */ +#define MCHIP_HIC_STATUS_BUSY 0x00000010 /* HIC busy */ + +#define MCHIP_HIC_S_RATE 0x60 /* MJPEG # frames */ + +#define MCHIP_HIC_PCI_VFMT 0x64 /* video format */ +#define MCHIP_HIC_PCI_VFMT_YVYU 0x00000001 /* 0: V Y' U Y */ + /* 1: Y' V Y U */ + +#define MCHIP_MCC_CMD 0x80 /* MCC commands */ +#define MCHIP_MCC_CMD_INITIAL 0x0 /* idle ? */ +#define MCHIP_MCC_CMD_IIC_START_SET 0x1 +#define MCHIP_MCC_CMD_IIC_END_SET 0x2 +#define MCHIP_MCC_CMD_FM_WRITE 0x3 /* frame memory */ +#define MCHIP_MCC_CMD_FM_READ 0x4 +#define MCHIP_MCC_CMD_FM_STOP 0x5 +#define MCHIP_MCC_CMD_CAPTURE 0x6 +#define MCHIP_MCC_CMD_DISPLAY 0x7 +#define MCHIP_MCC_CMD_END_DISP 0x8 +#define MCHIP_MCC_CMD_STILL_COMP 0x9 +#define MCHIP_MCC_CMD_STILL_DECOMP 0xa +#define MCHIP_MCC_CMD_STILL_OUTPUT 0xb +#define MCHIP_MCC_CMD_CONT_OUTPUT 0xc +#define MCHIP_MCC_CMD_CONT_COMP 0xd +#define MCHIP_MCC_CMD_CONT_DECOMP 0xe +#define MCHIP_MCC_CMD_RESET 0xf /* MCC reset */ + +#define MCHIP_MCC_IIC_WR 0x84 + +#define MCHIP_MCC_MCC_WR 0x88 + +#define MCHIP_MCC_MCC_RD 0x8c + +#define MCHIP_MCC_STATUS 0x90 +#define MCHIP_MCC_STATUS_CAPT 0x00000001 /* capturing */ +#define MCHIP_MCC_STATUS_DISP 0x00000002 /* displaying */ +#define MCHIP_MCC_STATUS_COMP 0x00000004 /* compressing */ +#define MCHIP_MCC_STATUS_DECOMP 0x00000008 /* decompressing */ +#define MCHIP_MCC_STATUS_MCC_WR 0x00000010 /* register ready */ +#define MCHIP_MCC_STATUS_MCC_RD 0x00000020 /* register ready */ +#define MCHIP_MCC_STATUS_IIC_WR 0x00000040 /* register ready */ +#define MCHIP_MCC_STATUS_OUTPUT 0x00000080 /* output in prog */ + +#define MCHIP_MCC_SIG_POLARITY 0x94 +#define MCHIP_MCC_SIG_POL_VS_H 0x00000001 /* VS active-high */ +#define MCHIP_MCC_SIG_POL_HS_H 0x00000002 /* HS active-high */ +#define MCHIP_MCC_SIG_POL_DOE_H 0x00000004 /* DOE active-high */ + +#define MCHIP_MCC_IRQ 0x98 +#define MCHIP_MCC_IRQ_CAPDIS_STRT 0x00000001 /* cap/disp started */ +#define MCHIP_MCC_IRQ_CAPDIS_STRT_MASK 0x00000010 +#define MCHIP_MCC_IRQ_CAPDIS_END 0x00000002 /* cap/disp ended */ +#define MCHIP_MCC_IRQ_CAPDIS_END_MASK 0x00000020 +#define MCHIP_MCC_IRQ_COMPDEC_STRT 0x00000004 /* (de)comp started */ +#define MCHIP_MCC_IRQ_COMPDEC_STRT_MASK 0x00000040 +#define MCHIP_MCC_IRQ_COMPDEC_END 0x00000008 /* (de)comp ended */ +#define MCHIP_MCC_IRQ_COMPDEC_END_MASK 0x00000080 + +#define MCHIP_MCC_HSTART 0x9c /* video in */ +#define MCHIP_MCC_VSTART 0xa0 +#define MCHIP_MCC_HCOUNT 0xa4 +#define MCHIP_MCC_VCOUNT 0xa8 +#define MCHIP_MCC_R_XBASE 0xac /* capt/disp */ +#define MCHIP_MCC_R_YBASE 0xb0 +#define MCHIP_MCC_R_XRANGE 0xb4 +#define MCHIP_MCC_R_YRANGE 0xb8 +#define MCHIP_MCC_B_XBASE 0xbc /* comp/decomp */ +#define MCHIP_MCC_B_YBASE 0xc0 +#define MCHIP_MCC_B_XRANGE 0xc4 +#define MCHIP_MCC_B_YRANGE 0xc8 + +#define MCHIP_MCC_R_SAMPLING 0xcc /* 1: 1:4 */ + +#define MCHIP_VRJ_CMD 0x100 /* VRJ commands */ + +/* VRJ registers (see table 12.2.4) */ +#define MCHIP_VRJ_COMPRESSED_DATA 0x1b0 +#define MCHIP_VRJ_PIXEL_DATA 0x1b8 + +#define MCHIP_VRJ_BUS_MODE 0x100 +#define MCHIP_VRJ_SIGNAL_ACTIVE_LEVEL 0x108 +#define MCHIP_VRJ_PDAT_USE 0x110 +#define MCHIP_VRJ_MODE_SPECIFY 0x118 +#define MCHIP_VRJ_LIMIT_COMPRESSED_LO 0x120 +#define MCHIP_VRJ_LIMIT_COMPRESSED_HI 0x124 +#define MCHIP_VRJ_COMP_DATA_FORMAT 0x128 +#define MCHIP_VRJ_TABLE_DATA 0x140 +#define MCHIP_VRJ_RESTART_INTERVAL 0x148 +#define MCHIP_VRJ_NUM_LINES 0x150 +#define MCHIP_VRJ_NUM_PIXELS 0x158 +#define MCHIP_VRJ_NUM_COMPONENTS 0x160 +#define MCHIP_VRJ_SOF1 0x168 +#define MCHIP_VRJ_SOF2 0x170 +#define MCHIP_VRJ_SOF3 0x178 +#define MCHIP_VRJ_SOF4 0x180 +#define MCHIP_VRJ_SOS 0x188 +#define MCHIP_VRJ_SOFT_RESET 0x190 + +#define MCHIP_VRJ_STATUS 0x1c0 +#define MCHIP_VRJ_STATUS_BUSY 0x00001 +#define MCHIP_VRJ_STATUS_COMP_ACCESS 0x00002 +#define MCHIP_VRJ_STATUS_PIXEL_ACCESS 0x00004 +#define MCHIP_VRJ_STATUS_ERROR 0x00008 + +#define MCHIP_VRJ_IRQ_FLAG 0x1c8 +#define MCHIP_VRJ_ERROR_REPORT 0x1d8 + +#define MCHIP_VRJ_START_COMMAND 0x1a0 + +/****************************************************************************/ +/* Driver definitions. */ +/****************************************************************************/ + +/* Sony Programmable I/O Controller for accessing the camera commands */ +#include <linux/sonypi.h> + +/* private API definitions */ +#include <linux/meye.h> +#include <linux/mutex.h> + + +/* Enable jpg software correction */ +#define MEYE_JPEG_CORRECTION 1 + +/* Maximum size of a buffer */ +#define MEYE_MAX_BUFSIZE 614400 /* 640 * 480 * 2 */ + +/* Maximum number of buffers */ +#define MEYE_MAX_BUFNBRS 32 + +/* State of a buffer */ +#define MEYE_BUF_UNUSED 0 /* not used */ +#define MEYE_BUF_USING 1 /* currently grabbing / playing */ +#define MEYE_BUF_DONE 2 /* done */ + +/* grab buffer */ +struct meye_grab_buffer { + int state; /* state of buffer */ + unsigned long size; /* size of jpg frame */ + struct timeval timestamp; /* timestamp */ + unsigned long sequence; /* sequence number */ +}; + +/* size of kfifos containings buffer indices */ +#define MEYE_QUEUE_SIZE MEYE_MAX_BUFNBRS + +/* Motion Eye device structure */ +struct meye { + struct pci_dev *mchip_dev; /* pci device */ + u8 mchip_irq; /* irq */ + u8 mchip_mode; /* actual mchip mode: HIC_MODE... */ + u8 mchip_fnum; /* current mchip frame number */ + unsigned char __iomem *mchip_mmregs;/* mchip: memory mapped registers */ + u8 *mchip_ptable[MCHIP_NB_PAGES];/* mchip: ptable */ + void *mchip_ptable_toc; /* mchip: ptable toc */ + dma_addr_t mchip_dmahandle; /* mchip: dma handle to ptable toc */ + unsigned char *grab_fbuffer; /* capture framebuffer */ + unsigned char *grab_temp; /* temporary buffer */ + /* list of buffers */ + struct meye_grab_buffer grab_buffer[MEYE_MAX_BUFNBRS]; + int vma_use_count[MEYE_MAX_BUFNBRS]; /* mmap count */ + struct mutex lock; /* mutex for open/mmap... */ + struct kfifo *grabq; /* queue for buffers to be grabbed */ + spinlock_t grabq_lock; /* lock protecting the queue */ + struct kfifo *doneq; /* queue for grabbed buffers */ + spinlock_t doneq_lock; /* lock protecting the queue */ + wait_queue_head_t proc_list; /* wait queue */ + struct video_device *video_dev; /* video device parameters */ + struct video_picture picture; /* video picture parameters */ + struct meye_params params; /* additional parameters */ +#ifdef CONFIG_PM + u8 pm_mchip_mode; /* old mchip mode */ +#endif +}; + +#endif diff --git a/linux/drivers/media/video/ov511.c b/linux/drivers/media/video/ov511.c new file mode 100644 index 000000000..fdc8e3f13 --- /dev/null +++ b/linux/drivers/media/video/ov511.c @@ -0,0 +1,5932 @@ +/* + * OmniVision OV511 Camera-to-USB Bridge Driver + * + * Copyright (c) 1999-2003 Mark W. McClelland + * Original decompression code Copyright 1998-2000 OmniVision Technologies + * Many improvements by Bret Wallach <bwallac1@san.rr.com> + * Color fixes by by Orion Sky Lawlor <olawlor@acm.org> (2/26/2000) + * Snapshot code by Kevin Moore + * OV7620 fixes by Charl P. Botha <cpbotha@ieee.org> + * Changes by Claudio Matsuoka <claudio@conectiva.com> + * Original SAA7111A code by Dave Perks <dperks@ibm.net> + * URB error messages from pwc driver by Nemosoft + * generic_ioctl() code from videodev.c by Gerd Knorr and Alan Cox + * Memory management (rvmalloc) code from bttv driver, by Gerd Knorr and others + * + * Based on the Linux CPiA driver written by Peter Pregler, + * Scott J. Bertin and Johannes Erdfelt. + * + * Please see the file: Documentation/usb/ov511.txt + * and the website at: http://alpha.dyndns.org/ov511 + * for more info. + * + * 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/init.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <asm/semaphore.h> +#include <asm/processor.h> +#include <linux/mm.h> +#include <linux/device.h> + +#if defined (__i386__) + #include <asm/cpufeature.h> +#endif + +#include "ov511.h" + +/* + * Version Information + */ +#define DRIVER_VERSION "v1.64 for Linux 2.5" +#define EMAIL "mark@alpha.dyndns.org" +#define DRIVER_AUTHOR "Mark McClelland <mark@alpha.dyndns.org> & Bret Wallach \ + & Orion Sky Lawlor <olawlor@acm.org> & Kevin Moore & Charl P. Botha \ + <cpbotha@ieee.org> & Claudio Matsuoka <claudio@conectiva.com>" +#define DRIVER_DESC "ov511 USB Camera Driver" + +#define OV511_I2C_RETRIES 3 +#define ENABLE_Y_QUANTABLE 1 +#define ENABLE_UV_QUANTABLE 1 + +#define OV511_MAX_UNIT_VIDEO 16 + +/* Pixel count * bytes per YUV420 pixel (1.5) */ +#define MAX_FRAME_SIZE(w, h) ((w) * (h) * 3 / 2) + +#define MAX_DATA_SIZE(w, h) (MAX_FRAME_SIZE(w, h) + sizeof(struct timeval)) + +/* Max size * bytes per YUV420 pixel (1.5) + one extra isoc frame for safety */ +#define MAX_RAW_DATA_SIZE(w, h) ((w) * (h) * 3 / 2 + 1024) + +#define FATAL_ERROR(rc) ((rc) < 0 && (rc) != -EPERM) + +/********************************************************************** + * Module Parameters + * (See ov511.txt for detailed descriptions of these) + **********************************************************************/ + +/* These variables (and all static globals) default to zero */ +static int autobright = 1; +static int autogain = 1; +static int autoexp = 1; +static int debug; +static int snapshot; +static int cams = 1; +static int compress; +static int testpat; +static int dumppix; +static int led = 1; +static int dump_bridge; +static int dump_sensor; +static int printph; +static int phy = 0x1f; +static int phuv = 0x05; +static int pvy = 0x06; +static int pvuv = 0x06; +static int qhy = 0x14; +static int qhuv = 0x03; +static int qvy = 0x04; +static int qvuv = 0x04; +static int lightfreq; +static int bandingfilter; +static int clockdiv = -1; +static int packetsize = -1; +static int framedrop = -1; +static int fastset; +static int force_palette; +static int backlight; +static int unit_video[OV511_MAX_UNIT_VIDEO]; +static int remove_zeros; +static int mirror; +static int ov518_color; + +module_param(autobright, int, 0); +MODULE_PARM_DESC(autobright, "Sensor automatically changes brightness"); +module_param(autogain, int, 0); +MODULE_PARM_DESC(autogain, "Sensor automatically changes gain"); +module_param(autoexp, int, 0); +MODULE_PARM_DESC(autoexp, "Sensor automatically changes exposure"); +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, + "Debug level: 0=none, 1=inits, 2=warning, 3=config, 4=functions, 5=max"); +module_param(snapshot, int, 0); +MODULE_PARM_DESC(snapshot, "Enable snapshot mode"); +module_param(cams, int, 0); +MODULE_PARM_DESC(cams, "Number of simultaneous cameras"); +module_param(compress, int, 0); +MODULE_PARM_DESC(compress, "Turn on compression"); +module_param(testpat, int, 0); +MODULE_PARM_DESC(testpat, + "Replace image with vertical bar testpattern (only partially working)"); +module_param(dumppix, int, 0); +MODULE_PARM_DESC(dumppix, "Dump raw pixel data"); +module_param(led, int, 0); +MODULE_PARM_DESC(led, + "LED policy (OV511+ or later). 0=off, 1=on (default), 2=auto (on when open)"); +module_param(dump_bridge, int, 0); +MODULE_PARM_DESC(dump_bridge, "Dump the bridge registers"); +module_param(dump_sensor, int, 0); +MODULE_PARM_DESC(dump_sensor, "Dump the sensor registers"); +module_param(printph, int, 0); +MODULE_PARM_DESC(printph, "Print frame start/end headers"); +module_param(phy, int, 0); +MODULE_PARM_DESC(phy, "Prediction range (horiz. Y)"); +module_param(phuv, int, 0); +MODULE_PARM_DESC(phuv, "Prediction range (horiz. UV)"); +module_param(pvy, int, 0); +MODULE_PARM_DESC(pvy, "Prediction range (vert. Y)"); +module_param(pvuv, int, 0); +MODULE_PARM_DESC(pvuv, "Prediction range (vert. UV)"); +module_param(qhy, int, 0); +MODULE_PARM_DESC(qhy, "Quantization threshold (horiz. Y)"); +module_param(qhuv, int, 0); +MODULE_PARM_DESC(qhuv, "Quantization threshold (horiz. UV)"); +module_param(qvy, int, 0); +MODULE_PARM_DESC(qvy, "Quantization threshold (vert. Y)"); +module_param(qvuv, int, 0); +MODULE_PARM_DESC(qvuv, "Quantization threshold (vert. UV)"); +module_param(lightfreq, int, 0); +MODULE_PARM_DESC(lightfreq, + "Light frequency. Set to 50 or 60 Hz, or zero for default settings"); +module_param(bandingfilter, int, 0); +MODULE_PARM_DESC(bandingfilter, + "Enable banding filter (to reduce effects of fluorescent lighting)"); +module_param(clockdiv, int, 0); +MODULE_PARM_DESC(clockdiv, "Force pixel clock divisor to a specific value"); +module_param(packetsize, int, 0); +MODULE_PARM_DESC(packetsize, "Force a specific isoc packet size"); +module_param(framedrop, int, 0); +MODULE_PARM_DESC(framedrop, "Force a specific frame drop register setting"); +module_param(fastset, int, 0); +MODULE_PARM_DESC(fastset, "Allows picture settings to take effect immediately"); +module_param(force_palette, int, 0); +MODULE_PARM_DESC(force_palette, "Force the palette to a specific value"); +module_param(backlight, int, 0); +MODULE_PARM_DESC(backlight, "For objects that are lit from behind"); +static int num_uv; +module_param_array(unit_video, int, &num_uv, 0); +MODULE_PARM_DESC(unit_video, + "Force use of specific minor number(s). 0 is not allowed."); +module_param(remove_zeros, int, 0); +MODULE_PARM_DESC(remove_zeros, + "Remove zero-padding from uncompressed incoming data"); +module_param(mirror, int, 0); +MODULE_PARM_DESC(mirror, "Reverse image horizontally"); +module_param(ov518_color, int, 0); +MODULE_PARM_DESC(ov518_color, "Enable OV518 color (experimental)"); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/********************************************************************** + * Miscellaneous Globals + **********************************************************************/ + +static struct usb_driver ov511_driver; + +/* Number of times to retry a failed I2C transaction. Increase this if you + * are getting "Failed to read sensor ID..." */ +static const int i2c_detect_tries = 5; + +static struct usb_device_id device_table [] = { + { USB_DEVICE(VEND_OMNIVISION, PROD_OV511) }, + { USB_DEVICE(VEND_OMNIVISION, PROD_OV511PLUS) }, + { USB_DEVICE(VEND_OMNIVISION, PROD_OV518) }, + { USB_DEVICE(VEND_OMNIVISION, PROD_OV518PLUS) }, + { USB_DEVICE(VEND_MATTEL, PROD_ME2CAM) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, device_table); + +static unsigned char yQuanTable511[] = OV511_YQUANTABLE; +static unsigned char uvQuanTable511[] = OV511_UVQUANTABLE; +static unsigned char yQuanTable518[] = OV518_YQUANTABLE; +static unsigned char uvQuanTable518[] = OV518_UVQUANTABLE; + +/********************************************************************** + * Symbolic Names + **********************************************************************/ + +/* Known OV511-based cameras */ +static struct symbolic_list camlist[] = { + { 0, "Generic Camera (no ID)" }, + { 1, "Mustek WCam 3X" }, + { 3, "D-Link DSB-C300" }, + { 4, "Generic OV511/OV7610" }, + { 5, "Puretek PT-6007" }, + { 6, "Lifeview USB Life TV (NTSC)" }, + { 21, "Creative Labs WebCam 3" }, + { 22, "Lifeview USB Life TV (PAL D/K+B/G)" }, + { 36, "Koala-Cam" }, + { 38, "Lifeview USB Life TV (PAL)" }, + { 41, "Samsung Anycam MPC-M10" }, + { 43, "Mtekvision Zeca MV402" }, + { 46, "Suma eON" }, + { 70, "Lifeview USB Life TV (PAL/SECAM)" }, + { 100, "Lifeview RoboCam" }, + { 102, "AverMedia InterCam Elite" }, + { 112, "MediaForte MV300" }, /* or OV7110 evaluation kit */ + { 134, "Ezonics EZCam II" }, + { 192, "Webeye 2000B" }, + { 253, "Alpha Vision Tech. AlphaCam SE" }, + { -1, NULL } +}; + +/* Video4Linux1 Palettes */ +static struct symbolic_list v4l1_plist[] = { + { VIDEO_PALETTE_GREY, "GREY" }, + { VIDEO_PALETTE_HI240, "HI240" }, + { VIDEO_PALETTE_RGB565, "RGB565" }, + { VIDEO_PALETTE_RGB24, "RGB24" }, + { VIDEO_PALETTE_RGB32, "RGB32" }, + { VIDEO_PALETTE_RGB555, "RGB555" }, + { VIDEO_PALETTE_YUV422, "YUV422" }, + { VIDEO_PALETTE_YUYV, "YUYV" }, + { VIDEO_PALETTE_UYVY, "UYVY" }, + { VIDEO_PALETTE_YUV420, "YUV420" }, + { VIDEO_PALETTE_YUV411, "YUV411" }, + { VIDEO_PALETTE_RAW, "RAW" }, + { VIDEO_PALETTE_YUV422P,"YUV422P" }, + { VIDEO_PALETTE_YUV411P,"YUV411P" }, + { VIDEO_PALETTE_YUV420P,"YUV420P" }, + { VIDEO_PALETTE_YUV410P,"YUV410P" }, + { -1, NULL } +}; + +static struct symbolic_list brglist[] = { + { BRG_OV511, "OV511" }, + { BRG_OV511PLUS, "OV511+" }, + { BRG_OV518, "OV518" }, + { BRG_OV518PLUS, "OV518+" }, + { -1, NULL } +}; + +static struct symbolic_list senlist[] = { + { SEN_OV76BE, "OV76BE" }, + { SEN_OV7610, "OV7610" }, + { SEN_OV7620, "OV7620" }, + { SEN_OV7620AE, "OV7620AE" }, + { SEN_OV6620, "OV6620" }, + { SEN_OV6630, "OV6630" }, + { SEN_OV6630AE, "OV6630AE" }, + { SEN_OV6630AF, "OV6630AF" }, + { SEN_OV8600, "OV8600" }, + { SEN_KS0127, "KS0127" }, + { SEN_KS0127B, "KS0127B" }, + { SEN_SAA7111A, "SAA7111A" }, + { -1, NULL } +}; + +/* URB error codes: */ +static struct symbolic_list urb_errlist[] = { + { -ENOSR, "Buffer error (overrun)" }, + { -EPIPE, "Stalled (device not responding)" }, + { -EOVERFLOW, "Babble (bad cable?)" }, + { -EPROTO, "Bit-stuff error (bad cable?)" }, + { -EILSEQ, "CRC/Timeout" }, + { -ETIMEDOUT, "NAK (device does not respond)" }, + { -1, NULL } +}; + +/********************************************************************** + * Memory management + **********************************************************************/ +static void * +rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + 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) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void +rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + +/********************************************************************** + * + * Register I/O + * + **********************************************************************/ + +/* Write an OV51x register */ +static int +reg_w(struct usb_ov511 *ov, unsigned char reg, unsigned char value) +{ + int rc; + + PDEBUG(5, "0x%02X:0x%02X", reg, value); + + mutex_lock(&ov->cbuf_lock); + ov->cbuf[0] = value; + rc = usb_control_msg(ov->dev, + usb_sndctrlpipe(ov->dev, 0), + (ov->bclass == BCL_OV518)?1:2 /* REG_IO */, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, (__u16)reg, &ov->cbuf[0], 1, 1000); + mutex_unlock(&ov->cbuf_lock); + + if (rc < 0) + err("reg write: error %d: %s", rc, symbolic(urb_errlist, rc)); + + return rc; +} + +/* Read from an OV51x register */ +/* returns: negative is error, pos or zero is data */ +static int +reg_r(struct usb_ov511 *ov, unsigned char reg) +{ + int rc; + + mutex_lock(&ov->cbuf_lock); + rc = usb_control_msg(ov->dev, + usb_rcvctrlpipe(ov->dev, 0), + (ov->bclass == BCL_OV518)?1:3 /* REG_IO */, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, (__u16)reg, &ov->cbuf[0], 1, 1000); + + if (rc < 0) { + err("reg read: error %d: %s", rc, symbolic(urb_errlist, rc)); + } else { + rc = ov->cbuf[0]; + PDEBUG(5, "0x%02X:0x%02X", reg, ov->cbuf[0]); + } + + mutex_unlock(&ov->cbuf_lock); + + return rc; +} + +/* + * Writes bits at positions specified by mask to an OV51x reg. Bits that are in + * the same position as 1's in "mask" are cleared and set to "value". Bits + * that are in the same position as 0's in "mask" are preserved, regardless + * of their respective state in "value". + */ +static int +reg_w_mask(struct usb_ov511 *ov, + unsigned char reg, + unsigned char value, + unsigned char mask) +{ + int ret; + unsigned char oldval, newval; + + ret = reg_r(ov, reg); + if (ret < 0) + return ret; + + oldval = (unsigned char) ret; + oldval &= (~mask); /* Clear the masked bits */ + value &= mask; /* Enforce mask on value */ + newval = oldval | value; /* Set the desired bits */ + + return (reg_w(ov, reg, newval)); +} + +/* + * Writes multiple (n) byte value to a single register. Only valid with certain + * registers (0x30 and 0xc4 - 0xce). + */ +static int +ov518_reg_w32(struct usb_ov511 *ov, unsigned char reg, u32 val, int n) +{ + int rc; + + PDEBUG(5, "0x%02X:%7d, n=%d", reg, val, n); + + mutex_lock(&ov->cbuf_lock); + + *((__le32 *)ov->cbuf) = __cpu_to_le32(val); + + rc = usb_control_msg(ov->dev, + usb_sndctrlpipe(ov->dev, 0), + 1 /* REG_IO */, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, (__u16)reg, ov->cbuf, n, 1000); + mutex_unlock(&ov->cbuf_lock); + + if (rc < 0) + err("reg write multiple: error %d: %s", rc, + symbolic(urb_errlist, rc)); + + return rc; +} + +static int +ov511_upload_quan_tables(struct usb_ov511 *ov) +{ + unsigned char *pYTable = yQuanTable511; + unsigned char *pUVTable = uvQuanTable511; + unsigned char val0, val1; + int i, rc, reg = R511_COMP_LUT_BEGIN; + + PDEBUG(4, "Uploading quantization tables"); + + for (i = 0; i < OV511_QUANTABLESIZE / 2; i++) { + if (ENABLE_Y_QUANTABLE) { + val0 = *pYTable++; + val1 = *pYTable++; + val0 &= 0x0f; + val1 &= 0x0f; + val0 |= val1 << 4; + rc = reg_w(ov, reg, val0); + if (rc < 0) + return rc; + } + + if (ENABLE_UV_QUANTABLE) { + val0 = *pUVTable++; + val1 = *pUVTable++; + val0 &= 0x0f; + val1 &= 0x0f; + val0 |= val1 << 4; + rc = reg_w(ov, reg + OV511_QUANTABLESIZE/2, val0); + if (rc < 0) + return rc; + } + + reg++; + } + + return 0; +} + +/* OV518 quantization tables are 8x4 (instead of 8x8) */ +static int +ov518_upload_quan_tables(struct usb_ov511 *ov) +{ + unsigned char *pYTable = yQuanTable518; + unsigned char *pUVTable = uvQuanTable518; + unsigned char val0, val1; + int i, rc, reg = R511_COMP_LUT_BEGIN; + + PDEBUG(4, "Uploading quantization tables"); + + for (i = 0; i < OV518_QUANTABLESIZE / 2; i++) { + if (ENABLE_Y_QUANTABLE) { + val0 = *pYTable++; + val1 = *pYTable++; + val0 &= 0x0f; + val1 &= 0x0f; + val0 |= val1 << 4; + rc = reg_w(ov, reg, val0); + if (rc < 0) + return rc; + } + + if (ENABLE_UV_QUANTABLE) { + val0 = *pUVTable++; + val1 = *pUVTable++; + val0 &= 0x0f; + val1 &= 0x0f; + val0 |= val1 << 4; + rc = reg_w(ov, reg + OV518_QUANTABLESIZE/2, val0); + if (rc < 0) + return rc; + } + + reg++; + } + + return 0; +} + +static int +ov51x_reset(struct usb_ov511 *ov, unsigned char reset_type) +{ + int rc; + + /* Setting bit 0 not allowed on 518/518Plus */ + if (ov->bclass == BCL_OV518) + reset_type &= 0xfe; + + PDEBUG(4, "Reset: type=0x%02X", reset_type); + + rc = reg_w(ov, R51x_SYS_RESET, reset_type); + rc = reg_w(ov, R51x_SYS_RESET, 0); + + if (rc < 0) + err("reset: command failed"); + + return rc; +} + +/********************************************************************** + * + * Low-level I2C I/O functions + * + **********************************************************************/ + +/* NOTE: Do not call this function directly! + * The OV518 I2C I/O procedure is different, hence, this function. + * This is normally only called from i2c_w(). Note that this function + * always succeeds regardless of whether the sensor is present and working. + */ +static int +ov518_i2c_write_internal(struct usb_ov511 *ov, + unsigned char reg, + unsigned char value) +{ + int rc; + + PDEBUG(5, "0x%02X:0x%02X", reg, value); + + /* Select camera register */ + rc = reg_w(ov, R51x_I2C_SADDR_3, reg); + if (rc < 0) + return rc; + + /* Write "value" to I2C data port of OV511 */ + rc = reg_w(ov, R51x_I2C_DATA, value); + if (rc < 0) + return rc; + + /* Initiate 3-byte write cycle */ + rc = reg_w(ov, R518_I2C_CTL, 0x01); + if (rc < 0) + return rc; + + return 0; +} + +/* NOTE: Do not call this function directly! */ +static int +ov511_i2c_write_internal(struct usb_ov511 *ov, + unsigned char reg, + unsigned char value) +{ + int rc, retries; + + PDEBUG(5, "0x%02X:0x%02X", reg, value); + + /* Three byte write cycle */ + for (retries = OV511_I2C_RETRIES; ; ) { + /* Select camera register */ + rc = reg_w(ov, R51x_I2C_SADDR_3, reg); + if (rc < 0) + break; + + /* Write "value" to I2C data port of OV511 */ + rc = reg_w(ov, R51x_I2C_DATA, value); + if (rc < 0) + break; + + /* Initiate 3-byte write cycle */ + rc = reg_w(ov, R511_I2C_CTL, 0x01); + if (rc < 0) + break; + + /* Retry until idle */ + do + rc = reg_r(ov, R511_I2C_CTL); + while (rc > 0 && ((rc&1) == 0)); + if (rc < 0) + break; + + /* Ack? */ + if ((rc&2) == 0) { + rc = 0; + break; + } +#if 0 + /* I2C abort */ + reg_w(ov, R511_I2C_CTL, 0x10); +#endif + if (--retries < 0) { + err("i2c write retries exhausted"); + rc = -1; + break; + } + } + + return rc; +} + +/* NOTE: Do not call this function directly! + * The OV518 I2C I/O procedure is different, hence, this function. + * This is normally only called from i2c_r(). Note that this function + * always succeeds regardless of whether the sensor is present and working. + */ +static int +ov518_i2c_read_internal(struct usb_ov511 *ov, unsigned char reg) +{ + int rc, value; + + /* Select camera register */ + rc = reg_w(ov, R51x_I2C_SADDR_2, reg); + if (rc < 0) + return rc; + + /* Initiate 2-byte write cycle */ + rc = reg_w(ov, R518_I2C_CTL, 0x03); + if (rc < 0) + return rc; + + /* Initiate 2-byte read cycle */ + rc = reg_w(ov, R518_I2C_CTL, 0x05); + if (rc < 0) + return rc; + + value = reg_r(ov, R51x_I2C_DATA); + + PDEBUG(5, "0x%02X:0x%02X", reg, value); + + return value; +} + +/* NOTE: Do not call this function directly! + * returns: negative is error, pos or zero is data */ +static int +ov511_i2c_read_internal(struct usb_ov511 *ov, unsigned char reg) +{ + int rc, value, retries; + + /* Two byte write cycle */ + for (retries = OV511_I2C_RETRIES; ; ) { + /* Select camera register */ + rc = reg_w(ov, R51x_I2C_SADDR_2, reg); + if (rc < 0) + return rc; + + /* Initiate 2-byte write cycle */ + rc = reg_w(ov, R511_I2C_CTL, 0x03); + if (rc < 0) + return rc; + + /* Retry until idle */ + do + rc = reg_r(ov, R511_I2C_CTL); + while (rc > 0 && ((rc&1) == 0)); + if (rc < 0) + return rc; + + if ((rc&2) == 0) /* Ack? */ + break; + + /* I2C abort */ + reg_w(ov, R511_I2C_CTL, 0x10); + + if (--retries < 0) { + err("i2c write retries exhausted"); + return -1; + } + } + + /* Two byte read cycle */ + for (retries = OV511_I2C_RETRIES; ; ) { + /* Initiate 2-byte read cycle */ + rc = reg_w(ov, R511_I2C_CTL, 0x05); + if (rc < 0) + return rc; + + /* Retry until idle */ + do + rc = reg_r(ov, R511_I2C_CTL); + while (rc > 0 && ((rc&1) == 0)); + if (rc < 0) + return rc; + + if ((rc&2) == 0) /* Ack? */ + break; + + /* I2C abort */ + rc = reg_w(ov, R511_I2C_CTL, 0x10); + if (rc < 0) + return rc; + + if (--retries < 0) { + err("i2c read retries exhausted"); + return -1; + } + } + + value = reg_r(ov, R51x_I2C_DATA); + + PDEBUG(5, "0x%02X:0x%02X", reg, value); + + /* This is needed to make i2c_w() work */ + rc = reg_w(ov, R511_I2C_CTL, 0x05); + if (rc < 0) + return rc; + + return value; +} + +/* returns: negative is error, pos or zero is data */ +static int +i2c_r(struct usb_ov511 *ov, unsigned char reg) +{ + int rc; + + mutex_lock(&ov->i2c_lock); + + if (ov->bclass == BCL_OV518) + rc = ov518_i2c_read_internal(ov, reg); + else + rc = ov511_i2c_read_internal(ov, reg); + + mutex_unlock(&ov->i2c_lock); + + return rc; +} + +static int +i2c_w(struct usb_ov511 *ov, unsigned char reg, unsigned char value) +{ + int rc; + + mutex_lock(&ov->i2c_lock); + + if (ov->bclass == BCL_OV518) + rc = ov518_i2c_write_internal(ov, reg, value); + else + rc = ov511_i2c_write_internal(ov, reg, value); + + mutex_unlock(&ov->i2c_lock); + + return rc; +} + +/* Do not call this function directly! */ +static int +ov51x_i2c_write_mask_internal(struct usb_ov511 *ov, + unsigned char reg, + unsigned char value, + unsigned char mask) +{ + int rc; + unsigned char oldval, newval; + + if (mask == 0xff) { + newval = value; + } else { + if (ov->bclass == BCL_OV518) + rc = ov518_i2c_read_internal(ov, reg); + else + rc = ov511_i2c_read_internal(ov, reg); + if (rc < 0) + return rc; + + oldval = (unsigned char) rc; + oldval &= (~mask); /* Clear the masked bits */ + value &= mask; /* Enforce mask on value */ + newval = oldval | value; /* Set the desired bits */ + } + + if (ov->bclass == BCL_OV518) + return (ov518_i2c_write_internal(ov, reg, newval)); + else + return (ov511_i2c_write_internal(ov, reg, newval)); +} + +/* Writes bits at positions specified by mask to an I2C reg. Bits that are in + * the same position as 1's in "mask" are cleared and set to "value". Bits + * that are in the same position as 0's in "mask" are preserved, regardless + * of their respective state in "value". + */ +static int +i2c_w_mask(struct usb_ov511 *ov, + unsigned char reg, + unsigned char value, + unsigned char mask) +{ + int rc; + + mutex_lock(&ov->i2c_lock); + rc = ov51x_i2c_write_mask_internal(ov, reg, value, mask); + mutex_unlock(&ov->i2c_lock); + + return rc; +} + +/* Set the read and write slave IDs. The "slave" argument is the write slave, + * and the read slave will be set to (slave + 1). ov->i2c_lock should be held + * when calling this. This should not be called from outside the i2c I/O + * functions. + */ +static int +i2c_set_slave_internal(struct usb_ov511 *ov, unsigned char slave) +{ + int rc; + + rc = reg_w(ov, R51x_I2C_W_SID, slave); + if (rc < 0) + return rc; + + rc = reg_w(ov, R51x_I2C_R_SID, slave + 1); + if (rc < 0) + return rc; + + return 0; +} + +/* Write to a specific I2C slave ID and register, using the specified mask */ +static int +i2c_w_slave(struct usb_ov511 *ov, + unsigned char slave, + unsigned char reg, + unsigned char value, + unsigned char mask) +{ + int rc = 0; + + mutex_lock(&ov->i2c_lock); + + /* Set new slave IDs */ + rc = i2c_set_slave_internal(ov, slave); + if (rc < 0) + goto out; + + rc = ov51x_i2c_write_mask_internal(ov, reg, value, mask); + +out: + /* Restore primary IDs */ + if (i2c_set_slave_internal(ov, ov->primary_i2c_slave) < 0) + err("Couldn't restore primary I2C slave"); + + mutex_unlock(&ov->i2c_lock); + return rc; +} + +/* Read from a specific I2C slave ID and register */ +static int +i2c_r_slave(struct usb_ov511 *ov, + unsigned char slave, + unsigned char reg) +{ + int rc; + + mutex_lock(&ov->i2c_lock); + + /* Set new slave IDs */ + rc = i2c_set_slave_internal(ov, slave); + if (rc < 0) + goto out; + + if (ov->bclass == BCL_OV518) + rc = ov518_i2c_read_internal(ov, reg); + else + rc = ov511_i2c_read_internal(ov, reg); + +out: + /* Restore primary IDs */ + if (i2c_set_slave_internal(ov, ov->primary_i2c_slave) < 0) + err("Couldn't restore primary I2C slave"); + + mutex_unlock(&ov->i2c_lock); + return rc; +} + +/* Sets I2C read and write slave IDs. Returns <0 for error */ +static int +ov51x_set_slave_ids(struct usb_ov511 *ov, unsigned char sid) +{ + int rc; + + mutex_lock(&ov->i2c_lock); + + rc = i2c_set_slave_internal(ov, sid); + if (rc < 0) + goto out; + + // FIXME: Is this actually necessary? + rc = ov51x_reset(ov, OV511_RESET_NOREGS); +out: + mutex_unlock(&ov->i2c_lock); + return rc; +} + +static int +write_regvals(struct usb_ov511 *ov, struct ov511_regvals * pRegvals) +{ + int rc; + + while (pRegvals->bus != OV511_DONE_BUS) { + if (pRegvals->bus == OV511_REG_BUS) { + if ((rc = reg_w(ov, pRegvals->reg, pRegvals->val)) < 0) + return rc; + } else if (pRegvals->bus == OV511_I2C_BUS) { + if ((rc = i2c_w(ov, pRegvals->reg, pRegvals->val)) < 0) + return rc; + } else { + err("Bad regval array"); + return -1; + } + pRegvals++; + } + return 0; +} + +#ifdef OV511_DEBUG +static void +dump_i2c_range(struct usb_ov511 *ov, int reg1, int regn) +{ + int i, rc; + + for (i = reg1; i <= regn; i++) { + rc = i2c_r(ov, i); + info("Sensor[0x%02X] = 0x%02X", i, rc); + } +} + +static void +dump_i2c_regs(struct usb_ov511 *ov) +{ + info("I2C REGS"); + dump_i2c_range(ov, 0x00, 0x7C); +} + +static void +dump_reg_range(struct usb_ov511 *ov, int reg1, int regn) +{ + int i, rc; + + for (i = reg1; i <= regn; i++) { + rc = reg_r(ov, i); + info("OV511[0x%02X] = 0x%02X", i, rc); + } +} + +static void +ov511_dump_regs(struct usb_ov511 *ov) +{ + info("CAMERA INTERFACE REGS"); + dump_reg_range(ov, 0x10, 0x1f); + info("DRAM INTERFACE REGS"); + dump_reg_range(ov, 0x20, 0x23); + info("ISO FIFO REGS"); + dump_reg_range(ov, 0x30, 0x31); + info("PIO REGS"); + dump_reg_range(ov, 0x38, 0x39); + dump_reg_range(ov, 0x3e, 0x3e); + info("I2C REGS"); + dump_reg_range(ov, 0x40, 0x49); + info("SYSTEM CONTROL REGS"); + dump_reg_range(ov, 0x50, 0x55); + dump_reg_range(ov, 0x5e, 0x5f); + info("OmniCE REGS"); + dump_reg_range(ov, 0x70, 0x79); + /* NOTE: Quantization tables are not readable. You will get the value + * in reg. 0x79 for every table register */ + dump_reg_range(ov, 0x80, 0x9f); + dump_reg_range(ov, 0xa0, 0xbf); + +} + +static void +ov518_dump_regs(struct usb_ov511 *ov) +{ + info("VIDEO MODE REGS"); + dump_reg_range(ov, 0x20, 0x2f); + info("DATA PUMP AND SNAPSHOT REGS"); + dump_reg_range(ov, 0x30, 0x3f); + info("I2C REGS"); + dump_reg_range(ov, 0x40, 0x4f); + info("SYSTEM CONTROL AND VENDOR REGS"); + dump_reg_range(ov, 0x50, 0x5f); + info("60 - 6F"); + dump_reg_range(ov, 0x60, 0x6f); + info("70 - 7F"); + dump_reg_range(ov, 0x70, 0x7f); + info("Y QUANTIZATION TABLE"); + dump_reg_range(ov, 0x80, 0x8f); + info("UV QUANTIZATION TABLE"); + dump_reg_range(ov, 0x90, 0x9f); + info("A0 - BF"); + dump_reg_range(ov, 0xa0, 0xbf); + info("CBR"); + dump_reg_range(ov, 0xc0, 0xcf); +} +#endif + +/*****************************************************************************/ + +/* Temporarily stops OV511 from functioning. Must do this before changing + * registers while the camera is streaming */ +static inline int +ov51x_stop(struct usb_ov511 *ov) +{ + PDEBUG(4, "stopping"); + ov->stopped = 1; + if (ov->bclass == BCL_OV518) + return (reg_w_mask(ov, R51x_SYS_RESET, 0x3a, 0x3a)); + else + return (reg_w(ov, R51x_SYS_RESET, 0x3d)); +} + +/* Restarts OV511 after ov511_stop() is called. Has no effect if it is not + * actually stopped (for performance). */ +static inline int +ov51x_restart(struct usb_ov511 *ov) +{ + if (ov->stopped) { + PDEBUG(4, "restarting"); + ov->stopped = 0; + + /* Reinitialize the stream */ + if (ov->bclass == BCL_OV518) + reg_w(ov, 0x2f, 0x80); + + return (reg_w(ov, R51x_SYS_RESET, 0x00)); + } + + return 0; +} + +/* Sleeps until no frames are active. Returns !0 if got signal */ +static int +ov51x_wait_frames_inactive(struct usb_ov511 *ov) +{ + return wait_event_interruptible(ov->wq, ov->curframe < 0); +} + +/* Resets the hardware snapshot button */ +static void +ov51x_clear_snapshot(struct usb_ov511 *ov) +{ + if (ov->bclass == BCL_OV511) { + reg_w(ov, R51x_SYS_SNAP, 0x00); + reg_w(ov, R51x_SYS_SNAP, 0x02); + reg_w(ov, R51x_SYS_SNAP, 0x00); + } else if (ov->bclass == BCL_OV518) { + warn("snapshot reset not supported yet on OV518(+)"); + } else { + err("clear snap: invalid bridge type"); + } +} + +#if 0 +/* Checks the status of the snapshot button. Returns 1 if it was pressed since + * it was last cleared, and zero in all other cases (including errors) */ +static int +ov51x_check_snapshot(struct usb_ov511 *ov) +{ + int ret, status = 0; + + if (ov->bclass == BCL_OV511) { + ret = reg_r(ov, R51x_SYS_SNAP); + if (ret < 0) { + err("Error checking snspshot status (%d)", ret); + } else if (ret & 0x08) { + status = 1; + } + } else if (ov->bclass == BCL_OV518) { + warn("snapshot check not supported yet on OV518(+)"); + } else { + err("check snap: invalid bridge type"); + } + + return status; +} +#endif + +/* This does an initial reset of an OmniVision sensor and ensures that I2C + * is synchronized. Returns <0 for failure. + */ +static int +init_ov_sensor(struct usb_ov511 *ov) +{ + int i, success; + + /* Reset the sensor */ + if (i2c_w(ov, 0x12, 0x80) < 0) + return -EIO; + + /* Wait for it to initialize */ + msleep(150); + + for (i = 0, success = 0; i < i2c_detect_tries && !success; i++) { + if ((i2c_r(ov, OV7610_REG_ID_HIGH) == 0x7F) && + (i2c_r(ov, OV7610_REG_ID_LOW) == 0xA2)) { + success = 1; + continue; + } + + /* Reset the sensor */ + if (i2c_w(ov, 0x12, 0x80) < 0) + return -EIO; + /* Wait for it to initialize */ + msleep(150); + /* Dummy read to sync I2C */ + if (i2c_r(ov, 0x00) < 0) + return -EIO; + } + + if (!success) + return -EIO; + + PDEBUG(1, "I2C synced in %d attempt(s)", i); + + return 0; +} + +static int +ov511_set_packet_size(struct usb_ov511 *ov, int size) +{ + int alt, mult; + + if (ov51x_stop(ov) < 0) + return -EIO; + + mult = size >> 5; + + if (ov->bridge == BRG_OV511) { + if (size == 0) + alt = OV511_ALT_SIZE_0; + else if (size == 257) + alt = OV511_ALT_SIZE_257; + else if (size == 513) + alt = OV511_ALT_SIZE_513; + else if (size == 769) + alt = OV511_ALT_SIZE_769; + else if (size == 993) + alt = OV511_ALT_SIZE_993; + else { + err("Set packet size: invalid size (%d)", size); + return -EINVAL; + } + } else if (ov->bridge == BRG_OV511PLUS) { + if (size == 0) + alt = OV511PLUS_ALT_SIZE_0; + else if (size == 33) + alt = OV511PLUS_ALT_SIZE_33; + else if (size == 129) + alt = OV511PLUS_ALT_SIZE_129; + else if (size == 257) + alt = OV511PLUS_ALT_SIZE_257; + else if (size == 385) + alt = OV511PLUS_ALT_SIZE_385; + else if (size == 513) + alt = OV511PLUS_ALT_SIZE_513; + else if (size == 769) + alt = OV511PLUS_ALT_SIZE_769; + else if (size == 961) + alt = OV511PLUS_ALT_SIZE_961; + else { + err("Set packet size: invalid size (%d)", size); + return -EINVAL; + } + } else { + err("Set packet size: Invalid bridge type"); + return -EINVAL; + } + + PDEBUG(3, "%d, mult=%d, alt=%d", size, mult, alt); + + if (reg_w(ov, R51x_FIFO_PSIZE, mult) < 0) + return -EIO; + + if (usb_set_interface(ov->dev, ov->iface, alt) < 0) { + err("Set packet size: set interface error"); + return -EBUSY; + } + + if (ov51x_reset(ov, OV511_RESET_NOREGS) < 0) + return -EIO; + + ov->packet_size = size; + + if (ov51x_restart(ov) < 0) + return -EIO; + + return 0; +} + +/* Note: Unlike the OV511/OV511+, the size argument does NOT include the + * optional packet number byte. The actual size *is* stored in ov->packet_size, + * though. */ +static int +ov518_set_packet_size(struct usb_ov511 *ov, int size) +{ + int alt; + + if (ov51x_stop(ov) < 0) + return -EIO; + + if (ov->bclass == BCL_OV518) { + if (size == 0) + alt = OV518_ALT_SIZE_0; + else if (size == 128) + alt = OV518_ALT_SIZE_128; + else if (size == 256) + alt = OV518_ALT_SIZE_256; + else if (size == 384) + alt = OV518_ALT_SIZE_384; + else if (size == 512) + alt = OV518_ALT_SIZE_512; + else if (size == 640) + alt = OV518_ALT_SIZE_640; + else if (size == 768) + alt = OV518_ALT_SIZE_768; + else if (size == 896) + alt = OV518_ALT_SIZE_896; + else { + err("Set packet size: invalid size (%d)", size); + return -EINVAL; + } + } else { + err("Set packet size: Invalid bridge type"); + return -EINVAL; + } + + PDEBUG(3, "%d, alt=%d", size, alt); + + ov->packet_size = size; + if (size > 0) { + /* Program ISO FIFO size reg (packet number isn't included) */ + ov518_reg_w32(ov, 0x30, size, 2); + + if (ov->packet_numbering) + ++ov->packet_size; + } + + if (usb_set_interface(ov->dev, ov->iface, alt) < 0) { + err("Set packet size: set interface error"); + return -EBUSY; + } + + /* Initialize the stream */ + if (reg_w(ov, 0x2f, 0x80) < 0) + return -EIO; + + if (ov51x_restart(ov) < 0) + return -EIO; + + if (ov51x_reset(ov, OV511_RESET_NOREGS) < 0) + return -EIO; + + return 0; +} + +/* Upload compression params and quantization tables. Returns 0 for success. */ +static int +ov511_init_compression(struct usb_ov511 *ov) +{ + int rc = 0; + + if (!ov->compress_inited) { + reg_w(ov, 0x70, phy); + reg_w(ov, 0x71, phuv); + reg_w(ov, 0x72, pvy); + reg_w(ov, 0x73, pvuv); + reg_w(ov, 0x74, qhy); + reg_w(ov, 0x75, qhuv); + reg_w(ov, 0x76, qvy); + reg_w(ov, 0x77, qvuv); + + if (ov511_upload_quan_tables(ov) < 0) { + err("Error uploading quantization tables"); + rc = -EIO; + goto out; + } + } + + ov->compress_inited = 1; +out: + return rc; +} + +/* Upload compression params and quantization tables. Returns 0 for success. */ +static int +ov518_init_compression(struct usb_ov511 *ov) +{ + int rc = 0; + + if (!ov->compress_inited) { + if (ov518_upload_quan_tables(ov) < 0) { + err("Error uploading quantization tables"); + rc = -EIO; + goto out; + } + } + + ov->compress_inited = 1; +out: + return rc; +} + +/* -------------------------------------------------------------------------- */ + +/* Sets sensor's contrast setting to "val" */ +static int +sensor_set_contrast(struct usb_ov511 *ov, unsigned short val) +{ + int rc; + + PDEBUG(3, "%d", val); + + if (ov->stop_during_set) + if (ov51x_stop(ov) < 0) + return -EIO; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV6620: + { + rc = i2c_w(ov, OV7610_REG_CNT, val >> 8); + if (rc < 0) + goto out; + break; + } + case SEN_OV6630: + { + rc = i2c_w_mask(ov, OV7610_REG_CNT, val >> 12, 0x0f); + if (rc < 0) + goto out; + break; + } + case SEN_OV7620: + { + unsigned char ctab[] = { + 0x01, 0x05, 0x09, 0x11, 0x15, 0x35, 0x37, 0x57, + 0x5b, 0xa5, 0xa7, 0xc7, 0xc9, 0xcf, 0xef, 0xff + }; + + /* Use Y gamma control instead. Bit 0 enables it. */ + rc = i2c_w(ov, 0x64, ctab[val>>12]); + if (rc < 0) + goto out; + break; + } + case SEN_SAA7111A: + { + rc = i2c_w(ov, 0x0b, val >> 9); + if (rc < 0) + goto out; + break; + } + default: + { + PDEBUG(3, "Unsupported with this sensor"); + rc = -EPERM; + goto out; + } + } + + rc = 0; /* Success */ + ov->contrast = val; +out: + if (ov51x_restart(ov) < 0) + return -EIO; + + return rc; +} + +/* Gets sensor's contrast setting */ +static int +sensor_get_contrast(struct usb_ov511 *ov, unsigned short *val) +{ + int rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV6620: + rc = i2c_r(ov, OV7610_REG_CNT); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_OV6630: + rc = i2c_r(ov, OV7610_REG_CNT); + if (rc < 0) + return rc; + else + *val = rc << 12; + break; + case SEN_OV7620: + /* Use Y gamma reg instead. Bit 0 is the enable bit. */ + rc = i2c_r(ov, 0x64); + if (rc < 0) + return rc; + else + *val = (rc & 0xfe) << 8; + break; + case SEN_SAA7111A: + *val = ov->contrast; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + } + + PDEBUG(3, "%d", *val); + ov->contrast = *val; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* Sets sensor's brightness setting to "val" */ +static int +sensor_set_brightness(struct usb_ov511 *ov, unsigned short val) +{ + int rc; + + PDEBUG(4, "%d", val); + + if (ov->stop_during_set) + if (ov51x_stop(ov) < 0) + return -EIO; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV76BE: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_w(ov, OV7610_REG_BRT, val >> 8); + if (rc < 0) + goto out; + break; + case SEN_OV7620: + /* 7620 doesn't like manual changes when in auto mode */ + if (!ov->auto_brt) { + rc = i2c_w(ov, OV7610_REG_BRT, val >> 8); + if (rc < 0) + goto out; + } + break; + case SEN_SAA7111A: + rc = i2c_w(ov, 0x0a, val >> 8); + if (rc < 0) + goto out; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + rc = -EPERM; + goto out; + } + + rc = 0; /* Success */ + ov->brightness = val; +out: + if (ov51x_restart(ov) < 0) + return -EIO; + + return rc; +} + +/* Gets sensor's brightness setting */ +static int +sensor_get_brightness(struct usb_ov511 *ov, unsigned short *val) +{ + int rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV76BE: + case SEN_OV7620: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_r(ov, OV7610_REG_BRT); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_SAA7111A: + *val = ov->brightness; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + } + + PDEBUG(3, "%d", *val); + ov->brightness = *val; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* Sets sensor's saturation (color intensity) setting to "val" */ +static int +sensor_set_saturation(struct usb_ov511 *ov, unsigned short val) +{ + int rc; + + PDEBUG(3, "%d", val); + + if (ov->stop_during_set) + if (ov51x_stop(ov) < 0) + return -EIO; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV76BE: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_w(ov, OV7610_REG_SAT, val >> 8); + if (rc < 0) + goto out; + break; + case SEN_OV7620: +// /* Use UV gamma control instead. Bits 0 & 7 are reserved. */ +// rc = ov_i2c_write(ov->dev, 0x62, (val >> 9) & 0x7e); +// if (rc < 0) +// goto out; + rc = i2c_w(ov, OV7610_REG_SAT, val >> 8); + if (rc < 0) + goto out; + break; + case SEN_SAA7111A: + rc = i2c_w(ov, 0x0c, val >> 9); + if (rc < 0) + goto out; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + rc = -EPERM; + goto out; + } + + rc = 0; /* Success */ + ov->colour = val; +out: + if (ov51x_restart(ov) < 0) + return -EIO; + + return rc; +} + +/* Gets sensor's saturation (color intensity) setting */ +static int +sensor_get_saturation(struct usb_ov511 *ov, unsigned short *val) +{ + int rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV76BE: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_r(ov, OV7610_REG_SAT); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_OV7620: +// /* Use UV gamma reg instead. Bits 0 & 7 are reserved. */ +// rc = i2c_r(ov, 0x62); +// if (rc < 0) +// return rc; +// else +// *val = (rc & 0x7e) << 9; + rc = i2c_r(ov, OV7610_REG_SAT); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_SAA7111A: + *val = ov->colour; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + } + + PDEBUG(3, "%d", *val); + ov->colour = *val; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +/* Sets sensor's hue (red/blue balance) setting to "val" */ +static int +sensor_set_hue(struct usb_ov511 *ov, unsigned short val) +{ + int rc; + + PDEBUG(3, "%d", val); + + if (ov->stop_during_set) + if (ov51x_stop(ov) < 0) + return -EIO; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_w(ov, OV7610_REG_RED, 0xFF - (val >> 8)); + if (rc < 0) + goto out; + + rc = i2c_w(ov, OV7610_REG_BLUE, val >> 8); + if (rc < 0) + goto out; + break; + case SEN_OV7620: +// Hue control is causing problems. I will enable it once it's fixed. +#if 0 + rc = i2c_w(ov, 0x7a, (unsigned char)(val >> 8) + 0xb); + if (rc < 0) + goto out; + + rc = i2c_w(ov, 0x79, (unsigned char)(val >> 8) + 0xb); + if (rc < 0) + goto out; +#endif + break; + case SEN_SAA7111A: + rc = i2c_w(ov, 0x0d, (val + 32768) >> 8); + if (rc < 0) + goto out; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + rc = -EPERM; + goto out; + } + + rc = 0; /* Success */ + ov->hue = val; +out: + if (ov51x_restart(ov) < 0) + return -EIO; + + return rc; +} + +/* Gets sensor's hue (red/blue balance) setting */ +static int +sensor_get_hue(struct usb_ov511 *ov, unsigned short *val) +{ + int rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV6620: + case SEN_OV6630: + rc = i2c_r(ov, OV7610_REG_BLUE); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_OV7620: + rc = i2c_r(ov, 0x7a); + if (rc < 0) + return rc; + else + *val = rc << 8; + break; + case SEN_SAA7111A: + *val = ov->hue; + break; + default: + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + } + + PDEBUG(3, "%d", *val); + ov->hue = *val; + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static int +sensor_set_picture(struct usb_ov511 *ov, struct video_picture *p) +{ + int rc; + + PDEBUG(4, "sensor_set_picture"); + + ov->whiteness = p->whiteness; + + /* Don't return error if a setting is unsupported, or rest of settings + * will not be performed */ + + rc = sensor_set_contrast(ov, p->contrast); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_brightness(ov, p->brightness); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_saturation(ov, p->colour); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_hue(ov, p->hue); + if (FATAL_ERROR(rc)) + return rc; + + return 0; +} + +static int +sensor_get_picture(struct usb_ov511 *ov, struct video_picture *p) +{ + int rc; + + PDEBUG(4, "sensor_get_picture"); + + /* Don't return error if a setting is unsupported, or rest of settings + * will not be performed */ + + rc = sensor_get_contrast(ov, &(p->contrast)); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_get_brightness(ov, &(p->brightness)); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_get_saturation(ov, &(p->colour)); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_get_hue(ov, &(p->hue)); + if (FATAL_ERROR(rc)) + return rc; + + p->whiteness = 105 << 8; + + return 0; +} + +#if 0 +// FIXME: Exposure range is only 0x00-0x7f in interlace mode +/* Sets current exposure for sensor. This only has an effect if auto-exposure + * is off */ +static inline int +sensor_set_exposure(struct usb_ov511 *ov, unsigned char val) +{ + int rc; + + PDEBUG(3, "%d", val); + + if (ov->stop_during_set) + if (ov51x_stop(ov) < 0) + return -EIO; + + switch (ov->sensor) { + case SEN_OV6620: + case SEN_OV6630: + case SEN_OV7610: + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + rc = i2c_w(ov, 0x10, val); + if (rc < 0) + goto out; + + break; + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for set_exposure"); + return -EINVAL; + } + + rc = 0; /* Success */ + ov->exposure = val; +out: + if (ov51x_restart(ov) < 0) + return -EIO; + + return rc; +} +#endif + +/* Gets current exposure level from sensor, regardless of whether it is under + * manual control. */ +static int +sensor_get_exposure(struct usb_ov511 *ov, unsigned char *val) +{ + int rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV6620: + case SEN_OV6630: + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + rc = i2c_r(ov, 0x10); + if (rc < 0) + return rc; + else + *val = rc; + break; + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + val = NULL; + PDEBUG(3, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for get_exposure"); + return -EINVAL; + } + + PDEBUG(3, "%d", *val); + ov->exposure = *val; + + return 0; +} + +/* Turns on or off the LED. Only has an effect with OV511+/OV518(+) */ +static void +ov51x_led_control(struct usb_ov511 *ov, int enable) +{ + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + if (ov->bridge == BRG_OV511PLUS) + reg_w(ov, R511_SYS_LED_CTL, enable ? 1 : 0); + else if (ov->bclass == BCL_OV518) + reg_w_mask(ov, R518_GPIO_OUT, enable ? 0x02 : 0x00, 0x02); + + return; +} + +/* Matches the sensor's internal frame rate to the lighting frequency. + * Valid frequencies are: + * 50 - 50Hz, for European and Asian lighting + * 60 - 60Hz, for American lighting + * + * Tested with: OV7610, OV7620, OV76BE, OV6620 + * Unsupported: KS0127, KS0127B, SAA7111A + * Returns: 0 for success + */ +static int +sensor_set_light_freq(struct usb_ov511 *ov, int freq) +{ + int sixty; + + PDEBUG(4, "%d Hz", freq); + + if (freq == 60) + sixty = 1; + else if (freq == 50) + sixty = 0; + else { + err("Invalid light freq (%d Hz)", freq); + return -EINVAL; + } + + switch (ov->sensor) { + case SEN_OV7610: + i2c_w_mask(ov, 0x2a, sixty?0x00:0x80, 0x80); + i2c_w(ov, 0x2b, sixty?0x00:0xac); + i2c_w_mask(ov, 0x13, 0x10, 0x10); + i2c_w_mask(ov, 0x13, 0x00, 0x10); + break; + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + i2c_w_mask(ov, 0x2a, sixty?0x00:0x80, 0x80); + i2c_w(ov, 0x2b, sixty?0x00:0xac); + i2c_w_mask(ov, 0x76, 0x01, 0x01); + break; + case SEN_OV6620: + case SEN_OV6630: + i2c_w(ov, 0x2b, sixty?0xa8:0x28); + i2c_w(ov, 0x2a, sixty?0x84:0xa4); + break; + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for set_light_freq"); + return -EINVAL; + } + + ov->lightfreq = freq; + + return 0; +} + +/* If enable is true, turn on the sensor's banding filter, otherwise turn it + * off. This filter tries to reduce the pattern of horizontal light/dark bands + * caused by some (usually fluorescent) lighting. The light frequency must be + * set either before or after enabling it with ov51x_set_light_freq(). + * + * Tested with: OV7610, OV7620, OV76BE, OV6620. + * Unsupported: KS0127, KS0127B, SAA7111A + * Returns: 0 for success + */ +static int +sensor_set_banding_filter(struct usb_ov511 *ov, int enable) +{ + int rc; + + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + if (ov->sensor == SEN_KS0127 || ov->sensor == SEN_KS0127B + || ov->sensor == SEN_SAA7111A) { + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + } + + rc = i2c_w_mask(ov, 0x2d, enable?0x04:0x00, 0x04); + if (rc < 0) + return rc; + + ov->bandfilt = enable; + + return 0; +} + +/* If enable is true, turn on the sensor's auto brightness control, otherwise + * turn it off. + * + * Unsupported: KS0127, KS0127B, SAA7111A + * Returns: 0 for success + */ +static int +sensor_set_auto_brightness(struct usb_ov511 *ov, int enable) +{ + int rc; + + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + if (ov->sensor == SEN_KS0127 || ov->sensor == SEN_KS0127B + || ov->sensor == SEN_SAA7111A) { + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + } + + rc = i2c_w_mask(ov, 0x2d, enable?0x10:0x00, 0x10); + if (rc < 0) + return rc; + + ov->auto_brt = enable; + + return 0; +} + +/* If enable is true, turn on the sensor's auto exposure control, otherwise + * turn it off. + * + * Unsupported: KS0127, KS0127B, SAA7111A + * Returns: 0 for success + */ +static int +sensor_set_auto_exposure(struct usb_ov511 *ov, int enable) +{ + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + switch (ov->sensor) { + case SEN_OV7610: + i2c_w_mask(ov, 0x29, enable?0x00:0x80, 0x80); + break; + case SEN_OV6620: + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + i2c_w_mask(ov, 0x13, enable?0x01:0x00, 0x01); + break; + case SEN_OV6630: + i2c_w_mask(ov, 0x28, enable?0x00:0x10, 0x10); + break; + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for set_auto_exposure"); + return -EINVAL; + } + + ov->auto_exp = enable; + + return 0; +} + +/* Modifies the sensor's exposure algorithm to allow proper exposure of objects + * that are illuminated from behind. + * + * Tested with: OV6620, OV7620 + * Unsupported: OV7610, OV76BE, KS0127, KS0127B, SAA7111A + * Returns: 0 for success + */ +static int +sensor_set_backlight(struct usb_ov511 *ov, int enable) +{ + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + switch (ov->sensor) { + case SEN_OV7620: + case SEN_OV8600: + i2c_w_mask(ov, 0x68, enable?0xe0:0xc0, 0xe0); + i2c_w_mask(ov, 0x29, enable?0x08:0x00, 0x08); + i2c_w_mask(ov, 0x28, enable?0x02:0x00, 0x02); + break; + case SEN_OV6620: + i2c_w_mask(ov, 0x4e, enable?0xe0:0xc0, 0xe0); + i2c_w_mask(ov, 0x29, enable?0x08:0x00, 0x08); + i2c_w_mask(ov, 0x0e, enable?0x80:0x00, 0x80); + break; + case SEN_OV6630: + i2c_w_mask(ov, 0x4e, enable?0x80:0x60, 0xe0); + i2c_w_mask(ov, 0x29, enable?0x08:0x00, 0x08); + i2c_w_mask(ov, 0x28, enable?0x02:0x00, 0x02); + break; + case SEN_OV7610: + case SEN_OV76BE: + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for set_backlight"); + return -EINVAL; + } + + ov->backlight = enable; + + return 0; +} + +static int +sensor_set_mirror(struct usb_ov511 *ov, int enable) +{ + PDEBUG(4, " (%s)", enable ? "turn on" : "turn off"); + + switch (ov->sensor) { + case SEN_OV6620: + case SEN_OV6630: + case SEN_OV7610: + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + i2c_w_mask(ov, 0x12, enable?0x40:0x00, 0x40); + break; + case SEN_KS0127: + case SEN_KS0127B: + case SEN_SAA7111A: + PDEBUG(5, "Unsupported with this sensor"); + return -EPERM; + default: + err("Sensor not supported for set_mirror"); + return -EINVAL; + } + + ov->mirror = enable; + + return 0; +} + +/* Returns number of bits per pixel (regardless of where they are located; + * planar or not), or zero for unsupported format. + */ +static inline int +get_depth(int palette) +{ + switch (palette) { + case VIDEO_PALETTE_GREY: return 8; + case VIDEO_PALETTE_YUV420: return 12; + case VIDEO_PALETTE_YUV420P: return 12; /* Planar */ + default: return 0; /* Invalid format */ + } +} + +/* Bytes per frame. Used by read(). Return of 0 indicates error */ +static inline long int +get_frame_length(struct ov511_frame *frame) +{ + if (!frame) + return 0; + else + return ((frame->width * frame->height + * get_depth(frame->format)) >> 3); +} + +static int +mode_init_ov_sensor_regs(struct usb_ov511 *ov, int width, int height, + int mode, int sub_flag, int qvga) +{ + int clock; + + /******** Mode (VGA/QVGA) and sensor specific regs ********/ + + switch (ov->sensor) { + case SEN_OV7610: + i2c_w(ov, 0x14, qvga?0x24:0x04); +// FIXME: Does this improve the image quality or frame rate? +#if 0 + i2c_w_mask(ov, 0x28, qvga?0x00:0x20, 0x20); + i2c_w(ov, 0x24, 0x10); + i2c_w(ov, 0x25, qvga?0x40:0x8a); + i2c_w(ov, 0x2f, qvga?0x30:0xb0); + i2c_w(ov, 0x35, qvga?0x1c:0x9c); +#endif + break; + case SEN_OV7620: +// i2c_w(ov, 0x2b, 0x00); + i2c_w(ov, 0x14, qvga?0xa4:0x84); + i2c_w_mask(ov, 0x28, qvga?0x00:0x20, 0x20); + i2c_w(ov, 0x24, qvga?0x20:0x3a); + i2c_w(ov, 0x25, qvga?0x30:0x60); + i2c_w_mask(ov, 0x2d, qvga?0x40:0x00, 0x40); + i2c_w_mask(ov, 0x67, qvga?0xf0:0x90, 0xf0); + i2c_w_mask(ov, 0x74, qvga?0x20:0x00, 0x20); + break; + case SEN_OV76BE: +// i2c_w(ov, 0x2b, 0x00); + i2c_w(ov, 0x14, qvga?0xa4:0x84); +// FIXME: Enable this once 7620AE uses 7620 initial settings +#if 0 + i2c_w_mask(ov, 0x28, qvga?0x00:0x20, 0x20); + i2c_w(ov, 0x24, qvga?0x20:0x3a); + i2c_w(ov, 0x25, qvga?0x30:0x60); + i2c_w_mask(ov, 0x2d, qvga?0x40:0x00, 0x40); + i2c_w_mask(ov, 0x67, qvga?0xb0:0x90, 0xf0); + i2c_w_mask(ov, 0x74, qvga?0x20:0x00, 0x20); +#endif + break; + case SEN_OV6620: + i2c_w(ov, 0x14, qvga?0x24:0x04); + break; + case SEN_OV6630: + i2c_w(ov, 0x14, qvga?0xa0:0x80); + break; + default: + err("Invalid sensor"); + return -EINVAL; + } + + /******** Palette-specific regs ********/ + + if (mode == VIDEO_PALETTE_GREY) { + if (ov->sensor == SEN_OV7610 || ov->sensor == SEN_OV76BE) { + /* these aren't valid on the OV6620/OV7620/6630? */ + i2c_w_mask(ov, 0x0e, 0x40, 0x40); + } + + if (ov->sensor == SEN_OV6630 && ov->bridge == BRG_OV518 + && ov518_color) { + i2c_w_mask(ov, 0x12, 0x00, 0x10); + i2c_w_mask(ov, 0x13, 0x00, 0x20); + } else { + i2c_w_mask(ov, 0x13, 0x20, 0x20); + } + } else { + if (ov->sensor == SEN_OV7610 || ov->sensor == SEN_OV76BE) { + /* not valid on the OV6620/OV7620/6630? */ + i2c_w_mask(ov, 0x0e, 0x00, 0x40); + } + + /* The OV518 needs special treatment. Although both the OV518 + * and the OV6630 support a 16-bit video bus, only the 8 bit Y + * bus is actually used. The UV bus is tied to ground. + * Therefore, the OV6630 needs to be in 8-bit multiplexed + * output mode */ + + if (ov->sensor == SEN_OV6630 && ov->bridge == BRG_OV518 + && ov518_color) { + i2c_w_mask(ov, 0x12, 0x10, 0x10); + i2c_w_mask(ov, 0x13, 0x20, 0x20); + } else { + i2c_w_mask(ov, 0x13, 0x00, 0x20); + } + } + + /******** Clock programming ********/ + + /* The OV6620 needs special handling. This prevents the + * severe banding that normally occurs */ + if (ov->sensor == SEN_OV6620 || ov->sensor == SEN_OV6630) + { + /* Clock down */ + + i2c_w(ov, 0x2a, 0x04); + + if (ov->compress) { +// clock = 0; /* This ensures the highest frame rate */ + clock = 3; + } else if (clockdiv == -1) { /* If user didn't override it */ + clock = 3; /* Gives better exposure time */ + } else { + clock = clockdiv; + } + + PDEBUG(4, "Setting clock divisor to %d", clock); + + i2c_w(ov, 0x11, clock); + + i2c_w(ov, 0x2a, 0x84); + /* This next setting is critical. It seems to improve + * the gain or the contrast. The "reserved" bits seem + * to have some effect in this case. */ + i2c_w(ov, 0x2d, 0x85); + } + else + { + if (ov->compress) { + clock = 1; /* This ensures the highest frame rate */ + } else if (clockdiv == -1) { /* If user didn't override it */ + /* Calculate and set the clock divisor */ + clock = ((sub_flag ? ov->subw * ov->subh + : width * height) + * (mode == VIDEO_PALETTE_GREY ? 2 : 3) / 2) + / 66000; + } else { + clock = clockdiv; + } + + PDEBUG(4, "Setting clock divisor to %d", clock); + + i2c_w(ov, 0x11, clock); + } + + /******** Special Features ********/ + + if (framedrop >= 0) + i2c_w(ov, 0x16, framedrop); + + /* Test Pattern */ + i2c_w_mask(ov, 0x12, (testpat?0x02:0x00), 0x02); + + /* Enable auto white balance */ + i2c_w_mask(ov, 0x12, 0x04, 0x04); + + // This will go away as soon as ov51x_mode_init_sensor_regs() + // is fully tested. + /* 7620/6620/6630? don't have register 0x35, so play it safe */ + if (ov->sensor == SEN_OV7610 || ov->sensor == SEN_OV76BE) { + if (width == 640 && height == 480) + i2c_w(ov, 0x35, 0x9e); + else + i2c_w(ov, 0x35, 0x1e); + } + + return 0; +} + +static int +set_ov_sensor_window(struct usb_ov511 *ov, int width, int height, int mode, + int sub_flag) +{ + int ret; + int hwsbase, hwebase, vwsbase, vwebase, hwsize, vwsize; + int hoffset, voffset, hwscale = 0, vwscale = 0; + + /* The different sensor ICs handle setting up of window differently. + * IF YOU SET IT WRONG, YOU WILL GET ALL ZERO ISOC DATA FROM OV51x!!! */ + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV76BE: + hwsbase = 0x38; + hwebase = 0x3a; + vwsbase = vwebase = 0x05; + break; + case SEN_OV6620: + case SEN_OV6630: + hwsbase = 0x38; + hwebase = 0x3a; + vwsbase = 0x05; + vwebase = 0x06; + break; + case SEN_OV7620: + hwsbase = 0x2f; /* From 7620.SET (spec is wrong) */ + hwebase = 0x2f; + vwsbase = vwebase = 0x05; + break; + default: + err("Invalid sensor"); + return -EINVAL; + } + + if (ov->sensor == SEN_OV6620 || ov->sensor == SEN_OV6630) { + /* Note: OV518(+) does downsample on its own) */ + if ((width > 176 && height > 144) + || ov->bclass == BCL_OV518) { /* CIF */ + ret = mode_init_ov_sensor_regs(ov, width, height, + mode, sub_flag, 0); + if (ret < 0) + return ret; + hwscale = 1; + vwscale = 1; /* The datasheet says 0; it's wrong */ + hwsize = 352; + vwsize = 288; + } else if (width > 176 || height > 144) { + err("Illegal dimensions"); + return -EINVAL; + } else { /* QCIF */ + ret = mode_init_ov_sensor_regs(ov, width, height, + mode, sub_flag, 1); + if (ret < 0) + return ret; + hwsize = 176; + vwsize = 144; + } + } else { + if (width > 320 && height > 240) { /* VGA */ + ret = mode_init_ov_sensor_regs(ov, width, height, + mode, sub_flag, 0); + if (ret < 0) + return ret; + hwscale = 2; + vwscale = 1; + hwsize = 640; + vwsize = 480; + } else if (width > 320 || height > 240) { + err("Illegal dimensions"); + return -EINVAL; + } else { /* QVGA */ + ret = mode_init_ov_sensor_regs(ov, width, height, + mode, sub_flag, 1); + if (ret < 0) + return ret; + hwscale = 1; + hwsize = 320; + vwsize = 240; + } + } + + /* Center the window */ + hoffset = ((hwsize - width) / 2) >> hwscale; + voffset = ((vwsize - height) / 2) >> vwscale; + + /* FIXME! - This needs to be changed to support 160x120 and 6620!!! */ + if (sub_flag) { + i2c_w(ov, 0x17, hwsbase+(ov->subx>>hwscale)); + i2c_w(ov, 0x18, hwebase+((ov->subx+ov->subw)>>hwscale)); + i2c_w(ov, 0x19, vwsbase+(ov->suby>>vwscale)); + i2c_w(ov, 0x1a, vwebase+((ov->suby+ov->subh)>>vwscale)); + } else { + i2c_w(ov, 0x17, hwsbase + hoffset); + i2c_w(ov, 0x18, hwebase + hoffset + (hwsize>>hwscale)); + i2c_w(ov, 0x19, vwsbase + voffset); + i2c_w(ov, 0x1a, vwebase + voffset + (vwsize>>vwscale)); + } + +#ifdef OV511_DEBUG + if (dump_sensor) + dump_i2c_regs(ov); +#endif + + return 0; +} + +/* Set up the OV511/OV511+ with the given image parameters. + * + * Do not put any sensor-specific code in here (including I2C I/O functions) + */ +static int +ov511_mode_init_regs(struct usb_ov511 *ov, + int width, int height, int mode, int sub_flag) +{ + int hsegs, vsegs; + + if (sub_flag) { + width = ov->subw; + height = ov->subh; + } + + PDEBUG(3, "width:%d, height:%d, mode:%d, sub:%d", + width, height, mode, sub_flag); + + // FIXME: This should be moved to a 7111a-specific function once + // subcapture is dealt with properly + if (ov->sensor == SEN_SAA7111A) { + if (width == 320 && height == 240) { + /* No need to do anything special */ + } else if (width == 640 && height == 480) { + /* Set the OV511 up as 320x480, but keep the + * V4L resolution as 640x480 */ + width = 320; + } else { + err("SAA7111A only allows 320x240 or 640x480"); + return -EINVAL; + } + } + + /* Make sure width and height are a multiple of 8 */ + if (width % 8 || height % 8) { + err("Invalid size (%d, %d) (mode = %d)", width, height, mode); + return -EINVAL; + } + + if (width < ov->minwidth || height < ov->minheight) { + err("Requested dimensions are too small"); + return -EINVAL; + } + + if (ov51x_stop(ov) < 0) + return -EIO; + + if (mode == VIDEO_PALETTE_GREY) { + reg_w(ov, R511_CAM_UV_EN, 0x00); + reg_w(ov, R511_SNAP_UV_EN, 0x00); + reg_w(ov, R511_SNAP_OPTS, 0x01); + } else { + reg_w(ov, R511_CAM_UV_EN, 0x01); + reg_w(ov, R511_SNAP_UV_EN, 0x01); + reg_w(ov, R511_SNAP_OPTS, 0x03); + } + + /* Here I'm assuming that snapshot size == image size. + * I hope that's always true. --claudio + */ + hsegs = (width >> 3) - 1; + vsegs = (height >> 3) - 1; + + reg_w(ov, R511_CAM_PXCNT, hsegs); + reg_w(ov, R511_CAM_LNCNT, vsegs); + reg_w(ov, R511_CAM_PXDIV, 0x00); + reg_w(ov, R511_CAM_LNDIV, 0x00); + + /* YUV420, low pass filter on */ + reg_w(ov, R511_CAM_OPTS, 0x03); + + /* Snapshot additions */ + reg_w(ov, R511_SNAP_PXCNT, hsegs); + reg_w(ov, R511_SNAP_LNCNT, vsegs); + reg_w(ov, R511_SNAP_PXDIV, 0x00); + reg_w(ov, R511_SNAP_LNDIV, 0x00); + + if (ov->compress) { + /* Enable Y and UV quantization and compression */ + reg_w(ov, R511_COMP_EN, 0x07); + reg_w(ov, R511_COMP_LUT_EN, 0x03); + ov51x_reset(ov, OV511_RESET_OMNICE); + } + + if (ov51x_restart(ov) < 0) + return -EIO; + + return 0; +} + +/* Sets up the OV518/OV518+ with the given image parameters + * + * OV518 needs a completely different approach, until we can figure out what + * the individual registers do. Also, only 15 FPS is supported now. + * + * Do not put any sensor-specific code in here (including I2C I/O functions) + */ +static int +ov518_mode_init_regs(struct usb_ov511 *ov, + int width, int height, int mode, int sub_flag) +{ + int hsegs, vsegs, hi_res; + + if (sub_flag) { + width = ov->subw; + height = ov->subh; + } + + PDEBUG(3, "width:%d, height:%d, mode:%d, sub:%d", + width, height, mode, sub_flag); + + if (width % 16 || height % 8) { + err("Invalid size (%d, %d)", width, height); + return -EINVAL; + } + + if (width < ov->minwidth || height < ov->minheight) { + err("Requested dimensions are too small"); + return -EINVAL; + } + + if (width >= 320 && height >= 240) { + hi_res = 1; + } else if (width >= 320 || height >= 240) { + err("Invalid width/height combination (%d, %d)", width, height); + return -EINVAL; + } else { + hi_res = 0; + } + + if (ov51x_stop(ov) < 0) + return -EIO; + + /******** Set the mode ********/ + + reg_w(ov, 0x2b, 0); + reg_w(ov, 0x2c, 0); + reg_w(ov, 0x2d, 0); + reg_w(ov, 0x2e, 0); + reg_w(ov, 0x3b, 0); + reg_w(ov, 0x3c, 0); + reg_w(ov, 0x3d, 0); + reg_w(ov, 0x3e, 0); + + if (ov->bridge == BRG_OV518 && ov518_color) { + /* OV518 needs U and V swapped */ + i2c_w_mask(ov, 0x15, 0x00, 0x01); + + if (mode == VIDEO_PALETTE_GREY) { + /* Set 16-bit input format (UV data are ignored) */ + reg_w_mask(ov, 0x20, 0x00, 0x08); + + /* Set 8-bit (4:0:0) output format */ + reg_w_mask(ov, 0x28, 0x00, 0xf0); + reg_w_mask(ov, 0x38, 0x00, 0xf0); + } else { + /* Set 8-bit (YVYU) input format */ + reg_w_mask(ov, 0x20, 0x08, 0x08); + + /* Set 12-bit (4:2:0) output format */ + reg_w_mask(ov, 0x28, 0x80, 0xf0); + reg_w_mask(ov, 0x38, 0x80, 0xf0); + } + } else { + reg_w(ov, 0x28, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80); + reg_w(ov, 0x38, (mode == VIDEO_PALETTE_GREY) ? 0x00:0x80); + } + + hsegs = width / 16; + vsegs = height / 4; + + reg_w(ov, 0x29, hsegs); + reg_w(ov, 0x2a, vsegs); + + reg_w(ov, 0x39, hsegs); + reg_w(ov, 0x3a, vsegs); + + /* Windows driver does this here; who knows why */ + reg_w(ov, 0x2f, 0x80); + + /******** Set the framerate (to 15 FPS) ********/ + + /* Mode independent, but framerate dependent, regs */ + reg_w(ov, 0x51, 0x02); /* Clock divider; lower==faster */ + reg_w(ov, 0x22, 0x18); + reg_w(ov, 0x23, 0xff); + + if (ov->bridge == BRG_OV518PLUS) + reg_w(ov, 0x21, 0x19); + else + reg_w(ov, 0x71, 0x19); /* Compression-related? */ + + // FIXME: Sensor-specific + /* Bit 5 is what matters here. Of course, it is "reserved" */ + i2c_w(ov, 0x54, 0x23); + + reg_w(ov, 0x2f, 0x80); + + if (ov->bridge == BRG_OV518PLUS) { + reg_w(ov, 0x24, 0x94); + reg_w(ov, 0x25, 0x90); + ov518_reg_w32(ov, 0xc4, 400, 2); /* 190h */ + ov518_reg_w32(ov, 0xc6, 540, 2); /* 21ch */ + ov518_reg_w32(ov, 0xc7, 540, 2); /* 21ch */ + ov518_reg_w32(ov, 0xc8, 108, 2); /* 6ch */ + ov518_reg_w32(ov, 0xca, 131098, 3); /* 2001ah */ + ov518_reg_w32(ov, 0xcb, 532, 2); /* 214h */ + ov518_reg_w32(ov, 0xcc, 2400, 2); /* 960h */ + ov518_reg_w32(ov, 0xcd, 32, 2); /* 20h */ + ov518_reg_w32(ov, 0xce, 608, 2); /* 260h */ + } else { + reg_w(ov, 0x24, 0x9f); + reg_w(ov, 0x25, 0x90); + ov518_reg_w32(ov, 0xc4, 400, 2); /* 190h */ + ov518_reg_w32(ov, 0xc6, 500, 2); /* 1f4h */ + ov518_reg_w32(ov, 0xc7, 500, 2); /* 1f4h */ + ov518_reg_w32(ov, 0xc8, 142, 2); /* 8eh */ + ov518_reg_w32(ov, 0xca, 131098, 3); /* 2001ah */ + ov518_reg_w32(ov, 0xcb, 532, 2); /* 214h */ + ov518_reg_w32(ov, 0xcc, 2000, 2); /* 7d0h */ + ov518_reg_w32(ov, 0xcd, 32, 2); /* 20h */ + ov518_reg_w32(ov, 0xce, 608, 2); /* 260h */ + } + + reg_w(ov, 0x2f, 0x80); + + if (ov51x_restart(ov) < 0) + return -EIO; + + /* Reset it just for good measure */ + if (ov51x_reset(ov, OV511_RESET_NOREGS) < 0) + return -EIO; + + return 0; +} + +/* This is a wrapper around the OV511, OV518, and sensor specific functions */ +static int +mode_init_regs(struct usb_ov511 *ov, + int width, int height, int mode, int sub_flag) +{ + int rc = 0; + + if (!ov || !ov->dev) + return -EFAULT; + + if (ov->bclass == BCL_OV518) { + rc = ov518_mode_init_regs(ov, width, height, mode, sub_flag); + } else { + rc = ov511_mode_init_regs(ov, width, height, mode, sub_flag); + } + + if (FATAL_ERROR(rc)) + return rc; + + switch (ov->sensor) { + case SEN_OV7610: + case SEN_OV7620: + case SEN_OV76BE: + case SEN_OV8600: + case SEN_OV6620: + case SEN_OV6630: + rc = set_ov_sensor_window(ov, width, height, mode, sub_flag); + break; + case SEN_KS0127: + case SEN_KS0127B: + err("KS0127-series decoders not supported yet"); + rc = -EINVAL; + break; + case SEN_SAA7111A: +// rc = mode_init_saa_sensor_regs(ov, width, height, mode, +// sub_flag); + + PDEBUG(1, "SAA status = 0x%02X", i2c_r(ov, 0x1f)); + break; + default: + err("Unknown sensor"); + rc = -EINVAL; + } + + if (FATAL_ERROR(rc)) + return rc; + + /* Sensor-independent settings */ + rc = sensor_set_auto_brightness(ov, ov->auto_brt); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_auto_exposure(ov, ov->auto_exp); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_banding_filter(ov, bandingfilter); + if (FATAL_ERROR(rc)) + return rc; + + if (ov->lightfreq) { + rc = sensor_set_light_freq(ov, lightfreq); + if (FATAL_ERROR(rc)) + return rc; + } + + rc = sensor_set_backlight(ov, ov->backlight); + if (FATAL_ERROR(rc)) + return rc; + + rc = sensor_set_mirror(ov, ov->mirror); + if (FATAL_ERROR(rc)) + return rc; + + return 0; +} + +/* This sets the default image parameters. This is useful for apps that use + * read() and do not set these. + */ +static int +ov51x_set_default_params(struct usb_ov511 *ov) +{ + int i; + + /* Set default sizes in case IOCTL (VIDIOCMCAPTURE) is not used + * (using read() instead). */ + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].width = ov->maxwidth; + ov->frame[i].height = ov->maxheight; + ov->frame[i].bytes_read = 0; + if (force_palette) + ov->frame[i].format = force_palette; + else + ov->frame[i].format = VIDEO_PALETTE_YUV420; + + ov->frame[i].depth = get_depth(ov->frame[i].format); + } + + PDEBUG(3, "%dx%d, %s", ov->maxwidth, ov->maxheight, + symbolic(v4l1_plist, ov->frame[0].format)); + + /* Initialize to max width/height, YUV420 or RGB24 (if supported) */ + if (mode_init_regs(ov, ov->maxwidth, ov->maxheight, + ov->frame[0].format, 0) < 0) + return -EINVAL; + + return 0; +} + +/********************************************************************** + * + * Video decoder stuff + * + **********************************************************************/ + +/* Set analog input port of decoder */ +static int +decoder_set_input(struct usb_ov511 *ov, int input) +{ + PDEBUG(4, "port %d", input); + + switch (ov->sensor) { + case SEN_SAA7111A: + { + /* Select mode */ + i2c_w_mask(ov, 0x02, input, 0x07); + /* Bypass chrominance trap for modes 4..7 */ + i2c_w_mask(ov, 0x09, (input > 3) ? 0x80:0x00, 0x80); + break; + } + default: + return -EINVAL; + } + + return 0; +} + +/* Get ASCII name of video input */ +static int +decoder_get_input_name(struct usb_ov511 *ov, int input, char *name) +{ + switch (ov->sensor) { + case SEN_SAA7111A: + { + if (input < 0 || input > 7) + return -EINVAL; + else if (input < 4) + sprintf(name, "CVBS-%d", input); + else // if (input < 8) + sprintf(name, "S-Video-%d", input - 4); + break; + } + default: + sprintf(name, "%s", "Camera"); + } + + return 0; +} + +/* Set norm (NTSC, PAL, SECAM, AUTO) */ +static int +decoder_set_norm(struct usb_ov511 *ov, int norm) +{ + PDEBUG(4, "%d", norm); + + switch (ov->sensor) { + case SEN_SAA7111A: + { + int reg_8, reg_e; + + if (norm == VIDEO_MODE_NTSC) { + reg_8 = 0x40; /* 60 Hz */ + reg_e = 0x00; /* NTSC M / PAL BGHI */ + } else if (norm == VIDEO_MODE_PAL) { + reg_8 = 0x00; /* 50 Hz */ + reg_e = 0x00; /* NTSC M / PAL BGHI */ + } else if (norm == VIDEO_MODE_AUTO) { + reg_8 = 0x80; /* Auto field detect */ + reg_e = 0x00; /* NTSC M / PAL BGHI */ + } else if (norm == VIDEO_MODE_SECAM) { + reg_8 = 0x00; /* 50 Hz */ + reg_e = 0x50; /* SECAM / PAL 4.43 */ + } else { + return -EINVAL; + } + + i2c_w_mask(ov, 0x08, reg_8, 0xc0); + i2c_w_mask(ov, 0x0e, reg_e, 0x70); + break; + } + default: + return -EINVAL; + } + + return 0; +} + +/********************************************************************** + * + * Raw data parsing + * + **********************************************************************/ + +/* Copies a 64-byte segment at pIn to an 8x8 block at pOut. The width of the + * image at pOut is specified by w. + */ +static inline void +make_8x8(unsigned char *pIn, unsigned char *pOut, int w) +{ + unsigned char *pOut1 = pOut; + int x, y; + + for (y = 0; y < 8; y++) { + pOut1 = pOut; + for (x = 0; x < 8; x++) { + *pOut1++ = *pIn++; + } + pOut += w; + } +} + +/* + * For RAW BW (YUV 4:0:0) images, data show up in 256 byte segments. + * The segments represent 4 squares of 8x8 pixels as follows: + * + * 0 1 ... 7 64 65 ... 71 ... 192 193 ... 199 + * 8 9 ... 15 72 73 ... 79 200 201 ... 207 + * ... ... ... + * 56 57 ... 63 120 121 ... 127 248 249 ... 255 + * + */ +static void +yuv400raw_to_yuv400p(struct ov511_frame *frame, + unsigned char *pIn0, unsigned char *pOut0) +{ + int x, y; + unsigned char *pIn, *pOut, *pOutLine; + + /* Copy Y */ + pIn = pIn0; + pOutLine = pOut0; + for (y = 0; y < frame->rawheight - 1; y += 8) { + pOut = pOutLine; + for (x = 0; x < frame->rawwidth - 1; x += 8) { + make_8x8(pIn, pOut, frame->rawwidth); + pIn += 64; + pOut += 8; + } + pOutLine += 8 * frame->rawwidth; + } +} + +/* + * For YUV 4:2:0 images, the data show up in 384 byte segments. + * The first 64 bytes of each segment are U, the next 64 are V. The U and + * V are arranged as follows: + * + * 0 1 ... 7 + * 8 9 ... 15 + * ... + * 56 57 ... 63 + * + * U and V are shipped at half resolution (1 U,V sample -> one 2x2 block). + * + * The next 256 bytes are full resolution Y data and represent 4 squares + * of 8x8 pixels as follows: + * + * 0 1 ... 7 64 65 ... 71 ... 192 193 ... 199 + * 8 9 ... 15 72 73 ... 79 200 201 ... 207 + * ... ... ... + * 56 57 ... 63 120 121 ... 127 ... 248 249 ... 255 + * + * Note that the U and V data in one segment represent a 16 x 16 pixel + * area, but the Y data represent a 32 x 8 pixel area. If the width is not an + * even multiple of 32, the extra 8x8 blocks within a 32x8 block belong to the + * next horizontal stripe. + * + * If dumppix module param is set, _parse_data just dumps the incoming segments, + * verbatim, in order, into the frame. When used with vidcat -f ppm -s 640x480 + * this puts the data on the standard output and can be analyzed with the + * parseppm.c utility I wrote. That's a much faster way for figuring out how + * these data are scrambled. + */ + +/* Converts from raw, uncompressed segments at pIn0 to a YUV420P frame at pOut0. + * + * FIXME: Currently only handles width and height that are multiples of 16 + */ +static void +yuv420raw_to_yuv420p(struct ov511_frame *frame, + unsigned char *pIn0, unsigned char *pOut0) +{ + int k, x, y; + unsigned char *pIn, *pOut, *pOutLine; + const unsigned int a = frame->rawwidth * frame->rawheight; + const unsigned int w = frame->rawwidth / 2; + + /* Copy U and V */ + pIn = pIn0; + pOutLine = pOut0 + a; + for (y = 0; y < frame->rawheight - 1; y += 16) { + pOut = pOutLine; + for (x = 0; x < frame->rawwidth - 1; x += 16) { + make_8x8(pIn, pOut, w); + make_8x8(pIn + 64, pOut + a/4, w); + pIn += 384; + pOut += 8; + } + pOutLine += 8 * w; + } + + /* Copy Y */ + pIn = pIn0 + 128; + pOutLine = pOut0; + k = 0; + for (y = 0; y < frame->rawheight - 1; y += 8) { + pOut = pOutLine; + for (x = 0; x < frame->rawwidth - 1; x += 8) { + make_8x8(pIn, pOut, frame->rawwidth); + pIn += 64; + pOut += 8; + if ((++k) > 3) { + k = 0; + pIn += 128; + } + } + pOutLine += 8 * frame->rawwidth; + } +} + +/********************************************************************** + * + * Decompression + * + **********************************************************************/ + +static int +request_decompressor(struct usb_ov511 *ov) +{ + if (ov->bclass == BCL_OV511 || ov->bclass == BCL_OV518) { + err("No decompressor available"); + } else { + err("Unknown bridge"); + } + + return -ENOSYS; +} + +static void +decompress(struct usb_ov511 *ov, struct ov511_frame *frame, + unsigned char *pIn0, unsigned char *pOut0) +{ + if (!ov->decomp_ops) + if (request_decompressor(ov)) + return; + +} + +/********************************************************************** + * + * Format conversion + * + **********************************************************************/ + +/* Fuses even and odd fields together, and doubles width. + * INPUT: an odd field followed by an even field at pIn0, in YUV planar format + * OUTPUT: a normal YUV planar image, with correct aspect ratio + */ +static void +deinterlace(struct ov511_frame *frame, int rawformat, + unsigned char *pIn0, unsigned char *pOut0) +{ + const int fieldheight = frame->rawheight / 2; + const int fieldpix = fieldheight * frame->rawwidth; + const int w = frame->width; + int x, y; + unsigned char *pInEven, *pInOdd, *pOut; + + PDEBUG(5, "fieldheight=%d", fieldheight); + + if (frame->rawheight != frame->height) { + err("invalid height"); + return; + } + + if ((frame->rawwidth * 2) != frame->width) { + err("invalid width"); + return; + } + + /* Y */ + pInOdd = pIn0; + pInEven = pInOdd + fieldpix; + pOut = pOut0; + for (y = 0; y < fieldheight; y++) { + for (x = 0; x < frame->rawwidth; x++) { + *pOut = *pInEven; + *(pOut+1) = *pInEven++; + *(pOut+w) = *pInOdd; + *(pOut+w+1) = *pInOdd++; + pOut += 2; + } + pOut += w; + } + + if (rawformat == RAWFMT_YUV420) { + /* U */ + pInOdd = pIn0 + fieldpix * 2; + pInEven = pInOdd + fieldpix / 4; + for (y = 0; y < fieldheight / 2; y++) { + for (x = 0; x < frame->rawwidth / 2; x++) { + *pOut = *pInEven; + *(pOut+1) = *pInEven++; + *(pOut+w/2) = *pInOdd; + *(pOut+w/2+1) = *pInOdd++; + pOut += 2; + } + pOut += w/2; + } + /* V */ + pInOdd = pIn0 + fieldpix * 2 + fieldpix / 2; + pInEven = pInOdd + fieldpix / 4; + for (y = 0; y < fieldheight / 2; y++) { + for (x = 0; x < frame->rawwidth / 2; x++) { + *pOut = *pInEven; + *(pOut+1) = *pInEven++; + *(pOut+w/2) = *pInOdd; + *(pOut+w/2+1) = *pInOdd++; + pOut += 2; + } + pOut += w/2; + } + } +} + +static void +ov51x_postprocess_grey(struct usb_ov511 *ov, struct ov511_frame *frame) +{ + /* Deinterlace frame, if necessary */ + if (ov->sensor == SEN_SAA7111A && frame->rawheight >= 480) { + if (frame->compressed) + decompress(ov, frame, frame->rawdata, + frame->tempdata); + else + yuv400raw_to_yuv400p(frame, frame->rawdata, + frame->tempdata); + + deinterlace(frame, RAWFMT_YUV400, frame->tempdata, + frame->data); + } else { + if (frame->compressed) + decompress(ov, frame, frame->rawdata, + frame->data); + else + yuv400raw_to_yuv400p(frame, frame->rawdata, + frame->data); + } +} + +/* Process raw YUV420 data into standard YUV420P */ +static void +ov51x_postprocess_yuv420(struct usb_ov511 *ov, struct ov511_frame *frame) +{ + /* Deinterlace frame, if necessary */ + if (ov->sensor == SEN_SAA7111A && frame->rawheight >= 480) { + if (frame->compressed) + decompress(ov, frame, frame->rawdata, frame->tempdata); + else + yuv420raw_to_yuv420p(frame, frame->rawdata, + frame->tempdata); + + deinterlace(frame, RAWFMT_YUV420, frame->tempdata, + frame->data); + } else { + if (frame->compressed) + decompress(ov, frame, frame->rawdata, frame->data); + else + yuv420raw_to_yuv420p(frame, frame->rawdata, + frame->data); + } +} + +/* Post-processes the specified frame. This consists of: + * 1. Decompress frame, if necessary + * 2. Deinterlace frame and scale to proper size, if necessary + * 3. Convert from YUV planar to destination format, if necessary + * 4. Fix the RGB offset, if necessary + */ +static void +ov51x_postprocess(struct usb_ov511 *ov, struct ov511_frame *frame) +{ + if (dumppix) { + memset(frame->data, 0, + MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); + PDEBUG(4, "Dumping %d bytes", frame->bytes_recvd); + memcpy(frame->data, frame->rawdata, frame->bytes_recvd); + } else { + switch (frame->format) { + case VIDEO_PALETTE_GREY: + ov51x_postprocess_grey(ov, frame); + break; + case VIDEO_PALETTE_YUV420: + case VIDEO_PALETTE_YUV420P: + ov51x_postprocess_yuv420(ov, frame); + break; + default: + err("Cannot convert data to %s", + symbolic(v4l1_plist, frame->format)); + } + } +} + +/********************************************************************** + * + * OV51x data transfer, IRQ handler + * + **********************************************************************/ + +static inline void +ov511_move_data(struct usb_ov511 *ov, unsigned char *in, int n) +{ + int num, offset; + int pnum = in[ov->packet_size - 1]; /* Get packet number */ + int max_raw = MAX_RAW_DATA_SIZE(ov->maxwidth, ov->maxheight); + struct ov511_frame *frame = &ov->frame[ov->curframe]; + struct timeval *ts; + + /* SOF/EOF packets have 1st to 8th bytes zeroed and the 9th + * byte non-zero. The EOF packet has image width/height in the + * 10th and 11th bytes. The 9th byte is given as follows: + * + * bit 7: EOF + * 6: compression enabled + * 5: 422/420/400 modes + * 4: 422/420/400 modes + * 3: 1 + * 2: snapshot button on + * 1: snapshot frame + * 0: even/odd field + */ + + if (printph) { + info("ph(%3d): %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x", + pnum, in[0], in[1], in[2], in[3], in[4], in[5], in[6], + in[7], in[8], in[9], in[10], in[11]); + } + + /* Check for SOF/EOF packet */ + if ((in[0] | in[1] | in[2] | in[3] | in[4] | in[5] | in[6] | in[7]) || + (~in[8] & 0x08)) + goto check_middle; + + /* Frame end */ + if (in[8] & 0x80) { + ts = (struct timeval *)(frame->data + + MAX_FRAME_SIZE(ov->maxwidth, ov->maxheight)); + do_gettimeofday(ts); + + /* Get the actual frame size from the EOF header */ + frame->rawwidth = ((int)(in[9]) + 1) * 8; + frame->rawheight = ((int)(in[10]) + 1) * 8; + + PDEBUG(4, "Frame end, frame=%d, pnum=%d, w=%d, h=%d, recvd=%d", + ov->curframe, pnum, frame->rawwidth, frame->rawheight, + frame->bytes_recvd); + + /* Validate the header data */ + RESTRICT_TO_RANGE(frame->rawwidth, ov->minwidth, ov->maxwidth); + RESTRICT_TO_RANGE(frame->rawheight, ov->minheight, + ov->maxheight); + + /* Don't allow byte count to exceed buffer size */ + RESTRICT_TO_RANGE(frame->bytes_recvd, 8, max_raw); + + if (frame->scanstate == STATE_LINES) { + int nextf; + + frame->grabstate = FRAME_DONE; + wake_up_interruptible(&frame->wq); + + /* If next frame is ready or grabbing, + * point to it */ + nextf = (ov->curframe + 1) % OV511_NUMFRAMES; + if (ov->frame[nextf].grabstate == FRAME_READY + || ov->frame[nextf].grabstate == FRAME_GRABBING) { + ov->curframe = nextf; + ov->frame[nextf].scanstate = STATE_SCANNING; + } else { + if (frame->grabstate == FRAME_DONE) { + PDEBUG(4, "** Frame done **"); + } else { + PDEBUG(4, "Frame not ready? state = %d", + ov->frame[nextf].grabstate); + } + + ov->curframe = -1; + } + } else { + PDEBUG(5, "Frame done, but not scanning"); + } + /* Image corruption caused by misplaced frame->segment = 0 + * fixed by carlosf@conectiva.com.br + */ + } else { + /* Frame start */ + PDEBUG(4, "Frame start, framenum = %d", ov->curframe); + + /* Check to see if it's a snapshot frame */ + /* FIXME?? Should the snapshot reset go here? Performance? */ + if (in[8] & 0x02) { + frame->snapshot = 1; + PDEBUG(3, "snapshot detected"); + } + + frame->scanstate = STATE_LINES; + frame->bytes_recvd = 0; + frame->compressed = in[8] & 0x40; + } + +check_middle: + /* Are we in a frame? */ + if (frame->scanstate != STATE_LINES) { + PDEBUG(5, "Not in a frame; packet skipped"); + return; + } + + /* If frame start, skip header */ + if (frame->bytes_recvd == 0) + offset = 9; + else + offset = 0; + + num = n - offset - 1; + + /* Dump all data exactly as received */ + if (dumppix == 2) { + frame->bytes_recvd += n - 1; + if (frame->bytes_recvd <= max_raw) + memcpy(frame->rawdata + frame->bytes_recvd - (n - 1), + in, n - 1); + else + PDEBUG(3, "Raw data buffer overrun!! (%d)", + frame->bytes_recvd - max_raw); + } else if (!frame->compressed && !remove_zeros) { + frame->bytes_recvd += num; + if (frame->bytes_recvd <= max_raw) + memcpy(frame->rawdata + frame->bytes_recvd - num, + in + offset, num); + else + PDEBUG(3, "Raw data buffer overrun!! (%d)", + frame->bytes_recvd - max_raw); + } else { /* Remove all-zero FIFO lines (aligned 32-byte blocks) */ + int b, read = 0, allzero, copied = 0; + if (offset) { + frame->bytes_recvd += 32 - offset; // Bytes out + memcpy(frame->rawdata, in + offset, 32 - offset); + read += 32; + } + + while (read < n - 1) { + allzero = 1; + for (b = 0; b < 32; b++) { + if (in[read + b]) { + allzero = 0; + break; + } + } + + if (allzero) { + /* Don't copy it */ + } else { + if (frame->bytes_recvd + copied + 32 <= max_raw) + { + memcpy(frame->rawdata + + frame->bytes_recvd + copied, + in + read, 32); + copied += 32; + } else { + PDEBUG(3, "Raw data buffer overrun!!"); + } + } + read += 32; + } + + frame->bytes_recvd += copied; + } +} + +static inline void +ov518_move_data(struct usb_ov511 *ov, unsigned char *in, int n) +{ + int max_raw = MAX_RAW_DATA_SIZE(ov->maxwidth, ov->maxheight); + struct ov511_frame *frame = &ov->frame[ov->curframe]; + struct timeval *ts; + + /* Don't copy the packet number byte */ + if (ov->packet_numbering) + --n; + + /* A false positive here is likely, until OVT gives me + * the definitive SOF/EOF format */ + if ((!(in[0] | in[1] | in[2] | in[3] | in[5])) && in[6]) { + if (printph) { + info("ph: %2x %2x %2x %2x %2x %2x %2x %2x", in[0], + in[1], in[2], in[3], in[4], in[5], in[6], in[7]); + } + + if (frame->scanstate == STATE_LINES) { + PDEBUG(4, "Detected frame end/start"); + goto eof; + } else { //scanstate == STATE_SCANNING + /* Frame start */ + PDEBUG(4, "Frame start, framenum = %d", ov->curframe); + goto sof; + } + } else { + goto check_middle; + } + +eof: + ts = (struct timeval *)(frame->data + + MAX_FRAME_SIZE(ov->maxwidth, ov->maxheight)); + do_gettimeofday(ts); + + PDEBUG(4, "Frame end, curframe = %d, hw=%d, vw=%d, recvd=%d", + ov->curframe, + (int)(in[9]), (int)(in[10]), frame->bytes_recvd); + + // FIXME: Since we don't know the header formats yet, + // there is no way to know what the actual image size is + frame->rawwidth = frame->width; + frame->rawheight = frame->height; + + /* Validate the header data */ + RESTRICT_TO_RANGE(frame->rawwidth, ov->minwidth, ov->maxwidth); + RESTRICT_TO_RANGE(frame->rawheight, ov->minheight, ov->maxheight); + + /* Don't allow byte count to exceed buffer size */ + RESTRICT_TO_RANGE(frame->bytes_recvd, 8, max_raw); + + if (frame->scanstate == STATE_LINES) { + int nextf; + + frame->grabstate = FRAME_DONE; + wake_up_interruptible(&frame->wq); + + /* If next frame is ready or grabbing, + * point to it */ + nextf = (ov->curframe + 1) % OV511_NUMFRAMES; + if (ov->frame[nextf].grabstate == FRAME_READY + || ov->frame[nextf].grabstate == FRAME_GRABBING) { + ov->curframe = nextf; + ov->frame[nextf].scanstate = STATE_SCANNING; + frame = &ov->frame[nextf]; + } else { + if (frame->grabstate == FRAME_DONE) { + PDEBUG(4, "** Frame done **"); + } else { + PDEBUG(4, "Frame not ready? state = %d", + ov->frame[nextf].grabstate); + } + + ov->curframe = -1; + PDEBUG(4, "SOF dropped (no active frame)"); + return; /* Nowhere to store this frame */ + } + } +sof: + PDEBUG(4, "Starting capture on frame %d", frame->framenum); + +// Snapshot not reverse-engineered yet. +#if 0 + /* Check to see if it's a snapshot frame */ + /* FIXME?? Should the snapshot reset go here? Performance? */ + if (in[8] & 0x02) { + frame->snapshot = 1; + PDEBUG(3, "snapshot detected"); + } +#endif + frame->scanstate = STATE_LINES; + frame->bytes_recvd = 0; + frame->compressed = 1; + +check_middle: + /* Are we in a frame? */ + if (frame->scanstate != STATE_LINES) { + PDEBUG(4, "scanstate: no SOF yet"); + return; + } + + /* Dump all data exactly as received */ + if (dumppix == 2) { + frame->bytes_recvd += n; + if (frame->bytes_recvd <= max_raw) + memcpy(frame->rawdata + frame->bytes_recvd - n, in, n); + else + PDEBUG(3, "Raw data buffer overrun!! (%d)", + frame->bytes_recvd - max_raw); + } else { + /* All incoming data are divided into 8-byte segments. If the + * segment contains all zero bytes, it must be skipped. These + * zero-segments allow the OV518 to mainain a constant data rate + * regardless of the effectiveness of the compression. Segments + * are aligned relative to the beginning of each isochronous + * packet. The first segment in each image is a header (the + * decompressor skips it later). + */ + + int b, read = 0, allzero, copied = 0; + + while (read < n) { + allzero = 1; + for (b = 0; b < 8; b++) { + if (in[read + b]) { + allzero = 0; + break; + } + } + + if (allzero) { + /* Don't copy it */ + } else { + if (frame->bytes_recvd + copied + 8 <= max_raw) + { + memcpy(frame->rawdata + + frame->bytes_recvd + copied, + in + read, 8); + copied += 8; + } else { + PDEBUG(3, "Raw data buffer overrun!!"); + } + } + read += 8; + } + frame->bytes_recvd += copied; + } +} + +static void +ov51x_isoc_irq(struct urb *urb, struct pt_regs *regs) +{ + int i; + struct usb_ov511 *ov; + struct ov511_sbuf *sbuf; + + if (!urb->context) { + PDEBUG(4, "no context"); + return; + } + + sbuf = urb->context; + ov = sbuf->ov; + + if (!ov || !ov->dev || !ov->user) { + PDEBUG(4, "no device, or not open"); + return; + } + + if (!ov->streaming) { + PDEBUG(4, "hmmm... not streaming, but got interrupt"); + return; + } + + if (urb->status == -ENOENT || urb->status == -ECONNRESET) { + PDEBUG(4, "URB unlinked"); + return; + } + + if (urb->status != -EINPROGRESS && urb->status != 0) { + err("ERROR: urb->status=%d: %s", urb->status, + symbolic(urb_errlist, urb->status)); + } + + /* Copy the data received into our frame buffer */ + PDEBUG(5, "sbuf[%d]: Moving %d packets", sbuf->n, + urb->number_of_packets); + for (i = 0; i < urb->number_of_packets; i++) { + /* Warning: Don't call *_move_data() if no frame active! */ + if (ov->curframe >= 0) { + int n = urb->iso_frame_desc[i].actual_length; + int st = urb->iso_frame_desc[i].status; + unsigned char *cdata; + + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = 0; + + cdata = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + + if (!n) { + PDEBUG(4, "Zero-length packet"); + continue; + } + + if (st) + PDEBUG(2, "data error: [%d] len=%d, status=%d", + i, n, st); + + if (ov->bclass == BCL_OV511) + ov511_move_data(ov, cdata, n); + else if (ov->bclass == BCL_OV518) + ov518_move_data(ov, cdata, n); + else + err("Unknown bridge device (%d)", ov->bridge); + + } else if (waitqueue_active(&ov->wq)) { + wake_up_interruptible(&ov->wq); + } + } + + /* Resubmit this URB */ + urb->dev = ov->dev; + if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0) + err("usb_submit_urb() ret %d", i); + + return; +} + +/**************************************************************************** + * + * Stream initialization and termination + * + ***************************************************************************/ + +static int +ov51x_init_isoc(struct usb_ov511 *ov) +{ + struct urb *urb; + int fx, err, n, size; + + PDEBUG(3, "*** Initializing capture ***"); + + ov->curframe = -1; + + if (ov->bridge == BRG_OV511) { + if (cams == 1) + size = 993; + else if (cams == 2) + size = 513; + else if (cams == 3 || cams == 4) + size = 257; + else { + err("\"cams\" parameter too high!"); + return -1; + } + } else if (ov->bridge == BRG_OV511PLUS) { + if (cams == 1) + size = 961; + else if (cams == 2) + size = 513; + else if (cams == 3 || cams == 4) + size = 257; + else if (cams >= 5 && cams <= 8) + size = 129; + else if (cams >= 9 && cams <= 31) + size = 33; + else { + err("\"cams\" parameter too high!"); + return -1; + } + } else if (ov->bclass == BCL_OV518) { + if (cams == 1) + size = 896; + else if (cams == 2) + size = 512; + else if (cams == 3 || cams == 4) + size = 256; + else if (cams >= 5 && cams <= 8) + size = 128; + else { + err("\"cams\" parameter too high!"); + return -1; + } + } else { + err("invalid bridge type"); + return -1; + } + + // FIXME: OV518 is hardcoded to 15 FPS (alternate 5) for now + if (ov->bclass == BCL_OV518) { + if (packetsize == -1) { + ov518_set_packet_size(ov, 640); + } else { + info("Forcing packet size to %d", packetsize); + ov518_set_packet_size(ov, packetsize); + } + } else { + if (packetsize == -1) { + ov511_set_packet_size(ov, size); + } else { + info("Forcing packet size to %d", packetsize); + ov511_set_packet_size(ov, packetsize); + } + } + + for (n = 0; n < OV511_NUMSBUF; n++) { + urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL); + if (!urb) { + err("init isoc: usb_alloc_urb ret. NULL"); + return -ENOMEM; + } + ov->sbuf[n].urb = urb; + urb->dev = ov->dev; + urb->context = &ov->sbuf[n]; + urb->pipe = usb_rcvisocpipe(ov->dev, OV511_ENDPOINT_ADDRESS); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = ov->sbuf[n].data; + urb->complete = ov51x_isoc_irq; + urb->number_of_packets = FRAMES_PER_DESC; + urb->transfer_buffer_length = ov->packet_size * FRAMES_PER_DESC; + urb->interval = 1; + for (fx = 0; fx < FRAMES_PER_DESC; fx++) { + urb->iso_frame_desc[fx].offset = ov->packet_size * fx; + urb->iso_frame_desc[fx].length = ov->packet_size; + } + } + + ov->streaming = 1; + + for (n = 0; n < OV511_NUMSBUF; n++) { + ov->sbuf[n].urb->dev = ov->dev; + err = usb_submit_urb(ov->sbuf[n].urb, GFP_KERNEL); + if (err) { + err("init isoc: usb_submit_urb(%d) ret %d", n, err); + return err; + } + } + + return 0; +} + +static void +ov51x_unlink_isoc(struct usb_ov511 *ov) +{ + int n; + + /* Unschedule all of the iso td's */ + for (n = OV511_NUMSBUF - 1; n >= 0; n--) { + if (ov->sbuf[n].urb) { + usb_kill_urb(ov->sbuf[n].urb); + usb_free_urb(ov->sbuf[n].urb); + ov->sbuf[n].urb = NULL; + } + } +} + +static void +ov51x_stop_isoc(struct usb_ov511 *ov) +{ + if (!ov->streaming || !ov->dev) + return; + + PDEBUG(3, "*** Stopping capture ***"); + + if (ov->bclass == BCL_OV518) + ov518_set_packet_size(ov, 0); + else + ov511_set_packet_size(ov, 0); + + ov->streaming = 0; + + ov51x_unlink_isoc(ov); +} + +static int +ov51x_new_frame(struct usb_ov511 *ov, int framenum) +{ + struct ov511_frame *frame; + int newnum; + + PDEBUG(4, "ov->curframe = %d, framenum = %d", ov->curframe, framenum); + + if (!ov->dev) + return -1; + + /* If we're not grabbing a frame right now and the other frame is */ + /* ready to be grabbed into, then use it instead */ + if (ov->curframe == -1) { + newnum = (framenum - 1 + OV511_NUMFRAMES) % OV511_NUMFRAMES; + if (ov->frame[newnum].grabstate == FRAME_READY) + framenum = newnum; + } else + return 0; + + frame = &ov->frame[framenum]; + + PDEBUG(4, "framenum = %d, width = %d, height = %d", framenum, + frame->width, frame->height); + + frame->grabstate = FRAME_GRABBING; + frame->scanstate = STATE_SCANNING; + frame->snapshot = 0; + + ov->curframe = framenum; + + /* Make sure it's not too big */ + if (frame->width > ov->maxwidth) + frame->width = ov->maxwidth; + + frame->width &= ~7L; /* Multiple of 8 */ + + if (frame->height > ov->maxheight) + frame->height = ov->maxheight; + + frame->height &= ~3L; /* Multiple of 4 */ + + return 0; +} + +/**************************************************************************** + * + * Buffer management + * + ***************************************************************************/ + +/* + * - You must acquire buf_lock before entering this function. + * - Because this code will free any non-null pointer, you must be sure to null + * them if you explicitly free them somewhere else! + */ +static void +ov51x_do_dealloc(struct usb_ov511 *ov) +{ + int i; + PDEBUG(4, "entered"); + + if (ov->fbuf) { + rvfree(ov->fbuf, OV511_NUMFRAMES + * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight)); + ov->fbuf = NULL; + } + + vfree(ov->rawfbuf); + ov->rawfbuf = NULL; + + vfree(ov->tempfbuf); + ov->tempfbuf = NULL; + + for (i = 0; i < OV511_NUMSBUF; i++) { + kfree(ov->sbuf[i].data); + ov->sbuf[i].data = NULL; + } + + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].data = NULL; + ov->frame[i].rawdata = NULL; + ov->frame[i].tempdata = NULL; + if (ov->frame[i].compbuf) { + free_page((unsigned long) ov->frame[i].compbuf); + ov->frame[i].compbuf = NULL; + } + } + + PDEBUG(4, "buffer memory deallocated"); + ov->buf_state = BUF_NOT_ALLOCATED; + PDEBUG(4, "leaving"); +} + +static int +ov51x_alloc(struct usb_ov511 *ov) +{ + int i; + const int w = ov->maxwidth; + const int h = ov->maxheight; + const int data_bufsize = OV511_NUMFRAMES * MAX_DATA_SIZE(w, h); + const int raw_bufsize = OV511_NUMFRAMES * MAX_RAW_DATA_SIZE(w, h); + + PDEBUG(4, "entered"); + mutex_lock(&ov->buf_lock); + + if (ov->buf_state == BUF_ALLOCATED) + goto out; + + ov->fbuf = rvmalloc(data_bufsize); + if (!ov->fbuf) + goto error; + + ov->rawfbuf = vmalloc(raw_bufsize); + if (!ov->rawfbuf) + goto error; + + memset(ov->rawfbuf, 0, raw_bufsize); + + ov->tempfbuf = vmalloc(raw_bufsize); + if (!ov->tempfbuf) + goto error; + + memset(ov->tempfbuf, 0, raw_bufsize); + + for (i = 0; i < OV511_NUMSBUF; i++) { + ov->sbuf[i].data = kmalloc(FRAMES_PER_DESC * + MAX_FRAME_SIZE_PER_DESC, GFP_KERNEL); + if (!ov->sbuf[i].data) + goto error; + + PDEBUG(4, "sbuf[%d] @ %p", i, ov->sbuf[i].data); + } + + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].data = ov->fbuf + i * MAX_DATA_SIZE(w, h); + ov->frame[i].rawdata = ov->rawfbuf + + i * MAX_RAW_DATA_SIZE(w, h); + ov->frame[i].tempdata = ov->tempfbuf + + i * MAX_RAW_DATA_SIZE(w, h); + + ov->frame[i].compbuf = + (unsigned char *) __get_free_page(GFP_KERNEL); + if (!ov->frame[i].compbuf) + goto error; + + PDEBUG(4, "frame[%d] @ %p", i, ov->frame[i].data); + } + + ov->buf_state = BUF_ALLOCATED; +out: + mutex_unlock(&ov->buf_lock); + PDEBUG(4, "leaving"); + return 0; +error: + ov51x_do_dealloc(ov); + mutex_unlock(&ov->buf_lock); + PDEBUG(4, "errored"); + return -ENOMEM; +} + +static void +ov51x_dealloc(struct usb_ov511 *ov) +{ + PDEBUG(4, "entered"); + mutex_lock(&ov->buf_lock); + ov51x_do_dealloc(ov); + mutex_unlock(&ov->buf_lock); + PDEBUG(4, "leaving"); +} + +/**************************************************************************** + * + * V4L 1 API + * + ***************************************************************************/ + +static int +ov51x_v4l1_open(struct inode *inode, struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct usb_ov511 *ov = video_get_drvdata(vdev); + int err, i; + + PDEBUG(4, "opening"); + + mutex_lock(&ov->lock); + + err = -EBUSY; + if (ov->user) + goto out; + + ov->sub_flag = 0; + + /* In case app doesn't set them... */ + err = ov51x_set_default_params(ov); + if (err < 0) + goto out; + + /* Make sure frames are reset */ + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].grabstate = FRAME_UNUSED; + ov->frame[i].bytes_read = 0; + } + + /* If compression is on, make sure now that a + * decompressor can be loaded */ + if (ov->compress && !ov->decomp_ops) { + err = request_decompressor(ov); + if (err && !dumppix) + goto out; + } + + err = ov51x_alloc(ov); + if (err < 0) + goto out; + + err = ov51x_init_isoc(ov); + if (err) { + ov51x_dealloc(ov); + goto out; + } + + ov->user++; + file->private_data = vdev; + + if (ov->led_policy == LED_AUTO) + ov51x_led_control(ov, 1); + +out: + mutex_unlock(&ov->lock); + return err; +} + +static int +ov51x_v4l1_close(struct inode *inode, struct file *file) +{ + struct video_device *vdev = file->private_data; + struct usb_ov511 *ov = video_get_drvdata(vdev); + + PDEBUG(4, "ov511_close"); + + mutex_lock(&ov->lock); + + ov->user--; + ov51x_stop_isoc(ov); + + if (ov->led_policy == LED_AUTO) + ov51x_led_control(ov, 0); + + if (ov->dev) + ov51x_dealloc(ov); + + mutex_unlock(&ov->lock); + + /* Device unplugged while open. Only a minimum of unregistration is done + * here; the disconnect callback already did the rest. */ + if (!ov->dev) { + mutex_lock(&ov->cbuf_lock); + kfree(ov->cbuf); + ov->cbuf = NULL; + mutex_unlock(&ov->cbuf_lock); + + ov51x_dealloc(ov); + kfree(ov); + ov = NULL; + } + + file->private_data = NULL; + return 0; +} + +/* Do not call this function directly! */ +static int +ov51x_v4l1_ioctl_internal(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = file->private_data; + struct usb_ov511 *ov = video_get_drvdata(vdev); + PDEBUG(5, "IOCtl: 0x%X", cmd); + + if (!ov->dev) + return -EIO; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + + PDEBUG(4, "VIDIOCGCAP"); + + memset(b, 0, sizeof(struct video_capability)); + sprintf(b->name, "%s USB Camera", + symbolic(brglist, ov->bridge)); + b->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE; + b->channels = ov->num_inputs; + b->audios = 0; + b->maxwidth = ov->maxwidth; + b->maxheight = ov->maxheight; + b->minwidth = ov->minwidth; + b->minheight = ov->minheight; + + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + + PDEBUG(4, "VIDIOCGCHAN"); + + if ((unsigned)(v->channel) >= ov->num_inputs) { + err("Invalid channel (%d)", v->channel); + return -EINVAL; + } + + v->norm = ov->norm; + v->type = VIDEO_TYPE_CAMERA; + v->flags = 0; +// v->flags |= (ov->has_decoder) ? VIDEO_VC_NORM : 0; + v->tuners = 0; + decoder_get_input_name(ov, v->channel, v->name); + + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + int err; + + PDEBUG(4, "VIDIOCSCHAN"); + + /* Make sure it's not a camera */ + if (!ov->has_decoder) { + if (v->channel == 0) + return 0; + else + return -EINVAL; + } + + if (v->norm != VIDEO_MODE_PAL && + v->norm != VIDEO_MODE_NTSC && + v->norm != VIDEO_MODE_SECAM && + v->norm != VIDEO_MODE_AUTO) { + err("Invalid norm (%d)", v->norm); + return -EINVAL; + } + + if ((unsigned)(v->channel) >= ov->num_inputs) { + err("Invalid channel (%d)", v->channel); + return -EINVAL; + } + + err = decoder_set_input(ov, v->channel); + if (err) + return err; + + err = decoder_set_norm(ov, v->norm); + if (err) + return err; + + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + + PDEBUG(4, "VIDIOCGPICT"); + + memset(p, 0, sizeof(struct video_picture)); + if (sensor_get_picture(ov, p)) + return -EIO; + + /* Can we get these from frame[0]? -claudio? */ + p->depth = ov->frame[0].depth; + p->palette = ov->frame[0].format; + + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + int i, rc; + + PDEBUG(4, "VIDIOCSPICT"); + + if (!get_depth(p->palette)) + return -EINVAL; + + if (sensor_set_picture(ov, p)) + return -EIO; + + if (force_palette && p->palette != force_palette) { + info("Palette rejected (%s)", + symbolic(v4l1_plist, p->palette)); + return -EINVAL; + } + + // FIXME: Format should be independent of frames + if (p->palette != ov->frame[0].format) { + PDEBUG(4, "Detected format change"); + + rc = ov51x_wait_frames_inactive(ov); + if (rc) + return rc; + + mode_init_regs(ov, ov->frame[0].width, + ov->frame[0].height, p->palette, ov->sub_flag); + } + + PDEBUG(4, "Setting depth=%d, palette=%s", + p->depth, symbolic(v4l1_plist, p->palette)); + + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].depth = p->depth; + ov->frame[i].format = p->palette; + } + + return 0; + } + case VIDIOCGCAPTURE: + { + int *vf = arg; + + PDEBUG(4, "VIDIOCGCAPTURE"); + + ov->sub_flag = *vf; + return 0; + } + case VIDIOCSCAPTURE: + { + struct video_capture *vc = arg; + + PDEBUG(4, "VIDIOCSCAPTURE"); + + if (vc->flags) + return -EINVAL; + if (vc->decimation) + return -EINVAL; + + vc->x &= ~3L; + vc->y &= ~1L; + vc->y &= ~31L; + + if (vc->width == 0) + vc->width = 32; + + vc->height /= 16; + vc->height *= 16; + if (vc->height == 0) + vc->height = 16; + + ov->subx = vc->x; + ov->suby = vc->y; + ov->subw = vc->width; + ov->subh = vc->height; + + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + int i, rc; + + PDEBUG(4, "VIDIOCSWIN: %dx%d", vw->width, vw->height); + +#if 0 + if (vw->flags) + return -EINVAL; + if (vw->clipcount) + return -EINVAL; + if (vw->height != ov->maxheight) + return -EINVAL; + if (vw->width != ov->maxwidth) + return -EINVAL; +#endif + + rc = ov51x_wait_frames_inactive(ov); + if (rc) + return rc; + + rc = mode_init_regs(ov, vw->width, vw->height, + ov->frame[0].format, ov->sub_flag); + if (rc < 0) + return rc; + + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].width = vw->width; + ov->frame[i].height = vw->height; + } + + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + + memset(vw, 0, sizeof(struct video_window)); + vw->x = 0; /* FIXME */ + vw->y = 0; + vw->width = ov->frame[0].width; + vw->height = ov->frame[0].height; + vw->flags = 30; + + PDEBUG(4, "VIDIOCGWIN: %dx%d", vw->width, vw->height); + + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf *vm = arg; + int i; + + PDEBUG(4, "VIDIOCGMBUF"); + + memset(vm, 0, sizeof(struct video_mbuf)); + vm->size = OV511_NUMFRAMES + * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight); + vm->frames = OV511_NUMFRAMES; + + vm->offsets[0] = 0; + for (i = 1; i < OV511_NUMFRAMES; i++) { + vm->offsets[i] = vm->offsets[i-1] + + MAX_DATA_SIZE(ov->maxwidth, ov->maxheight); + } + + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + int rc, depth; + unsigned int f = vm->frame; + + PDEBUG(4, "VIDIOCMCAPTURE: frame: %d, %dx%d, %s", f, vm->width, + vm->height, symbolic(v4l1_plist, vm->format)); + + depth = get_depth(vm->format); + if (!depth) { + PDEBUG(2, "VIDIOCMCAPTURE: invalid format (%s)", + symbolic(v4l1_plist, vm->format)); + return -EINVAL; + } + + if (f >= OV511_NUMFRAMES) { + err("VIDIOCMCAPTURE: invalid frame (%d)", f); + return -EINVAL; + } + + if (vm->width > ov->maxwidth + || vm->height > ov->maxheight) { + err("VIDIOCMCAPTURE: requested dimensions too big"); + return -EINVAL; + } + + if (ov->frame[f].grabstate == FRAME_GRABBING) { + PDEBUG(4, "VIDIOCMCAPTURE: already grabbing"); + return -EBUSY; + } + + if (force_palette && (vm->format != force_palette)) { + PDEBUG(2, "palette rejected (%s)", + symbolic(v4l1_plist, vm->format)); + return -EINVAL; + } + + if ((ov->frame[f].width != vm->width) || + (ov->frame[f].height != vm->height) || + (ov->frame[f].format != vm->format) || + (ov->frame[f].sub_flag != ov->sub_flag) || + (ov->frame[f].depth != depth)) { + PDEBUG(4, "VIDIOCMCAPTURE: change in image parameters"); + + rc = ov51x_wait_frames_inactive(ov); + if (rc) + return rc; + + rc = mode_init_regs(ov, vm->width, vm->height, + vm->format, ov->sub_flag); +#if 0 + if (rc < 0) { + PDEBUG(1, "Got error while initializing regs "); + return ret; + } +#endif + ov->frame[f].width = vm->width; + ov->frame[f].height = vm->height; + ov->frame[f].format = vm->format; + ov->frame[f].sub_flag = ov->sub_flag; + ov->frame[f].depth = depth; + } + + /* Mark it as ready */ + ov->frame[f].grabstate = FRAME_READY; + + PDEBUG(4, "VIDIOCMCAPTURE: renewing frame %d", f); + + return ov51x_new_frame(ov, f); + } + case VIDIOCSYNC: + { + unsigned int fnum = *((unsigned int *) arg); + struct ov511_frame *frame; + int rc; + + if (fnum >= OV511_NUMFRAMES) { + err("VIDIOCSYNC: invalid frame (%d)", fnum); + return -EINVAL; + } + + frame = &ov->frame[fnum]; + + PDEBUG(4, "syncing to frame %d, grabstate = %d", fnum, + frame->grabstate); + + switch (frame->grabstate) { + case FRAME_UNUSED: + return -EINVAL; + case FRAME_READY: + case FRAME_GRABBING: + case FRAME_ERROR: +redo: + if (!ov->dev) + return -EIO; + + rc = wait_event_interruptible(frame->wq, + (frame->grabstate == FRAME_DONE) + || (frame->grabstate == FRAME_ERROR)); + + if (rc) + return rc; + + if (frame->grabstate == FRAME_ERROR) { + if ((rc = ov51x_new_frame(ov, fnum)) < 0) + return rc; + goto redo; + } + /* Fall through */ + case FRAME_DONE: + if (ov->snap_enabled && !frame->snapshot) { + if ((rc = ov51x_new_frame(ov, fnum)) < 0) + return rc; + goto redo; + } + + frame->grabstate = FRAME_UNUSED; + + /* Reset the hardware snapshot button */ + /* FIXME - Is this the best place for this? */ + if ((ov->snap_enabled) && (frame->snapshot)) { + frame->snapshot = 0; + ov51x_clear_snapshot(ov); + } + + /* Decompression, format conversion, etc... */ + ov51x_postprocess(ov, frame); + + break; + } /* end switch */ + + return 0; + } + case VIDIOCGFBUF: + { + struct video_buffer *vb = arg; + + PDEBUG(4, "VIDIOCGFBUF"); + + memset(vb, 0, sizeof(struct video_buffer)); + + return 0; + } + case VIDIOCGUNIT: + { + struct video_unit *vu = arg; + + PDEBUG(4, "VIDIOCGUNIT"); + + memset(vu, 0, sizeof(struct video_unit)); + + vu->video = ov->vdev->minor; + vu->vbi = VIDEO_NO_UNIT; + vu->radio = VIDEO_NO_UNIT; + vu->audio = VIDEO_NO_UNIT; + vu->teletext = VIDEO_NO_UNIT; + + return 0; + } + case OV511IOC_WI2C: + { + struct ov511_i2c_struct *w = arg; + + return i2c_w_slave(ov, w->slave, w->reg, w->value, w->mask); + } + case OV511IOC_RI2C: + { + struct ov511_i2c_struct *r = arg; + int rc; + + rc = i2c_r_slave(ov, r->slave, r->reg); + if (rc < 0) + return rc; + + r->value = rc; + return 0; + } + default: + PDEBUG(3, "Unsupported IOCtl: 0x%X", cmd); + return -ENOIOCTLCMD; + } /* end switch */ + + return 0; +} + +static int +ov51x_v4l1_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vdev = file->private_data; + struct usb_ov511 *ov = video_get_drvdata(vdev); + int rc; + + if (mutex_lock_interruptible(&ov->lock)) + return -EINTR; + + rc = video_usercopy(inode, file, cmd, arg, ov51x_v4l1_ioctl_internal); + + mutex_unlock(&ov->lock); + return rc; +} + +static ssize_t +ov51x_v4l1_read(struct file *file, char __user *buf, size_t cnt, loff_t *ppos) +{ + struct video_device *vdev = file->private_data; + int noblock = file->f_flags&O_NONBLOCK; + unsigned long count = cnt; + struct usb_ov511 *ov = video_get_drvdata(vdev); + int i, rc = 0, frmx = -1; + struct ov511_frame *frame; + + if (mutex_lock_interruptible(&ov->lock)) + return -EINTR; + + PDEBUG(4, "%ld bytes, noblock=%d", count, noblock); + + if (!vdev || !buf) { + rc = -EFAULT; + goto error; + } + + if (!ov->dev) { + rc = -EIO; + goto error; + } + +// FIXME: Only supports two frames + /* See if a frame is completed, then use it. */ + if (ov->frame[0].grabstate >= FRAME_DONE) /* _DONE or _ERROR */ + frmx = 0; + else if (ov->frame[1].grabstate >= FRAME_DONE)/* _DONE or _ERROR */ + frmx = 1; + + /* If nonblocking we return immediately */ + if (noblock && (frmx == -1)) { + rc = -EAGAIN; + goto error; + } + + /* If no FRAME_DONE, look for a FRAME_GRABBING state. */ + /* See if a frame is in process (grabbing), then use it. */ + if (frmx == -1) { + if (ov->frame[0].grabstate == FRAME_GRABBING) + frmx = 0; + else if (ov->frame[1].grabstate == FRAME_GRABBING) + frmx = 1; + } + + /* If no frame is active, start one. */ + if (frmx == -1) { + if ((rc = ov51x_new_frame(ov, frmx = 0))) { + err("read: ov51x_new_frame error"); + goto error; + } + } + + frame = &ov->frame[frmx]; + +restart: + if (!ov->dev) { + rc = -EIO; + goto error; + } + + /* Wait while we're grabbing the image */ + PDEBUG(4, "Waiting image grabbing"); + rc = wait_event_interruptible(frame->wq, + (frame->grabstate == FRAME_DONE) + || (frame->grabstate == FRAME_ERROR)); + + if (rc) + goto error; + + PDEBUG(4, "Got image, frame->grabstate = %d", frame->grabstate); + PDEBUG(4, "bytes_recvd = %d", frame->bytes_recvd); + + if (frame->grabstate == FRAME_ERROR) { + frame->bytes_read = 0; + err("** ick! ** Errored frame %d", ov->curframe); + if (ov51x_new_frame(ov, frmx)) { + err("read: ov51x_new_frame error"); + goto error; + } + goto restart; + } + + + /* Repeat until we get a snapshot frame */ + if (ov->snap_enabled) + PDEBUG(4, "Waiting snapshot frame"); + if (ov->snap_enabled && !frame->snapshot) { + frame->bytes_read = 0; + if ((rc = ov51x_new_frame(ov, frmx))) { + err("read: ov51x_new_frame error"); + goto error; + } + goto restart; + } + + /* Clear the snapshot */ + if (ov->snap_enabled && frame->snapshot) { + frame->snapshot = 0; + ov51x_clear_snapshot(ov); + } + + /* Decompression, format conversion, etc... */ + ov51x_postprocess(ov, frame); + + PDEBUG(4, "frmx=%d, bytes_read=%ld, length=%ld", frmx, + frame->bytes_read, + get_frame_length(frame)); + + /* copy bytes to user space; we allow for partials reads */ +// if ((count + frame->bytes_read) +// > get_frame_length((struct ov511_frame *)frame)) +// count = frame->scanlength - frame->bytes_read; + + /* FIXME - count hardwired to be one frame... */ + count = get_frame_length(frame); + + PDEBUG(4, "Copy to user space: %ld bytes", count); + if ((i = copy_to_user(buf, frame->data + frame->bytes_read, count))) { + PDEBUG(4, "Copy failed! %d bytes not copied", i); + rc = -EFAULT; + goto error; + } + + frame->bytes_read += count; + PDEBUG(4, "{copy} count used=%ld, new bytes_read=%ld", + count, frame->bytes_read); + + /* If all data have been read... */ + if (frame->bytes_read + >= get_frame_length(frame)) { + frame->bytes_read = 0; + +// FIXME: Only supports two frames + /* Mark it as available to be used again. */ + ov->frame[frmx].grabstate = FRAME_UNUSED; + if ((rc = ov51x_new_frame(ov, !frmx))) { + err("ov51x_new_frame returned error"); + goto error; + } + } + + PDEBUG(4, "read finished, returning %ld (sweet)", count); + + mutex_unlock(&ov->lock); + return count; + +error: + mutex_unlock(&ov->lock); + return rc; +} + +static int +ov51x_v4l1_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + struct usb_ov511 *ov = video_get_drvdata(vdev); + unsigned long page, pos; + + if (ov->dev == NULL) + return -EIO; + + PDEBUG(4, "mmap: %ld (%lX) bytes", size, size); + + if (size > (((OV511_NUMFRAMES + * MAX_DATA_SIZE(ov->maxwidth, ov->maxheight) + + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)))) + return -EINVAL; + + if (mutex_lock_interruptible(&ov->lock)) + return -EINTR; + + pos = (unsigned long)ov->fbuf; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + mutex_unlock(&ov->lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + mutex_unlock(&ov->lock); + return 0; +} + +static struct file_operations ov511_fops = { + .owner = THIS_MODULE, + .open = ov51x_v4l1_open, + .release = ov51x_v4l1_close, + .read = ov51x_v4l1_read, + .mmap = ov51x_v4l1_mmap, + .ioctl = ov51x_v4l1_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device vdev_template = { + .owner = THIS_MODULE, + .name = "OV511 USB Camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_OV511, + .fops = &ov511_fops, + .release = video_device_release, + .minor = -1, +}; + +/**************************************************************************** + * + * OV511 and sensor configuration + * + ***************************************************************************/ + +/* This initializes the OV7610, OV7620, or OV76BE sensor. The OV76BE uses + * the same register settings as the OV7610, since they are very similar. + */ +static int +ov7xx0_configure(struct usb_ov511 *ov) +{ + int i, success; + int rc; + + /* Lawrence Glaister <lg@jfm.bc.ca> reports: + * + * Register 0x0f in the 7610 has the following effects: + * + * 0x85 (AEC method 1): Best overall, good contrast range + * 0x45 (AEC method 2): Very overexposed + * 0xa5 (spec sheet default): Ok, but the black level is + * shifted resulting in loss of contrast + * 0x05 (old driver setting): very overexposed, too much + * contrast + */ + static struct ov511_regvals aRegvalsNorm7610[] = { + { OV511_I2C_BUS, 0x10, 0xff }, + { OV511_I2C_BUS, 0x16, 0x06 }, + { OV511_I2C_BUS, 0x28, 0x24 }, + { OV511_I2C_BUS, 0x2b, 0xac }, + { OV511_I2C_BUS, 0x12, 0x00 }, + { OV511_I2C_BUS, 0x38, 0x81 }, + { OV511_I2C_BUS, 0x28, 0x24 }, /* 0c */ + { OV511_I2C_BUS, 0x0f, 0x85 }, /* lg's setting */ + { OV511_I2C_BUS, 0x15, 0x01 }, + { OV511_I2C_BUS, 0x20, 0x1c }, + { OV511_I2C_BUS, 0x23, 0x2a }, + { OV511_I2C_BUS, 0x24, 0x10 }, + { OV511_I2C_BUS, 0x25, 0x8a }, + { OV511_I2C_BUS, 0x26, 0xa2 }, + { OV511_I2C_BUS, 0x27, 0xc2 }, + { OV511_I2C_BUS, 0x2a, 0x04 }, + { OV511_I2C_BUS, 0x2c, 0xfe }, + { OV511_I2C_BUS, 0x2d, 0x93 }, + { OV511_I2C_BUS, 0x30, 0x71 }, + { OV511_I2C_BUS, 0x31, 0x60 }, + { OV511_I2C_BUS, 0x32, 0x26 }, + { OV511_I2C_BUS, 0x33, 0x20 }, + { OV511_I2C_BUS, 0x34, 0x48 }, + { OV511_I2C_BUS, 0x12, 0x24 }, + { OV511_I2C_BUS, 0x11, 0x01 }, + { OV511_I2C_BUS, 0x0c, 0x24 }, + { OV511_I2C_BUS, 0x0d, 0x24 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + static struct ov511_regvals aRegvalsNorm7620[] = { + { OV511_I2C_BUS, 0x00, 0x00 }, + { OV511_I2C_BUS, 0x01, 0x80 }, + { OV511_I2C_BUS, 0x02, 0x80 }, + { OV511_I2C_BUS, 0x03, 0xc0 }, + { OV511_I2C_BUS, 0x06, 0x60 }, + { OV511_I2C_BUS, 0x07, 0x00 }, + { OV511_I2C_BUS, 0x0c, 0x24 }, + { OV511_I2C_BUS, 0x0c, 0x24 }, + { OV511_I2C_BUS, 0x0d, 0x24 }, + { OV511_I2C_BUS, 0x11, 0x01 }, + { OV511_I2C_BUS, 0x12, 0x24 }, + { OV511_I2C_BUS, 0x13, 0x01 }, + { OV511_I2C_BUS, 0x14, 0x84 }, + { OV511_I2C_BUS, 0x15, 0x01 }, + { OV511_I2C_BUS, 0x16, 0x03 }, + { OV511_I2C_BUS, 0x17, 0x2f }, + { OV511_I2C_BUS, 0x18, 0xcf }, + { OV511_I2C_BUS, 0x19, 0x06 }, + { OV511_I2C_BUS, 0x1a, 0xf5 }, + { OV511_I2C_BUS, 0x1b, 0x00 }, + { OV511_I2C_BUS, 0x20, 0x18 }, + { OV511_I2C_BUS, 0x21, 0x80 }, + { OV511_I2C_BUS, 0x22, 0x80 }, + { OV511_I2C_BUS, 0x23, 0x00 }, + { OV511_I2C_BUS, 0x26, 0xa2 }, + { OV511_I2C_BUS, 0x27, 0xea }, + { OV511_I2C_BUS, 0x28, 0x20 }, + { OV511_I2C_BUS, 0x29, 0x00 }, + { OV511_I2C_BUS, 0x2a, 0x10 }, + { OV511_I2C_BUS, 0x2b, 0x00 }, + { OV511_I2C_BUS, 0x2c, 0x88 }, + { OV511_I2C_BUS, 0x2d, 0x91 }, + { OV511_I2C_BUS, 0x2e, 0x80 }, + { OV511_I2C_BUS, 0x2f, 0x44 }, + { OV511_I2C_BUS, 0x60, 0x27 }, + { OV511_I2C_BUS, 0x61, 0x02 }, + { OV511_I2C_BUS, 0x62, 0x5f }, + { OV511_I2C_BUS, 0x63, 0xd5 }, + { OV511_I2C_BUS, 0x64, 0x57 }, + { OV511_I2C_BUS, 0x65, 0x83 }, + { OV511_I2C_BUS, 0x66, 0x55 }, + { OV511_I2C_BUS, 0x67, 0x92 }, + { OV511_I2C_BUS, 0x68, 0xcf }, + { OV511_I2C_BUS, 0x69, 0x76 }, + { OV511_I2C_BUS, 0x6a, 0x22 }, + { OV511_I2C_BUS, 0x6b, 0x00 }, + { OV511_I2C_BUS, 0x6c, 0x02 }, + { OV511_I2C_BUS, 0x6d, 0x44 }, + { OV511_I2C_BUS, 0x6e, 0x80 }, + { OV511_I2C_BUS, 0x6f, 0x1d }, + { OV511_I2C_BUS, 0x70, 0x8b }, + { OV511_I2C_BUS, 0x71, 0x00 }, + { OV511_I2C_BUS, 0x72, 0x14 }, + { OV511_I2C_BUS, 0x73, 0x54 }, + { OV511_I2C_BUS, 0x74, 0x00 }, + { OV511_I2C_BUS, 0x75, 0x8e }, + { OV511_I2C_BUS, 0x76, 0x00 }, + { OV511_I2C_BUS, 0x77, 0xff }, + { OV511_I2C_BUS, 0x78, 0x80 }, + { OV511_I2C_BUS, 0x79, 0x80 }, + { OV511_I2C_BUS, 0x7a, 0x80 }, + { OV511_I2C_BUS, 0x7b, 0xe2 }, + { OV511_I2C_BUS, 0x7c, 0x00 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + PDEBUG(4, "starting configuration"); + + /* This looks redundant, but is necessary for WebCam 3 */ + ov->primary_i2c_slave = OV7xx0_SID; + if (ov51x_set_slave_ids(ov, OV7xx0_SID) < 0) + return -1; + + if (init_ov_sensor(ov) >= 0) { + PDEBUG(1, "OV7xx0 sensor initalized (method 1)"); + } else { + /* Reset the 76xx */ + if (i2c_w(ov, 0x12, 0x80) < 0) + return -1; + + /* Wait for it to initialize */ + msleep(150); + + i = 0; + success = 0; + while (i <= i2c_detect_tries) { + if ((i2c_r(ov, OV7610_REG_ID_HIGH) == 0x7F) && + (i2c_r(ov, OV7610_REG_ID_LOW) == 0xA2)) { + success = 1; + break; + } else { + i++; + } + } + +// Was (i == i2c_detect_tries) previously. This obviously used to always report +// success. Whether anyone actually depended on that bug is unknown + if ((i >= i2c_detect_tries) && (success == 0)) { + err("Failed to read sensor ID. You might not have an"); + err("OV7610/20, or it may be not responding. Report"); + err("this to " EMAIL); + err("This is only a warning. You can attempt to use"); + err("your camera anyway"); +// Only issue a warning for now +// return -1; + } else { + PDEBUG(1, "OV7xx0 initialized (method 2, %dx)", i+1); + } + } + + /* Detect sensor (sub)type */ + rc = i2c_r(ov, OV7610_REG_COM_I); + + if (rc < 0) { + err("Error detecting sensor type"); + return -1; + } else if ((rc & 3) == 3) { + info("Sensor is an OV7610"); + ov->sensor = SEN_OV7610; + } else if ((rc & 3) == 1) { + /* I don't know what's different about the 76BE yet. */ + if (i2c_r(ov, 0x15) & 1) + info("Sensor is an OV7620AE"); + else + info("Sensor is an OV76BE"); + + /* OV511+ will return all zero isoc data unless we + * configure the sensor as a 7620. Someone needs to + * find the exact reg. setting that causes this. */ + if (ov->bridge == BRG_OV511PLUS) { + info("Enabling 511+/7620AE workaround"); + ov->sensor = SEN_OV7620; + } else { + ov->sensor = SEN_OV76BE; + } + } else if ((rc & 3) == 0) { + info("Sensor is an OV7620"); + ov->sensor = SEN_OV7620; + } else { + err("Unknown image sensor version: %d", rc & 3); + return -1; + } + + if (ov->sensor == SEN_OV7620) { + PDEBUG(4, "Writing 7620 registers"); + if (write_regvals(ov, aRegvalsNorm7620)) + return -1; + } else { + PDEBUG(4, "Writing 7610 registers"); + if (write_regvals(ov, aRegvalsNorm7610)) + return -1; + } + + /* Set sensor-specific vars */ + ov->maxwidth = 640; + ov->maxheight = 480; + ov->minwidth = 64; + ov->minheight = 48; + + // FIXME: These do not match the actual settings yet + ov->brightness = 0x80 << 8; + ov->contrast = 0x80 << 8; + ov->colour = 0x80 << 8; + ov->hue = 0x80 << 8; + + return 0; +} + +/* This initializes the OV6620, OV6630, OV6630AE, or OV6630AF sensor. */ +static int +ov6xx0_configure(struct usb_ov511 *ov) +{ + int rc; + + static struct ov511_regvals aRegvalsNorm6x20[] = { + { OV511_I2C_BUS, 0x12, 0x80 }, /* reset */ + { OV511_I2C_BUS, 0x11, 0x01 }, + { OV511_I2C_BUS, 0x03, 0x60 }, + { OV511_I2C_BUS, 0x05, 0x7f }, /* For when autoadjust is off */ + { OV511_I2C_BUS, 0x07, 0xa8 }, + /* The ratio of 0x0c and 0x0d controls the white point */ + { OV511_I2C_BUS, 0x0c, 0x24 }, + { OV511_I2C_BUS, 0x0d, 0x24 }, + { OV511_I2C_BUS, 0x0f, 0x15 }, /* COMS */ + { OV511_I2C_BUS, 0x10, 0x75 }, /* AEC Exposure time */ + { OV511_I2C_BUS, 0x12, 0x24 }, /* Enable AGC */ + { OV511_I2C_BUS, 0x14, 0x04 }, + /* 0x16: 0x06 helps frame stability with moving objects */ + { OV511_I2C_BUS, 0x16, 0x06 }, +// { OV511_I2C_BUS, 0x20, 0x30 }, /* Aperture correction enable */ + { OV511_I2C_BUS, 0x26, 0xb2 }, /* BLC enable */ + /* 0x28: 0x05 Selects RGB format if RGB on */ + { OV511_I2C_BUS, 0x28, 0x05 }, + { OV511_I2C_BUS, 0x2a, 0x04 }, /* Disable framerate adjust */ +// { OV511_I2C_BUS, 0x2b, 0xac }, /* Framerate; Set 2a[7] first */ + { OV511_I2C_BUS, 0x2d, 0x99 }, + { OV511_I2C_BUS, 0x33, 0xa0 }, /* Color Processing Parameter */ + { OV511_I2C_BUS, 0x34, 0xd2 }, /* Max A/D range */ + { OV511_I2C_BUS, 0x38, 0x8b }, + { OV511_I2C_BUS, 0x39, 0x40 }, + + { OV511_I2C_BUS, 0x3c, 0x39 }, /* Enable AEC mode changing */ + { OV511_I2C_BUS, 0x3c, 0x3c }, /* Change AEC mode */ + { OV511_I2C_BUS, 0x3c, 0x24 }, /* Disable AEC mode changing */ + + { OV511_I2C_BUS, 0x3d, 0x80 }, + /* These next two registers (0x4a, 0x4b) are undocumented. They + * control the color balance */ + { OV511_I2C_BUS, 0x4a, 0x80 }, + { OV511_I2C_BUS, 0x4b, 0x80 }, + { OV511_I2C_BUS, 0x4d, 0xd2 }, /* This reduces noise a bit */ + { OV511_I2C_BUS, 0x4e, 0xc1 }, + { OV511_I2C_BUS, 0x4f, 0x04 }, +// Do 50-53 have any effect? +// Toggle 0x12[2] off and on here? + { OV511_DONE_BUS, 0x0, 0x00 }, /* END MARKER */ + }; + + static struct ov511_regvals aRegvalsNorm6x30[] = { + /*OK*/ { OV511_I2C_BUS, 0x12, 0x80 }, /* reset */ + { OV511_I2C_BUS, 0x11, 0x00 }, + /*OK*/ { OV511_I2C_BUS, 0x03, 0x60 }, + /*0A?*/ { OV511_I2C_BUS, 0x05, 0x7f }, /* For when autoadjust is off */ + { OV511_I2C_BUS, 0x07, 0xa8 }, + /* The ratio of 0x0c and 0x0d controls the white point */ + /*OK*/ { OV511_I2C_BUS, 0x0c, 0x24 }, + /*OK*/ { OV511_I2C_BUS, 0x0d, 0x24 }, + /*A*/ { OV511_I2C_BUS, 0x0e, 0x20 }, +// /*04?*/ { OV511_I2C_BUS, 0x14, 0x80 }, + { OV511_I2C_BUS, 0x16, 0x03 }, +// /*OK*/ { OV511_I2C_BUS, 0x20, 0x30 }, /* Aperture correction enable */ + // 21 & 22? The suggested values look wrong. Go with default + /*A*/ { OV511_I2C_BUS, 0x23, 0xc0 }, + /*A*/ { OV511_I2C_BUS, 0x25, 0x9a }, // Check this against default +// /*OK*/ { OV511_I2C_BUS, 0x26, 0xb2 }, /* BLC enable */ + + /* 0x28: 0x05 Selects RGB format if RGB on */ +// /*04?*/ { OV511_I2C_BUS, 0x28, 0x05 }, +// /*04?*/ { OV511_I2C_BUS, 0x28, 0x45 }, // DEBUG: Tristate UV bus + + /*OK*/ { OV511_I2C_BUS, 0x2a, 0x04 }, /* Disable framerate adjust */ +// /*OK*/ { OV511_I2C_BUS, 0x2b, 0xac }, /* Framerate; Set 2a[7] first */ + { OV511_I2C_BUS, 0x2d, 0x99 }, +// /*A*/ { OV511_I2C_BUS, 0x33, 0x26 }, // Reserved bits on 6620 +// /*d2?*/ { OV511_I2C_BUS, 0x34, 0x03 }, /* Max A/D range */ +// /*8b?*/ { OV511_I2C_BUS, 0x38, 0x83 }, +// /*40?*/ { OV511_I2C_BUS, 0x39, 0xc0 }, // 6630 adds bit 7 +// { OV511_I2C_BUS, 0x3c, 0x39 }, /* Enable AEC mode changing */ +// { OV511_I2C_BUS, 0x3c, 0x3c }, /* Change AEC mode */ +// { OV511_I2C_BUS, 0x3c, 0x24 }, /* Disable AEC mode changing */ + { OV511_I2C_BUS, 0x3d, 0x80 }, +// /*A*/ { OV511_I2C_BUS, 0x3f, 0x0e }, + + /* These next two registers (0x4a, 0x4b) are undocumented. They + * control the color balance */ +// /*OK?*/ { OV511_I2C_BUS, 0x4a, 0x80 }, // Check these +// /*OK?*/ { OV511_I2C_BUS, 0x4b, 0x80 }, + { OV511_I2C_BUS, 0x4d, 0x10 }, /* U = 0.563u, V = 0.714v */ + /*c1?*/ { OV511_I2C_BUS, 0x4e, 0x40 }, + + /* UV average mode, color killer: strongest */ + { OV511_I2C_BUS, 0x4f, 0x07 }, + + { OV511_I2C_BUS, 0x54, 0x23 }, /* Max AGC gain: 18dB */ + { OV511_I2C_BUS, 0x57, 0x81 }, /* (default) */ + { OV511_I2C_BUS, 0x59, 0x01 }, /* AGC dark current comp: +1 */ + { OV511_I2C_BUS, 0x5a, 0x2c }, /* (undocumented) */ + { OV511_I2C_BUS, 0x5b, 0x0f }, /* AWB chrominance levels */ +// { OV511_I2C_BUS, 0x5c, 0x10 }, + { OV511_DONE_BUS, 0x0, 0x00 }, /* END MARKER */ + }; + + PDEBUG(4, "starting sensor configuration"); + + if (init_ov_sensor(ov) < 0) { + err("Failed to read sensor ID. You might not have an OV6xx0,"); + err("or it may be not responding. Report this to " EMAIL); + return -1; + } else { + PDEBUG(1, "OV6xx0 sensor detected"); + } + + /* Detect sensor (sub)type */ + rc = i2c_r(ov, OV7610_REG_COM_I); + + if (rc < 0) { + err("Error detecting sensor type"); + return -1; + } + + if ((rc & 3) == 0) { + ov->sensor = SEN_OV6630; + info("Sensor is an OV6630"); + } else if ((rc & 3) == 1) { + ov->sensor = SEN_OV6620; + info("Sensor is an OV6620"); + } else if ((rc & 3) == 2) { + ov->sensor = SEN_OV6630; + info("Sensor is an OV6630AE"); + } else if ((rc & 3) == 3) { + ov->sensor = SEN_OV6630; + info("Sensor is an OV6630AF"); + } + + /* Set sensor-specific vars */ + ov->maxwidth = 352; + ov->maxheight = 288; + ov->minwidth = 64; + ov->minheight = 48; + + // FIXME: These do not match the actual settings yet + ov->brightness = 0x80 << 8; + ov->contrast = 0x80 << 8; + ov->colour = 0x80 << 8; + ov->hue = 0x80 << 8; + + if (ov->sensor == SEN_OV6620) { + PDEBUG(4, "Writing 6x20 registers"); + if (write_regvals(ov, aRegvalsNorm6x20)) + return -1; + } else { + PDEBUG(4, "Writing 6x30 registers"); + if (write_regvals(ov, aRegvalsNorm6x30)) + return -1; + } + + return 0; +} + +/* This initializes the KS0127 and KS0127B video decoders. */ +static int +ks0127_configure(struct usb_ov511 *ov) +{ + int rc; + +// FIXME: I don't know how to sync or reset it yet +#if 0 + if (ov51x_init_ks_sensor(ov) < 0) { + err("Failed to initialize the KS0127"); + return -1; + } else { + PDEBUG(1, "KS012x(B) sensor detected"); + } +#endif + + /* Detect decoder subtype */ + rc = i2c_r(ov, 0x00); + if (rc < 0) { + err("Error detecting sensor type"); + return -1; + } else if (rc & 0x08) { + rc = i2c_r(ov, 0x3d); + if (rc < 0) { + err("Error detecting sensor type"); + return -1; + } else if ((rc & 0x0f) == 0) { + info("Sensor is a KS0127"); + ov->sensor = SEN_KS0127; + } else if ((rc & 0x0f) == 9) { + info("Sensor is a KS0127B Rev. A"); + ov->sensor = SEN_KS0127B; + } + } else { + err("Error: Sensor is an unsupported KS0122"); + return -1; + } + + /* Set sensor-specific vars */ + ov->maxwidth = 640; + ov->maxheight = 480; + ov->minwidth = 64; + ov->minheight = 48; + + // FIXME: These do not match the actual settings yet + ov->brightness = 0x80 << 8; + ov->contrast = 0x80 << 8; + ov->colour = 0x80 << 8; + ov->hue = 0x80 << 8; + + /* This device is not supported yet. Bail out now... */ + err("This sensor is not supported yet."); + return -1; + + return 0; +} + +/* This initializes the SAA7111A video decoder. */ +static int +saa7111a_configure(struct usb_ov511 *ov) +{ + int rc; + + /* Since there is no register reset command, all registers must be + * written, otherwise gives erratic results */ + static struct ov511_regvals aRegvalsNormSAA7111A[] = { + { OV511_I2C_BUS, 0x06, 0xce }, + { OV511_I2C_BUS, 0x07, 0x00 }, + { OV511_I2C_BUS, 0x10, 0x44 }, /* YUV422, 240/286 lines */ + { OV511_I2C_BUS, 0x0e, 0x01 }, /* NTSC M or PAL BGHI */ + { OV511_I2C_BUS, 0x00, 0x00 }, + { OV511_I2C_BUS, 0x01, 0x00 }, + { OV511_I2C_BUS, 0x03, 0x23 }, + { OV511_I2C_BUS, 0x04, 0x00 }, + { OV511_I2C_BUS, 0x05, 0x00 }, + { OV511_I2C_BUS, 0x08, 0xc8 }, /* Auto field freq */ + { OV511_I2C_BUS, 0x09, 0x01 }, /* Chrom. trap off, APER=0.25 */ + { OV511_I2C_BUS, 0x0a, 0x80 }, /* BRIG=128 */ + { OV511_I2C_BUS, 0x0b, 0x40 }, /* CONT=1.0 */ + { OV511_I2C_BUS, 0x0c, 0x40 }, /* SATN=1.0 */ + { OV511_I2C_BUS, 0x0d, 0x00 }, /* HUE=0 */ + { OV511_I2C_BUS, 0x0f, 0x00 }, + { OV511_I2C_BUS, 0x11, 0x0c }, + { OV511_I2C_BUS, 0x12, 0x00 }, + { OV511_I2C_BUS, 0x13, 0x00 }, + { OV511_I2C_BUS, 0x14, 0x00 }, + { OV511_I2C_BUS, 0x15, 0x00 }, + { OV511_I2C_BUS, 0x16, 0x00 }, + { OV511_I2C_BUS, 0x17, 0x00 }, + { OV511_I2C_BUS, 0x02, 0xc0 }, /* Composite input 0 */ + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + +// FIXME: I don't know how to sync or reset it yet +#if 0 + if (ov51x_init_saa_sensor(ov) < 0) { + err("Failed to initialize the SAA7111A"); + return -1; + } else { + PDEBUG(1, "SAA7111A sensor detected"); + } +#endif + + /* 640x480 not supported with PAL */ + if (ov->pal) { + ov->maxwidth = 320; + ov->maxheight = 240; /* Even field only */ + } else { + ov->maxwidth = 640; + ov->maxheight = 480; /* Even/Odd fields */ + } + + ov->minwidth = 320; + ov->minheight = 240; /* Even field only */ + + ov->has_decoder = 1; + ov->num_inputs = 8; + ov->norm = VIDEO_MODE_AUTO; + ov->stop_during_set = 0; /* Decoder guarantees stable image */ + + /* Decoder doesn't change these values, so we use these instead of + * acutally reading the registers (which doesn't work) */ + ov->brightness = 0x80 << 8; + ov->contrast = 0x40 << 9; + ov->colour = 0x40 << 9; + ov->hue = 32768; + + PDEBUG(4, "Writing SAA7111A registers"); + if (write_regvals(ov, aRegvalsNormSAA7111A)) + return -1; + + /* Detect version of decoder. This must be done after writing the + * initial regs or the decoder will lock up. */ + rc = i2c_r(ov, 0x00); + + if (rc < 0) { + err("Error detecting sensor version"); + return -1; + } else { + info("Sensor is an SAA7111A (version 0x%x)", rc); + ov->sensor = SEN_SAA7111A; + } + + // FIXME: Fix this for OV518(+) + /* Latch to negative edge of clock. Otherwise, we get incorrect + * colors and jitter in the digital signal. */ + if (ov->bclass == BCL_OV511) + reg_w(ov, 0x11, 0x00); + else + warn("SAA7111A not yet supported with OV518/OV518+"); + + return 0; +} + +/* This initializes the OV511/OV511+ and the sensor */ +static int +ov511_configure(struct usb_ov511 *ov) +{ + static struct ov511_regvals aRegvalsInit511[] = { + { OV511_REG_BUS, R51x_SYS_RESET, 0x7f }, + { OV511_REG_BUS, R51x_SYS_INIT, 0x01 }, + { OV511_REG_BUS, R51x_SYS_RESET, 0x7f }, + { OV511_REG_BUS, R51x_SYS_INIT, 0x01 }, + { OV511_REG_BUS, R51x_SYS_RESET, 0x3f }, + { OV511_REG_BUS, R51x_SYS_INIT, 0x01 }, + { OV511_REG_BUS, R51x_SYS_RESET, 0x3d }, + { OV511_DONE_BUS, 0x0, 0x00}, + }; + + static struct ov511_regvals aRegvalsNorm511[] = { + { OV511_REG_BUS, R511_DRAM_FLOW_CTL, 0x01 }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x00 }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x02 }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x00 }, + { OV511_REG_BUS, R511_FIFO_OPTS, 0x1f }, + { OV511_REG_BUS, R511_COMP_EN, 0x00 }, + { OV511_REG_BUS, R511_COMP_LUT_EN, 0x03 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + static struct ov511_regvals aRegvalsNorm511Plus[] = { + { OV511_REG_BUS, R511_DRAM_FLOW_CTL, 0xff }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x00 }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x02 }, + { OV511_REG_BUS, R51x_SYS_SNAP, 0x00 }, + { OV511_REG_BUS, R511_FIFO_OPTS, 0xff }, + { OV511_REG_BUS, R511_COMP_EN, 0x00 }, + { OV511_REG_BUS, R511_COMP_LUT_EN, 0x03 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + PDEBUG(4, ""); + + ov->customid = reg_r(ov, R511_SYS_CUST_ID); + if (ov->customid < 0) { + err("Unable to read camera bridge registers"); + goto error; + } + + PDEBUG (1, "CustomID = %d", ov->customid); + ov->desc = symbolic(camlist, ov->customid); + info("model: %s", ov->desc); + + if (0 == strcmp(ov->desc, NOT_DEFINED_STR)) { + err("Camera type (%d) not recognized", ov->customid); + err("Please notify " EMAIL " of the name,"); + err("manufacturer, model, and this number of your camera."); + err("Also include the output of the detection process."); + } + + if (ov->customid == 70) /* USB Life TV (PAL/SECAM) */ + ov->pal = 1; + + if (write_regvals(ov, aRegvalsInit511)) + goto error; + + if (ov->led_policy == LED_OFF || ov->led_policy == LED_AUTO) + ov51x_led_control(ov, 0); + + /* The OV511+ has undocumented bits in the flow control register. + * Setting it to 0xff fixes the corruption with moving objects. */ + if (ov->bridge == BRG_OV511) { + if (write_regvals(ov, aRegvalsNorm511)) + goto error; + } else if (ov->bridge == BRG_OV511PLUS) { + if (write_regvals(ov, aRegvalsNorm511Plus)) + goto error; + } else { + err("Invalid bridge"); + } + + if (ov511_init_compression(ov)) + goto error; + + ov->packet_numbering = 1; + ov511_set_packet_size(ov, 0); + + ov->snap_enabled = snapshot; + + /* Test for 7xx0 */ + PDEBUG(3, "Testing for 0V7xx0"); + ov->primary_i2c_slave = OV7xx0_SID; + if (ov51x_set_slave_ids(ov, OV7xx0_SID) < 0) + goto error; + + if (i2c_w(ov, 0x12, 0x80) < 0) { + /* Test for 6xx0 */ + PDEBUG(3, "Testing for 0V6xx0"); + ov->primary_i2c_slave = OV6xx0_SID; + if (ov51x_set_slave_ids(ov, OV6xx0_SID) < 0) + goto error; + + if (i2c_w(ov, 0x12, 0x80) < 0) { + /* Test for 8xx0 */ + PDEBUG(3, "Testing for 0V8xx0"); + ov->primary_i2c_slave = OV8xx0_SID; + if (ov51x_set_slave_ids(ov, OV8xx0_SID) < 0) + goto error; + + if (i2c_w(ov, 0x12, 0x80) < 0) { + /* Test for SAA7111A */ + PDEBUG(3, "Testing for SAA7111A"); + ov->primary_i2c_slave = SAA7111A_SID; + if (ov51x_set_slave_ids(ov, SAA7111A_SID) < 0) + goto error; + + if (i2c_w(ov, 0x0d, 0x00) < 0) { + /* Test for KS0127 */ + PDEBUG(3, "Testing for KS0127"); + ov->primary_i2c_slave = KS0127_SID; + if (ov51x_set_slave_ids(ov, KS0127_SID) < 0) + goto error; + + if (i2c_w(ov, 0x10, 0x00) < 0) { + err("Can't determine sensor slave IDs"); + goto error; + } else { + if (ks0127_configure(ov) < 0) { + err("Failed to configure KS0127"); + goto error; + } + } + } else { + if (saa7111a_configure(ov) < 0) { + err("Failed to configure SAA7111A"); + goto error; + } + } + } else { + err("Detected unsupported OV8xx0 sensor"); + goto error; + } + } else { + if (ov6xx0_configure(ov) < 0) { + err("Failed to configure OV6xx0"); + goto error; + } + } + } else { + if (ov7xx0_configure(ov) < 0) { + err("Failed to configure OV7xx0"); + goto error; + } + } + + return 0; + +error: + err("OV511 Config failed"); + + return -EBUSY; +} + +/* This initializes the OV518/OV518+ and the sensor */ +static int +ov518_configure(struct usb_ov511 *ov) +{ + /* For 518 and 518+ */ + static struct ov511_regvals aRegvalsInit518[] = { + { OV511_REG_BUS, R51x_SYS_RESET, 0x40 }, + { OV511_REG_BUS, R51x_SYS_INIT, 0xe1 }, + { OV511_REG_BUS, R51x_SYS_RESET, 0x3e }, + { OV511_REG_BUS, R51x_SYS_INIT, 0xe1 }, + { OV511_REG_BUS, R51x_SYS_RESET, 0x00 }, + { OV511_REG_BUS, R51x_SYS_INIT, 0xe1 }, + { OV511_REG_BUS, 0x46, 0x00 }, + { OV511_REG_BUS, 0x5d, 0x03 }, + { OV511_DONE_BUS, 0x0, 0x00}, + }; + + static struct ov511_regvals aRegvalsNorm518[] = { + { OV511_REG_BUS, R51x_SYS_SNAP, 0x02 }, /* Reset */ + { OV511_REG_BUS, R51x_SYS_SNAP, 0x01 }, /* Enable */ + { OV511_REG_BUS, 0x31, 0x0f }, + { OV511_REG_BUS, 0x5d, 0x03 }, + { OV511_REG_BUS, 0x24, 0x9f }, + { OV511_REG_BUS, 0x25, 0x90 }, + { OV511_REG_BUS, 0x20, 0x00 }, + { OV511_REG_BUS, 0x51, 0x04 }, + { OV511_REG_BUS, 0x71, 0x19 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + static struct ov511_regvals aRegvalsNorm518Plus[] = { + { OV511_REG_BUS, R51x_SYS_SNAP, 0x02 }, /* Reset */ + { OV511_REG_BUS, R51x_SYS_SNAP, 0x01 }, /* Enable */ + { OV511_REG_BUS, 0x31, 0x0f }, + { OV511_REG_BUS, 0x5d, 0x03 }, + { OV511_REG_BUS, 0x24, 0x9f }, + { OV511_REG_BUS, 0x25, 0x90 }, + { OV511_REG_BUS, 0x20, 0x60 }, + { OV511_REG_BUS, 0x51, 0x02 }, + { OV511_REG_BUS, 0x71, 0x19 }, + { OV511_REG_BUS, 0x40, 0xff }, + { OV511_REG_BUS, 0x41, 0x42 }, + { OV511_REG_BUS, 0x46, 0x00 }, + { OV511_REG_BUS, 0x33, 0x04 }, + { OV511_REG_BUS, 0x21, 0x19 }, + { OV511_REG_BUS, 0x3f, 0x10 }, + { OV511_DONE_BUS, 0x0, 0x00 }, + }; + + PDEBUG(4, ""); + + /* First 5 bits of custom ID reg are a revision ID on OV518 */ + info("Device revision %d", 0x1F & reg_r(ov, R511_SYS_CUST_ID)); + + /* Give it the default description */ + ov->desc = symbolic(camlist, 0); + + if (write_regvals(ov, aRegvalsInit518)) + goto error; + + /* Set LED GPIO pin to output mode */ + if (reg_w_mask(ov, 0x57, 0x00, 0x02) < 0) + goto error; + + /* LED is off by default with OV518; have to explicitly turn it on */ + if (ov->led_policy == LED_OFF || ov->led_policy == LED_AUTO) + ov51x_led_control(ov, 0); + else + ov51x_led_control(ov, 1); + + /* Don't require compression if dumppix is enabled; otherwise it's + * required. OV518 has no uncompressed mode, to save RAM. */ + if (!dumppix && !ov->compress) { + ov->compress = 1; + warn("Compression required with OV518...enabling"); + } + + if (ov->bridge == BRG_OV518) { + if (write_regvals(ov, aRegvalsNorm518)) + goto error; + } else if (ov->bridge == BRG_OV518PLUS) { + if (write_regvals(ov, aRegvalsNorm518Plus)) + goto error; + } else { + err("Invalid bridge"); + } + + if (reg_w(ov, 0x2f, 0x80) < 0) + goto error; + + if (ov518_init_compression(ov)) + goto error; + + if (ov->bridge == BRG_OV518) + { + struct usb_interface *ifp; + struct usb_host_interface *alt; + __u16 mxps = 0; + + ifp = usb_ifnum_to_if(ov->dev, 0); + if (ifp) { + alt = usb_altnum_to_altsetting(ifp, 7); + if (alt) + mxps = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize); + } + + /* Some OV518s have packet numbering by default, some don't */ + if (mxps == 897) + ov->packet_numbering = 1; + else + ov->packet_numbering = 0; + } else { + /* OV518+ has packet numbering turned on by default */ + ov->packet_numbering = 1; + } + + ov518_set_packet_size(ov, 0); + + ov->snap_enabled = snapshot; + + /* Test for 76xx */ + ov->primary_i2c_slave = OV7xx0_SID; + if (ov51x_set_slave_ids(ov, OV7xx0_SID) < 0) + goto error; + + /* The OV518 must be more aggressive about sensor detection since + * I2C write will never fail if the sensor is not present. We have + * to try to initialize the sensor to detect its presence */ + + if (init_ov_sensor(ov) < 0) { + /* Test for 6xx0 */ + ov->primary_i2c_slave = OV6xx0_SID; + if (ov51x_set_slave_ids(ov, OV6xx0_SID) < 0) + goto error; + + if (init_ov_sensor(ov) < 0) { + /* Test for 8xx0 */ + ov->primary_i2c_slave = OV8xx0_SID; + if (ov51x_set_slave_ids(ov, OV8xx0_SID) < 0) + goto error; + + if (init_ov_sensor(ov) < 0) { + err("Can't determine sensor slave IDs"); + goto error; + } else { + err("Detected unsupported OV8xx0 sensor"); + goto error; + } + } else { + if (ov6xx0_configure(ov) < 0) { + err("Failed to configure OV6xx0"); + goto error; + } + } + } else { + if (ov7xx0_configure(ov) < 0) { + err("Failed to configure OV7xx0"); + goto error; + } + } + + ov->maxwidth = 352; + ov->maxheight = 288; + + // The OV518 cannot go as low as the sensor can + ov->minwidth = 160; + ov->minheight = 120; + + return 0; + +error: + err("OV518 Config failed"); + + return -EBUSY; +} + +/**************************************************************************** + * sysfs + ***************************************************************************/ + +static inline struct usb_ov511 *cd_to_ov(struct class_device *cd) +{ + struct video_device *vdev = to_video_device(cd); + return video_get_drvdata(vdev); +} + +static ssize_t show_custom_id(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + return sprintf(buf, "%d\n", ov->customid); +} +static CLASS_DEVICE_ATTR(custom_id, S_IRUGO, show_custom_id, NULL); + +static ssize_t show_model(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + return sprintf(buf, "%s\n", ov->desc); +} +static CLASS_DEVICE_ATTR(model, S_IRUGO, show_model, NULL); + +static ssize_t show_bridge(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + return sprintf(buf, "%s\n", symbolic(brglist, ov->bridge)); +} +static CLASS_DEVICE_ATTR(bridge, S_IRUGO, show_bridge, NULL); + +static ssize_t show_sensor(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + return sprintf(buf, "%s\n", symbolic(senlist, ov->sensor)); +} +static CLASS_DEVICE_ATTR(sensor, S_IRUGO, show_sensor, NULL); + +static ssize_t show_brightness(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + unsigned short x; + + if (!ov->dev) + return -ENODEV; + sensor_get_brightness(ov, &x); + return sprintf(buf, "%d\n", x >> 8); +} +static CLASS_DEVICE_ATTR(brightness, S_IRUGO, show_brightness, NULL); + +static ssize_t show_saturation(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + unsigned short x; + + if (!ov->dev) + return -ENODEV; + sensor_get_saturation(ov, &x); + return sprintf(buf, "%d\n", x >> 8); +} +static CLASS_DEVICE_ATTR(saturation, S_IRUGO, show_saturation, NULL); + +static ssize_t show_contrast(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + unsigned short x; + + if (!ov->dev) + return -ENODEV; + sensor_get_contrast(ov, &x); + return sprintf(buf, "%d\n", x >> 8); +} +static CLASS_DEVICE_ATTR(contrast, S_IRUGO, show_contrast, NULL); + +static ssize_t show_hue(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + unsigned short x; + + if (!ov->dev) + return -ENODEV; + sensor_get_hue(ov, &x); + return sprintf(buf, "%d\n", x >> 8); +} +static CLASS_DEVICE_ATTR(hue, S_IRUGO, show_hue, NULL); + +static ssize_t show_exposure(struct class_device *cd, char *buf) +{ + struct usb_ov511 *ov = cd_to_ov(cd); + unsigned char exp = 0; + + if (!ov->dev) + return -ENODEV; + sensor_get_exposure(ov, &exp); + return sprintf(buf, "%d\n", exp >> 8); +} +static CLASS_DEVICE_ATTR(exposure, S_IRUGO, show_exposure, NULL); + +static void ov_create_sysfs(struct video_device *vdev) +{ + video_device_create_file(vdev, &class_device_attr_custom_id); + video_device_create_file(vdev, &class_device_attr_model); + video_device_create_file(vdev, &class_device_attr_bridge); + video_device_create_file(vdev, &class_device_attr_sensor); + video_device_create_file(vdev, &class_device_attr_brightness); + video_device_create_file(vdev, &class_device_attr_saturation); + video_device_create_file(vdev, &class_device_attr_contrast); + video_device_create_file(vdev, &class_device_attr_hue); + video_device_create_file(vdev, &class_device_attr_exposure); +} + +/**************************************************************************** + * USB routines + ***************************************************************************/ + +static int +ov51x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_interface_descriptor *idesc; + struct usb_ov511 *ov; + int i; + + PDEBUG(1, "probing for device..."); + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + idesc = &intf->cur_altsetting->desc; + + if (idesc->bInterfaceClass != 0xFF) + return -ENODEV; + if (idesc->bInterfaceSubClass != 0x00) + return -ENODEV; + + if ((ov = kzalloc(sizeof(*ov), GFP_KERNEL)) == NULL) { + err("couldn't kmalloc ov struct"); + goto error_out; + } + + ov->dev = dev; + ov->iface = idesc->bInterfaceNumber; + ov->led_policy = led; + ov->compress = compress; + ov->lightfreq = lightfreq; + ov->num_inputs = 1; /* Video decoder init functs. change this */ + ov->stop_during_set = !fastset; + ov->backlight = backlight; + ov->mirror = mirror; + ov->auto_brt = autobright; + ov->auto_gain = autogain; + ov->auto_exp = autoexp; + + switch (le16_to_cpu(dev->descriptor.idProduct)) { + case PROD_OV511: + ov->bridge = BRG_OV511; + ov->bclass = BCL_OV511; + break; + case PROD_OV511PLUS: + ov->bridge = BRG_OV511PLUS; + ov->bclass = BCL_OV511; + break; + case PROD_OV518: + ov->bridge = BRG_OV518; + ov->bclass = BCL_OV518; + break; + case PROD_OV518PLUS: + ov->bridge = BRG_OV518PLUS; + ov->bclass = BCL_OV518; + break; + case PROD_ME2CAM: + if (le16_to_cpu(dev->descriptor.idVendor) != VEND_MATTEL) + goto error; + ov->bridge = BRG_OV511PLUS; + ov->bclass = BCL_OV511; + break; + default: + err("Unknown product ID 0x%04x", le16_to_cpu(dev->descriptor.idProduct)); + goto error; + } + + info("USB %s video device found", symbolic(brglist, ov->bridge)); + + init_waitqueue_head(&ov->wq); + + mutex_init(&ov->lock); /* to 1 == available */ + mutex_init(&ov->buf_lock); + mutex_init(&ov->i2c_lock); + mutex_init(&ov->cbuf_lock); + + ov->buf_state = BUF_NOT_ALLOCATED; + + if (usb_make_path(dev, ov->usb_path, OV511_USB_PATH_LEN) < 0) { + err("usb_make_path error"); + goto error; + } + + /* Allocate control transfer buffer. */ + /* Must be kmalloc()'ed, for DMA compatibility */ + ov->cbuf = kmalloc(OV511_CBUF_SIZE, GFP_KERNEL); + if (!ov->cbuf) + goto error; + + if (ov->bclass == BCL_OV518) { + if (ov518_configure(ov) < 0) + goto error; + } else { + if (ov511_configure(ov) < 0) + goto error; + } + + for (i = 0; i < OV511_NUMFRAMES; i++) { + ov->frame[i].framenum = i; + init_waitqueue_head(&ov->frame[i].wq); + } + + for (i = 0; i < OV511_NUMSBUF; i++) { + ov->sbuf[i].ov = ov; + spin_lock_init(&ov->sbuf[i].lock); + ov->sbuf[i].n = i; + } + + /* Unnecessary? (This is done on open(). Need to make sure variables + * are properly initialized without this before removing it, though). */ + if (ov51x_set_default_params(ov) < 0) + goto error; + +#ifdef OV511_DEBUG + if (dump_bridge) { + if (ov->bclass == BCL_OV511) + ov511_dump_regs(ov); + else + ov518_dump_regs(ov); + } +#endif + + ov->vdev = video_device_alloc(); + if (!ov->vdev) + goto error; + + memcpy(ov->vdev, &vdev_template, sizeof(*ov->vdev)); + ov->vdev->dev = &dev->dev; + video_set_drvdata(ov->vdev, ov); + + for (i = 0; i < OV511_MAX_UNIT_VIDEO; i++) { + /* Minor 0 cannot be specified; assume user wants autodetect */ + if (unit_video[i] == 0) + break; + + if (video_register_device(ov->vdev, VFL_TYPE_GRABBER, + unit_video[i]) >= 0) { + break; + } + } + + /* Use the next available one */ + if ((ov->vdev->minor == -1) && + video_register_device(ov->vdev, VFL_TYPE_GRABBER, -1) < 0) { + err("video_register_device failed"); + goto error; + } + + info("Device at %s registered to minor %d", ov->usb_path, + ov->vdev->minor); + + usb_set_intfdata(intf, ov); + ov_create_sysfs(ov->vdev); + return 0; + +error: + if (ov->vdev) { + if (-1 == ov->vdev->minor) + video_device_release(ov->vdev); + else + video_unregister_device(ov->vdev); + ov->vdev = NULL; + } + + if (ov->cbuf) { + mutex_lock(&ov->cbuf_lock); + kfree(ov->cbuf); + ov->cbuf = NULL; + mutex_unlock(&ov->cbuf_lock); + } + + kfree(ov); + ov = NULL; + +error_out: + err("Camera initialization failed"); + return -EIO; +} + +static void +ov51x_disconnect(struct usb_interface *intf) +{ + struct usb_ov511 *ov = usb_get_intfdata(intf); + int n; + + PDEBUG(3, ""); + + usb_set_intfdata (intf, NULL); + + if (!ov) + return; + + if (ov->vdev) + video_unregister_device(ov->vdev); + + for (n = 0; n < OV511_NUMFRAMES; n++) + ov->frame[n].grabstate = FRAME_ERROR; + + ov->curframe = -1; + + /* This will cause the process to request another frame */ + for (n = 0; n < OV511_NUMFRAMES; n++) + wake_up_interruptible(&ov->frame[n].wq); + + wake_up_interruptible(&ov->wq); + + ov->streaming = 0; + ov51x_unlink_isoc(ov); + + ov->dev = NULL; + + /* Free the memory */ + if (ov && !ov->user) { + mutex_lock(&ov->cbuf_lock); + kfree(ov->cbuf); + ov->cbuf = NULL; + mutex_unlock(&ov->cbuf_lock); + + ov51x_dealloc(ov); + kfree(ov); + ov = NULL; + } + + PDEBUG(3, "Disconnect complete"); +} + +static struct usb_driver ov511_driver = { + .name = "ov511", + .id_table = device_table, + .probe = ov51x_probe, + .disconnect = ov51x_disconnect +}; + +/**************************************************************************** + * + * Module routines + * + ***************************************************************************/ + +static int __init +usb_ov511_init(void) +{ + int retval; + + retval = usb_register(&ov511_driver); + if (retval) + goto out; + + info(DRIVER_VERSION " : " DRIVER_DESC); + +out: + return retval; +} + +static void __exit +usb_ov511_exit(void) +{ + usb_deregister(&ov511_driver); + info("driver deregistered"); + +} + +module_init(usb_ov511_init); +module_exit(usb_ov511_exit); + diff --git a/linux/drivers/media/video/ov511.h b/linux/drivers/media/video/ov511.h new file mode 100644 index 000000000..63da4e8d8 --- /dev/null +++ b/linux/drivers/media/video/ov511.h @@ -0,0 +1,569 @@ +#ifndef __LINUX_OV511_H +#define __LINUX_OV511_H + +#include <asm/uaccess.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/smp_lock.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +#define OV511_DEBUG /* Turn on debug messages */ + +#ifdef OV511_DEBUG + #define PDEBUG(level, fmt, args...) \ + if (debug >= (level)) info("[%s:%d] " fmt, \ + __FUNCTION__, __LINE__ , ## args) +#else + #define PDEBUG(level, fmt, args...) do {} while(0) +#endif + +/* This macro restricts an int variable to an inclusive range */ +#define RESTRICT_TO_RANGE(v,mi,ma) { \ + if ((v) < (mi)) (v) = (mi); \ + else if ((v) > (ma)) (v) = (ma); \ +} + +/* --------------------------------- */ +/* DEFINES FOR OV511 AND OTHER CHIPS */ +/* --------------------------------- */ + +/* USB IDs */ +#define VEND_OMNIVISION 0x05A9 +#define PROD_OV511 0x0511 +#define PROD_OV511PLUS 0xA511 +#define PROD_OV518 0x0518 +#define PROD_OV518PLUS 0xA518 + +#define VEND_MATTEL 0x0813 +#define PROD_ME2CAM 0x0002 + +/* --------------------------------- */ +/* OV51x REGISTER MNEMONICS */ +/* --------------------------------- */ + +/* Camera interface register numbers */ +#define R511_CAM_DELAY 0x10 +#define R511_CAM_EDGE 0x11 +#define R511_CAM_PXCNT 0x12 +#define R511_CAM_LNCNT 0x13 +#define R511_CAM_PXDIV 0x14 +#define R511_CAM_LNDIV 0x15 +#define R511_CAM_UV_EN 0x16 +#define R511_CAM_LINE_MODE 0x17 +#define R511_CAM_OPTS 0x18 + +/* Snapshot mode camera interface register numbers */ +#define R511_SNAP_FRAME 0x19 +#define R511_SNAP_PXCNT 0x1A +#define R511_SNAP_LNCNT 0x1B +#define R511_SNAP_PXDIV 0x1C +#define R511_SNAP_LNDIV 0x1D +#define R511_SNAP_UV_EN 0x1E +#define R511_SNAP_OPTS 0x1F + +/* DRAM register numbers */ +#define R511_DRAM_FLOW_CTL 0x20 +#define R511_DRAM_ARCP 0x21 +#define R511_DRAM_MRC 0x22 +#define R511_DRAM_RFC 0x23 + +/* ISO FIFO register numbers */ +#define R51x_FIFO_PSIZE 0x30 /* 2 bytes wide w/ OV518(+) */ +#define R511_FIFO_OPTS 0x31 + +/* Parallel IO register numbers */ +#define R511_PIO_OPTS 0x38 +#define R511_PIO_DATA 0x39 +#define R511_PIO_BIST 0x3E +#define R518_GPIO_IN 0x55 /* OV518(+) only */ +#define R518_GPIO_OUT 0x56 /* OV518(+) only */ +#define R518_GPIO_CTL 0x57 /* OV518(+) only */ +#define R518_GPIO_PULSE_IN 0x58 /* OV518(+) only */ +#define R518_GPIO_PULSE_CLEAR 0x59 /* OV518(+) only */ +#define R518_GPIO_PULSE_POL 0x5a /* OV518(+) only */ +#define R518_GPIO_PULSE_EN 0x5b /* OV518(+) only */ +#define R518_GPIO_RESET 0x5c /* OV518(+) only */ + +/* I2C registers */ +#define R511_I2C_CTL 0x40 +#define R518_I2C_CTL 0x47 /* OV518(+) only */ +#define R51x_I2C_W_SID 0x41 +#define R51x_I2C_SADDR_3 0x42 +#define R51x_I2C_SADDR_2 0x43 +#define R51x_I2C_R_SID 0x44 +#define R51x_I2C_DATA 0x45 +#define R51x_I2C_CLOCK 0x46 +#define R51x_I2C_TIMEOUT 0x47 + +/* I2C snapshot registers */ +#define R511_SI2C_SADDR_3 0x48 +#define R511_SI2C_DATA 0x49 + +/* System control registers */ +#define R51x_SYS_RESET 0x50 + /* Reset type definitions */ +#define OV511_RESET_UDC 0x01 +#define OV511_RESET_I2C 0x02 +#define OV511_RESET_FIFO 0x04 +#define OV511_RESET_OMNICE 0x08 +#define OV511_RESET_DRAM 0x10 +#define OV511_RESET_CAM_INT 0x20 +#define OV511_RESET_OV511 0x40 +#define OV511_RESET_NOREGS 0x3F /* All but OV511 & regs */ +#define OV511_RESET_ALL 0x7F + +#define R511_SYS_CLOCK_DIV 0x51 +#define R51x_SYS_SNAP 0x52 +#define R51x_SYS_INIT 0x53 +#define R511_SYS_PWR_CLK 0x54 /* OV511+/OV518(+) only */ +#define R511_SYS_LED_CTL 0x55 /* OV511+ only */ +#define R511_SYS_USER 0x5E +#define R511_SYS_CUST_ID 0x5F + +/* OmniCE (compression) registers */ +#define R511_COMP_PHY 0x70 +#define R511_COMP_PHUV 0x71 +#define R511_COMP_PVY 0x72 +#define R511_COMP_PVUV 0x73 +#define R511_COMP_QHY 0x74 +#define R511_COMP_QHUV 0x75 +#define R511_COMP_QVY 0x76 +#define R511_COMP_QVUV 0x77 +#define R511_COMP_EN 0x78 +#define R511_COMP_LUT_EN 0x79 +#define R511_COMP_LUT_BEGIN 0x80 + +/* --------------------------------- */ +/* ALTERNATE NUMBERS */ +/* --------------------------------- */ + +/* Alternate numbers for various max packet sizes (OV511 only) */ +#define OV511_ALT_SIZE_992 0 +#define OV511_ALT_SIZE_993 1 +#define OV511_ALT_SIZE_768 2 +#define OV511_ALT_SIZE_769 3 +#define OV511_ALT_SIZE_512 4 +#define OV511_ALT_SIZE_513 5 +#define OV511_ALT_SIZE_257 6 +#define OV511_ALT_SIZE_0 7 + +/* Alternate numbers for various max packet sizes (OV511+ only) */ +#define OV511PLUS_ALT_SIZE_0 0 +#define OV511PLUS_ALT_SIZE_33 1 +#define OV511PLUS_ALT_SIZE_129 2 +#define OV511PLUS_ALT_SIZE_257 3 +#define OV511PLUS_ALT_SIZE_385 4 +#define OV511PLUS_ALT_SIZE_513 5 +#define OV511PLUS_ALT_SIZE_769 6 +#define OV511PLUS_ALT_SIZE_961 7 + +/* Alternate numbers for various max packet sizes (OV518(+) only) */ +#define OV518_ALT_SIZE_0 0 +#define OV518_ALT_SIZE_128 1 +#define OV518_ALT_SIZE_256 2 +#define OV518_ALT_SIZE_384 3 +#define OV518_ALT_SIZE_512 4 +#define OV518_ALT_SIZE_640 5 +#define OV518_ALT_SIZE_768 6 +#define OV518_ALT_SIZE_896 7 + +/* --------------------------------- */ +/* OV7610 REGISTER MNEMONICS */ +/* --------------------------------- */ + +/* OV7610 registers */ +#define OV7610_REG_GAIN 0x00 /* gain setting (5:0) */ +#define OV7610_REG_BLUE 0x01 /* blue channel balance */ +#define OV7610_REG_RED 0x02 /* red channel balance */ +#define OV7610_REG_SAT 0x03 /* saturation */ + /* 04 reserved */ +#define OV7610_REG_CNT 0x05 /* Y contrast */ +#define OV7610_REG_BRT 0x06 /* Y brightness */ + /* 08-0b reserved */ +#define OV7610_REG_BLUE_BIAS 0x0C /* blue channel bias (5:0) */ +#define OV7610_REG_RED_BIAS 0x0D /* read channel bias (5:0) */ +#define OV7610_REG_GAMMA_COEFF 0x0E /* gamma settings */ +#define OV7610_REG_WB_RANGE 0x0F /* AEC/ALC/S-AWB settings */ +#define OV7610_REG_EXP 0x10 /* manual exposure setting */ +#define OV7610_REG_CLOCK 0x11 /* polarity/clock prescaler */ +#define OV7610_REG_COM_A 0x12 /* misc common regs */ +#define OV7610_REG_COM_B 0x13 /* misc common regs */ +#define OV7610_REG_COM_C 0x14 /* misc common regs */ +#define OV7610_REG_COM_D 0x15 /* misc common regs */ +#define OV7610_REG_FIELD_DIVIDE 0x16 /* field interval/mode settings */ +#define OV7610_REG_HWIN_START 0x17 /* horizontal window start */ +#define OV7610_REG_HWIN_END 0x18 /* horizontal window end */ +#define OV7610_REG_VWIN_START 0x19 /* vertical window start */ +#define OV7610_REG_VWIN_END 0x1A /* vertical window end */ +#define OV7610_REG_PIXEL_SHIFT 0x1B /* pixel shift */ +#define OV7610_REG_ID_HIGH 0x1C /* manufacturer ID MSB */ +#define OV7610_REG_ID_LOW 0x1D /* manufacturer ID LSB */ + /* 0e-0f reserved */ +#define OV7610_REG_COM_E 0x20 /* misc common regs */ +#define OV7610_REG_YOFFSET 0x21 /* Y channel offset */ +#define OV7610_REG_UOFFSET 0x22 /* U channel offset */ + /* 23 reserved */ +#define OV7610_REG_ECW 0x24 /* Exposure white level for AEC */ +#define OV7610_REG_ECB 0x25 /* Exposure black level for AEC */ +#define OV7610_REG_COM_F 0x26 /* misc settings */ +#define OV7610_REG_COM_G 0x27 /* misc settings */ +#define OV7610_REG_COM_H 0x28 /* misc settings */ +#define OV7610_REG_COM_I 0x29 /* misc settings */ +#define OV7610_REG_FRAMERATE_H 0x2A /* frame rate MSB + misc */ +#define OV7610_REG_FRAMERATE_L 0x2B /* frame rate LSB */ +#define OV7610_REG_ALC 0x2C /* Auto Level Control settings */ +#define OV7610_REG_COM_J 0x2D /* misc settings */ +#define OV7610_REG_VOFFSET 0x2E /* V channel offset adjustment */ +#define OV7610_REG_ARRAY_BIAS 0x2F /* Array bias -- don't change */ + /* 30-32 reserved */ +#define OV7610_REG_YGAMMA 0x33 /* misc gamma settings (7:6) */ +#define OV7610_REG_BIAS_ADJUST 0x34 /* misc bias settings */ +#define OV7610_REG_COM_L 0x35 /* misc settings */ + /* 36-37 reserved */ +#define OV7610_REG_COM_K 0x38 /* misc registers */ + +/* --------------------------------- */ +/* I2C ADDRESSES */ +/* --------------------------------- */ + +#define OV7xx0_SID 0x42 +#define OV6xx0_SID 0xC0 +#define OV8xx0_SID 0xA0 +#define KS0127_SID 0xD8 +#define SAA7111A_SID 0x48 + +/* --------------------------------- */ +/* MISCELLANEOUS DEFINES */ +/* --------------------------------- */ + +#define I2C_CLOCK_PRESCALER 0x03 + +#define FRAMES_PER_DESC 10 /* FIXME - What should this be? */ +#define MAX_FRAME_SIZE_PER_DESC 993 /* For statically allocated stuff */ +#define PIXELS_PER_SEG 256 /* Pixels per segment */ + +#define OV511_ENDPOINT_ADDRESS 1 /* Isoc endpoint number */ + +#define OV511_NUMFRAMES 2 +#if OV511_NUMFRAMES > VIDEO_MAX_FRAME + #error "OV511_NUMFRAMES is too high" +#endif + +#define OV511_NUMSBUF 2 + +/* Control transfers use up to 4 bytes */ +#define OV511_CBUF_SIZE 4 + +/* Size of usb_make_path() buffer */ +#define OV511_USB_PATH_LEN 64 + +/* Bridge types */ +enum { + BRG_UNKNOWN, + BRG_OV511, + BRG_OV511PLUS, + BRG_OV518, + BRG_OV518PLUS, +}; + +/* Bridge classes */ +enum { + BCL_UNKNOWN, + BCL_OV511, + BCL_OV518, +}; + +/* Sensor types */ +enum { + SEN_UNKNOWN, + SEN_OV76BE, + SEN_OV7610, + SEN_OV7620, + SEN_OV7620AE, + SEN_OV6620, + SEN_OV6630, + SEN_OV6630AE, + SEN_OV6630AF, + SEN_OV8600, + SEN_KS0127, + SEN_KS0127B, + SEN_SAA7111A, +}; + +enum { + STATE_SCANNING, /* Scanning for start */ + STATE_HEADER, /* Parsing header */ + STATE_LINES, /* Parsing lines */ +}; + +/* Buffer states */ +enum { + BUF_NOT_ALLOCATED, + BUF_ALLOCATED, +}; + +/* --------- Definition of ioctl interface --------- */ + +#define OV511_INTERFACE_VER 101 + +/* LED options */ +enum { + LED_OFF, + LED_ON, + LED_AUTO, +}; + +/* Raw frame formats */ +enum { + RAWFMT_INVALID, + RAWFMT_YUV400, + RAWFMT_YUV420, + RAWFMT_YUV422, + RAWFMT_GBR422, +}; + +struct ov511_i2c_struct { + unsigned char slave; /* Write slave ID (read ID - 1) */ + unsigned char reg; /* Index of register */ + unsigned char value; /* User sets this w/ write, driver does w/ read */ + unsigned char mask; /* Bits to be changed. Not used with read ops */ +}; + +/* ioctls */ +#define OV511IOC_WI2C _IOW('v', BASE_VIDIOCPRIVATE + 5, \ + struct ov511_i2c_struct) +#define OV511IOC_RI2C _IOWR('v', BASE_VIDIOCPRIVATE + 6, \ + struct ov511_i2c_struct) +/* ------------- End IOCTL interface -------------- */ + +struct usb_ov511; /* Forward declaration */ + +struct ov511_sbuf { + struct usb_ov511 *ov; + unsigned char *data; + struct urb *urb; + spinlock_t lock; + int n; +}; + +enum { + FRAME_UNUSED, /* Unused (no MCAPTURE) */ + FRAME_READY, /* Ready to start grabbing */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_ERROR, /* Something bad happened while processing */ +}; + +struct ov511_regvals { + enum { + OV511_DONE_BUS, + OV511_REG_BUS, + OV511_I2C_BUS, + } bus; + unsigned char reg; + unsigned char val; +}; + +struct ov511_frame { + int framenum; /* Index of this frame */ + unsigned char *data; /* Frame buffer */ + unsigned char *tempdata; /* Temp buffer for multi-stage conversions */ + unsigned char *rawdata; /* Raw camera data buffer */ + unsigned char *compbuf; /* Temp buffer for decompressor */ + + int depth; /* Bytes per pixel */ + int width; /* Width application is expecting */ + int height; /* Height application is expecting */ + + int rawwidth; /* Actual width of frame sent from camera */ + int rawheight; /* Actual height of frame sent from camera */ + + int sub_flag; /* Sub-capture mode for this frame? */ + unsigned int format; /* Format for this frame */ + int compressed; /* Is frame compressed? */ + + volatile int grabstate; /* State of grabbing */ + int scanstate; /* State of scanning */ + + int bytes_recvd; /* Number of image bytes received from camera */ + + long bytes_read; /* Amount that has been read() */ + + wait_queue_head_t wq; /* Processes waiting */ + + int snapshot; /* True if frame was a snapshot */ +}; + +#define DECOMP_INTERFACE_VER 4 + +/* Compression module operations */ +struct ov51x_decomp_ops { + int (*decomp_400)(unsigned char *, unsigned char *, unsigned char *, + int, int, int); + int (*decomp_420)(unsigned char *, unsigned char *, unsigned char *, + int, int, int); + int (*decomp_422)(unsigned char *, unsigned char *, unsigned char *, + int, int, int); + struct module *owner; +}; + +struct usb_ov511 { + struct video_device *vdev; + struct usb_device *dev; + + int customid; + char *desc; + unsigned char iface; + char usb_path[OV511_USB_PATH_LEN]; + + /* Determined by sensor type */ + int maxwidth; + int maxheight; + int minwidth; + int minheight; + + int brightness; + int colour; + int contrast; + int hue; + int whiteness; + int exposure; + int auto_brt; /* Auto brightness enabled flag */ + int auto_gain; /* Auto gain control enabled flag */ + int auto_exp; /* Auto exposure enabled flag */ + int backlight; /* Backlight exposure algorithm flag */ + int mirror; /* Image is reversed horizontally */ + + int led_policy; /* LED: off|on|auto; OV511+ only */ + + struct mutex lock; /* Serializes user-accessible operations */ + int user; /* user count for exclusive use */ + + int streaming; /* Are we streaming Isochronous? */ + int grabbing; /* Are we grabbing? */ + + int compress; /* Should the next frame be compressed? */ + int compress_inited; /* Are compression params uploaded? */ + + int lightfreq; /* Power (lighting) frequency */ + int bandfilt; /* Banding filter enabled flag */ + + unsigned char *fbuf; /* Videodev buffer area */ + unsigned char *tempfbuf; /* Temporary (intermediate) buffer area */ + unsigned char *rawfbuf; /* Raw camera data buffer area */ + + int sub_flag; /* Pix Array subcapture on flag */ + int subx; /* Pix Array subcapture x offset */ + int suby; /* Pix Array subcapture y offset */ + int subw; /* Pix Array subcapture width */ + int subh; /* Pix Array subcapture height */ + + int curframe; /* Current receiving sbuf */ + struct ov511_frame frame[OV511_NUMFRAMES]; + + struct ov511_sbuf sbuf[OV511_NUMSBUF]; + + wait_queue_head_t wq; /* Processes waiting */ + + int snap_enabled; /* Snapshot mode enabled */ + + int bridge; /* Type of bridge (BRG_*) */ + int bclass; /* Class of bridge (BCL_*) */ + int sensor; /* Type of image sensor chip (SEN_*) */ + + int packet_size; /* Frame size per isoc desc */ + int packet_numbering; /* Is ISO frame numbering enabled? */ + + /* Framebuffer/sbuf management */ + int buf_state; + struct mutex buf_lock; + + struct ov51x_decomp_ops *decomp_ops; + + /* Stop streaming while changing picture settings */ + int stop_during_set; + + int stopped; /* Streaming is temporarily paused */ + + /* Video decoder stuff */ + int input; /* Composite, S-VIDEO, etc... */ + int num_inputs; /* Number of inputs */ + int norm; /* NTSC / PAL / SECAM */ + int has_decoder; /* Device has a video decoder */ + int pal; /* Device is designed for PAL resolution */ + + /* I2C interface */ + struct mutex i2c_lock; /* Protect I2C controller regs */ + unsigned char primary_i2c_slave; /* I2C write id of sensor */ + + /* Control transaction stuff */ + unsigned char *cbuf; /* Buffer for payload */ + struct mutex cbuf_lock; +}; + +/* Used to represent a list of values and their respective symbolic names */ +struct symbolic_list { + int num; + char *name; +}; + +#define NOT_DEFINED_STR "Unknown" + +/* Returns the name of the matching element in the symbolic_list array. The + * end of the list must be marked with an element that has a NULL name. + */ +static inline char * +symbolic(struct symbolic_list list[], int num) +{ + int i; + + for (i = 0; list[i].name != NULL; i++) + if (list[i].num == num) + return (list[i].name); + + return (NOT_DEFINED_STR); +} + +/* Compression stuff */ + +#define OV511_QUANTABLESIZE 64 +#define OV518_QUANTABLESIZE 32 + +#define OV511_YQUANTABLE { \ + 0, 1, 1, 2, 2, 3, 3, 4, \ + 1, 1, 1, 2, 2, 3, 4, 4, \ + 1, 1, 2, 2, 3, 4, 4, 4, \ + 2, 2, 2, 3, 4, 4, 4, 4, \ + 2, 2, 3, 4, 4, 5, 5, 5, \ + 3, 3, 4, 4, 5, 5, 5, 5, \ + 3, 4, 4, 4, 5, 5, 5, 5, \ + 4, 4, 4, 4, 5, 5, 5, 5 \ +} + +#define OV511_UVQUANTABLE { \ + 0, 2, 2, 3, 4, 4, 4, 4, \ + 2, 2, 2, 4, 4, 4, 4, 4, \ + 2, 2, 3, 4, 4, 4, 4, 4, \ + 3, 4, 4, 4, 4, 4, 4, 4, \ + 4, 4, 4, 4, 4, 4, 4, 4, \ + 4, 4, 4, 4, 4, 4, 4, 4, \ + 4, 4, 4, 4, 4, 4, 4, 4, \ + 4, 4, 4, 4, 4, 4, 4, 4 \ +} + +#define OV518_YQUANTABLE { \ + 5, 4, 5, 6, 6, 7, 7, 7, \ + 5, 5, 5, 5, 6, 7, 7, 7, \ + 6, 6, 6, 6, 7, 7, 7, 8, \ + 7, 7, 6, 7, 7, 7, 8, 8 \ +} + +#define OV518_UVQUANTABLE { \ + 6, 6, 6, 7, 7, 7, 7, 7, \ + 6, 6, 6, 7, 7, 7, 7, 7, \ + 6, 6, 6, 7, 7, 7, 7, 8, \ + 7, 7, 7, 7, 7, 7, 8, 8 \ +} + +#endif diff --git a/linux/drivers/media/video/planb.c b/linux/drivers/media/video/planb.c new file mode 100644 index 000000000..c3026d701 --- /dev/null +++ b/linux/drivers/media/video/planb.c @@ -0,0 +1,2304 @@ +/* + 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/init.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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 "compat.h" +#include <linux/videodev.h> +#include <linux/wait.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 <linux/mutex.h> + +#include "planb.h" +#include "saa7196.h" + +/* Would you mind for some ugly debugging? */ +#if 0 +#define DEBUG(x...) printk(KERN_DEBUG ## x) /* Debug driver */ +#else +#define DEBUG(x...) /* Don't debug driver */ +#endif + +#if 0 +#define IDEBUG(x...) printk(KERN_DEBUG ## x) /* Debug interrupt part */ +#else +#define IDEBUG(x...) /* Don't debug interrupt part */ +#endif + +/* 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 */ +static int video_nr = -1; + +module_param(def_norm, int, 0); +MODULE_PARM_DESC(def_norm, "Default startup norm (0=PAL, 1=NTSC, 2=SECAM)"); +module_param(video_nr, int, 0); +MODULE_LICENSE("GPL"); + + +/* ------------------ 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; + SetPageReserved(virt_to_page(pb->rawbuf[i])); + } + if (i-- < npage) { + printk(KERN_DEBUG "PlanB: init_grab: grab buffer not allocated\n"); + for (; i > 0; i--) { + ClearPageReserved(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 */ + msleep_interruptible(30); + + 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 inline void planb_lock(struct planb *pb) +{ + mutex_lock(&pb->lock); +} + +static inline void planb_unlock(struct planb *pb) +{ + mutex_unlock(&pb->lock); +} + +/***************/ +/* 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++) { + ClearPageReserved(virt_to_page(pb->rawbuf[i])); + free_pages((unsigned long)pb->rawbuf[i], 0); + } + kfree(pb->rawbuf); + } + pb->rawbuf = NULL; +} + +/*****************************/ +/* overlay support functions */ +/*****************************/ + +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 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 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"); + 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"); +} + +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 */ + wait_event(pb->capq, !pb->grabbing); + 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 vm_area_struct *vma, 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++) { + unsigned long pfn; + + pfn = virt_to_phys((void *)pb->rawbuf[i]) >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start += PAGE_SIZE; + if (size <= PAGE_SIZE) + break; + size -= PAGE_SIZE; + } + return 0; +} + +static struct video_device planb_template= +{ + .owner = THIS_MODULE, + .name = PLANB_DEVICE_NAME, + .type = VID_TYPE_OVERLAY, + .hardware = VID_HARDWARE_PLANB, + .open = planb_open, + .close = planb_close, + .read = planb_read, + .write = planb_write, + .ioctl = planb_ioctl, + .mmap = planb_mmap, /* mmap? */ +}; + +static int init_planb(struct planb *pb) +{ + unsigned char saa_rev; + int i, result; + + 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; + mutex_init(&pb->lock); + 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; + + 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); + return result; + } + disable_irq(pb->irq); + + /* 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, video_nr)<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; + int rc; + + if (!machine_is(powermac)) + 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 "planb: cannot find slot\n"); + goto err_out; + } + + /* Enable response in memory space, bus mastering, + use memory write and invalidate */ + rc = pci_enable_device(pdev); + if (rc) { + printk(KERN_ERR "planb: cannot enable PCI device %s\n", + pci_name(pdev)); + goto err_out; + } + rc = pci_set_mwi(pdev); + if (rc) { + printk(KERN_ERR "planb: cannot enable MWI on PCI device %s\n", + pci_name(pdev)); + goto err_out_disable; + } + pci_set_master(pdev); + + /* 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; + +err_out_disable: + pci_disable_device(pdev); +err_out: + /* FIXME handle error */ /* comment moved from pci_find_slot, above */ + return 0; +} + +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); + } +} + +static int __init init_planbs(void) +{ + 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; +} + +static void __exit exit_planbs(void) +{ + release_planb(); +} + +module_init(init_planbs); +module_exit(exit_planbs); diff --git a/linux/drivers/media/video/planb.h b/linux/drivers/media/video/planb.h new file mode 100644 index 000000000..92823211d --- /dev/null +++ b/linux/drivers/media/video/planb.h @@ -0,0 +1,231 @@ +/* + 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; + struct mutex lock; + 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/linux/drivers/media/video/pms.c b/linux/drivers/media/video/pms.c new file mode 100644 index 000000000..7df71e084 --- /dev/null +++ b/linux/drivers/media/video/pms.c @@ -0,0 +1,1064 @@ +/* + * 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 doesn't work for you... + * + * Changes: + * 08/07/2003 Daniele Bellucci <bellucda@tiscali.it> + * - pms_capture: report back -EFAULT + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <asm/io.h> +#include <linux/sched.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.h> + +#include <asm/uaccess.h> + + +#define MOTOROLA 1 +#define PHILIPS2 2 +#define PHILIPS1 3 +#define MVVMEMORYWIDTH 0x40 /* 512 bytes */ + +struct pms_device +{ + struct video_device v; + struct video_picture picture; + int height; + int width; + struct mutex lock; +}; + +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; +static void __iomem *mem; +static int video_nr = -1; + + + +static inline void mvv_write(u8 index, u8 value) +{ + outw(index|(value<<8), io_port); +} + +static inline u8 mvv_read(u8 index) +{ + outb(index, io_port); + return inb(data_port); +} + +static int pms_i2c_stat(u8 slave) +{ + int counter; + int i; + + outb(0x28, io_port); + + counter=0; + while((inb(data_port)&0x01)==0) + if(counter++==256) + break; + + while((inb(data_port)&0x01)!=0) + if(counter++==256) + break; + + outb(slave, io_port); + + counter=0; + while((inb(data_port)&0x01)==0) + if(counter++==256) + break; + + while((inb(data_port)&0x01)!=0) + if(counter++==256) + break; + + for(i=0;i<12;i++) + { + char st=inb(data_port); + if((st&2)!=0) + return -1; + if((st&1)==0) + break; + } + outb(0x29, io_port); + return inb(data_port); +} + +static int pms_i2c_write(u16 slave, u16 sub, u16 data) +{ + int skip=0; + int count; + int i; + + for(i=0;i<i2c_count;i++) + { + if((i2cinfo[i].slave==slave) && + (i2cinfo[i].sub == sub)) + { + if(i2cinfo[i].data==data) + skip=1; + i2cinfo[i].data=data; + i=i2c_count+1; + } + } + + if(i==i2c_count && i2c_count<64) + { + i2cinfo[i2c_count].slave=slave; + i2cinfo[i2c_count].sub=sub; + i2cinfo[i2c_count].data=data; + i2c_count++; + } + + if(skip) + return 0; + + mvv_write(0x29, sub); + mvv_write(0x2A, data); + mvv_write(0x28, slave); + + outb(0x28, io_port); + + count=0; + while((inb(data_port)&1)==0) + if(count>255) + break; + while((inb(data_port)&1)!=0) + if(count>255) + break; + + count=inb(data_port); + + if(count&2) + return -1; + return count; +} + +static int pms_i2c_read(int slave, int sub) +{ + int i=0; + for(i=0;i<i2c_count;i++) + { + if(i2cinfo[i].slave==slave && i2cinfo[i].sub==sub) + return i2cinfo[i].data; + } + return 0; +} + + +static void pms_i2c_andor(int slave, int sub, int and, int or) +{ + u8 tmp; + + tmp=pms_i2c_read(slave, sub); + tmp = (tmp&and)|or; + pms_i2c_write(slave, sub, tmp); +} + +/* + * Control functions + */ + + +static void pms_videosource(short source) +{ + mvv_write(0x2E, source?0x31:0x30); +} + +static void pms_hue(short hue) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, hue); + break; + case PHILIPS2: + pms_i2c_write(0x8A, 0x07, hue); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x07, hue); + break; + } +} + +static void pms_colour(short colour) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, colour); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x12, colour); + break; + } +} + + +static void pms_contrast(short contrast) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, contrast); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x13, contrast); + break; + } +} + +static void pms_brightness(short brightness) +{ + switch(decoder) + { + case MOTOROLA: + pms_i2c_write(0x8A, 0x00, brightness); + pms_i2c_write(0x8A, 0x00, brightness); + pms_i2c_write(0x8A, 0x00, brightness); + break; + case PHILIPS1: + pms_i2c_write(0x42, 0x19, brightness); + break; + } +} + + +static void pms_format(short format) +{ + int target; + standard = format; + + if(decoder==PHILIPS1) + target=0x42; + else if(decoder==PHILIPS2) + target=0x8A; + else + return; + + switch(format) + { + case 0: /* Auto */ + pms_i2c_andor(target, 0x0D, 0xFE,0x00); + pms_i2c_andor(target, 0x0F, 0x3F,0x80); + break; + case 1: /* NTSC */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x00); + pms_i2c_andor(target, 0x0F, 0x3F, 0x40); + break; + case 2: /* PAL */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x00); + pms_i2c_andor(target, 0x0F, 0x3F, 0x00); + break; + case 3: /* SECAM */ + pms_i2c_andor(target, 0x0D, 0xFE, 0x01); + pms_i2c_andor(target, 0x0F, 0x3F, 0x00); + break; + } +} + +#ifdef FOR_FUTURE_EXPANSION + +/* + * These features of the PMS card are not currently exposes. They + * could become a private v4l ioctl for PMSCONFIG or somesuch if + * people need it. We also don't yet use the PMS interrupt. + */ + +static void pms_hstart(short start) +{ + switch(decoder) + { + case PHILIPS1: + pms_i2c_write(0x8A, 0x05, start); + pms_i2c_write(0x8A, 0x18, start); + break; + case PHILIPS2: + pms_i2c_write(0x42, 0x05, start); + pms_i2c_write(0x42, 0x18, start); + break; + } +} + +/* + * Bandpass filters + */ + +static void pms_bandpass(short pass) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xCF, (pass&0x03)<<4); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xCF, (pass&0x03)<<4); +} + +static void pms_antisnow(short snow) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xF3, (snow&0x03)<<2); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xF3, (snow&0x03)<<2); +} + +static void pms_sharpness(short sharp) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xFC, sharp&0x03); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xFC, sharp&0x03); +} + +static void pms_chromaagc(short agc) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0C, 0x9F, (agc&0x03)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0C, 0x9F, (agc&0x03)<<5); +} + +static void pms_vertnoise(short noise) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x10, 0xFC, noise&3); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x10, 0xFC, noise&3); +} + +static void pms_forcecolour(short colour) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0C, 0x7F, (colour&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0C, 0x7, (colour&1)<<7); +} + +static void pms_antigamma(short gamma) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x00, 0x7F, (gamma&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x20, 0x7, (gamma&1)<<7); +} + +static void pms_prefilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x06, 0xBF, (filter&1)<<6); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x06, 0xBF, (filter&1)<<6); +} + +static void pms_hfilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x04, 0x1F, (filter&7)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x24, 0x1F, (filter&7)<<5); +} + +static void pms_vfilter(short filter) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0xB8, 0x08, 0x9F, (filter&3)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x28, 0x9F, (filter&3)<<5); +} + +static void pms_killcolour(short colour) +{ + if(decoder==PHILIPS2) + { + pms_i2c_andor(0x8A, 0x08, 0x07, (colour&0x1F)<<3); + pms_i2c_andor(0x8A, 0x09, 0x07, (colour&0x1F)<<3); + } + else if(decoder==PHILIPS1) + { + pms_i2c_andor(0x42, 0x08, 0x07, (colour&0x1F)<<3); + pms_i2c_andor(0x42, 0x09, 0x07, (colour&0x1F)<<3); + } +} + +static void pms_chromagain(short chroma) +{ + if(decoder==PHILIPS2) + { + pms_i2c_write(0x8A, 0x11, chroma); + } + else if(decoder==PHILIPS1) + { + pms_i2c_write(0x42, 0x11, chroma); + } +} + + +static void pms_spacialcompl(short data) +{ + mvv_write(0x3B, data); +} + +static void pms_spacialcomph(short data) +{ + mvv_write(0x3A, data); +} + +static void pms_vstart(short start) +{ + mvv_write(0x16, start); + mvv_write(0x17, (start>>8)&0x01); +} + +#endif + +static void pms_secamcross(short cross) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A, 0x0F, 0xDF, (cross&1)<<5); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42, 0x0F, 0xDF, (cross&1)<<5); +} + + +static void pms_swsense(short sense) +{ + if(decoder==PHILIPS2) + { + pms_i2c_write(0x8A, 0x0A, sense); + pms_i2c_write(0x8A, 0x0B, sense); + } + else if(decoder==PHILIPS1) + { + pms_i2c_write(0x42, 0x0A, sense); + pms_i2c_write(0x42, 0x0B, sense); + } +} + + +static void pms_framerate(short frr) +{ + int fps=(standard==1)?30:25; + if(frr==0) + return; + fps=fps/frr; + mvv_write(0x14,0x80|fps); + mvv_write(0x15,1); +} + +static void pms_vert(u8 deciden, u8 decinum) +{ + mvv_write(0x1C, deciden); /* Denominator */ + mvv_write(0x1D, decinum); /* Numerator */ +} + +/* + * Turn 16bit ratios into best small ratio the chipset can grok + */ + +static void pms_vertdeci(unsigned short decinum, unsigned short deciden) +{ + /* Knock it down by /5 once */ + if(decinum%5==0) + { + deciden/=5; + decinum/=5; + } + /* + * 3's + */ + while(decinum%3==0 && deciden%3==0) + { + deciden/=3; + decinum/=3; + } + /* + * 2's + */ + while(decinum%2==0 && deciden%2==0) + { + decinum/=2; + deciden/=2; + } + /* + * Fudgyify + */ + while(deciden>32) + { + deciden/=2; + decinum=(decinum+1)/2; + } + if(deciden==32) + deciden--; + pms_vert(deciden,decinum); +} + +static void pms_horzdeci(short decinum, short deciden) +{ + if(decinum<=512) + { + if(decinum%5==0) + { + decinum/=5; + deciden/=5; + } + } + else + { + decinum=512; + deciden=640; /* 768 would be ideal */ + } + + while(((decinum|deciden)&1)==0) + { + decinum>>=1; + deciden>>=1; + } + while(deciden>32) + { + deciden>>=1; + decinum=(decinum+1)>>1; + } + if(deciden==32) + deciden--; + + mvv_write(0x24, 0x80|deciden); + mvv_write(0x25, decinum); +} + +static void pms_resolution(short width, short height) +{ + int fg_height; + + fg_height=height; + if(fg_height>280) + fg_height=280; + + mvv_write(0x18, fg_height); + mvv_write(0x19, fg_height>>8); + + if(standard==1) + { + mvv_write(0x1A, 0xFC); + mvv_write(0x1B, 0x00); + if(height>fg_height) + pms_vertdeci(240,240); + else + pms_vertdeci(fg_height,240); + } + else + { + mvv_write(0x1A, 0x1A); + mvv_write(0x1B, 0x01); + if(fg_height>256) + pms_vertdeci(270,270); + else + pms_vertdeci(fg_height, 270); + } + mvv_write(0x12,0); + mvv_write(0x13, MVVMEMORYWIDTH); + mvv_write(0x42, 0x00); + mvv_write(0x43, 0x00); + mvv_write(0x44, MVVMEMORYWIDTH); + + mvv_write(0x22, width+8); + mvv_write(0x23, (width+8)>> 8); + + if(standard==1) + pms_horzdeci(width,640); + else + pms_horzdeci(width+8, 768); + + mvv_write(0x30, mvv_read(0x30)&0xFE); + mvv_write(0x08, mvv_read(0x08)|0x01); + mvv_write(0x01, mvv_read(0x01)&0xFD); + mvv_write(0x32, 0x00); + mvv_write(0x33, MVVMEMORYWIDTH); +} + + +/* + * Set Input + */ + +static void pms_vcrinput(short input) +{ + if(decoder==PHILIPS2) + pms_i2c_andor(0x8A,0x0D,0x7F,(input&1)<<7); + else if(decoder==PHILIPS1) + pms_i2c_andor(0x42,0x0D,0x7F,(input&1)<<7); +} + + +static int pms_capture(struct pms_device *dev, char __user *buf, int rgb555, int count) +{ + int y; + int dw = 2*dev->width; + + char tmp[dw+32]; /* using a temp buffer is faster than direct */ + int cnt = 0; + int len=0; + unsigned char r8 = 0x5; /* value for reg8 */ + + if (rgb555) + r8 |= 0x20; /* else use untranslated rgb = 565 */ + mvv_write(0x08,r8); /* capture rgb555/565, init DRAM, PC enable */ + +/* printf("%d %d %d %d %d %x %x\n",width,height,voff,nom,den,mvv_buf); */ + + for (y = 0; y < dev->height; y++ ) + { + writeb(0, mem); /* synchronisiert neue Zeile */ + + /* + * This is in truth a fifo, be very careful as if you + * forgot this odd things will occur 8) + */ + + memcpy_fromio(tmp, mem, dw+32); /* discard 16 word */ + cnt -= dev->height; + while (cnt <= 0) + { + /* + * Don't copy too far + */ + int dt=dw; + if(dt+len>count) + dt=count-len; + cnt += dev->height; + if (copy_to_user(buf, tmp+32, dt)) + return len ? len : -EFAULT; + buf += dt; + len += dt; + } + } + return len; +} + + +/* + * Video4linux interfacing + */ + +static int pms_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct pms_device *pd=(struct pms_device *)dev; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + strcpy(b->name, "Mediavision PMS"); + b->type = VID_TYPE_CAPTURE|VID_TYPE_SCALES; + b->channels = 4; + b->audios = 0; + b->maxwidth = 640; + b->maxheight = 480; + b->minwidth = 16; + b->minheight = 16; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + if(v->channel<0 || v->channel>3) + return -EINVAL; + v->flags=0; + v->tuners=1; + /* Good question.. its composite or SVHS so.. */ + v->type = VIDEO_TYPE_CAMERA; + switch(v->channel) + { + case 0: + strcpy(v->name, "Composite");break; + case 1: + strcpy(v->name, "SVideo");break; + case 2: + strcpy(v->name, "Composite(VCR)");break; + case 3: + strcpy(v->name, "SVideo(VCR)");break; + } + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + if(v->channel<0 || v->channel>3) + return -EINVAL; + mutex_lock(&pd->lock); + pms_videosource(v->channel&1); + pms_vcrinput(v->channel>>1); + mutex_unlock(&pd->lock); + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + strcpy(v->name, "Format"); + v->rangelow=0; + v->rangehigh=0; + v->flags= VIDEO_TUNER_PAL|VIDEO_TUNER_NTSC|VIDEO_TUNER_SECAM; + switch(standard) + { + case 0: + v->mode = VIDEO_MODE_AUTO; + break; + case 1: + v->mode = VIDEO_MODE_NTSC; + break; + case 2: + v->mode = VIDEO_MODE_PAL; + break; + case 3: + v->mode = VIDEO_MODE_SECAM; + break; + } + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *v = arg; + if(v->tuner) + return -EINVAL; + mutex_lock(&pd->lock); + switch(v->mode) + { + case VIDEO_MODE_AUTO: + pms_framerate(25); + pms_secamcross(0); + pms_format(0); + break; + case VIDEO_MODE_NTSC: + pms_framerate(30); + pms_secamcross(0); + pms_format(1); + break; + case VIDEO_MODE_PAL: + pms_framerate(25); + pms_secamcross(0); + pms_format(2); + break; + case VIDEO_MODE_SECAM: + pms_framerate(25); + pms_secamcross(1); + pms_format(2); + break; + default: + mutex_unlock(&pd->lock); + return -EINVAL; + } + mutex_unlock(&pd->lock); + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + *p = pd->picture; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + if(!((p->palette==VIDEO_PALETTE_RGB565 && p->depth==16) + ||(p->palette==VIDEO_PALETTE_RGB555 && p->depth==15))) + return -EINVAL; + pd->picture= *p; + + /* + * Now load the card. + */ + + mutex_lock(&pd->lock); + pms_brightness(p->brightness>>8); + pms_hue(p->hue>>8); + pms_colour(p->colour>>8); + pms_contrast(p->contrast>>8); + mutex_unlock(&pd->lock); + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + if(vw->flags) + return -EINVAL; + if(vw->clipcount) + return -EINVAL; + if(vw->height<16||vw->height>480) + return -EINVAL; + if(vw->width<16||vw->width>640) + return -EINVAL; + pd->width=vw->width; + pd->height=vw->height; + mutex_lock(&pd->lock); + pms_resolution(pd->width, pd->height); + mutex_unlock(&pd->lock); /* Ok we figured out what to use from our wide choice */ + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + memset(vw,0,sizeof(*vw)); + vw->width=pd->width; + vw->height=pd->height; + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int pms_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, pms_do_ioctl); +} + +static ssize_t pms_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *v = video_devdata(file); + struct pms_device *pd=(struct pms_device *)v; + int len; + + mutex_lock(&pd->lock); + len=pms_capture(pd, buf, (pd->picture.depth==16)?0:1,count); + mutex_unlock(&pd->lock); + return len; +} + +static struct file_operations pms_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = pms_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .read = pms_read, + .llseek = no_llseek, +}; + +static struct video_device pms_template= +{ + .owner = THIS_MODULE, + .name = "Mediavision PMS", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_PMS, + .fops = &pms_fops, +}; + +static struct pms_device pms_device; + + +/* + * Probe for and initialise the Mediavision PMS + */ + +static int init_mediavision(void) +{ + int id; + int idec, decst; + int i; + + unsigned char i2c_defs[]={ + 0x4C,0x30,0x00,0xE8, + 0xB6,0xE2,0x00,0x00, + 0xFF,0xFF,0x00,0x00, + 0x00,0x00,0x78,0x98, + 0x00,0x00,0x00,0x00, + 0x34,0x0A,0xF4,0xCE, + 0xE4 + }; + + mem = ioremap(mem_base, 0x800); + if (!mem) + return -ENOMEM; + + if (!request_region(0x9A01, 1, "Mediavision PMS config")) + { + printk(KERN_WARNING "mediavision: unable to detect: 0x9A01 in use.\n"); + iounmap(mem); + return -EBUSY; + } + if (!request_region(io_port, 3, "Mediavision PMS")) + { + printk(KERN_WARNING "mediavision: I/O port %d in use.\n", io_port); + release_region(0x9A01, 1); + iounmap(mem); + return -EBUSY; + } + outb(0xB8, 0x9A01); /* Unlock */ + outb(io_port>>4, 0x9A01); /* Set IO port */ + + + id=mvv_read(3); + decst=pms_i2c_stat(0x43); + + if(decst!=-1) + idec=2; + else if(pms_i2c_stat(0xb9)!=-1) + idec=3; + else if(pms_i2c_stat(0x8b)!=-1) + idec=1; + else + idec=0; + + printk(KERN_INFO "PMS type is %d\n", idec); + if(idec == 0) { + release_region(io_port, 3); + release_region(0x9A01, 1); + iounmap(mem); + return -ENODEV; + } + + /* + * Ok we have a PMS of some sort + */ + + mvv_write(0x04, mem_base>>12); /* Set the memory area */ + + /* Ok now load the defaults */ + + for(i=0;i<0x19;i++) + { + if(i2c_defs[i]==0xFF) + pms_i2c_andor(0x8A, i, 0x07,0x00); + else + pms_i2c_write(0x8A, i, i2c_defs[i]); + } + + pms_i2c_write(0xB8,0x00,0x12); + pms_i2c_write(0xB8,0x04,0x00); + pms_i2c_write(0xB8,0x07,0x00); + pms_i2c_write(0xB8,0x08,0x00); + pms_i2c_write(0xB8,0x09,0xFF); + pms_i2c_write(0xB8,0x0A,0x00); + pms_i2c_write(0xB8,0x0B,0x10); + pms_i2c_write(0xB8,0x10,0x03); + + mvv_write(0x01, 0x00); + mvv_write(0x05, 0xA0); + mvv_write(0x08, 0x25); + mvv_write(0x09, 0x00); + mvv_write(0x0A, 0x20|MVVMEMORYWIDTH); + + mvv_write(0x10, 0x02); + mvv_write(0x1E, 0x0C); + mvv_write(0x1F, 0x03); + mvv_write(0x26, 0x06); + + mvv_write(0x2B, 0x00); + mvv_write(0x2C, 0x20); + mvv_write(0x2D, 0x00); + mvv_write(0x2F, 0x70); + mvv_write(0x32, 0x00); + mvv_write(0x33, MVVMEMORYWIDTH); + mvv_write(0x34, 0x00); + mvv_write(0x35, 0x00); + mvv_write(0x3A, 0x80); + mvv_write(0x3B, 0x10); + mvv_write(0x20, 0x00); + mvv_write(0x21, 0x00); + mvv_write(0x30, 0x22); + return 0; +} + +/* + * Initialization and module stuff + */ + +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)); + mutex_init(&pms_device.lock); + pms_device.height=240; + pms_device.width=320; + pms_swsense(75); + pms_resolution(320,240); + return video_register_device((struct video_device *)&pms_device, VFL_TYPE_GRABBER, video_nr); +} + +module_param(io_port, int, 0); +module_param(mem_base, int, 0); +module_param(video_nr, int, 0); +MODULE_LICENSE("GPL"); + + +static void __exit shutdown_mediavision(void) +{ + release_region(io_port,3); + release_region(0x9A01, 1); +} + +static void __exit cleanup_pms_module(void) +{ + shutdown_mediavision(); + video_unregister_device((struct video_device *)&pms_device); + iounmap(mem); +} + +module_init(init_pms_cards); +module_exit(cleanup_pms_module); + diff --git a/linux/drivers/media/video/saa5246a.c b/linux/drivers/media/video/saa5246a.c new file mode 100644 index 000000000..bfc970087 --- /dev/null +++ b/linux/drivers/media/video/saa5246a.c @@ -0,0 +1,837 @@ +/* + * Driver for the SAA5246A or SAA5281 Teletext (=Videotext) decoder chips from + * Philips. + * + * Only capturing of Teletext pages is tested. The videotext chips also have a + * TV output but my hardware doesn't use it. For this reason this driver does + * not support changing any TV display settings. + * + * Copyright (C) 2004 Michael Geng <linux@MichaelGeng.de> + * + * Derived from + * + * saa5249 driver + * Copyright (C) 1998 Richard Guenther + * <richard.guenther@student.uni-tuebingen.de> + * + * with changes by + * Alan Cox <Alan.Cox@linux.org> + * + * and + * + * vtx.c + * 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/init.h> +#include <linux/i2c.h> +#include <linux/videotext.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.h> + +#include "saa5246a.h" + +MODULE_AUTHOR("Michael Geng <linux@MichaelGeng.de>"); +MODULE_DESCRIPTION("Philips SAA5246A, SAA5281 Teletext decoder driver"); +MODULE_LICENSE("GPL"); + +struct saa5246a_device +{ + u8 pgbuf[NUM_DAUS][VTX_VIRTUALSIZE]; + int is_searching[NUM_DAUS]; + struct i2c_client *client; + struct mutex lock; +}; + +static struct video_device saa_template; /* Declared near bottom */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { I2C_ADDRESS, I2C_CLIENT_END }; +I2C_CLIENT_INSMOD; + +static struct i2c_client client_template; + +static int saa5246a_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int pgbuf; + int err; + struct i2c_client *client; + struct video_device *vd; + struct saa5246a_device *t; + + printk(KERN_INFO "saa5246a: 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 = kzalloc(sizeof(*t), GFP_KERNEL); + if(t==NULL) + { + kfree(client); + return -ENOMEM; + } + strlcpy(client->name, IF_NAME, I2C_NAME_SIZE); + mutex_init(&t->lock); + + /* + * Now create a video4linux device + */ + + vd = video_device_alloc(); + if(vd==NULL) + { + kfree(t); + kfree(client); + return -ENOMEM; + } + i2c_set_clientdata(client, vd); + memcpy(vd, &saa_template, sizeof(*vd)); + + for (pgbuf = 0; pgbuf < NUM_DAUS; pgbuf++) + { + memset(t->pgbuf[pgbuf], ' ', sizeof(t->pgbuf[0])); + t->is_searching[pgbuf] = FALSE; + } + vd->priv=t; + + + /* + * Register it + */ + + if((err=video_register_device(vd, VFL_TYPE_VTX,-1))<0) + { + kfree(t); + kfree(client); + video_device_release(vd); + return err; + } + t->client = client; + i2c_attach_client(client); + return 0; +} + +/* + * We do most of the hard work when we become a device on the i2c. + */ +static int saa5246a_probe(struct i2c_adapter *adap) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa5246a_attach); + return 0; +} + +static int saa5246a_detach(struct i2c_client *client) +{ + struct video_device *vd = i2c_get_clientdata(client); + i2c_detach_client(client); + video_unregister_device(vd); + kfree(vd->priv); + kfree(client); + return 0; +} + +/* + * I2C interfaces + */ + +static struct i2c_driver i2c_driver_videotext = +{ + .driver = { + .name = IF_NAME, /* name */ + }, + .id = I2C_DRIVERID_SAA5249, /* in i2c.h */ + .attach_adapter = saa5246a_probe, + .detach_client = saa5246a_detach, +}; + +static struct i2c_client client_template = { + .driver = &i2c_driver_videotext, + .name = "(unset)", +}; + +static int i2c_sendbuf(struct saa5246a_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 saa5246a_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 saa5246a_device *t, int count, u8 *buf) +{ + if(i2c_master_recv(t->client, buf, count)!=count) + return -1; + return 0; +} + +/* When a page is found then the not FOUND bit in one of the status registers + * of the SAA5264A chip is cleared. Unfortunately this bit is not set + * automatically when a new page is requested. Instead this function must be + * called after a page has been requested. + * + * Return value: 0 if successful + */ +static int saa5246a_clear_found_bit(struct saa5246a_device *t, + unsigned char dau_no) +{ + unsigned char row_25_column_8; + + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_8, + + COMMAND_END) || + i2c_getdata(t, 1, &row_25_column_8)) + { + return -EIO; + } + row_25_column_8 |= ROW25_COLUMN8_PAGE_NOT_FOUND; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_8, + + row_25_column_8, + + COMMAND_END)) + { + return -EIO; + } + + return 0; +} + +/* Requests one videotext page as described in req. The fields of req are + * checked and an error is returned if something is invalid. + * + * Return value: 0 if successful + */ +static int saa5246a_request_page(struct saa5246a_device *t, + vtx_pagereq_t *req) +{ + if (req->pagemask < 0 || req->pagemask >= PGMASK_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_PAGE) + if (req->page < 0 || req->page > PAGE_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_HOUR) + if (req->hour < 0 || req->hour > HOUR_MAX) + return -EINVAL; + if (req->pagemask & PGMASK_MINUTE) + if (req->minute < 0 || req->minute > MINUTE_MAX) + return -EINVAL; + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + + if (i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + req->pgbuf << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + HUNDREDS_OF_PAGE(req->page) | + R3_HOLD_PAGE | + (req->pagemask & PG_HUND ? + R3_PAGE_HUNDREDS_DO_CARE : + R3_PAGE_HUNDREDS_DO_NOT_CARE), + + TENS_OF_PAGE(req->page) | + (req->pagemask & PG_TEN ? + R3_PAGE_TENS_DO_CARE : + R3_PAGE_TENS_DO_NOT_CARE), + + UNITS_OF_PAGE(req->page) | + (req->pagemask & PG_UNIT ? + R3_PAGE_UNITS_DO_CARE : + R3_PAGE_UNITS_DO_NOT_CARE), + + TENS_OF_HOUR(req->hour) | + (req->pagemask & HR_TEN ? + R3_HOURS_TENS_DO_CARE : + R3_HOURS_TENS_DO_NOT_CARE), + + UNITS_OF_HOUR(req->hour) | + (req->pagemask & HR_UNIT ? + R3_HOURS_UNITS_DO_CARE : + R3_HOURS_UNITS_DO_NOT_CARE), + + TENS_OF_MINUTE(req->minute) | + (req->pagemask & MIN_TEN ? + R3_MINUTES_TENS_DO_CARE : + R3_MINUTES_TENS_DO_NOT_CARE), + + UNITS_OF_MINUTE(req->minute) | + (req->pagemask & MIN_UNIT ? + R3_MINUTES_UNITS_DO_CARE : + R3_MINUTES_UNITS_DO_NOT_CARE), + + COMMAND_END) || i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + req->pgbuf << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + HUNDREDS_OF_PAGE(req->page) | + R3_UPDATE_PAGE | + (req->pagemask & PG_HUND ? + R3_PAGE_HUNDREDS_DO_CARE : + R3_PAGE_HUNDREDS_DO_NOT_CARE), + + COMMAND_END)) + { + return -EIO; + } + + t->is_searching[req->pgbuf] = TRUE; + return 0; +} + +/* This routine decodes the page number from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return value: page number coded in hexadecimal, i. e. page 123 is coded 0x123 + */ +static inline int saa5246a_extract_pagenum_from_infobits( + unsigned char infobits[10]) +{ + int page_hundreds, page_tens, page_units; + + page_units = infobits[0] & ROW25_COLUMN0_PAGE_UNITS; + page_tens = infobits[1] & ROW25_COLUMN1_PAGE_TENS; + page_hundreds = infobits[8] & ROW25_COLUMN8_PAGE_HUNDREDS; + + /* page 0x.. means page 8.. */ + if (page_hundreds == 0) + page_hundreds = 8; + + return((page_hundreds << 8) | (page_tens << 4) | page_units); +} + +/* Decodes the hour from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return: hour coded in hexadecimal, i. e. 12h is coded 0x12 + */ +static inline int saa5246a_extract_hour_from_infobits( + unsigned char infobits[10]) +{ + int hour_tens, hour_units; + + hour_units = infobits[4] & ROW25_COLUMN4_HOUR_UNITS; + hour_tens = infobits[5] & ROW25_COLUMN5_HOUR_TENS; + + return((hour_tens << 4) | hour_units); +} + +/* Decodes the minutes from the infobits contained in line 25. + * + * Parameters: + * infobits: must be bits 0 to 9 of column 25 + * + * Return: minutes coded in hexadecimal, i. e. 10min is coded 0x10 + */ +static inline int saa5246a_extract_minutes_from_infobits( + unsigned char infobits[10]) +{ + int minutes_tens, minutes_units; + + minutes_units = infobits[2] & ROW25_COLUMN2_MINUTES_UNITS; + minutes_tens = infobits[3] & ROW25_COLUMN3_MINUTES_TENS; + + return((minutes_tens << 4) | minutes_units); +} + +/* Reads the status bits contained in the first 10 columns of the first line + * and extracts the information into info. + * + * Return value: 0 if successful + */ +static inline int saa5246a_get_status(struct saa5246a_device *t, + vtx_pageinfo_t *info, unsigned char dau_no) +{ + unsigned char infobits[10]; + int column; + + if (dau_no >= NUM_DAUS) + return -EINVAL; + + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + + dau_no | + R8_DO_NOT_CLEAR_MEMORY, + + R9_CURSER_ROW_25, + + R10_CURSER_COLUMN_0, + + COMMAND_END) || + i2c_getdata(t, 10, infobits)) + { + return -EIO; + } + + info->pagenum = saa5246a_extract_pagenum_from_infobits(infobits); + info->hour = saa5246a_extract_hour_from_infobits(infobits); + info->minute = saa5246a_extract_minutes_from_infobits(infobits); + info->charset = ((infobits[7] & ROW25_COLUMN7_CHARACTER_SET) >> 1); + info->delete = !!(infobits[3] & ROW25_COLUMN3_DELETE_PAGE); + info->headline = !!(infobits[5] & ROW25_COLUMN5_INSERT_HEADLINE); + info->subtitle = !!(infobits[5] & ROW25_COLUMN5_INSERT_SUBTITLE); + info->supp_header = !!(infobits[6] & ROW25_COLUMN6_SUPPRESS_HEADER); + info->update = !!(infobits[6] & ROW25_COLUMN6_UPDATE_PAGE); + info->inter_seq = !!(infobits[6] & ROW25_COLUMN6_INTERRUPTED_SEQUENCE); + info->dis_disp = !!(infobits[6] & ROW25_COLUMN6_SUPPRESS_DISPLAY); + info->serial = !!(infobits[7] & ROW25_COLUMN7_SERIAL_MODE); + info->notfound = !!(infobits[8] & ROW25_COLUMN8_PAGE_NOT_FOUND); + info->pblf = !!(infobits[9] & ROW25_COLUMN9_PAGE_BEING_LOOKED_FOR); + info->hamming = 0; + for (column = 0; column <= 7; column++) { + if (infobits[column] & ROW25_COLUMN0_TO_7_HAMMING_ERROR) { + info->hamming = 1; + break; + } + } + if (!info->hamming && !info->notfound) + t->is_searching[dau_no] = FALSE; + return 0; +} + +/* Reads 1 videotext page buffer of the SAA5246A. + * + * req is used both as input and as output. It contains information which part + * must be read. The videotext page is copied into req->buffer. + * + * Return value: 0 if successful + */ +static inline int saa5246a_get_page(struct saa5246a_device *t, + vtx_pagereq_t *req) +{ + int start, end, size; + char *buf; + int err; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS || + req->start < 0 || req->start > req->end || req->end >= VTX_PAGESIZE) + return -EINVAL; + + buf = kmalloc(VTX_PAGESIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Read "normal" part of page */ + err = -EIO; + + end = min(req->end, VTX_PAGESIZE - 1); + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + req->pgbuf | R8_DO_NOT_CLEAR_MEMORY, + ROW(req->start), COLUMN(req->start), COMMAND_END)) + goto out; + if (i2c_getdata(t, end - req->start + 1, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer, buf, end - req->start + 1)) + goto out; + + /* Always get the time from buffer 4, since this stupid SAA5246A only + * updates the currently displayed buffer... + */ + if (REQ_CONTAINS_TIME(req)) { + start = max(req->start, POS_TIME_START); + end = min(req->end, POS_TIME_END); + size = end - start + 1; + err = -EINVAL; + if (size < 0) + goto out; + err = -EIO; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + R8_ACTIVE_CHAPTER_4 | R8_DO_NOT_CLEAR_MEMORY, + R9_CURSER_ROW_0, start, COMMAND_END)) + goto out; + if (i2c_getdata(t, size, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer + start - req->start, buf, size)) + goto out; + } + /* Insert the header from buffer 4 only, if acquisition circuit is still searching for a page */ + if (REQ_CONTAINS_HEADER(req) && t->is_searching[req->pgbuf]) { + start = max(req->start, POS_HEADER_START); + end = min(req->end, POS_HEADER_END); + size = end - start + 1; + err = -EINVAL; + if (size < 0) + goto out; + err = -EIO; + if (i2c_senddata(t, SAA5246A_REGISTER_R8, + R8_ACTIVE_CHAPTER_4 | R8_DO_NOT_CLEAR_MEMORY, + R9_CURSER_ROW_0, start, COMMAND_END)) + goto out; + if (i2c_getdata(t, end - start + 1, buf)) + goto out; + err = -EFAULT; + if (copy_to_user(req->buffer + start - req->start, buf, size)) + goto out; + } + err = 0; +out: + kfree(buf); + return err; +} + +/* Stops the acquisition circuit given in dau_no. The page buffer associated + * with this acquisition circuit will no more be updated. The other daus are + * not affected. + * + * Return value: 0 if successful + */ +static inline int saa5246a_stop_dau(struct saa5246a_device *t, + unsigned char dau_no) +{ + if (dau_no >= NUM_DAUS) + return -EINVAL; + if (i2c_senddata(t, SAA5246A_REGISTER_R2, + + R2_IN_R3_SELECT_PAGE_HUNDREDS | + dau_no << 4 | + R2_BANK_0 | + R2_HAMMING_CHECK_OFF, + + R3_PAGE_HUNDREDS_0 | + R3_HOLD_PAGE | + R3_PAGE_HUNDREDS_DO_NOT_CARE, + + COMMAND_END)) + { + return -EIO; + } + t->is_searching[dau_no] = FALSE; + return 0; +} + +/* Handles ioctls defined in videotext.h + * + * Returns 0 if successful + */ +static int do_saa5246a_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t=vd->priv; + switch(cmd) + { + case VTXIOCGETINFO: + { + vtx_info_t *info = arg; + + info->version_major = MAJOR_VERSION; + info->version_minor = MINOR_VERSION; + info->numpages = NUM_DAUS; + return 0; + } + + case VTXIOCCLRPAGE: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + memset(t->pgbuf[req->pgbuf], ' ', sizeof(t->pgbuf[0])); + return 0; + } + + case VTXIOCCLRFOUND: + { + vtx_pagereq_t *req = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + return(saa5246a_clear_found_bit(t, req->pgbuf)); + } + + case VTXIOCPAGEREQ: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_request_page(t, req)); + } + + case VTXIOCGETSTAT: + { + vtx_pagereq_t *req = arg; + vtx_pageinfo_t info; + int rval; + + if ((rval = saa5246a_get_status(t, &info, req->pgbuf))) + return rval; + if(copy_to_user(req->buffer, &info, + sizeof(vtx_pageinfo_t))) + return -EFAULT; + return 0; + } + + case VTXIOCGETPAGE: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_get_page(t, req)); + } + + case VTXIOCSTOPDAU: + { + vtx_pagereq_t *req = arg; + + return(saa5246a_stop_dau(t, req->pgbuf)); + } + + case VTXIOCPUTPAGE: + case VTXIOCSETDISP: + case VTXIOCPUTSTAT: + return 0; + + case VTXIOCCLRCACHE: + { + return 0; + } + + case VTXIOCSETVIRT: + { + /* I do not know what "virtual mode" means */ + return 0; + } + } + return -EINVAL; +} + +/* + * Translates old vtx IOCTLs to new ones + * + * This keeps new kernel versions compatible with old userspace programs. + */ +static inline unsigned int vtx_fix_command(unsigned int cmd) +{ + switch (cmd) { + case VTXIOCGETINFO_OLD: + cmd = VTXIOCGETINFO; + break; + case VTXIOCCLRPAGE_OLD: + cmd = VTXIOCCLRPAGE; + break; + case VTXIOCCLRFOUND_OLD: + cmd = VTXIOCCLRFOUND; + break; + case VTXIOCPAGEREQ_OLD: + cmd = VTXIOCPAGEREQ; + break; + case VTXIOCGETSTAT_OLD: + cmd = VTXIOCGETSTAT; + break; + case VTXIOCGETPAGE_OLD: + cmd = VTXIOCGETPAGE; + break; + case VTXIOCSTOPDAU_OLD: + cmd = VTXIOCSTOPDAU; + break; + case VTXIOCPUTPAGE_OLD: + cmd = VTXIOCPUTPAGE; + break; + case VTXIOCSETDISP_OLD: + cmd = VTXIOCSETDISP; + break; + case VTXIOCPUTSTAT_OLD: + cmd = VTXIOCPUTSTAT; + break; + case VTXIOCCLRCACHE_OLD: + cmd = VTXIOCCLRCACHE; + break; + case VTXIOCSETVIRT_OLD: + cmd = VTXIOCSETVIRT; + break; + } + return cmd; +} + +/* + * Handle the locking + */ +static int saa5246a_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + int err; + + cmd = vtx_fix_command(cmd); + mutex_lock(&t->lock); + err = video_usercopy(inode, file, cmd, arg, do_saa5246a_ioctl); + mutex_unlock(&t->lock); + return err; +} + +static int saa5246a_open(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + int err; + + err = video_exclusive_open(inode,file); + if (err < 0) + return err; + + if (t->client==NULL) { + err = -ENODEV; + goto fail; + } + + if (i2c_senddata(t, SAA5246A_REGISTER_R0, + + R0_SELECT_R11 | + R0_PLL_TIME_CONSTANT_LONG | + R0_ENABLE_nODD_EVEN_OUTPUT | + R0_ENABLE_HDR_POLL | + R0_DO_NOT_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED | + R0_NO_FREE_RUN_PLL | + R0_NO_AUTOMATIC_FASTEXT_PROMPT, + + R1_NON_INTERLACED_312_312_LINES | + R1_DEW | + R1_EXTENDED_PACKET_DISABLE | + R1_DAUS_ALL_ON | + R1_8_BITS_NO_PARITY | + R1_VCS_TO_SCS, + + COMMAND_END) || + i2c_senddata(t, SAA5246A_REGISTER_R4, + + /* We do not care much for the TV display but nevertheless we + * need the currently displayed page later because only on that + * page the time is updated. */ + R4_DISPLAY_PAGE_4, + + COMMAND_END)) + { + err = -EIO; + goto fail; + } + + return 0; + +fail: + video_exclusive_release(inode,file); + return err; +} + +static int saa5246a_release(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5246a_device *t = vd->priv; + + /* Stop all acquisition circuits. */ + i2c_senddata(t, SAA5246A_REGISTER_R1, + + R1_INTERLACED_312_AND_HALF_312_AND_HALF_LINES | + R1_DEW | + R1_EXTENDED_PACKET_DISABLE | + R1_DAUS_ALL_OFF | + R1_8_BITS_NO_PARITY | + R1_VCS_TO_SCS, + + COMMAND_END); + video_exclusive_release(inode,file); + return 0; +} + +static int __init init_saa_5246a (void) +{ + printk(KERN_INFO + "SAA5246A (or compatible) Teletext decoder driver version %d.%d\n", + MAJOR_VERSION, MINOR_VERSION); + return i2c_add_driver(&i2c_driver_videotext); +} + +static void __exit cleanup_saa_5246a (void) +{ + i2c_del_driver(&i2c_driver_videotext); +} + +module_init(init_saa_5246a); +module_exit(cleanup_saa_5246a); + +static struct file_operations saa_fops = { + .owner = THIS_MODULE, + .open = saa5246a_open, + .release = saa5246a_release, + .ioctl = saa5246a_ioctl, + .llseek = no_llseek, +}; + +static struct video_device saa_template = +{ + .owner = THIS_MODULE, + .name = IF_NAME, + .type = VID_TYPE_TELETEXT, + .hardware = VID_HARDWARE_SAA5249, + .fops = &saa_fops, + .release = video_device_release, + .minor = -1, +}; diff --git a/linux/drivers/media/video/saa5246a.h b/linux/drivers/media/video/saa5246a.h new file mode 100644 index 000000000..7b9111230 --- /dev/null +++ b/linux/drivers/media/video/saa5246a.h @@ -0,0 +1,364 @@ +/* + Driver for the SAA5246A or SAA5281 Teletext (=Videotext) decoder chips from + Philips. + + Copyright (C) 2004 Michael Geng (linux@MichaelGeng.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 __SAA5246A_H__ +#define __SAA5246A_H__ + +#define MAJOR_VERSION 1 /* driver major version number */ +#define MINOR_VERSION 8 /* driver minor version number */ + +#define IF_NAME "SAA5246A" + +#define I2C_ADDRESS 17 + +/* Number of DAUs = number of pages that can be searched at the same time. */ +#define NUM_DAUS 4 + +#define NUM_ROWS_PER_PAGE 40 + +/* first column is 0 (not 1) */ +#define POS_TIME_START 32 +#define POS_TIME_END 39 + +#define POS_HEADER_START 7 +#define POS_HEADER_END 31 + +/* Returns TRUE if the part of the videotext page described with req contains + (at least parts of) the time field */ +#define REQ_CONTAINS_TIME(p_req) \ + ((p_req)->start <= POS_TIME_END && \ + (p_req)->end >= POS_TIME_START) + +/* Returns TRUE if the part of the videotext page described with req contains + (at least parts of) the page header */ +#define REQ_CONTAINS_HEADER(p_req) \ + ((p_req)->start <= POS_HEADER_END && \ + (p_req)->end >= POS_HEADER_START) + +#ifndef FALSE +#define FALSE 0 +#define TRUE 1 +#endif + +/*****************************************************************************/ +/* Mode register numbers of the SAA5246A */ +/*****************************************************************************/ +#define SAA5246A_REGISTER_R0 0 +#define SAA5246A_REGISTER_R1 1 +#define SAA5246A_REGISTER_R2 2 +#define SAA5246A_REGISTER_R3 3 +#define SAA5246A_REGISTER_R4 4 +#define SAA5246A_REGISTER_R5 5 +#define SAA5246A_REGISTER_R6 6 +#define SAA5246A_REGISTER_R7 7 +#define SAA5246A_REGISTER_R8 8 +#define SAA5246A_REGISTER_R9 9 +#define SAA5246A_REGISTER_R10 10 +#define SAA5246A_REGISTER_R11 11 +#define SAA5246A_REGISTER_R11B 11 + +/* SAA5246A mode registers often autoincrement to the next register. + Therefore we use variable argument lists. The following macro indicates + the end of a command list. */ +#define COMMAND_END (- 1) + +/*****************************************************************************/ +/* Contents of the mode registers of the SAA5246A */ +/*****************************************************************************/ +/* Register R0 (Advanced Control) */ +#define R0_SELECT_R11 0x00 +#define R0_SELECT_R11B 0x01 + +#define R0_PLL_TIME_CONSTANT_LONG 0x00 +#define R0_PLL_TIME_CONSTANT_SHORT 0x02 + +#define R0_ENABLE_nODD_EVEN_OUTPUT 0x00 +#define R0_DISABLE_nODD_EVEN_OUTPUT 0x04 + +#define R0_ENABLE_HDR_POLL 0x00 +#define R0_DISABLE_HDR_POLL 0x10 + +#define R0_DO_NOT_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED 0x00 +#define R0_FORCE_nODD_EVEN_LOW_IF_PICTURE_DISPLAYED 0x20 + +#define R0_NO_FREE_RUN_PLL 0x00 +#define R0_FREE_RUN_PLL 0x40 + +#define R0_NO_AUTOMATIC_FASTEXT_PROMPT 0x00 +#define R0_AUTOMATIC_FASTEXT_PROMPT 0x80 + +/* Register R1 (Mode) */ +#define R1_INTERLACED_312_AND_HALF_312_AND_HALF_LINES 0x00 +#define R1_NON_INTERLACED_312_313_LINES 0x01 +#define R1_NON_INTERLACED_312_312_LINES 0x02 +#define R1_FFB_LEADING_EDGE_IN_FIRST_BROAD_PULSE 0x03 +#define R1_FFB_LEADING_EDGE_IN_SECOND_BROAD_PULSE 0x07 + +#define R1_DEW 0x00 +#define R1_FULL_FIELD 0x08 + +#define R1_EXTENDED_PACKET_DISABLE 0x00 +#define R1_EXTENDED_PACKET_ENABLE 0x10 + +#define R1_DAUS_ALL_ON 0x00 +#define R1_DAUS_ALL_OFF 0x20 + +#define R1_7_BITS_PLUS_PARITY 0x00 +#define R1_8_BITS_NO_PARITY 0x40 + +#define R1_VCS_TO_SCS 0x00 +#define R1_NO_VCS_TO_SCS 0x80 + +/* Register R2 (Page request address) */ +#define R2_IN_R3_SELECT_PAGE_HUNDREDS 0x00 +#define R2_IN_R3_SELECT_PAGE_TENS 0x01 +#define R2_IN_R3_SELECT_PAGE_UNITS 0x02 +#define R2_IN_R3_SELECT_HOURS_TENS 0x03 +#define R2_IN_R3_SELECT_HOURS_UNITS 0x04 +#define R2_IN_R3_SELECT_MINUTES_TENS 0x05 +#define R2_IN_R3_SELECT_MINUTES_UNITS 0x06 + +#define R2_DAU_0 0x00 +#define R2_DAU_1 0x10 +#define R2_DAU_2 0x20 +#define R2_DAU_3 0x30 + +#define R2_BANK_0 0x00 +#define R2_BANK 1 0x40 + +#define R2_HAMMING_CHECK_ON 0x80 +#define R2_HAMMING_CHECK_OFF 0x00 + +/* Register R3 (Page request data) */ +#define R3_PAGE_HUNDREDS_0 0x00 +#define R3_PAGE_HUNDREDS_1 0x01 +#define R3_PAGE_HUNDREDS_2 0x02 +#define R3_PAGE_HUNDREDS_3 0x03 +#define R3_PAGE_HUNDREDS_4 0x04 +#define R3_PAGE_HUNDREDS_5 0x05 +#define R3_PAGE_HUNDREDS_6 0x06 +#define R3_PAGE_HUNDREDS_7 0x07 + +#define R3_HOLD_PAGE 0x00 +#define R3_UPDATE_PAGE 0x08 + +#define R3_PAGE_HUNDREDS_DO_NOT_CARE 0x00 +#define R3_PAGE_HUNDREDS_DO_CARE 0x10 + +#define R3_PAGE_TENS_DO_NOT_CARE 0x00 +#define R3_PAGE_TENS_DO_CARE 0x10 + +#define R3_PAGE_UNITS_DO_NOT_CARE 0x00 +#define R3_PAGE_UNITS_DO_CARE 0x10 + +#define R3_HOURS_TENS_DO_NOT_CARE 0x00 +#define R3_HOURS_TENS_DO_CARE 0x10 + +#define R3_HOURS_UNITS_DO_NOT_CARE 0x00 +#define R3_HOURS_UNITS_DO_CARE 0x10 + +#define R3_MINUTES_TENS_DO_NOT_CARE 0x00 +#define R3_MINUTES_TENS_DO_CARE 0x10 + +#define R3_MINUTES_UNITS_DO_NOT_CARE 0x00 +#define R3_MINUTES_UNITS_DO_CARE 0x10 + +/* Register R4 (Display chapter) */ +#define R4_DISPLAY_PAGE_0 0x00 +#define R4_DISPLAY_PAGE_1 0x01 +#define R4_DISPLAY_PAGE_2 0x02 +#define R4_DISPLAY_PAGE_3 0x03 +#define R4_DISPLAY_PAGE_4 0x04 +#define R4_DISPLAY_PAGE_5 0x05 +#define R4_DISPLAY_PAGE_6 0x06 +#define R4_DISPLAY_PAGE_7 0x07 + +/* Register R5 (Normal display control) */ +#define R5_PICTURE_INSIDE_BOXING_OFF 0x00 +#define R5_PICTURE_INSIDE_BOXING_ON 0x01 + +#define R5_PICTURE_OUTSIDE_BOXING_OFF 0x00 +#define R5_PICTURE_OUTSIDE_BOXING_ON 0x02 + +#define R5_TEXT_INSIDE_BOXING_OFF 0x00 +#define R5_TEXT_INSIDE_BOXING_ON 0x04 + +#define R5_TEXT_OUTSIDE_BOXING_OFF 0x00 +#define R5_TEXT_OUTSIDE_BOXING_ON 0x08 + +#define R5_CONTRAST_REDUCTION_INSIDE_BOXING_OFF 0x00 +#define R5_CONTRAST_REDUCTION_INSIDE_BOXING_ON 0x10 + +#define R5_CONTRAST_REDUCTION_OUTSIDE_BOXING_OFF 0x00 +#define R5_CONTRAST_REDUCTION_OUTSIDE_BOXING_ON 0x20 + +#define R5_BACKGROUND_COLOR_INSIDE_BOXING_OFF 0x00 +#define R5_BACKGROUND_COLOR_INSIDE_BOXING_ON 0x40 + +#define R5_BACKGROUND_COLOR_OUTSIDE_BOXING_OFF 0x00 +#define R5_BACKGROUND_COLOR_OUTSIDE_BOXING_ON 0x80 + +/* Register R6 (Newsflash display) */ +#define R6_NEWSFLASH_PICTURE_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_PICTURE_INSIDE_BOXING_ON 0x01 + +#define R6_NEWSFLASH_PICTURE_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_PICTURE_OUTSIDE_BOXING_ON 0x02 + +#define R6_NEWSFLASH_TEXT_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_TEXT_INSIDE_BOXING_ON 0x04 + +#define R6_NEWSFLASH_TEXT_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_TEXT_OUTSIDE_BOXING_ON 0x08 + +#define R6_NEWSFLASH_CONTRAST_REDUCTION_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_CONTRAST_REDUCTION_INSIDE_BOXING_ON 0x10 + +#define R6_NEWSFLASH_CONTRAST_REDUCTION_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_CONTRAST_REDUCTION_OUTSIDE_BOXING_ON 0x20 + +#define R6_NEWSFLASH_BACKGROUND_COLOR_INSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_BACKGROUND_COLOR_INSIDE_BOXING_ON 0x40 + +#define R6_NEWSFLASH_BACKGROUND_COLOR_OUTSIDE_BOXING_OFF 0x00 +#define R6_NEWSFLASH_BACKGROUND_COLOR_OUTSIDE_BOXING_ON 0x80 + +/* Register R7 (Display mode) */ +#define R7_BOX_OFF_ROW_0 0x00 +#define R7_BOX_ON_ROW_0 0x01 + +#define R7_BOX_OFF_ROW_1_TO_23 0x00 +#define R7_BOX_ON_ROW_1_TO_23 0x02 + +#define R7_BOX_OFF_ROW_24 0x00 +#define R7_BOX_ON_ROW_24 0x04 + +#define R7_SINGLE_HEIGHT 0x00 +#define R7_DOUBLE_HEIGHT 0x08 + +#define R7_TOP_HALF 0x00 +#define R7_BOTTOM_HALF 0x10 + +#define R7_REVEAL_OFF 0x00 +#define R7_REVEAL_ON 0x20 + +#define R7_CURSER_OFF 0x00 +#define R7_CURSER_ON 0x40 + +#define R7_STATUS_BOTTOM 0x00 +#define R7_STATUS_TOP 0x80 + +/* Register R8 (Active chapter) */ +#define R8_ACTIVE_CHAPTER_0 0x00 +#define R8_ACTIVE_CHAPTER_1 0x01 +#define R8_ACTIVE_CHAPTER_2 0x02 +#define R8_ACTIVE_CHAPTER_3 0x03 +#define R8_ACTIVE_CHAPTER_4 0x04 +#define R8_ACTIVE_CHAPTER_5 0x05 +#define R8_ACTIVE_CHAPTER_6 0x06 +#define R8_ACTIVE_CHAPTER_7 0x07 + +#define R8_CLEAR_MEMORY 0x08 +#define R8_DO_NOT_CLEAR_MEMORY 0x00 + +/* Register R9 (Curser row) */ +#define R9_CURSER_ROW_0 0x00 +#define R9_CURSER_ROW_1 0x01 +#define R9_CURSER_ROW_2 0x02 +#define R9_CURSER_ROW_25 0x19 + +/* Register R10 (Curser column) */ +#define R10_CURSER_COLUMN_0 0x00 +#define R10_CURSER_COLUMN_6 0x06 +#define R10_CURSER_COLUMN_8 0x08 + +/*****************************************************************************/ +/* Row 25 control data in column 0 to 9 */ +/*****************************************************************************/ +#define ROW25_COLUMN0_PAGE_UNITS 0x0F + +#define ROW25_COLUMN1_PAGE_TENS 0x0F + +#define ROW25_COLUMN2_MINUTES_UNITS 0x0F + +#define ROW25_COLUMN3_MINUTES_TENS 0x07 +#define ROW25_COLUMN3_DELETE_PAGE 0x08 + +#define ROW25_COLUMN4_HOUR_UNITS 0x0F + +#define ROW25_COLUMN5_HOUR_TENS 0x03 +#define ROW25_COLUMN5_INSERT_HEADLINE 0x04 +#define ROW25_COLUMN5_INSERT_SUBTITLE 0x08 + +#define ROW25_COLUMN6_SUPPRESS_HEADER 0x01 +#define ROW25_COLUMN6_UPDATE_PAGE 0x02 +#define ROW25_COLUMN6_INTERRUPTED_SEQUENCE 0x04 +#define ROW25_COLUMN6_SUPPRESS_DISPLAY 0x08 + +#define ROW25_COLUMN7_SERIAL_MODE 0x01 +#define ROW25_COLUMN7_CHARACTER_SET 0x0E + +#define ROW25_COLUMN8_PAGE_HUNDREDS 0x07 +#define ROW25_COLUMN8_PAGE_NOT_FOUND 0x10 + +#define ROW25_COLUMN9_PAGE_BEING_LOOKED_FOR 0x20 + +#define ROW25_COLUMN0_TO_7_HAMMING_ERROR 0x10 + +/*****************************************************************************/ +/* Helper macros for extracting page, hour and minute digits */ +/*****************************************************************************/ +/* BYTE_POS 0 is at row 0, column 0, + BYTE_POS 1 is at row 0, column 1, + BYTE_POS 40 is at row 1, column 0, (with NUM_ROWS_PER_PAGE = 40) + BYTE_POS 41 is at row 1, column 1, (with NUM_ROWS_PER_PAGE = 40), + ... */ +#define ROW(BYTE_POS) (BYTE_POS / NUM_ROWS_PER_PAGE) +#define COLUMN(BYTE_POS) (BYTE_POS % NUM_ROWS_PER_PAGE) + +/*****************************************************************************/ +/* Helper macros for extracting page, hour and minute digits */ +/*****************************************************************************/ +/* Macros for extracting hundreds, tens and units of a page number which + must be in the range 0 ... 0x799. + Note that page is coded in hexadecimal, i.e. 0x123 means page 123. + page 0x.. means page 8.. */ +#define HUNDREDS_OF_PAGE(page) (((page) / 0x100) & 0x7) +#define TENS_OF_PAGE(page) (((page) / 0x10) & 0xF) +#define UNITS_OF_PAGE(page) ((page) & 0xF) + +/* Macros for extracting tens and units of a hour information which + must be in the range 0 ... 0x24. + Note that hour is coded in hexadecimal, i.e. 0x12 means 12 hours */ +#define TENS_OF_HOUR(hour) ((hour) / 0x10) +#define UNITS_OF_HOUR(hour) ((hour) & 0xF) + +/* Macros for extracting tens and units of a minute information which + must be in the range 0 ... 0x59. + Note that minute is coded in hexadecimal, i.e. 0x12 means 12 minutes */ +#define TENS_OF_MINUTE(minute) ((minute) / 0x10) +#define UNITS_OF_MINUTE(minute) ((minute) & 0xF) + +#define HOUR_MAX 0x23 +#define MINUTE_MAX 0x59 +#define PAGE_MAX 0x8FF + +#endif /* __SAA5246A_H__ */ diff --git a/linux/drivers/media/video/saa5249.c b/linux/drivers/media/video/saa5249.c new file mode 100644 index 000000000..42c2052e4 --- /dev/null +++ b/linux/drivers/media/video/saa5249.c @@ -0,0 +1,720 @@ +/* + * Modified in order to keep it compatible both with new and old videotext IOCTLs by + * Michael Geng <linux@MichaelGeng.de> + * + * 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 "compat.h" +#include <linux/videodev.h> +#include <linux/mutex.h> + + +#include <asm/io.h> +#include <asm/uaccess.h> + +#define VTX_VER_MAJ 1 +#define VTX_VER_MIN 8 + + + +#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; + struct mutex lock; +}; + + +#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 + +#define RESCHED do { cond_resched(); } while(0) + +static struct video_device saa_template; /* Declared near bottom */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = {34>>1,I2C_CLIENT_END}; +I2C_CLIENT_INSMOD; + +static struct i2c_client client_template; + +static int saa5249_attach(struct i2c_adapter *adap, int addr, 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 = kzalloc(sizeof(*t), GFP_KERNEL); + if(t==NULL) + { + kfree(client); + return -ENOMEM; + } + strlcpy(client->name, IF_NAME, I2C_NAME_SIZE); + mutex_init(&t->lock); + + /* + * Now create a video4linux device + */ + + vd = kmalloc(sizeof(struct video_device), GFP_KERNEL); + if(vd==NULL) + { + kfree(t); + kfree(client); + return -ENOMEM; + } + i2c_set_clientdata(client, vd); + 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,-1))<0) + { + kfree(t); + kfree(vd); + kfree(client); + return err; + } + t->client = client; + i2c_attach_client(client); + 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) +{ + if (adap->class & I2C_CLASS_TV_ANALOG) + return i2c_probe(adap, &addr_data, saa5249_attach); + return 0; +} + +static int saa5249_detach(struct i2c_client *client) +{ + struct video_device *vd = i2c_get_clientdata(client); + i2c_detach_client(client); + video_unregister_device(vd); + kfree(vd->priv); + kfree(vd); + kfree(client); + return 0; +} + +/* new I2C driver support */ + +static struct i2c_driver i2c_driver_videotext = +{ + .driver = { + .name = IF_NAME, /* name */ + }, + .id = I2C_DRIVERID_SAA5249, /* in i2c.h */ + .attach_adapter = saa5249_probe, + .detach_client = saa5249_detach, +}; + +static struct i2c_client client_template = { + .driver = &i2c_driver_videotext, + .name = "(unset)", +}; + +/* + * 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->sighand->siglock); + sigfillset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + msleep_interruptible(jiffies_to_msecs(delay)); + + spin_lock_irq(¤t->sighand->siglock); + current->blocked = oldblocked; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); +} + + +/* + * 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 do_saa5249_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + static int virtual_mode = FALSE; + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + + switch(cmd) + { + case VTXIOCGETINFO: + { + vtx_info_t *info = arg; + info->version_major = VTX_VER_MAJ; + info->version_minor = VTX_VER_MIN; + info->numpages = NUM_DAUS; + /*info->cct_type = CCT_TYPE;*/ + return 0; + } + + case VTXIOCCLRPAGE: + { + vtx_pagereq_t *req = arg; + + 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 = arg; + + if (req->pgbuf < 0 || req->pgbuf >= NUM_DAUS) + return -EINVAL; + t->vdau[req->pgbuf].clrfound = TRUE; + return 0; + } + + case VTXIOCPAGEREQ: + { + vtx_pagereq_t *req = arg; + 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 = arg; + u8 infobits[10]; + vtx_pageinfo_t info; + int a; + + 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 = arg; + int start, end; + + 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 = arg; + + 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)(long)arg; + return 0; + } + } + return -EINVAL; +} + +/* + * Translates old vtx IOCTLs to new ones + * + * This keeps new kernel versions compatible with old userspace programs. + */ +static inline unsigned int vtx_fix_command(unsigned int cmd) +{ + switch (cmd) { + case VTXIOCGETINFO_OLD: + cmd = VTXIOCGETINFO; + break; + case VTXIOCCLRPAGE_OLD: + cmd = VTXIOCCLRPAGE; + break; + case VTXIOCCLRFOUND_OLD: + cmd = VTXIOCCLRFOUND; + break; + case VTXIOCPAGEREQ_OLD: + cmd = VTXIOCPAGEREQ; + break; + case VTXIOCGETSTAT_OLD: + cmd = VTXIOCGETSTAT; + break; + case VTXIOCGETPAGE_OLD: + cmd = VTXIOCGETPAGE; + break; + case VTXIOCSTOPDAU_OLD: + cmd = VTXIOCSTOPDAU; + break; + case VTXIOCPUTPAGE_OLD: + cmd = VTXIOCPUTPAGE; + break; + case VTXIOCSETDISP_OLD: + cmd = VTXIOCSETDISP; + break; + case VTXIOCPUTSTAT_OLD: + cmd = VTXIOCPUTSTAT; + break; + case VTXIOCCLRCACHE_OLD: + cmd = VTXIOCCLRCACHE; + break; + case VTXIOCSETVIRT_OLD: + cmd = VTXIOCSETVIRT; + break; + } + return cmd; +} + +/* + * Handle the locking + */ + +static int saa5249_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + int err; + + cmd = vtx_fix_command(cmd); + mutex_lock(&t->lock); + err = video_usercopy(inode,file,cmd,arg,do_saa5249_ioctl); + mutex_unlock(&t->lock); + return err; +} + +static int saa5249_open(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + struct saa5249_device *t=vd->priv; + int err,pgbuf; + + err = video_exclusive_open(inode,file); + if (err < 0) + return err; + + if (t->client==NULL) { + err = -ENODEV; + goto fail; + } + + 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 */ + + { + err = -EIO; + goto fail; + } + + 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; + return 0; + + fail: + video_exclusive_release(inode,file); + return err; +} + + + +static int saa5249_release(struct inode *inode, struct file *file) +{ + struct video_device *vd = video_devdata(file); + 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 */ + video_exclusive_release(inode,file); + return 0; +} + +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 file_operations saa_fops = { + .owner = THIS_MODULE, + .open = saa5249_open, + .release = saa5249_release, + .ioctl = saa5249_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; + +static struct video_device saa_template = +{ + .owner = THIS_MODULE, + .name = IF_NAME, + .type = VID_TYPE_TELETEXT, /*| VID_TYPE_TUNER ?? */ + .hardware = VID_HARDWARE_SAA5249, + .fops = &saa_fops, +}; + +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/saa7110.c b/linux/drivers/media/video/saa7110.c new file mode 100644 index 000000000..b1797cbb6 --- /dev/null +++ b/linux/drivers/media/video/saa7110.c @@ -0,0 +1,604 @@ +/* + * saa7110 - Philips SAA7110(A) video decoder driver + * + * Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl> + * + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * - some corrections for Pinnacle Systems Inc. DC10plus card. + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * 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/init.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7110 video decoder driver"); +MODULE_AUTHOR("Pauline Middelink"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include "compat.h" +#include <linux/videodev.h> +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +#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 SAA7110_NR_REG 0x35 + +struct saa7110 { + u8 reg[SAA7110_NR_REG]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; + + wait_queue_head_t wq; +}; + +/* ----------------------------------------------------------------------- */ +/* I2C support functions */ +/* ----------------------------------------------------------------------- */ + +static int +saa7110_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7110_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg = *data; /* first register to write to */ + + /* Sanity check */ + if (reg + (len - 1) > SAA7110_NR_REG) + return ret; + + /* the saa7110 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + struct saa7110 *decoder = i2c_get_clientdata(client); + + ret = i2c_master_send(client, data, len); + + /* Cache the written data */ + memcpy(decoder->reg + reg, data + 1, len - 1); + } else { + for (++data, --len; len; len--) { + if ((ret = saa7110_write(client, reg++, + *data++)) < 0) + break; + } + } + + return ret; +} + +static inline int +saa7110_read (struct i2c_client *client) +{ + return i2c_smbus_read_byte(client); +} + +/* ----------------------------------------------------------------------- */ +/* SAA7110 functions */ +/* ----------------------------------------------------------------------- */ + +#define FRESP_06H_COMPST 0x03 //0x13 +#define FRESP_06H_SVIDEO 0x83 //0xC0 + + +static int +saa7110_selmux (struct i2c_client *client, + int chan) +{ + static const unsigned char modes[9][8] = { + /* mode 0 */ + {FRESP_06H_COMPST, 0xD9, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 1 */ + {FRESP_06H_COMPST, 0xD8, 0x17, 0x40, 0x03, + 0x44, 0x75, 0x16}, + /* mode 2 */ + {FRESP_06H_COMPST, 0xBA, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 3 */ + {FRESP_06H_COMPST, 0xB8, 0x07, 0x91, 0x03, + 0x60, 0xB5, 0x05}, + /* mode 4 */ + {FRESP_06H_COMPST, 0x7C, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 5 */ + {FRESP_06H_COMPST, 0x78, 0x07, 0xD2, 0x83, + 0x60, 0xB5, 0x03}, + /* mode 6 */ + {FRESP_06H_SVIDEO, 0x59, 0x17, 0x42, 0xA3, + 0x44, 0x75, 0x12}, + /* mode 7 */ + {FRESP_06H_SVIDEO, 0x9A, 0x17, 0xB1, 0x13, + 0x60, 0xB5, 0x14}, + /* mode 8 */ + {FRESP_06H_SVIDEO, 0x3C, 0x27, 0xC1, 0x23, + 0x44, 0x75, 0x21} + }; + struct saa7110 *decoder = i2c_get_clientdata(client); + const unsigned char *ptr = modes[chan]; + + saa7110_write(client, 0x06, ptr[0]); /* Luminance control */ + saa7110_write(client, 0x20, ptr[1]); /* Analog Control #1 */ + saa7110_write(client, 0x21, ptr[2]); /* Analog Control #2 */ + saa7110_write(client, 0x22, ptr[3]); /* Mixer Control #1 */ + saa7110_write(client, 0x2C, ptr[4]); /* Mixer Control #2 */ + saa7110_write(client, 0x30, ptr[5]); /* ADCs gain control */ + saa7110_write(client, 0x31, ptr[6]); /* Mixer Control #3 */ + saa7110_write(client, 0x21, ptr[7]); /* Analog Control #2 */ + decoder->input = chan; + + return 0; +} + +static const unsigned char initseq[1 + SAA7110_NR_REG] = { + 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF2, 0x03, 0x00, + /* 0x08 */ 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x86, 0x18, 0x90, + /* 0x10 */ 0x00, 0x59, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA, + /* 0x18 */ 0xF2, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0xD9, 0x16, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F, + /* 0x28 */ 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x03, 0x0C, + /* 0x30 */ 0x44, 0x71, 0x02, 0x8C, 0x02 +}; + +static int +determine_norm (struct i2c_client *client) +{ + DEFINE_WAIT(wait); + struct saa7110 *decoder = i2c_get_clientdata(client); + int status; + + /* mode changed, start automatic detection */ + saa7110_write_block(client, initseq, sizeof(initseq)); + saa7110_selmux(client, decoder->input); + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/4); + finish_wait(&decoder->wq, &wait); + status = saa7110_read(client); + if (status & 0x40) { + dprintk(1, KERN_INFO "%s: status=0x%02x (no signal)\n", + I2C_NAME(client), status); + return decoder->norm; // no change + } + if ((status & 3) == 0) { + saa7110_write(client, 0x06, 0x83); + if (status & 0x20) { + dprintk(1, + KERN_INFO + "%s: status=0x%02x (NTSC/no color)\n", + I2C_NAME(client), status); + //saa7110_write(client,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + dprintk(1, KERN_INFO "%s: status=0x%02x (PAL/no color)\n", + I2C_NAME(client), status); + //saa7110_write(client,0x2E,0x9A); + return VIDEO_MODE_PAL; + } + //saa7110_write(client,0x06,0x03); + if (status & 0x20) { /* 60Hz */ + dprintk(1, KERN_INFO "%s: status=0x%02x (NTSC)\n", + I2C_NAME(client), status); + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x50); + saa7110_write(client, 0x11, 0x2C); + //saa7110_write(client,0x2E,0x81); + return VIDEO_MODE_NTSC; + } + + /* 50Hz -> PAL/SECAM */ + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client,0x2E,0x9A); + + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/4); + finish_wait(&decoder->wq, &wait); + + status = saa7110_read(client); + if ((status & 0x03) == 0x01) { + dprintk(1, KERN_INFO "%s: status=0x%02x (SECAM)\n", + I2C_NAME(client), status); + saa7110_write(client, 0x0D, 0x87); + return VIDEO_MODE_SECAM; + } + dprintk(1, KERN_INFO "%s: status=0x%02x (PAL)\n", I2C_NAME(client), + status); + return VIDEO_MODE_PAL; +} + +static int +saa7110_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + int v; + + switch (cmd) { + case 0: + //saa7110_write_block(client, initseq, sizeof(initseq)); + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *dc = arg; + + dc->flags = + VIDEO_DECODER_PAL | VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | VIDEO_DECODER_AUTO; + dc->inputs = SAA7110_MAX_INPUT; + dc->outputs = SAA7110_MAX_OUTPUT; + } + break; + + case DECODER_GET_STATUS: + { + int status; + int res = 0; + + status = saa7110_read(client); + dprintk(1, KERN_INFO "%s: status=0x%02x norm=%d\n", + I2C_NAME(client), status, decoder->norm); + 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(client, 0x06, 0x03); + switch (v) { + case VIDEO_MODE_NTSC: + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x50); + saa7110_write(client, 0x11, 0x2C); + //saa7110_write(client, 0x2E, 0x81); + dprintk(1, + KERN_INFO "%s: switched to NTSC\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_PAL: + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + dprintk(1, + KERN_INFO "%s: switched to PAL\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_SECAM: + saa7110_write(client, 0x0D, 0x87); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + dprintk(1, + KERN_INFO + "%s: switched to SECAM\n", + I2C_NAME(client)); + break; + case VIDEO_MODE_AUTO: + dprintk(1, + KERN_INFO + "%s: TV standard detection...\n", + I2C_NAME(client)); + decoder->norm = determine_norm(client); + *(int *) arg = decoder->norm; + break; + default: + return -EPERM; + } + } + break; + + case DECODER_SET_INPUT: + v = *(int *) arg; + if (v < 0 || v > SAA7110_MAX_INPUT) { + dprintk(1, + KERN_INFO "%s: input=%d not available\n", + I2C_NAME(client), v); + return -EINVAL; + } + if (decoder->input != v) { + saa7110_selmux(client, v); + dprintk(1, KERN_INFO "%s: switched to input=%d\n", + I2C_NAME(client), 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(client, 0x0E, v ? 0x18 : 0x80); + dprintk(1, KERN_INFO "%s: YUV %s\n", I2C_NAME(client), + v ? "on" : "off"); + } + 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(client, 0x19, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7110_write(client, 0x13, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7110_write(client, 0x12, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7110_write(client, 0x07, + (decoder->hue >> 8) - 128); + } + } + break; + + case DECODER_DUMP: + for (v = 0; v < SAA7110_NR_REG; v += 16) { + int j; + dprintk(1, KERN_DEBUG "%s: %02x:", I2C_NAME(client), + v); + for (j = 0; j < 16 && v + j < SAA7110_NR_REG; j++) + dprintk(1, " %02x", decoder->reg[v + j]); + dprintk(1, "\n"); + } + break; + + default: + dprintk(1, KERN_INFO "unknown saa7110_command??(%d)\n", + cmd); + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { + I2C_SAA7110 >> 1, + (I2C_SAA7110 >> 1) + 1, + I2C_CLIENT_END +}; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_saa7110; + +static int +saa7110_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + struct i2c_client *client; + struct saa7110 *decoder; + int rv; + + dprintk(1, + KERN_INFO + "saa7110.c: detecting saa7110 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality + (adapter, + I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7110; + strlcpy(I2C_NAME(client), "saa7110", sizeof(I2C_NAME(client))); + + decoder = kzalloc(sizeof(struct saa7110), GFP_KERNEL); + if (decoder == 0) { + kfree(client); + return -ENOMEM; + } + decoder->norm = VIDEO_MODE_PAL; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + init_waitqueue_head(&decoder->wq); + i2c_set_clientdata(client, decoder); + + rv = i2c_attach_client(client); + if (rv) { + kfree(client); + kfree(decoder); + return rv; + } + + rv = saa7110_write_block(client, initseq, sizeof(initseq)); + if (rv < 0) + dprintk(1, KERN_ERR "%s_attach: init status %d\n", + I2C_NAME(client), rv); + else { + int ver, status; + saa7110_write(client, 0x21, 0x10); + saa7110_write(client, 0x0e, 0x18); + saa7110_write(client, 0x0D, 0x04); + ver = saa7110_read(client); + saa7110_write(client, 0x0D, 0x06); + //mdelay(150); + status = saa7110_read(client); + dprintk(1, + KERN_INFO + "%s_attach: SAA7110A version %x at 0x%02x, status=0x%02x\n", + I2C_NAME(client), ver, client->addr << 1, status); + saa7110_write(client, 0x0D, 0x86); + saa7110_write(client, 0x0F, 0x10); + saa7110_write(client, 0x11, 0x59); + //saa7110_write(client, 0x2E, 0x9A); + } + + //saa7110_selmux(client,0); + //determine_norm(client); + /* setup and implicit mode 0 select has been performed */ + + return 0; +} + +static int +saa7110_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7110.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7110_detect_client); +} + +static int +saa7110_detach_client (struct i2c_client *client) +{ + struct saa7110 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7110 = { + .driver = { + .name = "saa7110", + }, + + .id = I2C_DRIVERID_SAA7110, + + .attach_adapter = saa7110_attach_adapter, + .detach_client = saa7110_detach_client, + .command = saa7110_command, +}; + +static int __init +saa7110_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7110); +} + +static void __exit +saa7110_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7110); +} + +module_init(saa7110_init); +module_exit(saa7110_exit); diff --git a/linux/drivers/media/video/saa7111.c b/linux/drivers/media/video/saa7111.c new file mode 100644 index 000000000..1fbd343c3 --- /dev/null +++ b/linux/drivers/media/video/saa7111.c @@ -0,0 +1,614 @@ +/* + * saa7111 - Philips SAA7111A video decoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * Changes by Michael Hunold <michael@mihu.de> + * - implemented DECODER_SET_GPIO, DECODER_INIT, DECODER_SET_VBI_BYPASS + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7111 video decoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +#define SAA7111_NR_REG 0x18 + +struct saa7111 { + unsigned char reg[SAA7111_NR_REG]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7111 0x48 + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7111_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7111_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the saa7111 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct saa7111 *decoder = i2c_get_clientdata(client); + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + decoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7111_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static int +saa7111_init_decoder (struct i2c_client *client, + struct video_decoder_init *init) +{ + return saa7111_write_block(client, init->data, init->len); +} + +static inline int +saa7111_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static const unsigned char saa7111_i2c_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, 0xf3, /* 06 - HSB at 13(50Hz) / 17(60Hz) + * pixels after end of last line */ + /*0x07, 0x13, * 07 - HSS at 113(50Hz) / 117(60Hz) pixels + * after end of last line */ + 0x07, 0xe8, /* 07 - HSS seems to be needed to + * work with NTSC, too */ + 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 */ +}; + +static int +saa7111_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + break; + case DECODER_INIT: + { + struct video_decoder_init *init = arg; + if (NULL != init) + return saa7111_init_decoder(client, init); + else { + struct video_decoder_init vdi; + vdi.data = saa7111_i2c_init; + vdi.len = sizeof(saa7111_i2c_init); + return saa7111_init_decoder(client, &vdi); + } + } + + case DECODER_DUMP: + { + int i; + + for (i = 0; i < SAA7111_NR_REG; i += 16) { + int j; + + printk(KERN_DEBUG "%s: %03x", I2C_NAME(client), i); + for (j = 0; j < 16 && i + j < SAA7111_NR_REG; ++j) { + printk(" %02x", + saa7111_read(client, i + j)); + } + printk("\n"); + } + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | + 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(client, 0x1f); + dprintk(1, KERN_DEBUG "%s status: 0x%02x\n", I2C_NAME(client), + status); + 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; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + 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_GPIO: + { + int *iarg = arg; + if (0 != *iarg) { + saa7111_write(client, 0x11, + (decoder->reg[0x11] | 0x80)); + } else { + saa7111_write(client, 0x11, + (decoder->reg[0x11] & 0x7f)); + } + break; + } + + case DECODER_SET_VBI_BYPASS: + { + int *iarg = arg; + if (0 != *iarg) { + saa7111_write(client, 0x13, + (decoder->reg[0x13] & 0xf0) | 0x0a); + } else { + saa7111_write(client, 0x13, + (decoder->reg[0x13] & 0xf0)); + } + break; + } + + case DECODER_SET_NORM: + { + int *iarg = arg; + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x40); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + break; + + case VIDEO_MODE_PAL: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x00); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + break; + + case VIDEO_MODE_SECAM: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x00); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f) | 0x50); + break; + + case VIDEO_MODE_AUTO: + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0x3f) | 0x80); + saa7111_write(client, 0x0e, + (decoder->reg[0x0e] & 0x8f)); + 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(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | decoder->input); + /* bypass chrominance trap for modes 4..7 */ + saa7111_write(client, 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(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | + decoder->input); + saa7111_write(client, 0x08, + (decoder->reg[0x08] & 0xfb)); + saa7111_write(client, 0x11, + (decoder-> + reg[0x11] & 0xf3) | 0x0c); + } else { + saa7111_write(client, 0x02, + (decoder->reg[0x02] & 0xf8)); + saa7111_write(client, 0x08, + (decoder-> + reg[0x08] & 0xfb) | 0x04); + saa7111_write(client, 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(client, 0x0a, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7111_write(client, 0x0b, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7111_write(client, 0x0c, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7111_write(client, 0x0d, + (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_SAA7111 >> 1, I2C_CLIENT_END }; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_saa7111; + +static int +saa7111_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct saa7111 *decoder; + struct video_decoder_init vdi; + + dprintk(1, + KERN_INFO + "saa7111.c: detecting saa7111 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7111; + strlcpy(I2C_NAME(client), "saa7111", sizeof(I2C_NAME(client))); + + decoder = kzalloc(sizeof(struct saa7111), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + i2c_set_clientdata(client, decoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + vdi.data = saa7111_i2c_init; + vdi.len = sizeof(saa7111_i2c_init); + i = saa7111_init_decoder(client, &vdi); + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach error: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %x at address 0x%x\n", + I2C_NAME(client), saa7111_read(client, 0x00) >> 4, + client->addr << 1); + } + + return 0; +} + +static int +saa7111_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7111.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7111_detect_client); +} + +static int +saa7111_detach_client (struct i2c_client *client) +{ + struct saa7111 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7111 = { + .driver = { + .name = "saa7111", + }, + + .id = I2C_DRIVERID_SAA7111A, + + .attach_adapter = saa7111_attach_adapter, + .detach_client = saa7111_detach_client, + .command = saa7111_command, +}; + +static int __init +saa7111_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7111); +} + +static void __exit +saa7111_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7111); +} + +module_init(saa7111_init); +module_exit(saa7111_exit); diff --git a/linux/drivers/media/video/saa7114.c b/linux/drivers/media/video/saa7114.c new file mode 100644 index 000000000..45242ad54 --- /dev/null +++ b/linux/drivers/media/video/saa7114.c @@ -0,0 +1,1221 @@ +/* + * saa7114 - Philips SAA7114H video decoder driver version 0.0.1 + * + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Based on saa7111 driver by Dave Perks + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> + +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7114H video decoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(x) (x)->name + +#include <linux/video_decoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct saa7114 { + unsigned char reg[0xf0 * 2]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; + int playback; +}; + +#define I2C_SAA7114 0x42 +#define I2C_SAA7114A 0x40 + +#define I2C_DELAY 10 + + +//#define SAA_7114_NTSC_HSYNC_START (-3) +//#define SAA_7114_NTSC_HSYNC_STOP (-18) + +#define SAA_7114_NTSC_HSYNC_START (-17) +#define SAA_7114_NTSC_HSYNC_STOP (-32) + +//#define SAA_7114_NTSC_HOFFSET (5) +#define SAA_7114_NTSC_HOFFSET (6) +#define SAA_7114_NTSC_VOFFSET (10) +#define SAA_7114_NTSC_WIDTH (720) +#define SAA_7114_NTSC_HEIGHT (250) + +#define SAA_7114_SECAM_HSYNC_START (-17) +#define SAA_7114_SECAM_HSYNC_STOP (-32) + +#define SAA_7114_SECAM_HOFFSET (2) +#define SAA_7114_SECAM_VOFFSET (10) +#define SAA_7114_SECAM_WIDTH (720) +#define SAA_7114_SECAM_HEIGHT (300) + +#define SAA_7114_PAL_HSYNC_START (-17) +#define SAA_7114_PAL_HSYNC_STOP (-32) + +#define SAA_7114_PAL_HOFFSET (2) +#define SAA_7114_PAL_VOFFSET (10) +#define SAA_7114_PAL_WIDTH (720) +#define SAA_7114_PAL_HEIGHT (300) + + + +#define SAA_7114_VERTICAL_CHROMA_OFFSET 0 //0x50504040 +#define SAA_7114_VERTICAL_LUMA_OFFSET 0 + +#define REG_ADDR(x) (((x) << 1) + 1) +#define LOBYTE(x) ((unsigned char)((x) & 0xff)) +#define HIBYTE(x) ((unsigned char)(((x) >> 8) & 0xff)) +#define LOWORD(x) ((unsigned short int)((x) & 0xffff)) +#define HIWORD(x) ((unsigned short int)(((x) >> 16) & 0xffff)) + + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7114_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7114_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the saa7114 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = data[1]; + reg++; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7114_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +static inline int +saa7114_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +// initially set NTSC, composite + + +static const unsigned char init[] = { + 0x00, 0x00, /* 00 - ID byte , chip version, + * read only */ + 0x01, 0x08, /* 01 - X,X,X,X, IDEL3 to IDEL0 - + * horizontal increment delay, + * recommended position */ + 0x02, 0x00, /* 02 - FUSE=3, GUDL=2, MODE=0 ; + * input control */ + 0x03, 0x10, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, + * HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x90, /* 04 - GAI1=256 */ + 0x05, 0x90, /* 05 - GAI2=256 */ + 0x06, SAA_7114_NTSC_HSYNC_START, /* 06 - HSB: hsync start, + * depends on the video standard */ + 0x07, SAA_7114_NTSC_HSYNC_STOP, /* 07 - HSS: hsync stop, depends + *on the video standard */ + 0x08, 0xb8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, + * HPLL: free running in playback, locked + * in capture, VNOI=0 */ + 0x09, 0x80, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, + * UPTCV=0, APER=1; depends from input */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x44, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x84, /* 0e - CDTO, CSTD2 to 0, DCVF, FCTC, + * CCOMB; depends from video standard */ + 0x0f, 0x24, /* 0f - ACGC,CGAIN6 to CGAIN0; depends + * from video standard */ + 0x10, 0x03, /* 10 - OFFU1 to 0, OFFV1 to 0, CHBW, + * LCBW2 to 0 */ + 0x11, 0x59, /* 11 - COLO, RTP1, HEDL1 to 0, RTP0, + * YDEL2 to 0 */ + 0x12, 0xc9, /* 12 - RT signal control RTSE13 to 10 + * and 03 to 00 */ + 0x13, 0x80, /* 13 - RT/X port output control */ + 0x14, 0x00, /* 14 - analog, ADC, compatibility control */ + 0x15, 0x00, /* 15 - VGATE start FID change */ + 0x16, 0xfe, /* 16 - VGATE stop */ + 0x17, 0x00, /* 17 - Misc., VGATE MSBs */ + 0x18, 0x40, /* RAWG */ + 0x19, 0x80, /* RAWO */ + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0x00, /* status byte, read only */ + 0x20, 0x00, /* video decoder reserved part */ + 0x21, 0x00, + 0x22, 0x00, + 0x23, 0x00, + 0x24, 0x00, + 0x25, 0x00, + 0x26, 0x00, + 0x27, 0x00, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x00, + 0x2b, 0x00, + 0x2c, 0x00, + 0x2d, 0x00, + 0x2e, 0x00, + 0x2f, 0x00, + 0x30, 0xbc, /* audio clock generator */ + 0x31, 0xdf, + 0x32, 0x02, + 0x33, 0x00, + 0x34, 0xcd, + 0x35, 0xcc, + 0x36, 0x3a, + 0x37, 0x00, + 0x38, 0x03, + 0x39, 0x10, + 0x3a, 0x00, + 0x3b, 0x00, + 0x3c, 0x00, + 0x3d, 0x00, + 0x3e, 0x00, + 0x3f, 0x00, + 0x40, 0x00, /* VBI data slicer */ + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xff, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x40, // framing code + 0x59, 0x47, // horizontal offset + 0x5a, 0x06, // vertical offset + 0x5b, 0x83, // field offset + 0x5c, 0x00, // reserved + 0x5d, 0x3e, // header and data + 0x5e, 0x00, // sliced data + 0x5f, 0x00, // reserved + 0x60, 0x00, /* video decoder reserved part */ + 0x61, 0x00, + 0x62, 0x00, + 0x63, 0x00, + 0x64, 0x00, + 0x65, 0x00, + 0x66, 0x00, + 0x67, 0x00, + 0x68, 0x00, + 0x69, 0x00, + 0x6a, 0x00, + 0x6b, 0x00, + 0x6c, 0x00, + 0x6d, 0x00, + 0x6e, 0x00, + 0x6f, 0x00, + 0x70, 0x00, /* video decoder reserved part */ + 0x71, 0x00, + 0x72, 0x00, + 0x73, 0x00, + 0x74, 0x00, + 0x75, 0x00, + 0x76, 0x00, + 0x77, 0x00, + 0x78, 0x00, + 0x79, 0x00, + 0x7a, 0x00, + 0x7b, 0x00, + 0x7c, 0x00, + 0x7d, 0x00, + 0x7e, 0x00, + 0x7f, 0x00, + 0x80, 0x00, /* X-port, I-port and scaler */ + 0x81, 0x00, + 0x82, 0x00, + 0x83, 0x00, + 0x84, 0xc5, + 0x85, 0x0d, // hsync and vsync ? + 0x86, 0x40, + 0x87, 0x01, + 0x88, 0x00, + 0x89, 0x00, + 0x8a, 0x00, + 0x8b, 0x00, + 0x8c, 0x00, + 0x8d, 0x00, + 0x8e, 0x00, + 0x8f, 0x00, + 0x90, 0x03, /* Task A definition */ + 0x91, 0x08, + 0x92, 0x00, + 0x93, 0x40, + 0x94, 0x00, // window settings + 0x95, 0x00, + 0x96, 0x00, + 0x97, 0x00, + 0x98, 0x00, + 0x99, 0x00, + 0x9a, 0x00, + 0x9b, 0x00, + 0x9c, 0x00, + 0x9d, 0x00, + 0x9e, 0x00, + 0x9f, 0x00, + 0xa0, 0x01, /* horizontal integer prescaling ratio */ + 0xa1, 0x00, /* horizontal prescaler accumulation + * sequence length */ + 0xa2, 0x00, /* UV FIR filter, Y FIR filter, prescaler + * DC gain */ + 0xa3, 0x00, + 0xa4, 0x80, // luminance brightness + 0xa5, 0x40, // luminance gain + 0xa6, 0x40, // chrominance saturation + 0xa7, 0x00, + 0xa8, 0x00, // horizontal luminance scaling increment + 0xa9, 0x04, + 0xaa, 0x00, // horizontal luminance phase offset + 0xab, 0x00, + 0xac, 0x00, // horizontal chrominance scaling increment + 0xad, 0x02, + 0xae, 0x00, // horizontal chrominance phase offset + 0xaf, 0x00, + 0xb0, 0x00, // vertical luminance scaling increment + 0xb1, 0x04, + 0xb2, 0x00, // vertical chrominance scaling increment + 0xb3, 0x04, + 0xb4, 0x00, + 0xb5, 0x00, + 0xb6, 0x00, + 0xb7, 0x00, + 0xb8, 0x00, + 0xb9, 0x00, + 0xba, 0x00, + 0xbb, 0x00, + 0xbc, 0x00, + 0xbd, 0x00, + 0xbe, 0x00, + 0xbf, 0x00, + 0xc0, 0x02, // Task B definition + 0xc1, 0x08, + 0xc2, 0x00, + 0xc3, 0x40, + 0xc4, 0x00, // window settings + 0xc5, 0x00, + 0xc6, 0x00, + 0xc7, 0x00, + 0xc8, 0x00, + 0xc9, 0x00, + 0xca, 0x00, + 0xcb, 0x00, + 0xcc, 0x00, + 0xcd, 0x00, + 0xce, 0x00, + 0xcf, 0x00, + 0xd0, 0x01, // horizontal integer prescaling ratio + 0xd1, 0x00, // horizontal prescaler accumulation sequence length + 0xd2, 0x00, // UV FIR filter, Y FIR filter, prescaler DC gain + 0xd3, 0x00, + 0xd4, 0x80, // luminance brightness + 0xd5, 0x40, // luminance gain + 0xd6, 0x40, // chrominance saturation + 0xd7, 0x00, + 0xd8, 0x00, // horizontal luminance scaling increment + 0xd9, 0x04, + 0xda, 0x00, // horizontal luminance phase offset + 0xdb, 0x00, + 0xdc, 0x00, // horizontal chrominance scaling increment + 0xdd, 0x02, + 0xde, 0x00, // horizontal chrominance phase offset + 0xdf, 0x00, + 0xe0, 0x00, // vertical luminance scaling increment + 0xe1, 0x04, + 0xe2, 0x00, // vertical chrominance scaling increment + 0xe3, 0x04, + 0xe4, 0x00, + 0xe5, 0x00, + 0xe6, 0x00, + 0xe7, 0x00, + 0xe8, 0x00, + 0xe9, 0x00, + 0xea, 0x00, + 0xeb, 0x00, + 0xec, 0x00, + 0xed, 0x00, + 0xee, 0x00, + 0xef, 0x00 +}; + +static int +saa7114_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7114 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + //dprintk(1, KERN_INFO "%s: writing init\n", I2C_NAME(client)); + //saa7114_write_block(client, init, sizeof(init)); + break; + + case DECODER_DUMP: + { + int i; + + dprintk(1, KERN_INFO "%s: decoder dump\n", I2C_NAME(client)); + + for (i = 0; i < 32; i += 16) { + int j; + + printk(KERN_DEBUG "%s: %03x", I2C_NAME(client), i); + for (j = 0; j < 16; ++j) { + printk(" %02x", + saa7114_read(client, i + j)); + } + printk("\n"); + } + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + dprintk(1, KERN_DEBUG "%s: decoder get capabilities\n", + I2C_NAME(client)); + + 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 = saa7114_read(client, 0x1f); + + dprintk(1, KERN_DEBUG "%s status: 0x%02x\n", I2C_NAME(client), + status); + 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; + case VIDEO_MODE_SECAM: + res |= DECODER_STATUS_SECAM; + 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; + + short int hoff = 0, voff = 0, w = 0, h = 0; + + dprintk(1, KERN_DEBUG "%s: decoder set norm ", + I2C_NAME(client)); + switch (*iarg) { + + case VIDEO_MODE_NTSC: + dprintk(1, "NTSC\n"); + decoder->reg[REG_ADDR(0x06)] = + SAA_7114_NTSC_HSYNC_START; + decoder->reg[REG_ADDR(0x07)] = + SAA_7114_NTSC_HSYNC_STOP; + + decoder->reg[REG_ADDR(0x08)] = decoder->playback ? 0x7c : 0xb8; // PLL free when playback, PLL close when capture + + decoder->reg[REG_ADDR(0x0e)] = 0x85; + decoder->reg[REG_ADDR(0x0f)] = 0x24; + + hoff = SAA_7114_NTSC_HOFFSET; + voff = SAA_7114_NTSC_VOFFSET; + w = SAA_7114_NTSC_WIDTH; + h = SAA_7114_NTSC_HEIGHT; + + break; + + case VIDEO_MODE_PAL: + dprintk(1, "PAL\n"); + decoder->reg[REG_ADDR(0x06)] = + SAA_7114_PAL_HSYNC_START; + decoder->reg[REG_ADDR(0x07)] = + SAA_7114_PAL_HSYNC_STOP; + + decoder->reg[REG_ADDR(0x08)] = decoder->playback ? 0x7c : 0xb8; // PLL free when playback, PLL close when capture + + decoder->reg[REG_ADDR(0x0e)] = 0x81; + decoder->reg[REG_ADDR(0x0f)] = 0x24; + + hoff = SAA_7114_PAL_HOFFSET; + voff = SAA_7114_PAL_VOFFSET; + w = SAA_7114_PAL_WIDTH; + h = SAA_7114_PAL_HEIGHT; + + break; + + default: + dprintk(1, " Unknown video mode!!!\n"); + return -EINVAL; + + } + + + decoder->reg[REG_ADDR(0x94)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0x95)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0x96)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0x97)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0x98)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0x99)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0x9a)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0x9b)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0x9c)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0x9d)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0x9e)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0x9f)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xc4)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0xc5)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0xc6)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0xc7)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0xc8)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0xc9)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0xca)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0xcb)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0xcc)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0xcd)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0xce)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0xcf)] = HIBYTE(h) & 0x0f; // out height high + + + saa7114_write(client, 0x80, 0x06); // i-port and scaler back end clock selection, task A&B off + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + + saa7114_write_block(client, decoder->reg + (0x06 << 1), + 3 << 1); + saa7114_write_block(client, decoder->reg + (0x0e << 1), + 2 << 1); + saa7114_write_block(client, decoder->reg + (0x5a << 1), + 2 << 1); + + saa7114_write_block(client, decoder->reg + (0x94 << 1), + (0x9f + 1 - 0x94) << 1); + saa7114_write_block(client, decoder->reg + (0xc4 << 1), + (0xcf + 1 - 0xc4) << 1); + + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + saa7114_write(client, 0x80, 0x36); // i-port and scaler back end clock selection + + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + dprintk(1, KERN_DEBUG "%s: decoder set input (%d)\n", + I2C_NAME(client), *iarg); + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + dprintk(1, KERN_DEBUG "%s: now setting %s input\n", + I2C_NAME(client), + *iarg >= 6 ? "S-Video" : "Composite"); + decoder->input = *iarg; + + /* select mode */ + decoder->reg[REG_ADDR(0x02)] = + (decoder-> + reg[REG_ADDR(0x02)] & 0xf0) | (decoder-> + input < + 6 ? 0x0 : 0x9); + saa7114_write(client, 0x02, + decoder->reg[REG_ADDR(0x02)]); + + /* bypass chrominance trap for modes 6..9 */ + decoder->reg[REG_ADDR(0x09)] = + (decoder-> + reg[REG_ADDR(0x09)] & 0x7f) | (decoder-> + input < + 6 ? 0x0 : + 0x80); + saa7114_write(client, 0x09, + decoder->reg[REG_ADDR(0x09)]); + + decoder->reg[REG_ADDR(0x0e)] = + decoder->input < + 6 ? decoder-> + reg[REG_ADDR(0x0e)] | 1 : decoder-> + reg[REG_ADDR(0x0e)] & ~1; + saa7114_write(client, 0x0e, + decoder->reg[REG_ADDR(0x0e)]); + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + dprintk(1, KERN_DEBUG "%s: decoder set output\n", + I2C_NAME(client)); + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + dprintk(1, KERN_DEBUG "%s: decoder %s output\n", + I2C_NAME(client), enable ? "enable" : "disable"); + + decoder->playback = !enable; + + 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) { + decoder->reg[REG_ADDR(0x08)] = 0xb8; + decoder->reg[REG_ADDR(0x12)] = 0xc9; + decoder->reg[REG_ADDR(0x13)] = 0x80; + decoder->reg[REG_ADDR(0x87)] = 0x01; + } else { + decoder->reg[REG_ADDR(0x08)] = 0x7c; + decoder->reg[REG_ADDR(0x12)] = 0x00; + decoder->reg[REG_ADDR(0x13)] = 0x00; + decoder->reg[REG_ADDR(0x87)] = 0x00; + } + + saa7114_write_block(client, + decoder->reg + (0x12 << 1), + 2 << 1); + saa7114_write(client, 0x08, + decoder->reg[REG_ADDR(0x08)]); + saa7114_write(client, 0x87, + decoder->reg[REG_ADDR(0x87)]); + saa7114_write(client, 0x88, 0xd8); // sw reset scaler + saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + saa7114_write(client, 0x80, 0x36); + + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + dprintk(1, + KERN_DEBUG + "%s: decoder set picture bright=%d contrast=%d saturation=%d hue=%d\n", + I2C_NAME(client), pic->brightness, pic->contrast, + pic->colour, pic->hue); + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + saa7114_write(client, 0x0a, decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + saa7114_write(client, 0x0b, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + saa7114_write(client, 0x0c, decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + saa7114_write(client, 0x0d, + (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_SAA7114 >> 1, I2C_SAA7114A >> 1, I2C_CLIENT_END }; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_saa7114; + +static int +saa7114_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i, err[30]; + short int hoff = SAA_7114_NTSC_HOFFSET; + short int voff = SAA_7114_NTSC_VOFFSET; + short int w = SAA_7114_NTSC_WIDTH; + short int h = SAA_7114_NTSC_HEIGHT; + struct i2c_client *client; + struct saa7114 *decoder; + + dprintk(1, + KERN_INFO + "saa7114.c: detecting saa7114 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7114; + strlcpy(I2C_NAME(client), "saa7114", sizeof(I2C_NAME(client))); + + decoder = kzalloc(sizeof(struct saa7114), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = -1; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + decoder->playback = 0; // initially capture mode useda + i2c_set_clientdata(client, decoder); + + memcpy(decoder->reg, init, sizeof(init)); + + decoder->reg[REG_ADDR(0x94)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0x95)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0x96)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0x97)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0x98)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0x99)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0x9a)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0x9b)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0x9c)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0x9d)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0x9e)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0x9f)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xc4)] = LOBYTE(hoff); // hoffset low + decoder->reg[REG_ADDR(0xc5)] = HIBYTE(hoff) & 0x0f; // hoffset high + decoder->reg[REG_ADDR(0xc6)] = LOBYTE(w); // width low + decoder->reg[REG_ADDR(0xc7)] = HIBYTE(w) & 0x0f; // width high + decoder->reg[REG_ADDR(0xc8)] = LOBYTE(voff); // voffset low + decoder->reg[REG_ADDR(0xc9)] = HIBYTE(voff) & 0x0f; // voffset high + decoder->reg[REG_ADDR(0xca)] = LOBYTE(h + 2); // height low + decoder->reg[REG_ADDR(0xcb)] = HIBYTE(h + 2) & 0x0f; // height high + decoder->reg[REG_ADDR(0xcc)] = LOBYTE(w); // out width low + decoder->reg[REG_ADDR(0xcd)] = HIBYTE(w) & 0x0f; // out width high + decoder->reg[REG_ADDR(0xce)] = LOBYTE(h); // out height low + decoder->reg[REG_ADDR(0xcf)] = HIBYTE(h) & 0x0f; // out height high + + decoder->reg[REG_ADDR(0xb8)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xb9)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xba)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xbb)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + + decoder->reg[REG_ADDR(0xbc)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbd)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbe)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xbf)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + + decoder->reg[REG_ADDR(0xe8)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xe9)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xea)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + decoder->reg[REG_ADDR(0xeb)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_CHROMA_OFFSET)); + + decoder->reg[REG_ADDR(0xec)] = + LOBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xed)] = + HIBYTE(LOWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xee)] = + LOBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + decoder->reg[REG_ADDR(0xef)] = + HIBYTE(HIWORD(SAA_7114_VERTICAL_LUMA_OFFSET)); + + + decoder->reg[REG_ADDR(0x13)] = 0x80; // RTC0 on + decoder->reg[REG_ADDR(0x87)] = 0x01; // I-Port + decoder->reg[REG_ADDR(0x12)] = 0xc9; // RTS0 + + decoder->reg[REG_ADDR(0x02)] = 0xc0; // set composite1 input, aveasy + decoder->reg[REG_ADDR(0x09)] = 0x00; // chrominance trap + decoder->reg[REG_ADDR(0x0e)] |= 1; // combfilter on + + + dprintk(1, KERN_DEBUG "%s_attach: starting decoder init\n", + I2C_NAME(client)); + + err[0] = + saa7114_write_block(client, decoder->reg + (0x20 << 1), + 0x10 << 1); + err[1] = + saa7114_write_block(client, decoder->reg + (0x30 << 1), + 0x10 << 1); + err[2] = + saa7114_write_block(client, decoder->reg + (0x63 << 1), + (0x7f + 1 - 0x63) << 1); + err[3] = + saa7114_write_block(client, decoder->reg + (0x89 << 1), + 6 << 1); + err[4] = + saa7114_write_block(client, decoder->reg + (0xb8 << 1), + 8 << 1); + err[5] = + saa7114_write_block(client, decoder->reg + (0xe8 << 1), + 8 << 1); + + + for (i = 0; i <= 5; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + for (i = 6; i < 8; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + dprintk(1, + KERN_DEBUG + "%s_attach: performing decoder reset sequence\n", + I2C_NAME(client)); + + err[6] = saa7114_write(client, 0x80, 0x06); // i-port and scaler backend clock selection, task A&B off + err[7] = saa7114_write(client, 0x88, 0xd8); // sw reset scaler + err[8] = saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + + for (i = 6; i <= 8; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + dprintk(1, KERN_INFO "%s_attach: performing the rest of init\n", + I2C_NAME(client)); + + + err[9] = saa7114_write(client, 0x01, decoder->reg[REG_ADDR(0x01)]); + err[10] = saa7114_write_block(client, decoder->reg + (0x03 << 1), (0x1e + 1 - 0x03) << 1); // big seq + err[11] = saa7114_write_block(client, decoder->reg + (0x40 << 1), (0x5f + 1 - 0x40) << 1); // slicer + err[12] = saa7114_write_block(client, decoder->reg + (0x81 << 1), 2 << 1); // ? + err[13] = saa7114_write_block(client, decoder->reg + (0x83 << 1), 5 << 1); // ? + err[14] = saa7114_write_block(client, decoder->reg + (0x90 << 1), 4 << 1); // Task A + err[15] = + saa7114_write_block(client, decoder->reg + (0x94 << 1), + 12 << 1); + err[16] = + saa7114_write_block(client, decoder->reg + (0xa0 << 1), + 8 << 1); + err[17] = + saa7114_write_block(client, decoder->reg + (0xa8 << 1), + 8 << 1); + err[18] = + saa7114_write_block(client, decoder->reg + (0xb0 << 1), + 8 << 1); + err[19] = saa7114_write_block(client, decoder->reg + (0xc0 << 1), 4 << 1); // Task B + err[15] = + saa7114_write_block(client, decoder->reg + (0xc4 << 1), + 12 << 1); + err[16] = + saa7114_write_block(client, decoder->reg + (0xd0 << 1), + 8 << 1); + err[17] = + saa7114_write_block(client, decoder->reg + (0xd8 << 1), + 8 << 1); + err[18] = + saa7114_write_block(client, decoder->reg + (0xe0 << 1), + 8 << 1); + + for (i = 9; i <= 18; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + + for (i = 6; i < 8; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + + for (i = 0x11; i <= 0x13; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + + dprintk(1, KERN_DEBUG "%s_attach: setting video input\n", + I2C_NAME(client)); + + err[19] = + saa7114_write(client, 0x02, decoder->reg[REG_ADDR(0x02)]); + err[20] = + saa7114_write(client, 0x09, decoder->reg[REG_ADDR(0x09)]); + err[21] = + saa7114_write(client, 0x0e, decoder->reg[REG_ADDR(0x0e)]); + + for (i = 19; i <= 21; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + dprintk(1, + KERN_DEBUG + "%s_attach: performing decoder reset sequence\n", + I2C_NAME(client)); + + err[22] = saa7114_write(client, 0x88, 0xd8); // sw reset scaler + err[23] = saa7114_write(client, 0x88, 0xf8); // sw reset scaler release + err[24] = saa7114_write(client, 0x80, 0x36); // i-port and scaler backend clock selection, task A&B off + + + for (i = 22; i <= 24; i++) { + if (err[i] < 0) { + dprintk(1, + KERN_ERR + "%s_attach: init error %d at stage %d, leaving attach.\n", + I2C_NAME(client), i, err[i]); + kfree(decoder); + kfree(client); + return 0; + } + } + + err[25] = saa7114_write(client, 0x06, init[REG_ADDR(0x06)]); + err[26] = saa7114_write(client, 0x07, init[REG_ADDR(0x07)]); + err[27] = saa7114_write(client, 0x10, init[REG_ADDR(0x10)]); + + dprintk(1, + KERN_INFO + "%s_attach: chip version %x, decoder status 0x%02x\n", + I2C_NAME(client), saa7114_read(client, 0x00) >> 4, + saa7114_read(client, 0x1f)); + dprintk(1, + KERN_DEBUG + "%s_attach: power save control: 0x%02x, scaler status: 0x%02x\n", + I2C_NAME(client), saa7114_read(client, 0x88), + saa7114_read(client, 0x8f)); + + + for (i = 0x94; i < 0x96; i++) { + dprintk(1, + KERN_DEBUG + "%s_attach: reg[0x%02x] = 0x%02x (0x%02x)\n", + I2C_NAME(client), i, saa7114_read(client, i), + decoder->reg[REG_ADDR(i)]); + } + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(decoder); + return i; + } + + //i = saa7114_write_block(client, init, sizeof(init)); + i = 0; + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach error: init status %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %x at address 0x%x\n", + I2C_NAME(client), saa7114_read(client, 0x00) >> 4, + client->addr << 1); + } + + return 0; +} + +static int +saa7114_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7114.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7114_detect_client); +} + +static int +saa7114_detach_client (struct i2c_client *client) +{ + struct saa7114 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7114 = { + .driver = { + .name = "saa7114", + }, + + .id = I2C_DRIVERID_SAA7114, + + .attach_adapter = saa7114_attach_adapter, + .detach_client = saa7114_detach_client, + .command = saa7114_command, +}; + +static int __init +saa7114_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7114); +} + +static void __exit +saa7114_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7114); +} + +module_init(saa7114_init); +module_exit(saa7114_exit); diff --git a/linux/drivers/media/video/saa7121.h b/linux/drivers/media/video/saa7121.h new file mode 100644 index 000000000..66967ae37 --- /dev/null +++ b/linux/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/linux/drivers/media/video/saa7146.h b/linux/drivers/media/video/saa7146.h new file mode 100644 index 000000000..2830b5e33 --- /dev/null +++ b/linux/drivers/media/video/saa7146.h @@ -0,0 +1,114 @@ +/* + 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/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; + 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 __iomem *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), saa->saa7146_adr+(adr)) +#define saaread(adr) readl(saa->saa7146_adr+(adr)) +#else +#define saawrite(dat,adr) writel((dat), 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/linux/drivers/media/video/saa7146reg.h b/linux/drivers/media/video/saa7146reg.h new file mode 100644 index 000000000..80ec2c146 --- /dev/null +++ b/linux/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/linux/drivers/media/video/saa7185.c b/linux/drivers/media/video/saa7185.c new file mode 100644 index 000000000..a9b45c25a --- /dev/null +++ b/linux/drivers/media/video/saa7185.c @@ -0,0 +1,508 @@ +/* + * saa7185 - Philips SAA7185B video encoder driver version 0.0.3 + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net> + * - moved over to linux>=2.4.x i2c protocol (1/1/2003) + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> + +#include "compat.h" +#include <linux/videodev.h> +#include <asm/uaccess.h> + +MODULE_DESCRIPTION("Philips SAA7185 video encoder driver"); +MODULE_AUTHOR("Dave Perks"); +MODULE_LICENSE("GPL"); + +#include <linux/i2c.h> + +#define I2C_NAME(s) (s)->name + +#include <linux/video_encoder.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ----------------------------------------------------------------------- */ + +struct saa7185 { + unsigned char reg[128]; + + int norm; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +#define I2C_SAA7185 0x88 + +/* ----------------------------------------------------------------------- */ + +static inline int +saa7185_read (struct i2c_client *client) +{ + return i2c_smbus_read_byte(client); +} + +static int +saa7185_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + + dprintk(1, KERN_DEBUG "SAA7185: %02x set to %02x\n", reg, value); + encoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int +saa7185_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + int ret = -1; + u8 reg; + + /* the adv7175 has an autoincrement function, use it if + * the adapter understands raw I2C */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + /* do raw I2C, not smbus compatible */ + struct saa7185 *encoder = i2c_get_clientdata(client); + u8 block_data[32]; + int block_len; + + while (len >= 2) { + block_len = 0; + block_data[block_len++] = reg = data[0]; + do { + block_data[block_len++] = + encoder->reg[reg++] = data[1]; + len -= 2; + data += 2; + } while (len >= 2 && data[0] == reg && + block_len < 32); + if ((ret = i2c_master_send(client, block_data, + block_len)) < 0) + break; + } + } else { + /* do some slow I2C emulation kind of thing */ + while (len >= 2) { + reg = *data++; + if ((ret = saa7185_write(client, reg, + *data++)) < 0) + break; + len -= 2; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------- */ + +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=0x005, 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_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + + switch (cmd) { + + case 0: + saa7185_write_block(client, init_common, + sizeof(init_common)); + switch (encoder->norm) { + + case VIDEO_MODE_NTSC: + saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + break; + + case VIDEO_MODE_PAL: + saa7185_write_block(client, init_pal, + sizeof(init_pal)); + break; + } + + break; + + 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; + + //saa7185_write_block(client, init_common, sizeof(init_common)); + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + break; + + case VIDEO_MODE_PAL: + saa7185_write_block(client, init_pal, + sizeof(init_pal)); + break; + + case VIDEO_MODE_SECAM: + default: + return -EINVAL; + + } + encoder->norm = *iarg; + } + break; + + case ENCODER_SET_INPUT: + { + int *iarg = arg; + + /* RJ: *iarg = 0: input is from SA7111 + *iarg = 1: input is from ZR36060 */ + + switch (*iarg) { + + case 0: + /* Switch RTCE to 1 */ + saa7185_write(client, 0x61, + (encoder->reg[0x61] & 0xf7) | 0x08); + saa7185_write(client, 0x6e, 0x01); + break; + + case 1: + /* Switch RTCE to 0 */ + saa7185_write(client, 0x61, + (encoder->reg[0x61] & 0xf7) | 0x00); + /* SW: a slight sync problem... */ + saa7185_write(client, 0x6e, 0x00); + break; + + default: + return -EINVAL; + + } + } + 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(client, 0x61, + (encoder->reg[0x61] & 0xbf) | + (encoder->enable ? 0x00 : 0x40)); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = { I2C_SAA7185 >> 1, I2C_CLIENT_END }; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver i2c_driver_saa7185; + +static int +saa7185_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int i; + struct i2c_client *client; + struct saa7185 *encoder; + + dprintk(1, + KERN_INFO + "saa7185.c: detecting saa7185 client on address 0x%x\n", + address << 1); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7185; + strlcpy(I2C_NAME(client), "saa7185", sizeof(I2C_NAME(client))); + + encoder = kzalloc(sizeof(struct saa7185), GFP_KERNEL); + if (encoder == NULL) { + kfree(client); + return -ENOMEM; + } + encoder->norm = VIDEO_MODE_NTSC; + encoder->enable = 1; + i2c_set_clientdata(client, encoder); + + i = i2c_attach_client(client); + if (i) { + kfree(client); + kfree(encoder); + return i; + } + + i = saa7185_write_block(client, init_common, sizeof(init_common)); + if (i >= 0) { + i = saa7185_write_block(client, init_ntsc, + sizeof(init_ntsc)); + } + if (i < 0) { + dprintk(1, KERN_ERR "%s_attach: init error %d\n", + I2C_NAME(client), i); + } else { + dprintk(1, + KERN_INFO + "%s_attach: chip version %d at address 0x%x\n", + I2C_NAME(client), saa7185_read(client) >> 5, + client->addr << 1); + } + + return 0; +} + +static int +saa7185_attach_adapter (struct i2c_adapter *adapter) +{ + dprintk(1, + KERN_INFO + "saa7185.c: starting probe for adapter %s (0x%x)\n", + I2C_NAME(adapter), adapter->id); + return i2c_probe(adapter, &addr_data, &saa7185_detect_client); +} + +static int +saa7185_detach_client (struct i2c_client *client) +{ + struct saa7185 *encoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + saa7185_write(client, 0x61, (encoder->reg[0x61]) | 0x40); /* SW: output off is active */ + //saa7185_write(client, 0x3a, (encoder->reg[0x3a]) | 0x80); /* SW: color bar */ + + kfree(encoder); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7185 = { + .driver = { + .name = "saa7185", /* name */ + }, + + .id = I2C_DRIVERID_SAA7185B, + + .attach_adapter = saa7185_attach_adapter, + .detach_client = saa7185_detach_client, + .command = saa7185_command, +}; + +static int __init +saa7185_init (void) +{ + return i2c_add_driver(&i2c_driver_saa7185); +} + +static void __exit +saa7185_exit (void) +{ + i2c_del_driver(&i2c_driver_saa7185); +} + +module_init(saa7185_init); +module_exit(saa7185_exit); diff --git a/linux/drivers/media/video/saa7191.c b/linux/drivers/media/video/saa7191.c new file mode 100644 index 000000000..746cadb8f --- /dev/null +++ b/linux/drivers/media/video/saa7191.c @@ -0,0 +1,808 @@ +/* + * saa7191.c - Philips SAA7191 video decoder driver + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <linux/videodev.h> +#include <linux/video_decoder.h> +#include <linux/i2c.h> + +#include "saa7191.h" + +#define SAA7191_MODULE_VERSION "0.0.5" + +MODULE_DESCRIPTION("Philips SAA7191 video decoder driver"); +MODULE_VERSION(SAA7191_MODULE_VERSION); +MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>"); +MODULE_LICENSE("GPL"); + +// #define SAA7191_DEBUG + +#ifdef SAA7191_DEBUG +#define dprintk(x...) printk("SAA7191: " x); +#else +#define dprintk(x...) +#endif + +#define SAA7191_SYNC_COUNT 30 +#define SAA7191_SYNC_DELAY 100 /* milliseconds */ + +struct saa7191 { + struct i2c_client *client; + + /* the register values are stored here as the actual + * I2C-registers are write-only */ + u8 reg[25]; + + int input; + int norm; +}; + +static struct i2c_driver i2c_driver_saa7191; + +static const u8 initseq[] = { + 0, /* Subaddress */ + + 0x50, /* (0x50) SAA7191_REG_IDEL */ + + /* 50 Hz signal timing */ + 0x30, /* (0x30) SAA7191_REG_HSYB */ + 0x00, /* (0x00) SAA7191_REG_HSYS */ + 0xe8, /* (0xe8) SAA7191_REG_HCLB */ + 0xb6, /* (0xb6) SAA7191_REG_HCLS */ + 0xf4, /* (0xf4) SAA7191_REG_HPHI */ + + /* control */ + SAA7191_LUMA_APER_1, /* (0x01) SAA7191_REG_LUMA - CVBS mode */ + 0x00, /* (0x00) SAA7191_REG_HUEC */ + 0xf8, /* (0xf8) SAA7191_REG_CKTQ */ + 0xf8, /* (0xf8) SAA7191_REG_CKTS */ + 0x90, /* (0x90) SAA7191_REG_PLSE */ + 0x90, /* (0x90) SAA7191_REG_SESE */ + 0x00, /* (0x00) SAA7191_REG_GAIN */ + SAA7191_STDC_NFEN | SAA7191_STDC_HRMV, /* (0x0c) SAA7191_REG_STDC + * - not SECAM, + * slow time constant */ + SAA7191_IOCK_OEDC | SAA7191_IOCK_OEHS | SAA7191_IOCK_OEVS + | SAA7191_IOCK_OEDY, /* (0x78) SAA7191_REG_IOCK + * - chroma from CVBS, GPSW1 & 2 off */ + SAA7191_CTL3_AUFD | SAA7191_CTL3_SCEN | SAA7191_CTL3_OFTS + | SAA7191_CTL3_YDEL0, /* (0x99) SAA7191_REG_CTL3 + * - automatic field detection */ + 0x00, /* (0x00) SAA7191_REG_CTL4 */ + 0x2c, /* (0x2c) SAA7191_REG_CHCV - PAL nominal value */ + 0x00, /* unused */ + 0x00, /* unused */ + + /* 60 Hz signal timing */ + 0x34, /* (0x34) SAA7191_REG_HS6B */ + 0x0a, /* (0x0a) SAA7191_REG_HS6S */ + 0xf4, /* (0xf4) SAA7191_REG_HC6B */ + 0xce, /* (0xce) SAA7191_REG_HC6S */ + 0xf4, /* (0xf4) SAA7191_REG_HP6I */ +}; + +/* SAA7191 register handling */ + +static u8 saa7191_read_reg(struct i2c_client *client, + u8 reg) +{ + return ((struct saa7191 *)i2c_get_clientdata(client))->reg[reg]; +} + +static int saa7191_read_status(struct i2c_client *client, + u8 *value) +{ + int ret; + + ret = i2c_master_recv(client, value, 1); + if (ret < 0) { + printk(KERN_ERR "SAA7191: saa7191_read_status(): read failed\n"); + return ret; + } + + return 0; +} + + +static int saa7191_write_reg(struct i2c_client *client, u8 reg, + u8 value) +{ + ((struct saa7191 *)i2c_get_clientdata(client))->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +/* the first byte of data must be the first subaddress number (register) */ +static int saa7191_write_block(struct i2c_client *client, + u8 length, u8 *data) +{ + int i; + int ret; + + struct saa7191 *decoder = (struct saa7191 *)i2c_get_clientdata(client); + for (i = 0; i < (length - 1); i++) { + decoder->reg[data[0] + i] = data[i + 1]; + } + + ret = i2c_master_send(client, data, length); + if (ret < 0) { + printk(KERN_ERR "SAA7191: saa7191_write_block(): " + "write failed\n"); + return ret; + } + + return 0; +} + +/* Helper functions */ + +static int saa7191_set_input(struct i2c_client *client, int input) +{ + struct saa7191 *decoder = i2c_get_clientdata(client); + u8 luma = saa7191_read_reg(client, SAA7191_REG_LUMA); + u8 iock = saa7191_read_reg(client, SAA7191_REG_IOCK); + int err; + + switch (input) { + case SAA7191_INPUT_COMPOSITE: /* Set Composite input */ + iock &= ~(SAA7191_IOCK_CHRS | SAA7191_IOCK_GPSW1 + | SAA7191_IOCK_GPSW2); + /* Chrominance trap active */ + luma &= ~SAA7191_LUMA_BYPS; + break; + case SAA7191_INPUT_SVIDEO: /* Set S-Video input */ + iock |= SAA7191_IOCK_CHRS | SAA7191_IOCK_GPSW2; + /* Chrominance trap bypassed */ + luma |= SAA7191_LUMA_BYPS; + break; + default: + return -EINVAL; + } + + err = saa7191_write_reg(client, SAA7191_REG_LUMA, luma); + if (err) + return -EIO; + err = saa7191_write_reg(client, SAA7191_REG_IOCK, iock); + if (err) + return -EIO; + + decoder->input = input; + + return 0; +} + +static int saa7191_set_norm(struct i2c_client *client, int norm) +{ + struct saa7191 *decoder = i2c_get_clientdata(client); + u8 stdc = saa7191_read_reg(client, SAA7191_REG_STDC); + u8 ctl3 = saa7191_read_reg(client, SAA7191_REG_CTL3); + u8 chcv = saa7191_read_reg(client, SAA7191_REG_CHCV); + int err; + + switch(norm) { + case SAA7191_NORM_PAL: + stdc &= ~SAA7191_STDC_SECS; + ctl3 &= ~(SAA7191_CTL3_AUFD | SAA7191_CTL3_FSEL); + chcv = SAA7191_CHCV_PAL; + break; + case SAA7191_NORM_NTSC: + stdc &= ~SAA7191_STDC_SECS; + ctl3 &= ~SAA7191_CTL3_AUFD; + ctl3 |= SAA7191_CTL3_FSEL; + chcv = SAA7191_CHCV_NTSC; + break; + case SAA7191_NORM_SECAM: + stdc |= SAA7191_STDC_SECS; + ctl3 &= ~(SAA7191_CTL3_AUFD | SAA7191_CTL3_FSEL); + chcv = SAA7191_CHCV_PAL; + break; + default: + return -EINVAL; + } + + err = saa7191_write_reg(client, SAA7191_REG_CTL3, ctl3); + if (err) + return -EIO; + err = saa7191_write_reg(client, SAA7191_REG_STDC, stdc); + if (err) + return -EIO; + err = saa7191_write_reg(client, SAA7191_REG_CHCV, chcv); + if (err) + return -EIO; + + decoder->norm = norm; + + dprintk("ctl3: %02x stdc: %02x chcv: %02x\n", ctl3, + stdc, chcv); + dprintk("norm: %d\n", norm); + + return 0; +} + +static int saa7191_wait_for_signal(struct i2c_client *client, u8 *status) +{ + int i = 0; + + dprintk("Checking for signal...\n"); + + for (i = 0; i < SAA7191_SYNC_COUNT; i++) { + if (saa7191_read_status(client, status)) + return -EIO; + + if (((*status) & SAA7191_STATUS_HLCK) == 0) { + dprintk("Signal found\n"); + return 0; + } + + msleep(SAA7191_SYNC_DELAY); + } + + dprintk("No signal\n"); + + return -EBUSY; +} + +static int saa7191_autodetect_norm_extended(struct i2c_client *client) +{ + u8 stdc = saa7191_read_reg(client, SAA7191_REG_STDC); + u8 ctl3 = saa7191_read_reg(client, SAA7191_REG_CTL3); + u8 status; + int err = 0; + + dprintk("SAA7191 extended signal auto-detection...\n"); + + stdc &= ~SAA7191_STDC_SECS; + ctl3 &= ~(SAA7191_CTL3_FSEL); + + err = saa7191_write_reg(client, SAA7191_REG_STDC, stdc); + if (err) { + err = -EIO; + goto out; + } + err = saa7191_write_reg(client, SAA7191_REG_CTL3, ctl3); + if (err) { + err = -EIO; + goto out; + } + + ctl3 |= SAA7191_CTL3_AUFD; + err = saa7191_write_reg(client, SAA7191_REG_CTL3, ctl3); + if (err) { + err = -EIO; + goto out; + } + + msleep(SAA7191_SYNC_DELAY); + + err = saa7191_wait_for_signal(client, &status); + if (err) + goto out; + + if (status & SAA7191_STATUS_FIDT) { + /* 60Hz signal -> NTSC */ + dprintk("60Hz signal: NTSC\n"); + return saa7191_set_norm(client, SAA7191_NORM_NTSC); + } + + /* 50Hz signal */ + dprintk("50Hz signal: Trying PAL...\n"); + + /* try PAL first */ + err = saa7191_set_norm(client, SAA7191_NORM_PAL); + if (err) + goto out; + + msleep(SAA7191_SYNC_DELAY); + + err = saa7191_wait_for_signal(client, &status); + if (err) + goto out; + + /* not 50Hz ? */ + if (status & SAA7191_STATUS_FIDT) { + dprintk("No 50Hz signal\n"); + err = -EAGAIN; + goto out; + } + + if (status & SAA7191_STATUS_CODE) { + dprintk("PAL\n"); + return 0; + } + + dprintk("No color detected with PAL - Trying SECAM...\n"); + + /* no color detected ? -> try SECAM */ + err = saa7191_set_norm(client, + SAA7191_NORM_SECAM); + if (err) + goto out; + + msleep(SAA7191_SYNC_DELAY); + + err = saa7191_wait_for_signal(client, &status); + if (err) + goto out; + + /* not 50Hz ? */ + if (status & SAA7191_STATUS_FIDT) { + dprintk("No 50Hz signal\n"); + err = -EAGAIN; + goto out; + } + + if (status & SAA7191_STATUS_CODE) { + /* Color detected -> SECAM */ + dprintk("SECAM\n"); + return 0; + } + + dprintk("No color detected with SECAM - Going back to PAL.\n"); + + /* still no color detected ? + * -> set norm back to PAL */ + err = saa7191_set_norm(client, + SAA7191_NORM_PAL); + if (err) + goto out; + +out: + ctl3 = saa7191_read_reg(client, SAA7191_REG_CTL3); + if (ctl3 & SAA7191_CTL3_AUFD) { + ctl3 &= ~(SAA7191_CTL3_AUFD); + err = saa7191_write_reg(client, SAA7191_REG_CTL3, ctl3); + if (err) { + err = -EIO; + } + } + + return err; +} + +static int saa7191_autodetect_norm(struct i2c_client *client) +{ + u8 status; + + dprintk("SAA7191 signal auto-detection...\n"); + + dprintk("Reading status...\n"); + + if (saa7191_read_status(client, &status)) + return -EIO; + + dprintk("Checking for signal...\n"); + + /* no signal ? */ + if (status & SAA7191_STATUS_HLCK) { + dprintk("No signal\n"); + return -EBUSY; + } + + dprintk("Signal found\n"); + + if (status & SAA7191_STATUS_FIDT) { + /* 60hz signal -> NTSC */ + dprintk("NTSC\n"); + return saa7191_set_norm(client, SAA7191_NORM_NTSC); + } else { + /* 50hz signal -> PAL */ + dprintk("PAL\n"); + return saa7191_set_norm(client, SAA7191_NORM_PAL); + } +} + +static int saa7191_get_control(struct i2c_client *client, + struct saa7191_control *ctrl) +{ + u8 reg; + int ret = 0; + + switch (ctrl->type) { + case SAA7191_CONTROL_BANDPASS: + case SAA7191_CONTROL_BANDPASS_WEIGHT: + case SAA7191_CONTROL_CORING: + reg = saa7191_read_reg(client, SAA7191_REG_LUMA); + switch (ctrl->type) { + case SAA7191_CONTROL_BANDPASS: + ctrl->value = ((s32)reg & SAA7191_LUMA_BPSS_MASK) + >> SAA7191_LUMA_BPSS_SHIFT; + break; + case SAA7191_CONTROL_BANDPASS_WEIGHT: + ctrl->value = ((s32)reg & SAA7191_LUMA_APER_MASK) + >> SAA7191_LUMA_APER_SHIFT; + break; + case SAA7191_CONTROL_CORING: + ctrl->value = ((s32)reg & SAA7191_LUMA_CORI_MASK) + >> SAA7191_LUMA_CORI_SHIFT; + break; + } + break; + case SAA7191_CONTROL_FORCE_COLOUR: + case SAA7191_CONTROL_CHROMA_GAIN: + reg = saa7191_read_reg(client, SAA7191_REG_GAIN); + if (ctrl->type == SAA7191_CONTROL_FORCE_COLOUR) + ctrl->value = ((s32)reg & SAA7191_GAIN_COLO) ? 1 : 0; + else + ctrl->value = ((s32)reg & SAA7191_GAIN_LFIS_MASK) + >> SAA7191_GAIN_LFIS_SHIFT; + break; + case SAA7191_CONTROL_HUE: + reg = saa7191_read_reg(client, SAA7191_REG_HUEC); + if (reg < 0x80) + reg += 0x80; + else + reg -= 0x80; + ctrl->value = (s32)reg; + break; + case SAA7191_CONTROL_VTRC: + reg = saa7191_read_reg(client, SAA7191_REG_STDC); + ctrl->value = ((s32)reg & SAA7191_STDC_VTRC) ? 1 : 0; + break; + case SAA7191_CONTROL_LUMA_DELAY: + reg = saa7191_read_reg(client, SAA7191_REG_CTL3); + ctrl->value = ((s32)reg & SAA7191_CTL3_YDEL_MASK) + >> SAA7191_CTL3_YDEL_SHIFT; + if (ctrl->value >= 4) + ctrl->value -= 8; + break; + case SAA7191_CONTROL_VNR: + reg = saa7191_read_reg(client, SAA7191_REG_CTL4); + ctrl->value = ((s32)reg & SAA7191_CTL4_VNOI_MASK) + >> SAA7191_CTL4_VNOI_SHIFT; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int saa7191_set_control(struct i2c_client *client, + struct saa7191_control *ctrl) +{ + u8 reg; + int ret = 0; + + switch (ctrl->type) { + case SAA7191_CONTROL_BANDPASS: + case SAA7191_CONTROL_BANDPASS_WEIGHT: + case SAA7191_CONTROL_CORING: + reg = saa7191_read_reg(client, SAA7191_REG_LUMA); + switch (ctrl->type) { + case SAA7191_CONTROL_BANDPASS: + reg &= ~SAA7191_LUMA_BPSS_MASK; + reg |= (ctrl->value << SAA7191_LUMA_BPSS_SHIFT) + & SAA7191_LUMA_BPSS_MASK; + break; + case SAA7191_CONTROL_BANDPASS_WEIGHT: + reg &= ~SAA7191_LUMA_APER_MASK; + reg |= (ctrl->value << SAA7191_LUMA_APER_SHIFT) + & SAA7191_LUMA_APER_MASK; + break; + case SAA7191_CONTROL_CORING: + reg &= ~SAA7191_LUMA_CORI_MASK; + reg |= (ctrl->value << SAA7191_LUMA_CORI_SHIFT) + & SAA7191_LUMA_CORI_MASK; + break; + } + ret = saa7191_write_reg(client, SAA7191_REG_LUMA, reg); + break; + case SAA7191_CONTROL_FORCE_COLOUR: + case SAA7191_CONTROL_CHROMA_GAIN: + reg = saa7191_read_reg(client, SAA7191_REG_GAIN); + if (ctrl->type == SAA7191_CONTROL_FORCE_COLOUR) { + if (ctrl->value) + reg |= SAA7191_GAIN_COLO; + else + reg &= ~SAA7191_GAIN_COLO; + } else { + reg &= ~SAA7191_GAIN_LFIS_MASK; + reg |= (ctrl->value << SAA7191_GAIN_LFIS_SHIFT) + & SAA7191_GAIN_LFIS_MASK; + } + ret = saa7191_write_reg(client, SAA7191_REG_GAIN, reg); + break; + case SAA7191_CONTROL_HUE: + reg = ctrl->value & 0xff; + if (reg < 0x80) + reg += 0x80; + else + reg -= 0x80; + ret = saa7191_write_reg(client, SAA7191_REG_HUEC, reg); + break; + case SAA7191_CONTROL_VTRC: + reg = saa7191_read_reg(client, SAA7191_REG_STDC); + if (ctrl->value) + reg |= SAA7191_STDC_VTRC; + else + reg &= ~SAA7191_STDC_VTRC; + ret = saa7191_write_reg(client, SAA7191_REG_STDC, reg); + break; + case SAA7191_CONTROL_LUMA_DELAY: { + s32 value = ctrl->value; + if (value < 0) + value += 8; + reg = saa7191_read_reg(client, SAA7191_REG_CTL3); + reg &= ~SAA7191_CTL3_YDEL_MASK; + reg |= (value << SAA7191_CTL3_YDEL_SHIFT) + & SAA7191_CTL3_YDEL_MASK; + ret = saa7191_write_reg(client, SAA7191_REG_CTL3, reg); + break; + } + case SAA7191_CONTROL_VNR: + reg = saa7191_read_reg(client, SAA7191_REG_CTL4); + reg &= ~SAA7191_CTL4_VNOI_MASK; + reg |= (ctrl->value << SAA7191_CTL4_VNOI_SHIFT) + & SAA7191_CTL4_VNOI_MASK; + ret = saa7191_write_reg(client, SAA7191_REG_CTL4, reg); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* I2C-interface */ + +static int saa7191_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int err = 0; + struct saa7191 *decoder; + struct i2c_client *client; + + printk(KERN_INFO "Philips SAA7191 driver version %s\n", + SAA7191_MODULE_VERSION); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + decoder = kzalloc(sizeof(*decoder), GFP_KERNEL); + if (!decoder) { + err = -ENOMEM; + goto out_free_client; + } + + client->addr = addr; + client->adapter = adap; + client->driver = &i2c_driver_saa7191; + client->flags = 0; + strcpy(client->name, "saa7191 client"); + i2c_set_clientdata(client, decoder); + + decoder->client = client; + + err = i2c_attach_client(client); + if (err) + goto out_free_decoder; + + err = saa7191_write_block(client, sizeof(initseq), (u8 *)initseq); + if (err) { + printk(KERN_ERR "SAA7191 initialization failed\n"); + goto out_detach_client; + } + + printk(KERN_INFO "SAA7191 initialized\n"); + + decoder->input = SAA7191_INPUT_COMPOSITE; + decoder->norm = SAA7191_NORM_PAL; + + err = saa7191_autodetect_norm(client); + if (err && (err != -EBUSY)) { + printk(KERN_ERR "SAA7191: Signal auto-detection failed\n"); + } + + return 0; + +out_detach_client: + i2c_detach_client(client); +out_free_decoder: + kfree(decoder); +out_free_client: + kfree(client); + return err; +} + +static int saa7191_probe(struct i2c_adapter *adap) +{ + /* Always connected to VINO */ + if (adap->id == I2C_HW_SGI_VINO) + return saa7191_attach(adap, SAA7191_ADDR, 0); + /* Feel free to add probe here :-) */ + return -ENODEV; +} + +static int saa7191_detach(struct i2c_client *client) +{ + struct saa7191 *decoder = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(decoder); + kfree(client); + return 0; +} + +static int saa7191_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + struct saa7191 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + case DECODER_GET_CAPABILITIES: { + struct video_decoder_capability *cap = arg; + + cap->flags = VIDEO_DECODER_PAL | VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | VIDEO_DECODER_AUTO; + cap->inputs = (client->adapter->id == I2C_HW_SGI_VINO) ? 2 : 1; + cap->outputs = 1; + break; + } + case DECODER_GET_STATUS: { + int *iarg = arg; + u8 status; + int res = 0; + + if (saa7191_read_status(client, &status)) { + return -EIO; + } + if ((status & SAA7191_STATUS_HLCK) == 0) + res |= DECODER_STATUS_GOOD; + if (status & SAA7191_STATUS_CODE) + res |= DECODER_STATUS_COLOR; + switch (decoder->norm) { + case SAA7191_NORM_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case SAA7191_NORM_PAL: + res |= DECODER_STATUS_PAL; + break; + case SAA7191_NORM_SECAM: + res |= DECODER_STATUS_SECAM; + break; + case SAA7191_NORM_AUTO: + default: + if (status & SAA7191_STATUS_FIDT) + res |= DECODER_STATUS_NTSC; + else + res |= DECODER_STATUS_PAL; + break; + } + *iarg = res; + break; + } + case DECODER_SET_NORM: { + int *iarg = arg; + + switch (*iarg) { + case VIDEO_MODE_AUTO: + return saa7191_autodetect_norm(client); + case VIDEO_MODE_PAL: + return saa7191_set_norm(client, SAA7191_NORM_PAL); + case VIDEO_MODE_NTSC: + return saa7191_set_norm(client, SAA7191_NORM_NTSC); + case VIDEO_MODE_SECAM: + return saa7191_set_norm(client, SAA7191_NORM_SECAM); + default: + return -EINVAL; + } + break; + } + case DECODER_SET_INPUT: { + int *iarg = arg; + + switch (client->adapter->id) { + case I2C_HW_SGI_VINO: + return saa7191_set_input(client, *iarg); + default: + if (*iarg != 0) + return -EINVAL; + } + break; + } + case DECODER_SET_OUTPUT: { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) + return -EINVAL; + break; + } + case DECODER_ENABLE_OUTPUT: { + /* Always enabled */ + break; + } + case DECODER_SET_PICTURE: { + struct video_picture *pic = arg; + unsigned val; + int err; + + val = (pic->hue >> 8) - 0x80; + + err = saa7191_write_reg(client, SAA7191_REG_HUEC, val); + if (err) + return -EIO; + + break; + } + case DECODER_SAA7191_GET_STATUS: { + struct saa7191_status *status = arg; + u8 status_reg; + + if (saa7191_read_status(client, &status_reg)) + return -EIO; + + status->signal = ((status_reg & SAA7191_STATUS_HLCK) == 0) + ? 1 : 0; + status->signal_60hz = (status_reg & SAA7191_STATUS_FIDT) + ? 1 : 0; + status->color = (status_reg & SAA7191_STATUS_CODE) ? 1 : 0; + + status->input = decoder->input; + status->norm = decoder->norm; + + break; + } + case DECODER_SAA7191_SET_NORM: { + int *norm = arg; + + switch (*norm) { + case SAA7191_NORM_AUTO: + return saa7191_autodetect_norm(client); + case SAA7191_NORM_AUTO_EXT: + return saa7191_autodetect_norm_extended(client); + default: + return saa7191_set_norm(client, *norm); + } + } + case DECODER_SAA7191_GET_CONTROL: { + return saa7191_get_control(client, arg); + } + case DECODER_SAA7191_SET_CONTROL: { + return saa7191_set_control(client, arg); + } + default: + return -EINVAL; + } + + return 0; +} + +static struct i2c_driver i2c_driver_saa7191 = { + .driver = { + .name = "saa7191", + }, + .id = I2C_DRIVERID_SAA7191, + .attach_adapter = saa7191_probe, + .detach_client = saa7191_detach, + .command = saa7191_command +}; + +static int saa7191_init(void) +{ + return i2c_add_driver(&i2c_driver_saa7191); +} + +static void saa7191_exit(void) +{ + i2c_del_driver(&i2c_driver_saa7191); +} + +module_init(saa7191_init); +module_exit(saa7191_exit); diff --git a/linux/drivers/media/video/saa7191.h b/linux/drivers/media/video/saa7191.h new file mode 100644 index 000000000..a2310da19 --- /dev/null +++ b/linux/drivers/media/video/saa7191.h @@ -0,0 +1,255 @@ +/* + * saa7191.h - Philips SAA7191 video decoder driver + * + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _SAA7191_H_ +#define _SAA7191_H_ + +/* Philips SAA7191 DMSD I2C bus address */ +#define SAA7191_ADDR 0x8a + +/* Register subaddresses. */ +#define SAA7191_REG_IDEL 0x00 +#define SAA7191_REG_HSYB 0x01 +#define SAA7191_REG_HSYS 0x02 +#define SAA7191_REG_HCLB 0x03 +#define SAA7191_REG_HCLS 0x04 +#define SAA7191_REG_HPHI 0x05 +#define SAA7191_REG_LUMA 0x06 +#define SAA7191_REG_HUEC 0x07 +#define SAA7191_REG_CKTQ 0x08 /* bits 3-7 */ +#define SAA7191_REG_CKTS 0x09 /* bits 3-7 */ +#define SAA7191_REG_PLSE 0x0a +#define SAA7191_REG_SESE 0x0b +#define SAA7191_REG_GAIN 0x0c +#define SAA7191_REG_STDC 0x0d +#define SAA7191_REG_IOCK 0x0e +#define SAA7191_REG_CTL3 0x0f +#define SAA7191_REG_CTL4 0x10 +#define SAA7191_REG_CHCV 0x11 +#define SAA7191_REG_HS6B 0x14 +#define SAA7191_REG_HS6S 0x15 +#define SAA7191_REG_HC6B 0x16 +#define SAA7191_REG_HC6S 0x17 +#define SAA7191_REG_HP6I 0x18 +#define SAA7191_REG_STATUS 0xff /* not really a subaddress */ + +/* Status Register definitions */ +#define SAA7191_STATUS_CODE 0x01 /* color detected flag */ +#define SAA7191_STATUS_FIDT 0x20 /* signal type 50/60 Hz */ +#define SAA7191_STATUS_HLCK 0x40 /* PLL unlocked(1)/locked(0) */ +#define SAA7191_STATUS_STTC 0x80 /* tv/vtr time constant */ + +/* Luminance Control Register definitions */ +/* input mode select bit: + * 0=CVBS (chrominance trap active), 1=S-Video (trap bypassed) */ +#define SAA7191_LUMA_BYPS 0x80 +/* pre-filter (only when chrominance trap is active) */ +#define SAA7191_LUMA_PREF 0x40 +/* aperture bandpass to select different characteristics with maximums + * (bits 4-5) */ +#define SAA7191_LUMA_BPSS_MASK 0x30 +#define SAA7191_LUMA_BPSS_SHIFT 4 +#define SAA7191_LUMA_BPSS_3 0x30 +#define SAA7191_LUMA_BPSS_2 0x20 +#define SAA7191_LUMA_BPSS_1 0x10 +#define SAA7191_LUMA_BPSS_0 0x00 +/* coring range for high frequency components according to 8-bit luminance + * (bits 2-3) + * 0=coring off, n= (+-)n LSB */ +#define SAA7191_LUMA_CORI_MASK 0x0c +#define SAA7191_LUMA_CORI_SHIFT 2 +#define SAA7191_LUMA_CORI_3 0x0c +#define SAA7191_LUMA_CORI_2 0x08 +#define SAA7191_LUMA_CORI_1 0x04 +#define SAA7191_LUMA_CORI_0 0x00 +/* aperture bandpass filter weights high frequency components of luminance + * signal (bits 0-1) + * 0=factor 0, 1=0.25, 2=0.5, 3=1 */ +#define SAA7191_LUMA_APER_MASK 0x03 +#define SAA7191_LUMA_APER_SHIFT 0 +#define SAA7191_LUMA_APER_3 0x03 +#define SAA7191_LUMA_APER_2 0x02 +#define SAA7191_LUMA_APER_1 0x01 +#define SAA7191_LUMA_APER_0 0x00 + +/* Chrominance Gain Control Settings Register definitions */ +/* colour on: 0=automatic colour-killer enabled, 1=forced colour on */ +#define SAA7191_GAIN_COLO 0x80 +/* chrominance gain control (AGC filter) + * 0=loop filter time constant slow, 1=medium, 2=fast, 3=actual gain */ +#define SAA7191_GAIN_LFIS_MASK 0x60 +#define SAA7191_GAIN_LFIS_SHIFT 5 +#define SAA7191_GAIN_LFIS_3 0x60 +#define SAA7191_GAIN_LFIS_2 0x40 +#define SAA7191_GAIN_LFIS_1 0x20 +#define SAA7191_GAIN_LFIS_0 0x00 + +/* Standard/Mode Control Register definitions */ +/* tv/vtr mode bit: 0=TV mode (slow time constant), + * 1=VTR mode (fast time constant) */ +#define SAA7191_STDC_VTRC 0x80 +/* SAA7191B-specific functions enable (RTCO, ODD and GPSW0 outputs) + * 0=outputs set to high-impedance (circuit equals SAA7191), 1=enabled */ +#define SAA7191_STDC_NFEN 0x08 +/* HREF generation: 0=like SAA7191, 1=HREF is 8xLLC2 clocks earlier */ +#define SAA7191_STDC_HRMV 0x04 +/* general purpose switch 0 + * (not used with VINO afaik) */ +#define SAA7191_STDC_GPSW0 0x02 +/* SECAM mode bit: 0=other standards, 1=SECAM */ +#define SAA7191_STDC_SECS 0x01 + +/* I/O and Clock Control Register definitions */ +/* horizontal clock PLL: 0=PLL closed, + * 1=PLL circuit open and horizontal freq fixed */ +#define SAA7191_IOCK_HPLL 0x80 +/* colour-difference output enable (outputs UV0-UV7) */ +#define SAA7191_IOCK_OEDC 0x40 +/* H-sync output enable */ +#define SAA7191_IOCK_OEHS 0x20 +/* V-sync output enable */ +#define SAA7191_IOCK_OEVS 0x10 +/* luminance output enable (outputs Y0-Y7) */ +#define SAA7191_IOCK_OEDY 0x08 +/* S-VHS bit (chrominance from CVBS or from chrominance input): + * 0=controlled by BYPS-bit, 1=from chrominance input */ +#define SAA7191_IOCK_CHRS 0x04 +/* general purpose switch 2 + * VINO-specific: 0=used with CVBS, 1=used with S-Video */ +#define SAA7191_IOCK_GPSW2 0x02 +/* general purpose switch 1 */ +/* VINO-specific: 0=always, 1=not used!*/ +#define SAA7191_IOCK_GPSW1 0x01 + +/* Miscellaneous Control #1 Register definitions */ +/* automatic field detection (50/60Hz standard) */ +#define SAA7191_CTL3_AUFD 0x80 +/* field select: (if AUFD=0) + * 0=50Hz (625 lines), 1=60Hz (525 lines) */ +#define SAA7191_CTL3_FSEL 0x40 +/* SECAM cross-colour reduction enable */ +#define SAA7191_CTL3_SXCR 0x20 +/* sync and clamping pulse enable (HCL and HSY outputs) */ +#define SAA7191_CTL3_SCEN 0x10 +/* output format: 0=4:1:1, 1=4:2:2 (4:2:2 for VINO) */ +#define SAA7191_CTL3_OFTS 0x08 +/* luminance delay compensation + * 0=0*2/LLC, 1=+1*2/LLC, 2=+2*2/LLC, 3=+3*2/LLC, + * 4=-4*2/LLC, 5=-3*2/LLC, 6=-2*2/LLC, 7=-1*2/LLC + * step size = 2/LLC = 67.8ns for 50Hz, 81.5ns for 60Hz */ +#define SAA7191_CTL3_YDEL_MASK 0x07 +#define SAA7191_CTL3_YDEL_SHIFT 0 +#define SAA7191_CTL3_YDEL2 0x04 +#define SAA7191_CTL3_YDEL1 0x02 +#define SAA7191_CTL3_YDEL0 0x01 + +/* Miscellaneous Control #2 Register definitions */ +/* select HREF position + * 0=normal, HREF is matched to YUV output port, + * 1=HREF is matched to CVBS input port */ +#define SAA7191_CTL4_HRFS 0x04 +/* vertical noise reduction + * 0=normal, 1=searching window, 2=auto-deflection, 3=reduction bypassed */ +#define SAA7191_CTL4_VNOI_MASK 0x03 +#define SAA7191_CTL4_VNOI_SHIFT 0 +#define SAA7191_CTL4_VNOI_3 0x03 +#define SAA7191_CTL4_VNOI_2 0x02 +#define SAA7191_CTL4_VNOI_1 0x01 +#define SAA7191_CTL4_VNOI_0 0x00 + +/* Chrominance Gain Control Register definitions + * - for QAM-modulated input signals, effects output amplitude + * (SECAM gain fixed) + * (nominal values for UV CCIR level) */ +#define SAA7191_CHCV_NTSC 0x2c +#define SAA7191_CHCV_PAL 0x59 + +/* Driver interface definitions */ +#define SAA7191_INPUT_COMPOSITE 0 +#define SAA7191_INPUT_SVIDEO 1 + +#define SAA7191_NORM_AUTO 0 +#define SAA7191_NORM_PAL 1 +#define SAA7191_NORM_NTSC 2 +#define SAA7191_NORM_SECAM 3 +#define SAA7191_NORM_AUTO_EXT 4 /* extended auto-detection */ + +struct saa7191_status { + /* 0=no signal, 1=signal detected */ + int signal; + /* 0=50hz (pal) signal, 1=60hz (ntsc) signal */ + int signal_60hz; + /* 0=no color detected, 1=color detected */ + int color; + + /* current SAA7191_INPUT_ */ + int input; + /* current SAA7191_NORM_ */ + int norm; +}; + +#define SAA7191_BANDPASS_MIN 0x00 +#define SAA7191_BANDPASS_MAX 0x03 +#define SAA7191_BANDPASS_DEFAULT 0x00 + +#define SAA7191_BANDPASS_WEIGHT_MIN 0x00 +#define SAA7191_BANDPASS_WEIGHT_MAX 0x03 +#define SAA7191_BANDPASS_WEIGHT_DEFAULT 0x01 + +#define SAA7191_CORING_MIN 0x00 +#define SAA7191_CORING_MAX 0x03 +#define SAA7191_CORING_DEFAULT 0x00 + +#define SAA7191_HUE_MIN 0x00 +#define SAA7191_HUE_MAX 0xff +#define SAA7191_HUE_DEFAULT 0x80 + +#define SAA7191_VTRC_MIN 0x00 +#define SAA7191_VTRC_MAX 0x01 +#define SAA7191_VTRC_DEFAULT 0x00 + +#define SAA7191_FORCE_COLOUR_MIN 0x00 +#define SAA7191_FORCE_COLOUR_MAX 0x01 +#define SAA7191_FORCE_COLOUR_DEFAULT 0x00 + +#define SAA7191_CHROMA_GAIN_MIN 0x00 +#define SAA7191_CHROMA_GAIN_MAX 0x03 +#define SAA7191_CHROMA_GAIN_DEFAULT 0x00 + +#define SAA7191_LUMA_DELAY_MIN -0x04 +#define SAA7191_LUMA_DELAY_MAX 0x03 +#define SAA7191_LUMA_DELAY_DEFAULT 0x01 + +#define SAA7191_VNR_MIN 0x00 +#define SAA7191_VNR_MAX 0x03 +#define SAA7191_VNR_DEFAULT 0x00 + +#define SAA7191_CONTROL_BANDPASS 0 +#define SAA7191_CONTROL_BANDPASS_WEIGHT 1 +#define SAA7191_CONTROL_CORING 2 +#define SAA7191_CONTROL_FORCE_COLOUR 3 /* boolean */ +#define SAA7191_CONTROL_CHROMA_GAIN 4 +#define SAA7191_CONTROL_HUE 5 +#define SAA7191_CONTROL_VTRC 6 /* boolean */ +#define SAA7191_CONTROL_LUMA_DELAY 7 +#define SAA7191_CONTROL_VNR 8 + +struct saa7191_control { + u8 type; + s32 value; +}; + +#define DECODER_SAA7191_GET_STATUS _IOR('d', 195, struct saa7191_status) +#define DECODER_SAA7191_SET_NORM _IOW('d', 196, int) +#define DECODER_SAA7191_GET_CONTROL _IOR('d', 197, struct saa7191_control) +#define DECODER_SAA7191_SET_CONTROL _IOW('d', 198, struct saa7191_control) + +#endif diff --git a/linux/drivers/media/video/saa7196.h b/linux/drivers/media/video/saa7196.h new file mode 100644 index 000000000..cd4b6354a --- /dev/null +++ b/linux/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/linux/drivers/media/video/se401.c b/linux/drivers/media/video/se401.c new file mode 100644 index 000000000..a846ebc78 --- /dev/null +++ b/linux/drivers/media/video/se401.c @@ -0,0 +1,1435 @@ +/* + * Endpoints (formerly known as AOX) se401 USB Camera Driver + * + * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org) + * + * Still somewhat based on the Linux ov511 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. + * + * 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. + * + * + * Thanks to Endpoints Inc. (www.endpoints.com) for making documentation on + * their chipset available and supporting me while writing this driver. + * - Jeroen Vreeken + */ + +static const char version[] = "0.24"; + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/usb.h> +#include "se401.h" + +static int flickerless=0; +static int video_nr = -1; + +static struct usb_device_id device_table [] = { + { USB_DEVICE(0x03e8, 0x0004) },/* Endpoints/Aox SE401 */ + { USB_DEVICE(0x0471, 0x030b) },/* Philips PCVC665K */ + { USB_DEVICE(0x047d, 0x5001) },/* Kensington 67014 */ + { USB_DEVICE(0x047d, 0x5002) },/* Kensington 6701(5/7) */ + { USB_DEVICE(0x047d, 0x5003) },/* Kensington 67016 */ + { } +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +MODULE_AUTHOR("Jeroen Vreeken <pe1rxq@amsat.org>"); +MODULE_DESCRIPTION("SE401 USB Camera Driver"); +MODULE_LICENSE("GPL"); +module_param(flickerless, int, 0); +MODULE_PARM_DESC(flickerless, "Net frequency to adjust exposure time to (0/50/60)"); +module_param(video_nr, int, 0); + +static struct usb_driver se401_driver; + + +/********************************************************************** + * + * Memory management + * + **********************************************************************/ +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + 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) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + + + +/**************************************************************************** + * + * se401 register read/write functions + * + ***************************************************************************/ + +static int se401_sndctrl(int set, struct usb_se401 *se401, unsigned short req, + unsigned short value, unsigned char *cp, int size) +{ + return usb_control_msg ( + se401->dev, + set ? usb_sndctrlpipe(se401->dev, 0) : usb_rcvctrlpipe(se401->dev, 0), + req, + (set ? USB_DIR_OUT : USB_DIR_IN) | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + 0, + cp, + size, + 1000 + ); +} + +static int se401_set_feature(struct usb_se401 *se401, unsigned short selector, + unsigned short param) +{ + /* specs say that the selector (address) should go in the value field + and the param in index, but in the logs of the windows driver they do + this the other way around... + */ + return usb_control_msg ( + se401->dev, + usb_sndctrlpipe(se401->dev, 0), + SE401_REQ_SET_EXT_FEATURE, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + param, + selector, + NULL, + 0, + 1000 + ); +} + +static unsigned short se401_get_feature(struct usb_se401 *se401, + unsigned short selector) +{ + /* For 'set' the selecetor should be in index, not sure if the spec is + wrong here to.... + */ + unsigned char cp[2]; + usb_control_msg ( + se401->dev, + usb_rcvctrlpipe(se401->dev, 0), + SE401_REQ_GET_EXT_FEATURE, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, + selector, + cp, + 2, + 1000 + ); + return cp[0]+cp[1]*256; +} + +/**************************************************************************** + * + * Camera control + * + ***************************************************************************/ + + +static int se401_send_pict(struct usb_se401 *se401) +{ + se401_set_feature(se401, HV7131_REG_TITL, se401->expose_l);/* integration time low */ + se401_set_feature(se401, HV7131_REG_TITM, se401->expose_m);/* integration time mid */ + se401_set_feature(se401, HV7131_REG_TITU, se401->expose_h);/* integration time mid */ + se401_set_feature(se401, HV7131_REG_ARLV, se401->resetlevel);/* reset level value */ + se401_set_feature(se401, HV7131_REG_ARCG, se401->rgain);/* red color gain */ + se401_set_feature(se401, HV7131_REG_AGCG, se401->ggain);/* green color gain */ + se401_set_feature(se401, HV7131_REG_ABCG, se401->bgain);/* blue color gain */ + + return 0; +} + +static void se401_set_exposure(struct usb_se401 *se401, int brightness) +{ + int integration=brightness<<5; + + if (flickerless==50) { + integration=integration-integration%106667; + } + if (flickerless==60) { + integration=integration-integration%88889; + } + se401->brightness=integration>>5; + se401->expose_h=(integration>>16)&0xff; + se401->expose_m=(integration>>8)&0xff; + se401->expose_l=integration&0xff; +} + +static int se401_get_pict(struct usb_se401 *se401, struct video_picture *p) +{ + p->brightness=se401->brightness; + if (se401->enhance) { + p->whiteness=32768; + } else { + p->whiteness=0; + } + p->colour=65535; + p->contrast=65535; + p->hue=se401->rgain<<10; + p->palette=se401->palette; + p->depth=3; /* rgb24 */ + return 0; +} + + +static int se401_set_pict(struct usb_se401 *se401, struct video_picture *p) +{ + if (p->palette != VIDEO_PALETTE_RGB24) + return 1; + se401->palette=p->palette; + if (p->hue!=se401->hue) { + se401->rgain= p->hue>>10; + se401->bgain= 0x40-(p->hue>>10); + se401->hue=p->hue; + } + if (p->brightness!=se401->brightness) { + se401_set_exposure(se401, p->brightness); + } + if (p->whiteness>=32768) { + se401->enhance=1; + } else { + se401->enhance=0; + } + se401_send_pict(se401); + se401_send_pict(se401); + return 0; +} + +/* + Hyundai have some really nice docs about this and other sensor related + stuff on their homepage: www.hei.co.kr +*/ +static void se401_auto_resetlevel(struct usb_se401 *se401) +{ + unsigned int ahrc, alrc; + int oldreset=se401->resetlevel; + + /* For some reason this normally read-only register doesn't get reset + to zero after reading them just once... + */ + se401_get_feature(se401, HV7131_REG_HIREFNOH); + se401_get_feature(se401, HV7131_REG_HIREFNOL); + se401_get_feature(se401, HV7131_REG_LOREFNOH); + se401_get_feature(se401, HV7131_REG_LOREFNOL); + ahrc=256*se401_get_feature(se401, HV7131_REG_HIREFNOH) + + se401_get_feature(se401, HV7131_REG_HIREFNOL); + alrc=256*se401_get_feature(se401, HV7131_REG_LOREFNOH) + + se401_get_feature(se401, HV7131_REG_LOREFNOL); + + /* Not an exact science, but it seems to work pretty well... */ + if (alrc > 10) { + while (alrc>=10 && se401->resetlevel < 63) { + se401->resetlevel++; + alrc /=2; + } + } else if (ahrc > 20) { + while (ahrc>=20 && se401->resetlevel > 0) { + se401->resetlevel--; + ahrc /=2; + } + } + if (se401->resetlevel!=oldreset) + se401_set_feature(se401, HV7131_REG_ARLV, se401->resetlevel); + + return; +} + +/* irq handler for snapshot button */ +static void se401_button_irq(struct urb *urb, struct pt_regs *regs) +{ + struct usb_se401 *se401 = urb->context; + int status; + + if (!se401->dev) { + info("ohoh: device vapourished"); + return; + } + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto exit; + } + + if (urb->actual_length >=2) { + if (se401->button) + se401->buttonpressed=1; + } +exit: + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status) + err ("%s - usb_submit_urb failed with result %d", + __FUNCTION__, status); +} + +static void se401_video_irq(struct urb *urb, struct pt_regs *regs) +{ + struct usb_se401 *se401 = urb->context; + int length = urb->actual_length; + + /* ohoh... */ + if (!se401->streaming) + return; + + if (!se401->dev) { + info ("ohoh: device vapourished"); + return; + } + + /* 0 sized packets happen if we are to fast, but sometimes the camera + keeps sending them forever... + */ + if (length && !urb->status) { + se401->nullpackets=0; + switch(se401->scratch[se401->scratch_next].state) { + case BUFFER_READY: + case BUFFER_BUSY: { + se401->dropped++; + break; + } + case BUFFER_UNUSED: { + memcpy(se401->scratch[se401->scratch_next].data, (unsigned char *)urb->transfer_buffer, length); + se401->scratch[se401->scratch_next].state=BUFFER_READY; + se401->scratch[se401->scratch_next].offset=se401->bayeroffset; + se401->scratch[se401->scratch_next].length=length; + if (waitqueue_active(&se401->wq)) { + wake_up_interruptible(&se401->wq); + } + se401->scratch_overflow=0; + se401->scratch_next++; + if (se401->scratch_next>=SE401_NUMSCRATCH) + se401->scratch_next=0; + break; + } + } + se401->bayeroffset+=length; + if (se401->bayeroffset>=se401->cheight*se401->cwidth) { + se401->bayeroffset=0; + } + } else { + se401->nullpackets++; + if (se401->nullpackets > SE401_MAX_NULLPACKETS) { + if (waitqueue_active(&se401->wq)) { + wake_up_interruptible(&se401->wq); + } + } + } + + /* Resubmit urb for new data */ + urb->status=0; + urb->dev=se401->dev; + if(usb_submit_urb(urb, GFP_KERNEL)) + info("urb burned down"); + return; +} + +static void se401_send_size(struct usb_se401 *se401, int width, int height) +{ + int i=0; + int mode=0x03; /* No compression */ + int sendheight=height; + int sendwidth=width; + + /* JangGu compression can only be used with the camera supported sizes, + but bayer seems to work with any size that fits on the sensor. + We check if we can use compression with the current size with either + 4 or 16 times subcapturing, if not we use uncompressed bayer data + but this will result in cutouts of the maximum size.... + */ + while (i<se401->sizes && !(se401->width[i]==width && se401->height[i]==height)) + i++; + while (i<se401->sizes) { + if (se401->width[i]==width*2 && se401->height[i]==height*2) { + sendheight=se401->height[i]; + sendwidth=se401->width[i]; + mode=0x40; + } + if (se401->width[i]==width*4 && se401->height[i]==height*4) { + sendheight=se401->height[i]; + sendwidth=se401->width[i]; + mode=0x42; + } + i++; + } + + se401_sndctrl(1, se401, SE401_REQ_SET_WIDTH, sendwidth, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_SET_HEIGHT, sendheight, NULL, 0); + se401_set_feature(se401, SE401_OPERATINGMODE, mode); + + if (mode==0x03) { + se401->format=FMT_BAYER; + } else { + se401->format=FMT_JANGGU; + } + + return; +} + +/* + In this function se401_send_pict is called several times, + for some reason (depending on the state of the sensor and the phase of + the moon :) doing this only in either place doesn't always work... +*/ +static int se401_start_stream(struct usb_se401 *se401) +{ + struct urb *urb; + int err=0, i; + se401->streaming=1; + + se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 1, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0); + + /* Set picture settings */ + se401_set_feature(se401, HV7131_REG_MODE_B, 0x05);/*windowed + pix intg */ + se401_send_pict(se401); + + se401_send_size(se401, se401->cwidth, se401->cheight); + + se401_sndctrl(1, se401, SE401_REQ_START_CONTINUOUS_CAPTURE, 0, NULL, 0); + + /* Do some memory allocation */ + for (i=0; i<SE401_NUMFRAMES; i++) { + se401->frame[i].data=se401->fbuf + i * se401->maxframesize; + se401->frame[i].curpix=0; + } + for (i=0; i<SE401_NUMSBUF; i++) { + se401->sbuf[i].data=kmalloc(SE401_PACKETSIZE, GFP_KERNEL); + } + + se401->bayeroffset=0; + se401->scratch_next=0; + se401->scratch_use=0; + se401->scratch_overflow=0; + for (i=0; i<SE401_NUMSCRATCH; i++) { + se401->scratch[i].data=kmalloc(SE401_PACKETSIZE, GFP_KERNEL); + se401->scratch[i].state=BUFFER_UNUSED; + } + + for (i=0; i<SE401_NUMSBUF; i++) { + urb=usb_alloc_urb(0, GFP_KERNEL); + if(!urb) + return -ENOMEM; + + usb_fill_bulk_urb(urb, se401->dev, + usb_rcvbulkpipe(se401->dev, SE401_VIDEO_ENDPOINT), + se401->sbuf[i].data, SE401_PACKETSIZE, + se401_video_irq, + se401); + + se401->urb[i]=urb; + + err=usb_submit_urb(se401->urb[i], GFP_KERNEL); + if(err) + err("urb burned down"); + } + + se401->framecount=0; + + return 0; +} + +static int se401_stop_stream(struct usb_se401 *se401) +{ + int i; + + if (!se401->streaming || !se401->dev) + return 1; + + se401->streaming=0; + + se401_sndctrl(1, se401, SE401_REQ_STOP_CONTINUOUS_CAPTURE, 0, NULL, 0); + + se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 0, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 0, NULL, 0); + + for (i=0; i<SE401_NUMSBUF; i++) if (se401->urb[i]) { + usb_kill_urb(se401->urb[i]); + usb_free_urb(se401->urb[i]); + se401->urb[i]=NULL; + kfree(se401->sbuf[i].data); + } + for (i=0; i<SE401_NUMSCRATCH; i++) { + kfree(se401->scratch[i].data); + se401->scratch[i].data=NULL; + } + + return 0; +} + +static int se401_set_size(struct usb_se401 *se401, int width, int height) +{ + int wasstreaming=se401->streaming; + /* Check to see if we need to change */ + if (se401->cwidth==width && se401->cheight==height) + return 0; + + /* Check for a valid mode */ + if (!width || !height) + return 1; + if ((width & 1) || (height & 1)) + return 1; + if (width>se401->width[se401->sizes-1]) + return 1; + if (height>se401->height[se401->sizes-1]) + return 1; + + /* Stop a current stream and start it again at the new size */ + if (wasstreaming) + se401_stop_stream(se401); + se401->cwidth=width; + se401->cheight=height; + if (wasstreaming) + se401_start_stream(se401); + return 0; +} + + +/**************************************************************************** + * + * Video Decoding + * + ***************************************************************************/ + +/* + This shouldn't really be done in a v4l driver.... + But it does make the image look a lot more usable. + Basically it lifts the dark pixels more than the light pixels. +*/ +static inline void enhance_picture(unsigned char *frame, int len) +{ + while (len--) { + *frame=(((*frame^255)*(*frame^255))/255)^255; + frame++; + } +} + +static inline void decode_JangGu_integrate(struct usb_se401 *se401, int data) +{ + struct se401_frame *frame=&se401->frame[se401->curframe]; + int linelength=se401->cwidth*3; + + if (frame->curlinepix >= linelength) { + frame->curlinepix=0; + frame->curline+=linelength; + } + + /* First three are absolute, all others relative. + * Format is rgb from right to left (mirrorred image), + * we flip it to get bgr from left to right. */ + if (frame->curlinepix < 3) { + *(frame->curline-frame->curlinepix)=1+data*4; + } else { + *(frame->curline-frame->curlinepix)= + *(frame->curline-frame->curlinepix+3)+data*4; + } + frame->curlinepix++; +} + +static inline void decode_JangGu_vlc (struct usb_se401 *se401, unsigned char *data, int bit_exp, int packetlength) +{ + int pos=0; + int vlc_cod=0; + int vlc_size=0; + int vlc_data=0; + int bit_cur; + int bit; + data+=4; + while (pos < packetlength) { + bit_cur=8; + while (bit_cur && bit_exp) { + bit=((*data)>>(bit_cur-1))&1; + if (!vlc_cod) { + if (bit) { + vlc_size++; + } else { + if (!vlc_size) { + decode_JangGu_integrate(se401, 0); + } else { + vlc_cod=2; + vlc_data=0; + } + } + } else { + if (vlc_cod==2) { + if (!bit) + vlc_data = -(1<<vlc_size) + 1; + vlc_cod--; + } + vlc_size--; + vlc_data+=bit<<vlc_size; + if (!vlc_size) { + decode_JangGu_integrate(se401, vlc_data); + vlc_cod=0; + } + } + bit_cur--; + bit_exp--; + } + pos++; + data++; + } +} + +static inline void decode_JangGu (struct usb_se401 *se401, struct se401_scratch *buffer) +{ + unsigned char *data=buffer->data; + int len=buffer->length; + int bit_exp=0, pix_exp=0, frameinfo=0, packetlength=0, size; + int datapos=0; + + /* New image? */ + if (!se401->frame[se401->curframe].curpix) { + se401->frame[se401->curframe].curlinepix=0; + se401->frame[se401->curframe].curline= + se401->frame[se401->curframe].data+ + se401->cwidth*3-1; + if (se401->frame[se401->curframe].grabstate==FRAME_READY) + se401->frame[se401->curframe].grabstate=FRAME_GRABBING; + se401->vlcdatapos=0; + } + while (datapos < len) { + size=1024-se401->vlcdatapos; + if (size+datapos > len) + size=len-datapos; + memcpy(se401->vlcdata+se401->vlcdatapos, data+datapos, size); + se401->vlcdatapos+=size; + packetlength=0; + if (se401->vlcdatapos >= 4) { + bit_exp=se401->vlcdata[3]+(se401->vlcdata[2]<<8); + pix_exp=se401->vlcdata[1]+((se401->vlcdata[0]&0x3f)<<8); + frameinfo=se401->vlcdata[0]&0xc0; + packetlength=((bit_exp+47)>>4)<<1; + if (packetlength > 1024) { + se401->vlcdatapos=0; + datapos=len; + packetlength=0; + se401->error++; + se401->frame[se401->curframe].curpix=0; + } + } + if (packetlength && se401->vlcdatapos >= packetlength) { + decode_JangGu_vlc(se401, se401->vlcdata, bit_exp, packetlength); + se401->frame[se401->curframe].curpix+=pix_exp*3; + datapos+=size-(se401->vlcdatapos-packetlength); + se401->vlcdatapos=0; + if (se401->frame[se401->curframe].curpix>=se401->cwidth*se401->cheight*3) { + if (se401->frame[se401->curframe].curpix==se401->cwidth*se401->cheight*3) { + if (se401->frame[se401->curframe].grabstate==FRAME_GRABBING) { + se401->frame[se401->curframe].grabstate=FRAME_DONE; + se401->framecount++; + se401->readcount++; + } + if (se401->frame[(se401->curframe+1)&(SE401_NUMFRAMES-1)].grabstate==FRAME_READY) { + se401->curframe=(se401->curframe+1) & (SE401_NUMFRAMES-1); + } + } else { + se401->error++; + } + se401->frame[se401->curframe].curpix=0; + datapos=len; + } + } else { + datapos+=size; + } + } +} + +static inline void decode_bayer (struct usb_se401 *se401, struct se401_scratch *buffer) +{ + unsigned char *data=buffer->data; + int len=buffer->length; + int offset=buffer->offset; + int datasize=se401->cwidth*se401->cheight; + struct se401_frame *frame=&se401->frame[se401->curframe]; + + unsigned char *framedata=frame->data, *curline, *nextline; + int width=se401->cwidth; + int blineoffset=0, bline; + int linelength=width*3, i; + + + if (frame->curpix==0) { + if (frame->grabstate==FRAME_READY) { + frame->grabstate=FRAME_GRABBING; + } + frame->curline=framedata+linelength; + frame->curlinepix=0; + } + + if (offset!=frame->curpix) { + /* Regard frame as lost :( */ + frame->curpix=0; + se401->error++; + return; + } + + /* Check if we have to much data */ + if (frame->curpix+len > datasize) { + len=datasize-frame->curpix; + } + if (se401->cheight%4) + blineoffset=1; + bline=frame->curpix/se401->cwidth+blineoffset; + + curline=frame->curline; + nextline=curline+linelength; + if (nextline >= framedata+datasize*3) + nextline=curline; + while (len) { + if (frame->curlinepix>=width) { + frame->curlinepix-=width; + bline=frame->curpix/width+blineoffset; + curline+=linelength*2; + nextline+=linelength*2; + if (curline >= framedata+datasize*3) { + frame->curlinepix++; + curline-=3; + nextline-=3; + len--; + data++; + frame->curpix++; + } + if (nextline >= framedata+datasize*3) + nextline=curline; + } + if ((bline&1)) { + if ((frame->curlinepix&1)) { + *(curline+2)=*data; + *(curline-1)=*data; + *(nextline+2)=*data; + *(nextline-1)=*data; + } else { + *(curline+1)= + (*(curline+1)+*data)/2; + *(curline-2)= + (*(curline-2)+*data)/2; + *(nextline+1)=*data; + *(nextline-2)=*data; + } + } else { + if ((frame->curlinepix&1)) { + *(curline+1)= + (*(curline+1)+*data)/2; + *(curline-2)= + (*(curline-2)+*data)/2; + *(nextline+1)=*data; + *(nextline-2)=*data; + } else { + *curline=*data; + *(curline-3)=*data; + *nextline=*data; + *(nextline-3)=*data; + } + } + frame->curlinepix++; + curline-=3; + nextline-=3; + len--; + data++; + frame->curpix++; + } + frame->curline=curline; + + if (frame->curpix>=datasize) { + /* Fix the top line */ + framedata+=linelength; + for (i=0; i<linelength; i++) { + framedata--; + *framedata=*(framedata+linelength); + } + /* Fix the left side (green is already present) */ + for (i=0; i<se401->cheight; i++) { + *framedata=*(framedata+3); + *(framedata+1)=*(framedata+4); + *(framedata+2)=*(framedata+5); + framedata+=linelength; + } + frame->curpix=0; + frame->grabstate=FRAME_DONE; + se401->framecount++; + se401->readcount++; + if (se401->frame[(se401->curframe+1)&(SE401_NUMFRAMES-1)].grabstate==FRAME_READY) { + se401->curframe=(se401->curframe+1) & (SE401_NUMFRAMES-1); + } + } +} + +static int se401_newframe(struct usb_se401 *se401, int framenr) +{ + DECLARE_WAITQUEUE(wait, current); + int errors=0; + + while (se401->streaming && + (se401->frame[framenr].grabstate==FRAME_READY || + se401->frame[framenr].grabstate==FRAME_GRABBING) ) { + if(!se401->frame[framenr].curpix) { + errors++; + } + wait_interruptible( + se401->scratch[se401->scratch_use].state!=BUFFER_READY, + &se401->wq, + &wait + ); + if (se401->nullpackets > SE401_MAX_NULLPACKETS) { + se401->nullpackets=0; + info("to many null length packets, restarting capture"); + se401_stop_stream(se401); + se401_start_stream(se401); + } else { + if (se401->scratch[se401->scratch_use].state!=BUFFER_READY) { + se401->frame[framenr].grabstate=FRAME_ERROR; + return -EIO; + } + se401->scratch[se401->scratch_use].state=BUFFER_BUSY; + if (se401->format==FMT_JANGGU) { + decode_JangGu(se401, &se401->scratch[se401->scratch_use]); + } else { + decode_bayer(se401, &se401->scratch[se401->scratch_use]); + } + se401->scratch[se401->scratch_use].state=BUFFER_UNUSED; + se401->scratch_use++; + if (se401->scratch_use>=SE401_NUMSCRATCH) + se401->scratch_use=0; + if (errors > SE401_MAX_ERRORS) { + errors=0; + info("to much errors, restarting capture"); + se401_stop_stream(se401); + se401_start_stream(se401); + } + } + } + + if (se401->frame[framenr].grabstate==FRAME_DONE) + if (se401->enhance) + enhance_picture(se401->frame[framenr].data, se401->cheight*se401->cwidth*3); + return 0; +} + +static void usb_se401_remove_disconnected (struct usb_se401 *se401) +{ + int i; + + se401->dev = NULL; + + for (i=0; i<SE401_NUMSBUF; i++) + if (se401->urb[i]) { + usb_kill_urb(se401->urb[i]); + usb_free_urb(se401->urb[i]); + se401->urb[i] = NULL; + kfree(se401->sbuf[i].data); + } + for (i=0; i<SE401_NUMSCRATCH; i++) { + kfree(se401->scratch[i].data); + } + if (se401->inturb) { + usb_kill_urb(se401->inturb); + usb_free_urb(se401->inturb); + } + info("%s disconnected", se401->camera_name); + + /* Free the memory */ + kfree(se401->width); + kfree(se401->height); + kfree(se401); +} + + + +/**************************************************************************** + * + * Video4Linux + * + ***************************************************************************/ + + +static int se401_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct usb_se401 *se401 = (struct usb_se401 *)dev; + int err = 0; + + if (se401->user) + return -EBUSY; + se401->fbuf = rvmalloc(se401->maxframesize * SE401_NUMFRAMES); + if (se401->fbuf) + file->private_data = dev; + else + err = -ENOMEM; + se401->user = !err; + + return err; +} + +static int se401_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + struct usb_se401 *se401 = (struct usb_se401 *)dev; + int i; + + rvfree(se401->fbuf, se401->maxframesize * SE401_NUMFRAMES); + if (se401->removed) { + usb_se401_remove_disconnected(se401); + info("device unregistered"); + } else { + for (i=0; i<SE401_NUMFRAMES; i++) + se401->frame[i].grabstate=FRAME_UNUSED; + if (se401->streaming) + se401_stop_stream(se401); + se401->user=0; + } + file->private_data = NULL; + return 0; +} + +static int se401_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = file->private_data; + struct usb_se401 *se401 = (struct usb_se401 *)vdev; + + if (!se401->dev) + return -EIO; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability *b = arg; + strcpy(b->name, se401->camera_name); + b->type = VID_TYPE_CAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = se401->width[se401->sizes-1]; + b->maxheight = se401->height[se401->sizes-1]; + b->minwidth = se401->width[0]; + b->minheight = se401->height[0]; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *v = arg; + + if (v->channel != 0) + return -EINVAL; + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + strcpy(v->name, "Camera"); + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *v = arg; + + if (v->channel != 0) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture *p = arg; + + se401_get_pict(se401, p); + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *p = arg; + + if (se401_set_pict(se401, p)) + return -EINVAL; + return 0; + } + case VIDIOCSWIN: + { + struct video_window *vw = arg; + + if (vw->flags) + return -EINVAL; + if (vw->clipcount) + return -EINVAL; + if (se401_set_size(se401, vw->width, vw->height)) + return -EINVAL; + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vw = arg; + + vw->x = 0; /* FIXME */ + vw->y = 0; + vw->chromakey = 0; + vw->flags = 0; + vw->clipcount = 0; + vw->width = se401->cwidth; + vw->height = se401->cheight; + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf *vm = arg; + int i; + + memset(vm, 0, sizeof(*vm)); + vm->size = SE401_NUMFRAMES * se401->maxframesize; + vm->frames = SE401_NUMFRAMES; + for (i=0; i<SE401_NUMFRAMES; i++) + vm->offsets[i] = se401->maxframesize * i; + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap *vm = arg; + + if (vm->format != VIDEO_PALETTE_RGB24) + return -EINVAL; + if (vm->frame >= SE401_NUMFRAMES) + return -EINVAL; + if (se401->frame[vm->frame].grabstate != FRAME_UNUSED) + return -EBUSY; + + /* Is this according to the v4l spec??? */ + if (se401_set_size(se401, vm->width, vm->height)) + return -EINVAL; + se401->frame[vm->frame].grabstate=FRAME_READY; + + if (!se401->streaming) + se401_start_stream(se401); + + /* Set the picture properties */ + if (se401->framecount==0) + se401_send_pict(se401); + /* Calibrate the reset level after a few frames. */ + if (se401->framecount%20==1) + se401_auto_resetlevel(se401); + + return 0; + } + case VIDIOCSYNC: + { + int *frame = arg; + int ret=0; + + if(*frame <0 || *frame >= SE401_NUMFRAMES) + return -EINVAL; + + ret=se401_newframe(se401, *frame); + se401->frame[*frame].grabstate=FRAME_UNUSED; + return ret; + } + case VIDIOCGFBUF: + { + struct video_buffer *vb = arg; + + memset(vb, 0, sizeof(*vb)); + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + return -EINVAL; + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCGTUNER: + case VIDIOCSTUNER: + return -EINVAL; + case VIDIOCGFREQ: + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } /* end switch */ + + return 0; +} + +static int se401_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, se401_do_ioctl); +} + +static ssize_t se401_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int realcount=count, ret=0; + struct video_device *dev = file->private_data; + struct usb_se401 *se401 = (struct usb_se401 *)dev; + + + if (se401->dev == NULL) + return -EIO; + if (realcount > se401->cwidth*se401->cheight*3) + realcount=se401->cwidth*se401->cheight*3; + + /* Shouldn't happen: */ + if (se401->frame[0].grabstate==FRAME_GRABBING) + return -EBUSY; + se401->frame[0].grabstate=FRAME_READY; + se401->frame[1].grabstate=FRAME_UNUSED; + se401->curframe=0; + + if (!se401->streaming) + se401_start_stream(se401); + + /* Set the picture properties */ + if (se401->framecount==0) + se401_send_pict(se401); + /* Calibrate the reset level after a few frames. */ + if (se401->framecount%20==1) + se401_auto_resetlevel(se401); + + ret=se401_newframe(se401, 0); + + se401->frame[0].grabstate=FRAME_UNUSED; + if (ret) + return ret; + if (copy_to_user(buf, se401->frame[0].data, realcount)) + return -EFAULT; + + return realcount; +} + +static int se401_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + struct usb_se401 *se401 = (struct usb_se401 *)dev; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end-vma->vm_start; + unsigned long page, pos; + + mutex_lock(&se401->lock); + + if (se401->dev == NULL) { + mutex_unlock(&se401->lock); + return -EIO; + } + if (size > (((SE401_NUMFRAMES * se401->maxframesize) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) { + mutex_unlock(&se401->lock); + return -EINVAL; + } + pos = (unsigned long)se401->fbuf; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + mutex_unlock(&se401->lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + mutex_unlock(&se401->lock); + + return 0; +} + +static struct file_operations se401_fops = { + .owner = THIS_MODULE, + .open = se401_open, + .release = se401_close, + .read = se401_read, + .mmap = se401_mmap, + .ioctl = se401_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +static struct video_device se401_template = { + .owner = THIS_MODULE, + .name = "se401 USB camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_SE401, + .fops = &se401_fops, +}; + + + +/***************************/ +static int se401_init(struct usb_se401 *se401, int button) +{ + int i=0, rc; + unsigned char cp[0x40]; + char temp[200]; + + /* led on */ + se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0); + + /* get camera descriptor */ + rc=se401_sndctrl(0, se401, SE401_REQ_GET_CAMERA_DESCRIPTOR, 0, cp, sizeof(cp)); + if (cp[1]!=0x41) { + err("Wrong descriptor type"); + return 1; + } + sprintf (temp, "ExtraFeatures: %d", cp[3]); + + se401->sizes=cp[4]+cp[5]*256; + se401->width=kmalloc(se401->sizes*sizeof(int), GFP_KERNEL); + if (!se401->width) + return 1; + se401->height=kmalloc(se401->sizes*sizeof(int), GFP_KERNEL); + if (!se401->height) { + kfree(se401->width); + return 1; + } + for (i=0; i<se401->sizes; i++) { + se401->width[i]=cp[6+i*4+0]+cp[6+i*4+1]*256; + se401->height[i]=cp[6+i*4+2]+cp[6+i*4+3]*256; + } + sprintf (temp, "%s Sizes:", temp); + for (i=0; i<se401->sizes; i++) { + sprintf(temp, "%s %dx%d", temp, se401->width[i], se401->height[i]); + } + info("%s", temp); + se401->maxframesize=se401->width[se401->sizes-1]*se401->height[se401->sizes-1]*3; + + rc=se401_sndctrl(0, se401, SE401_REQ_GET_WIDTH, 0, cp, sizeof(cp)); + se401->cwidth=cp[0]+cp[1]*256; + rc=se401_sndctrl(0, se401, SE401_REQ_GET_HEIGHT, 0, cp, sizeof(cp)); + se401->cheight=cp[0]+cp[1]*256; + + if (!cp[2] && SE401_FORMAT_BAYER) { + err("Bayer format not supported!"); + return 1; + } + /* set output mode (BAYER) */ + se401_sndctrl(1, se401, SE401_REQ_SET_OUTPUT_MODE, SE401_FORMAT_BAYER, NULL, 0); + + rc=se401_sndctrl(0, se401, SE401_REQ_GET_BRT, 0, cp, sizeof(cp)); + se401->brightness=cp[0]+cp[1]*256; + /* some default values */ + se401->resetlevel=0x2d; + se401->rgain=0x20; + se401->ggain=0x20; + se401->bgain=0x20; + se401_set_exposure(se401, 20000); + se401->palette=VIDEO_PALETTE_RGB24; + se401->enhance=1; + se401->dropped=0; + se401->error=0; + se401->framecount=0; + se401->readcount=0; + + /* Start interrupt transfers for snapshot button */ + if (button) { + se401->inturb=usb_alloc_urb(0, GFP_KERNEL); + if (!se401->inturb) { + info("Allocation of inturb failed"); + return 1; + } + usb_fill_int_urb(se401->inturb, se401->dev, + usb_rcvintpipe(se401->dev, SE401_BUTTON_ENDPOINT), + &se401->button, sizeof(se401->button), + se401_button_irq, + se401, + 8 + ); + if (usb_submit_urb(se401->inturb, GFP_KERNEL)) { + info("int urb burned down"); + return 1; + } + } else + se401->inturb=NULL; + + /* Flash the led */ + se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 1, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 1, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_CAMERA_POWER, 0, NULL, 0); + se401_sndctrl(1, se401, SE401_REQ_LED_CONTROL, 0, NULL, 0); + + return 0; +} + +static int se401_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_interface_descriptor *interface; + struct usb_se401 *se401; + char *camera_name=NULL; + int button=1; + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -ENODEV; + + interface = &intf->cur_altsetting->desc; + + /* Is it an se401? */ + if (le16_to_cpu(dev->descriptor.idVendor) == 0x03e8 && + le16_to_cpu(dev->descriptor.idProduct) == 0x0004) { + camera_name="Endpoints/Aox SE401"; + } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x0471 && + le16_to_cpu(dev->descriptor.idProduct) == 0x030b) { + camera_name="Philips PCVC665K"; + } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d && + le16_to_cpu(dev->descriptor.idProduct) == 0x5001) { + camera_name="Kensington VideoCAM 67014"; + } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d && + le16_to_cpu(dev->descriptor.idProduct) == 0x5002) { + camera_name="Kensington VideoCAM 6701(5/7)"; + } else if (le16_to_cpu(dev->descriptor.idVendor) == 0x047d && + le16_to_cpu(dev->descriptor.idProduct) == 0x5003) { + camera_name="Kensington VideoCAM 67016"; + button=0; + } else + return -ENODEV; + + /* Checking vendor/product should be enough, but what the hell */ + if (interface->bInterfaceClass != 0x00) + return -ENODEV; + if (interface->bInterfaceSubClass != 0x00) + return -ENODEV; + + /* We found one */ + info("SE401 camera found: %s", camera_name); + + if ((se401 = kzalloc(sizeof(*se401), GFP_KERNEL)) == NULL) { + err("couldn't kmalloc se401 struct"); + return -ENOMEM; + } + + se401->dev = dev; + se401->iface = interface->bInterfaceNumber; + se401->camera_name = camera_name; + + info("firmware version: %02x", le16_to_cpu(dev->descriptor.bcdDevice) & 255); + + if (se401_init(se401, button)) { + kfree(se401); + return -EIO; + } + + memcpy(&se401->vdev, &se401_template, sizeof(se401_template)); + memcpy(se401->vdev.name, se401->camera_name, strlen(se401->camera_name)); + init_waitqueue_head(&se401->wq); + mutex_init(&se401->lock); + wmb(); + + if (video_register_device(&se401->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + kfree(se401); + err("video_register_device failed"); + return -EIO; + } + info("registered new video device: video%d", se401->vdev.minor); + + usb_set_intfdata (intf, se401); + return 0; +} + +static void se401_disconnect(struct usb_interface *intf) +{ + struct usb_se401 *se401 = usb_get_intfdata (intf); + + usb_set_intfdata (intf, NULL); + if (se401) { + video_unregister_device(&se401->vdev); + if (!se401->user){ + usb_se401_remove_disconnected(se401); + } else { + se401->frame[0].grabstate = FRAME_ERROR; + se401->frame[0].grabstate = FRAME_ERROR; + + se401->streaming = 0; + + wake_up_interruptible(&se401->wq); + se401->removed = 1; + } + } +} + +static struct usb_driver se401_driver = { + .name = "se401", + .id_table = device_table, + .probe = se401_probe, + .disconnect = se401_disconnect, +}; + + + +/**************************************************************************** + * + * Module routines + * + ***************************************************************************/ + +static int __init usb_se401_init(void) +{ + info("SE401 usb camera driver version %s registering", version); + if (flickerless) + if (flickerless!=50 && flickerless!=60) { + info("Invallid flickerless value, use 0, 50 or 60."); + return -1; + } + return usb_register(&se401_driver); +} + +static void __exit usb_se401_exit(void) +{ + usb_deregister(&se401_driver); + info("SE401 driver deregistered"); +} + +module_init(usb_se401_init); +module_exit(usb_se401_exit); diff --git a/linux/drivers/media/video/se401.h b/linux/drivers/media/video/se401.h new file mode 100644 index 000000000..a78aa0720 --- /dev/null +++ b/linux/drivers/media/video/se401.h @@ -0,0 +1,235 @@ + +#ifndef __LINUX_se401_H +#define __LINUX_se401_H + +#include <asm/uaccess.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/smp_lock.h> +#include <linux/mutex.h> + +#define se401_DEBUG /* Turn on debug messages */ + +#ifdef se401_DEBUG +# define PDEBUG(level, fmt, args...) \ +if (debug >= level) info("[" __PRETTY_FUNCTION__ ":%d] " fmt, __LINE__ , ## args) +#else +# define PDEBUG(level, fmt, args...) do {} while(0) +#endif + +/* An almost drop-in replacement for sleep_on_interruptible */ +#define wait_interruptible(test, queue, wait) \ +{ \ + add_wait_queue(queue, wait); \ + set_current_state(TASK_INTERRUPTIBLE); \ + if (test) \ + schedule(); \ + remove_wait_queue(queue, wait); \ + set_current_state(TASK_RUNNING); \ + if (signal_pending(current)) \ + break; \ +} + +#define SE401_REQ_GET_CAMERA_DESCRIPTOR 0x06 +#define SE401_REQ_START_CONTINUOUS_CAPTURE 0x41 +#define SE401_REQ_STOP_CONTINUOUS_CAPTURE 0x42 +#define SE401_REQ_CAPTURE_FRAME 0x43 +#define SE401_REQ_GET_BRT 0x44 +#define SE401_REQ_SET_BRT 0x45 +#define SE401_REQ_GET_WIDTH 0x4c +#define SE401_REQ_SET_WIDTH 0x4d +#define SE401_REQ_GET_HEIGHT 0x4e +#define SE401_REQ_SET_HEIGHT 0x4f +#define SE401_REQ_GET_OUTPUT_MODE 0x50 +#define SE401_REQ_SET_OUTPUT_MODE 0x51 +#define SE401_REQ_GET_EXT_FEATURE 0x52 +#define SE401_REQ_SET_EXT_FEATURE 0x53 +#define SE401_REQ_CAMERA_POWER 0x56 +#define SE401_REQ_LED_CONTROL 0x57 +#define SE401_REQ_BIOS 0xff + +#define SE401_BIOS_READ 0x07 + +#define SE401_FORMAT_BAYER 0x40 + +/* Hyundai hv7131b registers + 7121 and 7141 should be the same (haven't really checked...) */ +/* Mode registers: */ +#define HV7131_REG_MODE_A 0x00 +#define HV7131_REG_MODE_B 0x01 +#define HV7131_REG_MODE_C 0x02 +/* Frame registers: */ +#define HV7131_REG_FRSU 0x10 +#define HV7131_REG_FRSL 0x11 +#define HV7131_REG_FCSU 0x12 +#define HV7131_REG_FCSL 0x13 +#define HV7131_REG_FWHU 0x14 +#define HV7131_REG_FWHL 0x15 +#define HV7131_REG_FWWU 0x16 +#define HV7131_REG_FWWL 0x17 +/* Timing registers: */ +#define HV7131_REG_THBU 0x20 +#define HV7131_REG_THBL 0x21 +#define HV7131_REG_TVBU 0x22 +#define HV7131_REG_TVBL 0x23 +#define HV7131_REG_TITU 0x25 +#define HV7131_REG_TITM 0x26 +#define HV7131_REG_TITL 0x27 +#define HV7131_REG_TMCD 0x28 +/* Adjust Registers: */ +#define HV7131_REG_ARLV 0x30 +#define HV7131_REG_ARCG 0x31 +#define HV7131_REG_AGCG 0x32 +#define HV7131_REG_ABCG 0x33 +#define HV7131_REG_APBV 0x34 +#define HV7131_REG_ASLP 0x54 +/* Offset Registers: */ +#define HV7131_REG_OFSR 0x50 +#define HV7131_REG_OFSG 0x51 +#define HV7131_REG_OFSB 0x52 +/* REset level statistics registers: */ +#define HV7131_REG_LOREFNOH 0x57 +#define HV7131_REG_LOREFNOL 0x58 +#define HV7131_REG_HIREFNOH 0x59 +#define HV7131_REG_HIREFNOL 0x5a + +/* se401 registers */ +#define SE401_OPERATINGMODE 0x2000 + + +/* size of usb transfers */ +#define SE401_PACKETSIZE 4096 +/* number of queued bulk transfers to use, should be about 8 */ +#define SE401_NUMSBUF 1 +/* read the usb specs for this one :) */ +#define SE401_VIDEO_ENDPOINT 1 +#define SE401_BUTTON_ENDPOINT 2 +/* number of frames supported by the v4l part */ +#define SE401_NUMFRAMES 2 +/* scratch buffers for passing data to the decoders */ +#define SE401_NUMSCRATCH 32 +/* maximum amount of data in a JangGu packet */ +#define SE401_VLCDATALEN 1024 +/* number of nul sized packets to receive before kicking the camera */ +#define SE401_MAX_NULLPACKETS 4000 +/* number of decoding errors before kicking the camera */ +#define SE401_MAX_ERRORS 200 + +struct usb_device; + +struct se401_sbuf { + unsigned char *data; +}; + +enum { + FRAME_UNUSED, /* Unused (no MCAPTURE) */ + FRAME_READY, /* Ready to start grabbing */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_ERROR, /* Something bad happened while processing */ +}; + +enum { + FMT_BAYER, + FMT_JANGGU, +}; + +enum { + BUFFER_UNUSED, + BUFFER_READY, + BUFFER_BUSY, + BUFFER_DONE, +}; + +struct se401_scratch { + unsigned char *data; + volatile int state; + int offset; + int length; +}; + +struct se401_frame { + unsigned char *data; /* Frame buffer */ + + volatile int grabstate; /* State of grabbing */ + + unsigned char *curline; + int curlinepix; + int curpix; +}; + +struct usb_se401 { + struct video_device vdev; + + /* Device structure */ + struct usb_device *dev; + + unsigned char iface; + + char *camera_name; + + int change; + int brightness; + int hue; + int rgain; + int ggain; + int bgain; + int expose_h; + int expose_m; + int expose_l; + int resetlevel; + + int enhance; + + int format; + int sizes; + int *width; + int *height; + int cwidth; /* current width */ + int cheight; /* current height */ + int palette; + int maxframesize; + int cframesize; /* current framesize */ + + struct mutex lock; + int user; /* user count for exclusive use */ + int removed; /* device disconnected */ + + int streaming; /* Are we streaming video? */ + + char *fbuf; /* Videodev buffer area */ + + struct urb *urb[SE401_NUMSBUF]; + struct urb *inturb; + + int button; + int buttonpressed; + + int curframe; /* Current receiving frame */ + struct se401_frame frame[SE401_NUMFRAMES]; + int readcount; + int framecount; + int error; + int dropped; + + int scratch_next; + int scratch_use; + int scratch_overflow; + struct se401_scratch scratch[SE401_NUMSCRATCH]; + + /* Decoder specific data: */ + unsigned char vlcdata[SE401_VLCDATALEN]; + int vlcdatapos; + int bayeroffset; + + struct se401_sbuf sbuf[SE401_NUMSBUF]; + + wait_queue_head_t wq; /* Processes waiting */ + + int nullpackets; +}; + + + +#endif + diff --git a/linux/drivers/media/video/stradis.c b/linux/drivers/media/video/stradis.c new file mode 100644 index 000000000..9aad8d66d --- /dev/null +++ b/linux/drivers/media/video/stradis.c @@ -0,0 +1,2213 @@ +/* + * 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/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.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/types.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <asm/uaccess.h> +#include <linux/vmalloc.h> +#include "compat.h" +#include <linux/videodev.h> + +#include "saa7146.h" +#include "saa7146reg.h" +#include "ibmmpeg2.h" +#include "saa7121.h" +#include "cs8420.h" + +#define DEBUG(x) /* debug driver */ +#undef IDEBUG /* debug irq handler */ +#undef MDEBUG /* debug memory management */ + +#define SAA7146_MAX 6 + +static struct saa7146 saa7146s[SAA7146_MAX]; + +static int saa_num = 0; /* number of SAA7146s in use */ + +static int video_nr = -1; +module_param(video_nr, int, 0); +MODULE_LICENSE("GPL"); + +#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 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 saa7146 *saa, unsigned char addr, + unsigned char subaddr, int dosub) +{ + 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); +} + +/* set both to write both bytes, reset it to write only b1 */ + +static int I2CWrite(struct saa7146 *saa, unsigned char addr, unsigned char b1, + unsigned char b2, int both) +{ + 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 saa7146 *saa, int id) +{ + 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(saa, 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 I2CBusScan(struct saa7146 *saa) +{ + int i; + for (i = 0; i < 0xff; i += 2) + if ((I2CRead(saa, i, 0, 0)) >= 0) + attach_inform(saa, i); +} + +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; +} + +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 irqreturn_t saa7146_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7146 *saa = dev_id; + u32 stat, astat; + int count; + int handled = 0; + + count = 0; + while (1) { + /* get/clear interrupt status bits */ + stat = saaread(SAA7146_ISR); + astat = stat & saaread(SAA7146_IER); + if (!astat) + break; + handled = 1; + 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); + } + } + return IRQ_RETVAL(handled); +} + +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, 0x22, 0x03, left > 94 ? 94 : left, 2); + I2CWrite(saa, 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, 0x22, 0x01, 0x11, 2); + /* ATAPI mixer settings */ + I2CWrite(saa, 0x22, 0x02, 0x49, 2); + /* attenuation left 3db */ + I2CWrite(saa, 0x22, 0x03, 0x00, 2); + /* attenuation right 3db */ + I2CWrite(saa, 0x22, 0x04, 0x00, 2); + I2CWrite(saa, 0x22, 0x01, 0x10, 2); + if (I2CRead(saa, 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, 0x20, init8420[i * 2], init8420[i * 2 + 1], 2); + for (i = 0; i < MODE8420LEN; i++) + I2CWrite(saa, 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, 0x88, 0x3a, 0x13, 2); + else if (sequence[i * 2] == 0x6b) + I2CWrite(saa, 0x88, 0x6b, 0x20, 2); + else if (sequence[i * 2] == 0x6c) + I2CWrite(saa, 0x88, 0x6c, + dopal ? 0x09 : 0xf5, 2); + else if (sequence[i * 2] == 0x6d) + I2CWrite(saa, 0x88, 0x6d, + dopal ? 0x20 : 0x00, 2); + else if (sequence[i * 2] == 0x7a) + I2CWrite(saa, 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i * 2] == 0x7b) + I2CWrite(saa, 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else + I2CWrite(saa, 0x88, sequence[i * 2], + sequence[i * 2 + 1], 2); + } else { + if (sequence[i * 2] == 0x6b && mod) + I2CWrite(saa, 0x88, 0x6b, + (sequence[i * 2 + 1] ^ 0x09), 2); + else if (sequence[i * 2] == 0x7a) + I2CWrite(saa, 0x88, 0x7a, + dopal ? (PALFirstActive - 1) : + (NTSCFirstActive - 4), 2); + else if (sequence[i * 2] == 0x7b) + I2CWrite(saa, 0x88, 0x7b, + dopal ? PALLastActive : + NTSCLastActive, 2); + else + I2CWrite(saa, 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] && !strncmp("decxl", + bitdata->loadwhat, 8)) + loadfile = 1; /* special */ + 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 && 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); + i = debiread(saa, debNormal, IBM_MP2_OSD_SIZE, 2); + if (i != 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 Inited\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 inode *inode, struct file *file, + unsigned int cmd, unsigned long argl) +{ + struct saa7146 *saa = file->private_data; + void __user *arg = (void __user *)argl; + + 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; + + /* stop capture */ + if (vw.flags || vw.width < 16 || vw.height < 16) { + saawrite((SAA7146_MC1_TR_E_1 << 16), + SAA7146_MC1); + return -EINVAL; + } + /* 32-bit align start and adjust width */ + if (saa->win.bpp < 4) { + 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 > 16384) { + return -EINVAL; + } else if (vw.clipcount > 0) { + vcp = vmalloc(sizeof(struct video_clip) * + vw.clipcount); + if (vcp == 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(arg, &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(&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(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_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct saa7146 *saa = file->private_data; + printk(KERN_DEBUG "stradis%d: saa_mmap called\n", saa->nr); + return -EINVAL; +} + +static ssize_t saa_read(struct file *file, char __user * buf, + size_t count, loff_t * ppos) +{ + return -EINVAL; +} + +static ssize_t saa_write(struct file *file, const char __user * buf, + size_t count, loff_t * ppos) +{ + struct saa7146 *saa = file->private_data; + 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 inode *inode, struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct saa7146 *saa = container_of(vdev, struct saa7146, video_dev); + + file->private_data = saa; + + 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 int saa_release(struct inode *inode, struct file *file) +{ + struct saa7146 *saa = file->private_data; + saa->user--; + + if (saa->user > 0) /* still someone using device */ + return 0; + saawrite(0x007f0000, SAA7146_MC1); /* stop all overlay dma */ + return 0; +} + +static struct file_operations saa_fops = { + .owner = THIS_MODULE, + .open = saa_open, + .release = saa_release, + .ioctl = saa_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .read = saa_read, + .llseek = no_llseek, + .write = saa_write, + .mmap = saa_mmap, +}; + +/* template for video_device-structure */ +static struct video_device saa_template = { + .name = "SAA7146A", + .type = VID_TYPE_CAPTURE | VID_TYPE_OVERLAY, + .hardware = VID_HARDWARE_SAA7146, + .fops = &saa_fops, + .minor = -1, +}; + +static int __devinit configure_saa7146(struct pci_dev *pdev, int num) +{ + int retval; + struct saa7146 *saa = pci_get_drvdata(pdev); + + 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->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); + + retval = pci_enable_device(pdev); + if (retval) { + dev_err(&pdev->dev, "%d: pci_enable_device failed!\n", num); + goto err; + } + + saa->id = pdev->device; + saa->irq = pdev->irq; + saa->video_dev.minor = -1; + saa->saa7146_adr = pci_resource_start(pdev, 0); + pci_read_config_byte(pdev, PCI_CLASS_REVISION, &saa->revision); + + saa->saa7146_mem = ioremap(saa->saa7146_adr, 0x200); + if (saa->saa7146_mem == NULL) { + dev_err(&pdev->dev, "%d: ioremap failed!\n", num); + retval = -EIO; + goto err; + } + + memcpy(&saa->video_dev, &saa_template, sizeof(saa_template)); + saawrite(0, SAA7146_IER); /* turn off all interrupts */ + + retval = request_irq(saa->irq, saa7146_irq, SA_SHIRQ | SA_INTERRUPT, + "stradis", saa); + if (retval == -EINVAL) + dev_err(&pdev->dev, "%d: Bad irq number or handler\n", num); + else if (retval == -EBUSY) + dev_err(&pdev->dev, "%d: IRQ %ld busy, change your PnP config " + "in BIOS\n", num, saa->irq); + if (retval < 0) + goto errio; + + pci_set_master(pdev); + retval = video_register_device(&saa->video_dev, VFL_TYPE_GRABBER, + video_nr); + if (retval < 0) { + dev_err(&pdev->dev, "%d: error in registering video device!\n", + num); + goto errio; + } + + return 0; +errio: + iounmap(saa->saa7146_mem); +err: + return retval; +} + +static int __devinit init_saa7146(struct pci_dev *pdev) +{ + struct saa7146 *saa = pci_get_drvdata(pdev); + + 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) { + dev_err(&pdev->dev, "%d: debi kmalloc failed\n", saa->nr); + goto err; + } +#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 && (saa->vidbuf = vmalloc(524288)) == NULL) { + dev_err(&pdev->dev, "%d: malloc failed\n", saa->nr); + goto err; + } + if (saa->audbuf == NULL && (saa->audbuf = vmalloc(65536)) == NULL) { + dev_err(&pdev->dev, "%d: malloc failed\n", saa->nr); + goto errfree; + } + if (saa->osdbuf == NULL && (saa->osdbuf = vmalloc(131072)) == NULL) { + dev_err(&pdev->dev, "%d: malloc failed\n", saa->nr); + goto errfree; + } + /* allocate 81920 byte buffer for clipping */ + if ((saa->dmavid2 = kzalloc(VIDEO_CLIPMAP_SIZE, GFP_KERNEL)) == NULL) { + dev_err(&pdev->dev, "%d: clip kmalloc failed\n", saa->nr); + goto errfree; + } + /* 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); + + return 0; +errfree: + vfree(saa->osdbuf); + vfree(saa->audbuf); + vfree(saa->vidbuf); + saa->audbuf = saa->osdbuf = saa->vidbuf = NULL; +err: + return -ENOMEM; +} + +static void stradis_release_saa(struct pci_dev *pdev) +{ + u8 command; + struct saa7146 *saa = pci_get_drvdata(pdev); + + /* 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); + + /* disable PCI bus-mastering */ + pci_read_config_byte(pdev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte(pdev, PCI_COMMAND, command); + + /* unmap and free memory */ + saa->audhead = saa->audtail = saa->osdhead = 0; + saa->vidhead = saa->vidtail = saa->osdtail = 0; + vfree(saa->vidbuf); + vfree(saa->audbuf); + vfree(saa->osdbuf); + kfree(saa->dmavid2); + saa->audbuf = saa->vidbuf = saa->osdbuf = NULL; + saa->dmavid2 = NULL; + kfree(saa->dmadebi); + kfree(saa->dmavid1); + kfree(saa->dmavid3); + kfree(saa->dmaa1in); + kfree(saa->dmaa1out); + kfree(saa->dmaa2in); + kfree(saa->dmaa2out); + kfree(saa->dmaRPS1); + kfree(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 __devinit stradis_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int retval = -EINVAL; + + if (saa_num >= SAA7146_MAX) + goto err; + + if (!pdev->subsystem_vendor) + dev_info(&pdev->dev, "%d: rev1 decoder\n", saa_num); + else + dev_info(&pdev->dev, "%d: SDM2xx found\n", saa_num); + + pci_set_drvdata(pdev, &saa7146s[saa_num]); + + retval = configure_saa7146(pdev, saa_num); + if (retval) { + dev_err(&pdev->dev, "%d: error in configuring\n", saa_num); + goto err; + } + + if (init_saa7146(pdev) < 0) { + dev_err(&pdev->dev, "%d: error in initialization\n", saa_num); + retval = -EIO; + goto errrel; + } + + saa_num++; + + return 0; +errrel: + stradis_release_saa(pdev); +err: + return retval; +} + +static void __devexit stradis_remove(struct pci_dev *pdev) +{ + stradis_release_saa(pdev); +} + +static struct pci_device_id stradis_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, stradis_pci_tbl); + +static struct pci_driver stradis_driver = { + .name = "stradis", + .id_table = stradis_pci_tbl, + .probe = stradis_probe, + .remove = __devexit_p(stradis_remove) +}; + +int __init stradis_init(void) +{ + int retval; + + saa_num = 0; + + retval = pci_register_driver(&stradis_driver); + if (retval) + printk(KERN_ERR "stradis: Unable to register pci driver.\n"); + + return retval; +} + +void __exit stradis_exit(void) +{ + pci_unregister_driver(&stradis_driver); + printk(KERN_INFO "stradis: module cleanup complete\n"); +} + +module_init(stradis_init); +module_exit(stradis_exit); diff --git a/linux/drivers/media/video/stv680.c b/linux/drivers/media/video/stv680.c new file mode 100644 index 000000000..b33ed6f73 --- /dev/null +++ b/linux/drivers/media/video/stv680.c @@ -0,0 +1,1509 @@ +/* + * STV0680 USB Camera Driver, by Kevin Sisson (kjsisson@bellsouth.net) + * + * Thanks to STMicroelectronics for information on the usb commands, and + * to Steve Miller at STM for his help and encouragement while I was + * writing this driver. + * + * This driver is based heavily on the + * Endpoints (formerly known as AOX) se401 USB Camera Driver + * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org) + * + * Still somewhat based on the Linux ov511 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. + * + * 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. + * + * History: + * ver 0.1 October, 2001. Initial attempt. + * + * ver 0.2 November, 2001. Fixed asbility to resize, added brightness + * function, made more stable (?) + * + * ver 0.21 Nov, 2001. Added gamma correction and white balance, + * due to Alexander Schwartz. Still trying to + * improve stablility. Moved stuff into stv680.h + * + * ver 0.22 Nov, 2001. Added sharpen function (by Michael Sweet, + * mike@easysw.com) from GIMP, also used in pencam. + * Simple, fast, good integer math routine. + * + * ver 0.23 Dec, 2001 (gkh) + * Took out sharpen function, ran code through + * Lindent, and did other minor tweaks to get + * things to work properly with 2.5.1 + * + * ver 0.24 Jan, 2002 (kjs) + * Fixed the problem with webcam crashing after + * two pictures. Changed the way pic is halved to + * improve quality. Got rid of green line around + * frame. Fix brightness reset when changing size + * bug. Adjusted gamma filters slightly. + * + * ver 0.25 Jan, 2002 (kjs) + * Fixed a bug in which the driver sometimes attempted + * to set to a non-supported size. This allowed + * gnomemeeting to work. + * Fixed proc entry removal bug. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/errno.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +#include "stv680.h" + +static int video_nr = -1; +static int swapRGB = 0; /* default for auto sleect */ +static int swapRGB_on = 0; /* default to allow auto select; -1=swap never, +1= swap always */ + +static unsigned int debug = 0; + +#define PDEBUG(level, fmt, args...) \ + do { \ + if (debug >= level) \ + info("[%s:%d] " fmt, __FUNCTION__, __LINE__ , ## args); \ + } while (0) + + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.25" +#define DRIVER_AUTHOR "Kevin Sisson <kjsisson@bellsouth.net>" +#define DRIVER_DESC "STV0680 USB Camera Driver" + +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_LICENSE ("GPL"); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC (debug, "Debug enabled or not"); +module_param(swapRGB_on, int, 0); +MODULE_PARM_DESC (swapRGB_on, "Red/blue swap: 1=always, 0=auto, -1=never"); +module_param(video_nr, int, 0); + +/******************************************************************** + * + * 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 + * + * So I copied it again for the ov511 driver -claudio + * + * Same for the se401 driver -Jeroen + * + * And the STV0680 driver - Kevin + ********************************************************************/ +static void *rvmalloc (unsigned long size) +{ + void *mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + 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) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + return mem; +} + +static void rvfree (void *mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree (mem); +} + + +/********************************************************************* + * pencam read/write functions + ********************************************************************/ + +static int stv_sndctrl (int set, struct usb_stv *stv680, unsigned short req, unsigned short value, unsigned char *buffer, int size) +{ + int ret = -1; + + switch (set) { + case 0: /* 0xc1 */ + ret = usb_control_msg (stv680->udev, + usb_rcvctrlpipe (stv680->udev, 0), + req, + (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 1: /* 0x41 */ + ret = usb_control_msg (stv680->udev, + usb_sndctrlpipe (stv680->udev, 0), + req, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 2: /* 0x80 */ + ret = usb_control_msg (stv680->udev, + usb_rcvctrlpipe (stv680->udev, 0), + req, + (USB_DIR_IN | USB_RECIP_DEVICE), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + case 3: /* 0x40 */ + ret = usb_control_msg (stv680->udev, + usb_sndctrlpipe (stv680->udev, 0), + req, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + value, 0, buffer, size, PENCAM_TIMEOUT); + break; + + } + if ((ret < 0) && (req != 0x0a)) { + PDEBUG (1, "STV(e): usb_control_msg error %i, request = 0x%x, error = %i", set, req, ret); + } + return ret; +} + +static int stv_set_config (struct usb_stv *dev, int configuration, int interface, int alternate) +{ + + if (configuration != dev->udev->actconfig->desc.bConfigurationValue + || usb_reset_configuration (dev->udev) < 0) { + PDEBUG (1, "STV(e): FAILED to reset configuration %i", configuration); + return -1; + } + if (usb_set_interface (dev->udev, interface, alternate) < 0) { + PDEBUG (1, "STV(e): FAILED to set alternate interface %i", alternate); + return -1; + } + return 0; +} + +static int stv_stop_video (struct usb_stv *dev) +{ + int i; + unsigned char *buf; + + buf = kmalloc (40, GFP_KERNEL); + if (buf == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + + /* this is a high priority command; it stops all lower order commands */ + if ((i = stv_sndctrl (1, dev, 0x04, 0x0000, buf, 0x0)) < 0) { + i = stv_sndctrl (0, dev, 0x80, 0, buf, 0x02); /* Get Last Error; 2 = busy */ + PDEBUG (1, "STV(i): last error: %i, command = 0x%x", buf[0], buf[1]); + } else { + PDEBUG (1, "STV(i): Camera reset to idle mode."); + } + + if ((i = stv_set_config (dev, 1, 0, 0)) < 0) + PDEBUG (1, "STV(e): Reset config during exit failed"); + + /* get current mode */ + buf[0] = 0xf0; + if ((i = stv_sndctrl (0, dev, 0x87, 0, buf, 0x08)) != 0x08) /* get mode */ + PDEBUG (0, "STV(e): Stop_video: problem setting original mode"); + if (dev->origMode != buf[0]) { + memset (buf, 0, 8); + buf[0] = (unsigned char) dev->origMode; + if ((i = stv_sndctrl (3, dev, 0x07, 0x0100, buf, 0x08)) != 0x08) { + PDEBUG (0, "STV(e): Stop_video: Set_Camera_Mode failed"); + i = -1; + } + buf[0] = 0xf0; + i = stv_sndctrl (0, dev, 0x87, 0, buf, 0x08); + if ((i != 0x08) || (buf[0] != dev->origMode)) { + PDEBUG (0, "STV(e): camera NOT set to original resolution."); + i = -1; + } else + PDEBUG (0, "STV(i): Camera set to original resolution"); + } + /* origMode */ + kfree(buf); + return i; +} + +static int stv_set_video_mode (struct usb_stv *dev) +{ + int i, stop_video = 1; + unsigned char *buf; + + buf = kmalloc (40, GFP_KERNEL); + if (buf == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + + if ((i = stv_set_config (dev, 1, 0, 0)) < 0) { + kfree(buf); + return i; + } + + i = stv_sndctrl (2, dev, 0x06, 0x0100, buf, 0x12); + if (!(i > 0) && (buf[8] == 0x53) && (buf[9] == 0x05)) { + PDEBUG (1, "STV(e): Could not get descriptor 0100."); + goto error; + } + + /* set alternate interface 1 */ + if ((i = stv_set_config (dev, 1, 0, 1)) < 0) + goto error; + + if ((i = stv_sndctrl (0, dev, 0x85, 0, buf, 0x10)) != 0x10) + goto error; + PDEBUG (1, "STV(i): Setting video mode."); + /* Switch to Video mode: 0x0100 = VGA (640x480), 0x0000 = CIF (352x288) 0x0300 = QVGA (320x240) */ + if ((i = stv_sndctrl (1, dev, 0x09, dev->VideoMode, buf, 0x0)) < 0) { + stop_video = 0; + goto error; + } + goto exit; + +error: + kfree(buf); + if (stop_video == 1) + stv_stop_video (dev); + return -1; + +exit: + kfree(buf); + return 0; +} + +static int stv_init (struct usb_stv *stv680) +{ + int i = 0; + unsigned char *buffer; + unsigned long int bufsize; + + buffer = kzalloc (40, GFP_KERNEL); + if (buffer == NULL) { + PDEBUG (0, "STV(e): Out of (small buf) memory"); + return -1; + } + udelay (100); + + /* set config 1, interface 0, alternate 0 */ + if ((i = stv_set_config (stv680, 1, 0, 0)) < 0) { + kfree(buffer); + PDEBUG (0, "STV(e): set config 1,0,0 failed"); + return -1; + } + /* ping camera to be sure STV0680 is present */ + if ((i = stv_sndctrl (0, stv680, 0x88, 0x5678, buffer, 0x02)) != 0x02) + goto error; + if ((buffer[0] != 0x56) || (buffer[1] != 0x78)) { + PDEBUG (1, "STV(e): camera ping failed!!"); + goto error; + } + + /* get camera descriptor */ + if ((i = stv_sndctrl (2, stv680, 0x06, 0x0200, buffer, 0x09)) != 0x09) + goto error; + i = stv_sndctrl (2, stv680, 0x06, 0x0200, buffer, 0x22); + if (!(i >= 0) && (buffer[7] == 0xa0) && (buffer[8] == 0x23)) { + PDEBUG (1, "STV(e): Could not get descriptor 0200."); + goto error; + } + if ((i = stv_sndctrl (0, stv680, 0x8a, 0, buffer, 0x02)) != 0x02) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x8b, 0, buffer, 0x24)) != 0x24) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x85, 0, buffer, 0x10)) != 0x10) + goto error; + + stv680->SupportedModes = buffer[7]; + i = stv680->SupportedModes; + stv680->CIF = 0; + stv680->VGA = 0; + stv680->QVGA = 0; + if (i & 1) + stv680->CIF = 1; + if (i & 2) + stv680->VGA = 1; + if (i & 8) + stv680->QVGA = 1; + if (stv680->SupportedModes == 0) { + PDEBUG (0, "STV(e): There are NO supported STV680 modes!!"); + i = -1; + goto error; + } else { + if (stv680->CIF) + PDEBUG (0, "STV(i): CIF is supported"); + if (stv680->QVGA) + PDEBUG (0, "STV(i): QVGA is supported"); + } + /* FW rev, ASIC rev, sensor ID */ + PDEBUG (1, "STV(i): Firmware rev is %i.%i", buffer[0], buffer[1]); + PDEBUG (1, "STV(i): ASIC rev is %i.%i", buffer[2], buffer[3]); + PDEBUG (1, "STV(i): Sensor ID is %i", (buffer[4]*16) + (buffer[5]>>4)); + + /* set alternate interface 1 */ + if ((i = stv_set_config (stv680, 1, 0, 1)) < 0) + goto error; + + if ((i = stv_sndctrl (0, stv680, 0x85, 0, buffer, 0x10)) != 0x10) + goto error; + if ((i = stv_sndctrl (0, stv680, 0x8d, 0, buffer, 0x08)) != 0x08) + goto error; + i = buffer[3]; + PDEBUG (0, "STV(i): Camera has %i pictures.", i); + + /* get current mode */ + if ((i = stv_sndctrl (0, stv680, 0x87, 0, buffer, 0x08)) != 0x08) + goto error; + stv680->origMode = buffer[0]; /* 01 = VGA, 03 = QVGA, 00 = CIF */ + + /* This will attemp CIF mode, if supported. If not, set to QVGA */ + memset (buffer, 0, 8); + if (stv680->CIF) + buffer[0] = 0x00; + else if (stv680->QVGA) + buffer[0] = 0x03; + if ((i = stv_sndctrl (3, stv680, 0x07, 0x0100, buffer, 0x08)) != 0x08) { + PDEBUG (0, "STV(i): Set_Camera_Mode failed"); + i = -1; + goto error; + } + buffer[0] = 0xf0; + stv_sndctrl (0, stv680, 0x87, 0, buffer, 0x08); + if (((stv680->CIF == 1) && (buffer[0] != 0x00)) || ((stv680->QVGA == 1) && (buffer[0] != 0x03))) { + PDEBUG (0, "STV(e): Error setting camera video mode!"); + i = -1; + goto error; + } else { + if (buffer[0] == 0) { + stv680->VideoMode = 0x0000; + PDEBUG (0, "STV(i): Video Mode set to CIF"); + } + if (buffer[0] == 0x03) { + stv680->VideoMode = 0x0300; + PDEBUG (0, "STV(i): Video Mode set to QVGA"); + } + } + if ((i = stv_sndctrl (0, stv680, 0x8f, 0, buffer, 0x10)) != 0x10) + goto error; + bufsize = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3]); + stv680->cwidth = (buffer[4] << 8) | (buffer[5]); /* ->camera = 322, 356, 644 */ + stv680->cheight = (buffer[6] << 8) | (buffer[7]); /* ->camera = 242, 292, 484 */ + stv680->origGain = buffer[12]; + + goto exit; + +error: + i = stv_sndctrl (0, stv680, 0x80, 0, buffer, 0x02); /* Get Last Error */ + PDEBUG (1, "STV(i): last error: %i, command = 0x%x", buffer[0], buffer[1]); + kfree(buffer); + return -1; + +exit: + kfree(buffer); + + /* video = 320x240, 352x288 */ + if (stv680->CIF == 1) { + stv680->maxwidth = 352; + stv680->maxheight = 288; + stv680->vwidth = 352; + stv680->vheight = 288; + } + if (stv680->QVGA == 1) { + stv680->maxwidth = 320; + stv680->maxheight = 240; + stv680->vwidth = 320; + stv680->vheight = 240; + } + + stv680->rawbufsize = bufsize; /* must be ./. by 8 */ + stv680->maxframesize = bufsize * 3; /* RGB size */ + PDEBUG (2, "STV(i): cwidth = %i, cheight = %i", stv680->cwidth, stv680->cheight); + PDEBUG (1, "STV(i): width = %i, height = %i, rawbufsize = %li", stv680->vwidth, stv680->vheight, stv680->rawbufsize); + + /* some default values */ + stv680->bulk_in_endpointAddr = 0x82; + stv680->dropped = 0; + stv680->error = 0; + stv680->framecount = 0; + stv680->readcount = 0; + stv680->streaming = 0; + /* bright, white, colour, hue, contrast are set by software, not in stv0680 */ + stv680->brightness = 32767; + stv680->chgbright = 0; + stv680->whiteness = 0; /* only for greyscale */ + stv680->colour = 32767; + stv680->contrast = 32767; + stv680->hue = 32767; + stv680->palette = STV_VIDEO_PALETTE; + stv680->depth = 24; /* rgb24 bits */ + if ((swapRGB_on == 0) && (swapRGB == 0)) + PDEBUG (1, "STV(i): swapRGB is (auto) OFF"); + else if ((swapRGB_on == 0) && (swapRGB == 1)) + PDEBUG (1, "STV(i): swapRGB is (auto) ON"); + else if (swapRGB_on == 1) + PDEBUG (1, "STV(i): swapRGB is (forced) ON"); + else if (swapRGB_on == -1) + PDEBUG (1, "STV(i): swapRGB is (forced) OFF"); + + if (stv_set_video_mode (stv680) < 0) { + PDEBUG (0, "STV(e): Could not set video mode in stv_init"); + return -1; + } + + return 0; +} + +/***************** last of pencam routines *******************/ + +/**************************************************************************** + * sysfs + ***************************************************************************/ +#define stv680_file(name, variable, field) \ +static ssize_t show_##name(struct class_device *class_dev, char *buf) \ +{ \ + struct video_device *vdev = to_video_device(class_dev); \ + struct usb_stv *stv = video_get_drvdata(vdev); \ + return sprintf(buf, field, stv->variable); \ +} \ +static CLASS_DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); + +stv680_file(model, camera_name, "%s\n"); +stv680_file(in_use, user, "%d\n"); +stv680_file(streaming, streaming, "%d\n"); +stv680_file(palette, palette, "%i\n"); +stv680_file(frames_total, readcount, "%d\n"); +stv680_file(frames_read, framecount, "%d\n"); +stv680_file(packets_dropped, dropped, "%d\n"); +stv680_file(decoding_errors, error, "%d\n"); + +static void stv680_create_sysfs_files(struct video_device *vdev) +{ + video_device_create_file(vdev, &class_device_attr_model); + video_device_create_file(vdev, &class_device_attr_in_use); + video_device_create_file(vdev, &class_device_attr_streaming); + video_device_create_file(vdev, &class_device_attr_palette); + video_device_create_file(vdev, &class_device_attr_frames_total); + video_device_create_file(vdev, &class_device_attr_frames_read); + video_device_create_file(vdev, &class_device_attr_packets_dropped); + video_device_create_file(vdev, &class_device_attr_decoding_errors); +} + +static void stv680_remove_sysfs_files(struct video_device *vdev) +{ + video_device_remove_file(vdev, &class_device_attr_model); + video_device_remove_file(vdev, &class_device_attr_in_use); + video_device_remove_file(vdev, &class_device_attr_streaming); + video_device_remove_file(vdev, &class_device_attr_palette); + video_device_remove_file(vdev, &class_device_attr_frames_total); + video_device_remove_file(vdev, &class_device_attr_frames_read); + video_device_remove_file(vdev, &class_device_attr_packets_dropped); + video_device_remove_file(vdev, &class_device_attr_decoding_errors); +} + +/******************************************************************** + * Camera control + *******************************************************************/ + +static int stv680_get_pict (struct usb_stv *stv680, struct video_picture *p) +{ + /* This sets values for v4l interface. max/min = 65535/0 */ + + p->brightness = stv680->brightness; + p->whiteness = stv680->whiteness; /* greyscale */ + p->colour = stv680->colour; + p->contrast = stv680->contrast; + p->hue = stv680->hue; + p->palette = stv680->palette; + p->depth = stv680->depth; + return 0; +} + +static int stv680_set_pict (struct usb_stv *stv680, struct video_picture *p) +{ + /* See above stv680_get_pict */ + + if (p->palette != STV_VIDEO_PALETTE) { + PDEBUG (2, "STV(e): Palette set error in _set_pic"); + return 1; + } + + if (stv680->brightness != p->brightness) { + stv680->chgbright = 1; + stv680->brightness = p->brightness; + } + + stv680->whiteness = p->whiteness; /* greyscale */ + stv680->colour = p->colour; + stv680->contrast = p->contrast; + stv680->hue = p->hue; + stv680->palette = p->palette; + stv680->depth = p->depth; + + return 0; +} + +static void stv680_video_irq (struct urb *urb, struct pt_regs *regs) +{ + struct usb_stv *stv680 = urb->context; + int length = urb->actual_length; + + if (length < stv680->rawbufsize) + PDEBUG (2, "STV(i): Lost data in transfer: exp %li, got %i", stv680->rawbufsize, length); + + /* ohoh... */ + if (!stv680->streaming) + return; + + if (!stv680->udev) { + PDEBUG (0, "STV(e): device vapourished in video_irq"); + return; + } + + /* 0 sized packets happen if we are to fast, but sometimes the camera + keeps sending them forever... + */ + if (length && !urb->status) { + stv680->nullpackets = 0; + switch (stv680->scratch[stv680->scratch_next].state) { + case BUFFER_READY: + case BUFFER_BUSY: + stv680->dropped++; + break; + + case BUFFER_UNUSED: + memcpy (stv680->scratch[stv680->scratch_next].data, + (unsigned char *) urb->transfer_buffer, length); + stv680->scratch[stv680->scratch_next].state = BUFFER_READY; + stv680->scratch[stv680->scratch_next].length = length; + if (waitqueue_active (&stv680->wq)) { + wake_up_interruptible (&stv680->wq); + } + stv680->scratch_overflow = 0; + stv680->scratch_next++; + if (stv680->scratch_next >= STV680_NUMSCRATCH) + stv680->scratch_next = 0; + break; + } /* switch */ + } else { + stv680->nullpackets++; + if (stv680->nullpackets > STV680_MAX_NULLPACKETS) { + if (waitqueue_active (&stv680->wq)) { + wake_up_interruptible (&stv680->wq); + } + } + } /* if - else */ + + /* Resubmit urb for new data */ + urb->status = 0; + urb->dev = stv680->udev; + if (usb_submit_urb (urb, GFP_ATOMIC)) + PDEBUG (0, "STV(e): urb burned down in video irq"); + return; +} /* _video_irq */ + +static int stv680_start_stream (struct usb_stv *stv680) +{ + struct urb *urb; + int err = 0, i; + + stv680->streaming = 1; + + /* Do some memory allocation */ + for (i = 0; i < STV680_NUMFRAMES; i++) { + stv680->frame[i].data = stv680->fbuf + i * stv680->maxframesize; + stv680->frame[i].curpix = 0; + } + /* packet size = 4096 */ + for (i = 0; i < STV680_NUMSBUF; i++) { + stv680->sbuf[i].data = kmalloc (stv680->rawbufsize, GFP_KERNEL); + if (stv680->sbuf[i].data == NULL) { + PDEBUG (0, "STV(e): Could not kmalloc raw data buffer %i", i); + return -1; + } + } + + stv680->scratch_next = 0; + stv680->scratch_use = 0; + stv680->scratch_overflow = 0; + for (i = 0; i < STV680_NUMSCRATCH; i++) { + stv680->scratch[i].data = kmalloc (stv680->rawbufsize, GFP_KERNEL); + if (stv680->scratch[i].data == NULL) { + PDEBUG (0, "STV(e): Could not kmalloc raw scratch buffer %i", i); + return -1; + } + stv680->scratch[i].state = BUFFER_UNUSED; + } + + for (i = 0; i < STV680_NUMSBUF; i++) { + urb = usb_alloc_urb (0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + /* sbuf is urb->transfer_buffer, later gets memcpyed to scratch */ + usb_fill_bulk_urb (urb, stv680->udev, + usb_rcvbulkpipe (stv680->udev, stv680->bulk_in_endpointAddr), + stv680->sbuf[i].data, stv680->rawbufsize, + stv680_video_irq, stv680); + stv680->urb[i] = urb; + err = usb_submit_urb (stv680->urb[i], GFP_KERNEL); + if (err) + PDEBUG (0, "STV(e): urb burned down in start stream"); + } /* i STV680_NUMSBUF */ + + stv680->framecount = 0; + return 0; +} + +static int stv680_stop_stream (struct usb_stv *stv680) +{ + int i; + + if (!stv680->streaming || !stv680->udev) + return 1; + + stv680->streaming = 0; + + for (i = 0; i < STV680_NUMSBUF; i++) + if (stv680->urb[i]) { + usb_kill_urb (stv680->urb[i]); + usb_free_urb (stv680->urb[i]); + stv680->urb[i] = NULL; + kfree(stv680->sbuf[i].data); + } + for (i = 0; i < STV680_NUMSCRATCH; i++) { + kfree(stv680->scratch[i].data); + stv680->scratch[i].data = NULL; + } + + return 0; +} + +static int stv680_set_size (struct usb_stv *stv680, int width, int height) +{ + int wasstreaming = stv680->streaming; + + /* Check to see if we need to change */ + if ((stv680->vwidth == width) && (stv680->vheight == height)) + return 0; + + PDEBUG (1, "STV(i): size request for %i x %i", width, height); + /* Check for a valid mode */ + if ((!width || !height) || ((width & 1) || (height & 1))) { + PDEBUG (1, "STV(e): set_size error: request: v.width = %i, v.height = %i actual: stv.width = %i, stv.height = %i", width, height, stv680->vwidth, stv680->vheight); + return 1; + } + + if ((width < (stv680->maxwidth / 2)) || (height < (stv680->maxheight / 2))) { + width = stv680->maxwidth / 2; + height = stv680->maxheight / 2; + } else if ((width >= 158) && (width <= 166) && (stv680->QVGA == 1)) { + width = 160; + height = 120; + } else if ((width >= 172) && (width <= 180) && (stv680->CIF == 1)) { + width = 176; + height = 144; + } else if ((width >= 318) && (width <= 350) && (stv680->QVGA == 1)) { + width = 320; + height = 240; + } else if ((width >= 350) && (width <= 358) && (stv680->CIF == 1)) { + width = 352; + height = 288; + } else { + PDEBUG (1, "STV(e): request for non-supported size: request: v.width = %i, v.height = %i actual: stv.width = %i, stv.height = %i", width, height, stv680->vwidth, stv680->vheight); + return 1; + } + + /* Stop a current stream and start it again at the new size */ + if (wasstreaming) + stv680_stop_stream (stv680); + stv680->vwidth = width; + stv680->vheight = height; + PDEBUG (1, "STV(i): size set to %i x %i", stv680->vwidth, stv680->vheight); + if (wasstreaming) + stv680_start_stream (stv680); + + return 0; +} + +/********************************************************************** + * Video Decoding + **********************************************************************/ + +/******* routines from the pencam program; hey, they work! ********/ + +/* + * STV0680 Vision Camera Chipset Driver + * Copyright (C) 2000 Adam Harrison <adam@antispin.org> +*/ + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define AD(x, y, w) (((y)*(w)+(x))*3) + +static void bayer_unshuffle (struct usb_stv *stv680, struct stv680_scratch *buffer) +{ + int x, y, i; + int w = stv680->cwidth; + int vw = stv680->cwidth, vh = stv680->cheight; + unsigned int p = 0; + int colour = 0, bayer = 0; + unsigned char *raw = buffer->data; + struct stv680_frame *frame = &stv680->frame[stv680->curframe]; + unsigned char *output = frame->data; + unsigned char *temp = frame->data; + int offset = buffer->offset; + + if (frame->curpix == 0) { + if (frame->grabstate == FRAME_READY) { + frame->grabstate = FRAME_GRABBING; + } + } + if (offset != frame->curpix) { /* Regard frame as lost :( */ + frame->curpix = 0; + stv680->error++; + return; + } + + if ((stv680->vwidth == 320) || (stv680->vwidth == 160)) { + vw = 320; + vh = 240; + } + if ((stv680->vwidth == 352) || (stv680->vwidth == 176)) { + vw = 352; + vh = 288; + } + + memset (output, 0, 3 * vw * vh); /* clear output matrix. */ + + for (y = 0; y < vh; y++) { + for (x = 0; x < vw; x++) { + if (x & 1) + p = *(raw + y * w + (x >> 1)); + else + p = *(raw + y * w + (x >> 1) + (w >> 1)); + + if (y & 1) + bayer = 2; + else + bayer = 0; + if (x & 1) + bayer++; + + switch (bayer) { + case 0: + case 3: + colour = 1; + break; + case 1: + colour = 0; + break; + case 2: + colour = 2; + break; + } + i = (y * vw + x) * 3; + *(output + i + colour) = (unsigned char) p; + } /* for x */ + + } /* for y */ + + /****** gamma correction plus hardcoded white balance */ + /* Thanks to Alexander Schwartx <alexander.schwartx@gmx.net> for this code. + Correction values red[], green[], blue[], are generated by + (pow(i/256.0, GAMMA)*255.0)*white balanceRGB where GAMMA=0.55, 1<i<255. + White balance (RGB)= 1.0, 1.17, 1.48. Values are calculated as double float and + converted to unsigned char. Values are in stv680.h */ + + for (y = 0; y < vh; y++) { + for (x = 0; x < vw; x++) { + i = (y * vw + x) * 3; + *(output + i) = red[*(output + i)]; + *(output + i + 1) = green[*(output + i + 1)]; + *(output + i + 2) = blue[*(output + i + 2)]; + } + } + + /****** bayer demosaic ******/ + for (y = 1; y < (vh - 1); y++) { + for (x = 1; x < (vw - 1); x++) { /* work out pixel type */ + if (y & 1) + bayer = 0; + else + bayer = 2; + if (!(x & 1)) + bayer++; + + switch (bayer) { + case 0: /* green. blue lr, red tb */ + *(output + AD (x, y, vw) + BLUE) = ((int) *(output + AD (x - 1, y, vw) + BLUE) + (int) *(output + AD (x + 1, y, vw) + BLUE)) >> 1; + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x, y - 1, vw) + RED) + (int) *(output + AD (x, y + 1, vw) + RED)) >> 1; + break; + + case 1: /* blue. green lrtb, red diagonals */ + *(output + AD (x, y, vw) + GREEN) = ((int) *(output + AD (x - 1, y, vw) + GREEN) + (int) *(output + AD (x + 1, y, vw) + GREEN) + (int) *(output + AD (x, y - 1, vw) + GREEN) + (int) *(output + AD (x, y + 1, vw) + GREEN)) >> 2; + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x - 1, y - 1, vw) + RED) + (int) *(output + AD (x - 1, y + 1, vw) + RED) + (int) *(output + AD (x + 1, y - 1, vw) + RED) + (int) *(output + AD (x + 1, y + 1, vw) + RED)) >> 2; + break; + + case 2: /* red. green lrtb, blue diagonals */ + *(output + AD (x, y, vw) + GREEN) = ((int) *(output + AD (x - 1, y, vw) + GREEN) + (int) *(output + AD (x + 1, y, vw) + GREEN) + (int) *(output + AD (x, y - 1, vw) + GREEN) + (int) *(output + AD (x, y + 1, vw) + GREEN)) >> 2; + *(output + AD (x, y, vw) + BLUE) = ((int) *(output + AD (x - 1, y - 1, vw) + BLUE) + (int) *(output + AD (x + 1, y - 1, vw) + BLUE) + (int) *(output + AD (x - 1, y + 1, vw) + BLUE) + (int) *(output + AD (x + 1, y + 1, vw) + BLUE)) >> 2; + break; + + case 3: /* green. red lr, blue tb */ + *(output + AD (x, y, vw) + RED) = ((int) *(output + AD (x - 1, y, vw) + RED) + (int) *(output + AD (x + 1, y, vw) + RED)) >> 1; + *(output + AD (x, y, vw) + BLUE) = ((int) *(output + AD (x, y - 1, vw) + BLUE) + (int) *(output + AD (x, y + 1, vw) + BLUE)) >> 1; + break; + } /* switch */ + } /* for x */ + } /* for y - end demosaic */ + + /* fix top and bottom row, left and right side */ + i = vw * 3; + memcpy (output, (output + i), i); + memcpy ((output + (vh * i)), (output + ((vh - 1) * i)), i); + for (y = 0; y < vh; y++) { + i = y * vw * 3; + memcpy ((output + i), (output + i + 3), 3); + memcpy ((output + i + (vw * 3)), (output + i + (vw - 1) * 3), 3); + } + + /* process all raw data, then trim to size if necessary */ + if ((stv680->vwidth == 160) || (stv680->vwidth == 176)) { + i = 0; + for (y = 0; y < vh; y++) { + if (!(y & 1)) { + for (x = 0; x < vw; x++) { + p = (y * vw + x) * 3; + if (!(x & 1)) { + *(output + i) = *(output + p); + *(output + i + 1) = *(output + p + 1); + *(output + i + 2) = *(output + p + 2); + i += 3; + } + } /* for x */ + } + } /* for y */ + } + /* reset to proper width */ + if ((stv680->vwidth == 160)) { + vw = 160; + vh = 120; + } + if ((stv680->vwidth == 176)) { + vw = 176; + vh = 144; + } + + /* output is RGB; some programs want BGR */ + /* swapRGB_on=0 -> program decides; swapRGB_on=1, always swap */ + /* swapRGB_on=-1, never swap */ + if (((swapRGB == 1) && (swapRGB_on != -1)) || (swapRGB_on == 1)) { + for (y = 0; y < vh; y++) { + for (x = 0; x < vw; x++) { + i = (y * vw + x) * 3; + *(temp) = *(output + i); + *(output + i) = *(output + i + 2); + *(output + i + 2) = *(temp); + } + } + } + /* brightness */ + if (stv680->chgbright == 1) { + if (stv680->brightness >= 32767) { + p = (stv680->brightness - 32767) / 256; + for (x = 0; x < (vw * vh * 3); x++) { + if ((*(output + x) + (unsigned char) p) > 255) + *(output + x) = 255; + else + *(output + x) += (unsigned char) p; + } /* for */ + } else { + p = (32767 - stv680->brightness) / 256; + for (x = 0; x < (vw * vh * 3); x++) { + if ((unsigned char) p > *(output + x)) + *(output + x) = 0; + else + *(output + x) -= (unsigned char) p; + } /* for */ + } /* else */ + } + /* if */ + frame->curpix = 0; + frame->curlinepix = 0; + frame->grabstate = FRAME_DONE; + stv680->framecount++; + stv680->readcount++; + if (stv680->frame[(stv680->curframe + 1) & (STV680_NUMFRAMES - 1)].grabstate == FRAME_READY) { + stv680->curframe = (stv680->curframe + 1) & (STV680_NUMFRAMES - 1); + } + +} /* bayer_unshuffle */ + +/******* end routines from the pencam program *********/ + +static int stv680_newframe (struct usb_stv *stv680, int framenr) +{ + int errors = 0; + + while (stv680->streaming && (stv680->frame[framenr].grabstate == FRAME_READY || stv680->frame[framenr].grabstate == FRAME_GRABBING)) { + if (!stv680->frame[framenr].curpix) { + errors++; + } + wait_event_interruptible (stv680->wq, (stv680->scratch[stv680->scratch_use].state == BUFFER_READY)); + + if (stv680->nullpackets > STV680_MAX_NULLPACKETS) { + stv680->nullpackets = 0; + PDEBUG (2, "STV(i): too many null length packets, restarting capture"); + stv680_stop_stream (stv680); + stv680_start_stream (stv680); + } else { + if (stv680->scratch[stv680->scratch_use].state != BUFFER_READY) { + stv680->frame[framenr].grabstate = FRAME_ERROR; + PDEBUG (2, "STV(e): FRAME_ERROR in _newframe"); + return -EIO; + } + stv680->scratch[stv680->scratch_use].state = BUFFER_BUSY; + + bayer_unshuffle (stv680, &stv680->scratch[stv680->scratch_use]); + + stv680->scratch[stv680->scratch_use].state = BUFFER_UNUSED; + stv680->scratch_use++; + if (stv680->scratch_use >= STV680_NUMSCRATCH) + stv680->scratch_use = 0; + if (errors > STV680_MAX_ERRORS) { + errors = 0; + PDEBUG (2, "STV(i): too many errors, restarting capture"); + stv680_stop_stream (stv680); + stv680_start_stream (stv680); + } + } /* else */ + } /* while */ + return 0; +} + +/********************************************************************* + * Video4Linux + *********************************************************************/ + +static int stv_open (struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct usb_stv *stv680 = video_get_drvdata(dev); + int err = 0; + + /* we are called with the BKL held */ + stv680->user = 1; + err = stv_init (stv680); /* main initialization routine for camera */ + + if (err >= 0) { + stv680->fbuf = rvmalloc (stv680->maxframesize * STV680_NUMFRAMES); + if (!stv680->fbuf) { + PDEBUG (0, "STV(e): Could not rvmalloc frame bufer"); + err = -ENOMEM; + } + file->private_data = dev; + } + if (err) + stv680->user = 0; + + return err; +} + +static int stv_close (struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + struct usb_stv *stv680 = video_get_drvdata(dev); + int i; + + for (i = 0; i < STV680_NUMFRAMES; i++) + stv680->frame[i].grabstate = FRAME_UNUSED; + if (stv680->streaming) + stv680_stop_stream (stv680); + + if ((i = stv_stop_video (stv680)) < 0) + PDEBUG (1, "STV(e): stop_video failed in stv_close"); + + rvfree (stv680->fbuf, stv680->maxframesize * STV680_NUMFRAMES); + stv680->user = 0; + + if (stv680->removed) { + kfree(stv680); + stv680 = NULL; + PDEBUG (0, "STV(i): device unregistered"); + } + file->private_data = NULL; + return 0; +} + +static int stv680_do_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = file->private_data; + struct usb_stv *stv680 = video_get_drvdata(vdev); + + if (!stv680->udev) + return -EIO; + + switch (cmd) { + case VIDIOCGCAP:{ + struct video_capability *b = arg; + + strcpy (b->name, stv680->camera_name); + b->type = VID_TYPE_CAPTURE; + b->channels = 1; + b->audios = 0; + b->maxwidth = stv680->maxwidth; + b->maxheight = stv680->maxheight; + b->minwidth = stv680->maxwidth / 2; + b->minheight = stv680->maxheight / 2; + return 0; + } + case VIDIOCGCHAN:{ + struct video_channel *v = arg; + + if (v->channel != 0) + return -EINVAL; + v->flags = 0; + v->tuners = 0; + v->type = VIDEO_TYPE_CAMERA; + strcpy (v->name, "STV Camera"); + return 0; + } + case VIDIOCSCHAN:{ + struct video_channel *v = arg; + if (v->channel != 0) + return -EINVAL; + return 0; + } + case VIDIOCGPICT:{ + struct video_picture *p = arg; + + stv680_get_pict (stv680, p); + return 0; + } + case VIDIOCSPICT:{ + struct video_picture *p = arg; + + if (stv680_set_pict (stv680, p)) + return -EINVAL; + return 0; + } + case VIDIOCSWIN:{ + struct video_window *vw = arg; + + if (vw->flags) + return -EINVAL; + if (vw->clipcount) + return -EINVAL; + if (vw->width != stv680->vwidth) { + if (stv680_set_size (stv680, vw->width, vw->height)) { + PDEBUG (2, "STV(e): failed (from user) set size in VIDIOCSWIN"); + return -EINVAL; + } + } + return 0; + } + case VIDIOCGWIN:{ + struct video_window *vw = arg; + + vw->x = 0; /* FIXME */ + vw->y = 0; + vw->chromakey = 0; + vw->flags = 0; + vw->clipcount = 0; + vw->width = stv680->vwidth; + vw->height = stv680->vheight; + return 0; + } + case VIDIOCGMBUF:{ + struct video_mbuf *vm = arg; + int i; + + memset (vm, 0, sizeof (*vm)); + vm->size = STV680_NUMFRAMES * stv680->maxframesize; + vm->frames = STV680_NUMFRAMES; + for (i = 0; i < STV680_NUMFRAMES; i++) + vm->offsets[i] = stv680->maxframesize * i; + return 0; + } + case VIDIOCMCAPTURE:{ + struct video_mmap *vm = arg; + + if (vm->format != STV_VIDEO_PALETTE) { + PDEBUG (2, "STV(i): VIDIOCMCAPTURE vm.format (%i) != VIDEO_PALETTE (%i)", + vm->format, STV_VIDEO_PALETTE); + if ((vm->format == 3) && (swapRGB_on == 0)) { + PDEBUG (2, "STV(i): VIDIOCMCAPTURE swapRGB is (auto) ON"); + /* this may fix those apps (e.g., xawtv) that want BGR */ + swapRGB = 1; + } + return -EINVAL; + } + if (vm->frame >= STV680_NUMFRAMES) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE vm.frame > NUMFRAMES"); + return -EINVAL; + } + if ((stv680->frame[vm->frame].grabstate == FRAME_ERROR) + || (stv680->frame[vm->frame].grabstate == FRAME_GRABBING)) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE grabstate (%i) error", + stv680->frame[vm->frame].grabstate); + return -EBUSY; + } + /* Is this according to the v4l spec??? */ + if (stv680->vwidth != vm->width) { + if (stv680_set_size (stv680, vm->width, vm->height)) { + PDEBUG (2, "STV(e): VIDIOCMCAPTURE set_size failed"); + return -EINVAL; + } + } + stv680->frame[vm->frame].grabstate = FRAME_READY; + + if (!stv680->streaming) + stv680_start_stream (stv680); + + return 0; + } + case VIDIOCSYNC:{ + int *frame = arg; + int ret = 0; + + if (*frame < 0 || *frame >= STV680_NUMFRAMES) { + PDEBUG (2, "STV(e): Bad frame # in VIDIOCSYNC"); + return -EINVAL; + } + ret = stv680_newframe (stv680, *frame); + stv680->frame[*frame].grabstate = FRAME_UNUSED; + return ret; + } + case VIDIOCGFBUF:{ + struct video_buffer *vb = arg; + + memset (vb, 0, sizeof (*vb)); + return 0; + } + case VIDIOCKEY: + return 0; + case VIDIOCCAPTURE: + { + PDEBUG (2, "STV(e): VIDIOCCAPTURE failed"); + return -EINVAL; + } + case VIDIOCSFBUF: + case VIDIOCGTUNER: + case VIDIOCSTUNER: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } /* end switch */ + + return 0; +} + +static int stv680_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, stv680_do_ioctl); +} + +static int stv680_mmap (struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + struct usb_stv *stv680 = video_get_drvdata(dev); + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end-vma->vm_start; + unsigned long page, pos; + + mutex_lock(&stv680->lock); + + if (stv680->udev == NULL) { + mutex_unlock(&stv680->lock); + return -EIO; + } + if (size > (((STV680_NUMFRAMES * stv680->maxframesize) + PAGE_SIZE - 1) + & ~(PAGE_SIZE - 1))) { + mutex_unlock(&stv680->lock); + return -EINVAL; + } + pos = (unsigned long) stv680->fbuf; + while (size > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { + mutex_unlock(&stv680->lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + mutex_unlock(&stv680->lock); + + return 0; +} + +static ssize_t stv680_read (struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *dev = file->private_data; + unsigned long int realcount = count; + int ret = 0; + struct usb_stv *stv680 = video_get_drvdata(dev); + unsigned long int i; + + if (STV680_NUMFRAMES != 2) { + PDEBUG (0, "STV(e): STV680_NUMFRAMES needs to be 2!"); + return -1; + } + if (stv680->udev == NULL) + return -EIO; + if (realcount > (stv680->vwidth * stv680->vheight * 3)) + realcount = stv680->vwidth * stv680->vheight * 3; + + /* Shouldn't happen: */ + if (stv680->frame[0].grabstate == FRAME_GRABBING) { + PDEBUG (2, "STV(e): FRAME_GRABBING in stv680_read"); + return -EBUSY; + } + stv680->frame[0].grabstate = FRAME_READY; + stv680->frame[1].grabstate = FRAME_UNUSED; + stv680->curframe = 0; + + if (!stv680->streaming) + stv680_start_stream (stv680); + + if (!stv680->streaming) { + ret = stv680_newframe (stv680, 0); /* ret should = 0 */ + } + + ret = stv680_newframe (stv680, 0); + + if (!ret) { + if ((i = copy_to_user (buf, stv680->frame[0].data, realcount)) != 0) { + PDEBUG (2, "STV(e): copy_to_user frame 0 failed, ret count = %li", i); + return -EFAULT; + } + } else { + realcount = ret; + } + stv680->frame[0].grabstate = FRAME_UNUSED; + return realcount; +} /* stv680_read */ + +static struct file_operations stv680_fops = { + .owner = THIS_MODULE, + .open = stv_open, + .release = stv_close, + .read = stv680_read, + .mmap = stv680_mmap, + .ioctl = stv680_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, +}; +static struct video_device stv680_template = { + .owner = THIS_MODULE, + .name = "STV0680 USB camera", + .type = VID_TYPE_CAPTURE, + .hardware = VID_HARDWARE_SE401, + .fops = &stv680_fops, + .release = video_device_release, + .minor = -1, +}; + +static int stv680_probe (struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_host_interface *interface; + struct usb_stv *stv680 = NULL; + char *camera_name = NULL; + int retval = 0; + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) { + PDEBUG (0, "STV(e): Number of Configurations != 1"); + return -ENODEV; + } + + interface = &intf->altsetting[0]; + /* Is it a STV680? */ + if ((le16_to_cpu(dev->descriptor.idVendor) == USB_PENCAM_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == USB_PENCAM_PRODUCT_ID)) { + camera_name = "STV0680"; + PDEBUG (0, "STV(i): STV0680 camera found."); + } else if ((le16_to_cpu(dev->descriptor.idVendor) == USB_CREATIVEGOMINI_VENDOR_ID) && + (le16_to_cpu(dev->descriptor.idProduct) == USB_CREATIVEGOMINI_PRODUCT_ID)) { + camera_name = "Creative WebCam Go Mini"; + PDEBUG (0, "STV(i): Creative WebCam Go Mini found."); + } else { + PDEBUG (0, "STV(e): Vendor/Product ID do not match STV0680 or Creative WebCam Go Mini values."); + PDEBUG (0, "STV(e): Check that the STV0680 or Creative WebCam Go Mini camera is connected to the computer."); + retval = -ENODEV; + goto error; + } + /* We found one */ + if ((stv680 = kzalloc (sizeof (*stv680), GFP_KERNEL)) == NULL) { + PDEBUG (0, "STV(e): couldn't kmalloc stv680 struct."); + retval = -ENOMEM; + goto error; + } + + stv680->udev = dev; + stv680->camera_name = camera_name; + + stv680->vdev = video_device_alloc(); + if (!stv680->vdev) { + retval = -ENOMEM; + goto error; + } + memcpy(stv680->vdev, &stv680_template, sizeof(stv680_template)); + stv680->vdev->dev = &intf->dev; + video_set_drvdata(stv680->vdev, stv680); + + memcpy (stv680->vdev->name, stv680->camera_name, strlen (stv680->camera_name)); + init_waitqueue_head (&stv680->wq); + mutex_init (&stv680->lock); + wmb (); + + if (video_register_device (stv680->vdev, VFL_TYPE_GRABBER, video_nr) == -1) { + PDEBUG (0, "STV(e): video_register_device failed"); + retval = -EIO; + goto error_vdev; + } + PDEBUG (0, "STV(i): registered new video device: video%d", stv680->vdev->minor); + + usb_set_intfdata (intf, stv680); + stv680_create_sysfs_files(stv680->vdev); + return 0; + +error_vdev: + video_device_release(stv680->vdev); +error: + kfree(stv680); + return retval; +} + +static inline void usb_stv680_remove_disconnected (struct usb_stv *stv680) +{ + int i; + + stv680->udev = NULL; + stv680->frame[0].grabstate = FRAME_ERROR; + stv680->frame[1].grabstate = FRAME_ERROR; + stv680->streaming = 0; + + wake_up_interruptible (&stv680->wq); + + for (i = 0; i < STV680_NUMSBUF; i++) + if (stv680->urb[i]) { + usb_kill_urb (stv680->urb[i]); + usb_free_urb (stv680->urb[i]); + stv680->urb[i] = NULL; + kfree(stv680->sbuf[i].data); + } + for (i = 0; i < STV680_NUMSCRATCH; i++) + kfree(stv680->scratch[i].data); + PDEBUG (0, "STV(i): %s disconnected", stv680->camera_name); + + /* Free the memory */ + kfree(stv680); +} + +static void stv680_disconnect (struct usb_interface *intf) +{ + struct usb_stv *stv680 = usb_get_intfdata (intf); + + usb_set_intfdata (intf, NULL); + + if (stv680) { + /* We don't want people trying to open up the device */ + if (stv680->vdev) { + stv680_remove_sysfs_files(stv680->vdev); + video_unregister_device(stv680->vdev); + stv680->vdev = NULL; + } + if (!stv680->user) { + usb_stv680_remove_disconnected (stv680); + } else { + stv680->removed = 1; + } + } +} + +static struct usb_driver stv680_driver = { + .name = "stv680", + .probe = stv680_probe, + .disconnect = stv680_disconnect, + .id_table = device_table +}; + +/******************************************************************** + * Module routines + ********************************************************************/ + +static int __init usb_stv680_init (void) +{ + if (usb_register (&stv680_driver) < 0) { + PDEBUG (0, "STV(e): Could not setup STV0680 driver"); + return -1; + } + PDEBUG (0, "STV(i): usb camera driver version %s registering", DRIVER_VERSION); + + info(DRIVER_DESC " " DRIVER_VERSION); + return 0; +} + +static void __exit usb_stv680_exit (void) +{ + usb_deregister (&stv680_driver); + PDEBUG (0, "STV(i): driver deregistered"); +} + +module_init (usb_stv680_init); +module_exit (usb_stv680_exit); diff --git a/linux/drivers/media/video/stv680.h b/linux/drivers/media/video/stv680.h new file mode 100644 index 000000000..a08f1b08a --- /dev/null +++ b/linux/drivers/media/video/stv680.h @@ -0,0 +1,227 @@ +/**************************************************************************** + * + * Filename: stv680.h + * + * Description: + * This is a USB driver for STV0680 based usb video cameras. + * + * 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. + * + ****************************************************************************/ + +/* size of usb transfers */ +#define STV680_PACKETSIZE 4096 + +/* number of queued bulk transfers to use, may have problems if > 1 */ +#define STV680_NUMSBUF 1 + +/* number of frames supported by the v4l part */ +#define STV680_NUMFRAMES 2 + +/* scratch buffers for passing data to the decoders: 2 or 4 are good */ +#define STV680_NUMSCRATCH 2 + +/* number of nul sized packets to receive before kicking the camera */ +#define STV680_MAX_NULLPACKETS 200 + +/* number of decoding errors before kicking the camera */ +#define STV680_MAX_ERRORS 100 + +#define USB_PENCAM_VENDOR_ID 0x0553 +#define USB_PENCAM_PRODUCT_ID 0x0202 + +#define USB_CREATIVEGOMINI_VENDOR_ID 0x041e +#define USB_CREATIVEGOMINI_PRODUCT_ID 0x4007 + +#define PENCAM_TIMEOUT 1000 +/* fmt 4 */ +#define STV_VIDEO_PALETTE VIDEO_PALETTE_RGB24 + +static struct usb_device_id device_table[] = { + {USB_DEVICE (USB_PENCAM_VENDOR_ID, USB_PENCAM_PRODUCT_ID)}, + {USB_DEVICE (USB_CREATIVEGOMINI_VENDOR_ID, USB_CREATIVEGOMINI_PRODUCT_ID)}, + {} +}; +MODULE_DEVICE_TABLE (usb, device_table); + +struct stv680_sbuf { + unsigned char *data; +}; + +enum { + FRAME_UNUSED, /* Unused (no MCAPTURE) */ + FRAME_READY, /* Ready to start grabbing */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_ERROR, /* Something bad happened while processing */ +}; + +enum { + BUFFER_UNUSED, + BUFFER_READY, + BUFFER_BUSY, + BUFFER_DONE, +}; + +/* raw camera data <- sbuf (urb transfer buf) */ +struct stv680_scratch { + unsigned char *data; + volatile int state; + int offset; + int length; +}; + +/* processed data for display ends up here, after bayer */ +struct stv680_frame { + unsigned char *data; /* Frame buffer */ + volatile int grabstate; /* State of grabbing */ + unsigned char *curline; + int curlinepix; + int curpix; +}; + +/* this is almost the video structure uvd_t, with extra parameters for stv */ +struct usb_stv { + struct video_device *vdev; + + struct usb_device *udev; + + unsigned char bulk_in_endpointAddr; /* __u8 the address of the bulk in endpoint */ + char *camera_name; + + unsigned int VideoMode; /* 0x0100 = VGA, 0x0000 = CIF, 0x0300 = QVGA */ + int SupportedModes; + int CIF; + int VGA; + int QVGA; + int cwidth; /* camera width */ + int cheight; /* camera height */ + int maxwidth; /* max video width */ + int maxheight; /* max video height */ + int vwidth; /* current width for video window */ + int vheight; /* current height for video window */ + unsigned long int rawbufsize; + unsigned long int maxframesize; /* rawbufsize * 3 for RGB */ + + int origGain; + int origMode; /* original camera mode */ + + struct mutex lock; /* to lock the structure */ + int user; /* user count for exclusive use */ + int removed; /* device disconnected */ + int streaming; /* Are we streaming video? */ + char *fbuf; /* Videodev buffer area */ + struct urb *urb[STV680_NUMSBUF]; /* # of queued bulk transfers */ + int curframe; /* Current receiving frame */ + struct stv680_frame frame[STV680_NUMFRAMES]; /* # frames supported by v4l part */ + int readcount; + int framecount; + int error; + int dropped; + int scratch_next; + int scratch_use; + int scratch_overflow; + struct stv680_scratch scratch[STV680_NUMSCRATCH]; /* for decoders */ + struct stv680_sbuf sbuf[STV680_NUMSBUF]; + + unsigned int brightness; + unsigned int chgbright; + unsigned int whiteness; + unsigned int colour; + unsigned int contrast; + unsigned int hue; + unsigned int palette; + unsigned int depth; /* rgb24 in bits */ + + wait_queue_head_t wq; /* Processes waiting */ + + int nullpackets; +}; + + +static const unsigned char red[256] = { + 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 25, 30, 35, 38, 42, + 44, 47, 50, 53, 54, 57, 59, 61, 63, 65, 67, 69, + 71, 71, 73, 75, 77, 78, 80, 81, 82, 84, 85, 87, + 88, 89, 90, 91, 93, 94, 95, 97, 98, 98, 99, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 125, 126, 127, 128, 129, 129, 130, 131, 132, 133, 134, + 134, 135, 135, 136, 137, 138, 139, 140, 140, 141, 142, 143, + 143, 143, 144, 145, 146, 147, 147, 148, 149, 150, 150, 151, + 152, 152, 152, 153, 154, 154, 155, 156, 157, 157, 158, 159, + 159, 160, 161, 161, 161, 162, 163, 163, 164, 165, 165, 166, + 167, 167, 168, 168, 169, 170, 170, 170, 171, 171, 172, 173, + 173, 174, 174, 175, 176, 176, 177, 178, 178, 179, 179, 179, + 180, 180, 181, 181, 182, 183, 183, 184, 184, 185, 185, 186, + 187, 187, 188, 188, 188, 188, 189, 190, 190, 191, 191, 192, + 192, 193, 193, 194, 195, 195, 196, 196, 197, 197, 197, 197, + 198, 198, 199, 199, 200, 201, 201, 202, 202, 203, 203, 204, + 204, 205, 205, 206, 206, 206, 206, 207, 207, 208, 208, 209, + 209, 210, 210, 211, 211, 212, 212, 213, 213, 214, 214, 215, + 215, 215, 215, 216, 216, 217, 217, 218, 218, 218, 219, 219, + 220, 220, 221, 221 +}; + +static const unsigned char green[256] = { + 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 28, 34, 39, 43, 47, + 50, 53, 56, 59, 61, 64, 66, 68, 71, 73, 75, 77, + 79, 80, 82, 84, 86, 87, 89, 91, 92, 94, 95, 97, + 98, 100, 101, 102, 104, 105, 106, 108, 109, 110, 111, 113, + 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 144, 145, 146, 147, 148, 149, + 150, 151, 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, + 160, 160, 161, 162, 163, 164, 164, 165, 166, 167, 167, 168, + 169, 170, 170, 171, 172, 172, 173, 174, 175, 175, 176, 177, + 177, 178, 179, 179, 180, 181, 182, 182, 183, 184, 184, 185, + 186, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, + 193, 194, 194, 195, 196, 196, 197, 198, 198, 199, 199, 200, + 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, + 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, + 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, + 221, 221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, + 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, + 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, + 239, 240, 240, 241, 241, 242, 242, 243, 243, 243, 244, 244, + 245, 245, 246, 246 +}; + +static const unsigned char blue[256] = { + 0, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 30, 37, 42, 47, 51, + 55, 58, 61, 64, 67, 70, 72, 74, 78, 80, 82, 84, + 86, 88, 90, 92, 94, 95, 97, 100, 101, 103, 104, 106, + 107, 110, 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, + 125, 126, 127, 128, 129, 132, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 154, 155, 156, 157, 158, 158, 159, 160, 161, 162, 163, + 165, 166, 166, 167, 168, 169, 170, 171, 171, 172, 173, 174, + 176, 176, 177, 178, 179, 180, 180, 181, 182, 183, 183, 184, + 185, 187, 187, 188, 189, 189, 190, 191, 192, 192, 193, 194, + 194, 195, 196, 196, 198, 199, 200, 200, 201, 202, 202, 203, + 204, 204, 205, 205, 206, 207, 207, 209, 210, 210, 211, 212, + 212, 213, 213, 214, 215, 215, 216, 217, 217, 218, 218, 220, + 221, 221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, + 228, 228, 229, 229, 231, 231, 232, 233, 233, 234, 234, 235, + 235, 236, 236, 237, 238, 238, 239, 239, 240, 240, 242, 242, + 243, 243, 244, 244, 245, 246, 246, 247, 247, 248, 248, 249, + 249, 250, 250, 251, 251, 253, 253, 254, 254, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 +}; diff --git a/linux/drivers/media/video/tuner-3036.c b/linux/drivers/media/video/tuner-3036.c new file mode 100644 index 000000000..545f54eaa --- /dev/null +++ b/linux/drivers/media/video/tuner-3036.c @@ -0,0 +1,214 @@ +/* + * 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/slab.h> +#include <linux/init.h> + +#include <linux/i2c.h> +#include "compat.h" +#include <linux/videodev.h> + +#include <media/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[] = { 0x60, 0x61, I2C_CLIENT_END }; +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +/* ---------------------------------------------------------------------- */ + +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, 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); + + 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) +{ + return 0; +} + +static int +tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int *iarg = (int*)arg; + + switch (cmd) + { + case VIDIOCSFREQ: + 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_HW_B_LP) + return i2c_probe(adap, &addr_data, tuner_attach); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver +i2c_driver_tuner = +{ + .driver = { + .name = "sab3036", + }, + .id = I2C_DRIVERID_SAB3036, + .attach_adapter = tuner_probe, + .detach_client = tuner_detach, + .command = tuner_command +}; + +static struct i2c_client client_template = +{ + .driver = &i2c_driver_tuner, + .name = "SAB3036", +}; + +static int __init +tuner3036_init(void) +{ + return i2c_add_driver(&i2c_driver_tuner); +} + +static void __exit +tuner3036_exit(void) +{ + i2c_del_driver(&i2c_driver_tuner); +} + +MODULE_DESCRIPTION("SAB3036 tuner driver"); +MODULE_AUTHOR("Philip Blundell <philb@gnu.org>"); +MODULE_LICENSE("GPL"); + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug,"Enable debugging output"); + +module_init(tuner3036_init); +module_exit(tuner3036_exit); diff --git a/linux/drivers/media/video/videocodec.c b/linux/drivers/media/video/videocodec.c new file mode 100644 index 000000000..8f271de57 --- /dev/null +++ b/linux/drivers/media/video/videocodec.c @@ -0,0 +1,481 @@ +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: videocodec.c,v 1.1.2.8 2003/03/29 07:16:04 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 VIDEOCODEC_VERSION "v0.2" + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> + +// kernel config is here (procfs flag) +#include <linux/config.h> + +#ifdef CONFIG_PROC_FS +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#endif + +#include "videocodec.h" + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +struct attached_list { + struct videocodec *codec; + struct attached_list *next; +}; + +struct codec_list { + const struct videocodec *codec; + int attached; + struct attached_list *list; + struct codec_list *next; +}; + +static struct codec_list *codeclist_top = NULL; + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +struct videocodec * +videocodec_attach (struct videocodec_master *master) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a, *ptr; + struct videocodec *codec; + int res; + + if (!master) { + dprintk(1, KERN_ERR "videocodec_attach: no data\n"); + return NULL; + } + + dprintk(2, + "videocodec_attach: '%s', type: %x, flags %lx, magic %lx\n", + master->name, master->type, master->flags, master->magic); + + if (!h) { + dprintk(1, + KERN_ERR + "videocodec_attach: no device available\n"); + return NULL; + } + + while (h) { + // attach only if the slave has at least the flags + // expected by the master + if ((master->flags & h->codec->flags) == master->flags) { + dprintk(4, "videocodec_attach: try '%s'\n", + h->codec->name); + + if (!try_module_get(h->codec->owner)) + return NULL; + + codec = + kmalloc(sizeof(struct videocodec), GFP_KERNEL); + if (!codec) { + dprintk(1, + KERN_ERR + "videocodec_attach: no mem\n"); + goto out_module_put; + } + memcpy(codec, h->codec, sizeof(struct videocodec)); + + snprintf(codec->name, sizeof(codec->name), + "%s[%d]", codec->name, h->attached); + codec->master_data = master; + res = codec->setup(codec); + if (res == 0) { + dprintk(3, "videocodec_attach '%s'\n", + codec->name); + ptr = kzalloc(sizeof(struct attached_list), GFP_KERNEL); + if (!ptr) { + dprintk(1, + KERN_ERR + "videocodec_attach: no memory\n"); + goto out_kfree; + } + ptr->codec = codec; + + a = h->list; + if (!a) { + h->list = ptr; + dprintk(4, + "videocodec: first element\n"); + } else { + while (a->next) + a = a->next; // find end + a->next = ptr; + dprintk(4, + "videocodec: in after '%s'\n", + h->codec->name); + } + + h->attached += 1; + return codec; + } else { + kfree(codec); + } + } + h = h->next; + } + + dprintk(1, KERN_ERR "videocodec_attach: no codec found!\n"); + return NULL; + + out_module_put: + module_put(h->codec->owner); + out_kfree: + kfree(codec); + return NULL; +} + +int +videocodec_detach (struct videocodec *codec) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a, *prev; + int res; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_detach: no data\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec_detach: '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + dprintk(1, + KERN_ERR "videocodec_detach: no device left...\n"); + return -ENXIO; + } + + while (h) { + a = h->list; + prev = NULL; + while (a) { + if (codec == a->codec) { + res = a->codec->unset(a->codec); + if (res >= 0) { + dprintk(3, + "videocodec_detach: '%s'\n", + a->codec->name); + a->codec->master_data = NULL; + } else { + dprintk(1, + KERN_ERR + "videocodec_detach: '%s'\n", + a->codec->name); + a->codec->master_data = NULL; + } + if (prev == NULL) { + h->list = a->next; + dprintk(4, + "videocodec: delete first\n"); + } else { + prev->next = a->next; + dprintk(4, + "videocodec: delete middle\n"); + } + module_put(a->codec->owner); + kfree(a->codec); + kfree(a); + h->attached -= 1; + return 0; + } + prev = a; + a = a->next; + } + h = h->next; + } + + dprintk(1, KERN_ERR "videocodec_detach: given codec not found!\n"); + return -EINVAL; +} + +int +videocodec_register (const struct videocodec *codec) +{ + struct codec_list *ptr, *h = codeclist_top; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_register: no data!\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec: register '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + ptr = kzalloc(sizeof(struct codec_list), GFP_KERNEL); + if (!ptr) { + dprintk(1, KERN_ERR "videocodec_register: no memory\n"); + return -ENOMEM; + } + ptr->codec = codec; + + if (!h) { + codeclist_top = ptr; + dprintk(4, "videocodec: hooked in as first element\n"); + } else { + while (h->next) + h = h->next; // find the end + h->next = ptr; + dprintk(4, "videocodec: hooked in after '%s'\n", + h->codec->name); + } + + return 0; +} + +int +videocodec_unregister (const struct videocodec *codec) +{ + struct codec_list *prev = NULL, *h = codeclist_top; + + if (!codec) { + dprintk(1, KERN_ERR "videocodec_unregister: no data!\n"); + return -EINVAL; + } + + dprintk(2, + "videocodec: unregister '%s', type: %x, flags %lx, magic %lx\n", + codec->name, codec->type, codec->flags, codec->magic); + + if (!h) { + dprintk(1, + KERN_ERR + "videocodec_unregister: no device left...\n"); + return -ENXIO; + } + + while (h) { + if (codec == h->codec) { + if (h->attached) { + dprintk(1, + KERN_ERR + "videocodec: '%s' is used\n", + h->codec->name); + return -EBUSY; + } + dprintk(3, "videocodec: unregister '%s' is ok.\n", + h->codec->name); + if (prev == NULL) { + codeclist_top = h->next; + dprintk(4, + "videocodec: delete first element\n"); + } else { + prev->next = h->next; + dprintk(4, + "videocodec: delete middle element\n"); + } + kfree(h); + return 0; + } + prev = h; + h = h->next; + } + + dprintk(1, + KERN_ERR + "videocodec_unregister: given codec not found!\n"); + return -EINVAL; +} + +#ifdef CONFIG_PROC_FS +/* ============ */ +/* procfs stuff */ +/* ============ */ + +static char *videocodec_buf = NULL; +static int videocodec_bufsize = 0; + +static int +videocodec_build_table (void) +{ + struct codec_list *h = codeclist_top; + struct attached_list *a; + int i = 0, size; + + // sum up amount of slaves plus their attached masters + while (h) { + i += h->attached + 1; + h = h->next; + } +#define LINESIZE 100 + size = LINESIZE * (i + 1); + + dprintk(3, "videocodec_build table: %d entries, %d bytes\n", i, + size); + + kfree(videocodec_buf); + videocodec_buf = (char *) kmalloc(size, GFP_KERNEL); + + i = 0; + i += scnprintf(videocodec_buf + i, size - 1, + "<S>lave or attached <M>aster name type flags magic "); + i += scnprintf(videocodec_buf + i, size -i - 1, "(connected as)\n"); + + h = codeclist_top; + while (h) { + if (i > (size - LINESIZE)) + break; // security check + i += scnprintf(videocodec_buf + i, size -i -1, + "S %32s %04x %08lx %08lx (TEMPLATE)\n", + h->codec->name, h->codec->type, + h->codec->flags, h->codec->magic); + a = h->list; + while (a) { + if (i > (size - LINESIZE)) + break; // security check + i += scnprintf(videocodec_buf + i, size -i -1, + "M %32s %04x %08lx %08lx (%s)\n", + a->codec->master_data->name, + a->codec->master_data->type, + a->codec->master_data->flags, + a->codec->master_data->magic, + a->codec->name); + a = a->next; + } + h = h->next; + } + + return i; +} + +//The definition: +//typedef int (read_proc_t)(char *page, char **start, off_t off, +// int count, int *eof, void *data); + +static int +videocodec_info (char *buffer, + char **buffer_location, + off_t offset, + int buffer_length, + int *eof, + void *data) +{ + int size; + + dprintk(3, "videocodec_info: offset: %ld, len %d / size %d\n", + offset, buffer_length, videocodec_bufsize); + + if (offset == 0) { + videocodec_bufsize = videocodec_build_table(); + } + if ((offset < 0) || (offset >= videocodec_bufsize)) { + dprintk(4, + "videocodec_info: call delivers no result, return 0\n"); + *eof = 1; + return 0; + } + + if (buffer_length < (videocodec_bufsize - offset)) { + dprintk(4, "videocodec_info: %ld needed, %d got\n", + videocodec_bufsize - offset, buffer_length); + size = buffer_length; + } else { + dprintk(4, "videocodec_info: last reading of %ld bytes\n", + videocodec_bufsize - offset); + size = videocodec_bufsize - offset; + *eof = 1; + } + + memcpy(buffer, videocodec_buf + offset, size); + /* doesn't work... */ + /* copy_to_user(buffer, videocodec_buf+offset, size); */ + /* *buffer_location = videocodec_buf+offset; */ + + return size; +} +#endif + +/* ===================== */ +/* hook in driver module */ +/* ===================== */ +static int __init +videocodec_init (void) +{ +#ifdef CONFIG_PROC_FS + static struct proc_dir_entry *videocodec_proc_entry; +#endif + + printk(KERN_INFO "Linux video codec intermediate layer: %s\n", + VIDEOCODEC_VERSION); + +#ifdef CONFIG_PROC_FS + videocodec_buf = NULL; + videocodec_bufsize = 0; + + videocodec_proc_entry = create_proc_entry("videocodecs", 0, NULL); + if (videocodec_proc_entry) { + videocodec_proc_entry->read_proc = videocodec_info; + videocodec_proc_entry->write_proc = NULL; + videocodec_proc_entry->data = NULL; + videocodec_proc_entry->owner = THIS_MODULE; + } else { + dprintk(1, KERN_ERR "videocodec: can't init procfs.\n"); + } +#endif + return 0; +} + +static void __exit +videocodec_exit (void) +{ +#ifdef CONFIG_PROC_FS + remove_proc_entry("videocodecs", NULL); + kfree(videocodec_buf); +#endif +} + +EXPORT_SYMBOL(videocodec_attach); +EXPORT_SYMBOL(videocodec_detach); +EXPORT_SYMBOL(videocodec_register); +EXPORT_SYMBOL(videocodec_unregister); + +module_init(videocodec_init); +module_exit(videocodec_exit); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Intermediate API module for video codecs " + VIDEOCODEC_VERSION); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/videocodec.h b/linux/drivers/media/video/videocodec.h new file mode 100644 index 000000000..3f968eb7b --- /dev/null +++ b/linux/drivers/media/video/videocodec.h @@ -0,0 +1,359 @@ +/* + * VIDEO MOTION CODECs internal API for video devices + * + * Interface for MJPEG (and maybe later MPEG/WAVELETS) codec's + * bound to a master device. + * + * (c) 2002 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: videocodec.h,v 1.1.2.4 2003/01/14 21:15:03 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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. + * + * ------------------------------------------------------------------------ + */ + +/* =================== */ +/* general description */ +/* =================== */ + +/* Should ease the (re-)usage of drivers supporting cards with (different) + video codecs. The codecs register to this module their functionality, + and the processors (masters) can attach to them if they fit. + + The codecs are typically have a "strong" binding to their master - so I + don't think it makes sense to have a full blown interfacing as with e.g. + i2c. If you have an other opinion, let's discuss & implement it :-))) + + Usage: + + The slave has just to setup the videocodec structure and use two functions: + videocodec_register(codecdata); + videocodec_unregister(codecdata); + The best is just calling them at module (de-)initialisation. + + The master sets up the structure videocodec_master and calls: + codecdata=videocodec_attach(master_codecdata); + videocodec_detach(codecdata); + + The slave is called during attach/detach via functions setup previously + during register. At that time, the master_data pointer is set up + and the slave can access any io registers of the master device (in the case + the slave is bound to it). Otherwise it doesn't need this functions and + therfor they may not be initialized. + + The other fuctions are just for convenience, as they are for sure used by + most/all of the codecs. The last ones may be ommited, too. + + See the structure declaration below for more information and which data has + to be set up for the master and the slave. + + ---------------------------------------------------------------------------- + The master should have "knowledge" of the slave and vice versa. So the data + structures sent to/from slave via set_data/get_data set_image/get_image are + device dependent and vary between MJPEG/MPEG/WAVELET/... devices. (!!!!) + ---------------------------------------------------------------------------- +*/ + + +/* ========================================== */ +/* description of the videocodec_io structure */ +/* ========================================== */ + +/* + ==== master setup ==== + name -> name of the device structure for reference and debugging + master_data -> data ref. for the master (e.g. the zr36055,57,67) + readreg -> ref. to read-fn from register (setup by master, used by slave) + writereg -> ref. to write-fn to register (setup by master, used by slave) + this two functions do the lowlevel I/O job + + ==== slave functionality setup ==== + slave_data -> data ref. for the slave (e.g. the zr36050,60) + check -> fn-ref. checks availability of an device, returns -EIO on failure or + the type on success + this makes espcecially sense if a driver module supports more than + one codec which may be quite similar to access, nevertheless it + is good for a first functionality check + + -- main functions you always need for compression/decompression -- + + set_mode -> this fn-ref. resets the entire codec, and sets up the mode + with the last defined norm/size (or device default if not + available) - it returns 0 if the mode is possible + set_size -> this fn-ref. sets the norm and image size for + compression/decompression (returns 0 on success) + the norm param is defined in videodev.h (VIDEO_MODE_*) + + additional setup may be available, too - but the codec should work with + some default values even without this + + set_data -> sets device-specific data (tables, quality etc.) + get_data -> query device-specific data (tables, quality etc.) + + if the device delivers interrupts, they may be setup/handled here + setup_interrupt -> codec irq setup (not needed for 36050/60) + handle_interrupt -> codec irq handling (not needed for 36050/60) + + if the device delivers pictures, they may be handled here + put_image -> puts image data to the codec (not needed for 36050/60) + get_image -> gets image data from the codec (not needed for 36050/60) + the calls include frame numbers and flags (even/odd/...) + if needed and a flag which allows blocking until its ready +*/ + +/* ============== */ +/* user interface */ +/* ============== */ + +/* + Currently there is only a information display planned, as the layer + is not visible for the user space at all. + + Information is available via procfs. The current entry is "/proc/videocodecs" + but it makes sense to "hide" it in the /proc/video tree of v4l(2) --TODO--. + +A example for such an output is: + +<S>lave or attached <M>aster name type flags magic (connected as) +S zr36050 0002 0000d001 00000000 (TEMPLATE) +M zr36055[0] 0001 0000c001 00000000 (zr36050[0]) +M zr36055[1] 0001 0000c001 00000000 (zr36050[1]) + +*/ + + +/* =============================================== */ +/* special defines for the videocodec_io structure */ +/* =============================================== */ + +#ifndef __LINUX_VIDEOCODEC_H +#define __LINUX_VIDEOCODEC_H + +#include "compat.h" +#include <linux/videodev.h> + +//should be in videodev.h ??? (VID_DO_....) +#define CODEC_DO_COMPRESSION 0 +#define CODEC_DO_EXPANSION 1 + +/* this are the current codec flags I think they are needed */ +/* -> type value in structure */ +#define CODEC_FLAG_JPEG 0x00000001L // JPEG codec +#define CODEC_FLAG_MPEG 0x00000002L // MPEG1/2/4 codec +#define CODEC_FLAG_DIVX 0x00000004L // DIVX codec +#define CODEC_FLAG_WAVELET 0x00000008L // WAVELET codec + // room for other types + +#define CODEC_FLAG_MAGIC 0x00000800L // magic key must match +#define CODEC_FLAG_HARDWARE 0x00001000L // is a hardware codec +#define CODEC_FLAG_VFE 0x00002000L // has direct video frontend +#define CODEC_FLAG_ENCODER 0x00004000L // compression capability +#define CODEC_FLAG_DECODER 0x00008000L // decompression capability +#define CODEC_FLAG_NEEDIRQ 0x00010000L // needs irq handling +#define CODEC_FLAG_RDWRPIC 0x00020000L // handles picture I/O + +/* a list of modes, some are just examples (is there any HW?) */ +#define CODEC_MODE_BJPG 0x0001 // Baseline JPEG +#define CODEC_MODE_LJPG 0x0002 // Lossless JPEG +#define CODEC_MODE_MPEG1 0x0003 // MPEG 1 +#define CODEC_MODE_MPEG2 0x0004 // MPEG 2 +#define CODEC_MODE_MPEG4 0x0005 // MPEG 4 +#define CODEC_MODE_MSDIVX 0x0006 // MS DivX +#define CODEC_MODE_ODIVX 0x0007 // Open DivX +#define CODEC_MODE_WAVELET 0x0008 // Wavelet + +/* this are the current codec types I want to implement */ +/* -> type value in structure */ +#define CODEC_TYPE_NONE 0 +#define CODEC_TYPE_L64702 1 +#define CODEC_TYPE_ZR36050 2 +#define CODEC_TYPE_ZR36016 3 +#define CODEC_TYPE_ZR36060 4 + +/* the type of data may be enhanced by future implementations (data-fn.'s) */ +/* -> used in command */ +#define CODEC_G_STATUS 0x0000 /* codec status (query only) */ +#define CODEC_S_CODEC_MODE 0x0001 /* codec mode (baseline JPEG, MPEG1,... */ +#define CODEC_G_CODEC_MODE 0x8001 +#define CODEC_S_VFE 0x0002 /* additional video frontend setup */ +#define CODEC_G_VFE 0x8002 +#define CODEC_S_MMAP 0x0003 /* MMAP setup (if available) */ + +#define CODEC_S_JPEG_TDS_BYTE 0x0010 /* target data size in bytes */ +#define CODEC_G_JPEG_TDS_BYTE 0x8010 +#define CODEC_S_JPEG_SCALE 0x0011 /* scaling factor for quant. tables */ +#define CODEC_G_JPEG_SCALE 0x8011 +#define CODEC_S_JPEG_HDT_DATA 0x0018 /* huffman-tables */ +#define CODEC_G_JPEG_HDT_DATA 0x8018 +#define CODEC_S_JPEG_QDT_DATA 0x0019 /* quantizing-tables */ +#define CODEC_G_JPEG_QDT_DATA 0x8019 +#define CODEC_S_JPEG_APP_DATA 0x001A /* APP marker */ +#define CODEC_G_JPEG_APP_DATA 0x801A +#define CODEC_S_JPEG_COM_DATA 0x001B /* COM marker */ +#define CODEC_G_JPEG_COM_DATA 0x801B + +#define CODEC_S_PRIVATE 0x1000 /* "private" commands start here */ +#define CODEC_G_PRIVATE 0x9000 + +#define CODEC_G_FLAG 0x8000 /* this is how 'get' is detected */ + +/* types of transfer, directly user space or a kernel buffer (image-fn.'s) */ +/* -> used in get_image, put_image */ +#define CODEC_TRANSFER_KERNEL 0 /* use "memcopy" */ +#define CODEC_TRANSFER_USER 1 /* use "to/from_user" */ + + +/* ========================= */ +/* the structures itself ... */ +/* ========================= */ + +struct vfe_polarity { + int vsync_pol:1; + int hsync_pol:1; + int field_pol:1; + int blank_pol:1; + int subimg_pol:1; + int poe_pol:1; + int pvalid_pol:1; + int vclk_pol:1; +}; + +struct vfe_settings { + __u32 x, y; /* Offsets into image */ + __u32 width, height; /* Area to capture */ + __u16 decimation; /* Decimation divider */ + __u16 flags; /* Flags for capture */ +/* flags are the same as in struct video_capture - see videodev.h: +#define VIDEO_CAPTURE_ODD 0 +#define VIDEO_CAPTURE_EVEN 1 +*/ + __u16 quality; /* quality of the video */ +}; + +struct tvnorm { + u16 Wt, Wa, HStart, HSyncStart, Ht, Ha, VStart; +}; + +struct jpeg_com_marker { + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct jpeg_app_marker { + int appn; /* number app segment */ + int len; /* number of usable bytes in data */ + char data[60]; +}; + +struct videocodec { + struct module *owner; + /* -- filled in by slave device during register -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* codec type */ + + /* -- these is filled in later during master device attach -- */ + + struct videocodec_master *master_data; + + /* -- these are filled in by the slave device during register -- */ + + void *data; /* private slave data */ + + /* attach/detach client functions (indirect call) */ + int (*setup) (struct videocodec * codec); + int (*unset) (struct videocodec * codec); + + /* main functions, every client needs them for sure! */ + // set compression or decompression (or freeze, stop, standby, etc) + int (*set_mode) (struct videocodec * codec, + int mode); + // setup picture size and norm (for the codec's video frontend) + int (*set_video) (struct videocodec * codec, + struct tvnorm * norm, + struct vfe_settings * cap, + struct vfe_polarity * pol); + // other control commands, also mmap setup etc. + int (*control) (struct videocodec * codec, + int type, + int size, + void *data); + + /* additional setup/query/processing (may be NULL pointer) */ + // interrupt setup / handling (for irq's delivered by master) + int (*setup_interrupt) (struct videocodec * codec, + long mode); + int (*handle_interrupt) (struct videocodec * codec, + int source, + long flag); + // picture interface (if any) + long (*put_image) (struct videocodec * codec, + int tr_type, + int block, + long *fr_num, + long *flag, + long size, + void *buf); + long (*get_image) (struct videocodec * codec, + int tr_type, + int block, + long *fr_num, + long *flag, + long size, + void *buf); +}; + +struct videocodec_master { + /* -- filled in by master device for registration -- */ + char name[32]; + unsigned long magic; /* may be used for client<->master attaching */ + unsigned long flags; /* functionality flags */ + unsigned int type; /* master type */ + + void *data; /* private master data */ + + __u32(*readreg) (struct videocodec * codec, + __u16 reg); + void (*writereg) (struct videocodec * codec, + __u16 reg, + __u32 value); +}; + + +/* ================================================= */ +/* function prototypes of the master/slave interface */ +/* ================================================= */ + +/* attach and detach commands for the master */ +// * master structure needs to be kmalloc'ed before calling attach +// and free'd after calling detach +// * returns pointer on success, NULL on failure +extern struct videocodec *videocodec_attach(struct videocodec_master *); +// * 0 on success, <0 (errno) on failure +extern int videocodec_detach(struct videocodec *); + +/* register and unregister commands for the slaves */ +// * 0 on success, <0 (errno) on failure +extern int videocodec_register(const struct videocodec *); +// * 0 on success, <0 (errno) on failure +extern int videocodec_unregister(const struct videocodec *); + +/* the other calls are directly done via the videocodec structure! */ + +#endif /*ifndef __LINUX_VIDEOCODEC_H */ diff --git a/linux/drivers/media/video/vino.c b/linux/drivers/media/video/vino.c new file mode 100644 index 000000000..a8c101494 --- /dev/null +++ b/linux/drivers/media/video/vino.c @@ -0,0 +1,4680 @@ +/* + * Driver for the VINO (Video In No Out) system found in SGI Indys. + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2 as published by the Free Software Foundation. + * + * Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi> + * + * Based on the previous version of the driver for 2.4 kernels by: + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + */ + +/* + * TODO: + * - remove "mark pages reserved-hacks" from memory allocation code + * and implement nopage() + * - check decimation, calculating and reporting image size when + * using decimation + * - implement read(), user mode buffers and overlay (?) + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/version.h> + +#ifdef CONFIG_KMOD +#include <linux/kmod.h> +#endif + +#include <linux/i2c.h> +#include <linux/i2c-algo-sgi.h> + +#include <linux/videodev.h> +#include <linux/videodev2.h> +#include <linux/video_decoder.h> +#include <linux/mutex.h> + +#include <asm/paccess.h> +#include <asm/io.h> +#include <asm/sgi/ip22.h> +#include <asm/sgi/mc.h> + +#include "vino.h" +#include "saa7191.h" +#include "indycam.h" + +/* Uncomment the following line to get lots and lots of (mostly useless) + * debug info. + * Note that the debug output also slows down the driver significantly */ +// #define VINO_DEBUG +// #define VINO_DEBUG_INT + +#define VINO_MODULE_VERSION "0.0.5" +#define VINO_VERSION_CODE KERNEL_VERSION(0, 0, 5) + +MODULE_DESCRIPTION("SGI VINO Video4Linux2 driver"); +MODULE_VERSION(VINO_MODULE_VERSION); +MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>"); +MODULE_LICENSE("GPL"); + +#ifdef VINO_DEBUG +#define dprintk(x...) printk("VINO: " x); +#else +#define dprintk(x...) +#endif + +#define VINO_NO_CHANNEL 0 +#define VINO_CHANNEL_A 1 +#define VINO_CHANNEL_B 2 + +#define VINO_PAL_WIDTH 768 +#define VINO_PAL_HEIGHT 576 +#define VINO_NTSC_WIDTH 640 +#define VINO_NTSC_HEIGHT 480 + +#define VINO_MIN_WIDTH 32 +#define VINO_MIN_HEIGHT 32 + +#define VINO_CLIPPING_START_ODD_D1 1 +#define VINO_CLIPPING_START_ODD_PAL 15 +#define VINO_CLIPPING_START_ODD_NTSC 12 + +#define VINO_CLIPPING_START_EVEN_D1 2 +#define VINO_CLIPPING_START_EVEN_PAL 15 +#define VINO_CLIPPING_START_EVEN_NTSC 12 + +#define VINO_INPUT_CHANNEL_COUNT 3 + +/* the number is the index for vino_inputs */ +#define VINO_INPUT_NONE -1 +#define VINO_INPUT_COMPOSITE 0 +#define VINO_INPUT_SVIDEO 1 +#define VINO_INPUT_D1 2 + +#define VINO_PAGE_RATIO (PAGE_SIZE / VINO_PAGE_SIZE) + +#define VINO_FIFO_THRESHOLD_DEFAULT 16 + +#define VINO_FRAMEBUFFER_SIZE ((VINO_PAL_WIDTH \ + * VINO_PAL_HEIGHT * 4 \ + + 3 * PAGE_SIZE) & ~(PAGE_SIZE - 1)) + +#define VINO_FRAMEBUFFER_COUNT_MAX 8 + +#define VINO_FRAMEBUFFER_UNUSED 0 +#define VINO_FRAMEBUFFER_IN_USE 1 +#define VINO_FRAMEBUFFER_READY 2 + +#define VINO_QUEUE_ERROR -1 +#define VINO_QUEUE_MAGIC 0x20050125 + +#define VINO_MEMORY_NONE 0 +#define VINO_MEMORY_MMAP 1 +#define VINO_MEMORY_USERPTR 2 + +#define VINO_DUMMY_DESC_COUNT 4 +#define VINO_DESC_FETCH_DELAY 5 /* microseconds */ + +#define VINO_MAX_FRAME_SKIP_COUNT 128 + +/* the number is the index for vino_data_formats */ +#define VINO_DATA_FMT_NONE -1 +#define VINO_DATA_FMT_GREY 0 +#define VINO_DATA_FMT_RGB332 1 +#define VINO_DATA_FMT_RGB32 2 +#define VINO_DATA_FMT_YUV 3 + +#define VINO_DATA_FMT_COUNT 4 + +/* the number is the index for vino_data_norms */ +#define VINO_DATA_NORM_NONE -1 +#define VINO_DATA_NORM_NTSC 0 +#define VINO_DATA_NORM_PAL 1 +#define VINO_DATA_NORM_SECAM 2 +#define VINO_DATA_NORM_D1 3 +/* The following are special entries that can be used to + * autodetect the norm. */ +#define VINO_DATA_NORM_AUTO 0xfe +#define VINO_DATA_NORM_AUTO_EXT 0xff + +#define VINO_DATA_NORM_COUNT 4 + +/* Internal data structure definitions */ + +struct vino_input { + char *name; + v4l2_std_id std; +}; + +struct vino_clipping { + unsigned int left, right, top, bottom; +}; + +struct vino_data_format { + /* the description */ + char *description; + /* bytes per pixel */ + unsigned int bpp; + /* V4L2 fourcc code */ + __u32 pixelformat; + /* V4L2 colorspace (duh!) */ + enum v4l2_colorspace colorspace; +}; + +struct vino_data_norm { + char *description; + unsigned int width, height; + struct vino_clipping odd; + struct vino_clipping even; + + v4l2_std_id std; + unsigned int fps_min, fps_max; + __u32 framelines; +}; + +struct vino_descriptor_table { + /* the number of PAGE_SIZE sized pages in the buffer */ + unsigned int page_count; + /* virtual (kmalloc'd) pointers to the actual data + * (in PAGE_SIZE chunks, used with mmap streaming) */ + unsigned long *virtual; + + /* cpu address for the VINO descriptor table + * (contains DMA addresses, VINO_PAGE_SIZE chunks) */ + unsigned long *dma_cpu; + /* dma address for the VINO descriptor table + * (contains DMA addresses, VINO_PAGE_SIZE chunks) */ + dma_addr_t dma; +}; + +struct vino_framebuffer { + /* identifier nubmer */ + unsigned int id; + /* the length of the whole buffer */ + unsigned int size; + /* the length of actual data in buffer */ + unsigned int data_size; + /* the data format */ + unsigned int data_format; + /* the state of buffer data */ + unsigned int state; + /* is the buffer mapped in user space? */ + unsigned int map_count; + /* memory offset for mmap() */ + unsigned int offset; + /* frame counter */ + unsigned int frame_counter; + /* timestamp (written when image capture finishes) */ + struct timeval timestamp; + + struct vino_descriptor_table desc_table; + + spinlock_t state_lock; +}; + +struct vino_framebuffer_fifo { + unsigned int length; + + unsigned int used; + unsigned int head; + unsigned int tail; + + unsigned int data[VINO_FRAMEBUFFER_COUNT_MAX]; +}; + +struct vino_framebuffer_queue { + unsigned int magic; + + /* VINO_MEMORY_NONE, VINO_MEMORY_MMAP or VINO_MEMORY_USERPTR */ + unsigned int type; + unsigned int length; + + /* data field of in and out contain index numbers for buffer */ + struct vino_framebuffer_fifo in; + struct vino_framebuffer_fifo out; + + struct vino_framebuffer *buffer[VINO_FRAMEBUFFER_COUNT_MAX]; + + spinlock_t queue_lock; + struct mutex queue_mutex; + wait_queue_head_t frame_wait_queue; +}; + +struct vino_interrupt_data { + struct timeval timestamp; + unsigned int frame_counter; + unsigned int skip_count; + unsigned int skip; +}; + +struct vino_channel_settings { + unsigned int channel; + + int input; + unsigned int data_format; + unsigned int data_norm; + struct vino_clipping clipping; + unsigned int decimation; + unsigned int line_size; + unsigned int alpha; + unsigned int fps; + unsigned int framert_reg; + + unsigned int fifo_threshold; + + struct vino_framebuffer_queue fb_queue; + + /* number of the current field */ + unsigned int field; + + /* read in progress */ + int reading; + /* streaming is active */ + int streaming; + /* the driver is currently processing the queue */ + int capturing; + + struct mutex mutex; + spinlock_t capture_lock; + + unsigned int users; + + struct vino_interrupt_data int_data; + + /* V4L support */ + struct video_device *v4l_device; +}; + +struct vino_client { + /* the channel which owns this client: + * VINO_NO_CHANNEL, VINO_CHANNEL_A or VINO_CHANNEL_B */ + unsigned int owner; + struct i2c_client *driver; +}; + +struct vino_settings { + struct vino_channel_settings a; + struct vino_channel_settings b; + + struct vino_client decoder; + struct vino_client camera; + + /* a lock for vino register access */ + spinlock_t vino_lock; + /* a lock for channel input changes */ + spinlock_t input_lock; + + unsigned long dummy_page; + struct vino_descriptor_table dummy_desc_table; +}; + +/* Module parameters */ + +/* + * Using vino_pixel_conversion the ABGR32-format pixels supplied + * by the VINO chip can be converted to more common formats + * like RGBA32 (or probably RGB24 in the future). This way we + * can give out data that can be specified correctly with + * the V4L2-definitions. + * + * The pixel format is specified as RGBA32 when no conversion + * is used. + * + * Note that this only affects the 32-bit bit depth. + * + * Use non-zero value to enable conversion. + */ +static int vino_pixel_conversion = 0; + +module_param_named(pixelconv, vino_pixel_conversion, int, 0); + +MODULE_PARM_DESC(pixelconv, + "enable pixel conversion (non-zero value enables)"); + +/* Internal data structures */ + +static struct sgi_vino *vino; + +static struct vino_settings *vino_drvdata; + +static const char *vino_driver_name = "vino"; +static const char *vino_driver_description = "SGI VINO"; +static const char *vino_bus_name = "GIO64 bus"; +static const char *vino_v4l_device_name_a = "SGI VINO Channel A"; +static const char *vino_v4l_device_name_b = "SGI VINO Channel B"; + +static void vino_capture_tasklet(unsigned long channel); + +DECLARE_TASKLET(vino_tasklet_a, vino_capture_tasklet, VINO_CHANNEL_A); +DECLARE_TASKLET(vino_tasklet_b, vino_capture_tasklet, VINO_CHANNEL_B); + +static const struct vino_input vino_inputs[] = { + { + .name = "Composite", + .std = V4L2_STD_NTSC | V4L2_STD_PAL + | V4L2_STD_SECAM, + },{ + .name = "S-Video", + .std = V4L2_STD_NTSC | V4L2_STD_PAL + | V4L2_STD_SECAM, + },{ + .name = "D1/IndyCam", + .std = V4L2_STD_NTSC, + } +}; + +static const struct vino_data_format vino_data_formats[] = { + { + .description = "8-bit greyscale", + .bpp = 1, + .pixelformat = V4L2_PIX_FMT_GREY, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + },{ + .description = "8-bit dithered RGB 3-3-2", + .bpp = 1, + .pixelformat = V4L2_PIX_FMT_RGB332, + .colorspace = V4L2_COLORSPACE_SRGB, + },{ + .description = "32-bit RGB", + .bpp = 4, + .pixelformat = V4L2_PIX_FMT_RGB32, + .colorspace = V4L2_COLORSPACE_SRGB, + },{ + .description = "YUV 4:2:2", + .bpp = 2, + .pixelformat = V4L2_PIX_FMT_YUYV, // XXX: swapped? + .colorspace = V4L2_COLORSPACE_SMPTE170M, + } +}; + +static const struct vino_data_norm vino_data_norms[] = { + { + .description = "NTSC", + .std = V4L2_STD_NTSC, + .fps_min = 6, + .fps_max = 30, + .framelines = 525, + .width = VINO_NTSC_WIDTH, + .height = VINO_NTSC_HEIGHT, + .odd = { + .top = VINO_CLIPPING_START_ODD_NTSC, + .left = 0, + .bottom = VINO_CLIPPING_START_ODD_NTSC + + VINO_NTSC_HEIGHT / 2 - 1, + .right = VINO_NTSC_WIDTH, + }, + .even = { + .top = VINO_CLIPPING_START_EVEN_NTSC, + .left = 0, + .bottom = VINO_CLIPPING_START_EVEN_NTSC + + VINO_NTSC_HEIGHT / 2 - 1, + .right = VINO_NTSC_WIDTH, + }, + },{ + .description = "PAL", + .std = V4L2_STD_PAL, + .fps_min = 5, + .fps_max = 25, + .framelines = 625, + .width = VINO_PAL_WIDTH, + .height = VINO_PAL_HEIGHT, + .odd = { + .top = VINO_CLIPPING_START_ODD_PAL, + .left = 0, + .bottom = VINO_CLIPPING_START_ODD_PAL + + VINO_PAL_HEIGHT / 2 - 1, + .right = VINO_PAL_WIDTH, + }, + .even = { + .top = VINO_CLIPPING_START_EVEN_PAL, + .left = 0, + .bottom = VINO_CLIPPING_START_EVEN_PAL + + VINO_PAL_HEIGHT / 2 - 1, + .right = VINO_PAL_WIDTH, + }, + },{ + .description = "SECAM", + .std = V4L2_STD_SECAM, + .fps_min = 5, + .fps_max = 25, + .framelines = 625, + .width = VINO_PAL_WIDTH, + .height = VINO_PAL_HEIGHT, + .odd = { + .top = VINO_CLIPPING_START_ODD_PAL, + .left = 0, + .bottom = VINO_CLIPPING_START_ODD_PAL + + VINO_PAL_HEIGHT / 2 - 1, + .right = VINO_PAL_WIDTH, + }, + .even = { + .top = VINO_CLIPPING_START_EVEN_PAL, + .left = 0, + .bottom = VINO_CLIPPING_START_EVEN_PAL + + VINO_PAL_HEIGHT / 2 - 1, + .right = VINO_PAL_WIDTH, + }, + },{ + .description = "NTSC/D1", + .std = V4L2_STD_NTSC, + .fps_min = 6, + .fps_max = 30, + .framelines = 525, + .width = VINO_NTSC_WIDTH, + .height = VINO_NTSC_HEIGHT, + .odd = { + .top = VINO_CLIPPING_START_ODD_D1, + .left = 0, + .bottom = VINO_CLIPPING_START_ODD_D1 + + VINO_NTSC_HEIGHT / 2 - 1, + .right = VINO_NTSC_WIDTH, + }, + .even = { + .top = VINO_CLIPPING_START_EVEN_D1, + .left = 0, + .bottom = VINO_CLIPPING_START_EVEN_D1 + + VINO_NTSC_HEIGHT / 2 - 1, + .right = VINO_NTSC_WIDTH, + }, + } +}; + +#define VINO_INDYCAM_V4L2_CONTROL_COUNT 9 + +struct v4l2_queryctrl vino_indycam_v4l2_controls[] = { + { + .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Automatic Gain Control", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = INDYCAM_AGC_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_AGC, 0 }, + },{ + .id = V4L2_CID_AUTO_WHITE_BALANCE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Automatic White Balance", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = INDYCAM_AWB_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_AWB, 0 }, + },{ + .id = V4L2_CID_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = INDYCAM_GAIN_MIN, + .maximum = INDYCAM_GAIN_MAX, + .step = 1, + .default_value = INDYCAM_GAIN_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_GAIN, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red Saturation", + .minimum = INDYCAM_RED_SATURATION_MIN, + .maximum = INDYCAM_RED_SATURATION_MAX, + .step = 1, + .default_value = INDYCAM_RED_SATURATION_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_RED_SATURATION, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 1, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue Saturation", + .minimum = INDYCAM_BLUE_SATURATION_MIN, + .maximum = INDYCAM_BLUE_SATURATION_MAX, + .step = 1, + .default_value = INDYCAM_BLUE_SATURATION_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_BLUE_SATURATION, 0 }, + },{ + .id = V4L2_CID_RED_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Red Balance", + .minimum = INDYCAM_RED_BALANCE_MIN, + .maximum = INDYCAM_RED_BALANCE_MAX, + .step = 1, + .default_value = INDYCAM_RED_BALANCE_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_RED_BALANCE, 0 }, + },{ + .id = V4L2_CID_BLUE_BALANCE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Blue Balance", + .minimum = INDYCAM_BLUE_BALANCE_MIN, + .maximum = INDYCAM_BLUE_BALANCE_MAX, + .step = 1, + .default_value = INDYCAM_BLUE_BALANCE_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_BLUE_BALANCE, 0 }, + },{ + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Shutter Control", + .minimum = INDYCAM_SHUTTER_MIN, + .maximum = INDYCAM_SHUTTER_MAX, + .step = 1, + .default_value = INDYCAM_SHUTTER_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_SHUTTER, 0 }, + },{ + .id = V4L2_CID_GAMMA, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gamma", + .minimum = INDYCAM_GAMMA_MIN, + .maximum = INDYCAM_GAMMA_MAX, + .step = 1, + .default_value = INDYCAM_GAMMA_DEFAULT, + .flags = 0, + .reserved = { INDYCAM_CONTROL_GAMMA, 0 }, + } +}; + +#define VINO_SAA7191_V4L2_CONTROL_COUNT 9 + +struct v4l2_queryctrl vino_saa7191_v4l2_controls[] = { + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = SAA7191_HUE_MIN, + .maximum = SAA7191_HUE_MAX, + .step = 1, + .default_value = SAA7191_HUE_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_HUE, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Luminance Bandpass", + .minimum = SAA7191_BANDPASS_MIN, + .maximum = SAA7191_BANDPASS_MAX, + .step = 1, + .default_value = SAA7191_BANDPASS_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_BANDPASS, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 1, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Luminance Bandpass Weight", + .minimum = SAA7191_BANDPASS_WEIGHT_MIN, + .maximum = SAA7191_BANDPASS_WEIGHT_MAX, + .step = 1, + .default_value = SAA7191_BANDPASS_WEIGHT_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_BANDPASS_WEIGHT, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 2, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "HF Luminance Coring", + .minimum = SAA7191_CORING_MIN, + .maximum = SAA7191_CORING_MAX, + .step = 1, + .default_value = SAA7191_CORING_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_CORING, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 3, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Force Colour", + .minimum = SAA7191_FORCE_COLOUR_MIN, + .maximum = SAA7191_FORCE_COLOUR_MAX, + .step = 1, + .default_value = SAA7191_FORCE_COLOUR_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_FORCE_COLOUR, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 4, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Chrominance Gain Control", + .minimum = SAA7191_CHROMA_GAIN_MIN, + .maximum = SAA7191_CHROMA_GAIN_MAX, + .step = 1, + .default_value = SAA7191_CHROMA_GAIN_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_CHROMA_GAIN, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 5, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "VTR Time Constant", + .minimum = SAA7191_VTRC_MIN, + .maximum = SAA7191_VTRC_MAX, + .step = 1, + .default_value = SAA7191_VTRC_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_VTRC, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 6, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Luminance Delay Compensation", + .minimum = SAA7191_LUMA_DELAY_MIN, + .maximum = SAA7191_LUMA_DELAY_MAX, + .step = 1, + .default_value = SAA7191_LUMA_DELAY_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_LUMA_DELAY, 0 }, + },{ + .id = V4L2_CID_PRIVATE_BASE + 7, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Vertical Noise Reduction", + .minimum = SAA7191_VNR_MIN, + .maximum = SAA7191_VNR_MAX, + .step = 1, + .default_value = SAA7191_VNR_DEFAULT, + .flags = 0, + .reserved = { SAA7191_CONTROL_VNR, 0 }, + } +}; + +/* VINO I2C bus functions */ + +unsigned i2c_vino_getctrl(void *data) +{ + return vino->i2c_control; +} + +void i2c_vino_setctrl(void *data, unsigned val) +{ + vino->i2c_control = val; +} + +unsigned i2c_vino_rdata(void *data) +{ + return vino->i2c_data; +} + +void i2c_vino_wdata(void *data, unsigned val) +{ + vino->i2c_data = val; +} + +static struct i2c_algo_sgi_data i2c_sgi_vino_data = +{ + .getctrl = &i2c_vino_getctrl, + .setctrl = &i2c_vino_setctrl, + .rdata = &i2c_vino_rdata, + .wdata = &i2c_vino_wdata, + .xfer_timeout = 200, + .ack_timeout = 1000, +}; + +/* + * There are two possible clients on VINO I2C bus, so we limit usage only + * to them. + */ +static int i2c_vino_client_reg(struct i2c_client *client) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + switch (client->driver->id) { + case I2C_DRIVERID_SAA7191: + if (vino_drvdata->decoder.driver) + ret = -EBUSY; + else + vino_drvdata->decoder.driver = client; + break; + case I2C_DRIVERID_INDYCAM: + if (vino_drvdata->camera.driver) + ret = -EBUSY; + else + vino_drvdata->camera.driver = client; + break; + default: + ret = -ENODEV; + } + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return ret; +} + +static int i2c_vino_client_unreg(struct i2c_client *client) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + if (client == vino_drvdata->decoder.driver) { + if (vino_drvdata->decoder.owner != VINO_NO_CHANNEL) + ret = -EBUSY; + else + vino_drvdata->decoder.driver = NULL; + } else if (client == vino_drvdata->camera.driver) { + if (vino_drvdata->camera.owner != VINO_NO_CHANNEL) + ret = -EBUSY; + else + vino_drvdata->camera.driver = NULL; + } + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return ret; +} + +static struct i2c_adapter vino_i2c_adapter = +{ + .name = "VINO I2C bus", + .id = I2C_HW_SGI_VINO, + .algo_data = &i2c_sgi_vino_data, + .client_register = &i2c_vino_client_reg, + .client_unregister = &i2c_vino_client_unreg, +}; + +static int vino_i2c_add_bus(void) +{ + return i2c_sgi_add_bus(&vino_i2c_adapter); +} + +static int vino_i2c_del_bus(void) +{ + return i2c_sgi_del_bus(&vino_i2c_adapter); +} + +static int i2c_camera_command(unsigned int cmd, void *arg) +{ + return vino_drvdata->camera.driver-> + driver->command(vino_drvdata->camera.driver, + cmd, arg); +} + +static int i2c_decoder_command(unsigned int cmd, void *arg) +{ + return vino_drvdata->decoder.driver-> + driver->command(vino_drvdata->decoder.driver, + cmd, arg); +} + +/* VINO framebuffer/DMA descriptor management */ + +static void vino_free_buffer_with_count(struct vino_framebuffer *fb, + unsigned int count) +{ + unsigned int i; + + dprintk("vino_free_buffer_with_count(): count = %d\n", count); + + for (i = 0; i < count; i++) { + ClearPageReserved(virt_to_page(fb->desc_table.virtual[i])); + dma_unmap_single(NULL, + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * i], + PAGE_SIZE, DMA_FROM_DEVICE); + free_page(fb->desc_table.virtual[i]); + } + + dma_free_coherent(NULL, + VINO_PAGE_RATIO * (fb->desc_table.page_count + 4) * + sizeof(dma_addr_t), (void *)fb->desc_table.dma_cpu, + fb->desc_table.dma); + kfree(fb->desc_table.virtual); + + memset(fb, 0, sizeof(struct vino_framebuffer)); +} + +static void vino_free_buffer(struct vino_framebuffer *fb) +{ + vino_free_buffer_with_count(fb, fb->desc_table.page_count); +} + +static int vino_allocate_buffer(struct vino_framebuffer *fb, + unsigned int size) +{ + unsigned int count, i, j; + int ret = 0; + + dprintk("vino_allocate_buffer():\n"); + + if (size < 1) + return -EINVAL; + + memset(fb, 0, sizeof(struct vino_framebuffer)); + + count = ((size / PAGE_SIZE) + 4) & ~3; + + dprintk("vino_allocate_buffer(): size = %d, count = %d\n", + size, count); + + /* allocate memory for table with virtual (page) addresses */ + fb->desc_table.virtual = (unsigned long *) + kmalloc(count * sizeof(unsigned long), GFP_KERNEL); + if (!fb->desc_table.virtual) + return -ENOMEM; + + /* allocate memory for table with dma addresses + * (has space for four extra descriptors) */ + fb->desc_table.dma_cpu = + dma_alloc_coherent(NULL, VINO_PAGE_RATIO * (count + 4) * + sizeof(dma_addr_t), &fb->desc_table.dma, + GFP_KERNEL | GFP_DMA); + if (!fb->desc_table.dma_cpu) { + ret = -ENOMEM; + goto out_free_virtual; + } + + /* allocate pages for the buffer and acquire the according + * dma addresses */ + for (i = 0; i < count; i++) { + dma_addr_t dma_data_addr; + + fb->desc_table.virtual[i] = + get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!fb->desc_table.virtual[i]) { + ret = -ENOBUFS; + break; + } + + dma_data_addr = + dma_map_single(NULL, + (void *)fb->desc_table.virtual[i], + PAGE_SIZE, DMA_FROM_DEVICE); + + for (j = 0; j < VINO_PAGE_RATIO; j++) { + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * i + j] = + dma_data_addr + VINO_PAGE_SIZE * j; + } + + SetPageReserved(virt_to_page(fb->desc_table.virtual[i])); + } + + /* page_count needs to be set anyway, because the descriptor table has + * been allocated according to this number */ + fb->desc_table.page_count = count; + + if (ret) { + /* the descriptor with index i doesn't contain + * a valid address yet */ + vino_free_buffer_with_count(fb, i); + return ret; + } + + //fb->size = size; + fb->size = count * PAGE_SIZE; + fb->data_format = VINO_DATA_FMT_NONE; + + /* set the dma stop-bit for the last (count+1)th descriptor */ + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * count] = VINO_DESC_STOP; + return 0; + + out_free_virtual: + kfree(fb->desc_table.virtual); + return ret; +} + +#if 0 +/* user buffers not fully implemented yet */ +static int vino_prepare_user_buffer(struct vino_framebuffer *fb, + void *user, + unsigned int size) +{ + unsigned int count, i, j; + int ret = 0; + + dprintk("vino_prepare_user_buffer():\n"); + + if (size < 1) + return -EINVAL; + + memset(fb, 0, sizeof(struct vino_framebuffer)); + + count = ((size / PAGE_SIZE)) & ~3; + + dprintk("vino_prepare_user_buffer(): size = %d, count = %d\n", + size, count); + + /* allocate memory for table with virtual (page) addresses */ + fb->desc_table.virtual = (unsigned long *) + kmalloc(count * sizeof(unsigned long), GFP_KERNEL); + if (!fb->desc_table.virtual) + return -ENOMEM; + + /* allocate memory for table with dma addresses + * (has space for four extra descriptors) */ + fb->desc_table.dma_cpu = + dma_alloc_coherent(NULL, VINO_PAGE_RATIO * (count + 4) * + sizeof(dma_addr_t), &fb->desc_table.dma, + GFP_KERNEL | GFP_DMA); + if (!fb->desc_table.dma_cpu) { + ret = -ENOMEM; + goto out_free_virtual; + } + + /* allocate pages for the buffer and acquire the according + * dma addresses */ + for (i = 0; i < count; i++) { + dma_addr_t dma_data_addr; + + fb->desc_table.virtual[i] = + get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!fb->desc_table.virtual[i]) { + ret = -ENOBUFS; + break; + } + + dma_data_addr = + dma_map_single(NULL, + (void *)fb->desc_table.virtual[i], + PAGE_SIZE, DMA_FROM_DEVICE); + + for (j = 0; j < VINO_PAGE_RATIO; j++) { + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * i + j] = + dma_data_addr + VINO_PAGE_SIZE * j; + } + + SetPageReserved(virt_to_page(fb->desc_table.virtual[i])); + } + + /* page_count needs to be set anyway, because the descriptor table has + * been allocated according to this number */ + fb->desc_table.page_count = count; + + if (ret) { + /* the descriptor with index i doesn't contain + * a valid address yet */ + vino_free_buffer_with_count(fb, i); + return ret; + } + + //fb->size = size; + fb->size = count * PAGE_SIZE; + + /* set the dma stop-bit for the last (count+1)th descriptor */ + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * count] = VINO_DESC_STOP; + return 0; + + out_free_virtual: + kfree(fb->desc_table.virtual); + return ret; +} +#endif + +static void vino_sync_buffer(struct vino_framebuffer *fb) +{ + int i; + + dprintk("vino_sync_buffer():\n"); + + for (i = 0; i < fb->desc_table.page_count; i++) + dma_sync_single(NULL, + fb->desc_table.dma_cpu[VINO_PAGE_RATIO * i], + PAGE_SIZE, DMA_FROM_DEVICE); +} + +/* Framebuffer fifo functions (need to be locked externally) */ + +static inline void vino_fifo_init(struct vino_framebuffer_fifo *f, + unsigned int length) +{ + f->length = 0; + f->used = 0; + f->head = 0; + f->tail = 0; + + if (length > VINO_FRAMEBUFFER_COUNT_MAX) + length = VINO_FRAMEBUFFER_COUNT_MAX; + + f->length = length; +} + +/* returns true/false */ +static inline int vino_fifo_has_id(struct vino_framebuffer_fifo *f, + unsigned int id) +{ + unsigned int i; + + for (i = f->head; i == (f->tail - 1); i = (i + 1) % f->length) { + if (f->data[i] == id) + return 1; + } + + return 0; +} + +#if 0 +/* returns true/false */ +static inline int vino_fifo_full(struct vino_framebuffer_fifo *f) +{ + return (f->used == f->length); +} +#endif + +static inline unsigned int vino_fifo_get_used(struct vino_framebuffer_fifo *f) +{ + return f->used; +} + +static int vino_fifo_enqueue(struct vino_framebuffer_fifo *f, unsigned int id) +{ + if (id >= f->length) { + return VINO_QUEUE_ERROR; + } + + if (vino_fifo_has_id(f, id)) { + return VINO_QUEUE_ERROR; + } + + if (f->used < f->length) { + f->data[f->tail] = id; + f->tail = (f->tail + 1) % f->length; + f->used++; + } else { + return VINO_QUEUE_ERROR; + } + + return 0; +} + +static int vino_fifo_peek(struct vino_framebuffer_fifo *f, unsigned int *id) +{ + if (f->used > 0) { + *id = f->data[f->head]; + } else { + return VINO_QUEUE_ERROR; + } + + return 0; +} + +static int vino_fifo_dequeue(struct vino_framebuffer_fifo *f, unsigned int *id) +{ + if (f->used > 0) { + *id = f->data[f->head]; + f->head = (f->head + 1) % f->length; + f->used--; + } else { + return VINO_QUEUE_ERROR; + } + + return 0; +} + +/* Framebuffer queue functions */ + +/* execute with queue_lock locked */ +static void vino_queue_free_with_count(struct vino_framebuffer_queue *q, + unsigned int length) +{ + unsigned int i; + + q->length = 0; + memset(&q->in, 0, sizeof(struct vino_framebuffer_fifo)); + memset(&q->out, 0, sizeof(struct vino_framebuffer_fifo)); + for (i = 0; i < length; i++) { + dprintk("vino_queue_free_with_count(): freeing buffer %d\n", + i); + vino_free_buffer(q->buffer[i]); + kfree(q->buffer[i]); + } + + q->type = VINO_MEMORY_NONE; + q->magic = 0; +} + +static void vino_queue_free(struct vino_framebuffer_queue *q) +{ + dprintk("vino_queue_free():\n"); + + if (q->magic != VINO_QUEUE_MAGIC) + return; + if (q->type != VINO_MEMORY_MMAP) + return; + + mutex_lock(&q->queue_mutex); + + vino_queue_free_with_count(q, q->length); + + mutex_unlock(&q->queue_mutex); +} + +static int vino_queue_init(struct vino_framebuffer_queue *q, + unsigned int *length) +{ + unsigned int i; + int ret = 0; + + dprintk("vino_queue_init(): length = %d\n", *length); + + if (q->magic == VINO_QUEUE_MAGIC) { + dprintk("vino_queue_init(): queue already initialized!\n"); + return -EINVAL; + } + + if (q->type != VINO_MEMORY_NONE) { + dprintk("vino_queue_init(): queue already initialized!\n"); + return -EINVAL; + } + + if (*length < 1) + return -EINVAL; + + mutex_lock(&q->queue_mutex); + + if (*length > VINO_FRAMEBUFFER_COUNT_MAX) + *length = VINO_FRAMEBUFFER_COUNT_MAX; + + q->length = 0; + + for (i = 0; i < *length; i++) { + dprintk("vino_queue_init(): allocating buffer %d\n", i); + q->buffer[i] = kmalloc(sizeof(struct vino_framebuffer), + GFP_KERNEL); + if (!q->buffer[i]) { + dprintk("vino_queue_init(): kmalloc() failed\n"); + ret = -ENOMEM; + break; + } + + ret = vino_allocate_buffer(q->buffer[i], + VINO_FRAMEBUFFER_SIZE); + if (ret) { + kfree(q->buffer[i]); + dprintk("vino_queue_init(): " + "vino_allocate_buffer() failed\n"); + break; + } + + q->buffer[i]->id = i; + if (i > 0) { + q->buffer[i]->offset = q->buffer[i - 1]->offset + + q->buffer[i - 1]->size; + } else { + q->buffer[i]->offset = 0; + } + + spin_lock_init(&q->buffer[i]->state_lock); + + dprintk("vino_queue_init(): buffer = %d, offset = %d, " + "size = %d\n", i, q->buffer[i]->offset, + q->buffer[i]->size); + } + + if (ret) { + vino_queue_free_with_count(q, i); + *length = 0; + } else { + q->length = *length; + vino_fifo_init(&q->in, q->length); + vino_fifo_init(&q->out, q->length); + q->type = VINO_MEMORY_MMAP; + q->magic = VINO_QUEUE_MAGIC; + } + + mutex_unlock(&q->queue_mutex); + + return ret; +} + +static struct vino_framebuffer *vino_queue_add(struct + vino_framebuffer_queue *q, + unsigned int id) +{ + struct vino_framebuffer *ret = NULL; + unsigned int total; + unsigned long flags; + + dprintk("vino_queue_add(): id = %d\n", id); + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + if (id >= q->length) + goto out; + + /* not needed?: if (vino_fifo_full(&q->out)) { + goto out; + }*/ + /* check that outgoing queue isn't already full + * (or that it won't become full) */ + total = vino_fifo_get_used(&q->in) + + vino_fifo_get_used(&q->out); + if (total >= q->length) + goto out; + + if (vino_fifo_enqueue(&q->in, id)) + goto out; + + ret = q->buffer[id]; + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static struct vino_framebuffer *vino_queue_transfer(struct + vino_framebuffer_queue *q) +{ + struct vino_framebuffer *ret = NULL; + struct vino_framebuffer *fb; + int id; + unsigned long flags; + + dprintk("vino_queue_transfer():\n"); + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + // now this actually removes an entry from the incoming queue + if (vino_fifo_dequeue(&q->in, &id)) { + goto out; + } + + dprintk("vino_queue_transfer(): id = %d\n", id); + fb = q->buffer[id]; + + // we have already checked that the outgoing queue is not full, but... + if (vino_fifo_enqueue(&q->out, id)) { + printk(KERN_ERR "vino_queue_transfer(): " + "outgoing queue is full, this shouldn't happen!\n"); + goto out; + } + + ret = fb; +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +/* returns true/false */ +static int vino_queue_incoming_contains(struct vino_framebuffer_queue *q, + unsigned int id) +{ + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + ret = vino_fifo_has_id(&q->in, id); + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +/* returns true/false */ +static int vino_queue_outgoing_contains(struct vino_framebuffer_queue *q, + unsigned int id) +{ + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + ret = vino_fifo_has_id(&q->out, id); + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static int vino_queue_get_incoming(struct vino_framebuffer_queue *q, + unsigned int *used) +{ + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return VINO_QUEUE_ERROR; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) { + ret = VINO_QUEUE_ERROR; + goto out; + } + + *used = vino_fifo_get_used(&q->in); + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static int vino_queue_get_outgoing(struct vino_framebuffer_queue *q, + unsigned int *used) +{ + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return VINO_QUEUE_ERROR; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) { + ret = VINO_QUEUE_ERROR; + goto out; + } + + *used = vino_fifo_get_used(&q->out); + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +#if 0 +static int vino_queue_get_total(struct vino_framebuffer_queue *q, + unsigned int *total) +{ + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return VINO_QUEUE_ERROR; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) { + ret = VINO_QUEUE_ERROR; + goto out; + } + + *total = vino_fifo_get_used(&q->in) + + vino_fifo_get_used(&q->out); + +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} +#endif + +static struct vino_framebuffer *vino_queue_peek(struct + vino_framebuffer_queue *q, + unsigned int *id) +{ + struct vino_framebuffer *ret = NULL; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + if (vino_fifo_peek(&q->in, id)) { + goto out; + } + + ret = q->buffer[*id]; +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static struct vino_framebuffer *vino_queue_remove(struct + vino_framebuffer_queue *q, + unsigned int *id) +{ + struct vino_framebuffer *ret = NULL; + unsigned long flags; + dprintk("vino_queue_remove():\n"); + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + if (vino_fifo_dequeue(&q->out, id)) { + goto out; + } + + dprintk("vino_queue_remove(): id = %d\n", *id); + ret = q->buffer[*id]; +out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static struct +vino_framebuffer *vino_queue_get_buffer(struct vino_framebuffer_queue *q, + unsigned int id) +{ + struct vino_framebuffer *ret = NULL; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + + if (q->length == 0) + goto out; + + if (id >= q->length) + goto out; + + ret = q->buffer[id]; + out: + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +static unsigned int vino_queue_get_length(struct vino_framebuffer_queue *q) +{ + unsigned int length = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return length; + } + + spin_lock_irqsave(&q->queue_lock, flags); + length = q->length; + spin_unlock_irqrestore(&q->queue_lock, flags); + + return length; +} + +static int vino_queue_has_mapped_buffers(struct vino_framebuffer_queue *q) +{ + unsigned int i; + int ret = 0; + unsigned long flags; + + if (q->magic != VINO_QUEUE_MAGIC) { + return ret; + } + + spin_lock_irqsave(&q->queue_lock, flags); + for (i = 0; i < q->length; i++) { + if (q->buffer[i]->map_count > 0) { + ret = 1; + break; + } + } + spin_unlock_irqrestore(&q->queue_lock, flags); + + return ret; +} + +/* VINO functions */ + +/* execute with input_lock locked */ +static void vino_update_line_size(struct vino_channel_settings *vcs) +{ + unsigned int w = vcs->clipping.right - vcs->clipping.left; + unsigned int d = vcs->decimation; + unsigned int bpp = vino_data_formats[vcs->data_format].bpp; + unsigned int lsize; + + dprintk("update_line_size(): before: w = %d, d = %d, " + "line_size = %d\n", w, d, vcs->line_size); + + /* line size must be multiple of 8 bytes */ + lsize = (bpp * (w / d)) & ~7; + w = (lsize / bpp) * d; + + vcs->clipping.right = vcs->clipping.left + w; + vcs->line_size = lsize; + + dprintk("update_line_size(): after: w = %d, d = %d, " + "line_size = %d\n", w, d, vcs->line_size); +} + +/* execute with input_lock locked */ +static void vino_set_clipping(struct vino_channel_settings *vcs, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + unsigned int maxwidth, maxheight; + unsigned int d; + + maxwidth = vino_data_norms[vcs->data_norm].width; + maxheight = vino_data_norms[vcs->data_norm].height; + d = vcs->decimation; + + y &= ~1; /* odd/even fields */ + + if (x > maxwidth) { + x = 0; + } + if (y > maxheight) { + y = 0; + } + + if (((w / d) < VINO_MIN_WIDTH) + || ((h / d) < VINO_MIN_HEIGHT)) { + w = VINO_MIN_WIDTH * d; + h = VINO_MIN_HEIGHT * d; + } + + if ((x + w) > maxwidth) { + w = maxwidth - x; + if ((w / d) < VINO_MIN_WIDTH) + x = maxwidth - VINO_MIN_WIDTH * d; + } + if ((y + h) > maxheight) { + h = maxheight - y; + if ((h / d) < VINO_MIN_HEIGHT) + y = maxheight - VINO_MIN_HEIGHT * d; + } + + vcs->clipping.left = x; + vcs->clipping.top = y; + vcs->clipping.right = x + w; + vcs->clipping.bottom = y + h; + + vino_update_line_size(vcs); + + dprintk("clipping %d, %d, %d, %d / %d - %d\n", + vcs->clipping.left, vcs->clipping.top, vcs->clipping.right, + vcs->clipping.bottom, vcs->decimation, vcs->line_size); +} + +/* execute with input_lock locked */ +static inline void vino_set_default_clipping(struct vino_channel_settings *vcs) +{ + vino_set_clipping(vcs, 0, 0, vino_data_norms[vcs->data_norm].width, + vino_data_norms[vcs->data_norm].height); +} + +/* execute with input_lock locked */ +static void vino_set_scaling(struct vino_channel_settings *vcs, + unsigned int w, unsigned int h) +{ + unsigned int x, y, curw, curh, d; + + x = vcs->clipping.left; + y = vcs->clipping.top; + curw = vcs->clipping.right - vcs->clipping.left; + curh = vcs->clipping.bottom - vcs->clipping.top; + + d = max(curw / w, curh / h); + + dprintk("scaling w: %d, h: %d, curw: %d, curh: %d, d: %d\n", + w, h, curw, curh, d); + + if (d < 1) { + d = 1; + } else if (d > 8) { + d = 8; + } + + vcs->decimation = d; + vino_set_clipping(vcs, x, y, w * d, h * d); + + dprintk("scaling %d, %d, %d, %d / %d - %d\n", vcs->clipping.left, + vcs->clipping.top, vcs->clipping.right, vcs->clipping.bottom, + vcs->decimation, vcs->line_size); +} + +/* execute with input_lock locked */ +static inline void vino_set_default_scaling(struct vino_channel_settings *vcs) +{ + vino_set_scaling(vcs, vcs->clipping.right - vcs->clipping.left, + vcs->clipping.bottom - vcs->clipping.top); +} + +/* execute with input_lock locked */ +static void vino_set_framerate(struct vino_channel_settings *vcs, + unsigned int fps) +{ + unsigned int mask; + + switch (vcs->data_norm) { + case VINO_DATA_NORM_NTSC: + case VINO_DATA_NORM_D1: + fps = (unsigned int)(fps / 6) * 6; // FIXME: round! + + if (fps < vino_data_norms[vcs->data_norm].fps_min) + fps = vino_data_norms[vcs->data_norm].fps_min; + if (fps > vino_data_norms[vcs->data_norm].fps_max) + fps = vino_data_norms[vcs->data_norm].fps_max; + + switch (fps) { + case 6: + mask = 0x003; + break; + case 12: + mask = 0x0c3; + break; + case 18: + mask = 0x333; + break; + case 24: + mask = 0x3ff; + break; + case 30: + mask = 0xfff; + break; + default: + mask = VINO_FRAMERT_FULL; + } + vcs->framert_reg = VINO_FRAMERT_RT(mask); + break; + case VINO_DATA_NORM_PAL: + case VINO_DATA_NORM_SECAM: + fps = (unsigned int)(fps / 5) * 5; // FIXME: round! + + if (fps < vino_data_norms[vcs->data_norm].fps_min) + fps = vino_data_norms[vcs->data_norm].fps_min; + if (fps > vino_data_norms[vcs->data_norm].fps_max) + fps = vino_data_norms[vcs->data_norm].fps_max; + + switch (fps) { + case 5: + mask = 0x003; + break; + case 10: + mask = 0x0c3; + break; + case 15: + mask = 0x333; + break; + case 20: + mask = 0x0ff; + break; + case 25: + mask = 0x3ff; + break; + default: + mask = VINO_FRAMERT_FULL; + } + vcs->framert_reg = VINO_FRAMERT_RT(mask) | VINO_FRAMERT_PAL; + break; + } + + vcs->fps = fps; +} + +/* execute with input_lock locked */ +static inline void vino_set_default_framerate(struct + vino_channel_settings *vcs) +{ + vino_set_framerate(vcs, vino_data_norms[vcs->data_norm].fps_max); +} + +/* + * Prepare VINO for DMA transfer... + * (execute only with vino_lock and input_lock locked) + */ +static int vino_dma_setup(struct vino_channel_settings *vcs, + struct vino_framebuffer *fb) +{ + u32 ctrl, intr; + struct sgi_vino_channel *ch; + const struct vino_data_norm *norm; + + dprintk("vino_dma_setup():\n"); + + vcs->field = 0; + fb->frame_counter = 0; + + ch = (vcs->channel == VINO_CHANNEL_A) ? &vino->a : &vino->b; + norm = &vino_data_norms[vcs->data_norm]; + + ch->page_index = 0; + ch->line_count = 0; + + /* VINO line size register is set 8 bytes less than actual */ + ch->line_size = vcs->line_size - 8; + + /* let VINO know where to transfer data */ + ch->start_desc_tbl = fb->desc_table.dma; + ch->next_4_desc = fb->desc_table.dma; + + /* give vino time to fetch the first four descriptors, 5 usec + * should be more than enough time */ + udelay(VINO_DESC_FETCH_DELAY); + + dprintk("vino_dma_setup(): start desc = %08x, next 4 desc = %08x\n", + ch->start_desc_tbl, ch->next_4_desc); + + /* set the alpha register */ + ch->alpha = vcs->alpha; + + /* set clipping registers */ + ch->clip_start = VINO_CLIP_ODD(norm->odd.top + vcs->clipping.top / 2) | + VINO_CLIP_EVEN(norm->even.top + + vcs->clipping.top / 2) | + VINO_CLIP_X(vcs->clipping.left); + ch->clip_end = VINO_CLIP_ODD(norm->odd.top + + vcs->clipping.bottom / 2 - 1) | + VINO_CLIP_EVEN(norm->even.top + + vcs->clipping.bottom / 2 - 1) | + VINO_CLIP_X(vcs->clipping.right); + + /* set the size of actual content in the buffer (DECIMATION !) */ + fb->data_size = ((vcs->clipping.right - vcs->clipping.left) / + vcs->decimation) * + ((vcs->clipping.bottom - vcs->clipping.top) / + vcs->decimation) * + vino_data_formats[vcs->data_format].bpp; + + ch->frame_rate = vcs->framert_reg; + + ctrl = vino->control; + intr = vino->intr_status; + + if (vcs->channel == VINO_CHANNEL_A) { + /* All interrupt conditions for this channel was cleared + * so clear the interrupt status register and enable + * interrupts */ + intr &= ~VINO_INTSTAT_A; + ctrl |= VINO_CTRL_A_INT; + + /* enable synchronization */ + ctrl |= VINO_CTRL_A_SYNC_ENBL; + + /* enable frame assembly */ + ctrl |= VINO_CTRL_A_INTERLEAVE_ENBL; + + /* set decimation used */ + if (vcs->decimation < 2) + ctrl &= ~VINO_CTRL_A_DEC_ENBL; + else { + ctrl |= VINO_CTRL_A_DEC_ENBL; + ctrl &= ~VINO_CTRL_A_DEC_SCALE_MASK; + ctrl |= (vcs->decimation - 1) << + VINO_CTRL_A_DEC_SCALE_SHIFT; + } + + /* select input interface */ + if (vcs->input == VINO_INPUT_D1) + ctrl |= VINO_CTRL_A_SELECT; + else + ctrl &= ~VINO_CTRL_A_SELECT; + + /* palette */ + ctrl &= ~(VINO_CTRL_A_LUMA_ONLY | VINO_CTRL_A_RGB | + VINO_CTRL_A_DITHER); + } else { + intr &= ~VINO_INTSTAT_B; + ctrl |= VINO_CTRL_B_INT; + + ctrl |= VINO_CTRL_B_SYNC_ENBL; + ctrl |= VINO_CTRL_B_INTERLEAVE_ENBL; + + if (vcs->decimation < 2) + ctrl &= ~VINO_CTRL_B_DEC_ENBL; + else { + ctrl |= VINO_CTRL_B_DEC_ENBL; + ctrl &= ~VINO_CTRL_B_DEC_SCALE_MASK; + ctrl |= (vcs->decimation - 1) << + VINO_CTRL_B_DEC_SCALE_SHIFT; + + } + if (vcs->input == VINO_INPUT_D1) + ctrl |= VINO_CTRL_B_SELECT; + else + ctrl &= ~VINO_CTRL_B_SELECT; + + ctrl &= ~(VINO_CTRL_B_LUMA_ONLY | VINO_CTRL_B_RGB | + VINO_CTRL_B_DITHER); + } + + /* set palette */ + fb->data_format = vcs->data_format; + + switch (vcs->data_format) { + case VINO_DATA_FMT_GREY: + ctrl |= (vcs->channel == VINO_CHANNEL_A) ? + VINO_CTRL_A_LUMA_ONLY : VINO_CTRL_B_LUMA_ONLY; + break; + case VINO_DATA_FMT_RGB32: + ctrl |= (vcs->channel == VINO_CHANNEL_A) ? + VINO_CTRL_A_RGB : VINO_CTRL_B_RGB; + break; + case VINO_DATA_FMT_YUV: + /* nothing needs to be done */ + break; + case VINO_DATA_FMT_RGB332: + ctrl |= (vcs->channel == VINO_CHANNEL_A) ? + VINO_CTRL_A_RGB | VINO_CTRL_A_DITHER : + VINO_CTRL_B_RGB | VINO_CTRL_B_DITHER; + break; + } + + vino->intr_status = intr; + vino->control = ctrl; + + return 0; +} + +/* (execute only with vino_lock locked) */ +static inline void vino_dma_start(struct vino_channel_settings *vcs) +{ + u32 ctrl = vino->control; + + dprintk("vino_dma_start():\n"); + ctrl |= (vcs->channel == VINO_CHANNEL_A) ? + VINO_CTRL_A_DMA_ENBL : VINO_CTRL_B_DMA_ENBL; + vino->control = ctrl; +} + +/* (execute only with vino_lock locked) */ +static inline void vino_dma_stop(struct vino_channel_settings *vcs) +{ + u32 ctrl = vino->control; + + ctrl &= (vcs->channel == VINO_CHANNEL_A) ? + ~VINO_CTRL_A_DMA_ENBL : ~VINO_CTRL_B_DMA_ENBL; + ctrl &= (vcs->channel == VINO_CHANNEL_A) ? + ~VINO_CTRL_A_INT : ~VINO_CTRL_B_INT; + vino->control = ctrl; + dprintk("vino_dma_stop():\n"); +} + +/* + * Load dummy page to descriptor registers. This prevents generating of + * spurious interrupts. (execute only with vino_lock locked) + */ +static void vino_clear_interrupt(struct vino_channel_settings *vcs) +{ + struct sgi_vino_channel *ch; + + ch = (vcs->channel == VINO_CHANNEL_A) ? &vino->a : &vino->b; + + ch->page_index = 0; + ch->line_count = 0; + + ch->start_desc_tbl = vino_drvdata->dummy_desc_table.dma; + ch->next_4_desc = vino_drvdata->dummy_desc_table.dma; + + udelay(VINO_DESC_FETCH_DELAY); + dprintk("channel %c clear interrupt condition\n", + (vcs->channel == VINO_CHANNEL_A) ? 'A':'B'); +} + +static int vino_capture(struct vino_channel_settings *vcs, + struct vino_framebuffer *fb) +{ + int err = 0; + unsigned long flags, flags2; + + spin_lock_irqsave(&fb->state_lock, flags); + + if (fb->state == VINO_FRAMEBUFFER_IN_USE) + err = -EBUSY; + fb->state = VINO_FRAMEBUFFER_IN_USE; + + spin_unlock_irqrestore(&fb->state_lock, flags); + + if (err) + return err; + + spin_lock_irqsave(&vino_drvdata->vino_lock, flags); + spin_lock_irqsave(&vino_drvdata->input_lock, flags2); + + vino_dma_setup(vcs, fb); + vino_dma_start(vcs); + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags2); + spin_unlock_irqrestore(&vino_drvdata->vino_lock, flags); + + return err; +} + +static +struct vino_framebuffer *vino_capture_enqueue(struct + vino_channel_settings *vcs, + unsigned int index) +{ + struct vino_framebuffer *fb; + unsigned long flags; + + dprintk("vino_capture_enqueue():\n"); + + spin_lock_irqsave(&vcs->capture_lock, flags); + + fb = vino_queue_add(&vcs->fb_queue, index); + if (fb == NULL) { + dprintk("vino_capture_enqueue(): vino_queue_add() failed, " + "queue full?\n"); + goto out; + } +out: + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + return fb; +} + +static int vino_capture_next(struct vino_channel_settings *vcs, int start) +{ + struct vino_framebuffer *fb; + unsigned int incoming, id; + int err = 0; + unsigned long flags; + + dprintk("vino_capture_next():\n"); + + spin_lock_irqsave(&vcs->capture_lock, flags); + + if (start) { + /* start capture only if capture isn't in progress already */ + if (vcs->capturing) { + spin_unlock_irqrestore(&vcs->capture_lock, flags); + return 0; + } + + } else { + /* capture next frame: + * stop capture if capturing is not set */ + if (!vcs->capturing) { + spin_unlock_irqrestore(&vcs->capture_lock, flags); + return 0; + } + } + + err = vino_queue_get_incoming(&vcs->fb_queue, &incoming); + if (err) { + dprintk("vino_capture_next(): vino_queue_get_incoming() " + "failed\n"); + err = -EINVAL; + goto out; + } + if (incoming == 0) { + dprintk("vino_capture_next(): no buffers available\n"); + goto out; + } + + fb = vino_queue_peek(&vcs->fb_queue, &id); + if (fb == NULL) { + dprintk("vino_capture_next(): vino_queue_peek() failed\n"); + err = -EINVAL; + goto out; + } + + if (start) { + vcs->capturing = 1; + } + + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + err = vino_capture(vcs, fb); + + return err; + +out: + vcs->capturing = 0; + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + return err; +} + +static inline int vino_is_capturing(struct vino_channel_settings *vcs) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&vcs->capture_lock, flags); + + ret = vcs->capturing; + + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + return ret; +} + +/* waits until a frame is captured */ +static int vino_wait_for_frame(struct vino_channel_settings *vcs) +{ + wait_queue_t wait; + int err = 0; + + dprintk("vino_wait_for_frame():\n"); + + init_waitqueue_entry(&wait, current); + /* add ourselves into wait queue */ + add_wait_queue(&vcs->fb_queue.frame_wait_queue, &wait); + /* and set current state */ + set_current_state(TASK_INTERRUPTIBLE); + + /* to ensure that schedule_timeout will return immediately + * if VINO interrupt was triggred meanwhile */ + schedule_timeout(HZ / 10); + + if (signal_pending(current)) + err = -EINTR; + + remove_wait_queue(&vcs->fb_queue.frame_wait_queue, &wait); + + dprintk("vino_wait_for_frame(): waiting for frame %s\n", + err ? "failed" : "ok"); + + return err; +} + +/* the function assumes that PAGE_SIZE % 4 == 0 */ +static void vino_convert_to_rgba(struct vino_framebuffer *fb) { + unsigned char *pageptr; + unsigned int page, i; + unsigned char a; + + for (page = 0; page < fb->desc_table.page_count; page++) { + pageptr = (unsigned char *)fb->desc_table.virtual[page]; + + for (i = 0; i < PAGE_SIZE; i += 4) { + a = pageptr[0]; + pageptr[0] = pageptr[3]; + pageptr[1] = pageptr[2]; + pageptr[2] = pageptr[1]; + pageptr[3] = a; + pageptr += 4; + } + } +} + +/* checks if the buffer is in correct state and syncs data */ +static int vino_check_buffer(struct vino_channel_settings *vcs, + struct vino_framebuffer *fb) +{ + int err = 0; + unsigned long flags; + + dprintk("vino_check_buffer():\n"); + + spin_lock_irqsave(&fb->state_lock, flags); + switch (fb->state) { + case VINO_FRAMEBUFFER_IN_USE: + err = -EIO; + break; + case VINO_FRAMEBUFFER_READY: + vino_sync_buffer(fb); + fb->state = VINO_FRAMEBUFFER_UNUSED; + break; + default: + err = -EINVAL; + } + spin_unlock_irqrestore(&fb->state_lock, flags); + + if (!err) { + if (vino_pixel_conversion + && (fb->data_format == VINO_DATA_FMT_RGB32)) { + vino_convert_to_rgba(fb); + } + } else if (err && (err != -EINVAL)) { + dprintk("vino_check_buffer(): buffer not ready\n"); + + spin_lock_irqsave(&vino_drvdata->vino_lock, flags); + vino_dma_stop(vcs); + vino_clear_interrupt(vcs); + spin_unlock_irqrestore(&vino_drvdata->vino_lock, flags); + } + + return err; +} + +/* forcefully terminates capture */ +static void vino_capture_stop(struct vino_channel_settings *vcs) +{ + unsigned int incoming = 0, outgoing = 0, id; + unsigned long flags, flags2; + + dprintk("vino_capture_stop():\n"); + + spin_lock_irqsave(&vcs->capture_lock, flags); + + /* unset capturing to stop queue processing */ + vcs->capturing = 0; + + spin_lock_irqsave(&vino_drvdata->vino_lock, flags2); + + vino_dma_stop(vcs); + vino_clear_interrupt(vcs); + + spin_unlock_irqrestore(&vino_drvdata->vino_lock, flags2); + + /* remove all items from the queue */ + if (vino_queue_get_incoming(&vcs->fb_queue, &incoming)) { + dprintk("vino_capture_stop(): " + "vino_queue_get_incoming() failed\n"); + goto out; + } + while (incoming > 0) { + vino_queue_transfer(&vcs->fb_queue); + + if (vino_queue_get_incoming(&vcs->fb_queue, &incoming)) { + dprintk("vino_capture_stop(): " + "vino_queue_get_incoming() failed\n"); + goto out; + } + } + + if (vino_queue_get_outgoing(&vcs->fb_queue, &outgoing)) { + dprintk("vino_capture_stop(): " + "vino_queue_get_outgoing() failed\n"); + goto out; + } + while (outgoing > 0) { + vino_queue_remove(&vcs->fb_queue, &id); + + if (vino_queue_get_outgoing(&vcs->fb_queue, &outgoing)) { + dprintk("vino_capture_stop(): " + "vino_queue_get_outgoing() failed\n"); + goto out; + } + } + +out: + spin_unlock_irqrestore(&vcs->capture_lock, flags); +} + +#if 0 +static int vino_capture_failed(struct vino_channel_settings *vcs) +{ + struct vino_framebuffer *fb; + unsigned long flags; + unsigned int i; + int ret; + + dprintk("vino_capture_failed():\n"); + + spin_lock_irqsave(&vino_drvdata->vino_lock, flags); + + vino_dma_stop(vcs); + vino_clear_interrupt(vcs); + + spin_unlock_irqrestore(&vino_drvdata->vino_lock, flags); + + ret = vino_queue_get_incoming(&vcs->fb_queue, &i); + if (ret == VINO_QUEUE_ERROR) { + dprintk("vino_queue_get_incoming() failed\n"); + return -EINVAL; + } + if (i == 0) { + /* no buffers to process */ + return 0; + } + + fb = vino_queue_peek(&vcs->fb_queue, &i); + if (fb == NULL) { + dprintk("vino_queue_peek() failed\n"); + return -EINVAL; + } + + spin_lock_irqsave(&fb->state_lock, flags); + if (fb->state == VINO_FRAMEBUFFER_IN_USE) { + fb->state = VINO_FRAMEBUFFER_UNUSED; + vino_queue_transfer(&vcs->fb_queue); + vino_queue_remove(&vcs->fb_queue, &i); + /* we should actually discard the newest frame, + * but who cares ... */ + } + spin_unlock_irqrestore(&fb->state_lock, flags); + + return 0; +} +#endif + +static void vino_skip_frame(struct vino_channel_settings *vcs) +{ + struct vino_framebuffer *fb; + unsigned long flags; + unsigned int id; + + spin_lock_irqsave(&vcs->capture_lock, flags); + fb = vino_queue_peek(&vcs->fb_queue, &id); + if (!fb) { + spin_unlock_irqrestore(&vcs->capture_lock, flags); + dprintk("vino_skip_frame(): vino_queue_peek() failed!\n"); + return; + } + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + spin_lock_irqsave(&fb->state_lock, flags); + fb->state = VINO_FRAMEBUFFER_UNUSED; + spin_unlock_irqrestore(&fb->state_lock, flags); + + vino_capture_next(vcs, 0); +} + +static void vino_frame_done(struct vino_channel_settings *vcs) +{ + struct vino_framebuffer *fb; + unsigned long flags; + + spin_lock_irqsave(&vcs->capture_lock, flags); + fb = vino_queue_transfer(&vcs->fb_queue); + if (!fb) { + spin_unlock_irqrestore(&vcs->capture_lock, flags); + dprintk("vino_frame_done(): vino_queue_transfer() failed!\n"); + return; + } + spin_unlock_irqrestore(&vcs->capture_lock, flags); + + fb->frame_counter = vcs->int_data.frame_counter; + memcpy(&fb->timestamp, &vcs->int_data.timestamp, + sizeof(struct timeval)); + + spin_lock_irqsave(&fb->state_lock, flags); + if (fb->state == VINO_FRAMEBUFFER_IN_USE) + fb->state = VINO_FRAMEBUFFER_READY; + spin_unlock_irqrestore(&fb->state_lock, flags); + + wake_up(&vcs->fb_queue.frame_wait_queue); + + vino_capture_next(vcs, 0); +} + +static void vino_capture_tasklet(unsigned long channel) { + struct vino_channel_settings *vcs; + + vcs = (channel == VINO_CHANNEL_A) + ? &vino_drvdata->a : &vino_drvdata->b; + + if (vcs->int_data.skip) + vcs->int_data.skip_count++; + + if (vcs->int_data.skip && (vcs->int_data.skip_count + <= VINO_MAX_FRAME_SKIP_COUNT)) { + vino_skip_frame(vcs); + } else { + vcs->int_data.skip_count = 0; + vino_frame_done(vcs); + } +} + +static irqreturn_t vino_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + u32 ctrl, intr; + unsigned int fc_a, fc_b; + int handled_a = 0, skip_a = 0, done_a = 0; + int handled_b = 0, skip_b = 0, done_b = 0; + +#ifdef VINO_DEBUG_INT + int loop = 0; + unsigned int line_count = vino->a.line_count, + page_index = vino->a.page_index, + field_counter = vino->a.field_counter, + start_desc_tbl = vino->a.start_desc_tbl, + next_4_desc = vino->a.next_4_desc; + unsigned int line_count_2, + page_index_2, + field_counter_2, + start_desc_tbl_2, + next_4_desc_2; +#endif + + spin_lock(&vino_drvdata->vino_lock); + + while ((intr = vino->intr_status)) { + fc_a = vino->a.field_counter >> 1; + fc_b = vino->b.field_counter >> 1; + + /* handle error-interrupts in some special way ? + * --> skips frames */ + if (intr & VINO_INTSTAT_A) { + if (intr & VINO_INTSTAT_A_EOF) { + vino_drvdata->a.field++; + if (vino_drvdata->a.field > 1) { + vino_dma_stop(&vino_drvdata->a); + vino_clear_interrupt(&vino_drvdata->a); + vino_drvdata->a.field = 0; + done_a = 1; + } else { + if (vino->a.page_index + != vino_drvdata->a.line_size) { + vino->a.line_count = 0; + vino->a.page_index = + vino_drvdata-> + a.line_size; + vino->a.next_4_desc = + vino->a.start_desc_tbl; + } + } + dprintk("channel A end-of-field " + "interrupt: %04x\n", intr); + } else { + vino_dma_stop(&vino_drvdata->a); + vino_clear_interrupt(&vino_drvdata->a); + vino_drvdata->a.field = 0; + skip_a = 1; + dprintk("channel A error interrupt: %04x\n", + intr); + } + +#ifdef VINO_DEBUG_INT + line_count_2 = vino->a.line_count; + page_index_2 = vino->a.page_index; + field_counter_2 = vino->a.field_counter; + start_desc_tbl_2 = vino->a.start_desc_tbl; + next_4_desc_2 = vino->a.next_4_desc; + + printk("intr = %04x, loop = %d, field = %d\n", + intr, loop, vino_drvdata->a.field); + printk("1- line count = %04d, page index = %04d, " + "start = %08x, next = %08x\n" + " fieldc = %d, framec = %d\n", + line_count, page_index, start_desc_tbl, + next_4_desc, field_counter, fc_a); + printk("12-line count = %04d, page index = %04d, " + " start = %08x, next = %08x\n", + line_count_2, page_index_2, start_desc_tbl_2, + next_4_desc_2); + + if (done_a) + printk("\n"); +#endif + } + + if (intr & VINO_INTSTAT_B) { + if (intr & VINO_INTSTAT_B_EOF) { + vino_drvdata->b.field++; + if (vino_drvdata->b.field > 1) { + vino_dma_stop(&vino_drvdata->b); + vino_clear_interrupt(&vino_drvdata->b); + vino_drvdata->b.field = 0; + done_b = 1; + } + dprintk("channel B end-of-field " + "interrupt: %04x\n", intr); + } else { + vino_dma_stop(&vino_drvdata->b); + vino_clear_interrupt(&vino_drvdata->b); + vino_drvdata->b.field = 0; + skip_b = 1; + dprintk("channel B error interrupt: %04x\n", + intr); + } + } + + /* Always remember to clear interrupt status. + * Disable VINO interrupts while we do this. */ + ctrl = vino->control; + vino->control = ctrl & ~(VINO_CTRL_A_INT | VINO_CTRL_B_INT); + vino->intr_status = ~intr; + vino->control = ctrl; + + spin_unlock(&vino_drvdata->vino_lock); + + if ((!handled_a) && (done_a || skip_a)) { + if (!skip_a) { + do_gettimeofday(&vino_drvdata-> + a.int_data.timestamp); + vino_drvdata->a.int_data.frame_counter = fc_a; + } + vino_drvdata->a.int_data.skip = skip_a; + + dprintk("channel A %s, interrupt: %d\n", + skip_a ? "skipping frame" : "frame done", + intr); + tasklet_hi_schedule(&vino_tasklet_a); + handled_a = 1; + } + + if ((!handled_b) && (done_b || skip_b)) { + if (!skip_b) { + do_gettimeofday(&vino_drvdata-> + b.int_data.timestamp); + vino_drvdata->b.int_data.frame_counter = fc_b; + } + vino_drvdata->b.int_data.skip = skip_b; + + dprintk("channel B %s, interrupt: %d\n", + skip_b ? "skipping frame" : "frame done", + intr); + tasklet_hi_schedule(&vino_tasklet_b); + handled_b = 1; + } + +#ifdef VINO_DEBUG_INT + loop++; +#endif + spin_lock(&vino_drvdata->vino_lock); + } + + spin_unlock(&vino_drvdata->vino_lock); + + return IRQ_HANDLED; +} + +/* VINO video input management */ + +static int vino_get_saa7191_input(int input) +{ + switch (input) { + case VINO_INPUT_COMPOSITE: + return SAA7191_INPUT_COMPOSITE; + case VINO_INPUT_SVIDEO: + return SAA7191_INPUT_SVIDEO; + default: + printk(KERN_ERR "VINO: vino_get_saa7191_input(): " + "invalid input!\n"); + return -1; + } +} + +static int vino_get_saa7191_norm(unsigned int data_norm) +{ + switch (data_norm) { + case VINO_DATA_NORM_AUTO: + return SAA7191_NORM_AUTO; + case VINO_DATA_NORM_AUTO_EXT: + return SAA7191_NORM_AUTO_EXT; + case VINO_DATA_NORM_PAL: + return SAA7191_NORM_PAL; + case VINO_DATA_NORM_NTSC: + return SAA7191_NORM_NTSC; + case VINO_DATA_NORM_SECAM: + return SAA7191_NORM_SECAM; + default: + printk(KERN_ERR "VINO: vino_get_saa7191_norm(): " + "invalid norm!\n"); + return -1; + } +} + +static int vino_get_from_saa7191_norm(int saa7191_norm) +{ + switch (saa7191_norm) { + case SAA7191_NORM_PAL: + return VINO_DATA_NORM_PAL; + case SAA7191_NORM_NTSC: + return VINO_DATA_NORM_NTSC; + case SAA7191_NORM_SECAM: + return VINO_DATA_NORM_SECAM; + default: + printk(KERN_ERR "VINO: vino_get_from_saa7191_norm(): " + "invalid norm!\n"); + return VINO_DATA_NORM_NONE; + } +} + +static int vino_saa7191_set_norm(unsigned int *data_norm) +{ + int saa7191_norm, new_data_norm; + int err = 0; + + saa7191_norm = vino_get_saa7191_norm(*data_norm); + + err = i2c_decoder_command(DECODER_SAA7191_SET_NORM, + &saa7191_norm); + if (err) + goto out; + + if ((*data_norm == VINO_DATA_NORM_AUTO) + || (*data_norm == VINO_DATA_NORM_AUTO_EXT)) { + struct saa7191_status status; + + err = i2c_decoder_command(DECODER_SAA7191_GET_STATUS, + &status); + if (err) + goto out; + + new_data_norm = + vino_get_from_saa7191_norm(status.norm); + if (new_data_norm == VINO_DATA_NORM_NONE) { + err = -EINVAL; + goto out; + } + + *data_norm = (unsigned int)new_data_norm; + } + +out: + return err; +} + +/* execute with input_lock locked */ +static int vino_is_input_owner(struct vino_channel_settings *vcs) +{ + switch(vcs->input) { + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: + return (vino_drvdata->decoder.owner == vcs->channel); + case VINO_INPUT_D1: + return (vino_drvdata->camera.owner == vcs->channel); + default: + return 0; + } +} + +static int vino_acquire_input(struct vino_channel_settings *vcs) +{ + unsigned long flags; + int ret = 0; + + dprintk("vino_acquire_input():\n"); + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + /* First try D1 and then SAA7191 */ + if (vino_drvdata->camera.driver + && (vino_drvdata->camera.owner == VINO_NO_CHANNEL)) { + if (i2c_use_client(vino_drvdata->camera.driver)) { + ret = -ENODEV; + goto out; + } + + vino_drvdata->camera.owner = vcs->channel; + vcs->input = VINO_INPUT_D1; + vcs->data_norm = VINO_DATA_NORM_D1; + } else if (vino_drvdata->decoder.driver + && (vino_drvdata->decoder.owner == VINO_NO_CHANNEL)) { + int input, data_norm; + int saa7191_input; + + if (i2c_use_client(vino_drvdata->decoder.driver)) { + ret = -ENODEV; + goto out; + } + + input = VINO_INPUT_COMPOSITE; + + saa7191_input = vino_get_saa7191_input(input); + ret = i2c_decoder_command(DECODER_SET_INPUT, + &saa7191_input); + if (ret) { + ret = -EINVAL; + goto out; + } + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + /* Don't hold spinlocks while auto-detecting norm + * as it may take a while... */ + + data_norm = VINO_DATA_NORM_AUTO_EXT; + + ret = vino_saa7191_set_norm(&data_norm); + if ((ret == -EBUSY) || (ret == -EAGAIN)) { + data_norm = VINO_DATA_NORM_PAL; + ret = vino_saa7191_set_norm(&data_norm); + } + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if (ret) { + ret = -EINVAL; + goto out; + } + + vino_drvdata->decoder.owner = vcs->channel; + + vcs->input = input; + vcs->data_norm = data_norm; + } else { + vcs->input = (vcs->channel == VINO_CHANNEL_A) ? + vino_drvdata->b.input : vino_drvdata->a.input; + vcs->data_norm = (vcs->channel == VINO_CHANNEL_A) ? + vino_drvdata->b.data_norm : vino_drvdata->a.data_norm; + } + + if (vcs->input == VINO_INPUT_NONE) { + ret = -ENODEV; + goto out; + } + + vino_set_default_clipping(vcs); + vino_set_default_scaling(vcs); + vino_set_default_framerate(vcs); + + dprintk("vino_acquire_input(): %s\n", vino_inputs[vcs->input].name); + +out: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return ret; +} + +static int vino_set_input(struct vino_channel_settings *vcs, int input) +{ + struct vino_channel_settings *vcs2 = (vcs->channel == VINO_CHANNEL_A) ? + &vino_drvdata->b : &vino_drvdata->a; + unsigned long flags; + int ret = 0; + + dprintk("vino_set_input():\n"); + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if (vcs->input == input) + goto out; + + switch (input) { + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: + if (!vino_drvdata->decoder.driver) { + ret = -EINVAL; + goto out; + } + + if (vino_drvdata->decoder.owner == VINO_NO_CHANNEL) { + if (i2c_use_client(vino_drvdata->decoder.driver)) { + ret = -ENODEV; + goto out; + } + vino_drvdata->decoder.owner = vcs->channel; + } + + if (vino_drvdata->decoder.owner == vcs->channel) { + int data_norm; + int saa7191_input; + + saa7191_input = vino_get_saa7191_input(input); + ret = i2c_decoder_command(DECODER_SET_INPUT, + &saa7191_input); + if (ret) { + vino_drvdata->decoder.owner = VINO_NO_CHANNEL; + ret = -EINVAL; + goto out; + } + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + /* Don't hold spinlocks while auto-detecting norm + * as it may take a while... */ + + data_norm = VINO_DATA_NORM_AUTO_EXT; + + ret = vino_saa7191_set_norm(&data_norm); + if ((ret == -EBUSY) || (ret == -EAGAIN)) { + data_norm = VINO_DATA_NORM_PAL; + ret = vino_saa7191_set_norm(&data_norm); + } + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if (ret) { + vino_drvdata->decoder.owner = VINO_NO_CHANNEL; + ret = -EINVAL; + goto out; + } + + vcs->input = input; + vcs->data_norm = data_norm; + } else { + if (input != vcs2->input) { + ret = -EBUSY; + goto out; + } + + vcs->input = input; + vcs->data_norm = vcs2->data_norm; + } + + if (vino_drvdata->camera.owner == vcs->channel) { + /* Transfer the ownership or release the input */ + if (vcs2->input == VINO_INPUT_D1) { + vino_drvdata->camera.owner = vcs2->channel; + } else { + i2c_release_client(vino_drvdata-> + camera.driver); + vino_drvdata->camera.owner = VINO_NO_CHANNEL; + } + } + break; + case VINO_INPUT_D1: + if (!vino_drvdata->camera.driver) { + ret = -EINVAL; + goto out; + } + + if (vino_drvdata->camera.owner == VINO_NO_CHANNEL) { + if (i2c_use_client(vino_drvdata->camera.driver)) { + ret = -ENODEV; + goto out; + } + vino_drvdata->camera.owner = vcs->channel; + } + + if (vino_drvdata->decoder.owner == vcs->channel) { + /* Transfer the ownership or release the input */ + if ((vcs2->input == VINO_INPUT_COMPOSITE) || + (vcs2->input == VINO_INPUT_SVIDEO)) { + vino_drvdata->decoder.owner = vcs2->channel; + } else { + i2c_release_client(vino_drvdata-> + decoder.driver); + vino_drvdata->decoder.owner = VINO_NO_CHANNEL; + } + } + + vcs->input = input; + vcs->data_norm = VINO_DATA_NORM_D1; + break; + default: + ret = -EINVAL; + goto out; + } + + vino_set_default_clipping(vcs); + vino_set_default_scaling(vcs); + vino_set_default_framerate(vcs); + + dprintk("vino_set_input(): %s\n", vino_inputs[vcs->input].name); + +out: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return ret; +} + +static void vino_release_input(struct vino_channel_settings *vcs) +{ + struct vino_channel_settings *vcs2 = (vcs->channel == VINO_CHANNEL_A) ? + &vino_drvdata->b : &vino_drvdata->a; + unsigned long flags; + + dprintk("vino_release_input():\n"); + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + /* Release ownership of the channel + * and if the other channel takes input from + * the same source, transfer the ownership */ + if (vino_drvdata->camera.owner == vcs->channel) { + if (vcs2->input == VINO_INPUT_D1) { + vino_drvdata->camera.owner = vcs2->channel; + } else { + i2c_release_client(vino_drvdata->camera.driver); + vino_drvdata->camera.owner = VINO_NO_CHANNEL; + } + } else if (vino_drvdata->decoder.owner == vcs->channel) { + if ((vcs2->input == VINO_INPUT_COMPOSITE) || + (vcs2->input == VINO_INPUT_SVIDEO)) { + vino_drvdata->decoder.owner = vcs2->channel; + } else { + i2c_release_client(vino_drvdata->decoder.driver); + vino_drvdata->decoder.owner = VINO_NO_CHANNEL; + } + } + vcs->input = VINO_INPUT_NONE; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); +} + +/* execute with input_lock locked */ +static int vino_set_data_norm(struct vino_channel_settings *vcs, + unsigned int data_norm, + unsigned long *flags) +{ + int err = 0; + + if (data_norm == vcs->data_norm) + return 0; + + switch (vcs->input) { + case VINO_INPUT_D1: + /* only one "norm" supported */ + if ((data_norm != VINO_DATA_NORM_D1) + && (data_norm != VINO_DATA_NORM_AUTO) + && (data_norm != VINO_DATA_NORM_AUTO_EXT)) + return -EINVAL; + break; + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: { + if ((data_norm != VINO_DATA_NORM_PAL) + && (data_norm != VINO_DATA_NORM_NTSC) + && (data_norm != VINO_DATA_NORM_SECAM) + && (data_norm != VINO_DATA_NORM_AUTO) + && (data_norm != VINO_DATA_NORM_AUTO_EXT)) + return -EINVAL; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, *flags); + + /* Don't hold spinlocks while setting norm + * as it may take a while... */ + + err = vino_saa7191_set_norm(&data_norm); + + spin_lock_irqsave(&vino_drvdata->input_lock, *flags); + + if (err) + goto out; + + vcs->data_norm = data_norm; + + vino_set_default_clipping(vcs); + vino_set_default_scaling(vcs); + vino_set_default_framerate(vcs); + break; + } + default: + return -EINVAL; + } + +out: + return err; +} + +/* V4L2 helper functions */ + +static int vino_find_data_format(__u32 pixelformat) +{ + int i; + + for (i = 0; i < VINO_DATA_FMT_COUNT; i++) { + if (vino_data_formats[i].pixelformat == pixelformat) + return i; + } + + return VINO_DATA_FMT_NONE; +} + +static int vino_enum_data_norm(struct vino_channel_settings *vcs, __u32 index) +{ + int data_norm = VINO_DATA_NORM_NONE; + unsigned long flags; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + switch(vcs->input) { + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: + if (index == 0) { + data_norm = VINO_DATA_NORM_PAL; + } else if (index == 1) { + data_norm = VINO_DATA_NORM_NTSC; + } else if (index == 2) { + data_norm = VINO_DATA_NORM_SECAM; + } + break; + case VINO_INPUT_D1: + if (index == 0) { + data_norm = VINO_DATA_NORM_D1; + } + break; + } + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return data_norm; +} + +static int vino_enum_input(struct vino_channel_settings *vcs, __u32 index) +{ + int input = VINO_INPUT_NONE; + unsigned long flags; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + if (vino_drvdata->decoder.driver && vino_drvdata->camera.driver) { + switch (index) { + case 0: + input = VINO_INPUT_COMPOSITE; + break; + case 1: + input = VINO_INPUT_SVIDEO; + break; + case 2: + input = VINO_INPUT_D1; + break; + } + } else if (vino_drvdata->decoder.driver) { + switch (index) { + case 0: + input = VINO_INPUT_COMPOSITE; + break; + case 1: + input = VINO_INPUT_SVIDEO; + break; + } + } else if (vino_drvdata->camera.driver) { + switch (index) { + case 0: + input = VINO_INPUT_D1; + break; + } + } + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return input; +} + +/* execute with input_lock locked */ +static __u32 vino_find_input_index(struct vino_channel_settings *vcs) +{ + __u32 index = 0; + // FIXME: detect when no inputs available + + if (vino_drvdata->decoder.driver && vino_drvdata->camera.driver) { + switch (vcs->input) { + case VINO_INPUT_COMPOSITE: + index = 0; + break; + case VINO_INPUT_SVIDEO: + index = 1; + break; + case VINO_INPUT_D1: + index = 2; + break; + } + } else if (vino_drvdata->decoder.driver) { + switch (vcs->input) { + case VINO_INPUT_COMPOSITE: + index = 0; + break; + case VINO_INPUT_SVIDEO: + index = 1; + break; + } + } else if (vino_drvdata->camera.driver) { + switch (vcs->input) { + case VINO_INPUT_D1: + index = 0; + break; + } + } + + return index; +} + +/* V4L2 ioctls */ + +static void vino_v4l2_querycap(struct v4l2_capability *cap) +{ + memset(cap, 0, sizeof(struct v4l2_capability)); + + strcpy(cap->driver, vino_driver_name); + strcpy(cap->card, vino_driver_description); + strcpy(cap->bus_info, vino_bus_name); + cap->version = VINO_VERSION_CODE; + cap->capabilities = + V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING; + // V4L2_CAP_OVERLAY, V4L2_CAP_READWRITE +} + +static int vino_v4l2_enuminput(struct vino_channel_settings *vcs, + struct v4l2_input *i) +{ + __u32 index = i->index; + int input; + dprintk("requested index = %d\n", index); + + input = vino_enum_input(vcs, index); + if (input == VINO_INPUT_NONE) + return -EINVAL; + + memset(i, 0, sizeof(struct v4l2_input)); + + i->index = index; + i->type = V4L2_INPUT_TYPE_CAMERA; + i->std = vino_inputs[input].std; + strcpy(i->name, vino_inputs[input].name); + + if ((input == VINO_INPUT_COMPOSITE) + || (input == VINO_INPUT_SVIDEO)) { + struct saa7191_status status; + i2c_decoder_command(DECODER_SAA7191_GET_STATUS, &status); + i->status |= status.signal ? 0 : V4L2_IN_ST_NO_SIGNAL; + i->status |= status.color ? 0 : V4L2_IN_ST_NO_COLOR; + } + + return 0; +} + +static int vino_v4l2_g_input(struct vino_channel_settings *vcs, + unsigned int *i) +{ + __u32 index; + int input; + unsigned long flags; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + input = vcs->input; + index = vino_find_input_index(vcs); + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + dprintk("input = %d\n", input); + + if (input == VINO_INPUT_NONE) { + return -EINVAL; + } + + *i = index; + + return 0; +} + +static int vino_v4l2_s_input(struct vino_channel_settings *vcs, + unsigned int *i) +{ + int input; + dprintk("requested input = %d\n", *i); + + input = vino_enum_input(vcs, *i); + if (input == VINO_INPUT_NONE) + return -EINVAL; + + return vino_set_input(vcs, input); +} + +static int vino_v4l2_enumstd(struct vino_channel_settings *vcs, + struct v4l2_standard *s) +{ + int index = s->index; + int data_norm; + + data_norm = vino_enum_data_norm(vcs, index); + dprintk("standard index = %d\n", index); + + if (data_norm == VINO_DATA_NORM_NONE) + return -EINVAL; + + dprintk("standard name = %s\n", + vino_data_norms[data_norm].description); + + memset(s, 0, sizeof(struct v4l2_standard)); + s->index = index; + + s->id = vino_data_norms[data_norm].std; + s->frameperiod.numerator = 1; + s->frameperiod.denominator = + vino_data_norms[data_norm].fps_max; + s->framelines = + vino_data_norms[data_norm].framelines; + strcpy(s->name, + vino_data_norms[data_norm].description); + + return 0; +} + +static int vino_v4l2_querystd(struct vino_channel_settings *vcs, + v4l2_std_id *std) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + switch (vcs->input) { + case VINO_INPUT_D1: + *std = vino_inputs[vcs->input].std; + break; + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: { + struct saa7191_status status; + + i2c_decoder_command(DECODER_SAA7191_GET_STATUS, &status); + + if (status.signal) { + if (status.signal_60hz) { + *std = V4L2_STD_NTSC; + } else { + *std = V4L2_STD_PAL | V4L2_STD_SECAM; + } + } else { + *std = vino_inputs[vcs->input].std; + } + break; + } + default: + err = -EINVAL; + } + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return err; +} + +static int vino_v4l2_g_std(struct vino_channel_settings *vcs, + v4l2_std_id *std) +{ + unsigned long flags; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + *std = vino_data_norms[vcs->data_norm].std; + dprintk("current standard = %d\n", vcs->data_norm); + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return 0; +} + +static int vino_v4l2_s_std(struct vino_channel_settings *vcs, + v4l2_std_id *std) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if (!vino_is_input_owner(vcs)) { + ret = -EBUSY; + goto out; + } + + /* check if the standard is valid for the current input */ + if ((*std) & vino_inputs[vcs->input].std) { + dprintk("standard accepted\n"); + + /* change the video norm for SAA7191 + * and accept NTSC for D1 (do nothing) */ + + if (vcs->input == VINO_INPUT_D1) + goto out; + + if (((*std) & V4L2_STD_PAL) + && ((*std) & V4L2_STD_NTSC) + && ((*std) & V4L2_STD_SECAM)) { + ret = vino_set_data_norm(vcs, VINO_DATA_NORM_AUTO_EXT, + &flags); + } else if ((*std) & V4L2_STD_PAL) { + ret = vino_set_data_norm(vcs, VINO_DATA_NORM_PAL, + &flags); + } else if ((*std) & V4L2_STD_NTSC) { + ret = vino_set_data_norm(vcs, VINO_DATA_NORM_NTSC, + &flags); + } else if ((*std) & V4L2_STD_SECAM) { + ret = vino_set_data_norm(vcs, VINO_DATA_NORM_SECAM, + &flags); + } else { + ret = -EINVAL; + } + + if (ret) { + ret = -EINVAL; + } + } else { + ret = -EINVAL; + } + +out: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return ret; +} + +static int vino_v4l2_enum_fmt(struct vino_channel_settings *vcs, + struct v4l2_fmtdesc *fd) +{ + enum v4l2_buf_type type = fd->type; + int index = fd->index; + dprintk("format index = %d\n", index); + + switch (fd->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if ((fd->index < 0) || + (fd->index >= VINO_DATA_FMT_COUNT)) + return -EINVAL; + dprintk("format name = %s\n", + vino_data_formats[index].description); + + memset(fd, 0, sizeof(struct v4l2_fmtdesc)); + fd->index = index; + fd->type = type; + fd->pixelformat = vino_data_formats[index].pixelformat; + strcpy(fd->description, vino_data_formats[index].description); + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_try_fmt(struct vino_channel_settings *vcs, + struct v4l2_format *f) +{ + struct vino_channel_settings tempvcs; + unsigned long flags; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct v4l2_pix_format *pf = &f->fmt.pix; + + dprintk("requested: w = %d, h = %d\n", + pf->width, pf->height); + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + memcpy(&tempvcs, vcs, sizeof(struct vino_channel_settings)); + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + tempvcs.data_format = vino_find_data_format(pf->pixelformat); + if (tempvcs.data_format == VINO_DATA_FMT_NONE) { + tempvcs.data_format = VINO_DATA_FMT_GREY; + pf->pixelformat = + vino_data_formats[tempvcs.data_format]. + pixelformat; + } + + /* data format must be set before clipping/scaling */ + vino_set_scaling(&tempvcs, pf->width, pf->height); + + dprintk("data format = %s\n", + vino_data_formats[tempvcs.data_format].description); + + pf->width = (tempvcs.clipping.right - tempvcs.clipping.left) / + tempvcs.decimation; + pf->height = (tempvcs.clipping.bottom - tempvcs.clipping.top) / + tempvcs.decimation; + + pf->field = V4L2_FIELD_INTERLACED; + pf->bytesperline = tempvcs.line_size; + pf->sizeimage = tempvcs.line_size * + (tempvcs.clipping.bottom - tempvcs.clipping.top) / + tempvcs.decimation; + pf->colorspace = + vino_data_formats[tempvcs.data_format].colorspace; + + pf->priv = 0; + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_g_fmt(struct vino_channel_settings *vcs, + struct v4l2_format *f) +{ + unsigned long flags; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct v4l2_pix_format *pf = &f->fmt.pix; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + pf->width = (vcs->clipping.right - vcs->clipping.left) / + vcs->decimation; + pf->height = (vcs->clipping.bottom - vcs->clipping.top) / + vcs->decimation; + pf->pixelformat = + vino_data_formats[vcs->data_format].pixelformat; + + pf->field = V4L2_FIELD_INTERLACED; + pf->bytesperline = vcs->line_size; + pf->sizeimage = vcs->line_size * + (vcs->clipping.bottom - vcs->clipping.top) / + vcs->decimation; + pf->colorspace = + vino_data_formats[vcs->data_format].colorspace; + + pf->priv = 0; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_s_fmt(struct vino_channel_settings *vcs, + struct v4l2_format *f) +{ + int data_format; + unsigned long flags; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct v4l2_pix_format *pf = &f->fmt.pix; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + data_format = vino_find_data_format(pf->pixelformat); + + if (data_format == VINO_DATA_FMT_NONE) { + vcs->data_format = VINO_DATA_FMT_GREY; + pf->pixelformat = + vino_data_formats[vcs->data_format]. + pixelformat; + } else { + vcs->data_format = data_format; + } + + /* data format must be set before clipping/scaling */ + vino_set_scaling(vcs, pf->width, pf->height); + + dprintk("data format = %s\n", + vino_data_formats[vcs->data_format].description); + + pf->width = vcs->clipping.right - vcs->clipping.left; + pf->height = vcs->clipping.bottom - vcs->clipping.top; + + pf->field = V4L2_FIELD_INTERLACED; + pf->bytesperline = vcs->line_size; + pf->sizeimage = vcs->line_size * + (vcs->clipping.bottom - vcs->clipping.top) / + vcs->decimation; + pf->colorspace = + vino_data_formats[vcs->data_format].colorspace; + + pf->priv = 0; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_cropcap(struct vino_channel_settings *vcs, + struct v4l2_cropcap *ccap) +{ + const struct vino_data_norm *norm; + unsigned long flags; + + switch (ccap->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + norm = &vino_data_norms[vcs->data_norm]; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + ccap->bounds.left = 0; + ccap->bounds.top = 0; + ccap->bounds.width = norm->width; + ccap->bounds.height = norm->height; + memcpy(&ccap->defrect, &ccap->bounds, + sizeof(struct v4l2_rect)); + + ccap->pixelaspect.numerator = 1; + ccap->pixelaspect.denominator = 1; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_g_crop(struct vino_channel_settings *vcs, + struct v4l2_crop *c) +{ + unsigned long flags; + + switch (c->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + c->c.left = vcs->clipping.left; + c->c.top = vcs->clipping.top; + c->c.width = vcs->clipping.right - vcs->clipping.left; + c->c.height = vcs->clipping.bottom - vcs->clipping.top; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_s_crop(struct vino_channel_settings *vcs, + struct v4l2_crop *c) +{ + unsigned long flags; + + switch (c->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + vino_set_clipping(vcs, c->c.left, c->c.top, + c->c.width, c->c.height); + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_g_parm(struct vino_channel_settings *vcs, + struct v4l2_streamparm *sp) +{ + unsigned long flags; + + switch (sp->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct v4l2_captureparm *cp = &sp->parm.capture; + memset(cp, 0, sizeof(struct v4l2_captureparm)); + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = 1; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + cp->timeperframe.denominator = vcs->fps; + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + // TODO: cp->readbuffers = xxx; + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_s_parm(struct vino_channel_settings *vcs, + struct v4l2_streamparm *sp) +{ + unsigned long flags; + + switch (sp->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct v4l2_captureparm *cp = &sp->parm.capture; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if ((cp->timeperframe.numerator == 0) || + (cp->timeperframe.denominator == 0)) { + /* reset framerate */ + vino_set_default_framerate(vcs); + } else { + vino_set_framerate(vcs, cp->timeperframe.denominator / + cp->timeperframe.numerator); + } + + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + // TODO: set buffers according to cp->readbuffers + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_reqbufs(struct vino_channel_settings *vcs, + struct v4l2_requestbuffers *rb) +{ + if (vcs->reading) + return -EBUSY; + + switch (rb->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + // TODO: check queue type + if (rb->memory != V4L2_MEMORY_MMAP) { + dprintk("type not mmap\n"); + return -EINVAL; + } + + dprintk("count = %d\n", rb->count); + if (rb->count > 0) { + if (vino_is_capturing(vcs)) { + dprintk("busy, capturing\n"); + return -EBUSY; + } + + if (vino_queue_has_mapped_buffers(&vcs->fb_queue)) { + dprintk("busy, buffers still mapped\n"); + return -EBUSY; + } else { + vcs->streaming = 0; + vino_queue_free(&vcs->fb_queue); + vino_queue_init(&vcs->fb_queue, &rb->count); + } + } else { + vcs->streaming = 0; + vino_capture_stop(vcs); + vino_queue_free(&vcs->fb_queue); + } + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static void vino_v4l2_get_buffer_status(struct vino_channel_settings *vcs, + struct vino_framebuffer *fb, + struct v4l2_buffer *b) +{ + if (vino_queue_outgoing_contains(&vcs->fb_queue, + fb->id)) { + b->flags &= ~V4L2_BUF_FLAG_QUEUED; + b->flags |= V4L2_BUF_FLAG_DONE; + } else if (vino_queue_incoming_contains(&vcs->fb_queue, + fb->id)) { + b->flags &= ~V4L2_BUF_FLAG_DONE; + b->flags |= V4L2_BUF_FLAG_QUEUED; + } else { + b->flags &= ~(V4L2_BUF_FLAG_DONE | + V4L2_BUF_FLAG_QUEUED); + } + + b->flags &= ~(V4L2_BUF_FLAG_TIMECODE); + + if (fb->map_count > 0) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + b->index = fb->id; + b->memory = (vcs->fb_queue.type == VINO_MEMORY_MMAP) ? + V4L2_MEMORY_MMAP : V4L2_MEMORY_USERPTR; + b->m.offset = fb->offset; + b->bytesused = fb->data_size; + b->length = fb->size; + b->field = V4L2_FIELD_INTERLACED; + b->sequence = fb->frame_counter; + memcpy(&b->timestamp, &fb->timestamp, + sizeof(struct timeval)); + // b->input ? + + dprintk("buffer %d: length = %d, bytesused = %d, offset = %d\n", + fb->id, fb->size, fb->data_size, fb->offset); +} + +static int vino_v4l2_querybuf(struct vino_channel_settings *vcs, + struct v4l2_buffer *b) +{ + if (vcs->reading) + return -EBUSY; + + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct vino_framebuffer *fb; + + // TODO: check queue type + if (b->index >= vino_queue_get_length(&vcs->fb_queue)) { + dprintk("invalid index = %d\n", + b->index); + return -EINVAL; + } + + fb = vino_queue_get_buffer(&vcs->fb_queue, + b->index); + if (fb == NULL) { + dprintk("vino_queue_get_buffer() failed"); + return -EINVAL; + } + + vino_v4l2_get_buffer_status(vcs, fb, b); + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_qbuf(struct vino_channel_settings *vcs, + struct v4l2_buffer *b) +{ + if (vcs->reading) + return -EBUSY; + + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct vino_framebuffer *fb; + int ret; + + // TODO: check queue type + if (b->memory != V4L2_MEMORY_MMAP) { + dprintk("type not mmap\n"); + return -EINVAL; + } + + fb = vino_capture_enqueue(vcs, b->index); + if (fb == NULL) + return -EINVAL; + + vino_v4l2_get_buffer_status(vcs, fb, b); + + if (vcs->streaming) { + ret = vino_capture_next(vcs, 1); + if (ret) + return ret; + } + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_dqbuf(struct vino_channel_settings *vcs, + struct v4l2_buffer *b, + unsigned int nonblocking) +{ + if (vcs->reading) + return -EBUSY; + + switch (b->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct vino_framebuffer *fb; + unsigned int incoming, outgoing; + int err; + + // TODO: check queue type + + err = vino_queue_get_incoming(&vcs->fb_queue, &incoming); + if (err) { + dprintk("vino_queue_get_incoming() failed\n"); + return -EINVAL; + } + err = vino_queue_get_outgoing(&vcs->fb_queue, &outgoing); + if (err) { + dprintk("vino_queue_get_outgoing() failed\n"); + return -EINVAL; + } + + dprintk("incoming = %d, outgoing = %d\n", incoming, outgoing); + + if (outgoing == 0) { + if (incoming == 0) { + dprintk("no incoming or outgoing buffers\n"); + return -EINVAL; + } + if (nonblocking) { + dprintk("non-blocking I/O was selected and " + "there are no buffers to dequeue\n"); + return -EAGAIN; + } + + err = vino_wait_for_frame(vcs); + if (err) { + err = vino_wait_for_frame(vcs); + if (err) { + /* interrupted or + * no frames captured because + * of frame skipping */ + // vino_capture_failed(vcs); + return -EIO; + } + } + } + + fb = vino_queue_remove(&vcs->fb_queue, &b->index); + if (fb == NULL) { + dprintk("vino_queue_remove() failed\n"); + return -EINVAL; + } + + err = vino_check_buffer(vcs, fb); + + vino_v4l2_get_buffer_status(vcs, fb, b); + + if (err) + return -EIO; + + break; + } + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + default: + return -EINVAL; + } + + return 0; +} + +static int vino_v4l2_streamon(struct vino_channel_settings *vcs) +{ + unsigned int incoming; + int ret; + if (vcs->reading) + return -EBUSY; + + if (vcs->streaming) + return 0; + + // TODO: check queue type + + if (vino_queue_get_length(&vcs->fb_queue) < 1) { + dprintk("no buffers allocated\n"); + return -EINVAL; + } + + ret = vino_queue_get_incoming(&vcs->fb_queue, &incoming); + if (ret) { + dprintk("vino_queue_get_incoming() failed\n"); + return -EINVAL; + } + + vcs->streaming = 1; + + if (incoming > 0) { + ret = vino_capture_next(vcs, 1); + if (ret) { + vcs->streaming = 0; + + dprintk("couldn't start capture\n"); + return -EINVAL; + } + } + + return 0; +} + +static int vino_v4l2_streamoff(struct vino_channel_settings *vcs) +{ + if (vcs->reading) + return -EBUSY; + + if (!vcs->streaming) + return 0; + + vcs->streaming = 0; + vino_capture_stop(vcs); + + return 0; +} + +static int vino_v4l2_queryctrl(struct vino_channel_settings *vcs, + struct v4l2_queryctrl *queryctrl) +{ + unsigned long flags; + int i; + int err = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + switch (vcs->input) { + case VINO_INPUT_D1: + for (i = 0; i < VINO_INDYCAM_V4L2_CONTROL_COUNT; i++) { + if (vino_indycam_v4l2_controls[i].id == + queryctrl->id) { + memcpy(queryctrl, + &vino_indycam_v4l2_controls[i], + sizeof(struct v4l2_queryctrl)); + queryctrl->reserved[0] = 0; + goto found; + } + } + + err = -EINVAL; + break; + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: + for (i = 0; i < VINO_SAA7191_V4L2_CONTROL_COUNT; i++) { + if (vino_saa7191_v4l2_controls[i].id == + queryctrl->id) { + memcpy(queryctrl, + &vino_saa7191_v4l2_controls[i], + sizeof(struct v4l2_queryctrl)); + queryctrl->reserved[0] = 0; + goto found; + } + } + + err = -EINVAL; + break; + default: + err = -EINVAL; + } + + found: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return err; +} + +static int vino_v4l2_g_ctrl(struct vino_channel_settings *vcs, + struct v4l2_control *control) +{ + unsigned long flags; + int i; + int err = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + switch (vcs->input) { + case VINO_INPUT_D1: { + struct indycam_control indycam_ctrl; + + for (i = 0; i < VINO_INDYCAM_V4L2_CONTROL_COUNT; i++) { + if (vino_indycam_v4l2_controls[i].id == + control->id) { + goto found1; + } + } + + err = -EINVAL; + goto out; + +found1: + indycam_ctrl.type = vino_indycam_v4l2_controls[i].reserved[0]; + + err = i2c_camera_command(DECODER_INDYCAM_GET_CONTROL, + &indycam_ctrl); + if (err) { + err = -EINVAL; + goto out; + } + + control->value = indycam_ctrl.value; + break; + } + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: { + struct saa7191_control saa7191_ctrl; + + for (i = 0; i < VINO_SAA7191_V4L2_CONTROL_COUNT; i++) { + if (vino_saa7191_v4l2_controls[i].id == + control->id) { + goto found2; + } + } + + err = -EINVAL; + goto out; + +found2: + saa7191_ctrl.type = vino_saa7191_v4l2_controls[i].reserved[0]; + + err = i2c_decoder_command(DECODER_SAA7191_GET_CONTROL, + &saa7191_ctrl); + if (err) { + err = -EINVAL; + goto out; + } + + control->value = saa7191_ctrl.value; + break; + } + default: + err = -EINVAL; + } + +out: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return err; +} + +static int vino_v4l2_s_ctrl(struct vino_channel_settings *vcs, + struct v4l2_control *control) +{ + unsigned long flags; + int i; + int err = 0; + + spin_lock_irqsave(&vino_drvdata->input_lock, flags); + + if (!vino_is_input_owner(vcs)) { + err = -EBUSY; + goto out; + } + + switch (vcs->input) { + case VINO_INPUT_D1: { + struct indycam_control indycam_ctrl; + + for (i = 0; i < VINO_INDYCAM_V4L2_CONTROL_COUNT; i++) { + if (vino_indycam_v4l2_controls[i].id == + control->id) { + if ((control->value >= + vino_indycam_v4l2_controls[i].minimum) + && (control->value <= + vino_indycam_v4l2_controls[i]. + maximum)) { + goto found1; + } else { + err = -ERANGE; + goto out; + } + } + } + + err = -EINVAL; + goto out; + +found1: + indycam_ctrl.type = vino_indycam_v4l2_controls[i].reserved[0]; + indycam_ctrl.value = control->value; + + err = i2c_camera_command(DECODER_INDYCAM_SET_CONTROL, + &indycam_ctrl); + if (err) + err = -EINVAL; + break; + } + case VINO_INPUT_COMPOSITE: + case VINO_INPUT_SVIDEO: { + struct saa7191_control saa7191_ctrl; + + for (i = 0; i < VINO_SAA7191_V4L2_CONTROL_COUNT; i++) { + if (vino_saa7191_v4l2_controls[i].id == + control->id) { + if ((control->value >= + vino_saa7191_v4l2_controls[i].minimum) + && (control->value <= + vino_saa7191_v4l2_controls[i]. + maximum)) { + goto found2; + } else { + err = -ERANGE; + goto out; + } + } + } + err = -EINVAL; + goto out; + +found2: + saa7191_ctrl.type = vino_saa7191_v4l2_controls[i].reserved[0]; + saa7191_ctrl.value = control->value; + + err = i2c_decoder_command(DECODER_SAA7191_SET_CONTROL, + &saa7191_ctrl); + if (err) + err = -EINVAL; + break; + } + default: + err = -EINVAL; + } + +out: + spin_unlock_irqrestore(&vino_drvdata->input_lock, flags); + + return err; +} + +/* File operations */ + +static int vino_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + int ret = 0; + dprintk("open(): channel = %c\n", + (vcs->channel == VINO_CHANNEL_A) ? 'A' : 'B'); + + mutex_lock(&vcs->mutex); + + if (vcs->users) { + dprintk("open(): driver busy\n"); + ret = -EBUSY; + goto out; + } + + ret = vino_acquire_input(vcs); + if (ret) { + dprintk("open(): vino_acquire_input() failed\n"); + goto out; + } + + vcs->users++; + + out: + mutex_unlock(&vcs->mutex); + + dprintk("open(): %s!\n", ret ? "failed" : "complete"); + + return ret; +} + +static int vino_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + dprintk("close():\n"); + + mutex_lock(&vcs->mutex); + + vcs->users--; + + if (!vcs->users) { + vino_release_input(vcs); + + /* stop DMA and free buffers */ + vino_capture_stop(vcs); + vino_queue_free(&vcs->fb_queue); + } + + mutex_unlock(&vcs->mutex); + + return 0; +} + +static void vino_vm_open(struct vm_area_struct *vma) +{ + struct vino_framebuffer *fb = vma->vm_private_data; + + fb->map_count++; + dprintk("vino_vm_open(): count = %d\n", fb->map_count); +} + +static void vino_vm_close(struct vm_area_struct *vma) +{ + struct vino_framebuffer *fb = vma->vm_private_data; + + fb->map_count--; + dprintk("vino_vm_close(): count = %d\n", fb->map_count); +} + +static struct vm_operations_struct vino_vm_ops = { + .open = vino_vm_open, + .close = vino_vm_close, +}; + +static int vino_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + + struct vino_framebuffer *fb = NULL; + unsigned int i, length; + int ret = 0; + + dprintk("mmap():\n"); + + // TODO: reject mmap if already mapped + + if (mutex_lock_interruptible(&vcs->mutex)) + return -EINTR; + + if (vcs->reading) { + ret = -EBUSY; + goto out; + } + + // TODO: check queue type + + if (!(vma->vm_flags & VM_WRITE)) { + dprintk("mmap(): app bug: PROT_WRITE please\n"); + ret = -EINVAL; + goto out; + } + if (!(vma->vm_flags & VM_SHARED)) { + dprintk("mmap(): app bug: MAP_SHARED please\n"); + ret = -EINVAL; + goto out; + } + + /* find the correct buffer using offset */ + length = vino_queue_get_length(&vcs->fb_queue); + if (length == 0) { + dprintk("mmap(): queue not initialized\n"); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < length; i++) { + fb = vino_queue_get_buffer(&vcs->fb_queue, i); + if (fb == NULL) { + dprintk("mmap(): vino_queue_get_buffer() failed\n"); + ret = -EINVAL; + goto out; + } + + if (fb->offset == offset) + goto found; + } + + dprintk("mmap(): invalid offset = %lu\n", offset); + ret = -EINVAL; + goto out; + +found: + dprintk("mmap(): buffer = %d\n", i); + + if (size > (fb->desc_table.page_count * PAGE_SIZE)) { + dprintk("mmap(): failed: size = %lu > %lu\n", + size, fb->desc_table.page_count * PAGE_SIZE); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < fb->desc_table.page_count; i++) { + unsigned long pfn = + virt_to_phys((void *)fb->desc_table.virtual[i]) >> + PAGE_SHIFT; + + if (size < PAGE_SIZE) + break; + + // protection was: PAGE_READONLY + if (remap_pfn_range(vma, start, pfn, PAGE_SIZE, + vma->vm_page_prot)) { + dprintk("mmap(): remap_pfn_range() failed\n"); + ret = -EAGAIN; + goto out; + } + + start += PAGE_SIZE; + size -= PAGE_SIZE; + } + + fb->map_count = 1; + + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_flags &= ~VM_IO; + vma->vm_private_data = fb; + vma->vm_file = file; + vma->vm_ops = &vino_vm_ops; + +out: + mutex_unlock(&vcs->mutex); + + return ret; +} + +static unsigned int vino_poll(struct file *file, poll_table *pt) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + unsigned int outgoing; + unsigned int ret = 0; + + // lock mutex (?) + // TODO: this has to be corrected for different read modes + + dprintk("poll():\n"); + + if (vino_queue_get_outgoing(&vcs->fb_queue, &outgoing)) { + dprintk("poll(): vino_queue_get_outgoing() failed\n"); + ret = POLLERR; + goto error; + } + if (outgoing > 0) + goto over; + + poll_wait(file, &vcs->fb_queue.frame_wait_queue, pt); + + if (vino_queue_get_outgoing(&vcs->fb_queue, &outgoing)) { + dprintk("poll(): vino_queue_get_outgoing() failed\n"); + ret = POLLERR; + goto error; + } + +over: + dprintk("poll(): data %savailable\n", + (outgoing > 0) ? "" : "not "); + + if (outgoing > 0) + ret = POLLIN | POLLRDNORM; + +error: + + return ret; +} + +static int vino_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + +#ifdef VINO_DEBUG + switch (_IOC_TYPE(cmd)) { + case 'v': + dprintk("ioctl(): V4L1 unsupported (0x%08x)\n", cmd); + break; + case 'V': + dprintk("ioctl(): V4L2 %s (0x%08x)\n", + v4l2_ioctl_names[_IOC_NR(cmd)], cmd); + break; + default: + dprintk("ioctl(): unsupported command 0x%08x\n", cmd); + } +#endif + + switch (cmd) { + /* V4L2 interface */ + case VIDIOC_QUERYCAP: { + vino_v4l2_querycap(arg); + break; + } + case VIDIOC_ENUMINPUT: { + return vino_v4l2_enuminput(vcs, arg); + } + case VIDIOC_G_INPUT: { + return vino_v4l2_g_input(vcs, arg); + } + case VIDIOC_S_INPUT: { + return vino_v4l2_s_input(vcs, arg); + } + case VIDIOC_ENUMSTD: { + return vino_v4l2_enumstd(vcs, arg); + } + case VIDIOC_QUERYSTD: { + return vino_v4l2_querystd(vcs, arg); + } + case VIDIOC_G_STD: { + return vino_v4l2_g_std(vcs, arg); + } + case VIDIOC_S_STD: { + return vino_v4l2_s_std(vcs, arg); + } + case VIDIOC_ENUM_FMT: { + return vino_v4l2_enum_fmt(vcs, arg); + } + case VIDIOC_TRY_FMT: { + return vino_v4l2_try_fmt(vcs, arg); + } + case VIDIOC_G_FMT: { + return vino_v4l2_g_fmt(vcs, arg); + } + case VIDIOC_S_FMT: { + return vino_v4l2_s_fmt(vcs, arg); + } + case VIDIOC_CROPCAP: { + return vino_v4l2_cropcap(vcs, arg); + } + case VIDIOC_G_CROP: { + return vino_v4l2_g_crop(vcs, arg); + } + case VIDIOC_S_CROP: { + return vino_v4l2_s_crop(vcs, arg); + } + case VIDIOC_G_PARM: { + return vino_v4l2_g_parm(vcs, arg); + } + case VIDIOC_S_PARM: { + return vino_v4l2_s_parm(vcs, arg); + } + case VIDIOC_REQBUFS: { + return vino_v4l2_reqbufs(vcs, arg); + } + case VIDIOC_QUERYBUF: { + return vino_v4l2_querybuf(vcs, arg); + } + case VIDIOC_QBUF: { + return vino_v4l2_qbuf(vcs, arg); + } + case VIDIOC_DQBUF: { + return vino_v4l2_dqbuf(vcs, arg, file->f_flags & O_NONBLOCK); + } + case VIDIOC_STREAMON: { + return vino_v4l2_streamon(vcs); + } + case VIDIOC_STREAMOFF: { + return vino_v4l2_streamoff(vcs); + } + case VIDIOC_QUERYCTRL: { + return vino_v4l2_queryctrl(vcs, arg); + } + case VIDIOC_G_CTRL: { + return vino_v4l2_g_ctrl(vcs, arg); + } + case VIDIOC_S_CTRL: { + return vino_v4l2_s_ctrl(vcs, arg); + } + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int vino_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct video_device *dev = video_devdata(file); + struct vino_channel_settings *vcs = video_get_drvdata(dev); + int ret; + + if (mutex_lock_interruptible(&vcs->mutex)) + return -EINTR; + + ret = video_usercopy(inode, file, cmd, arg, vino_do_ioctl); + + mutex_unlock(&vcs->mutex); + + return ret; +} + +/* Initialization and cleanup */ + +// __initdata +static int vino_init_stage = 0; + +static struct file_operations vino_fops = { + .owner = THIS_MODULE, + .open = vino_open, + .release = vino_close, + .ioctl = vino_ioctl, + .mmap = vino_mmap, + .poll = vino_poll, + .llseek = no_llseek, +}; + +static struct video_device v4l_device_template = { + .name = "NOT SET", + //.type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE | + // VID_TYPE_CLIPPING | VID_TYPE_SCALES, VID_TYPE_OVERLAY + .hardware = VID_HARDWARE_VINO, + .fops = &vino_fops, + .minor = -1, +}; + +static void vino_module_cleanup(int stage) +{ + switch(stage) { + case 10: + video_unregister_device(vino_drvdata->b.v4l_device); + vino_drvdata->b.v4l_device = NULL; + case 9: + video_unregister_device(vino_drvdata->a.v4l_device); + vino_drvdata->a.v4l_device = NULL; + case 8: + vino_i2c_del_bus(); + case 7: + free_irq(SGI_VINO_IRQ, NULL); + case 6: + if (vino_drvdata->b.v4l_device) { + video_device_release(vino_drvdata->b.v4l_device); + vino_drvdata->b.v4l_device = NULL; + } + case 5: + if (vino_drvdata->a.v4l_device) { + video_device_release(vino_drvdata->a.v4l_device); + vino_drvdata->a.v4l_device = NULL; + } + case 4: + /* all entries in dma_cpu dummy table have the same address */ + dma_unmap_single(NULL, + vino_drvdata->dummy_desc_table.dma_cpu[0], + PAGE_SIZE, DMA_FROM_DEVICE); + dma_free_coherent(NULL, VINO_DUMMY_DESC_COUNT + * sizeof(dma_addr_t), + (void *)vino_drvdata-> + dummy_desc_table.dma_cpu, + vino_drvdata->dummy_desc_table.dma); + case 3: + free_page(vino_drvdata->dummy_page); + case 2: + kfree(vino_drvdata); + case 1: + iounmap(vino); + case 0: + break; + default: + dprintk("vino_module_cleanup(): invalid cleanup stage = %d\n", + stage); + } +} + +static int vino_probe(void) +{ + unsigned long rev_id; + + if (ip22_is_fullhouse()) { + printk(KERN_ERR "VINO doesn't exist in IP22 Fullhouse\n"); + return -ENODEV; + } + + if (!(sgimc->systemid & SGIMC_SYSID_EPRESENT)) { + printk(KERN_ERR "VINO is not found (EISA BUS not present)\n"); + return -ENODEV; + } + + vino = (struct sgi_vino *)ioremap(VINO_BASE, sizeof(struct sgi_vino)); + if (!vino) { + printk(KERN_ERR "VINO: ioremap() failed\n"); + return -EIO; + } + vino_init_stage++; + + if (get_dbe(rev_id, &(vino->rev_id))) { + printk(KERN_ERR "Failed to read VINO revision register\n"); + vino_module_cleanup(vino_init_stage); + return -ENODEV; + } + + if (VINO_ID_VALUE(rev_id) != VINO_CHIP_ID) { + printk(KERN_ERR "Unknown VINO chip ID (Rev/ID: 0x%02lx)\n", + rev_id); + vino_module_cleanup(vino_init_stage); + return -ENODEV; + } + + printk(KERN_INFO "VINO revision %ld found\n", VINO_REV_NUM(rev_id)); + + return 0; +} + +static int vino_init(void) +{ + dma_addr_t dma_dummy_address; + int i; + + vino_drvdata = kzalloc(sizeof(struct vino_settings), GFP_KERNEL); + if (!vino_drvdata) { + vino_module_cleanup(vino_init_stage); + return -ENOMEM; + } + vino_init_stage++; + + /* create a dummy dma descriptor */ + vino_drvdata->dummy_page = get_zeroed_page(GFP_KERNEL | GFP_DMA); + if (!vino_drvdata->dummy_page) { + vino_module_cleanup(vino_init_stage); + return -ENOMEM; + } + vino_init_stage++; + + // TODO: use page_count in dummy_desc_table + + vino_drvdata->dummy_desc_table.dma_cpu = + dma_alloc_coherent(NULL, + VINO_DUMMY_DESC_COUNT * sizeof(dma_addr_t), + &vino_drvdata->dummy_desc_table.dma, + GFP_KERNEL | GFP_DMA); + if (!vino_drvdata->dummy_desc_table.dma_cpu) { + vino_module_cleanup(vino_init_stage); + return -ENOMEM; + } + vino_init_stage++; + + dma_dummy_address = dma_map_single(NULL, + (void *)vino_drvdata->dummy_page, + PAGE_SIZE, DMA_FROM_DEVICE); + for (i = 0; i < VINO_DUMMY_DESC_COUNT; i++) { + vino_drvdata->dummy_desc_table.dma_cpu[i] = dma_dummy_address; + } + + /* initialize VINO */ + + vino->control = 0; + vino->a.next_4_desc = vino_drvdata->dummy_desc_table.dma; + vino->b.next_4_desc = vino_drvdata->dummy_desc_table.dma; + udelay(VINO_DESC_FETCH_DELAY); + + vino->intr_status = 0; + + vino->a.fifo_thres = VINO_FIFO_THRESHOLD_DEFAULT; + vino->b.fifo_thres = VINO_FIFO_THRESHOLD_DEFAULT; + + return 0; +} + +static int vino_init_channel_settings(struct vino_channel_settings *vcs, + unsigned int channel, const char *name) +{ + vcs->channel = channel; + vcs->input = VINO_INPUT_NONE; + vcs->alpha = 0; + vcs->users = 0; + vcs->data_format = VINO_DATA_FMT_GREY; + vcs->data_norm = VINO_DATA_NORM_NTSC; + vcs->decimation = 1; + vino_set_default_clipping(vcs); + vino_set_default_framerate(vcs); + + vcs->capturing = 0; + + mutex_init(&vcs->mutex); + spin_lock_init(&vcs->capture_lock); + + mutex_init(&vcs->fb_queue.queue_mutex); + spin_lock_init(&vcs->fb_queue.queue_lock); + init_waitqueue_head(&vcs->fb_queue.frame_wait_queue); + + vcs->v4l_device = video_device_alloc(); + if (!vcs->v4l_device) { + vino_module_cleanup(vino_init_stage); + return -ENOMEM; + } + vino_init_stage++; + + memcpy(vcs->v4l_device, &v4l_device_template, + sizeof(struct video_device)); + strcpy(vcs->v4l_device->name, name); + vcs->v4l_device->release = video_device_release; + + video_set_drvdata(vcs->v4l_device, vcs); + + return 0; +} + +static int __init vino_module_init(void) +{ + int ret; + + printk(KERN_INFO "SGI VINO driver version %s\n", + VINO_MODULE_VERSION); + + ret = vino_probe(); + if (ret) + return ret; + + ret = vino_init(); + if (ret) + return ret; + + /* initialize data structures */ + + spin_lock_init(&vino_drvdata->vino_lock); + spin_lock_init(&vino_drvdata->input_lock); + + ret = vino_init_channel_settings(&vino_drvdata->a, VINO_CHANNEL_A, + vino_v4l_device_name_a); + if (ret) + return ret; + + ret = vino_init_channel_settings(&vino_drvdata->b, VINO_CHANNEL_B, + vino_v4l_device_name_b); + if (ret) + return ret; + + /* initialize hardware and register V4L devices */ + + ret = request_irq(SGI_VINO_IRQ, vino_interrupt, 0, + vino_driver_description, NULL); + if (ret) { + printk(KERN_ERR "VINO: requesting IRQ %02d failed\n", + SGI_VINO_IRQ); + vino_module_cleanup(vino_init_stage); + return -EAGAIN; + } + vino_init_stage++; + + ret = vino_i2c_add_bus(); + if (ret) { + printk(KERN_ERR "VINO I2C bus registration failed\n"); + vino_module_cleanup(vino_init_stage); + return ret; + } + vino_init_stage++; + + ret = video_register_device(vino_drvdata->a.v4l_device, + VFL_TYPE_GRABBER, -1); + if (ret < 0) { + printk(KERN_ERR "VINO channel A Video4Linux-device " + "registration failed\n"); + vino_module_cleanup(vino_init_stage); + return -EINVAL; + } + vino_init_stage++; + + ret = video_register_device(vino_drvdata->b.v4l_device, + VFL_TYPE_GRABBER, -1); + if (ret < 0) { + printk(KERN_ERR "VINO channel B Video4Linux-device " + "registration failed\n"); + vino_module_cleanup(vino_init_stage); + return -EINVAL; + } + vino_init_stage++; + +#if defined(CONFIG_KMOD) && defined(MODULE) + request_module("saa7191"); + request_module("indycam"); +#endif + + dprintk("init complete!\n"); + + return 0; +} + +static void __exit vino_module_exit(void) +{ + dprintk("exiting, stage = %d ...\n", vino_init_stage); + vino_module_cleanup(vino_init_stage); + dprintk("cleanup complete, exit!\n"); +} + +module_init(vino_module_init); +module_exit(vino_module_exit); diff --git a/linux/drivers/media/video/vino.h b/linux/drivers/media/video/vino.h new file mode 100644 index 000000000..de2d615ae --- /dev/null +++ b/linux/drivers/media/video/vino.h @@ -0,0 +1,138 @@ +/* + * Driver for the VINO (Video In No Out) system found in SGI Indys. + * + * This file is subject to the terms and conditions of the GNU General Public + * License version 2 as published by the Free Software Foundation. + * + * Copyright (C) 1999 Ulf Karlsson <ulfc@bun.falkenberg.se> + * Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org> + */ + +#ifndef _VINO_H_ +#define _VINO_H_ + +#define VINO_BASE 0x00080000 /* Vino is in the EISA address space, + * but it is not an EISA bus card */ +#define VINO_PAGE_SIZE 4096 + +struct sgi_vino_channel { + u32 _pad_alpha; + volatile u32 alpha; + +#define VINO_CLIP_X(x) ((x) & 0x3ff) /* bits 0:9 */ +#define VINO_CLIP_ODD(x) (((x) & 0x1ff) << 10) /* bits 10:18 */ +#define VINO_CLIP_EVEN(x) (((x) & 0x1ff) << 19) /* bits 19:27 */ + u32 _pad_clip_start; + volatile u32 clip_start; + u32 _pad_clip_end; + volatile u32 clip_end; + +#define VINO_FRAMERT_FULL 0xfff +#define VINO_FRAMERT_PAL (1<<0) /* 0=NTSC 1=PAL */ +#define VINO_FRAMERT_RT(x) (((x) & 0xfff) << 1) /* bits 1:12 */ + u32 _pad_frame_rate; + volatile u32 frame_rate; + + u32 _pad_field_counter; + volatile u32 field_counter; + u32 _pad_line_size; + volatile u32 line_size; + u32 _pad_line_count; + volatile u32 line_count; + u32 _pad_page_index; + volatile u32 page_index; + u32 _pad_next_4_desc; + volatile u32 next_4_desc; + u32 _pad_start_desc_tbl; + volatile u32 start_desc_tbl; + +#define VINO_DESC_JUMP (1<<30) +#define VINO_DESC_STOP (1<<31) +#define VINO_DESC_VALID (1<<32) + u32 _pad_desc_0; + volatile u32 desc_0; + u32 _pad_desc_1; + volatile u32 desc_1; + u32 _pad_desc_2; + volatile u32 desc_2; + u32 _pad_Bdesc_3; + volatile u32 desc_3; + + u32 _pad_fifo_thres; + volatile u32 fifo_thres; + u32 _pad_fifo_read; + volatile u32 fifo_read; + u32 _pad_fifo_write; + volatile u32 fifo_write; +}; + +struct sgi_vino { +#define VINO_CHIP_ID 0xb +#define VINO_REV_NUM(x) ((x) & 0x0f) +#define VINO_ID_VALUE(x) (((x) & 0xf0) >> 4) + u32 _pad_rev_id; + volatile u32 rev_id; + +#define VINO_CTRL_LITTLE_ENDIAN (1<<0) +#define VINO_CTRL_A_EOF_INT (1<<1) /* Field transferred int */ +#define VINO_CTRL_A_FIFO_INT (1<<2) /* FIFO overflow int */ +#define VINO_CTRL_A_EOD_INT (1<<3) /* End of desc table int */ +#define VINO_CTRL_A_INT (VINO_CTRL_A_EOF_INT | \ + VINO_CTRL_A_FIFO_INT | \ + VINO_CTRL_A_EOD_INT) +#define VINO_CTRL_B_EOF_INT (1<<4) /* Field transferred int */ +#define VINO_CTRL_B_FIFO_INT (1<<5) /* FIFO overflow int */ +#define VINO_CTRL_B_EOD_INT (1<<6) /* End of desc table int */ +#define VINO_CTRL_B_INT (VINO_CTRL_B_EOF_INT | \ + VINO_CTRL_B_FIFO_INT | \ + VINO_CTRL_B_EOD_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_SCALE_SHIFT (14) +#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<<23) /* 1=RGB 0=YUV */ +#define VINO_CTRL_B_LUMA_ONLY (1<<24) +#define VINO_CTRL_B_DEC_ENBL (1<<25) /* Decimation */ +#define VINO_CTRL_B_DEC_SCALE_MASK 0x1c000000 /* bits 26:28 */ +#define VINO_CTRL_B_DEC_SCALE_SHIFT (26) +#define VINO_CTRL_B_DEC_HOR_ONLY (1<<29) /* Decimation horizontal only */ +#define VINO_CTRL_B_DITHER (1<<30) /* ChanB 24 -> 8 bit dither */ + u32 _pad_control; + volatile u32 control; + +#define VINO_INTSTAT_A_EOF (1<<0) /* Field transferred int */ +#define VINO_INTSTAT_A_FIFO (1<<1) /* FIFO overflow int */ +#define VINO_INTSTAT_A_EOD (1<<2) /* End of desc table int */ +#define VINO_INTSTAT_A (VINO_INTSTAT_A_EOF | \ + VINO_INTSTAT_A_FIFO | \ + VINO_INTSTAT_A_EOD) +#define VINO_INTSTAT_B_EOF (1<<3) /* Field transferred int */ +#define VINO_INTSTAT_B_FIFO (1<<4) /* FIFO overflow int */ +#define VINO_INTSTAT_B_EOD (1<<5) /* End of desc table int */ +#define VINO_INTSTAT_B (VINO_INTSTAT_B_EOF | \ + VINO_INTSTAT_B_FIFO | \ + VINO_INTSTAT_B_EOD) + u32 _pad_intr_status; + volatile u32 intr_status; + + u32 _pad_i2c_control; + volatile u32 i2c_control; + u32 _pad_i2c_data; + volatile u32 i2c_data; + + struct sgi_vino_channel a; + struct sgi_vino_channel b; +}; + +#endif diff --git a/linux/drivers/media/video/vpx3220.c b/linux/drivers/media/video/vpx3220.c new file mode 100644 index 000000000..a34b47326 --- /dev/null +++ b/linux/drivers/media/video/vpx3220.c @@ -0,0 +1,749 @@ +/* + * vpx3220a, vpx3216b & vpx3214c video decoder driver version 0.0.1 + * + * Copyright (C) 2001 Laurent Pinchart <lpinchart@freegates.be> + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/slab.h> + +#include <linux/byteorder/swab.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/i2c.h> + +#define I2C_NAME(x) (x)->name + +#include "compat.h" +#include <linux/videodev.h> +#include <linux/video_decoder.h> + +#define I2C_VPX3220 0x86 +#define VPX3220_DEBUG KERN_DEBUG "vpx3220: " + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +#define VPX_TIMEOUT_COUNT 10 + +/* ----------------------------------------------------------------------- */ + +struct vpx3220 { + unsigned char reg[255]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +static char *inputs[] = { "internal", "composite", "svideo" }; + +/* ----------------------------------------------------------------------- */ +static inline int +vpx3220_write (struct i2c_client *client, + u8 reg, + u8 value) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + + decoder->reg[reg] = value; + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int +vpx3220_read (struct i2c_client *client, + u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int +vpx3220_fp_status (struct i2c_client *client) +{ + unsigned char status; + unsigned int i; + + for (i = 0; i < VPX_TIMEOUT_COUNT; i++) { + status = vpx3220_read(client, 0x29); + + if (!(status & 4)) + return 0; + + udelay(10); + + if (need_resched()) + cond_resched(); + } + + return -1; +} + +static int +vpx3220_fp_write (struct i2c_client *client, + u8 fpaddr, + u16 data) +{ + /* Write the 16-bit address to the FPWR register */ + if (i2c_smbus_write_word_data(client, 0x27, swab16(fpaddr)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(client) < 0) + return -1; + + /* Write the 16-bit data to the FPDAT register */ + if (i2c_smbus_write_word_data(client, 0x28, swab16(data)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + return 0; +} + +static u16 +vpx3220_fp_read (struct i2c_client *client, + u16 fpaddr) +{ + s16 data; + + /* Write the 16-bit address to the FPRD register */ + if (i2c_smbus_write_word_data(client, 0x26, swab16(fpaddr)) == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + if (vpx3220_fp_status(client) < 0) + return -1; + + /* Read the 16-bit data from the FPDAT register */ + data = i2c_smbus_read_word_data(client, 0x28); + if (data == -1) { + dprintk(1, VPX3220_DEBUG "%s: failed\n", __func__); + return -1; + } + + return swab16(data); +} + +static int +vpx3220_write_block (struct i2c_client *client, + const u8 *data, + unsigned int len) +{ + u8 reg; + int ret = -1; + + while (len >= 2) { + reg = *data++; + if ((ret = + vpx3220_write(client, reg, *data++)) < 0) + break; + len -= 2; + } + + return ret; +} + +static int +vpx3220_write_fp_block (struct i2c_client *client, + const u16 *data, + unsigned int len) +{ + u8 reg; + int ret = 0; + + while (len > 1) { + reg = *data++; + ret |= vpx3220_fp_write(client, reg, *data++); + len -= 2; + } + + return ret; +} + +/* ---------------------------------------------------------------------- */ + +static const unsigned short init_ntsc[] = { + 0x1c, 0x00, /* NTSC tint angle */ + 0x88, 17, /* Window 1 vertical */ + 0x89, 240, /* Vertical lines in */ + 0x8a, 240, /* Vertical lines out */ + 0x8b, 000, /* Horizontal begin */ + 0x8c, 640, /* Horizontal length */ + 0x8d, 640, /* Number of pixels */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x73, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x13, /* NTSC M, composite input */ + 0xe7, 0x1e1, /* Enable vertical standard + * locking @ 240 lines */ +}; + +static const unsigned short init_pal[] = { + 0x88, 23, /* Window 1 vertical begin */ + 0x89, 288, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x77, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d1, /* PAL B,G,H,I, composite input */ + 0xe7, 0x241, /* PAL/SECAM set to 288 lines */ +}; + +static const unsigned short init_secam[] = { + 0x88, 23, /* Window 1 vertical begin */ + 0x89, 288, /* Vertical lines in (16 lines + * skipped by the VFE) */ + 0x8a, 288, /* Vertical lines out (16 lines + * skipped by the VFE) */ + 0x8b, 16, /* Horizontal begin */ + 0x8c, 768, /* Horizontal length */ + 0x8d, 784, /* Number of pixels + * Must be >= Horizontal begin + Horizontal length */ + 0x8f, 0xc00, /* Disable window 2 */ + 0xf0, 0x77, /* 13.5 MHz transport, Forced + * mode, latch windows */ + 0xf2, 0x3d5, /* SECAM, composite input */ + 0xe7, 0x241, /* PAL/SECAM set to 288 lines */ +}; + +static const unsigned char init_common[] = { + 0xf2, 0x00, /* Disable all outputs */ + 0x33, 0x0d, /* Luma : VIN2, Chroma : CIN + * (clamp off) */ + 0xd8, 0xa8, /* HREF/VREF active high, VREF + * pulse = 2, Odd/Even flag */ + 0x20, 0x03, /* IF compensation 0dB/oct */ + 0xe0, 0xff, /* Open up all comparators */ + 0xe1, 0x00, + 0xe2, 0x7f, + 0xe3, 0x80, + 0xe4, 0x7f, + 0xe5, 0x80, + 0xe6, 0x00, /* Brightness set to 0 */ + 0xe7, 0xe0, /* Contrast to 1.0, noise shaping + * 10 to 8 2-bit error diffusion */ + 0xe8, 0xf8, /* YUV422, CbCr binary offset, + * ... (p.32) */ + 0xea, 0x18, /* LLC2 connected, output FIFO + * reset with VACTintern */ + 0xf0, 0x8a, /* Half full level to 10, bus + * shuffler [7:0, 23:16, 15:8] */ + 0xf1, 0x18, /* Single clock, sync mode, no + * FE delay, no HLEN counter */ + 0xf8, 0x12, /* Port A, PIXCLK, HF# & FE# + * strength to 2 */ + 0xf9, 0x24, /* Port B, HREF, VREF, PREF & + * ALPHA strength to 4 */ +}; + +static const unsigned short init_fp[] = { + 0x59, 0, + 0xa0, 2070, /* ACC reference */ + 0xa3, 0, + 0xa4, 0, + 0xa8, 30, + 0xb2, 768, + 0xbe, 27, + 0x58, 0, + 0x26, 0, + 0x4b, 0x298, /* PLL gain */ +}; + +static void +vpx3220_dump_i2c (struct i2c_client *client) +{ + int len = sizeof(init_common); + const unsigned char *data = init_common; + + while (len > 1) { + dprintk(1, + KERN_DEBUG "vpx3216b i2c reg 0x%02x data 0x%02x\n", + *data, vpx3220_read(client, *data)); + data += 2; + len -= 2; + } +} + +static int +vpx3220_command (struct i2c_client *client, + unsigned int cmd, + void *arg) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + + switch (cmd) { + case 0: + { + vpx3220_write_block(client, init_common, + sizeof(init_common)); + vpx3220_write_fp_block(client, init_fp, + sizeof(init_fp) >> 1); + switch (decoder->norm) { + + case VIDEO_MODE_NTSC: + vpx3220_write_fp_block(client, init_ntsc, + sizeof(init_ntsc) >> 1); + break; + + case VIDEO_MODE_PAL: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + break; + case VIDEO_MODE_SECAM: + vpx3220_write_fp_block(client, init_secam, + sizeof(init_secam) >> 1); + break; + default: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + break; + } + } + break; + + case DECODER_DUMP: + { + vpx3220_dump_i2c(client); + } + break; + + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + dprintk(1, KERN_DEBUG "%s: DECODER_GET_CAPABILITIES\n", + I2C_NAME(client)); + + cap->flags = VIDEO_DECODER_PAL | + VIDEO_DECODER_NTSC | + VIDEO_DECODER_SECAM | + VIDEO_DECODER_AUTO | + VIDEO_DECODER_CCIR; + cap->inputs = 3; + cap->outputs = 1; + } + break; + + case DECODER_GET_STATUS: + { + int res = 0, status; + + dprintk(1, KERN_INFO "%s: DECODER_GET_STATUS\n", + I2C_NAME(client)); + + status = vpx3220_fp_read(client, 0x0f3); + + dprintk(1, KERN_INFO "%s: status: 0x%04x\n", I2C_NAME(client), + status); + + if (status < 0) + return status; + + if ((status & 0x20) == 0) { + res |= DECODER_STATUS_GOOD | DECODER_STATUS_COLOR; + + switch (status & 0x18) { + + case 0x00: + case 0x10: + case 0x14: + case 0x18: + res |= DECODER_STATUS_PAL; + break; + + case 0x08: + res |= DECODER_STATUS_SECAM; + break; + + case 0x04: + case 0x0c: + case 0x1c: + res |= DECODER_STATUS_NTSC; + break; + } + } + + *(int *) arg = res; + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg, data; + int temp_input; + + /* Here we back up the input selection because it gets + overwritten when we fill the registers with the + choosen video norm */ + temp_input = vpx3220_fp_read(client, 0xf2); + + dprintk(1, KERN_DEBUG "%s: DECODER_SET_NORM %d\n", + I2C_NAME(client), *iarg); + switch (*iarg) { + + case VIDEO_MODE_NTSC: + vpx3220_write_fp_block(client, init_ntsc, + sizeof(init_ntsc) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to NTSC\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_PAL: + vpx3220_write_fp_block(client, init_pal, + sizeof(init_pal) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to PAL\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_SECAM: + vpx3220_write_fp_block(client, init_secam, + sizeof(init_secam) >> 1); + dprintk(1, KERN_INFO "%s: norm switched to SECAM\n", + I2C_NAME(client)); + break; + + case VIDEO_MODE_AUTO: + /* FIXME This is only preliminary support */ + data = vpx3220_fp_read(client, 0xf2) & 0x20; + vpx3220_fp_write(client, 0xf2, 0x00c0 | data); + dprintk(1, KERN_INFO "%s: norm switched to Auto\n", + I2C_NAME(client)); + break; + + default: + return -EINVAL; + + } + decoder->norm = *iarg; + + /* And here we set the backed up video input again */ + vpx3220_fp_write(client, 0xf2, temp_input | 0x0010); + udelay(10); + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg, data; + + /* RJ: *iarg = 0: ST8 (PCTV) input + *iarg = 1: COMPOSITE input + *iarg = 2: SVHS input */ + + const int input[3][2] = { + {0x0c, 0}, + {0x0d, 0}, + {0x0e, 1} + }; + + if (*iarg < 0 || *iarg > 2) + return -EINVAL; + + dprintk(1, KERN_INFO "%s: input switched to %s\n", + I2C_NAME(client), inputs[*iarg]); + + vpx3220_write(client, 0x33, input[*iarg][0]); + + data = vpx3220_fp_read(client, 0xf2) & ~(0x0020); + if (data < 0) + return data; + /* 0x0010 is required to latch the setting */ + vpx3220_fp_write(client, 0xf2, + data | (input[*iarg][1] << 5) | 0x0010); + + udelay(10); + } + 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; + + dprintk(1, KERN_DEBUG "%s: DECODER_ENABLE_OUTPUT %d\n", + I2C_NAME(client), *iarg); + + vpx3220_write(client, 0xf2, (*iarg ? 0x1b : 0x00)); + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want -128 to 128 we get 0-65535 */ + decoder->bright = pic->brightness; + vpx3220_write(client, 0xe6, + (decoder->bright - 32768) >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 64 we get 0-65535 */ + /* Bit 7 and 8 is for noise shaping */ + decoder->contrast = pic->contrast; + vpx3220_write(client, 0xe7, + (decoder->contrast >> 10) + 192); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 4096 we get 0-65535 */ + decoder->sat = pic->colour; + vpx3220_fp_write(client, 0xa0, + decoder->sat >> 4); + } + if (decoder->hue != pic->hue) { + /* We want -512 to 512 we get 0-65535 */ + decoder->hue = pic->hue; + vpx3220_fp_write(client, 0x1c, + ((decoder->hue - 32768) >> 6) & 0xFFF); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +vpx3220_init_client (struct i2c_client *client) +{ + vpx3220_write_block(client, init_common, sizeof(init_common)); + vpx3220_write_fp_block(client, init_fp, sizeof(init_fp) >> 1); + /* Default to PAL */ + vpx3220_write_fp_block(client, init_pal, sizeof(init_pal) >> 1); + + return 0; +} + +/* ----------------------------------------------------------------------- + * Client managment code + */ + +/* + * Generic i2c probe + * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' + */ +static unsigned short normal_i2c[] = + { I2C_VPX3220 >> 1, (I2C_VPX3220 >> 1) + 4, + I2C_CLIENT_END +}; + +static unsigned short ignore = I2C_CLIENT_END; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = &ignore, + .ignore = &ignore, +}; + +static struct i2c_driver vpx3220_i2c_driver; + +static int +vpx3220_detach_client (struct i2c_client *client) +{ + struct vpx3220 *decoder = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(decoder); + kfree(client); + + return 0; +} + +static int +vpx3220_detect_client (struct i2c_adapter *adapter, + int address, + int kind) +{ + int err; + struct i2c_client *client; + struct vpx3220 *decoder; + + dprintk(1, VPX3220_DEBUG "%s\n", __func__); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality + (adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return 0; + + client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == NULL) { + return -ENOMEM; + } + + client->addr = address; + client->adapter = adapter; + client->driver = &vpx3220_i2c_driver; + + /* Check for manufacture ID and part number */ + if (kind < 0) { + u8 id; + u16 pn; + + id = vpx3220_read(client, 0x00); + if (id != 0xec) { + dprintk(1, + KERN_INFO + "vpx3220_attach: Wrong manufacturer ID (0x%02x)\n", + id); + kfree(client); + return 0; + } + + pn = (vpx3220_read(client, 0x02) << 8) + + vpx3220_read(client, 0x01); + switch (pn) { + case 0x4680: + strlcpy(I2C_NAME(client), "vpx3220a", + sizeof(I2C_NAME(client))); + break; + case 0x4260: + strlcpy(I2C_NAME(client), "vpx3216b", + sizeof(I2C_NAME(client))); + break; + case 0x4280: + strlcpy(I2C_NAME(client), "vpx3214c", + sizeof(I2C_NAME(client))); + break; + default: + dprintk(1, + KERN_INFO + "%s: Wrong part number (0x%04x)\n", + __func__, pn); + kfree(client); + return 0; + } + } else { + strlcpy(I2C_NAME(client), "forced vpx32xx", + sizeof(I2C_NAME(client))); + } + + decoder = kzalloc(sizeof(struct vpx3220), GFP_KERNEL); + if (decoder == NULL) { + kfree(client); + return -ENOMEM; + } + decoder->norm = VIDEO_MODE_PAL; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + i2c_set_clientdata(client, decoder); + + err = i2c_attach_client(client); + if (err) { + kfree(client); + kfree(decoder); + return err; + } + + dprintk(1, KERN_INFO "%s: vpx32xx client found at address 0x%02x\n", + I2C_NAME(client), client->addr << 1); + + vpx3220_init_client(client); + + return 0; +} + +static int +vpx3220_attach_adapter (struct i2c_adapter *adapter) +{ + int ret; + + ret = i2c_probe(adapter, &addr_data, &vpx3220_detect_client); + dprintk(1, VPX3220_DEBUG "%s: i2c_probe returned %d\n", + __func__, ret); + return ret; +} + +/* ----------------------------------------------------------------------- + * Driver initialization and cleanup code + */ + +static struct i2c_driver vpx3220_i2c_driver = { + .driver = { + .name = "vpx3220", + }, + + .id = I2C_DRIVERID_VPX3220, + + .attach_adapter = vpx3220_attach_adapter, + .detach_client = vpx3220_detach_client, + .command = vpx3220_command, +}; + +static int __init +vpx3220_init (void) +{ + return i2c_add_driver(&vpx3220_i2c_driver); +} + +static void __exit +vpx3220_cleanup (void) +{ + i2c_del_driver(&vpx3220_i2c_driver); +} + +module_init(vpx3220_init); +module_exit(vpx3220_cleanup); + +MODULE_DESCRIPTION("vpx3220a/vpx3216b/vpx3214c video encoder driver"); +MODULE_AUTHOR("Laurent Pinchart"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/w9966.c b/linux/drivers/media/video/w9966.c new file mode 100644 index 000000000..5aea29c69 --- /dev/null +++ b/linux/drivers/media/video/w9966.c @@ -0,0 +1,986 @@ +/* + Winbond w9966cf Webcam parport driver. + + Version 0.32 + + Copyright (C) 2001 Jakob Kemi <jakob.kemi@post.utfors.se> + + 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. +*/ +/* + Supported devices: + *Lifeview FlyCam Supra (using the Philips saa7111a chip) + + Does any other model using the w9966 interface chip exist ? + + Todo: + + *Add a working EPP mode, since DMA ECP read isn't implemented + in the parport drivers. (That's why it's so sloow) + + *Add support for other ccd-control chips than the saa7111 + please send me feedback on what kind of chips you have. + + *Add proper probing. I don't know what's wrong with the IEEE1284 + parport drivers but (IEEE1284_MODE_NIBBLE|IEEE1284_DEVICE_ID) + and nibble read seems to be broken for some peripherals. + + *Add probing for onboard SRAM, port directions etc. (if possible) + + *Add support for the hardware compressed modes (maybe using v4l2) + + *Fix better support for the capture window (no skewed images, v4l + interface to capt. window) + + *Probably some bugs that I don't know of + + Please support me by sending feedback! + + Changes: + + Alan Cox: Removed RGB mode for kernel merge, added THIS_MODULE + and owner support for newer module locks +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/parport.h> + +//#define DEBUG // Undef me for production + +#ifdef DEBUG +#define DPRINTF(x, a...) printk(KERN_DEBUG "W9966: %s(): "x, __FUNCTION__ , ##a) +#else +#define DPRINTF(x...) +#endif + +/* + * Defines, simple typedefs etc. + */ + +#define W9966_DRIVERNAME "W9966CF Webcam" +#define W9966_MAXCAMS 4 // Maximum number of cameras +#define W9966_RBUFFER 2048 // Read buffer (must be an even number) +#define W9966_SRAMSIZE 131072 // 128kb +#define W9966_SRAMID 0x02 // check w9966cf.pdf + +// Empirically determined window limits +#define W9966_WND_MIN_X 16 +#define W9966_WND_MIN_Y 14 +#define W9966_WND_MAX_X 705 +#define W9966_WND_MAX_Y 253 +#define W9966_WND_MAX_W (W9966_WND_MAX_X - W9966_WND_MIN_X) +#define W9966_WND_MAX_H (W9966_WND_MAX_Y - W9966_WND_MIN_Y) + +// Keep track of our current state +#define W9966_STATE_PDEV 0x01 +#define W9966_STATE_CLAIMED 0x02 +#define W9966_STATE_VDEV 0x04 + +#define W9966_I2C_W_ID 0x48 +#define W9966_I2C_R_ID 0x49 +#define W9966_I2C_R_DATA 0x08 +#define W9966_I2C_R_CLOCK 0x04 +#define W9966_I2C_W_DATA 0x02 +#define W9966_I2C_W_CLOCK 0x01 + +struct w9966_dev { + unsigned char dev_state; + unsigned char i2c_state; + unsigned short ppmode; + struct parport* pport; + struct pardevice* pdev; + struct video_device vdev; + unsigned short width; + unsigned short height; + unsigned char brightness; + signed char contrast; + signed char color; + signed char hue; +}; + +/* + * Module specific properties + */ + +MODULE_AUTHOR("Jakob Kemi <jakob.kemi@post.utfors.se>"); +MODULE_DESCRIPTION("Winbond w9966cf WebCam driver (0.32)"); +MODULE_LICENSE("GPL"); + + +#ifdef MODULE +static const char* pardev[] = {[0 ... W9966_MAXCAMS] = ""}; +#else +static const char* pardev[] = {[0 ... W9966_MAXCAMS] = "aggressive"}; +#endif +module_param_array(pardev, charp, NULL, 0); +MODULE_PARM_DESC(pardev, "pardev: where to search for\n\ +\teach camera. 'aggressive' means brute-force search.\n\ +\tEg: >pardev=parport3,aggressive,parport2,parport1< would assign\n\ +\tcam 1 to parport3 and search every parport for cam 2 etc..."); + +static int parmode = 0; +module_param(parmode, int, 0); +MODULE_PARM_DESC(parmode, "parmode: transfer mode (0=auto, 1=ecp, 2=epp"); + +static int video_nr = -1; +module_param(video_nr, int, 0); + +/* + * Private data + */ + +static struct w9966_dev w9966_cams[W9966_MAXCAMS]; + +/* + * Private function declares + */ + +static inline void w9966_setState(struct w9966_dev* cam, int mask, int val); +static inline int w9966_getState(struct w9966_dev* cam, int mask, int val); +static inline void w9966_pdev_claim(struct w9966_dev *vdev); +static inline void w9966_pdev_release(struct w9966_dev *vdev); + +static int w9966_rReg(struct w9966_dev* cam, int reg); +static int w9966_wReg(struct w9966_dev* cam, int reg, int data); +#if 0 +static int w9966_rReg_i2c(struct w9966_dev* cam, int reg); +#endif +static int w9966_wReg_i2c(struct w9966_dev* cam, int reg, int data); +static int w9966_findlen(int near, int size, int maxlen); +static int w9966_calcscale(int size, int min, int max, int* beg, int* end, unsigned char* factor); +static int w9966_setup(struct w9966_dev* cam, int x1, int y1, int x2, int y2, int w, int h); + +static int w9966_init(struct w9966_dev* cam, struct parport* port); +static void w9966_term(struct w9966_dev* cam); + +static inline void w9966_i2c_setsda(struct w9966_dev* cam, int state); +static inline int w9966_i2c_setscl(struct w9966_dev* cam, int state); +static inline int w9966_i2c_getsda(struct w9966_dev* cam); +static inline int w9966_i2c_getscl(struct w9966_dev* cam); +static int w9966_i2c_wbyte(struct w9966_dev* cam, int data); +#if 0 +static int w9966_i2c_rbyte(struct w9966_dev* cam); +#endif + +static int w9966_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static ssize_t w9966_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos); + +static struct file_operations w9966_fops = { + .owner = THIS_MODULE, + .open = video_exclusive_open, + .release = video_exclusive_release, + .ioctl = w9966_v4l_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .read = w9966_v4l_read, + .llseek = no_llseek, +}; +static struct video_device w9966_template = { + .owner = THIS_MODULE, + .name = W9966_DRIVERNAME, + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .hardware = VID_HARDWARE_W9966, + .fops = &w9966_fops, +}; + +/* + * Private function defines + */ + + +// Set camera phase flags, so we know what to uninit when terminating +static inline void w9966_setState(struct w9966_dev* cam, int mask, int val) +{ + cam->dev_state = (cam->dev_state & ~mask) ^ val; +} + +// Get camera phase flags +static inline int w9966_getState(struct w9966_dev* cam, int mask, int val) +{ + return ((cam->dev_state & mask) == val); +} + +// Claim parport for ourself +static inline void w9966_pdev_claim(struct w9966_dev* cam) +{ + if (w9966_getState(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED)) + return; + parport_claim_or_block(cam->pdev); + w9966_setState(cam, W9966_STATE_CLAIMED, W9966_STATE_CLAIMED); +} + +// Release parport for others to use +static inline void w9966_pdev_release(struct w9966_dev* cam) +{ + if (w9966_getState(cam, W9966_STATE_CLAIMED, 0)) + return; + parport_release(cam->pdev); + w9966_setState(cam, W9966_STATE_CLAIMED, 0); +} + +// Read register from W9966 interface-chip +// Expects a claimed pdev +// -1 on error, else register data (byte) +static int w9966_rReg(struct w9966_dev* cam, int reg) +{ + // ECP, read, regtransfer, REG, REG, REG, REG, REG + const unsigned char addr = 0x80 | (reg & 0x1f); + unsigned char val; + + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) + return -1; + if (parport_write(cam->pport, &addr, 1) != 1) + return -1; + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) + return -1; + if (parport_read(cam->pport, &val, 1) != 1) + return -1; + + return val; +} + +// Write register to W9966 interface-chip +// Expects a claimed pdev +// -1 on error +static int w9966_wReg(struct w9966_dev* cam, int reg, int data) +{ + // ECP, write, regtransfer, REG, REG, REG, REG, REG + const unsigned char addr = 0xc0 | (reg & 0x1f); + const unsigned char val = data; + + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_ADDR) != 0) + return -1; + if (parport_write(cam->pport, &addr, 1) != 1) + return -1; + if (parport_negotiate(cam->pport, cam->ppmode | IEEE1284_DATA) != 0) + return -1; + if (parport_write(cam->pport, &val, 1) != 1) + return -1; + + return 0; +} + +// Initialize camera device. Setup all internal flags, set a +// default video mode, setup ccd-chip, register v4l device etc.. +// Also used for 'probing' of hardware. +// -1 on error +static int w9966_init(struct w9966_dev* cam, struct parport* port) +{ + if (cam->dev_state != 0) + return -1; + + cam->pport = port; + cam->brightness = 128; + cam->contrast = 64; + cam->color = 64; + cam->hue = 0; + +// Select requested transfer mode + switch(parmode) + { + default: // Auto-detect (priority: hw-ecp, hw-epp, sw-ecp) + case 0: + if (port->modes & PARPORT_MODE_ECP) + cam->ppmode = IEEE1284_MODE_ECP; + else if (port->modes & PARPORT_MODE_EPP) + cam->ppmode = IEEE1284_MODE_EPP; + else + cam->ppmode = IEEE1284_MODE_ECP; + break; + case 1: // hw- or sw-ecp + cam->ppmode = IEEE1284_MODE_ECP; + break; + case 2: // hw- or sw-epp + cam->ppmode = IEEE1284_MODE_EPP; + break; + } + +// Tell the parport driver that we exists + cam->pdev = parport_register_device(port, "w9966", NULL, NULL, NULL, 0, NULL); + if (cam->pdev == NULL) { + DPRINTF("parport_register_device() failed\n"); + return -1; + } + w9966_setState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV); + + w9966_pdev_claim(cam); + +// Setup a default capture mode + if (w9966_setup(cam, 0, 0, 1023, 1023, 200, 160) != 0) { + DPRINTF("w9966_setup() failed.\n"); + return -1; + } + + w9966_pdev_release(cam); + +// Fill in the video_device struct and register us to v4l + memcpy(&cam->vdev, &w9966_template, sizeof(struct video_device)); + cam->vdev.priv = cam; + + if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, video_nr) == -1) + return -1; + + w9966_setState(cam, W9966_STATE_VDEV, W9966_STATE_VDEV); + + // All ok + printk( + "w9966cf: Found and initialized a webcam on %s.\n", + cam->pport->name + ); + return 0; +} + + +// Terminate everything gracefully +static void w9966_term(struct w9966_dev* cam) +{ +// Unregister from v4l + if (w9966_getState(cam, W9966_STATE_VDEV, W9966_STATE_VDEV)) { + video_unregister_device(&cam->vdev); + w9966_setState(cam, W9966_STATE_VDEV, 0); + } + +// Terminate from IEEE1284 mode and release pdev block + if (w9966_getState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { + w9966_pdev_claim(cam); + parport_negotiate(cam->pport, IEEE1284_MODE_COMPAT); + w9966_pdev_release(cam); + } + +// Unregister from parport + if (w9966_getState(cam, W9966_STATE_PDEV, W9966_STATE_PDEV)) { + parport_unregister_device(cam->pdev); + w9966_setState(cam, W9966_STATE_PDEV, 0); + } +} + + +// Find a good length for capture window (used both for W and H) +// A bit ugly but pretty functional. The capture length +// have to match the downscale +static int w9966_findlen(int near, int size, int maxlen) +{ + int bestlen = size; + int besterr = abs(near - bestlen); + int len; + + for(len = size+1;len < maxlen;len++) + { + int err; + if ( ((64*size) %len) != 0) + continue; + + err = abs(near - len); + + // Only continue as long as we keep getting better values + if (err > besterr) + break; + + besterr = err; + bestlen = len; + } + + return bestlen; +} + +// Modify capture window (if necessary) +// and calculate downscaling +// Return -1 on error +static int w9966_calcscale(int size, int min, int max, int* beg, int* end, unsigned char* factor) +{ + int maxlen = max - min; + int len = *end - *beg + 1; + int newlen = w9966_findlen(len, size, maxlen); + int err = newlen - len; + + // Check for bad format + if (newlen > maxlen || newlen < size) + return -1; + + // Set factor (6 bit fixed) + *factor = (64*size) / newlen; + if (*factor == 64) + *factor = 0x00; // downscale is disabled + else + *factor |= 0x80; // set downscale-enable bit + + // Modify old beginning and end + *beg -= err / 2; + *end += err - (err / 2); + + // Move window if outside borders + if (*beg < min) { + *end += min - *beg; + *beg += min - *beg; + } + if (*end > max) { + *beg -= *end - max; + *end -= *end - max; + } + + return 0; +} + +// Setup the cameras capture window etc. +// Expects a claimed pdev +// return -1 on error +static int w9966_setup(struct w9966_dev* cam, int x1, int y1, int x2, int y2, int w, int h) +{ + unsigned int i; + unsigned int enh_s, enh_e; + unsigned char scale_x, scale_y; + unsigned char regs[0x1c]; + unsigned char saa7111_regs[] = { + 0x21, 0x00, 0xd8, 0x23, 0x00, 0x80, 0x80, 0x00, + 0x88, 0x10, 0x80, 0x40, 0x40, 0x00, 0x01, 0x00, + 0x48, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0xe7, 0x00, 0x00, 0xc0 + }; + + + if (w*h*2 > W9966_SRAMSIZE) + { + DPRINTF("capture window exceeds SRAM size!.\n"); + w = 200; h = 160; // Pick default values + } + + w &= ~0x1; + if (w < 2) w = 2; + if (h < 1) h = 1; + if (w > W9966_WND_MAX_W) w = W9966_WND_MAX_W; + if (h > W9966_WND_MAX_H) h = W9966_WND_MAX_H; + + cam->width = w; + cam->height = h; + + enh_s = 0; + enh_e = w*h*2; + +// Modify capture window if necessary and calculate downscaling + if ( + w9966_calcscale(w, W9966_WND_MIN_X, W9966_WND_MAX_X, &x1, &x2, &scale_x) != 0 || + w9966_calcscale(h, W9966_WND_MIN_Y, W9966_WND_MAX_Y, &y1, &y2, &scale_y) != 0 + ) return -1; + + DPRINTF( + "%dx%d, x: %d<->%d, y: %d<->%d, sx: %d/64, sy: %d/64.\n", + w, h, x1, x2, y1, y2, scale_x&~0x80, scale_y&~0x80 + ); + +// Setup registers + regs[0x00] = 0x00; // Set normal operation + regs[0x01] = 0x18; // Capture mode + regs[0x02] = scale_y; // V-scaling + regs[0x03] = scale_x; // H-scaling + + // Capture window + regs[0x04] = (x1 & 0x0ff); // X-start (8 low bits) + regs[0x05] = (x1 & 0x300)>>8; // X-start (2 high bits) + regs[0x06] = (y1 & 0x0ff); // Y-start (8 low bits) + regs[0x07] = (y1 & 0x300)>>8; // Y-start (2 high bits) + regs[0x08] = (x2 & 0x0ff); // X-end (8 low bits) + regs[0x09] = (x2 & 0x300)>>8; // X-end (2 high bits) + regs[0x0a] = (y2 & 0x0ff); // Y-end (8 low bits) + + regs[0x0c] = W9966_SRAMID; // SRAM-banks (1x 128kb) + + // Enhancement layer + regs[0x0d] = (enh_s& 0x000ff); // Enh. start (0-7) + regs[0x0e] = (enh_s& 0x0ff00)>>8; // Enh. start (8-15) + regs[0x0f] = (enh_s& 0x70000)>>16; // Enh. start (16-17/18??) + regs[0x10] = (enh_e& 0x000ff); // Enh. end (0-7) + regs[0x11] = (enh_e& 0x0ff00)>>8; // Enh. end (8-15) + regs[0x12] = (enh_e& 0x70000)>>16; // Enh. end (16-17/18??) + + // Misc + regs[0x13] = 0x40; // VEE control (raw 4:2:2) + regs[0x17] = 0x00; // ??? + regs[0x18] = cam->i2c_state = 0x00; // Serial bus + regs[0x19] = 0xff; // I/O port direction control + regs[0x1a] = 0xff; // I/O port data register + regs[0x1b] = 0x10; // ??? + + // SAA7111 chip settings + saa7111_regs[0x0a] = cam->brightness; + saa7111_regs[0x0b] = cam->contrast; + saa7111_regs[0x0c] = cam->color; + saa7111_regs[0x0d] = cam->hue; + +// Reset (ECP-fifo & serial-bus) + if (w9966_wReg(cam, 0x00, 0x03) == -1) + return -1; + +// Write regs to w9966cf chip + for (i = 0; i < 0x1c; i++) + if (w9966_wReg(cam, i, regs[i]) == -1) + return -1; + +// Write regs to saa7111 chip + for (i = 0; i < 0x20; i++) + if (w9966_wReg_i2c(cam, i, saa7111_regs[i]) == -1) + return -1; + + return 0; +} + +/* + * Ugly and primitive i2c protocol functions + */ + +// Sets the data line on the i2c bus. +// Expects a claimed pdev. +static inline void w9966_i2c_setsda(struct w9966_dev* cam, int state) +{ + if (state) + cam->i2c_state |= W9966_I2C_W_DATA; + else + cam->i2c_state &= ~W9966_I2C_W_DATA; + + w9966_wReg(cam, 0x18, cam->i2c_state); + udelay(5); +} + +// Get peripheral clock line +// Expects a claimed pdev. +static inline int w9966_i2c_getscl(struct w9966_dev* cam) +{ + const unsigned char state = w9966_rReg(cam, 0x18); + return ((state & W9966_I2C_R_CLOCK) > 0); +} + +// Sets the clock line on the i2c bus. +// Expects a claimed pdev. -1 on error +static inline int w9966_i2c_setscl(struct w9966_dev* cam, int state) +{ + unsigned long timeout; + + if (state) + cam->i2c_state |= W9966_I2C_W_CLOCK; + else + cam->i2c_state &= ~W9966_I2C_W_CLOCK; + + w9966_wReg(cam, 0x18, cam->i2c_state); + udelay(5); + + // we go to high, we also expect the peripheral to ack. + if (state) { + timeout = jiffies + 100; + while (!w9966_i2c_getscl(cam)) { + if (time_after(jiffies, timeout)) + return -1; + } + } + return 0; +} + +// Get peripheral data line +// Expects a claimed pdev. +static inline int w9966_i2c_getsda(struct w9966_dev* cam) +{ + const unsigned char state = w9966_rReg(cam, 0x18); + return ((state & W9966_I2C_R_DATA) > 0); +} + +// Write a byte with ack to the i2c bus. +// Expects a claimed pdev. -1 on error +static int w9966_i2c_wbyte(struct w9966_dev* cam, int data) +{ + int i; + for (i = 7; i >= 0; i--) + { + w9966_i2c_setsda(cam, (data >> i) & 0x01); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setscl(cam, 0); + } + + w9966_i2c_setsda(cam, 1); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setscl(cam, 0); + + return 0; +} + +// Read a data byte with ack from the i2c-bus +// Expects a claimed pdev. -1 on error +#if 0 +static int w9966_i2c_rbyte(struct w9966_dev* cam) +{ + unsigned char data = 0x00; + int i; + + w9966_i2c_setsda(cam, 1); + + for (i = 0; i < 8; i++) + { + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + data = data << 1; + if (w9966_i2c_getsda(cam)) + data |= 0x01; + + w9966_i2c_setscl(cam, 0); + } + return data; +} +#endif + +// Read a register from the i2c device. +// Expects claimed pdev. -1 on error +#if 0 +static int w9966_rReg_i2c(struct w9966_dev* cam, int reg) +{ + int data; + + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || + w9966_i2c_wbyte(cam, reg) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 1); + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_R_ID) == -1 || + (data = w9966_i2c_rbyte(cam)) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 0); + + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + w9966_i2c_setsda(cam, 1); + + return data; +} +#endif + +// Write a register to the i2c device. +// Expects claimed pdev. -1 on error +static int w9966_wReg_i2c(struct w9966_dev* cam, int reg, int data) +{ + w9966_i2c_setsda(cam, 0); + w9966_i2c_setscl(cam, 0); + + if ( + w9966_i2c_wbyte(cam, W9966_I2C_W_ID) == -1 || + w9966_i2c_wbyte(cam, reg) == -1 || + w9966_i2c_wbyte(cam, data) == -1 + ) + return -1; + + w9966_i2c_setsda(cam, 0); + if (w9966_i2c_setscl(cam, 1) == -1) + return -1; + + w9966_i2c_setsda(cam, 1); + + return 0; +} + +/* + * Video4linux interfacing + */ + +static int w9966_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct video_device *vdev = video_devdata(file); + struct w9966_dev *cam = vdev->priv; + + switch(cmd) + { + case VIDIOCGCAP: + { + static struct video_capability vcap = { + .name = W9966_DRIVERNAME, + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .channels = 1, + .maxwidth = W9966_WND_MAX_W, + .maxheight = W9966_WND_MAX_H, + .minwidth = 2, + .minheight = 1, + }; + struct video_capability *cap = arg; + *cap = vcap; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel *vch = arg; + if(vch->channel != 0) // We only support one channel (#0) + return -EINVAL; + memset(vch,0,sizeof(*vch)); + strcpy(vch->name, "CCD-input"); + vch->type = VIDEO_TYPE_CAMERA; + return 0; + } + case VIDIOCSCHAN: + { + struct video_channel *vch = arg; + if(vch->channel != 0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner *vtune = arg; + if(vtune->tuner != 0) + return -EINVAL; + strcpy(vtune->name, "no tuner"); + vtune->rangelow = 0; + vtune->rangehigh = 0; + vtune->flags = VIDEO_TUNER_NORM; + vtune->mode = VIDEO_MODE_AUTO; + vtune->signal = 0xffff; + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner *vtune = arg; + if (vtune->tuner != 0) + return -EINVAL; + if (vtune->mode != VIDEO_MODE_AUTO) + return -EINVAL; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture vpic = { + cam->brightness << 8, // brightness + (cam->hue + 128) << 8, // hue + cam->color << 9, // color + cam->contrast << 9, // contrast + 0x8000, // whiteness + 16, VIDEO_PALETTE_YUV422// bpp, palette format + }; + struct video_picture *pic = arg; + *pic = vpic; + return 0; + } + case VIDIOCSPICT: + { + struct video_picture *vpic = arg; + if (vpic->depth != 16 || vpic->palette != VIDEO_PALETTE_YUV422) + return -EINVAL; + + cam->brightness = vpic->brightness >> 8; + cam->hue = (vpic->hue >> 8) - 128; + cam->color = vpic->colour >> 9; + cam->contrast = vpic->contrast >> 9; + + w9966_pdev_claim(cam); + + if ( + w9966_wReg_i2c(cam, 0x0a, cam->brightness) == -1 || + w9966_wReg_i2c(cam, 0x0b, cam->contrast) == -1 || + w9966_wReg_i2c(cam, 0x0c, cam->color) == -1 || + w9966_wReg_i2c(cam, 0x0d, cam->hue) == -1 + ) { + w9966_pdev_release(cam); + return -EIO; + } + + w9966_pdev_release(cam); + return 0; + } + case VIDIOCSWIN: + { + int ret; + struct video_window *vwin = arg; + + if (vwin->flags != 0) + return -EINVAL; + if (vwin->clipcount != 0) + return -EINVAL; + if (vwin->width < 2 || vwin->width > W9966_WND_MAX_W) + return -EINVAL; + if (vwin->height < 1 || vwin->height > W9966_WND_MAX_H) + return -EINVAL; + + // Update camera regs + w9966_pdev_claim(cam); + ret = w9966_setup(cam, 0, 0, 1023, 1023, vwin->width, vwin->height); + w9966_pdev_release(cam); + + if (ret != 0) { + DPRINTF("VIDIOCSWIN: w9966_setup() failed.\n"); + return -EIO; + } + + return 0; + } + case VIDIOCGWIN: + { + struct video_window *vwin = arg; + memset(vwin, 0, sizeof(*vwin)); + vwin->width = cam->width; + vwin->height = cam->height; + return 0; + } + // Unimplemented + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCSFBUF: + case VIDIOCKEY: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int w9966_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, w9966_v4l_do_ioctl); +} + +// Capture data +static ssize_t w9966_v4l_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct video_device *vdev = video_devdata(file); + struct w9966_dev *cam = vdev->priv; + unsigned char addr = 0xa0; // ECP, read, CCD-transfer, 00000 + unsigned char __user *dest = (unsigned char __user *)buf; + unsigned long dleft = count; + unsigned char *tbuf; + + // Why would anyone want more than this?? + if (count > cam->width * cam->height * 2) + return -EINVAL; + + w9966_pdev_claim(cam); + w9966_wReg(cam, 0x00, 0x02); // Reset ECP-FIFO buffer + w9966_wReg(cam, 0x00, 0x00); // Return to normal operation + w9966_wReg(cam, 0x01, 0x98); // Enable capture + + // write special capture-addr and negotiate into data transfer + if ( + (parport_negotiate(cam->pport, cam->ppmode|IEEE1284_ADDR) != 0 )|| + (parport_write(cam->pport, &addr, 1) != 1 )|| + (parport_negotiate(cam->pport, cam->ppmode|IEEE1284_DATA) != 0 ) + ) { + w9966_pdev_release(cam); + return -EFAULT; + } + + tbuf = kmalloc(W9966_RBUFFER, GFP_KERNEL); + if (tbuf == NULL) { + count = -ENOMEM; + goto out; + } + + while(dleft > 0) + { + unsigned long tsize = (dleft > W9966_RBUFFER) ? W9966_RBUFFER : dleft; + + if (parport_read(cam->pport, tbuf, tsize) < tsize) { + count = -EFAULT; + goto out; + } + if (copy_to_user(dest, tbuf, tsize) != 0) { + count = -EFAULT; + goto out; + } + dest += tsize; + dleft -= tsize; + } + + w9966_wReg(cam, 0x01, 0x18); // Disable capture + +out: + kfree(tbuf); + w9966_pdev_release(cam); + + return count; +} + + +// Called once for every parport on init +static void w9966_attach(struct parport *port) +{ + int i; + + for (i = 0; i < W9966_MAXCAMS; i++) + { + if (w9966_cams[i].dev_state != 0) // Cam is already assigned + continue; + if ( + strcmp(pardev[i], "aggressive") == 0 || + strcmp(pardev[i], port->name) == 0 + ) { + if (w9966_init(&w9966_cams[i], port) != 0) + w9966_term(&w9966_cams[i]); + break; // return + } + } +} + +// Called once for every parport on termination +static void w9966_detach(struct parport *port) +{ + int i; + for (i = 0; i < W9966_MAXCAMS; i++) + if (w9966_cams[i].dev_state != 0 && w9966_cams[i].pport == port) + w9966_term(&w9966_cams[i]); +} + + +static struct parport_driver w9966_ppd = { + .name = W9966_DRIVERNAME, + .attach = w9966_attach, + .detach = w9966_detach, +}; + +// Module entry point +static int __init w9966_mod_init(void) +{ + int i; + for (i = 0; i < W9966_MAXCAMS; i++) + w9966_cams[i].dev_state = 0; + + return parport_register_driver(&w9966_ppd); +} + +// Module cleanup +static void __exit w9966_mod_term(void) +{ + parport_unregister_driver(&w9966_ppd); +} + +module_init(w9966_mod_init); +module_exit(w9966_mod_term); diff --git a/linux/drivers/media/video/w9968cf.c b/linux/drivers/media/video/w9968cf.c new file mode 100644 index 000000000..20f211b55 --- /dev/null +++ b/linux/drivers/media/video/w9968cf.c @@ -0,0 +1,3691 @@ +/*************************************************************************** + * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip. * + * * + * Copyright (C) 2002-2004 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * - Memory management code from bttv driver by Ralph Metzler, * + * Marcus Metzler and Gerd Knorr. * + * - I2C interface to kernel, high-level image sensor control routines and * + * some symbolic names from OV511 driver by Mark W. McClelland. * + * - Low-level I2C fast write function by Piotr Czerczak. * + * - Low-level I2C read function by Frederic Jouault. * + * * + * 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/kmod.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <linux/page-flags.h> +#include <linux/moduleparam.h> + +#include "w9968cf.h" +#include "w9968cf_decoder.h" + +static struct w9968cf_vpp_t* w9968cf_vpp; +static DECLARE_WAIT_QUEUE_HEAD(w9968cf_vppmod_wait); + +static LIST_HEAD(w9968cf_dev_list); /* head of V4L registered cameras list */ +static DEFINE_MUTEX(w9968cf_devlist_mutex); /* semaphore for list traversal */ + +static DECLARE_RWSEM(w9968cf_disconnect); /* prevent races with open() */ + + +/**************************************************************************** + * Module macros and parameters * + ****************************************************************************/ + +MODULE_DEVICE_TABLE(usb, winbond_id_table); + +MODULE_AUTHOR(W9968CF_MODULE_AUTHOR" "W9968CF_AUTHOR_EMAIL); +MODULE_DESCRIPTION(W9968CF_MODULE_NAME); +MODULE_VERSION(W9968CF_MODULE_VERSION); +MODULE_LICENSE(W9968CF_MODULE_LICENSE); +MODULE_SUPPORTED_DEVICE("Video"); + +static int ovmod_load = W9968CF_OVMOD_LOAD; +static unsigned short simcams = W9968CF_SIMCAMS; +static short video_nr[]={[0 ... W9968CF_MAX_DEVICES-1] = -1}; /*-1=first free*/ +static unsigned int packet_size[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_PACKET_SIZE}; +static unsigned short max_buffers[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_BUFFERS}; +static int double_buffer[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_DOUBLE_BUFFER}; +static int clamping[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLAMPING}; +static unsigned short filter_type[]= {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_FILTER_TYPE}; +static int largeview[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_LARGEVIEW}; +static unsigned short decompression[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_DECOMPRESSION}; +static int upscaling[]= {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_UPSCALING}; +static unsigned short force_palette[] = {[0 ... W9968CF_MAX_DEVICES-1] = 0}; +static int force_rgb[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_FORCE_RGB}; +static int autobright[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOBRIGHT}; +static int autoexp[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_AUTOEXP}; +static unsigned short lightfreq[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_LIGHTFREQ}; +static int bandingfilter[] = {[0 ... W9968CF_MAX_DEVICES-1]= + W9968CF_BANDINGFILTER}; +static short clockdiv[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_CLOCKDIV}; +static int backlight[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_BACKLIGHT}; +static int mirror[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_MIRROR}; +static int monochrome[] = {[0 ... W9968CF_MAX_DEVICES-1]=W9968CF_MONOCHROME}; +static unsigned int brightness[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_BRIGHTNESS}; +static unsigned int hue[] = {[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_HUE}; +static unsigned int colour[]={[0 ... W9968CF_MAX_DEVICES-1] = W9968CF_COLOUR}; +static unsigned int contrast[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_CONTRAST}; +static unsigned int whiteness[] = {[0 ... W9968CF_MAX_DEVICES-1] = + W9968CF_WHITENESS}; +#ifdef W9968CF_DEBUG +static unsigned short debug = W9968CF_DEBUG_LEVEL; +static int specific_debug = W9968CF_SPECIFIC_DEBUG; +#endif + +static unsigned int param_nv[24]; /* number of values per parameter */ + +#ifdef CONFIG_KMOD +module_param(ovmod_load, bool, 0644); +#endif +module_param(simcams, ushort, 0644); +module_param_array(video_nr, short, ¶m_nv[0], 0444); +module_param_array(packet_size, uint, ¶m_nv[1], 0444); +module_param_array(max_buffers, ushort, ¶m_nv[2], 0444); +module_param_array(double_buffer, bool, ¶m_nv[3], 0444); +module_param_array(clamping, bool, ¶m_nv[4], 0444); +module_param_array(filter_type, ushort, ¶m_nv[5], 0444); +module_param_array(largeview, bool, ¶m_nv[6], 0444); +module_param_array(decompression, ushort, ¶m_nv[7], 0444); +module_param_array(upscaling, bool, ¶m_nv[8], 0444); +module_param_array(force_palette, ushort, ¶m_nv[9], 0444); +module_param_array(force_rgb, ushort, ¶m_nv[10], 0444); +module_param_array(autobright, bool, ¶m_nv[11], 0444); +module_param_array(autoexp, bool, ¶m_nv[12], 0444); +module_param_array(lightfreq, ushort, ¶m_nv[13], 0444); +module_param_array(bandingfilter, bool, ¶m_nv[14], 0444); +module_param_array(clockdiv, short, ¶m_nv[15], 0444); +module_param_array(backlight, bool, ¶m_nv[16], 0444); +module_param_array(mirror, bool, ¶m_nv[17], 0444); +module_param_array(monochrome, bool, ¶m_nv[18], 0444); +module_param_array(brightness, uint, ¶m_nv[19], 0444); +module_param_array(hue, uint, ¶m_nv[20], 0444); +module_param_array(colour, uint, ¶m_nv[21], 0444); +module_param_array(contrast, uint, ¶m_nv[22], 0444); +module_param_array(whiteness, uint, ¶m_nv[23], 0444); +#ifdef W9968CF_DEBUG +module_param(debug, ushort, 0644); +module_param(specific_debug, bool, 0644); +#endif + +#ifdef CONFIG_KMOD +MODULE_PARM_DESC(ovmod_load, + "\n<0|1> Automatic 'ovcamchip' module loading." + "\n0 disabled, 1 enabled." + "\nIf enabled,'insmod' searches for the required 'ovcamchip'" + "\nmodule in the system, according to its configuration, and" + "\nattempts to load that module automatically. This action is" + "\nperformed once as soon as the 'w9968cf' module is loaded" + "\ninto memory." + "\nDefault value is "__MODULE_STRING(W9968CF_OVMOD_LOAD)"." + "\n"); +#endif +MODULE_PARM_DESC(simcams, + "\n<n> Number of cameras allowed to stream simultaneously." + "\nn may vary from 0 to " + __MODULE_STRING(W9968CF_MAX_DEVICES)"." + "\nDefault value is "__MODULE_STRING(W9968CF_SIMCAMS)"." + "\n"); +MODULE_PARM_DESC(video_nr, + "\n<-1|n[,...]> Specify V4L minor mode number." + "\n -1 = use next available (default)" + "\n n = use minor number n (integer >= 0)" + "\nYou can specify up to "__MODULE_STRING(W9968CF_MAX_DEVICES) + " cameras this way." + "\nFor example:" + "\nvideo_nr=-1,2,-1 would assign minor number 2 to" + "\nthe second camera and use auto for the first" + "\none and for every other camera." + "\n"); +MODULE_PARM_DESC(packet_size, + "\n<n[,...]> Specify the maximum data payload" + "\nsize in bytes for alternate settings, for each device." + "\nn is scaled between 63 and 1023 " + "(default is "__MODULE_STRING(W9968CF_PACKET_SIZE)")." + "\n"); +MODULE_PARM_DESC(max_buffers, + "\n<n[,...]> For advanced users." + "\nSpecify the maximum number of video frame buffers" + "\nto allocate for each device, from 2 to " + __MODULE_STRING(W9968CF_MAX_BUFFERS) + ". (default is "__MODULE_STRING(W9968CF_BUFFERS)")." + "\n"); +MODULE_PARM_DESC(double_buffer, + "\n<0|1[,...]> " + "Hardware double buffering: 0 disabled, 1 enabled." + "\nIt should be enabled if you want smooth video output: if" + "\nyou obtain out of sync. video, disable it, or try to" + "\ndecrease the 'clockdiv' module parameter value." + "\nDefault value is "__MODULE_STRING(W9968CF_DOUBLE_BUFFER) + " for every device." + "\n"); +MODULE_PARM_DESC(clamping, + "\n<0|1[,...]> Video data clamping: 0 disabled, 1 enabled." + "\nDefault value is "__MODULE_STRING(W9968CF_CLAMPING) + " for every device." + "\n"); +MODULE_PARM_DESC(filter_type, + "\n<0|1|2[,...]> Video filter type." + "\n0 none, 1 (1-2-1) 3-tap filter, " + "2 (2-3-6-3-2) 5-tap filter." + "\nDefault value is "__MODULE_STRING(W9968CF_FILTER_TYPE) + " for every device." + "\nThe filter is used to reduce noise and aliasing artifacts" + "\nproduced by the CCD or CMOS image sensor, and the scaling" + " process." + "\n"); +MODULE_PARM_DESC(largeview, + "\n<0|1[,...]> Large view: 0 disabled, 1 enabled." + "\nDefault value is "__MODULE_STRING(W9968CF_LARGEVIEW) + " for every device." + "\n"); +MODULE_PARM_DESC(upscaling, + "\n<0|1[,...]> Software scaling (for non-compressed video):" + "\n0 disabled, 1 enabled." + "\nDisable it if you have a slow CPU or you don't have" + " enough memory." + "\nDefault value is "__MODULE_STRING(W9968CF_UPSCALING) + " for every device." + "\nIf 'w9968cf-vpp' is not present, this parameter is" + " set to 0." + "\n"); +MODULE_PARM_DESC(decompression, + "\n<0|1|2[,...]> Software video decompression:" + "\n- 0 disables decompression (doesn't allow formats needing" + " decompression)" + "\n- 1 forces decompression (allows formats needing" + " decompression only);" + "\n- 2 allows any permitted formats." + "\nFormats supporting compressed video are YUV422P and" + " YUV420P/YUV420 " + "\nin any resolutions where both width and height are " + "a multiple of 16." + "\nDefault value is "__MODULE_STRING(W9968CF_DECOMPRESSION) + " for every device." + "\nIf 'w9968cf-vpp' is not present, forcing decompression is " + "\nnot allowed; in this case this parameter is set to 2." + "\n"); +MODULE_PARM_DESC(force_palette, + "\n<0" + "|" __MODULE_STRING(VIDEO_PALETTE_UYVY) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV420) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV422P) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV420P) + "|" __MODULE_STRING(VIDEO_PALETTE_YUYV) + "|" __MODULE_STRING(VIDEO_PALETTE_YUV422) + "|" __MODULE_STRING(VIDEO_PALETTE_GREY) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB555) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB565) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB24) + "|" __MODULE_STRING(VIDEO_PALETTE_RGB32) + "[,...]>" + " Force picture palette." + "\nIn order:" + "\n- 0 allows any of the following formats:" + "\n- UYVY 16 bpp - Original video, compression disabled" + "\n- YUV420 12 bpp - Original video, compression enabled" + "\n- YUV422P 16 bpp - Original video, compression enabled" + "\n- YUV420P 12 bpp - Original video, compression enabled" + "\n- YUVY 16 bpp - Software conversion from UYVY" + "\n- YUV422 16 bpp - Software conversion from UYVY" + "\n- GREY 8 bpp - Software conversion from UYVY" + "\n- RGB555 16 bpp - Software conversion from UYVY" + "\n- RGB565 16 bpp - Software conversion from UYVY" + "\n- RGB24 24 bpp - Software conversion from UYVY" + "\n- RGB32 32 bpp - Software conversion from UYVY" + "\nWhen not 0, this parameter will override 'decompression'." + "\nDefault value is 0 for every device." + "\nInitial palette is " + __MODULE_STRING(W9968CF_PALETTE_DECOMP_ON)"." + "\nIf 'w9968cf-vpp' is not present, this parameter is" + " set to 9 (UYVY)." + "\n"); +MODULE_PARM_DESC(force_rgb, + "\n<0|1[,...]> Read RGB video data instead of BGR:" + "\n 1 = use RGB component ordering." + "\n 0 = use BGR component ordering." + "\nThis parameter has effect when using RGBX palettes only." + "\nDefault value is "__MODULE_STRING(W9968CF_FORCE_RGB) + " for every device." + "\n"); +MODULE_PARM_DESC(autobright, + "\n<0|1[,...]> Image sensor automatically changes brightness:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_AUTOBRIGHT) + " for every device." + "\n"); +MODULE_PARM_DESC(autoexp, + "\n<0|1[,...]> Image sensor automatically changes exposure:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_AUTOEXP) + " for every device." + "\n"); +MODULE_PARM_DESC(lightfreq, + "\n<50|60[,...]> Light frequency in Hz:" + "\n 50 for European and Asian lighting," + " 60 for American lighting." + "\nDefault value is "__MODULE_STRING(W9968CF_LIGHTFREQ) + " for every device." + "\n"); +MODULE_PARM_DESC(bandingfilter, + "\n<0|1[,...]> Banding filter to reduce effects of" + " fluorescent lighting:" + "\n 0 disabled, 1 enabled." + "\nThis filter tries to reduce the pattern of horizontal" + "\nlight/dark bands caused by some (usually fluorescent)" + " lighting." + "\nDefault value is "__MODULE_STRING(W9968CF_BANDINGFILTER) + " for every device." + "\n"); +MODULE_PARM_DESC(clockdiv, + "\n<-1|n[,...]> " + "Force pixel clock divisor to a specific value (for experts):" + "\n n may vary from 0 to 127." + "\n -1 for automatic value." + "\nSee also the 'double_buffer' module parameter." + "\nDefault value is "__MODULE_STRING(W9968CF_CLOCKDIV) + " for every device." + "\n"); +MODULE_PARM_DESC(backlight, + "\n<0|1[,...]> Objects are lit from behind:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_BACKLIGHT) + " for every device." + "\n"); +MODULE_PARM_DESC(mirror, + "\n<0|1[,...]> Reverse image horizontally:" + "\n 0 = no, 1 = yes" + "\nDefault value is "__MODULE_STRING(W9968CF_MIRROR) + " for every device." + "\n"); +MODULE_PARM_DESC(monochrome, + "\n<0|1[,...]> Use image sensor as monochrome sensor:" + "\n 0 = no, 1 = yes" + "\nNot all the sensors support monochrome color." + "\nDefault value is "__MODULE_STRING(W9968CF_MONOCHROME) + " for every device." + "\n"); +MODULE_PARM_DESC(brightness, + "\n<n[,...]> Set picture brightness (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_BRIGHTNESS) + " for every device." + "\nThis parameter has no effect if 'autobright' is enabled." + "\n"); +MODULE_PARM_DESC(hue, + "\n<n[,...]> Set picture hue (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_HUE) + " for every device." + "\n"); +MODULE_PARM_DESC(colour, + "\n<n[,...]> Set picture saturation (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_COLOUR) + " for every device." + "\n"); +MODULE_PARM_DESC(contrast, + "\n<n[,...]> Set picture contrast (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_CONTRAST) + " for every device." + "\n"); +MODULE_PARM_DESC(whiteness, + "\n<n[,...]> Set picture whiteness (0-65535)." + "\nDefault value is "__MODULE_STRING(W9968CF_WHITENESS) + " for every device." + "\n"); +#ifdef W9968CF_DEBUG +MODULE_PARM_DESC(debug, + "\n<n> Debugging information level, from 0 to 6:" + "\n0 = none (use carefully)" + "\n1 = critical errors" + "\n2 = significant informations" + "\n3 = configuration or general messages" + "\n4 = warnings" + "\n5 = called functions" + "\n6 = function internals" + "\nLevel 5 and 6 are useful for testing only, when only " + "one device is used." + "\nDefault value is "__MODULE_STRING(W9968CF_DEBUG_LEVEL)"." + "\n"); +MODULE_PARM_DESC(specific_debug, + "\n<0|1> Enable or disable specific debugging messages:" + "\n0 = print messages concerning every level" + " <= 'debug' level." + "\n1 = print messages concerning the level" + " indicated by 'debug'." + "\nDefault value is " + __MODULE_STRING(W9968CF_SPECIFIC_DEBUG)"." + "\n"); +#endif /* W9968CF_DEBUG */ + + + +/**************************************************************************** + * Some prototypes * + ****************************************************************************/ + +/* Video4linux interface */ +static struct file_operations w9968cf_fops; +static int w9968cf_open(struct inode*, struct file*); +static int w9968cf_release(struct inode*, struct file*); +static int w9968cf_mmap(struct file*, struct vm_area_struct*); +static int w9968cf_ioctl(struct inode*, struct file*, unsigned, unsigned long); +static ssize_t w9968cf_read(struct file*, char __user *, size_t, loff_t*); +static int w9968cf_v4l_ioctl(struct inode*, struct file*, unsigned int, + void __user *); + +/* USB-specific */ +static int w9968cf_start_transfer(struct w9968cf_device*); +static int w9968cf_stop_transfer(struct w9968cf_device*); +static int w9968cf_write_reg(struct w9968cf_device*, u16 value, u16 index); +static int w9968cf_read_reg(struct w9968cf_device*, u16 index); +static int w9968cf_write_fsb(struct w9968cf_device*, u16* data); +static int w9968cf_write_sb(struct w9968cf_device*, u16 value); +static int w9968cf_read_sb(struct w9968cf_device*); +static int w9968cf_upload_quantizationtables(struct w9968cf_device*); +static void w9968cf_urb_complete(struct urb *urb, struct pt_regs *regs); + +/* Low-level I2C (SMBus) I/O */ +static int w9968cf_smbus_start(struct w9968cf_device*); +static int w9968cf_smbus_stop(struct w9968cf_device*); +static int w9968cf_smbus_write_byte(struct w9968cf_device*, u8 v); +static int w9968cf_smbus_read_byte(struct w9968cf_device*, u8* v); +static int w9968cf_smbus_write_ack(struct w9968cf_device*); +static int w9968cf_smbus_read_ack(struct w9968cf_device*); +static int w9968cf_smbus_refresh_bus(struct w9968cf_device*); +static int w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam, + u16 address, u8* value); +static int w9968cf_i2c_adap_read_byte_data(struct w9968cf_device*, u16 address, + u8 subaddress, u8* value); +static int w9968cf_i2c_adap_write_byte(struct w9968cf_device*, + u16 address, u8 subaddress); +static int w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device*, + u16 address, u8 subaddress, + u8 value); + +/* I2C interface to kernel */ +static int w9968cf_i2c_init(struct w9968cf_device*); +static int w9968cf_i2c_smbus_xfer(struct i2c_adapter*, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data*); +static u32 w9968cf_i2c_func(struct i2c_adapter*); +static int w9968cf_i2c_attach_inform(struct i2c_client*); +static int w9968cf_i2c_detach_inform(struct i2c_client*); +static int w9968cf_i2c_control(struct i2c_adapter*, unsigned int cmd, + unsigned long arg); + +/* Memory management */ +static void* rvmalloc(unsigned long size); +static void rvfree(void *mem, unsigned long size); +static void w9968cf_deallocate_memory(struct w9968cf_device*); +static int w9968cf_allocate_memory(struct w9968cf_device*); + +/* High-level image sensor control functions */ +static int w9968cf_sensor_set_control(struct w9968cf_device*,int cid,int val); +static int w9968cf_sensor_get_control(struct w9968cf_device*,int cid,int *val); +static int w9968cf_sensor_cmd(struct w9968cf_device*, + unsigned int cmd, void *arg); +static int w9968cf_sensor_init(struct w9968cf_device*); +static int w9968cf_sensor_update_settings(struct w9968cf_device*); +static int w9968cf_sensor_get_picture(struct w9968cf_device*); +static int w9968cf_sensor_update_picture(struct w9968cf_device*, + struct video_picture pict); + +/* Other helper functions */ +static void w9968cf_configure_camera(struct w9968cf_device*,struct usb_device*, + enum w9968cf_model_id, + const unsigned short dev_nr); +static void w9968cf_adjust_configuration(struct w9968cf_device*); +static int w9968cf_turn_on_led(struct w9968cf_device*); +static int w9968cf_init_chip(struct w9968cf_device*); +static inline u16 w9968cf_valid_palette(u16 palette); +static inline u16 w9968cf_valid_depth(u16 palette); +static inline u8 w9968cf_need_decompression(u16 palette); +static int w9968cf_set_picture(struct w9968cf_device*, struct video_picture); +static int w9968cf_set_window(struct w9968cf_device*, struct video_window); +static int w9968cf_postprocess_frame(struct w9968cf_device*, + struct w9968cf_frame_t*); +static int w9968cf_adjust_window_size(struct w9968cf_device*, u16* w, u16* h); +static void w9968cf_init_framelist(struct w9968cf_device*); +static void w9968cf_push_frame(struct w9968cf_device*, u8 f_num); +static void w9968cf_pop_frame(struct w9968cf_device*,struct w9968cf_frame_t**); +static void w9968cf_release_resources(struct w9968cf_device*); + + + +/**************************************************************************** + * Symbolic names * + ****************************************************************************/ + +/* Used to represent a list of values and their respective symbolic names */ +struct w9968cf_symbolic_list { + const int num; + const char *name; +}; + +/*-------------------------------------------------------------------------- + Returns the name of the matching element in the symbolic_list array. The + end of the list must be marked with an element that has a NULL name. + --------------------------------------------------------------------------*/ +static inline const char * +symbolic(struct w9968cf_symbolic_list list[], const int num) +{ + int i; + + for (i = 0; list[i].name != NULL; i++) + if (list[i].num == num) + return (list[i].name); + + return "Unknown"; +} + +static struct w9968cf_symbolic_list camlist[] = { + { W9968CF_MOD_GENERIC, "W996[87]CF JPEG USB Dual Mode Camera" }, + { W9968CF_MOD_CLVBWGP, "Creative Labs Video Blaster WebCam Go Plus" }, + + /* Other cameras (having the same descriptors as Generic W996[87]CF) */ + { W9968CF_MOD_ADPVDMA, "Aroma Digi Pen VGA Dual Mode ADG-5000" }, + { W9986CF_MOD_AAU, "AVerMedia AVerTV USB" }, + { W9968CF_MOD_CLVBWG, "Creative Labs Video Blaster WebCam Go" }, + { W9968CF_MOD_LL, "Lebon LDC-035A" }, + { W9968CF_MOD_EEEMC, "Ezonics EZ-802 EZMega Cam" }, + { W9968CF_MOD_OOE, "OmniVision OV8610-EDE" }, + { W9968CF_MOD_ODPVDMPC, "OPCOM Digi Pen VGA Dual Mode Pen Camera" }, + { W9968CF_MOD_PDPII, "Pretec Digi Pen-II" }, + { W9968CF_MOD_PDP480, "Pretec DigiPen-480" }, + + { -1, NULL } +}; + +static struct w9968cf_symbolic_list senlist[] = { + { CC_OV76BE, "OV76BE" }, + { CC_OV7610, "OV7610" }, + { CC_OV7620, "OV7620" }, + { CC_OV7620AE, "OV7620AE" }, + { CC_OV6620, "OV6620" }, + { CC_OV6630, "OV6630" }, + { CC_OV6630AE, "OV6630AE" }, + { CC_OV6630AF, "OV6630AF" }, + { -1, NULL } +}; + +/* Video4Linux1 palettes */ +static struct w9968cf_symbolic_list v4l1_plist[] = { + { VIDEO_PALETTE_GREY, "GREY" }, + { VIDEO_PALETTE_HI240, "HI240" }, + { VIDEO_PALETTE_RGB565, "RGB565" }, + { VIDEO_PALETTE_RGB24, "RGB24" }, + { VIDEO_PALETTE_RGB32, "RGB32" }, + { VIDEO_PALETTE_RGB555, "RGB555" }, + { VIDEO_PALETTE_YUV422, "YUV422" }, + { VIDEO_PALETTE_YUYV, "YUYV" }, + { VIDEO_PALETTE_UYVY, "UYVY" }, + { VIDEO_PALETTE_YUV420, "YUV420" }, + { VIDEO_PALETTE_YUV411, "YUV411" }, + { VIDEO_PALETTE_RAW, "RAW" }, + { VIDEO_PALETTE_YUV422P, "YUV422P" }, + { VIDEO_PALETTE_YUV411P, "YUV411P" }, + { VIDEO_PALETTE_YUV420P, "YUV420P" }, + { VIDEO_PALETTE_YUV410P, "YUV410P" }, + { -1, NULL } +}; + +/* Decoder error codes: */ +static struct w9968cf_symbolic_list decoder_errlist[] = { + { W9968CF_DEC_ERR_CORRUPTED_DATA, "Corrupted data" }, + { W9968CF_DEC_ERR_BUF_OVERFLOW, "Buffer overflow" }, + { W9968CF_DEC_ERR_NO_SOI, "SOI marker not found" }, + { W9968CF_DEC_ERR_NO_SOF0, "SOF0 marker not found" }, + { W9968CF_DEC_ERR_NO_SOS, "SOS marker not found" }, + { W9968CF_DEC_ERR_NO_EOI, "EOI marker not found" }, + { -1, NULL } +}; + +/* URB error codes: */ +static struct w9968cf_symbolic_list urb_errlist[] = { + { -ENOMEM, "No memory for allocation of internal structures" }, + { -ENOSPC, "The host controller's bandwidth is already consumed" }, + { -ENOENT, "URB was canceled by unlink_urb" }, + { -EXDEV, "ISO transfer only partially completed" }, + { -EAGAIN, "Too match scheduled for the future" }, + { -ENXIO, "URB already queued" }, + { -EFBIG, "Too much ISO frames requested" }, + { -ENOSR, "Buffer error (overrun)" }, + { -EPIPE, "Specified endpoint is stalled (device not responding)"}, + { -EOVERFLOW, "Babble (bad cable?)" }, + { -EPROTO, "Bit-stuff error (bad cable?)" }, + { -EILSEQ, "CRC/Timeout" }, + { -ETIMEDOUT, "NAK (device does not respond)" }, + { -1, NULL } +}; + + + +/**************************************************************************** + * Memory management functions * + ****************************************************************************/ +static void* rvmalloc(unsigned long size) +{ + void* mem; + unsigned long adr; + + size = PAGE_ALIGN(size); + 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) { + SetPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + + return mem; +} + + +static void rvfree(void* mem, unsigned long size) +{ + unsigned long adr; + + if (!mem) + return; + + adr = (unsigned long) mem; + while ((long) size > 0) { + ClearPageReserved(vmalloc_to_page((void *)adr)); + adr += PAGE_SIZE; + size -= PAGE_SIZE; + } + vfree(mem); +} + + +/*-------------------------------------------------------------------------- + Deallocate previously allocated memory. + --------------------------------------------------------------------------*/ +static void w9968cf_deallocate_memory(struct w9968cf_device* cam) +{ + u8 i; + + /* Free the isochronous transfer buffers */ + for (i = 0; i < W9968CF_URBS; i++) { + kfree(cam->transfer_buffer[i]); + cam->transfer_buffer[i] = NULL; + } + + /* Free temporary frame buffer */ + if (cam->frame_tmp.buffer) { + rvfree(cam->frame_tmp.buffer, cam->frame_tmp.size); + cam->frame_tmp.buffer = NULL; + } + + /* Free helper buffer */ + if (cam->frame_vpp.buffer) { + rvfree(cam->frame_vpp.buffer, cam->frame_vpp.size); + cam->frame_vpp.buffer = NULL; + } + + /* Free video frame buffers */ + if (cam->frame[0].buffer) { + rvfree(cam->frame[0].buffer, cam->nbuffers*cam->frame[0].size); + cam->frame[0].buffer = NULL; + } + + cam->nbuffers = 0; + + DBG(5, "Memory successfully deallocated") +} + + +/*-------------------------------------------------------------------------- + Allocate memory buffers for USB transfers and video frames. + This function is called by open() only. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_allocate_memory(struct w9968cf_device* cam) +{ + const u16 p_size = wMaxPacketSize[cam->altsetting-1]; + void* buff = NULL; + unsigned long hw_bufsize, vpp_bufsize; + u8 i, bpp; + + /* NOTE: Deallocation is done elsewhere in case of error */ + + /* Calculate the max amount of raw data per frame from the device */ + hw_bufsize = cam->maxwidth*cam->maxheight*2; + + /* Calculate the max buf. size needed for post-processing routines */ + bpp = (w9968cf_vpp) ? 4 : 2; + if (cam->upscaling) + vpp_bufsize = max(W9968CF_MAX_WIDTH*W9968CF_MAX_HEIGHT*bpp, + cam->maxwidth*cam->maxheight*bpp); + else + vpp_bufsize = cam->maxwidth*cam->maxheight*bpp; + + /* Allocate memory for the isochronous transfer buffers */ + for (i = 0; i < W9968CF_URBS; i++) { + if (!(cam->transfer_buffer[i] = + kzalloc(W9968CF_ISO_PACKETS*p_size, GFP_KERNEL))) { + DBG(1, "Couldn't allocate memory for the isochronous " + "transfer buffers (%u bytes)", + p_size * W9968CF_ISO_PACKETS) + return -ENOMEM; + } + } + + /* Allocate memory for the temporary frame buffer */ + if (!(cam->frame_tmp.buffer = rvmalloc(hw_bufsize))) { + DBG(1, "Couldn't allocate memory for the temporary " + "video frame buffer (%lu bytes)", hw_bufsize) + return -ENOMEM; + } + cam->frame_tmp.size = hw_bufsize; + cam->frame_tmp.number = -1; + + /* Allocate memory for the helper buffer */ + if (w9968cf_vpp) { + if (!(cam->frame_vpp.buffer = rvmalloc(vpp_bufsize))) { + DBG(1, "Couldn't allocate memory for the helper buffer" + " (%lu bytes)", vpp_bufsize) + return -ENOMEM; + } + cam->frame_vpp.size = vpp_bufsize; + } else + cam->frame_vpp.buffer = NULL; + + /* Allocate memory for video frame buffers */ + cam->nbuffers = cam->max_buffers; + while (cam->nbuffers >= 2) { + if ((buff = rvmalloc(cam->nbuffers * vpp_bufsize))) + break; + else + cam->nbuffers--; + } + + if (!buff) { + DBG(1, "Couldn't allocate memory for the video frame buffers") + cam->nbuffers = 0; + return -ENOMEM; + } + + if (cam->nbuffers != cam->max_buffers) + DBG(2, "Couldn't allocate memory for %u video frame buffers. " + "Only memory for %u buffers has been allocated", + cam->max_buffers, cam->nbuffers) + + for (i = 0; i < cam->nbuffers; i++) { + cam->frame[i].buffer = buff + i*vpp_bufsize; + cam->frame[i].size = vpp_bufsize; + cam->frame[i].number = i; + /* Circular list */ + if (i != cam->nbuffers-1) + cam->frame[i].next = &cam->frame[i+1]; + else + cam->frame[i].next = &cam->frame[0]; + cam->frame[i].status = F_UNUSED; + } + + DBG(5, "Memory successfully allocated") + return 0; +} + + + +/**************************************************************************** + * USB-specific functions * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + This is an handler function which is called after the URBs are completed. + It collects multiple data packets coming from the camera by putting them + into frame buffers: one or more zero data length data packets are used to + mark the end of a video frame; the first non-zero data packet is the start + of the next video frame; if an error is encountered in a packet, the entire + video frame is discarded and grabbed again. + If there are no requested frames in the FIFO list, packets are collected into + a temporary buffer. + --------------------------------------------------------------------------*/ +static void w9968cf_urb_complete(struct urb *urb, struct pt_regs *regs) +{ + struct w9968cf_device* cam = (struct w9968cf_device*)urb->context; + struct w9968cf_frame_t** f; + unsigned int len, status; + void* pos; + u8 i; + int err = 0; + + if ((!cam->streaming) || cam->disconnected) { + DBG(4, "Got interrupt, but not streaming") + return; + } + + /* "(*f)" will be used instead of "cam->frame_current" */ + f = &cam->frame_current; + + /* If a frame has been requested and we are grabbing into + the temporary frame, we'll switch to that requested frame */ + if ((*f) == &cam->frame_tmp && *cam->requested_frame) { + if (cam->frame_tmp.status == F_GRABBING) { + w9968cf_pop_frame(cam, &cam->frame_current); + (*f)->status = F_GRABBING; + (*f)->length = cam->frame_tmp.length; + memcpy((*f)->buffer, cam->frame_tmp.buffer, + (*f)->length); + DBG(6, "Switched from temp. frame to frame #%d", + (*f)->number) + } + } + + for (i = 0; i < urb->number_of_packets; i++) { + len = urb->iso_frame_desc[i].actual_length; + status = urb->iso_frame_desc[i].status; + pos = urb->iso_frame_desc[i].offset + urb->transfer_buffer; + + if (status && len != 0) { + DBG(4, "URB failed, error in data packet " + "(error #%u, %s)", + status, symbolic(urb_errlist, status)) + (*f)->status = F_ERROR; + continue; + } + + if (len) { /* start of frame */ + + if ((*f)->status == F_UNUSED) { + (*f)->status = F_GRABBING; + (*f)->length = 0; + } + + /* Buffer overflows shouldn't happen, however...*/ + if ((*f)->length + len > (*f)->size) { + DBG(4, "Buffer overflow: bad data packets") + (*f)->status = F_ERROR; + } + + if ((*f)->status == F_GRABBING) { + memcpy((*f)->buffer + (*f)->length, pos, len); + (*f)->length += len; + } + + } else if ((*f)->status == F_GRABBING) { /* end of frame */ + + DBG(6, "Frame #%d successfully grabbed", (*f)->number) + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + err = w9968cf_vpp->check_headers((*f)->buffer, + (*f)->length); + if (err) { + DBG(4, "Skip corrupted frame: %s", + symbolic(decoder_errlist, err)) + (*f)->status = F_UNUSED; + continue; /* grab this frame again */ + } + } + + (*f)->status = F_READY; + (*f)->queued = 0; + + /* Take a pointer to the new frame from the FIFO list. + If the list is empty,we'll use the temporary frame*/ + if (*cam->requested_frame) + w9968cf_pop_frame(cam, &cam->frame_current); + else { + cam->frame_current = &cam->frame_tmp; + (*f)->status = F_UNUSED; + } + + } else if ((*f)->status == F_ERROR) + (*f)->status = F_UNUSED; /* grab it again */ + + PDBGG("Frame length %lu | pack.#%u | pack.len. %u | state %d", + (unsigned long)(*f)->length, i, len, (*f)->status) + + } /* end for */ + + /* Resubmit this URB */ + urb->dev = cam->usbdev; + urb->status = 0; + spin_lock(&cam->urb_lock); + if (cam->streaming) + if ((err = usb_submit_urb(urb, GFP_ATOMIC))) { + cam->misconfigured = 1; + DBG(1, "Couldn't resubmit the URB: error %d, %s", + err, symbolic(urb_errlist, err)) + } + spin_unlock(&cam->urb_lock); + + /* Wake up the user process */ + wake_up_interruptible(&cam->wait_queue); +} + + +/*--------------------------------------------------------------------------- + Setup the URB structures for the isochronous transfer. + Submit the URBs so that the data transfer begins. + Return 0 on success, a negative number otherwise. + ---------------------------------------------------------------------------*/ +static int w9968cf_start_transfer(struct w9968cf_device* cam) +{ + struct usb_device *udev = cam->usbdev; + struct urb* urb; + const u16 p_size = wMaxPacketSize[cam->altsetting-1]; + u16 w, h, d; + int vidcapt; + u32 t_size; + int err = 0; + s8 i, j; + + for (i = 0; i < W9968CF_URBS; i++) { + urb = usb_alloc_urb(W9968CF_ISO_PACKETS, GFP_KERNEL); + cam->urb[i] = urb; + if (!urb) { + for (j = 0; j < i; j++) + usb_free_urb(cam->urb[j]); + DBG(1, "Couldn't allocate the URB structures") + return -ENOMEM; + } + + urb->dev = udev; + urb->context = (void*)cam; + urb->pipe = usb_rcvisocpipe(udev, 1); + urb->transfer_flags = URB_ISO_ASAP; + urb->number_of_packets = W9968CF_ISO_PACKETS; + urb->complete = w9968cf_urb_complete; + urb->transfer_buffer = cam->transfer_buffer[i]; + urb->transfer_buffer_length = p_size*W9968CF_ISO_PACKETS; + urb->interval = 1; + for (j = 0; j < W9968CF_ISO_PACKETS; j++) { + urb->iso_frame_desc[j].offset = p_size*j; + urb->iso_frame_desc[j].length = p_size; + } + } + + /* Transfer size per frame, in WORD ! */ + d = cam->hw_depth; + w = cam->hw_width; + h = cam->hw_height; + + t_size = (w*h*d)/16; + + err = w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset everything */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */ + + /* Transfer size */ + err += w9968cf_write_reg(cam, t_size & 0xffff, 0x3d); /* low bits */ + err += w9968cf_write_reg(cam, t_size >> 16, 0x3e); /* high bits */ + + if (cam->vpp_flag & VPP_DECOMPRESSION) + err += w9968cf_upload_quantizationtables(cam); + + vidcapt = w9968cf_read_reg(cam, 0x16); /* read picture settings */ + err += w9968cf_write_reg(cam, vidcapt|0x8000, 0x16); /* capt. enable */ + + err += usb_set_interface(udev, 0, cam->altsetting); + err += w9968cf_write_reg(cam, 0x8a05, 0x3c); /* USB FIFO enable */ + + if (err || (vidcapt < 0)) { + for (i = 0; i < W9968CF_URBS; i++) + usb_free_urb(cam->urb[i]); + DBG(1, "Couldn't tell the camera to start the data transfer") + return err; + } + + w9968cf_init_framelist(cam); + + /* Begin to grab into the temporary buffer */ + cam->frame_tmp.status = F_UNUSED; + cam->frame_tmp.queued = 0; + cam->frame_current = &cam->frame_tmp; + + if (!(cam->vpp_flag & VPP_DECOMPRESSION)) + DBG(5, "Isochronous transfer size: %lu bytes/frame", + (unsigned long)t_size*2) + + DBG(5, "Starting the isochronous transfer...") + + cam->streaming = 1; + + /* Submit the URBs */ + for (i = 0; i < W9968CF_URBS; i++) { + err = usb_submit_urb(cam->urb[i], GFP_KERNEL); + if (err) { + cam->streaming = 0; + for (j = i-1; j >= 0; j--) { + usb_kill_urb(cam->urb[j]); + usb_free_urb(cam->urb[j]); + } + DBG(1, "Couldn't send a transfer request to the " + "USB core (error #%d, %s)", err, + symbolic(urb_errlist, err)) + return err; + } + } + + return 0; +} + + +/*-------------------------------------------------------------------------- + Stop the isochronous transfer and set alternate setting to 0 (0Mb/s). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_stop_transfer(struct w9968cf_device* cam) +{ + struct usb_device *udev = cam->usbdev; + unsigned long lock_flags; + int err = 0; + s8 i; + + if (!cam->streaming) + return 0; + + /* This avoids race conditions with usb_submit_urb() + in the URB completition handler */ + spin_lock_irqsave(&cam->urb_lock, lock_flags); + cam->streaming = 0; + spin_unlock_irqrestore(&cam->urb_lock, lock_flags); + + for (i = W9968CF_URBS-1; i >= 0; i--) + if (cam->urb[i]) { + usb_kill_urb(cam->urb[i]); + usb_free_urb(cam->urb[i]); + cam->urb[i] = NULL; + } + + if (cam->disconnected) + goto exit; + + err = w9968cf_write_reg(cam, 0x0a05, 0x3c); /* stop USB transfer */ + err += usb_set_interface(udev, 0, 0); /* 0 Mb/s */ + err += w9968cf_write_reg(cam, 0x0000, 0x39); /* disable JPEG encoder */ + err += w9968cf_write_reg(cam, 0x0000, 0x16); /* stop video capture */ + + if (err) { + DBG(2, "Failed to tell the camera to stop the isochronous " + "transfer. However this is not a critical error.") + return -EIO; + } + +exit: + DBG(5, "Isochronous transfer stopped") + return 0; +} + + +/*-------------------------------------------------------------------------- + Write a W9968CF register. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_reg(struct w9968cf_device* cam, u16 value, u16 index) +{ + struct usb_device* udev = cam->usbdev; + int res; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + value, index, NULL, 0, W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to write a register " + "(value 0x%04X, index 0x%02X, error #%d, %s)", + value, index, res, symbolic(urb_errlist, res)) + + return (res >= 0) ? 0 : -1; +} + + +/*-------------------------------------------------------------------------- + Read a W9968CF register. + Return the register value on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_read_reg(struct w9968cf_device* cam, u16 index) +{ + struct usb_device* udev = cam->usbdev; + u16* buff = cam->control_buffer; + int res; + + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 1, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, index, buff, 2, W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to read a register " + "(index 0x%02X, error #%d, %s)", + index, res, symbolic(urb_errlist, res)) + + return (res >= 0) ? (int)(*buff) : -1; +} + + +/*-------------------------------------------------------------------------- + Write 64-bit data to the fast serial bus registers. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_fsb(struct w9968cf_device* cam, u16* data) +{ + struct usb_device* udev = cam->usbdev; + u16 value; + int res; + + value = *data++; + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0, + USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE, + value, 0x06, data, 6, W9968CF_USB_CTRL_TIMEOUT); + + if (res < 0) + DBG(4, "Failed to write the FSB registers " + "(error #%d, %s)", res, symbolic(urb_errlist, res)) + + return (res >= 0) ? 0 : -1; +} + + +/*-------------------------------------------------------------------------- + Write data to the serial bus control register. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_write_sb(struct w9968cf_device* cam, u16 value) +{ + int err = 0; + + err = w9968cf_write_reg(cam, value, 0x01); + udelay(W9968CF_I2C_BUS_DELAY); + + return err; +} + + +/*-------------------------------------------------------------------------- + Read data from the serial bus control register. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_read_sb(struct w9968cf_device* cam) +{ + int v = 0; + + v = w9968cf_read_reg(cam, 0x01); + udelay(W9968CF_I2C_BUS_DELAY); + + return v; +} + + +/*-------------------------------------------------------------------------- + Upload quantization tables for the JPEG compression. + This function is called by w9968cf_start_transfer(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_upload_quantizationtables(struct w9968cf_device* cam) +{ + u16 a, b; + int err = 0, i, j; + + err += w9968cf_write_reg(cam, 0x0010, 0x39); /* JPEG clock enable */ + + for (i = 0, j = 0; i < 32; i++, j += 2) { + a = Y_QUANTABLE[j] | ((unsigned)(Y_QUANTABLE[j+1]) << 8); + b = UV_QUANTABLE[j] | ((unsigned)(UV_QUANTABLE[j+1]) << 8); + err += w9968cf_write_reg(cam, a, 0x40+i); + err += w9968cf_write_reg(cam, b, 0x60+i); + } + err += w9968cf_write_reg(cam, 0x0012, 0x39); /* JPEG encoder enable */ + + return err; +} + + + +/**************************************************************************** + * Low-level I2C I/O functions. * + * The adapter supports the following I2C transfer functions: * + * i2c_adap_fastwrite_byte_data() (at 400 kHz bit frequency only) * + * i2c_adap_read_byte_data() * + * i2c_adap_read_byte() * + ****************************************************************************/ + +static int w9968cf_smbus_start(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + + return err; +} + + +static int w9968cf_smbus_stop(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */ + + return err; +} + + +static int w9968cf_smbus_write_byte(struct w9968cf_device* cam, u8 v) +{ + u8 bit; + int err = 0, sda; + + for (bit = 0 ; bit < 8 ; bit++) { + sda = (v & 0x80) ? 2 : 0; + v <<= 1; + /* SDE=1, SDA=sda, SCL=0 */ + err += w9968cf_write_sb(cam, 0x10 | sda); + /* SDE=1, SDA=sda, SCL=1 */ + err += w9968cf_write_sb(cam, 0x11 | sda); + /* SDE=1, SDA=sda, SCL=0 */ + err += w9968cf_write_sb(cam, 0x10 | sda); + } + + return err; +} + + +static int w9968cf_smbus_read_byte(struct w9968cf_device* cam, u8* v) +{ + u8 bit; + int err = 0; + + *v = 0; + for (bit = 0 ; bit < 8 ; bit++) { + *v <<= 1; + err += w9968cf_write_sb(cam, 0x0013); + *v |= (w9968cf_read_sb(cam) & 0x0008) ? 1 : 0; + err += w9968cf_write_sb(cam, 0x0012); + } + + return err; +} + + +static int w9968cf_smbus_write_ack(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + err += w9968cf_write_sb(cam, 0x0011); /* SDE=1, SDA=0, SCL=1 */ + err += w9968cf_write_sb(cam, 0x0010); /* SDE=1, SDA=0, SCL=0 */ + + return err; +} + + +static int w9968cf_smbus_read_ack(struct w9968cf_device* cam) +{ + int err = 0, sda; + + err += w9968cf_write_sb(cam, 0x0013); /* SDE=1, SDA=1, SCL=1 */ + sda = (w9968cf_read_sb(cam) & 0x08) ? 1 : 0; /* sda = SDA */ + err += w9968cf_write_sb(cam, 0x0012); /* SDE=1, SDA=1, SCL=0 */ + if (sda < 0) + err += sda; + if (sda == 1) { + DBG(6, "Couldn't receive the ACK") + err += -1; + } + + return err; +} + + +/* This seems to refresh the communication through the serial bus */ +static int w9968cf_smbus_refresh_bus(struct w9968cf_device* cam) +{ + int err = 0, j; + + for (j = 1; j <= 10; j++) { + err = w9968cf_write_reg(cam, 0x0020, 0x01); + err += w9968cf_write_reg(cam, 0x0000, 0x01); + if (err) + break; + } + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Subaddr [A] Value [A] P */ +static int +w9968cf_i2c_adap_fastwrite_byte_data(struct w9968cf_device* cam, + u16 address, u8 subaddress,u8 value) +{ + u16* data = cam->data_buffer; + int err = 0; + + err += w9968cf_smbus_refresh_bus(cam); + + /* Enable SBUS outputs */ + err += w9968cf_write_sb(cam, 0x0020); + + data[0] = 0x082f | ((address & 0x80) ? 0x1500 : 0x0); + data[0] |= (address & 0x40) ? 0x4000 : 0x0; + data[1] = 0x2082 | ((address & 0x40) ? 0x0005 : 0x0); + data[1] |= (address & 0x20) ? 0x0150 : 0x0; + data[1] |= (address & 0x10) ? 0x5400 : 0x0; + data[2] = 0x8208 | ((address & 0x08) ? 0x0015 : 0x0); + data[2] |= (address & 0x04) ? 0x0540 : 0x0; + data[2] |= (address & 0x02) ? 0x5000 : 0x0; + data[3] = 0x1d20 | ((address & 0x02) ? 0x0001 : 0x0); + data[3] |= (address & 0x01) ? 0x0054 : 0x0; + + err += w9968cf_write_fsb(cam, data); + + data[0] = 0x8208 | ((subaddress & 0x80) ? 0x0015 : 0x0); + data[0] |= (subaddress & 0x40) ? 0x0540 : 0x0; + data[0] |= (subaddress & 0x20) ? 0x5000 : 0x0; + data[1] = 0x0820 | ((subaddress & 0x20) ? 0x0001 : 0x0); + data[1] |= (subaddress & 0x10) ? 0x0054 : 0x0; + data[1] |= (subaddress & 0x08) ? 0x1500 : 0x0; + data[1] |= (subaddress & 0x04) ? 0x4000 : 0x0; + data[2] = 0x2082 | ((subaddress & 0x04) ? 0x0005 : 0x0); + data[2] |= (subaddress & 0x02) ? 0x0150 : 0x0; + data[2] |= (subaddress & 0x01) ? 0x5400 : 0x0; + data[3] = 0x001d; + + err += w9968cf_write_fsb(cam, data); + + data[0] = 0x8208 | ((value & 0x80) ? 0x0015 : 0x0); + data[0] |= (value & 0x40) ? 0x0540 : 0x0; + data[0] |= (value & 0x20) ? 0x5000 : 0x0; + data[1] = 0x0820 | ((value & 0x20) ? 0x0001 : 0x0); + data[1] |= (value & 0x10) ? 0x0054 : 0x0; + data[1] |= (value & 0x08) ? 0x1500 : 0x0; + data[1] |= (value & 0x04) ? 0x4000 : 0x0; + data[2] = 0x2082 | ((value & 0x04) ? 0x0005 : 0x0); + data[2] |= (value & 0x02) ? 0x0150 : 0x0; + data[2] |= (value & 0x01) ? 0x5400 : 0x0; + data[3] = 0xfe1d; + + err += w9968cf_write_fsb(cam, data); + + /* Disable SBUS outputs */ + err += w9968cf_write_sb(cam, 0x0000); + + if (!err) + DBG(5, "I2C write byte data done, addr.0x%04X, subaddr.0x%02X " + "value 0x%02X", address, subaddress, value) + else + DBG(5, "I2C write byte data failed, addr.0x%04X, " + "subaddr.0x%02X, value 0x%02X", + address, subaddress, value) + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Subaddr [A] P S Addr+1 Rd [A] [Value] NA P */ +static int +w9968cf_i2c_adap_read_byte_data(struct w9968cf_device* cam, + u16 address, u8 subaddress, + u8* value) +{ + int err = 0; + + /* Serial data enable */ + err += w9968cf_write_sb(cam, 0x0013); /* don't change ! */ + + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_write_byte(cam, subaddress); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_stop(cam); + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address + 1); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_read_byte(cam, value); + err += w9968cf_smbus_write_ack(cam); + err += w9968cf_smbus_stop(cam); + + /* Serial data disable */ + err += w9968cf_write_sb(cam, 0x0000); + + if (!err) + DBG(5, "I2C read byte data done, addr.0x%04X, " + "subaddr.0x%02X, value 0x%02X", + address, subaddress, *value) + else + DBG(5, "I2C read byte data failed, addr.0x%04X, " + "subaddr.0x%02X, wrong value 0x%02X", + address, subaddress, *value) + + return err; +} + + +/* SMBus protocol: S Addr+1 Rd [A] [Value] NA P */ +static int +w9968cf_i2c_adap_read_byte(struct w9968cf_device* cam, + u16 address, u8* value) +{ + int err = 0; + + /* Serial data enable */ + err += w9968cf_write_sb(cam, 0x0013); + + err += w9968cf_smbus_start(cam); + err += w9968cf_smbus_write_byte(cam, address + 1); + err += w9968cf_smbus_read_ack(cam); + err += w9968cf_smbus_read_byte(cam, value); + err += w9968cf_smbus_write_ack(cam); + err += w9968cf_smbus_stop(cam); + + /* Serial data disable */ + err += w9968cf_write_sb(cam, 0x0000); + + if (!err) + DBG(5, "I2C read byte done, addr.0x%04X, " + "value 0x%02X", address, *value) + else + DBG(5, "I2C read byte failed, addr.0x%04X, " + "wrong value 0x%02X", address, *value) + + return err; +} + + +/* SMBus protocol: S Addr Wr [A] Value [A] P */ +static int +w9968cf_i2c_adap_write_byte(struct w9968cf_device* cam, + u16 address, u8 value) +{ + DBG(4, "i2c_write_byte() is an unsupported transfer mode") + return -EINVAL; +} + + + +/**************************************************************************** + * I2C interface to kernel * + ****************************************************************************/ + +static int +w9968cf_i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct w9968cf_device* cam = i2c_get_adapdata(adapter); + u8 i; + int err = 0; + + switch (addr) { + case OV6xx0_SID: + case OV7xx0_SID: + break; + default: + DBG(4, "Rejected slave ID 0x%04X", addr) + return -EINVAL; + } + + if (size == I2C_SMBUS_BYTE) { + /* Why addr <<= 1? See OVXXX0_SID defines in ovcamchip.h */ + addr <<= 1; + + if (read_write == I2C_SMBUS_WRITE) + err = w9968cf_i2c_adap_write_byte(cam, addr, command); + else if (read_write == I2C_SMBUS_READ) + err = w9968cf_i2c_adap_read_byte(cam,addr,&data->byte); + + } else if (size == I2C_SMBUS_BYTE_DATA) { + addr <<= 1; + + if (read_write == I2C_SMBUS_WRITE) + err = w9968cf_i2c_adap_fastwrite_byte_data(cam, addr, + command, data->byte); + else if (read_write == I2C_SMBUS_READ) { + for (i = 1; i <= W9968CF_I2C_RW_RETRIES; i++) { + err = w9968cf_i2c_adap_read_byte_data(cam,addr, + command, &data->byte); + if (err) { + if (w9968cf_smbus_refresh_bus(cam)) { + err = -EIO; + break; + } + } else + break; + } + + } else + return -EINVAL; + + } else { + DBG(4, "Unsupported I2C transfer mode (%d)", size) + return -EINVAL; + } + + return err; +} + + +static u32 w9968cf_i2c_func(struct i2c_adapter* adap) +{ + return I2C_FUNC_SMBUS_READ_BYTE | + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + + +static int w9968cf_i2c_attach_inform(struct i2c_client* client) +{ + struct w9968cf_device* cam = i2c_get_adapdata(client->adapter); + int id = client->driver->id, err = 0; + + if (id == I2C_DRIVERID_OVCAMCHIP) { + cam->sensor_client = client; + err = w9968cf_sensor_init(cam); + if (err) { + cam->sensor_client = NULL; + return err; + } + } else { + DBG(4, "Rejected client [%s] with driver [%s]", + client->name, client->driver->driver.name) + return -EINVAL; + } + + DBG(5, "I2C attach client [%s] with driver [%s]", + client->name, client->driver->driver.name) + + return 0; +} + + +static int w9968cf_i2c_detach_inform(struct i2c_client* client) +{ + struct w9968cf_device* cam = i2c_get_adapdata(client->adapter); + + if (cam->sensor_client == client) + cam->sensor_client = NULL; + + DBG(5, "I2C detach client [%s]", client->name) + + return 0; +} + + +static int +w9968cf_i2c_control(struct i2c_adapter* adapter, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + + +static int w9968cf_i2c_init(struct w9968cf_device* cam) +{ + int err = 0; + + static struct i2c_algorithm algo = { + .smbus_xfer = w9968cf_i2c_smbus_xfer, + .algo_control = w9968cf_i2c_control, + .functionality = w9968cf_i2c_func, + }; + + static struct i2c_adapter adap = { + .id = I2C_HW_SMBUS_W9968CF, + .class = I2C_CLASS_CAM_DIGITAL, + .owner = THIS_MODULE, + .client_register = w9968cf_i2c_attach_inform, + .client_unregister = w9968cf_i2c_detach_inform, + .algo = &algo, + }; + + memcpy(&cam->i2c_adapter, &adap, sizeof(struct i2c_adapter)); + strcpy(cam->i2c_adapter.name, "w9968cf"); + i2c_set_adapdata(&cam->i2c_adapter, cam); + + DBG(6, "Registering I2C adapter with kernel...") + + err = i2c_add_adapter(&cam->i2c_adapter); + if (err) + DBG(1, "Failed to register the I2C adapter") + else + DBG(5, "I2C adapter registered") + + return err; +} + + + +/**************************************************************************** + * Helper functions * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + Turn on the LED on some webcams. A beep should be heard too. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_turn_on_led(struct w9968cf_device* cam) +{ + int err = 0; + + err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power-down */ + err += w9968cf_write_reg(cam, 0xbf17, 0x00); /* reset everything */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* normal operation */ + err += w9968cf_write_reg(cam, 0x0010, 0x01); /* serial bus, SDS high */ + err += w9968cf_write_reg(cam, 0x0000, 0x01); /* serial bus, SDS low */ + err += w9968cf_write_reg(cam, 0x0010, 0x01); /* ..high 'beep-beep' */ + + if (err) + DBG(2, "Couldn't turn on the LED") + + DBG(5, "LED turned on") + + return err; +} + + +/*-------------------------------------------------------------------------- + Write some registers for the device initialization. + This function is called once on open(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_init_chip(struct w9968cf_device* cam) +{ + unsigned long hw_bufsize = cam->maxwidth*cam->maxheight*2, + y0 = 0x0000, + u0 = y0 + hw_bufsize/2, + v0 = u0 + hw_bufsize/4, + y1 = v0 + hw_bufsize/4, + u1 = y1 + hw_bufsize/2, + v1 = u1 + hw_bufsize/4; + int err = 0; + + err += w9968cf_write_reg(cam, 0xff00, 0x00); /* power off */ + err += w9968cf_write_reg(cam, 0xbf10, 0x00); /* power on */ + + err += w9968cf_write_reg(cam, 0x405d, 0x03); /* DRAM timings */ + err += w9968cf_write_reg(cam, 0x0030, 0x04); /* SDRAM timings */ + + err += w9968cf_write_reg(cam, y0 & 0xffff, 0x20); /* Y buf.0, low */ + err += w9968cf_write_reg(cam, y0 >> 16, 0x21); /* Y buf.0, high */ + err += w9968cf_write_reg(cam, u0 & 0xffff, 0x24); /* U buf.0, low */ + err += w9968cf_write_reg(cam, u0 >> 16, 0x25); /* U buf.0, high */ + err += w9968cf_write_reg(cam, v0 & 0xffff, 0x28); /* V buf.0, low */ + err += w9968cf_write_reg(cam, v0 >> 16, 0x29); /* V buf.0, high */ + + err += w9968cf_write_reg(cam, y1 & 0xffff, 0x22); /* Y buf.1, low */ + err += w9968cf_write_reg(cam, y1 >> 16, 0x23); /* Y buf.1, high */ + err += w9968cf_write_reg(cam, u1 & 0xffff, 0x26); /* U buf.1, low */ + err += w9968cf_write_reg(cam, u1 >> 16, 0x27); /* U buf.1, high */ + err += w9968cf_write_reg(cam, v1 & 0xffff, 0x2a); /* V buf.1, low */ + err += w9968cf_write_reg(cam, v1 >> 16, 0x2b); /* V buf.1, high */ + + err += w9968cf_write_reg(cam, y1 & 0xffff, 0x32); /* JPEG buf 0 low */ + err += w9968cf_write_reg(cam, y1 >> 16, 0x33); /* JPEG buf 0 high */ + + err += w9968cf_write_reg(cam, y1 & 0xffff, 0x34); /* JPEG buf 1 low */ + err += w9968cf_write_reg(cam, y1 >> 16, 0x35); /* JPEG bug 1 high */ + + err += w9968cf_write_reg(cam, 0x0000, 0x36);/* JPEG restart interval */ + err += w9968cf_write_reg(cam, 0x0804, 0x37);/*JPEG VLE FIFO threshold*/ + err += w9968cf_write_reg(cam, 0x0000, 0x38);/* disable hw up-scaling */ + err += w9968cf_write_reg(cam, 0x0000, 0x3f); /* JPEG/MCTL test data */ + + err += w9968cf_set_picture(cam, cam->picture); /* this before */ + err += w9968cf_set_window(cam, cam->window); + + if (err) + DBG(1, "Chip initialization failed") + else + DBG(5, "Chip successfully initialized") + + return err; +} + + +/*-------------------------------------------------------------------------- + Return non-zero if the palette is supported, 0 otherwise. + --------------------------------------------------------------------------*/ +static inline u16 w9968cf_valid_palette(u16 palette) +{ + u8 i = 0; + while (w9968cf_formatlist[i].palette != 0) { + if (palette == w9968cf_formatlist[i].palette) + return palette; + i++; + } + return 0; +} + + +/*-------------------------------------------------------------------------- + Return the depth corresponding to the given palette. + Palette _must_ be supported ! + --------------------------------------------------------------------------*/ +static inline u16 w9968cf_valid_depth(u16 palette) +{ + u8 i=0; + while (w9968cf_formatlist[i].palette != palette) + i++; + + return w9968cf_formatlist[i].depth; +} + + +/*-------------------------------------------------------------------------- + Return non-zero if the format requires decompression, 0 otherwise. + --------------------------------------------------------------------------*/ +static inline u8 w9968cf_need_decompression(u16 palette) +{ + u8 i = 0; + while (w9968cf_formatlist[i].palette != 0) { + if (palette == w9968cf_formatlist[i].palette) + return w9968cf_formatlist[i].compression; + i++; + } + return 0; +} + + +/*-------------------------------------------------------------------------- + Change the picture settings of the camera. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_set_picture(struct w9968cf_device* cam, struct video_picture pict) +{ + u16 fmt, hw_depth, hw_palette, reg_v = 0x0000; + int err = 0; + + /* Make sure we are using a valid depth */ + pict.depth = w9968cf_valid_depth(pict.palette); + + fmt = pict.palette; + + hw_depth = pict.depth; /* depth used by the winbond chip */ + hw_palette = pict.palette; /* palette used by the winbond chip */ + + /* VS & HS polarities */ + reg_v = (cam->vs_polarity << 12) | (cam->hs_polarity << 11); + + switch (fmt) + { + case VIDEO_PALETTE_UYVY: + reg_v |= 0x0000; + cam->vpp_flag = VPP_NONE; + break; + case VIDEO_PALETTE_YUV422P: + reg_v |= 0x0002; + cam->vpp_flag = VPP_DECOMPRESSION; + break; + case VIDEO_PALETTE_YUV420: + case VIDEO_PALETTE_YUV420P: + reg_v |= 0x0003; + cam->vpp_flag = VPP_DECOMPRESSION; + break; + case VIDEO_PALETTE_YUYV: + case VIDEO_PALETTE_YUV422: + reg_v |= 0x0000; + cam->vpp_flag = VPP_SWAP_YUV_BYTES; + hw_palette = VIDEO_PALETTE_UYVY; + break; + /* Original video is used instead of RGBX palettes. + Software conversion later. */ + case VIDEO_PALETTE_GREY: + case VIDEO_PALETTE_RGB555: + case VIDEO_PALETTE_RGB565: + case VIDEO_PALETTE_RGB24: + case VIDEO_PALETTE_RGB32: + reg_v |= 0x0000; /* UYVY 16 bit is used */ + hw_depth = 16; + hw_palette = VIDEO_PALETTE_UYVY; + cam->vpp_flag = VPP_UYVY_TO_RGBX; + break; + } + + /* NOTE: due to memory issues, it is better to disable the hardware + double buffering during compression */ + if (cam->double_buffer && !(cam->vpp_flag & VPP_DECOMPRESSION)) + reg_v |= 0x0080; + + if (cam->clamping) + reg_v |= 0x0020; + + if (cam->filter_type == 1) + reg_v |= 0x0008; + else if (cam->filter_type == 2) + reg_v |= 0x000c; + + if ((err = w9968cf_write_reg(cam, reg_v, 0x16))) + goto error; + + if ((err = w9968cf_sensor_update_picture(cam, pict))) + goto error; + + /* If all went well, update the device data structure */ + memcpy(&cam->picture, &pict, sizeof(pict)); + cam->hw_depth = hw_depth; + cam->hw_palette = hw_palette; + + /* Settings changed, so we clear the frame buffers */ + memset(cam->frame[0].buffer, 0, cam->nbuffers*cam->frame[0].size); + + DBG(4, "Palette is %s, depth is %u bpp", + symbolic(v4l1_plist, pict.palette), pict.depth) + + return 0; + +error: + DBG(1, "Failed to change picture settings") + return err; +} + + +/*-------------------------------------------------------------------------- + Change the capture area size of the camera. + This function _must_ be called _after_ w9968cf_set_picture(). + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_set_window(struct w9968cf_device* cam, struct video_window win) +{ + u16 x, y, w, h, scx, scy, cw, ch, ax, ay; + unsigned long fw, fh; + struct ovcamchip_window s_win; + int err = 0; + + /* Work around to avoid FP arithmetics */ + #define __SC(x) ((x) << 10) + #define __UNSC(x) ((x) >> 10) + + /* Make sure we are using a supported resolution */ + if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width, + (u16*)&win.height))) + goto error; + + /* Scaling factors */ + fw = __SC(win.width) / cam->maxwidth; + fh = __SC(win.height) / cam->maxheight; + + /* Set up the width and height values used by the chip */ + if ((win.width > cam->maxwidth) || (win.height > cam->maxheight)) { + cam->vpp_flag |= VPP_UPSCALE; + /* Calculate largest w,h mantaining the same w/h ratio */ + w = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh; + h = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight; + if (w < cam->minwidth) /* just in case */ + w = cam->minwidth; + if (h < cam->minheight) /* just in case */ + h = cam->minheight; + } else { + cam->vpp_flag &= ~VPP_UPSCALE; + w = win.width; + h = win.height; + } + + /* x,y offsets of the cropped area */ + scx = cam->start_cropx; + scy = cam->start_cropy; + + /* Calculate cropped area manteining the right w/h ratio */ + if (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) { + cw = (fw >= fh) ? cam->maxwidth : __SC(win.width)/fh; + ch = (fw >= fh) ? __SC(win.height)/fw : cam->maxheight; + } else { + cw = w; + ch = h; + } + + /* Setup the window of the sensor */ + s_win.format = VIDEO_PALETTE_UYVY; + s_win.width = cam->maxwidth; + s_win.height = cam->maxheight; + s_win.quarter = 0; /* full progressive video */ + + /* Center it */ + s_win.x = (s_win.width - cw) / 2; + s_win.y = (s_win.height - ch) / 2; + + /* Clock divisor */ + if (cam->clockdiv >= 0) + s_win.clockdiv = cam->clockdiv; /* manual override */ + else + switch (cam->sensor) { + case CC_OV6620: + s_win.clockdiv = 0; + break; + case CC_OV6630: + s_win.clockdiv = 0; + break; + case CC_OV76BE: + case CC_OV7610: + case CC_OV7620: + s_win.clockdiv = 0; + break; + default: + s_win.clockdiv = W9968CF_DEF_CLOCKDIVISOR; + } + + /* We have to scale win.x and win.y offsets */ + if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) + || (cam->vpp_flag & VPP_UPSCALE) ) { + ax = __SC(win.x)/fw; + ay = __SC(win.y)/fh; + } else { + ax = win.x; + ay = win.y; + } + + if ((ax + cw) > cam->maxwidth) + ax = cam->maxwidth - cw; + + if ((ay + ch) > cam->maxheight) + ay = cam->maxheight - ch; + + /* Adjust win.x, win.y */ + if ( (cam->largeview && !(cam->vpp_flag & VPP_UPSCALE)) + || (cam->vpp_flag & VPP_UPSCALE) ) { + win.x = __UNSC(ax*fw); + win.y = __UNSC(ay*fh); + } else { + win.x = ax; + win.y = ay; + } + + /* Offsets used by the chip */ + x = ax + s_win.x; + y = ay + s_win.y; + + /* Go ! */ + if ((err = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_MODE, &s_win))) + goto error; + + err += w9968cf_write_reg(cam, scx + x, 0x10); + err += w9968cf_write_reg(cam, scy + y, 0x11); + err += w9968cf_write_reg(cam, scx + x + cw, 0x12); + err += w9968cf_write_reg(cam, scy + y + ch, 0x13); + err += w9968cf_write_reg(cam, w, 0x14); + err += w9968cf_write_reg(cam, h, 0x15); + + /* JPEG width & height */ + err += w9968cf_write_reg(cam, w, 0x30); + err += w9968cf_write_reg(cam, h, 0x31); + + /* Y & UV frame buffer strides (in WORD) */ + if (cam->vpp_flag & VPP_DECOMPRESSION) { + err += w9968cf_write_reg(cam, w/2, 0x2c); + err += w9968cf_write_reg(cam, w/4, 0x2d); + } else + err += w9968cf_write_reg(cam, w, 0x2c); + + if (err) + goto error; + + /* If all went well, update the device data structure */ + memcpy(&cam->window, &win, sizeof(win)); + cam->hw_width = w; + cam->hw_height = h; + + /* Settings changed, so we clear the frame buffers */ + memset(cam->frame[0].buffer, 0, cam->nbuffers*cam->frame[0].size); + + DBG(4, "The capture area is %dx%d, Offset (x,y)=(%u,%u)", + win.width, win.height, win.x, win.y) + + PDBGG("x=%u ,y=%u, w=%u, h=%u, ax=%u, ay=%u, s_win.x=%u, s_win.y=%u, " + "cw=%u, ch=%u, win.x=%u, win.y=%u, win.width=%u, win.height=%u", + x, y, w, h, ax, ay, s_win.x, s_win.y, cw, ch, win.x, win.y, + win.width, win.height) + + return 0; + +error: + DBG(1, "Failed to change the capture area size") + return err; +} + + +/*-------------------------------------------------------------------------- + Adjust the asked values for window width and height. + Return 0 on success, -1 otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_adjust_window_size(struct w9968cf_device* cam, u16* width, u16* height) +{ + u16 maxw, maxh; + + if ((*width < cam->minwidth) || (*height < cam->minheight)) + return -ERANGE; + + maxw = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION) && + w9968cf_vpp ? max((u16)W9968CF_MAX_WIDTH, cam->maxwidth) + : cam->maxwidth; + maxh = cam->upscaling && !(cam->vpp_flag & VPP_DECOMPRESSION) && + w9968cf_vpp ? max((u16)W9968CF_MAX_HEIGHT, cam->maxheight) + : cam->maxheight; + + if (*width > maxw) + *width = maxw; + if (*height > maxh) + *height = maxh; + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + *width &= ~15L; /* multiple of 16 */ + *height &= ~15L; + } + + PDBGG("Window size adjusted w=%u, h=%u ", *width, *height) + + return 0; +} + + +/*-------------------------------------------------------------------------- + Initialize the FIFO list of requested frames. + --------------------------------------------------------------------------*/ +static void w9968cf_init_framelist(struct w9968cf_device* cam) +{ + u8 i; + + for (i = 0; i < cam->nbuffers; i++) { + cam->requested_frame[i] = NULL; + cam->frame[i].queued = 0; + cam->frame[i].status = F_UNUSED; + } +} + + +/*-------------------------------------------------------------------------- + Add a frame in the FIFO list of requested frames. + This function is called in process context. + --------------------------------------------------------------------------*/ +static void w9968cf_push_frame(struct w9968cf_device* cam, u8 f_num) +{ + u8 f; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->flist_lock, lock_flags); + + for (f=0; cam->requested_frame[f] != NULL; f++); + cam->requested_frame[f] = &cam->frame[f_num]; + cam->frame[f_num].queued = 1; + cam->frame[f_num].status = F_UNUSED; /* clear the status */ + + spin_unlock_irqrestore(&cam->flist_lock, lock_flags); + + DBG(6, "Frame #%u pushed into the FIFO list. Position %u", f_num, f) +} + + +/*-------------------------------------------------------------------------- + Read, store and remove the first pointer in the FIFO list of requested + frames. This function is called in interrupt context. + --------------------------------------------------------------------------*/ +static void +w9968cf_pop_frame(struct w9968cf_device* cam, struct w9968cf_frame_t** framep) +{ + u8 i; + + spin_lock(&cam->flist_lock); + + *framep = cam->requested_frame[0]; + + /* Shift the list of pointers */ + for (i = 0; i < cam->nbuffers-1; i++) + cam->requested_frame[i] = cam->requested_frame[i+1]; + cam->requested_frame[i] = NULL; + + spin_unlock(&cam->flist_lock); + + DBG(6,"Popped frame #%d from the list", (*framep)->number) +} + + +/*-------------------------------------------------------------------------- + High-level video post-processing routine on grabbed frames. + Return 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_postprocess_frame(struct w9968cf_device* cam, + struct w9968cf_frame_t* fr) +{ + void *pIn = fr->buffer, *pOut = cam->frame_vpp.buffer, *tmp; + u16 w = cam->window.width, + h = cam->window.height, + d = cam->picture.depth, + fmt = cam->picture.palette, + rgb = cam->force_rgb, + hw_w = cam->hw_width, + hw_h = cam->hw_height, + hw_d = cam->hw_depth; + int err = 0; + + #define _PSWAP(pIn, pOut) {tmp = (pIn); (pIn) = (pOut); (pOut) = tmp;} + + if (cam->vpp_flag & VPP_DECOMPRESSION) { + memcpy(pOut, pIn, fr->length); + _PSWAP(pIn, pOut) + err = w9968cf_vpp->decode(pIn, fr->length, hw_w, hw_h, pOut); + PDBGG("Compressed frame length: %lu",(unsigned long)fr->length) + fr->length = (hw_w*hw_h*hw_d)/8; + _PSWAP(pIn, pOut) + if (err) { + DBG(4, "An error occurred while decoding the frame: " + "%s", symbolic(decoder_errlist, err)) + return err; + } else + DBG(6, "Frame decoded") + } + + if (cam->vpp_flag & VPP_SWAP_YUV_BYTES) { + w9968cf_vpp->swap_yuvbytes(pIn, fr->length); + DBG(6, "Original UYVY component ordering changed") + } + + if (cam->vpp_flag & VPP_UPSCALE) { + w9968cf_vpp->scale_up(pIn, pOut, hw_w, hw_h, hw_d, w, h); + fr->length = (w*h*hw_d)/8; + _PSWAP(pIn, pOut) + DBG(6, "Vertical up-scaling done: %u,%u,%ubpp->%u,%u", + hw_w, hw_h, hw_d, w, h) + } + + if (cam->vpp_flag & VPP_UYVY_TO_RGBX) { + w9968cf_vpp->uyvy_to_rgbx(pIn, fr->length, pOut, fmt, rgb); + fr->length = (w*h*d)/8; + _PSWAP(pIn, pOut) + DBG(6, "UYVY-16bit to %s conversion done", + symbolic(v4l1_plist, fmt)) + } + + if (pOut == fr->buffer) + memcpy(fr->buffer, cam->frame_vpp.buffer, fr->length); + + return 0; +} + + + +/**************************************************************************** + * Image sensor control routines * + ****************************************************************************/ + +static int +w9968cf_sensor_set_control(struct w9968cf_device* cam, int cid, int val) +{ + struct ovcamchip_control ctl; + int err; + + ctl.id = cid; + ctl.value = val; + + err = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_S_CTRL, &ctl); + + return err; +} + + +static int +w9968cf_sensor_get_control(struct w9968cf_device* cam, int cid, int* val) +{ + struct ovcamchip_control ctl; + int err; + + ctl.id = cid; + + err = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_G_CTRL, &ctl); + if (!err) + *val = ctl.value; + + return err; +} + + +static int +w9968cf_sensor_cmd(struct w9968cf_device* cam, unsigned int cmd, void* arg) +{ + struct i2c_client* c = cam->sensor_client; + int rc = 0; + + if (!c || !c->driver || !c->driver->command) + return -EINVAL; + + rc = c->driver->command(c, cmd, arg); + /* The I2C driver returns -EPERM on non-supported controls */ + return (rc < 0 && rc != -EPERM) ? rc : 0; +} + + +/*-------------------------------------------------------------------------- + Update some settings of the image sensor. + Returns: 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_sensor_update_settings(struct w9968cf_device* cam) +{ + int err = 0; + + /* Auto brightness */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOBRIGHT, + cam->auto_brt); + if (err) + return err; + + /* Auto exposure */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_AUTOEXP, + cam->auto_exp); + if (err) + return err; + + /* Banding filter */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BANDFILT, + cam->bandfilt); + if (err) + return err; + + /* Light frequency */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_FREQ, + cam->lightfreq); + if (err) + return err; + + /* Back light */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BACKLIGHT, + cam->backlight); + if (err) + return err; + + /* Mirror */ + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_MIRROR, + cam->mirror); + if (err) + return err; + + return 0; +} + + +/*-------------------------------------------------------------------------- + Get some current picture settings from the image sensor and update the + internal 'picture' structure of the camera. + Returns: 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_sensor_get_picture(struct w9968cf_device* cam) +{ + int err, v; + + err = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_CONT, &v); + if (err) + return err; + cam->picture.contrast = v; + + err = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_BRIGHT, &v); + if (err) + return err; + cam->picture.brightness = v; + + err = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_SAT, &v); + if (err) + return err; + cam->picture.colour = v; + + err = w9968cf_sensor_get_control(cam, OVCAMCHIP_CID_HUE, &v); + if (err) + return err; + cam->picture.hue = v; + + DBG(5, "Got picture settings from the image sensor") + + PDBGG("Brightness, contrast, hue, colour, whiteness are " + "%u,%u,%u,%u,%u", cam->picture.brightness,cam->picture.contrast, + cam->picture.hue, cam->picture.colour, cam->picture.whiteness) + + return 0; +} + + +/*-------------------------------------------------------------------------- + Update picture settings of the image sensor. + Returns: 0 on success, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int +w9968cf_sensor_update_picture(struct w9968cf_device* cam, + struct video_picture pict) +{ + int err = 0; + + if ((!cam->sensor_initialized) + || pict.contrast != cam->picture.contrast) { + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_CONT, + pict.contrast); + if (err) + goto fail; + DBG(4, "Contrast changed from %u to %u", + cam->picture.contrast, pict.contrast) + cam->picture.contrast = pict.contrast; + } + + if (((!cam->sensor_initialized) || + pict.brightness != cam->picture.brightness) && (!cam->auto_brt)) { + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_BRIGHT, + pict.brightness); + if (err) + goto fail; + DBG(4, "Brightness changed from %u to %u", + cam->picture.brightness, pict.brightness) + cam->picture.brightness = pict.brightness; + } + + if ((!cam->sensor_initialized) || pict.colour != cam->picture.colour) { + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_SAT, + pict.colour); + if (err) + goto fail; + DBG(4, "Colour changed from %u to %u", + cam->picture.colour, pict.colour) + cam->picture.colour = pict.colour; + } + + if ((!cam->sensor_initialized) || pict.hue != cam->picture.hue) { + err = w9968cf_sensor_set_control(cam, OVCAMCHIP_CID_HUE, + pict.hue); + if (err) + goto fail; + DBG(4, "Hue changed from %u to %u", + cam->picture.hue, pict.hue) + cam->picture.hue = pict.hue; + } + + return 0; + +fail: + DBG(4, "Failed to change sensor picture setting") + return err; +} + + + +/**************************************************************************** + * Camera configuration * + ****************************************************************************/ + +/*-------------------------------------------------------------------------- + This function is called when a supported image sensor is detected. + Return 0 if the initialization succeeds, a negative number otherwise. + --------------------------------------------------------------------------*/ +static int w9968cf_sensor_init(struct w9968cf_device* cam) +{ + int err = 0; + + if ((err = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_INITIALIZE, + &cam->monochrome))) + goto error; + + if ((err = w9968cf_sensor_cmd(cam, OVCAMCHIP_CMD_Q_SUBTYPE, + &cam->sensor))) + goto error; + + /* NOTE: Make sure width and height are a multiple of 16 */ + switch (cam->sensor_client->addr) { + case OV6xx0_SID: + cam->maxwidth = 352; + cam->maxheight = 288; + cam->minwidth = 64; + cam->minheight = 48; + break; + case OV7xx0_SID: + cam->maxwidth = 640; + cam->maxheight = 480; + cam->minwidth = 64; + cam->minheight = 48; + break; + default: + DBG(1, "Not supported image sensor detected for %s", + symbolic(camlist, cam->id)) + return -EINVAL; + } + + /* These values depend on the ones in the ovxxx0.c sources */ + switch (cam->sensor) { + case CC_OV7620: + cam->start_cropx = 287; + cam->start_cropy = 35; + /* Seems to work around a bug in the image sensor */ + cam->vs_polarity = 1; + cam->hs_polarity = 1; + break; + default: + cam->start_cropx = 320; + cam->start_cropy = 35; + cam->vs_polarity = 1; + cam->hs_polarity = 0; + } + + if ((err = w9968cf_sensor_update_settings(cam))) + goto error; + + if ((err = w9968cf_sensor_update_picture(cam, cam->picture))) + goto error; + + cam->sensor_initialized = 1; + + DBG(2, "%s image sensor initialized", symbolic(senlist, cam->sensor)) + return 0; + +error: + cam->sensor_initialized = 0; + cam->sensor = CC_UNKNOWN; + DBG(1, "Image sensor initialization failed for %s (/dev/video%d). " + "Try to detach and attach this device again", + symbolic(camlist, cam->id), cam->v4ldev->minor) + return err; +} + + +/*-------------------------------------------------------------------------- + Fill some basic fields in the main device data structure. + This function is called once on w9968cf_usb_probe() for each recognized + camera. + --------------------------------------------------------------------------*/ +static void +w9968cf_configure_camera(struct w9968cf_device* cam, + struct usb_device* udev, + enum w9968cf_model_id mod_id, + const unsigned short dev_nr) +{ + mutex_init(&cam->fileop_mutex); + init_waitqueue_head(&cam->open); + spin_lock_init(&cam->urb_lock); + spin_lock_init(&cam->flist_lock); + + cam->users = 0; + cam->disconnected = 0; + cam->id = mod_id; + cam->sensor = CC_UNKNOWN; + cam->sensor_initialized = 0; + + /* Calculate the alternate setting number (from 1 to 16) + according to the 'packet_size' module parameter */ + if (packet_size[dev_nr] < W9968CF_MIN_PACKET_SIZE) + packet_size[dev_nr] = W9968CF_MIN_PACKET_SIZE; + for (cam->altsetting = 1; + packet_size[dev_nr] < wMaxPacketSize[cam->altsetting-1]; + cam->altsetting++); + + cam->max_buffers = (max_buffers[dev_nr] < 2 || + max_buffers[dev_nr] > W9968CF_MAX_BUFFERS) + ? W9968CF_BUFFERS : (u8)max_buffers[dev_nr]; + + cam->double_buffer = (double_buffer[dev_nr] == 0 || + double_buffer[dev_nr] == 1) + ? (u8)double_buffer[dev_nr]:W9968CF_DOUBLE_BUFFER; + + cam->clamping = (clamping[dev_nr] == 0 || clamping[dev_nr] == 1) + ? (u8)clamping[dev_nr] : W9968CF_CLAMPING; + + cam->filter_type = (filter_type[dev_nr] == 0 || + filter_type[dev_nr] == 1 || + filter_type[dev_nr] == 2) + ? (u8)filter_type[dev_nr] : W9968CF_FILTER_TYPE; + + cam->capture = 1; + + cam->largeview = (largeview[dev_nr] == 0 || largeview[dev_nr] == 1) + ? (u8)largeview[dev_nr] : W9968CF_LARGEVIEW; + + cam->decompression = (decompression[dev_nr] == 0 || + decompression[dev_nr] == 1 || + decompression[dev_nr] == 2) + ? (u8)decompression[dev_nr]:W9968CF_DECOMPRESSION; + + cam->upscaling = (upscaling[dev_nr] == 0 || + upscaling[dev_nr] == 1) + ? (u8)upscaling[dev_nr] : W9968CF_UPSCALING; + + cam->auto_brt = (autobright[dev_nr] == 0 || autobright[dev_nr] == 1) + ? (u8)autobright[dev_nr] : W9968CF_AUTOBRIGHT; + + cam->auto_exp = (autoexp[dev_nr] == 0 || autoexp[dev_nr] == 1) + ? (u8)autoexp[dev_nr] : W9968CF_AUTOEXP; + + cam->lightfreq = (lightfreq[dev_nr] == 50 || lightfreq[dev_nr] == 60) + ? (u8)lightfreq[dev_nr] : W9968CF_LIGHTFREQ; + + cam->bandfilt = (bandingfilter[dev_nr] == 0 || + bandingfilter[dev_nr] == 1) + ? (u8)bandingfilter[dev_nr] : W9968CF_BANDINGFILTER; + + cam->backlight = (backlight[dev_nr] == 0 || backlight[dev_nr] == 1) + ? (u8)backlight[dev_nr] : W9968CF_BACKLIGHT; + + cam->clockdiv = (clockdiv[dev_nr] == -1 || clockdiv[dev_nr] >= 0) + ? (s8)clockdiv[dev_nr] : W9968CF_CLOCKDIV; + + cam->mirror = (mirror[dev_nr] == 0 || mirror[dev_nr] == 1) + ? (u8)mirror[dev_nr] : W9968CF_MIRROR; + + cam->monochrome = (monochrome[dev_nr] == 0 || monochrome[dev_nr] == 1) + ? monochrome[dev_nr] : W9968CF_MONOCHROME; + + cam->picture.brightness = (u16)brightness[dev_nr]; + cam->picture.hue = (u16)hue[dev_nr]; + cam->picture.colour = (u16)colour[dev_nr]; + cam->picture.contrast = (u16)contrast[dev_nr]; + cam->picture.whiteness = (u16)whiteness[dev_nr]; + if (w9968cf_valid_palette((u16)force_palette[dev_nr])) { + cam->picture.palette = (u16)force_palette[dev_nr]; + cam->force_palette = 1; + } else { + cam->force_palette = 0; + if (cam->decompression == 0) + cam->picture.palette = W9968CF_PALETTE_DECOMP_OFF; + else if (cam->decompression == 1) + cam->picture.palette = W9968CF_PALETTE_DECOMP_FORCE; + else + cam->picture.palette = W9968CF_PALETTE_DECOMP_ON; + } + cam->picture.depth = w9968cf_valid_depth(cam->picture.palette); + + cam->force_rgb = (force_rgb[dev_nr] == 0 || force_rgb[dev_nr] == 1) + ? (u8)force_rgb[dev_nr] : W9968CF_FORCE_RGB; + + cam->window.x = 0; + cam->window.y = 0; + cam->window.width = W9968CF_WIDTH; + cam->window.height = W9968CF_HEIGHT; + cam->window.chromakey = 0; + cam->window.clipcount = 0; + cam->window.flags = 0; + + DBG(3, "%s configured with settings #%u:", + symbolic(camlist, cam->id), dev_nr) + + DBG(3, "- Data packet size for USB isochrnous transfer: %u bytes", + wMaxPacketSize[cam->altsetting-1]) + + DBG(3, "- Number of requested video frame buffers: %u", + cam->max_buffers) + + if (cam->double_buffer) + DBG(3, "- Hardware double buffering enabled") + else + DBG(3, "- Hardware double buffering disabled") + + if (cam->filter_type == 0) + DBG(3, "- Video filtering disabled") + else if (cam->filter_type == 1) + DBG(3, "- Video filtering enabled: type 1-2-1") + else if (cam->filter_type == 2) + DBG(3, "- Video filtering enabled: type 2-3-6-3-2") + + if (cam->clamping) + DBG(3, "- Video data clamping (CCIR-601 format) enabled") + else + DBG(3, "- Video data clamping (CCIR-601 format) disabled") + + if (cam->largeview) + DBG(3, "- Large view enabled") + else + DBG(3, "- Large view disabled") + + if ((cam->decompression) == 0 && (!cam->force_palette)) + DBG(3, "- Decompression disabled") + else if ((cam->decompression) == 1 && (!cam->force_palette)) + DBG(3, "- Decompression forced") + else if ((cam->decompression) == 2 && (!cam->force_palette)) + DBG(3, "- Decompression allowed") + + if (cam->upscaling) + DBG(3, "- Software image scaling enabled") + else + DBG(3, "- Software image scaling disabled") + + if (cam->force_palette) + DBG(3, "- Image palette forced to %s", + symbolic(v4l1_plist, cam->picture.palette)) + + if (cam->force_rgb) + DBG(3, "- RGB component ordering will be used instead of BGR") + + if (cam->auto_brt) + DBG(3, "- Auto brightness enabled") + else + DBG(3, "- Auto brightness disabled") + + if (cam->auto_exp) + DBG(3, "- Auto exposure enabled") + else + DBG(3, "- Auto exposure disabled") + + if (cam->backlight) + DBG(3, "- Backlight exposure algorithm enabled") + else + DBG(3, "- Backlight exposure algorithm disabled") + + if (cam->mirror) + DBG(3, "- Mirror enabled") + else + DBG(3, "- Mirror disabled") + + if (cam->bandfilt) + DBG(3, "- Banding filter enabled") + else + DBG(3, "- Banding filter disabled") + + DBG(3, "- Power lighting frequency: %u", cam->lightfreq) + + if (cam->clockdiv == -1) + DBG(3, "- Automatic clock divisor enabled") + else + DBG(3, "- Clock divisor: %d", cam->clockdiv) + + if (cam->monochrome) + DBG(3, "- Image sensor used as monochrome") + else + DBG(3, "- Image sensor not used as monochrome") +} + + +/*-------------------------------------------------------------------------- + If the video post-processing module is not loaded, some parameters + must be overridden. + --------------------------------------------------------------------------*/ +static void w9968cf_adjust_configuration(struct w9968cf_device* cam) +{ + if (!w9968cf_vpp) { + if (cam->decompression == 1) { + cam->decompression = 2; + DBG(2, "Video post-processing module not found: " + "'decompression' parameter forced to 2") + } + if (cam->upscaling) { + cam->upscaling = 0; + DBG(2, "Video post-processing module not found: " + "'upscaling' parameter forced to 0") + } + if (cam->picture.palette != VIDEO_PALETTE_UYVY) { + cam->force_palette = 0; + DBG(2, "Video post-processing module not found: " + "'force_palette' parameter forced to 0") + } + cam->picture.palette = VIDEO_PALETTE_UYVY; + cam->picture.depth = w9968cf_valid_depth(cam->picture.palette); + } +} + + +/*-------------------------------------------------------------------------- + Release the resources used by the driver. + This function is called on disconnect + (or on close if deallocation has been deferred) + --------------------------------------------------------------------------*/ +static void w9968cf_release_resources(struct w9968cf_device* cam) +{ + mutex_lock(&w9968cf_devlist_mutex); + + DBG(2, "V4L device deregistered: /dev/video%d", cam->v4ldev->minor) + + video_unregister_device(cam->v4ldev); + list_del(&cam->v4llist); + i2c_del_adapter(&cam->i2c_adapter); + w9968cf_deallocate_memory(cam); + kfree(cam->control_buffer); + kfree(cam->data_buffer); + + mutex_unlock(&w9968cf_devlist_mutex); +} + + + +/**************************************************************************** + * Video4Linux interface * + ****************************************************************************/ + +static int w9968cf_open(struct inode* inode, struct file* filp) +{ + struct w9968cf_device* cam; + int err; + + /* This the only safe way to prevent race conditions with disconnect */ + if (!down_read_trylock(&w9968cf_disconnect)) + return -ERESTARTSYS; + + cam = (struct w9968cf_device*)video_get_drvdata(video_devdata(filp)); + + mutex_lock(&cam->dev_mutex); + + if (cam->sensor == CC_UNKNOWN) { + DBG(2, "No supported image sensor has been detected by the " + "'ovcamchip' module for the %s (/dev/video%d). Make " + "sure it is loaded *before* (re)connecting the camera.", + symbolic(camlist, cam->id), cam->v4ldev->minor) + mutex_unlock(&cam->dev_mutex); + up_read(&w9968cf_disconnect); + return -ENODEV; + } + + if (cam->users) { + DBG(2, "%s (/dev/video%d) has been already occupied by '%s'", + symbolic(camlist, cam->id),cam->v4ldev->minor,cam->command) + if ((filp->f_flags & O_NONBLOCK)||(filp->f_flags & O_NDELAY)) { + mutex_unlock(&cam->dev_mutex); + up_read(&w9968cf_disconnect); + return -EWOULDBLOCK; + } + mutex_unlock(&cam->dev_mutex); + err = wait_event_interruptible_exclusive(cam->open, + cam->disconnected || + !cam->users); + if (err) { + up_read(&w9968cf_disconnect); + return err; + } + if (cam->disconnected) { + up_read(&w9968cf_disconnect); + return -ENODEV; + } + mutex_lock(&cam->dev_mutex); + } + + DBG(5, "Opening '%s', /dev/video%d ...", + symbolic(camlist, cam->id), cam->v4ldev->minor) + + cam->streaming = 0; + cam->misconfigured = 0; + + w9968cf_adjust_configuration(cam); + + if ((err = w9968cf_allocate_memory(cam))) + goto deallocate_memory; + + if ((err = w9968cf_init_chip(cam))) + goto deallocate_memory; + + if ((err = w9968cf_start_transfer(cam))) + goto deallocate_memory; + + filp->private_data = cam; + + cam->users++; + strcpy(cam->command, current->comm); + + init_waitqueue_head(&cam->wait_queue); + + DBG(5, "Video device is open") + + mutex_unlock(&cam->dev_mutex); + up_read(&w9968cf_disconnect); + + return 0; + +deallocate_memory: + w9968cf_deallocate_memory(cam); + DBG(2, "Failed to open the video device") + mutex_unlock(&cam->dev_mutex); + up_read(&w9968cf_disconnect); + return err; +} + + +static int w9968cf_release(struct inode* inode, struct file* filp) +{ + struct w9968cf_device* cam; + + cam = (struct w9968cf_device*)video_get_drvdata(video_devdata(filp)); + + mutex_lock(&cam->dev_mutex); /* prevent disconnect() to be called */ + + w9968cf_stop_transfer(cam); + + if (cam->disconnected) { + w9968cf_release_resources(cam); + mutex_unlock(&cam->dev_mutex); + kfree(cam); + return 0; + } + + cam->users--; + w9968cf_deallocate_memory(cam); + wake_up_interruptible_nr(&cam->open, 1); + + DBG(5, "Video device closed") + mutex_unlock(&cam->dev_mutex); + return 0; +} + + +static ssize_t +w9968cf_read(struct file* filp, char __user * buf, size_t count, loff_t* f_pos) +{ + struct w9968cf_device* cam; + struct w9968cf_frame_t* fr; + int err = 0; + + cam = (struct w9968cf_device*)video_get_drvdata(video_devdata(filp)); + + if (filp->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return -ERESTARTSYS; + + if (cam->disconnected) { + DBG(2, "Device not present") + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again.") + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + + if (!cam->frame[0].queued) + w9968cf_push_frame(cam, 0); + + if (!cam->frame[1].queued) + w9968cf_push_frame(cam, 1); + + err = wait_event_interruptible(cam->wait_queue, + cam->frame[0].status == F_READY || + cam->frame[1].status == F_READY || + cam->disconnected); + if (err) { + mutex_unlock(&cam->fileop_mutex); + return err; + } + if (cam->disconnected) { + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + fr = (cam->frame[0].status == F_READY) ? &cam->frame[0]:&cam->frame[1]; + + if (w9968cf_vpp) + w9968cf_postprocess_frame(cam, fr); + + if (count > fr->length) + count = fr->length; + + if (copy_to_user(buf, fr->buffer, count)) { + fr->status = F_UNUSED; + mutex_unlock(&cam->fileop_mutex); + return -EFAULT; + } + *f_pos += count; + + fr->status = F_UNUSED; + + DBG(5, "%zu bytes read", count) + + mutex_unlock(&cam->fileop_mutex); + return count; +} + + +static int w9968cf_mmap(struct file* filp, struct vm_area_struct *vma) +{ + struct w9968cf_device* cam = (struct w9968cf_device*) + video_get_drvdata(video_devdata(filp)); + unsigned long vsize = vma->vm_end - vma->vm_start, + psize = cam->nbuffers * cam->frame[0].size, + start = vma->vm_start, + pos = (unsigned long)cam->frame[0].buffer, + page; + + if (cam->disconnected) { + DBG(2, "Device not present") + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again") + return -EIO; + } + + PDBGG("mmapping %lu bytes...", vsize) + + if (vsize > psize - (vma->vm_pgoff << PAGE_SHIFT)) + return -EINVAL; + + while (vsize > 0) { + page = vmalloc_to_pfn((void *)pos); + if (remap_pfn_range(vma, start, page + vma->vm_pgoff, + PAGE_SIZE, vma->vm_page_prot)) + return -EAGAIN; + start += PAGE_SIZE; + pos += PAGE_SIZE; + vsize -= PAGE_SIZE; + } + + DBG(5, "mmap method successfully called") + return 0; +} + + +static int +w9968cf_ioctl(struct inode* inode, struct file* filp, + unsigned int cmd, unsigned long arg) +{ + struct w9968cf_device* cam; + int err; + + cam = (struct w9968cf_device*)video_get_drvdata(video_devdata(filp)); + + if (mutex_lock_interruptible(&cam->fileop_mutex)) + return -ERESTARTSYS; + + if (cam->disconnected) { + DBG(2, "Device not present") + mutex_unlock(&cam->fileop_mutex); + return -ENODEV; + } + + if (cam->misconfigured) { + DBG(2, "The camera is misconfigured. Close and open it again.") + mutex_unlock(&cam->fileop_mutex); + return -EIO; + } + + err = w9968cf_v4l_ioctl(inode, filp, cmd, (void __user *)arg); + + mutex_unlock(&cam->fileop_mutex); + return err; +} + + +static int w9968cf_v4l_ioctl(struct inode* inode, struct file* filp, + unsigned int cmd, void __user * arg) +{ + struct w9968cf_device* cam; + const char* v4l1_ioctls[] = { + "?", "CGAP", "GCHAN", "SCHAN", "GTUNER", "STUNER", + "GPICT", "SPICT", "CCAPTURE", "GWIN", "SWIN", "GFBUF", + "SFBUF", "KEY", "GFREQ", "SFREQ", "GAUDIO", "SAUDIO", + "SYNC", "MCAPTURE", "GMBUF", "GUNIT", "GCAPTURE", "SCAPTURE", + "SPLAYMODE", "SWRITEMODE", "GPLAYINFO", "SMICROCODE", + "GVBIFMT", "SVBIFMT" + }; + + #define V4L1_IOCTL(cmd) \ + ((_IOC_NR((cmd)) < ARRAY_SIZE(v4l1_ioctls)) ? \ + v4l1_ioctls[_IOC_NR((cmd))] : "?") + + cam = (struct w9968cf_device*)video_get_drvdata(video_devdata(filp)); + + switch (cmd) { + + case VIDIOCGCAP: /* get video capability */ + { + struct video_capability cap = { + .type = VID_TYPE_CAPTURE | VID_TYPE_SCALES, + .channels = 1, + .audios = 0, + .minwidth = cam->minwidth, + .minheight = cam->minheight, + }; + sprintf(cap.name, "W996[87]CF USB Camera #%d", + cam->v4ldev->minor); + cap.maxwidth = (cam->upscaling && w9968cf_vpp) + ? max((u16)W9968CF_MAX_WIDTH, cam->maxwidth) + : cam->maxwidth; + cap.maxheight = (cam->upscaling && w9968cf_vpp) + ? max((u16)W9968CF_MAX_HEIGHT, cam->maxheight) + : cam->maxheight; + + if (copy_to_user(arg, &cap, sizeof(cap))) + return -EFAULT; + + DBG(5, "VIDIOCGCAP successfully called") + return 0; + } + + case VIDIOCGCHAN: /* get video channel informations */ + { + struct video_channel chan; + if (copy_from_user(&chan, arg, sizeof(chan))) + return -EFAULT; + + if (chan.channel != 0) + return -EINVAL; + + strcpy(chan.name, "Camera"); + chan.tuners = 0; + chan.flags = 0; + chan.type = VIDEO_TYPE_CAMERA; + chan.norm = VIDEO_MODE_AUTO; + + if (copy_to_user(arg, &chan, sizeof(chan))) + return -EFAULT; + + DBG(5, "VIDIOCGCHAN successfully called") + return 0; + } + + case VIDIOCSCHAN: /* set active channel */ + { + struct video_channel chan; + + if (copy_from_user(&chan, arg, sizeof(chan))) + return -EFAULT; + + if (chan.channel != 0) + return -EINVAL; + + DBG(5, "VIDIOCSCHAN successfully called") + return 0; + } + + case VIDIOCGPICT: /* get image properties of the picture */ + { + if (w9968cf_sensor_get_picture(cam)) + return -EIO; + + if (copy_to_user(arg, &cam->picture, sizeof(cam->picture))) + return -EFAULT; + + DBG(5, "VIDIOCGPICT successfully called") + return 0; + } + + case VIDIOCSPICT: /* change picture settings */ + { + struct video_picture pict; + int err = 0; + + if (copy_from_user(&pict, arg, sizeof(pict))) + return -EFAULT; + + if ( (cam->force_palette || !w9968cf_vpp) + && pict.palette != cam->picture.palette ) { + DBG(4, "Palette %s rejected: only %s is allowed", + symbolic(v4l1_plist, pict.palette), + symbolic(v4l1_plist, cam->picture.palette)) + return -EINVAL; + } + + if (!w9968cf_valid_palette(pict.palette)) { + DBG(4, "Palette %s not supported. VIDIOCSPICT failed", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + + if (!cam->force_palette) { + if (cam->decompression == 0) { + if (w9968cf_need_decompression(pict.palette)) { + DBG(4, "Decompression disabled: palette %s is not " + "allowed. VIDIOCSPICT failed", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + } else if (cam->decompression == 1) { + if (!w9968cf_need_decompression(pict.palette)) { + DBG(4, "Decompression forced: palette %s is not " + "allowed. VIDIOCSPICT failed", + symbolic(v4l1_plist, pict.palette)) + return -EINVAL; + } + } + } + + if (pict.depth != w9968cf_valid_depth(pict.palette)) { + DBG(4, "Requested depth %u bpp is not valid for %s " + "palette: ignored and changed to %u bpp", + pict.depth, symbolic(v4l1_plist, pict.palette), + w9968cf_valid_depth(pict.palette)) + pict.depth = w9968cf_valid_depth(pict.palette); + } + + if (pict.palette != cam->picture.palette) { + if(*cam->requested_frame + || cam->frame_current->queued) { + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + if (w9968cf_set_picture(cam, pict)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + + } else if (w9968cf_sensor_update_picture(cam, pict)) + return -EIO; + + + DBG(5, "VIDIOCSPICT successfully called") + return 0; + } + + case VIDIOCSWIN: /* set capture area */ + { + struct video_window win; + int err = 0; + + if (copy_from_user(&win, arg, sizeof(win))) + return -EFAULT; + + DBG(6, "VIDIOCSWIN called: clipcount=%d, flags=%u, " + "x=%u, y=%u, %ux%u", win.clipcount, win.flags, + win.x, win.y, win.width, win.height) + + if (win.clipcount != 0 || win.flags != 0) + return -EINVAL; + + if ((err = w9968cf_adjust_window_size(cam, (u16*)&win.width, + (u16*)&win.height))) { + DBG(4, "Resolution not supported (%ux%u). " + "VIDIOCSWIN failed", win.width, win.height) + return err; + } + + if (win.x != cam->window.x || + win.y != cam->window.y || + win.width != cam->window.width || + win.height != cam->window.height) { + if(*cam->requested_frame + || cam->frame_current->queued) { + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + /* This _must_ be called before set_window() */ + if (w9968cf_set_picture(cam, cam->picture)) + goto ioctl_fail; + + if (w9968cf_set_window(cam, win)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + } + + DBG(5, "VIDIOCSWIN successfully called. ") + return 0; + } + + case VIDIOCGWIN: /* get current window properties */ + { + if (copy_to_user(arg,&cam->window,sizeof(struct video_window))) + return -EFAULT; + + DBG(5, "VIDIOCGWIN successfully called") + return 0; + } + + case VIDIOCGMBUF: /* request for memory (mapped) buffer */ + { + struct video_mbuf mbuf; + u8 i; + + mbuf.size = cam->nbuffers * cam->frame[0].size; + mbuf.frames = cam->nbuffers; + for (i = 0; i < cam->nbuffers; i++) + mbuf.offsets[i] = (unsigned long)cam->frame[i].buffer - + (unsigned long)cam->frame[0].buffer; + + if (copy_to_user(arg, &mbuf, sizeof(mbuf))) + return -EFAULT; + + DBG(5, "VIDIOCGMBUF successfully called") + return 0; + } + + case VIDIOCMCAPTURE: /* start the capture to a frame */ + { + struct video_mmap mmap; + struct w9968cf_frame_t* fr; + int err = 0; + + if (copy_from_user(&mmap, arg, sizeof(mmap))) + return -EFAULT; + + DBG(6, "VIDIOCMCAPTURE called: frame #%u, format=%s, %dx%d", + mmap.frame, symbolic(v4l1_plist, mmap.format), + mmap.width, mmap.height) + + if (mmap.frame >= cam->nbuffers) { + DBG(4, "Invalid frame number (%u). " + "VIDIOCMCAPTURE failed", mmap.frame) + return -EINVAL; + } + + if (mmap.format!=cam->picture.palette && + (cam->force_palette || !w9968cf_vpp)) { + DBG(4, "Palette %s rejected: only %s is allowed", + symbolic(v4l1_plist, mmap.format), + symbolic(v4l1_plist, cam->picture.palette)) + return -EINVAL; + } + + if (!w9968cf_valid_palette(mmap.format)) { + DBG(4, "Palette %s not supported. " + "VIDIOCMCAPTURE failed", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + + if (!cam->force_palette) { + if (cam->decompression == 0) { + if (w9968cf_need_decompression(mmap.format)) { + DBG(4, "Decompression disabled: palette %s is not " + "allowed. VIDIOCSPICT failed", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + } else if (cam->decompression == 1) { + if (!w9968cf_need_decompression(mmap.format)) { + DBG(4, "Decompression forced: palette %s is not " + "allowed. VIDIOCSPICT failed", + symbolic(v4l1_plist, mmap.format)) + return -EINVAL; + } + } + } + + if ((err = w9968cf_adjust_window_size(cam, (u16*)&mmap.width, + (u16*)&mmap.height))) { + DBG(4, "Resolution not supported (%dx%d). " + "VIDIOCMCAPTURE failed", + mmap.width, mmap.height) + return err; + } + + fr = &cam->frame[mmap.frame]; + + if (mmap.width != cam->window.width || + mmap.height != cam->window.height || + mmap.format != cam->picture.palette) { + + struct video_window win; + struct video_picture pict; + + if(*cam->requested_frame + || cam->frame_current->queued) { + DBG(6, "VIDIOCMCAPTURE. Change settings for " + "frame #%u: %dx%d, format %s. Wait...", + mmap.frame, mmap.width, mmap.height, + symbolic(v4l1_plist, mmap.format)) + err = wait_event_interruptible + ( cam->wait_queue, + cam->disconnected || + (!*cam->requested_frame && + !cam->frame_current->queued) ); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + memcpy(&win, &cam->window, sizeof(win)); + memcpy(&pict, &cam->picture, sizeof(pict)); + win.width = mmap.width; + win.height = mmap.height; + pict.palette = mmap.format; + + if (w9968cf_stop_transfer(cam)) + goto ioctl_fail; + + /* This before set_window */ + if (w9968cf_set_picture(cam, pict)) + goto ioctl_fail; + + if (w9968cf_set_window(cam, win)) + goto ioctl_fail; + + if (w9968cf_start_transfer(cam)) + goto ioctl_fail; + + } else if (fr->queued) { + + DBG(6, "Wait until frame #%u is free", mmap.frame) + + err = wait_event_interruptible(cam->wait_queue, + cam->disconnected || + (!fr->queued)); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + } + + w9968cf_push_frame(cam, mmap.frame); + DBG(5, "VIDIOCMCAPTURE(%u): successfully called", mmap.frame) + return 0; + } + + case VIDIOCSYNC: /* wait until the capture of a frame is finished */ + { + unsigned int f_num; + struct w9968cf_frame_t* fr; + int err = 0; + + if (copy_from_user(&f_num, arg, sizeof(f_num))) + return -EFAULT; + + if (f_num >= cam->nbuffers) { + DBG(4, "Invalid frame number (%u). " + "VIDIOCMCAPTURE failed", f_num) + return -EINVAL; + } + + DBG(6, "VIDIOCSYNC called for frame #%u", f_num) + + fr = &cam->frame[f_num]; + + switch (fr->status) { + case F_UNUSED: + if (!fr->queued) { + DBG(4, "VIDIOSYNC: Frame #%u not requested!", + f_num) + return -EFAULT; + } + case F_ERROR: + case F_GRABBING: + err = wait_event_interruptible(cam->wait_queue, + (fr->status == F_READY) + || cam->disconnected); + if (err) + return err; + if (cam->disconnected) + return -ENODEV; + break; + case F_READY: + break; + } + + if (w9968cf_vpp) + w9968cf_postprocess_frame(cam, fr); + + fr->status = F_UNUSED; + + DBG(5, "VIDIOCSYNC(%u) successfully called", f_num) + return 0; + } + + case VIDIOCGUNIT:/* report the unit numbers of the associated devices*/ + { + struct video_unit unit = { + .video = cam->v4ldev->minor, + .vbi = VIDEO_NO_UNIT, + .radio = VIDEO_NO_UNIT, + .audio = VIDEO_NO_UNIT, + .teletext = VIDEO_NO_UNIT, + }; + + if (copy_to_user(arg, &unit, sizeof(unit))) + return -EFAULT; + + DBG(5, "VIDIOCGUNIT successfully called") + return 0; + } + + case VIDIOCKEY: + return 0; + + case VIDIOCGFBUF: + { + if (clear_user(arg, sizeof(struct video_buffer))) + return -EFAULT; + + DBG(5, "VIDIOCGFBUF successfully called") + return 0; + } + + case VIDIOCGTUNER: + { + struct video_tuner tuner; + if (copy_from_user(&tuner, arg, sizeof(tuner))) + return -EFAULT; + + if (tuner.tuner != 0) + return -EINVAL; + + strcpy(tuner.name, "no_tuner"); + tuner.rangelow = 0; + tuner.rangehigh = 0; + tuner.flags = VIDEO_TUNER_NORM; + tuner.mode = VIDEO_MODE_AUTO; + tuner.signal = 0xffff; + + if (copy_to_user(arg, &tuner, sizeof(tuner))) + return -EFAULT; + + DBG(5, "VIDIOCGTUNER successfully called") + return 0; + } + + case VIDIOCSTUNER: + { + struct video_tuner tuner; + if (copy_from_user(&tuner, arg, sizeof(tuner))) + return -EFAULT; + + if (tuner.tuner != 0) + return -EINVAL; + + if (tuner.mode != VIDEO_MODE_AUTO) + return -EINVAL; + + DBG(5, "VIDIOCSTUNER successfully called") + return 0; + } + + case VIDIOCSFBUF: + case VIDIOCCAPTURE: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + case VIDIOCSPLAYMODE: + case VIDIOCSWRITEMODE: + case VIDIOCGPLAYINFO: + case VIDIOCSMICROCODE: + case VIDIOCGVBIFMT: + case VIDIOCSVBIFMT: + DBG(4, "Unsupported V4L1 IOCtl: VIDIOC%s " + "(type 0x%01X, " + "n. 0x%01X, " + "dir. 0x%01X, " + "size 0x%02X)", + V4L1_IOCTL(cmd), + _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd)) + + return -EINVAL; + + default: + DBG(4, "Invalid V4L1 IOCtl: VIDIOC%s " + "type 0x%01X, " + "n. 0x%01X, " + "dir. 0x%01X, " + "size 0x%02X", + V4L1_IOCTL(cmd), + _IOC_TYPE(cmd),_IOC_NR(cmd),_IOC_DIR(cmd),_IOC_SIZE(cmd)) + + return -ENOIOCTLCMD; + + } /* end of switch */ + +ioctl_fail: + cam->misconfigured = 1; + DBG(1, "VIDIOC%s failed because of hardware problems. " + "To use the camera, close and open it again.", V4L1_IOCTL(cmd)) + return -EFAULT; +} + + +static struct file_operations w9968cf_fops = { + .owner = THIS_MODULE, + .open = w9968cf_open, + .release = w9968cf_release, + .read = w9968cf_read, + .ioctl = w9968cf_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .mmap = w9968cf_mmap, + .llseek = no_llseek, +}; + + + +/**************************************************************************** + * USB probe and V4L registration, disconnect and id_table[] definition * + ****************************************************************************/ + +static int +w9968cf_usb_probe(struct usb_interface* intf, const struct usb_device_id* id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct w9968cf_device* cam; + int err = 0; + enum w9968cf_model_id mod_id; + struct list_head* ptr; + u8 sc = 0; /* number of simultaneous cameras */ + static unsigned short dev_nr = 0; /* we are handling device number n */ + + if (le16_to_cpu(udev->descriptor.idVendor) == winbond_id_table[0].idVendor && + le16_to_cpu(udev->descriptor.idProduct) == winbond_id_table[0].idProduct) + mod_id = W9968CF_MOD_CLVBWGP; /* see camlist[] table */ + else if (le16_to_cpu(udev->descriptor.idVendor) == winbond_id_table[1].idVendor && + le16_to_cpu(udev->descriptor.idProduct) == winbond_id_table[1].idProduct) + mod_id = W9968CF_MOD_GENERIC; /* see camlist[] table */ + else + return -ENODEV; + + cam = (struct w9968cf_device*) + kzalloc(sizeof(struct w9968cf_device), GFP_KERNEL); + if (!cam) + return -ENOMEM; + + mutex_init(&cam->dev_mutex); + mutex_lock(&cam->dev_mutex); + + cam->usbdev = udev; + /* NOTE: a local copy is used to avoid possible race conditions */ + memcpy(&cam->dev, &udev->dev, sizeof(struct device)); + + DBG(2, "%s detected", symbolic(camlist, mod_id)) + + if (simcams > W9968CF_MAX_DEVICES) + simcams = W9968CF_SIMCAMS; + + /* How many cameras are connected ? */ + mutex_lock(&w9968cf_devlist_mutex); + list_for_each(ptr, &w9968cf_dev_list) + sc++; + mutex_unlock(&w9968cf_devlist_mutex); + + if (sc >= simcams) { + DBG(2, "Device rejected: too many connected cameras " + "(max. %u)", simcams) + err = -EPERM; + goto fail; + } + + + /* Allocate 2 bytes of memory for camera control USB transfers */ + if (!(cam->control_buffer = kzalloc(2, GFP_KERNEL))) { + DBG(1,"Couldn't allocate memory for camera control transfers") + err = -ENOMEM; + goto fail; + } + + /* Allocate 8 bytes of memory for USB data transfers to the FSB */ + if (!(cam->data_buffer = kzalloc(8, GFP_KERNEL))) { + DBG(1, "Couldn't allocate memory for data " + "transfers to the FSB") + err = -ENOMEM; + goto fail; + } + + /* Register the V4L device */ + cam->v4ldev = video_device_alloc(); + if (!cam->v4ldev) { + DBG(1, "Could not allocate memory for a V4L structure") + err = -ENOMEM; + goto fail; + } + + strcpy(cam->v4ldev->name, symbolic(camlist, mod_id)); + cam->v4ldev->owner = THIS_MODULE; + cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES; + cam->v4ldev->hardware = VID_HARDWARE_W9968CF; + cam->v4ldev->fops = &w9968cf_fops; + cam->v4ldev->minor = video_nr[dev_nr]; + cam->v4ldev->release = video_device_release; + video_set_drvdata(cam->v4ldev, cam); + cam->v4ldev->dev = &cam->dev; + + err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER, + video_nr[dev_nr]); + if (err) { + DBG(1, "V4L device registration failed") + if (err == -ENFILE && video_nr[dev_nr] == -1) + DBG(2, "Couldn't find a free /dev/videoX node") + video_nr[dev_nr] = -1; + dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0; + goto fail; + } + + DBG(2, "V4L device registered as /dev/video%d", cam->v4ldev->minor) + + /* Set some basic constants */ + w9968cf_configure_camera(cam, udev, mod_id, dev_nr); + + /* Add a new entry into the list of V4L registered devices */ + mutex_lock(&w9968cf_devlist_mutex); + list_add(&cam->v4llist, &w9968cf_dev_list); + mutex_unlock(&w9968cf_devlist_mutex); + dev_nr = (dev_nr < W9968CF_MAX_DEVICES-1) ? dev_nr+1 : 0; + + w9968cf_turn_on_led(cam); + + w9968cf_i2c_init(cam); + + usb_set_intfdata(intf, cam); + mutex_unlock(&cam->dev_mutex); + return 0; + +fail: /* Free unused memory */ + kfree(cam->control_buffer); + kfree(cam->data_buffer); + if (cam->v4ldev) + video_device_release(cam->v4ldev); + mutex_unlock(&cam->dev_mutex); + kfree(cam); + return err; +} + + +static void w9968cf_usb_disconnect(struct usb_interface* intf) +{ + struct w9968cf_device* cam = + (struct w9968cf_device*)usb_get_intfdata(intf); + + down_write(&w9968cf_disconnect); + + if (cam) { + /* Prevent concurrent accesses to data */ + mutex_lock(&cam->dev_mutex); + + cam->disconnected = 1; + + DBG(2, "Disconnecting %s...", symbolic(camlist, cam->id)) + + wake_up_interruptible_all(&cam->open); + + if (cam->users) { + DBG(2, "The device is open (/dev/video%d)! " + "Process name: %s. Deregistration and memory " + "deallocation are deferred on close.", + cam->v4ldev->minor, cam->command) + cam->misconfigured = 1; + w9968cf_stop_transfer(cam); + wake_up_interruptible(&cam->wait_queue); + } else + w9968cf_release_resources(cam); + + mutex_unlock(&cam->dev_mutex); + + if (!cam->users) + kfree(cam); + } + + up_write(&w9968cf_disconnect); +} + + +static struct usb_driver w9968cf_usb_driver = { + .name = "w9968cf", + .id_table = winbond_id_table, + .probe = w9968cf_usb_probe, + .disconnect = w9968cf_usb_disconnect, +}; + + + +/**************************************************************************** + * Module init, exit and intermodule communication * + ****************************************************************************/ + +static int __init w9968cf_module_init(void) +{ + int err; + + KDBG(2, W9968CF_MODULE_NAME" "W9968CF_MODULE_VERSION) + KDBG(3, W9968CF_MODULE_AUTHOR) + + if (ovmod_load) + request_module("ovcamchip"); + + if ((err = usb_register(&w9968cf_usb_driver))) + return err; + + return 0; +} + + +static void __exit w9968cf_module_exit(void) +{ + /* w9968cf_usb_disconnect() will be called */ + usb_deregister(&w9968cf_usb_driver); + + KDBG(2, W9968CF_MODULE_NAME" deregistered") +} + + +module_init(w9968cf_module_init); +module_exit(w9968cf_module_exit); + diff --git a/linux/drivers/media/video/w9968cf.h b/linux/drivers/media/video/w9968cf.h new file mode 100644 index 000000000..a89013fa3 --- /dev/null +++ b/linux/drivers/media/video/w9968cf.h @@ -0,0 +1,331 @@ +/*************************************************************************** + * Video4Linux driver for W996[87]CF JPEG USB Dual Mode Camera Chip. * + * * + * Copyright (C) 2002-2004 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 _W9968CF_H_ +#define _W9968CF_H_ + +#include "compat.h" +#include <linux/videodev.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/wait.h> +#include <linux/config.h> +#include <linux/param.h> +#include <linux/types.h> +#include <linux/rwsem.h> +#include <linux/mutex.h> + +#include <media/ovcamchip.h> + +#include "w9968cf_vpp.h" + + +/**************************************************************************** + * Default values * + ****************************************************************************/ + +#define W9968CF_OVMOD_LOAD 1 /* automatic 'ovcamchip' module loading */ +#define W9968CF_VPPMOD_LOAD 1 /* automatic 'w9968cf-vpp' module loading */ + +/* Comment/uncomment the following line to enable/disable debugging messages */ +#define W9968CF_DEBUG + +/* These have effect only if W9968CF_DEBUG is defined */ +#define W9968CF_DEBUG_LEVEL 2 /* from 0 to 6. 0 for no debug informations */ +#define W9968CF_SPECIFIC_DEBUG 0 /* 0 or 1 */ + +#define W9968CF_MAX_DEVICES 32 +#define W9968CF_SIMCAMS W9968CF_MAX_DEVICES /* simultaneous cameras */ + +#define W9968CF_MAX_BUFFERS 32 +#define W9968CF_BUFFERS 2 /* n. of frame buffers from 2 to MAX_BUFFERS */ + +/* Maximum data payload sizes in bytes for alternate settings */ +static const u16 wMaxPacketSize[] = {1023, 959, 895, 831, 767, 703, 639, 575, + 511, 447, 383, 319, 255, 191, 127, 63}; +#define W9968CF_PACKET_SIZE 1023 /* according to wMaxPacketSizes[] */ +#define W9968CF_MIN_PACKET_SIZE 63 /* minimum value */ +#define W9968CF_ISO_PACKETS 5 /* n.of packets for isochronous transfers */ +#define W9968CF_USB_CTRL_TIMEOUT 1000 /* timeout (ms) for usb control commands */ +#define W9968CF_URBS 2 /* n. of scheduled URBs for ISO transfer */ + +#define W9968CF_I2C_BUS_DELAY 4 /* delay in us for I2C bit r/w operations */ +#define W9968CF_I2C_RW_RETRIES 15 /* number of max I2C r/w retries */ + +/* Available video formats */ +struct w9968cf_format { + const u16 palette; + const u16 depth; + const u8 compression; +}; + +static const struct w9968cf_format w9968cf_formatlist[] = { + { VIDEO_PALETTE_UYVY, 16, 0 }, /* original video */ + { VIDEO_PALETTE_YUV422P, 16, 1 }, /* with JPEG compression */ + { VIDEO_PALETTE_YUV420P, 12, 1 }, /* with JPEG compression */ + { VIDEO_PALETTE_YUV420, 12, 1 }, /* same as YUV420P */ + { VIDEO_PALETTE_YUYV, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_YUV422, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_GREY, 8, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB555, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB565, 16, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB24, 24, 0 }, /* software conversion */ + { VIDEO_PALETTE_RGB32, 32, 0 }, /* software conversion */ + { 0, 0, 0 } /* 0 is a terminating entry */ +}; + +#define W9968CF_DECOMPRESSION 2 /* decomp:0=disable,1=force,2=any formats */ +#define W9968CF_PALETTE_DECOMP_OFF VIDEO_PALETTE_UYVY /* when decomp=0 */ +#define W9968CF_PALETTE_DECOMP_FORCE VIDEO_PALETTE_YUV420P /* when decomp=1 */ +#define W9968CF_PALETTE_DECOMP_ON VIDEO_PALETTE_UYVY /* when decomp=2 */ + +#define W9968CF_FORCE_RGB 0 /* read RGB instead of BGR, yes=1/no=0 */ + +#define W9968CF_MAX_WIDTH 800 /* Has effect if up-scaling is on */ +#define W9968CF_MAX_HEIGHT 600 /* Has effect if up-scaling is on */ +#define W9968CF_WIDTH 320 /* from 128 to 352, multiple of 16 */ +#define W9968CF_HEIGHT 240 /* from 96 to 288, multiple of 16 */ + +#define W9968CF_CLAMPING 0 /* 0 disable, 1 enable video data clamping */ +#define W9968CF_FILTER_TYPE 0 /* 0 disable 1 (1-2-1), 2 (2-3-6-3-2) */ +#define W9968CF_DOUBLE_BUFFER 1 /* 0 disable, 1 enable double buffer */ +#define W9968CF_LARGEVIEW 1 /* 0 disable, 1 enable */ +#define W9968CF_UPSCALING 0 /* 0 disable, 1 enable */ + +#define W9968CF_MONOCHROME 0 /* 0 not monochrome, 1 monochrome sensor */ +#define W9968CF_BRIGHTNESS 31000 /* from 0 to 65535 */ +#define W9968CF_HUE 32768 /* from 0 to 65535 */ +#define W9968CF_COLOUR 32768 /* from 0 to 65535 */ +#define W9968CF_CONTRAST 50000 /* from 0 to 65535 */ +#define W9968CF_WHITENESS 32768 /* from 0 to 65535 */ + +#define W9968CF_AUTOBRIGHT 0 /* 0 disable, 1 enable automatic brightness */ +#define W9968CF_AUTOEXP 1 /* 0 disable, 1 enable automatic exposure */ +#define W9968CF_LIGHTFREQ 50 /* light frequency. 50Hz (Europe) or 60Hz */ +#define W9968CF_BANDINGFILTER 0 /* 0 disable, 1 enable banding filter */ +#define W9968CF_BACKLIGHT 0 /* 0 or 1, 1=object is lit from behind */ +#define W9968CF_MIRROR 0 /* 0 or 1 [don't] reverse image horizontally*/ + +#define W9968CF_CLOCKDIV -1 /* -1 = automatic clock divisor */ +#define W9968CF_DEF_CLOCKDIVISOR 0 /* default sensor clock divisor value */ + + +/**************************************************************************** + * Globals * + ****************************************************************************/ + +#define W9968CF_MODULE_NAME "V4L driver for W996[87]CF JPEG USB " \ + "Dual Mode Camera Chip" +#define W9968CF_MODULE_VERSION "1:1.33-basic" +#define W9968CF_MODULE_AUTHOR "(C) 2002-2004 Luca Risolia" +#define W9968CF_AUTHOR_EMAIL "<luca.risolia@studio.unibo.it>" +#define W9968CF_MODULE_LICENSE "GPL" + +static const struct usb_device_id winbond_id_table[] = { + { + /* Creative Labs Video Blaster WebCam Go Plus */ + USB_DEVICE(0x041e, 0x4003), + .driver_info = (unsigned long)"w9968cf", + }, + { + /* Generic W996[87]CF JPEG USB Dual Mode Camera */ + USB_DEVICE(0x1046, 0x9967), + .driver_info = (unsigned long)"w9968cf", + }, + { } /* terminating entry */ +}; + +/* W996[87]CF camera models, internal ids: */ +enum w9968cf_model_id { + W9968CF_MOD_GENERIC = 1, /* Generic W996[87]CF based device */ + W9968CF_MOD_CLVBWGP = 11,/*Creative Labs Video Blaster WebCam Go Plus*/ + W9968CF_MOD_ADPVDMA = 21, /* Aroma Digi Pen VGA Dual Mode ADG-5000 */ + W9986CF_MOD_AAU = 31, /* AVerMedia AVerTV USB */ + W9968CF_MOD_CLVBWG = 34, /* Creative Labs Video Blaster WebCam Go */ + W9968CF_MOD_LL = 37, /* Lebon LDC-035A */ + W9968CF_MOD_EEEMC = 40, /* Ezonics EZ-802 EZMega Cam */ + W9968CF_MOD_OOE = 42, /* OmniVision OV8610-EDE */ + W9968CF_MOD_ODPVDMPC = 43,/* OPCOM Digi Pen VGA Dual Mode Pen Camera */ + W9968CF_MOD_PDPII = 46, /* Pretec Digi Pen-II */ + W9968CF_MOD_PDP480 = 49, /* Pretec DigiPen-480 */ +}; + +enum w9968cf_frame_status { + F_READY, /* finished grabbing & ready to be read/synced */ + F_GRABBING, /* in the process of being grabbed into */ + F_ERROR, /* something bad happened while processing */ + F_UNUSED /* unused (no VIDIOCMCAPTURE) */ +}; + +struct w9968cf_frame_t { + void* buffer; + unsigned long size; + u32 length; + int number; + enum w9968cf_frame_status status; + struct w9968cf_frame_t* next; + u8 queued; +}; + +enum w9968cf_vpp_flag { + VPP_NONE = 0x00, + VPP_UPSCALE = 0x01, + VPP_SWAP_YUV_BYTES = 0x02, + VPP_DECOMPRESSION = 0x04, + VPP_UYVY_TO_RGBX = 0x08, +}; + +/* Main device driver structure */ +struct w9968cf_device { + struct device dev; /* device structure */ + + enum w9968cf_model_id id; /* private device identifier */ + + struct video_device* v4ldev; /* -> V4L structure */ + struct list_head v4llist; /* entry of the list of V4L cameras */ + + struct usb_device* usbdev; /* -> main USB structure */ + struct urb* urb[W9968CF_URBS]; /* -> USB request block structs */ + void* transfer_buffer[W9968CF_URBS]; /* -> ISO transfer buffers */ + u16* control_buffer; /* -> buffer for control req.*/ + u16* data_buffer; /* -> data to send to the FSB */ + + struct w9968cf_frame_t frame[W9968CF_MAX_BUFFERS]; + struct w9968cf_frame_t frame_tmp; /* temporary frame */ + struct w9968cf_frame_t frame_vpp; /* helper frame.*/ + struct w9968cf_frame_t* frame_current; /* -> frame being grabbed */ + struct w9968cf_frame_t* requested_frame[W9968CF_MAX_BUFFERS]; + + u8 max_buffers, /* number of requested buffers */ + force_palette, /* yes=1/no=0 */ + force_rgb, /* read RGB instead of BGR, yes=1, no=0 */ + double_buffer, /* hardware double buffering yes=1/no=0 */ + clamping, /* video data clamping yes=1/no=0 */ + filter_type, /* 0=disabled, 1=3 tap, 2=5 tap filter */ + capture, /* 0=disabled, 1=enabled */ + largeview, /* 0=disabled, 1=enabled */ + decompression, /* 0=disabled, 1=forced, 2=allowed */ + upscaling; /* software image scaling, 0=enabled, 1=disabled */ + + struct video_picture picture; /* current picture settings */ + struct video_window window; /* current window settings */ + + u16 hw_depth, /* depth (used by the chip) */ + hw_palette, /* palette (used by the chip) */ + hw_width, /* width (used by the chip) */ + hw_height, /* height (used by the chip) */ + hs_polarity, /* 0=negative sync pulse, 1=positive sync pulse */ + vs_polarity, /* 0=negative sync pulse, 1=positive sync pulse */ + start_cropx, /* pixels from HS inactive edge to 1st cropped pixel*/ + start_cropy; /* pixels from VS inactive edge to 1st cropped pixel*/ + + enum w9968cf_vpp_flag vpp_flag; /* post-processing routines in use */ + + u8 nbuffers, /* number of allocated frame buffers */ + altsetting, /* camera alternate setting */ + disconnected, /* flag: yes=1, no=0 */ + misconfigured, /* flag: yes=1, no=0 */ + users, /* flag: number of users holding the device */ + streaming; /* flag: yes=1, no=0 */ + + u8 sensor_initialized; /* flag: yes=1, no=0 */ + + /* Determined by the image sensor type: */ + int sensor, /* type of image sensor chip (CC_*) */ + monochrome; /* image sensor is (probably) monochrome */ + u16 maxwidth, /* maximum width supported by the image sensor */ + maxheight, /* maximum height supported by the image sensor */ + minwidth, /* minimum width supported by the image sensor */ + minheight; /* minimum height supported by the image sensor */ + u8 auto_brt, /* auto brightness enabled flag */ + auto_exp, /* auto exposure enabled flag */ + backlight, /* backlight exposure algorithm flag */ + mirror, /* image is reversed horizontally */ + lightfreq, /* power (lighting) frequency */ + bandfilt; /* banding filter enabled flag */ + s8 clockdiv; /* clock divisor */ + + /* I2C interface to kernel */ + struct i2c_adapter i2c_adapter; + struct i2c_client* sensor_client; + + /* Locks */ + struct mutex dev_mutex, /* for probe, disconnect,open and close */ + fileop_mutex; /* for read and ioctl */ + spinlock_t urb_lock, /* for submit_urb() and unlink_urb() */ + flist_lock; /* for requested frame list accesses */ + wait_queue_head_t open, wait_queue; + + char command[16]; /* name of the program holding the device */ +}; + + +/**************************************************************************** + * Macros for debugging * + ****************************************************************************/ + +#undef DBG +#undef KDBG +#ifdef W9968CF_DEBUG +/* For device specific debugging messages */ +# define DBG(level, fmt, args...) \ +{ \ + if ( ((specific_debug) && (debug == (level))) || \ + ((!specific_debug) && (debug >= (level))) ) { \ + if ((level) == 1) \ + dev_err(&cam->dev, fmt "\n", ## args); \ + else if ((level) == 2 || (level) == 3) \ + dev_info(&cam->dev, fmt "\n", ## args); \ + else if ((level) == 4) \ + dev_warn(&cam->dev, fmt "\n", ## args); \ + else if ((level) >= 5) \ + dev_info(&cam->dev, "[%s:%d] " fmt "\n", \ + __FUNCTION__, __LINE__ , ## args); \ + } \ +} +/* For generic kernel (not device specific) messages */ +# define KDBG(level, fmt, args...) \ +{ \ + if ( ((specific_debug) && (debug == (level))) || \ + ((!specific_debug) && (debug >= (level))) ) { \ + if ((level) >= 1 && (level) <= 4) \ + pr_info("w9968cf: " fmt "\n", ## args); \ + else if ((level) >= 5) \ + pr_debug("w9968cf: [%s:%d] " fmt "\n", __FUNCTION__, \ + __LINE__ , ## args); \ + } \ +} +#else + /* Not debugging: nothing */ +# define DBG(level, fmt, args...) do {;} while(0); +# define KDBG(level, fmt, args...) do {;} while(0); +#endif + +#undef PDBG +#define PDBG(fmt, args...) \ +dev_info(&cam->dev, "[%s:%d] " fmt "\n", __FUNCTION__, __LINE__ , ## args); + +#undef PDBGG +#define PDBGG(fmt, args...) do {;} while(0); /* nothing: it's a placeholder */ + +#endif /* _W9968CF_H_ */ diff --git a/linux/drivers/media/video/w9968cf_decoder.h b/linux/drivers/media/video/w9968cf_decoder.h new file mode 100644 index 000000000..59decbfc5 --- /dev/null +++ b/linux/drivers/media/video/w9968cf_decoder.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Video decoder for the W996[87]CF driver for Linux. * + * * + * Copyright (C) 2003 2004 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 _W9968CF_DECODER_H_ +#define _W9968CF_DECODER_H_ + +/* Comment/uncomment this for high/low quality of compressed video */ +#define W9968CF_DEC_FAST_LOWQUALITY_VIDEO + +#ifdef W9968CF_DEC_FAST_LOWQUALITY_VIDEO +static const unsigned char Y_QUANTABLE[64] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 +}; + +static const unsigned char UV_QUANTABLE[64] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; +#else +static const unsigned char Y_QUANTABLE[64] = { + 8, 5, 5, 8, 12, 20, 25, 30, + 6, 6, 7, 9, 13, 29, 30, 27, + 7, 6, 8, 12, 20, 28, 34, 28, + 7, 8, 11, 14, 25, 43, 40, 31, + 9, 11, 18, 28, 34, 54, 51, 38, + 12, 17, 27, 32, 40, 52, 56, 46, + 24, 32, 39, 43, 51, 60, 60, 50, + 36, 46, 47, 49, 56, 50, 51, 49 +}; + +static const unsigned char UV_QUANTABLE[64] = { + 8, 9, 12, 23, 49, 49, 49, 49, + 9, 10, 13, 33, 49, 49, 49, 49, + 12, 13, 28, 49, 49, 49, 49, 49, + 23, 33, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 49, 49 +}; +#endif + +#define W9968CF_DEC_ERR_CORRUPTED_DATA -1 +#define W9968CF_DEC_ERR_BUF_OVERFLOW -2 +#define W9968CF_DEC_ERR_NO_SOI -3 +#define W9968CF_DEC_ERR_NO_SOF0 -4 +#define W9968CF_DEC_ERR_NO_SOS -5 +#define W9968CF_DEC_ERR_NO_EOI -6 + +extern void w9968cf_init_decoder(void); +extern int w9968cf_check_headers(const unsigned char* Pin, + const unsigned long BUF_SIZE); +extern int w9968cf_decode(const char* Pin, const unsigned long BUF_SIZE, + const unsigned W, const unsigned H, char* Pout); + +#endif /* _W9968CF_DECODER_H_ */ diff --git a/linux/drivers/media/video/w9968cf_vpp.h b/linux/drivers/media/video/w9968cf_vpp.h new file mode 100644 index 000000000..88c9b6c0c --- /dev/null +++ b/linux/drivers/media/video/w9968cf_vpp.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Interface for video post-processing functions for the W996[87]CF driver * + * for Linux. * + * * + * Copyright (C) 2002-2004 by Luca Risolia <luca.risolia@studio.unibo.it> * + * * + * 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 _W9968CF_VPP_H_ +#define _W9968CF_VPP_H_ + +#include <linux/module.h> +#include <asm/types.h> + +struct w9968cf_vpp_t { + struct module* owner; + int (*check_headers)(const unsigned char*, const unsigned long); + int (*decode)(const char*, const unsigned long, const unsigned, + const unsigned, char*); + void (*swap_yuvbytes)(void*, unsigned long); + void (*uyvy_to_rgbx)(u8*, unsigned long, u8*, u16, u8); + void (*scale_up)(u8*, u8*, u16, u16, u16, u16, u16); + + u8 busy; /* read-only flag: module is/is not in use */ +}; + +#endif /* _W9968CF_VPP_H_ */ diff --git a/linux/drivers/media/video/zoran.h b/linux/drivers/media/video/zoran.h new file mode 100644 index 000000000..0166f555a --- /dev/null +++ b/linux/drivers/media/video/zoran.h @@ -0,0 +1,515 @@ +/* + * zoran - Iomega Buz driver + * + * Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + * + * based on + * + * zoran.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. + */ + +#ifndef _BUZ_H_ +#define _BUZ_H_ + +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 MAJOR_VERSION 0 /* driver major version */ +#define MINOR_VERSION 9 /* driver minor version */ +#define RELEASE_VERSION 5 /* release version */ + +#define ZORAN_NAME "ZORAN" /* name of the device */ + +#define ZR_DEVNAME(zr) ((zr)->name) + +#define BUZ_MAX_WIDTH (zr->timing->Wa) +#define BUZ_MAX_HEIGHT (zr->timing->Ha) +#define BUZ_MIN_WIDTH 32 /* never display less than 32 pixels */ +#define BUZ_MIN_HEIGHT 24 /* never display less than 24 rows */ + +#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 */ + +#define BUZ_MAX_INPUT 8 + +#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) + +#define MAX_KMALLOC_MEM (128*1024) + +#include "zr36057.h" + +enum card_type { + UNKNOWN = -1, + + /* Pinnacle/Miro */ + DC10_old, /* DC30 like */ + DC10_new, /* DC10plus like */ + DC10plus, + DC30, + DC30plus, + + /* Linux Media Labs */ + LML33, + LML33R10, + + /* Iomega */ + BUZ, + + /* total number of cards */ + NUM_CARDS +}; + +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 */ +}; + +enum zoran_map_mode { + ZORAN_MAP_MODE_RAW, + ZORAN_MAP_MODE_JPG_REC, +#define ZORAN_MAP_MODE_JPG ZORAN_MAP_MODE_JPG_REC + ZORAN_MAP_MODE_JPG_PLAY, +}; + +enum gpio_type { + GPIO_JPEG_SLEEP = 0, + GPIO_JPEG_RESET, + GPIO_JPEG_FRAME, + GPIO_VID_DIR, + GPIO_VID_EN, + GPIO_VID_RESET, + GPIO_CLK_SEL1, + GPIO_CLK_SEL2, + GPIO_MAX, +}; + +enum gpcs_type { + GPCS_JPEG_RESET = 0, + GPCS_JPEG_START, + GPCS_MAX, +}; + +struct zoran_format { + char *name; + int palette; + __u32 fourcc; + int colorspace; + int depth; + __u32 flags; +}; +/* flags */ +#define ZORAN_FORMAT_COMPRESSED 1<<0 +#define ZORAN_FORMAT_OVERLAY 1<<1 +#define ZORAN_FORMAT_CAPTURE 1<<2 +#define ZORAN_FORMAT_PLAYBACK 1<<3 + +/* overlay-settings */ +struct zoran_overlay_settings { + int is_set; + int x, y, width, height; /* position */ + int clipcount; /* position and number of clips */ + const struct zoran_format *format; /* overlay format */ +}; + +/* v4l-capture settings */ +struct zoran_v4l_settings { + int width, height, bytesperline; /* capture size */ + const struct zoran_format *format; /* capture format */ +}; + +/* whoops, this one is undeclared if !v4l2 */ +#ifndef HAVE_V4L2 +struct v4l2_jpegcompression { + int quality; + int APPn; + int APP_len; + char APP_data[60]; + int COM_len; + char COM_data[60]; + __u32 jpeg_markers; + __u8 reserved[116]; +}; +#endif + +/* jpg-capture/-playback settings */ +struct zoran_jpg_settings { + int decimation; /* this bit is used to set everything to default */ + int HorDcm, VerDcm, TmpDcm; /* capture decimation settings (TmpDcm=1 means both fields) */ + int field_per_buff, odd_even; /* field-settings (odd_even=1 (+TmpDcm=1) means top-field-first) */ + int img_x, img_y, img_width, img_height; /* crop settings (subframe capture) */ + struct v4l2_jpegcompression jpg_comp; /* JPEG-specific capture settings */ +}; + +struct zoran_mapping { + struct file *file; + int count; +}; + +struct zoran_jpg_buffer { + struct zoran_mapping *map; + 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 zoran_v4l_buffer { + struct zoran_mapping *map; + 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_sync bs; /* DONE: info to return to application */ +}; + +enum zoran_lock_activity { + ZORAN_FREE, /* free for use */ + ZORAN_ACTIVE, /* active but unlocked */ + ZORAN_LOCKED, /* locked */ +}; + +/* buffer collections */ +struct zoran_jpg_struct { + enum zoran_lock_activity active; /* feature currently in use? */ + struct zoran_jpg_buffer buffer[BUZ_MAX_FRAME]; /* buffers */ + int num_buffers, buffer_size; + u8 allocated; /* Flag if buffers are allocated */ + u8 ready_to_be_freed; /* hack - see zoran_driver.c */ + u8 need_contiguous; /* Flag if contiguous buffers are needed */ +}; + +struct zoran_v4l_struct { + enum zoran_lock_activity active; /* feature currently in use? */ + struct zoran_v4l_buffer buffer[VIDEO_MAX_FRAME]; /* buffers */ + int num_buffers, buffer_size; + u8 allocated; /* Flag if buffers are allocated */ + u8 ready_to_be_freed; /* hack - see zoran_driver.c */ +}; + +struct zoran; + +/* zoran_fh contains per-open() settings */ +struct zoran_fh { + struct zoran *zr; + + enum zoran_map_mode map_mode; /* Flag which bufferset will map by next mmap() */ + + struct zoran_overlay_settings overlay_settings; + u32 *overlay_mask; /* overlay mask */ + enum zoran_lock_activity overlay_active; /* feature currently in use? */ + + struct zoran_v4l_settings v4l_settings; /* structure with a lot of things to play with */ + struct zoran_v4l_struct v4l_buffers; /* V4L buffers' info */ + + struct zoran_jpg_settings jpg_settings; /* structure with a lot of things to play with */ + struct zoran_jpg_struct jpg_buffers; /* MJPEG buffers' info */ +}; + +struct card_info { + enum card_type type; + char name[32]; + u16 i2c_decoder, i2c_encoder; /* I2C types */ + u16 video_vfe, video_codec; /* videocodec types */ + u16 audio_chip; /* audio type */ + u16 vendor_id, device_id; /* subsystem vendor/device ID */ + + int inputs; /* number of video inputs */ + struct input { + int muxsel; + char name[32]; + } input[BUZ_MAX_INPUT]; + + int norms; + struct tvnorm *tvn[3]; /* supported TV norms */ + + u32 jpeg_int; /* JPEG interrupt */ + u32 vsync_int; /* VSYNC interrupt */ + s8 gpio[GPIO_MAX]; + u8 gpcs[GPCS_MAX]; + + struct vfe_polarity vfe_pol; + u8 gpio_pol[GPIO_MAX]; + + /* is the /GWS line conected? */ + u8 gws_not_connected; + + void (*init) (struct zoran * zr); +}; + +struct zoran { + struct video_device *video_dev; + + struct i2c_adapter i2c_adapter; /* */ + struct i2c_algo_bit_data i2c_algo; /* */ + u32 i2cbr; + + struct i2c_client *decoder; /* video decoder i2c client */ + struct i2c_client *encoder; /* video encoder i2c client */ + + struct videocodec *codec; /* video codec */ + struct videocodec *vfe; /* video front end */ + + struct mutex resource_lock; /* prevent evil stuff */ + + u8 initialized; /* flag if zoran has been correctly initalized */ + int user; /* number of current users */ + struct card_info card; + struct tvnorm *timing; + + 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 */ + unsigned int zr36057_adr; /* bus address of IO mem returned by PCI BIOS */ + unsigned char __iomem *zr36057_mem;/* pointer to mapped IO memory */ + + spinlock_t spinlock; /* Spinlock */ + + /* Video for Linux parameters */ + int input, norm; /* card's norm and input - norm=VIDEO_MODE_* */ + int hue, saturation, contrast, brightness; /* Current picture params */ + struct video_buffer buffer; /* Current buffer params */ + struct zoran_overlay_settings overlay_settings; + u32 *overlay_mask; /* overlay mask */ + enum zoran_lock_activity overlay_active; /* feature currently in use? */ + + wait_queue_head_t v4l_capq; + + 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) + unsigned long v4l_grab_seq; /* Number of frames grabbed */ + struct zoran_v4l_settings v4l_settings; /* structure with a lot of things to play with */ + + /* V4L grab queue of frames pending */ + unsigned long v4l_pend_head; + unsigned long v4l_pend_tail; + unsigned long v4l_sync_tail; + int v4l_pend[V4L_MAX_FRAME]; + struct zoran_v4l_struct v4l_buffers; /* V4L buffers' info */ + + /* Buz MJPEG parameters */ + enum zoran_codec_mode codec_mode; /* status of codec */ + struct zoran_jpg_settings jpg_settings; /* 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 */ + unsigned long jpg_err_seq; /* last seq_num before error */ + unsigned long jpg_err_shift; + unsigned long jpg_queued_num; /* count of frames queued 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_jpg_struct jpg_buffers; /* MJPEG buffers' info */ + + /* Additional stuff for testing */ +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *zoran_proc; +#else + void *zoran_proc; +#endif + int testing; + int jpeg_error; + int intr_counter_GIRQ1; + int intr_counter_GIRQ0; + int intr_counter_CodRepIRQ; + int intr_counter_JPEGRepIRQ; + int field_counter; + int IRQ1_in; + int IRQ1_out; + int JPEG_in; + int JPEG_out; + int JPEG_0; + int JPEG_1; + int END_event_missed; + int JPEG_missed; + int JPEG_error; + int num_errors; + int JPEG_max_missed; + int JPEG_min_missed; + + u32 last_isr; + unsigned long frame_num; + + wait_queue_head_t test_q; +}; + +/*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), zr->zr36057_adr+(adr)) +#define btread(adr) readl(zr->zr36057_adr+(adr)) +#else +#define btwrite(dat,adr) writel((dat), 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) + +#endif /* __kernel__ */ + +#endif diff --git a/linux/drivers/media/video/zoran_card.c b/linux/drivers/media/video/zoran_card.c new file mode 100644 index 000000000..54c8b2a2c --- /dev/null +++ b/linux/drivers/media/video/zoran_card.c @@ -0,0 +1,1578 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/vmalloc.h> + +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> +#include <linux/kmod.h> +#include <linux/wait.h> + +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> +#include <linux/mutex.h> + +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_card.h" +#include "zoran_device.h" +#include "zoran_procfs.h" + +#define I2C_NAME(x) (x)->name + +extern const struct zoran_format zoran_formats[]; + +static int card[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(card, int, NULL, 0); +MODULE_PARM_DESC(card, "The type of card"); + +static int encoder[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(encoder, int, NULL, 0); +MODULE_PARM_DESC(encoder, "i2c TV encoder"); + +static int decoder[BUZ_MAX] = { -1, -1, -1, -1 }; +module_param_array(decoder, int, NULL, 0); +MODULE_PARM_DESC(decoder, "i2c TV decoder"); + +/* + 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 */ +module_param(vidmem, ulong, 0); + +/* + Default input and video norm at startup of the driver. +*/ + +static int default_input = 0; /* 0=Composite, 1=S-Video */ +module_param(default_input, int, 0); +MODULE_PARM_DESC(default_input, + "Default input (0=Composite, 1=S-Video, 2=Internal)"); + +static int default_norm = 0; /* 0=PAL, 1=NTSC 2=SECAM */ +module_param(default_norm, int, 0); +MODULE_PARM_DESC(default_norm, "Default norm (0=PAL, 1=NTSC, 2=SECAM)"); + +static int video_nr = -1; /* /dev/videoN, -1 for autodetect */ +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, "video device number"); + +/* + 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 ! + +*/ + +int v4l_nbufs = 2; +int v4l_bufsize = 128; /* Everybody should be able to work with this setting */ +module_param(v4l_nbufs, int, 0); +MODULE_PARM_DESC(v4l_nbufs, "Maximum number of V4L buffers to use"); +module_param(v4l_bufsize, int, 0); +MODULE_PARM_DESC(v4l_bufsize, "Maximum size per V4L buffer (in kB)"); + +int jpg_nbufs = 32; +int jpg_bufsize = 512; /* max size for 100% quality full-PAL frame */ +module_param(jpg_nbufs, int, 0); +MODULE_PARM_DESC(jpg_nbufs, "Maximum number of JPG buffers to use"); +module_param(jpg_bufsize, int, 0); +MODULE_PARM_DESC(jpg_bufsize, "Maximum size per JPG buffer (in kB)"); + +int pass_through = 0; /* 1=Pass through TV signal when device is not used */ + /* 0=Show color bar when device is not used (LML33: only if lml33dpath=1) */ +module_param(pass_through, int, 0); +MODULE_PARM_DESC(pass_through, + "Pass TV signal through to TV-out when idling"); + +static int debug = 1; +int *zr_debug = &debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +MODULE_DESCRIPTION("Zoran-36057/36067 JPEG codec driver"); +MODULE_AUTHOR("Serguei Miridonov"); +MODULE_LICENSE("GPL"); + +static struct pci_device_id zr36067_pci_tbl[] = { + {PCI_VENDOR_ID_ZORAN, PCI_DEVICE_ID_ZORAN_36057, + PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0} +}; +MODULE_DEVICE_TABLE(pci, zr36067_pci_tbl); + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +int zoran_num; /* number of Buzs in use */ +struct zoran zoran[BUZ_MAX]; + +/* videocodec bus functions ZR36060 */ +static u32 +zr36060_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return -1; + } + + data = post_office_read(zr, 0, 3) & 0xff; + return data; +} + +static void +zr36060_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 0, 1, reg >> 8) + || post_office_write(zr, 0, 2, reg & 0xff)) { + return; + } + + post_office_write(zr, 0, 3, val & 0xff); +} + +/* videocodec bus functions ZR36050 */ +static u32 +zr36050_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return -1; + } + + data = post_office_read(zr, 0, reg & 0x03) & 0xff; // reg. LOWBYTES + read + return data; +} + +static void +zr36050_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr) + || post_office_write(zr, 1, 0, reg >> 2)) { // reg. HIGHBYTES + return; + } + + post_office_write(zr, 0, reg & 0x03, val & 0xff); // reg. LOWBYTES + wr. data +} + +/* videocodec bus functions ZR36016 */ +static u32 +zr36016_read (struct videocodec *codec, + u16 reg) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + __u32 data; + + if (post_office_wait(zr)) { + return -1; + } + + data = post_office_read(zr, 2, reg & 0x03) & 0xff; // read + return data; +} + +/* hack for in zoran_device.c */ +void +zr36016_write (struct videocodec *codec, + u16 reg, + u32 val) +{ + struct zoran *zr = (struct zoran *) codec->master_data->data; + + if (post_office_wait(zr)) { + return; + } + + post_office_write(zr, 2, reg & 0x03, val & 0x0ff); // wr. data +} + +/* + * Board specific information + */ + +static void +dc10_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: dc10_init()\n", ZR_DEVNAME(zr)); + + /* Pixel clock selection */ + GPIO(zr, 4, 0); + GPIO(zr, 5, 1); + /* Enable the video bus sync signals */ + GPIO(zr, 7, 0); +} + +static void +dc10plus_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: dc10plus_init()\n", ZR_DEVNAME(zr)); +} + +static void +buz_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: buz_init()\n", ZR_DEVNAME(zr)); + + /* some stuff from Iomega */ + pci_write_config_dword(zr->pci_dev, 0xfc, 0x90680f15); + pci_write_config_dword(zr->pci_dev, 0x0c, 0x00012020); + pci_write_config_dword(zr->pci_dev, 0xe8, 0xc0200000); +} + +static void +lml33_init (struct zoran *zr) +{ + dprintk(3, KERN_DEBUG "%s: lml33_init()\n", ZR_DEVNAME(zr)); + + GPIO(zr, 2, 1); // Set Composite input/output +} + +static char * +i2cid_to_modulename (u16 i2c_id) +{ + char *name = NULL; + + switch (i2c_id) { + case I2C_DRIVERID_SAA7110: + name = "saa7110"; + break; + case I2C_DRIVERID_SAA7111A: + name = "saa7111"; + break; + case I2C_DRIVERID_SAA7114: + name = "saa7114"; + break; + case I2C_DRIVERID_SAA7185B: + name = "saa7185"; + break; + case I2C_DRIVERID_ADV7170: + name = "adv7170"; + break; + case I2C_DRIVERID_ADV7175: + name = "adv7175"; + break; + case I2C_DRIVERID_BT819: + name = "bt819"; + break; + case I2C_DRIVERID_BT856: + name = "bt856"; + break; + case I2C_DRIVERID_VPX3220: + name = "vpx3220"; + break; +/* case I2C_DRIVERID_VPX3224: + name = "vpx3224"; + break; + case I2C_DRIVERID_MSE3000: + name = "mse3000"; + break;*/ + default: + break; + } + + return name; +} + +static char * +codecid_to_modulename (u16 codecid) +{ + char *name = NULL; + + switch (codecid) { + case CODEC_TYPE_ZR36060: + name = "zr36060"; + break; + case CODEC_TYPE_ZR36050: + name = "zr36050"; + break; + case CODEC_TYPE_ZR36016: + name = "zr36016"; + break; + default: + break; + } + + return name; +} + +// struct tvnorm { +// u16 Wt, Wa, HStart, HSyncStart, Ht, Ha, VStart; +// }; + +static struct tvnorm f50sqpixel = { 944, 768, 83, 880, 625, 576, 16 }; +static struct tvnorm f60sqpixel = { 780, 640, 51, 716, 525, 480, 12 }; +static struct tvnorm f50ccir601 = { 864, 720, 75, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601 = { 858, 720, 57, 788, 525, 480, 16 }; + +static struct tvnorm f50ccir601_lml33 = { 864, 720, 75+34, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lml33 = { 858, 720, 57+34, 788, 525, 480, 16 }; + +/* The DC10 (57/16/50) uses VActive as HSync, so HStart must be 0 */ +static struct tvnorm f50sqpixel_dc10 = { 944, 768, 0, 880, 625, 576, 0 }; +static struct tvnorm f60sqpixel_dc10 = { 780, 640, 0, 716, 525, 480, 12 }; + +/* FIXME: I cannot swap U and V in saa7114, so i do one + * pixel left shift in zoran (75 -> 74) + * (Maxim Yevtyushkin <max@linuxmedialabs.com>) */ +static struct tvnorm f50ccir601_lm33r10 = { 864, 720, 74+54, 804, 625, 576, 18 }; +static struct tvnorm f60ccir601_lm33r10 = { 858, 720, 56+54, 788, 525, 480, 16 }; + +static struct card_info zoran_cards[NUM_CARDS] __devinitdata = { + { + .type = DC10_old, + .name = "DC10(old)", + .i2c_decoder = I2C_DRIVERID_VPX3220, + /*.i2c_encoder = I2C_DRIVERID_MSE3000,*/ + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = DC10_new, + .name = "DC10(new)", + .i2c_decoder = I2C_DRIVERID_SAA7110, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel}, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1}, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10plus_init, + }, { + .type = DC10plus, + .name = "DC10plus", + .vendor_id = PCI_VENDOR_ID_MIRO, + .device_id = PCI_DEVICE_ID_MIRO_DC10PLUS, + .i2c_decoder = I2C_DRIVERID_SAA7110, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 3, + .input = { + { 0, "Composite" }, + { 7, "S-Video" }, + { 5, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel, + &f60sqpixel, + &f50sqpixel + }, + .jpeg_int = ZR36057_ISR_GIRQ0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 3, 0, 6, 1, 2, -1, 4, 5 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { -1, 1 }, + .vfe_pol = { 1, 1, 1, 1, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10plus_init, + }, { + .type = DC30, + .name = "DC30", + .i2c_decoder = I2C_DRIVERID_VPX3220, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = DC30plus, + .name = "DC30plus", + .vendor_id = PCI_VENDOR_ID_MIRO, + .device_id = PCI_DEVICE_ID_MIRO_DC30PLUS, + .i2c_decoder = I2C_DRIVERID_VPX3220, + .i2c_encoder = I2C_DRIVERID_ADV7175, + .video_codec = CODEC_TYPE_ZR36050, + .video_vfe = CODEC_TYPE_ZR36016, + + .inputs = 3, + .input = { + { 1, "Composite" }, + { 2, "S-Video" }, + { 0, "Internal/comp" } + }, + .norms = 3, + .tvn = { + &f50sqpixel_dc10, + &f60sqpixel_dc10, + &f50sqpixel_dc10 + }, + .jpeg_int = 0, + .vsync_int = ZR36057_ISR_GIRQ1, + .gpio = { 2, 1, -1, 3, 7, 0, 4, 5 }, + .gpio_pol = { 0, 0, 0, 1, 0, 0, 0, 0 }, + .gpcs = { -1, 0 }, + .vfe_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gws_not_connected = 0, + .init = &dc10_init, + }, { + .type = LML33, + .name = "LML33", + .i2c_decoder = I2C_DRIVERID_BT819, + .i2c_encoder = I2C_DRIVERID_BT856, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = 2, + .tvn = { + &f50ccir601_lml33, + &f60ccir601_lml33, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &lml33_init, + }, { + .type = LML33R10, + .name = "LML33R10", + .vendor_id = PCI_VENDOR_ID_ELECTRONICDESIGNGMBH, + .device_id = PCI_DEVICE_ID_LML_33R10, + .i2c_decoder = I2C_DRIVERID_SAA7114, + .i2c_encoder = I2C_DRIVERID_ADV7170, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 0, "Composite" }, + { 7, "S-Video" } + }, + .norms = 2, + .tvn = { + &f50ccir601_lm33r10, + &f60ccir601_lm33r10, + NULL + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, 5, 7, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 1, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &lml33_init, + }, { + .type = BUZ, + .name = "Buz", + .vendor_id = PCI_VENDOR_ID_IOMEGA, + .device_id = PCI_DEVICE_ID_IOMEGA_BUZ, + .i2c_decoder = I2C_DRIVERID_SAA7111A, + .i2c_encoder = I2C_DRIVERID_SAA7185B, + .video_codec = CODEC_TYPE_ZR36060, + + .inputs = 2, + .input = { + { 3, "Composite" }, + { 7, "S-Video" } + }, + .norms = 3, + .tvn = { + &f50ccir601, + &f60ccir601, + &f50ccir601 + }, + .jpeg_int = ZR36057_ISR_GIRQ1, + .vsync_int = ZR36057_ISR_GIRQ0, + .gpio = { 1, -1, 3, -1, -1, -1, -1, -1 }, + .gpio_pol = { 0, 0, 0, 0, 0, 0, 0, 0 }, + .gpcs = { 3, 1 }, + .vfe_pol = { 1, 1, 0, 0, 0, 1, 0, 0 }, + .gws_not_connected = 1, + .init = &buz_init, + } +}; + +/* + * I2C functions + */ +/* software I2C functions */ +static int +zoran_i2c_getsda (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return (btread(ZR36057_I2CBR) >> 1) & 1; +} + +static int +zoran_i2c_getscl (void *data) +{ + struct zoran *zr = (struct zoran *) data; + + return btread(ZR36057_I2CBR) & 1; +} + +static void +zoran_i2c_setsda (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 2; + else + zr->i2cbr &= ~2; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static void +zoran_i2c_setscl (void *data, + int state) +{ + struct zoran *zr = (struct zoran *) data; + + if (state) + zr->i2cbr |= 1; + else + zr->i2cbr &= ~1; + btwrite(zr->i2cbr, ZR36057_I2CBR); +} + +static int +zoran_i2c_client_register (struct i2c_client *client) +{ + struct zoran *zr = (struct zoran *) i2c_get_adapdata(client->adapter); + int res = 0; + + dprintk(2, + KERN_DEBUG "%s: i2c_client_register() - driver id = %d\n", + ZR_DEVNAME(zr), client->driver->id); + + mutex_lock(&zr->resource_lock); + + if (zr->user > 0) { + /* we're already busy, so we keep a reference to + * them... Could do a lot of stuff here, but this + * is easiest. (Did I ever mention I'm a lazy ass?) + */ + res = -EBUSY; + goto clientreg_unlock_and_return; + } + + if (client->driver->id == zr->card.i2c_decoder) + zr->decoder = client; + else if (client->driver->id == zr->card.i2c_encoder) + zr->encoder = client; + else { + res = -ENODEV; + goto clientreg_unlock_and_return; + } + +clientreg_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + +static int +zoran_i2c_client_unregister (struct i2c_client *client) +{ + struct zoran *zr = (struct zoran *) i2c_get_adapdata(client->adapter); + int res = 0; + + dprintk(2, KERN_DEBUG "%s: i2c_client_unregister()\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + + if (zr->user > 0) { + res = -EBUSY; + goto clientunreg_unlock_and_return; + } + + /* try to locate it */ + if (client == zr->encoder) { + zr->encoder = NULL; + } else if (client == zr->decoder) { + zr->decoder = NULL; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%d]", zr->id); + } +clientunreg_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; +} + +static struct i2c_algo_bit_data zoran_i2c_bit_data_template = { + .setsda = zoran_i2c_setsda, + .setscl = zoran_i2c_setscl, + .getsda = zoran_i2c_getsda, + .getscl = zoran_i2c_getscl, + .udelay = 10, + .mdelay = 0, + .timeout = 100, +}; + +static struct i2c_adapter zoran_i2c_adapter_template = { + .name = "zr36057", + .id = I2C_HW_B_ZR36067, + .algo = NULL, + .client_register = zoran_i2c_client_register, + .client_unregister = zoran_i2c_client_unregister, +}; + +static int +zoran_register_i2c (struct zoran *zr) +{ + memcpy(&zr->i2c_algo, &zoran_i2c_bit_data_template, + sizeof(struct i2c_algo_bit_data)); + zr->i2c_algo.data = zr; + memcpy(&zr->i2c_adapter, &zoran_i2c_adapter_template, + sizeof(struct i2c_adapter)); + strncpy(I2C_NAME(&zr->i2c_adapter), ZR_DEVNAME(zr), + sizeof(I2C_NAME(&zr->i2c_adapter)) - 1); + i2c_set_adapdata(&zr->i2c_adapter, zr); + zr->i2c_adapter.algo_data = &zr->i2c_algo; + return i2c_bit_add_bus(&zr->i2c_adapter); +} + +static void +zoran_unregister_i2c (struct zoran *zr) +{ + i2c_bit_del_bus((&zr->i2c_adapter)); +} + +/* Check a zoran_params struct for correctness, insert default params */ + +int +zoran_check_jpg_settings (struct zoran *zr, + struct zoran_jpg_settings *settings) +{ + int err = 0, err0 = 0; + + dprintk(4, + KERN_DEBUG + "%s: check_jpg_settings() - dec: %d, Hdcm: %d, Vdcm: %d, Tdcm: %d\n", + ZR_DEVNAME(zr), settings->decimation, settings->HorDcm, + settings->VerDcm, settings->TmpDcm); + dprintk(4, + KERN_DEBUG + "%s: check_jpg_settings() - x: %d, y: %d, w: %d, y: %d\n", + ZR_DEVNAME(zr), settings->img_x, settings->img_y, + settings->img_width, settings->img_height); + /* Check decimation, set default values for decimation = 1, 2, 4 */ + switch (settings->decimation) { + case 1: + + settings->HorDcm = 1; + settings->VerDcm = 1; + settings->TmpDcm = 1; + settings->field_per_buff = 2; + settings->img_x = 0; + settings->img_y = 0; + settings->img_width = BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 2: + + settings->HorDcm = 2; + settings->VerDcm = 1; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 4: + + if (zr->card.type == DC10_new) { + dprintk(1, + KERN_DEBUG + "%s: check_jpg_settings() - HDec by 4 is not supported on the DC10\n", + ZR_DEVNAME(zr)); + err0++; + break; + } + + settings->HorDcm = 4; + settings->VerDcm = 2; + settings->TmpDcm = 2; + settings->field_per_buff = 1; + settings->img_x = (BUZ_MAX_WIDTH == 720) ? 8 : 0; + settings->img_y = 0; + settings->img_width = + (BUZ_MAX_WIDTH == 720) ? 704 : BUZ_MAX_WIDTH; + settings->img_height = BUZ_MAX_HEIGHT / 2; + break; + case 0: + + /* We have to check the data the user has set */ + + if (settings->HorDcm != 1 && settings->HorDcm != 2 && + (zr->card.type == DC10_new || settings->HorDcm != 4)) + err0++; + if (settings->VerDcm != 1 && settings->VerDcm != 2) + err0++; + if (settings->TmpDcm != 1 && settings->TmpDcm != 2) + err0++; + if (settings->field_per_buff != 1 && + settings->field_per_buff != 2) + err0++; + if (settings->img_x < 0) + err0++; + if (settings->img_y < 0) + err0++; + if (settings->img_width < 0) + err0++; + if (settings->img_height < 0) + err0++; + if (settings->img_x + settings->img_width > BUZ_MAX_WIDTH) + err0++; + if (settings->img_y + settings->img_height > + BUZ_MAX_HEIGHT / 2) + err0++; + if (settings->HorDcm && settings->VerDcm) { + if (settings->img_width % + (16 * settings->HorDcm) != 0) + err0++; + if (settings->img_height % + (8 * settings->VerDcm) != 0) + err0++; + } + + if (err0) { + dprintk(1, + KERN_ERR + "%s: check_jpg_settings() - error in params for decimation = 0\n", + ZR_DEVNAME(zr)); + err++; + } + break; + default: + dprintk(1, + KERN_ERR + "%s: check_jpg_settings() - decimation = %d, must be 0, 1, 2 or 4\n", + ZR_DEVNAME(zr), settings->decimation); + err++; + break; + } + + if (settings->jpg_comp.quality > 100) + settings->jpg_comp.quality = 100; + if (settings->jpg_comp.quality < 5) + settings->jpg_comp.quality = 5; + if (settings->jpg_comp.APPn < 0) + settings->jpg_comp.APPn = 0; + if (settings->jpg_comp.APPn > 15) + settings->jpg_comp.APPn = 15; + if (settings->jpg_comp.APP_len < 0) + settings->jpg_comp.APP_len = 0; + if (settings->jpg_comp.APP_len > 60) + settings->jpg_comp.APP_len = 60; + if (settings->jpg_comp.COM_len < 0) + settings->jpg_comp.COM_len = 0; + if (settings->jpg_comp.COM_len > 60) + settings->jpg_comp.COM_len = 60; + if (err) + return -EINVAL; + return 0; +} + +void +zoran_open_init_params (struct zoran *zr) +{ + int i; + + /* User must explicitly set a window */ + zr->overlay_settings.is_set = 0; + zr->overlay_mask = NULL; + zr->overlay_active = ZORAN_FREE; + + zr->v4l_memgrab_active = 0; + zr->v4l_overlay_active = 0; + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + zr->v4l_grab_seq = 0; + zr->v4l_settings.width = 192; + zr->v4l_settings.height = 144; + zr->v4l_settings.format = &zoran_formats[4]; /* YUY2 - YUV-4:2:2 packed */ + zr->v4l_settings.bytesperline = + zr->v4l_settings.width * + ((zr->v4l_settings.format->depth + 7) / 8); + + /* DMA ring stuff for V4L */ + zr->v4l_pend_tail = 0; + zr->v4l_pend_head = 0; + zr->v4l_sync_tail = 0; + zr->v4l_buffers.active = ZORAN_FREE; + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + zr->v4l_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->v4l_buffers.allocated = 0; + + for (i = 0; i < BUZ_MAX_FRAME; i++) { + zr->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + zr->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + /* Set necessary params and call zoran_check_jpg_settings to set the defaults */ + zr->jpg_settings.decimation = 1; + zr->jpg_settings.jpg_comp.quality = 50; /* default compression factor 8 */ + if (zr->card.type != BUZ) + zr->jpg_settings.odd_even = 1; + else + zr->jpg_settings.odd_even = 0; + zr->jpg_settings.jpg_comp.APPn = 0; + zr->jpg_settings.jpg_comp.APP_len = 0; /* No APPn marker */ + memset(zr->jpg_settings.jpg_comp.APP_data, 0, + sizeof(zr->jpg_settings.jpg_comp.APP_data)); + zr->jpg_settings.jpg_comp.COM_len = 0; /* No COM marker */ + memset(zr->jpg_settings.jpg_comp.COM_data, 0, + sizeof(zr->jpg_settings.jpg_comp.COM_data)); + zr->jpg_settings.jpg_comp.jpeg_markers = + JPEG_MARKER_DHT | JPEG_MARKER_DQT; + i = zoran_check_jpg_settings(zr, &zr->jpg_settings); + if (i) + dprintk(1, + KERN_ERR + "%s: zoran_open_init_params() internal error\n", + ZR_DEVNAME(zr)); + + clear_interrupt_counters(zr); + zr->testing = 0; +} + +static void __devinit +test_interrupts (struct zoran *zr) +{ + DEFINE_WAIT(wait); + int timeout, icr; + + clear_interrupt_counters(zr); + + zr->testing = 1; + icr = btread(ZR36057_ICR); + btwrite(0x78000000 | ZR36057_ICR_IntPinEn, ZR36057_ICR); + prepare_to_wait(&zr->test_q, &wait, TASK_INTERRUPTIBLE); + timeout = schedule_timeout(HZ); + finish_wait(&zr->test_q, &wait); + btwrite(0, ZR36057_ICR); + btwrite(0x78000000, ZR36057_ISR); + zr->testing = 0; + dprintk(5, KERN_INFO "%s: Testing interrupts...\n", ZR_DEVNAME(zr)); + if (timeout) { + dprintk(1, ": time spent: %d\n", 1 * HZ - timeout); + } + if (*zr_debug > 1) + print_interrupts(zr); + btwrite(icr, ZR36057_ICR); +} + +static int __devinit +zr36057_init (struct zoran *zr) +{ + int j, err; + int two = 2; + int zero = 0; + + dprintk(1, + KERN_INFO + "%s: zr36057_init() - initializing card[%d], zr=%p\n", + ZR_DEVNAME(zr), zr->id, zr); + + /* default setup of all parameters which will persist between opens */ + zr->user = 0; + + init_waitqueue_head(&zr->v4l_capq); + init_waitqueue_head(&zr->jpg_capq); + init_waitqueue_head(&zr->test_q); + zr->jpg_buffers.allocated = 0; + zr->v4l_buffers.allocated = 0; + + zr->buffer.base = (void *) vidmem; + zr->buffer.width = 0; + zr->buffer.height = 0; + zr->buffer.depth = 0; + zr->buffer.bytesperline = 0; + + /* Avoid nonsense settings from user for default input/norm */ + if (default_norm < VIDEO_MODE_PAL && + default_norm > VIDEO_MODE_SECAM) + default_norm = VIDEO_MODE_PAL; + zr->norm = default_norm; + if (!(zr->timing = zr->card.tvn[zr->norm])) { + dprintk(1, + KERN_WARNING + "%s: zr36057_init() - default TV standard not supported by hardware. PAL will be used.\n", + ZR_DEVNAME(zr)); + zr->norm = VIDEO_MODE_PAL; + zr->timing = zr->card.tvn[zr->norm]; + } + + zr->input = default_input = (default_input ? 1 : 0); + + /* Should the following be reset at every open ? */ + zr->hue = 32768; + zr->contrast = 32768; + zr->saturation = 32768; + zr->brightness = 32768; + + /* 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 */ + zr->stat_com = kzalloc(BUZ_NUM_STAT_COM * 4, GFP_KERNEL); + zr->video_dev = kmalloc(sizeof(struct video_device), GFP_KERNEL); + if (!zr->stat_com || !zr->video_dev) { + dprintk(1, + KERN_ERR + "%s: zr36057_init() - kmalloc (STAT_COM) failed\n", + ZR_DEVNAME(zr)); + err = -ENOMEM; + goto exit_free; + } + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = 1; /* mark as unavailable to zr36057 */ + } + + /* + * Now add the template and register the device unit. + */ + memcpy(zr->video_dev, &zoran_template, sizeof(zoran_template)); + strcpy(zr->video_dev->name, ZR_DEVNAME(zr)); + err = video_register_device(zr->video_dev, VFL_TYPE_GRABBER, video_nr); + if (err < 0) + goto exit_unregister; + + zoran_init_hardware(zr); + if (*zr_debug > 2) + detect_guest_activity(zr); + test_interrupts(zr); + if (!pass_through) { + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + encoder_command(zr, ENCODER_SET_INPUT, &two); + } + + zr->zoran_proc = NULL; + zr->initialized = 1; + return 0; + +exit_unregister: + zoran_unregister_i2c(zr); +exit_free: + kfree(zr->stat_com); + kfree(zr->video_dev); + return err; +} + +static void +zoran_release (struct zoran *zr) +{ + if (!zr->initialized) + return; + /* unregister videocodec bus */ + if (zr->codec) { + struct videocodec_master *master = zr->codec->master_data; + + videocodec_detach(zr->codec); + kfree(master); + } + if (zr->vfe) { + struct videocodec_master *master = zr->vfe->master_data; + + videocodec_detach(zr->vfe); + kfree(master); + } + + /* unregister i2c bus */ + zoran_unregister_i2c(zr); + /* disable PCI bus-mastering */ + zoran_set_pci_master(zr, 0); + /* put chip into reset */ + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); + /* unmap and free memory */ + kfree(zr->stat_com); + zoran_proc_cleanup(zr); + iounmap(zr->zr36057_mem); + pci_disable_device(zr->pci_dev); + video_unregister_device(zr->video_dev); +} + +void +zoran_vdev_release (struct video_device *vdev) +{ + kfree(vdev); +} + +static struct videocodec_master * __devinit +zoran_setup_videocodec (struct zoran *zr, + int type) +{ + struct videocodec_master *m = NULL; + + m = kmalloc(sizeof(struct videocodec_master), GFP_KERNEL); + if (!m) { + dprintk(1, + KERN_ERR + "%s: zoran_setup_videocodec() - no memory\n", + ZR_DEVNAME(zr)); + return m; + } + + m->magic = 0L; /* magic not used */ + m->type = VID_HARDWARE_ZR36067; + m->flags = CODEC_FLAG_ENCODER | CODEC_FLAG_DECODER; + strncpy(m->name, ZR_DEVNAME(zr), sizeof(m->name)); + m->data = zr; + + switch (type) + { + case CODEC_TYPE_ZR36060: + m->readreg = zr36060_read; + m->writereg = zr36060_write; + m->flags |= CODEC_FLAG_JPEG | CODEC_FLAG_VFE; + break; + case CODEC_TYPE_ZR36050: + m->readreg = zr36050_read; + m->writereg = zr36050_write; + m->flags |= CODEC_FLAG_JPEG; + break; + case CODEC_TYPE_ZR36016: + m->readreg = zr36016_read; + m->writereg = zr36016_write; + m->flags |= CODEC_FLAG_VFE; + break; + } + + return m; +} + +/* + * Scan for a Buz card (actually for the PCI contoler ZR36057), + * request the irq and map the io memory + */ +static int __devinit +find_zr36057 (void) +{ + unsigned char latency, need_latency; + struct zoran *zr; + struct pci_dev *dev = NULL; + int result; + struct videocodec_master *master_vfe = NULL; + struct videocodec_master *master_codec = NULL; + int card_num; + char *i2c_enc_name, *i2c_dec_name, *codec_name, *vfe_name; + + zoran_num = 0; + while (zoran_num < BUZ_MAX && + (dev = + pci_find_device(PCI_VENDOR_ID_ZORAN, + PCI_DEVICE_ID_ZORAN_36057, dev)) != NULL) { + card_num = card[zoran_num]; + zr = &zoran[zoran_num]; + memset(zr, 0, sizeof(struct zoran)); // Just in case if previous cycle failed + zr->pci_dev = dev; + //zr->zr36057_mem = NULL; + zr->id = zoran_num; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), "MJPEG[%u]", zr->id); + spin_lock_init(&zr->spinlock); + mutex_init(&zr->resource_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) { + dprintk(1, + KERN_INFO + "%s: Zoran ZR36057 (rev %d) irq: %d, memory: 0x%08x.\n", + ZR_DEVNAME(zr), zr->revision, zr->pci_dev->irq, + zr->zr36057_adr); + + if (card_num == -1) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no card specified, please use the card=X insmod option\n", + ZR_DEVNAME(zr)); + continue; + } + } else { + int i; + unsigned short ss_vendor, ss_device; + + ss_vendor = zr->pci_dev->subsystem_vendor; + ss_device = zr->pci_dev->subsystem_device; + dprintk(1, + KERN_INFO + "%s: Zoran ZR36067 (rev %d) irq: %d, memory: 0x%08x\n", + ZR_DEVNAME(zr), zr->revision, zr->pci_dev->irq, + zr->zr36057_adr); + dprintk(1, + KERN_INFO + "%s: subsystem vendor=0x%04x id=0x%04x\n", + ZR_DEVNAME(zr), ss_vendor, ss_device); + if (card_num == -1) { + dprintk(3, + KERN_DEBUG + "%s: find_zr36057() - trying to autodetect card type\n", + ZR_DEVNAME(zr)); + for (i=0;i<NUM_CARDS;i++) { + if (ss_vendor == zoran_cards[i].vendor_id && + ss_device == zoran_cards[i].device_id) { + dprintk(3, + KERN_DEBUG + "%s: find_zr36057() - card %s detected\n", + ZR_DEVNAME(zr), + zoran_cards[i].name); + card_num = i; + break; + } + } + if (i == NUM_CARDS) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - unknown card\n", + ZR_DEVNAME(zr)); + continue; + } + } + } + + if (card_num < 0 || card_num >= NUM_CARDS) { + dprintk(2, + KERN_ERR + "%s: find_zr36057() - invalid cardnum %d\n", + ZR_DEVNAME(zr), card_num); + continue; + } + + /* even though we make this a non pointer and thus + * theoretically allow for making changes to this struct + * on a per-individual card basis at runtime, this is + * strongly discouraged. This structure is intended to + * keep general card information, no settings or anything */ + zr->card = zoran_cards[card_num]; + snprintf(ZR_DEVNAME(zr), sizeof(ZR_DEVNAME(zr)), + "%s[%u]", zr->card.name, zr->id); + + zr->zr36057_mem = ioremap_nocache(zr->zr36057_adr, 0x1000); + if (!zr->zr36057_mem) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - ioremap failed\n", + ZR_DEVNAME(zr)); + continue; + } + + result = request_irq(zr->pci_dev->irq, + zoran_irq, + SA_SHIRQ | SA_INTERRUPT, + ZR_DEVNAME(zr), + (void *) zr); + if (result < 0) { + if (result == -EINVAL) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - bad irq number or handler\n", + ZR_DEVNAME(zr)); + } else if (result == -EBUSY) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - IRQ %d busy, change your PnP config in BIOS\n", + ZR_DEVNAME(zr), zr->pci_dev->irq); + } else { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - can't assign irq, error code %d\n", + ZR_DEVNAME(zr), result); + } + goto zr_unmap; + } + + /* set PCI latency timer */ + pci_read_config_byte(zr->pci_dev, PCI_LATENCY_TIMER, + &latency); + need_latency = zr->revision > 1 ? 32 : 48; + if (latency != need_latency) { + dprintk(2, + KERN_INFO + "%s: Changing PCI latency from %d to %d.\n", + ZR_DEVNAME(zr), latency, need_latency); + pci_write_config_byte(zr->pci_dev, + PCI_LATENCY_TIMER, + need_latency); + } + + zr36057_restart(zr); + /* i2c */ + dprintk(2, KERN_INFO "%s: Initializing i2c bus...\n", + ZR_DEVNAME(zr)); + + /* i2c decoder */ + if (decoder[zr->id] != -1) { + i2c_dec_name = i2cid_to_modulename(decoder[zr->id]); + zr->card.i2c_decoder = decoder[zr->id]; + } else if (zr->card.i2c_decoder != 0) { + i2c_dec_name = + i2cid_to_modulename(zr->card.i2c_decoder); + } else { + i2c_dec_name = NULL; + } + + if (i2c_dec_name) { + if ((result = request_module(i2c_dec_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load module %s: %d\n", + ZR_DEVNAME(zr), i2c_dec_name, result); + } + } + + /* i2c encoder */ + if (encoder[zr->id] != -1) { + i2c_enc_name = i2cid_to_modulename(encoder[zr->id]); + zr->card.i2c_encoder = encoder[zr->id]; + } else if (zr->card.i2c_encoder != 0) { + i2c_enc_name = + i2cid_to_modulename(zr->card.i2c_encoder); + } else { + i2c_enc_name = NULL; + } + + if (i2c_enc_name) { + if ((result = request_module(i2c_enc_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load module %s: %d\n", + ZR_DEVNAME(zr), i2c_enc_name, result); + } + } + + if (zoran_register_i2c(zr) < 0) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - can't initialize i2c bus\n", + ZR_DEVNAME(zr)); + goto zr_free_irq; + } + + dprintk(2, + KERN_INFO "%s: Initializing videocodec bus...\n", + ZR_DEVNAME(zr)); + + if (zr->card.video_codec != 0 && + (codec_name = + codecid_to_modulename(zr->card.video_codec)) != NULL) { + if ((result = request_module(codec_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), codec_name, result); + } + } + if (zr->card.video_vfe != 0 && + (vfe_name = + codecid_to_modulename(zr->card.video_vfe)) != NULL) { + if ((result = request_module(vfe_name)) < 0) { + dprintk(1, + KERN_ERR + "%s: failed to load modules %s: %d\n", + ZR_DEVNAME(zr), vfe_name, result); + } + } + + /* reset JPEG codec */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_reset(zr); + /* video bus enabled */ + /* display codec revision */ + if (zr->card.video_codec != 0) { + master_codec = zoran_setup_videocodec(zr, + zr->card.video_codec); + if (!master_codec) + goto zr_unreg_i2c; + zr->codec = videocodec_attach(master_codec); + if (!zr->codec) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no codec found\n", + ZR_DEVNAME(zr)); + goto zr_free_codec; + } + if (zr->codec->type != zr->card.video_codec) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - wrong codec\n", + ZR_DEVNAME(zr)); + goto zr_detach_codec; + } + } + if (zr->card.video_vfe != 0) { + master_vfe = zoran_setup_videocodec(zr, + zr->card.video_vfe); + if (!master_vfe) + goto zr_detach_codec; + zr->vfe = videocodec_attach(master_vfe); + if (!zr->vfe) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() - no VFE found\n", + ZR_DEVNAME(zr)); + goto zr_free_vfe; + } + if (zr->vfe->type != zr->card.video_vfe) { + dprintk(1, + KERN_ERR + "%s: find_zr36057() = wrong VFE\n", + ZR_DEVNAME(zr)); + goto zr_detach_vfe; + } + } + + zoran_num++; + continue; + + // Init errors + zr_detach_vfe: + videocodec_detach(zr->vfe); + zr_free_vfe: + kfree(master_vfe); + zr_detach_codec: + videocodec_detach(zr->codec); + zr_free_codec: + kfree(master_codec); + zr_unreg_i2c: + zoran_unregister_i2c(zr); + zr_free_irq: + btwrite(0, ZR36057_SPGPPCR); + free_irq(zr->pci_dev->irq, zr); + zr_unmap: + iounmap(zr->zr36057_mem); + continue; + } + if (zoran_num == 0) { + dprintk(1, KERN_INFO "No known MJPEG cards found.\n"); + } + return zoran_num; +} + +static int __init +init_dc10_cards (void) +{ + int i; + + memset(zoran, 0, sizeof(zoran)); + printk(KERN_INFO "Zoran MJPEG board driver version %d.%d.%d\n", + MAJOR_VERSION, MINOR_VERSION, RELEASE_VERSION); + + /* Look for cards */ + if (find_zr36057() < 0) { + return -EIO; + } + if (zoran_num == 0) + return -ENODEV; + dprintk(1, KERN_INFO "%s: %d card(s) found\n", ZORAN_NAME, + zoran_num); + /* check the parameters we have been given, adjust if necessary */ + if (v4l_nbufs < 2) + v4l_nbufs = 2; + 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; + if (jpg_nbufs < 4) + jpg_nbufs = 4; + if (jpg_nbufs > BUZ_MAX_FRAME) + jpg_nbufs = BUZ_MAX_FRAME; + jpg_bufsize = PAGE_ALIGN(jpg_bufsize * 1024); + if (jpg_bufsize < 8192) + jpg_bufsize = 8192; + if (jpg_bufsize > (512 * 1024)) + jpg_bufsize = 512 * 1024; + /* Use parameter for vidmem or try to find a video card */ + if (vidmem) { + dprintk(1, + KERN_INFO + "%s: Using supplied video memory base address @ 0x%lx\n", + ZORAN_NAME, vidmem); + } + + /* random nonsense */ + dprintk(5, KERN_DEBUG "Jotti is een held!\n"); + + /* some mainboards might not do PCI-PCI data transfer well */ + if (pci_pci_problems & PCIPCI_FAIL) { + dprintk(1, + KERN_WARNING + "%s: chipset may not support reliable PCI-PCI DMA\n", + ZORAN_NAME); + } + + /* take care of Natoma chipset and a revision 1 zr36057 */ + for (i = 0; i < zoran_num; i++) { + struct zoran *zr = &zoran[i]; + + if (pci_pci_problems & PCIPCI_NATOMA && zr->revision <= 1) { + zr->jpg_buffers.need_contiguous = 1; + dprintk(1, + KERN_INFO + "%s: ZR36057/Natoma bug, max. buffer size is 128K\n", + ZR_DEVNAME(zr)); + } + + if (zr36057_init(zr) < 0) { + for (i = 0; i < zoran_num; i++) + zoran_release(&zoran[i]); + return -EIO; + } + zoran_proc_init(zr); + } + + return 0; +} + +static void __exit +unload_dc10_cards (void) +{ + int i; + + for (i = 0; i < zoran_num; i++) + zoran_release(&zoran[i]); +} + +module_init(init_dc10_cards); +module_exit(unload_dc10_cards); diff --git a/linux/drivers/media/video/zoran_card.h b/linux/drivers/media/video/zoran_card.h new file mode 100644 index 000000000..ad997c30b --- /dev/null +++ b/linux/drivers/media/video/zoran_card.h @@ -0,0 +1,45 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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 __ZORAN_CARD_H__ +#define __ZORAN_CARD_H__ + +/* Anybody who uses more than four? */ +#define BUZ_MAX 4 +extern int zoran_num; +extern struct zoran zoran[BUZ_MAX]; + +extern struct video_device zoran_template; + +extern int zoran_check_jpg_settings(struct zoran *zr, + struct zoran_jpg_settings *settings); +extern void zoran_open_init_params(struct zoran *zr); +extern void zoran_vdev_release(struct video_device *vdev); + +#endif /* __ZORAN_CARD_H__ */ diff --git a/linux/drivers/media/video/zoran_device.c b/linux/drivers/media/video/zoran_device.c new file mode 100644 index 000000000..bb5011280 --- /dev/null +++ b/linux/drivers/media/video/zoran_device.c @@ -0,0 +1,1786 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles device access (PCI/I2C/codec/...) + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/vmalloc.h> +#include <linux/byteorder/generic.h> + +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> + +#include <linux/pci.h> +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_device.h" + +#define IRQ_MASK ( ZR36057_ISR_GIRQ0 | \ + ZR36057_ISR_GIRQ1 | \ + ZR36057_ISR_JPEGRepIRQ ) + +extern const struct zoran_format zoran_formats[]; + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +static int lml33dpath = 0; /* 1 will use digital path in capture + * mode instead of analog. It can be + * used for picture adjustments using + * tool like xawtv while watching image + * on TV monitor connected to the output. + * However, due to absence of 75 Ohm + * load on Bt819 input, there will be + * some image imperfections */ + +module_param(lml33dpath, bool, 0); +MODULE_PARM_DESC(lml33dpath, + "Use digital path capture mode (on LML33 cards)"); + +static void +zr36057_init_vfe (struct zoran *zr); + +/* + * General Purpose I/O and Guest bus access + */ + +/* + * This is a bit tricky. When a board lacks a GPIO function, the corresponding + * GPIO bit number in the card_info structure is set to 0. + */ + +void +GPIO (struct zoran *zr, + int bit, + unsigned int value) +{ + u32 reg; + u32 mask; + + /* Make sure the bit number is legal + * A bit number of -1 (lacking) gives a mask of 0, + * making it harmless */ + mask = (1 << (24 + bit)) & 0xff000000; + reg = btread(ZR36057_GPPGCR1) & ~mask; + if (value) { + reg |= mask; + } + btwrite(reg, ZR36057_GPPGCR1); + udelay(1); +} + +/* + * Wait til post office is no longer busy + */ + +int +post_office_wait (struct zoran *zr) +{ + u32 por; + +// while (((por = btread(ZR36057_POR)) & (ZR36057_POR_POPen | ZR36057_POR_POTime)) == ZR36057_POR_POPen) { + while ((por = btread(ZR36057_POR)) & ZR36057_POR_POPen) { + /* wait for something to happen */ + } + if ((por & ZR36057_POR_POTime) && !zr->card.gws_not_connected) { + /* In LML33/BUZ \GWS line is not connected, so it has always timeout set */ + dprintk(1, KERN_INFO "%s: pop timeout %08x\n", ZR_DEVNAME(zr), + por); + return -1; + } + + return 0; +} + +int +post_office_write (struct zoran *zr, + unsigned int guest, + unsigned int reg, + unsigned int value) +{ + u32 por; + + por = + ZR36057_POR_PODir | ZR36057_POR_POTime | ((guest & 7) << 20) | + ((reg & 7) << 16) | (value & 0xFF); + btwrite(por, ZR36057_POR); + + return post_office_wait(zr); +} + +int +post_office_read (struct zoran *zr, + unsigned int guest, + unsigned int reg) +{ + u32 por; + + 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; +} + +/* + * detect guests + */ + +static void +dump_guests (struct zoran *zr) +{ + if (*zr_debug > 2) { + int i, guest[8]; + + for (i = 1; i < 8; i++) { // Don't read jpeg codec here + guest[i] = post_office_read(zr, i, 0); + } + + printk(KERN_INFO "%s: Guests:", ZR_DEVNAME(zr)); + + for (i = 1; i < 8; i++) { + printk(" 0x%02x", guest[i]); + } + printk("\n"); + } +} + +static inline unsigned long +get_time (void) +{ + struct timeval tv; + + do_gettimeofday(&tv); + return (1000000 * tv.tv_sec + tv.tv_usec); +} + +void +detect_guest_activity (struct zoran *zr) +{ + int timeout, i, j, res, guest[8], guest0[8], change[8][3]; + unsigned long t0, t1; + + dump_guests(zr); + printk(KERN_INFO "%s: Detecting guests activity, please wait...\n", + ZR_DEVNAME(zr)); + for (i = 1; i < 8; i++) { // Don't read jpeg codec here + guest0[i] = guest[i] = post_office_read(zr, i, 0); + } + + timeout = 0; + j = 0; + t0 = get_time(); + while (timeout < 10000) { + udelay(10); + timeout++; + for (i = 1; (i < 8) && (j < 8); i++) { + res = post_office_read(zr, i, 0); + if (res != guest[i]) { + t1 = get_time(); + change[j][0] = (t1 - t0); + t0 = t1; + change[j][1] = i; + change[j][2] = res; + j++; + guest[i] = res; + } + } + if (j >= 8) + break; + } + printk(KERN_INFO "%s: Guests:", ZR_DEVNAME(zr)); + + for (i = 1; i < 8; i++) { + printk(" 0x%02x", guest0[i]); + } + printk("\n"); + if (j == 0) { + printk(KERN_INFO "%s: No activity detected.\n", ZR_DEVNAME(zr)); + return; + } + for (i = 0; i < j; i++) { + printk(KERN_INFO "%s: %6d: %d => 0x%02x\n", ZR_DEVNAME(zr), + change[i][0], change[i][1], change[i][2]); + } +} + +/* + * JPEG Codec access + */ + +void +jpeg_codec_sleep (struct zoran *zr, + int sleep) +{ + GPIO(zr, zr->card.gpio[GPIO_JPEG_SLEEP], !sleep); + if (!sleep) { + dprintk(3, + KERN_DEBUG + "%s: jpeg_codec_sleep() - wake GPIO=0x%08x\n", + ZR_DEVNAME(zr), btread(ZR36057_GPPGCR1)); + udelay(500); + } else { + dprintk(3, + KERN_DEBUG + "%s: jpeg_codec_sleep() - sleep GPIO=0x%08x\n", + ZR_DEVNAME(zr), btread(ZR36057_GPPGCR1)); + udelay(2); + } +} + +int +jpeg_codec_reset (struct zoran *zr) +{ + /* Take the codec out of sleep */ + jpeg_codec_sleep(zr, 0); + + if (zr->card.gpcs[GPCS_JPEG_RESET] != 0xff) { + post_office_write(zr, zr->card.gpcs[GPCS_JPEG_RESET], 0, + 0); + udelay(2); + } else { + GPIO(zr, zr->card.gpio[GPIO_JPEG_RESET], 0); + udelay(2); + GPIO(zr, zr->card.gpio[GPIO_JPEG_RESET], 1); + udelay(2); + } + + return 0; +} + +/* + * 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. + */ + +static void +zr36057_adjust_vfe (struct zoran *zr, + enum zoran_codec_mode mode) +{ + u32 reg; + + switch (mode) { + case BUZ_MODE_MOTION_DECOMPRESS: + btand(~ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if ((reg & (1 << 10)) && zr->card.type != LML33R10) { + reg += ((1 << 10) | 1); + } + btwrite(reg, ZR36057_VFEHCR); + break; + case BUZ_MODE_MOTION_COMPRESS: + case BUZ_MODE_IDLE: + default: + if (zr->norm == VIDEO_MODE_NTSC || + (zr->card.type == LML33R10 && + zr->norm == VIDEO_MODE_PAL)) + btand(~ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + else + btor(ZR36057_VFESPFR_ExtFl, ZR36057_VFESPFR); + reg = btread(ZR36057_VFEHCR); + if (!(reg & (1 << 10)) && zr->card.type != LML33R10) { + reg -= ((1 << 10) | 1); + } + btwrite(reg, ZR36057_VFEHCR); + break; + } +} + +/* + * set geometry + */ + +static void +zr36057_set_vfe (struct zoran *zr, + int video_width, + int video_height, + const struct zoran_format *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; + + tvn = zr->timing; + + Wa = tvn->Wa; + Ha = tvn->Ha; + + dprintk(2, KERN_INFO "%s: set_vfe() - width = %d, height = %d\n", + ZR_DEVNAME(zr), video_width, video_height); + + if (zr->norm != VIDEO_MODE_PAL && + zr->norm != VIDEO_MODE_NTSC && + zr->norm != VIDEO_MODE_SECAM) { + dprintk(1, + KERN_ERR "%s: set_vfe() - norm = %d not valid\n", + ZR_DEVNAME(zr), zr->norm); + return; + } + if (video_width < BUZ_MIN_WIDTH || + video_height < BUZ_MIN_HEIGHT || + video_width > Wa || video_height > Ha) { + dprintk(1, KERN_ERR "%s: set_vfe: w=%d h=%d not valid\n", + ZR_DEVNAME(zr), video_width, video_height); + return; + } + + /**** 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 ? tvn->HStart : 1; + /* (Ronald) Original comment: + * "| 1 Doesn't have any effect, tested on both a DC10 and a DC10+" + * this is false. It inverses chroma values on the LML33R10 (so Cr + * suddenly is shown as Cb and reverse, really cool effect if you + * want to see blue faces, not useful otherwise). So don't use |1. + * However, the DC10 has '0' as HStart, but does need |1, so we + * use a dirty check... + */ + HEnd = HStart + tvn->Wa - 1; + HStart += hcrop1; + HEnd -= hcrop2; + reg = ((HStart & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HStart) + | ((HEnd & ZR36057_VFEHCR_Hmask) << ZR36057_VFEHCR_HEnd); + if (zr->card.vfe_pol.hsync_pol) + reg |= ZR36057_VFEHCR_HSPol; + btwrite(reg, ZR36057_VFEHCR); + + /* Vertical */ + DispMode = !(video_height > BUZ_MAX_HEIGHT / 2); + 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; FIXME SnapShot times out with -1 in 768*576 on the DC10 - LP + VStart += vcrop1; + VEnd -= vcrop2; + reg = ((VStart & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VStart) + | ((VEnd & ZR36057_VFEVCR_Vmask) << ZR36057_VFEVCR_VEnd); + if (zr->card.vfe_pol.vsync_pol) + reg |= ZR36057_VFEVCR_VSPol; + btwrite(reg, ZR36057_VFEVCR); + + /* scaler and pixel format */ + reg = 0; + reg |= (HorDcm << ZR36057_VFESPFR_HorDcm); + reg |= (VerDcm << ZR36057_VFESPFR_VerDcm); + reg |= (DispMode << ZR36057_VFESPFR_DispMode); + if (format->palette != VIDEO_PALETTE_YUV422) + reg |= 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 */ + if (zr->norm != VIDEO_MODE_NTSC) + reg |= ZR36057_VFESPFR_ExtFl; // NEEDED!!!!!!! Wolfgang + reg |= ZR36057_VFESPFR_TopField; + switch (format->palette) { + + 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: + dprintk(1, + KERN_INFO "%s: set_vfe() - unknown color_fmt=%x\n", + ZR_DEVNAME(zr), format->palette); + 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 (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_Triton; + else + reg |= ZR36057_VDCR_Triton; + btwrite(reg, ZR36057_VDCR); + + /* (Ronald) don't write this if overlay_mask = NULL */ + if (zr->overlay_mask) { + /* Write overlay clipping mask data, but don't enable overlay clipping */ + /* RJ: since this makes only sense on the screen, we use + * zr->overlay_settings.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->overlay_settings.width + + 31) / 32; + if (DispMode == 0) + reg += mask_line_size; + reg <<= ZR36057_OCR_MaskStride; + btwrite(reg, ZR36057_OCR); + } + + zr36057_adjust_vfe(zr, zr->codec_mode); +} + +/* + * Switch overlay on or off + */ + +void +zr36057_overlay (struct zoran *zr, + int on) +{ + u32 reg; + + if (on) { + /* do the necessary settings ... */ + btand(~ZR36057_VDCR_VidEn, ZR36057_VDCR); /* switch it off first */ + + zr36057_set_vfe(zr, + zr->overlay_settings.width, + zr->overlay_settings.height, + zr->overlay_settings.format); + + /* 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->overlay_settings.x * + ((zr->overlay_settings.format->depth + 7) / 8) + + zr->overlay_settings.y * + zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDTR); + if (reg & 3) + dprintk(1, + KERN_ERR + "%s: zr36057_overlay() - video_address not aligned\n", + ZR_DEVNAME(zr)); + if (zr->overlay_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->buffer.bytesperline; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + reg = zr->buffer.bytesperline - + zr->overlay_settings.width * + ((zr->overlay_settings.format->depth + 7) / 8); + if (zr->overlay_settings.height > BUZ_MAX_HEIGHT / 2) + reg += zr->buffer.bytesperline; + if (reg & 3) + dprintk(1, + KERN_ERR + "%s: zr36057_overlay() - video_stride not aligned\n", + ZR_DEVNAME(zr)); + reg = (reg << ZR36057_VSSFGR_DispStride); + reg |= ZR36057_VSSFGR_VidOvf; /* clear overflow status */ + btwrite(reg, ZR36057_VSSFGR); + + /* Set overlay clipping */ + if (zr->overlay_settings.clipcount > 0) + 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. + */ + +void +write_overlay_mask (struct file *file, + struct video_clip *vp, + int count) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + 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(fh->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 > fh->overlay_settings.width) { + width = fh->overlay_settings.width - x; + } + if (y + height > fh->overlay_settings.height) { + height = fh->overlay_settings.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 = fh->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 */ + +void +zr36057_set_memgrab (struct zoran *zr, + int mode) +{ + if (mode) { + if (btread(ZR36057_VSSFGR) & + (ZR36057_VSSFGR_SnapShot | ZR36057_VSSFGR_FrameGrab)) + dprintk(1, + KERN_WARNING + "%s: zr36057_set_memgrab(1) with SnapShot or FrameGrab on!?\n", + ZR_DEVNAME(zr)); + + /* switch on VSync interrupts */ + btwrite(IRQ_MASK, ZR36057_ISR); // Clear Interrupts + btor(zr->card.vsync_int, ZR36057_ICR); // SW + + /* enable SnapShot */ + btor(ZR36057_VSSFGR_SnapShot, ZR36057_VSSFGR); + + /* Set zr36057 video front end and enable video */ + zr36057_set_vfe(zr, zr->v4l_settings.width, + zr->v4l_settings.height, + zr->v4l_settings.format); + + zr->v4l_memgrab_active = 1; + } else { + zr->v4l_memgrab_active = 0; + + /* switch off VSync interrupts */ + btand(~zr->card.vsync_int, ZR36057_ICR); // SW + + /* 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); + } + } +} + +int +wait_grab_pending (struct zoran *zr) +{ + unsigned long flags; + + /* wait until all pending grabs are finished */ + + if (!zr->v4l_memgrab_active) + return 0; + + wait_event_interruptible(zr->v4l_capq, + (zr->v4l_pend_tail == zr->v4l_pend_head)); + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&zr->spinlock, flags); + zr36057_set_memgrab(zr, 0); + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +/***************************************************************************** + * * + * Set up the Buz-specific MJPEG part * + * * + *****************************************************************************/ + +static inline void +set_frame (struct zoran *zr, + int val) +{ + GPIO(zr, zr->card.gpio[GPIO_JPEG_FRAME], val); +} + +static void +set_videobus_dir (struct zoran *zr, + int val) +{ + switch (zr->card.type) { + case LML33: + case LML33R10: + if (lml33dpath == 0) + GPIO(zr, 5, val); + else + GPIO(zr, 5, 1); + break; + default: + GPIO(zr, zr->card.gpio[GPIO_VID_DIR], + zr->card.gpio_pol[GPIO_VID_DIR] ? !val : val); + break; + } +} + +static void +init_jpeg_queue (struct zoran *zr) +{ + int i; + + /* 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; + zr->JPEG_error = 0; + zr->num_errors = 0; + zr->jpg_err_seq = 0; + zr->jpg_err_shift = 0; + zr->jpg_queued_num = 0; + for (i = 0; i < zr->jpg_buffers.num_buffers; i++) { + zr->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + } + for (i = 0; i < BUZ_NUM_STAT_COM; i++) { + zr->stat_com[i] = cpu_to_le32(1); /* mark as unavailable to zr36057 */ + } +} + +static void +zr36057_set_jpg (struct zoran *zr, + enum zoran_codec_mode mode) +{ + struct tvnorm *tvn; + u32 reg; + + tvn = zr->timing; + + /* assert P_Reset, disable code transfer, deassert Active */ + btwrite(0, ZR36057_JPC); + + /* 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->jpg_settings.VFIFO_FB) reg |= ZR36057_JMC_VFIFO_FB; // No, it doesn't. SM + 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->jpg_settings.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->jpg_settings.img_y + tvn->VStart) << ZR36057_FVAP_NAY) | + (zr->jpg_settings.img_height << ZR36057_FVAP_PAY); + btwrite(reg, ZR36057_FVAP); + + /* horizontal */ + if (zr->card.vfe_pol.hsync_pol) + btor(ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR); + else + btand(~ZR36057_VFEHCR_HSPol, ZR36057_VFEHCR); + reg = ((tvn->HSyncStart) << ZR36057_HSP_HsyncStart) | + (tvn->Wt << ZR36057_HSP_LineTot); + btwrite(reg, ZR36057_HSP); + reg = ((zr->jpg_settings.img_x + + tvn->HStart + 4) << ZR36057_FHAP_NAX) | + (zr->jpg_settings.img_width << ZR36057_FHAP_PAX); + btwrite(reg, ZR36057_FHAP); + + /* field process parameters */ + if (zr->jpg_settings.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 */ + reg = virt_to_bus(zr->stat_com); + btwrite(reg, ZR36057_JCBA); + + /* FIFO threshold (FIFO is 160. double words) */ + /* NOTE: decimal values here */ + switch (mode) { + + case BUZ_MODE_STILL_COMPRESS: + case BUZ_MODE_MOTION_COMPRESS: + if (zr->card.type != BUZ) + reg = 140; + else + reg = 60; + break; + + case BUZ_MODE_STILL_DECOMPRESS: + case BUZ_MODE_MOTION_DECOMPRESS: + reg = 20; + break; + + default: + reg = 80; + break; + + } + btwrite(reg, ZR36057_JCFT); + zr36057_adjust_vfe(zr, mode); + +} + +void +print_interrupts (struct zoran *zr) +{ + int res, noerr = 0; + + printk(KERN_INFO "%s: interrupts received:", ZR_DEVNAME(zr)); + if ((res = zr->field_counter) < -1 || res > 1) { + printk(" FD:%d", res); + } + if ((res = zr->intr_counter_GIRQ1) != 0) { + printk(" GIRQ1:%d", res); + noerr++; + } + if ((res = zr->intr_counter_GIRQ0) != 0) { + printk(" GIRQ0:%d", res); + noerr++; + } + if ((res = zr->intr_counter_CodRepIRQ) != 0) { + printk(" CodRepIRQ:%d", res); + noerr++; + } + if ((res = zr->intr_counter_JPEGRepIRQ) != 0) { + printk(" JPEGRepIRQ:%d", res); + noerr++; + } + if (zr->JPEG_max_missed) { + printk(" JPEG delays: max=%d min=%d", zr->JPEG_max_missed, + zr->JPEG_min_missed); + } + if (zr->END_event_missed) { + printk(" ENDs missed: %d", zr->END_event_missed); + } + //if (zr->jpg_queued_num) { + printk(" queue_state=%ld/%ld/%ld/%ld", zr->jpg_que_tail, + zr->jpg_dma_tail, zr->jpg_dma_head, zr->jpg_que_head); + //} + if (!noerr) { + printk(": no interrupts detected."); + } + printk("\n"); +} + +void +clear_interrupt_counters (struct zoran *zr) +{ + zr->intr_counter_GIRQ1 = 0; + zr->intr_counter_GIRQ0 = 0; + zr->intr_counter_CodRepIRQ = 0; + zr->intr_counter_JPEGRepIRQ = 0; + zr->field_counter = 0; + zr->IRQ1_in = 0; + zr->IRQ1_out = 0; + zr->JPEG_in = 0; + zr->JPEG_out = 0; + zr->JPEG_0 = 0; + zr->JPEG_1 = 0; + zr->END_event_missed = 0; + zr->JPEG_missed = 0; + zr->JPEG_max_missed = 0; + zr->JPEG_min_missed = 0x7fffffff; +} + +static u32 +count_reset_interrupt (struct zoran *zr) +{ + u32 isr; + + if ((isr = btread(ZR36057_ISR) & 0x78000000)) { + if (isr & ZR36057_ISR_GIRQ1) { + btwrite(ZR36057_ISR_GIRQ1, ZR36057_ISR); + zr->intr_counter_GIRQ1++; + } + if (isr & ZR36057_ISR_GIRQ0) { + btwrite(ZR36057_ISR_GIRQ0, ZR36057_ISR); + zr->intr_counter_GIRQ0++; + } + if (isr & ZR36057_ISR_CodRepIRQ) { + btwrite(ZR36057_ISR_CodRepIRQ, ZR36057_ISR); + zr->intr_counter_CodRepIRQ++; + } + if (isr & ZR36057_ISR_JPEGRepIRQ) { + btwrite(ZR36057_ISR_JPEGRepIRQ, ZR36057_ISR); + zr->intr_counter_JPEGRepIRQ++; + } + } + return isr; +} + +/* hack */ +extern void zr36016_write (struct videocodec *codec, + u16 reg, + u32 val); + +void +jpeg_start (struct zoran *zr) +{ + int reg; + + zr->frame_num = 0; + + /* deassert P_reset, disable code transfer, deassert Active */ + btwrite(ZR36057_JPC_P_Reset, ZR36057_JPC); + /* stop flushing the internal code buffer */ + btand(~ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + /* enable code transfer */ + btor(ZR36057_JPC_CodTrnsEn, ZR36057_JPC); + + /* clear IRQs */ + btwrite(IRQ_MASK, ZR36057_ISR); + /* enable the JPEG IRQs */ + btwrite(zr->card.jpeg_int | + ZR36057_ICR_JPEGRepIRQ | + ZR36057_ICR_IntPinEn, + ZR36057_ICR); + + set_frame(zr, 0); // \FRAME + + /* set the JPEG codec guest ID */ + reg = (zr->card.gpcs[1] << ZR36057_JCGI_JPEGuestID) | + (0 << ZR36057_JCGI_JPEGuestReg); + btwrite(reg, ZR36057_JCGI); + + if (zr->card.video_vfe == CODEC_TYPE_ZR36016 && + zr->card.video_codec == CODEC_TYPE_ZR36050) { + /* Enable processing on the ZR36016 */ + if (zr->vfe) + zr36016_write(zr->vfe, 0, 1); + + /* load the address of the GO register in the ZR36050 latch */ + post_office_write(zr, 0, 0, 0); + } + + /* assert Active */ + btor(ZR36057_JPC_Active, ZR36057_JPC); + + /* enable the Go generation */ + btor(ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(30); + + set_frame(zr, 1); // /FRAME + + dprintk(3, KERN_DEBUG "%s: jpeg_start\n", ZR_DEVNAME(zr)); +} + +void +zr36057_enable_jpg (struct zoran *zr, + enum zoran_codec_mode mode) +{ + static int zero = 0; + static int one = 1; + struct vfe_settings cap; + int field_size = + zr->jpg_buffers.buffer_size / zr->jpg_settings.field_per_buff; + + zr->codec_mode = mode; + + cap.x = zr->jpg_settings.img_x; + cap.y = zr->jpg_settings.img_y; + cap.width = zr->jpg_settings.img_width; + cap.height = zr->jpg_settings.img_height; + cap.decimation = + zr->jpg_settings.HorDcm | (zr->jpg_settings.VerDcm << 8); + cap.quality = zr->jpg_settings.jpg_comp.quality; + + switch (mode) { + + case BUZ_MODE_MOTION_COMPRESS: { + struct jpeg_app_marker app; + struct jpeg_com_marker com; + + /* In motion compress mode, the decoder output must be enabled, and + * the video bus direction set to input. + */ + set_videobus_dir(zr, 0); + decoder_command(zr, DECODER_ENABLE_OUTPUT, &one); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + + /* set JPEG app/com marker */ + app.appn = zr->jpg_settings.jpg_comp.APPn; + app.len = zr->jpg_settings.jpg_comp.APP_len; + memcpy(app.data, zr->jpg_settings.jpg_comp.APP_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_APP_DATA, + sizeof(struct jpeg_app_marker), &app); + + com.len = zr->jpg_settings.jpg_comp.COM_len; + memcpy(com.data, zr->jpg_settings.jpg_comp.COM_data, 60); + zr->codec->control(zr->codec, CODEC_S_JPEG_COM_DATA, + sizeof(struct jpeg_com_marker), &com); + + /* Setup the JPEG codec */ + zr->codec->control(zr->codec, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_COMPRESSION); + + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->control(zr->vfe, CODEC_S_JPEG_TDS_BYTE, + sizeof(int), &field_size); + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_COMPRESSION); + } + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + dprintk(2, KERN_INFO "%s: enable_jpg(MOTION_COMPRESS)\n", + ZR_DEVNAME(zr)); + break; + } + + case BUZ_MODE_MOTION_DECOMPRESS: + /* In motion decompression mode, the decoder output must be disabled, and + * the video bus direction set to output. + */ + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + set_videobus_dir(zr, 1); + encoder_command(zr, ENCODER_SET_INPUT, &one); + + /* Take the JPEG codec and the VFE out of sleep */ + jpeg_codec_sleep(zr, 0); + /* Setup the VFE */ + if (zr->vfe) { + zr->vfe->set_video(zr->vfe, zr->timing, &cap, + &zr->card.vfe_pol); + zr->vfe->set_mode(zr->vfe, CODEC_DO_EXPANSION); + } + /* Setup the JPEG codec */ + zr->codec->set_video(zr->codec, zr->timing, &cap, + &zr->card.vfe_pol); + zr->codec->set_mode(zr->codec, CODEC_DO_EXPANSION); + + init_jpeg_queue(zr); + zr36057_set_jpg(zr, mode); // \P_Reset, ... Video param, FIFO + + clear_interrupt_counters(zr); + dprintk(2, KERN_INFO "%s: enable_jpg(MOTION_DECOMPRESS)\n", + ZR_DEVNAME(zr)); + break; + + case BUZ_MODE_IDLE: + default: + /* shut down processing */ + btand(~(zr->card.jpeg_int | ZR36057_ICR_JPEGRepIRQ), + ZR36057_ICR); + btwrite(zr->card.jpeg_int | ZR36057_ICR_JPEGRepIRQ, + ZR36057_ISR); + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); // \Go_en + + msleep(50); + + set_videobus_dir(zr, 0); + set_frame(zr, 1); // /FRAME + btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); // /CFlush + btwrite(0, ZR36057_JPC); // \P_Reset,\CodTrnsEn,\Active + btand(~ZR36057_JMC_VFIFO_FB, ZR36057_JMC); + btand(~ZR36057_JMC_SyncMstr, ZR36057_JMC); + jpeg_codec_reset(zr); + jpeg_codec_sleep(zr, 1); + zr36057_adjust_vfe(zr, mode); + + decoder_command(zr, DECODER_ENABLE_OUTPUT, &one); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + dprintk(2, KERN_INFO "%s: enable_jpg(IDLE)\n", ZR_DEVNAME(zr)); + break; + + } +} + +/* when this is called the spinlock must be held */ +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->jpg_settings.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->jpg_settings.TmpDcm == 1) { + /* fill 1 stat_com entry */ + i = (zr->jpg_dma_head - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_com[i] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + } else { + /* fill 2 stat_com entries */ + i = ((zr->jpg_dma_head - + zr->jpg_err_shift) & 1) * 2; + if (!(zr->stat_com[i] & cpu_to_le32(1))) + break; + zr->stat_com[i] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + zr->stat_com[i + 1] = + cpu_to_le32(zr->jpg_buffers.buffer[frame].frag_tab_bus); + } + zr->jpg_buffers.buffer[frame].state = BUZ_STATE_DMA; + zr->jpg_dma_head++; + + } + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) + zr->jpg_queued_num++; +} + +/* 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; + struct zoran_jpg_buffer *buffer; + int frame; + + /* 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->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2 + 1; + + stat_com = le32_to_cpu(zr->stat_com[i]); + + if ((stat_com & 1) == 0) { + return; + } + frame = zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME]; + buffer = &zr->jpg_buffers.buffer[frame]; + do_gettimeofday(&buffer->bs.timestamp); + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + buffer->bs.length = (stat_com & 0x7fffff) >> 1; + + /* update sequence number with the help of the counter in stat_com */ + + seq = ((stat_com >> 24) + zr->jpg_err_seq) & 0xff; + dif = (seq - zr->jpg_seq_num) & 0xff; + zr->jpg_seq_num += dif; + } else { + buffer->bs.length = 0; + } + buffer->bs.seq = + zr->jpg_settings.TmpDcm == + 2 ? (zr->jpg_seq_num >> 1) : zr->jpg_seq_num; + buffer->state = BUZ_STATE_DONE; + + zr->jpg_dma_tail++; + } +} + +static void +error_handler (struct zoran *zr, + u32 astat, + u32 stat) +{ + /* This is JPEG error handling part */ + if ((zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) && + (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS)) { + //dprintk(1, KERN_ERR "%s: Internal error: error handling request in mode %d\n", ZR_DEVNAME(zr), zr->codec_mode); + return; + } + + if ((stat & 1) == 0 && + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS && + zr->jpg_dma_tail - zr->jpg_que_tail >= + zr->jpg_buffers.num_buffers) { + /* No free buffers... */ + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + zr->JPEG_missed = 0; + return; + } + + if (zr->JPEG_error != 1) { + /* + * First entry: error just happened during normal operation + * + * In BUZ_MODE_MOTION_COMPRESS: + * + * Possible glitch in TV signal. In this case we should + * stop the codec and wait for good quality signal before + * restarting it to avoid further problems + * + * In BUZ_MODE_MOTION_DECOMPRESS: + * + * Bad JPEG frame: we have to mark it as processed (codec crashed + * and was not able to do it itself), and to remove it from queue. + */ + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(1); + stat = stat | (post_office_read(zr, 7, 0) & 3) << 8; + btwrite(0, ZR36057_JPC); + btor(ZR36057_MCTCR_CFlush, ZR36057_MCTCR); + jpeg_codec_reset(zr); + jpeg_codec_sleep(zr, 1); + zr->JPEG_error = 1; + zr->num_errors++; + + /* Report error */ + if (*zr_debug > 1 && zr->num_errors <= 8) { + long frame; + frame = + zr->jpg_pend[zr->jpg_dma_tail & BUZ_MASK_FRAME]; + printk(KERN_ERR + "%s: JPEG error stat=0x%08x(0x%08x) queue_state=%ld/%ld/%ld/%ld seq=%ld frame=%ld. Codec stopped. ", + ZR_DEVNAME(zr), stat, zr->last_isr, + zr->jpg_que_tail, zr->jpg_dma_tail, + zr->jpg_dma_head, zr->jpg_que_head, + zr->jpg_seq_num, frame); + printk("stat_com frames:"); + { + int i, j; + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + for (i = 0; + i < zr->jpg_buffers.num_buffers; + i++) { + if (le32_to_cpu(zr->stat_com[j]) == + zr->jpg_buffers. + buffer[i]. + frag_tab_bus) { + printk("% d->%d", + j, i); + } + } + } + printk("\n"); + } + } + /* Find an entry in stat_com and rotate contents */ + { + int i; + + if (zr->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2; + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) { + /* Mimic zr36067 operation */ + zr->stat_com[i] |= cpu_to_le32(1); + if (zr->jpg_settings.TmpDcm != 1) + zr->stat_com[i + 1] |= cpu_to_le32(1); + /* Refill */ + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + /* Find an entry in stat_com again after refill */ + if (zr->jpg_settings.TmpDcm == 1) + i = (zr->jpg_dma_tail - + zr->jpg_err_shift) & + BUZ_MASK_STAT_COM; + else + i = ((zr->jpg_dma_tail - + zr->jpg_err_shift) & 1) * 2; + } + if (i) { + /* Rotate stat_comm entries to make current entry first */ + int j; + u32 bus_addr[BUZ_NUM_STAT_COM]; + + /* Here we are copying the stat_com array, which + * is already in little endian format, so + * no endian conversions here + */ + memcpy(bus_addr, zr->stat_com, + sizeof(bus_addr)); + for (j = 0; j < BUZ_NUM_STAT_COM; j++) { + zr->stat_com[j] = + bus_addr[(i + j) & + BUZ_MASK_STAT_COM]; + + } + zr->jpg_err_shift += i; + zr->jpg_err_shift &= BUZ_MASK_STAT_COM; + } + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) + zr->jpg_err_seq = zr->jpg_seq_num; /* + 1; */ + } + } + + /* Now the stat_comm buffer is ready for restart */ + do { + int status, mode; + + if (zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + decoder_command(zr, DECODER_GET_STATUS, &status); + mode = CODEC_DO_COMPRESSION; + } else { + status = 0; + mode = CODEC_DO_EXPANSION; + } + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + (status & DECODER_STATUS_GOOD)) { + /********** RESTART code *************/ + jpeg_codec_reset(zr); + zr->codec->set_mode(zr->codec, mode); + zr36057_set_jpg(zr, zr->codec_mode); + jpeg_start(zr); + + if (zr->num_errors <= 8) + dprintk(2, KERN_INFO "%s: Restart\n", + ZR_DEVNAME(zr)); + + zr->JPEG_missed = 0; + zr->JPEG_error = 2; + /********** End RESTART code ***********/ + } + } while (0); +} + +irqreturn_t +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; + + if (zr->testing) { + /* Testing interrupts */ + spin_lock_irqsave(&zr->spinlock, flags); + while ((stat = count_reset_interrupt(zr))) { + if (count++ > 100) { + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + dprintk(1, + KERN_ERR + "%s: IRQ lockup while testing, isr=0x%08x, cleared int mask\n", + ZR_DEVNAME(zr), stat); + wake_up_interruptible(&zr->test_q); + } + } + zr->last_isr = stat; + spin_unlock_irqrestore(&zr->spinlock, flags); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&zr->spinlock, flags); + while (1) { + /* get/clear interrupt status bits */ + stat = count_reset_interrupt(zr); + astat = stat & IRQ_MASK; + if (!astat) { + break; + } + dprintk(4, + KERN_DEBUG + "zoran_irq: astat: 0x%08x, mask: 0x%08x\n", + astat, btread(ZR36057_ICR)); + if (astat & zr->card.vsync_int) { // SW + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + /* count missed interrupts */ + zr->JPEG_missed++; + } + //post_office_read(zr,1,0); + /* 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) + dprintk(1, + KERN_WARNING + "%s: BuzIRQ with SnapShot off ???\n", + ZR_DEVNAME(zr)); + + 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_buffers.buffer[zr->v4l_grab_frame].state = BUZ_STATE_DONE; + zr->v4l_buffers.buffer[zr->v4l_grab_frame].bs.seq = zr->v4l_grab_seq; + do_gettimeofday(&zr->v4l_buffers.buffer[zr->v4l_grab_frame].bs.timestamp); + zr->v4l_grab_frame = NO_GRAB_ACTIVE; + 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_buffers.buffer[frame]. + fbuffer_bus; + btwrite(reg, ZR36057_VDTR); + if (zr->v4l_settings.height > + BUZ_MAX_HEIGHT / 2) + reg += + zr->v4l_settings. + bytesperline; + btwrite(reg, ZR36057_VDBR); + + /* video stride, status, and frame grab register */ + reg = 0; + if (zr->v4l_settings.height > + BUZ_MAX_HEIGHT / 2) + reg += + zr->v4l_settings. + bytesperline; + 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); + } + } + + /* even if we don't grab, we do want to increment + * the sequence counter to see lost frames */ + zr->v4l_grab_seq++; + } +#if (IRQ_MASK & ZR36057_ISR_CodRepIRQ) + if (astat & ZR36057_ISR_CodRepIRQ) { + zr->intr_counter_CodRepIRQ++; + IDEBUG(printk + (KERN_DEBUG "%s: ZR36057_ISR_CodRepIRQ\n", + ZR_DEVNAME(zr))); + btand(~ZR36057_ICR_CodRepIRQ, ZR36057_ICR); + } +#endif /* (IRQ_MASK & ZR36057_ISR_CodRepIRQ) */ + +#if (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) + if (astat & ZR36057_ISR_JPEGRepIRQ) { + + if (zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS || + zr->codec_mode == BUZ_MODE_MOTION_COMPRESS) { + if (*zr_debug > 1 && + (!zr->frame_num || zr->JPEG_error)) { + printk(KERN_INFO + "%s: first frame ready: state=0x%08x odd_even=%d field_per_buff=%d delay=%d\n", + ZR_DEVNAME(zr), stat, + zr->jpg_settings.odd_even, + zr->jpg_settings. + field_per_buff, + zr->JPEG_missed); + { + char sc[] = "0000"; + char sv[5]; + int i; + strcpy(sv, sc); + for (i = 0; i < 4; i++) { + if (le32_to_cpu(zr->stat_com[i]) & 1) + sv[i] = '1'; + } + sv[4] = 0; + printk(KERN_INFO + "%s: stat_com=%s queue_state=%ld/%ld/%ld/%ld\n", + ZR_DEVNAME(zr), sv, + zr->jpg_que_tail, + zr->jpg_dma_tail, + zr->jpg_dma_head, + zr->jpg_que_head); + } + } else { + if (zr->JPEG_missed > zr->JPEG_max_missed) // Get statistics + zr->JPEG_max_missed = + zr->JPEG_missed; + if (zr->JPEG_missed < + zr->JPEG_min_missed) + zr->JPEG_min_missed = + zr->JPEG_missed; + } + + if (*zr_debug > 2 && zr->frame_num < 6) { + int i; + printk("%s: seq=%ld stat_com:", + ZR_DEVNAME(zr), zr->jpg_seq_num); + for (i = 0; i < 4; i++) { + printk(" %08x", + le32_to_cpu(zr->stat_com[i])); + } + printk("\n"); + } + zr->frame_num++; + zr->JPEG_missed = 0; + zr->JPEG_error = 0; + zoran_reap_stat_com(zr); + zoran_feed_stat_com(zr); + wake_up_interruptible(&zr->jpg_capq); + } /*else { + dprintk(1, + KERN_ERR + "%s: JPEG interrupt while not in motion (de)compress mode!\n", + ZR_DEVNAME(zr)); + }*/ + } +#endif /* (IRQ_MASK & ZR36057_ISR_JPEGRepIRQ) */ + + /* DATERR, too many fields missed, error processing */ + if ((astat & zr->card.jpeg_int) || + zr->JPEG_missed > 25 || + zr->JPEG_error == 1 || + ((zr->codec_mode == BUZ_MODE_MOTION_DECOMPRESS) && + (zr->frame_num & (zr->JPEG_missed > + zr->jpg_settings.field_per_buff)))) { + error_handler(zr, astat, stat); + } + + count++; + if (count > 10) { + dprintk(2, KERN_WARNING "%s: irq loop %d\n", + ZR_DEVNAME(zr), count); + if (count > 20) { + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + dprintk(2, + KERN_ERR + "%s: IRQ lockup, cleared int mask\n", + ZR_DEVNAME(zr)); + break; + } + } + zr->last_isr = stat; + } + spin_unlock_irqrestore(&zr->spinlock, flags); + + return IRQ_HANDLED; +} + +void +zoran_set_pci_master (struct zoran *zr, + int set_master) +{ + if (set_master) { + pci_set_master(zr->pci_dev); + } else { + u16 command; + + pci_read_config_word(zr->pci_dev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_word(zr->pci_dev, PCI_COMMAND, command); + } +} + +void +zoran_init_hardware (struct zoran *zr) +{ + int j, zero = 0; + + /* Enable bus-mastering */ + zoran_set_pci_master(zr, 1); + + /* Initialize the board */ + if (zr->card.init) { + zr->card.init(zr); + } + + j = zr->card.input[zr->input].muxsel; + + decoder_command(zr, 0, NULL); + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + decoder_command(zr, DECODER_SET_INPUT, &j); + + encoder_command(zr, 0, NULL); + encoder_command(zr, ENCODER_SET_NORM, &zr->norm); + encoder_command(zr, ENCODER_SET_INPUT, &zero); + + /* toggle JPEG codec sleep to sync PLL */ + jpeg_codec_sleep(zr, 1); + jpeg_codec_sleep(zr, 0); + + /* set individual interrupt enables (without GIRQ1) + * but don't global enable until zoran_open() */ + + //btwrite(IRQ_MASK & ~ZR36057_ISR_GIRQ1, ZR36057_ICR); // SW + // It looks like using only JPEGRepIRQEn is not always reliable, + // may be when JPEG codec crashes it won't generate IRQ? So, + /*CP*/ // btwrite(IRQ_MASK, ZR36057_ICR); // Enable Vsync interrupts too. SM WHY ? LP + zr36057_init_vfe(zr); + + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + + btwrite(IRQ_MASK, ZR36057_ISR); // Clears interrupts +} + +void +zr36057_restart (struct zoran *zr) +{ + btwrite(0, ZR36057_SPGPPCR); + mdelay(1); + btor(ZR36057_SPGPPCR_SoftReset, ZR36057_SPGPPCR); + mdelay(1); + + /* assert P_Reset */ + btwrite(0, ZR36057_JPC); + /* set up GPIO direction - all output */ + btwrite(ZR36057_SPGPPCR_SoftReset | 0, ZR36057_SPGPPCR); + + /* set up GPIO pins and guest bus timing */ + btwrite((0x81 << 24) | 0x8888, ZR36057_GPPGCR1); +} + +/* + * initialize video front end + */ + +static void +zr36057_init_vfe (struct zoran *zr) +{ + u32 reg; + + reg = btread(ZR36057_VFESPFR); + reg |= ZR36057_VFESPFR_LittleEndian; + reg &= ~ZR36057_VFESPFR_VCLKPol; + reg |= ZR36057_VFESPFR_ExtFl; + reg |= ZR36057_VFESPFR_TopField; + btwrite(reg, ZR36057_VFESPFR); + reg = btread(ZR36057_VDCR); + if (pci_pci_problems & PCIPCI_TRITON) + // || zr->revision < 1) // Revision 1 has also Triton support + reg &= ~ZR36057_VDCR_Triton; + else + reg |= ZR36057_VDCR_Triton; + btwrite(reg, ZR36057_VDCR); +} + +/* + * Interface to decoder and encoder chips using i2c bus + */ + +int +decoder_command (struct zoran *zr, + int cmd, + void *data) +{ + if (zr->decoder == NULL) + return -EIO; + + if (zr->card.type == LML33 && + (cmd == DECODER_SET_NORM || DECODER_SET_INPUT)) { + int res; + + // Bt819 needs to reset its FIFO buffer using #FRST pin and + // LML33 card uses GPIO(7) for that. + GPIO(zr, 7, 0); + res = zr->decoder->driver->command(zr->decoder, cmd, data); + // Pull #FRST high. + GPIO(zr, 7, 1); + return res; + } else + return zr->decoder->driver->command(zr->decoder, cmd, + data); +} + +int +encoder_command (struct zoran *zr, + int cmd, + void *data) +{ + if (zr->encoder == NULL) + return -1; + + return zr->encoder->driver->command(zr->encoder, cmd, data); +} diff --git a/linux/drivers/media/video/zoran_device.h b/linux/drivers/media/video/zoran_device.h new file mode 100644 index 000000000..f19705cbd --- /dev/null +++ b/linux/drivers/media/video/zoran_device.h @@ -0,0 +1,91 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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 __ZORAN_DEVICE_H__ +#define __ZORAN_DEVICE_H__ + +/* general purpose I/O */ +extern void GPIO(struct zoran *zr, + int bit, + unsigned int value); + +/* codec (or actually: guest bus) access */ +extern int post_office_wait(struct zoran *zr); +extern int post_office_write(struct zoran *zr, + unsigned guest, + unsigned reg, + unsigned value); +extern int post_office_read(struct zoran *zr, + unsigned guest, + unsigned reg); + +extern void detect_guest_activity(struct zoran *zr); + +extern void jpeg_codec_sleep(struct zoran *zr, + int sleep); +extern int jpeg_codec_reset(struct zoran *zr); + +/* zr360x7 access to raw capture */ +extern void zr36057_overlay(struct zoran *zr, + int on); +extern void write_overlay_mask(struct file *file, + struct video_clip *vp, + int count); +extern void zr36057_set_memgrab(struct zoran *zr, + int mode); +extern int wait_grab_pending(struct zoran *zr); + +/* interrupts */ +extern void print_interrupts(struct zoran *zr); +extern void clear_interrupt_counters(struct zoran *zr); +extern irqreturn_t zoran_irq(int irq, + void *dev_id, + struct pt_regs *regs); + +/* JPEG codec access */ +extern void jpeg_start(struct zoran *zr); +extern void zr36057_enable_jpg(struct zoran *zr, + enum zoran_codec_mode mode); +extern void zoran_feed_stat_com(struct zoran *zr); + +/* general */ +extern void zoran_set_pci_master(struct zoran *zr, + int set_master); +extern void zoran_init_hardware(struct zoran *zr); +extern void zr36057_restart(struct zoran *zr); + +/* i2c */ +extern int decoder_command(struct zoran *zr, + int cmd, + void *data); +extern int encoder_command(struct zoran *zr, + int cmd, + void *data); + +#endif /* __ZORAN_DEVICE_H__ */ diff --git a/linux/drivers/media/video/zoran_driver.c b/linux/drivers/media/video/zoran_driver.c new file mode 100644 index 000000000..b5a576a37 --- /dev/null +++ b/linux/drivers/media/video/zoran_driver.c @@ -0,0 +1,4700 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Changes for BUZ by Wolfgang Scherr <scherr@net4you.net> + * + * Changes for DC10/DC30 by Laurent Pinchart <laurent.pinchart@skynet.be> + * + * Changes for LML33R10 by Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Changes for videodev2/v4l2 by Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * Based on + * + * Miro DC10 driver + * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net> + * + * Iomega Buz driver version 1.0 + * Copyright (C) 1999 Rainer Johanni <Rainer@Johanni.de> + * + * buz.0.0.3 + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * + * 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/config.h> +#include <linux/version.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/vmalloc.h> +#include <linux/wait.h> +#include <linux/byteorder/generic.h> + +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <linux/spinlock.h> +#define MAP_NR(x) virt_to_page(x) +#define ZORAN_HARDWARE VID_HARDWARE_ZR36067 +#define ZORAN_VID_TYPE ( \ + VID_TYPE_CAPTURE | \ + VID_TYPE_OVERLAY | \ + VID_TYPE_CLIPPING | \ + VID_TYPE_FRAMERAM | \ + VID_TYPE_SCALES | \ + VID_TYPE_MJPEG_DECODER | \ + VID_TYPE_MJPEG_ENCODER \ + ) + +#include <linux/videodev.h> +#include "videocodec.h" + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/proc_fs.h> + +#include <linux/video_decoder.h> +#include <linux/video_encoder.h> +#include <linux/mutex.h> +#include "zoran.h" +#include "zoran_device.h" +#include "zoran_card.h" + +#ifdef HAVE_V4L2 + /* we declare some card type definitions here, they mean + * the same as the v4l1 ZORAN_VID_TYPE above, except it's v4l2 */ +#define ZORAN_V4L2_VID_FLAGS ( \ + V4L2_CAP_STREAMING |\ + V4L2_CAP_VIDEO_CAPTURE |\ + V4L2_CAP_VIDEO_OUTPUT |\ + V4L2_CAP_VIDEO_OVERLAY \ + ) +#endif + +#include <asm/byteorder.h> + +const struct zoran_format zoran_formats[] = { + { + .name = "15-bit RGB", + .palette = VIDEO_PALETTE_RGB555, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_RGB555, +#else + .fourcc = V4L2_PIX_FMT_RGB555X, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 15, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "16-bit RGB", + .palette = VIDEO_PALETTE_RGB565, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_RGB565, +#else + .fourcc = V4L2_PIX_FMT_RGB565X, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "24-bit RGB", + .palette = VIDEO_PALETTE_RGB24, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_BGR24, +#else + .fourcc = V4L2_PIX_FMT_RGB24, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 24, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "32-bit RGB", + .palette = VIDEO_PALETTE_RGB32, +#ifdef HAVE_V4L2 +#ifdef __LITTLE_ENDIAN + .fourcc = V4L2_PIX_FMT_BGR32, +#else + .fourcc = V4L2_PIX_FMT_RGB32, +#endif + .colorspace = V4L2_COLORSPACE_SRGB, +#endif + .depth = 32, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "4:2:2, packed, YUYV", + .palette = VIDEO_PALETTE_YUV422, +#ifdef HAVE_V4L2 + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_SMPTE170M, +#endif + .depth = 16, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_OVERLAY, + }, { + .name = "Hardware-encoded Motion-JPEG", + .palette = -1, +#ifdef HAVE_V4L2 + .fourcc = V4L2_PIX_FMT_MJPEG, + .colorspace = V4L2_COLORSPACE_SMPTE170M, +#endif + .depth = 0, + .flags = ZORAN_FORMAT_CAPTURE | + ZORAN_FORMAT_PLAYBACK | + ZORAN_FORMAT_COMPRESSED, + } +}; +static const int zoran_num_formats = + (sizeof(zoran_formats) / sizeof(struct zoran_format)); + +// RJ: Test only - want to test BUZ_USE_HIMEM even when CONFIG_BIGPHYS_AREA is defined +#if !defined(CONFIG_BIGPHYS_AREA) +//#undef CONFIG_BIGPHYS_AREA +#define BUZ_USE_HIMEM +#endif + +#if defined(CONFIG_BIGPHYS_AREA) +# include <linux/bigphysarea.h> +#endif + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +extern int v4l_nbufs; +extern int v4l_bufsize; +extern int jpg_nbufs; +extern int jpg_bufsize; +extern int pass_through; + +static int lock_norm = 0; /* 1=Don't change TV standard (norm) */ +module_param(lock_norm, int, 0); +MODULE_PARM_DESC(lock_norm, "Users can't change norm"); + +#ifdef HAVE_V4L2 + /* small helper function for calculating buffersizes for v4l2 + * we calculate the nearest higher power-of-two, which + * will be the recommended buffersize */ +static __u32 +zoran_v4l2_calc_bufsize (struct zoran_jpg_settings *settings) +{ + __u8 div = settings->VerDcm * settings->HorDcm * settings->TmpDcm; + __u32 num = (1024 * 512) / (div); + __u32 result = 2; + + num--; + while (num) { + num >>= 1; + result <<= 1; + } + + if (result > jpg_bufsize) + return jpg_bufsize; + if (result < 8192) + return 8192; + return result; +} +#endif + +/* forward references */ +static void v4l_fbuffer_free(struct file *file); +static void jpg_fbuffer_free(struct file *file); + +/* + * Allocate the V4L grab buffers + * + * These have to be pysically contiguous. + * If v4l_bufsize <= MAX_KMALLOC_MEM we use kmalloc + * else we try to allocate them with bigphysarea_alloc_pages + * if the bigphysarea patch is present in the kernel, + * else we try to use high memory (if the user has bootet + * Linux with the necessary memory left over). + */ + +#if defined(BUZ_USE_HIMEM) && !defined(CONFIG_BIGPHYS_AREA) +static unsigned long +get_high_mem (unsigned long size) +{ +/* + * Check if there is usable memory at the end of Linux memory + * of at least size. Return the physical address of this memory, + * return 0 on failure. + * + * The idea is from Alexandro Rubini's book "Linux device drivers". + * The driver from him which is downloadable from O'Reilly's + * web site misses the "virt_to_phys(high_memory)" part + * (and therefore doesn't work at all - at least with 2.2.x kernels). + * + * It should be unnecessary to mention that THIS IS DANGEROUS, + * if more than one driver at a time has the idea to use this memory!!!! + */ + + volatile unsigned char __iomem *mem; + unsigned char c; + unsigned long hi_mem_ph; + unsigned long i; + + /* Map the high memory to user space */ + + hi_mem_ph = virt_to_phys(high_memory); + + mem = ioremap(hi_mem_ph, size); + if (!mem) { + dprintk(1, + KERN_ERR "%s: get_high_mem() - ioremap failed\n", + ZORAN_NAME); + return 0; + } + + for (i = 0; i < size; i++) { + /* Check if it is memory */ + c = i & 0xff; + writeb(c, mem + i); + if (readb(mem + i) != c) + break; + c = 255 - c; + writeb(c, mem + i); + if (readb(mem + i) != c) + break; + writeb(0, mem + i); /* zero out memory */ + + /* give the kernel air to breath */ + if ((i & 0x3ffff) == 0x3ffff) + schedule(); + } + + iounmap(mem); + + if (i != size) { + dprintk(1, + KERN_ERR + "%s: get_high_mem() - requested %lu, avail %lu\n", + ZORAN_NAME, size, i); + return 0; + } + + return hi_mem_ph; +} +#endif + +static int +v4l_fbuffer_alloc (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; +#if defined(BUZ_USE_HIMEM) && !defined(CONFIG_BIGPHYS_AREA) + unsigned long pmem = 0; +#endif + + /* we might have old buffers lying around... */ + if (fh->v4l_buffers.ready_to_be_freed) { + v4l_fbuffer_free(file); + } + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (fh->v4l_buffers.buffer[i].fbuffer) + dprintk(2, + KERN_WARNING + "%s: v4l_fbuffer_alloc() - buffer %d allready allocated!?\n", + ZR_DEVNAME(zr), i); + + //udelay(20); + if (fh->v4l_buffers.buffer_size <= MAX_KMALLOC_MEM) { + /* Use kmalloc */ + + mem = + (unsigned char *) kmalloc(fh->v4l_buffers. + buffer_size, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - kmalloc for V4L buf %d failed\n", + ZR_DEVNAME(zr), i); + v4l_fbuffer_free(file); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[i].fbuffer = mem; + fh->v4l_buffers.buffer[i].fbuffer_phys = + virt_to_phys(mem); + fh->v4l_buffers.buffer[i].fbuffer_bus = + virt_to_bus(mem); + for (off = 0; off < fh->v4l_buffers.buffer_size; + off += PAGE_SIZE) + SetPageReserved(MAP_NR(mem + off)); + dprintk(4, + KERN_INFO + "%s: v4l_fbuffer_alloc() - V4L frame %d mem 0x%lx (bus: 0x%lx)\n", + ZR_DEVNAME(zr), i, (unsigned long) mem, + virt_to_bus(mem)); + } else { +#if defined(CONFIG_BIGPHYS_AREA) + /* Use bigphysarea_alloc_pages */ + + int n = + (fh->v4l_buffers.buffer_size + PAGE_SIZE - + 1) / PAGE_SIZE; + + mem = + (unsigned char *) bigphysarea_alloc_pages(n, 0, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - bigphysarea_alloc_pages for V4L buf %d failed\n", + ZR_DEVNAME(zr), i); + v4l_fbuffer_free(file); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[i].fbuffer = mem; + fh->v4l_buffers.buffer[i].fbuffer_phys = + virt_to_phys(mem); + fh->v4l_buffers.buffer[i].fbuffer_bus = + virt_to_bus(mem); + dprintk(4, + KERN_INFO + "%s: Bigphysarea frame %d mem 0x%x (bus: 0x%x)\n", + ZR_DEVNAME(zr), i, (unsigned) mem, + (unsigned) virt_to_bus(mem)); + + /* Zero out the allocated memory */ + memset(fh->v4l_buffers.buffer[i].fbuffer, 0, + fh->v4l_buffers.buffer_size); +#elif defined(BUZ_USE_HIMEM) + + /* Use high memory which has been left at boot time */ + + /* Ok., Ok. this is an evil hack - we make + * the assumption that physical addresses are + * the same as bus addresses (true at least + * for Intel processors). The whole method of + * obtaining and using this memory is not very + * nice - but I hope it saves some poor users + * from kernel hacking, which might have even + * more evil results */ + + if (i == 0) { + int size = + fh->v4l_buffers.num_buffers * + fh->v4l_buffers.buffer_size; + + pmem = get_high_mem(size); + if (pmem == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - get_high_mem (size = %d KB) for V4L bufs failed\n", + ZR_DEVNAME(zr), size >> 10); + return -ENOBUFS; + } + fh->v4l_buffers.buffer[0].fbuffer = NULL; + fh->v4l_buffers.buffer[0].fbuffer_phys = pmem; + fh->v4l_buffers.buffer[0].fbuffer_bus = pmem; + dprintk(4, + KERN_INFO + "%s: v4l_fbuffer_alloc() - using %d KB high memory\n", + ZR_DEVNAME(zr), size >> 10); + } else { + fh->v4l_buffers.buffer[i].fbuffer = NULL; + fh->v4l_buffers.buffer[i].fbuffer_phys = + pmem + i * fh->v4l_buffers.buffer_size; + fh->v4l_buffers.buffer[i].fbuffer_bus = + pmem + i * fh->v4l_buffers.buffer_size; + } +#else + /* No bigphysarea present, usage of high memory disabled, + * but user wants buffers of more than MAX_KMALLOC_MEM */ + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - no bigphysarea_patch present, usage of high memory disabled,\n", + ZR_DEVNAME(zr)); + dprintk(1, + KERN_ERR + "%s: v4l_fbuffer_alloc() - sorry, could not allocate %d V4L buffers of size %d KB.\n", + ZR_DEVNAME(zr), fh->v4l_buffers.num_buffers, + fh->v4l_buffers.buffer_size >> 10); + return -ENOBUFS; +#endif + } + } + + fh->v4l_buffers.allocated = 1; + + return 0; +} + +/* free the V4L grab buffers */ +static void +v4l_fbuffer_free (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, off; + unsigned char *mem; + + dprintk(4, KERN_INFO "%s: v4l_fbuffer_free()\n", ZR_DEVNAME(zr)); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (!fh->v4l_buffers.buffer[i].fbuffer) + continue; + + if (fh->v4l_buffers.buffer_size <= MAX_KMALLOC_MEM) { + mem = fh->v4l_buffers.buffer[i].fbuffer; + for (off = 0; off < fh->v4l_buffers.buffer_size; + off += PAGE_SIZE) + ClearPageReserved(MAP_NR(mem + off)); + kfree((void *) fh->v4l_buffers.buffer[i].fbuffer); + } +#if defined(CONFIG_BIGPHYS_AREA) + else + bigphysarea_free_pages((void *) fh->v4l_buffers. + buffer[i].fbuffer); +#endif + fh->v4l_buffers.buffer[i].fbuffer = NULL; + } + + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 0; +} + +/* + * 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_zeroed_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_zeroed_page, but delivers you + * virtual addresses) and then again have to make a lot of efforts + * to get the physical address. + * + * Ben Capper: + * On big-endian architectures (such as ppc) some extra steps + * are needed. When reading and writing to the stat_com array + * and fragment buffers, the device expects to see little- + * endian values. The use of cpu_to_le32() and le32_to_cpu() + * in this function (and one or two others in zoran_device.c) + * ensure that these values are always stored in little-endian + * form, regardless of architecture. The zr36057 does Very Bad + * Things on big endian architectures if the stat_com array + * and fragment buffers are not little-endian. + */ + +static int +jpg_fbuffer_alloc (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, j, off; + unsigned long mem; + + /* we might have old buffers lying around */ + if (fh->jpg_buffers.ready_to_be_freed) { + jpg_fbuffer_free(file); + } + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (fh->jpg_buffers.buffer[i].frag_tab) + dprintk(2, + KERN_WARNING + "%s: jpg_fbuffer_alloc() - buffer %d allready allocated!?\n", + ZR_DEVNAME(zr), i); + + /* Allocate fragment table for this buffer */ + + mem = get_zeroed_page(GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - get_zeroed_page (frag_tab) failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + memset((void *) mem, 0, PAGE_SIZE); + fh->jpg_buffers.buffer[i].frag_tab = (u32 *) mem; + fh->jpg_buffers.buffer[i].frag_tab_bus = + virt_to_bus((void *) mem); + + //if (alloc_contig) { + if (fh->jpg_buffers.need_contiguous) { + mem = + (unsigned long) kmalloc(fh->jpg_buffers. + buffer_size, + GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - kmalloc failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + fh->jpg_buffers.buffer[i].frag_tab[0] = + cpu_to_le32(virt_to_bus((void *) mem)); + fh->jpg_buffers.buffer[i].frag_tab[1] = + cpu_to_le32(((fh->jpg_buffers.buffer_size / 4) << 1) | 1); + for (off = 0; off < fh->jpg_buffers.buffer_size; + off += PAGE_SIZE) + SetPageReserved(MAP_NR(mem + off)); + } else { + /* jpg_bufsize is allreay page aligned */ + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + mem = get_zeroed_page(GFP_KERNEL); + if (mem == 0) { + dprintk(1, + KERN_ERR + "%s: jpg_fbuffer_alloc() - get_zeroed_page failed for buffer %d\n", + ZR_DEVNAME(zr), i); + jpg_fbuffer_free(file); + return -ENOBUFS; + } + + fh->jpg_buffers.buffer[i].frag_tab[2 * j] = + cpu_to_le32(virt_to_bus((void *) mem)); + fh->jpg_buffers.buffer[i].frag_tab[2 * j + + 1] = + cpu_to_le32((PAGE_SIZE / 4) << 1); + SetPageReserved(MAP_NR(mem)); + } + + fh->jpg_buffers.buffer[i].frag_tab[2 * j - 1] |= cpu_to_le32(1); + } + } + + dprintk(4, + KERN_DEBUG "%s: jpg_fbuffer_alloc() - %d KB allocated\n", + ZR_DEVNAME(zr), + (fh->jpg_buffers.num_buffers * + fh->jpg_buffers.buffer_size) >> 10); + + fh->jpg_buffers.allocated = 1; + + return 0; +} + +/* free the MJPEG grab buffers */ +static void +jpg_fbuffer_free (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i, j, off; + unsigned char *mem; + + dprintk(4, KERN_DEBUG "%s: jpg_fbuffer_free()\n", ZR_DEVNAME(zr)); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (!fh->jpg_buffers.buffer[i].frag_tab) + continue; + + //if (alloc_contig) { + if (fh->jpg_buffers.need_contiguous) { + if (fh->jpg_buffers.buffer[i].frag_tab[0]) { + mem = (unsigned char *) bus_to_virt(le32_to_cpu( + fh->jpg_buffers.buffer[i].frag_tab[0])); + for (off = 0; + off < fh->jpg_buffers.buffer_size; + off += PAGE_SIZE) + ClearPageReserved(MAP_NR + (mem + off)); + kfree(mem); + fh->jpg_buffers.buffer[i].frag_tab[0] = 0; + fh->jpg_buffers.buffer[i].frag_tab[1] = 0; + } + } else { + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + if (!fh->jpg_buffers.buffer[i]. + frag_tab[2 * j]) + break; + ClearPageReserved(MAP_NR + (bus_to_virt + (le32_to_cpu + (fh->jpg_buffers. + buffer[i].frag_tab[2 * + j])))); + free_page((unsigned long) + bus_to_virt + (le32_to_cpu + (fh->jpg_buffers. + buffer[i]. + frag_tab[2 * j]))); + fh->jpg_buffers.buffer[i].frag_tab[2 * j] = + 0; + fh->jpg_buffers.buffer[i].frag_tab[2 * j + + 1] = 0; + } + } + + free_page((unsigned long) fh->jpg_buffers.buffer[i]. + frag_tab); + fh->jpg_buffers.buffer[i].frag_tab = NULL; + } + + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 0; +} + +/* + * V4L Buffer grabbing + */ + +static int +zoran_v4l_set_format (struct file *file, + int width, + int height, + const struct zoran_format *format) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int bpp; + + /* Check size and format of the grab wanted */ + + if (height < BUZ_MIN_HEIGHT || width < BUZ_MIN_WIDTH || + height > BUZ_MAX_HEIGHT || width > BUZ_MAX_WIDTH) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - wrong frame size (%dx%d)\n", + ZR_DEVNAME(zr), width, height); + return -EINVAL; + } + + bpp = (format->depth + 7) / 8; + + /* Check against available buffer size */ + if (height * width * bpp > fh->v4l_buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - video buffer size (%d kB) is too small\n", + ZR_DEVNAME(zr), fh->v4l_buffers.buffer_size >> 10); + return -EINVAL; + } + + /* The video front end needs 4-byte alinged line sizes */ + + if ((bpp == 2 && (width & 1)) || (bpp == 3 && (width & 3))) { + dprintk(1, + KERN_ERR + "%s: v4l_set_format() - wrong frame alingment\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + fh->v4l_settings.width = width; + fh->v4l_settings.height = height; + fh->v4l_settings.format = format; + fh->v4l_settings.bytesperline = bpp * fh->v4l_settings.width; + + return 0; +} + +static int +zoran_v4l_queue_frame (struct file *file, + int num) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + if (!fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->v4l_buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - buffer %d is out of range\n", + ZR_DEVNAME(zr), num); + res = -EINVAL; + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (fh->v4l_buffers.active == ZORAN_FREE) { + if (zr->v4l_buffers.active == ZORAN_FREE) { + zr->v4l_buffers = fh->v4l_buffers; + fh->v4l_buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: v4l_queue_frame() - another session is already capturing\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + } + } + + /* make sure a grab isn't going on currently with this buffer */ + if (!res) { + switch (zr->v4l_buffers.buffer[num].state) { + default: + case BUZ_STATE_PEND: + if (zr->v4l_buffers.active == ZORAN_FREE) { + fh->v4l_buffers.active = ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: v4l_queue_frame() - queueing buffer %d in state DONE!?\n", + ZR_DEVNAME(zr), num); + case BUZ_STATE_USER: + /* 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] = num; + zr->v4l_buffers.buffer[num].state = BUZ_STATE_PEND; + zr->v4l_buffers.buffer[num].bs.length = + fh->v4l_settings.bytesperline * + zr->v4l_settings.height; + fh->v4l_buffers.buffer[num] = + zr->v4l_buffers.buffer[num]; + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->v4l_buffers.active == ZORAN_FREE) + zr->v4l_buffers.active = fh->v4l_buffers.active; + + return res; +} + +static int +v4l_grab (struct file *file, + struct video_mmap *mp) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int res = 0, i; + + for (i = 0; i < zoran_num_formats; i++) { + if (zoran_formats[i].palette == mp->format && + zoran_formats[i].flags & ZORAN_FORMAT_CAPTURE && + !(zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED)) + break; + } + if (i == zoran_num_formats || zoran_formats[i].depth == 0) { + dprintk(1, + KERN_ERR + "%s: v4l_grab() - wrong bytes-per-pixel format\n", + ZR_DEVNAME(zr)); + 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->v4l_settings.width != mp->width || + zr->v4l_settings.height != mp->height || + zr->v4l_settings.format->palette != mp->format)) { + res = wait_grab_pending(zr); + if (res) + return res; + } + if ((res = zoran_v4l_set_format(file, + mp->width, + mp->height, + &zoran_formats[i]))) + return res; + zr->v4l_settings = fh->v4l_settings; + + /* queue the frame in the pending queue */ + if ((res = zoran_v4l_queue_frame(file, mp->frame))) { + fh->v4l_buffers.active = ZORAN_FREE; + return res; + } + + /* put the 36057 into frame grabbing mode */ + if (!res && !zr->v4l_memgrab_active) + zr36057_set_memgrab(zr, 1); + + //dprintk(4, KERN_INFO "%s: Frame grab 3...\n", ZR_DEVNAME(zr)); + + return res; +} + +/* + * Sync on a V4L buffer + */ + +static int +v4l_sync (struct file *file, + int frame) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + + if (fh->v4l_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: v4l_sync() - no grab active for this session\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* check passed-in frame number */ + if (frame >= fh->v4l_buffers.num_buffers || frame < 0) { + dprintk(1, + KERN_ERR "%s: v4l_sync() - frame %d is invalid\n", + ZR_DEVNAME(zr), frame); + return -EINVAL; + } + + /* Check if is buffer was queued at all */ + if (zr->v4l_buffers.buffer[frame].state == BUZ_STATE_USER) { + dprintk(1, + KERN_ERR + "%s: v4l_sync() - attempt to sync on a buffer which was not queued?\n", + ZR_DEVNAME(zr)); + return -EPROTO; + } + + /* wait on this buffer to get ready */ + if (!wait_event_interruptible_timeout(zr->v4l_capq, + (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_PEND), + 10*HZ)) + return -ETIME; + if (signal_pending(current)) + return -ERESTARTSYS; + + /* buffer should now be in BUZ_STATE_DONE */ + if (zr->v4l_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR "%s: v4l_sync() - internal state error\n", + ZR_DEVNAME(zr)); + + zr->v4l_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->v4l_buffers.buffer[frame] = zr->v4l_buffers.buffer[frame]; + + spin_lock_irqsave(&zr->spinlock, flags); + + /* Check if streaming capture has finished */ + if (zr->v4l_pend_tail == zr->v4l_pend_head) { + zr36057_set_memgrab(zr, 0); + if (zr->v4l_buffers.active == ZORAN_ACTIVE) { + fh->v4l_buffers.active = zr->v4l_buffers.active = + ZORAN_FREE; + zr->v4l_buffers.allocated = 0; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +/* + * Queue a MJPEG buffer for capture/playback + */ + +static int +zoran_jpg_queue_frame (struct file *file, + int num, + enum zoran_codec_mode mode) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int res = 0; + + /* Check if buffers are allocated */ + if (!fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + + /* No grabbing outside the buffer range! */ + if (num >= fh->jpg_buffers.num_buffers || num < 0) { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - buffer %d out of range\n", + ZR_DEVNAME(zr), num); + return -EINVAL; + } + + /* what is the codec mode right now? */ + if (zr->codec_mode == BUZ_MODE_IDLE) { + zr->jpg_settings = fh->jpg_settings; + } else if (zr->codec_mode != mode) { + /* wrong codec mode active - invalid */ + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - codec in wrong mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + if (fh->jpg_buffers.active == ZORAN_FREE) { + if (zr->jpg_buffers.active == ZORAN_FREE) { + zr->jpg_buffers = fh->jpg_buffers; + fh->jpg_buffers.active = ZORAN_ACTIVE; + } else { + dprintk(1, + KERN_ERR + "%s: jpg_queue_frame() - another session is already capturing\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + } + } + + if (!res && zr->codec_mode == BUZ_MODE_IDLE) { + /* Ok load up the jpeg codec */ + zr36057_enable_jpg(zr, mode); + } + + spin_lock_irqsave(&zr->spinlock, flags); + + if (!res) { + switch (zr->jpg_buffers.buffer[num].state) { + case BUZ_STATE_DONE: + dprintk(2, + KERN_WARNING + "%s: jpg_queue_frame() - queing frame in BUZ_STATE_DONE state!?\n", + ZR_DEVNAME(zr)); + 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] = + num; + zr->jpg_buffers.buffer[num].state = BUZ_STATE_PEND; + fh->jpg_buffers.buffer[num] = + zr->jpg_buffers.buffer[num]; + zoran_feed_stat_com(zr); + break; + default: + case BUZ_STATE_DMA: + case BUZ_STATE_PEND: + if (zr->jpg_buffers.active == ZORAN_FREE) { + fh->jpg_buffers.active = ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + } + res = -EBUSY; /* what are you doing? */ + break; + } + } + + spin_unlock_irqrestore(&zr->spinlock, flags); + + if (!res && zr->jpg_buffers.active == ZORAN_FREE) { + zr->jpg_buffers.active = fh->jpg_buffers.active; + } + + return res; +} + +static int +jpg_qbuf (struct file *file, + int frame, + enum zoran_codec_mode mode) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int res = 0; + + /* Does the user want to stop streaming? */ + if (frame < 0) { + if (zr->codec_mode == mode) { + if (fh->jpg_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: jpg_qbuf(-1) - session not active\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + fh->jpg_buffers.active = zr->jpg_buffers.active = + ZORAN_FREE; + zr->jpg_buffers.allocated = 0; + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + return 0; + } else { + dprintk(1, + KERN_ERR + "%s: jpg_qbuf() - stop streaming but not in streaming mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + } + + if ((res = zoran_jpg_queue_frame(file, frame, mode))) + return res; + + /* Start the jpeg codec when the first frame is queued */ + if (!res && zr->jpg_que_head == 1) + jpeg_start(zr); + + return res; +} + +/* + * Sync on a MJPEG buffer + */ + +static int +jpg_sync (struct file *file, + struct zoran_sync *bs) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long flags; + int frame; + + if (fh->jpg_buffers.active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: jpg_sync() - capture is not currently active\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (zr->codec_mode != BUZ_MODE_MOTION_DECOMPRESS && + zr->codec_mode != BUZ_MODE_MOTION_COMPRESS) { + dprintk(1, + KERN_ERR + "%s: jpg_sync() - codec not in streaming mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (!wait_event_interruptible_timeout(zr->jpg_capq, + (zr->jpg_que_tail != zr->jpg_dma_tail || + zr->jpg_dma_tail == zr->jpg_dma_head), + 10*HZ)) { + int isr; + + btand(~ZR36057_JMC_Go_en, ZR36057_JMC); + udelay(1); + zr->codec->control(zr->codec, CODEC_G_STATUS, + sizeof(isr), &isr); + dprintk(1, + KERN_ERR + "%s: jpg_sync() - timeout: codec isr=0x%02x\n", + ZR_DEVNAME(zr), isr); + + return -ETIME; + + } + if (signal_pending(current)) + return -ERESTARTSYS; + + spin_lock_irqsave(&zr->spinlock, flags); + + if (zr->jpg_dma_tail != zr->jpg_dma_head) + frame = zr->jpg_pend[zr->jpg_que_tail++ & BUZ_MASK_FRAME]; + else + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + + /* buffer should now be in BUZ_STATE_DONE */ + if (*zr_debug > 0) + if (zr->jpg_buffers.buffer[frame].state != BUZ_STATE_DONE) + dprintk(2, + KERN_ERR + "%s: jpg_sync() - internal state error\n", + ZR_DEVNAME(zr)); + + *bs = zr->jpg_buffers.buffer[frame].bs; + bs->frame = frame; + zr->jpg_buffers.buffer[frame].state = BUZ_STATE_USER; + fh->jpg_buffers.buffer[frame] = zr->jpg_buffers.buffer[frame]; + + spin_unlock_irqrestore(&zr->spinlock, flags); + + return 0; +} + +static void +zoran_open_init_session (struct file *file) +{ + int i; + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* Per default, map the V4L Buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + + /* take over the card's current settings */ + fh->overlay_settings = zr->overlay_settings; + fh->overlay_settings.is_set = 0; + fh->overlay_settings.format = zr->overlay_settings.format; + fh->overlay_active = ZORAN_FREE; + + /* v4l settings */ + fh->v4l_settings = zr->v4l_settings; + + /* v4l_buffers */ + memset(&fh->v4l_buffers, 0, sizeof(struct zoran_v4l_struct)); + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + fh->v4l_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + fh->v4l_buffers.buffer[i].bs.frame = i; + } + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 0; + fh->v4l_buffers.active = ZORAN_FREE; + fh->v4l_buffers.buffer_size = v4l_bufsize; + fh->v4l_buffers.num_buffers = v4l_nbufs; + + /* jpg settings */ + fh->jpg_settings = zr->jpg_settings; + + /* jpg_buffers */ + memset(&fh->jpg_buffers, 0, sizeof(struct zoran_jpg_struct)); + for (i = 0; i < BUZ_MAX_FRAME; i++) { + fh->jpg_buffers.buffer[i].state = BUZ_STATE_USER; /* nothing going on */ + fh->jpg_buffers.buffer[i].bs.frame = i; + } + fh->jpg_buffers.need_contiguous = zr->jpg_buffers.need_contiguous; + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 0; + fh->jpg_buffers.active = ZORAN_FREE; + fh->jpg_buffers.buffer_size = jpg_bufsize; + fh->jpg_buffers.num_buffers = jpg_nbufs; +} + +static void +zoran_close_end_session (struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* overlay */ + if (fh->overlay_active != ZORAN_FREE) { + fh->overlay_active = zr->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } + + /* v4l capture */ + if (fh->v4l_buffers.active != ZORAN_FREE) { + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_FREE; + } + + /* v4l buffers */ + if (fh->v4l_buffers.allocated || + fh->v4l_buffers.ready_to_be_freed) { + v4l_fbuffer_free(file); + } + + /* jpg capture */ + if (fh->jpg_buffers.active != ZORAN_FREE) { + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = fh->jpg_buffers.active = + ZORAN_FREE; + } + + /* jpg buffers */ + if (fh->jpg_buffers.allocated || + fh->jpg_buffers.ready_to_be_freed) { + jpg_fbuffer_free(file); + } +} + +/* + * Open a zoran card. Right now the flags stuff is just playing + */ + +static int +zoran_open (struct inode *inode, + struct file *file) +{ + unsigned int minor = iminor(inode); + struct zoran *zr = NULL; + struct zoran_fh *fh; + int i, res, first_open = 0, have_module_locks = 0; + + /* find the device */ + for (i = 0; i < zoran_num; i++) { + if (zoran[i].video_dev->minor == minor) { + zr = &zoran[i]; + break; + } + } + + if (!zr) { + dprintk(1, KERN_ERR "%s: device not found!\n", ZORAN_NAME); + res = -ENODEV; + goto open_unlock_and_return; + } + + /* see fs/device.c - the kernel already locks during open(), + * so locking ourselves only causes deadlocks */ + /*mutex_lock(&zr->resource_lock);*/ + + if (!zr->decoder) { + dprintk(1, + KERN_ERR "%s: no TV decoder loaded for device!\n", + ZR_DEVNAME(zr)); + res = -EIO; + goto open_unlock_and_return; + } + + /* try to grab a module lock */ + if (!try_module_get(THIS_MODULE)) { + dprintk(1, + KERN_ERR + "%s: failed to acquire my own lock! PANIC!\n", + ZR_DEVNAME(zr)); + res = -ENODEV; + goto open_unlock_and_return; + } + if (!try_module_get(zr->decoder->driver->driver.owner)) { + dprintk(1, + KERN_ERR + "%s: failed to grab ownership of i2c decoder\n", + ZR_DEVNAME(zr)); + res = -EIO; + module_put(THIS_MODULE); + goto open_unlock_and_return; + } + if (zr->encoder && + !try_module_get(zr->encoder->driver->driver.owner)) { + dprintk(1, + KERN_ERR + "%s: failed to grab ownership of i2c encoder\n", + ZR_DEVNAME(zr)); + res = -EIO; + module_put(zr->decoder->driver->driver.owner); + module_put(THIS_MODULE); + goto open_unlock_and_return; + } + + have_module_locks = 1; + + if (zr->user >= 2048) { + dprintk(1, KERN_ERR "%s: too many users (%d) on device\n", + ZR_DEVNAME(zr), zr->user); + res = -EBUSY; + goto open_unlock_and_return; + } + + dprintk(1, KERN_INFO "%s: zoran_open(%s, pid=[%d]), users(-)=%d\n", + ZR_DEVNAME(zr), current->comm, current->pid, zr->user); + + /* now, create the open()-specific file_ops struct */ + fh = kzalloc(sizeof(struct zoran_fh), GFP_KERNEL); + if (!fh) { + dprintk(1, + KERN_ERR + "%s: zoran_open() - allocation of zoran_fh failed\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto open_unlock_and_return; + } + /* used to be BUZ_MAX_WIDTH/HEIGHT, but that gives overflows + * on norm-change! */ + fh->overlay_mask = + kmalloc(((768 + 31) / 32) * 576 * 4, GFP_KERNEL); + if (!fh->overlay_mask) { + dprintk(1, + KERN_ERR + "%s: zoran_open() - allocation of overlay_mask failed\n", + ZR_DEVNAME(zr)); + kfree(fh); + res = -ENOMEM; + goto open_unlock_and_return; + } + + if (zr->user++ == 0) + first_open = 1; + + /*mutex_unlock(&zr->resource_lock);*/ + + /* default setup - TODO: look at flags */ + if (first_open) { /* First device open */ + zr36057_restart(zr); + zoran_open_init_params(zr); + zoran_init_hardware(zr); + + btor(ZR36057_ICR_IntPinEn, ZR36057_ICR); + } + + /* set file_ops stuff */ + file->private_data = fh; + fh->zr = zr; + zoran_open_init_session(file); + + return 0; + +open_unlock_and_return: + /* if we grabbed locks, release them accordingly */ + if (have_module_locks) { + module_put(zr->decoder->driver->driver.owner); + if (zr->encoder) { + module_put(zr->encoder->driver->driver.owner); + } + module_put(THIS_MODULE); + } + + /* if there's no device found, we didn't obtain the lock either */ + if (zr) { + /*mutex_unlock(&zr->resource_lock);*/ + } + + return res; +} + +static int +zoran_close (struct inode *inode, + struct file *file) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + dprintk(1, KERN_INFO "%s: zoran_close(%s, pid=[%d]), users(+)=%d\n", + ZR_DEVNAME(zr), current->comm, current->pid, zr->user); + + /* kernel locks (fs/device.c), so don't do that ourselves + * (prevents deadlocks) */ + /*mutex_lock(&zr->resource_lock);*/ + + zoran_close_end_session(file); + + if (zr->user-- == 1) { /* Last process */ + /* Clean up JPEG process */ + wake_up_interruptible(&zr->jpg_capq); + zr36057_enable_jpg(zr, BUZ_MODE_IDLE); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = ZORAN_FREE; + + /* disable interrupts */ + btand(~ZR36057_ICR_IntPinEn, ZR36057_ICR); + + if (*zr_debug > 1) + print_interrupts(zr); + + /* Overlay off */ + zr->v4l_overlay_active = 0; + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + + /* capture off */ + wake_up_interruptible(&zr->v4l_capq); + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = ZORAN_FREE; + zoran_set_pci_master(zr, 0); + + if (!pass_through) { /* Switch to color bar */ + int zero = 0, two = 2; + decoder_command(zr, DECODER_ENABLE_OUTPUT, &zero); + encoder_command(zr, ENCODER_SET_INPUT, &two); + } + } + + file->private_data = NULL; + kfree(fh->overlay_mask); + kfree(fh); + + /* release locks on the i2c modules */ + module_put(zr->decoder->driver->driver.owner); + if (zr->encoder) { + module_put(zr->encoder->driver->driver.owner); + } + module_put(THIS_MODULE); + + /*mutex_unlock(&zr->resource_lock);*/ + + dprintk(4, KERN_INFO "%s: zoran_close() done\n", ZR_DEVNAME(zr)); + + return 0; +} + + +static ssize_t +zoran_read (struct file *file, + char __user *data, + size_t count, + loff_t *ppos) +{ + /* we simply don't support read() (yet)... */ + + return -EINVAL; +} + +static ssize_t +zoran_write (struct file *file, + const char __user *data, + size_t count, + loff_t *ppos) +{ + /* ...and the same goes for write() */ + + return -EINVAL; +} + +static int +setup_fbuffer (struct file *file, + void *base, + const struct zoran_format *fmt, + int width, + int height, + int bytesperline) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* (Ronald) v4l/v4l2 guidelines */ + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* we need a bytesperline value, even if not given */ + if (!bytesperline) + bytesperline = width * ((fmt->depth + 7) & ~7) / 8; + +#if 0 + if (zr->overlay_active) { + /* dzjee... stupid users... don't even bother to turn off + * overlay before changing the memory location... + * normally, we would return errors here. However, one of + * the tools that does this is... xawtv! and since xawtv + * is used by +/- 99% of the users, we'd rather be user- + * friendly and silently do as if nothing went wrong */ + dprintk(3, + KERN_ERR + "%s: setup_fbuffer() - forced overlay turnoff because framebuffer changed\n", + ZR_DEVNAME(zr)); + zr36057_overlay(zr, 0); + } +#endif + + if (!(fmt->flags & ZORAN_FORMAT_OVERLAY)) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - no valid overlay format given\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (height <= 0 || width <= 0 || bytesperline <= 0) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - invalid height/width/bpl value (%d|%d|%d)\n", + ZR_DEVNAME(zr), width, height, bytesperline); + return -EINVAL; + } + if (bytesperline & 3) { + dprintk(1, + KERN_ERR + "%s: setup_fbuffer() - bytesperline (%d) must be 4-byte aligned\n", + ZR_DEVNAME(zr), bytesperline); + return -EINVAL; + } + + zr->buffer.base = (void *) ((unsigned long) base & ~3); + zr->buffer.height = height; + zr->buffer.width = width; + zr->buffer.depth = fmt->depth; + zr->overlay_settings.format = fmt; + zr->buffer.bytesperline = bytesperline; + + /* The user should set new window parameters */ + zr->overlay_settings.is_set = 0; + + return 0; +} + + +static int +setup_window (struct file *file, + int x, + int y, + int width, + int height, + struct video_clip __user *clips, + int clipcount, + void __user *bitmap) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + struct video_clip *vcp = NULL; + int on, end; + + + if (!zr->buffer.base) { + dprintk(1, + KERN_ERR + "%s: setup_window() - frame buffer has to be set first\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: setup_window() - no overlay format set\n", + ZR_DEVNAME(zr)); + 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 = (x + width) & ~1; /* round down */ + x = (x + 1) & ~1; /* round up */ + width = end - x; + } + + if (zr->buffer.depth == 24) { + end = (x + width) & ~3; /* round down */ + x = (x + 3) & ~3; /* round up */ + width = end - x; + } + + if (width > BUZ_MAX_WIDTH) + width = BUZ_MAX_WIDTH; + if (height > BUZ_MAX_HEIGHT) + height = BUZ_MAX_HEIGHT; + + /* Check for vaild parameters */ + if (width < BUZ_MIN_WIDTH || height < BUZ_MIN_HEIGHT || + width > BUZ_MAX_WIDTH || height > BUZ_MAX_HEIGHT) { + dprintk(1, + KERN_ERR + "%s: setup_window() - width = %d or height = %d invalid\n", + ZR_DEVNAME(zr), width, height); + return -EINVAL; + } + + fh->overlay_settings.x = x; + fh->overlay_settings.y = y; + fh->overlay_settings.width = width; + fh->overlay_settings.height = height; + fh->overlay_settings.clipcount = 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 && + zr->overlay_active != ZORAN_FREE && + fh->overlay_active != ZORAN_FREE; + if (on) + zr36057_overlay(zr, 0); + + /* + * Write the overlay mask if clips are wanted. + * We prefer a bitmap. + */ + if (bitmap) { + /* fake value - it just means we want clips */ + fh->overlay_settings.clipcount = 1; + + if (copy_from_user(fh->overlay_mask, bitmap, + (width * height + 7) / 8)) { + return -EFAULT; + } + } else if (clipcount > 0) { + /* write our own bitmap from the clips */ + vcp = vmalloc(sizeof(struct video_clip) * (clipcount + 4)); + if (vcp == NULL) { + dprintk(1, + KERN_ERR + "%s: setup_window() - Alloc of clip mask failed\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + if (copy_from_user + (vcp, clips, sizeof(struct video_clip) * clipcount)) { + vfree(vcp); + return -EFAULT; + } + write_overlay_mask(file, vcp, clipcount); + vfree(vcp); + } + + fh->overlay_settings.is_set = 1; + if (fh->overlay_active != ZORAN_FREE && + zr->overlay_active != ZORAN_FREE) + zr->overlay_settings = fh->overlay_settings; + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + return wait_grab_pending(zr); +} + +static int +setup_overlay (struct file *file, + int on) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + /* If there is nothing to do, return immediatly */ + if ((on && fh->overlay_active != ZORAN_FREE) || + (!on && fh->overlay_active == ZORAN_FREE)) + return 0; + + /* check whether we're touching someone else's overlay */ + if (on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - overlay is already active for another session\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + if (!on && zr->overlay_active != ZORAN_FREE && + fh->overlay_active == ZORAN_FREE) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - you cannot cancel someone else's session\n", + ZR_DEVNAME(zr)); + return -EPERM; + } + + if (on == 0) { + zr->overlay_active = fh->overlay_active = ZORAN_FREE; + zr->v4l_overlay_active = 0; + /* When a grab is running, the video simply + * won't be switched on any more */ + if (!zr->v4l_memgrab_active) + zr36057_overlay(zr, 0); + zr->overlay_mask = NULL; + } else { + if (!zr->buffer.base || !fh->overlay_settings.is_set) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - buffer or window not set\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + if (!fh->overlay_settings.format) { + dprintk(1, + KERN_ERR + "%s: setup_overlay() - no overlay format set\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + zr->overlay_active = fh->overlay_active = ZORAN_LOCKED; + zr->v4l_overlay_active = 1; + zr->overlay_mask = fh->overlay_mask; + zr->overlay_settings = fh->overlay_settings; + 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 */ + return wait_grab_pending(zr); +} + +#ifdef HAVE_V4L2 + /* get the status of a buffer in the clients buffer queue */ +static int +zoran_v4l2_buffer_status (struct file *file, + struct v4l2_buffer *buf, + int num) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + + buf->flags = V4L2_BUF_FLAG_MAPPED; + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + + /* check range */ + if (num < 0 || num >= fh->v4l_buffers.num_buffers || + !fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l2_buffer_status() - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->length = fh->v4l_buffers.buffer_size; + + /* get buffer */ + buf->bytesused = fh->v4l_buffers.buffer[num].bs.length; + if (fh->v4l_buffers.buffer[num].state == BUZ_STATE_DONE || + fh->v4l_buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->v4l_buffers.buffer[num].bs.seq; + buf->flags |= V4L2_BUF_FLAG_DONE; + buf->timestamp = + fh->v4l_buffers.buffer[num].bs.timestamp; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + if (fh->v4l_settings.height <= BUZ_MAX_HEIGHT / 2) + buf->field = V4L2_FIELD_TOP; + else + buf->field = V4L2_FIELD_INTERLACED; + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + /* check range */ + if (num < 0 || num >= fh->jpg_buffers.num_buffers || + !fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: v4l2_buffer_status() - wrong number or buffers not allocated\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + buf->type = (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) ? + V4L2_BUF_TYPE_VIDEO_CAPTURE : + V4L2_BUF_TYPE_VIDEO_OUTPUT; + buf->length = fh->jpg_buffers.buffer_size; + + /* these variables are only written after frame has been captured */ + if (fh->jpg_buffers.buffer[num].state == BUZ_STATE_DONE || + fh->jpg_buffers.buffer[num].state == BUZ_STATE_USER) { + buf->sequence = fh->jpg_buffers.buffer[num].bs.seq; + buf->timestamp = + fh->jpg_buffers.buffer[num].bs.timestamp; + buf->bytesused = + fh->jpg_buffers.buffer[num].bs.length; + buf->flags |= V4L2_BUF_FLAG_DONE; + } else { + buf->flags |= V4L2_BUF_FLAG_QUEUED; + } + + /* which fields are these? */ + if (fh->jpg_settings.TmpDcm != 1) + buf->field = + fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + else + buf->field = + fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT; + + break; + + default: + + dprintk(5, + KERN_ERR + "%s: v4l2_buffer_status() - invalid buffer type|map_mode (%d|%d)\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + return -EINVAL; + } + + buf->memory = V4L2_MEMORY_MMAP; + buf->index = num; + buf->m.offset = buf->length * num; + + return 0; +} +#endif + +static int +zoran_set_norm (struct zoran *zr, + int norm) /* VIDEO_MODE_* */ +{ + int norm_encoder, on; + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: set_norm() called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + + if (lock_norm && norm != zr->norm) { + if (lock_norm > 1) { + dprintk(1, + KERN_WARNING + "%s: set_norm() - TV standard is locked, can not switch norm\n", + ZR_DEVNAME(zr)); + return -EPERM; + } else { + dprintk(1, + KERN_WARNING + "%s: set_norm() - TV standard is locked, norm was not changed\n", + ZR_DEVNAME(zr)); + norm = zr->norm; + } + } + + if (norm != VIDEO_MODE_AUTO && + (norm < 0 || norm >= zr->card.norms || + !zr->card.tvn[norm])) { + dprintk(1, + KERN_ERR "%s: set_norm() - unsupported norm %d\n", + ZR_DEVNAME(zr), norm); + return -EINVAL; + } + + if (norm == VIDEO_MODE_AUTO) { + int status; + + /* if we have autodetect, ... */ + struct video_decoder_capability caps; + decoder_command(zr, DECODER_GET_CAPABILITIES, &caps); + if (!(caps.flags & VIDEO_DECODER_AUTO)) { + dprintk(1, KERN_ERR "%s: norm=auto unsupported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + decoder_command(zr, DECODER_SET_NORM, &norm); + + /* let changes come into effect */ + ssleep(2); + + decoder_command(zr, DECODER_GET_STATUS, &status); + if (!(status & DECODER_STATUS_GOOD)) { + dprintk(1, + KERN_ERR + "%s: set_norm() - no norm detected\n", + ZR_DEVNAME(zr)); + /* reset norm */ + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + return -EIO; + } + + if (status & DECODER_STATUS_NTSC) + norm = VIDEO_MODE_NTSC; + else if (status & DECODER_STATUS_SECAM) + norm = VIDEO_MODE_SECAM; + else + norm = VIDEO_MODE_PAL; + } + zr->timing = zr->card.tvn[norm]; + norm_encoder = norm; + + /* We switch overlay off and on since a change in the + * norm needs different VFE settings */ + on = zr->overlay_active && !zr->v4l_memgrab_active; + if (on) + zr36057_overlay(zr, 0); + + decoder_command(zr, DECODER_SET_NORM, &norm); + encoder_command(zr, ENCODER_SET_NORM, &norm_encoder); + + if (on) + zr36057_overlay(zr, 1); + + /* Make sure the changes come into effect */ + zr->norm = norm; + + return 0; +} + +static int +zoran_set_input (struct zoran *zr, + int input) +{ + int realinput; + + if (input == zr->input) { + return 0; + } + + if (zr->v4l_buffers.active != ZORAN_FREE || + zr->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: set_input() called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + return -EBUSY; + } + + if (input < 0 || input >= zr->card.inputs) { + dprintk(1, + KERN_ERR + "%s: set_input() - unnsupported input %d\n", + ZR_DEVNAME(zr), input); + return -EINVAL; + } + + realinput = zr->card.input[input].muxsel; + zr->input = input; + + decoder_command(zr, DECODER_SET_INPUT, &realinput); + + return 0; +} + +/* + * ioctl routine + */ + +static int +zoran_do_ioctl (struct inode *inode, + struct file *file, + unsigned int cmd, + void *arg) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + /* CAREFUL: used in multiple places here */ + struct zoran_jpg_settings settings; + + /* we might have older buffers lying around... We don't want + * to wait, but we do want to try cleaning them up ASAP. So + * we try to obtain the lock and free them. If that fails, we + * don't do anything and wait for the next turn. In the end, + * zoran_close() or a new allocation will still free them... + * This is just a 'the sooner the better' extra 'feature' + * + * We don't free the buffers right on munmap() because that + * causes oopses (kfree() inside munmap() oopses for no + * apparent reason - it's also not reproduceable in any way, + * but moving the free code outside the munmap() handler fixes + * all this... If someone knows why, please explain me (Ronald) + */ + if (!!mutex_trylock(&zr->resource_lock)) { + /* we obtained it! Let's try to free some things */ + if (fh->jpg_buffers.ready_to_be_freed) + jpg_fbuffer_free(file); + if (fh->v4l_buffers.ready_to_be_freed) + v4l_fbuffer_free(file); + + mutex_unlock(&zr->resource_lock); + } + + switch (cmd) { + + case VIDIOCGCAP: + { + struct video_capability *vcap = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGCAP\n", ZR_DEVNAME(zr)); + + memset(vcap, 0, sizeof(struct video_capability)); + strncpy(vcap->name, ZR_DEVNAME(zr), sizeof(vcap->name)); + vcap->type = ZORAN_VID_TYPE; + + vcap->channels = zr->card.inputs; + vcap->audios = 0; + mutex_lock(&zr->resource_lock); + vcap->maxwidth = BUZ_MAX_WIDTH; + vcap->maxheight = BUZ_MAX_HEIGHT; + vcap->minwidth = BUZ_MIN_WIDTH; + vcap->minheight = BUZ_MIN_HEIGHT; + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCGCHAN: + { + struct video_channel *vchan = arg; + int channel = vchan->channel; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGCHAN - channel=%d\n", + ZR_DEVNAME(zr), vchan->channel); + + memset(vchan, 0, sizeof(struct video_channel)); + if (channel > zr->card.inputs || channel < 0) { + dprintk(1, + KERN_ERR + "%s: VIDIOCGCHAN on not existing channel %d\n", + ZR_DEVNAME(zr), channel); + return -EINVAL; + } + + strcpy(vchan->name, zr->card.input[channel].name); + + vchan->tuners = 0; + vchan->flags = 0; + vchan->type = VIDEO_TYPE_CAMERA; + mutex_lock(&zr->resource_lock); + vchan->norm = zr->norm; + mutex_unlock(&zr->resource_lock); + vchan->channel = channel; + + return 0; + } + break; + + /* 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 *vchan = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSCHAN - channel=%d, norm=%d\n", + ZR_DEVNAME(zr), vchan->channel, vchan->norm); + + mutex_lock(&zr->resource_lock); + if ((res = zoran_set_input(zr, vchan->channel))) + goto schan_unlock_and_return; + if ((res = zoran_set_norm(zr, vchan->norm))) + goto schan_unlock_and_return; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + schan_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOCGPICT: + { + struct video_picture *vpict = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGPICT\n", ZR_DEVNAME(zr)); + + memset(vpict, 0, sizeof(struct video_picture)); + mutex_lock(&zr->resource_lock); + vpict->hue = zr->hue; + vpict->brightness = zr->brightness; + vpict->contrast = zr->contrast; + vpict->colour = zr->saturation; + if (fh->overlay_settings.format) { + vpict->depth = fh->overlay_settings.format->depth; + vpict->palette = fh->overlay_settings.format->palette; + } else { + vpict->depth = 0; + } + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCSPICT: + { + struct video_picture *vpict = arg; + int i; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSPICT - bri=%d, hue=%d, col=%d, con=%d, dep=%d, pal=%d\n", + ZR_DEVNAME(zr), vpict->brightness, vpict->hue, + vpict->colour, vpict->contrast, vpict->depth, + vpict->palette); + + for (i = 0; i < zoran_num_formats; i++) { + const struct zoran_format *fmt = &zoran_formats[i]; + + if (fmt->palette != -1 && + fmt->flags & ZORAN_FORMAT_OVERLAY && + fmt->palette == vpict->palette && + fmt->depth == vpict->depth) + break; + } + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOCSPICT - Invalid palette %d\n", + ZR_DEVNAME(zr), vpict->palette); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + + decoder_command(zr, DECODER_SET_PICTURE, vpict); + + zr->hue = vpict->hue; + zr->contrast = vpict->contrast; + zr->saturation = vpict->colour; + zr->brightness = vpict->brightness; + + fh->overlay_settings.format = &zoran_formats[i]; + + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOCCAPTURE: + { + int *on = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOCCAPTURE - on=%d\n", + ZR_DEVNAME(zr), *on); + + mutex_lock(&zr->resource_lock); + res = setup_overlay(file, *on); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGWIN: + { + struct video_window *vwin = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGWIN\n", ZR_DEVNAME(zr)); + + memset(vwin, 0, sizeof(struct video_window)); + mutex_lock(&zr->resource_lock); + vwin->x = fh->overlay_settings.x; + vwin->y = fh->overlay_settings.y; + vwin->width = fh->overlay_settings.width; + vwin->height = fh->overlay_settings.height; + mutex_unlock(&zr->resource_lock); + vwin->clipcount = 0; + return 0; + } + break; + + case VIDIOCSWIN: + { + struct video_window *vwin = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSWIN - x=%d, y=%d, w=%d, h=%d, clipcount=%d\n", + ZR_DEVNAME(zr), vwin->x, vwin->y, vwin->width, + vwin->height, vwin->clipcount); + + mutex_lock(&zr->resource_lock); + res = + setup_window(file, vwin->x, vwin->y, vwin->width, + vwin->height, vwin->clips, + vwin->clipcount, NULL); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGFBUF: + { + struct video_buffer *vbuf = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGFBUF\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + *vbuf = zr->buffer; + mutex_unlock(&zr->resource_lock); + return 0; + } + break; + + case VIDIOCSFBUF: + { + struct video_buffer *vbuf = arg; + int i, res = 0; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCSFBUF - base=%p, w=%d, h=%d, depth=%d, bpl=%d\n", + ZR_DEVNAME(zr), vbuf->base, vbuf->width, + vbuf->height, vbuf->depth, vbuf->bytesperline); + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].depth == vbuf->depth) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOCSFBUF - invalid fbuf depth %d\n", + ZR_DEVNAME(zr), vbuf->depth); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + res = + setup_fbuffer(file, vbuf->base, &zoran_formats[i], + vbuf->width, vbuf->height, + vbuf->bytesperline); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCSYNC: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOCSYNC - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + mutex_lock(&zr->resource_lock); + res = v4l_sync(file, *frame); + mutex_unlock(&zr->resource_lock); + if (!res) + zr->v4l_sync_tail++; + return res; + } + break; + + case VIDIOCMCAPTURE: + { + struct video_mmap *vmap = arg; + int res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOCMCAPTURE - frame=%d, geom=%dx%d, fmt=%d\n", + ZR_DEVNAME(zr), vmap->frame, vmap->width, vmap->height, + vmap->format); + + mutex_lock(&zr->resource_lock); + res = v4l_grab(file, vmap); + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOCGMBUF: + { + struct video_mbuf *vmbuf = arg; + int i, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGMBUF\n", ZR_DEVNAME(zr)); + + vmbuf->size = + fh->v4l_buffers.num_buffers * + fh->v4l_buffers.buffer_size; + vmbuf->frames = fh->v4l_buffers.num_buffers; + for (i = 0; i < vmbuf->frames; i++) { + vmbuf->offsets[i] = + i * fh->v4l_buffers.buffer_size; + } + + mutex_lock(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOCGMBUF - buffers already allocated\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto v4l1reqbuf_unlock_and_return; + } + + if (v4l_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l1reqbuf_unlock_and_return; + } + + /* The next mmap will map the V4L buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + v4l1reqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOCGUNIT: + { + struct video_unit *vunit = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOCGUNIT\n", ZR_DEVNAME(zr)); + + vunit->video = zr->video_dev->minor; + vunit->vbi = VIDEO_NO_UNIT; + vunit->radio = VIDEO_NO_UNIT; + vunit->audio = VIDEO_NO_UNIT; + vunit->teletext = VIDEO_NO_UNIT; + + return 0; + } + break; + + /* + * 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: + { + dprintk(3, KERN_ERR "%s: VIDIOCGCAPTURE not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + break; + + case VIDIOCSCAPTURE: + { + dprintk(3, KERN_ERR "%s: VIDIOCSCAPTURE not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + break; + + case BUZIOC_G_PARAMS: + { + struct zoran_params *bparams = arg; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_G_PARAMS\n", ZR_DEVNAME(zr)); + + memset(bparams, 0, sizeof(struct zoran_params)); + bparams->major_version = MAJOR_VERSION; + bparams->minor_version = MINOR_VERSION; + + mutex_lock(&zr->resource_lock); + + bparams->norm = zr->norm; + bparams->input = zr->input; + + bparams->decimation = fh->jpg_settings.decimation; + bparams->HorDcm = fh->jpg_settings.HorDcm; + bparams->VerDcm = fh->jpg_settings.VerDcm; + bparams->TmpDcm = fh->jpg_settings.TmpDcm; + bparams->field_per_buff = fh->jpg_settings.field_per_buff; + bparams->img_x = fh->jpg_settings.img_x; + bparams->img_y = fh->jpg_settings.img_y; + bparams->img_width = fh->jpg_settings.img_width; + bparams->img_height = fh->jpg_settings.img_height; + bparams->odd_even = fh->jpg_settings.odd_even; + + bparams->quality = fh->jpg_settings.jpg_comp.quality; + bparams->APPn = fh->jpg_settings.jpg_comp.APPn; + bparams->APP_len = fh->jpg_settings.jpg_comp.APP_len; + memcpy(bparams->APP_data, + fh->jpg_settings.jpg_comp.APP_data, + sizeof(bparams->APP_data)); + bparams->COM_len = zr->jpg_settings.jpg_comp.COM_len; + memcpy(bparams->COM_data, + fh->jpg_settings.jpg_comp.COM_data, + sizeof(bparams->COM_data)); + bparams->jpeg_markers = + fh->jpg_settings.jpg_comp.jpeg_markers; + + mutex_unlock(&zr->resource_lock); + + bparams->VFIFO_FB = 0; + + return 0; + } + break; + + case BUZIOC_S_PARAMS: + { + struct zoran_params *bparams = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_S_PARAMS\n", ZR_DEVNAME(zr)); + + settings.decimation = bparams->decimation; + settings.HorDcm = bparams->HorDcm; + settings.VerDcm = bparams->VerDcm; + settings.TmpDcm = bparams->TmpDcm; + settings.field_per_buff = bparams->field_per_buff; + settings.img_x = bparams->img_x; + settings.img_y = bparams->img_y; + settings.img_width = bparams->img_width; + settings.img_height = bparams->img_height; + settings.odd_even = bparams->odd_even; + + settings.jpg_comp.quality = bparams->quality; + settings.jpg_comp.APPn = bparams->APPn; + settings.jpg_comp.APP_len = bparams->APP_len; + memcpy(settings.jpg_comp.APP_data, bparams->APP_data, + sizeof(bparams->APP_data)); + settings.jpg_comp.COM_len = bparams->COM_len; + memcpy(settings.jpg_comp.COM_data, bparams->COM_data, + sizeof(bparams->COM_data)); + settings.jpg_comp.jpeg_markers = bparams->jpeg_markers; + + mutex_lock(&zr->resource_lock); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_S_PARAMS called, but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto sparams_unlock_and_return; + } + + /* Check the params first before overwriting our + * nternal values */ + if (zoran_check_jpg_settings(zr, &settings)) { + res = -EINVAL; + goto sparams_unlock_and_return; + } + + fh->jpg_settings = settings; + sparams_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_REQBUFS: + { + struct zoran_requestbuffers *breq = arg; + int res = 0; + + dprintk(3, + KERN_DEBUG + "%s: BUZIOC_REQBUFS - count=%lu, size=%lu\n", + ZR_DEVNAME(zr), breq->count, breq->size); + + /* Enforce reasonable lower and upper limits */ + if (breq->count < 4) + breq->count = 4; /* Could be choosen smaller */ + if (breq->count > jpg_nbufs) + breq->count = jpg_nbufs; + breq->size = PAGE_ALIGN(breq->size); + if (breq->size < 8192) + breq->size = 8192; /* Arbitrary */ + /* breq->size is limited by 1 page for the stat_com + * tables to a Maximum of 2 MB */ + if (breq->size > jpg_bufsize) + breq->size = jpg_bufsize; + if (fh->jpg_buffers.need_contiguous && + breq->size > MAX_KMALLOC_MEM) + breq->size = MAX_KMALLOC_MEM; + + mutex_lock(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_REQBUFS - buffers allready allocated\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto jpgreqbuf_unlock_and_return; + } + + fh->jpg_buffers.num_buffers = breq->count; + fh->jpg_buffers.buffer_size = breq->size; + + if (jpg_fbuffer_alloc(file)) { + res = -ENOMEM; + goto jpgreqbuf_unlock_and_return; + } + + /* The next mmap will map the MJPEG buffers - could + * also be *_PLAY, but it doesn't matter here */ + fh->map_mode = ZORAN_MAP_MODE_JPG_REC; + jpgreqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_QBUF_CAPT: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_QBUF_CAPT - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + mutex_lock(&zr->resource_lock); + res = jpg_qbuf(file, *frame, BUZ_MODE_MOTION_COMPRESS); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_QBUF_PLAY: + { + int *frame = arg, res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_QBUF_PLAY - frame=%d\n", + ZR_DEVNAME(zr), *frame); + + mutex_lock(&zr->resource_lock); + res = jpg_qbuf(file, *frame, BUZ_MODE_MOTION_DECOMPRESS); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_SYNC: + { + struct zoran_sync *bsync = arg; + int res; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_SYNC\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + res = jpg_sync(file, bsync); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case BUZIOC_G_STATUS: + { + struct zoran_status *bstat = arg; + int norm, input, status, res = 0; + + dprintk(3, KERN_DEBUG "%s: BUZIOC_G_STATUS\n", ZR_DEVNAME(zr)); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_G_STATUS called but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + input = zr->card.input[bstat->input].muxsel; + norm = VIDEO_MODE_AUTO; + + mutex_lock(&zr->resource_lock); + + if (zr->codec_mode != BUZ_MODE_IDLE) { + dprintk(1, + KERN_ERR + "%s: BUZIOC_G_STATUS called, but Buz in capture/playback mode\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto gstat_unlock_and_return; + } + + decoder_command(zr, DECODER_SET_INPUT, &input); + decoder_command(zr, DECODER_SET_NORM, &norm); + + /* sleep 1 second */ + ssleep(1); + + /* Get status of video decoder */ + decoder_command(zr, DECODER_GET_STATUS, &status); + + /* restore previous input and norm */ + input = zr->card.input[zr->input].muxsel; + decoder_command(zr, DECODER_SET_INPUT, &input); + decoder_command(zr, DECODER_SET_NORM, &zr->norm); + gstat_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + if (!res) { + bstat->signal = + (status & DECODER_STATUS_GOOD) ? 1 : 0; + if (status & DECODER_STATUS_NTSC) + bstat->norm = VIDEO_MODE_NTSC; + else if (status & DECODER_STATUS_SECAM) + bstat->norm = VIDEO_MODE_SECAM; + else + bstat->norm = VIDEO_MODE_PAL; + + bstat->color = + (status & DECODER_STATUS_COLOR) ? 1 : 0; + } + + return res; + } + break; + +#ifdef HAVE_V4L2 + + /* The new video4linux2 capture interface - much nicer than video4linux1, since + * it allows for integrating the JPEG capturing calls inside standard v4l2 + */ + + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_QUERYCAP\n", ZR_DEVNAME(zr)); + + memset(cap, 0, sizeof(*cap)); + strncpy(cap->card, ZR_DEVNAME(zr), sizeof(cap->card)); + strncpy(cap->driver, "zoran", sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(zr->pci_dev)); + cap->version = + KERNEL_VERSION(MAJOR_VERSION, MINOR_VERSION, + RELEASE_VERSION); + cap->capabilities = ZORAN_V4L2_VID_FLAGS; + + return 0; + } + break; + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *fmt = arg; + int index = fmt->index, num = -1, i, flag = 0, type = + fmt->type; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUM_FMT - index=%d\n", + ZR_DEVNAME(zr), fmt->index); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + flag = ZORAN_FORMAT_CAPTURE; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + flag = ZORAN_FORMAT_PLAYBACK; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + flag = ZORAN_FORMAT_OVERLAY; + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_ENUM_FMT - unknown type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + + for (i = 0; i < zoran_num_formats; i++) { + if (zoran_formats[i].flags & flag) + num++; + if (num == fmt->index) + break; + } + if (fmt->index < 0 /* late, but not too late */ || + i == zoran_num_formats) + return -EINVAL; + + memset(fmt, 0, sizeof(*fmt)); + fmt->index = index; + fmt->type = type; + strncpy(fmt->description, zoran_formats[i].name, 31); + fmt->pixelformat = zoran_formats[i].fourcc; + if (zoran_formats[i].flags & ZORAN_FORMAT_COMPRESSED) + fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; + + return 0; + } + break; + + case VIDIOC_G_FMT: + { + struct v4l2_format *fmt = arg; + int type = fmt->type; + + dprintk(5, KERN_DEBUG "%s: VIDIOC_G_FMT\n", ZR_DEVNAME(zr)); + + memset(fmt, 0, sizeof(*fmt)); + fmt->type = type; + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + + mutex_lock(&zr->resource_lock); + + fmt->fmt.win.w.left = fh->overlay_settings.x; + fmt->fmt.win.w.top = fh->overlay_settings.y; + fmt->fmt.win.w.width = fh->overlay_settings.width; + fmt->fmt.win.w.height = + fh->overlay_settings.height; + if (fh->overlay_settings.width * 2 > + BUZ_MAX_HEIGHT) + fmt->fmt.win.field = V4L2_FIELD_INTERLACED; + else + fmt->fmt.win.field = V4L2_FIELD_TOP; + + mutex_unlock(&zr->resource_lock); + + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + + mutex_lock(&zr->resource_lock); + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + fh->map_mode == ZORAN_MAP_MODE_RAW) { + + fmt->fmt.pix.width = + fh->v4l_settings.width; + fmt->fmt.pix.height = + fh->v4l_settings.height; + fmt->fmt.pix.sizeimage = + fh->v4l_buffers.buffer_size; + fmt->fmt.pix.pixelformat = + fh->v4l_settings.format->fourcc; + fmt->fmt.pix.colorspace = + fh->v4l_settings.format->colorspace; + fmt->fmt.pix.bytesperline = 0; + if (BUZ_MAX_HEIGHT < + (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = + V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = + V4L2_FIELD_TOP; + + } else { + + fmt->fmt.pix.width = + fh->jpg_settings.img_width / + fh->jpg_settings.HorDcm; + fmt->fmt.pix.height = + fh->jpg_settings.img_height / + (fh->jpg_settings.VerDcm * + fh->jpg_settings.TmpDcm); + fmt->fmt.pix.sizeimage = + zoran_v4l2_calc_bufsize(&fh-> + jpg_settings); + fmt->fmt.pix.pixelformat = + V4L2_PIX_FMT_MJPEG; + if (fh->jpg_settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_BT : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.colorspace = + V4L2_COLORSPACE_SMPTE170M; + } + + mutex_unlock(&zr->resource_lock); + + break; + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_FMT - unsupported type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + return 0; + } + break; + + case VIDIOC_S_FMT: + { + struct v4l2_format *fmt = arg; + int i, res = 0; + __u32 printformat; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_FMT - type=%d, ", + ZR_DEVNAME(zr), fmt->type); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + + dprintk(3, "x=%d, y=%d, w=%d, h=%d, cnt=%d, map=0x%p\n", + fmt->fmt.win.w.left, fmt->fmt.win.w.top, + fmt->fmt.win.w.width, + fmt->fmt.win.w.height, + fmt->fmt.win.clipcount, + fmt->fmt.win.bitmap); + mutex_lock(&zr->resource_lock); + res = + setup_window(file, fmt->fmt.win.w.left, + fmt->fmt.win.w.top, + fmt->fmt.win.w.width, + fmt->fmt.win.w.height, + (struct video_clip __user *) + fmt->fmt.win.clips, + fmt->fmt.win.clipcount, + fmt->fmt.win.bitmap); + mutex_unlock(&zr->resource_lock); + return res; + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + + printformat = + __cpu_to_le32(fmt->fmt.pix.pixelformat); + dprintk(3, "size=%dx%d, fmt=0x%x (%4.4s)\n", + fmt->fmt.pix.width, fmt->fmt.pix.height, + fmt->fmt.pix.pixelformat, + (char *) &printformat); + + if (fmt->fmt.pix.bytesperline > 0) { + dprintk(5, + KERN_ERR "%s: bpl not supported\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + /* we can be requested to do JPEG/raw playback/capture */ + if (! + (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + fmt->fmt.pix.pixelformat == + V4L2_PIX_FMT_MJPEG))) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unknown type %d/0x%x(%4.4s) combination\n", + ZR_DEVNAME(zr), fmt->type, + fmt->fmt.pix.pixelformat, + (char *) &printformat); + return -EINVAL; + } + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { + mutex_lock(&zr->resource_lock); + + settings = fh->jpg_settings; + + if (fh->v4l_buffers.allocated || + fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtjpg_unlock_and_return; + } + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > + BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= + fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + /* check */ + if ((res = + zoran_check_jpg_settings(zr, + &settings))) + goto sfmtjpg_unlock_and_return; + + /* it's ok, so set them */ + fh->jpg_settings = settings; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = + settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = + settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh-> + jpg_settings); + fmt->fmt.pix.sizeimage = + fh->jpg_buffers.buffer_size; + + /* we hereby abuse this variable to show that + * we're gonna do mjpeg capture */ + fh->map_mode = + (fmt->type == + V4L2_BUF_TYPE_VIDEO_CAPTURE) ? + ZORAN_MAP_MODE_JPG_REC : + ZORAN_MAP_MODE_JPG_PLAY; + sfmtjpg_unlock_and_return: + mutex_unlock(&zr->resource_lock); + } else { + for (i = 0; i < zoran_num_formats; i++) + if (fmt->fmt.pix.pixelformat == + zoran_formats[i].fourcc) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unknown/unsupported format 0x%x (%4.4s)\n", + ZR_DEVNAME(zr), + fmt->fmt.pix.pixelformat, + (char *) &printformat); + return -EINVAL; + } + mutex_lock(&zr->resource_lock); + if (fh->jpg_buffers.allocated || + (fh->v4l_buffers.allocated && + fh->v4l_buffers.active != + ZORAN_FREE)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - cannot change capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sfmtv4l_unlock_and_return; + } + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = + BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + + if ((res = + zoran_v4l_set_format(file, + fmt->fmt.pix. + width, + fmt->fmt.pix. + height, + &zoran_formats + [i]))) + goto sfmtv4l_unlock_and_return; + + /* tell the user the + * results/missing stuff */ + fmt->fmt.pix.sizeimage = fh->v4l_buffers.buffer_size /*zr->gbpl * zr->gheight */ + ; + if (BUZ_MAX_HEIGHT < + (fh->v4l_settings.height * 2)) + fmt->fmt.pix.field = + V4L2_FIELD_INTERLACED; + else + fmt->fmt.pix.field = + V4L2_FIELD_TOP; + + fh->map_mode = ZORAN_MAP_MODE_RAW; + sfmtv4l_unlock_and_return: + mutex_unlock(&zr->resource_lock); + } + + break; + + default: + dprintk(3, "unsupported\n"); + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FMT - unsupported type %d\n", + ZR_DEVNAME(zr), fmt->type); + return -EINVAL; + } + + return res; + } + break; + + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_FBUF\n", ZR_DEVNAME(zr)); + + memset(fb, 0, sizeof(*fb)); + mutex_lock(&zr->resource_lock); + fb->base = zr->buffer.base; + fb->fmt.width = zr->buffer.width; + fb->fmt.height = zr->buffer.height; + if (zr->overlay_settings.format) { + fb->fmt.pixelformat = + fh->overlay_settings.format->fourcc; + } + fb->fmt.bytesperline = zr->buffer.bytesperline; + mutex_unlock(&zr->resource_lock); + fb->fmt.colorspace = V4L2_COLORSPACE_SRGB; + fb->fmt.field = V4L2_FIELD_INTERLACED; + fb->flags = V4L2_FBUF_FLAG_OVERLAY; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + + return 0; + } + break; + + case VIDIOC_S_FBUF: + { + int i, res = 0; + struct v4l2_framebuffer *fb = arg; + __u32 printformat = __cpu_to_le32(fb->fmt.pixelformat); + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_S_FBUF - base=0x%p, size=%dx%d, bpl=%d, fmt=0x%x (%4.4s)\n", + ZR_DEVNAME(zr), fb->base, fb->fmt.width, fb->fmt.height, + fb->fmt.bytesperline, fb->fmt.pixelformat, + (char *) &printformat); + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].fourcc == fb->fmt.pixelformat) + break; + if (i == zoran_num_formats) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_FBUF - format=0x%x (%4.4s) not allowed\n", + ZR_DEVNAME(zr), fb->fmt.pixelformat, + (char *) &printformat); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + res = + setup_fbuffer(file, fb->base, &zoran_formats[i], + fb->fmt.width, fb->fmt.height, + fb->fmt.bytesperline); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_OVERLAY: + { + int *on = arg, res; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_PREVIEW - on=%d\n", + ZR_DEVNAME(zr), *on); + + mutex_lock(&zr->resource_lock); + res = setup_overlay(file, *on); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_REQBUFS - type=%d\n", + ZR_DEVNAME(zr), req->type); + + if (req->memory != V4L2_MEMORY_MMAP) { + dprintk(1, + KERN_ERR + "%s: only MEMORY_MMAP capture is supported, not %d\n", + ZR_DEVNAME(zr), req->memory); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + + if (fh->v4l_buffers.allocated || fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_REQBUFS - buffers allready allocated\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto v4l2reqbuf_unlock_and_return; + } + + if (fh->map_mode == ZORAN_MAP_MODE_RAW && + req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + + /* control user input */ + if (req->count < 2) + req->count = 2; + if (req->count > v4l_nbufs) + req->count = v4l_nbufs; + fh->v4l_buffers.num_buffers = req->count; + + if (v4l_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + + /* The next mmap will map the V4L buffers */ + fh->map_mode = ZORAN_MAP_MODE_RAW; + + } else if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC || + fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + + /* we need to calculate size ourselves now */ + if (req->count < 4) + req->count = 4; + if (req->count > jpg_nbufs) + req->count = jpg_nbufs; + fh->jpg_buffers.num_buffers = req->count; + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh->jpg_settings); + + if (jpg_fbuffer_alloc(file)) { + res = -ENOMEM; + goto v4l2reqbuf_unlock_and_return; + } + + /* The next mmap will map the MJPEG buffers */ + if (req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + fh->map_mode = ZORAN_MAP_MODE_JPG_REC; + else + fh->map_mode = ZORAN_MAP_MODE_JPG_PLAY; + + } else { + dprintk(1, + KERN_ERR + "%s: VIDIOC_REQBUFS - unknown type %d\n", + ZR_DEVNAME(zr), req->type); + res = -EINVAL; + goto v4l2reqbuf_unlock_and_return; + } + v4l2reqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + __u32 type = buf->type; + int index = buf->index, res; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_QUERYBUF - index=%d, type=%d\n", + ZR_DEVNAME(zr), buf->index, buf->type); + + memset(buf, 0, sizeof(buf)); + buf->type = type; + buf->index = index; + + mutex_lock(&zr->resource_lock); + res = zoran_v4l2_buffer_status(file, buf, buf->index); + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int res = 0, codec_mode, buf_type; + + dprintk(3, + KERN_DEBUG "%s: VIDIOC_QBUF - type=%d, index=%d\n", + ZR_DEVNAME(zr), buf->type, buf->index); + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = zoran_v4l_queue_frame(file, buf->index); + if (res) + goto qbuf_unlock_and_return; + if (!zr->v4l_memgrab_active && + fh->v4l_buffers.active == ZORAN_LOCKED) + zr36057_set_memgrab(zr, 1); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) { + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + codec_mode = BUZ_MODE_MOTION_DECOMPRESS; + } else { + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + codec_mode = BUZ_MODE_MOTION_COMPRESS; + } + + if (buf->type != buf_type) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + + res = + zoran_jpg_queue_frame(file, buf->index, + codec_mode); + if (res != 0) + goto qbuf_unlock_and_return; + if (zr->codec_mode == BUZ_MODE_IDLE && + fh->jpg_buffers.active == ZORAN_LOCKED) { + zr36057_enable_jpg(zr, codec_mode); + } + break; + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + goto qbuf_unlock_and_return; + } + qbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int res = 0, buf_type, num = -1; /* compiler borks here (?) */ + + dprintk(3, KERN_DEBUG "%s: VIDIOC_DQBUF - type=%d\n", + ZR_DEVNAME(zr), buf->type); + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = zr->v4l_pend[zr->v4l_sync_tail & V4L_MASK_FRAME]; + if (file->f_flags & O_NONBLOCK && + zr->v4l_buffers.buffer[num].state != + BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + res = v4l_sync(file, num); + if (res) + goto dqbuf_unlock_and_return; + else + zr->v4l_sync_tail++; + res = zoran_v4l2_buffer_status(file, buf, num); + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + { + struct zoran_sync bs; + + if (fh->map_mode == ZORAN_MAP_MODE_JPG_PLAY) + buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + else + buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (buf->type != buf_type) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_QBUF - invalid buf->type=%d for map_mode=%d\n", + ZR_DEVNAME(zr), buf->type, fh->map_mode); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + + num = + zr->jpg_pend[zr-> + jpg_que_tail & BUZ_MASK_FRAME]; + + if (file->f_flags & O_NONBLOCK && + zr->jpg_buffers.buffer[num].state != + BUZ_STATE_DONE) { + res = -EAGAIN; + goto dqbuf_unlock_and_return; + } + res = jpg_sync(file, &bs); + if (res) + goto dqbuf_unlock_and_return; + res = + zoran_v4l2_buffer_status(file, buf, bs.frame); + break; + } + + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_DQBUF - unsupported type %d\n", + ZR_DEVNAME(zr), buf->type); + res = -EINVAL; + goto dqbuf_unlock_and_return; + } + dqbuf_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_STREAMON: + { + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_STREAMON\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (zr->v4l_buffers.active != ZORAN_ACTIVE || + fh->v4l_buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_LOCKED; + zr->v4l_settings = fh->v4l_settings; + + zr->v4l_sync_tail = zr->v4l_pend_tail; + if (!zr->v4l_memgrab_active && + zr->v4l_pend_head != zr->v4l_pend_tail) { + zr36057_set_memgrab(zr, 1); + } + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + /* what is the codec mode right now? */ + if (zr->jpg_buffers.active != ZORAN_ACTIVE || + fh->jpg_buffers.active != ZORAN_ACTIVE) { + res = -EBUSY; + goto strmon_unlock_and_return; + } + + zr->jpg_buffers.active = fh->jpg_buffers.active = + ZORAN_LOCKED; + + if (zr->jpg_que_head != zr->jpg_que_tail) { + /* Start the jpeg codec when the first frame is queued */ + jpeg_start(zr); + } + + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_STREAMON - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + goto strmon_unlock_and_return; + } + strmon_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_STREAMOFF: + { + int i, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_STREAMOFF\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: /* raw capture */ + if (fh->v4l_buffers.active == ZORAN_FREE && + zr->v4l_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->v4l_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + /* unload capture */ + if (zr->v4l_memgrab_active) + zr36057_set_memgrab(zr, 0); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) + zr->v4l_buffers.buffer[i].state = + BUZ_STATE_USER; + fh->v4l_buffers = zr->v4l_buffers; + + zr->v4l_buffers.active = fh->v4l_buffers.active = + ZORAN_FREE; + + zr->v4l_grab_seq = 0; + zr->v4l_pend_head = zr->v4l_pend_tail = 0; + zr->v4l_sync_tail = 0; + + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->jpg_buffers.active == ZORAN_FREE && + zr->jpg_buffers.active != ZORAN_FREE) { + res = -EPERM; /* stay off other's settings! */ + goto strmoff_unlock_and_return; + } + if (zr->jpg_buffers.active == ZORAN_FREE) + goto strmoff_unlock_and_return; + + res = + jpg_qbuf(file, -1, + (fh->map_mode == + ZORAN_MAP_MODE_JPG_REC) ? + BUZ_MODE_MOTION_COMPRESS : + BUZ_MODE_MOTION_DECOMPRESS); + if (res) + goto strmoff_unlock_and_return; + break; + default: + dprintk(1, + KERN_ERR + "%s: VIDIOC_STREAMOFF - invalid map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = -EINVAL; + goto strmoff_unlock_and_return; + } + strmoff_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_QUERYCTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + else { + int id = ctrl->id; + memset(ctrl, 0, sizeof(*ctrl)); + ctrl->id = id; + } + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + strncpy(ctrl->name, "Brightness", 31); + break; + case V4L2_CID_CONTRAST: + strncpy(ctrl->name, "Contrast", 31); + break; + case V4L2_CID_SATURATION: + strncpy(ctrl->name, "Saturation", 31); + break; + case V4L2_CID_HUE: + strncpy(ctrl->name, "Hue", 31); + break; + } + + ctrl->minimum = 0; + ctrl->maximum = 65535; + ctrl->step = 1; + ctrl->default_value = 32768; + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + + return 0; + } + break; + + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_CTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = zr->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = zr->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = zr->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = zr->hue; + break; + } + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + struct video_picture pict; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_CTRL - id=%d\n", + ZR_DEVNAME(zr), ctrl->id); + + /* we only support hue/saturation/contrast/brightness */ + if (ctrl->id < V4L2_CID_BRIGHTNESS || + ctrl->id > V4L2_CID_HUE) + return -EINVAL; + + if (ctrl->value < 0 || ctrl->value > 65535) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_CTRL - invalid value %d for id=%d\n", + ZR_DEVNAME(zr), ctrl->value, ctrl->id); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + zr->brightness = ctrl->value; + break; + case V4L2_CID_CONTRAST: + zr->contrast = ctrl->value; + break; + case V4L2_CID_SATURATION: + zr->saturation = ctrl->value; + break; + case V4L2_CID_HUE: + zr->hue = ctrl->value; + break; + } + pict.brightness = zr->brightness; + pict.contrast = zr->contrast; + pict.colour = zr->saturation; + pict.hue = zr->hue; + + decoder_command(zr, DECODER_SET_PICTURE, &pict); + + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *std = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMSTD - index=%d\n", + ZR_DEVNAME(zr), std->index); + + if (std->index < 0 || std->index >= (zr->card.norms + 1)) + return -EINVAL; + else { + int id = std->index; + memset(std, 0, sizeof(*std)); + std->index = id; + } + + if (std->index == zr->card.norms) { + /* if we have autodetect, ... */ + struct video_decoder_capability caps; + decoder_command(zr, DECODER_GET_CAPABILITIES, + &caps); + if (caps.flags & VIDEO_DECODER_AUTO) { + std->id = V4L2_STD_ALL; + strncpy(std->name, "Autodetect", 31); + return 0; + } else + return -EINVAL; + } + switch (std->index) { + case 0: + std->id = V4L2_STD_PAL; + strncpy(std->name, "PAL", 31); + std->frameperiod.numerator = 1; + std->frameperiod.denominator = 25; + std->framelines = zr->card.tvn[0]->Ht; + break; + case 1: + std->id = V4L2_STD_NTSC; + strncpy(std->name, "NTSC", 31); + std->frameperiod.numerator = 1001; + std->frameperiod.denominator = 30000; + std->framelines = zr->card.tvn[1]->Ht; + break; + case 2: + std->id = V4L2_STD_SECAM; + strncpy(std->name, "SECAM", 31); + std->frameperiod.numerator = 1; + std->frameperiod.denominator = 25; + std->framelines = zr->card.tvn[2]->Ht; + break; + } + + return 0; + } + break; + + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + int norm; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_STD\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + norm = zr->norm; + mutex_unlock(&zr->resource_lock); + + switch (norm) { + case VIDEO_MODE_PAL: + *std = V4L2_STD_PAL; + break; + case VIDEO_MODE_NTSC: + *std = V4L2_STD_NTSC; + break; + case VIDEO_MODE_SECAM: + *std = V4L2_STD_SECAM; + break; + } + + return 0; + } + break; + + case VIDIOC_S_STD: + { + int norm = -1, res = 0; + v4l2_std_id *std = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_STD - norm=0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + + if (*std == V4L2_STD_PAL) + norm = VIDEO_MODE_PAL; + else if (*std == V4L2_STD_NTSC) + norm = VIDEO_MODE_NTSC; + else if (*std == V4L2_STD_SECAM) + norm = VIDEO_MODE_SECAM; + else if (*std == V4L2_STD_ALL) + norm = VIDEO_MODE_AUTO; + else { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_STD - invalid norm 0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + return -EINVAL; + } + + mutex_lock(&zr->resource_lock); + if ((res = zoran_set_norm(zr, norm))) + goto sstd_unlock_and_return; + + res = wait_grab_pending(zr); + sstd_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *inp = arg; + int status; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMINPUT - index=%d\n", + ZR_DEVNAME(zr), inp->index); + + if (inp->index < 0 || inp->index >= zr->card.inputs) + return -EINVAL; + else { + int id = inp->index; + memset(inp, 0, sizeof(*inp)); + inp->index = id; + } + + strncpy(inp->name, zr->card.input[inp->index].name, + sizeof(inp->name) - 1); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = V4L2_STD_ALL; + + /* Get status of video decoder */ + mutex_lock(&zr->resource_lock); + decoder_command(zr, DECODER_GET_STATUS, &status); + mutex_unlock(&zr->resource_lock); + + if (!(status & DECODER_STATUS_GOOD)) { + inp->status |= V4L2_IN_ST_NO_POWER; + inp->status |= V4L2_IN_ST_NO_SIGNAL; + } + if (!(status & DECODER_STATUS_COLOR)) + inp->status |= V4L2_IN_ST_NO_COLOR; + + return 0; + } + break; + + case VIDIOC_G_INPUT: + { + int *input = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_INPUT\n", ZR_DEVNAME(zr)); + + mutex_lock(&zr->resource_lock); + *input = zr->input; + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_INPUT: + { + int *input = arg, res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_INPUT - input=%d\n", + ZR_DEVNAME(zr), *input); + + mutex_lock(&zr->resource_lock); + if ((res = zoran_set_input(zr, *input))) + goto sinput_unlock_and_return; + + /* Make sure the changes come into effect */ + res = wait_grab_pending(zr); + sinput_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *outp = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_ENUMOUTPUT - index=%d\n", + ZR_DEVNAME(zr), outp->index); + + if (outp->index != 0) + return -EINVAL; + + memset(outp, 0, sizeof(*outp)); + outp->index = 0; + outp->type = V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY; + strncpy(outp->name, "Autodetect", 31); + + return 0; + } + break; + + case VIDIOC_G_OUTPUT: + { + int *output = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_OUTPUT\n", ZR_DEVNAME(zr)); + + *output = 0; + + return 0; + } + break; + + case VIDIOC_S_OUTPUT: + { + int *output = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_S_OUTPUT - output=%d\n", + ZR_DEVNAME(zr), *output); + + if (*output != 0) + return -EINVAL; + + return 0; + } + break; + + /* cropping (sub-frame capture) */ + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cropcap = arg; + int type = cropcap->type, res = 0; + + dprintk(3, KERN_ERR "%s: VIDIOC_CROPCAP - type=%d\n", + ZR_DEVNAME(zr), cropcap->type); + + memset(cropcap, 0, sizeof(*cropcap)); + cropcap->type = type; + + mutex_lock(&zr->resource_lock); + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_CROPCAP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto cropcap_unlock_and_return; + } + + cropcap->bounds.top = cropcap->bounds.left = 0; + cropcap->bounds.width = BUZ_MAX_WIDTH; + cropcap->bounds.height = BUZ_MAX_HEIGHT; + cropcap->defrect.top = cropcap->defrect.left = 0; + cropcap->defrect.width = BUZ_MIN_WIDTH; + cropcap->defrect.height = BUZ_MIN_HEIGHT; + cropcap_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + int type = crop->type, res = 0; + + dprintk(3, KERN_ERR "%s: VIDIOC_G_CROP - type=%d\n", + ZR_DEVNAME(zr), crop->type); + + memset(crop, 0, sizeof(*crop)); + crop->type = type; + + mutex_lock(&zr->resource_lock); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto gcrop_unlock_and_return; + } + + crop->c.top = fh->jpg_settings.img_y; + crop->c.left = fh->jpg_settings.img_x; + crop->c.width = fh->jpg_settings.img_width; + crop->c.height = fh->jpg_settings.img_height; + + gcrop_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + } + break; + + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + int res = 0; + + settings = fh->jpg_settings; + + dprintk(3, + KERN_ERR + "%s: VIDIOC_S_CROP - type=%d, x=%d,y=%d,w=%d,h=%d\n", + ZR_DEVNAME(zr), crop->type, crop->c.left, crop->c.top, + crop->c.width, crop->c.height); + + mutex_lock(&zr->resource_lock); + + if (fh->jpg_buffers.allocated || fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_S_CROP - cannot change settings while active\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto scrop_unlock_and_return; + } + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + fh->map_mode == ZORAN_MAP_MODE_RAW)) { + dprintk(1, + KERN_ERR + "%s: VIDIOC_G_CROP - subcapture only supported for compressed capture\n", + ZR_DEVNAME(zr)); + res = -EINVAL; + goto scrop_unlock_and_return; + } + + /* move into a form that we understand */ + settings.img_x = crop->c.left; + settings.img_y = crop->c.top; + settings.img_width = crop->c.width; + settings.img_height = crop->c.height; + + /* check validity */ + if ((res = zoran_check_jpg_settings(zr, &settings))) + goto scrop_unlock_and_return; + + /* accept */ + fh->jpg_settings = settings; + + scrop_unlock_and_return: + mutex_unlock(&zr->resource_lock); + return res; + } + break; + + case VIDIOC_G_JPEGCOMP: + { + struct v4l2_jpegcompression *params = arg; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_G_JPEGCOMP\n", + ZR_DEVNAME(zr)); + + memset(params, 0, sizeof(*params)); + + mutex_lock(&zr->resource_lock); + + params->quality = fh->jpg_settings.jpg_comp.quality; + params->APPn = fh->jpg_settings.jpg_comp.APPn; + memcpy(params->APP_data, + fh->jpg_settings.jpg_comp.APP_data, + fh->jpg_settings.jpg_comp.APP_len); + params->APP_len = fh->jpg_settings.jpg_comp.APP_len; + memcpy(params->COM_data, + fh->jpg_settings.jpg_comp.COM_data, + fh->jpg_settings.jpg_comp.COM_len); + params->COM_len = fh->jpg_settings.jpg_comp.COM_len; + params->jpeg_markers = + fh->jpg_settings.jpg_comp.jpeg_markers; + + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_S_JPEGCOMP: + { + struct v4l2_jpegcompression *params = arg; + int res = 0; + + settings = fh->jpg_settings; + + dprintk(3, + KERN_DEBUG + "%s: VIDIOC_S_JPEGCOMP - quality=%d, APPN=%d, APP_len=%d, COM_len=%d\n", + ZR_DEVNAME(zr), params->quality, params->APPn, + params->APP_len, params->COM_len); + + settings.jpg_comp = *params; + + mutex_lock(&zr->resource_lock); + + if (fh->v4l_buffers.active != ZORAN_FREE || + fh->jpg_buffers.active != ZORAN_FREE) { + dprintk(1, + KERN_WARNING + "%s: VIDIOC_S_JPEGCOMP called while in playback/capture mode\n", + ZR_DEVNAME(zr)); + res = -EBUSY; + goto sjpegc_unlock_and_return; + } + + if ((res = zoran_check_jpg_settings(zr, &settings))) + goto sjpegc_unlock_and_return; + if (!fh->jpg_buffers.allocated) + fh->jpg_buffers.buffer_size = + zoran_v4l2_calc_bufsize(&fh->jpg_settings); + fh->jpg_settings.jpg_comp = *params = settings.jpg_comp; + sjpegc_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return 0; + } + break; + + case VIDIOC_QUERYSTD: /* why is this useful? */ + { + v4l2_std_id *std = arg; + + dprintk(3, + KERN_DEBUG "%s: VIDIOC_QUERY_STD - std=0x%llx\n", + ZR_DEVNAME(zr), (unsigned long long)*std); + + if (*std == V4L2_STD_ALL || *std == V4L2_STD_NTSC || + *std == V4L2_STD_PAL || (*std == V4L2_STD_SECAM && + zr->card.norms == 3)) { + return 0; + } + + return -EINVAL; + } + break; + + case VIDIOC_TRY_FMT: + { + struct v4l2_format *fmt = arg; + int res = 0; + + dprintk(3, KERN_DEBUG "%s: VIDIOC_TRY_FMT - type=%d\n", + ZR_DEVNAME(zr), fmt->type); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + mutex_lock(&zr->resource_lock); + + if (fmt->fmt.win.w.width > BUZ_MAX_WIDTH) + fmt->fmt.win.w.width = BUZ_MAX_WIDTH; + if (fmt->fmt.win.w.width < BUZ_MIN_WIDTH) + fmt->fmt.win.w.width = BUZ_MIN_WIDTH; + if (fmt->fmt.win.w.height > BUZ_MAX_HEIGHT) + fmt->fmt.win.w.height = BUZ_MAX_HEIGHT; + if (fmt->fmt.win.w.height < BUZ_MIN_HEIGHT) + fmt->fmt.win.w.height = BUZ_MIN_HEIGHT; + + mutex_unlock(&zr->resource_lock); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (fmt->fmt.pix.bytesperline > 0) + return -EINVAL; + + mutex_lock(&zr->resource_lock); + + if (fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG) { + settings = fh->jpg_settings; + + /* we actually need to set 'real' parameters now */ + if ((fmt->fmt.pix.height * 2) > + BUZ_MAX_HEIGHT) + settings.TmpDcm = 1; + else + settings.TmpDcm = 2; + settings.decimation = 0; + if (fmt->fmt.pix.height <= + fh->jpg_settings.img_height / 2) + settings.VerDcm = 2; + else + settings.VerDcm = 1; + if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 4) + settings.HorDcm = 4; + else if (fmt->fmt.pix.width <= + fh->jpg_settings.img_width / 2) + settings.HorDcm = 2; + else + settings.HorDcm = 1; + if (settings.TmpDcm == 1) + settings.field_per_buff = 2; + else + settings.field_per_buff = 1; + + /* check */ + if ((res = + zoran_check_jpg_settings(zr, + &settings))) + goto tryfmt_unlock_and_return; + + /* tell the user what we actually did */ + fmt->fmt.pix.width = + settings.img_width / settings.HorDcm; + fmt->fmt.pix.height = + settings.img_height * 2 / + (settings.TmpDcm * settings.VerDcm); + if (settings.TmpDcm == 1) + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_SEQ_TB : + V4L2_FIELD_SEQ_BT); + else + fmt->fmt.pix.field = + (fh->jpg_settings. + odd_even ? V4L2_FIELD_TOP : + V4L2_FIELD_BOTTOM); + + fmt->fmt.pix.sizeimage = + zoran_v4l2_calc_bufsize(&settings); + } else if (fmt->type == + V4L2_BUF_TYPE_VIDEO_CAPTURE) { + int i; + + for (i = 0; i < zoran_num_formats; i++) + if (zoran_formats[i].fourcc == + fmt->fmt.pix.pixelformat) + break; + if (i == zoran_num_formats) { + res = -EINVAL; + goto tryfmt_unlock_and_return; + } + + if (fmt->fmt.pix.width > BUZ_MAX_WIDTH) + fmt->fmt.pix.width = BUZ_MAX_WIDTH; + if (fmt->fmt.pix.width < BUZ_MIN_WIDTH) + fmt->fmt.pix.width = BUZ_MIN_WIDTH; + if (fmt->fmt.pix.height > BUZ_MAX_HEIGHT) + fmt->fmt.pix.height = + BUZ_MAX_HEIGHT; + if (fmt->fmt.pix.height < BUZ_MIN_HEIGHT) + fmt->fmt.pix.height = + BUZ_MIN_HEIGHT; + } else { + res = -EINVAL; + goto tryfmt_unlock_and_return; + } + tryfmt_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; + break; + + default: + return -EINVAL; + } + + return 0; + } + break; +#endif + + default: + dprintk(1, KERN_DEBUG "%s: UNKNOWN ioctl cmd: 0x%x\n", + ZR_DEVNAME(zr), cmd); + return -ENOIOCTLCMD; + break; + + } + return 0; +} + + +static int +zoran_ioctl (struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, zoran_do_ioctl); +} + +static unsigned int +zoran_poll (struct file *file, + poll_table *wait) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + wait_queue_head_t *queue = NULL; + int res = 0, frame; + + /* we should check whether buffers are ready to be synced on + * (w/o waits - O_NONBLOCK) here + * if ready for read (sync), return POLLIN|POLLRDNORM, + * if ready for write (sync), return POLLOUT|POLLWRNORM, + * if error, return POLLERR, + * if no buffers queued or so, return POLLNVAL + */ + + mutex_lock(&zr->resource_lock); + + switch (fh->map_mode) { + case ZORAN_MAP_MODE_RAW: + if (fh->v4l_buffers.active == ZORAN_FREE || + zr->v4l_pend_head == zr->v4l_pend_tail) { + dprintk(1, + "%s: zoran_poll() - no buffers queued\n", + ZR_DEVNAME(zr)); + res = POLLNVAL; + goto poll_unlock_and_return; + } + queue = &zr->v4l_capq; + frame = zr->v4l_pend[zr->v4l_pend_tail & V4L_MASK_FRAME]; + poll_wait(file, queue, wait); + if (fh->v4l_buffers.buffer[frame].state == BUZ_STATE_DONE) + res = POLLIN | POLLRDNORM; + break; + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + if (fh->jpg_buffers.active == ZORAN_FREE || + zr->jpg_que_head == zr->jpg_que_tail) { + dprintk(1, + "%s: zoran_poll() - no buffers queued\n", + ZR_DEVNAME(zr)); + res = POLLNVAL; + goto poll_unlock_and_return; + } + queue = &zr->jpg_capq; + frame = zr->jpg_pend[zr->jpg_que_tail & BUZ_MASK_FRAME]; + poll_wait(file, queue, wait); + if (fh->jpg_buffers.buffer[frame].state == BUZ_STATE_DONE) { + if (fh->map_mode == ZORAN_MAP_MODE_JPG_REC) + res = POLLIN | POLLRDNORM; + else + res = POLLOUT | POLLWRNORM; + } + break; + + default: + dprintk(1, + "%s: zoran_poll() - internal error, unknown map_mode=%d\n", + ZR_DEVNAME(zr), fh->map_mode); + res = POLLNVAL; + goto poll_unlock_and_return; + } + +poll_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + return res; +} + + +/* + * This maps the buffers to user space. + * + * Depending on the state of fh->map_mode + * the V4L or the MJPEG buffers are mapped + * per buffer or all together + * + * Note that we need to connect to some + * unmap signal event to unmap the de-allocate + * the buffer accordingly (zoran_vm_close()) + */ + +static void +zoran_vm_open (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + + map->count++; +} + +static void +zoran_vm_close (struct vm_area_struct *vma) +{ + struct zoran_mapping *map = vma->vm_private_data; + struct file *file = map->file; + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + int i; + + map->count--; + if (map->count == 0) { + switch (fh->map_mode) { + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + dprintk(3, KERN_INFO "%s: munmap(MJPEG)\n", + ZR_DEVNAME(zr)); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) { + if (fh->jpg_buffers.buffer[i].map == map) { + fh->jpg_buffers.buffer[i].map = + NULL; + } + } + kfree(map); + + for (i = 0; i < fh->jpg_buffers.num_buffers; i++) + if (fh->jpg_buffers.buffer[i].map) + break; + if (i == fh->jpg_buffers.num_buffers) { + mutex_lock(&zr->resource_lock); + + if (fh->jpg_buffers.active != ZORAN_FREE) { + jpg_qbuf(file, -1, zr->codec_mode); + zr->jpg_buffers.allocated = 0; + zr->jpg_buffers.active = + fh->jpg_buffers.active = + ZORAN_FREE; + } + //jpg_fbuffer_free(file); + fh->jpg_buffers.allocated = 0; + fh->jpg_buffers.ready_to_be_freed = 1; + + mutex_unlock(&zr->resource_lock); + } + + break; + + case ZORAN_MAP_MODE_RAW: + + dprintk(3, KERN_INFO "%s: munmap(V4L)\n", + ZR_DEVNAME(zr)); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) { + if (fh->v4l_buffers.buffer[i].map == map) { + /* unqueue/unmap */ + fh->v4l_buffers.buffer[i].map = + NULL; + } + } + kfree(map); + + for (i = 0; i < fh->v4l_buffers.num_buffers; i++) + if (fh->v4l_buffers.buffer[i].map) + break; + if (i == fh->v4l_buffers.num_buffers) { + mutex_lock(&zr->resource_lock); + + if (fh->v4l_buffers.active != ZORAN_FREE) { + zr36057_set_memgrab(zr, 0); + zr->v4l_buffers.allocated = 0; + zr->v4l_buffers.active = + fh->v4l_buffers.active = + ZORAN_FREE; + } + //v4l_fbuffer_free(file); + fh->v4l_buffers.allocated = 0; + fh->v4l_buffers.ready_to_be_freed = 1; + + mutex_unlock(&zr->resource_lock); + } + + break; + + default: + printk(KERN_ERR + "%s: munmap() - internal error - unknown map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + break; + + } + } +} + +static struct vm_operations_struct zoran_vm_ops = { + .open = zoran_vm_open, + .close = zoran_vm_close, +}; + +static int +zoran_mmap (struct file *file, + struct vm_area_struct *vma) +{ + struct zoran_fh *fh = file->private_data; + struct zoran *zr = fh->zr; + unsigned long size = (vma->vm_end - vma->vm_start); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int i, j; + unsigned long page, start = vma->vm_start, todo, pos, fraglen; + int first, last; + struct zoran_mapping *map; + int res = 0; + + dprintk(3, + KERN_INFO "%s: mmap(%s) of 0x%08lx-0x%08lx (size=%lu)\n", + ZR_DEVNAME(zr), + fh->map_mode == ZORAN_MAP_MODE_RAW ? "V4L" : "MJPEG", + vma->vm_start, vma->vm_end, size); + + if (!(vma->vm_flags & VM_SHARED) || !(vma->vm_flags & VM_READ) || + !(vma->vm_flags & VM_WRITE)) { + dprintk(1, + KERN_ERR + "%s: mmap() - no MAP_SHARED/PROT_{READ,WRITE} given\n", + ZR_DEVNAME(zr)); + return -EINVAL; + } + + switch (fh->map_mode) { + + case ZORAN_MAP_MODE_JPG_REC: + case ZORAN_MAP_MODE_JPG_PLAY: + + /* lock */ + mutex_lock(&zr->resource_lock); + + /* Map the MJPEG buffers */ + if (!fh->jpg_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(MJPEG) - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto jpg_mmap_unlock_and_return; + } + + first = offset / fh->jpg_buffers.buffer_size; + last = first - 1 + size / fh->jpg_buffers.buffer_size; + if (offset % fh->jpg_buffers.buffer_size != 0 || + size % fh->jpg_buffers.buffer_size != 0 || first < 0 || + last < 0 || first >= fh->jpg_buffers.num_buffers || + last >= fh->jpg_buffers.num_buffers) { + dprintk(1, + KERN_ERR + "%s: mmap(MJPEG) - offset=%lu or size=%lu invalid for bufsize=%d and numbufs=%d\n", + ZR_DEVNAME(zr), offset, size, + fh->jpg_buffers.buffer_size, + fh->jpg_buffers.num_buffers); + res = -EINVAL; + goto jpg_mmap_unlock_and_return; + } + for (i = first; i <= last; i++) { + if (fh->jpg_buffers.buffer[i].map) { + dprintk(1, + KERN_ERR + "%s: mmap(MJPEG) - buffer %d already mapped\n", + ZR_DEVNAME(zr), i); + res = -EBUSY; + goto jpg_mmap_unlock_and_return; + } + } + + /* map these buffers (v4l_buffers[i]) */ + map = kmalloc(sizeof(struct zoran_mapping), GFP_KERNEL); + if (!map) { + res = -ENOMEM; + goto jpg_mmap_unlock_and_return; + } + map->file = file; + map->count = 1; + + vma->vm_ops = &zoran_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + for (i = first; i <= last; i++) { + for (j = 0; + j < fh->jpg_buffers.buffer_size / PAGE_SIZE; + j++) { + fraglen = + (le32_to_cpu(fh->jpg_buffers.buffer[i]. + frag_tab[2 * j + 1]) & ~1) << 1; + todo = size; + if (todo > fraglen) + todo = fraglen; + pos = + le32_to_cpu((unsigned long) fh->jpg_buffers. + buffer[i].frag_tab[2 * j]); + /* should just be pos on i386 */ + page = virt_to_phys(bus_to_virt(pos)) + >> PAGE_SHIFT; + if (remap_pfn_range(vma, start, page, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L) - remap_pfn_range failed\n", + ZR_DEVNAME(zr)); + res = -EAGAIN; + goto jpg_mmap_unlock_and_return; + } + size -= todo; + start += todo; + if (size == 0) + break; + if (le32_to_cpu(fh->jpg_buffers.buffer[i]. + frag_tab[2 * j + 1]) & 1) + break; /* was last fragment */ + } + fh->jpg_buffers.buffer[i].map = map; + if (size == 0) + break; + + } + jpg_mmap_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + break; + + case ZORAN_MAP_MODE_RAW: + + mutex_lock(&zr->resource_lock); + + /* Map the V4L buffers */ + if (!fh->v4l_buffers.allocated) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L) - buffers not yet allocated\n", + ZR_DEVNAME(zr)); + res = -ENOMEM; + goto v4l_mmap_unlock_and_return; + } + + first = offset / fh->v4l_buffers.buffer_size; + last = first - 1 + size / fh->v4l_buffers.buffer_size; + if (offset % fh->v4l_buffers.buffer_size != 0 || + size % fh->v4l_buffers.buffer_size != 0 || first < 0 || + last < 0 || first >= fh->v4l_buffers.num_buffers || + last >= fh->v4l_buffers.buffer_size) { + dprintk(1, + KERN_ERR + "%s: mmap(V4L) - offset=%lu or size=%lu invalid for bufsize=%d and numbufs=%d\n", + ZR_DEVNAME(zr), offset, size, + fh->v4l_buffers.buffer_size, + fh->v4l_buffers.num_buffers); + res = -EINVAL; + goto v4l_mmap_unlock_and_return; + } + for (i = first; i <= last; i++) { + if (fh->v4l_buffers.buffer[i].map) { + dprintk(1, + KERN_ERR + "%s: mmap(V4L) - buffer %d already mapped\n", + ZR_DEVNAME(zr), i); + res = -EBUSY; + goto v4l_mmap_unlock_and_return; + } + } + + /* map these buffers (v4l_buffers[i]) */ + map = kmalloc(sizeof(struct zoran_mapping), GFP_KERNEL); + if (!map) { + res = -ENOMEM; + goto v4l_mmap_unlock_and_return; + } + map->file = file; + map->count = 1; + + vma->vm_ops = &zoran_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + for (i = first; i <= last; i++) { + todo = size; + if (todo > fh->v4l_buffers.buffer_size) + todo = fh->v4l_buffers.buffer_size; + page = fh->v4l_buffers.buffer[i].fbuffer_phys; + if (remap_pfn_range(vma, start, page >> PAGE_SHIFT, + todo, PAGE_SHARED)) { + dprintk(1, + KERN_ERR + "%s: zoran_mmap(V4L)i - remap_pfn_range failed\n", + ZR_DEVNAME(zr)); + res = -EAGAIN; + goto v4l_mmap_unlock_and_return; + } + size -= todo; + start += todo; + fh->v4l_buffers.buffer[i].map = map; + if (size == 0) + break; + } + v4l_mmap_unlock_and_return: + mutex_unlock(&zr->resource_lock); + + break; + + default: + dprintk(1, + KERN_ERR + "%s: zoran_mmap() - internal error - unknown map mode %d\n", + ZR_DEVNAME(zr), fh->map_mode); + break; + } + + return 0; +} + +static struct file_operations zoran_fops = { + .owner = THIS_MODULE, + .open = zoran_open, + .release = zoran_close, + .ioctl = zoran_ioctl, + .compat_ioctl = v4l_compat_ioctl32, + .llseek = no_llseek, + .read = zoran_read, + .write = zoran_write, + .mmap = zoran_mmap, + .poll = zoran_poll, +}; + +struct video_device zoran_template __devinitdata = { + .name = ZORAN_NAME, + .type = ZORAN_VID_TYPE, +#ifdef HAVE_V4L2 + .type2 = ZORAN_V4L2_VID_FLAGS, +#endif + .hardware = ZORAN_HARDWARE, + .fops = &zoran_fops, + .release = &zoran_vdev_release, + .minor = -1 +}; + diff --git a/linux/drivers/media/video/zoran_procfs.c b/linux/drivers/media/video/zoran_procfs.c new file mode 100644 index 000000000..3472c00d4 --- /dev/null +++ b/linux/drivers/media/video/zoran_procfs.c @@ -0,0 +1,234 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles the procFS entries (/proc/ZORAN[%d]) + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/vmalloc.h> + +#include <linux/proc_fs.h> +#include <linux/pci.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include "compat.h" +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/sem.h> +#include <linux/seq_file.h> + +#include <linux/ctype.h> +#include <asm/io.h> + +#include "videocodec.h" +#include "zoran.h" +#include "zoran_procfs.h" + +extern int *zr_debug; + +#define dprintk(num, format, args...) \ + do { \ + if (*zr_debug >= num) \ + printk(format, ##args); \ + } while (0) + +#ifdef CONFIG_PROC_FS +struct procfs_params_zr36067 { + char *name; + short reg; + u32 mask; + short bit; +}; + +static const struct procfs_params_zr36067 zr67[] = { + {"HSPol", 0x000, 1, 30}, + {"HStart", 0x000, 0x3ff, 10}, + {"HEnd", 0x000, 0x3ff, 0}, + + {"VSPol", 0x004, 1, 30}, + {"VStart", 0x004, 0x3ff, 10}, + {"VEnd", 0x004, 0x3ff, 0}, + + {"ExtFl", 0x008, 1, 26}, + {"TopField", 0x008, 1, 25}, + {"VCLKPol", 0x008, 1, 24}, + {"DupFld", 0x008, 1, 20}, + {"LittleEndian", 0x008, 1, 0}, + + {"HsyncStart", 0x10c, 0xffff, 16}, + {"LineTot", 0x10c, 0xffff, 0}, + + {"NAX", 0x110, 0xffff, 16}, + {"PAX", 0x110, 0xffff, 0}, + + {"NAY", 0x114, 0xffff, 16}, + {"PAY", 0x114, 0xffff, 0}, + + /* {"",,,}, */ + + {NULL, 0, 0, 0}, +}; + +static void +setparam (struct zoran *zr, + char *name, + char *sval) +{ + int i = 0, reg0, reg, val; + + while (zr67[i].name != NULL) { + if (!strncmp(name, zr67[i].name, strlen(zr67[i].name))) { + reg = reg0 = btread(zr67[i].reg); + reg &= ~(zr67[i].mask << zr67[i].bit); + if (!isdigit(sval[0])) + break; + val = simple_strtoul(sval, NULL, 0); + if ((val & ~zr67[i].mask)) + break; + reg |= (val & zr67[i].mask) << zr67[i].bit; + dprintk(4, + KERN_INFO + "%s: setparam: setting ZR36067 register 0x%03x: 0x%08x=>0x%08x %s=%d\n", + ZR_DEVNAME(zr), zr67[i].reg, reg0, reg, + zr67[i].name, val); + btwrite(reg, zr67[i].reg); + break; + } + i++; + } +} + +static int zoran_show(struct seq_file *p, void *v) +{ + struct zoran *zr = p->private; + int i; + + seq_printf(p, "ZR36067 registers:\n"); + for (i = 0; i < 0x130; i += 16) + seq_printf(p, "%03X %08X %08X %08X %08X \n", i, + btread(i), btread(i+4), btread(i+8), btread(i+12)); + return 0; +} + +static int zoran_open(struct inode *inode, struct file *file) +{ + struct zoran *data = PDE(inode)->data; + return single_open(file, zoran_show, data); +} + +static ssize_t zoran_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct zoran *zr = PDE(file->f_dentry->d_inode)->data; + char *string, *sp; + char *line, *ldelim, *varname, *svar, *tdelim; + + if (count > 32768) /* Stupidity filter */ + return -EINVAL; + + string = sp = vmalloc(count + 1); + if (!string) { + dprintk(1, + KERN_ERR + "%s: write_proc: can not allocate memory\n", + ZR_DEVNAME(zr)); + return -ENOMEM; + } + if (copy_from_user(string, buffer, count)) { + vfree (string); + return -EFAULT; + } + string[count] = 0; + dprintk(4, KERN_INFO "%s: write_proc: name=%s count=%zu zr=%p\n", + ZR_DEVNAME(zr), file->f_dentry->d_name.name, count, zr); + ldelim = " \t\n"; + tdelim = "="; + line = strpbrk(sp, ldelim); + while (line) { + *line = 0; + svar = strpbrk(sp, tdelim); + if (svar) { + *svar = 0; + varname = sp; + svar++; + setparam(zr, varname, svar); + } + sp = line + 1; + line = strpbrk(sp, ldelim); + } + vfree(string); + + return count; +} + +static struct file_operations zoran_operations = { + .open = zoran_open, + .read = seq_read, + .write = zoran_write, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +int +zoran_proc_init (struct zoran *zr) +{ +#ifdef CONFIG_PROC_FS + char name[8]; + + snprintf(name, 7, "zoran%d", zr->id); + if ((zr->zoran_proc = create_proc_entry(name, 0, NULL))) { + zr->zoran_proc->data = zr; + zr->zoran_proc->owner = THIS_MODULE; + zr->zoran_proc->proc_fops = &zoran_operations; + dprintk(2, + KERN_INFO + "%s: procfs entry /proc/%s allocated. data=%p\n", + ZR_DEVNAME(zr), name, zr->zoran_proc->data); + } else { + dprintk(1, KERN_ERR "%s: Unable to initialise /proc/%s\n", + ZR_DEVNAME(zr), name); + return 1; + } +#endif + return 0; +} + +void +zoran_proc_cleanup (struct zoran *zr) +{ +#ifdef CONFIG_PROC_FS + char name[8]; + + snprintf(name, 7, "zoran%d", zr->id); + if (zr->zoran_proc) + remove_proc_entry(name, NULL); + zr->zoran_proc = NULL; +#endif +} diff --git a/linux/drivers/media/video/zoran_procfs.h b/linux/drivers/media/video/zoran_procfs.h new file mode 100644 index 000000000..f2d5b1ba4 --- /dev/null +++ b/linux/drivers/media/video/zoran_procfs.h @@ -0,0 +1,36 @@ +/* + * Zoran zr36057/zr36067 PCI controller driver, for the + * Pinnacle/Miro DC10/DC10+/DC30/DC30+, Iomega Buz, Linux + * Media Labs LML33/LML33R10. + * + * This part handles card-specific data and detection + * + * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx> + * + * Currently maintained by: + * Ronald Bultje <rbultje@ronald.bitfreak.net> + * Laurent Pinchart <laurent.pinchart@skynet.be> + * Mailinglist <mjpeg-users@lists.sf.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 __ZORAN_PROCFS_H__ +#define __ZORAN_PROCFS_H__ + +extern int zoran_proc_init(struct zoran *zr); +extern void zoran_proc_cleanup(struct zoran *zr); + +#endif /* __ZORAN_PROCFS_H__ */ diff --git a/linux/drivers/media/video/zr36016.c b/linux/drivers/media/video/zr36016.c new file mode 100644 index 000000000..7f958d803 --- /dev/null +++ b/linux/drivers/media/video/zr36016.c @@ -0,0 +1,531 @@ +/* + * Zoran ZR36016 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36016.c,v 1.1.2.14 2003/08/20 19:46:55 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 ZR016_VERSION "v0.7" + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* v4l API */ +#include "compat.h" +#include<linux/videodev.h> + +/* headerfile of this module */ +#include"zr36016.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36016_codecs = 0; + +/* debugging is available via module parameter */ + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36016_read (struct zr36016 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = + (ptr->codec->master_data-> + readreg(ptr->codec, reg)) & 0xFF; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + dprintk(4, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, + value); + + return value; +} + +static void +zr36016_write (struct zr36016 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, + reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) { + ptr->codec->master_data->writereg(ptr->codec, reg, value); + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* indirect read and write functions */ +/* the 016 supports auto-addr-increment, but + * writing it all time cost not much and is safer... */ +static u8 +zr36016_readi (struct zr36016 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if ((ptr->codec->master_data->writereg) && + (ptr->codec->master_data->readreg)) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); // ADDR + value = (ptr->codec->master_data->readreg(ptr->codec, ZR016_IDATA)) & 0xFF; // DATA + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing read (i)!\n", + ptr->name); + + dprintk(4, "%s: reading indirect from 0x%04x: %02x\n", ptr->name, + reg, value); + return value; +} + +static void +zr36016_writei (struct zr36016 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing indirect 0x%02x to 0x%04x\n", ptr->name, + value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) { + ptr->codec->master_data->writereg(ptr->codec, ZR016_IADDR, reg & 0x0F); // ADDR + ptr->codec->master_data->writereg(ptr->codec, ZR016_IDATA, value & 0x0FF); // DATA + } else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written (i)!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + version read + ========================================================================= */ + +/* version kept in datastructure */ +static u8 +zr36016_read_version (struct zr36016 *ptr) +{ + ptr->version = zr36016_read(ptr, 0) >> 4; + return ptr->version; +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from PAX-Lo register + ========================================================================= */ + +static int +zr36016_basic_test (struct zr36016 *ptr) +{ + if (debug) { + int i; + zr36016_writei(ptr, ZR016I_PAX_LO, 0x55); + dprintk(1, KERN_INFO "%s: registers: ", ptr->name); + for (i = 0; i <= 0x0b; i++) + dprintk(1, "%02x ", zr36016_readi(ptr, i)); + dprintk(1, "\n"); + } + // for testing just write 0, then the default value to a register and read + // it back in both cases + zr36016_writei(ptr, ZR016I_PAX_LO, 0x00); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to vfe processor!\n", + ptr->name); + return -ENXIO; + } + zr36016_writei(ptr, ZR016I_PAX_LO, 0x0d0); + if (zr36016_readi(ptr, ZR016I_PAX_LO) != 0x0d0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to vfe processor!\n", + ptr->name); + return -ENXIO; + } + // we allow version numbers from 0-3, should be enough, though + zr36016_read_version(ptr); + if (ptr->version & 0x0c) { + dprintk(1, + KERN_ERR + "%s: attach failed, suspicious version %d found...\n", + ptr->name, ptr->version); + return -ENXIO; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets - NO USE -- + ========================================================================= */ + +#if 0 +static int zr36016_pushit (struct zr36016 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i=0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", + ptr->name, startreg,len); + while (i<len) { + zr36016_writei(ptr, startreg++, data[i++]); + } + + return i; +} +#endif + +/* ========================================================================= + Basic datasets & init: + + //TODO// + ========================================================================= */ + +// needed offset values PAL NTSC SECAM +static const int zr016_xoff[] = { 20, 20, 20 }; +static const int zr016_yoff[] = { 8, 9, 7 }; + +static void +zr36016_init (struct zr36016 *ptr) +{ + // stop any processing + zr36016_write(ptr, ZR016_GOSTOP, 0); + + // mode setup (yuv422 in and out, compression/expansuon due to mode) + zr36016_write(ptr, ZR016_MODE, + ZR016_YUV422 | ZR016_YUV422_YUV422 | + (ptr->mode == CODEC_DO_COMPRESSION ? + ZR016_COMPRESSION : ZR016_EXPANSION)); + + // misc setup + zr36016_writei(ptr, ZR016I_SETUP1, + (ptr->xdec ? (ZR016_HRFL | ZR016_HORZ) : 0) | + (ptr->ydec ? ZR016_VERT : 0) | ZR016_CNTI); + zr36016_writei(ptr, ZR016I_SETUP2, ZR016_CCIR); + + // Window setup + // (no extra offset for now, norm defines offset, default width height) + zr36016_writei(ptr, ZR016I_PAX_HI, ptr->width >> 8); + zr36016_writei(ptr, ZR016I_PAX_LO, ptr->width & 0xFF); + zr36016_writei(ptr, ZR016I_PAY_HI, ptr->height >> 8); + zr36016_writei(ptr, ZR016I_PAY_LO, ptr->height & 0xFF); + zr36016_writei(ptr, ZR016I_NAX_HI, ptr->xoff >> 8); + zr36016_writei(ptr, ZR016I_NAX_LO, ptr->xoff & 0xFF); + zr36016_writei(ptr, ZR016I_NAY_HI, ptr->yoff >> 8); + zr36016_writei(ptr, ZR016I_NAY_LO, ptr->yoff & 0xFF); + + /* shall we continue now, please? */ + zr36016_write(ptr, ZR016_GOSTOP, 1); +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36016_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36016_init(ptr); + + return 0; +} + +/* set picture size */ +static int +zr36016_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + + dprintk(2, "%s: set_video %d.%d, %d/%d-%dx%d (0x%x) call\n", + ptr->name, norm->HStart, norm->VStart, + cap->x, cap->y, cap->width, cap->height, + cap->decimation); + + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y for now ... */ + ptr->width = cap->width; + ptr->height = cap->height; + /* (Ronald) This is ugly. zoran_device.c, line 387 + * already mentions what happens if HStart is even + * (blue faces, etc., cr/cb inversed). There's probably + * some good reason why HStart is 0 instead of 1, so I'm + * leaving it to this for now, but really... This can be + * done a lot simpler */ + ptr->xoff = (norm->HStart ? norm->HStart : 1) + cap->x; + /* Something to note here (I don't understand it), setting + * VStart too high will cause the codec to 'not work'. I + * really don't get it. values of 16 (VStart) already break + * it here. Just '0' seems to work. More testing needed! */ + ptr->yoff = norm->VStart + cap->y; + /* (Ronald) dzjeeh, can't this thing do hor_decimation = 4? */ + ptr->xdec = ((cap->decimation & 0xff) == 1) ? 0 : 1; + ptr->ydec = (((cap->decimation >> 8) & 0xff) == 1) ? 0 : 1; + + return 0; +} + +/* additional control functions */ +static int +zr36016_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36016 *ptr = (struct zr36016 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status - we don't know it ... */ + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = 0; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != 0) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36016_unset (struct videocodec *codec) +{ + struct zr36016 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36016_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36016_setup (struct videocodec *codec) +{ + struct zr36016 *ptr; + int res; + + dprintk(2, "zr36016: initializing VFE subsystem #%d.\n", + zr36016_codecs); + + if (zr36016_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36016: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kzalloc(sizeof(struct zr36016), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36016: Can't get enough memory!\n"); + return -ENOMEM; + } + + snprintf(ptr->name, sizeof(ptr->name), "zr36016[%d]", + zr36016_codecs); + ptr->num = zr36016_codecs++; + ptr->codec = codec; + + //testing + res = zr36016_basic_test(ptr); + if (res < 0) { + zr36016_unset(codec); + return res; + } + //final setup + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 768; + ptr->height = 288; + ptr->xdec = 1; + ptr->ydec = 0; + zr36016_init(ptr); + + dprintk(1, KERN_INFO "%s: codec v%d attached and running\n", + ptr->name, ptr->version); + + return 0; +} + +static const struct videocodec zr36016_codec = { + .owner = THIS_MODULE, + .name = "zr36016", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_HARDWARE | CODEC_FLAG_VFE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36016, + .setup = zr36016_setup, // functionality + .unset = zr36016_unset, + .set_mode = zr36016_set_mode, + .set_video = zr36016_set_video, + .control = zr36016_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36016_init_module (void) +{ + //dprintk(1, "ZR36016 driver %s\n",ZR016_VERSION); + zr36016_codecs = 0; + return videocodec_register(&zr36016_codec); +} + +static void __exit +zr36016_cleanup_module (void) +{ + if (zr36016_codecs) { + dprintk(1, + "zr36016: something's wrong - %d codecs left somehow.\n", + zr36016_codecs); + } + videocodec_unregister(&zr36016_codec); +} + +module_init(zr36016_init_module); +module_exit(zr36016_cleanup_module); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Driver module for ZR36016 video frontends " + ZR016_VERSION); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/zr36016.h b/linux/drivers/media/video/zr36016.h new file mode 100644 index 000000000..8c79229f6 --- /dev/null +++ b/linux/drivers/media/video/zr36016.h @@ -0,0 +1,111 @@ +/* + * Zoran ZR36016 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36016.h,v 1.1.2.3 2003/01/14 21:18:07 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 ZR36016_H +#define ZR36016_H + +/* data stored for each zoran jpeg codec chip */ +struct zr36016 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // coder status + __u8 version; + // actual coder setup + int mode; + + __u16 xoff; + __u16 yoff; + __u16 width; + __u16 height; + __u16 xdec; + __u16 ydec; +}; + +/* direct register addresses */ +#define ZR016_GOSTOP 0x00 +#define ZR016_MODE 0x01 +#define ZR016_IADDR 0x02 +#define ZR016_IDATA 0x03 + +/* indirect register addresses */ +#define ZR016I_SETUP1 0x00 +#define ZR016I_SETUP2 0x01 +#define ZR016I_NAX_LO 0x02 +#define ZR016I_NAX_HI 0x03 +#define ZR016I_PAX_LO 0x04 +#define ZR016I_PAX_HI 0x05 +#define ZR016I_NAY_LO 0x06 +#define ZR016I_NAY_HI 0x07 +#define ZR016I_PAY_LO 0x08 +#define ZR016I_PAY_HI 0x09 +#define ZR016I_NOL_LO 0x0a +#define ZR016I_NOL_HI 0x0b + +/* possible values for mode register */ +#define ZR016_RGB444_YUV444 0x00 +#define ZR016_RGB444_YUV422 0x01 +#define ZR016_RGB444_YUV411 0x02 +#define ZR016_RGB444_Y400 0x03 +#define ZR016_RGB444_RGB444 0x04 +#define ZR016_YUV444_YUV444 0x08 +#define ZR016_YUV444_YUV422 0x09 +#define ZR016_YUV444_YUV411 0x0a +#define ZR016_YUV444_Y400 0x0b +#define ZR016_YUV444_RGB444 0x0c +#define ZR016_YUV422_YUV422 0x11 +#define ZR016_YUV422_YUV411 0x12 +#define ZR016_YUV422_Y400 0x13 +#define ZR016_YUV411_YUV411 0x16 +#define ZR016_YUV411_Y400 0x17 +#define ZR016_4444_4444 0x19 +#define ZR016_100_100 0x1b + +#define ZR016_RGB444 0x00 +#define ZR016_YUV444 0x20 +#define ZR016_YUV422 0x40 + +#define ZR016_COMPRESSION 0x80 +#define ZR016_EXPANSION 0x80 + +/* possible values for setup 1 register */ +#define ZR016_CKRT 0x80 +#define ZR016_VERT 0x40 +#define ZR016_HORZ 0x20 +#define ZR016_HRFL 0x10 +#define ZR016_DSFL 0x08 +#define ZR016_SBFL 0x04 +#define ZR016_RSTR 0x02 +#define ZR016_CNTI 0x01 + +/* possible values for setup 2 register */ +#define ZR016_SYEN 0x40 +#define ZR016_CCIR 0x04 +#define ZR016_SIGN 0x02 +#define ZR016_YMCS 0x01 + +#endif /*fndef ZR36016_H */ diff --git a/linux/drivers/media/video/zr36050.c b/linux/drivers/media/video/zr36050.c new file mode 100644 index 000000000..a6bbd1256 --- /dev/null +++ b/linux/drivers/media/video/zr36050.c @@ -0,0 +1,905 @@ +/* + * Zoran ZR36050 basic configuration functions + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36050.c,v 1.1.2.11 2003/08/03 14:54:53 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 ZR050_VERSION "v0.7.1" + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* headerfile of this module */ +#include"zr36050.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36050_codecs = 0; + +/* debugging is available via module parameter */ + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36050_read (struct zr36050 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, + reg)) & 0xFF; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + dprintk(4, "%s: reading from 0x%04x: %02x\n", ptr->name, reg, + value); + + return value; +} + +static void +zr36050_write (struct zr36050 *ptr, + u16 reg, + u8 value) +{ + dprintk(4, "%s: writing 0x%02x to 0x%04x\n", ptr->name, value, + reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + status read + ========================================================================= */ + +/* status is kept in datastructure */ +static u8 +zr36050_read_status1 (struct zr36050 *ptr) +{ + ptr->status1 = zr36050_read(ptr, ZR050_STATUS_1); + + zr36050_read(ptr, 0); + return ptr->status1; +} + +/* ========================================================================= + Local helper function: + + scale factor read + ========================================================================= */ + +/* scale factor is kept in datastructure */ +static u16 +zr36050_read_scalefactor (struct zr36050 *ptr) +{ + ptr->scalefact = (zr36050_read(ptr, ZR050_SF_HI) << 8) | + (zr36050_read(ptr, ZR050_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36050_read(ptr, 0); + return ptr->scalefact; +} + +/* ========================================================================= + Local helper function: + + wait if codec is ready to proceed (end of processing) or time is over + ========================================================================= */ + +static void +zr36050_wait_end (struct zr36050 *ptr) +{ + int i = 0; + + while (!(zr36050_read_status1(ptr) & 0x4)) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for sure something wrong!!! + dprintk(1, + "%s: timout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status1); + break; + } + } +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from memory the SOF marker + ========================================================================= */ + +static int +zr36050_basic_test (struct zr36050 *ptr) +{ + zr36050_write(ptr, ZR050_SOF_IDX, 0x00); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0x00); + if ((zr36050_read(ptr, ZR050_SOF_IDX) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0x0000) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + zr36050_write(ptr, ZR050_SOF_IDX, 0xff); + zr36050_write(ptr, ZR050_SOF_IDX + 1, 0xc0); + if (((zr36050_read(ptr, ZR050_SOF_IDX) << 8) | + zr36050_read(ptr, ZR050_SOF_IDX + 1)) != 0xffc0) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + + zr36050_wait_end(ptr); + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, + KERN_ERR + "%s: attach failed, jpeg processor failed (end flag)!\n", + ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets + ========================================================================= */ + +static int +zr36050_pushit (struct zr36050 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i = 0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) { + zr36050_write(ptr, startreg++, data[i++]); + } + + return i; +} + +/* ========================================================================= + Basic datasets: + + jpeg baseline setup data (you find it on lots places in internet, or just + extract it from any regular .jpg image...) + + Could be variable, but until it's not needed it they are just fixed to save + memory. Otherwise expand zr36050 structure with arrays, push the values to + it and initalize from there, as e.g. the linux zr36057/60 driver does it. + ========================================================================= */ + +static const char zr36050_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 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, //Pq,Tq second table + 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 +}; + +static const char zr36050_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x01, //DC second table + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, //AC first table + 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, //AC second table + 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 +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36050_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36050_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36050_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36050_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36050_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* ========================================================================= + Local helper functions: + + calculation and setup of parameter-dependent JPEG baseline segments + (needed for compression only) + ========================================================================= */ + +/* ------------------------------------------------------------------------- */ + +/* SOF (start of frame) segment depends on width, height and sampling ratio + of each color component */ + +static int +zr36050_set_sof (struct zr36050 *ptr) +{ + char sof_data[34]; // max. size of register set + int i; + + dprintk(3, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36050 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36050_tq[i]; // Q table selection + } + return zr36050_pushit(ptr, ZR050_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* ------------------------------------------------------------------------- */ + +/* SOS (start of scan) segment depends on the used scan components + of each color component */ + +static int +zr36050_set_sos (struct zr36050 *ptr) +{ + char sos_data[16]; // max. size of register set + int i; + + dprintk(3, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36050_td[i] << 4) | zr36050_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3F; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36050_pushit(ptr, ZR050_SOS1_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* ------------------------------------------------------------------------- */ + +/* DRI (define restart interval) */ + +static int +zr36050_set_dri (struct zr36050 *ptr) +{ + char dri_data[6]; // max. size of register set + + dprintk(3, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = ptr->dri >> 8; + dri_data[5] = ptr->dri & 0xff; + return zr36050_pushit(ptr, ZR050_DRI_IDX, 6, dri_data); +} + +/* ========================================================================= + Setup function: + + Setup compression/decompression of Zoran's JPEG processor + ( see also zoran 36050 manual ) + + ... sorry for the spaghetti code ... + ========================================================================= */ +static void +zr36050_init (struct zr36050 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + + if (ptr->mode == CODEC_DO_COMPRESSION) { + dprintk(2, "%s: COMPRESSION SETUP\n", ptr->name); + + /* 050 communicates with 057 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, ZR050_HW_MSTR); + + /* encoding table preload for compression */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_TLM); + zr36050_write(ptr, ZR050_OPTIONS, 0); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + /* volume control settings */ + /*zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol);*/ + zr36050_write(ptr, ZR050_SF_HI, ptr->scalefact >> 8); + zr36050_write(ptr, ZR050_SF_LO, ptr->scalefact & 0xff); + + zr36050_write(ptr, ZR050_AF_HI, 0xff); + zr36050_write(ptr, ZR050_AF_M, 0xff); + zr36050_write(ptr, ZR050_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36050_set_sof(ptr); + sum += zr36050_set_sos(ptr); + sum += zr36050_set_dri(ptr); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + dprintk(3, "%s: write DQT, DHT, APP\n", ptr->name); + sum += zr36050_pushit(ptr, ZR050_DQT_IDX, + sizeof(zr36050_dqt), zr36050_dqt); + sum += zr36050_pushit(ptr, ZR050_DHT_IDX, + sizeof(zr36050_dht), zr36050_dht); + zr36050_write(ptr, ZR050_APP_IDX, 0xff); + zr36050_write(ptr, ZR050_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36050_write(ptr, ZR050_APP_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_APP_IDX + 3, ptr->app.len + 2); + sum += zr36050_pushit(ptr, ZR050_APP_IDX + 4, 60, + ptr->app.data) + 4; + zr36050_write(ptr, ZR050_COM_IDX, 0xff); + zr36050_write(ptr, ZR050_COM_IDX + 1, 0xfe); + zr36050_write(ptr, ZR050_COM_IDX + 2, 0x00); + zr36050_write(ptr, ZR050_COM_IDX + 3, ptr->com.len + 2); + sum += zr36050_pushit(ptr, ZR050_COM_IDX + 4, 60, + ptr->com.data) + 4; + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, KERN_ERR "%s: init aborted!\n", + ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + dprintk(3, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_NET_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_NET_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + dprintk(3, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36050_write(ptr, ZR050_TCV_DATA_HI, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36050_write(ptr, ZR050_TCV_DATA_ML, tmp >> 8); + zr36050_write(ptr, ZR050_TCV_DATA_LO, tmp & 0xff); + + /* compression setup with or without bitrate control */ + zr36050_write(ptr, ZR050_MODE, + ZR050_MO_COMP | ZR050_MO_PASS2 | + (ptr->bitrate_ctrl ? ZR050_MO_BRC : 0)); + + /* this headers seem to deliver "valid AVI" jpeg frames */ + zr36050_write(ptr, ZR050_MARKERS_EN, + ZR050_ME_DQT | ZR050_ME_DHT | + ((ptr->app.len > 0) ? ZR050_ME_APP : 0) | + ((ptr->com.len > 0) ? ZR050_ME_COM : 0)); + } else { + dprintk(2, "%s: EXPANSION SETUP\n", ptr->name); + + /* 050 communicates with 055 in master mode */ + zr36050_write(ptr, ZR050_HARDWARE, + ZR050_HW_MSTR | ZR050_HW_CFIS_2_CLK); + + /* encoding table preload */ + zr36050_write(ptr, ZR050_MODE, ZR050_MO_TLM); + + /* disable all IRQs */ + zr36050_write(ptr, ZR050_INT_REQ_0, 0); + zr36050_write(ptr, ZR050_INT_REQ_1, 3); // low 2 bits always 1 + + dprintk(3, "%s: write DHT\n", ptr->name); + zr36050_pushit(ptr, ZR050_DHT_IDX, sizeof(zr36050_dht), + zr36050_dht); + + /* do the internal huffman table preload */ + zr36050_write(ptr, ZR050_MARKERS_EN, ZR050_ME_DHTI); + + zr36050_write(ptr, ZR050_GO, 1); // launch codec + zr36050_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", + ptr->name, ptr->status1); + + if ((ptr->status1 & 0x4) == 0) { + dprintk(1, KERN_ERR "%s: init aborted!\n", + ptr->name); + return; // something is wrong, its timed out!!!! + } + + /* setup misc. data for expansion */ + zr36050_write(ptr, ZR050_MODE, 0); + zr36050_write(ptr, ZR050_MARKERS_EN, 0); + } + + /* adr on selected, to allow GO from master */ + zr36050_read(ptr, 0); +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36050_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36050_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int +zr36050_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + int size; + + dprintk(2, "%s: set_video %d.%d, %d/%d-%dx%d (0x%x) q%d call\n", + ptr->name, norm->HStart, norm->VStart, + cap->x, cap->y, cap->width, cap->height, + cap->decimation, cap->quality); + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / ((cap->decimation >> 8) & 0xff); + + /* (KM) JPEG quality */ + size = ptr->width * ptr->height; + size *= 16; /* size in bits */ + /* apply quality setting */ + size = size * cap->quality / 200; + + /* Minimum: 1kb */ + if (size < 8192) + size = 8192; + /* Maximum: 7/8 of code buffer */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* Set max_block_vol here (previously in zr36050_init, moved + * here for consistency with zr36060 code */ + zr36050_write(ptr, ZR050_MBCV, ptr->max_block_vol); + + return 0; +} + +/* additional control functions */ +static int +zr36050_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36050 *ptr = (struct zr36050 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36050_read_status1(ptr); + *ival = ptr->status1; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + /* (Kieran Morrissey) + * code copied from zr36060.c to ensure proper bitrate */ + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36050_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36050_unset (struct videocodec *codec) +{ + struct zr36050 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36050_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36050_setup (struct videocodec *codec) +{ + struct zr36050 *ptr; + int res; + + dprintk(2, "zr36050: initializing MJPEG subsystem #%d.\n", + zr36050_codecs); + + if (zr36050_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36050: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kzalloc(sizeof(struct zr36050), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36050: Can't get enough memory!\n"); + return -ENOMEM; + } + + snprintf(ptr->name, sizeof(ptr->name), "zr36050[%d]", + zr36050_codecs); + ptr->num = zr36050_codecs++; + ptr->codec = codec; + + //testing + res = zr36050_basic_test(ptr); + if (res < 0) { + zr36050_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36050_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36050_decimation_v, 8); + + ptr->bitrate_ctrl = 0; /* 0 or 1 - fixed file size flag + * (what is the difference?) */ + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; + ptr->max_block_vol = 240; + ptr->scalefact = 0x100; + ptr->dri = 1; + + /* no app/com marker by default */ + ptr->app.appn = 0; + ptr->app.len = 0; + ptr->com.len = 0; + + zr36050_init(ptr); + + dprintk(1, KERN_INFO "%s: codec attached and running\n", + ptr->name); + + return 0; +} + +static const struct videocodec zr36050_codec = { + .owner = THIS_MODULE, + .name = "zr36050", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER, + .type = CODEC_TYPE_ZR36050, + .setup = zr36050_setup, // functionality + .unset = zr36050_unset, + .set_mode = zr36050_set_mode, + .set_video = zr36050_set_video, + .control = zr36050_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36050_init_module (void) +{ + //dprintk(1, "ZR36050 driver %s\n",ZR050_VERSION); + zr36050_codecs = 0; + return videocodec_register(&zr36050_codec); +} + +static void __exit +zr36050_cleanup_module (void) +{ + if (zr36050_codecs) { + dprintk(1, + "zr36050: something's wrong - %d codecs left somehow.\n", + zr36050_codecs); + } + videocodec_unregister(&zr36050_codec); +} + +module_init(zr36050_init_module); +module_exit(zr36050_cleanup_module); + +MODULE_AUTHOR("Wolfgang Scherr <scherr@net4you.at>"); +MODULE_DESCRIPTION("Driver module for ZR36050 jpeg processors " + ZR050_VERSION); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/zr36050.h b/linux/drivers/media/video/zr36050.h new file mode 100644 index 000000000..9f52f0cdd --- /dev/null +++ b/linux/drivers/media/video/zr36050.h @@ -0,0 +1,184 @@ +/* + * Zoran ZR36050 basic configuration functions - header file + * + * Copyright (C) 2001 Wolfgang Scherr <scherr@net4you.at> + * + * $Id: zr36050.h,v 1.1.2.2 2003/01/14 21:18:22 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 ZR36050_H +#define ZR36050_H + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36050 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status1; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* com/app marker */ + struct jpeg_com_marker com; + struct jpeg_app_marker app; +}; + +/* zr36050 register addresses */ +#define ZR050_GO 0x000 +#define ZR050_HARDWARE 0x002 +#define ZR050_MODE 0x003 +#define ZR050_OPTIONS 0x004 +#define ZR050_MBCV 0x005 +#define ZR050_MARKERS_EN 0x006 +#define ZR050_INT_REQ_0 0x007 +#define ZR050_INT_REQ_1 0x008 +#define ZR050_TCV_NET_HI 0x009 +#define ZR050_TCV_NET_MH 0x00a +#define ZR050_TCV_NET_ML 0x00b +#define ZR050_TCV_NET_LO 0x00c +#define ZR050_TCV_DATA_HI 0x00d +#define ZR050_TCV_DATA_MH 0x00e +#define ZR050_TCV_DATA_ML 0x00f +#define ZR050_TCV_DATA_LO 0x010 +#define ZR050_SF_HI 0x011 +#define ZR050_SF_LO 0x012 +#define ZR050_AF_HI 0x013 +#define ZR050_AF_M 0x014 +#define ZR050_AF_LO 0x015 +#define ZR050_ACV_HI 0x016 +#define ZR050_ACV_MH 0x017 +#define ZR050_ACV_ML 0x018 +#define ZR050_ACV_LO 0x019 +#define ZR050_ACT_HI 0x01a +#define ZR050_ACT_MH 0x01b +#define ZR050_ACT_ML 0x01c +#define ZR050_ACT_LO 0x01d +#define ZR050_ACV_TRUN_HI 0x01e +#define ZR050_ACV_TRUN_MH 0x01f +#define ZR050_ACV_TRUN_ML 0x020 +#define ZR050_ACV_TRUN_LO 0x021 +#define ZR050_STATUS_0 0x02e +#define ZR050_STATUS_1 0x02f + +#define ZR050_SOF_IDX 0x040 +#define ZR050_SOS1_IDX 0x07a +#define ZR050_SOS2_IDX 0x08a +#define ZR050_SOS3_IDX 0x09a +#define ZR050_SOS4_IDX 0x0aa +#define ZR050_DRI_IDX 0x0c0 +#define ZR050_DNL_IDX 0x0c6 +#define ZR050_DQT_IDX 0x0cc +#define ZR050_DHT_IDX 0x1d4 +#define ZR050_APP_IDX 0x380 +#define ZR050_COM_IDX 0x3c0 + +/* zr36050 hardware register bits */ + +#define ZR050_HW_BSWD 0x80 +#define ZR050_HW_MSTR 0x40 +#define ZR050_HW_DMA 0x20 +#define ZR050_HW_CFIS_1_CLK 0x00 +#define ZR050_HW_CFIS_2_CLK 0x04 +#define ZR050_HW_CFIS_3_CLK 0x08 +#define ZR050_HW_CFIS_4_CLK 0x0C +#define ZR050_HW_CFIS_5_CLK 0x10 +#define ZR050_HW_CFIS_6_CLK 0x14 +#define ZR050_HW_CFIS_7_CLK 0x18 +#define ZR050_HW_CFIS_8_CLK 0x1C +#define ZR050_HW_BELE 0x01 + +/* zr36050 mode register bits */ + +#define ZR050_MO_COMP 0x80 +#define ZR050_MO_COMP 0x80 +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 +#define ZR050_MO_BRC 0x04 + +#define ZR050_MO_ATP 0x40 +#define ZR050_MO_PASS2 0x20 +#define ZR050_MO_TLM 0x10 +#define ZR050_MO_DCONLY 0x08 + +/* zr36050 option register bits */ + +#define ZR050_OP_NSCN_1 0x00 +#define ZR050_OP_NSCN_2 0x20 +#define ZR050_OP_NSCN_3 0x40 +#define ZR050_OP_NSCN_4 0x60 +#define ZR050_OP_NSCN_5 0x80 +#define ZR050_OP_NSCN_6 0xA0 +#define ZR050_OP_NSCN_7 0xC0 +#define ZR050_OP_NSCN_8 0xE0 +#define ZR050_OP_OVF 0x10 + + +/* zr36050 markers-enable register bits */ + +#define ZR050_ME_APP 0x80 +#define ZR050_ME_COM 0x40 +#define ZR050_ME_DRI 0x20 +#define ZR050_ME_DQT 0x10 +#define ZR050_ME_DHT 0x08 +#define ZR050_ME_DNL 0x04 +#define ZR050_ME_DQTI 0x02 +#define ZR050_ME_DHTI 0x01 + +/* zr36050 status0/1 register bit masks */ + +#define ZR050_ST_RST_MASK 0x20 +#define ZR050_ST_SOF_MASK 0x02 +#define ZR050_ST_SOS_MASK 0x02 +#define ZR050_ST_DATRDY_MASK 0x80 +#define ZR050_ST_MRKDET_MASK 0x40 +#define ZR050_ST_RFM_MASK 0x10 +#define ZR050_ST_RFD_MASK 0x08 +#define ZR050_ST_END_MASK 0x04 +#define ZR050_ST_TCVOVF_MASK 0x02 +#define ZR050_ST_DATOVF_MASK 0x01 + +/* pixel component idx */ + +#define ZR050_Y_COMPONENT 0 +#define ZR050_U_COMPONENT 1 +#define ZR050_V_COMPONENT 2 + +#endif /*fndef ZR36050_H */ diff --git a/linux/drivers/media/video/zr36057.h b/linux/drivers/media/video/zr36057.h new file mode 100644 index 000000000..54c9362aa --- /dev/null +++ b/linux/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/linux/drivers/media/video/zr36060.c b/linux/drivers/media/video/zr36060.c new file mode 100644 index 000000000..97c8f9b9d --- /dev/null +++ b/linux/drivers/media/video/zr36060.c @@ -0,0 +1,1014 @@ +/* + * Zoran ZR36060 basic configuration functions + * + * Copyright (C) 2002 Laurent Pinchart <laurent.pinchart@skynet.be> + * + * $Id: zr36060.c,v 1.1.2.22 2003/05/06 09:35:36 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 ZR060_VERSION "v0.7" + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include <linux/types.h> +#include <linux/wait.h> + +/* includes for structures and defines regarding video + #include<linux/videodev.h> */ + +/* I/O commands, error codes */ +#include<asm/io.h> +//#include<errno.h> + +/* headerfile of this module */ +#include"zr36060.h" + +/* codec io API */ +#include"videocodec.h" + +/* it doesn't make sense to have more than 20 or so, + just to prevent some unwanted loops */ +#define MAX_CODECS 20 + +/* amount of chips attached via this driver */ +static int zr36060_codecs = 0; + +static int low_bitrate = 0; +module_param(low_bitrate, bool, 0); +MODULE_PARM_DESC(low_bitrate, "Buz compatibility option, halves bitrate"); + +/* debugging is available via module parameter */ +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-4)"); + +#define dprintk(num, format, args...) \ + do { \ + if (debug >= num) \ + printk(format, ##args); \ + } while (0) + +/* ========================================================================= + Local hardware I/O functions: + + read/write via codec layer (registers are located in the master device) + ========================================================================= */ + +/* read and write functions */ +static u8 +zr36060_read (struct zr36060 *ptr, + u16 reg) +{ + u8 value = 0; + + // just in case something is wrong... + if (ptr->codec->master_data->readreg) + value = (ptr->codec->master_data->readreg(ptr->codec, + reg)) & 0xff; + else + dprintk(1, + KERN_ERR "%s: invalid I/O setup, nothing read!\n", + ptr->name); + + //dprintk(4, "%s: reading from 0x%04x: %02x\n",ptr->name,reg,value); + + return value; +} + +static void +zr36060_write(struct zr36060 *ptr, + u16 reg, + u8 value) +{ + //dprintk(4, "%s: writing 0x%02x to 0x%04x\n",ptr->name,value,reg); + dprintk(4, "0x%02x @0x%04x\n", value, reg); + + // just in case something is wrong... + if (ptr->codec->master_data->writereg) + ptr->codec->master_data->writereg(ptr->codec, reg, value); + else + dprintk(1, + KERN_ERR + "%s: invalid I/O setup, nothing written!\n", + ptr->name); +} + +/* ========================================================================= + Local helper function: + + status read + ========================================================================= */ + +/* status is kept in datastructure */ +static u8 +zr36060_read_status (struct zr36060 *ptr) +{ + ptr->status = zr36060_read(ptr, ZR060_CFSR); + + zr36060_read(ptr, 0); + return ptr->status; +} + +/* ========================================================================= + Local helper function: + + scale factor read + ========================================================================= */ + +/* scale factor is kept in datastructure */ +static u16 +zr36060_read_scalefactor (struct zr36060 *ptr) +{ + ptr->scalefact = (zr36060_read(ptr, ZR060_SF_HI) << 8) | + (zr36060_read(ptr, ZR060_SF_LO) & 0xFF); + + /* leave 0 selected for an eventually GO from master */ + zr36060_read(ptr, 0); + return ptr->scalefact; +} + +/* ========================================================================= + Local helper function: + + wait if codec is ready to proceed (end of processing) or time is over + ========================================================================= */ + +static void +zr36060_wait_end (struct zr36060 *ptr) +{ + int i = 0; + + while (zr36060_read_status(ptr) & ZR060_CFSR_Busy) { + udelay(1); + if (i++ > 200000) { // 200ms, there is for sure something wrong!!! + dprintk(1, + "%s: timout at wait_end (last status: 0x%02x)\n", + ptr->name, ptr->status); + break; + } + } +} + +/* ========================================================================= + Local helper function: + + basic test of "connectivity", writes/reads to/from memory the SOF marker + ========================================================================= */ + +static int +zr36060_basic_test (struct zr36060 *ptr) +{ + if ((zr36060_read(ptr, ZR060_IDR_DEV) != 0x33) && + (zr36060_read(ptr, ZR060_IDR_REV) != 0x01)) { + dprintk(1, + KERN_ERR + "%s: attach failed, can't connect to jpeg processor!\n", + ptr->name); + return -ENXIO; + } + + zr36060_wait_end(ptr); + if (ptr->status & ZR060_CFSR_Busy) { + dprintk(1, + KERN_ERR + "%s: attach failed, jpeg processor failed (end flag)!\n", + ptr->name); + return -EBUSY; + } + + return 0; /* looks good! */ +} + +/* ========================================================================= + Local helper function: + + simple loop for pushing the init datasets + ========================================================================= */ + +static int +zr36060_pushit (struct zr36060 *ptr, + u16 startreg, + u16 len, + const char *data) +{ + int i = 0; + + dprintk(4, "%s: write data block to 0x%04x (len=%d)\n", ptr->name, + startreg, len); + while (i < len) { + zr36060_write(ptr, startreg++, data[i++]); + } + + return i; +} + +/* ========================================================================= + Basic datasets: + + jpeg baseline setup data (you find it on lots places in internet, or just + extract it from any regular .jpg image...) + + Could be variable, but until it's not needed it they are just fixed to save + memory. Otherwise expand zr36060 structure with arrays, push the values to + it and initalize from there, as e.g. the linux zr36057/60 driver does it. + ========================================================================= */ + +static const char zr36060_dqt[0x86] = { + 0xff, 0xdb, //Marker: DQT + 0x00, 0x84, //Length: 2*65+2 + 0x00, //Pq,Tq first table + 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, //Pq,Tq second table + 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 +}; + +static const char zr36060_dht[0x1a4] = { + 0xff, 0xc4, //Marker: DHT + 0x01, 0xa2, //Length: 2*AC, 2*DC + 0x00, //DC first table + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x01, //DC second table + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, //AC first table + 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, //AC second table + 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 +}; + +/* jpeg baseline setup, this is just fixed in this driver (YUV pictures) */ +#define NO_OF_COMPONENTS 0x3 //Y,U,V +#define BASELINE_PRECISION 0x8 //MCU size (?) +static const char zr36060_tq[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's QT +static const char zr36060_td[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's DC +static const char zr36060_ta[8] = { 0, 1, 1, 0, 0, 0, 0, 0 }; //table idx's AC + +/* horizontal 422 decimation setup (maybe we support 411 or so later, too) */ +static const char zr36060_decimation_h[8] = { 2, 1, 1, 0, 0, 0, 0, 0 }; +static const char zr36060_decimation_v[8] = { 1, 1, 1, 0, 0, 0, 0, 0 }; + +/* ========================================================================= + Local helper functions: + + calculation and setup of parameter-dependent JPEG baseline segments + (needed for compression only) + ========================================================================= */ + +/* ------------------------------------------------------------------------- */ + +/* SOF (start of frame) segment depends on width, height and sampling ratio + of each color component */ + +static int +zr36060_set_sof (struct zr36060 *ptr) +{ + char sof_data[34]; // max. size of register set + int i; + + dprintk(3, "%s: write SOF (%dx%d, %d components)\n", ptr->name, + ptr->width, ptr->height, NO_OF_COMPONENTS); + sof_data[0] = 0xff; + sof_data[1] = 0xc0; + sof_data[2] = 0x00; + sof_data[3] = (3 * NO_OF_COMPONENTS) + 8; + sof_data[4] = BASELINE_PRECISION; // only '8' possible with zr36060 + sof_data[5] = (ptr->height) >> 8; + sof_data[6] = (ptr->height) & 0xff; + sof_data[7] = (ptr->width) >> 8; + sof_data[8] = (ptr->width) & 0xff; + sof_data[9] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sof_data[10 + (i * 3)] = i; // index identifier + sof_data[11 + (i * 3)] = (ptr->h_samp_ratio[i] << 4) | + (ptr->v_samp_ratio[i]); // sampling ratios + sof_data[12 + (i * 3)] = zr36060_tq[i]; // Q table selection + } + return zr36060_pushit(ptr, ZR060_SOF_IDX, + (3 * NO_OF_COMPONENTS) + 10, sof_data); +} + +/* ------------------------------------------------------------------------- */ + +/* SOS (start of scan) segment depends on the used scan components + of each color component */ + +static int +zr36060_set_sos (struct zr36060 *ptr) +{ + char sos_data[16]; // max. size of register set + int i; + + dprintk(3, "%s: write SOS\n", ptr->name); + sos_data[0] = 0xff; + sos_data[1] = 0xda; + sos_data[2] = 0x00; + sos_data[3] = 2 + 1 + (2 * NO_OF_COMPONENTS) + 3; + sos_data[4] = NO_OF_COMPONENTS; + for (i = 0; i < NO_OF_COMPONENTS; i++) { + sos_data[5 + (i * 2)] = i; // index + sos_data[6 + (i * 2)] = (zr36060_td[i] << 4) | + zr36060_ta[i]; // AC/DC tbl.sel. + } + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 2] = 00; // scan start + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 3] = 0x3f; + sos_data[2 + 1 + (2 * NO_OF_COMPONENTS) + 4] = 00; + return zr36060_pushit(ptr, ZR060_SOS_IDX, + 4 + 1 + (2 * NO_OF_COMPONENTS) + 3, + sos_data); +} + +/* ------------------------------------------------------------------------- */ + +/* DRI (define restart interval) */ + +static int +zr36060_set_dri (struct zr36060 *ptr) +{ + char dri_data[6]; // max. size of register set + + dprintk(3, "%s: write DRI\n", ptr->name); + dri_data[0] = 0xff; + dri_data[1] = 0xdd; + dri_data[2] = 0x00; + dri_data[3] = 0x04; + dri_data[4] = (ptr->dri) >> 8; + dri_data[5] = (ptr->dri) & 0xff; + return zr36060_pushit(ptr, ZR060_DRI_IDX, 6, dri_data); +} + +/* ========================================================================= + Setup function: + + Setup compression/decompression of Zoran's JPEG processor + ( see also zoran 36060 manual ) + + ... sorry for the spaghetti code ... + ========================================================================= */ +static void +zr36060_init (struct zr36060 *ptr) +{ + int sum = 0; + long bitcnt, tmp; + + if (ptr->mode == CODEC_DO_COMPRESSION) { + dprintk(2, "%s: COMPRESSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CodeMstr); + + /* Compression with or without variable scale factor */ + /*FIXME: What about ptr->bitrate_ctrl? */ + zr36060_write(ptr, ZR060_CMR, + ZR060_CMR_Comp | ZR060_CMR_Pass2 | + ZR060_CMR_BRB); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* volume control settings */ + zr36060_write(ptr, ZR060_SF_HI, ptr->scalefact >> 8); + zr36060_write(ptr, ZR060_SF_LO, ptr->scalefact & 0xff); + + zr36060_write(ptr, ZR060_AF_HI, 0xff); + zr36060_write(ptr, ZR060_AF_M, 0xff); + zr36060_write(ptr, ZR060_AF_LO, 0xff); + + /* setup the variable jpeg tables */ + sum += zr36060_set_sof(ptr); + sum += zr36060_set_sos(ptr); + sum += zr36060_set_dri(ptr); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + sum += + zr36060_pushit(ptr, ZR060_DQT_IDX, sizeof(zr36060_dqt), + zr36060_dqt); + sum += + zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), + zr36060_dht); + zr36060_write(ptr, ZR060_APP_IDX, 0xff); + zr36060_write(ptr, ZR060_APP_IDX + 1, 0xe0 + ptr->app.appn); + zr36060_write(ptr, ZR060_APP_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_APP_IDX + 3, ptr->app.len + 2); + sum += zr36060_pushit(ptr, ZR060_APP_IDX + 4, 60, + ptr->app.data) + 4; + zr36060_write(ptr, ZR060_COM_IDX, 0xff); + zr36060_write(ptr, ZR060_COM_IDX + 1, 0xfe); + zr36060_write(ptr, ZR060_COM_IDX + 2, 0x00); + zr36060_write(ptr, ZR060_COM_IDX + 3, ptr->com.len + 2); + sum += zr36060_pushit(ptr, ZR060_COM_IDX + 4, 60, + ptr->com.data) + 4; + + /* setup misc. data for compression (target code sizes) */ + + /* size of compressed code to reach without header data */ + sum = ptr->real_code_vol - sum; + bitcnt = sum << 3; /* need the size in bits */ + + tmp = bitcnt >> 16; + dprintk(3, + "%s: code: csize=%d, tot=%d, bit=%ld, highbits=%ld\n", + ptr->name, sum, ptr->real_code_vol, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_NET_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_NET_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_NET_LO, tmp & 0xff); + + bitcnt -= bitcnt >> 7; // bits without stuffing + bitcnt -= ((bitcnt * 5) >> 6); // bits without eob + + tmp = bitcnt >> 16; + dprintk(3, "%s: code: nettobit=%ld, highnettobits=%ld\n", + ptr->name, bitcnt, tmp); + zr36060_write(ptr, ZR060_TCV_DATA_HI, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_MH, tmp & 0xff); + tmp = bitcnt & 0xffff; + zr36060_write(ptr, ZR060_TCV_DATA_ML, tmp >> 8); + zr36060_write(ptr, ZR060_TCV_DATA_LO, tmp & 0xff); + + /* JPEG markers to be included in the compressed stream */ + zr36060_write(ptr, ZR060_MER, + ZR060_MER_DQT | ZR060_MER_DHT | + ((ptr->com.len > 0) ? ZR060_MER_Com : 0) | + ((ptr->app.len > 0) ? ZR060_MER_App : 0)); + + /* Setup the Video Frontend */ + /* Limit pixel range to 16..235 as per CCIR-601 */ + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_Range); + + } else { + dprintk(2, "%s: EXPANSION SETUP\n", ptr->name); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* 060 communicates with 067 in master mode */ + zr36060_write(ptr, ZR060_CIR, ZR060_CIR_CodeMstr); + + /* Decompression */ + zr36060_write(ptr, ZR060_CMR, 0); + + /* Must be zero */ + zr36060_write(ptr, ZR060_MBZ, 0x00); + zr36060_write(ptr, ZR060_TCR_HI, 0x00); + zr36060_write(ptr, ZR060_TCR_LO, 0x00); + + /* Disable all IRQs - no DataErr means autoreset */ + zr36060_write(ptr, ZR060_IMR, 0); + + /* setup misc. data for expansion */ + zr36060_write(ptr, ZR060_MER, 0); + + /* setup the fixed jpeg tables - maybe variable, though - + * (see table init section above) */ + zr36060_pushit(ptr, ZR060_DHT_IDX, sizeof(zr36060_dht), + zr36060_dht); + + /* Setup the Video Frontend */ + //zr36060_write(ptr, ZR060_VCR, ZR060_VCR_FIExt); + //this doesn't seem right and doesn't work... + zr36060_write(ptr, ZR060_VCR, ZR060_VCR_Range); + } + + /* Load the tables */ + zr36060_write(ptr, ZR060_LOAD, + ZR060_LOAD_SyncRst | ZR060_LOAD_Load); + zr36060_wait_end(ptr); + dprintk(2, "%s: Status after table preload: 0x%02x\n", ptr->name, + ptr->status); + + if (ptr->status & ZR060_CFSR_Busy) { + dprintk(1, KERN_ERR "%s: init aborted!\n", ptr->name); + return; // something is wrong, its timed out!!!! + } +} + +/* ========================================================================= + CODEC API FUNCTIONS + + this functions are accessed by the master via the API structure + ========================================================================= */ + +/* set compression/expansion mode and launches codec - + this should be the last call from the master before starting processing */ +static int +zr36060_set_mode (struct videocodec *codec, + int mode) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + + dprintk(2, "%s: set_mode %d call\n", ptr->name, mode); + + if ((mode != CODEC_DO_EXPANSION) && (mode != CODEC_DO_COMPRESSION)) + return -EINVAL; + + ptr->mode = mode; + zr36060_init(ptr); + + return 0; +} + +/* set picture size (norm is ignored as the codec doesn't know about it) */ +static int +zr36060_set_video (struct videocodec *codec, + struct tvnorm *norm, + struct vfe_settings *cap, + struct vfe_polarity *pol) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + u32 reg; + int size; + + dprintk(2, "%s: set_video %d/%d-%dx%d (%%%d) call\n", ptr->name, + cap->x, cap->y, cap->width, cap->height, cap->decimation); + + /* if () return -EINVAL; + * trust the master driver that it knows what it does - so + * we allow invalid startx/y and norm for now ... */ + ptr->width = cap->width / (cap->decimation & 0xff); + ptr->height = cap->height / (cap->decimation >> 8); + + zr36060_write(ptr, ZR060_LOAD, ZR060_LOAD_SyncRst); + + /* Note that VSPol/HSPol bits in zr36060 have the opposite + * meaning of their zr360x7 counterparts with the same names + * N.b. for VSPol this is only true if FIVEdge = 0 (default, + * left unchanged here - in accordance with datasheet). + */ + reg = (!pol->vsync_pol ? ZR060_VPR_VSPol : 0) + | (!pol->hsync_pol ? ZR060_VPR_HSPol : 0) + | (pol->field_pol ? ZR060_VPR_FIPol : 0) + | (pol->blank_pol ? ZR060_VPR_BLPol : 0) + | (pol->subimg_pol ? ZR060_VPR_SImgPol : 0) + | (pol->poe_pol ? ZR060_VPR_PoePol : 0) + | (pol->pvalid_pol ? ZR060_VPR_PValPol : 0) + | (pol->vclk_pol ? ZR060_VPR_VCLKPol : 0); + zr36060_write(ptr, ZR060_VPR, reg); + + reg = 0; + switch (cap->decimation & 0xff) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_HScale2; + break; + + case 4: + reg |= ZR060_SR_HScale4; + break; + } + + switch (cap->decimation >> 8) { + default: + case 1: + break; + + case 2: + reg |= ZR060_SR_VScale; + break; + } + zr36060_write(ptr, ZR060_SR, reg); + + zr36060_write(ptr, ZR060_BCR_Y, 0x00); + zr36060_write(ptr, ZR060_BCR_U, 0x80); + zr36060_write(ptr, ZR060_BCR_V, 0x80); + + /* sync generator */ + + reg = norm->Ht - 1; /* Vtotal */ + zr36060_write(ptr, ZR060_SGR_VTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_VTOTAL_LO, (reg >> 0) & 0xff); + + reg = norm->Wt - 1; /* Htotal */ + zr36060_write(ptr, ZR060_SGR_HTOTAL_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_HTOTAL_LO, (reg >> 0) & 0xff); + + reg = 6 - 1; /* VsyncSize */ + zr36060_write(ptr, ZR060_SGR_VSYNC, reg); + + //reg = 30 - 1; /* HsyncSize */ +///*CP*/ reg = (zr->params.norm == 1 ? 57 : 68); + reg = 68; + zr36060_write(ptr, ZR060_SGR_HSYNC, reg); + + reg = norm->VStart - 1; /* BVstart */ + zr36060_write(ptr, ZR060_SGR_BVSTART, reg); + + reg += norm->Ha / 2; /* BVend */ + zr36060_write(ptr, ZR060_SGR_BVEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BVEND_LO, (reg >> 0) & 0xff); + + reg = norm->HStart - 1; /* BHstart */ + zr36060_write(ptr, ZR060_SGR_BHSTART, reg); + + reg += norm->Wa; /* BHend */ + zr36060_write(ptr, ZR060_SGR_BHEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SGR_BHEND_LO, (reg >> 0) & 0xff); + + /* active area */ + reg = cap->y + norm->VStart; /* Vstart */ + zr36060_write(ptr, ZR060_AAR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VSTART_LO, (reg >> 0) & 0xff); + + reg += cap->height; /* Vend */ + zr36060_write(ptr, ZR060_AAR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_VEND_LO, (reg >> 0) & 0xff); + + reg = cap->x + norm->HStart; /* Hstart */ + zr36060_write(ptr, ZR060_AAR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HSTART_LO, (reg >> 0) & 0xff); + + reg += cap->width; /* Hend */ + zr36060_write(ptr, ZR060_AAR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_AAR_HEND_LO, (reg >> 0) & 0xff); + + /* subimage area */ + reg = norm->VStart - 4; /* SVstart */ + zr36060_write(ptr, ZR060_SWR_VSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VSTART_LO, (reg >> 0) & 0xff); + + reg += norm->Ha / 2 + 8; /* SVend */ + zr36060_write(ptr, ZR060_SWR_VEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_VEND_LO, (reg >> 0) & 0xff); + + reg = norm->HStart /*+ 64 */ - 4; /* SHstart */ + zr36060_write(ptr, ZR060_SWR_HSTART_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HSTART_LO, (reg >> 0) & 0xff); + + reg += norm->Wa + 8; /* SHend */ + zr36060_write(ptr, ZR060_SWR_HEND_HI, (reg >> 8) & 0xff); + zr36060_write(ptr, ZR060_SWR_HEND_LO, (reg >> 0) & 0xff); + + size = ptr->width * ptr->height; + /* Target compressed field size in bits: */ + size = size * 16; /* uncompressed size in bits */ + /* (Ronald) by default, quality = 100 is a compression + * ratio 1:2. Setting low_bitrate (insmod option) sets + * it to 1:4 (instead of 1:2, zr36060 max) as limit because the + * buz can't handle more at decimation=1... Use low_bitrate if + * you have a Buz, unless you know what you're doing */ + size = size * cap->quality / (low_bitrate ? 400 : 200); + /* Lower limit (arbitrary, 1 KB) */ + if (size < 8192) + size = 8192; + /* Upper limit: 7/8 of the code buffers */ + if (size > ptr->total_code_vol * 7) + size = ptr->total_code_vol * 7; + + ptr->real_code_vol = size >> 3; /* in bytes */ + + /* the MBCVR is the *maximum* block volume, according to the + * JPEG ISO specs, this shouldn't be used, since that allows + * for the best encoding quality. So set it to it's max value */ + reg = ptr->max_block_vol; + zr36060_write(ptr, ZR060_MBCVR, reg); + + return 0; +} + +/* additional control functions */ +static int +zr36060_control (struct videocodec *codec, + int type, + int size, + void *data) +{ + struct zr36060 *ptr = (struct zr36060 *) codec->data; + int *ival = (int *) data; + + dprintk(2, "%s: control %d call with %d byte\n", ptr->name, type, + size); + + switch (type) { + case CODEC_G_STATUS: /* get last status */ + if (size != sizeof(int)) + return -EFAULT; + zr36060_read_status(ptr); + *ival = ptr->status; + break; + + case CODEC_G_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + *ival = CODEC_MODE_BJPG; + break; + + case CODEC_S_CODEC_MODE: + if (size != sizeof(int)) + return -EFAULT; + if (*ival != CODEC_MODE_BJPG) + return -EINVAL; + /* not needed, do nothing */ + return 0; + + case CODEC_G_VFE: + case CODEC_S_VFE: + /* not needed, do nothing */ + return 0; + + case CODEC_S_MMAP: + /* not available, give an error */ + return -ENXIO; + + case CODEC_G_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + *ival = ptr->total_code_vol; + break; + + case CODEC_S_JPEG_TDS_BYTE: /* get target volume in byte */ + if (size != sizeof(int)) + return -EFAULT; + ptr->total_code_vol = *ival; + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + break; + + case CODEC_G_JPEG_SCALE: /* get scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + *ival = zr36060_read_scalefactor(ptr); + break; + + case CODEC_S_JPEG_SCALE: /* set scaling factor */ + if (size != sizeof(int)) + return -EFAULT; + ptr->scalefact = *ival; + break; + + case CODEC_G_JPEG_APP_DATA: { /* get appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + *app = ptr->app; + break; + } + + case CODEC_S_JPEG_APP_DATA: { /* set appn marker data */ + struct jpeg_app_marker *app = data; + + if (size != sizeof(struct jpeg_app_marker)) + return -EFAULT; + + ptr->app = *app; + break; + } + + case CODEC_G_JPEG_COM_DATA: { /* get comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + *com = ptr->com; + break; + } + + case CODEC_S_JPEG_COM_DATA: { /* set comment marker data */ + struct jpeg_com_marker *com = data; + + if (size != sizeof(struct jpeg_com_marker)) + return -EFAULT; + + ptr->com = *com; + break; + } + + default: + return -EINVAL; + } + + return size; +} + +/* ========================================================================= + Exit and unregister function: + + Deinitializes Zoran's JPEG processor + ========================================================================= */ + +static int +zr36060_unset (struct videocodec *codec) +{ + struct zr36060 *ptr = codec->data; + + if (ptr) { + /* do wee need some codec deinit here, too ???? */ + + dprintk(1, "%s: finished codec #%d\n", ptr->name, + ptr->num); + kfree(ptr); + codec->data = NULL; + + zr36060_codecs--; + return 0; + } + + return -EFAULT; +} + +/* ========================================================================= + Setup and registry function: + + Initializes Zoran's JPEG processor + + Also sets pixel size, average code size, mode (compr./decompr.) + (the given size is determined by the processor with the video interface) + ========================================================================= */ + +static int +zr36060_setup (struct videocodec *codec) +{ + struct zr36060 *ptr; + int res; + + dprintk(2, "zr36060: initializing MJPEG subsystem #%d.\n", + zr36060_codecs); + + if (zr36060_codecs == MAX_CODECS) { + dprintk(1, + KERN_ERR "zr36060: Can't attach more codecs!\n"); + return -ENOSPC; + } + //mem structure init + codec->data = ptr = kzalloc(sizeof(struct zr36060), GFP_KERNEL); + if (NULL == ptr) { + dprintk(1, KERN_ERR "zr36060: Can't get enough memory!\n"); + return -ENOMEM; + } + + snprintf(ptr->name, sizeof(ptr->name), "zr36060[%d]", + zr36060_codecs); + ptr->num = zr36060_codecs++; + ptr->codec = codec; + + //testing + res = zr36060_basic_test(ptr); + if (res < 0) { + zr36060_unset(codec); + return res; + } + //final setup + memcpy(ptr->h_samp_ratio, zr36060_decimation_h, 8); + memcpy(ptr->v_samp_ratio, zr36060_decimation_v, 8); + + ptr->bitrate_ctrl = 0; /* 0 or 1 - fixed file size flag + * (what is the difference?) */ + ptr->mode = CODEC_DO_COMPRESSION; + ptr->width = 384; + ptr->height = 288; + ptr->total_code_vol = 16000; /* CHECKME */ + ptr->real_code_vol = (ptr->total_code_vol * 6) >> 3; + ptr->max_block_vol = 240; /* CHECKME, was 120 is 240 */ + ptr->scalefact = 0x100; + ptr->dri = 1; /* CHECKME, was 8 is 1 */ + + /* by default, no COM or APP markers - app should set those */ + ptr->com.len = 0; + ptr->app.appn = 0; + ptr->app.len = 0; + + zr36060_init(ptr); + + dprintk(1, KERN_INFO "%s: codec attached and running\n", + ptr->name); + + return 0; +} + +static const struct videocodec zr36060_codec = { + .owner = THIS_MODULE, + .name = "zr36060", + .magic = 0L, // magic not used + .flags = + CODEC_FLAG_JPEG | CODEC_FLAG_HARDWARE | CODEC_FLAG_ENCODER | + CODEC_FLAG_DECODER | CODEC_FLAG_VFE, + .type = CODEC_TYPE_ZR36060, + .setup = zr36060_setup, // functionality + .unset = zr36060_unset, + .set_mode = zr36060_set_mode, + .set_video = zr36060_set_video, + .control = zr36060_control, + // others are not used +}; + +/* ========================================================================= + HOOK IN DRIVER AS KERNEL MODULE + ========================================================================= */ + +static int __init +zr36060_init_module (void) +{ + //dprintk(1, "zr36060 driver %s\n",ZR060_VERSION); + zr36060_codecs = 0; + return videocodec_register(&zr36060_codec); +} + +static void __exit +zr36060_cleanup_module (void) +{ + if (zr36060_codecs) { + dprintk(1, + "zr36060: something's wrong - %d codecs left somehow.\n", + zr36060_codecs); + } + + /* however, we can't just stay alive */ + videocodec_unregister(&zr36060_codec); +} + +module_init(zr36060_init_module); +module_exit(zr36060_cleanup_module); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@skynet.be>"); +MODULE_DESCRIPTION("Driver module for ZR36060 jpeg processors " + ZR060_VERSION); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/zr36060.h b/linux/drivers/media/video/zr36060.h new file mode 100644 index 000000000..914ffa4ad --- /dev/null +++ b/linux/drivers/media/video/zr36060.h @@ -0,0 +1,220 @@ +/* + * Zoran ZR36060 basic configuration functions - header file + * + * Copyright (C) 2002 Laurent Pinchart <laurent.pinchart@skynet.be> + * + * $Id: zr36060.h,v 1.1.1.1.2.3 2003/01/14 21:18:47 rbultje Exp $ + * + * ------------------------------------------------------------------------ + * + * 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 + +#include "videocodec.h" + +/* data stored for each zoran jpeg codec chip */ +struct zr36060 { + char name[32]; + int num; + /* io datastructure */ + struct videocodec *codec; + // last coder status + __u8 status; + // actual coder setup + int mode; + + __u16 width; + __u16 height; + + __u16 bitrate_ctrl; + + __u32 total_code_vol; + __u32 real_code_vol; + __u16 max_block_vol; + + __u8 h_samp_ratio[8]; + __u8 v_samp_ratio[8]; + __u16 scalefact; + __u16 dri; + + /* app/com marker data */ + struct jpeg_app_marker app; + struct jpeg_com_marker com; +}; + +/* ZR36060 register addresses */ +#define ZR060_LOAD 0x000 +#define ZR060_CFSR 0x001 +#define ZR060_CIR 0x002 +#define ZR060_CMR 0x003 +#define ZR060_MBZ 0x004 +#define ZR060_MBCVR 0x005 +#define ZR060_MER 0x006 +#define ZR060_IMR 0x007 +#define ZR060_ISR 0x008 +#define ZR060_TCV_NET_HI 0x009 +#define ZR060_TCV_NET_MH 0x00a +#define ZR060_TCV_NET_ML 0x00b +#define ZR060_TCV_NET_LO 0x00c +#define ZR060_TCV_DATA_HI 0x00d +#define ZR060_TCV_DATA_MH 0x00e +#define ZR060_TCV_DATA_ML 0x00f +#define ZR060_TCV_DATA_LO 0x010 +#define ZR060_SF_HI 0x011 +#define ZR060_SF_LO 0x012 +#define ZR060_AF_HI 0x013 +#define ZR060_AF_M 0x014 +#define ZR060_AF_LO 0x015 +#define ZR060_ACV_HI 0x016 +#define ZR060_ACV_MH 0x017 +#define ZR060_ACV_ML 0x018 +#define ZR060_ACV_LO 0x019 +#define ZR060_ACT_HI 0x01a +#define ZR060_ACT_MH 0x01b +#define ZR060_ACT_ML 0x01c +#define ZR060_ACT_LO 0x01d +#define ZR060_ACV_TRUN_HI 0x01e +#define ZR060_ACV_TRUN_MH 0x01f +#define ZR060_ACV_TRUN_ML 0x020 +#define ZR060_ACV_TRUN_LO 0x021 +#define ZR060_IDR_DEV 0x022 +#define ZR060_IDR_REV 0x023 +#define ZR060_TCR_HI 0x024 +#define ZR060_TCR_LO 0x025 +#define ZR060_VCR 0x030 +#define ZR060_VPR 0x031 +#define ZR060_SR 0x032 +#define ZR060_BCR_Y 0x033 +#define ZR060_BCR_U 0x034 +#define ZR060_BCR_V 0x035 +#define ZR060_SGR_VTOTAL_HI 0x036 +#define ZR060_SGR_VTOTAL_LO 0x037 +#define ZR060_SGR_HTOTAL_HI 0x038 +#define ZR060_SGR_HTOTAL_LO 0x039 +#define ZR060_SGR_VSYNC 0x03a +#define ZR060_SGR_HSYNC 0x03b +#define ZR060_SGR_BVSTART 0x03c +#define ZR060_SGR_BHSTART 0x03d +#define ZR060_SGR_BVEND_HI 0x03e +#define ZR060_SGR_BVEND_LO 0x03f +#define ZR060_SGR_BHEND_HI 0x040 +#define ZR060_SGR_BHEND_LO 0x041 +#define ZR060_AAR_VSTART_HI 0x042 +#define ZR060_AAR_VSTART_LO 0x043 +#define ZR060_AAR_VEND_HI 0x044 +#define ZR060_AAR_VEND_LO 0x045 +#define ZR060_AAR_HSTART_HI 0x046 +#define ZR060_AAR_HSTART_LO 0x047 +#define ZR060_AAR_HEND_HI 0x048 +#define ZR060_AAR_HEND_LO 0x049 +#define ZR060_SWR_VSTART_HI 0x04a +#define ZR060_SWR_VSTART_LO 0x04b +#define ZR060_SWR_VEND_HI 0x04c +#define ZR060_SWR_VEND_LO 0x04d +#define ZR060_SWR_HSTART_HI 0x04e +#define ZR060_SWR_HSTART_LO 0x04f +#define ZR060_SWR_HEND_HI 0x050 +#define ZR060_SWR_HEND_LO 0x051 + +#define ZR060_SOF_IDX 0x060 +#define ZR060_SOS_IDX 0x07a +#define ZR060_DRI_IDX 0x0c0 +#define ZR060_DQT_IDX 0x0cc +#define ZR060_DHT_IDX 0x1d4 +#define ZR060_APP_IDX 0x380 +#define ZR060_COM_IDX 0x3c0 + +/* ZR36060 LOAD register bits */ + +#define ZR060_LOAD_Load (1 << 7) +#define ZR060_LOAD_SyncRst (1 << 0) + +/* ZR36060 Code FIFO Status register bits */ + +#define ZR060_CFSR_Busy (1 << 7) +#define ZR060_CFSR_CBusy (1 << 2) +#define ZR060_CFSR_CFIFO (3 << 0) + +/* ZR36060 Code Interface register */ + +#define ZR060_CIR_Code16 (1 << 7) +#define ZR060_CIR_Endian (1 << 6) +#define ZR060_CIR_CFIS (1 << 2) +#define ZR060_CIR_CodeMstr (1 << 0) + +/* ZR36060 Codec Mode register */ + +#define ZR060_CMR_Comp (1 << 7) +#define ZR060_CMR_ATP (1 << 6) +#define ZR060_CMR_Pass2 (1 << 5) +#define ZR060_CMR_TLM (1 << 4) +#define ZR060_CMR_BRB (1 << 2) +#define ZR060_CMR_FSF (1 << 1) + +/* ZR36060 Markers Enable register */ + +#define ZR060_MER_App (1 << 7) +#define ZR060_MER_Com (1 << 6) +#define ZR060_MER_DRI (1 << 5) +#define ZR060_MER_DQT (1 << 4) +#define ZR060_MER_DHT (1 << 3) + +/* ZR36060 Interrupt Mask register */ + +#define ZR060_IMR_EOAV (1 << 3) +#define ZR060_IMR_EOI (1 << 2) +#define ZR060_IMR_End (1 << 1) +#define ZR060_IMR_DataErr (1 << 0) + +/* ZR36060 Interrupt Status register */ + +#define ZR060_ISR_ProCnt (3 << 6) +#define ZR060_ISR_EOAV (1 << 3) +#define ZR060_ISR_EOI (1 << 2) +#define ZR060_ISR_End (1 << 1) +#define ZR060_ISR_DataErr (1 << 0) + +/* ZR36060 Video Control register */ + +#define ZR060_VCR_Video8 (1 << 7) +#define ZR060_VCR_Range (1 << 6) +#define ZR060_VCR_FIDet (1 << 3) +#define ZR060_VCR_FIVedge (1 << 2) +#define ZR060_VCR_FIExt (1 << 1) +#define ZR060_VCR_SyncMstr (1 << 0) + +/* ZR36060 Video Polarity register */ + +#define ZR060_VPR_VCLKPol (1 << 7) +#define ZR060_VPR_PValPol (1 << 6) +#define ZR060_VPR_PoePol (1 << 5) +#define ZR060_VPR_SImgPol (1 << 4) +#define ZR060_VPR_BLPol (1 << 3) +#define ZR060_VPR_FIPol (1 << 2) +#define ZR060_VPR_HSPol (1 << 1) +#define ZR060_VPR_VSPol (1 << 0) + +/* ZR36060 Scaling register */ + +#define ZR060_SR_VScale (1 << 2) +#define ZR060_SR_HScale2 (1 << 0) +#define ZR060_SR_HScale4 (2 << 0) + +#endif /*fndef ZR36060_H */ |