diff options
Diffstat (limited to 'drivers/media')
35 files changed, 5106 insertions, 480 deletions
diff --git a/drivers/media/common/ir-common.c b/drivers/media/common/ir-common.c index 4b71fd6f7ae..7972c73bc14 100644 --- a/drivers/media/common/ir-common.c +++ b/drivers/media/common/ir-common.c @@ -126,6 +126,66 @@ IR_KEYTAB_TYPE ir_codes_winfast[IR_KEYTAB_SIZE] = { }; EXPORT_SYMBOL_GPL(ir_codes_winfast); +IR_KEYTAB_TYPE ir_codes_pinnacle[IR_KEYTAB_SIZE] = { + [ 0x59 ] = KEY_MUTE, + [ 0x4a ] = KEY_POWER, + + [ 0x18 ] = KEY_TEXT, + [ 0x26 ] = KEY_TV, + [ 0x3d ] = KEY_PRINT, + + [ 0x48 ] = KEY_RED, + [ 0x04 ] = KEY_GREEN, + [ 0x11 ] = KEY_YELLOW, + [ 0x00 ] = KEY_BLUE, + + [ 0x2d ] = KEY_VOLUMEUP, + [ 0x1e ] = KEY_VOLUMEDOWN, + + [ 0x49 ] = KEY_MENU, + + [ 0x16 ] = KEY_CHANNELUP, + [ 0x17 ] = KEY_CHANNELDOWN, + + [ 0x20 ] = KEY_UP, + [ 0x21 ] = KEY_DOWN, + [ 0x22 ] = KEY_LEFT, + [ 0x23 ] = KEY_RIGHT, + [ 0x0d ] = KEY_SELECT, + + + + [ 0x08 ] = KEY_BACK, + [ 0x07 ] = KEY_REFRESH, + + [ 0x2f ] = KEY_ZOOM, + [ 0x29 ] = KEY_RECORD, + + [ 0x4b ] = KEY_PAUSE, + [ 0x4d ] = KEY_REWIND, + [ 0x2e ] = KEY_PLAY, + [ 0x4e ] = KEY_FORWARD, + [ 0x53 ] = KEY_PREVIOUS, + [ 0x4c ] = KEY_STOP, + [ 0x54 ] = KEY_NEXT, + + [ 0x69 ] = KEY_KP0, + [ 0x6a ] = KEY_KP1, + [ 0x6b ] = KEY_KP2, + [ 0x6c ] = KEY_KP3, + [ 0x6d ] = KEY_KP4, + [ 0x6e ] = KEY_KP5, + [ 0x6f ] = KEY_KP6, + [ 0x70 ] = KEY_KP7, + [ 0x71 ] = KEY_KP8, + [ 0x72 ] = KEY_KP9, + + [ 0x74 ] = KEY_CHANNEL, + [ 0x0a ] = KEY_BACKSPACE, +}; + +EXPORT_SYMBOL_GPL(ir_codes_pinnacle); + /* empty keytable, can be used as placeholder for not-yet created keytables */ IR_KEYTAB_TYPE ir_codes_empty[IR_KEYTAB_SIZE] = { [ 42 ] = KEY_COFFEE, diff --git a/drivers/media/dvb/b2c2/Kconfig b/drivers/media/dvb/b2c2/Kconfig index d7417eac2ab..2583a865a58 100644 --- a/drivers/media/dvb/b2c2/Kconfig +++ b/drivers/media/dvb/b2c2/Kconfig @@ -7,6 +7,7 @@ config DVB_B2C2_FLEXCOP select DVB_NXT2002 select DVB_STV0297 select DVB_BCM3510 + select DVB_LGDT330X help Support for the digital TV receiver chip made by B2C2 Inc. included in Technisats PCI cards and USB boxes. diff --git a/drivers/media/dvb/cinergyT2/cinergyT2.c b/drivers/media/dvb/cinergyT2/cinergyT2.c index a1607e7d6d6..fb394a0d838 100644 --- a/drivers/media/dvb/cinergyT2/cinergyT2.c +++ b/drivers/media/dvb/cinergyT2/cinergyT2.c @@ -276,7 +276,7 @@ static void cinergyt2_free_stream_urbs (struct cinergyt2 *cinergyt2) if (cinergyt2->stream_urb[i]) usb_free_urb(cinergyt2->stream_urb[i]); - pci_free_consistent(NULL, STREAM_URB_COUNT*STREAM_BUF_SIZE, + usb_buffer_free(cinergyt2->udev, STREAM_URB_COUNT*STREAM_BUF_SIZE, cinergyt2->streambuf, cinergyt2->streambuf_dmahandle); } @@ -284,9 +284,8 @@ static int cinergyt2_alloc_stream_urbs (struct cinergyt2 *cinergyt2) { int i; - cinergyt2->streambuf = pci_alloc_consistent(NULL, - STREAM_URB_COUNT*STREAM_BUF_SIZE, - &cinergyt2->streambuf_dmahandle); + cinergyt2->streambuf = usb_buffer_alloc(cinergyt2->udev, STREAM_URB_COUNT*STREAM_BUF_SIZE, + SLAB_KERNEL, &cinergyt2->streambuf_dmahandle); if (!cinergyt2->streambuf) { dprintk(1, "failed to alloc consistent stream memory area, bailing out!\n"); return -ENOMEM; @@ -780,6 +779,8 @@ static int cinergyt2_register_rc(struct cinergyt2 *cinergyt2) input_register_device(cinergyt2->rc_input_dev); schedule_delayed_work(&cinergyt2->rc_query_work, HZ/2); + + return 0; } static void cinergyt2_unregister_rc(struct cinergyt2 *cinergyt2) diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 199b0118885..1a3b3c7e5e9 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -333,4 +333,18 @@ config VIDEO_M32R_AR_M64278 Say Y here to use the Renesas M64278E-800 camera module, which supports VGA(640x480 pixcels) size of images. +config VIDEO_AUDIO_DECODER + tristate "Add support for additional audio chipsets" + depends on VIDEO_DEV && I2C && EXPERIMENTAL + ---help--- + Say Y here to compile drivers for WM8775 and CS53L32A audio + decoders. + +config VIDEO_DECODER + tristate "Add support for additional video chipsets" + depends on VIDEO_DEV && I2C && EXPERIMENTAL + ---help--- + Say Y here to compile drivers for SAA7115, SAA7127 and CX25840 + video decoders. + endmenu diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 3ac46599240..82060f9909d 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -36,10 +36,11 @@ obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o obj-$(CONFIG_VIDEO_MEYE) += meye.o -obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ +obj-$(CONFIG_VIDEO_SAA7134) += ir-kbd-i2c.o saa7134/ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ obj-$(CONFIG_VIDEO_EM28XX) += saa711x.o tvp5150.o +obj-$(CONFIG_VIDEO_AUDIO_DECODER) += wm8775.o cs53l32a.o obj-$(CONFIG_VIDEO_OVCAMCHIP) += ovcamchip/ obj-$(CONFIG_VIDEO_MXB) += saa7111.o tuner.o tda9840.o tea6415c.o tea6420.o mxb.o obj-$(CONFIG_VIDEO_HEXIUM_ORION) += hexium_orion.o @@ -55,4 +56,6 @@ obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o +obj-$(CONFIG_VIDEO_DECODER) += saa7115.o cx25840/ saa7127.o + EXTRA_CFLAGS += -I$(srctree)/drivers/media/dvb/dvb-core diff --git a/drivers/media/video/bttv-cards.c b/drivers/media/video/bttv-cards.c index 3413bace443..e31ebb11c46 100644 --- a/drivers/media/video/bttv-cards.c +++ b/drivers/media/video/bttv-cards.c @@ -2133,7 +2133,10 @@ struct tvcard bttv_tvcards[] = { .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, .has_dvb = 1, + .has_remote = 1, + .gpiomask = 0x1b, .no_gpioirq = 1, + .any_irq = 1, }, [BTTV_BOARD_PV143] = { /* Jorge Boncompte - DTI2 <jorge@dti2.net> */ @@ -2796,7 +2799,24 @@ struct tvcard bttv_tvcards[] = { .tuner_addr = ADDR_UNSET, .radio_addr = ADDR_UNSET, }, - + /* ---- card 0x8e ---------------------------------- */ + [BTTV_BOARD_SABRENT_TVFM] = { + .name = "Sabrent TV-FM (bttv version)", + .video_inputs = 3, + .audio_inputs = 1, + .tuner = 0, + .svhs = 2, + .gpiomask = 0x108007, + .muxsel = { 2, 3, 1, 1}, + .audiomux = { 100000, 100002, 100002, 100000}, + .no_msp34xx = 1, + .no_tda9875 = 1, + .no_tda7432 = 1, + .pll = PLL_28, + .tuner_type = TUNER_TNF_5335MF, + .tuner_addr = ADDR_UNSET, + .has_radio = 1, + }, }; static const unsigned int bttv_num_tvcards = ARRAY_SIZE(bttv_tvcards); @@ -3367,6 +3387,8 @@ void __devinit bttv_init_card2(struct bttv *btv) btv->has_remote=1; if (!bttv_tvcards[btv->c.type].no_gpioirq) btv->gpioirq=1; + if (bttv_tvcards[btv->c.type].any_irq) + btv->any_irq = 1; if (bttv_tvcards[btv->c.type].audio_hook) btv->audio_hook=bttv_tvcards[btv->c.type].audio_hook; diff --git a/drivers/media/video/bttv-driver.c b/drivers/media/video/bttv-driver.c index 0005741d551..709099f03bd 100644 --- a/drivers/media/video/bttv-driver.c +++ b/drivers/media/video/bttv-driver.c @@ -3667,6 +3667,10 @@ static irqreturn_t bttv_irq(int irq, void *dev_id, struct pt_regs * regs) int handled = 0; btv=(struct bttv *)dev_id; + + if (btv->any_irq) + handled = bttv_any_irq(&btv->c); + count=0; while (1) { /* get/clear interrupt status bits */ diff --git a/drivers/media/video/bttv-gpio.c b/drivers/media/video/bttv-gpio.c index 575ce8b8e71..616a5b7e510 100644 --- a/drivers/media/video/bttv-gpio.c +++ b/drivers/media/video/bttv-gpio.c @@ -113,6 +113,24 @@ void bttv_gpio_irq(struct bttv_core *core) } } +int bttv_any_irq(struct bttv_core *core) +{ + struct bttv_sub_driver *drv; + struct bttv_sub_device *dev; + struct list_head *item; + int handled = 0; + + list_for_each(item,&core->subs) { + dev = list_entry(item,struct bttv_sub_device,list); + drv = to_bttv_sub_drv(dev->dev.driver); + if (drv && drv->any_irq) { + if (drv->any_irq(dev)) + handled = 1; + } + } + return handled; +} + /* ----------------------------------------------------------------------- */ /* external: sub-driver register/unregister */ diff --git a/drivers/media/video/bttv.h b/drivers/media/video/bttv.h index 124ea41dada..93298f06e01 100644 --- a/drivers/media/video/bttv.h +++ b/drivers/media/video/bttv.h @@ -162,6 +162,7 @@ #define BTTV_BOARD_PV_M4900 0x8b #define BTTV_BOARD_OSPREY440 0x8c #define BTTV_BOARD_ASOUND_SKYEYE 0x8d +#define BTTV_BOARD_SABRENT_TVFM 0x8e /* i2c address list */ #define I2C_TSA5522 0xc2 @@ -234,6 +235,7 @@ struct tvcard unsigned int has_dvb:1; unsigned int has_remote:1; unsigned int no_gpioirq:1; + unsigned int any_irq:1; /* other settings */ unsigned int pll; @@ -333,6 +335,7 @@ struct bttv_sub_driver { struct device_driver drv; char wanted[BUS_ID_SIZE]; void (*gpio_irq)(struct bttv_sub_device *sub); + int (*any_irq)(struct bttv_sub_device *sub); }; #define to_bttv_sub_drv(x) container_of((x), struct bttv_sub_driver, drv) diff --git a/drivers/media/video/bttvp.h b/drivers/media/video/bttvp.h index 386f546f7d1..3aa9c6e4fc3 100644 --- a/drivers/media/video/bttvp.h +++ b/drivers/media/video/bttvp.h @@ -208,6 +208,7 @@ extern struct bus_type bttv_sub_bus_type; int bttv_sub_add_device(struct bttv_core *core, char *name); int bttv_sub_del_devices(struct bttv_core *core); void bttv_gpio_irq(struct bttv_core *core); +int bttv_any_irq(struct bttv_core *core); /* ---------------------------------------------------------- */ @@ -273,6 +274,7 @@ struct bttv { struct bttv_pll_info pll; int triton1; int gpioirq; + int any_irq; int use_i2c_hw; /* old gpio interface */ diff --git a/drivers/media/video/cx25840/Makefile b/drivers/media/video/cx25840/Makefile new file mode 100644 index 00000000000..543ebacdc9d --- /dev/null +++ b/drivers/media/video/cx25840/Makefile @@ -0,0 +1,6 @@ +cx25840-objs := cx25840-core.o cx25840-audio.o cx25840-firmware.o \ + cx25840-vbi.o + +obj-$(CONFIG_VIDEO_DECODER) += cx25840.o + +EXTRA_CFLAGS += -I$(src)/.. diff --git a/drivers/media/video/cx25840/cx25840-audio.c b/drivers/media/video/cx25840/cx25840-audio.c new file mode 100644 index 00000000000..740908f8027 --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-audio.c @@ -0,0 +1,368 @@ +/* cx25840 audio functions + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/audiochip.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +inline static int set_audclk_freq(struct i2c_client *client, + enum v4l2_audio_clock_freq freq) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + /* assert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x01); + + /* common for all inputs and rates */ + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x10 */ + cx25840_write(client, 0x127, 0x50); + + switch (state->audio_input) { + case AUDIO_TUNER: + switch (freq) { + case V4L2_AUDCLK_32_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040610); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xee39bb01); + + /* src3/4/6_ctl = 0x0801f77f */ + cx25840_write4(client, 0x900, 0x7ff70108); + cx25840_write4(client, 0x904, 0x7ff70108); + cx25840_write4(client, 0x90c, 0x7ff70108); + break; + + case V4L2_AUDCLK_441_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040910); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xd66bec00); + + /* src3/4/6_ctl = 0x08016d59 */ + cx25840_write4(client, 0x900, 0x596d0108); + cx25840_write4(client, 0x904, 0x596d0108); + cx25840_write4(client, 0x90c, 0x596d0108); + break; + + case V4L2_AUDCLK_48_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040a10); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xe5d69800); + + /* src3/4/6_ctl = 0x08014faa */ + cx25840_write4(client, 0x900, 0xaa4f0108); + cx25840_write4(client, 0x904, 0xaa4f0108); + cx25840_write4(client, 0x90c, 0xaa4f0108); + break; + } + break; + + case AUDIO_EXTERN_1: + case AUDIO_EXTERN_2: + case AUDIO_INTERN: + case AUDIO_RADIO: + switch (freq) { + case V4L2_AUDCLK_32_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f04081e); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0x69082a01); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0x00000108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x00000208); + cx25840_write4(client, 0x904, 0x00000208); + cx25840_write4(client, 0x90c, 0x00000208); + + /* SA_MCLK_SEL=1, SA_MCLK_DIV=0x14 */ + cx25840_write(client, 0x127, 0x54); + break; + + case V4L2_AUDCLK_441_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040918); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xd66bec00); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0xcd600108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x85730108); + cx25840_write4(client, 0x904, 0x85730108); + cx25840_write4(client, 0x90c, 0x85730108); + break; + + case V4L2_AUDCLK_48_KHZ: + /* VID_PLL and AUX_PLL */ + cx25840_write4(client, 0x108, 0x0f040a18); + + /* AUX_PLL_FRAC */ + cx25840_write4(client, 0x110, 0xe5d69800); + + /* src1_ctl = 0x08010000 */ + cx25840_write4(client, 0x8f8, 0x00800108); + + /* src3/4/6_ctl = 0x08020000 */ + cx25840_write4(client, 0x900, 0x55550108); + cx25840_write4(client, 0x904, 0x55550108); + cx25840_write4(client, 0x90c, 0x55550108); + break; + } + break; + } + + /* deassert soft reset */ + cx25840_and_or(client, 0x810, ~0x1, 0x00); + + state->audclk_freq = freq; + + return 0; +} + +static int set_input(struct i2c_client *client, int audio_input) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + cx25840_dbg("set audio input (%d)\n", audio_input); + + /* stop microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0); + + /* Mute everything to prevent the PFFT! */ + cx25840_write(client, 0x8d3, 0x1f); + + switch (audio_input) { + case AUDIO_TUNER: + /* Set Path1 to Analog Demod Main Channel */ + cx25840_write4(client, 0x8d0, 0x7038061f); + + /* When the microcontroller detects the + * audio format, it will unmute the lines */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + break; + + case AUDIO_EXTERN_1: + case AUDIO_EXTERN_2: + case AUDIO_INTERN: + case AUDIO_RADIO: + /* Set Path1 to Serial Audio Input */ + cx25840_write4(client, 0x8d0, 0x12100101); + + /* The microcontroller should not be started for the + * non-tuner inputs: autodetection is specific for + * TV audio. */ + break; + + default: + cx25840_dbg("Invalid audio input selection %d\n", audio_input); + return -EINVAL; + } + + state->audio_input = audio_input; + + return set_audclk_freq(client, state->audclk_freq); +} + +inline static int get_volume(struct i2c_client *client) +{ + /* Volume runs +18dB to -96dB in 1/2dB steps + * change to fit the msp3400 -114dB to +12dB range */ + + /* check PATH1_VOLUME */ + int vol = 228 - cx25840_read(client, 0x8d4); + vol = (vol / 2) + 23; + return vol << 9; +} + +inline static void set_volume(struct i2c_client *client, int volume) +{ + /* First convert the volume to msp3400 values (0-127) */ + int vol = volume >> 9; + /* now scale it up to cx25840 values + * -114dB to -96dB maps to 0 + * this should be 19, but in my testing that was 4dB too loud */ + if (vol <= 23) { + vol = 0; + } else { + vol -= 23; + } + + /* PATH1_VOLUME */ + cx25840_write(client, 0x8d4, 228 - (vol * 2)); +} + +inline static int get_bass(struct i2c_client *client) +{ + /* bass is 49 steps +12dB to -12dB */ + + /* check PATH1_EQ_BASS_VOL */ + int bass = cx25840_read(client, 0x8d9) & 0x3f; + bass = (((48 - bass) * 0xffff) + 47) / 48; + return bass; +} + +inline static void set_bass(struct i2c_client *client, int bass) +{ + /* PATH1_EQ_BASS_VOL */ + cx25840_and_or(client, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff)); +} + +inline static int get_treble(struct i2c_client *client) +{ + /* treble is 49 steps +12dB to -12dB */ + + /* check PATH1_EQ_TREBLE_VOL */ + int treble = cx25840_read(client, 0x8db) & 0x3f; + treble = (((48 - treble) * 0xffff) + 47) / 48; + return treble; +} + +inline static void set_treble(struct i2c_client *client, int treble) +{ + /* PATH1_EQ_TREBLE_VOL */ + cx25840_and_or(client, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff)); +} + +inline static int get_balance(struct i2c_client *client) +{ + /* balance is 7 bit, 0 to -96dB */ + + /* check PATH1_BAL_LEVEL */ + int balance = cx25840_read(client, 0x8d5) & 0x7f; + /* check PATH1_BAL_LEFT */ + if ((cx25840_read(client, 0x8d5) & 0x80) == 0) + balance = 0x80 - balance; + else + balance = 0x80 + balance; + return balance << 8; +} + +inline static void set_balance(struct i2c_client *client, int balance) +{ + int bal = balance >> 8; + if (bal > 0x80) { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x80); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, bal & 0x7f); + } else { + /* PATH1_BAL_LEFT */ + cx25840_and_or(client, 0x8d5, 0x7f, 0x00); + /* PATH1_BAL_LEVEL */ + cx25840_and_or(client, 0x8d5, ~0x7f, 0x80 - bal); + } +} + +inline static int get_mute(struct i2c_client *client) +{ + /* check SRC1_MUTE_EN */ + return cx25840_read(client, 0x8d3) & 0x2 ? 1 : 0; +} + +inline static void set_mute(struct i2c_client *client, int mute) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + if (state->audio_input == AUDIO_TUNER) { + /* Must turn off microcontroller in order to mute sound. + * Not sure if this is the best method, but it does work. + * If the microcontroller is running, then it will undo any + * changes to the mute register. */ + if (mute) { + /* disable microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + cx25840_write(client, 0x8d3, 0x1f); + } else { + /* enable microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } + } else { + /* SRC1_MUTE_EN */ + cx25840_and_or(client, 0x8d3, ~0x2, mute ? 0x02 : 0x00); + } +} + +int cx25840_audio(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct v4l2_control *ctrl = arg; + + switch (cmd) { + case AUDC_SET_INPUT: + return set_input(client, *(int *)arg); + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + return set_audclk_freq(client, *(enum v4l2_audio_clock_freq *)arg); + case VIDIOC_G_CTRL: + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + ctrl->value = get_volume(client); + break; + case V4L2_CID_AUDIO_BASS: + ctrl->value = get_bass(client); + break; + case V4L2_CID_AUDIO_TREBLE: + ctrl->value = get_treble(client); + break; + case V4L2_CID_AUDIO_BALANCE: + ctrl->value = get_balance(client); + break; + case V4L2_CID_AUDIO_MUTE: + ctrl->value = get_mute(client); + break; + default: + return -EINVAL; + } + break; + case VIDIOC_S_CTRL: + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + set_volume(client, ctrl->value); + break; + case V4L2_CID_AUDIO_BASS: + set_bass(client, ctrl->value); + break; + case V4L2_CID_AUDIO_TREBLE: + set_treble(client, ctrl->value); + break; + case V4L2_CID_AUDIO_BALANCE: + set_balance(client, ctrl->value); + break; + case V4L2_CID_AUDIO_MUTE: + set_mute(client, ctrl->value); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} diff --git a/drivers/media/video/cx25840/cx25840-core.c b/drivers/media/video/cx25840/cx25840-core.c new file mode 100644 index 00000000000..f6afeec499c --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-core.c @@ -0,0 +1,1020 @@ +/* cx25840 - Conexant CX25840 audio/video decoder driver + * + * Copyright (C) 2004 Ulf Eklund + * + * Based on the saa7115 driver and on the first verison of Chris Kennedy's + * cx25840 driver. + * + * Changes by Tyler Trafford <tatrafford@comcast.net> + * - cleanup/rewrite for V4L2 API (2005) + * + * VBI support by Hans Verkuil <hverkuil@xs4all.nl>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/audiochip.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +MODULE_DESCRIPTION("Conexant CX25840 audio/video decoder driver"); +MODULE_AUTHOR("Ulf Eklund, Chris Kennedy, Hans Verkuil, Tyler Trafford"); +MODULE_LICENSE("GPL"); + +static unsigned short normal_i2c[] = { 0x88 >> 1, I2C_CLIENT_END }; + + +int cx25840_debug = 0; + +module_param(cx25840_debug, bool, 0644); + +MODULE_PARM_DESC(cx25840_debug, "Debugging messages [0=Off (default) 1=On]"); + +I2C_CLIENT_INSMOD; + +/* ----------------------------------------------------------------------- */ + +int cx25840_write(struct i2c_client *client, u16 addr, u8 value) +{ + u8 buffer[3]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value; + return i2c_master_send(client, buffer, 3); +} + +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value) +{ + u8 buffer[6]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + buffer[2] = value >> 24; + buffer[3] = (value >> 16) & 0xff; + buffer[4] = (value >> 8) & 0xff; + buffer[5] = value & 0xff; + return i2c_master_send(client, buffer, 6); +} + +u8 cx25840_read(struct i2c_client * client, u16 addr) +{ + u8 buffer[2]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + + if (i2c_master_send(client, buffer, 2) < 2) + return 0; + + if (i2c_master_recv(client, buffer, 1) < 1) + return 0; + + return buffer[0]; +} + +u32 cx25840_read4(struct i2c_client * client, u16 addr) +{ + u8 buffer[4]; + buffer[0] = addr >> 8; + buffer[1] = addr & 0xff; + + if (i2c_master_send(client, buffer, 2) < 2) + return 0; + + if (i2c_master_recv(client, buffer, 4) < 4) + return 0; + + return (buffer[0] << 24) | (buffer[1] << 16) | + (buffer[2] << 8) | buffer[3]; +} + +int cx25840_and_or(struct i2c_client *client, u16 addr, u8 and_mask, + u8 or_value) +{ + return cx25840_write(client, addr, + (cx25840_read(client, addr) & and_mask) | + or_value); +} + +/* ----------------------------------------------------------------------- */ + +static int set_input(struct i2c_client *, enum cx25840_input); +static void input_change(struct i2c_client *); +static void log_status(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ + +static inline void init_dll1(struct i2c_client *client) +{ + /* This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 1 (ADC DLL). */ + cx25840_write(client, 0x159, 0x23); + cx25840_write(client, 0x15a, 0x87); + cx25840_write(client, 0x15b, 0x06); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15a, 0x86); + cx25840_write(client, 0x159, 0xe0); + cx25840_write(client, 0x159, 0xe1); + cx25840_write(client, 0x15b, 0x10); +} + +static inline void init_dll2(struct i2c_client *client) +{ + /* This is the Hauppauge sequence used to + * initialize the Delay Lock Loop 2 (ADC DLL). */ + cx25840_write(client, 0x15d, 0xe3); + cx25840_write(client, 0x15e, 0x86); + cx25840_write(client, 0x15f, 0x06); + cx25840_write(client, 0x15d, 0xe1); + cx25840_write(client, 0x15d, 0xe0); + cx25840_write(client, 0x15d, 0xe1); +} + +static void cx25840_initialize(struct i2c_client *client, int loadfw) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + /* datasheet startup in numbered steps, refer to page 3-77 */ + /* 2. */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + /* The default of this register should be 4, but I get 0 instead. + * Set this register to 4 manually. */ + cx25840_write(client, 0x000, 0x04); + /* 3. */ + init_dll1(client); + init_dll2(client); + cx25840_write(client, 0x136, 0x0a); + /* 4. */ + cx25840_write(client, 0x13c, 0x01); + cx25840_write(client, 0x13c, 0x00); + /* 5. */ + if (loadfw) + cx25840_loadfw(client); + /* 6. */ + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + cx25840_write(client, 0x118, 0x02); + /* 7. */ + cx25840_write(client, 0x4a5, 0x80); + cx25840_write(client, 0x4a5, 0x00); + cx25840_write(client, 0x402, 0x00); + /* 8. */ + cx25840_write(client, 0x401, 0x18); + cx25840_write(client, 0x4a2, 0x10); + cx25840_write(client, 0x402, 0x04); + /* 10. */ + cx25840_write(client, 0x8d3, 0x1f); + cx25840_write(client, 0x8e3, 0x03); + + cx25840_vbi_setup(client); + + /* trial and error says these are needed to get audio */ + cx25840_write(client, 0x914, 0xa0); + cx25840_write(client, 0x918, 0xa0); + cx25840_write(client, 0x919, 0x01); + + /* stereo prefered */ + cx25840_write(client, 0x809, 0x04); + /* AC97 shift */ + cx25840_write(client, 0x8cf, 0x0f); + + /* (re)set video input */ + set_input(client, state->input); + /* (re)set audio input */ + cx25840_audio(client, AUDC_SET_INPUT, &state->audio_input); + + /* start microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x10); +} + +/* ----------------------------------------------------------------------- */ + +static void input_change(struct i2c_client *client) +{ + v4l2_std_id std = cx25840_get_v4lstd(client); + + if (std & V4L2_STD_PAL) { + /* Follow tuner change procedure for PAL */ + cx25840_write(client, 0x808, 0xff); + cx25840_write(client, 0x80b, 0x10); + } else if (std & V4L2_STD_SECAM) { + /* Select autodetect for SECAM */ + cx25840_write(client, 0x808, 0xff); + cx25840_write(client, 0x80b, 0x10); + } else if (std & V4L2_STD_NTSC) { + /* NTSC */ + cx25840_write(client, 0x808, 0xf6); + cx25840_write(client, 0x80b, 0x00); + } + + if (cx25840_read(client, 0x803) & 0x10) { + /* restart audio decoder microcontroller */ + cx25840_and_or(client, 0x803, ~0x10, 0x00); + cx25840_and_or(client, 0x803, ~0x10, 0x10); + } +} + +static int set_input(struct i2c_client *client, enum cx25840_input input) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + cx25840_dbg("decoder set input (%d)\n", input); + + switch (input) { + case CX25840_TUNER: + cx25840_dbg("now setting Tuner input\n"); + + if (state->cardtype == CARDTYPE_PVR150) { + /* CH_SEL_ADC2=1 */ + cx25840_and_or(client, 0x102, ~0x2, 0x02); + } + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x11); + } else { + cx25840_write(client, 0x103, 0x46); + } + + /* INPUT_MODE=0 */ + cx25840_and_or(client, 0x401, ~0x6, 0x00); + break; + + case CX25840_COMPOSITE0: + case CX25840_COMPOSITE1: + cx25840_dbg("now setting Composite input\n"); + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x00); + } else { + cx25840_write(client, 0x103, 0x02); + } + + /* INPUT_MODE=0 */ + cx25840_and_or(client, 0x401, ~0x6, 0x00); + break; + + case CX25840_SVIDEO0: + case CX25840_SVIDEO1: + cx25840_dbg("now setting S-Video input\n"); + + /* CH_SEL_ADC2=0 */ + cx25840_and_or(client, 0x102, ~0x2, 0x00); + + /* Video Input Control */ + if (state->cardtype == CARDTYPE_PG600) { + cx25840_write(client, 0x103, 0x02); + } else { + cx25840_write(client, 0x103, 0x10); + } + + /* INPUT_MODE=1 */ + cx25840_and_or(client, 0x401, ~0x6, 0x02); + break; + + default: + cx25840_err("%d is not a valid input!\n", input); + return -EINVAL; + } + + state->input = input; + input_change(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lstd(struct i2c_client *client, v4l2_std_id std) +{ + u8 fmt; + + switch (std) { + /* zero is autodetect */ + case 0: fmt = 0x0; break; + /* default ntsc to ntsc-m */ + case V4L2_STD_NTSC: + case V4L2_STD_NTSC_M: fmt = 0x1; break; + case V4L2_STD_NTSC_M_JP: fmt = 0x2; break; + case V4L2_STD_NTSC_443: fmt = 0x3; break; + case V4L2_STD_PAL: fmt = 0x4; break; + case V4L2_STD_PAL_M: fmt = 0x5; break; + case V4L2_STD_PAL_N: fmt = 0x6; break; + case V4L2_STD_PAL_Nc: fmt = 0x7; break; + case V4L2_STD_PAL_60: fmt = 0x8; break; + case V4L2_STD_SECAM: fmt = 0xc; break; + default: + return -ERANGE; + } + + cx25840_and_or(client, 0x400, ~0xf, fmt); + cx25840_vbi_setup(client); + return 0; +} + +v4l2_std_id cx25840_get_v4lstd(struct i2c_client * client) +{ + /* check VID_FMT_SEL first */ + u8 fmt = cx25840_read(client, 0x400) & 0xf; + + if (!fmt) { + /* check AFD_FMT_STAT if set to autodetect */ + fmt = cx25840_read(client, 0x40d) & 0xf; + } + + switch (fmt) { + case 0x1: return V4L2_STD_NTSC_M; + case 0x2: return V4L2_STD_NTSC_M_JP; + case 0x3: return V4L2_STD_NTSC_443; + case 0x4: return V4L2_STD_PAL; + case 0x5: return V4L2_STD_PAL_M; + case 0x6: return V4L2_STD_PAL_N; + case 0x7: return V4L2_STD_PAL_Nc; + case 0x8: return V4L2_STD_PAL_60; + case 0xc: return V4L2_STD_SECAM; + default: return V4L2_STD_UNKNOWN; + } +} + +/* ----------------------------------------------------------------------- */ + +static int set_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case CX25840_CID_CARDTYPE: + switch (ctrl->value) { + case CARDTYPE_PVR150: + case CARDTYPE_PG600: + state->cardtype = ctrl->value; + break; + default: + return -ERANGE; + } + + set_input(client, state->input); + break; + + case V4L2_CID_BRIGHTNESS: + if (ctrl->value < 0 || ctrl->value > 255) { + cx25840_err("invalid brightness setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x414, ctrl->value - 128); + break; + + case V4L2_CID_CONTRAST: + if (ctrl->value < 0 || ctrl->value > 127) { + cx25840_err("invalid contrast setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x415, ctrl->value << 1); + break; + + case V4L2_CID_SATURATION: + if (ctrl->value < 0 || ctrl->value > 127) { + cx25840_err("invalid saturation setting %d\n", + ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x420, ctrl->value << 1); + cx25840_write(client, 0x421, ctrl->value << 1); + break; + + case V4L2_CID_HUE: + if (ctrl->value < -127 || ctrl->value > 127) { + cx25840_err("invalid hue setting %d\n", ctrl->value); + return -ERANGE; + } + + cx25840_write(client, 0x422, ctrl->value); + break; + + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_MUTE: + return cx25840_audio(client, VIDIOC_S_CTRL, ctrl); + } + + return 0; +} + +static int get_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case CX25840_CID_CARDTYPE: + ctrl->value = state->cardtype; + break; + case V4L2_CID_BRIGHTNESS: + ctrl->value = cx25840_read(client, 0x414) + 128; + break; + case V4L2_CID_CONTRAST: + ctrl->value = cx25840_read(client, 0x415) >> 1; + break; + case V4L2_CID_SATURATION: + ctrl->value = cx25840_read(client, 0x420) >> 1; + break; + case V4L2_CID_HUE: + ctrl->value = cx25840_read(client, 0x422); + break; + case V4L2_CID_AUDIO_VOLUME: + case V4L2_CID_AUDIO_BASS: + case V4L2_CID_AUDIO_TREBLE: + case V4L2_CID_AUDIO_BALANCE: + case V4L2_CID_AUDIO_MUTE: + return cx25840_audio(client, VIDIOC_G_CTRL, ctrl); + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int get_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + switch (fmt->type) { + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_G_FMT, fmt); + default: + return -EINVAL; + } + + return 0; +} + +static int set_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + struct v4l2_pix_format *pix; + int HSC, VSC, Vsrc, Hsrc, filter, Vlines; + int is_pal = !(cx25840_get_v4lstd(client) & V4L2_STD_NTSC); + + switch (fmt->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pix = &(fmt->fmt.pix); + + Vsrc = (cx25840_read(client, 0x476) & 0x3f) << 4; + Vsrc |= (cx25840_read(client, 0x475) & 0xf0) >> 4; + + Hsrc = (cx25840_read(client, 0x472) & 0x3f) << 4; + Hsrc |= (cx25840_read(client, 0x471) & 0xf0) >> 4; + + Vlines = pix->height + (is_pal ? 4 : 7); + + if ((pix->width * 16 < Hsrc) || (Hsrc < pix->width) || + (Vlines * 8 < Vsrc) || (Vsrc < Vlines)) { + cx25840_err("%dx%d is not a valid size!\n", + pix->width, pix->height); + return -ERANGE; + } + + HSC = (Hsrc * (1 << 20)) / pix->width - (1 << 20); + VSC = (1 << 16) - (Vsrc * (1 << 9) / Vlines - (1 << 9)); + VSC &= 0x1fff; + + if (pix->width >= 385) + filter = 0; + else if (pix->width > 192) + filter = 1; + else if (pix->width > 96) + filter = 2; + else + filter = 3; + + cx25840_dbg("decoder set size %dx%d -> scale %ux%u\n", + pix->width, pix->height, HSC, VSC); + + /* HSCALE=HSC */ + cx25840_write(client, 0x418, HSC & 0xff); + cx25840_write(client, 0x419, (HSC >> 8) & 0xff); + cx25840_write(client, 0x41a, HSC >> 16); + /* VSCALE=VSC */ + cx25840_write(client, 0x41c, VSC & 0xff); + cx25840_write(client, 0x41d, VSC >> 8); + /* VS_INTRLACE=1 VFILT=filter */ + cx25840_write(client, 0x41e, 0x8 | filter); + break; + + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_S_FMT, fmt); + + case V4L2_BUF_TYPE_VBI_CAPTURE: + return cx25840_vbi(client, VIDIOC_S_FMT, fmt); + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int cx25840_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + struct v4l2_tuner *vt = arg; + int result = 0; + + switch (cmd) { + case 0: + break; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + /* ioctls to allow direct access to the + * cx25840 registers for testing */ + case VIDIOC_INT_G_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_CX25840) + return -EINVAL; + reg->val = cx25840_read(client, reg->reg & 0x0fff); + break; + } + + case VIDIOC_INT_S_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_CX25840) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + cx25840_write(client, reg->reg & 0x0fff, reg->val & 0xff); + break; + } +#endif + + case VIDIOC_INT_DECODE_VBI_LINE: + return cx25840_vbi(client, cmd, arg); + + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + case AUDC_SET_INPUT: + result = cx25840_audio(client, cmd, arg); + break; + + case VIDIOC_STREAMON: + cx25840_dbg("enable output\n"); + cx25840_write(client, 0x115, 0x8c); + cx25840_write(client, 0x116, 0x07); + break; + + case VIDIOC_STREAMOFF: + cx25840_dbg("disable output\n"); + cx25840_write(client, 0x115, 0x00); + cx25840_write(client, 0x116, 0x00); + break; + + case VIDIOC_LOG_STATUS: + log_status(client); + break; + + case VIDIOC_G_CTRL: + result = get_v4lctrl(client, (struct v4l2_control *)arg); + break; + + case VIDIOC_S_CTRL: + result = set_v4lctrl(client, (struct v4l2_control *)arg); + break; + + case VIDIOC_G_STD: + *(v4l2_std_id *)arg = cx25840_get_v4lstd(client); + break; + + case VIDIOC_S_STD: + result = set_v4lstd(client, *(v4l2_std_id *)arg); + break; + + case VIDIOC_G_INPUT: + *(int *)arg = state->input; + break; + + case VIDIOC_S_INPUT: + result = set_input(client, *(int *)arg); + break; + + case VIDIOC_S_FREQUENCY: + input_change(client); + break; + + case VIDIOC_G_TUNER: + { + u8 mode = cx25840_read(client, 0x804); + u8 pref = cx25840_read(client, 0x809) & 0xf; + u8 vpres = cx25840_read(client, 0x80a) & 0x10; + int val = 0; + + vt->capability |= + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + + vt->signal = vpres ? 0xffff : 0x0; + + /* get rxsubchans and audmode */ + if ((mode & 0xf) == 1) + val |= V4L2_TUNER_SUB_STEREO; + else + val |= V4L2_TUNER_SUB_MONO; + + if (mode == 2 || mode == 4) + val |= V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + + if (mode & 0x10) + val |= V4L2_TUNER_SUB_SAP; + + vt->rxsubchans = val; + + switch (pref) { + case 0: + vt->audmode = V4L2_TUNER_MODE_MONO; + break; + case 1: + case 2: + vt->audmode = V4L2_TUNER_MODE_LANG2; + break; + case 4: + default: + vt->audmode = V4L2_TUNER_MODE_STEREO; + } + break; + } + + case VIDIOC_S_TUNER: + switch (vt->audmode) { + case V4L2_TUNER_MODE_MONO: + case V4L2_TUNER_MODE_LANG1: + /* Force PREF_MODE to MONO */ + cx25840_and_or(client, 0x809, ~0xf, 0x00); + break; + case V4L2_TUNER_MODE_STEREO: + /* Force PREF_MODE to STEREO */ + cx25840_and_or(client, 0x809, ~0xf, 0x04); + break; + case V4L2_TUNER_MODE_LANG2: + /* Force PREF_MODE to LANG2 */ + cx25840_and_or(client, 0x809, ~0xf, 0x01); + break; + } + break; + + case VIDIOC_G_FMT: + result = get_v4lfmt(client, (struct v4l2_format *)arg); + break; + + case VIDIOC_S_FMT: + result = set_v4lfmt(client, (struct v4l2_format *)arg); + break; + + case VIDIOC_INT_RESET: + cx25840_initialize(client, 0); + break; + + case VIDIOC_INT_G_CHIP_IDENT: + *(enum v4l2_chip_ident *)arg = + V4L2_IDENT_CX25840 + ((cx25840_read(client, 0x100) >> 4) & 0xf); + break; + + default: + cx25840_err("invalid ioctl %x\n", cmd); + return -EINVAL; + } + + return result; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_cx25840; + +static int cx25840_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + struct i2c_client *client; + struct cx25840_state *state; + u16 device_id; + + /* Check if the adapter supports the needed features + * Not until kernel version 2.6.11 did the bit-algo + * correctly report that it would do an I2C-level xfer */ + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_cx25840; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name) - 1, "cx25840"); + + cx25840_dbg("detecting cx25840 client on address 0x%x\n", address << 1); + + device_id = cx25840_read(client, 0x101) << 8; + device_id |= cx25840_read(client, 0x100); + + /* The high byte of the device ID should be + * 0x84 if chip is present */ + if ((device_id & 0xff00) != 0x8400) { + cx25840_dbg("cx25840 not found\n"); + kfree(client); + return 0; + } + + cx25840_info("cx25%3x-2%x found @ 0x%x (%s)\n", + (device_id & 0xfff0) >> 4, + (device_id & 0x0f) < 3 ? (device_id & 0x0f) + 1 : 3, + address << 1, adapter->name); + + state = kmalloc(sizeof(struct cx25840_state), GFP_KERNEL); + if (state == NULL) { + kfree(client); + return -ENOMEM; + } + + i2c_set_clientdata(client, state); + memset(state, 0, sizeof(struct cx25840_state)); + state->input = CX25840_TUNER; + state->audclk_freq = V4L2_AUDCLK_48_KHZ; + state->audio_input = AUDIO_TUNER; + state->cardtype = CARDTYPE_PVR150; + + cx25840_initialize(client, 1); + + i2c_attach_client(client); + + return 0; +} + +static int cx25840_attach_adapter(struct i2c_adapter *adapter) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adapter->class & I2C_CLASS_TV_ANALOG) +#else + if (adapter->id == I2C_HW_B_BT848) +#endif + return i2c_probe(adapter, &addr_data, &cx25840_detect_client); + return 0; +} + +static int cx25840_detach_client(struct i2c_client *client) +{ + struct cx25840_state *state = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(state); + kfree(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_cx25840 = { + .name = "cx25840", + + .id = I2C_DRIVERID_CX25840, + .flags = I2C_DF_NOTIFY, + + .attach_adapter = cx25840_attach_adapter, + .detach_client = cx25840_detach_client, + .command = cx25840_command, + .owner = THIS_MODULE, +}; + + +static int __init m__init(void) +{ + return i2c_add_driver(&i2c_driver_cx25840); +} + +static void __exit m__exit(void) +{ + i2c_del_driver(&i2c_driver_cx25840); +} + +module_init(m__init); +module_exit(m__exit); + +/* ----------------------------------------------------------------------- */ + +static void log_status(struct i2c_client *client) +{ + static const char *const fmt_strs[] = { + "0x0", + "NTSC-M", "NTSC-J", "NTSC-4.43", + "PAL-BDGHI", "PAL-M", "PAL-N", "PAL-Nc", "PAL-60", + "0x9", "0xA", "0xB", + "SECAM", + "0xD", "0xE", "0xF" + }; + + struct cx25840_state *state = i2c_get_clientdata(client); + u8 microctrl_vidfmt = cx25840_read(client, 0x80a); + u8 vidfmt_sel = cx25840_read(client, 0x400) & 0xf; + u8 gen_stat1 = cx25840_read(client, 0x40d); + u8 download_ctl = cx25840_read(client, 0x803); + u8 mod_det_stat0 = cx25840_read(client, 0x804); + u8 mod_det_stat1 = cx25840_read(client, 0x805); + u8 audio_config = cx25840_read(client, 0x808); + u8 pref_mode = cx25840_read(client, 0x809); + u8 afc0 = cx25840_read(client, 0x80b); + u8 mute_ctl = cx25840_read(client, 0x8d3); + char *p; + + cx25840_info("Video signal: %spresent\n", + (microctrl_vidfmt & 0x10) ? "" : "not "); + cx25840_info("Detected format: %s\n", + fmt_strs[gen_stat1 & 0xf]); + + switch (mod_det_stat0) { + case 0x00: p = "mono"; break; + case 0x01: p = "stereo"; break; + case 0x02: p = "dual"; break; + case 0x04: p = "tri"; break; + case 0x10: p = "mono with SAP"; break; + case 0x11: p = "stereo with SAP"; break; + case 0x12: p = "dual with SAP"; break; + case 0x14: p = "tri with SAP"; break; + case 0xfe: p = "forced mode"; break; + default: p = "not defined"; + } + cx25840_info("Detected audio mode: %s\n", p); + + switch (mod_det_stat1) { + case 0x00: p = "not defined"; break; + case 0x01: p = "EIAJ"; break; + case 0x02: p = "A2-M"; break; + case 0x03: p = "A2-BG"; break; + case 0x04: p = "A2-DK1"; break; + case 0x05: p = "A2-DK2"; break; + case 0x06: p = "A2-DK3"; break; + case 0x07: p = "A1 (6.0 MHz FM Mono)"; break; + case 0x08: p = "AM-L"; break; + case 0x09: p = "NICAM-BG"; break; + case 0x0a: p = "NICAM-DK"; break; + case 0x0b: p = "NICAM-I"; break; + case 0x0c: p = "NICAM-L"; break; + case 0x0d: p = "BTSC/EIAJ/A2-M Mono (4.5 MHz FMMono)"; break; + case 0x0e: p = "IF FM Radio"; break; + case 0x0f: p = "BTSC"; break; + case 0x10: p = "high-deviation FM"; break; + case 0x11: p = "very high-deviation FM"; break; + case 0xfd: p = "unknown audio standard"; break; + case 0xfe: p = "forced audio standard"; break; + case 0xff: p = "no detected audio standard"; break; + default: p = "not defined"; + } + cx25840_info("Detected audio standard: %s\n", p); + cx25840_info("Audio muted: %s\n", + (mute_ctl & 0x2) ? "yes" : "no"); + cx25840_info("Audio microcontroller: %s\n", + (download_ctl & 0x10) ? "running" : "stopped"); + + switch (audio_config >> 4) { + case 0x00: p = "undefined"; break; + case 0x01: p = "BTSC"; break; + case 0x02: p = "EIAJ"; break; + case 0x03: p = "A2-M"; break; + case 0x04: p = "A2-BG"; break; + case 0x05: p = "A2-DK1"; break; + case 0x06: p = "A2-DK2"; break; + case 0x07: p = "A2-DK3"; break; + case 0x08: p = "A1 (6.0 MHz FM Mono)"; break; + case 0x09: p = "AM-L"; break; + case 0x0a: p = "NICAM-BG"; break; + case 0x0b: p = "NICAM-DK"; break; + case 0x0c: p = "NICAM-I"; break; + case 0x0d: p = "NICAM-L"; break; + case 0x0e: p = "FM radio"; break; + case 0x0f: p = "automatic detection"; break; + default: p = "undefined"; + } + cx25840_info("Configured audio standard: %s\n", p); + + if ((audio_config >> 4) < 0xF) { + switch (audio_config & 0xF) { + case 0x00: p = "MONO1 (LANGUAGE A/Mono L+R channel for BTSC, EIAJ, A2)"; break; + case 0x01: p = "MONO2 (LANGUAGE B)"; break; + case 0x02: p = "MONO3 (STEREO forced MONO)"; break; + case 0x03: p = "MONO4 (NICAM ANALOG-Language C/Analog Fallback)"; break; + case 0x04: p = "STEREO"; break; + case 0x05: p = "DUAL1 (AB)"; break; + case 0x06: p = "DUAL2 (AC) (FM)"; break; + case 0x07: p = "DUAL3 (BC) (FM)"; break; + case 0x08: p = "DUAL4 (AC) (AM)"; break; + case 0x09: p = "DUAL5 (BC) (AM)"; break; + case 0x0a: p = "SAP"; break; + default: p = "undefined"; + } + cx25840_info("Configured audio mode: %s\n", p); + } else { + switch (audio_config & 0xF) { + case 0x00: p = "BG"; break; + case 0x01: p = "DK1"; break; + case 0x02: p = "DK2"; break; + case 0x03: p = "DK3"; break; + case 0x04: p = "I"; break; + case 0x05: p = "L"; break; + case 0x06: p = "BTSC"; break; + case 0x07: p = "EIAJ"; break; + case 0x08: p = "A2-M"; break; + case 0x09: p = "FM Radio"; break; + case 0x0f: p = "automatic standard and mode detection"; break; + default: p = "undefined"; + } + cx25840_info("Configured audio system: %s\n", p); + } + + cx25840_info("Specified standard: %s\n", + vidfmt_sel ? fmt_strs[vidfmt_sel] : "automatic detection"); + + switch (state->input) { + case CX25840_COMPOSITE0: p = "Composite 0"; break; + case CX25840_COMPOSITE1: p = "Composite 1"; break; + case CX25840_SVIDEO0: p = "S-Video 0"; break; + case CX25840_SVIDEO1: p = "S-Video 1"; break; + case CX25840_TUNER: p = "Tuner"; break; + } + cx25840_info("Specified input: %s\n", p); + cx25840_info("Specified audio input: %s\n", + state->audio_input == 0 ? "Tuner" : "External"); + + switch (state->audclk_freq) { + case V4L2_AUDCLK_441_KHZ: p = "44.1 kHz"; break; + case V4L2_AUDCLK_48_KHZ: p = "48 kHz"; break; + case V4L2_AUDCLK_32_KHZ: p = "32 kHz"; break; + default: p = "undefined"; + } + cx25840_info("Specified audioclock freq: %s\n", p); + + switch (pref_mode & 0xf) { + case 0: p = "mono/language A"; break; + case 1: p = "language B"; break; + case 2: p = "language C"; break; + case 3: p = "analog fallback"; break; + case 4: p = "stereo"; break; + case 5: p = "language AC"; break; + case 6: p = "language BC"; break; + case 7: p = "language AB"; break; + default: p = "undefined"; + } + cx25840_info("Preferred audio mode: %s\n", p); + + if ((audio_config & 0xf) == 0xf) { + switch ((afc0 >> 3) & 0x3) { + case 0: p = "system DK"; break; + case 1: p = "system L"; break; + case 2: p = "autodetect"; break; + default: p = "undefined"; + } + cx25840_info("Selected 65 MHz format: %s\n", p); + + switch (afc0 & 0x7) { + case 0: p = "chroma"; break; + case 1: p = "BTSC"; break; + case 2: p = "EIAJ"; break; + case 3: p = "A2-M"; break; + case 4: p = "autodetect"; break; + default: p = "undefined"; + } + cx25840_info("Selected 45 MHz format: %s\n", p); + } +} diff --git a/drivers/media/video/cx25840/cx25840-firmware.c b/drivers/media/video/cx25840/cx25840-firmware.c new file mode 100644 index 00000000000..df9d50a7554 --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-firmware.c @@ -0,0 +1,167 @@ +/* cx25840 firmware functions + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/firmware.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +#define FWFILE "v4l-cx25840.fw" +#define FWSEND 1024 + +#define FWDEV(x) &((x)->adapter->dev) + +static int fastfw = 1; +static char *firmware = FWFILE; + +module_param(fastfw, bool, 0444); +module_param(firmware, charp, 0444); + +MODULE_PARM_DESC(fastfw, "Load firmware fast [0=100MHz 1=333MHz (default)]"); +MODULE_PARM_DESC(firmware, "Firmware image [default: " FWFILE "]"); + +static inline void set_i2c_delay(struct i2c_client *client, int delay) +{ + struct i2c_algo_bit_data *algod = client->adapter->algo_data; + + /* We aren't guaranteed to be using algo_bit, + * so avoid the null pointer dereference + * and disable the 'fast firmware load' */ + if (algod) { + algod->udelay = delay; + } else { + fastfw = 0; + } +} + +static inline void start_fw_load(struct i2c_client *client) +{ + /* DL_ADDR_LB=0 DL_ADDR_HB=0 */ + cx25840_write(client, 0x800, 0x00); + cx25840_write(client, 0x801, 0x00); + // DL_MAP=3 DL_AUTO_INC=0 DL_ENABLE=1 + cx25840_write(client, 0x803, 0x0b); + /* AUTO_INC_DIS=1 */ + cx25840_write(client, 0x000, 0x20); + + if (fastfw) + set_i2c_delay(client, 3); +} + +static inline void end_fw_load(struct i2c_client *client) +{ + if (fastfw) + set_i2c_delay(client, 10); + + /* AUTO_INC_DIS=0 */ + cx25840_write(client, 0x000, 0x00); + /* DL_ENABLE=0 */ + cx25840_write(client, 0x803, 0x03); +} + +static inline int check_fw_load(struct i2c_client *client, int size) +{ + /* DL_ADDR_HB DL_ADDR_LB */ + int s = cx25840_read(client, 0x801) << 8; + s |= cx25840_read(client, 0x800); + + if (size != s) { + cx25840_err("firmware %s load failed\n", firmware); + return -EINVAL; + } + + cx25840_info("loaded %s firmware (%d bytes)\n", firmware, size); + return 0; +} + +static inline int fw_write(struct i2c_client *client, u8 * data, int size) +{ + if (i2c_master_send(client, data, size) < size) { + + if (fastfw) { + cx25840_err("333MHz i2c firmware load failed\n"); + fastfw = 0; + set_i2c_delay(client, 10); + + if (i2c_master_send(client, data, size) < size) { + cx25840_err + ("100MHz i2c firmware load failed\n"); + return -ENOSYS; + } + + } else { + cx25840_err("firmware load i2c failure\n"); + return -ENOSYS; + } + + } + + return 0; +} + +int cx25840_loadfw(struct i2c_client *client) +{ + const struct firmware *fw = NULL; + u8 buffer[4], *ptr; + int size, send, retval; + + if (request_firmware(&fw, firmware, FWDEV(client)) != 0) { + cx25840_err("unable to open firmware %s\n", firmware); + return -EINVAL; + } + + start_fw_load(client); + + buffer[0] = 0x08; + buffer[1] = 0x02; + buffer[2] = fw->data[0]; + buffer[3] = fw->data[1]; + retval = fw_write(client, buffer, 4); + + if (retval < 0) { + release_firmware(fw); + return retval; + } + + size = fw->size - 2; + ptr = fw->data; + while (size > 0) { + ptr[0] = 0x08; + ptr[1] = 0x02; + send = size > (FWSEND - 2) ? FWSEND : size + 2; + retval = fw_write(client, ptr, send); + + if (retval < 0) { + release_firmware(fw); + return retval; + } + + size -= FWSEND - 2; + ptr += FWSEND - 2; + } + + end_fw_load(client); + + size = fw->size; + release_firmware(fw); + + return check_fw_load(client, size); +} diff --git a/drivers/media/video/cx25840/cx25840-vbi.c b/drivers/media/video/cx25840/cx25840-vbi.c new file mode 100644 index 00000000000..13ba4e15dde --- /dev/null +++ b/drivers/media/video/cx25840/cx25840-vbi.c @@ -0,0 +1,315 @@ +/* cx25840 VBI functions + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <media/v4l2-common.h> + +#include "cx25840.h" + +static inline int odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static inline int decode_vps(u8 * dst, u8 * p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + + u8 c, err = 0; + int i; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | + ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + + return err & 0xf0; +} + +void cx25840_vbi_setup(struct i2c_client *client) +{ + v4l2_std_id std = cx25840_get_v4lstd(client); + + if (std & ~V4L2_STD_NTSC) { + /* datasheet startup, step 8d */ + cx25840_write(client, 0x49f, 0x11); + + cx25840_write(client, 0x470, 0x84); + cx25840_write(client, 0x471, 0x00); + cx25840_write(client, 0x472, 0x2d); + cx25840_write(client, 0x473, 0x5d); + + cx25840_write(client, 0x474, 0x24); + cx25840_write(client, 0x475, 0x40); + cx25840_write(client, 0x476, 0x24); + cx25840_write(client, 0x477, 0x28); + + cx25840_write(client, 0x478, 0x1f); + cx25840_write(client, 0x479, 0x02); + + if (std & V4L2_STD_SECAM) { + cx25840_write(client, 0x47a, 0x80); + cx25840_write(client, 0x47b, 0x00); + cx25840_write(client, 0x47c, 0x5f); + cx25840_write(client, 0x47d, 0x42); + } else { + cx25840_write(client, 0x47a, 0x90); + cx25840_write(client, 0x47b, 0x20); + cx25840_write(client, 0x47c, 0x63); + cx25840_write(client, 0x47d, 0x82); + } + + cx25840_write(client, 0x47e, 0x0a); + cx25840_write(client, 0x47f, 0x01); + } else { + /* datasheet startup, step 8d */ + cx25840_write(client, 0x49f, 0x14); + + cx25840_write(client, 0x470, 0x7a); + cx25840_write(client, 0x471, 0x00); + cx25840_write(client, 0x472, 0x2d); + cx25840_write(client, 0x473, 0x5b); + + cx25840_write(client, 0x474, 0x1a); + cx25840_write(client, 0x475, 0x70); + cx25840_write(client, 0x476, 0x1e); + cx25840_write(client, 0x477, 0x1e); + + cx25840_write(client, 0x478, 0x1f); + cx25840_write(client, 0x479, 0x02); + cx25840_write(client, 0x47a, 0x50); + cx25840_write(client, 0x47b, 0x66); + + cx25840_write(client, 0x47c, 0x1f); + cx25840_write(client, 0x47d, 0x7c); + cx25840_write(client, 0x47e, 0x08); + cx25840_write(client, 0x47f, 0x00); + } +} + +int cx25840_vbi(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct v4l2_format *fmt; + struct v4l2_sliced_vbi_format *svbi; + + switch (cmd) { + case VIDIOC_G_FMT: + { + static u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_WSS_625, 0, /* 4 */ + V4L2_SLICED_CAPTION_525, /* 6 */ + 0, 0, V4L2_SLICED_VPS, 0, 0, /* 9 */ + 0, 0, 0, 0 + }; + int i; + + fmt = arg; + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + svbi = &fmt->fmt.sliced; + memset(svbi, 0, sizeof(*svbi)); + /* we're done if raw VBI is active */ + if ((cx25840_read(client, 0x404) & 0x10) == 0) + break; + + for (i = 7; i <= 23; i++) { + u8 v = cx25840_read(client, 0x424 + i - 7); + + svbi->service_lines[0][i] = lcr2vbi[v >> 4]; + svbi->service_lines[1][i] = lcr2vbi[v & 0xf]; + svbi->service_set |= + svbi->service_lines[0][i] | svbi->service_lines[1][i]; + } + break; + } + + case VIDIOC_S_FMT: + { + int is_pal = !(cx25840_get_v4lstd(client) & V4L2_STD_NTSC); + int vbi_offset = is_pal ? 1 : 0; + int i, x; + u8 lcr[24]; + + fmt = arg; + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + svbi = &fmt->fmt.sliced; + if (svbi->service_set == 0) { + /* raw VBI */ + memset(svbi, 0, sizeof(*svbi)); + + /* Setup VBI */ + cx25840_vbi_setup(client); + + /* VBI Offset */ + cx25840_write(client, 0x47f, vbi_offset); + cx25840_write(client, 0x404, 0x2e); + break; + } + + for (x = 0; x <= 23; x++) + lcr[x] = 0x00; + + /* Setup VBI */ + cx25840_vbi_setup(client); + + /* Sliced VBI */ + cx25840_write(client, 0x404, 0x36); /* Ancillery data */ + cx25840_write(client, 0x406, 0x13); + cx25840_write(client, 0x47f, vbi_offset); + + if (is_pal) { + for (i = 0; i <= 6; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } else { + for (i = 0; i <= 9; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + + for (i = 22; i <= 23; i++) + svbi->service_lines[0][i] = + svbi->service_lines[1][i] = 0; + } + + for (i = 7; i <= 23; i++) { + for (x = 0; x <= 1; x++) { + switch (svbi->service_lines[1-x][i]) { + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 6 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 9 << (4 * x); + break; + } + } + } + + for (x = 1, i = 0x424; i <= 0x434; i++, x++) { + cx25840_write(client, i, lcr[6 + x]); + } + + cx25840_write(client, 0x43c, 0x16); + + if (is_pal) { + cx25840_write(client, 0x474, 0x2a); + } else { + cx25840_write(client, 0x474, 0x1a + 6); + } + break; + } + + case VIDIOC_INT_DECODE_VBI_LINE: + { + struct v4l2_decode_vbi_line *vbi = arg; + u8 *p = vbi->p; + int id1, id2, l, err = 0; + + if (p[0] || p[1] != 0xff || p[2] != 0xff || + (p[3] != 0x55 && p[3] != 0x91)) { + vbi->line = vbi->type = 0; + break; + } + + p += 4; + id1 = p[-1]; + id2 = p[0] & 0xf; + l = p[2] & 0x3f; + l += 5; + p += 4; + + switch (id2) { + case 1: + id2 = V4L2_SLICED_TELETEXT_B; + break; + case 4: + id2 = V4L2_SLICED_WSS_625; + break; + case 6: + id2 = V4L2_SLICED_CAPTION_525; + err = !odd_parity(p[0]) || !odd_parity(p[1]); + break; + case 9: + id2 = V4L2_SLICED_VPS; + if (decode_vps(p, p) != 0) { + err = 1; + } + break; + default: + id2 = 0; + err = 1; + break; + } + + vbi->type = err ? 0 : id2; + vbi->line = err ? 0 : l; + vbi->is_second_field = err ? 0 : (id1 == 0x55); + vbi->p = p; + break; + } + } + + return 0; +} diff --git a/drivers/media/video/cx25840/cx25840.h b/drivers/media/video/cx25840/cx25840.h new file mode 100644 index 00000000000..5c3f0639fb7 --- /dev/null +++ b/drivers/media/video/cx25840/cx25840.h @@ -0,0 +1,85 @@ +/* cx25840 API header + * + * Copyright (C) 2003-2004 Chris Kennedy + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _CX25840_H_ +#define _CX25840_H_ + + +#include <linux/videodev2.h> +#include <linux/i2c.h> + +extern int cx25840_debug; + +#define cx25840_dbg(fmt, arg...) do { if (cx25840_debug) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +#define cx25840_err(fmt, arg...) do { \ + printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +#define cx25840_info(fmt, arg...) do { \ + printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +#define CX25840_CID_CARDTYPE (V4L2_CID_PRIVATE_BASE+0) + +enum cx25840_cardtype { + CARDTYPE_PVR150, + CARDTYPE_PG600 +}; + +enum cx25840_input { + CX25840_TUNER, + CX25840_COMPOSITE0, + CX25840_COMPOSITE1, + CX25840_SVIDEO0, + CX25840_SVIDEO1 +}; + +struct cx25840_state { + enum cx25840_cardtype cardtype; + enum cx25840_input input; + int audio_input; + enum v4l2_audio_clock_freq audclk_freq; +}; + +/* ----------------------------------------------------------------------- */ +/* cx25850-core.c */ +int cx25840_write(struct i2c_client *client, u16 addr, u8 value); +int cx25840_write4(struct i2c_client *client, u16 addr, u32 value); +u8 cx25840_read(struct i2c_client *client, u16 addr); +u32 cx25840_read4(struct i2c_client *client, u16 addr); +int cx25840_and_or(struct i2c_client *client, u16 addr, u8 mask, u8 value); +v4l2_std_id cx25840_get_v4lstd(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-firmware.c */ +int cx25840_loadfw(struct i2c_client *client); + +/* ----------------------------------------------------------------------- */ +/* cx25850-audio.c */ +int cx25840_audio(struct i2c_client *client, unsigned int cmd, void *arg); + +/* ----------------------------------------------------------------------- */ +/* cx25850-vbi.c */ +void cx25840_vbi_setup(struct i2c_client *client); +int cx25840_vbi(struct i2c_client *client, unsigned int cmd, void *arg); + +#endif diff --git a/drivers/media/video/cx88/cx88-dvb.c b/drivers/media/video/cx88/cx88-dvb.c index 9cce91ec334..99ea955f598 100644 --- a/drivers/media/video/cx88/cx88-dvb.c +++ b/drivers/media/video/cx88/cx88-dvb.c @@ -439,9 +439,6 @@ static int dvb_register(struct cx8802_dev *dev) /* Put the analog decoder in standby to keep it quiet */ cx88_call_i2c_clients (dev->core, TUNER_SET_STANDBY, NULL); - /* Put the analog decoder in standby to keep it quiet */ - cx88_call_i2c_clients (dev->core, TUNER_SET_STANDBY, NULL); - /* register everything */ return videobuf_dvb_register(&dev->dvb, THIS_MODULE, dev); } diff --git a/drivers/media/video/em28xx/em28xx-input.c b/drivers/media/video/em28xx/em28xx-input.c index 32c49df58ad..9b94f77d6fd 100644 --- a/drivers/media/video/em28xx/em28xx-input.c +++ b/drivers/media/video/em28xx/em28xx-input.c @@ -120,9 +120,6 @@ static int get_key_em_haup(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) if (buf[1]==0xff) return 0; - /* avoid fast reapeating */ - if (buf[1]==ir->old) - return 0; ir->old=buf[1]; /* Rearranges bits to the right order */ diff --git a/drivers/media/video/ir-kbd-gpio.c b/drivers/media/video/ir-kbd-gpio.c index ed81934ef3c..5abfc0fbf6d 100644 --- a/drivers/media/video/ir-kbd-gpio.c +++ b/drivers/media/video/ir-kbd-gpio.c @@ -221,24 +221,99 @@ static IR_KEYTAB_TYPE ir_codes_conceptronic[IR_KEYTAB_SIZE] = { [ 24 ] = KEY_MUTE // mute/unmute }; +static IR_KEYTAB_TYPE ir_codes_nebula[IR_KEYTAB_SIZE] = { + [0x00] = KEY_KP0, + [0x01] = KEY_KP1, + [0x02] = KEY_KP2, + [0x03] = KEY_KP3, + [0x04] = KEY_KP4, + [0x05] = KEY_KP5, + [0x06] = KEY_KP6, + [0x07] = KEY_KP7, + [0x08] = KEY_KP8, + [0x09] = KEY_KP9, + [0x0a] = KEY_TV, + [0x0b] = KEY_AUX, + [0x0c] = KEY_DVD, + [0x0d] = KEY_POWER, + [0x0e] = KEY_MHP, /* labelled 'Picture' */ + [0x0f] = KEY_AUDIO, + [0x10] = KEY_INFO, + [0x11] = KEY_F13, /* 16:9 */ + [0x12] = KEY_F14, /* 14:9 */ + [0x13] = KEY_EPG, + [0x14] = KEY_EXIT, + [0x15] = KEY_MENU, + [0x16] = KEY_UP, + [0x17] = KEY_DOWN, + [0x18] = KEY_LEFT, + [0x19] = KEY_RIGHT, + [0x1a] = KEY_ENTER, + [0x1b] = KEY_CHANNELUP, + [0x1c] = KEY_CHANNELDOWN, + [0x1d] = KEY_VOLUMEUP, + [0x1e] = KEY_VOLUMEDOWN, + [0x1f] = KEY_RED, + [0x20] = KEY_GREEN, + [0x21] = KEY_YELLOW, + [0x22] = KEY_BLUE, + [0x23] = KEY_SUBTITLE, + [0x24] = KEY_F15, /* AD */ + [0x25] = KEY_TEXT, + [0x26] = KEY_MUTE, + [0x27] = KEY_REWIND, + [0x28] = KEY_STOP, + [0x29] = KEY_PLAY, + [0x2a] = KEY_FASTFORWARD, + [0x2b] = KEY_F16, /* chapter */ + [0x2c] = KEY_PAUSE, + [0x2d] = KEY_PLAY, + [0x2e] = KEY_RECORD, + [0x2f] = KEY_F17, /* picture in picture */ + [0x30] = KEY_KPPLUS, /* zoom in */ + [0x31] = KEY_KPMINUS, /* zoom out */ + [0x32] = KEY_F18, /* capture */ + [0x33] = KEY_F19, /* web */ + [0x34] = KEY_EMAIL, + [0x35] = KEY_PHONE, + [0x36] = KEY_PC +}; + struct IR { struct bttv_sub_device *sub; struct input_dev *input; struct ir_input_state ir; char name[32]; char phys[32]; + + /* Usual gpio signalling */ + u32 mask_keycode; u32 mask_keydown; u32 mask_keyup; - - int polling; + u32 polling; u32 last_gpio; struct work_struct work; struct timer_list timer; + + /* RC5 gpio */ + + u32 rc5_gpio; + struct timer_list timer_end; /* timer_end for code completion */ + struct timer_list timer_keyup; /* timer_end for key release */ + u32 last_rc5; /* last good rc5 code */ + u32 last_bit; /* last raw bit seen */ + u32 code; /* raw code under construction */ + struct timeval base_time; /* time of last seen code */ + int active; /* building raw code */ }; static int debug; module_param(debug, int, 0644); /* debug level (0,1,2) */ +static int repeat_delay = 500; +module_param(repeat_delay, int, 0644); +static int repeat_period = 33; +module_param(repeat_period, int, 0644); #define DEVNAME "ir-kbd-gpio" #define dprintk(fmt, arg...) if (debug) \ @@ -254,7 +329,7 @@ static struct bttv_sub_driver driver = { .probe = ir_probe, .remove = ir_remove, }, - .gpio_irq = ir_irq, + .gpio_irq = ir_irq, }; /* ---------------------------------------------------------------------- */ @@ -327,6 +402,173 @@ static void ir_work(void *data) mod_timer(&ir->timer, timeout); } +/* ---------------------------------------------------------------*/ + +static int rc5_remote_gap = 885; +module_param(rc5_remote_gap, int, 0644); +static int rc5_key_timeout = 200; +module_param(rc5_key_timeout, int, 0644); + +#define RC5_START(x) (((x)>>12)&3) +#define RC5_TOGGLE(x) (((x)>>11)&1) +#define RC5_ADDR(x) (((x)>>6)&31) +#define RC5_INSTR(x) ((x)&63) + +/* decode raw bit pattern to RC5 code */ +static u32 rc5_decode(unsigned int code) +{ + unsigned int org_code = code; + unsigned int pair; + unsigned int rc5 = 0; + int i; + + code = (code << 1) | 1; + for (i = 0; i < 14; ++i) { + pair = code & 0x3; + code >>= 2; + + rc5 <<= 1; + switch (pair) { + case 0: + case 2: + break; + case 1: + rc5 |= 1; + break; + case 3: + dprintk("bad code: %x\n", org_code); + return 0; + } + } + dprintk("code=%x, rc5=%x, start=%x, toggle=%x, address=%x, " + "instr=%x\n", rc5, org_code, RC5_START(rc5), + RC5_TOGGLE(rc5), RC5_ADDR(rc5), RC5_INSTR(rc5)); + return rc5; +} + +static int ir_rc5_irq(struct bttv_sub_device *sub) +{ + struct IR *ir = dev_get_drvdata(&sub->dev); + struct timeval tv; + u32 gpio; + u32 gap; + unsigned long current_jiffies, timeout; + + /* read gpio port */ + gpio = bttv_gpio_read(ir->sub->core); + + /* remote IRQ? */ + if (!(gpio & 0x20)) + return 0; + + /* get time of bit */ + current_jiffies = jiffies; + do_gettimeofday(&tv); + + /* avoid overflow with gap >1s */ + if (tv.tv_sec - ir->base_time.tv_sec > 1) { + gap = 200000; + } else { + gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) + + tv.tv_usec - ir->base_time.tv_usec; + } + + /* active code => add bit */ + if (ir->active) { + /* only if in the code (otherwise spurious IRQ or timer + late) */ + if (ir->last_bit < 28) { + ir->last_bit = (gap - rc5_remote_gap / 2) / + rc5_remote_gap; + ir->code |= 1 << ir->last_bit; + } + /* starting new code */ + } else { + ir->active = 1; + ir->code = 0; + ir->base_time = tv; + ir->last_bit = 0; + + timeout = current_jiffies + (500 + 30 * HZ) / 1000; + mod_timer(&ir->timer_end, timeout); + } + + /* toggle GPIO pin 4 to reset the irq */ + bttv_gpio_write(ir->sub->core, gpio & ~(1 << 4)); + bttv_gpio_write(ir->sub->core, gpio | (1 << 4)); + return 1; +} + +static void ir_rc5_timer_end(unsigned long data) +{ + struct IR *ir = (struct IR *)data; + struct timeval tv; + unsigned long current_jiffies, timeout; + u32 gap; + + /* get time */ + current_jiffies = jiffies; + do_gettimeofday(&tv); + + /* avoid overflow with gap >1s */ + if (tv.tv_sec - ir->base_time.tv_sec > 1) { + gap = 200000; + } else { + gap = 1000000 * (tv.tv_sec - ir->base_time.tv_sec) + + tv.tv_usec - ir->base_time.tv_usec; + } + + /* Allow some timmer jitter (RC5 is ~24ms anyway so this is ok) */ + if (gap < 28000) { + dprintk("spurious timer_end\n"); + return; + } + + ir->active = 0; + if (ir->last_bit < 20) { + /* ignore spurious codes (caused by light/other remotes) */ + dprintk("short code: %x\n", ir->code); + } else { + u32 rc5 = rc5_decode(ir->code); + + /* two start bits? */ + if (RC5_START(rc5) != 3) { + dprintk("rc5 start bits invalid: %u\n", RC5_START(rc5)); + + /* right address? */ + } else if (RC5_ADDR(rc5) == 0x0) { + u32 toggle = RC5_TOGGLE(rc5); + u32 instr = RC5_INSTR(rc5); + + /* Good code, decide if repeat/repress */ + if (toggle != RC5_TOGGLE(ir->last_rc5) || + instr != RC5_INSTR(ir->last_rc5)) { + dprintk("instruction %x, toggle %x\n", instr, + toggle); + ir_input_nokey(ir->input, &ir->ir); + ir_input_keydown(ir->input, &ir->ir, instr, + instr); + } + + /* Set/reset key-up timer */ + timeout = current_jiffies + (500 + rc5_key_timeout + * HZ) / 1000; + mod_timer(&ir->timer_keyup, timeout); + + /* Save code for repeat test */ + ir->last_rc5 = rc5; + } + } +} + +static void ir_rc5_timer_keyup(unsigned long data) +{ + struct IR *ir = (struct IR *)data; + + dprintk("key released\n"); + ir_input_nokey(ir->input, &ir->ir); +} + /* ---------------------------------------------------------------------- */ static int ir_probe(struct device *dev) @@ -400,6 +642,12 @@ static int ir_probe(struct device *dev) ir->mask_keyup = 0x006000; ir->polling = 50; // ms break; + case BTTV_BOARD_NEBULA_DIGITV: + ir_codes = ir_codes_nebula; + driver.any_irq = ir_rc5_irq; + driver.gpio_irq = NULL; + ir->rc5_gpio = 1; + break; } if (NULL == ir_codes) { kfree(ir); @@ -407,9 +655,17 @@ static int ir_probe(struct device *dev) return -ENODEV; } - /* init hardware-specific stuff */ - bttv_gpio_inout(sub->core, ir->mask_keycode | ir->mask_keydown, 0); - ir->sub = sub; + if (ir->rc5_gpio) { + u32 gpio; + /* enable remote irq */ + bttv_gpio_inout(sub->core, (1 << 4), 1 << 4); + gpio = bttv_gpio_read(sub->core); + bttv_gpio_write(sub->core, gpio & ~(1 << 4)); + bttv_gpio_write(sub->core, gpio | (1 << 4)); + } else { + /* init hardware-specific stuff */ + bttv_gpio_inout(sub->core, ir->mask_keycode | ir->mask_keydown, 0); + } /* init input device */ snprintf(ir->name, sizeof(ir->name), "bttv IR (card=%d)", @@ -417,6 +673,7 @@ static int ir_probe(struct device *dev) snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0", pci_name(sub->core->pci)); + ir->sub = sub; ir_input_init(input_dev, &ir->ir, ir_type, ir_codes); input_dev->name = ir->name; input_dev->phys = ir->phys; @@ -437,11 +694,25 @@ static int ir_probe(struct device *dev) ir->timer.function = ir_timer; ir->timer.data = (unsigned long)ir; schedule_work(&ir->work); + } else if (ir->rc5_gpio) { + /* set timer_end for code completion */ + init_timer(&ir->timer_end); + ir->timer_end.function = ir_rc5_timer_end; + ir->timer_end.data = (unsigned long)ir; + + init_timer(&ir->timer_keyup); + ir->timer_keyup.function = ir_rc5_timer_keyup; + ir->timer_keyup.data = (unsigned long)ir; } /* all done */ dev_set_drvdata(dev, ir); input_register_device(ir->input); + printk(DEVNAME ": %s detected at %s\n",ir->name,ir->phys); + + /* the remote isn't as bouncy as a keyboard */ + ir->input->rep[REP_DELAY] = repeat_delay; + ir->input->rep[REP_PERIOD] = repeat_period; return 0; } @@ -454,6 +725,15 @@ static int ir_remove(struct device *dev) del_timer(&ir->timer); flush_scheduled_work(); } + if (ir->rc5_gpio) { + u32 gpio; + + del_timer(&ir->timer_end); + flush_scheduled_work(); + + gpio = bttv_gpio_read(ir->sub->core); + bttv_gpio_write(ir->sub->core, gpio & ~(1 << 4)); + } input_unregister_device(ir->input); kfree(ir); diff --git a/drivers/media/video/ir-kbd-i2c.c b/drivers/media/video/ir-kbd-i2c.c index 0085567a142..801c736e932 100644 --- a/drivers/media/video/ir-kbd-i2c.c +++ b/drivers/media/video/ir-kbd-i2c.c @@ -183,6 +183,58 @@ static int get_key_knc1(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) return 1; } +/* The new pinnacle PCTV remote (with the colored buttons) + * + * Ricardo Cerqueira <v4l@cerqueira.org> + */ + +int get_key_pinnacle(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) +{ + unsigned char b[4]; + unsigned int start = 0,parity = 0,code = 0; + + /* poll IR chip */ + if (4 != i2c_master_recv(&ir->c,b,4)) { + dprintk(2,"read error\n"); + return -EIO; + } + + for (start = 0; start<4; start++) { + if (b[start] == 0x80) { + code=b[(start+3)%4]; + parity=b[(start+2)%4]; + } + } + + /* Empty Request */ + if (parity==0) + return 0; + + /* Repeating... */ + if (ir->old == parity) + return 0; + + + ir->old = parity; + + /* Reduce code value to fit inside IR_KEYTAB_SIZE + * + * this is the only value that results in 42 unique + * codes < 128 + */ + + code %= 0x88; + + *ir_raw = code; + *ir_key = code; + + dprintk(1,"Pinnacle PCTV key %02x\n", code); + + return 1; +} + +EXPORT_SYMBOL_GPL(get_key_pinnacle); + /* ----------------------------------------------------------------------- */ static void ir_key_poll(struct IR_i2c *ir) diff --git a/drivers/media/video/saa7115.c b/drivers/media/video/saa7115.c new file mode 100644 index 00000000000..0235cef07b3 --- /dev/null +++ b/drivers/media/video/saa7115.c @@ -0,0 +1,1376 @@ +/* saa7115 - Philips SAA7114/SAA7115 video decoder driver + * + * Based on saa7114 driver by Maxim Yevtyushkin, which is based on + * the saa7111 driver by Dave Perks. + * + * Copyright (C) 1998 Dave Perks <dperks@ibm.net> + * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com> + * + * Slight changes for video timing and attachment output by + * Wolfgang Scherr <scherr@net4you.net> + * + * Moved over to the linux >= 2.4.x i2c protocol (1/1/2003) + * by Ronald Bultje <rbultje@ronald.bitfreak.net> + * + * Added saa7115 support by Kevin Thayer <nufan_wfk at yahoo.com> + * (2/17/2003) + * + * VBI support (2004) and cleanups (2005) by Hans Verkuil <hverkuil@xs4all.nl> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> + +MODULE_DESCRIPTION("Philips SAA7114/SAA7115 video decoder driver"); +MODULE_AUTHOR("Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_LICENSE("GPL"); + +static int debug = 0; +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define saa7115_dbg(fmt,arg...) \ + do { \ + if (debug) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); \ + } while (0) + +#define saa7115_err(fmt, arg...) do { \ + printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) +#define saa7115_info(fmt, arg...) do { \ + printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +static unsigned short normal_i2c[] = { 0x42 >> 1, 0x40 >> 1, I2C_CLIENT_END }; + + +I2C_CLIENT_INSMOD; + +struct saa7115_state { + v4l2_std_id std; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; + enum v4l2_chip_ident ident; + enum v4l2_audio_clock_freq audclk_freq; +}; + +/* ----------------------------------------------------------------------- */ + +static inline int saa7115_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int saa7115_writeregs(struct i2c_client *client, const unsigned char *regs) +{ + unsigned char reg, data; + + while (*regs != 0x00) { + reg = *(regs++); + data = *(regs++); + if (saa7115_write(client, reg, data) < 0) + return -1; + } + return 0; +} + +static inline int saa7115_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +/* If a value differs from the Hauppauge driver values, then the comment starts with + 'was 0xXX' to denote the Hauppauge value. Otherwise the value is identical to what the + Hauppauge driver sets. */ + +static const unsigned char saa7115_init_auto_input[] = { + 0x01, 0x48, /* white peak control disabled */ + 0x03, 0x20, /* was 0x30. 0x20: long vertical blanking */ + 0x04, 0x90, /* analog gain set to 0 */ + 0x05, 0x90, /* analog gain set to 0 */ + 0x06, 0xeb, /* horiz sync begin = -21 */ + 0x07, 0xe0, /* horiz sync stop = -17 */ + 0x0a, 0x80, /* was 0x88. decoder brightness, 0x80 is itu standard */ + 0x0b, 0x44, /* was 0x48. decoder contrast, 0x44 is itu standard */ + 0x0c, 0x40, /* was 0x47. decoder saturation, 0x40 is itu standard */ + 0x0d, 0x00, /* chrominance hue control */ + 0x0f, 0x00, /* chrominance gain control: use automicatic mode */ + 0x10, 0x06, /* chrominance/luminance control: active adaptive combfilter */ + 0x11, 0x00, /* delay control */ + 0x12, 0x9d, /* RTS0 output control: VGATE */ + 0x13, 0x80, /* X-port output control: ITU656 standard mode, RTCO output enable RTCE */ + 0x14, 0x00, /* analog/ADC/auto compatibility control */ + 0x18, 0x40, /* raw data gain 0x00 = nominal */ + 0x19, 0x80, /* raw data offset 0x80 = 0 LSB */ + 0x1a, 0x77, /* color killer level control 0x77 = recommended */ + 0x1b, 0x42, /* misc chroma control 0x42 = recommended */ + 0x1c, 0xa9, /* combfilter control 0xA9 = recommended */ + 0x1d, 0x01, /* combfilter control 0x01 = recommended */ + 0x88, 0xd0, /* reset device */ + 0x88, 0xf0, /* set device programmed, all in operational mode */ + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_reset_scaler[] = { + 0x87, 0x00, /* disable I-port output */ + 0x88, 0xd0, /* reset scaler */ + 0x88, 0xf0, /* activate scaler */ + 0x87, 0x01, /* enable I-port output */ + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates ============= */ + +static const unsigned char saa7115_cfg_60hz_fullres_x[] = { + 0xcc, 0xd0, /* hsize low (output), hor. output window size = 0x2d0 = 720 */ + 0xcd, 0x02, /* hsize hi (output) */ + + /* Why not in 60hz-Land, too? */ + 0xd0, 0x01, /* downscale = 1 */ + 0xd8, 0x00, /* hor lum scaling 0x0400 = 1 */ + 0xd9, 0x04, + 0xdc, 0x00, /* hor chrom scaling 0x0200. must be hor lum scaling / 2 */ + 0xdd, 0x02, /* H-scaling incr chroma */ + + 0x00, 0x00 +}; +static const unsigned char saa7115_cfg_60hz_fullres_y[] = { + 0xce, 0xf8, /* vsize low (output), ver. output window size = 248 (but 60hz is 240?) */ + 0xcf, 0x00, /* vsize hi (output) */ + + /* Why not in 60hz-Land, too? */ + 0xd5, 0x40, /* Lum contrast, nominal value = 0x40 */ + 0xd6, 0x40, /* Chroma satur. nominal value = 0x80 */ + + 0xe0, 0x00, /* V-scaling incr luma low */ + 0xe1, 0x04, /* " hi */ + 0xe2, 0x00, /* V-scaling incr chroma low */ + 0xe3, 0x04, /* " hi */ + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_60hz_video[] = { + 0x80, 0x00, /* reset tasks */ + 0x88, 0xd0, /* reset scaler */ + + 0x15, 0x03, /* VGATE pulse start */ + 0x16, 0x11, /* VGATE pulse stop */ + 0x17, 0x9c, /* VGATE MSB and other values */ + + 0x08, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */ + 0x0e, 0x07, /* lots of different stuff... video autodetection is on */ + + 0x5a, 0x06, /* Vertical offset, standard 60hz value for ITU656 line counting */ + + /* Task A */ + 0x90, 0x80, /* Task Handling Control */ + 0x91, 0x48, /* X-port formats/config */ + 0x92, 0x40, /* Input Ref. signal Def. */ + 0x93, 0x84, /* I-port config */ + 0x94, 0x01, /* hoffset low (input), 0x0002 is minimum */ + 0x95, 0x00, /* hoffset hi (input) */ + 0x96, 0xd0, /* hsize low (input), 0x02d0 = 720 */ + 0x97, 0x02, /* hsize hi (input) */ + 0x98, 0x05, /* voffset low (input) */ + 0x99, 0x00, /* voffset hi (input) */ + 0x9a, 0x0c, /* vsize low (input), 0x0c = 12 */ + 0x9b, 0x00, /* vsize hi (input) */ + 0x9c, 0xa0, /* hsize low (output), 0x05a0 = 1440 */ + 0x9d, 0x05, /* hsize hi (output) */ + 0x9e, 0x0c, /* vsize low (output), 0x0c = 12 */ + 0x9f, 0x00, /* vsize hi (output) */ + + /* Task B */ + 0xc0, 0x00, /* Task Handling Control */ + 0xc1, 0x08, /* X-port formats/config */ + 0xc2, 0x00, /* Input Ref. signal Def. */ + 0xc3, 0x80, /* I-port config */ + 0xc4, 0x02, /* hoffset low (input), 0x0002 is minimum */ + 0xc5, 0x00, /* hoffset hi (input) */ + 0xc6, 0xd0, /* hsize low (input), 0x02d0 = 720 */ + 0xc7, 0x02, /* hsize hi (input) */ + 0xc8, 0x12, /* voffset low (input), 0x12 = 18 */ + 0xc9, 0x00, /* voffset hi (input) */ + 0xca, 0xf8, /* vsize low (input), 0xf8 = 248 */ + 0xcb, 0x00, /* vsize hi (input) */ + 0xcc, 0xd0, /* hsize low (output), 0x02d0 = 720 */ + 0xcd, 0x02, /* hsize hi (output) */ + + 0xf0, 0xad, /* Set PLL Register. 60hz 525 lines per frame, 27 MHz */ + 0xf1, 0x05, /* low bit with 0xF0 */ + 0xf5, 0xad, /* Set pulse generator register */ + 0xf6, 0x01, + + 0x87, 0x00, /* Disable I-port output */ + 0x88, 0xd0, /* reset scaler */ + 0x80, 0x20, /* Activate only task "B", continuous mode (was 0xA0) */ + 0x88, 0xf0, /* activate scaler */ + 0x87, 0x01, /* Enable I-port output */ + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_50hz_fullres_x[] = { + 0xcc, 0xd0, /* hsize low (output), 720 same as 60hz */ + 0xcd, 0x02, /* hsize hi (output) */ + + 0xd0, 0x01, /* down scale = 1 */ + 0xd8, 0x00, /* hor lum scaling 0x0400 = 1 */ + 0xd9, 0x04, + 0xdc, 0x00, /* hor chrom scaling 0x0200. must be hor lum scaling / 2 */ + 0xdd, 0x02, /* H-scaling incr chroma */ + + 0x00, 0x00 +}; +static const unsigned char saa7115_cfg_50hz_fullres_y[] = { + 0xce, 0x20, /* vsize low (output), 0x0120 = 288 */ + 0xcf, 0x01, /* vsize hi (output) */ + + 0xd5, 0x40, /* Lum contrast, nominal value = 0x40 */ + 0xd6, 0x40, /* Chroma satur. nominal value = 0x80 */ + + 0xe0, 0x00, /* V-scaling incr luma low */ + 0xe1, 0x04, /* " hi */ + 0xe2, 0x00, /* V-scaling incr chroma low */ + 0xe3, 0x04, /* " hi */ + + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_50hz_video[] = { + 0x80, 0x00, /* reset tasks */ + 0x88, 0xd0, /* reset scaler */ + + 0x15, 0x37, /* VGATE start */ + 0x16, 0x16, /* VGATE stop */ + 0x17, 0x99, /* VGATE MSB and other values */ + + 0x08, 0x28, /* 0x28 = PAL */ + 0x0e, 0x07, /* chrominance control 1 */ + + 0x5a, 0x03, /* Vertical offset, standard 50hz value */ + + /* Task A */ + 0x90, 0x81, /* Task Handling Control */ + 0x91, 0x48, /* X-port formats/config */ + 0x92, 0x40, /* Input Ref. signal Def. */ + 0x93, 0x84, /* I-port config */ + /* This is weird: the datasheet says that you should use 2 as the minimum value, */ + /* but Hauppauge uses 0, and changing that to 2 causes indeed problems (for 50hz) */ + 0x94, 0x00, /* hoffset low (input), 0x0002 is minimum */ + 0x95, 0x00, /* hoffset hi (input) */ + 0x96, 0xd0, /* hsize low (input), 0x02d0 = 720 */ + 0x97, 0x02, /* hsize hi (input) */ + 0x98, 0x03, /* voffset low (input) */ + 0x99, 0x00, /* voffset hi (input) */ + 0x9a, 0x12, /* vsize low (input), 0x12 = 18 */ + 0x9b, 0x00, /* vsize hi (input) */ + 0x9c, 0xa0, /* hsize low (output), 0x05a0 = 1440 */ + 0x9d, 0x05, /* hsize hi (output) */ + 0x9e, 0x12, /* vsize low (output), 0x12 = 18 */ + 0x9f, 0x00, /* vsize hi (output) */ + + /* Task B */ + 0xc0, 0x00, /* Task Handling Control */ + 0xc1, 0x08, /* X-port formats/config */ + 0xc2, 0x00, /* Input Ref. signal Def. */ + 0xc3, 0x80, /* I-port config */ + 0xc4, 0x00, /* hoffset low (input), 0x0002 is minimum. See comment at 0x94 above. */ + 0xc5, 0x00, /* hoffset hi (input) */ + 0xc6, 0xd0, /* hsize low (input), 0x02d0 = 720 */ + 0xc7, 0x02, /* hsize hi (input) */ + 0xc8, 0x16, /* voffset low (input), 0x16 = 22 */ + 0xc9, 0x00, /* voffset hi (input) */ + 0xca, 0x20, /* vsize low (input), 0x0120 = 288 */ + 0xcb, 0x01, /* vsize hi (input) */ + 0xcc, 0xd0, /* hsize low (output), 0x02d0 = 720 */ + 0xcd, 0x02, /* hsize hi (output) */ + 0xce, 0x20, /* vsize low (output), 0x0120 = 288 */ + 0xcf, 0x01, /* vsize hi (output) */ + + 0xf0, 0xb0, /* Set PLL Register. 50hz 625 lines per frame, 27 MHz */ + 0xf1, 0x05, /* low bit with 0xF0, (was 0x05) */ + 0xf5, 0xb0, /* Set pulse generator register */ + 0xf6, 0x01, + + 0x87, 0x00, /* Disable I-port output */ + 0x88, 0xd0, /* reset scaler (was 0xD0) */ + 0x80, 0x20, /* Activate only task "B" */ + 0x88, 0xf0, /* activate scaler */ + 0x87, 0x01, /* Enable I-port output */ + 0x00, 0x00 +}; + +/* ============== SAA7715 VIDEO templates (end) ======= */ + +static const unsigned char saa7115_cfg_vbi_on[] = { + 0x80, 0x00, /* reset tasks */ + 0x88, 0xd0, /* reset scaler */ + 0x80, 0x30, /* Activate both tasks */ + 0x88, 0xf0, /* activate scaler */ + 0x87, 0x01, /* Enable I-port output */ + 0x00, 0x00 +}; + +static const unsigned char saa7115_cfg_vbi_off[] = { + 0x80, 0x00, /* reset tasks */ + 0x88, 0xd0, /* reset scaler */ + 0x80, 0x20, /* Activate only task "B" */ + 0x88, 0xf0, /* activate scaler */ + 0x87, 0x01, /* Enable I-port output */ + 0x00, 0x00 +}; + +static const unsigned char saa7115_init_misc[] = { + 0x38, 0x03, /* audio stuff */ + 0x39, 0x10, + 0x3a, 0x08, + + 0x81, 0x01, /* reg 0x15,0x16 define blanking window */ + 0x82, 0x00, + 0x83, 0x01, /* I port settings */ + 0x84, 0x20, + 0x85, 0x21, + 0x86, 0xc5, + 0x87, 0x01, + + /* Task A */ + 0xa0, 0x01, /* down scale = 1 */ + 0xa1, 0x00, /* prescale accumulation length = 1 */ + 0xa2, 0x00, /* dc gain and fir prefilter control */ + 0xa4, 0x80, /* Lum Brightness, nominal value = 0x80 */ + 0xa5, 0x40, /* Lum contrast, nominal value = 0x40 */ + 0xa6, 0x40, /* Chroma satur. nominal value = 0x80 */ + 0xa8, 0x00, /* hor lum scaling 0x0200 = 2 zoom */ + 0xa9, 0x02, /* note: 2 x zoom ensures that VBI lines have same length as video lines. */ + 0xaa, 0x00, /* H-phase offset Luma = 0 */ + 0xac, 0x00, /* hor chrom scaling 0x0200. must be hor lum scaling / 2 */ + 0xad, 0x01, /* H-scaling incr chroma */ + 0xae, 0x00, /* H-phase offset chroma. must be offset luma / 2 */ + + 0xb0, 0x00, /* V-scaling incr luma low */ + 0xb1, 0x04, /* " hi */ + 0xb2, 0x00, /* V-scaling incr chroma low */ + 0xb3, 0x04, /* " hi */ + 0xb4, 0x01, /* V-scaling mode control */ + 0xb8, 0x00, /* V-phase offset chroma 00 */ + 0xb9, 0x00, /* V-phase offset chroma 01 */ + 0xba, 0x00, /* V-phase offset chroma 10 */ + 0xbb, 0x00, /* V-phase offset chroma 11 */ + 0xbc, 0x00, /* V-phase offset luma 00 */ + 0xbd, 0x00, /* V-phase offset luma 01 */ + 0xbe, 0x00, /* V-phase offset luma 10 */ + 0xbf, 0x00, /* V-phase offset luma 11 */ + + /* Task B */ + 0xd0, 0x01, /* down scale = 1 */ + 0xd1, 0x00, /* prescale accumulation length = 1 */ + 0xd2, 0x00, /* dc gain and fir prefilter control */ + 0xd4, 0x80, /* Lum Brightness, nominal value = 0x80 */ + 0xd5, 0x40, /* Lum contrast, nominal value = 0x40 */ + 0xd6, 0x40, /* Chroma satur. nominal value = 0x80 */ + 0xd8, 0x00, /* hor lum scaling 0x0400 = 1 */ + 0xd9, 0x04, + 0xda, 0x00, /* H-phase offset Luma = 0 */ + 0xdc, 0x00, /* hor chrom scaling 0x0200. must be hor lum scaling / 2 */ + 0xdd, 0x02, /* H-scaling incr chroma */ + 0xde, 0x00, /* H-phase offset chroma. must be offset luma / 2 */ + + 0xe0, 0x00, /* V-scaling incr luma low */ + 0xe1, 0x04, /* " hi */ + 0xe2, 0x00, /* V-scaling incr chroma low */ + 0xe3, 0x04, /* " hi */ + 0xe4, 0x01, /* V-scaling mode control */ + 0xe8, 0x00, /* V-phase offset chroma 00 */ + 0xe9, 0x00, /* V-phase offset chroma 01 */ + 0xea, 0x00, /* V-phase offset chroma 10 */ + 0xeb, 0x00, /* V-phase offset chroma 11 */ + 0xec, 0x00, /* V-phase offset luma 00 */ + 0xed, 0x00, /* V-phase offset luma 01 */ + 0xee, 0x00, /* V-phase offset luma 10 */ + 0xef, 0x00, /* V-phase offset luma 11 */ + + 0xf2, 0x50, /* crystal clock = 24.576 MHz, target = 27MHz */ + 0xf3, 0x46, + 0xf4, 0x00, + 0xf7, 0x4b, /* not the recommended settings! */ + 0xf8, 0x00, + 0xf9, 0x4b, + 0xfa, 0x00, + 0xfb, 0x4b, + 0xff, 0x88, /* PLL2 lock detection settings: 71 lines 50% phase error */ + + /* Turn off VBI */ + 0x40, 0x20, /* No framing code errors allowed. */ + 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, + 0x59, 0x47, + 0x5b, 0x83, + 0x5d, 0xbd, + 0x5e, 0x35, + + 0x02, 0x84, /* input tuner -> input 4, amplifier active */ + 0x09, 0x53, /* 0x53, was 0x56 for 60hz. luminance control */ + + 0x80, 0x20, /* enable task B */ + 0x88, 0xd0, + 0x88, 0xf0, + 0x00, 0x00 +}; + +/* ============== SAA7715 AUDIO settings ============= */ + +/* 48.0 kHz */ +static const unsigned char saa7115_cfg_48_audio[] = { + 0x34, 0xce, + 0x35, 0xfb, + 0x36, 0x30, + 0x00, 0x00 +}; + +/* 44.1 kHz */ +static const unsigned char saa7115_cfg_441_audio[] = { + 0x34, 0xf2, + 0x35, 0x00, + 0x36, 0x2d, + 0x00, 0x00 +}; + +/* 32.0 kHz */ +static const unsigned char saa7115_cfg_32_audio[] = { + 0x34, 0xdf, + 0x35, 0xa7, + 0x36, 0x20, + 0x00, 0x00 +}; + +/* 48.0 kHz 60hz */ +static const unsigned char saa7115_cfg_60hz_48_audio[] = { + 0x30, 0xcd, + 0x31, 0x20, + 0x32, 0x03, + 0x00, 0x00 +}; + +/* 48.0 kHz 50hz */ +static const unsigned char saa7115_cfg_50hz_48_audio[] = { + 0x30, 0x00, + 0x31, 0xc0, + 0x32, 0x03, + 0x00, 0x00 +}; + +/* 44.1 kHz 60hz */ +static const unsigned char saa7115_cfg_60hz_441_audio[] = { + 0x30, 0xbc, + 0x31, 0xdf, + 0x32, 0x02, + 0x00, 0x00 +}; + +/* 44.1 kHz 50hz */ +static const unsigned char saa7115_cfg_50hz_441_audio[] = { + 0x30, 0x00, + 0x31, 0x72, + 0x32, 0x03, + 0x00, 0x00 +}; + +/* 32.0 kHz 60hz */ +static const unsigned char saa7115_cfg_60hz_32_audio[] = { + 0x30, 0xde, + 0x31, 0x15, + 0x32, 0x02, + 0x00, 0x00 +}; + +/* 32.0 kHz 50hz */ +static const unsigned char saa7115_cfg_50hz_32_audio[] = { + 0x30, 0x00, + 0x31, 0x80, + 0x32, 0x02, + 0x00, 0x00 +}; + +static int saa7115_odd_parity(u8 c) +{ + c ^= (c >> 4); + c ^= (c >> 2); + c ^= (c >> 1); + + return c & 1; +} + +static int saa7115_decode_vps(u8 * dst, u8 * p) +{ + static const u8 biphase_tbl[] = { + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xc3, 0x4b, 0x43, 0xc3, 0x87, 0x0f, 0x07, 0x87, + 0x83, 0x0b, 0x03, 0x83, 0xc3, 0x4b, 0x43, 0xc3, + 0xc1, 0x49, 0x41, 0xc1, 0x85, 0x0d, 0x05, 0x85, + 0x81, 0x09, 0x01, 0x81, 0xc1, 0x49, 0x41, 0xc1, + 0xe1, 0x69, 0x61, 0xe1, 0xa5, 0x2d, 0x25, 0xa5, + 0xa1, 0x29, 0x21, 0xa1, 0xe1, 0x69, 0x61, 0xe1, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xc2, 0x4a, 0x42, 0xc2, 0x86, 0x0e, 0x06, 0x86, + 0x82, 0x0a, 0x02, 0x82, 0xc2, 0x4a, 0x42, 0xc2, + 0xc0, 0x48, 0x40, 0xc0, 0x84, 0x0c, 0x04, 0x84, + 0x80, 0x08, 0x00, 0x80, 0xc0, 0x48, 0x40, 0xc0, + 0xe0, 0x68, 0x60, 0xe0, 0xa4, 0x2c, 0x24, 0xa4, + 0xa0, 0x28, 0x20, 0xa0, 0xe0, 0x68, 0x60, 0xe0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + 0xd2, 0x5a, 0x52, 0xd2, 0x96, 0x1e, 0x16, 0x96, + 0x92, 0x1a, 0x12, 0x92, 0xd2, 0x5a, 0x52, 0xd2, + 0xd0, 0x58, 0x50, 0xd0, 0x94, 0x1c, 0x14, 0x94, + 0x90, 0x18, 0x10, 0x90, 0xd0, 0x58, 0x50, 0xd0, + 0xf0, 0x78, 0x70, 0xf0, 0xb4, 0x3c, 0x34, 0xb4, + 0xb0, 0x38, 0x30, 0xb0, 0xf0, 0x78, 0x70, 0xf0, + }; + int i; + u8 c, err = 0; + + for (i = 0; i < 2 * 13; i += 2) { + err |= biphase_tbl[p[i]] | biphase_tbl[p[i + 1]]; + c = (biphase_tbl[p[i + 1]] & 0xf) | ((biphase_tbl[p[i]] & 0xf) << 4); + dst[i / 2] = c; + } + return err & 0xf0; +} + +static int saa7115_decode_wss(u8 * p) +{ + static const int wss_bits[8] = { + 0, 0, 0, 1, 0, 1, 1, 1 + }; + unsigned char parity; + int wss = 0; + int i; + + for (i = 0; i < 16; i++) { + int b1 = wss_bits[p[i] & 7]; + int b2 = wss_bits[(p[i] >> 3) & 7]; + + if (b1 == b2) + return -1; + wss |= b2 << i; + } + parity = wss & 15; + parity ^= parity >> 2; + parity ^= parity >> 1; + + if (!(parity & 1)) + return -1; + + return wss; +} + + +static int saa7115_set_audio_clock_freq(struct i2c_client *client, enum v4l2_audio_clock_freq freq) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + + saa7115_dbg("set audio clock freq: %d\n", freq); + switch (freq) { + case V4L2_AUDCLK_32_KHZ: + saa7115_writeregs(client, saa7115_cfg_32_audio); + if (state->std & V4L2_STD_525_60) { + saa7115_writeregs(client, saa7115_cfg_60hz_32_audio); + } else { + saa7115_writeregs(client, saa7115_cfg_50hz_32_audio); + } + break; + case V4L2_AUDCLK_441_KHZ: + saa7115_writeregs(client, saa7115_cfg_441_audio); + if (state->std & V4L2_STD_525_60) { + saa7115_writeregs(client, saa7115_cfg_60hz_441_audio); + } else { + saa7115_writeregs(client, saa7115_cfg_50hz_441_audio); + } + break; + case V4L2_AUDCLK_48_KHZ: + saa7115_writeregs(client, saa7115_cfg_48_audio); + if (state->std & V4L2_STD_525_60) { + saa7115_writeregs(client, saa7115_cfg_60hz_48_audio); + } else { + saa7115_writeregs(client, saa7115_cfg_50hz_48_audio); + } + break; + default: + saa7115_dbg("invalid audio setting %d\n", freq); + return -EINVAL; + } + state->audclk_freq = freq; + return 0; +} + +static int saa7115_set_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value < 0 || ctrl->value > 255) { + saa7115_err("invalid brightness setting %d\n", ctrl->value); + return -ERANGE; + } + + state->bright = ctrl->value; + saa7115_write(client, 0x0a, state->bright); + break; + + case V4L2_CID_CONTRAST: + if (ctrl->value < 0 || ctrl->value > 127) { + saa7115_err("invalid contrast setting %d\n", ctrl->value); + return -ERANGE; + } + + state->contrast = ctrl->value; + saa7115_write(client, 0x0b, state->contrast); + break; + + case V4L2_CID_SATURATION: + if (ctrl->value < 0 || ctrl->value > 127) { + saa7115_err("invalid saturation setting %d\n", ctrl->value); + return -ERANGE; + } + + state->sat = ctrl->value; + saa7115_write(client, 0x0c, state->sat); + break; + + case V4L2_CID_HUE: + if (ctrl->value < -127 || ctrl->value > 127) { + saa7115_err("invalid hue setting %d\n", ctrl->value); + return -ERANGE; + } + + state->hue = ctrl->value; + saa7115_write(client, 0x0d, state->hue); + break; + } + + return 0; +} + +static int saa7115_get_v4lctrl(struct i2c_client *client, struct v4l2_control *ctrl) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = state->bright; + break; + case V4L2_CID_CONTRAST: + ctrl->value = state->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = state->sat; + break; + case V4L2_CID_HUE: + ctrl->value = state->hue; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void saa7115_set_v4lstd(struct i2c_client *client, v4l2_std_id std) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + int taskb = saa7115_read(client, 0x80) & 0x10; + + // This works for NTSC-M, SECAM-L and the 50Hz PAL variants. + if (std & V4L2_STD_525_60) { + saa7115_dbg("decoder set standard 60 Hz\n"); + saa7115_writeregs(client, saa7115_cfg_60hz_video); + } else { + saa7115_dbg("decoder set standard 50 Hz\n"); + saa7115_writeregs(client, saa7115_cfg_50hz_video); + } + + state->std = std; + + /* restart task B if needed */ + if (taskb && state->ident == V4L2_IDENT_SAA7114) { + saa7115_writeregs(client, saa7115_cfg_vbi_on); + } + + /* switch audio mode too! */ + saa7115_set_audio_clock_freq(client, state->audclk_freq); +} + +static v4l2_std_id saa7115_get_v4lstd(struct i2c_client *client) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + + return state->std; +} + +static void saa7115_log_status(struct i2c_client *client) +{ + static const char * const audclk_freq_strs[] = { + "44.1 kHz", + "48 kHz", + "32 kHz" + }; + struct saa7115_state *state = i2c_get_clientdata(client); + int reg1e, reg1f; + int signalOk; + int vcr; + + saa7115_info("Audio frequency: %s\n", audclk_freq_strs[state->audclk_freq]); + if (client->name[6] == '4') { + /* status for the saa7114 */ + reg1f = saa7115_read(client, 0x1f); + signalOk = (reg1f & 0xc1) == 0x81; + saa7115_info("Video signal: %s\n", signalOk ? "ok" : "bad"); + saa7115_info("Frequency: %s\n", (reg1f & 0x20) ? "60Hz" : "50Hz"); + return; + } + + /* status for the saa7115 */ + reg1e = saa7115_read(client, 0x1e); + reg1f = saa7115_read(client, 0x1f); + + signalOk = (reg1f & 0xc1) == 0x81 && (reg1e & 0xc0) == 0x80; + vcr = !(reg1f & 0x10); + + saa7115_info("Video signal: %s\n", signalOk ? (vcr ? "VCR" : "broadcast/DVD") : "bad"); + saa7115_info("Frequency: %s\n", (reg1f & 0x20) ? "60Hz" : "50Hz"); + + switch (reg1e & 0x03) { + case 1: + saa7115_info("Detected format: NTSC\n"); + break; + case 2: + saa7115_info("Detected format: PAL\n"); + break; + case 3: + saa7115_info("Detected format: SECAM\n"); + break; + default: + saa7115_info("Detected format: BW/No color\n"); + break; + } +} + +/* setup the sliced VBI lcr registers according to the sliced VBI format */ +static void saa7115_set_lcr(struct i2c_client *client, struct v4l2_sliced_vbi_format *fmt) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + int is_50hz = (state->std & V4L2_STD_625_50); + u8 lcr[24]; + int i, x; + + /* saa7114 doesn't yet support VBI */ + if (state->ident == V4L2_IDENT_SAA7114) + return; + + for (i = 0; i <= 23; i++) + lcr[i] = 0xff; + + if (fmt->service_set == 0) { + /* raw VBI */ + if (is_50hz) + for (i = 6; i <= 23; i++) + lcr[i] = 0xdd; + else + for (i = 10; i <= 21; i++) + lcr[i] = 0xdd; + } else { + /* sliced VBI */ + /* first clear lines that cannot be captured */ + if (is_50hz) { + for (i = 0; i <= 5; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + else { + for (i = 0; i <= 9; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + for (i = 22; i <= 23; i++) + fmt->service_lines[0][i] = + fmt->service_lines[1][i] = 0; + } + + /* Now set the lcr values according to the specified service */ + for (i = 6; i <= 23; i++) { + lcr[i] = 0; + for (x = 0; x <= 1; x++) { + switch (fmt->service_lines[1-x][i]) { + case 0: + lcr[i] |= 0xf << (4 * x); + break; + case V4L2_SLICED_TELETEXT_B: + lcr[i] |= 1 << (4 * x); + break; + case V4L2_SLICED_CAPTION_525: + lcr[i] |= 4 << (4 * x); + break; + case V4L2_SLICED_WSS_625: + lcr[i] |= 5 << (4 * x); + break; + case V4L2_SLICED_VPS: + lcr[i] |= 7 << (4 * x); + break; + } + } + } + } + + /* write the lcr registers */ + for (i = 2; i <= 23; i++) { + saa7115_write(client, i - 2 + 0x41, lcr[i]); + } + + /* enable/disable raw VBI capturing */ + saa7115_writeregs(client, fmt->service_set == 0 ? saa7115_cfg_vbi_on : saa7115_cfg_vbi_off); +} + +static int saa7115_get_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + static u16 lcr2vbi[] = { + 0, V4L2_SLICED_TELETEXT_B, 0, /* 1 */ + 0, V4L2_SLICED_CAPTION_525, /* 4 */ + V4L2_SLICED_WSS_625, 0, /* 5 */ + V4L2_SLICED_VPS, 0, 0, 0, 0, /* 7 */ + 0, 0, 0, 0 + }; + struct v4l2_sliced_vbi_format *sliced = &fmt->fmt.sliced; + int i; + + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + memset(sliced, 0, sizeof(*sliced)); + /* done if using raw VBI */ + if (saa7115_read(client, 0x80) & 0x10) + return 0; + for (i = 2; i <= 23; i++) { + u8 v = saa7115_read(client, i - 2 + 0x41); + + sliced->service_lines[0][i] = lcr2vbi[v >> 4]; + sliced->service_lines[1][i] = lcr2vbi[v & 0xf]; + sliced->service_set |= + sliced->service_lines[0][i] | sliced->service_lines[1][i]; + } + return 0; +} + +static int saa7115_set_v4lfmt(struct i2c_client *client, struct v4l2_format *fmt) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + struct v4l2_pix_format *pix; + int HPSC, HFSC; + int VSCY, Vsrc; + int is_50hz = state->std & V4L2_STD_625_50; + + if (fmt->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) { + saa7115_set_lcr(client, &fmt->fmt.sliced); + return 0; + } + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + pix = &(fmt->fmt.pix); + + saa7115_dbg("decoder set size\n"); + + /* FIXME need better bounds checking here */ + if ((pix->width < 1) || (pix->width > 1440)) + return -EINVAL; + if ((pix->height < 1) || (pix->height > 960)) + return -EINVAL; + + /* probably have a valid size, let's set it */ + /* Set output width/height */ + /* width */ + saa7115_write(client, 0xcc, (u8) (pix->width & 0xff)); + saa7115_write(client, 0xcd, (u8) ((pix->width >> 8) & 0xff)); + /* height */ + saa7115_write(client, 0xce, (u8) (pix->height & 0xff)); + saa7115_write(client, 0xcf, (u8) ((pix->height >> 8) & 0xff)); + + /* Scaling settings */ + /* Hprescaler is floor(inres/outres) */ + /* FIXME hardcoding input res */ + if (pix->width != 720) { + HPSC = (int)(720 / pix->width); + /* 0 is not allowed (div. by zero) */ + HPSC = HPSC ? HPSC : 1; + HFSC = (int)((1024 * 720) / (HPSC * pix->width)); + + saa7115_dbg("Hpsc: 0x%05x, Hfsc: 0x%05x\n", HPSC, HFSC); + /* FIXME hardcodes to "Task B" + * write H prescaler integer */ + saa7115_write(client, 0xd0, (u8) (HPSC & 0x3f)); + + /* write H fine-scaling (luminance) */ + saa7115_write(client, 0xd8, (u8) (HFSC & 0xff)); + saa7115_write(client, 0xd9, (u8) ((HFSC >> 8) & 0xff)); + /* write H fine-scaling (chrominance) + * must be lum/2, so i'll just bitshift :) */ + saa7115_write(client, 0xDC, (u8) ((HFSC >> 1) & 0xff)); + saa7115_write(client, 0xDD, (u8) ((HFSC >> 9) & 0xff)); + } else { + if (is_50hz) { + saa7115_dbg("Setting full 50hz width\n"); + saa7115_writeregs(client, saa7115_cfg_50hz_fullres_x); + } else { + saa7115_dbg("Setting full 60hz width\n"); + saa7115_writeregs(client, saa7115_cfg_60hz_fullres_x); + } + } + + Vsrc = is_50hz ? 576 : 480; + + if (pix->height != Vsrc) { + VSCY = (int)((1024 * Vsrc) / pix->height); + saa7115_dbg("Vsrc: %d, Vscy: 0x%05x\n", Vsrc, VSCY); + + /* Correct Contrast and Luminance */ + saa7115_write(client, 0xd5, (u8) (64 * 1024 / VSCY)); + saa7115_write(client, 0xd6, (u8) (64 * 1024 / VSCY)); + + /* write V fine-scaling (luminance) */ + saa7115_write(client, 0xe0, (u8) (VSCY & 0xff)); + saa7115_write(client, 0xe1, (u8) ((VSCY >> 8) & 0xff)); + /* write V fine-scaling (chrominance) */ + saa7115_write(client, 0xe2, (u8) (VSCY & 0xff)); + saa7115_write(client, 0xe3, (u8) ((VSCY >> 8) & 0xff)); + } else { + if (is_50hz) { + saa7115_dbg("Setting full 50Hz height\n"); + saa7115_writeregs(client, saa7115_cfg_50hz_fullres_y); + } else { + saa7115_dbg("Setting full 60hz height\n"); + saa7115_writeregs(client, saa7115_cfg_60hz_fullres_y); + } + } + + saa7115_writeregs(client, saa7115_cfg_reset_scaler); + return 0; +} + +/* Decode the sliced VBI data stream as created by the saa7115. + The format is described in the saa7115 datasheet in Tables 25 and 26 + and in Figure 33. + The current implementation uses SAV/EAV codes and not the ancillary data + headers. The vbi->p pointer points to the SDID byte right after the SAV + code. */ +static void saa7115_decode_vbi_line(struct i2c_client *client, + struct v4l2_decode_vbi_line *vbi) +{ + static const char vbi_no_data_pattern[] = { + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0 + }; + struct saa7115_state *state = i2c_get_clientdata(client); + u8 *p = vbi->p; + u32 wss; + int id1, id2; /* the ID1 and ID2 bytes from the internal header */ + + vbi->type = 0; /* mark result as a failure */ + id1 = p[2]; + id2 = p[3]; + /* Note: the field bit is inverted for 60 Hz video */ + if (state->std & V4L2_STD_525_60) + id1 ^= 0x40; + + /* Skip internal header, p now points to the start of the payload */ + p += 4; + vbi->p = p; + + /* calculate field and line number of the VBI packet (1-23) */ + vbi->is_second_field = ((id1 & 0x40) != 0); + vbi->line = (id1 & 0x3f) << 3; + vbi->line |= (id2 & 0x70) >> 4; + + /* Obtain data type */ + id2 &= 0xf; + + /* If the VBI slicer does not detect any signal it will fill up + the payload buffer with 0xa0 bytes. */ + if (!memcmp(p, vbi_no_data_pattern, sizeof(vbi_no_data_pattern))) + return; + + /* decode payloads */ + switch (id2) { + case 1: + vbi->type = V4L2_SLICED_TELETEXT_B; + break; + case 4: + if (!saa7115_odd_parity(p[0]) || !saa7115_odd_parity(p[1])) + return; + vbi->type = V4L2_SLICED_CAPTION_525; + break; + case 5: + wss = saa7115_decode_wss(p); + if (wss == -1) + return; + p[0] = wss & 0xff; + p[1] = wss >> 8; + vbi->type = V4L2_SLICED_WSS_625; + break; + case 7: + if (saa7115_decode_vps(p, p) != 0) + return; + vbi->type = V4L2_SLICED_VPS; + break; + default: + return; + } +} + +/* ============ SAA7115 AUDIO settings (end) ============= */ + +static int saa7115_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + int *iarg = arg; + + /* ioctls to allow direct access to the saa7115 registers for testing */ + switch (cmd) { + case VIDIOC_S_FMT: + return saa7115_set_v4lfmt(client, (struct v4l2_format *)arg); + + case VIDIOC_G_FMT: + return saa7115_get_v4lfmt(client, (struct v4l2_format *)arg); + + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + return saa7115_set_audio_clock_freq(client, *(enum v4l2_audio_clock_freq *)arg); + + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *vt = arg; + int status; + + status = saa7115_read(client, 0x1f); + + saa7115_dbg("status: 0x%02x\n", status); + vt->signal = ((status & (1 << 6)) == 0) ? 0xffff : 0x0; + break; + } + + case VIDIOC_LOG_STATUS: + saa7115_log_status(client); + break; + + case VIDIOC_G_CTRL: + return saa7115_get_v4lctrl(client, (struct v4l2_control *)arg); + + case VIDIOC_S_CTRL: + return saa7115_set_v4lctrl(client, (struct v4l2_control *)arg); + + case VIDIOC_G_STD: + *(v4l2_std_id *)arg = saa7115_get_v4lstd(client); + break; + + case VIDIOC_S_STD: + saa7115_set_v4lstd(client, *(v4l2_std_id *)arg); + break; + + case VIDIOC_G_INPUT: + *(int *)arg = state->input; + break; + + case VIDIOC_S_INPUT: + saa7115_dbg("decoder set input %d\n", *iarg); + /* inputs from 0-9 are available */ + if (*iarg < 0 || *iarg > 9) { + return -EINVAL; + } + + if (state->input == *iarg) + break; + saa7115_dbg("now setting %s input\n", + *iarg >= 6 ? "S-Video" : "Composite"); + state->input = *iarg; + + /* select mode */ + saa7115_write(client, 0x02, + (saa7115_read(client, 0x02) & 0xf0) | + state->input); + + /* bypass chrominance trap for modes 6..9 */ + saa7115_write(client, 0x09, + (saa7115_read(client, 0x09) & 0x7f) | + (state->input < 6 ? 0x0 : 0x80)); + break; + + case VIDIOC_STREAMON: + case VIDIOC_STREAMOFF: + saa7115_dbg("%s output\n", + (cmd == VIDIOC_STREAMON) ? "enable" : "disable"); + + if (state->enable != (cmd == VIDIOC_STREAMON)) { + state->enable = (cmd == VIDIOC_STREAMON); + saa7115_write(client, 0x87, state->enable); + } + break; + + case VIDIOC_INT_DECODE_VBI_LINE: + saa7115_decode_vbi_line(client, arg); + break; + + case VIDIOC_INT_RESET: + saa7115_dbg("decoder RESET\n"); + saa7115_writeregs(client, saa7115_cfg_reset_scaler); + break; + + case VIDIOC_INT_G_VBI_DATA: + { + struct v4l2_sliced_vbi_data *data = arg; + + switch (data->id) { + case V4L2_SLICED_WSS_625: + if (saa7115_read(client, 0x6b) & 0xc0) + return -EIO; + data->data[0] = saa7115_read(client, 0x6c); + data->data[1] = saa7115_read(client, 0x6d); + return 0; + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) { + /* CC */ + if (saa7115_read(client, 0x66) & 0xc0) + return -EIO; + data->data[0] = saa7115_read(client, 0x67); + data->data[1] = saa7115_read(client, 0x68); + return 0; + } + /* XDS */ + if (saa7115_read(client, 0x66) & 0x30) + return -EIO; + data->data[0] = saa7115_read(client, 0x69); + data->data[1] = saa7115_read(client, 0x6a); + return 0; + default: + return -EINVAL; + } + break; + } + +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_INT_G_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA711X) + return -EINVAL; + reg->val = saa7115_read(client, reg->reg & 0xff); + break; + } + + case VIDIOC_INT_S_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA711X) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + saa7115_write(client, reg->reg & 0xff, reg->val & 0xff); + break; + } +#endif + + case VIDIOC_INT_G_CHIP_IDENT: + *iarg = state->ident; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7115; + +static int saa7115_attach(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + struct saa7115_state *state; + u8 chip_id; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7115; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name) - 1, "saa7115"); + + saa7115_dbg("detecting saa7115 client on address 0x%x\n", address << 1); + + saa7115_write(client, 0, 5); + chip_id = saa7115_read(client, 0) & 0x0f; + if (chip_id != 4 && chip_id != 5) { + saa7115_dbg("saa7115 not found\n"); + kfree(client); + return 0; + } + if (chip_id == 4) { + snprintf(client->name, sizeof(client->name) - 1, "saa7114"); + } + saa7115_info("saa711%d found @ 0x%x (%s)\n", chip_id, address << 1, adapter->name); + + state = kmalloc(sizeof(struct saa7115_state), GFP_KERNEL); + i2c_set_clientdata(client, state); + if (state == NULL) { + kfree(client); + return -ENOMEM; + } + memset(state, 0, sizeof(struct saa7115_state)); + state->std = V4L2_STD_NTSC; + state->input = -1; + state->enable = 1; + state->bright = 128; + state->contrast = 64; + state->hue = 0; + state->sat = 64; + state->ident = (chip_id == 4) ? V4L2_IDENT_SAA7114 : V4L2_IDENT_SAA7115; + state->audclk_freq = V4L2_AUDCLK_48_KHZ; + + saa7115_dbg("writing init values\n"); + + /* init to 60hz/48khz */ + saa7115_writeregs(client, saa7115_init_auto_input); + saa7115_writeregs(client, saa7115_init_misc); + saa7115_writeregs(client, saa7115_cfg_60hz_fullres_x); + saa7115_writeregs(client, saa7115_cfg_60hz_fullres_y); + saa7115_writeregs(client, saa7115_cfg_60hz_video); + saa7115_writeregs(client, saa7115_cfg_48_audio); + saa7115_writeregs(client, saa7115_cfg_60hz_48_audio); + saa7115_writeregs(client, saa7115_cfg_reset_scaler); + + i2c_attach_client(client); + + saa7115_dbg("status: (1E) 0x%02x, (1F) 0x%02x\n", + saa7115_read(client, 0x1e), saa7115_read(client, 0x1f)); + + return 0; +} + +static int saa7115_probe(struct i2c_adapter *adapter) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adapter->class & I2C_CLASS_TV_ANALOG) +#else + if (adapter->id == I2C_HW_B_BT848) +#endif + return i2c_probe(adapter, &addr_data, &saa7115_attach); + return 0; +} + +static int saa7115_detach(struct i2c_client *client) +{ + struct saa7115_state *state = i2c_get_clientdata(client); + int err; + + err = i2c_detach_client(client); + if (err) { + return err; + } + + kfree(state); + kfree(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +/* i2c implementation */ +static struct i2c_driver i2c_driver_saa7115 = { + .name = "saa7115", + .id = I2C_DRIVERID_SAA711X, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa7115_probe, + .detach_client = saa7115_detach, + .command = saa7115_command, + .owner = THIS_MODULE, +}; + + +static int __init saa7115_init_module(void) +{ + return i2c_add_driver(&i2c_driver_saa7115); +} + +static void __exit saa7115_cleanup_module(void) +{ + i2c_del_driver(&i2c_driver_saa7115); +} + +module_init(saa7115_init_module); +module_exit(saa7115_cleanup_module); diff --git a/drivers/media/video/saa711x.c b/drivers/media/video/saa711x.c index 9aa8827de2c..25b30f352d8 100644 --- a/drivers/media/video/saa711x.c +++ b/drivers/media/video/saa711x.c @@ -36,7 +36,6 @@ #include <asm/pgtable.h> #include <asm/page.h> #include <linux/sched.h> -#include <asm/segment.h> #include <linux/types.h> #include <asm/uaccess.h> #include <linux/videodev.h> diff --git a/drivers/media/video/saa7127.c b/drivers/media/video/saa7127.c new file mode 100644 index 00000000000..843431f10e3 --- /dev/null +++ b/drivers/media/video/saa7127.c @@ -0,0 +1,849 @@ +/* + * saa7127 - Philips SAA7127/SAA7129 video encoder driver + * + * Copyright (C) 2003 Roy Bulter <rbulter@hetnet.nl> + * + * Based on SAA7126 video encoder driver by Gillem & Andreas Oberritter + * + * Copyright (C) 2000-2001 Gillem <htoa@gmx.net> + * Copyright (C) 2002 Andreas Oberritter <obi@saftware.de> + * + * Based on Stadis 4:2:2 MPEG-2 Decoder Driver by Nathan Laredo + * + * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org> + * + * This driver is designed for the Hauppauge 250/350 Linux driver + * from the ivtv Project + * + * Copyright (C) 2003 Kevin Thayer <nufan_wfk@yahoo.com> + * + * Dual output support: + * Copyright (C) 2004 Eric Varsanyi + * + * NTSC Tuning and 7.5 IRE Setup + * Copyright (C) 2004 Chris Kennedy <c@groovy.org> + * + * VBI additions & cleanup: + * Copyright (C) 2004, 2005 Hans Verkuil <hverkuil@xs4all.nl> + * + * Note: the saa7126 is identical to the saa7127, and the saa7128 is + * identical to the saa7129, except that the saa7126 and saa7128 have + * macrovision anti-taping support. This driver will almost certainly + * work find for those chips, except of course for the missing anti-taping + * support. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> + +static int debug = 0; +static int test_image = 0; + +MODULE_DESCRIPTION("Philips SAA7127/9 video encoder driver"); +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil"); +MODULE_LICENSE("GPL"); +module_param(debug, int, 0644); +module_param(test_image, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); +MODULE_PARM_DESC(test_image, "test_image (0-1)"); + +#define saa7127_dbg(fmt, arg...) \ + do { \ + if (debug >= 1) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); \ + } while (0) + +/* High volume debug. Use with care. */ +#define saa7127_dbg_highvol(fmt, arg...) \ + do { \ + if (debug == 2) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); \ + } while (0) + +#define saa7127_err(fmt, arg...) do { \ + printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) +#define saa7127_info(fmt, arg...) do { \ + printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +static unsigned short normal_i2c[] = { 0x88 >> 1, I2C_CLIENT_END }; + + +I2C_CLIENT_INSMOD; + +/* + * SAA7127 registers + */ + +#define SAA7127_REG_STATUS 0x00 +#define SAA7127_REG_WIDESCREEN_CONFIG 0x26 +#define SAA7127_REG_WIDESCREEN_ENABLE 0x27 +#define SAA7127_REG_BURST_START 0x28 +#define SAA7127_REG_BURST_END 0x29 +#define SAA7127_REG_COPYGEN_0 0x2a +#define SAA7127_REG_COPYGEN_1 0x2b +#define SAA7127_REG_COPYGEN_2 0x2c +#define SAA7127_REG_OUTPUT_PORT_CONTROL 0x2d +#define SAA7127_REG_GAIN_LUMINANCE_RGB 0x38 +#define SAA7127_REG_GAIN_COLORDIFF_RGB 0x39 +#define SAA7127_REG_INPUT_PORT_CONTROL_1 0x3a +#define SAA7129_REG_FADE_KEY_COL2 0x4f +#define SAA7127_REG_CHROMA_PHASE 0x5a +#define SAA7127_REG_GAINU 0x5b +#define SAA7127_REG_GAINV 0x5c +#define SAA7127_REG_BLACK_LEVEL 0x5d +#define SAA7127_REG_BLANKING_LEVEL 0x5e +#define SAA7127_REG_VBI_BLANKING 0x5f +#define SAA7127_REG_DAC_CONTROL 0x61 +#define SAA7127_REG_BURST_AMP 0x62 +#define SAA7127_REG_SUBC3 0x63 +#define SAA7127_REG_SUBC2 0x64 +#define SAA7127_REG_SUBC1 0x65 +#define SAA7127_REG_SUBC0 0x66 +#define SAA7127_REG_LINE_21_ODD_0 0x67 +#define SAA7127_REG_LINE_21_ODD_1 0x68 +#define SAA7127_REG_LINE_21_EVEN_0 0x69 +#define SAA7127_REG_LINE_21_EVEN_1 0x6a +#define SAA7127_REG_RCV_PORT_CONTROL 0x6b +#define SAA7127_REG_VTRIG 0x6c +#define SAA7127_REG_HTRIG_HI 0x6d +#define SAA7127_REG_MULTI 0x6e +#define SAA7127_REG_CLOSED_CAPTION 0x6f +#define SAA7127_REG_RCV2_OUTPUT_START 0x70 +#define SAA7127_REG_RCV2_OUTPUT_END 0x71 +#define SAA7127_REG_RCV2_OUTPUT_MSBS 0x72 +#define SAA7127_REG_TTX_REQUEST_H_START 0x73 +#define SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH 0x74 +#define SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT 0x75 +#define SAA7127_REG_TTX_ODD_REQ_VERT_START 0x76 +#define SAA7127_REG_TTX_ODD_REQ_VERT_END 0x77 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_START 0x78 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_END 0x79 +#define SAA7127_REG_FIRST_ACTIVE 0x7a +#define SAA7127_REG_LAST_ACTIVE 0x7b +#define SAA7127_REG_MSB_VERTICAL 0x7c +#define SAA7127_REG_DISABLE_TTX_LINE_LO_0 0x7e +#define SAA7127_REG_DISABLE_TTX_LINE_LO_1 0x7f + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the SAA7127 + * + ********************************************************************** + */ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +static const struct i2c_reg_value saa7129_init_config_extra[] = { + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x38 }, + { SAA7127_REG_VTRIG, 0xfa }, +}; + +static const struct i2c_reg_value saa7127_init_config_common[] = { + { SAA7127_REG_WIDESCREEN_CONFIG, 0x0d }, + { SAA7127_REG_WIDESCREEN_ENABLE, 0x00 }, + { SAA7127_REG_COPYGEN_0, 0x77 }, + { SAA7127_REG_COPYGEN_1, 0x41 }, + { SAA7127_REG_COPYGEN_2, 0x00 }, /* Macrovision enable/disable */ + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x9e }, + { SAA7127_REG_GAIN_LUMINANCE_RGB, 0x00 }, + { SAA7127_REG_GAIN_COLORDIFF_RGB, 0x00 }, + { SAA7127_REG_INPUT_PORT_CONTROL_1, 0x80 }, /* for color bars */ + { SAA7127_REG_LINE_21_ODD_0, 0x77 }, + { SAA7127_REG_LINE_21_ODD_1, 0x41 }, + { SAA7127_REG_LINE_21_EVEN_0, 0x88 }, + { SAA7127_REG_LINE_21_EVEN_1, 0x41 }, + { SAA7127_REG_RCV_PORT_CONTROL, 0x12 }, + { SAA7127_REG_VTRIG, 0xf9 }, + { SAA7127_REG_HTRIG_HI, 0x00 }, + { SAA7127_REG_RCV2_OUTPUT_START, 0x41 }, + { SAA7127_REG_RCV2_OUTPUT_END, 0xc3 }, + { SAA7127_REG_RCV2_OUTPUT_MSBS, 0x00 }, + { SAA7127_REG_TTX_REQUEST_H_START, 0x3e }, + { SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH, 0xb8 }, + { SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT, 0x03 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_END, 0x16 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_END, 0x16 }, + { SAA7127_REG_FIRST_ACTIVE, 0x1a }, + { SAA7127_REG_LAST_ACTIVE, 0x01 }, + { SAA7127_REG_MSB_VERTICAL, 0xc0 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_0, 0x00 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_1, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_60HZ_DAC_CONTROL 0x15 +static const struct i2c_reg_value saa7127_init_config_60hz[] = { + { SAA7127_REG_BURST_START, 0x19 }, + /* BURST_END is also used as a chip ID in saa7127_detect_client */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0xa3 }, + { SAA7127_REG_GAINU, 0x98 }, + { SAA7127_REG_GAINV, 0xd3 }, + { SAA7127_REG_BLACK_LEVEL, 0x39 }, + { SAA7127_REG_BLANKING_LEVEL, 0x2e }, + { SAA7127_REG_VBI_BLANKING, 0x2e }, + { SAA7127_REG_DAC_CONTROL, 0x15 }, + { SAA7127_REG_BURST_AMP, 0x4d }, + { SAA7127_REG_SUBC3, 0x1f }, + { SAA7127_REG_SUBC2, 0x7c }, + { SAA7127_REG_SUBC1, 0xf0 }, + { SAA7127_REG_SUBC0, 0x21 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x11 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_DAC_CONTROL 0x02 +struct i2c_reg_value saa7127_init_config_50hz[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_detect_client */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x7d }, + { SAA7127_REG_GAINV, 0xaf }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x02 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xcb }, + { SAA7127_REG_SUBC2, 0x8a }, + { SAA7127_REG_SUBC1, 0x09 }, + { SAA7127_REG_SUBC0, 0x2a }, + { SAA7127_REG_MULTI, 0xa0 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +/* Enumeration for the Supported input types */ +enum saa7127_input_type { + SAA7127_INPUT_TYPE_NORMAL, + SAA7127_INPUT_TYPE_TEST_IMAGE +}; + +/* Enumeration for the Supported Output signal types */ +enum saa7127_output_type { + SAA7127_OUTPUT_TYPE_BOTH, + SAA7127_OUTPUT_TYPE_COMPOSITE, + SAA7127_OUTPUT_TYPE_SVIDEO, + SAA7127_OUTPUT_TYPE_RGB, + SAA7127_OUTPUT_TYPE_YUV_C, + SAA7127_OUTPUT_TYPE_YUV_V +}; + +/* + ********************************************************************** + * + * Encoder Struct, holds the configuration state of the encoder + * + ********************************************************************** + */ + +struct saa7127_state { + v4l2_std_id std; + enum v4l2_chip_ident ident; + enum saa7127_input_type input_type; + enum saa7127_output_type output_type; + int video_enable; + int wss_enable; + u16 wss_mode; + int cc_enable; + u16 cc_data; + int xds_enable; + u16 xds_data; + int vps_enable; + u8 vps_data[5]; + u8 reg_2d; + u8 reg_3a; + u8 reg_3a_cb; /* colorbar bit */ + u8 reg_61; +}; + +static const char * const output_strs[] = +{ + "S-Video + Composite", + "Composite", + "S-Video", + "RGB", + "YUV C", + "YUV V" +}; + +static const char * const wss_strs[] = { + "invalid", + "letterbox 14:9 center", + "letterbox 14:9 top", + "invalid", + "letterbox 16:9 top", + "invalid", + "invalid", + "16:9 full format anamorphic" + "4:3 full format", + "invalid", + "invalid", + "letterbox 16:9 center", + "invalid", + "letterbox >16:9 center", + "14:9 full format center", + "invalid", +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write(struct i2c_client *client, u8 reg, u8 val) +{ + int i; + + for (i = 0; i < 3; i++) { + if (i2c_smbus_write_byte_data(client, reg, val) == 0) + return 0; + } + saa7127_err("I2C Write Problem\n"); + return -1; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write_inittab(struct i2c_client *client, + const struct i2c_reg_value *regs) +{ + while (regs->reg != 0) { + saa7127_write(client, regs->reg, regs->value); + regs++; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_vps(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 16)) + return -EINVAL; + if (state->vps_enable != enable) { + saa7127_dbg("Turn VPS Signal %s\n", enable ? "on" : "off"); + saa7127_write(client, 0x54, enable << 7); + state->vps_enable = enable; + } + if (!enable) + return 0; + + state->vps_data[0] = data->data[4]; + state->vps_data[1] = data->data[10]; + state->vps_data[2] = data->data[11]; + state->vps_data[3] = data->data[12]; + state->vps_data[4] = data->data[13]; + saa7127_dbg("Set VPS data %02x %02x %02x %02x %02x\n", + state->vps_data[0], state->vps_data[1], + state->vps_data[2], state->vps_data[3], + state->vps_data[4]); + saa7127_write(client, 0x55, state->vps_data[0]); + saa7127_write(client, 0x56, state->vps_data[1]); + saa7127_write(client, 0x57, state->vps_data[2]); + saa7127_write(client, 0x58, state->vps_data[3]); + saa7127_write(client, 0x59, state->vps_data[4]); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_cc(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + u16 cc = data->data[0] << 8 | data->data[1]; + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 21)) + return -EINVAL; + if (state->cc_enable != enable) { + saa7127_dbg("Turn CC %s\n", enable ? "on" : "off"); + saa7127_write(client, SAA7127_REG_CLOSED_CAPTION, + (enable << 6) | 0x11); + state->cc_enable = enable; + } + if (!enable) + return 0; + + saa7127_dbg_highvol("CC data: %04x\n", cc); + saa7127_write(client, SAA7127_REG_LINE_21_ODD_0, cc & 0xff); + saa7127_write(client, SAA7127_REG_LINE_21_ODD_1, cc >> 8); + state->cc_data = cc; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_xds(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + u16 xds = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 1 || data->line != 21)) + return -EINVAL; + if (state->xds_enable != enable) { + saa7127_dbg("Turn XDS %s\n", enable ? "on" : "off"); + saa7127_write(client, SAA7127_REG_CLOSED_CAPTION, + (enable << 7) | 0x11); + state->xds_enable = enable; + } + if (!enable) + return 0; + + saa7127_dbg_highvol("XDS data: %04x\n", xds); + saa7127_write(client, SAA7127_REG_LINE_21_EVEN_0, xds & 0xff); + saa7127_write(client, SAA7127_REG_LINE_21_EVEN_1, xds >> 8); + state->xds_data = xds; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_wss(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 23)) + return -EINVAL; + if (state->wss_enable != enable) { + saa7127_dbg("Turn WSS %s\n", enable ? "on" : "off"); + saa7127_write(client, 0x27, enable << 7); + state->wss_enable = enable; + } + if (!enable) + return 0; + + saa7127_write(client, 0x26, data->data[0]); + saa7127_write(client, 0x27, 0x80 | (data->data[1] & 0x3f)); + saa7127_dbg("WSS mode: %s\n", wss_strs[data->data[0] & 0xf]); + state->wss_mode = (data->data[1] & 0x3f) << 8 | data->data[0]; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_video_enable(struct i2c_client *client, int enable) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + + if (enable) { + saa7127_dbg("Enable Video Output\n"); + saa7127_write(client, 0x2d, state->reg_2d); + saa7127_write(client, 0x61, state->reg_61); + } else { + saa7127_dbg("Disable Video Output\n"); + saa7127_write(client, 0x2d, (state->reg_2d & 0xf0)); + saa7127_write(client, 0x61, (state->reg_61 | 0xc0)); + } + state->video_enable = enable; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_std(struct i2c_client *client, v4l2_std_id std) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + const struct i2c_reg_value *inittab; + + if (std & V4L2_STD_525_60) { + saa7127_dbg("Selecting 60 Hz video Standard\n"); + inittab = saa7127_init_config_60hz; + state->reg_61 = SAA7127_60HZ_DAC_CONTROL; + } else { + saa7127_dbg("Selecting 50 Hz video Standard\n"); + inittab = saa7127_init_config_50hz; + state->reg_61 = SAA7127_50HZ_DAC_CONTROL; + } + + /* Write Table */ + saa7127_write_inittab(client, inittab); + state->std = std; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_output_type(struct i2c_client *client, int output) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + + switch (output) { + case SAA7127_OUTPUT_TYPE_RGB: + state->reg_2d = 0x0f; /* RGB + CVBS (for sync) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_COMPOSITE: + state->reg_2d = 0x08; /* 00001000 CVBS only, RGB DAC's off (high impedance mode) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_SVIDEO: + state->reg_2d = 0xff; /* 11111111 croma -> R, luma -> CVBS + G + B */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_V: + state->reg_2d = 0x4f; /* reg 2D = 01001111, all DAC's on, RGB + VBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_C: + state->reg_2d = 0x0f; /* reg 2D = 00001111, all DAC's on, RGB + CVBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_BOTH: + state->reg_2d = 0xbf; + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + default: + return -EINVAL; + } + saa7127_dbg("Selecting %s output type\n", output_strs[output]); + + /* Configure Encoder */ + saa7127_write(client, 0x2d, state->reg_2d); + saa7127_write(client, 0x3a, state->reg_3a | state->reg_3a_cb); + state->output_type = output; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_input_type(struct i2c_client *client, int input) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + + switch (input) { + case SAA7127_INPUT_TYPE_NORMAL: /* avia */ + saa7127_dbg("Selecting Normal Encoder Input\n"); + state->reg_3a_cb = 0; + break; + + case SAA7127_INPUT_TYPE_TEST_IMAGE: /* color bar */ + saa7127_dbg("Selecting Color Bar generator\n"); + state->reg_3a_cb = 0x80; + break; + + default: + return -EINVAL; + } + saa7127_write(client, 0x3a, state->reg_3a | state->reg_3a_cb); + state->input_type = input; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + struct v4l2_format *fmt = arg; + int *iarg = arg; + + switch (cmd) { + case VIDIOC_S_STD: + if (state->std == *(v4l2_std_id *)arg) + break; + return saa7127_set_std(client, *(v4l2_std_id *)arg); + + case VIDIOC_G_STD: + *(v4l2_std_id *)arg = state->std; + break; + + case VIDIOC_S_INPUT: + if (state->input_type == *iarg) + break; + return saa7127_set_input_type(client, *iarg); + + case VIDIOC_S_OUTPUT: + if (state->output_type == *iarg) + break; + return saa7127_set_output_type(client, *iarg); + + case VIDIOC_STREAMON: + case VIDIOC_STREAMOFF: + if (state->video_enable == (cmd == VIDIOC_STREAMON)) + break; + return saa7127_set_video_enable(client, cmd == VIDIOC_STREAMON); + + case VIDIOC_G_FMT: + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + + memset(&fmt->fmt.sliced, 0, sizeof(fmt->fmt.sliced)); + if (state->vps_enable) + fmt->fmt.sliced.service_lines[0][16] = V4L2_SLICED_VPS; + if (state->wss_enable) + fmt->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; + if (state->cc_enable) { + fmt->fmt.sliced.service_lines[0][21] = V4L2_SLICED_CAPTION_525; + fmt->fmt.sliced.service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } + fmt->fmt.sliced.service_set = + (state->vps_enable ? V4L2_SLICED_VPS : 0) | + (state->wss_enable ? V4L2_SLICED_WSS_625 : 0) | + (state->cc_enable ? V4L2_SLICED_CAPTION_525 : 0); + break; + + case VIDIOC_LOG_STATUS: + saa7127_info("Standard: %s\n", (state->std & V4L2_STD_525_60) ? "60 Hz" : "50 Hz"); + saa7127_info("Input: %s\n", state->input_type ? "color bars" : "normal"); + saa7127_info("Output: %s\n", state->video_enable ? + output_strs[state->output_type] : "disabled"); + saa7127_info("WSS: %s\n", state->wss_enable ? + wss_strs[state->wss_mode] : "disabled"); + saa7127_info("VPS: %s\n", state->vps_enable ? "enabled" : "disabled"); + saa7127_info("CC: %s\n", state->cc_enable ? "enabled" : "disabled"); + break; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_INT_G_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA7127) + return -EINVAL; + reg->val = saa7127_read(client, reg->reg & 0xff); + break; + } + + case VIDIOC_INT_S_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA7127) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + saa7127_write(client, reg->reg & 0xff, reg->val & 0xff); + break; + } +#endif + + case VIDIOC_INT_S_VBI_DATA: + { + struct v4l2_sliced_vbi_data *data = arg; + + switch (data->id) { + case V4L2_SLICED_WSS_625: + return saa7127_set_wss(client, data); + case V4L2_SLICED_VPS: + return saa7127_set_vps(client, data); + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) + return saa7127_set_cc(client, data); + return saa7127_set_xds(client, data); + default: + return -EINVAL; + } + break; + } + + case VIDIOC_INT_G_CHIP_IDENT: + *(enum v4l2_chip_ident *)arg = state->ident; + break; + + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7127; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_attach(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + struct saa7127_state *state; + struct v4l2_sliced_vbi_data vbi = { 0, 0, 0, 0 }; /* set to disabled */ + int read_result = 0; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7127; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name) - 1, "saa7127"); + + saa7127_dbg("detecting saa7127 client on address 0x%x\n", address << 1); + + /* First test register 0: Bits 5-7 are a version ID (should be 0), + and bit 2 should also be 0. + This is rather general, so the second test is more specific and + looks at the 'ending point of burst in clock cycles' which is + 0x1d after a reset and not expected to ever change. */ + if ((saa7127_read(client, 0) & 0xe4) != 0 || + (saa7127_read(client, 0x29) & 0x3f) != 0x1d) { + saa7127_dbg("saa7127 not found\n"); + kfree(client); + return 0; + } + state = kmalloc(sizeof(struct saa7127_state), GFP_KERNEL); + + if (state == NULL) { + kfree(client); + return (-ENOMEM); + } + + i2c_set_clientdata(client, state); + memset(state, 0, sizeof(struct saa7127_state)); + + /* Configure Encoder */ + + saa7127_dbg("Configuring encoder\n"); + saa7127_write_inittab(client, saa7127_init_config_common); + saa7127_set_std(client, V4L2_STD_NTSC); + saa7127_set_output_type(client, SAA7127_OUTPUT_TYPE_BOTH); + saa7127_set_vps(client, &vbi); + saa7127_set_wss(client, &vbi); + saa7127_set_cc(client, &vbi); + saa7127_set_xds(client, &vbi); + if (test_image == 1) { + /* The Encoder has an internal Colorbar generator */ + /* This can be used for debugging */ + saa7127_set_input_type(client, SAA7127_INPUT_TYPE_TEST_IMAGE); + } else { + saa7127_set_input_type(client, SAA7127_INPUT_TYPE_NORMAL); + } + saa7127_set_video_enable(client, 1); + + /* Detect if it's an saa7129 */ + read_result = saa7127_read(client, SAA7129_REG_FADE_KEY_COL2); + saa7127_write(client, SAA7129_REG_FADE_KEY_COL2, 0xaa); + if (saa7127_read(client, SAA7129_REG_FADE_KEY_COL2) == 0xaa) { + saa7127_info("saa7129 found @ 0x%x (%s)\n", address << 1, adapter->name); + saa7127_write(client, SAA7129_REG_FADE_KEY_COL2, read_result); + saa7127_write_inittab(client, saa7129_init_config_extra); + state->ident = V4L2_IDENT_SAA7129; + } else { + saa7127_info("saa7127 found @ 0x%x (%s)\n", address << 1, adapter->name); + state->ident = V4L2_IDENT_SAA7127; + } + + i2c_attach_client(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_probe(struct i2c_adapter *adapter) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adapter->class & I2C_CLASS_TV_ANALOG) +#else + if (adapter->id == I2C_HW_B_BT848) +#endif + return i2c_probe(adapter, &addr_data, saa7127_attach); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_detach(struct i2c_client *client) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + int err; + + /* Turn off TV output */ + saa7127_set_video_enable(client, 0); + + err = i2c_detach_client(client); + + if (err) { + return err; + } + + kfree(state); + kfree(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7127 = { + .name = "saa7127", + .id = I2C_DRIVERID_SAA7127, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa7127_probe, + .detach_client = saa7127_detach, + .command = saa7127_command, + .owner = THIS_MODULE, +}; + + +/* ----------------------------------------------------------------------- */ + +static int __init saa7127_init_module(void) +{ + return i2c_add_driver(&i2c_driver_saa7127); +} + +/* ----------------------------------------------------------------------- */ + +static void __exit saa7127_cleanup_module(void) +{ + i2c_del_driver(&i2c_driver_saa7127); +} + +/* ----------------------------------------------------------------------- */ + +module_init(saa7127_init_module); +module_exit(saa7127_cleanup_module); diff --git a/drivers/media/video/saa7134/Kconfig b/drivers/media/video/saa7134/Kconfig index 624e8808a51..7bdeabe638c 100644 --- a/drivers/media/video/saa7134/Kconfig +++ b/drivers/media/video/saa7134/Kconfig @@ -1,10 +1,11 @@ config VIDEO_SAA7134 tristate "Philips SAA7134 support" - depends on VIDEO_DEV && PCI && I2C && SOUND + depends on VIDEO_DEV && PCI && I2C && SOUND && SND select VIDEO_BUF select VIDEO_IR select VIDEO_TUNER select CRC32 + select SND_PCM_OSS ---help--- This is a video4linux driver for Philips SAA713x based TV cards. diff --git a/drivers/media/video/saa7134/Makefile b/drivers/media/video/saa7134/Makefile index e0b28f0533a..4226b61cc61 100644 --- a/drivers/media/video/saa7134/Makefile +++ b/drivers/media/video/saa7134/Makefile @@ -1,10 +1,11 @@ saa7134-objs := saa7134-cards.o saa7134-core.o saa7134-i2c.o \ - saa7134-oss.o saa7134-ts.o saa7134-tvaudio.o \ - saa7134-vbi.o saa7134-video.o saa7134-input.o + saa7134-ts.o saa7134-tvaudio.o saa7134-vbi.o \ + saa7134-video.o saa7134-input.o obj-$(CONFIG_VIDEO_SAA7134) += saa7134.o saa7134-empress.o \ - saa6752hs.o saa7134-alsa.o + saa6752hs.o saa7134-alsa.o \ + saa7134-oss.o obj-$(CONFIG_VIDEO_SAA7134_DVB) += saa7134-dvb.o EXTRA_CFLAGS += -I$(src)/.. diff --git a/drivers/media/video/saa7134/saa7134-alsa.c b/drivers/media/video/saa7134/saa7134-alsa.c index 4f3c4235432..5707c666660 100644 --- a/drivers/media/video/saa7134/saa7134-alsa.c +++ b/drivers/media/video/saa7134/saa7134-alsa.c @@ -30,7 +30,9 @@ #include <sound/core.h> #include <sound/control.h> #include <sound/pcm.h> +#include <sound/pcm_params.h> #include <sound/initval.h> +#include <linux/interrupt.h> #include "saa7134.h" #include "saa7134-reg.h" @@ -56,6 +58,8 @@ static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for SAA7134 capture interface(s)."); +int position; + #define dprintk(fmt, arg...) if (debug) \ printk(KERN_DEBUG "%s/alsa: " fmt, dev->name , ## arg) @@ -68,7 +72,7 @@ typedef struct snd_card_saa7134 { int mixer_volume[MIXER_ADDR_LAST+1][2]; int capture_source[MIXER_ADDR_LAST+1][2]; struct pci_dev *pci; - struct saa7134_dev *saadev; + struct saa7134_dev *dev; unsigned long iobase; int irq; @@ -83,12 +87,10 @@ typedef struct snd_card_saa7134 { */ typedef struct snd_card_saa7134_pcm { - struct saa7134_dev *saadev; + struct saa7134_dev *dev; spinlock_t lock; - unsigned int pcm_size; /* buffer size */ - unsigned int pcm_count; /* bytes per period */ - unsigned int pcm_bps; /* bytes per second */ + snd_pcm_substream_t *substream; } snd_card_saa7134_pcm_t; @@ -100,13 +102,11 @@ static snd_card_t *snd_saa7134_cards[SNDRV_CARDS]; * * Called when the capture device is released or the buffer overflows * - * - Copied verbatim from saa7134-oss's dsp_dma_stop. Can be dropped - * if we just share dsp_dma_stop and use it here + * - Copied verbatim from saa7134-oss's dsp_dma_stop. * */ static void saa7134_dma_stop(struct saa7134_dev *dev) - { dev->dmasound.dma_blk = -1; dev->dmasound.dma_running = 0; @@ -118,8 +118,7 @@ static void saa7134_dma_stop(struct saa7134_dev *dev) * * Called when preparing the capture device for use * - * - Copied verbatim from saa7134-oss's dsp_dma_start. Can be dropped - * if we just share dsp_dma_start and use it here + * - Copied verbatim from saa7134-oss's dsp_dma_start. * */ @@ -170,9 +169,9 @@ void saa7134_irq_alsa_done(struct saa7134_dev *dev, unsigned long status) if (dev->dmasound.read_count >= dev->dmasound.blksize * (dev->dmasound.blocks-2)) { dprintk("irq: overrun [full=%d/%d] - Blocks in %d\n",dev->dmasound.read_count, dev->dmasound.bufsize, dev->dmasound.blocks); + spin_unlock(&dev->slock); snd_pcm_stop(dev->dmasound.substream,SNDRV_PCM_STATE_XRUN); - saa7134_dma_stop(dev); - goto done; + return; } /* next block addr */ @@ -194,6 +193,7 @@ void saa7134_irq_alsa_done(struct saa7134_dev *dev, unsigned long status) snd_pcm_period_elapsed(dev->dmasound.substream); spin_lock(&dev->slock); } + done: spin_unlock(&dev->slock); @@ -209,7 +209,9 @@ void saa7134_irq_alsa_done(struct saa7134_dev *dev, unsigned long status) static irqreturn_t saa7134_alsa_irq(int irq, void *dev_id, struct pt_regs *regs) { - struct saa7134_dev *dev = (struct saa7134_dev*) dev_id; + struct saa7134_dmasound *dmasound = dev_id; + struct saa7134_dev *dev = dmasound->priv_data; + unsigned long report, status; int loop, handled = 0; @@ -248,56 +250,23 @@ static int snd_card_saa7134_capture_trigger(snd_pcm_substream_t * substream, int cmd) { snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_saa7134_pcm_t *saapcm = runtime->private_data; - struct saa7134_dev *dev=saapcm->saadev; + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + struct saa7134_dev *dev=pcm->dev; int err = 0; - spin_lock_irq(&dev->slock); - if (cmd == SNDRV_PCM_TRIGGER_START) { + spin_lock(&dev->slock); + if (cmd == SNDRV_PCM_TRIGGER_START) { /* start dma */ saa7134_dma_start(dev); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { /* stop dma */ saa7134_dma_stop(dev); - } else { - err = -EINVAL; - } - spin_unlock_irq(&dev->slock); - - return err; -} - -/* - * DMA buffer config - * - * Sets the values that will later be used as the size of the buffer, - * size of the fragments, and total number of fragments. - * Must be called during the preparation stage, before memory is - * allocated - * - * - Copied verbatim from saa7134-oss. Can be dropped - * if we just share dsp_buffer_conf from OSS. - */ + } else { + err = -EINVAL; + } + spin_unlock(&dev->slock); -static int dsp_buffer_conf(struct saa7134_dev *dev, int blksize, int blocks) -{ - if (blksize < 0x100) - blksize = 0x100; - if (blksize > 0x10000) - blksize = 0x10000; - - if (blocks < 2) - blocks = 2; - if ((blksize * blocks) > 1024*1024) - blocks = 1024*1024 / blksize; - - dev->dmasound.blocks = blocks; - dev->dmasound.blksize = blksize; - dev->dmasound.bufsize = blksize * blocks; - - dprintk("buffer config: %d blocks / %d bytes, %d kB total\n", - blocks,blksize,blksize * blocks / 1024); - return 0; + return err; } /* @@ -307,16 +276,16 @@ static int dsp_buffer_conf(struct saa7134_dev *dev, int blksize, int blocks) * ALSA, but I was unable to use ALSA's own DMA, and had to force the * usage of V4L's * - * - Copied verbatim from saa7134-oss. Can be dropped - * if we just share dsp_buffer_init from OSS. + * - Copied verbatim from saa7134-oss. + * */ static int dsp_buffer_init(struct saa7134_dev *dev) { int err; - if (!dev->dmasound.bufsize) - BUG(); + BUG_ON(!dev->dmasound.bufsize); + videobuf_dma_init(&dev->dmasound.dma); err = videobuf_dma_init_kernel(&dev->dmasound.dma, PCI_DMA_FROMDEVICE, (dev->dmasound.bufsize + PAGE_SIZE) >> PAGE_SHIFT); @@ -326,6 +295,28 @@ static int dsp_buffer_init(struct saa7134_dev *dev) } /* + * DMA buffer release + * + * Called after closing the device, during snd_card_saa7134_capture_close + * + */ + +static int dsp_buffer_free(struct saa7134_dev *dev) +{ + if (!dev->dmasound.blksize) + BUG(); + + videobuf_dma_free(&dev->dmasound.dma); + + dev->dmasound.blocks = 0; + dev->dmasound.blksize = 0; + dev->dmasound.bufsize = 0; + + return 0; +} + + +/* * ALSA PCM preparation * * - One of the ALSA capture callbacks. @@ -340,84 +331,30 @@ static int dsp_buffer_init(struct saa7134_dev *dev) static int snd_card_saa7134_capture_prepare(snd_pcm_substream_t * substream) { snd_pcm_runtime_t *runtime = substream->runtime; - int err, bswap, sign; + int bswap, sign; u32 fmt, control; snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); struct saa7134_dev *dev; - snd_card_saa7134_pcm_t *saapcm = runtime->private_data; - unsigned int bps; - unsigned long size; - unsigned count; - - size = snd_pcm_lib_buffer_bytes(substream); - count = snd_pcm_lib_period_bytes(substream); - - saapcm->saadev->dmasound.substream = substream; - bps = runtime->rate * runtime->channels; - bps *= snd_pcm_format_width(runtime->format); - bps /= 8; - if (bps <= 0) - return -EINVAL; - saapcm->pcm_bps = bps; - saapcm->pcm_size = snd_pcm_lib_buffer_bytes(substream); - saapcm->pcm_count = snd_pcm_lib_period_bytes(substream); - + snd_card_saa7134_pcm_t *pcm = runtime->private_data; - dev=saa7134->saadev; + pcm->dev->dmasound.substream = substream; - dsp_buffer_conf(dev,saapcm->pcm_count,(saapcm->pcm_size/saapcm->pcm_count)); + dev = saa7134->dev; - err = dsp_buffer_init(dev); - if (0 != err) - goto fail2; - - /* prepare buffer */ - if (0 != (err = videobuf_dma_pci_map(dev->pci,&dev->dmasound.dma))) - return err; - if (0 != (err = saa7134_pgtable_alloc(dev->pci,&dev->dmasound.pt))) - goto fail1; - if (0 != (err = saa7134_pgtable_build(dev->pci,&dev->dmasound.pt, - dev->dmasound.dma.sglist, - dev->dmasound.dma.sglen, - 0))) - goto fail2; - - - - switch (runtime->format) { - case SNDRV_PCM_FORMAT_U8: - case SNDRV_PCM_FORMAT_S8: + if (snd_pcm_format_width(runtime->format) == 8) fmt = 0x00; - break; - case SNDRV_PCM_FORMAT_U16_LE: - case SNDRV_PCM_FORMAT_U16_BE: - case SNDRV_PCM_FORMAT_S16_LE: - case SNDRV_PCM_FORMAT_S16_BE: + else fmt = 0x01; - break; - default: - err = -EINVAL; - return 1; - } - switch (runtime->format) { - case SNDRV_PCM_FORMAT_S8: - case SNDRV_PCM_FORMAT_S16_LE: - case SNDRV_PCM_FORMAT_S16_BE: + if (snd_pcm_format_signed(runtime->format)) sign = 1; - break; - default: + else sign = 0; - break; - } - switch (runtime->format) { - case SNDRV_PCM_FORMAT_U16_BE: - case SNDRV_PCM_FORMAT_S16_BE: - bswap = 1; break; - default: - bswap = 0; break; - } + if (snd_pcm_format_big_endian(runtime->format)) + bswap = 1; + else + bswap = 0; switch (dev->pci->device) { case PCI_DEVICE_ID_PHILIPS_SAA7134: @@ -445,7 +382,6 @@ static int snd_card_saa7134_capture_prepare(snd_pcm_substream_t * substream) fmt |= 0x04; saa_writel(SAA7133_NUM_SAMPLES, dev->dmasound.blksize -1); saa_writel(SAA7133_AUDIO_CHANNEL, 0x543210 | (fmt << 24)); - //saa_writel(SAA7133_AUDIO_CHANNEL, 0x543210); break; } @@ -459,12 +395,6 @@ static int snd_card_saa7134_capture_prepare(snd_pcm_substream_t * substream) if (bswap) control |= SAA7134_RS_CONTROL_BSWAP; - /* I should be able to use runtime->dma_addr in the control - byte, but it doesn't work. So I allocate the DMA using the - V4L functions, and force ALSA to use that as the DMA area */ - - runtime->dma_area = dev->dmasound.dma.vmalloc; - saa_writel(SAA7134_RS_BA1(6),0); saa_writel(SAA7134_RS_BA2(6),dev->dmasound.blksize); saa_writel(SAA7134_RS_PITCH(6),0); @@ -473,12 +403,6 @@ static int snd_card_saa7134_capture_prepare(snd_pcm_substream_t * substream) dev->dmasound.rate = runtime->rate; return 0; - fail2: - saa7134_pgtable_free(dev->pci,&dev->dmasound.pt); - fail1: - videobuf_dma_pci_unmap(dev->pci,&dev->dmasound.dma); - return err; - } @@ -496,10 +420,8 @@ static int snd_card_saa7134_capture_prepare(snd_pcm_substream_t * substream) static snd_pcm_uframes_t snd_card_saa7134_capture_pointer(snd_pcm_substream_t * substream) { snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_saa7134_pcm_t *saapcm = runtime->private_data; - struct saa7134_dev *dev=saapcm->saadev; - - + snd_card_saa7134_pcm_t *pcm = runtime->private_data; + struct saa7134_dev *dev=pcm->dev; if (dev->dmasound.read_count) { dev->dmasound.read_count -= snd_pcm_lib_period_bytes(substream); @@ -540,9 +462,9 @@ static snd_pcm_hardware_t snd_card_saa7134_capture = static void snd_card_saa7134_runtime_free(snd_pcm_runtime_t *runtime) { - snd_card_saa7134_pcm_t *saapcm = runtime->private_data; + snd_card_saa7134_pcm_t *pcm = runtime->private_data; - kfree(saapcm); + kfree(pcm); } @@ -552,17 +474,76 @@ static void snd_card_saa7134_runtime_free(snd_pcm_runtime_t *runtime) * - One of the ALSA capture callbacks. * * Called on initialization, right before the PCM preparation - * Usually used in ALSA to allocate the DMA, but since we don't use the - * ALSA DMA it does nothing * */ static int snd_card_saa7134_hw_params(snd_pcm_substream_t * substream, snd_pcm_hw_params_t * hw_params) { + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; + unsigned int period_size, periods; + int err; - return 0; + period_size = params_period_bytes(hw_params); + periods = params_periods(hw_params); + + snd_assert(period_size >= 0x100 && period_size <= 0x10000, + return -EINVAL); + snd_assert(periods >= 2, return -EINVAL); + snd_assert(period_size * periods <= 1024 * 1024, return -EINVAL); + dev = saa7134->dev; + + if (dev->dmasound.blocks == periods && + dev->dmasound.blksize == period_size) + return 0; + + /* release the old buffer */ + if (substream->runtime->dma_area) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + videobuf_dma_pci_unmap(dev->pci, &dev->dmasound.dma); + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } + dev->dmasound.blocks = periods; + dev->dmasound.blksize = period_size; + dev->dmasound.bufsize = period_size * periods; + + err = dsp_buffer_init(dev); + if (0 != err) { + dev->dmasound.blocks = 0; + dev->dmasound.blksize = 0; + dev->dmasound.bufsize = 0; + return err; + } + + if (0 != (err = videobuf_dma_pci_map(dev->pci, &dev->dmasound.dma))) { + dsp_buffer_free(dev); + return err; + } + if (0 != (err = saa7134_pgtable_alloc(dev->pci,&dev->dmasound.pt))) { + videobuf_dma_pci_unmap(dev->pci, &dev->dmasound.dma); + dsp_buffer_free(dev); + return err; + } + if (0 != (err = saa7134_pgtable_build(dev->pci,&dev->dmasound.pt, + dev->dmasound.dma.sglist, + dev->dmasound.dma.sglen, + 0))) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + videobuf_dma_pci_unmap(dev->pci, &dev->dmasound.dma); + dsp_buffer_free(dev); + return err; + } + + /* I should be able to use runtime->dma_addr in the control + byte, but it doesn't work. So I allocate the DMA using the + V4L functions, and force ALSA to use that as the DMA area */ + + substream->runtime->dma_area = dev->dmasound.dma.vmalloc; + + return 1; } @@ -572,33 +553,23 @@ static int snd_card_saa7134_hw_params(snd_pcm_substream_t * substream, * - One of the ALSA capture callbacks. * * Called after closing the device, but before snd_card_saa7134_capture_close - * Usually used in ALSA to free the DMA, but since we don't use the - * ALSA DMA I'm almost sure this isn't necessary. + * It stops the DMA audio and releases the buffers. * */ static int snd_card_saa7134_hw_free(snd_pcm_substream_t * substream) { - return 0; -} - -/* - * DMA buffer release - * - * Called after closing the device, during snd_card_saa7134_capture_close - * - */ - -static int dsp_buffer_free(struct saa7134_dev *dev) -{ - if (!dev->dmasound.blksize) - BUG(); + snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); + struct saa7134_dev *dev; - videobuf_dma_free(&dev->dmasound.dma); + dev = saa7134->dev; - dev->dmasound.blocks = 0; - dev->dmasound.blksize = 0; - dev->dmasound.bufsize = 0; + if (substream->runtime->dma_area) { + saa7134_pgtable_free(dev->pci, &dev->dmasound.pt); + videobuf_dma_pci_unmap(dev->pci, &dev->dmasound.dma); + dsp_buffer_free(dev); + substream->runtime->dma_area = NULL; + } return 0; } @@ -608,21 +579,12 @@ static int dsp_buffer_free(struct saa7134_dev *dev) * * - One of the ALSA capture callbacks. * - * Called after closing the device. It stops the DMA audio and releases - * the buffers + * Called after closing the device. * */ static int snd_card_saa7134_capture_close(snd_pcm_substream_t * substream) { - snd_card_saa7134_t *chip = snd_pcm_substream_chip(substream); - struct saa7134_dev *dev = chip->saadev; - - /* unlock buffer */ - saa7134_pgtable_free(dev->pci,&dev->dmasound.pt); - videobuf_dma_pci_unmap(dev->pci,&dev->dmasound.dma); - - dsp_buffer_free(dev); return 0; } @@ -639,29 +601,28 @@ static int snd_card_saa7134_capture_close(snd_pcm_substream_t * substream) static int snd_card_saa7134_capture_open(snd_pcm_substream_t * substream) { snd_pcm_runtime_t *runtime = substream->runtime; - snd_card_saa7134_pcm_t *saapcm; + snd_card_saa7134_pcm_t *pcm; snd_card_saa7134_t *saa7134 = snd_pcm_substream_chip(substream); - struct saa7134_dev *dev = saa7134->saadev; + struct saa7134_dev *dev = saa7134->dev; int err; down(&dev->dmasound.lock); - dev->dmasound.afmt = SNDRV_PCM_FORMAT_U8; - dev->dmasound.channels = 2; dev->dmasound.read_count = 0; dev->dmasound.read_offset = 0; up(&dev->dmasound.lock); - saapcm = kzalloc(sizeof(*saapcm), GFP_KERNEL); - if (saapcm == NULL) + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (pcm == NULL) return -ENOMEM; - saapcm->saadev=saa7134->saadev; - spin_lock_init(&saapcm->lock); + pcm->dev=saa7134->dev; - saapcm->substream = substream; - runtime->private_data = saapcm; + spin_lock_init(&pcm->lock); + + pcm->substream = substream; + runtime->private_data = pcm; runtime->private_free = snd_card_saa7134_runtime_free; runtime->hw = snd_card_saa7134_capture; @@ -736,7 +697,6 @@ static int snd_saa7134_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_ static int snd_saa7134_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) { snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); - unsigned long flags; int change, addr = kcontrol->private_value; int left, right; @@ -750,12 +710,12 @@ static int snd_saa7134_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_ right = 0; if (right > 20) right = 20; - spin_lock_irqsave(&chip->mixer_lock, flags); + spin_lock_irq(&chip->mixer_lock); change = chip->mixer_volume[addr][0] != left || chip->mixer_volume[addr][1] != right; chip->mixer_volume[addr][0] = left; chip->mixer_volume[addr][1] = right; - spin_unlock_irqrestore(&chip->mixer_lock, flags); + spin_unlock_irq(&chip->mixer_lock); return change; } @@ -777,38 +737,37 @@ static int snd_saa7134_capsrc_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_ static int snd_saa7134_capsrc_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) { snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); - unsigned long flags; int addr = kcontrol->private_value; - spin_lock_irqsave(&chip->mixer_lock, flags); + spin_lock_irq(&chip->mixer_lock); ucontrol->value.integer.value[0] = chip->capture_source[addr][0]; ucontrol->value.integer.value[1] = chip->capture_source[addr][1]; - spin_unlock_irqrestore(&chip->mixer_lock, flags); + spin_unlock_irq(&chip->mixer_lock); + return 0; } static int snd_saa7134_capsrc_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) { snd_card_saa7134_t *chip = snd_kcontrol_chip(kcontrol); - unsigned long flags; int change, addr = kcontrol->private_value; int left, right; u32 anabar, xbarin; int analog_io, rate; struct saa7134_dev *dev; - dev = chip->saadev; + dev = chip->dev; left = ucontrol->value.integer.value[0] & 1; right = ucontrol->value.integer.value[1] & 1; - spin_lock_irqsave(&chip->mixer_lock, flags); + spin_lock_irq(&chip->mixer_lock); change = chip->capture_source[addr][0] != left || chip->capture_source[addr][1] != right; chip->capture_source[addr][0] = left; chip->capture_source[addr][1] = right; dev->dmasound.input=addr; - spin_unlock_irqrestore(&chip->mixer_lock, flags); + spin_unlock_irq(&chip->mixer_lock); if (change) { @@ -898,43 +857,44 @@ static int snd_card_saa7134_new_mixer(snd_card_saa7134_t * chip) return 0; } -static int snd_saa7134_free(snd_card_saa7134_t *chip) +static void snd_saa7134_free(snd_card_t * card) { - return 0; -} + snd_card_saa7134_t *chip = card->private_data; + + if (chip->dev->dmasound.priv_data == NULL) + return; + + if (chip->irq >= 0) { + synchronize_irq(chip->irq); + free_irq(chip->irq, &chip->dev->dmasound); + } + + chip->dev->dmasound.priv_data = NULL; -static int snd_saa7134_dev_free(snd_device_t *device) -{ - snd_card_saa7134_t *chip = device->device_data; - return snd_saa7134_free(chip); } /* * ALSA initialization * - * Called by saa7134-core, it creates the basic structures and registers - * the ALSA devices + * Called by the init routine, once for each saa7134 device present, + * it creates the basic structures and registers the ALSA devices * */ -int alsa_card_saa7134_create (struct saa7134_dev *saadev) +int alsa_card_saa7134_create(struct saa7134_dev *dev, int devnum) { - static int dev; snd_card_t *card; snd_card_saa7134_t *chip; int err; - static snd_device_ops_t ops = { - .dev_free = snd_saa7134_dev_free, - }; - if (dev >= SNDRV_CARDS) + if (devnum >= SNDRV_CARDS) return -ENODEV; - if (!enable[dev]) + if (!enable[devnum]) return -ENODEV; - card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + card = snd_card_new(index[devnum], id[devnum], THIS_MODULE, sizeof(snd_card_saa7134_t)); if (card == NULL) return -ENOMEM; @@ -943,34 +903,33 @@ int alsa_card_saa7134_create (struct saa7134_dev *saadev) /* Card "creation" */ - chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); - if (chip == NULL) { - return -ENOMEM; - } + card->private_free = snd_saa7134_free; + chip = (snd_card_saa7134_t *) card->private_data; spin_lock_init(&chip->lock); spin_lock_init(&chip->mixer_lock); - chip->saadev = saadev; + chip->dev = dev; chip->card = card; - chip->pci = saadev->pci; - chip->irq = saadev->pci->irq; - chip->iobase = pci_resource_start(saadev->pci, 0); + chip->pci = dev->pci; + chip->iobase = pci_resource_start(dev->pci, 0); - err = request_irq(saadev->pci->irq, saa7134_alsa_irq, - SA_SHIRQ | SA_INTERRUPT, saadev->name, saadev); + + err = request_irq(dev->pci->irq, saa7134_alsa_irq, + SA_SHIRQ | SA_INTERRUPT, dev->name, + (void*) &dev->dmasound); if (err < 0) { printk(KERN_ERR "%s: can't get IRQ %d for ALSA\n", - saadev->name, saadev->pci->irq); + dev->name, dev->pci->irq); goto __nodev; } - if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { - goto __nodev; - } + chip->irq = dev->pci->irq; + + init_MUTEX(&dev->dmasound.lock); if ((err = snd_card_saa7134_new_mixer(chip)) < 0) goto __nodev; @@ -984,16 +943,15 @@ int alsa_card_saa7134_create (struct saa7134_dev *saadev) strcpy(card->shortname, "SAA7134"); sprintf(card->longname, "%s at 0x%lx irq %d", - chip->saadev->name, chip->iobase, chip->irq); + chip->dev->name, chip->iobase, chip->irq); if ((err = snd_card_register(card)) == 0) { - snd_saa7134_cards[dev] = card; + snd_saa7134_cards[devnum] = card; return 0; } __nodev: snd_card_free(card); - kfree(chip); return err; } @@ -1007,21 +965,29 @@ __nodev: static int saa7134_alsa_init(void) { - struct saa7134_dev *saadev = NULL; - struct list_head *list; + struct saa7134_dev *dev = NULL; + struct list_head *list; - printk(KERN_INFO "saa7134 ALSA driver for DMA sound loaded\n"); + position = 0; - list_for_each(list,&saa7134_devlist) { - saadev = list_entry(list, struct saa7134_dev, devlist); - alsa_card_saa7134_create(saadev); - } + printk(KERN_INFO "saa7134 ALSA driver for DMA sound loaded\n"); - if (saadev == NULL) + list_for_each(list,&saa7134_devlist) { + dev = list_entry(list, struct saa7134_dev, devlist); + if (dev->dmasound.priv_data == NULL) { + dev->dmasound.priv_data = dev; + alsa_card_saa7134_create(dev,position); + position++; + } else { + printk(KERN_ERR "saa7134 ALSA: DMA sound is being handled by OSS. ignoring %s\n",dev->name); + return -EBUSY; + } + } + + if (dev == NULL) printk(KERN_INFO "saa7134 ALSA: no saa7134 cards found\n"); return 0; - } /* diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c index 663d03e5bc6..75abc20b0cc 100644 --- a/drivers/media/video/saa7134/saa7134-cards.c +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -2529,6 +2529,32 @@ struct saa7134_board saa7134_boards[] = { .amux = LINE1, }}, }, + [SAA7134_BOARD_MSI_TVATANYWHERE_PLUS] = { + .name = "MSI TV@Anywhere plus", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .tv = 1, + },{ + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + },{ + .name = name_svideo, + .vmux = 0, + .amux = LINE1, + }}, + .radio = { + .name = name_radio, + .amux = LINE1, + }, + }, }; const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); @@ -2970,6 +2996,12 @@ struct pci_device_id saa7134_pci_tbl[] = { .subdevice = 0x2018, .driver_data = SAA7134_BOARD_PHILIPS_TIGER, },{ + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7133, + .subvendor = 0x1462, + .subdevice = 0x6231, + .driver_data = SAA7134_BOARD_MSI_TVATANYWHERE_PLUS, + },{ /* --- boards without eeprom + subsystem ID --- */ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7134, diff --git a/drivers/media/video/saa7134/saa7134-core.c b/drivers/media/video/saa7134/saa7134-core.c index 19b88744fb3..4275d2ddb86 100644 --- a/drivers/media/video/saa7134/saa7134-core.c +++ b/drivers/media/video/saa7134/saa7134-core.c @@ -53,13 +53,13 @@ static unsigned int gpio_tracking = 0; module_param(gpio_tracking, int, 0644); MODULE_PARM_DESC(gpio_tracking,"enable debug messages [gpio]"); -static unsigned int oss = 0; -module_param(oss, int, 0444); -MODULE_PARM_DESC(oss,"register oss devices (default: no)"); - static unsigned int alsa = 0; -module_param(alsa, int, 0444); -MODULE_PARM_DESC(alsa,"register alsa devices (default: no)"); +module_param(alsa, int, 0644); +MODULE_PARM_DESC(alsa,"enable ALSA DMA sound [dmasound]"); + +static unsigned int oss = 0; +module_param(oss, int, 0644); +MODULE_PARM_DESC(oss,"enable OSS DMA sound [dmasound]"); static unsigned int latency = UNSET; module_param(latency, int, 0444); @@ -68,24 +68,18 @@ MODULE_PARM_DESC(latency,"pci latency timer"); static unsigned int video_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; static unsigned int vbi_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; static unsigned int radio_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; -static unsigned int dsp_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; -static unsigned int mixer_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; static unsigned int tuner[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; static unsigned int card[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; module_param_array(video_nr, int, NULL, 0444); module_param_array(vbi_nr, int, NULL, 0444); module_param_array(radio_nr, int, NULL, 0444); -module_param_array(dsp_nr, int, NULL, 0444); -module_param_array(mixer_nr, int, NULL, 0444); module_param_array(tuner, int, NULL, 0444); module_param_array(card, int, NULL, 0444); MODULE_PARM_DESC(video_nr, "video device number"); MODULE_PARM_DESC(vbi_nr, "vbi device number"); MODULE_PARM_DESC(radio_nr, "radio device number"); -MODULE_PARM_DESC(dsp_nr, "oss dsp device number"); -MODULE_PARM_DESC(mixer_nr, "oss mixer device number"); MODULE_PARM_DESC(tuner, "tuner type"); MODULE_PARM_DESC(card, "card type"); @@ -195,6 +189,7 @@ void saa7134_track_gpio(struct saa7134_dev *dev, char *msg) static int need_empress; static int need_dvb; static int need_alsa; +static int need_oss; static int pending_call(struct notifier_block *self, unsigned long state, void *module) @@ -208,6 +203,8 @@ static int pending_call(struct notifier_block *self, unsigned long state, request_module("saa7134-dvb"); if (need_alsa) request_module("saa7134-alsa"); + if (need_oss) + request_module("saa7134-oss"); return NOTIFY_DONE; } @@ -218,10 +215,11 @@ static struct notifier_block pending_notifier = { static void request_module_depend(char *name, int *flag) { + int err; switch (THIS_MODULE->state) { case MODULE_STATE_COMING: if (!pending_registered) { - register_module_notifier(&pending_notifier); + err = register_module_notifier(&pending_notifier); pending_registered = 1; } *flag = 1; @@ -578,12 +576,14 @@ static irqreturn_t saa7134_irq(int irq, void *dev_id, struct pt_regs *regs) goto out; } - /* If alsa support is active and we get a sound report, exit - and let the saa7134-alsa module deal with it */ + /* If dmasound support is active and we get a sound report, exit + and let the saa7134-alsa/oss module deal with it */ - if ((report & SAA7134_IRQ_REPORT_DONE_RA3) && alsa) { + if ((report & SAA7134_IRQ_REPORT_DONE_RA3) && + (dev->dmasound.priv_data != NULL) ) + { if (irq_debug > 1) - printk(KERN_DEBUG "%s/irq: ignoring interrupt for ALSA\n", + printk(KERN_DEBUG "%s/irq: ignoring interrupt for DMA sound\n", dev->name); goto out; } @@ -609,12 +609,6 @@ static irqreturn_t saa7134_irq(int irq, void *dev_id, struct pt_regs *regs) card_has_mpeg(dev)) saa7134_irq_ts_done(dev,status); - if ((report & SAA7134_IRQ_REPORT_DONE_RA3)) { - if (oss) { - saa7134_irq_oss_done(dev,status); - } - } - if ((report & (SAA7134_IRQ_REPORT_GPIO16 | SAA7134_IRQ_REPORT_GPIO18)) && dev->remote) @@ -689,14 +683,6 @@ static int saa7134_hwinit1(struct saa7134_dev *dev) * audio will not work. */ - switch (dev->pci->device) { - case PCI_DEVICE_ID_PHILIPS_SAA7134: - case PCI_DEVICE_ID_PHILIPS_SAA7133: - case PCI_DEVICE_ID_PHILIPS_SAA7135: - saa7134_oss_init1(dev); - break; - } - /* enable peripheral devices */ saa_writeb(SAA7134_SPECIAL_MODE, 0x01); @@ -728,8 +714,6 @@ static int saa7134_hwinit2(struct saa7134_dev *dev) irq2_mask |= (SAA7134_IRQ2_INTE_GPIO18 | SAA7134_IRQ2_INTE_GPIO18A | SAA7134_IRQ2_INTE_GPIO16 ); - else if (dev->has_remote == SAA7134_REMOTE_I2C) - request_module("ir-kbd-i2c"); saa_writel(SAA7134_IRQ1, 0); saa_writel(SAA7134_IRQ2, irq2_mask); @@ -742,13 +726,6 @@ static int saa7134_hwfini(struct saa7134_dev *dev) { dprintk("hwfini\n"); - switch (dev->pci->device) { - case PCI_DEVICE_ID_PHILIPS_SAA7134: - case PCI_DEVICE_ID_PHILIPS_SAA7133: - case PCI_DEVICE_ID_PHILIPS_SAA7135: - saa7134_oss_fini(dev); - break; - } if (card_has_mpeg(dev)) saa7134_ts_fini(dev); saa7134_input_fini(dev); @@ -986,11 +963,12 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, if (card_is_dvb(dev)) request_module_depend("saa7134-dvb",&need_dvb); - if (!oss && alsa) { - dprintk("Requesting ALSA module\n"); + + if (alsa) request_module_depend("saa7134-alsa",&need_alsa); - } + if (oss) + request_module_depend("saa7134-oss",&need_oss); v4l2_prio_init(&dev->prio); @@ -1024,32 +1002,6 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, dev->name,dev->radio_dev->minor & 0x1f); } - /* register oss devices */ - switch (dev->pci->device) { - case PCI_DEVICE_ID_PHILIPS_SAA7134: - case PCI_DEVICE_ID_PHILIPS_SAA7133: - case PCI_DEVICE_ID_PHILIPS_SAA7135: - if (oss) { - err = dev->dmasound.minor_dsp = - register_sound_dsp(&saa7134_dsp_fops, - dsp_nr[dev->nr]); - if (err < 0) { - goto fail4; - } - printk(KERN_INFO "%s: registered device dsp%d\n", - dev->name,dev->dmasound.minor_dsp >> 4); - - err = dev->dmasound.minor_mixer = - register_sound_mixer(&saa7134_mixer_fops, - mixer_nr[dev->nr]); - if (err < 0) - goto fail5; - printk(KERN_INFO "%s: registered device mixer%d\n", - dev->name,dev->dmasound.minor_mixer >> 4); - } - break; - } - /* everything worked */ pci_set_drvdata(pci_dev,dev); saa7134_devcount++; @@ -1064,17 +1016,9 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, /* check for signal */ saa7134_irq_video_intl(dev); + return 0; - fail5: - switch (dev->pci->device) { - case PCI_DEVICE_ID_PHILIPS_SAA7134: - case PCI_DEVICE_ID_PHILIPS_SAA7133: - case PCI_DEVICE_ID_PHILIPS_SAA7135: - if (oss) - unregister_sound_dsp(dev->dmasound.minor_dsp); - break; - } fail4: saa7134_unregister_video(dev); saa7134_i2c_unregister(dev); @@ -1125,19 +1069,16 @@ static void __devexit saa7134_finidev(struct pci_dev *pci_dev) saa7134_devcount--; saa7134_i2c_unregister(dev); - switch (dev->pci->device) { - case PCI_DEVICE_ID_PHILIPS_SAA7134: - case PCI_DEVICE_ID_PHILIPS_SAA7133: - case PCI_DEVICE_ID_PHILIPS_SAA7135: - if (oss) { - unregister_sound_mixer(dev->dmasound.minor_mixer); - unregister_sound_dsp(dev->dmasound.minor_dsp); - } - break; - } saa7134_unregister_video(dev); - /* release ressources */ + /* the DMA sound modules should be unloaded before reaching + this, but just in case they are still present... */ + if (dev->dmasound.priv_data != NULL) { + free_irq(pci_dev->irq, &dev->dmasound); + dev->dmasound.priv_data = NULL; + } + + /* release resources */ free_irq(pci_dev->irq, dev); iounmap(dev->lmmio); release_mem_region(pci_resource_start(pci_dev,0), @@ -1225,7 +1166,7 @@ EXPORT_SYMBOL(saa7134_i2c_call_clients); EXPORT_SYMBOL(saa7134_devlist); EXPORT_SYMBOL(saa7134_boards); -/* ----------------- For ALSA -------------------------------- */ +/* ----------------- for the DMA sound modules --------------- */ EXPORT_SYMBOL(saa7134_pgtable_free); EXPORT_SYMBOL(saa7134_pgtable_build); diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c index 329accda6d4..e648cc3bc96 100644 --- a/drivers/media/video/saa7134/saa7134-input.c +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -485,64 +485,6 @@ static IR_KEYTAB_TYPE ir_codes_purpletv[IR_KEYTAB_SIZE] = { }; -static IR_KEYTAB_TYPE ir_codes_pinnacle[IR_KEYTAB_SIZE] = { - [ 0x59 ] = KEY_MUTE, - [ 0x4a ] = KEY_POWER, - - [ 0x18 ] = KEY_TEXT, - [ 0x26 ] = KEY_TV, - [ 0x3d ] = KEY_PRINT, - - [ 0x48 ] = KEY_RED, - [ 0x04 ] = KEY_GREEN, - [ 0x11 ] = KEY_YELLOW, - [ 0x00 ] = KEY_BLUE, - - [ 0x2d ] = KEY_VOLUMEUP, - [ 0x1e ] = KEY_VOLUMEDOWN, - - [ 0x49 ] = KEY_MENU, - - [ 0x16 ] = KEY_CHANNELUP, - [ 0x17 ] = KEY_CHANNELDOWN, - - [ 0x20 ] = KEY_UP, - [ 0x21 ] = KEY_DOWN, - [ 0x22 ] = KEY_LEFT, - [ 0x23 ] = KEY_RIGHT, - [ 0x0d ] = KEY_SELECT, - - - - [ 0x08 ] = KEY_BACK, - [ 0x07 ] = KEY_REFRESH, - - [ 0x2f ] = KEY_ZOOM, - [ 0x29 ] = KEY_RECORD, - - [ 0x4b ] = KEY_PAUSE, - [ 0x4d ] = KEY_REWIND, - [ 0x2e ] = KEY_PLAY, - [ 0x4e ] = KEY_FORWARD, - [ 0x53 ] = KEY_PREVIOUS, - [ 0x4c ] = KEY_STOP, - [ 0x54 ] = KEY_NEXT, - - [ 0x69 ] = KEY_KP0, - [ 0x6a ] = KEY_KP1, - [ 0x6b ] = KEY_KP2, - [ 0x6c ] = KEY_KP3, - [ 0x6d ] = KEY_KP4, - [ 0x6e ] = KEY_KP5, - [ 0x6f ] = KEY_KP6, - [ 0x70 ] = KEY_KP7, - [ 0x71 ] = KEY_KP8, - [ 0x72 ] = KEY_KP9, - - [ 0x74 ] = KEY_CHANNEL, - [ 0x0a ] = KEY_BACKSPACE, -}; - /* Mapping for the 28 key remote control as seen at http://www.sednacomputer.com/photo/cardbus-tv.jpg Pavel Mihaylov <bin@bash.info> */ @@ -635,57 +577,6 @@ static int get_key_purpletv(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) return 1; } -/* The new pinnacle PCTV remote (with the colored buttons) - * - * Ricardo Cerqueira <v4l@cerqueira.org> - */ - -static int get_key_pinnacle(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) -{ - unsigned char b[4]; - unsigned int start = 0,parity = 0,code = 0; - - /* poll IR chip */ - if (4 != i2c_master_recv(&ir->c,b,4)) { - i2cdprintk("read error\n"); - return -EIO; - } - - for (start = 0; start<4; start++) { - if (b[start] == 0x80) { - code=b[(start+3)%4]; - parity=b[(start+2)%4]; - } - } - - /* Empty Request */ - if (parity==0) - return 0; - - /* Repeating... */ - if (ir->old == parity) - return 0; - - - ir->old = parity; - - /* Reduce code value to fit inside IR_KEYTAB_SIZE - * - * this is the only value that results in 42 unique - * codes < 128 - */ - - code %= 0x88; - - *ir_raw = code; - *ir_key = code; - - i2cdprintk("Pinnacle PCTV key %02x\n", code); - - return 1; -} - - void saa7134_input_irq(struct saa7134_dev *dev) { struct saa7134_ir *ir = dev->remote; diff --git a/drivers/media/video/saa7134/saa7134-oss.c b/drivers/media/video/saa7134/saa7134-oss.c index fd53dfcc164..fd9ed11ab1e 100644 --- a/drivers/media/video/saa7134/saa7134-oss.c +++ b/drivers/media/video/saa7134/saa7134-oss.c @@ -4,6 +4,8 @@ * oss dsp interface * * (c) 2001,02 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs] + * 2005 conversion to standalone module: + * Ricardo Cerqueira <v4l@cerqueira.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 @@ -25,7 +27,9 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> +#include <linux/interrupt.h> #include <linux/slab.h> +#include <linux/sound.h> #include <linux/soundcard.h> #include "saa7134-reg.h" @@ -33,15 +37,23 @@ /* ------------------------------------------------------------------ */ -static unsigned int oss_debug = 0; -module_param(oss_debug, int, 0644); -MODULE_PARM_DESC(oss_debug,"enable debug messages [oss]"); +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug,"enable debug messages [oss]"); -static unsigned int oss_rate = 0; -module_param(oss_rate, int, 0444); -MODULE_PARM_DESC(oss_rate,"sample rate (valid are: 32000,48000)"); +static unsigned int rate = 0; +module_param(rate, int, 0444); +MODULE_PARM_DESC(rate,"sample rate (valid are: 32000,48000)"); -#define dprintk(fmt, arg...) if (oss_debug) \ +static unsigned int dsp_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM_DESC(dsp_nr, "device numbers for SAA7134 capture interface(s)."); +module_param_array(dsp_nr, int, NULL, 0444); + +static unsigned int mixer_nr[] = {[0 ... (SAA7134_MAXBOARDS - 1)] = UNSET }; +MODULE_PARM_DESC(mixer_nr, "mixer numbers for SAA7134 capture interface(s)."); +module_param_array(mixer_nr, int, NULL, 0444); + +#define dprintk(fmt, arg...) if (debug) \ printk(KERN_DEBUG "%s/oss: " fmt, dev->name , ## arg) @@ -369,7 +381,7 @@ static int dsp_ioctl(struct inode *inode, struct file *file, int __user *p = argp; int val = 0; - if (oss_debug > 1) + if (debug > 1) saa7134_print_ioctl(dev->name,cmd); switch (cmd) { case OSS_GETVERSION: @@ -665,7 +677,7 @@ static int mixer_ioctl(struct inode *inode, struct file *file, void __user *argp = (void __user *) arg; int __user *p = argp; - if (oss_debug > 1) + if (debug > 1) saa7134_print_ioctl(dev->name,cmd); switch (cmd) { case OSS_GETVERSION: @@ -768,8 +780,41 @@ struct file_operations saa7134_mixer_fops = { /* ------------------------------------------------------------------ */ +static irqreturn_t saa7134_oss_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct saa7134_dmasound *dmasound = dev_id; + struct saa7134_dev *dev = dmasound->priv_data; + unsigned long report, status; + int loop, handled = 0; + + for (loop = 0; loop < 10; loop++) { + report = saa_readl(SAA7134_IRQ_REPORT); + status = saa_readl(SAA7134_IRQ_STATUS); + + if (report & SAA7134_IRQ_REPORT_DONE_RA3) { + handled = 1; + saa_writel(SAA7134_IRQ_REPORT,report); + saa7134_irq_oss_done(dev, status); + } else { + goto out; + } + } + + if (loop == 10) { + dprintk("error! looping IRQ!"); + } +out: + return IRQ_RETVAL(handled); +} + int saa7134_oss_init1(struct saa7134_dev *dev) { + + if ((request_irq(dev->pci->irq, saa7134_oss_irq, + SA_SHIRQ | SA_INTERRUPT, dev->name, + (void*) &dev->dmasound)) < 0) + return -1; + /* general */ init_MUTEX(&dev->dmasound.lock); init_waitqueue_head(&dev->dmasound.wq); @@ -785,8 +830,8 @@ int saa7134_oss_init1(struct saa7134_dev *dev) /* dsp */ dev->dmasound.rate = 32000; - if (oss_rate) - dev->dmasound.rate = oss_rate; + if (rate) + dev->dmasound.rate = rate; dev->dmasound.rate = (dev->dmasound.rate > 40000) ? 48000 : 32000; /* mixer */ @@ -840,7 +885,7 @@ void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status) /* next block addr */ next_blk = (dev->dmasound.dma_blk + 2) % dev->dmasound.blocks; saa_writel(reg,next_blk * dev->dmasound.blksize); - if (oss_debug > 2) + if (debug > 2) dprintk("irq: ok, %s, next_blk=%d, addr=%x\n", (status & 0x10000000) ? "even" : "odd ", next_blk, next_blk * dev->dmasound.blksize); @@ -854,6 +899,98 @@ void saa7134_irq_oss_done(struct saa7134_dev *dev, unsigned long status) spin_unlock(&dev->slock); } +int saa7134_dsp_create(struct saa7134_dev *dev) +{ + int err; + + err = dev->dmasound.minor_dsp = + register_sound_dsp(&saa7134_dsp_fops, + dsp_nr[dev->nr]); + if (err < 0) { + goto fail; + } + printk(KERN_INFO "%s: registered device dsp%d\n", + dev->name,dev->dmasound.minor_dsp >> 4); + + err = dev->dmasound.minor_mixer = + register_sound_mixer(&saa7134_mixer_fops, + mixer_nr[dev->nr]); + if (err < 0) + goto fail; + printk(KERN_INFO "%s: registered device mixer%d\n", + dev->name,dev->dmasound.minor_mixer >> 4); + + return 0; + +fail: + unregister_sound_dsp(dev->dmasound.minor_dsp); + return 0; + + +} + +static int saa7134_oss_init(void) +{ + struct saa7134_dev *dev = NULL; + struct list_head *list; + + printk(KERN_INFO "saa7134 OSS driver for DMA sound loaded\n"); + + list_for_each(list,&saa7134_devlist) { + dev = list_entry(list, struct saa7134_dev, devlist); + if (dev->dmasound.priv_data == NULL) { + dev->dmasound.priv_data = dev; + saa7134_oss_init1(dev); + saa7134_dsp_create(dev); + } else { + printk(KERN_ERR "saa7134 OSS: DMA sound is being handled by ALSA, ignoring %s\n",dev->name); + return -EBUSY; + } + } + + if (dev == NULL) + printk(KERN_INFO "saa7134 OSS: no saa7134 cards found\n"); + + return 0; + +} + +void saa7134_oss_exit(void) +{ + struct saa7134_dev *dev = NULL; + struct list_head *list; + + list_for_each(list,&saa7134_devlist) { + dev = list_entry(list, struct saa7134_dev, devlist); + + /* Device isn't registered by OSS, probably ALSA's */ + if (!dev->dmasound.minor_dsp) + continue; + + unregister_sound_mixer(dev->dmasound.minor_mixer); + unregister_sound_dsp(dev->dmasound.minor_dsp); + + saa7134_oss_fini(dev); + + if (dev->pci->irq > 0) { + synchronize_irq(dev->pci->irq); + free_irq(dev->pci->irq,&dev->dmasound); + } + + dev->dmasound.priv_data = NULL; + + } + + printk(KERN_INFO "saa7134 OSS driver for DMA sound unloaded\n"); + + return; +} + +module_init(saa7134_oss_init); +module_exit(saa7134_oss_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]"); + /* ----------------------------------------------------------- */ /* * Local variables: diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h index fb972747166..244e1973081 100644 --- a/drivers/media/video/saa7134/saa7134.h +++ b/drivers/media/video/saa7134/saa7134.h @@ -208,6 +208,7 @@ struct saa7134_format { #define SAA7134_BOARD_SEDNA_PC_TV_CARDBUS 79 #define SAA7134_BOARD_ASUSTEK_DIGIMATRIX_TV 80 #define SAA7134_BOARD_PHILIPS_TIGER 81 +#define SAA7134_BOARD_MSI_TVATANYWHERE_PLUS 82 #define SAA7134_MAXBOARDS 8 #define SAA7134_INPUT_MAX 8 @@ -383,6 +384,7 @@ struct saa7134_dmasound { unsigned int dma_blk; unsigned int read_offset; unsigned int read_count; + void * priv_data; snd_pcm_substream_t *substream; }; diff --git a/drivers/media/video/tda8290.c b/drivers/media/video/tda8290.c index b2dfe07e9f9..61d94ddaff4 100644 --- a/drivers/media/video/tda8290.c +++ b/drivers/media/video/tda8290.c @@ -437,6 +437,10 @@ static void set_audio(struct tuner *t) t->sgIF = 124; t->tda8290_easy_mode = 0x20; mode = "L"; + } else if (t->std & V4L2_STD_SECAM_LC) { + t->sgIF = 20; + t->tda8290_easy_mode = 0x40; + mode = "LC"; } tuner_dbg("setting tda8290 to system %s\n", mode); } diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c index 73c4041c35d..e58abdfcaab 100644 --- a/drivers/media/video/tuner-core.c +++ b/drivers/media/video/tuner-core.c @@ -251,7 +251,7 @@ static inline int check_mode(struct tuner *t, char *cmd) static char pal[] = "-"; module_param_string(pal, pal, sizeof(pal), 0644); -static char secam[] = "-"; +static char secam[] = "--"; module_param_string(secam, secam, sizeof(secam), 0644); /* get more precise norm info from insmod option */ @@ -307,8 +307,13 @@ static int tuner_fixup_std(struct tuner *t) break; case 'l': case 'L': - tuner_dbg ("insmod fixup: SECAM => SECAM-L\n"); - t->std = V4L2_STD_SECAM_L; + if ((secam[1]=='C')||(secam[1]=='c')) { + tuner_dbg ("insmod fixup: SECAM => SECAM-L'\n"); + t->std = V4L2_STD_SECAM_LC; + } else { + tuner_dbg ("insmod fixup: SECAM => SECAM-L\n"); + t->std = V4L2_STD_SECAM_L; + } break; case '-': /* default parameter, do nothing */ diff --git a/drivers/media/video/tuner-simple.c b/drivers/media/video/tuner-simple.c index d832205818f..e0c9fdb9914 100644 --- a/drivers/media/video/tuner-simple.c +++ b/drivers/media/video/tuner-simple.c @@ -233,7 +233,7 @@ static struct tunertype tuners[] = { { "Ymec TVision TVF-5533MF", Philips, NTSC, 16*160.00,16*454.00,0x01,0x02,0x04,0x8e,732}, - /* 60-68 */ + /* 60-69 */ { "Thomson DDT 7611 (ATSC/NTSC)", THOMSON, ATSC, 16*157.25,16*454.00,0x39,0x3a,0x3c,0x8e,732}, { "Tena TNF9533-D/IF/TNF9533-B/DF", Philips, PAL, @@ -252,6 +252,8 @@ static struct tunertype tuners[] = { 16*160.00,16*442.00,0xa1,0xa2,0xa4,0xc8,623 }, { "Philips TUV1236D ATSC/NTSC dual in", Philips, ATSC, 16*157.25,16*454.00,0x01,0x02,0x04,0xce,732 }, + { "Tena TNF 5335 MF", Philips, NTSC, + 16*157.25,16*454.00,0x01,0x02,0x04,0x8e,732 }, }; unsigned const int tuner_count = ARRAY_SIZE(tuners); diff --git a/drivers/media/video/wm8775.c b/drivers/media/video/wm8775.c index 22f28622200..a6936ad74fc 100644 --- a/drivers/media/video/wm8775.c +++ b/drivers/media/video/wm8775.c @@ -5,6 +5,11 @@ * * Based on saa7115 driver * + * Copyright (C) 2005 Hans Verkuil <hverkuil@xs4all.nl> + * - Cleanup + * - V4L2 API update + * - sound fixes + * * 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 @@ -31,7 +36,7 @@ #include <media/audiochip.h> MODULE_DESCRIPTION("wm8775 driver"); -MODULE_AUTHOR("Ulf Eklund"); +MODULE_AUTHOR("Ulf Eklund, Hans Verkuil"); MODULE_LICENSE("GPL"); #define wm8775_err(fmt, arg...) do { \ |